Implement render graph compiler and transient fullscreen execution

This commit is contained in:
2026-04-14 04:37:07 +08:00
parent 72d09a1c49
commit c98b41f6f4
15 changed files with 2161 additions and 112 deletions

View File

@@ -0,0 +1,253 @@
#include <XCEngine/Rendering/Graph/RenderGraphCompiler.h>
#include <algorithm>
#include <unordered_set>
#include <utility>
namespace XCEngine {
namespace Rendering {
namespace {
RHI::ResourceStates ResolveRequiredState(
RenderGraphPassType passType,
RenderGraphAccessMode accessMode) {
if (accessMode == RenderGraphAccessMode::Write) {
return passType == RenderGraphPassType::Compute
? RHI::ResourceStates::UnorderedAccess
: RHI::ResourceStates::RenderTarget;
}
return passType == RenderGraphPassType::Compute
? RHI::ResourceStates::GenericRead
: RHI::ResourceStates::PixelShaderResource;
}
void WriteError(
const Containers::String& message,
Containers::String* outErrorMessage) {
if (outErrorMessage != nullptr) {
*outErrorMessage = message;
}
}
} // namespace
void CompiledRenderGraph::Reset() {
m_passes.clear();
m_textures.clear();
m_textureLifetimes.clear();
}
const Containers::String& CompiledRenderGraph::GetPassName(size_t index) const {
static const Containers::String kEmptyString;
return index < m_passes.size() ? m_passes[index].name : kEmptyString;
}
RenderGraphPassType CompiledRenderGraph::GetPassType(size_t index) const {
return index < m_passes.size()
? m_passes[index].type
: RenderGraphPassType::Raster;
}
bool CompiledRenderGraph::TryGetTextureLifetime(
RenderGraphTextureHandle handle,
RenderGraphTextureLifetime& outLifetime) const {
if (!handle.IsValid() || handle.index >= m_textureLifetimes.size()) {
return false;
}
outLifetime = m_textureLifetimes[handle.index];
return true;
}
bool CompiledRenderGraph::TryGetImportedTextureOptions(
RenderGraphTextureHandle handle,
RenderGraphImportedTextureOptions& outOptions) const {
if (!handle.IsValid() || handle.index >= m_textures.size()) {
return false;
}
const CompiledRenderGraph::CompiledTexture& texture = m_textures[handle.index];
if (texture.kind != RenderGraphTextureKind::Imported) {
return false;
}
outOptions = texture.importedOptions;
return true;
}
bool RenderGraphCompiler::Compile(
const RenderGraph& graph,
CompiledRenderGraph& outCompiledGraph,
Containers::String* outErrorMessage) {
outCompiledGraph.Reset();
for (const RenderGraph::TextureResource& texture : graph.m_textures) {
if (!texture.desc.IsValid()) {
WriteError(
Containers::String("RenderGraph texture desc is invalid: ") + texture.name,
outErrorMessage);
return false;
}
}
const size_t passCount = graph.m_passes.size();
const size_t textureCount = graph.m_textures.size();
std::vector<std::vector<Core::uint32>> outgoingEdges(passCount);
std::vector<Core::uint32> incomingEdgeCount(passCount, 0u);
std::unordered_set<Core::uint64> edgeKeys;
std::vector<Core::uint32> lastWriter(textureCount, kInvalidRenderGraphHandle);
std::vector<std::vector<Core::uint32>> lastReaders(textureCount);
auto addUniqueReader = [](
std::vector<Core::uint32>& readers,
Core::uint32 passIndex) {
if (std::find(readers.begin(), readers.end(), passIndex) == readers.end()) {
readers.push_back(passIndex);
}
};
auto addEdge = [&](
Core::uint32 from,
Core::uint32 to) {
if (from == kInvalidRenderGraphHandle || to == kInvalidRenderGraphHandle || from == to) {
return;
}
const Core::uint64 key =
(static_cast<Core::uint64>(from) << 32u) | static_cast<Core::uint64>(to);
if (!edgeKeys.insert(key).second) {
return;
}
outgoingEdges[from].push_back(to);
++incomingEdgeCount[to];
};
for (Core::uint32 passIndex = 0u; passIndex < static_cast<Core::uint32>(passCount); ++passIndex) {
const RenderGraph::PassNode& pass = graph.m_passes[passIndex];
for (const RenderGraph::TextureAccess& access : pass.accesses) {
if (!access.texture.IsValid() || access.texture.index >= textureCount) {
WriteError(
Containers::String("RenderGraph pass '") + pass.name +
"' references an invalid texture handle",
outErrorMessage);
return false;
}
const RenderGraph::TextureResource& texture = graph.m_textures[access.texture.index];
std::vector<Core::uint32>& readers = lastReaders[access.texture.index];
Core::uint32& writer = lastWriter[access.texture.index];
if (access.mode == RenderGraphAccessMode::Read) {
if (texture.kind == RenderGraphTextureKind::Transient &&
writer == kInvalidRenderGraphHandle) {
WriteError(
Containers::String("RenderGraph transient texture '") + texture.name +
"' is read before any pass writes it",
outErrorMessage);
return false;
}
addEdge(writer, passIndex);
addUniqueReader(readers, passIndex);
continue;
}
addEdge(writer, passIndex);
for (Core::uint32 readerPassIndex : readers) {
addEdge(readerPassIndex, passIndex);
}
readers.clear();
writer = passIndex;
}
}
std::vector<Core::uint32> executionOrder;
executionOrder.reserve(passCount);
std::vector<bool> emitted(passCount, false);
while (executionOrder.size() < passCount) {
bool progressed = false;
for (Core::uint32 passIndex = 0u; passIndex < static_cast<Core::uint32>(passCount); ++passIndex) {
if (emitted[passIndex] || incomingEdgeCount[passIndex] != 0u) {
continue;
}
emitted[passIndex] = true;
executionOrder.push_back(passIndex);
for (Core::uint32 dependentPassIndex : outgoingEdges[passIndex]) {
if (incomingEdgeCount[dependentPassIndex] > 0u) {
--incomingEdgeCount[dependentPassIndex];
}
}
progressed = true;
break;
}
if (!progressed) {
WriteError(
"RenderGraph failed to compile because pass dependencies contain a cycle",
outErrorMessage);
outCompiledGraph.Reset();
return false;
}
}
outCompiledGraph.m_textureLifetimes.resize(textureCount);
for (size_t textureIndex = 0u; textureIndex < textureCount; ++textureIndex) {
CompiledRenderGraph::CompiledTexture compiledTexture = {};
compiledTexture.name = graph.m_textures[textureIndex].name;
compiledTexture.desc = graph.m_textures[textureIndex].desc;
compiledTexture.kind = graph.m_textures[textureIndex].kind;
compiledTexture.importedView = graph.m_textures[textureIndex].importedView;
compiledTexture.importedOptions = graph.m_textures[textureIndex].importedOptions;
outCompiledGraph.m_textures.push_back(std::move(compiledTexture));
outCompiledGraph.m_textureLifetimes[textureIndex].kind = graph.m_textures[textureIndex].kind;
}
outCompiledGraph.m_passes.reserve(passCount);
for (Core::uint32 compiledPassIndex = 0u;
compiledPassIndex < static_cast<Core::uint32>(executionOrder.size());
++compiledPassIndex) {
const Core::uint32 originalPassIndex = executionOrder[compiledPassIndex];
const RenderGraph::PassNode& sourcePass = graph.m_passes[originalPassIndex];
CompiledRenderGraph::CompiledPass compiledPass = {};
compiledPass.name = sourcePass.name;
compiledPass.type = sourcePass.type;
compiledPass.originalPassIndex = originalPassIndex;
compiledPass.accesses.reserve(sourcePass.accesses.size());
for (const RenderGraph::TextureAccess& access : sourcePass.accesses) {
CompiledRenderGraph::CompiledTextureAccess compiledAccess = {};
compiledAccess.texture = access.texture;
compiledAccess.mode = access.mode;
compiledAccess.requiredState =
ResolveRequiredState(sourcePass.type, access.mode);
compiledPass.accesses.push_back(compiledAccess);
}
compiledPass.executeCallback = sourcePass.executeCallback;
outCompiledGraph.m_passes.push_back(std::move(compiledPass));
for (const RenderGraph::TextureAccess& access : sourcePass.accesses) {
RenderGraphTextureLifetime& lifetime =
outCompiledGraph.m_textureLifetimes[access.texture.index];
if (!lifetime.used) {
lifetime.used = true;
lifetime.firstPassIndex = compiledPassIndex;
}
lifetime.lastPassIndex = compiledPassIndex;
}
}
if (outErrorMessage != nullptr) {
outErrorMessage->Clear();
}
return true;
}
} // namespace Rendering
} // namespace XCEngine