feat: 引入 RenderGraph、Application 框架、ECS 与资产句柄系统

架构分层(自上而下):
  App(SandboxApp) → Application(生命周期+事件) → Scene(ECS) / AssetManager(句柄) → Renderer → RenderGraph → Vulkan 模块

- RenderGraph:声明式帧图。虚拟资源+生成号句柄、死 pass 剔除、
  拓扑排序、资源生命周期、barrier 编译期预算(Synchronization2)、
  物理镜像池按帧槽位隔离(帧间竞争从根上消除)
- 帧同步修复:per-frame-slot 命令缓冲与 UBO ring(原先单份存在
  数据竞争);统一 kMaxFramesInFlight / FrameContext
- Application:生命周期钩子(OnInit/OnUpdate/OnRender/OnShutdown)
  + 事件系统(GLFW 回调 → POD 事件 → 类型化钩子);main 只 new 派生类
- ECS:自研稀疏集 Registry(Entity=(index,generation) 防悬空,
  O(1) 增删查,Each 取最小池遍历);Scene 延迟销毁队列;
  IdComponent{UUID} 稳定外部身份
- Assets:AssetManager + AssetHandle。组件存句柄不存裸指针/Ref
  (单一所有者 + 多使用者),Renderer 删除 LoadModel 改为纯消费
- 修复退出析构:成员析构前 GPU waitIdle(资产层先于 Renderer
  析构导致 VUID-00922 资源销毁时仍在使用)
- 引入 entt 子模块备用

validation 全程零错误零警告(RTX 5060 实测)
This commit is contained in:
2026-09-22 13:46:05 +08:00
parent 7d628f8d8a
commit 8afdd9b245
33 changed files with 3013 additions and 561 deletions
+781
View File
@@ -0,0 +1,781 @@
//
// 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>
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);
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()
{
// writer → reader 建边(去重),Kahn 拓扑排序。
// 声明顺序通常已是拓扑序,但引擎级实现必须显式校验,
// 避免上层重构 pass 顺序时引入环。
std::vector<std::vector<u32>> writers(m_Textures.size());
for (u32 i = 0; i < m_Passes.size(); ++i) {
if (m_Passes[i].culled) continue;
for (const auto& use : m_Passes[i].uses) {
if (IsWriteUse(use.use)) {
writers[use.resIndex].push_back(i);
}
}
}
std::vector<std::set<u32>> adj(m_Passes.size());
std::vector<u32> indegree(m_Passes.size(), 0);
for (u32 q = 0; q < m_Passes.size(); ++q) {
if (m_Passes[q].culled) continue;
std::set<u32> seenWriters;
for (const auto& use : m_Passes[q].uses) {
if (IsWriteUse(use.use)) continue;
for (u32 p : writers[use.resIndex]) {
if (p == q || m_Passes[p].culled) continue;
if (seenWriters.insert(p).second) {
adj[p].insert(q);
++indegree[q];
}
}
}
}
m_ExecutionOrder.clear();
m_ExecutionOrder.reserve(m_Passes.size());
// 同层按声明序出队,保证确定性
std::set<u32> ready;
for (u32 i = 0; i < m_Passes.size(); ++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;
vk::Extent2D extent{
entry.desc.width ? entry.desc.width : m_TargetExtent.width,
entry.desc.height ? entry.desc.height : m_TargetExtent.height
};
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();
}
// ---------- 执行 ----------
void RenderGraph::Execute(const FrameContext& frame)
{
for (const u32 passIdx : m_ExecutionOrder) {
auto& pass = m_Passes[passIdx];
if (pass.culled) continue;
// 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 = m_TargetExtent;
ctx.graph = this;
pass.exec(ctx);
}
}
}
// ---------- 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 位值兼容,需显式转换
return vk::PipelineStageFlags2(
static_cast<VkPipelineStageFlags2>(
static_cast<VkShaderStageFlags>(use.readStage)));
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