diff --git a/docs/used/SRP_URP_NativeSceneFeatureIdBridgePlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_NativeSceneFeatureIdBridgePlan_2026-04-21_完成归档.md new file mode 100644 index 00000000..cb9427fa --- /dev/null +++ b/docs/used/SRP_URP_NativeSceneFeatureIdBridgePlan_2026-04-21_完成归档.md @@ -0,0 +1,74 @@ +# SRP / URP Native Scene Feature Id Bridge Plan + +## Background + +Current managed URP feature wrappers for gaussian splat and volumetric rendering +already live in the URP package, but the bridge into native runtime still uses +string names: + +- managed `NativeSceneFeaturePass("BuiltinGaussianSplatPass", ...)` +- managed `NativeSceneFeaturePass("BuiltinVolumetricPass", ...)` +- native host resolves feature passes by string comparison + +This is a transitional seam, not a stable long-term SRP/URP backbone. + +## Goal + +Replace string-based native scene feature dispatch with an explicit feature id bridge. + +After this phase: + +- managed URP records native scene features by enum/id +- native scene feature host resolves by stable feature id +- feature pass names remain only for logging / graph labeling +- future native-backed URP features can reuse the same bridge cleanly + +## Scope + +### Step 1. Add explicit native scene feature id type + +- add native enum for scene feature ids +- add managed enum mirror +- keep values explicit and stable + +### Step 2. Move recorder bridge from string to id + +- `ScriptableRenderContext.RecordNativeSceneFeaturePass(...)` +- Mono internal call bridge +- `NativeSceneRecorder::RecordFeaturePass(...)` +- `SceneRenderFeatureHost::RecordFeaturePass(...)` + +### Step 3. Bind builtin native features to explicit ids + +- `BuiltinGaussianSplatPass` +- `BuiltinVolumetricPass` +- managed `NativeSceneFeaturePass` +- managed builtin renderer features + +## Non-goals + +- changing gaussian splat runtime logic +- changing volumetric runtime logic +- moving shadow runtime in this phase +- deferred renderer work + +## Exit Criteria + +- no managed/native string-based native feature dispatch remains +- old editor `XCEditor` Debug build passes +- old editor smoke reaches `SceneReady` + +## Result + +Completed on 2026-04-21. + +- added explicit native/managed `NativeSceneFeaturePassId` enums +- replaced string-based native scene feature pass recording with id-based dispatch +- bound builtin gaussian splat / volumetric native passes to stable ids +- kept pass names only for logging and render-graph labeling + +## Verification + +- `cmake --build . --config Debug --target XCEditor` +- old editor smoke passed +- `SceneReady elapsed_ms=6277 first_frame_ms=637` diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 7fee873d..4b9222de 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -539,6 +539,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderSurface.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderSurfacePipelineUtils.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeaturePass.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeaturePassId.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeatureHost.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/ShaderVariantUtils.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Internal/RenderSurfacePipelineUtils.h diff --git a/engine/include/XCEngine/Rendering/Features/BuiltinGaussianSplatPass.h b/engine/include/XCEngine/Rendering/Features/BuiltinGaussianSplatPass.h index fee841d6..113d997b 100644 --- a/engine/include/XCEngine/Rendering/Features/BuiltinGaussianSplatPass.h +++ b/engine/include/XCEngine/Rendering/Features/BuiltinGaussianSplatPass.h @@ -48,6 +48,10 @@ public: SceneRenderInjectionPoint GetInjectionPoint() const override { return SceneRenderInjectionPoint::BeforeTransparent; } + NativeSceneFeaturePassId GetNativeSceneFeaturePassId() + const override { + return NativeSceneFeaturePassId::BuiltinGaussianSplat; + } bool Initialize(const RenderContext& context) override; bool IsActive(const RenderSceneData& sceneData) const override; bool Prepare( diff --git a/engine/include/XCEngine/Rendering/Features/BuiltinVolumetricPass.h b/engine/include/XCEngine/Rendering/Features/BuiltinVolumetricPass.h index dc03a88a..2b9164ff 100644 --- a/engine/include/XCEngine/Rendering/Features/BuiltinVolumetricPass.h +++ b/engine/include/XCEngine/Rendering/Features/BuiltinVolumetricPass.h @@ -43,6 +43,10 @@ public: SceneRenderInjectionPoint GetInjectionPoint() const override { return SceneRenderInjectionPoint::BeforeTransparent; } + NativeSceneFeaturePassId GetNativeSceneFeaturePassId() + const override { + return NativeSceneFeaturePassId::BuiltinVolumetric; + } bool Initialize(const RenderContext& context) override; bool IsActive(const RenderSceneData& sceneData) const override; bool Prepare( diff --git a/engine/include/XCEngine/Rendering/Pipelines/NativeSceneRecorder.h b/engine/include/XCEngine/Rendering/Pipelines/NativeSceneRecorder.h index 6f026bf1..8a04ee66 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/NativeSceneRecorder.h +++ b/engine/include/XCEngine/Rendering/Pipelines/NativeSceneRecorder.h @@ -30,7 +30,7 @@ public: bool RecordScenePhase(ScenePhase scenePhase); bool RecordSceneDrawSettings(const DrawSettings& drawSettings); bool RecordInjectionPoint(SceneRenderInjectionPoint injectionPoint); - bool RecordFeaturePass(const Containers::String& featurePassName); + bool RecordFeaturePass(NativeSceneFeaturePassId featurePassId); private: SceneRenderFeaturePassBeginCallback BuildBeginRecordedPassCallback( diff --git a/engine/include/XCEngine/Rendering/SceneRenderFeatureHost.h b/engine/include/XCEngine/Rendering/SceneRenderFeatureHost.h index 4deebf1a..4d5ffb88 100644 --- a/engine/include/XCEngine/Rendering/SceneRenderFeatureHost.h +++ b/engine/include/XCEngine/Rendering/SceneRenderFeatureHost.h @@ -23,7 +23,7 @@ public: bool* recordedAnyPass = nullptr) const; bool RecordFeaturePass( const SceneRenderFeaturePassRenderGraphContext& context, - const Containers::String& featurePassName, + NativeSceneFeaturePassId featurePassId, bool* recordedPass = nullptr) const; bool Execute( const FrameExecutionContext& executionContext, diff --git a/engine/include/XCEngine/Rendering/SceneRenderFeaturePass.h b/engine/include/XCEngine/Rendering/SceneRenderFeaturePass.h index 591684fc..7e2666a0 100644 --- a/engine/include/XCEngine/Rendering/SceneRenderFeaturePass.h +++ b/engine/include/XCEngine/Rendering/SceneRenderFeaturePass.h @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -44,6 +45,11 @@ public: return SceneRenderInjectionPoint::BeforeTransparent; } + virtual NativeSceneFeaturePassId + GetNativeSceneFeaturePassId() const { + return NativeSceneFeaturePassId::Invalid; + } + bool SupportsInjectionPoint(SceneRenderInjectionPoint injectionPoint) const { return GetInjectionPoint() == injectionPoint; } diff --git a/engine/include/XCEngine/Rendering/SceneRenderFeaturePassId.h b/engine/include/XCEngine/Rendering/SceneRenderFeaturePassId.h new file mode 100644 index 00000000..9eaae615 --- /dev/null +++ b/engine/include/XCEngine/Rendering/SceneRenderFeaturePassId.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Rendering { + +enum class NativeSceneFeaturePassId : uint32_t { + Invalid = 0u, + BuiltinGaussianSplat = 1u, + BuiltinVolumetric = 2u +}; + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp b/engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp index e1afcfac..16247a0f 100644 --- a/engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp +++ b/engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp @@ -374,9 +374,10 @@ bool NativeSceneRecorder::RecordInjectionPoint( } bool NativeSceneRecorder::RecordFeaturePass( - const Containers::String& featurePassName) { + NativeSceneFeaturePassId featurePassId) { if (m_context.stage != CameraFrameStage::MainScene || - featurePassName.Empty()) { + featurePassId == + NativeSceneFeaturePassId::Invalid) { return false; } @@ -400,7 +401,7 @@ bool NativeSceneRecorder::RecordFeaturePass( m_clearAttachments, beginRecordedPass, endRecordedPass), - featurePassName, + featurePassId, &recordedPass)) { return false; } diff --git a/engine/src/Rendering/SceneRenderFeatureHost.cpp b/engine/src/Rendering/SceneRenderFeatureHost.cpp index 8dfd527d..5a699439 100644 --- a/engine/src/Rendering/SceneRenderFeatureHost.cpp +++ b/engine/src/Rendering/SceneRenderFeatureHost.cpp @@ -48,12 +48,13 @@ Containers::String BuildNamedFeatureGraphPassName( return Containers::String(name.c_str()); } -bool MatchesFeaturePassName( +bool MatchesFeaturePassId( const SceneRenderFeaturePass& featurePass, - const Containers::String& featurePassName) { - const char* const passName = featurePass.GetName(); - return passName != nullptr && - featurePassName == Containers::String(passName); + NativeSceneFeaturePassId featurePassId) { + return featurePassId != + NativeSceneFeaturePassId::Invalid && + featurePass.GetNativeSceneFeaturePassId() == + featurePassId; } } // namespace @@ -180,13 +181,14 @@ bool SceneRenderFeatureHost::Record( bool SceneRenderFeatureHost::RecordFeaturePass( const SceneRenderFeaturePassRenderGraphContext& context, - const Containers::String& featurePassName, + NativeSceneFeaturePassId featurePassId, bool* recordedPass) const { if (recordedPass != nullptr) { *recordedPass = false; } - if (featurePassName.Empty()) { + if (featurePassId == + NativeSceneFeaturePassId::Invalid) { return false; } @@ -196,7 +198,9 @@ bool SceneRenderFeatureHost::RecordFeaturePass( m_featurePasses[featureIndex]; SceneRenderFeaturePass* const featurePass = featurePassOwner.get(); if (featurePass == nullptr || - !MatchesFeaturePassName(*featurePass, featurePassName)) { + !MatchesFeaturePassId( + *featurePass, + featurePassId)) { continue; } @@ -217,7 +221,7 @@ bool SceneRenderFeatureHost::RecordFeaturePass( Debug::LogCategory::Rendering, (Containers::String( "SceneRenderFeatureHost record failed for feature pass '") + - featurePassName + + featurePass->GetName() + "': " + featurePass->GetName()) .CStr()); @@ -233,8 +237,12 @@ bool SceneRenderFeatureHost::RecordFeaturePass( Debug::Logger::Get().Error( Debug::LogCategory::Rendering, (Containers::String( - "SceneRenderFeatureHost could not resolve feature pass: ") + - featurePassName) + "SceneRenderFeatureHost could not resolve feature pass id: ") + + Containers::String( + std::to_string( + static_cast( + featurePassId)) + .c_str())) .CStr()); return false; } diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 89bdfb18..1c7f5a03 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -5390,7 +5390,7 @@ InternalCall_Rendering_ScriptableRenderContext_RecordSceneInjectionPoint( mono_bool InternalCall_Rendering_ScriptableRenderContext_RecordNativeSceneFeaturePass( uint64_t nativeHandle, - MonoString* featurePassName) { + int32_t featurePassId) { ManagedScriptableRenderContextState* const state = FindManagedScriptableRenderContextState(nativeHandle); if (state == nullptr || @@ -5400,14 +5400,17 @@ InternalCall_Rendering_ScriptableRenderContext_RecordNativeSceneFeaturePass( return 0; } - const std::string featurePassNameUtf8 = - MonoStringToUtf8(featurePassName); - if (featurePassNameUtf8.empty()) { + const Rendering::NativeSceneFeaturePassId + resolvedFeaturePassId = + static_cast( + featurePassId); + if (resolvedFeaturePassId == + Rendering::NativeSceneFeaturePassId::Invalid) { return 0; } return state->sceneRecorder->RecordFeaturePass( - Containers::String(featurePassNameUtf8.c_str())) + resolvedFeaturePassId) ? 1 : 0; } diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index e23e4a01..412b0ef7 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -189,6 +189,7 @@ set(XCENGINE_SCRIPT_CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/FinalColorToneMappingMode.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/FilteringSettings.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/GraphicsSettings.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/NativeSceneFeaturePassId.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/RenderSceneSetupContext.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/RenderPipelineAsset.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/RenderClearFlags.cs diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinGaussianSplatRendererFeature.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinGaussianSplatRendererFeature.cs index ecd9730f..46dc72b9 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinGaussianSplatRendererFeature.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinGaussianSplatRendererFeature.cs @@ -25,7 +25,8 @@ namespace XCEngine.Rendering.Universal public override void Create() { m_pass = new NativeSceneFeaturePass( - "BuiltinGaussianSplatPass", + NativeSceneFeaturePassId + .BuiltinGaussianSplat, passEvent); } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinVolumetricRendererFeature.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinVolumetricRendererFeature.cs index 55800df3..51aac959 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinVolumetricRendererFeature.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinVolumetricRendererFeature.cs @@ -25,7 +25,8 @@ namespace XCEngine.Rendering.Universal public override void Create() { m_pass = new NativeSceneFeaturePass( - "BuiltinVolumetricPass", + NativeSceneFeaturePassId + .BuiltinVolumetric, passEvent); } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/NativeSceneFeaturePass.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/NativeSceneFeaturePass.cs index 60857aaa..4d2ff2c9 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/NativeSceneFeaturePass.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/NativeSceneFeaturePass.cs @@ -6,14 +6,14 @@ namespace XCEngine.Rendering.Universal internal sealed class NativeSceneFeaturePass : ScriptableRenderPass { - private readonly string m_featurePassName; + private readonly NativeSceneFeaturePassId m_featurePassId; public NativeSceneFeaturePass( - string featurePassName, + NativeSceneFeaturePassId featurePassId, RenderPassEvent passEvent) { - m_featurePassName = - featurePassName ?? string.Empty; + m_featurePassId = + featurePassId; renderPassEvent = passEvent; } @@ -31,7 +31,7 @@ namespace XCEngine.Rendering.Universal renderingData != null && renderingData.isMainSceneStage && context.RecordNativeSceneFeaturePass( - m_featurePassName); + m_featurePassId); } } } diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index b5a69838..49a0a0bd 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -891,7 +891,7 @@ namespace XCEngine internal static extern bool Rendering_ScriptableRenderContext_RecordNativeSceneFeaturePass( ulong nativeHandle, - string featurePassName); + int featurePassId); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern int diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/NativeSceneFeaturePassId.cs b/managed/XCEngine.ScriptCore/Rendering/Core/NativeSceneFeaturePassId.cs new file mode 100644 index 00000000..a0b6c29d --- /dev/null +++ b/managed/XCEngine.ScriptCore/Rendering/Core/NativeSceneFeaturePassId.cs @@ -0,0 +1,9 @@ +namespace XCEngine.Rendering +{ + public enum NativeSceneFeaturePassId + { + Invalid = 0, + BuiltinGaussianSplat = 1, + BuiltinVolumetric = 2 + } +} diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs index de2dfc66..c350cd51 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs @@ -70,14 +70,14 @@ namespace XCEngine.Rendering } public bool RecordNativeSceneFeaturePass( - string featurePassName) + NativeSceneFeaturePassId featurePassId) { - return !string.IsNullOrEmpty( - featurePassName) && + return featurePassId != + NativeSceneFeaturePassId.Invalid && InternalCalls .Rendering_ScriptableRenderContext_RecordNativeSceneFeaturePass( m_nativeHandle, - featurePassName); + (int)featurePassId); } public bool DrawRenderers(