Files
HelloVulkan/Engine/src/RenderGraph/RenderGraph.cpp
T
atdunbg 41e4cc85d5 feat: RenderGraph GPU timestamp profiling
- RG::SetQueryPool 可选注入;Execute 期每 pass 前后各写一枚
  writeTimestamp2(BOTTOM_OF_PIPE),索引按帧槽位分区 + 执行序排布
- 每帧录制开头 reset 本槽位查询区间(上一轮结果已在 fence 等待后读取)
- Renderer 在 WaitFence 后读 64bit 结果换算毫秒(timestampPeriod 缓存),
  经 GetPassNames/GetPassGpuMs 透出给 ImGui 面板显示
- 每槽位首帧跳过读取(查询尚未 primed,避免 VUID-09401)
2026-09-22 19:25:42 +08:00

883 lines
33 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);
vkDevice.destroyImage(img->image);
m_Allocator.Free(img->allocation);
}
}
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::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();
}
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];
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) {
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) {
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];
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);
}
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 || 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);
vkDevice.destroyImage(img->image);
m_Allocator.Free(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);
img->image = vkDevice.createImage(imageInfo);
auto reqs = vkDevice.getImageMemoryRequirements(img->image);
img->allocation = m_Allocator.Allocate(reqs, vk::MemoryPropertyFlagBits::eDeviceLocal);
vkDevice.bindImageMemory(img->image, img->allocation.Memory, img->allocation.Offset);
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];
bool useFirst;
if (entry.imported) {
useFirst = true; // 恒为 Undefined 起点
} else {
auto& slot = entry.slots[frame.frameSlot];
useFirst = !slot.used;
slot.used = true;
}
vk::ImageMemoryBarrier2 b = useFirst ? bp.first : bp.steady;
// image 句柄在执行期绑定:imported 用当帧 acquire 的 image,
// created 用当前帧槽位的物理镜像(每个槽位各有一份)
b.setImage(entry.imported
? entry.importedImage
: entry.physical[frame.frameSlot]->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);
}
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;
}
// ---------- 工具 / 统计 ----------
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