using System.Collections.Generic; using XCEngine; using XCEngine.Rendering; using XCEngine.Rendering.RenderGraphModule; namespace XCEngine.Rendering.Universal { public abstract class ScriptableRenderer { private struct PassQueueStageManifest { public bool shadowCaster; public bool depthOnly; public bool postProcess; public bool finalOutput; } private readonly List m_features = new List(); private readonly List m_activePassQueue = new List(); private readonly RendererBlocks m_rendererBlocks = new RendererBlocks(); private readonly CommandBuffer m_finishCameraStackCommandBuffer = new CommandBuffer("ScriptableRenderer.FinishCameraStack"); private bool m_isBuildingPassQueue; private CameraFrameStage m_passQueueStage = CameraFrameStage.MainScene; private bool m_disposed; protected ScriptableRenderer() { } internal void ReleaseRuntimeResourcesInstance() { if (m_disposed) { return; } ReleaseRuntimeResources(); for (int i = 0; i < m_features.Count; ++i) { ScriptableRendererFeature feature = m_features[i]; if (feature != null) { feature.ReleaseRuntimeResourcesInstance(); } } m_features.Clear(); m_activePassQueue.Clear(); m_rendererBlocks.Clear(); m_disposed = true; } public void EnqueuePass( ScriptableRenderPass renderPass) { if (renderPass == null) { return; } if (m_isBuildingPassQueue && !renderPass.SupportsStage(m_passQueueStage)) { return; } InsertActivePass( renderPass); } private void InsertActivePass( ScriptableRenderPass renderPass) { int insertIndex = m_activePassQueue.Count; while (insertIndex > 0 && m_activePassQueue[insertIndex - 1].renderPassEvent > renderPass.renderPassEvent) { insertIndex--; } m_activePassQueue.Insert( insertIndex, renderPass); } protected void AddFeature( ScriptableRendererFeature feature) { if (feature == null) { return; } m_features.Add(feature); feature.CreateInstance(); } protected virtual void AddRenderPasses( RenderingData renderingData) { } internal void AddFeatureInstance( ScriptableRendererFeature feature) { AddFeature(feature); } internal bool SupportsRendererRecordingInstance( RendererRecordingContext context) { return SupportsRendererRecording(context); } internal void ConfigureCameraFramePlanInstance( ScriptableRenderPipelinePlanningContext context) { ConfigureCameraFramePlan(context); ConfigureRendererFeaturesCameraFramePlan(context); } internal void ConfigurePassQueueCameraFramePlanInstance( ScriptableRenderPipelinePlanningContext context) { if (context == null) { return; } PassQueueStageManifest manifest = BuildPassQueueStageManifest(context); ApplyPassQueueStageManifest(context, manifest); ClearPassQueue(); } internal void FinalizeCameraFramePlanInstance( ScriptableRenderPipelinePlanningContext context) { FinalizeCameraFramePlan(context); } internal void ConfigureRenderSceneSetupInstance( RenderSceneSetupContext context) { ConfigureRenderSceneSetup(context); ConfigureRendererFeaturesRenderSceneSetup(context); } internal void ConfigureDirectionalShadowExecutionStateInstance( DirectionalShadowExecutionContext context) { ConfigureDirectionalShadowExecutionState(context); ConfigureRendererFeaturesDirectionalShadowExecutionState( context); } internal bool RecordRendererInstance( RendererRecordingContext context) { return RecordRenderer(context); } protected virtual bool SupportsRendererRecording( RendererRecordingContext context) { if (context == null) { return false; } RenderingData renderingData = context.renderingData; BuildPassQueue(renderingData); return SupportsRendererStage(context); } protected virtual bool RecordRenderer( RendererRecordingContext context) { if (context == null || context.renderContext == null) { return false; } RenderingData renderingData = context.renderingData; BuildPassQueue(renderingData); bool recorded; using (ContextContainer frameData = ScriptableRenderPass.BuildRenderGraphFrameData( renderingData)) { recorded = RecordRendererStage( context, new RenderGraph(context.renderContext), frameData); } if (recorded) { FinishCameraStackRendering(); } return recorded; } protected internal virtual bool SupportsStageRenderGraph( CameraFrameStage stage) { return SupportsRendererRecording( new RendererRecordingContext(stage)); } protected internal virtual bool RecordStageRenderGraph( ScriptableRenderContext context) { return context != null && RecordRenderer( new RendererRecordingContext(context)); } protected virtual void ConfigureCameraFramePlan( ScriptableRenderPipelinePlanningContext context) { } protected virtual void FinalizeCameraFramePlan( ScriptableRenderPipelinePlanningContext context) { } protected virtual void ConfigureRenderSceneSetup( RenderSceneSetupContext context) { } protected virtual void ConfigureDirectionalShadowExecutionState( DirectionalShadowExecutionContext context) { } private void ConfigureRendererFeaturesCameraFramePlan( ScriptableRenderPipelinePlanningContext context) { for (int i = 0; i < m_features.Count; ++i) { ScriptableRendererFeature feature = m_features[i]; if (feature != null && feature.isActive) { feature.ConfigureCameraFramePlan(context); } } } private void ConfigureRendererFeaturesRenderSceneSetup( RenderSceneSetupContext context) { for (int i = 0; i < m_features.Count; ++i) { ScriptableRendererFeature feature = m_features[i]; if (feature != null && feature.isActive) { feature.ConfigureRenderSceneSetup(context); } } } private void ConfigureRendererFeaturesDirectionalShadowExecutionState( DirectionalShadowExecutionContext context) { for (int i = 0; i < m_features.Count; ++i) { ScriptableRendererFeature feature = m_features[i]; if (feature != null && feature.isActive) { feature.ConfigureDirectionalShadowExecutionState( context); } } } private bool HasRendererBlock( RendererBlock block) { return m_rendererBlocks.HasPasses(block); } protected virtual bool SupportsRendererStage( RendererRecordingContext context) { if (context == null) { return false; } return HasRendererStageBlocks(context.stage); } private bool HasRendererStageBlocks( CameraFrameStage stage) { switch (stage) { case CameraFrameStage.ShadowCaster: return HasRendererBlock( RendererBlock.ShadowCaster); case CameraFrameStage.DepthOnly: return HasRendererBlock( RendererBlock.DepthPrepass); case CameraFrameStage.MainScene: return HasRendererBlock( RendererBlock.MainOpaque) || HasRendererBlock( RendererBlock.MainSkybox) || HasRendererBlock( RendererBlock.MainTransparent); case CameraFrameStage.PostProcess: return HasRendererBlock( RendererBlock.PostProcess); case CameraFrameStage.FinalOutput: return HasRendererBlock( RendererBlock.FinalOutput); default: return false; } } private PassQueueStageManifest BuildPassQueueStageManifest( ScriptableRenderPipelinePlanningContext context) { bool finalColorRequiresProcessing = context.HasFinalColorProcessing(); int rendererIndex = context.rendererIndex; return new PassQueueStageManifest { shadowCaster = HasPassQueueStage( CameraFrameStage.ShadowCaster, rendererIndex, finalColorRequiresProcessing), depthOnly = HasPassQueueStage( CameraFrameStage.DepthOnly, rendererIndex, finalColorRequiresProcessing), postProcess = HasPassQueueStage( CameraFrameStage.PostProcess, rendererIndex, finalColorRequiresProcessing), finalOutput = HasPassQueueStage( CameraFrameStage.FinalOutput, rendererIndex, finalColorRequiresProcessing) }; } private bool HasPassQueueStage( CameraFrameStage stage, int rendererIndex, bool finalColorRequiresProcessing) { BuildPassQueue( RenderingData.CreatePlanning( stage, rendererIndex, finalColorRequiresProcessing)); return HasRendererStageBlocks(stage); } private static void ApplyPassQueueStageManifest( ScriptableRenderPipelinePlanningContext context, PassQueueStageManifest manifest) { if (manifest.shadowCaster) { context.RequestShadowCasterStage(); } else { context.ClearShadowCasterStage(); } if (manifest.depthOnly) { context.RequestCameraDepthOnlyStage(); } else { context.ClearDepthOnlyStage(); } ApplyPostProcessStageManifest(context, manifest); ApplyFinalOutputStageManifest(context, manifest); } private static void ApplyPostProcessStageManifest( ScriptableRenderPipelinePlanningContext context, PassQueueStageManifest manifest) { if (!manifest.postProcess) { context.ClearFullscreenStage( CameraFrameStage.PostProcess); return; } bool finalOutputReadsPostProcess = ShouldFinalOutputReadPostProcess( context, manifest); if (!context.IsStageRequested( CameraFrameStage.PostProcess)) { context.RequestFullscreenStage( CameraFrameStage.PostProcess, CameraFrameColorSource.MainSceneColor, finalOutputReadsPostProcess); return; } if (finalOutputReadsPostProcess && !context.UsesGraphManagedOutputColor( CameraFrameStage.PostProcess)) { CameraFrameColorSource source = context.GetStageColorSource( CameraFrameStage.PostProcess); context.ClearFullscreenStage( CameraFrameStage.PostProcess); context.RequestFullscreenStage( CameraFrameStage.PostProcess, source, true); } } private static bool ShouldFinalOutputReadPostProcess( ScriptableRenderPipelinePlanningContext context, PassQueueStageManifest manifest) { if (!manifest.finalOutput || !manifest.postProcess) { return false; } return !context.IsStageRequested( CameraFrameStage.PostProcess) || context.GetStageColorSource( CameraFrameStage.PostProcess) != CameraFrameColorSource.ExplicitSurface; } private static void ApplyFinalOutputStageManifest( ScriptableRenderPipelinePlanningContext context, PassQueueStageManifest manifest) { if (!manifest.finalOutput) { context.ClearFullscreenStage( CameraFrameStage.FinalOutput); return; } if (context.IsStageRequested( CameraFrameStage.FinalOutput) && context.GetStageColorSource( CameraFrameStage.FinalOutput) == CameraFrameColorSource.ExplicitSurface) { return; } context.ClearFullscreenStage( CameraFrameStage.FinalOutput); context.RequestFullscreenStage( CameraFrameStage.FinalOutput, ResolveFinalOutputSource(context)); } private static CameraFrameColorSource ResolveFinalOutputSource( ScriptableRenderPipelinePlanningContext context) { if (context == null || !context.IsStageRequested( CameraFrameStage.PostProcess)) { return CameraFrameColorSource.MainSceneColor; } return context.GetStageColorSource( CameraFrameStage.PostProcess) == CameraFrameColorSource.ExplicitSurface ? CameraFrameColorSource.MainSceneColor : CameraFrameColorSource.PostProcessColor; } protected virtual bool RecordRendererStage( RendererRecordingContext context, RenderGraph renderGraph, ContextContainer frameData) { if (context == null || context.renderContext == null) { return false; } bool recordedAnyPass = false; switch (context.stage) { case CameraFrameStage.ShadowCaster: return RecordRendererBlock( RendererBlock.ShadowCaster, context, renderGraph, frameData, ref recordedAnyPass) && recordedAnyPass; case CameraFrameStage.DepthOnly: return RecordRendererBlock( RendererBlock.DepthPrepass, context, renderGraph, frameData, ref recordedAnyPass) && recordedAnyPass; case CameraFrameStage.MainScene: return RecordRendererBlock( RendererBlock.MainOpaque, context, renderGraph, frameData, ref recordedAnyPass) && RecordRendererBlock( RendererBlock.MainSkybox, context, renderGraph, frameData, ref recordedAnyPass) && RecordRendererBlock( RendererBlock.MainTransparent, context, renderGraph, frameData, ref recordedAnyPass) && recordedAnyPass; case CameraFrameStage.PostProcess: return RecordRendererBlock( RendererBlock.PostProcess, context, renderGraph, frameData, ref recordedAnyPass) && recordedAnyPass; case CameraFrameStage.FinalOutput: return RecordRendererBlock( RendererBlock.FinalOutput, context, renderGraph, frameData, ref recordedAnyPass) && recordedAnyPass; default: return false; } } private bool RecordRendererBlock( RendererBlock block, RendererRecordingContext context, RenderGraph renderGraph, ContextContainer frameData, ref bool recordedAnyPass) { if (context == null || context.renderContext == null) { return false; } int firstPassIndex; int lastPassIndex; if (!m_rendererBlocks.TryGetPassRange( block, out firstPassIndex, out lastPassIndex)) { return true; } for (int i = firstPassIndex; i <= lastPassIndex; ++i) { ScriptableRenderPass renderPass = m_activePassQueue[i]; if (renderPass == null || !renderPass.SupportsRendererBlock(block)) { continue; } if (!renderPass.Record( context.renderContext, context.renderingData, renderGraph, frameData)) { return false; } recordedAnyPass = true; } return true; } private void BuildPassQueue( RenderingData renderingData) { ClearPassQueue(); m_isBuildingPassQueue = true; m_passQueueStage = renderingData != null ? renderingData.stage : CameraFrameStage.MainScene; try { for (int i = 0; i < m_features.Count; ++i) { ScriptableRendererFeature feature = m_features[i]; if (feature == null || !feature.isActive) { continue; } feature.SetupRenderPasses( this, in renderingData); feature.AddRenderPasses( this, ref renderingData); } AddRenderPasses(renderingData); m_rendererBlocks.Build(m_activePassQueue); } finally { m_isBuildingPassQueue = false; m_passQueueStage = CameraFrameStage.MainScene; } } private void ClearPassQueue() { m_activePassQueue.Clear(); m_rendererBlocks.Clear(); } private void FinishCameraStackRendering() { for (int i = 0; i < m_activePassQueue.Count; ++i) { ScriptableRenderPass renderPass = m_activePassQueue[i]; if (renderPass == null) { continue; } renderPass.OnFinishCameraStackRendering( m_finishCameraStackCommandBuffer); } } protected virtual void ReleaseRuntimeResources() { } } }