diff --git a/docs/used/SRP_UnityStyleURPStandaloneStageBlockSemanticsPlan_完成归档_2026-04-21.md b/docs/used/SRP_UnityStyleURPStandaloneStageBlockSemanticsPlan_完成归档_2026-04-21.md new file mode 100644 index 00000000..c71f2f69 --- /dev/null +++ b/docs/used/SRP_UnityStyleURPStandaloneStageBlockSemanticsPlan_完成归档_2026-04-21.md @@ -0,0 +1,93 @@ +# SRP Unity-Style URP Standalone Stage Block Semantics Plan + +Date: 2026-04-21 + +## Goal + +Continue aligning the managed rendering stack with Unity's SRP/URP split: + +- `ScriptableRenderer` owns stage/block organization +- `RendererFeature` and `ScriptableRenderPass` inject work by `RenderPassEvent` +- standalone blocks such as shadow casting and depth prepass are valid managed recording targets + +This stage does not add deferred rendering. The purpose is to make `ShadowCaster` and `DepthOnly` first-class managed stage blocks, so later SRP work is built on a real execution seam instead of placeholder enums. + +## Current Problems + +The current code still has four structural gaps: + +1. Managed stage recording only supports `MainScene` and fullscreen stages. +2. `ScriptableRenderContext.DrawRenderersByDesc(...)` hard-blocks all non-`MainScene` stages. +3. `NativeSceneRecorder` rejects `ShadowCaster` and `DepthOnly`, so managed standalone stages cannot record meaningful scene draw work. +4. Managed `RenderPassEvent` still cannot express Unity-style shadow/prepass blocks. + +Because of this, adding `BeforeRenderingShadows` or `BeforeRenderingPrePasses` right now would only create API decoration without real execution support. + +## Scope + +### 1. Enable managed recording for standalone scene-pass stages + +Make the managed render-pipeline bridge treat `ShadowCaster` and `DepthOnly` as valid recordable stages. + +Required work: + +- relax managed stage-recording support checks +- allow `ScriptableRenderContext.DrawRenderersByDesc(...)` inside standalone scene-pass stages +- allow `NativeSceneRecorder` to build raster work for standalone scene-pass stages + +### 2. Make native scene drawing usable inside standalone stages + +The native scene path must be able to execute depth-only and shadow-caster material passes when recording through the managed bridge. + +Required work: + +- let `BuiltinForwardPipeline` render into depth-only surfaces +- extend scene-draw pass resolution so `ShadowCaster` and `DepthOnly` shader pass names are supported +- provide stage-aware defaults where needed so managed passes can target standalone stages without custom native-only code + +### 3. Add explicit planning controls for standalone stage ownership + +Match the earlier `ShadowCaster` stage-ownership cleanup and remove hidden coupling. + +Required work: + +- add explicit `DepthOnly` plan state beside `ShadowCaster` +- expose managed planning bridge methods for requesting and clearing `DepthOnly` +- keep `UniversalRenderer` / future renderer code responsible for stage existence instead of scattering it into random features + +### 4. Expand Unity-style block events + +After execution support exists, expand managed URP event semantics. + +Required work: + +- add `BeforeRenderingShadows` / `AfterRenderingShadows` +- add `BeforeRenderingPrePasses` / `AfterRenderingPrePasses` +- map those events to `CameraFrameStage.ShadowCaster` and `CameraFrameStage.DepthOnly` +- infer standalone stage requests from the queued pass list where appropriate + +## Out Of Scope + +- deferred renderer +- GBuffer +- moving all native shadow logic out of C++ in this stage +- full Unity parity for every URP event value +- editor-side UX work + +## Done Criteria + +1. Managed renderer/pass code can target `ShadowCaster` and `DepthOnly` as real render-graph stages. +2. `DrawRenderersByDesc(...)` can record scene draw work inside those standalone stages. +3. Managed `RenderPassEvent` includes Unity-style shadow/prepass block events. +4. `ScriptableRenderer` can infer standalone stage presence from its pass queue, instead of relying only on manual plan code. +5. Old `XCEditor` builds in Debug. +6. Old editor smoke passes for at least about 10 seconds and a fresh `SceneReady` appears in `editor/bin/Debug/editor.log`. + +## Follow-Up After This Stage + +Once this stage is closed, the next SRP work can safely move higher in the stack: + +- renderer-level block composition +- renderer variants / multiple renderer strategies +- managed shadow/depth passes replacing more native fallback ownership +- eventually, a cleaner path to Unity-style SRP + URP package layering diff --git a/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h b/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h index fac8290f..a7fcd9d2 100644 --- a/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h +++ b/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h @@ -41,6 +41,7 @@ struct CameraFramePlan { RenderPassSequence* postScenePasses = nullptr; RenderPassSequence* overlayPasses = nullptr; CameraFrameStandaloneStagePlan shadowCasterStage = {}; + CameraFrameStandaloneStagePlan depthOnlyStage = {}; CameraFrameColorChainPlan colorChain = {}; RenderSurface graphManagedSceneSurface = {}; @@ -74,10 +75,16 @@ struct CameraFramePlan { bool explicitlyConfigured = false); void ClearShadowCasterStage( bool explicitlyConfigured = false); + bool RequestDepthOnlyStage( + bool explicitlyConfigured = false); + void ClearDepthOnlyStage( + bool explicitlyConfigured = false); bool HasExplicitFullscreenStageConfiguration( CameraFrameStage stage) const; bool HasExplicitShadowCasterStageConfiguration() const; + bool HasExplicitDepthOnlyStageConfiguration() const; bool IsShadowCasterStageRequested() const; + bool IsDepthOnlyStageRequested() const; bool IsPostProcessStageValid() const; bool IsFinalOutputStageValid() const; bool HasFrameStage(CameraFrameStage stage) const; diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h index 7fc9f249..27447632 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -380,6 +380,8 @@ private: bool m_initialized = false; Resources::ResourceHandle m_builtinForwardShader; Resources::ResourceHandle m_builtinUnlitShader; + Resources::ResourceHandle m_builtinDepthOnlyShader; + Resources::ResourceHandle m_builtinShadowCasterShader; Resources::ResourceHandle m_builtinSkyboxShader; RenderResourceCache m_resourceCache; diff --git a/engine/src/Rendering/Execution/CameraFramePlan.cpp b/engine/src/Rendering/Execution/CameraFramePlan.cpp index ec2d4fda..85c3a6ad 100644 --- a/engine/src/Rendering/Execution/CameraFramePlan.cpp +++ b/engine/src/Rendering/Execution/CameraFramePlan.cpp @@ -63,6 +63,8 @@ CameraFramePlan CameraFramePlan::FromRequest(const CameraRenderRequest& request) plan.shadowCasterStage.requested = request.shadowCaster.IsRequested() || request.directionalShadow.IsValid(); + plan.depthOnlyStage.requested = + request.depthOnly.IsRequested(); plan.colorChain.postProcess.requested = request.postProcess.IsRequested(); plan.colorChain.finalOutput.requested = request.finalOutput.IsRequested(); return plan; @@ -232,6 +234,24 @@ void CameraFramePlan::ClearShadowCasterStage( explicitlyConfigured; } +bool CameraFramePlan::RequestDepthOnlyStage( + bool explicitlyConfigured) { + depthOnlyStage.requested = + request.depthOnly.IsRequested(); + depthOnlyStage.explicitlyConfigured = + depthOnlyStage.explicitlyConfigured || + explicitlyConfigured; + return depthOnlyStage.requested; +} + +void CameraFramePlan::ClearDepthOnlyStage( + bool explicitlyConfigured) { + depthOnlyStage.requested = false; + depthOnlyStage.explicitlyConfigured = + depthOnlyStage.explicitlyConfigured || + explicitlyConfigured; +} + bool CameraFramePlan::HasExplicitFullscreenStageConfiguration( CameraFrameStage stage) const { if (const CameraFrameFullscreenStagePlan* fullscreenStagePlan = @@ -247,10 +267,18 @@ bool CameraFramePlan::HasExplicitShadowCasterStageConfiguration() const { return shadowCasterStage.explicitlyConfigured; } +bool CameraFramePlan::HasExplicitDepthOnlyStageConfiguration() const { + return depthOnlyStage.explicitlyConfigured; +} + bool CameraFramePlan::IsShadowCasterStageRequested() const { return shadowCasterStage.requested; } +bool CameraFramePlan::IsDepthOnlyStageRequested() const { + return depthOnlyStage.requested; +} + bool CameraFramePlan::IsPostProcessStageValid() const { if (!IsFullscreenStageRequested(CameraFrameStage::PostProcess)) { return true; @@ -318,7 +346,7 @@ bool CameraFramePlan::HasFrameStage(CameraFrameStage stage) const { case CameraFrameStageRequestKind::ShadowCaster: return IsShadowCasterStageRequested(); case CameraFrameStageRequestKind::DepthOnly: - return request.depthOnly.IsRequested(); + return IsDepthOnlyStageRequested(); case CameraFrameStageRequestKind::ObjectId: return request.objectId.IsRequested(); default: diff --git a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceResolver.h b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceResolver.h index 20f7a8c8..39cdc396 100644 --- a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceResolver.h +++ b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceResolver.h @@ -25,7 +25,9 @@ inline const ScenePassRenderRequest* ResolveCameraFrameStageRuntimeScenePassRequ case CameraFrameStageRequestKind::DepthOnly: if (const ScenePassRenderRequest* request = plan.GetScenePassRequest(stage); - request != nullptr && request->IsRequested()) { + request != nullptr && + plan.IsDepthOnlyStageRequested() && + request->IsRequested()) { return request; } diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp index 8073268f..61d5bfb4 100644 --- a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp @@ -148,7 +148,8 @@ bool BuiltinForwardPipeline::BeginForwardScenePass( RHI::RHIResourceView* depthAttachment = surface.GetDepthAttachment(); std::vector renderTargets = CollectSurfaceColorAttachments(surface); - if (renderTargets.empty()) { + if (renderTargets.empty() && + depthAttachment == nullptr) { return false; } @@ -178,7 +179,9 @@ bool BuiltinForwardPipeline::BeginForwardScenePass( commandList->SetRenderTargets( static_cast(renderTargets.size()), - renderTargets.data(), + renderTargets.empty() + ? nullptr + : renderTargets.data(), depthAttachment); const RHI::Viewport viewport = { diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineLifecycle.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineLifecycle.cpp index 5e4a3932..dea2eeaa 100644 --- a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineLifecycle.cpp +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineLifecycle.cpp @@ -56,6 +56,26 @@ bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& contex return false; } + m_builtinDepthOnlyShader = + Resources::ResourceManager::Get().Load( + Resources::GetBuiltinDepthOnlyShaderPath()); + if (!m_builtinDepthOnlyShader.IsValid()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline failed to load builtin depth-only shader resource"); + return false; + } + + m_builtinShadowCasterShader = + Resources::ResourceManager::Get().Load( + Resources::GetBuiltinShadowCasterShaderPath()); + if (!m_builtinShadowCasterShader.IsValid()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline failed to load builtin shadow-caster shader resource"); + return false; + } + m_builtinSkyboxShader = Resources::ResourceManager::Get().Load( Resources::GetBuiltinSkyboxShaderPath()); if (!m_builtinSkyboxShader.IsValid()) { @@ -222,6 +242,8 @@ void BuiltinForwardPipeline::DestroyPipelineResources() { m_initialized = false; m_builtinForwardShader.Reset(); m_builtinUnlitShader.Reset(); + m_builtinDepthOnlyShader.Reset(); + m_builtinShadowCasterShader.Reset(); m_builtinSkyboxShader.Reset(); } diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineSurface.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineSurface.cpp index ad318ba3..cf5986b9 100644 --- a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineSurface.cpp +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineSurface.cpp @@ -129,6 +129,16 @@ bool TryResolveRequestedSurfacePassType( return true; } + if (MatchesBuiltinPassName(normalizedPassName, BuiltinMaterialPass::DepthOnly)) { + outPass = BuiltinMaterialPass::DepthOnly; + return true; + } + + if (MatchesBuiltinPassName(normalizedPassName, BuiltinMaterialPass::ShadowCaster)) { + outPass = BuiltinMaterialPass::ShadowCaster; + return true; + } + return false; } @@ -201,6 +211,12 @@ bool BuiltinForwardPipeline::TryResolveSurfacePassType( const BuiltinMaterialPass* preferredPass, BuiltinMaterialPass& outPass) { if (preferredPass != nullptr) { + if (*preferredPass == BuiltinMaterialPass::DepthOnly || + *preferredPass == BuiltinMaterialPass::ShadowCaster) { + outPass = *preferredPass; + return true; + } + if (MatchesBuiltinPass(material, *preferredPass)) { outPass = *preferredPass; return true; @@ -249,7 +265,22 @@ BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfac } const Resources::ResourceHandle* builtinShaderHandle = - pass == BuiltinMaterialPass::Unlit ? &m_builtinUnlitShader : &m_builtinForwardShader; + nullptr; + switch (pass) { + case BuiltinMaterialPass::Unlit: + builtinShaderHandle = &m_builtinUnlitShader; + break; + case BuiltinMaterialPass::DepthOnly: + builtinShaderHandle = &m_builtinDepthOnlyShader; + break; + case BuiltinMaterialPass::ShadowCaster: + builtinShaderHandle = &m_builtinShadowCasterShader; + break; + case BuiltinMaterialPass::ForwardLit: + default: + builtinShaderHandle = &m_builtinForwardShader; + break; + } if (builtinShaderHandle->IsValid()) { const Resources::Shader* builtinShader = builtinShaderHandle->Get(); if (const Resources::ShaderPass* shaderPass = @@ -654,7 +685,7 @@ bool BuiltinForwardPipeline::DrawVisibleItems( Debug::Logger::Get().Error( Debug::LogCategory::Rendering, (Containers::String( - "BuiltinForwardPipeline only supports explicit shaderPassName values of ForwardLit or Unlit for scene draws, received: ") + + "BuiltinForwardPipeline only supports explicit shaderPassName values of ForwardLit, Unlit, DepthOnly, or ShadowCaster for scene draws, received: ") + drawSettings.shaderPassName) .CStr()); return false; diff --git a/engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp b/engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp index 95ec441f..f94ef758 100644 --- a/engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp +++ b/engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp @@ -1,6 +1,7 @@ #include #include "Debug/Logger.h" +#include "Rendering/FrameData/RendererListUtils.h" #include "Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.h" #include @@ -21,17 +22,100 @@ struct NativeSceneRecorderState { namespace { -bool ScenePhaseSamplesMainDirectionalShadow(ScenePhase scenePhase) { +bool IsStandaloneSceneRecordingStage(CameraFrameStage stage) { + return IsCameraFrameScenePassRequestStage(stage); +} + +bool SupportsSceneRecordingStage(CameraFrameStage stage) { + return stage == CameraFrameStage::MainScene || + IsStandaloneSceneRecordingStage(stage); +} + +bool SupportsStandaloneScenePhase(ScenePhase scenePhase) { return scenePhase == ScenePhase::Opaque || scenePhase == ScenePhase::Transparent; } +bool ScenePhaseSamplesMainDirectionalShadow( + CameraFrameStage stage, + ScenePhase scenePhase) { + return stage == CameraFrameStage::MainScene && + SupportsStandaloneScenePhase(scenePhase); +} + bool DrawSettingsSamplesMainDirectionalShadow( + CameraFrameStage stage, const DrawSettings& drawSettings) { return ScenePhaseSamplesMainDirectionalShadow( + stage, drawSettings.scenePhase); } +RendererListDesc BuildStandaloneStageRendererListDesc( + CameraFrameStage stage, + ScenePhase scenePhase) { + const RendererListType rendererListType = + stage == CameraFrameStage::ShadowCaster + ? RendererListType::ShadowCaster + : RendererListType::AllVisible; + RendererListDesc rendererListDesc = + BuildDefaultRendererListDesc(rendererListType); + + switch (scenePhase) { + case ScenePhase::Opaque: + rendererListDesc.filtering.renderQueueMax = + static_cast( + Resources::MaterialRenderQueue::Transparent) - + 1; + rendererListDesc.sorting.sortMode = + RendererSortMode::FrontToBack; + break; + case ScenePhase::Transparent: + rendererListDesc.filtering.renderQueueMin = + static_cast( + Resources::MaterialRenderQueue::Transparent); + rendererListDesc.sorting.sortMode = + RendererSortMode::BackToFront; + break; + default: + break; + } + + return rendererListDesc; +} + +bool TryBuildStandaloneScenePhaseDrawSettings( + CameraFrameStage stage, + ScenePhase scenePhase, + DrawSettings& outDrawSettings) { + if (!IsStandaloneSceneRecordingStage(stage) || + !SupportsStandaloneScenePhase(scenePhase)) { + return false; + } + + outDrawSettings = {}; + outDrawSettings.scenePhase = scenePhase; + outDrawSettings.rendererListDesc = + BuildStandaloneStageRendererListDesc( + stage, + scenePhase); + return true; +} + +void ApplyStandaloneScenePassDefaults( + CameraFrameStage stage, + DrawSettings& drawSettings) { + if (!IsStandaloneSceneRecordingStage(stage) || + drawSettings.HasShaderPassName()) { + return; + } + + drawSettings.shaderPassName = + stage == CameraFrameStage::ShadowCaster + ? Containers::String("ShadowCaster") + : Containers::String("DepthOnly"); +} + } // namespace NativeSceneRecorder::NativeSceneRecorder( @@ -143,54 +227,77 @@ bool NativeSceneRecorder::RecordDefaultScene() { bool NativeSceneRecorder::RecordScenePhase( ScenePhase scenePhase) { - if (m_context.stage != CameraFrameStage::MainScene) { - return false; - } - - const RenderPipelineStageRenderGraphContext graphContext = - BuildGraphContext(); - const bool clearAttachments = m_clearAttachments; - const SceneRenderFeaturePassBeginCallback beginRecordedPass = - BuildBeginRecordedPassCallback(graphContext.executionSucceeded); - const SceneRenderFeaturePassEndCallback endRecordedPass = - BuildEndRecordedPassCallback(); - const RenderPassGraphBeginCallback beginPhasePass = - [beginRecordedPass, clearAttachments]( - const RenderPassContext& passContext) { - return beginRecordedPass( - passContext, - clearAttachments); - }; - const std::vector additionalReadTextures = - ScenePhaseSamplesMainDirectionalShadow(scenePhase) && - m_mainDirectionalShadowTexture.IsValid() - ? std::vector{ m_mainDirectionalShadowTexture } - : std::vector{}; - if (!::XCEngine::Rendering::RecordRenderPipelineStagePhasePass( - graphContext, - scenePhase, - [&sceneRenderer = m_sceneRenderer, scenePhase]( + if (m_context.stage == CameraFrameStage::MainScene) { + const RenderPipelineStageRenderGraphContext graphContext = + BuildGraphContext(); + const bool clearAttachments = m_clearAttachments; + const SceneRenderFeaturePassBeginCallback beginRecordedPass = + BuildBeginRecordedPassCallback(graphContext.executionSucceeded); + const SceneRenderFeaturePassEndCallback endRecordedPass = + BuildEndRecordedPassCallback(); + const RenderPassGraphBeginCallback beginPhasePass = + [beginRecordedPass, clearAttachments]( const RenderPassContext& passContext) { - return sceneRenderer.ExecuteScenePhase( + return beginRecordedPass( passContext, - scenePhase); - }, - beginPhasePass, - endRecordedPass, - additionalReadTextures)) { + clearAttachments); + }; + const std::vector additionalReadTextures = + ScenePhaseSamplesMainDirectionalShadow( + m_context.stage, + scenePhase) && + m_mainDirectionalShadowTexture.IsValid() + ? std::vector{ + m_mainDirectionalShadowTexture } + : std::vector{}; + if (!::XCEngine::Rendering::RecordRenderPipelineStagePhasePass( + graphContext, + scenePhase, + [&sceneRenderer = m_sceneRenderer, scenePhase]( + const RenderPassContext& passContext) { + return sceneRenderer.ExecuteScenePhase( + passContext, + scenePhase); + }, + beginPhasePass, + endRecordedPass, + additionalReadTextures)) { + return false; + } + + m_clearAttachments = false; + return true; + } + + DrawSettings drawSettings = {}; + if (!TryBuildStandaloneScenePhaseDrawSettings( + m_context.stage, + scenePhase, + drawSettings)) { return false; } - m_clearAttachments = false; - return true; + return RecordSceneDrawSettings(drawSettings); } bool NativeSceneRecorder::RecordSceneDrawSettings( const DrawSettings& drawSettings) { - if (m_context.stage != CameraFrameStage::MainScene) { + if (!SupportsSceneRecordingStage(m_context.stage)) { return false; } + DrawSettings effectiveDrawSettings = drawSettings; + if (IsStandaloneSceneRecordingStage(m_context.stage)) { + if (!SupportsStandaloneScenePhase( + effectiveDrawSettings.scenePhase)) { + return false; + } + + ApplyStandaloneScenePassDefaults( + m_context.stage, + effectiveDrawSettings); + } + const RenderPipelineStageRenderGraphContext graphContext = BuildGraphContext(); const bool clearAttachments = m_clearAttachments; @@ -206,18 +313,20 @@ bool NativeSceneRecorder::RecordSceneDrawSettings( clearAttachments); }; const std::vector additionalReadTextures = - DrawSettingsSamplesMainDirectionalShadow(drawSettings) && + DrawSettingsSamplesMainDirectionalShadow( + m_context.stage, + effectiveDrawSettings) && m_mainDirectionalShadowTexture.IsValid() ? std::vector{ m_mainDirectionalShadowTexture } : std::vector{}; if (!::XCEngine::Rendering::RecordRenderPipelineStagePhasePass( graphContext, - drawSettings.scenePhase, - [&sceneRenderer = m_sceneRenderer, drawSettings]( + effectiveDrawSettings.scenePhase, + [&sceneRenderer = m_sceneRenderer, effectiveDrawSettings]( const RenderPassContext& passContext) { return sceneRenderer.ExecuteSceneDrawSettings( passContext, - drawSettings); + effectiveDrawSettings); }, beginPhasePass, endRecordedPass, diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 0dff1566..78d9bb0e 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -1261,7 +1261,14 @@ Rendering::FinalColorSettings BuildManagedFinalColorSettings( bool SupportsManagedRenderPipelineStageGraphRecording( Rendering::CameraFrameStage stage) { return Rendering::SupportsCameraFramePipelineGraphRecording(stage) || - Rendering::IsCameraFrameFullscreenSequenceStage(stage); + Rendering::IsCameraFrameFullscreenSequenceStage(stage) || + Rendering::IsCameraFrameScenePassRequestStage(stage); +} + +bool SupportsManagedScriptableRenderContextSceneRecordingStage( + Rendering::CameraFrameStage stage) { + return stage == Rendering::CameraFrameStage::MainScene || + Rendering::IsCameraFrameScenePassRequestStage(stage); } Rendering::RenderPass* ConfigureManagedFullscreenPass( @@ -5532,7 +5539,8 @@ InternalCall_Rendering_ScriptableRenderContext_RecordScenePhase( if (state == nullptr || state->graphContext == nullptr || state->sceneRecorder == nullptr || - state->stage != Rendering::CameraFrameStage::MainScene) { + !SupportsManagedScriptableRenderContextSceneRecordingStage( + state->stage)) { return 0; } @@ -5599,7 +5607,8 @@ InternalCall_Rendering_ScriptableRenderContext_DrawRenderersByDesc( if (state == nullptr || state->graphContext == nullptr || state->sceneRecorder == nullptr || - state->stage != Rendering::CameraFrameStage::MainScene || + !SupportsManagedScriptableRenderContextSceneRecordingStage( + state->stage) || rendererListDescData == nullptr) { return 0; } @@ -5905,6 +5914,29 @@ void InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearShadowC state->plan->ClearShadowCasterStage(true); } +mono_bool +InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestDepthOnlyStage( + uint64_t nativeHandle) { + ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + return state != nullptr && + state->plan != nullptr && + state->plan->RequestDepthOnlyStage(true) + ? 1 + : 0; +} + +void InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearDepthOnlyStage( + uint64_t nativeHandle) { + ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + if (state == nullptr || state->plan == nullptr) { + return; + } + + state->plan->ClearDepthOnlyStage(true); +} + mono_bool InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetHasFinalColorProcessing( uint64_t nativeHandle) { @@ -6123,6 +6155,8 @@ void RegisterInternalCalls() { mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_RequestShadowCasterStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestShadowCasterStage)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearShadowCasterStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearShadowCasterStage)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_RequestDepthOnlyStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestDepthOnlyStage)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearDepthOnlyStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearDepthOnlyStage)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetHasFinalColorProcessing", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetHasFinalColorProcessing)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetRenderedBaseCameraCount", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_GetRenderedBaseCameraCount)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetRenderedRequestCount", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_GetRenderedRequestCount)); diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/DrawObjectsPass.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/DrawObjectsPass.cs index b0dc6040..a66e83e7 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/DrawObjectsPass.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/DrawObjectsPass.cs @@ -68,7 +68,7 @@ namespace XCEngine.Rendering.Universal { return context != null && renderingData != null && - renderingData.isMainSceneStage && + renderingData.isSceneDrawStage && context.DrawRenderers( m_scenePhase, m_rendererListDesc, diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderPassEvent.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderPassEvent.cs index e00383ff..fd9c1c8d 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderPassEvent.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderPassEvent.cs @@ -5,6 +5,10 @@ namespace XCEngine.Rendering.Universal { public enum RenderPassEvent { + BeforeRenderingShadows = 50, + AfterRenderingShadows = 60, + BeforeRenderingPrePasses = 70, + AfterRenderingPrePasses = 80, BeforeRenderingOpaques = 100, RenderOpaques = 110, AfterRenderingOpaques = 120, diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs index 57c28b91..01d06044 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs @@ -100,6 +100,17 @@ namespace XCEngine.Rendering.Universal public bool isMainSceneStage => stage == CameraFrameStage.MainScene; + public bool isShadowCasterStage => + stage == CameraFrameStage.ShadowCaster; + + public bool isDepthOnlyStage => + stage == CameraFrameStage.DepthOnly; + + public bool isSceneDrawStage => + isMainSceneStage || + isShadowCasterStage || + isDepthOnlyStage; + public bool isPostProcessStage => stage == CameraFrameStage.PostProcess; diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderPass.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderPass.cs index 6f857f04..715edee8 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderPass.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderPass.cs @@ -212,6 +212,14 @@ namespace XCEngine.Rendering.Universal { switch (passEvent) { + case RenderPassEvent.BeforeRenderingShadows: + case RenderPassEvent.AfterRenderingShadows: + stage = CameraFrameStage.ShadowCaster; + return true; + case RenderPassEvent.BeforeRenderingPrePasses: + case RenderPassEvent.AfterRenderingPrePasses: + stage = CameraFrameStage.DepthOnly; + return true; case RenderPassEvent.BeforeRenderingOpaques: case RenderPassEvent.RenderOpaques: case RenderPassEvent.AfterRenderingOpaques: diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs index 93311065..8b982cc5 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs @@ -245,6 +245,8 @@ namespace XCEngine.Rendering.Universal } BuildPassQueue(planningData); + ApplyInferredStandaloneStageRequests( + context); bool hasPostProcessPass = HasQueuedPassForStage( @@ -278,6 +280,22 @@ namespace XCEngine.Rendering.Universal } } + private void ApplyInferredStandaloneStageRequests( + ScriptableRenderPipelinePlanningContext context) + { + if (HasQueuedPassForStage( + CameraFrameStage.ShadowCaster)) + { + context.RequestShadowCasterStage(); + } + + if (HasQueuedPassForStage( + CameraFrameStage.DepthOnly)) + { + context.RequestDepthOnlyStage(); + } + } + private bool HasQueuedPassForStage( CameraFrameStage stage) { diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs index e235b790..87523370 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs @@ -44,6 +44,8 @@ namespace XCEngine.Rendering.Universal { case CameraFrameStage.ShadowCaster: return "BuiltinShadowCaster"; + case CameraFrameStage.DepthOnly: + return "BuiltinDepthOnly"; default: return base .GetCameraFrameStandalonePassAssetKey( diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index 5feefa3c..0552ba9e 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -532,6 +532,16 @@ namespace XCEngine Rendering_ScriptableRenderPipelinePlanningContext_ClearShadowCasterStage( ulong nativeHandle); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool + Rendering_ScriptableRenderPipelinePlanningContext_RequestDepthOnlyStage( + ulong nativeHandle); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void + Rendering_ScriptableRenderPipelinePlanningContext_ClearDepthOnlyStage( + ulong nativeHandle); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern bool Rendering_ScriptableRenderPipelinePlanningContext_GetHasFinalColorProcessing( diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs index 1c09c229..dcca8502 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs @@ -80,6 +80,20 @@ namespace XCEngine.Rendering m_nativeHandle); } + public bool RequestDepthOnlyStage() + { + return InternalCalls + .Rendering_ScriptableRenderPipelinePlanningContext_RequestDepthOnlyStage( + m_nativeHandle); + } + + public void ClearDepthOnlyStage() + { + InternalCalls + .Rendering_ScriptableRenderPipelinePlanningContext_ClearDepthOnlyStage( + m_nativeHandle); + } + public bool HasFinalColorProcessing() { return InternalCalls