vidya/crates/renderer/AGENTS.md
2026-10-24 21:17:06 +02:00

21 KiB

renderer

Purpose

renderer builds rendering orchestration, command submission, swapchains, and render-graph APIs on renderer_resources and the lower-level renderer_device layer. It re-exports selected renderer_device APIs and uses crate-private aliases to the resource crate. Follow ../renderer_device/AGENTS.md for device ownership internals and ../renderer_resources/AGENTS.md for resource construction and ownership.

Module map

  • lib.rs: public root API, Renderer2, sampler cache, egui state, texture manager, and renderer_device re-exports.
  • memory.rs (not declared by lib.rs): empty; not part of the compiled crate module tree.
  • renderer_resources::{buffers, images, pipeline}: public resource-crate modules aliased through pub(crate) use in this crate root. These aliases preserve internal crate::buffers::X, crate::images::X, and crate::pipeline::X paths without exposing public renderer modules.
  • commands.rs (public): command pools/buffers, command recording helpers, queue submission, and command futures.
  • swapchain.rs (public): Surface, swapchain configuration, acquired images/frames, and presentation.
  • render_graph/mod.rs (public): re-exports the legacy graph API from legacy.rs; sibling commands.rs, graph_builder.rs, recorder.rs, and resources.rs are private modules.
  • render_graph/legacy.rs: public resource/pass descriptions, graph construction/resolution, resource barriers, and RenderContext.
  • render_graph/commands.rs: private typed render-graph commands and resource-access declarations; not wired into the legacy graph.
  • render_graph/graph_builder.rs: private newer graph-builder scaffolding and API example.
  • render_graph/recorder.rs: private typed command-list and side-effect tracking scaffolding.
  • render_graph/resources.rs: graph resource IDs, buffer/texture ranges and access scopes; private.
  • rendering/mod.rs (public): Wireframe helper and re-exports egui_pass / egui_pre_pass.
  • egui.rs is included as private egui_pass: egui texture upload/pre-pass and drawing integration.
  • egui_pass is a private module, but rendering publicly re-exports its egui_pass and egui_pre_pass functions.
  • text.rs (private): font storage, glyph rasterization, and CPU-side glyph-atlas data; largely internal/dead-code-allowed.
  • util.rs (public): renderer helpers and stable re-exports of eq_f32, ord_f32, hash_f32, and F32 from renderer_resources::util; weak_vec moved there and is not re-exported from renderer.
  • texture (private inline module in lib.rs): TextureManager and TextureId around shared images.

