diff --git a/docs/used/SRP_UniversalShadowPlanningSettingsBridgePlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalShadowPlanningSettingsBridgePlan_完成归档_2026-04-21.md new file mode 100644 index 00000000..4469bc58 --- /dev/null +++ b/docs/used/SRP_UniversalShadowPlanningSettingsBridgePlan_完成归档_2026-04-21.md @@ -0,0 +1,42 @@ +# SRP Universal Shadow Planning Settings Bridge Plan 2026-04-21 + +## Goal + +Extend the current managed shadow ownership from a simple enable/disable switch to a real planning-settings bridge: + +1. managed can read the current directional shadow planning settings; +2. managed can override those settings; +3. native recomputes the shadow request after managed overrides, without changing the existing call order. + +## Why This Stage + +The previous stage moved the first shadow ownership decision into `UniversalRenderPipelineAsset`, but it still only controlled whether shadows stay enabled. + +The remaining gap is that native already receives `DirectionalShadowPlanningSettings`, while the managed runtime currently discards them. Until that bridge exists, URP cannot meaningfully own shadow planning defaults. + +## Scope + +Included: + +1. Add managed mirror structs for directional shadow planning settings. +2. Expose those settings on `CameraRenderRequestContext` and `RendererCameraRequestContext`. +3. Extend the Mono bridge state to carry a mutable settings copy and a dirty flag. +4. Re-run native default shadow planning after managed request configuration when settings changed. +5. Let `UniversalShadowSettings` push its planning defaults into the request context. +6. Rebuild `XCEditor` and run old editor smoke. + +Not included: + +1. Shadow cascades. +2. Shadow atlas / multiple lights. +3. C# shadow map raster pass implementation. +4. Editor UI for shadow settings. + +## Acceptance + +This stage is complete when: + +1. managed URP can override planner-owned main directional shadow defaults; +2. existing `ClearDirectionalShadow()` behavior still works; +3. the native shadow request is recomputed only when managed changed planning settings; +4. `XCEditor` build and old editor smoke both pass. diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index d72b7341..c0a57c53 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -19,9 +19,11 @@ #include "Rendering/Internal/RenderPipelineFactory.h" #include "Rendering/Passes/BuiltinVectorFullscreenPass.h" #include "Rendering/Planning/FullscreenPassDesc.h" +#include "Rendering/Planning/SceneRenderRequestPlanner.h" #include "Rendering/Pipelines/NativeSceneRecorder.h" #include "Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h" #include "Rendering/RenderPassGraphContract.h" +#include "Rendering/RenderPipelineAsset.h" #include "Rendering/RenderPipelineStageGraphContract.h" #include "Resources/BuiltinResources.h" #include "Core/Asset/ResourceManager.h" @@ -125,6 +127,10 @@ struct ManagedCameraRenderRequestContextState { Rendering::CameraRenderRequest* request = nullptr; size_t renderedBaseCameraCount = 0u; size_t renderedRequestCount = 0u; + Rendering::DirectionalShadowPlanningSettings + directionalShadowPlanningSettings = {}; + bool directionalShadowPlanningSettingsDirty = false; + bool suppressDirectionalShadow = false; }; struct ManagedScriptableRenderPipelinePlanningContextState { @@ -930,6 +936,45 @@ static_assert( sizeof(Rendering::RenderGraphTextureDesc), "Managed render graph texture desc bridge layout must match native RenderGraphTextureDesc."); +struct ManagedDirectionalShadowSamplingSettingsData { + float receiverDepthBias = 0.0010f; + float normalBiasScale = 2.0f; + float shadowStrength = 0.85f; +}; + +static_assert( + sizeof(ManagedDirectionalShadowSamplingSettingsData) == + sizeof(Rendering::DirectionalShadowSamplingSettings), + "Managed directional shadow sampling bridge layout must match native DirectionalShadowSamplingSettings."); + +struct ManagedDirectionalShadowCasterBiasSettingsData { + float depthBiasFactor = 2.5f; + int32_t depthBiasUnits = 4; +}; + +static_assert( + sizeof(ManagedDirectionalShadowCasterBiasSettingsData) == + sizeof(Rendering::DirectionalShadowCasterBiasSettings), + "Managed directional shadow caster bias bridge layout must match native DirectionalShadowCasterBiasSettings."); + +struct ManagedDirectionalShadowPlanningSettingsData { + uint32_t mapDimension = 2048u; + float minFocusDistance = 5.0f; + float maxFocusDistance = 32.0f; + float perspectiveFocusFactor = 1.0f; + float orthographicFocusFactor = 2.0f; + float minDepthRange = 20.0f; + float boundsPadding = 0.5f; + float minDepthPadding = 2.0f; + ManagedDirectionalShadowSamplingSettingsData sampling = {}; + ManagedDirectionalShadowCasterBiasSettingsData casterBias = {}; +}; + +static_assert( + sizeof(ManagedDirectionalShadowPlanningSettingsData) == + sizeof(Rendering::DirectionalShadowPlanningSettings), + "Managed directional shadow planning bridge layout must match native DirectionalShadowPlanningSettings."); + struct ManagedFilteringSettingsData { int32_t renderQueueMin = std::numeric_limits::lowest(); int32_t renderQueueMax = std::numeric_limits::max(); @@ -1735,7 +1780,8 @@ void MonoManagedRenderPipelineAssetRuntime::ConfigureCameraRenderRequest( Rendering::CameraRenderRequest& request, size_t renderedBaseCameraCount, size_t renderedRequestCount, - const Rendering::DirectionalShadowPlanningSettings&) const { + const Rendering::DirectionalShadowPlanningSettings& + directionalShadowSettings) const { if (!EnsureManagedAsset()) { return; } @@ -1752,6 +1798,8 @@ void MonoManagedRenderPipelineAssetRuntime::ConfigureCameraRenderRequest( requestContextState.request = &request; requestContextState.renderedBaseCameraCount = renderedBaseCameraCount; requestContextState.renderedRequestCount = renderedRequestCount; + requestContextState.directionalShadowPlanningSettings = + directionalShadowSettings; const uint64_t requestContextHandle = RegisterManagedCameraRenderRequestContextState( requestContextState); @@ -1770,6 +1818,16 @@ void MonoManagedRenderPipelineAssetRuntime::ConfigureCameraRenderRequest( method, args, nullptr); + if (requestContextState.suppressDirectionalShadow) { + request.directionalShadow = {}; + } else if (requestContextState.directionalShadowPlanningSettingsDirty) { + request.directionalShadow = {}; + Rendering::ApplyDefaultRenderPipelineAssetCameraRenderRequestPolicy( + request, + renderedBaseCameraCount, + renderedRequestCount, + requestContextState.directionalShadowPlanningSettings); + } UnregisterManagedCameraRenderRequestContextState( requestContextHandle); } @@ -5090,6 +5148,42 @@ InternalCall_Rendering_CameraRenderRequestContext_GetHasDirectionalShadow( : 0; } +void +InternalCall_Rendering_CameraRenderRequestContext_GetDirectionalShadowPlanningSettings( + uint64_t nativeHandle, + ManagedDirectionalShadowPlanningSettingsData* outSettings) { + const ManagedCameraRenderRequestContextState* const state = + FindManagedCameraRenderRequestContextState(nativeHandle); + if (outSettings == nullptr) { + return; + } + + const Rendering::DirectionalShadowPlanningSettings sourceSettings = + state != nullptr + ? state->directionalShadowPlanningSettings + : Rendering::DirectionalShadowPlanningSettings(); + *outSettings = + *reinterpret_cast( + &sourceSettings); +} + +void +InternalCall_Rendering_CameraRenderRequestContext_SetDirectionalShadowPlanningSettings( + uint64_t nativeHandle, + ManagedDirectionalShadowPlanningSettingsData* settings) { + ManagedCameraRenderRequestContextState* const state = + FindManagedCameraRenderRequestContextState(nativeHandle); + if (state == nullptr || + settings == nullptr) { + return; + } + + state->directionalShadowPlanningSettings = + *reinterpret_cast( + settings); + state->directionalShadowPlanningSettingsDirty = true; +} + void InternalCall_Rendering_CameraRenderRequestContext_ClearDirectionalShadow( uint64_t nativeHandle) { ManagedCameraRenderRequestContextState* const state = @@ -5099,6 +5193,7 @@ void InternalCall_Rendering_CameraRenderRequestContext_ClearDirectionalShadow( } state->request->directionalShadow = {}; + state->suppressDirectionalShadow = true; } int32_t @@ -5430,6 +5525,8 @@ void RegisterInternalCalls() { mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetRendererIndex", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_GetRendererIndex)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_SetRendererIndex", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_SetRendererIndex)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetHasDirectionalShadow", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_GetHasDirectionalShadow)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetDirectionalShadowPlanningSettings", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_GetDirectionalShadowPlanningSettings)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_SetDirectionalShadowPlanningSettings", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_SetDirectionalShadowPlanningSettings)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_ClearDirectionalShadow", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_ClearDirectionalShadow)); GetInternalCallRegistrationState() = true; diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index c99e44c9..1a0b2b26 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -175,6 +175,7 @@ set(XCENGINE_SCRIPT_CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/CameraFrameStage.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/CompareFunction.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/DepthState.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/DirectionalShadowPlanningSettings.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/DrawingSettings.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/FinalColorExposureMode.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/FinalColorOutputTransferMode.cs diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererCameraRequestContext.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererCameraRequestContext.cs index e7ed07f6..fd52873d 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererCameraRequestContext.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererCameraRequestContext.cs @@ -47,6 +47,26 @@ namespace XCEngine.Rendering.Universal m_requestContext != null && m_requestContext.hasDirectionalShadow; + public DirectionalShadowPlanningSettings + directionalShadowPlanningSettings + { + get => + m_requestContext != null + ? m_requestContext + .directionalShadowPlanningSettings + : DirectionalShadowPlanningSettings + .CreateDefault(); + set + { + if (m_requestContext != null) + { + m_requestContext + .directionalShadowPlanningSettings = + value; + } + } + } + public void ClearDirectionalShadow() { if (m_requestContext != null) diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs index 23a277a4..17b4bd3c 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs @@ -28,11 +28,24 @@ namespace XCEngine.Rendering.Universal return; } - if (!GetShadowSettingsInstance() - .supportsMainLightShadows && - context.hasDirectionalShadow) + UniversalShadowSettings shadowSettings = + GetShadowSettingsInstance(); + if (!shadowSettings.supportsMainLightShadows) { - context.ClearDirectionalShadow(); + if (context.hasDirectionalShadow) + { + context.ClearDirectionalShadow(); + } + return; + } + + DirectionalShadowPlanningSettings desiredPlanningSettings = + shadowSettings.mainLightPlanningSettings; + if (!context.directionalShadowPlanningSettings + .Equals(desiredPlanningSettings)) + { + context.directionalShadowPlanningSettings = + desiredPlanningSettings; } } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalShadowSettings.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalShadowSettings.cs index ecc0b688..5277af48 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalShadowSettings.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalShadowSettings.cs @@ -3,6 +3,10 @@ namespace XCEngine.Rendering.Universal public sealed class UniversalShadowSettings { public bool supportsMainLightShadows = true; + public DirectionalShadowPlanningSettings + mainLightPlanningSettings = + DirectionalShadowPlanningSettings + .CreateDefault(); public static UniversalShadowSettings CreateDefault() { diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index e9c87bf6..a1ec2f5a 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -839,6 +839,18 @@ namespace XCEngine Rendering_CameraRenderRequestContext_GetHasDirectionalShadow( ulong nativeHandle); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void + Rendering_CameraRenderRequestContext_GetDirectionalShadowPlanningSettings( + ulong nativeHandle, + out Rendering.DirectionalShadowPlanningSettings settings); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void + Rendering_CameraRenderRequestContext_SetDirectionalShadowPlanningSettings( + ulong nativeHandle, + ref Rendering.DirectionalShadowPlanningSettings settings); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void Rendering_CameraRenderRequestContext_ClearDirectionalShadow( diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/CameraRenderRequestContext.cs b/managed/XCEngine.ScriptCore/Rendering/Core/CameraRenderRequestContext.cs index 7fff03ca..6a9b7888 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/CameraRenderRequestContext.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/CameraRenderRequestContext.cs @@ -58,6 +58,26 @@ namespace XCEngine.Rendering .Rendering_CameraRenderRequestContext_GetHasDirectionalShadow( m_nativeHandle); + public DirectionalShadowPlanningSettings + directionalShadowPlanningSettings + { + get + { + InternalCalls + .Rendering_CameraRenderRequestContext_GetDirectionalShadowPlanningSettings( + m_nativeHandle, + out DirectionalShadowPlanningSettings settings); + return settings; + } + set + { + InternalCalls + .Rendering_CameraRenderRequestContext_SetDirectionalShadowPlanningSettings( + m_nativeHandle, + ref value); + } + } + public void ClearDirectionalShadow() { InternalCalls diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/DirectionalShadowPlanningSettings.cs b/managed/XCEngine.ScriptCore/Rendering/Core/DirectionalShadowPlanningSettings.cs new file mode 100644 index 00000000..1ddfe769 --- /dev/null +++ b/managed/XCEngine.ScriptCore/Rendering/Core/DirectionalShadowPlanningSettings.cs @@ -0,0 +1,166 @@ +using System; +using System.Runtime.InteropServices; + +namespace XCEngine.Rendering +{ + [StructLayout(LayoutKind.Sequential)] + public struct DirectionalShadowSamplingSettings + : IEquatable + { + public float receiverDepthBias; + public float normalBiasScale; + public float shadowStrength; + + public static DirectionalShadowSamplingSettings CreateDefault() + { + return new DirectionalShadowSamplingSettings + { + receiverDepthBias = 0.0010f, + normalBiasScale = 2.0f, + shadowStrength = 0.85f + }; + } + + public bool Equals( + DirectionalShadowSamplingSettings other) + { + return receiverDepthBias == other.receiverDepthBias && + normalBiasScale == other.normalBiasScale && + shadowStrength == other.shadowStrength; + } + + public override bool Equals(object obj) + { + return obj is DirectionalShadowSamplingSettings other && + Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + int hash = receiverDepthBias.GetHashCode(); + hash = (hash * 397) ^ normalBiasScale.GetHashCode(); + hash = (hash * 397) ^ shadowStrength.GetHashCode(); + return hash; + } + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct DirectionalShadowCasterBiasSettings + : IEquatable + { + public float depthBiasFactor; + public int depthBiasUnits; + + public static DirectionalShadowCasterBiasSettings CreateDefault() + { + return new DirectionalShadowCasterBiasSettings + { + depthBiasFactor = 2.5f, + depthBiasUnits = 4 + }; + } + + public bool Equals( + DirectionalShadowCasterBiasSettings other) + { + return depthBiasFactor == other.depthBiasFactor && + depthBiasUnits == other.depthBiasUnits; + } + + public override bool Equals(object obj) + { + return obj is DirectionalShadowCasterBiasSettings other && + Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return (depthBiasFactor.GetHashCode() * 397) ^ + depthBiasUnits; + } + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct DirectionalShadowPlanningSettings + : IEquatable + { + public uint mapDimension; + public float minFocusDistance; + public float maxFocusDistance; + public float perspectiveFocusFactor; + public float orthographicFocusFactor; + public float minDepthRange; + public float boundsPadding; + public float minDepthPadding; + public DirectionalShadowSamplingSettings sampling; + public DirectionalShadowCasterBiasSettings casterBias; + + public static DirectionalShadowPlanningSettings CreateDefault() + { + return new DirectionalShadowPlanningSettings + { + mapDimension = 2048u, + minFocusDistance = 5.0f, + maxFocusDistance = 32.0f, + perspectiveFocusFactor = 1.0f, + orthographicFocusFactor = 2.0f, + minDepthRange = 20.0f, + boundsPadding = 0.5f, + minDepthPadding = 2.0f, + sampling = + DirectionalShadowSamplingSettings.CreateDefault(), + casterBias = + DirectionalShadowCasterBiasSettings.CreateDefault() + }; + } + + public bool Equals( + DirectionalShadowPlanningSettings other) + { + return mapDimension == other.mapDimension && + minFocusDistance == other.minFocusDistance && + maxFocusDistance == other.maxFocusDistance && + perspectiveFocusFactor == + other.perspectiveFocusFactor && + orthographicFocusFactor == + other.orthographicFocusFactor && + minDepthRange == other.minDepthRange && + boundsPadding == other.boundsPadding && + minDepthPadding == other.minDepthPadding && + sampling.Equals(other.sampling) && + casterBias.Equals(other.casterBias); + } + + public override bool Equals(object obj) + { + return obj is DirectionalShadowPlanningSettings other && + Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + int hash = (int)mapDimension; + hash = (hash * 397) ^ minFocusDistance.GetHashCode(); + hash = (hash * 397) ^ maxFocusDistance.GetHashCode(); + hash = (hash * 397) ^ + perspectiveFocusFactor.GetHashCode(); + hash = (hash * 397) ^ + orthographicFocusFactor.GetHashCode(); + hash = (hash * 397) ^ minDepthRange.GetHashCode(); + hash = (hash * 397) ^ boundsPadding.GetHashCode(); + hash = (hash * 397) ^ minDepthPadding.GetHashCode(); + hash = (hash * 397) ^ sampling.GetHashCode(); + hash = (hash * 397) ^ casterBias.GetHashCode(); + return hash; + } + } + } +}