// Copyright Epic Games, Inc. All Rights Reserved. #include "LumenSurfaceCacheFeedback.h" #include "SceneRendering.h" #include "DeferredShadingRenderer.h" #include "GPUFeedbackCompaction.h" #include "LumenSceneData.h" #include "Lumen.h" #include "LumenReflections.h" #include "LumenVisualize.h" #include "ScenePrivate.h" int32 GLumenSurfaceCacheFeedback = 1; FAutoConsoleVariableRef CVarLumenSurfaceCacheFeedback( TEXT("r.LumenScene.SurfaceCache.Feedback"), GLumenSurfaceCacheFeedback, TEXT("Whether to use surface cache feedback to selectively map higher quality surface cache pages."), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenSurfaceCacheFeedbackTileSize = 16; FAutoConsoleVariableRef CVarLumenSurfaceCacheFeedbackTileSize( TEXT("r.LumenScene.SurfaceCache.Feedback.TileSize"), GLumenSurfaceCacheFeedbackTileSize, TEXT("One surface cache feedback element will be writen out per tile. Aligned to a power of two."), ECVF_Scalability | ECVF_RenderThreadSafe ); float GLumenSurfaceCacheFeedbackResLevelBias = -0.5f; FAutoConsoleVariableRef CVarLumenSurfaceCacheFeedbackResLevelBias( TEXT("r.LumenScene.SurfaceCache.Feedback.ResLevelBias"), GLumenSurfaceCacheFeedbackResLevelBias, TEXT("Bias resolution of on demand surface cache pages."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable) { Lumen::DebugResetSurfaceCache(); }), ECVF_Scalability | ECVF_RenderThreadSafe ); float GLumenSurfaceCacheFeedbackFeedbackMinPageHits = 16; FAutoConsoleVariableRef CVarLumenSurfaceCacheFeedbackMinPageHits( TEXT("r.LumenScene.SurfaceCache.Feedback.MinPageHits"), GLumenSurfaceCacheFeedbackFeedbackMinPageHits, TEXT("Min number of page hits to demand a new page."), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenSurfaceCacheFeedbackMaxUniqueElements = 1024; FAutoConsoleVariableRef CVarLumenSurfaceCacheFeedbackUniqueElements( TEXT("r.LumenScene.SurfaceCache.Feedback.UniqueElements"), GLumenSurfaceCacheFeedbackMaxUniqueElements, TEXT("Limit of unique surface cache feedback elements. Used to resize buffers."), ECVF_Scalability | ECVF_RenderThreadSafe ); uint32 Lumen::GetFeedbackBufferTileSize() { return FMath::RoundUpToPowerOfTwo(FMath::Clamp(GLumenSurfaceCacheFeedbackTileSize, 1, 256)); } uint32 Lumen::GetFeedbackBufferTileWrapMask() { // Index & TileWrapMask = Index % TileSize return GetFeedbackBufferTileSize() - 1; } uint32 Lumen::GetFeedbackBufferSize(const FViewFamilyInfo& ViewFamily) { const FSceneTexturesConfig& SceneTexturesConfig = ViewFamily.SceneTexturesConfig; const FIntPoint SceneTextureExtentInTiles = FIntPoint::DivideAndRoundUp(SceneTexturesConfig.Extent, Lumen::GetFeedbackBufferTileSize()); const uint32 FeedbackBufferSize = SceneTextureExtentInTiles.X * SceneTextureExtentInTiles.Y; return FeedbackBufferSize; } uint32 Lumen::GetCompactedFeedbackBufferSize() { return FMath::RoundUpToPowerOfTwo(FMath::Clamp(GLumenSurfaceCacheFeedbackMaxUniqueElements, 1, 16 * 1024)); } FLumenSurfaceCacheFeedback::FLumenSurfaceCacheFeedback() { ReadbackBuffers.AddZeroed(MaxReadbackBuffers); } FLumenSurfaceCacheFeedback::~FLumenSurfaceCacheFeedback() { for (int32 BufferIndex = 0; BufferIndex < ReadbackBuffers.Num(); ++BufferIndex) { if (ReadbackBuffers[BufferIndex]) { delete ReadbackBuffers[BufferIndex]; ReadbackBuffers[BufferIndex] = nullptr; } } } void FLumenSurfaceCacheFeedback::AllocateFeedbackResources(FRDGBuilder& GraphBuilder, FFeedbackResources& Resources, const FViewFamilyInfo& ViewFamily) const { Resources.BufferSize = Lumen::GetFeedbackBufferSize(ViewFamily); FRDGBuffer* BufferAllocator = GraphBuilder.CreateBuffer( FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), 1), TEXT("Lumen.FeedbackAllocator")); Resources.BufferAllocatorUAV = GraphBuilder.CreateUAV(BufferAllocator, ERDGUnorderedAccessViewFlags::SkipBarrier); Resources.BufferAllocatorSRV = GraphBuilder.CreateSRV(BufferAllocator, PF_R32_UINT); FRDGBuffer* Buffer = GraphBuilder.CreateBuffer( FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32) * Lumen::FeedbackBufferElementStride, Resources.BufferSize), TEXT("Lumen.Feedback")); Resources.BufferUAV = GraphBuilder.CreateUAV(Buffer, ERDGUnorderedAccessViewFlags::SkipBarrier); Resources.BufferSRV = GraphBuilder.CreateSRV(Buffer, PF_R32G32_UINT); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(BufferAllocator, PF_R32_UINT), 0); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(Buffer, PF_R32G32_UINT), 0); } FRDGBufferUAVRef FLumenSurfaceCacheFeedback::GetDummyFeedbackAllocatorUAV(FRDGBuilder& GraphBuilder) const { FRDGBufferRef DummyBufferAllocator = GraphBuilder.CreateBuffer( FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), 1), TEXT("Lumen.DummyFeedbackAllocator")); return GraphBuilder.CreateUAV(DummyBufferAllocator, ERDGUnorderedAccessViewFlags::SkipBarrier); } FRDGBufferUAVRef FLumenSurfaceCacheFeedback::GetDummyFeedbackUAV(FRDGBuilder& GraphBuilder) const { FRDGBufferRef DummyBuffer = GraphBuilder.CreateBuffer( FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32) * Lumen::FeedbackBufferElementStride, 1), TEXT("Lumen.DummyFeedback")); return GraphBuilder.CreateUAV(DummyBuffer, ERDGUnorderedAccessViewFlags::SkipBarrier); } void FLumenSurfaceCacheFeedback::SubmitFeedbackBuffer( const FViewInfo& View, FRDGBuilder& GraphBuilder, FLumenSurfaceCacheFeedback::FFeedbackResources& FeedbackResources) { if (ReadbackBuffersNumPending == MaxReadbackBuffers) { // Return when queue is full. It is NOT safe to EnqueueCopy on a buffer that already has a pending copy. return; } RDG_EVENT_SCOPE(GraphBuilder, "Submit Lumen surface cache feedback"); const uint32 CompactedFeedbackBufferSize = Lumen::GetCompactedFeedbackBufferSize(); const uint32 HashTableSize = 2 * CompactedFeedbackBufferSize; const uint32 HashTableIndexWrapMask = HashTableSize - 1; FRDGBufferDesc CompactedFeedbackBufferDesc(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32) * Lumen::FeedbackBufferElementStride, CompactedFeedbackBufferSize)); CompactedFeedbackBufferDesc.Usage |= BUF_SourceCopy; FRDGBufferRef CompactedFeedbackBuffer = GraphBuilder.CreateBuffer(CompactedFeedbackBufferDesc, TEXT("Lumen.CompactedFeedback")); // Need to clear this buffer, as first element will be used as an allocator AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(CompactedFeedbackBuffer, PF_R32_UINT), 0); FRDGBufferDesc HashTableBufferDesc(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), HashTableSize)); FRDGBufferRef HashTableKeyBuffer = GraphBuilder.CreateBuffer(HashTableBufferDesc, TEXT("Lumen.HashTableKeys")); FRDGBufferRef HashTableElementIndexBuffer = GraphBuilder.CreateBuffer(HashTableBufferDesc, TEXT("Lumen.HashTableElementIndices")); FRDGBufferRef HashTableElementCountBuffer = GraphBuilder.CreateBuffer(HashTableBufferDesc, TEXT("Lumen.HashTableElementCounts")); // Hash table depends on empty slots to be 0 AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(HashTableKeyBuffer, PF_R32_UINT), 0); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(HashTableElementCountBuffer, PF_R32_UINT), 0); FRDGBufferRef BuildHashTableIndirectArgBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(1), TEXT("Lumen.BuildHashTableIndirectArgs")); // Set indirect dispatch arguments for hash table building { FBuildFeedbackHashTableIndirectArgsCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->RWBuildHashTableIndirectArgs = GraphBuilder.CreateUAV(BuildHashTableIndirectArgBuffer, PF_R32_UINT); PassParameters->FeedbackBufferAllocator = FeedbackResources.BufferAllocatorSRV; PassParameters->FeedbackBuffer = FeedbackResources.BufferSRV; PassParameters->FeedbackBufferSize = FeedbackResources.BufferSize; FBuildFeedbackHashTableIndirectArgsCS::FPermutationDomain PermutationVector; PermutationVector.Set(Lumen::FeedbackBufferElementStride); auto ComputeShader = View.ShaderMap->GetShader(PermutationVector); const FIntVector GroupSize = FIntVector(1, 1, 1); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("Hash table indirect arguments"), ComputeShader, PassParameters, GroupSize); } // Build hash table of feedback elements { FBuildFeedbackHashTableCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->BuildHashTableIndirectArgs = BuildHashTableIndirectArgBuffer; PassParameters->RWHashTableKeys = GraphBuilder.CreateUAV(HashTableKeyBuffer); PassParameters->RWHashTableElementIndices = GraphBuilder.CreateUAV(HashTableElementIndexBuffer); PassParameters->RWHashTableElementCounts = GraphBuilder.CreateUAV(HashTableElementCountBuffer); PassParameters->HashTableSize = HashTableSize; PassParameters->HashTableIndexWrapMask = HashTableIndexWrapMask; PassParameters->FeedbackBufferAllocator = FeedbackResources.BufferAllocatorSRV; PassParameters->FeedbackBuffer = FeedbackResources.BufferSRV; PassParameters->FeedbackBufferSize = FeedbackResources.BufferSize; FBuildFeedbackHashTableCS::FPermutationDomain PermutationVector; PermutationVector.Set(Lumen::FeedbackBufferElementStride); auto ComputeShader = View.ShaderMap->GetShader(PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("Build feedback hash table"), ComputeShader, PassParameters, BuildHashTableIndirectArgBuffer, 0); } // Compact hash table into an array of unique feedback elements { FCompactFeedbackHashTableCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->RWCompactedFeedbackBuffer = GraphBuilder.CreateUAV(CompactedFeedbackBuffer); PassParameters->CompactedFeedbackBufferSize = CompactedFeedbackBufferSize; PassParameters->CompactedFeedbackCountShiftBits = 16; PassParameters->HashTableElementIndices = GraphBuilder.CreateSRV(HashTableElementIndexBuffer, PF_R32_UINT); PassParameters->HashTableElementCounts = GraphBuilder.CreateSRV(HashTableElementCountBuffer, PF_R32_UINT); PassParameters->HashTableSize = HashTableSize; PassParameters->HashTableIndexWrapMask = HashTableIndexWrapMask; PassParameters->FeedbackBufferAllocator = FeedbackResources.BufferAllocatorSRV; PassParameters->FeedbackBuffer = FeedbackResources.BufferSRV; PassParameters->FeedbackBufferSize = FeedbackResources.BufferSize; FCompactFeedbackHashTableCS::FPermutationDomain PermutationVector; PermutationVector.Set(Lumen::FeedbackBufferElementStride); auto ComputeShader = View.ShaderMap->GetShader(PermutationVector); const FIntVector GroupSize = FComputeShaderUtils::GetGroupCount(HashTableSize, FCompactFeedbackHashTableCS::GetGroupSize()); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("Compact feedback hash table"), ComputeShader, PassParameters, GroupSize); } if (ReadbackBuffers[ReadbackBuffersWriteIndex] == nullptr) { FRHIGPUBufferReadback* GPUBufferReadback = new FRHIGPUBufferReadback(TEXT("Lumen.SurfaceCacheFeedbackBuffer")); ReadbackBuffers[ReadbackBuffersWriteIndex] = GPUBufferReadback; } FRHIGPUBufferReadback* ReadbackBuffer = ReadbackBuffers[ReadbackBuffersWriteIndex]; AddReadbackBufferPass(GraphBuilder, RDG_EVENT_NAME("Readback"), CompactedFeedbackBuffer, [ReadbackBuffer, CompactedFeedbackBuffer](FRDGAsyncTask, FRHICommandList& RHICmdList) { ReadbackBuffer->EnqueueCopy(RHICmdList, CompactedFeedbackBuffer->GetRHI(), 0u); }); ReadbackBuffersWriteIndex = (ReadbackBuffersWriteIndex + 1) % MaxReadbackBuffers; ReadbackBuffersNumPending = FMath::Min(ReadbackBuffersNumPending + 1, MaxReadbackBuffers); ++FrameIndex; } FRHIGPUBufferReadback* FLumenSurfaceCacheFeedback::GetLatestReadbackBuffer() { FRHIGPUBufferReadback* LatestReadbackBuffer = nullptr; // Find latest buffer that is ready while (ReadbackBuffersNumPending > 0) { uint32 Index = (ReadbackBuffersWriteIndex + MaxReadbackBuffers - ReadbackBuffersNumPending) % MaxReadbackBuffers; if (ReadbackBuffers[Index]->IsReady()) { --ReadbackBuffersNumPending; LatestReadbackBuffer = ReadbackBuffers[Index]; } else { break; } } return LatestReadbackBuffer; } void FLumenSceneData::UpdateSurfaceCacheFeedback(FFeedbackData FeedbackData, const TArray>& LumenSceneCameraOrigins, TArray& SurfaceCacheRequests, const FViewFamilyInfo& ViewFamily, int32 RequestHistogram[Lumen::NumDistanceBuckets]) { TRACE_CPUPROFILER_EVENT_SCOPE(UpdateSurfaceCacheFeedback); NumHiResPagesToAdd = 0; if (FeedbackData.Data) { const int32 HeaderSize = 1; const int32 NumFeedbackElements = FMath::Min(FeedbackData.Data[0], FeedbackData.NumElements - HeaderSize); for (int32 FeedbackElementIndex = 0; FeedbackElementIndex < NumFeedbackElements; ++FeedbackElementIndex) { const uint32 PackedA = FeedbackData.Data[(FeedbackElementIndex + HeaderSize) * Lumen::FeedbackBufferElementStride + 0]; const uint32 PackedB = FeedbackData.Data[(FeedbackElementIndex + HeaderSize) * Lumen::FeedbackBufferElementStride + 1]; int32 CardIndex = PackedA & 0xFFFFFF; uint16 DesiredResLevel = FMath::Clamp(PackedA >> 24, Lumen::MinResLevel, Lumen::MaxResLevel); FIntPoint LocalPageCoord; LocalPageCoord.X = (PackedB >> 0) & 0xFF; LocalPageCoord.Y = (PackedB >> 8) & 0xFF; const uint32 PageHitNum = (PackedB >> 16) & 0xFFFF; if (PageHitNum > GLumenSurfaceCacheFeedbackFeedbackMinPageHits && CardIndex < Cards.Num() && Cards.IsAllocated(CardIndex)) { FLumenCard& Card = Cards[CardIndex]; FLumenMipMapDesc MipMapDesc; Card.GetMipMapDesc(DesiredResLevel, MipMapDesc); LocalPageCoord.X = FMath::Clamp(LocalPageCoord.X, 0, MipMapDesc.SizeInPages.X - 1); LocalPageCoord.Y = FMath::Clamp(LocalPageCoord.Y, 0, MipMapDesc.SizeInPages.Y - 1); const uint16 LocalPageIndex = LocalPageCoord.X + LocalPageCoord.Y * MipMapDesc.SizeInPages.X; FVirtualPageIndex PageIndex(CardIndex, DesiredResLevel, LocalPageIndex); const FLumenSurfaceMipMap MipMap = Card.GetMipMap(PageIndex.ResLevel); const int32 PageTableIndex = MipMap.PageTableSpanSize > 0 ? MipMap.GetPageTableIndex(PageIndex.LocalPageIndex) : -1; if (PageTableIndex >= 0 && PageTable[PageTableIndex].IsMapped()) { // Update last used time for existing pages if (!MipMap.bLocked) { UnlockedAllocationHeap.Update(SurfaceCacheFeedback.GetFrameIndex(), PageTableIndex); } } else { float DistanceSquared = FLT_MAX; // LWC_TODO for (FVector CameraOrigin : LumenSceneCameraOrigins) { DistanceSquared = FMath::Min(DistanceSquared, Card.WorldOBB.ComputeSquaredDistanceToPoint(CameraOrigin)); } float Distance = FMath::Sqrt(DistanceSquared); // Change priority based on the normalized number of hits and make those request less important than low res resident pages const float NormalizeNumberOfHits = PageHitNum / float(Lumen::GetFeedbackBufferSize(ViewFamily)); Distance += 2500.0f + 2500.0f * (1.0f - NormalizeNumberOfHits); // Requested missing page FSurfaceCacheRequest Request; Request.CardIndex = PageIndex.CardIndex; Request.ResLevel = PageIndex.ResLevel; Request.LocalPageIndex = PageIndex.LocalPageIndex; Request.Distance = Distance; SurfaceCacheRequests.Add(Request); uint32 Bin = Lumen::GetMeshCardDistanceBin(Distance); RequestHistogram[Bin]++; ensure(!Request.IsLockedMip()); ++NumHiResPagesToAdd; } } } } } FIntPoint FLumenSurfaceCacheFeedback::GetFeedbackBufferTileJitter() const { const uint32 TileSize = Lumen::GetFeedbackBufferTileSize(); const uint32 TileSizeLog2 = FMath::CeilLogTwo(TileSize); const uint32 SequenceSize = FMath::Square(TileSize); const uint32 PixelIndex = FrameIndex % SequenceSize; const uint32 PixelAddress = ReverseBits(PixelIndex) >> (32U - 2 * TileSizeLog2); FIntPoint TileJitter; TileJitter.X = FMath::ReverseMortonCode2(PixelAddress); TileJitter.Y = FMath::ReverseMortonCode2(PixelAddress >> 1); return TileJitter; } void FDeferredShadingSceneRenderer::BeginGatheringLumenSurfaceCacheFeedback(FRDGBuilder& GraphBuilder, const FViewInfo& View, FLumenSceneFrameTemporaries& FrameTemporaries) { const FPerViewPipelineState& ViewPipelineState = GetViewPipelineState(View); const bool bLumenActive = ViewPipelineState.DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen || ViewPipelineState.ReflectionsMethod == EReflectionsMethod::Lumen; if (bLumenActive && GLumenSurfaceCacheFeedback != 0) { FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(View); const bool bVisualizeUsesFeedback = LumenVisualize::UseSurfaceCacheFeedback(ViewFamily.EngineShowFlags); const bool bReflectionsUseFeedback = ViewPipelineState.ReflectionsMethod == EReflectionsMethod::Lumen && LumenReflections::UseSurfaceCacheFeedback(); if (!Lumen::IsSurfaceCacheFrozen() && (bVisualizeUsesFeedback || bReflectionsUseFeedback)) { ensure(FrameTemporaries.SurfaceCacheFeedbackResources.BufferUAV == nullptr); LumenSceneData.SurfaceCacheFeedback.AllocateFeedbackResources(GraphBuilder, FrameTemporaries.SurfaceCacheFeedbackResources, ViewFamily); } if (LumenSceneData.CardPageLastUsedBuffer && LumenSceneData.CardPageHighResLastUsedBuffer) { FRDGBuffer* CardPageLastUsedBuffer = GraphBuilder.RegisterExternalBuffer(LumenSceneData.CardPageLastUsedBuffer); FrameTemporaries.CardPageLastUsedBufferSRV = GraphBuilder.CreateSRV(CardPageLastUsedBuffer); FrameTemporaries.CardPageLastUsedBufferUAV = GraphBuilder.CreateUAV(CardPageLastUsedBuffer, ERDGUnorderedAccessViewFlags::SkipBarrier); FRDGBuffer* CardPageHighResLastUsedBuffer = GraphBuilder.RegisterExternalBuffer(LumenSceneData.CardPageHighResLastUsedBuffer); FrameTemporaries.CardPageHighResLastUsedBufferSRV = GraphBuilder.CreateSRV(CardPageHighResLastUsedBuffer); FrameTemporaries.CardPageHighResLastUsedBufferUAV = GraphBuilder.CreateUAV(CardPageHighResLastUsedBuffer, ERDGUnorderedAccessViewFlags::SkipBarrier); } } } void FDeferredShadingSceneRenderer::FinishGatheringLumenSurfaceCacheFeedback(FRDGBuilder& GraphBuilder, const FViewInfo& View, FLumenSceneFrameTemporaries& FrameTemporaries) { const FPerViewPipelineState& ViewPipelineState = GetViewPipelineState(View); const bool bLumenActive = ViewPipelineState.DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen || ViewPipelineState.ReflectionsMethod == EReflectionsMethod::Lumen; if (bLumenActive && GLumenSurfaceCacheFeedback != 0) { FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(View); if (FrameTemporaries.SurfaceCacheFeedbackResources.BufferUAV) { LumenSceneData.SurfaceCacheFeedback.SubmitFeedbackBuffer(Views[0], GraphBuilder, FrameTemporaries.SurfaceCacheFeedbackResources); FrameTemporaries.SurfaceCacheFeedbackResources = {}; } if (FrameTemporaries.CardPageLastUsedBufferUAV && FrameTemporaries.CardPageHighResLastUsedBufferUAV) { GraphBuilder.QueueBufferExtraction(FrameTemporaries.CardPageLastUsedBufferUAV->GetParent(), &LumenSceneData.CardPageLastUsedBuffer); GraphBuilder.QueueBufferExtraction(FrameTemporaries.CardPageHighResLastUsedBufferUAV->GetParent(), &LumenSceneData.CardPageHighResLastUsedBuffer); } } if (FrameTemporaries.AlbedoAtlas) { FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(View); GraphBuilder.QueueTextureExtraction(FrameTemporaries.DepthAtlas, &LumenSceneData.DepthAtlas); GraphBuilder.QueueTextureExtraction(FrameTemporaries.AlbedoAtlas, &LumenSceneData.AlbedoAtlas); GraphBuilder.QueueTextureExtraction(FrameTemporaries.OpacityAtlas, &LumenSceneData.OpacityAtlas); GraphBuilder.QueueTextureExtraction(FrameTemporaries.NormalAtlas, &LumenSceneData.NormalAtlas); GraphBuilder.QueueTextureExtraction(FrameTemporaries.EmissiveAtlas, &LumenSceneData.EmissiveAtlas); GraphBuilder.QueueTextureExtraction(FrameTemporaries.DirectLightingAtlas, &LumenSceneData.DirectLightingAtlas); GraphBuilder.QueueTextureExtraction(FrameTemporaries.IndirectLightingAtlas, &LumenSceneData.IndirectLightingAtlas); GraphBuilder.QueueTextureExtraction(FrameTemporaries.RadiosityNumFramesAccumulatedAtlas, &LumenSceneData.RadiosityNumFramesAccumulatedAtlas); GraphBuilder.QueueTextureExtraction(FrameTemporaries.FinalLightingAtlas, &LumenSceneData.FinalLightingAtlas); GraphBuilder.QueueBufferExtraction(FrameTemporaries.TileShadowDownsampleFactorAtlas, &LumenSceneData.TileShadowDownsampleFactorAtlas); if (FrameTemporaries.DiffuseLightingAndSecondMomentHistoryAtlas && FrameTemporaries.DiffuseLightingAndSecondMomentHistoryAtlas->HasBeenProduced()) { GraphBuilder.QueueTextureExtraction(FrameTemporaries.DiffuseLightingAndSecondMomentHistoryAtlas, &LumenSceneData.DiffuseLightingAndSecondMomentHistoryAtlas); } if (FrameTemporaries.NumFramesAccumulatedHistoryAtlas && FrameTemporaries.NumFramesAccumulatedHistoryAtlas->HasBeenProduced()) { GraphBuilder.QueueTextureExtraction(FrameTemporaries.NumFramesAccumulatedHistoryAtlas, &LumenSceneData.NumFramesAccumulatedHistoryAtlas); } } QueueExtractStochasticLighting(GraphBuilder, FrameTemporaries); }