feat(srp): lock project-side pipeline lifecycle contracts

Add project asset probes for renderer invalidation, asset invalidation, and runtime release through the public SRP API surface.

Cover the project/Assets bridge path with lifecycle scripting tests and archive the completed project-side SRP bridge plan.
This commit is contained in:
2026-04-20 15:26:33 +08:00
parent 3bdd45b590
commit 3e32f82e37
3 changed files with 957 additions and 0 deletions

View File

@@ -1,9 +1,38 @@
using System.Reflection;
using XCEngine;
using XCEngine.Rendering;
using XCEngine.Rendering.Universal;
namespace ProjectScripts
{
internal static class ProjectProbeRuntimeVersionUtility
{
private static readonly MethodInfo s_getRuntimeResourceVersionMethod =
typeof(ScriptableRenderPipelineAsset)
.GetMethod(
"GetRuntimeResourceVersionInstance",
BindingFlags.Instance |
BindingFlags.NonPublic);
public static int GetRuntimeResourceVersion(
ScriptableRenderPipelineAsset asset)
{
if (asset == null ||
s_getRuntimeResourceVersionMethod == null)
{
return 0;
}
object version =
s_getRuntimeResourceVersionMethod.Invoke(
asset,
null);
return version is int resolvedVersion
? resolvedVersion
: 0;
}
}
public sealed class ProjectPostProcessColorScalePass
: ScriptableRenderPass
{
@@ -154,4 +183,549 @@ namespace ProjectScripts
};
}
}
internal static class ProjectRendererInvalidationProbeState
{
public static int CreatePipelineCallCount;
public static int DisposePipelineCallCount;
public static int CreateRendererCallCount;
public static int SetupRendererCallCount;
public static int CreateFeatureCallCount;
public static int DisposeRendererCallCount;
public static int DisposeFeatureCallCount;
public static int InvalidateRendererCallCount;
public static void Reset()
{
CreatePipelineCallCount = 0;
DisposePipelineCallCount = 0;
CreateRendererCallCount = 0;
SetupRendererCallCount = 0;
CreateFeatureCallCount = 0;
DisposeRendererCallCount = 0;
DisposeFeatureCallCount = 0;
InvalidateRendererCallCount = 0;
}
}
public sealed class ProjectRendererInvalidationProbePipeline
: RendererBackedRenderPipeline
{
public ProjectRendererInvalidationProbePipeline(
RendererBackedRenderPipelineAsset asset)
: base(asset)
{
ProjectRendererInvalidationProbeState
.CreatePipelineCallCount++;
}
protected override void Dispose(
bool disposing)
{
if (disposing)
{
ProjectRendererInvalidationProbeState
.DisposePipelineCallCount++;
}
base.Dispose(disposing);
}
}
public sealed class ProjectRendererInvalidationProbeFeature
: ScriptableRendererFeature
{
protected override void ReleaseRuntimeResources()
{
ProjectRendererInvalidationProbeState
.DisposeFeatureCallCount++;
}
}
public sealed class ProjectRendererInvalidationProbeRenderer
: ScriptableRenderer
{
protected override bool SupportsRendererRecording(
RendererRecordingContext context)
{
return context != null &&
context.stage == CameraFrameStage.MainScene;
}
protected override bool RecordRenderer(
RendererRecordingContext context)
{
return context != null;
}
protected override void ReleaseRuntimeResources()
{
ProjectRendererInvalidationProbeState
.DisposeRendererCallCount++;
}
}
public sealed class ProjectRendererInvalidationProbeRendererData
: ScriptableRendererData
{
protected override ScriptableRenderer CreateRenderer()
{
ProjectRendererInvalidationProbeState
.CreateRendererCallCount++;
return new ProjectRendererInvalidationProbeRenderer();
}
protected override void SetupRenderer(
ScriptableRenderer renderer)
{
ProjectRendererInvalidationProbeState
.SetupRendererCallCount++;
base.SetupRenderer(renderer);
}
protected override ScriptableRendererFeature[]
CreateRendererFeatures()
{
ProjectRendererInvalidationProbeState
.CreateFeatureCallCount++;
return new ScriptableRendererFeature[]
{
new ProjectRendererInvalidationProbeFeature()
};
}
protected override string GetPipelineRendererAssetKey()
{
return "BuiltinForward";
}
public void InvalidateForTest()
{
ProjectRendererInvalidationProbeState
.InvalidateRendererCallCount++;
SetDirty();
}
}
public sealed class ProjectRendererInvalidationProbeAsset
: RendererBackedRenderPipelineAsset
{
private readonly ProjectRendererInvalidationProbeRendererData
m_rendererData;
public ProjectRendererInvalidationProbeAsset()
{
ProjectRendererInvalidationProbeState.Reset();
m_rendererData =
new ProjectRendererInvalidationProbeRendererData();
rendererDataList = new ScriptableRendererData[]
{
m_rendererData
};
}
protected override ScriptableRenderPipeline
CreateRendererBackedPipeline()
{
return new ProjectRendererInvalidationProbePipeline(
this);
}
public void InvalidateDefaultRendererForTest()
{
if (m_rendererData == null)
{
return;
}
m_rendererData.InvalidateForTest();
}
}
internal static class ProjectPersistentFeatureProbeState
{
public static int CreateRendererCallCount;
public static int CreateFeatureRuntimeCallCount;
public static int DisposeRendererCallCount;
public static int DisposeFeatureCallCount;
public static int InvalidateRendererCallCount;
public static void Reset()
{
CreateRendererCallCount = 0;
CreateFeatureRuntimeCallCount = 0;
DisposeRendererCallCount = 0;
DisposeFeatureCallCount = 0;
InvalidateRendererCallCount = 0;
}
}
public sealed class ProjectPersistentFeatureProbeRendererFeature
: ScriptableRendererFeature
{
public override void Create()
{
ProjectPersistentFeatureProbeState
.CreateFeatureRuntimeCallCount++;
}
protected override void ReleaseRuntimeResources()
{
ProjectPersistentFeatureProbeState
.DisposeFeatureCallCount++;
}
}
public sealed class ProjectPersistentFeatureProbeRenderer
: ScriptableRenderer
{
protected override bool SupportsRendererRecording(
RendererRecordingContext context)
{
return context != null &&
context.stage == CameraFrameStage.MainScene;
}
protected override bool RecordRenderer(
RendererRecordingContext context)
{
return context != null;
}
protected override void ReleaseRuntimeResources()
{
ProjectPersistentFeatureProbeState
.DisposeRendererCallCount++;
}
}
public sealed class ProjectPersistentFeatureProbeRendererData
: ScriptableRendererData
{
private readonly ProjectPersistentFeatureProbeRendererFeature
m_feature =
new ProjectPersistentFeatureProbeRendererFeature();
protected override ScriptableRenderer CreateRenderer()
{
ProjectPersistentFeatureProbeState
.CreateRendererCallCount++;
return new ProjectPersistentFeatureProbeRenderer();
}
protected override ScriptableRendererFeature[]
CreateRendererFeatures()
{
return new ScriptableRendererFeature[]
{
m_feature
};
}
protected override string GetPipelineRendererAssetKey()
{
return "BuiltinForward";
}
public void InvalidateForTest()
{
ProjectPersistentFeatureProbeState
.InvalidateRendererCallCount++;
SetDirty();
}
}
public sealed class ProjectPersistentFeatureProbeAsset
: RendererBackedRenderPipelineAsset
{
private readonly ProjectPersistentFeatureProbeRendererData
m_rendererData;
public ProjectPersistentFeatureProbeAsset()
{
ProjectPersistentFeatureProbeState.Reset();
m_rendererData =
new ProjectPersistentFeatureProbeRendererData();
rendererDataList = new ScriptableRendererData[]
{
m_rendererData
};
}
public void InvalidateDefaultRendererForTest()
{
if (m_rendererData == null)
{
return;
}
m_rendererData.InvalidateForTest();
}
}
internal static class ProjectAssetInvalidationProbeState
{
public static int CreatePipelineCallCount;
public static int DisposePipelineCallCount;
public static int InvalidateAssetCallCount;
public static int LastCreatedSupportedStage;
public static void Reset()
{
CreatePipelineCallCount = 0;
DisposePipelineCallCount = 0;
InvalidateAssetCallCount = 0;
LastCreatedSupportedStage =
(int)CameraFrameStage.MainScene;
}
}
public sealed class ProjectAssetInvalidationProbePipeline
: ScriptableRenderPipeline
{
private readonly CameraFrameStage m_supportedStage;
public ProjectAssetInvalidationProbePipeline(
CameraFrameStage supportedStage)
{
m_supportedStage = supportedStage;
ProjectAssetInvalidationProbeState
.LastCreatedSupportedStage =
(int)supportedStage;
}
protected override bool SupportsStageRenderGraph(
CameraFrameStage stage)
{
return stage == m_supportedStage;
}
protected override bool RecordStageRenderGraph(
ScriptableRenderContext context)
{
return context != null;
}
protected override void Dispose(
bool disposing)
{
if (disposing)
{
ProjectAssetInvalidationProbeState
.DisposePipelineCallCount++;
}
}
}
public sealed class ProjectAssetInvalidationProbeAsset
: ScriptableRenderPipelineAsset
{
private CameraFrameStage m_supportedStage =
CameraFrameStage.MainScene;
public ProjectAssetInvalidationProbeAsset()
{
ProjectAssetInvalidationProbeState.Reset();
}
protected override ScriptableRenderPipeline CreatePipeline()
{
ProjectAssetInvalidationProbeState
.CreatePipelineCallCount++;
return new ProjectAssetInvalidationProbePipeline(
m_supportedStage);
}
public void InvalidatePipelineForTest(
CameraFrameStage supportedStage)
{
if (m_supportedStage == supportedStage)
{
return;
}
m_supportedStage = supportedStage;
ProjectAssetInvalidationProbeState
.InvalidateAssetCallCount++;
SetDirty();
}
}
public sealed class ProjectRendererInvalidationRuntimeSelectionProbe
: MonoBehaviour
{
public void Start()
{
GraphicsSettings.renderPipelineAsset =
new ProjectRendererInvalidationProbeAsset();
}
}
public sealed class ProjectRendererInvalidationObservationProbe
: MonoBehaviour
{
public int ObservedCreatePipelineCallCount;
public int ObservedDisposePipelineCallCount;
public int ObservedCreateRendererCallCount;
public int ObservedSetupRendererCallCount;
public int ObservedCreateFeatureCallCount;
public int ObservedDisposeRendererCallCount;
public int ObservedDisposeFeatureCallCount;
public int ObservedInvalidateRendererCallCount;
public int ObservedRuntimeResourceVersionBeforeInvalidation;
public int ObservedRuntimeResourceVersionAfterInvalidation;
private bool m_requestedInvalidation;
public void Update()
{
ProjectRendererInvalidationProbeAsset selectedAsset =
GraphicsSettings.renderPipelineAsset
as ProjectRendererInvalidationProbeAsset;
if (!m_requestedInvalidation &&
selectedAsset != null &&
ProjectRendererInvalidationProbeState
.CreateRendererCallCount > 0)
{
ObservedRuntimeResourceVersionBeforeInvalidation =
ProjectProbeRuntimeVersionUtility
.GetRuntimeResourceVersion(
selectedAsset);
selectedAsset.InvalidateDefaultRendererForTest();
ObservedRuntimeResourceVersionAfterInvalidation =
ProjectProbeRuntimeVersionUtility
.GetRuntimeResourceVersion(
selectedAsset);
m_requestedInvalidation = true;
}
ObservedCreatePipelineCallCount =
ProjectRendererInvalidationProbeState
.CreatePipelineCallCount;
ObservedDisposePipelineCallCount =
ProjectRendererInvalidationProbeState
.DisposePipelineCallCount;
ObservedCreateRendererCallCount =
ProjectRendererInvalidationProbeState
.CreateRendererCallCount;
ObservedSetupRendererCallCount =
ProjectRendererInvalidationProbeState
.SetupRendererCallCount;
ObservedCreateFeatureCallCount =
ProjectRendererInvalidationProbeState
.CreateFeatureCallCount;
ObservedDisposeRendererCallCount =
ProjectRendererInvalidationProbeState
.DisposeRendererCallCount;
ObservedDisposeFeatureCallCount =
ProjectRendererInvalidationProbeState
.DisposeFeatureCallCount;
ObservedInvalidateRendererCallCount =
ProjectRendererInvalidationProbeState
.InvalidateRendererCallCount;
}
}
public sealed class ProjectPersistentFeatureRuntimeSelectionProbe
: MonoBehaviour
{
public void Start()
{
GraphicsSettings.renderPipelineAsset =
new ProjectPersistentFeatureProbeAsset();
}
}
public sealed class ProjectPersistentFeatureObservationProbe
: MonoBehaviour
{
public int ObservedCreateRendererCallCount;
public int ObservedCreateFeatureRuntimeCallCount;
public int ObservedDisposeRendererCallCount;
public int ObservedDisposeFeatureCallCount;
public int ObservedInvalidateRendererCallCount;
private bool m_requestedInvalidation;
public void Update()
{
ProjectPersistentFeatureProbeAsset selectedAsset =
GraphicsSettings.renderPipelineAsset
as ProjectPersistentFeatureProbeAsset;
if (!m_requestedInvalidation &&
selectedAsset != null &&
ProjectPersistentFeatureProbeState
.CreateFeatureRuntimeCallCount > 0)
{
selectedAsset.InvalidateDefaultRendererForTest();
m_requestedInvalidation = true;
}
ObservedCreateRendererCallCount =
ProjectPersistentFeatureProbeState
.CreateRendererCallCount;
ObservedCreateFeatureRuntimeCallCount =
ProjectPersistentFeatureProbeState
.CreateFeatureRuntimeCallCount;
ObservedDisposeRendererCallCount =
ProjectPersistentFeatureProbeState
.DisposeRendererCallCount;
ObservedDisposeFeatureCallCount =
ProjectPersistentFeatureProbeState
.DisposeFeatureCallCount;
ObservedInvalidateRendererCallCount =
ProjectPersistentFeatureProbeState
.InvalidateRendererCallCount;
}
}
public sealed class ProjectAssetInvalidationRuntimeSelectionProbe
: MonoBehaviour
{
public void Start()
{
GraphicsSettings.renderPipelineAsset =
new ProjectAssetInvalidationProbeAsset();
}
}
public sealed class ProjectAssetInvalidationObservationProbe
: MonoBehaviour
{
public int ObservedCreatePipelineCallCount;
public int ObservedDisposePipelineCallCount;
public int ObservedInvalidateAssetCallCount;
public int ObservedLastCreatedSupportedStage;
private bool m_requestedInvalidation;
public void Update()
{
ProjectAssetInvalidationProbeAsset selectedAsset =
GraphicsSettings.renderPipelineAsset
as ProjectAssetInvalidationProbeAsset;
if (!m_requestedInvalidation &&
selectedAsset != null &&
ProjectAssetInvalidationProbeState
.CreatePipelineCallCount > 0)
{
selectedAsset.InvalidatePipelineForTest(
CameraFrameStage.PostProcess);
m_requestedInvalidation = true;
}
ObservedCreatePipelineCallCount =
ProjectAssetInvalidationProbeState
.CreatePipelineCallCount;
ObservedDisposePipelineCallCount =
ProjectAssetInvalidationProbeState
.DisposePipelineCallCount;
ObservedInvalidateAssetCallCount =
ProjectAssetInvalidationProbeState
.InvalidateAssetCallCount;
ObservedLastCreatedSupportedStage =
ProjectAssetInvalidationProbeState
.LastCreatedSupportedStage;
}
}
}

