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:
@@ -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
|
||||
Reference in New Issue
Block a user