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 PassQueueStageSnapshot { public PassQueueStageSnapshot( CameraFrameStage stage, List activePassQueue, RendererBlocks rendererBlocks) { this.stage = stage; this.activePassQueue = activePassQueue ?? new List(); this.rendererBlocks = rendererBlocks ?? new RendererBlocks(); } public readonly CameraFrameStage stage; public readonly List activePassQueue; public readonly RendererBlocks rendererBlocks; } private sealed class PassQueueFrameSnapshot { private readonly Dictionary< CameraFrameStage, PassQueueStageSnapshot> m_stageSnapshots = new Dictionary< CameraFrameStage, PassQueueStageSnapshot>(); public PassQueueFrameSnapshot( ulong framePlanId, int rendererIndex) { this.framePlanId = framePlanId; this.rendererIndex = rendererIndex; } public readonly ulong framePlanId; public readonly int rendererIndex; public PassQueueStageManifest manifest; public void SetStageSnapshot( PassQueueStageSnapshot snapshot) { if (snapshot == null) { return; } m_stageSnapshots[snapshot.stage] = snapshot; } public bool TryGetStageSnapshot( CameraFrameStage stage, out PassQueueStageSnapshot snapshot) { return m_stageSnapshots.TryGetValue( stage, out snapshot); } } 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_framePlanSnapshots = new Dictionary(); 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_framePlanSnapshots.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; } PassQueueFrameSnapshot snapshot = BuildPassQueueFrameSnapshot(context); ApplyPassQueueStageManifest(context, snapshot.manifest); StorePassQueueFrameSnapshot(snapshot); 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; PassQueueStageSnapshot snapshot; if (TryGetPassQueueStageSnapshot( context, out snapshot)) { return SupportsRendererRecordingFromSnapshot( context, snapshot); } if (context.framePlanId != 0UL) { return false; } BuildPassQueue(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; PassQueueStageSnapshot snapshot; if (TryGetPassQueueStageSnapshot( context, out snapshot)) { return RecordRendererFromSnapshot( context, snapshot); } if (context.framePlanId != 0UL) { return false; } BuildPassQueue(renderingData); try { bool recorded; using (ContextContainer frameData = ScriptableRenderPass.BuildRenderGraphFrameData( renderingData)) { recorded = RecordRendererStage( context, new RenderGraph(context.renderContext), frameData); } if (recorded) { FinishCameraStackRendering(); } 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 PassQueueFrameSnapshot BuildPassQueueFrameSnapshot( ScriptableRenderPipelinePlanningContext context) { bool finalColorRequiresProcessing = context.HasFinalColorProcessing(); int rendererIndex = context.rendererIndex; ulong framePlanId = context.framePlanId; PassQueueFrameSnapshot snapshot = new PassQueueFrameSnapshot( framePlanId, rendererIndex); snapshot.SetStageSnapshot( BuildPassQueueStageSnapshot( CameraFrameStage.ShadowCaster, rendererIndex, finalColorRequiresProcessing, framePlanId)); snapshot.SetStageSnapshot( BuildPassQueueStageSnapshot( CameraFrameStage.DepthOnly, rendererIndex, finalColorRequiresProcessing, framePlanId)); snapshot.SetStageSnapshot( BuildPassQueueStageSnapshot( CameraFrameStage.MainScene, rendererIndex, finalColorRequiresProcessing, framePlanId)); snapshot.SetStageSnapshot( BuildPassQueueStageSnapshot( CameraFrameStage.PostProcess, rendererIndex, finalColorRequiresProcessing, framePlanId)); snapshot.SetStageSnapshot( BuildPassQueueStageSnapshot( CameraFrameStage.FinalOutput, rendererIndex, finalColorRequiresProcessing, framePlanId)); PassQueueStageSnapshot stageSnapshot; snapshot.manifest = new PassQueueStageManifest { shadowCaster = snapshot.TryGetStageSnapshot( CameraFrameStage.ShadowCaster, out stageSnapshot) && HasRendererStageBlocks( CameraFrameStage.ShadowCaster, stageSnapshot.rendererBlocks), depthOnly = snapshot.TryGetStageSnapshot( CameraFrameStage.DepthOnly, out stageSnapshot) && HasRendererStageBlocks( CameraFrameStage.DepthOnly, stageSnapshot.rendererBlocks), postProcess = snapshot.TryGetStageSnapshot( CameraFrameStage.PostProcess, out stageSnapshot) && HasRendererStageBlocks( CameraFrameStage.PostProcess, stageSnapshot.rendererBlocks), finalOutput = snapshot.TryGetStageSnapshot( CameraFrameStage.FinalOutput, out stageSnapshot) && HasRendererStageBlocks( CameraFrameStage.FinalOutput, stageSnapshot.rendererBlocks) }; return snapshot; } private PassQueueStageSnapshot BuildPassQueueStageSnapshot( CameraFrameStage stage, int rendererIndex, bool finalColorRequiresProcessing, ulong framePlanId) { BuildPassQueue( RenderingData.CreatePlanning( stage, rendererIndex, finalColorRequiresProcessing, framePlanId)); List activePassQueue = new List( m_activePassQueue); RendererBlocks rendererBlocks = new RendererBlocks(); rendererBlocks.Build(activePassQueue); return new PassQueueStageSnapshot( stage, activePassQueue, rendererBlocks); } 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 StorePassQueueFrameSnapshot( PassQueueFrameSnapshot snapshot) { if (snapshot == null || snapshot.framePlanId == 0UL) { return; } if (!m_framePlanSnapshots.ContainsKey( snapshot.framePlanId) && m_framePlanSnapshots.Count >= kMaxFramePlanSnapshotCount) { ulong oldestFramePlanId = 0UL; bool hasOldestFramePlanId = false; foreach (ulong framePlanId in m_framePlanSnapshots.Keys) { oldestFramePlanId = framePlanId; hasOldestFramePlanId = true; break; } if (hasOldestFramePlanId) { m_framePlanSnapshots.Remove( oldestFramePlanId); } } m_framePlanSnapshots[snapshot.framePlanId] = snapshot; } private bool TryGetPassQueueStageSnapshot( RendererRecordingContext context, out PassQueueStageSnapshot stageSnapshot) { stageSnapshot = null; if (context == null || context.framePlanId == 0UL) { return false; } PassQueueFrameSnapshot frameSnapshot; if (!m_framePlanSnapshots.TryGetValue( context.framePlanId, out frameSnapshot) || frameSnapshot == null || frameSnapshot.rendererIndex != context.rendererIndex) { return false; } return frameSnapshot.TryGetStageSnapshot( context.stage, out stageSnapshot); } private bool SupportsRendererRecordingFromSnapshot( RendererRecordingContext context, PassQueueStageSnapshot snapshot) { LoadPassQueueStageSnapshot(snapshot); try { return SupportsRendererStage(context); } finally { ClearPassQueue(); } } private bool RecordRendererFromSnapshot( RendererRecordingContext context, PassQueueStageSnapshot snapshot) { LoadPassQueueStageSnapshot(snapshot); try { bool recorded; using (ContextContainer frameData = ScriptableRenderPass.BuildRenderGraphFrameData( context.renderingData)) { recorded = RecordRendererStage( context, new RenderGraph(context.renderContext), frameData); } if (recorded) { FinishCameraStackRendering(); } return recorded; } finally { ClearPassQueue(); } } private void LoadPassQueueStageSnapshot( PassQueueStageSnapshot snapshot) { ClearPassQueue(); if (snapshot == null || snapshot.activePassQueue == null) { return; } m_activePassQueue.AddRange( snapshot.activePassQueue); m_rendererBlocks.Build(m_activePassQueue); } 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() { } } }