diff --git a/engine/include/XCEngine/Rendering/AGENTS.md b/engine/include/XCEngine/Rendering/AGENTS.md index 8be3f83b..021b00f6 100644 --- a/engine/include/XCEngine/Rendering/AGENTS.md +++ b/engine/include/XCEngine/Rendering/AGENTS.md @@ -205,9 +205,11 @@ Managed SRP assets 通过 `GraphicsSettings.renderPipelineAsset` 选择,并通 - `ManagedRenderPipelineAssetDescriptor` 标识 managed asset assembly、namespace、class、可选 `AssetRef`、 serialized ScriptableObject asset graph 和 retained managed object handle。它不再是 configured pipeline 的唯一身份;`GraphicsSettingsState` 中的 configured `AssetRef` 才是 Unity 风格 asset selection root。 -- Descriptor-only selection 仍保留给测试、legacy runtime fallback 和尚未接入 asset import 的路径。没有 - `serializedAssetGraph` 时才允许按 class 默认构造 materialize;一旦 snapshot 存在,缺失或失效的 - managed object handle 必须从 snapshot 重建,不能退回 code-created default asset。 +- Descriptor-only selection 仍保留给测试、legacy runtime fallback 和尚未接入 asset import 的路径;但 + `GraphicsSettings.renderPipelineAsset` getter 和任何带 `AssetRef` 的 managed runtime path 在没有 + `serializedAssetGraph` 时都必须显式失败,不能按 class 默认构造 materialize。只有明确的 descriptor-only + legacy/test path 才允许 class-default fallback;一旦 snapshot 存在,缺失或失效的 managed object handle + 必须从 snapshot 重建,不能退回 code-created default asset。 - `GraphicsSettings.renderPipelineAsset = asset` 必须把当前 managed SRP asset graph snapshot 收进 descriptor。 该 snapshot 覆盖 `UniversalRenderPipelineAsset -> ScriptableRendererData -> ScriptableRendererFeature` 以及 public/`[SerializeField]` serializable settings。Runtime cache 更新可以替换 handle,但不得丢失 @@ -392,7 +394,8 @@ Scene data 每个 camera frame 提取一次,然后由 pipeline 调整。 assembly/type/handle runtime cache,managed materialization 更新 cache 时保留 configured asset reference。 - Managed SRP descriptor 已接入 serialized ScriptableObject graph snapshot。`GraphicsSettings` setter 保存 当前 asset/data/features 图,getter 和 `MonoManagedRenderPipelineAssetRuntime` 在 handle 缺失或失效时按 - snapshot 重建;无 snapshot 的 descriptor-only selection 才保留 class 默认构造 fallback。 + snapshot 重建;`GraphicsSettings` getter 与任何带 `AssetRef` 的 runtime path 不再接受无 snapshot 的 + class 默认构造,只有 descriptor-only legacy/test path 保留该 fallback。 - Managed SRP execution 由 `ScriptableRenderPipelineHost` 承载,它组合 native backend 和可选 managed stage recorder。 - Mono-backed SRP assets 使用 `DefaultNativeBackend` 做 scene drawing,并把 managed stages 记录到 native diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 9aa22c79..a0d300a1 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -3013,6 +3013,13 @@ bool MonoManagedRenderPipelineAssetRuntime::EnsureManagedAsset() const { return m_ownsManagedAssetHandle; } + if (m_descriptor.HasAssetRef()) { + m_runtime->SetError( + "Managed render pipeline asset selected by AssetRef has no serialized asset graph; refusing class-default materialization: " + + m_descriptor.GetFullName() + "."); + return false; + } + MonoClass* assetClass = nullptr; if (!m_runtime->ResolveManagedClass( m_descriptor.assemblyName, @@ -9063,27 +9070,10 @@ bool MonoScriptRuntime::TryEnsureManagedRenderPipelineAssetHandle( return true; } - MonoClass* assetClass = nullptr; - if (!ResolveManagedClass( - ioDescriptor.assemblyName, - ioDescriptor.namespaceName, - ioDescriptor.className, - assetClass) || - assetClass == nullptr) { - return false; - } - - if (!IsMonoClassOrSubclass( - assetClass, - m_scriptableRenderPipelineAssetClass)) { - SetError( - "Managed render pipeline asset must derive from ScriptableRenderPipelineAsset: " + - ioDescriptor.GetFullName() + "."); - return false; - } - - return CreateExternalManagedObject(assetClass, ioDescriptor.managedAssetHandle) && - ioDescriptor.managedAssetHandle != 0u; + SetError( + "Configured managed render pipeline asset has no serialized asset graph; refusing GraphicsSettings class-default materialization: " + + ioDescriptor.GetFullName() + "."); + return false; } void MonoScriptRuntime::DestroyExternalManagedObject(uint32_t gcHandle) { diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index cae40da1..d0472058 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -127,6 +127,16 @@ MonoScriptRuntime::Settings CreateMonoSettings() { return settings; } +XCEngine::Resources::AssetRef MakeRenderPipelineAssetRefForTest( + uint64_t guidLow = 2u) { + XCEngine::Resources::AssetRef assetRef = {}; + assetRef.assetGuid = XCEngine::Resources::AssetGUID(1u, guidLow); + assetRef.localID = XCEngine::Resources::kMainAssetLocalID; + assetRef.resourceType = + XCEngine::Resources::ResourceType::RenderPipelineAsset; + return assetRef; +} + class MonoScriptRuntimeTest : public ::testing::Test { protected: void SetUp() override { @@ -478,7 +488,7 @@ TEST_F( TEST_F( MonoScriptRuntimeTest, - ManagedGraphicsSettingsGetterMaterializesConfiguredAssetWithoutExistingHandle) { + ManagedGraphicsSettingsGetterRejectsDescriptorOnlyAssetWithoutSerializedGraph) { Scene* runtimeScene = CreateScene("ManagedRenderPipelineGetterMaterializationScene"); GameObject* scriptObject = @@ -507,11 +517,7 @@ TEST_F( EXPECT_EQ(resolvedDescriptor.assemblyName, configuredDescriptor.assemblyName); EXPECT_EQ(resolvedDescriptor.namespaceName, configuredDescriptor.namespaceName); EXPECT_EQ(resolvedDescriptor.className, configuredDescriptor.className); - EXPECT_NE(resolvedDescriptor.managedAssetHandle, 0u); - EXPECT_NE( - runtime->GetExternalManagedObject( - resolvedDescriptor.managedAssetHandle), - nullptr); + EXPECT_EQ(resolvedDescriptor.managedAssetHandle, 0u); bool observedAssetWasNull = true; std::string observedPipelineAssetTypeName; @@ -519,14 +525,18 @@ TEST_F( script, "ObservedAssetWasNull", observedAssetWasNull)); - EXPECT_FALSE(observedAssetWasNull); + EXPECT_TRUE(observedAssetWasNull); EXPECT_TRUE(runtime->TryGetFieldValue( script, "ObservedPipelineAssetTypeName", observedPipelineAssetTypeName)); - EXPECT_EQ( - observedPipelineAssetTypeName, - "Gameplay.RenderPipelineApiProbeAsset"); + EXPECT_TRUE(observedPipelineAssetTypeName.empty()); + EXPECT_NE( + runtime->GetLastError().find("serialized asset graph"), + std::string::npos); + EXPECT_NE( + runtime->GetLastError().find("GraphicsSettings"), + std::string::npos); } TEST_F( @@ -679,6 +689,36 @@ TEST_F( EXPECT_FLOAT_EQ(observedFinalColorScale.w, 1.0f); } +TEST_F( + MonoScriptRuntimeTest, + ManagedRenderPipelineAssetRuntimeRejectsAssetRefWithoutSerializedGraph) { + const auto bridge = + XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge(); + ASSERT_NE(bridge, nullptr); + + XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor + descriptor = { + "GameScripts", + "Gameplay", + "ManagedRenderPipelineProbeAsset" + }; + descriptor.assetRef = MakeRenderPipelineAssetRefForTest(17u); + + std::shared_ptr + assetRuntime = bridge->CreateAssetRuntime(descriptor); + ASSERT_NE(assetRuntime, nullptr); + + XCEngine::Rendering::FinalColorSettings settings = {}; + EXPECT_FALSE(assetRuntime->TryGetDefaultFinalColorSettings(settings)); + EXPECT_NE( + runtime->GetLastError().find("AssetRef"), + std::string::npos); + EXPECT_NE( + runtime->GetLastError().find("serialized asset graph"), + std::string::npos); +} + TEST_F( MonoScriptRuntimeTest, RuntimeStopClearsManagedGraphicsSettingsSelection) {