DocsDecisions

ADR-0009: Graphics layer shape (typed handles, error scopes for shader diagnostics)

Status: accepted, 2026-11-08.

Context #

Phase 3 adds the Mojo-side graphics layer over wgpu-native. Three decisions needed recording: how GPU objects are owned and referenced, how shader compile errors are obtained given the gaps in wgpu-native 29.0.1.1, and how the typed pipeline layer talks to the raw bindings.

Decisions #

  1. Pooled ownership behind generational handles. Every wgpu object the engine creates lives in a Pool inside cinder.gfx.Resources and is referenced by a phantom-tagged Handle[Tag] (BufferHandle, TextureHandle, ... ComputePipelineHandle). Stale handles raise invalid handle; shutdown releases whatever is left alive and reports the count as leaked_at_shutdown. Swapchain views are the only raw WGPUTextureViews outside the pool, because their lifetime is one frame and owned by the surface.

    Amended 2026-11-09 (Phase 9 defect sweep): the leaked_at_shutdown field could never be read (it was written in the destructor), so it was removed. Shutdown now appends one gfx.resources warning to Gfx.diagnostics listing what was still alive, and Resources.live_counts() is the check to run before teardown. Views imported with import_texture_view (the swapchain view and the App's offscreen target) are tracked in the pool but never released by it: destroy_texture_view refuses them and forget_texture_view drops them. destroy_texture refuses while views of the texture are alive, and adopt_*/create_* release the new object when the pool is full.

  2. Shader diagnostics through validation error scopes, not compilation info. wgpuShaderModuleGetCompilationInfo is unimplemented in wgpu-native 29 (it aborts the process). ShaderManager.compile_source therefore pushes a Validation error scope, creates the module, pops the scope through the shim queue (cn_wgpu_device_pop_error_scope) and parses naga's report, which marks every location as wgsl:LINE:COLUMN. The message text is kept whole; the parsed locations drive editor-friendly path:line:col output. The event message cap was raised to 1000 bytes so a full naga report survives the queue. This is reconsidered when the pinned wgpu-native implements compilation info.

  3. Typed pipeline layer builds raw descriptors on the stack and keeps every pointee alive with keep. cinder.gfx.pipeline owns the translation from engine structs (LayoutEntry, BindingResource, VertexLayout, RenderPipelineDesc) to the wgpu descriptor graph. Pointees are plain Lists and locals; std.benchmark.keep after the C call is the lifetime fence (see docs/MOJO_NOTES.md). Build functions (build_render_pipeline, build_compute_pipeline) are separate from create_* so hot reload can rebuild a pipeline and swap it with replace_* under the same handle.

  4. Swapchain reconfiguration is lazy and recovery is bounded. Swapchain.resize only records the size; acquire reconfigures when needed, skips frames while the window has no area (minimised) and retries an Outdated/Lost/Timeout surface exactly once after reconfiguring before raising. Device loss is separate (Gfx.lost) and left to the application loop.

  5. Debug draw is a CPU line accumulator with a doubling GPU buffer. One line-list pipeline, one upload per frame, no per-call allocation in steady state (grows counter is asserted flat).

  6. Depth convention baked in. DepthState.reverse_z uses GreaterEqual, and DepthAttachment.cleared_reverse_z clears to 0.0, matching the reverse-Z infinite projection in cinder.core.math.mat4.

Consequences #

  • Hot reload of shaders and pipelines is possible without invalidating user-held handles.
  • Shader error reporting is exact in location and text but is one message per report rather than per naga diagnostic span; naga already concatenates spans into one report.
  • The command layer (cinder.gfx.commands) exposes wait_for_queue and read_buffer for tests and tools only; the frame loop never blocks on them.
  • Verified by tests/gfx/test_resources.mojo, test_shaders.mojo and test_render.mojo (offscreen triangle with compute-updated uniform, pixel readback).
View as MarkdownSource: docs/adr/0009-gfx-layer.md