feat(scripting): add managed SRP runtime bridge

This commit is contained in:
2026-04-17 21:38:22 +08:00
parent 8f847db816
commit f4fb859972
6 changed files with 908 additions and 4 deletions

View File

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