Skip to content

Ludus RHI and Graphics Device Interface Architecture

Status: Implemented bounded R0–R5 baseline and broader target architecture; baseline reconciliation recorded 2026-10-11 at 5659894; R0 negotiation and R6 storage implemented at 5014834; R7 measurement/workflow implemented with physical Vulkan/Wayland and deployed-browser gates open (2026-10-12). The R6–R18 milestone assignment includes the reference-resource gap review of 2026-10-13. The initial design was drafted on 2026-10-04 before opening the article map, then refined through five complete reference chapters and primary API documentation. The implementation record separates shipped profiles from the remaining work. Public declarations and the rendering guide define the current API and its bounds. The broader contracts below are design targets wherever that implementation record does not identify a shipped path.

R5 adds opt-in raster pass merging. A paired Metal overlay workload measured lower CPU acquire/encode/submit cost with full-image agreement; this is workload-specific CPU evidence, not a GPU or presentation claim.

Ludus should use a small explicit RHI, a Graphics Device Interface that plans and manages GPU work, and a renderer that owns scene policy. Make resource lifetime, pass dependencies, pipeline state, and capability selection explicit. Use one physical queue and predictable pass order first; add native optimizations through measured capability paths. This gives Vulkan and WebGPU room to perform well while keeping WebGL 2 a useful raster backend.

Here GDI means Graphics Device Interface, the proposed engine device services layer, with GraphicsDevice as its proposed module name. These services currently live inside GraphicsRhi; there is no separate GraphicsDevice module. This interpretation is a naming assumption; Windows GDI is outside the scope. RHI means rendering hardware interface. These are proposed roles, not two duplicate wrappers around every graphics call.

The low-level renderer systems architecture owns the scene submission, views/virtual screens, materials, lighting and overlay policy above this boundary, including the implemented L0–L3 profiles. Its Gems review records later renderer-specific refinements. Device/resource lifetime and pass lowering remain owned here; broader renderer features retain their own acceptance gates.

L1 renderer integration adds explicit depth comparisons/clear values and pass viewport/scissor rectangles to these existing owners. Its usage and lifetime rules are in the renderer L1 guide; the R3 contract owns actual attachment bounds, full clears and completion semantics.

Current implementation and compatibility

The current RHI has Vulkan on Linux, Metal on macOS, and WebGPU or WebGL 2 in the browser. General APIs share the compatibility facade's owner and registry. Their capabilities describe implemented operations on the enabled device.

Existing boundary Implemented scope Design boundary
Lifecycle, explicit device and facade One main-thread device/session and borrowed surface, copied requirements, asynchronous browser startup, owner validation and explicit loss/restart Enabled raster/compute/format constraints are negotiated before Ready; independent devices/surfaces remain extensions
Fullscreen resources Eight resources per kind, one uniform binding, one fullscreen triangle and legacy InUse rejection Preserve this compatibility contract separately from general resources
Portable raster Typed generational resources, reflected immutable bindings/pipelines and indexed/instanced draws; renderer L1–L3 extend depth, targets and sampled mip formats R8 adds bounded mutation/copy/readback; extended attachment/view and descriptor APIs remain deferred
Lifetime services Retained command batches, actual completion tokens, bounded buffer/texture transfers, initialized mip primitives and canonical pipeline requests Partial sampled views, sparse memory/eviction and persistent driver cache remain extensions
Ordered graph Authored order, in-place versions, RAW/WAR/WAW reports, culling, completion-gated object pooling and opt-in raster merging Conservative whole-resource hazards; no heap aliasing, parallel recording or async queues
Compute Single-queue buffer dispatch and indexed indirect drawing on Vulkan, Metal and WebGPU WebGL 2 requires an authored CPU/direct variant; storage textures and indirect dispatch are deferred
Vulkan and Metal Both lower bounded raster/compute, R6 storage, R7 measurement and R8 transfers; Vulkan general allocations use private VMA 3.3.0, while legacy frame targets retain their allocation path Keep native allocation, synchronization and presentation retirement private
Browser dispatcher WebGPU startup with one requirement-aware WebGL 2 fallback and Platform-managed canvas replacement Preserve ADR 0014 and callback isolation
Shader build helper Pinned Slang emits SPIR-V/WGSL and macOS MSL 2.3; pinned SPIRV-Cross emits GLSL ES 3.00, with bounded raster/compute reflection and target layout validation Reject unverified layout forms; broader package/feature support is future work

Retain Start, GetStartup, frame statuses, and DrawFullscreen as a facade through the migration. Keep existing resource ownership and InUse rejection semantics for that facade until an explicitly documented migration changes them. The new API's deferred destruction must not silently change old call behavior. The historical Milestone 2 predates the current common API and is not a statement that today's repository has no RHI.

Preserve startup-only fallback, forced-selection failure, Platform canvas ownership, zero-size skips, and explicit loss/restart. Never change backend in a ready session, conceal shader/validation defects through fallback, or revive an old handle after restart. Supporting a new feature does not imply upgrading the pins in config/web_toolchain.json, config/shader_toolchain.json, or config/spirv_cross_toolchain.json as part of this design.

Module boundaries and ownership

Today the runtime dependency is GraphicsRenderer -> GraphicsRhi -> Foundation/Platform. Graph, pipeline-request, upload/readback and pool services are implemented inside the RHI registry. The diagram and ownership split below describe a possible future extraction of those services into GraphicsDevice, not an additional shipped wrapper or registry. Extract it only when a consumer and maintenance benefit justify the boundary.

flowchart TD
    App[Game and editor] --> Renderer[GraphicsRenderer scene and material policy]
    Renderer --> Device[GraphicsDevice services and pass planning]
    Device --> RHI[GraphicsRhi device API]
    RHI --> Vulkan[Private Vulkan backend]
    RHI --> Metal[Private Metal backend]
    RHI --> WebGPU[Private WebGPU backend]
    RHI --> WebGL[Private WebGL 2 backend]
    RHI --> Platform[Platform window and canvas services]
    Tools[Offline shader tools] --> Package[Shader package and reflection]
    Package --> Renderer
    Package --> Device

The target dependency direction after that extraction would be GraphicsRenderer -> GraphicsDevice -> GraphicsRhi -> Foundation/Platform. Shader package arrows describe input data. A device need not have a surface; Platform still owns any borrowed window/canvas. Initially instantiate one device and one surface. Make those relationships explicit so offscreen work and future editor views do not depend on a global current window.

Owner Responsibilities Keep outside this owner
GraphicsRhi Native device/resources, one authoritative handle registry and submitted-use ledger, resource retention, encoders, binding/pipeline objects, semantic dependency lowering, submission/completion, surface acquisition/presentation Entities, asset paths, materials, visibility, frame effects, streaming priority
GraphicsDevice Upload/readback scheduling, frame storage, pipeline requests/prewarming, pass declarations and compilation, cross-frame import planning, transient resource pooling, retirement servicing Native API objects, shader business meaning, gameplay quality policy
GraphicsRenderer Immutable scene extraction, views, visibility, materials, effect variants, draw sorting/batching, desired quality and fallback Vulkan flags, WebGPU objects, GL selectors, native heap placement
Content/resource layer CPU source ownership, load priority, durable identity, recreation recipes, mip/residency decisions Physical release of in-flight GPU allocations
Platform Windows/canvases, input/listeners, legal browser canvas replacement Graphics context/resource lifetime

GDI calls the RHI's retain/release and completion operations; it does not keep a second registry or competing generation counter. The RHI remains correct for an expert direct client, which must provide the same resource-use and dependency contracts explicitly. Direct work and GDI work share one submission owner and state ledger, owned by RHI and updated on accepted submissions. GDI uses its snapshot to plan imports; it cannot independently commit a different device state. Unregistered backend calls cannot mutate tracked state.

Suggested layout follows the repository's public/private convention:

modules/graphics/rhi/
  include/ludus/graphics/rhi/       small device, resources, commands, surface APIs
  src/internal/                   registry, use ledger, retention, dispatch, validation
  src/backends/vulkan/            device, resources, bindings, commands, surface
  src/backends/webgpu/            device, resources, bindings, commands, surface
  src/backends/webgl/             context, resources, state cache, commands
modules/graphics/device/
  include/ludus/graphics/device/   uploads, readbacks, pipeline requests, passes
  src/internal/                   pass compiler, pools, import planning
