diff --git a/docs/used/SRP_UniversalDirectionalShadowExecutionPolicyManagedOwnershipPlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalDirectionalShadowExecutionPolicyManagedOwnershipPlan_完成归档_2026-04-21.md new file mode 100644 index 00000000..d3e380fa --- /dev/null +++ b/docs/used/SRP_UniversalDirectionalShadowExecutionPolicyManagedOwnershipPlan_完成归档_2026-04-21.md @@ -0,0 +1,65 @@ +# SRP Universal Directional Shadow Execution Policy Managed Ownership Plan 2026-04-21 + +## Goal + +Move directional shadow execution policy selection out of native host default logic and into managed URP ownership. + +This stage still keeps native shadow rendering and native surface allocation. +It only makes managed URP explicitly choose which native directional-shadow execution policy asset key should be used. + +## Why This Stage + +The current SRP/URP split already moved: + +1. renderer selection into managed URP; +2. builtin scene feature ownership into managed renderer features; +3. shadow planning defaults into managed asset settings; +4. shadow-caster standalone pass selection into managed URP. + +The remaining hardcoded gap is the conversion from: + +1. `DirectionalShadowRenderPlan` + +to + +2. `DirectionalShadowExecutionState` (`shadowCasterRequest + shadowData`). + +That conversion is still owned by native host fallback logic through +`RenderPipeline::ConfigureDirectionalShadowExecutionState()`. + +## Scope + +Included: + +1. add a native registry for directional shadow execution policies; +2. add managed asset / renderer-data API for directional shadow execution policy asset keys; +3. let Mono runtime resolve those keys from managed pipeline assets; +4. let `ScriptableRenderPipelineHost` use managed-selected directional shadow execution policy keys; +5. make `UniversalRendererData` explicitly own the builtin directional shadow execution policy key; +6. rebuild `XCEditor` and run old editor smoke. + +Not included: + +1. moving shadow rasterization into C#; +2. cascades; +3. atlas packing; +4. custom managed shadow execution policy authoring UI. + +## Acceptance + +This stage is complete when: + +1. managed URP explicitly owns directional shadow execution policy selection; +2. native host no longer blindly applies the default directional shadow execution policy for managed pipelines; +3. builtin fallback rendering still works; +4. `XCEditor` build and old editor smoke both pass. + +## Result + +Completed on 2026-04-21. + +Validation: + +1. `cmake --build . --config Debug --target XCEditor` passed; +2. old editor smoke passed with `editor/bin/Debug/XCEngine.exe`; +3. `editor/bin/Debug/editor.log` recorded `SceneReady` at `2026-04-21 02:47:28`. diff --git a/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h b/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h index f866380a..e2cc8d2a 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h +++ b/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h @@ -107,6 +107,15 @@ public: (void)rendererIndex; return GetCameraFrameStandalonePassAssetKey(stage); } + virtual std::string GetDirectionalShadowExecutionPolicyAssetKey() + const { + return {}; + } + virtual std::string GetDirectionalShadowExecutionPolicyAssetKey( + int32_t rendererIndex) const { + (void)rendererIndex; + return GetDirectionalShadowExecutionPolicyAssetKey(); + } virtual bool TryGetDefaultFinalColorSettings(FinalColorSettings&) const { return false; diff --git a/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h b/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h index 45539179..347c98b8 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h +++ b/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h @@ -58,6 +58,10 @@ public: const RenderContext& context, const RenderSurface& surface, const RenderSceneData& sceneData) override; + bool ConfigureDirectionalShadowExecutionState( + const CameraFramePlan& plan, + const DirectionalShadowSurfaceAllocation& shadowAllocation, + DirectionalShadowExecutionState& shadowState) const override; RenderPass* GetCameraFrameStandalonePass( CameraFrameStage stage, int32_t rendererIndex) const override; diff --git a/engine/src/Rendering/Internal/RenderPipelineFactory.cpp b/engine/src/Rendering/Internal/RenderPipelineFactory.cpp index 01264195..afb2f3dc 100644 --- a/engine/src/Rendering/Internal/RenderPipelineFactory.cpp +++ b/engine/src/Rendering/Internal/RenderPipelineFactory.cpp @@ -1,5 +1,6 @@ #include "Rendering/Internal/RenderPipelineFactory.h" +#include "Rendering/Execution/DirectionalShadowExecutionState.h" #include "Rendering/Pipelines/BuiltinForwardPipeline.h" #include "Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h" #include "Rendering/Pipelines/ScriptableRenderPipelineHost.h" @@ -41,6 +42,8 @@ using PipelineRendererAssetRegistry = std::unordered_map; using CameraFrameStandalonePassRegistry = std::unordered_map; +using DirectionalShadowExecutionPolicyRegistry = + std::unordered_map; PipelineRendererAssetRegistry& GetPipelineRendererAssetRegistry() { static PipelineRendererAssetRegistry registry = {}; @@ -52,6 +55,12 @@ CameraFrameStandalonePassRegistry& GetCameraFrameStandalonePassRegistry() { return registry; } +DirectionalShadowExecutionPolicyRegistry& +GetDirectionalShadowExecutionPolicyRegistry() { + static DirectionalShadowExecutionPolicyRegistry registry = {}; + return registry; +} + std::unordered_set& GetBuiltinPipelineRendererAssetKeys() { static std::unordered_set builtinKeys = {}; return builtinKeys; @@ -62,6 +71,12 @@ std::unordered_set& GetBuiltinCameraFrameStandalonePassKeys() { return builtinKeys; } +std::unordered_set& +GetBuiltinDirectionalShadowExecutionPolicyKeys() { + static std::unordered_set builtinKeys = {}; + return builtinKeys; +} + std::mutex& GetPipelineRendererAssetRegistryMutex() { static std::mutex mutex; return mutex; @@ -72,6 +87,11 @@ std::mutex& GetCameraFrameStandalonePassRegistryMutex() { return mutex; } +std::mutex& GetDirectionalShadowExecutionPolicyRegistryMutex() { + static std::mutex mutex; + return mutex; +} + void EnsureBuiltinPipelineRendererAssetRegistryInitialized() { static const bool initialized = []() { PipelineRendererAssetRegistry& registry = @@ -108,6 +128,29 @@ void EnsureBuiltinCameraFrameStandalonePassRegistryInitialized() { (void)initialized; } +void EnsureBuiltinDirectionalShadowExecutionPolicyRegistryInitialized() { + static const bool initialized = []() { + DirectionalShadowExecutionPolicyRegistry& registry = + GetDirectionalShadowExecutionPolicyRegistry(); + registry.emplace( + "BuiltinDirectionalShadow", + [](const CameraFramePlan& plan, + const DirectionalShadowSurfaceAllocation& shadowAllocation, + DirectionalShadowExecutionState& shadowState) { + ApplyDefaultRenderPipelineDirectionalShadowExecutionPolicy( + plan, + shadowAllocation, + shadowState); + return shadowState.shadowCasterRequest.IsValid() && + shadowState.shadowData.IsValid(); + }); + GetBuiltinDirectionalShadowExecutionPolicyKeys().insert( + "BuiltinDirectionalShadow"); + return true; + }(); + (void)initialized; +} + std::unique_ptr TryCreateNativeSceneRendererFromAsset( const std::shared_ptr& asset) { if (asset == nullptr) { @@ -260,6 +303,73 @@ std::unique_ptr CreateCameraFrameStandalonePassByKey( return it->second(); } +bool RegisterDirectionalShadowExecutionPolicy( + const std::string& key, + DirectionalShadowExecutionPolicy policy) { + if (key.empty() || !policy) { + return false; + } + + EnsureBuiltinDirectionalShadowExecutionPolicyRegistryInitialized(); + + std::lock_guard lock( + GetDirectionalShadowExecutionPolicyRegistryMutex()); + DirectionalShadowExecutionPolicyRegistry& registry = + GetDirectionalShadowExecutionPolicyRegistry(); + if (registry.find(key) != registry.end()) { + return false; + } + + registry.emplace(key, std::move(policy)); + return true; +} + +bool UnregisterDirectionalShadowExecutionPolicy( + const std::string& key) { + if (key.empty()) { + return false; + } + + EnsureBuiltinDirectionalShadowExecutionPolicyRegistryInitialized(); + + std::lock_guard lock( + GetDirectionalShadowExecutionPolicyRegistryMutex()); + if (GetBuiltinDirectionalShadowExecutionPolicyKeys().find(key) != + GetBuiltinDirectionalShadowExecutionPolicyKeys().end()) { + return false; + } + + DirectionalShadowExecutionPolicyRegistry& registry = + GetDirectionalShadowExecutionPolicyRegistry(); + return registry.erase(key) != 0u; +} + +bool ConfigureDirectionalShadowExecutionStateByKey( + const std::string& key, + const CameraFramePlan& plan, + const DirectionalShadowSurfaceAllocation& shadowAllocation, + DirectionalShadowExecutionState& shadowState) { + if (key.empty()) { + return false; + } + + EnsureBuiltinDirectionalShadowExecutionPolicyRegistryInitialized(); + + std::lock_guard lock( + GetDirectionalShadowExecutionPolicyRegistryMutex()); + const DirectionalShadowExecutionPolicyRegistry& registry = + GetDirectionalShadowExecutionPolicyRegistry(); + const auto it = registry.find(key); + if (it == registry.end() || !it->second) { + return false; + } + + return it->second( + plan, + shadowAllocation, + shadowState); +} + std::shared_ptr ResolveRenderPipelineAssetOrDefault( std::shared_ptr preferredAsset) { if (preferredAsset != nullptr) { diff --git a/engine/src/Rendering/Internal/RenderPipelineFactory.h b/engine/src/Rendering/Internal/RenderPipelineFactory.h index bb78efef..6d40087e 100644 --- a/engine/src/Rendering/Internal/RenderPipelineFactory.h +++ b/engine/src/Rendering/Internal/RenderPipelineFactory.h @@ -11,6 +11,9 @@ class NativeSceneRenderer; class RenderPipeline; class RenderPipelineAsset; class RenderPass; +struct CameraFramePlan; +struct DirectionalShadowExecutionState; +struct DirectionalShadowSurfaceAllocation; namespace Internal { @@ -18,6 +21,11 @@ using PipelineRendererAssetFactory = std::function()>; using CameraFrameStandalonePassFactory = std::function()>; +using DirectionalShadowExecutionPolicy = + std::function; std::shared_ptr CreateConfiguredRenderPipelineAsset(); std::shared_ptr CreateFallbackRenderPipelineAsset(); @@ -34,6 +42,16 @@ bool UnregisterCameraFrameStandalonePassFactory( const std::string& key); std::unique_ptr CreateCameraFrameStandalonePassByKey( const std::string& key); +bool RegisterDirectionalShadowExecutionPolicy( + const std::string& key, + DirectionalShadowExecutionPolicy policy); +bool UnregisterDirectionalShadowExecutionPolicy( + const std::string& key); +bool ConfigureDirectionalShadowExecutionStateByKey( + const std::string& key, + const CameraFramePlan& plan, + const DirectionalShadowSurfaceAllocation& shadowAllocation, + DirectionalShadowExecutionState& shadowState); std::shared_ptr ResolveRenderPipelineAssetOrDefault( std::shared_ptr preferredAsset); diff --git a/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp b/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp index bbcaae6c..040a94a9 100644 --- a/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp +++ b/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp @@ -171,6 +171,44 @@ bool ScriptableRenderPipelineHost::Render( m_pipelineRenderer->Render(context, surface, sceneData); } +bool ScriptableRenderPipelineHost::ConfigureDirectionalShadowExecutionState( + const CameraFramePlan& plan, + const DirectionalShadowSurfaceAllocation& shadowAllocation, + DirectionalShadowExecutionState& shadowState) const { + if (m_managedAssetRuntime == nullptr) { + return RenderPipeline::ConfigureDirectionalShadowExecutionState( + plan, + shadowAllocation, + shadowState); + } + + const std::string assetKey = + m_managedAssetRuntime + ->GetDirectionalShadowExecutionPolicyAssetKey( + plan.request.rendererIndex); + if (assetKey.empty()) { + return RenderPipeline::ConfigureDirectionalShadowExecutionState( + plan, + shadowAllocation, + shadowState); + } + + if (!Rendering::Internal::ConfigureDirectionalShadowExecutionStateByKey( + assetKey, + plan, + shadowAllocation, + shadowState)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + Containers::String( + "ScriptableRenderPipelineHost failed to resolve directional shadow execution policy asset key: ") + + assetKey.c_str()); + return false; + } + + return true; +} + RenderPass* ScriptableRenderPipelineHost::GetCameraFrameStandalonePass( CameraFrameStage stage, int32_t rendererIndex) const { diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 26d0e3ba..186d3aba 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -1319,6 +1319,10 @@ public: GetPipelineRendererAsset() const override; std::shared_ptr GetPipelineRendererAsset(int32_t rendererIndex) const override; + std::string GetDirectionalShadowExecutionPolicyAssetKey() const + override; + std::string GetDirectionalShadowExecutionPolicyAssetKey( + int32_t rendererIndex) const override; std::string GetCameraFrameStandalonePassAssetKey( Rendering::CameraFrameStage stage) const override; std::string GetCameraFrameStandalonePassAssetKey( @@ -1358,6 +1362,11 @@ private: MonoObject* assetObject) const; MonoMethod* ResolveGetPipelineRendererAssetKeyContextualMethod( MonoObject* assetObject) const; + MonoMethod* ResolveGetDirectionalShadowExecutionPolicyAssetKeyMethod( + MonoObject* assetObject) const; + MonoMethod* + ResolveGetDirectionalShadowExecutionPolicyAssetKeyContextualMethod( + MonoObject* assetObject) const; MonoMethod* ResolveGetCameraFrameStandalonePassAssetKeyMethod( MonoObject* assetObject) const; MonoMethod* @@ -1379,6 +1388,11 @@ private: mutable MonoMethod* m_getPipelineRendererAssetKeyMethod = nullptr; mutable MonoMethod* m_getPipelineRendererAssetKeyContextualMethod = nullptr; + mutable MonoMethod* + m_getDirectionalShadowExecutionPolicyAssetKeyMethod = nullptr; + mutable MonoMethod* + m_getDirectionalShadowExecutionPolicyAssetKeyContextualMethod = + nullptr; mutable MonoMethod* m_getCameraFrameStandalonePassAssetKeyMethod = nullptr; mutable MonoMethod* @@ -2015,6 +2029,61 @@ MonoManagedRenderPipelineAssetRuntime::GetPipelineRendererAsset( return m_pipelineRendererAsset; } +std::string MonoManagedRenderPipelineAssetRuntime:: + GetDirectionalShadowExecutionPolicyAssetKey() const { + return GetDirectionalShadowExecutionPolicyAssetKey(-1); +} + +std::string MonoManagedRenderPipelineAssetRuntime:: + GetDirectionalShadowExecutionPolicyAssetKey( + int32_t rendererIndex) const { + if (!SyncManagedAssetRuntimeState()) { + return {}; + } + + MonoObject* const assetObject = GetManagedAssetObject(); + if (assetObject == nullptr) { + return {}; + } + + MonoMethod* const contextualMethod = + ResolveGetDirectionalShadowExecutionPolicyAssetKeyContextualMethod( + assetObject); + if (contextualMethod != nullptr) { + void* args[1] = { &rendererIndex }; + MonoObject* managedKeyObject = nullptr; + if (!m_runtime->InvokeManagedMethod( + assetObject, + contextualMethod, + args, + &managedKeyObject)) { + return {}; + } + + return MonoStringToUtf8( + reinterpret_cast(managedKeyObject)); + } + + MonoMethod* const method = + ResolveGetDirectionalShadowExecutionPolicyAssetKeyMethod( + assetObject); + if (method == nullptr) { + return {}; + } + + MonoObject* managedKeyObject = nullptr; + if (!m_runtime->InvokeManagedMethod( + assetObject, + method, + nullptr, + &managedKeyObject)) { + return {}; + } + + return MonoStringToUtf8( + reinterpret_cast(managedKeyObject)); +} + std::string MonoManagedRenderPipelineAssetRuntime:: GetCameraFrameStandalonePassAssetKey( Rendering::CameraFrameStage stage) const { @@ -2288,6 +2357,9 @@ void MonoManagedRenderPipelineAssetRuntime::ReleaseManagedAsset() const { m_getRuntimeResourceVersionMethod = nullptr; m_getPipelineRendererAssetKeyMethod = nullptr; m_getPipelineRendererAssetKeyContextualMethod = nullptr; + m_getDirectionalShadowExecutionPolicyAssetKeyMethod = nullptr; + m_getDirectionalShadowExecutionPolicyAssetKeyContextualMethod = + nullptr; m_getCameraFrameStandalonePassAssetKeyMethod = nullptr; m_getCameraFrameStandalonePassAssetKeyContextualMethod = nullptr; m_pipelineRendererAsset.reset(); @@ -2438,6 +2510,39 @@ MonoManagedRenderPipelineAssetRuntime::ResolveGetPipelineRendererAssetKeyContext return m_getPipelineRendererAssetKeyContextualMethod; } +MonoMethod* +MonoManagedRenderPipelineAssetRuntime:: + ResolveGetDirectionalShadowExecutionPolicyAssetKeyMethod( + MonoObject* assetObject) const { + if (m_getDirectionalShadowExecutionPolicyAssetKeyMethod == + nullptr) { + m_getDirectionalShadowExecutionPolicyAssetKeyMethod = + m_runtime->ResolveManagedMethod( + assetObject, + "GetDirectionalShadowExecutionPolicyAssetKey", + 0); + } + + return m_getDirectionalShadowExecutionPolicyAssetKeyMethod; +} + +MonoMethod* +MonoManagedRenderPipelineAssetRuntime:: + ResolveGetDirectionalShadowExecutionPolicyAssetKeyContextualMethod( + MonoObject* assetObject) const { + if (m_getDirectionalShadowExecutionPolicyAssetKeyContextualMethod == + nullptr) { + m_getDirectionalShadowExecutionPolicyAssetKeyContextualMethod = + m_runtime->ResolveManagedMethod( + assetObject, + "GetDirectionalShadowExecutionPolicyAssetKeyContextual", + 1); + } + + return + m_getDirectionalShadowExecutionPolicyAssetKeyContextualMethod; +} + MonoMethod* MonoManagedRenderPipelineAssetRuntime:: ResolveGetCameraFrameStandalonePassAssetKeyMethod( diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBackedRenderPipelineAsset.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBackedRenderPipelineAsset.cs index f8c599a4..9dff0ca0 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBackedRenderPipelineAsset.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBackedRenderPipelineAsset.cs @@ -104,6 +104,29 @@ namespace XCEngine.Rendering.Universal rendererIndex); } + protected override string + GetDirectionalShadowExecutionPolicyAssetKeyContextual( + int rendererIndex) + { + ScriptableRendererData resolvedRendererData = + GetRendererData(rendererIndex); + if (resolvedRendererData != null) + { + string executionPolicyAssetKey = + resolvedRendererData + .GetDirectionalShadowExecutionPolicyAssetKeyInstance(); + if (!string.IsNullOrEmpty( + executionPolicyAssetKey)) + { + return executionPolicyAssetKey; + } + } + + return base + .GetDirectionalShadowExecutionPolicyAssetKeyContextual( + rendererIndex); + } + protected override void ReleaseRuntimeResources() { ReleaseRendererDataRuntimeResources(); diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs index 4d69e523..adfe8e4c 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs @@ -68,6 +68,12 @@ namespace XCEngine.Rendering.Universal stage); } + internal string + GetDirectionalShadowExecutionPolicyAssetKeyInstance() + { + return GetDirectionalShadowExecutionPolicyAssetKey(); + } + internal int GetRuntimeStateVersionInstance() { return m_runtimeStateVersion; @@ -166,6 +172,12 @@ namespace XCEngine.Rendering.Universal return string.Empty; } + protected virtual string + GetDirectionalShadowExecutionPolicyAssetKey() + { + return string.Empty; + } + protected virtual ScriptableRendererFeature[] CreateRendererFeatures() { return Array.Empty(); diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs index 9e916ffc..a5f7ffc0 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs @@ -46,6 +46,12 @@ namespace XCEngine.Rendering.Universal } } + protected override string + GetDirectionalShadowExecutionPolicyAssetKey() + { + return "BuiltinDirectionalShadow"; + } + internal UniversalMainSceneData GetMainSceneInstance() { if (mainScene == null) diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelineAsset.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelineAsset.cs index f07b3f86..1e594b82 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelineAsset.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelineAsset.cs @@ -73,6 +73,19 @@ namespace XCEngine.Rendering stage); } + protected virtual string + GetDirectionalShadowExecutionPolicyAssetKey() + { + return string.Empty; + } + + protected virtual string + GetDirectionalShadowExecutionPolicyAssetKeyContextual( + int rendererIndex) + { + return GetDirectionalShadowExecutionPolicyAssetKey(); + } + protected virtual void ReleaseRuntimeResources() { }