Files
HelloVulkan/Engine/src/RenderGraph/RenderGraph.cpp
T
atdunbg 27b7f8785c feat: TAA 时域抗锯齿(velocity + 邻域 clamp + 子像素抖动)
- Velocity buffer:Forward 第二颜色附件,vertex 计算 clip 空间运动向量
  (UBO 尾部追加 projNoJitter/prevView/prevProj;上一帧实例矩阵 SSBO
  Set0 binding2,与当前实例数组平行对齐——转动的物体速度正确不鬼影)
- 子像素抖动:SceneRenderer Halton(2,3)×8 序列,抖动只进光栅化投影
- TAA resolve pass:velocity 反推历史位置 + 3×3 邻域 clamp 去鬼影 +
  mix(cur,hist,0.9) 时域累积;TAAColor 自引用历史
- RG 历史读补 post-barrier:采样后恢复镜像 resting layout(TAA 的历史
  是 ColorAttachment layout,不恢复则下帧 entry oldLayout 脱节)
- 管线:Forward(HDR+Velocity) → TAA(TAAColor) → Bloom/Tonemap 改源 TAAColor
- 编译零错误、validation 零警告、8 秒冒烟通过(10 pass 全活跃)
2026-09-23 13:29:31 +08:00

996 lines
39 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Created by atdunbg on 2026/9/22.
//
#include <Engine/RenderGraph/RenderGraph.h>
#include <Engine/Vulkan/Device.h>
#include <Engine/Core/Log.h>
#include <algorithm>
#include <set>
#include <stdexcept>
namespace Engine::RG {
namespace {
bool IsWriteUse(ResourceUse use)
{
switch (use) {
case ResourceUse::ColorAttachment:
case ResourceUse::DepthAttachment:
case ResourceUse::TransferDst:
case ResourceUse::Present:
return true;
case ResourceUse::ShaderRead:
case ResourceUse::TransferSrc:
return false;
}
return false;
}
} // namespace
// ---------- 构造 / 析构 ----------
RenderGraph::RenderGraph(Device& device, MemoryAllocator& allocator, u32 framesInFlight)
: m_Device(device), m_Allocator(allocator), m_FramesInFlight(framesInFlight)
{
}
RenderGraph::~RenderGraph()
{
ReleasePhysical();
const auto& vkDevice = m_Device.GetDevice();
for (auto& [key, images] : m_Pool) {
for (auto& img : images) {
if (!img->valid) continue;
vkDevice.destroyImageView(img->view);
m_Allocator.Destroy(img->allocation); // image 资源与显存成对释放
}
}
m_Pool.clear();
}
// ---------- 资源声明 ----------
TextureHandle RenderGraph::CreateTexture(std::string name, const TextureDesc& desc)
{
CheckMutable();
TextureHandle h{static_cast<u32>(m_Textures.size()), 1};
TextureEntry entry;
entry.name = std::move(name);
entry.index = h.index;
entry.desc = desc;
entry.imported = false;
entry.generation = h.generation;
entry.physical.resize(m_FramesInFlight, nullptr);
entry.slots.resize(m_FramesInFlight);
m_Textures.push_back(std::move(entry));
return h;
}
TextureHandle RenderGraph::ImportSwapchain(std::string name, vk::Format format)
{
CheckMutable();
TextureHandle h{static_cast<u32>(m_Textures.size()), 1};
TextureEntry entry;
entry.name = std::move(name);
entry.index = h.index;
entry.desc.format = format;
entry.imported = true;
entry.generation = h.generation;
entry.physical.resize(m_FramesInFlight, nullptr);
entry.slots.resize(m_FramesInFlight);
m_Textures.push_back(std::move(entry));
return h;
}
void RenderGraph::SetImportedImage(TextureHandle h, vk::Image image, vk::ImageView view)
{
auto& entry = GetEntry(h);
if (!entry.imported) {
ENGINE_FATAL("RenderGraph: SetImportedImage on non-imported resource '{}'", entry.name);
}
entry.importedImage = image;
entry.importedView = view;
}
// ---------- Pass 声明 ----------
RenderGraph::PassBuilder RenderGraph::AddPass(std::string name, ExecuteFn exec)
{
CheckMutable();
PassNode node;
node.name = std::move(name);
node.exec = std::move(exec);
m_Passes.push_back(std::move(node));
return PassBuilder(*this, static_cast<u32>(m_Passes.size() - 1));
}
RenderGraph::PassBuilder& RenderGraph::PassBuilder::Write(TextureHandle h, ResourceUse use)
{
m_Graph.CheckMutable();
m_Graph.m_Passes[m_PassIndex].uses.push_back({h.index, use, {}});
return *this;
}
RenderGraph::PassBuilder& RenderGraph::PassBuilder::Read(TextureHandle h, ResourceUse use,
vk::ShaderStageFlags stage)
{
m_Graph.CheckMutable();
m_Graph.m_Passes[m_PassIndex].uses.push_back({h.index, use, stage});
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();
m_Graph.m_Passes[m_PassIndex].sideEffect = true;
return *this;
}
// ---------- 编译 ----------
void RenderGraph::Compile()
{
if (m_TargetExtent.width == 0 || m_TargetExtent.height == 0) {
ENGINE_FATAL("RenderGraph: Compile before SetTargetExtent");
}
ReleasePhysical();
// 清理上一次编译的中间结果
for (auto& entry : m_Textures) {
entry.usage = {};
entry.firstPass = -1;
entry.lastPass = -1;
entry.chainLayout = vk::ImageLayout::eUndefined;
entry.chainStage = {};
entry.chainAccess = {};
entry.chainWasWrite = false;
}
for (auto& pass : m_Passes) {
pass.culled = false;
pass.barriers.clear();
pass.postBarriers.clear();
}
for (auto& entry : m_Textures) ResolveUsage(entry);
// 每 pass 渲染尺寸 = 其写入附件的最大分辨率(半分辨率后处理的
// viewport/渲染区域由此决定);无写入(如 Present)回退目标尺寸
for (auto& pass : m_Passes) {
u32 w = 0, h = 0;
for (const auto& use : pass.uses) {
if (!IsWriteUse(use.use)) continue;
const auto& d = m_Textures[use.resIndex].desc;
w = std::max(w, d.width ? d.width
: std::max(1u, u32(m_TargetExtent.width * d.sizeScale)));
h = std::max(h, d.height ? d.height
: std::max(1u, u32(m_TargetExtent.height * d.sizeScale)));
}
pass.extent = vk::Extent2D(w ? w : m_TargetExtent.width,
h ? h : m_TargetExtent.height);
}
CullPasses();
Toposort();
ComputeLifetimes();
PrecomputeBarriers();
AllocatePhysical();
m_Compiled = true;
m_ActivePassCount = static_cast<u32>(
std::count_if(m_Passes.begin(), m_Passes.end(),
[](const PassNode& p) { return !p.culled; }));
ENGINE_INFO("RenderGraph compiled: {} passes ({} active), {} textures, {} barriers, {} gpu images",
m_Passes.size(), m_ActivePassCount, m_Textures.size(),
GetBarrierCount(), GetPooledImageCount());
}
void RenderGraph::SetTargetExtent(vk::Extent2D extent)
{
if (extent == m_TargetExtent) return;
// 尺寸变化:调用方必须保证 GPU 空闲(通常在 swapchain recreate 的 waitIdle 之后)
ReleasePhysical();
PurgePool(extent);
// 物理镜像全部换新,槽位状态清零
for (auto& entry : m_Textures) {
for (auto& slot : entry.slots) slot.used = false;
}
m_TargetExtent = extent;
Compile();
}
void RenderGraph::ResolveUsage(TextureEntry& entry)
{
if (entry.imported) return;
vk::ImageUsageFlags usage{};
for (const auto& pass : m_Passes) {
for (const auto& use : pass.uses) {
if (use.resIndex != entry.index) continue;
switch (use.use) {
case ResourceUse::ColorAttachment:
case ResourceUse::Present:
usage |= vk::ImageUsageFlagBits::eColorAttachment;
break;
case ResourceUse::DepthAttachment:
usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
entry.aspect = vk::ImageAspectFlagBits::eDepth;
break;
case ResourceUse::ShaderRead:
usage |= vk::ImageUsageFlagBits::eSampled;
break;
case ResourceUse::TransferSrc:
usage |= vk::ImageUsageFlagBits::eTransferSrc;
break;
case ResourceUse::TransferDst:
usage |= vk::ImageUsageFlagBits::eTransferDst;
break;
}
}
}
entry.usage = usage;
}
void RenderGraph::CullPasses()
{
// 引用计数剔除:一个 pass 存活当且仅当
// a) 声明了 sideEffect,或
// b) 它读写某个导入资源(对外可见),或
// c) 它写出的某个资源被某个存活 pass 读取。
// 迭代到不动点(最多 pass 数轮)。
std::vector<bool> kept(m_Passes.size(), false);
std::vector<bool> readByKept(m_Textures.size(), false);
bool changed = true;
while (changed) {
changed = false;
for (u32 i = 0; i < m_Passes.size(); ++i) {
if (kept[i]) continue;
const auto& pass = m_Passes[i];
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) {
keep = true;
}
}
if (!keep) continue;
kept[i] = true;
changed = true;
for (const auto& use : pass.uses) {
if (!IsWriteUse(use.use)) readByKept[use.resIndex] = true;
}
}
}
for (u32 i = 0; i < m_Passes.size(); ++i) {
m_Passes[i].culled = !kept[i];
}
}
void RenderGraph::Toposort()
{
// 按资源在声明序上的使用序列建边,Kahn 拓扑排序。
// RAW:reader → 依赖声明序上它的上一个 writer
// WAR:writer → 依赖上一次 write 之后出现过的所有 reader
// WAW:writer → 上一个 writer
// 多写者(ping-pong 资源)合法:若对 reader 连接"所有"
// writer(含声明序在其后的),BlurH 会同时收到 Extract→BlurH 与
// BlurV→BlurH,而 BlurH→BlurV 又存在,直接成环。
// 声明顺序通常已是拓扑序,但引擎级实现必须显式校验。
const u32 N = static_cast<u32>(m_Passes.size());
std::vector<std::set<u32>> adj(N);
std::vector<u32> indegree(N, 0);
auto addEdge = [&](u32 from, u32 to) {
if (from == to) return;
if (adj[from].insert(to).second) ++indegree[to];
};
constexpr u32 kNoWriter = UINT32_MAX;
std::vector<u32> lastWriter(m_Textures.size(), kNoWriter);
std::vector<std::set<u32>> pendingReaders(m_Textures.size());
for (u32 i = 0; i < N; ++i) {
if (m_Passes[i].culled) continue;
for (const auto& use : m_Passes[i].uses) {
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();
if (lw != kNoWriter) addEdge(lw, i); // WAW
lw = i;
} else {
if (lw != kNoWriter) addEdge(lw, i); // RAW
readers.insert(i);
}
}
}
m_ExecutionOrder.clear();
m_ExecutionOrder.reserve(m_Passes.size());
// 同层按声明序出队,保证确定性
std::set<u32> ready;
for (u32 i = 0; i < N; ++i) {
if (!m_Passes[i].culled && indegree[i] == 0) ready.insert(i);
}
while (!ready.empty()) {
u32 p = *ready.begin();
ready.erase(ready.begin());
m_ExecutionOrder.push_back(p);
for (u32 q : adj[p]) {
if (--indegree[q] == 0) ready.insert(q);
}
}
u32 activeCount = 0;
for (const auto& pass : m_Passes) {
if (!pass.culled) ++activeCount;
}
if (m_ExecutionOrder.size() != activeCount) {
ENGINE_FATAL("RenderGraph: cycle detected in pass declarations");
}
}
void RenderGraph::ComputeLifetimes()
{
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);
}
}
}
void RenderGraph::PrecomputeBarriers()
{
// 两阶段:
// Pass A:按执行序推演每个资源的状态链,得到稳态(resting)。
// Pass B:真正生成 barrier。
//
// entry barrier(资源在该 pass 首次被使用)有两个变体:
// first — 该物理实例的槽位尚未用过:old = Undefined(无历史依赖)
// steady — 槽位已用过:old = resting(上一轮执行结束时的状态)。
// 同一槽位两次进入图之间有 CPU fence 全等保,状态链闭合。
// imported 资源(swapchain)acquire 后 layout 语义上总是 Undefined,恒用 first。
//
// 非 entry:layout 变化或存在写读/读写/写写依赖时插入(read-read 无害跳过)。
// ---- 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);
entry.chainAccess = UseDstAccess(use);
entry.chainWasWrite = IsWriteUse(use.use);
}
}
for (auto& entry : m_Textures) {
if (entry.imported || entry.firstPass < 0) continue;
entry.restingLayout = entry.chainLayout;
entry.restingStage = entry.chainStage;
entry.restingAccess = entry.chainAccess;
}
// ---- Pass B:barrier 生成 ----
for (auto& entry : m_Textures) {
entry.chainLayout = vk::ImageLayout::eUndefined;
entry.chainStage = {};
entry.chainAccess = {};
entry.chainWasWrite = false;
}
for (u32 pos = 0; pos < m_ExecutionOrder.size(); ++pos) {
auto& pass = m_Passes[m_ExecutionOrder[pos]];
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});
// 出口 barrier:采样完成后把历史镜像转回 resting layout。
// (若 desired == resting 则是无害的空转换,但保留它以
// 统一语义——src scope 同时给出读取完成的内存序)
vk::ImageMemoryBarrier2 exit;
exit.setSrcStageMask(dstStage)
.setSrcAccessMask(dstAccess)
.setDstStageMask(vk::PipelineStageFlagBits2::eNone)
.setDstAccessMask(vk::AccessFlagBits2::eNone)
.setOldLayout(desiredLayout)
.setNewLayout(entry.restingLayout)
.setSubresourceRange({entry.aspect, 0, 1, 0, 1});
pass.postBarriers.push_back({use.resIndex, exit, exit, true});
continue; // 不推进 chain:本帧链只描述当前槽位镜像
}
const vk::ImageLayout desiredLayout = UseLayout(use.use);
const vk::PipelineStageFlags2 dstStage = UseDstStage(use);
const vk::AccessFlags2 dstAccess = UseDstAccess(use);
const bool curIsWrite = IsWriteUse(use.use);
auto makeBarrier = [&](vk::ImageLayout oldLayout,
vk::PipelineStageFlags2 srcStage,
vk::AccessFlags2 srcAccess) {
vk::ImageMemoryBarrier2 b;
b.setSrcStageMask(srcStage)
.setSrcAccessMask(srcAccess)
.setDstStageMask(dstStage)
.setDstAccessMask(dstAccess)
.setOldLayout(oldLayout)
.setNewLayout(desiredLayout)
// image 句柄由 AllocatePhysical/每帧导入回填
.setSubresourceRange({entry.aspect, 0, 1, 0, 1});
return b;
};
if (entry.firstPass == static_cast<i32>(pos)) {
// entry barrier:两个变体
vk::ImageMemoryBarrier2 first = makeBarrier(
vk::ImageLayout::eUndefined,
vk::PipelineStageFlagBits2::eNone,
vk::AccessFlagBits2::eNone);
vk::ImageMemoryBarrier2 steady;
if (entry.imported) {
// swapchain:每帧 acquire 后都是 Undefined,两变体相同
steady = first;
} else {
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});
entry.chainLayout = desiredLayout;
entry.chainStage = dstStage;
entry.chainAccess = dstAccess;
entry.chainWasWrite = curIsWrite;
continue;
}
// 非 entry:layout 变化,或存在写读/读写/写写依赖时插入
bool need = entry.chainLayout != desiredLayout ||
entry.chainWasWrite || curIsWrite;
if (!need) continue;
vk::ImageMemoryBarrier2 b = makeBarrier(
entry.chainLayout, entry.chainStage, entry.chainAccess);
pass.barriers.push_back({use.resIndex, b, b});
entry.chainLayout = desiredLayout;
entry.chainStage = dstStage;
entry.chainAccess = dstAccess;
entry.chainWasWrite = curIsWrite;
}
}
}
void RenderGraph::AllocatePhysical()
{
for (auto& entry : m_Textures) {
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{
entry.desc.width
? entry.desc.width
: std::max(1u, u32(m_TargetExtent.width * entry.desc.sizeScale)),
entry.desc.height
? entry.desc.height
: std::max(1u, u32(m_TargetExtent.height * entry.desc.sizeScale))
};
for (u32 slot = 0; slot < m_FramesInFlight; ++slot) {
entry.physical[slot] = AcquirePhysical(
entry.desc.format, extent, entry.usage, entry.aspect);
}
// barrier 的 image 句柄在 Execute 期按帧槽位绑定(见 Execute)
}
}
void RenderGraph::ReleasePhysical()
{
for (auto& entry : m_Textures) {
if (entry.imported) continue;
for (u32 slot = 0; slot < entry.physical.size(); ++slot) {
auto* img = entry.physical[slot];
if (!img) continue;
PoolKey key{
static_cast<u32>(img->format),
img->extent.width, img->extent.height,
static_cast<u32>(img->usage)
};
m_Pool[key].push_back(Scope<PhysicalImage>(img));
entry.physical[slot] = nullptr;
}
}
}
void RenderGraph::PurgePool(vk::Extent2D target)
{
const auto& vkDevice = m_Device.GetDevice();
for (auto it = m_Pool.begin(); it != m_Pool.end();) {
if (it->first.width != target.width || it->first.height != target.height) {
for (auto& img : it->second) {
if (!img->valid) continue;
vkDevice.destroyImageView(img->view);
m_Allocator.Destroy(img->allocation);
}
it = m_Pool.erase(it);
} else {
++it;
}
}
}
PhysicalImage* RenderGraph::AcquirePhysical(vk::Format format, vk::Extent2D extent,
vk::ImageUsageFlags usage,
vk::ImageAspectFlagBits aspect)
{
PoolKey key{static_cast<u32>(format), extent.width, extent.height,
static_cast<u32>(usage)};
// 池中取空闲镜像
auto it = m_Pool.find(key);
if (it != m_Pool.end() && !it->second.empty()) {
PhysicalImage* img = it->second.back().release();
it->second.pop_back();
return img;
}
// 新建:所有权归 TextureEntry.physical,Release 时归还池
auto img = CreateScope<PhysicalImage>();
img->extent = extent;
img->format = format;
img->usage = usage;
img->valid = true;
const auto& vkDevice = m_Device.GetDevice();
vk::ImageCreateInfo imageInfo;
imageInfo.setImageType(vk::ImageType::e2D)
.setFormat(format)
.setExtent({extent.width, extent.height, 1})
.setMipLevels(1)
.setArrayLayers(1)
.setSamples(vk::SampleCountFlagBits::e1)
.setTiling(vk::ImageTiling::eOptimal)
.setUsage(usage)
.setSharingMode(vk::SharingMode::eExclusive)
.setInitialLayout(vk::ImageLayout::eUndefined);
// VMA:创建 + 分配 + 绑定一步完成
img->allocation = m_Allocator.CreateImage(imageInfo,
vk::MemoryPropertyFlagBits::eDeviceLocal);
img->image = img->allocation.Image;
vk::ImageViewCreateInfo viewInfo;
viewInfo.setImage(img->image)
.setViewType(vk::ImageViewType::e2D)
.setFormat(format)
.setSubresourceRange({aspect, 0, 1, 0, 1});
img->view = vkDevice.createImageView(viewInfo);
return img.release();
}
vk::ImageView RenderGraph::GetPhysicalView(TextureHandle h, u32 frameSlot) const
{
const auto& entry = GetEntry(h);
if (entry.imported) return entry.importedView;
if (frameSlot >= entry.physical.size() || !entry.physical[frameSlot]) {
// ENGINE_FATAL 只记日志不中止,这里必须 throw 才能拦住空指针
//(Application::Run 的 catch 会接住并干净退出)
throw std::runtime_error(
"RenderGraph: no physical image for '" + entry.name +
"' slot " + std::to_string(frameSlot));
}
return entry.physical[frameSlot]->view;
}
void RenderGraph::SetQueryPool(vk::QueryPool pool, u32 queriesPerFrame)
{
m_QueryPool = pool;
m_QueriesPerFrame = queriesPerFrame;
}
std::vector<std::string> RenderGraph::GetPassNames() const
{
std::vector<std::string> names;
names.reserve(m_ExecutionOrder.size());
for (const u32 passIdx : m_ExecutionOrder) {
names.push_back(m_Passes[passIdx].name);
}
return names;
}
// ---------- 执行 ----------
void RenderGraph::Execute(const FrameContext& frame)
{
// GPU timestamp:可选。池由 Renderer 创建注入;每帧开头重置本槽位
// 区间(上一轮结果已在 fence 等待后被读取),每个 pass 前后各一枚
// 时间戳(BOTTOM_OF_PIPE),索引按执行序排布
u32 tsPos = 0;
if (m_QueryPool) {
frame.cmd.resetQueryPool(m_QueryPool,
frame.frameSlot * m_QueriesPerFrame,
m_QueriesPerFrame);
}
for (const u32 passIdx : m_ExecutionOrder) {
auto& pass = m_Passes[passIdx];
if (pass.culled) continue;
if (m_QueryPool) {
frame.cmd.writeTimestamp2(
vk::PipelineStageFlagBits2::eBottomOfPipe,
m_QueryPool, frame.frameSlot * m_QueriesPerFrame + tsPos * 2);
}
// barrier:按槽位状态挑选变体,批量提交
m_ScratchBarriers.clear();
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 {
useFirst = !entry.slots[imageSlot].used;
entry.slots[imageSlot].used = true;
}
vk::ImageMemoryBarrier2 b = useFirst ? bp.first : bp.steady;
// image 句柄在执行期绑定:imported 用当帧 acquire 的 image,
// created 用当前帧槽位(历史 barrier 用上一帧槽位)的物理镜像
b.setImage(entry.imported
? entry.importedImage
: entry.physical[imageSlot]->image);
m_ScratchBarriers.push_back(b);
}
if (!m_ScratchBarriers.empty()) {
vk::DependencyInfo dep;
dep.setImageMemoryBarriers(m_ScratchBarriers);
frame.cmd.pipelineBarrier2(dep);
}
if (pass.exec) {
RenderGraphContext ctx;
ctx.cmd = frame.cmd;
ctx.frame = frame;
ctx.extent = pass.extent;
ctx.graph = this;
pass.exec(ctx);
}
// 历史读出口:恢复上一帧槽位镜像的 resting layout
if (!pass.postBarriers.empty()) {
m_ScratchBarriers.clear();
for (const auto& bp : pass.postBarriers) {
auto& entry = m_Textures[bp.resIndex];
const u32 histSlot = HistorySlot(frame.frameSlot);
vk::ImageMemoryBarrier2 b = bp.steady;
b.setImage(entry.physical[histSlot]->image);
m_ScratchBarriers.push_back(b);
}
vk::DependencyInfo dep;
dep.setImageMemoryBarriers(m_ScratchBarriers);
frame.cmd.pipelineBarrier2(dep);
}
if (m_QueryPool) {
frame.cmd.writeTimestamp2(
vk::PipelineStageFlagBits2::eBottomOfPipe,
m_QueryPool, frame.frameSlot * m_QueriesPerFrame + tsPos * 2 + 1);
}
++tsPos;
}
}
// ---------- RenderGraphContext ----------
vk::ImageView RenderGraphContext::View(TextureHandle h) const
{
const auto& entry = graph->GetEntry(h);
if (entry.imported) return entry.importedView;
return entry.physical[frame.frameSlot]->view;
}
vk::Image RenderGraphContext::Image(TextureHandle h) const
{
const auto& entry = graph->GetEntry(h);
if (entry.imported) return entry.importedImage;
return entry.physical[frame.frameSlot]->image;
}
u32 RenderGraphContext::HistorySlot(u32 frameSlot) const
{
return graph->HistorySlot(frameSlot);
}
// ---------- 工具 / 统计 ----------
TextureEntry& RenderGraph::GetEntry(TextureHandle h)
{
CheckHandle(h);
return m_Textures[h.index];
}
const TextureEntry& RenderGraph::GetEntry(TextureHandle h) const
{
CheckHandle(h);
return m_Textures[h.index];
}
void RenderGraph::CheckHandle(TextureHandle h) const
{
if (!h.Valid() || h.index >= m_Textures.size() ||
m_Textures[h.index].generation != h.generation) {
ENGINE_FATAL("RenderGraph: invalid texture handle (index={}, gen={})",
h.index, h.generation);
}
}
void RenderGraph::CheckMutable() const
{
// 图结构(资源/pass/use)只在声明期可变;Compile 后 barrier 已预算,
// 静默接受修改会产生错误同步,直接拒绝。
if (m_Compiled) {
ENGINE_FATAL("RenderGraph: graph is compiled; structural changes are not allowed");
}
}
vk::ImageLayout RenderGraph::UseLayout(ResourceUse use)
{
switch (use) {
case ResourceUse::ColorAttachment: return vk::ImageLayout::eColorAttachmentOptimal;
case ResourceUse::DepthAttachment: return vk::ImageLayout::eDepthAttachmentOptimal;
case ResourceUse::ShaderRead: return vk::ImageLayout::eShaderReadOnlyOptimal;
case ResourceUse::TransferSrc: return vk::ImageLayout::eTransferSrcOptimal;
case ResourceUse::TransferDst: return vk::ImageLayout::eTransferDstOptimal;
case ResourceUse::Present: return vk::ImageLayout::ePresentSrcKHR;
}
return vk::ImageLayout::eUndefined;
}
vk::PipelineStageFlags2 RenderGraph::UseDstStage(const PassNode::Use& use)
{
switch (use.use) {
case ResourceUse::ColorAttachment:
return vk::PipelineStageFlagBits2::eColorAttachmentOutput;
case ResourceUse::DepthAttachment:
return vk::PipelineStageFlagBits2::eEarlyFragmentTests |
vk::PipelineStageFlagBits2::eLateFragmentTests;
case ResourceUse::ShaderRead: {
// ShaderStageFlags 与 PipelineStageFlags2 位值不同
//(eFragment=0x10 vs eFragmentShader=0x80),禁止位值直转,逐位映射
vk::PipelineStageFlags2 mapped{};
if (use.readStage & vk::ShaderStageFlagBits::eVertex)
mapped |= vk::PipelineStageFlagBits2::eVertexShader;
if (use.readStage & vk::ShaderStageFlagBits::eFragment)
mapped |= vk::PipelineStageFlagBits2::eFragmentShader;
if (use.readStage & vk::ShaderStageFlagBits::eCompute)
mapped |= vk::PipelineStageFlagBits2::eComputeShader;
if (use.readStage & vk::ShaderStageFlagBits::eGeometry)
mapped |= vk::PipelineStageFlagBits2::eGeometryShader;
if (use.readStage & vk::ShaderStageFlagBits::eTessellationControl)
mapped |= vk::PipelineStageFlagBits2::eTessellationControlShader;
if (use.readStage & vk::ShaderStageFlagBits::eTessellationEvaluation)
mapped |= vk::PipelineStageFlagBits2::eTessellationEvaluationShader;
if (!mapped) {
ENGINE_FATAL("RenderGraph: ShaderRead stage not mappable: {}",
static_cast<u32>(use.readStage));
}
return mapped;
}
case ResourceUse::TransferSrc:
case ResourceUse::TransferDst:
return vk::PipelineStageFlagBits2::eTransfer;
case ResourceUse::Present:
return vk::PipelineStageFlagBits2::eNone;
}
return vk::PipelineStageFlagBits2::eNone;
}
vk::PipelineStageFlags2 RenderGraph::UseSrcStage(ResourceUse use)
{
switch (use) {
case ResourceUse::ColorAttachment:
case ResourceUse::Present:
return vk::PipelineStageFlagBits2::eColorAttachmentOutput;
case ResourceUse::DepthAttachment:
return vk::PipelineStageFlagBits2::eEarlyFragmentTests |
vk::PipelineStageFlagBits2::eLateFragmentTests;
case ResourceUse::ShaderRead:
return vk::PipelineStageFlagBits2::eFragmentShader;
case ResourceUse::TransferSrc:
case ResourceUse::TransferDst:
return vk::PipelineStageFlagBits2::eTransfer;
}
return vk::PipelineStageFlagBits2::eNone;
}
vk::AccessFlags2 RenderGraph::UseDstAccess(const PassNode::Use& use)
{
switch (use.use) {
case ResourceUse::ColorAttachment:
return vk::AccessFlagBits2::eColorAttachmentWrite;
case ResourceUse::DepthAttachment:
return vk::AccessFlagBits2::eDepthStencilAttachmentWrite;
case ResourceUse::ShaderRead:
return vk::AccessFlagBits2::eShaderRead;
case ResourceUse::TransferSrc:
return vk::AccessFlagBits2::eTransferRead;
case ResourceUse::TransferDst:
return vk::AccessFlagBits2::eTransferWrite;
case ResourceUse::Present:
// layout-only 转换(交给 presentation engine),无访问掩码
return vk::AccessFlagBits2::eNone;
}
return vk::AccessFlagBits2::eNone;
}
vk::AccessFlags2 RenderGraph::UseSrcAccess(ResourceUse use)
{
switch (use) {
case ResourceUse::ColorAttachment:
case ResourceUse::Present:
return vk::AccessFlagBits2::eColorAttachmentWrite;
case ResourceUse::DepthAttachment:
return vk::AccessFlagBits2::eDepthStencilAttachmentWrite;
case ResourceUse::ShaderRead:
return vk::AccessFlagBits2::eShaderRead;
case ResourceUse::TransferSrc:
return vk::AccessFlagBits2::eTransferRead;
case ResourceUse::TransferDst:
return vk::AccessFlagBits2::eTransferWrite;
}
return vk::AccessFlagBits2::eNone;
}
u32 RenderGraph::GetPassCount() const { return static_cast<u32>(m_Passes.size()); }
u32 RenderGraph::GetActivePassCount() const { return m_ActivePassCount; }
u32 RenderGraph::GetTextureCount() const { return static_cast<u32>(m_Textures.size()); }
u32 RenderGraph::GetBarrierCount() const
{
u32 total = 0;
for (const auto& pass : m_Passes) {
total += static_cast<u32>(pass.barriers.size());
}
return total;
}
u32 RenderGraph::GetPooledImageCount() const
{
// 池中空闲 + 租借中(挂在资源上的物理镜像)
u32 total = 0;
for (const auto& [key, images] : m_Pool) {
total += static_cast<u32>(images.size());
}
for (const auto& entry : m_Textures) {
if (entry.imported) continue;
for (auto* img : entry.physical) {
if (img) ++total;
}
}
return total;
}
} // namespace Engine::RG