feat: ACES tonemap + Bloom 后处理链(Extract/BlurH/BlurV 半分辨率)
- ACES(Narkowicz) 经 push 开关启用,exposure 接 ImGui 滑条 - RG: TextureDesc 支持 sizeScale 相对尺寸;per-pass 渲染尺寸解析 (半分辨率 pass 的 viewport 由此决定) - RG Toposort 重写:RAW+WAR+WAW 按声明序逐资源建边,支持多写者 ping-pong 资源(旧逻辑对 reader 连接所有 writer,ping-pong 成环) - GetPhysicalView 缺物理镜像时 throw(ENGINE_FATAL 只记日志不中止) - 全屏 pass 通用 16B push + 公共录制 lambda,Extract/Blur/Tonemap 复用
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Engine::RG {
|
||||
|
||||
@@ -164,6 +165,22 @@ namespace Engine::RG {
|
||||
|
||||
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();
|
||||
@@ -272,34 +289,43 @@ namespace Engine::RG {
|
||||
|
||||
void RenderGraph::Toposort()
|
||||
{
|
||||
// writer → reader 建边(去重),Kahn 拓扑排序。
|
||||
// 声明顺序通常已是拓扑序,但引擎级实现必须显式校验,
|
||||
// 避免上层重构 pass 顺序时引入环。
|
||||
// 按资源在声明序上的使用序列建边,Kahn 拓扑排序。
|
||||
// RAW:reader → 依赖声明序上它的上一个 writer
|
||||
// WAR:writer → 依赖上一次 write 之后出现过的所有 reader
|
||||
// WAW:writer → 上一个 writer
|
||||
// 多写者(ping-pong 资源)合法:若对 reader 连接"所有"
|
||||
// writer(含声明序在其后的),BlurH 会同时收到 Extract→BlurH 与
|
||||
// BlurV→BlurH,而 BlurH→BlurV 又存在,直接成环。
|
||||
// 声明顺序通常已是拓扑序,但引擎级实现必须显式校验。
|
||||
|
||||
std::vector<std::vector<u32>> writers(m_Textures.size());
|
||||
for (u32 i = 0; i < m_Passes.size(); ++i) {
|
||||
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)) {
|
||||
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];
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,7 +335,7 @@ namespace Engine::RG {
|
||||
|
||||
// 同层按声明序出队,保证确定性
|
||||
std::set<u32> ready;
|
||||
for (u32 i = 0; i < m_Passes.size(); ++i) {
|
||||
for (u32 i = 0; i < N; ++i) {
|
||||
if (!m_Passes[i].culled && indegree[i] == 0) ready.insert(i);
|
||||
}
|
||||
|
||||
@@ -458,9 +484,14 @@ namespace Engine::RG {
|
||||
for (auto& entry : m_Textures) {
|
||||
if (entry.imported || entry.firstPass < 0) continue;
|
||||
|
||||
// 显式尺寸优先;否则 = 目标尺寸 × sizeScale(后处理降采样)
|
||||
vk::Extent2D extent{
|
||||
entry.desc.width ? entry.desc.width : m_TargetExtent.width,
|
||||
entry.desc.height ? entry.desc.height : m_TargetExtent.height
|
||||
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) {
|
||||
@@ -567,8 +598,11 @@ namespace Engine::RG {
|
||||
const auto& entry = GetEntry(h);
|
||||
if (entry.imported) return entry.importedView;
|
||||
if (frameSlot >= entry.physical.size() || !entry.physical[frameSlot]) {
|
||||
ENGINE_FATAL("RenderGraph: no physical image for '{}' slot {} "
|
||||
"(compile first?)", entry.name, 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;
|
||||
}
|
||||
@@ -614,7 +648,7 @@ namespace Engine::RG {
|
||||
RenderGraphContext ctx;
|
||||
ctx.cmd = frame.cmd;
|
||||
ctx.frame = frame;
|
||||
ctx.extent = m_TargetExtent;
|
||||
ctx.extent = pass.extent;
|
||||
ctx.graph = this;
|
||||
pass.exec(ctx);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user