DescriptorHeapVulkan: buffer region misplaced when image and buffer descriptor sizes do not divide evenly (latent) #163

Open
opened 2026-10-06 13:25:46 +00:00 by jorijnvdgraaf · 0 comments

Summary

DescriptorHeapVulkan::Initialize places the buffer-descriptor region with an integer division that only works when the image region's byte size is a multiple of the buffer descriptor size. Where it is not, the buffer region overlaps the last image descriptor, or the heap is allocated too small. This is latent: the two drivers I could check have sizes that divide evenly.

Line numbers refer to interfaces/Crafter.Graphics-DescriptorHeapVulkan.cppm at 6aa0338.

The arithmetic (:43-52)

descriptorRegion   = images * imageDescriptorSize + buffers * bufferDescriptorSize;
bufferStartElement = images * imageDescriptorSize / bufferDescriptorSize;   // rounds down
if (images > 0 && bufferStartElement == 0) bufferStartElement = 1;
bufferStartOffset  = bufferStartElement * bufferDescriptorSize;

Two ways this goes wrong:

  1. Overlap. If images * imageDescriptorSize is not a multiple of bufferDescriptorSize, the division rounds down and bufferStartOffset lands inside the image region. Example: image size 24, buffer size 16, 1 image → buffers start at byte 16, the image descriptor occupies bytes 0-23.
  2. Heap too small. If images * imageDescriptorSize < bufferDescriptorSize, the element is forced to 1, so buffers start at bufferDescriptorSize. The heap size was computed from descriptorRegion, which assumed buffers start right after the images. Example: image size 32, buffer size 64, 1 image, 4 buffers → region is 288 bytes, but the buffers occupy bytes 64-319. The tail runs into alignment padding or the reserved range.

bufferDescriptorAlignment is not considered at all. GetBufferOffset / GetBufferOffsetElement (:161-177) repeat the same rounding.

Drivers checked

Driver image size buffer size Affected
NVIDIA 610.57.04 (RTX 4090) 32 16 no
llvmpipe, Mesa 26.2.1 64 16 no

RADV and ANV were not checked. The extension does not promise that one size divides the other.

Suggested fix

Round the start up and size the heap from it:

bufferStartElement = (images * imageDescriptorSize + bufferDescriptorSize - 1) / bufferDescriptorSize;
bufferStartOffset  = bufferStartElement * bufferDescriptorSize;   // also check bufferDescriptorAlignment
descriptorRegion   = bufferStartOffset + buffers * bufferDescriptorSize;

and make the two static helpers share that code.

## Summary `DescriptorHeapVulkan::Initialize` places the buffer-descriptor region with an integer division that only works when the image region's byte size is a multiple of the buffer descriptor size. Where it is not, the buffer region overlaps the last image descriptor, or the heap is allocated too small. This is latent: the two drivers I could check have sizes that divide evenly. Line numbers refer to `interfaces/Crafter.Graphics-DescriptorHeapVulkan.cppm` at `6aa0338`. ## The arithmetic (`:43-52`) ```cpp descriptorRegion = images * imageDescriptorSize + buffers * bufferDescriptorSize; bufferStartElement = images * imageDescriptorSize / bufferDescriptorSize; // rounds down if (images > 0 && bufferStartElement == 0) bufferStartElement = 1; bufferStartOffset = bufferStartElement * bufferDescriptorSize; ``` Two ways this goes wrong: 1. **Overlap.** If `images * imageDescriptorSize` is not a multiple of `bufferDescriptorSize`, the division rounds down and `bufferStartOffset` lands inside the image region. Example: image size 24, buffer size 16, 1 image → buffers start at byte 16, the image descriptor occupies bytes 0-23. 2. **Heap too small.** If `images * imageDescriptorSize < bufferDescriptorSize`, the element is forced to 1, so buffers start at `bufferDescriptorSize`. The heap size was computed from `descriptorRegion`, which assumed buffers start right after the images. Example: image size 32, buffer size 64, 1 image, 4 buffers → region is 288 bytes, but the buffers occupy bytes 64-319. The tail runs into alignment padding or the reserved range. `bufferDescriptorAlignment` is not considered at all. `GetBufferOffset` / `GetBufferOffsetElement` (`:161-177`) repeat the same rounding. ## Drivers checked | Driver | image size | buffer size | Affected | |---|---|---|---| | NVIDIA 610.57.04 (RTX 4090) | 32 | 16 | no | | llvmpipe, Mesa 26.2.1 | 64 | 16 | no | RADV and ANV were not checked. The extension does not promise that one size divides the other. ## Suggested fix Round the start up and size the heap from it: ```cpp bufferStartElement = (images * imageDescriptorSize + bufferDescriptorSize - 1) / bufferDescriptorSize; bufferStartOffset = bufferStartElement * bufferDescriptorSize; // also check bufferDescriptorAlignment descriptorRegion = bufferStartOffset + buffers * bufferDescriptorSize; ``` and make the two static helpers share that code.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Catcrafts/Crafter.Graphics#163
No description provided.