# gpu intent API contract `gpu` is a frame intent orchestrator. It is not a UI toolkit, scene graph, particle engine, or raw backend API. Hosts decide what visible things mean; `gpu` turns already-decided drawing and dispatch intent into coherent backend work. ## Pipeline ```text host producers UI / compositor / scene / particles / debug -> draw and dispatch packets gpu -> frame IR -> validation -> legal planning -> backend lowering backend -> Vulkan / D3D11 / D3D12 / OpenGL / future software ``` ## Execution model The authoritative runtime direction is a planned-IR executor: validation produces diagnostics, planning orders commands, and backend lowering walks the planned IR to issue backend operations. A gated backend-native planned draw path now handles materialized IR buffers, render passes, shaders, graphics pipelines, descriptor sets, direct indexed draws, direct non-indexed draws, true per-instance vertex buffers for pipelines that declare an instance layout, single-command indirect indexed/non-indexed draws, supported borrowed imported mesh buffers, lit imported meshes, imported rect clips, the generated 2D rect/line/circle slice, generated textured quads with concrete texture IDs, and generated glyph quads with texture-backed materials. The current runtime translator that maps remaining generated/imported producer forms into legacy 2D/3D renderer calls is migration-only. New backend parity work must not add feature-specific translator branches unless the branch is a temporary, tracked bridge with a removal path. Runtime lowering must not fail silently. If a command cannot be lowered, `gpu` records a structured diagnostic with the command handle and reason, then fails submission or mode classification explicitly. ## Ownership boundary Host owns semantic visibility and policy: - widget/window/entity meaning - scene hierarchy and spatial visibility - layout, scroll regions, focus, modal rules - LOD selection and particle emitter behavior - whether hidden/minimized/disabled things emit packets `gpu` owns render-mechanical work: - targets, passes, clips, scissors, viewport setup - backend capability selection - pipeline/material/resource binding selection - resource transitions and uploads - legal batching, sorting, and culling - command generation and presentation Rule: if visibility requires meaning, host handles it. If visibility only requires geometry/render state, `gpu` may handle it. ## Resource lifetime and synchronization Backends must preserve GPU object lifetime until every command list that references the object has completed. Destroying a buffer, texture, shader, pipeline, framebuffer attachment, or temporary upload allocation while frames are in flight retires the backend object behind the completed fence that covers the already-recorded work. `wait_idle` drains all retired objects. CPU mapping is only valid for host-visible buffers. GPU-local/default buffers are not mappable through `map_buffer` or `get_buffer_mapped`; backends must report that explicitly and return `nil`. Host-visible upload buffers may stay persistently mapped when the backend memory model allows it. `compute_barrier(ctx)` is a conservative/global **in-frame** barrier for compute-written storage/UAV data consumed by later backend work in the same frame. Its consumer scope is later compute shader reads/writes, vertex/fragment shader reads, sampled texture reads, vertex/index/instance fetch, and indirect command reads. It is intentionally over-synchronized for correctness; it is not a render-target transition substitute, CPU readback fence, cross-frame lifetime rule, or feedback-loop validator. Backends that lack a complete storage descriptor path must report that separately through capabilities or diagnostics instead of pretending the barrier enables unsupported binding. Resource access diagnostics use backend-neutral access categories: color/depth attachment write, sampled texture read, storage read/write, vertex/index/instance stream read, indirect argument read, copy/upload write, and CPU map/read/write. Permanent invariant: a command must not sample a concrete `Resource_Handle` that is also bound as a render target in the same pass. Current limitation: sampled-input feedback diagnostics are exact only when sampled inputs are represented as `Resource_Handle`s; descriptor/material paths that only carry runtime texture IDs are marked as missing access metadata until those bindings become resource-backed. Current limitation: the runtime treats the default present pass as final, so offscreen work after present is rejected with a command diagnostic rather than silently reordered. Indirect draw argument buffers use the backend-neutral `Indirect_Draw_Args` and `Indirect_Draw_Indexed_Args` ABI. The layouts are intentionally byte-identical to Vulkan, D3D11, D3D12, and OpenGL single-command indirect arguments: 16 bytes for non-indexed draws and 20 bytes for indexed draws. In IR, `packet.instances.indirect` is the argument-buffer resource, `packet.instances.offset` is the byte offset, `packet.instances.stride` is zero-or-ABI-size for indirect arguments, and `packet.instances.count` is limited to one command in the current executor. Per-instance vertex streams use the normal `packet.instances.resource` field, not the indirect argument field. A planned instance-buffer draw requires a graphics pipeline with `has_instance_layout = true`, a nonzero `packet.instances.stride`, and a prepared instance buffer. Slot 0 is the per-vertex stream; slot 1 is the per-instance stream bound with `packet.instances.offset` and `packet.instances.stride`. Vulkan, D3D11, D3D12, and OpenGL all lower the same backend-neutral `Vertex_Binding.input_rate` and `Vertex_Attribute.binding` metadata; OpenGL uses the DSA vertex-attrib-binding/divisor path internally. Indirect draws may also bind an instance stream: the indirect buffer controls counts/firsts, while the instance buffer controls per-instance attributes. D3D12 runtime fence behavior cannot be executed by Linux CI. D3D12 lifetime and transition work is accepted by static backend checks, pure fence-order tests where available, and explicit Windows runtime verification debt. Current backend sync matrix: | Backend | Compute barrier scope | Sampled target feedback | Runtime proof | | --- | --- | --- | --- | | Vulkan | Conservative compute-write barrier for later shader reads/writes, sampled texture reads, vertex/index/instance fetch, and indirect command reads. | Render pass final layouts come from target usage; descriptor layout is caller-declared. | Static checks now; device smoke gate still needed. | | D3D11 | Immediate context resolves many SRV/RTV/UAV conflicts by auto-unbinding and debug warnings. Manual hazard cleanup needs slot tracking before it is safe. | No explicit image states; render-target/SRV overlap needs future warning-proof validation, not broad unbinds. | Windows runtime deferred. | | D3D12 | Global UAV barrier for compute storage visibility; indirect draws transition argument buffers to `INDIRECT_ARGUMENT`. | Render/depth targets transition to write at pass begin and to shader-resource lazily when bound as sampled descriptors. | Windows runtime deferred. | | OpenGL | Storage, texture-fetch, framebuffer, and command/indirect barriers are emitted after compute when requested. | Feedback-loop validation is a future diagnostics gap; current backend does not track attached texture handles at descriptor bind. | Linux device smoke gate still needed. | ## Public frame shape Target API shape: ```odin ctx := gpu.init_context(surface, width, height, title) frame := gpu.begin_frame(ctx) target := gpu.begin_target(frame, gpu.present_target(ctx), clear) gpu.push_layer(frame, gpu.Layer_Desc{name = "ui", sort_base = 10_000, order = .Strict}) gpu.draw_packet(frame, packet) gpu.pop_layer(frame) gpu.push_layer(frame, gpu.Layer_Desc{name = "world", sort_base = 0, order = .Depth}) gpu.draw_mesh(frame, mesh) gpu.draw_instances(frame, instances) gpu.dispatch(frame, dispatch) gpu.pop_layer(frame) gpu.end_target(frame, target) gpu.submit(frame) ``` Core handles: - `Context_Handle`: device/backend/resource owner. - `Frame_Handle`: per-frame packet builder and transient lifetime root. - `Target_Handle`: present surface or offscreen target. - `Layer_Handle`: ordering scope within a target. - `Clip_Handle`: scissor/clip scope. - `Material_Handle`: shader, blend/depth/cull/topology, bindings. - `Geometry_Handle` or `Geometry_View`: mesh/transient vertices/quad stream/glyph quads. - `Instance_View`: instance buffer/range/count/stride or indirect args. ## Ordering ```odin Order_Mode :: enum { Strict, Layered, Sortable, Depth, } ``` Planning precedence: ```text target -> pass/phase -> layer sort_base -> order mode -> sort_key/depth -> stable sequence ``` Invariants: - Every packet gets monotonic `sequence` when appended. - `Strict` preserves `sequence` exactly. No texture/material batching may cross strict sequence. - `Layered` preserves layer boundaries. Sorting inside a layer requires layer opt-in. - `Sortable` may stable-sort by explicit `sort_key` and compatible material/state. - `Depth` uses the host-provided depth `sort_key` and compatible material/state. `gpu` does not infer scene depth from semantic objects. - Equal keys keep original `sequence`. - Same input packets must produce identical planned command streams. - Dispatch packets are strict planning barriers because they may produce storage or indirect data consumed by later draws. ## Draw packet ```odin Draw_Packet :: struct { kind: Draw_Kind, target: Target_Handle, layer: Layer_Handle, sort_key: u64, sequence: u64, bounds: Bounds3, clip: Clip_Handle, transform: Mat4, material: Material_Handle, geometry: Geometry_View, instances: Instance_View, flags: Draw_Flags, order: Order_Mode, } ``` `Draw_Kind` stays graphical and generic: - mesh - indexed mesh - quad stream - line stream - point stream - glyph quad stream - indirect draw No particle emitter, UI widget, window, entity, or scene-node draw kind belongs in core `gpu`. ## Dispatch packet Compute is core because producers need it for simulation, culling, sorting, and buffer generation. ```odin Dispatch_Packet :: struct { target: Target_Handle, layer: Layer_Handle, sort_key: u64, sequence: u64, pipeline: Material_Handle, groups: [3]u32, resources: Resource_Binding_View, push_data: []byte, barrier_after: bool, } ``` Dispatch may feed later draw packets through storage buffers or indirect draw buffers. Producer semantics stay outside core. ## Producer rules Particles: - Particle systems own emitters, lifetime, spawn, gravity, sorting policy, and simulation. - Particle producers emit billboards, instance buffers, mesh instances, storage buffers, dispatches, or indirect draws. - Core `gpu` must not expose particle-specific system/config handles as final API. Text: - Text shaping/layout is producer work. - Preferred core form is glyph-quad geometry plus font atlas material. - Temporary helpers may exist during migration only if they lower to generic glyph geometry and do not become semantic text engine APIs. UI/compositor/scene: - UI emits visible boxes/text/glyphs/images with strict/layered order. - Compositor emits surfaces/quads with damage/clip hints. - Scene renderer emits visible meshes/instances and declares depth/sort rules. ## Targets and clips Targets can be present or offscreen. Target desc owns size, format, load/store, clear values, and sample/present usage. Clips start with rect/scissor. Mask clips are semantic `Clip_Handle` records whose source is geometry coverage rendered into stencil. The backend stencil contract exists below the IR: render pass descriptors carry stencil load/store, packed depth-stencil formats expose a stencil aspect, pipeline state carries front/back compare/ops/masks/ref, and each backend maps that state to native stencil state. Planned mask lowering is implemented as stencil-write plus stencil-test draw variants, and proof scenes exist for trusted readback validation. `gpu` may drop packets fully outside target bounds or clip bounds. It must not decide semantic hidden state. ## Capabilities Backend capabilities must be queried through `gpu`-neutral flags: - compute dispatch - storage buffers - indirect draws - offscreen color/depth targets - multiple render targets - stencil clips - sampled depth/color targets Capabilities describe **implemented gpu support**, not raw device support. A backend must only report a flag or limit when the public `gpu` API, IR lowering, and backend implementation can execute that behavior end-to-end. Raw driver limits may be higher; those remain internal until the `gpu` executor can use them safely. Current conservative limits: - `max_color_targets` reports the implemented color attachment count. Vulkan, D3D11, D3D12, and OpenGL currently report `8` because planned IR lowering, render pass/framebuffer arrays, pipeline color-attachment metadata, and backend RTV/FBO/render-pass binding support are implemented across all targets. - `max_push_constant_size` reports the implemented push-constant byte limit for the selected backend. Pipeline and compute-shader creation must reject sizes above this limit. - `Multiple_Render_Targets`, `Indirect_Draws`, and `Stencil_Clips` are implemented for the planned backend path. `Stencil_Clips` means the public IR, planned lowering, and backend stencil state contract are wired end-to-end; the runtime parity ledger remains open until trusted readback matrix rows pass across the required Vulkan, OpenGL, D3D11, and D3D12 backends. Texture-alpha/discard masking does not count as stencil clipping. Public API must not expose Vulkan/D3D-specific terms except through backend-only layers. OpenGL is a backend target, not a separate public API. It requires OpenGL 4.5 core, uses the OpenGL GLSL 450 Luma target, keeps one thread-affine current context for backend calls, flattens `(group, binding)` resource coordinates into documented GL binding points, and reserves one UBO binding for push-constant emulation. ## Migration policy Clean break. Migrate examples and tests to new API in small green slices. Do not preserve old Raylib-style public API as compatibility surface. Temporary internal bridge code is allowed only to keep intermediate snapshots buildable and must be removed before final cleanup. ## Validation requirements Required tests: - handles and packet counts start/validate correctly - target/layer/clip descriptors persist in FrameIR - strict order preserves append sequence - layered order preserves layer/barrier sequence - sortable/depth planning is stable and deterministic - dispatch commands split sortable/depth segments as barriers - same input packets produce identical planned streams - clip/scissor lowering matches bounds - off-target packets are culled mechanically - invalid clips/targets do not silently cull - particle-specific semantic draw kinds are absent from final core IR - Vulkan/D3D11/D3D12/OpenGL checks cover backend-neutral lowering Visual/runtime proof: - Final proof artifacts must come from deterministic readback images, not window screenshots. - The proof image contract is RGBA8, fixed dimensions, top-left assertion coordinates, straight alpha, and exact byte colors unless a test explicitly declares tolerance. - Window screenshots are smoke artifacts only and must not close backend-parity ledger rows.