vidya/crates/renderer_resources/AGENTS.md
janis 5e4bc0ccf0
rendergraph: correct transient-resource aliasing with pooled placement
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
2026-10-27 01:57:27 +01:00

15 KiB

renderer_resources

Purpose

renderer_resources owns Vulkan buffer, image, view, descriptor, sampler, shader-module, and pipeline construction on the renderer_device layer. It handles resource memory and handle lifetimes, but contains no window, swapchain, or presentation orchestration; those remain in renderer. It must not depend on renderer.

Module map

  • lib.rs: re-exports Device, Error, Result, device, and make_extension from renderer_device; declares public buffer, image, memory, pipeline, utility, and descriptor modules.
  • buffers.rs: BufferDesc and Buffer; allocated and unallocated Vulkan buffer creation, memory requirement query/binding, raw access, and host mapping.
  • images.rs: ImageDesc, Image, ImageViewDesc, ImageView, subresource ranges, extents, offsets, and queue ownership. ImageInner distinguishes allocated/unallocated images from borrowed swapchain images.
  • memory.rs: re-exports device MemoryRequirements; exposes memory validation errors, MemoryMember, and MemoryGroup for aligned shared buffer/image allocations.
  • pipeline.rs: shader-stage and graphics/compute pipeline descriptions; DescriptorPool, DescriptorSetLayout, PipelineLayout, Sampler, ShaderModule, and Pipeline.
  • descriptors.rs: managed descriptor binding/resource metadata, validation plans, errors, and pool-generation helpers; it remains independent of render-graph types.
  • util.rs: eq_f32, ord_f32, hash_f32, F32, and the crate-private weak_vec module / WeakVec used by image view tracking.

Key types and API

  • These modules are public from renderer_resources; the crate root re-exports the core device types and device module from renderer_device for resource construction.
  • BufferDesc configures Vulkan buffer flags, size, usage, queue families, renderer_device::device::MemoryLocation, allocation strategy, and optional debug name. Buffer::new requests a VMA allocation through DeviceInner::allocate_for_buffer and binds it through bind_buffer_memory; Buffer::new_unallocated creates only the Vulkan handle. Its memory_requirements includes dedicated-allocation hints, and bind_allocation(Arc<DeviceObject<GpuAllocation>>, offset) validates then binds shared memory. map and map_mut return mapped slices when available.
  • Buffer::raw returns the Vulkan handle and len returns the configured size. Mapping is bounded to the descriptor size, not the VMA allocation block size. CPU-mapped locations select host-visible, host-coherent memory.
  • ImageDesc configures image type, format, extent, layers/mips, samples, tiling, usage, initial layout, queue sharing, and allocation strategy/location. Image::new uses the device's VMA allocation and image-binding helpers.
  • Image::new creates an allocated image; Image::new_unallocated creates an unbound image. memory_requirements includes dedicated-allocation hints, and bind_allocation(Arc<DeviceObject<GpuAllocation>>, offset) validates then binds shared memory. Image::new_with_allocation accepts an explicit renderer_device::device::Allocation and validates size and memory-type compatibility.
  • MemoryGroup::new(memory_type_index, property_flags, size, alignment) fixes the shared pool's memory type and capacity. add(MemoryMember::Buffer(&mut buffer)) / add(MemoryMember::Image(&mut image)) queries requirements, checks that the selected type is allowed for every member, and packs each at an aligned offset; allocate(&device) creates aliasable memory, then consuming bind_all() transfers allocation references into the resources. The group does not retain its own allocation after binding.
  • Image::from_swapchain_raw(image, device, format, extent, usage) builds an image wrapper for an image owned by a swapchain; renderer calls it from Swapchain::acquire_image.
  • ImageViewDesc describes a view's type, format, component mapping, aspect, mip range, and layer range. Image::create_view validates compatibility and returns ManuallyDrop<ImageView>.
  • Image::create_view resolves remaining mip/layer sentinels against the parent image, defaults an undefined view format to the image format, and checks image/view kind and format compatibility.
  • Extent, Offset, and MipRange represent image dimensions, offsets, and mip/layer ranges. QueueOwnership carries source and destination family indices.
  • Image exposes its raw handle, format, dimensions, and optional allocation; swapchain-backed images have no allocation.
  • Pipeline configuration records include ShaderStageDesc, GraphicsPipelineDesc, ComputePipelineDesc, PipelineLayoutDesc, DescriptorSetLayoutDesc, DescriptorSetLayoutBindingDesc, DescriptorPoolDesc, DescriptorSetAllocDesc, SamplerDesc, and fixed-function state descriptions.
  • Fixed-function state records are VertexInputState, TessellationState, InputAssemblyState, ViewportState, DepthBiasState, RasterizationState, MultisampleState, DepthBounds, DepthState, StencilState, DepthStencilState, ColorBlendState, RenderingState, and DynamicState.
  • Pipeline wraps graphics or compute Vulkan pipelines and retains its PipelineLayout in an Arc. Creation uses device.pools.pipeline_cache.raw.
  • DescriptorPool serializes allocation/reset with a mutex; descriptor set layouts and pipeline layouts own their Vulkan handles.
  • DescriptorPool::allocate_untracked returns raw descriptor-set handles without retaining layouts or bound resources. allocate_managed returns a generation-checked DescriptorSet that retains its pool, layout facts, and written resource owners.
  • A managed DescriptorSet retains its pool-allocation token, layout facts, and the Arc<Buffer>, Arc<ImageView>, Arc<Sampler>, or Arc<BufferView> owners supplied for its written descriptors. Image views in turn retain their parent images.
  • DescriptorSet::raw() is fallible and checks the pool generation before exposing the Vulkan handle. Generation checks also apply to binding metadata and writer operations; they detect sets invalidated by reset but cannot revoke escaped raw handles or make concurrent reset/use safe.
  • DescriptorSet::writer stages buffer, image, sampler, and texel-buffer updates. DescriptorWriter validates the descriptor type, slot bounds, ranges, resource facts, and required enabled features before Vulkan updates; retained owners are replaced only after the Vulkan writes succeed. Partially-bound bindings may keep unwritten slots; other bindings must be complete at commit.
  • Managed writes support UNIFORM_BUFFER, UNIFORM_BUFFER_DYNAMIC, STORAGE_BUFFER, STORAGE_BUFFER_DYNAMIC, SAMPLED_IMAGE, STORAGE_IMAGE, COMBINED_IMAGE_SAMPLER, SAMPLER, UNIFORM_TEXEL_BUFFER, and STORAGE_TEXEL_BUFFER. Input attachments, acceleration structures, inline uniform blocks, and all other descriptor types are rejected until explicitly implemented.
  • Managed image writes reject swapchain-backed images (including combined image samplers) before updating Vulkan descriptors, because retaining an ImageView does not retain its swapchain. Use the untracked route only when the caller separately guarantees the external image/swapchain lifetime.
  • DescriptorSetLayout::new chains per-binding descriptor flags when the device API/extension supports the Vulkan structure and validates requested flags against enabled descriptor-indexing features.
  • PipelineLayoutDesc references descriptor-set layouts and push-constant ranges; PipelineLayout owns the created Vulkan layout.
  • SamplerDesc describes filtering, addressing, anisotropy, compare, LOD, and border settings; Sampler::new creates the Vulkan sampler.
  • ShaderModule::new_from_path reads a shader file relative to the process working directory; file-open failures map to Error::AssetMissing, while read/Vulkan failures use their corresponding Error conversions. new_from_memory accepts SPIR-V words.
  • GraphicsPipelineDesc supports render-pass/subpass inputs or dynamic RenderingState; optional fixed-function states are converted during pipeline creation.
  • SamplerDesc uses eq_f32 / hash_f32 for float-valued fields. F32 provides Eq/Ord/Hash semantics using the same helpers.
  • WeakVec<T> stores Weak<T> references and prunes entries whose strong count is zero when growing; it is crate-private, not public API.

