Files
UnrealEngineUWP/Engine/Source/Runtime/Renderer/Private/RendererScene.cpp
chris kulla 3dada130b2 Path Tracer: invalidate path traced output when decals are added, removed or transformed
#rb Tiago.Costa
#preflight 628fc6f48c077c0d661f7993

[CL 20383655 by chris kulla in ue5-main branch]
2022-05-26 15:26:34 -04:00

5685 lines
201 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
Scene.cpp: Scene manager implementation.
=============================================================================*/
#include "CoreMinimal.h"
#include "HAL/ThreadSafeCounter.h"
#include "HAL/PlatformFileManager.h"
#include "Stats/Stats.h"
#include "HAL/IConsoleManager.h"
#include "Misc/App.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 "SceneManagement.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/MotionVectorSimulation.h"
#include "PrimitiveSceneInfo.h"
#include "LightSceneInfo.h"
#include "LightMapRendering.h"
#include "SkyAtmosphereRendering.h"
#include "BasePassRendering.h"
#include "MobileBasePassRendering.h"
#include "ScenePrivate.h"
#include "RendererModule.h"
#include "StaticMeshResources.h"
#include "ParameterCollection.h"
#include "DistanceFieldAmbientOcclusion.h"
#include "EngineModule.h"
#include "FXSystem.h"
#include "DistanceFieldLightingShared.h"
#include "SpeedTreeWind.h"
#include "Components/WindDirectionalSourceComponent.h"
#include "Lumen/LumenSceneRendering.h"
#include "PlanarReflectionSceneProxy.h"
#include "Engine/StaticMesh.h"
#include "GPUSkinCache.h"
#include "ComputeSystemInterface.h"
#include "DynamicShadowMapChannelBindingHelper.h"
#include "GPUScene.h"
#include "HAL/LowLevelMemTracker.h"
#include "VT/RuntimeVirtualTextureSceneProxy.h"
#include "HairStrandsInterface.h"
#include "VelocityRendering.h"
#include "RectLightSceneProxy.h"
#include "RectLightTextureManager.h"
#if RHI_RAYTRACING
#include "RayTracingDynamicGeometryCollection.h"
#include "RayTracingSkinnedGeometry.h"
#include "RayTracing/RayTracingScene.h"
#endif
#include "RHIGPUReadback.h"
#include "ShaderPrint.h"
#include "VirtualShadowMaps/VirtualShadowMapCacheManager.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
);
/** Affects MobileBasePassPixelShader.usf so must relaunch editor to recompile shaders. */
static TAutoConsoleVariable<int32> CVarMobileEarlyZPassOnlyMaterialMasking(
TEXT("r.Mobile.EarlyZPassOnlyMaterialMasking"),
0,
TEXT("Whether to compute materials' mask opacity only in early Z pass for Mobile platform. Changing this setting requires restarting the editor.\n")
TEXT("<=0: off\n")
TEXT(" 1: on\n"),
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.\n")
TEXT(" 0: off\n")
TEXT(" 1: all opaque \n"),
ECVF_Scalability
);
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."));
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");
/** 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;
#define ENABLE_LOG_PRIMITIVE_INSTANCE_ID_STATS_TO_CSV 0
#if ENABLE_LOG_PRIMITIVE_INSTANCE_ID_STATS_TO_CSV
int32 GDumpPrimitiveAllocatorStatsToCSV = 0;
FAutoConsoleVariableRef CVarDumpPrimitiveStatsToCSV(
TEXT("r.DumpPrimitiveStatsToCSV"),
GDumpPrimitiveAllocatorStatsToCSV,
TEXT("Dump primitive and instance stats to CSV\n")
TEXT(" 0 - stop recording, dump to csv and clear array.\n")
TEXT(" 1 - start recording into array\n")
TEXT(" 2 - dump to csv and continue recording without clearing array\n"),
ECVF_RenderThreadSafe
);
static constexpr int32 StatStride = 8;
TArray<int32> GPrimitiveAllocatorStats;
void DumpPrimitiveAllocatorStats()
{
if (!GPrimitiveAllocatorStats.IsEmpty())
{
FString FileName = FPaths::ProjectLogDir() / TEXT("PrimitiveStats-") + FDateTime::Now().ToString() + TEXT(".csv");
UE_LOG(LogRenderer, Log, TEXT("Dumping primitive allocator stats trace to: '%s'"), *FileName);
FArchive* FileToLogTo = IFileManager::Get().CreateFileWriter(*FileName, false);
ensure(FileToLogTo);
if (FileToLogTo)
{
static const FString StatNames[StatStride] =
{
TEXT("NumPrimitives"),
TEXT("MaxPersistent"),
TEXT("NumPersistentAllocated"),
TEXT("PersistentFreeListSizeBC"),
TEXT("PersistentPendingListSizeBC"),
TEXT("PersistentFreeListSize"),
TEXT("MaxInstances"),
TEXT("NumInstancesAllocated"),
};
// Print header
FString StringToPrint;
for (int32 Index = 0; Index < StatStride; ++Index)
{
if (!StringToPrint.IsEmpty())
{
StringToPrint += TEXT(",");
}
if (Index < int32(UE_ARRAY_COUNT(StatNames)))
{
StringToPrint.Append(StatNames[Index]);
}
else
{
StringToPrint.Appendf(TEXT("Stat_%d"), Index);
}
}
StringToPrint += TEXT("\n");
FileToLogTo->Serialize(TCHAR_TO_ANSI(*StringToPrint), StringToPrint.Len());
int32 Num = GPrimitiveAllocatorStats.Num() / StatStride;
for (int32 Ind = 0; Ind < Num; ++Ind)
{
StringToPrint.Empty();
for (int32 StatInd = 0; StatInd < StatStride; ++StatInd)
{
if (!StringToPrint.IsEmpty())
{
StringToPrint.Append(TEXT(","));
}
StringToPrint.Appendf(TEXT("%d"), GPrimitiveAllocatorStats[Ind * StatStride + StatInd]);
}
StringToPrint += TEXT("\n");
FileToLogTo->Serialize(TCHAR_TO_ANSI(*StringToPrint), StringToPrint.Len());
}
FileToLogTo->Close();
}
}
}
void UpdatePrimitiveAllocatorStats(int32 NumPrimitives, int32 MaxPersistent, int32 NumPersistentAllocated, int32 PersistemFreeListSize, int32 PersistemFreeListSizeBC, int32 PersistentPendingListSizeBC, int32 MaxInstances, int32 NumInstancesAllocated)
{
// Dump and clear when turning off the cvar
if (GDumpPrimitiveAllocatorStatsToCSV == 0 && !GPrimitiveAllocatorStats.IsEmpty())
{
DumpPrimitiveAllocatorStats();
GPrimitiveAllocatorStats.Empty();
}
// Dump snapshot (and don't clear or stop) when cvar is set to 2
if (GDumpPrimitiveAllocatorStatsToCSV == 2 && !GPrimitiveAllocatorStats.IsEmpty())
{
DumpPrimitiveAllocatorStats();
GDumpPrimitiveAllocatorStatsToCSV = 1;
}
if (GDumpPrimitiveAllocatorStatsToCSV != 0)
{
GPrimitiveAllocatorStats.Add(NumPrimitives);
GPrimitiveAllocatorStats.Add(MaxPersistent);
GPrimitiveAllocatorStats.Add(NumPersistentAllocated);
GPrimitiveAllocatorStats.Add(PersistemFreeListSizeBC);
GPrimitiveAllocatorStats.Add(PersistentPendingListSizeBC);
GPrimitiveAllocatorStats.Add(PersistemFreeListSize);
GPrimitiveAllocatorStats.Add(MaxInstances);
GPrimitiveAllocatorStats.Add(NumInstancesAllocated);
}
}
#endif // ENABLE_LOG_PRIMITIVE_INSTANCE_ID_STATS_TO_CSV
/**
* 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)
, CameraAerialPerspectiveVolumeIndex(0)
{
}
void FPersistentSkyAtmosphereData::InitialiseOrNextFrame(ERHIFeatureLevel::Type FeatureLevel, FPooledRenderTargetDesc& AerialPerspectiveDesc, FRHICommandListImmediate& RHICmdList)
{
if (!bInitialised)
{
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"));
}
bInitialised = true;
}
CameraAerialPerspectiveVolumeIndex = (CameraAerialPerspectiveVolumeIndex + 1) % CameraAerialPerspectiveVolumeCount;
}
TRefCountPtr<IPooledRenderTarget> FPersistentSkyAtmosphereData::GetCurrentCameraAerialPerspectiveVolume()
{
check(CameraAerialPerspectiveVolumes[CameraAerialPerspectiveVolumeIndex].IsValid());
return CameraAerialPerspectiveVolumes[CameraAerialPerspectiveVolumeIndex];
}
/** Default constructor. */
FSceneViewState::FSceneViewState(ERHIFeatureLevel::Type FeatureLevel)
: OcclusionQueryPool(RHICreateRenderQueryPool(RQT_Occlusion))
{
// Set FeatureLevel to a valid value, so we get Init/ReleaseDynamicRHI calls on FeatureLevel changes
SetFeatureLevel(FeatureLevel);
UniqueID = FSceneViewState_UniqueID.Increment();
Scene = nullptr;
OcclusionFrameCounter = 0;
LastRenderTime = -FLT_MAX;
LastRenderTimeDelta = 0.0f;
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();
}
CachedVisibilityChunk = NULL;
CachedVisibilityHandlerId = INDEX_NONE;
CachedVisibilityBucketIndex = INDEX_NONE;
CachedVisibilityChunkIndex = INDEX_NONE;
MIDUsedCount = 0;
TemporalAASampleIndex = 0;
FrameIndex = 0;
DistanceFieldTemporalSampleIndex = 0;
bDOFHistory = true;
bDOFHistory2 = true;
// Sets the mipbias to invalid large number.
MaterialTextureCachedMipBias = BIG_NUMBER;
SequencerState = ESS_None;
bIsStereoView = false;
bRoundRobinOcclusionEnabled = false;
for (int32 CascadeIndex = 0; CascadeIndex < UE_ARRAY_COUNT(TranslucencyLightingCacheAllocations); CascadeIndex++)
{
TranslucencyLightingCacheAllocations[CascadeIndex] = NULL;
}
bInitializedGlobalDistanceFieldOrigins = false;
GlobalDistanceFieldUpdateIndex = 0;
GlobalDistanceFieldCameraVelocityOffset = FVector::ZeroVector;
ShadowOcclusionQueryMaps.Empty(FOcclusionQueryHelpers::MaxBufferedOcclusionFrames);
ShadowOcclusionQueryMaps.AddZeroed(FOcclusionQueryHelpers::MaxBufferedOcclusionFrames);
LastAutoDownsampleChangeTime = 0;
SmoothedHalfResTranslucencyGPUDuration = 0;
SmoothedFullResTranslucencyGPUDuration = 0;
bShouldAutoDownsampleTranslucency = false;
PreExposure = 1.f;
bUpdateLastExposure = false;
#if RHI_RAYTRACING
GatherPointsBuffer = nullptr;
GatherPointsResolution = FIntVector(0, 0, 0);
#endif
ViewVirtualShadowMapCache = nullptr;
}
void DestroyRenderResource(FRenderResource* RenderResource)
{
if (RenderResource)
{
ENQUEUE_RENDER_COMMAND(DestroySceneViewStateRenderResource)(
[RenderResource](FRHICommandList&)
{
RenderResource->ReleaseResource();
delete RenderResource;
});
}
}
void DestroyRWBuffer(FRWBuffer* RWBuffer)
{
ENQUEUE_RENDER_COMMAND(DestroyRWBuffer)(
[RWBuffer](FRHICommandList&)
{
delete RWBuffer;
});
}
FSceneViewState::~FSceneViewState()
{
CachedVisibilityChunk = NULL;
ShadowOcclusionQueryMaps.Reset();
for (int32 CascadeIndex = 0; CascadeIndex < UE_ARRAY_COUNT(TranslucencyLightingCacheAllocations); CascadeIndex++)
{
delete TranslucencyLightingCacheAllocations[CascadeIndex];
}
HairStrandsViewStateData.Release();
ShaderPrintStateData.Release();
if (ViewVirtualShadowMapCache)
{
delete ViewVirtualShadowMapCache;
ViewVirtualShadowMapCache = nullptr;
}
if (Scene)
{
Scene->RemoveViewState(this);
}
}
FSceneViewStateInterface* FScene::AllocateViewState()
{
FSceneViewState* Result = new FSceneViewState(FeatureLevel);
Result->Scene = this;
ViewStates.Add(Result);
return Result;
}
void FScene::RemoveViewState(FSceneViewStateInterface* ViewState)
{
for (int32 ViewStateIndex = 0; ViewStateIndex < ViewStates.Num(); ViewStateIndex++)
{
if (ViewStates[ViewStateIndex] == ViewState)
{
ViewStates.RemoveAt(ViewStateIndex);
break;
}
}
}
#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)
{
FVector2f FirstKey(-1, -1);
for (auto kvp : Requests)
{
FirstKey = kvp.Key;
break;
}
if (Requests.Contains(FirstKey))
{
Requests.Remove(FirstKey);
}
}
Requests.Add(ViewportUV, PixelInspectorRequest);
return true;
}
#endif //WITH_EDITOR
FDistanceFieldSceneData::FDistanceFieldSceneData(EShaderPlatform ShaderPlatform)
: NumObjectsInBuffer(0)
, NumHeightFieldObjectsInBuffer(0)
, IndirectionAtlasLayout(8, 8, 8, 512, 512, 512, false, true, false)
, HeightFieldAtlasGeneration(0)
, HFVisibilityAtlasGenerattion(0)
{
ObjectBuffers = nullptr;
HeightFieldObjectBuffers = nullptr;
HeightFieldObjectBuffers = nullptr;
bTrackAllPrimitives = ShouldAllPrimitivesHaveDistanceField(ShaderPlatform);
bCanUse16BitObjectIndices = RHISupportsBufferLoadTypeConversion(ShaderPlatform);
StreamingRequestReadbackBuffers.AddZeroed(MaxStreamingReadbackBuffers);
}
FDistanceFieldSceneData::~FDistanceFieldSceneData()
{
delete ObjectBuffers;
}
bool IncludePrimitiveInDistanceFieldSceneData(bool bTrackAllPrimitives, const FPrimitiveSceneProxy* Proxy)
{
return PrimitiveNeedsDistanceFieldSceneData(
bTrackAllPrimitives,
Proxy->CastsDynamicIndirectShadow(),
Proxy->AffectsDistanceFieldLighting(),
Proxy->IsDrawnInGame(),
Proxy->CastsHiddenShadow(),
Proxy->CastsDynamicShadow(),
Proxy->AffectsDynamicIndirectLighting());
}
void FDistanceFieldSceneData::AddPrimitive(FPrimitiveSceneInfo* InPrimitive)
{
FPrimitiveSceneProxy* Proxy = InPrimitive->Proxy;
if (IncludePrimitiveInDistanceFieldSceneData(bTrackAllPrimitives, Proxy))
{
if (Proxy->SupportsHeightfieldRepresentation())
{
UTexture2D* HeightAndNormal;
UTexture2D* DiffuseColor;
UTexture2D* Visibility;
FHeightfieldComponentDescription Desc(FMatrix::Identity);
Proxy->GetHeightfieldRepresentation(HeightAndNormal, DiffuseColor, Visibility, Desc);
GHeightFieldTextureAtlas.AddAllocation(HeightAndNormal);
if (Visibility)
{
check(Desc.VisibilityChannel >= 0 && Desc.VisibilityChannel < 4);
GHFVisibilityTextureAtlas.AddAllocation(Visibility, Desc.VisibilityChannel);
}
checkSlow(!PendingHeightFieldAddOps.Contains(InPrimitive));
checkSlow(!PendingHeightFieldUpdateOps.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);
PendingThrottledOperations.Remove(InPrimitive);
if (InPrimitive->DistanceFieldInstanceIndices.Num() > 0)
{
PendingRemoveOperations.Add(FPrimitiveRemoveInfo(InPrimitive));
}
InPrimitive->DistanceFieldInstanceIndices.Empty();
}
if (Proxy->SupportsHeightfieldRepresentation())
{
UTexture2D* HeightAndNormal;
UTexture2D* DiffuseColor;
UTexture2D* Visibility;
FHeightfieldComponentDescription Desc(FMatrix::Identity);
Proxy->GetHeightfieldRepresentation(HeightAndNormal, DiffuseColor, Visibility, Desc);
GHeightFieldTextureAtlas.RemoveAllocation(HeightAndNormal);
if (Visibility)
{
GHFVisibilityTextureAtlas.RemoveAllocation(Visibility);
}
PendingHeightFieldAddOps.Remove(InPrimitive);
PendingHeightFieldUpdateOps.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(!PendingThrottledOperations.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 PendingThrottledOperations. 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."));
checkf(!PendingHeightFieldUpdateOps.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 PendingHeightFieldUpdateOps. 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](FRHICommandList& RHICmdList)
{
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;
}
void FSceneViewState::AddVirtualShadowMapCache()
{
// Can only allocate a virtual shadow map cache if we have a scene pointer available
if (Scene)
{
// Don't allocate if one already exists
if (ViewVirtualShadowMapCache == nullptr)
{
FVirtualShadowMapArrayCacheManager* ViewCache = new FVirtualShadowMapArrayCacheManager(Scene);
// Need to add reference to virtual shadow map cache in render thread
ENQUEUE_RENDER_COMMAND(LinkVirtualShadowMapCache)(
[this, ViewCache](FRHICommandListImmediate& RHICmdList)
{
this->ViewVirtualShadowMapCache = ViewCache;
});
} //-V773
}
}
FVirtualShadowMapArrayCacheManager* FSceneViewState::GetVirtualShadowMapCache() const
{
return ViewVirtualShadowMapCache;
}
FVirtualShadowMapArrayCacheManager* FScene::GetVirtualShadowMapCache(FSceneView& View) const
{
FVirtualShadowMapArrayCacheManager* Result = DefaultVirtualShadowMapCache;
if (View.State)
{
FVirtualShadowMapArrayCacheManager* ViewCache = View.State->GetVirtualShadowMapCache();
if (ViewCache)
{
Result = ViewCache;
}
}
return Result;
}
void FScene::GetAllVirtualShadowMapCacheManagers(TArray<FVirtualShadowMapArrayCacheManager*, SceneRenderingAllocator>& OutCacheManagers) const
{
OutCacheManagers.Empty();
if (DefaultVirtualShadowMapCache)
{
OutCacheManagers.Add(DefaultVirtualShadowMapCache);
}
for (const FSceneViewState* ViewState : ViewStates)
{
if (ViewState->ViewVirtualShadowMapCache)
{
OutCacheManagers.Add(ViewState->ViewVirtualShadowMapCache);
}
}
}
void FScene::UpdateParameterCollections(const TArray<FMaterialParameterCollectionInstanceResource*>& InParameterCollections)
{
ENQUEUE_RENDER_COMMAND(UpdateParameterCollectionsCommand)(
[this, InParameterCollections](FRHICommandList&)
{
// 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);
}
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() == PrimitiveVirtualTextureFlags.Num());
check(Primitives.Num() == PrimitiveVirtualTextureLod.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() == PrimitiveRayTracingGroupIds.Num());
#endif
check(Primitives.Num() == PrimitivesNeedingStaticMeshUpdate.Num());
#if UE_BUILD_DEBUG
MaxTypeOffsetIndex = MaxTypeOffsetIndex == -1 ? TypeOffsetTable.Num() : MaxTypeOffsetIndex;
for (int i = 0; i < MaxTypeOffsetIndex; i++)
{
for (int j = i + 1; j < MaxTypeOffsetIndex; j++)
{
check(TypeOffsetTable[i].PrimitiveSceneProxyType != TypeOffsetTable[j].PrimitiveSceneProxyType);
check(TypeOffsetTable[i].Offset <= TypeOffsetTable[j].Offset);
}
}
uint32 NextOffset = 0;
for (int i = 0; i < MaxTypeOffsetIndex; i++)
{
const FTypeOffsetTableEntry& Entry = TypeOffsetTable[i];
for (uint32 Index = NextOffset; Index < Entry.Offset; Index++)
{
checkSlow(Primitives[Index]->Proxy == PrimitiveSceneProxies[Index]);
SIZE_T TypeHash = PrimitiveSceneProxies[Index]->GetTypeHash();
checkfSlow(TypeHash == Entry.PrimitiveSceneProxyType, TEXT("TypeHash: %i not matching, expected: %i"), TypeHash, Entry.PrimitiveSceneProxyType);
}
NextOffset = Entry.Offset;
}
#endif
}
static TAutoConsoleVariable<int32> CVarDoLazyStaticMeshUpdate(
TEXT("r.DoLazyStaticMeshUpdate"),
0,
TEXT("If true, then do not add meshes to the static mesh draw lists until they are visible. Experiemental option."));
static void DoLazyStaticMeshUpdateCVarSinkFunction()
{
if (!GIsRunning || GIsEditor || !FApp::CanEverRender())
{
return;
}
static bool CachedDoLazyStaticMeshUpdate = !!CVarDoLazyStaticMeshUpdate.GetValueOnGameThread();
bool DoLazyStaticMeshUpdate = !!CVarDoLazyStaticMeshUpdate.GetValueOnGameThread();
if (DoLazyStaticMeshUpdate != CachedDoLazyStaticMeshUpdate)
{
CachedDoLazyStaticMeshUpdate = DoLazyStaticMeshUpdate;
for (TObjectIterator<UWorld> It; It; ++It)
{
UWorld* World = *It;
if (World && World->Scene)
{
FScene* Scene = (FScene*)(World->Scene);
ENQUEUE_RENDER_COMMAND(UpdateDoLazyStaticMeshUpdate)(
[Scene](FRHICommandListImmediate& RHICmdList)
{
Scene->UpdateDoLazyStaticMeshUpdate(RHICmdList);
});
}
}
}
}
static FAutoConsoleVariableSink CVarDoLazyStaticMeshUpdateSink(FConsoleCommandDelegate::CreateStatic(&DoLazyStaticMeshUpdateCVarSinkFunction));
static void UpdateEarlyZPassModeCVarSinkFunction()
{
static auto* CVarAntiAliasingMethod = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AntiAliasingMethod"));
static int32 CachedAntiAliasingMethod = CVarAntiAliasingMethod->GetValueOnGameThread();
static int32 CachedEarlyZPass = CVarEarlyZPass.GetValueOnGameThread();
static int32 CachedBasePassWriteDepthEvenWithFullPrepass = CVarBasePassWriteDepthEvenWithFullPrepass.GetValueOnGameThread();
static int32 CachedMobileEarlyZPass = CVarMobileEarlyZPass.GetValueOnGameThread();
const int32 AntiAliasingMethod = CVarAntiAliasingMethod->GetValueOnGameThread();
const int32 EarlyZPass = CVarEarlyZPass.GetValueOnGameThread();
const int32 BasePassWriteDepthEvenWithFullPrepass = CVarBasePassWriteDepthEvenWithFullPrepass.GetValueOnGameThread();
const int32 MobileEarlyZPass = CVarMobileEarlyZPass.GetValueOnGameThread();
// Switching between MSAA and another AA in forward shading mode requires EarlyZPassMode to update.
if (AntiAliasingMethod != CachedAntiAliasingMethod
|| EarlyZPass != CachedEarlyZPass
|| BasePassWriteDepthEvenWithFullPrepass != CachedBasePassWriteDepthEvenWithFullPrepass
|| MobileEarlyZPass != CachedMobileEarlyZPass)
{
for (TObjectIterator<UWorld> It; It; ++It)
{
UWorld* World = *It;
if (World && World->Scene)
{
FScene* Scene = (FScene*)(World->Scene);
Scene->UpdateEarlyZPassMode();
}
}
CachedAntiAliasingMethod = AntiAliasingMethod;
CachedEarlyZPass = EarlyZPass;
CachedBasePassWriteDepthEvenWithFullPrepass = BasePassWriteDepthEvenWithFullPrepass;
CachedMobileEarlyZPass = MobileEarlyZPass;
}
}
static FAutoConsoleVariableSink CVarUpdateEarlyZPassModeSink(FConsoleCommandDelegate::CreateStatic(&UpdateEarlyZPassModeCVarSinkFunction));
void FScene::UpdateDoLazyStaticMeshUpdate(FRHICommandListImmediate& CmdList)
{
bool DoLazyStaticMeshUpdate = CVarDoLazyStaticMeshUpdate.GetValueOnRenderThread() && !GIsEditor && FApp::CanEverRender();
FPrimitiveSceneInfo::UpdateStaticMeshes(CmdList, this, Primitives, EUpdateStaticMeshFlags::AllCommands, !DoLazyStaticMeshUpdate);
}
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.PrimitivesToUpdate.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;
}
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)
{
check(IsInRenderingThread());
check(PrimitiveSceneInfo->PackedIndex == INDEX_NONE);
check(AddedPrimitiveSceneInfos.Find(PrimitiveSceneInfo) == nullptr);
AddedPrimitiveSceneInfos.FindOrAdd(PrimitiveSceneInfo);
if (PreviousTransform.IsSet())
{
OverridenPreviousTransforms.Update(PrimitiveSceneInfo, 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(UPrimitiveComponent* Component, UWorld* World)
{
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()
{
ViewUniformBuffer.SafeRelease();
for (auto& UniformBuffer : MobileDirectionalLightUniformBuffers)
{
UniformBuffer.SafeRelease();
}
MobileSkyReflectionUniformBuffer.SafeRelease();
Initialize();
}
void FPersistentUniformBuffers::Initialize()
{
FViewUniformShaderParameters ViewUniformBufferParameters;
ViewUniformBuffer = TUniformBufferRef<FViewUniformShaderParameters>::CreateUniformBufferImmediate(ViewUniformBufferParameters, UniformBuffer_MultiFrame, EUniformBufferValidation::None);
FNaniteUniformParameters NaniteUniformBufferParameters;
NaniteUniformBuffer = TUniformBufferRef<FNaniteUniformParameters>::CreateUniformBufferImmediate(NaniteUniformBufferParameters, UniformBuffer_MultiFrame, EUniformBufferValidation::None);
LumenCardCaptureViewUniformBuffer = TUniformBufferRef<FViewUniformShaderParameters>::CreateUniformBufferImmediate(ViewUniformBufferParameters, UniformBuffer_MultiFrame, EUniformBufferValidation::None);
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_SingleFrame, 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);
}
class FAsyncCreateLightPrimitiveInteractionsTask : public FNonAbandonableTask
{
TSet<FPrimitiveSceneInfo*> PendingPrimitives;
const FScene* Scene;
public:
FAsyncCreateLightPrimitiveInteractionsTask()
: Scene(nullptr)
{}
void Init(const FScene* InScene)
{
Scene = InScene;
}
void AddPrimitive(FPrimitiveSceneInfo* PrimInfo)
{
PendingPrimitives.Add(PrimInfo);
}
bool HasPendingPrimitives() const
{
return PendingPrimitives.Num() > 0;
}
void DoWork()
{
FOptionalTaskTagScope Scope(ETaskTag::EParallelRenderingThread);
for (TSet<FPrimitiveSceneInfo*>::TConstIterator It(PendingPrimitives); It; ++It)
{
FPrimitiveSceneInfo* PrimInfo = *It;
FPrimitiveSceneProxy* Proxy = PrimInfo->Proxy;
if (Proxy->GetLightingChannelMask() != 0)
{
FMemMark MemStackMark(FMemStack::Get());
const FBoxSphereBounds& Bounds = Proxy->GetBounds();
const FPrimitiveSceneInfoCompact PrimitiveSceneInfoCompact(PrimInfo);
// Find local lights that affect the primitive in the light octree.
Scene->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 : Scene->DirectionalShadowCastingLightIDs)
{
const FLightSceneInfoCompact& LightSceneInfoCompact = Scene->Lights[LightID];
LightSceneInfoCompact.LightSceneInfo->CreateLightPrimitiveInteraction(LightSceneInfoCompact, PrimitiveSceneInfoCompact);
}
}
}
PendingPrimitives.Reset();
Scene = nullptr;
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncCreateLightPrimitiveInteractionsTask, STATGROUP_ThreadPoolAsyncTasks);
}
};
FScene::FScene(UWorld* InWorld, bool bInRequiresHitProxies, bool bInIsEditorScene, bool bCreateFXSystem, ERHIFeatureLevel::Type InFeatureLevel)
: FSceneInterface(InFeatureLevel)
, World(InWorld)
, FXSystem(nullptr)
, bScenesPrimitivesNeedStaticMeshElementUpdate(false)
, PathTracingInvalidationCounter(0)
#if RHI_RAYTRACING
, CachedRayTracingMeshCommandsMode(ERayTracingMeshCommandsMode::RAY_TRACING)
#endif
, SkyLight(NULL)
, ConvolvedSkyRenderTargetReadyIndex(-1)
, RealTimeSlicedReflectionCaptureFirstFrameState(ERealTimeSlicedReflectionCaptureFirstFrameState::INIT)
, RealTimeSlicedReflectionCaptureState(-1)
, RealTimeSlicedReflectionCaptureStateStep(0)
, bRealTimeSlicedReflectionCaptureStateStepDone(true)
, RealTimeSlicedReflectionCaptureFrameNumber(0)
, PathTracingSkylightColor(0, 0, 0, 0)
, SimpleDirectionalLight(NULL)
, ReflectionSceneData(InFeatureLevel)
, IndirectLightingCache(InFeatureLevel)
, VolumetricLightmapSceneData(this)
, DistanceFieldSceneData(GShaderPlatformForFeatureLevel[InFeatureLevel])
, LumenSceneData(nullptr)
, PreshadowCacheLayout(0, 0, 0, 0, false)
, SkyAtmosphere(NULL)
, VolumetricCloud(NULL)
, PrecomputedVisibilityHandler(NULL)
, LocalShadowCastingLightOctree(FVector::ZeroVector, HALF_WORLD_MAX)
, PrimitiveOctree(FVector::ZeroVector, HALF_WORLD_MAX)
, bRequiresHitProxies(bInRequiresHitProxies)
, bIsEditorScene(bInIsEditorScene)
, NumUncachedStaticLightingInteractions(0)
, NumUnbuiltReflectionCaptures(0)
, NumMobileStaticAndCSMLights_RenderThread(0)
, NumMobileMovableDirectionalLights_RenderThread(0)
, GPUSkinCache(nullptr)
, SceneLODHierarchy(this)
, RuntimeVirtualTexturePrimitiveHideMaskEditor(0)
, RuntimeVirtualTexturePrimitiveHideMaskGame(0)
, DefaultMaxDistanceFieldOcclusionDistance(InWorld->GetWorldSettings()->DefaultMaxDistanceFieldOcclusionDistance)
, GlobalDistanceFieldViewDistance(InWorld->GetWorldSettings()->GlobalDistanceFieldViewDistance)
, DynamicIndirectShadowsSelfShadowingIntensity(FMath::Clamp(InWorld->GetWorldSettings()->DynamicIndirectShadowsSelfShadowingIntensity, 0.0f, 1.0f))
, ReadOnlyCVARCache(FReadOnlyCVARCache::Get())
#if RHI_RAYTRACING
, RayTracingDynamicGeometryCollection(nullptr)
, RayTracingSkinnedGeometryUpdateQueue(nullptr)
#endif
, NumVisibleLights_GameThread(0)
, NumEnabledSkylights_GameThread(0)
, SceneFrameNumber(0)
, bForceNoPrecomputedLighting(InWorld->GetWorldSettings()->bForceNoPrecomputedLighting)
{
FMemory::Memzero(MobileDirectionalLights);
FMemory::Memzero(AtmosphereLights);
check(World);
World->Scene = this;
FeatureLevel = World->FeatureLevel;
GPUScene.SetEnabled(FeatureLevel);
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);
}
ComputeSystemInterface::CreateWorkers(this, ComputeTaskWorkers);
#if RHI_RAYTRACING
if (IsRayTracingEnabled())
{
RayTracingDynamicGeometryCollection = new FRayTracingDynamicGeometryCollection();
RayTracingSkinnedGeometryUpdateQueue = new FRayTracingSkinnedGeometryUpdateQueue();
}
#endif
World->UpdateParameterCollectionInstances(false, false);
FPersistentUniformBuffers* PersistentUniformBuffers = &UniformBuffers;
ENQUEUE_RENDER_COMMAND(InitializeUniformBuffers)(
[PersistentUniformBuffers](FRHICommandListImmediate& RHICmdList)
{
PersistentUniformBuffers->Initialize();
});
UpdateEarlyZPassMode();
LumenSceneData = new FLumenSceneData(GShaderPlatformForFeatureLevel[InFeatureLevel], InWorld->WorldType);
// We use a default Virtual Shadow Map cache, if one hasn't been allocated for a specific view. GPU resources for
// a cache aren't allocated until the cache is actually used, so this shouldn't waste any GPU memory when not in use.
DefaultVirtualShadowMapCache = new FVirtualShadowMapArrayCacheManager(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(RemovedPrimitiveSceneInfos.Num() == 0, TEXT("All pending primitive removal operations are expected to be flushed when the scene is destroyed. Remaining operations are likely to cause a memory leak."));
checkf(AddedPrimitiveSceneInfos.Num() == 0, 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."));
checkf(ViewStates.Num() == 0, TEXT("All scene view states are expected to be removed before the scene is destroyed."));
// Delete default cache
if (DefaultVirtualShadowMapCache)
{
delete DefaultVirtualShadowMapCache;
DefaultVirtualShadowMapCache = nullptr;
}
if (LumenSceneData)
{
delete LumenSceneData;
LumenSceneData = nullptr;
}
ReflectionSceneData.CubemapArray.ReleaseResource();
IndirectLightingCache.ReleaseResource();
DistanceFieldSceneData.Release();
delete GPUSkinCache;
GPUSkinCache = nullptr;
ComputeSystemInterface::DestroyWorkers(this, ComputeTaskWorkers);
#if RHI_RAYTRACING
delete RayTracingDynamicGeometryCollection;
RayTracingDynamicGeometryCollection = nullptr;
delete RayTracingSkinnedGeometryUpdateQueue;
RayTracingSkinnedGeometryUpdateQueue = nullptr;
#endif // RHI_RAYTRACING
checkf(RemovedPrimitiveSceneInfos.Num() == 0, TEXT("Leaking %d FPrimitiveSceneInfo instances."), RemovedPrimitiveSceneInfos.Num()); // Ensure UpdateAllPrimitiveSceneInfos() is called before destruction.
}
void FScene::AddPrimitive(UPrimitiveComponent* Primitive)
{
SCOPE_CYCLE_COUNTER(STAT_AddScenePrimitiveGT);
SCOPED_NAMED_EVENT(FScene_AddPrimitive, FColor::Green);
checkf(!Primitive->IsUnreachable(), TEXT("%s"), *Primitive->GetFullName());
const float WorldTime = GetWorld()->GetTimeSeconds();
// Save the world transform for next time the primitive is added to the scene
float DeltaTime = WorldTime - Primitive->LastSubmitTime;
if ( DeltaTime < -0.0001f || Primitive->LastSubmitTime < 0.0001f )
{
// Time was reset?
Primitive->LastSubmitTime = WorldTime;
}
else if ( DeltaTime > 0.0001f )
{
// First call for the new frame?
Primitive->LastSubmitTime = WorldTime;
}
checkf(!Primitive->SceneProxy, TEXT("Primitive has already been added to the scene!"));
// Create the primitive's scene proxy.
FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->CreateSceneProxy();
Primitive->SceneProxy = PrimitiveSceneProxy;
if(!PrimitiveSceneProxy)
{
// Primitives which don't have a proxy are irrelevant to the scene manager.
return;
}
// 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(0);
AActor* AttachmentRoot = Primitive->GetAttachmentRootActor();
if (AttachmentRoot)
{
AttachmentRootPosition = AttachmentRoot->GetActorLocation();
}
struct FCreateRenderThreadParameters
{
FPrimitiveSceneProxy* PrimitiveSceneProxy;
FMatrix RenderMatrix;
FBoxSphereBounds WorldBounds;
FVector AttachmentRootPosition;
FBoxSphereBounds LocalBounds;
};
FCreateRenderThreadParameters Params =
{
PrimitiveSceneProxy,
RenderMatrix,
Primitive->Bounds,
AttachmentRootPosition,
Primitive->GetLocalBounds()
};
// Help track down primitive with bad bounds way before the it gets to the Renderer
ensureMsgf(!Primitive->Bounds.ContainsNaN(),
TEXT("Nans found on Bounds for Primitive %s: Origin %s, BoxExtent %s, SphereRadius %f"), *Primitive->GetName(), *Primitive->Bounds.Origin.ToString(), *Primitive->Bounds.BoxExtent.ToString(), Primitive->Bounds.SphereRadius);
INC_DWORD_STAT_BY( STAT_GameToRendererMallocTotal, PrimitiveSceneProxy->GetMemoryFootprint() + PrimitiveSceneInfo->GetMemoryFootprint() );
// Verify the primitive is valid
VerifyProperPIEScene(Primitive, World);
// Increment the attachment counter, the primitive is about to be attached to the scene.
Primitive->AttachmentCounter.Increment();
// Create any RenderThreadResources required and send a command to the rendering thread to add the primitive to the scene.
FScene* Scene = this;
// If this primitive has a simulated previous transform, ensure that the velocity data for the scene representation is correct
TOptional<FTransform> PreviousTransform = FMotionVectorSimulation::Get().GetPreviousTransform(Primitive);
ENQUEUE_RENDER_COMMAND(AddPrimitiveCommand)(
[Params = MoveTemp(Params), Scene, PrimitiveSceneInfo, PreviousTransform = MoveTemp(PreviousTransform)](FRHICommandListImmediate& RHICmdList)
{
FPrimitiveSceneProxy* SceneProxy = Params.PrimitiveSceneProxy;
FScopeCycleCounter Context(SceneProxy->GetStatId());
SceneProxy->SetTransform(Params.RenderMatrix, Params.WorldBounds, Params.LocalBounds, Params.AttachmentRootPosition);
// Create any RenderThreadResources required.
SceneProxy->CreateRenderThreadResources();
Scene->AddPrimitiveSceneInfo_RenderThread(PrimitiveSceneInfo, PreviousTransform);
});
}
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
);
void FScene::UpdatePrimitiveTransform_RenderThread(FPrimitiveSceneProxy* PrimitiveSceneProxy, const FBoxSphereBounds& WorldBounds, const FBoxSphereBounds& LocalBounds, const FMatrix& LocalToWorld, const FVector& AttachmentRootPosition, const TOptional<FTransform>& PreviousTransform)
{
check(IsInRenderingThread());
#if VALIDATE_PRIMITIVE_PACKED_INDEX
if (AddedPrimitiveSceneInfos.Find(PrimitiveSceneProxy->GetPrimitiveSceneInfo()) != nullptr)
{
check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex == INDEX_NONE);
}
else
{
check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex != INDEX_NONE);
}
check(RemovedPrimitiveSceneInfos.Find(PrimitiveSceneProxy->GetPrimitiveSceneInfo()) == nullptr);
#endif
UpdatedTransforms.Update(PrimitiveSceneProxy, { WorldBounds, LocalBounds, LocalToWorld, AttachmentRootPosition });
if (PreviousTransform.IsSet())
{
OverridenPreviousTransforms.Update(PrimitiveSceneProxy->GetPrimitiveSceneInfo(), PreviousTransform.GetValue().ToMatrixWithScale());
}
}
void FScene::UpdatePrimitiveOcclusionBoundsSlack_RenderThread(const FPrimitiveSceneProxy* PrimitiveSceneProxy, float NewSlack)
{
check(IsInRenderingThread());
#if VALIDATE_PRIMITIVE_PACKED_INDEX
if (AddedPrimitiveSceneInfos.Find(PrimitiveSceneProxy->GetPrimitiveSceneInfo()) != nullptr)
{
check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex == INDEX_NONE);
}
else
{
check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex != INDEX_NONE);
}
check(RemovedPrimitiveSceneInfos.Find(PrimitiveSceneProxy->GetPrimitiveSceneInfo()) == nullptr);
#endif
UpdatedOcclusionBoundsSlacks.Update(PrimitiveSceneProxy, NewSlack);
}
void FScene::UpdatePrimitiveTransform(UPrimitiveComponent* Primitive)
{
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveTransformGT);
SCOPED_NAMED_EVENT(FScene_UpdatePrimitiveTransform, FColor::Yellow);
// Save the world transform for next time the primitive is added to the scene
const float WorldTime = GetWorld()->GetTimeSeconds();
float DeltaTime = WorldTime - Primitive->LastSubmitTime;
if (DeltaTime < -0.0001f || Primitive->LastSubmitTime < 0.0001f)
{
// Time was reset?
Primitive->LastSubmitTime = WorldTime;
}
else if (DeltaTime > 0.0001f)
{
// First call for the new frame?
Primitive->LastSubmitTime = WorldTime;
}
if (Primitive->SceneProxy)
{
// Check if the primitive needs to recreate its proxy for the transform update.
if (Primitive->ShouldRecreateProxyOnUpdateTransform())
{
// Re-add the primitive from scratch to recreate the primitive's proxy.
RemovePrimitive(Primitive);
AddPrimitive(Primitive);
}
else
{
FVector AttachmentRootPosition(0);
AActor* Actor = Primitive->GetAttachmentRootActor();
if (Actor != nullptr)
{
AttachmentRootPosition = Actor->GetActorLocation();
}
struct FPrimitiveUpdateParams
{
FScene* Scene;
FPrimitiveSceneProxy* PrimitiveSceneProxy;
FBoxSphereBounds WorldBounds;
FBoxSphereBounds LocalBounds;
FMatrix LocalToWorld;
TOptional<FTransform> PreviousTransform;
FVector AttachmentRootPosition;
};
FPrimitiveUpdateParams UpdateParams;
UpdateParams.Scene = this;
UpdateParams.PrimitiveSceneProxy = Primitive->SceneProxy;
UpdateParams.WorldBounds = Primitive->Bounds;
UpdateParams.LocalToWorld = Primitive->GetRenderMatrix();
UpdateParams.AttachmentRootPosition = AttachmentRootPosition;
UpdateParams.LocalBounds = Primitive->GetLocalBounds();
UpdateParams.PreviousTransform = FMotionVectorSimulation::Get().GetPreviousTransform(Primitive);
// Help track down primitive with bad bounds way before the it gets to the renderer.
ensureMsgf(!UpdateParams.WorldBounds.BoxExtent.ContainsNaN() && !UpdateParams.WorldBounds.Origin.ContainsNaN() && !FMath::IsNaN(UpdateParams.WorldBounds.SphereRadius) && FMath::IsFinite(UpdateParams.WorldBounds.SphereRadius),
TEXT("NaNs found on Bounds for Primitive %s: Owner: %s, Resource: %s, Level: %s, Origin: %s, BoxExtent: %s, SphereRadius: %f"),
*Primitive->GetName(),
*Primitive->SceneProxy->GetOwnerName().ToString(),
*Primitive->SceneProxy->GetResourceName().ToString(),
*Primitive->SceneProxy->GetLevelName().ToString(),
*UpdateParams.WorldBounds.Origin.ToString(),
*UpdateParams.WorldBounds.BoxExtent.ToString(),
UpdateParams.WorldBounds.SphereRadius
);
bool bPerformUpdate = true;
const bool bAllowSkip = GSkipRedundantTransformUpdate && Primitive->SceneProxy->CanSkipRedundantTransformUpdates();
if (bAllowSkip || GWarningOnRedundantTransformUpdate)
{
if (Primitive->SceneProxy->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->SceneProxy->GetOwnerName().ToString(),
*Primitive->SceneProxy->GetResourceName().ToString(),
*Primitive->SceneProxy->GetLevelName().ToString()
);
}
}
}
if (bPerformUpdate)
{
ENQUEUE_RENDER_COMMAND(UpdateTransformCommand)(
[UpdateParams](FRHICommandListImmediate& RHICmdList)
{
FScopeCycleCounter Context(UpdateParams.PrimitiveSceneProxy->GetStatId());
UpdateParams.Scene->UpdatePrimitiveTransform_RenderThread(
UpdateParams.PrimitiveSceneProxy,
UpdateParams.WorldBounds,
UpdateParams.LocalBounds,
UpdateParams.LocalToWorld,
UpdateParams.AttachmentRootPosition,
UpdateParams.PreviousTransform
);
}
);
}
}
}
else
{
// If the primitive doesn't have a scene info object yet, it must be added from scratch.
AddPrimitive(Primitive);
}
}
void FScene::UpdatePrimitiveOcclusionBoundsSlack(UPrimitiveComponent* Primitive, float NewSlack)
{
if (Primitive->SceneProxy)
{
const FPrimitiveSceneProxy* SceneProxy = Primitive->SceneProxy;
ENQUEUE_RENDER_COMMAND(UpdateOcclusionBoundsSlackCmd)(
[this, SceneProxy, NewSlack](FRHICommandListImmediate&)
{
UpdatePrimitiveOcclusionBoundsSlack_RenderThread(SceneProxy, NewSlack);
});
}
}
void FScene::UpdatePrimitiveInstances(UInstancedStaticMeshComponent* Primitive)
{
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveInstanceGT);
SCOPED_NAMED_EVENT(FScene_UpdatePrimitiveInstance, FColor::Yellow);
if (Primitive->SceneProxy)
{
FUpdateInstanceCommand UpdateParams;
UpdateParams.PrimitiveSceneProxy = Primitive->SceneProxy;
UpdateParams.WorldBounds = Primitive->Bounds;
UpdateParams.LocalBounds = Cast<UPrimitiveComponent>(Primitive)->GetLocalBounds();
// #todo (jnadro) This code should not be dependent on static mesh bounds.
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(Primitive);
UpdateParams.StaticMeshBounds = StaticMeshComponent ? StaticMeshComponent->GetStaticMesh()->GetBounds() : FBoxSphereBounds();
UpdateParams.CmdBuffer = Primitive->InstanceUpdateCmdBuffer; // Copy
ENQUEUE_RENDER_COMMAND(UpdateInstanceCommand)(
[this, UpdateParams = MoveTemp(UpdateParams)](FRHICommandListImmediate& RHICmdList)
{
#if VALIDATE_PRIMITIVE_PACKED_INDEX
if (AddedPrimitiveSceneInfos.Find(UpdateParams.PrimitiveSceneProxy->GetPrimitiveSceneInfo()) != nullptr)
{
check(UpdateParams.PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex == INDEX_NONE);
}
else
{
check(UpdateParams.PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex != INDEX_NONE);
}
check(RemovedPrimitiveSceneInfos.Find(UpdateParams.PrimitiveSceneProxy->GetPrimitiveSceneInfo()) == nullptr);
#endif
FScopeCycleCounter Context(UpdateParams.PrimitiveSceneProxy->GetStatId());
UpdatedInstances.Update(UpdateParams.PrimitiveSceneProxy, UpdateParams);
}
);
}
else
{
// If the primitive doesn't have a scene info object yet, it must be added from scratch.
AddPrimitive(Primitive);
}
}
void FScene::UpdatePrimitiveSelectedState_RenderThread(const FPrimitiveSceneInfo* PrimitiveSceneInfo, bool bIsSelected)
{
check(IsInRenderingThread());
#if WITH_EDITOR
if (PrimitiveSceneInfo)
{
if (PrimitiveSceneInfo->GetIndex() != INDEX_NONE)
{
PrimitivesSelected[PrimitiveSceneInfo->GetIndex()] = bIsSelected;
}
}
#endif // WITH_EDITOR
}
void FScene::UpdatePrimitiveLightingAttachmentRoot(UPrimitiveComponent* Primitive)
{
const UPrimitiveComponent* NewLightingAttachmentRoot = Primitive->GetLightingAttachmentRoot();
if (NewLightingAttachmentRoot == Primitive)
{
NewLightingAttachmentRoot = NULL;
}
FPrimitiveComponentId NewComponentId = NewLightingAttachmentRoot ? NewLightingAttachmentRoot->ComponentId : FPrimitiveComponentId();
if (Primitive->SceneProxy)
{
FPrimitiveSceneProxy* Proxy = Primitive->SceneProxy;
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(UpdatePrimitiveAttachment)(
[Scene, Proxy, NewComponentId](FRHICommandList&)
{
FPrimitiveSceneInfo* PrimitiveInfo = Proxy->GetPrimitiveSceneInfo();
Scene->UpdatedAttachmentRoots.Update(PrimitiveInfo, 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(/*bAllowShrinking=*/ false);
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)
{
SCOPE_CYCLE_COUNTER(STAT_UpdateCustomPrimitiveDataGT);
SCOPED_NAMED_EVENT(FScene_UpdateCustomPrimitiveData, FColor::Yellow);
// This path updates the primitive data directly in the GPUScene.
if(Primitive->SceneProxy)
{
struct FUpdateParams
{
FScene* Scene;
FPrimitiveSceneProxy* PrimitiveSceneProxy;
FCustomPrimitiveData CustomPrimitiveData;
};
FUpdateParams UpdateParams;
UpdateParams.Scene = this;
UpdateParams.PrimitiveSceneProxy = Primitive->SceneProxy;
UpdateParams.CustomPrimitiveData = Primitive->GetCustomPrimitiveData();
ENQUEUE_RENDER_COMMAND(UpdateCustomPrimitiveDataCommand)(
[UpdateParams](FRHICommandListImmediate& RHICmdList)
{
UpdateParams.Scene->UpdatedCustomPrimitiveParams.Update(UpdateParams.PrimitiveSceneProxy, UpdateParams.CustomPrimitiveData);
});
}
}
void FScene::UpdatePrimitiveDistanceFieldSceneData_GameThread(UPrimitiveComponent* Primitive)
{
check(IsInGameThread());
if (Primitive->SceneProxy)
{
Primitive->LastSubmitTime = GetWorld()->GetTimeSeconds();
ENQUEUE_RENDER_COMMAND(UpdatePrimDFSceneDataCmd)(
[this, PrimitiveSceneProxy = Primitive->SceneProxy](FRHICommandList&)
{
if (PrimitiveSceneProxy && PrimitiveSceneProxy->GetPrimitiveSceneInfo())
{
FPrimitiveSceneInfo* Info = PrimitiveSceneProxy->GetPrimitiveSceneInfo();
this->DistanceFieldSceneDataUpdates.FindOrAdd(Info);
}
});
}
}
FPrimitiveSceneInfo* FScene::GetPrimitiveSceneInfo(int32 PrimitiveIndex)
{
if(Primitives.IsValidIndex(PrimitiveIndex))
{
return Primitives[PrimitiveIndex];
}
return NULL;
}
void FScene::RemovePrimitiveSceneInfo_RenderThread(FPrimitiveSceneInfo* PrimitiveSceneInfo)
{
check(IsInRenderingThread());
if (AddedPrimitiveSceneInfos.Remove(PrimitiveSceneInfo))
{
check(PrimitiveSceneInfo->PackedIndex == INDEX_NONE);
UpdatedTransforms.Remove(PrimitiveSceneInfo->Proxy);
UpdatedCustomPrimitiveParams.Remove(PrimitiveSceneInfo->Proxy);
OverridenPreviousTransforms.Remove(PrimitiveSceneInfo);
UpdatedOcclusionBoundsSlacks.Remove(PrimitiveSceneInfo->Proxy);
DistanceFieldSceneDataUpdates.Remove(PrimitiveSceneInfo);
UpdatedAttachmentRoots.Remove(PrimitiveSceneInfo);
{
SCOPED_NAMED_EVENT(FScene_DeletePrimitiveSceneInfo, FColor::Red);
// 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)));
delete PrimitiveSceneInfo->Proxy;
delete PrimitiveSceneInfo;
}
}
else
{
check(PrimitiveSceneInfo->PackedIndex != INDEX_NONE);
check(RemovedPrimitiveSceneInfos.Find(PrimitiveSceneInfo) == nullptr);
RemovedPrimitiveSceneInfos.FindOrAdd(PrimitiveSceneInfo);
}
}
void FScene::RemovePrimitive( UPrimitiveComponent* Primitive )
{
SCOPE_CYCLE_COUNTER(STAT_RemoveScenePrimitiveGT);
SCOPED_NAMED_EVENT(FScene_RemovePrimitive, FColor::Yellow);
FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->SceneProxy;
if(PrimitiveSceneProxy)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveSceneProxy->GetPrimitiveSceneInfo();
// Disassociate the primitive's scene proxy.
Primitive->SceneProxy = NULL;
// Send a command to the rendering thread to remove the primitive from the scene.
FScene* Scene = this;
FThreadSafeCounter* AttachmentCounter = &Primitive->AttachmentCounter;
ENQUEUE_RENDER_COMMAND(FRemovePrimitiveCommand)(
[Scene, PrimitiveSceneInfo, AttachmentCounter](FRHICommandList&)
{
PrimitiveSceneInfo->Proxy->DestroyRenderThreadResources();
Scene->RemovePrimitiveSceneInfo_RenderThread(PrimitiveSceneInfo);
AttachmentCounter->Decrement();
});
}
}
void FScene::ReleasePrimitive( UPrimitiveComponent* PrimitiveComponent )
{
// Send a command to the rendering thread to clean up any state dependent on this primitive
FScene* Scene = this;
FPrimitiveComponentId PrimitiveComponentId = PrimitiveComponent->ComponentId;
ENQUEUE_RENDER_COMMAND(FReleasePrimitiveCommand)(
[Scene, PrimitiveComponentId](FRHICommandList&)
{
// Free the space in the indirect lighting cache
Scene->IndirectLightingCache.ReleasePrimitive(PrimitiveComponentId);
});
}
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 && !IsMobilePlatform(GetShaderPlatform()))
{
// 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);
SCOPED_NAMED_EVENT(FScene_AddLightSceneInfo_RenderThread, FColor::Green);
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)LightSceneInfo->Proxy->GetLightType();
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(GetShadingPath() == 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:
if (!LightSceneInfo->Proxy->HasStaticShadowing() || bUseCSMForDynamicObjects)
{
bScenesPrimitivesNeedStaticMeshElementUpdate = true;
}
}
}
}
// Register rect. light source texture
if (LightType == LightType_Rect)
{
FRectLightSceneProxy* RectProxy = (FRectLightSceneProxy*)LightSceneInfo->Proxy;
RectProxy->AtlasSlotIndex = RectLightAtlas::AddRectLightTexture(RectProxy->SourceTexture);
}
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.
FScene* Scene = this;
FLightSceneInfo* LightSceneInfo = Proxy->LightSceneInfo;
ENQUEUE_RENDER_COMMAND(FAddLightCommand)(
[Scene, LightSceneInfo](FRHICommandListImmediate& RHICmdList)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Scene_AddLight);
FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());
Scene->AddLightSceneInfo_RenderThread(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](FRHICommandListImmediate& RHICmdList)
{
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](FRHICommandListImmediate& RHICmdList)
{
check(!Scene->SkyLightStack.Contains(LightProxy));
Scene->SkyLightStack.Push(LightProxy);
const bool bOriginalHadSkylight = Scene->ShouldRenderSkylightInBasePass(BLEND_Opaque);
// Use the most recently enabled skylight
Scene->SkyLight = LightProxy;
const bool bNewHasSkylight = Scene->ShouldRenderSkylightInBasePass(BLEND_Opaque);
if (bOriginalHadSkylight != bNewHasSkylight)
{
// 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
Scene->bScenesPrimitivesNeedStaticMeshElementUpdate = true;
}
Scene->InvalidatePathTracedOutput();
});
}
void FScene::DisableSkyLight(FSkyLightSceneProxy* LightProxy)
{
check(LightProxy);
NumEnabledSkylights_GameThread--;
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FDisableSkyLightCommand)
([Scene, LightProxy](FRHICommandListImmediate& RHICmdList)
{
const bool bOriginalHadSkylight = Scene->ShouldRenderSkylightInBasePass(BLEND_Opaque);
Scene->SkyLightStack.RemoveSingle(LightProxy);
if (Scene->SkyLightStack.Num() > 0)
{
// Use the most recently enabled skylight
Scene->SkyLight = Scene->SkyLightStack.Last();
}
else
{
Scene->SkyLight = NULL;
}
const bool bNewHasSkylight = Scene->ShouldRenderSkylightInBasePass(BLEND_Opaque);
// Update the scene if we switched skylight enabled states
if (bOriginalHadSkylight != bNewHasSkylight)
{
Scene->bScenesPrimitivesNeedStaticMeshElementUpdate = true;
}
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(TSparseArray<FDeferredDecalProxy*>::TIterator It(Decals); It; ++It)
{
FDeferredDecalProxy* CurrentProxy = *It;
if (CurrentProxy == Proxy)
{
InvalidatePathTracedOutput();
It.RemoveCurrent();
delete CurrentProxy;
break;
}
}
}
}
void FScene::SetPhysicsField(FPhysicsFieldSceneProxy* PhysicsFieldSceneProxy)
{
check(PhysicsFieldSceneProxy);
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FSetPhysicsFieldCommand)(
[Scene, PhysicsFieldSceneProxy](FRHICommandListImmediate& RHICmdList)
{
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)
{
if (!ShaderPrint::IsEnabled())
{
ShaderPrint::SetEnabled(true);
ShaderPrint::SetFontSize(8);
}
ShaderPrint::RequestSpaceForLines(128000);
}
}
void FScene::ResetPhysicsField()
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FResetPhysicsFieldCommand)(
[Scene](FRHICommandListImmediate& RHICmdList)
{
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();
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](FRHICommandListImmediate& RHICmdList)
{
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](FRHICommandListImmediate& RHICmdList)
{
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](FRHICommandListImmediate& RHICmdList)
{
// 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](FRHICommandListImmediate& RHICmdList)
{
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](FRHICommandListImmediate& RHICmdList)
{
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::AddHairStrands(FHairStrandsInstance* Proxy)
{
if (Proxy)
{
check(IsInRenderingThread());
const int32 PackedIndex = HairStrandsSceneData.RegisteredProxies.Add(Proxy);
Proxy->RegisteredIndex = PackedIndex;
}
}
void FScene::RemoveHairStrands(FHairStrandsInstance* Proxy)
{
if (Proxy)
{
check(IsInRenderingThread());
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;
}
}
}
void FScene::GetRectLightAtlasSlot(const FRectLightSceneProxy* Proxy, FLightRenderParameters* Out)
{
if (Proxy)
{
check(IsInRenderingThread());
const RectLightAtlas::FAtlasSlotDesc Slot = RectLightAtlas::GetRectLightAtlasSlot(Proxy->AtlasSlotIndex);
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](FRHICommandListImmediate& 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();
}
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](FRHICommandListImmediate& RHICmdList)
{
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(FUpdateTransformCommand)
([Scene, Proxy, Transform, bUsingPreviewCaptureData](FRHICommandListImmediate& 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();
}
});
}
}
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](FRHICommandListImmediate& RHICmdList)
{
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](FRHICommandListImmediate& RHICmdList)
{
Scene->PrecomputedLightVolumes.Add(Volume);
Scene->IndirectLightingCache.SetLightingCacheDirty(Scene, Volume);
});
}
void FScene::RemovePrecomputedLightVolume(const FPrecomputedLightVolume* Volume)
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(RemoveVolumeCommand)
([Scene, Volume](FRHICommandListImmediate& RHICmdList)
{
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](FRHICommandListImmediate& RHICmdList)
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (Volume && Scene->GetShadingPath() == EShadingPath::Mobile)
{
const FPrecomputedVolumetricLightmapData* VolumeData = Volume->Data;
if (VolumeData && VolumeData->BrickData.LQLightDirection.Data.Num() == 0)
{
FPlatformAtomics::InterlockedIncrement(&Scene->NumUncachedStaticLightingInteractions);
}
}
#endif
Scene->VolumetricLightmapSceneData.AddLevelVolume(Volume, Scene->GetShadingPath(), bIsPersistentLevel);
});
}
void FScene::RemovePrecomputedVolumetricLightmap(const FPrecomputedVolumetricLightmap* Volume)
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(RemoveVolumeCommand)
([Scene, Volume](FRHICommandListImmediate& RHICmdList)
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (Volume && Scene->GetShadingPath() == EShadingPath::Mobile)
{
const FPrecomputedVolumetricLightmapData* VolumeData = Volume->Data;
if (VolumeData && VolumeData->BrickData.LQLightDirection.Data.Num() == 0)
{
FPlatformAtomics::InterlockedDecrement(&Scene->NumUncachedStaticLightingInteractions);
}
}
#endif
Scene->VolumetricLightmapSceneData.RemoveLevelVolume(Volume);
});
}
void FScene::AddRuntimeVirtualTexture(class URuntimeVirtualTextureComponent* Component)
{
if (Component->SceneProxy == nullptr)
{
Component->SceneProxy = new FRuntimeVirtualTextureSceneProxy(Component);
FScene* Scene = this;
FRuntimeVirtualTextureSceneProxy* SceneProxy = Component->SceneProxy;
ENQUEUE_RENDER_COMMAND(AddRuntimeVirtualTextureCommand)(
[Scene, SceneProxy](FRHICommandListImmediate& RHICmdList)
{
Scene->AddRuntimeVirtualTexture_RenderThread(SceneProxy);
Scene->UpdateRuntimeVirtualTextureForAllPrimitives_RenderThread();
});
}
else
{
// This is a component update.
// Store the new FRuntimeVirtualTextureSceneProxy at the same index as the old (to avoid needing to update any associated primitives).
// Defer old proxy deletion to the render thread.
FRuntimeVirtualTextureSceneProxy* SceneProxyToReplace = Component->SceneProxy;
Component->SceneProxy = new FRuntimeVirtualTextureSceneProxy(Component);
FScene* Scene = this;
FRuntimeVirtualTextureSceneProxy* SceneProxy = Component->SceneProxy;
ENQUEUE_RENDER_COMMAND(AddRuntimeVirtualTextureCommand)(
[Scene, SceneProxy, SceneProxyToReplace](FRHICommandListImmediate& RHICmdList)
{
const bool bUpdatePrimitives = SceneProxy->VirtualTexture != SceneProxyToReplace->VirtualTexture;
Scene->UpdateRuntimeVirtualTexture_RenderThread(SceneProxy, SceneProxyToReplace);
if (bUpdatePrimitives)
{
Scene->UpdateRuntimeVirtualTextureForAllPrimitives_RenderThread();
}
});
}
}
void FScene::RemoveRuntimeVirtualTexture(class 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](FRHICommandListImmediate& RHICmdList)
{
Scene->RemoveRuntimeVirtualTexture_RenderThread(SceneProxy);
Scene->UpdateRuntimeVirtualTextureForAllPrimitives_RenderThread();
});
}
}
void FScene::AddRuntimeVirtualTexture_RenderThread(FRuntimeVirtualTextureSceneProxy* SceneProxy)
{
SceneProxy->SceneIndex = RuntimeVirtualTextures.Add(SceneProxy);
const uint8 HideFlagBit = 1 << SceneProxy->SceneIndex;
RuntimeVirtualTexturePrimitiveHideMaskEditor &= ~HideFlagBit;
RuntimeVirtualTexturePrimitiveHideMaskEditor |= (SceneProxy->bHidePrimitivesInEditor ? HideFlagBit : 0);
RuntimeVirtualTexturePrimitiveHideMaskGame &= ~HideFlagBit;
RuntimeVirtualTexturePrimitiveHideMaskGame |= (SceneProxy->bHidePrimitivesInGame ? HideFlagBit : 0);
}
void FScene::UpdateRuntimeVirtualTexture_RenderThread(FRuntimeVirtualTextureSceneProxy* SceneProxy, FRuntimeVirtualTextureSceneProxy* SceneProxyToReplace)
{
const uint8 HideFlagBit = 1 << SceneProxy->SceneIndex;
RuntimeVirtualTexturePrimitiveHideMaskEditor &= ~HideFlagBit;
RuntimeVirtualTexturePrimitiveHideMaskEditor |= (SceneProxy->bHidePrimitivesInEditor ? HideFlagBit : 0);
RuntimeVirtualTexturePrimitiveHideMaskGame &= ~HideFlagBit;
RuntimeVirtualTexturePrimitiveHideMaskGame |= (SceneProxy->bHidePrimitivesInGame ? HideFlagBit : 0);
for (TSparseArray<FRuntimeVirtualTextureSceneProxy*>::TIterator It(RuntimeVirtualTextures); It; ++It)
{
if (*It == SceneProxyToReplace)
{
SceneProxy->SceneIndex = It.GetIndex();
*It = SceneProxy;
delete SceneProxyToReplace;
return;
}
}
// If we get here then we didn't find the object to replace!
check(false);
}
void FScene::RemoveRuntimeVirtualTexture_RenderThread(FRuntimeVirtualTextureSceneProxy* SceneProxy)
{
const uint8 HideFlagBit = 1 << SceneProxy->SceneIndex;
RuntimeVirtualTexturePrimitiveHideMaskEditor &= ~HideFlagBit;
RuntimeVirtualTexturePrimitiveHideMaskGame &= ~HideFlagBit;
RuntimeVirtualTextures.RemoveAt(SceneProxy->SceneIndex);
delete SceneProxy;
}
void FScene::UpdateRuntimeVirtualTextureForAllPrimitives_RenderThread()
{
for (int32 Index = 0; Index < Primitives.Num(); ++Index)
{
if (PrimitiveVirtualTextureFlags[Index].bRenderToVirtualTexture)
{
Primitives[Index]->UpdateRuntimeVirtualTextureFlags();
PrimitiveVirtualTextureFlags[Index] = Primitives[Index]->GetRuntimeVirtualTextureFlags();
}
}
}
uint32 FScene::GetRuntimeVirtualTextureSceneIndex(uint32 ProducerId)
{
checkSlow(IsInRenderingThread());
for (FRuntimeVirtualTextureSceneProxy const* Proxy : RuntimeVirtualTextures)
{
if (Proxy->ProducerId == ProducerId)
{
return Proxy->SceneIndex;
}
}
// Should not get here
check(false);
return 0;
}
void FScene::GetRuntimeVirtualTextureHidePrimitiveMask(uint8& bHideMaskEditor, uint8& bHideMaskGame) const
{
bHideMaskEditor = RuntimeVirtualTexturePrimitiveHideMaskEditor;
bHideMaskGame = RuntimeVirtualTexturePrimitiveHideMaskGame;
}
void FScene::InvalidateRuntimeVirtualTexture(class URuntimeVirtualTextureComponent* Component, FBoxSphereBounds const& WorldBounds)
{
if (Component->SceneProxy != nullptr)
{
FRuntimeVirtualTextureSceneProxy* SceneProxy = Component->SceneProxy;
ENQUEUE_RENDER_COMMAND(RuntimeVirtualTextureComponent_SetDirty)(
[SceneProxy, WorldBounds](FRHICommandList& RHICmdList)
{
SceneProxy->Dirty(WorldBounds);
});
}
}
void FScene::InvalidatePathTracedOutput()
{
// NOTE: this is an atomic, so this function is ok to call from any thread
++PathTracingInvalidationCounter;
}
void FScene::FlushDirtyRuntimeVirtualTextures()
{
checkSlow(IsInRenderingThread());
for (TSparseArray<FRuntimeVirtualTextureSceneProxy*>::TIterator It(RuntimeVirtualTextures); It; ++It)
{
(*It)->FlushDirtyPages();
}
}
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)
{
// Recreate PrimitiveUniformBuffer on the frame after the primitive moved, since it contains PreviousLocalToWorld
VelocityData.PrimitiveSceneInfo->SetNeedsUniformBufferUpdate(true);
}
if (bTrimOld && (InternalFrameIndex - VelocityData.LastFrameUsed) > 10)
{
if (VelocityData.PrimitiveSceneInfo)
{
Scene->GPUScene.AddPrimitiveToUpdate(VelocityData.PrimitiveSceneInfo->GetIndex(), EPrimitiveDirtyState::ChangedOther);
}
It.RemoveCurrent();
}
}
}
void FScene::GetPrimitiveUniformShaderParameters_RenderThread(const FPrimitiveSceneInfo* PrimitiveSceneInfo, bool& bHasPrecomputedVolumetricLightmap, FMatrix& PreviousLocalToWorld, int32& SingleCaptureIndex, bool& bOutputVelocity) const
{
bHasPrecomputedVolumetricLightmap = VolumetricLightmapSceneData.HasData();
bOutputVelocity = VelocityData.GetComponentPreviousLocalToWorld(PrimitiveSceneInfo->PrimitiveComponentId, PreviousLocalToWorld);
if (!bOutputVelocity)
{
PreviousLocalToWorld = PrimitiveSceneInfo->Proxy->GetLocalToWorld();
}
// 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;
}
struct FUpdateLightTransformParameters
{
FMatrix LightToWorld;
FVector4 Position;
};
void FScene::UpdateLightTransform_RenderThread(FLightSceneInfo* LightSceneInfo, const FUpdateLightTransformParameters& Parameters)
{
SCOPE_CYCLE_COUNTER(STAT_UpdateSceneLightTime);
SCOPED_NAMED_EVENT(FScene_UpdateLightTransform_RenderThread, FColor::Yellow);
if( LightSceneInfo && LightSceneInfo->bVisible )
{
// Don't remove directional lights when their transform changes as nothing in RemoveFromScene() depends on their transform
bool bRemove = !(LightSceneInfo->Proxy->GetLightType() == LightType_Directional);
bool bHasId = LightSceneInfo->Id != INDEX_NONE;
if( bRemove )
{
// Remove the light from the scene.
LightSceneInfo->RemoveFromScene();
}
// 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( bHasId )
{
LightSceneInfo->Scene->Lights[LightSceneInfo->Id].Init(LightSceneInfo);
// Don't re-add directional lights when their transform changes as nothing in AddToScene() depends on their transform
if( bRemove )
{
// Add the light to the scene at its new location.
LightSceneInfo->AddToScene();
}
}
}
}
void FScene::UpdateLightTransform(ULightComponent* Light)
{
if(Light->SceneProxy)
{
FUpdateLightTransformParameters Parameters;
Parameters.LightToWorld = Light->GetComponentTransform().ToMatrixNoScale();
Parameters.Position = Light->GetLightPosition();
FScene* Scene = this;
FLightSceneInfo* LightSceneInfo = Light->SceneProxy->GetLightSceneInfo();
ENQUEUE_RENDER_COMMAND(UpdateLightTransform)(
[Scene, LightSceneInfo, Parameters](FRHICommandListImmediate& RHICmdList)
{
FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());
Scene->UpdateLightTransform_RenderThread(LightSceneInfo, Parameters);
});
}
}
/**
* Updates the color and brightness of a light which has already been added to the scene.
*
* @param Light - light component to update
*/
void FScene::UpdateLightColorAndBrightness(ULightComponent* Light)
{
if(Light->SceneProxy)
{
struct FUpdateLightColorParameters
{
FLinearColor NewColor;
float NewIndirectLightingScale;
float NewVolumetricScatteringIntensity;
};
FUpdateLightColorParameters NewParameters;
NewParameters.NewColor = FLinearColor(Light->LightColor) * Light->ComputeLightBrightness();
NewParameters.NewIndirectLightingScale = Light->IndirectLightingIntensity;
NewParameters.NewVolumetricScatteringIntensity = Light->VolumetricScatteringIntensity;
if( Light->bUseTemperature )
{
NewParameters.NewColor *= FLinearColor::MakeFromColorTemperature(Light->Temperature);
}
FScene* Scene = this;
FLightSceneInfo* LightSceneInfo = Light->SceneProxy->GetLightSceneInfo();
ENQUEUE_RENDER_COMMAND(UpdateLightColorAndBrightness)(
[LightSceneInfo, Scene, NewParameters](FRHICommandListImmediate& RHICmdList)
{
if( LightSceneInfo && LightSceneInfo->bVisible )
{
// 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.
Scene->bScenesPrimitivesNeedStaticMeshElementUpdate =
Scene->bScenesPrimitivesNeedStaticMeshElementUpdate ||
( Scene->GetShadingPath() == EShadingPath::Mobile
&& NewParameters.NewColor.IsAlmostBlack() != LightSceneInfo->Proxy->GetColor().IsAlmostBlack() );
// Path Tracing: something about the light has changed, restart path traced accumulation
Scene->InvalidatePathTracedOutput();
LightSceneInfo->Proxy->SetColor(NewParameters.NewColor);
LightSceneInfo->Proxy->IndirectLightingScale = NewParameters.NewIndirectLightingScale;
LightSceneInfo->Proxy->VolumetricScatteringIntensity = NewParameters.NewVolumetricScatteringIntensity;
// Also update the LightSceneInfoCompact
if( LightSceneInfo->Id != INDEX_NONE )
{
Scene->Lights[ LightSceneInfo->Id ].Color = NewParameters.NewColor;
}
}
});
}
}
void FScene::RemoveLightSceneInfo_RenderThread(FLightSceneInfo* LightSceneInfo)
{
SCOPE_CYCLE_COUNTER(STAT_RemoveSceneLightTime);
SCOPED_NAMED_EVENT(FScene_RemoveLightSceneInfo_RenderThread, FColor::Red);
if (LightSceneInfo->bVisible)
{
const bool bDirectionalLight = LightSceneInfo->Proxy->GetLightType() == LightType_Directional;
if (bDirectionalLight)
{
DirectionalLights.Remove(LightSceneInfo);
}
// check SimpleDirectionalLight
if (LightSceneInfo == SimpleDirectionalLight)
{
SimpleDirectionalLight = nullptr;
}
if(GetShadingPath() == 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
if (!LightSceneInfo->Proxy->HasStaticShadowing() || bUseCSMForDynamicObjects)
{
bScenesPrimitivesNeedStaticMeshElementUpdate = true;
}
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();
}
else
{
InvisibleLights.RemoveAt(LightSceneInfo->Id);
}
if (LightSceneInfo->Proxy->GetLightType() == LightType_Rect)
{
const FRectLightSceneProxy* RectProxy = (const FRectLightSceneProxy*)LightSceneInfo->Proxy;
RectLightAtlas::RemoveRectLightTexture(RectProxy->AtlasSlotIndex);
}
// 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 = NULL;
// Send a command to the rendering thread to remove the light from the scene.
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FRemoveLightCommand)(
[Scene, LightSceneInfo](FRHICommandListImmediate& RHICmdList)
{
FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());
Scene->RemoveLightSceneInfo_RenderThread(LightSceneInfo);
});
}
}
void FScene::AddExponentialHeightFog(UExponentialHeightFogComponent* FogComponent)
{
FScene* Scene = this;
FExponentialHeightFogSceneInfo HeightFogSceneInfo = FExponentialHeightFogSceneInfo(FogComponent);
ENQUEUE_RENDER_COMMAND(FAddFogCommand)(
[Scene, HeightFogSceneInfo](FRHICommandListImmediate& RHICmdList)
{
// Create a FExponentialHeightFogSceneInfo for the component in the scene's fog array.
new(Scene->ExponentialFogs) FExponentialHeightFogSceneInfo(HeightFogSceneInfo);
Scene->InvalidatePathTracedOutput();
});
}
void FScene::RemoveExponentialHeightFog(UExponentialHeightFogComponent* FogComponent)
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FRemoveFogCommand)(
[Scene, FogComponent](FRHICommandListImmediate& RHICmdList)
{
// 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].Component == FogComponent)
{
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](FRHICommandListImmediate& RHICmdList)
{
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](FRHICommandListImmediate& RHICmdList)
{
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](FRHICommandListImmediate& RHICmdList)
{
Scene->WindSources.Remove(OldSceneProxy);
delete OldSceneProxy;
});
}
if (WindComponent->IsActive())
{
FWindSourceSceneProxy* const NewSceneProxy = WindComponent->CreateSceneProxy();
WindComponent->SceneProxy = NewSceneProxy;
ENQUEUE_RENDER_COMMAND(FAddWindSourceCommand)(
[Scene = this, NewSceneProxy](FRHICommandListImmediate& RHICmdList)
{
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](FRHICommandListImmediate& RHICmdList)
{
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](FRHICommandListImmediate& 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(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( UPrimitiveComponent* Primitive, TArray<const ULightComponent*>* RelevantLights ) const
{
check( Primitive );
check( RelevantLights );
if( Primitive->SceneProxy )
{
for( const FLightPrimitiveInteraction* Interaction=Primitive->SceneProxy->GetPrimitiveSceneInfo()->LightList; Interaction; Interaction=Interaction->GetNextLight() )
{
RelevantLights->Add( Interaction->GetLight()->Proxy->GetLightComponent() );
}
}
}
/**
* 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 )
{
// Add interacting lights to the array.
const FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FGetRelevantLightsCommand)(
[Scene, Primitive, RelevantLights](FRHICommandListImmediate& RHICmdList)
{
Scene->GetRelevantLights_RenderThread( Primitive, RelevantLights );
});
// 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](FRHICommandListImmediate& RHICmdList)
{
Scene->PrecomputedVisibilityHandler = NewPrecomputedVisibilityHandler;
});
}
void FScene::UpdateStaticDrawLists_RenderThread(FRHICommandListImmediate& RHICmdList)
{
SCOPE_CYCLE_COUNTER(STAT_Scene_UpdateStaticDrawLists_RT);
SCOPED_NAMED_EVENT(FScene_UpdateStaticDrawLists_RenderThread, FColor::Yellow);
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()
{
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FUpdateDrawLists)(
[Scene](FRHICommandListImmediate& RHICmdList)
{
Scene->UpdateStaticDrawLists_RenderThread(RHICmdList);
});
}
void FScene::UpdateCachedRenderStates(FPrimitiveSceneProxy* SceneProxy)
{
check(IsInRenderingThread());
if (SceneProxy->GetPrimitiveSceneInfo())
{
SceneProxy->GetPrimitiveSceneInfo()->BeginDeferredUpdateStaticMeshes();
}
}
#if RHI_RAYTRACING
void FScene::UpdateCachedRayTracingState(FPrimitiveSceneProxy* SceneProxy)
{
check(IsInRenderingThread());
if (SceneProxy->GetPrimitiveSceneInfo())
{
SceneProxy->GetPrimitiveSceneInfo()->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);
// Send a command to the rendering thread to release the scene.
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FReleaseCommand)(
[Scene](FRHICommandListImmediate& RHICmdList)
{
// Flush any remaining batched primitive update commands before deleting the scene.
Scene->UpdateAllPrimitiveSceneInfos(RHICmdList);
delete Scene;
});
}
bool ShouldForceFullDepthPass(EShaderPlatform ShaderPlatform)
{
const bool bNaniteEnabled = UseNanite(ShaderPlatform);
const bool bDBufferAllowed = IsUsingDBuffers(ShaderPlatform);
static const auto StencilLODDitherCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.StencilForLODDither"));
const bool bStencilLODDither = StencilLODDitherCVar->GetValueOnAnyThread() != 0;
static const auto AOComputeCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AmbientOcclusion.Compute"));
const bool bAOCompute = AOComputeCVar->GetValueOnAnyThread() > 0;
const bool bEarlyZMaterialMasking = MaskedInEarlyPass(ShaderPlatform);
// Note: ShouldForceFullDepthPass affects which static draw lists meshes go into, so nothing it depends on can change at runtime, unless you do a FGlobalComponentRecreateRenderStateContext to propagate the cvar change
return bNaniteEnabled || bAOCompute || bDBufferAllowed || bStencilLODDither || bEarlyZMaterialMasking || IsForwardShadingEnabled(ShaderPlatform) || IsUsingSelectiveBasePassOutputs(ShaderPlatform);
}
void FScene::UpdateEarlyZPassMode()
{
checkSlow(IsInGameThread());
DefaultBasePassDepthStencilAccess = FExclusiveDepthStencil::DepthWrite_StencilWrite;
EarlyZPassMode = DDM_NonMaskedOnly;
bEarlyZPassMovable = false;
if (GetShadingPath(GetFeatureLevel()) == EShadingPath::Deferred)
{
// developer override, good for profiling, can be useful as project setting
{
const int32 CVarValue = CVarEarlyZPass.GetValueOnAnyThread();
switch (CVarValue)
{
case 0: EarlyZPassMode = DDM_None; break;
case 1: EarlyZPassMode = DDM_NonMaskedOnly; break;
case 2: EarlyZPassMode = DDM_AllOccluders; break;
case 3: break; // Note: 3 indicates "default behavior" and does not specify an override
}
}
const EShaderPlatform ShaderPlatform = GetFeatureLevelShaderPlatform(FeatureLevel);
if (ShouldForceFullDepthPass(ShaderPlatform))
{
// DBuffer decals and stencil LOD dithering force a full prepass
const bool bDepthPassCanOutputVelocity = FVelocityRendering::DepthPassCanOutputVelocity(FeatureLevel);
EarlyZPassMode = bDepthPassCanOutputVelocity ? DDM_AllOpaqueNoVelocity : DDM_AllOpaque;
bEarlyZPassMovable = bDepthPassCanOutputVelocity ? false : true;
}
if ((EarlyZPassMode == DDM_AllOpaque || EarlyZPassMode == DDM_AllOpaqueNoVelocity)
&& CVarBasePassWriteDepthEvenWithFullPrepass.GetValueOnAnyThread() == 0)
{
DefaultBasePassDepthStencilAccess = FExclusiveDepthStencil::DepthRead_StencilWrite;
}
}
else if (GetShadingPath(GetFeatureLevel()) == EShadingPath::Mobile)
{
EarlyZPassMode = DDM_None;
const EShaderPlatform ShaderPlatform = GetShaderPlatform();
static const bool bMaskedInEarlyPass = MaskedInEarlyPass(ShaderPlatform);
if (bMaskedInEarlyPass)
{
EarlyZPassMode = DDM_MaskedOnly;
}
const bool bIsMobileAmbientOcclusionEnabled = IsMobileAmbientOcclusionEnabled(ShaderPlatform);
const bool bMobileUsesShadowMaskTexture = MobileUsesShadowMaskTexture(ShaderPlatform);
if (bIsMobileAmbientOcclusionEnabled || bMobileUsesShadowMaskTexture)
{
EarlyZPassMode = DDM_AllOpaque;
}
bool bMobileForceFullDepthPrepass = CVarMobileEarlyZPass.GetValueOnAnyThread() == 1;
if (bMobileForceFullDepthPrepass)
{
EarlyZPassMode = DDM_AllOpaque;
}
}
}
void FScene::ConditionalMarkStaticMeshElementsForUpdate()
{
if (bScenesPrimitivesNeedStaticMeshElementUpdate
|| CachedDefaultBasePassDepthStencilAccess != DefaultBasePassDepthStencilAccess)
{
// Mark all primitives as needing an update
// Note: Only visible primitives will actually update their static mesh elements
for (int32 PrimitiveIndex = 0; PrimitiveIndex < Primitives.Num(); PrimitiveIndex++)
{
Primitives[PrimitiveIndex]->BeginDeferredUpdateStaticMeshes();
}
bScenesPrimitivesNeedStaticMeshElementUpdate = false;
CachedDefaultBasePassDepthStencilAccess = DefaultBasePassDepthStencilAccess;
}
}
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()->ComponentForDebuggingOnly->GetFullName());
}
}
for(FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionStaticPrimitiveList();
Interaction;
Interaction = Interaction->GetNextPrimitive())
{
if (Interaction->IsUncachedStaticLighting())
{
bLightHasUnbuiltInteractions = true;
PrimitivesWithUnbuiltInteractions.Add(Interaction->GetPrimitiveSceneInfo()->ComponentForDebuggingOnly->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(FVector InOffset)
{
// Send a command to the rendering thread to shift scene data
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FApplyWorldOffset)(
[Scene, InOffset](FRHICommandListImmediate& RHICmdList)
{
Scene->UpdateAllPrimitiveSceneInfos(RHICmdList);
Scene->ApplyWorldOffset_RenderThread(InOffset);
});
}
void FScene::ApplyWorldOffset_RenderThread(const FVector& InOffset)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_SceneApplyWorldOffset);
SCOPED_NAMED_EVENT(FScene_ApplyWorldOffset_RenderThread, FColor::Yellow);
GPUScene.bUpdateAllPrimitives = true;
// Primitives
checkf(AddedPrimitiveSceneInfos.Num() == 0, TEXT("All primitives found in AddedPrimitiveSceneInfos must have been added to the scene before the world offset is applied"));
for (int32 Idx = 0; Idx < Primitives.Num(); ++Idx)
{
Primitives[Idx]->ApplyWorldOffset(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(FName LevelAddedName, UWorld* InWorld, bool bIsLightingScenario)
{
if (bIsLightingScenario)
{
InWorld->PropagateLightingScenarioChange();
}
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FLevelAddedToWorld)(
[Scene, LevelAddedName](FRHICommandListImmediate& RHICmdList)
{
Scene->UpdateAllPrimitiveSceneInfos(RHICmdList);
Scene->OnLevelAddedToWorld_RenderThread(LevelAddedName);
});
}
void FScene::OnLevelAddedToWorld_RenderThread(FName InLevelName)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FScene::OnLevelAddedToWorld_RenderThread);
// Mark level primitives
TArray<FPrimitiveSceneInfo*> PrimitivesToAdd;
if (const TArray<FPrimitiveSceneInfo*>* LevelPrimitives = PrimitivesNeedingLevelUpdateNotification.Find(InLevelName))
{
for (FPrimitiveSceneInfo* Primitive : *LevelPrimitives)
{
// If the primitive proxy returns true, it needs it's static meshes added to the scene
if (Primitive->Proxy->OnLevelAddedToWorld_RenderThread())
{
// Remove static meshes and cached commands for any primitives that need to be added
Primitive->RemoveStaticMeshes();
PrimitivesToAdd.Add(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();
}
}
}
FPrimitiveSceneInfo::AddStaticMeshes(FRHICommandListExecutor::GetImmediateCommandList(), this, PrimitivesToAdd);
}
void FScene::OnLevelRemovedFromWorld(FName LevelRemovedName, UWorld* InWorld, bool bIsLightingScenario)
{
if (bIsLightingScenario)
{
InWorld->PropagateLightingScenarioChange();
}
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FLevelRemovedFromWorld)(
[Scene, LevelRemovedName](FRHICommandListImmediate& RHICmdList)
{
Scene->UpdateAllPrimitiveSceneInfos(RHICmdList);
Scene->OnLevelRemovedFromWorld_RenderThread(LevelRemovedName);
});
}
void FScene::OnLevelRemovedFromWorld_RenderThread(FName InLevelName)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FScene::OnLevelRemovedFromWorld_RenderThread);
if (TArray<FPrimitiveSceneInfo*>* LevelPrimitives = PrimitivesNeedingLevelUpdateNotification.Find(InLevelName))
{
for (FPrimitiveSceneInfo* Primitive : *LevelPrimitives)
{
Primitive->Proxy->OnLevelRemovedFromWorld_RenderThread();
// 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();
}
}
}
}
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
{
uint32 AHash = A.Proxy->GetTypeHash();
uint32 BHash = B.Proxy->GetTypeHash();
if (AHash == BHash)
{
return A.RegistrationSerialNumber < B.RegistrationSerialNumber;
}
else
{
return AHash < BHash;
}
}
};
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()))
{
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 Experimental::TRobinHoodHashSet<FPrimitiveSceneInfo*>& 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 Experimental::TRobinHoodHashSet<FPrimitiveSceneInfo*>& 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 ValueType>
inline void FScene::UpdateRayTracingGroupBounds_UpdatePrimitives(const Experimental::TRobinHoodHashMap<FPrimitiveSceneProxy*, ValueType>& InUpdatedTransforms)
{
Experimental::TRobinHoodHashSet<FRayTracingCullingGroup*> GroupsToUpdate;
for (const auto& Transform : InUpdatedTransforms)
{
FPrimitiveSceneProxy* const PrimitiveSceneProxy = Transform.Key;
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
static inline bool IsPrimitiveRelevantToPathTracing(FPrimitiveSceneInfo* PrimitiveSceneInfo)
{
#if RHI_RAYTRACING
// returns true if the primitive is likely to impact the path traced image
return PrimitiveSceneInfo->bIsRayTracingRelevant &&
PrimitiveSceneInfo->bIsVisibleInRayTracing &&
PrimitiveSceneInfo->bDrawInGame &&
PrimitiveSceneInfo->bShouldRenderInMainPass;
#else
return false;
#endif
}
void FScene::UpdateAllPrimitiveSceneInfos(FRDGBuilder& GraphBuilder, bool bAsyncCreateLPIs)
{
TRACE_CPUPROFILER_EVENT_SCOPE(Scene::UpdateAllPrimitiveSceneInfos);
SCOPED_NAMED_EVENT(FScene_UpdateAllPrimitiveSceneInfos, FColor::Orange);
SCOPE_CYCLE_COUNTER(STAT_UpdateScenePrimitiveRenderThreadTime);
check(IsInRenderingThread());
FSceneRenderer::WaitForCleanUpTasks(GraphBuilder.RHICmdList);
RDG_EVENT_SCOPE(GraphBuilder, "UpdateAllPrimitiveSceneInfos");
#if RHI_RAYTRACING
UpdateRayTracingGroupBounds_RemovePrimitives(RemovedPrimitiveSceneInfos);
UpdateRayTracingGroupBounds_AddPrimitives(AddedPrimitiveSceneInfos);
#endif
TArray<FPrimitiveSceneInfo*> RemovedLocalPrimitiveSceneInfos;
RemovedLocalPrimitiveSceneInfos.Reserve(RemovedPrimitiveSceneInfos.Num());
for (FPrimitiveSceneInfo* SceneInfo : RemovedPrimitiveSceneInfos)
{
RemovedLocalPrimitiveSceneInfos.Add(SceneInfo);
}
// NOTE: We clear this early because IsPrimitiveBeingRemoved gets called from the CreateLightPrimitiveInteraction (to make sure that old primitives are not accessed)
// we cannot safely kick off the AsyncCreateLightPrimitiveInteractionsTask before the RemovedPrimitiveSceneInfos has been cleared.
RemovedPrimitiveSceneInfos.Empty();
RemovedLocalPrimitiveSceneInfos.Sort(FPrimitiveArraySortKey());
TArray<FVirtualShadowMapArrayCacheManager*, SceneRenderingAllocator> VirtualShadowCacheManagers;
GetAllVirtualShadowMapCacheManagers(VirtualShadowCacheManagers);
for (FVirtualShadowMapArrayCacheManager* CacheManager : VirtualShadowCacheManagers)
{
FVirtualShadowMapArrayCacheManager::FInvalidatingPrimitiveCollector InvalidatingPrimitiveCollector(CacheManager);
// All removed primitives must invalidate their footprints in the VSM before leaving
for (const FPrimitiveSceneInfo* PrimitiveSceneInfo : RemovedLocalPrimitiveSceneInfos)
{
InvalidatingPrimitiveCollector.Add(PrimitiveSceneInfo);
}
// All updated instances must also before moving or re-allocating (TODO: filter out only those actually updated)
for (const auto& Instance : UpdatedInstances)
{
InvalidatingPrimitiveCollector.Add(Instance.Key->GetPrimitiveSceneInfo());
}
// As must all primitive updates,
for (const auto& Transform : UpdatedTransforms)
{
InvalidatingPrimitiveCollector.Add(Transform.Key->GetPrimitiveSceneInfo());
}
CacheManager->ProcessRemovedOrUpdatedPrimitives(GraphBuilder, GPUScene, InvalidatingPrimitiveCollector);
}
TArray<FPrimitiveSceneInfo*> AddedLocalPrimitiveSceneInfos;
AddedLocalPrimitiveSceneInfos.Reserve(AddedPrimitiveSceneInfos.Num());
for (FPrimitiveSceneInfo* SceneInfo : AddedPrimitiveSceneInfos)
{
AddedLocalPrimitiveSceneInfos.Add(SceneInfo);
}
AddedLocalPrimitiveSceneInfos.Sort(FPrimitiveArraySortKey());
TSet<FPrimitiveSceneInfo*> DeletedSceneInfos;
DeletedSceneInfos.Reserve(RemovedLocalPrimitiveSceneInfos.Num());
TArray<int32> RemovedPrimitiveIndices;
RemovedPrimitiveIndices.SetNumUninitialized(RemovedLocalPrimitiveSceneInfos.Num());
GPUScene.ResizeDirtyState(Primitives.Num());
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RemovePrimitiveSceneInfos);
SCOPED_NAMED_EVENT(FScene_RemovePrimitiveSceneInfos, FColor::Red);
SCOPE_CYCLE_COUNTER(STAT_RemoveScenePrimitiveTime);
GPUScene.BeginDeferAllocatorMerges();
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : RemovedLocalPrimitiveSceneInfos)
{
// clear it up, parent is getting removed
SceneLODHierarchy.UpdateNodeSceneInfo(PrimitiveSceneInfo->PrimitiveComponentId, nullptr);
}
while (RemovedLocalPrimitiveSceneInfos.Num())
{
int32 StartIndex = RemovedLocalPrimitiveSceneInfos.Num() - 1;
SIZE_T InsertProxyHash = RemovedLocalPrimitiveSceneInfos[StartIndex]->Proxy->GetTypeHash();
while (StartIndex > 0 && RemovedLocalPrimitiveSceneInfos[StartIndex - 1]->Proxy->GetTypeHash() == InsertProxyHash)
{
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 == InsertProxyHash)
{
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;
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(PrimitiveVirtualTextureFlags, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveVirtualTextureLod, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveOcclusionBounds, DestIndex, SourceIndex);
#if WITH_EDITOR
TBitArraySwapElements(PrimitivesSelected, DestIndex, SourceIndex);
#endif
#if RHI_RAYTRACING
TArraySwapElements(PrimitiveRayTracingFlags, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveRayTracingGroupIds, DestIndex, SourceIndex);
#endif
TBitArraySwapElements(PrimitivesNeedingStaticMeshUpdate, DestIndex, SourceIndex);
for (TMap<int32, TArray<FCachedShadowMapData>>::TIterator CachedShadowMapIt(CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt)
{
TArray<FCachedShadowMapData>& ShadowMapDatas = CachedShadowMapIt.Value();
for (auto& ShadowMapData : ShadowMapDatas)
{
if (ShadowMapData.StaticShadowSubjectMap.Num() > 0)
{
TBitArraySwapElements(ShadowMapData.StaticShadowSubjectMap, DestIndex, SourceIndex);
}
}
}
GPUScene.RecordPrimitiveIdSwap(DestIndex, SourceIndex);
#if RHI_RAYTRACING
// Update cached PrimitiveIndex after an index swap
Primitives[SourceIndex]->CachedRayTracingInstance.DefaultUserData = SourceIndex;
Primitives[DestIndex]->CachedRayTracingInstance.DefaultUserData = DestIndex;
#endif
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, false);
PrimitiveTransforms.RemoveAt(SourceIndex, RemoveCount, false);
PrimitiveSceneProxies.RemoveAt(SourceIndex, RemoveCount, false);
PrimitiveBounds.RemoveAt(SourceIndex, RemoveCount, false);
PrimitiveFlagsCompact.RemoveAt(SourceIndex, RemoveCount, false);
PrimitiveVisibilityIds.RemoveAt(SourceIndex, RemoveCount, false);
PrimitiveOctreeIndex.RemoveAt(SourceIndex, RemoveCount, false);
PrimitiveOcclusionFlags.RemoveAt(SourceIndex, RemoveCount, false);
PrimitiveComponentIds.RemoveAt(SourceIndex, RemoveCount, false);
PrimitiveVirtualTextureFlags.RemoveAt(SourceIndex, RemoveCount, false);
PrimitiveVirtualTextureLod.RemoveAt(SourceIndex, RemoveCount, false);
PrimitiveOcclusionBounds.RemoveAt(SourceIndex, RemoveCount, false);
#if WITH_EDITOR
PrimitivesSelected.RemoveAt(SourceIndex, RemoveCount);
#endif
#if RHI_RAYTRACING
PrimitiveRayTracingFlags.RemoveAt(SourceIndex, RemoveCount);
PrimitiveRayTracingGroupIds.RemoveAt(SourceIndex, RemoveCount);
#endif
PrimitivesNeedingStaticMeshUpdate.RemoveAt(SourceIndex, RemoveCount);
CheckPrimitiveArrays();
for (int32 RemoveIndex = StartIndex; RemoveIndex < RemovedLocalPrimitiveSceneInfos.Num(); RemoveIndex++)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = RemovedLocalPrimitiveSceneInfos[RemoveIndex];
FScopeCycleCounter Context(PrimitiveSceneInfo->Proxy->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();
// Update the primitive that was swapped to this index
GPUScene.AddPrimitiveToUpdate(PrimitiveIndex, EPrimitiveDirtyState::Removed);
DistanceFieldSceneData.RemovePrimitive(PrimitiveSceneInfo);
LumenSceneData->RemovePrimitive(PrimitiveSceneInfo, PrimitiveIndex);
DeletedSceneInfos.Add(PrimitiveSceneInfo);
for (const FLightSceneInfo* LightSceneInfo : DirectionalLights)
{
TArray<FCachedShadowMapData>* CachedShadowMapDatas = GetCachedShadowMapDatas(LightSceneInfo->Id);
if (CachedShadowMapDatas)
{
for (auto& CachedShadowMapData : *CachedShadowMapDatas)
{
if (CachedShadowMapData.StaticShadowSubjectMap[PrimitiveIndex] == true)
{
CachedShadowMapData.InvalidateCachedShadow();
}
}
}
}
PersistentPrimitiveIdAllocator.Free(PrimitiveSceneInfo->PersistentIndex.Index);
}
for (TMap<int32, TArray<FCachedShadowMapData>>::TIterator CachedShadowMapIt(CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt)
{
TArray<FCachedShadowMapData>& ShadowMapDatas = CachedShadowMapIt.Value();
for (auto& ShadowMapData : ShadowMapDatas)
{
if (ShadowMapData.StaticShadowSubjectMap.Num() > 0)
{
ShadowMapData.StaticShadowSubjectMap.RemoveAt(SourceIndex, RemoveCount);
check(Primitives.Num() == ShadowMapData.StaticShadowSubjectMap.Num());
}
}
}
RemovedLocalPrimitiveSceneInfos.RemoveAt(StartIndex, RemovedLocalPrimitiveSceneInfos.Num() - StartIndex, false);
}
GPUScene.EndDeferAllocatorMerges();
}
bool bNeedPathTracedInvalidation = false;
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(AddPrimitiveSceneInfos);
SCOPED_NAMED_EVENT(FScene_AddPrimitiveSceneInfos, FColor::Green);
SCOPE_CYCLE_COUNTER(STAT_AddScenePrimitiveRenderThreadTime);
#if ENABLE_LOG_PRIMITIVE_INSTANCE_ID_STATS_TO_CSV
int32 PersistentPrimitiveFreeListSizeBeforeConsolidate = PersistentPrimitiveIdAllocator.GetNumFreeSpans();
int32 PersistentPrimitivePendingFreeListSizeBeforeConsolidate = PersistentPrimitiveIdAllocator.GetNumPendingFreeSpans();
#endif
PersistentPrimitiveIdAllocator.Consolidate();
if (AddedLocalPrimitiveSceneInfos.Num())
{
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());
PrimitiveVirtualTextureFlags.Reserve(PrimitiveVirtualTextureFlags.Num() + AddedLocalPrimitiveSceneInfos.Num());
PrimitiveVirtualTextureLod.Reserve(PrimitiveVirtualTextureLod.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());
PrimitiveRayTracingGroupIds.Reserve(PrimitiveRayTracingGroupIds.Num() + AddedLocalPrimitiveSceneInfos.Num());
#endif
PrimitivesNeedingStaticMeshUpdate.Reserve(PrimitivesNeedingStaticMeshUpdate.Num() + AddedLocalPrimitiveSceneInfos.Num());
for (TMap<int32, TArray<FCachedShadowMapData>>::TIterator CachedShadowMapIt(CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt)
{
TArray<FCachedShadowMapData>& ShadowMapDatas = CachedShadowMapIt.Value();
for (auto& ShadowMapData : ShadowMapDatas)
{
if (ShadowMapData.StaticShadowSubjectMap.Num() > 0)
{
ShadowMapData.StaticShadowSubjectMap.Reserve(ShadowMapData.StaticShadowSubjectMap.Num() + AddedLocalPrimitiveSceneInfos.Num());
}
}
}
}
while (AddedLocalPrimitiveSceneInfos.Num())
{
int32 StartIndex = AddedLocalPrimitiveSceneInfos.Num() - 1;
SIZE_T InsertProxyHash = AddedLocalPrimitiveSceneInfos[StartIndex]->Proxy->GetTypeHash();
while (StartIndex > 0 && AddedLocalPrimitiveSceneInfos[StartIndex - 1]->Proxy->GetTypeHash() == InsertProxyHash)
{
StartIndex--;
}
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();
PrimitiveVirtualTextureFlags.AddUninitialized();
PrimitiveVirtualTextureLod.AddUninitialized();
PrimitiveOcclusionBounds.AddUninitialized();
#if WITH_EDITOR
PrimitivesSelected.Add(PrimitiveSceneInfo->Proxy->IsSelected());
#endif
#if RHI_RAYTRACING
PrimitiveRayTracingFlags.AddZeroed();
PrimitiveRayTracingGroupIds.Add(Experimental::FHashElementId());
#endif
PrimitivesNeedingStaticMeshUpdate.Add(false);
for (TMap<int32, TArray<FCachedShadowMapData>>::TIterator CachedShadowMapIt(CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt)
{
TArray<FCachedShadowMapData>& ShadowMapDatas = CachedShadowMapIt.Value();
for (auto& ShadowMapData : ShadowMapDatas)
{
if (ShadowMapData.StaticShadowSubjectMap.Num() > 0)
{
ShadowMapData.StaticShadowSubjectMap.Add(false);
}
}
}
const int32 SourceIndex = PrimitiveSceneProxies.Num() - 1;
PrimitiveSceneInfo->PackedIndex = SourceIndex;
checkSlow(PrimitiveSceneInfo->PersistentIndex.Index == INDEX_NONE);
PrimitiveSceneInfo->PersistentIndex = FPersistentPrimitiveIndex{ PersistentPrimitiveIdAllocator.Allocate() };
GPUScene.AddPrimitiveToUpdate(SourceIndex, EPrimitiveDirtyState::AddedMask);
}
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 == InsertProxyHash)
{
EntryFound = true;
break;
}
}
//new type encountered
if (EntryFound == false)
{
BroadIndex = TypeOffsetTable.Num();
if (BroadIndex)
{
FTypeOffsetTableEntry Entry = TypeOffsetTable[BroadIndex - 1];
//adding to the end of the list and offset of the tail (will will be incremented once during the while loop)
TypeOffsetTable.Push(FTypeOffsetTableEntry(InsertProxyHash, Entry.Offset));
}
else
{
//starting with an empty list and offset zero (will will be incremented once during the while loop)
TypeOffsetTable.Push(FTypeOffsetTableEntry(InsertProxyHash, 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;
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(PrimitiveVirtualTextureFlags, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveVirtualTextureLod, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveOcclusionBounds, DestIndex, SourceIndex);
#if WITH_EDITOR
TBitArraySwapElements(PrimitivesSelected, DestIndex, SourceIndex);
#endif
#if RHI_RAYTRACING
TArraySwapElements(PrimitiveRayTracingFlags, DestIndex, SourceIndex);
TArraySwapElements(PrimitiveRayTracingGroupIds, DestIndex, SourceIndex);
#endif
TBitArraySwapElements(PrimitivesNeedingStaticMeshUpdate, DestIndex, SourceIndex);
for (TMap<int32, TArray<FCachedShadowMapData>>::TIterator CachedShadowMapIt(CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt)
{
TArray<FCachedShadowMapData>& ShadowMapDatas = CachedShadowMapIt.Value();
for (auto& ShadowMapData : ShadowMapDatas)
{
if (ShadowMapData.StaticShadowSubjectMap.Num() > 0)
{
TBitArraySwapElements(ShadowMapData.StaticShadowSubjectMap, DestIndex, SourceIndex);
}
}
}
GPUScene.RecordPrimitiveIdSwap(DestIndex, SourceIndex);
#if RHI_RAYTRACING
// Update cached PrimitiveIndex after an index swap
Primitives[SourceIndex]->CachedRayTracingInstance.DefaultUserData = SourceIndex;
Primitives[DestIndex]->CachedRayTracingInstance.DefaultUserData = DestIndex;
#endif
}
}
}
}
CheckPrimitiveArrays();
#if ENABLE_LOG_PRIMITIVE_INSTANCE_ID_STATS_TO_CSV
UpdatePrimitiveAllocatorStats(Primitives.Num(), PersistentPrimitiveIdAllocator.GetMaxSize(), PersistentPrimitiveIdAllocator.GetSparselyAllocatedSize(), PersistentPrimitiveIdAllocator.GetNumFreeSpans(), PersistentPrimitiveFreeListSizeBeforeConsolidate, PersistentPrimitivePendingFreeListSizeBeforeConsolidate, GPUScene.GetNumInstances(), GPUScene.GetInstanceSceneDataAllocator().GetSparselyAllocatedSize());
#endif
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();
}
FPrimitiveSceneInfo::AllocateGPUSceneInstances(this, TArrayView<FPrimitiveSceneInfo*>(&AddedLocalPrimitiveSceneInfos[StartIndex], AddedLocalPrimitiveSceneInfos.Num() - StartIndex));
{
SCOPED_NAMED_EVENT(FScene_AddPrimitiveSceneInfoToScene, FColor::Turquoise);
if (GIsEditor)
{
FPrimitiveSceneInfo::AddToScene(GraphBuilder.RHICmdList, this, TArrayView<FPrimitiveSceneInfo*>(&AddedLocalPrimitiveSceneInfos[StartIndex], AddedLocalPrimitiveSceneInfos.Num() - StartIndex), true);
}
else
{
const bool bAddToDrawLists = !(CVarDoLazyStaticMeshUpdate.GetValueOnRenderThread());
if (bAddToDrawLists)
{
FPrimitiveSceneInfo::AddToScene(GraphBuilder.RHICmdList, this, TArrayView<FPrimitiveSceneInfo*>(&AddedLocalPrimitiveSceneInfos[StartIndex], AddedLocalPrimitiveSceneInfos.Num() - StartIndex), true, true, bAsyncCreateLPIs);
}
else
{
FPrimitiveSceneInfo::AddToScene(GraphBuilder.RHICmdList, this, TArrayView<FPrimitiveSceneInfo*>(&AddedLocalPrimitiveSceneInfos[StartIndex], AddedLocalPrimitiveSceneInfos.Num() - StartIndex), true, false, bAsyncCreateLPIs);
for (int AddIndex = StartIndex; AddIndex < AddedLocalPrimitiveSceneInfos.Num(); AddIndex++)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = AddedLocalPrimitiveSceneInfos[AddIndex];
PrimitiveSceneInfo->BeginDeferredUpdateStaticMeshes();
}
}
}
}
for (int AddIndex = StartIndex; AddIndex < AddedLocalPrimitiveSceneInfos.Num(); AddIndex++)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = AddedLocalPrimitiveSceneInfos[AddIndex];
int32 PrimitiveIndex = PrimitiveSceneInfo->PackedIndex;
if (ShouldPrimitiveOutputVelocity(PrimitiveSceneInfo->Proxy, 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);
LumenSceneData->AddPrimitive(PrimitiveSceneInfo);
// Flush virtual textures touched by primitive
PrimitiveSceneInfo->FlushRuntimeVirtualTexture();
// Set LOD parent information if valid
PrimitiveSceneInfo->LinkLODParentComponent();
// Update scene LOD tree
SceneLODHierarchy.UpdateNodeSceneInfo(PrimitiveSceneInfo->PrimitiveComponentId, PrimitiveSceneInfo);
bNeedPathTracedInvalidation = bNeedPathTracedInvalidation || IsPrimitiveRelevantToPathTracing(PrimitiveSceneInfo);
}
AddedLocalPrimitiveSceneInfos.RemoveAt(StartIndex, AddedLocalPrimitiveSceneInfos.Num() - StartIndex, false);
}
}
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(UpdatePrimitiveTransform);
SCOPED_NAMED_EVENT(FScene_AddPrimitiveSceneInfos, FColor::Yellow);
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveTransformRenderThreadTime);
TArray<FPrimitiveSceneInfo*> UpdatedSceneInfosWithStaticDrawListUpdate;
TArray<FPrimitiveSceneInfo*> UpdatedSceneInfosWithoutStaticDrawListUpdate;
UpdatedSceneInfosWithStaticDrawListUpdate.Reserve(UpdatedTransforms.Num());
UpdatedSceneInfosWithoutStaticDrawListUpdate.Reserve(UpdatedTransforms.Num());
for (const auto& Transform : UpdatedTransforms)
{
FPrimitiveSceneProxy* PrimitiveSceneProxy = Transform.Key;
if (DeletedSceneInfos.Contains(PrimitiveSceneProxy->GetPrimitiveSceneInfo()))
{
continue;
}
check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex != INDEX_NONE);
const FBoxSphereBounds& WorldBounds = Transform.Value.WorldBounds;
const FBoxSphereBounds& LocalBounds = Transform.Value.LocalBounds;
const FMatrix& LocalToWorld = Transform.Value.LocalToWorld;
const FVector& AttachmentRootPosition = Transform.Value.AttachmentRootPosition;
FScopeCycleCounter Context(PrimitiveSceneProxy->GetStatId());
FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveSceneProxy->GetPrimitiveSceneInfo();
const bool bUpdateStaticDrawLists = !PrimitiveSceneProxy->StaticElementsAlwaysUseProxyPrimitiveUniformBuffer();
if (bUpdateStaticDrawLists)
{
UpdatedSceneInfosWithStaticDrawListUpdate.Push(PrimitiveSceneInfo);
}
else
{
UpdatedSceneInfosWithoutStaticDrawListUpdate.Push(PrimitiveSceneInfo);
}
PrimitiveSceneInfo->FlushRuntimeVirtualTexture();
// 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 (ShouldPrimitiveOutputVelocity(PrimitiveSceneInfo->Proxy, GetShaderPlatform()))
{
PrimitiveSceneInfo->bRegisteredWithVelocityData = true;
VelocityData.UpdateTransform(PrimitiveSceneInfo, LocalToWorld, PrimitiveSceneProxy->GetLocalToWorld());
}
bNeedPathTracedInvalidation = bNeedPathTracedInvalidation || (IsPrimitiveRelevantToPathTracing(PrimitiveSceneInfo) &&
!PrimitiveTransforms[PrimitiveSceneInfo->PackedIndex].Equals(LocalToWorld, SMALL_NUMBER));
// Update the primitive transform.
PrimitiveSceneProxy->SetTransform(LocalToWorld, WorldBounds, LocalBounds, AttachmentRootPosition);
PrimitiveTransforms[PrimitiveSceneInfo->PackedIndex] = LocalToWorld;
if (!RHISupportsVolumeTextures(GetFeatureLevel())
&& (PrimitiveSceneProxy->IsMovable() || PrimitiveSceneProxy->NeedsUnbuiltPreviewLighting() || PrimitiveSceneProxy->GetLightmapType() == ELightmapType::ForceVolumetric))
{
PrimitiveSceneInfo->MarkIndirectLightingCacheBufferDirty();
}
GPUScene.AddPrimitiveToUpdate(PrimitiveSceneInfo->PackedIndex, EPrimitiveDirtyState::ChangedTransform);
DistanceFieldSceneData.UpdatePrimitive(PrimitiveSceneInfo);
LumenSceneData->UpdatePrimitive(PrimitiveSceneInfo);
#if RHI_RAYTRACING
PrimitiveSceneInfo->UpdateCachedRayTracingInstanceWorldBounds(LocalToWorld);
#endif
// 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
// Re-add the primitive to the scene with the new transform.
if (UpdatedSceneInfosWithStaticDrawListUpdate.Num() > 0)
{
FPrimitiveSceneInfo::AddToScene(GraphBuilder.RHICmdList, this, UpdatedSceneInfosWithStaticDrawListUpdate, true, true, bAsyncCreateLPIs);
}
if (UpdatedSceneInfosWithoutStaticDrawListUpdate.Num() > 0)
{
FPrimitiveSceneInfo::AddToScene(GraphBuilder.RHICmdList, this, UpdatedSceneInfosWithoutStaticDrawListUpdate, false, true, bAsyncCreateLPIs);
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : UpdatedSceneInfosWithoutStaticDrawListUpdate)
{
PrimitiveSceneInfo->FlushRuntimeVirtualTexture();
}
}
for (const auto& Transform : OverridenPreviousTransforms)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = Transform.Key;
VelocityData.OverridePreviousTransform(PrimitiveSceneInfo->PrimitiveComponentId, Transform.Value);
}
}
// handle scene changes
for (FVirtualShadowMapArrayCacheManager* CacheManager : VirtualShadowCacheManagers)
{
CacheManager->OnSceneChange();
}
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(UpdatePrimitiveInstances);
SCOPED_NAMED_EVENT(FScene_UpdatePrimitiveInstances, FColor::Emerald);
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveInstanceRenderThreadTime);
TArray<FPrimitiveSceneInfo*> UpdatedSceneInfosWithStaticDrawListUpdate;
UpdatedSceneInfosWithStaticDrawListUpdate.Reserve(UpdatedInstances.Num());
TArray<FPrimitiveSceneInfo*> UpdatedSceneInfosWithoutStaticDrawListUpdate;
UpdatedSceneInfosWithoutStaticDrawListUpdate.Reserve(UpdatedInstances.Num());
#if RHI_RAYTRACING
TArray<FPrimitiveSceneInfo*> RayTracingPrimitivesToUpdate;
RayTracingPrimitivesToUpdate.Reserve(UpdatedInstances.Num());
#endif
for (const auto& UpdateInstance : UpdatedInstances)
{
FPrimitiveSceneProxy* PrimitiveSceneProxy = UpdateInstance.Key;
if (DeletedSceneInfos.Contains(PrimitiveSceneProxy->GetPrimitiveSceneInfo()))
{
continue;
}
check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex != INDEX_NONE);
FScopeCycleCounter Context(PrimitiveSceneProxy->GetStatId());
FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveSceneProxy->GetPrimitiveSceneInfo();
PrimitiveSceneInfo->FlushRuntimeVirtualTexture();
const bool bUpdateStaticDrawLists = !PrimitiveSceneProxy->StaticElementsAlwaysUseProxyPrimitiveUniformBuffer();
// If we recorded no adds or removes the instance count has stayed the same. Therefore the cached mesh draw commands do not
// need to be updated. In situations where the instance count changes the mesh draw command stores the instance count which
// would need to be updated.
const bool bInstanceCountChanged = (UpdateInstance.Value.CmdBuffer.NumAdds > 0) || (UpdateInstance.Value.CmdBuffer.NumRemoves > 0);
if (bUpdateStaticDrawLists || bInstanceCountChanged)
{
UpdatedSceneInfosWithStaticDrawListUpdate.Push(PrimitiveSceneInfo);
PrimitiveSceneInfo->RemoveFromScene(true);
}
else
{
#if RHI_RAYTRACING
RayTracingPrimitivesToUpdate.Add(PrimitiveSceneInfo);
#endif
UpdatedSceneInfosWithoutStaticDrawListUpdate.Push(PrimitiveSceneInfo);
PrimitiveSceneInfo->RemoveFromScene(false);
}
// Update the Proxy's data.
PrimitiveSceneProxy->UpdateInstances_RenderThread(UpdateInstance.Value.CmdBuffer, UpdateInstance.Value.WorldBounds, UpdateInstance.Value.LocalBounds, UpdateInstance.Value.StaticMeshBounds);
if (!RHISupportsVolumeTextures(GetFeatureLevel())
&& (PrimitiveSceneProxy->IsMovable() || PrimitiveSceneProxy->NeedsUnbuiltPreviewLighting() || PrimitiveSceneProxy->GetLightmapType() == ELightmapType::ForceVolumetric))
{
PrimitiveSceneInfo->MarkIndirectLightingCacheBufferDirty();
}
if (UpdateInstance.Value.CmdBuffer.NumAdds > 0 || UpdateInstance.Value.CmdBuffer.NumRemoves > 0)
{
PrimitiveSceneInfo->FreeGPUSceneInstances();
FPrimitiveSceneInfo::AllocateGPUSceneInstances(this, MakeArrayView(&PrimitiveSceneInfo, 1));
DistanceFieldSceneData.RemovePrimitive(PrimitiveSceneInfo);
DistanceFieldSceneData.AddPrimitive(PrimitiveSceneInfo);
LumenSceneData->RemovePrimitive(PrimitiveSceneInfo, PrimitiveSceneInfo->GetIndex());
LumenSceneData->AddPrimitive(PrimitiveSceneInfo);
}
else
{
GPUScene.AddPrimitiveToUpdate(PrimitiveSceneInfo->PackedIndex, EPrimitiveDirtyState::ChangedAll);
DistanceFieldSceneData.UpdatePrimitive(PrimitiveSceneInfo);
LumenSceneData->UpdatePrimitive(PrimitiveSceneInfo);
}
bNeedPathTracedInvalidation = bNeedPathTracedInvalidation || IsPrimitiveRelevantToPathTracing(PrimitiveSceneInfo);
}
#if RHI_RAYTRACING
{
UpdateRayTracingGroupBounds_UpdatePrimitives(UpdatedInstances);
}
#endif
// Re-add the primitive to the scene with the new transform.
if (UpdatedSceneInfosWithStaticDrawListUpdate.Num() > 0)
{
FPrimitiveSceneInfo::AddToScene(GraphBuilder.RHICmdList, this, UpdatedSceneInfosWithStaticDrawListUpdate, true, true, bAsyncCreateLPIs);
}
if (UpdatedSceneInfosWithoutStaticDrawListUpdate.Num() > 0)
{
FPrimitiveSceneInfo::AddToScene(GraphBuilder.RHICmdList, this, UpdatedSceneInfosWithoutStaticDrawListUpdate, false, true, bAsyncCreateLPIs);
#if RHI_RAYTRACING
FPrimitiveSceneInfo::UpdateCachedRayTracingInstances(this, RayTracingPrimitivesToUpdate);
#endif
}
}
for (const auto& Attachments : UpdatedAttachmentRoots)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = Attachments.Key;
if (DeletedSceneInfos.Contains(PrimitiveSceneInfo))
{
continue;
}
PrimitiveSceneInfo->UnlinkAttachmentGroup();
PrimitiveSceneInfo->LightingAttachmentRoot = Attachments.Value;
PrimitiveSceneInfo->LinkAttachmentGroup();
}
for (const auto& CustomParams : UpdatedCustomPrimitiveParams)
{
FPrimitiveSceneProxy* PrimitiveSceneProxy = CustomParams.Key;
if (DeletedSceneInfos.Contains(PrimitiveSceneProxy->GetPrimitiveSceneInfo()))
{
continue;
}
FScopeCycleCounter Context(PrimitiveSceneProxy->GetStatId());
PrimitiveSceneProxy->CustomPrimitiveData = CustomParams.Value;
// Make sure the uniform buffer is updated before rendering
PrimitiveSceneProxy->GetPrimitiveSceneInfo()->SetNeedsUniformBufferUpdate(true);
}
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : DistanceFieldSceneDataUpdates)
{
if (DeletedSceneInfos.Contains(PrimitiveSceneInfo))
{
continue;
}
DistanceFieldSceneData.UpdatePrimitive(PrimitiveSceneInfo);
}
for (const auto& OccSlackDelta : UpdatedOcclusionBoundsSlacks)
{
const FPrimitiveSceneProxy* SceneProxy = OccSlackDelta.Key;
const FPrimitiveSceneInfo* SceneInfo = SceneProxy->GetPrimitiveSceneInfo();
if (DeletedSceneInfos.Contains(SceneInfo))
{
continue;
}
FBoxSphereBounds NewOccBounds;
if (SceneProxy->HasCustomOcclusionBounds())
{
NewOccBounds = SceneProxy->GetCustomOcclusionBounds();
}
else
{
NewOccBounds = SceneProxy->GetBounds();
}
PrimitiveOcclusionBounds[SceneInfo->PackedIndex] = NewOccBounds.ExpandBy(OCCLUSION_SLOP + OccSlackDelta.Value);
}
{
SCOPED_NAMED_EVENT(FScene_DeletePrimitiveSceneInfo, FColor::Red);
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : DeletedSceneInfos)
{
bNeedPathTracedInvalidation = bNeedPathTracedInvalidation || IsPrimitiveRelevantToPathTracing(PrimitiveSceneInfo);
// 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;
}
}
if (bNeedPathTracedInvalidation)
{
InvalidatePathTracedOutput();
}
UpdatedAttachmentRoots.Empty();
UpdatedTransforms.Empty();
UpdatedInstances.Empty();
UpdatedCustomPrimitiveParams.Empty();
OverridenPreviousTransforms.Empty();
UpdatedOcclusionBoundsSlacks.Empty();
DistanceFieldSceneDataUpdates.Empty();
AddedPrimitiveSceneInfos.Empty();
}
void FScene::CreateLightPrimitiveInteractionsForPrimitive(FPrimitiveSceneInfo* PrimitiveInfo, bool bAsyncCreateLPIs)
{
FPrimitiveSceneProxy* Proxy = PrimitiveInfo->Proxy;
if (Proxy->GetLightingChannelMask() != 0)
{
FMemMark MemStackMark(FMemStack::Get());
const FBoxSphereBounds& Bounds = Proxy->GetBounds();
const FPrimitiveSceneInfoCompact PrimitiveSceneInfoCompact(PrimitiveInfo);
// 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);
}
}
}
bool FScene::IsPrimitiveBeingRemoved(FPrimitiveSceneInfo* PrimitiveSceneInfo) const
{
check(IsInParallelRenderingThread() || IsInRenderingThread());
return RemovedPrimitiveSceneInfos.Find(PrimitiveSceneInfo) != nullptr;
}
/**
* Dummy NULL scene interface used by dedicated servers.
*/
class FNULLSceneInterface : public FSceneInterface
{
public:
FNULLSceneInterface(UWorld* InWorld, bool bCreateFXSystem )
: FSceneInterface(GMaxRHIFeatureLevel)
, World( InWorld )
, FXSystem( NULL )
{
World->Scene = this;
if (bCreateFXSystem)
{
World->CreateFXSystem();
}
else
{
World->FXSystem = NULL;
SetFXSystem(NULL);
}
}
virtual void AddPrimitive(UPrimitiveComponent* Primitive) override {}
virtual void RemovePrimitive(UPrimitiveComponent* Primitive) override {}
virtual void ReleasePrimitive(UPrimitiveComponent* Primitive) override {}
virtual void UpdateAllPrimitiveSceneInfos(FRDGBuilder& GraphBuilder, bool bAsyncCreateLPIs = false) override {}
virtual FPrimitiveSceneInfo* GetPrimitiveSceneInfo(int32 PrimiteIndex) override { return NULL; }
/** Updates the transform of a primitive which has already been added to the scene. */
virtual void UpdatePrimitiveTransform(UPrimitiveComponent* Primitive) override {}
virtual void UpdatePrimitiveInstances(UInstancedStaticMeshComponent* Primitive) override {}
virtual void UpdatePrimitiveOcclusionBoundsSlack(UPrimitiveComponent* Primitive, float NewSlack) override {}
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 {};
/** 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(class UExponentialHeightFogComponent* FogComponent) override {}
virtual void RemoveExponentialHeightFog(class UExponentialHeightFogComponent* FogComponent) override {}
virtual bool HasAnyExponentialHeightFog() const override { return false; }
virtual void AddSkyAtmosphere(FSkyAtmosphereSceneProxy* SkyAtmosphereSceneProxy, bool bStaticLightingBuilt) override {}
virtual void RemoveSkyAtmosphere(FSkyAtmosphereSceneProxy* SkyAtmosphereSceneProxy) override {}
virtual FSkyAtmosphereRenderSceneInfo* GetSkyAtmosphereSceneInfo() override { return NULL; }
virtual const FSkyAtmosphereRenderSceneInfo* GetSkyAtmosphereSceneInfo() const override { return NULL; }
virtual void AddHairStrands(FHairStrandsInstance* Proxy) override {}
virtual void RemoveHairStrands(FHairStrandsInstance* Proxy) 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;
}
/**
* Return the scene to be used for rendering
*/
virtual class FScene* GetRenderScene() override
{
return NULL;
}
/**
* 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; }
protected:
virtual FSceneViewStateInterface* AllocateViewState() override
{
return new FSceneViewState(FeatureLevel);
}
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);
}
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;
TMap<FScene*, TArray<FPrimitiveSceneInfo*>> UsedPrimitives;
for (TObjectIterator<UPrimitiveComponent> PrimitiveIt; PrimitiveIt; ++PrimitiveIt)
{
UPrimitiveComponent* PrimitiveComponent = *PrimitiveIt;
if (PrimitiveComponent->IsRenderStateCreated() && PrimitiveComponent->SceneProxy)
{
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->SceneProxy;
FPrimitiveSceneInfo* SceneInfo = SceneProxy->GetPrimitiveSceneInfo();
FScene* Scene = SceneInfo->Scene;
TArray<FPrimitiveSceneInfo*>& SceneInfos = UsedPrimitives.FindOrAdd(Scene);
SceneInfos.Add(SceneInfo);
break;
}
}
}
}
}
}
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(RHICmdList, 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);
}
void FRendererModule::InvalidatePathTracedOutput()
{
// 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();
}
}