2022-05-25 08:22:32 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
SceneVisibility . cpp : Scene visibility determination .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
# include "CoreMinimal.h"
# include "HAL/ThreadSafeCounter.h"
# include "Stats/Stats.h"
# include "Misc/MemStack.h"
# include "HAL/IConsoleManager.h"
# include "Misc/App.h"
# include "Async/TaskGraphInterfaces.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 "MaterialShared.h"
# include "SceneManagement.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 "ScenePrivate.h"
# include "FXSystem.h"
# include "PostProcess/PostProcessing.h"
# include "SceneView.h"
# include "Engine/LODActor.h"
# include "GPUScene.h"
# include "TranslucentRendering.h"
# include "Async/ParallelFor.h"
# include "HairStrands/HairStrandsRendering.h"
# include "HairStrands/HairStrandsData.h"
# include "RectLightSceneProxy.h"
# include "Math/Halton.h"
# include "ProfilingDebugging/DiagnosticTable.h"
# include "Algo/Unique.h"
# include "InstanceCulling/InstanceCullingManager.h"
# include "TemporalAA.h"
# include "RayTracing/RayTracingInstanceCulling.h"
# include "RendererModule.h"
/*------------------------------------------------------------------------------
Globals
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
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
) ;
float GMinScreenRadiusForCSMDepth = 0.01f ;
static FAutoConsoleVariableRef CVarMinScreenRadiusForCSMDepth (
TEXT ( " r.MinScreenRadiusForCSMDepth " ) ,
GMinScreenRadiusForCSMDepth ,
TEXT ( " Threshold below which meshes will be culled from CSM depth pass. " ) ,
ECVF_RenderThreadSafe
) ;
static TAutoConsoleVariable < int32 > CVarTemporalAASamples (
TEXT ( " r.TemporalAASamples " ) ,
8 ,
TEXT ( " Number of jittered positions for temporal AA (4, 8=default, 16, 32, 64). " ) ,
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
) ;
static int32 GVisualizeOccludedPrimitives = 0 ;
static FAutoConsoleVariableRef CVarVisualizeOccludedPrimitives (
TEXT ( " r.VisualizeOccludedPrimitives " ) ,
GVisualizeOccludedPrimitives ,
TEXT ( " Draw boxes for all occluded primitives " ) ,
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 < float > 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 < float > CVarMinAutomaticViewMipBias (
TEXT ( " r.ViewTextureMipBias.Min " ) ,
- 2.0f ,
TEXT ( " Automatic view mip bias's minimum value (default to -2). " ) ,
ECVF_RenderThreadSafe ) ;
static TAutoConsoleVariable < float > CVarMinAutomaticViewMipBiasOffset (
TEXT ( " r.ViewTextureMipBias.Offset " ) ,
- 0.3 ,
TEXT ( " Automatic view mip bias's constant offset (default to -0.3). " ) ,
ECVF_RenderThreadSafe ) ;
static int32 GOcclusionCullParallelPrimFetch = 0 ;
static FAutoConsoleVariableRef CVarOcclusionCullParallelPrimFetch (
TEXT ( " r.OcclusionCullParallelPrimFetch " ) ,
GOcclusionCullParallelPrimFetch ,
TEXT ( " Enables Parallel Occlusion Cull primitive fetch. " ) ,
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
) ;
static 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. " ) ,
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
) ;
/** 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 ) ;
static TAutoConsoleVariable < int32 > CVarParallelInitViews (
TEXT ( " r.ParallelInitViews " ) ,
1 ,
TEXT ( " Toggles parallel init views. 0 = off; 1 = on " ) ,
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
) ;
static TAutoConsoleVariable < bool > CVarEnableFrustumCull (
TEXT ( " r.EnableFrustumCull " ) ,
true ,
TEXT ( " Enables or disables frustum culling. Useful for comparing results to ensure culling is functioning properly. " ) ,
ECVF_RenderThreadSafe ) ;
static TAutoConsoleVariable < int32 > CVarAlsoUseSphereForFrustumCull (
TEXT ( " r.AlsoUseSphereForFrustumCull " ) ,
0 ,
TEXT ( " Performance tweak. If > 0, then use a sphere cull before and in addition to a box for frustum culling. " ) ,
ECVF_RenderThreadSafe
) ;
static TAutoConsoleVariable < int32 > CVarUseFastIntersect (
TEXT ( " r.UseFastIntersect " ) ,
1 ,
TEXT ( " Use optimized 8 plane fast intersection code if we have 8 permuted planes. " ) ,
ECVF_RenderThreadSafe
) ;
static TAutoConsoleVariable < int32 > CVarUseVisibilityOctree (
TEXT ( " r.UseVisibilityOctree " ) ,
0 ,
TEXT ( " Use the octree for visibility calculations. " ) ,
ECVF_RenderThreadSafe ) ;
static bool GOcclusionSingleRHIThreadStall = false ;
static FAutoConsoleVariableRef CVarOcclusionSingleRHIThreadStall (
TEXT ( " r.Occlusion.SingleRHIThreadStall " ) ,
GOcclusionSingleRHIThreadStall ,
TEXT ( " Enable a single RHI thread stall before polling occlusion queries. This will only happen if the RHI's occlusion queries would normally stall the RHI thread themselves. " ) ,
ECVF_RenderThreadSafe
) ;
# if !UE_BUILD_SHIPPING
static TAutoConsoleVariable < int32 > CVarTAADebugOverrideTemporalIndex (
TEXT ( " r.TemporalAA.Debug.OverrideTemporalIndex " ) , - 1 ,
TEXT ( " Override the temporal index for debugging purposes. " ) ,
ECVF_RenderThreadSafe ) ;
static TAutoConsoleVariable < float > CVarFreezeTemporalSequences (
TEXT ( " r.Test.FreezeTemporalSequences " ) , 0 ,
TEXT ( " Freezes all temporal sequences. " ) ,
ECVF_RenderThreadSafe ) ;
static TAutoConsoleVariable < float > CVarFreezeTemporalHistories (
TEXT ( " r.Test.FreezeTemporalHistories " ) , 0 ,
TEXT ( " Freezes all temporal histories as well as the temporal sequence. " ) ,
ECVF_RenderThreadSafe ) ;
static bool bDumpPrimitivesNextFrame = false ;
static FAutoConsoleCommand CVarDumpPrimitives (
TEXT ( " DumpPrimitives " ) ,
TEXT ( " Writes out all scene primitive names to a CSV file " ) ,
FConsoleCommandDelegate : : CreateStatic ( [ ] { bDumpPrimitivesNextFrame = true ; } ) ,
ECVF_Default ) ;
# endif
DECLARE_CYCLE_STAT ( TEXT ( " Occlusion Readback " ) , STAT_CLMM_OcclusionReadback , STATGROUP_CommandListMarkers ) ;
DECLARE_CYCLE_STAT ( TEXT ( " After Occlusion Readback " ) , STAT_CLMM_AfterOcclusionReadback , STATGROUP_CommandListMarkers ) ;
/*------------------------------------------------------------------------------
Visibility determination .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Update a primitive ' s fading state .
* @ param FadingState - State to update .
* @ param View - The view for which to update .
* @ param bVisible - Whether the primitive should be visible in the view .
*/
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 ;
}
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 MinDrawDistance , 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 ;
bool bHasMaxDrawDistance = InMaxDrawDistance ! = FLT_MAX ;
bool bHasMinDrawDistance = InMaxDrawDistance > 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 ;
}
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 ;
}
FORCEINLINE 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 ;
}
static int32 FrustumCullNumWordsPerTask = 128 ;
static FAutoConsoleVariableRef CVarFrustumCullNumWordsPerTask (
TEXT ( " r.FrustumCullNumWordsPerTask " ) ,
FrustumCullNumWordsPerTask ,
TEXT ( " Performance tweak. Controls the granularity for the ParallelFor for frustum culling. " ) ,
ECVF_Default
) ;
static TAutoConsoleVariable CVarNaniteMeshsAlwaysVisible (
TEXT ( " r.Nanite.PrimitivesAlwaysVisible " ) ,
0 ,
TEXT ( " True - All Nanite primitives skip culling phases, False - All Nanite primitives are run through the culling phase. " ) ,
ECVF_Default
) ;
// Access when not on the render thread
FORCEINLINE bool IsAlwaysVisible ( const FScene * RESTRICT Scene , int32 Index , bool bNaniteAlwaysVisible )
{
return bNaniteAlwaysVisible ? Scene - > PrimitiveFlagsCompact [ Index ] . bIsNaniteMesh : false ;
}
// Non template version
FORCEINLINE bool IsAlwaysVisible ( const FScene * RESTRICT Scene , int32 Index )
{
if ( CVarNaniteMeshsAlwaysVisible . GetValueOnRenderThread ( ) )
{
return Scene - > PrimitiveFlagsCompact [ Index ] . bIsNaniteMesh ;
}
return false ;
}
struct FPrimitiveCullingFlags
{
bool bShouldVisibilityCull ;
bool bUseCustomCulling ;
bool bAlsoUseSphereTest ;
bool bUseFastIntersect ;
bool bUseVisibilityOctree ;
bool bNaniteAlwaysVisible ;
bool bHasHiddenPrimitives ;
bool bHasShowOnlyPrimitives ;
# if RHI_RAYTRACING
bool bCullInRayTracing ;
# endif
} ;
// Returns true if the frustum and bounds intersect
FORCEINLINE bool IsPrimitiveVisible ( FViewInfo & View , const FPlane * PermutedPlanePtr , const FPrimitiveBounds & RESTRICT Bounds , int32 VisibilityId , const FPrimitiveCullingFlags & 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 . bAlsoUseSphereTest & & ! View . ViewFrustum . IntersectSphere ( Bounds . BoxSphereBounds . Origin , Bounds . BoxSphereBounds . SphereRadius ) )
{
return false ;
}
if ( Flags . bUseFastIntersect )
{
return IntersectBox8Plane ( Bounds . BoxSphereBounds . Origin , Bounds . BoxSphereBounds . BoxExtent , PermutedPlanePtr ) ;
}
else
{
return View . ViewFrustum . IntersectBox ( Bounds . BoxSphereBounds . Origin , Bounds . BoxSphereBounds . BoxExtent ) ;
}
}
FORCEINLINE bool IsPrimitiveHidden ( const FScene * RESTRICT Scene , FViewInfo & View , int PrimitiveIndex , const FPrimitiveCullingFlags & 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
FORCEINLINE bool ShouldCullForRayTracing ( const FScene * RESTRICT 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
static FORCEINLINE void CullOctree ( const FScene * RESTRICT Scene , FViewInfo & View , const FPrimitiveCullingFlags & Flags , FSceneBitArray & OutVisibleNodes )
{
TRACE_CPUPROFILER_EVENT_SCOPE ( SceneVisibility_CullOctree ) ;
// Two bits per octree node, 1st bit is Inisde Frustum, 2nd bit is Outside Frustum
OutVisibleNodes . Init ( false , Scene - > PrimitiveOctree . GetNumNodes ( ) * 2 ) ;
Scene - > PrimitiveOctree . FindNodesWithPredicate (
[ & View , & OutVisibleNodes , & Flags ] ( 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 = View . ViewFrustum . PermutedPlanes . GetData ( ) ;
bool bIntersects = false ;
if ( Flags . bUseFastIntersect )
{
bIntersects = IntersectBox8Plane ( NodeBounds . Center , NodeBounds . Extent , PermutedPlanePtr ) ;
}
else
{
bIntersects = View . ViewFrustum . IntersectBox ( NodeBounds . Center , NodeBounds . Extent ) ;
}
if ( bIntersects )
{
OutVisibleNodes [ NodeIndex * 2 ] = true ;
OutVisibleNodes [ NodeIndex * 2 + 1 ] = View . ViewFrustum . GetBoxIntersectionOutcode ( NodeBounds . Center , NodeBounds . Extent ) . GetOutside ( ) ;
}
return bIntersects ;
} ,
[ ] ( FScenePrimitiveOctree : : FNodeIndex /*ParentNodeIndex*/ , FScenePrimitiveOctree : : FNodeIndex /*NodeIndex*/ , const FBoxCenterAndExtent & /*NodeBounds*/ )
{
} ) ;
}
static void PrimitiveCullTask ( FThreadSafeCounter & NumCulledPrimitives , const FScene * RESTRICT Scene , FViewInfo & View , FPrimitiveCullingFlags Flags , float MaxDrawDistanceScale , const FHLODVisibilityState * const HLODState , const FSceneBitArray & VisibleNodes , int32 TaskIndex )
{
TRACE_CPUPROFILER_EVENT_SCOPE ( SceneVisibility_PrimitiveCull ) ;
SCOPED_NAMED_EVENT ( SceneVisibility_PrimitiveCull , FColor : : Red ) ;
QUICK_SCOPE_CYCLE_COUNTER ( STAT_PrimitiveCull_Loop ) ;
FTaskTagScope TaskTagScope ( ETaskTag : : EParallelRenderingThread ) ;
bool bDisableLODFade = GDisableLODFade | | View . bDisableDistanceBasedFadeTransitions ;
const FPlane * PermutedPlanePtr = View . ViewFrustum . PermutedPlanes . GetData ( ) ;
const int32 BitArrayNumInner = View . PrimitiveVisibilityMap . Num ( ) ;
FVector ViewOriginForDistanceCulling = View . ViewMatrices . GetViewOrigin ( ) ;
float FadeRadius = bDisableLODFade ? 0.0f : GDistanceFadeMaxTravel ;
uint8 CustomVisibilityFlags = EOcclusionFlags : : CanBeOccluded | EOcclusionFlags : : HasPrecomputedVisibility ;
uint32 NumPrimitivesCulledForTask = 0 ;
// Primitives may be explicitly removed from stereo views when using mono
const int32 TaskWordOffset = TaskIndex * FrustumCullNumWordsPerTask ;
FVector ViewOrigin = View . ViewMatrices . GetViewOrigin ( ) ;
for ( int32 WordIndex = TaskWordOffset ; WordIndex < TaskWordOffset + FrustumCullNumWordsPerTask & & WordIndex * NumBitsPerDWORD < BitArrayNumInner ; WordIndex + + )
{
uint32 Mask = 0x1 ;
uint32 VisBits = 0 ;
uint32 FadingBits = 0 ;
// If visibility culling is disabled, make sure to use the existing visibility state
if ( ! Flags . bShouldVisibilityCull )
{
VisBits = View . PrimitiveVisibilityMap . GetData ( ) [ WordIndex ] ;
}
# if RHI_RAYTRACING
uint32 RayTracingBits = 0 ;
# endif //RHI_RAYTRACING
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 | | ( Flags . bCullInRayTracing & & ShouldCullForRayTracing ( Scene , View , Index ) ) )
{
bIsVisibleInRayTracing = false ;
}
# endif //RHI_RAYTRACING
const FPrimitiveBounds & RESTRICT Bounds = Scene - > PrimitiveBounds [ Index ] ;
// Handle primitives that are not always visible.
if ( Flags . bShouldVisibilityCull & & bIsVisible & & ! IsAlwaysVisible ( Scene , Index , Flags . bNaniteAlwaysVisible ) )
{
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 ) )
{
bShouldFrustumCull = false ;
bShouldDistanceCull = false ;
}
else if ( HLODState - > IsNodeForcedHidden ( Index ) )
{
bShouldDistanceCull = false ;
bShouldFrustumCull = false ;
bIsVisible = false ;
}
}
// Frustum first
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 - > PrimitiveVisibilityIds [ Index ] . ByteIndex ;
}
bIsVisible = ! bPartiallyOutside | | IsPrimitiveVisible ( View , PermutedPlanePtr , Bounds , VisibilityId , Flags ) ;
}
}
bShouldDistanceCull = bShouldDistanceCull & & bIsVisible ;
// Distance cull if frustum cull passed
if ( bShouldDistanceCull )
{
// If cull distance is disabled, always show the primitive (except foliage)
if ( View . Family - > EngineShowFlags . DistanceCulledPrimitives
& & ! Scene - > Primitives [ Index ] - > Proxy - > 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 ) ;
float DistanceSquared = 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 ( ( DistanceSquared < MaxFadeDistanceSquared & & DistanceSquared > MinFadeDistanceSquared )
& & Scene - > Primitives [ Index ] - > Proxy - > 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 & & ( DistanceSquared > FMath : : Square ( MaxDrawDistance ) ) ;
const bool bNearDistanceCulled = bHasMinDrawDistance & & ( DistanceSquared < MinDrawDistanceSq ) ;
bool bIsDistanceCulled = bNearDistanceCulled | | bFarDistanceCulled ;
if ( bIsDistanceCulled )
{
bIsVisible = false ;
}
# if RHI_RAYTRACING
if ( bFarDistanceCulled )
{
bIsVisibleInRayTracing = false ;
}
# endif //RHI_RAYTRACING
}
}
}
if ( bIsVisible )
{
// The primitive is visible!
VisBits | = Mask ;
}
else
{
STAT ( + + NumPrimitivesCulledForTask ) ;
}
# if RHI_RAYTRACING
if ( bIsVisibleInRayTracing )
{
RayTracingBits | = Mask ;
}
# endif //RHI_RAYTRACING
}
if ( Flags . bShouldVisibilityCull & & FadingBits )
{
checkSlow ( ! View . PotentiallyFadingPrimitiveMap . GetData ( ) [ WordIndex ] ) ; // this should start at zero
View . PotentiallyFadingPrimitiveMap . GetData ( ) [ WordIndex ] = FadingBits ;
}
if ( Flags . bShouldVisibilityCull & & VisBits )
{
checkSlow ( ! View . PrimitiveVisibilityMap . GetData ( ) [ WordIndex ] ) ; // this should start at zero
View . PrimitiveVisibilityMap . GetData ( ) [ WordIndex ] = VisBits ;
}
# if RHI_RAYTRACING
if ( RayTracingBits )
{
checkSlow ( ! View . PrimitiveRayTracingVisibilityMap . GetData ( ) [ WordIndex ] ) ; // this should start at zero
View . PrimitiveRayTracingVisibilityMap . GetData ( ) [ WordIndex ] = RayTracingBits ;
}
# endif
}
STAT ( NumCulledPrimitives . Add ( NumPrimitivesCulledForTask ) ) ;
}
static int32 PrimitiveCull ( const FScene * RESTRICT Scene , FViewInfo & View , bool bShouldVisibilityCull )
{
FPrimitiveCullingFlags Flags ;
Flags . bShouldVisibilityCull = bShouldVisibilityCull ;
Flags . bUseCustomCulling = View . CustomVisibilityQuery & & View . CustomVisibilityQuery - > Prepare ( ) ;
Flags . bAlsoUseSphereTest = CVarAlsoUseSphereForFrustumCull . GetValueOnRenderThread ( ) > 0 ;
Flags . bUseFastIntersect = ( View . ViewFrustum . PermutedPlanes . Num ( ) = = 8 ) & & CVarUseFastIntersect . GetValueOnRenderThread ( ) ;
Flags . bUseVisibilityOctree = CVarUseVisibilityOctree . GetValueOnRenderThread ( ) > 0 ;
Flags . bNaniteAlwaysVisible = CVarNaniteMeshsAlwaysVisible . GetValueOnRenderThread ( ) > 0 ;
Flags . bHasHiddenPrimitives = View . HiddenPrimitives . Num ( ) > 0 ;
Flags . bHasShowOnlyPrimitives = View . ShowOnlyPrimitives . IsSet ( ) ;
# if RHI_RAYTRACING
View . RayTracingCullingParameters . Init ( View ) ;
Flags . bCullInRayTracing = View . RayTracingCullingParameters . CullInRayTracing > 0 ;
# endif
SCOPE_CYCLE_COUNTER ( STAT_PrimitiveCull ) ;
FMemMark MemStackMark ( FMemStack : : Get ( ) ) ;
FSceneBitArray VisibleNodes ;
if ( bShouldVisibilityCull & & Flags . bUseVisibilityOctree )
{
CullOctree ( Scene , View , Flags , VisibleNodes ) ;
}
//Primitives per ParallelFor task
//Using async FrustumCull. Thanks Yager! See https://udn.unrealengine.com/questions/252385/performance-of-frustumcull.html
//Performance varies on total primitive count and tasks scheduled. Check the mentioned link above for some measurements.
//There have been some changes as compared to the code measured in the link
FThreadSafeCounter NumCulledPrimitives ;
FSceneViewState * ViewState = ( FSceneViewState * ) View . State ;
const bool bHLODActive = Scene - > SceneLODHierarchy . IsActive ( ) ;
const FHLODVisibilityState * const HLODState = bHLODActive & & ViewState ? & ViewState - > HLODVisibilityState : nullptr ;
float MaxDrawDistanceScale = GetCachedScalabilityCVars ( ) . ViewDistanceScale * GetCachedScalabilityCVars ( ) . CalculateFieldOfViewDistanceScale ( View . DesiredFOV ) ;
const int32 BitArrayNum = View . PrimitiveVisibilityMap . Num ( ) ;
const int32 BitArrayWords = FMath : : DivideAndRoundUp ( BitArrayNum , ( int32 ) NumBitsPerDWORD ) ;
const int32 NumTasks = FMath : : DivideAndRoundUp ( BitArrayWords , FrustumCullNumWordsPerTask ) ;
ParallelFor ( NumTasks ,
[ & NumCulledPrimitives , Scene , & View , MaxDrawDistanceScale , HLODState , & VisibleNodes , & Flags ] ( int32 TaskIndex )
{
PrimitiveCullTask ( NumCulledPrimitives , Scene , View , Flags , MaxDrawDistanceScale , HLODState , VisibleNodes , TaskIndex ) ;
} ,
! FApp : : ShouldUseThreadingForPerformance ( ) | | ( Flags . bUseCustomCulling & & ! View . CustomVisibilityQuery - > IsThreadsafe ( ) ) | | CVarParallelInitViews . GetValueOnRenderThread ( ) = = 0 | | ! IsInActualRenderingThread ( )
) ;
return NumCulledPrimitives . GetValue ( ) ;
}
/**
* Updated primitive fading states for the view .
*/
static void UpdatePrimitiveFading ( const FScene * Scene , FViewInfo & View )
{
SCOPE_CYCLE_COUNTER ( STAT_UpdatePrimitiveFading ) ;
FSceneViewState * ViewState = ( FSceneViewState * ) View . State ;
if ( ViewState )
{
uint32 PrevFrameNumber = ViewState - > PrevFrameNumber ;
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 ( ) ;
}
}
// 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 ) ; BitIt ; + + 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 ( View . RayTracingCullingParameters . CullInRayTracing = = 0 | | ! ShouldCullForRayTracing ( Scene , View , BitIt . GetIndex ( ) ) )
{
View . PrimitiveRayTracingVisibilityMap . AccessCorrespondingBit ( BitIt ) = true ;
}
# endif //RHI_RAYTRACING
}
View . PrimitiveFadeUniformBuffers [ BitIt . GetIndex ( ) ] = UniformBuffer ;
View . PrimitiveFadeUniformBufferMap [ BitIt . GetIndex ( ) ] = UniformBuffer ! = nullptr ;
}
}
}
}
struct FOcclusionBounds
{
FOcclusionBounds ( FPrimitiveOcclusionHistory * InPrimitiveOcclusionHistory , const FVector & InBoundsOrigin , const FVector & InBoundsExtent , bool bInGroupedQuery )
: PrimitiveOcclusionHistory ( InPrimitiveOcclusionHistory )
, BoundsOrigin ( InBoundsOrigin )
, BoundsExtent ( InBoundsExtent )
, bGroupedQuery ( bInGroupedQuery )
{
}
FOcclusionBounds ( FPrimitiveOcclusionHistoryKey InPrimitiveOcclusionHistoryKey , const FVector & InBoundsOrigin , const FVector & InBoundsExtent , uint32 InLastQuerySubmitFrame )
: PrimitiveOcclusionHistoryKey ( InPrimitiveOcclusionHistoryKey )
, BoundsOrigin ( InBoundsOrigin )
, BoundsExtent ( InBoundsExtent )
, LastQuerySubmitFrame ( InLastQuerySubmitFrame )
{
}
union
{
FPrimitiveOcclusionHistory * PrimitiveOcclusionHistory ;
FPrimitiveOcclusionHistoryKey PrimitiveOcclusionHistoryKey ;
} ;
FVector BoundsOrigin ;
FVector BoundsExtent ;
union
{
bool bGroupedQuery ;
uint32 LastQuerySubmitFrame ;
} ;
} ;
struct FHZBBound
{
FHZBBound ( FPrimitiveOcclusionHistory * InTargetHistory , const FVector & InBoundsOrigin , const FVector & InBoundsExtent )
: TargetHistory ( InTargetHistory )
, BoundsOrigin ( InBoundsOrigin )
, BoundsExtent ( InBoundsExtent )
{ }
FPrimitiveOcclusionHistory * const TargetHistory ;
const FVector BoundsOrigin ;
const FVector BoundsExtent ;
} ;
# define BALANCE_LOAD 1
# define QUERY_SANITY_CHECK 0
struct FVisForPrimParams
{
FVisForPrimParams ( ) { }
FVisForPrimParams ( const FScene * InScene ,
FViewInfo * InView ,
FViewElementPDI * InOcclusionPDI ,
int32 InStartIndex ,
int32 InNumToProcess ,
bool bInSubmitQueries ,
bool bInHZBOcclusion ,
TArray < FOcclusionBounds > & OutQueriesToRun ,
TArray < bool > & OutSubIsOccluded )
: Scene ( InScene )
, View ( InView )
, OcclusionPDI ( InOcclusionPDI )
, StartIndex ( InStartIndex )
, NumToProcess ( InNumToProcess )
, bSubmitQueries ( bInSubmitQueries )
, bHZBOcclusion ( bInHZBOcclusion )
, QueriesToAdd ( & OutQueriesToRun )
, SubIsOccluded ( & OutSubIsOccluded )
{
OutQueriesToRun . Reset ( ) ;
OutSubIsOccluded . Reset ( ) ;
}
void Init ( const FScene * InScene ,
FViewInfo * InView ,
int32 InStartIndex ,
int32 InNumToProcess ,
bool bInSubmitQueries ,
bool bInHZBOcclusion ,
TArray < FPrimitiveOcclusionHistory > & OutOcclusionHistory ,
TArray < FPrimitiveOcclusionHistory * > & OutQueriesToRelease ,
TArray < FHZBBound > & OutHZBBounds ,
TArray < FOcclusionBounds > & OutQueriesToRun ,
TArray < bool > & OutSubIsOccluded )
{
OutOcclusionHistory . Reset ( ) ;
OutQueriesToRelease . Reset ( ) ;
OutHZBBounds . Reset ( ) ;
OutQueriesToRun . Reset ( ) ;
OutSubIsOccluded . Reset ( ) ;
Scene = InScene ;
View = InView ;
StartIndex = InStartIndex ;
NumToProcess = InNumToProcess ;
bSubmitQueries = bInSubmitQueries ;
bHZBOcclusion = bInHZBOcclusion ;
InsertPrimitiveOcclusionHistory = & OutOcclusionHistory ;
QueriesToRelease = & OutQueriesToRelease ;
HZBBoundsToAdd = & OutHZBBounds ;
QueriesToAdd = & OutQueriesToRun ;
SubIsOccluded = & OutSubIsOccluded ;
}
const FScene * Scene { } ;
FViewInfo * View { } ;
FViewElementPDI * OcclusionPDI { } ;
int32 StartIndex { } ;
int32 NumToProcess { } ;
bool bSubmitQueries { } ;
bool bHZBOcclusion { } ;
// Whether the entries written into the history need to be read using a scan search (see FPrimitiveOcclusionHistory::bNeedsScanOnRead)
bool bNeedsScanOnRead { } ;
//occlusion history to insert into. In parallel these will be all merged back into the view's history on the main thread.
//use TChunkedArray so pointers to the new FPrimitiveOcclusionHistory's won't change if the array grows.
TArray < FPrimitiveOcclusionHistory > * InsertPrimitiveOcclusionHistory { } ;
TArray < FPrimitiveOcclusionHistory * > * QueriesToRelease { } ;
TArray < FHZBBound > * HZBBoundsToAdd { } ;
TArray < FOcclusionBounds > * QueriesToAdd { } ;
int32 NumOccludedPrims { } ;
TArray < bool > * SubIsOccluded { } ;
} ;
//This function is shared between the single and multi-threaded versions. Modifications to any primitives indexed by BitIt should be ok
//since only one of the task threads will ever reference it. However, any modifications to shared state like the ViewState must be buffered
//to be recombined later.
template < bool bSingleThreaded >
static void FetchVisibilityForPrimitives_Range ( FVisForPrimParams & Params , FGlobalDynamicVertexBuffer * DynamicVertexBufferIfSingleThreaded )
{
SCOPED_NAMED_EVENT ( FetchVisibilityForPrimitives_Range , FColor : : Magenta ) ;
int32 NumOccludedPrimitives = 0 ;
const FScene * Scene = Params . Scene ;
FViewInfo & View = * Params . View ;
FViewElementPDI * OcclusionPDI = Params . OcclusionPDI ;
const int32 StartIndex = Params . StartIndex ;
const int32 NumToProcess = Params . NumToProcess ;
const bool bSubmitQueries = Params . bSubmitQueries ;
const bool bHZBOcclusion = Params . bHZBOcclusion ;
const float PrimitiveProbablyVisibleTime = GEngine - > PrimitiveProbablyVisibleTime ;
FSceneViewState * ViewState = ( FSceneViewState * ) View . State ;
const int32 NumBufferedFrames = FOcclusionQueryHelpers : : GetNumBufferedFrames ( Scene - > GetFeatureLevel ( ) ) ;
bool bClearQueries = ! View . Family - > EngineShowFlags . HitProxies ;
const float CurrentRealTime = View . Family - > Time . GetRealTimeSeconds ( ) ;
uint32 OcclusionFrameCounter = ViewState - > OcclusionFrameCounter ;
FHZBOcclusionTester & HZBOcclusionTests = ViewState - > HZBOcclusionTests ;
int32 ReadBackLagTolerance = NumBufferedFrames ;
const bool bIsStereoView = IStereoRendering : : IsStereoEyeView ( View ) ;
const bool bUseRoundRobinOcclusion = bIsStereoView & & ! View . bIsSceneCapture & & View . ViewState - > IsRoundRobinEnabled ( ) ;
if ( bUseRoundRobinOcclusion )
{
// We don't allow clearing of a history entry if we do not also submit an occlusion query to replace the deleted one
// as we want to keep the history as full as possible
bClearQueries & = bSubmitQueries ;
// However, if this frame happens to be the first frame, then we clear anyway since in the first frame we should not be
// reading past queries
bClearQueries | = View . bIgnoreExistingQueries ;
// Round-robin occlusion culling involves reading frames that could be twice as stale as without round-robin
ReadBackLagTolerance = NumBufferedFrames * 2 ;
}
// Round robin occlusion culling can make holes in the occlusion history which would require scanning the history when reading
Params . bNeedsScanOnRead = bUseRoundRobinOcclusion ;
TSet < FPrimitiveOcclusionHistory , FPrimitiveOcclusionHistoryKeyFuncs > & ViewPrimitiveOcclusionHistory = ViewState - > PrimitiveOcclusionHistorySet ;
TArray < FPrimitiveOcclusionHistory > * InsertPrimitiveOcclusionHistory = Params . InsertPrimitiveOcclusionHistory ;
TArray < FPrimitiveOcclusionHistory * > * QueriesToRelease = Params . QueriesToRelease ;
TArray < FHZBBound > * HZBBoundsToAdd = Params . HZBBoundsToAdd ;
TArray < FOcclusionBounds > * QueriesToAdd = Params . QueriesToAdd ;
const bool bNewlyConsideredBBoxExpandActive = GExpandNewlyOcclusionTestedBBoxesAmount > 0.0f & & GFramesToExpandNewlyOcclusionTestedBBoxes > 0 & & GFramesNotOcclusionTestedToExpandBBoxes > 0 ;
const float NeverOcclusionTestDistanceSquared = GNeverOcclusionTestDistance * GNeverOcclusionTestDistance ;
const FVector ViewOrigin = View . ViewMatrices . GetViewOrigin ( ) ;
const int32 ReserveAmount = NumToProcess ;
int32 NumQueriesToReserve = NumToProcess ;
if ( ! bSingleThreaded )
{
check ( InsertPrimitiveOcclusionHistory ) ;
check ( QueriesToRelease ) ;
check ( HZBBoundsToAdd ) ;
check ( QueriesToAdd ) ;
// We need to calculuate the actual number of queries to reserve since the pointers to InsertPrimitiveOcclusionHistory need to be preserved.
if ( GAllowSubPrimitiveQueries & & ! View . bDisableQuerySubmissions )
{
NumQueriesToReserve = 0 ;
int32 NumProcessed = 0 ;
# if BALANCE_LOAD
for ( FSceneSetBitIterator BitIt ( View . PrimitiveVisibilityMap , StartIndex ) ; BitIt & & ( NumProcessed < NumToProcess ) ; + + BitIt , + + NumProcessed )
# else
for ( TBitArray < SceneRenderingBitArrayAllocator > : : FIterator BitIt ( View . PrimitiveVisibilityMap , StartIndex ) ; BitIt & & ( NumProcessed < NumToProcess ) ; + + BitIt , + + NumProcessed )
# endif
{
# if !BALANCE_LOAD
if ( ! View . PrimitiveVisibilityMap . AccessCorrespondingBit ( BitIt ) )
{
continue ;
}
# endif
int32 Index = BitIt . GetIndex ( ) ;
const uint8 OcclusionFlags = Scene - > PrimitiveOcclusionFlags [ Index ] ;
if ( ( OcclusionFlags & EOcclusionFlags : : CanBeOccluded ) = = 0 )
{
continue ;
}
if ( ( OcclusionFlags & EOcclusionFlags : : HasSubprimitiveQueries ) ! = 0 )
{
NumQueriesToReserve + = Scene - > Primitives [ Index ] - > Proxy - > GetOcclusionQueries ( & View ) - > Num ( ) ;
}
else
{
NumQueriesToReserve + + ;
}
}
}
//avoid doing reallocs as much as possible. Unlikely to make an entry per processed element.
InsertPrimitiveOcclusionHistory - > Reserve ( NumQueriesToReserve ) ;
QueriesToRelease - > Reserve ( ReserveAmount ) ;
HZBBoundsToAdd - > Reserve ( ReserveAmount ) ;
QueriesToAdd - > Reserve ( ReserveAmount ) ;
}
int32 NumProcessed = 0 ;
int32 NumTotalPrims = View . PrimitiveVisibilityMap . Num ( ) ;
int32 NumTotalDefUnoccluded = View . PrimitiveDefinitelyUnoccludedMap . Num ( ) ;
{
// If we're going to stall the RHI thread for one query, we should stall it for all of them.
// !(View.bIgnoreExistingQueries || bHZBOcclusion) is the code path that calls GetQueryForReading.
const bool bShouldStallRHIThread = bSingleThreaded & & GOcclusionSingleRHIThreadStall & & ! GSupportsParallelOcclusionQueries & & IsInRenderingThread ( ) & & ! ( View . bIgnoreExistingQueries | | bHZBOcclusion ) ;
FScopedRHIThreadStaller StallRHIThread ( FRHICommandListExecutor : : GetImmediateCommandList ( ) , bShouldStallRHIThread ) ;
SCOPED_NAMED_EVENT_F ( TEXT ( " forEach over %d entries " ) , FColor : : Magenta , NumToProcess ) ;
//if we are load balanced then we iterate only the set bits, and the ranges have been pre-selected to evenly distribute set bits among the tasks with no overlaps.
//if not, then the entire array is evenly divided by range.
# if BALANCE_LOAD
for ( FSceneSetBitIterator BitIt ( View . PrimitiveVisibilityMap , StartIndex ) ; BitIt & & ( NumProcessed < NumToProcess ) ; + + BitIt , + + NumProcessed )
# else
for ( TBitArray < SceneRenderingBitArrayAllocator > : : FIterator BitIt ( View . PrimitiveVisibilityMap , StartIndex ) ; BitIt & & ( NumProcessed < NumToProcess ) ; + + BitIt , + + NumProcessed )
# endif
{
# if !BALANCE_LOAD
if ( ! View . PrimitiveVisibilityMap . AccessCorrespondingBit ( BitIt ) )
{
continue ;
}
# endif
int32 Index = BitIt . GetIndex ( ) ;
const uint8 OcclusionFlags = Scene - > PrimitiveOcclusionFlags [ Index ] ;
if ( ( OcclusionFlags & EOcclusionFlags : : CanBeOccluded ) = = 0 )
{
View . PrimitiveDefinitelyUnoccludedMap . AccessCorrespondingBit ( BitIt ) = true ;
continue ;
}
//we can't allow the prim history insertion array to realloc or it will invalidate pointers in the other output arrays.
const bool bCanAllocPrimHistory = bSingleThreaded | | InsertPrimitiveOcclusionHistory - > Num ( ) < InsertPrimitiveOcclusionHistory - > Max ( ) ;
# if WITH_EDITOR
bool bCanBeOccluded = true ;
if ( GIsEditor )
{
if ( Scene - > PrimitivesSelected [ Index ] )
{
// to render occluded outline for selected objects
bCanBeOccluded = false ;
}
}
# else
constexpr bool bCanBeOccluded = true ;
# endif
int32 NumSubQueries = 1 ;
bool bSubQueries = false ;
const TArray < FBoxSphereBounds > * SubBounds = nullptr ;
check ( Params . SubIsOccluded ) ;
TArray < bool > & SubIsOccluded = * Params . SubIsOccluded ;
int32 SubIsOccludedStart = SubIsOccluded . Num ( ) ;
if ( ( OcclusionFlags & EOcclusionFlags : : HasSubprimitiveQueries ) & & GAllowSubPrimitiveQueries & & ! View . bDisableQuerySubmissions )
{
FPrimitiveSceneProxy * Proxy = Scene - > Primitives [ Index ] - > Proxy ;
SubBounds = Proxy - > GetOcclusionQueries ( & View ) ;
NumSubQueries = SubBounds - > Num ( ) ;
bSubQueries = true ;
if ( ! NumSubQueries )
{
View . PrimitiveVisibilityMap . AccessCorrespondingBit ( BitIt ) = false ;
continue ;
}
SubIsOccluded . Reserve ( NumSubQueries ) ;
}
bool bAllSubOcclusionStateIsDefinite = true ;
bool bAllSubOccluded = true ;
FPrimitiveComponentId PrimitiveId = Scene - > PrimitiveComponentIds [ Index ] ;
for ( int32 SubQuery = 0 ; SubQuery < NumSubQueries ; SubQuery + + )
{
FPrimitiveOcclusionHistory * PrimitiveOcclusionHistory = ViewPrimitiveOcclusionHistory . Find ( FPrimitiveOcclusionHistoryKey ( PrimitiveId , SubQuery ) ) ;
bool bIsOccluded = false ;
bool bOcclusionStateIsDefinite = false ;
if ( ! PrimitiveOcclusionHistory )
{
// If the primitive doesn't have an occlusion history yet, create it.
if ( bSingleThreaded )
{
// In singlethreaded mode we can safely modify the view's history directly.
PrimitiveOcclusionHistory = & ViewPrimitiveOcclusionHistory [
ViewPrimitiveOcclusionHistory . Add ( FPrimitiveOcclusionHistory ( PrimitiveId , SubQuery ) )
] ;
}
else if ( bCanAllocPrimHistory )
{
// In multithreaded mode we have to buffer the new histories and add them to the view during a post-combine
PrimitiveOcclusionHistory = & ( * InsertPrimitiveOcclusionHistory ) [
InsertPrimitiveOcclusionHistory - > Add ( FPrimitiveOcclusionHistory ( PrimitiveId , SubQuery ) )
] ;
}
// If the primitive hasn't been visible recently enough to have a history, treat it as unoccluded this frame so it will be rendered as an occluder and its true occlusion state can be determined.
// already set bIsOccluded = false;
// Flag the primitive's occlusion state as indefinite, which will force it to be queried this frame.
// The exception is if the primitive isn't occludable, in which case we know that it's definitely unoccluded.
bOcclusionStateIsDefinite = ! bCanBeOccluded ;
}
else
{
if ( View . bIgnoreExistingQueries )
{
// If the view is ignoring occlusion queries, the primitive is definitely unoccluded.
// already set bIsOccluded = false;
bOcclusionStateIsDefinite = View . bDisableQuerySubmissions ;
}
else if ( bCanBeOccluded )
{
if ( 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 , NumBufferedFrames , ReadBackLagTolerance , bGrouped ) ;
if ( PastQuery )
{
//int32 RefCount = PastQuery.GetReference()->GetRefCount();
// NOTE: RHIGetOcclusionQueryResult should never fail when using a blocking call, rendering artifacts may show up.
//if (RHICmdList.GetRenderQueryResult(PastQuery, NumSamples, true))
if ( GDynamicRHI - > 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;
}
}
else
{
if ( NumBufferedFrames > 1 | | GRHIMaximumReccommendedOustandingOcclusionQueries < 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 & & OcclusionPDI & & bIsOccluded )
{
const FBoxSphereBounds & Bounds = bSubQueries ? ( * SubBounds ) [ SubQuery ] : Scene - > PrimitiveOcclusionBounds [ Index ] ;
DrawWireBox ( OcclusionPDI , Bounds . GetBox ( ) , FColor ( 50 , 255 , 50 ) , SDPG_Foreground ) ;
}
}
else
{
// Primitives that aren't occludable are considered definitely unoccluded.
// already set bIsOccluded = false;
bOcclusionStateIsDefinite = true ;
}
if ( bClearQueries )
{
if ( bSingleThreaded )
{
PrimitiveOcclusionHistory - > ReleaseQuery ( OcclusionFrameCounter , NumBufferedFrames ) ;
}
else
{
if ( PrimitiveOcclusionHistory - > GetQueryForEviction ( OcclusionFrameCounter , NumBufferedFrames ) )
{
QueriesToRelease - > Add ( PrimitiveOcclusionHistory ) ;
}
}
}
}
if ( PrimitiveOcclusionHistory )
{
if ( bSubmitQueries & & bCanBeOccluded )
{
bool bSkipNewlyConsidered = false ;
if ( bNewlyConsideredBBoxExpandActive )
{
if ( ! PrimitiveOcclusionHistory - > BecameEligibleForQueryCooldown & & OcclusionFrameCounter - PrimitiveOcclusionHistory - > LastConsideredFrameNumber > uint32 ( GFramesNotOcclusionTestedToExpandBBoxes ) )
{
PrimitiveOcclusionHistory - > BecameEligibleForQueryCooldown = GFramesToExpandNewlyOcclusionTestedBBoxes ;
}
bSkipNewlyConsidered = ! ! PrimitiveOcclusionHistory - > BecameEligibleForQueryCooldown ;
if ( bSkipNewlyConsidered )
{
PrimitiveOcclusionHistory - > BecameEligibleForQueryCooldown - - ;
}
}
bool bAllowBoundsTest ;
const FBoxSphereBounds OcclusionBounds = ( bSubQueries ? ( * SubBounds ) [ SubQuery ] : Scene - > PrimitiveOcclusionBounds [ Index ] ) . ExpandBy ( GExpandAllTestedBBoxesAmount + ( bSkipNewlyConsidered ? GExpandNewlyOcclusionTestedBBoxesAmount : 0.0 ) ) ;
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 ( bHZBOcclusion )
{
// Always run
if ( bSingleThreaded )
{
PrimitiveOcclusionHistory - > HZBTestIndex = HZBOcclusionTests . AddBounds ( OcclusionBounds . Origin , OcclusionBounds . BoxExtent ) ;
}
else
{
HZBBoundsToAdd - > Emplace ( 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 ( bSingleThreaded )
{
checkSlow ( DynamicVertexBufferIfSingleThreaded ) ;
if ( GRHIMaximumReccommendedOustandingOcclusionQueries < MAX_int32 & & ! bGroupedQuery )
{
QueriesToAdd - > Emplace ( FPrimitiveOcclusionHistoryKey ( PrimitiveId , SubQuery ) , BoundOrigin , BoundExtent , PrimitiveOcclusionHistory - > LastQuerySubmitFrame ( ) ) ;
}
else
{
PrimitiveOcclusionHistory - > SetCurrentQuery ( OcclusionFrameCounter ,
bGroupedQuery ?
View . GroupedOcclusionQueries . BatchPrimitive ( BoundOrigin , BoundExtent , * DynamicVertexBufferIfSingleThreaded ) :
View . IndividualOcclusionQueries . BatchPrimitive ( BoundOrigin , BoundExtent , * DynamicVertexBufferIfSingleThreaded ) ,
NumBufferedFrames ,
bGroupedQuery ,
Params . bNeedsScanOnRead
) ;
}
}
else
{
QueriesToAdd - > Emplace ( 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 )
{
View . PrimitiveVisibilityMap . AccessCorrespondingBit ( BitIt ) = false ;
STAT ( NumOccludedPrimitives + + ) ;
}
else if ( bOcclusionStateIsDefinite )
{
View . PrimitiveDefinitelyUnoccludedMap . AccessCorrespondingBit ( BitIt ) = true ;
}
}
}
if ( bSubQueries )
{
if ( SubIsOccluded . Num ( ) > 0 )
{
FPrimitiveSceneProxy * Proxy = Scene - > Primitives [ Index ] - > Proxy ;
Proxy - > AcceptOcclusionResults ( & View , & SubIsOccluded , SubIsOccludedStart , SubIsOccluded . Num ( ) - SubIsOccludedStart ) ;
}
if ( bAllSubOccluded )
{
View . PrimitiveVisibilityMap . AccessCorrespondingBit ( BitIt ) = false ;
STAT ( NumOccludedPrimitives + + ) ;
}
else if ( bAllSubOcclusionStateIsDefinite )
{
View . PrimitiveDefinitelyUnoccludedMap . AccessCorrespondingBit ( BitIt ) = true ;
}
}
}
}
check ( NumTotalDefUnoccluded = = View . PrimitiveDefinitelyUnoccludedMap . Num ( ) ) ;
check ( NumTotalPrims = = View . PrimitiveVisibilityMap . Num ( ) ) ;
check ( ! InsertPrimitiveOcclusionHistory | | InsertPrimitiveOcclusionHistory - > Num ( ) < = NumQueriesToReserve ) ;
Params . NumOccludedPrims = NumOccludedPrimitives ;
}
static int32 FetchVisibilityForPrimitives ( const FScene * Scene , FViewInfo & View , const bool bSubmitQueries , const bool bHZBOcclusion , FGlobalDynamicVertexBuffer & DynamicVertexBuffer )
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE ( FetchVisibilityForPrimitives ) ;
QUICK_SCOPE_CYCLE_COUNTER ( STAT_FetchVisibilityForPrimitives ) ;
FSceneViewState * ViewState = ( FSceneViewState * ) View . State ;
SCOPED_NAMED_EVENT ( FetchVisibilityForPrimitives , FColor : : Magenta ) ;
static int32 SubIsOccludedArrayIndex = 0 ;
SubIsOccludedArrayIndex = 1 - SubIsOccludedArrayIndex ;
const int32 NumBufferedFrames = FOcclusionQueryHelpers : : GetNumBufferedFrames ( Scene - > GetFeatureLevel ( ) ) ;
uint32 OcclusionFrameCounter = ViewState - > OcclusionFrameCounter ;
TSet < FPrimitiveOcclusionHistory , FPrimitiveOcclusionHistoryKeyFuncs > & ViewPrimitiveOcclusionHistory = ViewState - > PrimitiveOcclusionHistorySet ;
if ( GOcclusionCullParallelPrimFetch & & GSupportsParallelOcclusionQueries )
{
SCOPED_NAMED_EVENT ( FetchVisibilityParallel , FColor : : Magenta ) ;
constexpr int32 MaxNumCullTasks = 8 ;
constexpr int32 ActualNumCullTasks = 8 ;
constexpr int32 NumOutputArrays = MaxNumCullTasks ;
//params for each task
FVisForPrimParams Params [ NumOutputArrays ] ;
//output arrays for each task
TArray < FPrimitiveOcclusionHistory > OutputOcclusionHistory [ NumOutputArrays ] ;
TArray < FPrimitiveOcclusionHistory * > OutQueriesToRelease [ NumOutputArrays ] ;
TArray < FHZBBound > OutHZBBounds [ NumOutputArrays ] ;
TArray < FOcclusionBounds > OutQueriesToRun [ NumOutputArrays ] ;
static TArray < bool > FrameSubIsOccluded [ NumOutputArrays ] [ FSceneView : : NumBufferedSubIsOccludedArrays ] ;
//optionally balance the tasks by how the visible primitives are distributed in the array rather than just breaking up the array by range.
//should make the tasks more equal length.
# if BALANCE_LOAD
int32 StartIndices [ NumOutputArrays ] = { 0 } ;
int32 ProcessRange [ NumOutputArrays ] = { 0 } ;
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_FetchVisibilityForPrimitivesPreProcess ) ;
int32 NumBitsSet = 0 ;
for ( FSceneSetBitIterator BitIt ( View . PrimitiveVisibilityMap ) ; BitIt ; + + BitIt , + + NumBitsSet )
{
}
int32 BitsPerTask = NumBitsSet / ActualNumCullTasks ;
int32 NumBitsForRange = 0 ;
int32 CurrentStartIndex = 0 ;
int32 RangeToSet = 0 ;
//accumulate set bits for each task until we reach the target, then set the start/end and move on.
for ( FSceneSetBitIterator BitIt ( View . PrimitiveVisibilityMap ) ; BitIt & & RangeToSet < ( ActualNumCullTasks - 1 ) ; + + BitIt )
{
+ + NumBitsForRange ;
if ( NumBitsForRange = = BitsPerTask )
{
StartIndices [ RangeToSet ] = CurrentStartIndex ;
ProcessRange [ RangeToSet ] = NumBitsForRange ;
+ + RangeToSet ;
NumBitsForRange = 0 ;
CurrentStartIndex = BitIt . GetIndex ( ) + 1 ;
}
}
//final range is the rest of the set bits, no matter how many there are.
StartIndices [ ActualNumCullTasks - 1 ] = CurrentStartIndex ;
ProcessRange [ ActualNumCullTasks - 1 ] = NumBitsSet - ( BitsPerTask * 3 ) ;
}
# endif
const int32 NumPrims = View . PrimitiveVisibilityMap . Num ( ) ;
const int32 NumPerTask = NumPrims / ActualNumCullTasks ;
int32 StartIndex = 0 ;
int32 NumTasks = 0 ;
for ( int32 i = 0 ; i < ActualNumCullTasks & & ( StartIndex < NumPrims ) ; + + i , + + NumTasks )
{
const int32 NumToProcess = ( i = = ( ActualNumCullTasks - 1 ) ) ? ( NumPrims - StartIndex ) : NumPerTask ;
Params [ i ] . Init (
Scene ,
& View ,
# if BALANCE_LOAD
StartIndices [ i ] ,
ProcessRange [ i ] ,
# else
StartIndex ,
NumToProcess ,
# endif
bSubmitQueries ,
bHZBOcclusion ,
OutputOcclusionHistory [ i ] ,
OutQueriesToRelease [ i ] ,
OutHZBBounds [ i ] ,
OutQueriesToRun [ i ] ,
FrameSubIsOccluded [ i ] [ SubIsOccludedArrayIndex ]
) ;
StartIndex + = NumToProcess ;
}
ParallelFor ( NumTasks ,
[ & Params ] ( int32 Index )
{
FetchVisibilityForPrimitives_Range < false > ( Params [ Index ] , nullptr ) ;
} ,
! ( FApp : : ShouldUseThreadingForPerformance ( ) & & CVarParallelInitViews . GetValueOnRenderThread ( ) > 0 & & IsInActualRenderingThread ( ) )
) ;
FHZBOcclusionTester & HZBOcclusionTests = ViewState - > HZBOcclusionTests ;
int32 NumOccludedPrims = 0 ;
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_FetchVisibilityForPrimitivesCombine ) ;
SCOPED_NAMED_EVENT ( FetchVisibilityForPrimitivesCombine , FColor : : Magenta ) ;
# if QUERY_SANITY_CHECK
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_FetchVisibilityForPrimitivesSanity ) ;
TSet < int32 > ReleaseQuerySet ;
TSet < int32 > RunQuerySet ;
TSet < int32 > MasterPrimsProcessed ;
for ( int32 i = 0 ; i < NumTasks ; + + i )
{
bool bAlreadyIn = false ;
for ( FPrimitiveOcclusionHistory * History : OutQueriesToRelease [ i ] )
{
ReleaseQuerySet . Add ( History - > PrimitiveId . PrimIDValue , & bAlreadyIn ) ;
checkf ( ! bAlreadyIn , TEXT ( " Prim: %i double released query. " ) , History - > PrimitiveId . PrimIDValue ) ;
}
for ( const FOcclusionBounds & OcclusionBounds : OutQueriesToRun [ i ] )
{
FPrimitiveOcclusionHistory * History = OcclusionBounds - > PrimitiveOcclusionHistory ;
RunQuerySet . Add ( History - > PrimitiveId . PrimIDValue , & bAlreadyIn ) ;
checkf ( ! bAlreadyIn , TEXT ( " Prim: %i double run query. " ) , History - > PrimitiveId . PrimIDValue ) ;
}
}
}
# endif
//Add/Release query ops use stored PrimitiveHistory pointers. We must do ALL of these from all tasks before adding any new PrimitiveHistories to the view.
//Adding new histories to the view could cause the array to resize which would invalidate all the stored output pointers for the other operations.
for ( int32 i = 0 ; i < NumTasks ; + + i )
{
//HZB output
for ( const FHZBBound & HZBBounds : OutHZBBounds [ i ] )
{
HZBBounds . TargetHistory - > HZBTestIndex = HZBOcclusionTests . AddBounds ( HZBBounds . BoundsOrigin , HZBBounds . BoundsExtent ) ;
}
//Manual query release handling
for ( FPrimitiveOcclusionHistory * History : OutQueriesToRelease [ i ] )
{
History - > ReleaseQuery ( OcclusionFrameCounter , NumBufferedFrames ) ;
}
//New query batching
for ( const FOcclusionBounds & OcclusionBounds : OutQueriesToRun [ i ] )
{
OcclusionBounds . PrimitiveOcclusionHistory - > SetCurrentQuery ( OcclusionFrameCounter ,
OcclusionBounds . bGroupedQuery ?
View . GroupedOcclusionQueries . BatchPrimitive ( OcclusionBounds . BoundsOrigin , OcclusionBounds . BoundsExtent , DynamicVertexBuffer ) :
View . IndividualOcclusionQueries . BatchPrimitive ( OcclusionBounds . BoundsOrigin , OcclusionBounds . BoundsExtent , DynamicVertexBuffer ) ,
NumBufferedFrames ,
OcclusionBounds . bGroupedQuery ,
Params [ i ] . bNeedsScanOnRead
) ;
}
}
//now add new primitive histories to the view. may resize the view's array.
for ( int32 i = 0 ; i < NumTasks ; + + i )
{
ViewPrimitiveOcclusionHistory . Append ( MoveTemp ( OutputOcclusionHistory [ i ] ) ) ;
//accumulate occluded prims across tasks
NumOccludedPrims + = Params [ i ] . NumOccludedPrims ;
}
}
return NumOccludedPrims ;
}
else
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FetchVisibilityOther ) ;
static TArray < FOcclusionBounds > PendingIndividualQueriesWhenOptimizing ;
FViewElementPDI OcclusionPDI ( & View , nullptr , nullptr ) ;
int32 StartIndex = 0 ;
int32 NumToProcess = View . PrimitiveVisibilityMap . Num ( ) ;
FVisForPrimParams Params (
Scene ,
& View ,
& OcclusionPDI ,
StartIndex ,
NumToProcess ,
bSubmitQueries ,
bHZBOcclusion ,
PendingIndividualQueriesWhenOptimizing ,
//SubIsOccluded stuff needs a frame's lifetime
View . FrameSubIsOccluded [ SubIsOccludedArrayIndex ]
) ;
FetchVisibilityForPrimitives_Range < true > ( Params , & DynamicVertexBuffer ) ;
int32 IndQueries = PendingIndividualQueriesWhenOptimizing . Num ( ) ;
if ( IndQueries )
{
static TArray < FOcclusionBounds * > PendingIndividualQueriesWhenOptimizingSorter ;
PendingIndividualQueriesWhenOptimizingSorter . Reset ( ) ;
int32 SoftMaxQueries = GRHIMaximumReccommendedOustandingOcclusionQueries / FMath : : Min ( NumBufferedFrames , 2 ) ; // extra RHIT frame does not count
int32 UsedQueries = View . GroupedOcclusionQueries . GetNumBatchOcclusionQueries ( ) ;
int32 FirstQueryToDo = 0 ;
int32 QueriesToDo = IndQueries ;
if ( SoftMaxQueries < UsedQueries + IndQueries )
{
QueriesToDo = ( IndQueries + 9 ) / 10 ; // we need to make progress, even if it means stalling and waiting for the GPU. At a minimum, we will do 10%
if ( SoftMaxQueries > UsedQueries + QueriesToDo )
{
// we can do more than the minimum
QueriesToDo = SoftMaxQueries - UsedQueries ;
}
}
if ( QueriesToDo = = IndQueries )
{
for ( int32 Index = 0 ; Index < IndQueries ; Index + + )
{
FOcclusionBounds * RunQueriesIter = & PendingIndividualQueriesWhenOptimizing [ Index ] ;
FPrimitiveOcclusionHistory * PrimitiveOcclusionHistory = ViewPrimitiveOcclusionHistory . Find ( RunQueriesIter - > PrimitiveOcclusionHistoryKey ) ;
PrimitiveOcclusionHistory - > SetCurrentQuery ( OcclusionFrameCounter ,
View . IndividualOcclusionQueries . BatchPrimitive ( RunQueriesIter - > BoundsOrigin , RunQueriesIter - > BoundsExtent , DynamicVertexBuffer ) ,
NumBufferedFrames ,
false ,
Params . bNeedsScanOnRead
) ;
}
}
else
{
check ( QueriesToDo < IndQueries ) ;
PendingIndividualQueriesWhenOptimizingSorter . Reserve ( PendingIndividualQueriesWhenOptimizing . Num ( ) ) ;
for ( int32 Index = 0 ; Index < IndQueries ; Index + + )
{
FOcclusionBounds * RunQueriesIter = & PendingIndividualQueriesWhenOptimizing [ Index ] ;
PendingIndividualQueriesWhenOptimizingSorter . Add ( RunQueriesIter ) ;
}
PendingIndividualQueriesWhenOptimizingSorter . Sort (
[ ] ( const FOcclusionBounds & A , const FOcclusionBounds & B )
{
return A . LastQuerySubmitFrame < B . LastQuerySubmitFrame ;
}
) ;
for ( int32 Index = 0 ; Index < QueriesToDo ; Index + + )
{
FOcclusionBounds * RunQueriesIter = PendingIndividualQueriesWhenOptimizingSorter [ Index ] ;
FPrimitiveOcclusionHistory * PrimitiveOcclusionHistory = ViewPrimitiveOcclusionHistory . Find ( RunQueriesIter - > PrimitiveOcclusionHistoryKey ) ;
PrimitiveOcclusionHistory - > SetCurrentQuery ( OcclusionFrameCounter ,
View . IndividualOcclusionQueries . BatchPrimitive ( RunQueriesIter - > BoundsOrigin , RunQueriesIter - > BoundsExtent , DynamicVertexBuffer ) ,
NumBufferedFrames ,
false ,
Params . bNeedsScanOnRead
) ;
}
}
// lets prevent this from staying too large for too long
if ( PendingIndividualQueriesWhenOptimizing . GetSlack ( ) > IndQueries * 4 )
{
PendingIndividualQueriesWhenOptimizing . Empty ( ) ;
PendingIndividualQueriesWhenOptimizingSorter . Empty ( ) ;
}
else
{
PendingIndividualQueriesWhenOptimizing . Reset ( ) ;
PendingIndividualQueriesWhenOptimizingSorter . Reset ( ) ;
}
}
return Params . NumOccludedPrims ;
}
}
/**
* Cull occluded primitives in the view .
*/
static int32 OcclusionCull ( FRHICommandListImmediate & RHICmdList , const FScene * Scene , FViewInfo & View , FGlobalDynamicVertexBuffer & DynamicVertexBuffer )
{
SCOPE_CYCLE_COUNTER ( STAT_OcclusionCull ) ;
RHICmdList . SetCurrentStat ( GET_STATID ( STAT_CLMM_OcclusionReadback ) ) ;
// INITVIEWS_TODO: This could be more efficient if broken up in to separate concerns:
// - What is occluded?
// - For which primitives should we render occlusion queries?
// - Generate occlusion query geometry.
int32 NumOccludedPrimitives = 0 ;
FSceneViewState * ViewState = ( FSceneViewState * ) View . State ;
// Disable HZB on OpenGL platforms to avoid rendering artifacts
// It can be forced on by setting HZBOcclusion to 2
bool bHZBOcclusion = ! IsOpenGLPlatform ( GShaderPlatformForFeatureLevel [ Scene - > GetFeatureLevel ( ) ] ) ;
bHZBOcclusion = bHZBOcclusion & & GHZBOcclusion ;
bHZBOcclusion = bHZBOcclusion & & FDataDrivenShaderPlatformInfo : : GetSupportsHZBOcclusion ( GShaderPlatformForFeatureLevel [ Scene - > GetFeatureLevel ( ) ] ) ;
bHZBOcclusion = bHZBOcclusion | | ( GHZBOcclusion = = 2 ) ;
// Use precomputed visibility data if it is available.
if ( View . PrecomputedVisibilityData )
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_LookupPrecomputedVisibility ) ;
FViewElementPDI OcclusionPDI ( & View , nullptr , nullptr ) ;
uint8 PrecomputedVisibilityFlags = EOcclusionFlags : : CanBeOccluded | EOcclusionFlags : : HasPrecomputedVisibility ;
for ( FSceneSetBitIterator BitIt ( View . PrimitiveVisibilityMap ) ; BitIt ; + + BitIt )
{
if ( ( Scene - > PrimitiveOcclusionFlags [ BitIt . GetIndex ( ) ] & PrecomputedVisibilityFlags ) = = PrecomputedVisibilityFlags )
{
FPrimitiveVisibilityId VisibilityId = Scene - > PrimitiveVisibilityIds [ BitIt . GetIndex ( ) ] ;
if ( ( View . PrecomputedVisibilityData [ VisibilityId . ByteIndex ] & VisibilityId . BitMask ) = = 0 )
{
View . PrimitiveVisibilityMap . AccessCorrespondingBit ( BitIt ) = false ;
INC_DWORD_STAT_BY ( STAT_StaticallyOccludedPrimitives , 1 ) ;
STAT ( NumOccludedPrimitives + + ) ;
if ( GVisualizeOccludedPrimitives )
{
const FBoxSphereBounds & Bounds = Scene - > PrimitiveOcclusionBounds [ BitIt . GetIndex ( ) ] ;
DrawWireBox ( & OcclusionPDI , Bounds . GetBox ( ) , FColor ( 100 , 50 , 50 ) , SDPG_Foreground ) ;
}
}
}
}
}
float CurrentRealTime = View . Family - > Time . GetRealTimeSeconds ( ) ;
if ( ViewState )
{
bool bSubmitQueries = ! View . bDisableQuerySubmissions ;
# if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
bSubmitQueries = bSubmitQueries & & ! ViewState - > HasViewParent ( ) & & ! ViewState - > bIsFrozen ;
# endif
if ( bHZBOcclusion )
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_MapHZBResults ) ;
check ( ! ViewState - > HZBOcclusionTests . IsValidFrame ( ViewState - > OcclusionFrameCounter ) ) ;
ViewState - > HZBOcclusionTests . MapResults ( RHICmdList ) ;
}
// Perform round-robin occlusion queries
if ( View . ViewState - > IsRoundRobinEnabled ( ) & &
! View . bIsSceneCapture & & // We only round-robin on the main renderer (not scene captures)
! View . bIgnoreExistingQueries & & // We do not alternate occlusion queries when we want to refresh the occlusion history
( IStereoRendering : : IsStereoEyeView ( View ) ) ) // Only relevant to stereo views
{
// For even frames, prevent left eye from occlusion querying
// For odd frames, prevent right eye from occlusion querying
const bool FrameParity = ( ( View . ViewState - > PrevFrameNumber & 0x01 ) = = 1 ) ;
bSubmitQueries & = ( FrameParity & & IStereoRendering : : IsAPrimaryView ( View ) ) | |
( ! FrameParity & & IStereoRendering : : IsASecondaryView ( View ) ) ;
}
View . ViewState - > PrimitiveOcclusionQueryPool . AdvanceFrame (
ViewState - > OcclusionFrameCounter ,
FOcclusionQueryHelpers : : GetNumBufferedFrames ( Scene - > GetFeatureLevel ( ) ) ,
View . ViewState - > IsRoundRobinEnabled ( ) & & ! View . bIsSceneCapture & & IStereoRendering : : IsStereoEyeView ( View ) ) ;
NumOccludedPrimitives + = FetchVisibilityForPrimitives ( Scene , View , bSubmitQueries , bHZBOcclusion , DynamicVertexBuffer ) ;
if ( bHZBOcclusion )
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_HZBUnmapResults ) ;
ViewState - > HZBOcclusionTests . UnmapResults ( RHICmdList ) ;
if ( bSubmitQueries )
{
ViewState - > HZBOcclusionTests . SetValidFrameNumber ( ViewState - > OcclusionFrameCounter ) ;
}
}
}
RHICmdList . SetCurrentStat ( GET_STATID ( STAT_CLMM_AfterOcclusionReadback ) ) ;
return NumOccludedPrimitives ;
}
const int32 InputsPrimNumPerRelevancePacket = 128 ;
const int32 AverageMeshBatchNumPerRelevancePacket = InputsPrimNumPerRelevancePacket * 2 ;
template < class T , int TAmplifyFactor = 1 >
struct FRelevancePrimSet
{
enum
{
MaxInputPrims = InputsPrimNumPerRelevancePacket - 1 , // leave space for NumPrims.
MaxOutputPrims = MaxInputPrims * TAmplifyFactor
} ;
int32 NumPrims ;
T Prims [ MaxOutputPrims ] ;
FORCEINLINE FRelevancePrimSet ( )
: NumPrims ( 0 )
{
//FMemory::Memzero(Prims, sizeof(T) * GetMaxOutputPrim());
}
FORCEINLINE void AddPrim ( T Prim )
{
checkSlow ( NumPrims < MaxOutputPrims ) ;
Prims [ NumPrims + + ] = Prim ;
}
FORCEINLINE bool IsFull ( ) const
{
return NumPrims > = MaxOutputPrims ;
}
template < class TARRAY >
FORCEINLINE void AppendTo ( TARRAY & DestArray )
{
DestArray . Append ( Prims , NumPrims ) ;
}
} ;
struct FMarkRelevantStaticMeshesForViewData
{
FVector ViewOrigin ;
int32 ForcedLODLevel ;
float LODScale ;
float MinScreenRadiusForCSMDepthSquared ;
float MinScreenRadiusForDepthPrepassSquared ;
bool bFullEarlyZPass ;
FMarkRelevantStaticMeshesForViewData ( 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 ;
MinScreenRadiusForCSMDepthSquared = GMinScreenRadiusForCSMDepth * GMinScreenRadiusForCSMDepth ;
MinScreenRadiusForDepthPrepassSquared = GMinScreenRadiusForDepthPrepass * GMinScreenRadiusForDepthPrepass ;
extern bool ShouldForceFullDepthPass ( EShaderPlatform ShaderPlatform ) ;
EShaderPlatform ShaderPlatform = View . GetShaderPlatform ( ) ;
if ( IsMobilePlatform ( ShaderPlatform ) )
{
FScene * Scene = View . Family - > Scene - > GetRenderScene ( ) ;
bFullEarlyZPass = ( Scene & & Scene - > EarlyZPassMode = = DDM_AllOpaque ) ;
}
else
{
bFullEarlyZPass = ShouldForceFullDepthPass ( ShaderPlatform ) ;
}
}
} ;
namespace EMarkMaskBits
{
enum Type
{
StaticMeshVisibilityMapMask = 0x2 ,
StaticMeshFadeOutDitheredLODMapMask = 0x10 ,
StaticMeshFadeInDitheredLODMapMask = 0x20 ,
} ;
}
typedef TArray < FVisibleMeshDrawCommand > FPassDrawCommandArray ;
typedef TArray < const FStaticMeshBatch * > FPassDrawCommandBuildRequestArray ;
struct FDrawCommandRelevancePacket
{
FDrawCommandRelevancePacket ( )
{
bUseCachedMeshDrawCommands = UseCachedMeshDrawCommands ( ) ;
for ( int32 PassIndex = 0 ; PassIndex < EMeshPass : : Num ; + + PassIndex )
{
NumDynamicBuildRequestElements [ PassIndex ] = 0 ;
}
}
FPassDrawCommandArray VisibleCachedDrawCommands [ EMeshPass : : Num ] ;
FPassDrawCommandBuildRequestArray DynamicBuildRequests [ EMeshPass : : Num ] ;
int32 NumDynamicBuildRequestElements [ EMeshPass : : Num ] ;
bool bUseCachedMeshDrawCommands ;
void AddCommandsForMesh (
int32 PrimitiveIndex ,
const FPrimitiveSceneInfo * InPrimitiveSceneInfo ,
const FStaticMeshBatchRelevance & RESTRICT StaticMeshRelevance ,
const FStaticMeshBatch & RESTRICT StaticMesh ,
const FScene * RESTRICT Scene ,
bool bCanCache ,
EMeshPass : : Type PassType )
{
const EShadingPath ShadingPath = Scene - > GetShadingPath ( ) ;
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 ,
FMeshDrawCommandPrimitiveIdInfo ( PrimitiveIndex , InPrimitiveSceneInfo - > GetInstanceSceneDataOffset ( ) ) ,
CachedMeshDrawCommand . StateBucketId ,
CachedMeshDrawCommand . MeshFillMode ,
CachedMeshDrawCommand . MeshCullMode ,
CachedMeshDrawCommand . Flags ,
CachedMeshDrawCommand . SortKey ) ;
}
}
else
{
NumDynamicBuildRequestElements [ PassType ] + = StaticMeshRelevance . NumElements ;
DynamicBuildRequests [ PassType ] . Add ( & StaticMesh ) ;
}
}
} ;
struct FRelevancePacket
{
const float CurrentWorldTime ;
const float DeltaWorldTime ;
FRHICommandListImmediate & RHICmdList ;
const FScene * Scene ;
const FViewInfo & View ;
const FViewCommands & ViewCommands ;
const uint8 ViewBit ;
const FMarkRelevantStaticMeshesForViewData & ViewData ;
FPrimitiveViewMasks & OutHasDynamicMeshElementsMasks ;
FPrimitiveViewMasks & OutHasDynamicEditorMeshElementsMasks ;
uint8 * RESTRICT MarkMasks ;
FRelevancePrimSet < int32 > Input ;
FRelevancePrimSet < int32 > RelevantStaticPrimitives ;
FRelevancePrimSet < int32 > NotDrawRelevant ;
FRelevancePrimSet < int32 > TranslucentSelfShadowPrimitives ;
FRelevancePrimSet < FPrimitiveSceneInfo * > VisibleDynamicPrimitivesWithSimpleLights ;
int32 NumVisibleDynamicPrimitives ;
int32 NumVisibleDynamicEditorPrimitives ;
FMeshPassMask VisibleDynamicMeshesPassMask ;
FTranslucenyPrimCount TranslucentPrimCount ;
bool bHasDistortionPrimitives ;
bool bHasCustomDepthPrimitives ;
FRelevancePrimSet < FPrimitiveSceneInfo * > LazyUpdatePrimitives ;
FRelevancePrimSet < FPrimitiveSceneInfo * > DirtyIndirectLightingCacheBufferPrimitives ;
FRelevancePrimSet < FPrimitiveSceneInfo * > RecachedReflectionCapturePrimitives ;
# if WITH_EDITOR
FRelevancePrimSet < FPrimitiveSceneInfo * > EditorVisualizeLevelInstancePrimitives ;
FRelevancePrimSet < FPrimitiveSceneInfo * > EditorSelectedPrimitives ;
# endif
TArray < FMeshDecalBatch > MeshDecalBatches ;
TArray < FVolumetricMeshBatch > VolumetricMeshBatches ;
TArray < FSkyMeshBatch > SkyMeshBatches ;
TArray < FSortedTrianglesMeshBatch > SortedTrianglesMeshBatches ;
FDrawCommandRelevancePacket DrawCommandPacket ;
TSet < uint32 > CustomDepthStencilValues ;
struct FPrimitiveLODMask
{
FPrimitiveLODMask ( )
: PrimitiveIndex ( INDEX_NONE )
{ }
FPrimitiveLODMask ( const int32 InPrimitiveIndex , const FLODMask & InLODMask )
: PrimitiveIndex ( InPrimitiveIndex )
, LODMask ( InLODMask )
{ }
int32 PrimitiveIndex ;
FLODMask LODMask ;
} ;
FRelevancePrimSet < FPrimitiveLODMask > PrimitivesLODMask ; // group both lod mask with primitive index to be able to properly merge them in the view
uint16 CombinedShadingModelMask ;
bool bUsesGlobalDistanceField ;
bool bUsesLightingChannels ;
bool bTranslucentSurfaceLighting ;
bool bUsesSceneDepth ;
bool bUsesCustomDepth ;
bool bUsesCustomStencil ;
bool bSceneHasSkyMaterial ;
bool bHasSingleLayerWaterMaterial ;
bool bHasTranslucencySeparateModulation ;
FRelevancePacket (
FRHICommandListImmediate & InRHICmdList ,
const FScene * InScene ,
const FViewInfo & InView ,
const FViewCommands & InViewCommands ,
uint8 InViewBit ,
const FMarkRelevantStaticMeshesForViewData & InViewData ,
FPrimitiveViewMasks & InOutHasDynamicMeshElementsMasks ,
FPrimitiveViewMasks & InOutHasDynamicEditorMeshElementsMasks ,
uint8 * InMarkMasks )
: CurrentWorldTime ( InView . Family - > Time . GetWorldTimeSeconds ( ) )
, DeltaWorldTime ( InView . Family - > Time . GetDeltaWorldTimeSeconds ( ) )
, RHICmdList ( InRHICmdList )
, Scene ( InScene )
, View ( InView )
, ViewCommands ( InViewCommands )
, ViewBit ( InViewBit )
, ViewData ( InViewData )
, OutHasDynamicMeshElementsMasks ( InOutHasDynamicMeshElementsMasks )
, OutHasDynamicEditorMeshElementsMasks ( InOutHasDynamicEditorMeshElementsMasks )
, MarkMasks ( InMarkMasks )
, NumVisibleDynamicPrimitives ( 0 )
, NumVisibleDynamicEditorPrimitives ( 0 )
, bHasDistortionPrimitives ( false )
, bHasCustomDepthPrimitives ( false )
, CombinedShadingModelMask ( 0 )
, bUsesGlobalDistanceField ( false )
, bUsesLightingChannels ( false )
, bTranslucentSurfaceLighting ( false )
, bUsesSceneDepth ( false )
, bUsesCustomDepth ( false )
, bUsesCustomStencil ( false )
, bSceneHasSkyMaterial ( false )
, bHasSingleLayerWaterMaterial ( false )
, bHasTranslucencySeparateModulation ( false )
{
}
void AnyThreadTask ( )
{
FOptionalTaskTagScope Scope ( ETaskTag : : EParallelRenderingThread ) ;
ComputeRelevance ( ) ;
MarkRelevant ( ) ;
}
void ComputeRelevance ( )
{
CombinedShadingModelMask = 0 ;
bSceneHasSkyMaterial = 0 ;
bHasSingleLayerWaterMaterial = 0 ;
bHasTranslucencySeparateModulation = 0 ;
bUsesGlobalDistanceField = false ;
bUsesLightingChannels = false ;
bTranslucentSurfaceLighting = false ;
const EShadingPath ShadingPath = Scene - > GetShadingPath ( ) ;
const bool bAddLightmapDensityCommands = View . Family - > EngineShowFlags . LightMapDensity & & AllowDebugViewmodes ( ) ;
SCOPE_CYCLE_COUNTER ( STAT_ComputeViewRelevance ) ;
for ( int32 Index = 0 ; Index < Input . NumPrims ; Index + + )
{
int32 BitIndex = Input . Prims [ Index ] ;
FPrimitiveSceneInfo * PrimitiveSceneInfo = Scene - > Primitives [ BitIndex ] ;
FPrimitiveViewRelevance & ViewRelevance = const_cast < FPrimitiveViewRelevance & > ( View . PrimitiveViewRelevanceMap [ BitIndex ] ) ;
ViewRelevance = PrimitiveSceneInfo - > Proxy - > 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 bHairStrandsEnabled = ViewRelevance . bHairStrands & & IsHairStrandsEnabled ( EHairStrandsShaderType : : All , Scene - > GetShaderPlatform ( ) ) ;
if ( View . bIsReflectionCapture & & ! PrimitiveSceneInfo - > Proxy - > IsVisibleInReflectionCaptures ( ) )
{
NotDrawRelevant . AddPrim ( BitIndex ) ;
continue ;
}
if ( bStaticRelevance & & ( bDrawRelevance | | bShadowRelevance ) )
{
RelevantStaticPrimitives . AddPrim ( BitIndex ) ;
}
if ( ! bDrawRelevance )
{
NotDrawRelevant . AddPrim ( BitIndex ) ;
continue ;
}
# if WITH_EDITOR
if ( bEditorVisualizeLevelInstanceRelevance )
{
EditorVisualizeLevelInstancePrimitives . AddPrim ( PrimitiveSceneInfo ) ;
}
if ( bEditorSelectionRelevance )
{
EditorSelectedPrimitives . AddPrim ( PrimitiveSceneInfo ) ;
}
# endif
if ( bEditorRelevance )
{
+ + NumVisibleDynamicEditorPrimitives ;
if ( GIsEditor )
{
OutHasDynamicEditorMeshElementsMasks [ BitIndex ] | = ViewBit ;
}
}
else if ( bDynamicRelevance )
{
// Keep track of visible dynamic primitives.
+ + NumVisibleDynamicPrimitives ;
OutHasDynamicMeshElementsMasks [ BitIndex ] | = ViewBit ;
if ( ViewRelevance . bHasSimpleLights )
{
VisibleDynamicPrimitivesWithSimpleLights . AddPrim ( PrimitiveSceneInfo ) ;
}
}
else if ( bHairStrandsEnabled )
{
// Strands MeshElement
+ + NumVisibleDynamicPrimitives ;
OutHasDynamicMeshElementsMasks [ BitIndex ] | = ViewBit ;
}
if ( bTranslucentRelevance & & ! bEditorRelevance & & ViewRelevance . bRenderInMainPass )
{
if ( View . Family - > AllowTranslucencyAfterDOF ( ) )
{
if ( ViewRelevance . bNormalTranslucency )
{
TranslucentPrimCount . Add ( ETranslucencyPass : : TPT_StandardTranslucency , ViewRelevance . bUsesSceneColorCopy ) ;
}
if ( ViewRelevance . bSeparateTranslucency )
{
TranslucentPrimCount . Add ( ETranslucencyPass : : TPT_TranslucencyAfterDOF , ViewRelevance . bUsesSceneColorCopy ) ;
}
if ( ViewRelevance . bSeparateTranslucencyModulate )
{
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 ( ViewRelevance . bDistortion )
{
bHasDistortionPrimitives = true ;
}
}
CombinedShadingModelMask | = ViewRelevance . ShadingModelMask ;
bUsesGlobalDistanceField | = ViewRelevance . bUsesGlobalDistanceField ;
bUsesLightingChannels | = ViewRelevance . bUsesLightingChannels ;
bTranslucentSurfaceLighting | = ViewRelevance . bTranslucentSurfaceLighting ;
bUsesSceneDepth | = ViewRelevance . bUsesSceneDepth ;
bUsesCustomDepth | = ( ViewRelevance . CustomDepthStencilUsageMask & 1 ) > 0 ;
bUsesCustomStencil | = ( ViewRelevance . CustomDepthStencilUsageMask & ( 1 < < 1 ) ) > 0 ;
bSceneHasSkyMaterial | = ViewRelevance . bUsesSkyMaterial ;
bHasSingleLayerWaterMaterial | = ViewRelevance . bUsesSingleLayerWaterMaterial ;
bHasTranslucencySeparateModulation | = ViewRelevance . bSeparateTranslucencyModulate ;
if ( ViewRelevance . bRenderCustomDepth )
{
bHasCustomDepthPrimitives = true ;
CustomDepthStencilValues . Add ( PrimitiveSceneInfo - > Proxy - > GetCustomDepthStencilValue ( ) ) ;
}
extern bool GUseTranslucencyShadowDepths ;
if ( GUseTranslucencyShadowDepths & & ViewRelevance . bTranslucentSelfShadow )
{
TranslucentSelfShadowPrimitives . AddPrim ( BitIndex ) ;
}
// INITVIEWS_TODO: Do this in a separate pass? There are no dependencies
// here except maybe ParentPrimitives. This could be done in a
// low-priority background task and forgotten about.
PrimitiveSceneInfo - > LastRenderTime = CurrentWorldTime ;
// If the primitive is definitely unoccluded or if in Wireframe mode and the primitive is estimated
// to be unoccluded, then update the primitive components's LastRenderTime
// on the game thread. This signals that the primitive is visible.
if ( View . PrimitiveDefinitelyUnoccludedMap [ BitIndex ] | | ( View . Family - > EngineShowFlags . Wireframe & & View . PrimitiveVisibilityMap [ BitIndex ] ) )
{
PrimitiveSceneInfo - > UpdateComponentLastRenderTime ( CurrentWorldTime , /*bUpdateLastRenderTimeOnScreen=*/ true ) ;
}
// 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 ( ) ;
// With forward shading we need to track reflection capture cache updates
// in order to update primitive's uniform buffer's closest reflection capture id.
if ( IsForwardShadingEnabled ( Scene - > GetShaderPlatform ( ) ) )
{
RecachedReflectionCapturePrimitives . AddPrim ( PrimitiveSceneInfo ) ;
}
}
if ( PrimitiveSceneInfo - > NeedsUniformBufferUpdate ( ) )
{
LazyUpdatePrimitives . AddPrim ( PrimitiveSceneInfo ) ;
}
if ( PrimitiveSceneInfo - > NeedsIndirectLightingCacheBufferUpdate ( ) )
{
DirtyIndirectLightingCacheBufferPrimitives . AddPrim ( PrimitiveSceneInfo ) ;
}
}
}
void MarkRelevant ( )
{
SCOPE_CYCLE_COUNTER ( STAT_StaticRelevance ) ;
// using a local counter to reduce memory traffic
int32 NumVisibleStaticMeshElements = 0 ;
FViewInfo & WriteView = const_cast < FViewInfo & > ( View ) ;
const FSceneViewState * ViewState = ( FSceneViewState * ) View . State ;
const EShadingPath ShadingPath = Scene - > GetShadingPath ( ) ;
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 bHLODActive = Scene - > SceneLODHierarchy . IsActive ( ) ;
const FHLODVisibilityState * const HLODState = bHLODActive & & ViewState ? & ViewState - > HLODVisibilityState : nullptr ;
float MaxDrawDistanceScale = GetCachedScalabilityCVars ( ) . ViewDistanceScale ;
MaxDrawDistanceScale * = GetCachedScalabilityCVars ( ) . CalculateFieldOfViewDistanceScale ( View . DesiredFOV ) ;
for ( int32 StaticPrimIndex = 0 , Num = RelevantStaticPrimitives . NumPrims ; StaticPrimIndex < Num ; + + StaticPrimIndex )
{
int32 PrimitiveIndex = RelevantStaticPrimitives . Prims [ StaticPrimIndex ] ;
const FPrimitiveSceneInfo * RESTRICT PrimitiveSceneInfo = Scene - > Primitives [ PrimitiveIndex ] ;
const FPrimitiveBounds & Bounds = Scene - > PrimitiveBounds [ PrimitiveIndex ] ;
const FPrimitiveViewRelevance & ViewRelevance = View . PrimitiveViewRelevanceMap [ PrimitiveIndex ] ;
const bool bIsPrimitiveDistanceCullFading = View . PrimitiveFadeUniformBufferMap [ PrimitiveIndex ] ;
const int8 CurFirstLODIdx = PrimitiveSceneInfo - > Proxy - > GetCurrentFirstLODIdx_RenderThread ( ) ;
check ( CurFirstLODIdx > = 0 ) ;
float MeshScreenSizeSquared = 0 ;
FLODMask LODToRender = ComputeLODForMeshes ( PrimitiveSceneInfo - > StaticMeshRelevances , View , Bounds . BoxSphereBounds . Origin , Bounds . BoxSphereBounds . SphereRadius , ViewData . ForcedLODLevel , MeshScreenSizeSquared , CurFirstLODIdx , 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 ( ) ;
float DistanceSquared = ( Bounds . BoxSphereBounds . Origin - ViewData . ViewOrigin ) . SizeSquared ( ) ;
const float LODFactorDistanceSquared = DistanceSquared * FMath : : Square ( ViewData . LODScale ) ;
const bool bDrawShadowDepth = FMath : : Square ( Bounds . BoxSphereBounds . SphereRadius ) > ViewData . MinScreenRadiusForCSMDepthSquared * LODFactorDistanceSquared ;
const bool bDrawDepthOnly = ViewData . bFullEarlyZPass | | ( ( ShadingPath ! = EShadingPath : : Mobile ) & & ( FMath : : Square ( Bounds . BoxSphereBounds . SphereRadius ) > GMinScreenRadiusForDepthPrepass * GMinScreenRadiusForDepthPrepass * LODFactorDistanceSquared ) ) ;
const bool bAddLightmapDensityCommands = View . Family - > EngineShowFlags . LightMapDensity & & AllowDebugViewmodes ( ) ;
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 ( StaticMesh . bOverlayMaterial & & ! View . Family - > EngineShowFlags . DistanceCulledPrimitives )
{
// Overlay mesh can have a his own cull distance that is shorter than primitive cull distance
float OverlayMaterialMaxDrawDistance = StaticMeshRelevance . ScreenSize ;
if ( OverlayMaterialMaxDrawDistance > 1.f & & OverlayMaterialMaxDrawDistance ! = FLT_MAX )
{
if ( DistanceSquared > FMath : : Square ( OverlayMaterialMaxDrawDistance * MaxDrawDistanceScale ) )
{
// distance culled
continue ;
}
}
}
if ( LODToRender . ContainsLOD ( StaticMeshRelevance . LODIndex ) )
{
uint8 MarkMask = 0 ;
bool bHiddenByHLODFade = false ; // Hide mesh LOD levels that HLOD is substituting
if ( bIsHLODFading )
{
if ( bIsHLODFadingOut )
{
if ( bIsLODDithered & & LODToRender . DitheredLODIndices [ 1 ] = = StaticMeshRelevance . LODIndex )
{
bHiddenByHLODFade = true ;
}
else
{
MarkMask | = EMarkMaskBits : : StaticMeshFadeOutDitheredLODMapMask ;
}
}
else
{
if ( bIsLODDithered & & LODToRender . DitheredLODIndices [ 0 ] = = StaticMeshRelevance . LODIndex )
{
bHiddenByHLODFade = true ;
}
else
{
MarkMask | = EMarkMaskBits : : StaticMeshFadeInDitheredLODMapMask ;
}
}
}
else if ( bIsLODDithered )
{
if ( LODToRender . DitheredLODIndices [ 0 ] = = StaticMeshRelevance . LODIndex )
{
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 ;
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 ( ) )
{
const FPrimitiveSceneProxy * PrimitiveSceneProxy = PrimitiveSceneInfo - > Proxy ;
if ( FVelocityMeshProcessor : : PrimitiveHasVelocityForView ( View , PrimitiveSceneProxy ) )
{
if ( ViewRelevance . bVelocityRelevance & &
FOpaqueVelocityMeshProcessor : : PrimitiveCanHaveVelocity ( View . GetShaderPlatform ( ) , PrimitiveSceneProxy ) & &
FOpaqueVelocityMeshProcessor : : PrimitiveHasVelocityForFrame ( PrimitiveSceneProxy ) )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : Velocity ) ;
bIsMeshInVelocityPass = true ;
}
if ( ViewRelevance . bOutputsTranslucentVelocity & &
FTranslucentVelocityMeshProcessor : : PrimitiveCanHaveVelocity ( View . GetShaderPlatform ( ) , PrimitiveSceneProxy ) & &
FTranslucentVelocityMeshProcessor : : PrimitiveHasVelocityForFrame ( PrimitiveSceneProxy ) )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : TranslucentVelocity ) ;
}
}
}
}
// Add depth commands.
if ( StaticMeshRelevance . bUseForDepthPass & & ( bDrawDepthOnly | | ( bMobileMaskedInEarlyPass & & ViewRelevance . bMasked ) ) )
{
if ( ! ( bIsMeshInVelocityPass & & bVelocityPassWritesDepth ) )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : DepthPass ) ;
}
# if RHI_RAYTRACING
if ( IsRayTracingEnabled ( ) )
{
if ( MarkMask & EMarkMaskBits : : StaticMeshFadeOutDitheredLODMapMask )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , 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 )
{
// Skydome must not be added to base pass bucket
if ( ! StaticMeshRelevance . bUseSkyMaterial )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : BasePass ) ;
if ( ! bMobileBasePassAlwaysUsesCSM )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : MobileBasePassCSM ) ;
}
}
else
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , 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 , Scene , bCanCache , EMeshPass : : BasePass ) ;
MarkMask | = EMarkMaskBits : : StaticMeshVisibilityMapMask ;
if ( StaticMeshRelevance . bUseSkyMaterial )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : SkyPass ) ;
}
if ( StaticMeshRelevance . bUseSingleLayerWaterMaterial )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : SingleLayerWaterPass ) ;
}
}
if ( StaticMeshRelevance . bUseAnisotropy )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : AnisotropyPass ) ;
}
if ( ViewRelevance . bRenderCustomDepth )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : CustomDepth ) ;
}
if ( bAddLightmapDensityCommands )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : LightmapDensity ) ;
}
# if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
else if ( View . Family - > UseDebugViewPS ( ) )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : DebugViewMode ) ;
}
# endif
# if WITH_EDITOR
if ( StaticMeshRelevance . bSelectable )
{
if ( View . bAllowTranslucentPrimitivesInHitProxy )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : HitProxy ) ;
}
else
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , 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 )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : TranslucencyStandard ) ;
}
if ( ViewRelevance . bSeparateTranslucency )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : TranslucencyAfterDOF ) ;
}
if ( ViewRelevance . bSeparateTranslucencyModulate )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : TranslucencyAfterDOFModulate ) ;
}
if ( ViewRelevance . bPostMotionBlurTranslucency )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , 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 , Scene , bCanCache , EMeshPass : : TranslucencyAll ) ;
}
if ( ViewRelevance . bTranslucentSurfaceLighting )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : LumenTranslucencyRadianceCacheMark ) ;
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : LumenFrontLayerTranslucencyGBuffer ) ;
}
if ( ViewRelevance . bDistortion )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : Distortion ) ;
}
}
# if WITH_EDITOR
if ( ViewRelevance . bEditorVisualizeLevelInstanceRelevance )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : EditorLevelInstance ) ;
}
if ( ViewRelevance . bEditorStaticSelectionRelevance )
{
DrawCommandPacket . AddCommandsForMesh ( PrimitiveIndex , PrimitiveSceneInfo , StaticMeshRelevance , StaticMesh , Scene , bCanCache , EMeshPass : : EditorSelection ) ;
}
# endif
if ( ViewRelevance . bHasVolumeMaterialDomain )
{
VolumetricMeshBatches . AddUninitialized ( 1 ) ;
FVolumetricMeshBatch & BatchAndProxy = VolumetricMeshBatches . Last ( ) ;
BatchAndProxy . Mesh = & StaticMesh ;
BatchAndProxy . Proxy = PrimitiveSceneInfo - > Proxy ;
}
if ( ViewRelevance . bUsesSkyMaterial )
{
SkyMeshBatches . AddUninitialized ( 1 ) ;
FSkyMeshBatch & BatchAndProxy = SkyMeshBatches . Last ( ) ;
BatchAndProxy . Mesh = & StaticMesh ;
BatchAndProxy . Proxy = PrimitiveSceneInfo - > Proxy ;
BatchAndProxy . bVisibleInMainPass = ViewRelevance . bRenderInMainPass ;
BatchAndProxy . bVisibleInRealTimeSkyCapture = PrimitiveSceneInfo - > bVisibleInRealTimeSkyCapture ;
}
if ( ViewRelevance . HasTranslucency ( ) & & PrimitiveSceneInfo - > Proxy - > SupportsSortedTriangles ( ) ) // Need to check material as well
{
SortedTrianglesMeshBatches . AddUninitialized ( 1 ) ;
FSortedTrianglesMeshBatch & BatchAndProxy = SortedTrianglesMeshBatches . Last ( ) ;
BatchAndProxy . Mesh = & StaticMesh ;
BatchAndProxy . Proxy = PrimitiveSceneInfo - > Proxy ;
}
// 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 )
{
MeshDecalBatches . AddUninitialized ( 1 ) ;
FMeshDecalBatch & BatchAndProxy = MeshDecalBatches . Last ( ) ;
BatchAndProxy . Mesh = & StaticMesh ;
BatchAndProxy . Proxy = PrimitiveSceneInfo - > Proxy ;
BatchAndProxy . SortKey = PrimitiveSceneInfo - > Proxy - > GetTranslucencySortPriority ( ) ;
}
}
if ( MarkMask )
{
MarkMasks [ StaticMeshRelevance . Id ] = MarkMask ;
}
}
}
}
static_assert ( sizeof ( WriteView . NumVisibleStaticMeshElements ) = = sizeof ( int32 ) , " Atomic is the wrong size " ) ;
FPlatformAtomics : : InterlockedAdd ( ( volatile int32 * ) & WriteView . NumVisibleStaticMeshElements , NumVisibleStaticMeshElements ) ;
}
void RenderThreadFinalize ( )
{
FViewInfo & WriteView = const_cast < FViewInfo & > ( View ) ;
FViewCommands & WriteViewCommands = const_cast < FViewCommands & > ( ViewCommands ) ;
for ( int32 Index = 0 ; Index < NotDrawRelevant . NumPrims ; Index + + )
{
WriteView . PrimitiveVisibilityMap [ NotDrawRelevant . Prims [ Index ] ] = false ;
}
# if WITH_EDITOR
auto AddRelevantHitProxiesToArray = [ ] ( FRelevancePrimSet < FPrimitiveSceneInfo * > & PrimSet , TArray < uint32 > & OutHitProxyArray )
{
int32 TotalHitProxiesToAdd = 0 ;
for ( int32 Idx = 0 ; Idx < PrimSet . NumPrims ; + + Idx )
{
if ( PrimSet . Prims [ Idx ] - > NaniteHitProxyIds . Num ( ) )
{
TotalHitProxiesToAdd + = PrimSet . Prims [ Idx ] - > NaniteHitProxyIds . Num ( ) ;
}
}
OutHitProxyArray . Reserve ( OutHitProxyArray . Num ( ) + TotalHitProxiesToAdd ) ;
for ( int32 Idx = 0 ; Idx < PrimSet . NumPrims ; + + Idx )
{
if ( PrimSet . Prims [ Idx ] - > NaniteHitProxyIds . Num ( ) )
{
for ( uint32 IdValue : PrimSet . Prims [ Idx ] - > NaniteHitProxyIds )
{
OutHitProxyArray . Add ( IdValue ) ;
}
}
}
} ;
// Add hit proxies from editing LevelInstance Nanite primitives
AddRelevantHitProxiesToArray ( EditorVisualizeLevelInstancePrimitives , WriteView . EditorVisualizeLevelInstanceIds ) ;
// Add hit proxies from selected Nanite primitives.
AddRelevantHitProxiesToArray ( EditorSelectedPrimitives , WriteView . EditorSelectedHitProxyIds ) ;
# endif
WriteView . ShadingModelMaskInView | = CombinedShadingModelMask ;
WriteView . bUsesGlobalDistanceField | = bUsesGlobalDistanceField ;
WriteView . bUsesLightingChannels | = bUsesLightingChannels ;
WriteView . bTranslucentSurfaceLighting | = bTranslucentSurfaceLighting ;
WriteView . bUsesSceneDepth | = bUsesSceneDepth ;
WriteView . bSceneHasSkyMaterial | = bSceneHasSkyMaterial ;
WriteView . bHasSingleLayerWaterMaterial | = bHasSingleLayerWaterMaterial ;
WriteView . bHasTranslucencySeparateModulation | = bHasTranslucencySeparateModulation ;
VisibleDynamicPrimitivesWithSimpleLights . AppendTo ( WriteView . VisibleDynamicPrimitivesWithSimpleLights ) ;
WriteView . NumVisibleDynamicPrimitives + = NumVisibleDynamicPrimitives ;
WriteView . NumVisibleDynamicEditorPrimitives + = NumVisibleDynamicEditorPrimitives ;
WriteView . TranslucentPrimCount . Append ( TranslucentPrimCount ) ;
WriteView . bHasDistortionPrimitives | = bHasDistortionPrimitives ;
WriteView . bHasCustomDepthPrimitives | = bHasCustomDepthPrimitives ;
WriteView . CustomDepthStencilValues . Append ( CustomDepthStencilValues ) ;
WriteView . bUsesCustomDepth | = bUsesCustomDepth ;
WriteView . bUsesCustomStencil | = bUsesCustomStencil ;
DirtyIndirectLightingCacheBufferPrimitives . AppendTo ( WriteView . DirtyIndirectLightingCacheBufferPrimitives ) ;
WriteView . MeshDecalBatches . Append ( MeshDecalBatches ) ;
WriteView . VolumetricMeshBatches . Append ( VolumetricMeshBatches ) ;
WriteView . SkyMeshBatches . Append ( SkyMeshBatches ) ;
WriteView . SortedTrianglesMeshBatches . Append ( SortedTrianglesMeshBatches ) ;
for ( int32 Index = 0 ; Index < RecachedReflectionCapturePrimitives . NumPrims ; + + Index )
{
FPrimitiveSceneInfo * PrimitiveSceneInfo = RecachedReflectionCapturePrimitives . Prims [ Index ] ;
PrimitiveSceneInfo - > SetNeedsUniformBufferUpdate ( true ) ;
PrimitiveSceneInfo - > ConditionalUpdateUniformBuffer ( RHICmdList ) ;
FScene & WriteScene = * const_cast < FScene * > ( Scene ) ;
WriteScene . GPUScene . AddPrimitiveToUpdate ( PrimitiveSceneInfo - > GetIndex ( ) , EPrimitiveDirtyState : : ChangedAll ) ;
}
for ( int32 Index = 0 ; Index < LazyUpdatePrimitives . NumPrims ; Index + + )
{
LazyUpdatePrimitives . Prims [ Index ] - > ConditionalUpdateUniformBuffer ( RHICmdList ) ;
}
for ( int32 i = 0 ; i < PrimitivesLODMask . NumPrims ; + + i )
{
WriteView . PrimitivesLODMask [ PrimitivesLODMask . Prims [ i ] . PrimitiveIndex ] = PrimitivesLODMask . Prims [ i ] . LODMask ;
}
for ( int32 PassIndex = 0 ; PassIndex < EMeshPass : : Num ; PassIndex + + )
{
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 < const FStaticMeshBatch * , SceneRenderingAllocator > & 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 ] ) ) ;
}
WriteViewCommands . NumDynamicMeshCommandBuildRequestElements [ PassIndex ] + = DrawCommandPacket . NumDynamicBuildRequestElements [ PassIndex ] ;
}
// Prepare translucent self shadow uniform buffers.
for ( int32 Index = 0 ; Index < TranslucentSelfShadowPrimitives . NumPrims ; + + Index )
{
const int32 PrimitiveIndex = TranslucentSelfShadowPrimitives . Prims [ Index ] ;
FUniformBufferRHIRef & UniformBuffer = WriteView . TranslucentSelfShadowUniformBufferMap . FindOrAdd ( PrimitiveIndex ) ;
if ( ! UniformBuffer )
{
FTranslucentSelfShadowUniformParameters Parameters ;
SetupTranslucentSelfShadowUniformParameters ( nullptr , Parameters ) ;
UniformBuffer = FTranslucentSelfShadowUniformParameters : : CreateUniformBuffer ( Parameters , EUniformBufferUsage : : UniformBuffer_SingleFrame ) ;
}
}
}
} ;
static void ComputeAndMarkRelevanceForViewParallel (
FRHICommandListImmediate & RHICmdList ,
const FScene * Scene ,
FViewInfo & View ,
FViewCommands & ViewCommands ,
uint8 ViewBit ,
FPrimitiveViewMasks & OutHasDynamicMeshElementsMasks ,
FPrimitiveViewMasks & OutHasDynamicEditorMeshElementsMasks
)
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FSceneRenderer_ComputeAndMarkRelevanceForViewParallel ) ;
check ( OutHasDynamicMeshElementsMasks . Num ( ) = = Scene - > Primitives . Num ( ) ) ;
FFrozenSceneViewMatricesGuard FrozenMatricesGuard ( View ) ;
const FMarkRelevantStaticMeshesForViewData ViewData ( View ) ;
int32 NumMesh = View . StaticMeshVisibilityMap . Num ( ) ;
uint8 * RESTRICT MarkMasks = ( uint8 * ) FMemStack : : Get ( ) . Alloc ( NumMesh + 31 , 8 ) ; // some padding to simplify the high speed transpose
FMemory : : Memzero ( MarkMasks , NumMesh + 31 ) ;
int32 EstimateOfNumPackets = NumMesh / ( FRelevancePrimSet < int32 > : : MaxInputPrims * 4 ) ;
TArray < FRelevancePacket * , SceneRenderingAllocator > Packets ;
Packets . Reserve ( EstimateOfNumPackets ) ;
bool WillExecuteInParallel = FApp : : ShouldUseThreadingForPerformance ( ) & & CVarParallelInitViews . GetValueOnRenderThread ( ) > 0 & & IsInActualRenderingThread ( ) ;
{
FSceneSetBitIterator BitIt ( View . PrimitiveVisibilityMap ) ;
if ( BitIt )
{
FRelevancePacket * Packet = new ( FMemStack : : Get ( ) ) FRelevancePacket (
RHICmdList ,
Scene ,
View ,
ViewCommands ,
ViewBit ,
ViewData ,
OutHasDynamicMeshElementsMasks ,
OutHasDynamicEditorMeshElementsMasks ,
MarkMasks ) ;
Packets . Add ( Packet ) ;
while ( 1 )
{
Packet - > Input . AddPrim ( BitIt . GetIndex ( ) ) ;
+ + BitIt ;
if ( Packet - > Input . IsFull ( ) | | ! BitIt )
{
if ( ! BitIt )
{
break ;
}
else
{
Packet = new ( FMemStack : : Get ( ) ) FRelevancePacket (
RHICmdList ,
Scene ,
View ,
ViewCommands ,
ViewBit ,
ViewData ,
OutHasDynamicMeshElementsMasks ,
OutHasDynamicEditorMeshElementsMasks ,
MarkMasks ) ;
Packets . Add ( Packet ) ;
}
}
}
}
}
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_ComputeAndMarkRelevanceForViewParallel_ParallelFor ) ;
ParallelFor ( Packets . Num ( ) ,
[ & Packets ] ( int32 Index )
{
Packets [ Index ] - > AnyThreadTask ( ) ;
} ,
! WillExecuteInParallel
) ;
}
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_ComputeAndMarkRelevanceForViewParallel_RenderThreadFinalize ) ;
for ( int32 PassIndex = 0 ; PassIndex < EMeshPass : : Num ; PassIndex + + )
{
int32 NumVisibleCachedMeshDrawCommands = 0 ;
int32 NumDynamicBuildRequests = 0 ;
for ( auto Packet : Packets )
{
NumVisibleCachedMeshDrawCommands + = Packet - > DrawCommandPacket . VisibleCachedDrawCommands [ PassIndex ] . Num ( ) ;
NumDynamicBuildRequests + = Packet - > DrawCommandPacket . DynamicBuildRequests [ PassIndex ] . Num ( ) ;
}
ViewCommands . MeshCommands [ PassIndex ] . Reserve ( NumVisibleCachedMeshDrawCommands ) ;
ViewCommands . DynamicMeshCommandBuildRequests [ PassIndex ] . Reserve ( NumDynamicBuildRequests ) ;
}
for ( auto Packet : Packets )
{
Packet - > RenderThreadFinalize ( ) ;
Packet - > ~ FRelevancePacket ( ) ;
}
Packets . Empty ( ) ;
}
QUICK_SCOPE_CYCLE_COUNTER ( STAT_ComputeAndMarkRelevanceForViewParallel_TransposeMeshBits ) ;
check ( View . StaticMeshVisibilityMap . Num ( ) = = NumMesh & &
View . StaticMeshFadeOutDitheredLODMap . Num ( ) = = NumMesh & &
View . StaticMeshFadeInDitheredLODMap . Num ( ) = = NumMesh
) ;
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 ( int32 BaseIndex = 0 ; BaseIndex < NumMesh ; 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 + + ;
}
}
void ComputeDynamicMeshRelevance ( EShadingPath ShadingPath , bool bAddLightmapDensityCommands , const FPrimitiveViewRelevance & ViewRelevance , const FMeshBatchAndRelevance & MeshBatch , FViewInfo & View , FMeshPassMask & PassMask , FPrimitiveSceneInfo * PrimitiveSceneInfo , const FPrimitiveBounds & Bounds )
{
const int32 NumElements = MeshBatch . Mesh - > Elements . Num ( ) ;
if ( ViewRelevance . bDrawRelevance & & ( ViewRelevance . bRenderInMainPass | | ViewRelevance . bRenderCustomDepth | | ViewRelevance . bRenderInDepthPass ) )
{
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 ( ViewRelevance . bOutputsTranslucentVelocity )
{
PassMask . Set ( EMeshPass : : TranslucentVelocity ) ;
View . NumVisibleDynamicMeshElements [ EMeshPass : : TranslucentVelocity ] + = NumElements ;
}
if ( ViewRelevance . bUsesSingleLayerWaterMaterial )
{
PassMask . Set ( EMeshPass : : SingleLayerWaterPass ) ;
View . NumVisibleDynamicMeshElements [ EMeshPass : : SingleLayerWaterPass ] + = NumElements ;
}
}
}
if ( ViewRelevance . HasTranslucency ( )
& & ! ViewRelevance . bEditorPrimitiveRelevance
& & ViewRelevance . bRenderInMainPass )
{
if ( View . Family - > AllowTranslucencyAfterDOF ( ) )
{
if ( ViewRelevance . bNormalTranslucency )
{
PassMask . Set ( EMeshPass : : TranslucencyStandard ) ;
View . NumVisibleDynamicMeshElements [ EMeshPass : : TranslucencyStandard ] + = NumElements ;
}
if ( ViewRelevance . bSeparateTranslucency )
{
PassMask . Set ( EMeshPass : : TranslucencyAfterDOF ) ;
View . NumVisibleDynamicMeshElements [ EMeshPass : : TranslucencyAfterDOF ] + = NumElements ;
}
if ( ViewRelevance . bSeparateTranslucencyModulate )
{
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 ;
}
if ( ViewRelevance . bTranslucentSurfaceLighting )
{
PassMask . Set ( EMeshPass : : LumenTranslucencyRadianceCacheMark ) ;
View . NumVisibleDynamicMeshElements [ EMeshPass : : LumenTranslucencyRadianceCacheMark ] + = NumElements ;
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 )
{
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 )
{
View . MeshDecalBatches . AddUninitialized ( 1 ) ;
FMeshDecalBatch & BatchAndProxy = View . MeshDecalBatches . Last ( ) ;
BatchAndProxy . Mesh = MeshBatch . Mesh ;
BatchAndProxy . Proxy = MeshBatch . PrimitiveSceneProxy ;
BatchAndProxy . SortKey = MeshBatch . PrimitiveSceneProxy - > GetTranslucencySortPriority ( ) ;
}
const bool bIsHairStrandsCompatible = ViewRelevance . bHairStrands & & IsHairStrandsEnabled ( EHairStrandsShaderType : : All , View . GetShaderPlatform ( ) ) ;
if ( bIsHairStrandsCompatible )
{
if ( HairStrands : : IsHairStrandsVF ( MeshBatch . Mesh ) & & HairStrands : : IsHairVisible ( MeshBatch ) )
{
View . HairStrandsMeshElements . AddUninitialized ( 1 ) ;
FMeshBatchAndRelevance & BatchAndProxy = View . HairStrandsMeshElements . Last ( ) ;
BatchAndProxy = MeshBatch ;
}
if ( HairStrands : : IsHairCardsVF ( MeshBatch . Mesh ) )
{
View . HairCardsMeshElements . AddUninitialized ( 1 ) ;
FMeshBatchAndRelevance & BatchAndProxy = View . HairCardsMeshElements . Last ( ) ;
BatchAndProxy = MeshBatch ;
}
}
}
void FSceneRenderer : : GatherDynamicMeshElements (
TArrayView < FViewInfo > & InViews ,
const FScene * InScene ,
const FSceneViewFamily & InViewFamily ,
FGlobalDynamicIndexBuffer & DynamicIndexBuffer ,
FGlobalDynamicVertexBuffer & DynamicVertexBuffer ,
FGlobalDynamicReadBuffer & DynamicReadBuffer ,
const FPrimitiveViewMasks & HasDynamicMeshElementsMasks ,
const FPrimitiveViewMasks & HasDynamicEditorMeshElementsMasks ,
FMeshElementCollector & Collector )
{
SCOPE_CYCLE_COUNTER ( STAT_GetDynamicMeshElements ) ;
int32 NumPrimitives = InScene - > Primitives . Num ( ) ;
check ( HasDynamicMeshElementsMasks . Num ( ) = = NumPrimitives ) ;
int32 ViewCount = InViews . Num ( ) ;
{
Collector . ClearViewMeshArrays ( ) ;
for ( int32 ViewIndex = 0 ; ViewIndex < ViewCount ; ViewIndex + + )
{
Collector . AddViewMeshArrays (
& InViews [ ViewIndex ] ,
& InViews [ ViewIndex ] . DynamicMeshElements ,
& InViews [ ViewIndex ] . SimpleElementCollector ,
& InViews [ ViewIndex ] . DynamicPrimitiveCollector ,
InViewFamily . GetFeatureLevel ( ) ,
& DynamicIndexBuffer ,
& DynamicVertexBuffer ,
& DynamicReadBuffer ) ;
}
const EShadingPath ShadingPath = Scene - > GetShadingPath ( ) ;
for ( int32 PrimitiveIndex = 0 ; PrimitiveIndex < NumPrimitives ; + + PrimitiveIndex )
{
const uint8 ViewMask = HasDynamicMeshElementsMasks [ PrimitiveIndex ] ;
if ( ViewMask ! = 0 )
{
// 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 < ViewCount ; ViewIndex + + )
{
FViewInfo & View = InViews [ ViewIndex ] ;
if ( ViewMask & ( 1 < < ViewIndex ) & & IStereoRendering : : IsASecondaryView ( View ) )
{
ViewMaskFinal | = 1 < < InViews [ ViewIndex ] . PrimaryViewIndex ;
}
}
FPrimitiveSceneInfo * PrimitiveSceneInfo = InScene - > Primitives [ PrimitiveIndex ] ;
const FPrimitiveBounds & Bounds = InScene - > PrimitiveBounds [ PrimitiveIndex ] ;
Collector . SetPrimitive ( PrimitiveSceneInfo - > Proxy , PrimitiveSceneInfo - > DefaultDynamicHitProxyId ) ;
PrimitiveSceneInfo - > Proxy - > GetDynamicMeshElements ( InViewFamily . Views , InViewFamily , ViewMaskFinal , Collector ) ;
// Compute DynamicMeshElementsMeshPassRelevance for this primitive.
for ( int32 ViewIndex = 0 ; ViewIndex < ViewCount ; ViewIndex + + )
{
if ( ViewMaskFinal & ( 1 < < ViewIndex ) )
{
FViewInfo & View = InViews [ ViewIndex ] ;
const bool bAddLightmapDensityCommands = View . Family - > EngineShowFlags . LightMapDensity & & AllowDebugViewmodes ( ) ;
const FPrimitiveViewRelevance & ViewRelevance = View . PrimitiveViewRelevanceMap [ PrimitiveIndex ] ;
const int32 LastNumDynamicMeshElements = View . DynamicMeshElementsPassRelevance . Num ( ) ;
View . DynamicMeshElementsPassRelevance . SetNum ( View . DynamicMeshElements . Num ( ) ) ;
for ( int32 ElementIndex = LastNumDynamicMeshElements ; ElementIndex < View . DynamicMeshElements . Num ( ) ; + + ElementIndex )
{
const FMeshBatchAndRelevance & MeshBatch = View . DynamicMeshElements [ ElementIndex ] ;
FMeshPassMask & PassRelevance = View . DynamicMeshElementsPassRelevance [ ElementIndex ] ;
ComputeDynamicMeshRelevance ( ShadingPath , bAddLightmapDensityCommands , ViewRelevance , MeshBatch , View , PassRelevance , PrimitiveSceneInfo , Bounds ) ;
}
}
}
}
// Mark DynamicMeshEndIndices end.
for ( int32 ViewIndex = 0 ; ViewIndex < ViewCount ; ViewIndex + + )
{
InViews [ ViewIndex ] . DynamicMeshEndIndices [ PrimitiveIndex ] = Collector . GetMeshBatchCount ( ViewIndex ) ;
}
}
}
if ( GIsEditor )
{
Collector . ClearViewMeshArrays ( ) ;
for ( int32 ViewIndex = 0 ; ViewIndex < ViewCount ; ViewIndex + + )
{
Collector . AddViewMeshArrays (
& InViews [ ViewIndex ] ,
& InViews [ ViewIndex ] . DynamicEditorMeshElements ,
& InViews [ ViewIndex ] . EditorSimpleElementCollector ,
& InViews [ ViewIndex ] . DynamicPrimitiveCollector ,
InViewFamily . GetFeatureLevel ( ) ,
& DynamicIndexBuffer ,
& DynamicVertexBuffer ,
& DynamicReadBuffer ) ;
}
for ( int32 PrimitiveIndex = 0 ; PrimitiveIndex < NumPrimitives ; + + PrimitiveIndex )
{
const uint8 ViewMask = HasDynamicEditorMeshElementsMasks [ PrimitiveIndex ] ;
if ( ViewMask ! = 0 )
{
FPrimitiveSceneInfo * PrimitiveSceneInfo = InScene - > Primitives [ PrimitiveIndex ] ;
Collector . SetPrimitive ( PrimitiveSceneInfo - > Proxy , PrimitiveSceneInfo - > DefaultDynamicHitProxyId ) ;
PrimitiveSceneInfo - > Proxy - > GetDynamicMeshElements ( InViewFamily . Views , InViewFamily , ViewMask , Collector ) ;
}
}
}
ActiveViewFamily - > MeshCollector . ProcessTasks ( ) ;
}
/**
* 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 , const FSceneTexturesConfig & SceneTexturesConfig )
{
FRHICommandListImmediate & RHICmdList = GraphBuilder . RHICmdList ;
// Notify the RHI we are beginning to render a scene.
RHICmdList . BeginScene ( ) ;
{
static auto CVar = IConsoleManager : : Get ( ) . FindConsoleVariable ( TEXT ( " r.DoLazyStaticMeshUpdate " ) ) ;
const bool DoLazyStaticMeshUpdate = ( CVar - > GetInt ( ) & & ! GIsEditor ) ;
if ( DoLazyStaticMeshUpdate )
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_PreVisibilityFrameSetup_EvictionForLazyStaticMeshUpdate ) ;
static int32 RollingRemoveIndex = 0 ;
static int32 RollingPassShrinkIndex = 0 ;
if ( RollingRemoveIndex > = Scene - > Primitives . Num ( ) )
{
RollingRemoveIndex = 0 ;
RollingPassShrinkIndex + + ;
if ( RollingPassShrinkIndex > = UE_ARRAY_COUNT ( Scene - > CachedDrawLists ) )
{
RollingPassShrinkIndex = 0 ;
}
// Periodically shrink the SparseArray containing cached mesh draw commands which we are causing to be regenerated with UpdateStaticMeshes
Scene - > CachedDrawLists [ RollingPassShrinkIndex ] . MeshDrawCommands . Shrink ( ) ;
}
const int32 NumRemovedPerFrame = 10 ;
TArray < FPrimitiveSceneInfo * , TInlineAllocator < 10 > > SceneInfos ;
for ( int32 NumRemoved = 0 ; NumRemoved < NumRemovedPerFrame & & RollingRemoveIndex < Scene - > Primitives . Num ( ) ; NumRemoved + + , RollingRemoveIndex + + )
{
SceneInfos . Add ( Scene - > Primitives [ RollingRemoveIndex ] ) ;
}
FPrimitiveSceneInfo : : UpdateStaticMeshes ( RHICmdList , Scene , SceneInfos , EUpdateStaticMeshFlags : : AllCommands , false ) ;
}
}
if ( Views . Num ( ) > 0 & & ! ActiveViewFamily - > EngineShowFlags . HitProxies )
{
FHairStrandsBookmarkParameters Parameters = CreateHairStrandsBookmarkParameters ( Scene , Views ) ;
if ( Parameters . HasInstances ( ) )
{
RunHairStrandsBookmark ( GraphBuilder , EHairStrandsBookmark : : ProcessLODSelection , Parameters ) ;
}
}
if ( IsHairStrandsEnabled ( EHairStrandsShaderType : : All , Scene - > GetShaderPlatform ( ) ) & & Views . Num ( ) > 0 & & ! ActiveViewFamily - > EngineShowFlags . HitProxies )
{
// If we are rendering from scene capture we don't need to run another time the hair bookmarks.
if ( Views [ 0 ] . AllowGPUParticleUpdate ( ) )
{
FHairStrandsBookmarkParameters Parameters = CreateHairStrandsBookmarkParameters ( Scene , Views ) ;
RunHairStrandsBookmark ( GraphBuilder , EHairStrandsBookmark : : ProcessGuideInterpolation , Parameters ) ;
}
}
// Notify the FX system that the scene is about to perform visibility checks.
if ( FXSystem & & Views . IsValidIndex ( 0 ) )
{
FXSystem - > PreInitViews ( GraphBuilder , Views [ 0 ] . AllowGPUParticleUpdate ( ) & & ! ActiveViewFamily - > EngineShowFlags . HitProxies ) ;
}
# if WITH_EDITOR
// Draw lines to lights affecting this mesh if its selected.
if ( ActiveViewFamily - > EngineShowFlags . LightInfluences )
{
for ( TConstSetBitIterator < > It ( Scene - > PrimitivesSelected ) ; It ; + + It )
{
const FPrimitiveSceneInfo * PrimitiveSceneInfo = Scene - > Primitives [ It . GetIndex ( ) ] ;
FLightPrimitiveInteraction * LightList = PrimitiveSceneInfo - > LightList ;
while ( LightList )
{
const FLightSceneInfo * LightSceneInfo = LightList - > GetLight ( ) ;
bool bDynamic = true ;
bool bRelevant = false ;
bool bLightMapped = true ;
bool bShadowMapped = false ;
PrimitiveSceneInfo - > Proxy - > GetLightRelevance ( LightSceneInfo - > Proxy , 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 , LightSceneInfo - > Proxy - > GetLightToWorld ( ) . GetOrigin ( ) , LineColor , SDPG_World ) ;
}
}
LightList = LightList - > GetNextLight ( ) ;
}
}
}
# endif
# if UE_BUILD_SHIPPING
const bool bFreezeTemporalHistories = false ;
const bool bFreezeTemporalSequences = false ;
# else
bool bFreezeTemporalHistories = CVarFreezeTemporalHistories . GetValueOnRenderThread ( ) ! = 0 ;
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 < Views . Num ( ) ; ViewIndex + + )
{
FViewInfo & View = Views [ ViewIndex ] ;
FSceneViewState * ViewState = View . ViewState ;
check ( View . VerifyMembersChecks ( ) ) ;
// 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 = ActiveViewFamily - > EngineShowFlags . HitProxies ;
// Don't test occlusion queries in collision viewmode as they can be bigger then the rendering bounds.
const bool bCollisionView = ActiveViewFamily - > EngineShowFlags . CollisionVisibility | | ActiveViewFamily - > EngineShowFlags . CollisionPawn ;
if ( GIsHighResScreenshot | | ! DoOcclusionQueries ( ) | | bIsHitTesting | | bCollisionView | | ActiveViewFamily - > EngineShowFlags . DisableOcclusionQueries )
{
View . bDisableQuerySubmissions = true ;
View . bIgnoreExistingQueries = true ;
}
// set up the screen area for occlusion
{
float OcclusionPixelMultiplier = 1.0f ;
if ( UseDownsampledOcclusionQueries ( ) )
{
OcclusionPixelMultiplier = 1.0f / static_cast < float > ( FMath : : Square ( SceneTexturesConfig . SmallDepthDownsampleFactor ) ) ;
}
float NumPossiblePixels = static_cast < float > ( 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 = ActiveViewFamily - > bWorldIsPaused | | ActiveViewFamily - > EngineShowFlags . HitProxies | | bFreezeTemporalHistories ;
ViewState - > SetupDistanceFieldTemporalOffset ( * ActiveViewFamily ) ;
if ( ! View . bStatePrevViewInfoIsReadOnly & & ! bFreezeTemporalSequences )
{
ViewState - > FrameIndex + + ;
}
if ( View . OverrideFrameIndexValue . IsSet ( ) )
{
ViewState - > FrameIndex = View . OverrideFrameIndexValue . GetValue ( ) ;
}
}
// Subpixel jitter for temporal AA
int32 CVarTemporalAASamplesValue = CVarTemporalAASamples . GetValueOnRenderThread ( ) ;
EMainTAAPassConfig TAAConfig = ITemporalUpscaler : : GetMainTAAPassConfig ( View ) ;
bool bTemporalUpsampling = View . PrimaryScreenPercentageMethod = = EPrimaryScreenPercentageMethod : : TemporalUpscale ;
// Apply a sub pixel offset to the view.
if ( IsTemporalAccumulationBasedMethod ( View . AntiAliasingMethod ) & & ViewState & & ( CVarTemporalAASamplesValue > 0 | | bTemporalUpsampling ) & & View . bAllowTemporalJitter )
{
float EffectivePrimaryResolutionFraction = float ( View . ViewRect . Width ( ) ) / float ( View . 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 )
{
// 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 int8 kFirstPrimeNumbers [ 25 ] = {
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 ,
} ;
for ( int32 PrimeNumberId = 4 ; 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 = ViewState - > 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 )
{
ViewState - > TemporalAASampleIndex = TemporalSampleIndex ;
}
// Choose sub pixel sample coordinate in the temporal sequence.
float SampleX , SampleY ;
if ( View . 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 ;
View . MaterialTextureMipBias = - ( FMath : : Max ( - FMath : : Log2 ( EffectivePrimaryResolutionFraction ) , 0.0f ) ) + CVarMinAutomaticViewMipBiasOffset . GetValueOnRenderThread ( ) ;
View . MaterialTextureMipBias = FMath : : Max ( View . MaterialTextureMipBias , CVarMinAutomaticViewMipBias . GetValueOnRenderThread ( ) ) ;
}
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
{
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 ) ;
}
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 ;
NewPrevViewInfo . ViewRect = View . ViewRect ;
}
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
// 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 ( ) ;
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 )
{
ViewState - > PathTracingInvalidate ( ) ;
}
}
else
{
// for interactive usage - any movement or scene change should restart the path tracer
// For each view, we remember what the invalidation counter was set to last time we were here so we can catch all changes
bool bNeedsInvalidation = ViewState - > PathTracingInvalidationCounter ! = CurrentPathTracingInvalidationCounter ;
ViewState - > PathTracingInvalidationCounter = CurrentPathTracingInvalidationCounter ;
if ( bNeedsInvalidation | |
bResetCamera | |
bIsProjMatrixDifferent | |
bIsCameraMove | |
View . bForcePathTracerReset )
{
ViewState - > PathTracingInvalidate ( ) ;
}
}
# 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 ( ) ;
// store old view matrix and detect conditions where we should reset motion blur
# if RHI_RAYTRACING
{
if ( bResetCamera | | IsLargeCameraMovement ( View , ViewState - > PrevFrameViewInfo . ViewMatrices . GetViewMatrix ( ) , ViewState - > PrevFrameViewInfo . ViewMatrices . GetViewOrigin ( ) , 0.1f , 0.1f ) )
{
ViewState - > RayTracingNumIterations = 1 ;
}
else
{
ViewState - > RayTracingNumIterations + + ;
}
}
# endif // RHI_RAYTRACING
// 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 ( ! ActiveViewFamily - > 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 ;
}
}
// Setup global dither fade in and fade out uniform buffers.
for ( int32 ViewIndex = 0 ; ViewIndex < Views . Num ( ) ; ViewIndex + + )
{
FViewInfo & View = Views [ ViewIndex ] ;
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 ) ;
}
}
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 < float > ( MotionBlurTargetFPS ) ;
}
MotionBlurTimeScale = MotionBlurTargetDeltaTime / DeltaRealTime ;
}
void UpdateReflectionSceneData ( FScene * Scene )
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_UpdateReflectionSceneData )
SCOPED_NAMED_EVENT ( UpdateReflectionScene , FColor : : Red ) ;
FReflectionEnvironmentSceneData & ReflectionSceneData = Scene - > ReflectionSceneData ;
ReflectionSceneData . SortedCaptures . Reset ( ReflectionSceneData . RegisteredReflectionCaptures . Num ( ) ) ;
ReflectionSceneData . NumBoxCaptures = 0 ;
ReflectionSceneData . NumSphereCaptures = 0 ;
const int32 MaxCubemaps = ReflectionSceneData . CubemapArray . GetMaxCubemaps ( ) ;
int32_t PlatformMaxNumReflectionCaptures = FMath : : Min ( FMath : : FloorToInt ( GMaxTextureArrayLayers / 6.0f ) , GMaxNumReflectionCaptures ) ;
// Pack visible reflection captures into the uniform buffer, each with an index to its cubemap array entry.
// GPUScene primitive data stores closest reflection capture as index into this buffer, so this index which must be invalidate every time OutSortData contents change.
for ( int32 ReflectionProxyIndex = 0 ; ReflectionProxyIndex < ReflectionSceneData . RegisteredReflectionCaptures . Num ( ) & & ReflectionSceneData . SortedCaptures . Num ( ) < PlatformMaxNumReflectionCaptures ; ReflectionProxyIndex + + )
{
FReflectionCaptureProxy * CurrentCapture = ReflectionSceneData . RegisteredReflectionCaptures [ ReflectionProxyIndex ] ;
FReflectionCaptureSortData NewSortEntry ;
NewSortEntry . CubemapIndex = - 1 ;
NewSortEntry . CaptureOffsetAndAverageBrightness = FVector4f ( CurrentCapture - > CaptureOffset , 1.0f ) ;
NewSortEntry . CaptureProxy = CurrentCapture ;
if ( SupportsTextureCubeArray ( Scene - > GetFeatureLevel ( ) ) )
{
FCaptureComponentSceneState * ComponentStatePtr = ReflectionSceneData . AllocatedReflectionCaptureState . Find ( CurrentCapture - > Component ) ;
if ( ! ComponentStatePtr )
{
// Skip reflection captures without built data to upload
continue ;
}
NewSortEntry . CubemapIndex = ComponentStatePtr - > CubemapIndex ;
check ( NewSortEntry . CubemapIndex < MaxCubemaps | | NewSortEntry . CubemapIndex = = 0 ) ;
NewSortEntry . CaptureOffsetAndAverageBrightness . W = ComponentStatePtr - > AverageBrightness ;
}
NewSortEntry . Guid = CurrentCapture - > Guid ;
NewSortEntry . RelativePosition = CurrentCapture - > RelativePosition ;
NewSortEntry . TilePosition = CurrentCapture - > TilePosition ;
NewSortEntry . Radius = CurrentCapture - > InfluenceRadius ;
float ShapeTypeValue = ( float ) CurrentCapture - > Shape ;
NewSortEntry . CaptureProperties = FVector4f ( CurrentCapture - > Brightness , NewSortEntry . CubemapIndex , ShapeTypeValue , 0 ) ;
if ( CurrentCapture - > Shape = = EReflectionCaptureShape : : Plane )
{
//planes count as boxes in the compute shader.
+ + ReflectionSceneData . NumBoxCaptures ;
NewSortEntry . BoxTransform = FMatrix44f (
FPlane4f ( CurrentCapture - > LocalReflectionPlane ) ,
FPlane4f ( ( FVector4f ) CurrentCapture - > ReflectionXAxisAndYScale ) , // LWC_TODO: precision loss
FPlane4f ( 0 , 0 , 0 , 0 ) ,
FPlane4f ( 0 , 0 , 0 , 0 ) ) ;
NewSortEntry . BoxScales = FVector4f ( 0 ) ;
}
else if ( CurrentCapture - > Shape = = EReflectionCaptureShape : : Sphere )
{
+ + ReflectionSceneData . NumSphereCaptures ;
}
else
{
+ + ReflectionSceneData . NumBoxCaptures ;
NewSortEntry . BoxTransform = CurrentCapture - > BoxTransform ;
NewSortEntry . BoxScales = FVector4f ( CurrentCapture - > BoxScales , CurrentCapture - > BoxTransitionDistance ) ;
}
ReflectionSceneData . SortedCaptures . Add ( NewSortEntry ) ;
}
ReflectionSceneData . SortedCaptures . Sort ( ) ;
for ( int32 CaptureIndex = 0 ; CaptureIndex < ReflectionSceneData . SortedCaptures . Num ( ) ; CaptureIndex + + )
{
ReflectionSceneData . SortedCaptures [ CaptureIndex ] . CaptureProxy - > SortedCaptureIndex = CaptureIndex ;
}
// If SortedCaptures change, then in case of forward renderer all scene primitives need to be updated, as they
// store index into sorted reflection capture uniform buffer for the forward renderer.
if ( IsForwardShadingEnabled ( Scene - > GetShaderPlatform ( ) ) & & ReflectionSceneData . AllocatedReflectionCaptureStateHasChanged )
{
const int32 NumPrimitives = Scene - > Primitives . Num ( ) ;
for ( int32 PrimitiveIndex = 0 ; PrimitiveIndex < NumPrimitives ; + + PrimitiveIndex )
{
Scene - > Primitives [ PrimitiveIndex ] - > SetNeedsUniformBufferUpdate ( true ) ;
}
Scene - > GPUScene . bUpdateAllPrimitives = true ;
ReflectionSceneData . AllocatedReflectionCaptureStateHasChanged = false ;
}
// Mark all primitives for reflection proxy update
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_MarkAllPrimitivesForReflectionProxyUpdate ) ;
if ( Scene - > ReflectionSceneData . bRegisteredReflectionCapturesHasChanged )
{
// Mobile needs to re-cache all mesh commands when scene capture data has changed
const bool bNeedsStaticMeshUpdate = Scene - > GetShadingPath ( ) = = EShadingPath : : Mobile ;
// Mark all primitives as needing an update
// Note: Only visible primitives will actually update their reflection proxy
for ( int32 PrimitiveIndex = 0 ; PrimitiveIndex < Scene - > Primitives . Num ( ) ; PrimitiveIndex + + )
{
FPrimitiveSceneInfo * Primitive = Scene - > Primitives [ PrimitiveIndex ] ;
Primitive - > RemoveCachedReflectionCaptures ( ) ;
if ( bNeedsStaticMeshUpdate )
{
Primitive - > CacheReflectionCaptures ( ) ;
Primitive - > BeginDeferredUpdateStaticMeshes ( ) ;
}
}
Scene - > ReflectionSceneData . bRegisteredReflectionCapturesHasChanged = false ;
}
}
}
# if !UE_BUILD_SHIPPING
static uint32 GetDrawCountFromPrimitiveSceneInfo ( FScene * Scene , const FPrimitiveSceneInfo * PrimitiveSceneInfo )
{
uint32 DrawCount = 0 ;
for ( const FCachedMeshDrawCommandInfo & CachedCommand : PrimitiveSceneInfo - > StaticMeshCommandInfos )
{
if ( CachedCommand . MeshPass ! = EMeshPass : : BasePass )
continue ;
if ( CachedCommand . StateBucketId ! = INDEX_NONE | | CachedCommand . CommandIndex > = 0 )
{
DrawCount + + ;
}
}
return DrawCount ;
}
void FSceneRenderer : : DumpPrimitives ( const FViewCommands & ViewCommands )
{
if ( ! bDumpPrimitivesNextFrame )
{
return ;
}
bDumpPrimitivesNextFrame = false ;
struct FPrimitiveInfo
{
const FPrimitiveSceneInfo * PrimitiveSceneInfo ;
FString Name ;
uint32 DrawCount ;
bool operator < ( const FPrimitiveInfo & Other ) const
{
// Sort by name to group similar assets together, then by exact primitives so we can ignore duplicates
const int32 NameCompare = Name . Compare ( Other . Name ) ;
if ( NameCompare ! = 0 )
{
return NameCompare < 0 ;
}
return PrimitiveSceneInfo < Other . PrimitiveSceneInfo ;
}
} ;
TArray < FPrimitiveInfo > Primitives ;
Primitives . Reserve ( ViewCommands . MeshCommands [ EMeshPass : : BasePass ] . Num ( ) + ViewCommands . DynamicMeshCommandBuildRequests [ EMeshPass : : BasePass ] . Num ( ) ) ;
{
for ( const FVisibleMeshDrawCommand & Mesh : ViewCommands . MeshCommands [ EMeshPass : : BasePass ] )
{
int32 PrimitiveId = Mesh . PrimitiveIdInfo . ScenePrimitiveId ;
if ( PrimitiveId > = 0 & & PrimitiveId < Scene - > Primitives . Num ( ) )
{
const FPrimitiveSceneInfo * PrimitiveSceneInfo = Scene - > Primitives [ PrimitiveId ] ;
FString FullName = PrimitiveSceneInfo - > ComponentForDebuggingOnly - > GetFullName ( ) ;
uint32 DrawCount = GetDrawCountFromPrimitiveSceneInfo ( Scene , PrimitiveSceneInfo ) ;
Primitives . Add ( { PrimitiveSceneInfo , MoveTemp ( FullName ) , DrawCount } ) ;
}
}
for ( const FStaticMeshBatch * StaticMeshBatch : ViewCommands . DynamicMeshCommandBuildRequests [ EMeshPass : : BasePass ] )
{
const FPrimitiveSceneInfo * PrimitiveSceneInfo = StaticMeshBatch - > PrimitiveSceneInfo ;
FString FullName = PrimitiveSceneInfo - > ComponentForDebuggingOnly - > GetFullName ( ) ;
uint32 DrawCount = GetDrawCountFromPrimitiveSceneInfo ( Scene , PrimitiveSceneInfo ) ;
Primitives . Add ( { PrimitiveSceneInfo , MoveTemp ( FullName ) , DrawCount } ) ;
}
}
Primitives . Sort ( ) ;
const FString OutputPath = FPaths : : ProfilingDir ( ) / TEXT ( " Primitives " ) / FString : : Printf ( TEXT ( " Primitives-%s.csv " ) , * FDateTime : : Now ( ) . ToString ( ) ) ;
const bool bSuppressViewer = true ;
FDiagnosticTableViewer DrawViewer ( * OutputPath , bSuppressViewer ) ;
DrawViewer . AddColumn ( TEXT ( " Name " ) ) ;
DrawViewer . AddColumn ( TEXT ( " NumDraws " ) ) ;
DrawViewer . CycleRow ( ) ;
const FPrimitiveSceneInfo * LastPrimitiveSceneInfo = nullptr ;
for ( const FPrimitiveInfo & Primitive : Primitives )
{
if ( Primitive . PrimitiveSceneInfo ! = LastPrimitiveSceneInfo )
{
DrawViewer . AddColumn ( * Primitive . Name ) ;
DrawViewer . AddColumn ( * FString : : Printf ( TEXT ( " %d " ) , Primitive . DrawCount ) ) ;
DrawViewer . CycleRow ( ) ;
LastPrimitiveSceneInfo = Primitive . PrimitiveSceneInfo ;
}
}
}
# endif
# if WITH_EDITOR
static void UpdateHitProxyIdBuffer (
TArray < uint32 > & HitProxyIds ,
FDynamicReadBuffer & DynamicReadBuffer )
{
Algo : : Sort ( HitProxyIds ) ;
int32 EndIndex = Algo : : Unique ( HitProxyIds ) ;
HitProxyIds . RemoveAt ( EndIndex , HitProxyIds . Num ( ) - EndIndex ) ;
uint32 IdCount = HitProxyIds . Num ( ) ;
uint32 BufferCount = FMath : : Max ( FMath : : RoundUpToPowerOfTwo ( IdCount ) , 1u ) ;
if ( DynamicReadBuffer . NumBytes ! = BufferCount )
{
DynamicReadBuffer . Initialize ( TEXT ( " DynamicReadBuffer " ) , sizeof ( uint32 ) , BufferCount , PF_R32_UINT , BUF_Dynamic ) ;
}
DynamicReadBuffer . Lock ( ) ;
{
uint32 * Data = reinterpret_cast < uint32 * > ( DynamicReadBuffer . MappedBuffer ) ;
for ( uint32 i = 0 ; i < IdCount ; + + i )
{
Data [ i ] = HitProxyIds [ i ] ;
}
uint32 FillValue = IdCount = = 0 ? 0 : HitProxyIds . Last ( ) ;
for ( uint32 i = IdCount ; i < BufferCount ; + + i )
{
Data [ i ] = FillValue ;
}
}
DynamicReadBuffer . Unlock ( ) ;
}
# endif
void FSceneRenderer : : ComputeViewVisibility (
FRHICommandListImmediate & RHICmdList ,
FExclusiveDepthStencil : : Type BasePassDepthStencilAccess ,
FViewVisibleCommandsPerView & ViewCommandsPerView ,
FGlobalDynamicIndexBuffer & DynamicIndexBuffer ,
FGlobalDynamicVertexBuffer & DynamicVertexBuffer ,
FGlobalDynamicReadBuffer & DynamicReadBuffer ,
FInstanceCullingManager & InstanceCullingManager )
{
SCOPE_CYCLE_COUNTER ( STAT_ViewVisibilityTime ) ;
SCOPED_NAMED_EVENT ( FSceneRenderer_ComputeViewVisibility , FColor : : Magenta ) ;
STAT ( int32 NumProcessedPrimitives = 0 ) ;
STAT ( int32 NumCulledPrimitives = 0 ) ;
STAT ( int32 NumOccludedPrimitives = 0 ) ;
// Allocate the visible light info.
if ( Scene - > Lights . GetMaxIndex ( ) > 0 )
{
ActiveViewFamily - > VisibleLightInfos . AddDefaulted ( Scene - > Lights . GetMaxIndex ( ) ) ;
}
int32 NumPrimitives = Scene - > Primitives . Num ( ) ;
float CurrentRealTime = ActiveViewFamily - > Time . GetRealTimeSeconds ( ) ;
FPrimitiveViewMasks HasDynamicMeshElementsMasks ;
HasDynamicMeshElementsMasks . AddZeroed ( NumPrimitives ) ;
FPrimitiveViewMasks HasDynamicEditorMeshElementsMasks ;
if ( GIsEditor )
{
HasDynamicEditorMeshElementsMasks . AddZeroed ( NumPrimitives ) ;
}
UpdateReflectionSceneData ( Scene ) ;
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_ViewVisibilityTime_ConditionalUpdateStaticMeshesWithoutVisibilityCheck ) ;
SCOPED_NAMED_EVENT ( FSceneRenderer_ConditionalUpdateStaticMeshes , FColor : : Red ) ;
Scene - > ConditionalMarkStaticMeshElementsForUpdate ( ) ;
TArray < FPrimitiveSceneInfo * > UpdatedSceneInfos ;
for ( TSet < FPrimitiveSceneInfo * > : : TIterator It ( Scene - > PrimitivesNeedingStaticMeshUpdateWithoutVisibilityCheck ) ; It ; + + It )
{
FPrimitiveSceneInfo * Primitive = * It ;
if ( Primitive - > NeedsUpdateStaticMeshes ( ) )
{
UpdatedSceneInfos . Add ( Primitive ) ;
}
}
if ( UpdatedSceneInfos . Num ( ) > 0 )
{
FPrimitiveSceneInfo : : UpdateStaticMeshes ( RHICmdList , Scene , UpdatedSceneInfos , EUpdateStaticMeshFlags : : AllCommands ) ;
}
Scene - > PrimitivesNeedingStaticMeshUpdateWithoutVisibilityCheck . Reset ( ) ;
}
uint8 ViewBit = 0x1 ;
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FSceneRenderer_Views ) ;
for ( int32 ViewIndex = 0 ; ViewIndex < Views . Num ( ) ; + + ViewIndex , ViewBit < < = 1 )
{
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT ( * FString : : Printf ( TEXT ( " View %d " ) , ViewIndex ) ) ;
STAT ( NumProcessedPrimitives + = NumPrimitives ) ;
FViewInfo & View = Views [ ViewIndex ] ;
FViewCommands & ViewCommands = ViewCommandsPerView [ ViewIndex ] ;
FSceneViewState * ViewState = ( FSceneViewState * ) View . State ;
const bool bIsSinglePassStereo = View . bIsInstancedStereoEnabled | | View . bIsMobileMultiViewEnabled ;
// Allocate the view's visibility maps.
View . PrimitiveVisibilityMap . Init ( false , Scene - > Primitives . Num ( ) ) ;
View . PrimitiveRayTracingVisibilityMap . Init ( false , Scene - > Primitives . Num ( ) ) ;
// These are not initialized here, as we overwrite the whole array in GatherDynamicMeshElements().
View . DynamicMeshEndIndices . SetNumUninitialized ( Scene - > Primitives . Num ( ) ) ;
View . PrimitiveDefinitelyUnoccludedMap . Init ( false , Scene - > Primitives . Num ( ) ) ;
View . PotentiallyFadingPrimitiveMap . Init ( false , Scene - > Primitives . Num ( ) ) ;
View . PrimitiveFadeUniformBuffers . AddZeroed ( Scene - > Primitives . Num ( ) ) ;
View . PrimitiveFadeUniformBufferMap . Init ( false , Scene - > Primitives . Num ( ) ) ;
View . StaticMeshVisibilityMap . Init ( false , Scene - > StaticMeshes . GetMaxIndex ( ) ) ;
View . StaticMeshFadeOutDitheredLODMap . Init ( false , Scene - > StaticMeshes . GetMaxIndex ( ) ) ;
View . StaticMeshFadeInDitheredLODMap . Init ( false , Scene - > StaticMeshes . GetMaxIndex ( ) ) ;
View . PrimitivesLODMask . Init ( FLODMask ( ) , Scene - > Primitives . Num ( ) ) ;
View . VisibleLightInfos . Empty ( Scene - > Lights . GetMaxIndex ( ) ) ;
// The dirty list allocation must take into account the max possible size because when GILCUpdatePrimTaskEnabled is true,
// the indirect lighting cache will be update on by threaded job, which can not do reallocs on the buffer (since it uses the SceneRenderingAllocator).
View . DirtyIndirectLightingCacheBufferPrimitives . Reserve ( Scene - > Primitives . Num ( ) ) ;
for ( int32 LightIndex = 0 ; LightIndex < Scene - > Lights . GetMaxIndex ( ) ; LightIndex + + )
{
if ( LightIndex + 2 < Scene - > Lights . GetMaxIndex ( ) )
{
if ( LightIndex > 2 )
{
FLUSH_CACHE_LINE ( & View . VisibleLightInfos ( LightIndex - 2 ) ) ;
}
// @todo optimization These prefetches cause asserts since LightIndex > View.VisibleLightInfos.Num() - 1
//FPlatformMisc::Prefetch(&View.VisibleLightInfos[LightIndex+2]);
//FPlatformMisc::Prefetch(&View.VisibleLightInfos[LightIndex+1]);
}
new ( View . VisibleLightInfos ) FVisibleLightViewInfo ( ) ;
}
View . PrimitiveViewRelevanceMap . Reset ( Scene - > Primitives . Num ( ) ) ;
View . PrimitiveViewRelevanceMap . AddZeroed ( Scene - > Primitives . Num ( ) ) ;
// If this is the visibility-parent of other views, reset its ParentPrimitives list.
const bool bIsParent = ViewState & & ViewState - > IsViewParent ( ) ;
if ( bIsParent )
{
// PVS-Studio does not understand the validation of ViewState above, so we're disabling
// its warning that ViewState may be null:
ViewState - > ParentPrimitives . Reset ( ) ; //-V595
}
if ( ViewState )
{
SCOPE_CYCLE_COUNTER ( STAT_DecompressPrecomputedOcclusion ) ;
View . PrecomputedVisibilityData = ViewState - > GetPrecomputedVisibilityData ( View , Scene ) ;
}
else
{
View . PrecomputedVisibilityData = nullptr ;
}
if ( View . PrecomputedVisibilityData )
{
ActiveViewFamily - > bUsedPrecomputedVisibility = true ;
}
bool bNeedsFrustumCulling = CVarEnableFrustumCull . GetValueOnRenderThread ( ) ;
// Development builds sometimes override frustum culling, e.g. dependent views in the editor.
# if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if ( ViewState )
{
# if WITH_EDITOR
// For visibility child views, check if the primitive was visible in the parent view.
const FSceneViewState * const ViewParent = ( FSceneViewState * ) ViewState - > GetViewParent ( ) ;
if ( ViewParent )
{
bNeedsFrustumCulling = false ;
for ( int32 Index = 0 ; Index < View . PrimitiveVisibilityMap . Num ( ) ; + + Index )
{
if ( ViewParent - > ParentPrimitives . Contains ( Scene - > PrimitiveComponentIds [ Index ] ) | |
IsAlwaysVisible ( Scene , Index ) )
{
View . PrimitiveVisibilityMap [ Index ] = true ;
}
}
}
# endif
// For views with frozen visibility, check if the primitive is in the frozen visibility set.
if ( ViewState - > bIsFrozen )
{
bNeedsFrustumCulling = false ;
for ( int32 Index = 0 ; Index < View . PrimitiveVisibilityMap . Num ( ) ; + + Index )
{
if ( ViewState - > FrozenPrimitives . Contains ( Scene - > PrimitiveComponentIds [ Index ] ) | |
IsAlwaysVisible ( Scene , Index ) )
{
View . PrimitiveVisibilityMap [ Index ] = true ;
}
}
}
}
# endif
// Most views use standard frustum culling.
if ( bNeedsFrustumCulling )
{
// Update HLOD transition/visibility states to allow use during distance culling
FLODSceneTree & HLODTree = Scene - > SceneLODHierarchy ;
if ( HLODTree . IsActive ( ) )
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_ViewVisibilityTime_HLODUpdate ) ;
HLODTree . UpdateVisibilityStates ( View ) ;
}
else
{
HLODTree . ClearVisibilityState ( View ) ;
}
}
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FSceneRenderer_Cull ) ;
int32 NumCulledPrimitivesForView = PrimitiveCull ( Scene , View , bNeedsFrustumCulling ) ;
STAT ( NumCulledPrimitives + = NumCulledPrimitivesForView ) ;
}
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FSceneRenderer_UpdatePrimitiveFading ) ;
UpdatePrimitiveFading ( Scene , View ) ;
}
if ( View . ShowOnlyPrimitives . IsSet ( ) )
{
View . bHasNoVisiblePrimitive = View . ShowOnlyPrimitives - > Num ( ) = = 0 ;
}
if ( View . bStaticSceneOnly )
{
for ( FSceneSetBitIterator BitIt ( View . PrimitiveVisibilityMap ) ; BitIt ; + + BitIt )
{
// Reflection captures should only capture objects that won't move, since reflection captures won't update at runtime
if ( ! Scene - > Primitives [ BitIt . GetIndex ( ) ] - > Proxy - > HasStaticLighting ( ) )
{
View . PrimitiveVisibilityMap . AccessCorrespondingBit ( BitIt ) = false ;
}
}
}
// Cull small objects in wireframe in ortho views
// This is important for performance in the editor because wireframe disables any kind of occlusion culling
if ( View . Family - > EngineShowFlags . Wireframe )
{
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 ) ; BitIt ; + + BitIt )
{
if ( ScreenSizeScale * Scene - > PrimitiveBounds [ BitIt . GetIndex ( ) ] . BoxSphereBounds . SphereRadius < = GWireframeCullThreshold )
{
View . PrimitiveVisibilityMap . AccessCorrespondingBit ( BitIt ) = false ;
}
}
}
// Occlusion cull for all primitives in the view frustum, but not in wireframe.
if ( ! View . Family - > EngineShowFlags . Wireframe )
{
int32 NumOccludedPrimitivesInView = OcclusionCull ( RHICmdList , Scene , View , DynamicVertexBuffer ) ;
STAT ( NumOccludedPrimitives + = NumOccludedPrimitivesInView ) ;
}
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_ViewVisibilityTime_ConditionalUpdateStaticMeshes ) ;
SCOPED_NAMED_EVENT ( FSceneRenderer_UpdateStaticMeshes , FColor : : Red ) ;
TArray < FPrimitiveSceneInfo * > AddedSceneInfos ;
for ( TConstDualSetBitIterator < SceneRenderingBitArrayAllocator , FDefaultBitArrayAllocator > BitIt ( View . PrimitiveVisibilityMap , Scene - > PrimitivesNeedingStaticMeshUpdate ) ; BitIt ; + + BitIt )
{
int32 PrimitiveIndex = BitIt . GetIndex ( ) ;
AddedSceneInfos . Add ( Scene - > Primitives [ PrimitiveIndex ] ) ;
}
if ( AddedSceneInfos . Num ( ) > 0 )
{
FPrimitiveSceneInfo : : UpdateStaticMeshes ( RHICmdList , Scene , AddedSceneInfos , EUpdateStaticMeshFlags : : AllCommands ) ;
}
}
// Single-pass stereo views can't compute relevance until all views are visibility culled
if ( ! bIsSinglePassStereo )
{
SCOPE_CYCLE_COUNTER ( STAT_ViewRelevance ) ;
ComputeAndMarkRelevanceForViewParallel ( RHICmdList , Scene , View , ViewCommands , ViewBit , HasDynamicMeshElementsMasks , HasDynamicEditorMeshElementsMasks ) ;
}
# if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// Store the primitive for parent occlusion rendering.
if ( FPlatformProperties : : SupportsWindowedMode ( ) & & ViewState & & ViewState - > IsViewParent ( ) )
{
for ( FSceneDualSetBitIterator BitIt ( View . PrimitiveVisibilityMap , View . PrimitiveDefinitelyUnoccludedMap ) ; BitIt ; + + BitIt )
{
ViewState - > ParentPrimitives . Add ( Scene - > PrimitiveComponentIds [ BitIt . GetIndex ( ) ] ) ;
}
}
# endif
# if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// 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
// TODO: right now decals visibility computed right before rendering them, ideally it should be done in InitViews and this flag should be replaced with list of visible decals
// Currently used to disable stencil operations in forward base pass when scene has no any decals
View . bSceneHasDecals = ( Scene - > Decals . Num ( ) > 0 ) | | ( GForceSceneHasDecals ! = 0 ) ;
if ( bIsSinglePassStereo & & IStereoRendering : : IsASecondaryView ( View ) & & Views . IsValidIndex ( View . PrimaryViewIndex ) )
{
// Ensure primitives from the secondary view are visible in the primary view
FSceneBitArray & PrimaryVis = Views [ View . PrimaryViewIndex ] . PrimitiveVisibilityMap ;
const FSceneBitArray & SecondaryVis = View . PrimitiveVisibilityMap ;
check ( PrimaryVis . Num ( ) = = SecondaryVis . Num ( ) ) ;
const uint32 NumWords = FMath : : DivideAndRoundUp ( PrimaryVis . Num ( ) , NumBitsPerDWORD ) ;
uint32 * const PrimaryData = PrimaryVis . GetData ( ) ;
const uint32 * const SecondaryData = SecondaryVis . GetData ( ) ;
for ( uint32 Index = 0 ; Index < NumWords ; + + Index )
{
PrimaryData [ Index ] | = SecondaryData [ Index ] ;
}
}
}
}
ViewBit = 0x1 ;
for ( int32 ViewIndex = 0 ; ViewIndex < Views . Num ( ) ; + + ViewIndex , ViewBit < < = 1 )
{
FViewInfo & View = Views [ ViewIndex ] ;
FViewCommands & ViewCommands = ViewCommandsPerView [ ViewIndex ] ;
if ( View . bIsInstancedStereoEnabled | | View . bIsMobileMultiViewEnabled )
{
SCOPE_CYCLE_COUNTER ( STAT_ViewRelevance ) ;
ComputeAndMarkRelevanceForViewParallel ( RHICmdList , Scene , View , ViewCommands , ViewBit , HasDynamicMeshElementsMasks , HasDynamicEditorMeshElementsMasks ) ;
}
}
{
SCOPED_NAMED_EVENT ( FSceneRenderer_GatherDynamicMeshElements , FColor : : Yellow ) ;
// Gather FMeshBatches from scene proxies
GatherDynamicMeshElements ( Views , Scene , * ActiveViewFamily , DynamicIndexBuffer , DynamicVertexBuffer , DynamicReadBuffer ,
HasDynamicMeshElementsMasks , HasDynamicEditorMeshElementsMasks , ActiveViewFamily - > MeshCollector ) ;
}
for ( int32 ViewIndex = 0 ; ViewIndex < Views . Num ( ) ; ViewIndex + + )
{
FViewInfo & View = Views [ ViewIndex ] ;
# if WITH_EDITOR
UpdateHitProxyIdBuffer ( View . EditorSelectedHitProxyIds , View . EditorSelectedBuffer ) ;
UpdateHitProxyIdBuffer ( View . EditorVisualizeLevelInstanceIds , View . EditorVisualizeLevelInstanceBuffer ) ;
# endif
if ( ! View . ShouldRenderView ( ) )
{
continue ;
}
FViewCommands & ViewCommands = ViewCommandsPerView [ ViewIndex ] ;
# if !UE_BUILD_SHIPPING
DumpPrimitives ( ViewCommands ) ;
# endif
SetupMeshPass ( View , BasePassDepthStencilAccess , ViewCommands , InstanceCullingManager ) ;
}
INC_DWORD_STAT_BY ( STAT_ProcessedPrimitives , NumProcessedPrimitives ) ;
INC_DWORD_STAT_BY ( STAT_CulledPrimitives , NumCulledPrimitives ) ;
INC_DWORD_STAT_BY ( STAT_OccludedPrimitives , NumOccludedPrimitives ) ;
}
void FSceneRenderer : : PostVisibilityFrameSetup ( FILCUpdatePrimTaskData & OutILCTaskData )
{
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 ] ;
View . MeshDecalBatches . Sort ( ) ;
if ( View . State )
{
( ( FSceneViewState * ) View . State ) - > TrimHistoryRenderTargets ( Scene ) ;
}
}
}
const bool bSetupMobileLightShafts = FeatureLevel < = ERHIFeatureLevel : : ES3_1 & & ShouldRenderLightShafts ( * ActiveViewFamily ) ;
if ( ActiveViewFamily - > EngineShowFlags . HitProxies = = 0 & & Scene - > PrecomputedLightVolumes . Num ( ) > 0
& & GILCUpdatePrimTaskEnabled & & FPlatformProcess : : SupportsMultithreading ( ) )
{
Scene - > IndirectLightingCache . StartUpdateCachePrimitivesTask ( Scene , * this , true , OutILCTaskData ) ;
}
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_PostVisibilityFrameSetup_Light_Visibility ) ;
// 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 . ViewFrustum . IntersectSphere ( BoundingSphere . Center , BoundingSphere . W ) ;
if ( View . IsPerspectiveProjection ( ) )
{
const float DistanceSquared = ( BoundingSphere . Center - View . ViewMatrices . GetViewOrigin ( ) ) . SizeSquared ( ) ;
const float MaxDistSquared = Proxy - > GetMaxDrawDistance ( ) * Proxy - > GetMaxDrawDistance ( ) * GLightMaxDrawDistanceScale * GLightMaxDrawDistanceScale ;
const bool bDrawLight = ( FMath : : Square ( FMath : : Min ( 0.0002f , GMinScreenRadiusForLights / BoundingSphere . W ) * View . LODDistanceFactor ) * DistanceSquared < 1.0f )
& & ( MaxDistSquared = = 0 | | DistanceSquared < MaxDistSquared ) ;
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 )
{
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 = new ( FMemStack : : Get ( ) ) FColoredTexturedMaterialRenderProxy ( GEngine - > EmissiveMeshMaterial - > GetRenderProxy ( ) , Color , NAME_Color , SurfaceTexture , NAME_LinearColor ) ;
else
ColoredMeshInstance = new ( FMemStack : : Get ( ) ) FColoredMaterialRenderProxy ( 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 ) ;
}
}
}
}
}
}
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_PostVisibilityFrameSetup_InitFogConstants ) ;
InitFogConstants ( ) ;
}
}
uint32 GetShadowQuality ( ) ;
void UpdateHairResources ( FRDGBuilder & GraphBuilder , const FViewInfo & View ) ;
/**
* Performs once per frame setup prior to visibility determination .
*/
void FDeferredShadingSceneRenderer : : PreVisibilityFrameSetup ( FRDGBuilder & GraphBuilder , const FSceneTexturesConfig & SceneTexturesConfig )
{
// Possible stencil dither optimization approach
for ( int32 ViewIndex = 0 ; ViewIndex < Views . Num ( ) ; ViewIndex + + )
{
FViewInfo & View = Views [ ViewIndex ] ;
View . bAllowStencilDither = DepthPass . bDitheredLODTransitionsUseStencil ;
}
FSceneRenderer : : PreVisibilityFrameSetup ( GraphBuilder , SceneTexturesConfig ) ;
}
/**
* Initialize scene ' s views .
* Check visibility , build visible mesh commands , etc .
*/
void FDeferredShadingSceneRenderer : : InitViews ( FRDGBuilder & GraphBuilder , const FSceneTexturesConfig & SceneTexturesConfig , FExclusiveDepthStencil : : Type BasePassDepthStencilAccess , struct FILCUpdatePrimTaskData & ILCTaskData , FInstanceCullingManager & InstanceCullingManager )
{
SCOPED_NAMED_EVENT ( FDeferredShadingSceneRenderer_InitViews , FColor : : Emerald ) ;
SCOPE_CYCLE_COUNTER ( STAT_InitViewsTime ) ;
CSV_SCOPED_TIMING_STAT_EXCLUSIVE ( InitViews_Scene ) ;
PreVisibilityFrameSetup ( GraphBuilder , SceneTexturesConfig ) ;
FRHICommandListImmediate & RHICmdList = GraphBuilder . RHICmdList ;
const auto DispatchToRHIThread = [ & ] ( )
{
RHICmdList . ImmediateFlush ( EImmediateFlushType : : DispatchToRHIThread ) ;
} ;
DispatchToRHIThread ( ) ;
// Create GPU-side representation of the view for instance culling.
for ( int32 ViewIndex = 0 ; ViewIndex < Views . Num ( ) ; + + ViewIndex )
{
Views [ ViewIndex ] . GPUSceneViewId = InstanceCullingManager . RegisterView ( Views [ ViewIndex ] ) ;
}
{
// 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).
if ( FXSystem & & FXSystem - > RequiresEarlyViewUniformBuffer ( ) & & Views . IsValidIndex ( 0 ) )
{
Views [ 0 ] . InitRHIResources ( ) ;
FXSystem - > PostInitViews ( GraphBuilder , Views , ! ActiveViewFamily - > EngineShowFlags . HitProxies ) ;
}
}
LumenScenePDIVisualization ( ) ;
FViewVisibleCommandsPerView ViewCommandsPerView ;
ViewCommandsPerView . SetNum ( Views . Num ( ) ) ;
ComputeViewVisibility ( RHICmdList , BasePassDepthStencilAccess , ViewCommandsPerView , DynamicIndexBufferForInitViews , DynamicVertexBufferForInitViews , DynamicReadBufferForInitViews , InstanceCullingManager ) ;
DispatchToRHIThread ( ) ;
// This has to happen before Scene->IndirectLightingCache.UpdateCache, since primitives in View.IndirectShadowPrimitives need ILC updates
CreateIndirectCapsuleShadows ( ) ;
// This must happen before we start initialising and using views.
2022-05-25 15:53:11 -04:00
if ( Scene )
{
UpdateSkyIrradianceGpuBuffer ( RHICmdList , ActiveViewFamily - > EngineShowFlags , Scene - > SkyLight , Scene - > SkyIrradianceEnvironmentMap ) ;
}
2022-05-25 08:22:32 -04:00
DispatchToRHIThread ( ) ;
// Initialise Sky/View resources before the view global uniform buffer is built.
if ( ShouldRenderSkyAtmosphere ( Scene , ActiveViewFamily - > EngineShowFlags ) )
{
InitSkyAtmosphereForViews ( RHICmdList ) ;
}
PostVisibilityFrameSetup ( ILCTaskData ) ;
DispatchToRHIThread ( ) ;
FVector AverageViewPosition ( 0 ) ;
for ( int32 ViewIndex = 0 ; ViewIndex < Views . Num ( ) ; ViewIndex + + )
{
FViewInfo & View = Views [ ViewIndex ] ;
AverageViewPosition + = View . ViewMatrices . GetViewOrigin ( ) / Views . Num ( ) ;
}
InitViewsBeforePrepass ( GraphBuilder , InstanceCullingManager ) ;
{
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 ] ;
# if RHI_RAYTRACING
View . IESLightProfileResource = View . ViewState ? & View . ViewState - > IESLightProfileResources : nullptr ;
# endif
// Set the pre-exposure before initializing the constant buffers.
if ( View . ViewState )
{
View . ViewState - > UpdatePreExposure ( View ) ;
}
// Initialize the view's RHI resources.
UpdateHairResources ( GraphBuilder , View ) ;
View . InitRHIResources ( ) ;
}
// This is done for raytracing hit groups.
Scene - > UniformBuffers . ViewUniformBuffer . UpdateUniformBufferImmediate ( * Views [ 0 ] . CachedViewUniformShaderParameters ) ;
}
SetupVolumetricFog ( ) ;
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_InitViews_OnStartRender ) ;
OnStartRender ( RHICmdList ) ;
}
}
template < class T >
void CreateReflectionCaptureUniformBuffer ( const TArray < FReflectionCaptureSortData > & SortedCaptures , TUniformBufferRef < T > & OutReflectionCaptureUniformBuffer )
{
T SamplePositionsBuffer ;
for ( int32 CaptureIndex = 0 ; CaptureIndex < SortedCaptures . Num ( ) ; CaptureIndex + + )
{
SamplePositionsBuffer . PositionAndRadius [ CaptureIndex ] = FVector4f ( SortedCaptures [ CaptureIndex ] . RelativePosition , SortedCaptures [ CaptureIndex ] . Radius ) ;
SamplePositionsBuffer . TilePosition [ CaptureIndex ] = FVector4f ( SortedCaptures [ CaptureIndex ] . TilePosition , 0 ) ;
SamplePositionsBuffer . CaptureProperties [ CaptureIndex ] = SortedCaptures [ CaptureIndex ] . CaptureProperties ;
SamplePositionsBuffer . CaptureOffsetAndAverageBrightness [ CaptureIndex ] = SortedCaptures [ CaptureIndex ] . CaptureOffsetAndAverageBrightness ;
SamplePositionsBuffer . BoxTransform [ CaptureIndex ] = SortedCaptures [ CaptureIndex ] . BoxTransform ;
SamplePositionsBuffer . BoxScales [ CaptureIndex ] = SortedCaptures [ CaptureIndex ] . BoxScales ;
}
OutReflectionCaptureUniformBuffer = TUniformBufferRef < T > : : CreateUniformBufferImmediate ( SamplePositionsBuffer , UniformBuffer_SingleFrame ) ;
}
void FSceneRenderer : : SetupSceneReflectionCaptureBuffer ( FRHICommandListImmediate & RHICmdList )
{
const TArray < FReflectionCaptureSortData > & SortedCaptures = Scene - > ReflectionSceneData . SortedCaptures ;
TUniformBufferRef < FMobileReflectionCaptureShaderData > MobileReflectionCaptureUniformBuffer ;
TUniformBufferRef < FReflectionCaptureShaderData > ReflectionCaptureUniformBuffer ;
if ( IsMobilePlatform ( ShaderPlatform ) )
{
CreateReflectionCaptureUniformBuffer ( SortedCaptures , MobileReflectionCaptureUniformBuffer ) ;
}
else
{
CreateReflectionCaptureUniformBuffer ( SortedCaptures , ReflectionCaptureUniformBuffer ) ;
}
for ( int32 ViewIndex = 0 ; ViewIndex < Views . Num ( ) ; + + ViewIndex )
{
FViewInfo & View = Views [ ViewIndex ] ;
if ( IsMobilePlatform ( ShaderPlatform ) )
{
View . MobileReflectionCaptureUniformBuffer = MobileReflectionCaptureUniformBuffer ;
}
else
{
View . ReflectionCaptureUniformBuffer = 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 + + )
{
FLargeWorldRenderPosition AbsolutePosition ( SortedCaptures [ CaptureIndex ] . TilePosition , SortedCaptures [ CaptureIndex ] . RelativePosition ) ;
const FSphere BoundingSphere ( AbsolutePosition . GetAbsolute ( ) , SortedCaptures [ CaptureIndex ] . Radius ) ;
const float Distance = View . ViewMatrices . GetViewMatrix ( ) . TransformPosition ( BoundingSphere . Center ) . Z + BoundingSphere . W ;
View . FurthestReflectionCaptureDistance = FMath : : Max ( View . FurthestReflectionCaptureDistance , Distance ) ;
}
}
}
}
void FDeferredShadingSceneRenderer : : InitViewsBeforePrepass ( FRDGBuilder & GraphBuilder , FInstanceCullingManager & InstanceCullingManager )
{
const bool bHasRayTracedOverlay = HasRayTracedOverlay ( * ActiveViewFamily ) ;
if ( GEarlyInitDynamicShadows & &
CurrentDynamicShadowsTaskData = = nullptr & &
ActiveViewFamily - > EngineShowFlags . DynamicShadows
& & ! IsSimpleForwardShadingEnabled ( ShaderPlatform )
& & ! ActiveViewFamily - > EngineShowFlags . HitProxies
& & ! bHasRayTracedOverlay )
{
CurrentDynamicShadowsTaskData = BeginInitDynamicShadows ( true ) ;
}
}
void FDeferredShadingSceneRenderer : : InitViewsAfterPrepass ( FRDGBuilder & GraphBuilder , FLumenSceneFrameTemporaries & FrameTemporaries , struct FILCUpdatePrimTaskData & ILCTaskData , FInstanceCullingManager & InstanceCullingManager )
{
SCOPED_NAMED_EVENT ( FDeferredShadingSceneRenderer_InitViewsAfterPrepass , FColor : : Emerald ) ;
SCOPE_CYCLE_COUNTER ( STAT_InitViewsPossiblyAfterPrepass ) ;
FRHICommandListImmediate & RHICmdList = GraphBuilder . RHICmdList ;
const bool bHasRayTracedOverlay = HasRayTracedOverlay ( * ActiveViewFamily ) ;
if ( ActiveViewFamily - > EngineShowFlags . DynamicShadows
& & ! IsSimpleForwardShadingEnabled ( ShaderPlatform )
& & ! ActiveViewFamily - > EngineShowFlags . HitProxies
& & ! bHasRayTracedOverlay )
{
// Setup dynamic shadows.
if ( CurrentDynamicShadowsTaskData )
{
FinishInitDynamicShadows ( RHICmdList , CurrentDynamicShadowsTaskData , DynamicIndexBufferForInitShadows , DynamicVertexBufferForInitShadows , DynamicReadBufferForInitShadows , InstanceCullingManager ) ;
CurrentDynamicShadowsTaskData = nullptr ;
}
else
{
InitDynamicShadows ( RHICmdList , DynamicIndexBufferForInitShadows , DynamicVertexBufferForInitShadows , DynamicReadBufferForInitShadows , InstanceCullingManager ) ;
}
RHICmdList . ImmediateFlush ( EImmediateFlushType : : DispatchToRHIThread ) ;
}
// If parallel ILC update is disabled, then process it in place.
if ( ActiveViewFamily - > EngineShowFlags . HitProxies = = 0
& & Scene - > PrecomputedLightVolumes . Num ( ) > 0
& & ! ( GILCUpdatePrimTaskEnabled & & FPlatformProcess : : SupportsMultithreading ( ) ) )
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_PostVisibilityFrameSetup_IndirectLightingCache_Update ) ;
check ( ! ILCTaskData . TaskRef . IsValid ( ) ) ;
Scene - > IndirectLightingCache . UpdateCache ( Scene , * this , true ) ;
}
// If we kicked off ILC update via task, wait and finalize.
if ( ILCTaskData . TaskRef . IsValid ( ) )
{
Scene - > IndirectLightingCache . FinalizeCacheUpdates ( Scene , * this , ILCTaskData ) ;
}
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_InitViews_UpdatePrimitiveIndirectLightingCacheBuffers ) ;
// Now that the indirect lighting cache is updated, we can update the primitive precomputed lighting buffers.
UpdatePrimitiveIndirectLightingCacheBuffers ( ) ;
}
2022-05-26 05:54:01 -04:00
SeparateTranslucencyDimensions = UpdateSeparateTranslucencyDimensions ( * ActiveViewFamily ) ;
2022-05-25 08:22:32 -04:00
SetupSceneReflectionCaptureBuffer ( RHICmdList ) ;
BeginUpdateLumenSceneTasks ( GraphBuilder , FrameTemporaries ) ;
}
/*------------------------------------------------------------------------------
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 < FPrimitiveComponentId , FHLODSceneNodeVisibilityState > & VisibilityStates = ViewState - > HLODSceneNodeVisibilityStates ;
if ( VisibilityStates . Num ( ) > 0 )
{
VisibilityStates . Empty ( 0 ) ;
}
}
}
void FLODSceneTree : : UpdateVisibilityStates ( 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
// Per-frame initialization
FHLODVisibilityState & HLODState = ViewState - > HLODVisibilityState ;
TMap < FPrimitiveComponentId , FHLODSceneNodeVisibilityState > & 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 < FPrimitiveViewRelevance , SceneRenderingAllocator > & RelevanceMap = View . PrimitiveViewRelevanceMap ;
if ( HLODState . PrimitiveFadingLODMap . Num ( ) ! = Scene - > Primitives . Num ( ) )
{
checkf ( 0 , TEXT ( " HLOD update incorrectly allocated primitive maps " ) ) ;
return ;
}
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 < FStaticMeshBatchRelevance > & 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
const float DistanceSquared = Bounds . BoxSphereBounds . ComputeSquaredDistanceFromBoxToPoint ( View . ViewMatrices . GetViewOrigin ( ) ) ;
const bool bNearCulled = DistanceSquared < FMath : : Square ( Bounds . MinDrawDistance ) * HLODState . FOVDistanceScaleSq ;
const bool bFarCulled = DistanceSquared > 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 )
{
FLightPrimitiveInteraction * NodeLightList = SceneInfo - > LightList ;
while ( NodeLightList )
{
NodeLightList - > FlushCachedShadowMapData ( ) ;
NodeLightList = NodeLightList - > GetNextLight ( ) ;
}
}
// 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 ) ;
}
}
}
}
}
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 < FPrimitiveComponentId , FHLODSceneNodeVisibilityState > & 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 ) ;
}
}
}
}
}