modules/graphics/renderer/        existing scene/material consumers

The backend subdirectories and separate device target are proposed; RHI and Renderer already exist. Split large backend translation units by ownership as features grow; avoid a class per native function. Public headers use Ludus aliases, small descriptions, opaque typed handles, and spans. Backend SDK headers, heavy standard-library headers, owning caches, and private headers stay behind .cpp boundaries. Explicit includes, #pragma once, exception-free failure paths, noexcept, and header parse budgets apply.

For a future multi-device implementation, bind one small private function table per device at startup. Current backends use the private single-session dispatch boundary. Native single-backend builds can use direct binding; the browser uses its existing selection boundary. Dispatch at meaningful encoder/resource operations, not through an interface object attached to every draw/resource. Native encoders record their actual command buffers; WebGPU records its encoders; WebGL executes on the context owner. Worker-produced draw packets contain engine data, not a second universal native-command bytecode or serialization framework.

Capability negotiation and portable behavior

A backend name is not a feature guarantee. Record four distinct facts: compiled backends, adapter-supported features, device-enabled features/limits, and the renderer path selected for this workload. The current API publishes DeviceInfo with implemented Supported/Enabled features, plus GetRasterCapabilities, GetComputeCapabilities and GetLifetimeCapabilities. DeviceInfo::Profile now supplies an immutable enabled raster/interface/compute and format snapshot, with copied required/preferred admission constraints. The legacy StartupInfo::Capabilities uses DeviceCapabilities for fullscreen limits. The profile negotiation guide owns current request, diagnostic and fallback semantics; raw adapter catalogs and new native feature enabling remain future extensions. Requests include required and preferred features; unmet requirements fail before resource creation, while preferences select a supported path. Browser Auto fallback must validate the application's requirements against the fallback as well.

The broader target uses workload bundles with individually queryable capabilities:

Workload bundle Intended operations Vulkan Metal WebGPU WebGL 2
Portable raster Direct indexed/instanced draws, uniforms, sampled textures/samplers, color/depth passes, uploads/copies Implement Implement Implement Implement within verified limits
Portable compute Compute dispatch, storage buffers/textures within format/access limits Query and implement Query and implement Query enabled limits/formats Unsupported
Indirect rendering Indirect draw/dispatch and specified argument layouts Query each operation Query each operation Query each operation and optional features Outside the core baseline
Native advanced Descriptor indexing/bindless, mesh shaders, ray tracing, multiple physical queues, heap aliasing, external sharing Optional independent paths Only individually verified capabilities Only individually verified enabled features Unsupported unless a specific extension is deliberately added

This target table includes operations beyond the shipped profiles. Portable raster, lifetime services and ordered raster graphs run on all four backends; buffer compute and indexed indirect draws run on Vulkan, Metal and WebGPU. Neither storage textures nor indirect dispatch is enabled today; the rendering guide owns the exact per-profile restrictions. No operation is enabled merely because a latest spec or a proposal mentions it. The pinned browser C API and real runtime must expose and pass its conformance case. WebGPU is not restricted by Ludus's WebGL baseline; compute renderers remain available on suitable devices.

Formats need a matrix of sampled/filterable/renderable/blendable/storage/copy uses, sample counts, dimensions, and compatible view formats. Query and validate limits for each profile: bindings per stage, uniform range/offset alignment, vertex attributes/stride, attachment count, texture dimensions, and copy layout. Do not hardcode desktop counts or promise all float attachments are blendable. A renderer chooses its format/effect variant before creating the frame plan.

Portable raster deliberately constrains several API mismatches:

  • A sampled TextureView uses the original format/dimension and complete supported sampling range. An attachment view selects a legal mip/layer. Independently restricted sampled ranges and format reinterpretation are capabilities; WebGL 2 must not fake independent views by changing shared texture base/max-level state between incompatible bindings.
  • Vertex formats, topology, index types, instance attributes, and sampler behavior have a reviewed portable set. Base-vertex and first-instance behavior are explicit capabilities. The initial portable draw contract uses zero values, mesh-local indices, and deliberate buffer slices, rather than an invisible CPU reindexing path.
  • Backend copy restrictions, texture pitch/block alignment, and format compatibility are checked. An optional raster conversion is a declared GDI pass, not a hidden promise that every copy operation works everywhere.
  • WebGL 2 general compute is unsupported. An ocean or particle effect can choose a raster ping-pong implementation or a CPU implementation; these are authored renderer variants with their own costs. Do not simulate compute/indirect work through transparent synchronous readbacks.

The current WebGL 2 specification is an API constraint reference. Extension availability and the pinned Emscripten surface must be checked during implementation; do not treat experimental WebGL compute specifications as the deployed WebGL 2 baseline.

Resource and command contracts

Expose only the concepts needed by real consumers. This table describes target concepts, not exact public symbol names. Current recording uses CommandBatch and OrderedGraph; there is no general public CommandEncoder interface.

Concept Contract
Device and optional Surface Explicit owner/session; surface images are borrowed frame attachments
Buffer, Texture, TextureView, Sampler Immutable description and legal usage; mutable contents through scheduled writes/copies
Shader and BindingLayout Validated target artifact and explicit layout identity
BindingSet Immutable resource/range snapshot retaining its dependencies
GraphicsPipeline, optional ComputePipeline Immutable shader/layout/state combination, independent of particular uniforms
CommandEncoder and pass encoders Single owner, explicit begin/end, retained resource references, finish or discard
SubmissionToken, UploadTicket, ReadbackTicket Explicit asynchronous state, owner identity, cancellation/loss semantics

The old PipelineDescription.Uniform coupling ends at the compatibility facade. A new pipeline specifies vertex inputs, topology, raster/depth/stencil/blend state, sample count, attachment formats, layout, and shader versions. It does not own a particular material's data. Viewport, scissor, and declared dynamic buffer offsets are encoder data; target dimensions are not a pipeline key unless an explicit specialization makes them so.

The target error vocabulary below describes distinctions the APIs must preserve; individual headers define their actual status enums and output contracts. Calls return an explicit status/result, with bounded diagnostics where supported. Creation accepts a null output handle; Ready/Pending creates exactly one owned incarnation, while validation/admission failure leaves it null and rolls back private reservation. A published Pending incarnation consumes its generation on cancellation/failure. No output overwrites a previously owned resource. Separate Unsupported, InvalidDescription, InvalidHandle, InvalidState, OutOfMemory, BudgetExceeded, Pending, and DeviceLost.

Descriptions and borrowed input bytes are consumed/copied before the call returns, or the API takes an explicit ownership lease. A Pending result never borrows the caller's stack, string, span, or editor memory. Callback storage is bounded and engine-owned. Capacity exhaustion and integer/alignment overflow are checked before creating partial backend state. Shipping checks retain identity, range, capacity, and device-state validation; deeper hazard reporting is a development layer. Diagnostics use LUDUS_LOG_* and cannot affect success.

Each asynchronous request carries its device owner, request/resource generation, and operation identity. Completion publishes into a pre-reserved request record; the owner drains it without reentrant device mutations. Configure or synchronize the pinned API's callback mode explicitly, including spontaneous delivery. Request bookkeeping outlives callback invocation, and stale delivery still releases any callback-owned native result according to backend rules. Exhaustion is rejected before launching the request; a mandatory completion cannot be dropped like optional telemetry. Shutdown invalidates identities before teardown and does not assume that cancellation prevents a late callback.

An encoder is a simple state machine: Recording -> Finished -> Submitted -> Retired, or Recording/Finished -> Discarded. It cannot be submitted twice in the initial contract. Nested passes, commands in the wrong pass type, operations after finish, and failed-resource binding return errors. GDI preflights declared plans and complete immutable draw packets before execution, including WebGL requirements and bindings. Expert direct calls validate each operation before issuing it. A later invalid direct operation does not roll back earlier accepted WebGL calls; that path reports its partial-work/failure state explicitly.

Identity and physical lifetime

Use the following runtime identity rules for new public handles: a typed owner token plus slot and non-wrapping generation, with null distinct from a valid slot. Owner denotes the device registry lifetime, not its pointer. Existing fullscreen IDs remain compatible. UUIDs identify content; they are not GPU resource lookup keys. Keep hot slot metadata separate from diagnostic names and creation records. Return IdentityExhausted/CapacityExceeded; never wrap or silently evict a live slot.

