feat: RenderGraph 帧历史资源(ReadHistory)+ TemporalDiff 验证 pass
- PassBuilder::ReadHistory:读上一帧槽位 ((slot-1+F) mod F) 的物理镜像, TAA/运动模糊等时域效果的前置能力,RG 最后一个已知边界补齐 - 历史读不建拓扑边(跨帧依赖由同队列提交序保证)、不入本帧状态链、 不改生命周期;剔除照常参与(生产者存活判定) - 历史 barrier:首帧走 first(old=Undefined),之后走 steady 且 src=ALL_COMMANDS|MEMORY_WRITE 显式闭合跨帧可用性+可见性; 有历史读的资源,常规 entry steady 的 src 同样升级(历史读打破了 同槽位 fence 全等保前提) - TemporalDiff 调试 pass:|上一帧−当前帧|×增益,静止=黑/运动边缘发光 即链路正确;Tonemap binding2 经 push 开关切调试视图 - 编译零错误、validation 零警告、8 秒冒烟通过(9 pass 全活跃)
This commit is contained in:
@@ -130,6 +130,23 @@ namespace Engine::RG {
|
||||
return *this;
|
||||
}
|
||||
|
||||
RenderGraph::PassBuilder& RenderGraph::PassBuilder::ReadHistory(
|
||||
TextureHandle h, ResourceUse use, vk::ShaderStageFlags stage)
|
||||
{
|
||||
m_Graph.CheckMutable();
|
||||
m_Graph.CheckHandle(h);
|
||||
auto& entry = m_Graph.m_Textures[h.index];
|
||||
if (entry.imported) {
|
||||
// swapchain image 不按槽位持久存在,历史读无意义
|
||||
throw std::runtime_error(
|
||||
"RenderGraph: history read of imported resource '" + entry.name +
|
||||
"' is not supported");
|
||||
}
|
||||
entry.hasHistoryUse = true;
|
||||
m_Graph.m_Passes[m_PassIndex].uses.push_back({h.index, use, stage, true});
|
||||
return *this;
|
||||
}
|
||||
|
||||
RenderGraph::PassBuilder& RenderGraph::PassBuilder::SideEffect()
|
||||
{
|
||||
m_Graph.CheckMutable();
|
||||
@@ -265,6 +282,7 @@ namespace Engine::RG {
|
||||
|
||||
bool keep = pass.sideEffect;
|
||||
for (const auto& use : pass.uses) {
|
||||
// 帧历史读照常参与:它消费了上一帧的产出,生产者必须存活
|
||||
if (IsWriteUse(use.use) && readByKept[use.resIndex]) {
|
||||
keep = true;
|
||||
} else if (m_Textures[use.resIndex].imported) {
|
||||
@@ -317,6 +335,11 @@ namespace Engine::RG {
|
||||
auto& lw = lastWriter[use.resIndex];
|
||||
auto& readers = pendingReaders[use.resIndex];
|
||||
|
||||
// 帧历史读不建边:它消费的是上一帧槽位的物理镜像(与本帧
|
||||
// 任何写操作无资源竞争),跨帧依赖由同队列提交序 + 历史
|
||||
// barrier(ALL_COMMANDS 可见性同步)保证
|
||||
if (use.history) continue;
|
||||
|
||||
if (IsWriteUse(use.use)) {
|
||||
for (u32 r : readers) addEdge(r, i); // WAR
|
||||
readers.clear();
|
||||
@@ -363,6 +386,7 @@ namespace Engine::RG {
|
||||
for (u32 pos = 0; pos < m_ExecutionOrder.size(); ++pos) {
|
||||
const u32 passIdx = m_ExecutionOrder[pos];
|
||||
for (const auto& use : m_Passes[passIdx].uses) {
|
||||
if (use.history) continue; // 帧历史读不改变本帧状态链与生命周期
|
||||
auto& entry = m_Textures[use.resIndex];
|
||||
if (entry.firstPass < 0) entry.firstPass = static_cast<i32>(pos);
|
||||
entry.lastPass = static_cast<i32>(pos);
|
||||
@@ -387,6 +411,7 @@ namespace Engine::RG {
|
||||
// ---- Pass A:状态链推演 ----
|
||||
for (const u32 passIdx : m_ExecutionOrder) {
|
||||
for (const auto& use : m_Passes[passIdx].uses) {
|
||||
if (use.history) continue; // 历史读操作另一槽位的镜像,不入本帧链
|
||||
auto& entry = m_Textures[use.resIndex];
|
||||
entry.chainLayout = UseLayout(use.use);
|
||||
entry.chainStage = UseDstStage(use);
|
||||
@@ -416,6 +441,36 @@ namespace Engine::RG {
|
||||
for (const auto& use : pass.uses) {
|
||||
auto& entry = m_Textures[use.resIndex];
|
||||
|
||||
// ---- 帧历史读:操作上一帧槽位的物理镜像,不进入本帧状态链。
|
||||
// first = 该槽位镜像从未用过(图刚编译/重编译),old=Undefined
|
||||
// steady = 跨帧可见性同步:src=ALL_COMMANDS|MEMORY_WRITE
|
||||
// 覆盖上一帧提交对该镜像的全部写入(同队列提交序
|
||||
// 保证 barrier 执行时上一帧命令已全部完成,
|
||||
// ALL_COMMANDS 把可用性+可见性显式闭合),
|
||||
// old/new = resting layout(历史镜像静止在读布局)
|
||||
if (use.history) {
|
||||
const vk::ImageLayout desiredLayout = UseLayout(use.use);
|
||||
const vk::PipelineStageFlags2 dstStage = UseDstStage(use);
|
||||
const vk::AccessFlags2 dstAccess = UseDstAccess(use);
|
||||
|
||||
vk::ImageMemoryBarrier2 first;
|
||||
first.setSrcStageMask(vk::PipelineStageFlagBits2::eNone)
|
||||
.setSrcAccessMask(vk::AccessFlagBits2::eNone)
|
||||
.setDstStageMask(dstStage)
|
||||
.setDstAccessMask(dstAccess)
|
||||
.setOldLayout(vk::ImageLayout::eUndefined)
|
||||
.setNewLayout(desiredLayout)
|
||||
.setSubresourceRange({entry.aspect, 0, 1, 0, 1});
|
||||
|
||||
vk::ImageMemoryBarrier2 steady = first;
|
||||
steady.setSrcStageMask(vk::PipelineStageFlagBits2::eAllCommands)
|
||||
.setSrcAccessMask(vk::AccessFlagBits2::eMemoryWrite)
|
||||
.setOldLayout(entry.restingLayout);
|
||||
|
||||
pass.barriers.push_back({use.resIndex, steady, first, true});
|
||||
continue; // 不推进 chain:本帧链只描述当前槽位镜像
|
||||
}
|
||||
|
||||
const vk::ImageLayout desiredLayout = UseLayout(use.use);
|
||||
const vk::PipelineStageFlags2 dstStage = UseDstStage(use);
|
||||
const vk::AccessFlags2 dstAccess = UseDstAccess(use);
|
||||
@@ -451,6 +506,17 @@ namespace Engine::RG {
|
||||
steady = makeBarrier(entry.restingLayout,
|
||||
entry.restingStage,
|
||||
entry.restingAccess);
|
||||
if (entry.hasHistoryUse) {
|
||||
// 历史读打破了"同槽位两次进入之间有 fence 全等保"
|
||||
// 的前提(上一帧对该槽位镜像的历史读不在本槽位
|
||||
// fence 的保护范围内),src 升级为 ALL_COMMANDS
|
||||
// 覆盖跨帧的一切触碰
|
||||
steady.setSrcStageMask(
|
||||
vk::PipelineStageFlagBits2::eAllCommands)
|
||||
.setSrcAccessMask(
|
||||
vk::AccessFlagBits2::eMemoryWrite |
|
||||
vk::AccessFlagBits2::eMemoryRead);
|
||||
}
|
||||
}
|
||||
|
||||
pass.barriers.push_back({use.resIndex, steady, first});
|
||||
@@ -481,7 +547,16 @@ namespace Engine::RG {
|
||||
void RenderGraph::AllocatePhysical()
|
||||
{
|
||||
for (auto& entry : m_Textures) {
|
||||
if (entry.imported || entry.firstPass < 0) continue;
|
||||
if (entry.imported) continue;
|
||||
|
||||
// 帧历史校验:历史资源必须有同帧正常使用(否则既无产出也无镜像)
|
||||
if (entry.hasHistoryUse && entry.firstPass < 0) {
|
||||
throw std::runtime_error(
|
||||
"RenderGraph: history-read resource '" + entry.name +
|
||||
"' has no same-frame use (nothing to read history of)");
|
||||
}
|
||||
|
||||
if (entry.firstPass < 0) continue;
|
||||
|
||||
// 显式尺寸优先;否则 = 目标尺寸 × sizeScale(后处理降采样)
|
||||
vk::Extent2D extent{
|
||||
@@ -649,21 +724,27 @@ namespace Engine::RG {
|
||||
for (const auto& bp : pass.barriers) {
|
||||
auto& entry = m_Textures[bp.resIndex];
|
||||
|
||||
u32 imageSlot = frame.frameSlot;
|
||||
bool useFirst;
|
||||
if (entry.imported) {
|
||||
useFirst = true; // 恒为 Undefined 起点
|
||||
} else if (bp.history) {
|
||||
// 帧历史:操作上一帧槽位的物理镜像。
|
||||
// used 标记持久(跨帧),首帧走 first,之后走 steady
|
||||
imageSlot = HistorySlot(frame.frameSlot);
|
||||
useFirst = !entry.slots[imageSlot].used;
|
||||
entry.slots[imageSlot].used = true;
|
||||
} else {
|
||||
auto& slot = entry.slots[frame.frameSlot];
|
||||
useFirst = !slot.used;
|
||||
slot.used = true;
|
||||
useFirst = !entry.slots[imageSlot].used;
|
||||
entry.slots[imageSlot].used = true;
|
||||
}
|
||||
|
||||
vk::ImageMemoryBarrier2 b = useFirst ? bp.first : bp.steady;
|
||||
// image 句柄在执行期绑定:imported 用当帧 acquire 的 image,
|
||||
// created 用当前帧槽位的物理镜像(每个槽位各有一份)
|
||||
// created 用当前帧槽位(历史 barrier 用上一帧槽位)的物理镜像
|
||||
b.setImage(entry.imported
|
||||
? entry.importedImage
|
||||
: entry.physical[frame.frameSlot]->image);
|
||||
: entry.physical[imageSlot]->image);
|
||||
m_ScratchBarriers.push_back(b);
|
||||
}
|
||||
|
||||
@@ -707,6 +788,11 @@ namespace Engine::RG {
|
||||
return entry.physical[frame.frameSlot]->image;
|
||||
}
|
||||
|
||||
u32 RenderGraphContext::HistorySlot(u32 frameSlot) const
|
||||
{
|
||||
return graph->HistorySlot(frameSlot);
|
||||
}
|
||||
|
||||
// ---------- 工具 / 统计 ----------
|
||||
|
||||
TextureEntry& RenderGraph::GetEntry(TextureHandle h)
|
||||
|
||||
Reference in New Issue
Block a user