Invariants and gotchas

  • This crate consumes the renderer_device ownership types and re-exports; do not add a parallel device model or a dependency on renderer.
  • Image::from_swapchain_raw stores the raw image in the ImageInner::Swapchain variant with its device. The swapchain owns that Vulkan image; never destroy it as an independently allocated image.
  • from_swapchain_raw records a 2D, one-mip, one-layer, single-sample, optimal-tiled image with PRESENT queue sharing and UNDEFINED initial layout; the caller supplies format, extent, and usage.
  • from_swapchain_raw retains a Device for views and device queries, but not ownership of the image handle or swapchain lifetime; the renderer's frame/swapchain token must keep the swapchain alive.
  • Allocated images and buffers use DeviceObject for Vulkan handles and allocation ownership. Keep resource destruction ordered before freeing bound memory.
  • Unallocated resources use Allocation::Unmanaged until bound. Shared bind APIs are one-shot; they validate offset alignment, allowed memory type, and allocation bounds before calling the device layer. Keep DeviceObject<GpuAllocation> on the resource after a successful bind so Vulkan handles are destroyed before their allocation reference.
  • MemoryGroup only accepts members whose memory_type_bits include its selected type (the intersection across all members); it rejects dedicated-required resources. Group offsets use aligned bump packing, and allocation ownership transfers into each bound member at bind_all.
  • Buffer equality compares the raw buffer handle; Image equality compares the raw image handle. Descriptor equality is separate from resource object identity.
  • Allocation::Owned owns a device-managed allocation; Shared shares it through Arc and permits mutable access only when uniquely held; Unmanaged carries no allocation.
  • ImageView retains Arc<Image> so the source image outlives the view. Image::create_view returns ManuallyDrop<ImageView>; callers must transfer/drop it deliberately.
  • Image tracks views through WeakVec<ImageView> to avoid a strong-reference cycle. The weak list does not keep views alive.
  • ImageView's DeviceObject owns and destroys the Vulkan view; its parent Arc<Image> must remain alive through view destruction.
  • Image::create_view returns ManuallyDrop<ImageView>; when a managed descriptor needs an owned view, deliberately transfer it with ManuallyDrop::into_inner and place it in an Arc.
  • ImageDesc and BufferDesc omit alloc_scheme from both Hash and Eq; their Hash implementations omit name, and Eq also omits name. Do not treat these descriptors as complete resource identities without accounting for these fields.
  • Descriptor hashing/equality covers resource shape, usage, sharing, and memory location but not every construction input. Update both implementations together if adding descriptor-keyed resource deduplication.
  • Pipeline retains its layout with Arc; descriptor-pool operations are mutex-serialized. Keep these lifetime and synchronization guarantees when changing constructors or wrappers.
  • Descriptor indexing validation uses Device::enabled_features() (the feature snapshot requested for logical-device creation), not Device::features() (adapter-supported features). Managed layouts validate binding-flag and descriptor-type-specific feature requirements, and managed writes validate descriptor requirements against the same enabled snapshot.
  • DescriptorPool::allocate_untracked (and its backwards-compatible allocate alias) is a raw escape hatch: it retains no layout or binding owners, owns no managed-set token, and is not eligible for transitive managed import. Any raw handles are the caller's responsibility.
  • Managed pool reset and destroy are unsafe caller-synchronized operations. Before reset/destruction, callers must ensure relevant GPU work has completed and exclude all managed-set use, including allocation, writes, graph recording/import, and command-buffer recording/binding. Final pool destruction/drop has the same synchronization requirement. Generation checks are defense-in-depth and do not close concurrent reset/use races; wrapper drops do not prove GPU retirement. Any raw handle returned by raw() escapes these checks and remains the caller's responsibility. Raw/untracked reset and use are likewise caller-synchronized.
  • Descriptor-set layout binding flags are chained into layout creation when the device API version or descriptor-indexing extension supports them.
  • Pipeline cache lifetime and teardown belong to renderer_device; use the device's existing cache rather than adding a resource-crate cache.
  • Renderer shader assets remain under crates/renderer/shaders; this crate does not own those assets.
  • renderer accesses these modules through crate-private aliases. Making a resource type available in renderer's public API requires an explicit API/re-export decision there.

Conventions

  • renderer_resources has no crate-level nightly feature attributes; use APIs available under the workspace's pinned nightly toolchain and edition 2024.
  • Depend on renderer_device paths re-exported at this crate root (crate::Device, crate::device::..., and crate::make_extension) rather than introducing a dependency on renderer.
  • Do not commit changes. Verify with cargo check -p renderer_resources and cargo check --workspace from the workspace root.

Change guidance

  • Add buffer, image, view, descriptor, sampler, shader, and pipeline constructors in this crate, not in renderer's orchestration layer.
  • For each Vulkan handle, preserve the distinction between resource-owned handles and externally owned handles such as raw swapchain images.
  • Keep image-view range/format/kind validation close to image/view construction. Cover REMAINING_MIP_LEVELS and REMAINING_ARRAY_LAYERS normalization when changing range handling.
  • When modifying descriptor equality/hash behavior, update both together and account for name and alloc_scheme deliberately.
  • Preserve renderer_device::device::Allocation ownership semantics when accepting shared or unmanaged memory; ensure bound resources outlive their allocation use.
  • Keep SamplerDesc float equality and hashing consistent across every float field.
  • renderer_resources::util::weak_vec is internal support for images; do not expose or move it back into renderer without reviewing view ownership cycles.
  • Keep shader loading errors compatible with renderer_device::Error, and test path-based loading relative to the workspace runtime working directory.
  • Keep this crate independent of swapchain/window wrappers. Integrate swapchain images through the raw image plus explicit device, format, extent, and usage, not renderer types.
  • Keep pipeline descriptors, Vulkan state conversion, and handle ownership in this crate rather than moving resource ownership back into renderer orchestration.