// // Created by atdunbg on 2026/9/22. // #include #include #include #include #include #include #include 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 &images: m_Pool | std::views::values) { for (const 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(); const TextureHandle h{static_cast(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(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; } void RenderGraph::SetImportedExtent(TextureHandle h, vk::Extent2D extent) { auto& entry = GetEntry(h); if (!entry.imported) { ENGINE_FATAL("RenderGraph: SetImportedExtent on non-imported resource '{}'", entry.name); } entry.importedExtent = extent; } // ---------- 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(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( 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 | entry.desc.extraUsage; } void RenderGraph::CullPasses() { // 引用计数剔除:一个 pass 存活当且仅当 // a) 声明了 sideEffect,或 // b) 它读写某个导入资源(对外可见),或 // c) 它写出的某个资源被某个存活 pass 读取。 // 迭代到不动点(最多 pass 数轮)。 std::vector kept(m_Passes.size(), false); std::vector 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(m_Passes.size()); std::vector> adj(N); std::vector 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 lastWriter(m_Textures.size(), kNoWriter); std::vector> 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 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(pos); entry.lastPass = static_cast(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(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(img->format), img->extent.width, img->extent.height, static_cast(img->usage) }; m_Pool[key].push_back(Scope(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(format), extent.width, extent.height, static_cast(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(); 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 RenderGraph::GetPassNames() const { std::vector 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) { m_WrittenQueries[frame.frameSlot] = 0; // 上轮残留清零 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; // profiler zone:覆盖本 pass 的 barrier + lambda + postBarriers // (GPU 时间线里 pass 是完整段,含同步开销) Scope zoneScope; if (m_GpuProfiler) { zoneScope = m_GpuProfiler->BeginPass(pass.name.c_str(), frame.cmd); } 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); } zoneScope.reset(); // END 时间戳 ++tsPos; } // 记录本槽位实际写入的查询数:读侧(Renderer::ReadGpuTimes)按此 // 数取结果——被剔除 pass 的时间戳永远不写,按满额阻塞读会死锁 if (m_QueryPool) { m_WrittenQueries[frame.frameSlot] = tsPos * 2; } // 帧尾收集:profiler 在当帧 cmd 内解析查询并上报(提交前录制) if (m_GpuProfiler) { m_GpuProfiler->EndFrame(frame.cmd); } } // ---------- 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; } vk::Extent2D RenderGraphContext::Extent(TextureHandle h) const { const auto& entry = graph->GetEntry(h); if (entry.imported) return entry.importedExtent; return entry.physical[frame.frameSlot]->extent; } 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(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(m_Passes.size()); } u32 RenderGraph::GetActivePassCount() const { return m_ActivePassCount; } u32 RenderGraph::GetTextureCount() const { return static_cast(m_Textures.size()); } u32 RenderGraph::GetBarrierCount() const { u32 total = 0; for (const auto& pass : m_Passes) { total += static_cast(pass.barriers.size()); } return total; } u32 RenderGraph::GetPooledImageCount() const { // 池中空闲 + 租借中(挂在资源上的物理镜像) u32 total = 0; for (const auto& [key, images] : m_Pool) { total += static_cast(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