Logical destruction prevents new handle resolution immediately. Physical release requires both no retained CPU references and completion of all GPU uses. The portable collector repeats dependency-release scans only while a scan reclaims records, stopping at the first scan that changes no references. Completion and pending native work are still polled on every collection call; one call drains an eligible dependency chain without repeated idle pool scans. A built packet, finished encoder, pending submission, binding set, and readback request can retain a resource record before any submission token exists. Therefore last submitted fence alone is insufficient. Each retaining owner releases its references on discard, completion, or cancellation. Avoid cycles: a view retains a texture, a binding set retains resources/layout, and a pipeline retains its shader/layout; none retain their consumer.

An already accepted encoder keeps its retained records even if public ownership is destroyed. Destroy cannot add new encoding through the stale handle. Handle slots may be recycled only after their incarnation is safely detached and its generation advanced; detached backend records/storage remain unreusable until all leases and completion obligations end. The reference implementation can retain the slot until physical retirement for simpler debugging.

On submit acceptance, assign an owner/queue-scoped monotonic uint64 ordinal. Its progress state is Pending, Complete, Failed, or DeviceLost; zero/foreign owner is invalid, and ordinal exhaustion is explicit. A higher Complete ordinal covers earlier accepted work on that ordered queue. Multiple physical queues, when added, require one last-use token per queue unless a proven wait establishes completion dominance. A displayed frame number, CPU fence, or queue-submit return is not GPU completion.

Backend Initial completion lowering Important limit
Vulkan Existing fences mapped to ordinals; timeline semaphores only when negotiated Command/descriptor pools and staging cannot reset before completion
WebGPU Queue work-done callbacks, coalesced at submission boundaries, generation-checked Completion is asynchronous; buffer mapping has its own success/state
WebGL 2 Sync after a submitted batch; zero-timeout polling on later event-loop turns, explicit flush where needed Do not spin, wait with long timeouts, or substitute glFinish

WebGPU's queue and mapping semantics are specified separately. A mapped readback buffer proves that buffer is ready, not global queue completion. A render pass has one usage scope, including resource-binding commands; do not assume that only the final draw's active resources participate. WebGPU synchronization, mapping, and queues.

Presentation has a separate surface-generation obligation. Vulkan submission completion does not establish present completion. Preserve the current per-image semaphores and maintenance1 presentation fences during resize/shutdown; timeline semaphores do not replace WSI binary acquire/present synchronization. The Vulkan swapchain semaphore guide explains this distinction. Browsers control compositor presentation, so report GPU completion and browser presentation availability separately; do not invent an exact display-complete token.

On loss, invalidate device/surface generations, fail/cancel pending tickets, stop new work, and run backend loss teardown. Do not convert failed work to successful completion to satisfy a recycler. Content may recreate from recipes in a fresh session, with fresh handles. Normal new-device shutdown can be requested and polled while browser events continue; a main-thread destructor must not wait for callbacks that require that thread. Preserve existing Shutdown behavior while adding the new lifecycle, and keep the canvas/window alive until the relevant teardown contract finishes.

Memory uploads and readbacks

CPU allocation follows Foundation memory policy, using existing fallible Array/StaticArray seams and explicitly reserved storage. GPU allocation belongs to RHI. GDI manages budgets and schedules, without assuming that FoundationMemory arenas are already implemented. Frame storage resets only after its CPU packet jobs and GPU storage leases have retired.

General Vulkan resources already use a pinned private VMA 3.3.0 adapter. Keep that allocation boundary rather than extending the legacy frame-target allocation-per-resource helper into a second allocator. VMA returns explicit errors and does not require C++ exceptions; its implementation belongs in one private translation unit. Respect memory requirements, dedicated allocations, heap budgets, and non-coherent flush/invalidate alignment. Host allocation callbacks require a separately verified Foundation-compatible failure/alignment contract. VMA integration, Vulkan allocation guidance.

Do not put VMA handles/heaps in the common API. WebGPU/WebGL implementations manage their own memory; expose logical sizes and estimates, marking physical heap telemetry unavailable. Report requested bytes, logical live bytes, allocated backing, retired-but-pending bytes, staging/readback bytes, and backend-known budgets separately. Do not add suballocated payload to backing and call the sum GPU memory usage. Streaming admission/eviction priorities remain content policy.

Upload service accepts owned source blocks or immediately copies borrowed data into a bounded staging ring. It schedules early, returns an UploadTicket, and publishes destination use plus dependency information for pass imports. Distinct states describe source bytes consumed, copy queued, and destination usable. A consumer on the same queue can use a correctly ordered upload before host completion; an asynchronous queue consumer needs an explicit wait/ownership transfer. Recycle CPU staging only when the backend has consumed it or the GPU copy completes, according to the actual API contract.

For small dynamic data, allocate aligned slices from retained upload blocks: persistently mapped Vulkan memory where suitable, ordered WebGPU writes, and WebGL uniform/vertex uploads to unoccupied ranges. Reuse binding objects with dynamic offsets where legal; do not allocate a new descriptor for every object. Validate maximum bound range separately from overall buffer size. Ring pressure returns Backpressure or a budgeted growth request at a safe point; it never silently overwrites in-flight bytes or grows in a warmed draw loop.

Keep streaming uploads outside the transient render graph initially. Import one ready resource/dependency per consumed result; do not create one graph node for every streamed block or hidden descriptor edit. This is a refinement from Kapoulkine and Moore in the reference review.

Readback service schedules into staging, returns a ticket, and yields an owned CPU result after completion and any required mapping/invalidation. Vulkan uses host-readable staging; WebGPU uses copy-to-map-read staging; WebGL uses a pixel pack/copy buffer and sync where supported, then retrieves bounded data. Browser mapping remains asynchronous. Copy-out itself can cost time even after a fence. No synchronous per-draw queries or readbacks in normal renderer behavior. Cancellation removes the consumer but keeps physical staging retained until safe.

Bindings shaders and pipelines

The render graph authoring proposal adds an offline graph/schema compiler and editor workflow above this boundary. It emits explicit uses, reflected layouts and finite pipeline recipes through the existing graph and pipeline-request seams. Shader configurations belong to the asset author; resource connectivity cannot manufacture permutations. RHI retains sole ownership of handle lifetime, hazard lowering and accepted-submission state.

Use explicit layouts and immutable binding snapshots. A recommended raster scheme groups View/Frame, Material, and Draw data by update frequency, with Draw data in uniform slices. These are renderer conventions, not three mandatory sets in every pipeline. Compute and post-effects can have smaller purpose-built layouts. Generated target mappings flatten logical groups to WebGL uniform binding points/texture units and map them to Vulkan sets or WebGPU groups. Sampled texture/sampler pairs must have a verified GLSL lowering; unsupported pairing patterns fail the package build. Count actual per-stage requirements.

Keep frame descriptor pools separate from long-lived material sets on Vulkan. Batch allocation/update work, bulk reset frame pools on completion, and preserve binding/layout compatibility across pipelines. Never patch in-flight descriptors or reuse a binding slot based only on a frame index. Material edits and texture replacement publish a new binding snapshot; old packets use the old retained version. Shader hot reload similarly builds new pipelines and atomically publishes a ready compatible version at a frame boundary.

The WebGL backend owns a context-local shadow cache for program, VAO, framebuffer, texture/sampler units, uniform ranges, blend/depth/raster state, and dirty uploads. Apply required state at a draw boundary and eliminate identical backend calls. Use indexed slots/revisions, not a heap-allocated observer per uniform. Reset known state on context recreation and invalidate touched cache categories if an explicit foreign integration is ever allowed. Do not query GL to reconstruct state per draw. Large uploads still schedule before drawing; state deferral must not delay the bulk transfer work that should overlap GPU execution.

Keep the pinned offline shader chain: Slang to SPIR-V/WGSL and macOS MSL 2.3, plus SPIR-V through SPIRV-Cross to GLSL ES 3.00. General packages add versioned schema, source/tool hashes, target/profile/features, explicit entry names, resource bindings/access, vertex/fragment interface, and layout identity. Native consumers do not need host compiler tools at runtime. The helper already supports bounded raster and buffer-compute reflection alongside the legacy one-uniform profile. Broader package forms must continue rejecting every unverified layout.

