diff --git a/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h b/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h index 369d9d59..9bfd032e 100644 --- a/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h +++ b/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h @@ -220,6 +220,7 @@ private: uint32_t& outHandle); uint32_t RetainExternalManagedObject(MonoObject* instance); void DestroyExternalManagedObject(uint32_t gcHandle); + MonoObject* CreateManagedScriptableRenderContext(uint64_t nativeHandle); MonoObject* GetManagedObject(uint32_t gcHandle) const; MonoMethod* ResolveManagedMethod( MonoClass* monoClass, @@ -257,8 +258,10 @@ private: MonoClass* m_monoBehaviourClass = nullptr; MonoClass* m_scriptableRenderPipelineAssetClass = nullptr; MonoClass* m_scriptableRenderPipelineClass = nullptr; + MonoClass* m_scriptableRenderContextClass = nullptr; MonoClass* m_serializeFieldAttributeClass = nullptr; MonoMethod* m_gameObjectConstructor = nullptr; + MonoMethod* m_scriptableRenderContextConstructor = nullptr; MonoClassField* m_managedGameObjectUUIDField = nullptr; MonoClassField* m_gameObjectUUIDField = nullptr; MonoClassField* m_scriptComponentUUIDField = nullptr; diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 0a37c3cd..4e8bf1fe 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -10,6 +10,7 @@ #include "Debug/Logger.h" #include "Input/InputManager.h" #include "Physics/PhysicsWorld.h" +#include "Rendering/Pipelines/BuiltinForwardPipeline.h" #include "Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h" #include "Scene/Scene.h" #include "Scripting/ScriptComponent.h" @@ -83,6 +84,52 @@ bool& GetInternalCallRegistrationState() { return registered; } +struct ManagedScriptableRenderContextState { + uint64_t handle = 0; + Rendering::CameraFrameStage stage = Rendering::CameraFrameStage::MainScene; + const Rendering::RenderPipelineStageRenderGraphContext* graphContext = nullptr; + Rendering::Pipelines::BuiltinForwardPipeline builtinForwardPipeline; +}; + +uint64_t& GetManagedScriptableRenderContextNextHandle() { + static uint64_t nextHandle = 1; + return nextHandle; +} + +std::unordered_map& +GetManagedScriptableRenderContextRegistry() { + static std::unordered_map registry; + return registry; +} + +ManagedScriptableRenderContextState* FindManagedScriptableRenderContextState( + uint64_t handle) { + const auto it = GetManagedScriptableRenderContextRegistry().find(handle); + return it != GetManagedScriptableRenderContextRegistry().end() + ? it->second + : nullptr; +} + +uint64_t RegisterManagedScriptableRenderContextState( + ManagedScriptableRenderContextState& state) { + uint64_t handle = GetManagedScriptableRenderContextNextHandle()++; + if (handle == 0) { + handle = GetManagedScriptableRenderContextNextHandle()++; + } + + state.handle = handle; + GetManagedScriptableRenderContextRegistry()[handle] = &state; + return handle; +} + +void UnregisterManagedScriptableRenderContextState(uint64_t handle) { + if (handle == 0) { + return; + } + + GetManagedScriptableRenderContextRegistry().erase(handle); +} + void CleanupMonoRootDomainAtExit() { MonoRootState& rootState = GetMonoRootState(); if (!rootState.rootDomain) { @@ -233,14 +280,27 @@ public: return false; } - int32_t managedStage = static_cast(context.stage); - void* args[1] = { &managedStage }; + ManagedScriptableRenderContextState managedContextState = {}; + managedContextState.stage = context.stage; + managedContextState.graphContext = &context; + const uint64_t managedContextHandle = + RegisterManagedScriptableRenderContextState(managedContextState); + MonoObject* const managedContextObject = + m_runtime->CreateManagedScriptableRenderContext(managedContextHandle); + if (managedContextObject == nullptr) { + UnregisterManagedScriptableRenderContextState(managedContextHandle); + return false; + } + + void* args[1] = { managedContextObject }; MonoObject* result = nullptr; - if (!m_runtime->InvokeManagedMethod( + const bool invokeSucceeded = m_runtime->InvokeManagedMethod( pipelineObject, method, args, - &result)) { + &result); + UnregisterManagedScriptableRenderContextState(managedContextHandle); + if (!invokeSucceeded) { return false; } @@ -2100,6 +2160,31 @@ MonoString* InternalCall_Rendering_GetRenderPipelineAssetTypeName() { return mono_string_new(mono_domain_get(), assemblyQualifiedName.c_str()); } +int32_t InternalCall_Rendering_ScriptableRenderContext_GetStage( + uint64_t nativeHandle) { + const ManagedScriptableRenderContextState* const state = + FindManagedScriptableRenderContextState(nativeHandle); + return state != nullptr + ? static_cast(state->stage) + : static_cast(Rendering::CameraFrameStage::MainScene); +} + +mono_bool InternalCall_Rendering_ScriptableRenderContext_RenderBuiltinForwardMainScene( + uint64_t nativeHandle) { + ManagedScriptableRenderContextState* const state = + FindManagedScriptableRenderContextState(nativeHandle); + if (state == nullptr || + state->graphContext == nullptr || + state->stage != Rendering::CameraFrameStage::MainScene) { + return 0; + } + + return state->builtinForwardPipeline.RecordStageRenderGraph( + *state->graphContext) + ? 1 + : 0; +} + void RegisterInternalCalls() { if (GetInternalCallRegistrationState()) { return; @@ -2228,6 +2313,8 @@ void RegisterInternalCalls() { mono_add_internal_call("XCEngine.InternalCalls::Physics_Raycast", reinterpret_cast(&InternalCall_Physics_Raycast)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_SetRenderPipelineAssetType", reinterpret_cast(&InternalCall_Rendering_SetRenderPipelineAssetType)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_GetRenderPipelineAssetTypeName", reinterpret_cast(&InternalCall_Rendering_GetRenderPipelineAssetTypeName)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_GetStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_GetStage)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_RenderBuiltinForwardMainScene", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_RenderBuiltinForwardMainScene)); GetInternalCallRegistrationState() = true; } @@ -2280,6 +2367,8 @@ bool MonoScriptRuntime::Initialize() { void MonoScriptRuntime::Shutdown() { Rendering::Pipelines::ClearManagedRenderPipelineBridge(); + GetManagedScriptableRenderContextRegistry().clear(); + GetManagedScriptableRenderContextNextHandle() = 1; ClearManagedInstances(); ClearExternalManagedObjects(); ClearClassCache(); @@ -2294,8 +2383,10 @@ void MonoScriptRuntime::Shutdown() { m_monoBehaviourClass = nullptr; m_scriptableRenderPipelineAssetClass = nullptr; m_scriptableRenderPipelineClass = nullptr; + m_scriptableRenderContextClass = nullptr; m_serializeFieldAttributeClass = nullptr; m_gameObjectConstructor = nullptr; + m_scriptableRenderContextConstructor = nullptr; m_managedGameObjectUUIDField = nullptr; m_gameObjectUUIDField = nullptr; m_scriptComponentUUIDField = nullptr; @@ -2908,6 +2999,25 @@ bool MonoScriptRuntime::DiscoverScriptClasses() { return false; } + m_scriptableRenderContextClass = mono_class_from_name( + m_coreImage, + m_settings.baseNamespace.c_str(), + "ScriptableRenderContext"); + if (!m_scriptableRenderContextClass) { + SetError("Failed to locate the managed ScriptableRenderContext type."); + return false; + } + + m_scriptableRenderContextConstructor = mono_class_get_method_from_name( + m_scriptableRenderContextClass, + ".ctor", + 1); + if (!m_scriptableRenderContextConstructor) { + SetError( + "Failed to locate the managed ScriptableRenderContext constructor."); + return false; + } + m_serializeFieldAttributeClass = mono_class_from_name( m_coreImage, m_settings.baseNamespace.c_str(), @@ -3322,6 +3432,43 @@ void MonoScriptRuntime::DestroyExternalManagedObject(uint32_t gcHandle) { m_externalManagedObjects.erase(it); } +MonoObject* MonoScriptRuntime::CreateManagedScriptableRenderContext( + uint64_t nativeHandle) { + if (!m_initialized || + nativeHandle == 0 || + m_scriptableRenderContextClass == nullptr || + m_scriptableRenderContextConstructor == nullptr) { + return nullptr; + } + + SetCurrentDomain(); + + MonoObject* const contextObject = + mono_object_new(m_appDomain, m_scriptableRenderContextClass); + if (contextObject == nullptr) { + SetError( + "Mono failed to allocate a managed ScriptableRenderContext."); + return nullptr; + } + + void* args[1]; + uint64_t nativeHandleArgument = nativeHandle; + args[0] = &nativeHandleArgument; + + MonoObject* exception = nullptr; + mono_runtime_invoke( + m_scriptableRenderContextConstructor, + contextObject, + args, + &exception); + if (exception != nullptr) { + RecordException(exception); + return nullptr; + } + + return contextObject; +} + 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; diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index ae40c49e..01d4e062 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -122,6 +122,7 @@ set(XCENGINE_SCRIPT_CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/RenderPipelineAsset.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/ScriptableRenderPipeline.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/ScriptableRenderPipelineAsset.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/ScriptableRenderContext.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rigidbody.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/SerializeField.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Space.cs diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index f33d323b..e08181ab 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -34,10 +34,12 @@ namespace Gameplay } protected override bool RecordStageRenderGraph( - CameraFrameStage stage) + ScriptableRenderContext context) { RecordStageCallCount++; - return false; + return context != null && + context.stage == CameraFrameStage.MainScene && + context.RenderBuiltinForwardMainScene(); } } diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index 924b5c2d..9725773d 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -381,5 +381,14 @@ namespace XCEngine [MethodImpl(MethodImplOptions.InternalCall)] internal static extern string Rendering_GetRenderPipelineAssetTypeName(); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern int Rendering_ScriptableRenderContext_GetStage( + ulong nativeHandle); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool + Rendering_ScriptableRenderContext_RenderBuiltinForwardMainScene( + ulong nativeHandle); } } diff --git a/managed/XCEngine.ScriptCore/ScriptableRenderContext.cs b/managed/XCEngine.ScriptCore/ScriptableRenderContext.cs new file mode 100644 index 00000000..f5171d7a --- /dev/null +++ b/managed/XCEngine.ScriptCore/ScriptableRenderContext.cs @@ -0,0 +1,23 @@ +namespace XCEngine +{ + public sealed class ScriptableRenderContext + { + private readonly ulong m_nativeHandle; + + internal ScriptableRenderContext(ulong nativeHandle) + { + m_nativeHandle = nativeHandle; + } + + public CameraFrameStage stage => + (CameraFrameStage)InternalCalls.Rendering_ScriptableRenderContext_GetStage( + m_nativeHandle); + + public bool RenderBuiltinForwardMainScene() + { + return InternalCalls + .Rendering_ScriptableRenderContext_RenderBuiltinForwardMainScene( + m_nativeHandle); + } + } +} diff --git a/managed/XCEngine.ScriptCore/ScriptableRenderPipeline.cs b/managed/XCEngine.ScriptCore/ScriptableRenderPipeline.cs index a45574fb..c7f9f59a 100644 --- a/managed/XCEngine.ScriptCore/ScriptableRenderPipeline.cs +++ b/managed/XCEngine.ScriptCore/ScriptableRenderPipeline.cs @@ -13,7 +13,7 @@ namespace XCEngine } protected internal virtual bool RecordStageRenderGraph( - CameraFrameStage stage) + ScriptableRenderContext context) { return false; } diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index 60f31153..590bd8c3 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -16,7 +16,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -317,6 +319,102 @@ TEST_F(MonoScriptRuntimeTest, ManagedGraphicsSettingsRoundTripsRenderPipelineAss "Gameplay.RenderPipelineApiProbeAsset"); } +TEST_F( + MonoScriptRuntimeTest, + RegistersManagedRenderPipelineBridgeThatCreatesManagedStageRecorders) { + const auto bridge = + XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge(); + ASSERT_NE(bridge, nullptr); + + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { + "GameScripts", + "Gameplay", + "ManagedRenderPipelineProbeAsset" + }; + + std::unique_ptr recorder = + bridge->CreateStageRecorder(descriptor); + ASSERT_NE(recorder, nullptr); + + const XCEngine::Rendering::RenderContext context = {}; + EXPECT_TRUE(recorder->Initialize(context)); + EXPECT_TRUE( + recorder->SupportsStageRenderGraph( + XCEngine::Rendering::CameraFrameStage::MainScene)); + EXPECT_FALSE( + recorder->SupportsStageRenderGraph( + XCEngine::Rendering::CameraFrameStage::PostProcess)); + + recorder->Shutdown(); +} + +TEST_F( + MonoScriptRuntimeTest, + ManagedStageRecorderRecordsMainSceneThroughScriptableRenderContext) { + const auto bridge = + XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge(); + ASSERT_NE(bridge, nullptr); + + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { + "GameScripts", + "Gameplay", + "ManagedRenderPipelineProbeAsset" + }; + + std::unique_ptr recorder = + bridge->CreateStageRecorder(descriptor); + ASSERT_NE(recorder, nullptr); + + const XCEngine::Rendering::RenderContext renderContext = {}; + ASSERT_TRUE(recorder->Initialize(renderContext)); + ASSERT_TRUE( + recorder->SupportsStageRenderGraph( + XCEngine::Rendering::CameraFrameStage::MainScene)); + + XCEngine::Rendering::RenderGraph graph; + XCEngine::Rendering::RenderGraphBuilder graphBuilder(graph); + XCEngine::Rendering::RenderGraphTextureDesc colorDesc = {}; + colorDesc.width = 64u; + colorDesc.height = 64u; + colorDesc.format = + static_cast( + XCEngine::RHI::Format::R8G8B8A8_UNorm); + XCEngine::Rendering::RenderGraphTextureDesc depthDesc = colorDesc; + depthDesc.format = + static_cast( + XCEngine::RHI::Format::D32_Float); + const XCEngine::Rendering::RenderGraphTextureHandle colorTarget = + graphBuilder.CreateTransientTexture("ManagedMainSceneColor", colorDesc); + const XCEngine::Rendering::RenderGraphTextureHandle depthTarget = + graphBuilder.CreateTransientTexture("ManagedMainSceneDepth", depthDesc); + + const XCEngine::Rendering::RenderSceneData sceneData = {}; + const XCEngine::Rendering::RenderSurface surface(64u, 64u); + bool executionSucceeded = true; + XCEngine::Rendering::RenderGraphBlackboard blackboard = {}; + const XCEngine::Rendering::RenderPipelineStageRenderGraphContext graphContext = { + graphBuilder, + "ManagedMainScene", + XCEngine::Rendering::CameraFrameStage::MainScene, + renderContext, + sceneData, + surface, + nullptr, + nullptr, + XCEngine::RHI::ResourceStates::Common, + {}, + { colorTarget }, + depthTarget, + &executionSucceeded, + &blackboard + }; + + EXPECT_TRUE(recorder->RecordStageRenderGraph(graphContext)); + EXPECT_GT(graph.GetPassCount(), 0u); + + recorder->Shutdown(); +} + TEST_F(MonoScriptRuntimeTest, ClassFieldDefaultValueQueryReturnsNullComponentReferences) { std::vector fields;