refactor(srp): add managed lifecycle cleanup seams

Invoke managed pipeline disposal and asset runtime cleanup from the native bridge lifecycle. Add Universal renderer and feature cleanup hooks plus regression probes to verify runtime cache teardown semantics.
This commit is contained in:
2026-04-20 01:14:37 +08:00
parent beaf5809d5
commit 58dde75d3d
10 changed files with 900 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
# SRP Managed生命周期清理接缝计划 2026-04-20
## 1. 阶段目标
当前 managed SRP/URP 主线里,`RenderPipelineAsset -> active pipeline instance` 的 ownership 已经成立,但生命周期还缺最后一环:
1. managed `ScriptableRenderPipeline` 没有正式的 dispose/cleanup 接缝
2. `ScriptableRenderer` / `ScriptableRendererFeature` 也没有 cleanup 语义
3. `ScriptableRendererData` 虽然缓存了 renderer instance但没有正式释放路径
4. native 在释放 managed pipeline handle 时,只是销毁 GC handle没有先走 managed cleanup
这一阶段的目标,就是把这条生命周期链补齐,避免后续做资源化 renderer、更多 renderer feature、甚至真正的 SRP 资源对象时继续堆积隐式状态。
---
## 2. 当前问题
### 2.1 active pipeline 能建立,但不能正式销毁
现在 `MonoManagedRenderPipelineAssetRuntime` 会缓存并持有 shared managed pipeline instance。
但 release 时只是:
1. native 侧直接销毁 external managed object handle
2. managed pipeline 本身没有机会执行 cleanup
这意味着“创建”已经像 Unity 了,“释放”还没有。
### 2.2 renderer/feature 没有正式生命周期
现在 `ScriptableRenderer` 负责:
1. 持有 feature 列表
2. 构建 active pass queue
3. 执行 stage record
但它没有:
1. renderer cleanup hook
2. feature cleanup hook
3. renderer data 缓存失效/释放 hook
后面一旦 renderer 或 feature 开始持有真正的资源对象,这里就会成为脏状态积累点。
### 2.3 Universal 现在缺少“pipeline dispose -> renderer cache release”这一跳
当前 `UniversalRenderPipelineAsset` 已经正式持有:
1. renderer data 列表
2. 默认 renderer 选择
`ScriptableRendererData` 也已经正式缓存 renderer instance。
那就意味着 pipeline 销毁时,应该有一条明确链路把对应 renderer instance cache 收掉;否则 asset/runtime 生命周期和 renderer cache 生命周期仍然不闭合。
---
## 3. 本阶段方案
### 方案核心
建立一条完整的 managed 生命周期链:
1. native release managed pipeline 前,先调用 pipeline dispose
2. `ScriptableRenderPipeline` 提供正式 dispose seam
3. `UniversalRenderPipeline` 在 dispose 时回收 asset 上缓存的 renderer instance
4. `ScriptableRendererData` 提供释放 renderer cache 的内部入口
5. `ScriptableRenderer` 负责释放 feature 和自身队列状态
6. `ScriptableRendererFeature` 提供正式 dispose seam
### 预期结果
阶段完成后,生命周期关系会变成:
`ManagedAssetRuntime -> managed pipeline dispose -> pipeline asset cleanup -> renderer data release -> renderer dispose -> feature dispose`
---
## 4. 实施步骤
### Step 1补 managed core 生命周期 API
目标:
1.`ScriptableRenderPipeline` 增加内部 dispose 入口和受保护 cleanup seam
2.`ScriptableRenderer` 增加内部 dispose 入口和 feature release
3.`ScriptableRendererFeature` 增加受保护 dispose seam
4.`ScriptableRendererData` 增加 renderer cache release 入口
### Step 2把 Universal 链接到 cleanup
目标:
1.`ScriptableRenderPipelineAsset` 提供内部 pipeline-resource release seam
2.`UniversalRenderPipelineAsset` override 回收 rendererDataList 中的 renderer caches
3.`UniversalRenderPipeline` 在 dispose 时调用 asset cleanup
### Step 3native release 前调用 managed dispose
目标:
1. `MonoManagedRenderPipelineAssetRuntime` 在释放 shared pipeline handle 前先调用 managed pipeline dispose
2. 保持 runtime ownership 不回退、不重新散乱到 recorder
### Step 4补生命周期回归测试
目标:
1. 验证 runtime release 时 managed pipeline dispose 被调用
2. 验证 Universal renderer/feature cleanup 会沿 dispose 链执行
3. 保持现有 SRP/URP 创建、复用、录制能力不回归
### Step 5完整验证与收口
目标:
1. 编译 `rendering_unit_tests``scripting_tests``XCEditor`
2. 运行测试
3. 运行旧版 `XCEditor` 10 秒冒烟并验证新 `SceneReady`
4. 归档 plan提交并推送
---
## 5. 验收标准
本阶段完成后应满足:
1. managed pipeline release 前会先执行正式 dispose
2. `ScriptableRenderer` / `ScriptableRendererFeature` 不再只有创建,没有 cleanup
3. `ScriptableRendererData` 缓存的 renderer instance 有明确释放路径
4. `UniversalRenderPipeline` 生命周期能闭合到 renderer cache
5. 现有 SRP/URP 主线测试与 editor 冒烟全部通过