Reflect the selected target and validate emitted artifacts, not only source language declarations. Generate host upload structures/static layout checks or explicit serializers; use a common layout only when proven identical across targets. Names are authoring/debug metadata. Encoding uses numeric bindings and validated layout IDs, with no shader-source parser or string lookup per draw. Slang distinguishes types from their target/context-dependent layouts. Slang reflection documentation.

Pipeline requests are deduplicated by canonical field-wise keys containing shader content/version, layout, fixed state, vertex interface, attachment formats, samples, and specializations. Hash collisions compare the complete key. Do not hash object pointers, padding, or a backend struct dump. A renderer draw packet already holds a ready pipeline handle; it does not rebuild a key and create a pipeline during draw encoding.

Prewarm the finite technique variants required by installed content and graphics settings, with a manifest of missing variants. Vulkan creation can run in budgeted worker jobs with correct private cache synchronization; WebGPU uses asynchronous creation when the pinned C API supports it; WebGL compiles on the context owner and may use a verified parallel-compile extension. Offline source compilation does not eliminate runtime driver pipeline compilation.

A Pending pipeline yields a predeclared compatible error/fallback technique or an explicit skipped effect. A Failed required pipeline stops that content path with diagnostics, never backend Auto fallback. The fallback must also exist and be ready. Persist native driver caches only with validated identity/version and bounded input; reject incompatible/corrupt caches and rebuild. Keep portable technique manifests separate from driver blobs. Limit permutations before trying to solve their explosion through more compilation threads.

Pass declarations and deterministic compilation

The shipped R3/R4 contracts use complete retained packets and conservative whole-texture/whole-buffer hazards. R10 implements selected attachment initialization and bounded offscreen resolves; subresource hazard tracking, copy passes and physical aliasing remain target extensions. CreateGraphTexture acquires from a bounded whole-object pool during setup; compilation validates and reports resource lifetimes, rather than allocating transient storage from those intervals.

The first GDI execution API is an ordered pass list with declared resources. That list is compiled as a graph for validation, dependencies, culling, and lifetimes; no heuristic reordering is necessary. A renderer writes the intended order. Reading a version produced only by a later pass is an error, not permission to silently move the pass. Independent passes can overlap on the GPU where legal without changing their authored CPU order.

Each pass has a stable name/source location, queue class, immutable owned packet, explicit side effects, and resource uses. Buffer uses declare ranges; texture uses declare mip/layer/aspect. Access modes distinguish read, write, read/write, and relevant stage visibility: uniform, sampled, vertex/index, attachment, storage, transfer, indirect, and present. Track hazards at actual backend granularity; conservative whole-buffer tracking is acceptable initially, but must be visible as such in diagnostics.

A logical compute pass initially contains one dispatch. Multiple dependent dispatches require separate passes, or a later explicit internal use schedule with synchronization; a broad StorageReadWrite declaration does not establish ordering between them. Shader-local data races and workgroup barriers remain kernel correctness responsibilities. Optional raster storage writes similarly cannot rely on ordinary attachment draw order to synchronize arbitrary buffers.

Raster attachments specify format/view, extent, samples, Clear/Load/Discard, Store/Discard, optional resolve, and depth/stencil read-only behavior. Every read requires defined prior contents. Load requires a valid import/producer; discard or partial write does not establish full initialization. These declarations allow bandwidth savings, and the backend supplies required security initialization rather than exposing undefined memory. Resolve is an attachment operation where supported; do not always store a whole MSAA image and resolve it later. Public subpasses are unnecessary for the first API.

Compilation proceeds through one inspectable implementation:

  1. Validate requirements, handles/leases, descriptions, declared actual bindings, pass types, attachments, and capacity before executing anything.
  2. Create logical resource versions. Read/write in-place access carries explicit RAW, WAR, and WAW dependencies against the same physical resource. A new version does not automatically allocate a new texture or remove hazards.
  3. Verify producers and authored ordering. Reject incompatible same-pass uses, illegal feedback, cycles, and references to discarded/uninitialized contents. Reflection supplies binding types/visibility; it cannot discover every shader runtime access, so author declarations stay authoritative.
  4. Mark roots: present, exported/history resources, readbacks, and explicit side effects. Cull only passes that contribute to none. Future-frame history writes are observable roots, even if this frame does not sample them.
  5. Compute first/last uses and validate retained transient objects. The current setup-time pool matches complete descriptions and gates reuse on completion. Interval-driven allocation or heap aliasing would be separate extensions; fresh/aliased storage starts undefined.
  6. Produce semantic dependencies and a backend lowering plan, including upload imports, final uses, presentation, and cross-execution obligations.
  7. Encode and submit, transferring leases to submission records. Commit the RHI's persistent state ledger only for accepted work. If execution has begun and fails, do not pretend that a partial plan rolled back; fault affected state or the session as required by backend error semantics.

No per-pass owning lambda may outlive captured stack references. Begin with plain execution functions plus frame-owned packet data, explicit retention, and an encoder limited to resources declared by that pass. Development validation compares actual binds/commands against declarations, including extra WebGPU bindings that contribute to its usage scope. Resolve handles once per packet where possible, retaining all public boundary checks.

Packets expose their complete bindings and draw descriptions for preflight. Execution functions traverse those validated packets; commands that need additional resources or capabilities require rebuilding the plan. Backend runtime errors can still happen after preflight and follow the partial-failure rules above; validation is not a promise of transactional GPU execution.

Imported persistent resources carry known content validity, initial/final uses, owner and pending dependency tokens. One owner-managed ledger connects graph executions and direct submissions. A same-queue completion dependency can be lowered without a host wait; native queue ownership still needs appropriate barriers. History resources and double-buffered render targets are allocated against completion, not frameIndex % 2 alone. Plan-cache reuse is optional; if later added, its key includes topology, requirements, layouts, attachment compatibility, and external-state contracts, not just resolution.

Concrete portable frame

Ordered pass Declared inputs Outputs and required lifetime
Scene Uploaded geometry, View/Material/Draw bindings Linear scene color cleared/stored; depth cleared and retained only as needed
Transparency Scene color and depth as attachments, transparent packets Same color loaded/stored; read-only depth; painter/depth ordering preserved
UI Scene color as attachment, UI textures/packets Linear color loaded/stored; painter order and linear blending preserved
Composite Scene color sampled, acquired surface Surface cleared/written with exactly one output encoding and handed to presentation

Vulkan inserts the color-write to fragment-sample dependency and legal layout transition. WebGPU closes the first rendering scope before sampling scene color in a new pass. WebGL changes framebuffer and samples the offscreen texture in a legal ordered draw. The graph exports why each dependency exists. It never samples a texture while attaching it for conflicting write access. A compute post-effect is a different selected variant, not part of the WebGL plan.

Backend lowering and optional optimization

Semantic dependencies carry producer/consumer access, stages, subresources, and execution placement. They are not Vulkan enums renamed into engine types. Only the backend translates them to actual synchronization. Batch compatible dependencies at the same boundary without broadening their scope unnecessarily. RAW/WAW generally need memory ordering; WAR may need execution ordering only. Queue submission order alone is not a complete Vulkan memory dependency. Vulkan synchronization examples.

Backend Lowering rule
Vulkan Maintain one tested semantic mapper. Retain the Vulkan 1.1 barrier/render-pass path; negotiate synchronization2, timeline semaphores, and dynamic rendering as independent optional paths. Preserve narrow stages, correct layouts, queue ownership, load/store and WSI retirement
WebGPU Use resource creation flags, legal scopes/pass boundaries, ordered queue operations, error scopes and asynchronous status. No fake explicit barriers, heaps, or queue families in the public API
WebGL 2 Apply context state snapshots, legal FBO operations, copies/uploads and batch sync on its owner. Reject unsupported commands before execution. No desktop GL persistent mapping or memory-barrier assumptions

Choose the existing legacy Vulkan lowering initially. If adopting an optimized Vulkan baseline later, negotiate and test newer paths deliberately, then remove an obsolete lowering only through a supported-device policy change. Permanent duplication of two unmeasured implementations is a maintenance cost.

Transient pooling reuses equivalent resource objects after all uses finish. Native heap aliasing is a separate later feature: match memory requirements and resource classes, order old/new users, issue backend-required alias transitions, and initialize the new contents. Lifetime intervals on a CPU pass list are insufficient once queues overlap. WebGPU/WebGL can pool objects without claiming control over physical allocation aliasing.

