diff --git a/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md b/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md index 4c8d614c..16dfecd1 100644 --- a/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md +++ b/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md @@ -98,6 +98,8 @@ ### 4.4 稳住 renderer authoring / invalidation / bridge refresh +状态: `Completed` (`2026-04-26`) + 目标: 把当前已经存在但还容易漂移的 authoring 路径变成可回归验证的稳定面。 执行: diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 38a6c66d..fd487582 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -2747,6 +2747,20 @@ private: namespace { +std::string ResolveManagedMonoClassAssemblyName(MonoClass* monoClass) { + if (monoClass == nullptr) { + return {}; + } + + MonoImage* const image = + mono_class_get_image(monoClass); + return TrimAssemblyName( + SafeString( + image != nullptr + ? mono_image_get_name(image) + : nullptr)); +} + ManagedComponentTypeInfo ResolveManagedComponentTypeInfo(MonoClass* monoClass) { ManagedComponentTypeInfo typeInfo; if (!monoClass) { @@ -2783,13 +2797,17 @@ ManagedComponentTypeInfo ResolveManagedComponentTypeInfo(MonoClass* monoClass) { } MonoScriptRuntime* runtime = GetActiveMonoScriptRuntime(); + const std::string assemblyName = + ResolveManagedMonoClassAssemblyName( + monoClass); if (runtime + && !assemblyName.empty() && runtime->IsClassAvailable( - runtime->GetSettings().appAssemblyName, + assemblyName, typeInfo.namespaceName, typeInfo.className)) { typeInfo.kind = ManagedComponentKind::Script; - typeInfo.assemblyName = runtime->GetSettings().appAssemblyName; + typeInfo.assemblyName = assemblyName; } return typeInfo; @@ -7596,6 +7614,12 @@ bool MonoScriptRuntime::DiscoverScriptClasses() { } DiscoverScriptClassesInImage(m_settings.appAssemblyName, m_appImage); + for (const ManagedAssemblyDescriptor& assembly : + m_settings.engineAssemblies) { + DiscoverScriptClassesInImage( + assembly.name, + FindLoadedAssemblyImage(assembly.name)); + } DiscoverRenderPipelineAssetClassesInImage( m_settings.coreAssemblyName, m_coreImage); @@ -8748,11 +8772,14 @@ bool MonoScriptRuntime::TrySetFieldValue( return false; } + const std::string componentAssemblyName = + ResolveManagedMonoClassAssemblyName( + fieldMetadata.componentClass); ScriptComponent* component = FindScriptComponentByUUID(reference.scriptComponentUUID); if (!component || !component->GetGameObject() || component->GetGameObject()->GetUUID() != reference.gameObjectUUID - || component->GetAssemblyName() != m_settings.appAssemblyName + || component->GetAssemblyName() != componentAssemblyName || component->GetNamespaceName() != SafeString(mono_class_get_namespace(fieldMetadata.componentClass)) || component->GetClassName() != SafeString(mono_class_get_name(fieldMetadata.componentClass))) { return false; diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index 7ab0a7ea..5d3b1eb4 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -1852,6 +1852,42 @@ namespace Gameplay } } + public sealed class ManagedCameraOverrideRendererSelectionProbeAsset + : UniversalRenderPipelineAsset + { + public ManagedCameraOverrideRendererSelectionProbeAsset() + { + rendererDataList = + ProbeScriptableObjectFactory + .CreateRendererDataList( + ProbeScriptableObjectFactory + .Create(), + ProbeScriptableObjectFactory + .Create()); + defaultRendererIndex = 0; + } + + protected override ScriptableRenderPipeline + CreateRendererBackedPipeline() + { + return new ManagedCameraOverrideRendererSelectionProbePipeline(); + } + + protected override void ConfigureCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + if (context == null || + context.rendererIndex != 1) + { + return; + } + + context.RequestFullscreenStage( + CameraFrameStage.PostProcess, + CameraFrameColorSource.MainSceneColor); + } + } + public sealed class ManagedRendererReuseProbeAsset : UniversalRenderPipelineAsset { @@ -2242,6 +2278,34 @@ namespace Gameplay } } + internal sealed class ManagedCameraOverrideRendererSelectionProbePipeline + : ScriptableRenderPipeline + { + protected override bool SupportsStageRenderGraphContextual( + CameraFrameStage stage, + int rendererIndex) + { + return stage == CameraFrameStage.PostProcess && + rendererIndex == 1; + } + + protected override bool RecordStageRenderGraph( + ScriptableRenderContext context) + { + return context != null && + context.stage == CameraFrameStage.PostProcess && + context.rendererIndex == 1 && + context + .AddRasterPass( + "ManagedCameraOverridePostProcess") + .SetColorAttachment( + context.primaryColorTarget) + .SetColorScaleFullscreenExecution( + new Vector4(1.05f, 1.0f, 0.95f, 1.0f)) + .Commit(); + } + } + internal sealed class ManagedRenderPipelineProbe : ProbeSceneRenderer { diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index 045d5019..7cb2ed21 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -3168,6 +3168,186 @@ TEST_F( host->GetStageRecorder()->Shutdown(); } +TEST_F( + MonoScriptRuntimeTest, + ManagedRenderPipelineBridgeUsesCameraRendererOverrideAcrossPlanningAndExecution) { + const auto bridge = + XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge(); + ASSERT_NE(bridge, nullptr); + + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { + "GameScripts", + "Gameplay", + "ManagedCameraOverrideRendererSelectionProbeAsset" + }; + + std::shared_ptr + assetRuntime = bridge->CreateAssetRuntime(descriptor); + ASSERT_NE(assetRuntime, nullptr); + EXPECT_EQ( + assetRuntime->GetPipelineRendererAssetPolicy(), + XCEngine::Rendering::Pipelines::ManagedPipelineRendererAssetPolicy:: + DefaultNativeBackend); + + Scene* runtimeScene = + CreateScene("ManagedCameraOverrideRendererSelectionScene"); + GameObject* cameraObject = runtimeScene->CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + ASSERT_NE(camera, nullptr); + camera->SetPrimary(true); + + ScriptComponent* additionalCameraData = + cameraObject->AddComponent(); + ASSERT_NE(additionalCameraData, nullptr); + additionalCameraData->SetScriptClass( + "XCEngine.RenderPipelines.Universal", + "XCEngine.Rendering.Universal", + "UniversalAdditionalCameraData"); + additionalCameraData->GetFieldStorage().SetFieldValue( + "rendererIndex", + int32_t(1)); + + engine->OnRuntimeStart(runtimeScene); + engine->OnUpdate(0.016f); + + TestRenderDevice device; + TestRenderCommandList commandList; + TestRenderCommandQueue commandQueue; + TestRenderResourceView colorView( + XCEngine::RHI::ResourceViewType::RenderTarget, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::R8G8B8A8_UNorm); + TestRenderResourceView depthView( + XCEngine::RHI::ResourceViewType::DepthStencil, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::D32_Float); + + const XCEngine::Rendering::RenderContext context = + CreateRenderContext( + device, + commandList, + commandQueue); + XCEngine::Rendering::RenderSurface surface(64u, 64u); + surface.SetColorAttachment(&colorView); + surface.SetDepthAttachment(&depthView); + + XCEngine::Rendering::Pipelines::ManagedScriptableRenderPipelineAsset + asset(descriptor); + + XCEngine::Rendering::CameraRenderRequest request = {}; + request.scene = runtimeScene; + request.camera = camera; + request.context = context; + request.surface = surface; + asset.ConfigureCameraRenderRequest( + request, + 0u, + 0u, + XCEngine::Rendering::DirectionalShadowPlanningSettings{}); + EXPECT_EQ(request.rendererIndex, 1); + + XCEngine::Rendering::CameraFramePlan plan = + XCEngine::Rendering::CameraFramePlan::FromRequest(request); + asset.ConfigureCameraFramePlan(plan); + EXPECT_EQ(plan.request.rendererIndex, 1); + EXPECT_TRUE( + plan.IsFullscreenStageRequested( + XCEngine::Rendering::CameraFrameStage::PostProcess)); + EXPECT_EQ( + plan.ResolveStageColorSource( + XCEngine::Rendering::CameraFrameStage::PostProcess), + XCEngine::Rendering::CameraFrameColorSource::MainSceneColor); + EXPECT_TRUE(plan.IsPostProcessStageValid()); + + std::unique_ptr pipeline = + asset.CreatePipeline(); + ASSERT_NE(pipeline, nullptr); + auto* host = + dynamic_cast( + pipeline.get()); + ASSERT_NE(host, nullptr); + ASSERT_NE(host->GetStageRecorder(), nullptr); + EXPECT_TRUE(host->GetStageRecorder()->Initialize(context)) + << runtime->GetLastError(); + EXPECT_FALSE( + host->GetStageRecorder()->SupportsStageRenderGraph( + XCEngine::Rendering::RenderPipelineStageSupportContext{ + XCEngine::Rendering::CameraFrameStage::PostProcess, + 0 })); + EXPECT_TRUE( + host->GetStageRecorder()->SupportsStageRenderGraph( + XCEngine::Rendering::RenderPipelineStageSupportContext{ + XCEngine::Rendering::CameraFrameStage::PostProcess, + plan.request.rendererIndex })); + + XCEngine::Rendering::RenderGraph graph; + XCEngine::Rendering::RenderGraphBuilder graphBuilder(graph); + XCEngine::Rendering::RenderGraphTextureDesc postProcessColorDesc = {}; + postProcessColorDesc.width = 64u; + postProcessColorDesc.height = 64u; + postProcessColorDesc.format = + static_cast( + XCEngine::RHI::Format::R8G8B8A8_UNorm); + const XCEngine::Rendering::RenderGraphTextureHandle sourceColor = + graphBuilder.ImportTexture( + "ManagedCameraOverrideSource", + postProcessColorDesc, + &colorView, + {}); + const XCEngine::Rendering::RenderGraphTextureHandle outputColor = + graphBuilder.CreateTransientTexture( + "ManagedCameraOverrideOutput", + postProcessColorDesc); + + const XCEngine::Rendering::RenderSceneData sceneData = {}; + bool executionSucceeded = true; + XCEngine::Rendering::RenderGraphBlackboard blackboard = {}; + XCEngine::Rendering::EmplaceCameraFrameRenderGraphFrameData(blackboard) + .resources.mainScene.color = sourceColor; + XCEngine::Rendering::RenderPipelineStageRenderGraphContext graphContext = { + graphBuilder, + "ManagedCameraOverridePostProcess", + XCEngine::Rendering::CameraFrameStage::PostProcess, + context, + sceneData, + surface, + nullptr, + nullptr, + XCEngine::RHI::ResourceStates::Common, + sourceColor, + { outputColor }, + {}, + {}, + &executionSucceeded, + &blackboard + }; + graphContext.stageColorSource = + plan.ResolveStageColorSource( + XCEngine::Rendering::CameraFrameStage::PostProcess); + graphContext.usesGraphManagedOutputColor = + plan.UsesGraphManagedOutputColor( + XCEngine::Rendering::CameraFrameStage::PostProcess); + graphContext.rendererIndex = plan.request.rendererIndex; + + EXPECT_TRUE(host->GetStageRecorder()->RecordStageRenderGraph(graphContext)) + << runtime->GetLastError(); + + XCEngine::Rendering::CompiledRenderGraph compiledGraph = {}; + XCEngine::Containers::String errorMessage; + ASSERT_TRUE( + XCEngine::Rendering::RenderGraphCompiler::Compile( + graph, + compiledGraph, + &errorMessage)) + << errorMessage.CStr(); + ASSERT_EQ(compiledGraph.GetPassCount(), 1u); + EXPECT_STREQ( + compiledGraph.GetPassName(0).CStr(), + "ManagedCameraOverridePostProcess"); + + host->GetStageRecorder()->Shutdown(); +} + TEST_F( MonoScriptRuntimeTest, ManagedRenderPipelineBridgeFallsBackToDefaultSceneRecorderWhenBackendKeyIsUnknown) {