// Copyright Epic Games, Inc. All Rights Reserved. #include "RendererPrivate.h" #include "ScenePrivate.h" #include "SceneUtils.h" #include "PipelineStateCache.h" #include "ShaderParameterStruct.h" #include "PixelShaderUtils.h" #include "LumenSceneLighting.h" #include "LumenSceneCardCapture.h" #include "LumenRadiosity.h" #include "ComponentRecreateRenderStateContext.h" static TAutoConsoleVariable CVarLumenSurfaceCacheDilationMode( TEXT("r.LumenScene.SurfaceCache.DilationMode"), 0, TEXT("Whether to allow dilating card page data by one texel.\n") TEXT("Better coverage for meshes even if they don't match their surface cache very well but data from dilation may not be accurate.\n") TEXT("It is a choice between getting possibly incorrect lighting or no lighting.\n") TEXT("0 - Disabled\n") TEXT("1 - Only two-sided (foliage but currently only wired up for mostly two-sided Nanite skinned meshes)\n") TEXT("2 - All"), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable) { FGlobalComponentRecreateRenderStateContext Context; }), ECVF_Scalability | ECVF_RenderThreadSafe ); int32 GLumenSurfaceCacheCompress = 1; FAutoConsoleVariableRef CVarLumenSurfaceCacheCompress( TEXT("r.LumenScene.SurfaceCache.Compress"), GLumenSurfaceCacheCompress, TEXT("Whether to use run time compression for surface cache.\n") TEXT("0 - Disabled\n") TEXT("1 - Compress using UAV aliasing if supported\n") TEXT("2 - Compress using CopyTexture (may be very slow on some RHIs)\n"), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarLumenSurfaceCacheCullUndergroundTexels( TEXT("r.LumenScene.SurfaceCache.CullUndergroundTexels"), 0, TEXT("Whether to cull underground Lumen card texels. This relies on RVT to obtain landscape height. Enabling this will disable card sharing."), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarLumenSurfaceCacheCullUndergroundTexelsHeightBias( TEXT("r.LumenScene.SurfaceCache.CullUndergroundTexels.HeightBias"), 30.0f, TEXT("A bias added to texel world height before comparing against landscape height. Larger values push texels up and make them less likely culled."), ECVF_Scalability | ECVF_RenderThreadSafe ); bool LumenScene::CullUndergroundTexels() { return CVarLumenSurfaceCacheCullUndergroundTexels.GetValueOnRenderThread() != 0; } ESurfaceCacheCompression GetSurfaceCacheCompression() { const bool bSupportsBCTextureCompression = (GPixelFormats[PF_BC5].Supported && GPixelFormats[PF_BC6H].Supported && GPixelFormats[PF_BC7].Supported); if (GLumenSurfaceCacheCompress == 1) { if (GRHISupportsUAVFormatAliasing && bSupportsBCTextureCompression) { return ESurfaceCacheCompression::UAVAliasing; } else if (GRHISupportsLossyFramebufferCompression) { return ESurfaceCacheCompression::FramebufferCompression; } } else if (GLumenSurfaceCacheCompress == 2 && bSupportsBCTextureCompression) { return ESurfaceCacheCompression::CopyTextureRegion; } return ESurfaceCacheCompression::Disabled; } enum class ELumenSurfaceCacheLayer : uint8 { Depth, Albedo, Opacity, Normal, Emissive, MAX }; struct FLumenSurfaceLayerConfig { const TCHAR* Name; EPixelFormat UncompressedFormat; EPixelFormat CompressedFormat; EPixelFormat CompressedUAVFormat; FVector ClearValue; }; const FLumenSurfaceLayerConfig& GetSurfaceLayerConfig(ELumenSurfaceCacheLayer Layer) { static FLumenSurfaceLayerConfig Configs[(uint32)ELumenSurfaceCacheLayer::MAX] = { { TEXT("Depth"), PF_G16, PF_Unknown, PF_Unknown, FVector(1.0f, 0.0f, 0.0f) }, { TEXT("Albedo"), PF_R8G8B8A8, PF_BC7, PF_R32G32B32A32_UINT, FVector(0.0f, 0.0f, 0.0f) }, { TEXT("Opacity"), PF_G8, PF_Unknown, PF_Unknown, FVector(1.0f, 0.0f, 0.0f) }, // #lumen_todo: Fix BC4 compression and re-enable { TEXT("Normal"), PF_R8G8, PF_BC5, PF_R32G32B32A32_UINT, FVector(0.0f, 0.0f, 0.0f) }, { TEXT("Emissive"), PF_FloatR11G11B10, PF_BC6H, PF_R32G32B32A32_UINT, FVector(0.0f, 0.0f, 0.0f) } }; check((uint32)Layer < UE_ARRAY_COUNT(Configs)); return Configs[(uint32)Layer]; } class FLumenCardCopyPS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FLumenCardCopyPS); SHADER_USE_PARAMETER_STRUCT(FLumenCardCopyPS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FLumenCardScene, LumenCardScene) SHADER_PARAMETER_STRUCT_INCLUDE(FLumenLandscapeHeightSamplingParameters, LandscapeHeightSamplingParameters) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, CardUVRects) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, CardIndices) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWAtlasBlock4) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWAtlasBlock2) SHADER_PARAMETER(FVector2f, OneOverSourceAtlasSize) SHADER_PARAMETER(float, TexelCullingHeightBias) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceAlbedoAtlas) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceNormalAtlas) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceEmissiveAtlas) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceDepthAtlas) END_SHADER_PARAMETER_STRUCT() class FSurfaceCacheLayer : SHADER_PERMUTATION_ENUM_CLASS("SURFACE_LAYER", ELumenSurfaceCacheLayer); class FCompress : SHADER_PERMUTATION_BOOL("COMPRESS"); class FCullUndergroundTexels : SHADER_PERMUTATION_BOOL("CULL_UNDERGROUND_TEXELS"); using FPermutationDomain = TShaderPermutationDomain; static FPermutationDomain RemapPermutation(FPermutationDomain PermutationVector) { if (PermutationVector.Get() != ELumenSurfaceCacheLayer::Depth) { PermutationVector.Set(false); } return PermutationVector; } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { const FPermutationDomain PermutationVector(Parameters.PermutationId); if (RemapPermutation(PermutationVector) != PermutationVector) { return false; } return DoesPlatformSupportLumenGI(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); FPermutationDomain PermutationVector(Parameters.PermutationId); if (!PermutationVector.Get()) { const FLumenSurfaceLayerConfig& LumenSurfaceLayerConfig = GetSurfaceLayerConfig(PermutationVector.Get()); OutEnvironment.SetRenderTargetOutputFormat(0, LumenSurfaceLayerConfig.UncompressedFormat); } } }; IMPLEMENT_GLOBAL_SHADER(FLumenCardCopyPS, "/Engine/Private/Lumen/SurfaceCache/LumenSurfaceCache.usf", "LumenCardCopyPS", SF_Pixel); BEGIN_SHADER_PARAMETER_STRUCT(FLumenCardCopyParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FPixelShaderUtils::FRasterizeToRectsVS::FParameters, VS) SHADER_PARAMETER_STRUCT_INCLUDE(FLumenCardCopyPS::FParameters, PS) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() BEGIN_SHADER_PARAMETER_STRUCT(FClearLumenRectsParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FPixelShaderUtils::FRasterizeToRectsVS::FParameters, VS) SHADER_PARAMETER_STRUCT_INCLUDE(FClearLumenCardsPS::FParameters, PS) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() BEGIN_SHADER_PARAMETER_STRUCT(FCopyCardCaptureLightingToAtlasParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FPixelShaderUtils::FRasterizeToRectsVS::FParameters, VS) SHADER_PARAMETER_STRUCT_INCLUDE(FCopyCardCaptureLightingToAtlasPS::FParameters, PS) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() BEGIN_SHADER_PARAMETER_STRUCT(FCopyTextureParameters, ) RDG_TEXTURE_ACCESS(InputTexture, ERHIAccess::CopySrc) RDG_TEXTURE_ACCESS(OutputTexture, ERHIAccess::CopyDest) END_SHADER_PARAMETER_STRUCT() FRDGTextureRef CreateCardAtlas(FRDGBuilder& GraphBuilder, const FIntPoint PageAtlasSize, ESurfaceCacheCompression PhysicalAtlasCompression, ELumenSurfaceCacheLayer LayerId, const TCHAR* Name) { const FLumenSurfaceLayerConfig& Config = GetSurfaceLayerConfig(LayerId); ETextureCreateFlags TexFlags = TexCreate_ShaderResource | TexCreate_NoFastClear; EPixelFormat Format = Config.CompressedFormat; if (PhysicalAtlasCompression == ESurfaceCacheCompression::Disabled || PhysicalAtlasCompression == ESurfaceCacheCompression::FramebufferCompression || Config.CompressedFormat == PF_Unknown) { // Without compression we can write directly into this surface Format = Config.UncompressedFormat; TexFlags |= TexCreate_RenderTargetable; if (PhysicalAtlasCompression == ESurfaceCacheCompression::FramebufferCompression) { TexFlags |= TexCreate_LossyCompressionLowBitrate; } } FRDGTextureDesc CreateInfo = FRDGTextureDesc::Create2D( PageAtlasSize, Format, FClearValueBinding(FLinearColor(Config.ClearValue)), TexFlags); // With UAV aliasing we can directly write into a BC target by overriding UAV format if (PhysicalAtlasCompression == ESurfaceCacheCompression::UAVAliasing && Config.CompressedFormat != PF_Unknown) { CreateInfo.Flags |= TexCreate_UAV; CreateInfo.UAVFormat = Config.CompressedUAVFormat; } return GraphBuilder.CreateTexture(CreateInfo, Name); } void FLumenSceneData::AllocateCardAtlases(FRDGBuilder& GraphBuilder, FLumenSceneFrameTemporaries& FrameTemporaries, const FSceneViewFamily* ViewFamily) { const FIntPoint PageAtlasSize = GetPhysicalAtlasSize(); FrameTemporaries.AlbedoAtlas = CreateCardAtlas(GraphBuilder, PageAtlasSize, PhysicalAtlasCompression, ELumenSurfaceCacheLayer::Albedo, TEXT("Lumen.SceneAlbedo")); FrameTemporaries.OpacityAtlas = CreateCardAtlas(GraphBuilder, PageAtlasSize, PhysicalAtlasCompression, ELumenSurfaceCacheLayer::Opacity, TEXT("Lumen.SceneOpacity")); FrameTemporaries.DepthAtlas = CreateCardAtlas(GraphBuilder, PageAtlasSize, PhysicalAtlasCompression, ELumenSurfaceCacheLayer::Depth, TEXT("Lumen.SceneDepth")); FrameTemporaries.NormalAtlas = CreateCardAtlas(GraphBuilder, PageAtlasSize, PhysicalAtlasCompression, ELumenSurfaceCacheLayer::Normal, TEXT("Lumen.SceneNormal")); FrameTemporaries.EmissiveAtlas = CreateCardAtlas(GraphBuilder, PageAtlasSize, PhysicalAtlasCompression, ELumenSurfaceCacheLayer::Emissive, TEXT("Lumen.SceneEmissive")); FrameTemporaries.DirectLightingAtlas = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D( PageAtlasSize, Lumen::GetDirectLightingAtlasFormat(), FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV ), TEXT("Lumen.SceneDirectLighting")); FrameTemporaries.IndirectLightingAtlas = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D( GetRadiosityAtlasSize(), Lumen::GetIndirectLightingAtlasFormat(), FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV ), TEXT("Lumen.SceneIndirectLighting")); FrameTemporaries.RadiosityNumFramesAccumulatedAtlas = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D( GetRadiosityAtlasSize(), Lumen::GetNumFramesAccumulatedAtlasFormat(), FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV ), TEXT("Lumen.SceneNumFramesAccumulatedAtlas")); FrameTemporaries.FinalLightingAtlas = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D( PageAtlasSize, Lumen::GetLightingDataFormat(), FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV ), TEXT("Lumen.SceneFinalLighting")); const FIntPoint PageAtlasSizeInTiles = PageAtlasSize / Lumen::CardTileSize; FrameTemporaries.TileShadowDownsampleFactorAtlas = GraphBuilder.CreateBuffer( FRDGBufferDesc::CreateBufferDesc( sizeof(uint32), PageAtlasSizeInTiles.X * PageAtlasSizeInTiles.Y * Lumen::CardTileShadowDownsampleFactorDwords ), TEXT("Lumen.TileShadowDownsampleFactorAtlas")); if (LumenSceneDirectLighting::UseStochasticLighting(*ViewFamily)) { FrameTemporaries.DiffuseLightingAndSecondMomentHistoryAtlas = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D( PageAtlasSize, PF_FloatRGBA, FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV ), TEXT("Lumen.SceneDirectLighting.DiffuseLightingAndSecondMomentHistory")); FrameTemporaries.NumFramesAccumulatedHistoryAtlas = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D( PageAtlasSize, PF_G8, FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV ), TEXT("Lumen.SceneDirectLighting.NumFramesAccumulatedHistory")); } } class FGenerateDilationTileDataCS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FGenerateDilationTileDataCS) SHADER_USE_PARAMETER_STRUCT(FGenerateDilationTileDataCS, FGlobalShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(uint32, NumCardPages) SHADER_PARAMETER(uint32, DilationTileDataOffset) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, CardPageRectBuffer) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, DilationPageMaskBuffer) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWTileAllocator) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWDilationTileAllocator) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWPackedCardTileDataBuffer) END_SHADER_PARAMETER_STRUCT() using FPermutationDomain = TShaderPermutationDomain<>; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportLumenGI(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), GetGroupSize()); } static int32 GetGroupSize() { return 64; } }; IMPLEMENT_GLOBAL_SHADER(FGenerateDilationTileDataCS, "/Engine/Private/Lumen/SurfaceCache/LumenSurfaceCache.usf", "GenerateDilationTileDataCS", SF_Compute); class FCopyCapturedCardPageCS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FCopyCapturedCardPageCS) SHADER_USE_PARAMETER_STRUCT(FCopyCapturedCardPageCS, FGlobalShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER(uint32, DilationTileDataOffset) SHADER_PARAMETER(FVector2f, OneOverSourceAtlasSize) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceAlbedoAtlas) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceNormalAtlas) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceDepthAtlas) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, CardPageRectBuffer) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, PackedCardTileDataBuffer) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWAlbedoAtlas) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWNormalAtlas) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWDepthAtlas) END_SHADER_PARAMETER_STRUCT() class FDilateOneTexel : SHADER_PERMUTATION_BOOL("DILATE_ONE_TEXEL"); using FPermutationDomain = TShaderPermutationDomain; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportLumenGI(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), GetGroupSize()); } static int32 GetGroupSize() { return 8; } }; IMPLEMENT_GLOBAL_SHADER(FCopyCapturedCardPageCS, "/Engine/Private/Lumen/SurfaceCache/LumenSurfaceCache.usf", "CopyCapturedCardPageCS", SF_Compute); void FDeferredShadingSceneRenderer::DilateCardPageOneTexel( FRDGBuilder& GraphBuilder, const FViewInfo& View, int32 NumPagesNeedDilation, FRDGBufferSRVRef CardCaptureRectBufferSRV, TConstArrayView CardPagesToRender, FCardCaptureAtlas& CardCaptureAtlas) { const int32 DilationMode = CVarLumenSurfaceCacheDilationMode.GetValueOnRenderThread(); if ((NumPagesNeedDilation == 0 && DilationMode != 2) || DilationMode == 0) { return; } LLM_SCOPE_BYTAG(Lumen); RDG_EVENT_SCOPE(GraphBuilder, "DilateCardPageOneTexel"); FRDGUploadData DilationPageMaskData(GraphBuilder, FMath::DivideAndRoundUp(CardPagesToRender.Num(), 32)); FMemory::Memzero(DilationPageMaskData.GetData(), DilationPageMaskData.Num() * sizeof(uint32)); int32 CopyTileCount = 0; int32 DilationTileCount = 0; int32 LocalNumPagesNeedDilation = 0; for (int32 PageIndex = 0; PageIndex < CardPagesToRender.Num(); ++PageIndex) { const FCardPageRenderData& CardPageRenderData = CardPagesToRender[PageIndex]; const FIntPoint PageSize = CardPageRenderData.CardCaptureAtlasRect.Size(); check(PageSize.X % Lumen::CardTileSize == 0 && PageSize.Y % Lumen::CardTileSize == 0); const int32 TileCount = (PageSize.X / Lumen::CardTileSize) * (PageSize.Y / Lumen::CardTileSize); if (CardPageRenderData.NeedsRender() && (CardPageRenderData.DilationMode == ELumenCardDilationMode::DilateOneTexel || DilationMode == 2)) { DilationPageMaskData[PageIndex / 32] |= (1u << (PageIndex % 32u)); DilationTileCount += TileCount; ++LocalNumPagesNeedDilation; } else { CopyTileCount += TileCount; } } check(LocalNumPagesNeedDilation == NumPagesNeedDilation || DilationMode == 2); const int32 PageTileCount = CopyTileCount + DilationTileCount; FRDGBufferRef DilationPageMaskBuffer = CreateUploadBuffer(GraphBuilder, TEXT("Lumen.DilateOneTexel.DilationPageMask"), DilationPageMaskData); FRDGBufferRef TileDataBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), PageTileCount), TEXT("Lumen.DilateOneTexel.PackedTileData")); { FRDGBufferRef TileAllocatorBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 1), TEXT("Lumen.DilateOneTexel.TileAllocator")); FRDGBufferRef DilationTileAllocatorBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 1), TEXT("Lumen.DilateOneTexel.DilationTileAllocator")); FRDGUploadData TileAllocatorInitData(GraphBuilder, 1); TileAllocatorInitData[0] = 0; GraphBuilder.QueueBufferUpload(TileAllocatorBuffer, TileAllocatorInitData.GetData(), TileAllocatorInitData.GetTotalSize(), ERDGInitialDataFlags::NoCopy); GraphBuilder.QueueBufferUpload(DilationTileAllocatorBuffer, TileAllocatorInitData.GetData(), TileAllocatorInitData.GetTotalSize(), ERDGInitialDataFlags::NoCopy); FRDGBufferUAVRef TileAllocatorUAV = GraphBuilder.CreateUAV(TileAllocatorBuffer, PF_R32_UINT); FRDGBufferUAVRef DilationTileAllocatorUAV = GraphBuilder.CreateUAV(DilationTileAllocatorBuffer, PF_R32_UINT); FGenerateDilationTileDataCS::FParameters* Parameters = GraphBuilder.AllocParameters(); Parameters->NumCardPages = CardPagesToRender.Num(); Parameters->DilationTileDataOffset = CopyTileCount; Parameters->CardPageRectBuffer = CardCaptureRectBufferSRV; Parameters->DilationPageMaskBuffer = GraphBuilder.CreateSRV(DilationPageMaskBuffer, PF_R32_UINT); Parameters->RWTileAllocator = TileAllocatorUAV; Parameters->RWDilationTileAllocator = DilationTileAllocatorUAV; Parameters->RWPackedCardTileDataBuffer = GraphBuilder.CreateUAV(TileDataBuffer, PF_R32_UINT); auto ComputeShader = View.ShaderMap->GetShader(); const FIntVector GroupCount = FComputeShaderUtils::GetGroupCount(CardPagesToRender.Num(), FGenerateDilationTileDataCS::GetGroupSize()); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("GenerateDilationTileData"), ComputeShader, Parameters, GroupCount); } FRDGTextureDesc NewAtlasDesc = CardCaptureAtlas.Albedo->Desc; NewAtlasDesc.Flags |= TexCreate_UAV; FRDGTextureRef DilatedAlbedo = GraphBuilder.CreateTexture(NewAtlasDesc, CardCaptureAtlas.Albedo->Name); FRDGTextureUAVRef DilatedAlbedoUAV = GraphBuilder.CreateUAV(DilatedAlbedo, ERDGUnorderedAccessViewFlags::SkipBarrier); NewAtlasDesc = CardCaptureAtlas.Normal->Desc; NewAtlasDesc.Flags |= TexCreate_UAV; FRDGTextureRef DilatedNormal = GraphBuilder.CreateTexture(NewAtlasDesc, CardCaptureAtlas.Normal->Name); FRDGTextureUAVRef DilatedNormalUAV = GraphBuilder.CreateUAV(DilatedNormal, ERDGUnorderedAccessViewFlags::SkipBarrier); // Cannot create UAVs on PF_DepthStencil so use PF_R32_FLOAT instead. As of now, the stencil plane is not used later down the pipeline NewAtlasDesc = FRDGTextureDesc::Create2D(CardCaptureAtlas.Size, PF_R32_FLOAT, FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_UAV | TexCreate_RenderTargetable | TexCreate_NoFastClear); FRDGTextureRef DilatedDepth = GraphBuilder.CreateTexture(NewAtlasDesc, CardCaptureAtlas.DepthStencil->Name); FRDGTextureUAVRef DilatedDepthUAV = GraphBuilder.CreateUAV(DilatedDepth, ERDGUnorderedAccessViewFlags::SkipBarrier); auto CopyCapturedCardPages = [&](bool bDilateOneTexel) { const int32 GroupCount = bDilateOneTexel ? DilationTileCount : CopyTileCount; if (GroupCount == 0) { return; } FCopyCapturedCardPageCS::FParameters* Parameters = GraphBuilder.AllocParameters(); Parameters->View = View.ViewUniformBuffer; Parameters->DilationTileDataOffset = CopyTileCount; Parameters->OneOverSourceAtlasSize = FVector2f(1.f / CardCaptureAtlas.Size.X, 1.f / CardCaptureAtlas.Size.Y); Parameters->SourceAlbedoAtlas = CardCaptureAtlas.Albedo; Parameters->SourceNormalAtlas = CardCaptureAtlas.Normal; Parameters->SourceDepthAtlas = CardCaptureAtlas.DepthStencil; Parameters->CardPageRectBuffer = CardCaptureRectBufferSRV; Parameters->PackedCardTileDataBuffer = GraphBuilder.CreateSRV(TileDataBuffer, PF_R32_UINT); Parameters->RWAlbedoAtlas = DilatedAlbedoUAV; Parameters->RWNormalAtlas = DilatedNormalUAV; Parameters->RWDepthAtlas = DilatedDepthUAV; FCopyCapturedCardPageCS::FPermutationDomain PermutationVector; PermutationVector.Set(bDilateOneTexel); auto ComputeShader = View.ShaderMap->GetShader(PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("CopyCapturedCardPages%s", bDilateOneTexel ? TEXT(" DilateOneTexel") : TEXT("")), ComputeShader, Parameters, FIntVector(GroupCount, 1, 1)); }; CopyCapturedCardPages(false); CopyCapturedCardPages(true); CardCaptureAtlas.Albedo = DilatedAlbedo; CardCaptureAtlas.Normal = DilatedNormal; CardCaptureAtlas.DepthStencil = DilatedDepth; } // Copy captured cards into surface cache. Possibly with compression. Has three paths: // - Compress from capture atlas to surface cache (for platforms supporting GRHISupportsUAVFormatAliasing or when compression is disabled) // - Compress from capture atlas into a temporary atlas and copy results into surface cache // - Straight copy into uncompressed atlas void FDeferredShadingSceneRenderer::UpdateLumenSurfaceCacheAtlas( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FLumenSceneFrameTemporaries& FrameTemporaries, const TArray& CardPagesToRender, FRDGBufferSRVRef CardCaptureRectBufferSRV, const FCardCaptureAtlas& CardCaptureAtlas, const FResampledCardCaptureAtlas& ResampledCardCaptureAtlas) { LLM_SCOPE_BYTAG(Lumen); RDG_EVENT_SCOPE(GraphBuilder, "CopyCardsToSurfaceCache"); FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(View); // Create rect buffer FRDGBufferRef SurfaceCacheRectBuffer; { FRDGUploadData SurfaceCacheRectArray(GraphBuilder, CardPagesToRender.Num()); for (int32 Index = 0; Index < CardPagesToRender.Num(); Index++) { const FCardPageRenderData& CardPageRenderData = CardPagesToRender[Index]; FUintVector4& Rect = SurfaceCacheRectArray[Index]; Rect.X = FMath::Max(CardPageRenderData.SurfaceCacheAtlasRect.Min.X, 0); Rect.Y = FMath::Max(CardPageRenderData.SurfaceCacheAtlasRect.Min.Y, 0); Rect.Z = FMath::Max(CardPageRenderData.SurfaceCacheAtlasRect.Max.X, 0); Rect.W = FMath::Max(CardPageRenderData.SurfaceCacheAtlasRect.Max.Y, 0); } SurfaceCacheRectBuffer = CreateUploadBuffer(GraphBuilder, TEXT("Lumen.SurfaceCacheRects"), sizeof(FUintVector4), FMath::RoundUpToPowerOfTwo(CardPagesToRender.Num()), SurfaceCacheRectArray); } FRDGBufferSRVRef SurfaceCacheRectBufferSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(SurfaceCacheRectBuffer, PF_R32G32B32A32_UINT)); const FIntPoint PhysicalAtlasSize = LumenSceneData.GetPhysicalAtlasSize(); const ESurfaceCacheCompression PhysicalAtlasCompression = LumenSceneData.GetPhysicalAtlasCompression(); const FIntPoint CardCaptureAtlasSize = LumenSceneData.GetCardCaptureAtlasSize(); struct FPassConfig { FRDGTextureRef SurfaceCacheAtlas = nullptr; ELumenSurfaceCacheLayer Layer = ELumenSurfaceCacheLayer::MAX; }; FPassConfig PassConfigs[(uint32)ELumenSurfaceCacheLayer::MAX] = { { FrameTemporaries.DepthAtlas, ELumenSurfaceCacheLayer::Depth }, { FrameTemporaries.AlbedoAtlas, ELumenSurfaceCacheLayer::Albedo }, { FrameTemporaries.OpacityAtlas, ELumenSurfaceCacheLayer::Opacity }, { FrameTemporaries.NormalAtlas, ELumenSurfaceCacheLayer::Normal }, { FrameTemporaries.EmissiveAtlas, ELumenSurfaceCacheLayer::Emissive }, }; for (FPassConfig& Pass : PassConfigs) { const FLumenSurfaceLayerConfig& LayerConfig = GetSurfaceLayerConfig(Pass.Layer); if (PhysicalAtlasCompression == ESurfaceCacheCompression::UAVAliasing && LayerConfig.CompressedFormat != PF_Unknown) { // Compress to surface cache directly { const FIntPoint CompressedCardCaptureAtlasSize = FIntPoint::DivideAndRoundUp(CardCaptureAtlasSize, 4); const FIntPoint CompressedPhysicalAtlasSize = FIntPoint::DivideAndRoundUp(PhysicalAtlasSize, 4); FLumenCardCopyParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->PS.View = View.ViewUniformBuffer; PassParameters->PS.RWAtlasBlock4 = LayerConfig.CompressedUAVFormat == PF_R32G32B32A32_UINT ? GraphBuilder.CreateUAV(Pass.SurfaceCacheAtlas) : nullptr; PassParameters->PS.RWAtlasBlock2 = LayerConfig.CompressedUAVFormat == PF_R32G32_UINT ? GraphBuilder.CreateUAV(Pass.SurfaceCacheAtlas) : nullptr; PassParameters->PS.SourceAlbedoAtlas = CardCaptureAtlas.Albedo; PassParameters->PS.SourceNormalAtlas = CardCaptureAtlas.Normal; PassParameters->PS.SourceEmissiveAtlas = CardCaptureAtlas.Emissive; PassParameters->PS.SourceDepthAtlas = CardCaptureAtlas.DepthStencil; PassParameters->PS.OneOverSourceAtlasSize = FVector2f(1.0f, 1.0f) / FVector2f(CardCaptureAtlasSize); FLumenCardCopyPS::FPermutationDomain PermutationVector; PermutationVector.Set(Pass.Layer); PermutationVector.Set(true); auto PixelShader = View.ShaderMap->GetShader(PermutationVector); const ERDGPassFlags AdditionalRenderPassFlags = (PassParameters->RenderTargets.GetActiveCount() == 0) ? ERDGPassFlags::SkipRenderPass : ERDGPassFlags::None; FPixelShaderUtils::AddRasterizeToRectsPass(GraphBuilder, View.ShaderMap, RDG_EVENT_NAME("CompressToSurfaceCache %s", LayerConfig.Name), PixelShader, PassParameters, CompressedPhysicalAtlasSize, SurfaceCacheRectBufferSRV, CardPagesToRender.Num(), /*BlendState*/ nullptr, /*RasterizerState*/ nullptr, /*DepthStencilState*/ nullptr, /*StencilRef*/ 0, /*TextureSize*/ CompressedCardCaptureAtlasSize, /*RectUVBufferSRV*/ CardCaptureRectBufferSRV, /*DownsampleFactor*/ 4, AdditionalRenderPassFlags); } } else if (PhysicalAtlasCompression == ESurfaceCacheCompression::CopyTextureRegion && LayerConfig.CompressedFormat != PF_Unknown) { // Compress through a temp surface const FIntPoint TempAtlasSize = FIntPoint::DivideAndRoundUp(CardCaptureAtlasSize, 4); // TempAtlas is required on platforms without UAV aliasing (GRHISupportsUAVFormatAliasing), where we can't directly compress into the final surface cache FRDGTextureRef TempAtlas = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D( TempAtlasSize, LayerConfig.CompressedUAVFormat, FClearValueBinding::None, TexCreate_UAV | TexCreate_ShaderResource | TexCreate_NoFastClear), TEXT("Lumen.TempCaptureAtlas")); // Compress into temporary atlas { FLumenCardCopyParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->PS.View = View.ViewUniformBuffer; PassParameters->PS.RWAtlasBlock4 = LayerConfig.CompressedUAVFormat == PF_R32G32B32A32_UINT ? GraphBuilder.CreateUAV(TempAtlas) : nullptr; PassParameters->PS.RWAtlasBlock2 = LayerConfig.CompressedUAVFormat == PF_R32G32_UINT ? GraphBuilder.CreateUAV(TempAtlas) : nullptr; PassParameters->PS.SourceAlbedoAtlas = CardCaptureAtlas.Albedo; PassParameters->PS.SourceNormalAtlas = CardCaptureAtlas.Normal; PassParameters->PS.SourceEmissiveAtlas = CardCaptureAtlas.Emissive; PassParameters->PS.SourceDepthAtlas = CardCaptureAtlas.DepthStencil; PassParameters->PS.OneOverSourceAtlasSize = FVector2f(1.0f, 1.0f) / FVector2f(CardCaptureAtlasSize); FLumenCardCopyPS::FPermutationDomain PermutationVector; PermutationVector.Set(Pass.Layer); PermutationVector.Set(true); auto PixelShader = View.ShaderMap->GetShader(PermutationVector); const ERDGPassFlags AdditionalRenderPassFlags = (PassParameters->RenderTargets.GetActiveCount() == 0) ? ERDGPassFlags::SkipRenderPass : ERDGPassFlags::None; FPixelShaderUtils::AddRasterizeToRectsPass(GraphBuilder, View.ShaderMap, RDG_EVENT_NAME("CompressToTemp %s", LayerConfig.Name), PixelShader, PassParameters, TempAtlasSize, CardCaptureRectBufferSRV, CardPagesToRender.Num(), /*BlendState*/ nullptr, /*RasterizerState*/ nullptr, /*DepthStencilState*/ nullptr, /*StencilRef*/ 0, /*TextureSize*/ TempAtlasSize, /*RectUVBufferSRV*/ nullptr, /*DownsampleFactor*/ 4, AdditionalRenderPassFlags); } // Copy from temporary atlas to surface cache { FCopyTextureParameters* Parameters = GraphBuilder.AllocParameters(); Parameters->InputTexture = TempAtlas; Parameters->OutputTexture = Pass.SurfaceCacheAtlas; GraphBuilder.AddPass( RDG_EVENT_NAME("CopyTempToSurfaceCache %s", LayerConfig.Name), Parameters, ERDGPassFlags::Copy, [&CardPagesToRender, InputTexture = TempAtlas, OutputTexture = Pass.SurfaceCacheAtlas](FRDGAsyncTask, FRHICommandList& RHICmdList) { for (int32 PageIndex = 0; PageIndex < CardPagesToRender.Num(); ++PageIndex) { const FCardPageRenderData& Page = CardPagesToRender[PageIndex]; FRHICopyTextureInfo CopyInfo; CopyInfo.Size.X = Page.CardCaptureAtlasRect.Width() / 4; CopyInfo.Size.Y = Page.CardCaptureAtlasRect.Height() / 4; CopyInfo.Size.Z = 1; CopyInfo.SourcePosition.X = Page.CardCaptureAtlasRect.Min.X / 4; CopyInfo.SourcePosition.Y = Page.CardCaptureAtlasRect.Min.Y / 4; CopyInfo.SourcePosition.Z = 0; CopyInfo.DestPosition.X = Page.SurfaceCacheAtlasRect.Min.X; CopyInfo.DestPosition.Y = Page.SurfaceCacheAtlasRect.Min.Y; CopyInfo.DestPosition.Z = 0; RHICmdList.CopyTexture(InputTexture->GetRHI(), OutputTexture->GetRHI(), CopyInfo); } }); } } else { // Copy uncompressed to surface cache { FLumenCardCopyParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->RenderTargets[0] = FRenderTargetBinding(Pass.SurfaceCacheAtlas, ERenderTargetLoadAction::ELoad, 0); PassParameters->PS.View = View.ViewUniformBuffer; PassParameters->PS.SourceAlbedoAtlas = CardCaptureAtlas.Albedo; PassParameters->PS.SourceNormalAtlas = CardCaptureAtlas.Normal; PassParameters->PS.SourceEmissiveAtlas = CardCaptureAtlas.Emissive; PassParameters->PS.SourceDepthAtlas = CardCaptureAtlas.DepthStencil; PassParameters->PS.OneOverSourceAtlasSize = FVector2f(1.0f, 1.0f) / FVector2f(CardCaptureAtlasSize); bool bCullUndergroundTexels = Pass.Layer == ELumenSurfaceCacheLayer::Depth && LumenScene::CullUndergroundTexels(); if (bCullUndergroundTexels) { bCullUndergroundTexels = Lumen::SetLandscapeHeightSamplingParameters( FrameTemporaries.ViewOrigins[0].LumenSceneViewOrigin, Scene, PassParameters->PS.LandscapeHeightSamplingParameters); if (bCullUndergroundTexels) { FRDGUploadData CardUVRectsData(GraphBuilder, CardPagesToRender.Num()); FRDGUploadData CardIndicesData(GraphBuilder, CardPagesToRender.Num()); for (int32 Index = 0; Index < CardPagesToRender.Num(); ++Index) { const FCardPageRenderData& CardPageRenderData = CardPagesToRender[Index]; check(CardPageRenderData.CardIndex >= 0); CardUVRectsData[Index] = CardPageRenderData.CardUVRect; CardIndicesData[Index] = CardPageRenderData.bHeightField ? 0xffffffff : CardPageRenderData.CardIndex; } FRDGBufferRef CardUVRectsBuffer = CreateUploadBuffer(GraphBuilder, TEXT("Lumen.CardUVRects"), CardUVRectsData); FRDGBufferRef CardIndicesBuffer = CreateUploadBuffer(GraphBuilder, TEXT("Lumen.CardIndices"), CardIndicesData); PassParameters->PS.LumenCardScene = FrameTemporaries.LumenCardSceneUniformBuffer; PassParameters->PS.CardUVRects = GraphBuilder.CreateSRV(CardUVRectsBuffer, PF_R32G32B32A32_UINT); PassParameters->PS.CardIndices = GraphBuilder.CreateSRV(CardIndicesBuffer, PF_R32_UINT); PassParameters->PS.TexelCullingHeightBias = CVarLumenSurfaceCacheCullUndergroundTexelsHeightBias.GetValueOnRenderThread(); } } FLumenCardCopyPS::FPermutationDomain PermutationVector; PermutationVector.Set(Pass.Layer); PermutationVector.Set(false); PermutationVector.Set(bCullUndergroundTexels); auto PixelShader = View.ShaderMap->GetShader(PermutationVector); FPixelShaderUtils::AddRasterizeToRectsPass(GraphBuilder, View.ShaderMap, RDG_EVENT_NAME("CopyToSurfaceCache %s", LayerConfig.Name), PixelShader, PassParameters, PhysicalAtlasSize, SurfaceCacheRectBufferSRV, CardPagesToRender.Num(), /*BlendState*/ nullptr, /*RasterizerState*/ nullptr, /*DepthStencilState*/ nullptr, /*StencilRef*/ 0, /*TextureSize*/ CardCaptureAtlasSize, /*RectUVBufferSRV*/ CardCaptureRectBufferSRV); } } } // Fill lighting for newly captured cards { // Downsampled radiosity atlas copy not implemented yet check(LumenRadiosity::GetAtlasDownsampleFactor() == 1); extern int32 GLumenSceneSurfaceCacheResampleLighting; const bool bResample = GLumenSceneSurfaceCacheResampleLighting != 0 && ResampledCardCaptureAtlas.DirectLighting != nullptr; const bool bRadiosityEnabled = LumenRadiosity::IsEnabled(ViewFamily); FCopyCardCaptureLightingToAtlasParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->RenderTargets[0] = FRenderTargetBinding(FrameTemporaries.DirectLightingAtlas, ERenderTargetLoadAction::ELoad, 0); PassParameters->RenderTargets[1] = FRenderTargetBinding(FrameTemporaries.FinalLightingAtlas, ERenderTargetLoadAction::ELoad, 0); PassParameters->RenderTargets[2] = FRenderTargetBinding(FrameTemporaries.IndirectLightingAtlas, ERenderTargetLoadAction::ELoad, 0); PassParameters->RenderTargets[3] = FRenderTargetBinding(FrameTemporaries.RadiosityNumFramesAccumulatedAtlas, ERenderTargetLoadAction::ELoad, 0); PassParameters->PS.View = View.ViewUniformBuffer; PassParameters->PS.DiffuseColorBoost = 1.0f / FMath::Max(View.FinalPostProcessSettings.LumenDiffuseColorBoost, 1.0f); const FIntPoint CardCaptureAtlasSizeInTiles = CardCaptureAtlasSize / Lumen::CardTileSize; PassParameters->PS.CardCaptureAtlasSizeInTiles = FUintVector2(CardCaptureAtlasSizeInTiles.X, CardCaptureAtlasSizeInTiles.Y); PassParameters->PS.OutputAtlasWidthInTiles = PhysicalAtlasSize.X / Lumen::CardTileSize; PassParameters->PS.AlbedoCardCaptureAtlas = CardCaptureAtlas.Albedo; PassParameters->PS.EmissiveCardCaptureAtlas = CardCaptureAtlas.Emissive; PassParameters->PS.DirectLightingCardCaptureAtlas = ResampledCardCaptureAtlas.DirectLighting; PassParameters->PS.RadiosityCardCaptureAtlas = ResampledCardCaptureAtlas.IndirectLighting; PassParameters->PS.RadiosityNumFramesAccumulatedCardCaptureAtlas = ResampledCardCaptureAtlas.NumFramesAccumulated; PassParameters->PS.TileShadowDownsampleFactorAtlasForResampling = bResample ? GraphBuilder.CreateSRV(ResampledCardCaptureAtlas.TileShadowDownsampleFactor, PF_R32G32B32A32_UINT) : nullptr; PassParameters->PS.RWTileShadowDownsampleFactorAtlas = GraphBuilder.CreateUAV(FrameTemporaries.TileShadowDownsampleFactorAtlas, PF_R32G32B32A32_UINT); FCopyCardCaptureLightingToAtlasPS::FPermutationDomain PermutationVector; PermutationVector.Set(bRadiosityEnabled); PermutationVector.Set(bResample); auto PixelShader = View.ShaderMap->GetShader(PermutationVector); FPixelShaderUtils::AddRasterizeToRectsPass(GraphBuilder, View.ShaderMap, RDG_EVENT_NAME("CopyCardCaptureLightingToAtlas"), PixelShader, PassParameters, LumenSceneData.GetPhysicalAtlasSize(), SurfaceCacheRectBufferSRV, CardPagesToRender.Num(), /*BlendState*/ nullptr, /*RasterizerState*/ nullptr, /*DepthStencilState*/ nullptr, /*StencilRef*/ 0, /*TextureSize*/ CardCaptureAtlasSize, /*RectUVBufferSRV*/ CardCaptureRectBufferSRV, /*DownsampleFactor*/ 1); } } class FClearCompressedAtlasCS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FClearCompressedAtlasCS) SHADER_USE_PARAMETER_STRUCT(FClearCompressedAtlasCS, FGlobalShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWAtlasBlock4) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWAtlasBlock2) SHADER_PARAMETER(FVector3f, ClearValue) SHADER_PARAMETER(FIntPoint, OutputAtlasSize) END_SHADER_PARAMETER_STRUCT() class FSurfaceCacheLayer : SHADER_PERMUTATION_ENUM_CLASS("SURFACE_LAYER", ELumenSurfaceCacheLayer); using FPermutationDomain = TShaderPermutationDomain; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportLumenGI(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), GetGroupSize()); } static int32 GetGroupSize() { return 8; } }; IMPLEMENT_GLOBAL_SHADER(FClearCompressedAtlasCS, "/Engine/Private/Lumen/SurfaceCache/LumenSurfaceCache.usf", "ClearCompressedAtlasCS", SF_Compute); // Clear entire Lumen surface cache to debug default values // Surface cache can be compressed void FDeferredShadingSceneRenderer::ClearLumenSurfaceCacheAtlas( FRDGBuilder& GraphBuilder, const FLumenSceneFrameTemporaries& FrameTemporaries, const FGlobalShaderMap* GlobalShaderMap) { RDG_EVENT_SCOPE(GraphBuilder, "ClearLumenSurfaceCache"); FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(Views[0]); struct FPassConfig { FRDGTextureRef SurfaceCacheAtlas = nullptr; ELumenSurfaceCacheLayer Layer = ELumenSurfaceCacheLayer::MAX; }; FPassConfig PassConfigs[(uint32)ELumenSurfaceCacheLayer::MAX] = { { FrameTemporaries.DepthAtlas, ELumenSurfaceCacheLayer::Depth }, { FrameTemporaries.AlbedoAtlas, ELumenSurfaceCacheLayer::Albedo }, { FrameTemporaries.OpacityAtlas, ELumenSurfaceCacheLayer::Opacity }, { FrameTemporaries.NormalAtlas, ELumenSurfaceCacheLayer::Normal }, { FrameTemporaries.EmissiveAtlas, ELumenSurfaceCacheLayer::Emissive }, }; const FIntPoint PhysicalAtlasSize = LumenSceneData.GetPhysicalAtlasSize(); const ESurfaceCacheCompression PhysicalAtlasCompression = LumenSceneData.GetPhysicalAtlasCompression(); for (FPassConfig& Pass : PassConfigs) { const FLumenSurfaceLayerConfig& LayerConfig = GetSurfaceLayerConfig(Pass.Layer); if (PhysicalAtlasCompression == ESurfaceCacheCompression::UAVAliasing && LayerConfig.CompressedFormat != PF_Unknown) { // Clear compressed surface cache directly { const FRDGTextureUAVDesc CompressedSurfaceUAVDesc(Pass.SurfaceCacheAtlas, 0, LayerConfig.CompressedUAVFormat); FClearCompressedAtlasCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->RWAtlasBlock4 = LayerConfig.CompressedUAVFormat == PF_R32G32B32A32_UINT ? GraphBuilder.CreateUAV(CompressedSurfaceUAVDesc) : nullptr; PassParameters->RWAtlasBlock2 = LayerConfig.CompressedUAVFormat == PF_R32G32_UINT ? GraphBuilder.CreateUAV(CompressedSurfaceUAVDesc) : nullptr; PassParameters->ClearValue = (FVector3f)LayerConfig.ClearValue; PassParameters->OutputAtlasSize = PhysicalAtlasSize; FClearCompressedAtlasCS::FPermutationDomain PermutationVector; PermutationVector.Set(Pass.Layer); auto ComputeShader = GlobalShaderMap->GetShader(PermutationVector); FIntPoint GroupSize(FIntPoint::DivideAndRoundUp(PhysicalAtlasSize, FClearCompressedAtlasCS::GetGroupSize())); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("ClearCompressedAtlas %s", LayerConfig.Name), ComputeShader, PassParameters, FIntVector(GroupSize.X, GroupSize.Y, 1)); } } else if (PhysicalAtlasCompression == ESurfaceCacheCompression::CopyTextureRegion && LayerConfig.CompressedFormat != PF_Unknown) { // Temporary atlas is required on platforms without UAV aliasing (GRHISupportsUAVFormatAliasing), where we can't directly compress into the final surface cache const FIntPoint TempAtlasSize = FIntPoint::DivideAndRoundUp(LumenSceneData.GetCardCaptureAtlasSize(), 4); const EPixelFormat TempFormat = LayerConfig.CompressedUAVFormat; FRDGTextureRef TempAtlas = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D( TempAtlasSize, LayerConfig.CompressedUAVFormat, FClearValueBinding::None, TexCreate_UAV | TexCreate_ShaderResource | TexCreate_NoFastClear), TEXT("Lumen.TempCaptureAtlas")); // Clear temporary atlas { FClearCompressedAtlasCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->RWAtlasBlock4 = LayerConfig.CompressedUAVFormat == PF_R32G32B32A32_UINT ? GraphBuilder.CreateUAV(TempAtlas) : nullptr; PassParameters->RWAtlasBlock2 = LayerConfig.CompressedUAVFormat == PF_R32G32_UINT ? GraphBuilder.CreateUAV(TempAtlas) : nullptr; PassParameters->ClearValue = (FVector3f)LayerConfig.ClearValue; PassParameters->OutputAtlasSize = TempAtlasSize; FClearCompressedAtlasCS::FPermutationDomain PermutationVector; PermutationVector.Set(Pass.Layer); auto ComputeShader = GlobalShaderMap->GetShader(PermutationVector); FIntPoint GroupSize(FIntPoint::DivideAndRoundUp(TempAtlasSize, FClearCompressedAtlasCS::GetGroupSize())); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("ClearCompressedAtlas %s", LayerConfig.Name), ComputeShader, PassParameters, FIntVector(GroupSize.X, GroupSize.Y, 1)); } // Copy from temporary atlas into surface cache { FCopyTextureParameters* Parameters = GraphBuilder.AllocParameters(); Parameters->InputTexture = TempAtlas; Parameters->OutputTexture = Pass.SurfaceCacheAtlas; GraphBuilder.AddPass( RDG_EVENT_NAME("CopyToSurfaceCache %s", LayerConfig.Name), Parameters, ERDGPassFlags::Copy, [InputTexture = TempAtlas, PhysicalAtlasSize, TempAtlasSize, OutputTexture = Pass.SurfaceCacheAtlas](FRDGAsyncTask, FRHICommandList& RHICmdList) { const int32 NumTilesX = FMath::DivideAndRoundDown(PhysicalAtlasSize.X / 4, TempAtlasSize.X); const int32 NumTilesY = FMath::DivideAndRoundDown(PhysicalAtlasSize.Y / 4, TempAtlasSize.Y); for (int32 TileY = 0; TileY < NumTilesY; ++TileY) { for (int32 TileX = 0; TileX < NumTilesX; ++TileX) { FRHICopyTextureInfo CopyInfo; CopyInfo.Size.X = TempAtlasSize.X; CopyInfo.Size.Y = TempAtlasSize.Y; CopyInfo.Size.Z = 1; CopyInfo.SourcePosition.X = 0; CopyInfo.SourcePosition.Y = 0; CopyInfo.SourcePosition.Z = 0; CopyInfo.DestPosition.X = TileX * TempAtlasSize.X * 4; CopyInfo.DestPosition.Y = TileY * TempAtlasSize.Y * 4; CopyInfo.DestPosition.Z = 0; RHICmdList.CopyTexture(InputTexture->GetRHI(), OutputTexture->GetRHI(), CopyInfo); } } }); } } else { // Simple clear of an uncompressed surface cache AddClearRenderTargetPass(GraphBuilder, Pass.SurfaceCacheAtlas, FLinearColor(LayerConfig.ClearValue)); } } AddClearRenderTargetPass(GraphBuilder, FrameTemporaries.DirectLightingAtlas); AddClearRenderTargetPass(GraphBuilder, FrameTemporaries.IndirectLightingAtlas); AddClearRenderTargetPass(GraphBuilder, FrameTemporaries.RadiosityNumFramesAccumulatedAtlas); AddClearRenderTargetPass(GraphBuilder, FrameTemporaries.FinalLightingAtlas); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(FrameTemporaries.TileShadowDownsampleFactorAtlas, PF_R32_UINT), 0); }