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