perf(ui): flush only the written descriptor range, not the whole heap (#61) #99

Merged
catbot merged 2 commits from claude/issue-61 into master 2026-06-16 20:56:46 +02:00
Member

Problem

WriteBufferDescriptor / WriteSampledImageDescriptor / RegisterSampler (and the swapchain/init paths) each called the whole-buffer FlushDevice() on the multi-KB per-frame descriptor heap after writing a single few-byte descriptor — flushing thousands of bytes of cache to push a handful.

Fix

  • Add VulkanBuffer::FlushDevice(offset, bytes) — flushes only the touched byte range. It rounds the range outward to VkPhysicalDeviceLimits::nonCoherentAtomSize (cached on Device), which vkFlushMappedMemoryRanges requires for a sub-buffer range; the old VK_WHOLE_SIZE path sidestepped that. The end is clamped to the mapping size so the call stays valid even when the allocation size is not atom-aligned (the spec's offset+size == mapping size exception).
  • The issue #60 coherent-memory gate is preserved: coherent memory still skips the flush entirely.
  • WriteSwapchainDescriptors / WriteSampledImageDescriptor / WriteBufferDescriptor / RegisterSampler now flush just their written descriptor range. The redundant whole-heap flushes in UIRenderer::Initialize and the resize callback are removed (each writer self-flushes).

Correctness caveat (from the issue)

The ranged flush respects nonCoherentAtomSize alignment, and the coherent skip is real coherence detection inherited from #60 — not a blanket skip.

Testing

  • New VulkanBufferRangedFlush test drives AlignMappedFlushRange (pure rounding math: sub-atom, straddling, already-aligned, atom==1, tail-clamp) and confirms the ranged FlushDevice keeps the coherent gate — no GPU device needed.
  • Full suite: 14 passed.
  • Verified end-to-end by running HelloUI on a real NVIDIA RTX 4090 with validation layers enabled: font-atlas glyphs, sampler, and storage-image descriptors all flush correctly, no flush/memory-range VUIDs.

Screenshots

HelloUI rendering correctly

Resolves #61

## Problem `WriteBufferDescriptor` / `WriteSampledImageDescriptor` / `RegisterSampler` (and the swapchain/init paths) each called the whole-buffer `FlushDevice()` on the multi-KB per-frame descriptor heap after writing a single few-byte descriptor — flushing thousands of bytes of cache to push a handful. ## Fix - Add `VulkanBuffer::FlushDevice(offset, bytes)` — flushes only the touched byte range. It rounds the range outward to `VkPhysicalDeviceLimits::nonCoherentAtomSize` (cached on `Device`), which `vkFlushMappedMemoryRanges` requires for a sub-buffer range; the old `VK_WHOLE_SIZE` path sidestepped that. The end is clamped to the mapping size so the call stays valid even when the allocation size is not atom-aligned (the spec's `offset+size == mapping size` exception). - The issue #60 coherent-memory gate is preserved: coherent memory still skips the flush entirely. - `WriteSwapchainDescriptors` / `WriteSampledImageDescriptor` / `WriteBufferDescriptor` / `RegisterSampler` now flush just their written descriptor range. The redundant whole-heap flushes in `UIRenderer::Initialize` and the resize callback are removed (each writer self-flushes). ## Correctness caveat (from the issue) The ranged flush respects `nonCoherentAtomSize` alignment, and the coherent skip is real coherence detection inherited from #60 — not a blanket skip. ## Testing - New `VulkanBufferRangedFlush` test drives `AlignMappedFlushRange` (pure rounding math: sub-atom, straddling, already-aligned, atom==1, tail-clamp) and confirms the ranged `FlushDevice` keeps the coherent gate — no GPU device needed. - Full suite: **14 passed**. - Verified end-to-end by running `HelloUI` on a real NVIDIA RTX 4090 with validation layers enabled: font-atlas glyphs, sampler, and storage-image descriptors all flush correctly, no flush/memory-range VUIDs. ## Screenshots ![HelloUI rendering correctly](https://forgejo.catcrafts.net/attachments/a2956963-2f36-46c8-9d2c-63d01c9591ac) Resolves #61
WriteBufferDescriptor / WriteSampledImageDescriptor / RegisterSampler /
WriteSwapchainDescriptors each FlushDevice()'d the entire multi-KB per-frame
descriptor heap after writing one few-byte descriptor. Add a ranged
VulkanBuffer::FlushDevice(offset, bytes) that flushes only the touched bytes,
rounding the range outward to nonCoherentAtomSize as vkFlushMappedMemoryRanges
requires (the WHOLE_SIZE path sidestepped that). The coherent-memory gate from
issue #60 is preserved — coherent memory still skips the flush entirely.

The descriptor writers now self-flush their written range, so the redundant
whole-heap flushes in UIRenderer::Initialize and the resize callback are gone.

Rounding lives in AlignMappedFlushRange (pure math) and is unit-tested without
a device in the new VulkanBufferRangedFlush test; the change was also verified
end-to-end by running HelloUI on a real GPU with validation layers (font-atlas
glyphs, sampler and storage-image descriptors all flush correctly, no VUIDs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	interfaces/Crafter.Graphics-Device.cppm
#	interfaces/Crafter.Graphics-VulkanBuffer.cppm
#	project.cpp
catbot merged commit c3d7f52891 into master 2026-06-16 20:56:46 +02:00
catbot deleted branch claude/issue-61 2026-06-16 20:56:46 +02:00
Sign in to join this conversation.
No reviewers
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!99
No description provided.