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:
@@ -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.
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user