The shipped R5 merging contract is explicit opt-in through GraphCompileOptions::MergeRasterPasses; the default keeps logical raster passes separate. It merges only compatible consecutive passes while preserving draw order and legal resource scopes. Broader merging with resolve or other attachment forms needs separate correctness and measurement proof. One logical pass is not necessarily one submission. Start with one graphics submission per rendered frame plus budgeted upload work; split to feed the GPU sooner only when latency and utilization measurements justify it. No fixed historical millisecond, command-buffer, or submit count from a book is a shipping budget.

Renderer packets are compact contiguous data with stable pipeline/binding/mesh references. Sort opaque objects within the declared pass by pipeline/material with suitable depth buckets; preserve transparency, UI painter order, queries, and side effects. Batch adjacent compatible UI draws and instanced geometry. Add GPU culling/indirect submission as a compute-capable renderer path; it still buckets by pipeline and retains direct raster fallbacks.

Threads surfaces and rendering conventions

One owner thread controls startup, surfaces, submission, state-ledger updates, loss, and retirement. It remains the browser main/context thread initially. Jobs can extract immutable scene packets, prepare uploads, and request pipelines through bounded queues; publication establishes CPU synchronization, separately from GPU completion. Creation mutations do not race frame encoding.

Native parallel recording is optional. The compiler fixes execution order and initial states before jobs start; each worker receives a disjoint packet and private recording pools per completion domain. Vulkan command/descriptor pools are never concurrently mutated or reset before GPU completion. Join finished buffers in deterministic order and submit through the owner. Small passes remain serial. Measure worker idle time, command memory, join cost, and input-to-display latency as well as throughput; extra buffering is not automatically a win.

A surface publishes acquired extent, format/encoding, sample count, transform, and generation. Surface attachments cannot escape their acquired frame. Resize invalidates extent-dependent pools/plans and reacquires images; pipeline rebuild is required only for format/sample/layout/specialization changes. Preserve the current facade's restart-on-format-change contract until its replacement is implemented and documented. Suspended/zero-size surfaces skip acquisition and drawing; they do not require zero-size GPU textures. Browser composition clears or replaces drawing buffers, so history uses engine-owned textures.

Choose canonical shader clip coordinates with Y up and depth in [0, 1], and pixel rectangles with a top-left origin. WebGL lowering converts depth to its clip range; viewport/scissor/winding and render-target sampling transforms are explicit per backend. Do not blindly flip every texture or double-transform compiler-adjusted output. Imported image origin is package metadata. Verify ordinary sampling, render-to-texture sampling, front face, depth, readback row order, and viewport/scissor through diagnostic scenes on every backend.

Keep linear light through scene rendering/blending. Declare texture encoding, attachment conversion, output color space, and canvas alpha/premultiplication separately. An sRGB presentation color-space label does not make a UNORM attachment blend in linear light. For a browser UNORM final target, composite UI into the linear offscreen color and encode once in the final output shader; otherwise blending UI after encoding would be wrong. Use a verified sRGB attachment conversion when available, with no duplicate shader encoding. HDR, wide gamut and surface transforms are future negotiated output policies, not implicit consequences of backend selection.

Debugging and measurement

Every resource, pipeline, pass, encoder, upload and submission gets a stable numeric identity and optional bounded name/source context. Report the offending pass, handle owner/generation, actual versus declared use, prior writer, format, layout, and completion obligation. Store native errors alongside engine status rather than flattening every failure to a generic rendering error.

The current GraphReport exposes authored/culled passes, dependency reasons, resource versions/lifetimes, raster groups and logical plan/buffer/texture byte counts. These are not driver allocations or physical backing estimates. The R5 guide owns report definitions and measurement limits.

Extend the readable execution report as needed with lowered barriers, binding layout, pipeline keys, pool assignments, upload/readback traffic, pending retirements, and surface generation. Normal logging and profiling may drop optional records under pressure; record diagnostic loss and never crash device work because a name, capture, or telemetry allocation failed. Do not build a custom capture viewer before existing logs/artifacts answer the needed questions.

Current GraphCompileOptions independently controls culling and merging; serial recording, one physical queue and no heap aliasing are structural bounds, not additional runtime switches. Future investigation modes can include fresh transient resources and disabled state-cache elision. All modes still produce valid dependencies and initialization. A graph dump maps execution functions back to setup sites. Debug changes can hide timing or lifetime bugs, so compare the original optimized plan as well. Epic's RDG debugging documentation illustrates why deferred execution needs those controls; this design adopts the principle without requiring Unreal's framework.

Use Vulkan validation including synchronization validation, GPU debug markers, and RenderDoc/vendor captures where the actual platform supports them. Browser validation/error scopes, program logs and extension-based timers complement image tests. GPU timestamps are optional: report clock domain/availability and browser precision, never substitute CPU encoding time or zero for unavailable GPU duration. Do not compare unrelated queue clocks without calibration.

D3D12 Metal and compute expansion

The current macOS Metal backend implements the same bounded device, raster, lifetime, ordered graph, buffer-compute and merging profiles as Vulkan, within its enabled capabilities. The public rendering guide owns setup and platform limits. The Direct3D 12 backend currently clears and presents only; its staged plan brings it to those contracts. Their binding systems, residency, heaps, native barrier choices, command allocator reuse and completion mechanisms stay private. Future backend conformance must prove all common semantics before the renderer uses it. D3D12 allocator reset requires completed GPU uses, and Metal resource synchronization depends on its selected API and hazard mode; portable dependencies remain meaningful in both. Microsoft allocator lifetime, Apple resource synchronization.

CUDA and other compute infrastructures are future execution domains, not raster backends that implement empty graphics methods. Keep the current API surface-free where possible, but do not implement a universal GPU interface now. When a compute consumer arrives, extract the genuinely shared buffer/range, capability, lifetime and completion vocabulary into a small common module; implement kernel artifacts/arguments/streams in its compute executor. There is no promise that a Slang raster package automatically becomes a CUDA kernel, or that CUDA and graphics workloads share storage merely by running on one GPU.

The native texture sharing contract provides retained same-logical-device, same-queue color reads and admission of an existing Metal/Vulkan device. It is independent of external compute and does not complete cross-device external memory/semaphores or R14's multi-device scope.

A later GDI external-compute node imports declared inputs/outputs and completion obligations. Native zero-copy interop is a separate bridge requiring matching physical-device identity, compatible export/import handle types, layout/format, allocation/dedicated requirements, OS handle ownership, semaphore protocol, and explicit release/acquire of ownership. Keep resources retained across both APIs. Unsupported interop uses an explicitly scheduled host copy or an alternative kernel path, with its cost visible. Browser WebGPU/WebGL have no ordinary CUDA memory-import path to expose. NVIDIA documents device UUID matching and external memory/semaphore interoperability. CUDA interoperability.

Implementation phases and acceptance

All initial bounded profiles are implemented. Phase completion does not claim that every broader target contract in this document is available.

Phase Shipped baseline Contract and acceptance owner
R0 Contracts Copied requirements, explicit device/target owners, required/preferred workload admission, reference lifecycle validation, copied enabled profile/format constraints and compatibility facade Explicit device ownership; general slot/generation identities arrived with R1/R2
R1 Portable resources Typed resources, reflected immutable bindings/pipelines and indexed/instanced raster; renderer L1–L3 extend attachments, depth and sampled formats Portable raster and linked renderer extensions
R2 Lifetime services Completion tokens, retained batches/records, buffer upload/readback rings, private Vulkan allocator and canonical pipeline requests Lifetime services
R3 Ordered graph Authored order, in-place versions, RAW/WAR/WAW reports, roots/culling, completion-gated object pool and attachment lowering Ordered graphs
R4 Buffer compute Single-queue storage buffers, reflected dispatch, indexed indirect draw and authored CPU/direct variant Compute and indirect rendering
R5 Measured optimization Opt-in raster merging, independent culling/merging controls, paired benchmark and image comparison Measured pass merging

Baseline and evidence record

