perf(ui): scope inter-dispatch barrier to swapchain image (#48)
UIRenderer::Dispatch inserted a queue-wide VkMemoryBarrier (SHADER_WRITE -> SHADER_READ|WRITE) before every dispatch after the first, flushing/invalidating all shader caches even though the only hazard is the read-modify-write of the single swapchain storage image that every UI pass shares. Narrow it to a VkImageMemoryBarrier scoped to that image's subresource (COMPUTE -> COMPUTE, GENERAL -> GENERAL, no layout transition). Same correctness — later passes still observe earlier passes' writes — but only this image's caches are flushed; unrelated resources are no longer invalidated. This is a narrower cache flush only: the execution dependency stays compute->compute queue-wide, so passes still do not overlap. Eliminating the barriers entirely requires pass fusion (#47) and is intentionally out of scope here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c58b68ec40
commit
c1fec9e8af
1 changed files with 31 additions and 5 deletions
|
|
@ -182,15 +182,41 @@ void UIRenderer::Dispatch(GraphicsCommandBuffer cmd, const GraphicsComputeShader
|
||||||
const void* push, std::uint32_t pushBytes,
|
const void* push, std::uint32_t pushBytes,
|
||||||
std::uint32_t gx, std::uint32_t gy, std::uint32_t gz) {
|
std::uint32_t gx, std::uint32_t gy, std::uint32_t gz) {
|
||||||
if (!firstDispatchThisFrame_) {
|
if (!firstDispatchThisFrame_) {
|
||||||
VkMemoryBarrier mb {
|
// Every UI pass read-modify-writes the same swapchain storage image
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
// (later passes overdraw earlier ones), so each dispatch must observe
|
||||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
// the previous dispatch's writes. The only resource the hazard touches
|
||||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
|
// is that one image, so scope the dependency to its subresource with a
|
||||||
|
// VkImageMemoryBarrier rather than a queue-wide VkMemoryBarrier: same
|
||||||
|
// correctness, but only this image's shader caches are flushed —
|
||||||
|
// unrelated buffers/images are no longer invalidated. The image stays
|
||||||
|
// in VK_IMAGE_LAYOUT_GENERAL (bound as a storage image), so this is a
|
||||||
|
// pure memory dependency, no layout transition.
|
||||||
|
//
|
||||||
|
// The execution dependency is still COMPUTE->COMPUTE over the whole
|
||||||
|
// queue, so the passes do NOT overlap — this is a narrower cache flush
|
||||||
|
// only. Eliminating the barriers entirely requires fusing the passes
|
||||||
|
// into one dispatch (tracked in #47); do not attempt that here.
|
||||||
|
VkImageMemoryBarrier ib {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
|
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||||
|
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
|
||||||
|
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.image = window_->images[window_->currentBuffer],
|
||||||
|
.subresourceRange = {
|
||||||
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
|
.baseMipLevel = 0,
|
||||||
|
.levelCount = 1,
|
||||||
|
.baseArrayLayer = 0,
|
||||||
|
.layerCount = 1,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
vkCmdPipelineBarrier(cmd,
|
vkCmdPipelineBarrier(cmd,
|
||||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||||
0, 1, &mb, 0, nullptr, 0, nullptr);
|
0, 0, nullptr, 0, nullptr, 1, &ib);
|
||||||
}
|
}
|
||||||
firstDispatchThisFrame_ = false;
|
firstDispatchThisFrame_ = false;
|
||||||
shader.Dispatch(cmd, push, pushBytes, gx, gy, gz);
|
shader.Dispatch(cmd, push, pushBytes, gx, gy, gz);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue