feat(srp): add managed camera frame planning seam

Expose native camera frame planning controls to managed pipeline assets and renderer features.

Allow managed planning to override fullscreen stage heuristics while keeping CameraFramePlan as the native execution contract.

Add scripting coverage, probe assets, and archive the phase plan after build, test, and editor smoke validation.
This commit is contained in:
2026-04-20 01:48:16 +08:00
parent 58dde75d3d
commit 9e6c473186
17 changed files with 926 additions and 9 deletions

View File

@@ -111,6 +111,11 @@ struct ManagedCameraRenderRequestContextState {
size_t renderedRequestCount = 0u;
};
struct ManagedScriptableRenderPipelinePlanningContextState {
uint64_t handle = 0;
Rendering::CameraFramePlan* plan = nullptr;
};
uint64_t& GetManagedScriptableRenderContextNextHandle() {
static uint64_t nextHandle = 1;
return nextHandle;
@@ -274,6 +279,92 @@ void UnregisterManagedCameraRenderRequestContextState(
GetManagedCameraRenderRequestContextRegistry().erase(handle);
}
uint64_t& GetManagedScriptableRenderPipelinePlanningContextNextHandle() {
static uint64_t nextHandle = 1;
return nextHandle;
}
std::unordered_map<uint64_t, ManagedScriptableRenderPipelinePlanningContextState*>&
GetManagedScriptableRenderPipelinePlanningContextRegistry() {
static std::unordered_map<
uint64_t,
ManagedScriptableRenderPipelinePlanningContextState*> registry;
return registry;
}
ManagedScriptableRenderPipelinePlanningContextState*
FindManagedScriptableRenderPipelinePlanningContextState(
uint64_t handle) {
const auto it =
GetManagedScriptableRenderPipelinePlanningContextRegistry().find(
handle);
return it !=
GetManagedScriptableRenderPipelinePlanningContextRegistry().end()
? it->second
: nullptr;
}
uint64_t RegisterManagedScriptableRenderPipelinePlanningContextState(
ManagedScriptableRenderPipelinePlanningContextState& state) {
uint64_t handle =
GetManagedScriptableRenderPipelinePlanningContextNextHandle()++;
if (handle == 0) {
handle =
GetManagedScriptableRenderPipelinePlanningContextNextHandle()++;
}
state.handle = handle;
GetManagedScriptableRenderPipelinePlanningContextRegistry()[handle] =
&state;
return handle;
}
void UnregisterManagedScriptableRenderPipelinePlanningContextState(
uint64_t handle) {
if (handle == 0) {
return;
}
GetManagedScriptableRenderPipelinePlanningContextRegistry().erase(handle);
}
bool TryResolveManagedCameraFrameStage(
int32_t value,
Rendering::CameraFrameStage& outStage) {
switch (value) {
case static_cast<int32_t>(Rendering::CameraFrameStage::PreScenePasses):
outStage = Rendering::CameraFrameStage::PreScenePasses;
return true;
case static_cast<int32_t>(Rendering::CameraFrameStage::ShadowCaster):
outStage = Rendering::CameraFrameStage::ShadowCaster;
return true;
case static_cast<int32_t>(Rendering::CameraFrameStage::DepthOnly):
outStage = Rendering::CameraFrameStage::DepthOnly;
return true;
case static_cast<int32_t>(Rendering::CameraFrameStage::MainScene):
outStage = Rendering::CameraFrameStage::MainScene;
return true;
case static_cast<int32_t>(Rendering::CameraFrameStage::PostProcess):
outStage = Rendering::CameraFrameStage::PostProcess;
return true;
case static_cast<int32_t>(Rendering::CameraFrameStage::FinalOutput):
outStage = Rendering::CameraFrameStage::FinalOutput;
return true;
case static_cast<int32_t>(Rendering::CameraFrameStage::ObjectId):
outStage = Rendering::CameraFrameStage::ObjectId;
return true;
case static_cast<int32_t>(Rendering::CameraFrameStage::PostScenePasses):
outStage = Rendering::CameraFrameStage::PostScenePasses;
return true;
case static_cast<int32_t>(Rendering::CameraFrameStage::OverlayPasses):
outStage = Rendering::CameraFrameStage::OverlayPasses;
return true;
default:
outStage = Rendering::CameraFrameStage::MainScene;
return false;
}
}
void CleanupMonoRootDomainAtExit() {
MonoRootState& rootState = GetMonoRootState();
if (!rootState.rootDomain) {
@@ -715,6 +806,8 @@ public:
size_t renderedRequestCount,
const Rendering::DirectionalShadowPlanningSettings&
directionalShadowSettings) const override;
void ConfigureCameraFramePlan(
Rendering::CameraFramePlan& plan) const override;
bool TryGetDefaultFinalColorSettings(
Rendering::FinalColorSettings& settings) const override;
@@ -742,6 +835,8 @@ private:
MonoMethod* ResolveCreatePipelineMethod(MonoObject* assetObject) const;
MonoMethod* ResolveConfigureCameraRenderRequestMethod(
MonoObject* assetObject) const;
MonoMethod* ResolveConfigureCameraFramePlanMethod(
MonoObject* assetObject) const;
MonoMethod* ResolveGetDefaultFinalColorSettingsMethod(
MonoObject* assetObject) const;
MonoMethod* ResolveGetPipelineRendererAssetKeyMethod(
@@ -756,6 +851,7 @@ private:
mutable MonoMethod* m_disposePipelineMethod = nullptr;
mutable MonoMethod* m_createPipelineMethod = nullptr;
mutable MonoMethod* m_configureCameraRenderRequestMethod = nullptr;
mutable MonoMethod* m_configureCameraFramePlanMethod = nullptr;
mutable MonoMethod* m_getDefaultFinalColorSettingsMethod = nullptr;
mutable MonoMethod* m_getPipelineRendererAssetKeyMethod = nullptr;
mutable MonoMethod* m_releaseRuntimeResourcesMethod = nullptr;
@@ -1051,6 +1147,44 @@ void MonoManagedRenderPipelineAssetRuntime::ConfigureCameraRenderRequest(
requestContextHandle);
}
void MonoManagedRenderPipelineAssetRuntime::ConfigureCameraFramePlan(
Rendering::CameraFramePlan& plan) const {
if (!EnsureManagedAsset()) {
return;
}
MonoObject* const assetObject = GetManagedAssetObject();
MonoMethod* const method =
ResolveConfigureCameraFramePlanMethod(assetObject);
if (assetObject == nullptr || method == nullptr) {
return;
}
ManagedScriptableRenderPipelinePlanningContextState
planningContextState = {};
planningContextState.plan = &plan;
const uint64_t planningContextHandle =
RegisterManagedScriptableRenderPipelinePlanningContextState(
planningContextState);
MonoObject* const planningContextObject =
m_runtime->CreateManagedScriptableRenderPipelinePlanningContext(
planningContextHandle);
if (planningContextObject == nullptr) {
UnregisterManagedScriptableRenderPipelinePlanningContextState(
planningContextHandle);
return;
}
void* args[1] = { planningContextObject };
m_runtime->InvokeManagedMethod(
assetObject,
method,
args,
nullptr);
UnregisterManagedScriptableRenderPipelinePlanningContextState(
planningContextHandle);
}
bool MonoManagedRenderPipelineAssetRuntime::TryGetDefaultFinalColorSettings(
Rendering::FinalColorSettings& settings) const {
settings = {};
@@ -1268,6 +1402,7 @@ void MonoManagedRenderPipelineAssetRuntime::ReleaseManagedAsset() const {
m_releaseRuntimeResourcesMethod = nullptr;
m_createPipelineMethod = nullptr;
m_configureCameraRenderRequestMethod = nullptr;
m_configureCameraFramePlanMethod = nullptr;
m_getDefaultFinalColorSettingsMethod = nullptr;
m_getPipelineRendererAssetKeyMethod = nullptr;
m_pipelineRendererAsset.reset();
@@ -1345,6 +1480,20 @@ MonoManagedRenderPipelineAssetRuntime::ResolveConfigureCameraRenderRequestMethod
return m_configureCameraRenderRequestMethod;
}
MonoMethod*
MonoManagedRenderPipelineAssetRuntime::ResolveConfigureCameraFramePlanMethod(
MonoObject* assetObject) const {
if (m_configureCameraFramePlanMethod == nullptr) {
m_configureCameraFramePlanMethod =
m_runtime->ResolveManagedMethod(
assetObject,
"ConfigureCameraFramePlanInstance",
1);
}
return m_configureCameraFramePlanMethod;
}
MonoMethod*
MonoManagedRenderPipelineAssetRuntime::ResolveGetDefaultFinalColorSettingsMethod(
MonoObject* assetObject) const {
@@ -3803,6 +3952,115 @@ void InternalCall_Rendering_CameraRenderRequestContext_ClearDirectionalShadow(
state->request->directionalShadow = {};
}
mono_bool
InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_IsStageRequested(
uint64_t nativeHandle,
int32_t stage) {
const ManagedScriptableRenderPipelinePlanningContextState* const state =
FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle);
if (state == nullptr || state->plan == nullptr) {
return 0;
}
Rendering::CameraFrameStage resolvedStage =
Rendering::CameraFrameStage::MainScene;
if (!TryResolveManagedCameraFrameStage(stage, resolvedStage)) {
return 0;
}
return state->plan->HasFrameStage(resolvedStage) ? 1 : 0;
}
int32_t
InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetStageColorSource(
uint64_t nativeHandle,
int32_t stage) {
const ManagedScriptableRenderPipelinePlanningContextState* const state =
FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle);
if (state == nullptr || state->plan == nullptr) {
return static_cast<int32_t>(
Rendering::CameraFrameColorSource::ExplicitSurface);
}
Rendering::CameraFrameStage resolvedStage =
Rendering::CameraFrameStage::MainScene;
if (!TryResolveManagedCameraFrameStage(stage, resolvedStage)) {
return static_cast<int32_t>(
Rendering::CameraFrameColorSource::ExplicitSurface);
}
return static_cast<int32_t>(
state->plan->ResolveStageColorSource(resolvedStage));
}
mono_bool
InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetStageUsesGraphManagedOutputColor(
uint64_t nativeHandle,
int32_t stage) {
const ManagedScriptableRenderPipelinePlanningContextState* const state =
FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle);
if (state == nullptr || state->plan == nullptr) {
return 0;
}
Rendering::CameraFrameStage resolvedStage =
Rendering::CameraFrameStage::MainScene;
if (!TryResolveManagedCameraFrameStage(stage, resolvedStage)) {
return 0;
}
return state->plan->UsesGraphManagedOutputColor(resolvedStage)
? 1
: 0;
}
mono_bool
InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestFullscreenStage(
uint64_t nativeHandle,
int32_t stage,
int32_t source,
mono_bool usesGraphManagedOutputColor) {
ManagedScriptableRenderPipelinePlanningContextState* const state =
FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle);
if (state == nullptr || state->plan == nullptr) {
return 0;
}
Rendering::CameraFrameStage resolvedStage =
Rendering::CameraFrameStage::MainScene;
if (!TryResolveManagedCameraFrameStage(stage, resolvedStage)) {
return 0;
}
const Rendering::CameraFrameColorSource resolvedSource =
static_cast<Rendering::CameraFrameColorSource>(source);
return state->plan->RequestFullscreenStage(
resolvedStage,
resolvedSource,
usesGraphManagedOutputColor != 0,
true)
? 1
: 0;
}
void InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage(
uint64_t nativeHandle,
int32_t stage) {
ManagedScriptableRenderPipelinePlanningContextState* const state =
FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle);
if (state == nullptr || state->plan == nullptr) {
return;
}
Rendering::CameraFrameStage resolvedStage =
Rendering::CameraFrameStage::MainScene;
if (!TryResolveManagedCameraFrameStage(stage, resolvedStage)) {
return;
}
state->plan->ClearFullscreenStage(resolvedStage, true);
}
void RegisterInternalCalls() {
if (GetInternalCallRegistrationState()) {
return;
@@ -3986,6 +4244,11 @@ void RegisterInternalCalls() {
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_RecordScenePhase", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderContext_RecordScenePhase));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_RecordSceneInjectionPoint", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderContext_RecordSceneInjectionPoint));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_RecordFullscreenPass", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderContext_RecordFullscreenPass));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_IsStageRequested", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_IsStageRequested));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetStageColorSource", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetStageColorSource));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetStageUsesGraphManagedOutputColor", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetStageUsesGraphManagedOutputColor));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_RequestFullscreenStage", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestFullscreenStage));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetRenderedBaseCameraCount", reinterpret_cast<const void*>(&InternalCall_Rendering_CameraRenderRequestContext_GetRenderedBaseCameraCount));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetRenderedRequestCount", reinterpret_cast<const void*>(&InternalCall_Rendering_CameraRenderRequestContext_GetRenderedRequestCount));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetHasDirectionalShadow", reinterpret_cast<const void*>(&InternalCall_Rendering_CameraRenderRequestContext_GetHasDirectionalShadow));
@@ -4049,6 +4312,8 @@ void MonoScriptRuntime::Shutdown() {
GetManagedScriptableRenderContextNextHandle() = 1;
GetManagedCameraRenderRequestContextRegistry().clear();
GetManagedCameraRenderRequestContextNextHandle() = 1;
GetManagedScriptableRenderPipelinePlanningContextRegistry().clear();
GetManagedScriptableRenderPipelinePlanningContextNextHandle() = 1;
ClearManagedInstances();
ClearExternalManagedObjects();
ClearClassCache();
@@ -4066,10 +4331,12 @@ void MonoScriptRuntime::Shutdown() {
m_scriptableRenderPipelineClass = nullptr;
m_scriptableRenderContextClass = nullptr;
m_cameraRenderRequestContextClass = nullptr;
m_scriptableRenderPipelinePlanningContextClass = nullptr;
m_serializeFieldAttributeClass = nullptr;
m_gameObjectConstructor = nullptr;
m_scriptableRenderContextConstructor = nullptr;
m_cameraRenderRequestContextConstructor = nullptr;
m_scriptableRenderPipelinePlanningContextConstructor = nullptr;
m_managedGameObjectUUIDField = nullptr;
m_gameObjectUUIDField = nullptr;
m_scriptComponentUUIDField = nullptr;
@@ -4863,6 +5130,27 @@ bool MonoScriptRuntime::DiscoverScriptClasses() {
return false;
}
m_scriptableRenderPipelinePlanningContextClass = mono_class_from_name(
m_coreImage,
kManagedRenderingNamespace,
"ScriptableRenderPipelinePlanningContext");
if (!m_scriptableRenderPipelinePlanningContextClass) {
SetError(
"Failed to locate the managed ScriptableRenderPipelinePlanningContext type.");
return false;
}
m_scriptableRenderPipelinePlanningContextConstructor =
mono_class_get_method_from_name(
m_scriptableRenderPipelinePlanningContextClass,
".ctor",
1);
if (!m_scriptableRenderPipelinePlanningContextConstructor) {
SetError(
"Failed to locate the managed ScriptableRenderPipelinePlanningContext constructor.");
return false;
}
m_serializeFieldAttributeClass = mono_class_from_name(
m_coreImage,
m_settings.baseNamespace.c_str(),
@@ -5458,6 +5746,46 @@ MonoScriptRuntime::CreateManagedCameraRenderRequestContext(
return contextObject;
}
MonoObject*
MonoScriptRuntime::CreateManagedScriptableRenderPipelinePlanningContext(
uint64_t nativeHandle) {
if (!m_initialized ||
nativeHandle == 0 ||
m_scriptableRenderPipelinePlanningContextClass == nullptr ||
m_scriptableRenderPipelinePlanningContextConstructor == nullptr) {
return nullptr;
}
SetCurrentDomain();
MonoObject* const contextObject =
mono_object_new(
m_appDomain,
m_scriptableRenderPipelinePlanningContextClass);
if (contextObject == nullptr) {
SetError(
"Mono failed to allocate a managed ScriptableRenderPipelinePlanningContext.");
return nullptr;
}
void* args[1];
uint64_t nativeHandleArgument = nativeHandle;
args[0] = &nativeHandleArgument;
MonoObject* exception = nullptr;
mono_runtime_invoke(
m_scriptableRenderPipelinePlanningContextConstructor,
contextObject,
args,
&exception);
if (exception != nullptr) {
RecordException(exception);
return nullptr;
}
return contextObject;
}
MonoScriptRuntime::InstanceData* MonoScriptRuntime::FindInstance(const ScriptRuntimeContext& context) {
const auto it = m_instances.find(InstanceKey{context.gameObjectUUID, context.scriptComponentUUID});
return it != m_instances.end() ? &it->second : nullptr;