The initial milestone series ends with R5, PR 189, merged at 859d02d; its evidence reconciliation used repository revision 5659894. R0 negotiation, PR 201, subsequently merged at fb6842c, adds copied enabled limits, format/use constraints and required/preferred admission. The follow-up roadmap was assigned against comparison revision 0344d67; current statuses and evidence are recorded below. R6 was merged in PR 215; R7 in PR 216. R8 is submitted in PR 219. R5 source revision e4a6b7a83ef9a1572e77c92af55ca57c681c5b98 passed all 28 executed CI checks, including native/browser builds, sanitizer and static-analysis checks, installed/relocated SDK consumers and packaged browser rendering tests. The device lifecycle tests, raster tests, render tests, compute tests and installed raster consumer are reproducible acceptance sources, not substitutes for target-hardware qualification.

The rendering guide owns diagnostic workloads, reproduction commands and the paired R5 measurements. Use the toolchain pins in config/tool_versions.json, config/web_toolchain.json, config/shader_toolchain.json and config/spirv_cross_toolchain.json at the comparison revision. The headless Metal runs establish a workload-specific CPU benefit with identical full images and logical byte counts. GPU duration, input-to-display latency and driver allocation/backing measurements are unavailable and recorded as such. Software Vulkan/browser correctness evidence does not establish physical GPU performance or complete Wayland/hosted-iframe qualification.

This bounded baseline is ready for selected trials from the research queue. Before each trial, pin its comparison revision, backend/profile, workload and reproduction command; retain the original path and report unavailable measurements explicitly. Reading a source does not adopt it. Adoption needs a consumer, correctness evidence and measurements relevant to the proposed benefit, with maintenance cost assessed. API/specification checks remain normal implementation work; the queued conference/journal sources are not prerequisites for this baseline.

Remaining work and decision gates

R0 capability, format and limit negotiation is implemented. The remaining one-device/surface scope, resource table bounds and restricted operations are assigned below. R6 implements configurable, budgeted storage, and R7 provides measurement and qualification tooling with explicitly open host acceptance. R8 adds mutable transfers and incremental initialized mip primitives; partial sampling is supplied by implemented R9a; R9 also supplies its documented texture types and formats. R11 adds the bounded binding and table profiles below; R12–R17 retain their separately tracked subprofiles, and R18 is implemented. They extend the shipped R0–R5 profiles rather than reopening their original acceptance.

Follow-up milestones R6–R18

This is the canonical RHI/GDI milestone assignment. The scope is device APIs, GPU resources, shader interfaces, command execution and graph services. Scene import, asset decoding, materials, visibility algorithms, animation, lighting, and path-tracing estimators belong to their existing owners. Low-level tests may supply prepared scene-sized geometry, textures and command streams without requiring those higher-level systems to be implemented first.

Milestone Deliverable Dependencies Acceptance gate
R6: scalable resource and plan storage (Implemented) Replace fixed general-resource/backend tables and GDI graph/batch storage with stable, budgeted capacity. Negotiate configured maxima before Ready; grow backing between frames, without changing published limits or allocating during command execution. Keep the fullscreen facade's separate compatibility bounds. R0/R1/R2/R3 Exceed the historical 64 records per kind, 16 resources/32 passes per graph, 256 draws per retained batch/graph and 1024 draws per frame in independently budgeted fixtures. Include at least 72 small resident textures and 405 prepared draw packets. Test stable handles through growth, allocation rollback, checked capacity arithmetic, generation exhaustion, pending/detached/in-flight retention and loss/restart. Optional RT0 now uses stable pages with independently configured ray resource and instance counts; renderer L5.1 owns its integration and capacity evidence. Counts prove capacity, not full reference-scene performance.
R7: GPU measurement and qualification (Measurement/workflow implemented; host gates open) R7a: optional GPU timing and actual allocation/retirement telemetry, with logical, API-allocated and physical/resident bytes distinguished. R7b: named physical-GPU and host/surface qualification records. R7c: prepared reference-scene resource/command qualification, including large textures, hundreds of draws, thousands of instances and measured peak memory. Existing R2/R3/R5; extend telemetry for each later milestone Query lifecycle, clock domains, precision/disjointness, reporting overhead and unavailable results are explicit. CPU acquire/encode/submit time is not GPU duration or display latency. Qualification records pin hardware, workload and enabled profile, include resize/loss and selected Wayland/hosted-browser journeys, and report measured CPU/GPU distributions and peak memory. Unavailable physical residency/display observations are not invented; their qualification remains open.
R8: mutable transfers and residency primitives (Implemented, RGBA8 transfer profile) R8a: existing-buffer updates and bounded 2D texture upload/update/copy/readback. R8b: incremental initialized mip residency and staging budgets with explicit source ownership, completion and cancellation. R8a: R6 plus existing R2/R3; R8b initialization: R8a; sampling partial residency: R9a initialized-view contracts; use R7 evidence Round trips, offset/pitch/range checks, partial initialization, RAW/WAR/WAW and cancellation/pressure/loss tests. Mutation must reject, order or rename around retained/in-flight uses; never overwrite host-visible GPU data still in use. Reuse staging only after actual completion. Later texture types/block formats extend these checks in R9. Residency here does not promise sparse GPU memory or consumer eviction policy.
R9: texture types, formats and views (Implemented, sampled color/BC profiles) R9a: partial sampled views/mip/layer/aspect ranges and uploaded HDR formats. R9b: arrays/cubemaps. R9c: 3D textures and compressed formats, each with explicit enabled format/use support and target reflection. R6/R8a; partial views initially use conservative R3 hazards Validate block-edge layouts, mip/slice bounds, cube orientation, 3D addressing, initialized sampling and retained view lifetimes. Installed SDK pixels/readback qualify every claimed backend/profile. Unsupported format/use combinations reject explicitly; array/cube/3D and compression capabilities are independent.
R10: attachment extensions (Implemented, bounded offscreen profiles) R10a: multiple color attachments, sampled depth and stencil. R10b: MSAA/resolve. R10c: partial attachment views and extended GDI load/store declarations. Existing R1/R3 plus R6; R9a/view aspects for sampled depth and R10c; no dependency on all texture types or transfers Validate per-format/sample limits, attachment compatibility, resolves, depth/stencil behavior, feedback and undefined loads. Prove graph/direct image agreement, loss/resize retirement and authored alternatives. Conservatively track full-resource hazards until R13a; stencil, attachment counts and MSAA are separate capabilities.
R11: scalable binding and optional bindless (Implemented, bounded binding and table profiles) R11a: descriptor arenas and dynamic offsets. R11b: reflected binding arrays and separately enabled descriptor indexing/bindless, with explicit reachable-resource declarations and completion-gated reuse. R11c (Implemented): wider fixed color texture/sampler/buffer descriptor kinds, separately enabled bounded runtime texture arrays, logical partially populated tables with retained fallback descriptors, and safe publication of replacement snapshots after earlier binds/submissions. Native in-place UPDATE_AFTER_BIND semantics are not claimed by this immutable snapshot profile. Each operation retains its documented backend and descriptor budgets. R11a: R6 and existing R1/R2; R11b: R11a plus verified target reflection/capabilities; R9 only for new texture types/views Alignment/range checks, descriptor exhaustion/rollback, in-flight replacement and stale index tests. Arrays do not imply runtime-sized/non-uniform indexing or update-after-bind. Retain all declared reachable resources; unsafe or undeclared access rejects. Compare binding CPU cost and retained memory through R7. Bindless is optional and does not remove table/memory budgets.
R12: expanded compute and indirect commands R12a: broader indexed/non-indexed indirect arguments and indirect dispatch. R12b: raster-stage storage buffers and storage textures. R12c: optional native multi-draw/GPU-provided draw counts. Preserve R4's bounded indexed-indirect path. R12a/R12c: R6 and existing R4/R3; R12b: R6 and target shader reflection, plus R9 only for new texture/view types; bindless and mutable transfers are not prerequisites Validate argument ABI/alignment/count/stride and enabled offsets, producer-to-command/shader dependencies, zero work and overflow. GPU-produced command fixtures match direct draws without reading counts back to the CPU. R12c reports native support separately from CPU loops over indirect commands. Retain a bounded single-command path and authored WebGL CPU/direct alternative. Visibility/compaction algorithms remain renderer policy.
R13: finer GDI hazards and transient aliasing R13a: buffer ranges and texture mip/layer/aspect tracking. R13b: transient backing/heap aliasing with explicit initialization and lifetime intervals. R13a: R6 and existing R3; incorporate R8–R10/R12 uses when those profiles exist. R13b: R13a plus RHI allocation/alias capabilities and R7 memory evidence Adversarial overlapping ranges/views, cross-frame imports, alias transitions/reuse, RAW/WAR/WAW, feedback and undefined loads. Aliasing excludes imported/persistent/in-flight backing unless ownership and last use explicitly permit it. Reject invalid plans before side effects. Compare images/synchronization with whole-resource, non-aliased execution and measure peak API-allocated memory.
R14: independent devices and surfaces Multiple device/surface owners, raw adapter catalogs separated from enabled profiles, per-owner registries/completion and independent loss/presentation retirement. R6 plus existing R0/R2 ownership; independent of aliasing, bindless and GPU-driven commands Concurrent owners, stale/cross-owner rejection, independently resized/lost surfaces, pending browser callbacks and separate render/present completion. One owner's failure must not corrupt another. Preserve the single-owner facade. A separate GraphicsDevice module remains an ownership decision, not a duplicate registry.
R15: parallel recording and multiple queues R15a: native worker-owned recording/pools, deterministic owner-thread submission. R15b: optional queue domains, async work, release/acquire and completion joins. Preserve serial/single-queue execution. R15a: existing R2/R3 plus R6 and R7 evidence. R15b: existing R3/R4 plus R6/R7 and explicit per-queue state/completion; R8a for transfer queues. Neither requires R12, R13b or R14; R13a is an optimization for overlap, not a correctness prerequisite Worker/pool ownership, cancellation/loss, delayed completion and cross-queue RAW/WAR/WAW under stress. Whole-resource synchronization remains a legal conservative baseline. Validate ownership transfers and wait graphs without deadlocks; retain all backing until every participating queue completes. Prove image agreement and measured join/submit cost, tail latency and overlap memory.
R16: ray-tracing resource foundation R16a: native acceleration-structure handles, static triangle BLAS/TLAS builds, instances and scratch sizing. R16b: supported update/refit and compaction. R16c: independently supported procedural bounds and motion geometry/instances, with explicit build inputs and limits. Declare build/read/copy uses in GDI and completion-gated retirement. R6 plus existing R0/R2/R3 buffer/lifetime contracts; R8 for mutable input journeys; R7 for qualification. No scalable descriptors, bindless, aliasing, multi-device or multi-queue prerequisite Input role/usage, size/alignment and build/update limits are explicit. Test rollback, invalid geometry, retained geometry/scratch/instances, topology-change rejection/rebuild, stale handles, loss and compaction retirement. Initial R16a can use immutable uploaded buffers. Build/lifetime completion does not claim traversal correctness: build-to-query and hit/miss qualification are completed jointly with R17a.
R17: ray queries and ray pipelines R17a: reflected acceleration-structure binding and compute-stage ray queries/intersectors. R17b: independently supported dedicated ray pipelines, trace commands and shader/function tables. R17c: broader query stages and procedural intersection/motion traversal, gated with R16c and target reflection. R17a: R16a and existing R4 compute/reflection; extend ordinary R1 binding for acceleration structures. R17b: R16a and a verified backend/toolchain pipeline profile. R11, R12c, R13b and R15 are not prerequisites Independent triangle intersection fixtures verify hit/miss, ray intervals, transforms, IDs, masks and admitted opacity behavior. Validate build-to-read synchronization, shader interfaces, table ranges, payload/recursion limits where relevant and in-flight replacement. Native query support never implies dedicated pipeline support. Test every claimed lowering; unsupported backends reject or use an authored raster alternative. Shading/denoising/path-tracing algorithms remain outside RHI/GDI.

