Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/SceneVisibility.cpp
2025-05-18 13:04:45 +08:00

6229 lines
235 KiB
C++

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