perf(ui): flush only the written descriptor range, not the whole heap (#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>
This commit is contained in:
parent
ab222ffa1f
commit
6d7ad87e38
6 changed files with 236 additions and 7 deletions
|
|
@ -604,6 +604,10 @@ void Device::Initialize() {
|
|||
};
|
||||
vkGetPhysicalDeviceProperties2(physDevice, &properties2);
|
||||
|
||||
// Cache the flush/invalidate alignment for ranged host-memory flushes
|
||||
// (VulkanBuffer::FlushDevice(offset,bytes)).
|
||||
nonCoherentAtomSize = properties2.properties.limits.nonCoherentAtomSize;
|
||||
|
||||
// NVIDIA's brand-new VK_EXT_descriptor_heap acceleration-structure read
|
||||
// path faults (see #7); enable the SPIR-V rewrite workaround there. Other
|
||||
// drivers (and any future fixed NVIDIA driver, once this gate is removed)
|
||||
|
|
|
|||
|
|
@ -67,16 +67,16 @@ void UIRenderer::Initialize(Window& window, GraphicsDescriptorHeap& heap, Graphi
|
|||
WriteFontAtlasDescriptor();
|
||||
}
|
||||
|
||||
// Flush the host-mapped descriptor heaps so the GPU sees what we wrote.
|
||||
for (auto& h : heap_->resourceHeap) h.FlushDevice();
|
||||
for (auto& h : heap_->samplerHeap) h.FlushDevice();
|
||||
// No whole-heap flush here: WriteSwapchainDescriptors / WriteFontAtlasDescriptor
|
||||
// / RegisterSampler each flush their own written byte range above, so the GPU
|
||||
// already sees everything written during setup.
|
||||
|
||||
// Re-bind the swapchain image descriptors after every resize. Window
|
||||
// already drained the queue and rebuilt the imageViews[] before
|
||||
// firing the event, so vkWriteResourceDescriptorsEXT is safe here.
|
||||
// WriteSwapchainDescriptors flushes its own range, so no extra flush needed.
|
||||
resizeSub_.SetEvent(&window.onResize, [this]() {
|
||||
WriteSwapchainDescriptors();
|
||||
for (auto& h : heap_->resourceHeap) h.FlushDevice();
|
||||
});
|
||||
|
||||
(void)initCmd; // reserved for future image-layout tweaks
|
||||
|
|
@ -315,6 +315,12 @@ void UIRenderer::WriteSwapchainDescriptors() {
|
|||
Device::vkWriteResourceDescriptorsEXT(
|
||||
Device::device, Window::numFrames, resources.data(), destinations.data()
|
||||
);
|
||||
// Only the one image descriptor at outImageSlot_ changed in each per-frame
|
||||
// heap (same slot offset in every frame), so flush just that byte range
|
||||
// rather than the whole multi-KB heap.
|
||||
const std::uint32_t off = heap_->ImageByteOffset(outImageSlot_);
|
||||
const std::uint32_t sz = Device::descriptorHeapProperties.imageDescriptorSize;
|
||||
for (auto& h : heap_->resourceHeap) h.FlushDevice(off, sz);
|
||||
}
|
||||
|
||||
void UIRenderer::WriteFontAtlasDescriptor() {
|
||||
|
|
@ -366,7 +372,11 @@ void UIRenderer::WriteSampledImageDescriptor(std::uint16_t slot,
|
|||
Device::vkWriteResourceDescriptorsEXT(
|
||||
Device::device, Window::numFrames, resources.data(), destinations.data()
|
||||
);
|
||||
for (auto& h : heap_->resourceHeap) h.FlushDevice();
|
||||
// One image descriptor written per heap, at the same slot offset — flush
|
||||
// just that range rather than the whole heap.
|
||||
const std::uint32_t off = heap_->ImageByteOffset(slot);
|
||||
const std::uint32_t sz = Device::descriptorHeapProperties.imageDescriptorSize;
|
||||
for (auto& h : heap_->resourceHeap) h.FlushDevice(off, sz);
|
||||
}
|
||||
|
||||
void UIRenderer::WriteBufferDescriptor(std::uint16_t slot, VkDeviceAddress address, std::uint32_t size) {
|
||||
|
|
@ -389,7 +399,11 @@ void UIRenderer::WriteBufferDescriptor(std::uint16_t slot, VkDeviceAddress addre
|
|||
Device::vkWriteResourceDescriptorsEXT(
|
||||
Device::device, Window::numFrames, resources.data(), destinations.data()
|
||||
);
|
||||
for (auto& h : heap_->resourceHeap) h.FlushDevice();
|
||||
// One buffer descriptor written per heap, at the same slot offset — flush
|
||||
// just that range rather than the whole heap.
|
||||
const std::uint32_t off = heap_->BufferByteOffset(slot);
|
||||
const std::uint32_t sz = Device::descriptorHeapProperties.bufferDescriptorSize;
|
||||
for (auto& h : heap_->resourceHeap) h.FlushDevice(off, sz);
|
||||
}
|
||||
|
||||
SamplerSlot UIRenderer::RegisterSampler(const VkSamplerCreateInfo& info) {
|
||||
|
|
@ -406,7 +420,11 @@ SamplerSlot UIRenderer::RegisterSampler(const VkSamplerCreateInfo& info) {
|
|||
Device::vkWriteSamplerDescriptorsEXT(
|
||||
Device::device, Window::numFrames, infos.data(), destinations.data()
|
||||
);
|
||||
for (auto& h : heap_->samplerHeap) h.FlushDevice();
|
||||
// One sampler descriptor written per heap, at the same slot offset — flush
|
||||
// just that range rather than the whole sampler heap.
|
||||
const std::uint32_t off = heap_->SamplerByteOffset(range.firstElement);
|
||||
const std::uint32_t sz = Device::descriptorHeapProperties.samplerDescriptorSize;
|
||||
for (auto& h : heap_->samplerHeap) h.FlushDevice(off, sz);
|
||||
return SamplerSlot{heap_, range.firstElement};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue