feat(scripting): add managed SRP runtime bridge
This commit is contained in:
306
docs/plan/SRP_Runtime_v1_2026-04-17.md
Normal file
306
docs/plan/SRP_Runtime_v1_2026-04-17.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# SRP Runtime v1 实施计划 2026-04-17
|
||||
|
||||
## 1. 结论
|
||||
|
||||
现在**可以正式进入 SRP 主线**了。
|
||||
|
||||
但这里的“开始搭 SRP”,准确含义不是:
|
||||
|
||||
1. 直接开做 `URP` 包层。
|
||||
2. 直接做延迟渲染。
|
||||
3. 直接把阴影、高斯、体积、后处理全部搬到 C#。
|
||||
|
||||
本阶段正确目标是:
|
||||
|
||||
`Native RenderGraph / Planning / Execution`
|
||||
`-> Managed SRP Runtime v1`
|
||||
`-> Managed Forward Pipeline v1`
|
||||
`-> 之后再开 URP-like Package`
|
||||
|
||||
也就是说,下一阶段确实是搭 SRP,但搭的是 **SRP Runtime v1**,不是直接搭“最终版 URP”。
|
||||
|
||||
---
|
||||
|
||||
## 2. 为什么现在可以开始做 SRP
|
||||
|
||||
当前 native 渲染层已经具备进入 SRP 主线的前提:
|
||||
|
||||
1. `RenderGraph` 已经可用,不是空壳。
|
||||
2. `CameraFramePlan` 已经把每相机渲染组织稳定下来。
|
||||
3. `CameraFrameStage` 和 stage dispatch 已经把主链拆清楚了。
|
||||
4. `BuiltinForwardPipeline` 已经能把 `MainScene` 录进 render graph。
|
||||
5. `ScriptableRenderPipelineHost` 已经提供了 `stage recorder + fallback renderer` 的宿主接缝。
|
||||
6. 默认 pipeline 工厂已经能从 `managed or default asset` 入口创建渲染管线。
|
||||
|
||||
所以当前真正缺的已经不是 native 渲染骨架,而是:
|
||||
|
||||
1. managed pipeline 在 runtime 里真实存在。
|
||||
2. native 能持有并调用 managed pipeline。
|
||||
3. managed 层拿到最小可用的渲染上下文。
|
||||
|
||||
换句话说,**native 这一段已经够了,下一刀就该切 SRP runtime**。
|
||||
|
||||
---
|
||||
|
||||
## 3. 本阶段目标
|
||||
|
||||
本阶段收口目标只有四个:
|
||||
|
||||
1. native 能创建并持有真实 managed `ScriptableRenderPipelineAsset` / `ScriptableRenderPipeline` 实例。
|
||||
2. `ScriptableRenderPipelineHost` 能稳定回调到 managed pipeline 参与 `MainScene` stage graph 录制。
|
||||
3. managed 拿到一个最小可用的 `ScriptableRenderContext v1`。
|
||||
4. 跑通一条最小 managed forward pipeline 主链,同时保留 builtin forward fallback。
|
||||
|
||||
如果这四件事没有形成闭环,就不算进入 SRP 主线。
|
||||
|
||||
---
|
||||
|
||||
## 4. 本阶段明确不做的事
|
||||
|
||||
这一阶段**明确不做**下面这些内容:
|
||||
|
||||
1. 不开 `URP-like package`。
|
||||
2. 不做 deferred pipeline。
|
||||
3. 不把阴影、后处理、高斯、体积的组织权全部搬到 C#。
|
||||
4. 不直接对外暴露原始 `RHI` 或原始 native internal type。
|
||||
5. 不追求第一版就完全对齐 Unity 的 `ScriptableRenderContext` 和 `CommandBuffer`。
|
||||
|
||||
原因很简单:
|
||||
|
||||
当前根问题不是“缺某个 renderer feature”,而是“managed pipeline 运行时还没真正成立”。
|
||||
|
||||
---
|
||||
|
||||
## 5. 本阶段的正确边界
|
||||
|
||||
### 留在 C++ 的
|
||||
|
||||
1. `RHI`
|
||||
2. `RenderGraph`
|
||||
3. frame planning / stage dispatch / graph compile / execute
|
||||
4. scene extraction / culling / frame data
|
||||
5. builtin renderer 的底层执行实现
|
||||
|
||||
### 开始上移到 managed 的
|
||||
|
||||
1. pipeline asset 的实例化和生命周期
|
||||
2. pipeline 的主场景录制组织
|
||||
3. 受控的 render context 封装
|
||||
4. 后续 `URP-like` 包层需要依赖的 SRP runtime 接缝
|
||||
|
||||
本阶段的核心原则是:
|
||||
|
||||
> C++ 继续负责渲染内核,C# 开始接管渲染组织。
|
||||
|
||||
---
|
||||
|
||||
## 6. 分阶段实施顺序
|
||||
|
||||
### Phase A: 建立通用 managed object runtime
|
||||
|
||||
目标:
|
||||
|
||||
1. 在 `MonoScriptRuntime` 中补齐“任意 managed 对象”的创建、持有、释放能力。
|
||||
2. 不再只围绕 `ScriptComponent`、`GameObject`、`Component` wrapper 打转。
|
||||
3. 能按类型描述创建 asset 实例和 pipeline 实例。
|
||||
4. 能稳定保存 `GCHandle` 或等价持有关系。
|
||||
5. 能稳定回调实例方法并正确处理异常。
|
||||
|
||||
这一阶段是整个 SRP runtime 的硬前提。
|
||||
|
||||
如果这一步不成立,后面一切 SRP API 都只是空壳。
|
||||
|
||||
### Phase B: 把 render pipeline asset 从“类型描述”升级成“真实 runtime 对象”
|
||||
|
||||
当前 `GraphicsSettings.renderPipelineAssetType` 的语义还是:
|
||||
|
||||
`把一个 C# 类型名写给 native`
|
||||
|
||||
这一阶段需要升级成:
|
||||
|
||||
1. native 能根据类型创建真实 managed asset 实例。
|
||||
2. managed asset 能创建真实 managed pipeline 实例。
|
||||
3. native 能持有 asset / pipeline 生命周期。
|
||||
4. `ManagedScriptableRenderPipelineAsset` 不再只是 descriptor-only 包装。
|
||||
|
||||
第一版允许继续用 `Type` 作为 bootstrap 入口,但 native 内部不能再停留在 descriptor 模型。
|
||||
|
||||
### Phase C: 实现真实的 managed render pipeline bridge
|
||||
|
||||
目标:
|
||||
|
||||
1. `ManagedRenderPipelineBridge` 由 `MonoScriptRuntime` 提供真实实现。
|
||||
2. `ManagedScriptableRenderPipelineAsset` 创建的 host 能绑定到真实 managed pipeline。
|
||||
3. `ScriptableRenderPipelineHost` 能调用 managed pipeline 的 stage 录制方法。
|
||||
4. bridge 不再只是测试用 mock seam。
|
||||
|
||||
这一阶段完成后,managed pipeline 才算真正“活过来”。
|
||||
|
||||
### Phase D: 定义 `ScriptableRenderContext v1`
|
||||
|
||||
第一版不要追求大而全。
|
||||
|
||||
只需要提供最小可用能力:
|
||||
|
||||
1. 查询当前 camera frame / stage 基本信息。
|
||||
2. 参与 `MainScene` render graph 录制。
|
||||
3. 调用 native scene renderer 绘制 `Opaque / Skybox / Transparent`。
|
||||
4. 访问最基础的 source / target / blackboard 语义。
|
||||
5. 预留 fullscreen / blit 接口,但不强求一步到位。
|
||||
|
||||
这一版不直接暴露 raw `RenderGraphTextureHandle` 和 raw `RenderSurface` 给用户脚本。
|
||||
|
||||
### Phase E: 跑通最小 managed forward pipeline v1
|
||||
|
||||
目标:
|
||||
|
||||
1. 用 C# 创建一个 managed pipeline。
|
||||
2. managed pipeline 能声明支持 `MainScene` stage graph。
|
||||
3. managed pipeline 能在 `MainScene` 调 native forward scene renderer。
|
||||
4. editor / runtime 在切到 managed pipeline 后仍能正常渲染。
|
||||
5. builtin forward 仍可作为 fallback 路径保留。
|
||||
|
||||
这一阶段完成后,才算正式进入 “SRP 已经开始工作” 的状态。
|
||||
|
||||
---
|
||||
|
||||
## 7. 第一批要改的文件
|
||||
|
||||
### Native 侧核心落点
|
||||
|
||||
1. `engine/src/Scripting/Mono/MonoScriptRuntime.cpp`
|
||||
2. `engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h`
|
||||
3. `engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp`
|
||||
4. `engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h`
|
||||
5. `engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp`
|
||||
6. `engine/src/Rendering/Internal/RenderPipelineFactory.cpp`
|
||||
|
||||
### Managed 侧核心落点
|
||||
|
||||
1. `managed/XCEngine.ScriptCore/InternalCalls.cs`
|
||||
2. `managed/XCEngine.ScriptCore/GraphicsSettings.cs`
|
||||
3. `managed/XCEngine.ScriptCore/ScriptableRenderPipelineAsset.cs`
|
||||
4. `managed/XCEngine.ScriptCore/ScriptableRenderPipeline.cs`
|
||||
5. 新增 `managed/XCEngine.ScriptCore/ScriptableRenderContext.cs`
|
||||
6. 可能新增最小 `managed/XCEngine.ScriptCore/Rendering/*` 辅助类型
|
||||
|
||||
### 测试与验证
|
||||
|
||||
1. `managed/GameScripts/RenderPipelineApiProbe.cs`
|
||||
2. 新增 native / managed bridge smoke tests
|
||||
3. editor 冒烟场景验证
|
||||
|
||||
---
|
||||
|
||||
## 8. 代码层面的实施拆分
|
||||
|
||||
建议按下面的提交粒度推进,而不是一口气乱改:
|
||||
|
||||
### Commit 1: managed object runtime 基础设施
|
||||
|
||||
完成后应该能:
|
||||
|
||||
1. 按类型创建普通 managed 对象。
|
||||
2. 持有并释放对象句柄。
|
||||
3. 回调实例方法。
|
||||
|
||||
### Commit 2: managed pipeline asset / pipeline 生命周期
|
||||
|
||||
完成后应该能:
|
||||
|
||||
1. 从 `GraphicsSettings.renderPipelineAssetType` 生成真实 asset 实例。
|
||||
2. 从 asset 生成真实 pipeline 实例。
|
||||
3. 由 native 负责持有这两个对象。
|
||||
|
||||
### Commit 3: host 与 managed bridge 接通
|
||||
|
||||
完成后应该能:
|
||||
|
||||
1. `ScriptableRenderPipelineHost` 绑定真实 managed stage recorder。
|
||||
2. `MainScene` 录制可回调到 managed pipeline。
|
||||
|
||||
### Commit 4: `ScriptableRenderContext v1`
|
||||
|
||||
完成后应该能:
|
||||
|
||||
1. managed 侧拿到最小上下文。
|
||||
2. managed 能调用 native scene renderer 画主场景阶段。
|
||||
|
||||
### Commit 5: managed forward pipeline v1 smoke path
|
||||
|
||||
完成后应该能:
|
||||
|
||||
1. 用 managed pipeline 跑通主场景。
|
||||
2. 与 builtin forward fallback 共存。
|
||||
|
||||
---
|
||||
|
||||
## 9. 关键风险
|
||||
|
||||
### 风险 1: Mono 生命周期和 GC handle 泄漏
|
||||
|
||||
这是第一风险。
|
||||
|
||||
如果 object runtime 持有策略不稳,后面渲染管线切换、domain reload、editor 热切换都会出问题。
|
||||
|
||||
### 风险 2: 把 host 做成“全局单例桥”
|
||||
|
||||
如果 bridge 继续停留在粗糙全局状态,后面多实例、多场景、重建 pipeline 都会很难收口。
|
||||
|
||||
### 风险 3: 过早暴露原始 native 类型
|
||||
|
||||
如果第一版就把 raw graph/resource handle 暴露给 managed,API 很快会固化,后面很难收拾。
|
||||
|
||||
### 风险 4: builtin forward 与 managed forward 边界不清
|
||||
|
||||
如果 managed 直接依赖 `BuiltinForwardPipeline` 私有实现细节,后面就不是 SRP,而是“脚本包一层 builtin forward”。
|
||||
|
||||
---
|
||||
|
||||
## 10. 阶段验收标准
|
||||
|
||||
本阶段收口时,必须同时满足下面这些条件:
|
||||
|
||||
1. native 里存在真实的 managed pipeline bridge,不再只是 mock seam。
|
||||
2. `GraphicsSettings` 能驱动创建真实 managed asset / pipeline 实例。
|
||||
3. `ScriptableRenderPipelineHost` 能稳定回调 managed pipeline 的 `MainScene` 录制。
|
||||
4. managed 侧存在最小 `ScriptableRenderContext v1`。
|
||||
5. 一条最小 managed forward pipeline 能真实跑出主场景。
|
||||
6. builtin forward fallback 路径仍然可用。
|
||||
7. editor 编译通过,基础冒烟渲染不崩。
|
||||
|
||||
只要这 7 条有一条不成立,这一阶段就不能算收口。
|
||||
|
||||
---
|
||||
|
||||
## 11. 本阶段完成后,下一阶段才做什么
|
||||
|
||||
等 `SRP Runtime v1` 收口之后,下一阶段才适合开始:
|
||||
|
||||
1. `URP-like asset`
|
||||
2. `RendererData`
|
||||
3. `RendererFeature`
|
||||
4. `RenderPassEvent`
|
||||
5. 阴影 / 后处理 / 高斯 / 体积 等组织权逐步上移
|
||||
|
||||
也就是说:
|
||||
|
||||
**现在可以开始搭 SRP。**
|
||||
|
||||
但准确说法应该是:
|
||||
|
||||
**现在开始搭的是 `SRP Runtime v1`,不是直接搭最终版 URP。**
|
||||
|
||||
---
|
||||
|
||||
## 12. 下一刀从哪里下
|
||||
|
||||
下一步第一刀就应该下在:
|
||||
|
||||
`MonoScriptRuntime`
|
||||
`-> 通用 managed object runtime`
|
||||
`-> 真实 ManagedRenderPipelineBridge`
|
||||
|
||||
这是整个 SRP 主线最根上的卡点。
|
||||
|
||||
如果这里不先解决,后面无论做多少 `ScriptableRenderPipelineAsset`、`ScriptableRenderContext`、`RendererFeature`,都只是外壳。
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -21,6 +22,8 @@ namespace XCEngine {
|
||||
namespace Scripting {
|
||||
|
||||
class ScriptComponent;
|
||||
class MonoManagedRenderPipelineBridge;
|
||||
class MonoManagedRenderPipelineStageRecorder;
|
||||
|
||||
class MonoScriptRuntime : public IScriptRuntime {
|
||||
public:
|
||||
@@ -156,6 +159,11 @@ private:
|
||||
uint32_t gcHandle = 0;
|
||||
};
|
||||
|
||||
struct ExternalManagedObjectData {
|
||||
MonoClass* monoClass = nullptr;
|
||||
uint32_t gcHandle = 0;
|
||||
};
|
||||
|
||||
void ResolveSettings();
|
||||
bool InitializeRootDomain();
|
||||
bool CreateAppDomain();
|
||||
@@ -195,11 +203,43 @@ private:
|
||||
bool TryReadFieldValue(MonoObject* instance, const FieldMetadata& fieldMetadata, ScriptFieldValue& outValue) const;
|
||||
|
||||
void ClearManagedInstances();
|
||||
void ClearExternalManagedObjects();
|
||||
void ClearClassCache();
|
||||
bool InvokeManagedMethod(MonoObject* instance, MonoMethod* method, void** args = nullptr);
|
||||
bool ResolveManagedClass(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className,
|
||||
MonoClass*& outClass) const;
|
||||
bool CreateExternalManagedObject(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className,
|
||||
uint32_t& outHandle);
|
||||
bool CreateExternalManagedObject(
|
||||
MonoClass* monoClass,
|
||||
uint32_t& outHandle);
|
||||
uint32_t RetainExternalManagedObject(MonoObject* instance);
|
||||
void DestroyExternalManagedObject(uint32_t gcHandle);
|
||||
MonoObject* GetManagedObject(uint32_t gcHandle) const;
|
||||
MonoMethod* ResolveManagedMethod(
|
||||
MonoClass* monoClass,
|
||||
const char* methodName,
|
||||
int parameterCount) const;
|
||||
MonoMethod* ResolveManagedMethod(
|
||||
MonoObject* instance,
|
||||
const char* methodName,
|
||||
int parameterCount) const;
|
||||
bool InvokeManagedMethod(
|
||||
MonoObject* instance,
|
||||
MonoMethod* method,
|
||||
void** args = nullptr,
|
||||
MonoObject** outReturnValue = nullptr);
|
||||
void RecordException(MonoObject* exception);
|
||||
void SetError(const std::string& error);
|
||||
|
||||
friend class MonoManagedRenderPipelineBridge;
|
||||
friend class MonoManagedRenderPipelineStageRecorder;
|
||||
|
||||
Settings m_settings;
|
||||
Components::Scene* m_activeScene = nullptr;
|
||||
|
||||
@@ -215,6 +255,8 @@ private:
|
||||
MonoClass* m_behaviourClass = nullptr;
|
||||
MonoClass* m_gameObjectClass = nullptr;
|
||||
MonoClass* m_monoBehaviourClass = nullptr;
|
||||
MonoClass* m_scriptableRenderPipelineAssetClass = nullptr;
|
||||
MonoClass* m_scriptableRenderPipelineClass = nullptr;
|
||||
MonoClass* m_serializeFieldAttributeClass = nullptr;
|
||||
MonoMethod* m_gameObjectConstructor = nullptr;
|
||||
MonoClassField* m_managedGameObjectUUIDField = nullptr;
|
||||
@@ -223,6 +265,8 @@ private:
|
||||
|
||||
std::unordered_map<std::string, ClassMetadata> m_classes;
|
||||
std::unordered_map<InstanceKey, InstanceData, InstanceKeyHasher> m_instances;
|
||||
std::unordered_map<uint32_t, ExternalManagedObjectData> m_externalManagedObjects;
|
||||
std::shared_ptr<void> m_runtimeLifetimeToken;
|
||||
};
|
||||
|
||||
} // namespace Scripting
|
||||
|
||||
@@ -109,7 +109,9 @@ bool ScriptableRenderPipelineHost::RecordStageRenderGraph(
|
||||
|
||||
if (m_stageRecorder != nullptr &&
|
||||
m_stageRecorder->SupportsStageRenderGraph(context.stage)) {
|
||||
return m_stageRecorder->RecordStageRenderGraph(context);
|
||||
if (m_stageRecorder->RecordStageRenderGraph(context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return m_pipelineRenderer != nullptr &&
|
||||
|
||||
@@ -148,6 +148,272 @@ MonoScriptRuntime* GetActiveMonoScriptRuntime() {
|
||||
return dynamic_cast<MonoScriptRuntime*>(ScriptEngine::Get().GetRuntime());
|
||||
}
|
||||
|
||||
bool TryUnboxManagedBoolean(MonoObject* boxedValue, bool& outValue) {
|
||||
outValue = false;
|
||||
if (!boxedValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void* const rawValue = mono_object_unbox(boxedValue);
|
||||
if (!rawValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = (*static_cast<mono_bool*>(rawValue)) != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class MonoManagedRenderPipelineStageRecorder final
|
||||
: public Rendering::RenderPipelineStageRecorder {
|
||||
public:
|
||||
MonoManagedRenderPipelineStageRecorder(
|
||||
MonoScriptRuntime* runtime,
|
||||
std::weak_ptr<void> runtimeLifetime,
|
||||
Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor)
|
||||
: m_runtime(runtime)
|
||||
, m_runtimeLifetime(std::move(runtimeLifetime))
|
||||
, m_descriptor(std::move(descriptor)) {
|
||||
}
|
||||
|
||||
~MonoManagedRenderPipelineStageRecorder() override {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool Initialize(const Rendering::RenderContext&) override {
|
||||
return EnsureManagedPipeline();
|
||||
}
|
||||
|
||||
void Shutdown() override {
|
||||
ReleaseManagedObjects();
|
||||
m_supportsStageMethod = nullptr;
|
||||
m_recordStageMethod = nullptr;
|
||||
m_pipelineCreationAttempted = false;
|
||||
}
|
||||
|
||||
bool SupportsStageRenderGraph(Rendering::CameraFrameStage stage) const override {
|
||||
if (!Rendering::SupportsCameraFramePipelineGraphRecording(stage) ||
|
||||
!EnsureManagedPipeline()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoObject* const pipelineObject =
|
||||
m_runtime->GetManagedObject(m_pipelineHandle);
|
||||
MonoMethod* const method = ResolveSupportsStageMethod(pipelineObject);
|
||||
if (!pipelineObject || !method) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t managedStage = static_cast<int32_t>(stage);
|
||||
void* args[1] = { &managedStage };
|
||||
MonoObject* result = nullptr;
|
||||
if (!m_runtime->InvokeManagedMethod(
|
||||
pipelineObject,
|
||||
method,
|
||||
args,
|
||||
&result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool supportsStage = false;
|
||||
return TryUnboxManagedBoolean(result, supportsStage) && supportsStage;
|
||||
}
|
||||
|
||||
bool RecordStageRenderGraph(
|
||||
const Rendering::RenderPipelineStageRenderGraphContext& context) override {
|
||||
if (!EnsureManagedPipeline()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoObject* const pipelineObject =
|
||||
m_runtime->GetManagedObject(m_pipelineHandle);
|
||||
MonoMethod* const method = ResolveRecordStageMethod(pipelineObject);
|
||||
if (!pipelineObject || !method) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t managedStage = static_cast<int32_t>(context.stage);
|
||||
void* args[1] = { &managedStage };
|
||||
MonoObject* result = nullptr;
|
||||
if (!m_runtime->InvokeManagedMethod(
|
||||
pipelineObject,
|
||||
method,
|
||||
args,
|
||||
&result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool recorded = false;
|
||||
return TryUnboxManagedBoolean(result, recorded) && recorded;
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsRuntimeAlive() const {
|
||||
return m_runtime != nullptr &&
|
||||
!m_runtimeLifetime.expired() &&
|
||||
m_runtime->m_initialized;
|
||||
}
|
||||
|
||||
bool EnsureManagedPipeline() const {
|
||||
if (m_pipelineHandle != 0) {
|
||||
return true;
|
||||
}
|
||||
if (m_pipelineCreationAttempted || !IsRuntimeAlive() ||
|
||||
!m_descriptor.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pipelineCreationAttempted = true;
|
||||
|
||||
MonoClass* assetClass = nullptr;
|
||||
if (!m_runtime->ResolveManagedClass(
|
||||
m_descriptor.assemblyName,
|
||||
m_descriptor.namespaceName,
|
||||
m_descriptor.className,
|
||||
assetClass) ||
|
||||
assetClass == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsMonoClassOrSubclass(
|
||||
assetClass,
|
||||
m_runtime->m_scriptableRenderPipelineAssetClass)) {
|
||||
m_runtime->SetError(
|
||||
"Managed render pipeline asset must derive from ScriptableRenderPipelineAsset: " +
|
||||
m_descriptor.GetFullName() + ".");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t assetHandle = 0;
|
||||
if (!m_runtime->CreateExternalManagedObject(assetClass, assetHandle) ||
|
||||
assetHandle == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoObject* const assetObject = m_runtime->GetManagedObject(assetHandle);
|
||||
MonoMethod* const createPipelineMethod =
|
||||
m_runtime->ResolveManagedMethod(assetObject, "CreatePipeline", 0);
|
||||
if (!assetObject || !createPipelineMethod) {
|
||||
m_runtime->DestroyExternalManagedObject(assetHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoObject* pipelineObject = nullptr;
|
||||
if (!m_runtime->InvokeManagedMethod(
|
||||
assetObject,
|
||||
createPipelineMethod,
|
||||
nullptr,
|
||||
&pipelineObject) ||
|
||||
pipelineObject == nullptr) {
|
||||
m_runtime->DestroyExternalManagedObject(assetHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsMonoClassOrSubclass(
|
||||
mono_object_get_class(pipelineObject),
|
||||
m_runtime->m_scriptableRenderPipelineClass)) {
|
||||
m_runtime->SetError(
|
||||
"Managed render pipeline asset returned a non-ScriptableRenderPipeline instance: " +
|
||||
m_descriptor.GetFullName() + ".");
|
||||
m_runtime->DestroyExternalManagedObject(assetHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t pipelineHandle =
|
||||
m_runtime->RetainExternalManagedObject(pipelineObject);
|
||||
if (pipelineHandle == 0) {
|
||||
m_runtime->DestroyExternalManagedObject(assetHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_assetHandle = assetHandle;
|
||||
m_pipelineHandle = pipelineHandle;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReleaseManagedObjects() {
|
||||
if (!IsRuntimeAlive()) {
|
||||
m_assetHandle = 0;
|
||||
m_pipelineHandle = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_pipelineHandle != 0) {
|
||||
m_runtime->DestroyExternalManagedObject(m_pipelineHandle);
|
||||
m_pipelineHandle = 0;
|
||||
}
|
||||
if (m_assetHandle != 0) {
|
||||
m_runtime->DestroyExternalManagedObject(m_assetHandle);
|
||||
m_assetHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
MonoMethod* ResolveSupportsStageMethod(MonoObject* pipelineObject) const {
|
||||
if (m_supportsStageMethod == nullptr) {
|
||||
m_supportsStageMethod =
|
||||
m_runtime->ResolveManagedMethod(
|
||||
pipelineObject,
|
||||
"SupportsStageRenderGraph",
|
||||
1);
|
||||
}
|
||||
|
||||
return m_supportsStageMethod;
|
||||
}
|
||||
|
||||
MonoMethod* ResolveRecordStageMethod(MonoObject* pipelineObject) const {
|
||||
if (m_recordStageMethod == nullptr) {
|
||||
m_recordStageMethod =
|
||||
m_runtime->ResolveManagedMethod(
|
||||
pipelineObject,
|
||||
"RecordStageRenderGraph",
|
||||
1);
|
||||
}
|
||||
|
||||
return m_recordStageMethod;
|
||||
}
|
||||
|
||||
MonoScriptRuntime* m_runtime = nullptr;
|
||||
std::weak_ptr<void> m_runtimeLifetime;
|
||||
Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor m_descriptor;
|
||||
mutable uint32_t m_assetHandle = 0;
|
||||
mutable uint32_t m_pipelineHandle = 0;
|
||||
mutable MonoMethod* m_supportsStageMethod = nullptr;
|
||||
mutable MonoMethod* m_recordStageMethod = nullptr;
|
||||
mutable bool m_pipelineCreationAttempted = false;
|
||||
};
|
||||
|
||||
class MonoManagedRenderPipelineBridge final
|
||||
: public Rendering::Pipelines::ManagedRenderPipelineBridge {
|
||||
public:
|
||||
MonoManagedRenderPipelineBridge(
|
||||
MonoScriptRuntime* runtime,
|
||||
std::weak_ptr<void> runtimeLifetime)
|
||||
: m_runtime(runtime)
|
||||
, m_runtimeLifetime(std::move(runtimeLifetime)) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Rendering::RenderPipelineStageRecorder> CreateStageRecorder(
|
||||
const Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor& descriptor) const override {
|
||||
if (m_runtime == nullptr ||
|
||||
m_runtimeLifetime.expired() ||
|
||||
!descriptor.IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<MonoManagedRenderPipelineStageRecorder>(
|
||||
m_runtime,
|
||||
m_runtimeLifetime,
|
||||
descriptor);
|
||||
}
|
||||
|
||||
private:
|
||||
MonoScriptRuntime* m_runtime = nullptr;
|
||||
std::weak_ptr<void> m_runtimeLifetime;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
ManagedComponentTypeInfo ResolveManagedComponentTypeInfo(MonoClass* monoClass) {
|
||||
ManagedComponentTypeInfo typeInfo;
|
||||
if (!monoClass) {
|
||||
@@ -2003,12 +2269,19 @@ bool MonoScriptRuntime::Initialize() {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_runtimeLifetimeToken = std::make_shared<int>(0);
|
||||
m_initialized = true;
|
||||
Rendering::Pipelines::SetManagedRenderPipelineBridge(
|
||||
std::make_shared<MonoManagedRenderPipelineBridge>(
|
||||
this,
|
||||
m_runtimeLifetimeToken));
|
||||
return true;
|
||||
}
|
||||
|
||||
void MonoScriptRuntime::Shutdown() {
|
||||
Rendering::Pipelines::ClearManagedRenderPipelineBridge();
|
||||
ClearManagedInstances();
|
||||
ClearExternalManagedObjects();
|
||||
ClearClassCache();
|
||||
|
||||
m_coreAssembly = nullptr;
|
||||
@@ -2019,6 +2292,8 @@ void MonoScriptRuntime::Shutdown() {
|
||||
m_behaviourClass = nullptr;
|
||||
m_gameObjectClass = nullptr;
|
||||
m_monoBehaviourClass = nullptr;
|
||||
m_scriptableRenderPipelineAssetClass = nullptr;
|
||||
m_scriptableRenderPipelineClass = nullptr;
|
||||
m_serializeFieldAttributeClass = nullptr;
|
||||
m_gameObjectConstructor = nullptr;
|
||||
m_managedGameObjectUUIDField = nullptr;
|
||||
@@ -2030,6 +2305,7 @@ void MonoScriptRuntime::Shutdown() {
|
||||
m_activeScene = nullptr;
|
||||
GetInternalCallScene() = nullptr;
|
||||
GetInternalCallDeltaTime() = 0.0f;
|
||||
m_runtimeLifetimeToken.reset();
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
@@ -2614,6 +2890,24 @@ bool MonoScriptRuntime::DiscoverScriptClasses() {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_scriptableRenderPipelineAssetClass = mono_class_from_name(
|
||||
m_coreImage,
|
||||
m_settings.baseNamespace.c_str(),
|
||||
"ScriptableRenderPipelineAsset");
|
||||
if (!m_scriptableRenderPipelineAssetClass) {
|
||||
SetError("Failed to locate the managed ScriptableRenderPipelineAsset base type.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_scriptableRenderPipelineClass = mono_class_from_name(
|
||||
m_coreImage,
|
||||
m_settings.baseNamespace.c_str(),
|
||||
"ScriptableRenderPipeline");
|
||||
if (!m_scriptableRenderPipelineClass) {
|
||||
SetError("Failed to locate the managed ScriptableRenderPipeline base type.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_serializeFieldAttributeClass = mono_class_from_name(
|
||||
m_coreImage,
|
||||
m_settings.baseNamespace.c_str(),
|
||||
@@ -2917,6 +3211,117 @@ const MonoScriptRuntime::ClassMetadata* MonoScriptRuntime::FindClassMetadata(
|
||||
return it != m_classes.end() ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::ResolveManagedClass(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className,
|
||||
MonoClass*& outClass) const {
|
||||
outClass = nullptr;
|
||||
if (!m_initialized || assemblyName.empty() || className.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoImage* image = nullptr;
|
||||
if (assemblyName == m_settings.coreAssemblyName) {
|
||||
image = m_coreImage;
|
||||
} else if (assemblyName == m_settings.appAssemblyName) {
|
||||
image = m_appImage;
|
||||
}
|
||||
|
||||
if (!image) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
outClass = mono_class_from_name(
|
||||
image,
|
||||
namespaceName.c_str(),
|
||||
className.c_str());
|
||||
return outClass != nullptr;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::CreateExternalManagedObject(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className,
|
||||
uint32_t& outHandle) {
|
||||
outHandle = 0;
|
||||
|
||||
MonoClass* monoClass = nullptr;
|
||||
if (!ResolveManagedClass(
|
||||
assemblyName,
|
||||
namespaceName,
|
||||
className,
|
||||
monoClass)) {
|
||||
SetError(
|
||||
"Managed class was not found: " +
|
||||
assemblyName + "|" +
|
||||
BuildFullClassName(namespaceName, className));
|
||||
return false;
|
||||
}
|
||||
|
||||
return CreateExternalManagedObject(monoClass, outHandle);
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::CreateExternalManagedObject(
|
||||
MonoClass* monoClass,
|
||||
uint32_t& outHandle) {
|
||||
outHandle = 0;
|
||||
if (!m_initialized || !monoClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
|
||||
MonoObject* const instance = mono_object_new(m_appDomain, monoClass);
|
||||
if (!instance) {
|
||||
SetError(
|
||||
"Mono failed to allocate a managed object for " +
|
||||
BuildFullClassName(
|
||||
SafeString(mono_class_get_namespace(monoClass)),
|
||||
SafeString(mono_class_get_name(monoClass))) + ".");
|
||||
return false;
|
||||
}
|
||||
|
||||
mono_runtime_object_init(instance);
|
||||
outHandle = RetainExternalManagedObject(instance);
|
||||
return outHandle != 0;
|
||||
}
|
||||
|
||||
uint32_t MonoScriptRuntime::RetainExternalManagedObject(MonoObject* instance) {
|
||||
if (!m_initialized || !instance) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
|
||||
const uint32_t gcHandle = mono_gchandle_new(instance, false);
|
||||
if (gcHandle == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
m_externalManagedObjects[gcHandle] = ExternalManagedObjectData{
|
||||
mono_object_get_class(instance),
|
||||
gcHandle
|
||||
};
|
||||
return gcHandle;
|
||||
}
|
||||
|
||||
void MonoScriptRuntime::DestroyExternalManagedObject(uint32_t gcHandle) {
|
||||
if (gcHandle == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto it = m_externalManagedObjects.find(gcHandle);
|
||||
if (it == m_externalManagedObjects.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
mono_gchandle_free(gcHandle);
|
||||
m_externalManagedObjects.erase(it);
|
||||
}
|
||||
|
||||
MonoScriptRuntime::InstanceData* MonoScriptRuntime::FindInstance(const ScriptRuntimeContext& context) {
|
||||
const auto it = m_instances.find(InstanceKey{context.gameObjectUUID, context.scriptComponentUUID});
|
||||
return it != m_instances.end() ? &it->second : nullptr;
|
||||
@@ -2948,6 +3353,43 @@ MonoObject* MonoScriptRuntime::GetManagedObject(const InstanceData& instanceData
|
||||
return mono_gchandle_get_target(instanceData.gcHandle);
|
||||
}
|
||||
|
||||
MonoObject* MonoScriptRuntime::GetManagedObject(uint32_t gcHandle) const {
|
||||
if (gcHandle == 0 ||
|
||||
m_externalManagedObjects.find(gcHandle) == m_externalManagedObjects.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
return mono_gchandle_get_target(gcHandle);
|
||||
}
|
||||
|
||||
MonoMethod* MonoScriptRuntime::ResolveManagedMethod(
|
||||
MonoClass* monoClass,
|
||||
const char* methodName,
|
||||
int parameterCount) const {
|
||||
if (!monoClass || !methodName) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
return mono_class_get_method_from_name(
|
||||
monoClass,
|
||||
methodName,
|
||||
parameterCount);
|
||||
}
|
||||
|
||||
MonoMethod* MonoScriptRuntime::ResolveManagedMethod(
|
||||
MonoObject* instance,
|
||||
const char* methodName,
|
||||
int parameterCount) const {
|
||||
return instance != nullptr
|
||||
? ResolveManagedMethod(
|
||||
mono_object_get_class(instance),
|
||||
methodName,
|
||||
parameterCount)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::ApplyContextFields(const ScriptRuntimeContext& context, MonoObject* instance) {
|
||||
if (!instance) {
|
||||
return false;
|
||||
@@ -3552,24 +3994,49 @@ void MonoScriptRuntime::ClearManagedInstances() {
|
||||
m_instances.clear();
|
||||
}
|
||||
|
||||
void MonoScriptRuntime::ClearExternalManagedObjects() {
|
||||
for (auto& [gcHandle, objectData] : m_externalManagedObjects) {
|
||||
(void)objectData;
|
||||
if (gcHandle != 0) {
|
||||
mono_gchandle_free(gcHandle);
|
||||
}
|
||||
}
|
||||
|
||||
m_externalManagedObjects.clear();
|
||||
}
|
||||
|
||||
void MonoScriptRuntime::ClearClassCache() {
|
||||
m_classes.clear();
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::InvokeManagedMethod(MonoObject* instance, MonoMethod* method, void** args) {
|
||||
bool MonoScriptRuntime::InvokeManagedMethod(
|
||||
MonoObject* instance,
|
||||
MonoMethod* method,
|
||||
void** args,
|
||||
MonoObject** outReturnValue) {
|
||||
if (!instance || !method) {
|
||||
if (outReturnValue) {
|
||||
*outReturnValue = nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
|
||||
MonoObject* exception = nullptr;
|
||||
mono_runtime_invoke(method, instance, args, &exception);
|
||||
MonoObject* returnValue = mono_runtime_invoke(method, instance, args, &exception);
|
||||
if (exception) {
|
||||
if (outReturnValue) {
|
||||
*outReturnValue = nullptr;
|
||||
}
|
||||
RecordException(exception);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outReturnValue) {
|
||||
*outReturnValue = returnValue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,37 @@ namespace Gameplay
|
||||
{
|
||||
}
|
||||
|
||||
public sealed class ManagedRenderPipelineProbeAsset : ScriptableRenderPipelineAsset
|
||||
{
|
||||
public static int CreatePipelineCallCount;
|
||||
|
||||
protected override ScriptableRenderPipeline CreatePipeline()
|
||||
{
|
||||
CreatePipelineCallCount++;
|
||||
return new ManagedRenderPipelineProbe();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ManagedRenderPipelineProbe : ScriptableRenderPipeline
|
||||
{
|
||||
public static int SupportsStageCallCount;
|
||||
public static int RecordStageCallCount;
|
||||
|
||||
protected override bool SupportsStageRenderGraph(
|
||||
CameraFrameStage stage)
|
||||
{
|
||||
SupportsStageCallCount++;
|
||||
return stage == CameraFrameStage.MainScene;
|
||||
}
|
||||
|
||||
protected override bool RecordStageRenderGraph(
|
||||
CameraFrameStage stage)
|
||||
{
|
||||
RecordStageCallCount++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RenderPipelineApiProbe : MonoBehaviour
|
||||
{
|
||||
public bool InitialTypeWasNull;
|
||||
|
||||
@@ -4253,6 +4253,60 @@ TEST(ScriptableRenderPipelineHost_Test, PrefersStageRecorderBeforeFallbackRender
|
||||
EXPECT_EQ(replacementRecorderState->shutdownCalls, 1);
|
||||
}
|
||||
|
||||
TEST(
|
||||
ScriptableRenderPipelineHost_Test,
|
||||
FallsBackToRendererWhenStageRecorderDeclinesRecording) {
|
||||
auto rendererState = std::make_shared<MockPipelineState>();
|
||||
auto recorderState = std::make_shared<MockStageRecorderState>();
|
||||
|
||||
rendererState->supportsMainSceneRenderGraph = true;
|
||||
recorderState->supportsMainSceneRenderGraph = true;
|
||||
recorderState->recordMainSceneResult = false;
|
||||
|
||||
Pipelines::ScriptableRenderPipelineHost host(
|
||||
std::make_unique<MockPipeline>(rendererState));
|
||||
host.SetStageRecorder(
|
||||
std::make_unique<MockStageRecorder>(recorderState));
|
||||
|
||||
Scene scene("ScriptableRenderPipelineFallbackScene");
|
||||
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
||||
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
||||
camera->SetPrimary(true);
|
||||
camera->SetDepth(1.0f);
|
||||
|
||||
const RenderContext context = CreateValidContext();
|
||||
const RenderSurface surface(800, 600);
|
||||
RenderSceneData sceneData =
|
||||
CreateSceneDataForCamera(scene, *camera, surface);
|
||||
RenderGraph graph = {};
|
||||
RenderGraphBuilder graphBuilder(graph);
|
||||
RenderGraphBlackboard blackboard = {};
|
||||
bool executionSucceeded = true;
|
||||
const RenderPipelineStageRenderGraphContext graphContext = {
|
||||
graphBuilder,
|
||||
"MainScene",
|
||||
CameraFrameStage::MainScene,
|
||||
context,
|
||||
sceneData,
|
||||
surface,
|
||||
nullptr,
|
||||
nullptr,
|
||||
XCEngine::RHI::ResourceStates::Common,
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
&executionSucceeded,
|
||||
&blackboard
|
||||
};
|
||||
|
||||
EXPECT_TRUE(host.SupportsStageRenderGraph(CameraFrameStage::MainScene));
|
||||
EXPECT_TRUE(host.RecordStageRenderGraph(graphContext));
|
||||
EXPECT_EQ(recorderState->recordMainSceneCalls, 1);
|
||||
EXPECT_EQ(rendererState->recordMainSceneCalls, 1);
|
||||
EXPECT_TRUE(recorderState->lastReceivedRenderGraphBlackboard);
|
||||
EXPECT_TRUE(rendererState->lastReceivedRenderGraphBlackboard);
|
||||
}
|
||||
|
||||
TEST(ScriptableRenderPipelineHost_Test, DirectHostDoesNotInstallBuiltinStandaloneStagePasses) {
|
||||
auto rendererState = std::make_shared<MockPipelineState>();
|
||||
Pipelines::ScriptableRenderPipelineHost host(
|
||||
|
||||
Reference in New Issue
Block a user