View File

@@ -1,12 +1,17 @@
#include <gtest/gtest.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Input/InputManager.h>
#include <XCEngine/Rendering/Execution/CameraFrameRenderGraphFrameData.h>
#include <XCEngine/Rendering/Graph/RenderGraph.h>
#include <XCEngine/Rendering/Graph/RenderGraphCompiler.h>
#include <XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/RHI/RHIResourceView.h>
#include <XCEngine/Scene/Scene.h>
#include <XCEngine/Scripting/Mono/MonoScriptRuntime.h>
#include <XCEngine/Scripting/ScriptComponent.h>
#include <XCEngine/Scripting/ScriptEngine.h>
#include <algorithm>
#include <filesystem>
@@ -19,6 +24,7 @@
#include <windows.h>
#endif
using namespace XCEngine::Components;
using namespace XCEngine::Scripting;
namespace {
@@ -77,6 +83,15 @@ MonoScriptRuntime::Settings CreateProjectMonoSettings() {
class ProjectScriptAssemblyTest : public ::testing::Test {
protected:
void SetUp() override {
engine = &ScriptEngine::Get();
engine->OnRuntimeStop();
engine->SetRuntimeFixedDeltaTime(
ScriptEngine::DefaultFixedDeltaTime);
XCEngine::Input::InputManager::Get().Shutdown();
XCEngine::Resources::ResourceManager::Get().Shutdown();
XCEngine::Rendering::Pipelines::
ClearConfiguredManagedRenderPipelineAssetDescriptor();
ASSERT_TRUE(std::filesystem::exists(ResolveProjectScriptCoreDllPath()));
ASSERT_TRUE(std::filesystem::exists(ResolveProjectGameScriptsDllPath()));
@@ -97,9 +112,43 @@ protected:
runtime = std::make_unique<MonoScriptRuntime>(std::move(settings));
ASSERT_TRUE(runtime->Initialize()) << runtime->GetLastError();
engine->SetRuntime(runtime.get());
}
void TearDown() override {
if (engine != nullptr) {
engine->OnRuntimeStop();
engine->SetRuntime(nullptr);
}
XCEngine::Input::InputManager::Get().Shutdown();
XCEngine::Rendering::Pipelines::
ClearConfiguredManagedRenderPipelineAssetDescriptor();
runtime.reset();
scene.reset();
XCEngine::Resources::ResourceManager::Get().Shutdown();
}
Scene* CreateScene(const std::string& sceneName) {
scene = std::make_unique<Scene>(sceneName);
return scene.get();
}
ScriptComponent* AddProjectScript(
GameObject* gameObject,
const std::string& className) {
ScriptComponent* component =
gameObject->AddComponent<ScriptComponent>();
component->SetScriptClass(
"GameScripts",
"ProjectScripts",
className);
return component;
}
ScriptEngine* engine = nullptr;
std::unique_ptr<MonoScriptRuntime> runtime;
std::unique_ptr<Scene> scene;
};
TEST_F(ProjectScriptAssemblyTest, InitializesFromProjectScriptAssemblyDirectory) {
@@ -360,4 +409,338 @@ TEST_F(
recorder->Shutdown();
}
TEST_F(
ProjectScriptAssemblyTest,
ProjectManagedBridgeRebuildsRendererAfterProjectRendererDataInvalidation) {
Scene* runtimeScene =
CreateScene("ProjectRendererInvalidationScene");
GameObject* selectionObject =
runtimeScene->CreateGameObject(
"ProjectRendererInvalidationSelection");
ScriptComponent* selectionScript =
AddProjectScript(
selectionObject,
"ProjectRendererInvalidationRuntimeSelectionProbe");
ASSERT_NE(selectionScript, nullptr);
GameObject* observationObject =
runtimeScene->CreateGameObject(
"ProjectRendererInvalidationObservation");
ScriptComponent* observationScript =
AddProjectScript(
observationObject,
"ProjectRendererInvalidationObservationProbe");
ASSERT_NE(observationScript, nullptr);
engine->OnRuntimeStart(runtimeScene);
engine->OnUpdate(0.016f);
const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor
descriptor =
XCEngine::Rendering::Pipelines::
GetConfiguredManagedRenderPipelineAssetDescriptor();
ASSERT_TRUE(descriptor.IsValid());
ASSERT_NE(descriptor.managedAssetHandle, 0u);
EXPECT_EQ(descriptor.assemblyName, "GameScripts");
EXPECT_EQ(descriptor.namespaceName, "ProjectScripts");
EXPECT_EQ(
descriptor.className,
"ProjectRendererInvalidationProbeAsset");
const auto bridge =
XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge();
ASSERT_NE(bridge, nullptr);
std::shared_ptr<const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetRuntime>
assetRuntime = bridge->CreateAssetRuntime(descriptor);
ASSERT_NE(assetRuntime, nullptr);
std::unique_ptr<XCEngine::Rendering::RenderPipelineStageRecorder>
recorder = assetRuntime->CreateStageRecorder();
ASSERT_NE(recorder, nullptr);
const XCEngine::Rendering::RenderContext context = {};
ASSERT_TRUE(recorder->Initialize(context));
ASSERT_TRUE(
recorder->SupportsStageRenderGraph(
XCEngine::Rendering::CameraFrameStage::MainScene));
engine->OnUpdate(0.016f);
ASSERT_TRUE(
recorder->SupportsStageRenderGraph(
XCEngine::Rendering::CameraFrameStage::MainScene));
engine->OnUpdate(0.016f);
EXPECT_TRUE(runtime->GetLastError().empty()) << runtime->GetLastError();
int observedCreatePipelineCallCount = 0;
int observedDisposePipelineCallCount = 0;
int observedCreateRendererCallCount = 0;
int observedSetupRendererCallCount = 0;
int observedCreateFeatureCallCount = 0;
int observedDisposeRendererCallCount = 0;
int observedDisposeFeatureCallCount = 0;
int observedInvalidateRendererCallCount = 0;
int observedRuntimeResourceVersionBeforeInvalidation = 0;
int observedRuntimeResourceVersionAfterInvalidation = 0;
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedCreatePipelineCallCount",
observedCreatePipelineCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedDisposePipelineCallCount",
observedDisposePipelineCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedCreateRendererCallCount",
observedCreateRendererCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedSetupRendererCallCount",
observedSetupRendererCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedCreateFeatureCallCount",
observedCreateFeatureCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedDisposeRendererCallCount",
observedDisposeRendererCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedDisposeFeatureCallCount",
observedDisposeFeatureCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedInvalidateRendererCallCount",
observedInvalidateRendererCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedRuntimeResourceVersionBeforeInvalidation",
observedRuntimeResourceVersionBeforeInvalidation));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedRuntimeResourceVersionAfterInvalidation",
observedRuntimeResourceVersionAfterInvalidation));
EXPECT_EQ(observedCreatePipelineCallCount, 2);
EXPECT_EQ(observedDisposePipelineCallCount, 1);
EXPECT_EQ(observedCreateRendererCallCount, 2);
EXPECT_EQ(observedSetupRendererCallCount, 2);
EXPECT_EQ(observedCreateFeatureCallCount, 2);
EXPECT_EQ(observedDisposeRendererCallCount, 1);
EXPECT_EQ(observedDisposeFeatureCallCount, 1);
EXPECT_EQ(observedInvalidateRendererCallCount, 1);
EXPECT_GT(observedRuntimeResourceVersionBeforeInvalidation, 0);
EXPECT_EQ(
observedRuntimeResourceVersionAfterInvalidation,
observedRuntimeResourceVersionBeforeInvalidation + 1);
recorder->Shutdown();
}
TEST_F(
ProjectScriptAssemblyTest,
ProjectManagedBridgeReleasesProjectRendererCachesAcrossInvalidationAndAssetRuntimeRelease) {
Scene* runtimeScene =
CreateScene("ProjectPersistentFeatureLifecycleScene");
GameObject* selectionObject =
runtimeScene->CreateGameObject(
"ProjectPersistentFeatureSelection");
ScriptComponent* selectionScript =
AddProjectScript(
selectionObject,
"ProjectPersistentFeatureRuntimeSelectionProbe");
ASSERT_NE(selectionScript, nullptr);
GameObject* observationObject =
runtimeScene->CreateGameObject(
"ProjectPersistentFeatureObservation");
ScriptComponent* observationScript =
AddProjectScript(
observationObject,
"ProjectPersistentFeatureObservationProbe");
ASSERT_NE(observationScript, nullptr);
engine->OnRuntimeStart(runtimeScene);
engine->OnUpdate(0.016f);
const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor
descriptor =
XCEngine::Rendering::Pipelines::
GetConfiguredManagedRenderPipelineAssetDescriptor();
ASSERT_TRUE(descriptor.IsValid());
ASSERT_NE(descriptor.managedAssetHandle, 0u);
EXPECT_EQ(descriptor.assemblyName, "GameScripts");
EXPECT_EQ(descriptor.namespaceName, "ProjectScripts");
EXPECT_EQ(
descriptor.className,
"ProjectPersistentFeatureProbeAsset");
{
const auto bridge =
XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge();
ASSERT_NE(bridge, nullptr);
std::shared_ptr<const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetRuntime>
assetRuntime = bridge->CreateAssetRuntime(descriptor);
ASSERT_NE(assetRuntime, nullptr);
std::unique_ptr<XCEngine::Rendering::RenderPipelineStageRecorder>
recorder = assetRuntime->CreateStageRecorder();
ASSERT_NE(recorder, nullptr);
const XCEngine::Rendering::RenderContext context = {};
ASSERT_TRUE(recorder->Initialize(context));
ASSERT_TRUE(
recorder->SupportsStageRenderGraph(
XCEngine::Rendering::CameraFrameStage::MainScene));
engine->OnUpdate(0.016f);
ASSERT_TRUE(
recorder->SupportsStageRenderGraph(
XCEngine::Rendering::CameraFrameStage::MainScene));
recorder->Shutdown();
}
engine->OnUpdate(0.016f);
EXPECT_TRUE(runtime->GetLastError().empty()) << runtime->GetLastError();
int observedCreateRendererCallCount = 0;
int observedCreateFeatureRuntimeCallCount = 0;
int observedDisposeRendererCallCount = 0;
int observedDisposeFeatureCallCount = 0;
int observedInvalidateRendererCallCount = 0;
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedCreateRendererCallCount",
observedCreateRendererCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedCreateFeatureRuntimeCallCount",
observedCreateFeatureRuntimeCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedDisposeRendererCallCount",
observedDisposeRendererCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedDisposeFeatureCallCount",
observedDisposeFeatureCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedInvalidateRendererCallCount",
observedInvalidateRendererCallCount));
EXPECT_EQ(observedCreateRendererCallCount, 2);
EXPECT_EQ(observedCreateFeatureRuntimeCallCount, 2);
EXPECT_EQ(observedDisposeRendererCallCount, 2);
EXPECT_EQ(observedDisposeFeatureCallCount, 2);
EXPECT_EQ(observedInvalidateRendererCallCount, 1);
}
TEST_F(
ProjectScriptAssemblyTest,
ProjectManagedBridgeRebuildsPipelineAfterProjectAssetInvalidation) {
Scene* runtimeScene =
CreateScene("ProjectAssetInvalidationScene");
GameObject* selectionObject =
runtimeScene->CreateGameObject(
"ProjectAssetInvalidationSelection");
ScriptComponent* selectionScript =
AddProjectScript(
selectionObject,
"ProjectAssetInvalidationRuntimeSelectionProbe");
ASSERT_NE(selectionScript, nullptr);
GameObject* observationObject =
runtimeScene->CreateGameObject(
"ProjectAssetInvalidationObservation");
ScriptComponent* observationScript =
AddProjectScript(
observationObject,
"ProjectAssetInvalidationObservationProbe");
ASSERT_NE(observationScript, nullptr);
engine->OnRuntimeStart(runtimeScene);
engine->OnUpdate(0.016f);
const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor
descriptor =
XCEngine::Rendering::Pipelines::
GetConfiguredManagedRenderPipelineAssetDescriptor();
ASSERT_TRUE(descriptor.IsValid());
ASSERT_NE(descriptor.managedAssetHandle, 0u);
EXPECT_EQ(descriptor.assemblyName, "GameScripts");
EXPECT_EQ(descriptor.namespaceName, "ProjectScripts");
EXPECT_EQ(
descriptor.className,
"ProjectAssetInvalidationProbeAsset");
const auto bridge =
XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge();
ASSERT_NE(bridge, nullptr);
std::shared_ptr<const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetRuntime>
assetRuntime = bridge->CreateAssetRuntime(descriptor);
ASSERT_NE(assetRuntime, nullptr);
std::unique_ptr<XCEngine::Rendering::RenderPipelineStageRecorder>
recorder = assetRuntime->CreateStageRecorder();
ASSERT_NE(recorder, nullptr);
const XCEngine::Rendering::RenderContext context = {};
ASSERT_TRUE(recorder->Initialize(context));
ASSERT_TRUE(
recorder->SupportsStageRenderGraph(
XCEngine::Rendering::CameraFrameStage::MainScene));
ASSERT_FALSE(
recorder->SupportsStageRenderGraph(
XCEngine::Rendering::CameraFrameStage::PostProcess));
engine->OnUpdate(0.016f);
ASSERT_FALSE(
recorder->SupportsStageRenderGraph(
XCEngine::Rendering::CameraFrameStage::MainScene));
ASSERT_TRUE(
recorder->SupportsStageRenderGraph(
XCEngine::Rendering::CameraFrameStage::PostProcess));
engine->OnUpdate(0.016f);
EXPECT_TRUE(runtime->GetLastError().empty()) << runtime->GetLastError();
int observedCreatePipelineCallCount = 0;
int observedDisposePipelineCallCount = 0;
int observedInvalidateAssetCallCount = 0;
int observedLastCreatedSupportedStage = 0;
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedCreatePipelineCallCount",
observedCreatePipelineCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedDisposePipelineCallCount",
observedDisposePipelineCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedInvalidateAssetCallCount",
observedInvalidateAssetCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
observationScript,
"ObservedLastCreatedSupportedStage",
observedLastCreatedSupportedStage));
EXPECT_EQ(observedCreatePipelineCallCount, 2);
EXPECT_EQ(observedDisposePipelineCallCount, 1);
EXPECT_EQ(observedInvalidateAssetCallCount, 1);
EXPECT_EQ(
observedLastCreatedSupportedStage,
static_cast<int>(
XCEngine::Rendering::CameraFrameStage::PostProcess));
recorder->Shutdown();
}
} // namespace