From 9ebc01b3a9fb03618ec7eefee8b34a33fd0798fa Mon Sep 17 00:00:00 2001 From: hydrogendeuteride Date: Fri, 19 Dec 2025 16:41:38 +0900 Subject: [PATCH] EDIT: More tight render graph system(barrier resource cond) --- src/render/graph/graph.cpp | 247 +++++++++++++++++++++++++-------- src/render/graph/resources.cpp | 19 +++ src/render/graph/resources.h | 4 + src/render/graph/types.h | 5 + 4 files changed, 218 insertions(+), 57 deletions(-) diff --git a/src/render/graph/graph.cpp b/src/render/graph/graph.cpp index a169dfd..72056c5 100644 --- a/src/render/graph/graph.cpp +++ b/src/render/graph/graph.cpp @@ -230,20 +230,35 @@ bool RenderGraph::compile() } } - struct ImageState - { - bool initialized = false; - VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; - VkPipelineStageFlags2 stage = VK_PIPELINE_STAGE_2_NONE; - VkAccessFlags2 access = 0; - }; + struct ImageState + { + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + // Accumulate read stages/accesses since last barrier or write. + VkPipelineStageFlags2 readStage = VK_PIPELINE_STAGE_2_NONE; + VkAccessFlags2 readAccess = 0; + // Track last write since last barrier. + VkPipelineStageFlags2 writeStage = VK_PIPELINE_STAGE_2_NONE; + VkAccessFlags2 writeAccess = 0; + }; + + struct BufferState + { + VkPipelineStageFlags2 readStage = VK_PIPELINE_STAGE_2_NONE; + VkAccessFlags2 readAccess = 0; + VkPipelineStageFlags2 writeStage = VK_PIPELINE_STAGE_2_NONE; + VkAccessFlags2 writeAccess = 0; + }; - struct BufferState - { - bool initialized = false; - VkPipelineStageFlags2 stage = VK_PIPELINE_STAGE_2_NONE; - VkAccessFlags2 access = 0; - }; + auto access_has_write = [](VkAccessFlags2 access) -> bool { + constexpr VkAccessFlags2 WRITE_MASK = + VK_ACCESS_2_TRANSFER_WRITE_BIT | + VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | + VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_2_HOST_WRITE_BIT | + VK_ACCESS_2_MEMORY_WRITE_BIT; + return (access & WRITE_MASK) != 0; + }; auto is_depth_format = [](VkFormat format) { switch (format) @@ -410,10 +425,57 @@ bool RenderGraph::compile() } }; - const size_t imageCount = _resources.image_count(); - const size_t bufferCount = _resources.buffer_count(); - std::vector imageStates(imageCount); - std::vector bufferStates(bufferCount); + const size_t imageCount = _resources.image_count(); + const size_t bufferCount = _resources.buffer_count(); + std::vector imageStates(imageCount); + std::vector bufferStates(bufferCount); + + // Seed initial states from imported/transient records. If an imported image has a known + // starting layout but no stage/access, be conservative and assume an unknown prior write. + for (size_t i = 0; i < imageCount; ++i) + { + const RGImageRecord *rec = _resources.get_image(RGImageHandle{static_cast(i)}); + if (!rec) continue; + imageStates[i].layout = rec->initialLayout; + if (rec->initialLayout == VK_IMAGE_LAYOUT_UNDEFINED) continue; + + VkPipelineStageFlags2 st = rec->initialStage; + VkAccessFlags2 ac = rec->initialAccess; + if (st == VK_PIPELINE_STAGE_2_NONE && ac == 0) + { + st = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; + ac = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; + } + if (access_has_write(ac)) + { + imageStates[i].writeStage = st; + imageStates[i].writeAccess = ac; + } + else if (ac != 0) + { + imageStates[i].readStage = st; + imageStates[i].readAccess = ac; + } + } + + for (size_t i = 0; i < bufferCount; ++i) + { + const RGBufferRecord *rec = _resources.get_buffer(RGBufferHandle{static_cast(i)}); + if (!rec) continue; + VkPipelineStageFlags2 st = rec->initialStage; + VkAccessFlags2 ac = rec->initialAccess; + if (st == VK_PIPELINE_STAGE_2_NONE) st = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + if (access_has_write(ac)) + { + bufferStates[i].writeStage = st; + bufferStates[i].writeAccess = ac; + } + else if (ac != 0) + { + bufferStates[i].readStage = st; + bufferStates[i].readAccess = ac; + } + } // Track first/last use for lifetime diagnostics and future aliasing std::vector imageFirst(imageCount, -1), imageLast(imageCount, -1); @@ -457,26 +519,50 @@ bool RenderGraph::compile() ImageUsageInfo desired = usage_info_image(usage); - ImageState prev = imageStates[id]; - VkImageLayout prevLayout = prev.initialized ? prev.layout : _resources.initial_layout(RGImageHandle{id}); - VkPipelineStageFlags2 srcStage = prev.initialized - ? prev.stage - : (prevLayout == VK_IMAGE_LAYOUT_UNDEFINED - ? VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT - : VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT); - VkAccessFlags2 srcAccess = prev.initialized - ? prev.access - : (prevLayout == VK_IMAGE_LAYOUT_UNDEFINED - ? VkAccessFlags2{0} - : (VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT)); + ImageState &state = imageStates[id]; + const VkImageLayout prevLayout = state.layout; + const bool layoutChange = prevLayout != desired.layout; + const bool desiredWrite = access_has_write(desired.access); + const bool prevHasWrite = state.writeAccess != 0; + const bool prevHasReads = state.readAccess != 0; - bool needBarrier = !prev.initialized - || prevLayout != desired.layout - || prev.stage != desired.stage - || prev.access != desired.access; + bool needBarrier = layoutChange || (prevHasReads && desiredWrite); + if (prevHasWrite) + { + // Keep previous behavior: don't force a barrier if we stay in the exact same write state. + if (!(desiredWrite && !layoutChange && state.writeStage == desired.stage && state.writeAccess == desired.access)) + { + needBarrier = true; + } + } if (needBarrier) { + VkPipelineStageFlags2 srcStage = VK_PIPELINE_STAGE_2_NONE; + VkAccessFlags2 srcAccess = 0; + if (prevHasWrite) + { + srcStage = state.writeStage; + srcAccess = state.writeAccess; + } + else if (prevHasReads) + { + srcStage = state.readStage; + srcAccess = state.readAccess; + } + else if (prevLayout == VK_IMAGE_LAYOUT_UNDEFINED) + { + srcStage = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + srcAccess = 0; + } + else + { + // Known layout but unknown access; be conservative. + srcStage = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; + srcAccess = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; + } + if (srcStage == VK_PIPELINE_STAGE_2_NONE) srcStage = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + VkImageMemoryBarrier2 barrier{.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2}; barrier.srcStageMask = srcStage; barrier.srcAccessMask = srcAccess; @@ -525,10 +611,28 @@ bool RenderGraph::compile() } } - imageStates[id].initialized = true; - imageStates[id].layout = desired.layout; - imageStates[id].stage = desired.stage; - imageStates[id].access = desired.access; + if (needBarrier) + { + state.readStage = VK_PIPELINE_STAGE_2_NONE; + state.readAccess = 0; + state.writeStage = VK_PIPELINE_STAGE_2_NONE; + state.writeAccess = 0; + } + state.layout = desired.layout; + if (desiredWrite) + { + state.readStage = VK_PIPELINE_STAGE_2_NONE; + state.readAccess = 0; + state.writeStage = desired.stage; + state.writeAccess = desired.access; + } + else + { + state.writeStage = VK_PIPELINE_STAGE_2_NONE; + state.writeAccess = 0; + state.readStage |= desired.stage; + state.readAccess |= desired.access; + } } if (bufferCount == 0) continue; @@ -563,24 +667,37 @@ bool RenderGraph::compile() BufferUsageInfo desired = usage_info_buffer(usage); - BufferState prev = bufferStates[id]; - VkPipelineStageFlags2 srcStage = prev.initialized - ? prev.stage - : _resources.initial_stage(RGBufferHandle{id}); - if (srcStage == VK_PIPELINE_STAGE_2_NONE) - { - srcStage = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; - } - VkAccessFlags2 srcAccess = prev.initialized - ? prev.access - : _resources.initial_access(RGBufferHandle{id}); + BufferState &state = bufferStates[id]; + const bool desiredWrite = access_has_write(desired.access); + const bool prevHasWrite = state.writeAccess != 0; + const bool prevHasReads = state.readAccess != 0; - bool needBarrier = !prev.initialized - || prev.stage != desired.stage - || prev.access != desired.access; + bool needBarrier = (prevHasReads && desiredWrite); + if (prevHasWrite) + { + // Keep previous behavior: no barrier if staying in the exact same write state. + if (!(desiredWrite && state.writeStage == desired.stage && state.writeAccess == desired.access)) + { + needBarrier = true; + } + } if (needBarrier) { + VkPipelineStageFlags2 srcStage = VK_PIPELINE_STAGE_2_NONE; + VkAccessFlags2 srcAccess = 0; + if (prevHasWrite) + { + srcStage = state.writeStage; + srcAccess = state.writeAccess; + } + else if (prevHasReads) + { + srcStage = state.readStage; + srcAccess = state.readAccess; + } + if (srcStage == VK_PIPELINE_STAGE_2_NONE) srcStage = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + VkBufferMemoryBarrier2 barrier{.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2}; barrier.srcStageMask = srcStage; barrier.srcAccessMask = srcAccess; @@ -616,11 +733,29 @@ bool RenderGraph::compile() } } - bufferStates[id].initialized = true; - bufferStates[id].stage = desired.stage; - bufferStates[id].access = desired.access; + if (needBarrier) + { + state.readStage = VK_PIPELINE_STAGE_2_NONE; + state.readAccess = 0; + state.writeStage = VK_PIPELINE_STAGE_2_NONE; + state.writeAccess = 0; + } + if (desiredWrite) + { + state.readStage = VK_PIPELINE_STAGE_2_NONE; + state.readAccess = 0; + state.writeStage = desired.stage; + state.writeAccess = desired.access; + } + else + { + state.writeStage = VK_PIPELINE_STAGE_2_NONE; + state.writeAccess = 0; + state.readStage |= desired.stage; + state.readAccess |= desired.access; + } + } } - } // Store lifetimes into records for diagnostics/aliasing for (size_t i = 0; i < imageCount; ++i) @@ -760,8 +895,6 @@ void RenderGraph::execute(VkCommandBuffer cmd) { depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; } - if (p.depthAttachment.clearOnLoad) depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - else depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; if (!p.depthAttachment.store) depthInfo.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; hasDepth = true; if (rec->extent.width && rec->extent.height) set_or_clamp(rec->extent); diff --git a/src/render/graph/resources.cpp b/src/render/graph/resources.cpp index cd2557b..ea03c7c 100644 --- a/src/render/graph/resources.cpp +++ b/src/render/graph/resources.cpp @@ -28,6 +28,9 @@ RGImageHandle RGResourceRegistry::add_imported(const RGImportedImageDesc& d) rec.format = d.format; rec.extent = d.extent; rec.initialLayout = d.currentLayout; + // Keep the earliest known stage/access if set; otherwise record provided + if (rec.initialStage == VK_PIPELINE_STAGE_2_NONE) rec.initialStage = d.currentStage; + if (rec.initialAccess == 0) rec.initialAccess = d.currentAccess; return RGImageHandle{it->second}; } @@ -39,6 +42,8 @@ RGImageHandle RGResourceRegistry::add_imported(const RGImportedImageDesc& d) rec.format = d.format; rec.extent = d.extent; rec.initialLayout = d.currentLayout; + rec.initialStage = d.currentStage; + rec.initialAccess = d.currentAccess; _images.push_back(rec); uint32_t id = static_cast(_images.size() - 1); if (d.image != VK_NULL_HANDLE) _imageLookup[d.image] = id; @@ -53,6 +58,8 @@ RGImageHandle RGResourceRegistry::add_transient(const RGImageDesc& d) rec.format = d.format; rec.extent = d.extent; rec.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + rec.initialStage = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + rec.initialAccess = 0; rec.creationUsage = d.usage; VkExtent3D size{ d.extent.width, d.extent.height, 1 }; @@ -171,6 +178,18 @@ VkFormat RGResourceRegistry::image_format(RGImageHandle h) const return rec ? rec->format : VK_FORMAT_UNDEFINED; } +VkPipelineStageFlags2 RGResourceRegistry::initial_stage(RGImageHandle h) const +{ + const RGImageRecord* rec = get_image(h); + return rec ? rec->initialStage : VK_PIPELINE_STAGE_2_NONE; +} + +VkAccessFlags2 RGResourceRegistry::initial_access(RGImageHandle h) const +{ + const RGImageRecord* rec = get_image(h); + return rec ? rec->initialAccess : VkAccessFlags2{0}; +} + VkPipelineStageFlags2 RGResourceRegistry::initial_stage(RGBufferHandle h) const { const RGBufferRecord* rec = get_buffer(h); diff --git a/src/render/graph/resources.h b/src/render/graph/resources.h index e7cb88b..e3f6891 100644 --- a/src/render/graph/resources.h +++ b/src/render/graph/resources.h @@ -19,6 +19,8 @@ struct RGImageRecord VkFormat format = VK_FORMAT_UNDEFINED; VkExtent2D extent{0, 0}; VkImageLayout initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VkPipelineStageFlags2 initialStage = VK_PIPELINE_STAGE_2_NONE; + VkAccessFlags2 initialAccess = 0; VkImageUsageFlags creationUsage = 0; // if transient; 0 for imported // If transient, keep allocation owner for cleanup @@ -75,6 +77,8 @@ public: VkImageLayout initial_layout(RGImageHandle h) const; VkFormat image_format(RGImageHandle h) const; + VkPipelineStageFlags2 initial_stage(RGImageHandle h) const; + VkAccessFlags2 initial_access(RGImageHandle h) const; VkPipelineStageFlags2 initial_stage(RGBufferHandle h) const; VkAccessFlags2 initial_access(RGBufferHandle h) const; diff --git a/src/render/graph/types.h b/src/render/graph/types.h index aa42d04..9e5e56e 100644 --- a/src/render/graph/types.h +++ b/src/render/graph/types.h @@ -64,6 +64,11 @@ struct RGImportedImageDesc VkFormat format = VK_FORMAT_UNDEFINED; VkExtent2D extent{0, 0}; VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; // layout at graph begin + // Optional: last known access state at graph begin. If left as NONE/0 and + // currentLayout is not UNDEFINED, the graph conservatively assumes an + // unknown prior write (ALL_COMMANDS + MEMORY_READ|WRITE) for the first barrier. + VkPipelineStageFlags2 currentStage = VK_PIPELINE_STAGE_2_NONE; + VkAccessFlags2 currentAccess = 0; }; struct RGImportedBufferDesc