DescriptorHeapVulkan
- The buffer region started at images * imageDescriptorSize /
bufferDescriptorSize, rounded down. A descriptor_heap array indexes
the heap from byte 0 in strides of its descriptor size, so buffer slot
0 has to sit on a whole buffer descriptor past the images. Where the
image descriptors were not a whole number of buffer descriptors, the
first buffer descriptors overwrote the last image descriptors. Where
they were less than one, the start was bumped to descriptor 1, but the
heap was still sized as if the buffers began right after the images,
so the last buffer descriptors went into the implementation's reserved
range. Every descriptor size is a power of two, so both cases need
image descriptors smaller than buffer descriptors, which NVIDIA (32
and 16 bytes) and llvmpipe (64 and 16) do not have. The start now
rounds up and the heap is sized from it (#163).
- The reserved range sat at a multiple of imageDescriptorAlignment only,
where vkCmdBindResourceHeapEXT also wants bufferDescriptorAlignment
(VUID-vkCmdBindResourceHeapEXT-pBindInfo-11435 and -11436). It now
starts on a multiple of the larger. The buffer descriptors need no more
than the rounding: each alignment is at most its descriptor size, so a
whole number of descriptors is aligned.
- bufferStartElement is 16 bits and took the division unchecked, so an
image region past 65535 buffer descriptors (16384 images on llvmpipe)
wrapped the buffer region around onto the images. Slot counts whose
buffer indices do not fit 16 bits, 0xFFFF being the empty slot, now
throw, naming the counts. A heap without buffer slots has no buffer
region, which starts at 0 as in descriptor-set mode, so image-only
heaps have no index to fit.
- On a driver that reserves nothing (llvmpipe), a heap without sampler
slots was a zero-size buffer, which every Initialize(1, 1, 0) made,
and Window::Render bound its address each frame. Each heap now holds at
least one descriptor, as each binding does in descriptor-set mode. That
grows NVIDIA's sampler heap by one descriptor when no sampler slots are
asked for; the resource heaps of the slot counts in the tree keep their
layouts byte for byte.
- ComputeLayout holds the arithmetic, pure over the descriptor-heap
properties, and Initialize goes through it. GetBufferOffset and
GetBufferOffsetElement, which had their own copies of the old
arithmetic and never had a caller, are gone.
- Bind(cmd, frame) binds a frame's heaps with the reserved ranges
Initialize stored, or in descriptor-set mode the frame's descriptor
set. Window::Render and the RTVolume example worked the reserved-range
offset out from the heap's size with the image-alignment mask, and
call Bind now. The window's per-heap cache of bind structs goes with
it: keyed on the heap pointer, it kept binding the old buffers after a
re-Initialize that reallocated them. The heap still ends with its
reserved range, so code that derives the offset from the size keeps
getting the right one.
Tests
- DescriptorHeapLayout: the issue's two layouts, the reserved range where
buffer descriptors are aligned more strictly than image ones, every
legal combination of descriptor sizes and alignments up to 256 bytes,
no heap empty, the NVIDIA and llvmpipe layouts, the 16-bit limit and
image-only heaps. Pure CPU, no device. Against the old arithmetic it
fails 12 of its 18 checks.
Docs
- README.md's heap bullet and AGENTS.consumer.md say how to bind the
heap outside the frame loop, and the consumer notes that Initialize
throws when buffer indices would pass 16 bits. AGENTS.md lists
ComputeLayout among the functions the pure-logic tests drive.
Closes#163.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both landed on master while the guide was being written: ExampleShaders
compiles every example shader with glslang and needs no GPU,
WindowOptionalGlobals needs a compositor. The test section names the
tests in each group, so it names these two as well.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
AGENTS.md
- Guidance for agents and contributors working on Crafter.Graphics: the
layout and the partitions by backend, the dev environment and the
targets, the build and test commands, how tests are declared and what
each group needs, code style, the design rules the code embodies,
commit conventions, security notes and gotchas. It carries the
dependency-notes section crafter-build 3.0.0 writes, holding
Crafter.Math's notes.
AGENTS.consumer.md
- What an agent working in a consumer needs: depending on the library,
the program shape and the frame order, device features, lifetimes, the
heap and its descriptor-set fallback, buffers and images, the UI tiers,
custom compute shaders, ray tracing, input, the DOM build, the
environment variables, testing a consumer, and which example to copy.
crafter-build injects it into consumers' AGENTS.md on their builds.
Both were reviewed against the code. The review found that --timing no
longer compiles (#178), which both files note.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>