// Copyright Epic Games, Inc. All Rights Reserved. #include "RendererPrivate.h" #include "ScenePrivate.h" #include "SceneProxies/SkyLightSceneProxy.h" #include "SceneUtils.h" #include "PipelineStateCache.h" #include "ShaderParameterStruct.h" #include "LightSceneProxy.h" #include "GPUScene.h" #include "Rendering/NaniteResources.h" #include "Nanite/Nanite.h" #include "Nanite/NaniteShading.h" #include "PixelShaderUtils.h" #include "Lumen.h" #include "LumenMeshCards.h" #include "LumenSurfaceCacheFeedback.h" #include "LumenSceneLighting.h" #include "LumenSceneCardCapture.h" #include "LumenTracingUtils.h" #include "GlobalDistanceField.h" #include "DistanceFieldAmbientOcclusion.h" #include "HAL/LowLevelMemStats.h" #include "PostProcess/SceneRenderTargets.h" #include "ProfilingDebugging/CpuProfilerTrace.h" #include "DataDrivenShaderPlatformInfo.h" #include "StaticMeshBatch.h" #include "LumenReflections.h" #include "RayTracedTranslucency.h" #include "LumenRadiosity.h" #include "StereoRendering.h" #include "SkyAtmosphereRendering.h" #include "ComponentRecreateRenderStateContext.h" #include "VT/VirtualTextureFeedbackResource.h" int32 GLumenFastCameraMode = 0; FAutoConsoleVariableRef CVarLumenFastCameraMode( TEXT("r.LumenScene.FastCameraMode"), GLumenFastCameraMode, TEXT("Whether to update the Lumen Scene for fast camera movement - lower quality, faster updates so lighting can keep up with the camera."), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenSceneParallelUpdate = 1; FAutoConsoleVariableRef CVarLumenSceneParallelUpdate( TEXT("r.LumenScene.ParallelUpdate"), GLumenSceneParallelUpdate, TEXT("Whether to run the Lumen Scene update in parallel."), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenScenePrimitivesPerTask = 128; FAutoConsoleVariableRef CVarLumenScenePrimitivePerTask( TEXT("r.LumenScene.PrimitivesPerTask"), GLumenScenePrimitivesPerTask, TEXT("How many primitives to process per single surface cache update task."), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenSceneMeshCardsPerTask = 128; FAutoConsoleVariableRef CVarLumenSceneMeshCardsPerTask( TEXT("r.LumenScene.MeshCardsPerTask"), GLumenSceneMeshCardsPerTask, TEXT("How many mesh cards to process per single surface cache update task."), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenSurfaceCacheFreeze = 0; FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheFreeze( TEXT("r.LumenScene.SurfaceCache.Freeze"), GLumenSurfaceCacheFreeze, TEXT("Freeze surface cache updates for debugging.\n"), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenSurfaceCacheFreezeUpdateFrame = 0; FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheFreezeUpdateFrame( TEXT("r.LumenScene.SurfaceCache.FreezeUpdateFrame"), GLumenSurfaceCacheFreezeUpdateFrame, TEXT("Keep updating the same subset of surface cache for debugging and profiling.\n"), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenSceneSurfaceCacheReset = 0; FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheReset( TEXT("r.LumenScene.SurfaceCache.Reset"), GLumenSceneSurfaceCacheReset, TEXT("Reset all atlases and captured cards.\n"), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenSceneSurfaceCacheResetEveryNthFrame = 0; FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheResetEveryNthFrame( TEXT("r.LumenScene.SurfaceCache.ResetEveryNthFrame"), GLumenSceneSurfaceCacheResetEveryNthFrame, TEXT("Continuously reset all atlases and captured cards every N-th frame.\n"), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenSceneCardCapturesPerFrame = 300; FAutoConsoleVariableRef CVarLumenSceneCardCapturesPerFrame( TEXT("r.LumenScene.SurfaceCache.CardCapturesPerFrame"), GLumenSceneCardCapturesPerFrame, TEXT(""), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenSceneCardCaptureFactor = 64; FAutoConsoleVariableRef CVarLumenSceneCardCaptureFactor( TEXT("r.LumenScene.SurfaceCache.CardCaptureFactor"), GLumenSceneCardCaptureFactor, TEXT("Controls how many texels can be captured per frame. Texels = SurfaceCacheTexels / Factor."), ECVF_Scalability | ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarLumenSceneSurfaceCacheRemovesPerFrame( TEXT("r.LumenScene.SurfaceCache.RemovesPerFrame"), 512, TEXT("How many mesh cards removes can be done per frame."), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable CVarLumenSceneCardCaptureRefreshFraction( TEXT("r.LumenScene.SurfaceCache.CardCaptureRefreshFraction"), 0.125f, TEXT("Fraction of card capture budget allowed to be spent on re-capturing existing pages in order to refresh surface cache materials.\n") TEXT("0 disables card refresh."), ECVF_Scalability | ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarLumenSceneCardCaptureEnableInvalidation( TEXT("r.LumenScene.SurfaceCache.CardCaptureEnableInvalidation"), 1, TEXT("Whether to enable manual card recapture through InvalidateSurfaceCacheForPrimitive().\n"), ECVF_Scalability | ECVF_RenderThreadSafe ); float GLumenSceneCardFixedDebugResolution = -1; FAutoConsoleVariableRef CVarLumenSceneCardFixedDebugResolution( TEXT("r.LumenScene.SurfaceCache.CardFixedDebugResolution"), GLumenSceneCardFixedDebugResolution, TEXT("Lumen card resolution"), ECVF_Scalability | ECVF_RenderThreadSafe ); float GLumenSceneCardMaxTexelDensity = .2f; FAutoConsoleVariableRef CVarLumenSceneCardMaxTexelDensity( TEXT("r.LumenScene.SurfaceCache.CardMaxTexelDensity"), GLumenSceneCardMaxTexelDensity, TEXT("Lumen card texels per world space distance"), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenSceneCardMaxResolution = 512; FAutoConsoleVariableRef CVarLumenSceneCardMaxResolution( TEXT("r.LumenScene.SurfaceCache.CardMaxResolution"), GLumenSceneCardMaxResolution, TEXT("Maximum card resolution in Lumen Scene"), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GSurfaceCacheNumFramesToKeepUnusedPages = 256; FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheNumFramesToKeepUnusedPages( TEXT("r.LumenScene.SurfaceCache.NumFramesToKeepUnusedPages"), GSurfaceCacheNumFramesToKeepUnusedPages, TEXT("Num frames to keep unused pages in surface cache."), ECVF_RenderThreadSafe ); int32 GLumenSceneForceEvictHiResPages = 0; FAutoConsoleVariableRef CVarLumenSceneForceEvictHiResPages( TEXT("r.LumenScene.SurfaceCache.ForceEvictHiResPages"), GLumenSceneForceEvictHiResPages, TEXT("Evict all optional hi-res surface cache pages."), ECVF_RenderThreadSafe ); int32 GLumenSceneRecaptureLumenSceneEveryFrame = 0; FAutoConsoleVariableRef CVarLumenGIRecaptureLumenSceneEveryFrame( TEXT("r.LumenScene.SurfaceCache.RecaptureEveryFrame"), GLumenSceneRecaptureLumenSceneEveryFrame, TEXT(""), ECVF_RenderThreadSafe ); int32 GLumenSceneSurfaceCacheLogUpdates = 0; FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheLogUpdates( TEXT("r.LumenScene.SurfaceCache.LogUpdates"), GLumenSceneSurfaceCacheLogUpdates, TEXT("Whether to log Lumen surface cache updates.\n") TEXT("2 - will log mesh names."), ECVF_RenderThreadSafe ); int32 GLumenSceneSurfaceCacheResampleLighting = 1; FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheResampleLighting( TEXT("r.LumenScene.SurfaceCache.ResampleLighting"), GLumenSceneSurfaceCacheResampleLighting, TEXT("Whether to resample card lighting when cards are reallocated. This is needed for Radiosity temporal accumulation but can be disabled for debugging."), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarLumenSceneSurfaceCacheNaniteMultiView( TEXT("r.LumenScene.SurfaceCache.Nanite.MultiView"), 1, TEXT("Toggle multi view Lumen Nanite Card capture for debugging."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable) { Lumen::DebugResetSurfaceCache(); }), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarLumenSceneSurfaceCacheNaniteAsyncRasterization( TEXT("r.LumenScene.SurfaceCache.Nanite.AsyncRasterization"), 0, TEXT("Whether to use Nanite async rasterization for Mesh Card capture."), ECVF_Scalability | ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarLumenScenePropagateGlobalLightingChange( TEXT("r.LumenScene.PropagateGlobalLightingChange"), 1, TEXT("Whether to detect big scene lighting changes and speedup Lumen update for those frames."), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarLumenSceneGPUDrivenUpdate( TEXT("r.LumenScene.GPUDrivenUpdate"), 0, TEXT("Whether to use GPU to update Lumen Scene. Work in progress."), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarLumenSceneViewOriginDistanceThreshold( TEXT("r.LumenScene.ViewOriginDistanceThreshold"), 100, TEXT("Distance threshold below which views' origins are considered identical. Used for streaming request with multiple views. Default 100 (= 1 meter)"), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarLumenSceneUploadEveryFrame( TEXT("r.LumenScene.UploadEveryFrame"), 0, TEXT("Whether to upload the entire Lumen Scene's data every frame. Useful for debugging."), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarLumenSceneSurfaceCacheAllowCardDownsampleFromSelf( TEXT("r.LumenScene.SurfaceCache.AllowCardDownsampleFromSelf"), 1, TEXT("Whether cards are allowed to downsample from self instead of recapture when lowering resolutions."), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarLumenSceneSurfaceCacheAllowCardSharing( TEXT("r.LumenScene.SurfaceCache.AllowCardSharing"), 1, TEXT("Whether to allow sharing Lumen cards between instances. Automatically disabled if r.LumenScene.SurfaceCache.CullUndergroundTexels is enabled."), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarLumenSceneSurfaceCacheDetectCardSharingCompatibility( TEXT("r.LumenScene.SurfaceCache.DetectCardSharingCompatibility"), 1, TEXT("Whether to auto detect card sharing compatibility. Currently, a component is incompatible if it uses any material that has any of the following nodes: ") TEXT("PerInstanceRandom, PerInstanceCustomData, WorldPosition, and ActorPositionWS."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* CVar) { FGlobalComponentRecreateRenderStateContext Context; }), ECVF_RenderThreadSafe ); #if ENABLE_LOW_LEVEL_MEM_TRACKER DECLARE_LLM_MEMORY_STAT(TEXT("Lumen"), STAT_LumenLLM, STATGROUP_LLMFULL); DECLARE_LLM_MEMORY_STAT(TEXT("Lumen"), STAT_LumenSummaryLLM, STATGROUP_LLM); LLM_DEFINE_TAG(Lumen, NAME_None, NAME_None, GET_STATFNAME(STAT_LumenLLM), GET_STATFNAME(STAT_LumenSummaryLLM)); #endif // ENABLE_LOW_LEVEL_MEM_TRACKER extern int32 GAllowLumenDiffuseIndirect; extern int32 GAllowLumenReflections; void Lumen::DebugResetSurfaceCache() { GLumenSceneSurfaceCacheReset = 1; } bool Lumen::IsSurfaceCacheFrozen() { return GLumenSurfaceCacheFreeze != 0; } bool Lumen::IsSurfaceCacheUpdateFrameFrozen() { return GLumenSurfaceCacheFreeze != 0 || GLumenSurfaceCacheFreezeUpdateFrame != 0; } int32 GetCardMaxResolution() { if (GLumenFastCameraMode) { return GLumenSceneCardMaxResolution / 2; } return GLumenSceneCardMaxResolution; } int32 GetMaxLumenSceneCardCapturesPerFrame() { return FMath::Max(GLumenSceneCardCapturesPerFrame * (GLumenFastCameraMode ? 2 : 1), 0); } namespace LumenScene { bool AllowSurfaceCacheCardSharing() { return CVarLumenSceneSurfaceCacheAllowCardSharing.GetValueOnRenderThread() != 0 && !LumenScene::CullUndergroundTexels(); } int32 GetMaxMeshCardsToAddPerFrame() { return 2 * GetMaxLumenSceneCardCapturesPerFrame(); } int32 GetMaxMeshCardsRemovesPerFrame() { return FMath::Max(CVarLumenSceneSurfaceCacheRemovesPerFrame.GetValueOnRenderThread(), 0); } } int32 GetMaxTileCapturesPerFrame() { if (Lumen::IsSurfaceCacheFrozen()) { return 0; } if (GLumenSceneRecaptureLumenSceneEveryFrame != 0) { return INT32_MAX; } return GetMaxLumenSceneCardCapturesPerFrame(); } uint32 FLumenSceneData::GetSurfaceCacheUpdateFrameIndex() const { return SurfaceCacheUpdateFrameIndex; } void FLumenSceneData::IncrementSurfaceCacheUpdateFrameIndex() { if (!Lumen::IsSurfaceCacheUpdateFrameFrozen()) { ++SurfaceCacheUpdateFrameIndex; if (SurfaceCacheUpdateFrameIndex == 0) { ++SurfaceCacheUpdateFrameIndex; } } } void AddLumenStreamingViewOrigins(const FSceneViewFamily& ViewFamily, TArray>& OutOrigins) { // Add streaming view origins, only if there are futher apart than existing origins const float DistanceThreshold = CVarLumenSceneViewOriginDistanceThreshold.GetValueOnRenderThread(); const float SqDistanceThreshold = DistanceThreshold * DistanceThreshold; for (const FVector& StreamViewOrigin : ViewFamily.StreamingViewOrigins) { bool bAddOrigin = true; for (const FVector& Origin : OutOrigins) { if (FVector::DistSquared(StreamViewOrigin, Origin) < SqDistanceThreshold) { bAddOrigin = false; break; } } if (bAddOrigin && OutOrigins.Num() < LUMEN_MAX_VIEWS) { OutOrigins.Add(StreamViewOrigin); } } } DECLARE_GPU_STAT(LumenSceneUpdate); DECLARE_GPU_STAT(UpdateLumenSceneBuffers); IMPLEMENT_STATIC_UNIFORM_BUFFER_STRUCT(FLumenCardPassUniformParameters, "LumenCardPass", SceneTextures); bool LumenScene::HasPrimitiveNaniteMeshBatches(const FPrimitiveSceneProxy* Proxy) { return Proxy && Proxy->ShouldRenderInMainPass() && (Proxy->AffectsDynamicIndirectLighting() || (Proxy->IsLandscapeNaniteProxy() && Proxy->GetSourceLandscapeComponentIds().Num() > 0)); } struct FMeshCardsAdd { int32 PrimitiveGroupIndex; float DistanceSquared; }; struct FMeshCardsRemove { int32 PrimitiveGroupIndex; }; struct FInstanceRange { int32 Offset; int32 Num; FInstanceRange(int32 InOffset, int32 InNum) : Offset(InOffset) , Num(InNum) {} }; struct FCardAllocationOutput { bool bVisible = false; int32 ResLevel = -1; }; // Loop over Lumen primitive culling infos and output FMeshCards adds, removes, and instance culling ranges struct FLumenSurfaceCacheCullPrimitivesTask { public: FLumenSurfaceCacheCullPrimitivesTask( TSparseArray& InPrimitiveCullingInfos, const TArray>& InViewOrigins, bool bInOrthographicCamera, float InLumenSceneDetail, float InMaxDistanceFromCamera, int32 InFirstCullingInfoIndex, int32 InNumCullingInfosPerPacket, bool InAddTranslucentToCache) : PrimitiveCullingInfos(InPrimitiveCullingInfos) , ViewOrigins(InViewOrigins) , bOrthographicCamera(bInOrthographicCamera) , FirstCullingInfoIndex(InFirstCullingInfoIndex) , NumCullingInfosPerPacket(InNumCullingInfosPerPacket) , LumenSceneDetail(InLumenSceneDetail) , MaxDistanceFromCameraSq(InMaxDistanceFromCamera * InMaxDistanceFromCamera) , TexelDensityScale(LumenScene::GetCardTexelDensity()) , MinCardResolution(FMath::Clamp(FMath::RoundToInt(LumenScene::GetCardMinResolution(bInOrthographicCamera) / LumenSceneDetail), 1, 1024)) , FarFieldCardMaxDistanceSq(LumenScene::GetFarFieldCardMaxDistance() * LumenScene::GetFarFieldCardMaxDistance()) , FarFieldCardTexelDensity(LumenScene::GetFarFieldCardTexelDensity()) , bAddTranslucentToCache(InAddTranslucentToCache) { } // Output TArray MeshCardsAdds; TArray MeshCardsRemoves; TArray InstanceCullingRanges; void AnyThreadTask() { const int32 LastCullingInfoIndex = FMath::Min(FirstCullingInfoIndex + NumCullingInfosPerPacket, PrimitiveCullingInfos.GetMaxIndex()); for (int32 CullingInfoIndex = FirstCullingInfoIndex; CullingInfoIndex < LastCullingInfoIndex; ++CullingInfoIndex) { if (PrimitiveCullingInfos.IsAllocated(CullingInfoIndex)) { FLumenPrimitiveGroupCullingInfo& CullingInfo = PrimitiveCullingInfos[CullingInfoIndex]; // Rough card min resolution test float DistanceSquared = FLT_MAX; // LWC_TODO for (const FVector& ViewOrigin : ViewOrigins) { DistanceSquared = FMath::Min(DistanceSquared, ComputeSquaredDistanceFromBoxToPoint(FVector(CullingInfo.WorldSpaceBoundingBox.Min), FVector(CullingInfo.WorldSpaceBoundingBox.Max), ViewOrigin)); // LWC_TODO } const float CardMaxDistanceSq = CullingInfo.bFarField ? FarFieldCardMaxDistanceSq : MaxDistanceFromCameraSq; if (CullingInfo.NumInstances > 0) { const bool bWasVisible = CullingInfo.bVisible; CullingInfo.bVisible = DistanceSquared <= CardMaxDistanceSq; // May need to hide instances if was visible. May need to show instances if is visible if (bWasVisible || CullingInfo.bVisible) { InstanceCullingRanges.Add(FInstanceRange(CullingInfo.InstanceCullingInfoOffset, CullingInfo.NumInstances)); } } else { const float MaxCardExtent = CullingInfo.WorldSpaceBoundingBox.GetExtent().GetMax(); float MaxCardResolution; // Far field cards have constant resolution over entire range if (CullingInfo.bFarField) { MaxCardResolution = MaxCardExtent * FarFieldCardTexelDensity; } else { MaxCardResolution = (TexelDensityScale * MaxCardExtent) / FMath::Sqrt(FMath::Max(DistanceSquared, 1.0f)) + 0.01f; } if (DistanceSquared <= CardMaxDistanceSq && MaxCardResolution >= (CullingInfo.bEmissiveLightSource ? 1.0f : MinCardResolution) && (CullingInfo.bOpaqueOrMasked || bAddTranslucentToCache)) { if (!CullingInfo.bVisible && CullingInfo.bValidMeshCards) { FMeshCardsAdd Add; Add.PrimitiveGroupIndex = CullingInfo.PrimitiveGroupIndex; Add.DistanceSquared = DistanceSquared; MeshCardsAdds.Add(Add); } } else if (CullingInfo.bVisible) { FMeshCardsRemove Remove; Remove.PrimitiveGroupIndex = CullingInfo.PrimitiveGroupIndex; MeshCardsRemoves.Add(Remove); } } } } } TSparseArray& PrimitiveCullingInfos; TArray> ViewOrigins; bool bOrthographicCamera; int32 FirstCullingInfoIndex; int32 NumCullingInfosPerPacket; float LumenSceneDetail; float MaxDistanceFromCameraSq; float TexelDensityScale; const int32 MinCardResolution; const float FarFieldCardMaxDistanceSq; const float FarFieldCardTexelDensity; const bool bAddTranslucentToCache; }; // Loop over selected instances and output FMeshCards adds and removes struct FLumenSurfaceCacheCullInstancesTask { public: FLumenSurfaceCacheCullInstancesTask( const TSparseSpanArray& InInstanceCullingInfos, TConstArrayView InInstanceRanges, TConstArrayView InRebasedRangeOffsets, const TArray>& InViewOrigins, bool bInOrthographicCamera, float InLumenSceneDetail, float InMaxDistanceFromCamera, int32 InFirstCullingInfoIndex, int32 InNumCullingInfosPerPacket, bool InAddTranslucentToCache) : InstanceCullingInfos(InInstanceCullingInfos) , InstanceRanges(InInstanceRanges) , RebasedRangeOffsets(InRebasedRangeOffsets) , ViewOrigins(InViewOrigins) , bOrthographicCamera(bInOrthographicCamera) , FirstCullingInfoIndex(InFirstCullingInfoIndex) , NumCullingInfosPerPacket(InNumCullingInfosPerPacket) , LumenSceneDetail(InLumenSceneDetail) , MaxDistanceFromCameraSq(InMaxDistanceFromCamera * InMaxDistanceFromCamera) , TexelDensityScale(LumenScene::GetCardTexelDensity()) , MinCardResolution(FMath::Clamp(FMath::RoundToInt(LumenScene::GetCardMinResolution(bInOrthographicCamera) / LumenSceneDetail), 1, 1024)) , FarFieldCardMaxDistanceSq(LumenScene::GetFarFieldCardMaxDistance()* LumenScene::GetFarFieldCardMaxDistance()) , FarFieldCardTexelDensity(LumenScene::GetFarFieldCardTexelDensity()) , bAddTranslucentToCache(InAddTranslucentToCache) { } // Output TArray MeshCardsAdds; TArray MeshCardsRemoves; void AnyThreadTask() { int32 InstanceRangeIndex = Algo::UpperBound(RebasedRangeOffsets, FirstCullingInfoIndex) - 1; if (InstanceRangeIndex >= RebasedRangeOffsets.Num() - 1) { return; } const int32 LastCullingInfoIndex = FMath::Min(FirstCullingInfoIndex + NumCullingInfosPerPacket, RebasedRangeOffsets.Last()); int32 NumCullingInfos = LastCullingInfoIndex - FirstCullingInfoIndex; int32 CullingInfoIndex = FirstCullingInfoIndex - RebasedRangeOffsets[InstanceRangeIndex] + InstanceRanges[InstanceRangeIndex].Offset; while (NumCullingInfos > 0) { const FLumenPrimitiveGroupCullingInfo& CullingInfo = InstanceCullingInfos[CullingInfoIndex]; check(InstanceCullingInfos.IsAllocated(CullingInfoIndex) && CullingInfo.NumInstances == 0); // Rough card min resolution test float DistanceSquared = FLT_MAX; // LWC_TODO for (const FVector& ViewOrigin : ViewOrigins) { DistanceSquared = FMath::Min(DistanceSquared, ComputeSquaredDistanceFromBoxToPoint(FVector(CullingInfo.WorldSpaceBoundingBox.Min), FVector(CullingInfo.WorldSpaceBoundingBox.Max), ViewOrigin)); // LWC_TODO } const float MaxCardExtent = CullingInfo.WorldSpaceBoundingBox.GetExtent().GetMax(); float CardMaxDistanceSq; float MaxCardResolution; // Far field cards have constant resolution over entire range if (CullingInfo.bFarField) { CardMaxDistanceSq = FarFieldCardMaxDistanceSq; MaxCardResolution = MaxCardExtent * FarFieldCardTexelDensity; } else { CardMaxDistanceSq = MaxDistanceFromCameraSq; MaxCardResolution = (TexelDensityScale * MaxCardExtent) / FMath::Sqrt(FMath::Max(DistanceSquared, 1.0f)) + 0.01f; } if (DistanceSquared <= CardMaxDistanceSq && MaxCardResolution >= (CullingInfo.bEmissiveLightSource ? 1.0f : MinCardResolution) && (CullingInfo.bOpaqueOrMasked || bAddTranslucentToCache)) { if (!CullingInfo.bVisible && CullingInfo.bValidMeshCards) { FMeshCardsAdd Add; Add.PrimitiveGroupIndex = CullingInfo.PrimitiveGroupIndex; Add.DistanceSquared = DistanceSquared; MeshCardsAdds.Add(Add); } } else if (CullingInfo.bVisible) { FMeshCardsRemove Remove; Remove.PrimitiveGroupIndex = CullingInfo.PrimitiveGroupIndex; MeshCardsRemoves.Add(Remove); } --NumCullingInfos; ++CullingInfoIndex; if (NumCullingInfos > 0 && CullingInfoIndex >= InstanceRanges[InstanceRangeIndex].Offset + InstanceRanges[InstanceRangeIndex].Num) { ++InstanceRangeIndex; CullingInfoIndex = InstanceRanges[InstanceRangeIndex].Offset; } } } const TSparseSpanArray& InstanceCullingInfos; TConstArrayView InstanceRanges; TConstArrayView RebasedRangeOffsets; TArray> ViewOrigins; bool bOrthographicCamera; int32 FirstCullingInfoIndex; int32 NumCullingInfosPerPacket; float LumenSceneDetail; float MaxDistanceFromCameraSq; float TexelDensityScale; const int32 MinCardResolution; const float FarFieldCardMaxDistanceSq; const float FarFieldCardTexelDensity; const bool bAddTranslucentToCache; }; struct FSurfaceCacheRemove { public: int32 LumenCardIndex; }; // Loop over Lumen mesh cards and output card updates struct FLumenSurfaceCacheUpdateMeshCardsTask { public: FLumenSurfaceCacheUpdateMeshCardsTask( const TSparseSpanArray& InLumenMeshCards, const TSparseSpanArray& InLumenCards, const TArray>& InViewOrigins, bool bInOrthographicCamera, float InSurfaceCacheResolution, float InLumenSceneDetail, float InMaxDistanceFromCamera, int32 InFirstMeshCardsIndex, int32 InNumMeshCardsPerPacket) : LumenMeshCards(InLumenMeshCards) , LumenCards(InLumenCards) , ViewOrigins(InViewOrigins) , bOrthographicCamera(bInOrthographicCamera) , LumenSceneDetail(InLumenSceneDetail) , FirstMeshCardsIndex(InFirstMeshCardsIndex) , NumMeshCardsPerPacket(InNumMeshCardsPerPacket) , MaxDistanceFromCamera(InMaxDistanceFromCamera) , TexelDensityScale(LumenScene::GetCardTexelDensity() * InSurfaceCacheResolution) , MaxTexelDensity(GLumenSceneCardMaxTexelDensity) , MinCardResolution(FMath::Clamp(FMath::RoundToInt(LumenScene::GetCardMinResolution(bInOrthographicCamera) / LumenSceneDetail), 1, 1024)) , FarFieldCardMaxDistance(LumenScene::GetFarFieldCardMaxDistance()) , FarFieldCardTexelDensity(LumenScene::GetFarFieldCardTexelDensity()) { } // Output TArray SurfaceCacheRequests; TArray CardsToHide; int32 Histogram[Lumen::NumDistanceBuckets] { 0 }; void AnyThreadTask() { QUICK_SCOPE_CYCLE_COUNTER(LumenSurfaceCacheUpdateMeshCardsTask) const int32 LastLumenMeshCardsIndex = FMath::Min(FirstMeshCardsIndex + NumMeshCardsPerPacket, LumenMeshCards.Num()); const int32 CardMaxResolution = GetCardMaxResolution(); for (int32 MeshCardsIndex = FirstMeshCardsIndex; MeshCardsIndex < LastLumenMeshCardsIndex; ++MeshCardsIndex) { if (LumenMeshCards.IsAllocated(MeshCardsIndex)) { const FLumenMeshCards& MeshCardsInstance = LumenMeshCards[MeshCardsIndex]; const int32 MinCardResolutionForMeshCards = MeshCardsInstance.bEmissiveLightSource ? 1 : MinCardResolution; for (uint32 CardIndex = MeshCardsInstance.FirstCardIndex; CardIndex < MeshCardsInstance.FirstCardIndex + MeshCardsInstance.NumCards; ++CardIndex) { const FLumenCard& LumenCard = LumenCards[CardIndex]; float ViewerDistance = FLT_MAX; // LWC_TODO for (const FVector& ViewOrigin : ViewOrigins) { ViewerDistance = FMath::Min(ViewerDistance, FMath::Max(FMath::Sqrt(LumenCard.WorldOBB.ComputeSquaredDistanceToPoint(ViewOrigin)), 100.0f)); } // Compute resolution based on its largest extent float MaxExtent = FMath::Max(LumenCard.WorldOBB.Extent.X, LumenCard.WorldOBB.Extent.Y); float CardMaxDistance; float MaxProjectedSize; // Far field cards have constant resolution over entire range if (MeshCardsInstance.bFarField) { CardMaxDistance = FarFieldCardMaxDistance; MaxProjectedSize = FarFieldCardTexelDensity * MaxExtent * LumenCard.ResolutionScale; } else { CardMaxDistance = MaxDistanceFromCamera; MaxProjectedSize = FMath::Min(TexelDensityScale * MaxExtent * LumenCard.ResolutionScale / ViewerDistance, GLumenSceneCardMaxTexelDensity * MaxExtent); } if (GLumenSceneCardFixedDebugResolution > 0) { MaxProjectedSize = GLumenSceneCardFixedDebugResolution; } const int32 MaxSnappedRes = FMath::RoundUpToPowerOfTwo(FMath::Min(FMath::TruncToInt(MaxProjectedSize), CardMaxResolution)); const bool bVisible = ViewerDistance < CardMaxDistance && MaxSnappedRes >= MinCardResolutionForMeshCards; const int32 ResLevel = FMath::FloorLog2(FMath::Max(MaxSnappedRes, Lumen::MinCardResolution)); if (!bVisible && LumenCard.bVisible) { CardsToHide.Add(CardIndex); } else if (bVisible && ResLevel != LumenCard.DesiredLockedResLevel) { float Distance = ViewerDistance; if (LumenCard.bVisible && LumenCard.DesiredLockedResLevel != ResLevel) { // Make reallocation less important than capturing new cards const float ResLevelDelta = FMath::Abs((int32)LumenCard.DesiredLockedResLevel - ResLevel); Distance += (1.0f - FMath::Clamp((ResLevelDelta + 1.0f) / 3.0f, 0.0f, 1.0f)) * 2500.0f; } FSurfaceCacheRequest Request; Request.ResLevel = ResLevel; Request.CardIndex = CardIndex; Request.LocalPageIndex = UINT16_MAX; Request.Distance = Distance; SurfaceCacheRequests.Add(Request); const int32 DistanceBin = Lumen::GetMeshCardDistanceBin(Distance); Histogram[DistanceBin]++; ensure(Request.IsLockedMip()); } } } } } const TSparseSpanArray& LumenMeshCards; const TSparseSpanArray& LumenCards; TArray> ViewOrigins; bool bOrthographicCamera; float LumenSceneDetail; int32 FirstMeshCardsIndex; int32 NumMeshCardsPerPacket; float MaxDistanceFromCamera; float TexelDensityScale; float MaxTexelDensity; const int32 MinCardResolution; const float FarFieldCardMaxDistance; const float FarFieldCardTexelDensity; }; /** * Make sure that all mesh rendering data is prepared before we render this primitive group * @return Will return true it primitive group is ready to render or we need to wait until next frame */ bool UpdateStaticMeshes(const FLumenPrimitiveGroup& PrimitiveGroup, FLumenCardRenderer& LumenCardRenderer) { bool bReadyToRender = true; for (FPrimitiveSceneInfo* PrimitiveSceneInfo : PrimitiveGroup.Primitives) { if (PrimitiveSceneInfo && PrimitiveSceneInfo->Proxy->AffectsDynamicIndirectLighting()) { if (PrimitiveSceneInfo->Proxy->StaticMeshHasPendingStreaming()) { bReadyToRender = false; } if (PrimitiveGroup.bHeightfield && PrimitiveSceneInfo->Proxy->HeightfieldHasPendingStreaming()) { bReadyToRender = false; } } } return bReadyToRender; } bool FLumenSceneData::RecaptureCardPage(const FViewInfo& MainView, FLumenCardRenderer& LumenCardRenderer, FLumenSurfaceCacheAllocator& CaptureAtlasAllocator, FRHIGPUMask GPUMask, int32 PageTableIndex) { TArray& CardPagesToRender = LumenCardRenderer.CardPagesToRender; FLumenPageTableEntry& PageTableEntry = GetPageTableEntry(PageTableIndex); const FLumenCard& Card = Cards[PageTableEntry.CardIndex]; const FLumenMeshCards& MeshCardsElement = MeshCards[Card.MeshCardsIndex]; // Can we fit this card into the temporary card capture allocator? if (CaptureAtlasAllocator.IsSpaceAvailable(Card, PageTableEntry.ResLevel, /*bSinglePage*/ true)) { // Allocate space in temporary allocation atlas FLumenSurfaceCacheAllocator::FAllocation CardCaptureAllocation; CaptureAtlasAllocator.Allocate(PageTableEntry, CardCaptureAllocation); check(CardCaptureAllocation.PhysicalPageCoord.X >= 0); CardPagesToRender.Add(FCardPageRenderData( MainView, Card, PageTableEntry.CardUVRect, CardCaptureAllocation.PhysicalAtlasRect, PageTableEntry.PhysicalAtlasRect, MeshCardsElement.PrimitiveGroupIndex, PageTableEntry.CardIndex, PageTableIndex, /*bResampleLastLighting*/ true)); for (uint32 GPUIndex : GPUMask) { LastCapturedPageHeap[GPUIndex].Update(GetSurfaceCacheUpdateFrameIndex(), PageTableIndex); } LumenCardRenderer.NumCardTexelsToCapture += PageTableEntry.PhysicalAtlasRect.Area(); return true; } return false; } /** * Process a throttled number of Lumen surface cache add requests * It will make virtual and physical allocations, and evict old pages as required */ void FLumenSceneData::ProcessLumenSurfaceCacheRequests( const FViewInfo& MainView, float MaxCardUpdateDistanceFromCamera, int32 MaxTileCapturesPerFrame, FLumenCardRenderer& LumenCardRenderer, FRHIGPUMask GPUMask, const TArray& SurfaceCacheRequests) { QUICK_SCOPE_CYCLE_COUNTER(ProcessLumenSurfaceCacheRequests); TArray& CardPagesToRender = LumenCardRenderer.CardPagesToRender; TArray HiResPagesToMap; TSparseUniqueList DirtyCards; FLumenSurfaceCacheAllocator CaptureAtlasAllocator; CaptureAtlasAllocator.Init(GetCardCaptureAtlasSizeInPages()); for (int32 RequestIndex = 0; RequestIndex < SurfaceCacheRequests.Num(); ++RequestIndex) { const FSurfaceCacheRequest& Request = SurfaceCacheRequests[RequestIndex]; if (Request.IsLockedMip()) { // Update low-res locked (always resident) pages FLumenCard& Card = Cards[Request.CardIndex]; if (Card.DesiredLockedResLevel != Request.ResLevel) { // Check if we can make this allocation at all bool bCanAlloc = true; uint8 NewLockedAllocationResLevel = Request.ResLevel; while (!IsPhysicalSpaceAvailable(Card, NewLockedAllocationResLevel, /*bSinglePage*/ false)) { const int32 MaxFramesSinceLastUsed = 2; if (!EvictOldestAllocation(/*MaxFramesSinceLastUsed*/ MaxFramesSinceLastUsed, DirtyCards)) { bCanAlloc = false; break; } } // Try to decrease resolution if allocation still can't be made while (!bCanAlloc && NewLockedAllocationResLevel > Lumen::MinResLevel) { --NewLockedAllocationResLevel; bCanAlloc = IsPhysicalSpaceAvailable(Card, NewLockedAllocationResLevel, /*bSinglePage*/ false); } // Can we fit this card into the temporary card capture allocator? if (!CaptureAtlasAllocator.IsSpaceAvailable(Card, NewLockedAllocationResLevel, /*bSinglePage*/ false)) { bCanAlloc = false; } const FLumenMeshCards& MeshCardsElement = MeshCards[Card.MeshCardsIndex]; const FLumenPrimitiveGroup& PrimitiveGroup = PrimitiveGroups[MeshCardsElement.PrimitiveGroupIndex]; if (bCanAlloc && UpdateStaticMeshes(PrimitiveGroup, LumenCardRenderer)) { Card.bVisible = true; Card.DesiredLockedResLevel = Request.ResLevel; const bool bResampleLastLighting = Card.IsAllocated(); const bool bCopyFromSelf = bAllowCardDownsampleFromSelf && Card.IsAllocated() && NewLockedAllocationResLevel <= Card.MinAllocatedResLevel; // Free previous MinAllocatedResLevel FreeVirtualSurface(Card, Card.MinAllocatedResLevel, Card.MinAllocatedResLevel); // Free anything lower res than the new res level FreeVirtualSurface(Card, Card.MinAllocatedResLevel, NewLockedAllocationResLevel - 1); const bool bLockPages = true; ReallocVirtualSurface(Card, Request.CardIndex, NewLockedAllocationResLevel, bLockPages); int32 CopyCardIndex = INDEX_NONE; bool bAxisXFlipped = false; if (bCopyFromSelf) { CopyCardIndex = Request.CardIndex; } else if (const FLumenCardSharingInfo* CardSharingInfo = FindMatchingCardForCopy(Card.CardSharingId, Card.MinAllocatedResLevel)) { check(Card.MinAllocatedResLevel <= CardSharingInfo->MinAllocatedResLevel); CopyCardIndex = CardSharingInfo->CardIndex; bAxisXFlipped = Card.bAxisXFlipped != CardSharingInfo->bAxisXFlipped; } // Map and update all pages FLumenSurfaceMipMap& MipMap = Card.GetMipMap(Card.MinAllocatedResLevel); for (int32 LocalPageIndex = 0; LocalPageIndex < MipMap.SizeInPagesX * MipMap.SizeInPagesY; ++LocalPageIndex) { const int32 PageIndex = MipMap.GetPageTableIndex(LocalPageIndex); FLumenPageTableEntry& PageTableEntry = GetPageTableEntry(PageIndex); if (!PageTableEntry.IsMapped()) { MapSurfaceCachePage(MipMap, PageIndex, GPUMask); check(PageTableEntry.IsMapped()); // Allocate space in temporary allocation atlas FLumenSurfaceCacheAllocator::FAllocation CardCaptureAllocation; CaptureAtlasAllocator.Allocate(PageTableEntry, CardCaptureAllocation); check(CardCaptureAllocation.PhysicalPageCoord.X >= 0); CardPagesToRender.Add(FCardPageRenderData( MainView, Card, PageTableEntry.CardUVRect, CardCaptureAllocation.PhysicalAtlasRect, PageTableEntry.PhysicalAtlasRect, MeshCardsElement.PrimitiveGroupIndex, Request.CardIndex, PageIndex, bResampleLastLighting, bAxisXFlipped, CopyCardIndex)); if (CopyCardIndex >= 0) { LumenCardRenderer.bHasAnyCardCopy = true; } for (uint32 GPUIndex : GPUMask) { LastCapturedPageHeap[GPUIndex].Update(GetSurfaceCacheUpdateFrameIndex(), PageIndex); } LumenCardRenderer.NumCardTexelsToCapture += PageTableEntry.PhysicalAtlasRect.Area(); } } DirtyCards.Add(Request.CardIndex); } } } else { // Hi-Res if (Cards.IsAllocated(Request.CardIndex)) { FLumenCard& Card = Cards[Request.CardIndex]; if (Card.bVisible && Card.MinAllocatedResLevel >= 0 && Request.ResLevel > Card.MinAllocatedResLevel) { HiResPagesToMap.Add(FVirtualPageIndex(Request.CardIndex, Request.ResLevel, Request.LocalPageIndex)); } } } if (CardPagesToRender.Num() + HiResPagesToMap.Num() >= MaxTileCapturesPerFrame) { break; } } // Process hi-res optional pages after locked low res ones are done for (const FVirtualPageIndex& VirtualPageIndex : HiResPagesToMap) { FLumenCard& Card = Cards[VirtualPageIndex.CardIndex]; if (VirtualPageIndex.ResLevel > Card.MinAllocatedResLevel) { // Make room for new physical allocations bool bCanAlloc = true; while (!IsPhysicalSpaceAvailable(Card, VirtualPageIndex.ResLevel, /*bSinglePage*/ true)) { // Don't want to evict pages which may be picked up a jittering tile feedback const int32 MaxFramesSinceLastUsed = Lumen::GetFeedbackBufferTileSize() * Lumen::GetFeedbackBufferTileSize(); if (!EvictOldestAllocation(MaxFramesSinceLastUsed, DirtyCards)) { bCanAlloc = false; break; } } // Can we fit this card into the temporary card capture allocator? if (!CaptureAtlasAllocator.IsSpaceAvailable(Card, VirtualPageIndex.ResLevel, /*bSinglePage*/ true)) { bCanAlloc = false; } const FLumenMeshCards& MeshCardsElement = MeshCards[Card.MeshCardsIndex]; const FLumenPrimitiveGroup& PrimitiveGroup = PrimitiveGroups[MeshCardsElement.PrimitiveGroupIndex]; if (bCanAlloc && UpdateStaticMeshes(PrimitiveGroup, LumenCardRenderer)) { const bool bLockPages = false; const bool bResampleLastLighting = Card.IsAllocated(); ReallocVirtualSurface(Card, VirtualPageIndex.CardIndex, VirtualPageIndex.ResLevel, bLockPages); FLumenSurfaceMipMap& MipMap = Card.GetMipMap(VirtualPageIndex.ResLevel); const int32 PageIndex = MipMap.GetPageTableIndex(VirtualPageIndex.LocalPageIndex); FLumenPageTableEntry& PageTableEntry = GetPageTableEntry(PageIndex); if (!PageTableEntry.IsMapped()) { MapSurfaceCachePage(MipMap, PageIndex, GPUMask); check(PageTableEntry.IsMapped()); // Allocate space in temporary allocation atlas FLumenSurfaceCacheAllocator::FAllocation CardCaptureAllocation; CaptureAtlasAllocator.Allocate(PageTableEntry, CardCaptureAllocation); check(CardCaptureAllocation.PhysicalPageCoord.X >= 0); CardPagesToRender.Add(FCardPageRenderData( MainView, Card, PageTableEntry.CardUVRect, CardCaptureAllocation.PhysicalAtlasRect, PageTableEntry.PhysicalAtlasRect, MeshCardsElement.PrimitiveGroupIndex, VirtualPageIndex.CardIndex, PageIndex, bResampleLastLighting)); for (uint32 GPUIndex : GPUMask) { LastCapturedPageHeap[GPUIndex].Update(GetSurfaceCacheUpdateFrameIndex(), PageIndex); } LumenCardRenderer.NumCardTexelsToCapture += PageTableEntry.PhysicalAtlasRect.Area(); DirtyCards.Add(VirtualPageIndex.CardIndex); } } } } // Reconcile removal, addition, and res change of cards. This must be done after querying CardSharingInfoMap because we // copy cards from the physical atlas before current frame capture data is merged back so CardSharingInfoMap needs to // reflect the state right after last frame captures FlushPendingCardSharingInfos(); // Process any surface cache page invalidation requests { QUICK_SCOPE_CYCLE_COUNTER(SceneCardCaptureInvalidation); if (CVarLumenSceneCardCaptureEnableInvalidation.GetValueOnRenderThread() == 0) { for (uint32 GPUIndex = 0; GPUIndex < GNumExplicitGPUsForRendering; GPUIndex++) { PagesToRecaptureHeap[GPUIndex].Clear(); } } FBinaryHeap& PageHeap = PagesToRecaptureHeap[GPUMask.GetFirstIndex()]; while (PageHeap.Num() > 0) { const uint32 PageTableIndex = PageHeap.Top(); if (RecaptureCardPage(MainView, LumenCardRenderer, CaptureAtlasAllocator, GPUMask, PageTableIndex)) { PageHeap.Pop(); } else { break; } } } // Finally process card refresh to capture any material updates, or render cards that need to be initialized for the first time on // a given GPU in multi-GPU scenarios. Uninitialized cards on a particular GPU will have a zero captured frame index set when the // card was allocated. A zero frame index otherwise can't occur on a card, because the constructor sets SurfaceCacheUpdateFrameIndex // to 1, and IncrementSurfaceCacheUpdateFrameIndex skips over zero if it happens to wrap around. { QUICK_SCOPE_CYCLE_COUNTER(SceneCardCaptureRefresh); int32 NumTexelsLeftToRefresh = GetCardCaptureRefreshNumTexels(); int32 NumPagesLeftToRefesh = FMath::Min((int32)GetCardCaptureRefreshNumPages(), MaxTileCapturesPerFrame - CardPagesToRender.Num()); FBinaryHeap& PageHeap = LastCapturedPageHeap[GPUMask.GetFirstIndex()]; bool bCanCapture = true; while (PageHeap.Num() > 0 && bCanCapture) { bCanCapture = false; const uint32 PageTableIndex = PageHeap.Top(); const uint32 CapturedSurfaceCacheFrameIndex = PageHeap.GetKey(PageTableIndex); const int32 FramesSinceLastUpdated = GetSurfaceCacheUpdateFrameIndex() - CapturedSurfaceCacheFrameIndex; if (FramesSinceLastUpdated > 0) { #if WITH_MGPU // Limit number of re-captured texels and pages per frame, except always allow captures of uninitialized // cards where the captured frame index is zero (don't count them against the throttled limits). // Uninitialized cards on a particular GPU will always be at the front of the heap, due to the zero index, // so even if the limits are set to zero, we'll still process them if needed (the limit comparisons below // are >= 0, and will pass if nothing has been decremented from the limits yet). if ((CapturedSurfaceCacheFrameIndex != 0) || (GNumExplicitGPUsForRendering == 1)) #endif { FLumenPageTableEntry& PageTableEntry = GetPageTableEntry(PageTableIndex); check(PageTableEntry.IsMapped()); if (PageTableEntry.IsMapped()) { const FLumenCard& Card = Cards[PageTableEntry.CardIndex]; FLumenMipMapDesc MipMapDesc; Card.GetMipMapDesc(PageTableEntry.ResLevel, MipMapDesc); NumTexelsLeftToRefresh -= MipMapDesc.PageResolution.X * MipMapDesc.PageResolution.Y; NumPagesLeftToRefesh -= 1; } } if (NumTexelsLeftToRefresh >= 0 && NumPagesLeftToRefesh >= 0) { bCanCapture = RecaptureCardPage(MainView, LumenCardRenderer, CaptureAtlasAllocator, GPUMask, PageTableIndex); } } } } // Evict pages which weren't used recently if (!Lumen::IsSurfaceCacheFrozen()) { uint32 MaxFramesSinceLastUsed = FMath::Max(GSurfaceCacheNumFramesToKeepUnusedPages, 0); while (EvictOldestAllocation(MaxFramesSinceLastUsed, DirtyCards)) { } } for (int32 CardIndex : DirtyCards.Array) { FLumenCard& Card = Cards[CardIndex]; UpdateCardMipMapHierarchy(Card); CardIndicesToUpdateInBuffer.Add(CardIndex); } } void ProcessSceneRemoveOpsReadbackData(FLumenSceneData& LumenSceneData, const FLumenSceneReadback::FRemoveOp* RemoveOpsData) { if (RemoveOpsData) { // #lumen_todo: Temporary workaround until we optimized FLumenSurfaceCacheAllocator::Free to use fast batched removes int32 NumMeshCardsRemoves = 0; const int32 MaxMeshCardsRemoves = LumenScene::GetMaxMeshCardsRemovesPerFrame(); // First element encodes array size const int32 HeaderSize = 1; const int32 NumReadbackElements = FMath::Min(RemoveOpsData[0].PrimitiveGroupIndex, LumenSceneData.SceneReadback.GetMaxRemoveOps() - HeaderSize); for (int32 ElementIndex = 0; ElementIndex < NumReadbackElements; ++ElementIndex) { if (NumMeshCardsRemoves >= MaxMeshCardsRemoves) { break; } const uint32 PrimitiveGroupIndex = RemoveOpsData[ElementIndex + HeaderSize].PrimitiveGroupIndex; if (LumenSceneData.PrimitiveGroups.IsAllocated(PrimitiveGroupIndex)) { const FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData.PrimitiveGroups[PrimitiveGroupIndex]; if (PrimitiveGroup.bValidMeshCards && PrimitiveGroup.MeshCardsIndex >= 0) { LumenSceneData.RemoveMeshCards(PrimitiveGroupIndex); ++NumMeshCardsRemoves; } } } } } void ProcessSceneAddOpsReadbackData(FLumenSceneData& LumenSceneData, const FLumenSceneReadback::FAddOp* AddOpsData) { if (AddOpsData) { TArray MeshCardsAdds; // First element encodes array size const int32 HeaderSize = 1; const int32 NumReadbackElements = FMath::Min(AddOpsData[0].PrimitiveGroupIndex, LumenSceneData.SceneReadback.GetMaxAddOps() - HeaderSize); for (int32 ElementIndex = 0; ElementIndex < NumReadbackElements; ++ElementIndex) { const FLumenSceneReadback::FAddOp AddOp = AddOpsData[ElementIndex + HeaderSize]; if (LumenSceneData.PrimitiveGroups.IsAllocated(AddOp.PrimitiveGroupIndex)) { const FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData.PrimitiveGroups[AddOp.PrimitiveGroupIndex]; if (PrimitiveGroup.bValidMeshCards && PrimitiveGroup.MeshCardsIndex == -1) { FMeshCardsAdd& MeshCardsAdd = MeshCardsAdds.AddDefaulted_GetRef(); MeshCardsAdd.PrimitiveGroupIndex = AddOp.PrimitiveGroupIndex; MeshCardsAdd.DistanceSquared = AddOp.DistanceSq; } } } if (MeshCardsAdds.Num() > 0) { QUICK_SCOPE_CYCLE_COUNTER(SortAdds); struct FSortBySmallerDistance { FORCEINLINE bool operator()(const FMeshCardsAdd& A, const FMeshCardsAdd& B) const { return A.DistanceSquared < B.DistanceSquared; } }; MeshCardsAdds.Sort(FSortBySmallerDistance()); } const int32 MeshCardsToAddPerFrame = LumenScene::GetMaxMeshCardsToAddPerFrame(); for (int32 MeshCardsIndex = 0; MeshCardsIndex < FMath::Min(MeshCardsAdds.Num(), MeshCardsToAddPerFrame); ++MeshCardsIndex) { const FMeshCardsAdd& MeshCardsAdd = MeshCardsAdds[MeshCardsIndex]; LumenSceneData.AddMeshCards(MeshCardsAdd.PrimitiveGroupIndex); } } } void UpdateSurfaceCachePrimitives( FLumenSceneData& LumenSceneData, const TArray>& LumenSceneCameraOrigins, bool bOrthographicCamera, float LumenSceneDetail, float MaxCardUpdateDistanceFromCamera, FLumenCardRenderer& LumenCardRenderer, bool bAddTranslucentToCache) { QUICK_SCOPE_CYCLE_COUNTER(UpdateSurfaceCachePrimitives); { const bool bExecuteInParallel = FApp::ShouldUseThreadingForPerformance() && GLumenSceneParallelUpdate != 0; const int32 NumPrimitiveTasks = ParallelForImpl::GetNumberOfThreadTasks(LumenSceneData.PrimitiveCullingInfos.GetMaxIndex(), GLumenScenePrimitivesPerTask, EParallelForFlags::None); const int32 NumPrimitivesPerTask = FMath::DivideAndRoundUp(LumenSceneData.PrimitiveCullingInfos.GetMaxIndex(), NumPrimitiveTasks); TArray PrimitiveTasks; PrimitiveTasks.Reserve(NumPrimitiveTasks); for (int32 TaskIndex = 0; TaskIndex < NumPrimitiveTasks; ++TaskIndex) { PrimitiveTasks.Emplace( LumenSceneData.PrimitiveCullingInfos, LumenSceneCameraOrigins, bOrthographicCamera, LumenSceneDetail, MaxCardUpdateDistanceFromCamera, TaskIndex * NumPrimitivesPerTask, NumPrimitivesPerTask, bAddTranslucentToCache); } ParallelFor( NumPrimitiveTasks, [&PrimitiveTasks](int32 Index) { PrimitiveTasks[Index].AnyThreadTask(); }, !bExecuteInParallel); TArray InstanceRanges; TArray RebasedRangeOffsets; int32 NumInstances = 0; for (int32 TaskIndex = 0; TaskIndex < NumPrimitiveTasks; ++TaskIndex) { const FLumenSurfaceCacheCullPrimitivesTask& Task = PrimitiveTasks[TaskIndex]; InstanceRanges.Reserve(InstanceRanges.Num() + Task.InstanceCullingRanges.Num()); RebasedRangeOffsets.Reserve(RebasedRangeOffsets.Num() + Task.InstanceCullingRanges.Num()); for (const FInstanceRange& InstanceRange : Task.InstanceCullingRanges) { InstanceRanges.Add(InstanceRange); RebasedRangeOffsets.Add(NumInstances); NumInstances += InstanceRange.Num; } } RebasedRangeOffsets.Add(NumInstances); const int32 NumInstanceTasks = ParallelForImpl::GetNumberOfThreadTasks(NumInstances, GLumenScenePrimitivesPerTask, EParallelForFlags::None); const int32 NumInstancesPerTask = FMath::DivideAndRoundUp(NumInstances, NumInstanceTasks); TArray InstanceTasks; InstanceTasks.Reserve(NumInstanceTasks); for (int32 TaskIndex = 0; TaskIndex < NumInstanceTasks; ++TaskIndex) { InstanceTasks.Emplace( LumenSceneData.InstanceCullingInfos, InstanceRanges, RebasedRangeOffsets, LumenSceneCameraOrigins, bOrthographicCamera, LumenSceneDetail, MaxCardUpdateDistanceFromCamera, TaskIndex * NumInstancesPerTask, NumInstancesPerTask, bAddTranslucentToCache); } ParallelFor( NumInstanceTasks, [&InstanceTasks](int32 Index) { InstanceTasks[Index].AnyThreadTask(); }, !bExecuteInParallel); TArray MeshCardsAdds; for (int32 TaskIndex = 0; TaskIndex < NumPrimitiveTasks; ++TaskIndex) { const FLumenSurfaceCacheCullPrimitivesTask& Task = PrimitiveTasks[TaskIndex]; LumenSceneData.NumMeshCardsToAdd += Task.MeshCardsAdds.Num(); // Append requests to the global array { MeshCardsAdds.Reserve(MeshCardsAdds.Num() + Task.MeshCardsAdds.Num()); for (int32 RequestIndex = 0; RequestIndex < Task.MeshCardsAdds.Num(); ++RequestIndex) { MeshCardsAdds.Add(Task.MeshCardsAdds[RequestIndex]); } } // #lumen_todo: Temporary workaround until we optimized FLumenSurfaceCacheAllocator::Free to use fast batched removes int32 NumMeshCardsRemoves = 0; const int32 MaxMeshCardsRemoves = LumenScene::GetMaxMeshCardsRemovesPerFrame(); for (const FMeshCardsRemove& MeshCardsRemove : Task.MeshCardsRemoves) { if (NumMeshCardsRemoves >= MaxMeshCardsRemoves) { break; } ++NumMeshCardsRemoves; LumenSceneData.RemoveMeshCards(MeshCardsRemove.PrimitiveGroupIndex); } } for (int32 TaskIndex = 0; TaskIndex < NumInstanceTasks; ++TaskIndex) { const FLumenSurfaceCacheCullInstancesTask& Task = InstanceTasks[TaskIndex]; LumenSceneData.NumMeshCardsToAdd += Task.MeshCardsAdds.Num(); // Append requests to the global array { MeshCardsAdds.Reserve(MeshCardsAdds.Num() + Task.MeshCardsAdds.Num()); for (int32 RequestIndex = 0; RequestIndex < Task.MeshCardsAdds.Num(); ++RequestIndex) { MeshCardsAdds.Add(Task.MeshCardsAdds[RequestIndex]); } } // #lumen_todo: Temporary workaround until we optimized FLumenSurfaceCacheAllocator::Free to use fast batched removes int32 NumMeshCardsRemoves = 0; const int32 MaxMeshCardsRemoves = LumenScene::GetMaxMeshCardsRemovesPerFrame(); for (const FMeshCardsRemove& MeshCardsRemove : Task.MeshCardsRemoves) { if (NumMeshCardsRemoves >= MaxMeshCardsRemoves) { break; } ++NumMeshCardsRemoves; LumenSceneData.RemoveMeshCards(MeshCardsRemove.PrimitiveGroupIndex); } } if (MeshCardsAdds.Num() > 0) { QUICK_SCOPE_CYCLE_COUNTER(SortAdds); struct FSortBySmallerDistance { FORCEINLINE bool operator()(const FMeshCardsAdd& A, const FMeshCardsAdd& B) const { return A.DistanceSquared < B.DistanceSquared; } }; MeshCardsAdds.Sort(FSortBySmallerDistance()); } const int32 MeshCardsToAddPerFrame = LumenScene::GetMaxMeshCardsToAddPerFrame(); for (int32 MeshCardsIndex = 0; MeshCardsIndex < FMath::Min(MeshCardsAdds.Num(), MeshCardsToAddPerFrame); ++MeshCardsIndex) { const FMeshCardsAdd& MeshCardsAdd = MeshCardsAdds[MeshCardsIndex]; LumenSceneData.AddMeshCards(MeshCardsAdd.PrimitiveGroupIndex); } } } void UpdateSurfaceCacheMeshCards( FLumenSceneData& LumenSceneData, FLumenSceneData::FFeedbackData LumenFeedbackData, const TArray>& LumenSceneCameraOrigins, bool bOrthographicCamera, float LumenSceneDetail, float MaxCardUpdateDistanceFromCamera, TArray& SurfaceCacheRequests, const FViewFamilyInfo& ViewFamily) { QUICK_SCOPE_CYCLE_COUNTER(UpdateMeshCards); const int32 NumMeshCardsPerTask = FMath::Max(GLumenSceneMeshCardsPerTask, 1); const int32 NumTasks = FMath::DivideAndRoundUp(LumenSceneData.MeshCards.Num(), NumMeshCardsPerTask); if (NumTasks == 0) { return; } int32 RequestHistogram[Lumen::NumDistanceBuckets] { 0 }; TArray Tasks; Tasks.Reserve(NumTasks); for (int32 TaskIndex = 0; TaskIndex < NumTasks; ++TaskIndex) { Tasks.Emplace( LumenSceneData.MeshCards, LumenSceneData.Cards, LumenSceneCameraOrigins, bOrthographicCamera, LumenSceneData.SurfaceCacheResolution, LumenSceneDetail, MaxCardUpdateDistanceFromCamera, TaskIndex * NumMeshCardsPerTask, NumMeshCardsPerTask); } const bool bExecuteInParallel = FApp::ShouldUseThreadingForPerformance() && GLumenSceneParallelUpdate != 0; ParallelFor(Tasks.Num(), [&Tasks](int32 Index) { Tasks[Index].AnyThreadTask(); }, !bExecuteInParallel); uint32 TotalSurfaceCacheRequests = 0; for (int32 TaskIndex = 0; TaskIndex < Tasks.Num(); ++TaskIndex) { const FLumenSurfaceCacheUpdateMeshCardsTask& Task = Tasks[TaskIndex]; TotalSurfaceCacheRequests += Task.SurfaceCacheRequests.Num(); } for (int32 TaskIndex = 0; TaskIndex < Tasks.Num(); ++TaskIndex) { const FLumenSurfaceCacheUpdateMeshCardsTask& Task = Tasks[TaskIndex]; LumenSceneData.NumLockedCardsToUpdate += Task.SurfaceCacheRequests.Num(); for (int32 i = 0; i < Lumen::NumDistanceBuckets; ++i) { RequestHistogram[i] += Task.Histogram[i]; } for (int32 CardIndex : Task.CardsToHide) { FLumenCard& Card = LumenSceneData.Cards[CardIndex]; if (Card.bVisible) { LumenSceneData.RemoveCardFromAtlas(CardIndex); Card.bVisible = false; } } } LumenSceneData.UpdateSurfaceCacheFeedback(LumenFeedbackData, LumenSceneCameraOrigins, Tasks[0].SurfaceCacheRequests, ViewFamily, RequestHistogram); int32 SurfaceCacheRequestsCount = 0; int32 LastBucketRequestCount = 0; int32 LastBucketIndex = 0; for (; LastBucketIndex < Lumen::NumDistanceBuckets; ++LastBucketIndex) { SurfaceCacheRequestsCount += RequestHistogram[LastBucketIndex]; if (SurfaceCacheRequestsCount >= GetMaxLumenSceneCardCapturesPerFrame()) { LastBucketRequestCount = GLumenSceneCardCapturesPerFrame - (SurfaceCacheRequestsCount - RequestHistogram[LastBucketIndex]); SurfaceCacheRequestsCount = GLumenSceneCardCapturesPerFrame; break; } } if (SurfaceCacheRequestsCount == 0) { return; } SurfaceCacheRequests.Reserve(SurfaceCacheRequestsCount); for (int32 TaskIndex = 0; TaskIndex < Tasks.Num(); ++TaskIndex) { const FLumenSurfaceCacheUpdateMeshCardsTask& Task = Tasks[TaskIndex]; for (int32 RequestIndex = 0; RequestIndex < Task.SurfaceCacheRequests.Num(); ++RequestIndex) { const FSurfaceCacheRequest& Request = Task.SurfaceCacheRequests[RequestIndex]; const int32 BucketIndex = Lumen::GetMeshCardDistanceBin(Request.Distance); if (BucketIndex > LastBucketIndex) { continue; } if (BucketIndex == LastBucketIndex) { if (LastBucketRequestCount == 0) { continue; } --LastBucketRequestCount; } SurfaceCacheRequests.Add(Request); if (--SurfaceCacheRequestsCount == 0) { break; } } if (SurfaceCacheRequestsCount == 0) { break; } } } extern void UpdateLumenScenePrimitives(FRHIGPUMask GPUMask, FScene* Scene); void AllocateResampledCardCaptureAtlas(FRDGBuilder& GraphBuilder, FIntPoint CardCaptureAtlasSize, FResampledCardCaptureAtlas& CardCaptureAtlas) { CardCaptureAtlas.Size = CardCaptureAtlasSize; CardCaptureAtlas.DirectLighting = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D( CardCaptureAtlasSize, Lumen::GetDirectLightingAtlasFormat(), FClearValueBinding::Green, TexCreate_ShaderResource | TexCreate_NoFastClear | TexCreate_UAV), TEXT("Lumen.ResampledCardCaptureDirectLighting")); CardCaptureAtlas.IndirectLighting = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D( CardCaptureAtlasSize, Lumen::GetIndirectLightingAtlasFormat(), FClearValueBinding::Green, TexCreate_ShaderResource | TexCreate_NoFastClear | TexCreate_UAV), TEXT("Lumen.ResampledCardCaptureIndirectLighting")); CardCaptureAtlas.NumFramesAccumulated = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D( CardCaptureAtlasSize, Lumen::GetNumFramesAccumulatedAtlasFormat(), FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_NoFastClear | TexCreate_UAV), TEXT("Lumen.ResampledCardCaptureNumFramesAccumulated")); const FIntPoint CardCaptureAtlasSizeInTiles = CardCaptureAtlasSize / Lumen::CardTileSize; CardCaptureAtlas.TileShadowDownsampleFactor = GraphBuilder.CreateBuffer( FRDGBufferDesc::CreateBufferDesc( sizeof(uint32), CardCaptureAtlasSizeInTiles.X * CardCaptureAtlasSizeInTiles.Y * Lumen::CardTileShadowDownsampleFactorDwords), TEXT("Lumen.ResampledCardCaptureTileShadowDownsampleFactorAtlas")); } class FResampleLightingHistoryToCardCaptureAtlasCS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FResampleLightingHistoryToCardCaptureAtlasCS); SHADER_USE_PARAMETER_STRUCT(FResampleLightingHistoryToCardCaptureAtlasCS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FLumenCardScene, LumenCardScene) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, DirectLightingAtlas) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, IndirectLightingAtlas) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, RadiosityNumFramesAccumulatedAtlas) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, TileShadowDownsampleFactorAtlasForResampling) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWDirectLightingCardCaptureAtlas) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWRadiosityCardCaptureAtlas) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWRadiosityNumFramesAccumulatedCardCaptureAtlas) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWTileShadowDownsampleFactorAtlas) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, NewCardPageResampleData) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, NewCardTileResampleData) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, RectCoordBuffer) SHADER_PARAMETER(uint32, CardCaptureAtlasWidthInTiles) END_SHADER_PARAMETER_STRUCT() using FPermutationDomain = TShaderPermutationDomain<>; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportLumenGI(Parameters.Platform); } }; IMPLEMENT_GLOBAL_SHADER(FResampleLightingHistoryToCardCaptureAtlasCS, "/Engine/Private/Lumen/LumenSceneLighting.usf", "ResampleLightingHistoryToCardCaptureAtlasCS", SF_Compute); class FLumenCardResamplePS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FLumenCardResamplePS); SHADER_USE_PARAMETER_STRUCT(FLumenCardResamplePS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FLumenCardScene, LumenCardScene) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceAlbedoAtlas) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceOpacityAtlas) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceNormalAtlas) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceEmissiveAtlas) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceDepthAtlas) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, SourceCardData) END_SHADER_PARAMETER_STRUCT() using FPermutationDomain = TShaderPermutationDomain<>; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportLumenGI(Parameters.Platform); } }; IMPLEMENT_GLOBAL_SHADER(FLumenCardResamplePS, "/Engine/Private/Lumen/SurfaceCache/LumenSurfaceCache.usf", "LumenCardResamplePS", SF_Pixel); BEGIN_SHADER_PARAMETER_STRUCT(FLumenCardResampleParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FPixelShaderUtils::FRasterizeToRectsVS::FParameters, VS) SHADER_PARAMETER_STRUCT_INCLUDE(FLumenCardResamplePS::FParameters, PS) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() void ResampleLumenCards( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FScene* Scene, const FLumenSceneData& LumenSceneData, const FLumenSceneFrameTemporaries& FrameTemporaries, TConstArrayView CardPagesToRender, bool bHasAnyCardCopy, FCardCaptureAtlas& OutCardCaptureAtlas, FResampledCardCaptureAtlas& OutCardCaptureLightingAtlas, FRDGBufferSRVRef& OutCardCaptureRectBufferSRV, int32& OutNumPagesNeedDilation) { if (!FrameTemporaries.PageTableBufferSRV || !FrameTemporaries.CardBufferSRV) { return; } const bool bNeedsResample = bHasAnyCardCopy || GLumenSceneSurfaceCacheResampleLighting != 0; const uint32 NumRects = CardPagesToRender.Num(); FRDGUploadData CardCaptureRectArray(GraphBuilder, NumRects); FRDGUploadData CardPageResampleDataArray(GraphBuilder, bNeedsResample ? NumRects * 2 : 1); uint32 NumCaptureTiles = 0; OutNumPagesNeedDilation = 0; for (uint32 Index = 0; Index < NumRects; ++Index) { const FCardPageRenderData& CardPageRenderData = CardPagesToRender[Index]; const FIntPoint RectSizeInTiles = CardPageRenderData.CardCaptureAtlasRect.Size() / Lumen::CardTileSize; NumCaptureTiles += RectSizeInTiles.X * RectSizeInTiles.Y; FUintVector4& Rect = CardCaptureRectArray[Index]; Rect.X = CardPageRenderData.CardCaptureAtlasRect.Min.X; Rect.Y = CardPageRenderData.CardCaptureAtlasRect.Min.Y; Rect.Z = CardPageRenderData.CardCaptureAtlasRect.Max.X; Rect.W = CardPageRenderData.CardCaptureAtlasRect.Max.Y; if (CardPageRenderData.NeedsRender() && CardPageRenderData.DilationMode == ELumenCardDilationMode::DilateOneTexel) { ++OutNumPagesNeedDilation; } if (bNeedsResample) { FUintVector4& CardPageResampleData0 = CardPageResampleDataArray[Index * 2 + 0]; FUintVector4& CardPageResampleData1 = CardPageResampleDataArray[Index * 2 + 1]; CardPageResampleData0.X = CardPageRenderData.bResampleLastLighting ? CardPageRenderData.CardIndex : -1; CardPageResampleData0.Y = CardPageRenderData.NeedsRender() ? 0 : CardPageRenderData.CopyCardIndex + 1; CardPageResampleData0.Y |= CardPageRenderData.bAxisXFlipped ? 0x80000000 : 0; CardPageResampleData1 = FUintVector4( *(const uint32*)&CardPageRenderData.CardUVRect.X, *(const uint32*)&CardPageRenderData.CardUVRect.Y, *(const uint32*)&CardPageRenderData.CardUVRect.Z, *(const uint32*)&CardPageRenderData.CardUVRect.W); } } FRDGBufferRef CardCaptureRectBuffer = CreateUploadBuffer(GraphBuilder, TEXT("Lumen.CardCaptureRects"), sizeof(FUintVector4), FMath::RoundUpToPowerOfTwo(NumRects), CardCaptureRectArray); OutCardCaptureRectBufferSRV = GraphBuilder.CreateSRV(CardCaptureRectBuffer, PF_R32G32B32A32_UINT); if (!bNeedsResample) { return; } FRDGBufferRef CardPageResampleDataBuffer = CreateUploadBuffer(GraphBuilder, TEXT("Lumen.CardPageResampleDataBuffer"), sizeof(FUintVector4), FMath::RoundUpToPowerOfTwo(NumRects * 2), CardPageResampleDataArray); FRDGBufferSRVRef CardPageResampleDataBufferSRV = GraphBuilder.CreateSRV(CardPageResampleDataBuffer, PF_R32G32B32A32_UINT); // Resample card material attributes if requested. Otherwise, clear if (bHasAnyCardCopy) { LumenScene::AllocateCardCaptureAtlas(GraphBuilder, LumenSceneData.GetCardCaptureAtlasSize(), OutCardCaptureAtlas, Scene->GetShaderPlatform()); auto* PassParameters = GraphBuilder.AllocParameters(); PassParameters->PS.View = View.ViewUniformBuffer; PassParameters->PS.LumenCardScene = FrameTemporaries.LumenCardSceneUniformBuffer; PassParameters->PS.SourceAlbedoAtlas = FrameTemporaries.AlbedoAtlas; PassParameters->PS.SourceOpacityAtlas = FrameTemporaries.OpacityAtlas; PassParameters->PS.SourceNormalAtlas = FrameTemporaries.NormalAtlas; PassParameters->PS.SourceEmissiveAtlas = FrameTemporaries.EmissiveAtlas; PassParameters->PS.SourceDepthAtlas = FrameTemporaries.DepthAtlas; PassParameters->PS.SourceCardData = CardPageResampleDataBufferSRV; PassParameters->RenderTargets[0] = FRenderTargetBinding(OutCardCaptureAtlas.Albedo, ERenderTargetLoadAction::ELoad); PassParameters->RenderTargets[1] = FRenderTargetBinding(OutCardCaptureAtlas.Normal, ERenderTargetLoadAction::ELoad); PassParameters->RenderTargets[2] = FRenderTargetBinding(OutCardCaptureAtlas.Emissive, ERenderTargetLoadAction::ELoad); PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(OutCardCaptureAtlas.DepthStencil, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthWrite_StencilWrite); auto PixelShader = View.ShaderMap->GetShader(); FPixelShaderUtils::AddRasterizeToRectsPass( GraphBuilder, View.ShaderMap, RDG_EVENT_NAME("ResampleLumenCards"), PixelShader, PassParameters, OutCardCaptureAtlas.Size, OutCardCaptureRectBufferSRV, NumRects, TStaticBlendState<>::GetRHI(), TStaticRasterizerState<>::GetRHI(), TStaticDepthStencilState::GetRHI()); } // Try to resample direct lighting and indirect lighting (radiosity) from existing surface cache to new captured cards if (GLumenSceneSurfaceCacheResampleLighting != 0) { AllocateResampledCardCaptureAtlas(GraphBuilder, LumenSceneData.GetCardCaptureAtlasSize(), OutCardCaptureLightingAtlas); FRDGUploadData CardTileResampleDataArray(GraphBuilder, NumCaptureTiles); for (int32 RectIndex = 0, TileIndex = 0; RectIndex < CardPagesToRender.Num(); RectIndex++) { const FCardPageRenderData& CardPageRenderData = CardPagesToRender[RectIndex]; const FIntPoint RectSizeInTiles = CardPageRenderData.CardCaptureAtlasRect.Size() / Lumen::CardTileSize; for (uint32 TileY = 0; TileY < (uint32)RectSizeInTiles.Y; ++TileY) { for (uint32 TileX = 0; TileX < (uint32)RectSizeInTiles.X; ++TileX) { const uint32 PackedTileData = (RectIndex << 8u) | (TileY << 4u) | TileX; CardTileResampleDataArray[TileIndex++] = PackedTileData; } } } FRDGBufferRef CardTileResampleDataBuffer = CreateUploadBuffer(GraphBuilder, TEXT("Lumen.CardTileResampleDataBuffer"), sizeof(uint32), FMath::RoundUpToPowerOfTwo(NumCaptureTiles), CardTileResampleDataArray); FRDGBufferSRVRef CardTileResampleDataBufferSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(CardTileResampleDataBuffer, PF_R32_UINT)); auto* PassParameters = GraphBuilder.AllocParameters(); PassParameters->View = View.ViewUniformBuffer; PassParameters->LumenCardScene = FrameTemporaries.LumenCardSceneUniformBuffer; PassParameters->DirectLightingAtlas = FrameTemporaries.DirectLightingAtlas; PassParameters->IndirectLightingAtlas = FrameTemporaries.IndirectLightingAtlas; PassParameters->RadiosityNumFramesAccumulatedAtlas = FrameTemporaries.RadiosityNumFramesAccumulatedAtlas; PassParameters->TileShadowDownsampleFactorAtlasForResampling = GraphBuilder.CreateSRV(FrameTemporaries.TileShadowDownsampleFactorAtlas, PF_R32G32B32A32_UINT); PassParameters->RWDirectLightingCardCaptureAtlas = GraphBuilder.CreateUAV(OutCardCaptureLightingAtlas.DirectLighting); PassParameters->RWRadiosityCardCaptureAtlas = GraphBuilder.CreateUAV(OutCardCaptureLightingAtlas.IndirectLighting); PassParameters->RWRadiosityNumFramesAccumulatedCardCaptureAtlas = GraphBuilder.CreateUAV(OutCardCaptureLightingAtlas.NumFramesAccumulated); PassParameters->RWTileShadowDownsampleFactorAtlas = GraphBuilder.CreateUAV(OutCardCaptureLightingAtlas.TileShadowDownsampleFactor, PF_R32G32B32A32_UINT); PassParameters->NewCardPageResampleData = CardPageResampleDataBufferSRV; PassParameters->NewCardTileResampleData = CardTileResampleDataBufferSRV; PassParameters->RectCoordBuffer = OutCardCaptureRectBufferSRV; PassParameters->CardCaptureAtlasWidthInTiles = LumenSceneData.GetCardCaptureAtlasSize().X / Lumen::CardTileSize; auto ComputeShader = View.ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("ResampleLightingHistoryToCardCaptureAtlasCS"), ComputeShader, PassParameters, FIntVector(NumCaptureTiles, 1, 1)); } } void FLumenSceneData::FillFrameTemporaries(FRDGBuilder& GraphBuilder, FLumenSceneFrameTemporaries& FrameTemporaries) { const auto FillBuffer = [&](FRDGBufferSRV*& OutSRV, const TRefCountPtr& InBuffer) { if (!OutSRV && InBuffer) { OutSRV = GraphBuilder.CreateSRV(GraphBuilder.RegisterExternalBuffer(InBuffer)); } }; FillBuffer(FrameTemporaries.CardBufferSRV, CardBuffer); FillBuffer(FrameTemporaries.MeshCardsBufferSRV, MeshCardsBuffer); FillBuffer(FrameTemporaries.HeightfieldBufferSRV, HeightfieldBuffer); FillBuffer(FrameTemporaries.PrimitiveGroupBufferSRV, PrimitiveGroupBuffer); FillBuffer(FrameTemporaries.SceneInstanceIndexToMeshCardsIndexBufferSRV, SceneInstanceIndexToMeshCardsIndexBuffer); FillBuffer(FrameTemporaries.PageTableBufferSRV, PageTableBuffer); FillBuffer(FrameTemporaries.CardPageBufferSRV, CardPageBuffer); const auto FillTexture = [&](FRDGTexture*& OutTexture, const TRefCountPtr& InTexture) { if (!OutTexture && InTexture) { OutTexture = GraphBuilder.RegisterExternalTexture(InTexture); } }; FillTexture(FrameTemporaries.AlbedoAtlas, AlbedoAtlas); FillTexture(FrameTemporaries.OpacityAtlas, OpacityAtlas); FillTexture(FrameTemporaries.NormalAtlas, NormalAtlas); FillTexture(FrameTemporaries.EmissiveAtlas, EmissiveAtlas); FillTexture(FrameTemporaries.DepthAtlas, DepthAtlas); FillTexture(FrameTemporaries.DirectLightingAtlas, DirectLightingAtlas); FillTexture(FrameTemporaries.IndirectLightingAtlas, IndirectLightingAtlas); FillTexture(FrameTemporaries.RadiosityNumFramesAccumulatedAtlas, RadiosityNumFramesAccumulatedAtlas); FillTexture(FrameTemporaries.FinalLightingAtlas, FinalLightingAtlas); if (!FrameTemporaries.TileShadowDownsampleFactorAtlas && TileShadowDownsampleFactorAtlas) { FrameTemporaries.TileShadowDownsampleFactorAtlas = GraphBuilder.RegisterExternalBuffer(TileShadowDownsampleFactorAtlas); } FillTexture(FrameTemporaries.DiffuseLightingAndSecondMomentHistoryAtlas, DiffuseLightingAndSecondMomentHistoryAtlas); FillTexture(FrameTemporaries.NumFramesAccumulatedHistoryAtlas, NumFramesAccumulatedHistoryAtlas); } void FDeferredShadingSceneRenderer::BeginUpdateLumenSceneTasks(FRDGBuilder& GraphBuilder, FLumenSceneFrameTemporaries& FrameTemporaries) { LLM_SCOPE_BYTAG(Lumen); bool bAnyLumenActive = false; bool bHasOrthographicView = false; for (FViewInfo& View : Views) { bool bLumenActive = ShouldRenderLumenDiffuseGI(Scene, View); bAnyLumenActive = bAnyLumenActive || bLumenActive; if (!bHasOrthographicView && !View.IsPerspectiveProjection()) { bHasOrthographicView = true; } if (bLumenActive) { // Cache LumenSceneData pointer per view for efficient lookup of the view specific Lumen scene (also nice for debugging) View.ViewLumenSceneData = Scene->FindLumenSceneData(View.ViewState ? View.ViewState->GetShareOriginViewKey() : 0, View.GPUMask.GetFirstIndex()); #if WITH_MGPU if (View.ViewLumenSceneData->bViewSpecific) { // Update view specific scene data if the GPU mask changed (copies resources cross GPU so CPU and GPU data are coherent) View.ViewLumenSceneData->UpdateGPUMask(GraphBuilder, FrameTemporaries, View.ViewState->Lumen, View.GPUMask); } else if (View.GPUMask.GetFirstIndex() != 0) { // Otherwise, if this view is on a different GPU, we need to allocate GPU specific scene data (if not already allocated) if (View.ViewLumenSceneData == Scene->DefaultLumenSceneData) { View.ViewLumenSceneData = new FLumenSceneData(Scene->DefaultLumenSceneData->bTrackAllPrimitives); View.ViewLumenSceneData->CopyInitialData(*Scene->DefaultLumenSceneData); // Key shouldn't already exist in Scene, because "FindLumenSceneData" above should have found it FLumenSceneDataKey ByGPUIndex = { 0, View.GPUMask.GetFirstIndex() }; check(Scene->PerViewOrGPULumenSceneData.Find(ByGPUIndex) == nullptr); Scene->PerViewOrGPULumenSceneData.Emplace(ByGPUIndex, View.ViewLumenSceneData); } } #endif // WITH_MGPU } } LumenCardRenderer.Reset(); // Release Lumen scene resource if it's disabled by scalability if (!LumenDiffuseIndirect::IsAllowed()) { FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(Views[0]); LumenSceneData.ReleaseAtlas(); } if (!bAnyLumenActive || ViewFamily.EngineShowFlags.HitProxies) { return; } const FRHIGPUMask GPUMask = GraphBuilder.RHICmdList.GetGPUMask(); const bool bAllowCardSharing = LumenScene::AllowSurfaceCacheCardSharing(); const bool bAllowCardDownsampleFromSelf = CVarLumenSceneSurfaceCacheAllowCardDownsampleFromSelf.GetValueOnRenderThread() != 0; FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(Views[0]); LumenSceneData.bDebugClearAllCachedState = GLumenSceneRecaptureLumenSceneEveryFrame != 0 || LumenSceneData.bAllowCardSharing != bAllowCardSharing || LumenSceneData.bAllowCardDownsampleFromSelf != bAllowCardDownsampleFromSelf; LumenSceneData.bAllowCardSharing = bAllowCardSharing; LumenSceneData.bAllowCardDownsampleFromSelf = bAllowCardDownsampleFromSelf; FrameTemporaries.bReallocateAtlas = LumenSceneData.UpdateAtlasSize(); FLumenSceneData::FFeedbackData SurfaceCacheFeedbackData; { extern int32 GLumenSurfaceCacheFeedback; if (GLumenSurfaceCacheFeedback != 0) { FrameTemporaries.SurfaceCacheFeedbackBuffer = LumenSceneData.SurfaceCacheFeedback.GetLatestReadbackBuffer(); if (FrameTemporaries.SurfaceCacheFeedbackBuffer) { QUICK_SCOPE_CYCLE_COUNTER(LockSurfaceCacheFeedbackBuffer); SurfaceCacheFeedbackData.NumElements = Lumen::GetCompactedFeedbackBufferSize(); SurfaceCacheFeedbackData.Data = (const uint32*) FrameTemporaries.SurfaceCacheFeedbackBuffer->Lock(SurfaceCacheFeedbackData.NumElements * sizeof(uint32) * Lumen::FeedbackBufferElementStride); } } } const FLumenSceneReadback::FAddOp* SceneAddOpsReadbackData = nullptr; const FLumenSceneReadback::FRemoveOp* SceneRemoveOpsReadbackData = nullptr; if (CVarLumenSceneGPUDrivenUpdate.GetValueOnRenderThread() != 0) { QUICK_SCOPE_CYCLE_COUNTER(LockSceneReadbackBuffer); FLumenSceneReadback::FBuffersRHI ReadbackBuffers = LumenSceneData.SceneReadback.GetLatestReadbackBuffers(); FrameTemporaries.SceneAddOpsReadbackBuffer = ReadbackBuffers.AddOps; FrameTemporaries.SceneRemoveOpsReadbackBuffer = ReadbackBuffers.RemoveOps; if (ReadbackBuffers.AddOps) { SceneAddOpsReadbackData = (const FLumenSceneReadback::FAddOp*) FrameTemporaries.SceneAddOpsReadbackBuffer->Lock(LumenSceneData.SceneReadback.GetAddOpsBufferSizeInBytes()); } if (ReadbackBuffers.RemoveOps) { SceneRemoveOpsReadbackData = (const FLumenSceneReadback::FRemoveOp*)FrameTemporaries.SceneRemoveOpsReadbackBuffer->Lock(LumenSceneData.SceneReadback.GetRemoveOpsBufferSizeInBytes()); } } FrameTemporaries.UpdateSceneTask = GraphBuilder.AddSetupTask([this, GPUMask, &LumenSceneData, bReallocateAtlas = FrameTemporaries.bReallocateAtlas, SurfaceCacheFeedbackData, SceneAddOpsReadbackData, SceneRemoveOpsReadbackData, bHasOrthographicView] { SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_BeginUpdateLumenSceneTasks, FColor::Emerald); QUICK_SCOPE_CYCLE_COUNTER(BeginUpdateLumenSceneTasks); // Surface cache reset for debugging if ((GLumenSceneSurfaceCacheReset != 0) || (GLumenSceneSurfaceCacheResetEveryNthFrame > 0 && (ViewFamily.FrameNumber % (uint32)GLumenSceneSurfaceCacheResetEveryNthFrame == 0))) { LumenSceneData.bDebugClearAllCachedState = true; GLumenSceneSurfaceCacheReset = 0; } if (GLumenSceneForceEvictHiResPages != 0) { LumenSceneData.ForceEvictEntireCache(); GLumenSceneForceEvictHiResPages = 0; } LumenSceneData.NumMeshCardsToAdd = 0; LumenSceneData.NumLockedCardsToUpdate = 0; LumenSceneData.NumHiResPagesToAdd = 0; UpdateLumenScenePrimitives(GPUMask, Scene); if (LumenSceneData.bDebugClearAllCachedState || bReallocateAtlas) { LumenSceneData.RemoveAllMeshCards(); } TArray> LumenSceneCameraOrigins; float MaxCardUpdateDistanceFromCamera = 0.0f; float LumenSceneDetail = 0.0f; bool bAddTranslucentToCache = false; for (const FViewInfo& View : Views) { LumenSceneCameraOrigins.Add(Lumen::GetLumenSceneViewOrigin(View, Lumen::GetNumGlobalDFClipmaps(View) - 1)); MaxCardUpdateDistanceFromCamera = FMath::Max(MaxCardUpdateDistanceFromCamera, LumenScene::GetCardMaxDistance(View)); LumenSceneDetail = FMath::Max(LumenSceneDetail, FMath::Clamp(View.FinalPostProcessSettings.LumenSceneDetail, .125f, 8.0f)); bAddTranslucentToCache |= LumenReflections::UseTranslucentRayTracing(View) && LumenReflections::UseHitLighting(View, GetViewPipelineState(View).DiffuseIndirectMethod); bAddTranslucentToCache |= RayTracedTranslucency::IsEnabled(View); } // Add streaming view origins, only if there are futher apart than existing origins AddLumenStreamingViewOrigins(ViewFamily, LumenSceneCameraOrigins); const int32 MaxTileCapturesPerFrame = GetMaxTileCapturesPerFrame(); if (MaxTileCapturesPerFrame > 0) { QUICK_SCOPE_CYCLE_COUNTER(FillCardPagesToRender); TArray SurfaceCacheRequests; if (CVarLumenSceneGPUDrivenUpdate.GetValueOnRenderThread() != 0) { ProcessSceneRemoveOpsReadbackData(LumenSceneData, SceneRemoveOpsReadbackData); ProcessSceneAddOpsReadbackData(LumenSceneData, SceneAddOpsReadbackData); } else { UpdateSurfaceCachePrimitives( LumenSceneData, LumenSceneCameraOrigins, bHasOrthographicView, LumenSceneDetail, MaxCardUpdateDistanceFromCamera, LumenCardRenderer, bAddTranslucentToCache); } UpdateSurfaceCacheMeshCards( LumenSceneData, SurfaceCacheFeedbackData, LumenSceneCameraOrigins, bHasOrthographicView, LumenSceneDetail, MaxCardUpdateDistanceFromCamera, SurfaceCacheRequests, ViewFamily); LumenSceneData.ProcessLumenSurfaceCacheRequests( Views[0], MaxCardUpdateDistanceFromCamera, MaxTileCapturesPerFrame, LumenCardRenderer, GPUMask, SurfaceCacheRequests); } TArray& CardPagesToRender = LumenCardRenderer.CardPagesToRender; if (CardPagesToRender.Num() > 0) { QUICK_SCOPE_CYCLE_COUNTER(MeshPassSetup); #if (UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT) && STATS if (GLumenSceneSurfaceCacheLogUpdates != 0) { UE_LOG(LogRenderer, Log, TEXT("Surface Cache Updates: %d"), CardPagesToRender.Num()); if (GLumenSceneSurfaceCacheLogUpdates > 1) { for (const FCardPageRenderData& CardPageRenderData : CardPagesToRender) { const FLumenPrimitiveGroup& LumenPrimitiveGroup = LumenSceneData.PrimitiveGroups[CardPageRenderData.PrimitiveGroupIndex]; UE_LOG(LogRenderer, Log, TEXT("%s Instance:%d NumPrimsInGroup: %d"), *LumenPrimitiveGroup.Primitives[0]->Proxy->GetStatId().GetName().ToString(), LumenPrimitiveGroup.PrimitiveInstanceIndex, LumenPrimitiveGroup.Primitives.Num()); } } } #endif for (FCardPageRenderData& CardPageRenderData : CardPagesToRender) { CardPageRenderData.StartMeshDrawCommandIndex = LumenCardRenderer.MeshDrawCommands.Num(); CardPageRenderData.NumMeshDrawCommands = 0; int32 NumNanitePrimitives = 0; const FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData.PrimitiveGroups[CardPageRenderData.PrimitiveGroupIndex]; const FLumenCard& Card = LumenSceneData.Cards[CardPageRenderData.CardIndex]; ensure(Card.bVisible); if (PrimitiveGroup.bHeightfield) { LumenScene::AddCardCaptureDraws( Scene, CardPageRenderData, PrimitiveGroup, LumenSceneData.LandscapePrimitives, LumenCardRenderer.MeshDrawCommands, LumenCardRenderer.MeshDrawPrimitiveIds); } else { LumenScene::AddCardCaptureDraws( Scene, CardPageRenderData, PrimitiveGroup, PrimitiveGroup.Primitives, LumenCardRenderer.MeshDrawCommands, LumenCardRenderer.MeshDrawPrimitiveIds); } CardPageRenderData.NumMeshDrawCommands = LumenCardRenderer.MeshDrawCommands.Num() - CardPageRenderData.StartMeshDrawCommandIndex; } } }, MakeArrayView({ Scene->GetCacheMeshDrawCommandsTask(), Scene->GetCacheNaniteMaterialBinsTask() }), UE::Tasks::ETaskPriority::High); } IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FLumenCardScene, "LumenCardScene"); void UpdateLumenCardSceneUniformBuffer( FRDGBuilder& GraphBuilder, FScene* Scene, const FLumenSceneData& LumenSceneData, FLumenSceneFrameTemporaries& FrameTemporaries) { FLumenCardScene* UniformParameters = GraphBuilder.AllocParameters(); UniformParameters->NumCards = LumenSceneData.Cards.Num(); UniformParameters->NumMeshCards = LumenSceneData.MeshCards.Num(); UniformParameters->NumCardPages = LumenSceneData.GetNumCardPages(); UniformParameters->NumHeightfields = LumenSceneData.Heightfields.Num(); UniformParameters->NumPrimitiveGroups = LumenSceneData.PrimitiveGroups.Num(); UniformParameters->PhysicalAtlasSize = LumenSceneData.GetPhysicalAtlasSize(); UniformParameters->InvPhysicalAtlasSize = FVector2f(1.0f) / UniformParameters->PhysicalAtlasSize; UniformParameters->IndirectLightingAtlasDownsampleFactor = LumenRadiosity::GetAtlasDownsampleFactor(); if (FrameTemporaries.CardBufferSRV) { UniformParameters->CardData = FrameTemporaries.CardBufferSRV; UniformParameters->MeshCardsData = FrameTemporaries.MeshCardsBufferSRV; UniformParameters->HeightfieldData = FrameTemporaries.HeightfieldBufferSRV; UniformParameters->PrimitiveGroupData = FrameTemporaries.PrimitiveGroupBufferSRV; UniformParameters->SceneInstanceIndexToMeshCardsIndexBuffer = FrameTemporaries.SceneInstanceIndexToMeshCardsIndexBufferSRV; UniformParameters->PageTableBuffer = FrameTemporaries.PageTableBufferSRV; UniformParameters->CardPageData = FrameTemporaries.CardPageBufferSRV; } else { FRDGBufferSRVRef DefaultSRV = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(FVector4f))); UniformParameters->CardData = DefaultSRV; UniformParameters->MeshCardsData = DefaultSRV; UniformParameters->HeightfieldData = DefaultSRV; UniformParameters->CardPageData = DefaultSRV; UniformParameters->PrimitiveGroupData = DefaultSRV; UniformParameters->PageTableBuffer = UniformParameters->SceneInstanceIndexToMeshCardsIndexBuffer = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultByteAddressBuffer(GraphBuilder, sizeof(FVector4f))); } if (FrameTemporaries.AlbedoAtlas) { UniformParameters->AlbedoAtlas = FrameTemporaries.AlbedoAtlas; UniformParameters->OpacityAtlas = FrameTemporaries.OpacityAtlas; UniformParameters->NormalAtlas = FrameTemporaries.NormalAtlas; UniformParameters->EmissiveAtlas = FrameTemporaries.EmissiveAtlas; UniformParameters->DepthAtlas = FrameTemporaries.DepthAtlas; } else { UniformParameters->AlbedoAtlas = UniformParameters->OpacityAtlas = UniformParameters->NormalAtlas = UniformParameters->EmissiveAtlas = UniformParameters->DepthAtlas = GSystemTextures.GetBlackDummy(GraphBuilder); } FrameTemporaries.LumenCardSceneUniformBuffer = GraphBuilder.CreateUniformBuffer(UniformParameters); } DECLARE_GPU_STAT(UpdateCardSceneBuffer); class FClearLumenCardCapturePS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FClearLumenCardCapturePS); SHADER_USE_PARAMETER_STRUCT(FClearLumenCardCapturePS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) END_SHADER_PARAMETER_STRUCT() using FPermutationDomain = TShaderPermutationDomain<>; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportLumenGI(Parameters.Platform); } }; IMPLEMENT_GLOBAL_SHADER(FClearLumenCardCapturePS, "/Engine/Private/Lumen/LumenSceneLighting.usf", "ClearLumenCardCapturePS", SF_Pixel); BEGIN_SHADER_PARAMETER_STRUCT(FClearLumenCardCaptureParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FPixelShaderUtils::FRasterizeToRectsVS::FParameters, VS) SHADER_PARAMETER_STRUCT_INCLUDE(FClearLumenCardCapturePS::FParameters, PS) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() void ClearLumenCardCapture( FRDGBuilder& GraphBuilder, const FGlobalShaderMap* GlobalShaderMap, const FCardCaptureAtlas& Atlas, FRDGBufferSRVRef RectCoordBufferSRV, uint32 NumRects) { FClearLumenCardCaptureParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->RenderTargets[0] = FRenderTargetBinding(Atlas.Albedo, ERenderTargetLoadAction::ELoad); PassParameters->RenderTargets[1] = FRenderTargetBinding(Atlas.Normal, ERenderTargetLoadAction::ELoad); PassParameters->RenderTargets[2] = FRenderTargetBinding(Atlas.Emissive, ERenderTargetLoadAction::ELoad); PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(Atlas.DepthStencil, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthWrite_StencilWrite); auto PixelShader = GlobalShaderMap->GetShader(); FPixelShaderUtils::AddRasterizeToRectsPass( GraphBuilder, GlobalShaderMap, RDG_EVENT_NAME("ClearCardCapture"), PixelShader, PassParameters, Atlas.Size, RectCoordBufferSRV, NumRects, TStaticBlendState<>::GetRHI(), TStaticRasterizerState<>::GetRHI(), TStaticDepthStencilState::GetRHI()); } BEGIN_SHADER_PARAMETER_STRUCT(FLumenCardPassParameters, ) // An RDG View uniform buffer is used as an optimization to move creation off the render thread. SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FViewUniformShaderParameters, View) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FLumenCardPassUniformParameters, CardPass) SHADER_PARAMETER_STRUCT_INCLUDE(FInstanceCullingDrawParams, InstanceCullingDrawParams) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() FIntPoint FLumenSceneData::GetCardCaptureAtlasSizeInPages() const { const float MultPerComponent = 1.0f / FMath::Sqrt(FMath::Clamp(GLumenSceneCardCaptureFactor, 1.0f, 1024.0f)); FIntPoint CaptureAtlasSizeInPages; CaptureAtlasSizeInPages.X = FMath::DivideAndRoundUp(PhysicalAtlasSize.X * MultPerComponent + 0.5f, Lumen::PhysicalPageSize); CaptureAtlasSizeInPages.Y = FMath::DivideAndRoundUp(PhysicalAtlasSize.Y * MultPerComponent + 0.5f, Lumen::PhysicalPageSize); return CaptureAtlasSizeInPages; } FIntPoint FLumenSceneData::GetCardCaptureAtlasSize() const { return GetCardCaptureAtlasSizeInPages() * Lumen::PhysicalPageSize; } uint32 FLumenSceneData::GetCardCaptureRefreshNumTexels() const { const float CardCaptureRefreshFraction = FMath::Clamp(CVarLumenSceneCardCaptureRefreshFraction.GetValueOnRenderThread(), 0.0f, 1.0f); if (CardCaptureRefreshFraction > 0.0f) { // Allow to capture at least 1 full physical page FIntPoint CardCaptureAtlasSize = GetCardCaptureAtlasSize(); return FMath::Max(CardCaptureAtlasSize.X * CardCaptureAtlasSize.Y * CardCaptureRefreshFraction, Lumen::PhysicalPageSize * Lumen::PhysicalPageSize); } return 0; } uint32 FLumenSceneData::GetCardCaptureRefreshNumPages() const { const float CardCaptureRefreshFraction = FMath::Clamp(CVarLumenSceneCardCaptureRefreshFraction.GetValueOnRenderThread(), 0.0f, 1.0f); if (CardCaptureRefreshFraction > 0.0f) { // Allow to capture at least 1 full physical page return FMath::Clamp(GetMaxTileCapturesPerFrame() * CardCaptureRefreshFraction, 1, GetMaxTileCapturesPerFrame()); } return 0; } bool UpdateGlobalLightingState(const FScene* Scene, const FViewInfo& View, FLumenSceneData& LumenSceneData) { FLumenGlobalLightingState& GlobalLightingState = LumenSceneData.GlobalLightingState; bool bPropagateGlobalLightingChange = false; const FLightSceneInfo* DirectionalLightSceneInfo = nullptr; for (const FLightSceneInfo* LightSceneInfo : Scene->DirectionalLights) { if (LightSceneInfo->ShouldRenderLightViewIndependent() && LightSceneInfo->ShouldRenderLight(View, true) && LightSceneInfo->Proxy->GetIndirectLightingScale() > 0.0f) { DirectionalLightSceneInfo = LightSceneInfo; break; } } { const float OldMax = GlobalLightingState.bDirectionalLightValid ? GlobalLightingState.DirectionalLightColor.GetMax() : 0.0f; const float NewMax = DirectionalLightSceneInfo ? DirectionalLightSceneInfo->Proxy->GetColor().GetMax() : 0.0f; const float Ratio = FMath::Max(OldMax, .00001f) / FMath::Max(NewMax, .00001f); if (Ratio > 4.0f || Ratio < .25f) { bPropagateGlobalLightingChange = true; } } if (DirectionalLightSceneInfo) { GlobalLightingState.DirectionalLightColor = DirectionalLightSceneInfo->Proxy->GetColor(); GlobalLightingState.bDirectionalLightValid = true; } else { GlobalLightingState.DirectionalLightColor = FLinearColor::Black; GlobalLightingState.bDirectionalLightValid = false; } const FSkyLightSceneProxy* SkyLightProxy = Scene->SkyLight; { const float OldMax = GlobalLightingState.bSkyLightValid ? GlobalLightingState.SkyLightColor.GetMax() : 0.0f; const float NewMax = SkyLightProxy ? SkyLightProxy->GetEffectiveLightColor().GetMax() : 0.0f; const float Ratio = FMath::Max(OldMax, .00001f) / FMath::Max(NewMax, .00001f); if (Ratio > 4.0f || Ratio < .25f) { bPropagateGlobalLightingChange = true; } } if (SkyLightProxy) { GlobalLightingState.SkyLightColor = SkyLightProxy->GetEffectiveLightColor(); GlobalLightingState.bSkyLightValid = true; } else { GlobalLightingState.SkyLightColor = FLinearColor::Black; GlobalLightingState.bSkyLightValid = false; } if (CVarLumenScenePropagateGlobalLightingChange.GetValueOnRenderThread() == 0) { bPropagateGlobalLightingChange = false; } return bPropagateGlobalLightingChange; } void FDeferredShadingSceneRenderer::UpdateLumenScene(FRDGBuilder& GraphBuilder, FLumenSceneFrameTemporaries& FrameTemporaries) { LLM_SCOPE_BYTAG(Lumen); TRACE_CPUPROFILER_EVENT_SCOPE(FDeferredShadingSceneRenderer::UpdateLumenScene); CSV_SCOPED_SET_WAIT_STAT(UpdateLumenScene); FrameTemporaries.UpdateSceneTask.Wait(); if (FrameTemporaries.SceneAddOpsReadbackBuffer) { FrameTemporaries.SceneAddOpsReadbackBuffer->Unlock(); } if (FrameTemporaries.SceneRemoveOpsReadbackBuffer) { FrameTemporaries.SceneRemoveOpsReadbackBuffer->Unlock(); } if (FrameTemporaries.SurfaceCacheFeedbackBuffer) { FrameTemporaries.SurfaceCacheFeedbackBuffer->Unlock(); } bool bAnyLumenActive = false; for (FViewInfo& View : Views) { const FPerViewPipelineState& ViewPipelineState = GetViewPipelineState(View); bool bLumenActive = (ViewPipelineState.DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen // Don't update scene lighting for secondary views && !View.bIsPlanarReflection && !View.bIsReflectionCapture && View.ViewState); bAnyLumenActive = bAnyLumenActive || bLumenActive; } if (bAnyLumenActive) { FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(Views[0]); const TArray& CardPagesToRender = LumenCardRenderer.CardPagesToRender; QUICK_SCOPE_CYCLE_COUNTER(UpdateLumenScene); RHI_BREADCRUMB_EVENT_STAT(GraphBuilder.RHICmdList, UpdateLumenSceneBuffers, "UpdateLumenSceneBuffers"); SCOPED_GPU_STAT(GraphBuilder.RHICmdList, UpdateLumenSceneBuffers); RDG_EVENT_SCOPE_STAT(GraphBuilder, LumenSceneUpdate, "LumenSceneUpdate: %u card captures %.3fM texels", CardPagesToRender.Num(), LumenCardRenderer.NumCardTexelsToCapture / (1024.0f * 1024.0f)); RDG_GPU_STAT_SCOPE(GraphBuilder, LumenSceneUpdate); // Atlas reallocation if (FrameTemporaries.bReallocateAtlas || !LumenSceneData.AlbedoAtlas) { LumenSceneData.AllocateCardAtlases(GraphBuilder, FrameTemporaries, Views[0].Family); ClearLumenSurfaceCacheAtlas(GraphBuilder, FrameTemporaries, Views[0].ShaderMap); } LumenSceneData.FillFrameTemporaries(GraphBuilder, FrameTemporaries); if (LumenSceneData.bDebugClearAllCachedState) { ClearLumenSurfaceCacheAtlas(GraphBuilder, FrameTemporaries, Views[0].ShaderMap); } if (CVarLumenSceneUploadEveryFrame.GetValueOnRenderThread() != 0) { LumenSceneData.bReuploadSceneRequest = true; } UpdateLumenCardSceneUniformBuffer(GraphBuilder, Scene, *Scene->GetLumenSceneData(Views[0]), FrameTemporaries); FCardCaptureAtlas CardCaptureAtlas; FRDGBufferSRVRef CardCaptureRectBufferSRV = nullptr; bool bNeedsClearCaptureAtlas = false; int32 NumPagesNeedDilation = 0; if (CardPagesToRender.Num()) { // Before we update the GPU page table, read from the persistent atlases for the card pages we are reallocating, and write it to the card capture atlas // This is a resample operation, as the original data may have been at a different mip level, or didn't exist at all ResampleLumenCards( GraphBuilder, Views[0], Scene, LumenSceneData, FrameTemporaries, CardPagesToRender, LumenCardRenderer.bHasAnyCardCopy, CardCaptureAtlas, LumenCardRenderer.ResampledCardCaptureAtlas, CardCaptureRectBufferSRV, NumPagesNeedDilation); } FRDGScatterUploadBuilder& UploadBuilder = *FRDGScatterUploadBuilder::Create(GraphBuilder); LumenSceneData.UploadPageTable(GraphBuilder, UploadBuilder, FrameTemporaries); LumenCardRenderer.bPropagateGlobalLightingChange = UpdateGlobalLightingState(Scene, Views[0], LumenSceneData); Lumen::UpdateCardSceneBuffer(GraphBuilder, UploadBuilder, FrameTemporaries, ViewFamily, Scene); UploadBuilder.Execute(GraphBuilder); if (CVarLumenSceneGPUDrivenUpdate.GetValueOnRenderThread() != 0) { LumenScene::GPUDrivenUpdate(GraphBuilder, Scene, Views, FrameTemporaries); } // Init transient render targets for capturing cards if (!CardCaptureAtlas.Albedo) { LumenScene::AllocateCardCaptureAtlas(GraphBuilder, LumenSceneData.GetCardCaptureAtlasSize(), CardCaptureAtlas, Scene->GetShaderPlatform()); bNeedsClearCaptureAtlas = true; } if (CardPagesToRender.Num() > 0) { FRHIBuffer* PrimitiveIdVertexBuffer = nullptr; FInstanceCullingResult InstanceCullingResult; FInstanceCullingContext* InstanceCullingContext = nullptr; if (Scene->GPUScene.IsEnabled()) { InstanceCullingContext = GraphBuilder.AllocObject(TEXT("LumenCardCapture"), Views[0].GetShaderPlatform(), nullptr, TArrayView(&Views[0].SceneRendererPrimaryViewId, 1), nullptr); int32 MaxInstances = 0; int32 VisibleMeshDrawCommandsNum = 0; int32 NewPassVisibleMeshDrawCommandsNum = 0; InstanceCullingContext->SetupDrawCommands(LumenCardRenderer.MeshDrawCommands, false, Scene, MaxInstances, VisibleMeshDrawCommandsNum, NewPassVisibleMeshDrawCommandsNum); // Not supposed to do any compaction here. ensure(VisibleMeshDrawCommandsNum == LumenCardRenderer.MeshDrawCommands.Num()); InstanceCullingContext->BuildRenderingCommands(GraphBuilder, Scene->GPUScene, Views[0].DynamicPrimitiveCollector.GetInstanceSceneDataOffset(), Views[0].DynamicPrimitiveCollector.NumInstances(), InstanceCullingResult); } else { // Prepare primitive Id VB for rendering mesh draw commands. if (LumenCardRenderer.MeshDrawPrimitiveIds.Num() > 0) { const uint32 PrimitiveIdBufferDataSize = LumenCardRenderer.MeshDrawPrimitiveIds.Num() * sizeof(int32); FPrimitiveIdVertexBufferPoolEntry Entry = GPrimitiveIdVertexBufferPool.Allocate(GraphBuilder.RHICmdList, PrimitiveIdBufferDataSize); PrimitiveIdVertexBuffer = Entry.BufferRHI; void* RESTRICT Data = GraphBuilder.RHICmdList.LockBuffer(PrimitiveIdVertexBuffer, 0, PrimitiveIdBufferDataSize, RLM_WriteOnly); FMemory::Memcpy(Data, LumenCardRenderer.MeshDrawPrimitiveIds.GetData(), PrimitiveIdBufferDataSize); GraphBuilder.RHICmdList.UnlockBuffer(PrimitiveIdVertexBuffer); GPrimitiveIdVertexBufferPool.ReturnToFreeList(Entry); } } InstanceCullingResult.Parameters.Scene = GetSceneUniforms().GetBuffer(GraphBuilder); if (!CardCaptureRectBufferSRV) { FRDGBufferRef CardCaptureRectBuffer = nullptr; FRDGUploadData CardCaptureRectArray(GraphBuilder, CardPagesToRender.Num()); for (int32 Index = 0; Index < CardPagesToRender.Num(); Index++) { const FCardPageRenderData& CardPageRenderData = CardPagesToRender[Index]; FUintVector4& Rect = CardCaptureRectArray[Index]; Rect.X = FMath::Max(CardPageRenderData.CardCaptureAtlasRect.Min.X, 0); Rect.Y = FMath::Max(CardPageRenderData.CardCaptureAtlasRect.Min.Y, 0); Rect.Z = FMath::Max(CardPageRenderData.CardCaptureAtlasRect.Max.X, 0); Rect.W = FMath::Max(CardPageRenderData.CardCaptureAtlasRect.Max.Y, 0); if (CardPageRenderData.NeedsRender() && CardPageRenderData.DilationMode == ELumenCardDilationMode::DilateOneTexel) { ++NumPagesNeedDilation; } } CardCaptureRectBuffer = CreateUploadBuffer(GraphBuilder, TEXT("Lumen.CardCaptureRects"), sizeof(FUintVector4), FMath::RoundUpToPowerOfTwo(CardPagesToRender.Num()), CardCaptureRectArray); CardCaptureRectBufferSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(CardCaptureRectBuffer, PF_R32G32B32A32_UINT)); } if (bNeedsClearCaptureAtlas) { ClearLumenCardCapture(GraphBuilder, Views[0].ShaderMap, CardCaptureAtlas, CardCaptureRectBufferSRV, CardPagesToRender.Num()); } FViewInfo* SharedView = Views[0].CreateSnapshot(); { SharedView->DynamicPrimitiveCollector = FGPUScenePrimitiveCollector(&GetGPUSceneDynamicContext()); SharedView->StereoPass = EStereoscopicPass::eSSP_FULL; SharedView->DrawDynamicFlags = EDrawDynamicFlags::ForceLowestLOD; // Don't do material texture mip biasing in proxy card rendering SharedView->MaterialTextureMipBias = 0; SharedView->CachedViewUniformShaderParameters = MakeUnique(); ViewParametersUtils::CopyViewParametersOnly(*SharedView->CachedViewUniformShaderParameters, *Views[0].CachedViewUniformShaderParameters); // Resources needed by Substrate SharedView->CachedViewUniformShaderParameters->ShadingEnergyDiffuseTexture = Views[0].CachedViewUniformShaderParameters->ShadingEnergyDiffuseTexture; SharedView->CachedViewUniformShaderParameters->ShadingEnergyClothSpecTexture= Views[0].CachedViewUniformShaderParameters->ShadingEnergyClothSpecTexture; SharedView->CachedViewUniformShaderParameters->ShadingEnergyGGXGlassTexture = Views[0].CachedViewUniformShaderParameters->ShadingEnergyGGXGlassTexture; SharedView->CachedViewUniformShaderParameters->ShadingEnergyGGXSpecTexture = Views[0].CachedViewUniformShaderParameters->ShadingEnergyGGXSpecTexture; SharedView->CachedViewUniformShaderParameters->ShadingEnergySampler = Views[0].CachedViewUniformShaderParameters->ShadingEnergySampler; SharedView->CachedViewUniformShaderParameters->SimpleVolumeTexture = Views[0].CachedViewUniformShaderParameters->SimpleVolumeTexture; SharedView->CachedViewUniformShaderParameters->SimpleVolumeTextureSampler = Views[0].CachedViewUniformShaderParameters->SimpleVolumeTextureSampler; SharedView->CachedViewUniformShaderParameters->SimpleVolumeEnvTexture = Views[0].CachedViewUniformShaderParameters->SimpleVolumeEnvTexture; SharedView->CachedViewUniformShaderParameters->SimpleVolumeEnvTextureSampler= Views[0].CachedViewUniformShaderParameters->SimpleVolumeEnvTextureSampler; // Copy atmospheric light resources that could be used in emissive materials. Some setups apparently uses that to inject extra light bounce from atmospheric lights, accounting for atsmosphere transmittance. if (ShouldRenderSkyAtmosphere(Scene, SharedView->Family->EngineShowFlags)) { if (FSkyAtmosphereRenderSceneInfo* SkyInfo = Scene->GetSkyAtmosphereSceneInfo()) { SharedView->CachedViewUniformShaderParameters->TransmittanceLutTexture = SkyInfo->GetTransmittanceLutTexture()->GetRHI(); SharedView->CachedViewUniformShaderParameters->TransmittanceLutTextureSampler = Views[0].CachedViewUniformShaderParameters->TransmittanceLutTextureSampler; SharedView->CachedViewUniformShaderParameters->DistantSkyLightLutBufferSRV = SkyInfo->GetDistantSkyLightLutBufferSRV(); SharedView->CachedViewUniformShaderParameters->MobileDistantSkyLightLutBufferSRV = SkyInfo->GetMobileDistantSkyLightLutBufferSRV(); // SkyViewLutTexture and CameraAerialPerspectiveVolume textures are not available at this stage. // Those are also view dependent so there is no reason to make them available for Lumen card. } } VirtualTexture::FFeedbackShaderParams VirtualTextureFeedbackShaderParams; VirtualTexture::GetFeedbackShaderParams(VirtualTextureFeedbackShaderParams); VirtualTexture::UpdateViewUniformShaderParameters(VirtualTextureFeedbackShaderParams, *SharedView->CachedViewUniformShaderParameters); SharedView->CreateViewUniformBuffers(*SharedView->CachedViewUniformShaderParameters); } FLumenCardPassUniformParameters* PassUniformParameters = GraphBuilder.AllocParameters(); SetupSceneTextureUniformParameters(GraphBuilder, &GetActiveSceneTextures(), Scene->GetFeatureLevel(), /*SceneTextureSetupMode*/ ESceneTextureSetupMode::None, PassUniformParameters->SceneTextures); PassUniformParameters->EyeAdaptationBuffer = GraphBuilder.CreateSRV(GetEyeAdaptationBuffer(GraphBuilder, Views[0])); PassUniformParameters->CachedLightingPreExposure = Lumen::GetCachedLightingPreExposure(); { uint32 NumPages = 0; uint32 NumDraws = 0; uint32 NumInstances = 0; uint32 NumTris = 0; // Compute some stats about non Nanite meshes which are captured #if RDG_EVENTS != RDG_EVENTS_NONE { for (const FCardPageRenderData& CardPageRenderData : CardPagesToRender) { if (!CardPageRenderData.NeedsRender()) { continue; } if (CardPageRenderData.NumMeshDrawCommands > 0) { NumPages += 1; NumDraws += CardPageRenderData.NumMeshDrawCommands; for (int32 DrawCommandIndex = CardPageRenderData.StartMeshDrawCommandIndex; DrawCommandIndex < CardPageRenderData.StartMeshDrawCommandIndex + CardPageRenderData.NumMeshDrawCommands; ++DrawCommandIndex) { const FVisibleMeshDrawCommand& VisibleDrawCommand = LumenCardRenderer.MeshDrawCommands[DrawCommandIndex]; const FMeshDrawCommand* MeshDrawCommand = VisibleDrawCommand.MeshDrawCommand; uint32 NumInstancesPerDraw = 0; // Count number of instances to draw if (VisibleDrawCommand.NumRuns) { for (int32 InstanceRunIndex = 0; InstanceRunIndex < VisibleDrawCommand.NumRuns; ++InstanceRunIndex) { const int32 FirstInstance = VisibleDrawCommand.RunArray[InstanceRunIndex * 2 + 0]; const int32 LastInstance = VisibleDrawCommand.RunArray[InstanceRunIndex * 2 + 1]; NumInstancesPerDraw += LastInstance - FirstInstance + 1; } } else { NumInstancesPerDraw += MeshDrawCommand->NumInstances; } NumInstances += NumInstancesPerDraw; NumTris += MeshDrawCommand->NumPrimitives * NumInstancesPerDraw; } } } } #endif QUICK_SCOPE_CYCLE_COUNTER(CardPageRenderPasses); FLumenCardPassParameters* CommonPassParameters = GraphBuilder.AllocParameters(); CommonPassParameters->CardPass = GraphBuilder.CreateUniformBuffer(PassUniformParameters); CommonPassParameters->RenderTargets[0] = FRenderTargetBinding(CardCaptureAtlas.Albedo, ERenderTargetLoadAction::ELoad); CommonPassParameters->RenderTargets[1] = FRenderTargetBinding(CardCaptureAtlas.Normal, ERenderTargetLoadAction::ELoad); CommonPassParameters->RenderTargets[2] = FRenderTargetBinding(CardCaptureAtlas.Emissive, ERenderTargetLoadAction::ELoad); CommonPassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(CardCaptureAtlas.DepthStencil, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthWrite_StencilNop); InstanceCullingResult.GetDrawParameters(CommonPassParameters->InstanceCullingDrawParams); for (const FCardPageRenderData& CardPageRenderData : CardPagesToRender) { if (!CardPageRenderData.NeedsRender()) { continue; } RDG_EVENT_SCOPE(GraphBuilder, "MeshCardCapture Pages:%u Draws:%u Instances:%u Tris:%u", NumPages, NumDraws, NumInstances, NumTris); if (CardPageRenderData.NumMeshDrawCommands > 0) { CardPageRenderData.PatchView(Scene, SharedView); FLumenCardPassParameters* PassParameters = GraphBuilder.AllocParameters(CommonPassParameters); PassParameters->View = GraphBuilder.CreateUniformBuffer(GraphBuilder.AllocParameters(SharedView->CachedViewUniformShaderParameters.Get())); GraphBuilder.AddPass( RDG_EVENT_NAME("CardPage Commands:%u", CardPageRenderData.NumMeshDrawCommands), PassParameters, ERDGPassFlags::Raster, [this, ShaderPlatform = Scene->GetShaderPlatform(), bGPUSceneEnabled = Scene->GPUScene.IsEnabled(), PrimitiveIdVertexBuffer, &CardPageRenderData, PassParameters, InstanceCullingContext](FRDGAsyncTask, FRHICommandList& RHICmdList) { QUICK_SCOPE_CYCLE_COUNTER(MeshPass); const FIntRect ViewRect = CardPageRenderData.CardCaptureAtlasRect; RHICmdList.SetViewport(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, ViewRect.Max.X, ViewRect.Max.Y, 1.0f); FGraphicsMinimalPipelineStateSet GraphicsMinimalPipelineStateSet; if (bGPUSceneEnabled) { FInstanceCullingDrawParams& InstanceCullingDrawParams = PassParameters->InstanceCullingDrawParams; InstanceCullingContext->SubmitDrawCommands( LumenCardRenderer.MeshDrawCommands, GraphicsMinimalPipelineStateSet, GetMeshDrawCommandOverrideArgs(PassParameters->InstanceCullingDrawParams), CardPageRenderData.StartMeshDrawCommandIndex, CardPageRenderData.NumMeshDrawCommands, 1, RHICmdList); } else { FMeshDrawCommandSceneArgs SceneArgs; SceneArgs.PrimitiveIdsBuffer = PrimitiveIdVertexBuffer; SubmitMeshDrawCommandsRange( LumenCardRenderer.MeshDrawCommands, GraphicsMinimalPipelineStateSet, SceneArgs, FInstanceCullingContext::GetInstanceIdBufferStride(ShaderPlatform), false, CardPageRenderData.StartMeshDrawCommandIndex, CardPageRenderData.NumMeshDrawCommands, 1, RHICmdList); } }); } } } bool bAnyNaniteMeshes = false; for (const FCardPageRenderData& CardPageRenderData : CardPagesToRender) { if (CardPageRenderData.HasNanite() && CardPageRenderData.NeedsRender()) { bAnyNaniteMeshes = true; break; } } if (UseNanite(ShaderPlatform) && ViewFamily.EngineShowFlags.NaniteMeshes && bAnyNaniteMeshes) { QUICK_SCOPE_CYCLE_COUNTER(NaniteMeshPass); // Should have launched earlier in the frame, but ensure we have built Lumen commands here just in case (launched early will make this a no-op) Nanite::BuildShadingCommands(GraphBuilder, *Scene, ENaniteMeshPass::LumenCardCapture, Scene->NaniteShadingCommands[ENaniteMeshPass::LumenCardCapture]); const FIntPoint DepthStencilAtlasSize = CardCaptureAtlas.Size; const FIntRect DepthAtlasRect = FIntRect(0, 0, DepthStencilAtlasSize.X, DepthStencilAtlasSize.Y); Nanite::FSharedContext SharedContext{}; SharedContext.FeatureLevel = Scene->GetFeatureLevel(); SharedContext.ShaderMap = GetGlobalShaderMap(SharedContext.FeatureLevel); SharedContext.Pipeline = Nanite::EPipeline::Lumen; Nanite::FRasterContext RasterContext = Nanite::InitRasterContext( GraphBuilder, SharedContext, ViewFamily, DepthStencilAtlasSize, DepthAtlasRect, Nanite::EOutputBufferMode::VisBuffer, true, /*bAsyncCompute*/ CVarLumenSceneSurfaceCacheNaniteAsyncRasterization.GetValueOnRenderThread() != 0, CardCaptureRectBufferSRV, CardPagesToRender.Num()); Nanite::FConfiguration CullingConfig = { 0 }; CullingConfig.bSupportsMultiplePasses = true; CullingConfig.SetViewFlags(*SharedView); CullingConfig.bIsLumenCapture = true; CullingConfig.bDisableProgrammable = true; auto NaniteRenderer = Nanite::IRenderer::Create( GraphBuilder, *Scene, *SharedView, GetSceneUniforms(), SharedContext, RasterContext, CullingConfig, FIntRect(), nullptr ); Nanite::FRasterResults RasterResults; const uint32 NumCardPagesToRender = CardPagesToRender.Num(); uint32 NextCardIndex = 0; while(NextCardIndex < NumCardPagesToRender) { TArray CardPagesToCreatePackedView; TArray NaniteInstanceDraws; while(NextCardIndex < NumCardPagesToRender && CardPagesToCreatePackedView.Num() < NANITE_MAX_VIEWS_PER_CULL_RASTERIZE_PASS) { const FCardPageRenderData& CardPageRenderData = CardPagesToRender[NextCardIndex]; if (CardPageRenderData.NaniteInstanceIds.Num() > 0 && CardPageRenderData.NeedsRender()) { for(uint32 InstanceID : CardPageRenderData.NaniteInstanceIds) { NaniteInstanceDraws.Add(Nanite::FInstanceDraw { InstanceID, (uint32)CardPagesToCreatePackedView.Num() }); } CardPagesToCreatePackedView.Add(NextCardIndex); } NextCardIndex++; } if (NaniteInstanceDraws.Num() > 0) { RDG_EVENT_SCOPE(GraphBuilder, "Nanite::RasterizeLumenCards"); const uint32 NumPrimaryViews = CardPagesToCreatePackedView.Num(); Nanite::FPackedViewArray* NaniteViews = Nanite::FPackedViewArray::CreateWithSetupTask( GraphBuilder, NumPrimaryViews, [CardPagesToCreatePackedView = MoveTemp(CardPagesToCreatePackedView), &CardPagesToRender, NumCardPagesToRender, DepthStencilAtlasSize] (Nanite::FPackedViewArray::ArrayType& OutViews) { QUICK_SCOPE_CYCLE_COUNTER(CreateLumenPackedViews); for (const int32 CardPageToRenderIndex : CardPagesToCreatePackedView) { const FCardPageRenderData& CardPageRenderData = CardPagesToRender[CardPageToRenderIndex]; Nanite::FPackedViewParams Params; Params.ViewMatrices = CardPageRenderData.ViewMatrices; Params.PrevViewMatrices = CardPageRenderData.ViewMatrices; Params.ViewRect = CardPageRenderData.CardCaptureAtlasRect; Params.RasterContextSize = DepthStencilAtlasSize; Params.MaxPixelsPerEdgeMultipler = 1.0f / CardPageRenderData.NaniteLODScaleFactor; OutViews.Add(Nanite::CreatePackedView(Params)); } }); NaniteRenderer->DrawGeometry( Scene->NaniteRasterPipelines[ENaniteMeshPass::LumenCardCapture], RasterResults.VisibilityQuery, *NaniteViews, NaniteInstanceDraws ); } } NaniteRenderer->ExtractResults( RasterResults ); if (CVarLumenSceneSurfaceCacheNaniteMultiView.GetValueOnRenderThread() != 0) { DispatchLumenMeshCapturePass( GraphBuilder, *Scene, SharedView, TArrayView(CardPagesToRender), RasterResults, RasterContext, PassUniformParameters, CardCaptureRectBufferSRV, CardPagesToRender.Num(), CardCaptureAtlas.Size, CardCaptureAtlas.Albedo, CardCaptureAtlas.Normal, CardCaptureAtlas.Emissive, CardCaptureAtlas.DepthStencil ); } else { // Single capture per card. Slow path, only for debugging. for (int32 PageIndex = 0; PageIndex < CardPagesToRender.Num(); ++PageIndex) { if (CardPagesToRender[PageIndex].HasNanite()) { DispatchLumenMeshCapturePass( GraphBuilder, *Scene, SharedView, TArrayView(&CardPagesToRender[PageIndex], 1), RasterResults, RasterContext, PassUniformParameters, CardCaptureRectBufferSRV, CardPagesToRender.Num(), CardCaptureAtlas.Size, CardCaptureAtlas.Albedo, CardCaptureAtlas.Normal, CardCaptureAtlas.Emissive, CardCaptureAtlas.DepthStencil ); } } } } DilateCardPageOneTexel( GraphBuilder, Views[0], NumPagesNeedDilation, CardCaptureRectBufferSRV, CardPagesToRender, CardCaptureAtlas); UpdateLumenSurfaceCacheAtlas( GraphBuilder, Views[0], FrameTemporaries, CardPagesToRender, CardCaptureRectBufferSRV, CardCaptureAtlas, LumenCardRenderer.ResampledCardCaptureAtlas); } } UpdateLumenCardSceneUniformBuffer(GraphBuilder, Scene, *Scene->GetLumenSceneData(Views[0]), FrameTemporaries); // Reset arrays, but keep allocated memory for 1024 elements int32 LumenSlack = bAnyLumenActive ? 1024 : 0; FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(Views[0]); // Refresh LumenScene if some updates were ignored due to Lumen being inactive // For scene captures, don't trigger a reupload unless the scene capture has view specific LumenSceneData (not the default data) if (!bAnyLumenActive && (!Views[0].bIsSceneCapture || &LumenSceneData != Scene->DefaultLumenSceneData)) { if (LumenSceneData.CardIndicesToUpdateInBuffer.Num() > 0 || LumenSceneData.MeshCardsIndicesToUpdateInBuffer.Num() > 0 || LumenSceneData.HeightfieldIndicesToUpdateInBuffer.Num() > 0 || LumenSceneData.PrimitivesToUpdateMeshCards.Num() > 0 || LumenSceneData.PrimitiveGroupIndicesToUpdateInBuffer.Num() > 0 || LumenSceneData.PageTableIndicesToUpdateInBuffer.Num() > 0) { LumenSceneData.bReuploadSceneRequest = true; } } GraphBuilder.AddPostExecuteCallback([&LumenSceneData, LumenSlack] { LumenSceneData.CardIndicesToUpdateInBuffer.Empty(LumenSlack); LumenSceneData.MeshCardsIndicesToUpdateInBuffer.Empty(LumenSlack); LumenSceneData.HeightfieldIndicesToUpdateInBuffer.Empty(LumenSlack); LumenSceneData.PrimitivesToUpdateMeshCards.Empty(LumenSlack); LumenSceneData.PrimitiveGroupIndicesToUpdateInBuffer.Empty(LumenSlack); LumenSceneData.PageTableIndicesToUpdateInBuffer.Empty(LumenSlack); }); } void FLumenViewOrigin::Init(const FViewInfo& View) { Family = View.Family; LumenSceneViewOrigin = Lumen::GetLumenSceneViewOrigin(View, Lumen::GetNumGlobalDFClipmaps(View) - 1); WorldCameraOrigin = FVector4f((FVector3f)View.ViewMatrices.GetViewOrigin(), 0.0f); // LUMEN_LWC_TODO ViewToClip = FMatrix44f(View.ViewMatrices.GetViewProjectionMatrix()); PreViewTranslationDF = FDFVector3{ View.ViewMatrices.GetPreViewTranslation() }; FrustumTranslatedWorldToClip = FMatrix44f(View.ViewMatrices.GetTranslatedViewProjectionMatrix()); OrthoMaxDimension = View.ViewMatrices.GetOrthoDimensions().GetMax(); // Returns zero if not orthographic LastEyeAdaptationExposure = View.GetLastEyeAdaptationExposure(); if (LastEyeAdaptationExposure <= 0.f) { // InverseExposureLerp() returns NaN if fed a non-positive exposure value LastEyeAdaptationExposure = 1.f; } MaxTraceDistance = Lumen::GetMaxTraceDistance(View); CardMaxDistance = LumenScene::GetCardMaxDistance(View); LumenSceneDetail = FMath::Clamp(View.FinalPostProcessSettings.LumenSceneDetail, .125f, 8.0f); ReferenceView = &View; } FLumenSceneFrameTemporaries::FLumenSceneFrameTemporaries(const TArray& Views) { if (Views[0].bIsSceneCaptureCube) { // Cube captures use a single origin ViewOrigins.SetNum(1); ViewOrigins[0].Init(Views[0]); // Cube captures are omnidirectional, so we want a matrix that will pass anything as in-frustum. An all zero matrix // will produce a clip position of [0,0,0,1] for any input vector, accomplishing that goal. FVector3f ZeroVector(ForceInitToZero); ViewOrigins[0].FrustumTranslatedWorldToClip = FMatrix44f(ZeroVector, ZeroVector, ZeroVector, ZeroVector); } else if (IStereoRendering::IsStereoEyeView(Views[0])) { // Stereo views can share the same origin with Primary one due to their closeness ViewOrigins.SetNum(1); ViewOrigins[0].Init(*Views[0].GetPrimaryView()); } else { // Limit the number of view origin to LUMEN_MAX_VIEWS to avoid issue down the pipe. const int32 ViewCount = FMath::Min(LUMEN_MAX_VIEWS, Views.Num()); ViewOrigins.SetNum(ViewCount); for (int32 ViewIndex = 0; ViewIndex < ViewCount; ++ViewIndex) { ViewOrigins[ViewIndex].Init(Views[ViewIndex]); } } // Actual extent of viewports -- useful for passing to EncloseVisualizeExtent (used by VisualizeTexture debug feature) ViewExtent = FIntPoint(0,0); for (const FViewInfo& View : Views) { ViewExtent.X = FMath::Max(ViewExtent.X, View.ViewRect.Max.X); ViewExtent.Y = FMath::Max(ViewExtent.Y, View.ViewRect.Max.Y); } } FRDGTextureRef FLumenSharedRT::CreateSharedRT( FRDGBuilder& Builder, const FRDGTextureDesc& Desc, FIntPoint VisibleExtent, const TCHAR* Name, ERDGTextureFlags Flags) { if (RenderTarget) { check(Desc.Extent == RenderTarget->Desc.Extent); return RenderTarget; } RenderTarget = Builder.CreateTexture(Desc, Name, Flags); RenderTarget->EncloseVisualizeExtent(VisibleExtent); return RenderTarget; }