Key types and API surface

  • Public modules are commands, render_graph, rendering, swapchain, and util; text, egui_pass, and texture are private. buffers, images, and pipeline are crate-private aliases to public renderer_resources modules, not public paths through renderer.
  • lib.rs re-exports ash and the principal renderer_device APIs at crate root, including device, instance, queue, sync, Device, DeviceOwned, DeviceRef, Instance, Error, and Result.
  • Root entry point: Renderer2 owns a Device, SamplerCache, TextureManager, and display handle. Its API creates surfaces and builds/submits a RenderGraph in draw_graph.
  • Root helper types include EguiState, EguiTextureInfo, and SamplerCache. TextureManager and TextureId are used by these internals but are not re-exported at crate root.
  • SamplerCache caches renderer_resources::pipeline::Sampler objects by SamplerDesc; EguiState keeps texture mappings and descriptor/pipeline resources for egui.
  • commands::CommandPools, CommandPool, CommandBuffer, and CommandBufferFuture provide per-thread command-pool allocation and submission. SingleUseCommandPool/SingleUseCommand support one-shot operations.
  • commands::CommandBufferExt adds recording helpers for barriers, copies, rendering, and pipeline state. CommandList<T: HasQueue> submits command buffers that share a queue.
  • swapchain::Surface owns the surface and current configured swapchain; SwapchainConfiguration describes image format, extent, present mode, usage, and image count. Acquired image values must be presented.
  • Public render_graph items come from legacy.rs: RenderGraph, GraphResourceId, GraphResourceDesc, PassDesc, RenderContext, Access, LoadOp, and StoreOp provide pass/resource declaration and resolution.
  • RenderGraph::add_resource / import_image / import_buffer / import_framebuffer register graph resources; add_pass records read/write declarations and a callback; resolve creates internal resources and returns a command list with graph transitions.
  • rendering::Wireframe is an example higher-level renderer; egui pass functions are re-exported from rendering rather than the private egui_pass module.
  • Resource types are defined in renderer_resources: Buffer/BufferDesc; Image/ImageDesc, ImageView/ImageViewDesc, MipRange, Extent, and Offset; and pipeline, descriptor, shader, and sampler types.
  • Buffer/image descriptors carry Vulkan usage, memory location, queue-sharing flags, and allocation strategy; Buffer::map/map_mut expose mapped memory only when the allocator returns a mapped allocation.
  • The egui integration uploads texture deltas to GPU images and records passes through the legacy RenderGraph; shader binaries remain under crates/renderer/shaders, and renderer's hardcoded shader paths are unchanged.
  • ImageDesc includes image shape, format, layout, usage, queue family selection, and allocation options. The resource crate's Image::new_with_allocation checks allocation size and memory-type compatibility before binding.
  • ImageViewDesc selects view type, format, aspects, mip range, and array layers; the resource crate's Image::create_view normalizes REMAINING_MIP_LEVELS / REMAINING_ARRAY_LAYERS and checks image/view compatibility.
  • BufferDesc and ImageDesc use renderer_device::device::QueueFlags to select concurrent sharing families. Empty/single-family selections use exclusive sharing.
  • Pipeline retains its PipelineLayout via Arc; DescriptorPool serializes descriptor allocation/reset using a mutex. Pipeline creation uses the device pipeline cache.
  • Surface::configure skips recreating an equivalent swapchain for the same device; configuration validation falls back for unsupported present mode/format and rejects zero or oversized extents.
  • SwapchainImage dereferences to an ImageView; SwapchainFrame exposes the swapchain, image, format, view, and acquire/release semaphores used by frame submission.
  • render_graph::legacy::RenderGraph stores resource descriptions until resolve; framebuffer imports are marked as present outputs.
  • render_graph::legacy::PassDesc lists per-pass reads, writes, and an optional RecordFn; the graph resolver builds dependencies and transitions between Access states.
  • render_graph::commands::Command distinguishes side-effect declaration from GPU command application, with commands marking resource ranges/stages/accesses for barriers.
  • render_graph::recorder::CommandList type-erases recorded commands into bytes and tracks per-resource side effects.
  • Internal text support defines FontId and a private font atlas store; it rasterizes glyphs with cosmic-text into CPU image data but is not a public renderer API.
  • util::DropGuard, MutexExt, FormatExt, Rgba/Rgba8, and def_monotonic_id! support lifetime callbacks, lock closures, format conversion, color values, and IDs.
  • render_graph::legacy::RenderContext exposes the selected device, one-shot command buffer, graph resources, and optional framebuffer to each pass record callback.
  • Graph Access includes stage, access mask, and image layout. Imported resources require callers to describe their initial access; framebuffer import marks presentation as an output.
  • DeviceObject<vk::Buffer>, DeviceObject<vk::Image>, DeviceObject<vk::ImageView>, and DeviceObject<vk::SwapchainKHR> wrap device-owned Vulkan handles. Resource wrappers retain allocations/devices as needed; see the device-layer guide for lifecycle rules.
  • renderer::device::Allocation (from renderer_device) distinguishes owned, shared, and unmanaged GPU allocations; the resource image implementation uses it when constructing allocated images.
  • GraphResource can hold imported resources, owned buffers/images, framebuffers, or resource descriptions awaiting resolution. RenderGraph::resolve creates described resources and records SingleUseCommands for its passes.
  • Descriptor, pipeline, shader, and sampler abstractions are implemented in renderer_resources::pipeline. Root SamplerCache uses them through the private alias, not a public renderer pipeline module.

