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

6950 lines
254 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
Scene.cpp: Scene manager implementation.
=============================================================================*/
#include "CoreMinimal.h"
#include "Engine/Level.h"
#include "Engine/TextureLightProfile.h"
#include "HAL/ThreadSafeCounter.h"
#include "HAL/PlatformFileManager.h"
#include "Stats/Stats.h"
#include "HAL/IConsoleManager.h"
#include "Misc/App.h"
#include "UObject/Package.h"
#include "UObject/UObjectIterator.h"
#include "Misc/PackageName.h"
#include "EngineDefines.h"
#include "EngineGlobals.h"
#include "Components/ActorComponent.h"
#include "RHI.h"
#include "RenderingThread.h"
#include "RenderResource.h"
#include "UniformBuffer.h"
#include "SceneTypes.h"
#include "SceneInterface.h"
#include "Components/PrimitiveComponent.h"
#include "PhysicsField/PhysicsFieldComponent.h"
#include "MaterialShared.h"
#include "PrimitiveDrawingUtils.h"
#include "PrecomputedLightVolume.h"
#include "PrecomputedVolumetricLightmap.h"
#include "Components/LightComponent.h"
#include "GameFramework/WorldSettings.h"
#include "Components/DecalComponent.h"
#include "Components/ReflectionCaptureComponent.h"
#include "Components/RuntimeVirtualTextureComponent.h"
#include "Components/InstancedStaticMeshComponent.h"
#include "ScenePrivateBase.h"
#include "SceneCore.h"
#include "Rendering/RayTracingGeometryManager.h"
#include "Rendering/MotionVectorSimulation.h"
#include "PrimitiveSceneInfo.h"
#include "LightSceneInfo.h"
#include "LightMapRendering.h"
#include "SkyAtmosphereRendering.h"
#include "BasePassRendering.h"
#include "MobileBasePassRendering.h"
#include "PrimitiveSceneDesc.h"
#include "InstancedStaticMeshSceneProxyDesc.h"
#include "ScenePrivate.h"
#include "SceneProxies/DeferredDecalProxy.h"
#include "SceneProxies/ReflectionCaptureProxy.h"
#include "SceneProxies/SkyAtmosphereSceneProxy.h"
#include "SceneProxies/SkyLightSceneProxy.h"
#include "SceneProxies/WindSourceSceneProxy.h"
#include "RendererModule.h"
#include "StaticMeshResources.h"
#include "ParameterCollection.h"
#include "DistanceFieldAmbientOcclusion.h"
#include "DistanceFieldAtlas.h"
#include "EngineModule.h"
#include "FXSystem.h"
#include "DistanceFieldLightingShared.h"
#include "SpeedTreeWind.h"
#include "Components/WindDirectionalSourceComponent.h"
#include "Lumen/LumenSceneData.h"
#include "PlanarReflectionSceneProxy.h"
#include "Engine/StaticMesh.h"
#include "GPUSkinCache.h"
#include "SkeletalMeshUpdater.h"
#include "ComputeSystemInterface.h"
#include "DynamicShadowMapChannelBindingHelper.h"
#include "GPUScene.h"
#include "HAL/LowLevelMemStats.h"
#include "HAL/LowLevelMemTracker.h"
#include "VT/RuntimeVirtualTextureEnum.h"
#include "VT/RuntimeVirtualTextureSceneProxy.h"
#include "VT/VirtualTextureSystem.h"
#include "HairStrandsInterface.h"
#include "VelocityRendering.h"
#include "RectLightSceneProxy.h"
#include "RectLightTextureManager.h"
#include "RenderCore.h"
#include "SceneRenderBuilder.h"
#include "IESTextureManager.h"
#include "Materials/MaterialRenderProxy.h"
#include "ProfilingDebugging/AssetMetadataTrace.h"
#include "ProfilingDebugging/CountersTrace.h"
#include "StateStream/SkinnedMeshStateStreamImpl.h"
#include "StateStream/StaticMeshStateStreamImpl.h"
#include "StateStream/ParticleSystemStateStreamImpl.h"
#include "StateStreamCreator.h"
#include "StateStreamManagerImpl.h"
#include "SceneCulling/SceneCulling.h"
#include "TransformStateStreamImpl.h"
#include "InstanceCulling/InstanceCullingOcclusionQuery.h"
#include "ComputeWorkerInterface.h"
#include "Nanite/NaniteMaterialsSceneExtension.h"
#include "Nanite/NaniteSkinningSceneExtension.h"
#include "ObjectCacheContext.h"
#include "Animation/AnimBank.h"
#include "Skinning/AnimBankTransformProvider.h"
#include "Skinning/SkinningTransformProvider.h"
#include "PathTracing.h"
#if RHI_RAYTRACING
#include "Nanite/NaniteRayTracing.h"
#include "RayTracingDynamicGeometryUpdateManager.h"
#include "RayTracing/RayTracingScene.h"
#include "RayTracing/RayTracingInstanceMask.h"
#endif
#include "RHIGPUReadback.h"
#include "ShaderPrint.h"
#include "VirtualShadowMaps/VirtualShadowMapCacheManager.h"
#include "Shadows/ShadowScene.h"
#include "VariableRateShadingImageManager.h"
#include "Streaming/SimpleStreamableAssetManager.h"
#if WITH_EDITOR
#include "Rendering/StaticLightingSystemInterface.h"
#endif
#define VALIDATE_PRIMITIVE_PACKED_INDEX 0
/** Affects BasePassPixelShader.usf so must relaunch editor to recompile shaders. */
static TAutoConsoleVariable<int32> CVarEarlyZPassOnlyMaterialMasking(
TEXT("r.EarlyZPassOnlyMaterialMasking"),
0,
TEXT("Whether to compute materials' mask opacity only in early Z pass. Changing this setting requires restarting the editor.\n")
TEXT("Note: Needs r.EarlyZPass == 2 && r.EarlyZPassMovable == 1"),
ECVF_RenderThreadSafe | ECVF_ReadOnly
);
TAutoConsoleVariable<int32> CVarEarlyZPass(
TEXT("r.EarlyZPass"),
3,
TEXT("Whether to use a depth only pass to initialize Z culling for the base pass. Cannot be changed at runtime.\n")
TEXT("Note: also look at r.EarlyZPassMovable\n")
TEXT(" 0: off\n")
TEXT(" 1: good occluders only: not masked, and large on screen\n")
TEXT(" 2: all opaque (including masked)\n")
TEXT(" x: use built in heuristic (default is 3)"),
ECVF_Scalability);
static TAutoConsoleVariable<int32> CVarMobileEarlyZPass(
TEXT("r.Mobile.EarlyZPass"),
0,
TEXT("Whether to use a depth only pass to initialize Z culling for the mobile base pass. Changing this setting requires restarting the editor.\n")
TEXT(" 0: off\n")
TEXT(" 1: all opaque \n")
TEXT(" 2: masked primitives only \n"),
ECVF_ReadOnly
);
static TAutoConsoleVariable<int32> CVarBasePassWriteDepthEvenWithFullPrepass(
TEXT("r.BasePassWriteDepthEvenWithFullPrepass"),
0,
TEXT("0 to allow a readonly base pass, which skips an MSAA depth resolve, and allows masked materials to get EarlyZ (writing to depth while doing clip() disables EarlyZ) (default)\n")
TEXT("1 to force depth writes in the base pass. Useful for debugging when the prepass and base pass don't match what they render."));
int32 GVisibilitySkipAlwaysVisible = 1;
static FAutoConsoleVariableRef CVarVisibilitySkipAlwaysVisible(
TEXT("r.Visibility.SkipAlwaysVisible"),
GVisibilitySkipAlwaysVisible,
TEXT("Whether visibility passes should skip primitives marked always visible")
TEXT("0: All primitives are processed by visibility passes")
TEXT("1: Only primitives not marked with bAlwaysVisible will be processed by visibility passes"),
ECVF_RenderThreadSafe
);
static TAutoConsoleVariable<int32> CVarVisibilityLocalLightPrimitiveInteraction(
TEXT("r.Visibility.LocalLightPrimitiveInteraction"),
1,
TEXT("Whether to allow computing local primitive interactions. May greatly speedup render thread time if not needed."),
ECVF_Scalability | ECVF_RenderThreadSafe
);
DECLARE_CYCLE_STAT(TEXT("DeferredShadingSceneRenderer MotionBlurStartFrame"), STAT_FDeferredShadingSceneRenderer_MotionBlurStartFrame, STATGROUP_SceneRendering);
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FDistanceCullFadeUniformShaderParameters, "PrimitiveFade");
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FDitherUniformShaderParameters, "PrimitiveDither");
TRACE_DECLARE_INT_COUNTER(ScenePrimitives, TEXT("Scene/Primitives"));
TRACE_DECLARE_INT_COUNTER(ScenePrimitivesAdds, TEXT("Scene/Primitives/Adds"));
TRACE_DECLARE_INT_COUNTER(ScenePrimitivesRemoves, TEXT("Scene/Primitives/Removes"));
TRACE_DECLARE_INT_COUNTER(ScenePrimitivesUpdatesTransforms, TEXT("Scene/Primitives/Updates/Transforms"));
TRACE_DECLARE_INT_COUNTER(ScenePrimitivesUpdatesInstances, TEXT("Scene/Primitives/Updates/Instances"));
TRACE_DECLARE_INT_COUNTER(ScenePrimitivesArrayMax, TEXT("Scene/PrimitiveArrayMax"));
TRACE_DECLARE_INT_COUNTER(SceneLights, TEXT("Scene/Lights"));
/** Global primitive uniform buffer resource containing distance cull faded in */
TGlobalResource< FGlobalDistanceCullFadeUniformBuffer > GDistanceCullFadedInUniformBuffer;
/** Global primitive uniform buffer resource containing dither faded in */
TGlobalResource< FGlobalDitherUniformBuffer > GDitherFadedInUniformBuffer;
static FThreadSafeCounter FSceneViewState_UniqueID;
// Maintained on the render thread, must not be accessed on any other thread (except worker threads within the scope of a RT command).
static TSet<uint32> FSceneViewState_ActiveUniqueIDs;
/**
* Holds the info to update SpeedTree wind per unique tree object in the scene, instead of per instance
*/
struct FSpeedTreeWindComputation
{
explicit FSpeedTreeWindComputation() :
ReferenceCount(1)
{
}
/** SpeedTree wind object */
FSpeedTreeWind Wind;
/** Uniform buffer shared between trees of the same type. */
TUniformBufferRef<FSpeedTreeUniformParameters> UniformBuffer;
int32 ReferenceCount;
};
FPersistentSkyAtmosphereData::FPersistentSkyAtmosphereData()
: bInitialised(false)
, CurrentScreenResolution(0)
, CurrentDepthResolution(0)
, CurrentTextureAerialLUTFormat(PF_Unknown)
, CameraAerialPerspectiveVolumeIndex(0)
, bSeparatedAtmosphereMieRayLeigh(false)
{
}
void FPersistentSkyAtmosphereData::InitialiseOrNextFrame(ERHIFeatureLevel::Type FeatureLevel, FPooledRenderTargetDesc& AerialPerspectiveDesc, FRHICommandListImmediate& RHICmdList, bool bSeparatedAtmosphereMieRayLeighIn)
{
if (!bInitialised || (bInitialised && ((AerialPerspectiveDesc.Extent.X != CurrentScreenResolution) || (AerialPerspectiveDesc.Depth != CurrentDepthResolution)
|| (AerialPerspectiveDesc.Format != CurrentTextureAerialLUTFormat) || (bSeparatedAtmosphereMieRayLeigh != bSeparatedAtmosphereMieRayLeighIn))))
{
bSeparatedAtmosphereMieRayLeigh = bSeparatedAtmosphereMieRayLeighIn;
CameraAerialPerspectiveVolumeCount = FeatureLevel == ERHIFeatureLevel::ES3_1 ? 2 : 1;
for (int i = 0; i < CameraAerialPerspectiveVolumeCount; ++i)
{
GRenderTargetPool.FindFreeElement(RHICmdList, AerialPerspectiveDesc, CameraAerialPerspectiveVolumes[i],
i==0 ? TEXT("SkyAtmosphere.CameraAPVolume0") : TEXT("SkyAtmosphere.CameraAPVolume1"));
if (bSeparatedAtmosphereMieRayLeigh)
{
GRenderTargetPool.FindFreeElement(RHICmdList, AerialPerspectiveDesc, CameraAerialPerspectiveVolumesMieOnly[i],
i == 0 ? TEXT("SkyAtmosphere.CameraAPVolumeMieOnly0") : TEXT("SkyAtmosphere.CameraAPVolumeMieOnly1"));
GRenderTargetPool.FindFreeElement(RHICmdList, AerialPerspectiveDesc, CameraAerialPerspectiveVolumesRayOnly[i],
i == 0 ? TEXT("SkyAtmosphere.CameraAPVolumeRayOnly0") : TEXT("SkyAtmosphere.CameraAPVolumeRayOnly1"));
}
else
{
CameraAerialPerspectiveVolumesMieOnly[i] = nullptr;
CameraAerialPerspectiveVolumesRayOnly[i] = nullptr;
}
}
bInitialised = true;
CurrentScreenResolution = AerialPerspectiveDesc.Extent.X;
CurrentDepthResolution = AerialPerspectiveDesc.Depth;
CurrentTextureAerialLUTFormat = AerialPerspectiveDesc.Format;
}
CameraAerialPerspectiveVolumeIndex = (CameraAerialPerspectiveVolumeIndex + 1) % CameraAerialPerspectiveVolumeCount;
}
TRefCountPtr<IPooledRenderTarget> FPersistentSkyAtmosphereData::GetCurrentCameraAerialPerspectiveVolume()
{
check(CameraAerialPerspectiveVolumes[CameraAerialPerspectiveVolumeIndex].IsValid());
return CameraAerialPerspectiveVolumes[CameraAerialPerspectiveVolumeIndex];
}
TRefCountPtr<IPooledRenderTarget> FPersistentSkyAtmosphereData::GetCurrentCameraAerialPerspectiveVolumeMieOnly()
{
check(CameraAerialPerspectiveVolumesMieOnly[CameraAerialPerspectiveVolumeIndex].IsValid());
return CameraAerialPerspectiveVolumesMieOnly[CameraAerialPerspectiveVolumeIndex];
}
TRefCountPtr<IPooledRenderTarget> FPersistentSkyAtmosphereData::GetCurrentCameraAerialPerspectiveVolumeRayOnly()
{
check(CameraAerialPerspectiveVolumesRayOnly[CameraAerialPerspectiveVolumeIndex].IsValid());
return CameraAerialPerspectiveVolumesRayOnly[CameraAerialPerspectiveVolumeIndex];
}
/** Default constructor. */
FSceneViewState::FSceneViewState(ERHIFeatureLevel::Type FeatureLevel, FSceneViewState* ShareOriginTarget)
: OcclusionQueryPool(RHICreateRenderQueryPool(RQT_Occlusion))
{
// Set FeatureLevel to a valid value, so we get Init/ReleaseRHI calls on FeatureLevel changes
SetFeatureLevel(FeatureLevel);
UniqueID = FSceneViewState_UniqueID.Increment();
ENQUEUE_RENDER_COMMAND(SceneViewStateAdddUniqueID)(
[UniqueID = UniqueID] (FRHICommandListBase&)
{
FSceneViewState_ActiveUniqueIDs.Add(UniqueID);
});
Scene = nullptr;
OcclusionFrameCounter = 0;
LastRenderTime = -FLT_MAX;
MotionBlurTimeScale = 1.0f;
MotionBlurTargetDeltaTime = 1.0f / 60.0f; // Start with a reasonable default of 60hz.
PrevViewMatrixForOcclusionQuery.SetIdentity();
PrevViewOriginForOcclusionQuery = FVector::ZeroVector;
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
bIsFreezing = false;
bIsFrozen = false;
bIsFrozenViewMatricesCached = false;
#endif
// Register this object as a resource, so it will receive device reset notifications.
if ( IsInGameThread() )
{
BeginInitResource(this);
}
else
{
InitResource(FRHICommandListImmediate::Get());
}
CachedVisibilityChunk = NULL;
CachedVisibilityHandlerId = INDEX_NONE;
CachedVisibilityBucketIndex = INDEX_NONE;
CachedVisibilityChunkIndex = INDEX_NONE;
MIDUsedCount = 0;
TemporalAASampleIndex = 0;
FrameIndex = 0;
OutputFrameIndex = 0;
DistanceFieldTemporalSampleIndex = 0;
// Sets the mipbias to invalid large number.
MaterialTextureCachedMipBias = BIG_NUMBER;
LandscapeCachedMipBias = BIG_NUMBER;
SequencerState = ESS_None;
bIsStereoView = false;
bRoundRobinOcclusionEnabled = false;
if (ShareOriginTarget)
{
GlobalDistanceFieldData = ShareOriginTarget->GlobalDistanceFieldData;
ShareOriginUniqueID = ShareOriginTarget->UniqueID;
}
else
{
GlobalDistanceFieldData = new FPersistentGlobalDistanceFieldData;
ShareOriginUniqueID = UniqueID;
}
ShadowOcclusionQueryMaps.Empty(FOcclusionQueryHelpers::MaxBufferedOcclusionFrames);
ShadowOcclusionQueryMaps.AddZeroed(FOcclusionQueryHelpers::MaxBufferedOcclusionFrames);
PreExposure = 1.f;
bUpdateLastExposure = false;
bLumenSceneDataAdded = false;
LumenSurfaceCacheResolution = 1.0f;
// OcclusionFeedback works only with mobile rendering atm
if (FeatureLevel == ERHIFeatureLevel::ES3_1)
{
extern int32 GOcclusionFeedback_Enable;
if (GOcclusionFeedback_Enable != 0)
{
BeginInitResource(&OcclusionFeedback);
}
}
Occlusion.LastOcclusionQueryArray.SetNumZeroed(FOcclusionQueryHelpers::MaxBufferedOcclusionFrames);
}
FSceneViewState::~FSceneViewState()
{
check(IsInRenderingThread());
FSceneViewState_ActiveUniqueIDs.Remove(UniqueID);
CachedVisibilityChunk = NULL;
ShadowOcclusionQueryMaps.Reset();
HairStrandsViewStateData.Release();
ShaderPrintStateData.Release();
if (Scene)
{
Scene->RemoveViewState_RenderThread(this);
}
}
void FSceneViewState::ReleaseRHI()
{
// FSceneViewState contains multiple sub-components that have GPU readback buffers which can have in-flight async copy
// passes registered with RDG (e.g. AddEnqueueCopyPass), so we need to sync the execution tasks prior to releasing those buffers.
FRDGBuilder::WaitForAsyncExecuteTask();
HZBOcclusionTests.ReleaseRHI();
EyeAdaptationManager.SafeRelease();
SubstrateViewDebugData.SafeRelease();
OcclusionFeedback.ReleaseResource();
PRAGMA_DISABLE_DEPRECATION_WARNINGS
bValidEyeAdaptationTexture = false;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
bValidEyeAdaptationBuffer = false;
}
inline FScene::FPrimitiveSceneProxyType::FPrimitiveSceneProxyType(const FPrimitiveSceneProxy *PrimitiveSceneProxy)
: ProxyTypeHash(PrimitiveSceneProxy->GetTypeHash())
, bIsAlwaysVisible(PrimitiveSceneProxy->IsAlwaysVisible())
{
}
void FScene::RemoveViewLumenSceneData_RenderThread(FSceneViewStateInterface* ViewState)
{
FLumenSceneDataKey ByViewKey = { ViewState->GetViewKey(), (uint32)INDEX_NONE };
FLumenSceneData* const* Found = PerViewOrGPULumenSceneData.Find(ByViewKey);
if (Found)
{
delete* Found;
PerViewOrGPULumenSceneData.Remove(ByViewKey);
}
}
void FScene::RemoveViewState_RenderThread(FSceneViewStateInterface* ViewState)
{
for (int32 ViewStateIndex = 0; ViewStateIndex < ViewStates.Num(); ViewStateIndex++)
{
if (ViewStates[ViewStateIndex] == ViewState)
{
ViewStates.RemoveAt(ViewStateIndex);
break;
}
}
RemoveViewLumenSceneData_RenderThread(ViewState);
}
#if WITH_EDITOR
FPixelInspectorData::FPixelInspectorData()
{
for (int32 i = 0; i < 2; ++i)
{
RenderTargetBufferFinalColor[i] = nullptr;
RenderTargetBufferDepth[i] = nullptr;
RenderTargetBufferSceneColor[i] = nullptr;
RenderTargetBufferHDR[i] = nullptr;
RenderTargetBufferA[i] = nullptr;
RenderTargetBufferBCDEF[i] = nullptr;
}
}
void FPixelInspectorData::InitializeBuffers(FRenderTarget* BufferFinalColor, FRenderTarget* BufferSceneColor, FRenderTarget* BufferDepth, FRenderTarget* BufferHDR, FRenderTarget* BufferA, FRenderTarget* BufferBCDEF, int32 BufferIndex)
{
RenderTargetBufferFinalColor[BufferIndex] = BufferFinalColor;
RenderTargetBufferDepth[BufferIndex] = BufferDepth;
RenderTargetBufferSceneColor[BufferIndex] = BufferSceneColor;
RenderTargetBufferHDR[BufferIndex] = BufferHDR;
RenderTargetBufferA[BufferIndex] = BufferA;
RenderTargetBufferBCDEF[BufferIndex] = BufferBCDEF;
check(RenderTargetBufferBCDEF[BufferIndex] != nullptr);
FIntPoint BufferSize = RenderTargetBufferBCDEF[BufferIndex]->GetSizeXY();
check(BufferSize.X == 4 && BufferSize.Y == 1);
if (RenderTargetBufferA[BufferIndex] != nullptr)
{
BufferSize = RenderTargetBufferA[BufferIndex]->GetSizeXY();
check(BufferSize.X == 1 && BufferSize.Y == 1);
}
if (RenderTargetBufferFinalColor[BufferIndex] != nullptr)
{
BufferSize = RenderTargetBufferFinalColor[BufferIndex]->GetSizeXY();
//The Final color grab an area and can change depending on the setup
//It should at least contain 1 pixel but can be 3x3 or more
check(BufferSize.X > 0 && BufferSize.Y > 0);
}
if (RenderTargetBufferDepth[BufferIndex] != nullptr)
{
BufferSize = RenderTargetBufferDepth[BufferIndex]->GetSizeXY();
check(BufferSize.X == 1 && BufferSize.Y == 1);
}
if (RenderTargetBufferSceneColor[BufferIndex] != nullptr)
{
BufferSize = RenderTargetBufferSceneColor[BufferIndex]->GetSizeXY();
check(BufferSize.X == 1 && BufferSize.Y == 1);
}
if (RenderTargetBufferHDR[BufferIndex] != nullptr)
{
BufferSize = RenderTargetBufferHDR[BufferIndex]->GetSizeXY();
check(BufferSize.X == 1 && BufferSize.Y == 1);
}
}
bool FPixelInspectorData::AddPixelInspectorRequest(FPixelInspectorRequest *PixelInspectorRequest)
{
if (PixelInspectorRequest == nullptr)
return false;
FVector2f ViewportUV = PixelInspectorRequest->SourceViewportUV;
if (Requests.Contains(ViewportUV))
return false;
//Remove the oldest request since the new request use the buffer
if (Requests.Num() > 1)
{
auto It = Requests.CreateIterator();
It.RemoveCurrent();
}
Requests.Add(ViewportUV, PixelInspectorRequest);
return true;
}
#endif //WITH_EDITOR
bool IncludePrimitiveInDistanceFieldSceneData(bool bTrackAllPrimitives, const FPrimitiveSceneProxy* Proxy)
{
return PrimitiveNeedsDistanceFieldSceneData(
bTrackAllPrimitives,
Proxy->CastsDynamicIndirectShadow(),
Proxy->AffectsDistanceFieldLighting(),
Proxy->IsDrawnInGame(),
Proxy->CastsHiddenShadow(),
Proxy->CastsDynamicShadow(),
Proxy->AffectsDynamicIndirectLighting(),
Proxy->AffectsIndirectLightingWhileHidden());
}
void FDistanceFieldSceneData::AddPrimitive(FPrimitiveSceneInfo* InPrimitive)
{
FPrimitiveSceneProxy* Proxy = InPrimitive->Proxy;
if (IncludePrimitiveInDistanceFieldSceneData(bTrackAllPrimitives, Proxy))
{
if (Proxy->SupportsHeightfieldRepresentation())
{
UTexture2D* HeightAndNormal;
UTexture2D* Visibility;
FHeightfieldComponentDescription Desc(FMatrix::Identity, InPrimitive->GetInstanceSceneDataOffset());
Proxy->GetHeightfieldRepresentation(HeightAndNormal, Visibility, Desc);
GHeightFieldTextureAtlas.AddAllocation(HeightAndNormal);
if (Visibility)
{
check(Desc.VisibilityChannel >= 0 && Desc.VisibilityChannel < 4);
GHFVisibilityTextureAtlas.AddAllocation(Visibility, Desc.VisibilityChannel);
}
checkSlow(!PendingHeightFieldAddOps.Contains(InPrimitive));
PendingHeightFieldAddOps.Add(InPrimitive);
}
if (Proxy->SupportsDistanceFieldRepresentation())
{
checkSlow(!PendingAddOperations.Contains(InPrimitive));
checkSlow(!PendingUpdateOperations.Contains(InPrimitive));
PendingAddOperations.Add(InPrimitive);
}
}
}
void FDistanceFieldSceneData::UpdatePrimitive(FPrimitiveSceneInfo* InPrimitive)
{
const FPrimitiveSceneProxy* Proxy = InPrimitive->Proxy;
if (IncludePrimitiveInDistanceFieldSceneData(bTrackAllPrimitives, Proxy)
&& Proxy->SupportsDistanceFieldRepresentation()
&& !PendingAddOperations.Contains(InPrimitive)
// This is needed to prevent infinite buildup when DF features are off such that the pending operations don't get consumed
&& !PendingUpdateOperations.Contains(InPrimitive)
// This can happen when the primitive fails to allocate from the SDF atlas
&& InPrimitive->DistanceFieldInstanceIndices.Num() > 0)
{
PendingUpdateOperations.Add(InPrimitive);
}
}
void FDistanceFieldSceneData::RemovePrimitive(FPrimitiveSceneInfo* InPrimitive)
{
FPrimitiveSceneProxy* Proxy = InPrimitive->Proxy;
if (IncludePrimitiveInDistanceFieldSceneData(bTrackAllPrimitives, Proxy))
{
if (Proxy->SupportsDistanceFieldRepresentation())
{
PendingAddOperations.Remove(InPrimitive);
PendingUpdateOperations.Remove(InPrimitive);
if (InPrimitive->DistanceFieldInstanceIndices.Num() > 0)
{
PendingRemoveOperations.Add(FPrimitiveRemoveInfo(InPrimitive));
}
InPrimitive->DistanceFieldInstanceIndices.Empty();
}
if (Proxy->SupportsHeightfieldRepresentation())
{
UTexture2D* HeightAndNormal;
UTexture2D* Visibility;
FHeightfieldComponentDescription Desc(FMatrix::Identity, InPrimitive->GetInstanceSceneDataOffset());
Proxy->GetHeightfieldRepresentation(HeightAndNormal, Visibility, Desc);
GHeightFieldTextureAtlas.RemoveAllocation(HeightAndNormal);
if (Visibility)
{
GHFVisibilityTextureAtlas.RemoveAllocation(Visibility);
}
PendingHeightFieldAddOps.Remove(InPrimitive);
if (InPrimitive->DistanceFieldInstanceIndices.Num() > 0)
{
PendingHeightFieldRemoveOps.Add(FHeightFieldPrimitiveRemoveInfo(InPrimitive));
}
InPrimitive->DistanceFieldInstanceIndices.Empty();
}
}
checkf(!PendingAddOperations.Contains(InPrimitive), TEXT("Primitive is being removed from the scene, but didn't remove from Distance Field Scene properly - a crash will occur when processing PendingAddOperations. This can happen if the proxy's properties have changed without recreating its render state."));
checkf(!PendingUpdateOperations.Contains(InPrimitive), TEXT("Primitive is being removed from the scene, but didn't remove from Distance Field Scene properly - a crash will occur when processing PendingUpdateOperations. This can happen if the proxy's properties have changed without recreating its render state."));
checkf(!PendingHeightFieldAddOps.Contains(InPrimitive), TEXT("Primitive is being removed from the scene, but didn't remove from Distance Field Scene properly - a crash will occur when processing PendingHeightFieldAddOps. This can happen if the proxy's properties have changed without recreating its render state."));
}
void FDistanceFieldSceneData::Release()
{
if (ObjectBuffers != nullptr)
{
ObjectBuffers->Release();
}
for (int32 BufferIndex = 0; BufferIndex < StreamingRequestReadbackBuffers.Num(); ++BufferIndex)
{
if (StreamingRequestReadbackBuffers[BufferIndex])
{
delete StreamingRequestReadbackBuffers[BufferIndex];
StreamingRequestReadbackBuffers[BufferIndex] = nullptr;
}
}
}
void FDistanceFieldSceneData::VerifyIntegrity()
{
#if DO_CHECK
check(NumObjectsInBuffer == PrimitiveInstanceMapping.Num());
for (int32 PrimitiveInstanceIndex = 0; PrimitiveInstanceIndex < PrimitiveInstanceMapping.Num(); PrimitiveInstanceIndex++)
{
const FPrimitiveAndInstance& PrimitiveAndInstance = PrimitiveInstanceMapping[PrimitiveInstanceIndex];
check(PrimitiveAndInstance.Primitive && PrimitiveAndInstance.Primitive->DistanceFieldInstanceIndices.Num() > 0);
check(PrimitiveAndInstance.Primitive->DistanceFieldInstanceIndices.IsValidIndex(PrimitiveAndInstance.InstanceIndex));
const int32 InstanceIndex = PrimitiveAndInstance.Primitive->DistanceFieldInstanceIndices[PrimitiveAndInstance.InstanceIndex];
check(InstanceIndex == PrimitiveInstanceIndex || InstanceIndex == -1);
}
#endif
}
void FScene::UpdateSceneSettings(AWorldSettings* WorldSettings)
{
FScene* Scene = this;
float InDefaultMaxDistanceFieldOcclusionDistance = WorldSettings->DefaultMaxDistanceFieldOcclusionDistance;
float InGlobalDistanceFieldViewDistance = WorldSettings->GlobalDistanceFieldViewDistance;
float InDynamicIndirectShadowsSelfShadowingIntensity = FMath::Clamp(WorldSettings->DynamicIndirectShadowsSelfShadowingIntensity, 0.0f, 1.0f);
ENQUEUE_RENDER_COMMAND(UpdateSceneSettings)(
[Scene, InDefaultMaxDistanceFieldOcclusionDistance, InGlobalDistanceFieldViewDistance, InDynamicIndirectShadowsSelfShadowingIntensity] (FRHICommandListBase&)
{
Scene->DefaultMaxDistanceFieldOcclusionDistance = InDefaultMaxDistanceFieldOcclusionDistance;
Scene->GlobalDistanceFieldViewDistance = InGlobalDistanceFieldViewDistance;
Scene->DynamicIndirectShadowsSelfShadowingIntensity = InDynamicIndirectShadowsSelfShadowingIntensity;
});
}
/**
* Sets the FX system associated with the scene.
*/
void FScene::SetFXSystem( class FFXSystemInterface* InFXSystem )
{
FXSystem = InFXSystem;
}
/**
* Get the FX system associated with the scene.
*/
FFXSystemInterface* FScene::GetFXSystem()
{
return FXSystem;
}
FLumenSceneData* FScene::FindLumenSceneData(uint32 ViewKey, uint32 GPUIndex) const
{
// First search by ViewKey
FLumenSceneDataKey ByViewKey = { ViewKey, (uint32)INDEX_NONE };
FLumenSceneData* const* Found = PerViewOrGPULumenSceneData.Find(ByViewKey);
if (Found)
{
return *Found;
}
// Then search by GPU
FLumenSceneDataKey ByGPUIndex = { 0, GPUIndex };
Found = PerViewOrGPULumenSceneData.Find(ByGPUIndex);
if (Found)
{
return *Found;
}
// If both fail, return default
return DefaultLumenSceneData;
}
void FScene::UpdateParameterCollections(const TArray<FMaterialParameterCollectionInstanceResource*>& InParameterCollections)
{
ENQUEUE_RENDER_COMMAND(UpdateParameterCollectionsCommand)(
[this, InParameterCollections] (FRHICommandListBase&)
{
// Async RDG tasks can call FMaterialShader::SetParameters which touch material parameter collections.
FRDGBuilder::WaitForAsyncExecuteTask();
// Empty the scene's map so any unused uniform buffers will be released
ParameterCollections.Empty();
// Add each existing parameter collection id and its uniform buffer
for (int32 CollectionIndex = 0; CollectionIndex < InParameterCollections.Num(); CollectionIndex++)
{
FMaterialParameterCollectionInstanceResource* InstanceResource = InParameterCollections[CollectionIndex];
ParameterCollections.Add(InstanceResource->GetId(), InstanceResource->GetUniformBuffer());
}
});
}
bool FScene::RequestGPUSceneUpdate(FPrimitiveSceneInfo& PrimitiveSceneInfo, EPrimitiveDirtyState PrimitiveDirtyState)
{
return PrimitiveSceneInfo.RequestGPUSceneUpdate(PrimitiveDirtyState);
}
bool FScene::RequestUniformBufferUpdate(FPrimitiveSceneInfo& PrimitiveSceneInfo)
{
return PrimitiveSceneInfo.RequestUniformBufferUpdate();
}
void FScene::RefreshNaniteRasterBins(FPrimitiveSceneInfo& PrimitiveSceneInfo)
{
PrimitiveSceneInfo.RefreshNaniteRasterBins();
}
void FScene::ReloadNaniteFixedFunctionBins()
{
for (int32 NanitePass = 0; NanitePass < ENaniteMeshPass::Num; ++NanitePass)
{
NaniteRasterPipelines[NanitePass].ReloadFixedFunctionBins();
}
}
SIZE_T FScene::GetSizeBytes() const
{
return sizeof(*this)
+ Primitives.GetAllocatedSize()
+ Lights.GetAllocatedSize()
+ StaticMeshes.GetAllocatedSize()
+ ExponentialFogs.GetAllocatedSize()
+ WindSources.GetAllocatedSize()
+ SpeedTreeVertexFactoryMap.GetAllocatedSize()
+ SpeedTreeWindComputationMap.GetAllocatedSize()
+ LocalShadowCastingLightOctree.GetSizeBytes()
+ PrimitiveOctree.GetSizeBytes();
}
void FScene::OnWorldCleanup()
{
UniformBuffers.Clear();
}
void FScene::CheckPrimitiveArrays(int MaxTypeOffsetIndex)
{
check(Primitives.Num() == PrimitiveTransforms.Num());
check(Primitives.Num() == PrimitiveSceneProxies.Num());
check(Primitives.Num() == PrimitiveBounds.Num());
check(Primitives.Num() == PrimitiveFlagsCompact.Num());
check(Primitives.Num() == PrimitiveVisibilityIds.Num());
check(Primitives.Num() == PrimitiveOctreeIndex.Num());
check(Primitives.Num() == PrimitiveOcclusionFlags.Num());
check(Primitives.Num() == PrimitiveComponentIds.Num());
check(Primitives.Num() == PrimitiveOcclusionBounds.Num());
#if WITH_EDITOR
check(Primitives.Num() == PrimitivesSelected.Num());
#endif
#if RHI_RAYTRACING
check(Primitives.Num() == PrimitiveRayTracingFlags.Num());
check(Primitives.Num() == PrimitiveRayTracingDatas.Num());
check(Primitives.Num() == PrimitiveRayTracingGroupIds.Num());
#endif
check(Primitives.Num() == PrimitivesNeedingStaticMeshUpdate.Num());
check(Primitives.Num() == PrimitivesNeedingUniformBufferUpdate.Num());
#if UE_BUILD_DEBUG
MaxTypeOffsetIndex = MaxTypeOffsetIndex == INDEX_NONE ? TypeOffsetTable.Num() : MaxTypeOffsetIndex;
for (int32 i = 0; i < MaxTypeOffsetIndex; i++)
{
for (int32 j = i + 1; j < MaxTypeOffsetIndex; j++)
{
check(TypeOffsetTable[i].PrimitiveSceneProxyType != TypeOffsetTable[j].PrimitiveSceneProxyType);
check(TypeOffsetTable[i].Offset <= TypeOffsetTable[j].Offset);
}
}
uint32 NextOffset = 0;
for (int32 i = 0; i < MaxTypeOffsetIndex; i++)
{
const FTypeOffsetTableEntry& Entry = TypeOffsetTable[i];
for (uint32 Index = NextOffset; Index < Entry.Offset; Index++)
{
checkSlow(Primitives[Index]->Proxy == PrimitiveSceneProxies[Index]);
FPrimitiveSceneProxyType PrimitiveSceneProxyType = FPrimitiveSceneProxyType(PrimitiveSceneProxies[Index]);
checkfSlow(PrimitiveSceneProxyType == Entry.PrimitiveSceneProxyType, TEXT("TypeHash: %i not matching, expected: %i"), PrimitiveSceneProxyType.ProxyTypeHash, Entry.PrimitiveSceneProxyType.ProxyTypeHash);
}
NextOffset = Entry.Offset;
}
#endif
}
static void UpdateEarlyZPassModeCVarSinkFunction()
{
static auto* CVarAntiAliasingMethod = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AntiAliasingMethod"));
static auto* CVarMSAACount = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MSAACount"));
static int32 CachedAntiAliasingMethod = CVarAntiAliasingMethod->GetValueOnGameThread();
static int32 CachedMSAACount = CVarMSAACount->GetValueOnGameThread();
static int32 CachedEarlyZPass = CVarEarlyZPass.GetValueOnGameThread();
static int32 CachedBasePassWriteDepthEvenWithFullPrepass = CVarBasePassWriteDepthEvenWithFullPrepass.GetValueOnGameThread();
const int32 AntiAliasingMethod = CVarAntiAliasingMethod->GetValueOnGameThread();
const int32 MSAACount = CVarMSAACount->GetValueOnGameThread();
const int32 EarlyZPass = CVarEarlyZPass.GetValueOnGameThread();
const int32 BasePassWriteDepthEvenWithFullPrepass = CVarBasePassWriteDepthEvenWithFullPrepass.GetValueOnGameThread();
// Switching between MSAA and another AA in forward shading mode requires EarlyZPassMode to update.
if (AntiAliasingMethod != CachedAntiAliasingMethod
|| MSAACount != CachedMSAACount
|| EarlyZPass != CachedEarlyZPass
|| BasePassWriteDepthEvenWithFullPrepass != CachedBasePassWriteDepthEvenWithFullPrepass)
{
for (TObjectIterator<UWorld> It; It; ++It)
{
UWorld* World = *It;
if (World && World->Scene)
{
World->Scene->UpdateEarlyZPassMode();
}
}
CachedAntiAliasingMethod = AntiAliasingMethod;
CachedMSAACount = MSAACount;
CachedEarlyZPass = EarlyZPass;
CachedBasePassWriteDepthEvenWithFullPrepass = BasePassWriteDepthEvenWithFullPrepass;
}
}
static FAutoConsoleVariableSink CVarUpdateEarlyZPassModeSink(FConsoleCommandDelegate::CreateStatic(&UpdateEarlyZPassModeCVarSinkFunction));
void FScene::DumpMeshDrawCommandMemoryStats()
{
SIZE_T TotalCachedMeshDrawCommands = 0;
SIZE_T TotalStaticMeshCommandInfos = 0;
struct FPassStats
{
SIZE_T CachedMeshDrawCommandBytes = 0;
SIZE_T PSOBytes = 0;
SIZE_T ShaderBindingInlineBytes = 0;
SIZE_T ShaderBindingHeapBytes = 0;
SIZE_T VertexStreamsInlineBytes = 0;
SIZE_T DebugDataBytes = 0;
SIZE_T DrawCommandParameterBytes = 0;
uint32 NumCommands = 0;
};
FPassStats AllPassStats[EMeshPass::Num];
TArray<bool> StateBucketAccounted[EMeshPass::Num];
for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++)
{
StateBucketAccounted[PassIndex].Empty(CachedMeshDrawCommandStateBuckets[PassIndex].GetMaxIndex());
StateBucketAccounted[PassIndex].AddZeroed(CachedMeshDrawCommandStateBuckets[PassIndex].GetMaxIndex());
}
for (int32 i = 0; i < Primitives.Num(); i++)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = Primitives[i];
TotalStaticMeshCommandInfos += PrimitiveSceneInfo->StaticMeshCommandInfos.GetAllocatedSize();
for (int32 CommandIndex = 0; CommandIndex < PrimitiveSceneInfo->StaticMeshCommandInfos.Num(); ++CommandIndex)
{
const FCachedMeshDrawCommandInfo& CachedCommand = PrimitiveSceneInfo->StaticMeshCommandInfos[CommandIndex];
int PassIndex = CachedCommand.MeshPass;
const FMeshDrawCommand* MeshDrawCommandPtr = nullptr;
if (CachedCommand.StateBucketId != INDEX_NONE)
{
if (!StateBucketAccounted[PassIndex][CachedCommand.StateBucketId])
{
StateBucketAccounted[PassIndex][CachedCommand.StateBucketId] = true;
MeshDrawCommandPtr = &CachedMeshDrawCommandStateBuckets[PassIndex].GetByElementId(CachedCommand.StateBucketId).Key;
}
}
else if (CachedCommand.CommandIndex >= 0)
{
FCachedPassMeshDrawList& PassDrawList = CachedDrawLists[CachedCommand.MeshPass];
MeshDrawCommandPtr = &PassDrawList.MeshDrawCommands[CachedCommand.CommandIndex];
}
if (MeshDrawCommandPtr)
{
const FMeshDrawCommand& MeshDrawCommand = *MeshDrawCommandPtr;
FPassStats& PassStats = AllPassStats[CachedCommand.MeshPass];
SIZE_T CommandBytes = sizeof(MeshDrawCommand) + MeshDrawCommand.GetAllocatedSize();
PassStats.CachedMeshDrawCommandBytes += CommandBytes;
TotalCachedMeshDrawCommands += MeshDrawCommand.GetAllocatedSize();
PassStats.PSOBytes += sizeof(MeshDrawCommand.CachedPipelineId);
PassStats.ShaderBindingInlineBytes += sizeof(MeshDrawCommand.ShaderBindings);
PassStats.ShaderBindingHeapBytes += MeshDrawCommand.ShaderBindings.GetAllocatedSize();
PassStats.VertexStreamsInlineBytes += sizeof(MeshDrawCommand.VertexStreams);
PassStats.DebugDataBytes += MeshDrawCommand.GetDebugDataSize();
PassStats.DrawCommandParameterBytes += sizeof(MeshDrawCommand.IndexBuffer) + sizeof(MeshDrawCommand.FirstIndex) + sizeof(MeshDrawCommand.NumPrimitives) + sizeof(MeshDrawCommand.NumInstances) + sizeof(MeshDrawCommand.VertexParams); //-V568
PassStats.NumCommands++;
}
}
}
for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++)
{
TotalCachedMeshDrawCommands += CachedMeshDrawCommandStateBuckets[PassIndex].GetAllocatedSize();
}
for (int32 i = 0; i < EMeshPass::Num; i++)
{
TotalCachedMeshDrawCommands += CachedDrawLists[i].MeshDrawCommands.GetAllocatedSize();
}
for (int32 i = 0; i < EMeshPass::Num; i++)
{
const FPassStats& PassStats = AllPassStats[i];
if (PassStats.NumCommands > 0)
{
UE_LOG(LogRenderer, Log, TEXT("%s: %.1fKb for %u CachedMeshDrawCommands"), GetMeshPassName((EMeshPass::Type)i), PassStats.CachedMeshDrawCommandBytes / 1024.0f, PassStats.NumCommands);
if (PassStats.CachedMeshDrawCommandBytes > 1024 && i <= EMeshPass::BasePass)
{
UE_LOG(LogRenderer, Log, TEXT(" avg %.1f bytes PSO"), PassStats.PSOBytes / (float)PassStats.NumCommands);
UE_LOG(LogRenderer, Log, TEXT(" avg %.1f bytes ShaderBindingInline"), PassStats.ShaderBindingInlineBytes / (float)PassStats.NumCommands);
UE_LOG(LogRenderer, Log, TEXT(" avg %.1f bytes ShaderBindingHeap"), PassStats.ShaderBindingHeapBytes / (float)PassStats.NumCommands);
UE_LOG(LogRenderer, Log, TEXT(" avg %.1f bytes VertexStreamsInline"), PassStats.VertexStreamsInlineBytes / (float)PassStats.NumCommands);
UE_LOG(LogRenderer, Log, TEXT(" avg %.1f bytes DebugData"), PassStats.DebugDataBytes / (float)PassStats.NumCommands);
UE_LOG(LogRenderer, Log, TEXT(" avg %.1f bytes DrawCommandParameters"), PassStats.DrawCommandParameterBytes / (float)PassStats.NumCommands);
const SIZE_T Other = PassStats.CachedMeshDrawCommandBytes -
(PassStats.PSOBytes +
PassStats.ShaderBindingInlineBytes +
PassStats.ShaderBindingHeapBytes +
PassStats.VertexStreamsInlineBytes +
PassStats.DebugDataBytes +
PassStats.DrawCommandParameterBytes);
UE_LOG(LogRenderer, Log, TEXT(" avg %.1f bytes Other"), Other / (float)PassStats.NumCommands);
}
}
}
UE_LOG(LogRenderer, Log, TEXT("sizeof(FMeshDrawCommand) %u"), sizeof(FMeshDrawCommand));
UE_LOG(LogRenderer, Log, TEXT("Total cached MeshDrawCommands %.3fMb"), TotalCachedMeshDrawCommands / 1024.0f / 1024.0f);
UE_LOG(LogRenderer, Log, TEXT("Primitive StaticMeshCommandInfos %.1fKb"), TotalStaticMeshCommandInfos / 1024.0f);
UE_LOG(LogRenderer, Log, TEXT("GPUScene CPU structures %.1fKb"), GPUScene.GetAllocatedSize() / 1024.0f);
UE_LOG(LogRenderer, Log, TEXT("PSO persistent Id table %.1fKb %d elements"), FGraphicsMinimalPipelineStateId::GetPersistentIdTableSize() / 1024.0f, FGraphicsMinimalPipelineStateId::GetPersistentIdNum());
UE_LOG(LogRenderer, Log, TEXT("PSO one frame Id %.1fKb"), FGraphicsMinimalPipelineStateId::GetLocalPipelineIdTableSize() / 1024.0f);
}
template<typename T>
static void TArraySwapElements(TArray<T>& Array, int i1, int i2)
{
T tmp = Array[i1];
Array[i1] = Array[i2];
Array[i2] = tmp;
}
template<typename T>
static void TArraySwapElements(TScenePrimitiveArray<T>& Array, int i1, int i2)
{
T tmp = Array[i1];
Array[i1] = Array[i2];
Array[i2] = tmp;
}
static void TBitArraySwapElements(TBitArray<>& Array, int32 i1, int32 i2)
{
FBitReference BitRef1 = Array[i1];
FBitReference BitRef2 = Array[i2];
bool Bit1 = BitRef1;
bool Bit2 = BitRef2;
BitRef1 = Bit2;
BitRef2 = Bit1;
}
void FScene::AddPrimitiveSceneInfo_RenderThread(FPrimitiveSceneInfo* PrimitiveSceneInfo, const TOptional<FTransform>& PreviousTransform)
{
// Must always be a novel primitive that is added
check(PrimitiveSceneInfo->PackedIndex == INDEX_NONE);
PrimitiveUpdates.EnqueueAdd(PrimitiveSceneInfo);
if (PreviousTransform.IsSet())
{
PrimitiveUpdates.Enqueue<FUpdateOverridePreviousTransformData>(PrimitiveSceneInfo, FUpdateOverridePreviousTransformData(PreviousTransform.GetValue().ToMatrixWithScale()));
}
}
/**
* Verifies that a component is added to the proper scene
*
* @param Component Component to verify
* @param World World who's scene the primitive is being attached to
*/
FORCEINLINE static void VerifyProperPIEScene(UObject* Component, UWorld* World)
{
if (!Component)
{
return;
}
checkfSlow(Component->GetOuter() == GetTransientPackage() ||
(FPackageName::GetLongPackageAssetName(Component->GetOutermostObject()->GetPackage()->GetName()).StartsWith(PLAYWORLD_PACKAGE_PREFIX) ==
FPackageName::GetLongPackageAssetName(World->GetPackage()->GetName()).StartsWith(PLAYWORLD_PACKAGE_PREFIX)),
TEXT("The component %s was added to the wrong world's scene (due to PIE). The callstack should tell you why"),
*Component->GetFullName()
);
}
void FPersistentUniformBuffers::Clear()
{
for (auto& UniformBuffer : MobileDirectionalLightUniformBuffers)
{
UniformBuffer.SafeRelease();
}
MobileSkyReflectionUniformBuffer.SafeRelease();
Initialize();
}
void FPersistentUniformBuffers::Initialize()
{
FViewUniformShaderParameters ViewUniformBufferParameters;
FMobileDirectionalLightShaderParameters MobileDirectionalLightShaderParameters = {};
for (int32 Index = 0; Index < UE_ARRAY_COUNT(MobileDirectionalLightUniformBuffers); ++Index)
{
// UniformBuffer_SingleFrame here is an optimization as this buffer gets uploaded everyframe
MobileDirectionalLightUniformBuffers[Index] = TUniformBufferRef<FMobileDirectionalLightShaderParameters>::CreateUniformBufferImmediate(MobileDirectionalLightShaderParameters, UniformBuffer_MultiFrame, EUniformBufferValidation::None);
}
const FMobileReflectionCaptureShaderParameters* DefaultMobileSkyReflectionParameters = (const FMobileReflectionCaptureShaderParameters*)GDefaultMobileReflectionCaptureUniformBuffer.GetContents();
MobileSkyReflectionUniformBuffer = TUniformBufferRef<FMobileReflectionCaptureShaderParameters>::CreateUniformBufferImmediate(*DefaultMobileSkyReflectionParameters, UniformBuffer_MultiFrame, EUniformBufferValidation::None);
}
TSet<IPersistentViewUniformBufferExtension*> PersistentViewUniformBufferExtensions;
void FRendererModule::RegisterPersistentViewUniformBufferExtension(IPersistentViewUniformBufferExtension* Extension)
{
PersistentViewUniformBufferExtensions.Add(Extension);
}
FScene::FScene(UWorld* InWorld, bool bInRequiresHitProxies, bool bInIsEditorScene, bool bCreateFXSystem, ERHIFeatureLevel::Type InFeatureLevel)
: FSceneInterface(InFeatureLevel)
, World(InWorld)
, FXSystem(nullptr)
, bCachedShouldRenderSkylightInBasePass(false)
, bCachedSkyLightRealTimeCapture(false)
, bScenesPrimitivesNeedStaticMeshElementUpdate(false)
, PathTracingInvalidationCounter(0)
#if RHI_RAYTRACING
, CachedRayTracingMeshCommandsType(ERayTracingType::RayTracing)
#endif
, SkyLight(NULL)
, ConvolvedSkyRenderTargetReadyIndex(-1)
, PathTracingSkylightColor(0, 0, 0, 0)
, SimpleDirectionalLight(NULL)
, ReflectionSceneData(InFeatureLevel)
, IndirectLightingCache(InFeatureLevel)
, VolumetricLightmapSceneData(this)
, GPUScene(*this)
, DistanceFieldSceneData(GShaderPlatformForFeatureLevel[InFeatureLevel])
, DefaultLumenSceneData(nullptr)
, PreshadowCacheLayout(0, 0, 0, 0, false)
, SkyAtmosphere(NULL)
, VolumetricCloud(NULL)
, PrecomputedVisibilityHandler(NULL)
, LocalShadowCastingLightOctree(FVector::ZeroVector, UE_OLD_HALF_WORLD_MAX)
, PrimitiveOctree(FVector::ZeroVector, UE_OLD_HALF_WORLD_MAX)
, bRequiresHitProxies(bInRequiresHitProxies)
, bIsEditorScene(bInIsEditorScene)
, bRuntimeVirtualTexturePrimitiveHideEditor(false)
, bRuntimeVirtualTexturePrimitiveHideGame(false)
, NumUncachedStaticLightingInteractions(0)
, NumUnbuiltReflectionCaptures(0)
, NumMobileStaticAndCSMLights_RenderThread(0)
, NumMobileMovableDirectionalLights_RenderThread(0)
, GPUSkinCache(nullptr)
, SceneLODHierarchy(this)
, DefaultMaxDistanceFieldOcclusionDistance(InWorld->GetWorldSettings()->DefaultMaxDistanceFieldOcclusionDistance)
, GlobalDistanceFieldViewDistance(InWorld->GetWorldSettings()->GlobalDistanceFieldViewDistance)
, DynamicIndirectShadowsSelfShadowingIntensity(FMath::Clamp(InWorld->GetWorldSettings()->DynamicIndirectShadowsSelfShadowingIntensity, 0.0f, 1.0f))
#if RHI_RAYTRACING
, RayTracingDynamicGeometryUpdateManager(nullptr)
#endif
, PersistentViewStateDebugFlags(0)
, NumVisibleLights_GameThread(0)
, NumEnabledSkylights_GameThread(0)
, SceneFrameNumber(0)
, SceneFrameNumberRenderThread(0)
, bForceNoPrecomputedLighting(InWorld->GetWorldSettings()->bForceNoPrecomputedLighting)
{
FMemory::Memzero(MobileDirectionalLights);
FMemory::Memzero(AtmosphereLights);
FMemory::Memzero(MobileSkyLightRealTimeCaptureIrradianceEnvironmentMap);
FullWorldName = World->GetFullName();
check(World);
World->Scene = this;
FeatureLevel = World->GetFeatureLevel();
checkf((uint32)FeatureLevel < (uint32)ERHIFeatureLevel::Num, TEXT("World provided an invalid feature level (%d) to FScene."), FeatureLevel);
checkf(GShaderPlatformForFeatureLevel[FeatureLevel] != SP_NumPlatforms, TEXT("Invalid feature level %s for platform (max feature level %s)"), *LexToString(FeatureLevel), *LexToString(GMaxRHIFeatureLevel));
GPUScene.SetEnabled(FeatureLevel);
if (GPUScene.IsEnabled())
{
InstanceCullingOcclusionQueryRenderer = new FInstanceCullingOcclusionQueryRenderer;
}
if (World->FXSystem)
{
FFXSystemInterface::Destroy(World->FXSystem);
}
if (bCreateFXSystem)
{
World->CreateFXSystem();
}
else
{
World->FXSystem = NULL;
SetFXSystem(NULL);
}
if (IsGPUSkinCacheAvailable(GetFeatureLevelShaderPlatform(InFeatureLevel)))
{
const bool bRequiresMemoryLimit = !bInIsEditorScene;
GPUSkinCache = new FGPUSkinCache(InFeatureLevel, bRequiresMemoryLimit, World);
}
SkeletalMeshUpdater = new FSkeletalMeshUpdater(this, GPUSkinCache);
ComputeSystemInterface::CreateWorkers(this, ComputeTaskWorkers);
#if RHI_RAYTRACING
if (IsRayTracingAllowed())
{
RayTracingDynamicGeometryUpdateManager = new FRayTracingDynamicGeometryUpdateManager();
}
#endif
World->UpdateParameterCollectionInstances(false, false);
FPersistentUniformBuffers* PersistentUniformBuffers = &UniformBuffers;
ENQUEUE_RENDER_COMMAND(InitializeUniformBuffers)(
[PersistentUniformBuffers] (FRHICommandListBase&)
{
PersistentUniformBuffers->Initialize();
});
UpdateEarlyZPassMode();
DefaultLumenSceneData = new FLumenSceneData(GShaderPlatformForFeatureLevel[InFeatureLevel], InWorld->WorldType);
SceneLightInfoUpdates = new FSceneLightInfoUpdates;
// Make sure we initialize the SceneRenderExtensions last, when the rest of the scene is initialized
SceneExtensions.Init(*this);
}
FScene::~FScene()
{
#if 0 // if you have component that has invalid scene, try this code to see this is reason.
for (FThreadSafeObjectIterator Iter(UActorComponent::StaticClass()); Iter; ++Iter)
{
UActorComponent * ActorComp = CastChecked<UActorComponent>(*Iter);
if (ActorComp->GetScene() == this)
{
UE_LOG(LogRenderer, Log, TEXT("%s's scene is going to get invalidated"), *ActorComp->GetName());
}
}
#endif
checkf(PrimitiveUpdates.IsEmpty(), TEXT("All pending primitive addition operations are expected to be flushed when the scene is destroyed. Remaining operations are likely to cause a memory leak."));
checkf(Primitives.Num() == 0, TEXT("All primitives are expected to be removed before the scene is destroyed. Remaining primitives are likely to cause a memory leak."));
delete InstanceCullingOcclusionQueryRenderer;
// Unlink any view states from the scene
for (FSceneViewState* ViewState : ViewStates)
{
check(ViewState->Scene == this);
ViewState->Scene = nullptr;
}
ViewStates.Empty();
if (DefaultLumenSceneData)
{
delete DefaultLumenSceneData;
DefaultLumenSceneData = nullptr;
}
for (FLumenSceneDataMap::TConstIterator LumenSceneData(PerViewOrGPULumenSceneData); LumenSceneData; ++LumenSceneData)
{
delete LumenSceneData.Value();
}
PerViewOrGPULumenSceneData.Empty();
ReflectionSceneData.CubemapArray.ReleaseResource();
IndirectLightingCache.ReleaseResource();
DistanceFieldSceneData.Release();
delete GPUSkinCache;
GPUSkinCache = nullptr;
delete SkeletalMeshUpdater;
SkeletalMeshUpdater = nullptr;
ComputeSystemInterface::DestroyWorkers(this, ComputeTaskWorkers);
#if RHI_RAYTRACING
delete RayTracingDynamicGeometryUpdateManager;
RayTracingDynamicGeometryUpdateManager = nullptr;
#endif // RHI_RAYTRACING
delete SceneLightInfoUpdates;
TSharedPtr<FRHIGPUBufferReadback> It;
while (MobileSkyLightRealTimeCaptureIrradianceReadBackQueries.Dequeue(It))
{
It.Reset();
}
}
// Helpers for internal templates
UObject* ToUObject(FPrimitiveSceneDesc* Desc)
{
return Desc->PrimitiveUObject;
}
UObject* ToUObject(UPrimitiveComponent* Prim)
{
return Prim;
}
void FScene::AddPrimitive(UPrimitiveComponent* Primitive)
{
// If the bulk reregister flag is set, add / remove will be handled in bulk by the FStaticMeshComponentBulkReregisterContext
if (Primitive->bBulkReregister)
{
return;
}
BatchAddPrimitivesInternal(MakeArrayView(&Primitive, 1));
}
void FScene::AddPrimitive(FPrimitiveSceneDesc* Primitive)
{
// If the bulk reregister flag is set, add / remove will be handled in bulk by the FStaticMeshComponentBulkReregisterContext
if (Primitive->bBulkReregister)
{
return;
}
BatchAddPrimitivesInternal(MakeArrayView(&Primitive, 1));
}
template<typename PrimitiveType>
static void CheckAndSanitizePrimitiveBounds(FBoxSphereBounds &InOutWorldBounds, PrimitiveType *Primitive)
{
// Help track down primitive with bad bounds way before the it gets to the Renderer
if (!ensureMsgf(!InOutWorldBounds.ContainsNaN(),
TEXT("NaNs found on Bounds for Primitive %s: Owner: %s, Resource: %s, Level: %s, Origin: %s, BoxExtent: %s, SphereRadius: %f"),
*Primitive->GetName(),
*Primitive->GetSceneProxy()->GetOwnerName().ToString(),
*Primitive->GetSceneProxy()->GetResourceName().ToString(),
*Primitive->GetSceneProxy()->GetLevelName().ToString(),
*InOutWorldBounds.Origin.ToString(),
*InOutWorldBounds.BoxExtent.ToString(),
InOutWorldBounds.SphereRadius
))
{
InOutWorldBounds = FBoxSphereBounds(ForceInit);
}
}
template<class T>
void FScene::BatchAddPrimitivesInternal(TArrayView<T*> InPrimitives)
{
check(InPrimitives.Num() > 0);
#if ENABLE_LOW_LEVEL_MEM_TRACKER && LLM_ENABLED_STAT_TAGS
// If detailed per-tag asset memory stats are active, don't batch primitives, so the memory tags can be independent
if (FLowLevelMemTracker::Get().IsTagSetActive(ELLMTagSet::Assets) && InPrimitives.Num() > 1)
{
for (T* Primitive : InPrimitives)
{
BatchAddPrimitivesInternal(MakeArrayView(TArrayView<T*>(&Primitive, 1)));
}
return;
}
#endif
LLM_SCOPE_DYNAMIC_STAT_OBJECTPATH_FNAME(ToUObject(InPrimitives[0]) ? InPrimitives[0]->GetOutermost()->GetFName() : NAME_None, ELLMTagSet::Assets);
UE_TRACE_METADATA_SCOPE_ASSET_FNAME(NAME_None, NAME_None, ToUObject(InPrimitives[0]) ? InPrimitives[0]->GetOutermost()->GetFName() : NAME_None);
SCOPE_CYCLE_COUNTER(STAT_AddScenePrimitiveGT);
struct FCreateCommand
{
FCreateCommand(
FPrimitiveSceneInfo* InPrimitiveSceneInfo,
FPrimitiveSceneProxy* InPrimitiveSceneProxy,
TOptional<FTransform> InPreviousTransform,
FMatrix InRenderMatrix,
FBoxSphereBounds InWorldBounds,
FVector InAttachmentRootPosition,
FBoxSphereBounds InLocalBounds)
: PrimitiveSceneInfo(InPrimitiveSceneInfo)
, PrimitiveSceneProxy(InPrimitiveSceneProxy)
, PreviousTransform(InPreviousTransform)
, RenderMatrix(InRenderMatrix)
, WorldBounds(InWorldBounds)
, AttachmentRootPosition(InAttachmentRootPosition)
, LocalBounds(InLocalBounds)
{}
FPrimitiveSceneInfo* PrimitiveSceneInfo;
FPrimitiveSceneProxy* PrimitiveSceneProxy;
TOptional<FTransform> PreviousTransform;
FMatrix RenderMatrix;
FBoxSphereBounds WorldBounds;
FVector AttachmentRootPosition;
FBoxSphereBounds LocalBounds;
};
TArray<FCreateCommand, SceneRenderingAllocator> CreateCommands;
CreateCommands.Reserve(InPrimitives.Num());
for (T* Primitive : InPrimitives)
{
FPrimitiveSceneInfoData& SceneData = Primitive->GetSceneData();
checkf(!Primitive->IsUnreachable(), TEXT("%s"), *Primitive->GetFullName());
const float WorldTime = GetWorld()->GetTimeSeconds();
FPrimitiveSceneProxy* PrimitiveSceneProxy = nullptr;
if (Primitive->GetPrimitiveComponentInterface())
{
checkf(!Primitive->GetSceneProxy(), TEXT("Primitive has already been added to the scene!"));
PrimitiveSceneProxy = Primitive->GetPrimitiveComponentInterface()->CreateSceneProxy();
check(SceneData.SceneProxy == PrimitiveSceneProxy); // CreateSceneProxy has access to the shared SceneData and should set it properly
}
else
{
check(!Primitive->ShouldRecreateProxyOnUpdateTransform()); // recreating proxies when updating the transform requires a IPrimitiveComponentInterface
PrimitiveSceneProxy = Primitive->GetSceneProxy();
}
if(!PrimitiveSceneProxy)
{
// Primitives which don't have a proxy are irrelevant to the scene manager.
continue;
}
// Create the primitive scene info.
FPrimitiveSceneInfo* PrimitiveSceneInfo = new FPrimitiveSceneInfo(Primitive, this);
PrimitiveSceneProxy->PrimitiveSceneInfo = PrimitiveSceneInfo;
// Cache the primitives initial transform.
FMatrix RenderMatrix = Primitive->GetRenderMatrix();
FVector AttachmentRootPosition = Primitive->GetActorPositionForRenderer();
FBoxSphereBounds WorldBounds = Primitive->Bounds;
CheckAndSanitizePrimitiveBounds(WorldBounds, Primitive);
CreateCommands.Emplace(
PrimitiveSceneInfo,
PrimitiveSceneProxy,
// If this primitive has a simulated previous transform, ensure that the velocity data for the scene representation is correct.
FMotionVectorSimulation::Get().GetPreviousTransform(ToUObject(Primitive)),
RenderMatrix,
WorldBounds,
AttachmentRootPosition,
Primitive->GetLocalBounds()
);
INC_DWORD_STAT_BY( STAT_GameToRendererMallocTotal, PrimitiveSceneProxy->GetMemoryFootprint() + PrimitiveSceneInfo->GetMemoryFootprint() );
// Verify the primitive is valid
VerifyProperPIEScene(ToUObject(Primitive), World);
if (FSimpleStreamableAssetManager::IsEnabled())
{
FSimpleStreamableAssetManager::Register(FSimpleStreamableAssetManager::FRegister(PrimitiveSceneProxy, Primitive));
}
// Increment the attachment counter, the primitive is about to be attached to the scene.
SceneData.AttachmentCounter.Increment();
}
if (!CreateCommands.IsEmpty())
{
// Must enqueue RT commands to ensure SetTransform & CreateRenderThreadResources are executed in-order.
ENQUEUE_RENDER_COMMAND(AddPrimitiveCommand)(
[this, CreateCommands = MoveTemp(CreateCommands)](FRHICommandListBase& RHICmdList)
{
for (const FCreateCommand& Command : CreateCommands)
{
FScopeCycleCounter Context(Command.PrimitiveSceneProxy->GetStatId());
Command.PrimitiveSceneProxy->SetTransform(RHICmdList, Command.RenderMatrix, Command.WorldBounds, Command.LocalBounds, Command.AttachmentRootPosition);
Command.PrimitiveSceneProxy->CreateRenderThreadResources(RHICmdList);
AddPrimitiveSceneInfo_RenderThread(Command.PrimitiveSceneInfo, Command.PreviousTransform);
}
});
}
}
void FScene::BatchAddPrimitives(TArrayView<UPrimitiveComponent*> InPrimitives)
{
BatchAddPrimitivesInternal(InPrimitives);
}
void FScene::BatchAddPrimitives(TArrayView<FPrimitiveSceneDesc*> InPrimitives)
{
BatchAddPrimitivesInternal(InPrimitives);
}
static int32 GWarningOnRedundantTransformUpdate = 0;
static FAutoConsoleVariableRef CVarWarningOnRedundantTransformUpdate(
TEXT("r.WarningOnRedundantTransformUpdate"),
GWarningOnRedundantTransformUpdate,
TEXT("Produce a warning when UpdatePrimitiveTransform is called redundantly."),
ECVF_Default
);
static int32 GSkipRedundantTransformUpdate = 1;
static FAutoConsoleVariableRef CVarSkipRedundantTransformUpdate(
TEXT("r.SkipRedundantTransformUpdate"),
GSkipRedundantTransformUpdate,
TEXT("Skip updates UpdatePrimitiveTransform is called redundantly, if the proxy allows it."),
ECVF_Default
);
#if VALIDATE_PRIMITIVE_PACKED_INDEX
inline void ValidatePackedPrimitiveIndexForUpdate(FPrimitiveSceneInfo* PrimitiveSceneInfo, const FScenePrimitiveUpdate& PrimitiveUpdates)
{
if (const FPrimitiveUpdateCommand* Cmd = PrimitiveUpdates.FindCommand(PrimitiveSceneInfo))
{
if (Cmd->bAdded)
{
check(PrimitiveSceneInfo->GetIndex() == INDEX_NONE);
}
else
{
check(PrimitiveSceneInfo->GetIndex() != INDEX_NONE);
}
}
else
{
// sending update, and no queued command - must have a valid index
check(PrimitiveSceneInfo->GetIndex() != INDEX_NONE);
}
}
#endif
void FScene::UpdatePrimitiveTransform_RenderThread(FPrimitiveSceneProxy* PrimitiveSceneProxy, const FBoxSphereBounds& WorldBounds, const FBoxSphereBounds& LocalBounds, const FMatrix& LocalToWorld, const FVector& AttachmentRootPosition, const TOptional<FTransform>& PreviousTransform)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveSceneProxy->GetPrimitiveSceneInfo();
#if VALIDATE_PRIMITIVE_PACKED_INDEX
ValidatePackedPrimitiveIndexForUpdate(PrimitiveSceneInfo, PrimitiveUpdates);
#endif
PrimitiveUpdates.Enqueue(PrimitiveSceneInfo, FUpdateTransformCommand { .WorldBounds = WorldBounds, .LocalBounds = LocalBounds, .LocalToWorld = LocalToWorld, .AttachmentRootPosition = AttachmentRootPosition });
if (PreviousTransform.IsSet())
{
PrimitiveUpdates.Enqueue(PrimitiveSceneInfo, FUpdateOverridePreviousTransformData(PreviousTransform.GetValue().ToMatrixWithScale()));
}
}
template <typename UpdatePayloadType>
void FScene::UpdatePrimitiveInternal(FPrimitiveSceneProxy* SceneProxy, UpdatePayloadType&& InUpdatePayload)
{
if (SceneProxy != nullptr)
{
ENQUEUE_RENDER_COMMAND(UpdatePrimitiveCmd)(
[this, PrimitiveSceneInfo = SceneProxy->PrimitiveSceneInfo, UpdatePayload = MoveTemp(InUpdatePayload)] (FRHICommandListBase&) mutable
{
#if VALIDATE_PRIMITIVE_PACKED_INDEX
ValidatePackedPrimitiveIndexForUpdate(PrimitiveSceneInfo, PrimitiveUpdates);
#endif
PrimitiveUpdates.Enqueue<UpdatePayloadType>(PrimitiveSceneInfo, MoveTemp(UpdatePayload));
});
}
}
void FScene::UpdatePrimitiveTransform(UPrimitiveComponent* Primitive)
{
UpdatePrimitiveTransformInternal(Primitive);
}
void FScene::UpdatePrimitiveTransform(FPrimitiveSceneDesc* Primitive)
{
UpdatePrimitiveTransformInternal(Primitive);
}
template<class T>
void FScene::UpdatePrimitiveTransformInternal(T* Primitive)
{
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveTransformGT);
FPrimitiveSceneInfoData& SceneData = Primitive->GetSceneData();
if (Primitive->GetSceneProxy())
{
// Check if the primitive needs to recreate its proxy for the transform update.
if (Primitive->ShouldRecreateProxyOnUpdateTransform())
{
check(Primitive->GetPrimitiveComponentInterface()); // required to execute the Remove/Add sequence inside this method
// Re-add the primitive from scratch to recreate the primitive's proxy.
RemovePrimitive(Primitive);
AddPrimitive(Primitive);
}
else
{
FVector AttachmentRootPosition = Primitive->GetActorPositionForRenderer();
FPrimitiveUpdateParams UpdateParams;
UpdateParams.Scene = this;
UpdateParams.PrimitiveSceneProxy = Primitive->GetSceneProxy();
UpdateParams.WorldBounds = Primitive->Bounds;
UpdateParams.LocalToWorld = Primitive->GetRenderMatrix();
UpdateParams.AttachmentRootPosition = AttachmentRootPosition;
UpdateParams.LocalBounds = Primitive->GetLocalBounds();
UpdateParams.PreviousTransform = FMotionVectorSimulation::Get().GetPreviousTransform(ToUObject(Primitive));
CheckAndSanitizePrimitiveBounds(UpdateParams.WorldBounds, Primitive);
bool bPerformUpdate = true;
const bool bAllowSkip = GSkipRedundantTransformUpdate && Primitive->GetSceneProxy()->CanSkipRedundantTransformUpdates();
if (bAllowSkip || GWarningOnRedundantTransformUpdate)
{
if (Primitive->GetSceneProxy()->WouldSetTransformBeRedundant_AnyThread(
UpdateParams.LocalToWorld,
UpdateParams.WorldBounds,
UpdateParams.LocalBounds,
UpdateParams.AttachmentRootPosition))
{
if (bAllowSkip)
{
// Do not perform the transform update!
bPerformUpdate = false;
}
else
{
// Not skipping, and warnings are enabled.
UE_LOG(LogRenderer, Warning,
TEXT("Redundant UpdatePrimitiveTransform for Primitive %s: Owner: %s, Resource: %s, Level: %s"),
*Primitive->GetName(),
*Primitive->GetSceneProxy()->GetOwnerName().ToString(),
*Primitive->GetSceneProxy()->GetResourceName().ToString(),
*Primitive->GetSceneProxy()->GetLevelName().ToString()
);
}
}
}
if (bPerformUpdate)
{
bool bNeedsTransformCommand = true;
// Accumulate all transform updates and enqueue them as once
if (auto* Updater = static_cast<FPrimitiveTransformUpdater*>(IPrimitiveTransformUpdater::GetInstanceTLS()))
{
const int32 Index = Updater->Index.fetch_add(1, std::memory_order_relaxed);
if (ensure(Index < Updater->Params.Num()))
{
Updater->Params[Index] = MoveTemp(UpdateParams);
bNeedsTransformCommand = false;
}
}
if (bNeedsTransformCommand)
{
ENQUEUE_RENDER_COMMAND(UpdateTransformCommand)(
[UpdateParams](FRHICommandListBase&)
{
FScopeCycleCounter Context(UpdateParams.PrimitiveSceneProxy->GetStatId());
UpdateParams.Scene->UpdatePrimitiveTransform_RenderThread(
UpdateParams.PrimitiveSceneProxy,
UpdateParams.WorldBounds,
UpdateParams.LocalBounds,
UpdateParams.LocalToWorld,
UpdateParams.AttachmentRootPosition,
UpdateParams.PreviousTransform
);
if (FSimpleStreamableAssetManager::IsEnabled())
{
const FPrimitiveSceneProxy* SceneProxy = UpdateParams.PrimitiveSceneProxy;
FSimpleStreamableAssetManager::Update(
FSimpleStreamableAssetManager::FUpdate
{
SceneProxy,
SceneProxy->SimpleStreamableAssetManagerIndex,
UpdateParams.WorldBounds,
SceneProxy->GetMinDrawDistance(),
SceneProxy->GetMaxDrawDistance(),
SceneProxy->PrimitiveSceneInfo->LastRenderTime,
SceneProxy->IsForceMipStreaming()
}
);
}
}
);
}
}
}
}
else
{
// If the primitive doesn't have a scene info object yet, it must be added from scratch.
AddPrimitive(Primitive);
}
}
FScene::IPrimitiveTransformUpdater* FScene::CreatePrimitiveTransformUpdater(int32 MaxPrimitives)
{
if (MaxPrimitives > 0)
{
FPrimitiveTransformUpdater* Updater = new FPrimitiveTransformUpdater;
Updater->Params.SetNum(MaxPrimitives);
return Updater;
}
return nullptr;
}
void FScene::UpdatePrimitiveTransforms(IPrimitiveTransformUpdater* InUpdater)
{
if (auto* Updater = static_cast<FPrimitiveTransformUpdater*>(InUpdater))
{
ENQUEUE_RENDER_COMMAND(UpdateTransformCommand)([Updater](FRHICommandListBase&)
{
const int32 NumPrimitiveUpdates = FMath::Min(Updater->Params.Num(), Updater->Index.load(std::memory_order_relaxed));
for (int32 Index = 0; Index < NumPrimitiveUpdates; ++Index)
{
const auto& UpdateParams = Updater->Params[Index];
FScopeCycleCounter Context(UpdateParams.PrimitiveSceneProxy->GetStatId());
UpdateParams.Scene->UpdatePrimitiveTransform_RenderThread(
UpdateParams.PrimitiveSceneProxy,
UpdateParams.WorldBounds,
UpdateParams.LocalBounds,
UpdateParams.LocalToWorld,
UpdateParams.AttachmentRootPosition,
UpdateParams.PreviousTransform
);
if (FSimpleStreamableAssetManager::IsEnabled())
{
const FPrimitiveSceneProxy* SceneProxy = UpdateParams.PrimitiveSceneProxy;
FSimpleStreamableAssetManager::Update(
FSimpleStreamableAssetManager::FUpdate
{
SceneProxy,
SceneProxy->SimpleStreamableAssetManagerIndex,
UpdateParams.WorldBounds,
SceneProxy->GetMinDrawDistance(),
SceneProxy->GetMaxDrawDistance(),
SceneProxy->GetPrimitiveSceneInfo()->LastRenderTime,
SceneProxy->IsForceMipStreaming()
}
);
}
}
delete Updater;
});
}
}
void FScene::UpdatePrimitiveOcclusionBoundsSlack(UPrimitiveComponent* Primitive, float NewSlack)
{
UpdatePrimitiveInternal(Primitive->GetSceneProxy(), FUpdateOcclusionBoundsSlacksData(NewSlack));
}
void FScene::UpdatePrimitiveDrawDistance(UPrimitiveComponent* Primitive, float MinDrawDistance, float MaxDrawDistance, float VirtualTextureMaxDrawDistance)
{
UpdatePrimitiveInternal(Primitive->GetSceneProxy(), FUpdateDrawDistanceData(FVector3f(MinDrawDistance, MaxDrawDistance, VirtualTextureMaxDrawDistance)));
}
void FScene::UpdateInstanceCullDistance(UPrimitiveComponent* Primitive, float StartCullDistance, float EndCullDistance)
{
UpdatePrimitiveInternal(Primitive->GetSceneProxy(), FUpdateInstanceCullDistanceData(FVector2f(StartCullDistance, EndCullDistance)));
}
void FScene::UpdatePrimitiveInstances(UPrimitiveComponent* Primitive)
{
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveInstanceGT);
// If the primitive doesn't have a scene info object yet, it must be added from scratch.
if (!Primitive->GetSceneProxy())
{
AddPrimitive(Primitive);
return;
}
FUpdateInstanceCommand UpdateParams;
UpdateParams.PrimitiveSceneProxy = Primitive->GetSceneProxy();
UpdateParams.WorldBounds = Primitive->Bounds;
UpdateParams.LocalBounds = static_cast<UPrimitiveComponent*>(Primitive)->GetLocalBounds();
CheckAndSanitizePrimitiveBounds(UpdateParams.WorldBounds, Primitive);
UpdatePrimitiveInstances(UpdateParams);
}
void FScene::UpdatePrimitiveInstancesFromCompute(FPrimitiveSceneDesc* Primitive, FGPUSceneWriteDelegate&& DataWriterGPU)
{
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveInstanceFromComputeGT);
check(Primitive);
FPrimitiveSceneProxy* SceneProxy = Primitive->GetSceneProxy();
if (!ensureMsgf(SceneProxy, TEXT("Primitive must be added to scene prior to updating its instances from compute.")))
{
return;
}
FUpdateInstanceFromComputeCommand UpdateCommand;
UpdateCommand.PrimitiveSceneProxy = SceneProxy;
UpdateCommand.GPUSceneWriter = MoveTemp(DataWriterGPU);
UpdatePrimitiveInternal(SceneProxy, MoveTemp(UpdateCommand));
}
void FScene::UpdatePrimitiveInstancesFromCompute(FPrimitiveSceneInfo* PrimitiveSceneInfo, FGPUSceneWriteDelegate&& DataWriterGPU)
{
SCOPED_NAMED_EVENT(FScene_UpdatePrimitiveInstanceFromCompute, FColor::Yellow);
check(PrimitiveSceneInfo);
// Primitive must have a scene proxy already created in order to update it's instance data.
if (!ensureAlways(PrimitiveSceneInfo->Proxy != nullptr))
{
return;
}
// Only updates to GPU-only primitives are currently allowed.
if (!ensureAlways(PrimitiveSceneInfo->Proxy->IsInstanceDataGPUOnly()))
{
return;
}
FUpdateInstanceFromComputeCommand UpdateCommand;
UpdateCommand.PrimitiveSceneProxy = PrimitiveSceneInfo->Proxy;
UpdateCommand.GPUSceneWriter = MoveTemp(DataWriterGPU);
// This is already on the renderthread so queue directly.
PrimitiveUpdates.Enqueue(PrimitiveSceneInfo, MoveTemp(UpdateCommand));
}
void FScene::UpdatePrimitivesDrawnInGame_RenderThread(TArrayView<FPrimitiveSceneProxy*> InPrimitiveSceneProxies, bool bDrawnInGame)
{
check(IsInRenderingThread());
for (FPrimitiveSceneProxy* PrimitiveSceneProxy : InPrimitiveSceneProxies)
{
if (PrimitiveSceneProxy->IsDrawnInGame() != bDrawnInGame)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveSceneProxy->GetPrimitiveSceneInfo();
const int32 PrimitiveIndex = PrimitiveSceneInfo->GetIndex();
const bool bPrimitiveIndexValid = PrimitiveSceneInfo->IsIndexValid();
check(bPrimitiveIndexValid);
if (!bDrawnInGame && bPrimitiveIndexValid)
{
DistanceFieldSceneData.RemovePrimitive(PrimitiveSceneInfo);
LumenRemovePrimitive(PrimitiveSceneInfo, PrimitiveIndex);
}
PrimitiveSceneProxy->SetDrawnInGame_RenderThread(bDrawnInGame);
if (bDrawnInGame && bPrimitiveIndexValid)
{
DistanceFieldSceneData.AddPrimitive(PrimitiveSceneInfo);
LumenAddPrimitive(PrimitiveSceneInfo);
}
#if RHI_RAYTRACING
if (bPrimitiveIndexValid && PrimitiveSceneProxy->HasRayTracingRepresentation())
{
FRayTracingInstance CachedRayTracingInstance;
ERayTracingPrimitiveFlags& Flags = PrimitiveRayTracingFlags[PrimitiveIndex];
// Write flags
Flags = PrimitiveSceneInfo->Proxy->GetCachedRayTracingInstance(CachedRayTracingInstance);
FPrimitiveSceneInfo::UpdateCachedRayTracingInstance(PrimitiveSceneInfo, CachedRayTracingInstance, Flags);
}
#endif
}
}
}
void FScene::UpdatePrimitiveInstances(FInstancedStaticMeshSceneDesc* Primitive)
{
UpdatePrimitiveInstances(*Primitive);
}
void FScene::UpdatePrimitiveInstances(FPrimitiveSceneDesc* Primitive)
{
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveInstanceGT);
// If the primitive doesn't have a scene info object yet, it must be added from scratch.
if (!Primitive->GetSceneProxy())
{
AddPrimitive(Primitive);
return;
}
FUpdateInstanceCommand UpdateParams;
UpdateParams.PrimitiveSceneProxy = Primitive->GetSceneProxy();
UpdateParams.WorldBounds = Primitive->GetBounds();
UpdateParams.LocalBounds = Primitive->GetLocalBounds();
CheckAndSanitizePrimitiveBounds(UpdateParams.WorldBounds, Primitive);
UpdatePrimitiveInstances(UpdateParams);
}
void FScene::UpdatePrimitiveInstances(FUpdateInstanceCommand& UpdateParams)
{
UpdatePrimitiveInternal(UpdateParams.PrimitiveSceneProxy, MoveTemp(UpdateParams));
}
void FScene::UpdatePrimitiveSelectedState_RenderThread(const FPrimitiveSceneInfo* PrimitiveSceneInfo, bool bIsSelected)
{
check(IsInParallelRenderingThread());
#if WITH_EDITOR
if (PrimitiveSceneInfo)
{
if (PrimitiveSceneInfo->GetIndex() != INDEX_NONE)
{
PrimitivesSelected[PrimitiveSceneInfo->GetIndex()].AtomicSet(bIsSelected);
}
}
#endif // WITH_EDITOR
}
void FScene::UpdatePrimitiveLightingAttachmentRoot(UPrimitiveComponent* Primitive)
{
const UPrimitiveComponent* NewLightingAttachmentRoot = Primitive->GetLightingAttachmentRoot();
if (NewLightingAttachmentRoot == Primitive)
{
NewLightingAttachmentRoot = nullptr;
}
FPrimitiveComponentId NewComponentId = NewLightingAttachmentRoot ? NewLightingAttachmentRoot->GetPrimitiveSceneId() : FPrimitiveComponentId();
UpdatePrimitiveInternal(Primitive->GetSceneProxy(), FUpdateAttachmentRootData(NewComponentId));
}
void FScene::UpdatePrimitiveAttachment(UPrimitiveComponent* Primitive)
{
TArray<USceneComponent*, TInlineAllocator<1> > ProcessStack;
ProcessStack.Push(Primitive);
// Walk down the tree updating, because the scene's attachment data structures must be updated if the root of the attachment tree changes
while (ProcessStack.Num() > 0)
{
USceneComponent* Current = ProcessStack.Pop(EAllowShrinking::No);
if (Current)
{
UPrimitiveComponent* CurrentPrimitive = Cast<UPrimitiveComponent>(Current);
if (CurrentPrimitive
&& CurrentPrimitive->GetWorld()
&& CurrentPrimitive->GetWorld()->Scene == this
&& CurrentPrimitive->ShouldComponentAddToScene())
{
UpdatePrimitiveLightingAttachmentRoot(CurrentPrimitive);
}
ProcessStack.Append(Current->GetAttachChildren());
}
}
}
void FScene::UpdateCustomPrimitiveData(UPrimitiveComponent* Primitive)
{
UpdateCustomPrimitiveData(Primitive->GetSceneProxy(), Primitive->GetCustomPrimitiveData());
}
void FScene::UpdateCustomPrimitiveData(FPrimitiveSceneDesc* Primitive, const FCustomPrimitiveData& CustomPrimitiveData)
{
UpdateCustomPrimitiveData(Primitive->GetSceneProxy(), CustomPrimitiveData);
}
void FScene::UpdateCustomPrimitiveData(FPrimitiveSceneProxy* SceneProxy, const FCustomPrimitiveData& CustomPrimitiveData)
{
UpdatePrimitiveInternal(SceneProxy, FUpdateCustomPrimitiveData(CustomPrimitiveData));
}
void FScene::UpdatePrimitiveDistanceFieldSceneData_GameThread(UPrimitiveComponent* Primitive)
{
check(IsInGameThread());
if (Primitive->SceneProxy)
{
UpdatePrimitiveInternal(Primitive->GetSceneProxy(), FUpdateDistanceFieldSceneData{});
}
}
FPrimitiveSceneInfo* FScene::GetPrimitiveSceneInfo(int32 PrimitiveIndex) const
{
if (Primitives.IsValidIndex(PrimitiveIndex))
{
return Primitives[PrimitiveIndex];
}
return nullptr;
}
FPrimitiveSceneInfo* FScene::GetPrimitiveSceneInfo(FPrimitiveComponentId PrimitiveId) const
{
return GetPrimitiveSceneInfo(PrimitiveComponentIds.Find(PrimitiveId));
}
FPrimitiveSceneInfo* FScene::GetPrimitiveSceneInfo(const FPersistentPrimitiveIndex& PersistentPrimitiveIndex) const
{
int32 PrimitiveIndex = GetPrimitiveIndex(PersistentPrimitiveIndex);
return GetPrimitiveSceneInfo(PrimitiveIndex);
}
void FScene::RemovePrimitiveSceneInfo_RenderThread(FPrimitiveSceneInfo* PrimitiveSceneInfo)
{
PrimitiveUpdates.EnqueueDelete(PrimitiveSceneInfo);
}
void FScene::RemovePrimitive(UPrimitiveComponent* Primitive)
{
// If the bulk reregister flag is set, add / remove will be handled in bulk by the FStaticMeshComponentBulkReregisterContext
if (Primitive->bBulkReregister)
{
return;
}
BatchRemovePrimitives(MakeArrayView(&Primitive, 1));
}
void FScene::RemovePrimitive(FPrimitiveSceneDesc* Primitive)
{
// If the bulk reregister flag is set, add / remove will be handled in bulk by the FStaticMeshComponentBulkReregisterContext
if (Primitive->bBulkReregister)
{
return;
}
BatchRemovePrimitives(MakeArrayView(&Primitive, 1));
}
template <class T>
void FScene::BatchRemovePrimitivesInternal(TArrayView<T*> InPrimitives)
{
SCOPE_CYCLE_COUNTER(STAT_RemoveScenePrimitiveGT);
struct FDetachCommand
{
FPrimitiveSceneInfo* PrimitiveSceneInfo;
FPrimitiveSceneProxy* PrimitiveSceneProxy;
FThreadSafeCounter* AttachmentCounter;
};
TArray<FDetachCommand, SceneRenderingAllocator> DestroyCommands;
for (T* Primitive : InPrimitives)
{
FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->GetSceneProxy();
if (PrimitiveSceneProxy)
{
if (FSimpleStreamableAssetManager::IsEnabled())
{
FSimpleStreamableAssetManager::Unregister(FSimpleStreamableAssetManager::FUnregister{ PrimitiveSceneProxy });
}
FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveSceneProxy->GetPrimitiveSceneInfo();
// Disassociate the primitive's scene proxy.
Primitive->ReleaseSceneProxy();
DestroyCommands.Add({ PrimitiveSceneInfo, PrimitiveSceneProxy, Primitive->GetAttachmentCounter() });
}
}
if (!DestroyCommands.IsEmpty())
{
// must run RT cmds in order here too
ENQUEUE_RENDER_COMMAND(FRemovePrimitiveCommand)(
[this, DestroyCommands = MoveTemp(DestroyCommands)](FRHICommandListBase&)
{
for (const FDetachCommand& Command : DestroyCommands)
{
RemovePrimitiveSceneInfo_RenderThread(Command.PrimitiveSceneInfo);
Command.PrimitiveSceneProxy->DestroyRenderThreadResources();
if(Command.AttachmentCounter)
{
Command.AttachmentCounter->Decrement();
}
}
});
}
}
void FScene::BatchRemovePrimitives(TArrayView<UPrimitiveComponent*> InPrimitives)
{
BatchRemovePrimitivesInternal(InPrimitives);
}
void FScene::BatchRemovePrimitives(TArrayView<FPrimitiveSceneDesc*> InPrimitives)
{
BatchRemovePrimitivesInternal(InPrimitives);
}
void FScene::BatchRemovePrimitives(TArray<FPrimitiveSceneProxy*>&& InPrimitives)
{
if (!InPrimitives.IsEmpty())
{
ENQUEUE_RENDER_COMMAND(BatchRemovePrimitives)(
[this, InPrimitives = MoveTemp(InPrimitives)](FRHICommandListBase&)
{
for (FPrimitiveSceneProxy* PrimitiveSceneProxy : InPrimitives)
{
if (FSimpleStreamableAssetManager::IsEnabled())
{
FSimpleStreamableAssetManager::Unregister(FSimpleStreamableAssetManager::FUnregister{ PrimitiveSceneProxy });
}
RemovePrimitiveSceneInfo_RenderThread(PrimitiveSceneProxy->GetPrimitiveSceneInfo());
PrimitiveSceneProxy->DestroyRenderThreadResources();
}
});
}
}
void FScene::ReleasePrimitive(UPrimitiveComponent* PrimitiveComponent)
{
// Check if this components was already bulk released on the render side
if (PrimitiveComponent->bBulkReregister)
{
return;
}
BatchReleasePrimitives(MakeArrayView(&PrimitiveComponent, 1));
}
void FScene::ReleasePrimitive(FPrimitiveSceneDesc* Primitive)
{
// Check if this components was already bulk released on the render side
if (Primitive->bBulkReregister)
{
return;
}
BatchReleasePrimitives(MakeArrayView(&Primitive, 1));
}
template<class T>
void FScene::BatchReleasePrimitivesInternal(TArrayView<T*> InPrimitives)
{
// Send a command to the rendering thread to clean up any state dependent on this primitive
FScene* Scene = this;
TArray<FPrimitiveComponentId, TInlineAllocator<1>> ReleaseComponentIds;
ReleaseComponentIds.SetNumUninitialized(InPrimitives.Num());
for (int32 ComponentIndex = 0; ComponentIndex < InPrimitives.Num(); ComponentIndex++)
{
ReleaseComponentIds[ComponentIndex] = InPrimitives[ComponentIndex]->GetPrimitiveSceneId();
}
ENQUEUE_RENDER_COMMAND(FReleasePrimitiveCommand)(
[Scene, ReleaseComponentIds = MoveTemp(ReleaseComponentIds)] (FRHICommandListBase&)
{
for (FPrimitiveComponentId PrimitiveComponentId : ReleaseComponentIds)
{
// Free the space in the indirect lighting cache
Scene->IndirectLightingCache.ReleasePrimitive(PrimitiveComponentId);
}
});
}
void FScene::BatchReleasePrimitives(TArrayView<UPrimitiveComponent*> InPrimitives)
{
BatchReleasePrimitivesInternal(InPrimitives);
}
void FScene::BatchReleasePrimitives(TArrayView<FPrimitiveSceneDesc*> InPrimitives)
{
BatchReleasePrimitivesInternal(InPrimitives);
}
void FScene::AssignAvailableShadowMapChannelForLight(FLightSceneInfo* LightSceneInfo)
{
FDynamicShadowMapChannelBindingHelper Helper;
check(LightSceneInfo && LightSceneInfo->Proxy);
// For lights with static shadowing, only check for lights intersecting the preview channel if any.
if (LightSceneInfo->Proxy->HasStaticShadowing())
{
Helper.DisableAllOtherChannels(LightSceneInfo->GetDynamicShadowMapChannel());
// If this static shadowing light does not need a (preview) channel, skip it.
if (!Helper.HasAnyChannelEnabled())
{
return;
}
}
else if (LightSceneInfo->Proxy->GetLightType() == LightType_Directional)
{
// The implementation of forward lighting in ShadowProjectionPixelShader.usf does not support binding the directional light to channel 3.
// This is related to the USE_FADE_PLANE feature that encodes the CSM blend factor the alpha channel.
Helper.DisableChannel(3);
}
Helper.UpdateAvailableChannels(Lights, LightSceneInfo);
const int32 NewChannelIndex = Helper.GetBestAvailableChannel();
if (NewChannelIndex != INDEX_NONE)
{
// Unbind the channels previously allocated to lower priority lights.
for (FLightSceneInfo* OtherLight : Helper.GetLights(NewChannelIndex))
{
OtherLight->SetDynamicShadowMapChannel(INDEX_NONE);
}
LightSceneInfo->SetDynamicShadowMapChannel(NewChannelIndex);
// Try to assign new channels to lights that were just unbound.
// Sort the lights so that they only get inserted once (prevents recursion).
Helper.SortLightByPriority(NewChannelIndex);
for (FLightSceneInfo* OtherLight : Helper.GetLights(NewChannelIndex))
{
AssignAvailableShadowMapChannelForLight(OtherLight);
}
}
else
{
LightSceneInfo->SetDynamicShadowMapChannel(INDEX_NONE);
OverflowingDynamicShadowedLights.AddUnique(LightSceneInfo->Proxy->GetOwnerNameOrLabel());
}
}
void FScene::AddLightSceneInfo_RenderThread(FLightSceneInfo* LightSceneInfo)
{
SCOPE_CYCLE_COUNTER(STAT_AddSceneLightTime);
check(LightSceneInfo->bVisible);
// Add the light to the light list.
LightSceneInfo->Id = Lights.Add(FLightSceneInfoCompact(LightSceneInfo));
const FLightSceneInfoCompact& LightSceneInfoCompact = Lights[LightSceneInfo->Id];
const ELightComponentType LightType = ELightComponentType(LightSceneInfoCompact.LightType);
const bool bDirectionalLight = LightType == LightType_Directional;
if (bDirectionalLight)
{
DirectionalLights.Add(LightSceneInfo);
}
if (bDirectionalLight &&
// Only use a stationary or movable light
!(LightSceneInfo->Proxy->HasStaticLighting()
// if it is a Static DirectionalLight and the light has not been built, add it to MobileDirectionalLights for mobile preview.
&& LightSceneInfo->IsPrecomputedLightingValid())
)
{
// Set SimpleDirectionalLight
if(!SimpleDirectionalLight)
{
SimpleDirectionalLight = LightSceneInfo;
}
if(GetFeatureLevelShadingPath(FeatureLevel) == EShadingPath::Mobile)
{
const bool bUseCSMForDynamicObjects = LightSceneInfo->Proxy->UseCSMForDynamicObjects();
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// these are tracked for disabled shader permutation warnings
if (LightSceneInfo->Proxy->IsMovable())
{
NumMobileMovableDirectionalLights_RenderThread++;
}
if (bUseCSMForDynamicObjects)
{
NumMobileStaticAndCSMLights_RenderThread++;
}
#endif
// Set MobileDirectionalLights entry
int32 FirstLightingChannel = GetFirstLightingChannelFromMask(LightSceneInfo->Proxy->GetLightingChannelMask());
if (FirstLightingChannel >= 0 && MobileDirectionalLights[FirstLightingChannel] == nullptr)
{
MobileDirectionalLights[FirstLightingChannel] = LightSceneInfo;
// if this light is a dynamic shadowcast then we need to update the static draw lists to pick a new lighting policy:
const bool bUsesDirectionalLightForLighmapPolicySelection = IsStaticLightingAllowed() && !IsMobileDeferredShadingEnabled(GetShaderPlatform());
if (bUsesDirectionalLightForLighmapPolicySelection && (!LightSceneInfo->Proxy->HasStaticShadowing() || bUseCSMForDynamicObjects))
{
bScenesPrimitivesNeedStaticMeshElementUpdate = true;
UE_CLOG(!GIsEditor, LogRenderer, Log, TEXT("Forcing update for all mesh draw commands: Add directional light"));
}
}
}
}
// Register rect. light texture
if (LightType == LightType_Rect)
{
FRectLightSceneProxy* RectProxy = (FRectLightSceneProxy*)LightSceneInfo->Proxy;
RectProxy->RectAtlasId = RectLightAtlas::AddTexture(RectProxy->SourceTexture, RectProxy->SourceTextureScaleOffset);
}
// Register IES texture
if (UTextureLightProfile* IESTexture = LightSceneInfo->Proxy->GetIESTexture())
{
LightSceneInfo->Proxy->IESAtlasId = IESAtlas::AddTexture(IESTexture);
}
const EShaderPlatform ShaderPlatform = GetShaderPlatform();
const bool bAssignShadowMapChannel = IsForwardShadingEnabled(ShaderPlatform) || (IsMobilePlatform(ShaderPlatform) && MobileUsesShadowMaskTexture(ShaderPlatform));
if (bAssignShadowMapChannel && (LightSceneInfo->Proxy->CastsDynamicShadow() || LightSceneInfo->Proxy->GetLightFunctionMaterial()))
{
AssignAvailableShadowMapChannelForLight(LightSceneInfo);
}
ProcessAtmosphereLightAddition_RenderThread(LightSceneInfo);
InvalidatePathTracedOutput();
// Add the light to the scene.
LightSceneInfo->AddToScene();
}
void FScene::AddLight(ULightComponent* Light)
{
LLM_SCOPE(ELLMTag::SceneRender);
// Create the light's scene proxy.
FLightSceneProxy* Proxy = Light->CreateSceneProxy();
if(Proxy)
{
// Associate the proxy with the light.
Light->SceneProxy = Proxy;
// Update the light's transform and position.
Proxy->SetTransform(Light->GetComponentTransform().ToMatrixNoScale(), Light->GetLightPosition());
// Create the light scene info.
Proxy->LightSceneInfo = new FLightSceneInfo(Proxy, true);
INC_DWORD_STAT(STAT_SceneLights);
// Adding a new light
++NumVisibleLights_GameThread;
// Send a command to the rendering thread to add the light to the scene.
FLightSceneInfo* LightSceneInfo = Proxy->LightSceneInfo;
ENQUEUE_RENDER_COMMAND(FAddLightCommand)(
[this, LightSceneInfo] (FRHICommandListBase&)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Scene_AddLight);
FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());
// It should not have been added to the scene already
check(LightSceneInfo->Id == INDEX_NONE);
SceneLightInfoUpdates->EnqueueAdd(LightSceneInfo);
});
}
}
void FScene::AddInvisibleLight(ULightComponent* Light)
{
// Create the light's scene proxy.
FLightSceneProxy* Proxy = Light->CreateSceneProxy();
if(Proxy)
{
// Associate the proxy with the light.
Light->SceneProxy = Proxy;
// Update the light's transform and position.
Proxy->SetTransform(Light->GetComponentTransform().ToMatrixNoScale(),Light->GetLightPosition());
// Create the light scene info.
Proxy->LightSceneInfo = new FLightSceneInfo(Proxy, false);
INC_DWORD_STAT(STAT_SceneLights);
// Send a command to the rendering thread to add the light to the scene.
FScene* Scene = this;
FLightSceneInfo* LightSceneInfo = Proxy->LightSceneInfo;
ENQUEUE_RENDER_COMMAND(FAddLightCommand)(
[Scene, LightSceneInfo] (FRHICommandListBase&)
{
FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());
LightSceneInfo->Id = Scene->InvisibleLights.Add(FLightSceneInfoCompact(LightSceneInfo));
});
}
}
void FScene::SetSkyLight(FSkyLightSceneProxy* LightProxy)
{
check(LightProxy);
NumEnabledSkylights_GameThread++;
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FSetSkyLightCommand)(
[Scene, LightProxy] (FRHICommandListBase&)
{
check(!Scene->SkyLightStack.Contains(LightProxy));
Scene->SkyLightStack.Push(LightProxy);
// Use the most recently enabled skylight
Scene->SkyLight = LightProxy;
Scene->InvalidatePathTracedOutput();
});
}
void FScene::DisableSkyLight(FSkyLightSceneProxy* LightProxy)
{
check(LightProxy);
NumEnabledSkylights_GameThread--;
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FDisableSkyLightCommand)(
[Scene, LightProxy] (FRHICommandListBase&)
{
Scene->SkyLightStack.RemoveSingle(LightProxy);
if (Scene->SkyLightStack.Num() > 0)
{
// Use the most recently enabled skylight
Scene->SkyLight = Scene->SkyLightStack.Last();
}
else
{
Scene->SkyLight = NULL;
}
Scene->InvalidatePathTracedOutput();
});
}
bool FScene::HasSkyLightRequiringLightingBuild() const
{
return SkyLight != nullptr && !SkyLight->IsMovable();
}
bool FScene::HasAtmosphereLightRequiringLightingBuild() const
{
bool AnySunLightNotMovable = false;
for (uint8 Index = 0; Index < NUM_ATMOSPHERE_LIGHTS; ++Index)
{
AnySunLightNotMovable |= AtmosphereLights[Index] != nullptr && !AtmosphereLights[Index]->Proxy->IsMovable();
}
return AnySunLightNotMovable;
}
void FScene::AddOrRemoveDecal_RenderThread(FDeferredDecalProxy* Proxy, bool bAdd)
{
if (bAdd)
{
Decals.Add(Proxy);
InvalidatePathTracedOutput();
}
else
{
// can be optimized
for (int32 Index = 0; Index < Decals.Num(); ++Index)
{
if (Decals[Index] == Proxy)
{
InvalidatePathTracedOutput();
Decals.RemoveAtSwap(Index, EAllowShrinking::No);
delete Proxy;
break;
}
}
}
}
void FScene::SetPhysicsField(FPhysicsFieldSceneProxy* PhysicsFieldSceneProxy)
{
check(PhysicsFieldSceneProxy);
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FSetPhysicsFieldCommand)(
[Scene, PhysicsFieldSceneProxy] (FRHICommandListBase&)
{
Scene->PhysicsField = PhysicsFieldSceneProxy;
});
}
void FScene::ShowPhysicsField()
{
// Set the shader print/debug values from game thread if
// physics field visualisation has been enabled
if (PhysicsField && PhysicsField->FieldResource && PhysicsField->FieldResource->FieldInfos.bShowFields)
{
// Force ShaderPrint on.
ShaderPrint::SetEnabled(true);
ShaderPrint::RequestSpaceForLines(128000);
}
}
void FScene::ResetPhysicsField()
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FResetPhysicsFieldCommand)(
[Scene] (FRHICommandListBase&)
{
Scene->PhysicsField = nullptr;
});
}
void FScene::UpdatePhysicsField(FRDGBuilder& GraphBuilder, FViewInfo& View)
{
if (PhysicsField)
{
PhysicsField->FieldResource->FieldInfos.ViewOrigin = View.ViewMatrices.GetViewOrigin();
if (View.Family )
{
PhysicsField->FieldResource->FieldInfos.bShowFields = View.Family->EngineShowFlags.PhysicsField;
}
}
}
void FScene::AddDecal(UDecalComponent* Component)
{
if(!Component->SceneProxy)
{
// Create the decals's scene proxy.
Component->SceneProxy = Component->CreateSceneProxy();
// If there is no scene proxy then don't add to scene
if (!Component->SceneProxy)
{
return;
}
INC_DWORD_STAT(STAT_SceneDecals);
// Send a command to the rendering thread to add the light to the scene.
FScene* Scene = this;
FDeferredDecalProxy* Proxy = Component->SceneProxy;
ENQUEUE_RENDER_COMMAND(FAddDecalCommand)(
[Scene, Proxy] (FRHICommandListBase&)
{
Scene->AddOrRemoveDecal_RenderThread(Proxy, true);
});
}
}
void FScene::RemoveDecal(UDecalComponent* Component)
{
if(Component->SceneProxy)
{
DEC_DWORD_STAT(STAT_SceneDecals);
// Send a command to the rendering thread to remove the light from the scene.
FScene* Scene = this;
FDeferredDecalProxy* Proxy = Component->SceneProxy;
ENQUEUE_RENDER_COMMAND(FRemoveDecalCommand)(
[Scene, Proxy] (FRHICommandListBase&)
{
Scene->AddOrRemoveDecal_RenderThread(Proxy, false);
});
// Disassociate the primitive's scene proxy.
Component->SceneProxy = NULL;
}
}
void FScene::UpdateDecalTransform(UDecalComponent* Decal)
{
if(Decal->SceneProxy)
{
//Send command to the rendering thread to update the decal's transform.
FScene* Scene = this;
FDeferredDecalProxy* DecalSceneProxy = Decal->SceneProxy;
FTransform ComponentToWorldIncludingDecalSize = Decal->GetTransformIncludingDecalSize();
FBoxSphereBounds Bounds = Decal->CalcBounds(Decal->GetComponentTransform());
ENQUEUE_RENDER_COMMAND(UpdateTransformCommand)(
[DecalSceneProxy, ComponentToWorldIncludingDecalSize, Bounds, Scene] (FRHICommandListBase&)
{
// Invalidate the path tracer only if the decal was sufficiently moved
if (!ComponentToWorldIncludingDecalSize.Equals(DecalSceneProxy->ComponentTrans, SMALL_NUMBER))
{
Scene->InvalidatePathTracedOutput();
}
// Update the primitive's transform.
DecalSceneProxy->SetTransformIncludingDecalSize(ComponentToWorldIncludingDecalSize, Bounds);
});
}
}
void FScene::UpdateDecalFadeOutTime(UDecalComponent* Decal)
{
FDeferredDecalProxy* Proxy = Decal->SceneProxy;
if(Proxy)
{
float CurrentTime = GetWorld()->GetTimeSeconds();
float DecalFadeStartDelay = Decal->FadeStartDelay;
float DecalFadeDuration = Decal->FadeDuration;
ENQUEUE_RENDER_COMMAND(FUpdateDecalFadeInTimeCommand)(
[Proxy, CurrentTime, DecalFadeStartDelay, DecalFadeDuration] (FRHICommandListBase&)
{
if (DecalFadeDuration > 0.0f)
{
Proxy->InvFadeDuration = 1.0f / DecalFadeDuration;
Proxy->FadeStartDelayNormalized = (CurrentTime + DecalFadeStartDelay + DecalFadeDuration) * Proxy->InvFadeDuration;
}
else
{
Proxy->InvFadeDuration = -1.0f;
Proxy->FadeStartDelayNormalized = 1.0f;
}
});
}
}
void FScene::UpdateDecalFadeInTime(UDecalComponent* Decal)
{
FDeferredDecalProxy* Proxy = Decal->SceneProxy;
if (Proxy)
{
float CurrentTime = GetWorld()->GetTimeSeconds();
float DecalFadeStartDelay = Decal->FadeInStartDelay;
float DecalFadeDuration = Decal->FadeInDuration;
ENQUEUE_RENDER_COMMAND(FUpdateDecalFadeInTimeCommand)(
[Proxy, CurrentTime, DecalFadeStartDelay, DecalFadeDuration] (FRHICommandListBase&)
{
if (DecalFadeDuration > 0.0f)
{
Proxy->InvFadeInDuration = 1.0f / DecalFadeDuration;
Proxy->FadeInStartDelayNormalized = (CurrentTime + DecalFadeStartDelay) * -Proxy->InvFadeInDuration;
}
else
{
Proxy->InvFadeInDuration = 1.0f;
Proxy->FadeInStartDelayNormalized = 0.0f;
}
});
}
}
void FScene::BatchUpdateDecals(TArray<FDeferredDecalUpdateParams>&& UpdateParams)
{
ENQUEUE_RENDER_COMMAND(FBatchUpdateDecalsCommand)(
[Scene=this, UpdateParams_RT=MoveTemp(UpdateParams)] (FRHICommandListBase&)
{
for (const FDeferredDecalUpdateParams& DecalUpdate : UpdateParams_RT )
{
if (DecalUpdate.OperationType == FDeferredDecalUpdateParams::EOperationType::RemoveFromSceneAndDelete)
{
Scene->AddOrRemoveDecal_RenderThread(DecalUpdate.DecalProxy, false);
continue;
}
if (DecalUpdate.OperationType == FDeferredDecalUpdateParams::EOperationType::AddToSceneAndUpdate )
{
Scene->AddOrRemoveDecal_RenderThread(DecalUpdate.DecalProxy, true);
}
DecalUpdate.DecalProxy->SetTransformIncludingDecalSize(DecalUpdate.Transform, DecalUpdate.Bounds);
// When FadeDuration is intentionally set to 0 the user expects the decal to not fade automatically
if (DecalUpdate.FadeDuration == 0.0f)
{
DecalUpdate.DecalProxy->InvFadeDuration = -1.0f;
}
else
{
DecalUpdate.DecalProxy->InitializeFadingParameters(DecalUpdate.AbsSpawnTime, DecalUpdate.FadeDuration, DecalUpdate.FadeStartDelay, DecalUpdate.FadeInDuration, DecalUpdate.FadeInStartDelay);
}
DecalUpdate.DecalProxy->FadeScreenSize = DecalUpdate.FadeScreenSize;
DecalUpdate.DecalProxy->SortOrder = DecalUpdate.SortOrder;
DecalUpdate.DecalProxy->DecalColor = DecalUpdate.DecalColor;
}
}
);
}
void FScene::AddHairStrands(FHairStrandsInstance* Proxy)
{
if (Proxy)
{
const int32 PackedIndex = HairStrandsSceneData.RegisteredProxies.Add(Proxy);
Proxy->RegisteredIndex = PackedIndex;
}
}
void FScene::RemoveHairStrands(FHairStrandsInstance* Proxy)
{
if (Proxy)
{
int32 ProxyIndex = Proxy->RegisteredIndex;
if (HairStrandsSceneData.RegisteredProxies.IsValidIndex(ProxyIndex))
{
HairStrandsSceneData.RegisteredProxies.RemoveAtSwap(ProxyIndex);
}
Proxy->RegisteredIndex = -1;
if (HairStrandsSceneData.RegisteredProxies.IsValidIndex(ProxyIndex))
{
FHairStrandsInstance* Other = HairStrandsSceneData.RegisteredProxies[ProxyIndex];
Other->RegisteredIndex = ProxyIndex;
}
}
}
TArray<FAnimBankRecordHandle> FScene::RegisterAnimBank(const TConstArrayView<FAnimBankDesc> Descs)
{
check(IsInRenderingThread());
TArray<FAnimBankRecordHandle> Handles;
Handles.Reserve(Descs.Num());
FAnimBankTransformProvider* BankProvider = GetExtensionPtr<FAnimBankTransformProvider>();
if (BankProvider != nullptr)
{
for (const FAnimBankDesc& Desc : Descs)
{
if (!Desc.BankAsset.IsValid() || !Desc.Asset.IsValid())
{
Handles.Emplace(FAnimBankRecordHandle());
continue;
}
const FAnimBankData& BankData = Desc.BankAsset->GetData();
if (Desc.SequenceIndex >= uint32(BankData.Entries.Num()))
{
Handles.Emplace(FAnimBankRecordHandle());
continue;
}
Handles.Emplace(BankProvider->RegisterBank(Desc));
}
}
return Handles;
}
void FScene::UnregisterAnimBank(const TConstArrayView<FAnimBankRecordHandle> Handles)
{
check(IsInRenderingThread());
FAnimBankTransformProvider* BankProvider = GetExtensionPtr<FAnimBankTransformProvider>();
if (BankProvider != nullptr)
{
for (const FAnimBankRecordHandle& Handle : Handles)
{
if (!Handle.IsValid())
{
continue;
}
BankProvider->UnregisterBank(Handle);
}
}
}
void FScene::GetLightIESAtlasSlot(const FLightSceneProxy* Proxy, FLightRenderParameters* Out)
{
if (Proxy)
{
//check(!IsInGameThread());
Out->IESAtlasIndex = IESAtlas::GetAtlasSlot(Proxy->IESAtlasId);
}
}
void FScene::GetRectLightAtlasSlot(const FRectLightSceneProxy* Proxy, FLightRenderParameters* Out)
{
if (Proxy)
{
//check(IsInRenderingThread());
const RectLightAtlas::FAtlasSlotDesc Slot = RectLightAtlas::GetAtlasSlot(Proxy->RectAtlasId);
Out->RectLightAtlasUVOffset = Slot.UVOffset;
Out->RectLightAtlasUVScale = Slot.UVScale;
Out->RectLightAtlasMaxLevel = Slot.MaxMipLevel;
}
}
void FScene::AddReflectionCapture(UReflectionCaptureComponent* Component)
{
if (!Component->SceneProxy)
{
Component->SceneProxy = Component->CreateSceneProxy();
FScene* Scene = this;
FReflectionCaptureProxy* Proxy = Component->SceneProxy;
const FVector Position = Component->GetComponentLocation();
ENQUEUE_RENDER_COMMAND(FAddCaptureCommand)(
[Scene, Proxy, Position](FRHICommandListBase& RHICmdList)
{
if (Proxy->bUsingPreviewCaptureData)
{
FPlatformAtomics::InterlockedIncrement(&Scene->NumUnbuiltReflectionCaptures);
}
Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged = true;
const int32 PackedIndex = Scene->ReflectionSceneData.RegisteredReflectionCaptures.Add(Proxy);
Proxy->PackedIndex = PackedIndex;
Scene->ReflectionSceneData.RegisteredReflectionCapturePositionAndRadius.Add(FSphere(Position, Proxy->InfluenceRadius));
if (Scene->GetFeatureLevel() <= ERHIFeatureLevel::ES3_1)
{
Proxy->UpdateMobileUniformBuffer(RHICmdList);
}
checkSlow(Scene->ReflectionSceneData.RegisteredReflectionCaptures.Num() == Scene->ReflectionSceneData.RegisteredReflectionCapturePositionAndRadius.Num());
});
}
}
void FScene::RemoveReflectionCapture(UReflectionCaptureComponent* Component)
{
if (Component->SceneProxy)
{
FScene* Scene = this;
FReflectionCaptureProxy* Proxy = Component->SceneProxy;
ENQUEUE_RENDER_COMMAND(FRemoveCaptureCommand)(
[Scene, Proxy] (FRHICommandListBase&)
{
if (Proxy->bUsingPreviewCaptureData)
{
FPlatformAtomics::InterlockedDecrement(&Scene->NumUnbuiltReflectionCaptures);
}
Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged = true;
// Need to clear out all reflection captures on removal to avoid dangling pointers.
for (int32 PrimitiveIndex = 0; PrimitiveIndex < Scene->Primitives.Num(); ++PrimitiveIndex)
{
Scene->Primitives[PrimitiveIndex]->RemoveCachedReflectionCaptures();
}
int32 CaptureIndex = Proxy->PackedIndex;
Scene->ReflectionSceneData.RegisteredReflectionCaptures.RemoveAtSwap(CaptureIndex);
Scene->ReflectionSceneData.RegisteredReflectionCapturePositionAndRadius.RemoveAtSwap(CaptureIndex);
if (Scene->ReflectionSceneData.RegisteredReflectionCaptures.IsValidIndex(CaptureIndex))
{
FReflectionCaptureProxy* OtherCapture = Scene->ReflectionSceneData.RegisteredReflectionCaptures[CaptureIndex];
OtherCapture->PackedIndex = CaptureIndex;
}
delete Proxy;
checkSlow(Scene->ReflectionSceneData.RegisteredReflectionCaptures.Num() == Scene->ReflectionSceneData.RegisteredReflectionCapturePositionAndRadius.Num());
});
// Disassociate the primitive's scene proxy.
Component->SceneProxy = NULL;
}
}
void FScene::UpdateReflectionCaptureTransform(UReflectionCaptureComponent* Component)
{
if (Component->SceneProxy)
{
const FReflectionCaptureMapBuildData* MapBuildData = Component->GetMapBuildData();
bool bUsingPreviewCaptureData = MapBuildData == NULL;
FScene* Scene = this;
FReflectionCaptureProxy* Proxy = Component->SceneProxy;
FMatrix Transform = Component->GetComponentTransform().ToMatrixWithScale();
ENQUEUE_RENDER_COMMAND(UpdateReflectionCaptureTransformCommand)(
[Scene, Proxy, Transform, bUsingPreviewCaptureData](FRHICommandListBase& RHICmdList)
{
if (Proxy->bUsingPreviewCaptureData)
{
FPlatformAtomics::InterlockedDecrement(&Scene->NumUnbuiltReflectionCaptures);
}
Proxy->bUsingPreviewCaptureData = bUsingPreviewCaptureData;
if (Proxy->bUsingPreviewCaptureData)
{
FPlatformAtomics::InterlockedIncrement(&Scene->NumUnbuiltReflectionCaptures);
}
Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged = true;
Proxy->SetTransform(Transform);
if (Scene->GetFeatureLevel() <= ERHIFeatureLevel::ES3_1)
{
Proxy->UpdateMobileUniformBuffer(RHICmdList);
}
});
}
}
void FScene::ReleaseReflectionCubemap(UReflectionCaptureComponent* CaptureComponent)
{
bool bRemoved = false;
for (TSparseArray<UReflectionCaptureComponent*>::TIterator It(ReflectionSceneData.AllocatedReflectionCapturesGameThread); It; ++It)
{
UReflectionCaptureComponent* CurrentCapture = *It;
if (CurrentCapture == CaptureComponent)
{
It.RemoveCurrent();
bRemoved = true;
break;
}
}
if (bRemoved)
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(RemoveCaptureCommand)(
[CaptureComponent, Scene] (FRHICommandListBase&)
{
int32 IndexToFree = -1;
const FCaptureComponentSceneState* ComponentStatePtr = Scene->ReflectionSceneData.AllocatedReflectionCaptureState.Find(CaptureComponent);
if (ComponentStatePtr)
{
// We track removed captures so we can remap them when reallocating the cubemap array
check(ComponentStatePtr->CubemapIndex != -1);
IndexToFree = ComponentStatePtr->CubemapIndex;
}
const bool bDidRemove = Scene->ReflectionSceneData.AllocatedReflectionCaptureState.Remove(CaptureComponent);
if (bDidRemove && (IndexToFree != -1))
{
Scene->ReflectionSceneData.CubemapArraySlotsUsed[IndexToFree] = false;
}
});
}
}
const FReflectionCaptureProxy* FScene::FindClosestReflectionCapture(FVector Position) const
{
checkSlow(IsInParallelRenderingThread());
float ClosestDistanceSquared = FLT_MAX;
int32 ClosestInfluencingCaptureIndex = INDEX_NONE;
// Linear search through the scene's reflection captures
// ReflectionSceneData.RegisteredReflectionCapturePositionAndRadius has been packed densely to make this coherent in memory
for (int32 CaptureIndex = 0; CaptureIndex < ReflectionSceneData.RegisteredReflectionCapturePositionAndRadius.Num(); CaptureIndex++)
{
const FSphere& ReflectionCapturePositionAndRadius = ReflectionSceneData.RegisteredReflectionCapturePositionAndRadius[CaptureIndex];
const float DistanceSquared = (ReflectionCapturePositionAndRadius.Center - Position).SizeSquared();
// If the Position is inside the InfluenceRadius of a ReflectionCapture
if (DistanceSquared <= FMath::Square(ReflectionCapturePositionAndRadius.W))
{
// Choose the closest ReflectionCapture or record the first one found.
if (ClosestInfluencingCaptureIndex == INDEX_NONE || DistanceSquared < ClosestDistanceSquared)
{
ClosestDistanceSquared = DistanceSquared;
ClosestInfluencingCaptureIndex = CaptureIndex;
}
}
}
return ClosestInfluencingCaptureIndex != INDEX_NONE ? ReflectionSceneData.RegisteredReflectionCaptures[ClosestInfluencingCaptureIndex] : NULL;
}
const FPlanarReflectionSceneProxy* FScene::FindClosestPlanarReflection(const FBoxSphereBounds& Bounds) const
{
checkSlow(IsInParallelRenderingThread());
const FPlanarReflectionSceneProxy* ClosestPlanarReflection = NULL;
float ClosestDistance = FLT_MAX;
FBox PrimitiveBoundingBox(Bounds.Origin - Bounds.BoxExtent, Bounds.Origin + Bounds.BoxExtent);
// Linear search through the scene's planar reflections
for (int32 CaptureIndex = 0; CaptureIndex < PlanarReflections.Num(); CaptureIndex++)
{
FPlanarReflectionSceneProxy* CurrentPlanarReflection = PlanarReflections[CaptureIndex];
const FBox ReflectionBounds = CurrentPlanarReflection->WorldBounds;
if (PrimitiveBoundingBox.Intersect(ReflectionBounds))
{
const float Distance = FMath::Abs(CurrentPlanarReflection->ReflectionPlane.PlaneDot(Bounds.Origin));
if (Distance < ClosestDistance)
{
ClosestDistance = Distance;
ClosestPlanarReflection = CurrentPlanarReflection;
}
}
}
return ClosestPlanarReflection;
}
const FPlanarReflectionSceneProxy* FScene::GetForwardPassGlobalPlanarReflection() const
{
// For the forward pass just pick first planar reflection.
if (PlanarReflections.Num() > 0)
{
return PlanarReflections[0];
}
return nullptr;
}
void FScene::FindClosestReflectionCaptures(FVector Position, const FReflectionCaptureProxy* (&SortedByDistanceOUT)[FPrimitiveSceneInfo::MaxCachedReflectionCaptureProxies]) const
{
checkSlow(IsInParallelRenderingThread());
static const int32 ArraySize = FPrimitiveSceneInfo::MaxCachedReflectionCaptureProxies;
struct FReflectionCaptureDistIndex
{
int32 CaptureIndex;
float CaptureDistance;
const FReflectionCaptureProxy* CaptureProxy;
};
// Find the nearest n captures to this primitive.
const int32 NumRegisteredReflectionCaptures = ReflectionSceneData.RegisteredReflectionCapturePositionAndRadius.Num();
const int32 PopulateCaptureCount = FMath::Min(ArraySize, NumRegisteredReflectionCaptures);
TArray<FReflectionCaptureDistIndex, TFixedAllocator<ArraySize>> ClosestCaptureIndices;
ClosestCaptureIndices.AddUninitialized(PopulateCaptureCount);
for (int32 CaptureIndex = 0; CaptureIndex < PopulateCaptureCount; CaptureIndex++)
{
ClosestCaptureIndices[CaptureIndex].CaptureIndex = CaptureIndex;
ClosestCaptureIndices[CaptureIndex].CaptureDistance = (ReflectionSceneData.RegisteredReflectionCapturePositionAndRadius[CaptureIndex].Center - Position).SizeSquared();
}
for (int32 CaptureIndex = PopulateCaptureCount; CaptureIndex < NumRegisteredReflectionCaptures; CaptureIndex++)
{
const float DistanceSquared = (ReflectionSceneData.RegisteredReflectionCapturePositionAndRadius[CaptureIndex].Center - Position).SizeSquared();
for (int32 i = 0; i < ArraySize; i++)
{
if (DistanceSquared<ClosestCaptureIndices[i].CaptureDistance)
{
ClosestCaptureIndices[i].CaptureDistance = DistanceSquared;
ClosestCaptureIndices[i].CaptureIndex = CaptureIndex;
break;
}
}
}
for (int32 CaptureIndex = 0; CaptureIndex < PopulateCaptureCount; CaptureIndex++)
{
FReflectionCaptureProxy* CaptureProxy = ReflectionSceneData.RegisteredReflectionCaptures[ClosestCaptureIndices[CaptureIndex].CaptureIndex];
ClosestCaptureIndices[CaptureIndex].CaptureProxy = CaptureProxy;
}
// Sort by influence radius.
ClosestCaptureIndices.Sort([](const FReflectionCaptureDistIndex& A, const FReflectionCaptureDistIndex& B)
{
if (A.CaptureProxy->InfluenceRadius != B.CaptureProxy->InfluenceRadius)
{
return (A.CaptureProxy->InfluenceRadius < B.CaptureProxy->InfluenceRadius);
}
return A.CaptureProxy->Guid < B.CaptureProxy->Guid;
});
FMemory::Memzero(SortedByDistanceOUT);
for (int32 CaptureIndex = 0; CaptureIndex < PopulateCaptureCount; CaptureIndex++)
{
SortedByDistanceOUT[CaptureIndex] = ClosestCaptureIndices[CaptureIndex].CaptureProxy;
}
}
int64 FScene::GetCachedWholeSceneShadowMapsSize() const
{
int64 CachedShadowmapMemory = 0;
for (TMap<int32, TArray<FCachedShadowMapData>>::TConstIterator CachedShadowMapIt(CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt)
{
const TArray<FCachedShadowMapData>& ShadowMapDatas = CachedShadowMapIt.Value();
for (const auto& ShadowMapData : ShadowMapDatas)
{
if (ShadowMapData.ShadowMap.IsValid())
{
CachedShadowmapMemory += ShadowMapData.ShadowMap.ComputeMemorySize();
}
}
}
return CachedShadowmapMemory;
}
void FScene::AddPrecomputedLightVolume(const FPrecomputedLightVolume* Volume)
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(AddVolumeCommand)(
[Scene, Volume] (FRHICommandListBase&)
{
Scene->PrecomputedLightVolumes.Add(Volume);
Scene->IndirectLightingCache.SetLightingCacheDirty(Scene, Volume);
});
}
void FScene::RemovePrecomputedLightVolume(const FPrecomputedLightVolume* Volume)
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(RemoveVolumeCommand)(
[Scene, Volume] (FRHICommandListBase&)
{
Scene->PrecomputedLightVolumes.Remove(Volume);
Scene->IndirectLightingCache.SetLightingCacheDirty(Scene, Volume);
});
}
void FVolumetricLightmapSceneData::AddLevelVolume(const FPrecomputedVolumetricLightmap* InVolume, EShadingPath ShadingPath, bool bIsPersistentLevel)
{
LevelVolumetricLightmaps.Add(InVolume);
if (bIsPersistentLevel)
{
PersistentLevelVolumetricLightmap = InVolume;
}
InVolume->Data->AddToSceneData(&GlobalVolumetricLightmapData);
// Invalidate CPU lightmap lookup cache
CPUInterpolationCache.Empty();
}
void FVolumetricLightmapSceneData::RemoveLevelVolume(const FPrecomputedVolumetricLightmap* InVolume)
{
LevelVolumetricLightmaps.Remove(InVolume);
InVolume->Data->RemoveFromSceneData(&GlobalVolumetricLightmapData, PersistentLevelVolumetricLightmap ? PersistentLevelVolumetricLightmap->Data->BrickDataBaseOffsetInAtlas : 0);
if (PersistentLevelVolumetricLightmap == InVolume)
{
PersistentLevelVolumetricLightmap = nullptr;
}
// Invalidate CPU lightmap lookup cache
CPUInterpolationCache.Empty();
}
const FPrecomputedVolumetricLightmap* FVolumetricLightmapSceneData::GetLevelVolumetricLightmap() const
{
#if WITH_EDITOR
if (FStaticLightingSystemInterface::GetPrecomputedVolumetricLightmap(Scene->GetWorld()))
{
return FStaticLightingSystemInterface::GetPrecomputedVolumetricLightmap(Scene->GetWorld());
}
#endif
return &GlobalVolumetricLightmap;
}
bool FVolumetricLightmapSceneData::HasData() const
{
#if WITH_EDITOR
if (FStaticLightingSystemInterface::GetPrecomputedVolumetricLightmap(Scene->GetWorld()))
{
return true;
}
#endif
if (LevelVolumetricLightmaps.Num() > 0)
{
if (Scene->GetFeatureLevel() >= ERHIFeatureLevel::SM5)
{
return GlobalVolumetricLightmapData.IndirectionTexture.Texture.IsValid();
}
else
{
return GlobalVolumetricLightmapData.IndirectionTexture.Data.Num() > 0;
}
}
return false;
}
bool FScene::HasPrecomputedVolumetricLightmap_RenderThread() const
{
#if WITH_EDITOR
if (FStaticLightingSystemInterface::GetPrecomputedVolumetricLightmap(GetWorld()))
{
return true;
}
#endif
return VolumetricLightmapSceneData.HasData();
}
void FScene::AddPrecomputedVolumetricLightmap(const FPrecomputedVolumetricLightmap* Volume, bool bIsPersistentLevel)
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(AddVolumeCommand)
([Scene, Volume, bIsPersistentLevel] (FRHICommandListBase&)
{
Scene->VolumetricLightmapSceneData.AddLevelVolume(Volume, Scene->GetShadingPath(), bIsPersistentLevel);
});
}
void FScene::RemovePrecomputedVolumetricLightmap(const FPrecomputedVolumetricLightmap* Volume)
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(RemoveVolumeCommand)
([Scene, Volume] (FRHICommandListBase&)
{
Scene->VolumetricLightmapSceneData.RemoveLevelVolume(Volume);
});
}
static void UpdateRuntimeVirtualTextureHidePrimitiveMasks(TSparseArray<FRuntimeVirtualTextureSceneProxy*> const& InProxies, bool& bOutEditor, bool& bOutGame)
{
// Set the global flag to hide when any one volume sets it.
bOutEditor = false;
bOutGame = false;
for (TSparseArray<FRuntimeVirtualTextureSceneProxy*>::TConstIterator It(InProxies); It && !bOutEditor && !bOutGame; ++It)
{
if ((*It)->RuntimeVirtualTextureId != -1)
{
bOutEditor |= (*It)->bHidePrimitivesInEditor;
bOutGame |= (*It)->bHidePrimitivesInGame;
}
}
}
static void FixMultipleRuntimeVirtualTextureUsage(TSparseArray<FRuntimeVirtualTextureSceneProxy*> const& InProxies, FRuntimeVirtualTextureSceneProxy const* SceneProxy)
{
// If we add a runtime virtual texture component to a scene which uses the same asset as another, then we need to reset the first one.
// This should only happen for bad data setups, so warn here.
for (TSparseArray<FRuntimeVirtualTextureSceneProxy*>::TConstIterator It(InProxies); It; ++It)
{
if (*It != SceneProxy && (*It)->RuntimeVirtualTextureId == SceneProxy->RuntimeVirtualTextureId)
{
UE_LOG(LogRenderer, Warning, TEXT("Found two RVT components in a scene using the same RVT asset."));
(*It)->MarkUnused();
}
}
}
void FScene::AddRuntimeVirtualTexture(URuntimeVirtualTextureComponent* Component)
{
if (Component->SceneProxy == nullptr)
{
Component->SceneProxy = new FRuntimeVirtualTextureSceneProxy(Component);
FScene* Scene = this;
FRuntimeVirtualTextureSceneProxy* SceneProxy = Component->SceneProxy;
ENQUEUE_RENDER_COMMAND(AddRuntimeVirtualTextureCommand)(
[Scene, SceneProxy] (FRHICommandListBase&)
{
SceneProxy->SceneIndex = Scene->RuntimeVirtualTextures.Add(SceneProxy);
FixMultipleRuntimeVirtualTextureUsage(Scene->RuntimeVirtualTextures, SceneProxy);
UpdateRuntimeVirtualTextureHidePrimitiveMasks(Scene->RuntimeVirtualTextures, Scene->bRuntimeVirtualTexturePrimitiveHideEditor, Scene->bRuntimeVirtualTexturePrimitiveHideGame);
});
}
else
{
// This is a component update.
// Delete old proxy and add new one on the render thread.
FRuntimeVirtualTextureSceneProxy* SceneProxyToRemove = Component->SceneProxy;
SceneProxyToRemove->Release();
Component->SceneProxy = new FRuntimeVirtualTextureSceneProxy(Component);
FScene* Scene = this;
FRuntimeVirtualTextureSceneProxy* SceneProxyToAdd = Component->SceneProxy;
ENQUEUE_RENDER_COMMAND(UpdateRuntimeVirtualTextureCommand)(
[Scene, SceneProxyToAdd, SceneProxyToRemove] (FRHICommandListBase&)
{
Scene->RuntimeVirtualTextures.RemoveAt(SceneProxyToRemove->SceneIndex);
delete SceneProxyToRemove;
SceneProxyToAdd->SceneIndex = Scene->RuntimeVirtualTextures.Add(SceneProxyToAdd);
UpdateRuntimeVirtualTextureHidePrimitiveMasks(Scene->RuntimeVirtualTextures, Scene->bRuntimeVirtualTexturePrimitiveHideEditor, Scene->bRuntimeVirtualTexturePrimitiveHideGame);
});
}
}
void FScene::RemoveRuntimeVirtualTexture(URuntimeVirtualTextureComponent* Component)
{
FRuntimeVirtualTextureSceneProxy* SceneProxy = Component->SceneProxy;
if (SceneProxy != nullptr)
{
// Release now but defer any deletion to the render thread
Component->SceneProxy->Release();
Component->SceneProxy = nullptr;
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(RemoveRuntimeVirtualTextureCommand)(
[Scene, SceneProxy] (FRHICommandListBase&)
{
Scene->RuntimeVirtualTextures.RemoveAt(SceneProxy->SceneIndex);
delete SceneProxy;
UpdateRuntimeVirtualTextureHidePrimitiveMasks(Scene->RuntimeVirtualTextures, Scene->bRuntimeVirtualTexturePrimitiveHideEditor, Scene->bRuntimeVirtualTexturePrimitiveHideGame);
});
}
}
void FScene::GetRuntimeVirtualTextureHidePrimitiveMask(uint8& bHideMaskEditor, uint8& bHideMaskGame) const
{
bHideMaskEditor = bRuntimeVirtualTexturePrimitiveHideEditor ? 0xff : 0;
bHideMaskGame = bRuntimeVirtualTexturePrimitiveHideGame ? 0xff : 0;
}
void FScene::InvalidateRuntimeVirtualTexture(URuntimeVirtualTextureComponent* Component, FBoxSphereBounds const& WorldBounds, EVTInvalidatePriority InvalidatePriority)
{
if (Component->SceneProxy != nullptr)
{
FRuntimeVirtualTextureSceneProxy* SceneProxy = Component->SceneProxy;
ENQUEUE_RENDER_COMMAND(InvalidateRuntimeVirtualTextureCommand)(
[SceneProxy, WorldBounds, InvalidatePriority] (FRHICommandListBase&)
{
SceneProxy->Dirty(WorldBounds, InvalidatePriority);
});
}
}
void FScene::FlushDirtyRuntimeVirtualTextures()
{
checkSlow(IsInRenderingThread());
for (TSparseArray<FRuntimeVirtualTextureSceneProxy*>::TIterator It(RuntimeVirtualTextures); It; ++It)
{
(*It)->FlushDirtyPages();
}
}
void FScene::RequestPreloadRuntimeVirtualTexture(class URuntimeVirtualTextureComponent* Component, FBoxSphereBounds const& WorldBounds, int32 Level)
{
if (Component->SceneProxy != nullptr)
{
FRuntimeVirtualTextureSceneProxy* SceneProxy = Component->SceneProxy;
ENQUEUE_RENDER_COMMAND(PreloadRuntimeVirtualTextureCommand)(
[SceneProxy, WorldBounds, Level](FRHICommandListBase&)
{
SceneProxy->RequestPreload(WorldBounds, Level);
});
}
}
void FScene::InvalidatePathTracedOutput(PathTracing::EInvalidateReason InvalidateReason)
{
#if RHI_RAYTRACING
if (PathTracing::IsOutputInvalidateAllowed(InvalidateReason))
{
// NOTE: this is an atomic, so this function is ok to call from any thread
++PathTracingInvalidationCounter;
}
#endif
}
void FScene::InvalidateLumenSurfaceCache_GameThread(UPrimitiveComponent* Component)
{
check(IsInGameThread());
if (Component->SceneProxy)
{
ENQUEUE_RENDER_COMMAND(InvalidateLumenSurfaceCacheCmd)(
[this, PrimitiveSceneProxy = Component->SceneProxy] (FRHICommandListBase&)
{
if (PrimitiveSceneProxy && PrimitiveSceneProxy->GetPrimitiveSceneInfo())
{
LumenInvalidateSurfaceCacheForPrimitive(PrimitiveSceneProxy->GetPrimitiveSceneInfo());
}
});
}
}
bool FScene::GetPreviousLocalToWorld(const FPrimitiveSceneInfo* PrimitiveSceneInfo, FMatrix& OutPreviousLocalToWorld) const
{
return VelocityData.GetComponentPreviousLocalToWorld(PrimitiveSceneInfo->PrimitiveComponentId, OutPreviousLocalToWorld);
}
void FSceneVelocityData::StartFrame(FScene* Scene)
{
InternalFrameIndex++;
const bool bTrimOld = InternalFrameIndex % 100 == 0;
for (TMap<FPrimitiveComponentId, FComponentVelocityData>::TIterator It(ComponentData); It; ++It)
{
FComponentVelocityData& VelocityData = It.Value();
VelocityData.PreviousLocalToWorld = VelocityData.LocalToWorld;
VelocityData.bPreviousLocalToWorldValid = true;
if ((InternalFrameIndex - VelocityData.LastFrameUpdated == 1) && VelocityData.PrimitiveSceneInfo)
{
// Force an update of the primitive data on the frame after the primitive moved, since it contains PreviousLocalToWorld
VelocityData.PrimitiveSceneInfo->MarkGPUStateDirty(EPrimitiveDirtyState::ChangedTransform);
}
if (bTrimOld && (InternalFrameIndex - VelocityData.LastFrameUsed) > 10)
{
if (VelocityData.PrimitiveSceneInfo)
{
Scene->GPUScene.AddPrimitiveToUpdate(VelocityData.PrimitiveSceneInfo->GetPersistentIndex(), EPrimitiveDirtyState::ChangedOther);
}
It.RemoveCurrent();
}
}
}
void FScene::GetPrimitiveUniformShaderParameters_RenderThread(const FPrimitiveSceneInfo* PrimitiveSceneInfo, bool& bHasPrecomputedVolumetricLightmap, FMatrix& PreviousLocalToWorld, int32& SingleCaptureIndex, bool& bOutputVelocity) const
{
SCOPED_NAMED_EVENT(GetPrimitiveUniformShaderParameters_RenderThread, FColor::Yellow);
const FMatrix LocalToWorld = PrimitiveSceneInfo->Proxy->GetLocalToWorld();
PreviousLocalToWorld = LocalToWorld;
bOutputVelocity = false;
const bool bHasPreviousLocalToWorld = VelocityData.GetComponentPreviousLocalToWorld(PrimitiveSceneInfo->PrimitiveComponentId, PreviousLocalToWorld);
if (bHasPreviousLocalToWorld)
{
bOutputVelocity = !LocalToWorld.Equals(PreviousLocalToWorld, 0.0001f);
}
bHasPrecomputedVolumetricLightmap = VolumetricLightmapSceneData.HasData();
// Get index if proxy exists, otherwise fall back to index 0 which will contain the default black cubemap
SingleCaptureIndex = PrimitiveSceneInfo->CachedReflectionCaptureProxy ? PrimitiveSceneInfo->CachedReflectionCaptureProxy->SortedCaptureIndex : 0;
}
bool DoesPlatformNeedLocalLightPrimitiveInteraction(EShaderPlatform ShaderPlatform)
{
if (CVarVisibilityLocalLightPrimitiveInteraction.GetValueOnRenderThread() == 0)
{
return false;
}
extern bool MobileLocalLightsUseSinglePermutation(EShaderPlatform ShaderPlatform);
return !IsMobilePlatform(ShaderPlatform) || !MobileLocalLightsUseSinglePermutation(ShaderPlatform) || IsMobileMovableSpotlightShadowsEnabled(ShaderPlatform);
}
void FScene::UpdateLightTransform_RenderThread(int32 LightId, FLightSceneInfo* LightSceneInfo, const FUpdateLightTransformParameters& Parameters)
{
SCOPED_NAMED_EVENT(FScene_UpdateLightTransform_RenderThread, FColor::Yellow);
// This is called without a valid ID when the update is fused with an 'add' command (saves redundant scene updates to do the update first)
const bool bHasId = LightId != INDEX_NONE;
// Don't Update Primitive Interactions for directional lights
const bool bUpdatePrimitiveInteractions = bHasId && (Lights[LightId].LightType != LightType_Directional);
// Invalidate the path tracer if the transform actually changed
// NOTE: Position is derived from the Matrix, so there is no need to check it separately
if (!Parameters.LightToWorld.Equals(LightSceneInfo->Proxy->LightToWorld, SMALL_NUMBER))
{
InvalidatePathTracedOutput();
}
// Update the light's transform and position.
LightSceneInfo->Proxy->SetTransform(Parameters.LightToWorld, Parameters.Position);
// Also update the LightSceneInfoCompact (if one exists)
if (bHasId)
{
checkSlow(Lights[LightId].LightSceneInfo == LightSceneInfo);
Lights[LightId].Init(LightSceneInfo);
if (bUpdatePrimitiveInteractions && DoesPlatformNeedLocalLightPrimitiveInteraction(GetShaderPlatform()))
{
using PrimitiveSceneInfoSet = TSet<FPrimitiveSceneInfo*, DefaultKeyFuncs<FPrimitiveSceneInfo*>, SceneRenderingSetAllocator>;
PrimitiveSceneInfoSet PrevPrimitivesInBounds;
TMap<FPrimitiveSceneInfo*, FLightPrimitiveInteraction*, SceneRenderingSetAllocator> PrimitivesToInteractions;
for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionOftenMovingPrimitiveList();
Interaction;
Interaction = Interaction->GetNextPrimitive()
)
{
PrevPrimitivesInBounds.Add(Interaction->GetPrimitiveSceneInfo());
PrimitivesToInteractions.Add(Interaction->GetPrimitiveSceneInfo(), Interaction);
}
for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionStaticPrimitiveList();
Interaction;
Interaction = Interaction->GetNextPrimitive()
)
{
PrevPrimitivesInBounds.Add(Interaction->GetPrimitiveSceneInfo());
PrimitivesToInteractions.Add(Interaction->GetPrimitiveSceneInfo(), Interaction);
}
PrimitiveSceneInfoSet CurrentPrimitivesInBounds;
const FLightSceneInfoCompact& LightSceneInfoCompact = Lights[LightId];
if (LightSceneInfo->OctreeId.IsValidId())
{
// Re-add the light to the octree after transform update.
LocalShadowCastingLightOctree.RemoveElement(LightSceneInfo->OctreeId);
LightSceneInfo->OctreeId = FOctreeElementId2();
LocalShadowCastingLightOctree.AddElement(LightSceneInfoCompact);
}
PrimitiveOctree.FindElementsWithBoundsTest(LightSceneInfo->GetBoundingBox(), [&LightSceneInfoCompact, &CurrentPrimitivesInBounds, this](const FPrimitiveSceneInfoCompact& PrimitiveSceneInfoCompact)
{
CurrentPrimitivesInBounds.Add(PrimitiveSceneInfoCompact.PrimitiveSceneInfo);
});
PrimitiveSceneInfoSet PrimitivesToBeRemoved = PrevPrimitivesInBounds.Difference(CurrentPrimitivesInBounds);
PrimitiveSceneInfoSet PrimitivesToAdd = CurrentPrimitivesInBounds.Difference(PrevPrimitivesInBounds);
for (FPrimitiveSceneInfo* PrimitiveToRemove : PrimitivesToBeRemoved)
{
FLightPrimitiveInteraction::Destroy(PrimitivesToInteractions[PrimitiveToRemove]);
}
for (FPrimitiveSceneInfo* PrimitiveToAdd : PrimitivesToAdd)
{
LightSceneInfo->CreateLightPrimitiveInteraction(LightSceneInfoCompact, PrimitiveToAdd);
}
}
}
}
template <typename UpdatePayloadType>
void FScene::UpdateLightInternal(FLightSceneProxy* LightSceneProxy, UpdatePayloadType&& InUpdatePayload)
{
if (LightSceneProxy)
{
FLightSceneInfo* LightSceneInfo = LightSceneProxy->GetLightSceneInfo();
if (LightSceneInfo->bVisible)
{
ENQUEUE_RENDER_COMMAND(UpdateLightTransform)(
[this, LightSceneInfo, UpdatePayload = MoveTemp(InUpdatePayload)] (FRHICommandListBase&) mutable
{
FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());
SceneLightInfoUpdates->Enqueue(LightSceneInfo, MoveTemp(UpdatePayload));
});
}
}
}
void FScene::UpdateLightTransform(ULightComponent* Light)
{
UpdateLightInternal(Light->SceneProxy, FUpdateLightTransformParameters{.LightToWorld = Light->GetComponentTransform().ToMatrixNoScale(), .Position = Light->GetLightPosition()});
}
void FScene::UpdateLightColorAndBrightness(ULightComponent* Light)
{
UpdateLightInternal(Light->SceneProxy, FUpdateLightColorParameters{
.NewColor = Light->GetColoredLightBrightness(),
.NewIndirectLightingScale = Light->IndirectLightingIntensity,
.NewVolumetricScatteringIntensity = Light->VolumetricScatteringIntensity});
}
void FScene::RemoveLightSceneInfo_RenderThread(FLightSceneInfo* LightSceneInfo)
{
SCOPE_CYCLE_COUNTER(STAT_RemoveSceneLightTime);
check(LightSceneInfo->bVisible);
const bool bDirectionalLight = LightSceneInfo->Proxy->GetLightType() == LightType_Directional;
if (bDirectionalLight)
{
DirectionalLights.Remove(LightSceneInfo);
}
// check SimpleDirectionalLight
if (LightSceneInfo == SimpleDirectionalLight)
{
SimpleDirectionalLight = nullptr;
}
if(GetFeatureLevelShadingPath(FeatureLevel) == EShadingPath::Mobile)
{
const bool bUseCSMForDynamicObjects = LightSceneInfo->Proxy->UseCSMForDynamicObjects();
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// Tracked for disabled shader permutation warnings.
// Condition must match that in AddLightSceneInfo_RenderThread
if (LightSceneInfo->Proxy->GetLightType() == LightType_Directional && !LightSceneInfo->Proxy->HasStaticLighting())
{
if (LightSceneInfo->Proxy->IsMovable())
{
NumMobileMovableDirectionalLights_RenderThread--;
}
if (bUseCSMForDynamicObjects)
{
NumMobileStaticAndCSMLights_RenderThread--;
}
}
#endif
// check MobileDirectionalLights
for (int32 LightChannelIdx = 0; LightChannelIdx < UE_ARRAY_COUNT(MobileDirectionalLights); LightChannelIdx++)
{
if (LightSceneInfo == MobileDirectionalLights[LightChannelIdx])
{
MobileDirectionalLights[LightChannelIdx] = nullptr;
// find another light that could be the new MobileDirectionalLight for this channel
for (const FLightSceneInfoCompact& OtherLight : Lights)
{
if (OtherLight.LightSceneInfo != LightSceneInfo &&
OtherLight.LightType == LightType_Directional &&
!OtherLight.bStaticLighting &&
GetFirstLightingChannelFromMask(OtherLight.LightSceneInfo->Proxy->GetLightingChannelMask()) == LightChannelIdx)
{
MobileDirectionalLights[LightChannelIdx] = OtherLight.LightSceneInfo;
break;
}
}
// if this light is a dynamic shadowcast then we need to update the static draw lists to pick a new lightingpolicy
const bool bUsesDirectionalLightForLighmapPolicySelection = IsStaticLightingAllowed() && !IsMobileDeferredShadingEnabled(GetShaderPlatform());
if (bUsesDirectionalLightForLighmapPolicySelection && (!LightSceneInfo->Proxy->HasStaticShadowing() || bUseCSMForDynamicObjects))
{
bScenesPrimitivesNeedStaticMeshElementUpdate = true;
UE_CLOG(!GIsEditor, LogRenderer, Log, TEXT("Forcing update for all mesh draw commands: Remove directional light"));
}
break;
}
}
}
ProcessAtmosphereLightRemoval_RenderThread(LightSceneInfo);
// Remove the light from the scene.
LightSceneInfo->RemoveFromScene();
// Remove the light from the lights list.
Lights.RemoveAt(LightSceneInfo->Id);
if (!LightSceneInfo->Proxy->HasStaticShadowing()
&& LightSceneInfo->Proxy->CastsDynamicShadow()
&& LightSceneInfo->GetDynamicShadowMapChannel() == -1)
{
OverflowingDynamicShadowedLights.Remove(LightSceneInfo->Proxy->GetOwnerNameOrLabel());
}
InvalidatePathTracedOutput();
if (LightSceneInfo->Proxy->GetLightType() == LightType_Rect)
{
const FRectLightSceneProxy* RectProxy = (const FRectLightSceneProxy*)LightSceneInfo->Proxy;
RectLightAtlas::RemoveTexture(RectProxy->RectAtlasId);
}
if (UTextureLightProfile* IESTexture = LightSceneInfo->Proxy->GetIESTexture())
{
IESAtlas::RemoveTexture(LightSceneInfo->Proxy->IESAtlasId);
}
// Free the light scene info and proxy.
delete LightSceneInfo->Proxy;
delete LightSceneInfo;
}
void FScene::RemoveLight(ULightComponent* Light)
{
if(Light->SceneProxy)
{
FLightSceneInfo* LightSceneInfo = Light->SceneProxy->GetLightSceneInfo();
DEC_DWORD_STAT(STAT_SceneLights);
// Removing one visible light
--NumVisibleLights_GameThread;
// Disassociate the primitive's render info.
Light->SceneProxy = nullptr;
// Send a command to the rendering thread to queue the light for removal from the scene.
ENQUEUE_RENDER_COMMAND(FQueueRemoveLightCommand)(
[this, LightSceneInfo] (FRHICommandListBase&)
{
FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());
if (LightSceneInfo->bVisible)
{
SceneLightInfoUpdates->EnqueueDelete(LightSceneInfo);
// Note: all the deletes are deferred now (except the invisible ones) as this simpifies the update buffer.
return;
}
else
{
// There should never be updates queued for lights that are not visible
check(!SceneLightInfoUpdates->HasCommand(LightSceneInfo));
// The "invisible lights" are removed at once.
InvisibleLights.RemoveAt(LightSceneInfo->Id);
}
// Free the light scene info and proxy.
delete LightSceneInfo->Proxy;
delete LightSceneInfo;
});
}
}
void FScene::AddExponentialHeightFog(uint64 Id, const FExponentialHeightFogDynamicState& State)
{
FExponentialHeightFogSceneInfo HeightFogSceneInfo(Id, State);
ENQUEUE_RENDER_COMMAND(FAddFogCommand)(
[Scene = this, HeightFogSceneInfo] (FRHICommandListBase&)
{
// Create a FExponentialHeightFogSceneInfo for the component in the scene's fog array.
new(Scene->ExponentialFogs) FExponentialHeightFogSceneInfo(HeightFogSceneInfo);
Scene->InvalidatePathTracedOutput();
});
}
void FScene::RemoveExponentialHeightFog(uint64 Id)
{
ENQUEUE_RENDER_COMMAND(FRemoveFogCommand)(
[Scene = this, Id] (FRHICommandListBase&)
{
// Remove the given component's FExponentialHeightFogSceneInfo from the scene's fog array.
for(int32 FogIndex = 0;FogIndex < Scene->ExponentialFogs.Num();FogIndex++)
{
if(Scene->ExponentialFogs[FogIndex].Id == Id)
{
Scene->ExponentialFogs.RemoveAt(FogIndex);
Scene->InvalidatePathTracedOutput();
break;
}
}
});
}
bool FScene::HasAnyExponentialHeightFog() const
{
return this->ExponentialFogs.Num() > 0;
}
void FScene::AddWindSource(UWindDirectionalSourceComponent* WindComponent)
{
// if this wind component is not activated (or Auto Active is set to false), then don't add to WindSources
if(!WindComponent->IsActive())
{
return;
}
ensure(IsInGameThread());
WindComponents_GameThread.Add(WindComponent);
FWindSourceSceneProxy* SceneProxy = WindComponent->CreateSceneProxy();
WindComponent->SceneProxy = SceneProxy;
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FAddWindSourceCommand)(
[Scene, SceneProxy] (FRHICommandListBase&)
{
Scene->WindSources.Add(SceneProxy);
});
}
void FScene::RemoveWindSource(UWindDirectionalSourceComponent* WindComponent)
{
ensure(IsInGameThread());
WindComponents_GameThread.Remove(WindComponent);
FWindSourceSceneProxy* SceneProxy = WindComponent->SceneProxy;
WindComponent->SceneProxy = NULL;
if(SceneProxy)
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FRemoveWindSourceCommand)(
[Scene, SceneProxy] (FRHICommandListBase&)
{
Scene->WindSources.Remove(SceneProxy);
delete SceneProxy;
});
}
}
void FScene::UpdateWindSource(UWindDirectionalSourceComponent* WindComponent)
{
// Recreate the scene proxy without touching WindComponents_GameThread
// so that this function is kept thread safe when iterating in parallel
// over components (unlike AddWindSource and RemoveWindSource)
FWindSourceSceneProxy* const OldSceneProxy = WindComponent->SceneProxy;
if (OldSceneProxy)
{
WindComponent->SceneProxy = nullptr;
ENQUEUE_RENDER_COMMAND(FRemoveWindSourceCommand)(
[Scene = this, OldSceneProxy] (FRHICommandListBase&)
{
Scene->WindSources.Remove(OldSceneProxy);
delete OldSceneProxy;
});
}
if (WindComponent->IsActive())
{
FWindSourceSceneProxy* const NewSceneProxy = WindComponent->CreateSceneProxy();
WindComponent->SceneProxy = NewSceneProxy;
ENQUEUE_RENDER_COMMAND(FAddWindSourceCommand)(
[Scene = this, NewSceneProxy] (FRHICommandListBase&)
{
Scene->WindSources.Add(NewSceneProxy);
});
}
}
const TArray<FWindSourceSceneProxy*>& FScene::GetWindSources_RenderThread() const
{
checkSlow(IsInRenderingThread());
return WindSources;
}
void FScene::GetWindParameters(const FVector& Position, FVector& OutDirection, float& OutSpeed, float& OutMinGustAmt, float& OutMaxGustAmt) const
{
FWindData AccumWindData;
AccumWindData.PrepareForAccumulate();
int32 NumActiveWindSources = 0;
FVector4f AccumulatedDirectionAndSpeed(0,0,0,0);
float TotalWeight = 0.0f;
for (int32 i = 0; i < WindSources.Num(); i++)
{
FVector4f CurrentDirectionAndSpeed;
float Weight;
const FWindSourceSceneProxy* CurrentSource = WindSources[i];
FWindData CurrentSourceData;
if (CurrentSource->GetWindParameters(Position, CurrentSourceData, Weight))
{
AccumWindData.AddWeighted(CurrentSourceData, Weight);
TotalWeight += Weight;
NumActiveWindSources++;
}
}
AccumWindData.NormalizeByTotalWeight(TotalWeight);
if (NumActiveWindSources == 0)
{
AccumWindData.Direction = FVector(1.0f, 0.0f, 0.0f);
}
OutDirection = AccumWindData.Direction;
OutSpeed = AccumWindData.Speed;
OutMinGustAmt = AccumWindData.MinGustAmt;
OutMaxGustAmt = AccumWindData.MaxGustAmt;
}
void FScene::GetWindParameters_GameThread(const FVector& Position, FVector& OutDirection, float& OutSpeed, float& OutMinGustAmt, float& OutMaxGustAmt) const
{
FWindData AccumWindData;
AccumWindData.PrepareForAccumulate();
const int32 NumSources = WindComponents_GameThread.Num();
int32 NumActiveSources = 0;
float TotalWeight = 0.0f;
// read the wind component array, this is safe for the game thread
for(UWindDirectionalSourceComponent* Component : WindComponents_GameThread)
{
float Weight = 0.0f;
FWindData CurrentComponentData;
if(Component && Component->GetWindParameters(Position, CurrentComponentData, Weight))
{
AccumWindData.AddWeighted(CurrentComponentData, Weight);
TotalWeight += Weight;
++NumActiveSources;
}
}
AccumWindData.NormalizeByTotalWeight(TotalWeight);
if(NumActiveSources == 0)
{
AccumWindData.Direction = FVector(1.0f, 0.0f, 0.0f);
}
OutDirection = AccumWindData.Direction;
OutSpeed = AccumWindData.Speed;
OutMinGustAmt = AccumWindData.MinGustAmt;
OutMaxGustAmt = AccumWindData.MaxGustAmt;
}
void FScene::GetDirectionalWindParameters(FVector& OutDirection, float& OutSpeed, float& OutMinGustAmt, float& OutMaxGustAmt) const
{
FWindData AccumWindData;
AccumWindData.PrepareForAccumulate();
int32 NumActiveWindSources = 0;
FVector4f AccumulatedDirectionAndSpeed(0,0,0,0);
float TotalWeight = 0.0f;
for (int32 i = 0; i < WindSources.Num(); i++)
{
FVector4f CurrentDirectionAndSpeed;
float Weight;
const FWindSourceSceneProxy* CurrentSource = WindSources[i];
FWindData CurrentSourceData;
if (CurrentSource->GetDirectionalWindParameters(CurrentSourceData, Weight))
{
AccumWindData.AddWeighted(CurrentSourceData, Weight);
TotalWeight += Weight;
NumActiveWindSources++;
}
}
AccumWindData.NormalizeByTotalWeight(TotalWeight);
if (NumActiveWindSources == 0)
{
AccumWindData.Direction = FVector(1.0f, 0.0f, 0.0f);
}
OutDirection = AccumWindData.Direction;
OutSpeed = AccumWindData.Speed;
OutMinGustAmt = AccumWindData.MinGustAmt;
OutMaxGustAmt = AccumWindData.MaxGustAmt;
}
void FScene::AddSpeedTreeWind(FVertexFactory* VertexFactory, const UStaticMesh* StaticMesh)
{
if (StaticMesh != NULL && StaticMesh->SpeedTreeWind.IsValid() && StaticMesh->GetRenderData())
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FAddSpeedTreeWindCommand)(
[Scene, StaticMesh, VertexFactory] (FRHICommandListBase&)
{
Scene->SpeedTreeVertexFactoryMap.Add(VertexFactory, StaticMesh);
if (Scene->SpeedTreeWindComputationMap.Contains(StaticMesh))
{
(*(Scene->SpeedTreeWindComputationMap.Find(StaticMesh)))->ReferenceCount++;
}
else
{
FSpeedTreeWindComputation* WindComputation = new FSpeedTreeWindComputation;
WindComputation->Wind = *(StaticMesh->SpeedTreeWind.Get( ));
FSpeedTreeUniformParameters UniformParameters;
FPlatformMemory::Memzero(&UniformParameters, sizeof(UniformParameters));
WindComputation->UniformBuffer = TUniformBufferRef<FSpeedTreeUniformParameters>::CreateUniformBufferImmediate(UniformParameters, UniformBuffer_MultiFrame);
Scene->SpeedTreeWindComputationMap.Add(StaticMesh, WindComputation);
}
});
}
}
void FScene::RemoveSpeedTreeWind_RenderThread(class FVertexFactory* VertexFactory, const class UStaticMesh* StaticMesh)
{
FSpeedTreeWindComputation** WindComputationRef = SpeedTreeWindComputationMap.Find(StaticMesh);
if (WindComputationRef != NULL)
{
FSpeedTreeWindComputation* WindComputation = *WindComputationRef;
WindComputation->ReferenceCount--;
if (WindComputation->ReferenceCount < 1)
{
for (auto Iter = SpeedTreeVertexFactoryMap.CreateIterator(); Iter; ++Iter )
{
if (Iter.Value() == StaticMesh)
{
Iter.RemoveCurrent();
}
}
SpeedTreeWindComputationMap.Remove(StaticMesh);
delete WindComputation;
}
}
}
void FScene::UpdateSpeedTreeWind(double CurrentTime)
{
#define SET_SPEEDTREE_TABLE_FLOAT4V(name, offset) \
UniformParameters.name = *(FVector4f*)(WindShaderValues + FSpeedTreeWind::offset); \
UniformParameters.Prev##name = *(FVector4f*)(WindShaderValues + FSpeedTreeWind::offset + FSpeedTreeWind::NUM_SHADER_VALUES);
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FUpdateSpeedTreeWindCommand)(
[Scene, CurrentTime] (FRHICommandListBase& RHICmdList)
{
FVector WindDirection;
float WindSpeed;
float WindMinGustAmt;
float WindMaxGustAmt;
Scene->GetDirectionalWindParameters(WindDirection, WindSpeed, WindMinGustAmt, WindMaxGustAmt);
for (TMap<const UStaticMesh*, FSpeedTreeWindComputation*>::TIterator It(Scene->SpeedTreeWindComputationMap); It; ++It )
{
const UStaticMesh* StaticMesh = It.Key();
FSpeedTreeWindComputation* WindComputation = It.Value();
if( !(StaticMesh->GetRenderData() && StaticMesh->SpeedTreeWind.IsValid()) )
{
It.RemoveCurrent();
continue;
}
if (GIsEditor && StaticMesh->SpeedTreeWind->NeedsReload( ))
{
// reload the wind since it may have changed or been scaled differently during reimport
StaticMesh->SpeedTreeWind->SetNeedsReload(false);
WindComputation->Wind = *(StaticMesh->SpeedTreeWind.Get( ));
}
// advance the wind object
WindComputation->Wind.SetDirection(WindDirection);
WindComputation->Wind.SetStrength(WindSpeed);
WindComputation->Wind.SetGustMin(WindMinGustAmt);
WindComputation->Wind.SetGustMax(WindMaxGustAmt);
WindComputation->Wind.Advance(true, CurrentTime);
// copy data into uniform buffer
const float* WindShaderValues = WindComputation->Wind.GetShaderTable();
FSpeedTreeUniformParameters UniformParameters;
UniformParameters.WindAnimation.Set(CurrentTime, 0.0f, 0.0f, 0.0f);
SET_SPEEDTREE_TABLE_FLOAT4V(WindVector, SH_WIND_DIR_X);
SET_SPEEDTREE_TABLE_FLOAT4V(WindGlobal, SH_GLOBAL_TIME);
SET_SPEEDTREE_TABLE_FLOAT4V(WindBranch, SH_BRANCH_1_TIME);
SET_SPEEDTREE_TABLE_FLOAT4V(WindBranchTwitch, SH_BRANCH_1_TWITCH);
SET_SPEEDTREE_TABLE_FLOAT4V(WindBranchWhip, SH_BRANCH_1_WHIP);
SET_SPEEDTREE_TABLE_FLOAT4V(WindBranchAnchor, SH_WIND_ANCHOR_X);
SET_SPEEDTREE_TABLE_FLOAT4V(WindBranchAdherences, SH_GLOBAL_DIRECTION_ADHERENCE);
SET_SPEEDTREE_TABLE_FLOAT4V(WindTurbulences, SH_BRANCH_1_TURBULENCE);
SET_SPEEDTREE_TABLE_FLOAT4V(WindLeaf1Ripple, SH_LEAF_1_RIPPLE_TIME);
SET_SPEEDTREE_TABLE_FLOAT4V(WindLeaf1Tumble, SH_LEAF_1_TUMBLE_TIME);
SET_SPEEDTREE_TABLE_FLOAT4V(WindLeaf1Twitch, SH_LEAF_1_TWITCH_THROW);
SET_SPEEDTREE_TABLE_FLOAT4V(WindLeaf2Ripple, SH_LEAF_2_RIPPLE_TIME);
SET_SPEEDTREE_TABLE_FLOAT4V(WindLeaf2Tumble, SH_LEAF_2_TUMBLE_TIME);
SET_SPEEDTREE_TABLE_FLOAT4V(WindLeaf2Twitch, SH_LEAF_2_TWITCH_THROW);
SET_SPEEDTREE_TABLE_FLOAT4V(WindFrondRipple, SH_FROND_RIPPLE_TIME);
SET_SPEEDTREE_TABLE_FLOAT4V(WindRollingBranch, SH_ROLLING_BRANCH_FIELD_MIN);
SET_SPEEDTREE_TABLE_FLOAT4V(WindRollingLeafAndDirection, SH_ROLLING_LEAF_RIPPLE_MIN);
SET_SPEEDTREE_TABLE_FLOAT4V(WindRollingNoise, SH_ROLLING_NOISE_PERIOD);
WindComputation->UniformBuffer.UpdateUniformBufferImmediate(RHICmdList, UniformParameters);
}
});
#undef SET_SPEEDTREE_TABLE_FLOAT4V
}
FRHIUniformBuffer* FScene::GetSpeedTreeUniformBuffer(const FVertexFactory* VertexFactory) const
{
if (VertexFactory != NULL)
{
const UStaticMesh* const* StaticMesh = SpeedTreeVertexFactoryMap.Find(VertexFactory);
if (StaticMesh != NULL)
{
const FSpeedTreeWindComputation* const * WindComputation = SpeedTreeWindComputationMap.Find(*StaticMesh);
if (WindComputation != NULL)
{
return (*WindComputation)->UniformBuffer;
}
}
}
return nullptr;
}
/**
* Retrieves the lights interacting with the passed in primitive and adds them to the out array.
*
* Render thread version of function.
*
* @param Primitive Primitive to retrieve interacting lights for
* @param RelevantLights [out] Array of lights interacting with primitive
*/
void FScene::GetRelevantLights_RenderThread( const FPrimitiveSceneProxy* PrimitiveSceneProxy, TArray<const FLightSceneProxy*> &OutRelevantLights ) const
{
if (ShouldSkipNaniteLPIs(GetShaderPlatform()) && PrimitiveSceneProxy->IsNaniteMesh())
{
if (PrimitiveSceneProxy->GetLightingChannelMask() != 0)
{
const FBoxSphereBounds& Bounds = PrimitiveSceneProxy->GetBounds();
const FPrimitiveSceneInfoCompact PrimitiveSceneInfoCompact(PrimitiveSceneProxy->GetPrimitiveSceneInfo());
auto TestAddLight = [&](const FLightSceneInfoCompact& LightSceneInfoCompact)
{
if (LightSceneInfoCompact.LightSceneInfo->ShouldCreateLightPrimitiveInteraction(LightSceneInfoCompact, PrimitiveSceneInfoCompact))
{
OutRelevantLights.Add( LightSceneInfoCompact.LightSceneInfo->Proxy);
}
};
if(DoesPlatformNeedLocalLightPrimitiveInteraction(GetShaderPlatform()))
{
// Find local lights that affect the primitive in the light octree.
LocalShadowCastingLightOctree.FindElementsWithBoundsTest(Bounds.GetBox(), TestAddLight);
}
// Also loop through non-local (directional) shadow-casting lights
for (int32 LightID : DirectionalShadowCastingLightIDs)
{
TestAddLight(Lights[LightID]);
}
}
}
else
{
for( const FLightPrimitiveInteraction* Interaction = PrimitiveSceneProxy->GetPrimitiveSceneInfo()->LightList; Interaction; Interaction=Interaction->GetNextLight() )
{
OutRelevantLights.Add( Interaction->GetLight()->Proxy);
}
}
}
/**
* Retrieves the lights interacting with the passed in primitive and adds them to the out array.
*
* @param Primitive Primitive to retrieve interacting lights for
* @param RelevantLights [out] Array of lights interacting with primitive
*/
void FScene::GetRelevantLights( UPrimitiveComponent* Primitive, TArray<const ULightComponent*>* RelevantLights ) const
{
if( Primitive && RelevantLights && Primitive->GetSceneProxy() != nullptr)
{
// Add interacting lights to the array.
ENQUEUE_RENDER_COMMAND(FGetRelevantLightsCommand)(
[this, PrimitiveSceneProxy = Primitive->GetSceneProxy(), RelevantLights] (FRHICommandListBase&)
{
TArray<const FLightSceneProxy*> RelevantLightProxies;
GetRelevantLights_RenderThread( PrimitiveSceneProxy, RelevantLightProxies );
for (const FLightSceneProxy* LightSceneProxy : RelevantLightProxies)
{
RelevantLights->Add(LightSceneProxy->GetLightComponent());
}
});
// We need to block the main thread as the rendering thread needs to finish modifying the array before we can continue.
FlushRenderingCommands();
}
}
/** Sets the precomputed visibility handler for the scene, or NULL to clear the current one. */
void FScene::SetPrecomputedVisibility(const FPrecomputedVisibilityHandler* NewPrecomputedVisibilityHandler)
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(UpdatePrecomputedVisibility)(
[Scene, NewPrecomputedVisibilityHandler] (FRHICommandListBase&)
{
Scene->PrecomputedVisibilityHandler = NewPrecomputedVisibilityHandler;
});
}
void FScene::UpdateStaticDrawLists_RenderThread(FRHICommandListImmediate& RHICmdList)
{
SCOPE_CYCLE_COUNTER(STAT_Scene_UpdateStaticDrawLists_RT);
const int32 NumPrimitives = Primitives.Num();
for (int32 PrimitiveIndex = 0; PrimitiveIndex < NumPrimitives; ++PrimitiveIndex)
{
FPrimitiveSceneInfo* Primitive = Primitives[PrimitiveIndex];
Primitive->RemoveStaticMeshes();
}
FPrimitiveSceneInfo::AddStaticMeshes(RHICmdList, this, Primitives);
}
void FScene::UpdateStaticDrawLists()
{
UE::RenderCommandPipe::FSyncScope SyncScope;
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FUpdateDrawLists)(
[Scene](FRHICommandListImmediate& RHICmdList)
{
Scene->UpdateStaticDrawLists_RenderThread(RHICmdList);
});
}
void FScene::UpdateCachedRenderStates(FPrimitiveSceneProxy* SceneProxy)
{
if (SceneProxy->GetPrimitiveSceneInfo())
{
SceneProxy->GetPrimitiveSceneInfo()->RequestStaticMeshUpdate();
}
}
#if RHI_RAYTRACING
void FScene::UpdateCachedRayTracingState(FPrimitiveSceneProxy* SceneProxy)
{
if (SceneProxy->GetPrimitiveSceneInfo())
{
const int32 PrimitiveIndex = SceneProxy->GetPrimitiveSceneInfo()->GetIndex();
if (ensure(PrimitiveIndex != INDEX_NONE))
{
PrimitiveRayTracingDatas[PrimitiveIndex].bCachedRaytracingDataDirty = true;
}
// Clear the recounted pointer as well since we don't need it anymore
SceneProxy->GetPrimitiveSceneInfo()->CachedRayTracingInstance.GeometryRHI = nullptr;
}
}
#endif // RHI_RAYTRACING
/**
* @return true if hit proxies should be rendered in this scene.
*/
bool FScene::RequiresHitProxies() const
{
return (GIsEditor && bRequiresHitProxies);
}
void FScene::Release()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FScene::Release);
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// Verify that no components reference this scene being destroyed
static bool bTriggeredOnce = false;
if (!bTriggeredOnce)
{
for (auto* ActorComponent : TObjectRange<UActorComponent>())
{
if ( !ensureMsgf(!ActorComponent->IsRegistered() || ActorComponent->GetScene() != this,
TEXT("Component Name: %s World Name: %s Component Asset: %s"),
*ActorComponent->GetFullName(),
*GetWorld()->GetFullName(),
*ActorComponent->AdditionalStatObject()->GetPathName()) )
{
bTriggeredOnce = true;
break;
}
}
}
#endif
GetRendererModule().RemoveScene(this);
UE::RenderCommandPipe::FSyncScope SyncScope;
#if WITH_STATE_STREAM
FStateStreamManagerImpl* StateStreamManager = static_cast<FStateStreamManagerImpl*>(World->GetStateStreamManager());
#endif
if (SkeletalMeshUpdater)
{
SkeletalMeshUpdater->Shutdown();
}
// Send a command to the rendering thread to release the scene.
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FReleaseCommand)(
[Scene
#if WITH_STATE_STREAM
, StateStreamManager
#endif
](FRHICommandListImmediate& RHICmdList)
{
#if WITH_STATE_STREAM
StateStreamManager->Render_Exit();
GetRendererModule().FreeStateStream(StateStreamManager);
#endif
// Flush any remaining batched primitive update commands before deleting the scene.
FUpdateParameters UpdateParameters;
UpdateParameters.bDestruction = true;
// Scope required so that the GraphBuilder is destructed before this Scene
{
FRDGBuilder GraphBuilder(RHICmdList, FRDGEventName(TEXT("UpdateAllPrimitiveSceneInfos")));
Scene->Update(GraphBuilder, UpdateParameters);
GraphBuilder.Execute();
}
// Wait for RDG to complete async deletion as scene extensions can be allocated through RDG.
FRDGBuilder::WaitForAsyncDeleteTask();
delete Scene;
});
}
void FScene::UpdateEarlyZPassMode()
{
checkSlow(IsInGameThread());
DefaultBasePassDepthStencilAccess = GetDefaultBasePassDepthStencilAccess(GetFeatureLevel());
GetEarlyZPassMode(GetFeatureLevel(), EarlyZPassMode, bEarlyZPassMovable);
}
FExclusiveDepthStencil::Type FScene::GetDefaultBasePassDepthStencilAccess(ERHIFeatureLevel::Type InFeatureLevel)
{
FExclusiveDepthStencil::Type BasePassDepthStencilAccess = FExclusiveDepthStencil::DepthWrite_StencilWrite;
if (GetFeatureLevelShadingPath(InFeatureLevel) == EShadingPath::Deferred)
{
const EShaderPlatform ShaderPlatform = GetFeatureLevelShaderPlatform(InFeatureLevel);
if (ShouldForceFullDepthPass(ShaderPlatform)
&& CVarBasePassWriteDepthEvenWithFullPrepass.GetValueOnAnyThread() == 0)
{
BasePassDepthStencilAccess = FExclusiveDepthStencil::DepthRead_StencilWrite;
}
}
return BasePassDepthStencilAccess;
}
void FScene::GetEarlyZPassMode(ERHIFeatureLevel::Type InFeatureLevel, EDepthDrawingMode & OutZPassMode, bool& bOutEarlyZPassMovable)
{
OutZPassMode = DDM_NonMaskedOnly;
bOutEarlyZPassMovable = false;
const EShaderPlatform ShaderPlatform = GetFeatureLevelShaderPlatform(InFeatureLevel);
if (GetFeatureLevelShadingPath(InFeatureLevel) == EShadingPath::Deferred)
{
// developer override, good for profiling, can be useful as project setting
{
const int32 CVarValue = CVarEarlyZPass.GetValueOnAnyThread();
switch (CVarValue)
{
case 0: OutZPassMode = DDM_None; break;
case 1: OutZPassMode = DDM_NonMaskedOnly; break;
case 2: OutZPassMode = DDM_AllOccluders; break;
case 3: break; // Note: 3 indicates "default behavior" and does not specify an override
}
}
if (ShouldForceFullDepthPass(ShaderPlatform))
{
// DBuffer decals and stencil LOD dithering force a full prepass
const bool bDepthPassCanOutputVelocity = FVelocityRendering::DepthPassCanOutputVelocity(InFeatureLevel);
OutZPassMode = bDepthPassCanOutputVelocity ? DDM_AllOpaqueNoVelocity : DDM_AllOpaque;
bOutEarlyZPassMovable = bDepthPassCanOutputVelocity ? false : true;
}
}
else if (GetFeatureLevelShadingPath(InFeatureLevel) == EShadingPath::Mobile)
{
OutZPassMode = DDM_None;
const bool bMaskedOnlyPrePass = FReadOnlyCVARCache::MobileEarlyZPass(ShaderPlatform) == 2;
if (bMaskedOnlyPrePass)
{
OutZPassMode = DDM_MaskedOnly;
}
if (MobileUsesFullDepthPrepass(ShaderPlatform))
{
const bool bDepthPassCanOutputVelocity = PlatformSupportsVelocityRendering(ShaderPlatform) && FVelocityRendering::DepthPassCanOutputVelocity(InFeatureLevel);
OutZPassMode = bDepthPassCanOutputVelocity ? DDM_AllOpaqueNoVelocity : DDM_AllOpaque;
}
}
}
void FScene::DumpUnbuiltLightInteractions( FOutputDevice& Ar ) const
{
FlushRenderingCommands();
TSet<FString> LightsWithUnbuiltInteractions;
TSet<FString> PrimitivesWithUnbuiltInteractions;
// if want to print out all of the lights
for( auto It = Lights.CreateConstIterator(); It; ++It )
{
const FLightSceneInfoCompact& LightCompactInfo = *It;
FLightSceneInfo* LightSceneInfo = LightCompactInfo.LightSceneInfo;
bool bLightHasUnbuiltInteractions = false;
for(FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionOftenMovingPrimitiveList();
Interaction;
Interaction = Interaction->GetNextPrimitive())
{
if (Interaction->IsUncachedStaticLighting())
{
bLightHasUnbuiltInteractions = true;
PrimitivesWithUnbuiltInteractions.Add(Interaction->GetPrimitiveSceneInfo()->GetComponentForDebugOnly()->GetFullName());
}
}
for(FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionStaticPrimitiveList();
Interaction;
Interaction = Interaction->GetNextPrimitive())
{
if (Interaction->IsUncachedStaticLighting())
{
bLightHasUnbuiltInteractions = true;
PrimitivesWithUnbuiltInteractions.Add(Interaction->GetPrimitiveSceneInfo()->GetComponentForDebugOnly()->GetFullName());
}
}
if (bLightHasUnbuiltInteractions)
{
LightsWithUnbuiltInteractions.Add(LightSceneInfo->Proxy->GetOwnerNameOrLabel());
}
}
Ar.Logf( TEXT( "DumpUnbuiltLightIteractions" ) );
Ar.Logf( TEXT( "Lights with unbuilt interactions: %d" ), LightsWithUnbuiltInteractions.Num() );
for (auto &LightName : LightsWithUnbuiltInteractions)
{
Ar.Logf(TEXT(" Light %s"), *LightName);
}
Ar.Logf( TEXT( "" ) );
Ar.Logf( TEXT( "Primitives with unbuilt interactions: %d" ), PrimitivesWithUnbuiltInteractions.Num() );
for (auto &PrimitiveName : PrimitivesWithUnbuiltInteractions)
{
Ar.Logf(TEXT(" Primitive %s"), *PrimitiveName);
}
}
/**
* Exports the scene.
*
* @param Ar The Archive used for exporting.
**/
void FScene::Export( FArchive& Ar ) const
{
}
void FScene::ApplyWorldOffset(const FVector& InOffset)
{
UE::RenderCommandPipe::FSyncScope SyncScope;
// Send a command to the rendering thread to shift scene data
FScene* Scene = this;
FVector Offset = InOffset;
ENQUEUE_RENDER_COMMAND(FApplyWorldOffset)(
[Scene, Offset](FRHICommandListImmediate& RHICmdList)
{
Scene->UpdateAllPrimitiveSceneInfos(RHICmdList);
Scene->ApplyWorldOffset_RenderThread(RHICmdList, Offset);
});
}
void FScene::ApplyWorldOffset_RenderThread(FRHICommandListBase& RHICmdList, const FVector& InOffset)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_SceneApplyWorldOffset);
GPUScene.bUpdateAllPrimitives = true;
// Primitives
for (int32 Idx = 0; Idx < Primitives.Num(); ++Idx)
{
Primitives[Idx]->ApplyWorldOffset(RHICmdList, InOffset);
}
// Primitive transforms
for (int32 Idx = 0; Idx < PrimitiveTransforms.Num(); ++Idx)
{
PrimitiveTransforms[Idx].SetOrigin(PrimitiveTransforms[Idx].GetOrigin() + InOffset);
}
// Primitive bounds
for (int32 Idx = 0; Idx < PrimitiveBounds.Num(); ++Idx)
{
PrimitiveBounds[Idx].BoxSphereBounds.Origin+= InOffset;
}
#if RHI_RAYTRACING
for (auto& BoundsPair : PrimitiveRayTracingGroups)
{
BoundsPair.Value.Bounds.Origin += InOffset;
}
#endif
// Primitive occlusion bounds
for (int32 Idx = 0; Idx < PrimitiveOcclusionBounds.Num(); ++Idx)
{
PrimitiveOcclusionBounds[Idx].Origin+= InOffset;
}
// Precomputed light volumes
for (const FPrecomputedLightVolume* It : PrecomputedLightVolumes)
{
const_cast<FPrecomputedLightVolume*>(It)->ApplyWorldOffset(InOffset);
}
// Precomputed visibility
if (PrecomputedVisibilityHandler)
{
const_cast<FPrecomputedVisibilityHandler*>(PrecomputedVisibilityHandler)->ApplyWorldOffset(InOffset);
}
// Invalidate indirect lighting cache
IndirectLightingCache.SetLightingCacheDirty(this, NULL);
// Primitives octree
PrimitiveOctree.ApplyOffset(InOffset, /*bGlobalOctee*/ true);
// Lights
VectorRegister OffsetReg = VectorLoadFloat3_W0(&InOffset);
for (auto It = Lights.CreateIterator(); It; ++It)
{
(*It).BoundingSphereVector = VectorAdd((*It).BoundingSphereVector, OffsetReg);
(*It).LightSceneInfo->Proxy->ApplyWorldOffset(InOffset);
}
LocalShadowCastingLightOctree.ApplyOffset(InOffset, /*bGlobalOctee*/ true);
// Cached preshadows
for (auto It = CachedPreshadows.CreateIterator(); It; ++It)
{
(*It)->PreShadowTranslation-= InOffset;
(*It)->ShadowBounds.Center+= InOffset;
}
// Decals
for (auto It = Decals.CreateIterator(); It; ++It)
{
(*It)->ComponentTrans.AddToTranslation(InOffset);
}
// Wind sources
for (auto It = WindSources.CreateIterator(); It; ++It)
{
(*It)->ApplyWorldOffset(InOffset);
}
// Reflection captures
for (auto It = ReflectionSceneData.RegisteredReflectionCaptures.CreateIterator(); It; ++It)
{
FMatrix NewTransform = FMatrix((*It)->BoxTransform.Inverse().ConcatTranslation((FVector3f)InOffset));
(*It)->SetTransform(NewTransform);
}
// Planar reflections
for (auto It = PlanarReflections.CreateIterator(); It; ++It)
{
(*It)->ApplyWorldOffset(InOffset);
}
// Exponential Fog
for (FExponentialHeightFogSceneInfo& FogInfo : ExponentialFogs)
{
for (FExponentialHeightFogSceneInfo::FExponentialHeightFogSceneData& FogData : FogInfo.FogData)
{
FogData.Height += InOffset.Z;
}
}
// SkyAtmospheres
for (FSkyAtmosphereSceneProxy* SkyAtmosphereProxy : SkyAtmosphereStack)
{
SkyAtmosphereProxy->ApplyWorldOffset((FVector3f)InOffset);
}
VelocityData.ApplyOffset(InOffset);
}
void FScene::OnLevelAddedToWorld(const FName& InLevelAddedName, UWorld* InWorld, bool bIsLightingScenario)
{
if (bIsLightingScenario)
{
InWorld->PropagateLightingScenarioChange();
}
FScene* Scene = this;
FName LevelAddedName = InLevelAddedName;
ENQUEUE_RENDER_COMMAND(FLevelAddedToWorld)(
[Scene, LevelAddedName] (FRHICommandListBase&)
{
FLevelCommand Cmd;
Cmd.Name = LevelAddedName;
Cmd.Op = FLevelCommand::EOp::Add;
Scene->LevelCommands.Add(Cmd);
});
}
void FScene::OnLevelRemovedFromWorld(const FName& InLevelRemovedName, UWorld* InWorld, bool bIsLightingScenario)
{
if (bIsLightingScenario)
{
InWorld->PropagateLightingScenarioChange();
}
FScene* Scene = this;
FName LevelRemovedName = InLevelRemovedName;
ENQUEUE_RENDER_COMMAND(FLevelRemovedFromWorld)(
[Scene, LevelRemovedName] (FRHICommandListBase&)
{
FLevelCommand Cmd;
Cmd.Name = LevelRemovedName;
Cmd.Op = FLevelCommand::EOp::Remove;
Scene->LevelCommands.Add(Cmd);
});
}
void FScene::ProcessAtmosphereLightAddition_RenderThread(FLightSceneInfo* LightSceneInfo)
{
if (LightSceneInfo->Proxy->IsUsedAsAtmosphereSunLight())
{
const uint8 Index = LightSceneInfo->Proxy->GetAtmosphereSunLightIndex();
if (!AtmosphereLights[Index] || // Set it if null
LightSceneInfo->Proxy->GetColor().GetLuminance() > AtmosphereLights[Index]->Proxy->GetColor().GetLuminance()) // Or choose the brightest sun light
{
AtmosphereLights[Index] = LightSceneInfo;
}
}
}
void FScene::ProcessAtmosphereLightRemoval_RenderThread(FLightSceneInfo* LightSceneInfo)
{
// When a light has its intensity or index changed, it will be removed first, then re-added. So we only need to check the index of the removed light.
const uint8 Index = LightSceneInfo->Proxy->GetAtmosphereSunLightIndex();
if (AtmosphereLights[Index] == LightSceneInfo)
{
AtmosphereLights[Index] = nullptr;
float SelectedLightLuminance = 0.0f;
for (auto It = Lights.CreateConstIterator(); It; ++It)
{
const FLightSceneInfoCompact& LightInfo = *It;
float LightLuminance = LightInfo.LightSceneInfo->Proxy->GetColor().GetLuminance();
if (LightInfo.LightSceneInfo != LightSceneInfo
&& LightInfo.LightSceneInfo->Proxy->IsUsedAsAtmosphereSunLight() && LightInfo.LightSceneInfo->Proxy->GetAtmosphereSunLightIndex() == Index
&& (!AtmosphereLights[Index] || SelectedLightLuminance < LightLuminance))
{
AtmosphereLights[Index] = LightInfo.LightSceneInfo;
SelectedLightLuminance = LightLuminance;
}
}
}
}
#if WITH_EDITOR
bool FScene::InitializePixelInspector(FRenderTarget* BufferFinalColor, FRenderTarget* BufferSceneColor, FRenderTarget* BufferDepth, FRenderTarget* BufferHDR, FRenderTarget* BufferA, FRenderTarget* BufferBCDEF, int32 BufferIndex)
{
//Initialize the buffers
PixelInspectorData.InitializeBuffers(BufferFinalColor, BufferSceneColor, BufferDepth, BufferHDR, BufferA, BufferBCDEF, BufferIndex);
//return true when the interface is implemented
return true;
}
bool FScene::AddPixelInspectorRequest(FPixelInspectorRequest *PixelInspectorRequest)
{
return PixelInspectorData.AddPixelInspectorRequest(PixelInspectorRequest);
}
#endif //WITH_EDITOR
struct FPrimitiveArraySortKey
{
inline bool operator()(const FPrimitiveSceneInfo& A, const FPrimitiveSceneInfo& B) const
{
const uint32 A_TypeHash = A.Proxy->GetTypeHash();
const uint32 B_TypeHash = B.Proxy->GetTypeHash();
const uint32 A_AlwaysVisible = A.Proxy->IsAlwaysVisible() ? 1u : 0u;
const uint32 B_AlwaysVisible = B.Proxy->IsAlwaysVisible() ? 1u : 0u;
// First group all proxies by test visibility vs. always visible (at the end)
if (A_AlwaysVisible != B_AlwaysVisible)
{
return A_AlwaysVisible > B_AlwaysVisible;
}
// Then group up all proxies in the two ranges by type for better cache coherency
if (A_TypeHash != B_TypeHash)
{
return A_TypeHash > B_TypeHash;
}
// Finally, sort by primitive component ID to add more determinism/stability to the sort
return A.PrimitiveComponentId.PrimIDValue > B.PrimitiveComponentId.PrimIDValue;
}
};
static bool ShouldPrimitiveOutputVelocity(const FPrimitiveSceneProxy* Proxy, const FStaticShaderPlatform ShaderPlatform)
{
bool bShouldPrimitiveOutputVelocity = Proxy->HasDynamicTransform();
bool bPlatformSupportsVelocityRendering = PlatformSupportsVelocityRendering(ShaderPlatform);
return bPlatformSupportsVelocityRendering && bShouldPrimitiveOutputVelocity;
}
void FScene::UpdatePrimitiveVelocityState_RenderThread(FPrimitiveSceneInfo* PrimitiveSceneInfo, bool bIsBeingMoved)
{
if (bIsBeingMoved)
{
if (ShouldPrimitiveOutputVelocity(PrimitiveSceneInfo->Proxy, GetShaderPlatform()))
{
if (PrimitiveSceneInfo->IsIndexValid())
{
PrimitiveSceneInfo->bRegisteredWithVelocityData = true;
// We must register the initial LocalToWorld with the velocity state.
int32 PrimitiveIndex = PrimitiveSceneInfo->PackedIndex;
VelocityData.UpdateTransform(PrimitiveSceneInfo, PrimitiveTransforms[PrimitiveIndex], PrimitiveTransforms[PrimitiveIndex]);
}
}
}
else if (PrimitiveSceneInfo->bRegisteredWithVelocityData)
{
PrimitiveSceneInfo->bRegisteredWithVelocityData = false;
VelocityData.RemoveFromScene(PrimitiveSceneInfo->PrimitiveComponentId, true);
}
}
#if RHI_RAYTRACING
void FScene::UpdateRayTracingGroupBounds_AddPrimitives(const TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator>& PrimitiveSceneInfos)
{
for (FPrimitiveSceneInfo* const PrimitiveSceneInfo : PrimitiveSceneInfos)
{
const int32 GroupId = PrimitiveSceneInfo->Proxy->GetRayTracingGroupId();
if (GroupId != -1)
{
bool bInMap = false;
static const FRayTracingCullingGroup DefaultGroup = {};
FRayTracingCullingGroup* const Group = PrimitiveRayTracingGroups.FindOrAdd(GroupId, DefaultGroup, bInMap);
if (bInMap)
{
Group->Bounds = Group->Bounds + PrimitiveSceneInfo->Proxy->GetBounds();
Group->MinDrawDistance = FMath::Max(Group->MinDrawDistance, PrimitiveSceneInfo->Proxy->GetMinDrawDistance());
}
else
{
Group->Bounds = PrimitiveSceneInfo->Proxy->GetBounds();
Group->MinDrawDistance = PrimitiveSceneInfo->Proxy->GetMinDrawDistance();
}
Group->Primitives.Add(PrimitiveSceneInfo);
}
}
}
static void UpdateRayTracingGroupBounds(Experimental::TRobinHoodHashSet<FScene::FRayTracingCullingGroup*>& GroupsToUpdate)
{
for (FScene::FRayTracingCullingGroup* const Group : GroupsToUpdate)
{
bool bFirstBounds = false;
for (FPrimitiveSceneInfo* const Primitive : Group->Primitives)
{
if (!bFirstBounds)
{
Group->Bounds = Primitive->Proxy->GetBounds();
bFirstBounds = true;
}
else
{
Group->Bounds = Group->Bounds + Primitive->Proxy->GetBounds();
}
}
}
}
void FScene::UpdateRayTracingGroupBounds_RemovePrimitives(const TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator>& PrimitiveSceneInfos)
{
Experimental::TRobinHoodHashSet<FRayTracingCullingGroup*> GroupsToUpdate;
for (FPrimitiveSceneInfo* const PrimitiveSceneInfo : PrimitiveSceneInfos)
{
const int32 RayTracingGroupId = PrimitiveSceneInfo->Proxy->GetRayTracingGroupId();
const Experimental::FHashElementId GroupId = (RayTracingGroupId != -1) ? PrimitiveRayTracingGroups.FindId(RayTracingGroupId) : Experimental::FHashElementId();
if (GroupId.IsValid())
{
FRayTracingCullingGroup& Group = PrimitiveRayTracingGroups.GetByElementId(GroupId).Value;
Group.Primitives.RemoveSingleSwap(PrimitiveSceneInfo);
if (Group.Primitives.Num() == 0)
{
PrimitiveRayTracingGroups.RemoveByElementId(GroupId);
}
else
{
GroupsToUpdate.FindOrAdd(&Group);
}
}
}
UpdateRayTracingGroupBounds(GroupsToUpdate);
}
template<typename RangeType>
inline void FScene::UpdateRayTracingGroupBounds_UpdatePrimitives(const RangeType& InUpdatedTransforms)
{
Experimental::TRobinHoodHashSet<FRayTracingCullingGroup*> GroupsToUpdate;
for (const auto& Transform : InUpdatedTransforms)
{
FPrimitiveSceneProxy* const PrimitiveSceneProxy = Transform.SceneInfo->Proxy;
const int32 RayTracingGroupId = PrimitiveSceneProxy->GetRayTracingGroupId();
const Experimental::FHashElementId GroupId = (RayTracingGroupId != -1) ? PrimitiveRayTracingGroups.FindId(RayTracingGroupId) : Experimental::FHashElementId();
if (GroupId.IsValid())
{
FRayTracingCullingGroup& Group = PrimitiveRayTracingGroups.GetByElementId(GroupId).Value;
GroupsToUpdate.FindOrAdd(&Group);
}
}
UpdateRayTracingGroupBounds(GroupsToUpdate);
}
#endif
void FScene::UpdateLights(FRDGBuilder& GraphBuilder, FSceneExtensionsUpdaters& SceneExtensionsUpdaters)
{
SCOPED_NAMED_EVENT(FScene_UpdateAllLightSceneInfos, FColor::Orange);
struct FFLightSceneChangeSetAllocation
{
TArray<int32, SceneRenderingAllocator> RemovedLightIds;
TArray<int32, SceneRenderingAllocator> AddedLightIds;
FFLightSceneChangeSetAllocation(FSceneLightInfoUpdates&& InSceneLightInfoUpdates, int32 InPreUpdateMaxIndex)
: SceneLightInfoUpdates(MoveTemp(InSceneLightInfoUpdates))
, RemovedLightsMask(false, InPreUpdateMaxIndex)
, PreUpdateChangeSet{TConstArrayView<int32>(RemovedLightIds), TConstArrayView<int32>(), SceneLightInfoUpdates, RemovedLightsMask, InPreUpdateMaxIndex, -1 }
, PostUpdateChangeSet{TConstArrayView<int32>(RemovedLightIds), TConstArrayView<int32>(AddedLightIds), SceneLightInfoUpdates, RemovedLightsMask, InPreUpdateMaxIndex, -1 }
{}
FSceneLightInfoUpdates SceneLightInfoUpdates;
TBitArray<SceneRenderingAllocator> RemovedLightsMask;
// These are the accessors to the data that are read only and can be passed around by pointer or reference as they are kept alive in the alloc.
FLightSceneChangeSet PreUpdateChangeSet;
FLightSceneChangeSet PostUpdateChangeSet;
};
// Allocate change set storage with graph builder lifetime such that we can safely pass it to async tasks.
FFLightSceneChangeSetAllocation& ChangeSetAlloc = *GraphBuilder.AllocObject<FFLightSceneChangeSetAllocation>(MoveTemp(*SceneLightInfoUpdates), Lights.GetMaxIndex());
#if DO_CHECK
FSceneLightInfoUpdates::FReadAccessScope ReadAccessScope(ChangeSetAlloc.SceneLightInfoUpdates);
#endif
// Filter out removes & never-adds
ChangeSetAlloc.SceneLightInfoUpdates.ForEachCommand([&](const FUpdateLightCommand& UpdateLightCommand)
{
int32 Id = UpdateLightCommand.GetSceneInfo()->Id;
if (UpdateLightCommand.IsDelete())
{
if (Id != INDEX_NONE)
{
ChangeSetAlloc.RemovedLightIds.Add(Id);
ChangeSetAlloc.RemovedLightsMask[Id] = true;
}
else
{
delete UpdateLightCommand.GetSceneInfo()->Proxy;
delete UpdateLightCommand.GetSceneInfo();
}
}
});
ChangeSetAlloc.PreUpdateChangeSet.RemovedLightIds = ChangeSetAlloc.RemovedLightIds;
ChangeSetAlloc.PostUpdateChangeSet.RemovedLightIds = ChangeSetAlloc.RemovedLightIds;
SceneExtensionsUpdaters.PreLightsUpdate(GraphBuilder, ChangeSetAlloc.PreUpdateChangeSet);
// This can't access the scene light data if done async since it happens before the actual removals.
OnPreLightSceneInfoUpdate.Broadcast(GraphBuilder, ChangeSetAlloc.PreUpdateChangeSet);
// Batch process all light removes
for (int32 LightId : ChangeSetAlloc.RemovedLightIds)
{
FLightSceneInfo* LightSceneInfo = Lights[LightId].LightSceneInfo;
FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());
RemoveLightSceneInfo_RenderThread(LightSceneInfo);
}
// Process all light adds & updates
ChangeSetAlloc.SceneLightInfoUpdates.ForEachCommand(ESceneUpdateCommandFilter::AddedUpdated, [&](const FUpdateLightCommand& UpdateLightCommand)
{
FLightSceneInfo* LightSceneInfo = UpdateLightCommand.GetSceneInfo();
FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());
const int32 Id = LightSceneInfo->Id;
const bool bHasId = Id != INDEX_NONE;
check(bHasId == !UpdateLightCommand.IsAdd());
// Directly process updates.
if (FUpdateLightTransformParameters* TransformParameters = ChangeSetAlloc.SceneLightInfoUpdates.GetPayloadPtr<FUpdateLightTransformParameters>(UpdateLightCommand))
{
SCOPE_CYCLE_COUNTER(STAT_UpdateSceneLightTime);
UpdateLightTransform_RenderThread(Id, LightSceneInfo, *TransformParameters);
}
if (FUpdateLightColorParameters* ColorParameters = ChangeSetAlloc.SceneLightInfoUpdates.GetPayloadPtr<FUpdateLightColorParameters>(UpdateLightCommand))
{
const FUpdateLightColorParameters& NewParameters = *ColorParameters;
SCOPE_CYCLE_COUNTER(STAT_UpdateSceneLightTime);
// Mobile renderer:
// a light with no color/intensity can cause the light to be ignored when rendering.
// thus, lights that change state in this way must update the draw lists.
if (GetFeatureLevelShadingPath(FeatureLevel) == EShadingPath::Mobile
&& LightSceneInfo->Proxy->GetLightType() == LightType_Directional
&& NewParameters.NewColor.IsAlmostBlack() != LightSceneInfo->Proxy->GetColor().IsAlmostBlack())
{
bScenesPrimitivesNeedStaticMeshElementUpdate = true;
UE_CLOG(!GIsEditor, LogRenderer, Log, TEXT("Forcing update for all mesh draw commands: Toggle directional light"));
}
// Path Tracing: something about the light has changed, restart path traced accumulation
InvalidatePathTracedOutput();
LightSceneInfo->Proxy->SetColor(NewParameters.NewColor);
LightSceneInfo->Proxy->IndirectLightingScale = NewParameters.NewIndirectLightingScale;
LightSceneInfo->Proxy->VolumetricScatteringIntensity = NewParameters.NewVolumetricScatteringIntensity;
// Also update the LightSceneInfoCompact (if it does not have an ID, it is being added)
if (bHasId)
{
Lights[Id].Color = NewParameters.NewColor;
}
}
// Perform Add after update, since that reduces redundant processing (e.g., Add + Move)
if (UpdateLightCommand.IsAdd())
{
AddLightSceneInfo_RenderThread(LightSceneInfo);
// Note: Id is set in AddLightSceneInfo_RenderThread so we must fetch it again
ChangeSetAlloc.AddedLightIds.Add(LightSceneInfo->Id);
}
});
ChangeSetAlloc.PostUpdateChangeSet.PostUpdateMaxIndex = Lights.GetMaxIndex();
ChangeSetAlloc.PostUpdateChangeSet.AddedLightIds = ChangeSetAlloc.AddedLightIds;
SceneExtensionsUpdaters.PostLightsUpdate(GraphBuilder, ChangeSetAlloc.PostUpdateChangeSet);
OnPostLightSceneInfoUpdate.Broadcast(GraphBuilder, ChangeSetAlloc.PostUpdateChangeSet);
GPUScene.OnPostLightSceneInfoUpdate(GraphBuilder, ChangeSetAlloc.PostUpdateChangeSet);
}
template<class T>
static void CreateReflectionCaptureUniformBuffer(const TArray<FReflectionCaptureSortData>& SortedCaptures, TUniformBufferRef<T>& OutReflectionCaptureUniformBuffer)
{
T SamplePositionsBuffer;
for (int32 CaptureIndex = 0; CaptureIndex < SortedCaptures.Num(); CaptureIndex++)
{
SamplePositionsBuffer.PositionHighAndRadius[CaptureIndex] = FVector4f(SortedCaptures[CaptureIndex].Position.High, SortedCaptures[CaptureIndex].Radius);
SamplePositionsBuffer.PositionLow[CaptureIndex] = FVector4f(SortedCaptures[CaptureIndex].Position.Low, 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_MultiFrame);
}
void UpdateReflectionSceneData(FScene* Scene)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_UpdateReflectionSceneData)
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), GetMaxNumReflectionCaptures(Scene->GetShaderPlatform()));
// 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.Position = CurrentCapture->Position;
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;
}
// Create uniform buffers with a sorted captures
if (ReflectionSceneData.bRegisteredReflectionCapturesHasChanged ||
ReflectionSceneData.AllocatedReflectionCaptureStateHasChanged)
{
ReflectionSceneData.ReflectionCaptureUniformBuffer.SafeRelease();
ReflectionSceneData.MobileReflectionCaptureUniformBuffer.SafeRelease();
if (IsMobilePlatform(Scene->GetShaderPlatform()))
{
CreateReflectionCaptureUniformBuffer(ReflectionSceneData.SortedCaptures, ReflectionSceneData.MobileReflectionCaptureUniformBuffer);
}
else
{
CreateReflectionCaptureUniformBuffer(ReflectionSceneData.SortedCaptures, ReflectionSceneData.ReflectionCaptureUniformBuffer);
}
}
// 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 (ReflectionSceneData.AllocatedReflectionCaptureStateHasChanged)
{
if (IsForwardShadingEnabled(Scene->GetShaderPlatform()))
{
const int32 NumPrimitives = Scene->Primitives.Num();
for (int32 PrimitiveIndex = 0; PrimitiveIndex < NumPrimitives; ++PrimitiveIndex)
{
Scene->PrimitivesNeedingUniformBufferUpdate[PrimitiveIndex] = 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 = GetFeatureLevelShadingPath(Scene->GetFeatureLevel()) == 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->RequestStaticMeshUpdate();
}
}
Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged = false;
}
}
}
/**
* Container for scene change set, that can live on beyond the update function. Note that the values are not safe to interpret at all points
* (e.g., a persistent ID of a removed item is not valid after the remove phase), but the arrays are valid as long as the RDG lives.
*/
struct FSceneUpdateChangeSetStorage
{
FSceneUpdateChangeSetStorage()
: PreUpdateChangeSet(FScenePreUpdateChangeSet {
TConstArrayView<FPersistentPrimitiveIndex>(RemovedPrimitiveIds),
TConstArrayView<FPrimitiveSceneInfo*>(RemovedPrimitiveSceneInfos),
PrimitiveUpdates
})
, PostUpdateChangeSet(FScenePostUpdateChangeSet {
TConstArrayView<FPersistentPrimitiveIndex>(AddedPrimitiveIds),
TConstArrayView<FPrimitiveSceneInfo*>(AddedPrimitiveSceneInfos),
PrimitiveUpdates
})
{
}
FSceneUpdateChangeSetStorage(FScenePrimitiveUpdates&& InPrimitiveUpdates, const FViewSceneChangeSet* InViewUpdateChangeSet)
: PrimitiveUpdates(MoveTemp(InPrimitiveUpdates))
, ViewUpdateChangeSet(InViewUpdateChangeSet)
, PreUpdateChangeSet(FScenePreUpdateChangeSet {
TConstArrayView<FPersistentPrimitiveIndex>(RemovedPrimitiveIds),
TConstArrayView<FPrimitiveSceneInfo*>(RemovedPrimitiveSceneInfos),
PrimitiveUpdates
})
, PostUpdateChangeSet(FScenePostUpdateChangeSet {
TConstArrayView<FPersistentPrimitiveIndex>(AddedPrimitiveIds),
TConstArrayView<FPrimitiveSceneInfo*>(AddedPrimitiveSceneInfos),
PrimitiveUpdates
})
{
}
TArray<FPersistentPrimitiveIndex, SceneRenderingAllocator> RemovedPrimitiveIds;
TArray<FPrimitiveSceneInfo*,SceneRenderingAllocator> RemovedPrimitiveSceneInfos;
TArray<FPersistentPrimitiveIndex, SceneRenderingAllocator> AddedPrimitiveIds;
TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator> AddedPrimitiveSceneInfos;
FScenePrimitiveUpdates PrimitiveUpdates;
const FViewSceneChangeSet* ViewUpdateChangeSet = nullptr;
FScenePreUpdateChangeSet PreUpdateChangeSet;
FScenePostUpdateChangeSet PostUpdateChangeSet;
const FScenePreUpdateChangeSet& GetPreUpdateSet()
{
PreUpdateChangeSet.RemovedPrimitiveIds = TConstArrayView<FPersistentPrimitiveIndex>(RemovedPrimitiveIds);
PreUpdateChangeSet.RemovedPrimitiveSceneInfos = TConstArrayView<FPrimitiveSceneInfo*>(RemovedPrimitiveSceneInfos);
PreUpdateChangeSet.ViewUpdateChangeSet = ViewUpdateChangeSet;
return PreUpdateChangeSet;
}
const FScenePostUpdateChangeSet& GetPostUpdateSet()
{
PostUpdateChangeSet.AddedPrimitiveIds = TConstArrayView<FPersistentPrimitiveIndex>(AddedPrimitiveIds);
PostUpdateChangeSet.AddedPrimitiveSceneInfos = TConstArrayView<FPrimitiveSceneInfo*>(AddedPrimitiveSceneInfos);
PostUpdateChangeSet.ViewUpdateChangeSet = ViewUpdateChangeSet;
return PostUpdateChangeSet;
}
};
void FScene::UpdateAllPrimitiveSceneInfos(FRDGBuilder& GraphBuilder, EUpdateAllPrimitiveSceneInfosAsyncOps AsyncOps)
{
FUpdateParameters Parameters;
Parameters.AsyncOps = AsyncOps;
Update(GraphBuilder, Parameters);
}
bool FScene::CanSampleSkyLightRealTimeCaptureData() const
{
// We need a sky light, with bRealTimeCaptureEnabled (only true if supported by the platform settings) and if the captured data is ready.
return SkyLight && SkyLight->bRealTimeCaptureEnabled && ConvolvedSkyRenderTargetReadyIndex >= 0;
}
FViewSceneChangeSet* FScene::ProcessViewChanges(FRDGBuilder& GraphBuilder, TConstArrayView<FViewInfo*> Views)
{
FViewSceneChangeSet* ChangeSet = GraphBuilder.AllocObject<FViewSceneChangeSet>();
ChangeSet->AddedViewIds.Reserve(Views.Num());
ChangeSet->RemovedViewIds.Reserve(PersistentViewStateUniqueIDs.Num());
uint64 NewViewStateDebugFlags = PersistentViewStateDebugFlags;
// deferred cleanup of any view states that are no longer in the set of allocated IDs.
for (auto It = PersistentViewStateUniqueIDs.CreateIterator(); It; ++It)
{
int32 ViewStateUniqueId = (*It);
if (!FSceneViewState_ActiveUniqueIDs.Contains(ViewStateUniqueId))
{
ChangeSet->RemovedViewIds.Add(FPersistentViewId { It.GetIndex() });
It.RemoveCurrent();
NewViewStateDebugFlags &= ~(1ull << It.GetIndex());
}
}
// helper to find existing persistent ID
auto FindExistingPersistentId = [&](int32 InViewStateUniqueId)
{
for (auto It = PersistentViewStateUniqueIDs.CreateConstIterator(); It; ++It)
{
int32 ViewStateUniqueId = (*It);
if (ViewStateUniqueId == InViewStateUniqueId)
{
return FPersistentViewId { It.GetIndex() };
}
}
return FPersistentViewId{};
};
for (FViewInfo* View : Views)
{
FPersistentViewId PersistentViewId;
if (View->ViewState != nullptr)
{
const int32 ViewStateUniqueId = View->ViewState->UniqueID;
check(FSceneViewState_ActiveUniqueIDs.Contains(ViewStateUniqueId));
PersistentViewId = FindExistingPersistentId(ViewStateUniqueId);
if (!PersistentViewId.IsValid())
{
int32 StartIndex = 0;
PersistentViewId = FPersistentViewId { PersistentViewStateUniqueIDs.EmplaceAtLowestFreeIndex(StartIndex, ViewStateUniqueId) };
ChangeSet->AddedViewIds.Emplace(PersistentViewId);
}
if (View->RequiresDebugMaterials())
{
NewViewStateDebugFlags |= (1ull << PersistentViewId.Index);
}
else
{
NewViewStateDebugFlags &= ~(1ull << PersistentViewId.Index);
}
}
// Note: In a future where view updates are decoupled from the rendering properly, this association should happen as part of the rendering.
View->PersistentViewId = PersistentViewId;
}
// Track changes in combined state of view debug flags.
ChangeSet->bIsRequiresDebugMaterialChanged = NewViewStateDebugFlags != PersistentViewStateDebugFlags;
PersistentViewStateDebugFlags = NewViewStateDebugFlags;
return ChangeSet;
}
void FScene::Update(FRDGBuilder& GraphBuilder, const FUpdateParameters& Parameters)
{
LLM_SCOPE(ELLMTag::SceneRender);
SCOPE_CYCLE_COUNTER(STAT_UpdateScenePrimitiveRenderThreadTime);
check(IsInRenderingThread());
check(!UE::RenderCommandPipe::IsReplaying());
// Wait for all outstanding RHI command lists so we can safely modify proxies.
GraphBuilder.RHICmdList.ImmediateFlush(EImmediateFlushType::WaitForOutstandingTasksOnly);
FSkeletalMeshUpdater::FSubmitTasks GPUSkinTasks;
// Only allow async compute when we are updating as part of a render and are allowing async ops to occur.
const ERHIPipeline GPUSkinCachePipeline = GPUSkinCache && Parameters.AsyncOps != EUpdateAllPrimitiveSceneInfosAsyncOps::None ? FGPUSkinCache::GetDispatchPipeline(GraphBuilder) : ERHIPipeline::Graphics;
if (SkeletalMeshUpdater)
{
GPUSkinTasks = SkeletalMeshUpdater->Submit(GraphBuilder, GPUSkinCachePipeline);
GPUSkinUpdateTask = GPUSkinTasks.SkinCache;
}
// Check if the skin cache is available here, because we might be midway through a feature level change
if (GPUSkinCache && IsGPUSkinCacheAvailable(GetFeatureLevelShaderPlatform(GetFeatureLevel())))
{
GPUSkinCacheTask = GPUSkinCache->Dispatch(GraphBuilder, GPUSkinTasks.SkinCache, GPUSkinCachePipeline);
}
UE::Tasks::FTask UpdateUniformExpressionsTask;
FMaterialRenderProxy::UpdateDeferredCachedUniformExpressions(GraphBuilder.RHICmdList, EnumHasAnyFlags(Parameters.AsyncOps, EUpdateAllPrimitiveSceneInfosAsyncOps::CacheMaterialUniformExpressions) ? &UpdateUniformExpressionsTask : nullptr);
RDG_EVENT_SCOPE(GraphBuilder, "UpdateAllPrimitiveSceneInfos");
// Allocated with render graph lifetime, safe to reference from RDG tasks.
FSceneUpdateChangeSetStorage& SceneUpdateChangeSetStorage = *GraphBuilder.AllocObject<FSceneUpdateChangeSetStorage>(MoveTemp(PrimitiveUpdates), Parameters.ViewUpdateChangeSet);
TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator> DeletedPrimitiveSceneInfos;
DeletedPrimitiveSceneInfos.Reserve(SceneUpdateChangeSetStorage.PrimitiveUpdates.NumCommands());
SceneUpdateChangeSetStorage.AddedPrimitiveSceneInfos.Reserve(SceneUpdateChangeSetStorage.PrimitiveUpdates.NumCommands());
SceneUpdateChangeSetStorage.RemovedPrimitiveSceneInfos.Reserve(SceneUpdateChangeSetStorage.PrimitiveUpdates.NumCommands());
SceneUpdateChangeSetStorage.RemovedPrimitiveIds.Reserve(SceneUpdateChangeSetStorage.PrimitiveUpdates.NumCommands());
// Retrieve the commonly used categories used in scene updates.
// 1. RemovedPrimitiveSceneInfos: Primitives that exist in the scene before the update and will be removed.
// 2. AddedPrimitiveSceneInfos: Primitives not in the scene before the update that will be added.
// 3. DeletedPrimitiveSceneInfos: Primitives that needs to be deleted.
SceneUpdateChangeSetStorage.PrimitiveUpdates.ForEachCommand([&](const FPrimitiveUpdateCommand& Cmd)
{
// Skip those that were added & removed in the same frame
if (Cmd.IsAdd() && !Cmd.IsDelete())
{
SceneUpdateChangeSetStorage.AddedPrimitiveSceneInfos.Add(Cmd.GetSceneInfo());
}
if (Cmd.IsDelete())
{
FPrimitiveSceneInfo* SceneInfo = Cmd.GetSceneInfo();
SceneInfo->bPendingDelete = true;
DeletedPrimitiveSceneInfos.Add(SceneInfo);
// Skip those that were added & removed in the same frame
if (!Cmd.IsAdd())
{
SceneUpdateChangeSetStorage.RemovedPrimitiveSceneInfos.Add(Cmd.GetSceneInfo());
SceneUpdateChangeSetStorage.RemovedPrimitiveIds.Add(Cmd.GetPersistentId());
}
}
});
const TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator> &AddedPrimitiveSceneInfos = SceneUpdateChangeSetStorage.AddedPrimitiveSceneInfos;
const TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator> &RemovedPrimitiveSceneInfos = SceneUpdateChangeSetStorage.RemovedPrimitiveSceneInfos;
TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator> RemovedLocalPrimitiveSceneInfos;
RemovedLocalPrimitiveSceneInfos.Reserve(RemovedPrimitiveSceneInfos.Num());
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : RemovedPrimitiveSceneInfos)
{
RemovedLocalPrimitiveSceneInfos.Add(PrimitiveSceneInfo);
}
TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator> AddedLocalPrimitiveSceneInfos;
AddedLocalPrimitiveSceneInfos.Reserve(AddedPrimitiveSceneInfos.Num());
for (FPrimitiveSceneInfo* SceneInfo : AddedPrimitiveSceneInfos)
{
AddedLocalPrimitiveSceneInfos.Add(SceneInfo);
}
GPUScene.OnPreSceneUpdate(GraphBuilder, SceneUpdateChangeSetStorage.GetPreUpdateSet());
// Create a SceneUB that permits access to the scene for invalidation processing.
FSceneUniformBuffer SceneUB;
GPUScene.FillSceneUniformBuffer(GraphBuilder, SceneUB);
FSceneExtensionsUpdaters& SceneExtensionsUpdaters = *GraphBuilder.AllocObject<FSceneExtensionsUpdaters>(*this);
UpdateLights(GraphBuilder, SceneExtensionsUpdaters);
SceneExtensionsUpdaters.PreSceneUpdate(GraphBuilder, SceneUpdateChangeSetStorage.GetPreUpdateSet(), SceneUB);
#if RHI_RAYTRACING
UpdateRayTracingGroupBounds_RemovePrimitives(RemovedPrimitiveSceneInfos);
UpdateRayTracingGroupBounds_AddPrimitives(AddedPrimitiveSceneInfos);
#endif
auto UpdatedInstances = SceneUpdateChangeSetStorage.PrimitiveUpdates.GetRangeView<FUpdateInstanceCommand>();
auto UpdatedTransforms = SceneUpdateChangeSetStorage.PrimitiveUpdates.GetRangeView<FUpdateTransformCommand>();
auto UpdatedInstanceCullDistance = SceneUpdateChangeSetStorage.PrimitiveUpdates.GetRangeView<FUpdateInstanceCullDistanceData>();
auto OverridenPreviousTransforms = SceneUpdateChangeSetStorage.PrimitiveUpdates.GetRangeView<FUpdateOverridePreviousTransformData>();
RemovedLocalPrimitiveSceneInfos.Sort(FPrimitiveArraySortKey());
AddedLocalPrimitiveSceneInfos.Sort(FPrimitiveArraySortKey());
TArray<int32> RemovedPrimitiveIndices;
RemovedPrimitiveIndices.SetNumUninitialized(RemovedLocalPrimitiveSceneInfos.Num());
bool bNeedPathTracedInvalidation = false;
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RemovePrimitiveSceneInfos);
SCOPE_CYCLE_COUNTER(STAT_RemoveScenePrimitiveTime);
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : RemovedLocalPrimitiveSceneInfos)
{
// clear it up, parent is getting removed
SceneLODHierarchy.UpdateNodeSceneInfo(PrimitiveSceneInfo->PrimitiveComponentId, nullptr);
}
while (RemovedLocalPrimitiveSceneInfos.Num())
{
int32 StartIndex = RemovedLocalPrimitiveSceneInfos.Num() - 1;
FPrimitiveSceneProxyType RemovedProxyType = FPrimitiveSceneProxyType(RemovedLocalPrimitiveSceneInfos[StartIndex]->Proxy);
while (StartIndex > 0 && FPrimitiveSceneProxyType(RemovedLocalPrimitiveSceneInfos[StartIndex - 1]->Proxy) == RemovedProxyType)
{
StartIndex--;
}
int32 BroadIndex = -1;
//broad phase search for a matching type
for (BroadIndex = TypeOffsetTable.Num() - 1; BroadIndex >= 0; BroadIndex--)
{
// example how the prefix sum of the tails could look like
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,2,1,1,1,7,4,8]
// TypeOffsetTable[3,8,12,15,16,17,18]
if (TypeOffsetTable[BroadIndex].PrimitiveSceneProxyType == RemovedProxyType)
{
const int32 InsertionOffset = TypeOffsetTable[BroadIndex].Offset;
const int32 PrevOffset = BroadIndex > 0 ? TypeOffsetTable[BroadIndex - 1].Offset : 0;
for (int32 CheckIndex = StartIndex; CheckIndex < RemovedLocalPrimitiveSceneInfos.Num(); CheckIndex++)
{
int32 PrimitiveIndex = RemovedLocalPrimitiveSceneInfos[CheckIndex]->PackedIndex;
checkfSlow(PrimitiveIndex >= PrevOffset && PrimitiveIndex < InsertionOffset, TEXT("PrimitiveIndex %d not in Bucket Range [%d, %d]"), PrimitiveIndex, PrevOffset, InsertionOffset);
}
break;
}
}
{
SCOPED_NAMED_EVENT(FScene_SwapPrimitiveSceneInfos, FColor::Turquoise);
for (int32 CheckIndex = StartIndex; CheckIndex < RemovedLocalPrimitiveSceneInfos.Num(); CheckIndex++)
{
int32 SourceIndex = RemovedLocalPrimitiveSceneInfos[CheckIndex]->PackedIndex;
for (int32 TypeIndex = BroadIndex; TypeIndex < TypeOffsetTable.Num(); TypeIndex++)
{
FTypeOffsetTableEntry& NextEntry = TypeOffsetTable[TypeIndex];
int DestIndex = --NextEntry.Offset; //decrement and prepare swap
// example swap chain of removing X
// PrimitiveSceneProxies[0,0,0,6,X,6,6,6,2,2,2,2,1,1,1,7,4,8]
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,X,2,2,2,1,1,1,7,4,8]
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,X,1,1,1,7,4,8]
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,1,1,1,X,7,4,8]
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,1,1,1,7,X,4,8]
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,1,1,1,7,4,X,8]
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,1,1,1,7,4,8,X]
if (DestIndex != SourceIndex)
{
checkfSlow(DestIndex > SourceIndex, TEXT("Corrupted Prefix Sum [%d, %d]"), DestIndex, SourceIndex);
Primitives[DestIndex]->PackedIndex = SourceIndex;
// Update (the dynamic/compacted) primitive ID for the swapped primitive (not moved), no need to do the other one since it is being removed.
FPersistentPrimitiveIndex MovedPersisitentIndex = Primitives[DestIndex]->PersistentIndex;
PersistentPrimitiveIdToIndexMap[MovedPersisitentIndex.Index] = SourceIndex;
Primitives[SourceIndex]->PackedIndex = DestIndex;
TArraySwapElements(Primitives, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveTransforms, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveSceneProxies, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveBounds, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveFlagsCompact, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveVisibilityIds, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveOctreeIndex, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveOcclusionFlags, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveComponentIds, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveOcclusionBounds, DestIndex, SourceIndex);
#if WITH_EDITOR
TBitArraySwapElements(PrimitivesSelected, DestIndex, SourceIndex);
#endif
#if RHI_RAYTRACING
TArraySwapElements(PrimitiveRayTracingFlags, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveRayTracingDatas, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveRayTracingGroupIds, DestIndex, SourceIndex);
#endif
TBitArraySwapElements(PrimitivesNeedingStaticMeshUpdate, DestIndex, SourceIndex);
TBitArraySwapElements(PrimitivesNeedingUniformBufferUpdate, DestIndex, SourceIndex);
SourceIndex = DestIndex;
}
}
}
}
const int32 PreviousOffset = BroadIndex > 0 ? TypeOffsetTable[BroadIndex - 1].Offset : 0;
const int32 CurrentOffset = TypeOffsetTable[BroadIndex].Offset;
checkfSlow(PreviousOffset <= CurrentOffset, TEXT("Corrupted Bucket [%d, %d]"), PreviousOffset, CurrentOffset);
if (CurrentOffset - PreviousOffset == 0)
{
// remove empty OffsetTable entries e.g.
// TypeOffsetTable[3,8,12,15,15,17,18]
// TypeOffsetTable[3,8,12,15,17,18]
TypeOffsetTable.RemoveAt(BroadIndex);
}
checkfSlow((TypeOffsetTable.Num() == 0 && Primitives.Num() == (RemovedLocalPrimitiveSceneInfos.Num() - StartIndex)) || TypeOffsetTable[TypeOffsetTable.Num() - 1].Offset == Primitives.Num() - (RemovedLocalPrimitiveSceneInfos.Num() - StartIndex), TEXT("Corrupted Tail Offset [%d, %d]"), TypeOffsetTable[TypeOffsetTable.Num() - 1].Offset, Primitives.Num() - (RemovedLocalPrimitiveSceneInfos.Num() - StartIndex));
for (int32 RemoveIndex = StartIndex; RemoveIndex < RemovedLocalPrimitiveSceneInfos.Num(); RemoveIndex++)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = RemovedLocalPrimitiveSceneInfos[RemoveIndex];
checkf(RemovedLocalPrimitiveSceneInfos[RemoveIndex]->PackedIndex >= Primitives.Num() - RemovedLocalPrimitiveSceneInfos.Num(), TEXT("Removed item should be at the end"));
// Store the previous index for use later, and set the PackedIndex member to invalid.
// FPrimitiveOctreeSemantics::SetOctreeNodeIndex will attempt to remove the node index from the
// PrimitiveOctreeIndex. Since the elements have already been swapped, this will cause an invalid change to PrimitiveOctreeIndex.
// Setting the packed index to INDEX_NONE prevents this from happening, but we also need to keep track of the old
// index for use below.
RemovedPrimitiveIndices[RemoveIndex] = RemovedLocalPrimitiveSceneInfos[RemoveIndex]->PackedIndex;
PrimitiveSceneInfo->PackedIndex = INDEX_NONE;
}
//Remove all items from the location of StartIndex to the end of the arrays.
int RemoveCount = RemovedLocalPrimitiveSceneInfos.Num() - StartIndex;
int SourceIndex = Primitives.Num() - RemoveCount;
Primitives.RemoveAt(SourceIndex, RemoveCount, EAllowShrinking::No);
PrimitiveTransforms.Remove(RemoveCount, EAllowShrinking::No);
PrimitiveSceneProxies.RemoveAt(SourceIndex, RemoveCount, EAllowShrinking::No);
PrimitiveBounds.Remove(RemoveCount, EAllowShrinking::No);
PrimitiveFlagsCompact.RemoveAt(SourceIndex, RemoveCount, EAllowShrinking::No);
PrimitiveVisibilityIds.RemoveAt(SourceIndex, RemoveCount, EAllowShrinking::No);
PrimitiveOctreeIndex.RemoveAt(SourceIndex, RemoveCount, EAllowShrinking::No);
PrimitiveOcclusionFlags.RemoveAt(SourceIndex, RemoveCount, EAllowShrinking::No);
PrimitiveComponentIds.RemoveAt(SourceIndex, RemoveCount, EAllowShrinking::No);
PrimitiveOcclusionBounds.Remove(RemoveCount, EAllowShrinking::No);
#if WITH_EDITOR
PrimitivesSelected.RemoveAt(SourceIndex, RemoveCount);
#endif
#if RHI_RAYTRACING
PrimitiveRayTracingFlags.RemoveAt(SourceIndex, RemoveCount);
PrimitiveRayTracingDatas.RemoveAt(SourceIndex, RemoveCount);
PrimitiveRayTracingGroupIds.RemoveAt(SourceIndex, RemoveCount);
#endif
PrimitivesNeedingStaticMeshUpdate.RemoveAt(SourceIndex, RemoveCount);
PrimitivesNeedingUniformBufferUpdate.RemoveAt(SourceIndex, RemoveCount);
CheckPrimitiveArrays();
for (int32 RemoveIndex = StartIndex; RemoveIndex < RemovedLocalPrimitiveSceneInfos.Num(); RemoveIndex++)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = RemovedLocalPrimitiveSceneInfos[RemoveIndex];
FPrimitiveSceneProxy* SceneProxy = PrimitiveSceneInfo->Proxy;
FScopeCycleCounter Context(SceneProxy->GetStatId());
// The removed items PrimitiveIndex has already been invalidated, but a backup is kept in RemovedPrimitiveIndices
int32 PrimitiveIndex = RemovedPrimitiveIndices[RemoveIndex];
if (PrimitiveSceneInfo->bRegisteredWithVelocityData)
{
// Remove primitive's motion blur information.
VelocityData.RemoveFromScene(PrimitiveSceneInfo->PrimitiveComponentId, false);
}
// Unlink the primitive from its shadow parent.
PrimitiveSceneInfo->UnlinkAttachmentGroup();
// Unlink the LOD parent info if valid
PrimitiveSceneInfo->UnlinkLODParentComponent();
// Flush virtual textures touched by primitive
PrimitiveSceneInfo->FlushRuntimeVirtualTexture();
// Remove the primitive from the scene.
PrimitiveSceneInfo->RemoveFromScene(true);
PrimitiveSceneInfo->FreeGPUSceneInstances();
DistanceFieldSceneData.RemovePrimitive(PrimitiveSceneInfo);
LumenRemovePrimitive(PrimitiveSceneInfo, PrimitiveIndex);
#if RHI_RAYTRACING
if (SceneProxy->HasRayTracingRepresentation())
{
RayTracing::FGeometryGroupHandle RayTracingGeometryGroupHandle = SceneProxy->GetRayTracingGeometryGroupHandle();
if (RayTracingGeometryGroupHandle != INDEX_NONE)
{
((FRayTracingGeometryManager*)GRayTracingGeometryManager)->UnregisterProxyWithCachedRayTracingState(SceneProxy, RayTracingGeometryGroupHandle);
}
if (SceneProxy->IsNaniteMesh())
{
Nanite::GRayTracingManager.Remove(PrimitiveSceneInfo);
}
}
bNeedPathTracedInvalidation = bNeedPathTracedInvalidation || PrimitiveSceneInfo->bIsRelevantToPathTracing;
#endif
const int32 PersistentIndex = PrimitiveSceneInfo->PersistentIndex.Index;
PersistentPrimitiveIdAllocator.Free(PersistentIndex);
PersistentPrimitiveIdToIndexMap[PersistentIndex] = INDEX_NONE;
}
RemovedLocalPrimitiveSceneInfos.RemoveAt(StartIndex, RemovedLocalPrimitiveSceneInfos.Num() - StartIndex, EAllowShrinking::No);
}
}
const int32 SceneInfosContainerReservedSize = AddedPrimitiveSceneInfos.Num() + UpdatedTransforms.Num() + UpdatedInstances.Num();
TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator>& SceneInfosWithAddToScene = *GraphBuilder.AllocObject<TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator>>();
TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator> SceneInfosWithFlushRuntimeVirtualTexture;
TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator>& SceneInfosWithStaticDrawListUpdate = *GraphBuilder.AllocObject<TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator>>();
SceneInfosWithAddToScene.Reserve(SceneInfosContainerReservedSize);
SceneInfosWithFlushRuntimeVirtualTexture.Reserve(SceneInfosContainerReservedSize);
SceneInfosWithStaticDrawListUpdate.Reserve(SceneInfosContainerReservedSize);
const auto QueueFlushRuntimeVirtualTexture = [&](FPrimitiveSceneInfo* SceneInfo) -> bool
{
if (SceneInfo->bWritesRuntimeVirtualTexture && !SceneInfo->bPendingFlushRuntimeVirtualTexture)
{
SceneInfo->bPendingFlushRuntimeVirtualTexture = true;
SceneInfosWithFlushRuntimeVirtualTexture.Push(SceneInfo);
return true;
}
return false;
};
const auto QueueAddToScene = [&](FPrimitiveSceneInfo* SceneInfo) -> bool
{
if (!SceneInfo->bPendingAddToScene)
{
SceneInfo->bPendingAddToScene = true;
SceneInfosWithAddToScene.Push(SceneInfo);
return true;
}
return false;
};
const auto QueueAddStaticMeshes = [&](FPrimitiveSceneInfo* SceneInfo)
{
if (SceneInfo->bPendingDelete)
{
UE_LOG(LogRenderer, Fatal, TEXT("SceneInfo %s is marked for deletion but is being queued for static mesh updates. This is a race condition and will crash."), *SceneInfo->Proxy->GetOwnerName().ToString());
}
if (!SceneInfo->bPendingAddStaticMeshes)
{
SceneInfo->bPendingAddStaticMeshes = 1;
SceneInfosWithStaticDrawListUpdate.Push(SceneInfo);
PrimitivesNeedingStaticMeshUpdate[SceneInfo->PackedIndex] = false;
return true;
}
return false;
};
TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator> PendingAllocateInstanceIds;
PendingAllocateInstanceIds.Reserve(UpdatedInstances.Num() + AddedLocalPrimitiveSceneInfos.Num());
// All added primitive scene infos need to be allocated.
PendingAllocateInstanceIds.Append(AddedLocalPrimitiveSceneInfos);
// Perform instance releases before adds, such that allocator consolidation can happen only once.
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(UpdatePrimitiveInstances);
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveInstanceRenderThreadTime);
for (const auto& UpdateInstance : UpdatedInstances)
{
FPrimitiveSceneProxy* PrimitiveSceneProxy = UpdateInstance.SceneInfo->Proxy;
FScopeCycleCounter Context(PrimitiveSceneProxy->GetStatId());
FPrimitiveSceneInfo* PrimitiveSceneInfo = UpdateInstance.SceneInfo;
const FInstanceDataBufferHeader &InstanceDataBufferHeader = PrimitiveSceneInfo->GetInstanceDataHeader();
const bool bInstanceCountChanged = PrimitiveSceneInfo->GetNumInstanceSceneDataEntries() != InstanceDataBufferHeader.NumInstances;
const bool bInstancePayloadDataStrideChanged = InstanceDataBufferHeader.NumInstances > 0 && PrimitiveSceneInfo->GetInstancePayloadDataStride() != InstanceDataBufferHeader.PayloadDataStride;
// Append to queue if not added (if it is also added it will already be queued up)
if ((bInstanceCountChanged || bInstancePayloadDataStrideChanged) && PrimitiveSceneInfo->GetIndex() != INDEX_NONE)
{
PrimitiveSceneInfo->FreeGPUSceneInstances();
PendingAllocateInstanceIds.Add(PrimitiveSceneInfo);
}
}
}
GPUScene.ConsolidateInstanceDataAllocations();
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(AddPrimitiveSceneInfos);
SCOPE_CYCLE_COUNTER(STAT_AddScenePrimitiveRenderThreadTime);
PersistentPrimitiveIdAllocator.Consolidate();
SceneUpdateChangeSetStorage.PrimitiveUpdates.ForEachCommand(ESceneUpdateCommandFilter::Added, [&](FPrimitiveUpdateCommand& Cmd)
{
// Skip those that were added & removed in the same frame
if (Cmd.IsAdd() && !Cmd.IsDelete())
{
check(!Cmd.GetPersistentId().IsValid());
check(!Cmd.GetSceneInfo()->PersistentIndex.IsValid());
FPersistentPrimitiveIndex PersistentPrimitiveIndex{ PersistentPrimitiveIdAllocator.Allocate() };
// Note: propagate to the command such that is it valid for subsequent passes without having to hit the primitive scene info.
Cmd.SetPersistentId(PersistentPrimitiveIndex);
Cmd.GetSceneInfo()->PersistentIndex = PersistentPrimitiveIndex;
}
});
PersistentPrimitiveIdToIndexMap.SetNumUninitialized(GetMaxPersistentPrimitiveIndex());
if (AddedLocalPrimitiveSceneInfos.Num())
{
SCOPED_NAMED_EVENT(FScene_AddPrimitiveSceneInfos_ReserveMemory, FColor::Green);
Primitives.Reserve(Primitives.Num() + AddedLocalPrimitiveSceneInfos.Num());
PrimitiveTransforms.Reserve(PrimitiveTransforms.Num() + AddedLocalPrimitiveSceneInfos.Num());
PrimitiveSceneProxies.Reserve(PrimitiveSceneProxies.Num() + AddedLocalPrimitiveSceneInfos.Num());
PrimitiveBounds.Reserve(PrimitiveBounds.Num() + AddedLocalPrimitiveSceneInfos.Num());
PrimitiveFlagsCompact.Reserve(PrimitiveFlagsCompact.Num() + AddedLocalPrimitiveSceneInfos.Num());
PrimitiveVisibilityIds.Reserve(PrimitiveVisibilityIds.Num() + AddedLocalPrimitiveSceneInfos.Num());
PrimitiveOcclusionFlags.Reserve(PrimitiveOcclusionFlags.Num() + AddedLocalPrimitiveSceneInfos.Num());
PrimitiveComponentIds.Reserve(PrimitiveComponentIds.Num() + AddedLocalPrimitiveSceneInfos.Num());
PrimitiveOcclusionBounds.Reserve(PrimitiveOcclusionBounds.Num() + AddedLocalPrimitiveSceneInfos.Num());
#if WITH_EDITOR
PrimitivesSelected.Reserve(PrimitivesSelected.Num() + AddedLocalPrimitiveSceneInfos.Num());
#endif
#if RHI_RAYTRACING
PrimitiveRayTracingFlags.Reserve(PrimitiveRayTracingFlags.Num() + AddedLocalPrimitiveSceneInfos.Num());
PrimitiveRayTracingDatas.Reserve(PrimitiveRayTracingDatas.Num() + AddedLocalPrimitiveSceneInfos.Num());
PrimitiveRayTracingGroupIds.Reserve(PrimitiveRayTracingGroupIds.Num() + AddedLocalPrimitiveSceneInfos.Num());
#endif
PrimitivesNeedingStaticMeshUpdate.Reserve(PrimitivesNeedingStaticMeshUpdate.Num() + AddedLocalPrimitiveSceneInfos.Num());
PrimitivesNeedingUniformBufferUpdate.Reserve(PrimitivesNeedingUniformBufferUpdate.Num() + AddedLocalPrimitiveSceneInfos.Num());
}
while (AddedLocalPrimitiveSceneInfos.Num())
{
int32 StartIndex = AddedLocalPrimitiveSceneInfos.Num() - 1;
FPrimitiveSceneProxyType InsertProxyType = FPrimitiveSceneProxyType(AddedLocalPrimitiveSceneInfos[StartIndex]->Proxy);
while (StartIndex > 0 && FPrimitiveSceneProxyType(AddedLocalPrimitiveSceneInfos[StartIndex - 1]->Proxy) == InsertProxyType)
{
StartIndex--;
}
{
SCOPED_NAMED_EVENT(FScene_AddPrimitiveSceneInfos, FColor::Turquoise);
for (int32 AddIndex = StartIndex; AddIndex < AddedLocalPrimitiveSceneInfos.Num(); AddIndex++)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = AddedLocalPrimitiveSceneInfos[AddIndex];
Primitives.Add(PrimitiveSceneInfo);
const FMatrix LocalToWorld = PrimitiveSceneInfo->Proxy->GetLocalToWorld();
PrimitiveTransforms.Add(LocalToWorld);
PrimitiveSceneProxies.Add(PrimitiveSceneInfo->Proxy);
PrimitiveBounds.AddUninitialized();
PrimitiveFlagsCompact.AddUninitialized();
PrimitiveVisibilityIds.AddUninitialized();
PrimitiveOctreeIndex.Add(0);
PrimitiveOcclusionFlags.AddUninitialized();
PrimitiveComponentIds.AddUninitialized();
PrimitiveOcclusionBounds.AddUninitialized();
#if WITH_EDITOR
PrimitivesSelected.Add(PrimitiveSceneInfo->Proxy->WantsEditorEffects() || PrimitiveSceneInfo->Proxy->IsSelected());
#endif
#if RHI_RAYTRACING
PrimitiveRayTracingFlags.AddZeroed();
PrimitiveRayTracingDatas.AddZeroed();
PrimitiveRayTracingGroupIds.Add(Experimental::FHashElementId());
#endif
PrimitivesNeedingStaticMeshUpdate.Add(false);
PrimitivesNeedingUniformBufferUpdate.Add(true);
const int32 SourceIndex = PrimitiveSceneProxies.Num() - 1;
PrimitiveSceneInfo->PackedIndex = SourceIndex;
check(PrimitiveSceneInfo->PersistentIndex.IsValid());
PersistentPrimitiveIdToIndexMap[PrimitiveSceneInfo->PersistentIndex.Index] = SourceIndex;
}
}
bool EntryFound = false;
int32 BroadIndex = -1;
//broad phase search for a matching type
for (BroadIndex = TypeOffsetTable.Num() - 1; BroadIndex >= 0; BroadIndex--)
{
// example how the prefix sum of the tails could look like
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,2,1,1,1,7,4,8]
// TypeOffsetTable[3,8,12,15,16,17,18]
if (TypeOffsetTable[BroadIndex].PrimitiveSceneProxyType == InsertProxyType)
{
EntryFound = true;
break;
}
}
// New type encountered
if (!EntryFound)
{
BroadIndex = TypeOffsetTable.Num();
if (BroadIndex)
{
uint32 NextTypeOffset = 0;
for (int32 TypeOffsetIndex = 0; TypeOffsetIndex < TypeOffsetTable.Num(); ++TypeOffsetIndex)
{
const FTypeOffsetTableEntry& TypeEntry = TypeOffsetTable[TypeOffsetIndex];
if (PrimitiveSceneProxies[NextTypeOffset]->IsAlwaysVisible())
{
BroadIndex = TypeOffsetIndex;
break;
}
NextTypeOffset = TypeEntry.Offset;
}
int32 PrevEntryOffset = BroadIndex > 0 ? TypeOffsetTable[BroadIndex - 1].Offset : 0;
TypeOffsetTable.Insert(FTypeOffsetTableEntry(InsertProxyType, PrevEntryOffset), BroadIndex);
}
else
{
// Starting with an empty list and zero offset (offset will be incremented during the while loop)
TypeOffsetTable.Push(FTypeOffsetTableEntry(InsertProxyType, 0));
}
}
{
SCOPED_NAMED_EVENT(FScene_SwapPrimitiveSceneInfos, FColor::Turquoise);
for (int32 AddIndex = StartIndex; AddIndex < AddedLocalPrimitiveSceneInfos.Num(); AddIndex++)
{
int32 SourceIndex = AddedLocalPrimitiveSceneInfos[AddIndex]->PackedIndex;
for (int32 TypeIndex = BroadIndex; TypeIndex < TypeOffsetTable.Num(); TypeIndex++)
{
FTypeOffsetTableEntry& NextEntry = TypeOffsetTable[TypeIndex];
int32 DestIndex = NextEntry.Offset++; //prepare swap and increment
// Example swap chain of inserting a type of 6 at the end
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,2,1,1,1,7,4,8,6]
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,6,2,2,2,1,1,1,7,4,8,2]
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,6,2,2,2,2,1,1,7,4,8,1]
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,6,2,2,2,2,1,1,1,4,8,7]
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,6,2,2,2,2,1,1,1,7,8,4]
// PrimitiveSceneProxies[0,0,0,6,6,6,6,6,6,2,2,2,2,1,1,1,7,4,8]
if (DestIndex != SourceIndex)
{
checkfSlow(SourceIndex > DestIndex, TEXT("Corrupted Prefix Sum [%d, %d]"), SourceIndex, DestIndex);
Primitives[DestIndex]->PackedIndex = SourceIndex;
Primitives[SourceIndex]->PackedIndex = DestIndex;
// Update (the dynamic/compacted) primitive ID for the swapped primitives
{
FPersistentPrimitiveIndex PersistentIndex = Primitives[DestIndex]->PersistentIndex;
PersistentPrimitiveIdToIndexMap[PersistentIndex.Index] = SourceIndex;
}
{
FPersistentPrimitiveIndex PersistentIndex = Primitives[SourceIndex]->PersistentIndex;
PersistentPrimitiveIdToIndexMap[PersistentIndex.Index] = DestIndex;
}
TArraySwapElements(Primitives, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveTransforms, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveSceneProxies, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveBounds, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveFlagsCompact, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveVisibilityIds, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveOctreeIndex, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveOcclusionFlags, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveComponentIds, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveOcclusionBounds, DestIndex, SourceIndex);
#if WITH_EDITOR
TBitArraySwapElements(PrimitivesSelected, DestIndex, SourceIndex);
#endif
#if RHI_RAYTRACING
TArraySwapElements(PrimitiveRayTracingFlags, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveRayTracingDatas, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveRayTracingGroupIds, DestIndex, SourceIndex);
#endif
TBitArraySwapElements(PrimitivesNeedingStaticMeshUpdate, DestIndex, SourceIndex);
TBitArraySwapElements(PrimitivesNeedingUniformBufferUpdate, DestIndex, SourceIndex);
}
}
}
}
CheckPrimitiveArrays();
for (int32 AddIndex = StartIndex; AddIndex < AddedLocalPrimitiveSceneInfos.Num(); AddIndex++)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = AddedLocalPrimitiveSceneInfos[AddIndex];
FScopeCycleCounter Context(PrimitiveSceneInfo->Proxy->GetStatId());
int32 PrimitiveIndex = PrimitiveSceneInfo->PackedIndex;
// Add the primitive to its shadow parent's linked list of children.
// Note: must happen before AddToScene because AddToScene depends on LightingAttachmentRoot
PrimitiveSceneInfo->LinkAttachmentGroup();
}
for (int32 AddIndex = StartIndex; AddIndex < AddedLocalPrimitiveSceneInfos.Num(); AddIndex++)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = AddedLocalPrimitiveSceneInfos[AddIndex];
int32 PrimitiveIndex = PrimitiveSceneInfo->PackedIndex;
FPrimitiveSceneProxy* SceneProxy = PrimitiveSceneInfo->Proxy;
SceneProxy->CreateUniformBuffer();
if (ShouldPrimitiveOutputVelocity(SceneProxy, GetShaderPlatform()))
{
PrimitiveSceneInfo->bRegisteredWithVelocityData = true;
// We must register the initial LocalToWorld with the velocity state.
// In the case of a moving component with MarkRenderStateDirty() called every frame, UpdateTransform will never happen.
VelocityData.UpdateTransform(PrimitiveSceneInfo, PrimitiveTransforms[PrimitiveIndex], PrimitiveTransforms[PrimitiveIndex]);
}
DistanceFieldSceneData.AddPrimitive(PrimitiveSceneInfo);
LumenAddPrimitive(PrimitiveSceneInfo);
#if RHI_RAYTRACING
if (SceneProxy->HasRayTracingRepresentation())
{
// TODO: investigate registering proxy when data is actually cached (for example, in FPrimitiveSceneInfo::CacheRayTracingPrimitives(...))
RayTracing::FGeometryGroupHandle RayTracingGeometryGroupHandle = SceneProxy->GetRayTracingGeometryGroupHandle();
if (RayTracingGeometryGroupHandle != INDEX_NONE)
{
((FRayTracingGeometryManager*)GRayTracingGeometryManager)->RegisterProxyWithCachedRayTracingState(SceneProxy, RayTracingGeometryGroupHandle);
}
if (SceneProxy->IsNaniteMesh())
{
Nanite::GRayTracingManager.Add(PrimitiveSceneInfo);
}
}
{
FScene::FPrimitiveRayTracingData& PrimitiveRayTracingData = PrimitiveRayTracingDatas[PrimitiveIndex];
PrimitiveRayTracingData.bDrawInGame = SceneProxy->IsDrawnInGame();
PrimitiveRayTracingData.bRayTracingFarField = SceneProxy->IsRayTracingFarField();
PrimitiveRayTracingData.bShouldRenderInMainPass = SceneProxy->ShouldRenderInMainPass();
PrimitiveRayTracingData.bCastHiddenShadow = SceneProxy->CastsHiddenShadow();
PrimitiveRayTracingData.bAffectIndirectLightingWhileHidden = SceneProxy->AffectsIndirectLightingWhileHidden();
PrimitiveRayTracingData.bIsVisibleInSceneCaptures = !SceneProxy->IsHiddenInSceneCapture();
PrimitiveRayTracingData.bIsVisibleInSceneCapturesOnly = SceneProxy->IsVisibleInSceneCaptureOnly();
PrimitiveRayTracingData.bIsRayTracingRelevant = SceneProxy->IsRayTracingRelevant();
PrimitiveRayTracingData.bIsVisibleInRayTracing = SceneProxy->IsVisibleInRayTracing();
PrimitiveRayTracingData.bUsesLightingChannels = SceneProxy->GetLightingChannelMask() != GetDefaultLightingChannelMask();
PrimitiveRayTracingData.bCachedRaytracingDataDirty = true;
PrimitiveRayTracingData.CoarseMeshStreamingHandle = SceneProxy->GetCoarseMeshStreamingHandle();
PrimitiveRayTracingData.RayTracingGeometryGroupHandle = SceneProxy->GetRayTracingGeometryGroupHandle();
// cache bIsRelevantToPathTracing in PrimitiveSceneInfo
// since we can't access the corresponding PrimitiveRayTracingData entry when primitive is removed
PrimitiveSceneInfo->bIsRelevantToPathTracing =
PrimitiveRayTracingData.bIsRayTracingRelevant &&
PrimitiveRayTracingData.bIsVisibleInRayTracing &&
(PrimitiveRayTracingData.bDrawInGame ||
PrimitiveRayTracingData.bAffectIndirectLightingWhileHidden ||
PrimitiveRayTracingData.bCastHiddenShadow) &&
PrimitiveRayTracingData.bShouldRenderInMainPass;
}
bNeedPathTracedInvalidation = bNeedPathTracedInvalidation || PrimitiveSceneInfo->bIsRelevantToPathTracing;
#endif
QueueFlushRuntimeVirtualTexture(PrimitiveSceneInfo);
QueueAddToScene(PrimitiveSceneInfo);
QueueAddStaticMeshes(PrimitiveSceneInfo);
}
AddedLocalPrimitiveSceneInfos.RemoveAt(StartIndex, AddedLocalPrimitiveSceneInfos.Num() - StartIndex, EAllowShrinking::No);
}
}
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(UpdatePrimitiveTransform);
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveTransformRenderThreadTime);
for (const auto& Item : UpdatedTransforms)
{
FPrimitiveSceneProxy* PrimitiveSceneProxy = Item.SceneInfo->Proxy;
const FBoxSphereBounds& WorldBounds = Item.Payload.WorldBounds;
const FBoxSphereBounds& LocalBounds = Item.Payload.LocalBounds;
const FMatrix& LocalToWorld = Item.Payload.LocalToWorld;
const FVector& AttachmentRootPosition = Item.Payload.AttachmentRootPosition;
check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex != INDEX_NONE);
FScopeCycleCounter Context(PrimitiveSceneProxy->GetStatId());
FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveSceneProxy->GetPrimitiveSceneInfo();
const bool bUpdateStaticDrawLists = !PrimitiveSceneProxy->StaticElementsAlwaysUseProxyPrimitiveUniformBuffer();
if (QueueAddToScene(PrimitiveSceneInfo))
{
// Remove the primitive from the scene at its old location
// (note that the octree update relies on the bounds not being modified yet).
PrimitiveSceneInfo->RemoveFromScene(bUpdateStaticDrawLists);
if (bUpdateStaticDrawLists)
{
QueueAddStaticMeshes(PrimitiveSceneInfo);
}
}
QueueFlushRuntimeVirtualTexture(PrimitiveSceneInfo);
if (ShouldPrimitiveOutputVelocity(PrimitiveSceneInfo->Proxy, GetShaderPlatform()))
{
PrimitiveSceneInfo->bRegisteredWithVelocityData = true;
VelocityData.UpdateTransform(PrimitiveSceneInfo, LocalToWorld, PrimitiveSceneProxy->GetLocalToWorld());
}
#if RHI_RAYTRACING
bNeedPathTracedInvalidation = bNeedPathTracedInvalidation || (PrimitiveSceneInfo->bIsRelevantToPathTracing &&
!PrimitiveTransforms[PrimitiveSceneInfo->PackedIndex].Equals(LocalToWorld, SMALL_NUMBER));
#endif
// Update the primitive transform.
PrimitiveSceneProxy->SetTransform(GraphBuilder.RHICmdList, LocalToWorld, WorldBounds, LocalBounds, AttachmentRootPosition);
PrimitiveTransforms[PrimitiveSceneInfo->PackedIndex] = LocalToWorld;
if (!RHISupportsVolumeTextures(GetFeatureLevel())
&& (PrimitiveSceneProxy->IsMovable() || PrimitiveSceneProxy->NeedsUnbuiltPreviewLighting() || PrimitiveSceneProxy->GetLightmapType() == ELightmapType::ForceVolumetric))
{
PrimitiveSceneInfo->MarkIndirectLightingCacheBufferDirty();
}
DistanceFieldSceneData.UpdatePrimitive(PrimitiveSceneInfo);
LumenUpdatePrimitive(PrimitiveSceneInfo);
// If the primitive has static mesh elements, it should have returned true from ShouldRecreateProxyOnUpdateTransform!
check(!(bUpdateStaticDrawLists && PrimitiveSceneInfo->StaticMeshes.Num()));
}
#if RHI_RAYTRACING
{
UpdateRayTracingGroupBounds_UpdatePrimitives(UpdatedTransforms);
}
#endif
for (const auto& Transform : OverridenPreviousTransforms)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = Transform.SceneInfo;
VelocityData.OverridePreviousTransform(PrimitiveSceneInfo->PrimitiveComponentId, Transform.Payload.Value);
}
}
// Updating step of instance update. Note that instance IDs are not allocated at this point.
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(UpdatePrimitiveInstances);
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveInstanceRenderThreadTime);
for (const auto& Item : UpdatedInstances)
{
FPrimitiveSceneProxy* PrimitiveSceneProxy = Item.SceneInfo->Proxy;
FPrimitiveSceneInfo* PrimitiveSceneInfo = Item.SceneInfo;
const FUpdateInstanceCommand &UpdateInstance = Item.Payload;
FScopeCycleCounter Context(PrimitiveSceneProxy->GetStatId());
QueueFlushRuntimeVirtualTexture(PrimitiveSceneInfo);
// TODO: no need to do this if only the payload size changed, we only need it because the MDC stores the instance count!
// Better yet: don't update MDCs on instance data change as we can pull it from elsewhere.
const bool bInstanceDataAllocationChanged = PrimitiveSceneInfo->GetInstanceSceneDataOffset() == INDEX_NONE;
const bool bUpdateStaticDrawLists = !PrimitiveSceneProxy->StaticElementsAlwaysUseProxyPrimitiveUniformBuffer()
// Re-cache if instance count changed & it is not promising to get instance count from the Scene OR it is Nanite (which does not have MDCs anyway and is GPU-Driven)
|| (bInstanceDataAllocationChanged && !PrimitiveSceneProxy->DoesMeshBatchesUseSceneInstanceCount() && !PrimitiveSceneProxy->IsNaniteMesh())
// In the mobile path, the call to UpdateInstances_RenderThread may/will update the vertex buffers, which leads to stale buffer references in the MDCs (TODO, make this not the case)
|| !GPUScene.IsEnabled();
if (QueueAddToScene(PrimitiveSceneInfo))
{
PrimitiveSceneInfo->RemoveFromScene(bUpdateStaticDrawLists);
}
// If it was not queued to add the static meshes, do so now and remove them (this may happen if e.g., a transform update happened in the same frame)
if (bUpdateStaticDrawLists && !PrimitiveSceneInfo->bPendingAddStaticMeshes)
{
PrimitiveSceneInfo->RemoveStaticMeshes();
QueueAddStaticMeshes(PrimitiveSceneInfo);
}
#if RHI_RAYTRACING
if (!PrimitiveSceneInfo->bPendingAddStaticMeshes)
{
UpdateCachedRayTracingState(PrimitiveSceneProxy);
}
#endif
// Update the Proxy's data.
PrimitiveSceneProxy->UpdateInstances_RenderThread(GraphBuilder.RHICmdList, UpdateInstance.WorldBounds, UpdateInstance.LocalBounds);
if (!RHISupportsVolumeTextures(GetFeatureLevel())
&& (PrimitiveSceneProxy->IsMovable() || PrimitiveSceneProxy->NeedsUnbuiltPreviewLighting() || PrimitiveSceneProxy->GetLightmapType() == ELightmapType::ForceVolumetric))
{
PrimitiveSceneInfo->MarkIndirectLightingCacheBufferDirty();
}
if (bInstanceDataAllocationChanged)
{
DistanceFieldSceneData.RemovePrimitive(PrimitiveSceneInfo);
DistanceFieldSceneData.AddPrimitive(PrimitiveSceneInfo);
LumenRemovePrimitive(PrimitiveSceneInfo, PrimitiveSceneInfo->GetIndex());
LumenAddPrimitive(PrimitiveSceneInfo);
}
else
{
// TODO: should modify the batched data to make this possible to discern
GPUScene.AddPrimitiveToUpdate(PrimitiveSceneInfo->GetPersistentIndex(), EPrimitiveDirtyState::ChangedAll);
DistanceFieldSceneData.UpdatePrimitive(PrimitiveSceneInfo);
LumenUpdatePrimitive(PrimitiveSceneInfo);
}
#if RHI_RAYTRACING
bNeedPathTracedInvalidation = bNeedPathTracedInvalidation || PrimitiveSceneInfo->bIsRelevantToPathTracing;
#endif
}
#if RHI_RAYTRACING
{
UpdateRayTracingGroupBounds_UpdatePrimitives(UpdatedInstances);
}
#endif
}
// Determine the test visible vs. always visible primitive index ranges
PrimitivesAlwaysVisibleOffset = ~0u;
// TODO: Support skip always visible in the editor (need to handle dynamic relevance)
#if !WITH_EDITOR
if (GVisibilitySkipAlwaysVisible != 0)
{
uint32 NextTypeOffset = 0;
for (int32 TypeOffsetIndex = 0; TypeOffsetIndex < TypeOffsetTable.Num(); ++TypeOffsetIndex)
{
const FTypeOffsetTableEntry& TypeEntry = TypeOffsetTable[TypeOffsetIndex];
#if UE_BUILD_DEBUG
// Sanity check
checkSlow(Primitives[NextTypeOffset]->Proxy == PrimitiveSceneProxies[NextTypeOffset]);
// Sanity check
const FPrimitiveSceneProxyType Type = FPrimitiveSceneProxyType(PrimitiveSceneProxies[NextTypeOffset]);
checkfSlow (Type == TypeEntry.PrimitiveSceneProxyType, TEXT("TypeHash: %i not matching TypeOffsetTable, expected: %i"), Type.ProxyTypeHash, TypeEntry.PrimitiveSceneProxyType.ProxyTypeHash);
#endif
if (PrimitiveSceneProxies[NextTypeOffset]->IsAlwaysVisible())
{
PrimitivesAlwaysVisibleOffset = NextTypeOffset;
break;
}
NextTypeOffset = TypeEntry.Offset;
}
#if 0
for (int32 Test = 0; Test < PrimitiveSceneProxies.Num(); ++Test)
{
const FPrimitiveSceneProxy* TestProxy = PrimitiveSceneProxies[Test];
const bool AlwaysVisible = TestProxy->IsAlwaysVisible();
const bool IsNanite = TestProxy->IsNaniteMesh();
if (uint32(Test) < PrimitivesAlwaysVisibleOffset)
{
check(!AlwaysVisible);
}
else
{
check(AlwaysVisible);
check(IsNanite);
}
}
#endif
// Align up to next full dword - this is to avoid having a single dword spanning "tested" and "always visible" primitives,
// making the lockless parallel calculations much more efficient. This will push a few (<32) primitives from always visible
// into the tested path, but this is not a big deal.
if (PrimitivesAlwaysVisibleOffset != ~0u)
{
PrimitivesAlwaysVisibleOffset = (PrimitivesAlwaysVisibleOffset + uint32(NumBitsPerDWORD) - 1u) & ~(uint32(NumBitsPerDWORD) - 1u);
if (int32(PrimitivesAlwaysVisibleOffset) >= Primitives.Num())
{
PrimitivesAlwaysVisibleOffset = ~0u;
}
}
}
#endif // !WITH_EDITOR
// Allocate all instance slots. Needs to happen after the instance data is updated since that may change the counts.
FPrimitiveSceneInfo::AllocateGPUSceneInstances(this, PendingAllocateInstanceIds);
if (SceneInfosWithAddToScene.Num() > 0)
{
FPrimitiveSceneInfo::AddToScene(this, SceneInfosWithAddToScene);
}
for (const FLevelCommand& Command : LevelCommands)
{
const auto InvalidateNaniteGPUScene = [](FPrimitiveSceneInfo* Primitive)
{
// Invalidate primitive proxy entry in GPU Scene. This is necessary for Nanite::FSceneProxy to be uploaded to GPU scene (see GetPrimitiveID in GPUScene.cpp)
if (Primitive->Proxy->IsNaniteMesh())
{
Primitive->RequestGPUSceneUpdate();
}
};
if (Command.Op == FLevelCommand::EOp::Add)
{
if (const TArray<FPrimitiveSceneInfo*>* LevelPrimitives = PrimitivesNeedingLevelUpdateNotification.Find(Command.Name))
{
for (FPrimitiveSceneInfo* Primitive : *LevelPrimitives)
{
Primitive->Proxy->OnLevelAddedToWorld_RenderThread();
// Primitive was force hidden & excluded from the occlusion culling processing
// Now that it's going to be rendered, update it's occlusion flags so that it is considered.
Primitive->UpdateOcclusionFlags();
InvalidateNaniteGPUScene(Primitive);
}
}
}
else
{
if (TArray<FPrimitiveSceneInfo*>* LevelPrimitives = PrimitivesNeedingLevelUpdateNotification.Find(Command.Name))
{
for (FPrimitiveSceneInfo* Primitive : *LevelPrimitives)
{
Primitive->Proxy->OnLevelRemovedFromWorld_RenderThread();
// Primitive is now force hidden & should be excluded from the occlusion culling processing
Primitive->UpdateOcclusionFlags();
InvalidateNaniteGPUScene(Primitive);
}
}
}
}
SceneUpdateChangeSetStorage.AddedPrimitiveIds.Reserve(AddedPrimitiveSceneInfos.Num());
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : AddedPrimitiveSceneInfos)
{
SceneUpdateChangeSetStorage.AddedPrimitiveIds.Add(PrimitiveSceneInfo->GetPersistentIndex());
}
GPUScene.OnPostSceneUpdate(GraphBuilder, SceneUpdateChangeSetStorage.GetPostUpdateSet());
#if RHI_RAYTRACING
RayTracingSBT.FlushAllocationsToClear(GraphBuilder.RHICmdList);
#endif // RHI_RAYTRACING
UpdateCachedShadowState(SceneUpdateChangeSetStorage.GetPreUpdateSet(), SceneUpdateChangeSetStorage.GetPostUpdateSet());
SceneExtensionsUpdaters.PostSceneUpdate(GraphBuilder, SceneUpdateChangeSetStorage.GetPostUpdateSet());
const bool bAsyncCacheMeshDrawCommands = EnumHasAnyFlags(Parameters.AsyncOps, EUpdateAllPrimitiveSceneInfosAsyncOps::CacheMeshDrawCommands) && GRHISupportsMultithreadedShaderCreation;
UE::Tasks::FTask AddStaticMeshesTask = GraphBuilder.AddCommandListSetupTask(
[this, AddStaticMeshes = CopyTemp(SceneInfosWithStaticDrawListUpdate), SceneInfosWithFlushRuntimeVirtualTexture = MoveTemp(SceneInfosWithFlushRuntimeVirtualTexture), &SceneInfosWithAddToScene]
(FRHICommandListBase& RHICmdList) mutable
{
SCOPED_NAMED_EVENT(StaticMeshUpdate, FColor::Emerald);
if (AddStaticMeshes.Num() > 0)
{
FPrimitiveSceneInfo::AddStaticMeshes(RHICmdList, this, AddStaticMeshes, false);
}
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : SceneInfosWithFlushRuntimeVirtualTexture)
{
PrimitiveSceneInfo->FlushRuntimeVirtualTexture();
PrimitiveSceneInfo->bPendingFlushRuntimeVirtualTexture = false;
}
}, MakeArrayView({ UpdateUniformExpressionsTask, GPUSkinTasks.Inline }), UE::Tasks::ETaskPriority::High, bAsyncCacheMeshDrawCommands);
UpdateReflectionSceneData(this);
{
SCOPED_NAMED_EVENT(UpdateStaticMeshes, FColor::Emerald);
const bool bLastFrameShouldRenderSkylightInBasePass = bCachedShouldRenderSkylightInBasePass;
bCachedShouldRenderSkylightInBasePass = ShouldRenderSkylightInBasePass(false);
if (bCachedShouldRenderSkylightInBasePass != bLastFrameShouldRenderSkylightInBasePass)
{
// Mark the scene as needing static draw lists to be recreated if needed
// The base pass chooses shaders based on whether there's a skylight in the scene, and that is cached in static draw lists
UE_CLOG(!GIsEditor, LogRenderer, Log, TEXT("Forcing update for all mesh draw commands: SkyLight change"));
bScenesPrimitivesNeedStaticMeshElementUpdate = true;
}
const bool bLastFrameSkyLightRealTimeCapture = bCachedSkyLightRealTimeCapture;
bCachedSkyLightRealTimeCapture = CanSampleSkyLightRealTimeCaptureData();
if (bCachedSkyLightRealTimeCapture != bLastFrameSkyLightRealTimeCapture)
{
// When real time capture on the sky light is toggled, we also need to recreate draw list for parameters.
UE_CLOG(!GIsEditor, LogRenderer, Log, TEXT("Forcing update for all mesh draw commands: SkyLight real-time capture change"));
bScenesPrimitivesNeedStaticMeshElementUpdate = true;
}
if (Parameters.ViewUpdateChangeSet && Parameters.ViewUpdateChangeSet->bIsRequiresDebugMaterialChanged)
{
UE_CLOG(!GIsEditor, LogRenderer, Log, TEXT("Forcing update for all mesh draw commands: Scene view debug rendering change"));
bScenesPrimitivesNeedStaticMeshElementUpdate = true;
}
if (GVRSImageManager.GetNeedStaticMeshUpdate())
{
UE_CLOG(!GIsEditor, LogRenderer, Log, TEXT("Forcing update for all mesh draw commands: Variable rate shading toggled"));
bScenesPrimitivesNeedStaticMeshElementUpdate = true;
GVRSImageManager.SetNeedStaticMeshUpdate(false);
}
if (bScenesPrimitivesNeedStaticMeshElementUpdate || CachedDefaultBasePassDepthStencilAccess != DefaultBasePassDepthStencilAccess)
{
// Mark all primitives as needing an update
PrimitivesNeedingStaticMeshUpdate.Init(true, PrimitivesNeedingStaticMeshUpdate.Num());
bScenesPrimitivesNeedStaticMeshElementUpdate = false;
CachedDefaultBasePassDepthStencilAccess = DefaultBasePassDepthStencilAccess;
}
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : SceneInfosWithStaticDrawListUpdate)
{
PrimitivesNeedingStaticMeshUpdate[PrimitiveSceneInfo->PackedIndex] = false;
}
for (TConstSetBitIterator<> BitIt(PrimitivesNeedingStaticMeshUpdate); BitIt; ++BitIt)
{
const int32 Index = BitIt.GetIndex();
FPrimitiveSceneInfo* Primitive = Primitives[Index];
Primitive->RemoveCachedMeshDrawCommands();
Primitive->RemoveCachedNaniteMaterialBins();
#if RHI_RAYTRACING
Primitive->RemoveCachedRayTracingPrimitives();
#endif
SceneInfosWithStaticDrawListUpdate.Emplace(Primitive);
PrimitivesNeedingStaticMeshUpdate[Index] = false;
}
}
// LPI creation needs to launch after the static mesh update as it can call RequestStaticMeshUpdate() which modifies PrimitivesNeedingStaticMeshUpdate.
CreateLightPrimitiveInteractionsTask = GraphBuilder.AddSetupTask([this, &SceneInfosWithAddToScene]
{
SCOPED_NAMED_EVENT(CreateLightPrimitiveInteractions, FColor::Emerald);
bool bSkipNaniteLPIs = ShouldSkipNaniteLPIs(GetShaderPlatform());
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : SceneInfosWithAddToScene)
{
FPrimitiveSceneProxy* Proxy = PrimitiveSceneInfo->Proxy;
if (Proxy->GetLightingChannelMask() != 0)
{
// Don't create LPIs for Nanite
if (bSkipNaniteLPIs && Proxy->IsNaniteMesh())
{
continue;
}
const FBoxSphereBounds& Bounds = Proxy->GetBounds();
const FPrimitiveSceneInfoCompact PrimitiveSceneInfoCompact(PrimitiveSceneInfo);
if(DoesPlatformNeedLocalLightPrimitiveInteraction(GetShaderPlatform()))
{
// Find local lights that affect the primitive in the light octree.
LocalShadowCastingLightOctree.FindElementsWithBoundsTest(Bounds.GetBox(), [&PrimitiveSceneInfoCompact](const FLightSceneInfoCompact& LightSceneInfoCompact)
{
LightSceneInfoCompact.LightSceneInfo->CreateLightPrimitiveInteraction(LightSceneInfoCompact, PrimitiveSceneInfoCompact);
});
}
// Also loop through non-local (directional) shadow-casting lights
for (int32 LightID : DirectionalShadowCastingLightIDs)
{
const FLightSceneInfoCompact& LightSceneInfoCompact = Lights[LightID];
LightSceneInfoCompact.LightSceneInfo->CreateLightPrimitiveInteraction(LightSceneInfoCompact, PrimitiveSceneInfoCompact);
}
}
}
}, EnumHasAnyFlags(Parameters.AsyncOps, EUpdateAllPrimitiveSceneInfosAsyncOps::CreateLightPrimitiveInteractions));
if (bScenesPrimitivesNeedStaticMeshElementUpdate)
{
for (int32 PrimitiveIndex = 0; PrimitiveIndex < Primitives.Num(); PrimitiveIndex++)
{
// HACK: Update Nanite primitives that need re-caching in GPU Scene
// TODO: Should be able to remove this after the move to compute materials.
if (PrimitiveSceneProxies[PrimitiveIndex] && PrimitiveSceneProxies[PrimitiveIndex]->IsNaniteMesh())
{
GPUScene.AddPrimitiveToUpdate(Primitives[PrimitiveIndex]->GetPersistentIndex(), EPrimitiveDirtyState::ChangedOther);
}
}
}
if (SceneInfosWithStaticDrawListUpdate.Num() > 0)
{
CacheMeshDrawCommandsTask = GraphBuilder.AddSetupTask([this, &SceneInfosWithStaticDrawListUpdate]
{
FPrimitiveSceneInfo::CacheMeshDrawCommands(this, SceneInfosWithStaticDrawListUpdate);
}, MakeArrayView({ AddStaticMeshesTask, IsMobilePlatform(GetShaderPlatform()) ? CreateLightPrimitiveInteractionsTask : UE::Tasks::FTask() }), UE::Tasks::ETaskPriority::Normal, bAsyncCacheMeshDrawCommands);
CacheNaniteMaterialBinsTask = GraphBuilder.AddSetupTask([this, &SceneInfosWithStaticDrawListUpdate]
{
FPrimitiveSceneInfo::CacheNaniteMaterialBins(this, SceneInfosWithStaticDrawListUpdate);
}, AddStaticMeshesTask, UE::Tasks::ETaskPriority::Normal, bAsyncCacheMeshDrawCommands);
#if RHI_RAYTRACING
CacheRayTracingPrimitivesTask = GraphBuilder.AddSetupTask([this, &SceneInfosWithStaticDrawListUpdate]
{
FPrimitiveSceneInfo::CacheRayTracingPrimitives(this, SceneInfosWithStaticDrawListUpdate);
}, AddStaticMeshesTask, UE::Tasks::ETaskPriority::Normal, bAsyncCacheMeshDrawCommands);
#endif
}
for (const auto& Item : SceneUpdateChangeSetStorage.PrimitiveUpdates.GetRangeView<FUpdateCustomPrimitiveData>())
{
FPrimitiveSceneProxy* PrimitiveSceneProxy = Item.SceneInfo->Proxy;
FScopeCycleCounter Context(PrimitiveSceneProxy->GetStatId());
PrimitiveSceneProxy->CustomPrimitiveData = Item.Payload.Value;
PrimitivesNeedingUniformBufferUpdate[Item.SceneInfo->PackedIndex] = true;
}
if (auto NaniteMaterialsUpdater = SceneExtensionsUpdaters.GetUpdaterPtr<Nanite::FMaterialsSceneExtension::FUpdater>())
{
NaniteMaterialsUpdater->PostCacheNaniteMaterialBins(GraphBuilder, SceneInfosWithStaticDrawListUpdate);
}
if (auto NaniteSkinningUpdater = SceneExtensionsUpdaters.GetUpdaterPtr<Nanite::FSkinningSceneExtension::FUpdater>())
{
NaniteSkinningUpdater->PostMeshUpdate(GraphBuilder, SceneInfosWithStaticDrawListUpdate);
}
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : AddedPrimitiveSceneInfos)
{
// Set LOD parent information if valid
PrimitiveSceneInfo->LinkLODParentComponent();
// Update scene LOD tree
SceneLODHierarchy.UpdateNodeSceneInfo(PrimitiveSceneInfo->PrimitiveComponentId, PrimitiveSceneInfo);
}
auto UpdatedAttachmentRoots = SceneUpdateChangeSetStorage.PrimitiveUpdates.GetRangeView<FUpdateAttachmentRootData>();
auto DistanceFieldSceneDataUpdates = SceneUpdateChangeSetStorage.PrimitiveUpdates.GetRangeView<FUpdateDistanceFieldSceneData>();
auto UpdatedOcclusionBoundsSlacks = SceneUpdateChangeSetStorage.PrimitiveUpdates.GetRangeView<FUpdateOcclusionBoundsSlacksData>();
auto UpdatedDrawDistance = SceneUpdateChangeSetStorage.PrimitiveUpdates.GetRangeView<FUpdateDrawDistanceData>();
for (const auto& Item : UpdatedAttachmentRoots)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = Item.SceneInfo;
PrimitiveSceneInfo->UnlinkAttachmentGroup();
PrimitiveSceneInfo->LightingAttachmentRoot = Item.Payload.Value;
PrimitiveSceneInfo->LinkAttachmentGroup();
}
for (const auto& Item : DistanceFieldSceneDataUpdates)
{
DistanceFieldSceneData.UpdatePrimitive(Item.SceneInfo);
}
for (const auto& OccSlackDelta : UpdatedOcclusionBoundsSlacks)
{
const FPrimitiveSceneProxy* SceneProxy = OccSlackDelta.SceneInfo->Proxy;
const FPrimitiveSceneInfo* SceneInfo = OccSlackDelta.SceneInfo;
float Delta = OccSlackDelta.Payload.Value;
FBoxSphereBounds NewOccBounds;
if (SceneProxy->HasCustomOcclusionBounds())
{
NewOccBounds = SceneProxy->GetCustomOcclusionBounds();
}
else
{
NewOccBounds = SceneProxy->GetBounds();
}
PrimitiveOcclusionBounds[SceneInfo->PackedIndex] = NewOccBounds.ExpandBy(OCCLUSION_SLOP + Delta);
}
for (const auto& CullDistance : UpdatedInstanceCullDistance)
{
FPrimitiveSceneProxy* SceneProxy = CullDistance.SceneInfo->Proxy;
float StartCullDistance = CullDistance.Payload.Value.X;
float EndCullDistance = CullDistance.Payload.Value.Y;
SceneProxy->SetInstanceCullDistance_RenderThread(StartCullDistance, EndCullDistance);
}
for (const auto& DrawDistance : UpdatedDrawDistance)
{
FPrimitiveSceneProxy* SceneProxy = DrawDistance.SceneInfo->Proxy;
FPrimitiveSceneInfo* SceneInfo = DrawDistance.SceneInfo;
float MinDrawDistance = DrawDistance.Payload.Value.X;
float MaxDrawDistance = DrawDistance.Payload.Value.Y;
float VirtualTextureMaxDrawDistance = DrawDistance.Payload.Value.Z;
SceneProxy->SetDrawDistance_RenderThread(MinDrawDistance, MaxDrawDistance, VirtualTextureMaxDrawDistance);
if (SceneInfo->PackedIndex != INDEX_NONE)
{
PrimitiveBounds[SceneInfo->PackedIndex].MinDrawDistance = SceneProxy->GetMinDrawDistance();
PrimitiveBounds[SceneInfo->PackedIndex].MaxDrawDistance = SceneProxy->GetMaxDrawDistance();
PrimitiveBounds[SceneInfo->PackedIndex].MaxCullDistance = SceneProxy->GetMaxDrawDistance();
}
// Update the primitive info in octree.
if (SceneInfo->OctreeId.IsValidId())
{
FPrimitiveSceneInfoCompact& CompactPrimitiveSceneInfo = PrimitiveOctree.GetElementById(SceneInfo->OctreeId);
CompactPrimitiveSceneInfo.MinDrawDistance = SceneProxy->GetMinDrawDistance();
CompactPrimitiveSceneInfo.MaxDrawDistance = SceneProxy->GetMaxDrawDistance();
}
DistanceFieldSceneData.UpdatePrimitive(SceneInfo);
}
if (Parameters.Callbacks.PostStaticMeshUpdate)
{
Parameters.Callbacks.PostStaticMeshUpdate(AddStaticMeshesTask);
}
{
SCOPED_NAMED_EVENT(UpdateUniformBuffers, FColor::Emerald);
TArray<FPrimitiveSceneProxy*, SceneRenderingAllocator> ProxiesToUpdate;
for (TConstSetBitIterator<> BitIt(PrimitivesNeedingUniformBufferUpdate); BitIt; ++BitIt)
{
const int32 Index = BitIt.GetIndex();
FPrimitiveSceneInfo* Primitive = Primitives[Index];
PrimitivesNeedingUniformBufferUpdate[Index] = false;
ProxiesToUpdate.Emplace(Primitive->Proxy);
GPUScene.AddPrimitiveToUpdate(Primitive->GetPersistentIndex(), EPrimitiveDirtyState::ChangedAll);
}
GraphBuilder.AddCommandListSetupTask([this, ProxiesToUpdate = MoveTemp(ProxiesToUpdate)](FRHICommandList& RHICmdList)
{
SCOPED_NAMED_EVENT(AsyncUpdateUniformBuffers, FColor::Emerald);
FRHICommandListScopedFence ScopedFence(RHICmdList);
for (FPrimitiveSceneProxy* Proxy : ProxiesToUpdate)
{
Proxy->UpdateUniformBuffer(RHICmdList);
}
});
}
#if RHI_RAYTRACING
if (IsRayTracingEnabled(GetShaderPlatform()))
{
// Nanite raytracing manager update must run before GPUScene update since it can modify primitive data
Nanite::GRayTracingManager.Update();
}
#endif
{
RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, UpdateGPUScene);
RDG_EVENT_SCOPE_STAT(GraphBuilder, GPUSceneUpdate, "GPUSceneUpdate");
RDG_GPU_STAT_SCOPE(GraphBuilder, GPUSceneUpdate);
FRDGExternalAccessQueue ExternalAccessQueue;
GPUScene.Update(GraphBuilder, SceneUB, ExternalAccessQueue, SceneUpdateChangeSetStorage.PrimitiveUpdates.GetRangeView<FUpdateInstanceFromComputeCommand>(), Parameters.GPUSceneUpdateTaskPrerequisites);
ExternalAccessQueue.Submit(GraphBuilder);
}
SceneExtensionsUpdaters.PostGPUSceneUpdate(GraphBuilder, SceneUB);
GraphBuilder.AddSetupTask([DeletedPrimitiveSceneInfos = MoveTemp(DeletedPrimitiveSceneInfos)]
{
SCOPED_NAMED_EVENT(FScene_DeletePrimitiveSceneInfo, FColor::Red);
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : DeletedPrimitiveSceneInfos)
{
// It is possible that the HitProxies list isn't empty if PrimitiveSceneInfo was Added/Removed in same frame
// Delete the PrimitiveSceneInfo on the game thread after the rendering thread has processed its removal.
// This must be done on the game thread because the hit proxy references (and possibly other members) need to be freed on the game thread.
struct DeferDeleteHitProxies : FDeferredCleanupInterface
{
DeferDeleteHitProxies(TArray<TRefCountPtr<HHitProxy>>&& InHitProxies) : HitProxies(MoveTemp(InHitProxies)) {}
TArray<TRefCountPtr<HHitProxy>> HitProxies;
};
BeginCleanup(new DeferDeleteHitProxies(MoveTemp(PrimitiveSceneInfo->HitProxies)));
// free the primitive scene proxy.
delete PrimitiveSceneInfo->Proxy;
delete PrimitiveSceneInfo;
}
});
for (IComputeTaskWorker* ComputeTaskWorker : ComputeTaskWorkers)
{
if (ComputeTaskWorker->HasWork(ComputeTaskExecutionGroup::EndOfFrameUpdate))
{
ComputeTaskWorker->SubmitWork(GraphBuilder, ComputeTaskExecutionGroup::EndOfFrameUpdate, FeatureLevel);
}
}
AddStaticMeshesTask.Wait();
if (bNeedPathTracedInvalidation)
{
InvalidatePathTracedOutput();
}
LevelCommands.Empty();
LastUpdateFrameCounter = GFrameCounterRenderThread;
TRACE_COUNTER_SET(SceneLights, Lights.Num());
TRACE_COUNTER_SET(ScenePrimitives, Primitives.Num());
TRACE_COUNTER_SET(ScenePrimitivesArrayMax, Primitives.Max());
#if DO_GUARD_SLOW
for (const FPrimitiveSceneInfo* PrimitiveSceneInfo : Primitives)
{
checkSlow(PrimitiveSceneInfo->PackedIndex != INDEX_NONE);
checkSlow(PrimitiveSceneInfo->PackedIndex < Primitives.Num());
checkSlow(PrimitiveSceneInfo->PersistentIndex.Index != INDEX_NONE);
checkSlow(PersistentPrimitiveIdToIndexMap[PrimitiveSceneInfo->PersistentIndex.Index] == PrimitiveSceneInfo->PackedIndex);
}
#endif
}
bool FScene::ShouldRenderSkylightInBasePass(bool bIsTranslucent) const
{
if (IsMobilePlatform(GetShaderPlatform()))
{
return false;
}
else
{
bool bRenderSkyLight = SkyLight && !SkyLight->bHasStaticLighting && !(ShouldRenderRayTracingSkyLight(SkyLight, GetShaderPlatform()) && !IsForwardShadingEnabled(GetShaderPlatform()));
if (bIsTranslucent)
{
// Both stationary and movable skylights are applied in base pass for translucent materials
bRenderSkyLight = bRenderSkyLight
&& (FReadOnlyCVARCache::EnableStationarySkylight() || !SkyLight->bWantsStaticShadowing);
}
else
{
// For opaque materials, stationary skylight is applied in base pass but movable skylight
// is applied in a separate render pass (bWantssStaticShadowing means stationary skylight)
bRenderSkyLight = bRenderSkyLight
&& ((FReadOnlyCVARCache::EnableStationarySkylight() && SkyLight->bWantsStaticShadowing)
|| (!SkyLight->bWantsStaticShadowing
&& IsForwardShadingEnabled(GetShaderPlatform())));
}
return bRenderSkyLight;
}
}
void FScene::AddGPUSkinCacheAsyncComputeWait(FRDGBuilder& GraphBuilder) const
{
if (GPUSkinCache)
{
GPUSkinCache->AddAsyncComputeWait(GraphBuilder);
}
}
/**
* Dummy NULL scene interface used by dedicated servers.
*/
class FNULLSceneInterface : public FSceneInterface
{
public:
FNULLSceneInterface(UWorld* InWorld, bool bCreateFXSystem )
: FSceneInterface(GMaxRHIFeatureLevel)
, World( InWorld )
, FXSystem( nullptr )
{
World->Scene = this;
if (bCreateFXSystem)
{
World->CreateFXSystem();
}
else
{
World->FXSystem = nullptr;
SetFXSystem(nullptr);
}
}
virtual void AddPrimitive(UPrimitiveComponent* Primitive) override {}
virtual void RemovePrimitive(UPrimitiveComponent* Primitive) override {}
virtual void ReleasePrimitive(UPrimitiveComponent* Primitive) override {}
virtual void BatchAddPrimitives(TArrayView<UPrimitiveComponent*> InPrimitives) override {}
virtual void BatchRemovePrimitives(TArrayView<UPrimitiveComponent*> InPrimitives) override {}
virtual void BatchReleasePrimitives(TArrayView<UPrimitiveComponent*> InPrimitives) override {}
virtual void UpdateAllPrimitiveSceneInfos(FRDGBuilder& GraphBuilder, EUpdateAllPrimitiveSceneInfosAsyncOps = EUpdateAllPrimitiveSceneInfosAsyncOps::None) override {}
virtual FPrimitiveSceneInfo* GetPrimitiveSceneInfo(int32 PrimiteIndex) const final { return nullptr; }
virtual FPrimitiveSceneInfo* GetPrimitiveSceneInfo(FPrimitiveComponentId PrimitiveId) const final { return nullptr; }
virtual FPrimitiveSceneInfo* GetPrimitiveSceneInfo(const FPersistentPrimitiveIndex& PersistentPrimitiveIndex) const final { return nullptr; }
/** Updates the transform of a primitive which has already been added to the scene. */
virtual void UpdatePrimitiveTransform(UPrimitiveComponent* Primitive) override {}
virtual void UpdatePrimitiveInstances(UPrimitiveComponent* Primitive) override {}
virtual void UpdatePrimitiveInstancesFromCompute(FPrimitiveSceneDesc* Primitive, FGPUSceneWriteDelegate&& DataWriterGPU) override {}
virtual void UpdatePrimitiveOcclusionBoundsSlack(UPrimitiveComponent* Primitive, float NewSlack) override {}
virtual void UpdatePrimitiveDrawDistance(UPrimitiveComponent* Primitive, float MinDrawDistance, float MaxDrawDistance, float VirtualTextureMaxDrawDistance) override {}
virtual void UpdateInstanceCullDistance(UPrimitiveComponent* Primitive, float StartCullDistance, float EndCullDistance) {}
virtual void UpdatePrimitiveAttachment(UPrimitiveComponent* Primitive) override {}
virtual void UpdateCustomPrimitiveData(UPrimitiveComponent* Primitive) override {}
virtual void AddLight(ULightComponent* Light) override {}
virtual void RemoveLight(ULightComponent* Light) override {}
virtual void AddInvisibleLight(ULightComponent* Light) override {}
virtual void SetSkyLight(FSkyLightSceneProxy* Light) override {}
virtual void DisableSkyLight(FSkyLightSceneProxy* Light) override {}
virtual bool HasSkyLightRequiringLightingBuild() const { return false; }
virtual bool HasAtmosphereLightRequiringLightingBuild() const { return false; }
virtual void AddDecal(UDecalComponent*) override {}
virtual void RemoveDecal(UDecalComponent*) override {}
virtual void UpdateDecalTransform(UDecalComponent* Decal) override {}
virtual void UpdateDecalFadeOutTime(UDecalComponent* Decal) override {};
virtual void UpdateDecalFadeInTime(UDecalComponent* Decal) override {};
virtual void BatchUpdateDecals(TArray<FDeferredDecalUpdateParams>&& UpdateParams) override {}
/** Updates the transform of a light which has already been added to the scene. */
virtual void UpdateLightTransform(ULightComponent* Light) override {}
virtual void UpdateLightColorAndBrightness(ULightComponent* Light) override {}
virtual void AddExponentialHeightFog(uint64 Id, const FExponentialHeightFogDynamicState& State) override {}
virtual void RemoveExponentialHeightFog(uint64 Id) override {}
virtual bool HasAnyExponentialHeightFog() const override { return false; }
virtual void AddLocalFogVolume(class FLocalFogVolumeSceneProxy* FogProxy) override {}
virtual void RemoveLocalFogVolume(class FLocalFogVolumeSceneProxy* FogProxy) override {}
virtual bool HasAnyLocalFogVolume() const override { return false; }
virtual void AddSkyAtmosphere(FSkyAtmosphereSceneProxy* SkyAtmosphereSceneProxy, bool bStaticLightingBuilt) override {}
virtual void RemoveSkyAtmosphere(FSkyAtmosphereSceneProxy* SkyAtmosphereSceneProxy) override {}
virtual FSkyAtmosphereRenderSceneInfo* GetSkyAtmosphereSceneInfo() override { return nullptr; }
virtual const FSkyAtmosphereRenderSceneInfo* GetSkyAtmosphereSceneInfo() const override { return nullptr; }
virtual void AddSparseVolumeTextureViewer(FSparseVolumeTextureViewerSceneProxy* SVTV) override {}
virtual void RemoveSparseVolumeTextureViewer(FSparseVolumeTextureViewerSceneProxy* SVTV) override {}
virtual TArray<FAnimBankRecordHandle> RegisterAnimBank(const TConstArrayView<FAnimBankDesc> Descs) { return TArray<FAnimBankRecordHandle>(); }
virtual void UnregisterAnimBank(const TConstArrayView<FAnimBankRecordHandle> Handles) override {}
virtual void AddHairStrands(FHairStrandsInstance* Proxy) override {}
virtual void RemoveHairStrands(FHairStrandsInstance* Proxy) override {}
virtual void GetLightIESAtlasSlot(const FLightSceneProxy* Proxy, FLightRenderParameters* Out) override {}
virtual void GetRectLightAtlasSlot(const FRectLightSceneProxy* Proxy, FLightRenderParameters* Out) override {}
virtual void SetPhysicsField(FPhysicsFieldSceneProxy* PhysicsFieldSceneProxy) override {}
virtual void ResetPhysicsField() override {}
virtual void ShowPhysicsField() override {}
virtual void UpdatePhysicsField(FRDGBuilder& GraphBuilder, FViewInfo& View) override {}
virtual void AddVolumetricCloud(FVolumetricCloudSceneProxy* VolumetricCloudSceneProxy) override {}
virtual void RemoveVolumetricCloud(FVolumetricCloudSceneProxy* VolumetricCloudSceneProxy) override {}
virtual FVolumetricCloudRenderSceneInfo* GetVolumetricCloudSceneInfo() override { return NULL; }
virtual const FVolumetricCloudRenderSceneInfo* GetVolumetricCloudSceneInfo() const override { return NULL; }
virtual void AddWindSource(class UWindDirectionalSourceComponent* WindComponent) override {}
virtual void RemoveWindSource(class UWindDirectionalSourceComponent* WindComponent) override {}
virtual void UpdateWindSource(class UWindDirectionalSourceComponent* WindComponent) override {}
virtual const TArray<class FWindSourceSceneProxy*>& GetWindSources_RenderThread() const override
{
static TArray<class FWindSourceSceneProxy*> NullWindSources;
return NullWindSources;
}
virtual void GetWindParameters(const FVector& Position, FVector& OutDirection, float& OutSpeed, float& OutMinGustAmt, float& OutMaxGustAmt) const override { OutDirection = FVector(1.0f, 0.0f, 0.0f); OutSpeed = 0.0f; OutMinGustAmt = 0.0f; OutMaxGustAmt = 0.0f; }
virtual void GetWindParameters_GameThread(const FVector& Position, FVector& OutDirection, float& OutSpeed, float& OutMinGustAmt, float& OutMaxGustAmt) const override { OutDirection = FVector(1.0f, 0.0f, 0.0f); OutSpeed = 0.0f; OutMinGustAmt = 0.0f; OutMaxGustAmt = 0.0f; }
virtual void GetDirectionalWindParameters(FVector& OutDirection, float& OutSpeed, float& OutMinGustAmt, float& OutMaxGustAmt) const override { OutDirection = FVector(1.0f, 0.0f, 0.0f); OutSpeed = 0.0f; OutMinGustAmt = 0.0f; OutMaxGustAmt = 0.0f; }
virtual void AddSpeedTreeWind(class FVertexFactory* VertexFactory, const class UStaticMesh* StaticMesh) override {}
virtual void RemoveSpeedTreeWind_RenderThread(class FVertexFactory* VertexFactory, const class UStaticMesh* StaticMesh) override {}
virtual void UpdateSpeedTreeWind(double CurrentTime) override {}
virtual FRHIUniformBuffer* GetSpeedTreeUniformBuffer(const FVertexFactory* VertexFactory) const override { return nullptr; }
virtual void Release() override {}
/**
* Retrieves the lights interacting with the passed in primitive and adds them to the out array.
*
* @param Primitive Primitive to retrieve interacting lights for
* @param RelevantLights [out] Array of lights interacting with primitive
*/
virtual void GetRelevantLights( UPrimitiveComponent* Primitive, TArray<const ULightComponent*>* RelevantLights ) const override {}
/**
* @return true if hit proxies should be rendered in this scene.
*/
virtual bool RequiresHitProxies() const override
{
return false;
}
// Accessors.
virtual class UWorld* GetWorld() const override
{
return World;
}
virtual FScene* GetRenderScene() override
{
return nullptr;
}
virtual const FScene* GetRenderScene() const override
{
return nullptr;
}
/**
* Sets the FX system associated with the scene.
*/
virtual void SetFXSystem( class FFXSystemInterface* InFXSystem ) override
{
FXSystem = InFXSystem;
}
/**
* Get the FX system associated with the scene.
*/
virtual class FFXSystemInterface* GetFXSystem() override
{
return FXSystem;
}
virtual bool HasAnyLights() const override { return false; }
virtual TConstArrayView<FPrimitiveSceneProxy*> GetPrimitiveSceneProxies() const override
{
return TConstArrayView<FPrimitiveSceneProxy*>();
}
virtual TConstArrayView<FPrimitiveComponentId> GetScenePrimitiveComponentIds() const final
{
return TConstArrayView<FPrimitiveComponentId>();
}
virtual void AddPrimitive(FPrimitiveSceneDesc* Primitive) override {};
virtual void RemovePrimitive(FPrimitiveSceneDesc* Primitive) override {};
virtual void ReleasePrimitive(FPrimitiveSceneDesc* Primitive) override {};
virtual void UpdatePrimitiveTransform(FPrimitiveSceneDesc* Primitive) override {};
virtual void BatchAddPrimitives(TArrayView<FPrimitiveSceneDesc*> InPrimitives) override {};
virtual void BatchRemovePrimitives(TArrayView<FPrimitiveSceneDesc*> InPrimitives) override {};
virtual void BatchReleasePrimitives(TArrayView<FPrimitiveSceneDesc*> InPrimitives) override {};
virtual void BatchRemovePrimitives(TArray<FPrimitiveSceneProxy*>&& InPrimitives) override {};
virtual void UpdateCustomPrimitiveData(FPrimitiveSceneDesc* Primitive, const FCustomPrimitiveData&) override {}
virtual void UpdatePrimitiveInstances(FPrimitiveSceneDesc* Primitive) override {};
virtual void UpdatePrimitiveInstances(FInstancedStaticMeshSceneDesc* Primitive) override {};
virtual void UpdatePrimitivesDrawnInGame_RenderThread(TArrayView<FPrimitiveSceneProxy*> InPrimitiveSceneProxies, bool bDrawnInGame) override {}
private:
UWorld* World;
class FFXSystemInterface* FXSystem;
};
FSceneInterface* FRendererModule::AllocateScene(UWorld* World, bool bInRequiresHitProxies, bool bCreateFXSystem, ERHIFeatureLevel::Type InFeatureLevel)
{
LLM_SCOPE(ELLMTag::SceneRender);
check(IsInGameThread());
// Create a full fledged scene if we have something to render.
if (GIsClient && FApp::CanEverRender() && !GUsingNullRHI)
{
FScene* NewScene = new FScene(World, bInRequiresHitProxies, GIsEditor && (!World || !World->IsGameWorld()), bCreateFXSystem, InFeatureLevel);
AllocatedScenes.Add(NewScene);
return NewScene;
}
// And fall back to a dummy/ NULL implementation for commandlets and dedicated server.
else
{
return new FNULLSceneInterface(World, bCreateFXSystem);
}
}
void FRendererModule::RemoveScene(FSceneInterface* Scene)
{
check(IsInGameThread());
AllocatedScenes.Remove(Scene);
}
#if WITH_STATE_STREAM
IStateStreamManager* FRendererModule::AllocateStateStream(UWorld* World)
{
FSceneInterface* Scene = World->Scene;
FStateStreamManagerImpl* Manager = new FStateStreamManagerImpl();
FStateStreamCreator::RegisterStateStreams(FStateStreamRegisterContext{*Manager, Scene});
return Manager;
}
void FRendererModule::FreeStateStream(IStateStreamManager* Manager)
{
FStateStreamManagerImpl* ManagerImpl = static_cast<FStateStreamManagerImpl*>(Manager);
FStateStreamCreator::UnregisterStateStreams(FStateStreamUnregisterContext{*ManagerImpl});
delete ManagerImpl;
}
#endif
void FRendererModule::UpdateStaticDrawLists()
{
// Update all static meshes in order to recache cached mesh draw commands.
check(IsInGameThread()); // AllocatedScenes is managed by the game thread
for (FSceneInterface* Scene : AllocatedScenes)
{
Scene->UpdateStaticDrawLists();
}
}
void UpdateStaticMeshesForMaterials(const TArray<const FMaterial*>& MaterialResourcesToUpdate)
{
TRACE_CPUPROFILER_EVENT_SCOPE(UpdateStaticMeshesForMaterials);
TArray<UMaterialInterface*> UsedMaterials;
TSet<UMaterialInterface*> UsedMaterialsDependencies;
FObjectCacheContextScope ObjectCacheScope;
TMap<FScene*, TArray<FPrimitiveSceneInfo*>> UsedPrimitives;
for (IPrimitiveComponent* PrimitiveComponent: ObjectCacheScope.GetContext().GetPrimitiveComponents())
{
if (PrimitiveComponent->IsRenderStateCreated() && PrimitiveComponent->GetSceneProxy() && PrimitiveComponent->GetSceneProxy()->GetPrimitiveSceneInfo()->IsIndexValid())
{
UsedMaterialsDependencies.Reset();
UsedMaterials.Reset();
// Note: relying on GetUsedMaterials to be accurate, or else we won't propagate to the right primitives and the renderer will crash later
// FPrimitiveSceneProxy::VerifyUsedMaterial is used to make sure that all materials used for rendering are reported in GetUsedMaterials
PrimitiveComponent->GetUsedMaterials(UsedMaterials);
for (UMaterialInterface* UsedMaterial : UsedMaterials)
{
if (UsedMaterial)
{
UsedMaterial->GetDependencies(UsedMaterialsDependencies);
}
}
if (UsedMaterialsDependencies.Num() > 0)
{
for (const FMaterial* MaterialResourceToUpdate : MaterialResourcesToUpdate)
{
UMaterialInterface* UpdatedMaterialInterface = MaterialResourceToUpdate->GetMaterialInterface();
if (UpdatedMaterialInterface)
{
if (UsedMaterialsDependencies.Contains(UpdatedMaterialInterface))
{
FPrimitiveSceneProxy* SceneProxy = PrimitiveComponent->GetSceneProxy();
FPrimitiveSceneInfo* SceneInfo = SceneProxy->GetPrimitiveSceneInfo();
FScene* Scene = SceneInfo->Scene;
TArray<FPrimitiveSceneInfo*>& SceneInfos = UsedPrimitives.FindOrAdd(Scene);
SceneInfos.Add(SceneInfo);
break;
}
}
}
}
}
}
UE::RenderCommandPipe::FSyncScope SyncScope;
ENQUEUE_RENDER_COMMAND(FUpdateStaticMeshesForMaterials)(
[UsedPrimitives = MoveTemp(UsedPrimitives)](FRHICommandListImmediate& RHICmdList) mutable
{
// Defer the caching until the next render tick, to make sure that all render components queued
// for re-creation are processed. Otherwise, we may end up caching mesh commands from stale data.
for (auto& SceneInfos: UsedPrimitives)
{
SceneInfos.Key->UpdateAllPrimitiveSceneInfos(RHICmdList);
}
for (auto& SceneInfos : UsedPrimitives)
{
TArray<FPrimitiveSceneInfo*>& SceneInfoArray = SceneInfos.Value;
FPrimitiveSceneInfo::UpdateStaticMeshes(SceneInfos.Key, SceneInfoArray, EUpdateStaticMeshFlags::AllCommands, false);
}
});
}
void FRendererModule::UpdateStaticDrawListsForMaterials(const TArray<const FMaterial*>& Materials)
{
// Update static meshes for a given set of materials in order to recache cached mesh draw commands.
UpdateStaticMeshesForMaterials(Materials);
}
FSceneViewStateInterface* FRendererModule::AllocateViewState(ERHIFeatureLevel::Type FeatureLevel)
{
return new FSceneViewState(FeatureLevel, nullptr);
}
FSceneViewStateInterface* FRendererModule::AllocateViewState(ERHIFeatureLevel::Type FeatureLevel, FSceneViewStateInterface* ShareOriginTarget)
{
return new FSceneViewState(FeatureLevel, (FSceneViewState*)ShareOriginTarget);
}
TUniquePtr<ISceneRenderBuilder> FRendererModule::CreateSceneRenderBuilder(FSceneInterface* SceneInterface)
{
return MakeUnique<FSceneRenderBuilder>(SceneInterface);
}
void FRendererModule::InvalidatePathTracedOutput(PathTracing::EInvalidateReason InvalidateReason)
{
// AllocatedScenes is managed by the game thread
// #jira UE-130700:
// Because material updates call this function and could happen in parallel, we also allow the parallel game thread here.
// We assume that no changes will be made to AllocatedScene during this time, otherwise locking would need to
// be introduced (which could have performance implications).
check(IsInGameThread() || IsInParallelGameThread());
for (FSceneInterface* Scene : AllocatedScenes)
{
Scene->InvalidatePathTracedOutput(InvalidateReason);
}
}
uint32 FScene::GetFrameNumber() const
{
if (IsInGameThread())
{
return SceneFrameNumber;
}
else
{
return SceneFrameNumberRenderThread;
}
}
void FScene::IncrementFrameNumber()
{
// Increment game-tread version
++SceneFrameNumber;
ENQUEUE_RENDER_COMMAND(SceneStartFrame)([this,NewNumber = SceneFrameNumber](FRHICommandListImmediate& RHICmdList)
{
SceneFrameNumberRenderThread = NewNumber;
});
}
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
void FScene::DebugRender(TArrayView<FViewInfo> Views)
{
// TODO: debug rendering should move to the renderer
if (FShadowScene* ShadowScene = GetExtensionPtr<FShadowScene>())
{
ShadowScene->DebugRender(Views);
}
}
#endif
bool FScene::AddCustomRenderPass(const FSceneViewFamily* ViewFamily, const FCustomRenderPassRendererInput& CustomRenderPassInput)
{
CustomRenderPassRendererInputs.Add(CustomRenderPassInput);
return true;
}
void FScene::UpdateCachedShadowState(const FScenePreUpdateChangeSet &ScenePreUpdateChangeSet, const FScenePostUpdateChangeSet &ScenePostUpdateChangeSet)
{
for (const FLightSceneInfo* LightSceneInfo : DirectionalLights)
{
TArray<FCachedShadowMapData>* CachedShadowMapDatas = GetCachedShadowMapDatas(LightSceneInfo->Id);
if (CachedShadowMapDatas)
{
for (auto& CachedShadowMapData : *CachedShadowMapDatas)
{
for (FPersistentPrimitiveIndex PersistentPrimitiveIndex : ScenePreUpdateChangeSet.RemovedPrimitiveIds)
{
if (CachedShadowMapData.StaticShadowSubjectPersistentPrimitiveIdMap[PersistentPrimitiveIndex.Index] == true)
{
CachedShadowMapData.InvalidateCachedShadow();
break;
}
}
CachedShadowMapData.StaticShadowSubjectPersistentPrimitiveIdMap.SetNum(GetMaxPersistentPrimitiveIndex(), false);
}
}
}
}
FVirtualShadowMapArrayCacheManager* FScene::GetVirtualShadowMapCache()
{
return GetExtensionPtr<FVirtualShadowMapArrayCacheManager>();
}