diff --git a/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h b/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h index 5ba8fb17..5af08519 100644 --- a/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h +++ b/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h @@ -15,6 +15,7 @@ enum class CameraFrameColorSource { }; struct CameraFrameFullscreenStagePlan { + bool requested = false; bool usesGraphManagedOutputColor = false; CameraFrameColorSource source = CameraFrameColorSource::ExplicitSurface; }; @@ -58,6 +59,12 @@ struct CameraFramePlan { bool UsesGraphManagedSceneColor() const; bool UsesGraphManagedOutputColor(CameraFrameStage stage) const; CameraFrameColorSource ResolveStageColorSource(CameraFrameStage stage) const; + bool IsFullscreenStageRequested(CameraFrameStage stage) const; + bool RequestFullscreenStage( + CameraFrameStage stage, + CameraFrameColorSource source, + bool usesGraphManagedOutputColor = false); + void ClearFullscreenStage(CameraFrameStage stage); bool IsPostProcessStageValid() const; bool IsFinalOutputStageValid() const; bool HasFrameStage(CameraFrameStage stage) const; @@ -72,6 +79,8 @@ struct CameraFramePlan { bool RequiresIntermediateSceneColor() const; private: + void RefreshGraphManagedSceneSurfaceState(); + std::shared_ptr m_ownedPostProcessSequence = {}; std::shared_ptr m_ownedFinalOutputSequence = {}; }; diff --git a/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h b/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h index 882c3aef..a7d210ac 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h +++ b/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h @@ -7,6 +7,9 @@ namespace XCEngine { namespace Rendering { + +struct CameraFramePlan; + namespace Pipelines { struct ManagedRenderPipelineAssetDescriptor { @@ -36,6 +39,7 @@ public: std::unique_ptr CreatePipeline() const override; FinalColorSettings GetDefaultFinalColorSettings() const override; + void ConfigureCameraFramePlan(CameraFramePlan& plan) const override; private: ManagedRenderPipelineAssetDescriptor m_descriptor; @@ -50,6 +54,11 @@ public: const ManagedRenderPipelineAssetDescriptor&) const { return nullptr; } + + virtual void ConfigureCameraFramePlan( + const ManagedRenderPipelineAssetDescriptor&, + CameraFramePlan&) const { + } }; void SetManagedRenderPipelineBridge( diff --git a/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h b/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h index 9bfd032e..07738d7f 100644 --- a/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h +++ b/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h @@ -221,6 +221,8 @@ private: uint32_t RetainExternalManagedObject(MonoObject* instance); void DestroyExternalManagedObject(uint32_t gcHandle); MonoObject* CreateManagedScriptableRenderContext(uint64_t nativeHandle); + MonoObject* CreateManagedScriptableRenderPipelinePlanningContext( + uint64_t nativeHandle); MonoObject* GetManagedObject(uint32_t gcHandle) const; MonoMethod* ResolveManagedMethod( MonoClass* monoClass, @@ -259,9 +261,11 @@ private: MonoClass* m_scriptableRenderPipelineAssetClass = nullptr; MonoClass* m_scriptableRenderPipelineClass = nullptr; MonoClass* m_scriptableRenderContextClass = nullptr; + MonoClass* m_scriptableRenderPipelinePlanningContextClass = nullptr; MonoClass* m_serializeFieldAttributeClass = nullptr; MonoMethod* m_gameObjectConstructor = nullptr; MonoMethod* m_scriptableRenderContextConstructor = nullptr; + MonoMethod* m_scriptableRenderPipelinePlanningContextConstructor = nullptr; MonoClassField* m_managedGameObjectUUIDField = nullptr; MonoClassField* m_gameObjectUUIDField = nullptr; MonoClassField* m_scriptComponentUUIDField = nullptr; diff --git a/engine/src/Rendering/Execution/CameraFramePlan.cpp b/engine/src/Rendering/Execution/CameraFramePlan.cpp index f3820b6b..665cb56f 100644 --- a/engine/src/Rendering/Execution/CameraFramePlan.cpp +++ b/engine/src/Rendering/Execution/CameraFramePlan.cpp @@ -3,6 +3,42 @@ namespace XCEngine { namespace Rendering { +namespace { + +CameraFrameFullscreenStagePlan* GetMutableFullscreenStagePlan( + CameraFramePlan& plan, + CameraFrameStage stage) { + switch (stage) { + case CameraFrameStage::PostProcess: + return &plan.colorChain.postProcess; + case CameraFrameStage::FinalOutput: + return &plan.colorChain.finalOutput; + default: + return nullptr; + } +} + +FullscreenPassRenderRequest* GetMutableFullscreenPassRequest( + CameraFramePlan& plan, + CameraFrameStage stage) { + switch (stage) { + case CameraFrameStage::PostProcess: + return &plan.postProcess; + case CameraFrameStage::FinalOutput: + return &plan.finalOutput; + default: + return nullptr; + } +} + +bool DoesStageNeedGraphManagedSceneColor( + const CameraFrameFullscreenStagePlan& stagePlan) { + return stagePlan.requested && + stagePlan.source == CameraFrameColorSource::MainSceneColor; +} + +} // namespace + RenderSurface CameraFramePlan::BuildGraphManagedIntermediateSurfaceTemplate( const RenderSurface& surface) { RenderSurface graphManagedSurface = surface; @@ -24,6 +60,8 @@ CameraFramePlan CameraFramePlan::FromRequest(const CameraRenderRequest& request) plan.preScenePasses = request.preScenePasses; plan.postScenePasses = request.postScenePasses; plan.overlayPasses = request.overlayPasses; + plan.colorChain.postProcess.requested = request.postProcess.IsRequested(); + plan.colorChain.finalOutput.requested = request.finalOutput.IsRequested(); return plan; } @@ -98,28 +136,96 @@ CameraFrameColorSource CameraFramePlan::ResolveStageColorSource(CameraFrameStage return CameraFrameColorSource::ExplicitSurface; } +bool CameraFramePlan::IsFullscreenStageRequested(CameraFrameStage stage) const { + if (const CameraFrameFullscreenStagePlan* fullscreenStagePlan = + GetFullscreenStagePlan(stage); + fullscreenStagePlan != nullptr) { + return fullscreenStagePlan->requested; + } + + return false; +} + +bool CameraFramePlan::RequestFullscreenStage( + CameraFrameStage stage, + CameraFrameColorSource source, + bool usesGraphManagedOutputColor) { + CameraFrameFullscreenStagePlan* const fullscreenStagePlan = + GetMutableFullscreenStagePlan(*this, stage); + FullscreenPassRenderRequest* const fullscreenRequest = + GetMutableFullscreenPassRequest(*this, stage); + if (fullscreenStagePlan == nullptr || fullscreenRequest == nullptr) { + return false; + } + + fullscreenStagePlan->requested = true; + fullscreenStagePlan->source = source; + fullscreenStagePlan->usesGraphManagedOutputColor = + stage == CameraFrameStage::PostProcess + ? usesGraphManagedOutputColor + : false; + + if (source != CameraFrameColorSource::ExplicitSurface) { + fullscreenRequest->sourceSurface = {}; + fullscreenRequest->sourceColorView = nullptr; + fullscreenRequest->sourceColorState = RHI::ResourceStates::Common; + } + + if (stage == CameraFrameStage::PostProcess) { + fullscreenRequest->destinationSurface = + fullscreenStagePlan->usesGraphManagedOutputColor + ? RenderSurface{} + : request.surface; + } else { + fullscreenRequest->destinationSurface = request.surface; + } + + RefreshGraphManagedSceneSurfaceState(); + return true; +} + +void CameraFramePlan::ClearFullscreenStage(CameraFrameStage stage) { + if (stage == CameraFrameStage::PostProcess) { + ClearOwnedPostProcessSequence(); + postProcess = {}; + colorChain.postProcess = {}; + } else if (stage == CameraFrameStage::FinalOutput) { + ClearOwnedFinalOutputSequence(); + finalOutput = {}; + colorChain.finalOutput = {}; + } else { + return; + } + + RefreshGraphManagedSceneSurfaceState(); +} + bool CameraFramePlan::IsPostProcessStageValid() const { - if (!postProcess.IsRequested()) { + if (!IsFullscreenStageRequested(CameraFrameStage::PostProcess)) { return true; } - if (ResolveStageColorSource(CameraFrameStage::PostProcess) == - CameraFrameColorSource::ExplicitSurface) { + const CameraFrameColorSource postProcessSource = + ResolveStageColorSource(CameraFrameStage::PostProcess); + if (postProcessSource == CameraFrameColorSource::ExplicitSurface) { return postProcess.IsValid(); } + if (postProcessSource != CameraFrameColorSource::MainSceneColor) { + return false; + } + const bool hasUsableDestination = UsesGraphManagedOutputColor(CameraFrameStage::PostProcess) || (HasValidColorTarget(postProcess.destinationSurface) && HasValidSurfaceSampleDescription(postProcess.destinationSurface)); return UsesGraphManagedOutputColor(CameraFrameStage::MainScene) && - postProcess.passes != nullptr && HasValidSingleSampleColorSource(request.surface) && hasUsableDestination; } bool CameraFramePlan::IsFinalOutputStageValid() const { - if (!finalOutput.IsRequested()) { + if (!IsFullscreenStageRequested(CameraFrameStage::FinalOutput)) { return true; } @@ -134,7 +240,6 @@ bool CameraFramePlan::IsFinalOutputStageValid() const { ? UsesGraphManagedOutputColor(CameraFrameStage::MainScene) : UsesGraphManagedOutputColor(CameraFrameStage::PostProcess); return hasUsableSource && - finalOutput.passes != nullptr && HasValidColorTarget(finalOutput.destinationSurface) && HasValidSurfaceSampleDescription(finalOutput.destinationSurface); } @@ -145,6 +250,10 @@ bool CameraFramePlan::HasFrameStage(CameraFrameStage stage) const { } if (IsCameraFrameSequenceStage(stage)) { + if (IsCameraFrameFullscreenSequenceStage(stage)) { + return IsFullscreenStageRequested(stage); + } + if (const FullscreenPassRenderRequest* fullscreenRequest = GetFullscreenPassRequest(stage); fullscreenRequest != nullptr) { @@ -248,12 +357,12 @@ const RenderSurface& CameraFramePlan::GetMainSceneSurface() const { return graphManagedSceneSurface; } - if (postProcess.IsRequested() && + if (IsFullscreenStageRequested(CameraFrameStage::PostProcess) && HasValidColorTarget(postProcess.sourceSurface)) { return postProcess.sourceSurface; } - if (finalOutput.IsRequested() && + if (IsFullscreenStageRequested(CameraFrameStage::FinalOutput) && HasValidColorTarget(finalOutput.sourceSurface)) { return finalOutput.sourceSurface; } @@ -262,12 +371,12 @@ const RenderSurface& CameraFramePlan::GetMainSceneSurface() const { } const RenderSurface& CameraFramePlan::GetFinalCompositedSurface() const { - if (finalOutput.IsRequested() && + if (IsFullscreenStageRequested(CameraFrameStage::FinalOutput) && HasValidColorTarget(finalOutput.destinationSurface)) { return finalOutput.destinationSurface; } - if (postProcess.IsRequested() && + if (IsFullscreenStageRequested(CameraFrameStage::PostProcess) && HasValidColorTarget(postProcess.destinationSurface)) { return postProcess.destinationSurface; } @@ -276,7 +385,20 @@ const RenderSurface& CameraFramePlan::GetFinalCompositedSurface() const { } bool CameraFramePlan::RequiresIntermediateSceneColor() const { - return postProcess.IsRequested() || finalOutput.IsRequested(); + return IsFullscreenStageRequested(CameraFrameStage::PostProcess) || + IsFullscreenStageRequested(CameraFrameStage::FinalOutput); +} + +void CameraFramePlan::RefreshGraphManagedSceneSurfaceState() { + colorChain.usesGraphManagedSceneColor = + DoesStageNeedGraphManagedSceneColor(colorChain.postProcess) || + DoesStageNeedGraphManagedSceneColor(colorChain.finalOutput); + + if (colorChain.usesGraphManagedSceneColor) { + ConfigureGraphManagedSceneSurface(); + } else { + graphManagedSceneSurface = {}; + } } } // namespace Rendering diff --git a/engine/src/Rendering/Execution/CameraRenderer.cpp b/engine/src/Rendering/Execution/CameraRenderer.cpp index 74d28bd3..d7d79d06 100644 --- a/engine/src/Rendering/Execution/CameraRenderer.cpp +++ b/engine/src/Rendering/Execution/CameraRenderer.cpp @@ -12,6 +12,17 @@ namespace XCEngine { namespace Rendering { +namespace { + +bool RequiresPipelineStageRecording( + const CameraFramePlan& plan, + CameraFrameStage stage) { + return plan.IsFullscreenStageRequested(stage) && + plan.GetPassSequence(stage) == nullptr; +} + +} // namespace + CameraRenderer::CameraRenderer() : CameraRenderer(Internal::CreateDefaultRenderPipelineAsset()) { } @@ -120,7 +131,7 @@ bool CameraRenderer::Render( "CameraRenderer::Render failed: depth-only request invalid"); return false; } - if (plan.postProcess.IsRequested() && + if (plan.IsFullscreenStageRequested(CameraFrameStage::PostProcess) && !plan.IsPostProcessStageValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, @@ -135,13 +146,26 @@ bool CameraRenderer::Render( "CameraRenderer::Render failed: graph-managed main scene color requires pipeline main-scene render-graph support"); return false; } - if (plan.finalOutput.IsRequested() && + if (plan.IsFullscreenStageRequested(CameraFrameStage::FinalOutput) && !plan.IsFinalOutputStageValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "CameraRenderer::Render failed: final-output request invalid"); return false; } + for (const CameraFrameStage stage : + { CameraFrameStage::PostProcess, CameraFrameStage::FinalOutput }) { + if (RequiresPipelineStageRecording(plan, stage) && + (m_pipeline == nullptr || + !m_pipeline->SupportsStageRenderGraph(stage))) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + Containers::String( + "CameraRenderer::Render failed: fullscreen stage requires pipeline render-graph support when no legacy sequence is present: ") + + GetCameraFrameStageName(stage)); + return false; + } + } if (plan.request.objectId.IsRequested() && !plan.request.objectId.IsValid()) { Debug::Logger::Get().Error( diff --git a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceResolver.h b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceResolver.h index 4b584afe..20f7a8c8 100644 --- a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceResolver.h +++ b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceResolver.h @@ -69,7 +69,7 @@ inline const RenderSurface* ResolveCameraFrameStageOutputSurface( if (const FullscreenPassRenderRequest* fullscreenRequest = plan.GetFullscreenPassRequest(stage); fullscreenRequest != nullptr && - fullscreenRequest->IsRequested() && + plan.IsFullscreenStageRequested(stage) && HasValidColorTarget(fullscreenRequest->destinationSurface)) { return &fullscreenRequest->destinationSurface; } @@ -91,7 +91,7 @@ inline CameraFrameStageSourceBinding ResolveCameraFrameStageSourceBinding( if (const FullscreenPassRenderRequest* fullscreenRequest = plan.GetFullscreenPassRequest(stage); fullscreenRequest != nullptr) { - return fullscreenRequest->IsRequested() && + return plan.IsFullscreenStageRequested(stage) && plan.ResolveStageColorSource(stage) == CameraFrameColorSource::ExplicitSurface ? CameraFrameStageSourceBinding{ diff --git a/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp b/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp index b690e284..1dd81d8e 100644 --- a/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp +++ b/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp @@ -46,6 +46,17 @@ FinalColorSettings ManagedScriptableRenderPipelineAsset::GetDefaultFinalColorSet return m_fallbackAsset.GetDefaultFinalColorSettings(); } +void ManagedScriptableRenderPipelineAsset::ConfigureCameraFramePlan( + CameraFramePlan& plan) const { + RenderPipelineAsset::ConfigureCameraFramePlan(plan); + + const std::shared_ptr bridge = + GetManagedRenderPipelineBridgeStorage(); + if (bridge != nullptr) { + bridge->ConfigureCameraFramePlan(m_descriptor, plan); + } +} + void SetManagedRenderPipelineBridge( std::shared_ptr bridge) { GetManagedRenderPipelineBridgeStorage() = std::move(bridge); diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 09048d13..c49f6d7d 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/Execution/CameraFramePlan.h" #include "Rendering/Passes/BuiltinColorScalePostProcessPass.h" #include "Rendering/Pipelines/BuiltinForwardPipeline.h" #include "Rendering/Pipelines/BuiltinForwardSceneRecorder.h" @@ -96,6 +97,11 @@ struct ManagedScriptableRenderContextState { std::vector queuedBuiltinColorScaleFullscreenPasses = {}; }; +struct ManagedScriptableRenderPipelinePlanningContextState { + uint64_t handle = 0; + Rendering::CameraFramePlan* plan = nullptr; +}; + uint64_t& GetManagedScriptableRenderContextNextHandle() { static uint64_t nextHandle = 1; return nextHandle; @@ -135,6 +141,49 @@ void UnregisterManagedScriptableRenderContextState(uint64_t handle) { GetManagedScriptableRenderContextRegistry().erase(handle); } +uint64_t& GetManagedScriptableRenderPipelinePlanningContextNextHandle() { + static uint64_t nextHandle = 1; + return nextHandle; +} + +std::unordered_map& +GetManagedScriptableRenderPipelinePlanningContextRegistry() { + static std::unordered_map + registry; + return registry; +} + +ManagedScriptableRenderPipelinePlanningContextState* +FindManagedScriptableRenderPipelinePlanningContextState( + uint64_t handle) { + const auto it = + GetManagedScriptableRenderPipelinePlanningContextRegistry().find(handle); + return it != GetManagedScriptableRenderPipelinePlanningContextRegistry().end() + ? it->second + : nullptr; +} + +uint64_t RegisterManagedScriptableRenderPipelinePlanningContextState( + ManagedScriptableRenderPipelinePlanningContextState& state) { + uint64_t handle = + GetManagedScriptableRenderPipelinePlanningContextNextHandle()++; + if (handle == 0) { + handle = GetManagedScriptableRenderPipelinePlanningContextNextHandle()++; + } + + state.handle = handle; + GetManagedScriptableRenderPipelinePlanningContextRegistry()[handle] = &state; + return handle; +} + +void UnregisterManagedScriptableRenderPipelinePlanningContextState(uint64_t handle) { + if (handle == 0) { + return; + } + + GetManagedScriptableRenderPipelinePlanningContextRegistry().erase(handle); +} + void CleanupMonoRootDomainAtExit() { MonoRootState& rootState = GetMonoRootState(); if (!rootState.rootDomain) { @@ -522,9 +571,7 @@ public: std::unique_ptr CreateStageRecorder( const Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor& descriptor) const override { - if (m_runtime == nullptr || - m_runtimeLifetime.expired() || - !descriptor.IsValid()) { + if (!IsRuntimeAlive() || !descriptor.IsValid()) { return nullptr; } @@ -534,7 +581,98 @@ public: descriptor); } + void ConfigureCameraFramePlan( + const Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor& descriptor, + Rendering::CameraFramePlan& plan) const override { + if (!IsRuntimeAlive() || !descriptor.IsValid()) { + return; + } + + uint32_t assetHandle = 0; + MonoObject* const assetObject = + CreateManagedAssetInstance(descriptor, assetHandle); + if (assetObject == nullptr || assetHandle == 0) { + if (assetHandle != 0) { + m_runtime->DestroyExternalManagedObject(assetHandle); + } + return; + } + + MonoMethod* const method = + m_runtime->ResolveManagedMethod( + assetObject, + "ConfigureCameraFramePlan", + 1); + if (method == nullptr) { + m_runtime->DestroyExternalManagedObject(assetHandle); + return; + } + + ManagedScriptableRenderPipelinePlanningContextState planningContextState = {}; + planningContextState.plan = &plan; + const uint64_t planningContextHandle = + RegisterManagedScriptableRenderPipelinePlanningContextState( + planningContextState); + MonoObject* const planningContextObject = + m_runtime->CreateManagedScriptableRenderPipelinePlanningContext( + planningContextHandle); + if (planningContextObject == nullptr) { + UnregisterManagedScriptableRenderPipelinePlanningContextState( + planningContextHandle); + m_runtime->DestroyExternalManagedObject(assetHandle); + return; + } + + void* args[1] = { planningContextObject }; + m_runtime->InvokeManagedMethod( + assetObject, + method, + args, + nullptr); + UnregisterManagedScriptableRenderPipelinePlanningContextState( + planningContextHandle); + m_runtime->DestroyExternalManagedObject(assetHandle); + } + private: + bool IsRuntimeAlive() const { + return m_runtime != nullptr && + !m_runtimeLifetime.expired() && + m_runtime->m_initialized; + } + + MonoObject* CreateManagedAssetInstance( + const Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor& descriptor, + uint32_t& outAssetHandle) const { + outAssetHandle = 0; + + MonoClass* assetClass = nullptr; + if (!m_runtime->ResolveManagedClass( + descriptor.assemblyName, + descriptor.namespaceName, + descriptor.className, + assetClass) || + assetClass == nullptr) { + return nullptr; + } + + if (!IsMonoClassOrSubclass( + assetClass, + m_runtime->m_scriptableRenderPipelineAssetClass)) { + m_runtime->SetError( + "Managed render pipeline asset must derive from ScriptableRenderPipelineAsset: " + + descriptor.GetFullName() + "."); + return nullptr; + } + + if (!m_runtime->CreateExternalManagedObject(assetClass, outAssetHandle) || + outAssetHandle == 0) { + return nullptr; + } + + return m_runtime->GetManagedObject(outAssetHandle); + } + MonoScriptRuntime* m_runtime = nullptr; std::weak_ptr m_runtimeLifetime; }; @@ -2307,6 +2445,68 @@ InternalCall_Rendering_ScriptableRenderContext_RecordBuiltinColorScaleFullscreen return 1; } +void InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearPostProcessStage( + uint64_t nativeHandle) { + ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + if (state == nullptr || state->plan == nullptr) { + return; + } + + state->plan->ClearFullscreenStage(Rendering::CameraFrameStage::PostProcess); +} + +mono_bool +InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestPostProcessStage( + uint64_t nativeHandle, + int32_t source, + mono_bool usesGraphManagedOutputColor) { + ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + if (state == nullptr || + state->plan == nullptr || + source == static_cast(Rendering::CameraFrameColorSource::ExplicitSurface)) { + return 0; + } + + return state->plan->RequestFullscreenStage( + Rendering::CameraFrameStage::PostProcess, + static_cast(source), + usesGraphManagedOutputColor != 0) + ? 1 + : 0; +} + +void InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearFinalOutputStage( + uint64_t nativeHandle) { + ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + if (state == nullptr || state->plan == nullptr) { + return; + } + + state->plan->ClearFullscreenStage(Rendering::CameraFrameStage::FinalOutput); +} + +mono_bool +InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestFinalOutputStage( + uint64_t nativeHandle, + int32_t source) { + ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + if (state == nullptr || + state->plan == nullptr || + source == static_cast(Rendering::CameraFrameColorSource::ExplicitSurface)) { + return 0; + } + + return state->plan->RequestFullscreenStage( + Rendering::CameraFrameStage::FinalOutput, + static_cast(source)) + ? 1 + : 0; +} + void RegisterInternalCalls() { if (GetInternalCallRegistrationState()) { return; @@ -2440,6 +2640,10 @@ void RegisterInternalCalls() { mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_RecordBuiltinForwardScenePhase", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_RecordBuiltinForwardScenePhase)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_RecordBuiltinForwardInjectionPoint", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_RecordBuiltinForwardInjectionPoint)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_RecordBuiltinColorScaleFullscreenPass", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_RecordBuiltinColorScaleFullscreenPass)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearPostProcessStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearPostProcessStage)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_RequestPostProcessStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestPostProcessStage)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearFinalOutputStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearFinalOutputStage)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_RequestFinalOutputStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestFinalOutputStage)); GetInternalCallRegistrationState() = true; } @@ -2494,6 +2698,8 @@ void MonoScriptRuntime::Shutdown() { Rendering::Pipelines::ClearManagedRenderPipelineBridge(); GetManagedScriptableRenderContextRegistry().clear(); GetManagedScriptableRenderContextNextHandle() = 1; + GetManagedScriptableRenderPipelinePlanningContextRegistry().clear(); + GetManagedScriptableRenderPipelinePlanningContextNextHandle() = 1; ClearManagedInstances(); ClearExternalManagedObjects(); ClearClassCache(); @@ -2509,9 +2715,11 @@ void MonoScriptRuntime::Shutdown() { m_scriptableRenderPipelineAssetClass = nullptr; m_scriptableRenderPipelineClass = nullptr; m_scriptableRenderContextClass = nullptr; + m_scriptableRenderPipelinePlanningContextClass = nullptr; m_serializeFieldAttributeClass = nullptr; m_gameObjectConstructor = nullptr; m_scriptableRenderContextConstructor = nullptr; + m_scriptableRenderPipelinePlanningContextConstructor = nullptr; m_managedGameObjectUUIDField = nullptr; m_gameObjectUUIDField = nullptr; m_scriptComponentUUIDField = nullptr; @@ -3143,6 +3351,27 @@ bool MonoScriptRuntime::DiscoverScriptClasses() { return false; } + m_scriptableRenderPipelinePlanningContextClass = mono_class_from_name( + m_coreImage, + m_settings.baseNamespace.c_str(), + "ScriptableRenderPipelinePlanningContext"); + if (!m_scriptableRenderPipelinePlanningContextClass) { + SetError( + "Failed to locate the managed ScriptableRenderPipelinePlanningContext type."); + return false; + } + + m_scriptableRenderPipelinePlanningContextConstructor = + mono_class_get_method_from_name( + m_scriptableRenderPipelinePlanningContextClass, + ".ctor", + 1); + if (!m_scriptableRenderPipelinePlanningContextConstructor) { + SetError( + "Failed to locate the managed ScriptableRenderPipelinePlanningContext constructor."); + return false; + } + m_serializeFieldAttributeClass = mono_class_from_name( m_coreImage, m_settings.baseNamespace.c_str(), @@ -3594,6 +3823,45 @@ MonoObject* MonoScriptRuntime::CreateManagedScriptableRenderContext( return contextObject; } +MonoObject* MonoScriptRuntime::CreateManagedScriptableRenderPipelinePlanningContext( + uint64_t nativeHandle) { + if (!m_initialized || + nativeHandle == 0 || + m_scriptableRenderPipelinePlanningContextClass == nullptr || + m_scriptableRenderPipelinePlanningContextConstructor == nullptr) { + return nullptr; + } + + SetCurrentDomain(); + + MonoObject* const contextObject = + mono_object_new( + m_appDomain, + m_scriptableRenderPipelinePlanningContextClass); + if (contextObject == nullptr) { + SetError( + "Mono failed to allocate a managed ScriptableRenderPipelinePlanningContext."); + return nullptr; + } + + void* args[1]; + uint64_t nativeHandleArgument = nativeHandle; + args[0] = &nativeHandleArgument; + + MonoObject* exception = nullptr; + mono_runtime_invoke( + m_scriptableRenderPipelinePlanningContextConstructor, + 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 1f337a32..3fce933f 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -101,6 +101,7 @@ endforeach() set(XCENGINE_SCRIPT_CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Behaviour.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Camera.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/CameraFrameColorSource.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/CameraFrameStage.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Component.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Debug.cs @@ -124,6 +125,7 @@ set(XCENGINE_SCRIPT_CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/SceneRenderInjectionPoint.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/ScriptableRenderPipeline.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/ScriptableRenderPipelineAsset.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/ScriptableRenderPipelinePlanningContext.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 diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index 048999f7..2c6f5f22 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -29,6 +29,27 @@ namespace Gameplay } } + public sealed class ManagedPlannedFullscreenRenderPipelineProbeAsset + : ScriptableRenderPipelineAsset + { + protected override ScriptableRenderPipeline CreatePipeline() + { + return new ManagedPlannedFullscreenRenderPipelineProbe(); + } + + protected override void ConfigureCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + context.ClearPostProcessStage(); + context.ClearFinalOutputStage(); + context.RequestPostProcessStage( + CameraFrameColorSource.MainSceneColor, + true); + context.RequestFinalOutputStage( + CameraFrameColorSource.PostProcessColor); + } + } + public sealed class ManagedRenderPipelineProbe : ScriptableRenderPipeline { public static int SupportsStageCallCount; @@ -85,6 +106,27 @@ namespace Gameplay } } + public sealed class ManagedPlannedFullscreenRenderPipelineProbe + : ScriptableRenderPipeline + { + protected override bool SupportsStageRenderGraph( + CameraFrameStage stage) + { + return stage == CameraFrameStage.PostProcess || + stage == CameraFrameStage.FinalOutput; + } + + protected override bool RecordStageRenderGraph( + ScriptableRenderContext context) + { + return context != null && + (context.stage == CameraFrameStage.PostProcess || + context.stage == CameraFrameStage.FinalOutput) && + context.RecordBuiltinColorScaleFullscreenPass( + new Vector4(1.05f, 1.0f, 0.95f, 1.0f)); + } + } + public sealed class RenderPipelineApiProbe : MonoBehaviour { public bool InitialTypeWasNull; diff --git a/managed/XCEngine.ScriptCore/CameraFrameColorSource.cs b/managed/XCEngine.ScriptCore/CameraFrameColorSource.cs new file mode 100644 index 00000000..6cdd7487 --- /dev/null +++ b/managed/XCEngine.ScriptCore/CameraFrameColorSource.cs @@ -0,0 +1,9 @@ +namespace XCEngine +{ + public enum CameraFrameColorSource + { + ExplicitSurface = 0, + MainSceneColor = 1, + PostProcessColor = 2 + } +} diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index 2ac7036c..4bbf645e 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -408,5 +408,28 @@ namespace XCEngine Rendering_ScriptableRenderContext_RecordBuiltinColorScaleFullscreenPass( ulong nativeHandle, ref Vector4 colorScale); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void + Rendering_ScriptableRenderPipelinePlanningContext_ClearPostProcessStage( + ulong nativeHandle); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool + Rendering_ScriptableRenderPipelinePlanningContext_RequestPostProcessStage( + ulong nativeHandle, + int source, + bool usesGraphManagedOutputColor); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void + Rendering_ScriptableRenderPipelinePlanningContext_ClearFinalOutputStage( + ulong nativeHandle); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool + Rendering_ScriptableRenderPipelinePlanningContext_RequestFinalOutputStage( + ulong nativeHandle, + int source); } } diff --git a/managed/XCEngine.ScriptCore/ScriptableRenderPipelineAsset.cs b/managed/XCEngine.ScriptCore/ScriptableRenderPipelineAsset.cs index a0c18bcd..7ea4da94 100644 --- a/managed/XCEngine.ScriptCore/ScriptableRenderPipelineAsset.cs +++ b/managed/XCEngine.ScriptCore/ScriptableRenderPipelineAsset.cs @@ -10,5 +10,10 @@ namespace XCEngine { return null; } + + protected internal virtual void ConfigureCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + } } } diff --git a/managed/XCEngine.ScriptCore/ScriptableRenderPipelinePlanningContext.cs b/managed/XCEngine.ScriptCore/ScriptableRenderPipelinePlanningContext.cs new file mode 100644 index 00000000..722a1a93 --- /dev/null +++ b/managed/XCEngine.ScriptCore/ScriptableRenderPipelinePlanningContext.cs @@ -0,0 +1,68 @@ +using System; + +namespace XCEngine +{ + public sealed class ScriptableRenderPipelinePlanningContext + { + private readonly ulong m_nativeHandle; + + internal ScriptableRenderPipelinePlanningContext(ulong nativeHandle) + { + m_nativeHandle = nativeHandle; + } + + public void ClearPostProcessStage() + { + InternalCalls.Rendering_ScriptableRenderPipelinePlanningContext_ClearPostProcessStage( + m_nativeHandle); + } + + public void RequestPostProcessStage( + CameraFrameColorSource source, + bool usesGraphManagedOutputColor) + { + if (source == CameraFrameColorSource.ExplicitSurface) + { + throw new ArgumentException( + "Managed planning currently requires a graph-managed source for post-process stages.", + nameof(source)); + } + + if (!InternalCalls + .Rendering_ScriptableRenderPipelinePlanningContext_RequestPostProcessStage( + m_nativeHandle, + (int)source, + usesGraphManagedOutputColor)) + { + throw new InvalidOperationException( + "Failed to request the managed post-process stage."); + } + } + + public void ClearFinalOutputStage() + { + InternalCalls.Rendering_ScriptableRenderPipelinePlanningContext_ClearFinalOutputStage( + m_nativeHandle); + } + + public void RequestFinalOutputStage( + CameraFrameColorSource source) + { + if (source == CameraFrameColorSource.ExplicitSurface) + { + throw new ArgumentException( + "Managed planning currently requires a graph-managed source for final-output stages.", + nameof(source)); + } + + if (!InternalCalls + .Rendering_ScriptableRenderPipelinePlanningContext_RequestFinalOutputStage( + m_nativeHandle, + (int)source)) + { + throw new InvalidOperationException( + "Failed to request the managed final-output stage."); + } + } + } +} diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 63a779ea..0d429e37 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -776,6 +776,9 @@ struct MockManagedRenderPipelineBridgeState { int createStageRecorderCalls = 0; Pipelines::ManagedRenderPipelineAssetDescriptor lastDescriptor = {}; std::shared_ptr lastCreatedStageRecorderState; + int configureCameraFramePlanCalls = 0; + Pipelines::ManagedRenderPipelineAssetDescriptor lastPlanningDescriptor = {}; + std::function configureCameraFramePlan = {}; }; class MockManagedRenderPipelineBridge final @@ -796,6 +799,16 @@ public: m_state->lastCreatedStageRecorderState); } + void ConfigureCameraFramePlan( + const Pipelines::ManagedRenderPipelineAssetDescriptor& descriptor, + CameraFramePlan& plan) const override { + ++m_state->configureCameraFramePlanCalls; + m_state->lastPlanningDescriptor = descriptor; + if (m_state->configureCameraFramePlan) { + m_state->configureCameraFramePlan(plan); + } + } + private: std::shared_ptr m_state; }; @@ -4471,6 +4484,180 @@ TEST(ManagedScriptableRenderPipelineAsset_Test, CreatesHostWithStageRecorderFrom Pipelines::ClearManagedRenderPipelineBridge(); } +TEST(ManagedScriptableRenderPipelineAsset_Test, LetsManagedBridgeRequestFullscreenStagesDuringPlanConfiguration) { + Pipelines::ClearManagedRenderPipelineBridge(); + + const Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { + "GameScripts", + "Gameplay", + "ManagedPlannedFullscreenRenderPipelineProbeAsset" + }; + auto bridgeState = std::make_shared(); + bridgeState->configureCameraFramePlan = []( + CameraFramePlan& plan) { + plan.ClearFullscreenStage(CameraFrameStage::PostProcess); + plan.ClearFullscreenStage(CameraFrameStage::FinalOutput); + EXPECT_TRUE( + plan.RequestFullscreenStage( + CameraFrameStage::PostProcess, + CameraFrameColorSource::MainSceneColor, + true)); + EXPECT_TRUE( + plan.RequestFullscreenStage( + CameraFrameStage::FinalOutput, + CameraFrameColorSource::PostProcessColor)); + }; + Pipelines::SetManagedRenderPipelineBridge( + std::make_shared(bridgeState)); + + auto allocationState = std::make_shared(); + MockShadowView colorView( + allocationState, + XCEngine::RHI::ResourceViewType::RenderTarget, + XCEngine::RHI::Format::R8G8B8A8_UNorm, + XCEngine::RHI::ResourceViewDimension::Texture2D); + MockShadowView depthView( + allocationState, + XCEngine::RHI::ResourceViewType::DepthStencil, + XCEngine::RHI::Format::D24_UNorm_S8_UInt, + XCEngine::RHI::ResourceViewDimension::Texture2D); + + CameraRenderRequest request = {}; + request.context = CreateValidContext(); + request.surface = RenderSurface(320, 180); + request.surface.SetColorAttachment(&colorView); + request.surface.SetDepthAttachment(&depthView); + + CameraFramePlan plan = CameraFramePlan::FromRequest(request); + Pipelines::ManagedScriptableRenderPipelineAsset asset(descriptor); + asset.ConfigureCameraFramePlan(plan); + + EXPECT_EQ(bridgeState->configureCameraFramePlanCalls, 1); + EXPECT_EQ(bridgeState->lastPlanningDescriptor.assemblyName, "GameScripts"); + EXPECT_EQ(bridgeState->lastPlanningDescriptor.namespaceName, "Gameplay"); + EXPECT_EQ( + bridgeState->lastPlanningDescriptor.className, + "ManagedPlannedFullscreenRenderPipelineProbeAsset"); + EXPECT_TRUE(plan.IsFullscreenStageRequested(CameraFrameStage::PostProcess)); + EXPECT_TRUE(plan.IsFullscreenStageRequested(CameraFrameStage::FinalOutput)); + EXPECT_EQ(plan.postProcess.passes, nullptr); + EXPECT_EQ(plan.finalOutput.passes, nullptr); + EXPECT_TRUE(plan.UsesGraphManagedSceneColor()); + EXPECT_TRUE(plan.UsesGraphManagedOutputColor(CameraFrameStage::PostProcess)); + EXPECT_EQ( + plan.ResolveStageColorSource(CameraFrameStage::PostProcess), + CameraFrameColorSource::MainSceneColor); + EXPECT_EQ( + plan.ResolveStageColorSource(CameraFrameStage::FinalOutput), + CameraFrameColorSource::PostProcessColor); + EXPECT_TRUE(plan.IsPostProcessStageValid()); + EXPECT_TRUE(plan.IsFinalOutputStageValid()); + + Pipelines::ClearManagedRenderPipelineBridge(); +} + +TEST(CameraRenderer_Test, RendersManagedRequestedPostProcessWithoutLegacySequence) { + Scene scene("CameraRendererManagedRequestedPostProcessScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + + auto state = std::make_shared(); + state->supportsMainSceneRenderGraph = true; + state->supportsPostProcessRenderGraph = true; + CameraRenderer renderer(std::make_unique(state)); + + auto allocationState = std::make_shared(); + MockShadowDevice device(allocationState); + MockShadowView colorView( + allocationState, + XCEngine::RHI::ResourceViewType::RenderTarget, + XCEngine::RHI::Format::R8G8B8A8_UNorm, + XCEngine::RHI::ResourceViewDimension::Texture2D); + MockShadowView depthView( + allocationState, + XCEngine::RHI::ResourceViewType::DepthStencil, + XCEngine::RHI::Format::D24_UNorm_S8_UInt, + XCEngine::RHI::ResourceViewDimension::Texture2D); + + CameraRenderRequest request = {}; + request.scene = &scene; + request.camera = camera; + request.context = CreateValidContext(); + request.context.device = &device; + request.surface = RenderSurface(320, 180); + request.surface.SetColorAttachment(&colorView); + request.surface.SetDepthAttachment(&depthView); + request.cameraDepth = camera->GetDepth(); + + CameraFramePlan plan = CameraFramePlan::FromRequest(request); + ASSERT_TRUE( + plan.RequestFullscreenStage( + CameraFrameStage::PostProcess, + CameraFrameColorSource::MainSceneColor, + false)); + + ASSERT_TRUE(plan.IsFullscreenStageRequested(CameraFrameStage::PostProcess)); + ASSERT_EQ(plan.postProcess.passes, nullptr); + ASSERT_TRUE(plan.IsPostProcessStageValid()); + ASSERT_TRUE(renderer.Render(plan)); + EXPECT_EQ(state->recordMainSceneCalls, 1); + EXPECT_EQ(state->recordPostProcessCalls, 1); + EXPECT_EQ( + state->eventLog, + (std::vector{ + "pipelineGraph", + "pipelineGraph" })); +} + +TEST(CameraRenderer_Test, RejectsManagedRequestedPostProcessWithoutSupportedStageRecorder) { + Scene scene("CameraRendererManagedRequestedPostProcessUnsupportedScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + + auto state = std::make_shared(); + state->supportsMainSceneRenderGraph = true; + CameraRenderer renderer(std::make_unique(state)); + + auto allocationState = std::make_shared(); + MockShadowDevice device(allocationState); + MockShadowView colorView( + allocationState, + XCEngine::RHI::ResourceViewType::RenderTarget, + XCEngine::RHI::Format::R8G8B8A8_UNorm, + XCEngine::RHI::ResourceViewDimension::Texture2D); + MockShadowView depthView( + allocationState, + XCEngine::RHI::ResourceViewType::DepthStencil, + XCEngine::RHI::Format::D24_UNorm_S8_UInt, + XCEngine::RHI::ResourceViewDimension::Texture2D); + + CameraRenderRequest request = {}; + request.scene = &scene; + request.camera = camera; + request.context = CreateValidContext(); + request.context.device = &device; + request.surface = RenderSurface(320, 180); + request.surface.SetColorAttachment(&colorView); + request.surface.SetDepthAttachment(&depthView); + request.cameraDepth = camera->GetDepth(); + + CameraFramePlan plan = CameraFramePlan::FromRequest(request); + ASSERT_TRUE( + plan.RequestFullscreenStage( + CameraFrameStage::PostProcess, + CameraFrameColorSource::MainSceneColor, + false)); + + ASSERT_TRUE(plan.IsPostProcessStageValid()); + EXPECT_FALSE(renderer.Render(plan)); + EXPECT_EQ(state->recordMainSceneCalls, 0); + EXPECT_EQ(state->recordPostProcessCalls, 0); +} + TEST(CameraRenderer_Test, DefaultPipelineAssetUsesManagedSelectionWhenPresent) { const Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { "GameScripts", diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index 6702c0c4..f7124406 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -434,6 +436,138 @@ TEST_F( recorder->Shutdown(); } +TEST_F( + MonoScriptRuntimeTest, + ManagedStageRecorderRecordsPostProcessThroughScriptableRenderContext) { + const auto bridge = + XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge(); + ASSERT_NE(bridge, nullptr); + + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { + "GameScripts", + "Gameplay", + "ManagedPostProcessRenderPipelineProbeAsset" + }; + + std::unique_ptr recorder = + bridge->CreateStageRecorder(descriptor); + ASSERT_NE(recorder, nullptr); + + const XCEngine::Rendering::RenderContext renderContext = {}; + ASSERT_TRUE(recorder->Initialize(renderContext)); + EXPECT_FALSE( + recorder->SupportsStageRenderGraph( + XCEngine::Rendering::CameraFrameStage::MainScene)); + ASSERT_TRUE( + recorder->SupportsStageRenderGraph( + XCEngine::Rendering::CameraFrameStage::PostProcess)); + + 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); + const XCEngine::Rendering::RenderGraphTextureHandle sourceColor = + graphBuilder.ImportTexture( + "ManagedPostProcessSource", + colorDesc, + reinterpret_cast(801), + {}); + const XCEngine::Rendering::RenderGraphTextureHandle outputColor = + graphBuilder.CreateTransientTexture("ManagedPostProcessOutput", colorDesc); + + const XCEngine::Rendering::RenderSceneData sceneData = {}; + const XCEngine::Rendering::RenderSurface surface(64u, 64u); + bool executionSucceeded = true; + XCEngine::Rendering::RenderGraphBlackboard blackboard = {}; + XCEngine::Rendering::EmplaceCameraFrameRenderGraphFrameData(blackboard) + .resources.mainScene.color = sourceColor; + const XCEngine::Rendering::RenderPipelineStageRenderGraphContext graphContext = { + graphBuilder, + "ManagedPostProcess", + XCEngine::Rendering::CameraFrameStage::PostProcess, + renderContext, + sceneData, + surface, + nullptr, + nullptr, + XCEngine::RHI::ResourceStates::Common, + {}, + { outputColor }, + {}, + &executionSucceeded, + &blackboard + }; + + EXPECT_TRUE(recorder->RecordStageRenderGraph(graphContext)); + + XCEngine::Rendering::CompiledRenderGraph compiledGraph = {}; + XCEngine::Containers::String errorMessage; + ASSERT_TRUE( + XCEngine::Rendering::RenderGraphCompiler::Compile( + graph, + compiledGraph, + &errorMessage)) + << errorMessage.CStr(); + ASSERT_EQ(compiledGraph.GetPassCount(), 2u); + EXPECT_STREQ( + compiledGraph.GetPassName(0).CStr(), + "ManagedPostProcess.Pass0"); + EXPECT_STREQ( + compiledGraph.GetPassName(1).CStr(), + "ManagedPostProcess.Pass1"); + + recorder->Shutdown(); +} + +TEST_F( + MonoScriptRuntimeTest, + ManagedRenderPipelineAssetConfiguresFullscreenStagesThroughPlanningContext) { + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { + "GameScripts", + "Gameplay", + "ManagedPlannedFullscreenRenderPipelineProbeAsset" + }; + + XCEngine::Rendering::Pipelines::ManagedScriptableRenderPipelineAsset asset( + descriptor); + + XCEngine::Rendering::CameraRenderRequest request = {}; + request.surface = XCEngine::Rendering::RenderSurface(64u, 64u); + request.surface.SetColorAttachment( + reinterpret_cast(1)); + + XCEngine::Rendering::CameraFramePlan plan = + XCEngine::Rendering::CameraFramePlan::FromRequest(request); + asset.ConfigureCameraFramePlan(plan); + + EXPECT_TRUE( + plan.IsFullscreenStageRequested( + XCEngine::Rendering::CameraFrameStage::PostProcess)); + EXPECT_TRUE( + plan.IsFullscreenStageRequested( + XCEngine::Rendering::CameraFrameStage::FinalOutput)); + EXPECT_EQ(plan.postProcess.passes, nullptr); + EXPECT_EQ(plan.finalOutput.passes, nullptr); + EXPECT_TRUE(plan.UsesGraphManagedSceneColor()); + EXPECT_TRUE( + plan.UsesGraphManagedOutputColor( + XCEngine::Rendering::CameraFrameStage::PostProcess)); + EXPECT_EQ( + plan.ResolveStageColorSource( + XCEngine::Rendering::CameraFrameStage::PostProcess), + XCEngine::Rendering::CameraFrameColorSource::MainSceneColor); + EXPECT_EQ( + plan.ResolveStageColorSource( + XCEngine::Rendering::CameraFrameStage::FinalOutput), + XCEngine::Rendering::CameraFrameColorSource::PostProcessColor); + EXPECT_TRUE(plan.IsPostProcessStageValid()); + EXPECT_TRUE(plan.IsFinalOutputStageValid()); +} + TEST_F(MonoScriptRuntimeTest, ClassFieldDefaultValueQueryReturnsNullComponentReferences) { std::vector fields;