Implement render graph compiler and transient fullscreen execution
This commit is contained in:
253
engine/src/Rendering/Graph/RenderGraphCompiler.cpp
Normal file
253
engine/src/Rendering/Graph/RenderGraphCompiler.cpp
Normal 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
|
||||
Reference in New Issue
Block a user