- 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 全活跃)
996 lines
39 KiB
C++
996 lines
39 KiB
C++
//
|
||
// 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
|