rendering: close renderer override bridge phase 4.4

This commit is contained in:
2026-04-26 02:44:21 +08:00
parent 97f3c3486a
commit cf0b00b0d2
4 changed files with 276 additions and 3 deletions

View File

@@ -98,6 +98,8 @@
### 4.4 稳住 renderer authoring / invalidation / bridge refresh
状态: `Completed` (`2026-04-26`)
目标: 把当前已经存在但还容易漂移的 authoring 路径变成可回归验证的稳定面。
执行:

View File

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

View File

@@ -1852,6 +1852,42 @@ namespace Gameplay
}
}
public sealed class ManagedCameraOverrideRendererSelectionProbeAsset
: UniversalRenderPipelineAsset
{
public ManagedCameraOverrideRendererSelectionProbeAsset()
{
rendererDataList =
ProbeScriptableObjectFactory
.CreateRendererDataList(
ProbeScriptableObjectFactory
.Create<ManagedRenderPipelineProbeRendererData>(),
ProbeScriptableObjectFactory
.Create<ManagedRenderPipelineProbeRendererData>());
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
{

View File

@@ -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<const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetRuntime>
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<CameraComponent>();
ASSERT_NE(camera, nullptr);
camera->SetPrimary(true);
ScriptComponent* additionalCameraData =
cameraObject->AddComponent<ScriptComponent>();
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<XCEngine::Rendering::RenderPipeline> pipeline =
asset.CreatePipeline();
ASSERT_NE(pipeline, nullptr);
auto* host =
dynamic_cast<XCEngine::Rendering::Pipelines::ScriptableRenderPipelineHost*>(
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::Core::uint32>(
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) {