| R18: reference-scene texture and resource profiles | R18a (Implemented): original BC5_UNORM two-channel sampled D2/array/cube uploads and views, including independently negotiated native unaligned base edges. R18b (Implemented): enabled texture width/height negotiation beyond the historical 4096 ceiling. R18c (Implemented): enabled buffer/complete-upload and compute-storage ranges, sixteen-slot binding metadata with per-stage count admission, and independent backend array/volume limits. | R18a/R18b: existing R0/R6/R9; R18c: R0/R6 and existing R4/R8, with R12 only for new operations; R7c for scene qualification | BC5 block/pitch/mip/slice validation, capability rejection and retained-view lifetime; installed SDK pixels prove both channel palettes, zero blue and opaque alpha. Required/preferred dimensions use enabled device/context and per-format/use limits, not raw adapter claims. Include 8240-wide prepared textures, exact-limit/over-limit rejection and checked byte budgets without implicit resizing/transcoding. Larger limits require explicit admission, arithmetic/rollback and producer/consumer fixtures; no limit is raised solely to silence a failure. |

R6 storage and R7a measurement support are implemented; R7b physical Vulkan/Wayland and deployed physical-browser journeys remain open. See the R7 usage and named qualification evidence. R8a transfers and R8b mip initialization/staging budgets are implemented for the RGBA8 transfer profile. R8b initializes mips incrementally and gates the existing full-resource view until all mips are defined. R9a adds sampling of initialized subsets through partial color views; its texture contract and SDK journey record the enabled dimensions, compression profile and backend limitations. R10 adds bounded attachment extensions. R11a/R11b are Implemented in PR #239, with the binding profile and SDK/CI evidence recording dynamic offsets, fixed native texture arrays and independently enabled non-uniform indexing. R12a reuses R4 and R15a reuses R3 without waiting for the texture/attachment series. R14 is a separate ownership track. R16a/R17a provide an early native RT query track; scalable bindings, native ray pipelines, compaction and async queues do not block that baseline. Numbers identify scope; only the subprofile prerequisites define order.

A dependency on an interface extension does not require every optional subprofile of that milestone. Later resource types add their own reflection, hazard and lifetime cases to the existing tests. Fine-grained synchronization, bindless, aliasing and queue overlap must retain conservative reference paths. RHI exposes validated operations; GDI schedules their declared uses. It does not silently supply renderer visibility, material, lighting or streaming-priority algorithms.

Each named subprofile is tracked separately as Planned, In progress, Implemented or Qualified, with PR/revision, backend/profile matrix and evidence links. R6 is Implemented with the storage contract and SDK fixture; R7a is Implemented and R7b physical host gates remain open. R8a and R8b's initialized-mip/staging primitives are Implemented with their transfer contract and SDK journey; R9a/R9b/R9c and R10a/R10b/R10c are Implemented with explicit backend profiles; the R10 contract and SDK journey record MRT, private aspect loads, stencil, color resolves and selected attachment initialization. R11a/R11b are Implemented with the bounded backend matrix and correctness evidence linked above. R11c is Implemented in PR #266 with the independently enabled table profiles documented in that same contract; physical-host qualification remains R7b/R7c. R12–R17 subprofiles remain Planned, except for the existing RT0 static native-query profile overlapping R16a/R17a. That immutable native build/query path is implemented; it does not complete their broader GDI build/read declarations or R16b/R17b's refit/compaction and dedicated ray-pipeline scope, nor R16c/R17c motion/procedural extensions. R18a/R18b are Implemented in PR #242; R18c is Implemented in PR #246. The R18 texture contract and SDK fixture record the scoped format, dimension and byte-budget behavior. BC5 addresses ATI2 DDS data in the reference sets; width/height negotiation addresses images as large as 8240 by 8200. R18c removes the former 64 MiB allocation/complete-upload ceiling, 16 MiB compute storage-range ceiling and four-storage/eight-total binding profile. Enabled limits remain independent of logical byte budgets and the bounded R8 transfer rings; R12 retains ownership of new compute/indirect operations. The R4 indexed indirect path still admits one command per packet with zero FirstIndex/BaseVertex/FirstInstance; R12 owns nonzero offsets, non-indexed commands, indirect dispatch, multi-draw and GPU-provided counts. R13/R15 optimize memory/scheduling; R14 independent owners is a separate track. R7c remains Planned: capacity fixtures alone do not qualify a full reference scene or establish physical-GPU performance. Implemented means the declared operations and failure paths pass SDK/correctness gates; Qualified adds the named hardware/workload measurements. Unavailable evidence stays open. An umbrella milestone cannot be marked complete by selecting only its easiest subprofile: every named subprofile needs its own recorded disposition and all promised operations must be implemented on their stated supported matrix. Unsupported backend entries are explicit capability results, not silently omitted deliverables. Optional optimizations require measured benefit before default enablement; they need not improve every workload.

