> For the complete documentation index, see [llms.txt](https://docs.perception.cx/perception/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.perception.cx/perception/docs/angel-script/render-api.md).

# Render API

**📐 Constants**

```cpp
const uint8 RR_TOP_LEFT
const uint8 RR_TOP_RIGHT
const uint8 RR_BOTTOM_LEFT
const uint8 RR_BOTTOM_RIGHT
```

> Rectangle-corner rounding flags (bitmask).

```cpp
const int TE_NONE
const int TE_OUTLINE
const int TE_SHADOW
const int TE_GLOW
```

> Text rendering effects.

***

**🎨 Viewport**

```cpp
void get_view(float &out w, float &out h)
float get_view_scale()
double get_fps()
```

* `get_view` — Returns the current viewport width/height in pixels.
* `get_view_scale` — Returns the viewport reference scale (for DPI scaling, etc).
* `get_fps()` — Returns current frames per second.

***

**📏 Basic Shapes**

**Rectangle**

```cpp
void draw_rect(float x,float y,float w,float h,
               uint8 r,uint8 g,uint8 b,uint8 a,
               float thickness, float rounding, uint8 rounding_flags)
```

Draws an **outlined rectangle**.

**Filled Rectangle**

```cpp
void draw_rect_filled(float x,float y,float w,float h,
                      uint8 r,uint8 g,uint8 b,uint8 a,
                      float rounding, uint8 rounding_flags)
```

Draws a **filled rectangle**.

**Line**

```cpp
void draw_line(float x1,float y1,float x2,float y2,
               uint8 r,uint8 g,uint8 b,uint8 a, float thickness)
```

Draws a line between `(x1,y1)` and `(x2,y2)`.

**Arc**

```cpp
void draw_arc(float cx, float cy, float rx, float ry,
              float start_angle_deg, float sweep_angle_deg,
              uint8 r, uint8 g, uint8 b, uint8 a,
              float thickness, bool filled)
```

Draws an arc or pie-slice centered at `(cx, cy)`.

**Circle**

```cpp
void draw_circle(float cx,float cy,float radius,
                 uint8 r,uint8 g,uint8 b,uint8 a,
                 float thickness, bool filled)
```

**Triangle**

```cpp
void draw_triangle(float ax,float ay,float bx,float by,float cx,float cy,
                   uint8 r,uint8 g,uint8 b,uint8 a,
                   float thickness, bool filled)
```

**Polygon**

```cpp
void draw_polygon(const array<float> &in xy_pairs, uint count_pairs,
                  uint8 r,uint8 g,uint8 b,uint8 a,
                  float thickness, bool filled)
```

* `xy_pairs` — `[x0, y0, x1, y1, …]`
* `count_pairs` — optional; set `0` to use full array length.

**Four Corner Gradient**

```cpp
void draw_four_corner_gradient(float x,float y,float w,float h,
    uint8 tlr,uint8 tlg,uint8 tlb,uint8 tla,
    uint8 trr,uint8 trg,uint8 trb,uint8 tra,
    uint8 blr,uint8 blg,uint8 blb,uint8 bla,
    uint8 brr,uint8 brg,uint8 brb,uint8 bra,
    float rounding)
```

Draws a gradient rectangle with individual RGBA colors for each corner.

***

**🖼️ Bitmaps**

```cpp
uint64 create_bitmap(const array<uint8> &in data)
void   draw_bitmap(uint64 bmp, float x,float y,float w,float h,
                   uint8 r,uint8 g,uint8 b,uint8 a, bool rounded)
```

* `create_bitmap` — Creates a bitmap from raw RGBA or compressed bytes. Returns an **encrypted handle** (`uint64`) used for drawing.
* `draw_bitmap` — Draws the bitmap tinted with color `(r,g,b,a)`.

***

**🔤 Fonts & Text**

```cpp
uint64 create_font(const string &in path, float size, bool antialias, 
bool load_color, array<uint> @glyph_ranges = null)
//load color should only be used for fonts that require texture coloring (e.g. emojis)

/* Automatically resolves font paths within the API directory
   (e.g., verdana_custom.ttf or fonts/verdana_custom.ttf).
   The API may also fall back to searching system font locations.
   If a system font shares the same name, it may be loaded instead.
   Use unique font names to avoid naming conflicts. */

uint64 create_font_mem(const string &in label, float size, const array<uint8> &in buf, 
 bool antialias, bool load_color, array<uint> @glyph_ranges = null)

uint64 get_font18() // default font of size 18
uint64 get_font20() // default font of size 20
uint64 get_font24() // default font of size 24
uint64 get_font28() // default font of size 28

// Using custom glyph ranges
array<uint> ranges = {
    0x0020, 0x00FF,   // Basic Latin + Latin-1 Supplement
    0x0400, 0x04FF,   // Cyrillic
    0
};
uint64 font2 = create_font("Arial", 16.0f, true, false, ranges);
```

**Text Drawing**

```cpp
void draw_text(const string &in text, float x,float y,
               uint8 r,uint8 g,uint8 b,uint8 a,
               uint64 font, int effect,
               uint8 er,uint8 eg,uint8 eb,uint8 ea,
               float effect_amount)
```

* `effect` — One of `TE_NONE`, `TE_OUTLINE`, `TE_SHADOW`, `TE_GLOW`.
* `effect_color` — `(er,eg,eb,ea)`.
* `effect_amount` — Intensity scalar.

**Text Metrics**

```cpp
void get_text_size(uint64 font, const string &in text, int maxw, int maxh,
                   float &out w, float &out h)
int get_char_advance(uint64 font, uint wchar32)
```

> Measure text dimensions or a single character's advance width.

***

**✂️ Clipping**

```cpp
void clip_push(float x,float y,float w,float h)
void clip_pop()
```

***

**Example**

```cpp
void draw_ui()
{
    float vw, vh; get_view(vw, vh);
    float cx = vw * 0.5f, cy = vh * 0.5f;

    uint64 font = get_font20();

    draw_rect_filled(cx - 100, cy - 50, 200, 100,
                     40, 40, 40, 255, 8.0f, RR_TOP_LEFT|RR_TOP_RIGHT);

    draw_text("Hello World", cx - 60, cy - 10,
              255,255,255,255, font, TE_SHADOW,
              0,0,0,180, 1.0f);
}
```

***

**🔺 Custom Draw (Shaders & Direct GPU Access)**

The custom draw API gives scripts direct access to the D3D11 GPU pipeline — custom HLSL shaders, vertex buffers, textures, render targets, and all primitive topologies. Custom draw commands respect draw order with all other render functions.

**Constants**

**Topology**

```cpp
const int TOPO_TRIANGLE_LIST    // Default. 3 vertices per triangle.
const int TOPO_TRIANGLE_STRIP   // Shared edges. N vertices = N-2 triangles.
const int TOPO_LINE_LIST        // 2 vertices per line segment.
const int TOPO_LINE_STRIP       // Connected line segments.
const int TOPO_POINT_LIST       // Individual points.
```

**Blend Factors**

```cpp
const int BLEND_ZERO
const int BLEND_ONE
const int BLEND_SRC_ALPHA
const int BLEND_INV_SRC_ALPHA
const int BLEND_DEST_ALPHA
const int BLEND_INV_DEST_ALPHA
const int BLEND_SRC_COLOR
const int BLEND_INV_SRC_COLOR
const int BLEND_DEST_COLOR
const int BLEND_INV_DEST_COLOR
```

**Blend Operations**

```cpp
const int BLEND_OP_ADD
const int BLEND_OP_SUBTRACT
const int BLEND_OP_REV_SUBTRACT
const int BLEND_OP_MIN
const int BLEND_OP_MAX
```

**Vertex Layout Element Types**

```cpp
const int ELEM_FLOAT1       // 4 bytes
const int ELEM_FLOAT2       // 8 bytes
const int ELEM_FLOAT3       // 12 bytes
const int ELEM_FLOAT4       // 16 bytes
const int ELEM_BYTE4_UNORM  // 4 bytes (normalized 0-1)
const int ELEM_UINT1        // 4 bytes
```

**Texture Filter Modes**

```cpp
const int FILTER_POINT
const int FILTER_LINEAR
const int FILTER_ANISOTROPIC
```

**Texture Address Modes**

```cpp
const int ADDRESS_WRAP
const int ADDRESS_CLAMP
const int ADDRESS_MIRROR
const int ADDRESS_BORDER
```

**Shaders**

```cpp
uint64 create_shader(const string &in vs_source, const string &in ps_source,
                     const string &in layout)
void   destroy_shader(uint64 shader)
```

Creates a compiled shader from HLSL source strings. The `layout` parameter is a format string describing the vertex input layout:

```
"POSITION:0:FLOAT2, COLOR:0:FLOAT4"
"POSITION:0:FLOAT2, TEXCOORD:0:FLOAT2"
"POSITION:0:FLOAT3, NORMAL:0:FLOAT3, TEXCOORD:0:FLOAT2"
```

Format: `SEMANTIC:semantic_index:TYPE` separated by commas. Supported types: `FLOAT1`, `FLOAT2`, `FLOAT3`, `FLOAT4`, `BYTE4`, `UINT1`.

Returns `0` on compilation failure. Shader entry point must be `main` for both VS and PS.

**Vertex Buffers**

```cpp
uint64 create_vertex_buffer(uint stride, uint max_vertices, bool dynamic)
void   destroy_vertex_buffer(uint64 vb)
```

* `stride` — Bytes per vertex (must match shader layout).
* `max_vertices` — Maximum number of vertices the buffer can hold.
* `dynamic` — `true` for per-frame updates (typical), `false` for static geometry.

**Constant Buffers**

```cpp
uint64 create_constant_buffer(uint size)
void   destroy_constant_buffer(uint64 cb)
```

Size is automatically aligned to 16 bytes. Bound to both VS and PS at the specified slot.

**Blend States**

```cpp
uint64 create_blend_state(int src, int dst, int op,
                          int src_alpha, int dst_alpha, int op_alpha)
void   destroy_blend_state(uint64 bs)
```

For premultiplied alpha (recommended for overlay rendering):

```cpp
uint64 blend = create_blend_state(BLEND_ONE, BLEND_INV_SRC_ALPHA, BLEND_OP_ADD,
                                   BLEND_ONE, BLEND_INV_SRC_ALPHA, BLEND_OP_ADD);
```

For standard alpha blending:

```cpp
uint64 blend = create_blend_state(BLEND_SRC_ALPHA, BLEND_INV_SRC_ALPHA, BLEND_OP_ADD,
                                   BLEND_ONE, BLEND_INV_SRC_ALPHA, BLEND_OP_ADD);
```

**Samplers**

```cpp
uint64 create_sampler(int filter, int address_u, int address_v)
void   destroy_sampler(uint64 s)
```

Controls how textures are sampled. `FILTER_LINEAR` for smooth scaling, `FILTER_POINT` for pixel-perfect rendering.

**Textures**

```cpp
uint64 create_texture(uint width, uint height, const array<uint8> &in rgba_data)
void   destroy_texture(uint64 tex)
```

Creates a texture from raw RGBA pixel data. The `rgba_data` array must be exactly `width * height * 4` bytes.

**Render Targets**

```cpp
uint64 create_render_target(uint width, uint height)
void   destroy_render_target(uint64 rt)
```

Creates an offscreen render target for multi-pass rendering. Pass `0` for width/height to match the viewport size.

**Drawing**

```cpp
void custom_draw(uint64 shader, uint64 vb,
                 const array<uint8> &in vertex_data, uint vertex_count,
                 int topology,
                 uint64 blend, uint64 sampler, uint64 texture, int tex_slot,
                 uint64 cb, const array<uint8> @cb_data, int cb_slot)
```

The main draw call. Submits custom geometry to the GPU using the specified shader and resources.

* `shader` — Required. Handle from `create_shader`.
* `vb` — Required. Handle from `create_vertex_buffer`.
* `vertex_data` — Raw vertex bytes packed into `array<uint8>`. Must match the shader's layout stride.
* `vertex_count` — Number of vertices to draw.
* `topology` — One of the `TOPO_*` constants.
* `blend` — Blend state handle, or `0` for default blending.
* `sampler` — Sampler handle, or `0` for no sampler.
* `texture` — Texture handle, or `0` for no texture.
* `tex_slot` — Texture/sampler register slot (usually `0`).
* `cb` — Constant buffer handle, or `0` for no constants.
* `cb_data` — Raw constant data as `array<uint8>`, or `null` if no constants.
* `cb_slot` — Constant buffer register slot (usually `0`).

> Custom draw commands respect draw order with all other render functions. If you call `draw_rect_filled`, then `custom_draw`, then `draw_text`, they render in exactly that order.

**Render Target Control**

```cpp
void custom_set_render_target(uint64 rt)
void custom_reset_render_target()
void custom_bind_rt_as_texture(uint64 rt, int slot)
void custom_restore_state()
```

* `custom_set_render_target` — Redirects subsequent `custom_draw` calls to an offscreen render target.
* `custom_reset_render_target` — Switches back to the main backbuffer.
* `custom_bind_rt_as_texture` — Binds a render target's contents as a texture for sampling in a shader.
* `custom_restore_state` — Resets the D3D11 pipeline state. Call after render target operations to ensure normal rendering continues correctly.

**Packing Vertex Data**

Since `custom_draw` takes raw bytes, you need to pack floats into `array<uint8>`. Use `fpToIEEE` to convert floats:

```cpp
void pack_float(array<uint8> &b, int offset, float val)
{
    uint bits = fpToIEEE(val);
    b[offset]     = uint8(bits & 0xFF);
    b[offset + 1] = uint8((bits >> 8) & 0xFF);
    b[offset + 2] = uint8((bits >> 16) & 0xFF);
    b[offset + 3] = uint8((bits >> 24) & 0xFF);
}
```

**Resource Cleanup**

All custom draw resources are automatically destroyed when a script is unloaded. You can also destroy them manually:

```cpp
destroy_shader(shader);
destroy_vertex_buffer(vb);
destroy_constant_buffer(cb);
destroy_blend_state(blend);
destroy_sampler(sampler);
destroy_texture(tex);
destroy_render_target(rt);
destroy_index_buffer(ib);
destroy_depth_stencil_state(ds);
destroy_rasterizer_state(rs);
destroy_compute_shader(cs);
destroy_structured_buffer(sb);
destroy_mesh(mesh);
destroy_depth_buffer(db);
```

***

**🔢 Index Buffers & Indexed Drawing**

```cpp
uint64 create_index_buffer(uint max_indices, bool use_32bit, bool dynamic)
void   destroy_index_buffer(uint64 ib)
```

* `max_indices` — Maximum number of indices the buffer can hold.
* `use_32bit` — `true` for 32-bit indices (`uint`), `false` for 16-bit (`uint16`). Use 32-bit for meshes with more than 65535 vertices.
* `dynamic` — `true` for per-frame updates, `false` for static geometry.

```cpp
void custom_draw_indexed(uint64 shader, uint64 vb,
    const array<uint8> &in vertex_data, uint vertex_count,
    uint64 ib, const array<uint8> &in index_data, uint index_count,
    int topology,
    uint64 blend, uint64 sampler, uint64 texture, int tex_slot,
    uint64 cb, const array<uint8> @cb_data, int cb_slot)
```

Same as `custom_draw` but uses an index buffer to avoid duplicating shared vertices. Useful for cubes, grids, and any geometry with shared edges.

***

**🧊 Depth/Stencil State**

```cpp
uint64 create_depth_stencil_state(bool depth_enable, bool depth_write, int compare_func)
void   destroy_depth_stencil_state(uint64 ds)
void   custom_set_depth_stencil_state(uint64 ds)
```

**Depth Comparison Constants**

```cpp
const int CMP_NEVER
const int CMP_LESS           // Standard 3D depth testing
const int CMP_EQUAL
const int CMP_LESS_EQUAL
const int CMP_GREATER
const int CMP_NOT_EQUAL
const int CMP_GREATER_EQUAL
const int CMP_ALWAYS         // Disable depth test (still writes if depth_write=true)
```

* `depth_enable` — Enable depth testing.
* `depth_write` — Write to depth buffer on pass.
* `compare_func` — One of the `CMP_*` constants.
* `custom_set_depth_stencil_state(0)` — Resets to default (no depth testing).

For solid 3D rendering, use `create_depth_stencil_state(true, true, CMP_LESS)`.

***

**🔳 Rasterizer State**

```cpp
uint64 create_rasterizer_state(int cull_mode, int fill_mode, bool scissor_enable)
void   destroy_rasterizer_state(uint64 rs)
void   custom_set_rasterizer_state(uint64 rs)
```

**Cull/Fill Constants**

```cpp
const int CULL_NONE          // Render both sides
const int CULL_FRONT
const int CULL_BACK          // Default — cull back-facing triangles

const int FILL_SOLID         // Default
const int FILL_WIREFRAME
```

* `scissor_enable` — Typically `true`. Required for clipping to work.
* `custom_set_rasterizer_state(0)` — Resets to default.

For 3D meshes where you want to see all faces (pyramids, etc), use `CULL_NONE`.

***

**📺 Viewport & Texture Binding**

```cpp
void custom_set_viewport(float x, float y, float w, float h)
void custom_reset_viewport()
void custom_bind_texture(uint64 texture, uint64 sampler, int slot)
```

* `custom_set_viewport` — Restricts rendering to a sub-region of the screen. Use before `draw_mesh` or `custom_draw` to confine 3D rendering to a panel.
* `custom_reset_viewport` — Restores the full viewport.
* `custom_bind_texture` — Binds a texture + sampler to a slot, persisting across subsequent draw calls. Pass `0` for texture to bind the backbuffer capture result.

***

**💻 Compute Shaders**

```cpp
uint64 create_compute_shader(const string &in cs_source)
void   destroy_compute_shader(uint64 cs)
void   dispatch_compute(uint64 cs, uint x, uint y, uint z)
```

Creates and dispatches a compute shader (cs\_5\_0). Entry point must be `main`. Thread group counts `(x, y, z)` are passed to `Dispatch()`.

Compute shaders are dispatched as state-only commands — no geometry is drawn. Use structured buffers for CS input/output.

**Bind Stage Constants**

```cpp
const int STAGE_VS   // Vertex shader (0)
const int STAGE_PS   // Pixel shader (1)
const int STAGE_CS   // Compute shader (2)
```

***

**📊 Structured Buffers**

```cpp
uint64 create_structured_buffer(uint element_size, uint element_count, bool cpu_write, bool gpu_write)
void   destroy_structured_buffer(uint64 sb)
void   update_structured_buffer(uint64 sb, const array<uint8> &in data)
void   bind_structured_buffer(uint64 sb, int slot, int stage)
```

* `element_size` — Size of each element in bytes (typically 16 for `float4`).
* `element_count` — Number of elements.
* `cpu_write` — `true` to allow CPU updates via `update_structured_buffer`. Creates SRV only.
* `gpu_write` — `true` to allow GPU writes from compute shaders. Creates both SRV and UAV.
* `bind_structured_buffer` — Binds the buffer to a shader stage. CS stage with `gpu_write` binds as UAV, otherwise SRV. Use `STAGE_VS`, `STAGE_PS`, or `STAGE_CS`.

**Example: GPU particle buffer**

```cpp
// Create: 16 bytes per particle (float4), 1024 particles, GPU-writable
uint64 sb = create_structured_buffer(16, 1024, false, true);

// In compute shader: bind as UAV for writing
bind_structured_buffer(sb, 0, STAGE_CS);
dispatch_compute(cs, 16, 1, 1);  // 16 groups × 64 threads = 1024 particles

// In pixel shader: bind as SRV for reading
bind_structured_buffer(sb, 0, STAGE_PS);
```

***

**📷 Backbuffer Capture**

```cpp
void capture_backbuffer(int slot)
```

Captures the current backbuffer contents to a staging texture and binds it as a shader resource at the specified slot. Use with a custom pixel shader to create post-processing effects like bloom, blur, color grading, or screen-space reflections.

```cpp
capture_backbuffer(0);
custom_bind_texture(0, sampler, 0);  // texture=0 uses the captured backbuffer
custom_draw(post_fx_shader, vb, fullscreen_quad, 6, TOPO_TRIANGLE_LIST, ...);
```

***

**🖼️ Texture Loading**

```cpp
uint64 load_texture_mem(const array<uint8> &in data)
uint64 load_texture(const string &in path)
void   get_texture_info(uint64 tex, float &out w, float &out h)
```

* `load_texture_mem` — Decodes PNG, JPG, BMP, TGA, or GIF from a byte array. Returns a texture handle (same type as `create_texture`).
* `load_texture` — Loads an image file from disk. Tries the script directory first, then the absolute path.
* `get_texture_info` — Returns the texture dimensions.
* Destroy with `destroy_texture(handle)`.

***

**🏔️ Mesh Loading (OBJ)**

```cpp
uint64 load_mesh_mem(const array<uint8> &in data)
uint64 load_mesh(const string &in path)
void   get_mesh_info(uint64 mesh, float &out vert_count, float &out index_count,
                     float &out min_x, float &out min_y, float &out min_z,
                     float &out max_x, float &out max_y, float &out max_z)
void   destroy_mesh(uint64 mesh)
```

Parses Wavefront OBJ format with support for positions (`v`), normals (`vn`), texture coordinates (`vt`), faces with 3+ vertices (auto-triangulated), and negative indices.

**Fixed vertex layout:** `POSITION:0:FLOAT3, NORMAL:0:FLOAT3, TEXCOORD:0:FLOAT2` — 32 bytes per vertex. Shaders used with loaded meshes must match this layout.

* `get_mesh_info` — Returns vertex/index counts and the axis-aligned bounding box.
* `load_mesh` — Loads from disk, tries script directory first.

***

**🔺 Drawing Meshes**

```cpp
void draw_mesh(uint64 mesh, uint64 shader, int topology,
               uint64 blend, uint64 sampler, uint64 texture, int tex_slot,
               uint64 cb, const array<uint8> @cb_data, int cb_slot)
```

Convenience draw call for loaded or procedural meshes. Binds the mesh's internal vertex/index buffers and calls `DrawIndexed` in one step. Pass `0` for optional handles (sampler, texture, cb). The shader layout must match the mesh's vertex format.

**Example: render a lit OBJ mesh**

```cpp
// Create mesh and shader
uint64 mesh = load_mesh("model.obj");
uint64 shader = create_shader(vs_3d, ps_lit,
    "POSITION:0:FLOAT3, NORMAL:0:FLOAT3, TEXCOORD:0:FLOAT2");
uint64 cb_mvp = create_constant_buffer(64);

// Each frame: build MVP matrix, draw
custom_set_viewport(x, y, w, h);
custom_set_rasterizer_state(rs_nocull);
custom_set_depth_stencil_state(ds_off);
draw_mesh(mesh, shader, TOPO_TRIANGLE_LIST, blend, 0, 0, 0, cb_mvp, mvp_data, 0);
custom_reset_viewport();
custom_set_rasterizer_state(0);
custom_set_depth_stencil_state(0);
custom_restore_state();
```

***

**🌊 Depth Buffers & 3D Rendering**

```cpp
uint64 create_depth_buffer(uint width, uint height)
void   destroy_depth_buffer(uint64 db)
void   custom_set_render_target_ext(uint64 rt, uint64 depth_buffer)
void   custom_clear_render_target(uint64 rt, float r, float g, float b, float a)
void   custom_clear_depth_buffer(uint64 db)
```

* `create_depth_buffer` — Creates a D24S8 depth/stencil buffer. Pass `0, 0` to match viewport size.
* `custom_set_render_target_ext` — Binds a render target with an optional depth buffer for proper 3D occlusion. Auto-clears both, sets viewport and scissor to RT dimensions. Pass `0` for depth buffer to use color-only (same behavior as `custom_set_render_target`).
* `custom_clear_render_target` — Clears a render target to a specific color without re-binding it.
* `custom_clear_depth_buffer` — Clears depth to 1.0 and stencil to 0.

**Example: solid 3D scene with depth**

```cpp
uint64 rt = create_render_target(400, 300);
uint64 db = create_depth_buffer(400, 300);
uint64 ds = create_depth_stencil_state(true, true, CMP_LESS);

// Render pass
custom_set_render_target_ext(rt, db);
custom_clear_render_target(rt, 0, 0, 0, 1);
custom_clear_depth_buffer(db);
custom_set_depth_stencil_state(ds);
custom_set_rasterizer_state(rs_nocull);
draw_mesh(mesh1, shader, TOPO_TRIANGLE_LIST, blend, 0, 0, 0, cb, mvp1_data, 0);
draw_mesh(mesh2, shader, TOPO_TRIANGLE_LIST, blend, 0, 0, 0, cb, mvp2_data, 0);
custom_reset_render_target();
custom_set_rasterizer_state(0);
custom_set_depth_stencil_state(0);

// Blit result to screen
custom_bind_rt_as_texture(rt, 0);
custom_draw(blit_shader, vb, quad, 6, TOPO_TRIANGLE_LIST, blend, sampler, 0, 0, cb, screen_cb, 0);
custom_restore_state();
```

***

**🎛️ Multi-CB Binding**

```cpp
void custom_bind_constant_buffer(uint64 cb, const array<uint8> &in data, int slot, int stage)
```

Binds a constant buffer to a specific register slot and shader stage, independently of draw calls. This persists across subsequent draws until changed. Use `STAGE_VS`, `STAGE_PS`, or `STAGE_CS`.

This enables multi-buffer shader setups where camera data lives on `b0`, material properties on `b1`, and lighting on `b2`:

```cpp
custom_bind_constant_buffer(cb_camera, camera_data, 0, STAGE_VS);   // b0 in VS
custom_bind_constant_buffer(cb_material, mat_data, 1, STAGE_PS);    // b1 in PS
custom_bind_constant_buffer(cb_light, light_data, 2, STAGE_PS);     // b2 in PS
draw_mesh(mesh, shader, TOPO_TRIANGLE_LIST, blend, 0, 0, 0, 0, null, 0);
```

Note: when using `custom_bind_constant_buffer` for the MVP, pass `0` for the `cb` parameter of `draw_mesh` to avoid it overwriting your manually bound buffers.

***

**🧱 Procedural Mesh**

```cpp
uint64 create_mesh_raw(const array<uint8> &in vertex_data, uint vertex_count, uint stride,
                       const array<uint8> &in index_data, uint index_count, bool use_32bit)
float  get_mesh_stride(uint64 mesh)
```

* `create_mesh_raw` — Builds a mesh from raw vertex and index byte arrays with any vertex layout. The shader must match the stride. Use for procedural terrain, generated geometry, or particle quads.
* `get_mesh_stride` — Returns the vertex stride in bytes (32 for OBJ-loaded meshes).
* Destroy with `destroy_mesh(handle)`.

The vertex data can use any layout — it's not restricted to the OBJ format's position+normal+uv.

***

**🎨 Dynamic Texture Update**

```cpp
void custom_update_texture(uint64 tex, uint x, uint y, uint w, uint h,
                           const array<uint8> &in rgba_data)
```

Performs a partial update of an existing texture. The `rgba_data` must be exactly `w * h * 4` bytes. Use for dynamic sprite sheets, minimaps, procedural atlases, or any per-frame texture content.

```cpp
// Update an 8x8 region at position (10, 10)
array<uint8> pixels(8 * 8 * 4);
// ... fill pixels ...
custom_update_texture(tex, 10, 10, 8, 8, pixels);
```

***

**🌌 Example: Nebula Shader**

```cpp
string vs_src =
"cbuffer cb : register(b0) { float2 screen; float time; float pad; };\n"
"struct vi { float2 pos : POSITION; float2 uv : TEXCOORD0; };\n"
"struct vo { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float time : TEXCOORD1; float2 res : TEXCOORD2; };\n"
"vo main(vi i) {\n"
"    vo o;\n"
"    o.pos = float4(i.pos.x / screen.x * 2.0 - 1.0,\n"
"                   1.0 - i.pos.y / screen.y * 2.0, 0.0, 1.0);\n"
"    o.uv = i.uv;\n"
"    o.time = time;\n"
"    o.res = screen;\n"
"    return o;\n"
"}\n";

string ps_src =
"struct vo { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float time : TEXCOORD1; float2 res : TEXCOORD2; };\n"
"\n"
"float hash(float2 p) {\n"
"    float3 p3 = frac(float3(p.xyx) * 0.1031);\n"
"    p3 += dot(p3, p3.yzx + 33.33);\n"
"    return frac((p3.x + p3.y) * p3.z);\n"
"}\n"
"\n"
"float noise(float2 p) {\n"
"    float2 i = floor(p);\n"
"    float2 f = frac(p);\n"
"    f = f * f * (3.0 - 2.0 * f);\n"
"    float a = hash(i);\n"
"    float b = hash(i + float2(1.0, 0.0));\n"
"    float c = hash(i + float2(0.0, 1.0));\n"
"    float d = hash(i + float2(1.0, 1.0));\n"
"    return lerp(lerp(a, b, f.x), lerp(c, d, f.x), f.y);\n"
"}\n"
"\n"
"float fbm(float2 p) {\n"
"    float v = 0.0;\n"
"    float a = 0.5;\n"
"    float2x2 rot = float2x2(0.8, 0.6, -0.6, 0.8);\n"
"    for (int i = 0; i < 5; i++) {\n"
"        v += a * noise(p);\n"
"        p = mul(rot, p) * 2.0;\n"
"        a *= 0.5;\n"
"    }\n"
"    return v;\n"
"}\n"
"\n"
"float4 main(vo i) : SV_TARGET {\n"
"    float2 uv = i.uv;\n"
"    float t = i.time * 0.25;\n"
"\n"
"    float2 p = uv * 3.0;\n"
"    float f1 = fbm(p + float2(t * 0.7, t * 0.4));\n"
"    float f2 = fbm(p + float2(f1 * 1.5 + t * 0.1, f1 * 1.2 - t * 0.2));\n"
"    float f3 = fbm(p + float2(f2 * 1.8 - t * 0.3, f2 * 0.9 + t * 0.5));\n"
"\n"
"    float3 c1 = float3(0.02, 0.01, 0.08);\n"
"    float3 c2 = float3(0.08, 0.3, 0.7);\n"
"    float3 c3 = float3(0.5, 0.08, 0.75);\n"
"    float3 c4 = float3(0.0, 0.7, 0.5);\n"
"\n"
"    float3 col = c1;\n"
"    col = lerp(col, c2, smoothstep(0.0, 0.6, f1));\n"
"    col = lerp(col, c3, smoothstep(0.2, 0.8, f2));\n"
"    col = lerp(col, c4, smoothstep(0.3, 0.7, f3 * f1));\n"
"\n"
"    col += float3(0.3, 0.15, 0.5) * pow(f3, 3.0) * 1.2;\n"
"\n"
"    float vignette = 1.0 - length((uv - 0.5) * 1.4);\n"
"    vignette = smoothstep(0.0, 0.7, vignette);\n"
"    col *= vignette;\n"
"\n"
"    float edge = smoothstep(0.0, 0.06, uv.x) * smoothstep(0.0, 0.06, uv.y)\n"
"               * smoothstep(0.0, 0.06, 1.0 - uv.x) * smoothstep(0.0, 0.06, 1.0 - uv.y);\n"
"\n"
"    float alpha = saturate(length(col) * 1.2) * edge * 0.9;\n"
"    return float4(col * alpha, alpha);\n"
"}\n";

uint64 g_shader = 0;
uint64 g_vb = 0;
uint64 g_cb = 0;
uint64 g_blend = 0;
int g_cb_id = 0;
float g_time = 0.0f;

void pack_float(array<uint8> &buf, int offset, float val)
{
    uint bits = fpToIEEE(val);
    buf[offset + 0] = uint8(bits & 0xFF);
    buf[offset + 1] = uint8((bits >> 8) & 0xFF);
    buf[offset + 2] = uint8((bits >> 16) & 0xFF);
    buf[offset + 3] = uint8((bits >> 24) & 0xFF);
}

void pack_quad(array<uint8> &buf, int offset,
    float x0, float y0, float x1, float y1)
{
    int o = offset;
    pack_float(buf, o,    x0); pack_float(buf, o+4,  y0);
    pack_float(buf, o+8,  0.0f); pack_float(buf, o+12, 0.0f); o += 16;
    pack_float(buf, o,    x1); pack_float(buf, o+4,  y0);
    pack_float(buf, o+8,  1.0f); pack_float(buf, o+12, 0.0f); o += 16;
    pack_float(buf, o,    x0); pack_float(buf, o+4,  y1);
    pack_float(buf, o+8,  0.0f); pack_float(buf, o+12, 1.0f); o += 16;
    pack_float(buf, o,    x1); pack_float(buf, o+4,  y0);
    pack_float(buf, o+8,  1.0f); pack_float(buf, o+12, 0.0f); o += 16;
    pack_float(buf, o,    x1); pack_float(buf, o+4,  y1);
    pack_float(buf, o+8,  1.0f); pack_float(buf, o+12, 1.0f); o += 16;
    pack_float(buf, o,    x0); pack_float(buf, o+4,  y1);
    pack_float(buf, o+8,  0.0f); pack_float(buf, o+12, 1.0f);
}

void on_frame(int cb_id, int data)
{
    double fps = get_fps();
    if (fps > 0.0)
        g_time += float(1.0 / fps);

    float sw, sh;
    get_view(sw, sh);

    float panel_w = 440.0f;
    float panel_h = 270.0f;
    float panel_x = (sw - panel_w) * 0.5f;
    float panel_y = (sh - panel_h) * 0.5f - 60.0f;

    draw_rect_filled(panel_x - 2, panel_y - 2, panel_w + 4, panel_h + 4,
        120, 80, 200, 35, 12.0f,
        RR_TOP_LEFT | RR_TOP_RIGHT | RR_BOTTOM_LEFT | RR_BOTTOM_RIGHT);

    array<uint8> verts(6 * 16);
    pack_quad(verts, 0, panel_x, panel_y, panel_x + panel_w, panel_y + panel_h);

    array<uint8> cb_data(16);
    pack_float(cb_data, 0,  sw);
    pack_float(cb_data, 4,  sh);
    pack_float(cb_data, 8,  g_time);
    pack_float(cb_data, 12, 0.0f);

    custom_draw(g_shader, g_vb, verts, 6, TOPO_TRIANGLE_LIST,
        g_blend, 0, 0, 0,
        g_cb, cb_data, 0);

    draw_rect(panel_x - 1, panel_y - 1, panel_w + 2, panel_h + 2,
        255, 255, 255, 25, 1.0f, 12.0f,
        RR_TOP_LEFT | RR_TOP_RIGHT | RR_BOTTOM_LEFT | RR_BOTTOM_RIGHT);

    uint64 font = get_font18();
    float tw, th;

    string title = "Perception.cx";
    get_text_size(font, title, -1, -1, tw, th);
    draw_text(title, panel_x + (panel_w - tw) * 0.5f, panel_y + panel_h + 12,
        200, 180, 255, 190, font, TE_NONE, 0, 0, 0, 0, 0.0f);

    string info = "FPS: " + formatFloat(fps, "", 0, 1);
    get_text_size(font, info, -1, -1, tw, th);
    draw_text(info, panel_x + (panel_w - tw) * 0.5f, panel_y + panel_h + 32,
        140, 140, 140, 130, font, TE_NONE, 0, 0, 0, 0, 0.0f);
}

int main()
{
    g_shader = create_shader(vs_src, ps_src, "POSITION:0:FLOAT2, TEXCOORD:0:FLOAT2");

    if (g_shader == 0)
    {
        log_error("shader compilation failed");
        return -1;
    }

    g_vb    = create_vertex_buffer(16, 64, true);
    g_cb    = create_constant_buffer(16);
    g_blend = create_blend_state(BLEND_ONE, BLEND_INV_SRC_ALPHA, BLEND_OP_ADD,
                                  BLEND_ONE, BLEND_INV_SRC_ALPHA, BLEND_OP_ADD);

    if (g_vb == 0 || g_cb == 0 || g_blend == 0)
    {
        log_error("resource creation failed");
        return -1;
    }

    log("custom shader test loaded");
    g_cb_id = register_callback(@on_frame, 1, 0);
    return 1;
}

void on_unload()
{
    if (g_cb_id > 0)
        unregister_callback(g_cb_id);

    if (g_shader != 0) destroy_shader(g_shader);
    if (g_vb != 0)     destroy_vertex_buffer(g_vb);
    if (g_cb != 0)     destroy_constant_buffer(g_cb);
    if (g_blend != 0)  destroy_blend_state(g_blend);

    log("custom shader test unloaded");
}
```

***

**🧪 Example: Full API Test**

```cpp
string vs_uv =
"cbuffer cb:register(b0){float2 screen;float time;float pad;};\n"
"struct vi{float2 pos:POSITION;float2 uv:TEXCOORD0;};\n"
"struct vo{float4 pos:SV_POSITION;float2 uv:TEXCOORD0;float time:TEXCOORD1;};\n"
"vo main(vi i){vo o;\n"
"  o.pos=float4(i.pos.x/screen.x*2-1,1-i.pos.y/screen.y*2,0,1);\n"
"  o.uv=i.uv;o.time=time;return o;}\n";

string ps_nebula =
"struct vo{float4 pos:SV_POSITION;float2 uv:TEXCOORD0;float time:TEXCOORD1;};\n"
"float hash(float2 p){float3 p3=frac(float3(p.xyx)*0.1031);p3+=dot(p3,p3.yzx+33.33);return frac((p3.x+p3.y)*p3.z);}\n"
"float noise(float2 p){float2 i=floor(p),f=frac(p);f=f*f*(3-2*f);\n"
"  return lerp(lerp(hash(i),hash(i+float2(1,0)),f.x),lerp(hash(i+float2(0,1)),hash(i+float2(1,1)),f.x),f.y);}\n"
"float fbm(float2 p){float v=0,a=0.5;float2x2 r={0.8,0.6,-0.6,0.8};\n"
"  for(int i=0;i<5;i++){v+=a*noise(p);p=mul(r,p)*2;a*=0.5;}return v;}\n"
"float4 main(vo i):SV_TARGET{\n"
"  float t=i.time*0.25;float2 p=i.uv*3;\n"
"  float f1=fbm(p+float2(t*0.7,t*0.4));\n"
"  float f2=fbm(p+float2(f1*1.5+t*0.1,f1*1.2-t*0.2));\n"
"  float f3=fbm(p+float2(f2*1.8-t*0.3,f2*0.9+t*0.5));\n"
"  float3 c=float3(0.02,0.01,0.08);\n"
"  c=lerp(c,float3(0.08,0.3,0.7),smoothstep(0,0.6,f1));\n"
"  c=lerp(c,float3(0.5,0.08,0.75),smoothstep(0.2,0.8,f2));\n"
"  c=lerp(c,float3(0,0.7,0.5),smoothstep(0.3,0.7,f3*f1));\n"
"  c+=float3(0.3,0.15,0.5)*pow(f3,3)*1.2;\n"
"  float v=smoothstep(0,0.7,1-length((i.uv-0.5)*1.4));\n"
"  float e=smoothstep(0,0.05,i.uv.x)*smoothstep(0,0.05,i.uv.y)*smoothstep(0,0.05,1-i.uv.x)*smoothstep(0,0.05,1-i.uv.y);\n"
"  float a=saturate(length(c)*1.2)*v*e*0.92;\n"
"  return float4(c*a,a);}\n";

string ps_tex =
"Texture2D tex0:register(t0);SamplerState samp0:register(s0);\n"
"struct vo{float4 pos:SV_POSITION;float2 uv:TEXCOORD0;float time:TEXCOORD1;};\n"
"float4 main(vo i):SV_TARGET{float4 c=tex0.Sample(samp0,i.uv);return float4(c.rgb*c.a,c.a);}\n";

string vs_col =
"cbuffer cb:register(b0){float2 screen;float time;float pad;};\n"
"struct vi{float2 pos:POSITION;float4 col:COLOR;};\n"
"struct vo{float4 pos:SV_POSITION;float4 col:COLOR;};\n"
"vo main(vi i){vo o;o.pos=float4(i.pos.x/screen.x*2-1,1-i.pos.y/screen.y*2,0,1);o.col=i.col;return o;}\n";

string ps_col =
"struct vo{float4 pos:SV_POSITION;float4 col:COLOR;};\n"
"float4 main(vo i):SV_TARGET{return float4(i.col.rgb*i.col.a,i.col.a);}\n";

uint64 g_sh_nebula=0, g_sh_tex=0, g_sh_col=0;
uint64 g_vb_uv=0, g_vb_col=0, g_cb=0, g_blend=0;
uint64 g_samp_linear=0, g_samp_point=0;
uint64 g_texture=0, g_rt=0;
int g_cb_id=0;
float g_time=0;

void pf(array<uint8> &b,int o,float v){uint bits=fpToIEEE(v);b[o]=uint8(bits&0xFF);b[o+1]=uint8((bits>>8)&0xFF);b[o+2]=uint8((bits>>16)&0xFF);b[o+3]=uint8((bits>>24)&0xFF);}

int vu(array<uint8> &b,int o,float x,float y,float u,float v){pf(b,o,x);pf(b,o+4,y);pf(b,o+8,u);pf(b,o+12,v);return o+16;}
int vc(array<uint8> &b,int o,float x,float y,float r,float g,float bl,float a){pf(b,o,x);pf(b,o+4,y);pf(b,o+8,r);pf(b,o+12,g);pf(b,o+16,bl);pf(b,o+20,a);return o+24;}

int qu(array<uint8> &b,int o,float x0,float y0,float x1,float y1){
    o=vu(b,o,x0,y0,0,0);o=vu(b,o,x1,y0,1,0);o=vu(b,o,x0,y1,0,1);
    o=vu(b,o,x1,y0,1,0);o=vu(b,o,x1,y1,1,1);o=vu(b,o,x0,y1,0,1);return o;}

array<uint8> mcb(float sw,float sh){array<uint8> c(16);pf(c,0,sw);pf(c,4,sh);pf(c,8,g_time);pf(c,12,0);return c;}

void label(uint64 font,float cx,float y,const string &in text,uint8 a=180){
    float tw,th; get_text_size(font,text,-1,-1,tw,th);
    draw_text(text,cx-tw*0.5f,y,190,175,235,a,font,TE_NONE,0,0,0,0,0);
}

void sublabel(uint64 font,float cx,float y,const string &in text){
    float tw,th; get_text_size(font,text,-1,-1,tw,th);
    draw_text(text,cx-tw*0.5f,y,140,135,160,140,font,TE_NONE,0,0,0,0,0);
}

void border(float x,float y,float w,float h,float r=6){
    draw_rect(x-1,y-1,w+2,h+2,255,255,255,25,1,r,RR_TOP_LEFT|RR_TOP_RIGHT|RR_BOTTOM_LEFT|RR_BOTTOM_RIGHT);
}

void on_frame(int cb_id,int data)
{
    double fps=get_fps();
    if(fps>0) g_time+=float(1.0/fps);

    float sw,sh; get_view(sw,sh);
    array<uint8> cb_data=mcb(sw,sh);
    uint64 font=get_font18();

    float total_w=780, total_h=340;
    float ox=(sw-total_w)*0.5f;
    float oy=(sh-total_h)*0.5f;
    float row1_y=oy+30;
    float gap=15;

    // title
    draw_rect_filled(ox-10,oy-10,total_w+20,total_h+20,20,15,35,140,12,
        RR_TOP_LEFT|RR_TOP_RIGHT|RR_BOTTOM_LEFT|RR_BOTTOM_RIGHT);
    border(ox-10,oy-10,total_w+20,total_h+20,12);
    label(font,ox+total_w*0.5f,oy+2,"Perception.cx  -  Custom Draw API Test",220);

    float p1x=ox, p1y=row1_y, p1w=300, p1h=200;
    {
        custom_set_render_target(g_rt);

        array<uint8> rt_cb(16);
        pf(rt_cb,0,320);pf(rt_cb,4,200);pf(rt_cb,8,g_time);pf(rt_cb,12,0);

        array<uint8> rv(6*16);int o=0;
        o=qu(rv,o,0,0,320,200);

        custom_draw(g_sh_nebula,g_vb_uv,rv,6,TOPO_TRIANGLE_LIST,g_blend,0,0,0,g_cb,rt_cb,0);
        custom_reset_render_target();

        custom_bind_rt_as_texture(g_rt,0);
        array<uint8> sv(6*16);o=0;
        o=qu(sv,o,p1x,p1y,p1x+p1w,p1y+p1h);
        custom_draw(g_sh_tex,g_vb_uv,sv,6,TOPO_TRIANGLE_LIST,g_blend,g_samp_linear,0,0,g_cb,cb_data,0);
        custom_restore_state();

        border(p1x,p1y,p1w,p1h);
        label(font,p1x+p1w*0.5f,p1y+p1h+6,"Render Target -> Blit");
        sublabel(font,p1x+p1w*0.5f,p1y+p1h+24,"set_rt / reset_rt / bind_rt / restore");
    }

    float p2x=p1x+p1w+gap, p2y=row1_y, p2w=150, p2h=200;
    {
        float half=p2w*0.5f;

        array<uint8> lv(6*16);int o=0;
        o=qu(lv,o,p2x,p2y,p2x+half,p2y+p2h);
        custom_draw(g_sh_tex,g_vb_uv,lv,6,TOPO_TRIANGLE_LIST,g_blend,g_samp_linear,g_texture,0,g_cb,cb_data,0);

        array<uint8> rv(6*16);o=0;
        o=qu(rv,o,p2x+half,p2y,p2x+p2w,p2y+p2h);
        custom_draw(g_sh_tex,g_vb_uv,rv,6,TOPO_TRIANGLE_LIST,g_blend,g_samp_point,g_texture,0,g_cb,cb_data,0);

        border(p2x,p2y,p2w,p2h,0);
        draw_line(p2x+half,p2y,p2x+half,p2y+p2h,255,255,255,40,1);

        label(font,p2x+p2w*0.5f,p2y+p2h+6,"LINEAR | POINT");
        sublabel(font,p2x+p2w*0.5f,p2y+p2h+24,"texture + sampler");
    }
    
    float p3x=p2x+p2w+gap, p3y=row1_y, p3w=300, p3h=200;
    {
        draw_rect_filled(p3x,p3y,p3w,p3h,15,12,30,100,6,
            RR_TOP_LEFT|RR_TOP_RIGHT|RR_BOTTOM_LEFT|RR_BOTTOM_RIGHT);
        border(p3x,p3y,p3w,p3h);

        float sec=p3h/5.0f;
        float ty=p3y;
        float draw_x=p3x+p3w*0.45f;
        float draw_w=p3w*0.48f;

        // TRIANGLE_LIST
        {
            array<uint8> v(3*24);int o=0;
            float cx=draw_x+draw_w*0.5f;
            o=vc(v,o,cx,ty+6,1,0.2f,0.2f,1);
            o=vc(v,o,cx+16,ty+sec-6,0.2f,1,0.2f,1);
            o=vc(v,o,cx-16,ty+sec-6,0.2f,0.2f,1,1);
            custom_draw(g_sh_col,g_vb_col,v,3,TOPO_TRIANGLE_LIST,g_blend,0,0,0,g_cb,cb_data,0);
            draw_text("TRIANGLE_LIST",p3x+8,ty+sec*0.5f-7,130,130,130,160,font,TE_NONE,0,0,0,0,0);
            ty+=sec;
        }

        // TRIANGLE_STRIP
        {
            array<uint8> v(4*24);int o=0;
            float lx=draw_x+draw_w*0.3f;
            o=vc(v,o,lx,ty+6,1,0.6f,0,1);
            o=vc(v,o,lx+30,ty+6,1,0.9f,0.2f,1);
            o=vc(v,o,lx+8,ty+sec-6,0.9f,1,0.3f,1);
            o=vc(v,o,lx+38,ty+sec-6,1,1,0.5f,1);
            custom_draw(g_sh_col,g_vb_col,v,4,TOPO_TRIANGLE_STRIP,g_blend,0,0,0,g_cb,cb_data,0);
            draw_text("TRIANGLE_STRIP",p3x+8,ty+sec*0.5f-7,130,130,130,160,font,TE_NONE,0,0,0,0,0);
            ty+=sec;
        }

        // LINE_LIST
        {
            array<uint8> v(4*24);int o=0;
            float lx=draw_x+draw_w*0.2f;
            o=vc(v,o,lx,ty+8,0,1,1,1);
            o=vc(v,o,lx+draw_w*0.5f,ty+sec-8,0,1,1,1);
            o=vc(v,o,lx+15,ty+8,1,0,1,1);
            o=vc(v,o,lx+draw_w*0.5f+15,ty+sec-8,1,0,1,1);
            custom_draw(g_sh_col,g_vb_col,v,4,TOPO_LINE_LIST,g_blend,0,0,0,g_cb,cb_data,0);
            draw_text("LINE_LIST",p3x+8,ty+sec*0.5f-7,130,130,130,160,font,TE_NONE,0,0,0,0,0);
            ty+=sec;
        }

        // LINE_STRIP (animated)
        {
            array<uint8> v(8*24);int o=0;
            for(int i=0;i<8;i++){
                float f=float(i)/7.0f;
                float lx=draw_x+draw_w*0.15f+f*draw_w*0.7f;
                float ly=ty+sec*0.5f+sin(g_time*3+f*6.28f)*12;
                o=vc(v,o,lx,ly,f,1-f,0.5f,1);
            }
            custom_draw(g_sh_col,g_vb_col,v,8,TOPO_LINE_STRIP,g_blend,0,0,0,g_cb,cb_data,0);
            draw_text("LINE_STRIP",p3x+8,ty+sec*0.5f-7,130,130,130,160,font,TE_NONE,0,0,0,0,0);
            ty+=sec;
        }

        // POINT_LIST
        {
            array<uint8> v(12*24);int o=0;
            for(int i=0;i<12;i++){
                float f=float(i)/11.0f;
                float px=draw_x+draw_w*0.15f+f*draw_w*0.7f;
                float py=ty+sec*0.5f+sin(f*6.28f*2+g_time)*6;
                o=vc(v,o,px,py,1,1,1,0.9f);
            }
            custom_draw(g_sh_col,g_vb_col,v,12,TOPO_POINT_LIST,g_blend,0,0,0,g_cb,cb_data,0);
            draw_text("POINT_LIST",p3x+8,ty+sec*0.5f-7,130,130,130,160,font,TE_NONE,0,0,0,0,0);
        }

        label(font,p3x+p3w*0.5f,p3y+p3h+6,"All Topologies");
        sublabel(font,p3x+p3w*0.5f,p3y+p3h+24,"5 primitive types + per-vertex color");
    }
    
    string s="FPS: "+formatFloat(fps,"",0,1)+"   |   3 shaders   2 VBs   1 CB   1 blend   2 samplers   1 texture   1 RT   5 topologies";
    sublabel(font,ox+total_w*0.5f,oy+total_h-12,s);
}

array<uint8> make_checker(uint sz,uint tile){
    array<uint8> d(sz*sz*4);
    for(uint y=0;y<sz;y++){
        for(uint x=0;x<sz;x++){
            uint i=(y*sz+x)*4;
            bool w=(((x/tile)+(y/tile))%2)==0;
            d[i]=w?230:30;d[i+1]=w?220:30;d[i+2]=w?250:70;d[i+3]=255;
        }
    }
    return d;
}

int main()
{
    g_sh_nebula=create_shader(vs_uv,ps_nebula,"POSITION:0:FLOAT2, TEXCOORD:0:FLOAT2");
    g_sh_tex=create_shader(vs_uv,ps_tex,"POSITION:0:FLOAT2, TEXCOORD:0:FLOAT2");
    g_sh_col=create_shader(vs_col,ps_col,"POSITION:0:FLOAT2, COLOR:0:FLOAT4");
    if(g_sh_nebula==0||g_sh_tex==0||g_sh_col==0){log_error("shader fail");return 1;}

    g_vb_uv=create_vertex_buffer(16,128,true);
    g_vb_col=create_vertex_buffer(24,128,true);
    if(g_vb_uv==0||g_vb_col==0){log_error("vb fail");return 1;}

    g_cb=create_constant_buffer(16);
    if(g_cb==0){log_error("cb fail");return 1;}

    g_blend=create_blend_state(BLEND_ONE,BLEND_INV_SRC_ALPHA,BLEND_OP_ADD,BLEND_ONE,BLEND_INV_SRC_ALPHA,BLEND_OP_ADD);
    if(g_blend==0){log_error("blend fail");return 1;}

    g_samp_linear=create_sampler(FILTER_LINEAR,ADDRESS_CLAMP,ADDRESS_CLAMP);
    g_samp_point=create_sampler(FILTER_POINT,ADDRESS_WRAP,ADDRESS_WRAP);
    if(g_samp_linear==0||g_samp_point==0){log_error("sampler fail");return 1;}

    array<uint8> checker=make_checker(8,2);
    g_texture=create_texture(8,8,checker);
    if(g_texture==0){log_error("texture fail");return 1;}

    g_rt=create_render_target(320,200);
    if(g_rt==0){log_error("rt fail");return 1;}

    log("custom draw test loaded — all resources created");
    g_cb_id=register_callback(@on_frame,1,0);
    return 1;
}

void on_unload()
{
    if(g_cb_id>0) unregister_callback(g_cb_id);
    if(g_sh_nebula!=0) destroy_shader(g_sh_nebula);
    if(g_sh_tex!=0) destroy_shader(g_sh_tex);
    if(g_sh_col!=0) destroy_shader(g_sh_col);
    if(g_vb_uv!=0) destroy_vertex_buffer(g_vb_uv);
    if(g_vb_col!=0) destroy_vertex_buffer(g_vb_col);
    if(g_cb!=0) destroy_constant_buffer(g_cb);
    if(g_blend!=0) destroy_blend_state(g_blend);
    if(g_samp_linear!=0) destroy_sampler(g_samp_linear);
    if(g_samp_point!=0) destroy_sampler(g_samp_point);
    if(g_texture!=0) destroy_texture(g_texture);
    if(g_rt!=0) destroy_render_target(g_rt);
    log("custom draw test unloaded");
}
```

***

**🧪 Example: Full API Test 2 — Advanced (3D, Compute, Mesh Loading)**

```cpp
uint64 g_sh_2d, g_sh_mat, g_sh_sb, g_sh_bb, g_sh_mesh_tex;
uint64 g_cs;
uint64 g_vb, g_cb_scr, g_cb_mvp, g_cb_mat;
uint64 g_blend, g_samp_lin, g_samp_pt;
uint64 g_rt, g_db, g_ds_on, g_ds_off, g_rs_nc;
uint64 g_mesh_pyr, g_mesh_quad;
uint64 g_tex_loaded, g_tex_dyn;
uint64 g_sb_gpu, g_sb_params;
int g_cb_id = 0;
float g_time = 0;
bool g_ok = false;

void wf(array<uint8> &buf, uint off, float val) {
    if (val == 0.0f) { buf[off]=0; buf[off+1]=0; buf[off+2]=0; buf[off+3]=0; return; }
    int s = 0;
    float v = val;
    if (v < 0.0f) { s = 1; v = -v; }
    int e = 127;
    while (v >= 2.0f && e < 254) { v /= 2.0f; e++; }
    while (v < 1.0f && e > 1) { v *= 2.0f; e--; }
    v -= 1.0f;
    uint m = uint(v * 8388608.0f);
    uint bits = uint(s) << 31 | uint(e) << 23 | (m & 0x7FFFFF);
    buf[off]   = uint8(bits & 0xFF);
    buf[off+1] = uint8((bits >> 8) & 0xFF);
    buf[off+2] = uint8((bits >> 16) & 0xFF);
    buf[off+3] = uint8((bits >> 24) & 0xFF);
}

float rf(const array<uint8> &buf, uint off) {
    uint bits = uint(buf[off]) | (uint(buf[off+1]) << 8) | (uint(buf[off+2]) << 16) | (uint(buf[off+3]) << 24);
    if (bits == 0) return 0.0f;
    int s = int((bits >> 31) & 1);
    int e = int((bits >> 23) & 0xFF) - 127;
    float val = 1.0f + float(bits & 0x7FFFFF) / 8388608.0f;
    if (e > 0) { for (int i = 0; i < e; i++) val *= 2.0f; }
    else if (e < 0) { for (int i = 0; i < -e; i++) val /= 2.0f; }
    return s != 0 ? -val : val;
}

array<uint8> fb(float a, float b, float c, float d) {
    array<uint8> r(16, 0);
    wf(r, 0, a); wf(r, 4, b); wf(r, 8, c); wf(r, 12, d);
    return r;
}

array<uint8> mat4_persp(float fov, float asp, float zn, float zf) {
    float f = cos(fov * 0.5f) / sin(fov * 0.5f);
    float r = zn - zf;
    array<uint8> m(64, 0);
    wf(m, 0, f / asp); wf(m, 20, f);
    wf(m, 40, (zf + zn) / r); wf(m, 44, -1);
    wf(m, 56, (2 * zf * zn) / r);
    return m;
}

array<uint8> mat4_roty(float a) {
    float c = cos(a), s = sin(a);
    array<uint8> m(64, 0);
    wf(m, 0, c); wf(m, 8, s); wf(m, 20, 1);
    wf(m, 32, -s); wf(m, 40, c);
    wf(m, 60, 1);
    return m;
}

array<uint8> mat4_rotx(float a) {
    float c = cos(a), s = sin(a);
    array<uint8> m(64, 0);
    wf(m, 0, 1); wf(m, 20, c); wf(m, 24, -s);
    wf(m, 36, s); wf(m, 40, c);
    wf(m, 60, 1);
    return m;
}

array<uint8> mat4_trans(float tx, float ty, float tz) {
    array<uint8> m(64, 0);
    wf(m, 0, 1); wf(m, 20, 1); wf(m, 40, 1);
    wf(m, 48, tx); wf(m, 52, ty); wf(m, 56, tz); wf(m, 60, 1);
    return m;
}

array<uint8> mat4_mul(const array<uint8> &a, const array<uint8> &b) {
    array<float> fa(16), fb2(16);
    for (uint i = 0; i < 16; i++) { fa[i] = rf(a, i * 4); fb2[i] = rf(b, i * 4); }
    array<uint8> r(64, 0);
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++) {
            float s = 0;
            for (int k = 0; k < 4; k++) s += fa[i * 4 + k] * fb2[k * 4 + j];
            wf(r, uint((i * 4 + j) * 4), s);
        }
    return r;
}

void quad_verts(array<uint8> &buf, uint off, float x0, float y0, float x1, float y1) {
    wf(buf, off,      x0); wf(buf, off + 4,  y0); wf(buf, off + 8,  0); wf(buf, off + 12, 0);
    wf(buf, off + 16, x1); wf(buf, off + 20, y0); wf(buf, off + 24, 1); wf(buf, off + 28, 0);
    wf(buf, off + 32, x1); wf(buf, off + 36, y1); wf(buf, off + 40, 1); wf(buf, off + 44, 1);
    wf(buf, off + 48, x0); wf(buf, off + 52, y0); wf(buf, off + 56, 0); wf(buf, off + 60, 0);
    wf(buf, off + 64, x1); wf(buf, off + 68, y1); wf(buf, off + 72, 1); wf(buf, off + 76, 1);
    wf(buf, off + 80, x0); wf(buf, off + 84, y1); wf(buf, off + 88, 0); wf(buf, off + 92, 1);
}

string g_vs_2d =
    "cbuffer cb:register(b0){float2 screen;float time;float pad;};\n"
    "struct vi{float2 pos:POSITION;float2 uv:TEXCOORD0;};\n"
    "struct vo{float4 pos:SV_POSITION;float2 uv:TEXCOORD0;float time:TEXCOORD1;};\n"
    "vo main(vi i){vo o;o.pos=float4(i.pos.x/screen.x*2-1,-(i.pos.y/screen.y*2-1),0,1);o.uv=i.uv;o.time=time;return o;}\n";

string g_ps_tex =
    "Texture2D tex0:register(t0);SamplerState samp0:register(s0);\n"
    "struct vo{float4 pos:SV_POSITION;float2 uv:TEXCOORD0;float time:TEXCOORD1;};\n"
    "float4 main(vo i):SV_TARGET{return tex0.Sample(samp0,i.uv);}\n";

string g_vs_mesh =
    "cbuffer cb:register(b0){float4x4 mvp;};\n"
    "struct vi{float3 pos:POSITION;float3 norm:NORMAL;float2 uv:TEXCOORD0;};\n"
    "struct vo{float4 pos:SV_POSITION;float3 norm:TEXCOORD0;float2 uv:TEXCOORD1;};\n"
    "vo main(vi i){vo o;o.pos=mul(mvp,float4(i.pos,1));o.norm=i.norm;o.uv=i.uv;return o;}\n";

string g_ps_mat =
    "cbuffer cb_mat:register(b1){float4 mat_color;};\n"
    "struct vo{float4 pos:SV_POSITION;float3 norm:TEXCOORD0;float2 uv:TEXCOORD1;};\n"
    "float4 main(vo i):SV_TARGET{\n"
    "  float3 L=normalize(float3(0.4,0.8,0.6));\n"
    "  float ndl=saturate(dot(normalize(i.norm),L));\n"
    "  float3 col=mat_color.rgb*(ndl*0.7+0.3);\n"
    "  return float4(col,mat_color.a);}\n";

string g_ps_lit =
    "struct vo{float4 pos:SV_POSITION;float3 norm:TEXCOORD0;float2 uv:TEXCOORD1;};\n"
    "float4 main(vo i):SV_TARGET{\n"
    "  float3 L=normalize(float3(0.5,1.0,0.3));\n"
    "  float ndl=saturate(dot(normalize(i.norm),L));\n"
    "  return float4(float3(0.2,0.6,1.0)*ndl+float3(0.05,0.05,0.1),1);}\n";

string g_ps_mesh_tex =
    "Texture2D tex0:register(t0);SamplerState samp0:register(s0);\n"
    "struct vo{float4 pos:SV_POSITION;float3 norm:TEXCOORD0;float2 uv:TEXCOORD1;};\n"
    "float4 main(vo i):SV_TARGET{\n"
    "  float3 L=normalize(float3(0.5,1.0,0.3));\n"
    "  float ndl=saturate(dot(normalize(i.norm),L));\n"
    "  float4 tc=tex0.Sample(samp0,i.uv);\n"
    "  return float4(tc.rgb*(ndl*0.8+0.2),1);}\n";

string g_ps_sb_vis =
    "StructuredBuffer<float4> colors:register(t0);\n"
    "struct vo{float4 pos:SV_POSITION;float2 uv:TEXCOORD0;float time:TEXCOORD1;};\n"
    "float4 main(vo i):SV_TARGET{\n"
    "  uint x=clamp(uint(i.uv.x*16),0,15);\n"
    "  uint y=clamp(uint(i.uv.y*16),0,15);\n"
    "  return float4(colors[y*16+x].rgb,1);}\n";

string g_cs_src =
    "StructuredBuffer<float4> params:register(t1);\n"
    "RWStructuredBuffer<float4> buf:register(u0);\n"
    "[numthreads(16,16,1)] void main(uint3 id:SV_DispatchThreadID){\n"
    "  uint idx=id.y*16+id.x;\n"
    "  float t=params[0].x;\n"
    "  float u=id.x/15.0,v=id.y/15.0;\n"
    "  float r=0.5+0.5*sin(t*2+u*6.28);\n"
    "  float g=0.5+0.5*sin(t*1.5+v*6.28);\n"
    "  float b=0.5+0.5*cos(t+(u+v)*3.14);\n"
    "  buf[idx]=float4(r,g,b,1);}\n";

string g_ps_bb =
    "Texture2D tex0:register(t0);SamplerState samp0:register(s0);\n"
    "struct vo{float4 pos:SV_POSITION;float2 uv:TEXCOORD0;float time:TEXCOORD1;};\n"
    "float4 main(vo i):SV_TARGET{\n"
    "  float4 c=tex0.Sample(samp0,i.uv);\n"
    "  float3 inv=1.0-c.rgb;\n"
    "  float br=max(max(c.r,c.g),c.b);\n"
    "  return float4(br>0.01?inv:float3(0.12,0.12,0.18),1);}\n";

string g_obj =
    "v 0 0.8 0\nv -0.6 -0.5 0.6\nv 0.6 -0.5 0.6\nv 0.6 -0.5 -0.6\nv -0.6 -0.5 -0.6\n"
    "vn 0 0.447 0.894\nvn 0.894 0.447 0\nvn 0 0.447 -0.894\nvn -0.894 0.447 0\nvn 0 -1 0\n"
    "vt 0.5 1\nvt 0 0\nvt 1 0\n"
    "f 1/1/1 2/2/1 3/3/1\nf 1/1/2 3/2/2 4/3/2\nf 1/1/3 4/2/3 5/3/3\nf 1/1/4 5/2/4 2/3/4\n"
    "f 2/2/5 5/3/5 4/2/5\nf 2/2/5 4/2/5 3/3/5\n";

uint64 g_sh_lit;

void label(float x, float y, string text, uint8 r, uint8 g, uint8 b) {
    draw_text(text, x, y, r, g, b, 255, get_font18(), 0, 0, 0, 0, 0, 0.0f);
}

void border(float x, float y, float w, float h, uint8 r, uint8 g, uint8 b) {
    draw_rect(x - 2, y - 2, w + 4, h + 4, r, g, b, 255, 2, 0.0f, 0);
}

void render_cb(int callback_id, int data_index) {
    if (!g_ok) {
        draw_text("INIT FAILED", 100, 100, 255, 80, 80, 255, get_font24(), 0, 0, 0, 0, 0, 0.0f);
        return;
    }

    g_time += 0.016f;
    float sw = 0, sh = 0;
    get_view(sw, sh);
    array<uint8> cb_scr = fb(sw, sh, g_time, 0);

    float total_w = 740, total_h = 620;
    float ox = (sw - total_w) * 0.5f;
    float oy = (sh - total_h) * 0.5f;
    float gap = 14;
    float lbl_h = 22;

    draw_rect_filled(ox - 16, oy - 40, total_w + 32, total_h + 56, 10, 10, 18, 220, 6.0f, 0);
    draw_text("PCX Draw API - All 26 New Functions", ox, oy - 28, 200, 220, 255, 255, get_font20(), 0, 0, 0, 0, 0, 0.0f);

    // ===== ROW 1 =====
    float r1y = oy + 4;

    // A: Compute Shader + Structured Buffer
    float aw = 230, ah = 150;
    float ax = ox, ay = r1y + lbl_h;
    label(ax + 4, r1y, "A: Compute Shader + SB", 0, 255, 0);
    border(ax, ay, aw, ah, 0, 255, 0);

    array<uint8> params_data = fb(g_time, 0, 0, 0);
    update_structured_buffer(g_sb_params, params_data);
    bind_structured_buffer(g_sb_params, 1, STAGE_CS);
    bind_structured_buffer(g_sb_gpu, 0, STAGE_CS);
    dispatch_compute(g_cs, 1, 1, 1);
    bind_structured_buffer(g_sb_gpu, 0, STAGE_PS);
    array<uint8> va(96, 0);
    quad_verts(va, 0, ax, ay, ax + aw, ay + ah);
    custom_draw(g_sh_sb, g_vb, va, 6, TOPO_TRIANGLE_LIST, g_blend, 0, 0, 0, g_cb_scr, cb_scr, 0);
    custom_restore_state();

    // B: Depth Buffer + draw_mesh into RT
    float bw = total_w - aw - gap, bh = ah + lbl_h;
    float bx = ax + aw + gap, by = r1y + lbl_h;
    label(bx + 4, r1y, "B: Depth Buffer + Multi-CB", 255, 160, 0);
    border(bx, by, bw, ah, 255, 160, 0);

    custom_set_render_target_ext(g_rt, g_db);
    custom_clear_render_target(g_rt, 0.04f, 0.04f, 0.08f, 1.0f);
    custom_clear_depth_buffer(g_db);
    custom_set_rasterizer_state(g_rs_nc);
    custom_set_depth_stencil_state(g_ds_on);

    array<uint8> proj = mat4_persp(1.0f, 256.0f / 180.0f, 0.1f, 100.0f);
    array<uint8> rxm = mat4_rotx(0.4f);

    array<uint8> ry1 = mat4_roty(g_time * 0.9f);
    array<uint8> t1 = mat4_trans(-0.5f, 0, -3.5f);
    array<uint8> tmp1 = mat4_mul(ry1, rxm);
    array<uint8> tmp2 = mat4_mul(tmp1, t1);
    array<uint8> mvp1 = mat4_mul(tmp2, proj);
    array<uint8> mat_blue = fb(0.2f, 0.4f, 1.0f, 1.0f);
    custom_bind_constant_buffer(g_cb_mat, mat_blue, 1, STAGE_PS);
    draw_mesh(g_mesh_pyr, g_sh_mat, TOPO_TRIANGLE_LIST, g_blend, 0, 0, 0, g_cb_mvp, mvp1, 0);

    array<uint8> ry2 = mat4_roty(g_time * 0.9f + 0.6f);
    array<uint8> t2 = mat4_trans(0.5f, 0, -2.5f);
    array<uint8> tmp3 = mat4_mul(ry2, rxm);
    array<uint8> tmp4 = mat4_mul(tmp3, t2);
    array<uint8> mvp2 = mat4_mul(tmp4, proj);
    array<uint8> mat_red = fb(1.0f, 0.25f, 0.15f, 1.0f);
    custom_bind_constant_buffer(g_cb_mat, mat_red, 1, STAGE_PS);
    draw_mesh(g_mesh_pyr, g_sh_mat, TOPO_TRIANGLE_LIST, g_blend, 0, 0, 0, g_cb_mvp, mvp2, 0);

    custom_reset_render_target();
    custom_set_rasterizer_state(0);
    custom_set_depth_stencil_state(0);
    custom_bind_rt_as_texture(g_rt, 0);
    array<uint8> vb2(96, 0);
    quad_verts(vb2, 0, bx, by, bx + bw, by + ah);
    custom_draw(g_sh_2d, g_vb, vb2, 6, TOPO_TRIANGLE_LIST, g_blend, g_samp_lin, 0, 0, g_cb_scr, cb_scr, 0);
    custom_restore_state();

    // ===== ROW 2 =====
    float r2y = ay + ah + gap;

    // C: Loaded Texture
    float cw = 160, ch = 120;
    float cx2 = ox, cy = r2y + lbl_h;
    label(cx2 + 4, r2y, "C: Texture Load", 255, 0, 255);
    border(cx2, cy, cw, ch, 255, 0, 255);
    custom_bind_texture(g_tex_loaded, g_samp_pt, 0);
    array<uint8> vc(96, 0);
    quad_verts(vc, 0, cx2, cy, cx2 + cw, cy + ch);
    custom_draw(g_sh_2d, g_vb, vc, 6, TOPO_TRIANGLE_LIST, g_blend, g_samp_pt, g_tex_loaded, 0, g_cb_scr, cb_scr, 0);
    custom_restore_state();

    // D: Lit Mesh
    float dw = 190, dh = 120;
    float dx2 = cx2 + cw + gap, dy = r2y + lbl_h;
    label(dx2 + 4, r2y, "D: Lit Mesh", 255, 50, 50);
    border(dx2, dy, dw, dh, 255, 50, 50);
    {
        array<uint8> projd = mat4_persp(1.0f, dw / dh, 0.1f, 100.0f);
        array<uint8> ryd = mat4_roty(g_time * 1.2f);
        array<uint8> rxd = mat4_rotx(0.3f);
        array<uint8> td = mat4_trans(0, 0, -3.0f);
        array<uint8> t1d = mat4_mul(ryd, rxd);
        array<uint8> t2d = mat4_mul(t1d, td);
        array<uint8> mvpd = mat4_mul(t2d, projd);

        custom_set_viewport(dx2, dy, dw, dh);
        custom_set_rasterizer_state(g_rs_nc);
        custom_set_depth_stencil_state(g_ds_off);
        draw_mesh(g_mesh_pyr, g_sh_lit, TOPO_TRIANGLE_LIST, g_blend, 0, 0, 0, g_cb_mvp, mvpd, 0);
        custom_reset_viewport();
        custom_set_rasterizer_state(0);
        custom_set_depth_stencil_state(0);
        custom_restore_state();
    }

    // E: Textured Mesh
    float ew = total_w - cw - dw - gap * 2, eh = 120;
    float ex = dx2 + dw + gap, ey = r2y + lbl_h;
    label(ex + 4, r2y, "E: Textured Mesh", 255, 255, 255);
    border(ex, ey, ew, eh, 255, 255, 255);
    {
        array<uint8> proje = mat4_persp(1.0f, ew / eh, 0.1f, 100.0f);
        array<uint8> rye = mat4_roty(-g_time * 0.8f);
        array<uint8> rxe = mat4_rotx(0.5f);
        array<uint8> te = mat4_trans(0, 0, -3.0f);
        array<uint8> t1e = mat4_mul(rye, rxe);
        array<uint8> t2e = mat4_mul(t1e, te);
        array<uint8> mvpe = mat4_mul(t2e, proje);

        custom_set_viewport(ex, ey, ew, eh);
        custom_set_rasterizer_state(g_rs_nc);
        custom_set_depth_stencil_state(g_ds_off);
        draw_mesh(g_mesh_pyr, g_sh_mesh_tex, TOPO_TRIANGLE_LIST, g_blend, g_samp_lin, g_tex_loaded, 0, g_cb_mvp, mvpe, 0);
        custom_reset_viewport();
        custom_set_rasterizer_state(0);
        custom_set_depth_stencil_state(0);
        custom_restore_state();
    }

    // ===== ROW 3 =====
    float r3y = cy + ch + gap;

    // F: Dynamic Texture
    float fw = 200, fh = 120;
    float fx = ox, fy = r3y + lbl_h;
    label(fx + 4, r3y, "F: Dynamic Texture", 0, 200, 180);
    border(fx, fy, fw, fh, 0, 200, 180);
    {
        array<uint8> clear_px(32 * 32 * 4);
        for (uint i = 0; i < clear_px.length(); i += 4) {
            clear_px[i] = 25; clear_px[i+1] = 25; clear_px[i+2] = 45; clear_px[i+3] = 255;
        }
        custom_update_texture(g_tex_dyn, 0, 0, 32, 32, clear_px);

        int blk_x = int(g_time * 6) % 24;
        int blk_y = int(g_time * 4) % 24;
        array<uint8> block(8 * 8 * 4);
        for (int py = 0; py < 8; py++)
            for (int px = 0; px < 8; px++) {
                uint idx = uint((py * 8 + px) * 4);
                block[idx]     = uint8(255.0f * (0.5f + 0.5f * sin(g_time * 3.0f)));
                block[idx + 1] = uint8(255.0f * (0.5f + 0.5f * sin(g_time * 3.0f + 2.0f)));
                block[idx + 2] = 255;
                block[idx + 3] = 255;
            }
        custom_update_texture(g_tex_dyn, uint(blk_x), uint(blk_y), 8, 8, block);

        custom_bind_texture(g_tex_dyn, g_samp_pt, 0);
        array<uint8> vf(96, 0);
        quad_verts(vf, 0, fx, fy, fx + fw, fy + fh);
        custom_draw(g_sh_2d, g_vb, vf, 6, TOPO_TRIANGLE_LIST, g_blend, g_samp_pt, g_tex_dyn, 0, g_cb_scr, cb_scr, 0);
        custom_restore_state();
    }

    // G: Raw Mesh + Multi-CB
    float gw = 190, gh = 120;
    float gx = fx + fw + gap, gy = r3y + lbl_h;
    label(gx + 4, r3y, "G: Raw Mesh + Multi-CB", 100, 180, 255);
    border(gx, gy, gw, gh, 100, 180, 255);
    {
        float hue = g_time * 0.8f;
        array<uint8> mat_anim = fb(0.5f + 0.5f * sin(hue), 0.5f + 0.5f * sin(hue + 2.09f), 0.5f + 0.5f * sin(hue + 4.19f), 1.0f);
        array<uint8> projg = mat4_persp(1.0f, gw / gh, 0.1f, 100.0f);
        array<uint8> tg = mat4_trans(0, 0, -2.0f);
        array<uint8> mvpg = mat4_mul(tg, projg);

        custom_set_viewport(gx, gy, gw, gh);
        custom_set_rasterizer_state(g_rs_nc);
        custom_set_depth_stencil_state(g_ds_off);
        custom_bind_constant_buffer(g_cb_mvp, mvpg, 0, STAGE_VS);
        custom_bind_constant_buffer(g_cb_mat, mat_anim, 1, STAGE_PS);
        draw_mesh(g_mesh_quad, g_sh_mat, TOPO_TRIANGLE_LIST, g_blend, 0, 0, 0, 0, null, 0);
        custom_reset_viewport();
        custom_set_rasterizer_state(0);
        custom_set_depth_stencil_state(0);
        custom_restore_state();
    }

    // H: Backbuffer Capture
    float hw = total_w - fw - gw - gap * 2, hh = 120;
    float hx = gx + gw + gap, hy = r3y + lbl_h;
    label(hx + 4, r3y, "H: Backbuffer Capture", 0, 255, 255);
    border(hx, hy, hw, hh, 0, 255, 255);
    {
        capture_backbuffer(0);
        custom_bind_texture(0, g_samp_lin, 0);
        array<uint8> vh(96, 0);
        quad_verts(vh, 0, hx, hy, hx + hw, hy + hh);
        custom_draw(g_sh_bb, g_vb, vh, 6, TOPO_TRIANGLE_LIST, g_blend, g_samp_lin, 0, 0, g_cb_scr, cb_scr, 0);
        custom_restore_state();
    }

    // Status bar
    float sy = fy + fh + 12;
    draw_rect_filled(ox - 8, sy - 4, total_w + 16, 22, 20, 20, 35, 200, 4.0f, 0);
    string status = "mesh_pyr=" + (g_mesh_pyr != 0 ? "OK" : "FAIL")
        + "   mesh_quad=" + (g_mesh_quad != 0 ? "OK" : "FAIL")
        + "   tex=" + (g_tex_loaded != 0 ? "OK" : "FAIL")
        + "   depth=" + (g_db != 0 ? "OK" : "FAIL")
        + "   rt=" + (g_rt != 0 ? "OK" : "FAIL")
        + "   cs=" + (g_cs != 0 ? "OK" : "FAIL");
    draw_text(status, ox, sy, 140, 160, 200, 255, get_font18(), 0, 0, 0, 0, 0, 0.0f);
}

int main() {
    log("PCX Draw API Test - init");

    string ly_2d = "POSITION:0:FLOAT2, TEXCOORD:0:FLOAT2";
    string ly_mesh = "POSITION:0:FLOAT3, NORMAL:0:FLOAT3, TEXCOORD:0:FLOAT2";

    g_sh_2d       = create_shader(g_vs_2d, g_ps_tex, ly_2d);
    g_sh_sb       = create_shader(g_vs_2d, g_ps_sb_vis, ly_2d);
    g_sh_bb       = create_shader(g_vs_2d, g_ps_bb, ly_2d);
    g_sh_mat      = create_shader(g_vs_mesh, g_ps_mat, ly_mesh);
    g_sh_lit      = create_shader(g_vs_mesh, g_ps_lit, ly_mesh);
    g_sh_mesh_tex = create_shader(g_vs_mesh, g_ps_mesh_tex, ly_mesh);
    g_cs          = create_compute_shader(g_cs_src);

    g_vb       = create_vertex_buffer(16, 128, true);
    g_cb_scr   = create_constant_buffer(16);
    g_cb_mvp   = create_constant_buffer(64);
    g_cb_mat   = create_constant_buffer(16);
    g_blend    = create_blend_state(BLEND_SRC_ALPHA, BLEND_INV_SRC_ALPHA, BLEND_OP_ADD, BLEND_ONE, BLEND_INV_SRC_ALPHA, BLEND_OP_ADD);
    g_samp_lin = create_sampler(FILTER_LINEAR, ADDRESS_CLAMP, ADDRESS_CLAMP);
    g_samp_pt  = create_sampler(FILTER_POINT, ADDRESS_CLAMP, ADDRESS_CLAMP);

    g_rt     = create_render_target(256, 180);
    g_db     = create_depth_buffer(256, 180);
    g_ds_on  = create_depth_stencil_state(true, true, CMP_LESS);
    g_ds_off = create_depth_stencil_state(false, false, CMP_ALWAYS);
    g_rs_nc  = create_rasterizer_state(CULL_NONE, FILL_SOLID, true);

    g_sb_gpu    = create_structured_buffer(16, 256, false, true);
    g_sb_params = create_structured_buffer(16, 1, true, false);

    array<uint8> obj_bytes(g_obj.length());
    for (uint i = 0; i < g_obj.length(); i++) obj_bytes[i] = uint8(g_obj[i]);
    g_mesh_pyr = load_mesh_mem(obj_bytes);
    if (g_mesh_pyr != 0) {
        float vc, ic, mnx, mny, mnz, mxx, mxy, mxz;
        get_mesh_info(g_mesh_pyr, vc, ic, mnx, mny, mnz, mxx, mxy, mxz);
        log("Mesh: " + int(vc) + " verts, " + int(ic) + " idx, bounds " + mnx + "," + mny + "," + mnz + " -> " + mxx + "," + mxy + "," + mxz);
    } else {
        log_error("load_mesh_mem FAILED");
    }

    array<uint8> tga(18 + 16 * 16 * 4, 0);
    tga[2] = 2; tga[12] = 16; tga[14] = 16; tga[16] = 32; tga[17] = 0x20;
    for (int y = 0; y < 16; y++)
        for (int x = 0; x < 16; x++) {
            uint i = 18 + uint((y * 16 + x) * 4);
            bool cross = (x == y) || (x == 15 - y);
            tga[i]     = uint8(x * 17);
            tga[i + 1] = cross ? 255 : uint8(y * 17);
            tga[i + 2] = uint8((15 - x) * 17);
            tga[i + 3] = 255;
        }
    g_tex_loaded = load_texture_mem(tga);
    if (g_tex_loaded != 0) {
        float tw, th;
        get_texture_info(g_tex_loaded, tw, th);
        log("Texture: " + int(tw) + "x" + int(th));
    }

    array<uint8> dyn_px(32 * 32 * 4);
    for (uint i = 0; i < dyn_px.length(); i += 4) {
        dyn_px[i] = 30; dyn_px[i+1] = 30; dyn_px[i+2] = 50; dyn_px[i+3] = 255;
    }
    g_tex_dyn = create_texture(32, 32, dyn_px);

    array<uint8> raw_vb(4 * 32, 0);
    wf(raw_vb,  0,-0.6f); wf(raw_vb,  4,-0.5f); wf(raw_vb,  8, 0); wf(raw_vb, 12, 0); wf(raw_vb, 16, 0); wf(raw_vb, 20, 1); wf(raw_vb, 24, 0); wf(raw_vb, 28, 1);
    wf(raw_vb, 32, 0.6f); wf(raw_vb, 36,-0.5f); wf(raw_vb, 40, 0); wf(raw_vb, 44, 0); wf(raw_vb, 48, 0); wf(raw_vb, 52, 1); wf(raw_vb, 56, 1); wf(raw_vb, 60, 1);
    wf(raw_vb, 64, 0.6f); wf(raw_vb, 68, 0.5f); wf(raw_vb, 72, 0); wf(raw_vb, 76, 0); wf(raw_vb, 80, 0); wf(raw_vb, 84, 1); wf(raw_vb, 88, 1); wf(raw_vb, 92, 0);
    wf(raw_vb, 96,-0.6f); wf(raw_vb,100, 0.5f); wf(raw_vb,104, 0); wf(raw_vb,108, 0); wf(raw_vb,112, 0); wf(raw_vb,116, 1); wf(raw_vb,120, 0); wf(raw_vb,124, 0);
    array<uint8> raw_ib(24, 0);
    raw_ib[0]=0; raw_ib[4]=1; raw_ib[8]=2; raw_ib[12]=0; raw_ib[16]=2; raw_ib[20]=3;
    g_mesh_quad = create_mesh_raw(raw_vb, 4, 32, raw_ib, 6, true);
    if (g_mesh_quad != 0) {
        float stride = get_mesh_stride(g_mesh_quad);
        log("Raw mesh: stride=" + int(stride));
    }

    g_ok = g_sh_2d != 0 && g_sh_mat != 0 && g_sh_lit != 0 && g_sh_sb != 0
        && g_sh_bb != 0 && g_sh_mesh_tex != 0 && g_cs != 0
        && g_vb != 0 && g_cb_scr != 0 && g_cb_mvp != 0 && g_cb_mat != 0
        && g_blend != 0 && g_samp_lin != 0 && g_samp_pt != 0
        && g_rt != 0 && g_db != 0 && g_ds_on != 0 && g_ds_off != 0 && g_rs_nc != 0
        && g_mesh_pyr != 0 && g_mesh_quad != 0
        && g_tex_loaded != 0 && g_tex_dyn != 0
        && g_sb_gpu != 0 && g_sb_params != 0;

    if (!g_ok) { log_error("INIT FAILED"); return 0; }

    log("All OK");
    g_cb_id = register_callback(@render_cb, 16, 0);
    return g_cb_id > 0 ? 1 : 0;
}

void on_unload() {
    if (g_cb_id > 0) unregister_callback(g_cb_id);
}

```
