EDIT: More tight render graph system(barrier resource cond)

This commit is contained in:
2025-12-19 16:41:38 +09:00
parent 49b36f0df4
commit 9ebc01b3a9
4 changed files with 218 additions and 57 deletions

View File

@@ -230,20 +230,35 @@ bool RenderGraph::compile()
} }
} }
struct ImageState struct ImageState
{ {
bool initialized = false; VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; // Accumulate read stages/accesses since last barrier or write.
VkPipelineStageFlags2 stage = VK_PIPELINE_STAGE_2_NONE; VkPipelineStageFlags2 readStage = VK_PIPELINE_STAGE_2_NONE;
VkAccessFlags2 access = 0; 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 auto access_has_write = [](VkAccessFlags2 access) -> bool {
{ constexpr VkAccessFlags2 WRITE_MASK =
bool initialized = false; VK_ACCESS_2_TRANSFER_WRITE_BIT |
VkPipelineStageFlags2 stage = VK_PIPELINE_STAGE_2_NONE; VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT |
VkAccessFlags2 access = 0; 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) { auto is_depth_format = [](VkFormat format) {
switch (format) switch (format)
@@ -410,10 +425,57 @@ bool RenderGraph::compile()
} }
}; };
const size_t imageCount = _resources.image_count(); const size_t imageCount = _resources.image_count();
const size_t bufferCount = _resources.buffer_count(); const size_t bufferCount = _resources.buffer_count();
std::vector<ImageState> imageStates(imageCount); std::vector<ImageState> imageStates(imageCount);
std::vector<BufferState> bufferStates(bufferCount); std::vector<BufferState> 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<uint32_t>(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<uint32_t>(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 // Track first/last use for lifetime diagnostics and future aliasing
std::vector<int> imageFirst(imageCount, -1), imageLast(imageCount, -1); std::vector<int> imageFirst(imageCount, -1), imageLast(imageCount, -1);
@@ -457,26 +519,50 @@ bool RenderGraph::compile()
ImageUsageInfo desired = usage_info_image(usage); ImageUsageInfo desired = usage_info_image(usage);
ImageState prev = imageStates[id]; ImageState &state = imageStates[id];
VkImageLayout prevLayout = prev.initialized ? prev.layout : _resources.initial_layout(RGImageHandle{id}); const VkImageLayout prevLayout = state.layout;
VkPipelineStageFlags2 srcStage = prev.initialized const bool layoutChange = prevLayout != desired.layout;
? prev.stage const bool desiredWrite = access_has_write(desired.access);
: (prevLayout == VK_IMAGE_LAYOUT_UNDEFINED const bool prevHasWrite = state.writeAccess != 0;
? VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT const bool prevHasReads = state.readAccess != 0;
: 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));
bool needBarrier = !prev.initialized bool needBarrier = layoutChange || (prevHasReads && desiredWrite);
|| prevLayout != desired.layout if (prevHasWrite)
|| prev.stage != desired.stage {
|| prev.access != desired.access; // 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) 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}; VkImageMemoryBarrier2 barrier{.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2};
barrier.srcStageMask = srcStage; barrier.srcStageMask = srcStage;
barrier.srcAccessMask = srcAccess; barrier.srcAccessMask = srcAccess;
@@ -525,10 +611,28 @@ bool RenderGraph::compile()
} }
} }
imageStates[id].initialized = true; if (needBarrier)
imageStates[id].layout = desired.layout; {
imageStates[id].stage = desired.stage; state.readStage = VK_PIPELINE_STAGE_2_NONE;
imageStates[id].access = desired.access; 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; if (bufferCount == 0) continue;
@@ -563,24 +667,37 @@ bool RenderGraph::compile()
BufferUsageInfo desired = usage_info_buffer(usage); BufferUsageInfo desired = usage_info_buffer(usage);
BufferState prev = bufferStates[id]; BufferState &state = bufferStates[id];
VkPipelineStageFlags2 srcStage = prev.initialized const bool desiredWrite = access_has_write(desired.access);
? prev.stage const bool prevHasWrite = state.writeAccess != 0;
: _resources.initial_stage(RGBufferHandle{id}); const bool prevHasReads = state.readAccess != 0;
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});
bool needBarrier = !prev.initialized bool needBarrier = (prevHasReads && desiredWrite);
|| prev.stage != desired.stage if (prevHasWrite)
|| prev.access != desired.access; {
// 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) 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}; VkBufferMemoryBarrier2 barrier{.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2};
barrier.srcStageMask = srcStage; barrier.srcStageMask = srcStage;
barrier.srcAccessMask = srcAccess; barrier.srcAccessMask = srcAccess;
@@ -616,11 +733,29 @@ bool RenderGraph::compile()
} }
} }
bufferStates[id].initialized = true; if (needBarrier)
bufferStates[id].stage = desired.stage; {
bufferStates[id].access = desired.access; 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 // Store lifetimes into records for diagnostics/aliasing
for (size_t i = 0; i < imageCount; ++i) for (size_t i = 0; i < imageCount; ++i)
@@ -760,8 +895,6 @@ void RenderGraph::execute(VkCommandBuffer cmd)
{ {
depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; 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; if (!p.depthAttachment.store) depthInfo.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
hasDepth = true; hasDepth = true;
if (rec->extent.width && rec->extent.height) set_or_clamp(rec->extent); if (rec->extent.width && rec->extent.height) set_or_clamp(rec->extent);

View File

@@ -28,6 +28,9 @@ RGImageHandle RGResourceRegistry::add_imported(const RGImportedImageDesc& d)
rec.format = d.format; rec.format = d.format;
rec.extent = d.extent; rec.extent = d.extent;
rec.initialLayout = d.currentLayout; 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}; return RGImageHandle{it->second};
} }
@@ -39,6 +42,8 @@ RGImageHandle RGResourceRegistry::add_imported(const RGImportedImageDesc& d)
rec.format = d.format; rec.format = d.format;
rec.extent = d.extent; rec.extent = d.extent;
rec.initialLayout = d.currentLayout; rec.initialLayout = d.currentLayout;
rec.initialStage = d.currentStage;
rec.initialAccess = d.currentAccess;
_images.push_back(rec); _images.push_back(rec);
uint32_t id = static_cast<uint32_t>(_images.size() - 1); uint32_t id = static_cast<uint32_t>(_images.size() - 1);
if (d.image != VK_NULL_HANDLE) _imageLookup[d.image] = id; 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.format = d.format;
rec.extent = d.extent; rec.extent = d.extent;
rec.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; rec.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
rec.initialStage = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT;
rec.initialAccess = 0;
rec.creationUsage = d.usage; rec.creationUsage = d.usage;
VkExtent3D size{ d.extent.width, d.extent.height, 1 }; 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; 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 VkPipelineStageFlags2 RGResourceRegistry::initial_stage(RGBufferHandle h) const
{ {
const RGBufferRecord* rec = get_buffer(h); const RGBufferRecord* rec = get_buffer(h);

View File

@@ -19,6 +19,8 @@ struct RGImageRecord
VkFormat format = VK_FORMAT_UNDEFINED; VkFormat format = VK_FORMAT_UNDEFINED;
VkExtent2D extent{0, 0}; VkExtent2D extent{0, 0};
VkImageLayout initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkImageLayout initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkPipelineStageFlags2 initialStage = VK_PIPELINE_STAGE_2_NONE;
VkAccessFlags2 initialAccess = 0;
VkImageUsageFlags creationUsage = 0; // if transient; 0 for imported VkImageUsageFlags creationUsage = 0; // if transient; 0 for imported
// If transient, keep allocation owner for cleanup // If transient, keep allocation owner for cleanup
@@ -75,6 +77,8 @@ public:
VkImageLayout initial_layout(RGImageHandle h) const; VkImageLayout initial_layout(RGImageHandle h) const;
VkFormat image_format(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; VkPipelineStageFlags2 initial_stage(RGBufferHandle h) const;
VkAccessFlags2 initial_access(RGBufferHandle h) const; VkAccessFlags2 initial_access(RGBufferHandle h) const;

View File

@@ -64,6 +64,11 @@ struct RGImportedImageDesc
VkFormat format = VK_FORMAT_UNDEFINED; VkFormat format = VK_FORMAT_UNDEFINED;
VkExtent2D extent{0, 0}; VkExtent2D extent{0, 0};
VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; // layout at graph begin 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 struct RGImportedBufferDesc