Invariants and gotchas

  • Device ownership is delegated to renderer_device: wrappers here strongly retain Device through its DeviceObject or explicit fields. Do not place a device-owning wrapper back inside DeviceInner.
  • Image::from_swapchain_raw(image, device, format, extent, usage) wraps a swapchain-owned raw image without taking image ownership. Swapchain::acquire_image passes the swapchain device, configured format/usage, and configured extent with depth 1. Its descriptor remains TYPE_2D, one mip/layer, TYPE_1 samples, OPTIMAL tiling, PRESENT queue flags, UNDEFINED layout, and GpuOnly memory. Do not destroy that image as an independently allocated image.
  • ImageView retains an Arc<Image> so its source image outlives the Vulkan view. Image::create_view in renderer_resources returns ManuallyDrop<ImageView>; renderer call sites transfer it deliberately (the swapchain path uses ManuallyDrop::into_inner).
  • For allocated images, Allocation::Owned frees allocation memory with its DeviceObject; Allocation::Shared permits shared ownership and only exposes mutable access when uniquely held; Unmanaged carries no allocator allocation.
  • Swapchain owns raw swapchain images and manually created acquire/release semaphores. Its drop path waits idle and destroys those semaphores; the DeviceObject destroys the swapchain handle.
  • SwapchainImage / SwapchainFrame are #[must_use] acquired-frame values. Presentation consumes the frame; preserve its Arc<Swapchain> so image ownership stays valid until presentation.
  • Surface uses a DropGuard for surface destruction and keeps its Instance; field order drops the swapchain lock first, then the guard, then the instance.
  • SingleUseCommand and CommandBuffer use DeviceObject<vk::CommandBuffer>, whose device-layer destroy implementation is intentionally a no-op. Their own drops free command buffers through their pools. SingleUseCommandPool owns a raw mutex-protected pool and destroys it itself.
  • CommandList::submit requires all commands to come from the same queue; CommandBuffer::submit checks queue-family compatibility.
  • CommandPool is explicitly !Sync; CommandBuffer is explicitly !Send and !Sync. SingleUseCommand is explicitly !Sync. Preserve these thread-affinity constraints when changing allocation/submission wrappers.
  • CommandPool::alloc currently calls free_command_buffers() before allocating, so its free-list is not an enduring reusable buffer cache. CommandBufferFuture frees the submitted buffer when the future is dropped.
  • SingleUseCommand begins recording with ONE_TIME_SUBMIT; callers must end/submit according to its state and must not access it concurrently (it is !Sync).
  • CommandPools is a thread-local registry sharded by thread and queue family; its unsafe Send implementation documents an assumption that shards are unused during drop/movement.
  • Surface::new_from_raw_window_handle is unsafe: the caller keeps display/window handles valid while surface use continues.
  • Swapchain::release_resources is an unsafe once-only pre-destruction operation; Drop invokes it and currently ignores the result of its internal idle wait.
  • Swapchain uses separate synchronization semaphore lifetimes: acquire semaphores are sized for frames in flight, release semaphores are per swapchain image. Preserve their indexing, explicit destruction, and uniqueness; they are manually managed, not entries in DevicePools.
  • Resource implementation details are owned by renderer_resources; crate::buffers, crate::images, and crate::pipeline resolve through crate-private aliases. Do not expose these aliases as renderer public modules.
  • Allocation::Shared only permits allocation_mut() through Arc::get_mut; callers must not assume shared allocation contents can always be mutated.
  • ImageDesc and BufferDesc omit alloc_scheme from both Hash and Eq; Hash omits name, and Eq also omits name. Check descriptor-cache assumptions before relying on either as a complete semantic identity.
  • Image::new_with_allocation in renderer_resources validates memory size/type but still creates the raw image before validation; failure paths should account for cleaning up handles if this function is revised.
  • Surface::configure replaces the current Arc<Swapchain> after creating the new one; in-flight tokens retain old swapchains, so preserve that overlap behavior on reconfiguration.
  • Swapchain::acquire_image moves the potentially blocking acquire call to smol::unblock, waits on a pooled fence, and constructs the acquired view before returning the frame.
  • Renderer2::draw_graph acquires a frame, imports its image, resolves/submits the graph with acquire/release semaphores, awaits completion, and then presents.
  • The active public graph API is render_graph/legacy.rs. Other graph siblings are private experimental/incomplete implementation, not a replacement API; avoid relying on TODO scaffolding.
  • render_graph/legacy.rs has partial/unfinished behavior in some paths; inspect the exact pass/resource path being changed.
  • GraphResource::simple_hash is used as an import deduplication key, not a collision-proof identity or equality check. resolve appends an output-read pass.
  • RenderGraph::resolve creates a SingleUseCommandPool on the device main queue and records pass callbacks into command buffers. The graph's task scheduling remains single-queue in this path.
  • Some legacy graph commands (clear_pass, present_pass) are convenience helpers; present_pass is deprecated in favor of marking outputs with Access::present().
  • The newer render_graph/commands.rs and recorder.rs use additional ResourceId/typed resource wrappers, distinct from legacy GraphResourceId and GraphResource.
  • The crate root also exposes Extent2D and utils alongside renderer modules and device re-exports.
  • SamplerDesc implements float-aware equality and hashing with utility helpers; keep those implementations consistent if adding sampler fields.
  • Pipeline::new_graphics supports dynamic rendering state through RenderingState; its render-pass and rendering choices are supplied in GraphicsPipelineDesc.
  • Buffer and Image creation in renderer_resources allocates/binds via DeviceInner.alloc2; device ownership and final allocation release are handled by the device-layer wrappers.
  • Resource types live in renderer_resources; the renderer aliases are crate-private and are not externally reachable as renderer::buffers::... etc.
  • Renderer2::new selects an adapter with None for the surface, then creates the logical device using its retained display handle. choose_adapter_default does not evaluate raw surface compatibility.
  • render_graph/legacy.rs implements the active public graph resolver. Its resources are ordered by GraphResourceId, and passes declare reads/writes through PassDesc so the resolver can derive dependencies and transitions.