Portable raster remains supported on Vulkan, Metal, WebGPU and WebGL 2. Compute/indirect extensions preserve the declared WebGL CPU/direct alternative. Bindless, parallel/multi-queue execution and RT require explicit capability selection; no milestone silently changes toolchain pins, enables unimplemented native features or removes a supported backend. Implementation PRs specify the backend/profile matrix, required/preferred admission, authored alternatives and failure behavior before claiming completion.

Thanks to Khronos Group's Vulkan Guide, Descriptor Arrays and Ray Tracing (sections VK_KHR_acceleration_structure, VK_KHR_ray_query and VK_KHR_ray_tracing_pipeline), and Apple's Ray tracing with acceleration structures for the independently gated binding/query/pipeline distinctions used here. These are API checks for the milestone review on 2026-10-12, not adoption of a new renderer algorithm. R17 must not impose Vulkan's shader-binding-table model on Metal intersectors/function tables; the backend-specific package and lowering must be verified against Ludus's pinned toolchains before support is enabled.

The earlier candidate for D3D12 or external compute interoperability remains consumer-triggered work outside this assignment. A new backend or bridge needs its own scope and evidence; it is not implied by R14 or the native RT track. Research trials continue to use the review queue and must retain a baseline/control path. Persistent driver pipeline caching and other unassigned optimizations remain consumer-triggered extensions.

Qualification targets

The following remains the acceptance checklist for new extensions and broader shipping profiles, not a claim that every case or metric has already been proven.

The validation suite must include resource failure rollback; handle and token exhaustion; binding replacement while old packets remain; shader hot reload with layout changes; mapping/copy pitch/range errors; RAW/WAR/WAW transitions; persistent cross-frame data; feedback and undefined loads; graph capacity failure before side effects; zero-size/minimize/restore; format changes; startup fallback, failed canvas replacement and stale browser callbacks; and separate render/present retirement. A null/reference backend validates plans and lifecycle only; it is not evidence of GPU correctness or image quality.

Implementation uses the pinned native tools and required warning-clean builds, unit tests, ASan/UBSan, format/tidy, header self-sufficiency/foundational includes, parse-budget gates, and installed-SDK consumers. Browser builds use the pinned web toolchain. Test Vulkan headless and real Wayland, real forced browser backends and Auto, physical GPUs and hosted HTTPS/iframe scenarios. Software GPUs and mock callbacks are additional evidence, not substitutes for those environments. Game-project setup/repair must also follow AGENTS.md preset/toolchain checks.

Benchmark the existing fullscreen baseline and representative textured/instanced, UI/transparent, multi-pass, upload-pressure, resize and compute scenes. Record median and tail CPU/GPU frame times where measurable, input-to-display latency, submit/command/state-change counts, driver compilation stalls, descriptor traffic, upload/readback bytes, live/backing/pending memory, waits and high-water capacities. Count engine warm-path allocations separately from opaque driver or browser allocation. Reserved engine buffers can be allocation-free after warmup; this design makes no promise of allocation-free driver internals.

Establish budgets per supported profile on actual target hardware. Require an image-correctness/synchronization result before accepting a speedup, and reject optimizations that improve one workload while making the supported baseline unmaintainable. The first success criterion is a small readable implementation whose cost and lifetime can be explained from a frame report.

Decisions from the reference review

The reference review records reading scope, exact pages, and adopted/rejected techniques. Its concrete refinements are integrated above: explicit ordered graph compilation; separate streaming dependencies; VMA-backed Vulkan allocation; completion-based staging/pool reuse; frequency-based immutable bindings; private WebGL state deferral without delaying large uploads; explicit attachment load/store/resolve intent; enumerated/prewarmed pipeline variants; compact packets with measured recording granularity; and debugger controls that preserve resource-use validation.

Do not adopt a universal software command interpreter, mandatory bindless path, public Vulkan subpasses/heaps, automatic arbitrary pass reordering, live resource relocation, per-draw uniform observers, or always-on async queues. Each introduces an ownership or portability cost without a current Ludus consumer proving its benefit. The architecture leaves measured extension points for them without making them prerequisites for a correct portable frame.

Historical first implementation slice: capability negotiation

StartupInfo.Capabilities is a value snapshot of the implemented fullscreen API's effective limits while the session is Ready. It is zero during startup, failure, device loss and shutdown. Raw adapter support is not advertised as an engine operation. Frame dimensions are bounded by the backend's image/viewport (and Vulkan framebuffer) limits; uniform sizes are capped by the negotiated binding/allocation limits, the 16 KiB engine capacity, and 16-byte size granularity. Resource capacities and the single draw per frame describe the compatibility facade, not the current general raster/graph APIs.

The four-argument Start(app, window, selection, requirements) copies minimum frame/uniform requirements before asynchronous requests. Every backend attempt checks these before publishing Ready. Auto may fall back once when requirements are unmet; forced selections fail. RequirementsUnsatisfied and the first unmet limit remain visible in startup diagnostics. Unsupported compiled backend policy reports BackendUnavailable. Existing Start overloads impose no extra minima. This slice retains WebGPU's default feature/limit request policy: it checks the enabled device's limits without elevating them to raw adapter maxima. General required/preferred feature enabling belongs to the explicit device API.

The smoke renderer declares its own requirement from its actual uniform type. Lifecycle tests exercise rejection, accepted boundary values, reduced limits, Busy requests, copied inputs, stale completion, loss and both fallback outcomes. Browser tests inject insufficient negotiated limits on WebGPU and WebGL to prove that fallback and rejection occur before any resource creation or frame.

This historical slice retained the singleton owner and monotonic facade handles. The explicit device API and R1/R2 subsequently added owner-validated typed generational resources and retained lifetime services; R3 added the reference plan validator. Independent multi-device/surface ownership and a separate GraphicsDevice module remain possible extensions, as recorded in remaining work.

Optional static ray-query profile

ray.h exposes independently requested native static triangle BLAS/TLAS and asynchronous closest/visibility batches. Public declarations own validation, lifetime and failure contracts; the RT0/RT1 implementation profile owns backend/toolchain, memory accounting and reference transport details. Required ray requests reject unsupported startup; preferred requests preserve raster. Browser backends return Unsupported and never silently select CPU traversal. Texture CPU content is separately opt-in for initialized RGBA8 mips; retained views keep the upload shadow alive after the texture owner detaches.

Native compute library adapters

CreateNativeComputePipeline admits a storage-buffer-only adapter into the ordinary compute registry. The immutable binding snapshot, layout and context are retained through graph and GPU completion. Prepare runs between frames and can return Pending while a worker initializes private resources, without touching bound buffer contents. Context and bindings remain retained until preparation completes, including cancellation; shutdown drains pending setup before native resources disappear. Submit must use the exact prepared private queue without waiting or reentering RHI. Dispatch uses the exact snapshot and unit workgroup counts. The shared MAX_NATIVE_COMPUTE_PASSES_PER_FRAME limit is checked before encoding, so excess work preserves the graph and completion output. The initial Metal implementation commits the preceding RHI encoder, signals a native event, orders the library's private queue after that event, and orders subsequent RHI work after the library's completion event. Completion of the last RHI command buffer covers all preceding operations. Private preparation never submits on a borrowed framework queue. The shutdown-only Drain callback completes private preparation before buffers or the private queue are destroyed. Prefix command buffers remain retained for error checks. The renderer owns OIDN policy and library objects; RHI contains no denoising dependency. Vulkan/browser adapters are unsupported. See the OIDN graph contract for quality, signal and accounting limitations.

Native spatial reconstruction

spatial_upscale.h prepares device-scoped MetalFX Spatial effects outside frames. The current profile accepts distinct single-sample RGBA8 D2 textures with one mip and layer, exact configured dimensions, initialized source color and an offscreen private attachment destination. Output dimensions cannot be smaller than input. Linear and Perceptual modes describe stored channel interpretation; no implicit AA, tone mapping or display conversion occurs. GetSpatialUpscaleCapabilities reports device support; unsupported backends do not create substitute effects.

Effects use the existing raster resource generations, per-kind instance bound, frame retention and completion retirement. Encoding uses the current Metal command buffer after ending the raster encoder. Validation failures leave the current pass intact; a successful native operation defines destination color, invalidates private depth/stencil, and requires BeginRasterPass before subsequent raster draws. Framework-private GPU allocations are opaque, and cannot be reported as measured texture bytes. Prepare/reuse effects outside frames because creation may compile vendor pipelines. See graph integration and the authoring workflow.