Files
UnrealEngineUWP/Engine/Source/Runtime/Renderer/Private/SceneVisibility.cpp

2285 lines
82 KiB
C++
Raw Normal View History

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
SceneVisibility.cpp: Scene visibility determination.
=============================================================================*/
#include "RendererPrivate.h"
#include "Engine.h"
#include "ScenePrivate.h"
#include "FXSystem.h"
#include "../../Engine/Private/SkeletalRenderGPUSkin.h" // GPrevPerBoneMotionBlur
#include "SceneUtils.h"
#include "PostProcessing.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);
#if PLATFORM_MAC // @todo: disabled until rendering problems with HZB occlusion in OpenGL are solved
static int32 GHZBOcclusion = 0;
#else
static int32 GHZBOcclusion = 0;
#endif
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
);
static TAutoConsoleVariable<int32> CVarLightShaftQuality(
TEXT("r.LightShaftQuality"),
1,
TEXT("Defines the light shaft quality.\n")
TEXT(" 0: off\n")
TEXT(" 1: on (default)"),
ECVF_Scalability | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarStaticMeshLODDistanceScale(
TEXT("r.StaticMeshLODDistanceScale"),
1.0f,
TEXT("Scale factor for the distance used in computing discrete LOD for static meshes. (0.25-1)"),
ECVF_Scalability | 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 );
/*------------------------------------------------------------------------------
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 UpdatePrimitiveFadingState(FPrimitiveFadingState& FadingState, FViewInfo& View, bool bVisible)
{
if (FadingState.bValid)
{
if (FadingState.bIsVisible != bVisible)
{
float CurrentRealTime = View.Family->CurrentRealTime;
// 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 = FadingState.FadeTimeScaleBias;
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 = FadingState.FadeTimeScaleBias;
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)
{
float MaxDrawDistanceScale = GetCachedScalabilityCVars().ViewDistanceScale;
float FadeRadius = GDisableLODFade ? 0.0f : GDistanceFadeMaxTravel;
float MaxDrawDistance = InMaxDrawDistance * MaxDrawDistanceScale;
// 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 (DistanceSquared > FMath::Square(MaxDrawDistance + FadeRadius) ||
DistanceSquared < FMath::Square(MinDrawDistance))
{
return true;
}
const bool bDistanceCulled = (DistanceSquared > FMath::Square(MaxDrawDistance));
const bool bMayBeFading = (DistanceSquared > FMath::Square(MaxDrawDistance - FadeRadius));
bool bStillFading = false;
if( !GDisableLODFade && bMayBeFading && State != NULL && !bDisableDistanceBasedFadeTransitions )
{
// Update distance-based visibility and fading state if it has not already been updated.
int32 PrimitiveIndex = PrimitiveSceneInfo->GetIndex();
FRelativeBitReference PrimitiveBit(PrimitiveIndex);
if (PotentiallyFadingPrimitiveMap.AccessCorrespondingBit(PrimitiveBit) == false)
{
FPrimitiveFadingState& FadingState = ((FSceneViewState*)State)->PrimitiveFadingStates.FindOrAdd(PrimitiveSceneInfo->PrimitiveComponentId);
UpdatePrimitiveFadingState(FadingState, *this, !bDistanceCulled);
FUniformBufferRHIParamRef UniformBuffer = FadingState.UniformBuffer;
bStillFading = (UniformBuffer != NULL);
PrimitiveFadeUniformBuffers[PrimitiveIndex] = UniformBuffer;
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 ( bDistanceCulled && !bStillFading );
}
/**
* Frustum cull primitives in the scene against the view.
*/
template<bool UseCustomCulling>
static int32 FrustumCull(const FScene* Scene, FViewInfo& View)
{
SCOPE_CYCLE_COUNTER(STAT_FrustumCull);
int32 NumCulledPrimitives = 0;
FVector ViewOriginForDistanceCulling = View.ViewMatrices.ViewOrigin;
float MaxDrawDistanceScale = GetCachedScalabilityCVars().ViewDistanceScale;
float FadeRadius = GDisableLODFade ? 0.0f : GDistanceFadeMaxTravel;
uint8 CustomVisibilityFlags = EOcclusionFlags::CanBeOccluded | EOcclusionFlags::HasPrecomputedVisibility;
for (FSceneBitArray::FIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
{
const FPrimitiveBounds& Bounds = Scene->PrimitiveBounds[BitIt.GetIndex()];
float DistanceSquared = (Bounds.Origin - ViewOriginForDistanceCulling).SizeSquared();
float MaxDrawDistance = Bounds.MaxDrawDistance * MaxDrawDistanceScale;
int32 VisibilityId = INDEX_NONE;
if (UseCustomCulling &&
((Scene->PrimitiveOcclusionFlags[BitIt.GetIndex()] & CustomVisibilityFlags) == CustomVisibilityFlags))
{
VisibilityId = Scene->PrimitiveVisibilityIds[BitIt.GetIndex()].ByteIndex;
}
// If cull distance is disabled, always show (except foliage)
if (View.Family->EngineShowFlags.DistanceCulledPrimitives
&& !Scene->Primitives[BitIt.GetIndex()]->Proxy->IsDetailMesh())
{
MaxDrawDistance = FLT_MAX;
}
// The primitive is always culled if it exceeds the max fade distance or lay outside the view frustum.
if (DistanceSquared > FMath::Square(MaxDrawDistance + FadeRadius) ||
DistanceSquared < Bounds.MinDrawDistanceSq ||
(UseCustomCulling && !View.CustomVisibilityQuery->IsVisible(VisibilityId, FBoxSphereBounds(Bounds.Origin, Bounds.BoxExtent, Bounds.SphereRadius))) ||
View.ViewFrustum.IntersectSphere(Bounds.Origin, Bounds.SphereRadius) == false ||
View.ViewFrustum.IntersectBox(Bounds.Origin, Bounds.BoxExtent) == false)
{
STAT(NumCulledPrimitives++);
continue;
}
if (DistanceSquared > FMath::Square(MaxDrawDistance))
{
View.PotentiallyFadingPrimitiveMap.AccessCorrespondingBit(BitIt) = true;
}
else
{
// The primitive is visible!
BitIt.GetValue() = true;
if (DistanceSquared > FMath::Square(MaxDrawDistance - FadeRadius))
{
View.PotentiallyFadingPrimitiveMap.AccessCorrespondingBit(BitIt) = true;
}
}
}
return NumCulledPrimitives;
}
/**
* 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->CurrentRealTime;
// 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()]);
UpdatePrimitiveFadingState(FadingState, View, bVisible);
FUniformBufferRHIParamRef UniformBuffer = FadingState.UniformBuffer;
if (UniformBuffer && !bVisible)
{
// If the primitive is fading out make sure it remains visible.
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = true;
}
View.PrimitiveFadeUniformBuffers[BitIt.GetIndex()] = UniformBuffer;
}
}
}
}
/**
* Cull occluded primitives in the view.
*/
static int32 OcclusionCull(FRHICommandListImmediate& RHICmdList, const FScene* Scene, FViewInfo& View)
{
SCOPE_CYCLE_COUNTER(STAT_OcclusionCull);
// 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 artefacts
// It can be forced on by setting HZBOcclusion to 2
bool bHZBOcclusion = (!IsOpenGLPlatform(GShaderPlatformForFeatureLevel[Scene->GetFeatureLevel()]) && GHZBOcclusion) || (GHZBOcclusion == 2);
// Use precomputed visibility data if it is available.
if (View.PrecomputedVisibilityData)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_LookupPrecomputedVisibility);
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++);
}
}
}
}
float CurrentRealTime = View.Family->CurrentRealTime;
if (ViewState)
{
if (Scene->GetFeatureLevel() >= ERHIFeatureLevel::SM4)
{
bool bClearQueries = !View.Family->EngineShowFlags.HitProxies;
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);
}
FViewElementPDI OcclusionPDI(&View, NULL);
QUICK_SCOPE_CYCLE_COUNTER(STAT_FetchVisibilityForPrimitives);
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
{
uint8 OcclusionFlags = Scene->PrimitiveOcclusionFlags[BitIt.GetIndex()];
bool bCanBeOccluded = (OcclusionFlags & EOcclusionFlags::CanBeOccluded) != 0;
if(GIsEditor)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene->Primitives[BitIt.GetIndex()];
if(PrimitiveSceneInfo->Proxy->IsSelected())
{
// to render occluded outline for selected objects
bCanBeOccluded = false;
}
}
int32 NumSubQueries = 1;
bool bSubQueries = false;
const TArray<FBoxSphereBounds>* SubBounds = nullptr;
TArray<bool, SceneRenderingAllocator> SubIsOccluded;
if ((OcclusionFlags & EOcclusionFlags::HasSubprimitiveQueries) && GAllowSubPrimitiveQueries)
{
FPrimitiveSceneProxy* Proxy = Scene->Primitives[BitIt.GetIndex()]->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[BitIt.GetIndex()];
for (int32 SubQuery = 0; SubQuery < NumSubQueries; SubQuery++)
{
FPrimitiveOcclusionHistory* PrimitiveOcclusionHistory = ViewState->PrimitiveOcclusionHistorySet.Find(FPrimitiveOcclusionHistoryKey(PrimitiveId, SubQuery));
bool bIsOccluded = false;
bool bOcclusionStateIsDefinite = false;
if (!PrimitiveOcclusionHistory)
{
// If the primitive doesn't have an occlusion history yet, create it.
PrimitiveOcclusionHistory = &ViewState->PrimitiveOcclusionHistorySet[
ViewState->PrimitiveOcclusionHistorySet.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 ? false : true;
}
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( ViewState->HZBOcclusionTests.IsValidFrame(PrimitiveOcclusionHistory->HZBTestFrameNumber) )
{
bIsOccluded = !ViewState->HZBOcclusionTests.IsVisible( PrimitiveOcclusionHistory->HZBTestIndex );
bOcclusionStateIsDefinite = true;
}
}
else
{
// Read the occlusion query results.
uint64 NumSamples = 0;
FRenderQueryRHIRef& PastQuery = PrimitiveOcclusionHistory->GetPastQuery(ViewState->OcclusionFrameCounter);
if (IsValidRef(PastQuery))
{
// NOTE: RHIGetOcclusionQueryResult should never fail when using a blocking call, rendering artifacts may show up.
if (RHICmdList.GetRenderQueryResult(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 = !PrimitiveOcclusionHistory->bGroupedQuery;
}
else
{
// If the occlusion query failed, treat the primitive as visible.
// already set bIsOccluded = false;
}
}
else
{
// If there's no occlusion query for the primitive, set it's visibility state to whether it has been unoccluded recently.
bIsOccluded = (PrimitiveOcclusionHistory->LastVisibleTime + GEngine->PrimitiveProbablyVisibleTime < CurrentRealTime);
if (bIsOccluded)
{
PrimitiveOcclusionHistory->LastPixelsPercentage = 0.0f;
}
else
{
PrimitiveOcclusionHistory->LastPixelsPercentage = GEngine->MaxOcclusionPixelsFraction;
}
// the state was definite last frame, otherwise we would have ran a query
bOcclusionStateIsDefinite = true;
}
}
if( GVisualizeOccludedPrimitives && bIsOccluded )
{
const FBoxSphereBounds& Bounds = bSubQueries ? (*SubBounds)[SubQuery] : Scene->PrimitiveOcclusionBounds[ BitIt.GetIndex() ];
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)
{
ViewState->OcclusionQueryPool.ReleaseQuery(RHICmdList, PrimitiveOcclusionHistory->GetPastQuery(ViewState->OcclusionFrameCounter));
}
}
// Set the primitive's considered time to keep its occlusion history from being trimmed.
PrimitiveOcclusionHistory->LastConsideredTime = CurrentRealTime;
if (bSubmitQueries && bCanBeOccluded)
{
bool bAllowBoundsTest;
const FBoxSphereBounds& OcclusionBounds = bSubQueries ? (*SubBounds)[SubQuery] : Scene->PrimitiveOcclusionBounds[ BitIt.GetIndex() ];
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.ProjMatrix.M[2][2] * OcclusionBounds.SphereRadius < 1;
}
else
{
bAllowBoundsTest = OcclusionBounds.SphereRadius < HALF_WORLD_MAX;
}
if (bAllowBoundsTest)
{
if( bHZBOcclusion )
{
// Always run
PrimitiveOcclusionHistory->HZBTestIndex = ViewState->HZBOcclusionTests.AddBounds( OcclusionBounds.Origin, OcclusionBounds.BoxExtent );
PrimitiveOcclusionHistory->HZBTestFrameNumber = ViewState->OcclusionFrameCounter;
}
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)
{
// If the primitive's is definitely unoccluded, only requery it occasionally.
float FractionMultiplier = FMath::Max(PrimitiveOcclusionHistory->LastPixelsPercentage/GEngine->MaxOcclusionPixelsFraction, 1.0f);
bRunQuery = (FractionMultiplier * GOcclusionRandomStream.GetFraction() < GEngine->MaxOcclusionPixelsFraction);
bGroupedQuery = false;
}
else
{
bGroupedQuery = false;
bRunQuery = true;
}
}
else
{
// Primitives that need precise occlusion results use individual queries.
bGroupedQuery = false;
bRunQuery = true;
}
if (bRunQuery)
{
PrimitiveOcclusionHistory->SetCurrentQuery(ViewState->OcclusionFrameCounter,
bGroupedQuery ?
View.GroupedOcclusionQueries.BatchPrimitive(OcclusionBounds.Origin + View.ViewMatrices.PreViewTranslation,OcclusionBounds.BoxExtent) :
View.IndividualOcclusionQueries.BatchPrimitive(OcclusionBounds.Origin + View.ViewMatrices.PreViewTranslation,OcclusionBounds.BoxExtent)
);
}
PrimitiveOcclusionHistory->bGroupedQuery = bGroupedQuery;
}
}
else
{
// If the primitive's bounding box intersects the near clipping plane, treat it as definitely unoccluded.
bIsOccluded = false;
bOcclusionStateIsDefinite = true;
}
}
if (bSubQueries)
{
SubIsOccluded.Add(bIsOccluded);
if (!bIsOccluded)
{
bAllSubOccluded = false;
if (bOcclusionStateIsDefinite)
{
PrimitiveOcclusionHistory->LastVisibleTime = CurrentRealTime;
}
}
if (bIsOccluded || !bOcclusionStateIsDefinite)
{
bAllSubOcclusionStateIsDefinite = false;
}
}
else
{
if (bIsOccluded)
{
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
STAT(NumOccludedPrimitives++);
}
else if (bOcclusionStateIsDefinite)
{
PrimitiveOcclusionHistory->LastVisibleTime = CurrentRealTime;
View.PrimitiveDefinitelyUnoccludedMap.AccessCorrespondingBit(BitIt) = true;
}
}
}
if (bSubQueries)
{
FPrimitiveSceneProxy* Proxy = Scene->Primitives[BitIt.GetIndex()]->Proxy;
Proxy->AcceptOcclusionResults(&View, &SubIsOccluded[0], SubIsOccluded.Num());
if (bAllSubOccluded)
{
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
STAT(NumOccludedPrimitives++);
}
else if (bAllSubOcclusionStateIsDefinite)
{
View.PrimitiveDefinitelyUnoccludedMap.AccessCorrespondingBit(BitIt) = true;
}
}
}
if( bHZBOcclusion )
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_HZBUnmapResults);
ViewState->HZBOcclusionTests.UnmapResults(RHICmdList);
if( bSubmitQueries )
{
ViewState->HZBOcclusionTests.SetValidFrameNumber(ViewState->OcclusionFrameCounter);
}
}
}
else
{
// No occlusion queries, so mark primitives as not occluded
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
{
View.PrimitiveDefinitelyUnoccludedMap.AccessCorrespondingBit(BitIt) = true;
}
}
}
return NumOccludedPrimitives;
}
template<class T>
struct FRelevancePrimSet
{
enum
{
MaxPrims = 127 //like 128, but we leave space for NumPrims
};
int32 NumPrims;
T Prims[MaxPrims];
FORCEINLINE FRelevancePrimSet()
: NumPrims(0)
{
//FMemory::Memzero(Prims, sizeof(T) * MaxPrims);
}
FORCEINLINE void AddPrim(T Prim)
{
checkSlow(NumPrims < MaxPrims);
Prims[NumPrims++] = Prim;
}
FORCEINLINE bool IsFull() const
{
return NumPrims >= MaxPrims;
}
template<class TARRAY>
FORCEINLINE void AppendTo(TARRAY& Array)
{
Array.Append(Prims, NumPrims);
}
};
struct FMarkRelevantStaticMeshesForViewData
{
FVector ViewOrigin;
float MaxDrawDistanceScaleSquared;
int32 ForcedLODLevel;
float LODScale;
float InvLODScale;
float MinScreenRadiusForCSMDepthSquared;
float MinScreenRadiusForDepthPrepassSquared;
bool bForceEarlyZPass;
FMarkRelevantStaticMeshesForViewData(FViewInfo& View)
{
ViewOrigin = View.ViewMatrices.ViewOrigin;
MaxDrawDistanceScaleSquared = GetCachedScalabilityCVars().ViewDistanceScaleSquared;
// outside of the loop to be more efficient
ForcedLODLevel = (View.Family->EngineShowFlags.LOD) ? GetCVarForceLOD() : 0;
LODScale = CVarStaticMeshLODDistanceScale.GetValueOnRenderThread();
InvLODScale = 1.0f / LODScale;
MinScreenRadiusForCSMDepthSquared = GMinScreenRadiusForCSMDepth * GMinScreenRadiusForCSMDepth;
MinScreenRadiusForDepthPrepassSquared = GMinScreenRadiusForDepthPrepass * GMinScreenRadiusForDepthPrepass;
extern TAutoConsoleVariable<int32> CVarEarlyZPass;
bForceEarlyZPass = CVarEarlyZPass.GetValueOnRenderThread() == 2;
}
};
namespace EMarkMaskBits
{
enum Type
{
StaticMeshShadowDepthMapMask = 0x1,
StaticMeshVisibilityMapMask = 0x2,
StaticMeshVelocityMapMask = 0x4,
StaticMeshOccluderMapMask = 0x8,
StaticMeshFadeOutDitheredLODMapMask = 0x10,
StaticMeshFadeInDitheredLODMapMask = 0x20,
};
}
struct FRelevancePacket
{
const float CurrentWorldTime;
const float DeltaWorldTime;
FRHICommandListImmediate& RHICmdList;
const FScene* Scene;
const FViewInfo& View;
const uint8 ViewBit;
const FMarkRelevantStaticMeshesForViewData& ViewData;
FPrimitiveViewMasks& OutHasDynamicMeshElementsMasks;
FPrimitiveViewMasks& OutHasDynamicEditorMeshElementsMasks;
uint8* RESTRICT MarkMasks;
FRelevancePrimSet<int32> Input;
FRelevancePrimSet<int32> RelevantStaticPrimitives;
FRelevancePrimSet<int32> NotDrawRelevant;
FRelevancePrimSet<FPrimitiveSceneInfo*> VisibleDynamicPrimitives;
FRelevancePrimSet<FTranslucentPrimSet::FSortedPrim> SortedSeparateTranslucencyPrims;
FRelevancePrimSet<FTranslucentPrimSet::FSortedPrim> SortedTranslucencyPrims;
FRelevancePrimSet<FPrimitiveSceneProxy*> DistortionPrimSet;
FRelevancePrimSet<FPrimitiveSceneProxy*> CustomDepthSet;
FRelevancePrimSet<FPrimitiveSceneInfo*> UpdateStaticMeshes;
FRelevancePrimSet<FPrimitiveSceneInfo*> VisibleEditorPrimitives;
uint16 CombinedShadingModelMask;
FRelevancePacket(
FRHICommandListImmediate& InRHICmdList,
const FScene* InScene,
const FViewInfo& InView,
uint8 InViewBit,
const FMarkRelevantStaticMeshesForViewData& InViewData,
FPrimitiveViewMasks& InOutHasDynamicMeshElementsMasks,
FPrimitiveViewMasks& InOutHasDynamicEditorMeshElementsMasks,
uint8* InMarkMasks)
: CurrentWorldTime(InView.Family->CurrentWorldTime)
, DeltaWorldTime(InView.Family->DeltaWorldTime)
, RHICmdList(InRHICmdList)
, Scene(InScene)
, View(InView)
, ViewBit(InViewBit)
, ViewData(InViewData)
, OutHasDynamicMeshElementsMasks(InOutHasDynamicMeshElementsMasks)
, OutHasDynamicEditorMeshElementsMasks(InOutHasDynamicEditorMeshElementsMasks)
, MarkMasks(InMarkMasks)
, CombinedShadingModelMask(0)
{
}
void AnyThreadTask()
{
ComputeRelevance();
MarkRelevant();
}
void ComputeRelevance()
{
CombinedShadingModelMask = 0;
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 bTranslucentRelevance = ViewRelevance.HasTranslucency();
if (bStaticRelevance && (bDrawRelevance || bShadowRelevance))
{
RelevantStaticPrimitives.AddPrim(BitIndex);
}
if (!bDrawRelevance)
{
NotDrawRelevant.AddPrim(BitIndex);
continue;
}
if (bEditorRelevance)
{
// Editor primitives are rendered after post processing and composited onto the scene
VisibleEditorPrimitives.AddPrim(PrimitiveSceneInfo);
if (GIsEditor)
{
OutHasDynamicEditorMeshElementsMasks[BitIndex] |= ViewBit;
}
}
else if(bDynamicRelevance)
{
// Keep track of visible dynamic primitives.
VisibleDynamicPrimitives.AddPrim(PrimitiveSceneInfo);
OutHasDynamicMeshElementsMasks[BitIndex] |= ViewBit;
}
if (ViewRelevance.HasTranslucency() && !bEditorRelevance && ViewRelevance.bRenderInMainPass)
{
// Add to set of dynamic translucent primitives
FTranslucentPrimSet::PlaceScenePrimitive(PrimitiveSceneInfo, View, ViewRelevance.bNormalTranslucencyRelevance, ViewRelevance.bSeparateTranslucencyRelevance,
&SortedTranslucencyPrims.Prims[SortedTranslucencyPrims.NumPrims], SortedTranslucencyPrims.NumPrims,
&SortedSeparateTranslucencyPrims.Prims[SortedSeparateTranslucencyPrims.NumPrims], SortedSeparateTranslucencyPrims.NumPrims
);
if (ViewRelevance.bDistortionRelevance)
{
// Add to set of dynamic distortion primitives
DistortionPrimSet.AddPrim(PrimitiveSceneInfo->Proxy);
}
}
CombinedShadingModelMask |= ViewRelevance.ShadingModelMaskRelevance;
if (ViewRelevance.bRenderCustomDepth)
{
// Add to set of dynamic distortion primitives
CustomDepthSet.AddPrim(PrimitiveSceneInfo->Proxy);
}
// 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.
// If the primitive's last render time is older than last frame, consider
// it newly visible and update its visibility change time
if (PrimitiveSceneInfo->LastRenderTime < CurrentWorldTime - DeltaWorldTime - DELTA)
{
PrimitiveSceneInfo->LastVisibilityChangeTime = CurrentWorldTime;
}
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]))
{
// Update the PrimitiveComponent's LastRenderTime.
*(PrimitiveSceneInfo->ComponentLastRenderTime) = CurrentWorldTime;
}
// Cache the nearest reflection proxy if needed
if (PrimitiveSceneInfo->bNeedsCachedReflectionCaptureUpdate
// During Forward Shading, the per-object reflection is used for everything
// Otherwise it is just used on translucency
&& (!Scene->ShouldUseDeferredRenderer() || bTranslucentRelevance))
{
PrimitiveSceneInfo->CachedReflectionCaptureProxy = Scene->FindClosestReflectionCapture(Scene->PrimitiveBounds[BitIndex].Origin);
PrimitiveSceneInfo->bNeedsCachedReflectionCaptureUpdate = false;
}
if (PrimitiveSceneInfo->NeedsUpdateStaticMeshes())
{
UpdateStaticMeshes.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);
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];
FLODMask LODToRender = ComputeLODForMeshes( PrimitiveSceneInfo->StaticMeshes, View, Bounds.Origin, Bounds.SphereRadius, ViewData.ForcedLODLevel, ViewData.LODScale);
float DistanceSquared = (Bounds.Origin - ViewData.ViewOrigin).SizeSquared();
const float LODFactorDistanceSquared = DistanceSquared * FMath::Square(View.LODDistanceFactor * ViewData.InvLODScale);
const bool bDrawShadowDepth = FMath::Square(Bounds.SphereRadius) > ViewData.MinScreenRadiusForCSMDepthSquared * LODFactorDistanceSquared;
const bool bDrawDepthOnly = ViewData.bForceEarlyZPass || FMath::Square(Bounds.SphereRadius) > GMinScreenRadiusForDepthPrepass * GMinScreenRadiusForDepthPrepass * LODFactorDistanceSquared;
const int32 NumStaticMeshes = PrimitiveSceneInfo->StaticMeshes.Num();
for(int32 MeshIndex = 0;MeshIndex < NumStaticMeshes;MeshIndex++)
{
const FStaticMesh& StaticMesh = PrimitiveSceneInfo->StaticMeshes[MeshIndex];
if (LODToRender.ContainsLOD(StaticMesh.LODIndex))
{
uint8 MarkMask = 0;
bool bNeedsBatchVisibility = false;
if (LODToRender.IsDithered())
{
if (LODToRender.DitheredLODIndices[0] == StaticMesh.LODIndex)
{
MarkMask |= EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask;
}
if (LODToRender.DitheredLODIndices[1] == StaticMesh.LODIndex)
{
MarkMask |= EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask;
}
}
if (ViewRelevance.bShadowRelevance && bDrawShadowDepth && StaticMesh.CastShadow)
{
// Mark static mesh as visible in shadows.
MarkMask |= EMarkMaskBits::StaticMeshShadowDepthMapMask;
bNeedsBatchVisibility = true;
}
if(ViewRelevance.bDrawRelevance && !StaticMesh.bShadowOnly && (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth))
{
// Mark static mesh as visible for rendering
MarkMask |= EMarkMaskBits::StaticMeshVisibilityMapMask;
if (PrimitiveSceneInfo->ShouldRenderVelocity(View, false))
{
MarkMask |= EMarkMaskBits::StaticMeshVelocityMapMask;
}
++NumVisibleStaticMeshElements;
// If the static mesh is an occluder, check whether it covers enough of the screen to be used as an occluder.
if( StaticMesh.bUseAsOccluder && bDrawDepthOnly )
{
MarkMask |= EMarkMaskBits::StaticMeshOccluderMapMask;
}
bNeedsBatchVisibility = true;
}
if (MarkMask)
{
MarkMasks[StaticMesh.Id] = MarkMask;
}
// Static meshes with a single element always draw, as if the mask were 0x1.
if(bNeedsBatchVisibility && StaticMesh.Elements.Num() > 1)
{
WriteView.StaticMeshBatchVisibility[StaticMesh.Id] = StaticMesh.VertexFactory->GetStaticBatchElementVisibility(View, &StaticMesh);
}
}
}
}
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);
for (int32 Index = 0; Index < NotDrawRelevant.NumPrims; Index++)
{
WriteView.PrimitiveVisibilityMap[NotDrawRelevant.Prims[Index]] = false;
}
WriteView.ShadingModelMaskInView |= CombinedShadingModelMask;
VisibleEditorPrimitives.AppendTo(WriteView.VisibleEditorPrimitives);
VisibleDynamicPrimitives.AppendTo(WriteView.VisibleDynamicPrimitives);
WriteView.TranslucentPrimSet.AppendScenePrimitives(SortedTranslucencyPrims.Prims, SortedTranslucencyPrims.NumPrims, SortedSeparateTranslucencyPrims.Prims, SortedSeparateTranslucencyPrims.NumPrims);
DistortionPrimSet.AppendTo(WriteView.DistortionPrimSet);
CustomDepthSet.AppendTo(WriteView.CustomDepthSet);
for (int32 Index = 0; Index < UpdateStaticMeshes.NumPrims; Index++)
{
UpdateStaticMeshes.Prims[Index]->UpdateStaticMeshes(RHICmdList);
}
}
};
class FRelevancePacketAnyThreadTask
{
FRelevancePacket& Packet;
ENamedThreads::Type ThreadToUse;
public:
FRelevancePacketAnyThreadTask(FRelevancePacket& InPacket, ENamedThreads::Type InThreadToUse)
: Packet(InPacket)
, ThreadToUse(InThreadToUse)
{
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FRelevancePacketAnyThreadTask, STATGROUP_TaskGraphTasks);
}
ENamedThreads::Type GetDesiredThread()
{
return ThreadToUse;
}
static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
Packet.AnyThreadTask();
}
};
class FRelevancePacketRenderThreadTask
{
FRelevancePacket& Packet;
public:
FRelevancePacketRenderThreadTask(FRelevancePacket& InPacket)
: Packet(InPacket)
{
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FRelevancePacketRenderThreadTask, STATGROUP_TaskGraphTasks);
}
ENamedThreads::Type GetDesiredThread()
{
return ENamedThreads::RenderThread_Local;
}
static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
Packet.RenderThreadFinalize();
}
};
static TAutoConsoleVariable<int32> CVarParallelInitViews(
TEXT("r.ParallelInitViews"),
#if WITH_EDITOR
0,
#else
1,
#endif
TEXT("Toggles parallel init views."),
ECVF_RenderThreadSafe
);
/**
* Computes view relevance for visible primitives in the view and adds them to
* appropriate per-view rendering lists.
* @param Scene - The scene being rendered.
* @param View - The view for which to compute relevance.
* @param ViewBit - Bit mask: 1 << ViewIndex where Views(ViewIndex) == View.
* @param OutRelevantStaticPrimitives - Upon return contains a list of relevant
* static primitives.
* callback for this view will have ViewBit set.
*/
static void ComputeAndMarkRelevanceForViewParallel(
FRHICommandListImmediate& RHICmdList,
const FScene* Scene,
FViewInfo& View,
uint8 ViewBit,
FPrimitiveViewMasks& OutHasDynamicMeshElementsMasks,
FPrimitiveViewMasks& OutHasDynamicEditorMeshElementsMasks
)
{
check(OutHasDynamicMeshElementsMasks.Num() == Scene->Primitives.Num());
const FMarkRelevantStaticMeshesForViewData ViewData(View);
int32 NumMesh = View.StaticMeshVisibilityMap.Num();
check(View.StaticMeshShadowDepthMap.Num() == NumMesh && View.StaticMeshVelocityMap.Num() == NumMesh && View.StaticMeshOccluderMap.Num() == NumMesh);
uint8* RESTRICT MarkMasks = (uint8*)FMemStack::Get().Alloc(NumMesh + 31 , 8); // some padding to simplify the high speed transpose
FMemory::Memzero(MarkMasks, NumMesh + 31);
// I am going to do the render thread tasks in order, maybe that isn't necessary
FGraphEventRef LastRenderThread;
{
FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap);
if (BitIt)
{
int32 AnyThreadTasksPerRenderThreadTasks = 1;
if (FApp::ShouldUseThreadingForPerformance() && CVarParallelInitViews.GetValueOnRenderThread() > 0)
{
AnyThreadTasksPerRenderThreadTasks = FTaskGraphInterface::Get().GetNumWorkerThreads() + 1; // the idea is to put the render thread to work
}
// else AnyThreadTasksPerRenderThreadTasks == 1 means we never use a task thread
int32 WorkingAnyThreadTasksPerRenderThreadTasks = AnyThreadTasksPerRenderThreadTasks;
FRelevancePacket* Packet = new(FMemStack::Get()) FRelevancePacket(
RHICmdList,
Scene,
View,
ViewBit,
ViewData,
OutHasDynamicMeshElementsMasks,
OutHasDynamicEditorMeshElementsMasks,
MarkMasks);
while (1)
{
Packet->Input.AddPrim(BitIt.GetIndex());
++BitIt;
if (Packet->Input.IsFull() || !BitIt)
{
// submit task
ENamedThreads::Type ThreadToUse = ENamedThreads::AnyThread;
if (!--WorkingAnyThreadTasksPerRenderThreadTasks)
{
WorkingAnyThreadTasksPerRenderThreadTasks = AnyThreadTasksPerRenderThreadTasks;
ThreadToUse = ENamedThreads::RenderThread_Local;
}
FGraphEventArray RenderPrereqs;
if (LastRenderThread.GetReference())
{
RenderPrereqs.Add(LastRenderThread); // this puts the render thread ones in order
}
FGraphEventRef AnyThread = TGraphTask<FRelevancePacketAnyThreadTask>::CreateTask(nullptr, ENamedThreads::RenderThread).ConstructAndDispatchWhenReady(*Packet, ThreadToUse);
RenderPrereqs.Add(AnyThread);
LastRenderThread = TGraphTask<FRelevancePacketRenderThreadTask>::CreateTask(&RenderPrereqs, ENamedThreads::RenderThread).ConstructAndDispatchWhenReady(*Packet);
if (!BitIt)
{
break;
}
else
{
Packet = new(FMemStack::Get()) FRelevancePacket(
RHICmdList,
Scene,
View,
ViewBit,
ViewData,
OutHasDynamicMeshElementsMasks,
OutHasDynamicEditorMeshElementsMasks,
MarkMasks);
}
}
}
}
}
if (LastRenderThread.GetReference())
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_Wait);
FTaskGraphInterface::Get().WaitUntilTaskCompletes(LastRenderThread, ENamedThreads::RenderThread_Local);
}
QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_TransposeMeshBits);
check(View.StaticMeshVelocityMap.Num() == NumMesh &&
View.StaticMeshShadowDepthMap.Num() == NumMesh &&
View.StaticMeshVisibilityMap.Num() == NumMesh &&
View.StaticMeshOccluderMap.Num() == NumMesh &&
View.StaticMeshFadeOutDitheredLODMap.Num() == NumMesh &&
View.StaticMeshFadeInDitheredLODMap.Num() == NumMesh
);
uint32* RESTRICT StaticMeshVisibilityMap_Words = View.StaticMeshVisibilityMap.GetData();
uint32* RESTRICT StaticMeshVelocityMap_Words = View.StaticMeshVelocityMap.GetData();
uint32* RESTRICT StaticMeshShadowDepthMap_Words = View.StaticMeshShadowDepthMap.GetData();
uint32* RESTRICT StaticMeshOccluderMap_Words = View.StaticMeshOccluderMap.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 StaticMeshVelocityMap_Word = 0;
uint32 StaticMeshShadowDepthMap_Word = 0;
uint32 StaticMeshOccluderMap_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;
StaticMeshVelocityMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshVelocityMapMask) ? Mask : 0;
StaticMeshShadowDepthMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshShadowDepthMapMask) ? Mask : 0;
StaticMeshOccluderMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshOccluderMapMask) ? 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 && !*StaticMeshVelocityMap_Words && !*StaticMeshShadowDepthMap_Words && !*StaticMeshOccluderMap_Words && !*StaticMeshFadeOutDitheredLODMap_Words && !*StaticMeshFadeInDitheredLODMap_Words);
*StaticMeshVisibilityMap_Words = StaticMeshVisibilityMap_Word;
*StaticMeshVelocityMap_Words = StaticMeshVelocityMap_Word;
*StaticMeshShadowDepthMap_Words = StaticMeshShadowDepthMap_Word;
*StaticMeshOccluderMap_Words = StaticMeshOccluderMap_Word;
*StaticMeshFadeOutDitheredLODMap_Words = StaticMeshFadeOutDitheredLODMap_Word;
*StaticMeshFadeInDitheredLODMap_Words = StaticMeshFadeInDitheredLODMap_Word;
}
StaticMeshVisibilityMap_Words++;
StaticMeshVelocityMap_Words++;
StaticMeshShadowDepthMap_Words++;
StaticMeshOccluderMap_Words++;
StaticMeshFadeOutDitheredLODMap_Words++;
StaticMeshFadeInDitheredLODMap_Words++;
}
}
void FSceneRenderer::GatherDynamicMeshElements(
TArray<FViewInfo>& InViews,
const FScene* InScene,
const FSceneViewFamily& InViewFamily,
const FPrimitiveViewMasks& HasDynamicMeshElementsMasks,
const FPrimitiveViewMasks& HasDynamicEditorMeshElementsMasks,
FMeshElementCollector& Collector)
{
SCOPE_CYCLE_COUNTER(STAT_GetDynamicMeshElements);
int32 NumPrimitives = InScene->Primitives.Num();
check(HasDynamicMeshElementsMasks.Num() == NumPrimitives);
{
Collector.ClearViewMeshArrays();
for (int32 ViewIndex = 0; ViewIndex < InViews.Num(); ViewIndex++)
{
Collector.AddViewMeshArrays(&InViews[ViewIndex], &InViews[ViewIndex].DynamicMeshElements, &InViews[ViewIndex].SimpleElementCollector, InViewFamily.GetFeatureLevel());
}
for (int32 PrimitiveIndex = 0; PrimitiveIndex < NumPrimitives; ++PrimitiveIndex)
{
const uint8 ViewMask = HasDynamicMeshElementsMasks[PrimitiveIndex];
if (ViewMask != 0)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = InScene->Primitives[PrimitiveIndex];
Collector.SetPrimitive(PrimitiveSceneInfo->Proxy, PrimitiveSceneInfo->DefaultDynamicHitProxyId);
PrimitiveSceneInfo->Proxy->GetDynamicMeshElements(InViewFamily.Views, InViewFamily, ViewMask, Collector);
}
}
}
if (GIsEditor)
{
Collector.ClearViewMeshArrays();
for (int32 ViewIndex = 0; ViewIndex < InViews.Num(); ViewIndex++)
{
Collector.AddViewMeshArrays(&InViews[ViewIndex], &InViews[ViewIndex].DynamicEditorMeshElements, &InViews[ViewIndex].EditorSimpleElementCollector, InViewFamily.GetFeatureLevel());
}
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);
}
}
}
}
static void MarkAllPrimitivesForReflectionProxyUpdate(FScene* Scene)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_MarkAllPrimitivesForReflectionProxyUpdate);
if (Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged)
{
// 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++)
{
Scene->Primitives[PrimitiveIndex]->bNeedsCachedReflectionCaptureUpdate = true;
}
Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged = false;
}
}
/**
* 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(CameraRotationThreshold * PI / 180.0f);
float ViewRightAngle = View.ViewMatrices.ViewMatrix.GetColumn(0) | PrevViewMatrix.GetColumn(0);
float ViewUpAngle = View.ViewMatrices.ViewMatrix.GetColumn(1) | PrevViewMatrix.GetColumn(1);
float ViewDirectionAngle = View.ViewMatrices.ViewMatrix.GetColumn(2) | PrevViewMatrix.GetColumn(2);
FVector Distance = FVector(View.ViewMatrices.ViewOrigin) - PrevViewOrigin;
return
ViewRightAngle < RotationThreshold ||
ViewUpAngle < RotationThreshold ||
ViewDirectionAngle < RotationThreshold ||
Distance.SizeSquared() > CameraTranslationThreshold * CameraTranslationThreshold;
}
float Halton( int32 Index, int32 Base )
{
float Result = 0.0f;
float InvBase = 1.0f / Base;
float Fraction = InvBase;
while( Index > 0 )
{
Result += ( Index % Base ) * Fraction;
Index /= Base;
Fraction *= InvBase;
}
return Result;
}
void FSceneRenderer::PreVisibilityFrameSetup(FRHICommandListImmediate& RHICmdList)
{
// Notify the RHI we are beginning to render a scene.
RHICmdList.BeginScene();
// Notify the FX system that the scene is about to perform visibility checks.
if (Scene->FXSystem)
{
Scene->FXSystem->PreInitViews();
}
// Draw lines to lights affecting this mesh if its selected.
if (ViewFamily.EngineShowFlags.LightInfluences)
{
for (TArray<FPrimitiveSceneInfo*>::TConstIterator It(Scene->Primitives); It; ++It)
{
const FPrimitiveSceneInfo* PrimitiveSceneInfo = *It;
if (PrimitiveSceneInfo->Proxy->IsSelected())
{
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,NULL);
LightInfluencesPDI.DrawLine(PrimitiveSceneInfo->Proxy->GetBounds().Origin, LightSceneInfo->Proxy->GetLightToWorld().GetOrigin(), LineColor, SDPG_World);
}
}
LightList = LightList->GetNextLight();
}
}
}
}
// Setup motion blur parameters (also check for camera movement thresholds)
for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
FSceneViewState* ViewState = (FSceneViewState*) View.State;
static bool bEnableTimeScale = true;
// Once per render increment the occlusion frame counter.
if (ViewState)
{
ViewState->OcclusionFrameCounter++;
}
// HighResScreenshot should get best results so we don't do the occlusion optimization based on the former frame
extern bool GIsHighResScreenshot;
const bool bIsHitTesting = ViewFamily.EngineShowFlags.HitProxies;
if (GIsHighResScreenshot || !DoOcclusionQueries(FeatureLevel) || bIsHitTesting)
{
View.bDisableQuerySubmissions = true;
View.bIgnoreExistingQueries = true;
}
// set up the screen area for occlusion
float NumPossiblePixels = GSceneRenderTargets.UseDownsizedOcclusionQueries() && IsValidRef(GSceneRenderTargets.GetSmallDepthSurface()) ?
(float)View.ViewRect.Width() / GSceneRenderTargets.GetSmallColorDepthDownsampleFactor() * (float)View.ViewRect.Height() / GSceneRenderTargets.GetSmallColorDepthDownsampleFactor() :
View.ViewRect.Width() * View.ViewRect.Height();
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).
View.TemporalJitterPixelsX = 0.0f;
View.TemporalJitterPixelsY = 0.0f;
if( View.FinalPostProcessSettings.AntiAliasingMethod == AAM_TemporalAA && ViewState )
{
// Subpixel jitter for temporal AA
int32 TemporalAASamples = CVarTemporalAASamples.GetValueOnRenderThread();
if( TemporalAASamples > 1 )
{
float SampleX, SampleY;
if (Scene->GetFeatureLevel() < ERHIFeatureLevel::SM4)
{
// Only support 2 samples for mobile temporal AA.
TemporalAASamples = 2;
}
if( TemporalAASamples == 2 )
{
#if 0
// 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 };
#else
// This pattern is only used for mobile.
// Shift to reduce blur.
float SamplesX[] = { -8.0f/16.0f, 0.0/16.0f };
float SamplesY[] = { /* - */ 0.0f/16.0f, 8.0/16.0f };
#endif
ViewState->SetupTemporalAA(ARRAY_COUNT(SamplesX), ViewFamily);
uint32 Index = ViewState->GetCurrentTemporalAASampleIndex();
SampleX = SamplesX[ Index ];
SampleY = SamplesY[ Index ];
}
else if( TemporalAASamples == 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 };
ViewState->SetupTemporalAA(ARRAY_COUNT(SamplesX), ViewFamily);
uint32 Index = ViewState->GetCurrentTemporalAASampleIndex();
SampleX = SamplesX[ Index ];
SampleY = SamplesY[ Index ];
}
else if( TemporalAASamples == 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 };
ViewState->SetupTemporalAA(ARRAY_COUNT(SamplesX), ViewFamily);
uint32 Index = ViewState->GetCurrentTemporalAASampleIndex();
SampleX = SamplesX[ Index ];
SampleY = SamplesY[ Index ];
}
else if( TemporalAASamples == 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 };
ViewState->SetupTemporalAA(ARRAY_COUNT(SamplesX), ViewFamily);
uint32 Index = ViewState->GetCurrentTemporalAASampleIndex();
SampleX = SamplesX[ Index ];
SampleY = SamplesY[ Index ];
}
else if( TemporalAASamples == 8 )
{
// This works better than various orderings of 8xMSAA.
ViewState->SetupTemporalAA(8, ViewFamily);
uint32 Index = ViewState->GetCurrentTemporalAASampleIndex();
SampleX = Halton( Index, 2 ) - 0.5f;
SampleY = Halton( Index, 3 ) - 0.5f;
}
else
{
// More than 8 samples can improve quality.
ViewState->SetupTemporalAA(TemporalAASamples, ViewFamily);
uint32 Index = ViewState->GetCurrentTemporalAASampleIndex();
SampleX = Halton( Index, 2 ) - 0.5f;
SampleY = Halton( Index, 3 ) - 0.5f;
}
View.ViewMatrices.TemporalAASample.X = SampleX;
View.ViewMatrices.TemporalAASample.Y = SampleY;
View.ViewMatrices.ProjMatrix.M[2][0] += View.ViewMatrices.TemporalAASample.X * 2.0f / View.ViewRect.Width();
View.ViewMatrices.ProjMatrix.M[2][1] += View.ViewMatrices.TemporalAASample.Y * 2.0f / View.ViewRect.Height();;
// Compute the view projection matrix and its inverse.
View.ViewProjectionMatrix = View.ViewMatrices.ViewMatrix * View.ViewMatrices.ProjMatrix;
View.InvViewProjectionMatrix = View.ViewMatrices.GetInvProjMatrix() * View.InvViewMatrix;
/** The view transform, starting from world-space points translated by -ViewOrigin. */
FMatrix TranslatedViewMatrix = FTranslationMatrix(-View.ViewMatrices.PreViewTranslation) * View.ViewMatrices.ViewMatrix;
// Compute a transform from view origin centered world-space to clip space.
View.ViewMatrices.TranslatedViewProjectionMatrix = TranslatedViewMatrix * View.ViewMatrices.ProjMatrix;
View.ViewMatrices.InvTranslatedViewProjectionMatrix = View.ViewMatrices.TranslatedViewProjectionMatrix.Inverse();
}
}
else if(ViewState)
{
// no TemporalAA
ViewState->SetupTemporalAA(1, ViewFamily);
}
if ( ViewState )
{
// In case world origin was rebased, reset previous view transformations
if (View.bOriginOffsetThisFrame)
{
ViewState->PrevViewMatrices = View.ViewMatrices;
ViewState->PendingPrevViewMatrices = View.ViewMatrices;
}
// determine if we are initializing or we should reset the persistent state
const float DeltaTime = View.Family->CurrentRealTime - ViewState->LastRenderTime;
const bool bFirstFrameOrTimeWasReset = DeltaTime < -0.0001f || ViewState->LastRenderTime < 0.0001f;
// detect conditions where we should reset occlusion queries
if (bFirstFrameOrTimeWasReset ||
ViewState->LastRenderTime + GEngine->PrimitiveProbablyVisibleTime < View.Family->CurrentRealTime ||
View.bCameraCut ||
IsLargeCameraMovement(
View,
ViewState->PrevViewMatrixForOcclusionQuery,
ViewState->PrevViewOriginForOcclusionQuery,
GEngine->CameraRotationThreshold, GEngine->CameraTranslationThreshold))
{
View.bIgnoreExistingQueries = true;
View.bDisableDistanceBasedFadeTransitions = true;
}
ViewState->PrevViewMatrixForOcclusionQuery = View.ViewMatrices.ViewMatrix;
ViewState->PrevViewOriginForOcclusionQuery = View.ViewMatrices.ViewOrigin;
// store old view matrix and detect conditions where we should reset motion blur
{
bool bResetCamera = bFirstFrameOrTimeWasReset
|| View.bCameraCut
|| IsLargeCameraMovement(View, ViewState->PrevViewMatrices.ViewMatrix, ViewState->PrevViewMatrices.ViewOrigin, 45.0f, 10000.0f);
if (bResetCamera)
{
ViewState->PrevViewMatrices = View.ViewMatrices;
ViewState->PendingPrevViewMatrices = ViewState->PrevViewMatrices;
// 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
{
// check for pause so we can keep motion blur in paused mode (doesn't work in editor)
if(!ViewFamily.bWorldIsPaused)
{
ViewState->PrevViewMatrices = ViewState->PendingPrevViewMatrices;
// pending is needed as we are in init view and still need to render.
ViewState->PendingPrevViewMatrices = View.ViewMatrices;
}
}
// 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
if (!ViewFamily.bWorldIsPaused)
{
ViewState->MotionBlurTimeScale = bEnableTimeScale ? (1.0f / (FMath::Max(View.Family->DeltaWorldTime, .00833f) * 30.0f)) : 1.0f;
}
View.PrevViewMatrices = ViewState->PrevViewMatrices;
View.PrevViewProjMatrix = ViewState->PrevViewMatrices.GetViewProjMatrix();
View.PrevViewRotationProjMatrix = ViewState->PrevViewMatrices.GetViewRotationProjMatrix();
}
ViewState->PrevFrameNumber = ViewState->PendingPrevFrameNumber;
ViewState->PendingPrevFrameNumber = View.Family->FrameNumber;
// This finishes the update of view state
ViewState->UpdateLastRenderTime(*View.Family);
ViewState->UpdateTemporalLODTransition(View);
}
}
}
void FSceneRenderer::ComputeViewVisibility(FRHICommandListImmediate& RHICmdList)
{
SCOPE_CYCLE_COUNTER(STAT_ViewVisibilityTime);
STAT(int32 NumProcessedPrimitives = 0);
STAT(int32 NumCulledPrimitives = 0);
STAT(int32 NumOccludedPrimitives = 0);
// Allocate the visible light info.
if (Scene->Lights.GetMaxIndex() > 0)
{
VisibleLightInfos.AddZeroed(Scene->Lights.GetMaxIndex());
}
int32 NumPrimitives = Scene->Primitives.Num();
float CurrentRealTime = ViewFamily.CurrentRealTime;
FPrimitiveViewMasks HasDynamicMeshElementsMasks;
HasDynamicMeshElementsMasks.AddZeroed(NumPrimitives);
FPrimitiveViewMasks HasDynamicEditorMeshElementsMasks;
if (GIsEditor)
{
HasDynamicEditorMeshElementsMasks.AddZeroed(NumPrimitives);
}
uint8 ViewBit = 0x1;
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex, ViewBit <<= 1)
{
STAT(NumProcessedPrimitives += NumPrimitives);
FViewInfo& View = Views[ViewIndex];
FSceneViewState* ViewState = (FSceneViewState*)View.State;
// Allocate the view's visibility maps.
View.PrimitiveVisibilityMap.Init(false,Scene->Primitives.Num());
View.PrimitiveDefinitelyUnoccludedMap.Init(false,Scene->Primitives.Num());
View.PotentiallyFadingPrimitiveMap.Init(false,Scene->Primitives.Num());
View.PrimitiveFadeUniformBuffers.AddZeroed(Scene->Primitives.Num());
View.StaticMeshVisibilityMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
View.StaticMeshOccluderMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
View.StaticMeshFadeOutDitheredLODMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
View.StaticMeshFadeInDitheredLODMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
View.StaticMeshVelocityMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
View.StaticMeshShadowDepthMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
View.StaticMeshBatchVisibility.AddZeroed(Scene->StaticMeshes.GetMaxIndex());
View.VisibleLightInfos.Empty(Scene->Lights.GetMaxIndex());
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.Empty(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.Empty(); //-V595
}
if (ViewState)
{
SCOPE_CYCLE_COUNTER(STAT_DecompressPrecomputedOcclusion);
View.PrecomputedVisibilityData = ViewState->GetPrecomputedVisibilityData(View, Scene);
}
else
{
View.PrecomputedVisibilityData = NULL;
}
if (View.PrecomputedVisibilityData)
{
bUsedPrecomputedVisibility = true;
}
bool bNeedsFrustumCulling = true;
// 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 (FSceneBitArray::FIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
{
if (ViewParent->ParentPrimitives.Contains(Scene->PrimitiveComponentIds[BitIt.GetIndex()]))
{
BitIt.GetValue() = true;
}
}
}
#endif
// For views with frozen visibility, check if the primitive is in the frozen visibility set.
if(ViewState->bIsFrozen)
{
bNeedsFrustumCulling = false;
for (FSceneBitArray::FIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
{
if (ViewState->FrozenPrimitives.Contains(Scene->PrimitiveComponentIds[BitIt.GetIndex()]))
{
BitIt.GetValue() = true;
}
}
}
}
#endif
// Most views use standard frustum culling.
if (bNeedsFrustumCulling)
{
int32 NumCulledPrimitivesForView;
if (View.CustomVisibilityQuery && View.CustomVisibilityQuery->Prepare())
{
NumCulledPrimitivesForView = FrustumCull<true>(Scene, View);
}
else
{
NumCulledPrimitivesForView = FrustumCull<false>(Scene, View);
}
STAT(NumCulledPrimitives += NumCulledPrimitivesForView);
UpdatePrimitiveFading(Scene, View);
}
// If any primitives are explicitly hidden, remove them now.
if (View.HiddenPrimitives.Num())
{
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
{
if (View.HiddenPrimitives.Contains(Scene->PrimitiveComponentIds[BitIt.GetIndex()]))
{
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
}
}
}
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.ProjMatrix.M[0][0] * View.ViewRect.Width(), View.ViewMatrices.ProjMatrix.M[1][1] * View.ViewRect.Height());
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
{
if (ScreenSizeScale * Scene->PrimitiveBounds[BitIt.GetIndex()].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);
STAT(NumOccludedPrimitives += NumOccludedPrimitivesInView);
}
// visibility test is done, so now build the hidden flags based on visibility set up
bool bLODSceneTreeActive = Scene->SceneLODHierarchy.IsActive();
FSceneBitArray PrimitiveHiddenByLODMap;
if (bLODSceneTreeActive)
{
PrimitiveHiddenByLODMap.Init(false, View.PrimitiveVisibilityMap.Num());
Scene->SceneLODHierarchy.PopulateHiddenFlags(View, PrimitiveHiddenByLODMap);
// now iterate through turn off visibility if hidden by LOD
for(FSceneSetBitIterator BitIt(PrimitiveHiddenByLODMap); BitIt; ++BitIt)
{
if(PrimitiveHiddenByLODMap.AccessCorrespondingBit(BitIt))
{
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
}
}
}
MarkAllPrimitivesForReflectionProxyUpdate(Scene);
Scene->ConditionalMarkStaticMeshElementsForUpdate();
{
SCOPE_CYCLE_COUNTER(STAT_ViewRelevance);
ComputeAndMarkRelevanceForViewParallel(RHICmdList, Scene, View, ViewBit, HasDynamicMeshElementsMasks, HasDynamicEditorMeshElementsMasks);
if (bLODSceneTreeActive)
{
for (FSceneBitArray::FIterator BitIt(PrimitiveHiddenByLODMap); BitIt; ++BitIt)
{
View.PrimitiveViewRelevanceMap[BitIt.GetIndex()].bInitializedThisFrame = true;
}
}
}
#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
}
GatherDynamicMeshElements(Views, Scene, ViewFamily, HasDynamicMeshElementsMasks, HasDynamicEditorMeshElementsMasks, MeshCollector);
INC_DWORD_STAT_BY(STAT_ProcessedPrimitives,NumProcessedPrimitives);
INC_DWORD_STAT_BY(STAT_CulledPrimitives,NumCulledPrimitives);
INC_DWORD_STAT_BY(STAT_OccludedPrimitives,NumOccludedPrimitives);
}
void FSceneRenderer::PostVisibilityFrameSetup()
{
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
// sort the translucent primitives
View.TranslucentPrimSet.SortPrimitives();
if (View.State)
{
((FSceneViewState*)View.State)->TrimHistoryRenderTargets(Scene);
}
}
bool bCheckLightShafts = false;
if (Scene->GetFeatureLevel() <= ERHIFeatureLevel::ES3_1)
{
// Clear the mobile light shaft data.
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
View.bLightShaftUse = false;
View.LightShaftCenter.X = 0.0f;
View.LightShaftCenter.Y = 0.0f;
View.LightShaftColorMask = FLinearColor(0.0f,0.0f,0.0f);
View.LightShaftColorApply = FLinearColor(0.0f,0.0f,0.0f);
}
bCheckLightShafts = ViewFamily.EngineShowFlags.LightShafts && CVarLightShaftQuality.GetValueOnRenderThread() > 0;
}
if (ViewFamily.EngineShowFlags.HitProxies == 0)
{
Scene->IndirectLightingCache.UpdateCache(Scene, *this, true);
}
// determine visibility of each light
for(TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights);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)
{
const float Radius = Proxy->GetRadius();
if (View.ViewFrustum.IntersectSphere(Proxy->GetOrigin(), Radius))
{
FSphere Bounds = Proxy->GetBoundingSphere();
float DistanceSquared = (Bounds.Center - View.ViewMatrices.ViewOrigin).SizeSquared();
const bool bDrawLight = FMath::Square( FMath::Min( 0.0002f, GMinScreenRadiusForLights / Bounds.W ) * View.LODDistanceFactor ) * DistanceSquared < 1.0f;
VisibleLightViewInfo.bInViewFrustum = bDrawLight;
}
}
else
{
VisibleLightViewInfo.bInViewFrustum = true;
static const auto CVarMobileMSAA = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MobileMSAA"));
bool bNotMobileMSAA = !(CVarMobileMSAA ? CVarMobileMSAA->GetValueOnRenderThread() > 1 : false);
// Setup single sun-shaft from direction lights for mobile.
if(bCheckLightShafts && LightSceneInfo->bEnableLightShaftBloom)
{
// Find directional light for sun shafts.
// Tweaked values from UE3 implementation.
const float PointLightFadeDistanceIncrease = 200.0f;
const float PointLightRadiusFadeFactor = 5.0f;
const FVector WorldSpaceBlurOrigin = LightSceneInfo->Proxy->GetPosition();
// Transform into post projection space
FVector4 ProjectedBlurOrigin = View.WorldToScreen(WorldSpaceBlurOrigin);
const float DistanceToBlurOrigin = (View.ViewMatrices.ViewOrigin - WorldSpaceBlurOrigin).Size() + PointLightFadeDistanceIncrease;
// Don't render if the light's origin is behind the view
if(ProjectedBlurOrigin.W >= 0.0f
// Don't render point lights that have completely faded out
&& (LightSceneInfo->Proxy->GetLightType() == LightType_Directional
|| DistanceToBlurOrigin < LightSceneInfo->Proxy->GetRadius() * PointLightRadiusFadeFactor))
{
View.bLightShaftUse = bNotMobileMSAA;
View.LightShaftCenter.X = ProjectedBlurOrigin.X / ProjectedBlurOrigin.W;
View.LightShaftCenter.Y = ProjectedBlurOrigin.Y / ProjectedBlurOrigin.W;
// TODO: Might want to hookup different colors for these.
View.LightShaftColorMask = LightSceneInfo->BloomTint;
View.LightShaftColorApply = LightSceneInfo->BloomTint;
}
}
}
// 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.ViewOrigin;
float DistanceSqr = ToLight | ToLight;
float Radius = Proxy->GetRadius();
if( DistanceSqr < Radius * Radius )
{
FVector4 PositionAndInvRadius;
FVector4 ColorAndFalloffExponent;
FVector Direction;
FVector2D SpotAngles;
float SourceRadius;
float SourceLength;
float MinRoughness;
Proxy->GetParameters( PositionAndInvRadius, ColorAndFalloffExponent, Direction, SpotAngles, SourceRadius, SourceLength, MinRoughness );
// Force to be at least 0.75 pixels
float CubemapSize = 128.0f;
float Distance = FMath::Sqrt( DistanceSqr );
float MinRadius = Distance * 0.75f / CubemapSize;
SourceRadius = FMath::Max( MinRadius, 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.ViewOrigin;
FLinearColor Color( ColorAndFalloffExponent );
if( Proxy->IsInverseSquared() )
{
float LightRadiusMask = FMath::Square( 1.0f - FMath::Square( DistanceSqr * FMath::Square( PositionAndInvRadius.W ) ) );
Color *= LightRadiusMask;
// Correction for lumen units
Color *= 16.0f;
}
else
{
// Remove inverse square falloff
Color *= DistanceSqr + 1.0f;
// Apply falloff
Color *= FMath::Pow( 1.0f - DistanceSqr * FMath::Square( PositionAndInvRadius.W ), ColorAndFalloffExponent.W );
}
// Spot falloff
FVector L = ToLight.GetSafeNormal();
Color *= FMath::Square( FMath::Clamp( ( (L | Direction) - SpotAngles.X ) * SpotAngles.Y, 0.0f, 1.0f ) );
// Scale by visible area
Color /= PI * FMath::Square( SourceRadius );
// Always opaque
Color.A = 1.0f;
FViewElementPDI LightPDI( &View, NULL );
FMaterialRenderProxy* const ColoredMeshInstance = new(FMemStack::Get()) FColoredMaterialRenderProxy( GEngine->DebugMeshMaterial->GetRenderProxy(false), Color );
DrawSphere( &LightPDI, Origin, FVector( SourceRadius, SourceRadius, SourceRadius ), 8, 6, ColoredMeshInstance, SDPG_World );
}
}
}
}
// Initialize the fog constants.
InitFogConstants();
InitAtmosphereConstants();
}
uint32 GetShadowQuality();
/**
* Initialize scene's views.
* Check visibility, sort translucent items, etc.
*/
void FDeferredShadingSceneRenderer::InitViews(FRHICommandListImmediate& RHICmdList)
{
SCOPED_DRAW_EVENT(RHICmdList, InitViews);
SCOPE_CYCLE_COUNTER(STAT_InitViewsTime);
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
if (!GPostProcessing.AllowFullPostProcessing(View, FeatureLevel))
{
// Disable anti-aliasing if we are not going to be able to apply final post process effects
View.FinalPostProcessSettings.AntiAliasingMethod = AAM_None;
}
}
PreVisibilityFrameSetup(RHICmdList);
ComputeViewVisibility(RHICmdList);
PostVisibilityFrameSetup();
FVector AverageViewPosition(0);
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
AverageViewPosition += View.ViewMatrices.ViewOrigin / Views.Num();
}
SortBasePassStaticData(AverageViewPosition);
bool bDynamicShadows = ViewFamily.EngineShowFlags.DynamicShadows && GetShadowQuality() > 0;
if (bDynamicShadows && !IsSimpleDynamicLightingEnabled())
{
// Setup dynamic shadows.
InitDynamicShadows(RHICmdList);
}
// initialize per-view uniform buffer.
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
// Initialize the view's RHI resources.
Views[ViewIndex].InitRHIResources(nullptr);
}
OnStartFrame();
}
/*------------------------------------------------------------------------------
FLODSceneTree Implementation
------------------------------------------------------------------------------*/
void FLODSceneTree::AddChildNode(const FPrimitiveComponentId NodeId, FPrimitiveSceneInfo* ChildSceneInfo)
{
if (NodeId.IsValid() && ChildSceneInfo)
{
FLODSceneNode* Node = SceneNodes.Find(NodeId);
if(!Node)
{
Node = &SceneNodes.Add(NodeId, FLODSceneNode());
// scene info can be added later depending on order of adding to the scene
// but at least add componentId, that way when parent is added, it will add its info properly
int32 ParentIndex = Scene->PrimitiveComponentIds.Find(NodeId);
if(Scene->Primitives.IsValidIndex(ParentIndex))
{
Node->SceneInfo = Scene->Primitives[ParentIndex];
}
}
Node->AddChild(ChildSceneInfo);
}
}
void FLODSceneTree::RemoveChildNode(const FPrimitiveComponentId NodeId, FPrimitiveSceneInfo* ChildSceneInfo)
{
if(NodeId.IsValid() && ChildSceneInfo)
{
FLODSceneNode* Node = SceneNodes.Find(NodeId);
if (Node)
{
Node->RemoveChild(ChildSceneInfo);
// delete from scene if no children remains
if(Node->ChildrenSceneInfos.Num() == 0)
{
SceneNodes.Remove(NodeId);
}
}
}
}
void FLODSceneTree::UpdateNodeSceneInfo(FPrimitiveComponentId NodeId, FPrimitiveSceneInfo* SceneInfo)
{
FLODSceneNode* Node = SceneNodes.Find(NodeId);
if(Node)
{
Node->SceneInfo = SceneInfo;
}
}
void FLODSceneTree::PopulateHiddenFlagsToChildren(FSceneBitArray& HiddenFlags, FLODSceneNode& Node)
{
// if already updated, no reason to do this
if(Node.LatestUpdateCount != UpdateCount)
{
Node.LatestUpdateCount = UpdateCount;
// if node doesn't have scene info, that means it doesn't to populate, children is disconnected, so don't bother
// in this case we still update children when this node is missing scene info
// because parent is showing, so you don't have to show anyway anybody below
// although this node might be MIA at this moment
for(const auto& Child : Node.ChildrenSceneInfos)
{
const int32 ChildIndex = Child->GetIndex();
// first update the flags
FRelativeBitReference BitRef(ChildIndex);
HiddenFlags.AccessCorrespondingBit(BitRef) = true;
// find the node for it
FLODSceneNode* ChildNode = SceneNodes.Find(Child->PrimitiveComponentId);
// if you have child, populate it again,
if (ChildNode)
{
PopulateHiddenFlagsToChildren(HiddenFlags, *ChildNode);
}
}
}
}
void FLODSceneTree::PopulateHiddenFlags(FViewInfo& View, FSceneBitArray& HiddenFlags)
{
++UpdateCount;
// @todo this is experimental code - hide the children if parent is showing
for(auto Iter = SceneNodes.CreateIterator(); Iter; ++Iter)
{
FLODSceneNode& Node = Iter.Value();
// if already updated, no reason to do this
if(Node.LatestUpdateCount != UpdateCount)
{
Node.LatestUpdateCount = UpdateCount;
// if node doesn't have scene info, that means it doesn't have any
if(Node.SceneInfo)
{
int32 NodeIndex = Node.SceneInfo->GetIndex();
// if this node is visible, children shouldn't show up
if(View.PrimitiveVisibilityMap[NodeIndex])
{
for(const auto& Child : Node.ChildrenSceneInfos)
{
const int32 ChildIndex = Child->GetIndex();
// first update the flags
FRelativeBitReference BitRef(ChildIndex);
HiddenFlags.AccessCorrespondingBit(BitRef) = true;
// find the node for it
FLODSceneNode* ChildNode = SceneNodes.Find(Child->PrimitiveComponentId);
// if you have child, populate it again,
if(ChildNode)
{
PopulateHiddenFlagsToChildren(HiddenFlags, *ChildNode);
}
}
}
else
{
HiddenFlags.AccessCorrespondingBit(FRelativeBitReference(NodeIndex)) = true;
}
}
}
}
}