You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb none #jira UE-152561 #preflight 628f49d99d313ae1c7ddf9a8 [CL 20377304 by Guillaume Abadie in ue5-main branch]
5479 lines
212 KiB
C++
5479 lines
212 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
SceneVisibility.cpp: Scene visibility determination.
|
|
=============================================================================*/
|
|
#include "CoreMinimal.h"
|
|
#include "HAL/ThreadSafeCounter.h"
|
|
#include "Stats/Stats.h"
|
|
#include "Misc/MemStack.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "Misc/App.h"
|
|
#include "Async/TaskGraphInterfaces.h"
|
|
#include "EngineDefines.h"
|
|
#include "EngineGlobals.h"
|
|
#include "EngineStats.h"
|
|
#include "RHIDefinitions.h"
|
|
#include "SceneTypes.h"
|
|
#include "SceneInterface.h"
|
|
#include "RendererInterface.h"
|
|
#include "PrimitiveViewRelevance.h"
|
|
#include "MaterialShared.h"
|
|
#include "SceneManagement.h"
|
|
#include "ScenePrivateBase.h"
|
|
#include "PostProcess/SceneRenderTargets.h"
|
|
#include "SceneCore.h"
|
|
#include "SceneOcclusion.h"
|
|
#include "LightSceneInfo.h"
|
|
#include "SceneRendering.h"
|
|
#include "DeferredShadingRenderer.h"
|
|
#include "DynamicPrimitiveDrawing.h"
|
|
#include "ScenePrivate.h"
|
|
#include "FXSystem.h"
|
|
#include "PostProcess/PostProcessing.h"
|
|
#include "SceneView.h"
|
|
#include "Engine/LODActor.h"
|
|
#include "GPUScene.h"
|
|
#include "TranslucentRendering.h"
|
|
#include "Async/ParallelFor.h"
|
|
#include "HairStrands/HairStrandsRendering.h"
|
|
#include "HairStrands/HairStrandsData.h"
|
|
#include "RectLightSceneProxy.h"
|
|
#include "Math/Halton.h"
|
|
#include "ProfilingDebugging/DiagnosticTable.h"
|
|
#include "Algo/Unique.h"
|
|
#include "InstanceCulling/InstanceCullingManager.h"
|
|
#include "TemporalAA.h"
|
|
#include "RayTracing/RayTracingInstanceCulling.h"
|
|
#include "RendererModule.h"
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
Globals
|
|
------------------------------------------------------------------------------*/
|
|
|
|
static float GWireframeCullThreshold = 5.0f;
|
|
static FAutoConsoleVariableRef CVarWireframeCullThreshold(
|
|
TEXT("r.WireframeCullThreshold"),
|
|
GWireframeCullThreshold,
|
|
TEXT("Threshold below which objects in ortho wireframe views will be culled."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
float GMinScreenRadiusForLights = 0.03f;
|
|
static FAutoConsoleVariableRef CVarMinScreenRadiusForLights(
|
|
TEXT("r.MinScreenRadiusForLights"),
|
|
GMinScreenRadiusForLights,
|
|
TEXT("Threshold below which lights will be culled."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
float GMinScreenRadiusForDepthPrepass = 0.03f;
|
|
static FAutoConsoleVariableRef CVarMinScreenRadiusForDepthPrepass(
|
|
TEXT("r.MinScreenRadiusForDepthPrepass"),
|
|
GMinScreenRadiusForDepthPrepass,
|
|
TEXT("Threshold below which meshes will be culled from depth only pass."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
float GMinScreenRadiusForCSMDepth = 0.01f;
|
|
static FAutoConsoleVariableRef CVarMinScreenRadiusForCSMDepth(
|
|
TEXT("r.MinScreenRadiusForCSMDepth"),
|
|
GMinScreenRadiusForCSMDepth,
|
|
TEXT("Threshold below which meshes will be culled from CSM depth pass."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarTemporalAASamples(
|
|
TEXT("r.TemporalAASamples"),
|
|
8,
|
|
TEXT("Number of jittered positions for temporal AA (4, 8=default, 16, 32, 64)."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static int32 GHZBOcclusion = 0;
|
|
static FAutoConsoleVariableRef CVarHZBOcclusion(
|
|
TEXT("r.HZBOcclusion"),
|
|
GHZBOcclusion,
|
|
TEXT("Defines which occlusion system is used.\n")
|
|
TEXT(" 0: Hardware occlusion queries\n")
|
|
TEXT(" 1: Use HZB occlusion system (default, less GPU and CPU cost, more conservative results)")
|
|
TEXT(" 2: Force HZB occlusion system (overrides rendering platform preferences)"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GVisualizeOccludedPrimitives = 0;
|
|
static FAutoConsoleVariableRef CVarVisualizeOccludedPrimitives(
|
|
TEXT("r.VisualizeOccludedPrimitives"),
|
|
GVisualizeOccludedPrimitives,
|
|
TEXT("Draw boxes for all occluded primitives"),
|
|
ECVF_RenderThreadSafe | ECVF_Cheat
|
|
);
|
|
|
|
static int32 GAllowSubPrimitiveQueries = 1;
|
|
static FAutoConsoleVariableRef CVarAllowSubPrimitiveQueries(
|
|
TEXT("r.AllowSubPrimitiveQueries"),
|
|
GAllowSubPrimitiveQueries,
|
|
TEXT("Enables sub primitive queries, currently only used by hierarchical instanced static meshes. 1: Enable, 0 Disabled. When disabled, one query is used for the entire proxy."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
RENDERER_API TAutoConsoleVariable<float> CVarStaticMeshLODDistanceScale(
|
|
TEXT("r.StaticMeshLODDistanceScale"),
|
|
1.0f,
|
|
TEXT("Scale factor for the distance used in computing discrete LOD for static meshes. (defaults to 1)\n")
|
|
TEXT("(higher values make LODs transition earlier, e.g., 2 is twice as fast / half the distance)"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<float> CVarMinAutomaticViewMipBias(
|
|
TEXT("r.ViewTextureMipBias.Min"),
|
|
-2.0f,
|
|
TEXT("Automatic view mip bias's minimum value (default to -2)."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<float> CVarMinAutomaticViewMipBiasOffset(
|
|
TEXT("r.ViewTextureMipBias.Offset"),
|
|
-0.3,
|
|
TEXT("Automatic view mip bias's constant offset (default to -0.3)."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
|
|
static int32 GOcclusionCullParallelPrimFetch = 0;
|
|
static FAutoConsoleVariableRef CVarOcclusionCullParallelPrimFetch(
|
|
TEXT("r.OcclusionCullParallelPrimFetch"),
|
|
GOcclusionCullParallelPrimFetch,
|
|
TEXT("Enables Parallel Occlusion Cull primitive fetch."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GILCUpdatePrimTaskEnabled = 1;
|
|
|
|
static FAutoConsoleVariableRef CVarILCUpdatePrimitivesTask(
|
|
TEXT("r.Cache.UpdatePrimsTaskEnabled"),
|
|
GILCUpdatePrimTaskEnabled,
|
|
TEXT("Enable threading for ILC primitive update. Will overlap with the rest the end of InitViews."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GEarlyInitDynamicShadows = 1;
|
|
static FAutoConsoleVariableRef CVarEarlyInitDynamicShadows(
|
|
TEXT("r.EarlyInitDynamicShadows"),
|
|
GEarlyInitDynamicShadows,
|
|
TEXT("Starts shadow culling tasks earlier in the frame."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GFramesNotOcclusionTestedToExpandBBoxes = 5;
|
|
static FAutoConsoleVariableRef CVarFramesNotOcclusionTestedToExpandBBoxes(
|
|
TEXT("r.GFramesNotOcclusionTestedToExpandBBoxes"),
|
|
GFramesNotOcclusionTestedToExpandBBoxes,
|
|
TEXT("If we don't occlusion test a primitive for this many frames, then we expand the BBox when we do occlusion test it for a few frames. See also r.ExpandNewlyOcclusionTestedBBoxesAmount, r.FramesToExpandNewlyOcclusionTestedBBoxes"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GFramesToExpandNewlyOcclusionTestedBBoxes = 2;
|
|
static FAutoConsoleVariableRef CVarFramesToExpandNewlyOcclusionTestedBBoxes(
|
|
TEXT("r.FramesToExpandNewlyOcclusionTestedBBoxes"),
|
|
GFramesToExpandNewlyOcclusionTestedBBoxes,
|
|
TEXT("If we don't occlusion test a primitive for r.GFramesNotOcclusionTestedToExpandBBoxes frames, then we expand the BBox when we do occlusion test it for this number of frames. See also r.GFramesNotOcclusionTestedToExpandBBoxes, r.ExpandNewlyOcclusionTestedBBoxesAmount"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static float GExpandNewlyOcclusionTestedBBoxesAmount = 0.0f;
|
|
static FAutoConsoleVariableRef CVarExpandNewlyOcclusionTestedBBoxesAmount(
|
|
TEXT("r.ExpandNewlyOcclusionTestedBBoxesAmount"),
|
|
GExpandNewlyOcclusionTestedBBoxesAmount,
|
|
TEXT("If we don't occlusion test a primitive for r.GFramesNotOcclusionTestedToExpandBBoxes frames, then we expand the BBox when we do occlusion test it for a few frames by this amount. See also r.FramesToExpandNewlyOcclusionTestedBBoxes, r.GFramesNotOcclusionTestedToExpandBBoxes."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static float GExpandAllTestedBBoxesAmount = 0.0f;
|
|
static FAutoConsoleVariableRef CVarExpandAllTestedBBoxesAmount(
|
|
TEXT("r.ExpandAllOcclusionTestedBBoxesAmount"),
|
|
GExpandAllTestedBBoxesAmount,
|
|
TEXT("Amount to expand all occlusion test bounds by."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static float GNeverOcclusionTestDistance = 0.0f;
|
|
static FAutoConsoleVariableRef CVarNeverOcclusionTestDistance(
|
|
TEXT("r.NeverOcclusionTestDistance"),
|
|
GNeverOcclusionTestDistance,
|
|
TEXT("When the distance between the viewpoint and the bounding sphere center is less than this, never occlusion cull."),
|
|
ECVF_RenderThreadSafe | ECVF_Scalability
|
|
);
|
|
|
|
static int32 GForceSceneHasDecals = 0;
|
|
static FAutoConsoleVariableRef CVarForceSceneHasDecals(
|
|
TEXT("r.ForceSceneHasDecals"),
|
|
GForceSceneHasDecals,
|
|
TEXT("Whether to always assume that scene has decals, so we don't switch depth state conditionally. This can significantly reduce total number of PSOs at a minor GPU cost."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static float GCameraCutTranslationThreshold = 10000.0f;
|
|
static FAutoConsoleVariableRef CVarCameraCutTranslationThreshold(
|
|
TEXT("r.CameraCutTranslationThreshold"),
|
|
GCameraCutTranslationThreshold,
|
|
TEXT("The maximum camera translation disatance in centimeters allowed between two frames before a camera cut is automatically inserted."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
/** Distance fade cvars */
|
|
static int32 GDisableLODFade = false;
|
|
static FAutoConsoleVariableRef CVarDisableLODFade( TEXT("r.DisableLODFade"), GDisableLODFade, TEXT("Disable fading for distance culling"), ECVF_RenderThreadSafe );
|
|
|
|
static float GFadeTime = 0.25f;
|
|
static FAutoConsoleVariableRef CVarLODFadeTime( TEXT("r.LODFadeTime"), GFadeTime, TEXT("How long LOD takes to fade (in seconds)."), ECVF_RenderThreadSafe );
|
|
|
|
static float GDistanceFadeMaxTravel = 1000.0f;
|
|
static FAutoConsoleVariableRef CVarDistanceFadeMaxTravel( TEXT("r.DistanceFadeMaxTravel"), GDistanceFadeMaxTravel, TEXT("Max distance that the player can travel during the fade time."), ECVF_RenderThreadSafe );
|
|
|
|
static TAutoConsoleVariable<int32> CVarParallelInitViews(
|
|
TEXT("r.ParallelInitViews"),
|
|
1,
|
|
TEXT("Toggles parallel init views. 0 = off; 1 = on"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
float GLightMaxDrawDistanceScale = 1.0f;
|
|
static FAutoConsoleVariableRef CVarLightMaxDrawDistanceScale(
|
|
TEXT("r.LightMaxDrawDistanceScale"),
|
|
GLightMaxDrawDistanceScale,
|
|
TEXT("Scale applied to the MaxDrawDistance of lights. Useful for fading out local lights more aggressively on some platforms."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<bool> CVarEnableFrustumCull(
|
|
TEXT("r.EnableFrustumCull"),
|
|
true,
|
|
TEXT("Enables or disables frustum culling. Useful for comparing results to ensure culling is functioning properly."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarAlsoUseSphereForFrustumCull(
|
|
TEXT("r.AlsoUseSphereForFrustumCull"),
|
|
0,
|
|
TEXT("Performance tweak. If > 0, then use a sphere cull before and in addition to a box for frustum culling."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarUseFastIntersect(
|
|
TEXT("r.UseFastIntersect"),
|
|
1,
|
|
TEXT("Use optimized 8 plane fast intersection code if we have 8 permuted planes."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarUseVisibilityOctree(
|
|
TEXT("r.UseVisibilityOctree"),
|
|
0,
|
|
TEXT("Use the octree for visibility calculations."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static bool GOcclusionSingleRHIThreadStall = false;
|
|
static FAutoConsoleVariableRef CVarOcclusionSingleRHIThreadStall(
|
|
TEXT("r.Occlusion.SingleRHIThreadStall"),
|
|
GOcclusionSingleRHIThreadStall,
|
|
TEXT("Enable a single RHI thread stall before polling occlusion queries. This will only happen if the RHI's occlusion queries would normally stall the RHI thread themselves."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
|
|
static TAutoConsoleVariable<int32> CVarTAADebugOverrideTemporalIndex(
|
|
TEXT("r.TemporalAA.Debug.OverrideTemporalIndex"), -1,
|
|
TEXT("Override the temporal index for debugging purposes."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<float> CVarFreezeTemporalSequences(
|
|
TEXT("r.Test.FreezeTemporalSequences"), 0,
|
|
TEXT("Freezes all temporal sequences."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<float> CVarFreezeTemporalHistories(
|
|
TEXT("r.Test.FreezeTemporalHistories"), 0,
|
|
TEXT("Freezes all temporal histories as well as the temporal sequence."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static bool bDumpPrimitivesNextFrame = false;
|
|
|
|
static FAutoConsoleCommand CVarDumpPrimitives(
|
|
TEXT("DumpPrimitives"),
|
|
TEXT("Writes out all scene primitive names to a CSV file"),
|
|
FConsoleCommandDelegate::CreateStatic([] { bDumpPrimitivesNextFrame = true; }),
|
|
ECVF_Default);
|
|
|
|
#endif
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("Occlusion Readback"), STAT_CLMM_OcclusionReadback, STATGROUP_CommandListMarkers);
|
|
DECLARE_CYCLE_STAT(TEXT("After Occlusion Readback"), STAT_CLMM_AfterOcclusionReadback, STATGROUP_CommandListMarkers);
|
|
|
|
/*------------------------------------------------------------------------------
|
|
Visibility determination.
|
|
------------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Update a primitive's fading state.
|
|
* @param FadingState - State to update.
|
|
* @param View - The view for which to update.
|
|
* @param bVisible - Whether the primitive should be visible in the view.
|
|
*/
|
|
static void UpdatePrimitiveFadingStateHelper(FPrimitiveFadingState& FadingState, const FViewInfo& View, bool bVisible)
|
|
{
|
|
if (FadingState.bValid)
|
|
{
|
|
if (FadingState.bIsVisible != bVisible)
|
|
{
|
|
float CurrentRealTime = View.Family->Time.GetRealTimeSeconds();
|
|
|
|
// Need to kick off a fade, so make sure that we have fading state for that
|
|
if( !IsValidRef(FadingState.UniformBuffer) )
|
|
{
|
|
// Primitive is not currently fading. Start a new fade!
|
|
FadingState.EndTime = CurrentRealTime + GFadeTime;
|
|
|
|
if( bVisible )
|
|
{
|
|
// Fading in
|
|
// (Time - StartTime) / FadeTime
|
|
FadingState.FadeTimeScaleBias.X = 1.0f / GFadeTime;
|
|
FadingState.FadeTimeScaleBias.Y = -CurrentRealTime / GFadeTime;
|
|
}
|
|
else
|
|
{
|
|
// Fading out
|
|
// 1 - (Time - StartTime) / FadeTime
|
|
FadingState.FadeTimeScaleBias.X = -1.0f / GFadeTime;
|
|
FadingState.FadeTimeScaleBias.Y = 1.0f + CurrentRealTime / GFadeTime;
|
|
}
|
|
|
|
FDistanceCullFadeUniformShaderParameters Uniforms;
|
|
Uniforms.FadeTimeScaleBias = FVector2f(FadingState.FadeTimeScaleBias); // LWC_TODO: Precision loss
|
|
FadingState.UniformBuffer = FDistanceCullFadeUniformBufferRef::CreateUniformBufferImmediate( Uniforms, UniformBuffer_MultiFrame );
|
|
}
|
|
else
|
|
{
|
|
// Reverse fading direction but maintain current opacity
|
|
// Solve for d: a*x+b = -a*x+d
|
|
FadingState.FadeTimeScaleBias.Y = 2.0f * CurrentRealTime * FadingState.FadeTimeScaleBias.X + FadingState.FadeTimeScaleBias.Y;
|
|
FadingState.FadeTimeScaleBias.X = -FadingState.FadeTimeScaleBias.X;
|
|
|
|
if( bVisible )
|
|
{
|
|
// Fading in
|
|
// Solve for x: a*x+b = 1
|
|
FadingState.EndTime = ( 1.0f - FadingState.FadeTimeScaleBias.Y ) / FadingState.FadeTimeScaleBias.X;
|
|
}
|
|
else
|
|
{
|
|
// Fading out
|
|
// Solve for x: a*x+b = 0
|
|
FadingState.EndTime = -FadingState.FadeTimeScaleBias.Y / FadingState.FadeTimeScaleBias.X;
|
|
}
|
|
|
|
FDistanceCullFadeUniformShaderParameters Uniforms;
|
|
Uniforms.FadeTimeScaleBias = FVector2f(FadingState.FadeTimeScaleBias); // LWC_TODO: Precision loss
|
|
FadingState.UniformBuffer = FDistanceCullFadeUniformBufferRef::CreateUniformBufferImmediate( Uniforms, UniformBuffer_MultiFrame );
|
|
}
|
|
}
|
|
}
|
|
|
|
FadingState.FrameNumber = View.Family->FrameNumber;
|
|
FadingState.bIsVisible = bVisible;
|
|
FadingState.bValid = true;
|
|
}
|
|
|
|
bool FViewInfo::IsDistanceCulled( float DistanceSquared, float MinDrawDistance, float InMaxDrawDistance, const FPrimitiveSceneInfo* PrimitiveSceneInfo)
|
|
{
|
|
bool bMayBeFading;
|
|
bool bFadingIn;
|
|
bool bDistanceCulled = IsDistanceCulled_AnyThread(DistanceSquared, MinDrawDistance, InMaxDrawDistance, PrimitiveSceneInfo, bMayBeFading, bFadingIn);
|
|
|
|
if (bMayBeFading)
|
|
{
|
|
bDistanceCulled = UpdatePrimitiveFadingState(PrimitiveSceneInfo, bFadingIn);
|
|
}
|
|
|
|
return bDistanceCulled;
|
|
}
|
|
|
|
bool FViewInfo::IsDistanceCulled_AnyThread(float DistanceSquared, float MinDrawDistance, float InMaxDrawDistance, const FPrimitiveSceneInfo* PrimitiveSceneInfo, bool& bOutMayBeFading, bool& bOutFadingIn) const
|
|
{
|
|
const float MaxDrawDistanceScale = GetCachedScalabilityCVars().ViewDistanceScale;
|
|
const float FadeRadius = GDisableLODFade ? 0.0f : GDistanceFadeMaxTravel;
|
|
const float MaxDrawDistance = InMaxDrawDistance * MaxDrawDistanceScale;
|
|
|
|
bool bHasMaxDrawDistance = InMaxDrawDistance != FLT_MAX;
|
|
bool bHasMinDrawDistance = InMaxDrawDistance > 0;
|
|
bOutMayBeFading = false;
|
|
|
|
|
|
if (!bHasMaxDrawDistance && !bHasMinDrawDistance)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If cull distance is disabled, always show (except foliage)
|
|
if (Family->EngineShowFlags.DistanceCulledPrimitives && !PrimitiveSceneInfo->Proxy->IsDetailMesh())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// The primitive is always culled if it exceeds the max fade distance.
|
|
if ((bHasMaxDrawDistance && DistanceSquared > FMath::Square(MaxDrawDistance + FadeRadius)) || (bHasMinDrawDistance && DistanceSquared < FMath::Square(MinDrawDistance)))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const bool bDistanceCulled = bHasMaxDrawDistance && (DistanceSquared > FMath::Square(MaxDrawDistance));
|
|
const bool bMayBeFading = bHasMaxDrawDistance && (DistanceSquared > FMath::Square(MaxDrawDistance - FadeRadius));
|
|
|
|
if (!GDisableLODFade && bMayBeFading && State != NULL && !bDisableDistanceBasedFadeTransitions && PrimitiveSceneInfo->Proxy->IsUsingDistanceCullFade())
|
|
{
|
|
// Don't update primitive fading state yet because current thread may be not render thread
|
|
bOutMayBeFading = true;
|
|
bOutFadingIn = !bDistanceCulled;
|
|
}
|
|
|
|
return bDistanceCulled && !bOutMayBeFading;
|
|
}
|
|
|
|
bool FViewInfo::UpdatePrimitiveFadingState(const FPrimitiveSceneInfo* PrimitiveSceneInfo, bool bFadingIn)
|
|
{
|
|
// Update distance-based visibility and fading state if it has not already been updated.
|
|
const int32 PrimitiveIndex = PrimitiveSceneInfo->GetIndex();
|
|
const FRelativeBitReference PrimitiveBit(PrimitiveIndex);
|
|
bool bStillFading = false;
|
|
|
|
if (!PotentiallyFadingPrimitiveMap.AccessCorrespondingBit(PrimitiveBit))
|
|
{
|
|
FPrimitiveFadingState& FadingState = ((FSceneViewState*)State)->PrimitiveFadingStates.FindOrAdd(PrimitiveSceneInfo->PrimitiveComponentId);
|
|
UpdatePrimitiveFadingStateHelper(FadingState, *this, bFadingIn);
|
|
FRHIUniformBuffer* UniformBuffer = FadingState.UniformBuffer;
|
|
bStillFading = UniformBuffer != nullptr;
|
|
PrimitiveFadeUniformBuffers[PrimitiveIndex] = UniformBuffer;
|
|
PrimitiveFadeUniformBufferMap[PrimitiveIndex] = UniformBuffer != nullptr;
|
|
PotentiallyFadingPrimitiveMap.AccessCorrespondingBit(PrimitiveBit) = true;
|
|
}
|
|
|
|
// If we're still fading then make sure the object is still drawn, even if it's beyond the max draw distance
|
|
return !bFadingIn && !bStillFading;
|
|
}
|
|
|
|
FORCEINLINE bool IntersectBox8Plane(const FVector& InOrigin, const FVector& InExtent, const FPlane*PermutedPlanePtr)
|
|
{
|
|
// this removes a lot of the branches as we know there's 8 planes
|
|
// copied directly out of ConvexVolume.cpp
|
|
const VectorRegister Origin = VectorLoadFloat3(&InOrigin);
|
|
const VectorRegister Extent = VectorLoadFloat3(&InExtent);
|
|
|
|
const VectorRegister PlanesX_0 = VectorLoadAligned(&PermutedPlanePtr[0]);
|
|
const VectorRegister PlanesY_0 = VectorLoadAligned(&PermutedPlanePtr[1]);
|
|
const VectorRegister PlanesZ_0 = VectorLoadAligned(&PermutedPlanePtr[2]);
|
|
const VectorRegister PlanesW_0 = VectorLoadAligned(&PermutedPlanePtr[3]);
|
|
|
|
const VectorRegister PlanesX_1 = VectorLoadAligned(&PermutedPlanePtr[4]);
|
|
const VectorRegister PlanesY_1 = VectorLoadAligned(&PermutedPlanePtr[5]);
|
|
const VectorRegister PlanesZ_1 = VectorLoadAligned(&PermutedPlanePtr[6]);
|
|
const VectorRegister PlanesW_1 = VectorLoadAligned(&PermutedPlanePtr[7]);
|
|
|
|
// Splat origin into 3 vectors
|
|
VectorRegister OrigX = VectorReplicate(Origin, 0);
|
|
VectorRegister OrigY = VectorReplicate(Origin, 1);
|
|
VectorRegister OrigZ = VectorReplicate(Origin, 2);
|
|
// Splat the already abs Extent for the push out calculation
|
|
VectorRegister AbsExtentX = VectorReplicate(Extent, 0);
|
|
VectorRegister AbsExtentY = VectorReplicate(Extent, 1);
|
|
VectorRegister AbsExtentZ = VectorReplicate(Extent, 2);
|
|
|
|
// Calculate the distance (x * x) + (y * y) + (z * z) - w
|
|
VectorRegister DistX_0 = VectorMultiply(OrigX, PlanesX_0);
|
|
VectorRegister DistY_0 = VectorMultiplyAdd(OrigY, PlanesY_0, DistX_0);
|
|
VectorRegister DistZ_0 = VectorMultiplyAdd(OrigZ, PlanesZ_0, DistY_0);
|
|
VectorRegister Distance_0 = VectorSubtract(DistZ_0, PlanesW_0);
|
|
// Now do the push out FMath::Abs(x * x) + FMath::Abs(y * y) + FMath::Abs(z * z)
|
|
VectorRegister PushX_0 = VectorMultiply(AbsExtentX, VectorAbs(PlanesX_0));
|
|
VectorRegister PushY_0 = VectorMultiplyAdd(AbsExtentY, VectorAbs(PlanesY_0), PushX_0);
|
|
VectorRegister PushOut_0 = VectorMultiplyAdd(AbsExtentZ, VectorAbs(PlanesZ_0), PushY_0);
|
|
|
|
// Check for completely outside
|
|
if (VectorAnyGreaterThan(Distance_0, PushOut_0))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Calculate the distance (x * x) + (y * y) + (z * z) - w
|
|
VectorRegister DistX_1 = VectorMultiply(OrigX, PlanesX_1);
|
|
VectorRegister DistY_1 = VectorMultiplyAdd(OrigY, PlanesY_1, DistX_1);
|
|
VectorRegister DistZ_1 = VectorMultiplyAdd(OrigZ, PlanesZ_1, DistY_1);
|
|
VectorRegister Distance_1 = VectorSubtract(DistZ_1, PlanesW_1);
|
|
// Now do the push out FMath::Abs(x * x) + FMath::Abs(y * y) + FMath::Abs(z * z)
|
|
VectorRegister PushX_1 = VectorMultiply(AbsExtentX, VectorAbs(PlanesX_1));
|
|
VectorRegister PushY_1 = VectorMultiplyAdd(AbsExtentY, VectorAbs(PlanesY_1), PushX_1);
|
|
VectorRegister PushOut_1 = VectorMultiplyAdd(AbsExtentZ, VectorAbs(PlanesZ_1), PushY_1);
|
|
|
|
// Check for completely outside
|
|
if (VectorAnyGreaterThan(Distance_1, PushOut_1))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int32 FrustumCullNumWordsPerTask = 128;
|
|
static FAutoConsoleVariableRef CVarFrustumCullNumWordsPerTask(
|
|
TEXT("r.FrustumCullNumWordsPerTask"),
|
|
FrustumCullNumWordsPerTask,
|
|
TEXT("Performance tweak. Controls the granularity for the ParallelFor for frustum culling."),
|
|
ECVF_Default
|
|
);
|
|
|
|
static TAutoConsoleVariable CVarNaniteMeshsAlwaysVisible(
|
|
TEXT("r.Nanite.PrimitivesAlwaysVisible"),
|
|
0,
|
|
TEXT("True - All Nanite primitives skip culling phases, False - All Nanite primitives are run through the culling phase."),
|
|
ECVF_Default
|
|
);
|
|
|
|
// Access when not on the render thread
|
|
FORCEINLINE bool IsAlwaysVisible(const FScene* RESTRICT Scene, int32 Index, bool bNaniteAlwaysVisible)
|
|
{
|
|
return bNaniteAlwaysVisible ? Scene->PrimitiveFlagsCompact[Index].bIsNaniteMesh : false;
|
|
}
|
|
|
|
// Non template version
|
|
FORCEINLINE bool IsAlwaysVisible(const FScene* RESTRICT Scene, int32 Index)
|
|
{
|
|
if (CVarNaniteMeshsAlwaysVisible.GetValueOnRenderThread())
|
|
{
|
|
return Scene->PrimitiveFlagsCompact[Index].bIsNaniteMesh;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
struct FPrimitiveCullingFlags
|
|
{
|
|
bool bShouldVisibilityCull;
|
|
bool bUseCustomCulling;
|
|
bool bAlsoUseSphereTest;
|
|
bool bUseFastIntersect;
|
|
bool bUseVisibilityOctree;
|
|
bool bNaniteAlwaysVisible;
|
|
bool bHasHiddenPrimitives;
|
|
bool bHasShowOnlyPrimitives;
|
|
|
|
#if RHI_RAYTRACING
|
|
bool bCullInRayTracing;
|
|
#endif
|
|
};
|
|
|
|
// Returns true if the frustum and bounds intersect
|
|
FORCEINLINE bool IsPrimitiveVisible(FViewInfo& View, const FPlane* PermutedPlanePtr, const FPrimitiveBounds& RESTRICT Bounds, int32 VisibilityId, const FPrimitiveCullingFlags& Flags)
|
|
{
|
|
// The custom culling and sphere culling are additional tests, meaning that if they pass, the
|
|
// remaining culling tests will still be performed. If any of the tests fail, then the primitive
|
|
// is culled, and the remaining tests do not need be performed
|
|
|
|
if (Flags.bUseCustomCulling && !View.CustomVisibilityQuery->IsVisible(VisibilityId, FBoxSphereBounds(Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.BoxExtent, Bounds.BoxSphereBounds.SphereRadius)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Flags.bAlsoUseSphereTest && !View.ViewFrustum.IntersectSphere(Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.SphereRadius))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Flags.bUseFastIntersect)
|
|
{
|
|
return IntersectBox8Plane(Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.BoxExtent, PermutedPlanePtr);
|
|
}
|
|
else
|
|
{
|
|
return View.ViewFrustum.IntersectBox(Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.BoxExtent);
|
|
}
|
|
}
|
|
|
|
FORCEINLINE bool IsPrimitiveHidden(const FScene* RESTRICT Scene, FViewInfo& View, int PrimitiveIndex, const FPrimitiveCullingFlags& Flags)
|
|
{
|
|
// If any primitives are explicitly hidden, remove them now.
|
|
if (Flags.bHasHiddenPrimitives && View.HiddenPrimitives.Contains(Scene->PrimitiveComponentIds[PrimitiveIndex]))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If the view has any show only primitives, hide everything else
|
|
if (Flags.bHasShowOnlyPrimitives && !View.ShowOnlyPrimitives->Contains(Scene->PrimitiveComponentIds[PrimitiveIndex]))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
|
|
FORCEINLINE bool ShouldCullForRayTracing(const FScene* RESTRICT Scene, FViewInfo& View, int32 PrimitiveIndex)
|
|
{
|
|
const FRayTracingCullingParameters& RayTracingCullingParameters = View.RayTracingCullingParameters;
|
|
|
|
if (RayTracing::CullPrimitiveByFlags(RayTracingCullingParameters, Scene, PrimitiveIndex))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const bool bIsFarFieldPrimitive = EnumHasAnyFlags(Scene->PrimitiveRayTracingFlags[PrimitiveIndex], ERayTracingPrimitiveFlags::FarField);
|
|
const Experimental::FHashElementId GroupId = Scene->PrimitiveRayTracingGroupIds[PrimitiveIndex];
|
|
|
|
if (RayTracingCullingParameters.bCullUsingGroupIds && GroupId.IsValid())
|
|
{
|
|
const FBoxSphereBounds& GroupBounds = Scene->PrimitiveRayTracingGroups.GetByElementId(GroupId).Value.Bounds;
|
|
const float GroupMinDrawDistance = Scene->PrimitiveRayTracingGroups.GetByElementId(GroupId).Value.MinDrawDistance;
|
|
return RayTracing::ShouldCullBounds(RayTracingCullingParameters, GroupBounds, GroupMinDrawDistance, bIsFarFieldPrimitive);
|
|
}
|
|
else
|
|
{
|
|
const FPrimitiveBounds& RESTRICT Bounds = Scene->PrimitiveBounds[PrimitiveIndex];
|
|
return RayTracing::ShouldCullBounds(RayTracingCullingParameters, Bounds.BoxSphereBounds, Bounds.MinDrawDistance, bIsFarFieldPrimitive);
|
|
}
|
|
};
|
|
#endif //RHI_RAYTRACING
|
|
|
|
static FORCEINLINE void CullOctree(const FScene* RESTRICT Scene, FViewInfo& View, const FPrimitiveCullingFlags& Flags, FSceneBitArray& OutVisibleNodes)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(SceneVisibility_CullOctree);
|
|
|
|
// Two bits per octree node, 1st bit is Inisde Frustum, 2nd bit is Outside Frustum
|
|
OutVisibleNodes.Init(false, Scene->PrimitiveOctree.GetNumNodes() * 2);
|
|
|
|
Scene->PrimitiveOctree.FindNodesWithPredicate(
|
|
[&View, &OutVisibleNodes, &Flags](FScenePrimitiveOctree::FNodeIndex ParentNodeIndex, FScenePrimitiveOctree::FNodeIndex NodeIndex, const FBoxCenterAndExtent& NodeBounds)
|
|
{
|
|
// If the parent node is completely contained there is no need to test containment
|
|
if (ParentNodeIndex != INDEX_NONE && !OutVisibleNodes[(ParentNodeIndex * 2) + 1])
|
|
{
|
|
OutVisibleNodes[NodeIndex * 2] = true;
|
|
OutVisibleNodes[NodeIndex * 2 + 1] = false;
|
|
return true;
|
|
}
|
|
|
|
const FPlane* PermutedPlanePtr = View.ViewFrustum.PermutedPlanes.GetData();
|
|
bool bIntersects = false;
|
|
|
|
if (Flags.bUseFastIntersect)
|
|
{
|
|
bIntersects = IntersectBox8Plane(NodeBounds.Center, NodeBounds.Extent, PermutedPlanePtr);
|
|
}
|
|
else
|
|
{
|
|
bIntersects = View.ViewFrustum.IntersectBox(NodeBounds.Center, NodeBounds.Extent);
|
|
}
|
|
|
|
if (bIntersects)
|
|
{
|
|
OutVisibleNodes[NodeIndex * 2] = true;
|
|
OutVisibleNodes[NodeIndex * 2 + 1] = View.ViewFrustum.GetBoxIntersectionOutcode(NodeBounds.Center, NodeBounds.Extent).GetOutside();
|
|
}
|
|
|
|
return bIntersects;
|
|
},
|
|
[](FScenePrimitiveOctree::FNodeIndex /*ParentNodeIndex*/, FScenePrimitiveOctree::FNodeIndex /*NodeIndex*/, const FBoxCenterAndExtent& /*NodeBounds*/)
|
|
{
|
|
|
|
});
|
|
}
|
|
|
|
static void PrimitiveCullTask(FThreadSafeCounter& NumCulledPrimitives, const FScene* RESTRICT Scene, FViewInfo& View, FPrimitiveCullingFlags Flags, float MaxDrawDistanceScale, const FHLODVisibilityState* const HLODState, const FSceneBitArray& VisibleNodes, int32 TaskIndex)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(SceneVisibility_PrimitiveCull);
|
|
SCOPED_NAMED_EVENT(SceneVisibility_PrimitiveCull, FColor::Red);
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PrimitiveCull_Loop);
|
|
|
|
FTaskTagScope TaskTagScope(ETaskTag::EParallelRenderingThread);
|
|
|
|
bool bDisableLODFade = GDisableLODFade || View.bDisableDistanceBasedFadeTransitions;
|
|
const FPlane* PermutedPlanePtr = View.ViewFrustum.PermutedPlanes.GetData();
|
|
const int32 BitArrayNumInner = View.PrimitiveVisibilityMap.Num();
|
|
FVector ViewOriginForDistanceCulling = View.ViewMatrices.GetViewOrigin();
|
|
float FadeRadius = bDisableLODFade ? 0.0f : GDistanceFadeMaxTravel;
|
|
uint8 CustomVisibilityFlags = EOcclusionFlags::CanBeOccluded | EOcclusionFlags::HasPrecomputedVisibility;
|
|
|
|
uint32 NumPrimitivesCulledForTask = 0;
|
|
|
|
// Primitives may be explicitly removed from stereo views when using mono
|
|
const int32 TaskWordOffset = TaskIndex * FrustumCullNumWordsPerTask;
|
|
|
|
FVector ViewOrigin = View.ViewMatrices.GetViewOrigin();
|
|
|
|
for (int32 WordIndex = TaskWordOffset; WordIndex < TaskWordOffset + FrustumCullNumWordsPerTask && WordIndex * NumBitsPerDWORD < BitArrayNumInner; WordIndex++)
|
|
{
|
|
uint32 Mask = 0x1;
|
|
uint32 VisBits = 0;
|
|
uint32 FadingBits = 0;
|
|
|
|
// If visibility culling is disabled, make sure to use the existing visibility state
|
|
if (!Flags.bShouldVisibilityCull)
|
|
{
|
|
VisBits = View.PrimitiveVisibilityMap.GetData()[WordIndex];
|
|
}
|
|
|
|
|
|
#if RHI_RAYTRACING
|
|
uint32 RayTracingBits = 0;
|
|
#endif //RHI_RAYTRACING
|
|
for (int32 BitSubIndex = 0; BitSubIndex < NumBitsPerDWORD && WordIndex * NumBitsPerDWORD + BitSubIndex < BitArrayNumInner; BitSubIndex++, Mask <<= 1)
|
|
{
|
|
int32 Index = WordIndex * NumBitsPerDWORD + BitSubIndex;
|
|
bool bPrimitiveIsHidden = IsPrimitiveHidden(Scene, View, Index, Flags);
|
|
bool bIsVisible = Flags.bShouldVisibilityCull ? true : (VisBits & Mask) == Mask;
|
|
|
|
bIsVisible = bIsVisible && !bPrimitiveIsHidden;
|
|
|
|
#if RHI_RAYTRACING
|
|
bool bIsVisibleInRayTracing = true;
|
|
|
|
if (bPrimitiveIsHidden || (Flags.bCullInRayTracing && ShouldCullForRayTracing(Scene, View, Index)))
|
|
{
|
|
bIsVisibleInRayTracing = false;
|
|
}
|
|
#endif //RHI_RAYTRACING
|
|
|
|
const FPrimitiveBounds& RESTRICT Bounds = Scene->PrimitiveBounds[Index];
|
|
|
|
// Handle primitives that are not always visible.
|
|
if (Flags.bShouldVisibilityCull && bIsVisible && !IsAlwaysVisible(Scene, Index, Flags.bNaniteAlwaysVisible))
|
|
{
|
|
bool bShouldDistanceCull = true;
|
|
bool bPartiallyOutside = true;
|
|
bool bShouldFrustumCull = true;
|
|
|
|
// Fading HLODs and their children must be visible, objects hidden by HLODs can be culled
|
|
if (HLODState)
|
|
{
|
|
if (HLODState->IsNodeForcedVisible(Index))
|
|
{
|
|
bShouldFrustumCull = false;
|
|
bShouldDistanceCull = false;
|
|
}
|
|
else if (HLODState->IsNodeForcedHidden(Index))
|
|
{
|
|
bShouldDistanceCull = false;
|
|
bShouldFrustumCull = false;
|
|
bIsVisible = false;
|
|
}
|
|
}
|
|
|
|
// Frustum first
|
|
if (bShouldFrustumCull)
|
|
{
|
|
if (Flags.bUseVisibilityOctree)
|
|
{
|
|
// If the parent octree node was completely contained by the frustum, there is no need do an additional frustum test on the primitive bounds
|
|
// If the parent octree node is partially in the frustum, perform an additional test on the primitive bounds
|
|
uint32 OctreeNodeIndex = Scene->PrimitiveOctreeIndex[Index];
|
|
|
|
bIsVisible = VisibleNodes[OctreeNodeIndex * 2];
|
|
bPartiallyOutside = VisibleNodes[OctreeNodeIndex * 2 + 1];
|
|
}
|
|
|
|
if (bIsVisible)
|
|
{
|
|
int32 VisibilityId = INDEX_NONE;
|
|
|
|
if (Flags.bUseCustomCulling &&
|
|
((Scene->PrimitiveOcclusionFlags[Index] & CustomVisibilityFlags) == CustomVisibilityFlags))
|
|
{
|
|
VisibilityId = Scene->PrimitiveVisibilityIds[Index].ByteIndex;
|
|
}
|
|
|
|
bIsVisible = !bPartiallyOutside || IsPrimitiveVisible(View, PermutedPlanePtr, Bounds, VisibilityId, Flags);
|
|
}
|
|
}
|
|
|
|
bShouldDistanceCull = bShouldDistanceCull && bIsVisible;
|
|
|
|
// Distance cull if frustum cull passed
|
|
if (bShouldDistanceCull)
|
|
{
|
|
// If cull distance is disabled, always show the primitive (except foliage)
|
|
if (View.Family->EngineShowFlags.DistanceCulledPrimitives
|
|
&& !Scene->Primitives[Index]->Proxy->IsDetailMesh()) // Proxy call is intentionally behind the DistancedCulledPrimitives check to prevent an expensive memory read
|
|
{
|
|
bShouldDistanceCull = false;
|
|
}
|
|
}
|
|
|
|
if (bShouldDistanceCull)
|
|
{
|
|
// Preserve infinite draw distance
|
|
bool bHasMaxDrawDistance = Bounds.MaxCullDistance < FLT_MAX;
|
|
bool bHasMinDrawDistance = Bounds.MinDrawDistance > 0;
|
|
|
|
if (bHasMaxDrawDistance || bHasMinDrawDistance)
|
|
{
|
|
float MaxDrawDistance = Bounds.MaxCullDistance * MaxDrawDistanceScale;
|
|
float MinDrawDistanceSq = FMath::Square(Bounds.MinDrawDistance);
|
|
float DistanceSquared = FVector::DistSquared(Bounds.BoxSphereBounds.Origin, ViewOriginForDistanceCulling);
|
|
|
|
// Always test the fade in distance. If a primitive was set to always draw, it may need to be faded in.
|
|
if (bHasMaxDrawDistance)
|
|
{
|
|
float MaxFadeDistanceSquared = FMath::Square(MaxDrawDistance + FadeRadius);
|
|
float MinFadeDistanceSquared = FMath::Square(MaxDrawDistance - FadeRadius);
|
|
if ((DistanceSquared < MaxFadeDistanceSquared && DistanceSquared > MinFadeDistanceSquared)
|
|
&& Scene->Primitives[Index]->Proxy->IsUsingDistanceCullFade()) // Proxy call is intentionally behind the fade check to prevent an expensive memory read
|
|
{
|
|
FadingBits |= Mask;
|
|
}
|
|
}
|
|
|
|
// Check for distance culling first
|
|
const bool bFarDistanceCulled = bHasMaxDrawDistance && (DistanceSquared > FMath::Square(MaxDrawDistance));
|
|
const bool bNearDistanceCulled = bHasMinDrawDistance && (DistanceSquared < MinDrawDistanceSq);
|
|
bool bIsDistanceCulled = bNearDistanceCulled || bFarDistanceCulled;
|
|
|
|
if (bIsDistanceCulled)
|
|
{
|
|
bIsVisible = false;
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
if (bFarDistanceCulled)
|
|
{
|
|
bIsVisibleInRayTracing = false;
|
|
}
|
|
#endif //RHI_RAYTRACING
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bIsVisible)
|
|
{
|
|
// The primitive is visible!
|
|
VisBits |= Mask;
|
|
}
|
|
else
|
|
{
|
|
STAT(++NumPrimitivesCulledForTask);
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
if (bIsVisibleInRayTracing)
|
|
{
|
|
RayTracingBits |= Mask;
|
|
}
|
|
#endif //RHI_RAYTRACING
|
|
}
|
|
|
|
if (Flags.bShouldVisibilityCull && FadingBits)
|
|
{
|
|
checkSlow(!View.PotentiallyFadingPrimitiveMap.GetData()[WordIndex]); // this should start at zero
|
|
View.PotentiallyFadingPrimitiveMap.GetData()[WordIndex] = FadingBits;
|
|
}
|
|
|
|
if (Flags.bShouldVisibilityCull && VisBits)
|
|
{
|
|
checkSlow(!View.PrimitiveVisibilityMap.GetData()[WordIndex]); // this should start at zero
|
|
View.PrimitiveVisibilityMap.GetData()[WordIndex] = VisBits;
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
if (RayTracingBits)
|
|
{
|
|
checkSlow(!View.PrimitiveRayTracingVisibilityMap.GetData()[WordIndex]); // this should start at zero
|
|
View.PrimitiveRayTracingVisibilityMap.GetData()[WordIndex] = RayTracingBits;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
STAT(NumCulledPrimitives.Add(NumPrimitivesCulledForTask));
|
|
}
|
|
|
|
static int32 PrimitiveCull(const FScene* RESTRICT Scene, FViewInfo& View, bool bShouldVisibilityCull)
|
|
{
|
|
FPrimitiveCullingFlags Flags;
|
|
Flags.bShouldVisibilityCull = bShouldVisibilityCull;
|
|
Flags.bUseCustomCulling = View.CustomVisibilityQuery && View.CustomVisibilityQuery->Prepare();
|
|
Flags.bAlsoUseSphereTest = CVarAlsoUseSphereForFrustumCull.GetValueOnRenderThread() > 0;
|
|
Flags.bUseFastIntersect = (View.ViewFrustum.PermutedPlanes.Num() == 8) && CVarUseFastIntersect.GetValueOnRenderThread();
|
|
Flags.bUseVisibilityOctree = CVarUseVisibilityOctree.GetValueOnRenderThread() > 0;
|
|
Flags.bNaniteAlwaysVisible = CVarNaniteMeshsAlwaysVisible.GetValueOnRenderThread() > 0;
|
|
Flags.bHasHiddenPrimitives = View.HiddenPrimitives.Num() > 0;
|
|
Flags.bHasShowOnlyPrimitives = View.ShowOnlyPrimitives.IsSet();
|
|
|
|
#if RHI_RAYTRACING
|
|
View.RayTracingCullingParameters.Init(View);
|
|
Flags.bCullInRayTracing = View.RayTracingCullingParameters.CullInRayTracing > 0;
|
|
#endif
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_PrimitiveCull);
|
|
|
|
FMemMark MemStackMark(FMemStack::Get());
|
|
|
|
FSceneBitArray VisibleNodes;
|
|
|
|
if (bShouldVisibilityCull && Flags.bUseVisibilityOctree)
|
|
{
|
|
CullOctree(Scene, View, Flags, VisibleNodes);
|
|
}
|
|
|
|
//Primitives per ParallelFor task
|
|
//Using async FrustumCull. Thanks Yager! See https://udn.unrealengine.com/questions/252385/performance-of-frustumcull.html
|
|
//Performance varies on total primitive count and tasks scheduled. Check the mentioned link above for some measurements.
|
|
//There have been some changes as compared to the code measured in the link
|
|
|
|
FThreadSafeCounter NumCulledPrimitives;
|
|
FSceneViewState* ViewState = (FSceneViewState*)View.State;
|
|
const bool bHLODActive = Scene->SceneLODHierarchy.IsActive();
|
|
const FHLODVisibilityState* const HLODState = bHLODActive && ViewState ? &ViewState->HLODVisibilityState : nullptr;
|
|
float MaxDrawDistanceScale = GetCachedScalabilityCVars().ViewDistanceScale * GetCachedScalabilityCVars().CalculateFieldOfViewDistanceScale(View.DesiredFOV);
|
|
|
|
const int32 BitArrayNum = View.PrimitiveVisibilityMap.Num();
|
|
const int32 BitArrayWords = FMath::DivideAndRoundUp(BitArrayNum, (int32)NumBitsPerDWORD);
|
|
const int32 NumTasks = FMath::DivideAndRoundUp(BitArrayWords, FrustumCullNumWordsPerTask);
|
|
|
|
ParallelFor(NumTasks,
|
|
[&NumCulledPrimitives, Scene, &View, MaxDrawDistanceScale, HLODState, &VisibleNodes, &Flags](int32 TaskIndex)
|
|
{
|
|
PrimitiveCullTask(NumCulledPrimitives, Scene, View, Flags, MaxDrawDistanceScale, HLODState, VisibleNodes, TaskIndex);
|
|
},
|
|
!FApp::ShouldUseThreadingForPerformance() || (Flags.bUseCustomCulling && !View.CustomVisibilityQuery->IsThreadsafe()) || CVarParallelInitViews.GetValueOnRenderThread() == 0 || !IsInActualRenderingThread()
|
|
);
|
|
|
|
|
|
return NumCulledPrimitives.GetValue();
|
|
}
|
|
|
|
/**
|
|
* Updated primitive fading states for the view.
|
|
*/
|
|
static void UpdatePrimitiveFading(const FScene* Scene, FViewInfo& View)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveFading);
|
|
|
|
FSceneViewState* ViewState = (FSceneViewState*)View.State;
|
|
|
|
if (ViewState)
|
|
{
|
|
uint32 PrevFrameNumber = ViewState->PrevFrameNumber;
|
|
float CurrentRealTime = View.Family->Time.GetRealTimeSeconds();
|
|
|
|
// First clear any stale fading states.
|
|
for (FPrimitiveFadingStateMap::TIterator It(ViewState->PrimitiveFadingStates); It; ++It)
|
|
{
|
|
FPrimitiveFadingState& FadingState = It.Value();
|
|
if (FadingState.FrameNumber != PrevFrameNumber ||
|
|
(IsValidRef(FadingState.UniformBuffer) && CurrentRealTime >= FadingState.EndTime))
|
|
{
|
|
It.RemoveCurrent();
|
|
}
|
|
}
|
|
|
|
// Should we allow fading transitions at all this frame? For frames where the camera moved
|
|
// a large distance or where we haven't rendered a view in awhile, it's best to disable
|
|
// fading so users don't see unexpected object transitions.
|
|
if (!GDisableLODFade && !View.bDisableDistanceBasedFadeTransitions)
|
|
{
|
|
// Do a pass over potentially fading primitives and update their states.
|
|
for (FSceneSetBitIterator BitIt(View.PotentiallyFadingPrimitiveMap); BitIt; ++BitIt)
|
|
{
|
|
bool bVisible = View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt);
|
|
FPrimitiveFadingState& FadingState = ViewState->PrimitiveFadingStates.FindOrAdd(Scene->PrimitiveComponentIds[BitIt.GetIndex()]);
|
|
UpdatePrimitiveFadingStateHelper(FadingState, View, bVisible);
|
|
FRHIUniformBuffer* UniformBuffer = FadingState.UniformBuffer;
|
|
if (UniformBuffer && !bVisible)
|
|
{
|
|
// If the primitive is fading out make sure it remains visible.
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = true;
|
|
|
|
#if RHI_RAYTRACING
|
|
// Cannot just assume the ray tracing visibility will be true, so a complete recalculation for its culling needs to happen
|
|
// This should be a very rare occurrence, so the hit is not worrisome.
|
|
// TODO: Could this be moved into the actual culling phase?
|
|
|
|
if (View.RayTracingCullingParameters.CullInRayTracing == 0 || !ShouldCullForRayTracing(Scene, View, BitIt.GetIndex()))
|
|
{
|
|
View.PrimitiveRayTracingVisibilityMap.AccessCorrespondingBit(BitIt) = true;
|
|
}
|
|
#endif //RHI_RAYTRACING
|
|
}
|
|
View.PrimitiveFadeUniformBuffers[BitIt.GetIndex()] = UniformBuffer;
|
|
View.PrimitiveFadeUniformBufferMap[BitIt.GetIndex()] = UniformBuffer != nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct FOcclusionBounds
|
|
{
|
|
FOcclusionBounds(FPrimitiveOcclusionHistory* InPrimitiveOcclusionHistory, const FVector& InBoundsOrigin, const FVector& InBoundsExtent, bool bInGroupedQuery)
|
|
: PrimitiveOcclusionHistory(InPrimitiveOcclusionHistory)
|
|
, BoundsOrigin(InBoundsOrigin)
|
|
, BoundsExtent(InBoundsExtent)
|
|
, bGroupedQuery(bInGroupedQuery)
|
|
{
|
|
}
|
|
FOcclusionBounds(FPrimitiveOcclusionHistoryKey InPrimitiveOcclusionHistoryKey, const FVector& InBoundsOrigin, const FVector& InBoundsExtent, uint32 InLastQuerySubmitFrame)
|
|
: PrimitiveOcclusionHistoryKey(InPrimitiveOcclusionHistoryKey)
|
|
, BoundsOrigin(InBoundsOrigin)
|
|
, BoundsExtent(InBoundsExtent)
|
|
, LastQuerySubmitFrame(InLastQuerySubmitFrame)
|
|
{
|
|
}
|
|
union
|
|
{
|
|
FPrimitiveOcclusionHistory* PrimitiveOcclusionHistory;
|
|
FPrimitiveOcclusionHistoryKey PrimitiveOcclusionHistoryKey;
|
|
};
|
|
|
|
FVector BoundsOrigin;
|
|
FVector BoundsExtent;
|
|
union
|
|
{
|
|
bool bGroupedQuery;
|
|
uint32 LastQuerySubmitFrame;
|
|
};
|
|
};
|
|
|
|
struct FHZBBound
|
|
{
|
|
FHZBBound(FPrimitiveOcclusionHistory* InTargetHistory, const FVector& InBoundsOrigin, const FVector& InBoundsExtent)
|
|
: TargetHistory(InTargetHistory)
|
|
, BoundsOrigin(InBoundsOrigin)
|
|
, BoundsExtent(InBoundsExtent)
|
|
{}
|
|
|
|
FPrimitiveOcclusionHistory* const TargetHistory;
|
|
const FVector BoundsOrigin;
|
|
const FVector BoundsExtent;
|
|
};
|
|
|
|
#define BALANCE_LOAD 1
|
|
#define QUERY_SANITY_CHECK 0
|
|
|
|
struct FVisForPrimParams
|
|
{
|
|
FVisForPrimParams(){}
|
|
|
|
FVisForPrimParams(const FScene* InScene,
|
|
FViewInfo* InView,
|
|
FViewElementPDI* InOcclusionPDI,
|
|
int32 InStartIndex,
|
|
int32 InNumToProcess,
|
|
bool bInSubmitQueries,
|
|
bool bInHZBOcclusion,
|
|
TArray<FOcclusionBounds>& OutQueriesToRun,
|
|
TArray<bool>& OutSubIsOccluded)
|
|
: Scene(InScene)
|
|
, View(InView)
|
|
, OcclusionPDI(InOcclusionPDI)
|
|
, StartIndex(InStartIndex)
|
|
, NumToProcess(InNumToProcess)
|
|
, bSubmitQueries(bInSubmitQueries)
|
|
, bHZBOcclusion(bInHZBOcclusion)
|
|
, QueriesToAdd(&OutQueriesToRun)
|
|
, SubIsOccluded(&OutSubIsOccluded)
|
|
{
|
|
OutQueriesToRun.Reset();
|
|
OutSubIsOccluded.Reset();
|
|
}
|
|
|
|
void Init( const FScene* InScene,
|
|
FViewInfo* InView,
|
|
int32 InStartIndex,
|
|
int32 InNumToProcess,
|
|
bool bInSubmitQueries,
|
|
bool bInHZBOcclusion,
|
|
TArray<FPrimitiveOcclusionHistory>& OutOcclusionHistory,
|
|
TArray<FPrimitiveOcclusionHistory*>& OutQueriesToRelease,
|
|
TArray<FHZBBound>& OutHZBBounds,
|
|
TArray<FOcclusionBounds>& OutQueriesToRun,
|
|
TArray<bool>& OutSubIsOccluded)
|
|
{
|
|
OutOcclusionHistory.Reset();
|
|
OutQueriesToRelease.Reset();
|
|
OutHZBBounds.Reset();
|
|
OutQueriesToRun.Reset();
|
|
OutSubIsOccluded.Reset();
|
|
|
|
Scene = InScene;
|
|
View = InView;
|
|
StartIndex = InStartIndex;
|
|
NumToProcess = InNumToProcess;
|
|
bSubmitQueries = bInSubmitQueries;
|
|
bHZBOcclusion = bInHZBOcclusion;
|
|
InsertPrimitiveOcclusionHistory = &OutOcclusionHistory;
|
|
QueriesToRelease = &OutQueriesToRelease;
|
|
HZBBoundsToAdd = &OutHZBBounds;
|
|
QueriesToAdd = &OutQueriesToRun;
|
|
SubIsOccluded = &OutSubIsOccluded;
|
|
}
|
|
|
|
const FScene* Scene{};
|
|
FViewInfo* View{};
|
|
FViewElementPDI* OcclusionPDI{};
|
|
int32 StartIndex{};
|
|
int32 NumToProcess{};
|
|
bool bSubmitQueries{};
|
|
bool bHZBOcclusion{};
|
|
|
|
// Whether the entries written into the history need to be read using a scan search (see FPrimitiveOcclusionHistory::bNeedsScanOnRead)
|
|
bool bNeedsScanOnRead{};
|
|
|
|
//occlusion history to insert into. In parallel these will be all merged back into the view's history on the main thread.
|
|
//use TChunkedArray so pointers to the new FPrimitiveOcclusionHistory's won't change if the array grows.
|
|
TArray<FPrimitiveOcclusionHistory>* InsertPrimitiveOcclusionHistory{};
|
|
TArray<FPrimitiveOcclusionHistory*>* QueriesToRelease{};
|
|
TArray<FHZBBound>* HZBBoundsToAdd{};
|
|
TArray<FOcclusionBounds>* QueriesToAdd{};
|
|
int32 NumOccludedPrims{};
|
|
TArray<bool>* SubIsOccluded{};
|
|
};
|
|
|
|
//This function is shared between the single and multi-threaded versions. Modifications to any primitives indexed by BitIt should be ok
|
|
//since only one of the task threads will ever reference it. However, any modifications to shared state like the ViewState must be buffered
|
|
//to be recombined later.
|
|
template<bool bSingleThreaded>
|
|
static void FetchVisibilityForPrimitives_Range(FVisForPrimParams& Params, FGlobalDynamicVertexBuffer* DynamicVertexBufferIfSingleThreaded)
|
|
{
|
|
SCOPED_NAMED_EVENT(FetchVisibilityForPrimitives_Range, FColor::Magenta);
|
|
int32 NumOccludedPrimitives = 0;
|
|
|
|
const FScene* Scene = Params.Scene;
|
|
FViewInfo& View = *Params.View;
|
|
FViewElementPDI* OcclusionPDI = Params.OcclusionPDI;
|
|
const int32 StartIndex = Params.StartIndex;
|
|
const int32 NumToProcess = Params.NumToProcess;
|
|
const bool bSubmitQueries = Params.bSubmitQueries;
|
|
const bool bHZBOcclusion = Params.bHZBOcclusion;
|
|
|
|
const float PrimitiveProbablyVisibleTime = GEngine->PrimitiveProbablyVisibleTime;
|
|
|
|
FSceneViewState* ViewState = (FSceneViewState*)View.State;
|
|
const int32 NumBufferedFrames = FOcclusionQueryHelpers::GetNumBufferedFrames(Scene->GetFeatureLevel());
|
|
bool bClearQueries = !View.Family->EngineShowFlags.HitProxies;
|
|
const float CurrentRealTime = View.Family->Time.GetRealTimeSeconds();
|
|
uint32 OcclusionFrameCounter = ViewState->OcclusionFrameCounter;
|
|
FHZBOcclusionTester& HZBOcclusionTests = ViewState->HZBOcclusionTests;
|
|
|
|
int32 ReadBackLagTolerance = NumBufferedFrames;
|
|
|
|
const bool bIsStereoView = IStereoRendering::IsStereoEyeView(View);
|
|
const bool bUseRoundRobinOcclusion = bIsStereoView && !View.bIsSceneCapture && View.ViewState->IsRoundRobinEnabled();
|
|
if (bUseRoundRobinOcclusion)
|
|
{
|
|
// We don't allow clearing of a history entry if we do not also submit an occlusion query to replace the deleted one
|
|
// as we want to keep the history as full as possible
|
|
bClearQueries &= bSubmitQueries;
|
|
|
|
// However, if this frame happens to be the first frame, then we clear anyway since in the first frame we should not be
|
|
// reading past queries
|
|
bClearQueries |= View.bIgnoreExistingQueries;
|
|
|
|
// Round-robin occlusion culling involves reading frames that could be twice as stale as without round-robin
|
|
ReadBackLagTolerance = NumBufferedFrames * 2;
|
|
}
|
|
// Round robin occlusion culling can make holes in the occlusion history which would require scanning the history when reading
|
|
Params.bNeedsScanOnRead = bUseRoundRobinOcclusion;
|
|
|
|
TSet<FPrimitiveOcclusionHistory, FPrimitiveOcclusionHistoryKeyFuncs>& ViewPrimitiveOcclusionHistory = ViewState->PrimitiveOcclusionHistorySet;
|
|
TArray<FPrimitiveOcclusionHistory>* InsertPrimitiveOcclusionHistory = Params.InsertPrimitiveOcclusionHistory;
|
|
TArray<FPrimitiveOcclusionHistory*>* QueriesToRelease = Params.QueriesToRelease;
|
|
TArray<FHZBBound>* HZBBoundsToAdd = Params.HZBBoundsToAdd;
|
|
TArray<FOcclusionBounds>* QueriesToAdd = Params.QueriesToAdd;
|
|
|
|
const bool bNewlyConsideredBBoxExpandActive = GExpandNewlyOcclusionTestedBBoxesAmount > 0.0f && GFramesToExpandNewlyOcclusionTestedBBoxes > 0 && GFramesNotOcclusionTestedToExpandBBoxes > 0;
|
|
const float NeverOcclusionTestDistanceSquared = GNeverOcclusionTestDistance * GNeverOcclusionTestDistance;
|
|
const FVector ViewOrigin = View.ViewMatrices.GetViewOrigin();
|
|
|
|
const int32 ReserveAmount = NumToProcess;
|
|
int32 NumQueriesToReserve = NumToProcess;
|
|
if (!bSingleThreaded)
|
|
{
|
|
check(InsertPrimitiveOcclusionHistory);
|
|
check(QueriesToRelease);
|
|
check(HZBBoundsToAdd);
|
|
check(QueriesToAdd);
|
|
|
|
// We need to calculuate the actual number of queries to reserve since the pointers to InsertPrimitiveOcclusionHistory need to be preserved.
|
|
if (GAllowSubPrimitiveQueries && !View.bDisableQuerySubmissions)
|
|
{
|
|
NumQueriesToReserve = 0;
|
|
int32 NumProcessed = 0;
|
|
#if BALANCE_LOAD
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, StartIndex); BitIt && (NumProcessed < NumToProcess); ++BitIt, ++NumProcessed)
|
|
#else
|
|
for (TBitArray<SceneRenderingBitArrayAllocator>::FIterator BitIt(View.PrimitiveVisibilityMap, StartIndex); BitIt && (NumProcessed < NumToProcess); ++BitIt, ++NumProcessed)
|
|
#endif
|
|
{
|
|
#if !BALANCE_LOAD
|
|
if (!View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt))
|
|
{
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
int32 Index = BitIt.GetIndex();
|
|
|
|
const uint8 OcclusionFlags = Scene->PrimitiveOcclusionFlags[Index];
|
|
|
|
if ((OcclusionFlags & EOcclusionFlags::CanBeOccluded) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((OcclusionFlags & EOcclusionFlags::HasSubprimitiveQueries) != 0)
|
|
{
|
|
NumQueriesToReserve += Scene->Primitives[Index]->Proxy->GetOcclusionQueries(&View)->Num();
|
|
}
|
|
else
|
|
{
|
|
NumQueriesToReserve++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//avoid doing reallocs as much as possible. Unlikely to make an entry per processed element.
|
|
InsertPrimitiveOcclusionHistory->Reserve(NumQueriesToReserve);
|
|
QueriesToRelease->Reserve(ReserveAmount);
|
|
HZBBoundsToAdd->Reserve(ReserveAmount);
|
|
QueriesToAdd->Reserve(ReserveAmount);
|
|
}
|
|
|
|
int32 NumProcessed = 0;
|
|
int32 NumTotalPrims = View.PrimitiveVisibilityMap.Num();
|
|
int32 NumTotalDefUnoccluded = View.PrimitiveDefinitelyUnoccludedMap.Num();
|
|
|
|
{
|
|
// If we're going to stall the RHI thread for one query, we should stall it for all of them.
|
|
// !(View.bIgnoreExistingQueries || bHZBOcclusion) is the code path that calls GetQueryForReading.
|
|
const bool bShouldStallRHIThread = bSingleThreaded && GOcclusionSingleRHIThreadStall && !GSupportsParallelOcclusionQueries && IsInRenderingThread() && !(View.bIgnoreExistingQueries || bHZBOcclusion);
|
|
FScopedRHIThreadStaller StallRHIThread(FRHICommandListExecutor::GetImmediateCommandList(), bShouldStallRHIThread);
|
|
|
|
SCOPED_NAMED_EVENT_F(TEXT("forEach over %d entries"), FColor::Magenta, NumToProcess);
|
|
|
|
//if we are load balanced then we iterate only the set bits, and the ranges have been pre-selected to evenly distribute set bits among the tasks with no overlaps.
|
|
//if not, then the entire array is evenly divided by range.
|
|
#if BALANCE_LOAD
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, StartIndex); BitIt && (NumProcessed < NumToProcess); ++BitIt, ++NumProcessed)
|
|
#else
|
|
for (TBitArray<SceneRenderingBitArrayAllocator>::FIterator BitIt(View.PrimitiveVisibilityMap, StartIndex); BitIt && (NumProcessed < NumToProcess); ++BitIt, ++NumProcessed)
|
|
#endif
|
|
{
|
|
#if !BALANCE_LOAD
|
|
if (!View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt))
|
|
{
|
|
continue;
|
|
}
|
|
#endif
|
|
int32 Index = BitIt.GetIndex();
|
|
|
|
const uint8 OcclusionFlags = Scene->PrimitiveOcclusionFlags[Index];
|
|
|
|
if ((OcclusionFlags & EOcclusionFlags::CanBeOccluded) == 0)
|
|
{
|
|
View.PrimitiveDefinitelyUnoccludedMap.AccessCorrespondingBit(BitIt) = true;
|
|
continue;
|
|
}
|
|
|
|
//we can't allow the prim history insertion array to realloc or it will invalidate pointers in the other output arrays.
|
|
const bool bCanAllocPrimHistory = bSingleThreaded || InsertPrimitiveOcclusionHistory->Num() < InsertPrimitiveOcclusionHistory->Max();
|
|
|
|
#if WITH_EDITOR
|
|
bool bCanBeOccluded = true;
|
|
if (GIsEditor)
|
|
{
|
|
if (Scene->PrimitivesSelected[Index])
|
|
{
|
|
// to render occluded outline for selected objects
|
|
bCanBeOccluded = false;
|
|
}
|
|
}
|
|
#else
|
|
constexpr bool bCanBeOccluded = true;
|
|
#endif
|
|
|
|
int32 NumSubQueries = 1;
|
|
bool bSubQueries = false;
|
|
const TArray<FBoxSphereBounds>* SubBounds = nullptr;
|
|
|
|
check(Params.SubIsOccluded);
|
|
TArray<bool>& SubIsOccluded = *Params.SubIsOccluded;
|
|
int32 SubIsOccludedStart = SubIsOccluded.Num();
|
|
if ((OcclusionFlags & EOcclusionFlags::HasSubprimitiveQueries) && GAllowSubPrimitiveQueries && !View.bDisableQuerySubmissions)
|
|
{
|
|
FPrimitiveSceneProxy* Proxy = Scene->Primitives[Index]->Proxy;
|
|
SubBounds = Proxy->GetOcclusionQueries(&View);
|
|
NumSubQueries = SubBounds->Num();
|
|
bSubQueries = true;
|
|
if (!NumSubQueries)
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
continue;
|
|
}
|
|
SubIsOccluded.Reserve(NumSubQueries);
|
|
}
|
|
|
|
bool bAllSubOcclusionStateIsDefinite = true;
|
|
bool bAllSubOccluded = true;
|
|
FPrimitiveComponentId PrimitiveId = Scene->PrimitiveComponentIds[Index];
|
|
|
|
for (int32 SubQuery = 0; SubQuery < NumSubQueries; SubQuery++)
|
|
{
|
|
FPrimitiveOcclusionHistory* PrimitiveOcclusionHistory = ViewPrimitiveOcclusionHistory.Find(FPrimitiveOcclusionHistoryKey(PrimitiveId, SubQuery));
|
|
|
|
bool bIsOccluded = false;
|
|
bool bOcclusionStateIsDefinite = false;
|
|
|
|
if (!PrimitiveOcclusionHistory)
|
|
{
|
|
// If the primitive doesn't have an occlusion history yet, create it.
|
|
if (bSingleThreaded)
|
|
{
|
|
// In singlethreaded mode we can safely modify the view's history directly.
|
|
PrimitiveOcclusionHistory = &ViewPrimitiveOcclusionHistory[
|
|
ViewPrimitiveOcclusionHistory.Add(FPrimitiveOcclusionHistory(PrimitiveId, SubQuery))
|
|
];
|
|
}
|
|
else if (bCanAllocPrimHistory)
|
|
{
|
|
// In multithreaded mode we have to buffer the new histories and add them to the view during a post-combine
|
|
PrimitiveOcclusionHistory = &(*InsertPrimitiveOcclusionHistory)[
|
|
InsertPrimitiveOcclusionHistory->Add(FPrimitiveOcclusionHistory(PrimitiveId, SubQuery))
|
|
];
|
|
}
|
|
|
|
// If the primitive hasn't been visible recently enough to have a history, treat it as unoccluded this frame so it will be rendered as an occluder and its true occlusion state can be determined.
|
|
// already set bIsOccluded = false;
|
|
|
|
// Flag the primitive's occlusion state as indefinite, which will force it to be queried this frame.
|
|
// The exception is if the primitive isn't occludable, in which case we know that it's definitely unoccluded.
|
|
bOcclusionStateIsDefinite = !bCanBeOccluded;
|
|
}
|
|
else
|
|
{
|
|
if (View.bIgnoreExistingQueries)
|
|
{
|
|
// If the view is ignoring occlusion queries, the primitive is definitely unoccluded.
|
|
// already set bIsOccluded = false;
|
|
bOcclusionStateIsDefinite = View.bDisableQuerySubmissions;
|
|
}
|
|
else if (bCanBeOccluded)
|
|
{
|
|
if (bHZBOcclusion)
|
|
{
|
|
if (HZBOcclusionTests.IsValidFrame(PrimitiveOcclusionHistory->LastTestFrameNumber))
|
|
{
|
|
bIsOccluded = !HZBOcclusionTests.IsVisible(PrimitiveOcclusionHistory->HZBTestIndex);
|
|
bOcclusionStateIsDefinite = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Read the occlusion query results.
|
|
uint64 NumSamples = 0;
|
|
bool bGrouped = false;
|
|
FRHIRenderQuery* PastQuery = PrimitiveOcclusionHistory->GetQueryForReading(OcclusionFrameCounter, NumBufferedFrames, ReadBackLagTolerance, bGrouped);
|
|
if (PastQuery)
|
|
{
|
|
//int32 RefCount = PastQuery.GetReference()->GetRefCount();
|
|
// NOTE: RHIGetOcclusionQueryResult should never fail when using a blocking call, rendering artifacts may show up.
|
|
//if (RHICmdList.GetRenderQueryResult(PastQuery, NumSamples, true))
|
|
if (GDynamicRHI->RHIGetRenderQueryResult(PastQuery, NumSamples, true))
|
|
{
|
|
// we render occlusion without MSAA
|
|
uint32 NumPixels = (uint32)NumSamples;
|
|
|
|
// The primitive is occluded if none of its bounding box's pixels were visible in the previous frame's occlusion query.
|
|
bIsOccluded = (NumPixels == 0);
|
|
|
|
|
|
if (!bIsOccluded)
|
|
{
|
|
checkSlow(View.OneOverNumPossiblePixels > 0.0f);
|
|
PrimitiveOcclusionHistory->LastPixelsPercentage = NumPixels * View.OneOverNumPossiblePixels;
|
|
}
|
|
else
|
|
{
|
|
PrimitiveOcclusionHistory->LastPixelsPercentage = 0.0f;
|
|
}
|
|
|
|
|
|
// Flag the primitive's occlusion state as definite if it wasn't grouped.
|
|
bOcclusionStateIsDefinite = !bGrouped;
|
|
}
|
|
else
|
|
{
|
|
// If the occlusion query failed, treat the primitive as visible.
|
|
// already set bIsOccluded = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (NumBufferedFrames > 1 || GRHIMaximumReccommendedOustandingOcclusionQueries < MAX_int32)
|
|
{
|
|
// If there's no occlusion query for the primitive, assume it is whatever it was last frame
|
|
bIsOccluded = PrimitiveOcclusionHistory->WasOccludedLastFrame;
|
|
bOcclusionStateIsDefinite = PrimitiveOcclusionHistory->OcclusionStateWasDefiniteLastFrame;
|
|
}
|
|
else
|
|
{
|
|
// If there's no occlusion query for the primitive, set it's visibility state to whether it has been unoccluded recently.
|
|
bIsOccluded = (PrimitiveOcclusionHistory->LastProvenVisibleTime + GEngine->PrimitiveProbablyVisibleTime < CurrentRealTime);
|
|
// the state was definite last frame, otherwise we would have ran a query
|
|
bOcclusionStateIsDefinite = true;
|
|
}
|
|
if (bIsOccluded)
|
|
{
|
|
PrimitiveOcclusionHistory->LastPixelsPercentage = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
PrimitiveOcclusionHistory->LastPixelsPercentage = GEngine->MaxOcclusionPixelsFraction;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GVisualizeOccludedPrimitives && OcclusionPDI && bIsOccluded)
|
|
{
|
|
const FBoxSphereBounds& Bounds = bSubQueries ? (*SubBounds)[SubQuery] : Scene->PrimitiveOcclusionBounds[Index];
|
|
DrawWireBox(OcclusionPDI, Bounds.GetBox(), FColor(50, 255, 50), SDPG_Foreground);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Primitives that aren't occludable are considered definitely unoccluded.
|
|
// already set bIsOccluded = false;
|
|
bOcclusionStateIsDefinite = true;
|
|
}
|
|
|
|
if (bClearQueries)
|
|
{
|
|
if (bSingleThreaded)
|
|
{
|
|
PrimitiveOcclusionHistory->ReleaseQuery(OcclusionFrameCounter, NumBufferedFrames);
|
|
}
|
|
else
|
|
{
|
|
if (PrimitiveOcclusionHistory->GetQueryForEviction(OcclusionFrameCounter, NumBufferedFrames))
|
|
{
|
|
QueriesToRelease->Add(PrimitiveOcclusionHistory);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (PrimitiveOcclusionHistory)
|
|
{
|
|
if (bSubmitQueries && bCanBeOccluded)
|
|
{
|
|
bool bSkipNewlyConsidered = false;
|
|
|
|
if (bNewlyConsideredBBoxExpandActive)
|
|
{
|
|
if (!PrimitiveOcclusionHistory->BecameEligibleForQueryCooldown && OcclusionFrameCounter - PrimitiveOcclusionHistory->LastConsideredFrameNumber > uint32(GFramesNotOcclusionTestedToExpandBBoxes))
|
|
{
|
|
PrimitiveOcclusionHistory->BecameEligibleForQueryCooldown = GFramesToExpandNewlyOcclusionTestedBBoxes;
|
|
}
|
|
|
|
bSkipNewlyConsidered = !!PrimitiveOcclusionHistory->BecameEligibleForQueryCooldown;
|
|
|
|
if (bSkipNewlyConsidered)
|
|
{
|
|
PrimitiveOcclusionHistory->BecameEligibleForQueryCooldown--;
|
|
}
|
|
}
|
|
|
|
|
|
bool bAllowBoundsTest;
|
|
const FBoxSphereBounds OcclusionBounds = (bSubQueries ? (*SubBounds)[SubQuery] : Scene->PrimitiveOcclusionBounds[Index]).ExpandBy(GExpandAllTestedBBoxesAmount + (bSkipNewlyConsidered ? GExpandNewlyOcclusionTestedBBoxesAmount : 0.0));
|
|
if (FVector::DistSquared(ViewOrigin, OcclusionBounds.Origin) < NeverOcclusionTestDistanceSquared)
|
|
{
|
|
bAllowBoundsTest = false;
|
|
}
|
|
else if (View.bHasNearClippingPlane)
|
|
{
|
|
bAllowBoundsTest = View.NearClippingPlane.PlaneDot(OcclusionBounds.Origin) <
|
|
-(FVector::BoxPushOut(View.NearClippingPlane, OcclusionBounds.BoxExtent));
|
|
|
|
}
|
|
else if (!View.IsPerspectiveProjection())
|
|
{
|
|
// Transform parallel near plane
|
|
static_assert((int32)ERHIZBuffer::IsInverted != 0, "Check equation for culling!");
|
|
bAllowBoundsTest = View.WorldToScreen(OcclusionBounds.Origin).Z - View.ViewMatrices.GetProjectionMatrix().M[2][2] * OcclusionBounds.SphereRadius < 1;
|
|
}
|
|
else
|
|
{
|
|
bAllowBoundsTest = OcclusionBounds.SphereRadius < HALF_WORLD_MAX;
|
|
}
|
|
|
|
if (bAllowBoundsTest)
|
|
{
|
|
PrimitiveOcclusionHistory->LastTestFrameNumber = OcclusionFrameCounter;
|
|
if (bHZBOcclusion)
|
|
{
|
|
// Always run
|
|
if (bSingleThreaded)
|
|
{
|
|
PrimitiveOcclusionHistory->HZBTestIndex = HZBOcclusionTests.AddBounds(OcclusionBounds.Origin, OcclusionBounds.BoxExtent);
|
|
}
|
|
else
|
|
{
|
|
HZBBoundsToAdd->Emplace(PrimitiveOcclusionHistory, OcclusionBounds.Origin, OcclusionBounds.BoxExtent);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// decide if a query should be run this frame
|
|
bool bRunQuery, bGroupedQuery;
|
|
|
|
if (!bSubQueries && // sub queries are never grouped, we assume the custom code knows what it is doing and will group internally if it wants
|
|
(OcclusionFlags & EOcclusionFlags::AllowApproximateOcclusion))
|
|
{
|
|
if (bIsOccluded)
|
|
{
|
|
// Primitives that were occluded the previous frame use grouped queries.
|
|
bGroupedQuery = true;
|
|
bRunQuery = true;
|
|
}
|
|
else if (bOcclusionStateIsDefinite)
|
|
{
|
|
bGroupedQuery = false;
|
|
float Rnd = GOcclusionRandomStream.GetFraction();
|
|
if (GRHISupportsExactOcclusionQueries)
|
|
{
|
|
float FractionMultiplier = FMath::Max(PrimitiveOcclusionHistory->LastPixelsPercentage / GEngine->MaxOcclusionPixelsFraction, 1.0f);
|
|
bRunQuery = (FractionMultiplier * Rnd) < GEngine->MaxOcclusionPixelsFraction;
|
|
}
|
|
else
|
|
{
|
|
bRunQuery = CurrentRealTime - PrimitiveOcclusionHistory->LastProvenVisibleTime > PrimitiveProbablyVisibleTime * (0.5f * 0.25f * Rnd);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bGroupedQuery = false;
|
|
bRunQuery = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Primitives that need precise occlusion results use individual queries.
|
|
bGroupedQuery = false;
|
|
bRunQuery = true;
|
|
}
|
|
|
|
if (bRunQuery)
|
|
{
|
|
const FVector BoundOrigin = OcclusionBounds.Origin + View.ViewMatrices.GetPreViewTranslation();
|
|
const FVector BoundExtent = OcclusionBounds.BoxExtent;
|
|
|
|
if (bSingleThreaded)
|
|
{
|
|
checkSlow(DynamicVertexBufferIfSingleThreaded);
|
|
|
|
if (GRHIMaximumReccommendedOustandingOcclusionQueries < MAX_int32 && !bGroupedQuery)
|
|
{
|
|
QueriesToAdd->Emplace(FPrimitiveOcclusionHistoryKey(PrimitiveId, SubQuery), BoundOrigin, BoundExtent, PrimitiveOcclusionHistory->LastQuerySubmitFrame());
|
|
}
|
|
else
|
|
{
|
|
PrimitiveOcclusionHistory->SetCurrentQuery(OcclusionFrameCounter,
|
|
bGroupedQuery ?
|
|
View.GroupedOcclusionQueries.BatchPrimitive(BoundOrigin, BoundExtent, *DynamicVertexBufferIfSingleThreaded) :
|
|
View.IndividualOcclusionQueries.BatchPrimitive(BoundOrigin, BoundExtent, *DynamicVertexBufferIfSingleThreaded),
|
|
NumBufferedFrames,
|
|
bGroupedQuery,
|
|
Params.bNeedsScanOnRead
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QueriesToAdd->Emplace(PrimitiveOcclusionHistory, BoundOrigin, BoundExtent, bGroupedQuery);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the primitive's bounding box intersects the near clipping plane, treat it as definitely unoccluded.
|
|
bIsOccluded = false;
|
|
bOcclusionStateIsDefinite = true;
|
|
}
|
|
}
|
|
// Set the primitive's considered time to keep its occlusion history from being trimmed.
|
|
PrimitiveOcclusionHistory->LastConsideredTime = CurrentRealTime;
|
|
if (!bIsOccluded && bOcclusionStateIsDefinite)
|
|
{
|
|
PrimitiveOcclusionHistory->LastProvenVisibleTime = CurrentRealTime;
|
|
}
|
|
PrimitiveOcclusionHistory->LastConsideredFrameNumber = OcclusionFrameCounter;
|
|
PrimitiveOcclusionHistory->WasOccludedLastFrame = bIsOccluded;
|
|
PrimitiveOcclusionHistory->OcclusionStateWasDefiniteLastFrame = bOcclusionStateIsDefinite;
|
|
}
|
|
|
|
if (bSubQueries)
|
|
{
|
|
SubIsOccluded.Add(bIsOccluded);
|
|
if (!bIsOccluded)
|
|
{
|
|
bAllSubOccluded = false;
|
|
}
|
|
if (bIsOccluded || !bOcclusionStateIsDefinite)
|
|
{
|
|
bAllSubOcclusionStateIsDefinite = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
if (bIsOccluded)
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
STAT(NumOccludedPrimitives++);
|
|
}
|
|
else if (bOcclusionStateIsDefinite)
|
|
{
|
|
View.PrimitiveDefinitelyUnoccludedMap.AccessCorrespondingBit(BitIt) = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bSubQueries)
|
|
{
|
|
if (SubIsOccluded.Num() > 0)
|
|
{
|
|
FPrimitiveSceneProxy* Proxy = Scene->Primitives[Index]->Proxy;
|
|
Proxy->AcceptOcclusionResults(&View, &SubIsOccluded, SubIsOccludedStart, SubIsOccluded.Num() - SubIsOccludedStart);
|
|
}
|
|
|
|
if (bAllSubOccluded)
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
STAT(NumOccludedPrimitives++);
|
|
}
|
|
else if (bAllSubOcclusionStateIsDefinite)
|
|
{
|
|
View.PrimitiveDefinitelyUnoccludedMap.AccessCorrespondingBit(BitIt) = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
check(NumTotalDefUnoccluded == View.PrimitiveDefinitelyUnoccludedMap.Num());
|
|
check(NumTotalPrims == View.PrimitiveVisibilityMap.Num());
|
|
check(!InsertPrimitiveOcclusionHistory || InsertPrimitiveOcclusionHistory->Num() <= NumQueriesToReserve);
|
|
Params.NumOccludedPrims = NumOccludedPrimitives;
|
|
}
|
|
|
|
static int32 FetchVisibilityForPrimitives(const FScene* Scene, FViewInfo& View, const bool bSubmitQueries, const bool bHZBOcclusion, FGlobalDynamicVertexBuffer& DynamicVertexBuffer)
|
|
{
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(FetchVisibilityForPrimitives);
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FetchVisibilityForPrimitives);
|
|
FSceneViewState* ViewState = (FSceneViewState*)View.State;
|
|
SCOPED_NAMED_EVENT(FetchVisibilityForPrimitives, FColor::Magenta);
|
|
|
|
static int32 SubIsOccludedArrayIndex = 0;
|
|
SubIsOccludedArrayIndex = 1 - SubIsOccludedArrayIndex;
|
|
|
|
const int32 NumBufferedFrames = FOcclusionQueryHelpers::GetNumBufferedFrames(Scene->GetFeatureLevel());
|
|
uint32 OcclusionFrameCounter = ViewState->OcclusionFrameCounter;
|
|
TSet<FPrimitiveOcclusionHistory, FPrimitiveOcclusionHistoryKeyFuncs>& ViewPrimitiveOcclusionHistory = ViewState->PrimitiveOcclusionHistorySet;
|
|
|
|
if (GOcclusionCullParallelPrimFetch && GSupportsParallelOcclusionQueries)
|
|
{
|
|
SCOPED_NAMED_EVENT(FetchVisibilityParallel, FColor::Magenta);
|
|
constexpr int32 MaxNumCullTasks = 8;
|
|
constexpr int32 ActualNumCullTasks = 8;
|
|
constexpr int32 NumOutputArrays = MaxNumCullTasks;
|
|
|
|
//params for each task
|
|
FVisForPrimParams Params[NumOutputArrays];
|
|
|
|
//output arrays for each task
|
|
TArray<FPrimitiveOcclusionHistory> OutputOcclusionHistory[NumOutputArrays];
|
|
TArray<FPrimitiveOcclusionHistory*> OutQueriesToRelease[NumOutputArrays];
|
|
TArray<FHZBBound> OutHZBBounds[NumOutputArrays];
|
|
TArray<FOcclusionBounds> OutQueriesToRun[NumOutputArrays];
|
|
|
|
static TArray<bool> FrameSubIsOccluded[NumOutputArrays][FSceneView::NumBufferedSubIsOccludedArrays];
|
|
|
|
//optionally balance the tasks by how the visible primitives are distributed in the array rather than just breaking up the array by range.
|
|
//should make the tasks more equal length.
|
|
#if BALANCE_LOAD
|
|
int32 StartIndices[NumOutputArrays] = { 0 };
|
|
int32 ProcessRange[NumOutputArrays] = { 0 };
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FetchVisibilityForPrimitivesPreProcess);
|
|
int32 NumBitsSet = 0;
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt, ++NumBitsSet)
|
|
{
|
|
}
|
|
|
|
int32 BitsPerTask = NumBitsSet / ActualNumCullTasks;
|
|
int32 NumBitsForRange = 0;
|
|
int32 CurrentStartIndex = 0;
|
|
int32 RangeToSet = 0;
|
|
|
|
//accumulate set bits for each task until we reach the target, then set the start/end and move on.
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt && RangeToSet < (ActualNumCullTasks - 1); ++BitIt)
|
|
{
|
|
++NumBitsForRange;
|
|
if (NumBitsForRange == BitsPerTask)
|
|
{
|
|
StartIndices[RangeToSet] = CurrentStartIndex;
|
|
ProcessRange[RangeToSet] = NumBitsForRange;
|
|
|
|
++RangeToSet;
|
|
NumBitsForRange = 0;
|
|
CurrentStartIndex = BitIt.GetIndex() + 1;
|
|
}
|
|
}
|
|
|
|
//final range is the rest of the set bits, no matter how many there are.
|
|
StartIndices[ActualNumCullTasks - 1] = CurrentStartIndex;
|
|
ProcessRange[ActualNumCullTasks - 1] = NumBitsSet - (BitsPerTask * 3);
|
|
}
|
|
#endif
|
|
|
|
const int32 NumPrims = View.PrimitiveVisibilityMap.Num();
|
|
const int32 NumPerTask = NumPrims / ActualNumCullTasks;
|
|
int32 StartIndex = 0;
|
|
|
|
int32 NumTasks = 0;
|
|
for (int32 i = 0; i < ActualNumCullTasks && (StartIndex < NumPrims); ++i, ++NumTasks)
|
|
{
|
|
const int32 NumToProcess = (i == (ActualNumCullTasks - 1)) ? (NumPrims - StartIndex) : NumPerTask;
|
|
|
|
Params[i].Init(
|
|
Scene,
|
|
&View,
|
|
#if BALANCE_LOAD
|
|
StartIndices[i],
|
|
ProcessRange[i],
|
|
#else
|
|
StartIndex,
|
|
NumToProcess,
|
|
#endif
|
|
bSubmitQueries,
|
|
bHZBOcclusion,
|
|
OutputOcclusionHistory[i],
|
|
OutQueriesToRelease[i],
|
|
OutHZBBounds[i],
|
|
OutQueriesToRun[i],
|
|
FrameSubIsOccluded[i][SubIsOccludedArrayIndex]
|
|
);
|
|
|
|
StartIndex += NumToProcess;
|
|
}
|
|
|
|
ParallelFor(NumTasks,
|
|
[&Params](int32 Index)
|
|
{
|
|
FetchVisibilityForPrimitives_Range<false>(Params[Index], nullptr);
|
|
},
|
|
!(FApp::ShouldUseThreadingForPerformance() && CVarParallelInitViews.GetValueOnRenderThread() > 0 && IsInActualRenderingThread())
|
|
);
|
|
|
|
FHZBOcclusionTester& HZBOcclusionTests = ViewState->HZBOcclusionTests;
|
|
|
|
int32 NumOccludedPrims = 0;
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FetchVisibilityForPrimitivesCombine);
|
|
SCOPED_NAMED_EVENT(FetchVisibilityForPrimitivesCombine, FColor::Magenta);
|
|
|
|
#if QUERY_SANITY_CHECK
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FetchVisibilityForPrimitivesSanity);
|
|
TSet<int32> ReleaseQuerySet;
|
|
TSet<int32> RunQuerySet;
|
|
TSet<int32> MasterPrimsProcessed;
|
|
for (int32 i = 0; i < NumTasks; ++i)
|
|
{
|
|
bool bAlreadyIn = false;
|
|
for (FPrimitiveOcclusionHistory* History : OutQueriesToRelease[i])
|
|
{
|
|
ReleaseQuerySet.Add(History->PrimitiveId.PrimIDValue, &bAlreadyIn);
|
|
checkf(!bAlreadyIn, TEXT("Prim: %i double released query."), History->PrimitiveId.PrimIDValue);
|
|
}
|
|
|
|
for (const FOcclusionBounds& OcclusionBounds : OutQueriesToRun[i])
|
|
{
|
|
FPrimitiveOcclusionHistory* History = OcclusionBounds->PrimitiveOcclusionHistory;
|
|
RunQuerySet.Add(History->PrimitiveId.PrimIDValue, &bAlreadyIn);
|
|
checkf(!bAlreadyIn, TEXT("Prim: %i double run query."), History->PrimitiveId.PrimIDValue);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//Add/Release query ops use stored PrimitiveHistory pointers. We must do ALL of these from all tasks before adding any new PrimitiveHistories to the view.
|
|
//Adding new histories to the view could cause the array to resize which would invalidate all the stored output pointers for the other operations.
|
|
for (int32 i = 0; i < NumTasks; ++i)
|
|
{
|
|
//HZB output
|
|
for (const FHZBBound& HZBBounds : OutHZBBounds[i])
|
|
{
|
|
HZBBounds.TargetHistory->HZBTestIndex = HZBOcclusionTests.AddBounds(HZBBounds.BoundsOrigin, HZBBounds.BoundsExtent);
|
|
}
|
|
|
|
//Manual query release handling
|
|
for (FPrimitiveOcclusionHistory* History : OutQueriesToRelease[i])
|
|
{
|
|
History->ReleaseQuery(OcclusionFrameCounter, NumBufferedFrames);
|
|
}
|
|
|
|
//New query batching
|
|
for (const FOcclusionBounds& OcclusionBounds : OutQueriesToRun[i])
|
|
{
|
|
OcclusionBounds.PrimitiveOcclusionHistory->SetCurrentQuery(OcclusionFrameCounter,
|
|
OcclusionBounds.bGroupedQuery ?
|
|
View.GroupedOcclusionQueries.BatchPrimitive(OcclusionBounds.BoundsOrigin, OcclusionBounds.BoundsExtent, DynamicVertexBuffer) :
|
|
View.IndividualOcclusionQueries.BatchPrimitive(OcclusionBounds.BoundsOrigin, OcclusionBounds.BoundsExtent, DynamicVertexBuffer),
|
|
NumBufferedFrames,
|
|
OcclusionBounds.bGroupedQuery,
|
|
Params[i].bNeedsScanOnRead
|
|
);
|
|
}
|
|
}
|
|
|
|
//now add new primitive histories to the view. may resize the view's array.
|
|
for (int32 i = 0; i < NumTasks; ++i)
|
|
{
|
|
ViewPrimitiveOcclusionHistory.Append(MoveTemp(OutputOcclusionHistory[i]));
|
|
|
|
//accumulate occluded prims across tasks
|
|
NumOccludedPrims += Params[i].NumOccludedPrims;
|
|
}
|
|
}
|
|
|
|
return NumOccludedPrims;
|
|
}
|
|
else
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FetchVisibilityOther);
|
|
|
|
static TArray<FOcclusionBounds> PendingIndividualQueriesWhenOptimizing;
|
|
|
|
FViewElementPDI OcclusionPDI(&View, nullptr, nullptr);
|
|
int32 StartIndex = 0;
|
|
int32 NumToProcess = View.PrimitiveVisibilityMap.Num();
|
|
FVisForPrimParams Params(
|
|
Scene,
|
|
&View,
|
|
&OcclusionPDI,
|
|
StartIndex,
|
|
NumToProcess,
|
|
bSubmitQueries,
|
|
bHZBOcclusion,
|
|
PendingIndividualQueriesWhenOptimizing,
|
|
//SubIsOccluded stuff needs a frame's lifetime
|
|
View.FrameSubIsOccluded[SubIsOccludedArrayIndex]
|
|
);
|
|
|
|
FetchVisibilityForPrimitives_Range<true>(Params, &DynamicVertexBuffer);
|
|
|
|
int32 IndQueries = PendingIndividualQueriesWhenOptimizing.Num();
|
|
if (IndQueries)
|
|
{
|
|
static TArray<FOcclusionBounds*> PendingIndividualQueriesWhenOptimizingSorter;
|
|
PendingIndividualQueriesWhenOptimizingSorter.Reset();
|
|
|
|
int32 SoftMaxQueries = GRHIMaximumReccommendedOustandingOcclusionQueries / FMath::Min(NumBufferedFrames, 2); // extra RHIT frame does not count
|
|
int32 UsedQueries = View.GroupedOcclusionQueries.GetNumBatchOcclusionQueries();
|
|
|
|
int32 FirstQueryToDo = 0;
|
|
int32 QueriesToDo = IndQueries;
|
|
|
|
|
|
if (SoftMaxQueries < UsedQueries + IndQueries)
|
|
{
|
|
QueriesToDo = (IndQueries + 9) / 10; // we need to make progress, even if it means stalling and waiting for the GPU. At a minimum, we will do 10%
|
|
|
|
if (SoftMaxQueries > UsedQueries + QueriesToDo)
|
|
{
|
|
// we can do more than the minimum
|
|
QueriesToDo = SoftMaxQueries - UsedQueries;
|
|
}
|
|
}
|
|
if (QueriesToDo == IndQueries)
|
|
{
|
|
for (int32 Index = 0; Index < IndQueries; Index++)
|
|
{
|
|
FOcclusionBounds* RunQueriesIter = &PendingIndividualQueriesWhenOptimizing[Index];
|
|
FPrimitiveOcclusionHistory* PrimitiveOcclusionHistory = ViewPrimitiveOcclusionHistory.Find(RunQueriesIter->PrimitiveOcclusionHistoryKey);
|
|
|
|
PrimitiveOcclusionHistory->SetCurrentQuery(OcclusionFrameCounter,
|
|
View.IndividualOcclusionQueries.BatchPrimitive(RunQueriesIter->BoundsOrigin, RunQueriesIter->BoundsExtent, DynamicVertexBuffer),
|
|
NumBufferedFrames,
|
|
false,
|
|
Params.bNeedsScanOnRead
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
check(QueriesToDo < IndQueries);
|
|
PendingIndividualQueriesWhenOptimizingSorter.Reserve(PendingIndividualQueriesWhenOptimizing.Num());
|
|
for (int32 Index = 0; Index < IndQueries; Index++)
|
|
{
|
|
FOcclusionBounds* RunQueriesIter = &PendingIndividualQueriesWhenOptimizing[Index];
|
|
PendingIndividualQueriesWhenOptimizingSorter.Add(RunQueriesIter);
|
|
}
|
|
|
|
PendingIndividualQueriesWhenOptimizingSorter.Sort(
|
|
[](const FOcclusionBounds& A, const FOcclusionBounds& B)
|
|
{
|
|
return A.LastQuerySubmitFrame < B.LastQuerySubmitFrame;
|
|
}
|
|
);
|
|
for (int32 Index = 0; Index < QueriesToDo; Index++)
|
|
{
|
|
FOcclusionBounds* RunQueriesIter = PendingIndividualQueriesWhenOptimizingSorter[Index];
|
|
FPrimitiveOcclusionHistory* PrimitiveOcclusionHistory = ViewPrimitiveOcclusionHistory.Find(RunQueriesIter->PrimitiveOcclusionHistoryKey);
|
|
PrimitiveOcclusionHistory->SetCurrentQuery(OcclusionFrameCounter,
|
|
View.IndividualOcclusionQueries.BatchPrimitive(RunQueriesIter->BoundsOrigin, RunQueriesIter->BoundsExtent, DynamicVertexBuffer),
|
|
NumBufferedFrames,
|
|
false,
|
|
Params.bNeedsScanOnRead
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
// lets prevent this from staying too large for too long
|
|
if (PendingIndividualQueriesWhenOptimizing.GetSlack() > IndQueries * 4)
|
|
{
|
|
PendingIndividualQueriesWhenOptimizing.Empty();
|
|
PendingIndividualQueriesWhenOptimizingSorter.Empty();
|
|
}
|
|
else
|
|
{
|
|
PendingIndividualQueriesWhenOptimizing.Reset();
|
|
PendingIndividualQueriesWhenOptimizingSorter.Reset();
|
|
}
|
|
}
|
|
return Params.NumOccludedPrims;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cull occluded primitives in the view.
|
|
*/
|
|
static int32 OcclusionCull(FRHICommandListImmediate& RHICmdList, const FScene* Scene, FViewInfo& View, FGlobalDynamicVertexBuffer& DynamicVertexBuffer)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_OcclusionCull);
|
|
RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_OcclusionReadback));
|
|
|
|
// INITVIEWS_TODO: This could be more efficient if broken up in to separate concerns:
|
|
// - What is occluded?
|
|
// - For which primitives should we render occlusion queries?
|
|
// - Generate occlusion query geometry.
|
|
|
|
int32 NumOccludedPrimitives = 0;
|
|
FSceneViewState* ViewState = (FSceneViewState*)View.State;
|
|
|
|
// Disable HZB on OpenGL platforms to avoid rendering artifacts
|
|
// It can be forced on by setting HZBOcclusion to 2
|
|
bool bHZBOcclusion = !IsOpenGLPlatform(GShaderPlatformForFeatureLevel[Scene->GetFeatureLevel()]);
|
|
bHZBOcclusion = bHZBOcclusion && GHZBOcclusion;
|
|
bHZBOcclusion = bHZBOcclusion && FDataDrivenShaderPlatformInfo::GetSupportsHZBOcclusion(GShaderPlatformForFeatureLevel[Scene->GetFeatureLevel()]);
|
|
bHZBOcclusion = bHZBOcclusion || (GHZBOcclusion == 2);
|
|
|
|
// Use precomputed visibility data if it is available.
|
|
if (View.PrecomputedVisibilityData)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_LookupPrecomputedVisibility);
|
|
|
|
FViewElementPDI OcclusionPDI(&View, nullptr, nullptr);
|
|
uint8 PrecomputedVisibilityFlags = EOcclusionFlags::CanBeOccluded | EOcclusionFlags::HasPrecomputedVisibility;
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
if ((Scene->PrimitiveOcclusionFlags[BitIt.GetIndex()] & PrecomputedVisibilityFlags) == PrecomputedVisibilityFlags)
|
|
{
|
|
FPrimitiveVisibilityId VisibilityId = Scene->PrimitiveVisibilityIds[BitIt.GetIndex()];
|
|
if ((View.PrecomputedVisibilityData[VisibilityId.ByteIndex] & VisibilityId.BitMask) == 0)
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
INC_DWORD_STAT_BY(STAT_StaticallyOccludedPrimitives,1);
|
|
STAT(NumOccludedPrimitives++);
|
|
|
|
if (GVisualizeOccludedPrimitives)
|
|
{
|
|
const FBoxSphereBounds& Bounds = Scene->PrimitiveOcclusionBounds[BitIt.GetIndex()];
|
|
DrawWireBox(&OcclusionPDI, Bounds.GetBox(), FColor(100, 50, 50), SDPG_Foreground);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float CurrentRealTime = View.Family->Time.GetRealTimeSeconds();
|
|
if (ViewState)
|
|
{
|
|
bool bSubmitQueries = !View.bDisableQuerySubmissions;
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
bSubmitQueries = bSubmitQueries && !ViewState->HasViewParent() && !ViewState->bIsFrozen;
|
|
#endif
|
|
|
|
if( bHZBOcclusion )
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_MapHZBResults);
|
|
check(!ViewState->HZBOcclusionTests.IsValidFrame(ViewState->OcclusionFrameCounter));
|
|
ViewState->HZBOcclusionTests.MapResults(RHICmdList);
|
|
}
|
|
|
|
// Perform round-robin occlusion queries
|
|
if (View.ViewState->IsRoundRobinEnabled() &&
|
|
!View.bIsSceneCapture && // We only round-robin on the main renderer (not scene captures)
|
|
!View.bIgnoreExistingQueries && // We do not alternate occlusion queries when we want to refresh the occlusion history
|
|
(IStereoRendering::IsStereoEyeView(View))) // Only relevant to stereo views
|
|
{
|
|
// For even frames, prevent left eye from occlusion querying
|
|
// For odd frames, prevent right eye from occlusion querying
|
|
const bool FrameParity = ((View.ViewState->PrevFrameNumber & 0x01) == 1);
|
|
bSubmitQueries &= (FrameParity && IStereoRendering::IsAPrimaryView(View)) ||
|
|
(!FrameParity && IStereoRendering::IsASecondaryView(View));
|
|
}
|
|
|
|
View.ViewState->PrimitiveOcclusionQueryPool.AdvanceFrame(
|
|
ViewState->OcclusionFrameCounter,
|
|
FOcclusionQueryHelpers::GetNumBufferedFrames(Scene->GetFeatureLevel()),
|
|
View.ViewState->IsRoundRobinEnabled() && !View.bIsSceneCapture && IStereoRendering::IsStereoEyeView(View));
|
|
|
|
NumOccludedPrimitives += FetchVisibilityForPrimitives(Scene, View, bSubmitQueries, bHZBOcclusion, DynamicVertexBuffer);
|
|
|
|
if( bHZBOcclusion )
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_HZBUnmapResults);
|
|
|
|
ViewState->HZBOcclusionTests.UnmapResults(RHICmdList);
|
|
|
|
if( bSubmitQueries )
|
|
{
|
|
ViewState->HZBOcclusionTests.SetValidFrameNumber(ViewState->OcclusionFrameCounter);
|
|
}
|
|
}
|
|
}
|
|
RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_AfterOcclusionReadback));
|
|
return NumOccludedPrimitives;
|
|
}
|
|
|
|
const int32 InputsPrimNumPerRelevancePacket = 128;
|
|
const int32 AverageMeshBatchNumPerRelevancePacket = InputsPrimNumPerRelevancePacket * 2;
|
|
|
|
template<class T, int TAmplifyFactor = 1>
|
|
struct FRelevancePrimSet
|
|
{
|
|
enum
|
|
{
|
|
MaxInputPrims = InputsPrimNumPerRelevancePacket - 1, // leave space for NumPrims.
|
|
MaxOutputPrims = MaxInputPrims * TAmplifyFactor
|
|
};
|
|
int32 NumPrims;
|
|
|
|
T Prims[MaxOutputPrims];
|
|
|
|
FORCEINLINE FRelevancePrimSet()
|
|
: NumPrims(0)
|
|
{
|
|
//FMemory::Memzero(Prims, sizeof(T) * GetMaxOutputPrim());
|
|
}
|
|
FORCEINLINE void AddPrim(T Prim)
|
|
{
|
|
checkSlow(NumPrims < MaxOutputPrims);
|
|
Prims[NumPrims++] = Prim;
|
|
}
|
|
FORCEINLINE bool IsFull() const
|
|
{
|
|
return NumPrims >= MaxOutputPrims;
|
|
}
|
|
template<class TARRAY>
|
|
FORCEINLINE void AppendTo(TARRAY& DestArray)
|
|
{
|
|
DestArray.Append(Prims, NumPrims);
|
|
}
|
|
};
|
|
|
|
struct FMarkRelevantStaticMeshesForViewData
|
|
{
|
|
FVector ViewOrigin;
|
|
int32 ForcedLODLevel;
|
|
float LODScale;
|
|
float MinScreenRadiusForCSMDepthSquared;
|
|
float MinScreenRadiusForDepthPrepassSquared;
|
|
bool bFullEarlyZPass;
|
|
|
|
FMarkRelevantStaticMeshesForViewData(FViewInfo& View)
|
|
{
|
|
ViewOrigin = View.ViewMatrices.GetViewOrigin();
|
|
|
|
// outside of the loop to be more efficient
|
|
ForcedLODLevel = (View.Family->EngineShowFlags.LOD) ? GetCVarForceLOD() : 0;
|
|
|
|
LODScale = CVarStaticMeshLODDistanceScale.GetValueOnRenderThread() * View.LODDistanceFactor;
|
|
|
|
MinScreenRadiusForCSMDepthSquared = GMinScreenRadiusForCSMDepth * GMinScreenRadiusForCSMDepth;
|
|
MinScreenRadiusForDepthPrepassSquared = GMinScreenRadiusForDepthPrepass * GMinScreenRadiusForDepthPrepass;
|
|
|
|
extern bool ShouldForceFullDepthPass(EShaderPlatform ShaderPlatform);
|
|
EShaderPlatform ShaderPlatform = View.GetShaderPlatform();
|
|
if (IsMobilePlatform(ShaderPlatform))
|
|
{
|
|
FScene* Scene = View.Family->Scene->GetRenderScene();
|
|
bFullEarlyZPass = (Scene && Scene->EarlyZPassMode == DDM_AllOpaque);
|
|
}
|
|
else
|
|
{
|
|
bFullEarlyZPass = ShouldForceFullDepthPass(ShaderPlatform);
|
|
}
|
|
}
|
|
};
|
|
|
|
namespace EMarkMaskBits
|
|
{
|
|
enum Type
|
|
{
|
|
StaticMeshVisibilityMapMask = 0x2,
|
|
StaticMeshFadeOutDitheredLODMapMask = 0x10,
|
|
StaticMeshFadeInDitheredLODMapMask = 0x20,
|
|
};
|
|
}
|
|
|
|
typedef TArray<FVisibleMeshDrawCommand> FPassDrawCommandArray;
|
|
typedef TArray<const FStaticMeshBatch*> FPassDrawCommandBuildRequestArray;
|
|
|
|
struct FDrawCommandRelevancePacket
|
|
{
|
|
FDrawCommandRelevancePacket()
|
|
{
|
|
bUseCachedMeshDrawCommands = UseCachedMeshDrawCommands();
|
|
|
|
for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; ++PassIndex)
|
|
{
|
|
NumDynamicBuildRequestElements[PassIndex] = 0;
|
|
}
|
|
}
|
|
|
|
FPassDrawCommandArray VisibleCachedDrawCommands[EMeshPass::Num];
|
|
FPassDrawCommandBuildRequestArray DynamicBuildRequests[EMeshPass::Num];
|
|
int32 NumDynamicBuildRequestElements[EMeshPass::Num];
|
|
bool bUseCachedMeshDrawCommands;
|
|
|
|
void AddCommandsForMesh(
|
|
int32 PrimitiveIndex,
|
|
const FPrimitiveSceneInfo* InPrimitiveSceneInfo,
|
|
const FStaticMeshBatchRelevance& RESTRICT StaticMeshRelevance,
|
|
const FStaticMeshBatch& RESTRICT StaticMesh,
|
|
const FScene* RESTRICT Scene,
|
|
bool bCanCache,
|
|
EMeshPass::Type PassType)
|
|
{
|
|
const EShadingPath ShadingPath = Scene->GetShadingPath();
|
|
const bool bUseCachedMeshCommand = bUseCachedMeshDrawCommands
|
|
&& !!(FPassProcessorManager::GetPassFlags(ShadingPath, PassType) & EMeshPassFlags::CachedMeshCommands)
|
|
&& StaticMeshRelevance.bSupportsCachingMeshDrawCommands
|
|
&& bCanCache;
|
|
|
|
if (bUseCachedMeshCommand)
|
|
{
|
|
const int32 StaticMeshCommandInfoIndex = StaticMeshRelevance.GetStaticMeshCommandInfoIndex(PassType);
|
|
if (StaticMeshCommandInfoIndex >= 0)
|
|
{
|
|
const FCachedMeshDrawCommandInfo& CachedMeshDrawCommand = InPrimitiveSceneInfo->StaticMeshCommandInfos[StaticMeshCommandInfoIndex];
|
|
const FCachedPassMeshDrawList& SceneDrawList = Scene->CachedDrawLists[PassType];
|
|
|
|
// AddUninitialized_GetRef()
|
|
VisibleCachedDrawCommands[(uint32)PassType].AddUninitialized();
|
|
FVisibleMeshDrawCommand& NewVisibleMeshDrawCommand = VisibleCachedDrawCommands[(uint32)PassType].Last();
|
|
|
|
const FMeshDrawCommand* MeshDrawCommand = CachedMeshDrawCommand.StateBucketId >= 0
|
|
? &Scene->CachedMeshDrawCommandStateBuckets[PassType].GetByElementId(CachedMeshDrawCommand.StateBucketId).Key
|
|
: &SceneDrawList.MeshDrawCommands[CachedMeshDrawCommand.CommandIndex];
|
|
|
|
NewVisibleMeshDrawCommand.Setup(
|
|
MeshDrawCommand,
|
|
FMeshDrawCommandPrimitiveIdInfo(PrimitiveIndex, InPrimitiveSceneInfo->GetInstanceSceneDataOffset()),
|
|
CachedMeshDrawCommand.StateBucketId,
|
|
CachedMeshDrawCommand.MeshFillMode,
|
|
CachedMeshDrawCommand.MeshCullMode,
|
|
CachedMeshDrawCommand.Flags,
|
|
CachedMeshDrawCommand.SortKey);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NumDynamicBuildRequestElements[PassType] += StaticMeshRelevance.NumElements;
|
|
DynamicBuildRequests[PassType].Add(&StaticMesh);
|
|
}
|
|
}
|
|
};
|
|
|
|
struct FRelevancePacket
|
|
{
|
|
const float CurrentWorldTime;
|
|
const float DeltaWorldTime;
|
|
|
|
FRHICommandListImmediate& RHICmdList;
|
|
const FScene* Scene;
|
|
const FViewInfo& View;
|
|
const FViewCommands& ViewCommands;
|
|
const uint8 ViewBit;
|
|
const FMarkRelevantStaticMeshesForViewData& ViewData;
|
|
FPrimitiveViewMasks& OutHasDynamicMeshElementsMasks;
|
|
FPrimitiveViewMasks& OutHasDynamicEditorMeshElementsMasks;
|
|
uint8* RESTRICT MarkMasks;
|
|
|
|
FRelevancePrimSet<int32> Input;
|
|
FRelevancePrimSet<int32> RelevantStaticPrimitives;
|
|
FRelevancePrimSet<int32> NotDrawRelevant;
|
|
FRelevancePrimSet<int32> TranslucentSelfShadowPrimitives;
|
|
FRelevancePrimSet<FPrimitiveSceneInfo*> VisibleDynamicPrimitivesWithSimpleLights;
|
|
int32 NumVisibleDynamicPrimitives;
|
|
int32 NumVisibleDynamicEditorPrimitives;
|
|
FMeshPassMask VisibleDynamicMeshesPassMask;
|
|
FTranslucenyPrimCount TranslucentPrimCount;
|
|
bool bHasDistortionPrimitives;
|
|
bool bHasCustomDepthPrimitives;
|
|
FRelevancePrimSet<FPrimitiveSceneInfo*> LazyUpdatePrimitives;
|
|
FRelevancePrimSet<FPrimitiveSceneInfo*> DirtyIndirectLightingCacheBufferPrimitives;
|
|
FRelevancePrimSet<FPrimitiveSceneInfo*> RecachedReflectionCapturePrimitives;
|
|
#if WITH_EDITOR
|
|
FRelevancePrimSet<FPrimitiveSceneInfo*> EditorVisualizeLevelInstancePrimitives;
|
|
FRelevancePrimSet<FPrimitiveSceneInfo*> EditorSelectedPrimitives;
|
|
#endif
|
|
|
|
TArray<FMeshDecalBatch> MeshDecalBatches;
|
|
TArray<FVolumetricMeshBatch> VolumetricMeshBatches;
|
|
TArray<FSkyMeshBatch> SkyMeshBatches;
|
|
TArray<FSortedTrianglesMeshBatch> SortedTrianglesMeshBatches;
|
|
FDrawCommandRelevancePacket DrawCommandPacket;
|
|
TSet<uint32> CustomDepthStencilValues;
|
|
|
|
struct FPrimitiveLODMask
|
|
{
|
|
FPrimitiveLODMask()
|
|
: PrimitiveIndex(INDEX_NONE)
|
|
{}
|
|
|
|
FPrimitiveLODMask(const int32 InPrimitiveIndex, const FLODMask& InLODMask)
|
|
: PrimitiveIndex(InPrimitiveIndex)
|
|
, LODMask(InLODMask)
|
|
{}
|
|
|
|
int32 PrimitiveIndex;
|
|
FLODMask LODMask;
|
|
};
|
|
|
|
FRelevancePrimSet<FPrimitiveLODMask> PrimitivesLODMask; // group both lod mask with primitive index to be able to properly merge them in the view
|
|
|
|
uint16 CombinedShadingModelMask;
|
|
bool bUsesGlobalDistanceField;
|
|
bool bUsesLightingChannels;
|
|
bool bTranslucentSurfaceLighting;
|
|
bool bUsesSceneDepth;
|
|
bool bUsesCustomDepth;
|
|
bool bUsesCustomStencil;
|
|
bool bSceneHasSkyMaterial;
|
|
bool bHasSingleLayerWaterMaterial;
|
|
bool bHasTranslucencySeparateModulation;
|
|
|
|
FRelevancePacket(
|
|
FRHICommandListImmediate& InRHICmdList,
|
|
const FScene* InScene,
|
|
const FViewInfo& InView,
|
|
const FViewCommands& InViewCommands,
|
|
uint8 InViewBit,
|
|
const FMarkRelevantStaticMeshesForViewData& InViewData,
|
|
FPrimitiveViewMasks& InOutHasDynamicMeshElementsMasks,
|
|
FPrimitiveViewMasks& InOutHasDynamicEditorMeshElementsMasks,
|
|
uint8* InMarkMasks)
|
|
|
|
: CurrentWorldTime(InView.Family->Time.GetWorldTimeSeconds())
|
|
, DeltaWorldTime(InView.Family->Time.GetDeltaWorldTimeSeconds())
|
|
, RHICmdList(InRHICmdList)
|
|
, Scene(InScene)
|
|
, View(InView)
|
|
, ViewCommands(InViewCommands)
|
|
, ViewBit(InViewBit)
|
|
, ViewData(InViewData)
|
|
, OutHasDynamicMeshElementsMasks(InOutHasDynamicMeshElementsMasks)
|
|
, OutHasDynamicEditorMeshElementsMasks(InOutHasDynamicEditorMeshElementsMasks)
|
|
, MarkMasks(InMarkMasks)
|
|
, NumVisibleDynamicPrimitives(0)
|
|
, NumVisibleDynamicEditorPrimitives(0)
|
|
, bHasDistortionPrimitives(false)
|
|
, bHasCustomDepthPrimitives(false)
|
|
, CombinedShadingModelMask(0)
|
|
, bUsesGlobalDistanceField(false)
|
|
, bUsesLightingChannels(false)
|
|
, bTranslucentSurfaceLighting(false)
|
|
, bUsesSceneDepth(false)
|
|
, bUsesCustomDepth(false)
|
|
, bUsesCustomStencil(false)
|
|
, bSceneHasSkyMaterial(false)
|
|
, bHasSingleLayerWaterMaterial(false)
|
|
, bHasTranslucencySeparateModulation(false)
|
|
{
|
|
}
|
|
|
|
void AnyThreadTask()
|
|
{
|
|
FOptionalTaskTagScope Scope(ETaskTag::EParallelRenderingThread);
|
|
ComputeRelevance();
|
|
MarkRelevant();
|
|
}
|
|
|
|
void ComputeRelevance()
|
|
{
|
|
CombinedShadingModelMask = 0;
|
|
bSceneHasSkyMaterial = 0;
|
|
bHasSingleLayerWaterMaterial = 0;
|
|
bHasTranslucencySeparateModulation = 0;
|
|
bUsesGlobalDistanceField = false;
|
|
bUsesLightingChannels = false;
|
|
bTranslucentSurfaceLighting = false;
|
|
const EShadingPath ShadingPath = Scene->GetShadingPath();
|
|
const bool bAddLightmapDensityCommands = View.Family->EngineShowFlags.LightMapDensity && AllowDebugViewmodes();
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_ComputeViewRelevance);
|
|
for (int32 Index = 0; Index < Input.NumPrims; Index++)
|
|
{
|
|
int32 BitIndex = Input.Prims[Index];
|
|
FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene->Primitives[BitIndex];
|
|
FPrimitiveViewRelevance& ViewRelevance = const_cast<FPrimitiveViewRelevance&>(View.PrimitiveViewRelevanceMap[BitIndex]);
|
|
ViewRelevance = PrimitiveSceneInfo->Proxy->GetViewRelevance(&View);
|
|
ViewRelevance.bInitializedThisFrame = true;
|
|
|
|
const bool bStaticRelevance = ViewRelevance.bStaticRelevance;
|
|
const bool bDrawRelevance = ViewRelevance.bDrawRelevance;
|
|
const bool bDynamicRelevance = ViewRelevance.bDynamicRelevance;
|
|
const bool bShadowRelevance = ViewRelevance.bShadowRelevance;
|
|
const bool bEditorRelevance = ViewRelevance.bEditorPrimitiveRelevance;
|
|
const bool bEditorVisualizeLevelInstanceRelevance = ViewRelevance.bEditorVisualizeLevelInstanceRelevance;
|
|
const bool bEditorSelectionRelevance = ViewRelevance.bEditorStaticSelectionRelevance;
|
|
const bool bTranslucentRelevance = ViewRelevance.HasTranslucency();
|
|
|
|
const bool bHairStrandsEnabled = ViewRelevance.bHairStrands && IsHairStrandsEnabled(EHairStrandsShaderType::All, Scene->GetShaderPlatform());
|
|
|
|
if (View.bIsReflectionCapture && !PrimitiveSceneInfo->Proxy->IsVisibleInReflectionCaptures())
|
|
{
|
|
NotDrawRelevant.AddPrim(BitIndex);
|
|
continue;
|
|
}
|
|
|
|
if (bStaticRelevance && (bDrawRelevance || bShadowRelevance))
|
|
{
|
|
RelevantStaticPrimitives.AddPrim(BitIndex);
|
|
}
|
|
|
|
if (!bDrawRelevance)
|
|
{
|
|
NotDrawRelevant.AddPrim(BitIndex);
|
|
continue;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (bEditorVisualizeLevelInstanceRelevance)
|
|
{
|
|
EditorVisualizeLevelInstancePrimitives.AddPrim(PrimitiveSceneInfo);
|
|
}
|
|
|
|
if (bEditorSelectionRelevance)
|
|
{
|
|
EditorSelectedPrimitives.AddPrim(PrimitiveSceneInfo);
|
|
}
|
|
#endif
|
|
|
|
if (bEditorRelevance)
|
|
{
|
|
++NumVisibleDynamicEditorPrimitives;
|
|
|
|
if (GIsEditor)
|
|
{
|
|
OutHasDynamicEditorMeshElementsMasks[BitIndex] |= ViewBit;
|
|
}
|
|
}
|
|
else if(bDynamicRelevance)
|
|
{
|
|
// Keep track of visible dynamic primitives.
|
|
++NumVisibleDynamicPrimitives;
|
|
OutHasDynamicMeshElementsMasks[BitIndex] |= ViewBit;
|
|
|
|
if (ViewRelevance.bHasSimpleLights)
|
|
{
|
|
VisibleDynamicPrimitivesWithSimpleLights.AddPrim(PrimitiveSceneInfo);
|
|
}
|
|
}
|
|
else if (bHairStrandsEnabled)
|
|
{
|
|
// Strands MeshElement
|
|
++NumVisibleDynamicPrimitives;
|
|
OutHasDynamicMeshElementsMasks[BitIndex] |= ViewBit;
|
|
}
|
|
|
|
if (bTranslucentRelevance && !bEditorRelevance && ViewRelevance.bRenderInMainPass)
|
|
{
|
|
if (View.Family->AllowTranslucencyAfterDOF())
|
|
{
|
|
if (ViewRelevance.bNormalTranslucency)
|
|
{
|
|
TranslucentPrimCount.Add(ETranslucencyPass::TPT_StandardTranslucency, ViewRelevance.bUsesSceneColorCopy);
|
|
}
|
|
|
|
if (ViewRelevance.bSeparateTranslucency)
|
|
{
|
|
TranslucentPrimCount.Add(ETranslucencyPass::TPT_TranslucencyAfterDOF, ViewRelevance.bUsesSceneColorCopy);
|
|
}
|
|
|
|
if (ViewRelevance.bSeparateTranslucencyModulate)
|
|
{
|
|
TranslucentPrimCount.Add(ETranslucencyPass::TPT_TranslucencyAfterDOFModulate, ViewRelevance.bUsesSceneColorCopy);
|
|
}
|
|
|
|
if (ViewRelevance.bPostMotionBlurTranslucency)
|
|
{
|
|
TranslucentPrimCount.Add(ETranslucencyPass::TPT_TranslucencyAfterMotionBlur, ViewRelevance.bUsesSceneColorCopy);
|
|
}
|
|
}
|
|
else // Otherwise, everything is rendered in a single bucket. This is not related to whether DOF is currently enabled or not.
|
|
{
|
|
// When using all translucency, Standard and AfterDOF are sorted together instead of being rendered like 2 buckets.
|
|
TranslucentPrimCount.Add(ETranslucencyPass::TPT_AllTranslucency, ViewRelevance.bUsesSceneColorCopy);
|
|
}
|
|
|
|
if (ViewRelevance.bDistortion)
|
|
{
|
|
bHasDistortionPrimitives = true;
|
|
}
|
|
}
|
|
|
|
CombinedShadingModelMask |= ViewRelevance.ShadingModelMask;
|
|
bUsesGlobalDistanceField |= ViewRelevance.bUsesGlobalDistanceField;
|
|
bUsesLightingChannels |= ViewRelevance.bUsesLightingChannels;
|
|
bTranslucentSurfaceLighting |= ViewRelevance.bTranslucentSurfaceLighting;
|
|
bUsesSceneDepth |= ViewRelevance.bUsesSceneDepth;
|
|
bUsesCustomDepth |= (ViewRelevance.CustomDepthStencilUsageMask & 1) > 0;
|
|
bUsesCustomStencil |= (ViewRelevance.CustomDepthStencilUsageMask & (1 << 1)) > 0;
|
|
bSceneHasSkyMaterial |= ViewRelevance.bUsesSkyMaterial;
|
|
bHasSingleLayerWaterMaterial |= ViewRelevance.bUsesSingleLayerWaterMaterial;
|
|
bHasTranslucencySeparateModulation |= ViewRelevance.bSeparateTranslucencyModulate;
|
|
|
|
if (ViewRelevance.bRenderCustomDepth)
|
|
{
|
|
bHasCustomDepthPrimitives = true;
|
|
CustomDepthStencilValues.Add(PrimitiveSceneInfo->Proxy->GetCustomDepthStencilValue());
|
|
}
|
|
|
|
extern bool GUseTranslucencyShadowDepths;
|
|
if (GUseTranslucencyShadowDepths && ViewRelevance.bTranslucentSelfShadow)
|
|
{
|
|
TranslucentSelfShadowPrimitives.AddPrim(BitIndex);
|
|
}
|
|
|
|
// INITVIEWS_TODO: Do this in a separate pass? There are no dependencies
|
|
// here except maybe ParentPrimitives. This could be done in a
|
|
// low-priority background task and forgotten about.
|
|
|
|
PrimitiveSceneInfo->LastRenderTime = CurrentWorldTime;
|
|
|
|
// If the primitive is definitely unoccluded or if in Wireframe mode and the primitive is estimated
|
|
// to be unoccluded, then update the primitive components's LastRenderTime
|
|
// on the game thread. This signals that the primitive is visible.
|
|
if (View.PrimitiveDefinitelyUnoccludedMap[BitIndex] || (View.Family->EngineShowFlags.Wireframe && View.PrimitiveVisibilityMap[BitIndex]))
|
|
{
|
|
PrimitiveSceneInfo->UpdateComponentLastRenderTime(CurrentWorldTime, /*bUpdateLastRenderTimeOnScreen=*/true);
|
|
}
|
|
|
|
// Cache the nearest reflection proxy if needed
|
|
if (PrimitiveSceneInfo->NeedsReflectionCaptureUpdate())
|
|
{
|
|
// mobile should not have any outstanding reflection capture update requests at this point, except for when lighting isn't rebuilt
|
|
PrimitiveSceneInfo->CacheReflectionCaptures();
|
|
|
|
// With forward shading we need to track reflection capture cache updates
|
|
// in order to update primitive's uniform buffer's closest reflection capture id.
|
|
if (IsForwardShadingEnabled(Scene->GetShaderPlatform()))
|
|
{
|
|
RecachedReflectionCapturePrimitives.AddPrim(PrimitiveSceneInfo);
|
|
}
|
|
}
|
|
|
|
if (PrimitiveSceneInfo->NeedsUniformBufferUpdate())
|
|
{
|
|
LazyUpdatePrimitives.AddPrim(PrimitiveSceneInfo);
|
|
}
|
|
if (PrimitiveSceneInfo->NeedsIndirectLightingCacheBufferUpdate())
|
|
{
|
|
DirtyIndirectLightingCacheBufferPrimitives.AddPrim(PrimitiveSceneInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MarkRelevant()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_StaticRelevance);
|
|
|
|
// using a local counter to reduce memory traffic
|
|
int32 NumVisibleStaticMeshElements = 0;
|
|
FViewInfo& WriteView = const_cast<FViewInfo&>(View);
|
|
const FSceneViewState* ViewState = (FSceneViewState*)View.State;
|
|
const EShadingPath ShadingPath = Scene->GetShadingPath();
|
|
const bool bMobileMaskedInEarlyPass = (ShadingPath == EShadingPath::Mobile) && Scene->EarlyZPassMode == DDM_MaskedOnly;
|
|
const bool bMobileBasePassAlwaysUsesCSM = (ShadingPath == EShadingPath::Mobile) && MobileBasePassAlwaysUsesCSM(Scene->GetShaderPlatform());
|
|
const bool bVelocityPassWritesDepth = Scene->EarlyZPassMode == DDM_AllOpaqueNoVelocity;
|
|
const bool bHLODActive = Scene->SceneLODHierarchy.IsActive();
|
|
const FHLODVisibilityState* const HLODState = bHLODActive && ViewState ? &ViewState->HLODVisibilityState : nullptr;
|
|
float MaxDrawDistanceScale = GetCachedScalabilityCVars().ViewDistanceScale;
|
|
MaxDrawDistanceScale *= GetCachedScalabilityCVars().CalculateFieldOfViewDistanceScale(View.DesiredFOV);
|
|
|
|
|
|
for (int32 StaticPrimIndex = 0, Num = RelevantStaticPrimitives.NumPrims; StaticPrimIndex < Num; ++StaticPrimIndex)
|
|
{
|
|
int32 PrimitiveIndex = RelevantStaticPrimitives.Prims[StaticPrimIndex];
|
|
const FPrimitiveSceneInfo* RESTRICT PrimitiveSceneInfo = Scene->Primitives[PrimitiveIndex];
|
|
const FPrimitiveBounds& Bounds = Scene->PrimitiveBounds[PrimitiveIndex];
|
|
const FPrimitiveViewRelevance& ViewRelevance = View.PrimitiveViewRelevanceMap[PrimitiveIndex];
|
|
const bool bIsPrimitiveDistanceCullFading = View.PrimitiveFadeUniformBufferMap[PrimitiveIndex];
|
|
|
|
const int8 CurFirstLODIdx = PrimitiveSceneInfo->Proxy->GetCurrentFirstLODIdx_RenderThread();
|
|
check(CurFirstLODIdx >= 0);
|
|
float MeshScreenSizeSquared = 0;
|
|
FLODMask LODToRender = ComputeLODForMeshes(PrimitiveSceneInfo->StaticMeshRelevances, View, Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.SphereRadius, ViewData.ForcedLODLevel, MeshScreenSizeSquared, CurFirstLODIdx, ViewData.LODScale);
|
|
|
|
PrimitivesLODMask.AddPrim(FRelevancePacket::FPrimitiveLODMask(PrimitiveIndex, LODToRender));
|
|
|
|
const bool bIsHLODFading = HLODState ? HLODState->IsNodeFading(PrimitiveIndex) : false;
|
|
const bool bIsHLODFadingOut = HLODState ? HLODState->IsNodeFadingOut(PrimitiveIndex) : false;
|
|
const bool bIsLODDithered = LODToRender.IsDithered();
|
|
|
|
float DistanceSquared = (Bounds.BoxSphereBounds.Origin - ViewData.ViewOrigin).SizeSquared();
|
|
const float LODFactorDistanceSquared = DistanceSquared * FMath::Square(ViewData.LODScale);
|
|
const bool bDrawShadowDepth = FMath::Square(Bounds.BoxSphereBounds.SphereRadius) > ViewData.MinScreenRadiusForCSMDepthSquared * LODFactorDistanceSquared;
|
|
const bool bDrawDepthOnly = ViewData.bFullEarlyZPass || ((ShadingPath != EShadingPath::Mobile) && (FMath::Square(Bounds.BoxSphereBounds.SphereRadius) > GMinScreenRadiusForDepthPrepass * GMinScreenRadiusForDepthPrepass * LODFactorDistanceSquared));
|
|
|
|
const bool bAddLightmapDensityCommands = View.Family->EngineShowFlags.LightMapDensity && AllowDebugViewmodes();
|
|
|
|
const int32 NumStaticMeshes = PrimitiveSceneInfo->StaticMeshRelevances.Num();
|
|
for(int32 MeshIndex = 0;MeshIndex < NumStaticMeshes;MeshIndex++)
|
|
{
|
|
const FStaticMeshBatchRelevance& StaticMeshRelevance = PrimitiveSceneInfo->StaticMeshRelevances[MeshIndex];
|
|
const FStaticMeshBatch& StaticMesh = PrimitiveSceneInfo->StaticMeshes[MeshIndex];
|
|
|
|
if (StaticMesh.bOverlayMaterial && !View.Family->EngineShowFlags.DistanceCulledPrimitives)
|
|
{
|
|
// Overlay mesh can have a his own cull distance that is shorter than primitive cull distance
|
|
float OverlayMaterialMaxDrawDistance = StaticMeshRelevance.ScreenSize;
|
|
if (OverlayMaterialMaxDrawDistance > 1.f && OverlayMaterialMaxDrawDistance != FLT_MAX)
|
|
{
|
|
if (DistanceSquared > FMath::Square(OverlayMaterialMaxDrawDistance * MaxDrawDistanceScale))
|
|
{
|
|
// distance culled
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (LODToRender.ContainsLOD(StaticMeshRelevance.LODIndex))
|
|
{
|
|
uint8 MarkMask = 0;
|
|
bool bHiddenByHLODFade = false; // Hide mesh LOD levels that HLOD is substituting
|
|
|
|
if (bIsHLODFading)
|
|
{
|
|
if (bIsHLODFadingOut)
|
|
{
|
|
if (bIsLODDithered && LODToRender.DitheredLODIndices[1] == StaticMeshRelevance.LODIndex)
|
|
{
|
|
bHiddenByHLODFade = true;
|
|
}
|
|
else
|
|
{
|
|
MarkMask |= EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bIsLODDithered && LODToRender.DitheredLODIndices[0] == StaticMeshRelevance.LODIndex)
|
|
{
|
|
bHiddenByHLODFade = true;
|
|
}
|
|
else
|
|
{
|
|
MarkMask |= EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask;
|
|
}
|
|
}
|
|
}
|
|
else if (bIsLODDithered)
|
|
{
|
|
if (LODToRender.DitheredLODIndices[0] == StaticMeshRelevance.LODIndex)
|
|
{
|
|
MarkMask |= EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask;
|
|
}
|
|
else
|
|
{
|
|
MarkMask |= EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask;
|
|
}
|
|
}
|
|
|
|
// Don't cache if it requires per view per mesh state for LOD dithering or distance cull fade.
|
|
const bool bIsMeshDitheringLOD = StaticMeshRelevance.bDitheredLODTransition && (MarkMask & (EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask | EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask));
|
|
const bool bCanCache = !bIsPrimitiveDistanceCullFading && !bIsMeshDitheringLOD;
|
|
|
|
if (ViewRelevance.bDrawRelevance)
|
|
{
|
|
if ((StaticMeshRelevance.bUseForMaterial || StaticMeshRelevance.bUseAsOccluder)
|
|
&& (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth || ViewRelevance.bRenderInDepthPass)
|
|
&& !bHiddenByHLODFade)
|
|
{
|
|
// Add velocity commands first to track for case where velocity pass writes depth.
|
|
bool bIsMeshInVelocityPass = false;
|
|
if (StaticMeshRelevance.bUseForMaterial && ViewRelevance.bRenderInMainPass)
|
|
{
|
|
if (ViewRelevance.HasVelocity())
|
|
{
|
|
const FPrimitiveSceneProxy* PrimitiveSceneProxy = PrimitiveSceneInfo->Proxy;
|
|
|
|
if (FVelocityMeshProcessor::PrimitiveHasVelocityForView(View, PrimitiveSceneProxy))
|
|
{
|
|
if (ViewRelevance.bVelocityRelevance &&
|
|
FOpaqueVelocityMeshProcessor::PrimitiveCanHaveVelocity(View.GetShaderPlatform(), PrimitiveSceneProxy) &&
|
|
FOpaqueVelocityMeshProcessor::PrimitiveHasVelocityForFrame(PrimitiveSceneProxy))
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::Velocity);
|
|
bIsMeshInVelocityPass = true;
|
|
}
|
|
|
|
if (ViewRelevance.bOutputsTranslucentVelocity &&
|
|
FTranslucentVelocityMeshProcessor::PrimitiveCanHaveVelocity(View.GetShaderPlatform(), PrimitiveSceneProxy) &&
|
|
FTranslucentVelocityMeshProcessor::PrimitiveHasVelocityForFrame(PrimitiveSceneProxy))
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::TranslucentVelocity);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add depth commands.
|
|
if (StaticMeshRelevance.bUseForDepthPass && (bDrawDepthOnly || (bMobileMaskedInEarlyPass && ViewRelevance.bMasked)))
|
|
{
|
|
if (!(bIsMeshInVelocityPass && bVelocityPassWritesDepth))
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::DepthPass);
|
|
}
|
|
#if RHI_RAYTRACING
|
|
if (IsRayTracingEnabled())
|
|
{
|
|
if (MarkMask & EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::DitheredLODFadingOutMaskPass);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Mark static mesh as visible for rendering
|
|
if (StaticMeshRelevance.bUseForMaterial && (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth))
|
|
{
|
|
// Specific logic for mobile packets
|
|
if (ShadingPath == EShadingPath::Mobile)
|
|
{
|
|
// Skydome must not be added to base pass bucket
|
|
if (!StaticMeshRelevance.bUseSkyMaterial)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::BasePass);
|
|
if (!bMobileBasePassAlwaysUsesCSM)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::MobileBasePassCSM);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::SkyPass);
|
|
}
|
|
// bUseSingleLayerWaterMaterial is added to BasePass on Mobile. No need to add it to SingleLayerWaterPass
|
|
|
|
MarkMask |= EMarkMaskBits::StaticMeshVisibilityMapMask;
|
|
}
|
|
else // Regular shading path
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::BasePass);
|
|
MarkMask |= EMarkMaskBits::StaticMeshVisibilityMapMask;
|
|
|
|
if (StaticMeshRelevance.bUseSkyMaterial)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::SkyPass);
|
|
}
|
|
if (StaticMeshRelevance.bUseSingleLayerWaterMaterial)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::SingleLayerWaterPass);
|
|
}
|
|
}
|
|
|
|
if (StaticMeshRelevance.bUseAnisotropy)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::AnisotropyPass);
|
|
}
|
|
|
|
if (ViewRelevance.bRenderCustomDepth)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::CustomDepth);
|
|
}
|
|
|
|
if (bAddLightmapDensityCommands)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::LightmapDensity);
|
|
}
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
else if (View.Family->UseDebugViewPS())
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::DebugViewMode);
|
|
}
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
if (StaticMeshRelevance.bSelectable)
|
|
{
|
|
if (View.bAllowTranslucentPrimitivesInHitProxy)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::HitProxy);
|
|
}
|
|
else
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::HitProxyOpaqueOnly);
|
|
}
|
|
}
|
|
#endif
|
|
++NumVisibleStaticMeshElements;
|
|
|
|
INC_DWORD_STAT_BY(STAT_StaticMeshTriangles, StaticMesh.GetNumPrimitives());
|
|
}
|
|
}
|
|
|
|
if (StaticMeshRelevance.bUseForMaterial
|
|
&& ViewRelevance.HasTranslucency()
|
|
&& !ViewRelevance.bEditorPrimitiveRelevance
|
|
&& ViewRelevance.bRenderInMainPass)
|
|
{
|
|
if (View.Family->AllowTranslucencyAfterDOF())
|
|
{
|
|
if (ViewRelevance.bNormalTranslucency)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::TranslucencyStandard);
|
|
}
|
|
|
|
if (ViewRelevance.bSeparateTranslucency)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::TranslucencyAfterDOF);
|
|
}
|
|
|
|
if (ViewRelevance.bSeparateTranslucencyModulate)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::TranslucencyAfterDOFModulate);
|
|
}
|
|
|
|
if (ViewRelevance.bPostMotionBlurTranslucency)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::TranslucencyAfterMotionBlur);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, everything is rendered in a single bucket. This is not related to whether DOF is currently enabled or not.
|
|
// When using all translucency, Standard and AfterDOF are sorted together instead of being rendered like 2 buckets.
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::TranslucencyAll);
|
|
}
|
|
|
|
if (ViewRelevance.bTranslucentSurfaceLighting)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::LumenTranslucencyRadianceCacheMark);
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::LumenFrontLayerTranslucencyGBuffer);
|
|
}
|
|
|
|
if (ViewRelevance.bDistortion)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::Distortion);
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (ViewRelevance.bEditorVisualizeLevelInstanceRelevance)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::EditorLevelInstance);
|
|
}
|
|
|
|
if (ViewRelevance.bEditorStaticSelectionRelevance)
|
|
{
|
|
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::EditorSelection);
|
|
}
|
|
#endif
|
|
|
|
if (ViewRelevance.bHasVolumeMaterialDomain)
|
|
{
|
|
VolumetricMeshBatches.AddUninitialized(1);
|
|
FVolumetricMeshBatch& BatchAndProxy = VolumetricMeshBatches.Last();
|
|
BatchAndProxy.Mesh = &StaticMesh;
|
|
BatchAndProxy.Proxy = PrimitiveSceneInfo->Proxy;
|
|
}
|
|
|
|
if (ViewRelevance.bUsesSkyMaterial)
|
|
{
|
|
SkyMeshBatches.AddUninitialized(1);
|
|
FSkyMeshBatch& BatchAndProxy = SkyMeshBatches.Last();
|
|
BatchAndProxy.Mesh = &StaticMesh;
|
|
BatchAndProxy.Proxy = PrimitiveSceneInfo->Proxy;
|
|
BatchAndProxy.bVisibleInMainPass = ViewRelevance.bRenderInMainPass;
|
|
BatchAndProxy.bVisibleInRealTimeSkyCapture = PrimitiveSceneInfo->bVisibleInRealTimeSkyCapture;
|
|
}
|
|
|
|
if (ViewRelevance.HasTranslucency() && PrimitiveSceneInfo->Proxy->SupportsSortedTriangles()) // Need to check material as well
|
|
{
|
|
SortedTrianglesMeshBatches.AddUninitialized(1);
|
|
FSortedTrianglesMeshBatch& BatchAndProxy = SortedTrianglesMeshBatches.Last();
|
|
BatchAndProxy.Mesh = &StaticMesh;
|
|
BatchAndProxy.Proxy = PrimitiveSceneInfo->Proxy;
|
|
}
|
|
|
|
// FIXME: Now if a primitive has one batch with a decal material all primitive mesh batches will be added as decals
|
|
// Because ViewRelevance is a sum of all material relevances in the primitive
|
|
if (ViewRelevance.bRenderInMainPass && ViewRelevance.bDecal && StaticMeshRelevance.bUseForMaterial)
|
|
{
|
|
MeshDecalBatches.AddUninitialized(1);
|
|
FMeshDecalBatch& BatchAndProxy = MeshDecalBatches.Last();
|
|
BatchAndProxy.Mesh = &StaticMesh;
|
|
BatchAndProxy.Proxy = PrimitiveSceneInfo->Proxy;
|
|
BatchAndProxy.SortKey = PrimitiveSceneInfo->Proxy->GetTranslucencySortPriority();
|
|
}
|
|
}
|
|
|
|
if (MarkMask)
|
|
{
|
|
MarkMasks[StaticMeshRelevance.Id] = MarkMask;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
static_assert(sizeof(WriteView.NumVisibleStaticMeshElements) == sizeof(int32), "Atomic is the wrong size");
|
|
FPlatformAtomics::InterlockedAdd((volatile int32*)&WriteView.NumVisibleStaticMeshElements, NumVisibleStaticMeshElements);
|
|
}
|
|
|
|
void RenderThreadFinalize()
|
|
{
|
|
FViewInfo& WriteView = const_cast<FViewInfo&>(View);
|
|
FViewCommands& WriteViewCommands = const_cast<FViewCommands&>(ViewCommands);
|
|
|
|
for (int32 Index = 0; Index < NotDrawRelevant.NumPrims; Index++)
|
|
{
|
|
WriteView.PrimitiveVisibilityMap[NotDrawRelevant.Prims[Index]] = false;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
auto AddRelevantHitProxiesToArray = [](FRelevancePrimSet<FPrimitiveSceneInfo*>& PrimSet, TArray<uint32>& OutHitProxyArray)
|
|
{
|
|
int32 TotalHitProxiesToAdd = 0;
|
|
for (int32 Idx = 0; Idx < PrimSet.NumPrims; ++Idx)
|
|
{
|
|
if (PrimSet.Prims[Idx]->NaniteHitProxyIds.Num())
|
|
{
|
|
TotalHitProxiesToAdd += PrimSet.Prims[Idx]->NaniteHitProxyIds.Num();
|
|
}
|
|
}
|
|
|
|
OutHitProxyArray.Reserve(OutHitProxyArray.Num() + TotalHitProxiesToAdd);
|
|
|
|
for (int32 Idx = 0; Idx < PrimSet.NumPrims; ++Idx)
|
|
{
|
|
if (PrimSet.Prims[Idx]->NaniteHitProxyIds.Num())
|
|
{
|
|
for (uint32 IdValue : PrimSet.Prims[Idx]->NaniteHitProxyIds)
|
|
{
|
|
OutHitProxyArray.Add(IdValue);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Add hit proxies from editing LevelInstance Nanite primitives
|
|
AddRelevantHitProxiesToArray(EditorVisualizeLevelInstancePrimitives, WriteView.EditorVisualizeLevelInstanceIds);
|
|
|
|
// Add hit proxies from selected Nanite primitives.
|
|
AddRelevantHitProxiesToArray(EditorSelectedPrimitives, WriteView.EditorSelectedHitProxyIds);
|
|
#endif
|
|
|
|
WriteView.ShadingModelMaskInView |= CombinedShadingModelMask;
|
|
WriteView.bUsesGlobalDistanceField |= bUsesGlobalDistanceField;
|
|
WriteView.bUsesLightingChannels |= bUsesLightingChannels;
|
|
WriteView.bTranslucentSurfaceLighting |= bTranslucentSurfaceLighting;
|
|
WriteView.bUsesSceneDepth |= bUsesSceneDepth;
|
|
WriteView.bSceneHasSkyMaterial |= bSceneHasSkyMaterial;
|
|
WriteView.bHasSingleLayerWaterMaterial |= bHasSingleLayerWaterMaterial;
|
|
WriteView.bHasTranslucencySeparateModulation |= bHasTranslucencySeparateModulation;
|
|
VisibleDynamicPrimitivesWithSimpleLights.AppendTo(WriteView.VisibleDynamicPrimitivesWithSimpleLights);
|
|
WriteView.NumVisibleDynamicPrimitives += NumVisibleDynamicPrimitives;
|
|
WriteView.NumVisibleDynamicEditorPrimitives += NumVisibleDynamicEditorPrimitives;
|
|
WriteView.TranslucentPrimCount.Append(TranslucentPrimCount);
|
|
WriteView.bHasDistortionPrimitives |= bHasDistortionPrimitives;
|
|
WriteView.bHasCustomDepthPrimitives |= bHasCustomDepthPrimitives;
|
|
WriteView.CustomDepthStencilValues.Append(CustomDepthStencilValues);
|
|
WriteView.bUsesCustomDepth |= bUsesCustomDepth;
|
|
WriteView.bUsesCustomStencil |= bUsesCustomStencil;
|
|
DirtyIndirectLightingCacheBufferPrimitives.AppendTo(WriteView.DirtyIndirectLightingCacheBufferPrimitives);
|
|
|
|
WriteView.MeshDecalBatches.Append(MeshDecalBatches);
|
|
WriteView.VolumetricMeshBatches.Append(VolumetricMeshBatches);
|
|
WriteView.SkyMeshBatches.Append(SkyMeshBatches);
|
|
WriteView.SortedTrianglesMeshBatches.Append(SortedTrianglesMeshBatches);
|
|
|
|
for (int32 Index = 0; Index < RecachedReflectionCapturePrimitives.NumPrims; ++Index)
|
|
{
|
|
FPrimitiveSceneInfo* PrimitiveSceneInfo = RecachedReflectionCapturePrimitives.Prims[Index];
|
|
|
|
PrimitiveSceneInfo->SetNeedsUniformBufferUpdate(true);
|
|
PrimitiveSceneInfo->ConditionalUpdateUniformBuffer(RHICmdList);
|
|
|
|
FScene& WriteScene = *const_cast<FScene*>(Scene);
|
|
WriteScene.GPUScene.AddPrimitiveToUpdate(PrimitiveSceneInfo->GetIndex(), EPrimitiveDirtyState::ChangedAll);
|
|
}
|
|
|
|
for (int32 Index = 0; Index < LazyUpdatePrimitives.NumPrims; Index++)
|
|
{
|
|
LazyUpdatePrimitives.Prims[Index]->ConditionalUpdateUniformBuffer(RHICmdList);
|
|
}
|
|
|
|
for (int32 i = 0; i < PrimitivesLODMask.NumPrims; ++i)
|
|
{
|
|
WriteView.PrimitivesLODMask[PrimitivesLODMask.Prims[i].PrimitiveIndex] = PrimitivesLODMask.Prims[i].LODMask;
|
|
}
|
|
|
|
for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++)
|
|
{
|
|
FPassDrawCommandArray& SrcCommands = DrawCommandPacket.VisibleCachedDrawCommands[PassIndex];
|
|
FMeshCommandOneFrameArray& DstCommands = WriteViewCommands.MeshCommands[PassIndex];
|
|
if (SrcCommands.Num() > 0)
|
|
{
|
|
static_assert(sizeof(SrcCommands[0]) == sizeof(DstCommands[0]), "Memcpy sizes must match.");
|
|
const int32 PrevNum = DstCommands.AddUninitialized(SrcCommands.Num());
|
|
FMemory::Memcpy(&DstCommands[PrevNum], &SrcCommands[0], SrcCommands.Num() * sizeof(SrcCommands[0]));
|
|
}
|
|
|
|
FPassDrawCommandBuildRequestArray& SrcRequests = DrawCommandPacket.DynamicBuildRequests[PassIndex];
|
|
TArray<const FStaticMeshBatch*, SceneRenderingAllocator>& DstRequests = WriteViewCommands.DynamicMeshCommandBuildRequests[PassIndex];
|
|
if (SrcRequests.Num() > 0)
|
|
{
|
|
static_assert(sizeof(SrcRequests[0]) == sizeof(DstRequests[0]), "Memcpy sizes must match.");
|
|
const int32 PrevNum = DstRequests.AddUninitialized(SrcRequests.Num());
|
|
FMemory::Memcpy(&DstRequests[PrevNum], &SrcRequests[0], SrcRequests.Num() * sizeof(SrcRequests[0]));
|
|
}
|
|
|
|
WriteViewCommands.NumDynamicMeshCommandBuildRequestElements[PassIndex] += DrawCommandPacket.NumDynamicBuildRequestElements[PassIndex];
|
|
}
|
|
|
|
// Prepare translucent self shadow uniform buffers.
|
|
for (int32 Index = 0; Index < TranslucentSelfShadowPrimitives.NumPrims; ++Index)
|
|
{
|
|
const int32 PrimitiveIndex = TranslucentSelfShadowPrimitives.Prims[Index];
|
|
|
|
FUniformBufferRHIRef& UniformBuffer = WriteView.TranslucentSelfShadowUniformBufferMap.FindOrAdd(PrimitiveIndex);
|
|
|
|
if (!UniformBuffer)
|
|
{
|
|
FTranslucentSelfShadowUniformParameters Parameters;
|
|
SetupTranslucentSelfShadowUniformParameters(nullptr, Parameters);
|
|
UniformBuffer = FTranslucentSelfShadowUniformParameters::CreateUniformBuffer(Parameters, EUniformBufferUsage::UniformBuffer_SingleFrame);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
static void ComputeAndMarkRelevanceForViewParallel(
|
|
FRHICommandListImmediate& RHICmdList,
|
|
const FScene* Scene,
|
|
FViewInfo& View,
|
|
FViewCommands& ViewCommands,
|
|
uint8 ViewBit,
|
|
FPrimitiveViewMasks& OutHasDynamicMeshElementsMasks,
|
|
FPrimitiveViewMasks& OutHasDynamicEditorMeshElementsMasks
|
|
)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneRenderer_ComputeAndMarkRelevanceForViewParallel);
|
|
|
|
check(OutHasDynamicMeshElementsMasks.Num() == Scene->Primitives.Num());
|
|
|
|
FFrozenSceneViewMatricesGuard FrozenMatricesGuard(View);
|
|
const FMarkRelevantStaticMeshesForViewData ViewData(View);
|
|
|
|
int32 NumMesh = View.StaticMeshVisibilityMap.Num();
|
|
uint8* RESTRICT MarkMasks = (uint8*)FMemStack::Get().Alloc(NumMesh + 31 , 8); // some padding to simplify the high speed transpose
|
|
FMemory::Memzero(MarkMasks, NumMesh + 31);
|
|
|
|
int32 EstimateOfNumPackets = NumMesh / (FRelevancePrimSet<int32>::MaxInputPrims * 4);
|
|
|
|
TArray<FRelevancePacket*,SceneRenderingAllocator> Packets;
|
|
Packets.Reserve(EstimateOfNumPackets);
|
|
|
|
bool WillExecuteInParallel = FApp::ShouldUseThreadingForPerformance() && CVarParallelInitViews.GetValueOnRenderThread() > 0 && IsInActualRenderingThread();
|
|
|
|
{
|
|
FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap);
|
|
if (BitIt)
|
|
{
|
|
|
|
FRelevancePacket* Packet = new(FMemStack::Get()) FRelevancePacket(
|
|
RHICmdList,
|
|
Scene,
|
|
View,
|
|
ViewCommands,
|
|
ViewBit,
|
|
ViewData,
|
|
OutHasDynamicMeshElementsMasks,
|
|
OutHasDynamicEditorMeshElementsMasks,
|
|
MarkMasks);
|
|
Packets.Add(Packet);
|
|
|
|
while (1)
|
|
{
|
|
Packet->Input.AddPrim(BitIt.GetIndex());
|
|
++BitIt;
|
|
if (Packet->Input.IsFull() || !BitIt)
|
|
{
|
|
if (!BitIt)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Packet = new(FMemStack::Get()) FRelevancePacket(
|
|
RHICmdList,
|
|
Scene,
|
|
View,
|
|
ViewCommands,
|
|
ViewBit,
|
|
ViewData,
|
|
OutHasDynamicMeshElementsMasks,
|
|
OutHasDynamicEditorMeshElementsMasks,
|
|
MarkMasks);
|
|
Packets.Add(Packet);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_ParallelFor);
|
|
ParallelFor(Packets.Num(),
|
|
[&Packets](int32 Index)
|
|
{
|
|
Packets[Index]->AnyThreadTask();
|
|
},
|
|
!WillExecuteInParallel
|
|
);
|
|
}
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_RenderThreadFinalize);
|
|
|
|
for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++)
|
|
{
|
|
int32 NumVisibleCachedMeshDrawCommands = 0;
|
|
int32 NumDynamicBuildRequests = 0;
|
|
|
|
for (auto Packet : Packets)
|
|
{
|
|
NumVisibleCachedMeshDrawCommands += Packet->DrawCommandPacket.VisibleCachedDrawCommands[PassIndex].Num();
|
|
NumDynamicBuildRequests += Packet->DrawCommandPacket.DynamicBuildRequests[PassIndex].Num();
|
|
}
|
|
|
|
ViewCommands.MeshCommands[PassIndex].Reserve(NumVisibleCachedMeshDrawCommands);
|
|
ViewCommands.DynamicMeshCommandBuildRequests[PassIndex].Reserve(NumDynamicBuildRequests);
|
|
}
|
|
|
|
for (auto Packet : Packets)
|
|
{
|
|
Packet->RenderThreadFinalize();
|
|
Packet->~FRelevancePacket();
|
|
}
|
|
|
|
Packets.Empty();
|
|
}
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_TransposeMeshBits);
|
|
check(View.StaticMeshVisibilityMap.Num() == NumMesh &&
|
|
View.StaticMeshFadeOutDitheredLODMap.Num() == NumMesh &&
|
|
View.StaticMeshFadeInDitheredLODMap.Num() == NumMesh
|
|
);
|
|
uint32* RESTRICT StaticMeshVisibilityMap_Words = View.StaticMeshVisibilityMap.GetData();
|
|
uint32* RESTRICT StaticMeshFadeOutDitheredLODMap_Words = View.StaticMeshFadeOutDitheredLODMap.GetData();
|
|
uint32* RESTRICT StaticMeshFadeInDitheredLODMap_Words = View.StaticMeshFadeInDitheredLODMap.GetData();
|
|
const uint64* RESTRICT MarkMasks64 = (const uint64* RESTRICT)MarkMasks;
|
|
const uint8* RESTRICT MarkMasks8 = MarkMasks;
|
|
for (int32 BaseIndex = 0; BaseIndex < NumMesh; BaseIndex += 32)
|
|
{
|
|
uint32 StaticMeshVisibilityMap_Word = 0;
|
|
uint32 StaticMeshFadeOutDitheredLODMap_Word = 0;
|
|
uint32 StaticMeshFadeInDitheredLODMap_Word = 0;
|
|
uint32 Mask = 1;
|
|
bool bAny = false;
|
|
for (int32 QWordIndex = 0; QWordIndex < 4; QWordIndex++)
|
|
{
|
|
if (*MarkMasks64++)
|
|
{
|
|
for (int32 ByteIndex = 0; ByteIndex < 8; ByteIndex++, Mask <<= 1, MarkMasks8++)
|
|
{
|
|
uint8 MaskMask = *MarkMasks8;
|
|
StaticMeshVisibilityMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshVisibilityMapMask) ? Mask : 0;
|
|
StaticMeshFadeOutDitheredLODMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask) ? Mask : 0;
|
|
StaticMeshFadeInDitheredLODMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask) ? Mask : 0;
|
|
}
|
|
bAny = true;
|
|
}
|
|
else
|
|
{
|
|
MarkMasks8 += 8;
|
|
Mask <<= 8;
|
|
}
|
|
}
|
|
if (bAny)
|
|
{
|
|
checkSlow(!*StaticMeshVisibilityMap_Words && !*StaticMeshFadeOutDitheredLODMap_Words && !*StaticMeshFadeInDitheredLODMap_Words);
|
|
*StaticMeshVisibilityMap_Words = StaticMeshVisibilityMap_Word;
|
|
*StaticMeshFadeOutDitheredLODMap_Words = StaticMeshFadeOutDitheredLODMap_Word;
|
|
*StaticMeshFadeInDitheredLODMap_Words = StaticMeshFadeInDitheredLODMap_Word;
|
|
}
|
|
StaticMeshVisibilityMap_Words++;
|
|
StaticMeshFadeOutDitheredLODMap_Words++;
|
|
StaticMeshFadeInDitheredLODMap_Words++;
|
|
}
|
|
}
|
|
|
|
void ComputeDynamicMeshRelevance(EShadingPath ShadingPath, bool bAddLightmapDensityCommands, const FPrimitiveViewRelevance& ViewRelevance, const FMeshBatchAndRelevance& MeshBatch, FViewInfo& View, FMeshPassMask& PassMask, FPrimitiveSceneInfo* PrimitiveSceneInfo, const FPrimitiveBounds& Bounds)
|
|
{
|
|
const int32 NumElements = MeshBatch.Mesh->Elements.Num();
|
|
|
|
if (ViewRelevance.bDrawRelevance && (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth || ViewRelevance.bRenderInDepthPass))
|
|
{
|
|
PassMask.Set(EMeshPass::DepthPass);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::DepthPass] += NumElements;
|
|
|
|
if (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth)
|
|
{
|
|
PassMask.Set(EMeshPass::BasePass);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::BasePass] += NumElements;
|
|
|
|
if (ViewRelevance.bUsesSkyMaterial)
|
|
{
|
|
PassMask.Set(EMeshPass::SkyPass);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::SkyPass] += NumElements;
|
|
}
|
|
|
|
if (ViewRelevance.bUsesAnisotropy)
|
|
{
|
|
PassMask.Set(EMeshPass::AnisotropyPass);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::AnisotropyPass] += NumElements;
|
|
}
|
|
|
|
if (ShadingPath == EShadingPath::Mobile)
|
|
{
|
|
PassMask.Set(EMeshPass::MobileBasePassCSM);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::MobileBasePassCSM] += NumElements;
|
|
}
|
|
|
|
if (ViewRelevance.bRenderCustomDepth)
|
|
{
|
|
PassMask.Set(EMeshPass::CustomDepth);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::CustomDepth] += NumElements;
|
|
}
|
|
|
|
if (bAddLightmapDensityCommands)
|
|
{
|
|
PassMask.Set(EMeshPass::LightmapDensity);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::LightmapDensity] += NumElements;
|
|
}
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
else if (View.Family->UseDebugViewPS())
|
|
{
|
|
PassMask.Set(EMeshPass::DebugViewMode);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::DebugViewMode] += NumElements;
|
|
}
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
if (View.bAllowTranslucentPrimitivesInHitProxy)
|
|
{
|
|
PassMask.Set(EMeshPass::HitProxy);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::HitProxy] += NumElements;
|
|
}
|
|
else
|
|
{
|
|
PassMask.Set(EMeshPass::HitProxyOpaqueOnly);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::HitProxyOpaqueOnly] += NumElements;
|
|
}
|
|
#endif
|
|
|
|
if (ViewRelevance.bVelocityRelevance)
|
|
{
|
|
PassMask.Set(EMeshPass::Velocity);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::Velocity] += NumElements;
|
|
}
|
|
|
|
if (ViewRelevance.bOutputsTranslucentVelocity)
|
|
{
|
|
PassMask.Set(EMeshPass::TranslucentVelocity);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::TranslucentVelocity] += NumElements;
|
|
}
|
|
|
|
if (ViewRelevance.bUsesSingleLayerWaterMaterial)
|
|
{
|
|
PassMask.Set(EMeshPass::SingleLayerWaterPass);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::SingleLayerWaterPass] += NumElements;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ViewRelevance.HasTranslucency()
|
|
&& !ViewRelevance.bEditorPrimitiveRelevance
|
|
&& ViewRelevance.bRenderInMainPass)
|
|
{
|
|
if (View.Family->AllowTranslucencyAfterDOF())
|
|
{
|
|
if (ViewRelevance.bNormalTranslucency)
|
|
{
|
|
PassMask.Set(EMeshPass::TranslucencyStandard);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::TranslucencyStandard] += NumElements;
|
|
}
|
|
|
|
if (ViewRelevance.bSeparateTranslucency)
|
|
{
|
|
PassMask.Set(EMeshPass::TranslucencyAfterDOF);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::TranslucencyAfterDOF] += NumElements;
|
|
}
|
|
|
|
if (ViewRelevance.bSeparateTranslucencyModulate)
|
|
{
|
|
PassMask.Set(EMeshPass::TranslucencyAfterDOFModulate);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::TranslucencyAfterDOFModulate] += NumElements;
|
|
}
|
|
|
|
if (ViewRelevance.bPostMotionBlurTranslucency)
|
|
{
|
|
PassMask.Set(EMeshPass::TranslucencyAfterMotionBlur);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::TranslucencyAfterMotionBlur] += NumElements;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PassMask.Set(EMeshPass::TranslucencyAll);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::TranslucencyAll] += NumElements;
|
|
}
|
|
|
|
if (ViewRelevance.bTranslucentSurfaceLighting)
|
|
{
|
|
PassMask.Set(EMeshPass::LumenTranslucencyRadianceCacheMark);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::LumenTranslucencyRadianceCacheMark] += NumElements;
|
|
|
|
PassMask.Set(EMeshPass::LumenFrontLayerTranslucencyGBuffer);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::LumenFrontLayerTranslucencyGBuffer] += NumElements;
|
|
}
|
|
|
|
if (ViewRelevance.bDistortion)
|
|
{
|
|
PassMask.Set(EMeshPass::Distortion);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::Distortion] += NumElements;
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (ViewRelevance.bDrawRelevance)
|
|
{
|
|
PassMask.Set(EMeshPass::EditorSelection);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::EditorSelection] += NumElements;
|
|
|
|
PassMask.Set(EMeshPass::EditorLevelInstance);
|
|
View.NumVisibleDynamicMeshElements[EMeshPass::EditorLevelInstance] += NumElements;
|
|
}
|
|
|
|
// Hair strands are not rendered into the base pass (bRenderInMainPass=0) and so this
|
|
// adds a special pass for allowing hair strands to be selectable.
|
|
if (ViewRelevance.bHairStrands)
|
|
{
|
|
const EMeshPass::Type MeshPassType = View.bAllowTranslucentPrimitivesInHitProxy ? EMeshPass::HitProxy : EMeshPass::HitProxyOpaqueOnly;
|
|
PassMask.Set(MeshPassType);
|
|
View.NumVisibleDynamicMeshElements[MeshPassType] += NumElements;
|
|
}
|
|
#endif
|
|
|
|
if (ViewRelevance.bHasVolumeMaterialDomain)
|
|
{
|
|
View.VolumetricMeshBatches.AddUninitialized(1);
|
|
FVolumetricMeshBatch& BatchAndProxy = View.VolumetricMeshBatches.Last();
|
|
BatchAndProxy.Mesh = MeshBatch.Mesh;
|
|
BatchAndProxy.Proxy = MeshBatch.PrimitiveSceneProxy;
|
|
}
|
|
|
|
if (ViewRelevance.bUsesSkyMaterial)
|
|
{
|
|
View.SkyMeshBatches.AddUninitialized(1);
|
|
FSkyMeshBatch& BatchAndProxy = View.SkyMeshBatches.Last();
|
|
BatchAndProxy.Mesh = MeshBatch.Mesh;
|
|
BatchAndProxy.Proxy = MeshBatch.PrimitiveSceneProxy;
|
|
BatchAndProxy.bVisibleInMainPass = ViewRelevance.bRenderInMainPass;
|
|
BatchAndProxy.bVisibleInRealTimeSkyCapture = PrimitiveSceneInfo->bVisibleInRealTimeSkyCapture;
|
|
}
|
|
|
|
if (ViewRelevance.HasTranslucency() && PrimitiveSceneInfo->Proxy->SupportsSortedTriangles())
|
|
{
|
|
View.SortedTrianglesMeshBatches.AddUninitialized(1);
|
|
FSortedTrianglesMeshBatch& BatchAndProxy = View.SortedTrianglesMeshBatches.Last();
|
|
BatchAndProxy.Mesh = MeshBatch.Mesh;
|
|
BatchAndProxy.Proxy = MeshBatch.PrimitiveSceneProxy;
|
|
}
|
|
|
|
if (ViewRelevance.bRenderInMainPass && ViewRelevance.bDecal)
|
|
{
|
|
View.MeshDecalBatches.AddUninitialized(1);
|
|
FMeshDecalBatch& BatchAndProxy = View.MeshDecalBatches.Last();
|
|
BatchAndProxy.Mesh = MeshBatch.Mesh;
|
|
BatchAndProxy.Proxy = MeshBatch.PrimitiveSceneProxy;
|
|
BatchAndProxy.SortKey = MeshBatch.PrimitiveSceneProxy->GetTranslucencySortPriority();
|
|
}
|
|
|
|
const bool bIsHairStrandsCompatible = ViewRelevance.bHairStrands && IsHairStrandsEnabled(EHairStrandsShaderType::All, View.GetShaderPlatform());
|
|
if (bIsHairStrandsCompatible)
|
|
{
|
|
if (HairStrands::IsHairStrandsVF(MeshBatch.Mesh) && HairStrands::IsHairVisible(MeshBatch))
|
|
{
|
|
View.HairStrandsMeshElements.AddUninitialized(1);
|
|
FMeshBatchAndRelevance& BatchAndProxy = View.HairStrandsMeshElements.Last();
|
|
BatchAndProxy = MeshBatch;
|
|
}
|
|
|
|
if (HairStrands::IsHairCardsVF(MeshBatch.Mesh))
|
|
{
|
|
View.HairCardsMeshElements.AddUninitialized(1);
|
|
FMeshBatchAndRelevance& BatchAndProxy = View.HairCardsMeshElements.Last();
|
|
BatchAndProxy = MeshBatch;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSceneRenderer::GatherDynamicMeshElements(
|
|
TArrayView<FViewInfo>& InViews,
|
|
const FScene* InScene,
|
|
const FSceneViewFamily& InViewFamily,
|
|
FGlobalDynamicIndexBuffer& DynamicIndexBuffer,
|
|
FGlobalDynamicVertexBuffer& DynamicVertexBuffer,
|
|
FGlobalDynamicReadBuffer& DynamicReadBuffer,
|
|
const FPrimitiveViewMasks& HasDynamicMeshElementsMasks,
|
|
const FPrimitiveViewMasks& HasDynamicEditorMeshElementsMasks,
|
|
FMeshElementCollector& Collector)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_GetDynamicMeshElements);
|
|
|
|
int32 NumPrimitives = InScene->Primitives.Num();
|
|
check(HasDynamicMeshElementsMasks.Num() == NumPrimitives);
|
|
|
|
int32 ViewCount = InViews.Num();
|
|
{
|
|
Collector.ClearViewMeshArrays();
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < ViewCount; ViewIndex++)
|
|
{
|
|
Collector.AddViewMeshArrays(
|
|
&InViews[ViewIndex],
|
|
&InViews[ViewIndex].DynamicMeshElements,
|
|
&InViews[ViewIndex].SimpleElementCollector,
|
|
&InViews[ViewIndex].DynamicPrimitiveCollector,
|
|
InViewFamily.GetFeatureLevel(),
|
|
&DynamicIndexBuffer,
|
|
&DynamicVertexBuffer,
|
|
&DynamicReadBuffer);
|
|
}
|
|
|
|
const EShadingPath ShadingPath = Scene->GetShadingPath();
|
|
|
|
for (int32 PrimitiveIndex = 0; PrimitiveIndex < NumPrimitives; ++PrimitiveIndex)
|
|
{
|
|
const uint8 ViewMask = HasDynamicMeshElementsMasks[PrimitiveIndex];
|
|
|
|
if (ViewMask != 0)
|
|
{
|
|
// If a mesh is visible in a secondary view, mark it as visible in the primary view
|
|
uint8 ViewMaskFinal = ViewMask;
|
|
for (int32 ViewIndex = 0; ViewIndex < ViewCount; ViewIndex++)
|
|
{
|
|
FViewInfo& View = InViews[ViewIndex];
|
|
if (ViewMask & (1 << ViewIndex) && IStereoRendering::IsASecondaryView(View))
|
|
{
|
|
ViewMaskFinal |= 1 << InViews[ViewIndex].PrimaryViewIndex;
|
|
}
|
|
}
|
|
|
|
FPrimitiveSceneInfo* PrimitiveSceneInfo = InScene->Primitives[PrimitiveIndex];
|
|
const FPrimitiveBounds& Bounds = InScene->PrimitiveBounds[PrimitiveIndex];
|
|
Collector.SetPrimitive(PrimitiveSceneInfo->Proxy, PrimitiveSceneInfo->DefaultDynamicHitProxyId);
|
|
|
|
PrimitiveSceneInfo->Proxy->GetDynamicMeshElements(InViewFamily.Views, InViewFamily, ViewMaskFinal, Collector);
|
|
|
|
// Compute DynamicMeshElementsMeshPassRelevance for this primitive.
|
|
for (int32 ViewIndex = 0; ViewIndex < ViewCount; ViewIndex++)
|
|
{
|
|
if (ViewMaskFinal & (1 << ViewIndex))
|
|
{
|
|
FViewInfo& View = InViews[ViewIndex];
|
|
const bool bAddLightmapDensityCommands = View.Family->EngineShowFlags.LightMapDensity && AllowDebugViewmodes();
|
|
const FPrimitiveViewRelevance& ViewRelevance = View.PrimitiveViewRelevanceMap[PrimitiveIndex];
|
|
|
|
const int32 LastNumDynamicMeshElements = View.DynamicMeshElementsPassRelevance.Num();
|
|
View.DynamicMeshElementsPassRelevance.SetNum(View.DynamicMeshElements.Num());
|
|
|
|
for (int32 ElementIndex = LastNumDynamicMeshElements; ElementIndex < View.DynamicMeshElements.Num(); ++ElementIndex)
|
|
{
|
|
const FMeshBatchAndRelevance& MeshBatch = View.DynamicMeshElements[ElementIndex];
|
|
FMeshPassMask& PassRelevance = View.DynamicMeshElementsPassRelevance[ElementIndex];
|
|
|
|
ComputeDynamicMeshRelevance(ShadingPath, bAddLightmapDensityCommands, ViewRelevance, MeshBatch, View, PassRelevance, PrimitiveSceneInfo, Bounds);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mark DynamicMeshEndIndices end.
|
|
for (int32 ViewIndex = 0; ViewIndex < ViewCount; ViewIndex++)
|
|
{
|
|
InViews[ViewIndex].DynamicMeshEndIndices[PrimitiveIndex] = Collector.GetMeshBatchCount(ViewIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GIsEditor)
|
|
{
|
|
Collector.ClearViewMeshArrays();
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < ViewCount; ViewIndex++)
|
|
{
|
|
Collector.AddViewMeshArrays(
|
|
&InViews[ViewIndex],
|
|
&InViews[ViewIndex].DynamicEditorMeshElements,
|
|
&InViews[ViewIndex].EditorSimpleElementCollector,
|
|
&InViews[ViewIndex].DynamicPrimitiveCollector,
|
|
InViewFamily.GetFeatureLevel(),
|
|
&DynamicIndexBuffer,
|
|
&DynamicVertexBuffer,
|
|
&DynamicReadBuffer);
|
|
}
|
|
|
|
for (int32 PrimitiveIndex = 0; PrimitiveIndex < NumPrimitives; ++PrimitiveIndex)
|
|
{
|
|
const uint8 ViewMask = HasDynamicEditorMeshElementsMasks[PrimitiveIndex];
|
|
|
|
if (ViewMask != 0)
|
|
{
|
|
FPrimitiveSceneInfo* PrimitiveSceneInfo = InScene->Primitives[PrimitiveIndex];
|
|
Collector.SetPrimitive(PrimitiveSceneInfo->Proxy, PrimitiveSceneInfo->DefaultDynamicHitProxyId);
|
|
|
|
PrimitiveSceneInfo->Proxy->GetDynamicMeshElements(InViewFamily.Views, InViewFamily, ViewMask, Collector);
|
|
}
|
|
}
|
|
}
|
|
ActiveViewFamily->MeshCollector.ProcessTasks();
|
|
}
|
|
|
|
/**
|
|
* Helper for InitViews to detect large camera movement, in both angle and position.
|
|
*/
|
|
static bool IsLargeCameraMovement(FSceneView& View, const FMatrix& PrevViewMatrix, const FVector& PrevViewOrigin, float CameraRotationThreshold, float CameraTranslationThreshold)
|
|
{
|
|
float RotationThreshold = FMath::Cos(FMath::DegreesToRadians(CameraRotationThreshold));
|
|
float ViewRightAngle = View.ViewMatrices.GetViewMatrix().GetColumn(0) | PrevViewMatrix.GetColumn(0);
|
|
float ViewUpAngle = View.ViewMatrices.GetViewMatrix().GetColumn(1) | PrevViewMatrix.GetColumn(1);
|
|
float ViewDirectionAngle = View.ViewMatrices.GetViewMatrix().GetColumn(2) | PrevViewMatrix.GetColumn(2);
|
|
|
|
FVector Distance = FVector(View.ViewMatrices.GetViewOrigin()) - PrevViewOrigin;
|
|
return
|
|
ViewRightAngle < RotationThreshold ||
|
|
ViewUpAngle < RotationThreshold ||
|
|
ViewDirectionAngle < RotationThreshold ||
|
|
Distance.SizeSquared() > CameraTranslationThreshold * CameraTranslationThreshold;
|
|
}
|
|
|
|
void FSceneRenderer::PreVisibilityFrameSetup(FRDGBuilder& GraphBuilder, const FSceneTexturesConfig& SceneTexturesConfig)
|
|
{
|
|
FRHICommandListImmediate& RHICmdList = GraphBuilder.RHICmdList;
|
|
|
|
// Notify the RHI we are beginning to render a scene.
|
|
RHICmdList.BeginScene();
|
|
|
|
{
|
|
static auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.DoLazyStaticMeshUpdate"));
|
|
const bool DoLazyStaticMeshUpdate = (CVar->GetInt() && !GIsEditor);
|
|
|
|
if (DoLazyStaticMeshUpdate)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PreVisibilityFrameSetup_EvictionForLazyStaticMeshUpdate);
|
|
static int32 RollingRemoveIndex = 0;
|
|
static int32 RollingPassShrinkIndex = 0;
|
|
if (RollingRemoveIndex >= Scene->Primitives.Num())
|
|
{
|
|
RollingRemoveIndex = 0;
|
|
RollingPassShrinkIndex++;
|
|
if (RollingPassShrinkIndex >= UE_ARRAY_COUNT(Scene->CachedDrawLists))
|
|
{
|
|
RollingPassShrinkIndex = 0;
|
|
}
|
|
// Periodically shrink the SparseArray containing cached mesh draw commands which we are causing to be regenerated with UpdateStaticMeshes
|
|
Scene->CachedDrawLists[RollingPassShrinkIndex].MeshDrawCommands.Shrink();
|
|
}
|
|
const int32 NumRemovedPerFrame = 10;
|
|
TArray<FPrimitiveSceneInfo*, TInlineAllocator<10>> SceneInfos;
|
|
for (int32 NumRemoved = 0; NumRemoved < NumRemovedPerFrame && RollingRemoveIndex < Scene->Primitives.Num(); NumRemoved++, RollingRemoveIndex++)
|
|
{
|
|
SceneInfos.Add(Scene->Primitives[RollingRemoveIndex]);
|
|
}
|
|
FPrimitiveSceneInfo::UpdateStaticMeshes(RHICmdList, Scene, SceneInfos, EUpdateStaticMeshFlags::AllCommands, false);
|
|
}
|
|
}
|
|
|
|
if (Views.Num() > 0 && !ActiveViewFamily->EngineShowFlags.HitProxies)
|
|
{
|
|
FHairStrandsBookmarkParameters Parameters = CreateHairStrandsBookmarkParameters(Scene, Views);
|
|
if (Parameters.HasInstances())
|
|
{
|
|
RunHairStrandsBookmark(GraphBuilder, EHairStrandsBookmark::ProcessLODSelection, Parameters);
|
|
}
|
|
}
|
|
|
|
if (IsHairStrandsEnabled(EHairStrandsShaderType::All, Scene->GetShaderPlatform()) && Views.Num() > 0 && !ActiveViewFamily->EngineShowFlags.HitProxies)
|
|
{
|
|
// If we are rendering from scene capture we don't need to run another time the hair bookmarks.
|
|
if (Views[0].AllowGPUParticleUpdate())
|
|
{
|
|
FHairStrandsBookmarkParameters Parameters = CreateHairStrandsBookmarkParameters(Scene, Views);
|
|
RunHairStrandsBookmark(GraphBuilder, EHairStrandsBookmark::ProcessGuideInterpolation, Parameters);
|
|
}
|
|
}
|
|
|
|
// Notify the FX system that the scene is about to perform visibility checks.
|
|
|
|
if (FXSystem && Views.IsValidIndex(0))
|
|
{
|
|
FXSystem->PreInitViews(GraphBuilder, Views[0].AllowGPUParticleUpdate() && !ActiveViewFamily->EngineShowFlags.HitProxies);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
// Draw lines to lights affecting this mesh if its selected.
|
|
if (ActiveViewFamily->EngineShowFlags.LightInfluences)
|
|
{
|
|
for (TConstSetBitIterator<> It(Scene->PrimitivesSelected); It; ++It)
|
|
{
|
|
const FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene->Primitives[It.GetIndex()];
|
|
FLightPrimitiveInteraction *LightList = PrimitiveSceneInfo->LightList;
|
|
while (LightList)
|
|
{
|
|
const FLightSceneInfo* LightSceneInfo = LightList->GetLight();
|
|
|
|
bool bDynamic = true;
|
|
bool bRelevant = false;
|
|
bool bLightMapped = true;
|
|
bool bShadowMapped = false;
|
|
PrimitiveSceneInfo->Proxy->GetLightRelevance(LightSceneInfo->Proxy, bDynamic, bRelevant, bLightMapped, bShadowMapped);
|
|
|
|
if (bRelevant)
|
|
{
|
|
// Draw blue for light-mapped lights and orange for dynamic lights
|
|
const FColor LineColor = bLightMapped ? FColor(0,140,255) : FColor(255,140,0);
|
|
for (int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
FViewElementPDI LightInfluencesPDI(&View,nullptr,&View.DynamicPrimitiveCollector);
|
|
LightInfluencesPDI.DrawLine(PrimitiveSceneInfo->Proxy->GetBounds().Origin, LightSceneInfo->Proxy->GetLightToWorld().GetOrigin(), LineColor, SDPG_World);
|
|
}
|
|
}
|
|
LightList = LightList->GetNextLight();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if UE_BUILD_SHIPPING
|
|
const bool bFreezeTemporalHistories = false;
|
|
const bool bFreezeTemporalSequences = false;
|
|
#else
|
|
bool bFreezeTemporalHistories = CVarFreezeTemporalHistories.GetValueOnRenderThread() != 0;
|
|
bool bFreezeTemporalSequences = bFreezeTemporalHistories || CVarFreezeTemporalSequences.GetValueOnRenderThread() != 0;
|
|
#endif
|
|
|
|
// Load this field once so it has a consistent value for all views (and to avoid the atomic load in the loop).
|
|
// While the value may not be perfectly in sync when we render other view families, this is ok as this
|
|
// invalidation mechanism is only used for interactive rendering where we expect to be constantly drawing the scene.
|
|
// Therefore it is acceptable for some view families to be a frame or so behind others.
|
|
uint32 CurrentPathTracingInvalidationCounter = Scene->PathTracingInvalidationCounter.Load();
|
|
|
|
// Setup motion blur parameters (also check for camera movement thresholds)
|
|
for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
FSceneViewState* ViewState = View.ViewState;
|
|
|
|
check(View.VerifyMembersChecks());
|
|
|
|
// Once per render increment the occlusion frame counter.
|
|
if (ViewState)
|
|
{
|
|
ViewState->OcclusionFrameCounter++;
|
|
}
|
|
|
|
// HighResScreenshot should get best results so we don't do the occlusion optimization based on the former frame
|
|
extern bool GIsHighResScreenshot;
|
|
const bool bIsHitTesting = ActiveViewFamily->EngineShowFlags.HitProxies;
|
|
// Don't test occlusion queries in collision viewmode as they can be bigger then the rendering bounds.
|
|
const bool bCollisionView = ActiveViewFamily->EngineShowFlags.CollisionVisibility || ActiveViewFamily->EngineShowFlags.CollisionPawn;
|
|
if (GIsHighResScreenshot || !DoOcclusionQueries() || bIsHitTesting || bCollisionView || ActiveViewFamily->EngineShowFlags.DisableOcclusionQueries)
|
|
{
|
|
View.bDisableQuerySubmissions = true;
|
|
View.bIgnoreExistingQueries = true;
|
|
}
|
|
|
|
// set up the screen area for occlusion
|
|
{
|
|
float OcclusionPixelMultiplier = 1.0f;
|
|
if (UseDownsampledOcclusionQueries())
|
|
{
|
|
OcclusionPixelMultiplier = 1.0f / static_cast<float>(FMath::Square(SceneTexturesConfig.SmallDepthDownsampleFactor));
|
|
}
|
|
float NumPossiblePixels = static_cast<float>(View.ViewRect.Width() * View.ViewRect.Height()) * OcclusionPixelMultiplier;
|
|
View.OneOverNumPossiblePixels = NumPossiblePixels > 0.0 ? 1.0f / NumPossiblePixels : 0.0f;
|
|
}
|
|
|
|
// Still need no jitter to be set for temporal feedback on SSR (it is enabled even when temporal AA is off).
|
|
check(View.TemporalJitterPixels.X == 0.0f);
|
|
check(View.TemporalJitterPixels.Y == 0.0f);
|
|
|
|
// Cache the projection matrix b
|
|
// Cache the projection matrix before AA is applied
|
|
View.ViewMatrices.SaveProjectionNoAAMatrix();
|
|
|
|
if (ViewState)
|
|
{
|
|
check(View.bStatePrevViewInfoIsReadOnly);
|
|
View.bStatePrevViewInfoIsReadOnly = ActiveViewFamily->bWorldIsPaused || ActiveViewFamily->EngineShowFlags.HitProxies || bFreezeTemporalHistories;
|
|
|
|
ViewState->SetupDistanceFieldTemporalOffset(*ActiveViewFamily);
|
|
|
|
if (!View.bStatePrevViewInfoIsReadOnly && !bFreezeTemporalSequences)
|
|
{
|
|
ViewState->FrameIndex++;
|
|
}
|
|
|
|
if (View.OverrideFrameIndexValue.IsSet())
|
|
{
|
|
ViewState->FrameIndex = View.OverrideFrameIndexValue.GetValue();
|
|
}
|
|
}
|
|
|
|
// Subpixel jitter for temporal AA
|
|
int32 CVarTemporalAASamplesValue = CVarTemporalAASamples.GetValueOnRenderThread();
|
|
|
|
EMainTAAPassConfig TAAConfig = ITemporalUpscaler::GetMainTAAPassConfig(View);
|
|
|
|
bool bTemporalUpsampling = View.PrimaryScreenPercentageMethod == EPrimaryScreenPercentageMethod::TemporalUpscale;
|
|
|
|
// Apply a sub pixel offset to the view.
|
|
if (IsTemporalAccumulationBasedMethod(View.AntiAliasingMethod) && ViewState && (CVarTemporalAASamplesValue > 0 || bTemporalUpsampling) && View.bAllowTemporalJitter)
|
|
{
|
|
float EffectivePrimaryResolutionFraction = float(View.ViewRect.Width()) / float(View.GetSecondaryViewRectSize().X);
|
|
|
|
// Compute number of TAA samples.
|
|
int32 TemporalAASamples;
|
|
{
|
|
if (TAAConfig == EMainTAAPassConfig::TSR)
|
|
{
|
|
// Force the number of AA sample to make sure the quality doesn't get
|
|
// compromised by previously set settings for Gen4 TAA
|
|
TemporalAASamples = 8;
|
|
}
|
|
else
|
|
{
|
|
TemporalAASamples = FMath::Clamp(CVarTemporalAASamplesValue, 1, 255);
|
|
}
|
|
|
|
if (bTemporalUpsampling)
|
|
{
|
|
// When doing TAA upsample with screen percentage < 100%, we need extra temporal samples to have a
|
|
// constant temporal sample density for final output pixels to avoid output pixel aligned converging issues.
|
|
TemporalAASamples = FMath::RoundToInt(float(TemporalAASamples) * FMath::Max(1.f, 1.f / (EffectivePrimaryResolutionFraction * EffectivePrimaryResolutionFraction)));
|
|
}
|
|
else if (CVarTemporalAASamplesValue == 5)
|
|
{
|
|
TemporalAASamples = 4;
|
|
}
|
|
|
|
// Use immediately higher prime number to break up coherence between the TAA jitter sequence and any
|
|
// other random signal that are power of two of View.StateFrameIndex
|
|
if (TAAConfig == EMainTAAPassConfig::TSR)
|
|
{
|
|
static const int8 kFirstPrimeNumbers[25] = {
|
|
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
|
|
};
|
|
|
|
for (int32 PrimeNumberId = 4; PrimeNumberId < UE_ARRAY_COUNT(kFirstPrimeNumbers); PrimeNumberId++)
|
|
{
|
|
if (int32(kFirstPrimeNumbers[PrimeNumberId]) >= TemporalAASamples)
|
|
{
|
|
TemporalAASamples = int32(kFirstPrimeNumbers[PrimeNumberId]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute the new sample index in the temporal sequence.
|
|
int32 TemporalSampleIndex = ViewState->TemporalAASampleIndex + 1;
|
|
if (TemporalSampleIndex >= TemporalAASamples || View.bCameraCut)
|
|
{
|
|
TemporalSampleIndex = 0;
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (CVarTAADebugOverrideTemporalIndex.GetValueOnRenderThread() >= 0)
|
|
{
|
|
TemporalSampleIndex = CVarTAADebugOverrideTemporalIndex.GetValueOnRenderThread();
|
|
}
|
|
#endif
|
|
|
|
// Updates view state.
|
|
if (!View.bStatePrevViewInfoIsReadOnly && !bFreezeTemporalSequences)
|
|
{
|
|
ViewState->TemporalAASampleIndex = TemporalSampleIndex;
|
|
}
|
|
|
|
// Choose sub pixel sample coordinate in the temporal sequence.
|
|
float SampleX, SampleY;
|
|
if (View.PrimaryScreenPercentageMethod == EPrimaryScreenPercentageMethod::TemporalUpscale)
|
|
{
|
|
// Uniformly distribute temporal jittering in [-.5; .5], because there is no longer any alignement of input and output pixels.
|
|
SampleX = Halton(TemporalSampleIndex + 1, 2) - 0.5f;
|
|
SampleY = Halton(TemporalSampleIndex + 1, 3) - 0.5f;
|
|
|
|
View.MaterialTextureMipBias = -(FMath::Max(-FMath::Log2(EffectivePrimaryResolutionFraction), 0.0f) ) + CVarMinAutomaticViewMipBiasOffset.GetValueOnRenderThread();
|
|
View.MaterialTextureMipBias = FMath::Max(View.MaterialTextureMipBias, CVarMinAutomaticViewMipBias.GetValueOnRenderThread());
|
|
}
|
|
else if( CVarTemporalAASamplesValue == 2 )
|
|
{
|
|
// 2xMSAA
|
|
// Pattern docs: http://msdn.microsoft.com/en-us/library/windows/desktop/ff476218(v=vs.85).aspx
|
|
// N.
|
|
// .S
|
|
float SamplesX[] = { -4.0f/16.0f, 4.0/16.0f };
|
|
float SamplesY[] = { -4.0f/16.0f, 4.0/16.0f };
|
|
check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX));
|
|
SampleX = SamplesX[ TemporalSampleIndex ];
|
|
SampleY = SamplesY[ TemporalSampleIndex ];
|
|
}
|
|
else if( CVarTemporalAASamplesValue == 3 )
|
|
{
|
|
// 3xMSAA
|
|
// A..
|
|
// ..B
|
|
// .C.
|
|
// Rolling circle pattern (A,B,C).
|
|
float SamplesX[] = { -2.0f/3.0f, 2.0/3.0f, 0.0/3.0f };
|
|
float SamplesY[] = { -2.0f/3.0f, 0.0/3.0f, 2.0/3.0f };
|
|
check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX));
|
|
SampleX = SamplesX[ TemporalSampleIndex ];
|
|
SampleY = SamplesY[ TemporalSampleIndex ];
|
|
}
|
|
else if( CVarTemporalAASamplesValue == 4 )
|
|
{
|
|
// 4xMSAA
|
|
// Pattern docs: http://msdn.microsoft.com/en-us/library/windows/desktop/ff476218(v=vs.85).aspx
|
|
// .N..
|
|
// ...E
|
|
// W...
|
|
// ..S.
|
|
// Rolling circle pattern (N,E,S,W).
|
|
float SamplesX[] = { -2.0f/16.0f, 6.0/16.0f, 2.0/16.0f, -6.0/16.0f };
|
|
float SamplesY[] = { -6.0f/16.0f, -2.0/16.0f, 6.0/16.0f, 2.0/16.0f };
|
|
check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX));
|
|
SampleX = SamplesX[ TemporalSampleIndex ];
|
|
SampleY = SamplesY[ TemporalSampleIndex ];
|
|
}
|
|
else if( CVarTemporalAASamplesValue == 5 )
|
|
{
|
|
// Compressed 4 sample pattern on same vertical and horizontal line (less temporal flicker).
|
|
// Compressed 1/2 works better than correct 2/3 (reduced temporal flicker).
|
|
// . N .
|
|
// W . E
|
|
// . S .
|
|
// Rolling circle pattern (N,E,S,W).
|
|
float SamplesX[] = { 0.0f/2.0f, 1.0/2.0f, 0.0/2.0f, -1.0/2.0f };
|
|
float SamplesY[] = { -1.0f/2.0f, 0.0/2.0f, 1.0/2.0f, 0.0/2.0f };
|
|
check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX));
|
|
SampleX = SamplesX[ TemporalSampleIndex ];
|
|
SampleY = SamplesY[ TemporalSampleIndex ];
|
|
}
|
|
else
|
|
{
|
|
float u1 = Halton( TemporalSampleIndex + 1, 2 );
|
|
float u2 = Halton( TemporalSampleIndex + 1, 3 );
|
|
|
|
// Generates samples in normal distribution
|
|
// exp( x^2 / Sigma^2 )
|
|
|
|
static auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.TemporalAAFilterSize"));
|
|
float FilterSize = CVar->GetFloat();
|
|
|
|
// Scale distribution to set non-unit variance
|
|
// Variance = Sigma^2
|
|
float Sigma = 0.47f * FilterSize;
|
|
|
|
// Window to [-0.5, 0.5] output
|
|
// Without windowing we could generate samples far away on the infinite tails.
|
|
float OutWindow = 0.5f;
|
|
float InWindow = FMath::Exp( -0.5 * FMath::Square( OutWindow / Sigma ) );
|
|
|
|
// Box-Muller transform
|
|
float Theta = 2.0f * PI * u2;
|
|
float r = Sigma * FMath::Sqrt( -2.0f * FMath::Loge( (1.0f - u1) * InWindow + u1 ) );
|
|
|
|
SampleX = r * FMath::Cos( Theta );
|
|
SampleY = r * FMath::Sin( Theta );
|
|
}
|
|
|
|
View.TemporalJitterSequenceLength = TemporalAASamples;
|
|
View.TemporalJitterIndex = TemporalSampleIndex;
|
|
View.TemporalJitterPixels.X = SampleX;
|
|
View.TemporalJitterPixels.Y = SampleY;
|
|
|
|
View.ViewMatrices.HackAddTemporalAAProjectionJitter(FVector2D(SampleX * 2.0f / View.ViewRect.Width(), SampleY * -2.0f / View.ViewRect.Height()));
|
|
}
|
|
|
|
// Setup a new FPreviousViewInfo from current frame infos.
|
|
FPreviousViewInfo NewPrevViewInfo;
|
|
{
|
|
NewPrevViewInfo.ViewRect = View.ViewRect;
|
|
NewPrevViewInfo.ViewMatrices = View.ViewMatrices;
|
|
NewPrevViewInfo.ViewRect = View.ViewRect;
|
|
}
|
|
|
|
if ( ViewState )
|
|
{
|
|
// update previous frame matrices in case world origin was rebased on this frame
|
|
if (!View.OriginOffsetThisFrame.IsZero())
|
|
{
|
|
ViewState->PrevFrameViewInfo.ViewMatrices.ApplyWorldOffset(View.OriginOffsetThisFrame);
|
|
}
|
|
|
|
// determine if we are initializing or we should reset the persistent state
|
|
const float DeltaTime = View.Family->Time.GetRealTimeSeconds() - ViewState->LastRenderTime;
|
|
const bool bFirstFrameOrTimeWasReset = DeltaTime < -0.0001f || ViewState->LastRenderTime < 0.0001f;
|
|
const bool bIsLargeCameraMovement = IsLargeCameraMovement(
|
|
View,
|
|
ViewState->PrevFrameViewInfo.ViewMatrices.GetViewMatrix(),
|
|
ViewState->PrevFrameViewInfo.ViewMatrices.GetViewOrigin(),
|
|
75.0f, GCameraCutTranslationThreshold);
|
|
const bool bResetCamera = (bFirstFrameOrTimeWasReset || View.bCameraCut || bIsLargeCameraMovement || View.bForceCameraVisibilityReset);
|
|
|
|
#if RHI_RAYTRACING
|
|
// Note: 0.18 deg is the minimum angle for avoiding numerical precision issue (which would cause constant invalidation)
|
|
const bool bIsCameraMove = IsLargeCameraMovement(
|
|
View,
|
|
ViewState->PrevFrameViewInfo.ViewMatrices.GetViewMatrix(),
|
|
ViewState->PrevFrameViewInfo.ViewMatrices.GetViewOrigin(),
|
|
0.18f /*degree*/, 0.1f /*cm*/);
|
|
const bool bIsProjMatrixDifferent = View.ViewMatrices.GetProjectionNoAAMatrix() != View.ViewState->PrevFrameViewInfo.ViewMatrices.GetProjectionNoAAMatrix();
|
|
|
|
if (View.bIsOfflineRender)
|
|
{
|
|
// In the offline context, we want precise control over when to restart the path tracer's accumulation to allow for motion blur
|
|
// So we use the camera cut signal only. In particular - we should not use bForceCameraVisibilityReset since this has
|
|
// interactions with the motion blur post process effect in tiled rendering (see comment below).
|
|
if (View.bCameraCut || View.bForcePathTracerReset)
|
|
{
|
|
ViewState->PathTracingInvalidate();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// for interactive usage - any movement or scene change should restart the path tracer
|
|
|
|
// For each view, we remember what the invalidation counter was set to last time we were here so we can catch all changes
|
|
bool bNeedsInvalidation = ViewState->PathTracingInvalidationCounter != CurrentPathTracingInvalidationCounter;
|
|
ViewState->PathTracingInvalidationCounter = CurrentPathTracingInvalidationCounter;
|
|
if (bNeedsInvalidation ||
|
|
bResetCamera ||
|
|
bIsProjMatrixDifferent ||
|
|
bIsCameraMove ||
|
|
View.bForcePathTracerReset)
|
|
{
|
|
ViewState->PathTracingInvalidate();
|
|
}
|
|
}
|
|
|
|
#endif // RHI_RAYTRACING
|
|
|
|
if (bResetCamera)
|
|
{
|
|
View.PrevViewInfo = NewPrevViewInfo;
|
|
|
|
// PT: If the motion blur shader is the last shader in the post-processing chain then it is the one that is
|
|
// adjusting for the viewport offset. So it is always required and we can't just disable the work the
|
|
// shader does. The correct fix would be to disable the effect when we don't need it and to properly mark
|
|
// the uber-postprocessing effect as the last effect in the chain.
|
|
|
|
View.bPrevTransformsReset = true;
|
|
}
|
|
else
|
|
{
|
|
View.PrevViewInfo = ViewState->PrevFrameViewInfo;
|
|
}
|
|
|
|
// Replace previous view info of the view state with this frame, clearing out references over render target.
|
|
if (!View.bStatePrevViewInfoIsReadOnly)
|
|
{
|
|
ViewState->PrevFrameViewInfo = NewPrevViewInfo;
|
|
}
|
|
|
|
// If the view has a previous view transform, then overwrite the previous view info for the _current_ frame.
|
|
if (View.PreviousViewTransform.IsSet())
|
|
{
|
|
// Note that we must ensure this transform ends up in ViewState->PrevFrameViewInfo else it will be used to calculate the next frame's motion vectors as well
|
|
View.PrevViewInfo.ViewMatrices.UpdateViewMatrix(View.PreviousViewTransform->GetTranslation(), View.PreviousViewTransform->GetRotation().Rotator());
|
|
}
|
|
|
|
// detect conditions where we should reset occlusion queries
|
|
if (bFirstFrameOrTimeWasReset ||
|
|
ViewState->LastRenderTime + GEngine->PrimitiveProbablyVisibleTime < View.Family->Time.GetRealTimeSeconds() ||
|
|
View.bCameraCut ||
|
|
View.bForceCameraVisibilityReset ||
|
|
IsLargeCameraMovement(
|
|
View,
|
|
FMatrix(ViewState->PrevViewMatrixForOcclusionQuery),
|
|
ViewState->PrevViewOriginForOcclusionQuery,
|
|
GEngine->CameraRotationThreshold, GEngine->CameraTranslationThreshold))
|
|
{
|
|
View.bIgnoreExistingQueries = true;
|
|
View.bDisableDistanceBasedFadeTransitions = true;
|
|
}
|
|
|
|
// Turn on/off round-robin occlusion querying in the ViewState
|
|
static const auto CVarRROCC = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.RoundRobinOcclusion"));
|
|
const bool bEnableRoundRobin = CVarRROCC ? (CVarRROCC->GetValueOnAnyThread() != false) : false;
|
|
if (bEnableRoundRobin != ViewState->IsRoundRobinEnabled())
|
|
{
|
|
ViewState->UpdateRoundRobin(bEnableRoundRobin);
|
|
View.bIgnoreExistingQueries = true;
|
|
}
|
|
|
|
ViewState->PrevViewMatrixForOcclusionQuery = FMatrix44f(View.ViewMatrices.GetViewMatrix()); // LWC_TODO: Precision loss
|
|
ViewState->PrevViewOriginForOcclusionQuery = View.ViewMatrices.GetViewOrigin();
|
|
|
|
// store old view matrix and detect conditions where we should reset motion blur
|
|
#if RHI_RAYTRACING
|
|
{
|
|
if (bResetCamera || IsLargeCameraMovement(View, ViewState->PrevFrameViewInfo.ViewMatrices.GetViewMatrix(), ViewState->PrevFrameViewInfo.ViewMatrices.GetViewOrigin(), 0.1f, 0.1f))
|
|
{
|
|
ViewState->RayTracingNumIterations = 1;
|
|
}
|
|
else
|
|
{
|
|
ViewState->RayTracingNumIterations++;
|
|
}
|
|
}
|
|
#endif // RHI_RAYTRACING
|
|
|
|
// we don't use DeltaTime as it can be 0 (in editor) and is computed by subtracting floats (loses precision over time)
|
|
// Clamp DeltaWorldTime to reasonable values for the purposes of motion blur, things like TimeDilation can make it very small
|
|
// Offline renders always control the timestep for the view and always need the timescales calculated.
|
|
if (!ActiveViewFamily->bWorldIsPaused || View.bIsOfflineRender)
|
|
{
|
|
ViewState->UpdateMotionBlurTimeScale(View);
|
|
}
|
|
|
|
|
|
ViewState->PrevFrameNumber = ViewState->PendingPrevFrameNumber;
|
|
ViewState->PendingPrevFrameNumber = View.Family->FrameNumber;
|
|
|
|
// This finishes the update of view state
|
|
ViewState->UpdateLastRenderTime(*View.Family);
|
|
|
|
ViewState->UpdateTemporalLODTransition(View);
|
|
}
|
|
else
|
|
{
|
|
// Without a viewstate, we just assume that camera has not moved.
|
|
View.PrevViewInfo = NewPrevViewInfo;
|
|
}
|
|
}
|
|
|
|
// Setup global dither fade in and fade out uniform buffers.
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
|
|
FDitherUniformShaderParameters DitherUniformShaderParameters;
|
|
DitherUniformShaderParameters.LODFactor = View.GetTemporalLODTransition();
|
|
View.DitherFadeOutUniformBuffer = FDitherUniformBufferRef::CreateUniformBufferImmediate(DitherUniformShaderParameters, UniformBuffer_SingleFrame);
|
|
|
|
DitherUniformShaderParameters.LODFactor = View.GetTemporalLODTransition() - 1.0f;
|
|
View.DitherFadeInUniformBuffer = FDitherUniformBufferRef::CreateUniformBufferImmediate(DitherUniformShaderParameters, UniformBuffer_SingleFrame);
|
|
}
|
|
}
|
|
|
|
void FSceneViewState::UpdateMotionBlurTimeScale(const FViewInfo& View)
|
|
{
|
|
const int32 MotionBlurTargetFPS = View.FinalPostProcessSettings.MotionBlurTargetFPS;
|
|
|
|
// Ensure we can divide by the Delta Time later without a divide by zero.
|
|
float DeltaRealTime = FMath::Max(View.Family->Time.GetDeltaRealTimeSeconds(), SMALL_NUMBER);
|
|
|
|
// Track the current FPS by using an exponential moving average of the current delta time.
|
|
if (MotionBlurTargetFPS <= 0)
|
|
{
|
|
// Keep motion vector lengths stable for paused sequencer frames.
|
|
if (GetSequencerState() == ESS_Paused)
|
|
{
|
|
// Reset the moving average to the current delta time.
|
|
MotionBlurTargetDeltaTime = DeltaRealTime;
|
|
}
|
|
else
|
|
{
|
|
// Smooth the target delta time using a moving average.
|
|
MotionBlurTargetDeltaTime = FMath::Lerp(MotionBlurTargetDeltaTime, DeltaRealTime, 0.1f);
|
|
}
|
|
}
|
|
else // Track a fixed target FPS.
|
|
{
|
|
// Keep motion vector lengths stable for paused sequencer frames. Assumes a 60 FPS tick.
|
|
// Tuned for content compatibility with existing content when target is the default 30 FPS.
|
|
if (GetSequencerState() == ESS_Paused)
|
|
{
|
|
DeltaRealTime = 1.0f / 60.0f;
|
|
}
|
|
|
|
|
|
MotionBlurTargetDeltaTime = 1.0f / static_cast<float>(MotionBlurTargetFPS);
|
|
}
|
|
|
|
MotionBlurTimeScale = MotionBlurTargetDeltaTime / DeltaRealTime;
|
|
}
|
|
|
|
void UpdateReflectionSceneData(FScene* Scene)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_UpdateReflectionSceneData)
|
|
SCOPED_NAMED_EVENT(UpdateReflectionScene, FColor::Red);
|
|
|
|
|
|
FReflectionEnvironmentSceneData& ReflectionSceneData = Scene->ReflectionSceneData;
|
|
|
|
ReflectionSceneData.SortedCaptures.Reset(ReflectionSceneData.RegisteredReflectionCaptures.Num());
|
|
ReflectionSceneData.NumBoxCaptures = 0;
|
|
ReflectionSceneData.NumSphereCaptures = 0;
|
|
|
|
const int32 MaxCubemaps = ReflectionSceneData.CubemapArray.GetMaxCubemaps();
|
|
int32_t PlatformMaxNumReflectionCaptures = FMath::Min(FMath::FloorToInt(GMaxTextureArrayLayers / 6.0f), GMaxNumReflectionCaptures);
|
|
|
|
// Pack visible reflection captures into the uniform buffer, each with an index to its cubemap array entry.
|
|
// GPUScene primitive data stores closest reflection capture as index into this buffer, so this index which must be invalidate every time OutSortData contents change.
|
|
for (int32 ReflectionProxyIndex = 0; ReflectionProxyIndex < ReflectionSceneData.RegisteredReflectionCaptures.Num() && ReflectionSceneData.SortedCaptures.Num() < PlatformMaxNumReflectionCaptures; ReflectionProxyIndex++)
|
|
{
|
|
FReflectionCaptureProxy* CurrentCapture = ReflectionSceneData.RegisteredReflectionCaptures[ReflectionProxyIndex];
|
|
|
|
FReflectionCaptureSortData NewSortEntry;
|
|
|
|
NewSortEntry.CubemapIndex = -1;
|
|
NewSortEntry.CaptureOffsetAndAverageBrightness = FVector4f(CurrentCapture->CaptureOffset, 1.0f);
|
|
NewSortEntry.CaptureProxy = CurrentCapture;
|
|
if (SupportsTextureCubeArray(Scene->GetFeatureLevel()))
|
|
{
|
|
FCaptureComponentSceneState* ComponentStatePtr = ReflectionSceneData.AllocatedReflectionCaptureState.Find(CurrentCapture->Component);
|
|
if (!ComponentStatePtr)
|
|
{
|
|
// Skip reflection captures without built data to upload
|
|
continue;
|
|
}
|
|
|
|
NewSortEntry.CubemapIndex = ComponentStatePtr->CubemapIndex;
|
|
check(NewSortEntry.CubemapIndex < MaxCubemaps || NewSortEntry.CubemapIndex == 0);
|
|
NewSortEntry.CaptureOffsetAndAverageBrightness.W = ComponentStatePtr->AverageBrightness;
|
|
}
|
|
|
|
NewSortEntry.Guid = CurrentCapture->Guid;
|
|
NewSortEntry.RelativePosition = CurrentCapture->RelativePosition;
|
|
NewSortEntry.TilePosition = CurrentCapture->TilePosition;
|
|
NewSortEntry.Radius = CurrentCapture->InfluenceRadius;
|
|
float ShapeTypeValue = (float)CurrentCapture->Shape;
|
|
NewSortEntry.CaptureProperties = FVector4f(CurrentCapture->Brightness, NewSortEntry.CubemapIndex, ShapeTypeValue, 0);
|
|
|
|
if (CurrentCapture->Shape == EReflectionCaptureShape::Plane)
|
|
{
|
|
//planes count as boxes in the compute shader.
|
|
++ReflectionSceneData.NumBoxCaptures;
|
|
NewSortEntry.BoxTransform = FMatrix44f(
|
|
FPlane4f(CurrentCapture->LocalReflectionPlane),
|
|
FPlane4f((FVector4f)CurrentCapture->ReflectionXAxisAndYScale), // LWC_TODO: precision loss
|
|
FPlane4f(0, 0, 0, 0),
|
|
FPlane4f(0, 0, 0, 0));
|
|
|
|
NewSortEntry.BoxScales = FVector4f(0);
|
|
}
|
|
else if (CurrentCapture->Shape == EReflectionCaptureShape::Sphere)
|
|
{
|
|
++ReflectionSceneData.NumSphereCaptures;
|
|
}
|
|
else
|
|
{
|
|
++ReflectionSceneData.NumBoxCaptures;
|
|
NewSortEntry.BoxTransform = CurrentCapture->BoxTransform;
|
|
NewSortEntry.BoxScales = FVector4f(CurrentCapture->BoxScales, CurrentCapture->BoxTransitionDistance);
|
|
}
|
|
|
|
ReflectionSceneData.SortedCaptures.Add(NewSortEntry);
|
|
}
|
|
|
|
ReflectionSceneData.SortedCaptures.Sort();
|
|
|
|
for (int32 CaptureIndex = 0; CaptureIndex < ReflectionSceneData.SortedCaptures.Num(); CaptureIndex++)
|
|
{
|
|
ReflectionSceneData.SortedCaptures[CaptureIndex].CaptureProxy->SortedCaptureIndex = CaptureIndex;
|
|
}
|
|
|
|
|
|
// If SortedCaptures change, then in case of forward renderer all scene primitives need to be updated, as they
|
|
// store index into sorted reflection capture uniform buffer for the forward renderer.
|
|
if (IsForwardShadingEnabled(Scene->GetShaderPlatform()) && ReflectionSceneData.AllocatedReflectionCaptureStateHasChanged)
|
|
{
|
|
const int32 NumPrimitives = Scene->Primitives.Num();
|
|
for (int32 PrimitiveIndex = 0; PrimitiveIndex < NumPrimitives; ++PrimitiveIndex)
|
|
{
|
|
Scene->Primitives[PrimitiveIndex]->SetNeedsUniformBufferUpdate(true);
|
|
}
|
|
|
|
Scene->GPUScene.bUpdateAllPrimitives = true;
|
|
|
|
ReflectionSceneData.AllocatedReflectionCaptureStateHasChanged = false;
|
|
}
|
|
|
|
|
|
// Mark all primitives for reflection proxy update
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_MarkAllPrimitivesForReflectionProxyUpdate);
|
|
|
|
if (Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged)
|
|
{
|
|
// Mobile needs to re-cache all mesh commands when scene capture data has changed
|
|
const bool bNeedsStaticMeshUpdate = Scene->GetShadingPath() == EShadingPath::Mobile;
|
|
|
|
// Mark all primitives as needing an update
|
|
// Note: Only visible primitives will actually update their reflection proxy
|
|
for (int32 PrimitiveIndex = 0; PrimitiveIndex < Scene->Primitives.Num(); PrimitiveIndex++)
|
|
{
|
|
FPrimitiveSceneInfo* Primitive = Scene->Primitives[PrimitiveIndex];
|
|
Primitive->RemoveCachedReflectionCaptures();
|
|
|
|
if (bNeedsStaticMeshUpdate)
|
|
{
|
|
Primitive->CacheReflectionCaptures();
|
|
Primitive->BeginDeferredUpdateStaticMeshes();
|
|
}
|
|
}
|
|
|
|
Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
static uint32 GetDrawCountFromPrimitiveSceneInfo(FScene* Scene, const FPrimitiveSceneInfo* PrimitiveSceneInfo)
|
|
{
|
|
uint32 DrawCount = 0;
|
|
for (const FCachedMeshDrawCommandInfo& CachedCommand : PrimitiveSceneInfo->StaticMeshCommandInfos)
|
|
{
|
|
if (CachedCommand.MeshPass != EMeshPass::BasePass)
|
|
continue;
|
|
|
|
if (CachedCommand.StateBucketId != INDEX_NONE || CachedCommand.CommandIndex >= 0)
|
|
{
|
|
DrawCount++;
|
|
}
|
|
}
|
|
|
|
return DrawCount;
|
|
}
|
|
|
|
void FSceneRenderer::DumpPrimitives(const FViewCommands& ViewCommands)
|
|
{
|
|
if (!bDumpPrimitivesNextFrame)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bDumpPrimitivesNextFrame = false;
|
|
|
|
struct FPrimitiveInfo
|
|
{
|
|
const FPrimitiveSceneInfo* PrimitiveSceneInfo;
|
|
FString Name;
|
|
uint32 DrawCount;
|
|
|
|
bool operator<(const FPrimitiveInfo& Other) const
|
|
{
|
|
// Sort by name to group similar assets together, then by exact primitives so we can ignore duplicates
|
|
const int32 NameCompare = Name.Compare(Other.Name);
|
|
if (NameCompare != 0)
|
|
{
|
|
return NameCompare < 0;
|
|
}
|
|
|
|
return PrimitiveSceneInfo < Other.PrimitiveSceneInfo;
|
|
}
|
|
};
|
|
|
|
TArray<FPrimitiveInfo> Primitives;
|
|
Primitives.Reserve(ViewCommands.MeshCommands[EMeshPass::BasePass].Num() + ViewCommands.DynamicMeshCommandBuildRequests[EMeshPass::BasePass].Num());
|
|
|
|
{
|
|
for (const FVisibleMeshDrawCommand& Mesh : ViewCommands.MeshCommands[EMeshPass::BasePass])
|
|
{
|
|
int32 PrimitiveId = Mesh.PrimitiveIdInfo.ScenePrimitiveId;
|
|
if (PrimitiveId >= 0 && PrimitiveId < Scene->Primitives.Num())
|
|
{
|
|
const FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene->Primitives[PrimitiveId];
|
|
FString FullName = PrimitiveSceneInfo->ComponentForDebuggingOnly->GetFullName();
|
|
|
|
uint32 DrawCount = GetDrawCountFromPrimitiveSceneInfo(Scene, PrimitiveSceneInfo);
|
|
|
|
Primitives.Add({ PrimitiveSceneInfo, MoveTemp(FullName), DrawCount });
|
|
}
|
|
}
|
|
|
|
for (const FStaticMeshBatch* StaticMeshBatch : ViewCommands.DynamicMeshCommandBuildRequests[EMeshPass::BasePass])
|
|
{
|
|
const FPrimitiveSceneInfo* PrimitiveSceneInfo = StaticMeshBatch->PrimitiveSceneInfo;
|
|
FString FullName = PrimitiveSceneInfo->ComponentForDebuggingOnly->GetFullName();
|
|
|
|
uint32 DrawCount = GetDrawCountFromPrimitiveSceneInfo(Scene, PrimitiveSceneInfo);
|
|
|
|
Primitives.Add({ PrimitiveSceneInfo, MoveTemp(FullName), DrawCount });
|
|
}
|
|
}
|
|
|
|
Primitives.Sort();
|
|
|
|
const FString OutputPath = FPaths::ProfilingDir() / TEXT("Primitives") / FString::Printf(TEXT("Primitives-%s.csv"), *FDateTime::Now().ToString());
|
|
const bool bSuppressViewer = true;
|
|
FDiagnosticTableViewer DrawViewer(*OutputPath, bSuppressViewer);
|
|
DrawViewer.AddColumn(TEXT("Name"));
|
|
DrawViewer.AddColumn(TEXT("NumDraws"));
|
|
DrawViewer.CycleRow();
|
|
|
|
const FPrimitiveSceneInfo* LastPrimitiveSceneInfo = nullptr;
|
|
for (const FPrimitiveInfo& Primitive : Primitives)
|
|
{
|
|
if (Primitive.PrimitiveSceneInfo != LastPrimitiveSceneInfo)
|
|
{
|
|
DrawViewer.AddColumn(*Primitive.Name);
|
|
DrawViewer.AddColumn(*FString::Printf(TEXT("%d"), Primitive.DrawCount));
|
|
DrawViewer.CycleRow();
|
|
|
|
LastPrimitiveSceneInfo = Primitive.PrimitiveSceneInfo;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
|
|
static void UpdateHitProxyIdBuffer(
|
|
TArray<uint32>& HitProxyIds,
|
|
FDynamicReadBuffer& DynamicReadBuffer)
|
|
{
|
|
Algo::Sort(HitProxyIds);
|
|
int32 EndIndex = Algo::Unique(HitProxyIds);
|
|
HitProxyIds.RemoveAt(EndIndex, HitProxyIds.Num() - EndIndex);
|
|
|
|
uint32 IdCount = HitProxyIds.Num();
|
|
uint32 BufferCount = FMath::Max(FMath::RoundUpToPowerOfTwo(IdCount), 1u);
|
|
|
|
if (DynamicReadBuffer.NumBytes != BufferCount)
|
|
{
|
|
DynamicReadBuffer.Initialize(TEXT("DynamicReadBuffer"), sizeof(uint32), BufferCount, PF_R32_UINT, BUF_Dynamic);
|
|
}
|
|
|
|
DynamicReadBuffer.Lock();
|
|
{
|
|
uint32* Data = reinterpret_cast<uint32*>(DynamicReadBuffer.MappedBuffer);
|
|
|
|
for (uint32 i = 0; i < IdCount; ++i)
|
|
{
|
|
Data[i] = HitProxyIds[i];
|
|
}
|
|
|
|
uint32 FillValue = IdCount == 0 ? 0 : HitProxyIds.Last();
|
|
|
|
for (uint32 i = IdCount; i < BufferCount; ++i)
|
|
{
|
|
Data[i] = FillValue;
|
|
}
|
|
}
|
|
DynamicReadBuffer.Unlock();
|
|
}
|
|
|
|
#endif
|
|
|
|
void FSceneRenderer::ComputeViewVisibility(
|
|
FRHICommandListImmediate& RHICmdList,
|
|
FExclusiveDepthStencil::Type BasePassDepthStencilAccess,
|
|
FViewVisibleCommandsPerView& ViewCommandsPerView,
|
|
FGlobalDynamicIndexBuffer& DynamicIndexBuffer,
|
|
FGlobalDynamicVertexBuffer& DynamicVertexBuffer,
|
|
FGlobalDynamicReadBuffer& DynamicReadBuffer,
|
|
FInstanceCullingManager& InstanceCullingManager)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ViewVisibilityTime);
|
|
SCOPED_NAMED_EVENT(FSceneRenderer_ComputeViewVisibility, FColor::Magenta);
|
|
|
|
STAT(int32 NumProcessedPrimitives = 0);
|
|
STAT(int32 NumCulledPrimitives = 0);
|
|
STAT(int32 NumOccludedPrimitives = 0);
|
|
|
|
// Allocate the visible light info.
|
|
if (Scene->Lights.GetMaxIndex() > 0)
|
|
{
|
|
ActiveViewFamily->VisibleLightInfos.AddDefaulted(Scene->Lights.GetMaxIndex());
|
|
}
|
|
|
|
int32 NumPrimitives = Scene->Primitives.Num();
|
|
float CurrentRealTime = ActiveViewFamily->Time.GetRealTimeSeconds();
|
|
|
|
FPrimitiveViewMasks HasDynamicMeshElementsMasks;
|
|
HasDynamicMeshElementsMasks.AddZeroed(NumPrimitives);
|
|
|
|
FPrimitiveViewMasks HasDynamicEditorMeshElementsMasks;
|
|
|
|
if (GIsEditor)
|
|
{
|
|
HasDynamicEditorMeshElementsMasks.AddZeroed(NumPrimitives);
|
|
}
|
|
|
|
UpdateReflectionSceneData(Scene);
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ViewVisibilityTime_ConditionalUpdateStaticMeshesWithoutVisibilityCheck);
|
|
SCOPED_NAMED_EVENT(FSceneRenderer_ConditionalUpdateStaticMeshes, FColor::Red);
|
|
|
|
Scene->ConditionalMarkStaticMeshElementsForUpdate();
|
|
|
|
TArray<FPrimitiveSceneInfo*> UpdatedSceneInfos;
|
|
for (TSet<FPrimitiveSceneInfo*>::TIterator It(Scene->PrimitivesNeedingStaticMeshUpdateWithoutVisibilityCheck); It; ++It)
|
|
{
|
|
FPrimitiveSceneInfo* Primitive = *It;
|
|
if (Primitive->NeedsUpdateStaticMeshes())
|
|
{
|
|
UpdatedSceneInfos.Add(Primitive);
|
|
}
|
|
}
|
|
if (UpdatedSceneInfos.Num() > 0)
|
|
{
|
|
FPrimitiveSceneInfo::UpdateStaticMeshes(RHICmdList, Scene, UpdatedSceneInfos, EUpdateStaticMeshFlags::AllCommands);
|
|
}
|
|
Scene->PrimitivesNeedingStaticMeshUpdateWithoutVisibilityCheck.Reset();
|
|
}
|
|
|
|
uint8 ViewBit = 0x1;
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneRenderer_Views);
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex, ViewBit <<= 1)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("View %d"), ViewIndex));
|
|
STAT(NumProcessedPrimitives += NumPrimitives);
|
|
|
|
FViewInfo& View = Views[ViewIndex];
|
|
FViewCommands& ViewCommands = ViewCommandsPerView[ViewIndex];
|
|
FSceneViewState* ViewState = (FSceneViewState*)View.State;
|
|
|
|
const bool bIsSinglePassStereo = View.bIsInstancedStereoEnabled || View.bIsMobileMultiViewEnabled;
|
|
|
|
// Allocate the view's visibility maps.
|
|
View.PrimitiveVisibilityMap.Init(false, Scene->Primitives.Num());
|
|
View.PrimitiveRayTracingVisibilityMap.Init(false, Scene->Primitives.Num());
|
|
|
|
// These are not initialized here, as we overwrite the whole array in GatherDynamicMeshElements().
|
|
View.DynamicMeshEndIndices.SetNumUninitialized(Scene->Primitives.Num());
|
|
View.PrimitiveDefinitelyUnoccludedMap.Init(false, Scene->Primitives.Num());
|
|
View.PotentiallyFadingPrimitiveMap.Init(false, Scene->Primitives.Num());
|
|
View.PrimitiveFadeUniformBuffers.AddZeroed(Scene->Primitives.Num());
|
|
View.PrimitiveFadeUniformBufferMap.Init(false, Scene->Primitives.Num());
|
|
View.StaticMeshVisibilityMap.Init(false, Scene->StaticMeshes.GetMaxIndex());
|
|
View.StaticMeshFadeOutDitheredLODMap.Init(false, Scene->StaticMeshes.GetMaxIndex());
|
|
View.StaticMeshFadeInDitheredLODMap.Init(false, Scene->StaticMeshes.GetMaxIndex());
|
|
View.PrimitivesLODMask.Init(FLODMask(), Scene->Primitives.Num());
|
|
|
|
View.VisibleLightInfos.Empty(Scene->Lights.GetMaxIndex());
|
|
|
|
// The dirty list allocation must take into account the max possible size because when GILCUpdatePrimTaskEnabled is true,
|
|
// the indirect lighting cache will be update on by threaded job, which can not do reallocs on the buffer (since it uses the SceneRenderingAllocator).
|
|
View.DirtyIndirectLightingCacheBufferPrimitives.Reserve(Scene->Primitives.Num());
|
|
|
|
for (int32 LightIndex = 0;LightIndex < Scene->Lights.GetMaxIndex();LightIndex++)
|
|
{
|
|
if (LightIndex + 2 < Scene->Lights.GetMaxIndex())
|
|
{
|
|
if (LightIndex > 2)
|
|
{
|
|
FLUSH_CACHE_LINE(&View.VisibleLightInfos(LightIndex - 2));
|
|
}
|
|
// @todo optimization These prefetches cause asserts since LightIndex > View.VisibleLightInfos.Num() - 1
|
|
//FPlatformMisc::Prefetch(&View.VisibleLightInfos[LightIndex+2]);
|
|
//FPlatformMisc::Prefetch(&View.VisibleLightInfos[LightIndex+1]);
|
|
}
|
|
new(View.VisibleLightInfos) FVisibleLightViewInfo();
|
|
}
|
|
|
|
View.PrimitiveViewRelevanceMap.Reset(Scene->Primitives.Num());
|
|
View.PrimitiveViewRelevanceMap.AddZeroed(Scene->Primitives.Num());
|
|
|
|
// If this is the visibility-parent of other views, reset its ParentPrimitives list.
|
|
const bool bIsParent = ViewState && ViewState->IsViewParent();
|
|
if (bIsParent)
|
|
{
|
|
// PVS-Studio does not understand the validation of ViewState above, so we're disabling
|
|
// its warning that ViewState may be null:
|
|
ViewState->ParentPrimitives.Reset(); //-V595
|
|
}
|
|
|
|
if (ViewState)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_DecompressPrecomputedOcclusion);
|
|
View.PrecomputedVisibilityData = ViewState->GetPrecomputedVisibilityData(View, Scene);
|
|
}
|
|
else
|
|
{
|
|
View.PrecomputedVisibilityData = nullptr;
|
|
}
|
|
|
|
if (View.PrecomputedVisibilityData)
|
|
{
|
|
ActiveViewFamily->bUsedPrecomputedVisibility = true;
|
|
}
|
|
|
|
bool bNeedsFrustumCulling = CVarEnableFrustumCull.GetValueOnRenderThread();
|
|
|
|
// Development builds sometimes override frustum culling, e.g. dependent views in the editor.
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
if (ViewState)
|
|
{
|
|
#if WITH_EDITOR
|
|
// For visibility child views, check if the primitive was visible in the parent view.
|
|
const FSceneViewState* const ViewParent = (FSceneViewState*)ViewState->GetViewParent();
|
|
if (ViewParent)
|
|
{
|
|
bNeedsFrustumCulling = false;
|
|
for (int32 Index = 0; Index < View.PrimitiveVisibilityMap.Num(); ++Index)
|
|
{
|
|
if (ViewParent->ParentPrimitives.Contains(Scene->PrimitiveComponentIds[Index]) ||
|
|
IsAlwaysVisible(Scene, Index))
|
|
{
|
|
View.PrimitiveVisibilityMap[Index] = true;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
// For views with frozen visibility, check if the primitive is in the frozen visibility set.
|
|
if (ViewState->bIsFrozen)
|
|
{
|
|
bNeedsFrustumCulling = false;
|
|
for (int32 Index = 0; Index < View.PrimitiveVisibilityMap.Num(); ++Index)
|
|
{
|
|
if (ViewState->FrozenPrimitives.Contains(Scene->PrimitiveComponentIds[Index]) ||
|
|
IsAlwaysVisible(Scene, Index))
|
|
{
|
|
View.PrimitiveVisibilityMap[Index] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Most views use standard frustum culling.
|
|
if(bNeedsFrustumCulling)
|
|
{
|
|
// Update HLOD transition/visibility states to allow use during distance culling
|
|
FLODSceneTree& HLODTree = Scene->SceneLODHierarchy;
|
|
if (HLODTree.IsActive())
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ViewVisibilityTime_HLODUpdate);
|
|
HLODTree.UpdateVisibilityStates(View);
|
|
}
|
|
else
|
|
{
|
|
HLODTree.ClearVisibilityState(View);
|
|
}
|
|
}
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneRenderer_Cull);
|
|
int32 NumCulledPrimitivesForView = PrimitiveCull(Scene, View, bNeedsFrustumCulling);
|
|
STAT(NumCulledPrimitives += NumCulledPrimitivesForView);
|
|
}
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneRenderer_UpdatePrimitiveFading);
|
|
UpdatePrimitiveFading(Scene, View);
|
|
}
|
|
|
|
if (View.ShowOnlyPrimitives.IsSet())
|
|
{
|
|
View.bHasNoVisiblePrimitive = View.ShowOnlyPrimitives->Num() == 0;
|
|
}
|
|
|
|
if (View.bStaticSceneOnly)
|
|
{
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
// Reflection captures should only capture objects that won't move, since reflection captures won't update at runtime
|
|
if (!Scene->Primitives[BitIt.GetIndex()]->Proxy->HasStaticLighting())
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cull small objects in wireframe in ortho views
|
|
// This is important for performance in the editor because wireframe disables any kind of occlusion culling
|
|
if (View.Family->EngineShowFlags.Wireframe)
|
|
{
|
|
float ScreenSizeScale = FMath::Max(View.ViewMatrices.GetProjectionMatrix().M[0][0] * View.ViewRect.Width(), View.ViewMatrices.GetProjectionMatrix().M[1][1] * View.ViewRect.Height());
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
if (ScreenSizeScale * Scene->PrimitiveBounds[BitIt.GetIndex()].BoxSphereBounds.SphereRadius <= GWireframeCullThreshold)
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Occlusion cull for all primitives in the view frustum, but not in wireframe.
|
|
if (!View.Family->EngineShowFlags.Wireframe)
|
|
{
|
|
int32 NumOccludedPrimitivesInView = OcclusionCull(RHICmdList, Scene, View, DynamicVertexBuffer);
|
|
STAT(NumOccludedPrimitives += NumOccludedPrimitivesInView);
|
|
}
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ViewVisibilityTime_ConditionalUpdateStaticMeshes);
|
|
SCOPED_NAMED_EVENT(FSceneRenderer_UpdateStaticMeshes, FColor::Red);
|
|
|
|
TArray<FPrimitiveSceneInfo*> AddedSceneInfos;
|
|
for (TConstDualSetBitIterator<SceneRenderingBitArrayAllocator, FDefaultBitArrayAllocator> BitIt(View.PrimitiveVisibilityMap, Scene->PrimitivesNeedingStaticMeshUpdate); BitIt; ++BitIt)
|
|
{
|
|
int32 PrimitiveIndex = BitIt.GetIndex();
|
|
AddedSceneInfos.Add(Scene->Primitives[PrimitiveIndex]);
|
|
}
|
|
|
|
if (AddedSceneInfos.Num() > 0)
|
|
{
|
|
FPrimitiveSceneInfo::UpdateStaticMeshes(RHICmdList, Scene, AddedSceneInfos, EUpdateStaticMeshFlags::AllCommands);
|
|
}
|
|
}
|
|
|
|
// Single-pass stereo views can't compute relevance until all views are visibility culled
|
|
if (!bIsSinglePassStereo)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ViewRelevance);
|
|
ComputeAndMarkRelevanceForViewParallel(RHICmdList, Scene, View, ViewCommands, ViewBit, HasDynamicMeshElementsMasks, HasDynamicEditorMeshElementsMasks);
|
|
}
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
// Store the primitive for parent occlusion rendering.
|
|
if (FPlatformProperties::SupportsWindowedMode() && ViewState && ViewState->IsViewParent())
|
|
{
|
|
for (FSceneDualSetBitIterator BitIt(View.PrimitiveVisibilityMap, View.PrimitiveDefinitelyUnoccludedMap); BitIt; ++BitIt)
|
|
{
|
|
ViewState->ParentPrimitives.Add(Scene->PrimitiveComponentIds[BitIt.GetIndex()]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
// if we are freezing the scene, then remember the primitives that are rendered.
|
|
if (ViewState && ViewState->bIsFreezing)
|
|
{
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
ViewState->FrozenPrimitives.Add(Scene->PrimitiveComponentIds[BitIt.GetIndex()]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// TODO: right now decals visibility computed right before rendering them, ideally it should be done in InitViews and this flag should be replaced with list of visible decals
|
|
// Currently used to disable stencil operations in forward base pass when scene has no any decals
|
|
View.bSceneHasDecals = (Scene->Decals.Num() > 0) || (GForceSceneHasDecals != 0);
|
|
|
|
if (bIsSinglePassStereo && IStereoRendering::IsASecondaryView(View) && Views.IsValidIndex(View.PrimaryViewIndex))
|
|
{
|
|
// Ensure primitives from the secondary view are visible in the primary view
|
|
FSceneBitArray& PrimaryVis = Views[View.PrimaryViewIndex].PrimitiveVisibilityMap;
|
|
const FSceneBitArray& SecondaryVis = View.PrimitiveVisibilityMap;
|
|
|
|
check(PrimaryVis.Num() == SecondaryVis.Num());
|
|
|
|
const uint32 NumWords = FMath::DivideAndRoundUp(PrimaryVis.Num(), NumBitsPerDWORD);
|
|
uint32* const PrimaryData = PrimaryVis.GetData();
|
|
const uint32* const SecondaryData = SecondaryVis.GetData();
|
|
|
|
for (uint32 Index = 0; Index < NumWords; ++Index)
|
|
{
|
|
PrimaryData[Index] |= SecondaryData[Index];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ViewBit = 0x1;
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex, ViewBit <<= 1)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
FViewCommands& ViewCommands = ViewCommandsPerView[ViewIndex];
|
|
|
|
if (View.bIsInstancedStereoEnabled || View.bIsMobileMultiViewEnabled)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ViewRelevance);
|
|
ComputeAndMarkRelevanceForViewParallel(RHICmdList, Scene, View, ViewCommands, ViewBit, HasDynamicMeshElementsMasks, HasDynamicEditorMeshElementsMasks);
|
|
}
|
|
}
|
|
|
|
{
|
|
SCOPED_NAMED_EVENT(FSceneRenderer_GatherDynamicMeshElements, FColor::Yellow);
|
|
// Gather FMeshBatches from scene proxies
|
|
GatherDynamicMeshElements(Views, Scene, *ActiveViewFamily, DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer,
|
|
HasDynamicMeshElementsMasks, HasDynamicEditorMeshElementsMasks, ActiveViewFamily->MeshCollector);
|
|
}
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
|
|
#if WITH_EDITOR
|
|
UpdateHitProxyIdBuffer(View.EditorSelectedHitProxyIds, View.EditorSelectedBuffer);
|
|
UpdateHitProxyIdBuffer(View.EditorVisualizeLevelInstanceIds, View.EditorVisualizeLevelInstanceBuffer);
|
|
#endif
|
|
|
|
if (!View.ShouldRenderView())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FViewCommands& ViewCommands = ViewCommandsPerView[ViewIndex];
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
DumpPrimitives(ViewCommands);
|
|
#endif
|
|
|
|
SetupMeshPass(View, BasePassDepthStencilAccess, ViewCommands, InstanceCullingManager);
|
|
}
|
|
|
|
INC_DWORD_STAT_BY(STAT_ProcessedPrimitives,NumProcessedPrimitives);
|
|
INC_DWORD_STAT_BY(STAT_CulledPrimitives,NumCulledPrimitives);
|
|
INC_DWORD_STAT_BY(STAT_OccludedPrimitives,NumOccludedPrimitives);
|
|
}
|
|
|
|
void FSceneRenderer::PostVisibilityFrameSetup(FILCUpdatePrimTaskData& OutILCTaskData)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup);
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_Sort);
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
|
|
View.MeshDecalBatches.Sort();
|
|
|
|
if (View.State)
|
|
{
|
|
((FSceneViewState*)View.State)->TrimHistoryRenderTargets(Scene);
|
|
}
|
|
}
|
|
}
|
|
|
|
const bool bSetupMobileLightShafts = FeatureLevel <= ERHIFeatureLevel::ES3_1 && ShouldRenderLightShafts(*ActiveViewFamily);
|
|
|
|
if (ActiveViewFamily->EngineShowFlags.HitProxies == 0 && Scene->PrecomputedLightVolumes.Num() > 0
|
|
&& GILCUpdatePrimTaskEnabled && FPlatformProcess::SupportsMultithreading())
|
|
{
|
|
Scene->IndirectLightingCache.StartUpdateCachePrimitivesTask(Scene, *this, true, OutILCTaskData);
|
|
}
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_Light_Visibility);
|
|
// determine visibility of each light
|
|
for(auto LightIt = Scene->Lights.CreateConstIterator();LightIt;++LightIt)
|
|
{
|
|
const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
|
|
const FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;
|
|
|
|
// view frustum cull lights in each view
|
|
for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
|
|
{
|
|
const FLightSceneProxy* Proxy = LightSceneInfo->Proxy;
|
|
FViewInfo& View = Views[ViewIndex];
|
|
FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()];
|
|
// dir lights are always visible, and point/spot only if in the frustum
|
|
if( Proxy->GetLightType() == LightType_Point ||
|
|
Proxy->GetLightType() == LightType_Spot ||
|
|
Proxy->GetLightType() == LightType_Rect )
|
|
{
|
|
const FSphere& BoundingSphere = Proxy->GetBoundingSphere();
|
|
const bool bInViewFrustum = View.ViewFrustum.IntersectSphere(BoundingSphere.Center, BoundingSphere.W);
|
|
|
|
if (View.IsPerspectiveProjection())
|
|
{
|
|
const float DistanceSquared = (BoundingSphere.Center - View.ViewMatrices.GetViewOrigin()).SizeSquared();
|
|
const float MaxDistSquared = Proxy->GetMaxDrawDistance() * Proxy->GetMaxDrawDistance() * GLightMaxDrawDistanceScale * GLightMaxDrawDistanceScale;
|
|
const bool bDrawLight = (FMath::Square(FMath::Min(0.0002f, GMinScreenRadiusForLights / BoundingSphere.W) * View.LODDistanceFactor) * DistanceSquared < 1.0f)
|
|
&& (MaxDistSquared == 0 || DistanceSquared < MaxDistSquared);
|
|
|
|
VisibleLightViewInfo.bInViewFrustum = bDrawLight && bInViewFrustum;
|
|
VisibleLightViewInfo.bInDrawRange = bDrawLight;
|
|
}
|
|
else
|
|
{
|
|
VisibleLightViewInfo.bInViewFrustum = bInViewFrustum;
|
|
VisibleLightViewInfo.bInDrawRange = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VisibleLightViewInfo.bInViewFrustum = true;
|
|
VisibleLightViewInfo.bInDrawRange = true;
|
|
// Setup single sun-shaft from direction lights for mobile.
|
|
if (bSetupMobileLightShafts && LightSceneInfo->bEnableLightShaftBloom && ShouldRenderLightShaftsForLight(View, *LightSceneInfo->Proxy))
|
|
{
|
|
View.MobileLightShaft = GetMobileLightShaftInfo(View, *LightSceneInfo);
|
|
}
|
|
}
|
|
|
|
// Draw shapes for reflection captures
|
|
if( View.bIsReflectionCapture
|
|
&& VisibleLightViewInfo.bInViewFrustum
|
|
&& Proxy->HasStaticLighting()
|
|
&& Proxy->GetLightType() != LightType_Directional )
|
|
{
|
|
FVector Origin = Proxy->GetOrigin();
|
|
FVector ToLight = Origin - View.ViewMatrices.GetViewOrigin();
|
|
float DistanceSqr = ToLight | ToLight;
|
|
float Radius = Proxy->GetRadius();
|
|
|
|
if( DistanceSqr < Radius * Radius )
|
|
{
|
|
FLightRenderParameters LightParameters;
|
|
|
|
Proxy->GetLightShaderParameters(LightParameters);
|
|
|
|
// Force to be at least 0.75 pixels
|
|
float CubemapSize = (float)IConsoleManager::Get().FindTConsoleVariableDataInt( TEXT("r.ReflectionCaptureResolution") )->GetValueOnAnyThread();
|
|
float Distance = FMath::Sqrt( DistanceSqr );
|
|
float MinRadius = Distance * 0.75f / CubemapSize;
|
|
LightParameters.SourceRadius = FMath::Max( MinRadius, LightParameters.SourceRadius );
|
|
|
|
// Snap to cubemap pixel center to reduce aliasing
|
|
FVector Scale = ToLight.GetAbs();
|
|
int32 MaxComponent = Scale.X > Scale.Y ? ( Scale.X > Scale.Z ? 0 : 2 ) : ( Scale.Y > Scale.Z ? 1 : 2 );
|
|
for( int32 k = 1; k < 3; k++ )
|
|
{
|
|
float Projected = ToLight[ (MaxComponent + k) % 3 ] / Scale[ MaxComponent ];
|
|
float Quantized = ( FMath::RoundToFloat( Projected * (0.5f * CubemapSize) - 0.5f ) + 0.5f ) / (0.5f * CubemapSize);
|
|
ToLight[ (MaxComponent + k) % 3 ] = Quantized * Scale[ MaxComponent ];
|
|
}
|
|
Origin = ToLight + View.ViewMatrices.GetViewOrigin();
|
|
|
|
FLinearColor Color( LightParameters.Color.R, LightParameters.Color.G, LightParameters.Color.B, LightParameters.FalloffExponent );
|
|
const bool bIsRectLight = Proxy->IsRectLight();
|
|
if( !bIsRectLight )
|
|
{
|
|
const float SphereArea = (4.0f * PI) * FMath::Square( LightParameters.SourceRadius );
|
|
const float CylinderArea = (2.0f * PI) * LightParameters.SourceRadius * LightParameters.SourceLength;
|
|
const float SurfaceArea = SphereArea + CylinderArea;
|
|
Color *= 4.0f / SurfaceArea;
|
|
}
|
|
|
|
if( Proxy->IsInverseSquared() )
|
|
{
|
|
float LightRadiusMask = FMath::Square( 1.0f - FMath::Square( DistanceSqr * FMath::Square( LightParameters.InvRadius ) ) );
|
|
Color.A = LightRadiusMask;
|
|
}
|
|
else
|
|
{
|
|
// Remove inverse square falloff
|
|
Color *= DistanceSqr + 1.0f;
|
|
|
|
// Apply falloff
|
|
Color.A = FMath::Pow( 1.0f - DistanceSqr * FMath::Square(LightParameters.InvRadius ), LightParameters.FalloffExponent );
|
|
}
|
|
|
|
// Spot falloff
|
|
FVector L = ToLight.GetSafeNormal();
|
|
Color.A *= FMath::Square( FMath::Clamp( ( (L | (FVector)LightParameters.Direction) - LightParameters.SpotAngles.X ) * LightParameters.SpotAngles.Y, 0.0f, 1.0f ) );
|
|
|
|
Color.A *= LightParameters.SpecularScale;
|
|
|
|
// Rect is one sided
|
|
if( bIsRectLight && (L | (FVector)LightParameters.Direction) < 0.0f )
|
|
continue;
|
|
|
|
UTexture* SurfaceTexture = nullptr;
|
|
if (bIsRectLight)
|
|
{
|
|
const FRectLightSceneProxy* RectLightProxy = (const FRectLightSceneProxy*)Proxy;
|
|
SurfaceTexture = RectLightProxy->SourceTexture;
|
|
}
|
|
|
|
FMaterialRenderProxy* ColoredMeshInstance = nullptr;
|
|
if (SurfaceTexture)
|
|
ColoredMeshInstance = new(FMemStack::Get()) FColoredTexturedMaterialRenderProxy(GEngine->EmissiveMeshMaterial->GetRenderProxy(), Color, NAME_Color, SurfaceTexture, NAME_LinearColor);
|
|
else
|
|
ColoredMeshInstance = new(FMemStack::Get()) FColoredMaterialRenderProxy(GEngine->EmissiveMeshMaterial->GetRenderProxy(), Color, NAME_Color);
|
|
|
|
FMatrix LightToWorld = Proxy->GetLightToWorld();
|
|
LightToWorld.RemoveScaling();
|
|
|
|
FViewElementPDI LightPDI( &View, NULL, &View.DynamicPrimitiveCollector);
|
|
|
|
if( bIsRectLight )
|
|
{
|
|
DrawBox( &LightPDI, LightToWorld, FVector( 0.0f, LightParameters.SourceRadius, LightParameters.SourceLength ), ColoredMeshInstance, SDPG_World );
|
|
}
|
|
else if( LightParameters.SourceLength > 0.0f )
|
|
{
|
|
DrawSphere( &LightPDI, Origin + 0.5f * LightParameters.SourceLength * LightToWorld.GetUnitAxis( EAxis::Z ), FRotator::ZeroRotator, LightParameters.SourceRadius * FVector::OneVector, 36, 24, ColoredMeshInstance, SDPG_World );
|
|
DrawSphere( &LightPDI, Origin - 0.5f * LightParameters.SourceLength * LightToWorld.GetUnitAxis( EAxis::Z ), FRotator::ZeroRotator, LightParameters.SourceRadius * FVector::OneVector, 36, 24, ColoredMeshInstance, SDPG_World );
|
|
DrawCylinder( &LightPDI, Origin, LightToWorld.GetUnitAxis( EAxis::X ), LightToWorld.GetUnitAxis( EAxis::Y ), LightToWorld.GetUnitAxis( EAxis::Z ), LightParameters.SourceRadius, 0.5f * LightParameters.SourceLength, 36, ColoredMeshInstance, SDPG_World );
|
|
}
|
|
else
|
|
{
|
|
DrawSphere( &LightPDI, Origin, FRotator::ZeroRotator, LightParameters.SourceRadius * FVector::OneVector, 36, 24, ColoredMeshInstance, SDPG_World );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_InitFogConstants);
|
|
InitFogConstants();
|
|
}
|
|
}
|
|
|
|
uint32 GetShadowQuality();
|
|
void UpdateHairResources(FRDGBuilder& GraphBuilder, const FViewInfo& View);
|
|
|
|
/**
|
|
* Performs once per frame setup prior to visibility determination.
|
|
*/
|
|
void FDeferredShadingSceneRenderer::PreVisibilityFrameSetup(FRDGBuilder& GraphBuilder, const FSceneTexturesConfig& SceneTexturesConfig)
|
|
{
|
|
// Possible stencil dither optimization approach
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
View.bAllowStencilDither = DepthPass.bDitheredLODTransitionsUseStencil;
|
|
}
|
|
|
|
FSceneRenderer::PreVisibilityFrameSetup(GraphBuilder, SceneTexturesConfig);
|
|
}
|
|
|
|
/**
|
|
* Initialize scene's views.
|
|
* Check visibility, build visible mesh commands, etc.
|
|
*/
|
|
void FDeferredShadingSceneRenderer::InitViews(FRDGBuilder& GraphBuilder, const FSceneTexturesConfig& SceneTexturesConfig, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, struct FILCUpdatePrimTaskData& ILCTaskData, FInstanceCullingManager& InstanceCullingManager)
|
|
{
|
|
SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_InitViews, FColor::Emerald);
|
|
SCOPE_CYCLE_COUNTER(STAT_InitViewsTime);
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(InitViews_Scene);
|
|
|
|
PreVisibilityFrameSetup(GraphBuilder, SceneTexturesConfig);
|
|
|
|
FRHICommandListImmediate& RHICmdList = GraphBuilder.RHICmdList;
|
|
|
|
const auto DispatchToRHIThread = [&]()
|
|
{
|
|
RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
|
|
};
|
|
|
|
DispatchToRHIThread();
|
|
|
|
// Create GPU-side representation of the view for instance culling.
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
|
|
{
|
|
Views[ViewIndex].GPUSceneViewId = InstanceCullingManager.RegisterView(Views[ViewIndex]);
|
|
}
|
|
|
|
{
|
|
// This is to init the ViewUniformBuffer before rendering for the Niagara compute shader.
|
|
// This needs to run before ComputeViewVisibility() is called, but the views normally initialize the ViewUniformBuffer after that (at the end of this method).
|
|
if (FXSystem && FXSystem->RequiresEarlyViewUniformBuffer() && Views.IsValidIndex(0))
|
|
{
|
|
Views[0].InitRHIResources();
|
|
FXSystem->PostInitViews(GraphBuilder, Views, !ActiveViewFamily->EngineShowFlags.HitProxies);
|
|
}
|
|
}
|
|
|
|
LumenScenePDIVisualization();
|
|
|
|
FViewVisibleCommandsPerView ViewCommandsPerView;
|
|
ViewCommandsPerView.SetNum(Views.Num());
|
|
|
|
ComputeViewVisibility(RHICmdList, BasePassDepthStencilAccess, ViewCommandsPerView, DynamicIndexBufferForInitViews, DynamicVertexBufferForInitViews, DynamicReadBufferForInitViews, InstanceCullingManager);
|
|
|
|
DispatchToRHIThread();
|
|
|
|
// This has to happen before Scene->IndirectLightingCache.UpdateCache, since primitives in View.IndirectShadowPrimitives need ILC updates
|
|
CreateIndirectCapsuleShadows();
|
|
|
|
// This must happen before we start initialising and using views.
|
|
if (Scene)
|
|
{
|
|
UpdateSkyIrradianceGpuBuffer(RHICmdList, ActiveViewFamily->EngineShowFlags, Scene->SkyLight, Scene->SkyIrradianceEnvironmentMap);
|
|
}
|
|
|
|
DispatchToRHIThread();
|
|
|
|
// Initialise Sky/View resources before the view global uniform buffer is built.
|
|
if (ShouldRenderSkyAtmosphere(Scene, ActiveViewFamily->EngineShowFlags))
|
|
{
|
|
InitSkyAtmosphereForViews(RHICmdList);
|
|
}
|
|
|
|
PostVisibilityFrameSetup(ILCTaskData);
|
|
DispatchToRHIThread();
|
|
|
|
FVector AverageViewPosition(0);
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
AverageViewPosition += View.ViewMatrices.GetViewOrigin() / Views.Num();
|
|
}
|
|
|
|
InitViewsBeforePrepass(GraphBuilder, InstanceCullingManager);
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_InitViews_InitRHIResources);
|
|
// initialize per-view uniform buffer. Do it from back to front because secondary stereo view follows its primary one, but primary needs to know the instanced's params
|
|
for (int32 ViewIndex = Views.Num() - 1; ViewIndex >= 0; --ViewIndex)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
|
|
#if RHI_RAYTRACING
|
|
View.IESLightProfileResource = View.ViewState ? &View.ViewState->IESLightProfileResources : nullptr;
|
|
#endif
|
|
// Set the pre-exposure before initializing the constant buffers.
|
|
if (View.ViewState)
|
|
{
|
|
View.ViewState->UpdatePreExposure(View);
|
|
}
|
|
|
|
// Initialize the view's RHI resources.
|
|
UpdateHairResources(GraphBuilder, View);
|
|
View.InitRHIResources();
|
|
}
|
|
|
|
// This is done for raytracing hit groups.
|
|
Scene->UniformBuffers.ViewUniformBuffer.UpdateUniformBufferImmediate(*Views[0].CachedViewUniformShaderParameters);
|
|
}
|
|
|
|
SetupVolumetricFog();
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_InitViews_OnStartRender);
|
|
OnStartRender(RHICmdList);
|
|
}
|
|
}
|
|
|
|
template<class T>
|
|
void CreateReflectionCaptureUniformBuffer(const TArray<FReflectionCaptureSortData>& SortedCaptures, TUniformBufferRef<T>& OutReflectionCaptureUniformBuffer)
|
|
{
|
|
T SamplePositionsBuffer;
|
|
for (int32 CaptureIndex = 0; CaptureIndex < SortedCaptures.Num(); CaptureIndex++)
|
|
{
|
|
SamplePositionsBuffer.PositionAndRadius[CaptureIndex] = FVector4f(SortedCaptures[CaptureIndex].RelativePosition, SortedCaptures[CaptureIndex].Radius);
|
|
SamplePositionsBuffer.TilePosition[CaptureIndex] = FVector4f(SortedCaptures[CaptureIndex].TilePosition, 0);
|
|
|
|
SamplePositionsBuffer.CaptureProperties[CaptureIndex] = SortedCaptures[CaptureIndex].CaptureProperties;
|
|
SamplePositionsBuffer.CaptureOffsetAndAverageBrightness[CaptureIndex] = SortedCaptures[CaptureIndex].CaptureOffsetAndAverageBrightness;
|
|
SamplePositionsBuffer.BoxTransform[CaptureIndex] = SortedCaptures[CaptureIndex].BoxTransform;
|
|
SamplePositionsBuffer.BoxScales[CaptureIndex] = SortedCaptures[CaptureIndex].BoxScales;
|
|
}
|
|
|
|
OutReflectionCaptureUniformBuffer = TUniformBufferRef<T>::CreateUniformBufferImmediate(SamplePositionsBuffer, UniformBuffer_SingleFrame);
|
|
}
|
|
|
|
void FSceneRenderer::SetupSceneReflectionCaptureBuffer(FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
const TArray<FReflectionCaptureSortData>& SortedCaptures = Scene->ReflectionSceneData.SortedCaptures;
|
|
|
|
TUniformBufferRef<FMobileReflectionCaptureShaderData> MobileReflectionCaptureUniformBuffer;
|
|
TUniformBufferRef<FReflectionCaptureShaderData> ReflectionCaptureUniformBuffer;
|
|
|
|
if (IsMobilePlatform(ShaderPlatform))
|
|
{
|
|
CreateReflectionCaptureUniformBuffer(SortedCaptures, MobileReflectionCaptureUniformBuffer);
|
|
}
|
|
else
|
|
{
|
|
CreateReflectionCaptureUniformBuffer(SortedCaptures, ReflectionCaptureUniformBuffer);
|
|
}
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
|
|
if (IsMobilePlatform(ShaderPlatform))
|
|
{
|
|
View.MobileReflectionCaptureUniformBuffer = MobileReflectionCaptureUniformBuffer;
|
|
}
|
|
else
|
|
{
|
|
View.ReflectionCaptureUniformBuffer = ReflectionCaptureUniformBuffer;
|
|
}
|
|
|
|
View.NumBoxReflectionCaptures = 0;
|
|
View.NumSphereReflectionCaptures = 0;
|
|
View.FurthestReflectionCaptureDistance = 0.0f;
|
|
|
|
if (View.Family->EngineShowFlags.ReflectionEnvironment
|
|
// Avoid feedback
|
|
&& !View.bIsReflectionCapture)
|
|
{
|
|
View.NumBoxReflectionCaptures = Scene->ReflectionSceneData.NumBoxCaptures;
|
|
View.NumSphereReflectionCaptures = Scene->ReflectionSceneData.NumSphereCaptures;
|
|
|
|
for (int32 CaptureIndex = 0; CaptureIndex < SortedCaptures.Num(); CaptureIndex++)
|
|
{
|
|
FLargeWorldRenderPosition AbsolutePosition(SortedCaptures[CaptureIndex].TilePosition, SortedCaptures[CaptureIndex].RelativePosition);
|
|
const FSphere BoundingSphere(AbsolutePosition.GetAbsolute(), SortedCaptures[CaptureIndex].Radius);
|
|
|
|
const float Distance = View.ViewMatrices.GetViewMatrix().TransformPosition(BoundingSphere.Center).Z + BoundingSphere.W;
|
|
|
|
View.FurthestReflectionCaptureDistance = FMath::Max(View.FurthestReflectionCaptureDistance, Distance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDeferredShadingSceneRenderer::InitViewsBeforePrepass(FRDGBuilder& GraphBuilder, FInstanceCullingManager& InstanceCullingManager)
|
|
{
|
|
const bool bHasRayTracedOverlay = HasRayTracedOverlay(*ActiveViewFamily);
|
|
|
|
if (GEarlyInitDynamicShadows &&
|
|
CurrentDynamicShadowsTaskData == nullptr &&
|
|
ActiveViewFamily->EngineShowFlags.DynamicShadows
|
|
&& !IsSimpleForwardShadingEnabled(ShaderPlatform)
|
|
&& !ActiveViewFamily->EngineShowFlags.HitProxies
|
|
&& !bHasRayTracedOverlay)
|
|
{
|
|
CurrentDynamicShadowsTaskData = BeginInitDynamicShadows(true);
|
|
}
|
|
}
|
|
|
|
void FDeferredShadingSceneRenderer::InitViewsAfterPrepass(FRDGBuilder& GraphBuilder, FLumenSceneFrameTemporaries& FrameTemporaries, struct FILCUpdatePrimTaskData& ILCTaskData, FInstanceCullingManager& InstanceCullingManager)
|
|
{
|
|
SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_InitViewsAfterPrepass, FColor::Emerald);
|
|
SCOPE_CYCLE_COUNTER(STAT_InitViewsPossiblyAfterPrepass);
|
|
|
|
FRHICommandListImmediate& RHICmdList = GraphBuilder.RHICmdList;
|
|
|
|
const bool bHasRayTracedOverlay = HasRayTracedOverlay(*ActiveViewFamily);
|
|
|
|
if (ActiveViewFamily->EngineShowFlags.DynamicShadows
|
|
&& !IsSimpleForwardShadingEnabled(ShaderPlatform)
|
|
&& !ActiveViewFamily->EngineShowFlags.HitProxies
|
|
&& !bHasRayTracedOverlay)
|
|
{
|
|
// Setup dynamic shadows.
|
|
if (CurrentDynamicShadowsTaskData)
|
|
{
|
|
FinishInitDynamicShadows(RHICmdList, CurrentDynamicShadowsTaskData, DynamicIndexBufferForInitShadows, DynamicVertexBufferForInitShadows, DynamicReadBufferForInitShadows, InstanceCullingManager);
|
|
CurrentDynamicShadowsTaskData = nullptr;
|
|
}
|
|
else
|
|
{
|
|
InitDynamicShadows(RHICmdList, DynamicIndexBufferForInitShadows, DynamicVertexBufferForInitShadows, DynamicReadBufferForInitShadows, InstanceCullingManager);
|
|
}
|
|
|
|
RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
|
|
}
|
|
|
|
// If parallel ILC update is disabled, then process it in place.
|
|
if (ActiveViewFamily->EngineShowFlags.HitProxies == 0
|
|
&& Scene->PrecomputedLightVolumes.Num() > 0
|
|
&& !(GILCUpdatePrimTaskEnabled && FPlatformProcess::SupportsMultithreading()))
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_IndirectLightingCache_Update);
|
|
check(!ILCTaskData.TaskRef.IsValid());
|
|
Scene->IndirectLightingCache.UpdateCache(Scene, *this, true);
|
|
}
|
|
|
|
// If we kicked off ILC update via task, wait and finalize.
|
|
if (ILCTaskData.TaskRef.IsValid())
|
|
{
|
|
Scene->IndirectLightingCache.FinalizeCacheUpdates(Scene, *this, ILCTaskData);
|
|
}
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_InitViews_UpdatePrimitiveIndirectLightingCacheBuffers);
|
|
// Now that the indirect lighting cache is updated, we can update the primitive precomputed lighting buffers.
|
|
UpdatePrimitiveIndirectLightingCacheBuffers();
|
|
}
|
|
|
|
SeparateTranslucencyDimensions = UpdateSeparateTranslucencyDimensions(*ActiveViewFamily);
|
|
|
|
SetupSceneReflectionCaptureBuffer(RHICmdList);
|
|
|
|
BeginUpdateLumenSceneTasks(GraphBuilder, FrameTemporaries);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------
|
|
FLODSceneTree Implementation
|
|
------------------------------------------------------------------------------*/
|
|
void FLODSceneTree::AddChildNode(const FPrimitiveComponentId ParentId, FPrimitiveSceneInfo* ChildSceneInfo)
|
|
{
|
|
if (ParentId.IsValid() && ChildSceneInfo)
|
|
{
|
|
FLODSceneNode* Parent = SceneNodes.Find(ParentId);
|
|
|
|
// If parent SceneNode hasn't been created yet (possible, depending on the order actors are added to the scene)
|
|
if (!Parent)
|
|
{
|
|
// Create parent SceneNode, assign correct SceneInfo
|
|
Parent = &SceneNodes.Add(ParentId, FLODSceneNode());
|
|
|
|
int32 ParentIndex = Scene->PrimitiveComponentIds.Find(ParentId);
|
|
if (ParentIndex != INDEX_NONE)
|
|
{
|
|
Parent->SceneInfo = Scene->Primitives[ParentIndex];
|
|
check(Parent->SceneInfo->PrimitiveComponentId == ParentId);
|
|
}
|
|
}
|
|
|
|
Parent->AddChild(ChildSceneInfo);
|
|
}
|
|
}
|
|
|
|
void FLODSceneTree::RemoveChildNode(const FPrimitiveComponentId ParentId, FPrimitiveSceneInfo* ChildSceneInfo)
|
|
{
|
|
if (ParentId.IsValid() && ChildSceneInfo)
|
|
{
|
|
if (FLODSceneNode* Parent = SceneNodes.Find(ParentId))
|
|
{
|
|
Parent->RemoveChild(ChildSceneInfo);
|
|
|
|
// Delete from scene if no children remain
|
|
if (Parent->ChildrenSceneInfos.Num() == 0)
|
|
{
|
|
SceneNodes.Remove(ParentId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FLODSceneTree::UpdateNodeSceneInfo(FPrimitiveComponentId NodeId, FPrimitiveSceneInfo* SceneInfo)
|
|
{
|
|
if (FLODSceneNode* Node = SceneNodes.Find(NodeId))
|
|
{
|
|
Node->SceneInfo = SceneInfo;
|
|
}
|
|
}
|
|
|
|
void FLODSceneTree::ClearVisibilityState(FViewInfo& View)
|
|
{
|
|
if (FSceneViewState* ViewState = (FSceneViewState*)View.State)
|
|
{
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
// Skip update logic when frozen
|
|
if (ViewState->bIsFrozen)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
FHLODVisibilityState& HLODState = ViewState->HLODVisibilityState;
|
|
|
|
if(HLODState.IsValidPrimitiveIndex(0))
|
|
{
|
|
HLODState.PrimitiveFadingLODMap.Empty(0);
|
|
HLODState.PrimitiveFadingOutLODMap.Empty(0);
|
|
HLODState.ForcedVisiblePrimitiveMap.Empty(0);
|
|
HLODState.ForcedHiddenPrimitiveMap.Empty(0);
|
|
}
|
|
|
|
TMap<FPrimitiveComponentId, FHLODSceneNodeVisibilityState>& VisibilityStates = ViewState->HLODSceneNodeVisibilityStates;
|
|
|
|
if(VisibilityStates.Num() > 0)
|
|
{
|
|
VisibilityStates.Empty(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FLODSceneTree::UpdateVisibilityStates(FViewInfo& View)
|
|
{
|
|
if (FSceneViewState* ViewState = (FSceneViewState*)View.State)
|
|
{
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
// Skip update logic when frozen
|
|
if (ViewState->bIsFrozen)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// Per-frame initialization
|
|
FHLODVisibilityState& HLODState = ViewState->HLODVisibilityState;
|
|
TMap<FPrimitiveComponentId, FHLODSceneNodeVisibilityState>& VisibilityStates = ViewState->HLODSceneNodeVisibilityStates;
|
|
|
|
HLODState.PrimitiveFadingLODMap.Init(false, Scene->Primitives.Num());
|
|
HLODState.PrimitiveFadingOutLODMap.Init(false, Scene->Primitives.Num());
|
|
HLODState.ForcedVisiblePrimitiveMap.Init(false, Scene->Primitives.Num());
|
|
HLODState.ForcedHiddenPrimitiveMap.Init(false, Scene->Primitives.Num());
|
|
TArray<FPrimitiveViewRelevance, SceneRenderingAllocator>& RelevanceMap = View.PrimitiveViewRelevanceMap;
|
|
|
|
if (HLODState.PrimitiveFadingLODMap.Num() != Scene->Primitives.Num())
|
|
{
|
|
checkf(0, TEXT("HLOD update incorrectly allocated primitive maps"));
|
|
return;
|
|
}
|
|
|
|
int32 UpdateCount = ++HLODState.UpdateCount;
|
|
|
|
// Update persistent state on temporal dither sync frames
|
|
const FTemporalLODState& LODState = ViewState->GetTemporalLODState();
|
|
bool bSyncFrame = false;
|
|
|
|
if (HLODState.TemporalLODSyncTime != LODState.TemporalLODTime[0])
|
|
{
|
|
HLODState.TemporalLODSyncTime = LODState.TemporalLODTime[0];
|
|
bSyncFrame = true;
|
|
|
|
// Only update our scaling on sync frames else we might end up changing transition direction mid-fade
|
|
const FCachedSystemScalabilityCVars& ScalabilityCVars = GetCachedScalabilityCVars();
|
|
if (ScalabilityCVars.FieldOfViewAffectsHLOD)
|
|
{
|
|
HLODState.FOVDistanceScaleSq = ScalabilityCVars.CalculateFieldOfViewDistanceScale(View.DesiredFOV);
|
|
HLODState.FOVDistanceScaleSq *= HLODState.FOVDistanceScaleSq;
|
|
}
|
|
else
|
|
{
|
|
HLODState.FOVDistanceScaleSq = 1.f;
|
|
}
|
|
}
|
|
|
|
for (auto Iter = SceneNodes.CreateIterator(); Iter; ++Iter)
|
|
{
|
|
FLODSceneNode& Node = Iter.Value();
|
|
FPrimitiveSceneInfo* SceneInfo = Node.SceneInfo;
|
|
|
|
if (!SceneInfo || !SceneInfo->PrimitiveComponentId.IsValid() || !SceneInfo->IsIndexValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FHLODSceneNodeVisibilityState& NodeVisibility = VisibilityStates.FindOrAdd(SceneInfo->PrimitiveComponentId);
|
|
const TArray<FStaticMeshBatchRelevance>& NodeMeshRelevances = SceneInfo->StaticMeshRelevances;
|
|
|
|
// Ignore already updated nodes, or those that we can't work with
|
|
if (NodeVisibility.UpdateCount == UpdateCount || !NodeMeshRelevances.IsValidIndex(0))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int32 NodeIndex = SceneInfo->GetIndex();
|
|
|
|
if (!Scene->PrimitiveBounds.IsValidIndex(NodeIndex))
|
|
{
|
|
checkf(0, TEXT("A HLOD Node's PrimitiveSceneInfo PackedIndex was out of Scene.Primitive bounds!"));
|
|
continue;
|
|
}
|
|
|
|
FPrimitiveBounds& Bounds = Scene->PrimitiveBounds[NodeIndex];
|
|
const bool bForcedIntoView = FMath::IsNearlyZero(Bounds.MinDrawDistance);
|
|
|
|
// Update visibility states of this node and owned children
|
|
const float DistanceSquared = Bounds.BoxSphereBounds.ComputeSquaredDistanceFromBoxToPoint(View.ViewMatrices.GetViewOrigin());
|
|
const bool bNearCulled = DistanceSquared < FMath::Square(Bounds.MinDrawDistance) * HLODState.FOVDistanceScaleSq;
|
|
const bool bFarCulled = DistanceSquared > Bounds.MaxDrawDistance * Bounds.MaxDrawDistance * HLODState.FOVDistanceScaleSq;
|
|
const bool bIsInDrawRange = !bNearCulled && !bFarCulled;
|
|
|
|
const bool bWasFadingPreUpdate = !!NodeVisibility.bIsFading;
|
|
const bool bIsDitheredTransition = NodeMeshRelevances[0].bDitheredLODTransition;
|
|
|
|
if (bIsDitheredTransition && !bForcedIntoView)
|
|
{
|
|
// Update fading state with syncs
|
|
if (bSyncFrame)
|
|
{
|
|
// Fade when HLODs change threshold
|
|
const bool bChangedRange = bIsInDrawRange != !!NodeVisibility.bWasVisible;
|
|
|
|
if (NodeVisibility.bIsFading)
|
|
{
|
|
NodeVisibility.bIsFading = false;
|
|
}
|
|
else if (bChangedRange)
|
|
{
|
|
NodeVisibility.bIsFading = true;
|
|
}
|
|
|
|
NodeVisibility.bWasVisible = NodeVisibility.bIsVisible;
|
|
NodeVisibility.bIsVisible = bIsInDrawRange;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Instant transitions without dithering
|
|
NodeVisibility.bWasVisible = NodeVisibility.bIsVisible;
|
|
NodeVisibility.bIsVisible = bIsInDrawRange || bForcedIntoView;
|
|
NodeVisibility.bIsFading = false;
|
|
}
|
|
|
|
// Flush cached lighting data when changing visible contents
|
|
if (NodeVisibility.bIsVisible != NodeVisibility.bWasVisible || bWasFadingPreUpdate || NodeVisibility.bIsFading)
|
|
{
|
|
FLightPrimitiveInteraction* NodeLightList = SceneInfo->LightList;
|
|
while (NodeLightList)
|
|
{
|
|
NodeLightList->FlushCachedShadowMapData();
|
|
NodeLightList = NodeLightList->GetNextLight();
|
|
}
|
|
}
|
|
|
|
// Force fully disabled view relevance so shadows don't attempt to recompute
|
|
if (!NodeVisibility.bIsVisible)
|
|
{
|
|
if (RelevanceMap.IsValidIndex(NodeIndex))
|
|
{
|
|
FPrimitiveViewRelevance& ViewRelevance = RelevanceMap[NodeIndex];
|
|
FMemory::Memzero(&ViewRelevance, sizeof(FPrimitiveViewRelevance));
|
|
ViewRelevance.bInitializedThisFrame = true;
|
|
}
|
|
else
|
|
{
|
|
checkf(0, TEXT("A HLOD Node's PrimitiveSceneInfo PackedIndex was out of View.Relevancy bounds!"));
|
|
}
|
|
}
|
|
|
|
// NOTE: We update our children last as HideNodeChildren can add new visibility
|
|
// states, potentially invalidating our cached reference above, NodeVisibility
|
|
if (NodeVisibility.bIsFading)
|
|
{
|
|
// Fade until state back in sync
|
|
HLODState.PrimitiveFadingLODMap[NodeIndex] = true;
|
|
HLODState.PrimitiveFadingOutLODMap[NodeIndex] = !NodeVisibility.bIsVisible;
|
|
HLODState.ForcedVisiblePrimitiveMap[NodeIndex] = true;
|
|
ApplyNodeFadingToChildren(ViewState, Node, NodeVisibility, true, !!NodeVisibility.bIsVisible);
|
|
}
|
|
else if (NodeVisibility.bIsVisible)
|
|
{
|
|
// If stable and visible, override hierarchy visibility
|
|
HLODState.ForcedVisiblePrimitiveMap[NodeIndex] = true;
|
|
HideNodeChildren(ViewState, Node);
|
|
}
|
|
else
|
|
{
|
|
// Not visible and waiting for a transition to fade, keep HLOD hidden
|
|
HLODState.ForcedHiddenPrimitiveMap[NodeIndex] = true;
|
|
|
|
// Also hide children when performing far culling
|
|
if (bFarCulled)
|
|
{
|
|
HideNodeChildren(ViewState, Node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FLODSceneTree::ApplyNodeFadingToChildren(FSceneViewState* ViewState, FLODSceneNode& Node, FHLODSceneNodeVisibilityState& NodeVisibility, const bool bIsFading, const bool bIsFadingOut)
|
|
{
|
|
checkSlow(ViewState);
|
|
if (Node.SceneInfo)
|
|
{
|
|
FHLODVisibilityState& HLODState = ViewState->HLODVisibilityState;
|
|
NodeVisibility.UpdateCount = HLODState.UpdateCount;
|
|
|
|
// Force visibility during fades
|
|
for (const auto Child : Node.ChildrenSceneInfos)
|
|
{
|
|
if (!Child || !Child->PrimitiveComponentId.IsValid() || !Child->IsIndexValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int32 ChildIndex = Child->GetIndex();
|
|
|
|
if (!HLODState.PrimitiveFadingLODMap.IsValidIndex(ChildIndex))
|
|
{
|
|
checkf(0, TEXT("A HLOD Child's PrimitiveSceneInfo PackedIndex was out of FadingMap's bounds!"));
|
|
continue;
|
|
}
|
|
|
|
HLODState.PrimitiveFadingLODMap[ChildIndex] = bIsFading;
|
|
HLODState.PrimitiveFadingOutLODMap[ChildIndex] = bIsFadingOut;
|
|
HLODState.ForcedHiddenPrimitiveMap[ChildIndex] = false;
|
|
|
|
if (bIsFading)
|
|
{
|
|
HLODState.ForcedVisiblePrimitiveMap[ChildIndex] = true;
|
|
}
|
|
|
|
// Fading only occurs at the adjacent hierarchy level, below should be hidden
|
|
if (FLODSceneNode* ChildNode = SceneNodes.Find(Child->PrimitiveComponentId))
|
|
{
|
|
HideNodeChildren(ViewState, *ChildNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FLODSceneTree::HideNodeChildren(FSceneViewState* ViewState, FLODSceneNode& Node)
|
|
{
|
|
checkSlow(ViewState);
|
|
if (Node.SceneInfo)
|
|
{
|
|
FHLODVisibilityState& HLODState = ViewState->HLODVisibilityState;
|
|
TMap<FPrimitiveComponentId, FHLODSceneNodeVisibilityState>& VisibilityStates = ViewState->HLODSceneNodeVisibilityStates;
|
|
FHLODSceneNodeVisibilityState& NodeVisibility = VisibilityStates.FindOrAdd(Node.SceneInfo->PrimitiveComponentId);
|
|
|
|
if (NodeVisibility.UpdateCount != HLODState.UpdateCount)
|
|
{
|
|
NodeVisibility.UpdateCount = HLODState.UpdateCount;
|
|
|
|
for (const auto Child : Node.ChildrenSceneInfos)
|
|
{
|
|
if (!Child || !Child->PrimitiveComponentId.IsValid() || !Child->IsIndexValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int32 ChildIndex = Child->GetIndex();
|
|
|
|
if (!HLODState.ForcedHiddenPrimitiveMap.IsValidIndex(ChildIndex))
|
|
{
|
|
checkf(0, TEXT("A HLOD Child's PrimitiveSceneInfo PackedIndex was out of ForcedHidden's bounds!"));
|
|
continue;
|
|
}
|
|
|
|
HLODState.ForcedHiddenPrimitiveMap[ChildIndex] = true;
|
|
|
|
// Clear the force visible flag in case the child was processed before it's parent
|
|
HLODState.ForcedVisiblePrimitiveMap[ChildIndex] = false;
|
|
|
|
if (FLODSceneNode* ChildNode = SceneNodes.Find(Child->PrimitiveComponentId))
|
|
{
|
|
HideNodeChildren(ViewState, *ChildNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|