Support graph-owned imported texture transitions

This commit is contained in:
2026-04-14 04:41:58 +08:00
parent c98b41f6f4
commit c0d62dc749
2 changed files with 150 additions and 31 deletions

View File

@@ -9,9 +9,11 @@ namespace Rendering {
namespace {
bool IsGraphManagedTransientState(RHI::ResourceStates state) {
return state == RHI::ResourceStates::RenderTarget ||
return state == RHI::ResourceStates::Common ||
state == RHI::ResourceStates::RenderTarget ||
state == RHI::ResourceStates::PixelShaderResource ||
state == RHI::ResourceStates::GenericRead;
state == RHI::ResourceStates::GenericRead ||
state == RHI::ResourceStates::Present;
}
RenderGraphTextureViewType ResolveBarrierViewType(RHI::ResourceStates state) {
@@ -42,6 +44,10 @@ public:
for (size_t textureIndex = 0u; textureIndex < m_graph.m_textures.size(); ++textureIndex) {
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[textureIndex];
const RenderGraphTextureLifetime& lifetime = m_graph.m_textureLifetimes[textureIndex];
if (texture.kind == RenderGraphTextureKind::Imported) {
m_textureStates[textureIndex] = texture.importedOptions.initialState;
}
if (texture.kind != RenderGraphTextureKind::Transient || !lifetime.used) {
continue;
}
@@ -110,6 +116,32 @@ public:
m_graph.m_textures[handle.index].kind == RenderGraphTextureKind::Transient;
}
bool TransitionGraphOwnedImportsToFinalStates(
const RenderContext& renderContext,
Containers::String* outErrorMessage) {
for (size_t textureIndex = 0u; textureIndex < m_graph.m_textures.size(); ++textureIndex) {
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[textureIndex];
if (texture.kind != RenderGraphTextureKind::Imported ||
!texture.importedOptions.graphOwnsTransitions ||
texture.importedView == nullptr ||
textureIndex >= m_graph.m_textureLifetimes.size() ||
!m_graph.m_textureLifetimes[textureIndex].used ||
!IsGraphManagedTransientState(texture.importedOptions.finalState)) {
continue;
}
if (!TransitionTexture(
{ static_cast<Core::uint32>(textureIndex) },
texture.importedOptions.finalState,
renderContext,
outErrorMessage)) {
return false;
}
}
return true;
}
bool TransitionPassResources(
const CompiledRenderGraph::CompiledPass& pass,
const RenderContext& renderContext,
@@ -117,41 +149,18 @@ public:
for (const CompiledRenderGraph::CompiledTextureAccess& access : pass.accesses) {
if (!access.texture.IsValid() ||
access.texture.index >= m_graph.m_textures.size() ||
!IsTransientTexture(access.texture) ||
!ShouldGraphManageTransitions(access.texture) ||
!IsGraphManagedTransientState(access.requiredState)) {
continue;
}
if (renderContext.commandList == nullptr) {
if (outErrorMessage != nullptr) {
*outErrorMessage =
Containers::String("RenderGraph cannot transition transient texture without a valid command list: ") +
m_graph.m_textures[access.texture.index].name;
}
if (!TransitionTexture(
access.texture,
access.requiredState,
renderContext,
outErrorMessage)) {
return false;
}
RHI::ResourceStates& currentState = m_textureStates[access.texture.index];
if (currentState == access.requiredState) {
continue;
}
RHI::RHIResourceView* resourceView =
ResolveTextureView(access.texture, ResolveBarrierViewType(access.requiredState));
if (resourceView == nullptr) {
if (outErrorMessage != nullptr) {
*outErrorMessage =
Containers::String("RenderGraph cannot resolve transient texture view for state transition: ") +
m_graph.m_textures[access.texture.index].name;
}
return false;
}
renderContext.commandList->TransitionBarrier(
resourceView,
currentState,
access.requiredState);
currentState = access.requiredState;
}
return true;
@@ -164,6 +173,55 @@ private:
RHI::RHIResourceView* shaderResourceView = nullptr;
};
bool ShouldGraphManageTransitions(RenderGraphTextureHandle handle) const {
if (!handle.IsValid() || handle.index >= m_graph.m_textures.size()) {
return false;
}
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[handle.index];
return texture.kind == RenderGraphTextureKind::Transient ||
(texture.kind == RenderGraphTextureKind::Imported &&
texture.importedOptions.graphOwnsTransitions);
}
bool TransitionTexture(
RenderGraphTextureHandle handle,
RHI::ResourceStates targetState,
const RenderContext& renderContext,
Containers::String* outErrorMessage) {
if (renderContext.commandList == nullptr) {
if (outErrorMessage != nullptr) {
*outErrorMessage =
Containers::String("RenderGraph cannot transition texture without a valid command list: ") +
m_graph.m_textures[handle.index].name;
}
return false;
}
RHI::ResourceStates& currentState = m_textureStates[handle.index];
if (currentState == targetState) {
return true;
}
RHI::RHIResourceView* resourceView =
ResolveTextureView(handle, ResolveBarrierViewType(targetState));
if (resourceView == nullptr) {
if (outErrorMessage != nullptr) {
*outErrorMessage =
Containers::String("RenderGraph cannot resolve texture view for state transition: ") +
m_graph.m_textures[handle.index].name;
}
return false;
}
renderContext.commandList->TransitionBarrier(
resourceView,
currentState,
targetState);
currentState = targetState;
return true;
}
static void DestroyTextureAllocation(TextureAllocation& allocation) {
if (allocation.renderTargetView != nullptr) {
allocation.renderTargetView->Shutdown();
@@ -286,6 +344,12 @@ bool RenderGraphExecutor::Execute(
}
}
if (!runtimeResources.TransitionGraphOwnedImportsToFinalStates(
renderContext,
outErrorMessage)) {
return false;
}
return true;
}

View File

@@ -108,6 +108,16 @@ private:
Format m_format = Format::Unknown;
};
class MockImportedView final : public RHIResourceView {
public:
void Shutdown() override {}
void* GetNativeHandle() override { return nullptr; }
bool IsValid() const override { return true; }
ResourceViewType GetViewType() const override { return ResourceViewType::RenderTarget; }
ResourceViewDimension GetDimension() const override { return ResourceViewDimension::Texture2D; }
Format GetFormat() const override { return Format::R8G8B8A8_UNorm; }
};
class MockTransientDevice final : public RHIDevice {
public:
explicit MockTransientDevice(std::shared_ptr<MockTransientAllocationState> state)
@@ -421,6 +431,51 @@ TEST(RenderGraph_Test, PreservesImportedTextureStateContractAcrossCompile) {
EXPECT_TRUE(resolvedOptions.graphOwnsTransitions);
}
TEST(RenderGraph_Test, ExecutesGraphOwnedImportedTextureTransitionsAtGraphBoundaries) {
RenderGraph graph;
RenderGraphBuilder builder(graph);
const RenderGraphTextureDesc desc = BuildTestTextureDesc();
RenderGraphImportedTextureOptions importedOptions = {};
importedOptions.initialState = ResourceStates::Present;
importedOptions.finalState = ResourceStates::Present;
importedOptions.graphOwnsTransitions = true;
MockImportedView importedView;
const RenderGraphTextureHandle backBuffer = builder.ImportTexture(
"BackBuffer",
desc,
&importedView,
importedOptions);
builder.AddRasterPass(
"FinalBlit",
[&](RenderGraphPassBuilder& pass) {
pass.WriteTexture(backBuffer);
});
CompiledRenderGraph compiledGraph;
XCEngine::Containers::String errorMessage;
ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage))
<< errorMessage.CStr();
MockTransientCommandList commandList;
RenderContext renderContext = {};
renderContext.device = reinterpret_cast<RHIDevice*>(1);
renderContext.commandList = &commandList;
renderContext.commandQueue = reinterpret_cast<RHICommandQueue*>(1);
ASSERT_TRUE(RenderGraphExecutor::Execute(compiledGraph, renderContext, &errorMessage))
<< errorMessage.CStr();
ASSERT_EQ(commandList.transitionCalls.size(), 2u);
EXPECT_EQ(commandList.transitionCalls[0].resource, &importedView);
EXPECT_EQ(commandList.transitionCalls[0].before, ResourceStates::Present);
EXPECT_EQ(commandList.transitionCalls[0].after, ResourceStates::RenderTarget);
EXPECT_EQ(commandList.transitionCalls[1].resource, &importedView);
EXPECT_EQ(commandList.transitionCalls[1].before, ResourceStates::RenderTarget);
EXPECT_EQ(commandList.transitionCalls[1].after, ResourceStates::Present);
}
TEST(RenderGraph_Test, RejectsTransientTextureReadBeforeWrite) {
RenderGraph graph;
RenderGraphBuilder builder(graph);