Align RenderGraph imported texture ownership

This commit is contained in:
2026-04-28 14:47:50 +08:00
parent 8c66cdac07
commit 0e506f21ec
10 changed files with 170 additions and 27 deletions

View File

@@ -143,6 +143,7 @@ native RenderGraph 当前支持:
- 执行阶段通过 `RenderGraphExecutionContext` 解析 view 和 texture desc。
- Graph-managed surface 必须关闭 `RenderSurface::autoTransition`,让 graph 统一负责 transition。
- imported surface 的 transition ownership 只能通过 `RenderGraphImportedTextureOptions` 表达,不要在 pass 内外重复 barrier。
- imported texture 的资源身份是 `RHITexture*`,不是某个 primary view。需要 graph 管理 transition 时,必须通过 `RenderGraphImportedTextureDesc.texture` 显式给出资源;`primaryView` 只能作为默认访问 view。surface import registry 应优先按 texture 合并,只有拿不到 texture 的非 graph-owned legacy/view-only import 才能退回按 view 区分。
- fullscreen chain 中间输出优先使用 transient texture不要把后处理链硬绑到 backbuffer。
## 6. Builtin Forward

View File

@@ -83,6 +83,10 @@ public:
void Reset();
RenderGraphTextureHandle ImportTexture(
const Containers::String& name,
const RenderGraphImportedTextureDesc& importedDesc);
RenderGraphTextureHandle ImportTexture(
const Containers::String& name,
const RenderGraphTextureDesc& desc,

View File

@@ -92,6 +92,13 @@ struct RenderGraphImportedTextureOptions {
bool graphOwnsTransitions = false;
};
struct RenderGraphImportedTextureDesc {
RenderGraphTextureDesc textureDesc = {};
RHI::RHITexture* texture = nullptr;
RHI::RHIResourceView* primaryView = nullptr;
RenderGraphImportedTextureOptions options = {};
};
struct RenderGraphTextureTransitionPlan {
bool graphOwnsTransitions = false;
bool hasFirstAccessState = false;

View File

@@ -77,15 +77,25 @@ RenderGraphTextureHandle ImportRenderGraphTexture(
return {};
}
const auto existing = registry.find(view);
RHI::RHITexture* const texture = view->GetTextureResource();
RenderGraphImportedTextureRegistryKey registryKey = {};
registryKey.texture = texture;
registryKey.view = texture == nullptr ? view : nullptr;
const auto existing = registry.find(registryKey);
if (existing != registry.end()) {
builder.MergeImportedTextureOptions(existing->second, importedOptions);
return existing->second;
}
RenderGraphImportedTextureDesc importedDesc = {};
importedDesc.textureDesc = desc;
importedDesc.texture = texture;
importedDesc.primaryView = view;
importedDesc.options = importedOptions;
const RenderGraphTextureHandle handle =
builder.ImportTexture(name, desc, view, importedOptions);
registry.emplace(view, handle);
builder.ImportTexture(name, importedDesc);
registry.emplace(registryKey, handle);
return handle;
}

View File

@@ -5,11 +5,13 @@
#include <XCEngine/Rendering/Graph/RenderGraphTypes.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <functional>
#include <unordered_map>
#include <vector>
namespace XCEngine {
namespace RHI {
class RHITexture;
class RHIResourceView;
} // namespace RHI
@@ -30,8 +32,27 @@ enum class RenderGraphSurfaceAccessMode {
ColorDepth = 1
};
struct RenderGraphImportedTextureRegistryKey {
RHI::RHITexture* texture = nullptr;
RHI::RHIResourceView* view = nullptr;
bool operator==(const RenderGraphImportedTextureRegistryKey& other) const {
return texture == other.texture && view == other.view;
}
};
struct RenderGraphImportedTextureRegistryKeyHash {
size_t operator()(const RenderGraphImportedTextureRegistryKey& key) const {
return std::hash<RHI::RHITexture*>{}(key.texture) ^
(std::hash<RHI::RHIResourceView*>{}(key.view) << 1u);
}
};
using RenderGraphImportedTextureRegistry =
std::unordered_map<RHI::RHIResourceView*, RenderGraphTextureHandle>;
std::unordered_map<
RenderGraphImportedTextureRegistryKey,
RenderGraphTextureHandle,
RenderGraphImportedTextureRegistryKeyHash>;
enum class RenderGraphSurfaceImportUsage {
Source = 0,

View File

@@ -79,17 +79,14 @@ void RenderGraphBuilder::Reset() {
RenderGraphTextureHandle RenderGraphBuilder::ImportTexture(
const Containers::String& name,
const RenderGraphTextureDesc& desc,
RHI::RHIResourceView* importedView,
const RenderGraphImportedTextureOptions& importedOptions) {
const RenderGraphImportedTextureDesc& importedDesc) {
RenderGraph::TextureResource resource = {};
resource.name = name;
resource.desc = desc;
resource.desc = importedDesc.textureDesc;
resource.kind = RenderGraphTextureKind::Imported;
resource.importedView = importedView;
resource.importedTexture =
importedView != nullptr ? importedView->GetTextureResource() : nullptr;
resource.importedOptions = importedOptions;
resource.importedView = importedDesc.primaryView;
resource.importedTexture = importedDesc.texture;
resource.importedOptions = importedDesc.options;
m_graph.m_textures.push_back(resource);
RenderGraphTextureHandle handle = {};
@@ -97,6 +94,20 @@ RenderGraphTextureHandle RenderGraphBuilder::ImportTexture(
return handle;
}
RenderGraphTextureHandle RenderGraphBuilder::ImportTexture(
const Containers::String& name,
const RenderGraphTextureDesc& desc,
RHI::RHIResourceView* importedView,
const RenderGraphImportedTextureOptions& importedOptions) {
RenderGraphImportedTextureDesc importedDesc = {};
importedDesc.textureDesc = desc;
importedDesc.texture =
importedView != nullptr ? importedView->GetTextureResource() : nullptr;
importedDesc.primaryView = importedView;
importedDesc.options = importedOptions;
return ImportTexture(name, importedDesc);
}
RenderGraphTextureHandle RenderGraphBuilder::CreateTransientTexture(
const Containers::String& name,
const RenderGraphTextureDesc& desc) {

View File

@@ -117,7 +117,7 @@ bool RenderGraphCompiler::Compile(
texture.importedOptions.graphOwnsTransitions &&
texture.importedTexture == nullptr) {
WriteError(
Containers::String("RenderGraph imported texture requires a texture-backed view when graph owns transitions: ") +
Containers::String("RenderGraph graph-owned imported texture requires an explicit RHI texture resource: ") +
texture.name,
outErrorMessage);
return false;

View File

@@ -1,6 +1,7 @@
#include <gtest/gtest.h>
#include "Rendering/Internal/ShaderVariantUtils.h"
#include "Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.h"
#include <XCEngine/Debug/Logger.h>
#include <XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h>
@@ -12,6 +13,7 @@
#include <XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h>
#include <array>
#include <functional>
#include <memory>
#include <unordered_map>
#define private public
#include <XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h>
@@ -102,6 +104,35 @@ void AppendDefaultBuiltinPassResources(ShaderPass& pass) {
}
}
class TestResourceViewBackingTexture final : public XCEngine::RHI::RHITexture {
public:
explicit TestResourceViewBackingTexture(
XCEngine::RHI::Format format,
XCEngine::RHI::TextureType textureType = XCEngine::RHI::TextureType::Texture2D)
: m_format(format)
, m_textureType(textureType) {
}
uint32_t GetWidth() const override { return 1u; }
uint32_t GetHeight() const override { return 1u; }
uint32_t GetDepth() const override { return 1u; }
uint32_t GetMipLevels() const override { return 1u; }
XCEngine::RHI::Format GetFormat() const override { return m_format; }
XCEngine::RHI::TextureType GetTextureType() const override { return m_textureType; }
XCEngine::RHI::ResourceStates GetState() const override { return m_state; }
void SetState(XCEngine::RHI::ResourceStates state) override { m_state = state; }
void* GetNativeHandle() override { return nullptr; }
const std::string& GetName() const override { return m_name; }
void SetName(const std::string& name) override { m_name = name; }
void Shutdown() override {}
private:
XCEngine::RHI::Format m_format = XCEngine::RHI::Format::Unknown;
XCEngine::RHI::TextureType m_textureType = XCEngine::RHI::TextureType::Texture2D;
XCEngine::RHI::ResourceStates m_state = XCEngine::RHI::ResourceStates::Common;
std::string m_name;
};
class TestResourceView final : public XCEngine::RHI::RHIResourceView {
public:
TestResourceView(
@@ -136,10 +167,16 @@ public:
return m_format;
}
XCEngine::RHI::RHITexture* GetTextureResource() const override {
return m_texture.get();
}
private:
XCEngine::RHI::ResourceViewType m_viewType;
XCEngine::RHI::ResourceViewDimension m_dimension;
XCEngine::RHI::Format m_format;
std::unique_ptr<TestResourceViewBackingTexture> m_texture =
std::make_unique<TestResourceViewBackingTexture>(m_format);
};
class MockForwardPipelineLayout final : public XCEngine::RHI::RHIPipelineLayout {
@@ -1188,6 +1225,8 @@ TEST(SceneRenderFeaturePass_Test, ReadsSourceColorTextureAndCopiesSourceSurfaceT
TEST(BuiltinForwardPipeline_Test, RegistersBuiltinDefaultForwardSceneFeatures) {
BuiltinForwardPipeline pipeline;
XCEngine::Rendering::Pipelines::Internal::ConfigureBuiltinForwardSceneDrawBackend(
pipeline);
ASSERT_EQ(pipeline.GetForwardSceneFeaturePassCount(), 2u);
ASSERT_NE(pipeline.GetForwardSceneFeaturePass(0u), nullptr);
@@ -1414,6 +1453,8 @@ TEST(BuiltinForwardPipeline_Test, RegistersAdditionalForwardSceneFeaturePasses)
TEST(BuiltinForwardPipeline_Test, RecordsActiveFeatureInjectionPassesIntoMainSceneGraph) {
BuiltinForwardPipeline pipeline;
XCEngine::Rendering::Pipelines::Internal::ConfigureBuiltinForwardSceneDrawBackend(
pipeline);
RenderGraph graph = {};
RenderGraphBuilder graphBuilder(graph);

View File

@@ -188,11 +188,11 @@ RenderGraphTextureDesc BuildTestRenderGraphTextureDesc() {
RenderGraphTextureHandle ImportTestTexture(
RenderGraphBuilder& graphBuilder,
size_t viewId) {
return graphBuilder.ImportTexture(
"Imported",
BuildTestRenderGraphTextureDesc(),
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(viewId),
{});
RenderGraphImportedTextureDesc importedDesc = {};
importedDesc.textureDesc = BuildTestRenderGraphTextureDesc();
importedDesc.primaryView =
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(viewId);
return graphBuilder.ImportTexture("Imported", importedDesc);
}
RenderSurface CreateColorSurface(
@@ -483,6 +483,7 @@ TEST(CameraFrameRenderGraphStageContract_Test, ResolvesScenePassRequestsFromRunt
testContext.plan.request.depthOnly.surface = RenderSurface(320, 180);
testContext.plan.request.depthOnly.surface.SetDepthAttachment(
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(142));
testContext.plan.RequestDepthOnlyStage(true);
const CameraFrameRenderGraphStageContext context = testContext.BuildStageContext();
@@ -1534,6 +1535,7 @@ TEST(CameraFrameRenderGraphStageContract_Test, ExecutesDepthOnlyFallbackPassUsin
testContext.plan.request.depthOnly.surface.SetDepthAttachment(
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(501));
testContext.plan.request.depthOnly.clearFlags = RenderClearFlags::Color;
testContext.plan.RequestDepthOnlyStage(true);
testContext.sceneData.cameraData.viewportWidth = 111u;
CameraFrameStageGraphBuildState stageState = {};

View File

@@ -122,13 +122,50 @@ private:
Format m_format = Format::Unknown;
};
class MockImportedTexture final : public RHITexture {
public:
explicit MockImportedTexture(
Format format = Format::R8G8B8A8_UNorm,
uint32_t width = 1280u,
uint32_t height = 720u)
: m_format(format)
, m_width(width)
, m_height(height) {
}
uint32_t GetWidth() const override { return m_width; }
uint32_t GetHeight() const override { return m_height; }
uint32_t GetDepth() const override { return 1u; }
uint32_t GetMipLevels() const override { return 1u; }
Format GetFormat() const override { return m_format; }
TextureType GetTextureType() const override { return TextureType::Texture2D; }
ResourceStates GetState() const override { return m_stateValue; }
void SetState(ResourceStates state) override { m_stateValue = state; }
void* GetNativeHandle() override { return nullptr; }
const std::string& GetName() const override { return m_name; }
void SetName(const std::string& name) override { m_name = name; }
void Shutdown() override {}
private:
Format m_format = Format::Unknown;
uint32_t m_width = 0u;
uint32_t m_height = 0u;
ResourceStates m_stateValue = ResourceStates::Common;
std::string m_name;
};
class MockImportedView final : public RHIResourceView {
public:
explicit MockImportedView(
ResourceViewType viewType = ResourceViewType::RenderTarget,
Format format = Format::R8G8B8A8_UNorm)
Format format = Format::R8G8B8A8_UNorm,
RHITexture* texture = nullptr)
: m_viewType(viewType)
, m_format(format) {
, m_format(format)
, m_ownedTexture(texture == nullptr
? std::make_unique<MockImportedTexture>(format)
: nullptr)
, m_texture(texture != nullptr ? texture : m_ownedTexture.get()) {
}
void Shutdown() override {}
@@ -137,10 +174,13 @@ public:
ResourceViewType GetViewType() const override { return m_viewType; }
ResourceViewDimension GetDimension() const override { return ResourceViewDimension::Texture2D; }
Format GetFormat() const override { return m_format; }
RHITexture* GetTextureResource() const override { return m_texture; }
private:
ResourceViewType m_viewType = ResourceViewType::RenderTarget;
Format m_format = Format::R8G8B8A8_UNorm;
std::unique_ptr<MockImportedTexture> m_ownedTexture;
RHITexture* m_texture = nullptr;
};
class MockTransientDevice final : public RHIDevice {
@@ -396,14 +436,16 @@ TEST(RenderGraph_Test, OrdersImportedTextureHazardsAcrossFullscreenStyleChain) {
RenderGraphBuilder builder(graph);
const RenderGraphTextureDesc desc = BuildTestTextureDesc();
MockImportedView sceneColorView;
MockImportedView postColorView;
const RenderGraphTextureHandle sceneColor = builder.ImportTexture(
"SceneColor",
desc,
reinterpret_cast<RHIResourceView*>(1));
&sceneColorView);
const RenderGraphTextureHandle postColor = builder.ImportTexture(
"PostColor",
desc,
reinterpret_cast<RHIResourceView*>(2));
&postColorView);
builder.AddRasterPass(
"MainScene",
@@ -457,10 +499,13 @@ TEST(RenderGraph_Test, PreservesImportedTextureStateContractAcrossCompile) {
importedOptions.finalState = ResourceStates::PixelShaderResource;
importedOptions.graphOwnsTransitions = true;
MockImportedView importedView(
ResourceViewType::ShaderResource,
Format::R8G8B8A8_UNorm);
const RenderGraphTextureHandle importedTexture = builder.ImportTexture(
"ImportedColor",
desc,
reinterpret_cast<RHIResourceView*>(7),
&importedView,
importedOptions);
builder.AddRasterPass(
@@ -676,15 +721,15 @@ TEST(RenderGraph_Test, ExecutesTransientDepthTransitionsWithDepthStencilView) {
EXPECT_EQ(allocationState->createTextureCalls, 1);
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 0);
EXPECT_EQ(allocationState->createDepthViewCalls, 1);
EXPECT_EQ(allocationState->createShaderViewCalls, 0);
EXPECT_EQ(allocationState->createShaderViewCalls, 1);
EXPECT_EQ(allocationState->shutdownTextureCalls, 1);
EXPECT_EQ(allocationState->shutdownRenderTargetViewCalls, 0);
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 1);
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 0);
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 1);
EXPECT_EQ(allocationState->destroyTextureCalls, 1);
EXPECT_EQ(allocationState->destroyRenderTargetViewCalls, 0);
EXPECT_EQ(allocationState->destroyDepthViewCalls, 1);
EXPECT_EQ(allocationState->destroyShaderViewCalls, 0);
EXPECT_EQ(allocationState->destroyShaderViewCalls, 1);
}
TEST(RenderGraph_Test, ExecutesGraphOwnedImportedDepthTransitionsAtGraphBoundaries) {
@@ -864,10 +909,11 @@ TEST(RenderGraph_Test, ExecutesCompiledPassCallbacksInCompiledOrder) {
const RenderGraphTextureDesc desc = BuildTestTextureDesc();
const RenderGraphTextureHandle sceneColor = builder.CreateTransientTexture("SceneColor", desc);
MockImportedView backBufferView;
const RenderGraphTextureHandle backBuffer = builder.ImportTexture(
"BackBuffer",
desc,
reinterpret_cast<RHIResourceView*>(1));
&backBufferView);
std::vector<std::string> eventLog;
auto allocationState = std::make_shared<MockTransientAllocationState>();