View File

@@ -738,6 +738,7 @@ private:
void ReleaseManagedPipeline() const;
void ReleaseManagedAsset() const;
MonoObject* GetManagedAssetObject() const;
MonoMethod* ResolveDisposePipelineMethod(MonoObject* pipelineObject) const;
MonoMethod* ResolveCreatePipelineMethod(MonoObject* assetObject) const;
MonoMethod* ResolveConfigureCameraRenderRequestMethod(
MonoObject* assetObject) const;
@@ -745,15 +746,19 @@ private:
MonoObject* assetObject) const;
MonoMethod* ResolveGetPipelineRendererAssetKeyMethod(
MonoObject* assetObject) const;
MonoMethod* ResolveReleaseRuntimeResourcesMethod(
MonoObject* assetObject) const;
MonoScriptRuntime* m_runtime = nullptr;
std::weak_ptr<void> m_runtimeLifetime;
Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor m_descriptor;
mutable uint32_t m_assetHandle = 0;
mutable MonoMethod* m_disposePipelineMethod = nullptr;
mutable MonoMethod* m_createPipelineMethod = nullptr;
mutable MonoMethod* m_configureCameraRenderRequestMethod = nullptr;
mutable MonoMethod* m_getDefaultFinalColorSettingsMethod = nullptr;
mutable MonoMethod* m_getPipelineRendererAssetKeyMethod = nullptr;
mutable MonoMethod* m_releaseRuntimeResourcesMethod = nullptr;
mutable bool m_ownsManagedAssetHandle = false;
mutable bool m_assetCreationAttempted = false;
mutable uint32_t m_pipelineHandle = 0;
@@ -1233,6 +1238,7 @@ bool MonoManagedRenderPipelineAssetRuntime::EnsureManagedAsset() const {
void MonoManagedRenderPipelineAssetRuntime::ReleaseManagedPipeline() const {
m_pipelineCreationAttempted = false;
m_disposePipelineMethod = nullptr;
if (!IsRuntimeAlive()) {
m_pipelineHandle = 0;
@@ -1240,6 +1246,18 @@ void MonoManagedRenderPipelineAssetRuntime::ReleaseManagedPipeline() const {
}
if (m_pipelineHandle != 0) {
MonoObject* const pipelineObject =
m_runtime->GetManagedObject(m_pipelineHandle);
MonoMethod* const disposeMethod =
ResolveDisposePipelineMethod(pipelineObject);
if (pipelineObject != nullptr && disposeMethod != nullptr) {
m_runtime->InvokeManagedMethod(
pipelineObject,
disposeMethod,
nullptr,
nullptr);
}
m_runtime->DestroyExternalManagedObject(m_pipelineHandle);
m_pipelineHandle = 0;
}
@@ -1247,6 +1265,7 @@ void MonoManagedRenderPipelineAssetRuntime::ReleaseManagedPipeline() const {
void MonoManagedRenderPipelineAssetRuntime::ReleaseManagedAsset() const {
ReleaseManagedPipeline();
m_releaseRuntimeResourcesMethod = nullptr;
m_createPipelineMethod = nullptr;
m_configureCameraRenderRequestMethod = nullptr;
m_getDefaultFinalColorSettingsMethod = nullptr;
@@ -1262,6 +1281,17 @@ void MonoManagedRenderPipelineAssetRuntime::ReleaseManagedAsset() const {
return;
}
MonoObject* const assetObject = GetManagedAssetObject();
MonoMethod* const releaseRuntimeResourcesMethod =
ResolveReleaseRuntimeResourcesMethod(assetObject);
if (assetObject != nullptr && releaseRuntimeResourcesMethod != nullptr) {
m_runtime->InvokeManagedMethod(
assetObject,
releaseRuntimeResourcesMethod,
nullptr,
nullptr);
}
if (m_assetHandle != 0 && ownsManagedAssetHandle) {
m_runtime->DestroyExternalManagedObject(m_assetHandle);
}
@@ -1275,6 +1305,19 @@ MonoObject* MonoManagedRenderPipelineAssetRuntime::GetManagedAssetObject() const
: nullptr;
}
MonoMethod* MonoManagedRenderPipelineAssetRuntime::ResolveDisposePipelineMethod(
MonoObject* pipelineObject) const {
if (m_disposePipelineMethod == nullptr) {
m_disposePipelineMethod =
m_runtime->ResolveManagedMethod(
pipelineObject,
"DisposeInstance",
0);
}
return m_disposePipelineMethod;
}
MonoMethod* MonoManagedRenderPipelineAssetRuntime::ResolveCreatePipelineMethod(
MonoObject* assetObject) const {
if (m_createPipelineMethod == nullptr) {
@@ -1330,6 +1373,20 @@ MonoManagedRenderPipelineAssetRuntime::ResolveGetPipelineRendererAssetKeyMethod(
return m_getPipelineRendererAssetKeyMethod;
}
MonoMethod*
MonoManagedRenderPipelineAssetRuntime::ResolveReleaseRuntimeResourcesMethod(
MonoObject* assetObject) const {
if (m_releaseRuntimeResourcesMethod == nullptr) {
m_releaseRuntimeResourcesMethod =
m_runtime->ResolveManagedMethod(
assetObject,
"ReleaseRuntimeResourcesInstance",
0);
}
return m_releaseRuntimeResourcesMethod;
}
class MonoManagedRenderPipelineBridge final
: public Rendering::Pipelines::ManagedRenderPipelineBridge {
public:

View File

@@ -917,6 +917,126 @@ namespace Gameplay
}
}
internal static class ManagedLifecycleProbeState
{
public static int CreatePipelineCallCount;
public static int CreateRendererCallCount;
public static int CreateFeatureCallCount;
public static int ReleaseRendererDataRuntimeResourcesCallCount;
public static int DisposePipelineCallCount;
public static int ReleaseAssetRuntimeResourcesCallCount;
public static int DisposeRendererCallCount;
public static int DisposeFeatureCallCount;
public static void Reset()
{
CreatePipelineCallCount = 0;
CreateRendererCallCount = 0;
CreateFeatureCallCount = 0;
ReleaseRendererDataRuntimeResourcesCallCount = 0;
DisposePipelineCallCount = 0;
ReleaseAssetRuntimeResourcesCallCount = 0;
DisposeRendererCallCount = 0;
DisposeFeatureCallCount = 0;
}
}
internal sealed class ManagedLifecycleProbeRendererFeature
: ScriptableRendererFeature
{
protected override void ReleaseRuntimeResources()
{
ManagedLifecycleProbeState.DisposeFeatureCallCount++;
}
}
internal sealed class ManagedLifecycleProbeRenderer
: ScriptableRenderer
{
public ManagedLifecycleProbeRenderer(
ScriptableRendererFeature[] rendererFeatures)
{
ScriptableRendererFeature[] resolvedRendererFeatures =
rendererFeatures ?? Array.Empty<ScriptableRendererFeature>();
for (int i = 0; i < resolvedRendererFeatures.Length; ++i)
{
AddFeature(resolvedRendererFeatures[i]);
}
}
protected override bool SupportsStageRenderGraph(
CameraFrameStage stage)
{
return stage == CameraFrameStage.MainScene;
}
protected override bool RecordStageRenderGraph(
ScriptableRenderContext context)
{
return context != null;
}
protected override void ReleaseRuntimeResources()
{
ManagedLifecycleProbeState.DisposeRendererCallCount++;
}
}
internal sealed class ManagedLifecycleProbeRendererData
: ScriptableRendererData
{
protected override ScriptableRenderer CreateRenderer()
{
ManagedLifecycleProbeState.CreateRendererCallCount++;
return new ManagedLifecycleProbeRenderer(
CreateRendererFeatures());
}
protected override ScriptableRendererFeature[] CreateRendererFeatures()
{
ManagedLifecycleProbeState.CreateFeatureCallCount++;
return new ScriptableRendererFeature[]
{
new ManagedLifecycleProbeRendererFeature()
};
}
protected override string GetPipelineRendererAssetKey()
{
return "BuiltinForward";
}
protected override void ReleaseRuntimeResources()
{
ManagedLifecycleProbeState.ReleaseRendererDataRuntimeResourcesCallCount++;
}
}
internal sealed class ManagedPipelineDisposeProbe
: ScriptableRenderPipeline
{
protected override bool SupportsStageRenderGraph(
CameraFrameStage stage)
{
return stage == CameraFrameStage.MainScene;
}
protected override bool RecordStageRenderGraph(
ScriptableRenderContext context)
{
return context != null;
}
protected override void Dispose(
bool disposing)
{
if (disposing)
{
ManagedLifecycleProbeState.DisposePipelineCallCount++;
}
}
}
public sealed class LegacyRenderPipelineApiProbeAsset : RenderPipelineAsset
{
}
@@ -925,6 +1045,40 @@ namespace Gameplay
{
}
public sealed class ManagedPipelineDisposeProbeAsset
: ScriptableRenderPipelineAsset
{
protected override ScriptableRenderPipeline CreatePipeline()
{
ManagedLifecycleProbeState.CreatePipelineCallCount++;
return new ManagedPipelineDisposeProbe();
}
}
public sealed class ManagedUniversalLifecycleProbeAsset
: UniversalRenderPipelineAsset
{
public ManagedUniversalLifecycleProbeAsset()
{
rendererDataList = new ScriptableRendererData[]
{
new ManagedLifecycleProbeRendererData()
};
}
protected override ScriptableRenderPipeline CreatePipeline()
{
ManagedLifecycleProbeState.CreatePipelineCallCount++;
return base.CreatePipeline();
}
protected override void ReleaseRuntimeResources()
{
ManagedLifecycleProbeState.ReleaseAssetRuntimeResourcesCallCount++;
ReleaseRendererDataRuntimeResources();
}
}
public sealed class ManagedRenderPipelineProbeAsset
: UniversalRenderPipelineAsset
{
@@ -1323,6 +1477,86 @@ namespace Gameplay
}
}
public sealed class ManagedLifecycleObservationProbe
: MonoBehaviour
{
public int ObservedCreatePipelineCallCount;
public int ObservedCreateRendererCallCount;
public int ObservedCreateFeatureCallCount;
public int ObservedReleaseRendererDataRuntimeResourcesCallCount;
public int ObservedDisposePipelineCallCount;
public int ObservedReleaseAssetRuntimeResourcesCallCount;
public int ObservedDisposeRendererCallCount;
public int ObservedDisposeFeatureCallCount;
public void Start()
{
ManagedLifecycleProbeState.Reset();
}
public void Update()
{
ObservedCreatePipelineCallCount =
ManagedLifecycleProbeState.CreatePipelineCallCount;
ObservedCreateRendererCallCount =
ManagedLifecycleProbeState.CreateRendererCallCount;
ObservedCreateFeatureCallCount =
ManagedLifecycleProbeState.CreateFeatureCallCount;
ObservedReleaseRendererDataRuntimeResourcesCallCount =
ManagedLifecycleProbeState
.ReleaseRendererDataRuntimeResourcesCallCount;
ObservedDisposePipelineCallCount =
ManagedLifecycleProbeState.DisposePipelineCallCount;
ObservedReleaseAssetRuntimeResourcesCallCount =
ManagedLifecycleProbeState.ReleaseAssetRuntimeResourcesCallCount;
ObservedDisposeRendererCallCount =
ManagedLifecycleProbeState.DisposeRendererCallCount;
ObservedDisposeFeatureCallCount =
ManagedLifecycleProbeState.DisposeFeatureCallCount;
}
}
public sealed class ManagedUniversalLifecycleRuntimeSelectionProbe
: MonoBehaviour
{
public int ObservedCreatePipelineCallCount;
public int ObservedCreateRendererCallCount;
public int ObservedCreateFeatureCallCount;
public int ObservedReleaseRendererDataRuntimeResourcesCallCount;
public int ObservedDisposePipelineCallCount;
public int ObservedReleaseAssetRuntimeResourcesCallCount;
public int ObservedDisposeRendererCallCount;
public int ObservedDisposeFeatureCallCount;
public void Start()
{
ManagedLifecycleProbeState.Reset();
GraphicsSettings.renderPipelineAsset =
new ManagedUniversalLifecycleProbeAsset();
}
public void Update()
{
ObservedCreatePipelineCallCount =
ManagedLifecycleProbeState.CreatePipelineCallCount;
ObservedCreateRendererCallCount =
ManagedLifecycleProbeState.CreateRendererCallCount;
ObservedCreateFeatureCallCount =
ManagedLifecycleProbeState.CreateFeatureCallCount;
ObservedReleaseRendererDataRuntimeResourcesCallCount =
ManagedLifecycleProbeState
.ReleaseRendererDataRuntimeResourcesCallCount;
ObservedDisposePipelineCallCount =
ManagedLifecycleProbeState.DisposePipelineCallCount;
ObservedReleaseAssetRuntimeResourcesCallCount =
ManagedLifecycleProbeState.ReleaseAssetRuntimeResourcesCallCount;
ObservedDisposeRendererCallCount =
ManagedLifecycleProbeState.DisposeRendererCallCount;
ObservedDisposeFeatureCallCount =
ManagedLifecycleProbeState.DisposeFeatureCallCount;
}
}
public sealed class ScriptCoreUniversalRenderPipelineSelectionProbe
: MonoBehaviour
{

View File

@@ -4,10 +4,23 @@ namespace XCEngine.Rendering
{
public abstract class ScriptableRenderPipeline : Object
{
private bool m_disposed;
protected ScriptableRenderPipeline()
{
}
internal void DisposeInstance()
{
if (m_disposed)
{
return;
}
Dispose(true);
m_disposed = true;
}
protected virtual bool SupportsStageRenderGraph(
CameraFrameStage stage)
{
@@ -19,6 +32,11 @@ namespace XCEngine.Rendering
{
return false;
}
protected virtual void Dispose(
bool disposing)
{
}
}
}

View File

@@ -8,6 +8,11 @@ namespace XCEngine.Rendering
{
}
internal void ReleaseRuntimeResourcesInstance()
{
ReleaseRuntimeResources();
}
protected virtual ScriptableRenderPipeline CreatePipeline()
{
return null;
@@ -27,6 +32,10 @@ namespace XCEngine.Rendering
{
return string.Empty;
}
protected virtual void ReleaseRuntimeResources()
{
}
}
}

View File

@@ -10,11 +10,35 @@ namespace XCEngine.Rendering.Universal
new List<ScriptableRendererFeature>();
private readonly List<ScriptableRenderPass> m_activePassQueue =
new List<ScriptableRenderPass>();
private bool m_disposed;
protected ScriptableRenderer()
{
}
internal void ReleaseRuntimeResourcesInstance()
{
if (m_disposed)
{
return;
}
ReleaseRuntimeResources();
for (int i = 0; i < m_features.Count; ++i)
{
ScriptableRendererFeature feature = m_features[i];
if (feature != null)
{
feature.ReleaseRuntimeResourcesInstance();
}
}
m_features.Clear();
m_activePassQueue.Clear();
m_disposed = true;
}
public void EnqueuePass(
ScriptableRenderPass renderPass)
{
@@ -126,6 +150,10 @@ namespace XCEngine.Rendering.Universal
AddRenderPasses(renderingData);
}
protected virtual void ReleaseRuntimeResources()
{
}
}
}

View File

@@ -38,6 +38,35 @@ namespace XCEngine.Rendering.Universal
return GetPipelineRendererAssetKey();
}
internal void ReleaseRuntimeResourcesInstance()
{
if (m_rendererInstance != null)
{
m_rendererInstance.ReleaseRuntimeResourcesInstance();
m_rendererInstance = null;
m_rendererFeatures = null;
ReleaseRuntimeResources();
return;
}
if (m_rendererFeatures != null)
{
for (int i = 0; i < m_rendererFeatures.Length; ++i)
{
ScriptableRendererFeature rendererFeature =
m_rendererFeatures[i];
if (rendererFeature != null)
{
rendererFeature.ReleaseRuntimeResourcesInstance();
}
}
m_rendererFeatures = null;
}
ReleaseRuntimeResources();
}
internal void ConfigureCameraRenderRequestInstance(
CameraRenderRequestContext context)
{
@@ -80,6 +109,10 @@ namespace XCEngine.Rendering.Universal
return Array.Empty<ScriptableRendererFeature>();
}
protected virtual void ReleaseRuntimeResources()
{
}
protected bool HasDirectionalShadow(
CameraRenderRequestContext context)
{

View File

@@ -5,12 +5,25 @@ namespace XCEngine.Rendering.Universal
{
public abstract class ScriptableRendererFeature
{
private bool m_disposed;
protected ScriptableRendererFeature()
{
}
public bool isActive { get; set; } = true;
internal void ReleaseRuntimeResourcesInstance()
{
if (m_disposed)
{
return;
}
ReleaseRuntimeResources();
m_disposed = true;
}
public virtual void Create()
{
}
@@ -47,6 +60,10 @@ namespace XCEngine.Rendering.Universal
.Rendering_CameraRenderRequestContext_ClearDirectionalShadow(
context.nativeHandle);
}
protected virtual void ReleaseRuntimeResources()
{
}
}
}

View File

@@ -41,6 +41,11 @@ namespace XCEngine.Rendering.Universal
: string.Empty;
}
protected override void ReleaseRuntimeResources()
{
ReleaseRendererDataRuntimeResources();
}
internal ScriptableRendererData GetDefaultRendererData()
{
return GetRendererData(defaultRendererIndex);
@@ -92,6 +97,42 @@ namespace XCEngine.Rendering.Universal
defaultRendererIndex = 0;
}
protected void ReleaseRendererDataRuntimeResources()
{
EnsureRendererDataList();
for (int i = 0; i < rendererDataList.Length; ++i)
{
ScriptableRendererData rendererData =
rendererDataList[i];
if (rendererData == null ||
WasRendererDataReleasedEarlier(i))
{
continue;
}
rendererData.ReleaseRuntimeResourcesInstance();
}
}
private bool WasRendererDataReleasedEarlier(
int rendererDataIndex)
{
ScriptableRendererData rendererData =
rendererDataList[rendererDataIndex];
for (int i = 0; i < rendererDataIndex; ++i)
{
if (object.ReferenceEquals(
rendererDataList[i],
rendererData))
{
return true;
}
}
return false;
}
private int ResolveRendererIndex(
int rendererIndex)
{

View File

@@ -2499,6 +2499,337 @@ TEST_F(
secondRecorder->Shutdown();
}
TEST_F(
MonoScriptRuntimeTest,
ManagedRenderPipelineBridgeInvokesManagedPipelineDisposeBeforeAssetRuntimeRelease) {
Scene* runtimeScene =
CreateScene("ManagedLifecycleDisposeObservationScene");
GameObject* scriptObject =
runtimeScene->CreateGameObject("ManagedLifecycleObservationProbe");
ScriptComponent* script =
AddScript(
scriptObject,
"Gameplay",
"ManagedLifecycleObservationProbe");
ASSERT_NE(script, nullptr);
engine->OnRuntimeStart(runtimeScene);
engine->OnUpdate(0.016f);
{
const auto bridge =
XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge();
ASSERT_NE(bridge, nullptr);
const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = {
"GameScripts",
"Gameplay",
"ManagedPipelineDisposeProbeAsset"
};
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));
EXPECT_TRUE(
recorder->SupportsStageRenderGraph(
XCEngine::Rendering::CameraFrameStage::MainScene));
recorder->Shutdown();
}
engine->OnUpdate(0.016f);
EXPECT_TRUE(runtime->GetLastError().empty()) << runtime->GetLastError();
int observedCreatePipelineCallCount = 0;
int observedCreateRendererCallCount = 0;
int observedCreateFeatureCallCount = 0;
int observedReleaseRendererDataRuntimeResourcesCallCount = 0;
int observedDisposePipelineCallCount = 0;
int observedReleaseAssetRuntimeResourcesCallCount = 0;
int observedDisposeRendererCallCount = 0;
int observedDisposeFeatureCallCount = 0;
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedCreatePipelineCallCount",
observedCreatePipelineCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedCreateRendererCallCount",
observedCreateRendererCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedCreateFeatureCallCount",
observedCreateFeatureCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedReleaseRendererDataRuntimeResourcesCallCount",
observedReleaseRendererDataRuntimeResourcesCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedDisposePipelineCallCount",
observedDisposePipelineCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedReleaseAssetRuntimeResourcesCallCount",
observedReleaseAssetRuntimeResourcesCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedDisposeRendererCallCount",
observedDisposeRendererCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedDisposeFeatureCallCount",
observedDisposeFeatureCallCount));
EXPECT_EQ(observedCreatePipelineCallCount, 1);
EXPECT_EQ(observedCreateRendererCallCount, 0);
EXPECT_EQ(observedCreateFeatureCallCount, 0);
EXPECT_EQ(observedReleaseRendererDataRuntimeResourcesCallCount, 0);
EXPECT_EQ(observedDisposePipelineCallCount, 1);
EXPECT_EQ(observedReleaseAssetRuntimeResourcesCallCount, 0);
EXPECT_EQ(observedDisposeRendererCallCount, 0);
EXPECT_EQ(observedDisposeFeatureCallCount, 0);
}
TEST_F(
MonoScriptRuntimeTest,
ManagedRenderPipelineBridgeReleasesUniversalRendererCachesOnAssetRuntimeRelease) {
Scene* runtimeScene =
CreateScene("ManagedUniversalLifecycleObservationScene");
GameObject* scriptObject =
runtimeScene->CreateGameObject("ManagedUniversalLifecycleSelectionProbe");
ScriptComponent* script =
AddScript(
scriptObject,
"Gameplay",
"ManagedUniversalLifecycleRuntimeSelectionProbe");
ASSERT_NE(script, 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, "Gameplay");
EXPECT_EQ(descriptor.className, "ManagedUniversalLifecycleProbeAsset");
{
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));
EXPECT_TRUE(
recorder->SupportsStageRenderGraph(
XCEngine::Rendering::CameraFrameStage::MainScene));
recorder->Shutdown();
}
{
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));
EXPECT_TRUE(
recorder->SupportsStageRenderGraph(
XCEngine::Rendering::CameraFrameStage::MainScene));
recorder->Shutdown();
}
engine->OnUpdate(0.016f);
EXPECT_TRUE(runtime->GetLastError().empty()) << runtime->GetLastError();
int observedCreatePipelineCallCount = 0;
int observedCreateRendererCallCount = 0;
int observedCreateFeatureCallCount = 0;
int observedReleaseRendererDataRuntimeResourcesCallCount = 0;
int observedDisposePipelineCallCount = 0;
int observedReleaseAssetRuntimeResourcesCallCount = 0;
int observedDisposeRendererCallCount = 0;
int observedDisposeFeatureCallCount = 0;
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedCreatePipelineCallCount",
observedCreatePipelineCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedCreateRendererCallCount",
observedCreateRendererCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedCreateFeatureCallCount",
observedCreateFeatureCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedReleaseRendererDataRuntimeResourcesCallCount",
observedReleaseRendererDataRuntimeResourcesCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedDisposePipelineCallCount",
observedDisposePipelineCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedReleaseAssetRuntimeResourcesCallCount",
observedReleaseAssetRuntimeResourcesCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedDisposeRendererCallCount",
observedDisposeRendererCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedDisposeFeatureCallCount",
observedDisposeFeatureCallCount));
EXPECT_EQ(observedCreatePipelineCallCount, 2);
EXPECT_EQ(observedCreateRendererCallCount, 2);
EXPECT_EQ(observedCreateFeatureCallCount, 2);
EXPECT_EQ(observedReleaseRendererDataRuntimeResourcesCallCount, 2);
EXPECT_EQ(observedDisposePipelineCallCount, 0);
EXPECT_EQ(observedReleaseAssetRuntimeResourcesCallCount, 2);
}
TEST_F(
MonoScriptRuntimeTest,
ManagedRenderPipelineBridgeReleasesUniversalFeatureCachesWithoutPipelineCreation) {
Scene* runtimeScene =
CreateScene("ManagedUniversalLifecycleRequestObservationScene");
GameObject* scriptObject =
runtimeScene->CreateGameObject("ManagedUniversalLifecycleSelectionProbe");
ScriptComponent* script =
AddScript(
scriptObject,
"Gameplay",
"ManagedUniversalLifecycleRuntimeSelectionProbe");
ASSERT_NE(script, 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, "Gameplay");
EXPECT_EQ(descriptor.className, "ManagedUniversalLifecycleProbeAsset");
{
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);
XCEngine::Rendering::CameraRenderRequest request = {};
assetRuntime->ConfigureCameraRenderRequest(
request,
0u,
0u,
XCEngine::Rendering::DirectionalShadowPlanningSettings{});
}
{
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);
XCEngine::Rendering::CameraRenderRequest request = {};
assetRuntime->ConfigureCameraRenderRequest(
request,
0u,
0u,
XCEngine::Rendering::DirectionalShadowPlanningSettings{});
}
engine->OnUpdate(0.016f);
EXPECT_TRUE(runtime->GetLastError().empty()) << runtime->GetLastError();
int observedCreatePipelineCallCount = 0;
int observedCreateRendererCallCount = 0;
int observedCreateFeatureCallCount = 0;
int observedReleaseRendererDataRuntimeResourcesCallCount = 0;
int observedDisposePipelineCallCount = 0;
int observedReleaseAssetRuntimeResourcesCallCount = 0;
int observedDisposeRendererCallCount = 0;
int observedDisposeFeatureCallCount = 0;
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedCreatePipelineCallCount",
observedCreatePipelineCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedCreateRendererCallCount",
observedCreateRendererCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedCreateFeatureCallCount",
observedCreateFeatureCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedReleaseRendererDataRuntimeResourcesCallCount",
observedReleaseRendererDataRuntimeResourcesCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedDisposePipelineCallCount",
observedDisposePipelineCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedReleaseAssetRuntimeResourcesCallCount",
observedReleaseAssetRuntimeResourcesCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedDisposeRendererCallCount",
observedDisposeRendererCallCount));
EXPECT_TRUE(runtime->TryGetFieldValue(
script,
"ObservedDisposeFeatureCallCount",
observedDisposeFeatureCallCount));
EXPECT_EQ(observedCreatePipelineCallCount, 0);
EXPECT_EQ(observedCreateRendererCallCount, 0);
EXPECT_EQ(observedCreateFeatureCallCount, 2);
EXPECT_EQ(observedReleaseRendererDataRuntimeResourcesCallCount, 2);
EXPECT_EQ(observedDisposePipelineCallCount, 0);
EXPECT_EQ(observedReleaseAssetRuntimeResourcesCallCount, 2);
}
TEST_F(
MonoScriptRuntimeTest,
ManagedRenderPipelineBridgeRuntimeExposesBuiltinForwardRendererAsset) {