using System.Collections.Generic; using XCEngine; using XCEngine.Rendering; using XCEngine.Rendering.RenderGraphModule; namespace XCEngine.Rendering.Universal { public abstract class ScriptableRenderer { private const int kMaxFramePlanSnapshotCount = 64; private struct PassQueueStageManifest { public bool shadowCaster; public bool depthOnly; public bool postProcess; public bool finalOutput; } private sealed class RendererFramePlan { public RendererFramePlan( ulong framePlanId, int rendererIndex, List activePassQueue) { this.framePlanId = framePlanId; this.rendererIndex = rendererIndex; this.activePassQueue = activePassQueue ?? new List(); } public readonly ulong framePlanId; public readonly int rendererIndex; public readonly List activePassQueue; public PassQueueStageManifest manifest; } private readonly List m_features = new List(); private readonly List m_activePassQueue = new List(); private readonly RendererBlocks m_rendererBlocks = new RendererBlocks(); private readonly Dictionary m_framePlans = new Dictionary(); private readonly List m_pendingCameraStackFinishPasses = new List(); private readonly List m_pendingCameraStackFinishFramePlans = new List(); private readonly CommandBuffer m_finishCameraStackCommandBuffer = new CommandBuffer("ScriptableRenderer.FinishCameraStack"); 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_framePlans.Clear(); m_pendingCameraStackFinishPasses.Clear(); m_pendingCameraStackFinishFramePlans.Clear(); m_disposed = true; } public void EnqueuePass( ScriptableRenderPass renderPass) { if (renderPass == null) { 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( RendererPassQueueData passQueueData) { } 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; } RendererFramePlan framePlan = BuildRendererFramePlan(context); ApplyPassQueueStageManifest(context, framePlan.manifest); StoreRendererFramePlan(framePlan); 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); } internal void FinishCameraStackRenderingInstance( RendererRecordingContext context) { FinishCameraStackRendering(context); } protected virtual bool SupportsRendererRecording( RendererRecordingContext context) { if (context == null) { return false; } RenderingData renderingData = context.renderingData; RendererFramePlan framePlan; if (TryGetRendererFramePlan( context, out framePlan)) { return SupportsRendererRecordingFromFramePlan( context, framePlan); } if (context.framePlanId != 0UL) { return false; } BuildPassQueue( RendererPassQueueData.FromRenderingData( renderingData)); try { return SupportsRendererStage(context); } finally { ClearPassQueue(); } } protected virtual bool RecordRenderer( RendererRecordingContext context) { if (context == null || context.renderContext == null) { return false; } RenderingData renderingData = context.renderingData; RendererFramePlan framePlan; if (TryGetRendererFramePlan( context, out framePlan)) { return RecordRendererFromFramePlan( context, framePlan); } if (context.framePlanId != 0UL) { return false; } BuildPassQueue( RendererPassQueueData.FromRenderingData( renderingData)); try { bool recorded; using (ContextContainer frameData = ScriptableRenderPass.BuildRenderGraphFrameData( renderingData)) { recorded = RecordRendererStage( context, new RenderGraph(context.renderContext), frameData); } if (recorded) { QueueCameraStackFinish( m_activePassQueue, 0UL); FinishCameraStackRendering(null); } return recorded; } finally { ClearPassQueue(); } } 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); } private static bool HasRendererBlock( RendererBlocks rendererBlocks, RendererBlock block) { return rendererBlocks != null && rendererBlocks.HasPasses(block); } protected virtual bool SupportsRendererStage( RendererRecordingContext context) { if (context == null) { return false; } return HasRendererStageBlocks(context.stage); } private bool HasRendererStageBlocks( CameraFrameStage stage) { return HasRendererStageBlocks( stage, m_rendererBlocks); } private static bool HasRendererStageBlocks( CameraFrameStage stage, RendererBlocks rendererBlocks) { switch (stage) { case CameraFrameStage.ShadowCaster: return HasRendererBlock( rendererBlocks, RendererBlock.ShadowCaster); case CameraFrameStage.DepthOnly: return HasRendererBlock( rendererBlocks, RendererBlock.DepthPrepass); case CameraFrameStage.MainScene: return HasRendererBlock( rendererBlocks, RendererBlock.MainOpaque) || HasRendererBlock( rendererBlocks, RendererBlock.MainSkybox) || HasRendererBlock( rendererBlocks, RendererBlock.MainTransparent); case CameraFrameStage.PostProcess: return HasRendererBlock( rendererBlocks, RendererBlock.PostProcess); case CameraFrameStage.FinalOutput: return HasRendererBlock( rendererBlocks, RendererBlock.FinalOutput); default: return false; } } private RendererFramePlan BuildRendererFramePlan( ScriptableRenderPipelinePlanningContext context) { bool finalColorRequiresProcessing = context.HasFinalColorProcessing(); int rendererIndex = context.rendererIndex; ulong framePlanId = context.framePlanId; BuildPassQueue( RendererPassQueueData.CreatePlanning( rendererIndex, finalColorRequiresProcessing, framePlanId)); List activePassQueue = SnapshotActivePassQueue( m_activePassQueue); RendererBlocks rendererBlocks = new RendererBlocks(); rendererBlocks.Build(activePassQueue); RendererFramePlan framePlan = new RendererFramePlan( framePlanId, rendererIndex, activePassQueue); framePlan.manifest = new PassQueueStageManifest { shadowCaster = HasRendererStageBlocks( CameraFrameStage.ShadowCaster, rendererBlocks), depthOnly = HasRendererStageBlocks( CameraFrameStage.DepthOnly, rendererBlocks), postProcess = HasRendererStageBlocks( CameraFrameStage.PostProcess, rendererBlocks), finalOutput = HasRendererStageBlocks( CameraFrameStage.FinalOutput, rendererBlocks) }; return framePlan; } 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 StoreRendererFramePlan( RendererFramePlan framePlan) { if (framePlan == null || framePlan.framePlanId == 0UL) { return; } if (!m_framePlans.ContainsKey( framePlan.framePlanId) && m_framePlans.Count >= kMaxFramePlanSnapshotCount) { ulong oldestFramePlanId = 0UL; bool hasOldestFramePlanId = false; foreach (ulong framePlanId in m_framePlans.Keys) { oldestFramePlanId = framePlanId; hasOldestFramePlanId = true; break; } if (hasOldestFramePlanId) { m_framePlans.Remove( oldestFramePlanId); } } m_framePlans[framePlan.framePlanId] = framePlan; } private bool TryGetRendererFramePlan( RendererRecordingContext context, out RendererFramePlan framePlan) { framePlan = null; if (context == null || context.framePlanId == 0UL) { return false; } if (!m_framePlans.TryGetValue( context.framePlanId, out framePlan) || framePlan == null || framePlan.rendererIndex != context.rendererIndex) { return false; } return true; } private bool SupportsRendererRecordingFromFramePlan( RendererRecordingContext context, RendererFramePlan framePlan) { LoadRendererFramePlan(framePlan); try { return SupportsRendererStage(context); } finally { ClearPassQueue(); } } private bool RecordRendererFromFramePlan( RendererRecordingContext context, RendererFramePlan framePlan) { LoadRendererFramePlan(framePlan); try { bool recorded; using (ContextContainer frameData = ScriptableRenderPass.BuildRenderGraphFrameData( context.renderingData)) { recorded = RecordRendererStage( context, new RenderGraph(context.renderContext), frameData); } if (recorded) { QueueCameraStackFinish(framePlan); } return recorded; } finally { ClearPassQueue(); } } private void LoadRendererFramePlan( RendererFramePlan framePlan) { ClearPassQueue(); if (framePlan == null || framePlan.activePassQueue == null) { return; } m_activePassQueue.AddRange( framePlan.activePassQueue); m_rendererBlocks.Build(m_activePassQueue); } private static List SnapshotActivePassQueue( IList activePassQueue) { List snapshot = new List(); if (activePassQueue == null) { return snapshot; } for (int i = 0; i < activePassQueue.Count; ++i) { ScriptableRenderPass renderPass = activePassQueue[i]; if (renderPass == null) { continue; } snapshot.Add( renderPass.CreateFramePlanSnapshot()); } return snapshot; } private void BuildPassQueue( RendererPassQueueData passQueueData) { ClearPassQueue(); for (int i = 0; i < m_features.Count; ++i) { ScriptableRendererFeature feature = m_features[i]; if (feature == null || !feature.isActive) { continue; } feature.SetupRenderPasses( this, in passQueueData); feature.AddRenderPasses( this, ref passQueueData); } AddRenderPasses(passQueueData); m_rendererBlocks.Build(m_activePassQueue); } private void ClearPassQueue() { m_activePassQueue.Clear(); m_rendererBlocks.Clear(); } private void QueueCameraStackFinish( RendererFramePlan framePlan) { if (framePlan == null) { return; } QueueCameraStackFinish( framePlan.activePassQueue, framePlan.framePlanId); } private void QueueCameraStackFinish( IList renderPasses, ulong framePlanId) { if (renderPasses == null || renderPasses.Count == 0) { return; } if (framePlanId != 0UL && m_pendingCameraStackFinishFramePlans.Contains( framePlanId)) { return; } if (framePlanId != 0UL) { m_pendingCameraStackFinishFramePlans.Add( framePlanId); } for (int i = 0; i < renderPasses.Count; ++i) { ScriptableRenderPass renderPass = renderPasses[i]; if (renderPass != null) { m_pendingCameraStackFinishPasses.Add( renderPass); } } } private void FinishCameraStackRendering( RendererRecordingContext context) { if (context != null && context.framePlanId != 0UL && !m_pendingCameraStackFinishFramePlans.Contains( context.framePlanId)) { return; } for (int i = 0; i < m_pendingCameraStackFinishPasses.Count; ++i) { ScriptableRenderPass renderPass = m_pendingCameraStackFinishPasses[i]; if (renderPass == null) { continue; } renderPass.OnFinishCameraStackRendering( m_finishCameraStackCommandBuffer); } m_pendingCameraStackFinishPasses.Clear(); m_pendingCameraStackFinishFramePlans.Clear(); } protected virtual void ReleaseRuntimeResources() { } } }