Conventions

  • Nightly crate features in lib.rs: negative_impls, map_try_insert, debug_closure_helpers.
  • Use renderer's re-exported Device, DeviceOwned, and DeviceRef APIs rather than adding a parallel device ownership model.
  • Do not commit changes. Verify with cargo check --workspace; device-layer tests run with cargo test -p renderer_device.
  • Run focused crate checks with cargo check -p renderer; use cargo check --workspace for changes crossing crate boundaries.
  • Resource APIs live in the public renderer_resources crate. Renderer aliases are deliberately crate-private; keep the renderer-facing API intentional when adding re-exports.

Change guidance

  • Add buffer/image/pipeline resource constructors in renderer_resources, using its renderer_device re-exports and retaining the correct device/handle owner. Do not add a dependency from renderer_resources back to renderer.
  • For every resource, identify whether it owns, shares, or merely borrows Vulkan handles and memory before implementing Drop or changing field order.
  • Keep image-view format, aspect, mip, layer, and view-kind validation in renderer_resources; Vulkan view compatibility depends on the backing image description and create flags.
  • A swapchain image is not an independently destroyable Image; retain its swapchain and do not attach a GPU allocation to it.
  • Keep allocation freeing and Vulkan handle destruction in renderer_resources in a safe drop order: image/buffer before bound allocation memory.
  • Use the correct queue family set in buffer/image descriptions when resources are accessed from more than one queue; QueueFlags here is the device-layer selection type.
  • Command buffer recording and queue submission have separate synchronization requirements. Preserve queue locks and ensure an in-flight command buffer/pool is not freed before completion.
  • CommandBufferFuture owns the submitted command buffer until the future is dropped; callers that discard it early change its lifetime and must ensure GPU work is complete.
  • Surface/window raw-handle validity is the caller's obligation; retain the originating instance until surface teardown.
  • SwapchainImage and SwapchainFrame are must-use ownership tokens; keep presentation consuming the token to prevent duplicate presentation.
  • Graph callbacks in PassDesc capture resources across graph resolution. Preserve their lifetimes and correctly declare resource reads/writes for dependency/barrier generation.
  • When adding a public graph API, check whether it belongs to the legacy render_graph re-export or an unexposed experimental sibling; do not accidentally promise incomplete internals.
  • The newer typed graph modules have stubs and todo!() paths; add tests and complete the actual resolve/record flow before routing runtime rendering through them.
  • Egui texture IDs map separately between egui::TextureId and internal TextureId; updates and frees must maintain both maps and texture manager state.
  • Shader paths are currently relative to the workspace working directory; preserve or intentionally redesign that runtime asset lookup contract.
  • renderer_resources::util owns the float helpers and F32; renderer re-exports those names through renderer::util. Keep SamplerDesc float equality/hash helpers in sync for every field so SamplerCache map keys remain valid.
  • For pipeline changes, account for both legacy render-pass and dynamic-rendering descriptions and the shared device.pools.pipeline_cache.raw handle.
  • util::DropGuard invokes its closure at drop; do not allow a closure to outlive raw Vulkan dependencies it captures.
  • The egui pre-pass currently packs texture updates into a staging image/buffer and registers graph work; texture-free deltas and upload ordering deserve explicit coverage when changing the integration.
  • The private font-atlas image is CPU memory, not a Vulkan images::Image; do not conflate the similarly named Image types when editing text.rs.
  • weak_vec moved to renderer_resources::util and is not re-exported by renderer; it prunes dead weak references on capacity pressure. Image-side view tracking should remain weak to avoid ownership cycles.
  • Render graph IDs are distinct: legacy GraphResourceId wraps u32; the newer internal resources::ResourceId wraps u64.
  • Public commands::traits::CommandBufferExt contains raw recording convenience methods; those methods assume valid command-buffer state and do not all enforce it at runtime.
  • SwapchainConfiguration.image_count is treated as the additional desired in-flight count during validation, while creation asks for minimum surface image count plus that configured value. The fallback-configuration helper assigns a total count.
  • The supported surface capability extension query can filter present modes based on the selected mode; its behavior depends on optional InstanceExtensions support.
  • rendering::Wireframe allocates GPU buffers and submits a one-shot upload before returning, then records its rendering pass through the legacy graph.