refactor(srp): bridge universal shadow planning settings into managed asset

- expose directional shadow planning settings on camera request context\n- let the managed universal asset override planner defaults\n- recompute native directional shadow requests only when settings change
This commit is contained in:
2026-04-21 02:19:30 +08:00
parent 5747968fc4
commit bfc4b90ce6
9 changed files with 380 additions and 5 deletions

View File

@@ -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.

View File

@@ -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<int32_t>::lowest();
int32_t renderQueueMax = std::numeric_limits<int32_t>::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<const ManagedDirectionalShadowPlanningSettingsData*>(
&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<const Rendering::DirectionalShadowPlanningSettings*>(
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<const void*>(&InternalCall_Rendering_CameraRenderRequestContext_GetRendererIndex));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_SetRendererIndex", reinterpret_cast<const void*>(&InternalCall_Rendering_CameraRenderRequestContext_SetRendererIndex));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetHasDirectionalShadow", reinterpret_cast<const void*>(&InternalCall_Rendering_CameraRenderRequestContext_GetHasDirectionalShadow));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetDirectionalShadowPlanningSettings", reinterpret_cast<const void*>(&InternalCall_Rendering_CameraRenderRequestContext_GetDirectionalShadowPlanningSettings));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_SetDirectionalShadowPlanningSettings", reinterpret_cast<const void*>(&InternalCall_Rendering_CameraRenderRequestContext_SetDirectionalShadowPlanningSettings));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_ClearDirectionalShadow", reinterpret_cast<const void*>(&InternalCall_Rendering_CameraRenderRequestContext_ClearDirectionalShadow));
GetInternalCallRegistrationState() = true;

View File

@@ -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

View File

@@ -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)

View File

@@ -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;
}
}

View File

@@ -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()
{

View File

@@ -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(

View File

@@ -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

View File

@@ -0,0 +1,166 @@
using System;
using System.Runtime.InteropServices;
namespace XCEngine.Rendering
{
[StructLayout(LayoutKind.Sequential)]
public struct DirectionalShadowSamplingSettings
: IEquatable<DirectionalShadowSamplingSettings>
{
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<DirectionalShadowCasterBiasSettings>
{
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<DirectionalShadowPlanningSettings>
{
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;
}
}
}
}