Lifetimes derive from the real dependency ordering (retained DAG + compile-time happens-before closure), not command-index intervals. Aliasing is restricted to the single-queue contract; alias reuse is synchronized with memory/image barriers anchored at first-use commands. - renderer_device: aliasable shared allocations (CAN_ALIAS), memory- requirements queries incl. dedicated hints, offset-aware bind_buffer/image_memory2, grouped bind under one allocator lock - renderer_resources: unallocated resource creation, requirements cached in Allocation::Unmanaged (OnceLock), MemoryGroup pooling with intersecting memoryTypeBits, bind-time validation - renderer_rendergraph: retained dependency DAG, happens_before closure, batch lifetimes, Placement type, plan_placements with pooled aliasing (first-fit over offset-sorted ranges, pool classes split by bufferImageGranularity), alias-barrier construction, placement/bind error variants - perf: quadratic compile/resolve paths eliminated (per-resource indexing, frontier caching, backward-scan prune, cached recording index, recorder scratch reuse); single requirements query per resource through plan/validate/bind - design plan + GPU-gated validation tests
21 KiB
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, andrenderer_devicere-exports.memory.rs(not declared bylib.rs): empty; not part of the compiled crate module tree.renderer_resources::{buffers, images, pipeline}: public resource-crate modules aliased throughpub(crate) usein this crate root. These aliases preserve internalcrate::buffers::X,crate::images::X, andcrate::pipeline::Xpaths 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 fromlegacy.rs; siblingcommands.rs,graph_builder.rs,recorder.rs, andresources.rsare private modules.render_graph/legacy.rs: public resource/pass descriptions, graph construction/resolution, resource barriers, andRenderContext.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):Wireframehelper and re-exportsegui_pass/egui_pre_pass.egui.rsis included as privateegui_pass: egui texture upload/pre-pass and drawing integration.egui_passis a private module, butrenderingpublicly re-exports itsegui_passandegui_pre_passfunctions.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 ofeq_f32,ord_f32,hash_f32, andF32fromrenderer_resources::util;weak_vecmoved there and is not re-exported from renderer.texture(private inline module inlib.rs):TextureManagerandTextureIdaround shared images.
Key types and API surface
- Public modules are
commands,render_graph,rendering,swapchain, andutil;text,egui_pass, andtextureare private.buffers,images, andpipelineare crate-private aliases to publicrenderer_resourcesmodules, not public paths throughrenderer. lib.rsre-exportsashand the principalrenderer_deviceAPIs at crate root, includingdevice,instance,queue,sync,Device,DeviceOwned,DeviceRef,Instance,Error, andResult.- Root entry point:
Renderer2owns aDevice,SamplerCache,TextureManager, and display handle. Its API creates surfaces and builds/submits aRenderGraphindraw_graph. - Root helper types include
EguiState,EguiTextureInfo, andSamplerCache.TextureManagerandTextureIdare used by these internals but are not re-exported at crate root. SamplerCachecachesrenderer_resources::pipeline::Samplerobjects bySamplerDesc;EguiStatekeeps texture mappings and descriptor/pipeline resources for egui.commands::CommandPools,CommandPool,CommandBuffer, andCommandBufferFutureprovide per-thread command-pool allocation and submission.SingleUseCommandPool/SingleUseCommandsupport one-shot operations.commands::CommandBufferExtadds recording helpers for barriers, copies, rendering, and pipeline state.CommandList<T: HasQueue>submits command buffers that share a queue.swapchain::Surfaceowns the surface and current configured swapchain;SwapchainConfigurationdescribes image format, extent, present mode, usage, and image count. Acquired image values must be presented.- Public
render_graphitems come fromlegacy.rs:RenderGraph,GraphResourceId,GraphResourceDesc,PassDesc,RenderContext,Access,LoadOp, andStoreOpprovide pass/resource declaration and resolution. RenderGraph::add_resource/import_image/import_buffer/import_framebufferregister graph resources;add_passrecords read/write declarations and a callback;resolvecreates internal resources and returns a command list with graph transitions.rendering::Wireframeis an example higher-level renderer; egui pass functions are re-exported fromrenderingrather than the privateegui_passmodule.- Resource types are defined in
renderer_resources:Buffer/BufferDesc;Image/ImageDesc,ImageView/ImageViewDesc,MipRange,Extent, andOffset; and pipeline, descriptor, shader, and sampler types. - Buffer/image descriptors carry Vulkan usage,
renderer_device::device::MemoryLocation, queue-sharing flags, and allocation strategy;Buffer::map/map_mutexpose mapped memory for VMA-mapped allocations (CPU-mapped locations require host-visible, host-coherent memory). - The egui integration uploads texture deltas to GPU images and records passes through the legacy
RenderGraph; shader binaries remain undercrates/renderer/shaders, and renderer's hardcoded shader paths are unchanged. ImageDescincludes image shape, format, layout, usage, queue family selection, and allocation options. The resource crate'sImage::new_with_allocationchecks allocation size and memory-type compatibility before binding.ImageViewDescselects view type, format, aspects, mip range, and array layers; the resource crate'sImage::create_viewnormalizesREMAINING_MIP_LEVELS/REMAINING_ARRAY_LAYERSand checks image/view compatibility.BufferDescandImageDescuserenderer_device::device::QueueFlagsto select concurrent sharing families. Empty/single-family selections use exclusive sharing.Pipelineretains itsPipelineLayoutviaArc;DescriptorPoolserializes descriptor allocation/reset using a mutex. Pipeline creation uses the device pipeline cache.Surface::configureskips recreating an equivalent swapchain for the same device; configuration validation falls back for unsupported present mode/format and rejects zero or oversized extents.SwapchainImagedereferences to anImageView;SwapchainFrameexposes the swapchain, image, format, view, and acquire/release semaphores used by frame submission.render_graph::legacy::RenderGraphstores resource descriptions untilresolve; framebuffer imports are marked as present outputs.render_graph::legacy::PassDesclists per-passreads,writes, and an optionalRecordFn; the graph resolver builds dependencies and transitions betweenAccessstates.render_graph::commands::Commanddistinguishes side-effect declaration from GPU command application, with commands marking resource ranges/stages/accesses for barriers.render_graph::recorder::CommandListtype-erases recorded commands into bytes and tracks per-resource side effects.- Internal text support defines
FontIdand a private font atlas store; it rasterizes glyphs withcosmic-textinto CPU image data but is not a public renderer API. util::DropGuard,MutexExt,FormatExt,Rgba/Rgba8, anddef_monotonic_id!support lifetime callbacks, lock closures, format conversion, color values, and IDs.render_graph::legacy::RenderContextexposes the selected device, one-shot command buffer, graph resources, and optional framebuffer to each pass record callback.- Graph
Accessincludes 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>, andDeviceObject<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(fromrenderer_device) distinguishes owned, shared, and unmanaged GPU allocations; the resource image implementation uses it when constructing allocated images.GraphResourcecan hold imported resources, owned buffers/images, framebuffers, or resource descriptions awaiting resolution.RenderGraph::resolvecreates described resources and recordsSingleUseCommands for its passes.- Descriptor, pipeline, shader, and sampler abstractions are implemented in
renderer_resources::pipeline. RootSamplerCacheuses them through the private alias, not a public renderer pipeline module.
Invariants and gotchas
- Device ownership is delegated to
renderer_device: wrappers here strongly retainDevicethrough itsDeviceObjector explicit fields. Do not place a device-owning wrapper back insideDeviceInner. Image::from_swapchain_raw(image, device, format, extent, usage)wraps a swapchain-owned raw image without taking image ownership.Swapchain::acquire_imagepasses 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.ImageViewretains anArc<Image>so its source image outlives the Vulkan view.Image::create_viewinrenderer_resourcesreturnsManuallyDrop<ImageView>; renderer call sites transfer it deliberately (the swapchain path usesManuallyDrop::into_inner).- For allocated images,
Allocation::Ownedfrees VMA allocation memory with itsDeviceObject;Allocation::Sharedpermits shared ownership and only exposes mutable access when uniquely held;Unmanagedcarries no allocation. Swapchainowns raw swapchain images and manually created acquire/release semaphores. Its drop path waits idle and destroys those semaphores; theDeviceObjectdestroys the swapchain handle.SwapchainImage/SwapchainFrameare#[must_use]acquired-frame values. Presentation consumes the frame; preserve itsArc<Swapchain>so image ownership stays valid until presentation.Surfaceuses aDropGuardfor surface destruction and keeps itsInstance; field order drops the swapchain lock first, then the guard, then the instance.SingleUseCommandandCommandBufferuseDeviceObject<vk::CommandBuffer>, whose device-layer destroy implementation is intentionally a no-op. Their own drops free command buffers through their pools.SingleUseCommandPoolowns a raw mutex-protected pool and destroys it itself.CommandList::submitrequires all commands to come from the same queue;CommandBuffer::submitchecks queue-family compatibility.CommandPoolis explicitly!Sync;CommandBufferis explicitly!Sendand!Sync.SingleUseCommandis explicitly!Sync. Preserve these thread-affinity constraints when changing allocation/submission wrappers.CommandPool::alloccurrently callsfree_command_buffers()before allocating, so its free-list is not an enduring reusable buffer cache.CommandBufferFuturefrees the submitted buffer when the future is dropped.SingleUseCommandbegins recording withONE_TIME_SUBMIT; callers must end/submit according to its state and must not access it concurrently (it is!Sync).CommandPoolsis a thread-local registry sharded by thread and queue family; its unsafeSendimplementation documents an assumption that shards are unused during drop/movement.Surface::new_from_raw_window_handleis unsafe: the caller keeps display/window handles valid while surface use continues.Swapchain::release_resourcesis an unsafe once-only pre-destruction operation;Dropinvokes it and currently ignores the result of its internal idle wait.Swapchainuses 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 inDevicePools.- Resource implementation details are owned by
renderer_resources;crate::buffers,crate::images, andcrate::pipelineresolve through crate-private aliases. Do not expose these aliases as renderer public modules. Allocation::Sharedonly permitsallocation_mut()throughArc::get_mut; callers must not assume shared allocation contents can always be mutated.ImageDescandBufferDescomitalloc_schemefrom bothHashandEq;Hashomitsname, andEqalso omitsname. Check descriptor-cache assumptions before relying on either as a complete semantic identity.Image::new_with_allocationinrenderer_resourcesvalidates 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::configurereplaces the currentArc<Swapchain>after creating the new one; in-flight tokens retain old swapchains, so preserve that overlap behavior on reconfiguration.Swapchain::acquire_imagemoves the potentially blocking acquire call tosmol::unblock, waits on a pooled fence, and constructs the acquired view before returning the frame.Renderer2::draw_graphacquires 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.rshas partial/unfinished behavior in some paths; inspect the exact pass/resource path being changed.GraphResource::simple_hashis used as an import deduplication key, not a collision-proof identity or equality check.resolveappends an output-read pass.RenderGraph::resolvecreates aSingleUseCommandPoolon 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_passis deprecated in favor of marking outputs withAccess::present(). - The newer
render_graph/commands.rsandrecorder.rsuse additionalResourceId/typed resource wrappers, distinct from legacyGraphResourceIdandGraphResource. - The crate root also exposes
Extent2Dandutilsalongside renderer modules and device re-exports. SamplerDescimplements float-aware equality and hashing with utility helpers; keep those implementations consistent if adding sampler fields.Pipeline::new_graphicssupports dynamic rendering state throughRenderingState; its render-pass and rendering choices are supplied inGraphicsPipelineDesc.BufferandImagecreation inrenderer_resourcesallocates/binds throughDeviceInner::allocate_for_buffer/allocate_for_imageandbind_buffer_memory/bind_image_memory; device ownership and final VMA allocation release are handled by the device-layer wrappers.- Resource types live in
renderer_resources; therendereraliases are crate-private and are not externally reachable asrenderer::buffers::...etc. Renderer2::newselects an adapter withNonefor the surface, then creates the logical device using its retained display handle.choose_adapter_defaultdoes not evaluate raw surface compatibility.render_graph/legacy.rsimplements the active public graph resolver. Its resources are ordered byGraphResourceId, and passes declare reads/writes throughPassDescso 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-exportedDevice,DeviceOwned, andDeviceRefAPIs rather than adding a parallel device ownership model. - Do not commit changes. Verify with
cargo check --workspace; device-layer tests run withcargo test -p renderer_device. - Run focused crate checks with
cargo check -p renderer; usecargo check --workspacefor changes crossing crate boundaries. - Resource APIs live in the public
renderer_resourcescrate. 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 itsrenderer_devicere-exports and retaining the correct device/handle owner. Do not add a dependency fromrenderer_resourcesback torenderer. - For every resource, identify whether it owns, shares, or merely borrows Vulkan handles and memory before implementing
Dropor 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_resourcesin 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;
QueueFlagshere 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.
CommandBufferFutureowns 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.
SwapchainImageandSwapchainFrameare must-use ownership tokens; keep presentation consuming the token to prevent duplicate presentation.- Graph callbacks in
PassDesccapture 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_graphre-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::TextureIdand internalTextureId; 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::utilowns the float helpers andF32; renderer re-exports those names throughrenderer::util. KeepSamplerDescfloat equality/hash helpers in sync for every field soSamplerCachemap keys remain valid.- For pipeline changes, account for both legacy render-pass and dynamic-rendering descriptions and the shared
device.pools.pipeline_cache.rawhandle. util::DropGuardinvokes its closure at drop; do not allow a closure to outlive raw Vulkan dependencies it captures.- The
eguipre-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 namedImagetypes when editingtext.rs. weak_vecmoved torenderer_resources::utiland is not re-exported byrenderer; 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
GraphResourceIdwrapsu32; the newer internalresources::ResourceIdalso wrapsu32(constructed viaResourceId::from_u32). - Public
commands::traits::CommandBufferExtcontains raw recording convenience methods; those methods assume valid command-buffer state and do not all enforce it at runtime. SwapchainConfiguration.image_countis 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
InstanceExtensionssupport. rendering::Wireframeallocates GPU buffers and submits a one-shot upload before returning, then records its rendering pass through the legacy graph.