// Copyright Epic Games, Inc. All Rights Reserved. #include "SceneVisibility.h" #include "SceneVisibilityPrivate.h" #include "CoreMinimal.h" #include "ComputeWorkerInterface.h" #include "HAL/ThreadSafeCounter.h" #include "Stats/Stats.h" #include "Misc/MemStack.h" #include "HAL/IConsoleManager.h" #include "Misc/App.h" #include "EngineDefines.h" #include "EngineGlobals.h" #include "EngineStats.h" #include "RHIDefinitions.h" #include "SceneTypes.h" #include "SceneInterface.h" #include "RendererInterface.h" #include "PrimitiveViewRelevance.h" #include "Materials/Material.h" #include "MaterialShared.h" #include "PrimitiveDrawingUtils.h" #include "ScenePrivateBase.h" #include "PostProcess/SceneRenderTargets.h" #include "SceneCore.h" #include "SceneOcclusion.h" #include "LightSceneInfo.h" #include "SceneRendering.h" #include "DeferredShadingRenderer.h" #include "DynamicPrimitiveDrawing.h" #include "FXSystem.h" #include "PostProcess/PostProcessing.h" #include "SceneView.h" #include "SkyAtmosphereRendering.h" #include "Engine/LODActor.h" #include "GPUScene.h" #include "TranslucentRendering.h" #include "TranslucentLighting.h" #include "Async/ParallelFor.h" #include "HairStrands/HairStrandsRendering.h" #include "HairStrands/HairStrandsData.h" #include "RectLightSceneProxy.h" #include "Math/Halton.h" #include "ProfilingDebugging/CountersTrace.h" #include "Algo/Unique.h" #include "InstanceCulling/InstanceCullingManager.h" #include "InstanceCulling/InstanceCullingOcclusionQuery.h" #include "PostProcess/TemporalAA.h" #include "RayTracing/RayTracingInstanceCulling.h" #include "RayTracing/RayTracing.h" #include "HeterogeneousVolumes/HeterogeneousVolumes.h" #include "RendererModule.h" #include "SceneViewExtension.h" #include "RenderCore.h" #include "UnrealEngine.h" #include "VT/VirtualTextureSystem.h" #include "NaniteSceneProxy.h" #include "ViewDebug.h" #include "DecalRenderingCommon.h" #include "CompositionLighting/PostProcessDeferredDecals.h" #include "DecalRenderingShared.h" bool GDistanceCullToSphereEdge = true; static FAutoConsoleVariableRef CVarDistanceCullToSphereEdge( TEXT("r.DistanceCullToSphereEdge"), GDistanceCullToSphereEdge, TEXT("If true frustum cull will check distance to bounding sphere edge rather than origin."), ECVF_RenderThreadSafe ); static float GWireframeCullThreshold = 5.0f; static FAutoConsoleVariableRef CVarWireframeCullThreshold( TEXT("r.WireframeCullThreshold"), GWireframeCullThreshold, TEXT("Threshold below which objects in ortho wireframe views will be culled."), ECVF_RenderThreadSafe ); float GMinScreenRadiusForLights = 0.03f; static FAutoConsoleVariableRef CVarMinScreenRadiusForLights( TEXT("r.MinScreenRadiusForLights"), GMinScreenRadiusForLights, TEXT("Threshold below which lights will be culled."), ECVF_RenderThreadSafe ); float GMinScreenRadiusForDepthPrepass = 0.03f; static FAutoConsoleVariableRef CVarMinScreenRadiusForDepthPrepass( TEXT("r.MinScreenRadiusForDepthPrepass"), GMinScreenRadiusForDepthPrepass, TEXT("Threshold below which meshes will be culled from depth only pass."), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarTemporalAASamples( TEXT("r.TemporalAASamples"), 8, TEXT("Number of jittered positions for temporal AA (4, 8=default, 16, 32, 64)."), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarTemporalAAScaleSamples( TEXT("r.TemporalAAScaleSamples"), 1, TEXT("Whether or not to scale the number of jittered positions for temporal AA when upsampling to maintain a consistent density."), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarInvertTemporalJitterX( TEXT("r.InvertTemporalJitterX"), 0, TEXT("Whether or not to invert the X value of jittered positions for temporal AA."), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarInvertTemporalJitterY( TEXT("r.InvertTemporalJitterY"), 0, TEXT("Whether or not to invert the Y value of jittered positions for temporal AA."), ECVF_RenderThreadSafe); static int32 GHZBOcclusion = 0; static FAutoConsoleVariableRef CVarHZBOcclusion( TEXT("r.HZBOcclusion"), GHZBOcclusion, TEXT("Defines which occlusion system is used.\n") TEXT(" 0: Hardware occlusion queries\n") TEXT(" 1: Use HZB occlusion system (default, less GPU and CPU cost, more conservative results)") TEXT(" 2: Force HZB occlusion system (overrides rendering platform preferences)"), ECVF_RenderThreadSafe ); int32 GOcclusionFeedback_Enable = 0; static FAutoConsoleVariableRef CVarOcclusionFeedback_Enable( TEXT("r.OcclusionFeedback.Enable"), GOcclusionFeedback_Enable, TEXT("Whether to enable occlusion system based on a rendering feedback. Currently works only with a mobile rendering\n"), ECVF_RenderThreadSafe ); static int32 GVisualizeOccludedPrimitives = 0; static FAutoConsoleVariableRef CVarVisualizeOccludedPrimitives( TEXT("r.VisualizeOccludedPrimitives"), GVisualizeOccludedPrimitives, TEXT("Draw boxes for all occluded primitives.\n") TEXT(" 0: Do not draw bounding boxes for occluded primitives (default).\n") TEXT(" 1: Draw the primitive bounding boxes for all occluded primitives.\n") TEXT(" 2: Draw the primitive bounding boxes for all occluded primitives after expansion. See r.Expand*BBoxes* for expansion.\n"), ECVF_RenderThreadSafe | ECVF_Cheat ); static int32 GVisualizePrimitiveBBoxes = 0; static FAutoConsoleVariableRef CVarVisualizePrimitiveBBoxes( TEXT("r.VisualizePrimitiveBBoxes"), GVisualizePrimitiveBBoxes, TEXT("Draw boxes for all primitives.\n") TEXT(" 0: Do not draw bounding boxes for all primitives (default).\n") TEXT(" 1: Draw bounding boxes only after expansion. See r.Expand*BBoxes for expansion.\n") TEXT(" 2: Draw bounding boxes for both before expansion and after expansion."), ECVF_RenderThreadSafe | ECVF_Cheat ); static int32 GAllowSubPrimitiveQueries = 1; static FAutoConsoleVariableRef CVarAllowSubPrimitiveQueries( TEXT("r.AllowSubPrimitiveQueries"), GAllowSubPrimitiveQueries, TEXT("Enables sub primitive queries, currently only used by hierarchical instanced static meshes. 1: Enable, 0 Disabled. When disabled, one query is used for the entire proxy."), ECVF_RenderThreadSafe ); RENDERER_API TAutoConsoleVariable CVarStaticMeshLODDistanceScale( TEXT("r.StaticMeshLODDistanceScale"), 1.0f, TEXT("Scale factor for the distance used in computing discrete LOD for static meshes. (defaults to 1)\n") TEXT("(higher values make LODs transition earlier, e.g., 2 is twice as fast / half the distance)"), ECVF_Scalability | ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarAutomaticViewMipBiasMin( TEXT("r.ViewTextureMipBias.Min"), -2.0f, TEXT("Automatic view mip bias's minimum value (default to -2)."), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarAutomaticViewMipBiasOffset( TEXT("r.ViewTextureMipBias.Offset"), -0.3, TEXT("Automatic view mip bias's constant offset (default to -0.3)."), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarAutomaticViewMipBiasQuantization( TEXT("r.ViewTextureMipBias.Quantization"), 1024, TEXT("Quantization step count to use for automatic view mip bias, measured between -4.0f and 4.0f.\n") TEXT("Lowering this can significantly reduce unique sampler states when Dynamic Resolution is active."), ECVF_RenderThreadSafe); static int32 GILCUpdatePrimTaskEnabled = 1; static FAutoConsoleVariableRef CVarILCUpdatePrimitivesTask( TEXT("r.Cache.UpdatePrimsTaskEnabled"), GILCUpdatePrimTaskEnabled, TEXT("Enable threading for ILC primitive update. Will overlap with the rest the end of InitViews."), ECVF_RenderThreadSafe ); int32 GEarlyInitDynamicShadows = 1; static FAutoConsoleVariableRef CVarEarlyInitDynamicShadows( TEXT("r.EarlyInitDynamicShadows"), GEarlyInitDynamicShadows, TEXT("Starts shadow culling tasks earlier in the frame."), ECVF_RenderThreadSafe ); static int32 GFramesNotOcclusionTestedToExpandBBoxes = 5; static FAutoConsoleVariableRef CVarFramesNotOcclusionTestedToExpandBBoxes( TEXT("r.GFramesNotOcclusionTestedToExpandBBoxes"), GFramesNotOcclusionTestedToExpandBBoxes, TEXT("If we don't occlusion test a primitive for this many frames, then we expand the BBox when we do occlusion test it for a few frames. See also r.ExpandNewlyOcclusionTestedBBoxesAmount, r.FramesToExpandNewlyOcclusionTestedBBoxes"), ECVF_RenderThreadSafe ); static int32 GFramesToExpandNewlyOcclusionTestedBBoxes = 2; static FAutoConsoleVariableRef CVarFramesToExpandNewlyOcclusionTestedBBoxes( TEXT("r.FramesToExpandNewlyOcclusionTestedBBoxes"), GFramesToExpandNewlyOcclusionTestedBBoxes, TEXT("If we don't occlusion test a primitive for r.GFramesNotOcclusionTestedToExpandBBoxes frames, then we expand the BBox when we do occlusion test it for this number of frames. See also r.GFramesNotOcclusionTestedToExpandBBoxes, r.ExpandNewlyOcclusionTestedBBoxesAmount"), ECVF_RenderThreadSafe ); static float GExpandNewlyOcclusionTestedBBoxesAmount = 0.0f; static FAutoConsoleVariableRef CVarExpandNewlyOcclusionTestedBBoxesAmount( TEXT("r.ExpandNewlyOcclusionTestedBBoxesAmount"), GExpandNewlyOcclusionTestedBBoxesAmount, TEXT("If we don't occlusion test a primitive for r.GFramesNotOcclusionTestedToExpandBBoxes frames, then we expand the BBox when we do occlusion test it for a few frames by this amount. See also r.FramesToExpandNewlyOcclusionTestedBBoxes, r.GFramesNotOcclusionTestedToExpandBBoxes."), ECVF_RenderThreadSafe ); static float GExpandAllTestedBBoxesAmount = 0.0f; static FAutoConsoleVariableRef CVarExpandAllTestedBBoxesAmount( TEXT("r.ExpandAllOcclusionTestedBBoxesAmount"), GExpandAllTestedBBoxesAmount, TEXT("Amount to expand all occlusion test bounds by.\n") TEXT("This variable can be applied in conjunction with r.ExpandNewlyOcclusionTestedBBoxesScreenSpace and r.ExpandAllOcclusionTestedBBoxesScreenSpace. This variable always applies before those two.\n"), ECVF_RenderThreadSafe ); // TODO: UE-273143 This is the start of a significant refactor in the way that objects get culled. // The previous implementation took cvars to determine how much all bounding boxes should expand in world space. // The current implementation extends this to expand by a screen space amount (in pixels). // The idealized ultimate solution would take into account camera positional and/or angular velocity, over the previous n frames, // and extrapolate to determine the size at which bboxes should expand (in the event that they are likely to become unoccluded in the next m frames). static float GExpandNewlyOcclusionTestedBBoxesScreenSpace = 0.0f; static FAutoConsoleVariableRef CVarExpandNewlyOcclusionTestedBBoxesScreenSpace( TEXT("r.ExpandNewlyOcclusionTestedBBoxesScreenSpace"), GExpandNewlyOcclusionTestedBBoxesScreenSpace, TEXT("If we don't occlusion test a primitive for r.GFramesNotOcclusionTestedToExpandBBoxes frames, then we expand the BBox when we do occlusion test it for a few frames by this amount. See also r.FramesToExpandNewlyOcclusionTestedBBoxes, r.GFramesNotOcclusionTestedToExpandBBoxes."), ECVF_RenderThreadSafe ); static float GExpandAllTestedBBoxesScreenSpace = 0.0f; static FAutoConsoleVariableRef CVarExpandAllTestedBBoxesScreenSpace( TEXT("r.ExpandAllOcclusionTestedBBoxesScreenSpace"), GExpandAllTestedBBoxesScreenSpace, TEXT("Screen space amount in pixels to increase the occlusion test of BBoxes by.\n") TEXT("This variable can be applied in conjunction with r.ExpandNewlyOcclusionTestedBBoxesAmount and r.ExpandAllOcclusionTestedBBoxesAmount. This variable always applies after those two.\n"), ECVF_RenderThreadSafe ); static float GNeverOcclusionTestDistance = 0.0f; static FAutoConsoleVariableRef CVarNeverOcclusionTestDistance( TEXT("r.NeverOcclusionTestDistance"), GNeverOcclusionTestDistance, TEXT("When the distance between the viewpoint and the bounding sphere center is less than this, never occlusion cull."), ECVF_RenderThreadSafe | ECVF_Scalability ); static int32 GForceSceneHasDecals = 0; static FAutoConsoleVariableRef CVarForceSceneHasDecals( TEXT("r.ForceSceneHasDecals"), GForceSceneHasDecals, TEXT("Whether to always assume that scene has decals, so we don't switch depth state conditionally. This can significantly reduce total number of PSOs at a minor GPU cost."), ECVF_RenderThreadSafe ); static float GCameraCutTranslationThreshold = 10000.0f; static FAutoConsoleVariableRef CVarCameraCutTranslationThreshold( TEXT("r.CameraCutTranslationThreshold"), GCameraCutTranslationThreshold, TEXT("The maximum camera translation disatance in centimeters allowed between two frames before a camera cut is automatically inserted."), ECVF_RenderThreadSafe ); float GOverlayMaterialMaxDrawDistanceOverride = 0.0f; static FAutoConsoleVariableRef CVarGMaterialOverlayMaxDistanceOverride( TEXT("r.OverlayMaterialMaxDrawDistanceOverride"), GOverlayMaterialMaxDrawDistanceOverride, TEXT("Override the max draw distance for an Overlay Material. Any value greater than 0 will trigger the override."), ECVF_RenderThreadSafe ); /** Distance fade cvars */ static int32 GDisableLODFade = false; static FAutoConsoleVariableRef CVarDisableLODFade( TEXT("r.DisableLODFade"), GDisableLODFade, TEXT("Disable fading for distance culling"), ECVF_RenderThreadSafe ); static float GFadeTime = 0.25f; static FAutoConsoleVariableRef CVarLODFadeTime( TEXT("r.LODFadeTime"), GFadeTime, TEXT("How long LOD takes to fade (in seconds)."), ECVF_RenderThreadSafe ); static float GDistanceFadeMaxTravel = 1000.0f; static FAutoConsoleVariableRef CVarDistanceFadeMaxTravel( TEXT("r.DistanceFadeMaxTravel"), GDistanceFadeMaxTravel, TEXT("Max distance that the player can travel during the fade time."), ECVF_RenderThreadSafe ); extern int32 GVisibilitySkipAlwaysVisible; static int32 GVisibilityTaskSchedule = 1; static FAutoConsoleVariableRef CVarVisibilityTaskSchedule( TEXT("r.Visibility.TaskSchedule"), GVisibilityTaskSchedule, TEXT("Controls how the visibility task graph is scheduled.") TEXT("0: Work is primarily done on the render thread with the potential for parallel help;") TEXT("1: Work is done on an async task graph (if supported by platform);"), ECVF_RenderThreadSafe ); static int32 GFrustumCullNumPrimitivesPerTask = 0; static FAutoConsoleVariableRef CVarFrustumCullNumPrimitivesPerTask( TEXT("r.Visibility.FrustumCull.NumPrimitivesPerTask"), GFrustumCullNumPrimitivesPerTask, TEXT("Assigns a fixed number of primitives for each frustum cull task.") TEXT(" 0: Automatic;") TEXT(">0: Fixed number of primitives per task (clamped to fixed limits);"), ECVF_RenderThreadSafe ); static bool GFrustumCullEnabled = true; static FAutoConsoleVariableRef CVarFrustumCullEnable( TEXT("r.Visibility.FrustumCull.Enabled"), GFrustumCullEnabled, TEXT("Enables frustum culling."), ECVF_RenderThreadSafe ); static bool GFrustumCullUseOctree = false; static FAutoConsoleVariableRef CVarFrustumCullUseOctree( TEXT("r.Visibility.FrustumCull.UseOctree"), GFrustumCullUseOctree, TEXT("Use the octree for visibility calculations."), ECVF_RenderThreadSafe ); static bool GFrustumCullUseSphereTestFirst = false; static FAutoConsoleVariableRef CVarFrustumCullUseSphereTestFirst( TEXT("r.Visibility.FrustumCull.UseSphereTestFirst"), GFrustumCullUseSphereTestFirst, TEXT("Performance tweak. Uses a sphere cull before and in addition to a box for frustum culling."), ECVF_RenderThreadSafe ); static bool GFrustumCullUseFastIntersect = true; static FAutoConsoleVariableRef CVarFrustumCullUseFastIntersect( TEXT("r.Visibility.FrustumCull.UseFastIntersect"), GFrustumCullUseFastIntersect, TEXT("Use optimized 8 plane fast intersection code if we have 8 permuted planes."), ECVF_RenderThreadSafe ); static int32 GOcclusionCullMaxQueriesPerTask = 0; static FAutoConsoleVariableRef CVarOcclusionCullMaxQueriesPerTask( TEXT("r.Visibility.OcclusionCull.MaxQueriesPerTask"), GOcclusionCullMaxQueriesPerTask, TEXT("Assigns a fixed number of occlusion queries for each occlusion cull task.") TEXT(" 0: Automatic;") TEXT(">0: Fixed number of occlusion queries per task;"), ECVF_RenderThreadSafe ); static int32 GNumDynamicMeshElementTasks = 4; static FAutoConsoleVariableRef CVarNumDynamicMeshElementTasks( TEXT("r.Visibility.DynamicMeshElements.NumMainViewTasks"), GNumDynamicMeshElementTasks, TEXT("Controls the number of gather dynamic mesh elements tasks to run asynchronously during view visibility."), ECVF_RenderThreadSafe ); inline uint32 GetNumDynamicMeshElementTasks() { if (!IsParallelGatherDynamicMeshElementsEnabled()) { return 0; } return FMath::Clamp(GNumDynamicMeshElementTasks, 0, LowLevelTasks::FScheduler::Get().GetNumWorkers()); } static bool GOcclusionCullEnabled = true; static FAutoConsoleVariableRef CVarOcclusionCullEnable( // TODO: Move to r.Visibility.OcclusionCull.Enable. Still several explicit references. TEXT("r.AllowOcclusionQueries"), GOcclusionCullEnabled, TEXT("Enables hardware occlusion culling."), ECVF_RenderThreadSafe ); bool DoOcclusionQueries(const FSceneViewFamily& ViewFamily) { return GOcclusionCullEnabled && !ViewFamily.EngineShowFlags.DisableOcclusionQueries; } bool FSceneRenderer::DoOcclusionQueries() const { return ::DoOcclusionQueries(ViewFamily); } static int32 GRelevanceNumPrimitivesPerPacket = 0; static FAutoConsoleVariableRef CVarRelevanceNumPrimitivesPerPacket( TEXT("r.Visibility.Relevance.NumPrimitivesPerPacket"), GRelevanceNumPrimitivesPerPacket, TEXT("Assigns a fixed number of primitives for each relevance packet.") TEXT(" 0: Automatic;") TEXT(">0: Fixed number of primitives per packet (clamped to fixed limits);"), ECVF_RenderThreadSafe ); float GLightMaxDrawDistanceScale = 1.0f; static FAutoConsoleVariableRef CVarLightMaxDrawDistanceScale( TEXT("r.LightMaxDrawDistanceScale"), GLightMaxDrawDistanceScale, TEXT("Scale applied to the MaxDrawDistance of lights. Useful for fading out local lights more aggressively on some platforms."), ECVF_Scalability | ECVF_RenderThreadSafe ); #if !UE_BUILD_SHIPPING static TAutoConsoleVariable CVarTAADebugOverrideTemporalIndex( TEXT("r.TemporalAA.Debug.OverrideTemporalIndex"), -1, TEXT("Override the temporal index for debugging purposes."), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarFreezeTemporalSequences( TEXT("r.Test.FreezeTemporalSequences"), 0, TEXT("Freezes all temporal sequences."), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarFreezeTemporalHistories( TEXT("r.Test.FreezeTemporalHistories"), 0, TEXT("Freezes all temporal histories as well as the temporal sequence."), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarFreezeTemporalHistoriesProgress( TEXT("r.Test.FreezeTemporalHistories.Progress"), 0, TEXT("Progress the temporal histories by one frame when modified."), ECVF_RenderThreadSafe); #endif TRACE_DECLARE_INT_COUNTER(Scene_Visibility_NumProcessedPrimitives, TEXT("Scene/Visibility/NumProcessedPrimitives")); TRACE_DECLARE_INT_COUNTER(Scene_Visibility_FrustumCull_NumCulledPrimitives, TEXT("Scene/Visibility/FrustumCull/NumCulledPrimitives")); TRACE_DECLARE_INT_COUNTER(Scene_Visibility_FrustumCull_NumPrimitivesPerTask, TEXT("Scene/Visibility/FrustumCull/NumPrimitivesPerTask")); TRACE_DECLARE_INT_COUNTER(Scene_Visibility_OcclusionCull_NumTestedQueries, TEXT("Scene/Visibility/OcclusionCull/NumTestedQueries")); TRACE_DECLARE_INT_COUNTER(Scene_Visibility_OcclusionCull_NumCulledPrimitives, TEXT("Scene/Visibility/OcclusionCull/NumCulledPrimitives")); TRACE_DECLARE_INT_COUNTER(Scene_Visibility_Relevance_NumPrimitivesPerPacket, TEXT("Scene/Visibility/Relevance/NumPrimitivesPerPacket")); TRACE_DECLARE_INT_COUNTER(Scene_Visibility_Relevance_NumPrimitivesProcessed, TEXT("Scene/Visibility/Relevance/NumPrimitivesProcessed")); /////////////////////////////////////////////////////////////////////////////// IVisibilityTaskData* LaunchVisibilityTasks(FRHICommandListImmediate& RHICmdList, FSceneRenderer& SceneRenderer, const UE::Tasks::FTask& BeginInitVisibilityPrerequisites) { FVisibilityTaskData* TaskData = SceneRenderer.Allocator.Create(RHICmdList, SceneRenderer); TaskData->LaunchVisibilityTasks(BeginInitVisibilityPrerequisites); return TaskData; } /////////////////////////////////////////////////////////////////////////////// bool FViewInfo::IsDistanceCulled( float DistanceSquared, float MinDrawDistance, float InMaxDrawDistance, const FPrimitiveSceneInfo* PrimitiveSceneInfo) { bool bMayBeFading; bool bFadingIn; bool bDistanceCulled = IsDistanceCulled_AnyThread(DistanceSquared, MinDrawDistance, InMaxDrawDistance, PrimitiveSceneInfo, bMayBeFading, bFadingIn); if (bMayBeFading) { bDistanceCulled = UpdatePrimitiveFadingState(PrimitiveSceneInfo, bFadingIn); } return bDistanceCulled; } bool FViewInfo::IsDistanceCulled_AnyThread(float DistanceSquared, float InMinDrawDistance, float InMaxDrawDistance, const FPrimitiveSceneInfo* PrimitiveSceneInfo, bool& bOutMayBeFading, bool& bOutFadingIn) const { const float MaxDrawDistanceScale = GetCachedScalabilityCVars().ViewDistanceScale; const float FadeRadius = GDisableLODFade ? 0.0f : GDistanceFadeMaxTravel; const float MaxDrawDistance = InMaxDrawDistance * MaxDrawDistanceScale; const float MinDrawDistance = InMinDrawDistance * MaxDrawDistanceScale; bool bHasMaxDrawDistance = InMaxDrawDistance != FLT_MAX; bool bHasMinDrawDistance = InMinDrawDistance > 0; bOutMayBeFading = false; if (!bHasMaxDrawDistance && !bHasMinDrawDistance) { return false; } // If cull distance is disabled, always show (except foliage) if (Family->EngineShowFlags.DistanceCulledPrimitives && !PrimitiveSceneInfo->Proxy->IsDetailMesh()) { return false; } // The primitive is always culled if it exceeds the max fade distance. if ((bHasMaxDrawDistance && DistanceSquared > FMath::Square(MaxDrawDistance + FadeRadius)) || (bHasMinDrawDistance && DistanceSquared < FMath::Square(MinDrawDistance))) { return true; } const bool bDistanceCulled = bHasMaxDrawDistance && (DistanceSquared > FMath::Square(MaxDrawDistance)); const bool bMayBeFading = bHasMaxDrawDistance && (DistanceSquared > FMath::Square(MaxDrawDistance - FadeRadius)); if (!GDisableLODFade && bMayBeFading && State != NULL && !bDisableDistanceBasedFadeTransitions && PrimitiveSceneInfo->Proxy->IsUsingDistanceCullFade()) { // Don't update primitive fading state yet because current thread may be not render thread bOutMayBeFading = true; bOutFadingIn = !bDistanceCulled; } return bDistanceCulled && !bOutMayBeFading; } inline bool IntersectBox8Plane(const FVector& InOrigin, const FVector& InExtent, const FPlane*PermutedPlanePtr) { // this removes a lot of the branches as we know there's 8 planes // copied directly out of ConvexVolume.cpp const VectorRegister Origin = VectorLoadFloat3(&InOrigin); const VectorRegister Extent = VectorLoadFloat3(&InExtent); const VectorRegister PlanesX_0 = VectorLoadAligned(&PermutedPlanePtr[0]); const VectorRegister PlanesY_0 = VectorLoadAligned(&PermutedPlanePtr[1]); const VectorRegister PlanesZ_0 = VectorLoadAligned(&PermutedPlanePtr[2]); const VectorRegister PlanesW_0 = VectorLoadAligned(&PermutedPlanePtr[3]); const VectorRegister PlanesX_1 = VectorLoadAligned(&PermutedPlanePtr[4]); const VectorRegister PlanesY_1 = VectorLoadAligned(&PermutedPlanePtr[5]); const VectorRegister PlanesZ_1 = VectorLoadAligned(&PermutedPlanePtr[6]); const VectorRegister PlanesW_1 = VectorLoadAligned(&PermutedPlanePtr[7]); // Splat origin into 3 vectors VectorRegister OrigX = VectorReplicate(Origin, 0); VectorRegister OrigY = VectorReplicate(Origin, 1); VectorRegister OrigZ = VectorReplicate(Origin, 2); // Splat the already abs Extent for the push out calculation VectorRegister AbsExtentX = VectorReplicate(Extent, 0); VectorRegister AbsExtentY = VectorReplicate(Extent, 1); VectorRegister AbsExtentZ = VectorReplicate(Extent, 2); // Calculate the distance (x * x) + (y * y) + (z * z) - w VectorRegister DistX_0 = VectorMultiply(OrigX, PlanesX_0); VectorRegister DistY_0 = VectorMultiplyAdd(OrigY, PlanesY_0, DistX_0); VectorRegister DistZ_0 = VectorMultiplyAdd(OrigZ, PlanesZ_0, DistY_0); VectorRegister Distance_0 = VectorSubtract(DistZ_0, PlanesW_0); // Now do the push out FMath::Abs(x * x) + FMath::Abs(y * y) + FMath::Abs(z * z) VectorRegister PushX_0 = VectorMultiply(AbsExtentX, VectorAbs(PlanesX_0)); VectorRegister PushY_0 = VectorMultiplyAdd(AbsExtentY, VectorAbs(PlanesY_0), PushX_0); VectorRegister PushOut_0 = VectorMultiplyAdd(AbsExtentZ, VectorAbs(PlanesZ_0), PushY_0); // Check for completely outside if (VectorAnyGreaterThan(Distance_0, PushOut_0)) { return false; } // Calculate the distance (x * x) + (y * y) + (z * z) - w VectorRegister DistX_1 = VectorMultiply(OrigX, PlanesX_1); VectorRegister DistY_1 = VectorMultiplyAdd(OrigY, PlanesY_1, DistX_1); VectorRegister DistZ_1 = VectorMultiplyAdd(OrigZ, PlanesZ_1, DistY_1); VectorRegister Distance_1 = VectorSubtract(DistZ_1, PlanesW_1); // Now do the push out FMath::Abs(x * x) + FMath::Abs(y * y) + FMath::Abs(z * z) VectorRegister PushX_1 = VectorMultiply(AbsExtentX, VectorAbs(PlanesX_1)); VectorRegister PushY_1 = VectorMultiplyAdd(AbsExtentY, VectorAbs(PlanesY_1), PushX_1); VectorRegister PushOut_1 = VectorMultiplyAdd(AbsExtentZ, VectorAbs(PlanesZ_1), PushY_1); // Check for completely outside if (VectorAnyGreaterThan(Distance_1, PushOut_1)) { return false; } return true; } struct FFrustumCullingFlags { bool bShouldVisibilityCull; bool bUseCustomCulling; bool bUseSphereTestFirst; bool bUseFastIntersect; bool bUseVisibilityOctree; bool bHasHiddenPrimitives; bool bHasShowOnlyPrimitives; }; // Returns true if the frustum and bounds intersect inline bool IsPrimitiveVisible(FViewInfo& View, const FPlane* PermutedPlanePtr, const FConvexVolume& ViewCullingFrustum, const FPrimitiveBounds& Bounds, int32 VisibilityId, FFrustumCullingFlags Flags) { // The custom culling and sphere culling are additional tests, meaning that if they pass, the // remaining culling tests will still be performed. If any of the tests fail, then the primitive // is culled, and the remaining tests do not need be performed if (Flags.bUseCustomCulling && !View.CustomVisibilityQuery->IsVisible(VisibilityId, FBoxSphereBounds(Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.BoxExtent, Bounds.BoxSphereBounds.SphereRadius))) { return false; } if (Flags.bUseSphereTestFirst && !ViewCullingFrustum.IntersectSphere(Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.SphereRadius)) { return false; } if (Flags.bUseFastIntersect) { return IntersectBox8Plane(Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.BoxExtent, PermutedPlanePtr); } else { return ViewCullingFrustum.IntersectBox(Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.BoxExtent); } } inline bool IsPrimitiveHidden(const FScene& Scene, FViewInfo& View, int32 PrimitiveIndex, FFrustumCullingFlags Flags) { // If any primitives are explicitly hidden, remove them now. if (Flags.bHasHiddenPrimitives && View.HiddenPrimitives.Contains(Scene.PrimitiveComponentIds[PrimitiveIndex])) { return true; } // If the view has any show only primitives, hide everything else if (Flags.bHasShowOnlyPrimitives && !View.ShowOnlyPrimitives->Contains(Scene.PrimitiveComponentIds[PrimitiveIndex])) { return true; } return false; } #if RHI_RAYTRACING inline bool ShouldCullForRayTracing(const FScene& Scene, FViewInfo& View, int32 PrimitiveIndex) { const FRayTracingCullingParameters& RayTracingCullingParameters = View.RayTracingCullingParameters; if (RayTracing::CullPrimitiveByFlags(RayTracingCullingParameters, &Scene, PrimitiveIndex)) { return true; } const bool bIsFarFieldPrimitive = EnumHasAnyFlags(Scene.PrimitiveRayTracingFlags[PrimitiveIndex], ERayTracingPrimitiveFlags::FarField); const Experimental::FHashElementId GroupId = Scene.PrimitiveRayTracingGroupIds[PrimitiveIndex]; if (RayTracingCullingParameters.bCullUsingGroupIds && GroupId.IsValid()) { const FBoxSphereBounds& GroupBounds = Scene.PrimitiveRayTracingGroups.GetByElementId(GroupId).Value.Bounds; const float GroupMinDrawDistance = Scene.PrimitiveRayTracingGroups.GetByElementId(GroupId).Value.MinDrawDistance; return RayTracing::ShouldCullBounds(RayTracingCullingParameters, GroupBounds, GroupMinDrawDistance, bIsFarFieldPrimitive); } else { const FPrimitiveBounds& RESTRICT Bounds = Scene.PrimitiveBounds[PrimitiveIndex]; return RayTracing::ShouldCullBounds(RayTracingCullingParameters, Bounds.BoxSphereBounds, Bounds.MinDrawDistance, bIsFarFieldPrimitive); } }; #endif //RHI_RAYTRACING inline void ComputeDistances(const FPrimitiveBounds& Bounds, const FVector& ViewOriginForDistanceCulling, float& OutClosestDistSq, float& OutFurthestDistSq) { float Dist = (Bounds.BoxSphereBounds.Origin - ViewOriginForDistanceCulling).Length(); float ClosestDist = Dist - Bounds.BoxSphereBounds.SphereRadius; float FurthestDist = Dist + Bounds.BoxSphereBounds.SphereRadius; OutClosestDistSq = Dist >= Bounds.BoxSphereBounds.SphereRadius ? FMath::Square(ClosestDist) : 0; OutFurthestDistSq = FMath::Square(FurthestDist); } static void CullOctree(const FScene& Scene, FViewInfo& View, const FFrustumCullingFlags& Flags, FSceneBitArray& OutVisibleNodes, const FConvexVolume& ViewCullingFrustum) { TRACE_CPUPROFILER_EVENT_SCOPE(SceneVisibility_CullOctree); // Two bits per octree node, 1st bit is Inside Frustum, 2nd bit is Outside Frustum OutVisibleNodes.Init(false, Scene.PrimitiveOctree.GetNumNodes() * 2); Scene.PrimitiveOctree.FindNodesWithPredicate( [&View, &OutVisibleNodes, &Flags, &ViewCullingFrustum](FScenePrimitiveOctree::FNodeIndex ParentNodeIndex, FScenePrimitiveOctree::FNodeIndex NodeIndex, const FBoxCenterAndExtent& NodeBounds) { // If the parent node is completely contained there is no need to test containment if (ParentNodeIndex != INDEX_NONE && !OutVisibleNodes[(ParentNodeIndex * 2) + 1]) { OutVisibleNodes[NodeIndex * 2] = true; OutVisibleNodes[NodeIndex * 2 + 1] = false; return true; } const FPlane* PermutedPlanePtr = ViewCullingFrustum.PermutedPlanes.GetData(); bool bIntersects = false; if (Flags.bUseFastIntersect) { bIntersects = IntersectBox8Plane(NodeBounds.Center, NodeBounds.Extent, PermutedPlanePtr); } else { bIntersects = ViewCullingFrustum.IntersectBox(NodeBounds.Center, NodeBounds.Extent); } if (bIntersects) { OutVisibleNodes[NodeIndex * 2] = true; OutVisibleNodes[NodeIndex * 2 + 1] = ViewCullingFrustum.GetBoxIntersectionOutcode(NodeBounds.Center, NodeBounds.Extent).GetOutside(); } return bIntersects; }, [](FScenePrimitiveOctree::FNodeIndex /*ParentNodeIndex*/, FScenePrimitiveOctree::FNodeIndex /*NodeIndex*/, const FBoxCenterAndExtent& /*NodeBounds*/) { }); } static void UpdateAlwaysVisible(const FScene& Scene, FViewInfo& View, FFrustumCullingFlags Flags, const FVisibilityTaskConfig& TaskConfig, int32 TaskIndex, float CurrentWorldTime) { QUICK_SCOPE_CYCLE_COUNTER(STAT_AlwaysVisible_Loop); check(Scene.PrimitivesAlwaysVisibleOffset != ~0u); const int32 BitArrayNumInner = TaskConfig.NumVisiblePrimitives; const int32 StartWord = int32(Scene.PrimitivesAlwaysVisibleOffset) / NumBitsPerDWORD; const int32 TaskWordOffset = TaskIndex * TaskConfig.AlwaysVisible.NumWordsPerTask; uint32* RESTRICT VisWords = View.PrimitiveVisibilityMap.GetData(); #if RHI_RAYTRACING uint32* RESTRICT RTWords = View.PrimitiveRayTracingVisibilityMap.GetData(); const bool bRayTracingEnabled = IsRayTracingEnabled(View.GetShaderPlatform()) && View.IsRayTracingAllowedForView(); #endif for (int32 WordIndex = TaskWordOffset; WordIndex < TaskWordOffset + int32(TaskConfig.AlwaysVisible.NumWordsPerTask) && WordIndex * NumBitsPerDWORD < BitArrayNumInner; ++WordIndex) { uint32 Mask = 0x1; uint32 VisBits = 0; #if RHI_RAYTRACING uint32 RayTracingBits = 0; #endif for (int32 BitSubIndex = 0; BitSubIndex < NumBitsPerDWORD && WordIndex * NumBitsPerDWORD + BitSubIndex < BitArrayNumInner; ++BitSubIndex, Mask <<= 1) { const int32 Index = (StartWord + WordIndex) * NumBitsPerDWORD + BitSubIndex; VisBits |= Mask; #if RHI_RAYTRACING if (bRayTracingEnabled && !IsPrimitiveHidden(Scene, View, Index, Flags) && !ShouldCullForRayTracing(Scene, View, Index)) { RayTracingBits |= Mask; } #endif } VisWords[StartWord + WordIndex] = VisBits; #if RHI_RAYTRACING if (RayTracingBits) { RTWords[StartWord + WordIndex] = RayTracingBits; } #endif } } static int32 FrustumCull(const FScene& Scene, FViewInfo& View, FFrustumCullingFlags Flags, float MaxDrawDistanceScale, const FHLODVisibilityState* const HLODState, const FSceneBitArray* VisibleNodes, const FVisibilityTaskConfig& TaskConfig, int32 TaskIndex) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FrustumCull_Loop); bool bDisableLODFade = GDisableLODFade || View.bDisableDistanceBasedFadeTransitions; const FPlane* PermutedPlanePtr = View.GetCullingFrustum().PermutedPlanes.GetData(); const FConvexVolume& ViewCullingFrustum = View.GetCullingFrustum(); FVector ViewOriginForDistanceCulling = View.CullingOrigin; float FadeRadius = bDisableLODFade ? 0.0f : GDistanceFadeMaxTravel; uint8 CustomVisibilityFlags = EOcclusionFlags::CanBeOccluded | EOcclusionFlags::HasPrecomputedVisibility; int32 BitArrayNumInner = TaskConfig.NumTestedPrimitives; uint32 NumPrimitivesCulledForTask = 0; // Primitives may be explicitly removed from stereo views when using mono const int32 TaskWordOffset = TaskIndex * TaskConfig.FrustumCull.NumWordsPerTask; uint32* RESTRICT VisWords = View.PrimitiveVisibilityMap.GetData(); uint32* RESTRICT FadeWords = View.PotentiallyFadingPrimitiveMap.GetData(); #if RHI_RAYTRACING uint32* RESTRICT RTWords = View.PrimitiveRayTracingVisibilityMap.GetData(); const bool bRayTracingEnabled = IsRayTracingEnabled(View.GetShaderPlatform()) && View.IsRayTracingAllowedForView(); #endif for (int32 WordIndex = TaskWordOffset; WordIndex < TaskWordOffset + int32(TaskConfig.FrustumCull.NumWordsPerTask) && WordIndex * NumBitsPerDWORD < BitArrayNumInner; WordIndex++) { uint32 Mask = 0x1; uint32 VisBits = 0; uint32 FadingBits = 0; #if RHI_RAYTRACING uint32 RayTracingBits = 0; #endif // If visibility culling is disabled, make sure to use the existing visibility state if (!Flags.bShouldVisibilityCull) { VisBits = VisWords[WordIndex]; } for (int32 BitSubIndex = 0; BitSubIndex < NumBitsPerDWORD && WordIndex * NumBitsPerDWORD + BitSubIndex < BitArrayNumInner; BitSubIndex++, Mask <<= 1) { int32 Index = WordIndex * NumBitsPerDWORD + BitSubIndex; bool bPrimitiveIsHidden = IsPrimitiveHidden(Scene, View, Index, Flags); bool bIsVisible = Flags.bShouldVisibilityCull ? true : (VisBits & Mask) == Mask; bIsVisible = bIsVisible && !bPrimitiveIsHidden; #if RHI_RAYTRACING bool bIsVisibleInRayTracing = true; if (bPrimitiveIsHidden || !bRayTracingEnabled || ShouldCullForRayTracing(Scene, View, Index)) { bIsVisibleInRayTracing = false; } #endif const FPrimitiveBounds& RESTRICT Bounds = Scene.PrimitiveBounds[Index]; // Zero sized bounds indicates that we are not visible. bIsVisible &= Bounds.BoxSphereBounds.SphereRadius > 0; if (Flags.bShouldVisibilityCull && bIsVisible) { bool bShouldDistanceCull = true; bool bPartiallyOutside = true; bool bShouldFrustumCull = true; // Fading HLODs and their children must be visible, objects hidden by HLODs can be culled if (HLODState) { if (HLODState->IsNodeForcedVisible(Index)) { bShouldDistanceCull = false; } else if (HLODState->IsNodeForcedHidden(Index)) { bIsVisible = false; } } // Frustum first bShouldFrustumCull = bShouldFrustumCull && bIsVisible; if (bShouldFrustumCull) { if (Flags.bUseVisibilityOctree) { // If the parent octree node was completely contained by the frustum, there is no need do an additional frustum test on the primitive bounds // If the parent octree node is partially in the frustum, perform an additional test on the primitive bounds uint32 OctreeNodeIndex = Scene.PrimitiveOctreeIndex[Index]; bIsVisible = (*VisibleNodes)[OctreeNodeIndex * 2]; bPartiallyOutside = (*VisibleNodes)[OctreeNodeIndex * 2 + 1]; } if (bIsVisible) { int32 VisibilityId = INDEX_NONE; if (Flags.bUseCustomCulling && ((Scene.PrimitiveOcclusionFlags[Index] & CustomVisibilityFlags) == CustomVisibilityFlags)) { VisibilityId = Scene.PrimitiveSceneProxies[Index]->GetVisibilityId(); } bIsVisible = !bPartiallyOutside || IsPrimitiveVisible(View, PermutedPlanePtr, ViewCullingFrustum, Bounds, VisibilityId, Flags); } } // Distance cull if frustum cull passed bShouldDistanceCull = bShouldDistanceCull && bIsVisible; if (bShouldDistanceCull) { // If cull distance is disabled, always show the primitive (except foliage) if (View.Family->EngineShowFlags.DistanceCulledPrimitives && !Scene.PrimitiveSceneProxies[Index]->IsDetailMesh()) // Proxy call is intentionally behind the DistancedCulledPrimitives check to prevent an expensive memory read { bShouldDistanceCull = false; } } if (bShouldDistanceCull) { // Preserve infinite draw distance bool bHasMaxDrawDistance = Bounds.MaxCullDistance < FLT_MAX; bool bHasMinDrawDistance = Bounds.MinDrawDistance > 0; if (bHasMaxDrawDistance || bHasMinDrawDistance) { float MaxDrawDistance = Bounds.MaxCullDistance * MaxDrawDistanceScale; float MinDrawDistanceSq = FMath::Square(Bounds.MinDrawDistance * MaxDrawDistanceScale); float ClosestDistSquared, FurthestDistSquared; if (GDistanceCullToSphereEdge) { ComputeDistances(Bounds, ViewOriginForDistanceCulling, ClosestDistSquared, FurthestDistSquared); } else { ClosestDistSquared = FurthestDistSquared = FVector::DistSquared(Bounds.BoxSphereBounds.Origin, ViewOriginForDistanceCulling); } // Always test the fade in distance. If a primitive was set to always draw, it may need to be faded in. if (bHasMaxDrawDistance) { float MaxFadeDistanceSquared = FMath::Square(MaxDrawDistance + FadeRadius); float MinFadeDistanceSquared = FMath::Square(MaxDrawDistance - FadeRadius); if ((ClosestDistSquared < MaxFadeDistanceSquared && ClosestDistSquared > MinFadeDistanceSquared) && Scene.PrimitiveSceneProxies[Index]->IsUsingDistanceCullFade()) // Proxy call is intentionally behind the fade check to prevent an expensive memory read { FadingBits |= Mask; } } // Check for distance culling first const bool bFarDistanceCulled = bHasMaxDrawDistance && (ClosestDistSquared > FMath::Square(MaxDrawDistance)); const bool bNearDistanceCulled = bHasMinDrawDistance && (FurthestDistSquared < MinDrawDistanceSq); bool bIsDistanceCulled = bNearDistanceCulled || bFarDistanceCulled; if (bIsDistanceCulled) { bIsVisible = false; } #if RHI_RAYTRACING if (bFarDistanceCulled) { bIsVisibleInRayTracing = false; } #endif } } } if (bIsVisible) { // The primitive is visible! VisBits |= Mask; } else { ++NumPrimitivesCulledForTask; } #if RHI_RAYTRACING if (bIsVisibleInRayTracing) { RayTracingBits |= Mask; } #endif } if (Flags.bShouldVisibilityCull && FadingBits) { FadeWords[WordIndex] = FadingBits; } if (Flags.bShouldVisibilityCull && VisBits) { VisWords[WordIndex] = VisBits; } #if RHI_RAYTRACING if (RayTracingBits) { RTWords[WordIndex] = RayTracingBits; } #endif } return NumPrimitivesCulledForTask; } /////////////////////////////////////////////////////////////////////////////// static void ClearStalePrimitiveFadingStates(FViewInfo& View, FSceneViewState* ViewState) { if (!ViewState) { return; } const uint32 PrevFrameNumber = ViewState->PrevFrameNumber; const float CurrentRealTime = View.Family->Time.GetRealTimeSeconds(); // First clear any stale fading states. for (FPrimitiveFadingStateMap::TIterator It(ViewState->PrimitiveFadingStates); It; ++It) { FPrimitiveFadingState& FadingState = It.Value(); if (FadingState.FrameNumber != PrevFrameNumber || (IsValidRef(FadingState.UniformBuffer) && CurrentRealTime >= FadingState.EndTime)) { It.RemoveCurrent(); } } } static void UpdatePrimitiveFadingStateHelper(FPrimitiveFadingState& FadingState, const FViewInfo& View, bool bVisible) { if (FadingState.bValid) { if (FadingState.bIsVisible != bVisible) { float CurrentRealTime = View.Family->Time.GetRealTimeSeconds(); // Need to kick off a fade, so make sure that we have fading state for that if (!IsValidRef(FadingState.UniformBuffer)) { // Primitive is not currently fading. Start a new fade! FadingState.EndTime = CurrentRealTime + GFadeTime; if (bVisible) { // Fading in // (Time - StartTime) / FadeTime FadingState.FadeTimeScaleBias.X = 1.0f / GFadeTime; FadingState.FadeTimeScaleBias.Y = -CurrentRealTime / GFadeTime; } else { // Fading out // 1 - (Time - StartTime) / FadeTime FadingState.FadeTimeScaleBias.X = -1.0f / GFadeTime; FadingState.FadeTimeScaleBias.Y = 1.0f + CurrentRealTime / GFadeTime; } FDistanceCullFadeUniformShaderParameters Uniforms; Uniforms.FadeTimeScaleBias = FVector2f(FadingState.FadeTimeScaleBias); // LWC_TODO: Precision loss FadingState.UniformBuffer = FDistanceCullFadeUniformBufferRef::CreateUniformBufferImmediate(Uniforms, UniformBuffer_MultiFrame); } else { // Reverse fading direction but maintain current opacity // Solve for d: a*x+b = -a*x+d FadingState.FadeTimeScaleBias.Y = 2.0f * CurrentRealTime * FadingState.FadeTimeScaleBias.X + FadingState.FadeTimeScaleBias.Y; FadingState.FadeTimeScaleBias.X = -FadingState.FadeTimeScaleBias.X; if (bVisible) { // Fading in // Solve for x: a*x+b = 1 FadingState.EndTime = (1.0f - FadingState.FadeTimeScaleBias.Y) / FadingState.FadeTimeScaleBias.X; } else { // Fading out // Solve for x: a*x+b = 0 FadingState.EndTime = -FadingState.FadeTimeScaleBias.Y / FadingState.FadeTimeScaleBias.X; } FDistanceCullFadeUniformShaderParameters Uniforms; Uniforms.FadeTimeScaleBias = FVector2f(FadingState.FadeTimeScaleBias); // LWC_TODO: Precision loss FadingState.UniformBuffer = FDistanceCullFadeUniformBufferRef::CreateUniformBufferImmediate(Uniforms, UniformBuffer_MultiFrame); } } } FadingState.FrameNumber = View.Family->FrameNumber; FadingState.bIsVisible = bVisible; FadingState.bValid = true; } static void UpdatePrimitiveFading(const FScene& Scene, FViewInfo& View, FSceneViewState* ViewState, FPrimitiveRange PrimitiveRange) { if (!ViewState) { return; } SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveFading); #if RHI_RAYTRACING const bool bRayTracingEnabled = IsRayTracingEnabled(View.GetShaderPlatform()) && View.IsRayTracingAllowedForView(); #endif // Should we allow fading transitions at all this frame? For frames where the camera moved // a large distance or where we haven't rendered a view in awhile, it's best to disable // fading so users don't see unexpected object transitions. if (!GDisableLODFade && !View.bDisableDistanceBasedFadeTransitions) { // Do a pass over potentially fading primitives and update their states. for (FSceneSetBitIterator BitIt(View.PotentiallyFadingPrimitiveMap, PrimitiveRange.StartIndex); BitIt.GetIndex() < PrimitiveRange.EndIndex; ++BitIt) { bool bVisible = View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt); FPrimitiveFadingState& FadingState = ViewState->PrimitiveFadingStates.FindOrAdd(Scene.PrimitiveComponentIds[BitIt.GetIndex()]); UpdatePrimitiveFadingStateHelper(FadingState, View, bVisible); FRHIUniformBuffer* UniformBuffer = FadingState.UniformBuffer; if (UniformBuffer && !bVisible) { // If the primitive is fading out make sure it remains visible. View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = true; #if RHI_RAYTRACING // Cannot just assume the ray tracing visibility will be true, so a complete recalculation for its culling needs to happen // This should be a very rare occurrence, so the hit is not worrisome. // TODO: Could this be moved into the actual culling phase? if (bRayTracingEnabled && !ShouldCullForRayTracing(Scene, View, BitIt.GetIndex())) { View.PrimitiveRayTracingVisibilityMap.AccessCorrespondingBit(BitIt) = true; } #endif } View.PrimitiveFadeUniformBuffers[BitIt.GetIndex()] = UniformBuffer; View.PrimitiveFadeUniformBufferMap[BitIt.GetIndex()] = UniformBuffer != nullptr; } } } bool FViewInfo::UpdatePrimitiveFadingState(const FPrimitiveSceneInfo* PrimitiveSceneInfo, bool bFadingIn) { // Update distance-based visibility and fading state if it has not already been updated. const int32 PrimitiveIndex = PrimitiveSceneInfo->GetIndex(); const FRelativeBitReference PrimitiveBit(PrimitiveIndex); bool bStillFading = false; if (!PotentiallyFadingPrimitiveMap.AccessCorrespondingBit(PrimitiveBit)) { FPrimitiveFadingState& FadingState = ((FSceneViewState*)State)->PrimitiveFadingStates.FindOrAdd(PrimitiveSceneInfo->PrimitiveComponentId); UpdatePrimitiveFadingStateHelper(FadingState, *this, bFadingIn); FRHIUniformBuffer* UniformBuffer = FadingState.UniformBuffer; bStillFading = UniformBuffer != nullptr; PrimitiveFadeUniformBuffers[PrimitiveIndex] = UniformBuffer; PrimitiveFadeUniformBufferMap[PrimitiveIndex] = UniformBuffer != nullptr; PotentiallyFadingPrimitiveMap.AccessCorrespondingBit(PrimitiveBit) = true; } // If we're still fading then make sure the object is still drawn, even if it's beyond the max draw distance return !bFadingIn && !bStillFading; } /////////////////////////////////////////////////////////////////////////////// FFilterStaticMeshesForViewData::FFilterStaticMeshesForViewData(FViewInfo& View) { ViewOrigin = View.ViewMatrices.GetViewOrigin(); // outside of the loop to be more efficient ForcedLODLevel = (View.Family->EngineShowFlags.LOD) ? GetCVarForceLOD() : 0; LODScale = CVarStaticMeshLODDistanceScale.GetValueOnRenderThread() * View.LODDistanceFactor; MinScreenRadiusForDepthPrepassSquared = GMinScreenRadiusForDepthPrepass * GMinScreenRadiusForDepthPrepass; bFullEarlyZPass = ShouldForceFullDepthPass(View.GetShaderPlatform()); } /////////////////////////////////////////////////////////////////////////////// FDrawCommandRelevancePacket::FDrawCommandRelevancePacket() { bUseCachedMeshDrawCommands = UseCachedMeshDrawCommands(); for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; ++PassIndex) { NumDynamicBuildRequestElements[PassIndex] = 0; } } void FDrawCommandRelevancePacket::AddCommandsForMesh( int32 PrimitiveIndex, const FPrimitiveSceneInfo* InPrimitiveSceneInfo, const FStaticMeshBatchRelevance& RESTRICT StaticMeshRelevance, const FStaticMeshBatch& RESTRICT StaticMesh, EMeshDrawCommandCullingPayloadFlags CullingPayloadFlags, const FScene& Scene, bool bCanCache, EMeshPass::Type PassType) { const bool bIsNaniteMesh = Scene.PrimitiveFlagsCompact[PrimitiveIndex].bIsNaniteMesh; if (bIsNaniteMesh && Scene.PrimitivesAlwaysVisibleOffset != ~0u) { return; } const EShadingPath ShadingPath = GetFeatureLevelShadingPath(Scene.GetFeatureLevel()); const bool bUseCachedMeshCommand = bUseCachedMeshDrawCommands && !!(FPassProcessorManager::GetPassFlags(ShadingPath, PassType) & EMeshPassFlags::CachedMeshCommands) && StaticMeshRelevance.bSupportsCachingMeshDrawCommands && bCanCache; if (bUseCachedMeshCommand) { const int32 StaticMeshCommandInfoIndex = StaticMeshRelevance.GetStaticMeshCommandInfoIndex(PassType); if (StaticMeshCommandInfoIndex >= 0) { const FCachedMeshDrawCommandInfo& CachedMeshDrawCommand = InPrimitiveSceneInfo->StaticMeshCommandInfos[StaticMeshCommandInfoIndex]; const FCachedPassMeshDrawList& SceneDrawList = Scene.CachedDrawLists[PassType]; // AddUninitialized_GetRef() VisibleCachedDrawCommands[(uint32)PassType].AddUninitialized(); FVisibleMeshDrawCommand& NewVisibleMeshDrawCommand = VisibleCachedDrawCommands[(uint32)PassType].Last(); const FMeshDrawCommand* MeshDrawCommand = CachedMeshDrawCommand.StateBucketId >= 0 ? &Scene.CachedMeshDrawCommandStateBuckets[PassType].GetByElementId(CachedMeshDrawCommand.StateBucketId).Key : &SceneDrawList.MeshDrawCommands[CachedMeshDrawCommand.CommandIndex]; NewVisibleMeshDrawCommand.Setup( MeshDrawCommand, InPrimitiveSceneInfo->GetMDCIdInfo(), CachedMeshDrawCommand.StateBucketId, CachedMeshDrawCommand.MeshFillMode, CachedMeshDrawCommand.MeshCullMode, CachedMeshDrawCommand.Flags, CachedMeshDrawCommand.SortKey, CachedMeshDrawCommand.CullingPayload, CullingPayloadFlags); } } else { NumDynamicBuildRequestElements[PassType] += StaticMeshRelevance.NumElements; DynamicBuildRequests[PassType].Add(&StaticMesh); DynamicBuildFlags[PassType].Add(CullingPayloadFlags); } } /////////////////////////////////////////////////////////////////////////////// FRelevancePacket::FRelevancePacket( FVisibilityTaskData& InTaskData, const FViewInfo& InView, int32 InViewIndex, const FFilterStaticMeshesForViewData& InViewData, uint8* InMarkMasks, const UE::Tasks::FTask& InPrerequisitesTask) : CurrentWorldTime(InView.Family->Time.GetWorldTimeSeconds()) , DeltaWorldTime(InView.Family->Time.GetDeltaWorldTimeSeconds()) , TaskData(InTaskData) , TaskConfig(InTaskData.TaskConfig) , Scene(TaskData.Scene) , View(InView) , ViewCommands(TaskData.DynamicMeshElements.ViewCommandsPerView[InViewIndex]) , ViewBit(1 << InViewIndex) , ViewData(InViewData) , DynamicPrimitiveViewMasks(TaskData.DynamicMeshElements.PrimitiveViewMasks) , MarkMasks(InMarkMasks) , PrerequisitesTask(InPrerequisitesTask) , Input(TaskConfig.Relevance.NumPrimitivesPerPacket) , NotDrawRelevant(TaskConfig.Relevance.NumPrimitivesPerPacket) , TranslucentSelfShadowPrimitives(TaskConfig.Relevance.NumPrimitivesPerPacket) , VisibleDynamicPrimitivesWithSimpleLights(TaskConfig.Relevance.NumPrimitivesPerPacket) , DirtyIndirectLightingCacheBufferPrimitives(TaskConfig.Relevance.NumPrimitivesPerPacket) , NaniteCustomDepthInstances(TaskConfig.Relevance.NumPrimitivesPerPacket) , PrimitivesLODMask(TaskConfig.Relevance.NumPrimitivesPerPacket) , bAddLightmapDensityCommands(TaskData.bAddLightmapDensityCommands) {} void FRelevancePacket::LaunchComputeRelevanceTask() { check(!bComputeRelevanceTaskLaunched); if (!Input.IsEmpty()) { bComputeRelevanceTaskLaunched = true; if (TaskData.DynamicMeshElements.CommandPipe) { TaskData.DynamicMeshElements.CommandPipe->AddNumCommands(1); } ComputeRelevanceTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [this] { FTaskTagScope Scope(ETaskTag::EParallelRenderingThread); FDynamicPrimitiveIndexList DynamicPrimitiveIndexList; ComputeRelevance(DynamicPrimitiveIndexList); if (TaskData.DynamicMeshElements.CommandPipe) { if (DynamicPrimitiveIndexList.IsEmpty()) { TaskData.DynamicMeshElements.CommandPipe->ReleaseNumCommands(1); } else { TaskData.DynamicMeshElements.CommandPipe->EnqueueCommand(MoveTemp(DynamicPrimitiveIndexList)); } } }, PrerequisitesTask, TaskConfig.Relevance.ComputeRelevanceTaskPriority); } } void FRelevancePacket::Finalize() { FViewInfo& WriteView = const_cast(View); FViewCommands& WriteViewCommands = const_cast(ViewCommands); const EShadingPath ShadingPath = GetFeatureLevelShadingPath(Scene.GetFeatureLevel()); for (int32 BitIndex : NotDrawRelevant.Prims) { WriteView.PrimitiveVisibilityMap[BitIndex] = false; } TaskConfig.Relevance.NumPrimitivesProcessed += Input.Prims.Num(); #if WITH_EDITOR WriteView.EditorVisualizeLevelInstancesNanite.Append(EditorVisualizeLevelInstancesNanite); WriteView.EditorSelectedInstancesNanite.Append(EditorSelectedInstancesNanite); WriteView.EditorSelectedInstancesNanite.Append(EditorOverlaidInstancesNanite); WriteView.EditorSelectedNaniteHitProxyIds.Append(EditorSelectedNaniteHitProxyIds); #endif WriteView.ShadingModelMaskInView |= CombinedShadingModelMask; WriteView.bUsesGlobalDistanceField |= bUsesGlobalDistanceField; WriteView.bUsesLightingChannels |= bUsesLightingChannels; WriteView.bTranslucentSurfaceLighting |= bTranslucentSurfaceLighting; WriteView.bSceneHasSkyMaterial |= bSceneHasSkyMaterial; WriteView.bHasSingleLayerWaterMaterial |= bHasSingleLayerWaterMaterial; WriteView.bUsesSecondStageDepthPass |= bUsesSecondStageDepthPass && ShadingPath != EShadingPath::Mobile; VisibleDynamicPrimitivesWithSimpleLights.AppendTo(WriteView.VisibleDynamicPrimitivesWithSimpleLights); WriteView.NumVisibleDynamicPrimitives += NumVisibleDynamicPrimitives; WriteView.NumVisibleDynamicEditorPrimitives += NumVisibleDynamicEditorPrimitives; WriteView.TranslucentPrimCount.Append(TranslucentPrimCount); WriteView.bHasDistortionPrimitives |= bHasDistortionPrimitives; WriteView.bHasCustomDepthPrimitives |= bHasCustomDepthPrimitives; WriteView.CustomDepthStencilValues.Append(CustomDepthStencilValues); NaniteCustomDepthInstances.AppendTo(WriteView.NaniteCustomDepthInstances); WriteView.bUsesCustomDepth |= bUsesCustomDepth; WriteView.bUsesMotionVectorWorldOffset |= bUsesMotionVectorWorldOffset; WriteView.bUsesCustomStencil |= bUsesCustomStencil; WriteView.SubstrateViewData.MaxClosurePerPixel = FMath::Max(WriteView.SubstrateViewData.MaxClosurePerPixel, 8u - FMath::CountLeadingZeros8(SubstrateClosureCountMask)); WriteView.SubstrateViewData.MaxBytesPerPixel = FMath::Max(WriteView.SubstrateViewData.MaxBytesPerPixel, SubstrateUintPerPixel * 4u); WriteView.SubstrateViewData.UsesTileTypeMask |= SubstrateTileTypeMask; WriteView.SubstrateViewData.bUsesAnisotropy |= bUsesAnisotropy; DirtyIndirectLightingCacheBufferPrimitives.AppendTo(WriteView.DirtyIndirectLightingCacheBufferPrimitives); WriteView.VolumetricMeshBatches.Append(VolumetricMeshBatches); WriteView.HeterogeneousVolumesMeshBatches.Append(HeterogeneousVolumesMeshBatches); WriteView.SkyMeshBatches.Append(SkyMeshBatches); WriteView.SortedTrianglesMeshBatches.Append(SortedTrianglesMeshBatches); for (FPrimitiveLODMask PrimitiveLODMask : PrimitivesLODMask.Prims) { WriteView.PrimitivesLODMask[PrimitiveLODMask.PrimitiveIndex] = PrimitiveLODMask.LODMask; } for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++) { if (PassIndex == EMeshPass::NaniteMeshPass && Scene.PrimitivesAlwaysVisibleOffset != ~0u) { continue; } FPassDrawCommandArray& SrcCommands = DrawCommandPacket.VisibleCachedDrawCommands[PassIndex]; FMeshCommandOneFrameArray& DstCommands = WriteViewCommands.MeshCommands[PassIndex]; if (SrcCommands.Num() > 0) { static_assert(sizeof(SrcCommands[0]) == sizeof(DstCommands[0]), "Memcpy sizes must match."); const int32 PrevNum = DstCommands.AddUninitialized(SrcCommands.Num()); FMemory::Memcpy(&DstCommands[PrevNum], &SrcCommands[0], SrcCommands.Num() * sizeof(SrcCommands[0])); } FPassDrawCommandBuildRequestArray& SrcRequests = DrawCommandPacket.DynamicBuildRequests[PassIndex]; TArray& DstRequests = WriteViewCommands.DynamicMeshCommandBuildRequests[PassIndex]; if (SrcRequests.Num() > 0) { static_assert(sizeof(SrcRequests[0]) == sizeof(DstRequests[0]), "Memcpy sizes must match."); const int32 PrevNum = DstRequests.AddUninitialized(SrcRequests.Num()); FMemory::Memcpy(&DstRequests[PrevNum], &SrcRequests[0], SrcRequests.Num() * sizeof(SrcRequests[0])); } FPassDrawCommandBuildFlagsArray& SrcFlags = DrawCommandPacket.DynamicBuildFlags[PassIndex]; TArray& DstFlags = WriteViewCommands.DynamicMeshCommandBuildFlags[PassIndex]; if (SrcFlags.Num() > 0) { static_assert(sizeof(SrcFlags[0]) == sizeof(DstFlags[0]), "Memcpy sizes must match."); const int32 PrevNum = DstFlags.AddUninitialized(SrcFlags.Num()); FMemory::Memcpy(&DstFlags[PrevNum], &SrcFlags[0], SrcFlags.Num() * sizeof(SrcFlags[0])); } WriteViewCommands.NumDynamicMeshCommandBuildRequestElements[PassIndex] += DrawCommandPacket.NumDynamicBuildRequestElements[PassIndex]; } // Prepare translucent self shadow uniform buffers. for (int32 PrimitiveIndex : TranslucentSelfShadowPrimitives.Prims) { FUniformBufferRHIRef& UniformBuffer = WriteView.TranslucentSelfShadowUniformBufferMap.FindOrAdd(PrimitiveIndex); if (!UniformBuffer) { FTranslucentSelfShadowUniformParameters Parameters; SetupTranslucentSelfShadowUniformParameters(nullptr, Parameters); UniformBuffer = FTranslucentSelfShadowUniformParameters::CreateUniformBuffer(Parameters, EUniformBufferUsage::UniformBuffer_SingleFrame); } } } void FRelevancePacket::ComputeRelevance(FDynamicPrimitiveIndexList& DynamicPrimitiveIndexList) { TRACE_CPUPROFILER_EVENT_SCOPE(ComputeViewRelevance); SCOPE_CYCLE_COUNTER(STAT_ComputeViewRelevance); CombinedShadingModelMask = 0; SubstrateUintPerPixel = 0; SubstrateTileTypeMask = 0; bUsesAnisotropy = false; SubstrateClosureCountMask = 0; bSceneHasSkyMaterial = 0; bHasSingleLayerWaterMaterial = 0; bUsesSecondStageDepthPass = 0; bUsesGlobalDistanceField = false; bUsesLightingChannels = false; bTranslucentSurfaceLighting = false; const EShadingPath ShadingPath = GetFeatureLevelShadingPath(Scene.GetFeatureLevel()); const bool bHairStrandsEnabled = IsHairStrandsEnabled(EHairStrandsShaderType::All, Scene.GetShaderPlatform()); const bool bIsTLVUsingVoxelMarking = IsTranslucencyLightingVolumeUsingVoxelMarking(); int32 NumVisibleStaticMeshElements = 0; FViewInfo& WriteView = const_cast(View); const FSceneViewState* ViewState = (FSceneViewState*)View.State; const bool bMobileMaskedInEarlyPass = (ShadingPath == EShadingPath::Mobile) && Scene.EarlyZPassMode == DDM_MaskedOnly; const bool bMobileBasePassAlwaysUsesCSM = (ShadingPath == EShadingPath::Mobile) && MobileBasePassAlwaysUsesCSM(Scene.GetShaderPlatform()); const bool bVelocityPassWritesDepth = Scene.EarlyZPassMode == DDM_AllOpaqueNoVelocity; const bool bIsTranslucentHoldoutEnabled = IsPrimitiveAlphaHoldoutEnabled(View); const bool bIsTranslucentClippedDepthEnabled = NeedTSRThinGeometryDetectionPass(View); const bool bHLODActive = Scene.SceneLODHierarchy.IsActive(); const bool bRenderCustomDepth = (ShadingPath == EShadingPath::Mobile) ? FMobileSceneRenderer::ShouldRenderCustomDepth(View) : true; const bool bMobileSupportsSM5MaterialNodes = MobileSupportsSM5MaterialNodes(View.GetShaderPlatform()); const FHLODVisibilityState* const HLODState = bHLODActive && ViewState ? &ViewState->HLODVisibilityState : nullptr; float MaxDrawDistanceScale = GetCachedScalabilityCVars().ViewDistanceScale; MaxDrawDistanceScale *= GetCachedScalabilityCVars().CalculateFieldOfViewDistanceScale(View.DesiredFOV); const auto AddEditorDynamicPrimitive = [this, &DynamicPrimitiveIndexList](int32 PrimitiveIndex) { #if WITH_EDITOR if (GIsEditor) { ++NumVisibleDynamicEditorPrimitives; if (DynamicPrimitiveViewMasks) { FPlatformAtomics::InterlockedOr((volatile int8*)&DynamicPrimitiveViewMasks->EditorPrimitives[PrimitiveIndex], ViewBit); } else { DynamicPrimitiveIndexList.EditorPrimitives.Emplace(PrimitiveIndex, ViewBit); } } #endif }; const auto AddDynamicPrimitive = [this, &DynamicPrimitiveIndexList](int32 PrimitiveIndex) { ++NumVisibleDynamicPrimitives; if (DynamicPrimitiveViewMasks) { FPlatformAtomics::InterlockedOr((volatile int8*)&DynamicPrimitiveViewMasks->Primitives[PrimitiveIndex], ViewBit); } else { DynamicPrimitiveIndexList.Primitives.Emplace(PrimitiveIndex, ViewBit); } }; for (int32 InputPrimsIndex = 0; InputPrimsIndex < Input.Prims.Num(); ++InputPrimsIndex) { int32 BitIndex = Input.Prims[InputPrimsIndex]; if (InputPrimsIndex + 1 < Input.Prims.Num()) { int32 NextBitIndex = Input.Prims[InputPrimsIndex + 1]; // Prefetch the next primitive / proxy pair for the next loop. FPlatformMisc::Prefetch(Scene.Primitives[NextBitIndex]); FPlatformMisc::Prefetch(Scene.PrimitiveSceneProxies[NextBitIndex]); } FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene.Primitives[BitIndex]; FPrimitiveViewRelevance& ViewRelevance = const_cast(View.PrimitiveViewRelevanceMap[BitIndex]); const FPrimitiveSceneProxy* PrimitiveSceneProxy = PrimitiveSceneInfo->Proxy; // Prefetch the scene data and static mesh relevance array now while we call GetViewRelevance to reduce memory waits. FPlatformMisc::Prefetch(PrimitiveSceneInfo->StaticMeshRelevances.GetData()); FPlatformMisc::Prefetch(PrimitiveSceneInfo->GetSceneData()); ViewRelevance = PrimitiveSceneProxy->GetViewRelevance(&View); ViewRelevance.bInitializedThisFrame = true; const bool bStaticRelevance = ViewRelevance.bStaticRelevance; const bool bDrawRelevance = ViewRelevance.bDrawRelevance; const bool bDynamicRelevance = ViewRelevance.bDynamicRelevance; const bool bShadowRelevance = ViewRelevance.bShadowRelevance; const bool bEditorRelevance = ViewRelevance.bEditorPrimitiveRelevance; const bool bEditorVisualizeLevelInstanceRelevance = ViewRelevance.bEditorVisualizeLevelInstanceRelevance; const bool bEditorSelectionRelevance = ViewRelevance.bEditorStaticSelectionRelevance; const bool bTranslucentRelevance = ViewRelevance.HasTranslucency(); const bool bHairStrandsRelevance = bHairStrandsEnabled && ViewRelevance.bHairStrands; if (View.bIsReflectionCapture && !PrimitiveSceneProxy->IsVisibleInReflectionCaptures()) { NotDrawRelevant.AddPrim(BitIndex); continue; } if (bStaticRelevance && (bDrawRelevance || bShadowRelevance)) { const FDesiredLODLevel DesiredLODLevel = PrimitiveSceneProxy->GetDesiredLODLevel_RenderThread(&View); int32 PrimitiveIndex = BitIndex; const FPrimitiveBounds& Bounds = Scene.PrimitiveBounds[PrimitiveIndex]; const bool bIsPrimitiveDistanceCullFading = View.PrimitiveFadeUniformBufferMap[PrimitiveIndex]; check(DesiredLODLevel.LOD >= 0); float MeshScreenSizeSquared = 0; FLODMask LODToRender; if (DesiredLODLevel.IsFixed()) { LODToRender.SetLOD(DesiredLODLevel.LOD); } else { LODToRender = ComputeLODForMeshes(PrimitiveSceneInfo->StaticMeshRelevances, View, Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.SphereRadius, PrimitiveSceneInfo->GpuLodInstanceRadius, ViewData.ForcedLODLevel, MeshScreenSizeSquared, DesiredLODLevel.LOD, ViewData.LODScale); } PrimitivesLODMask.AddPrim(FRelevancePacket::FPrimitiveLODMask(PrimitiveIndex, LODToRender)); const bool bIsHLODFading = HLODState ? HLODState->IsNodeFading(PrimitiveIndex) : false; const bool bIsHLODFadingOut = HLODState ? HLODState->IsNodeFadingOut(PrimitiveIndex) : false; const bool bIsLODDithered = LODToRender.IsDithered(); const bool bIsLODRange = LODToRender.IsLODRange(); float DistanceSquared = (Bounds.BoxSphereBounds.Origin - ViewData.ViewOrigin).SizeSquared(); const float LODFactorDistanceSquared = DistanceSquared * FMath::Square(ViewData.LODScale); const bool bDrawDepthOnly = ViewData.bFullEarlyZPass || ((ShadingPath != EShadingPath::Mobile) && (FMath::Square(Bounds.BoxSphereBounds.SphereRadius) > GMinScreenRadiusForDepthPrepass * GMinScreenRadiusForDepthPrepass * LODFactorDistanceSquared)); const int32 NumStaticMeshes = PrimitiveSceneInfo->StaticMeshRelevances.Num(); for (int32 MeshIndex = 0; MeshIndex < NumStaticMeshes; MeshIndex++) { const FStaticMeshBatchRelevance& StaticMeshRelevance = PrimitiveSceneInfo->StaticMeshRelevances[MeshIndex]; const FStaticMeshBatch& StaticMesh = PrimitiveSceneInfo->StaticMeshes[MeshIndex]; if (StaticMeshRelevance.bOverlayMaterial && !View.Family->EngineShowFlags.DistanceCulledPrimitives) { // Overlay mesh can have its own cull distance that is shorter than primitive cull distance float OverlayMaterialMaxDrawDistance = GOverlayMaterialMaxDrawDistanceOverride > 0.0f ? GOverlayMaterialMaxDrawDistanceOverride : StaticMeshRelevance.ScreenSize; if (OverlayMaterialMaxDrawDistance > 0.f && OverlayMaterialMaxDrawDistance != FLT_MAX) { if (DistanceSquared > FMath::Square(OverlayMaterialMaxDrawDistance * MaxDrawDistanceScale)) { // distance culled continue; } } } int8 StaticMeshLODIndex = StaticMeshRelevance.GetLODIndex(); if (LODToRender.ContainsLOD(StaticMeshLODIndex)) { uint8 MarkMask = 0; bool bHiddenByHLODFade = false; // Hide mesh LOD levels that HLOD is substituting if (bIsHLODFading) { if (bIsHLODFadingOut) { if (bIsLODDithered && LODToRender.LODIndex1 == StaticMeshLODIndex) { bHiddenByHLODFade = true; } else { MarkMask |= EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask; } } else { if (bIsLODDithered && LODToRender.LODIndex0 == StaticMeshLODIndex) { bHiddenByHLODFade = true; } else { MarkMask |= EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask; } } } else if (bIsLODDithered) { if (LODToRender.LODIndex0 == StaticMeshLODIndex) { MarkMask |= EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask; } else { MarkMask |= EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask; } } // Don't cache if it requires per view per mesh state for LOD dithering or distance cull fade. const bool bIsMeshDitheringLOD = StaticMeshRelevance.bDitheredLODTransition && (MarkMask & (EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask | EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask)); const bool bCanCache = !bIsPrimitiveDistanceCullFading && !bIsMeshDitheringLOD; // When we apply LOD selection on GPU we submit a range of LODs and then cull each one according to screen size. // At both ends of a LOD range we only want to cull by screen size in one direction. This ensures that all possible screen sizes map to one LOD in the range. EMeshDrawCommandCullingPayloadFlags CullingPayloadFlags = EMeshDrawCommandCullingPayloadFlags::Default; CullingPayloadFlags |= bIsLODRange && !LODToRender.IsMaxLODInRange(StaticMeshRelevance.GetLODIndex()) ? EMeshDrawCommandCullingPayloadFlags::MinScreenSizeCull : (EMeshDrawCommandCullingPayloadFlags)0; CullingPayloadFlags |= bIsLODRange && !LODToRender.IsMinLODInRange(StaticMeshRelevance.GetLODIndex()) ? EMeshDrawCommandCullingPayloadFlags::MaxScreenSizeCull : (EMeshDrawCommandCullingPayloadFlags)0; if (ViewRelevance.bDrawRelevance) { if ((StaticMeshRelevance.bUseForMaterial || StaticMeshRelevance.bUseAsOccluder) && (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth || ViewRelevance.bRenderInDepthPass) && !bHiddenByHLODFade) { // Add velocity commands first to track for case where velocity pass writes depth. bool bIsMeshInVelocityPass = false; if (StaticMeshRelevance.bUseForMaterial && ViewRelevance.bRenderInMainPass) { if (ViewRelevance.HasVelocity()) { if (FVelocityMeshProcessor::PrimitiveHasVelocityForView(View, PrimitiveSceneProxy)) { if (ViewRelevance.bVelocityRelevance && FOpaqueVelocityMeshProcessor::PrimitiveCanHaveVelocity(View.GetShaderPlatform(), PrimitiveSceneProxy) && FOpaqueVelocityMeshProcessor::PrimitiveHasVelocityForFrame(View.GetShaderPlatform(), PrimitiveSceneProxy)) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::Velocity); bIsMeshInVelocityPass = true; if (PrimitiveSceneProxy->AnyMaterialUsesMotionVectorWorldOffset()) { bUsesMotionVectorWorldOffset = true; } } if (ViewRelevance.bOutputsTranslucentVelocity && FTranslucentVelocityMeshProcessor::PrimitiveCanHaveVelocity(View.GetShaderPlatform(), PrimitiveSceneProxy) && FTranslucentVelocityMeshProcessor::PrimitiveHasVelocityForFrame(PrimitiveSceneProxy)) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::TranslucentVelocity); // Only Before DoF Translucency needs this pass const bool bAnyMaterialUsesTemporalResponsiveness = PrimitiveSceneProxy->AnyMaterialUsesTemporalResponsiveness(); if (ViewRelevance.bNormalTranslucency && bIsTranslucentClippedDepthEnabled && bAnyMaterialUsesTemporalResponsiveness) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::TranslucentVelocityClippedDepth); } } } } } // Add depth commands. if (StaticMeshRelevance.bUseForDepthPass && (bDrawDepthOnly || (bMobileMaskedInEarlyPass && ViewRelevance.bMasked))) { if (!(bIsMeshInVelocityPass && bVelocityPassWritesDepth)) { if (ViewRelevance.bRenderInSecondStageDepthPass && ShadingPath != EShadingPath::Mobile) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::SecondStageDepthPass); } else { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::DepthPass); } } #if RHI_RAYTRACING // Only used by ray traced shadows if (IsRayTracingEnabled() && View.bHasRayTracingShadows && View.IsRayTracingAllowedForView()) { if (MarkMask & EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::DitheredLODFadingOutMaskPass); } } #endif } // Mark static mesh as visible for rendering if (StaticMeshRelevance.bUseForMaterial && (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth)) { // Specific logic for mobile packets if (ShadingPath == EShadingPath::Mobile) { if (StaticMeshRelevance.bUseSingleLayerWaterMaterial && bMobileSupportsSM5MaterialNodes) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::SingleLayerWaterPass); } // Skydome must not be added to base pass bucket else if (!StaticMeshRelevance.bUseSkyMaterial) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::BasePass); if (!bMobileBasePassAlwaysUsesCSM) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::MobileBasePassCSM); } } else { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::SkyPass); } // bUseSingleLayerWaterMaterial is added to BasePass on Mobile. No need to add it to SingleLayerWaterPass MarkMask |= EMarkMaskBits::StaticMeshVisibilityMapMask; } else // Regular shading path { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::BasePass); MarkMask |= EMarkMaskBits::StaticMeshVisibilityMapMask; if (StaticMeshRelevance.bUseSkyMaterial) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::SkyPass); } if (StaticMeshRelevance.bUseSingleLayerWaterMaterial) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::SingleLayerWaterPass); DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::SingleLayerWaterDepthPrepass); } } if (StaticMeshRelevance.bUseAnisotropy) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::AnisotropyPass); } if (ViewRelevance.bRenderCustomDepth && bRenderCustomDepth) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::CustomDepth); } if (bAddLightmapDensityCommands) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::LightmapDensity); } #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) else if (View.Family->UseDebugViewPS()) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::DebugViewMode); } #endif #if WITH_EDITOR if (StaticMeshRelevance.bSelectable) { if (View.bAllowTranslucentPrimitivesInHitProxy) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::HitProxy); } else { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::HitProxyOpaqueOnly); } } #endif ++NumVisibleStaticMeshElements; INC_DWORD_STAT_BY(STAT_StaticMeshTriangles, StaticMesh.GetNumPrimitives()); } } if (StaticMeshRelevance.bUseForMaterial && ViewRelevance.HasTranslucency() && !ViewRelevance.bEditorPrimitiveRelevance && ViewRelevance.bRenderInMainPass) { if (View.Family->AllowTranslucencyAfterDOF()) { if ((ViewRelevance.bNormalTranslucency || (View.AutoBeforeDOFTranslucencyBoundary > 0.0f && ViewRelevance.bSeparateTranslucency))) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::TranslucencyStandard); } if ((ViewRelevance.bNormalTranslucency || (View.AutoBeforeDOFTranslucencyBoundary > 0.0f && ViewRelevance.bSeparateTranslucency)) && ViewRelevance.bTranslucencyModulate && View.Family->AllowStandardTranslucencySeparated()) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::TranslucencyStandardModulate); } if (ViewRelevance.bSeparateTranslucency) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::TranslucencyAfterDOF); } if (ViewRelevance.bSeparateTranslucency && ViewRelevance.bTranslucencyModulate) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::TranslucencyAfterDOFModulate); } if (ViewRelevance.bPostMotionBlurTranslucency) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::TranslucencyAfterMotionBlur); } } else { // Otherwise, everything is rendered in a single bucket. This is not related to whether DOF is currently enabled or not. // When using all translucency, Standard and AfterDOF are sorted together instead of being rendered like 2 buckets. DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::TranslucencyAll); } if (bIsTranslucentHoldoutEnabled) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::TranslucencyHoldout); } if (ViewRelevance.bTranslucentSurfaceLighting || bIsTLVUsingVoxelMarking) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::LumenTranslucencyRadianceCacheMark); } if (ViewRelevance.bTranslucentSurfaceLighting) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::LumenFrontLayerTranslucencyGBuffer); } if (ViewRelevance.bDistortion) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::Distortion); } } #if WITH_EDITOR if (ViewRelevance.bEditorVisualizeLevelInstanceRelevance) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::EditorLevelInstance); } if (ViewRelevance.bEditorStaticSelectionRelevance) { DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::EditorSelection); } #endif if (ViewRelevance.bHasVolumeMaterialDomain) { if (ShouldRenderMeshBatchWithHeterogeneousVolumes(&StaticMesh, PrimitiveSceneProxy, View.FeatureLevel)) { HeterogeneousVolumesMeshBatches.AddUninitialized(1); FVolumetricMeshBatch& BatchAndProxy = HeterogeneousVolumesMeshBatches.Last(); BatchAndProxy.Mesh = &StaticMesh; BatchAndProxy.Proxy = PrimitiveSceneProxy; } else { VolumetricMeshBatches.AddUninitialized(1); FVolumetricMeshBatch& BatchAndProxy = VolumetricMeshBatches.Last(); BatchAndProxy.Mesh = &StaticMesh; BatchAndProxy.Proxy = PrimitiveSceneProxy; } } if (ViewRelevance.bUsesSkyMaterial) { SkyMeshBatches.AddUninitialized(1); FSkyMeshBatch& BatchAndProxy = SkyMeshBatches.Last(); BatchAndProxy.Mesh = &StaticMesh; BatchAndProxy.Proxy = PrimitiveSceneProxy; BatchAndProxy.bVisibleInMainPass = ViewRelevance.bRenderInMainPass; BatchAndProxy.bVisibleInRealTimeSkyCapture = PrimitiveSceneInfo->bVisibleInRealTimeSkyCapture; } if (ViewRelevance.HasTranslucency() && PrimitiveSceneProxy->SupportsSortedTriangles()) // Need to check material as well { SortedTrianglesMeshBatches.AddUninitialized(1); FSortedTrianglesMeshBatch& BatchAndProxy = SortedTrianglesMeshBatches.Last(); BatchAndProxy.Mesh = &StaticMesh; BatchAndProxy.Proxy = PrimitiveSceneProxy; } // FIXME: Now if a primitive has one batch with a decal material all primitive mesh batches will be added as decals // Because ViewRelevance is a sum of all material relevances in the primitive if (ViewRelevance.bRenderInMainPass && ViewRelevance.bDecal && StaticMeshRelevance.bUseForMaterial) { for (uint8 DecalRenderTargetMode = 0; DecalRenderTargetMode < (uint8)EDecalRenderTargetMode::Num; ++DecalRenderTargetMode) { if (DecalRendering::IsCompatibleWithRenderTargetMode(StaticMeshRelevance.DecalRenderTargetModeMask, (EDecalRenderTargetMode)DecalRenderTargetMode)) { EMeshPass::Type DecalMeshPassType = DecalRendering::GetMeshPassType((EDecalRenderTargetMode)DecalRenderTargetMode); DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, DecalMeshPassType); } } } } if (MarkMask) { MarkMasks[StaticMeshRelevance.Id] = MarkMask; } } } } if (!bDrawRelevance) { NotDrawRelevant.AddPrim(BitIndex); continue; } #if WITH_EDITOR auto CollectNaniteInstanceDraws = []( const FPrimitiveSceneInfo& PrimitiveSceneInfo, const FPrimitiveSceneProxy* PrimitiveSceneProxy, TArray& OutSelectedInstanceDraws, TArray* OutOverlaidInstanceDraws, TArray* OutSelectedInstanceHitProxyIDs, bool bSelectedInstancesOnly ) { if (!PrimitiveSceneProxy->IsNaniteMesh()) { return; } auto* NaniteProxy = static_cast(PrimitiveSceneProxy); if (bSelectedInstancesOnly) { if (!NaniteProxy->IsSelected() && !NaniteProxy->WantsEditorEffects()) { // We're only concerned with selected instances or those with editor effects return; } if (NaniteProxy->IsSelected() && !NaniteProxy->HasSelectedInstances() && OutSelectedInstanceHitProxyIDs != nullptr) { // Primitive is selected but not individual instances, so just add the primitive's hit proxy IDs for (auto& HitProxyId : NaniteProxy->GetHitProxyIds()) { const uint32 HitProxyID = HitProxyId.GetColor().ToPackedABGR(); OutSelectedInstanceHitProxyIDs->Add(HitProxyID); } } } const FInstanceSceneDataBuffers* InstanceSceneDataBuffers = PrimitiveSceneInfo.GetInstanceSceneDataBuffers(); const bool bCollectInstanceHitProxyIds = bSelectedInstancesOnly && NaniteProxy->HasSelectedInstances() && OutSelectedInstanceHitProxyIDs != nullptr && InstanceSceneDataBuffers != nullptr && !InstanceSceneDataBuffers->IsInstanceDataGPUOnly(); const bool bOverlaidDraws = OutOverlaidInstanceDraws && NaniteProxy->WantsEditorEffects() && !NaniteProxy->IsSelected(); const int32 MaxInstances = PrimitiveSceneInfo.GetNumInstanceSceneDataEntries(); TArray& OutDrawArray = bOverlaidDraws ? *OutOverlaidInstanceDraws : OutSelectedInstanceDraws; OutDrawArray.Reserve(OutDrawArray.Num() + MaxInstances); for (int32 Idx = 0; Idx < MaxInstances; ++Idx) { if (bCollectInstanceHitProxyIds) { FInstanceSceneDataBuffers::FReadView ProxyData = InstanceSceneDataBuffers->GetReadView(); // If we have per-instance editor data, exclude instance draws of unselected instances // draws of unselected instances if (ProxyData.InstanceEditorData.IsValidIndex(Idx)) { FColor HitProxyColor; bool bSelected; FInstanceEditorData::Unpack(ProxyData.InstanceEditorData[Idx], HitProxyColor, bSelected); if (!bSelected) { continue; } const uint32 HitProxyID = HitProxyColor.ToPackedABGR(); OutSelectedInstanceHitProxyIDs->Add(HitProxyID); } } OutDrawArray.Add( Nanite::FInstanceDraw { uint32(PrimitiveSceneInfo.GetInstanceSceneDataOffset() + Idx), 0u } ); } }; if (bEditorVisualizeLevelInstanceRelevance) { CollectNaniteInstanceDraws(*PrimitiveSceneInfo, PrimitiveSceneProxy, EditorVisualizeLevelInstancesNanite, nullptr, nullptr, false); } if (bEditorSelectionRelevance) { CollectNaniteInstanceDraws(*PrimitiveSceneInfo, PrimitiveSceneProxy, EditorSelectedInstancesNanite, &EditorOverlaidInstancesNanite, &EditorSelectedNaniteHitProxyIds, true); } #endif if (bEditorRelevance) { AddEditorDynamicPrimitive(BitIndex); } else if(bDynamicRelevance) { AddDynamicPrimitive(BitIndex); if (ViewRelevance.bHasSimpleLights) { VisibleDynamicPrimitivesWithSimpleLights.AddPrim(PrimitiveSceneInfo); } } // Filter with !bStaticRelevance to avoid regular/non-strands geometry to be added two times. else if (!bStaticRelevance && bHairStrandsRelevance) { AddDynamicPrimitive(BitIndex); } if (bTranslucentRelevance && !bEditorRelevance && ViewRelevance.bRenderInMainPass) { if (View.Family->AllowTranslucencyAfterDOF()) { if ((ViewRelevance.bNormalTranslucency || (View.AutoBeforeDOFTranslucencyBoundary > 0.0f && ViewRelevance.bSeparateTranslucency))) { TranslucentPrimCount.Add(ETranslucencyPass::TPT_TranslucencyStandard, ViewRelevance.bUsesSceneColorCopy); } if ((ViewRelevance.bNormalTranslucency || (View.AutoBeforeDOFTranslucencyBoundary > 0.0f && ViewRelevance.bSeparateTranslucency)) && ViewRelevance.bTranslucencyModulate && View.Family->AllowStandardTranslucencySeparated()) { TranslucentPrimCount.Add(ETranslucencyPass::TPT_TranslucencyStandardModulate, ViewRelevance.bUsesSceneColorCopy); } if (ViewRelevance.bSeparateTranslucency) { TranslucentPrimCount.Add(ETranslucencyPass::TPT_TranslucencyAfterDOF, ViewRelevance.bUsesSceneColorCopy); } if (ViewRelevance.bSeparateTranslucency && ViewRelevance.bTranslucencyModulate) { TranslucentPrimCount.Add(ETranslucencyPass::TPT_TranslucencyAfterDOFModulate, ViewRelevance.bUsesSceneColorCopy); } if (ViewRelevance.bPostMotionBlurTranslucency) { TranslucentPrimCount.Add(ETranslucencyPass::TPT_TranslucencyAfterMotionBlur, ViewRelevance.bUsesSceneColorCopy); } } else // Otherwise, everything is rendered in a single bucket. This is not related to whether DOF is currently enabled or not. { // When using all translucency, Standard and AfterDOF are sorted together instead of being rendered like 2 buckets. TranslucentPrimCount.Add(ETranslucencyPass::TPT_AllTranslucency, ViewRelevance.bUsesSceneColorCopy); } if (bIsTranslucentHoldoutEnabled) { TranslucentPrimCount.Add(ETranslucencyPass::TPT_TranslucencyHoldout, true); // use scene copy } if (ViewRelevance.bDistortion) { bHasDistortionPrimitives = true; } } CombinedShadingModelMask |= ViewRelevance.ShadingModelMask; SubstrateUintPerPixel = FMath::Max(SubstrateUintPerPixel, uint8(ViewRelevance.SubstrateUintPerPixel)); SubstrateTileTypeMask |= ViewRelevance.SubstrateTileTypeMask; bUsesAnisotropy |= ViewRelevance.bUsesAnisotropy; SubstrateClosureCountMask |= ViewRelevance.SubstrateClosureCountMask; bUsesGlobalDistanceField |= ViewRelevance.bUsesGlobalDistanceField; bUsesLightingChannels |= ViewRelevance.bUsesLightingChannels; bTranslucentSurfaceLighting |= ViewRelevance.bTranslucentSurfaceLighting; bUsesCustomDepth |= (ViewRelevance.CustomDepthStencilUsageMask & 1) > 0; bUsesCustomStencil |= (ViewRelevance.CustomDepthStencilUsageMask & (1 << 1)) > 0; bSceneHasSkyMaterial |= ViewRelevance.bUsesSkyMaterial; bHasSingleLayerWaterMaterial |= ViewRelevance.bUsesSingleLayerWaterMaterial; bUsesSecondStageDepthPass |= ViewRelevance.bRenderInSecondStageDepthPass && ShadingPath!=EShadingPath::Mobile; if (ViewRelevance.bRenderCustomDepth) { bHasCustomDepthPrimitives = true; CustomDepthStencilValues.Add(PrimitiveSceneInfo->Proxy->GetCustomDepthStencilValue()); if (PrimitiveSceneInfo->Proxy->IsNaniteMesh()) { check(PrimitiveSceneInfo->IsIndexValid()); NaniteCustomDepthInstances.AddPrim( FPrimitiveInstanceRange { PrimitiveSceneInfo->GetIndex(), PrimitiveSceneInfo->GetInstanceSceneDataOffset(), PrimitiveSceneInfo->GetNumInstanceSceneDataEntries() } ); } } extern bool GUseTranslucencyShadowDepths; if (GUseTranslucencyShadowDepths && ViewRelevance.bTranslucentSelfShadow) { TranslucentSelfShadowPrimitives.AddPrim(BitIndex); } PrimitiveSceneInfo->LastRenderTime = CurrentWorldTime; // Update the last component render time only if we know for certain the primitive is un-occluded. if ((View.PrimitiveDefinitelyUnoccludedMap[BitIndex] || View.Family->EngineShowFlags.Wireframe) && !PrimitiveSceneProxy->IsAlwaysVisible()) { const bool bUpdateLastRenderTimeOnScreen = true; PrimitiveSceneInfo->UpdateComponentLastRenderTime(CurrentWorldTime, bUpdateLastRenderTimeOnScreen); } // Cache the nearest reflection proxy if needed if (PrimitiveSceneInfo->NeedsReflectionCaptureUpdate()) { // mobile should not have any outstanding reflection capture update requests at this point, except for when lighting isn't rebuilt PrimitiveSceneInfo->CacheReflectionCaptures(); } if (PrimitiveSceneInfo->NeedsIndirectLightingCacheBufferUpdate()) { DirtyIndirectLightingCacheBufferPrimitives.AddPrim(PrimitiveSceneInfo); } } static_assert(sizeof(WriteView.NumVisibleStaticMeshElements) == sizeof(int32), "Atomic is the wrong size"); FPlatformAtomics::InterlockedAdd((volatile int32*)&WriteView.NumVisibleStaticMeshElements, NumVisibleStaticMeshElements); } /////////////////////////////////////////////////////////////////////////////// FComputeAndMarkRelevance::FComputeAndMarkRelevance(FVisibilityTaskData& InTaskData, FScene& InScene, FViewInfo& InView, uint8 InViewIndex, const UE::Tasks::FTask& InPrerequisitesTask) : TaskData(InTaskData) , Scene(InScene) , View(InView) , ViewCommands(TaskData.DynamicMeshElements.ViewCommandsPerView[InViewIndex]) , ViewIndex(InViewIndex) , ViewData(View) , NumMeshes(Scene.StaticMeshes.GetMaxIndex()) , NumPrimitivesPerPacket(InTaskData.TaskConfig.Relevance.NumPrimitivesPerPacket) , PrerequisitesTask(InPrerequisitesTask) , bLaunchOnAddPrimitive(TaskData.TaskConfig.Schedule == EVisibilityTaskSchedule::Parallel) , bFinished(!bLaunchOnAddPrimitive) { MarkMasks = (uint8*)TaskData.Allocator.Malloc(NumMeshes + 31, 8); // some padding to simplify the high speed transpose FMemory::Memzero(MarkMasks, NumMeshes + 31); Packets.Reserve(InTaskData.TaskConfig.Relevance.NumEstimatedPackets); CreateRelevancePacket(); } void FComputeAndMarkRelevance::AddPrimitives(FPrimitiveIndexList&& PrimitiveIndexList) { if (PrimitiveIndexList.Num() == NumPrimitivesPerPacket) { // Create a one-off packet that will take all the primitives. FRelevancePacket* Packet = CreateRelevancePacket(); Packet->Input.Prims = MoveTemp(PrimitiveIndexList); if (bLaunchOnAddPrimitive) { Packet->LaunchComputeRelevanceTask(); } // Put the previous last packet that was in progress back in the last slot. Swap(Packets[Packets.Num() - 1], Packets[Packets.Num() - 2]); } else { for (int32 Index : PrimitiveIndexList) { AddPrimitive(Index); } } } void FComputeAndMarkRelevance::AddPrimitive(int32 Index) { FRelevancePacket* Packet = Packets.Last(); if (Packet->Input.AddPrim(Index); Packet->Input.IsFull()) { if (bLaunchOnAddPrimitive) { Packet->LaunchComputeRelevanceTask(); } CreateRelevancePacket(); } } void FComputeAndMarkRelevance::Finish(UE::Tasks::FTaskEvent& ComputeRelevanceTaskEvent) { check(!bFinished); bFinished = true; SCOPED_NAMED_EVENT(FinishRelevance, FColor::Magenta); Packets.Last()->LaunchComputeRelevanceTask(); for (FRelevancePacket* Packet : Packets) { if (Packet->bComputeRelevanceTaskLaunched) { ComputeRelevanceTaskEvent.AddPrerequisites(Packet->ComputeRelevanceTask); } } ComputeRelevanceTaskEvent.Trigger(); } void FComputeAndMarkRelevance::Finalize() { check(bFinished && !bFinalized); bFinalized = true; PrerequisitesTask.Wait(); if (!bLaunchOnAddPrimitive) { ParallelFor(Packets.Num(), [this](int32 Index) { FTaskTagScope Scope(ETaskTag::EParallelRenderingThread); FDynamicPrimitiveIndexList DynamicPrimitiveIndexList; FRelevancePacket* Packet = Packets[Index]; Packet->ComputeRelevance(DynamicPrimitiveIndexList); }); } SCOPED_NAMED_EVENT(FinalizeRelevance, FColor::Magenta); { QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_Finalize); for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++) { int32 NumVisibleCachedMeshDrawCommands = 0; int32 NumDynamicBuildRequests = 0; int32 NumDynamicBuildFlags = 0; for (auto Packet : Packets) { NumVisibleCachedMeshDrawCommands += Packet->DrawCommandPacket.VisibleCachedDrawCommands[PassIndex].Num(); NumDynamicBuildRequests += Packet->DrawCommandPacket.DynamicBuildRequests[PassIndex].Num(); NumDynamicBuildFlags += Packet->DrawCommandPacket.DynamicBuildFlags[PassIndex].Num(); } ViewCommands.MeshCommands[PassIndex].Reserve(NumVisibleCachedMeshDrawCommands); ViewCommands.DynamicMeshCommandBuildRequests[PassIndex].Reserve(NumDynamicBuildRequests); ViewCommands.DynamicMeshCommandBuildFlags[PassIndex].Reserve(NumDynamicBuildFlags); check(NumDynamicBuildRequests == NumDynamicBuildFlags); } View.DirtyIndirectLightingCacheBufferPrimitivesMutex.Lock(); for (auto Packet : Packets) { Packet->Finalize(); delete Packet; } View.DirtyIndirectLightingCacheBufferPrimitivesMutex.Unlock(); Packets.Empty(); // Finalize Nanite materials if (TaskData.bAddNaniteRelevance) { // This needs to complete before InitViews runs so that combined primitive/material relevance has been computed for Nanite Scene.WaitForCacheNaniteMaterialBinsTask(); FViewInfo& WriteView = View; { const FNaniteShadingPipelines& ShadingPipelines = Scene.NaniteShadingPipelines[ENaniteMeshPass::BasePass]; const FPrimitiveViewRelevance& CombinedRelevance = ShadingPipelines.CombinedRelevance; WriteView.ShadingModelMaskInView |= CombinedRelevance.ShadingModelMask; WriteView.bUsesGlobalDistanceField |= CombinedRelevance.bUsesGlobalDistanceField; WriteView.bUsesLightingChannels |= CombinedRelevance.bUsesLightingChannels; WriteView.bSceneHasSkyMaterial |= CombinedRelevance.bUsesSkyMaterial; WriteView.bHasDistortionPrimitives |= CombinedRelevance.bDistortion; WriteView.bHasCustomDepthPrimitives |= CombinedRelevance.bRenderCustomDepth; WriteView.bUsesCustomDepth |= (CombinedRelevance.CustomDepthStencilUsageMask & 1) > 0; WriteView.bUsesCustomStencil |= (CombinedRelevance.CustomDepthStencilUsageMask & (1 << 1)) > 0; WriteView.SubstrateViewData.MaxClosurePerPixel = FMath::Max(WriteView.SubstrateViewData.MaxClosurePerPixel, 8u - FMath::CountLeadingZeros8(CombinedRelevance.SubstrateClosureCountMask)); WriteView.SubstrateViewData.MaxBytesPerPixel = FMath::Max(WriteView.SubstrateViewData.MaxBytesPerPixel, CombinedRelevance.SubstrateUintPerPixel * 4u); WriteView.SubstrateViewData.UsesTileTypeMask |= CombinedRelevance.SubstrateTileTypeMask; WriteView.SubstrateViewData.bUsesAnisotropy |= CombinedRelevance.bUsesAnisotropy; } } } TRACE_COUNTER_SET(Scene_Visibility_Relevance_NumPrimitivesProcessed, TaskData.TaskConfig.Relevance.NumPrimitivesProcessed); QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_TransposeMeshBits); check(View.StaticMeshVisibilityMap.Num() == NumMeshes && View.StaticMeshFadeOutDitheredLODMap.Num() == NumMeshes && View.StaticMeshFadeInDitheredLODMap.Num() == NumMeshes ); uint32* RESTRICT StaticMeshVisibilityMap_Words = View.StaticMeshVisibilityMap.GetData(); uint32* RESTRICT StaticMeshFadeOutDitheredLODMap_Words = View.StaticMeshFadeOutDitheredLODMap.GetData(); uint32* RESTRICT StaticMeshFadeInDitheredLODMap_Words = View.StaticMeshFadeInDitheredLODMap.GetData(); const uint64* RESTRICT MarkMasks64 = (const uint64 * RESTRICT)MarkMasks; const uint8* RESTRICT MarkMasks8 = MarkMasks; for (uint32 BaseIndex = 0; BaseIndex < NumMeshes; BaseIndex += 32) { uint32 StaticMeshVisibilityMap_Word = 0; uint32 StaticMeshFadeOutDitheredLODMap_Word = 0; uint32 StaticMeshFadeInDitheredLODMap_Word = 0; uint32 Mask = 1; bool bAny = false; for (int32 QWordIndex = 0; QWordIndex < 4; QWordIndex++) { if (*MarkMasks64++) { for (int32 ByteIndex = 0; ByteIndex < 8; ByteIndex++, Mask <<= 1, MarkMasks8++) { uint8 MaskMask = *MarkMasks8; StaticMeshVisibilityMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshVisibilityMapMask) ? Mask : 0; StaticMeshFadeOutDitheredLODMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask) ? Mask : 0; StaticMeshFadeInDitheredLODMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask) ? Mask : 0; } bAny = true; } else { MarkMasks8 += 8; Mask <<= 8; } } if (bAny) { checkSlow(!*StaticMeshVisibilityMap_Words && !*StaticMeshFadeOutDitheredLODMap_Words && !*StaticMeshFadeInDitheredLODMap_Words); *StaticMeshVisibilityMap_Words = StaticMeshVisibilityMap_Word; *StaticMeshFadeOutDitheredLODMap_Words = StaticMeshFadeOutDitheredLODMap_Word; *StaticMeshFadeInDitheredLODMap_Words = StaticMeshFadeInDitheredLODMap_Word; } StaticMeshVisibilityMap_Words++; StaticMeshFadeOutDitheredLODMap_Words++; StaticMeshFadeInDitheredLODMap_Words++; } if (View.bIsSinglePassStereo) { check(View.StereoPass != EStereoscopicPass::eSSP_SECONDARY); // Secondary views should not calculate relevance if (const FViewInfo* SecondaryViewConst = View.GetInstancedView()) { FViewInfo* SecondaryView = const_cast(SecondaryViewConst); SecondaryView->PrimitiveVisibilityMap = View.PrimitiveVisibilityMap; SecondaryView->PrimitiveRayTracingVisibilityMap = View.PrimitiveRayTracingVisibilityMap; SecondaryView->PrimitiveDefinitelyUnoccludedMap = View.PrimitiveDefinitelyUnoccludedMap; SecondaryView->PrimitiveViewRelevanceMap = View.PrimitiveViewRelevanceMap; SecondaryView->StaticMeshVisibilityMap = View.StaticMeshVisibilityMap; SecondaryView->PrimitivesLODMask = View.PrimitivesLODMask; SecondaryView->NumVisibleDynamicPrimitives = View.NumVisibleDynamicPrimitives; SecondaryView->NumVisibleDynamicEditorPrimitives = View.NumVisibleDynamicEditorPrimitives; SecondaryView->ShadingModelMaskInView = View.ShadingModelMaskInView; SecondaryView->bUsesGlobalDistanceField = View.bUsesGlobalDistanceField; SecondaryView->bUsesLightingChannels = View.bUsesLightingChannels; SecondaryView->bTranslucentSurfaceLighting = View.bTranslucentSurfaceLighting; SecondaryView->bSceneHasSkyMaterial = View.bSceneHasSkyMaterial; SecondaryView->bHasSingleLayerWaterMaterial = View.bHasSingleLayerWaterMaterial; SecondaryView->bUsesSecondStageDepthPass = View.bUsesSecondStageDepthPass; SecondaryView->TranslucentPrimCount = View.TranslucentPrimCount; SecondaryView->bHasDistortionPrimitives = View.bHasDistortionPrimitives; SecondaryView->bHasCustomDepthPrimitives = View.bHasCustomDepthPrimitives; SecondaryView->CustomDepthStencilValues = View.CustomDepthStencilValues; SecondaryView->bUsesCustomDepth = View.bUsesCustomDepth; SecondaryView->bUsesCustomStencil = View.bUsesCustomStencil; SecondaryView->SubstrateViewData.MaxClosurePerPixel = View.SubstrateViewData.MaxClosurePerPixel; SecondaryView->SubstrateViewData.MaxBytesPerPixel = View.SubstrateViewData.MaxClosurePerPixel; SecondaryView->SubstrateViewData.UsesTileTypeMask = View.SubstrateViewData.UsesTileTypeMask; SecondaryView->SubstrateViewData.bUsesAnisotropy = View.SubstrateViewData.bUsesAnisotropy; } } } /////////////////////////////////////////////////////////////////////////////// static void ComputeDynamicMeshRelevance( EShadingPath ShadingPath, bool bAddLightmapDensityCommands, bool bIsTLVUsingVoxelMarking, const FPrimitiveViewRelevance& ViewRelevance, const FMeshBatchAndRelevance& MeshBatch, FViewInfo& View, FMeshPassMask& PassMask, FPrimitiveSceneInfo* PrimitiveSceneInfo, const FPrimitiveBounds& Bounds) { const int32 NumElements = MeshBatch.Mesh->Elements.Num(); const bool bIsTranslucentClippedDepthEnabled = NeedTSRThinGeometryDetectionPass(View); if (ViewRelevance.bDrawRelevance && (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth || ViewRelevance.bRenderInDepthPass)) { if (ViewRelevance.bRenderInSecondStageDepthPass && ShadingPath != EShadingPath::Mobile) { PassMask.Set(EMeshPass::SecondStageDepthPass); View.NumVisibleDynamicMeshElements[EMeshPass::SecondStageDepthPass] += NumElements; } else { PassMask.Set(EMeshPass::DepthPass); View.NumVisibleDynamicMeshElements[EMeshPass::DepthPass] += NumElements; } if (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth) { PassMask.Set(EMeshPass::BasePass); View.NumVisibleDynamicMeshElements[EMeshPass::BasePass] += NumElements; if (ViewRelevance.bUsesSkyMaterial) { PassMask.Set(EMeshPass::SkyPass); View.NumVisibleDynamicMeshElements[EMeshPass::SkyPass] += NumElements; } if (ViewRelevance.bUsesAnisotropy) { PassMask.Set(EMeshPass::AnisotropyPass); View.NumVisibleDynamicMeshElements[EMeshPass::AnisotropyPass] += NumElements; } if (ShadingPath == EShadingPath::Mobile) { PassMask.Set(EMeshPass::MobileBasePassCSM); View.NumVisibleDynamicMeshElements[EMeshPass::MobileBasePassCSM] += NumElements; } if (ViewRelevance.bRenderCustomDepth) { PassMask.Set(EMeshPass::CustomDepth); View.NumVisibleDynamicMeshElements[EMeshPass::CustomDepth] += NumElements; } if (bAddLightmapDensityCommands) { PassMask.Set(EMeshPass::LightmapDensity); View.NumVisibleDynamicMeshElements[EMeshPass::LightmapDensity] += NumElements; } #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) else if (View.Family->UseDebugViewPS()) { PassMask.Set(EMeshPass::DebugViewMode); View.NumVisibleDynamicMeshElements[EMeshPass::DebugViewMode] += NumElements; } #endif #if WITH_EDITOR if (View.bAllowTranslucentPrimitivesInHitProxy) { PassMask.Set(EMeshPass::HitProxy); View.NumVisibleDynamicMeshElements[EMeshPass::HitProxy] += NumElements; } else { PassMask.Set(EMeshPass::HitProxyOpaqueOnly); View.NumVisibleDynamicMeshElements[EMeshPass::HitProxyOpaqueOnly] += NumElements; } #endif if (ViewRelevance.bVelocityRelevance) { PassMask.Set(EMeshPass::Velocity); View.NumVisibleDynamicMeshElements[EMeshPass::Velocity] += NumElements; if (MeshBatch.PrimitiveSceneProxy->AnyMaterialUsesMotionVectorWorldOffset()) { View.bUsesMotionVectorWorldOffset = true; } } if (ViewRelevance.bOutputsTranslucentVelocity) { PassMask.Set(EMeshPass::TranslucentVelocity); View.NumVisibleDynamicMeshElements[EMeshPass::TranslucentVelocity] += NumElements; const bool bAnyMaterialUsesTemporalResponsiveness = MeshBatch.PrimitiveSceneProxy->AnyMaterialUsesTemporalResponsiveness(); if (ViewRelevance.bNormalTranslucency && bIsTranslucentClippedDepthEnabled && bAnyMaterialUsesTemporalResponsiveness) { PassMask.Set(EMeshPass::TranslucentVelocityClippedDepth); View.NumVisibleDynamicMeshElements[EMeshPass::TranslucentVelocityClippedDepth] += NumElements; } } if (ViewRelevance.bUsesSingleLayerWaterMaterial) { PassMask.Set(EMeshPass::SingleLayerWaterPass); View.NumVisibleDynamicMeshElements[EMeshPass::SingleLayerWaterPass] += NumElements; PassMask.Set(EMeshPass::SingleLayerWaterDepthPrepass); View.NumVisibleDynamicMeshElements[EMeshPass::SingleLayerWaterDepthPrepass] += NumElements; } } } if (ViewRelevance.HasTranslucency() && !ViewRelevance.bEditorPrimitiveRelevance && ViewRelevance.bRenderInMainPass) { if (View.Family->AllowTranslucencyAfterDOF()) { if ((ViewRelevance.bNormalTranslucency || (View.AutoBeforeDOFTranslucencyBoundary > 0.0f && ViewRelevance.bSeparateTranslucency))) { PassMask.Set(EMeshPass::TranslucencyStandard); View.NumVisibleDynamicMeshElements[EMeshPass::TranslucencyStandard] += NumElements; } if ((ViewRelevance.bNormalTranslucency || (View.AutoBeforeDOFTranslucencyBoundary > 0.0f && ViewRelevance.bSeparateTranslucency)) && ViewRelevance.bTranslucencyModulate && View.Family->AllowStandardTranslucencySeparated()) { PassMask.Set(EMeshPass::TranslucencyStandardModulate); View.NumVisibleDynamicMeshElements[EMeshPass::TranslucencyStandardModulate] += NumElements; } if (ViewRelevance.bSeparateTranslucency) { PassMask.Set(EMeshPass::TranslucencyAfterDOF); View.NumVisibleDynamicMeshElements[EMeshPass::TranslucencyAfterDOF] += NumElements; } if (ViewRelevance.bSeparateTranslucency && ViewRelevance.bTranslucencyModulate) { PassMask.Set(EMeshPass::TranslucencyAfterDOFModulate); View.NumVisibleDynamicMeshElements[EMeshPass::TranslucencyAfterDOFModulate] += NumElements; } if (ViewRelevance.bPostMotionBlurTranslucency) { PassMask.Set(EMeshPass::TranslucencyAfterMotionBlur); View.NumVisibleDynamicMeshElements[EMeshPass::TranslucencyAfterMotionBlur] += NumElements; } } else { PassMask.Set(EMeshPass::TranslucencyAll); View.NumVisibleDynamicMeshElements[EMeshPass::TranslucencyAll] += NumElements; } { PassMask.Set(EMeshPass::TranslucencyHoldout); View.NumVisibleDynamicMeshElements[EMeshPass::TranslucencyHoldout] += NumElements; } if (ViewRelevance.bTranslucentSurfaceLighting || bIsTLVUsingVoxelMarking) { PassMask.Set(EMeshPass::LumenTranslucencyRadianceCacheMark); View.NumVisibleDynamicMeshElements[EMeshPass::LumenTranslucencyRadianceCacheMark] += NumElements; } if (ViewRelevance.bTranslucentSurfaceLighting) { PassMask.Set(EMeshPass::LumenFrontLayerTranslucencyGBuffer); View.NumVisibleDynamicMeshElements[EMeshPass::LumenFrontLayerTranslucencyGBuffer] += NumElements; } if (ViewRelevance.bDistortion) { PassMask.Set(EMeshPass::Distortion); View.NumVisibleDynamicMeshElements[EMeshPass::Distortion] += NumElements; } } #if WITH_EDITOR if (ViewRelevance.bDrawRelevance) { PassMask.Set(EMeshPass::EditorSelection); View.NumVisibleDynamicMeshElements[EMeshPass::EditorSelection] += NumElements; PassMask.Set(EMeshPass::EditorLevelInstance); View.NumVisibleDynamicMeshElements[EMeshPass::EditorLevelInstance] += NumElements; } // Hair strands are not rendered into the base pass (bRenderInMainPass=0) and so this // adds a special pass for allowing hair strands to be selectable. if (ViewRelevance.bHairStrands) { const EMeshPass::Type MeshPassType = View.bAllowTranslucentPrimitivesInHitProxy ? EMeshPass::HitProxy : EMeshPass::HitProxyOpaqueOnly; PassMask.Set(MeshPassType); View.NumVisibleDynamicMeshElements[MeshPassType] += NumElements; } #endif if (ViewRelevance.bHasVolumeMaterialDomain) { if (ShouldRenderMeshBatchWithHeterogeneousVolumes(MeshBatch.Mesh, MeshBatch.PrimitiveSceneProxy, View.FeatureLevel)) { View.HeterogeneousVolumesMeshBatches.AddUninitialized(1); FVolumetricMeshBatch& BatchAndProxy = View.HeterogeneousVolumesMeshBatches.Last(); BatchAndProxy.Mesh = MeshBatch.Mesh; BatchAndProxy.Proxy = MeshBatch.PrimitiveSceneProxy; } else { View.VolumetricMeshBatches.AddUninitialized(1); FVolumetricMeshBatch& BatchAndProxy = View.VolumetricMeshBatches.Last(); BatchAndProxy.Mesh = MeshBatch.Mesh; BatchAndProxy.Proxy = MeshBatch.PrimitiveSceneProxy; } } if (ViewRelevance.bUsesSkyMaterial) { View.SkyMeshBatches.AddUninitialized(1); FSkyMeshBatch& BatchAndProxy = View.SkyMeshBatches.Last(); BatchAndProxy.Mesh = MeshBatch.Mesh; BatchAndProxy.Proxy = MeshBatch.PrimitiveSceneProxy; BatchAndProxy.bVisibleInMainPass = ViewRelevance.bRenderInMainPass; BatchAndProxy.bVisibleInRealTimeSkyCapture = PrimitiveSceneInfo->bVisibleInRealTimeSkyCapture; } if (ViewRelevance.HasTranslucency() && PrimitiveSceneInfo->Proxy->SupportsSortedTriangles()) { View.SortedTrianglesMeshBatches.AddUninitialized(1); FSortedTrianglesMeshBatch& BatchAndProxy = View.SortedTrianglesMeshBatches.Last(); BatchAndProxy.Mesh = MeshBatch.Mesh; BatchAndProxy.Proxy = MeshBatch.PrimitiveSceneProxy; } if (ViewRelevance.bRenderInMainPass && ViewRelevance.bDecal) { // DecalRenderTargetModeMask could be cached in FMeshBatchAndRelevance as well const FMaterial& Material = MeshBatch.Mesh->MaterialRenderProxy->GetIncompleteMaterialWithFallback(View.FeatureLevel); uint8 DecalRenderTargetModeMask = DecalRendering::GetDecalRenderTargetModeMask(Material, View.FeatureLevel); for (uint8 DecalRenderTargetMode = 0; DecalRenderTargetMode < (uint8)EDecalRenderTargetMode::Num; ++DecalRenderTargetMode) { if (DecalRendering::IsCompatibleWithRenderTargetMode(DecalRenderTargetModeMask, (EDecalRenderTargetMode)DecalRenderTargetMode)) { EMeshPass::Type DecalMeshPassType = DecalRendering::GetMeshPassType((EDecalRenderTargetMode)DecalRenderTargetMode); PassMask.Set(DecalMeshPassType); View.NumVisibleDynamicMeshElements[DecalMeshPassType] += NumElements; } } } const bool bIsHairStrandsCompatible = ViewRelevance.bHairStrands && IsHairStrandsEnabled(EHairStrandsShaderType::All, View.GetShaderPlatform()); if (bIsHairStrandsCompatible) { // Disable bCheckLengthScaleInitialize when running hit proxy as LengthScale is not initialized const bool bCheckLengthScale = !View.Family->EngineShowFlags.HitProxies; if (HairStrands::IsHairStrandsVF(MeshBatch.Mesh) && HairStrands::IsHairVisible(MeshBatch, bCheckLengthScale)) { View.HairStrandsMeshElements.AddUninitialized(1); FMeshBatchAndRelevance& BatchAndProxy = View.HairStrandsMeshElements.Last(); BatchAndProxy = MeshBatch; } if (HairStrands::IsHairCardsVF(MeshBatch.Mesh) && ViewRelevance.bRenderInMainPass) { View.HairCardsMeshElements.AddUninitialized(1); FMeshBatchAndRelevance& BatchAndProxy = View.HairCardsMeshElements.Last(); BatchAndProxy = MeshBatch; } } } /////////////////////////////////////////////////////////////////////////////// FGPUOcclusionPacket::FGPUOcclusionPacket(FVisibilityViewPacket& InViewPacket, const FGPUOcclusionState& InOcclusionState) : ViewPacket(InViewPacket) , View(ViewPacket.View) , ViewState(*ViewPacket.ViewState) , ViewElementPDI(ViewPacket.ViewElementPDI) , HZBOcclusionTests(ViewState.HZBOcclusionTests) , OcclusionFeedback(ViewState.OcclusionFeedback) , PrimitiveOcclusionHistorySet(ViewState.Occlusion.PrimitiveOcclusionHistorySet) , Scene(ViewPacket.Scene) , OcclusionState(InOcclusionState) , ViewOrigin(View.ViewMatrices.GetViewOrigin()) , OcclusionFrameCounter(ViewState.OcclusionFrameCounter) , PrimitiveProbablyVisibleTime(GEngine->PrimitiveProbablyVisibleTime) , CurrentRealTime(View.Family->Time.GetRealTimeSeconds()) , NeverOcclusionTestDistanceSquared(GNeverOcclusionTestDistance* GNeverOcclusionTestDistance) , bUseOcclusionFeedback(View.FeatureLevel == ERHIFeatureLevel::ES3_1 && OcclusionFeedback.IsInitialized()) , bNewlyConsideredBBoxExpandActive( GExpandNewlyOcclusionTestedBBoxesAmount > 0.0f && GExpandNewlyOcclusionTestedBBoxesScreenSpace > 0.0f && GFramesToExpandNewlyOcclusionTestedBBoxes > 0 && GFramesNotOcclusionTestedToExpandBBoxes > 0) {} template bool FGPUOcclusionPacket::OcclusionCullPrimitive(VisitorType& Visitor, FOcclusionCullResult& Result, int32 Index) { const uint8 OcclusionFlags = Scene.PrimitiveOcclusionFlags[Index]; int32 NumSubQueries = 1; bool bSubQueries = false; const TArray* SubBounds = nullptr; int32 SubIsOccludedStart = 0; const auto SetAtomicBit = [] (FSceneBitArray& Array, int32 Index, bool Value) { if constexpr (bIsParallel) { Array[Index].AtomicSet(Value); } else { Array[Index] = Value; } }; if (OcclusionFlags & EOcclusionFlags::IsForceHidden) { // Primitive is not visible and does not need to be occlusion tested return false; } if ((OcclusionFlags & EOcclusionFlags::HasSubprimitiveQueries) && OcclusionState.bAllowSubQueries) { FPrimitiveSceneProxy* Proxy = Scene.PrimitiveSceneProxies[Index]; SubBounds = Proxy->GetOcclusionQueries(&View); NumSubQueries = SubBounds->Num(); bSubQueries = true; if (!NumSubQueries) { SetAtomicBit(View.PrimitiveVisibilityMap, Index, false); return false; } if (!SubIsOccluded || SubIsOccluded->Num() + NumSubQueries > SubIsOccluded->Max()) { SubIsOccluded = View.Allocator.Create>(); SubIsOccluded->Reserve(FMath::Max(NumSubQueries, SubIsOccludedPageSize)); } SubIsOccludedStart = SubIsOccluded->Num(); } bool bIsVisible = true; bool bAllSubOcclusionStateIsDefinite = true; bool bAllSubOccluded = true; FPrimitiveComponentId PrimitiveId = Scene.PrimitiveComponentIds[Index]; // Presolve for screen space bbox expansion FVector4 ExpansionWorldSpaceNewlyConsidered; FVector4 ExpansionWorldSpaceNotNewlyConsidered; FVector4 ViewProjectionHomogeneousVector; if (GExpandAllTestedBBoxesScreenSpace > 0.0f || GExpandNewlyOcclusionTestedBBoxesScreenSpace > 0.0f) { const FMatrix& InvViewProjectionMatrix = View.ViewMatrices.GetInvViewProjectionMatrix(); float ExtentNewly = GExpandAllTestedBBoxesScreenSpace + GExpandNewlyOcclusionTestedBBoxesScreenSpace; FVector4 ExpansionScreenSpaceNewlyConsidered = FVector4(ExtentNewly / View.ViewRect.Width(), ExtentNewly / View.ViewRect.Height(), 0.0f, 0.0f); ExpansionWorldSpaceNewlyConsidered = InvViewProjectionMatrix.TransformFVector4(ExpansionScreenSpaceNewlyConsidered); float ExtentNotNewly = GExpandAllTestedBBoxesScreenSpace; FVector4 ExpansionScreenSpaceNotNewlyConsidered = FVector4(ExtentNotNewly / View.ViewRect.Width(), ExtentNotNewly / View.ViewRect.Height(), 0.0f, 0.0f); ExpansionWorldSpaceNotNewlyConsidered = InvViewProjectionMatrix.TransformFVector4(ExpansionScreenSpaceNotNewlyConsidered); const FMatrix& ViewProjectionMatrix = View.ViewMatrices.GetViewProjectionMatrix(); ViewProjectionHomogeneousVector = FVector4(ViewProjectionMatrix.M[0][3], ViewProjectionMatrix.M[1][3], ViewProjectionMatrix.M[2][3], ViewProjectionMatrix.M[3][3]); } for (int32 SubQuery = 0; SubQuery < NumSubQueries; SubQuery++) { FPrimitiveOcclusionHistory* PrimitiveOcclusionHistory = PrimitiveOcclusionHistorySet.Find(FPrimitiveOcclusionHistoryKey(PrimitiveId, SubQuery)); bool bIsOccluded = false; bool bOcclusionStateIsDefinite = false; if (!PrimitiveOcclusionHistory) { PrimitiveOcclusionHistory = Visitor.AddOcclusionHistory(FPrimitiveOcclusionHistory(PrimitiveId, SubQuery)); } else { if (View.bIgnoreExistingQueries) { // If the view is ignoring occlusion queries, the primitive is definitely unoccluded. bOcclusionStateIsDefinite = View.bDisableQuerySubmissions; } else { if (bUseOcclusionFeedback) { bIsOccluded = OcclusionFeedback.IsOccluded(FPrimitiveOcclusionHistoryKey(PrimitiveId, SubQuery)); bOcclusionStateIsDefinite = true; } else if (OcclusionState.bHZBOcclusion) { if (HZBOcclusionTests.IsValidFrame(PrimitiveOcclusionHistory->LastTestFrameNumber)) { bIsOccluded = !HZBOcclusionTests.IsVisible(PrimitiveOcclusionHistory->HZBTestIndex); bOcclusionStateIsDefinite = true; } } else { // Read the occlusion query results. uint64 NumSamples = 0; bool bGrouped = false; FRHIRenderQuery* PastQuery = PrimitiveOcclusionHistory->GetQueryForReading(OcclusionFrameCounter, OcclusionState.NumBufferedFrames, OcclusionState.ReadBackLagTolerance, bGrouped); if (PastQuery) { if (RHIGetRenderQueryResult(PastQuery, NumSamples, true)) { // we render occlusion without MSAA uint32 NumPixels = (uint32)NumSamples; // The primitive is occluded if none of its bounding box's pixels were visible in the previous frame's occlusion query. bIsOccluded = (NumPixels == 0); if (!bIsOccluded) { checkSlow(View.OneOverNumPossiblePixels > 0.0f); PrimitiveOcclusionHistory->LastPixelsPercentage = NumPixels * View.OneOverNumPossiblePixels; } else { PrimitiveOcclusionHistory->LastPixelsPercentage = 0.0f; } // Flag the primitive's occlusion state as definite if it wasn't grouped. bOcclusionStateIsDefinite = !bGrouped; } else { // If the occlusion query failed, treat the primitive as visible. // already set bIsOccluded = false; } Result.NumTestedQueries++; } else { if (OcclusionState.NumBufferedFrames > 1 || GRHIMaximumInFlightQueries < MAX_int32) { // If there's no occlusion query for the primitive, assume it is whatever it was last frame bIsOccluded = PrimitiveOcclusionHistory->WasOccludedLastFrame; bOcclusionStateIsDefinite = PrimitiveOcclusionHistory->OcclusionStateWasDefiniteLastFrame; } else { // If there's no occlusion query for the primitive, set it's visibility state to whether it has been unoccluded recently. bIsOccluded = (PrimitiveOcclusionHistory->LastProvenVisibleTime + GEngine->PrimitiveProbablyVisibleTime < CurrentRealTime); // the state was definite last frame, otherwise we would have ran a query bOcclusionStateIsDefinite = true; } if (bIsOccluded) { PrimitiveOcclusionHistory->LastPixelsPercentage = 0.0f; } else { PrimitiveOcclusionHistory->LastPixelsPercentage = GEngine->MaxOcclusionPixelsFraction; } } } if (GVisualizeOccludedPrimitives == 1 && bIsOccluded) { const FBoxSphereBounds& Bounds = bSubQueries ? (*SubBounds)[SubQuery] : Scene.PrimitiveOcclusionBounds[Index]; Visitor.AddVisualizeQuery(Bounds.GetBox(), FColor(50, 255, 50)); } } } if (OcclusionState.bSubmitQueries) { bool bSkipNewlyConsidered = false; if (bNewlyConsideredBBoxExpandActive) { if (!PrimitiveOcclusionHistory->BecameEligibleForQueryCooldown && OcclusionFrameCounter - PrimitiveOcclusionHistory->LastConsideredFrameNumber > uint32(GFramesNotOcclusionTestedToExpandBBoxes)) { PrimitiveOcclusionHistory->BecameEligibleForQueryCooldown = GFramesToExpandNewlyOcclusionTestedBBoxes; } bSkipNewlyConsidered = !!PrimitiveOcclusionHistory->BecameEligibleForQueryCooldown; if (bSkipNewlyConsidered) { PrimitiveOcclusionHistory->BecameEligibleForQueryCooldown--; } } const FBoxSphereBounds PrimitiveOcclusionBounds = (bSubQueries ? (*SubBounds)[SubQuery] : Scene.PrimitiveOcclusionBounds[Index]); // Print unexpanded bounding boxes in green if not already visualized by GVisualizeOccludedPrimitives if (GVisualizePrimitiveBBoxes == 2 && !(GVisualizeOccludedPrimitives == 1 && bIsOccluded)) { Visitor.AddVisualizeQuery(PrimitiveOcclusionBounds.GetBox(), FColor(0, 155, 0)); } // scale occlusion bounds by GExpand*TestedBBoxesAmount in world space FBoxSphereBounds WorldSpaceExpandedOcclusionBounds = PrimitiveOcclusionBounds; if (GExpandAllTestedBBoxesAmount > 0.0f || GExpandNewlyOcclusionTestedBBoxesAmount > 0.0f) { WorldSpaceExpandedOcclusionBounds = WorldSpaceExpandedOcclusionBounds.ExpandBy(GExpandAllTestedBBoxesAmount + (bSkipNewlyConsidered ? GExpandNewlyOcclusionTestedBBoxesAmount : 0.0)); } // scale occlusion bounds by GExpand*TestedBBoxesScreenSpace in screen space FBoxSphereBounds FinalOcclusionBounds = WorldSpaceExpandedOcclusionBounds; if (GExpandAllTestedBBoxesScreenSpace > 0.0f || GExpandNewlyOcclusionTestedBBoxesScreenSpace > 0.0f) { FVector ExpansionWorldSpace = (bSkipNewlyConsidered) ? ExpansionWorldSpaceNewlyConsidered : ExpansionWorldSpaceNotNewlyConsidered; const float BoundsScreenSpace_W = FVector::DotProduct(FinalOcclusionBounds.Origin, ViewProjectionHomogeneousVector) + ViewProjectionHomogeneousVector.W; ExpansionWorldSpace *= BoundsScreenSpace_W; ExpansionWorldSpace = FVector(abs(ExpansionWorldSpace.X), abs(ExpansionWorldSpace.Y), abs(ExpansionWorldSpace.Z)); FinalOcclusionBounds.BoxExtent = FinalOcclusionBounds.BoxExtent + ExpansionWorldSpace; FinalOcclusionBounds.SphereRadius = FinalOcclusionBounds.BoxExtent.Length(); } const FBoxSphereBounds OcclusionBounds = FinalOcclusionBounds; if (GVisualizeOccludedPrimitives == 2 && bIsOccluded) { Visitor.AddVisualizeQuery(OcclusionBounds.GetBox(), FColor(50, 255, 50)); } else if (GVisualizePrimitiveBBoxes > 0) { Visitor.AddVisualizeQuery(OcclusionBounds.GetBox(), FColor(155, 0, 0)); } bool bAllowBoundsTest; if (FVector::DistSquared(ViewOrigin, OcclusionBounds.Origin) < NeverOcclusionTestDistanceSquared) { bAllowBoundsTest = false; } else if (View.bHasNearClippingPlane) { bAllowBoundsTest = View.NearClippingPlane.PlaneDot(OcclusionBounds.Origin) < -(FVector::BoxPushOut(View.NearClippingPlane, OcclusionBounds.BoxExtent)); } else if (!View.IsPerspectiveProjection()) { // Transform parallel near plane static_assert((int32)ERHIZBuffer::IsInverted != 0, "Check equation for culling!"); bAllowBoundsTest = View.WorldToScreen(OcclusionBounds.Origin).Z - View.ViewMatrices.GetProjectionMatrix().M[2][2] * OcclusionBounds.SphereRadius < 1; } else { bAllowBoundsTest = OcclusionBounds.SphereRadius < HALF_WORLD_MAX; } if (bAllowBoundsTest) { PrimitiveOcclusionHistory->LastTestFrameNumber = OcclusionFrameCounter; if (bUseOcclusionFeedback) { const FVector BoundOrigin = OcclusionBounds.Origin + View.ViewMatrices.GetPreViewTranslation(); const FVector BoundExtent = OcclusionBounds.BoxExtent; Visitor.AddOcclusionFeedback(FOcclusionFeedbackEntry(FPrimitiveOcclusionHistoryKey(PrimitiveId, SubQuery), BoundOrigin, BoundExtent)); } else if (OcclusionState.bHZBOcclusion) { Visitor.AddHZBBounds(FHZBBound(PrimitiveOcclusionHistory, OcclusionBounds.Origin, OcclusionBounds.BoxExtent)); } else { // decide if a query should be run this frame bool bRunQuery, bGroupedQuery; if (!bSubQueries && // sub queries are never grouped, we assume the custom code knows what it is doing and will group internally if it wants (OcclusionFlags & EOcclusionFlags::AllowApproximateOcclusion)) { if (bIsOccluded) { // Primitives that were occluded the previous frame use grouped queries. bGroupedQuery = true; bRunQuery = true; } else if (bOcclusionStateIsDefinite) { bGroupedQuery = false; float Rnd = GOcclusionRandomStream.GetFraction(); if (GRHISupportsExactOcclusionQueries) { float FractionMultiplier = FMath::Max(PrimitiveOcclusionHistory->LastPixelsPercentage / GEngine->MaxOcclusionPixelsFraction, 1.0f); bRunQuery = (FractionMultiplier * Rnd) < GEngine->MaxOcclusionPixelsFraction; } else { bRunQuery = CurrentRealTime - PrimitiveOcclusionHistory->LastProvenVisibleTime > PrimitiveProbablyVisibleTime * (0.5f * 0.25f * Rnd); } } else { bGroupedQuery = false; bRunQuery = true; } } else { // Primitives that need precise occlusion results use individual queries. bGroupedQuery = false; bRunQuery = true; } if (bRunQuery) { const FVector BoundOrigin = OcclusionBounds.Origin + View.ViewMatrices.GetPreViewTranslation(); const FVector BoundExtent = OcclusionBounds.BoxExtent; if (GRHIMaximumInFlightQueries < MAX_int32 && !bGroupedQuery) { Visitor.AddThrottledOcclusionQuery(FThrottledOcclusionQuery(FPrimitiveOcclusionHistoryKey(PrimitiveId, SubQuery), BoundOrigin, BoundExtent, PrimitiveOcclusionHistory->LastQuerySubmitFrame())); } else { Visitor.AddOcclusionQuery(FOcclusionQuery(PrimitiveOcclusionHistory, BoundOrigin, BoundExtent, bGroupedQuery)); } } } } else { // If the primitive's bounding box intersects the near clipping plane, treat it as definitely unoccluded. bIsOccluded = false; bOcclusionStateIsDefinite = true; } } // Set the primitive's considered time to keep its occlusion history from being trimmed. PrimitiveOcclusionHistory->LastConsideredTime = CurrentRealTime; if (!bIsOccluded && bOcclusionStateIsDefinite) { PrimitiveOcclusionHistory->LastProvenVisibleTime = CurrentRealTime; } PrimitiveOcclusionHistory->LastConsideredFrameNumber = OcclusionFrameCounter; PrimitiveOcclusionHistory->WasOccludedLastFrame = bIsOccluded; PrimitiveOcclusionHistory->OcclusionStateWasDefiniteLastFrame = bOcclusionStateIsDefinite; if (bSubQueries) { SubIsOccluded->Add(bIsOccluded); if (!bIsOccluded) { bAllSubOccluded = false; } if (bIsOccluded || !bOcclusionStateIsDefinite) { bAllSubOcclusionStateIsDefinite = false; } } else { if (bIsOccluded) { SetAtomicBit(View.PrimitiveVisibilityMap, Index, false); bIsVisible = false; Result.NumCulledPrimitives++; } else if (bOcclusionStateIsDefinite) { SetAtomicBit(View.PrimitiveDefinitelyUnoccludedMap, Index, true); } } } if (bSubQueries) { FPrimitiveSceneProxy* Proxy = Scene.PrimitiveSceneProxies[Index]; Proxy->AcceptOcclusionResults(&View, SubIsOccluded, SubIsOccludedStart, SubIsOccluded->Num() - SubIsOccludedStart); if (bAllSubOccluded) { SetAtomicBit(View.PrimitiveVisibilityMap, Index, false); bIsVisible = false; Result.NumCulledPrimitives++; } else if (bAllSubOcclusionStateIsDefinite) { SetAtomicBit(View.PrimitiveDefinitelyUnoccludedMap, Index, true); } } return bIsVisible; } void FGPUOcclusionPacket::FProcessVisitor::AddOcclusionQuery(const FOcclusionQuery& Query) { FRHIRenderQuery* RenderQuery = nullptr; if (Query.bGroupedQuery) { RenderQuery = Packet.View.GroupedOcclusionQueries.BatchPrimitive(Query.Bounds.Origin, Query.Bounds.Extent, DynamicVertexBuffer); } else { RenderQuery = Packet.View.IndividualOcclusionQueries.BatchPrimitive(Query.Bounds.Origin, Query.Bounds.Extent, DynamicVertexBuffer); } const uint32 QueryIndex = FOcclusionQueryHelpers::GetQueryIssueIndex(Packet.ViewState.OcclusionFrameCounter, Packet.OcclusionState.NumBufferedFrames); Packet.ViewState.Occlusion.LastOcclusionQueryArray[QueryIndex] = RenderQuery; Packet.ViewState.Occlusion.NumRequestedQueries++; Query.PrimitiveOcclusionHistory->SetCurrentQuery( Packet.ViewState.OcclusionFrameCounter, RenderQuery, Packet.OcclusionState.NumBufferedFrames, Query.bGroupedQuery, Packet.OcclusionState.bUseRoundRobinOcclusion ); } void FGPUOcclusionPacket::FProcessVisitor::SubmitThrottledOcclusionQueries() { if (ThrottledOcclusionQueries.IsEmpty()) { return; } TArray SortedQueries; SortedQueries.Reserve(ThrottledOcclusionQueries.Num()); for (FThrottledOcclusionQuery& ThrottledOcclusionQuery : ThrottledOcclusionQueries) { SortedQueries.Emplace(&ThrottledOcclusionQuery); } const int32 NumRequestedThrottledQueries = SortedQueries.Num(); const int32 NumUsedQueries = Packet.View.GroupedOcclusionQueries.GetNumBatchOcclusionQueries(); const int32 ThrottleThreshold = GRHIMaximumInFlightQueries / FMath::Min(Packet.OcclusionState.NumBufferedFrames, 2); // extra RHIT frame does not count int32 NumThrottledQueries = NumRequestedThrottledQueries; if (NumUsedQueries + NumRequestedThrottledQueries > ThrottleThreshold) { // We need to make progress, even if it means stalling and waiting for the GPU. At a minimum, we will do 10%. NumThrottledQueries = (NumRequestedThrottledQueries + 9) / 10; if (NumUsedQueries + NumThrottledQueries < ThrottleThreshold) { // We can do more than the minimum. NumThrottledQueries = ThrottleThreshold - NumUsedQueries; } } if (NumThrottledQueries < NumRequestedThrottledQueries) { SortedQueries.Sort([](const FThrottledOcclusionQuery& A, const FThrottledOcclusionQuery& B) { return A.LastQuerySubmitFrame < B.LastQuerySubmitFrame; }); } for (int32 Index = 0; Index < NumThrottledQueries; ++Index) { FThrottledOcclusionQuery* Query = SortedQueries[Index]; FPrimitiveOcclusionHistory* PrimitiveOcclusionHistory = PrimitiveOcclusionHistorySet.Find(Query->PrimitiveOcclusionHistoryKey); FRHIRenderQuery* RenderQuery = Packet.View.IndividualOcclusionQueries.BatchPrimitive(Query->Bounds.Origin, Query->Bounds.Extent, DynamicVertexBuffer); const uint32 QueryIndex = FOcclusionQueryHelpers::GetQueryIssueIndex(Packet.ViewState.OcclusionFrameCounter, Packet.OcclusionState.NumBufferedFrames); Packet.ViewState.Occlusion.LastOcclusionQueryArray[QueryIndex] = RenderQuery; Packet.ViewState.Occlusion.NumRequestedQueries++; PrimitiveOcclusionHistory->SetCurrentQuery( Packet.ViewState.OcclusionFrameCounter, RenderQuery, Packet.OcclusionState.NumBufferedFrames, false, Packet.OcclusionState.bUseRoundRobinOcclusion ); } } /////////////////////////////////////////////////////////////////////////////// FGPUOcclusion::FGPUOcclusion(FVisibilityViewPacket& InViewPacket) : ViewPacket(InViewPacket) , Scene(ViewPacket.Scene) , View(ViewPacket.View) , ViewState(*ViewPacket.ViewState) { // In single pass stereo we only submit queries from the primary view. State.bSubmitQueries = !View.bDisableQuerySubmissions && !(View.bIsSinglePassStereo && View.StereoPass == EStereoscopicPass::eSSP_SECONDARY); #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) State.bSubmitQueries &= !ViewState.bIsFrozen; #endif // Perform round-robin occlusion queries const bool bUseRoundRobinOcclusion = ViewState.IsRoundRobinEnabled() && !View.bIsSinglePassStereo && // No round robin on single-pass stereo !View.bIsSceneCapture && // We only round-robin on the main renderer (not scene captures) IStereoRendering::IsStereoEyeView(View); // Only relevant to stereo views if (bUseRoundRobinOcclusion && !View.bIgnoreExistingQueries) // We do not alternate occlusion queries when we want to refresh the occlusion history { // For even frames, prevent left eye from occlusion querying // For odd frames, prevent right eye from occlusion querying const bool FrameParity = ((ViewState.PrevFrameNumber & 0x01) == 1); State.bSubmitQueries &= (FrameParity && IStereoRendering::IsAPrimaryView(View)) || (!FrameParity && IStereoRendering::IsASecondaryView(View)); } // Disable HZB on OpenGL platforms to avoid rendering artifacts // It can be forced on by setting HZBOcclusion to 2 State.bHZBOcclusion = !IsOpenGLPlatform(View.GetShaderPlatform()); State.bHZBOcclusion &= FDataDrivenShaderPlatformInfo::GetSupportsHZBOcclusion(View.GetShaderPlatform()); State.bHZBOcclusion &= GHZBOcclusion != 0; State.bHZBOcclusion |= GHZBOcclusion == 2; State.bUseRoundRobinOcclusion = bUseRoundRobinOcclusion; State.NumBufferedFrames = FOcclusionQueryHelpers::GetNumBufferedFrames(Scene.GetFeatureLevel()); State.ReadBackLagTolerance = State.NumBufferedFrames; State.bAllowSubQueries = GAllowSubPrimitiveQueries && !View.bDisableQuerySubmissions; if (State.bUseRoundRobinOcclusion) { // Round-robin occlusion culling involves reading frames that could be twice as stale as without round-robin State.ReadBackLagTolerance = State.NumBufferedFrames * 2; } } void FGPUOcclusion::Map(FRHICommandListImmediate& RHICmdListImmediate) { SCOPED_NAMED_EVENT(MapOcclusionResults, FColor::Magenta); if (View.FeatureLevel == ERHIFeatureLevel::ES3_1 && ViewState.OcclusionFeedback.IsInitialized()) { QUICK_SCOPE_CYCLE_COUNTER(STAT_OcclusionFeedback_ReadbackResults); ViewState.OcclusionFeedback.ReadbackResults(RHICmdListImmediate); ViewState.OcclusionFeedback.AdvanceFrame(ViewState.OcclusionFrameCounter); } else if (State.bHZBOcclusion) { QUICK_SCOPE_CYCLE_COUNTER(STAT_MapHZBResults); check(!ViewState.HZBOcclusionTests.IsValidFrame(ViewState.OcclusionFrameCounter)); ViewState.HZBOcclusionTests.MapResults(RHICmdListImmediate); } ViewState.PrimitiveOcclusionQueryPool.AdvanceFrame( ViewState.OcclusionFrameCounter, FOcclusionQueryHelpers::GetNumBufferedFrames(Scene.GetFeatureLevel()), State.bUseRoundRobinOcclusion); ViewState.Occlusion.NumRequestedQueries = 0; } void FGPUOcclusion::Unmap(FRHICommandListImmediate& RHICmdListImmediate) { if (State.bHZBOcclusion) { ViewState.HZBOcclusionTests.UnmapResults(RHICmdListImmediate); } if (View.FeatureLevel == ERHIFeatureLevel::ES3_1) { // Initialize/release OcclusionFeedback system on demand if (GOcclusionFeedback_Enable == 0 && ViewState.OcclusionFeedback.IsInitialized()) { ViewState.OcclusionFeedback.ReleaseResource(); } else if (GOcclusionFeedback_Enable != 0 && !ViewState.OcclusionFeedback.IsInitialized()) { ViewState.OcclusionFeedback.InitResource(RHICmdListImmediate); } } if (State.bHZBOcclusion && State.bSubmitQueries) { ViewState.HZBOcclusionTests.SetValidFrameNumber(ViewState.OcclusionFrameCounter); } } void FGPUOcclusion::WaitForLastOcclusionQuery() { const uint32 QueryIndex = FOcclusionQueryHelpers::GetQueryLookupIndex(ViewState.OcclusionFrameCounter, State.NumBufferedFrames); if (ViewState.Occlusion.LastOcclusionQueryArray[QueryIndex]) { TRACE_CPUPROFILER_EVENT_SCOPE(GPUBound_WaitingForGPUForOcclusionQueries_SeeGPUTrack); uint64 Result; const bool bWait = true; RHIGetRenderQueryResult(ViewState.Occlusion.LastOcclusionQueryArray[QueryIndex], Result, bWait); ViewState.Occlusion.LastOcclusionQueryArray[QueryIndex] = nullptr; } } /////////////////////////////////////////////////////////////////////////////// bool FGPUOcclusionParallelPacket::AddPrimitive(int32 PrimitiveIndex) { EOcclusionFlags::Type OcclusionFlags; if (CanBeOccluded(PrimitiveIndex, OcclusionFlags)) { Input.AddElement(PrimitiveIndex); int32 NumSubQueries = 1; if (EnumHasAnyFlags(OcclusionFlags, EOcclusionFlags::HasSubprimitiveQueries) && OcclusionState.bAllowSubQueries) { NumSubQueries = Scene.PrimitiveSceneProxies[PrimitiveIndex]->GetOcclusionQueries(&View)->Num(); } NumInputSubQueries += NumSubQueries; return true; } View.PrimitiveDefinitelyUnoccludedMap[PrimitiveIndex].AtomicSet(true); return false; } void FGPUOcclusionParallelPacket::LaunchOcclusionCullTask() { check(!bTaskLaunched); if (Input.IsEmpty()) { return; } bTaskLaunched = true; ViewPacket.Relevance.CommandPipe.AddNumCommands(1); FVisibilityTaskConfig& TaskConfig = ViewPacket.TaskConfig; Task = UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, &TaskConfig] { FTaskTagScope Scope(ETaskTag::EParallelRenderingThread); FPrimitiveIndexList PrimitiveIndexList; const FOcclusionCullResult Result = OcclusionCullTask(PrimitiveIndexList); if (PrimitiveIndexList.IsEmpty()) { ViewPacket.Relevance.CommandPipe.ReleaseNumCommands(1); } else { ViewPacket.Relevance.CommandPipe.EnqueueCommand(MoveTemp(PrimitiveIndexList)); } RecordOcclusionCullResult(Result); }, TaskConfig.OcclusionCull.TaskPriority); } FOcclusionCullResult FGPUOcclusionParallelPacket::OcclusionCullTask(FPrimitiveIndexList& PrimitiveIndexList) { SCOPED_NAMED_EVENT(OcclusionCull, FColor::Magenta); FOcclusionCullResult Result; PrimitiveIndexList.Reserve(Input.Num()); for (int32 Index : Input) { if (OcclusionCullPrimitive(RecordVisitor, Result, Index)) { PrimitiveIndexList.Emplace(Index); } } return Result; } /////////////////////////////////////////////////////////////////////////////// void FGPUOcclusionParallel::AddPrimitives(FPrimitiveRange PrimitiveRange) { WaitForLastOcclusionQuery(); for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, PrimitiveRange.StartIndex); BitIt.GetIndex() < PrimitiveRange.EndIndex; ++BitIt) { FGPUOcclusionParallelPacket* Packet = Packets.Last(); if (Packet->AddPrimitive(BitIt.GetIndex())) { if (Packet->IsFull()) { Packet->LaunchOcclusionCullTask(); CreateOcclusionPacket(); } } else { // The primitive will not be occluded, so accumulate a packet of primitives to send directly to the relevance pipe to reduce latency. NonOccludedPrimitives.Emplace(BitIt.GetIndex()); if (NonOccludedPrimitives.Num() == MaxNonOccludedPrimitives) { ViewPacket.Relevance.CommandPipe.AddNumCommands(1); ViewPacket.Relevance.CommandPipe.EnqueueCommand(MoveTemp(NonOccludedPrimitives)); NonOccludedPrimitives.Reset(); NonOccludedPrimitives.Reserve(MaxNonOccludedPrimitives); } } } } void FGPUOcclusionParallel::Finish(UE::Tasks::FTaskEvent& OcclusionCullTasks) { check(!bFinished); bFinished = true; Packets.Last()->LaunchOcclusionCullTask(); if (!NonOccludedPrimitives.IsEmpty()) { ViewPacket.Relevance.CommandPipe.AddNumCommands(1); ViewPacket.Relevance.CommandPipe.EnqueueCommand(MoveTemp(NonOccludedPrimitives)); NonOccludedPrimitives.Reset(); } for (FGPUOcclusionParallelPacket* Packet : Packets) { if (Packet->bTaskLaunched) { OcclusionCullTasks.AddPrerequisites(Packet->Task); } } OcclusionCullTasks.Trigger(); } void FGPUOcclusionParallel::Finalize() { check(bFinished); bFinalized = true; SCOPED_NAMED_EVENT(FinalizeOcclusionCull, FColor::Magenta); for (FGPUOcclusionParallelPacket* Packet : Packets) { FGPUOcclusionPacket::FProcessVisitor ProcessVisitor(*Packet, *RHICmdList, DynamicVertexBuffer); ProcessVisitor.Replay(Packet->RecordVisitor); } for (FGPUOcclusionParallelPacket* Packet : Packets) { // Wait to add occlusion histories until after replaying commands since it will invalidate pointers. for (FPrimitiveOcclusionHistory& OcclusionHistory : Packet->RecordVisitor.OcclusionHistories) { ViewState.Occlusion.PrimitiveOcclusionHistorySet.Add(OcclusionHistory); } delete Packet; } Packets.Empty(); DynamicVertexBuffer.Commit(); RHICmdList->FinishRecording(); FinalizeTask.Trigger(); } void FGPUOcclusionParallel::Map(FRHICommandListImmediate& RHICmdListImmediate) { FGPUOcclusion::Map(RHICmdListImmediate); RHICmdList = new FRHICommandList(RHICmdListImmediate.GetGPUMask()); RHICmdList->SwitchPipeline(ERHIPipeline::Graphics); DynamicVertexBuffer.Init(*RHICmdList); } void FGPUOcclusionParallel::Unmap(FRHICommandListImmediate& RHICmdListImmediate) { FinalizeTask.Wait(); check(bFinalized); RHICmdListImmediate.QueueAsyncCommandListSubmit(RHICmdList); FGPUOcclusion::Unmap(RHICmdListImmediate); } /////////////////////////////////////////////////////////////////////////////// void FGPUOcclusionSerial::AddPrimitives(FPrimitiveRange PrimitiveRange) { CSV_SCOPED_TIMING_STAT_EXCLUSIVE(FetchVisibilityForPrimitives); for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, PrimitiveRange.StartIndex); BitIt.GetIndex() < PrimitiveRange.EndIndex; ++BitIt) { EOcclusionFlags::Type OcclusionFlags; if (Packet.CanBeOccluded(BitIt.GetIndex(), OcclusionFlags)) { Packet.OcclusionCullPrimitive(ProcessVisitor, OcclusionCullResult, BitIt.GetIndex()); } else { View.PrimitiveDefinitelyUnoccludedMap.AccessCorrespondingBit(BitIt) = true; } } } void FGPUOcclusionSerial::Map(FRHICommandListImmediate& RHICmdListImmediate) { FGPUOcclusion::Map(RHICmdListImmediate); DynamicVertexBuffer.Init(RHICmdListImmediate); } void FGPUOcclusionSerial::Unmap(FRHICommandListImmediate& RHICmdListImmediate) { DynamicVertexBuffer.Commit(); ProcessVisitor.SubmitThrottledOcclusionQueries(); Packet.RecordOcclusionCullResult(OcclusionCullResult); FGPUOcclusion::Unmap(RHICmdListImmediate); } /////////////////////////////////////////////////////////////////////////////// static int32 PrecomputedOcclusionCull(FVisibilityViewPacket& ViewPacket, FPrimitiveRange PrimitiveRange) { int32 NumOccludedPrimitives = 0; const FScene& Scene = ViewPacket.Scene; FViewInfo& View = ViewPacket.View; if (View.PrecomputedVisibilityData) { SCOPED_NAMED_EVENT(PrecomputedOcclusionCull, FColor::Magenta); QUICK_SCOPE_CYCLE_COUNTER(STAT_PrecomputedOcclusionCull); uint8 PrecomputedVisibilityFlags = EOcclusionFlags::CanBeOccluded | EOcclusionFlags::HasPrecomputedVisibility; for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, PrimitiveRange.StartIndex); BitIt.GetIndex() < PrimitiveRange.EndIndex; ++BitIt) { if ((Scene.PrimitiveOcclusionFlags[BitIt.GetIndex()] & PrecomputedVisibilityFlags) == PrecomputedVisibilityFlags) { FPrimitiveVisibilityId VisibilityId = Scene.PrimitiveVisibilityIds[BitIt.GetIndex()]; if ((View.PrecomputedVisibilityData[VisibilityId.ByteIndex] & VisibilityId.BitMask) == 0) { if (GVisualizeOccludedPrimitives) { const FBoxSphereBounds& Bounds = Scene.PrimitiveOcclusionBounds[BitIt.GetIndex()]; DrawWireBox(&ViewPacket.ViewElementPDI, Bounds.GetBox(), FColor(100, 50, 50), SDPG_Foreground); } View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false; INC_DWORD_STAT_BY(STAT_StaticallyOccludedPrimitives, 1); NumOccludedPrimitives++; } } } } return NumOccludedPrimitives; } /////////////////////////////////////////////////////////////////////////////// FVisibilityTaskConfig::FVisibilityTaskConfig(const FScene& Scene, TConstArrayView Views) { Schedule = GVisibilityTaskSchedule != 0 ? EVisibilityTaskSchedule::Parallel : EVisibilityTaskSchedule::RenderThread; if (Schedule == EVisibilityTaskSchedule::Parallel) { if (!FApp::ShouldUseThreadingForPerformance() || !GIsThreadedRendering || !GSupportsParallelOcclusionQueries || GVisualizeOccludedPrimitives > 0 || GVisualizePrimitiveBBoxes > 0 || IsMobilePlatform(Scene.GetShaderPlatform())) { Schedule = EVisibilityTaskSchedule::RenderThread; } #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) for (FViewInfo* View : Views) { auto* ViewState = static_cast(View->State); // Frozen matrices require modifying the view matrices which does not support the async path. if (ViewState && ViewState->bIsFrozen) { Schedule = EVisibilityTaskSchedule::RenderThread; break; } } #endif } NumTestedPrimitives = uint32(Scene.Primitives.Num()); NumVisiblePrimitives = 0u; if (Scene.PrimitivesAlwaysVisibleOffset != ~0u) { NumTestedPrimitives = Scene.PrimitivesAlwaysVisibleOffset; NumVisiblePrimitives = uint32(Scene.Primitives.Num()) - NumTestedPrimitives; // Ensure that the dword alignment code is correct and we never have partial dword offsets check(uint32(NumTestedPrimitives % NumBitsPerDWORD) == 0u); } const uint32 NumWorkerThreads = FMath::Min(LowLevelTasks::FScheduler::Get().GetNumWorkers(), 16u); // These values tune the task granularity based on number of primitives in the scene and the number of worker tasks available. const uint32 NumAlwaysVisibleTasksPerThread = 2; const uint32 NumFrustumCullTasksPerThread = 2; const uint32 NumOcclusionCullTasksPerThread = 2; const uint32 NumRelevanceTasksPerThread = 32; const uint32 NumWordsPerTaskIfRenderThread = 128; // Always Visible if (NumVisiblePrimitives > 0u) { const uint32 NumPrimitiveWords = FMath::DivideAndRoundUp(NumVisiblePrimitives, NumBitsPerDWORD); if (Schedule == EVisibilityTaskSchedule::RenderThread) { AlwaysVisible.NumWordsPerTask = NumWordsPerTaskIfRenderThread; } else { AlwaysVisible.NumWordsPerTask = FMath::DivideAndRoundUp(NumPrimitiveWords, NumWorkerThreads * NumAlwaysVisibleTasksPerThread); } AlwaysVisible.NumWordsPerTask = FMath::Clamp(AlwaysVisible.NumWordsPerTask, AlwaysVisible.MinWordsPerTask, NumPrimitiveWords); AlwaysVisible.NumPrimitivesPerTask = AlwaysVisible.NumWordsPerTask * NumBitsPerDWORD; AlwaysVisible.NumTasks = FMath::DivideAndRoundUp(NumVisiblePrimitives, AlwaysVisible.NumPrimitivesPerTask); } else { AlwaysVisible.NumWordsPerTask = 0; AlwaysVisible.NumPrimitivesPerTask = 0; AlwaysVisible.NumTasks = 0; } // Frustum Cull { const uint32 NumPrimitiveWords = FMath::DivideAndRoundUp(NumTestedPrimitives, NumBitsPerDWORD); if (GFrustumCullNumPrimitivesPerTask > 0) { FrustumCull.NumWordsPerTask = FMath::DivideAndRoundUp(GFrustumCullNumPrimitivesPerTask, NumBitsPerDWORD); } else if (Schedule == EVisibilityTaskSchedule::RenderThread) { FrustumCull.NumWordsPerTask = NumWordsPerTaskIfRenderThread; } else { FrustumCull.NumWordsPerTask = FMath::DivideAndRoundUp(NumPrimitiveWords, NumWorkerThreads * NumFrustumCullTasksPerThread); } FrustumCull.NumWordsPerTask = FMath::Clamp(FrustumCull.NumWordsPerTask, FrustumCull.MinWordsPerTask, NumPrimitiveWords); FrustumCull.NumPrimitivesPerTask = FrustumCull.NumWordsPerTask * NumBitsPerDWORD; FrustumCull.NumTasks = FMath::DivideAndRoundUp(NumTestedPrimitives, FrustumCull.NumPrimitivesPerTask); } // Occlusion Cull { OcclusionCull.Views.SetNum(Views.Num()); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex) { uint32& MaxQueriesPerTask = OcclusionCull.Views[ViewIndex].MaxQueriesPerTask; if (GOcclusionCullMaxQueriesPerTask > 0) { MaxQueriesPerTask = GOcclusionCullMaxQueriesPerTask; } else if (FSceneViewState* ViewState = static_cast(Views[ViewIndex]->State)) { MaxQueriesPerTask = FMath::DivideAndRoundUp(ViewState->Occlusion.NumRequestedQueries, FMath::Max(1u, NumWorkerThreads) * NumOcclusionCullTasksPerThread); } MaxQueriesPerTask = FMath::Max(MaxQueriesPerTask, OcclusionCull.MinQueriesPerTask); } } // Relevance { const uint32 NumPrimitivesPerPacketIfRenderThread = 128; if (GRelevanceNumPrimitivesPerPacket > 0) { Relevance.NumPrimitivesPerPacket = GRelevanceNumPrimitivesPerPacket; } else if (Schedule == EVisibilityTaskSchedule::RenderThread) { Relevance.NumPrimitivesPerPacket = NumPrimitivesPerPacketIfRenderThread; } else { Relevance.NumPrimitivesPerPacket = FMath::DivideAndRoundUp(NumTestedPrimitives, NumWorkerThreads * NumRelevanceTasksPerThread); } Relevance.NumPrimitivesPerPacket = FMath::Clamp(Relevance.NumPrimitivesPerPacket, Relevance.MinPrimitivesPerTask, Relevance.MaxPrimitivesPerTask); Relevance.NumEstimatedPackets = FMath::DivideAndRoundUp(NumTestedPrimitives, Relevance.NumPrimitivesPerPacket); } } /////////////////////////////////////////////////////////////////////////////// FVisibilityViewPacket::FVisibilityViewPacket(FVisibilityTaskData& InTaskData, FScene& InScene, FViewInfo& InView, int32 InViewIndex) : TaskData(InTaskData) , TaskConfig(TaskData.TaskConfig) , Scene(InScene) , View(InView) , ViewState(static_cast(View.ViewState)) , ViewIndex(InViewIndex) , ViewElementPDI(&View, nullptr, nullptr) { if (ViewState && !View.Family->EngineShowFlags.Wireframe && DoOcclusionQueries(*View.Family)) { if (TaskConfig.Schedule == EVisibilityTaskSchedule::Parallel) { OcclusionCull.ContextIfParallel = TaskData.Allocator.Create(*this); } else { OcclusionCull.ContextIfSerial = TaskData.Allocator.Create(*this); } } if (TaskConfig.Schedule == EVisibilityTaskSchedule::Parallel) { // Chain the frustum cull task to the relevance task since we only wait on relevance. Tasks.ComputeRelevance.AddPrerequisites(Tasks.FrustumCull); // Callback for when an occlusion command is queued from frustum culling. OcclusionCull.CommandPipe.SetCommandFunction([this](FPrimitiveRange PrimitiveRange) { UpdatePrimitiveFading(Scene, View, ViewState, PrimitiveRange); const int32 NumCulledPrimitives = PrecomputedOcclusionCull(*this, PrimitiveRange); if (OcclusionCull.ContextIfParallel) { OcclusionCull.ContextIfParallel->AddPrimitives(PrimitiveRange); } else { // When occlusion is disabled primitives are queued directly to the relevance context rather than as a command on the relevance pipe. for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, PrimitiveRange.StartIndex); BitIt.GetIndex() < PrimitiveRange.EndIndex; ++BitIt) { Relevance.Context->AddPrimitive(BitIt.GetIndex()); } } TaskConfig.OcclusionCull.NumCulledPrimitives.fetch_add(NumCulledPrimitives, std::memory_order_relaxed); }); // Callback for when the occlusion pipe is done accepting commands. OcclusionCull.CommandPipe.SetEmptyFunction([this] { if (OcclusionCull.ContextIfParallel) { OcclusionCull.ContextIfParallel->Finish(Tasks.OcclusionCull); // Launch a follow-up task to finalize occlusion results after all occlusion packets have completed. UE::Tasks::Launch(UE_SOURCE_LOCATION, [this] { FTaskTagScope Scope(ETaskTag::EParallelRenderingThread); Relevance.CommandPipe.ReleaseNumCommands(1); OcclusionCull.ContextIfParallel->Finalize(); }, Tasks.OcclusionCull, TaskConfig.OcclusionCull.FinalizeTaskPriority); } else { Relevance.CommandPipe.ReleaseNumCommands(1); Tasks.OcclusionCull.Trigger(); } }); // Take a reference on the occlusion command pipe that is released when all frustum cull commands are enqueued. OcclusionCull.CommandPipe.AddNumCommands(1); // Callback for when a relevance command is queued from occlusion. Relevance.CommandPipe.SetCommandFunction([this](FPrimitiveIndexList&& PrimitiveIndexList) { Relevance.Context->AddPrimitives(MoveTemp(PrimitiveIndexList)); }); // Callback for when the relevance pipe is done accepting commands. Relevance.CommandPipe.SetEmptyFunction([this] { Relevance.Context->Finish(Tasks.ComputeRelevance); // Only used in the ViewPackets.Num() == 1 case if (TaskData.DynamicMeshElements.CommandPipe) { TaskData.DynamicMeshElements.CommandPipe->ReleaseNumCommands(1); } }); // Take a reference on the relevance command pipe that is released by the occlusion pipe when all occlusion commands are complete. Relevance.CommandPipe.AddNumCommands(1); } } void FVisibilityViewPacket::BeginInitVisibility() { TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("BeginInitVisibility %d"), ViewIndex)); // Mark all primitives as visible when not visibility culling bool bShouldVisibilityCull = GFrustumCullEnabled; UE::Tasks::FTaskEvent RelevancePrereqs{ UE_SOURCE_LOCATION }; RelevancePrereqs.AddPrerequisites(Scene.GetCacheMeshDrawCommandsTask()); // Offload initialization of maps that are not touched by frustum culling / occlusion until the relevance phase. These can be quite expensive to zero out. RelevancePrereqs.AddPrerequisites(UE::Tasks::Launch(UE_SOURCE_LOCATION, [this] { TRACE_CPUPROFILER_EVENT_SCOPE(SceneVisibility_InitMaps); View.DynamicMeshElementRanges.SetNumZeroed(Scene.Primitives.Num()); View.PrimitivesLODMask.Init(FLODMask(), Scene.Primitives.Num()); View.StaticMeshVisibilityMap.Init(false, Scene.StaticMeshes.GetMaxIndex()); View.StaticMeshFadeOutDitheredLODMap.Init(false, Scene.StaticMeshes.GetMaxIndex()); View.StaticMeshFadeInDitheredLODMap.Init(false, Scene.StaticMeshes.GetMaxIndex()); }, TaskConfig.TaskPriority)); // Allocate the view's visibility maps. View.PrimitiveVisibilityMap.Init(!bShouldVisibilityCull, Scene.Primitives.Num()); View.PrimitiveRayTracingVisibilityMap.Init(false, Scene.Primitives.Num()); View.PrimitiveDefinitelyUnoccludedMap.Init(!DoOcclusionQueries(*View.Family), Scene.Primitives.Num()); View.PotentiallyFadingPrimitiveMap.Init(false, Scene.Primitives.Num()); View.PrimitiveFadeUniformBuffers.AddZeroed(Scene.Primitives.Num()); View.PrimitiveFadeUniformBufferMap.Init(false, Scene.Primitives.Num()); View.PrimitiveViewRelevanceMap.Reset(Scene.Primitives.Num()); View.PrimitiveViewRelevanceMap.AddZeroed(Scene.Primitives.Num()); RelevancePrereqs.Trigger(); if (View.ShowOnlyPrimitives.IsSet()) { View.bHasNoVisiblePrimitive = View.ShowOnlyPrimitives->Num() == 0; } Relevance.Context = TaskData.Allocator.Create(TaskData, Scene, View, ViewIndex, RelevancePrereqs); ClearStalePrimitiveFadingStates(View, ViewState); // Development builds sometimes override frustum culling, e.g. dependent views in the editor. #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (ViewState) { // For views with frozen visibility, check if the primitive is in the frozen visibility set. if (ViewState->bIsFrozen) { bShouldVisibilityCull = false; for (int32 Index = 0; Index < int32(TaskConfig.NumTestedPrimitives); ++Index) { if (ViewState->FrozenPrimitives.Contains(Scene.PrimitiveComponentIds[Index])) { View.PrimitiveVisibilityMap[Index] = true; } } } } #endif const float MaxDrawDistanceScale = GetCachedScalabilityCVars().ViewDistanceScale * GetCachedScalabilityCVars().CalculateFieldOfViewDistanceScale(View.DesiredFOV); const FHLODVisibilityState* HLODState = nullptr; // Most views use standard frustum culling. if (bShouldVisibilityCull) { TRACE_CPUPROFILER_EVENT_SCOPE(HLODInit); // Update HLOD transition/visibility states to allow use during distance culling FLODSceneTree& HLODTree = Scene.SceneLODHierarchy; if (HLODTree.IsActive()) { HLODTree.UpdateVisibilityStates(View, Tasks.LightVisibility); if (ViewState) { HLODState = &ViewState->HLODVisibilityState; } } else { HLODTree.ClearVisibilityState(View); } } Tasks.LightVisibility.Trigger(); FFrustumCullingFlags Flags; Flags.bShouldVisibilityCull = bShouldVisibilityCull; Flags.bUseCustomCulling = View.CustomVisibilityQuery && View.CustomVisibilityQuery->Prepare(); Flags.bUseSphereTestFirst = GFrustumCullUseSphereTestFirst; Flags.bUseFastIntersect = (View.GetCullingFrustum().PermutedPlanes.Num() == 8) && GFrustumCullUseFastIntersect; Flags.bUseVisibilityOctree = GFrustumCullUseOctree; Flags.bHasHiddenPrimitives = View.HiddenPrimitives.Num() > 0; Flags.bHasShowOnlyPrimitives = View.ShowOnlyPrimitives.IsSet(); UE::Tasks::FTask PrerequisiteTask; #if RHI_RAYTRACING View.RayTracingCullingParameters.Init(View); // Sync cached raytracing tasks ShouldCullForRayTracing. PrerequisiteTask = Scene.GetCacheRayTracingPrimitivesTask(); #endif FSceneBitArray* VisibleNodes = nullptr; if (bShouldVisibilityCull && Flags.bUseVisibilityOctree) { VisibleNodes = TaskData.Allocator.Create(); const FConvexVolume& ViewCullingFrustum = View.GetCullingFrustum(); CullOctree(Scene, View, Flags, *VisibleNodes, ViewCullingFrustum); } const bool bCullingIsThreadsafe = (!Flags.bUseCustomCulling || View.CustomVisibilityQuery->IsThreadsafe()); if (TaskConfig.Schedule == EVisibilityTaskSchedule::Parallel) { // Always Visible const bool bHasAlwaysVisible = TaskConfig.NumVisiblePrimitives > 0; if (bHasAlwaysVisible) { const float CurrentWorldTime = View.Family->Time.GetWorldTimeSeconds(); for (uint32 TaskIndex = 0; TaskIndex < TaskConfig.AlwaysVisible.NumTasks; ++TaskIndex) { Tasks.AlwaysVisible.AddPrerequisites( UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, Flags, TaskIndex, CurrentWorldTime]() mutable { TRACE_CPUPROFILER_EVENT_SCOPE(SceneVisibility_AlwaysVisible); SCOPE_CYCLE_COUNTER(STAT_UpdateAlwaysVisible); FTaskTagScope TaskTagScope(ETaskTag::EParallelRenderingThread); UpdateAlwaysVisible(Scene, View, Flags, TaskConfig, TaskIndex, CurrentWorldTime); }, PrerequisiteTask, TaskConfig.TaskPriority, UE::Tasks::EExtendedTaskPriority::None)); } } // Frustum Cull { // Frustum culling tasks have to run serially if custom culling is not thread-safe. const UE::Tasks::EExtendedTaskPriority ExtendedTaskPriority = GetExtendedTaskPriority(bCullingIsThreadsafe); // Assign the number of expected commands first so the pipe can determine when the last task has completed. OcclusionCull.CommandPipe.AddNumCommands(TaskConfig.FrustumCull.NumTasks); for (uint32 TaskIndex = 0; TaskIndex < TaskConfig.FrustumCull.NumTasks; ++TaskIndex) { Tasks.FrustumCull.AddPrerequisites( UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, Flags, MaxDrawDistanceScale, HLODState, VisibleNodes, TaskIndex]() mutable { TRACE_CPUPROFILER_EVENT_SCOPE(SceneVisibility_FrustumCull); FTaskTagScope TaskTagScope(ETaskTag::EParallelRenderingThread); int32 NumCulledPrimitives = FrustumCull(Scene, View, Flags, MaxDrawDistanceScale, HLODState, VisibleNodes, TaskConfig, TaskIndex); FPrimitiveRange PrimitiveRange; PrimitiveRange.StartIndex = TaskConfig.FrustumCull.NumPrimitivesPerTask * (TaskIndex); PrimitiveRange.EndIndex = TaskConfig.FrustumCull.NumPrimitivesPerTask + PrimitiveRange.StartIndex; PrimitiveRange.EndIndex = FMath::Min(PrimitiveRange.EndIndex, int32(TaskConfig.NumTestedPrimitives)); // Skip rendering of dynamic objects without static lighting for static reflection captures. if (View.bStaticSceneOnly) { for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, PrimitiveRange.StartIndex); BitIt.GetIndex() < PrimitiveRange.EndIndex; ++BitIt) { if (!Scene.PrimitiveSceneProxies[BitIt.GetIndex()]->HasStaticLighting()) { View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false; NumCulledPrimitives++; } } } // Skip rendering of small objects when in wireframe mode for performance since wireframe doesn't enable occlusion culling. if (View.Family->EngineShowFlags.Wireframe) { const float ScreenSizeScale = FMath::Max(View.ViewMatrices.GetProjectionMatrix().M[0][0] * View.ViewRect.Width(), View.ViewMatrices.GetProjectionMatrix().M[1][1] * View.ViewRect.Height()); for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, PrimitiveRange.StartIndex); BitIt.GetIndex() < PrimitiveRange.EndIndex; ++BitIt) { if (ScreenSizeScale * Scene.PrimitiveBounds[BitIt.GetIndex()].BoxSphereBounds.SphereRadius <= GWireframeCullThreshold) { View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false; NumCulledPrimitives++; } } } const uint32 NumVisiblePrimitives = PrimitiveRange.EndIndex - PrimitiveRange.StartIndex - NumCulledPrimitives; if (NumVisiblePrimitives == 0) { OcclusionCull.CommandPipe.ReleaseNumCommands(1); } else { OcclusionCull.CommandPipe.EnqueueCommand(PrimitiveRange); } TaskConfig.FrustumCull.NumCulledPrimitives.fetch_add(NumCulledPrimitives, std::memory_order_relaxed); }, bHasAlwaysVisible ? Tasks.AlwaysVisible : PrerequisiteTask, TaskConfig.TaskPriority, ExtendedTaskPriority)); } OcclusionCull.CommandPipe.ReleaseNumCommands(1); } } else { const bool bSingleThreaded = !FApp::ShouldUseThreadingForPerformance() || !bCullingIsThreadsafe; PrerequisiteTask.Wait(); const float CurrentWorldTime = View.Family->Time.GetWorldTimeSeconds(); ParallelFor(TaskConfig.AlwaysVisible.NumTasks, [this, Flags, CurrentWorldTime](int32 TaskIndex) { TRACE_CPUPROFILER_EVENT_SCOPE(SceneVisibility_AlwaysVisible); FTaskTagScope TaskTagScope(ETaskTag::EParallelRenderingThread); UpdateAlwaysVisible(Scene, View, Flags, TaskConfig, TaskIndex, CurrentWorldTime); }, bSingleThreaded); ParallelFor(TaskConfig.FrustumCull.NumTasks, [this, Flags, MaxDrawDistanceScale, HLODState, VisibleNodes](int32 TaskIndex) { TRACE_CPUPROFILER_EVENT_SCOPE(SceneVisibility_FrustumCull); FTaskTagScope TaskTagScope(ETaskTag::EParallelRenderingThread); int32 NumCulledPrimitives = FrustumCull(Scene, View, Flags, MaxDrawDistanceScale, HLODState, VisibleNodes, TaskConfig, TaskIndex); TaskConfig.FrustumCull.NumCulledPrimitives.fetch_add(NumCulledPrimitives, std::memory_order_relaxed); }, bSingleThreaded); } Tasks.AlwaysVisible.Trigger(); Tasks.FrustumCull.Trigger(); } /////////////////////////////////////////////////////////////////////////////// FDynamicMeshElementContext::FDynamicMeshElementContext(FSceneRenderer& SceneRenderer) : FirstViewFamily(SceneRenderer.ViewFamily) , Views(SceneRenderer.AllViews) , Primitives(SceneRenderer.Scene->Primitives) // Defer committing GPU scene and Material updates until the mesh collectors are finished. Deferring materials allows VT updates to // overlap with GDME (uniform expression updates can't happen at the same time as the async VT system update), and since we are going // wide across a single view we would otherwise have to lock for GPU scene updates. , MeshCollector(SceneRenderer.FeatureLevel, SceneRenderer.Allocator, FMeshElementCollector::ECommitFlags::DeferAll) #if WITH_EDITOR , EditorMeshCollector(SceneRenderer.FeatureLevel, SceneRenderer.Allocator, FMeshElementCollector::ECommitFlags::DeferAll) #endif , RHICmdList(new FRHICommandList(FRHIGPUMask::All())) , DynamicVertexBuffer(*RHICmdList) , DynamicIndexBuffer(*RHICmdList) { // With Custom Render Passes, it's possible to have Views that point to a different Family. Divide Views into groups with the same family. uint8 ViewSubsetMask = 0; int32 LastViewIndex = Views.Num() - 1; for (int32 ViewIndex = 0; ViewIndex <= LastViewIndex; ViewIndex++) { ViewSubsetMask |= 1u << ViewIndex; // Check if the next element has a different ViewFamily, or this is the last view. If so, emit the GetDynamicMeshElements call // with visibility mask bits set for the subset of views with the same ViewFamily, and reset the mask for the next family. if (ViewIndex == LastViewIndex || Views[ViewIndex]->Family != Views[ViewIndex + 1]->Family) { // Emit a batch with the given mask, and reset the mask ViewFamilyGroups.Add({ Views[ViewIndex]->Family, ViewSubsetMask }); ViewSubsetMask = 0; } } RHICmdList->SwitchPipeline(ERHIPipeline::Graphics); ViewMeshArraysPerView.SetNum(Views.Num()); MeshCollector.Start( *RHICmdList, DynamicVertexBuffer, DynamicIndexBuffer, SceneRenderer.DynamicReadBufferForInitViews ); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { FViewInfo& View = *Views[ViewIndex]; FViewMeshArrays& ViewMeshArrays = ViewMeshArraysPerView[ViewIndex]; MeshCollector.AddViewMeshArrays( &View, &ViewMeshArrays.DynamicMeshElements, &ViewMeshArrays.SimpleElementCollector, &View.DynamicPrimitiveCollector #if UE_ENABLE_DEBUG_DRAWING , &ViewMeshArrays.DebugSimpleElementCollector #endif ); } #if WITH_EDITOR if (GIsEditor) { EditorMeshCollector.Start( *RHICmdList, DynamicVertexBuffer, DynamicIndexBuffer, SceneRenderer.DynamicReadBufferForInitViews ); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { FViewInfo& View = *Views[ViewIndex]; FViewMeshArrays& ViewMeshArrays = ViewMeshArraysPerView[ViewIndex]; EditorMeshCollector.AddViewMeshArrays( &View, &ViewMeshArrays.DynamicEditorMeshElements, &ViewMeshArrays.EditorSimpleElementCollector, &View.DynamicPrimitiveCollector #if UE_ENABLE_DEBUG_DRAWING , &ViewMeshArrays.DebugSimpleElementCollector #endif ); } } #endif } FGraphEventRef FDynamicMeshElementContext::LaunchRenderThreadTask(FDynamicPrimitiveIndexList&& PrimitiveIndexList) { return FFunctionGraphTask::CreateAndDispatchWhenReady([this, PrimitiveIndexList = MoveTemp(PrimitiveIndexList)] { for (FDynamicPrimitiveIndex PrimitiveIndex : PrimitiveIndexList.Primitives) { GatherDynamicMeshElementsForPrimitive(Primitives[PrimitiveIndex.Index], PrimitiveIndex.ViewMask); } #if WITH_EDITOR for (FDynamicPrimitiveIndex PrimitiveIndex : PrimitiveIndexList.EditorPrimitives) { GatherDynamicMeshElementsForEditorPrimitive(Primitives[PrimitiveIndex.Index], PrimitiveIndex.ViewMask); } #endif }, TStatId{}, nullptr, ENamedThreads::GetRenderThread_Local()); } UE::Tasks::FTask FDynamicMeshElementContext::LaunchAsyncTask(FDynamicPrimitiveIndexQueue* PrimitiveIndexQueue, UE::Tasks::ETaskPriority TaskPriority) { return Pipe.Launch(UE_SOURCE_LOCATION, [this, PrimitiveIndexQueue] { FTaskTagScope Scope(ETaskTag::EParallelRenderingThread); FDynamicPrimitiveIndex PrimitiveIndex; while (PrimitiveIndexQueue->Pop(PrimitiveIndex)) { GatherDynamicMeshElementsForPrimitive(Primitives[PrimitiveIndex.Index], PrimitiveIndex.ViewMask); } #if WITH_EDITOR while (PrimitiveIndexQueue->PopEditor(PrimitiveIndex)) { GatherDynamicMeshElementsForEditorPrimitive(Primitives[PrimitiveIndex.Index], PrimitiveIndex.ViewMask); } #endif }, TaskPriority); } void FDynamicMeshElementContext::GatherDynamicMeshElementsForPrimitive(FPrimitiveSceneInfo* Primitive, uint8 ViewMask) { SCOPED_NAMED_EVENT(DynamicPrimitive, FColor::Magenta); TArray> MeshBatchCountBefore; MeshBatchCountBefore.SetNumUninitialized(Views.Num()); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { MeshBatchCountBefore[ViewIndex] = MeshCollector.GetMeshBatchCount(ViewIndex); } MeshCollector.SetPrimitive(Primitive->Proxy, Primitive->DefaultDynamicHitProxyId); // If Custom Render Passes aren't in use, there will be only one group, which is the common case. if (ViewFamilyGroups.Num() == 1 || Primitive->Proxy->SinglePassGDME()) { Primitive->Proxy->GetDynamicMeshElements(FirstViewFamily.AllViews, FirstViewFamily, ViewMask, MeshCollector); } else { for (FViewFamilyGroup& Group : ViewFamilyGroups) { if (uint8 MaskedViewMask = ViewMask & Group.ViewSubsetMask) { Primitive->Proxy->GetDynamicMeshElements(FirstViewFamily.AllViews, *Group.Family, MaskedViewMask, MeshCollector); } } } for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { FViewInfo& View = *Views[ViewIndex]; if (ViewMask & (1 << ViewIndex)) { FDynamicPrimitive& DynamicPrimitive = DynamicPrimitives.Emplace_GetRef(); DynamicPrimitive.PrimitiveIndex = Primitive->GetIndex(); DynamicPrimitive.ViewIndex = ViewIndex; DynamicPrimitive.StartElementIndex = MeshBatchCountBefore[ViewIndex]; DynamicPrimitive.EndElementIndex = MeshCollector.GetMeshBatchCount(ViewIndex); } } } void FDynamicMeshElementContext::GatherDynamicMeshElementsForEditorPrimitive(FPrimitiveSceneInfo* Primitive, uint8 ViewMask) { #if WITH_EDITOR EditorMeshCollector.SetPrimitive(Primitive->Proxy, Primitive->DefaultDynamicHitProxyId); // If Custom Render Passes aren't in use, there will be only one group, which is the common case. if (ViewFamilyGroups.Num() == 1 || Primitive->Proxy->SinglePassGDME()) { Primitive->Proxy->GetDynamicMeshElements(FirstViewFamily.AllViews, FirstViewFamily, ViewMask, EditorMeshCollector); } else { for (FViewFamilyGroup& Group : ViewFamilyGroups) { if (uint8 MaskedViewMask = ViewMask & Group.ViewSubsetMask) { Primitive->Proxy->GetDynamicMeshElements(FirstViewFamily.AllViews, *Group.Family, MaskedViewMask, EditorMeshCollector); } } } #endif } void FDynamicMeshElementContext::Finish() { MeshCollector.Finish(); #if WITH_EDITOR if (GIsEditor) { EditorMeshCollector.Finish(); } #endif DynamicVertexBuffer.Commit(); DynamicIndexBuffer.Commit(); RHICmdList->FinishRecording(); // Even though task dependencies are setup so all work is done by this point, we still have to wait on the // pipe to clear out its internal state. Otherwise it can assert that it still has work at shutdown. Pipe.WaitUntilEmpty(); } FDynamicMeshElementContextContainer::~FDynamicMeshElementContextContainer() { check(CommandLists.IsEmpty()); } UE::Tasks::FTask FDynamicMeshElementContextContainer::LaunchAsyncTask(FDynamicPrimitiveIndexQueue* PrimitiveIndexQueue, int32 Index, UE::Tasks::ETaskPriority TaskPriority) { return Contexts[Index]->LaunchAsyncTask(PrimitiveIndexQueue, TaskPriority); } FGraphEventRef FDynamicMeshElementContextContainer::LaunchRenderThreadTask(FDynamicPrimitiveIndexList&& PrimitiveIndexList) { return Contexts.Last()->LaunchRenderThreadTask(MoveTemp(PrimitiveIndexList)); } void FDynamicMeshElementContextContainer::Init(FSceneRenderer& SceneRenderer, int32 NumAsyncContexts) { const int32 NumRenderThreadContexts = 1; const int32 NumContexts = NumAsyncContexts + NumRenderThreadContexts; Views = SceneRenderer.AllViews; Contexts.Reserve(NumContexts); CommandLists.Reserve(Contexts.Num()); for (int32 Index = 0; Index < NumContexts; ++Index) { FDynamicMeshElementContext* Context = SceneRenderer.Allocator.Create(SceneRenderer); Contexts.Emplace(Context); CommandLists.Emplace(Context->RHICmdList); } } void FDynamicMeshElementContextContainer::MergeContexts(TArray& OutDynamicPrimitives) { SCOPED_NAMED_EVENT(MergeGatherDynamicMeshElementContexts, FColor::Magenta); check(!Views.IsEmpty()); // Fast path for one context; just move the memory instead of copying. if (Contexts.Num() == 1) { FDynamicMeshElementContext* Context = Contexts[0]; OutDynamicPrimitives = MoveTemp(Context->DynamicPrimitives); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex) { FViewInfo& View = *Views[ViewIndex]; FDynamicMeshElementContext::FViewMeshArrays& ViewMeshArrays = Context->ViewMeshArraysPerView[ViewIndex]; View.SimpleElementCollector = MoveTemp(ViewMeshArrays.SimpleElementCollector); View.DynamicMeshElements = MoveTemp(ViewMeshArrays.DynamicMeshElements); #if WITH_EDITOR View.EditorSimpleElementCollector = MoveTemp(ViewMeshArrays.EditorSimpleElementCollector); View.DynamicEditorMeshElements = MoveTemp(ViewMeshArrays.DynamicEditorMeshElements); #endif #if UE_ENABLE_DEBUG_DRAWING View.DebugSimpleElementCollector = MoveTemp(ViewMeshArrays.DebugSimpleElementCollector); #endif } } // >1 context means we have to merge the containers. else { struct FViewAllocationInfo { FSimpleElementCollector::FAllocationInfo SimpleElementCollector; uint32 NumDynamicMeshElements = 0; #if WITH_EDITOR FSimpleElementCollector::FAllocationInfo EditorSimpleElementCollector; uint32 NumDynamicEditorMeshElements = 0; #endif #if UE_ENABLE_DEBUG_DRAWING FSimpleElementCollector::FAllocationInfo DebugSimpleElementCollector; #endif }; TArray> AllocationInfosPerView; AllocationInfosPerView.AddDefaulted(Views.Num()); uint32 NumDynamicPrimitives = 0; for (FDynamicMeshElementContext* Context : Contexts) { NumDynamicPrimitives += Context->DynamicPrimitives.Num(); check(AllocationInfosPerView.Num() == Context->ViewMeshArraysPerView.Num()); // Accumulate allocation info for each context in order to reserve container memory once. for (int32 ViewIndex = 0; ViewIndex < AllocationInfosPerView.Num(); ++ViewIndex) { const FDynamicMeshElementContext::FViewMeshArrays& ViewMeshArrays = Context->ViewMeshArraysPerView[ViewIndex]; FViewAllocationInfo& AllocationInfo = AllocationInfosPerView[ViewIndex]; ViewMeshArrays.SimpleElementCollector.AddAllocationInfo(AllocationInfo.SimpleElementCollector); AllocationInfo.NumDynamicMeshElements += ViewMeshArrays.DynamicMeshElements.Num(); #if WITH_EDITOR ViewMeshArrays.EditorSimpleElementCollector.AddAllocationInfo(AllocationInfo.EditorSimpleElementCollector); AllocationInfo.NumDynamicEditorMeshElements += ViewMeshArrays.DynamicEditorMeshElements.Num(); #endif #if UE_ENABLE_DEBUG_DRAWING ViewMeshArrays.DebugSimpleElementCollector.AddAllocationInfo(AllocationInfo.DebugSimpleElementCollector); #endif } } OutDynamicPrimitives.Reserve(NumDynamicPrimitives); // Reserve memory for merged containers. for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex) { FViewAllocationInfo& AllocationInfo = AllocationInfosPerView[ViewIndex]; FViewInfo& View = *Views[ViewIndex]; View.SimpleElementCollector.Reserve(AllocationInfo.SimpleElementCollector); View.DynamicMeshElements.Reserve(AllocationInfo.NumDynamicMeshElements); #if WITH_EDITOR View.EditorSimpleElementCollector.Reserve(AllocationInfo.EditorSimpleElementCollector); View.DynamicEditorMeshElements.Reserve(AllocationInfo.NumDynamicEditorMeshElements); #endif #if UE_ENABLE_DEBUG_DRAWING View.DebugSimpleElementCollector.Reserve(AllocationInfo.DebugSimpleElementCollector); #endif // Reset dynamic element count to use as offset for copying ranges in the next loop. AllocationInfo.NumDynamicMeshElements = 0; } for (FDynamicMeshElementContext* Context : Contexts) { for (FDynamicPrimitive DynamicPrimitive : Context->DynamicPrimitives) { const uint32 NumDynamicMeshElements = AllocationInfosPerView[DynamicPrimitive.ViewIndex].NumDynamicMeshElements; // Offset the dynamic element range by the current number of meshes in the final container. DynamicPrimitive.StartElementIndex += NumDynamicMeshElements; DynamicPrimitive.EndElementIndex += NumDynamicMeshElements; OutDynamicPrimitives.Emplace(DynamicPrimitive); Views[DynamicPrimitive.ViewIndex]->DynamicMeshElementRanges[DynamicPrimitive.PrimitiveIndex] = FInt32Vector2(DynamicPrimitive.StartElementIndex, DynamicPrimitive.EndElementIndex); } for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex) { FViewInfo& View = *Views[ViewIndex]; FDynamicMeshElementContext::FViewMeshArrays& ViewMeshArrays = Context->ViewMeshArraysPerView[ViewIndex]; AllocationInfosPerView[ViewIndex].NumDynamicMeshElements += ViewMeshArrays.DynamicMeshElements.Num(); View.SimpleElementCollector.Append(ViewMeshArrays.SimpleElementCollector); View.DynamicMeshElements.Append(ViewMeshArrays.DynamicMeshElements); #if WITH_EDITOR View.EditorSimpleElementCollector.Append(ViewMeshArrays.EditorSimpleElementCollector); View.DynamicEditorMeshElements.Append(ViewMeshArrays.DynamicEditorMeshElements); #endif #if UE_ENABLE_DEBUG_DRAWING View.DebugSimpleElementCollector.Append(ViewMeshArrays.DebugSimpleElementCollector); #endif } Context->DynamicPrimitives.Empty(); Context->ViewMeshArraysPerView.Empty(); } } } void FDynamicMeshElementContextContainer::Submit(FRHICommandListImmediate& RHICmdList) { for (FDynamicMeshElementContext* Context : Contexts) { Context->Finish(); } RHICmdList.QueueAsyncCommandListSubmit(CommandLists); CommandLists.Empty(); } /////////////////////////////////////////////////////////////////////////////// FVisibilityTaskData::FVisibilityTaskData(FRHICommandListImmediate& InRHICmdList, FSceneRenderer& InSceneRenderer) : RHICmdList(InRHICmdList) , SceneRenderer(InSceneRenderer) , Scene(*SceneRenderer.Scene) , Views(SceneRenderer.AllViews) , ViewFamily(SceneRenderer.ViewFamily) , ShadingPath(GetFeatureLevelShadingPath(Scene.GetFeatureLevel())) , TaskConfig(Scene, Views) , bAddNaniteRelevance(InSceneRenderer.ShouldRenderNanite()) , bAddLightmapDensityCommands(ViewFamily.EngineShowFlags.LightMapDensity&& AllowDebugViewmodes()) { Tasks.bWaitingAllowed = TaskConfig.Schedule == EVisibilityTaskSchedule::Parallel; } void FVisibilityTaskData::LaunchVisibilityTasks(const UE::Tasks::FTask& BeginInitVisibilityPrerequisites) { SCOPED_NAMED_EVENT(LaunchVisibilityTasks, FColor::Magenta); ViewPackets.Reserve(Views.Num()); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex) { if (Views[ViewIndex]->bIsSinglePassStereo && Views[ViewIndex]->StereoPass == EStereoscopicPass::eSSP_SECONDARY) { continue; } // Each non-vestigial view gets its own visibility task packet which contains all the state to manage the task graph for a view. FVisibilityViewPacket& ViewPacket = ViewPackets.Emplace_GetRef(*this, Scene, *Views[ViewIndex], ViewIndex); // Each respective stage of visibility for each view is connected to its corresponding task event in order to track when all views complete each stage. Tasks.LightVisibility.AddPrerequisites(ViewPacket.Tasks.LightVisibility); Tasks.FrustumCull.AddPrerequisites(ViewPacket.Tasks.FrustumCull); Tasks.OcclusionCull.AddPrerequisites(ViewPacket.Tasks.OcclusionCull); Tasks.ComputeRelevance.AddPrerequisites(ViewPacket.Tasks.ComputeRelevance); if (ViewPacket.ViewState) { SCOPE_CYCLE_COUNTER(STAT_DecompressPrecomputedOcclusion); ViewPacket.View.PrecomputedVisibilityData = ViewPacket.ViewState->ResolvePrecomputedVisibilityData(ViewPacket.View, &Scene); if (ViewPacket.View.PrecomputedVisibilityData) { SceneRenderer.bUsedPrecomputedVisibility = true; } } } // Each relevance task should have this as a prerequisite, but in case there aren't any tasks we make it explicit. Tasks.ComputeRelevance.AddPrerequisites(Scene.GetCacheMeshDrawCommandsTask()); // Wait on the GPU skin update task prior to GDME. Tasks.DynamicMeshElementsPrerequisites.AddPrerequisites(Scene.GetGPUSkinUpdateTask()); bool bAllocatePrimitiveViewMasks = true; if (TaskConfig.Schedule == EVisibilityTaskSchedule::Parallel) { if (ViewPackets.Num() == 1) { // When using a single view, dynamic mesh elements are pushed into a pipe that is executed on the render thread which allows for some overlap with compute relevance work. DynamicMeshElements.CommandPipe = Allocator.Create>(TEXT("GatherDynamicMeshElements")); DynamicMeshElements.CommandPipe->SetCommandFunction([this](FDynamicPrimitiveIndexList&& DynamicPrimitiveIndexList) { GatherDynamicMeshElements(MoveTemp(DynamicPrimitiveIndexList)); }); DynamicMeshElements.CommandPipe->SetPrerequisiteTask(Tasks.DynamicMeshElementsPrerequisites); Tasks.DynamicMeshElementsPipe = FGraphEvent::CreateGraphEvent(); DynamicMeshElements.CommandPipe->SetEmptyFunction([this] { Tasks.DynamicMeshElementsPipe->DispatchSubsequents(); Tasks.DynamicMeshElements.Trigger(); }); // Take a reference that is released when the relevance pipe has completed. We only need to take one since there can only be one view. DynamicMeshElements.CommandPipe->AddNumCommands(1); // We don't need the primitive view masks when in parallel mode with a single view. bAllocatePrimitiveViewMasks = false; } } if (bAllocatePrimitiveViewMasks) { DynamicMeshElements.PrimitiveViewMasks = Allocator.Create(); DynamicMeshElements.PrimitiveViewMasks->Primitives.AddZeroed(Scene.Primitives.Num()); #if WITH_EDITOR if (GIsEditor) { DynamicMeshElements.PrimitiveViewMasks->EditorPrimitives.AddZeroed(Scene.Primitives.Num()); } #endif } DynamicMeshElements.ContextContainer.Init(SceneRenderer, GetNumDynamicMeshElementTasks()); DynamicMeshElements.ViewCommandsPerView.SetNum(Views.Num()); Tasks.LightVisibility.AddPrerequisites(UE::Tasks::Launch(UE_SOURCE_LOCATION, [this] { FTaskTagScope Scope(ETaskTag::EParallelRenderingThread); SceneRenderer.ComputeLightVisibility(); }, TaskConfig.TaskPriority)); if (TaskConfig.Schedule == EVisibilityTaskSchedule::Parallel) { SceneRenderer.WaitOcclusionTests(RHICmdList); // Parallel occlusion culling is not supported on mobile check(!Views.IsEmpty()) checkf(!Views[0]->bIsMobileMultiViewEnabled, TEXT("This culling path was not tested with MMV")); for (FVisibilityViewPacket& ViewPacket : ViewPackets) { if (ViewPacket.OcclusionCull.ContextIfParallel) { ViewPacket.OcclusionCull.ContextIfParallel->Map(RHICmdList); } Tasks.BeginInitVisibility.AddPrerequisites(UE::Tasks::Launch(UE_SOURCE_LOCATION, [&ViewPacket] { FTaskTagScope Scope(ETaskTag::EParallelRenderingThread); ViewPacket.BeginInitVisibility(); }, BeginInitVisibilityPrerequisites, TaskConfig.TaskPriority)); } // Static relevance is finalized for ALL views after each view completes static mesh filtering tasks. Tasks.FinalizeRelevance = UE::Tasks::Launch(UE_SOURCE_LOCATION, [this] { TRACE_CPUPROFILER_EVENT_SCOPE(FSceneRenderer_FinalizeStaticRelevance); for (FVisibilityViewPacket& ViewPacket : ViewPackets) { ViewPacket.Relevance.Context->Finalize(); } }, Tasks.ComputeRelevance, TaskConfig.TaskPriority); } // All task events are connected to prerequisites now and can be safely triggered. Tasks.BeginInitVisibility.Trigger(); Tasks.LightVisibility.Trigger(); Tasks.FrustumCull.Trigger(); Tasks.OcclusionCull.Trigger(); Tasks.ComputeRelevance.Trigger(); } void FVisibilityTaskData::GatherDynamicMeshElements(FDynamicPrimitiveIndexList&& DynamicPrimitiveIndexList) { FDynamicPrimitiveIndexList RenderThreadDynamicPrimitiveIndexList; const int32 NumAsyncContexts = DynamicMeshElements.ContextContainer.GetNumAsyncContexts(); if (NumAsyncContexts > 0) { const auto FilterDynamicPrimitives = [] (TArrayView PrimitiveSceneProxies, FDynamicPrimitiveIndexList::FList& Primitives, FDynamicPrimitiveIndexList::FList& RenderThreadPrimitives) { for (int32 Index = 0; Index < Primitives.Num(); ) { const FDynamicPrimitiveIndex PrimitiveIndex = Primitives[Index]; if (!PrimitiveSceneProxies[PrimitiveIndex.Index]->SupportsParallelGDME()) { RenderThreadPrimitives.Emplace(PrimitiveIndex); Primitives.RemoveAtSwap(Index, EAllowShrinking::No); } else { Index++; } } }; FilterDynamicPrimitives(Scene.PrimitiveSceneProxies, DynamicPrimitiveIndexList.Primitives, RenderThreadDynamicPrimitiveIndexList.Primitives); #if WITH_EDITOR FilterDynamicPrimitives(Scene.PrimitiveSceneProxies, DynamicPrimitiveIndexList.EditorPrimitives, RenderThreadDynamicPrimitiveIndexList.EditorPrimitives); #endif } else { RenderThreadDynamicPrimitiveIndexList = MoveTemp(DynamicPrimitiveIndexList); DynamicPrimitiveIndexList = {}; } if (!RenderThreadDynamicPrimitiveIndexList.IsEmpty()) { Tasks.DynamicMeshElementsRenderThread = DynamicMeshElements.ContextContainer.LaunchRenderThreadTask(MoveTemp(RenderThreadDynamicPrimitiveIndexList)); } if (!DynamicPrimitiveIndexList.IsEmpty()) { FDynamicPrimitiveIndexQueue* Queue = Allocator.Create(MoveTemp(DynamicPrimitiveIndexList)); for (int32 Index = 0; Index < DynamicMeshElements.ContextContainer.GetNumAsyncContexts(); ++Index) { Tasks.DynamicMeshElements.AddPrerequisites(DynamicMeshElements.ContextContainer.LaunchAsyncTask(Queue, Index, TaskConfig.TaskPriority)); } } } void FVisibilityTaskData::GatherDynamicMeshElements(const FDynamicPrimitiveViewMasks& DynamicPrimitiveViewMasks) { SCOPED_NAMED_EVENT(GatherDynamicMeshElements, FColor::Magenta); Tasks.DynamicMeshElementsPrerequisites.Wait(); const auto GetPrimaryViewMask = [this] (uint8 ViewMask) -> uint8 { // If a mesh is visible in a secondary view, mark it as visible in the primary view uint8 ViewMaskFinal = ViewMask; for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { const FViewInfo& View = *Views[ViewIndex]; if (ViewMask & (1 << ViewIndex) && IStereoRendering::IsASecondaryView(View)) { ViewMaskFinal |= 1 << Views[ViewIndex]->PrimaryViewIndex; } } return ViewMaskFinal; }; FDynamicPrimitiveIndexList DynamicPrimitiveIndexList; DynamicPrimitiveIndexList.Primitives.Reserve(128); const int32 NumPrimitives = DynamicPrimitiveViewMasks.Primitives.Num(); for (int32 PrimitiveIndex = 0; PrimitiveIndex < NumPrimitives; ++PrimitiveIndex) { const uint8 ViewMask = DynamicPrimitiveViewMasks.Primitives[PrimitiveIndex]; if (ViewMask != 0) { DynamicPrimitiveIndexList.Primitives.Emplace(PrimitiveIndex, GetPrimaryViewMask(ViewMask)); } } #if WITH_EDITOR if (GIsEditor) { DynamicPrimitiveIndexList.EditorPrimitives.Reserve(128); for (int32 PrimitiveIndex = 0; PrimitiveIndex < NumPrimitives; ++PrimitiveIndex) { const uint8 ViewMask = DynamicPrimitiveViewMasks.EditorPrimitives[PrimitiveIndex]; if (ViewMask != 0) { DynamicPrimitiveIndexList.EditorPrimitives.Emplace(PrimitiveIndex, GetPrimaryViewMask(ViewMask)); } } } #endif GatherDynamicMeshElements(MoveTemp(DynamicPrimitiveIndexList)); Tasks.DynamicMeshElements.Trigger(); } void FVisibilityTaskData::SetupMeshPasses(FExclusiveDepthStencil::Type BasePassDepthStencilAccess, FInstanceCullingManager& InstanceCullingManager) { DynamicMeshElements.ContextContainer.MergeContexts(DynamicMeshElements.DynamicPrimitives); { SCOPED_NAMED_EVENT(DynamicRelevance, FColor::Magenta); for (FViewInfo* View : Views) { View->DynamicMeshElementsPassRelevance.SetNum(View->DynamicMeshElements.Num()); } const bool bIsTLVUsingVoxelMarking = IsTranslucencyLightingVolumeUsingVoxelMarking(); for (FDynamicPrimitive DynamicPrimitive : DynamicMeshElements.DynamicPrimitives) { FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene.Primitives[DynamicPrimitive.PrimitiveIndex]; const FPrimitiveBounds& Bounds = Scene.PrimitiveBounds[DynamicPrimitive.PrimitiveIndex]; FViewInfo& View = *Views[DynamicPrimitive.ViewIndex]; const FPrimitiveViewRelevance& ViewRelevance = View.PrimitiveViewRelevanceMap[DynamicPrimitive.PrimitiveIndex]; for (int32 ElementIndex = DynamicPrimitive.StartElementIndex; ElementIndex < DynamicPrimitive.EndElementIndex; ++ElementIndex) { const FMeshBatchAndRelevance& MeshBatch = View.DynamicMeshElements[ElementIndex]; FMeshPassMask& PassRelevance = View.DynamicMeshElementsPassRelevance[ElementIndex]; ComputeDynamicMeshRelevance(ShadingPath, bAddLightmapDensityCommands, bIsTLVUsingVoxelMarking, ViewRelevance, MeshBatch, View, PassRelevance, PrimitiveSceneInfo, Bounds); } } } for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { FViewInfo& View = *Views[ViewIndex]; #if WITH_EDITOR { // Sort the selected Nanite hit proxy IDs Algo::Sort(View.EditorSelectedNaniteHitProxyIds); // Make sure it is power of 2 as it is searched in the shader with binary search that only works with power2 buffer sizes int32 PreviousCount = View.EditorSelectedNaniteHitProxyIds.Num(); int32 NewCount = FMath::RoundUpToPowerOfTwo(PreviousCount); if (PreviousCount > 0) { const uint32 LastValue = View.EditorSelectedNaniteHitProxyIds.Last(); View.EditorSelectedNaniteHitProxyIds.SetNumUninitialized(NewCount); // Pad the end with the last value to ensure still sorted for (int32 Index = PreviousCount; Index < NewCount; ++Index) { View.EditorSelectedNaniteHitProxyIds[Index] = LastValue; } } } #endif #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FSceneViewState* ViewState = static_cast(View.State); // if we are freezing the scene, then remember the primitives that are rendered. if (ViewState && ViewState->bIsFreezing) { for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt) { ViewState->FrozenPrimitives.Add(Scene.PrimitiveComponentIds[BitIt.GetIndex()]); } } #endif if (!View.ShouldRenderView()) { continue; } FViewCommands& ViewCommands = DynamicMeshElements.ViewCommandsPerView[ViewIndex]; #if !UE_BUILD_SHIPPING FViewDebugInfo::Get().ProcessPrimitives(&Scene, View, ViewCommands); #endif SceneRenderer.SetupMeshPass(View, BasePassDepthStencilAccess, ViewCommands, InstanceCullingManager); } } void FVisibilityTaskData::ProcessRenderThreadTasks() { SCOPE_CYCLE_COUNTER(STAT_ViewVisibilityTime); SCOPED_NAMED_EVENT(ProcessVisibilityTasks, FColor::Magenta); UE::Tasks::FTask VirtualTextureTask; StartGatherDynamicMeshElements(); FPrimitiveRange PrimitiveRange; PrimitiveRange.StartIndex = 0u; PrimitiveRange.EndIndex = TaskConfig.NumTestedPrimitives; if (TaskConfig.Schedule == EVisibilityTaskSchedule::RenderThread) { for (FVisibilityViewPacket& ViewPacket : ViewPackets) { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (ViewPacket.ViewState) { ViewPacket.ViewState->ActivateFrozenViewMatrices(ViewPacket.View); } #endif ViewPacket.BeginInitVisibility(); UpdatePrimitiveFading(Scene, ViewPacket.View, ViewPacket.ViewState, PrimitiveRange); } SceneRenderer.WaitOcclusionTests(RHICmdList); for (FVisibilityViewPacket& ViewPacket : ViewPackets) { SCOPED_NAMED_EVENT(OcclusionCull, FColor::Magenta); const int32 NumCulledPrimitives = PrecomputedOcclusionCull(ViewPacket, PrimitiveRange); if (ViewPacket.OcclusionCull.ContextIfSerial) { ViewPacket.OcclusionCull.ContextIfSerial->Map(RHICmdList); ViewPacket.OcclusionCull.ContextIfSerial->AddPrimitives(PrimitiveRange); ViewPacket.OcclusionCull.ContextIfSerial->Unmap(RHICmdList); } if (NumCulledPrimitives > 0) { TaskConfig.OcclusionCull.NumCulledPrimitives.fetch_add(NumCulledPrimitives, std::memory_order_relaxed); } ViewPacket.Tasks.OcclusionCull.Trigger(); } // Relevance requires that cached mesh commands be available first. Scene.WaitForCacheMeshDrawCommandsTask(); for (FVisibilityViewPacket& ViewPacket : ViewPackets) { for (FSceneSetBitIterator BitIt(ViewPacket.View.PrimitiveVisibilityMap, PrimitiveRange.StartIndex); BitIt.GetIndex() < PrimitiveRange.EndIndex; ++BitIt) { ViewPacket.Relevance.Context->AddPrimitive(BitIt.GetIndex()); } ViewPacket.Relevance.Context->Finalize(); ViewPacket.Tasks.ComputeRelevance.Trigger(); } Tasks.bWaitingAllowed = true; check(DynamicMeshElements.PrimitiveViewMasks); GatherDynamicMeshElements(*DynamicMeshElements.PrimitiveViewMasks); #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) for (FVisibilityViewPacket& ViewPacket : ViewPackets) { if (ViewPacket.ViewState) { ViewPacket.ViewState->RestoreUnfrozenViewMatrices(ViewPacket.View); } } #endif } else { if (DynamicMeshElements.CommandPipe) { SCOPED_NAMED_EVENT(WaitForVisibilityTasks, FColor::Magenta); CSV_SCOPED_SET_WAIT_STAT(Visibility); // Wait on the command pipe first as it will be continually updating the render thread event (and process tasks while we wait). Tasks.DynamicMeshElementsPipe->Wait(ENamedThreads::GetRenderThread_Local()); } else { Tasks.ComputeRelevance.Wait(); check(DynamicMeshElements.PrimitiveViewMasks); GatherDynamicMeshElements(*DynamicMeshElements.PrimitiveViewMasks); } for (FVisibilityViewPacket& ViewPacket : ViewPackets) { if (ViewPacket.OcclusionCull.ContextIfParallel) { ViewPacket.OcclusionCull.ContextIfParallel->Unmap(RHICmdList); } } } // Now process all gather dynamic mesh element tasks that were queued up to run on the render thread. if (Tasks.DynamicMeshElementsRenderThread) { Tasks.DynamicMeshElementsRenderThread->Wait(ENamedThreads::GetRenderThread_Local()); } Tasks.LightVisibility.Wait(); Tasks.FinalizeRelevance.Wait(); INC_DWORD_STAT_BY(STAT_ProcessedPrimitives, PrimitiveRange.EndIndex * Views.Num()); INC_DWORD_STAT_BY(STAT_CulledPrimitives, TaskConfig.FrustumCull.NumCulledPrimitives); INC_DWORD_STAT_BY(STAT_OccludedPrimitives, TaskConfig.OcclusionCull.NumCulledPrimitives); TRACE_COUNTER_SET(Scene_Visibility_NumProcessedPrimitives, PrimitiveRange.EndIndex * Views.Num()); TRACE_COUNTER_SET(Scene_Visibility_FrustumCull_NumPrimitivesPerTask, TaskConfig.FrustumCull.NumPrimitivesPerTask); TRACE_COUNTER_SET(Scene_Visibility_FrustumCull_NumCulledPrimitives, TaskConfig.FrustumCull.NumCulledPrimitives); TRACE_COUNTER_SET(Scene_Visibility_OcclusionCull_NumCulledPrimitives, TaskConfig.OcclusionCull.NumCulledPrimitives); TRACE_COUNTER_SET(Scene_Visibility_OcclusionCull_NumTestedQueries, TaskConfig.OcclusionCull.NumTestedQueries); TRACE_COUNTER_SET(Scene_Visibility_Relevance_NumPrimitivesPerPacket, TaskConfig.Relevance.NumPrimitivesPerPacket); } void FVisibilityTaskData::FinishGatherDynamicMeshElements(FExclusiveDepthStencil::Type BasePassDepthStencilAccess, FInstanceCullingManager& InstanceCullingManager, FVirtualTextureUpdater* VirtualTextureUpdater) { check(IsInRenderingThread()); SCOPED_NAMED_EVENT(FinishDynamicMeshElements, FColor::Magenta); FVirtualTextureSystem::Get().WaitForTasks(VirtualTextureUpdater); Tasks.DynamicMeshElements.Wait(); DynamicMeshElements.ContextContainer.Submit(RHICmdList); Tasks.MeshPassSetup = UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, BasePassDepthStencilAccess, &InstanceCullingManager] { FTaskTagScope Scope(ETaskTag::EParallelRenderingThread); SetupMeshPasses(BasePassDepthStencilAccess, InstanceCullingManager); }, TaskConfig.TaskPriority); FSceneRenderer::DynamicReadBufferForInitViews.Commit(RHICmdList); } void FVisibilityTaskData::Finish() { SCOPED_NAMED_EVENT(FinishVisibility, FColor::Magenta); Tasks.ComputeRelevance.Wait(); Tasks.FinalizeRelevance.Wait(); Tasks.DynamicMeshElements.Wait(); Tasks.MeshPassSetup.Wait(); ViewPackets.Empty(); DynamicMeshElements.DynamicPrimitives.Empty(); Allocator.BulkDelete(); bFinished = true; } /////////////////////////////////////////////////////////////////////////////// /** * Helper for InitViews to detect large camera movement, in both angle and position. */ static bool IsLargeCameraMovement(FSceneView& View, const FMatrix& PrevViewMatrix, const FVector& PrevViewOrigin, float CameraRotationThreshold, float CameraTranslationThreshold) { float RotationThreshold = FMath::Cos(FMath::DegreesToRadians(CameraRotationThreshold)); float ViewRightAngle = View.ViewMatrices.GetViewMatrix().GetColumn(0) | PrevViewMatrix.GetColumn(0); float ViewUpAngle = View.ViewMatrices.GetViewMatrix().GetColumn(1) | PrevViewMatrix.GetColumn(1); float ViewDirectionAngle = View.ViewMatrices.GetViewMatrix().GetColumn(2) | PrevViewMatrix.GetColumn(2); FVector Distance = FVector(View.ViewMatrices.GetViewOrigin()) - PrevViewOrigin; return ViewRightAngle < RotationThreshold || ViewUpAngle < RotationThreshold || ViewDirectionAngle < RotationThreshold || Distance.SizeSquared() > CameraTranslationThreshold * CameraTranslationThreshold; } void FSceneRenderer::PreVisibilityFrameSetup(FRDGBuilder& GraphBuilder) { // Possible stencil dither optimization approach for (FViewInfo& View : Views) { View.bAllowStencilDither = DepthPass.bDitheredLODTransitionsUseStencil; } // Initialize InstanceFactor. AllViews includes CustomRenderPass views, which might possibly also require InstanceFactor to be correctly assigned. const int32 InstanceFactor = FMath::Max(1, GEngine->StereoRenderingDevice.IsValid() ? GEngine->StereoRenderingDevice->GetDesiredNumberOfViews(true) : 1); if (InstanceFactor > 1) { for (FViewInfo* View : AllViews) { if (View->bIsInstancedStereoEnabled && IStereoRendering::IsStereoEyeView(*View)) { View->InstanceFactor = InstanceFactor; } } } FRHICommandListImmediate& RHICmdList = GraphBuilder.RHICmdList; if (GetRendererOutput() == ERendererOutput::FinalSceneColor) { FHairStrandsBookmarkParameters Parameters; bool bHasHairStrandsBookmarks = false; if (Views.Num() > 0 && !ViewFamily.EngineShowFlags.HitProxies) { CreateHairStrandsBookmarkParameters(Scene, Views, AllViews, Parameters, false /*bComputeVisibleInstances*/); Parameters.TransientResources = AllocateHairTransientResources(GraphBuilder, Scene, Views); if (Parameters.HasInstances()) { if (Scene && IsHairStrandsEnabled(EHairStrandsShaderType::All, Scene->GetShaderPlatform())) { // Need GPUSkinCache results to be available in order to get correct skel. mesh data Scene->GetGPUSkinCacheTask().Wait(); // 1.Update binding surfaces // Prepare surface data (MeshLODData) RunHairStrandsBookmark(GraphBuilder, EHairStrandsBookmark::ProcessBindingSurfaceUpdate, Parameters); // 2. Prepare surface data for guides if (Views[0].AllowGPUParticleUpdate()) { RunHairStrandsBookmark(GraphBuilder, EHairStrandsBookmark::ProcessGuideInterpolation, Parameters); bHasHairStrandsBookmarks = true; } } } } if (Scene) { for (IComputeTaskWorker* ComputeTaskWorker : Scene->ComputeTaskWorkers) { if (ComputeTaskWorker->HasWork(ComputeTaskExecutionGroup::BeginInitViews)) { FComputeContext Context { .GraphBuilder = GraphBuilder, .ExecutionGroupName = ComputeTaskExecutionGroup::BeginInitViews, .FeatureLevel = FeatureLevel, .Scene = Scene, }; ComputeTaskWorker->SubmitWork(Context); } } } // Notify the FX system that the scene is about to perform visibility checks. if (FXSystem && Views.IsValidIndex(0)) { TArray> AllLinkedFamilies; EnumerateLinkedViewFamilies([&] (const FSceneViewFamily& Family) { AllLinkedFamilies.Emplace(&Family); return true; }); FXSystem->PreInitViews(GraphBuilder, Views[0].AllowGPUParticleUpdate() && !ViewFamily.EngineShowFlags.HitProxies, AllLinkedFamilies, &ViewFamily); } if(bHasHairStrandsBookmarks) { RunHairStrandsBookmark(GraphBuilder, EHairStrandsBookmark::ProcessGuideDeformation, Parameters); } #if WITH_EDITOR // Draw lines to lights affecting this mesh if its selected. if (ViewFamily.EngineShowFlags.LightInfluences && Scene) { Scene->WaitForCreateLightPrimitiveInteractionsTask(); for (TConstSetBitIterator<> It(Scene->PrimitivesSelected); It; ++It) { const FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene->Primitives[It.GetIndex()]; TArray RelevantLights; Scene->GetRelevantLights_RenderThread(PrimitiveSceneInfo->Proxy, RelevantLights); for (const FLightSceneProxy* LightSceneProxy : RelevantLights) { bool bDynamic = true; bool bRelevant = false; bool bLightMapped = true; bool bShadowMapped = false; PrimitiveSceneInfo->Proxy->GetLightRelevance(LightSceneProxy, bDynamic, bRelevant, bLightMapped, bShadowMapped); if (bRelevant) { // Draw blue for light-mapped lights and orange for dynamic lights const FColor LineColor = bLightMapped ? FColor(0, 140, 255) : FColor(255, 140, 0); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { FViewInfo& View = Views[ViewIndex]; FViewElementPDI LightInfluencesPDI(&View, nullptr, &View.DynamicPrimitiveCollector); LightInfluencesPDI.DrawLine(PrimitiveSceneInfo->Proxy->GetBounds().Origin, LightSceneProxy->GetLightToWorld().GetOrigin(), LineColor, SDPG_World); } } } } } #endif #if RHI_RAYTRACING if (Scene) { for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { FViewInfo& View = Views[ViewIndex]; View.bRayTracingFeedbackEnabled = Scene->RayTracingScene.IsRayTracingFeedbackEnabled(); } } #endif } for (const auto& ViewExtension : ViewFamily.ViewExtensions) { ViewExtension->PreInitViews_RenderThread(GraphBuilder); } } void FSceneRenderer::PrepareViewStateForVisibility(const FSceneTexturesConfig& SceneTexturesConfig) { TRACE_CPUPROFILER_EVENT_SCOPE(PrepareViewStateForVisibility); #if UE_BUILD_SHIPPING const bool bFreezeTemporalHistories = false; const bool bFreezeTemporalSequences = false; #else bool bFreezeTemporalHistories = CVarFreezeTemporalHistories.GetValueOnRenderThread() != 0; static int32 CurrentFreezeTemporalHistoriesProgress = 0; if (CurrentFreezeTemporalHistoriesProgress != CVarFreezeTemporalHistoriesProgress.GetValueOnRenderThread()) { bFreezeTemporalHistories = false; CurrentFreezeTemporalHistoriesProgress = CVarFreezeTemporalHistoriesProgress.GetValueOnRenderThread(); } bool bFreezeTemporalSequences = bFreezeTemporalHistories || CVarFreezeTemporalSequences.GetValueOnRenderThread() != 0; #endif // Load this field once so it has a consistent value for all views (and to avoid the atomic load in the loop). // While the value may not be perfectly in sync when we render other view families, this is ok as this // invalidation mechanism is only used for interactive rendering where we expect to be constantly drawing the scene. // Therefore it is acceptable for some view families to be a frame or so behind others. uint32 CurrentPathTracingInvalidationCounter = Scene->PathTracingInvalidationCounter.Load(); // Setup motion blur parameters (also check for camera movement thresholds) for(int32 ViewIndex = 0;ViewIndex < AllViews.Num();ViewIndex++) { FViewInfo& View = *AllViews[ViewIndex]; FSceneViewState* ViewState = View.ViewState; #if DO_CHECK || USING_CODE_ANALYSIS check(View.VerifyMembersChecks()); #endif // Setup global dither fade in and fade out uniform buffers. { FDitherUniformShaderParameters DitherUniformShaderParameters; DitherUniformShaderParameters.LODFactor = View.GetTemporalLODTransition(); View.DitherFadeOutUniformBuffer = FDitherUniformBufferRef::CreateUniformBufferImmediate(DitherUniformShaderParameters, UniformBuffer_SingleFrame); DitherUniformShaderParameters.LODFactor = View.GetTemporalLODTransition() - 1.0f; View.DitherFadeInUniformBuffer = FDitherUniformBufferRef::CreateUniformBufferImmediate(DitherUniformShaderParameters, UniformBuffer_SingleFrame); } // Once per render increment the occlusion frame counter. if (ViewState) { ViewState->OcclusionFrameCounter++; } // HighResScreenshot should get best results so we don't do the occlusion optimization based on the former frame extern bool GIsHighResScreenshot; const bool bIsHitTesting = ViewFamily.EngineShowFlags.HitProxies; // Don't test occlusion queries in collision viewmode as they can be bigger then the rendering bounds. const bool bCollisionView = ViewFamily.EngineShowFlags.CollisionVisibility || ViewFamily.EngineShowFlags.CollisionPawn; if (GIsHighResScreenshot || !DoOcclusionQueries() || bIsHitTesting || bCollisionView) { View.bDisableQuerySubmissions = true; View.bIgnoreExistingQueries = true; } // set up the screen area for occlusion { float OcclusionPixelMultiplier = 1.0f; if (UseDownsampledOcclusionQueries()) { OcclusionPixelMultiplier = 1.0f / static_cast(FMath::Square(SceneTexturesConfig.SmallDepthDownsampleFactor)); } float NumPossiblePixels = static_cast(View.ViewRect.Width() * View.ViewRect.Height()) * OcclusionPixelMultiplier; View.OneOverNumPossiblePixels = NumPossiblePixels > 0.0 ? 1.0f / NumPossiblePixels : 0.0f; } // Still need no jitter to be set for temporal feedback on SSR (it is enabled even when temporal AA is off). check(View.TemporalJitterPixels.X == 0.0f); check(View.TemporalJitterPixels.Y == 0.0f); // Cache the projection matrix b // Cache the projection matrix before AA is applied View.ViewMatrices.SaveProjectionNoAAMatrix(); if (ViewState) { check(View.bStatePrevViewInfoIsReadOnly); View.bStatePrevViewInfoIsReadOnly = ViewFamily.bWorldIsPaused || ViewFamily.EngineShowFlags.HitProxies || bFreezeTemporalHistories; ViewState->SetupDistanceFieldTemporalOffset(ViewFamily); if (!View.bStatePrevViewInfoIsReadOnly && !bFreezeTemporalSequences) { ViewState->FrameIndex++; } if (View.OverrideFrameIndexValue.IsSet()) { ViewState->FrameIndex = View.OverrideFrameIndexValue.GetValue(); } if (View.OverrideOutputFrameIndexValue.IsSet()) { ViewState->OutputFrameIndex = View.OverrideOutputFrameIndexValue.GetValue(); } else { // If the output frame index isn't being overwritten then we keep it in sync with // FrameIndex so that downstream systems are unchanged. ViewState->OutputFrameIndex = ViewState->FrameIndex; } } // Subpixel jitter for temporal AA int32 CVarTemporalAASamplesValue = CVarTemporalAASamples.GetValueOnRenderThread(); const bool bShouldScaleTemporalAASampleCount = (CVarTemporalAAScaleSamples.GetValueOnRenderThread() != 0); // Custom render passes support sharing temporal state with the main view FViewInfo& TemporalSourceView = View.TemporalSourceView ? *View.TemporalSourceView : View; FSceneViewState* TemporalViewState = TemporalSourceView.ViewState; EMainTAAPassConfig TAAConfig = GetMainTAAPassConfig(TemporalSourceView); bool bTemporalUpsampling = TemporalSourceView.PrimaryScreenPercentageMethod == EPrimaryScreenPercentageMethod::TemporalUpscale; // Apply a sub pixel offset to the view. if (IsTemporalAccumulationBasedMethod(TemporalSourceView.AntiAliasingMethod) && TemporalViewState && (CVarTemporalAASamplesValue > 0 || bTemporalUpsampling) && TemporalSourceView.bAllowTemporalJitter) { float EffectivePrimaryResolutionFraction = float(View.ViewRect.Width()) / float(TemporalSourceView.GetSecondaryViewRectSize().X); // Compute number of TAA samples. int32 TemporalAASamples; { if (TAAConfig == EMainTAAPassConfig::TSR) { // Force the number of AA sample to make sure the quality doesn't get // compromised by previously set settings for Gen4 TAA TemporalAASamples = 8; } else { TemporalAASamples = FMath::Clamp(CVarTemporalAASamplesValue, 1, 255); } if (bTemporalUpsampling && bShouldScaleTemporalAASampleCount) { // When doing TAA upsample with screen percentage < 100%, we need extra temporal samples to have a // constant temporal sample density for final output pixels to avoid output pixel aligned converging issues. TemporalAASamples = FMath::RoundToInt(float(TemporalAASamples) * FMath::Max(1.f, 1.f / (EffectivePrimaryResolutionFraction * EffectivePrimaryResolutionFraction))); } else if (CVarTemporalAASamplesValue == 5) { TemporalAASamples = 4; } // Use immediately higher prime number to break up coherence between the TAA jitter sequence and any // other random signal that are power of two of View.StateFrameIndex if (TAAConfig == EMainTAAPassConfig::TSR) { static const uint8 kFirstPrimeNumbers[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, }; for (int32 PrimeNumberId = FMath::Max(4, (TemporalAASamples - 1) / 5); PrimeNumberId < UE_ARRAY_COUNT(kFirstPrimeNumbers); PrimeNumberId++) { if (int32(kFirstPrimeNumbers[PrimeNumberId]) >= TemporalAASamples) { TemporalAASamples = int32(kFirstPrimeNumbers[PrimeNumberId]); break; } } } } // Compute the new sample index in the temporal sequence. int32 TemporalSampleIndex; if (View.TemporalSourceView) { // Where a TemporalSourceView is specified, the temporal sample index is pulled directly from the source view's state, and not incremented, // assuming the source view already will have updated that. TemporalSampleIndex = TemporalViewState->TemporalAASampleIndex; } else { TemporalSampleIndex = TemporalViewState->TemporalAASampleIndex + 1; if (TemporalSampleIndex >= TemporalAASamples || View.bCameraCut) { TemporalSampleIndex = 0; } #if !UE_BUILD_SHIPPING if (CVarTAADebugOverrideTemporalIndex.GetValueOnRenderThread() >= 0) { TemporalSampleIndex = CVarTAADebugOverrideTemporalIndex.GetValueOnRenderThread(); } #endif // Updates view state. if (!View.bStatePrevViewInfoIsReadOnly && !bFreezeTemporalSequences) { TemporalViewState->TemporalAASampleIndex = TemporalSampleIndex; } } // Choose sub pixel sample coordinate in the temporal sequence. float SampleX = 0.0f, SampleY = 0.0f; if (TemporalSourceView.PrimaryScreenPercentageMethod == EPrimaryScreenPercentageMethod::TemporalUpscale) { // Uniformly distribute temporal jittering in [-.5; .5], because there is no longer any alignement of input and output pixels. SampleX = Halton(TemporalSampleIndex + 1, 2) - 0.5f; SampleY = Halton(TemporalSampleIndex + 1, 3) - 0.5f; // Adjust Automatic View Mip Bias float MipBias = -(FMath::Max(-FMath::Log2(EffectivePrimaryResolutionFraction), 0.0f)) + CVarAutomaticViewMipBiasOffset.GetValueOnRenderThread(); MipBias = FMath::Clamp(FMath::Max(MipBias, CVarAutomaticViewMipBiasMin.GetValueOnRenderThread()), -4.0f, 4.0f); // Quantize to discrete values between -4.0f and 4.0f (range of 8.0f), since each value creates a unique sampler state const int32 MipBiasQuantization = CVarAutomaticViewMipBiasQuantization.GetValueOnRenderThread(); if (MipBiasQuantization > 0) { // Divide the 8.0f range by the number of desired steps to get the step size const float QuantizationStepSize = 8.0f / ((float)MipBiasQuantization); MipBias = FMath::CeilToFloat(MipBias / QuantizationStepSize) * QuantizationStepSize; } View.MaterialTextureMipBias = MipBias; } else if( CVarTemporalAASamplesValue == 2 ) { // 2xMSAA // Pattern docs: http://msdn.microsoft.com/en-us/library/windows/desktop/ff476218(v=vs.85).aspx // N. // .S float SamplesX[] = { -4.0f/16.0f, 4.0/16.0f }; float SamplesY[] = { -4.0f/16.0f, 4.0/16.0f }; check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX)); SampleX = SamplesX[ TemporalSampleIndex ]; SampleY = SamplesY[ TemporalSampleIndex ]; } else if( CVarTemporalAASamplesValue == 3 ) { // 3xMSAA // A.. // ..B // .C. // Rolling circle pattern (A,B,C). float SamplesX[] = { -2.0f/3.0f, 2.0/3.0f, 0.0/3.0f }; float SamplesY[] = { -2.0f/3.0f, 0.0/3.0f, 2.0/3.0f }; check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX)); SampleX = SamplesX[ TemporalSampleIndex ]; SampleY = SamplesY[ TemporalSampleIndex ]; } else if( CVarTemporalAASamplesValue == 4 ) { // 4xMSAA // Pattern docs: http://msdn.microsoft.com/en-us/library/windows/desktop/ff476218(v=vs.85).aspx // .N.. // ...E // W... // ..S. // Rolling circle pattern (N,E,S,W). float SamplesX[] = { -2.0f/16.0f, 6.0/16.0f, 2.0/16.0f, -6.0/16.0f }; float SamplesY[] = { -6.0f/16.0f, -2.0/16.0f, 6.0/16.0f, 2.0/16.0f }; check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX)); SampleX = SamplesX[ TemporalSampleIndex ]; SampleY = SamplesY[ TemporalSampleIndex ]; } else if( CVarTemporalAASamplesValue == 5 ) { // Compressed 4 sample pattern on same vertical and horizontal line (less temporal flicker). // Compressed 1/2 works better than correct 2/3 (reduced temporal flicker). // . N . // W . E // . S . // Rolling circle pattern (N,E,S,W). float SamplesX[] = { 0.0f/2.0f, 1.0/2.0f, 0.0/2.0f, -1.0/2.0f }; float SamplesY[] = { -1.0f/2.0f, 0.0/2.0f, 1.0/2.0f, 0.0/2.0f }; check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX)); SampleX = SamplesX[ TemporalSampleIndex ]; SampleY = SamplesY[ TemporalSampleIndex ]; } else if(View.IsPerspectiveProjection()) { float u1 = Halton( TemporalSampleIndex + 1, 2 ); float u2 = Halton( TemporalSampleIndex + 1, 3 ); // Generates samples in normal distribution // exp( x^2 / Sigma^2 ) static auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.TemporalAAFilterSize")); float FilterSize = CVar->GetFloat(); // Scale distribution to set non-unit variance // Variance = Sigma^2 float Sigma = 0.47f * FilterSize; // Window to [-0.5, 0.5] output // Without windowing we could generate samples far away on the infinite tails. float OutWindow = 0.5f; float InWindow = FMath::Exp( -0.5 * FMath::Square( OutWindow / Sigma ) ); // Box-Muller transform float Theta = 2.0f * PI * u2; float r = Sigma * FMath::Sqrt( -2.0f * FMath::Loge( (1.0f - u1) * InWindow + u1 ) ); SampleX = r * FMath::Cos( Theta ); SampleY = r * FMath::Sin( Theta ); } if (CVarInvertTemporalJitterX.GetValueOnRenderThread() != 0) { SampleX = -SampleX; } if (CVarInvertTemporalJitterY.GetValueOnRenderThread() != 0) { SampleY = -SampleY; } View.TemporalJitterSequenceLength = TemporalAASamples; View.TemporalJitterIndex = TemporalSampleIndex; View.TemporalJitterPixels.X = SampleX; View.TemporalJitterPixels.Y = SampleY; View.ViewMatrices.HackAddTemporalAAProjectionJitter(FVector2D(SampleX * 2.0f / View.ViewRect.Width(), SampleY * -2.0f / View.ViewRect.Height())); } // Setup a new FPreviousViewInfo from current frame infos. FPreviousViewInfo NewPrevViewInfo; { NewPrevViewInfo.ViewRect = View.ViewRect; NewPrevViewInfo.ViewMatrices = View.ViewMatrices; } if ( ViewState ) { // update previous frame matrices in case world origin was rebased on this frame if (!View.OriginOffsetThisFrame.IsZero()) { ViewState->PrevFrameViewInfo.ViewMatrices.ApplyWorldOffset(View.OriginOffsetThisFrame); } // determine if we are initializing or we should reset the persistent state const float DeltaTime = View.Family->Time.GetRealTimeSeconds() - ViewState->LastRenderTime; const bool bFirstFrameOrTimeWasReset = DeltaTime < -0.0001f || ViewState->LastRenderTime < 0.0001f; const bool bIsLargeCameraMovement = IsLargeCameraMovement( View, ViewState->PrevFrameViewInfo.ViewMatrices.GetViewMatrix(), ViewState->PrevFrameViewInfo.ViewMatrices.GetViewOrigin(), 75.0f, GCameraCutTranslationThreshold); const bool bResetCamera = (bFirstFrameOrTimeWasReset || View.bCameraCut || bIsLargeCameraMovement || View.bForceCameraVisibilityReset); #if RHI_RAYTRACING static const auto CVarTemporalDenoiser = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.PathTracing.TemporalDenoiser.mode")); const int TemporalDenoiserMode = CVarTemporalDenoiser ? CVarTemporalDenoiser->GetValueOnAnyThread() : 0; if (View.bIsOfflineRender) { // In the offline context, we want precise control over when to restart the path tracer's accumulation to allow for motion blur // So we use the camera cut signal only. In particular - we should not use bForceCameraVisibilityReset since this has // interactions with the motion blur post process effect in tiled rendering (see comment below). if (View.bCameraCut || View.bForcePathTracerReset) { const bool bClearTemporalDenoisingHistory = (TemporalDenoiserMode == 1) ? View.bCameraCut : true; ViewState->PathTracingInvalidate(bClearTemporalDenoisingHistory); } } else { // for interactive usage - any movement or scene change should restart the path tracer // Note: 0.18 deg is the minimum angle for avoiding numerical precision issue (which would cause constant invalidation) const bool bIsCameraMove = IsLargeCameraMovement( View, ViewState->PrevFrameViewInfo.ViewMatrices.GetViewMatrix(), ViewState->PrevFrameViewInfo.ViewMatrices.GetViewOrigin(), 0.18f /*degree*/, 0.1f /*cm*/); const bool bIsProjMatrixDifferent = View.ViewMatrices.GetProjectionNoAAMatrix() != View.ViewState->PrevFrameViewInfo.ViewMatrices.GetProjectionNoAAMatrix(); // For each view, we remember what the invalidation counter was set to last time we were here so we can catch all changes const bool bNeedsInvalidation = ViewState->PathTracingInvalidationCounter != CurrentPathTracingInvalidationCounter; ViewState->PathTracingInvalidationCounter = CurrentPathTracingInvalidationCounter; if (bNeedsInvalidation || bIsProjMatrixDifferent || bIsCameraMove || View.bCameraCut || View.bForceCameraVisibilityReset || View.bForcePathTracerReset) { const bool bClearTemporalDenoisingHistory = (TemporalDenoiserMode == 2) ? View.bCameraCut : true; ViewState->PathTracingInvalidate(bClearTemporalDenoisingHistory); } } #endif // RHI_RAYTRACING if (bResetCamera) { View.PrevViewInfo = NewPrevViewInfo; // PT: If the motion blur shader is the last shader in the post-processing chain then it is the one that is // adjusting for the viewport offset. So it is always required and we can't just disable the work the // shader does. The correct fix would be to disable the effect when we don't need it and to properly mark // the uber-postprocessing effect as the last effect in the chain. View.bPrevTransformsReset = true; } else { View.PrevViewInfo = ViewState->PrevFrameViewInfo; } // Replace previous view info of the view state with this frame, clearing out references over render target. if (!View.bStatePrevViewInfoIsReadOnly) { ViewState->PrevFrameViewInfo = NewPrevViewInfo; } // If the view has a previous view transform, then overwrite the previous view info for the _current_ frame. if (View.PreviousViewTransform.IsSet()) { // Note that we must ensure this transform ends up in ViewState->PrevFrameViewInfo else it will be used to calculate the next frame's motion vectors as well View.PrevViewInfo.ViewMatrices.UpdateViewMatrix(View.PreviousViewTransform->GetTranslation(), View.PreviousViewTransform->GetRotation().Rotator()); } // detect conditions where we should reset occlusion queries if (bFirstFrameOrTimeWasReset || ViewState->LastRenderTime + GEngine->PrimitiveProbablyVisibleTime < View.Family->Time.GetRealTimeSeconds() || View.bCameraCut || View.bForceCameraVisibilityReset || IsLargeCameraMovement( View, FMatrix(ViewState->PrevViewMatrixForOcclusionQuery), ViewState->PrevViewOriginForOcclusionQuery, GEngine->CameraRotationThreshold, GEngine->CameraTranslationThreshold)) { View.bIgnoreExistingQueries = true; View.bDisableDistanceBasedFadeTransitions = true; } // Turn on/off round-robin occlusion querying in the ViewState static const auto CVarRROCC = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.RoundRobinOcclusion")); const bool bEnableRoundRobin = CVarRROCC ? (CVarRROCC->GetValueOnAnyThread() != false) : false; if (bEnableRoundRobin != ViewState->IsRoundRobinEnabled()) { ViewState->UpdateRoundRobin(bEnableRoundRobin); View.bIgnoreExistingQueries = true; } ViewState->PrevViewMatrixForOcclusionQuery = FMatrix44f(View.ViewMatrices.GetViewMatrix()); // LWC_TODO: Precision loss ViewState->PrevViewOriginForOcclusionQuery = View.ViewMatrices.GetViewOrigin(); // we don't use DeltaTime as it can be 0 (in editor) and is computed by subtracting floats (loses precision over time) // Clamp DeltaWorldTime to reasonable values for the purposes of motion blur, things like TimeDilation can make it very small // Offline renders always control the timestep for the view and always need the timescales calculated. if (!ViewFamily.bWorldIsPaused || View.bIsOfflineRender) { ViewState->UpdateMotionBlurTimeScale(View); } ViewState->PrevFrameNumber = ViewState->PendingPrevFrameNumber; ViewState->PendingPrevFrameNumber = View.Family->FrameNumber; // This finishes the update of view state ViewState->UpdateLastRenderTime(*View.Family); ViewState->UpdateTemporalLODTransition(View); } else { // Without a viewstate, we just assume that camera has not moved. View.PrevViewInfo = NewPrevViewInfo; } } } void FSceneViewState::UpdateMotionBlurTimeScale(const FViewInfo& View) { const int32 MotionBlurTargetFPS = View.FinalPostProcessSettings.MotionBlurTargetFPS; // Ensure we can divide by the Delta Time later without a divide by zero. float DeltaRealTime = FMath::Max(View.Family->Time.GetDeltaRealTimeSeconds(), SMALL_NUMBER); // Track the current FPS by using an exponential moving average of the current delta time. if (MotionBlurTargetFPS <= 0) { // Keep motion vector lengths stable for paused sequencer frames. if (GetSequencerState() == ESS_Paused) { // Reset the moving average to the current delta time. MotionBlurTargetDeltaTime = DeltaRealTime; } else { // Smooth the target delta time using a moving average. MotionBlurTargetDeltaTime = FMath::Lerp(MotionBlurTargetDeltaTime, DeltaRealTime, 0.1f); } } else // Track a fixed target FPS. { // Keep motion vector lengths stable for paused sequencer frames. Assumes a 60 FPS tick. // Tuned for content compatibility with existing content when target is the default 30 FPS. if (GetSequencerState() == ESS_Paused) { DeltaRealTime = 1.0f / 60.0f; } MotionBlurTargetDeltaTime = 1.0f / static_cast(MotionBlurTargetFPS); } MotionBlurTimeScale = MotionBlurTargetDeltaTime / DeltaRealTime; } void FDeferredShadingSceneRenderer::ComputeLightVisibility() { FSceneRenderer::ComputeLightVisibility(); CreateIndirectCapsuleShadows(); SetupVolumetricFog(); } void FSceneRenderer::ComputeLightVisibility() { QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_Light_Visibility); VisibleLightInfos.AddDefaulted(Scene->Lights.GetMaxIndex()); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { FViewInfo& View = Views[ViewIndex]; View.VisibleLightInfos.Empty(Scene->Lights.GetMaxIndex()); for (int32 LightIndex = 0; LightIndex < Scene->Lights.GetMaxIndex(); LightIndex++) { new (View.VisibleLightInfos) FVisibleLightViewInfo(); } } const bool bSetupMobileLightShafts = FeatureLevel <= ERHIFeatureLevel::ES3_1 && ShouldRenderLightShafts(ViewFamily); // determine visibility of each light for(auto LightIt = Scene->Lights.CreateConstIterator();LightIt;++LightIt) { const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt; const FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo; // view frustum cull lights in each view for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++) { const FLightSceneProxy* Proxy = LightSceneInfo->Proxy; FViewInfo& View = Views[ViewIndex]; FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()]; // dir lights are always visible, and point/spot only if in the frustum if( Proxy->GetLightType() == LightType_Point || Proxy->GetLightType() == LightType_Spot || Proxy->GetLightType() == LightType_Rect ) { const FSphere& BoundingSphere = Proxy->GetBoundingSphere(); const bool bInViewFrustum = View.GetCullingFrustum().IntersectSphere(BoundingSphere.Center, BoundingSphere.W); if (View.IsPerspectiveProjection()) { const float DistanceSquared = (BoundingSphere.Center - View.CullingOrigin).SizeSquared(); const float ProxyMaxDistance = Proxy->GetMaxDrawDistance(); const float ScaledMaxDistance = ProxyMaxDistance * GLightMaxDrawDistanceScale; const bool bDrawLight = (FMath::Square(FMath::Min(0.0002f, GMinScreenRadiusForLights / BoundingSphere.W) * View.LODDistanceFactor) * DistanceSquared < 1.0f) && (ProxyMaxDistance <= 0.0 || DistanceSquared < FMath::Square(ScaledMaxDistance)); VisibleLightViewInfo.bInViewFrustum = bDrawLight && bInViewFrustum; VisibleLightViewInfo.bInDrawRange = bDrawLight; } else { VisibleLightViewInfo.bInViewFrustum = bInViewFrustum; VisibleLightViewInfo.bInDrawRange = true; } } else { VisibleLightViewInfo.bInViewFrustum = true; VisibleLightViewInfo.bInDrawRange = true; // Setup single sun-shaft from direction lights for mobile. if (bSetupMobileLightShafts && LightSceneInfo->bEnableLightShaftBloom && ShouldRenderLightShaftsForLight(View, *LightSceneInfo->Proxy)) { View.MobileLightShaft = GetMobileLightShaftInfo(View, *LightSceneInfo); } } // Draw shapes for reflection captures if( View.bIsReflectionCapture && VisibleLightViewInfo.bInViewFrustum && Proxy->HasStaticLighting() && Proxy->GetLightType() != LightType_Directional ) { FVector Origin = Proxy->GetOrigin(); FVector ToLight = Origin - View.ViewMatrices.GetViewOrigin(); float DistanceSqr = ToLight | ToLight; float Radius = Proxy->GetRadius(); if( DistanceSqr < Radius * Radius ) { View.VisibleReflectionCaptureLights.Emplace(Proxy); } } } } InitFogConstants(); } void FSceneRenderer::GatherReflectionCaptureLightMeshElements() { // view frustum cull lights in each view for (FViewInfo& View : Views) { for (const FLightSceneProxy* Proxy : View.VisibleReflectionCaptureLights) { FVector Origin = Proxy->GetOrigin(); FVector ToLight = Origin - View.ViewMatrices.GetViewOrigin(); float DistanceSqr = ToLight | ToLight; float Radius = Proxy->GetRadius(); FLightRenderParameters LightParameters; Proxy->GetLightShaderParameters(LightParameters); // Force to be at least 0.75 pixels float CubemapSize = (float)IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.ReflectionCaptureResolution"))->GetValueOnAnyThread(); float Distance = FMath::Sqrt(DistanceSqr); float MinRadius = Distance * 0.75f / CubemapSize; LightParameters.SourceRadius = FMath::Max(MinRadius, LightParameters.SourceRadius); // Snap to cubemap pixel center to reduce aliasing FVector Scale = ToLight.GetAbs(); int32 MaxComponent = Scale.X > Scale.Y ? (Scale.X > Scale.Z ? 0 : 2) : (Scale.Y > Scale.Z ? 1 : 2); for (int32 k = 1; k < 3; k++) { float Projected = ToLight[(MaxComponent + k) % 3] / Scale[MaxComponent]; float Quantized = (FMath::RoundToFloat(Projected * (0.5f * CubemapSize) - 0.5f) + 0.5f) / (0.5f * CubemapSize); ToLight[(MaxComponent + k) % 3] = Quantized * Scale[MaxComponent]; } Origin = ToLight + View.ViewMatrices.GetViewOrigin(); FLinearColor Color(LightParameters.Color.R, LightParameters.Color.G, LightParameters.Color.B, LightParameters.FalloffExponent); const bool bIsRectLight = Proxy->IsRectLight(); if (!bIsRectLight) { const float SphereArea = (4.0f * PI) * FMath::Square(LightParameters.SourceRadius); const float CylinderArea = (2.0f * PI) * LightParameters.SourceRadius * LightParameters.SourceLength; const float SurfaceArea = SphereArea + CylinderArea; Color *= 4.0f / SurfaceArea; } if (Proxy->IsInverseSquared()) { float LightRadiusMask = FMath::Square(1.0f - FMath::Square(DistanceSqr * FMath::Square(LightParameters.InvRadius))); Color.A = LightRadiusMask; } else { // Remove inverse square falloff Color *= DistanceSqr + 1.0f; // Apply falloff Color.A = FMath::Pow(1.0f - DistanceSqr * FMath::Square(LightParameters.InvRadius), LightParameters.FalloffExponent); } // Spot falloff FVector L = ToLight.GetSafeNormal(); Color.A *= FMath::Square(FMath::Clamp(((L | (FVector)LightParameters.Direction) - LightParameters.SpotAngles.X) * LightParameters.SpotAngles.Y, 0.0f, 1.0f)); Color.A *= LightParameters.SpecularScale; // Rect is one sided if (bIsRectLight && (L | (FVector)LightParameters.Direction) < 0.0f) continue; UTexture* SurfaceTexture = nullptr; if (bIsRectLight) { const FRectLightSceneProxy* RectLightProxy = (const FRectLightSceneProxy*)Proxy; SurfaceTexture = RectLightProxy->SourceTexture; } FMaterialRenderProxy* ColoredMeshInstance = nullptr; if (SurfaceTexture) { ColoredMeshInstance = Allocator.Create(GEngine->EmissiveMeshMaterial->GetRenderProxy(), Color, NAME_Color, SurfaceTexture, NAME_LinearColor); } else { ColoredMeshInstance = Allocator.Create(GEngine->EmissiveMeshMaterial->GetRenderProxy(), Color, NAME_Color); } FMatrix LightToWorld = Proxy->GetLightToWorld(); LightToWorld.RemoveScaling(); FViewElementPDI LightPDI(&View, NULL, &View.DynamicPrimitiveCollector); if (bIsRectLight) { DrawBox(&LightPDI, LightToWorld, FVector(0.0f, LightParameters.SourceRadius, LightParameters.SourceLength), ColoredMeshInstance, SDPG_World); } else if (LightParameters.SourceLength > 0.0f) { DrawSphere(&LightPDI, Origin + 0.5f * LightParameters.SourceLength * LightToWorld.GetUnitAxis(EAxis::Z), FRotator::ZeroRotator, LightParameters.SourceRadius * FVector::OneVector, 36, 24, ColoredMeshInstance, SDPG_World); DrawSphere(&LightPDI, Origin - 0.5f * LightParameters.SourceLength * LightToWorld.GetUnitAxis(EAxis::Z), FRotator::ZeroRotator, LightParameters.SourceRadius * FVector::OneVector, 36, 24, ColoredMeshInstance, SDPG_World); DrawCylinder(&LightPDI, Origin, LightToWorld.GetUnitAxis(EAxis::X), LightToWorld.GetUnitAxis(EAxis::Y), LightToWorld.GetUnitAxis(EAxis::Z), LightParameters.SourceRadius, 0.5f * LightParameters.SourceLength, 36, ColoredMeshInstance, SDPG_World); } else { DrawSphere(&LightPDI, Origin, FRotator::ZeroRotator, LightParameters.SourceRadius * FVector::OneVector, 36, 24, ColoredMeshInstance, SDPG_World); } } View.VisibleReflectionCaptureLights.Empty(); } } void FSceneRenderer::PostVisibilityFrameSetup(FILCUpdatePrimTaskData*& OutILCTaskData) { if (GetRendererOutput() != ERendererOutput::FinalSceneColor) { return; } QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup); { QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_Sort); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { FViewInfo& View = Views[ViewIndex]; if (View.State) { ((FSceneViewState*)View.State)->TrimHistoryRenderTargets(Scene); } } } GatherReflectionCaptureLightMeshElements(); if (ViewFamily.EngineShowFlags.HitProxies == 0 && Scene->PrecomputedLightVolumes.Num() > 0 && GILCUpdatePrimTaskEnabled && FPlatformProcess::SupportsMultithreading()) { OutILCTaskData = Allocator.Create(); Scene->IndirectLightingCache.StartUpdateCachePrimitivesTask(Scene, *this, true, *OutILCTaskData); check(OutILCTaskData->TaskRef.IsValid()); } } uint32 GetShadowQuality(); void UpdateHairResources(FRDGBuilder& GraphBuilder, const FViewInfo& View); /** * Performs once per frame setup prior to visibility determination. */ void FDeferredShadingSceneRenderer::PreVisibilityFrameSetup(FRDGBuilder& GraphBuilder) { FSceneRenderer::PreVisibilityFrameSetup(GraphBuilder); } /** * Initialize scene's views. * Check visibility, build visible mesh commands, etc. */ void FDeferredShadingSceneRenderer::BeginInitViews( FRDGBuilder& GraphBuilder, const FSceneTexturesConfig& SceneTexturesConfig, FInstanceCullingManager& InstanceCullingManager, FRDGExternalAccessQueue& ExternalAccessQueue, FInitViewTaskDatas& TaskDatas) { SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_InitViews, FColor::Emerald); SCOPE_CYCLE_COUNTER(STAT_InitViewsTime); RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, InitViews_Scene); const bool bRendererOutputFinalSceneColor = (GetRendererOutput() == ERendererOutput::FinalSceneColor); PreVisibilityFrameSetup(GraphBuilder); { // This is to init the ViewUniformBuffer before rendering for the Niagara compute shader. // This needs to run before ComputeViewVisibility() is called, but the views normally initialize the ViewUniformBuffer after that (at the end of this method). // Note: This MUST happen before we start to execute GDME tasks of any kind otherwise we will race with PostInitViews renderers! if (FXSystem && FXSystem->RequiresEarlyViewUniformBuffer() && Views.IsValidIndex(0) && bRendererOutputFinalSceneColor) { // during ISR, instanced view RHI resources need to be initialized first. if (FViewInfo* InstancedView = const_cast(Views[0].GetInstancedView())) { InstancedView->InitRHIResources(); } Views[0].InitRHIResources(); FXSystem->PostInitViews(GraphBuilder, GetSceneViews(), !ViewFamily.EngineShowFlags.HitProxies); } } // Start processing dynamic mesh elements tasks early enough to overlap with shadows and GPU scene update. TaskDatas.VisibilityTaskData->StartGatherDynamicMeshElements(); #if RHI_RAYTRACING // Start processing dynamic ray tracing instances early enough to overlap with shadows and GPU scene update. if (TaskDatas.RayTracingGatherInstances != nullptr) { RayTracing::BeginGatherDynamicRayTracingInstances(*TaskDatas.RayTracingGatherInstances); } #endif if (!ViewFamily.EngineShowFlags.HitProxies) { TaskDatas.Decals = FDecalVisibilityTaskData::Launch(GraphBuilder, *Scene, Views); } // Attempt to launch dynamic shadow tasks early before finalizing visibility. if (bRendererOutputFinalSceneColor) { BeginInitDynamicShadows(GraphBuilder, TaskDatas, InstanceCullingManager); } FRHICommandListImmediate& RHICmdList = GraphBuilder.RHICmdList; if (InstanceCullingManager.IsEnabled() && Scene->InstanceCullingOcclusionQueryRenderer && Scene->InstanceCullingOcclusionQueryRenderer->InstanceOcclusionQueryBuffer) { InstanceCullingManager.InstanceOcclusionQueryBuffer = GraphBuilder.RegisterExternalBuffer(Scene->InstanceCullingOcclusionQueryRenderer->InstanceOcclusionQueryBuffer); InstanceCullingManager.InstanceOcclusionQueryBufferFormat = Scene->InstanceCullingOcclusionQueryRenderer->InstanceOcclusionQueryBufferFormat; } LumenScenePDIVisualization(); // This must happen before we start initialising and using views. UpdateSkyIrradianceGpuBuffer(GraphBuilder, ViewFamily.EngineShowFlags, Scene->SkyLight, Scene->SkyIrradianceEnvironmentMap); // Initialise Sky/View resources before the view global uniform buffer is built. if (ShouldRenderSkyAtmosphere(Scene, ViewFamily.EngineShowFlags)) { InitSkyAtmosphereForViews(RHICmdList, GraphBuilder); } { QUICK_SCOPE_CYCLE_COUNTER(STAT_InitViews_InitRHIResources); // initialize per-view uniform buffer. Do it from back to front because secondary stereo view follows its primary one, but primary needs to know the instanced's params for (int32 ViewIndex = Views.Num() - 1; ViewIndex >= 0; --ViewIndex) { FViewInfo& View = Views[ViewIndex]; // Set the pre-exposure before initializing the constant buffers. View.UpdatePreExposure(); // Initialize the view's RHI resources. UpdateHairResources(GraphBuilder, View); View.InitRHIResources(); } for (FCustomRenderPassInfo& PassInfo : CustomRenderPassInfos) { for (FViewInfo& View : PassInfo.Views) { View.InitRHIResources(); } } } TaskDatas.VisibilityTaskData->ProcessRenderThreadTasks(); // Make a second attempt to launch shadow tasks it wasn't able to the first time due to visibility being deferred. if (bRendererOutputFinalSceneColor) { BeginInitDynamicShadows(GraphBuilder, TaskDatas, InstanceCullingManager); } PostVisibilityFrameSetup(TaskDatas.ILCUpdatePrim); } void FSceneRenderer::SetupSceneReflectionCaptureBuffer(FRHICommandListImmediate& RHICmdList) { const TArray& SortedCaptures = Scene->ReflectionSceneData.SortedCaptures; for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex) { FViewInfo& View = Views[ViewIndex]; if (IsMobilePlatform(ShaderPlatform)) { View.MobileReflectionCaptureUniformBuffer = Scene->ReflectionSceneData.MobileReflectionCaptureUniformBuffer; } else { View.ReflectionCaptureUniformBuffer = Scene->ReflectionSceneData.ReflectionCaptureUniformBuffer; } View.NumBoxReflectionCaptures = 0; View.NumSphereReflectionCaptures = 0; View.FurthestReflectionCaptureDistance = 0.0f; if (View.Family->EngineShowFlags.ReflectionEnvironment // Avoid feedback && !View.bIsReflectionCapture) { View.NumBoxReflectionCaptures = Scene->ReflectionSceneData.NumBoxCaptures; View.NumSphereReflectionCaptures = Scene->ReflectionSceneData.NumSphereCaptures; for (int32 CaptureIndex = 0; CaptureIndex < SortedCaptures.Num(); CaptureIndex++) { const FSphere BoundingSphere(SortedCaptures[CaptureIndex].Position.GetVector3d(), SortedCaptures[CaptureIndex].Radius); const float Distance = View.ViewMatrices.GetViewMatrix().TransformPosition(BoundingSphere.Center).Z + BoundingSphere.W; View.FurthestReflectionCaptureDistance = FMath::Max(View.FurthestReflectionCaptureDistance, Distance); } } } } void FDeferredShadingSceneRenderer::EndInitViews( FRDGBuilder& GraphBuilder, FLumenSceneFrameTemporaries& FrameTemporaries, FInstanceCullingManager& InstanceCullingManager, FInitViewTaskDatas& TaskDatas) { SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_InitViewsAfterPrepass, FColor::Emerald); SCOPE_CYCLE_COUNTER(STAT_InitViewsPossiblyAfterPrepass); TaskDatas.VisibilityTaskData->Finish(); // Trigger shadow GDME tasks after the main visibility tasks are synced. Projection stencil shadows reference the main view dynamic elements. BeginShadowGatherDynamicMeshElements(TaskDatas.DynamicShadows); FRHICommandListImmediate& RHICmdList = GraphBuilder.RHICmdList; // If parallel ILC update is disabled, then process it in place. if (ViewFamily.EngineShowFlags.HitProxies == 0 && Scene->PrecomputedLightVolumes.Num() > 0 && !(GILCUpdatePrimTaskEnabled && FPlatformProcess::SupportsMultithreading())) { QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_IndirectLightingCache_Update); check(!TaskDatas.ILCUpdatePrim); Scene->IndirectLightingCache.UpdateCache(Scene, *this, true); } // If we kicked off ILC update via task, wait and finalize. if (TaskDatas.ILCUpdatePrim) { Scene->IndirectLightingCache.FinalizeCacheUpdates(Scene, *this, *TaskDatas.ILCUpdatePrim); } { QUICK_SCOPE_CYCLE_COUNTER(STAT_InitViews_UpdatePrimitiveIndirectLightingCacheBuffers); // Now that the indirect lighting cache is updated, we can update the primitive precomputed lighting buffers. UpdatePrimitiveIndirectLightingCacheBuffers(GraphBuilder.RHICmdList); } SeparateTranslucencyDimensions = UpdateSeparateTranslucencyDimensions(*this); SetupSceneReflectionCaptureBuffer(RHICmdList); if (IsForwardShadingEnabled(ShaderPlatform)) { // Dynamic shadows are synced earlier when forward shading is enabled. FinishInitDynamicShadows(GraphBuilder, TaskDatas.DynamicShadows, InstanceCullingManager); } } /*------------------------------------------------------------------------------ FLODSceneTree Implementation ------------------------------------------------------------------------------*/ void FLODSceneTree::AddChildNode(const FPrimitiveComponentId ParentId, FPrimitiveSceneInfo* ChildSceneInfo) { if (ParentId.IsValid() && ChildSceneInfo) { FLODSceneNode* Parent = SceneNodes.Find(ParentId); // If parent SceneNode hasn't been created yet (possible, depending on the order actors are added to the scene) if (!Parent) { // Create parent SceneNode, assign correct SceneInfo Parent = &SceneNodes.Add(ParentId, FLODSceneNode()); int32 ParentIndex = Scene->PrimitiveComponentIds.Find(ParentId); if (ParentIndex != INDEX_NONE) { Parent->SceneInfo = Scene->Primitives[ParentIndex]; check(Parent->SceneInfo->PrimitiveComponentId == ParentId); } } Parent->AddChild(ChildSceneInfo); } } void FLODSceneTree::RemoveChildNode(const FPrimitiveComponentId ParentId, FPrimitiveSceneInfo* ChildSceneInfo) { if (ParentId.IsValid() && ChildSceneInfo) { if (FLODSceneNode* Parent = SceneNodes.Find(ParentId)) { Parent->RemoveChild(ChildSceneInfo); // Delete from scene if no children remain if (Parent->ChildrenSceneInfos.Num() == 0) { SceneNodes.Remove(ParentId); } } } } void FLODSceneTree::UpdateNodeSceneInfo(FPrimitiveComponentId NodeId, FPrimitiveSceneInfo* SceneInfo) { if (FLODSceneNode* Node = SceneNodes.Find(NodeId)) { Node->SceneInfo = SceneInfo; } } void FLODSceneTree::ClearVisibilityState(FViewInfo& View) { if (FSceneViewState* ViewState = (FSceneViewState*)View.State) { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // Skip update logic when frozen if (ViewState->bIsFrozen) { return; } #endif FHLODVisibilityState& HLODState = ViewState->HLODVisibilityState; if(HLODState.IsValidPrimitiveIndex(0)) { HLODState.PrimitiveFadingLODMap.Empty(0); HLODState.PrimitiveFadingOutLODMap.Empty(0); HLODState.ForcedVisiblePrimitiveMap.Empty(0); HLODState.ForcedHiddenPrimitiveMap.Empty(0); } TMap& VisibilityStates = ViewState->HLODSceneNodeVisibilityStates; if(VisibilityStates.Num() > 0) { VisibilityStates.Empty(0); } } } void FLODSceneTree::UpdateVisibilityStates(FViewInfo& View, UE::Tasks::FTaskEvent& FlushCachedShadowsTaskEvent) { if (FSceneViewState* ViewState = (FSceneViewState*)View.State) { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // Skip update logic when frozen if (ViewState->bIsFrozen) { return; } #endif QUICK_SCOPE_CYCLE_COUNTER(STAT_ViewVisibilityTime_HLODUpdate); // Per-frame initialization FHLODVisibilityState& HLODState = ViewState->HLODVisibilityState; TMap& VisibilityStates = ViewState->HLODSceneNodeVisibilityStates; HLODState.PrimitiveFadingLODMap.Init(false, Scene->Primitives.Num()); HLODState.PrimitiveFadingOutLODMap.Init(false, Scene->Primitives.Num()); HLODState.ForcedVisiblePrimitiveMap.Init(false, Scene->Primitives.Num()); HLODState.ForcedHiddenPrimitiveMap.Init(false, Scene->Primitives.Num()); TArray& RelevanceMap = View.PrimitiveViewRelevanceMap; if (HLODState.PrimitiveFadingLODMap.Num() != Scene->Primitives.Num()) { checkf(0, TEXT("HLOD update incorrectly allocated primitive maps")); return; } TArray FlushCachedShadowPrimitives; int32 UpdateCount = ++HLODState.UpdateCount; // Update persistent state on temporal dither sync frames const FTemporalLODState& LODState = ViewState->GetTemporalLODState(); bool bSyncFrame = false; if (HLODState.TemporalLODSyncTime != LODState.TemporalLODTime[0]) { HLODState.TemporalLODSyncTime = LODState.TemporalLODTime[0]; bSyncFrame = true; // Only update our scaling on sync frames else we might end up changing transition direction mid-fade const FCachedSystemScalabilityCVars& ScalabilityCVars = GetCachedScalabilityCVars(); if (ScalabilityCVars.FieldOfViewAffectsHLOD) { HLODState.FOVDistanceScaleSq = ScalabilityCVars.CalculateFieldOfViewDistanceScale(View.DesiredFOV); HLODState.FOVDistanceScaleSq *= HLODState.FOVDistanceScaleSq; } else { HLODState.FOVDistanceScaleSq = 1.f; } } for (auto Iter = SceneNodes.CreateIterator(); Iter; ++Iter) { FLODSceneNode& Node = Iter.Value(); FPrimitiveSceneInfo* SceneInfo = Node.SceneInfo; if (!SceneInfo || !SceneInfo->PrimitiveComponentId.IsValid() || !SceneInfo->IsIndexValid()) { continue; } FHLODSceneNodeVisibilityState& NodeVisibility = VisibilityStates.FindOrAdd(SceneInfo->PrimitiveComponentId); const TArray& NodeMeshRelevances = SceneInfo->StaticMeshRelevances; // Ignore already updated nodes, or those that we can't work with if (NodeVisibility.UpdateCount == UpdateCount || !NodeMeshRelevances.IsValidIndex(0)) { continue; } const int32 NodeIndex = SceneInfo->GetIndex(); if (!Scene->PrimitiveBounds.IsValidIndex(NodeIndex)) { checkf(0, TEXT("A HLOD Node's PrimitiveSceneInfo PackedIndex was out of Scene.Primitive bounds!")); continue; } FPrimitiveBounds& Bounds = Scene->PrimitiveBounds[NodeIndex]; const bool bForcedIntoView = FMath::IsNearlyZero(Bounds.MinDrawDistance); // Update visibility states of this node and owned children float ClosestDistSquared, FurthestDistSquared; if (GDistanceCullToSphereEdge) { ComputeDistances(Bounds, View.ViewMatrices.GetViewOrigin(), ClosestDistSquared, FurthestDistSquared); } else { ClosestDistSquared = FurthestDistSquared = Bounds.BoxSphereBounds.ComputeSquaredDistanceFromBoxToPoint(View.ViewMatrices.GetViewOrigin()); } const bool bNearCulled = FurthestDistSquared < FMath::Square(Bounds.MinDrawDistance) * HLODState.FOVDistanceScaleSq; const bool bFarCulled = ClosestDistSquared > Bounds.MaxDrawDistance * Bounds.MaxDrawDistance * HLODState.FOVDistanceScaleSq; const bool bIsInDrawRange = !bNearCulled && !bFarCulled; const bool bWasFadingPreUpdate = !!NodeVisibility.bIsFading; const bool bIsDitheredTransition = NodeMeshRelevances[0].bDitheredLODTransition; if (bIsDitheredTransition && !bForcedIntoView) { // Update fading state with syncs if (bSyncFrame) { // Fade when HLODs change threshold const bool bChangedRange = bIsInDrawRange != !!NodeVisibility.bWasVisible; if (NodeVisibility.bIsFading) { NodeVisibility.bIsFading = false; } else if (bChangedRange) { NodeVisibility.bIsFading = true; } NodeVisibility.bWasVisible = NodeVisibility.bIsVisible; NodeVisibility.bIsVisible = bIsInDrawRange; } } else { // Instant transitions without dithering NodeVisibility.bWasVisible = NodeVisibility.bIsVisible; NodeVisibility.bIsVisible = bIsInDrawRange || bForcedIntoView; NodeVisibility.bIsFading = false; } // Flush cached lighting data when changing visible contents if (NodeVisibility.bIsVisible != NodeVisibility.bWasVisible || bWasFadingPreUpdate || NodeVisibility.bIsFading) { FlushCachedShadowPrimitives.Emplace(SceneInfo); } // Force fully disabled view relevance so shadows don't attempt to recompute if (!NodeVisibility.bIsVisible) { if (RelevanceMap.IsValidIndex(NodeIndex)) { FPrimitiveViewRelevance& ViewRelevance = RelevanceMap[NodeIndex]; FMemory::Memzero(&ViewRelevance, sizeof(FPrimitiveViewRelevance)); ViewRelevance.bInitializedThisFrame = true; } else { checkf(0, TEXT("A HLOD Node's PrimitiveSceneInfo PackedIndex was out of View.Relevancy bounds!")); } } // NOTE: We update our children last as HideNodeChildren can add new visibility // states, potentially invalidating our cached reference above, NodeVisibility if (NodeVisibility.bIsFading) { // Fade until state back in sync HLODState.PrimitiveFadingLODMap[NodeIndex] = true; HLODState.PrimitiveFadingOutLODMap[NodeIndex] = !NodeVisibility.bIsVisible; HLODState.ForcedVisiblePrimitiveMap[NodeIndex] = true; ApplyNodeFadingToChildren(ViewState, Node, NodeVisibility, true, !!NodeVisibility.bIsVisible); } else if (NodeVisibility.bIsVisible) { // If stable and visible, override hierarchy visibility HLODState.ForcedVisiblePrimitiveMap[NodeIndex] = true; HideNodeChildren(ViewState, Node); } else { // Not visible and waiting for a transition to fade, keep HLOD hidden HLODState.ForcedHiddenPrimitiveMap[NodeIndex] = true; // Also hide children when performing far culling if (bFarCulled) { HideNodeChildren(ViewState, Node); } } } if (!FlushCachedShadowPrimitives.IsEmpty()) { // We don't want to block on the LPI creation task as it overlaps with this work. Spawn a new task that will be waited on further down the pipe. FlushCachedShadowsTaskEvent.AddPrerequisites(UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, Primitives = MoveTemp(FlushCachedShadowPrimitives)] { for (FPrimitiveSceneInfo* SceneInfo : Primitives) { FLightPrimitiveInteraction* NodeLightList = SceneInfo->LightList; while (NodeLightList) { NodeLightList->FlushCachedShadowMapData(); NodeLightList = NodeLightList->GetNextLight(); } } }, Scene->GetCreateLightPrimitiveInteractionsTask())); } } } void FLODSceneTree::ApplyNodeFadingToChildren(FSceneViewState* ViewState, FLODSceneNode& Node, FHLODSceneNodeVisibilityState& NodeVisibility, const bool bIsFading, const bool bIsFadingOut) { checkSlow(ViewState); if (Node.SceneInfo) { FHLODVisibilityState& HLODState = ViewState->HLODVisibilityState; NodeVisibility.UpdateCount = HLODState.UpdateCount; // Force visibility during fades for (const auto Child : Node.ChildrenSceneInfos) { if (!Child || !Child->PrimitiveComponentId.IsValid() || !Child->IsIndexValid()) { continue; } const int32 ChildIndex = Child->GetIndex(); if (!HLODState.PrimitiveFadingLODMap.IsValidIndex(ChildIndex)) { checkf(0, TEXT("A HLOD Child's PrimitiveSceneInfo PackedIndex was out of FadingMap's bounds!")); continue; } HLODState.PrimitiveFadingLODMap[ChildIndex] = bIsFading; HLODState.PrimitiveFadingOutLODMap[ChildIndex] = bIsFadingOut; HLODState.ForcedHiddenPrimitiveMap[ChildIndex] = false; if (bIsFading) { HLODState.ForcedVisiblePrimitiveMap[ChildIndex] = true; } // Fading only occurs at the adjacent hierarchy level, below should be hidden if (FLODSceneNode* ChildNode = SceneNodes.Find(Child->PrimitiveComponentId)) { HideNodeChildren(ViewState, *ChildNode); } } } } void FLODSceneTree::HideNodeChildren(FSceneViewState* ViewState, FLODSceneNode& Node) { checkSlow(ViewState); if (Node.SceneInfo) { FHLODVisibilityState& HLODState = ViewState->HLODVisibilityState; TMap& VisibilityStates = ViewState->HLODSceneNodeVisibilityStates; FHLODSceneNodeVisibilityState& NodeVisibility = VisibilityStates.FindOrAdd(Node.SceneInfo->PrimitiveComponentId); if (NodeVisibility.UpdateCount != HLODState.UpdateCount) { NodeVisibility.UpdateCount = HLODState.UpdateCount; for (const auto Child : Node.ChildrenSceneInfos) { if (!Child || !Child->PrimitiveComponentId.IsValid() || !Child->IsIndexValid()) { continue; } const int32 ChildIndex = Child->GetIndex(); if (!HLODState.ForcedHiddenPrimitiveMap.IsValidIndex(ChildIndex)) { checkf(0, TEXT("A HLOD Child's PrimitiveSceneInfo PackedIndex was out of ForcedHidden's bounds!")); continue; } HLODState.ForcedHiddenPrimitiveMap[ChildIndex] = true; // Clear the force visible flag in case the child was processed before it's parent HLODState.ForcedVisiblePrimitiveMap[ChildIndex] = false; if (FLODSceneNode* ChildNode = SceneNodes.Find(Child->PrimitiveComponentId)) { HideNodeChildren(ViewState, *ChildNode); } } } } }