// 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 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 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 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 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 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 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 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 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& 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& 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 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 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 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 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 static void TArraySwapElements(TArray& 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& 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::CreateUniformBufferImmediate(ViewUniformBufferParameters, UniformBuffer_MultiFrame, EUniformBufferValidation::None); FNaniteUniformParameters NaniteUniformBufferParameters; NaniteUniformBuffer = TUniformBufferRef::CreateUniformBufferImmediate(NaniteUniformBufferParameters, UniformBuffer_MultiFrame, EUniformBufferValidation::None); LumenCardCaptureViewUniformBuffer = TUniformBufferRef::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::CreateUniformBufferImmediate(MobileDirectionalLightShaderParameters, UniformBuffer_SingleFrame, EUniformBufferValidation::None); } const FMobileReflectionCaptureShaderParameters* DefaultMobileSkyReflectionParameters = (const FMobileReflectionCaptureShaderParameters*)GDefaultMobileReflectionCaptureUniformBuffer.GetContents(); MobileSkyReflectionUniformBuffer = TUniformBufferRef::CreateUniformBufferImmediate(*DefaultMobileSkyReflectionParameters, UniformBuffer_MultiFrame, EUniformBufferValidation::None); } TSet PersistentViewUniformBufferExtensions; void FRendererModule::RegisterPersistentViewUniformBufferExtension(IPersistentViewUniformBufferExtension* Extension) { PersistentViewUniformBufferExtensions.Add(Extension); } class FAsyncCreateLightPrimitiveInteractionsTask : public FNonAbandonableTask { TSet 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::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(*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 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& 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 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(Primitive)->GetLocalBounds(); // #todo (jnadro) This code should not be dependent on static mesh bounds. UStaticMeshComponent* StaticMeshComponent = Cast(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 > 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(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>&& InHitProxies) : HitProxies(MoveTemp(InHitProxies)) {} TArray> 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::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::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> 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 (DistanceSquaredInfluenceRadius != 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>::TConstIterator CachedShadowMapIt(CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt) { const TArray& 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::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::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::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& 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::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::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* 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* 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()) { 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 LightsWithUnbuiltInteractions; TSet 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(It)->ApplyWorldOffset(InOffset); } // Precomputed visibility if (PrecomputedVisibilityHandler) { const_cast(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 PrimitivesToAdd; if (const TArray* 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* 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& 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& 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& PrimitiveSceneInfos) { Experimental::TRobinHoodHashSet 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 inline void FScene::UpdateRayTracingGroupBounds_UpdatePrimitives(const Experimental::TRobinHoodHashMap& InUpdatedTransforms) { Experimental::TRobinHoodHashSet 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 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 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 AddedLocalPrimitiveSceneInfos; AddedLocalPrimitiveSceneInfos.Reserve(AddedPrimitiveSceneInfos.Num()); for (FPrimitiveSceneInfo* SceneInfo : AddedPrimitiveSceneInfos) { AddedLocalPrimitiveSceneInfos.Add(SceneInfo); } AddedLocalPrimitiveSceneInfos.Sort(FPrimitiveArraySortKey()); TSet DeletedSceneInfos; DeletedSceneInfos.Reserve(RemovedLocalPrimitiveSceneInfos.Num()); TArray 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>::TIterator CachedShadowMapIt(CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt) { TArray& 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* CachedShadowMapDatas = GetCachedShadowMapDatas(LightSceneInfo->Id); if (CachedShadowMapDatas) { for (auto& CachedShadowMapData : *CachedShadowMapDatas) { if (CachedShadowMapData.StaticShadowSubjectMap[PrimitiveIndex] == true) { CachedShadowMapData.InvalidateCachedShadow(); } } } } PersistentPrimitiveIdAllocator.Free(PrimitiveSceneInfo->PersistentIndex.Index); } for (TMap>::TIterator CachedShadowMapIt(CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt) { TArray& 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>::TIterator CachedShadowMapIt(CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt) { TArray& 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>::TIterator CachedShadowMapIt(CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt) { TArray& 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>::TIterator CachedShadowMapIt(CachedShadowMaps); CachedShadowMapIt; ++CachedShadowMapIt) { TArray& 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(&AddedLocalPrimitiveSceneInfos[StartIndex], AddedLocalPrimitiveSceneInfos.Num() - StartIndex)); { SCOPED_NAMED_EVENT(FScene_AddPrimitiveSceneInfoToScene, FColor::Turquoise); if (GIsEditor) { FPrimitiveSceneInfo::AddToScene(GraphBuilder.RHICmdList, this, TArrayView(&AddedLocalPrimitiveSceneInfos[StartIndex], AddedLocalPrimitiveSceneInfos.Num() - StartIndex), true); } else { const bool bAddToDrawLists = !(CVarDoLazyStaticMeshUpdate.GetValueOnRenderThread()); if (bAddToDrawLists) { FPrimitiveSceneInfo::AddToScene(GraphBuilder.RHICmdList, this, TArrayView(&AddedLocalPrimitiveSceneInfos[StartIndex], AddedLocalPrimitiveSceneInfos.Num() - StartIndex), true, true, bAsyncCreateLPIs); } else { FPrimitiveSceneInfo::AddToScene(GraphBuilder.RHICmdList, this, TArrayView(&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 UpdatedSceneInfosWithStaticDrawListUpdate; TArray 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 UpdatedSceneInfosWithStaticDrawListUpdate; UpdatedSceneInfosWithStaticDrawListUpdate.Reserve(UpdatedInstances.Num()); TArray UpdatedSceneInfosWithoutStaticDrawListUpdate; UpdatedSceneInfosWithoutStaticDrawListUpdate.Reserve(UpdatedInstances.Num()); #if RHI_RAYTRACING TArray 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>&& InHitProxies) : HitProxies(MoveTemp(InHitProxies)) {} TArray> 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& GetWindSources_RenderThread() const override { static TArray 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* 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& MaterialResourcesToUpdate) { TRACE_CPUPROFILER_EVENT_SCOPE(UpdateStaticMeshesForMaterials); TArray UsedMaterials; TSet UsedMaterialsDependencies; TMap> UsedPrimitives; for (TObjectIterator 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& 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& SceneInfoArray = SceneInfos.Value; FPrimitiveSceneInfo::UpdateStaticMeshes(RHICmdList, SceneInfos.Key, SceneInfoArray, EUpdateStaticMeshFlags::AllCommands, false); } }); } void FRendererModule::UpdateStaticDrawListsForMaterials(const TArray& 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(); } }