// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= VirtualShadowMap.h: =============================================================================*/ #include "VirtualShadowMapArray.h" #include "VirtualShadowMapVisualizationData.h" #include "VirtualShadowMapDefinitions.h" #include "../BasePassRendering.h" #include "../ScreenPass.h" #include "Components/LightComponent.h" #include "RendererModule.h" #include "Rendering/NaniteResources.h" #include "ShaderPrint.h" #include "ShaderPrintParameters.h" #include "VirtualShadowMapCacheManager.h" #include "VirtualShadowMapClipmap.h" #include "ComponentRecreateRenderStateContext.h" #include "HairStrands/HairStrandsData.h" #include "SceneTextureReductions.h" #include "GPUMessaging.h" #include "InstanceCulling/InstanceCullingMergedContext.h" #define DEBUG_ALLOW_STATIC_SEPARATE_WITHOUT_CACHING 0 IMPLEMENT_STATIC_UNIFORM_BUFFER_SLOT(VirtualShadowMapUbSlot); IMPLEMENT_STATIC_UNIFORM_BUFFER_STRUCT(FVirtualShadowMapUniformParameters, "VirtualShadowMap", VirtualShadowMapUbSlot); struct FShadowMapCacheData { int32 PrevVirtualShadowMapId = INDEX_NONE; }; struct FPhysicalPageMetaData { uint32 Flags; uint32 Age; uint32 VirtualPageOffset; }; int32 GEnableVirtualShadowMaps = 0; FAutoConsoleVariableRef CVarEnableVirtualShadowMaps( TEXT("r.Shadow.Virtual.Enable"), GEnableVirtualShadowMaps, TEXT("Enable Virtual Shadow Maps."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable) { // Needed because the depth state changes with method (so cached draw commands must be re-created) see SetStateForShadowDepth FGlobalComponentRecreateRenderStateContext Context; }), ECVF_Scalability | ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarMaxPhysicalPages( TEXT("r.Shadow.Virtual.MaxPhysicalPages"), 2048, TEXT("Maximum number of physical pages in the pool."), ECVF_Scalability | ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarCacheStaticSeparate( TEXT("r.Shadow.Virtual.Cache.StaticSeparate"), 1, TEXT("When enabled, caches static objects in separate pages from dynamic objects.\n") TEXT("This can improve performance in largely static scenes, but doubles the memory cost of the physical page pool."), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarShowStats( TEXT("r.Shadow.Virtual.ShowStats"), 0, TEXT("ShowStats, also toggle shaderprint one!"), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarResolutionLodBiasLocal( TEXT("r.Shadow.Virtual.ResolutionLodBiasLocal"), 0.0f, TEXT("Bias applied to LOD calculations for local lights. -1.0 doubles resolution, 1.0 halves it and so on."), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarPageDilationBorderSizeDirectional( TEXT("r.Shadow.Virtual.PageDilationBorderSizeDirectional"), 0.05f, TEXT("If a screen pixel falls within this fraction of a page border for directional lights, the adacent page will also be mapped.") TEXT("Higher values can reduce page misses at screen edges or disocclusions, but increase total page counts."), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarPageDilationBorderSizeLocal( TEXT("r.Shadow.Virtual.PageDilationBorderSizeLocal"), 0.05f, TEXT("If a screen pixel falls within this fraction of a page border for local lights, the adacent page will also be mapped.") TEXT("Higher values can reduce page misses at screen edges or disocclusions, but increase total page counts."), ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarMarkPixelPages( TEXT("r.Shadow.Virtual.MarkPixelPages"), 1, TEXT("Marks pages in virtual shadow maps based on depth buffer pixels. Ability to disable is primarily for profiling and debugging."), ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarMarkCoarsePagesDirectional( TEXT("r.Shadow.Virtual.MarkCoarsePagesDirectional"), 1, TEXT("Marks coarse pages in directional light virtual shadow maps so that low resolution data is available everywhere.") TEXT("Ability to disable is primarily for profiling and debugging."), ECVF_Scalability | ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarMarkCoarsePagesLocal( TEXT("r.Shadow.Virtual.MarkCoarsePagesLocal"), 1, TEXT("Marks coarse pages in local light virtual shadow maps so that low resolution data is available everywhere.") TEXT("Ability to disable is primarily for profiling and debugging."), ECVF_Scalability | ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarCoarsePagesIncludeNonNanite( TEXT("r.Shadow.Virtual.NonNanite.IncludeInCoarsePages"), 1, TEXT("Include non-Nanite geometry in coarse pages.") TEXT("Rendering non-Nanite geometry into large coarse pages can be expensive; disabling this can be a significant performance win."), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarShowClipmapStats( TEXT("r.Shadow.Virtual.ShowClipmapStats"), -1, TEXT("Set to the number of clipmap you want to show stats for (-1 == off)"), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarCullBackfacingPixels( TEXT("r.Shadow.Virtual.CullBackfacingPixels"), 1, TEXT("When enabled does not generate shadow data for pixels that are backfacing to the light."), ECVF_RenderThreadSafe ); int32 GEnableNonNaniteVSM = 1; FAutoConsoleVariableRef CVarEnableNonNaniteVSM( TEXT("r.Shadow.Virtual.NonNaniteVSM"), GEnableNonNaniteVSM, TEXT("Enable support for non-nanite Virtual Shadow Maps.") TEXT("Read-only and to be set in a config file (requires restart)."), ECVF_RenderThreadSafe | ECVF_ReadOnly ); static TAutoConsoleVariable CVarNonNaniteVsmUseHzb( TEXT("r.Shadow.Virtual.NonNanite.UseHZB"), 2, TEXT("Cull Non-Nanite instances using HZB. If set to 2, attempt to use Nanite-HZB from the current frame."), ECVF_RenderThreadSafe); TAutoConsoleVariable CVarInitializePhysicalUsingIndirect( TEXT("r.Shadow.Virtual.InitPhysicalUsingIndirect"), 1, TEXT("."), ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarMergePhysicalUsingIndirect( TEXT("r.Shadow.Virtual.MergePhysicalUsingIndirect"), 1, TEXT("."), ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarVirtualShadowOnePassProjectionMaxLights( TEXT("r.Shadow.Virtual.OnePassProjection.MaxLightsPerPixel"), 16, TEXT("Maximum lights per pixel that get full filtering when using one pass projection and clustered shading.") TEXT("Generally set to 8 (32bpp), 16 (64bpp) or 32 (128bpp). Lower values require less transient VRAM during the lighting pass."), ECVF_Scalability | ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarDoNonNaniteBatching( TEXT("r.Shadow.Virtual.NonNanite.Batch"), 1, TEXT("."), ECVF_RenderThreadSafe ); #if !UE_BUILD_SHIPPING bool GDumpVSMLightNames = false; void DumpVSMLightNames() { ENQUEUE_RENDER_COMMAND(DumpVSMLightNames)( [](FRHICommandList& RHICmdList) { GDumpVSMLightNames = true; }); } FAutoConsoleCommand CmdDumpVSMLightNames( TEXT("r.Shadow.Virtual.Visualize.DumpLightNames"), TEXT("Dump light names with virtual shadow maps (for developer use in non-shiping builds)"), FConsoleCommandDelegate::CreateStatic(DumpVSMLightNames) ); FString GVirtualShadowMapVisualizeLightName; FAutoConsoleVariableRef CVarVisualizeLightName( TEXT("r.Shadow.Virtual.Visualize.LightName"), GVirtualShadowMapVisualizeLightName, TEXT("Sets the name of a specific light to visualize (for developer use in non-shiping builds)"), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarVisualizeLayout( TEXT("r.Shadow.Virtual.Visualize.Layout"), 0, TEXT("Overlay layout when virtual shadow map visualization is enabled:\n") TEXT(" 0: Full screen\n") TEXT(" 1: Thumbnail\n") TEXT(" 2: Split screen"), ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarDebugSkipMergePhysical( TEXT("r.Shadow.Virtual.DebugSkipMergePhysical"), 0, TEXT(""), ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarDebugSkipDynamicPageInvalidation( TEXT("r.Shadow.Virtual.Cache.DebugSkipDynamicPageInvalidation"), 0, TEXT("Skip invalidation of cached pages when geometry moves for debugging purposes. This will create obvious visual artifacts when disabled.\n"), ECVF_RenderThreadSafe ); #endif // !UE_BUILD_SHIPPING static TAutoConsoleVariable CVarMaxMaterialPositionInvalidationRange( TEXT("r.Shadow.Virtual.Cache.MaxMaterialPositionInvalidationRange"), -1.0f, TEXT("Beyond this distance in world units, material position effects (e.g., WPO or PDO) cease to cause VSM invalidations.\n") TEXT(" This can be used to tune performance by reducing re-draw overhead, but causes some artifacts.\n") TEXT(" < 0 <=> infinite (default)"), ECVF_Scalability | ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarShadowsVirtualUseHZB( TEXT("r.Shadow.Virtual.UseHZB"), 2, TEXT("Enables HZB for (Nanite) Virtual Shadow Maps - Non-Nanite unfortunately has a separate flag with different semantics: r.Shadow.Virtual.NonNanite.UseHZB.\n") TEXT(" 0 - No HZB occlusion culling\n") TEXT(" 1 - Approximate Single-pass HZB occlusion culling (using previous frame HZB)\n") TEXT(" 2 - Two-pass occlusion culling (default)."), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarShadowsVirtualForceFullHZBUpdate( TEXT("r.Shadow.Virtual.ForceFullHZBUpdate"), 0, TEXT("Forces full HZB update every frame rather than just dirty pages.\n"), ECVF_RenderThreadSafe); FMatrix CalcTranslatedWorldToShadowUVMatrix( const FMatrix& TranslatedWorldToShadowView, const FMatrix& ViewToClip) { FMatrix TranslatedWorldToShadowClip = TranslatedWorldToShadowView * ViewToClip; FMatrix ScaleAndBiasToSmUV = FScaleMatrix(FVector(0.5f, -0.5f, 1.0f)) * FTranslationMatrix(FVector(0.5f, 0.5f, 0.0f)); FMatrix TranslatedWorldToShadowUv = TranslatedWorldToShadowClip * ScaleAndBiasToSmUV; return TranslatedWorldToShadowUv; } FMatrix CalcTranslatedWorldToShadowUVNormalMatrix( const FMatrix& TranslatedWorldToShadowView, const FMatrix& ViewToClip) { return CalcTranslatedWorldToShadowUVMatrix(TranslatedWorldToShadowView, ViewToClip).GetTransposed().Inverse(); } template static bool SetStatsArgsAndPermutation(FRDGBuilder& GraphBuilder, FRDGBufferRef StatsBufferRDG, typename ShaderType::FParameters *OutPassParameters, typename ShaderType::FPermutationDomain& OutPermutationVector) { bool bGenerateStats = StatsBufferRDG != nullptr; if (bGenerateStats) { OutPassParameters->OutStatsBuffer = GraphBuilder.CreateUAV(StatsBufferRDG); } ; OutPermutationVector.template Set(bGenerateStats); return bGenerateStats; } FVirtualShadowMapArray::FVirtualShadowMapArray() { } BEGIN_SHADER_PARAMETER_STRUCT(FCacheDataParameters, ) SHADER_PARAMETER_RDG_BUFFER_SRV( StructuredBuffer< FShadowMapCacheData >, ShadowMapCacheData ) SHADER_PARAMETER_RDG_BUFFER_SRV( StructuredBuffer< uint >, PrevPageFlags ) SHADER_PARAMETER_RDG_BUFFER_SRV( StructuredBuffer< uint >, PrevPageTable ) SHADER_PARAMETER_RDG_BUFFER_SRV( StructuredBuffer< FPhysicalPageMetaData >, PrevPhysicalPageMetaData) SHADER_PARAMETER_RDG_BUFFER_SRV( StructuredBuffer< uint >, PrevDynamicCasterPageFlags) SHADER_PARAMETER_RDG_BUFFER_SRV( StructuredBuffer< uint >, PrevProjectionData) END_SHADER_PARAMETER_STRUCT() static void SetCacheDataShaderParameters(FRDGBuilder& GraphBuilder, const TArray &ShadowMaps, FVirtualShadowMapArrayCacheManager* CacheManager, FCacheDataParameters &CacheDataParameters) { TArray ShadowMapCacheData; ShadowMapCacheData.AddDefaulted(ShadowMaps.Num()); for (int32 SmIndex = 0; SmIndex < ShadowMaps.Num(); ++SmIndex) { TSharedPtr VirtualShadowMapCacheEntry = ShadowMaps[SmIndex]->VirtualShadowMapCacheEntry; if (VirtualShadowMapCacheEntry != nullptr && VirtualShadowMapCacheEntry->IsValid()) { ShadowMapCacheData[SmIndex].PrevVirtualShadowMapId = VirtualShadowMapCacheEntry->PrevVirtualShadowMapId; } else { ShadowMapCacheData[SmIndex].PrevVirtualShadowMapId = INDEX_NONE; } } CacheDataParameters.ShadowMapCacheData = GraphBuilder.CreateSRV(CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.ShadowMapCacheData"), ShadowMapCacheData)); CacheDataParameters.PrevPageFlags = GraphBuilder.CreateSRV(GraphBuilder.RegisterExternalBuffer(CacheManager->PrevBuffers.PageFlags, TEXT("Shadow.Virtual.PrevPageFlags"))); CacheDataParameters.PrevPageTable = GraphBuilder.CreateSRV(GraphBuilder.RegisterExternalBuffer(CacheManager->PrevBuffers.PageTable, TEXT("Shadow.Virtual.PrevPageTable"))); CacheDataParameters.PrevPhysicalPageMetaData = GraphBuilder.CreateSRV(GraphBuilder.RegisterExternalBuffer(CacheManager->PrevBuffers.PhysicalPageMetaData, TEXT("Shadow.Virtual.PrevPhysicalPageMetaData"))); CacheDataParameters.PrevDynamicCasterPageFlags = GraphBuilder.CreateSRV(GraphBuilder.RegisterExternalBuffer(CacheManager->PrevBuffers.DynamicCasterPageFlags, TEXT("Shadow.Virtual.PrevDynamicCasterPageFlags"))); CacheDataParameters.PrevProjectionData = GraphBuilder.CreateSRV(GraphBuilder.RegisterExternalBuffer(CacheManager->PrevBuffers.ProjectionData, TEXT("Shadow.Virtual.PrevProjectionData"))); } static FRDGBufferRef CreateProjectionDataBuffer( FRDGBuilder& GraphBuilder, const TCHAR* Name, const TArray& InitialData) { uint64 DataSize = InitialData.Num() * InitialData.GetTypeSize(); FRDGBufferDesc Desc; Desc.Usage = EBufferUsageFlags::UnorderedAccess | EBufferUsageFlags::ShaderResource | EBufferUsageFlags::ByteAddressBuffer | EBufferUsageFlags::StructuredBuffer; Desc.BytesPerElement = 4; Desc.NumElements = DataSize / 4; FRDGBufferRef Buffer = GraphBuilder.CreateBuffer(Desc, Name); GraphBuilder.QueueBufferUpload(Buffer, InitialData.GetData(), DataSize); return Buffer; } void FVirtualShadowMapArray::Initialize(FRDGBuilder& GraphBuilder, FVirtualShadowMapArrayCacheManager* InCacheManager, bool bInEnabled) { bInitialized = true; bEnabled = bInEnabled; CacheManager = InCacheManager; bCullBackfacingPixels = CVarCullBackfacingPixels.GetValueOnRenderThread() != 0; bUseHzbOcclusion = CVarShadowsVirtualUseHZB.GetValueOnRenderThread() != 0; bUseTwoPassHzbOcclusion = CVarShadowsVirtualUseHZB.GetValueOnRenderThread() == 2; UniformParameters.NumShadowMaps = 0; UniformParameters.NumDirectionalLights = 0; UniformParameters.MaxPhysicalPages = 0; UniformParameters.StaticCachedArrayIndex = 0; // NOTE: Most uniform values don't matter when VSM is disabled // Reference dummy data in the UB initially const uint32 DummyPageTableElement = 0xFFFFFFFF; UniformParameters.PageTable = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(DummyPageTableElement), DummyPageTableElement)); UniformParameters.ProjectionData = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(FVirtualShadowMapProjectionShaderData))); UniformParameters.PageFlags = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(uint32))); UniformParameters.PageRectBounds = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(FIntVector4))); if (bEnabled) { // Fixed physical page pool width, we adjust the height to accomodate the requested maximum // NOTE: Row size in pages has to be POT since we use mask & shift in place of integer ops // NOTE: This assumes GetMax2DTextureDimension() is a power of two on supported platforms const uint32 PhysicalPagesX = FMath::DivideAndRoundDown(GetMax2DTextureDimension(), FVirtualShadowMap::PageSize); check(FMath::IsPowerOfTwo(PhysicalPagesX)); uint32 PhysicalPagesY = FMath::DivideAndRoundUp((uint32)FMath::Max(1, CVarMaxPhysicalPages.GetValueOnRenderThread()), PhysicalPagesX); UniformParameters.MaxPhysicalPages = PhysicalPagesX * PhysicalPagesY; if (CVarCacheStaticSeparate.GetValueOnRenderThread() != 0) { #if !DEBUG_ALLOW_STATIC_SEPARATE_WITHOUT_CACHING if (CacheManager->IsValid()) #endif { // Enable separate static caching in the second texture array element UniformParameters.StaticCachedArrayIndex = 1; } } uint32 PhysicalX = PhysicalPagesX * FVirtualShadowMap::PageSize; uint32 PhysicalY = PhysicalPagesY * FVirtualShadowMap::PageSize; // TODO: Some sort of better fallback with warning? // All supported platforms support at least 16384 texture dimensions which translates to 16384 max pages with default 128x128 page size check(PhysicalX <= GetMax2DTextureDimension()); check(PhysicalY <= GetMax2DTextureDimension()); UniformParameters.PhysicalPageRowMask = (PhysicalPagesX - 1); UniformParameters.PhysicalPageRowShift = FMath::FloorLog2( PhysicalPagesX ); UniformParameters.RecPhysicalPoolSize = FVector4f( 1.0f / PhysicalX, 1.0f / PhysicalY, 1.0f, 1.0f ); UniformParameters.PhysicalPoolSize = FIntPoint( PhysicalX, PhysicalY ); UniformParameters.PhysicalPoolSizePages = FIntPoint( PhysicalPagesX, PhysicalPagesY ); // TODO: Parameterize this in a useful way; potentially modify it automatically // when there are fewer lights in the scene and/or clustered shading settings differ. UniformParameters.PackedShadowMaskMaxLightCount = FMath::Min(CVarVirtualShadowOnePassProjectionMaxLights.GetValueOnRenderThread(), 32); // If enabled, ensure we have a properly-sized physical page pool // We can do this here since the pool is independent of the number of shadow maps const int PoolArraySize = ShouldCacheStaticSeparately() ? 2 : 1; TRefCountPtr PhysicalPagePool = CacheManager->SetPhysicalPoolSize(GraphBuilder, GetPhysicalPoolSize(), PoolArraySize); PhysicalPagePoolRDG = GraphBuilder.RegisterExternalTexture(PhysicalPagePool); UniformParameters.PhysicalPagePool = PhysicalPagePoolRDG; } else { CacheManager->FreePhysicalPool(); UniformParameters.PhysicalPagePool = GSystemTextures.GetZeroUIntArrayDummy(GraphBuilder); } if (bEnabled && bUseHzbOcclusion) { TRefCountPtr HzbPhysicalPagePool = CacheManager->SetHZBPhysicalPoolSize(GraphBuilder, GetHZBPhysicalPoolSize(), PF_R32_FLOAT); HZBPhysical = GraphBuilder.RegisterExternalTexture(HzbPhysicalPagePool); } else { CacheManager->FreeHZBPhysicalPool(); HZBPhysical = GSystemTextures.GetZeroUIntDummy(GraphBuilder); } } FVirtualShadowMapArray::~FVirtualShadowMapArray() { for (FVirtualShadowMap *SM : ShadowMaps) { SM->~FVirtualShadowMap(); } } EPixelFormat FVirtualShadowMapArray::GetPackedShadowMaskFormat() const { // TODO: Check if we're after any point that determines the format later too (light setup) check(bInitialized); // NOTE: Currently 4bpp/light if (UniformParameters.PackedShadowMaskMaxLightCount <= 8) { return PF_R32_UINT; } else if (UniformParameters.PackedShadowMaskMaxLightCount <= 16) { return PF_R32G32_UINT; } else { check(UniformParameters.PackedShadowMaskMaxLightCount <= 32); return PF_R32G32B32A32_UINT; } } FIntPoint FVirtualShadowMapArray::GetPhysicalPoolSize() const { check(bInitialized); return FIntPoint(UniformParameters.PhysicalPoolSize.X, UniformParameters.PhysicalPoolSize.Y); } FIntPoint FVirtualShadowMapArray::GetHZBPhysicalPoolSize() const { check(bInitialized); FIntPoint PhysicalPoolSize = GetPhysicalPoolSize(); FIntPoint HZBSize(FMath::Max(FPlatformMath::RoundUpToPowerOfTwo(PhysicalPoolSize.X) >> 1, 1u), FMath::Max(FPlatformMath::RoundUpToPowerOfTwo(PhysicalPoolSize.Y) >> 1, 1u)); return HZBSize; } uint32 FVirtualShadowMapArray::GetTotalAllocatedPhysicalPages() const { check(bInitialized); return ShouldCacheStaticSeparately() ? (2U * UniformParameters.MaxPhysicalPages) : UniformParameters.MaxPhysicalPages; } TRDGUniformBufferRef FVirtualShadowMapArray::GetUniformBuffer(FRDGBuilder& GraphBuilder) const { // NOTE: Need to allocate new parameter space since the UB changes over the frame as dummy references are replaced // TODO: Should we be caching this once all the relevant updates to parameters have been made in a frame? FVirtualShadowMapUniformParameters* VersionedParameters = GraphBuilder.AllocParameters(); *VersionedParameters = UniformParameters; return GraphBuilder.CreateUniformBuffer(VersionedParameters); } void FVirtualShadowMapArray::SetShaderDefines(FShaderCompilerEnvironment& OutEnvironment) { static_assert(FVirtualShadowMap::Log2Level0DimPagesXY * 2U + NANITE_MAX_VIEWS_PER_CULL_RASTERIZE_PASS_BITS <= 32U, "Page indirection plus view index must fit into 32-bits for page-routing storage!"); OutEnvironment.SetDefine(TEXT("ENABLE_NON_NANITE_VSM"), GEnableNonNaniteVSM); OutEnvironment.SetDefine(TEXT("VSM_PAGE_SIZE"), FVirtualShadowMap::PageSize); OutEnvironment.SetDefine(TEXT("VSM_PAGE_SIZE_MASK"), FVirtualShadowMap::PageSizeMask); OutEnvironment.SetDefine(TEXT("VSM_LOG2_PAGE_SIZE"), FVirtualShadowMap::Log2PageSize); OutEnvironment.SetDefine(TEXT("VSM_LEVEL0_DIM_PAGES_XY"), FVirtualShadowMap::Level0DimPagesXY); OutEnvironment.SetDefine(TEXT("VSM_LOG2_LEVEL0_DIM_PAGES_XY"), FVirtualShadowMap::Log2Level0DimPagesXY); OutEnvironment.SetDefine(TEXT("VSM_MAX_MIP_LEVELS"), FVirtualShadowMap::MaxMipLevels); OutEnvironment.SetDefine(TEXT("VSM_VIRTUAL_MAX_RESOLUTION_XY"), FVirtualShadowMap::VirtualMaxResolutionXY); OutEnvironment.SetDefine(TEXT("VSM_RASTER_WINDOW_PAGES"), FVirtualShadowMap::RasterWindowPages); OutEnvironment.SetDefine(TEXT("VSM_PAGE_TABLE_SIZE"), FVirtualShadowMap::PageTableSize); OutEnvironment.SetDefine(TEXT("VSM_NUM_STATS"), NumStats); OutEnvironment.SetDefine(TEXT("INDEX_NONE"), INDEX_NONE); } FVirtualShadowMapSamplingParameters FVirtualShadowMapArray::GetSamplingParameters(FRDGBuilder& GraphBuilder) const { // Sanity check: either VSMs are disabled and it's expected to be relying on dummy data, or we should have valid data // If this fires, it is likely because the caller is trying to sample VSMs before they have been rendered by the ShadowDepths pass // This should not crash, but it is not an intended production path as it will not return valid shadow data. // TODO: Disabled warning until SkyAtmosphereLUT is moved after ShadowDepths //ensureMsgf(!IsEnabled() || IsAllocated(), // TEXT("Attempt to use Virtual Shadow Maps before they have been rendered by ShadowDepths.")); FVirtualShadowMapSamplingParameters Parameters; Parameters.VirtualShadowMap = GetUniformBuffer(GraphBuilder); return Parameters; } class FVirtualPageManagementShader : public FGlobalShader { public: // Kernel launch group sizes static constexpr uint32 DefaultCSGroupXY = 8; static constexpr uint32 DefaultCSGroupX = 256; static constexpr uint32 GeneratePageFlagsGroupXYZ = 4; static constexpr uint32 BuildExplicitBoundsGroupXY = 16; FVirtualPageManagementShader() { } FVirtualPageManagementShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer) : FGlobalShader(Initializer) { } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) && DoesPlatformSupportNanite(Parameters.Platform); } /** * Can be overridden by FVertexFactory subclasses to modify their compile environment just before compilation occurs. */ static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); FVirtualShadowMapArray::SetShaderDefines(OutEnvironment); OutEnvironment.SetDefine(TEXT("VSM_DEFAULT_CS_GROUP_X"), DefaultCSGroupX); OutEnvironment.SetDefine(TEXT("VSM_DEFAULT_CS_GROUP_XY"), DefaultCSGroupXY); OutEnvironment.SetDefine(TEXT("VSM_GENERATE_PAGE_FLAGS_CS_GROUP_XYZ"), GeneratePageFlagsGroupXYZ); OutEnvironment.SetDefine(TEXT("VSM_BUILD_EXPLICIT_BOUNDS_CS_XY"), BuildExplicitBoundsGroupXY); FForwardLightingParameters::ModifyCompilationEnvironment(Parameters.Platform, OutEnvironment); OutEnvironment.SetDefine(TEXT("VF_SUPPORTS_PRIMITIVE_SCENE_DATA"), 1); } }; class FGeneratePageFlagsFromPixelsCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FGeneratePageFlagsFromPixelsCS); SHADER_USE_PARAMETER_STRUCT(FGeneratePageFlagsFromPixelsCS, FVirtualPageManagementShader) class FInputType : SHADER_PERMUTATION_INT("PERMUTATION_INPUT_TYPE", 3); using FPermutationDomain = TShaderPermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSceneTextureUniformParameters, SceneTexturesStruct) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FHairStrandsViewUniformParameters, HairStrands) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FForwardLightData, ForwardLightData) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, VisBuffer64) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, OutPageRequestFlags) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint >, DirectionalLightIds) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FStrataGlobalUniformParameters, Strata) RDG_BUFFER_ACCESS(IndirectBufferArgs, ERHIAccess::IndirectArgs) SHADER_PARAMETER(uint32, InputType) SHADER_PARAMETER(uint32, NumDirectionalLightSmInds) SHADER_PARAMETER(uint32, bPostBasePass) SHADER_PARAMETER(float, ResolutionLodBiasLocal) SHADER_PARAMETER(float, PageDilationBorderSizeDirectional) SHADER_PARAMETER(float, PageDilationBorderSizeLocal) SHADER_PARAMETER(uint32, bCullBackfacingPixels) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FGeneratePageFlagsFromPixelsCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "GeneratePageFlagsFromPixels", SF_Compute); class FMarkCoarsePagesCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FMarkCoarsePagesCS); SHADER_USE_PARAMETER_STRUCT(FMarkCoarsePagesCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, OutPageRequestFlags) SHADER_PARAMETER(uint32, bMarkCoarsePagesLocal) SHADER_PARAMETER(uint32, bIncludeNonNaniteGeometry) SHADER_PARAMETER(uint32, ClipmapIndexMask) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FMarkCoarsePagesCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "MarkCoarsePages", SF_Compute); class FGenerateHierarchicalPageFlagsCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FGenerateHierarchicalPageFlagsCS); SHADER_USE_PARAMETER_STRUCT(FGenerateHierarchicalPageFlagsCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, OutPageFlags) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, OutPageRectBounds) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FGenerateHierarchicalPageFlagsCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "GenerateHierarchicalPageFlags", SF_Compute); class FInitPhysicalPageMetaData : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FInitPhysicalPageMetaData); SHADER_USE_PARAMETER_STRUCT(FInitPhysicalPageMetaData, FVirtualPageManagementShader ) BEGIN_SHADER_PARAMETER_STRUCT( FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< FPhysicalPageMetaData >, OutPhysicalPageMetaData) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< uint >, OutFreePhysicalPages ) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FInitPhysicalPageMetaData, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "InitPhysicalPageMetaData", SF_Compute ); class FCreateCachedPageMappingsCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FCreateCachedPageMappingsCS); SHADER_USE_PARAMETER_STRUCT(FCreateCachedPageMappingsCS, FVirtualPageManagementShader) class FHasCacheDataDim : SHADER_PERMUTATION_BOOL("HAS_CACHE_DATA"); class FGenerateStatsDim : SHADER_PERMUTATION_BOOL("VSM_GENERATE_STATS"); using FPermutationDomain = TShaderPermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER( FVirtualShadowMapUniformParameters, VirtualShadowMap ) SHADER_PARAMETER_STRUCT_INCLUDE( FCacheDataParameters, CacheDataParameters ) SHADER_PARAMETER_RDG_BUFFER_SRV( StructuredBuffer< uint >, PageRequestFlags ) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< uint >, OutPageFlags) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< uint >, OutPageTable ) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< FPhysicalPageMetaData >, OutPhysicalPageMetaData ) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< uint >, OutStatsBuffer ) SHADER_PARAMETER( int32, bDynamicPageInvalidation ) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FCreateCachedPageMappingsCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "CreateCachedPageMappings", SF_Compute); class FPackFreePagesCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FPackFreePagesCS); SHADER_USE_PARAMETER_STRUCT(FPackFreePagesCS, FVirtualPageManagementShader ) BEGIN_SHADER_PARAMETER_STRUCT( FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_SRV( StructuredBuffer< FPhysicalPageMetaData >, PhysicalPageMetaData) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< uint >, OutFreePhysicalPages ) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FPackFreePagesCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "PackFreePages", SF_Compute ); class FAllocateNewPageMappingsCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FAllocateNewPageMappingsCS); SHADER_USE_PARAMETER_STRUCT(FAllocateNewPageMappingsCS, FVirtualPageManagementShader) class FGenerateStatsDim : SHADER_PERMUTATION_BOOL("VSM_GENERATE_STATS"); using FPermutationDomain = TShaderPermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER( FVirtualShadowMapUniformParameters, VirtualShadowMap ) SHADER_PARAMETER_RDG_BUFFER_SRV( StructuredBuffer< uint >, PageRequestFlags ) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< uint >, OutFreePhysicalPages ) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< uint >, OutPageFlags) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< uint >, OutPageTable ) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< FPhysicalPageMetaData >, OutPhysicalPageMetaData ) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< uint >, OutStatsBuffer ) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FAllocateNewPageMappingsCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "AllocateNewPageMappings", SF_Compute); class FPropagateMappedMipsCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FPropagateMappedMipsCS); SHADER_USE_PARAMETER_STRUCT(FPropagateMappedMipsCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER( FVirtualShadowMapUniformParameters, VirtualShadowMap ) SHADER_PARAMETER_RDG_BUFFER_UAV( RWStructuredBuffer< uint >, OutPageTable ) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FPropagateMappedMipsCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "PropagateMappedMips", SF_Compute); class FInitializePhysicalPagesCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FInitializePhysicalPagesCS); SHADER_USE_PARAMETER_STRUCT(FInitializePhysicalPagesCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, PhysicalPageMetaData) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutPhysicalPagePool) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FInitializePhysicalPagesCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "InitializePhysicalPages", SF_Compute); class FSelectPagesToInitializeCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FSelectPagesToInitializeCS); SHADER_USE_PARAMETER_STRUCT(FSelectPagesToInitializeCS, FVirtualPageManagementShader) class FGenerateStatsDim : SHADER_PERMUTATION_BOOL("VSM_GENERATE_STATS"); using FPermutationDomain = TShaderPermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, PhysicalPageMetaData) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, OutInitializePagesIndirectArgsBuffer) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer< uint >, OutPhysicalPagesToInitialize) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer< uint >, OutStatsBuffer) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FSelectPagesToInitializeCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "SelectPagesToInitializeCS", SF_Compute); class FInitializePhysicalPagesIndirectCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FInitializePhysicalPagesIndirectCS); SHADER_USE_PARAMETER_STRUCT(FInitializePhysicalPagesIndirectCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint >, PhysicalPagesToInitialize) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutPhysicalPagePool) RDG_BUFFER_ACCESS(IndirectArgs, ERHIAccess::IndirectArgs) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FInitializePhysicalPagesIndirectCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "InitializePhysicalPagesIndirectCS", SF_Compute); class FClearIndirectDispatchArgs1DCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FClearIndirectDispatchArgs1DCS); SHADER_USE_PARAMETER_STRUCT(FClearIndirectDispatchArgs1DCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(uint32, NumIndirectArgs) SHADER_PARAMETER(uint32, IndirectArgStride) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, OutIndirectArgsBuffer) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FClearIndirectDispatchArgs1DCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "ClearIndirectDispatchArgs1DCS", SF_Compute); static void AddClearIndirectDispatchArgs1DPass(FRDGBuilder& GraphBuilder, FRDGBufferRef IndirectArgsRDG, uint32 NumIndirectArgs = 1U, uint32 IndirectArgStride = 4U) { FClearIndirectDispatchArgs1DCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->NumIndirectArgs = NumIndirectArgs; PassParameters->IndirectArgStride = IndirectArgStride; PassParameters->OutIndirectArgsBuffer = GraphBuilder.CreateUAV(IndirectArgsRDG); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("ClearIndirectDispatchArgs"), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(NumIndirectArgs, 64) ); } class FMergeStaticPhysicalPagesCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FMergeStaticPhysicalPagesCS); SHADER_USE_PARAMETER_STRUCT(FMergeStaticPhysicalPagesCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, PhysicalPageMetaData) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutPhysicalPagePool) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FMergeStaticPhysicalPagesCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "MergeStaticPhysicalPages", SF_Compute); class FSelectPagesToMergeCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FSelectPagesToMergeCS); SHADER_USE_PARAMETER_STRUCT(FSelectPagesToMergeCS, FVirtualPageManagementShader) class FGenerateStatsDim : SHADER_PERMUTATION_BOOL("VSM_GENERATE_STATS"); using FPermutationDomain = TShaderPermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, PhysicalPageMetaData) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, OutMergePagesIndirectArgsBuffer) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer< uint >, OutPhysicalPagesToMerge) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer< uint >, OutStatsBuffer) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FSelectPagesToMergeCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "SelectPagesToMergeCS", SF_Compute); class FMergeStaticPhysicalPagesIndirectCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FMergeStaticPhysicalPagesIndirectCS); SHADER_USE_PARAMETER_STRUCT(FMergeStaticPhysicalPagesIndirectCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint >, PhysicalPagesToMerge) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutPhysicalPagePool) RDG_BUFFER_ACCESS(IndirectArgs, ERHIAccess::IndirectArgs) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FMergeStaticPhysicalPagesIndirectCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "MergeStaticPhysicalPagesIndirectCS", SF_Compute); void FVirtualShadowMapArray::MergeStaticPhysicalPages(FRDGBuilder& GraphBuilder) { check(IsEnabled()); if (ShadowMaps.Num() == 0 || !ShouldCacheStaticSeparately()) { return; } #if !UE_BUILD_SHIPPING if (CVarDebugSkipMergePhysical.GetValueOnRenderThread() != 0) { return; } #endif RDG_EVENT_SCOPE(GraphBuilder, "FVirtualShadowMapArray::MergeStaticPhysicalPages"); if (CVarMergePhysicalUsingIndirect.GetValueOnRenderThread() != 0) { FRDGBufferRef MergePagesIndirectArgsRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(3), TEXT("Shadow.Virtual.MergePagesIndirectArgs")); // Note: We use GetTotalAllocatedPhysicalPages() to size the buffer as the selection shader emits both static/dynamic pages separately when enabled. FRDGBufferRef PhysicalPagesToMergeRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(int32), GetTotalAllocatedPhysicalPages() + 1), TEXT("Shadow.Virtual.PhysicalPagesToMerge")); // 1. Initialize the indirect args buffer AddClearIndirectDispatchArgs1DPass(GraphBuilder, MergePagesIndirectArgsRDG); // 2. Filter the relevant physical pages and set up the indirect args { FSelectPagesToMergeCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->PhysicalPageMetaData = GraphBuilder.CreateSRV(PhysicalPageMetaDataRDG); PassParameters->OutMergePagesIndirectArgsBuffer = GraphBuilder.CreateUAV(MergePagesIndirectArgsRDG); PassParameters->OutPhysicalPagesToMerge = GraphBuilder.CreateUAV(PhysicalPagesToMergeRDG); FSelectPagesToMergeCS::FPermutationDomain PermutationVector; SetStatsArgsAndPermutation(GraphBuilder, StatsBufferRDG, PassParameters, PermutationVector); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("SelectPagesToMerge"), ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(GetMaxPhysicalPages(), FSelectPagesToMergeCS::DefaultCSGroupX), 1, 1) ); } // 3. Indirect dispatch to clear the selected pages { FMergeStaticPhysicalPagesIndirectCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->OutPhysicalPagePool = GraphBuilder.CreateUAV(PhysicalPagePoolRDG); PassParameters->IndirectArgs = MergePagesIndirectArgsRDG; PassParameters->PhysicalPagesToMerge = GraphBuilder.CreateSRV(PhysicalPagesToMergeRDG); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("MergeStaticPhysicalPagesIndirect"), ComputeShader, PassParameters, PassParameters->IndirectArgs, 0 ); } } else { FMergeStaticPhysicalPagesCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->PhysicalPageMetaData = GraphBuilder.CreateSRV(PhysicalPageMetaDataRDG); PassParameters->OutPhysicalPagePool = GraphBuilder.CreateUAV(PhysicalPagePoolRDG); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(); // Shader contains logic to deal with static cached pages if enabled // We only need to launch one per page, even if there are multiple cached pages per page FIntPoint PoolSize = GetPhysicalPoolSize(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("MergeStaticPhysicalPages"), ComputeShader, PassParameters, FIntVector( FMath::DivideAndRoundUp(PoolSize.X, 16), FMath::DivideAndRoundUp(PoolSize.Y, 16), 1) ); } } /** * Helper to get hold of / check for associated virtual shadow map */ FORCEINLINE FProjectedShadowInfo* GetVirtualShadowMapInfo(const FVisibleLightInfo &LightInfo) { for (FProjectedShadowInfo *ProjectedShadowInfo : LightInfo.AllProjectedShadows) { if (ProjectedShadowInfo->HasVirtualShadowMap()) { return ProjectedShadowInfo; } } return nullptr; } class FInitPageRectBoundsCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FInitPageRectBoundsCS); SHADER_USE_PARAMETER_STRUCT(FInitPageRectBoundsCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, OutPageRectBounds) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FInitPageRectBoundsCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "InitPageRectBounds", SF_Compute); class FVirtualSmFeedbackStatusCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FVirtualSmFeedbackStatusCS); SHADER_USE_PARAMETER_STRUCT(FVirtualSmFeedbackStatusCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint >, FreePhysicalPages) SHADER_PARAMETER_STRUCT_INCLUDE(GPUMessage::FParameters, GPUMessageParams) SHADER_PARAMETER(uint32, StatusMessageId) END_SHADER_PARAMETER_STRUCT() static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FVirtualPageManagementShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); } }; IMPLEMENT_GLOBAL_SHADER(FVirtualSmFeedbackStatusCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "FeedbackStatusCS", SF_Compute); void FVirtualShadowMapVisualizeLightSearch::CheckLight(const FLightSceneProxy* CheckProxy, int CheckVirtualShadowMapId) { #if !UE_BUILD_SHIPPING FString CheckLightName = CheckProxy->GetOwnerNameOrLabel(); if (GDumpVSMLightNames) { UE_LOG(LogRenderer, Display, TEXT("%s"), *CheckLightName); } const ULightComponent* Component = CheckProxy->GetLightComponent(); check(Component); // Fill out new sort key and compare to our best found so far SortKey CheckKey; CheckKey.Packed = 0; CheckKey.Fields.bExactNameMatch = (CheckLightName == GVirtualShadowMapVisualizeLightName); CheckKey.Fields.bPartialNameMatch = CheckKey.Fields.bExactNameMatch || CheckLightName.Contains(GVirtualShadowMapVisualizeLightName); CheckKey.Fields.bSelected = Component->IsSelected(); CheckKey.Fields.bOwnerSelected = Component->IsOwnerSelected(); CheckKey.Fields.bDirectionalLight = CheckProxy->GetLightType() == LightType_Directional; CheckKey.Fields.bExists = 1; if (CheckKey.Packed > FoundKey.Packed) //-V547 { FoundKey = CheckKey; FoundProxy = CheckProxy; FoundVirtualShadowMapId = CheckVirtualShadowMapId; } #endif } static FRDGTextureRef CreateDebugVisualizationTexture(FRDGBuilder& GraphBuilder, FIntPoint Extent) { const FLinearColor ClearColor(0.0f, 0.0f, 0.0f, 0.0f); FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( Extent, PF_R8G8B8A8, FClearValueBinding(ClearColor), TexCreate_ShaderResource | TexCreate_UAV); FRDGTextureRef Texture = GraphBuilder.CreateTexture(Desc, TEXT("Shadow.Virtual.DebugProjection")); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(Texture), ClearColor); return Texture; } static uint32 GetShadowMapsToAllocate(uint32 NumShadowMaps) { // Round up to powers of two to be friendlier to the buffer pool return FMath::RoundUpToPowerOfTwo(FMath::Max(64U, NumShadowMaps)); } void FVirtualShadowMapArray::BuildPageAllocations( FRDGBuilder& GraphBuilder, const FMinimalSceneTextures& SceneTextures, const TArrayView& Views, const FEngineShowFlags& EngineShowFlags, const FSortedLightSetSceneInfo& SortedLightsInfo, const TArray& VisibleLightInfos, const TArray>& NaniteRasterResults, FScene& Scene) { check(IsEnabled()); if (ShadowMaps.Num() == 0 || Views.Num() == 0) { // Nothing to do return; } RDG_EVENT_SCOPE(GraphBuilder, "FVirtualShadowMapArray::BuildPageAllocation"); bool bDebugOutputEnabled = false; VisualizeLight.Reset(); #if !UE_BUILD_SHIPPING if (GDumpVSMLightNames) { bDebugOutputEnabled = true; UE_LOG(LogRenderer, Display, TEXT("Lights with Virtual Shadow Maps:")); } // Setup debug visualization/output if enabled { FVirtualShadowMapVisualizationData& VisualizationData = GetVirtualShadowMapVisualizationData(); for (const FViewInfo& View : Views) { const FName& VisualizationMode = View.CurrentVirtualShadowMapVisualizationMode; // for stereo views that aren't multi-view, don't account for the left FIntPoint Extent = View.ViewRect.Max - View.ViewRect.Min; if (VisualizationData.Update(VisualizationMode)) { // TODO - automatically enable the show flag when set from command line? //EngineShowFlags.SetVisualizeVirtualShadowMap(true); } if (VisualizationData.IsActive() && EngineShowFlags.VisualizeVirtualShadowMap) { bDebugOutputEnabled = true; DebugVisualizationOutput.Add(CreateDebugVisualizationTexture(GraphBuilder, Extent)); } } } #endif //!UE_BUILD_SHIPPING // Store shadow map projection data for each virtual shadow map TArray ProjectionData; ProjectionData.AddDefaulted(ShadowMaps.Num()); // Gather directional light virtual shadow maps TArray DirectionalLightIds; for (const FVisibleLightInfo& VisibleLightInfo : VisibleLightInfos) { for (const TSharedPtr& Clipmap : VisibleLightInfo.VirtualShadowMapClipmaps) { // NOTE: Shader assumes all levels from a given clipmap are contiguous int32 ClipmapID = Clipmap->GetVirtualShadowMap()->ID; DirectionalLightIds.Add(ClipmapID); for (int32 ClipmapLevel = 0; ClipmapLevel < Clipmap->GetLevelCount(); ++ClipmapLevel) { ProjectionData[ClipmapID + ClipmapLevel] = Clipmap->GetProjectionShaderData(ClipmapLevel); } if (bDebugOutputEnabled) { VisualizeLight.CheckLight(Clipmap->GetLightSceneInfo().Proxy, ClipmapID); } } for (FProjectedShadowInfo* ProjectedShadowInfo : VisibleLightInfo.AllProjectedShadows) { if (ProjectedShadowInfo->HasVirtualShadowMap()) { check(ProjectedShadowInfo->CascadeSettings.ShadowSplitIndex == INDEX_NONE); // We use clipmaps for virtual shadow maps, not cascades // NOTE: Virtual shadow maps are never atlased, but verify our assumptions { const FVector4f ClipToShadowUV = ProjectedShadowInfo->GetClipToShadowBufferUvScaleBias(); check(ProjectedShadowInfo->BorderSize == 0); check(ProjectedShadowInfo->X == 0); check(ProjectedShadowInfo->Y == 0); const FIntRect ShadowViewRect = ProjectedShadowInfo->GetInnerViewRect(); check(ShadowViewRect.Min.X == 0); check(ShadowViewRect.Min.Y == 0); check(ShadowViewRect.Max.X == FVirtualShadowMap::VirtualMaxResolutionXY); check(ShadowViewRect.Max.Y == FVirtualShadowMap::VirtualMaxResolutionXY); } int32 NumMaps = ProjectedShadowInfo->bOnePassPointLightShadow ? 6 : 1; for( int32 i = 0; i < NumMaps; i++ ) { int32 ID = ProjectedShadowInfo->VirtualShadowMaps[i]->ID; const FViewMatrices ViewMatrices = ProjectedShadowInfo->GetShadowDepthRenderingViewMatrices( i, true ); const FLargeWorldRenderPosition PreViewTranslation(ProjectedShadowInfo->PreShadowTranslation); FVirtualShadowMapProjectionShaderData& Data = ProjectionData[ ID ]; Data.TranslatedWorldToShadowViewMatrix = FMatrix44f(ViewMatrices.GetTranslatedViewMatrix()); // LWC_TODO: Precision loss? Data.ShadowViewToClipMatrix = FMatrix44f(ViewMatrices.GetProjectionMatrix()); Data.TranslatedWorldToShadowUVMatrix = FMatrix44f(CalcTranslatedWorldToShadowUVMatrix( ViewMatrices.GetTranslatedViewMatrix(), ViewMatrices.GetProjectionMatrix() )); Data.TranslatedWorldToShadowUVNormalMatrix = FMatrix44f(CalcTranslatedWorldToShadowUVNormalMatrix( ViewMatrices.GetTranslatedViewMatrix(), ViewMatrices.GetProjectionMatrix() )); Data.PreViewTranslationLWCTile = PreViewTranslation.GetTile(); Data.PreViewTranslationLWCOffset = PreViewTranslation.GetOffset(); Data.LightType = ProjectedShadowInfo->GetLightSceneInfo().Proxy->GetLightType(); Data.LightSourceRadius = ProjectedShadowInfo->GetLightSceneInfo().Proxy->GetSourceRadius(); } if (bDebugOutputEnabled) { VisualizeLight.CheckLight(ProjectedShadowInfo->GetLightSceneInfo().Proxy, ProjectedShadowInfo->VirtualShadowMaps[0]->ID); } } } } UniformParameters.NumShadowMaps = ShadowMaps.Num(); UniformParameters.NumDirectionalLights = DirectionalLightIds.Num(); ProjectionDataRDG = CreateProjectionDataBuffer(GraphBuilder, TEXT("Shadow.Virtual.ProjectionData"), ProjectionData); UniformParameters.ProjectionData = GraphBuilder.CreateSRV(ProjectionDataRDG); if (CVarShowStats.GetValueOnRenderThread() || CacheManager->IsAccumulatingStats()) { StatsBufferRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), NumStats), TEXT("Shadow.Virtual.StatsBuffer")); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(StatsBufferRDG), 0); } // We potentially over-allocate these to avoid too many different allocation sizes each frame const uint32 NumShadowMapsToAllocate = GetShadowMapsToAllocate(ShadowMaps.Num()); const uint32 NumPageFlagsToAllocate = NumShadowMapsToAllocate * FVirtualShadowMap::PageTableSize; // Create and clear the requested page flags FRDGBufferRef PageRequestFlagsRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), NumPageFlagsToAllocate), TEXT("Shadow.Virtual.PageRequestFlags")); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(PageRequestFlagsRDG), 0); DynamicCasterPageFlagsRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), NumPageFlagsToAllocate), TEXT("Shadow.Virtual.DynamicCasterPageFlags")); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(DynamicCasterPageFlagsRDG), 0); DirtyPageFlagsRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), NumPageFlagsToAllocate), TEXT("Shadow.Virtual.DirtyPageFlags")); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(DirtyPageFlagsRDG), 0); // Record the number of instances the buffer has capactiy for, should anything change (it shouldn't!) NumInvalidatingInstanceSlots = Scene.GPUScene.GetNumInstances(); // Allocate space for counter, worst case ID storage, and flags. int32 InstanceInvalidationBufferSize = 1 + NumInvalidatingInstanceSlots + FMath::DivideAndRoundUp(NumInvalidatingInstanceSlots, 32); InvalidatingInstancesRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), InstanceInvalidationBufferSize), TEXT("Shadow.Virtual.InvalidatingInstances")); // Clear to zero, technically only need to clear first Scene.GPUScene.GetNumInstances() + 1 uints AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(InvalidatingInstancesRDG), 0); const uint32 NumPageRects = ShadowMaps.Num() * FVirtualShadowMap::MaxMipLevels; const uint32 NumPageRectsToAllocate = NumShadowMapsToAllocate * FVirtualShadowMap::MaxMipLevels; PageRectBoundsRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(FIntVector4), NumPageRectsToAllocate), TEXT("Shadow.Virtual.PageRectBounds")); { FInitPageRectBoundsCS::FParameters* PassParameters = GraphBuilder.AllocParameters< FInitPageRectBoundsCS::FParameters >(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->OutPageRectBounds = GraphBuilder.CreateUAV(PageRectBoundsRDG); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(); ClearUnusedGraphResources(ComputeShader, PassParameters); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("InitPageRectBounds"), ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(NumPageRects, FInitPageRectBoundsCS::DefaultCSGroupX), 1, 1) ); } for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex) { RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex); const FViewInfo &View = Views[ViewIndex]; // This view contained no local lights (that were stored in the light grid), and no directional lights, so nothing to do. if (View.ForwardLightingResources.LocalLightVisibleLightInfosIndex.Num() + DirectionalLightIds.Num() == 0) { continue; } FRDGBufferRef DirectionalLightIdsRDG = CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.DirectionalLightIds"), DirectionalLightIds); const FRDGSystemTextures& SystemTextures = FRDGSystemTextures::Get(GraphBuilder); FRDGBufferRef ScreenSpaceGridBoundsRDG = nullptr; { // It's safe to overlap these passes that all write to page request flags FRDGBufferUAVRef PageRequestFlagsUAV = GraphBuilder.CreateUAV(PageRequestFlagsRDG, ERDGUnorderedAccessViewFlags::SkipBarrier); // Mark pages based on projected depth buffer pixels if (CVarMarkPixelPages.GetValueOnRenderThread() != 0) { auto GeneratePageFlags = [&](const EVirtualShadowMapProjectionInputType InputType) { FGeneratePageFlagsFromPixelsCS::FPermutationDomain PermutationVector; PermutationVector.Set(InputType == EVirtualShadowMapProjectionInputType::HairStrands ? 1u : 0u); FGeneratePageFlagsFromPixelsCS::FParameters* PassParameters = GraphBuilder.AllocParameters< FGeneratePageFlagsFromPixelsCS::FParameters >(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->SceneTexturesStruct = SceneTextures.UniformBuffer; PassParameters->HairStrands = HairStrands::BindHairStrandsViewUniformParameters(View); PassParameters->View = View.ViewUniformBuffer; PassParameters->OutPageRequestFlags = PageRequestFlagsUAV; PassParameters->ForwardLightData = View.ForwardLightingResources.ForwardLightUniformBuffer; PassParameters->DirectionalLightIds = GraphBuilder.CreateSRV(DirectionalLightIdsRDG); PassParameters->ResolutionLodBiasLocal = CVarResolutionLodBiasLocal.GetValueOnRenderThread(); PassParameters->PageDilationBorderSizeLocal = CVarPageDilationBorderSizeLocal.GetValueOnRenderThread(); PassParameters->PageDilationBorderSizeDirectional = CVarPageDilationBorderSizeDirectional.GetValueOnRenderThread(); PassParameters->bCullBackfacingPixels = ShouldCullBackfacingPixels() ? 1 : 0; PassParameters->Strata = Strata::BindStrataGlobalUniformParameters(View); auto ComputeShader = View.ShaderMap->GetShader(PermutationVector); static_assert((FVirtualPageManagementShader::DefaultCSGroupXY % 2) == 0, "GeneratePageFlagsFromPixels requires even-sized CS groups for quad swizzling."); const FIntPoint GridSize = FIntPoint::DivideAndRoundUp(View.ViewRect.Size(), FVirtualPageManagementShader::DefaultCSGroupXY); if (InputType == EVirtualShadowMapProjectionInputType::HairStrands && View.HairStrandsViewData.VisibilityData.TileData.IsValid()) { PassParameters->IndirectBufferArgs = View.HairStrandsViewData.VisibilityData.TileData.TileIndirectDispatchBuffer; FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("GeneratePageFlagsFromPixels(HairStrands,Tile)"), ComputeShader, PassParameters, View.HairStrandsViewData.VisibilityData.TileData.TileIndirectDispatchBuffer, View.HairStrandsViewData.VisibilityData.TileData.GetIndirectDispatchArgOffset(FHairStrandsTiles::ETileType::HairAll)); } else { FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("GeneratePageFlagsFromPixels(%s,NumShadowMaps=%d)", ToString(InputType), ShadowMaps.Num()), ComputeShader, PassParameters, FIntVector(GridSize.X, GridSize.Y, 1)); } }; GeneratePageFlags(EVirtualShadowMapProjectionInputType::GBuffer); if (HairStrands::HasViewHairStrandsData(View)) { GeneratePageFlags(EVirtualShadowMapProjectionInputType::HairStrands); } } // Mark coarse pages bool bMarkCoarsePagesDirectional = CVarMarkCoarsePagesDirectional.GetValueOnRenderThread() != 0; bool bMarkCoarsePagesLocal = CVarMarkCoarsePagesLocal.GetValueOnRenderThread() != 0; if (bMarkCoarsePagesDirectional || bMarkCoarsePagesLocal) { FMarkCoarsePagesCS::FParameters* PassParameters = GraphBuilder.AllocParameters< FMarkCoarsePagesCS::FParameters >(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->OutPageRequestFlags = PageRequestFlagsUAV; PassParameters->bMarkCoarsePagesLocal = bMarkCoarsePagesLocal ? 1 : 0; PassParameters->ClipmapIndexMask = bMarkCoarsePagesDirectional ? FVirtualShadowMapClipmap::GetCoarsePageClipmapIndexMask() : 0; PassParameters->bIncludeNonNaniteGeometry = CVarCoarsePagesIncludeNonNanite.GetValueOnRenderThread(); auto ComputeShader = View.ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("MarkCoarsePages"), ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(uint32(ShadowMaps.Num()), FMarkCoarsePagesCS::DefaultCSGroupX), 1, 1) ); } } } PageTableRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), NumPageFlagsToAllocate), TEXT("Shadow.Virtual.PageTable")); // Note: these are passed to the rendering and are not identical to the PageRequest flags coming in from GeneratePageFlagsFromPixels PageFlagsRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), NumPageFlagsToAllocate), TEXT("Shadow.Virtual.PageFlags")); // One additional element as the last element is used as an atomic counter FRDGBufferRef FreePhysicalPagesRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(int32), GetMaxPhysicalPages() + 1), TEXT("Shadow.Virtual.FreePhysicalPages")); // Enough space for all physical pages that might be allocated PhysicalPageMetaDataRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(FPhysicalPageMetaData), GetMaxPhysicalPages()), TEXT("Shadow.Virtual.PhysicalPageMetaData")); AllocatedPageRectBoundsRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(FIntVector4), NumPageRects), TEXT("Shadow.Virtual.AllocatedPageRectBounds")); { FInitPhysicalPageMetaData::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->OutPhysicalPageMetaData = GraphBuilder.CreateUAV(PhysicalPageMetaDataRDG); PassParameters->OutFreePhysicalPages = GraphBuilder.CreateUAV(FreePhysicalPagesRDG); auto ComputeShader = Views[0].ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("InitPhysicalPageMetaData"), ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(GetMaxPhysicalPages(), FInitPhysicalPageMetaData::DefaultCSGroupX), 1, 1) ); } // Start by marking any physical pages that we are going to keep due to caching // NOTE: We run this pass even with no caching since we still need to initialize the metadata { FCreateCachedPageMappingsCS::FParameters* PassParameters = GraphBuilder.AllocParameters< FCreateCachedPageMappingsCS::FParameters >(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->PageRequestFlags = GraphBuilder.CreateSRV(PageRequestFlagsRDG); PassParameters->OutPageTable = GraphBuilder.CreateUAV(PageTableRDG); PassParameters->OutPhysicalPageMetaData = GraphBuilder.CreateUAV(PhysicalPageMetaDataRDG); PassParameters->OutPageFlags = GraphBuilder.CreateUAV(PageFlagsRDG); PassParameters->bDynamicPageInvalidation = 1; #if !UE_BUILD_SHIPPING PassParameters->bDynamicPageInvalidation = CVarDebugSkipDynamicPageInvalidation.GetValueOnRenderThread() == 0 ? 1 : 0; #endif bool bCacheEnabled = CacheManager->IsValid(); if (bCacheEnabled) { SetCacheDataShaderParameters(GraphBuilder, ShadowMaps, CacheManager, PassParameters->CacheDataParameters); } FCreateCachedPageMappingsCS::FPermutationDomain PermutationVector; SetStatsArgsAndPermutation(GraphBuilder, StatsBufferRDG, PassParameters, PermutationVector); PermutationVector.Set(bCacheEnabled); auto ComputeShader = Views[0].ShaderMap->GetShader(PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("CreateCachedPageMappings"), ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(FVirtualShadowMap::PageTableSize, FCreateCachedPageMappingsCS::DefaultCSGroupX), ShadowMaps.Num(), 1) ); } // After we've marked any cached pages, collect all the remaining free pages into a list // NOTE: We could optimize this more in the case where there's no caching of course; TBD priority { FPackFreePagesCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->PhysicalPageMetaData = GraphBuilder.CreateSRV(PhysicalPageMetaDataRDG); PassParameters->OutFreePhysicalPages = GraphBuilder.CreateUAV(FreePhysicalPagesRDG); auto ComputeShader = Views[0].ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("PackFreePages"), ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(GetMaxPhysicalPages(), FPackFreePagesCS::DefaultCSGroupX), 1, 1) ); } // Allocate any new physical pages that were not cached from the free list { FAllocateNewPageMappingsCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->PageRequestFlags = GraphBuilder.CreateSRV(PageRequestFlagsRDG); PassParameters->OutPageTable = GraphBuilder.CreateUAV(PageTableRDG); PassParameters->OutPageFlags = GraphBuilder.CreateUAV(PageFlagsRDG); PassParameters->OutFreePhysicalPages = GraphBuilder.CreateUAV(FreePhysicalPagesRDG); PassParameters->OutPhysicalPageMetaData = GraphBuilder.CreateUAV(PhysicalPageMetaDataRDG); FAllocateNewPageMappingsCS::FPermutationDomain PermutationVector; SetStatsArgsAndPermutation(GraphBuilder, StatsBufferRDG, PassParameters, PermutationVector); auto ComputeShader = Views[0].ShaderMap->GetShader(PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("AllocateNewPageMappings"), ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(FVirtualShadowMap::PageTableSize, FAllocateNewPageMappingsCS::DefaultCSGroupX), ShadowMaps.Num(), 1) ); } { // Run pass building hierarchical page flags to make culling acceptable performance. FGenerateHierarchicalPageFlagsCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->OutPageFlags = GraphBuilder.CreateUAV(PageFlagsRDG); PassParameters->OutPageRectBounds = GraphBuilder.CreateUAV(PageRectBoundsRDG); auto ComputeShader = Views[0].ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("GenerateHierarchicalPageFlags"), ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(FVirtualShadowMap::PageTableSize, FGenerateHierarchicalPageFlagsCS::DefaultCSGroupX), ShadowMaps.Num(), 1) ); } // NOTE: We could skip this (in shader) for shadow maps that only have 1 mip (ex. clipmaps) { // Propagate mapped mips down the hierarchy to allow O(1) lookup of coarser mapped pages FPropagateMappedMipsCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->OutPageTable = GraphBuilder.CreateUAV(PageTableRDG); auto ComputeShader = Views[0].ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("PropagateMappedMips"), ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(FMath::Square(FVirtualShadowMap::Level0DimPagesXY), FPropagateMappedMipsCS::DefaultCSGroupX), ShadowMaps.Num(), 1) ); } // Initialize the physical page pool check(PhysicalPagePoolRDG != nullptr); { RDG_EVENT_SCOPE( GraphBuilder, "InitializePhysicalPages" ); if (CVarInitializePhysicalUsingIndirect.GetValueOnRenderThread() != 0) { FRDGBufferRef InitializePagesIndirectArgsRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(3), TEXT("Shadow.Virtual.InitializePagesIndirectArgs")); // Note: We use GetTotalAllocatedPhysicalPages() to size the buffer as the selection shader emits both static/dynamic pages separately when enabled. FRDGBufferRef PhysicalPagesToInitializeRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(int32), GetTotalAllocatedPhysicalPages() + 1), TEXT("Shadow.Virtual.PhysicalPagesToInitialize")); // 1. Initialize the indirect args buffer AddClearIndirectDispatchArgs1DPass(GraphBuilder, InitializePagesIndirectArgsRDG); // 2. Filter the relevant physical pages and set up the indirect args { FSelectPagesToInitializeCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->PhysicalPageMetaData = GraphBuilder.CreateSRV(PhysicalPageMetaDataRDG); PassParameters->OutInitializePagesIndirectArgsBuffer = GraphBuilder.CreateUAV(InitializePagesIndirectArgsRDG); PassParameters->OutPhysicalPagesToInitialize = GraphBuilder.CreateUAV(PhysicalPagesToInitializeRDG); bool bGenerateStats = StatsBufferRDG != nullptr; if (bGenerateStats) { PassParameters->OutStatsBuffer = GraphBuilder.CreateUAV(StatsBufferRDG); } FSelectPagesToInitializeCS::FPermutationDomain PermutationVector; PermutationVector.Set(bGenerateStats); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("SelectPagesToInitialize"), ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(GetMaxPhysicalPages(), FSelectPagesToInitializeCS::DefaultCSGroupX), 1, 1) ); } // 3. Indirect dispatch to clear the selected pages { FInitializePhysicalPagesIndirectCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->OutPhysicalPagePool = GraphBuilder.CreateUAV(PhysicalPagePoolRDG); PassParameters->IndirectArgs = InitializePagesIndirectArgsRDG; PassParameters->PhysicalPagesToInitialize = GraphBuilder.CreateSRV(PhysicalPagesToInitializeRDG); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("InitializePhysicalMemoryIndirect"), ComputeShader, PassParameters, PassParameters->IndirectArgs, 0 ); } } else { FInitializePhysicalPagesCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->PhysicalPageMetaData = GraphBuilder.CreateSRV(PhysicalPageMetaDataRDG); PassParameters->OutPhysicalPagePool = GraphBuilder.CreateUAV(PhysicalPagePoolRDG); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(); // Shader contains logic to deal with static cached pages if enabled // We only need to launch one per page, even if there are multiple cached pages per page FIntPoint PoolSize = GetPhysicalPoolSize(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("InitializePhysicalPages"), ComputeShader, PassParameters, FIntVector( FMath::DivideAndRoundUp(PoolSize.X, 16), FMath::DivideAndRoundUp(PoolSize.Y, 16), 1) ); } } UniformParameters.PageTable = GraphBuilder.CreateSRV(PageTableRDG); UniformParameters.PageFlags = GraphBuilder.CreateSRV(PageFlagsRDG); UniformParameters.PageRectBounds = GraphBuilder.CreateSRV(PageRectBoundsRDG); // Add pass to pipe back important stats { FVirtualSmFeedbackStatusCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->FreePhysicalPages = GraphBuilder.CreateSRV(FreePhysicalPagesRDG); PassParameters->GPUMessageParams = GPUMessage::GetShaderParameters(GraphBuilder); PassParameters->StatusMessageId = CacheManager->StatusFeedbackSocket.GetMessageId().GetIndex(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("Feedback Status"), ComputeShader, PassParameters, FIntVector(1, 1, 1) ); } #if !UE_BUILD_SHIPPING // Only dump one frame of light data GDumpVSMLightNames = false; #endif } class FDebugVisualizeVirtualSmCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FDebugVisualizeVirtualSmCS); SHADER_USE_PARAMETER_STRUCT(FDebugVisualizeVirtualSmCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FVirtualShadowMapSamplingParameters, ProjectionParameters) SHADER_PARAMETER(uint32, DebugTargetWidth) SHADER_PARAMETER(uint32, DebugTargetHeight) SHADER_PARAMETER(uint32, BorderWidth) SHADER_PARAMETER(uint32, VisualizeModeId) SHADER_PARAMETER(int32, VirtualShadowMapId) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutVisualize) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FDebugVisualizeVirtualSmCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapDebug.usf", "DebugVisualizeVirtualSmCS", SF_Compute); void FVirtualShadowMapArray::RenderDebugInfo(FRDGBuilder& GraphBuilder, TArrayView Views) { check(IsEnabled()); if (DebugVisualizationOutput.IsEmpty() || !VisualizeLight.IsValid()) { return; } const FVirtualShadowMapVisualizationData& VisualizationData = GetVirtualShadowMapVisualizationData(); if (VisualizationData.GetActiveModeID() != VIRTUAL_SHADOW_MAP_VISUALIZE_CLIPMAP_VIRTUAL_SPACE) { return; } int32 BorderWidth = 2; for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { FViewInfo& View = Views[ViewIndex]; FIntPoint DebugTargetExtent = DebugVisualizationOutput[ViewIndex]->Desc.Extent; FDebugVisualizeVirtualSmCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->ProjectionParameters = GetSamplingParameters(GraphBuilder); PassParameters->DebugTargetWidth = DebugTargetExtent.X; PassParameters->DebugTargetHeight = DebugTargetExtent.Y; PassParameters->BorderWidth = BorderWidth; PassParameters->VisualizeModeId = VisualizationData.GetActiveModeID(); PassParameters->VirtualShadowMapId = VisualizeLight.GetVirtualShadowMapId(); PassParameters->OutVisualize = GraphBuilder.CreateUAV(DebugVisualizationOutput[ViewIndex]); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("DebugVisualizeVirtualShadowMap"), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(DebugTargetExtent, FVirtualPageManagementShader::DefaultCSGroupXY) ); } } class FVirtualSmPrintStatsCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FVirtualSmPrintStatsCS); SHADER_USE_PARAMETER_STRUCT(FVirtualSmPrintStatsCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_STRUCT_INCLUDE( ShaderPrint::FShaderParameters, ShaderPrintStruct ) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint >, InStatsBuffer) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, AllocatedPageRectBounds) SHADER_PARAMETER(int, ShowStatsValue) END_SHADER_PARAMETER_STRUCT() static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FVirtualPageManagementShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); // Disable optimizations as shader print causes long compile times OutEnvironment.CompilerFlags.Add(CFLAG_SkipOptimizations); } }; IMPLEMENT_GLOBAL_SHADER(FVirtualSmPrintStatsCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPrintStats.usf", "PrintStats", SF_Compute); void FVirtualShadowMapArray::PrintStats(FRDGBuilder& GraphBuilder, const FViewInfo& View) { check(IsEnabled()); LLM_SCOPE_BYTAG(Nanite); // Print stats int ShowStatsValue = CVarShowStats.GetValueOnRenderThread(); if (ShowStatsValue != 0 && StatsBufferRDG) { { FVirtualSmPrintStatsCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); ShaderPrint::SetParameters(GraphBuilder, View, PassParameters->ShaderPrintStruct); PassParameters->InStatsBuffer = GraphBuilder.CreateSRV(StatsBufferRDG); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->ShowStatsValue = ShowStatsValue; auto ComputeShader = View.ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("Print Stats"), ComputeShader, PassParameters, FIntVector(1, 1, 1) ); } } } void FVirtualShadowMapArray::CreateMipViews( TArray& Views ) const { // strategy: // 1. Use the cull pass to generate copies of every node for every view needed. // [2. Fabricate a HZB array?] ensure(Views.Num() <= ShadowMaps.Num()); const int32 NumPrimaryViews = Views.Num(); // 1. create derivative views for each of the Mip levels, Views.AddDefaulted( NumPrimaryViews * ( FVirtualShadowMap::MaxMipLevels - 1) ); int32 MaxMips = 0; for (int32 ViewIndex = 0; ViewIndex < NumPrimaryViews; ++ViewIndex) { const Nanite::FPackedView& PrimaryView = Views[ViewIndex]; ensure( PrimaryView.TargetLayerIdX_AndMipLevelY_AndNumMipLevelsZ.X >= 0 && PrimaryView.TargetLayerIdX_AndMipLevelY_AndNumMipLevelsZ.X < ShadowMaps.Num() ); ensure( PrimaryView.TargetLayerIdX_AndMipLevelY_AndNumMipLevelsZ.Y == 0 ); ensure( PrimaryView.TargetLayerIdX_AndMipLevelY_AndNumMipLevelsZ.Z > 0 && PrimaryView.TargetLayerIdX_AndMipLevelY_AndNumMipLevelsZ.Z <= FVirtualShadowMap::MaxMipLevels ); const int32 NumMips = PrimaryView.TargetLayerIdX_AndMipLevelY_AndNumMipLevelsZ.Z; MaxMips = FMath::Max(MaxMips, NumMips); for (int32 MipLevel = 0; MipLevel < NumMips; ++MipLevel) { Nanite::FPackedView& MipView = Views[ MipLevel * NumPrimaryViews + ViewIndex ]; // Primary (Non-Mip views) first followed by derived mip views. if( MipLevel > 0 ) { MipView = PrimaryView; // Slightly messy, but extract any scale factor that was applied to the LOD scale for re-application below MipView.UpdateLODScales(); float LODScaleFactor = PrimaryView.LODScales.X / MipView.LODScales.X; MipView.TargetLayerIdX_AndMipLevelY_AndNumMipLevelsZ.Y = MipLevel; MipView.TargetLayerIdX_AndMipLevelY_AndNumMipLevelsZ.Z = NumMips; //FVirtualShadowMap::MaxMipLevels; // Size of view, for the virtual SMs these are assumed to not be offset. FIntPoint ViewSize = FIntPoint::DivideAndRoundUp( FIntPoint( PrimaryView.ViewSizeAndInvSize.X + 0.5f, PrimaryView.ViewSizeAndInvSize.Y + 0.5f ), 1U << MipLevel ); FIntPoint ViewMin = FIntPoint(MipView.ViewRect.X, MipView.ViewRect.Y) / (1U << MipLevel); MipView.ViewSizeAndInvSize = FVector4f(ViewSize.X, ViewSize.Y, 1.0f / float(ViewSize.X), 1.0f / float(ViewSize.Y)); MipView.ViewRect = FIntVector4(ViewMin.X, ViewMin.Y, ViewMin.X + ViewSize.X, ViewMin.Y + ViewSize.Y); MipView.UpdateLODScales(); MipView.LODScales.X *= LODScaleFactor; MipView.TranslatedWorldToSubpixelClip = Nanite::FPackedView::CalcTranslatedWorldToSubpixelClip(MipView.TranslatedWorldToClip, FIntRect(ViewMin.X, ViewMin.Y, ViewMin.X + ViewSize.X, ViewMin.Y + ViewSize.Y)); } MipView.HZBTestViewRect = MipView.ViewRect; // Assumed to always be the same for VSM float RcpExtXY = 1.0f / ( FVirtualShadowMap::PageSize * FVirtualShadowMap::RasterWindowPages ); // Transform clip from virtual address space to viewport. MipView.ClipSpaceScaleOffset = FVector4f( MipView.ViewSizeAndInvSize.X * RcpExtXY, MipView.ViewSizeAndInvSize.Y * RcpExtXY, (MipView.ViewSizeAndInvSize.X + 2.0f * MipView.ViewRect.X) * RcpExtXY - 1.0f, -(MipView.ViewSizeAndInvSize.Y + 2.0f * MipView.ViewRect.Y) * RcpExtXY + 1.0f); uint32 StreamingPriorityCategory = 0; uint32 ViewFlags = NANITE_VIEW_FLAG_HZBTEST | NANITE_VIEW_FLAG_NEAR_CLIP; MipView.StreamingPriorityCategory_AndFlags = (ViewFlags << NANITE_NUM_STREAMING_PRIORITY_CATEGORY_BITS) | StreamingPriorityCategory; } } // Remove unused mip views check(Views.IsEmpty() || MaxMips > 0); Views.SetNum(MaxMips * NumPrimaryViews, false); } class FVirtualSmPrintClipmapStatsCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FVirtualSmPrintClipmapStatsCS); SHADER_USE_PARAMETER_STRUCT(FVirtualSmPrintClipmapStatsCS, FVirtualPageManagementShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) //SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_STRUCT_INCLUDE(ShaderPrint::FShaderParameters, ShaderPrintStruct) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< FIntVector4 >, PageRectBounds) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< FIntVector4 >, AllocatedPageRectBounds) SHADER_PARAMETER(uint32, ShadowMapIdRangeStart) SHADER_PARAMETER(uint32, ShadowMapIdRangeEnd) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FVirtualSmPrintClipmapStatsCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPrintStats.usf", "PrintClipmapStats", SF_Compute); BEGIN_SHADER_PARAMETER_STRUCT(FVirtualShadowDepthPassParameters,) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FShadowDepthPassUniformParameters, ShadowDepthPass) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_STRUCT_INCLUDE(FInstanceCullingDrawParams, InstanceCullingDrawParams) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< FPackedView >, InViews) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() struct FVSMCullingBatchInfo { FVector3f CullingViewOriginOffset; uint32 FirstPrimaryView; FVector3f CullingViewOriginTile; uint32 NumPrimaryViews; }; struct FVisibleInstanceCmd { uint32 PackedPageInfo; uint32 InstanceId; uint32 DrawCommandId; }; class FCullPerPageDrawCommandsCs : public FGlobalShader { DECLARE_GLOBAL_SHADER(FCullPerPageDrawCommandsCs); SHADER_USE_PARAMETER_STRUCT(FCullPerPageDrawCommandsCs, FGlobalShader) class FNearClipDim : SHADER_PERMUTATION_BOOL( "NEAR_CLIP" ); class FUseHzbDim : SHADER_PERMUTATION_BOOL("USE_HZB_OCCLUSION"); class FGenerateStatsDim : SHADER_PERMUTATION_BOOL("VSM_GENERATE_STATS"); class FBatchedDim : SHADER_PERMUTATION_BOOL("ENABLE_BATCH_MODE"); using FPermutationDomain = TShaderPermutationDomain< FUseHzbDim, FNearClipDim, FBatchedDim, FGenerateStatsDim >; public: static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) && DoesPlatformSupportNanite(Parameters.Platform); } /** */ static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); FVirtualShadowMapArray::SetShaderDefines(OutEnvironment); FInstanceProcessingGPULoadBalancer::SetShaderDefines(OutEnvironment); OutEnvironment.SetDefine(TEXT("NANITE_MULTI_VIEW"), 1); OutEnvironment.SetDefine(TEXT("INDIRECT_ARGS_NUM_WORDS"), FInstanceCullingContext::IndirectArgsNumWords); OutEnvironment.SetDefine(TEXT("VF_SUPPORTS_PRIMITIVE_SCENE_DATA"), 1); OutEnvironment.SetDefine(TEXT("USE_GLOBAL_GPU_SCENE_DATA"), 1); } BEGIN_SHADER_PARAMETER_STRUCT(FHZBShaderParameters, ) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint >, HZBPageTable) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint >, HZBPageFlags) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint4 >, HZBPageRectBounds) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, HZBTexture) SHADER_PARAMETER_SAMPLER(SamplerState, HZBSampler) SHADER_PARAMETER(FVector2f, HZBSize) SHADER_PARAMETER(uint32, HZBMode) END_SHADER_PARAMETER_STRUCT() BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, GPUSceneInstanceSceneData) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, GPUSceneInstancePayloadData) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, GPUScenePrimitiveSceneData) SHADER_PARAMETER(uint32, InstanceSceneDataSOAStride) SHADER_PARAMETER(uint32, GPUSceneFrameNumber) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, PrimitiveRevealedMask) SHADER_PARAMETER(uint32, PrimitiveRevealedNum) SHADER_PARAMETER_STRUCT_INCLUDE(FInstanceProcessingGPULoadBalancer::FShaderParameters, LoadBalancerParameters) SHADER_PARAMETER(int32, FirstPrimaryView) SHADER_PARAMETER(int32, NumPrimaryViews) SHADER_PARAMETER(uint32, TotalPrimaryViews) SHADER_PARAMETER(uint32, VisibleInstancesBufferNum) SHADER_PARAMETER(int32, DynamicInstanceIdOffset) SHADER_PARAMETER(int32, DynamicInstanceIdMax) SHADER_PARAMETER(float, MaxMaterialPositionInvalidationRange) SHADER_PARAMETER(FVector3f, CullingViewOriginOffset) SHADER_PARAMETER(FVector3f, CullingViewOriginTile) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< FPackedView >, InViews) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< FInstanceCullingContext::FDrawCommandDesc >, DrawCommandDescs) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< FContextBatchInfo >, BatchInfos) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< FVSMCullingBatchInfo >, VSMCullingBatchInfos) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint32 >, BatchInds) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, VisibleInstancesOut) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, DrawIndirectArgsBufferOut) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, VisibleInstanceCountBufferOut) SHADER_PARAMETER_STRUCT_INCLUDE(FHZBShaderParameters, HZBShaderParameters) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, OutInvalidatingInstances) SHADER_PARAMETER(uint32, NumInvalidatingInstanceSlots) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer< uint >, OutStatsBuffer) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FCullPerPageDrawCommandsCs, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapBuildPerPageDrawCommands.usf", "CullPerPageDrawCommandsCs", SF_Compute); class FAllocateCommandInstanceOutputSpaceCs : public FGlobalShader { DECLARE_GLOBAL_SHADER(FAllocateCommandInstanceOutputSpaceCs); SHADER_USE_PARAMETER_STRUCT(FAllocateCommandInstanceOutputSpaceCs, FGlobalShader) public: static constexpr int32 NumThreadsPerGroup = 64; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) && DoesPlatformSupportNanite(Parameters.Platform); } /** */ static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); FVirtualShadowMapArray::SetShaderDefines(OutEnvironment); FInstanceProcessingGPULoadBalancer::SetShaderDefines(OutEnvironment); OutEnvironment.SetDefine(TEXT("NUM_THREADS_PER_GROUP"), NumThreadsPerGroup); OutEnvironment.SetDefine(TEXT("NANITE_MULTI_VIEW"), 1); OutEnvironment.SetDefine(TEXT("INDIRECT_ARGS_NUM_WORDS"), FInstanceCullingContext::IndirectArgsNumWords); } BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(uint32, NumIndirectArgs) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, DrawIndirectArgsBuffer) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, InstanceIdOffsetBufferOut) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, OutputOffsetBufferOut) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, TmpInstanceIdOffsetBufferOut) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FAllocateCommandInstanceOutputSpaceCs, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapBuildPerPageDrawCommands.usf", "AllocateCommandInstanceOutputSpaceCs", SF_Compute); class FOutputCommandInstanceListsCs : public FGlobalShader { DECLARE_GLOBAL_SHADER(FOutputCommandInstanceListsCs); SHADER_USE_PARAMETER_STRUCT(FOutputCommandInstanceListsCs, FGlobalShader) public: static constexpr int32 NumThreadsPerGroup = 64; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) && DoesPlatformSupportNanite(Parameters.Platform); } /** */ static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); FVirtualShadowMapArray::SetShaderDefines(OutEnvironment); FInstanceProcessingGPULoadBalancer::SetShaderDefines(OutEnvironment); OutEnvironment.SetDefine(TEXT("NUM_THREADS_PER_GROUP"), NumThreadsPerGroup); OutEnvironment.SetDefine(TEXT("NANITE_MULTI_VIEW"), 1); OutEnvironment.SetDefine(TEXT("INDIRECT_ARGS_NUM_WORDS"), FInstanceCullingContext::IndirectArgsNumWords); } BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< FVisibleInstanceCmd >, VisibleInstances) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, InstanceIdsBufferOut) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, PageInfoBufferOut) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, TmpInstanceIdOffsetBufferOut) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, VisibleInstanceCountBuffer) // Needed reference for make RDG happy somehow RDG_BUFFER_ACCESS(IndirectArgs, ERHIAccess::IndirectArgs) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FOutputCommandInstanceListsCs, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapBuildPerPageDrawCommands.usf", "OutputCommandInstanceListsCs", SF_Compute); struct FCullingResult { FRDGBufferRef DrawIndirectArgsRDG; FRDGBufferRef InstanceIdOffsetBufferRDG; FRDGBufferRef InstanceIdsBuffer; FRDGBufferRef PageInfoBuffer; uint32 MaxNumInstancesPerPass; }; template static FCullingResult AddCullingPasses(FRDGBuilder& GraphBuilder, const TConstArrayView &IndirectArgs, const TConstArrayView& DrawCommandDescs, const TConstArrayView& InstanceIdOffsets, InstanceCullingLoadBalancerType *LoadBalancer, const TConstArrayView BatchInfos, const TConstArrayView VSMCullingBatchInfos, const TConstArrayView BatchInds, bool bUseNearClip, uint32 TotalInstances, uint32 TotalPrimaryViews, FRDGBufferRef VirtualShadowViewsRDG, const FCullPerPageDrawCommandsCs::FHZBShaderParameters &HZBShaderParameters, FVirtualShadowMapArray *VirtualShadowMapArray, FGPUScene& GPUScene, FRDGBufferRef PrimitiveRevealedMaskRdg, int32 PrimitiveRevealedNum) { int32 NumIndirectArgs = IndirectArgs.Num(); FRDGBufferRef TmpInstanceIdOffsetBufferRDG = CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.TmpInstanceIdOffsetBuffer"), sizeof(uint32), NumIndirectArgs, nullptr, 0); // TODO: This is both not right, and also over conservative when running with the atomic path FCullingResult CullingResult; CullingResult.MaxNumInstancesPerPass = TotalInstances * 64u; FRDGBufferRef VisibleInstancesRdg = CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.VisibleInstances"), sizeof(FVisibleInstanceCmd), CullingResult.MaxNumInstancesPerPass, nullptr, 0); FRDGBufferRef VisibleInstanceWriteOffsetRDG = CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.VisibleInstanceWriteOffset"), sizeof(uint32), 1, nullptr, 0); FRDGBufferRef OutputOffsetBufferRDG = CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.OutputOffsetBuffer"), sizeof(uint32), 1, nullptr, 0); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(VisibleInstanceWriteOffsetRDG), 0); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(OutputOffsetBufferRDG), 0); // Create buffer for indirect args and upload draw arg data, also clears the instance to zero FRDGBufferDesc IndirectArgsDesc = FRDGBufferDesc::CreateIndirectDesc(FInstanceCullingContext::IndirectArgsNumWords * IndirectArgs.Num()); IndirectArgsDesc.Usage |= BUF_MultiGPUGraphIgnore; CullingResult.DrawIndirectArgsRDG = GraphBuilder.CreateBuffer(IndirectArgsDesc, TEXT("Shadow.Virtual.DrawIndirectArgsBuffer")); GraphBuilder.QueueBufferUpload(CullingResult.DrawIndirectArgsRDG, IndirectArgs.GetData(), IndirectArgs.GetTypeSize() * IndirectArgs.Num()); FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel); // Note: we redundantly clear the instance counts here as there is some issue with replays on certain consoles. FInstanceCullingContext::AddClearIndirectArgInstanceCountPass(GraphBuilder, ShaderMap, CullingResult.DrawIndirectArgsRDG); // not using structured buffer as we have to get at it as a vertex buffer CullingResult.InstanceIdOffsetBufferRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), InstanceIdOffsets.Num()), TEXT("Shadow.Virtual.InstanceIdOffsetBuffer")); { FCullPerPageDrawCommandsCs::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = VirtualShadowMapArray->GetUniformBuffer(GraphBuilder); const FGPUSceneResourceParameters GPUSceneParameters = GPUScene.GetShaderParameters(); PassParameters->GPUSceneInstanceSceneData = GPUSceneParameters.GPUSceneInstanceSceneData; PassParameters->GPUSceneInstancePayloadData = GPUSceneParameters.GPUSceneInstancePayloadData; PassParameters->GPUScenePrimitiveSceneData = GPUSceneParameters.GPUScenePrimitiveSceneData; PassParameters->GPUSceneFrameNumber = GPUSceneParameters.GPUSceneFrameNumber; PassParameters->InstanceSceneDataSOAStride = GPUSceneParameters.InstanceDataSOAStride; // Make sure there is enough space in the buffer for all the primitive IDs that might be used to index. check(PrimitiveRevealedMaskRdg->Desc.NumElements * 32u >= uint32(PrimitiveRevealedNum)); PassParameters->PrimitiveRevealedMask = GraphBuilder.CreateSRV(PrimitiveRevealedMaskRdg); PassParameters->PrimitiveRevealedNum = uint32(PrimitiveRevealedNum); PassParameters->DynamicInstanceIdOffset = BatchInfos[0].DynamicInstanceIdOffset; PassParameters->DynamicInstanceIdMax = BatchInfos[0].DynamicInstanceIdMax; PassParameters->MaxMaterialPositionInvalidationRange = CVarMaxMaterialPositionInvalidationRange.GetValueOnRenderThread(); auto GPUData = LoadBalancer->Upload(GraphBuilder); GPUData.GetShaderParameters(GraphBuilder, PassParameters->LoadBalancerParameters); PassParameters->FirstPrimaryView = VSMCullingBatchInfos[0].FirstPrimaryView; PassParameters->NumPrimaryViews = VSMCullingBatchInfos[0].NumPrimaryViews; PassParameters->CullingViewOriginOffset = VSMCullingBatchInfos[0].CullingViewOriginOffset; PassParameters->CullingViewOriginTile = VSMCullingBatchInfos[0].CullingViewOriginTile; PassParameters->TotalPrimaryViews = TotalPrimaryViews; PassParameters->VisibleInstancesBufferNum = CullingResult.MaxNumInstancesPerPass; PassParameters->InViews = GraphBuilder.CreateSRV(VirtualShadowViewsRDG); PassParameters->DrawCommandDescs = GraphBuilder.CreateSRV(CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.DrawCommandDescs"), DrawCommandDescs)); const bool bUseBatchMode = !BatchInds.IsEmpty(); if (bUseBatchMode) { PassParameters->BatchInfos = GraphBuilder.CreateSRV(CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.BatchInfos"), BatchInfos)); PassParameters->VSMCullingBatchInfos = GraphBuilder.CreateSRV(CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.VSMCullingBatchInfos"), VSMCullingBatchInfos)); PassParameters->BatchInds = GraphBuilder.CreateSRV(CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.BatchInds"), BatchInds)); } PassParameters->DrawIndirectArgsBufferOut = GraphBuilder.CreateUAV(CullingResult.DrawIndirectArgsRDG, PF_R32_UINT); PassParameters->VisibleInstancesOut = GraphBuilder.CreateUAV(VisibleInstancesRdg); PassParameters->VisibleInstanceCountBufferOut = GraphBuilder.CreateUAV(VisibleInstanceWriteOffsetRDG); PassParameters->OutInvalidatingInstances = GraphBuilder.CreateUAV(VirtualShadowMapArray->InvalidatingInstancesRDG); PassParameters->NumInvalidatingInstanceSlots = VirtualShadowMapArray->NumInvalidatingInstanceSlots; PassParameters->HZBShaderParameters = HZBShaderParameters; bool bGenerateStats = VirtualShadowMapArray->StatsBufferRDG != nullptr; if (bGenerateStats) { PassParameters->OutStatsBuffer = GraphBuilder.CreateUAV(VirtualShadowMapArray->StatsBufferRDG); } FCullPerPageDrawCommandsCs::FPermutationDomain PermutationVector; PermutationVector.Set< FCullPerPageDrawCommandsCs::FNearClipDim >(bUseNearClip); PermutationVector.Set< FCullPerPageDrawCommandsCs::FBatchedDim >(bUseBatchMode); PermutationVector.Set< FCullPerPageDrawCommandsCs::FUseHzbDim >(HZBShaderParameters.HZBTexture != nullptr); PermutationVector.Set< FCullPerPageDrawCommandsCs::FGenerateStatsDim >(bGenerateStats); auto ComputeShader = ShaderMap->GetShader(PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("CullPerPageDrawCommands"), ComputeShader, PassParameters, LoadBalancer->GetWrappedCsGroupCount() ); } // 2.2.Allocate space for the final instance ID output and so on. if (true) { FAllocateCommandInstanceOutputSpaceCs::FParameters* PassParameters = GraphBuilder.AllocParameters(); FRDGBufferRef InstanceIdOutOffsetBufferRDG = CreateStructuredBuffer(GraphBuilder, TEXT("InstanceCulling.OutputOffsetBufferOut"), sizeof(uint32), 1, nullptr, 0); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(InstanceIdOutOffsetBufferRDG), 0); PassParameters->NumIndirectArgs = NumIndirectArgs; PassParameters->InstanceIdOffsetBufferOut = GraphBuilder.CreateUAV(CullingResult.InstanceIdOffsetBufferRDG, PF_R32_UINT);; PassParameters->OutputOffsetBufferOut = GraphBuilder.CreateUAV(InstanceIdOutOffsetBufferRDG); PassParameters->TmpInstanceIdOffsetBufferOut = GraphBuilder.CreateUAV(TmpInstanceIdOffsetBufferRDG); PassParameters->DrawIndirectArgsBuffer = GraphBuilder.CreateSRV(CullingResult.DrawIndirectArgsRDG, PF_R32_UINT); auto ComputeShader = ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("AllocateCommandInstanceOutputSpaceCs"), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(NumIndirectArgs, FAllocateCommandInstanceOutputSpaceCs::NumThreadsPerGroup) ); } // 2.3. Perform final pass to re-shuffle the instance ID's to their final resting places CullingResult.InstanceIdsBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), CullingResult.MaxNumInstancesPerPass), TEXT("Shadow.Virtual.InstanceIdsBuffer")); CullingResult.PageInfoBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), CullingResult.MaxNumInstancesPerPass), TEXT("Shadow.Virtual.PageInfoBuffer")); FRDGBufferRef OutputPassIndirectArgs = FComputeShaderUtils::AddIndirectArgsSetupCsPass1D(GraphBuilder, VisibleInstanceWriteOffsetRDG, TEXT("Shadow.Virtual.IndirectArgs"), FOutputCommandInstanceListsCs::NumThreadsPerGroup); if (true) { FOutputCommandInstanceListsCs::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VisibleInstances = GraphBuilder.CreateSRV(VisibleInstancesRdg); PassParameters->PageInfoBufferOut = GraphBuilder.CreateUAV(CullingResult.PageInfoBuffer); PassParameters->InstanceIdsBufferOut = GraphBuilder.CreateUAV(CullingResult.InstanceIdsBuffer); PassParameters->TmpInstanceIdOffsetBufferOut = GraphBuilder.CreateUAV(TmpInstanceIdOffsetBufferRDG); PassParameters->VisibleInstanceCountBuffer = GraphBuilder.CreateSRV(VisibleInstanceWriteOffsetRDG); PassParameters->IndirectArgs = OutputPassIndirectArgs; auto ComputeShader = ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("OutputCommandInstanceListsCs"), ComputeShader, PassParameters, OutputPassIndirectArgs, 0 ); } return CullingResult; } static void AddRasterPass( FRDGBuilder& GraphBuilder, FRDGEventName&& PassName, const FViewInfo * ShadowDepthView, const TRDGUniformBufferRef &ShadowDepthPassUniformBuffer, FVirtualShadowMapArray* VirtualShadowMapArray, FRDGBufferRef VirtualShadowViewsRDG, const FCullingResult &CullingResult, FParallelMeshDrawCommandPass& MeshCommandPass, FVirtualShadowDepthPassParameters* PassParameters, TRDGUniformBufferRef InstanceCullingUniformBuffer) { PassParameters->View = ShadowDepthView->ViewUniformBuffer; PassParameters->ShadowDepthPass = ShadowDepthPassUniformBuffer; PassParameters->VirtualShadowMap = VirtualShadowMapArray->GetUniformBuffer(GraphBuilder); PassParameters->InViews = GraphBuilder.CreateSRV(VirtualShadowViewsRDG); PassParameters->InstanceCullingDrawParams.DrawIndirectArgsBuffer = CullingResult.DrawIndirectArgsRDG; PassParameters->InstanceCullingDrawParams.InstanceIdOffsetBuffer = CullingResult.InstanceIdOffsetBufferRDG; PassParameters->InstanceCullingDrawParams.InstanceCulling = InstanceCullingUniformBuffer; FIntRect ViewRect; ViewRect.Max = FVirtualShadowMap::VirtualMaxResolutionXY; GraphBuilder.AddPass( MoveTemp(PassName), PassParameters, ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass, [&MeshCommandPass, PassParameters, ViewRect](FRHICommandList& RHICmdList) { FRHIRenderPassInfo RPInfo; RPInfo.ResolveRect = FResolveRect(ViewRect); RHICmdList.BeginRenderPass(RPInfo, TEXT("RasterizeVirtualShadowMaps(Non-Nanite)")); RHICmdList.SetViewport(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, FMath::Min(ViewRect.Max.X, 32767), FMath::Min(ViewRect.Max.Y, 32767), 1.0f); MeshCommandPass.DispatchDraw(nullptr, RHICmdList, &PassParameters->InstanceCullingDrawParams); RHICmdList.EndRenderPass(); }); } void FVirtualShadowMapArray::RenderVirtualShadowMapsNonNanite(FRDGBuilder& GraphBuilder, const TArray& VirtualSmMeshCommandPasses, FScene& Scene, TArrayView Views) { if (VirtualSmMeshCommandPasses.Num() == 0) { return; } RDG_EVENT_SCOPE(GraphBuilder, "RenderVirtualShadowMaps(Non-Nanite)"); FGPUScene& GPUScene = Scene.GPUScene; FRDGBufferSRVRef PrevPageTableRDGSRV = CacheManager->IsValid() ? GraphBuilder.CreateSRV(GraphBuilder.RegisterExternalBuffer(CacheManager->PrevBuffers.PageTable, TEXT("Shadow.Virtual.PrevPageTable"))) : nullptr; FRDGBufferSRVRef PrevPageFlagsRDGSRV = CacheManager->IsValid() ? GraphBuilder.CreateSRV(GraphBuilder.RegisterExternalBuffer(CacheManager->PrevBuffers.PageFlags, TEXT("Shadow.Virtual.PrevPageFlags"))) : nullptr; FRDGBufferSRVRef PrevPageRectBoundsRDGSRV = CacheManager->IsValid() ? GraphBuilder.CreateSRV(GraphBuilder.RegisterExternalBuffer(CacheManager->PrevBuffers.PageRectBounds, TEXT("Shadow.Virtual.PrevPageRectBounds"))) : nullptr; int32 HZBMode = CVarNonNaniteVsmUseHzb.GetValueOnRenderThread(); auto InitHZB = [&]()->FRDGTextureRef { if (HZBMode == 1 && CacheManager->IsValid()) { return GraphBuilder.RegisterExternalTexture(CacheManager->PrevBuffers.HZBPhysical); } if (HZBMode == 2 && HZBPhysical != nullptr) { return HZBPhysical; } return nullptr; }; const FRDGTextureRef HZBTexture = InitHZB(); TArray UnBatchedVSMCullingBatchInfo; TArray BatchedVirtualSmMeshCommandPasses; TArray UnBatchedVirtualSmMeshCommandPasses; UnBatchedVSMCullingBatchInfo.Reserve(VirtualSmMeshCommandPasses.Num()); BatchedVirtualSmMeshCommandPasses.Reserve(VirtualSmMeshCommandPasses.Num()); UnBatchedVirtualSmMeshCommandPasses.Reserve(VirtualSmMeshCommandPasses.Num()); TArray VirtualShadowViews; TArray VSMCullingBatchInfos; VSMCullingBatchInfos.Reserve(VirtualSmMeshCommandPasses.Num()); TArray BatchedPassParameters; BatchedPassParameters.Reserve(VirtualSmMeshCommandPasses.Num()); /** * Use the 'dependent view' i.e., the view used to set up a view dependent CSM/VSM(clipmap) OR select the view closest to the local light. * This last is important to get some kind of reasonable behavior for split screen. */ auto GetCullingViewOrigin = [&Views](const FProjectedShadowInfo* ProjectedShadowInfo) -> FLargeWorldRenderPosition { if (ProjectedShadowInfo->DependentView != nullptr) { return FLargeWorldRenderPosition(ProjectedShadowInfo->DependentView->ShadowViewMatrices.GetViewOrigin()); } // VSM supports only whole scene shadows, so those without a "DependentView" are local lights // For local lights the origin is the (inverse of) pre-shadow translation. check(ProjectedShadowInfo->bWholeSceneShadow); FVector MinOrigin = Views[0].ShadowViewMatrices.GetViewOrigin(); double MinDistanceSq = (MinOrigin + ProjectedShadowInfo->PreShadowTranslation).SquaredLength(); for (int Index = 1; Index < Views.Num(); ++Index) { FVector TestOrigin = Views[Index].ShadowViewMatrices.GetViewOrigin(); double TestDistanceSq = (TestOrigin + ProjectedShadowInfo->PreShadowTranslation).SquaredLength(); if (TestDistanceSq < MinDistanceSq) { MinOrigin = TestOrigin; MinDistanceSq = TestDistanceSq; } } return FLargeWorldRenderPosition(MinOrigin); }; FInstanceCullingMergedContext InstanceCullingMergedContext(GMaxRHIFeatureLevel); // We don't use the registered culling views (this redundancy should probably be addressed at some point), set the number to disable index range checking InstanceCullingMergedContext.NumCullingViews = -1; for (int32 Index = 0; Index < VirtualSmMeshCommandPasses.Num(); ++Index) { FProjectedShadowInfo* ProjectedShadowInfo = VirtualSmMeshCommandPasses[Index]; ProjectedShadowInfo->BeginRenderView(GraphBuilder, &Scene); FVSMCullingBatchInfo VSMCullingBatchInfo; VSMCullingBatchInfo.FirstPrimaryView = uint32(VirtualShadowViews.Num()); VSMCullingBatchInfo.NumPrimaryViews = 0U; { const FLargeWorldRenderPosition CullingViewOrigin = GetCullingViewOrigin(ProjectedShadowInfo); VSMCullingBatchInfo.CullingViewOriginOffset = CullingViewOrigin.GetOffset(); VSMCullingBatchInfo.CullingViewOriginTile = CullingViewOrigin.GetTile(); } const TSharedPtr Clipmap = ProjectedShadowInfo->VirtualShadowMapClipmap; if (Clipmap) { VSMCullingBatchInfo.NumPrimaryViews = AddRenderViews(Clipmap, 1.0f, HZBTexture != nullptr, false, ProjectedShadowInfo->ShouldClampToNearPlane(), VirtualShadowViews); UnBatchedVSMCullingBatchInfo.Add(VSMCullingBatchInfo); UnBatchedVirtualSmMeshCommandPasses.Add(ProjectedShadowInfo); } else if (ProjectedShadowInfo->HasVirtualShadowMap()) { FParallelMeshDrawCommandPass& MeshCommandPass = ProjectedShadowInfo->GetShadowDepthPass(); MeshCommandPass.WaitForSetupTask(); FInstanceCullingContext* InstanceCullingContext = MeshCommandPass.GetInstanceCullingContext(); if (InstanceCullingContext->HasCullingCommands()) { VSMCullingBatchInfo.NumPrimaryViews = AddRenderViews(ProjectedShadowInfo, 1.0f, HZBTexture != nullptr, false, ProjectedShadowInfo->ShouldClampToNearPlane(), VirtualShadowViews); if (CVarDoNonNaniteBatching.GetValueOnRenderThread()) { FViewInfo* ShadowDepthView = ProjectedShadowInfo->ShadowDepthView; uint32 DynamicInstanceIdOffset = ShadowDepthView->DynamicPrimitiveCollector.GetInstanceSceneDataOffset(); uint32 DynamicInstanceIdMax = DynamicInstanceIdOffset + ShadowDepthView->DynamicPrimitiveCollector.NumInstances(); VSMCullingBatchInfos.Add(VSMCullingBatchInfo); // Note: we have to allocate these up front as the context merging machinery writes the offsets directly to the &PassParameters->InstanceCullingDrawParams, // this is a side-effect from sharing the code with the deferred culling. Should probably be refactored. FVirtualShadowDepthPassParameters* PassParameters = GraphBuilder.AllocParameters(); InstanceCullingMergedContext.AddBatch(GraphBuilder, InstanceCullingContext, DynamicInstanceIdOffset, ShadowDepthView->DynamicPrimitiveCollector.NumInstances(), &PassParameters->InstanceCullingDrawParams); BatchedVirtualSmMeshCommandPasses.Add(ProjectedShadowInfo); BatchedPassParameters.Add(PassParameters); } else { UnBatchedVSMCullingBatchInfo.Add(VSMCullingBatchInfo); UnBatchedVirtualSmMeshCommandPasses.Add(ProjectedShadowInfo); } } } } uint32 TotalPrimaryViews = uint32(VirtualShadowViews.Num()); CreateMipViews(VirtualShadowViews); FRDGBufferRef VirtualShadowViewsRDG = CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.VirtualShadowViews"), VirtualShadowViews); // Helper function to create raster pass UB - only really need two of these ever const FSceneTextures* SceneTextures = &GetViewFamily(Views).GetSceneTextures(); auto CreateShadowDepthPassUniformBuffer = [this, &VirtualShadowViewsRDG, &GraphBuilder, SceneTextures](bool bClampToNearPlane) { FShadowDepthPassUniformParameters* ShadowDepthPassParameters = GraphBuilder.AllocParameters(); check(PhysicalPagePoolRDG != nullptr); // TODO: These are not used for this case anyway ShadowDepthPassParameters->ProjectionMatrix = FMatrix44f::Identity; ShadowDepthPassParameters->ViewMatrix = FMatrix44f::Identity; ShadowDepthPassParameters->ShadowParams = FVector4f(0.0f, 0.0f, 0.0f, 1.0f); ShadowDepthPassParameters->bRenderToVirtualShadowMap = true; ShadowDepthPassParameters->VirtualSmPageTable = GraphBuilder.CreateSRV(PageTableRDG); ShadowDepthPassParameters->PackedNaniteViews = GraphBuilder.CreateSRV(VirtualShadowViewsRDG); ShadowDepthPassParameters->PageRectBounds = GraphBuilder.CreateSRV(PageRectBoundsRDG); ShadowDepthPassParameters->OutDepthBufferArray = GraphBuilder.CreateUAV(PhysicalPagePoolRDG, ERDGUnorderedAccessViewFlags::SkipBarrier); SetupSceneTextureUniformParameters(GraphBuilder, SceneTextures, GMaxRHIFeatureLevel, ESceneTextureSetupMode::None, ShadowDepthPassParameters->SceneTextures); ShadowDepthPassParameters->bClampToNearPlane = bClampToNearPlane; return GraphBuilder.CreateUniformBuffer(ShadowDepthPassParameters); }; FCullPerPageDrawCommandsCs::FHZBShaderParameters HZBShaderParameters; if (HZBTexture) { // Mode 2 uses the current frame HZB & page table. HZBShaderParameters.HZBPageTable = HZBMode == 2 ? GraphBuilder.CreateSRV(PageTableRDG) : PrevPageTableRDGSRV; HZBShaderParameters.HZBPageFlags = HZBMode == 2 ? GraphBuilder.CreateSRV(PageFlagsRDG) : PrevPageFlagsRDGSRV; HZBShaderParameters.HZBPageRectBounds = HZBMode == 2 ? GraphBuilder.CreateSRV(PageRectBoundsRDG) : PrevPageRectBoundsRDGSRV; HZBShaderParameters.HZBTexture = HZBTexture; HZBShaderParameters.HZBSize = HZBTexture->Desc.Extent; HZBShaderParameters.HZBSampler = TStaticSamplerState< SF_Point, AM_Clamp, AM_Clamp, AM_Clamp >::GetRHI(); HZBShaderParameters.HZBMode = HZBMode; } // Process batched passes if (!InstanceCullingMergedContext.Batches.IsEmpty()) { RDG_EVENT_SCOPE(GraphBuilder, "Batched"); InstanceCullingMergedContext.MergeBatches(); GraphBuilder.BeginEventScope(RDG_EVENT_NAME("CullingPasses")); FCullingResult CullingResult = AddCullingPasses( GraphBuilder, InstanceCullingMergedContext.IndirectArgs, InstanceCullingMergedContext.DrawCommandDescs, InstanceCullingMergedContext.InstanceIdOffsets, &InstanceCullingMergedContext.LoadBalancers[uint32(EBatchProcessingMode::Generic)], InstanceCullingMergedContext.BatchInfos, VSMCullingBatchInfos, InstanceCullingMergedContext.BatchInds[uint32(EBatchProcessingMode::Generic)], true, InstanceCullingMergedContext.TotalInstances, TotalPrimaryViews, VirtualShadowViewsRDG, HZBShaderParameters, this, GPUScene, GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, 4), 0 ); GraphBuilder.EndEventScope(); TRDGUniformBufferRef ShadowDepthPassUniformBuffer = CreateShadowDepthPassUniformBuffer(false); FInstanceCullingGlobalUniforms* InstanceCullingGlobalUniforms = GraphBuilder.AllocParameters(); InstanceCullingGlobalUniforms->InstanceIdsBuffer = GraphBuilder.CreateSRV(CullingResult.InstanceIdsBuffer); InstanceCullingGlobalUniforms->PageInfoBuffer = GraphBuilder.CreateSRV(CullingResult.PageInfoBuffer); InstanceCullingGlobalUniforms->BufferCapacity = CullingResult.MaxNumInstancesPerPass; TRDGUniformBufferRef InstanceCullingUniformBuffer = GraphBuilder.CreateUniformBuffer(InstanceCullingGlobalUniforms); { RDG_EVENT_SCOPE(GraphBuilder, "RasterPasses"); for (int Index = 0; Index < BatchedVirtualSmMeshCommandPasses.Num(); ++Index) { FProjectedShadowInfo* ProjectedShadowInfo = BatchedVirtualSmMeshCommandPasses[Index]; FParallelMeshDrawCommandPass& MeshCommandPass = ProjectedShadowInfo->GetShadowDepthPass(); FViewInfo* ShadowDepthView = ProjectedShadowInfo->ShadowDepthView; // Local lights are assumed to not use the clamp to near-plane (this is used for some per-object SMs but these should never be used for VSM). check(!ProjectedShadowInfo->ShouldClampToNearPlane()); FString LightNameWithLevel; FSceneRenderer::GetLightNameForDrawEvent(ProjectedShadowInfo->GetLightSceneInfo().Proxy, LightNameWithLevel); AddRasterPass(GraphBuilder, RDG_EVENT_NAME("Rasterize[%s]", *LightNameWithLevel), ShadowDepthView, ShadowDepthPassUniformBuffer, this, VirtualShadowViewsRDG, CullingResult, MeshCommandPass, BatchedPassParameters[Index], InstanceCullingUniformBuffer); } } } // Loop over the un batched mesh command passes needed, these are all the clipmaps (but we may change the criteria) for (int Index = 0; Index < UnBatchedVirtualSmMeshCommandPasses.Num(); ++Index) { const auto VSMCullingBatchInfo = UnBatchedVSMCullingBatchInfo[Index]; FProjectedShadowInfo* ProjectedShadowInfo = UnBatchedVirtualSmMeshCommandPasses[Index]; FInstanceCullingMergedContext::FContextBatchInfo CullingBatchInfo = FInstanceCullingMergedContext::FContextBatchInfo{ 0 }; FParallelMeshDrawCommandPass& MeshCommandPass = ProjectedShadowInfo->GetShadowDepthPass(); const TSharedPtr Clipmap = ProjectedShadowInfo->VirtualShadowMapClipmap; FViewInfo* ShadowDepthView = ProjectedShadowInfo->ShadowDepthView; MeshCommandPass.WaitForSetupTask(); FInstanceCullingContext* InstanceCullingContext = MeshCommandPass.GetInstanceCullingContext(); if (InstanceCullingContext->HasCullingCommands()) { FString LightNameWithLevel; FSceneRenderer::GetLightNameForDrawEvent(ProjectedShadowInfo->GetLightSceneInfo().Proxy, LightNameWithLevel); RDG_EVENT_SCOPE(GraphBuilder, "%s", *LightNameWithLevel); CullingBatchInfo.DynamicInstanceIdOffset = ShadowDepthView->DynamicPrimitiveCollector.GetInstanceSceneDataOffset(); CullingBatchInfo.DynamicInstanceIdMax = CullingBatchInfo.DynamicInstanceIdOffset + ShadowDepthView->DynamicPrimitiveCollector.NumInstances(); FRDGBufferRef PrimitiveRevealedMaskRdg = GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, 4); int32 PrimitiveRevealedNum = 0; if (!Clipmap->GetRevealedPrimitivesMask().IsEmpty()) { PrimitiveRevealedMaskRdg = CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.RevealedPrimitivesMask"), Clipmap->GetRevealedPrimitivesMask()); PrimitiveRevealedNum = Clipmap->GetNumRevealedPrimitives(); } FCullingResult CullingResult = AddCullingPasses( GraphBuilder, InstanceCullingContext->IndirectArgs, InstanceCullingContext->DrawCommandDescs, InstanceCullingContext->InstanceIdOffsets, InstanceCullingContext->LoadBalancers[uint32(EBatchProcessingMode::Generic)], MakeArrayView(&CullingBatchInfo, 1), MakeArrayView(&VSMCullingBatchInfo, 1), MakeArrayView(nullptr, 0), !Clipmap.IsValid(), InstanceCullingContext->TotalInstances, TotalPrimaryViews, VirtualShadowViewsRDG, HZBShaderParameters, this, GPUScene, PrimitiveRevealedMaskRdg, PrimitiveRevealedNum ); TRDGUniformBufferRef ShadowDepthPassUniformBuffer = CreateShadowDepthPassUniformBuffer(ProjectedShadowInfo->ShouldClampToNearPlane()); FInstanceCullingGlobalUniforms* InstanceCullingGlobalUniforms = GraphBuilder.AllocParameters(); InstanceCullingGlobalUniforms->InstanceIdsBuffer = GraphBuilder.CreateSRV(CullingResult.InstanceIdsBuffer); InstanceCullingGlobalUniforms->PageInfoBuffer = GraphBuilder.CreateSRV(CullingResult.PageInfoBuffer); InstanceCullingGlobalUniforms->BufferCapacity = CullingResult.MaxNumInstancesPerPass; TRDGUniformBufferRef InstanceCullingUniformBuffer = GraphBuilder.CreateUniformBuffer(InstanceCullingGlobalUniforms); FVirtualShadowDepthPassParameters* DepthPassParams = GraphBuilder.AllocParameters(); DepthPassParams->InstanceCullingDrawParams.IndirectArgsByteOffset = 0; DepthPassParams->InstanceCullingDrawParams.InstanceDataByteOffset = 0; AddRasterPass(GraphBuilder, RDG_EVENT_NAME("Rasterize"), ShadowDepthView, ShadowDepthPassUniformBuffer, this, VirtualShadowViewsRDG, CullingResult, MeshCommandPass, DepthPassParams, InstanceCullingUniformBuffer); } // if (Index == CVarShowClipmapStats.GetValueOnRenderThread()) { // The 'main' view the shadow was created with respect to const FViewInfo* ViewUsedToCreateShadow = ProjectedShadowInfo->DependentView; const FViewInfo& View = *ViewUsedToCreateShadow; FVirtualSmPrintClipmapStatsCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); ShaderPrint::SetParameters(GraphBuilder, View, PassParameters->ShaderPrintStruct); //PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->ShadowMapIdRangeStart = Clipmap->GetVirtualShadowMap(0)->ID; // Note: assumes range! PassParameters->ShadowMapIdRangeEnd = Clipmap->GetVirtualShadowMap(0)->ID + Clipmap->GetLevelCount(); PassParameters->PageRectBounds = GraphBuilder.CreateSRV(PageRectBoundsRDG); PassParameters->AllocatedPageRectBounds = GraphBuilder.CreateSRV(AllocatedPageRectBoundsRDG); auto ComputeShader = View.ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("PrintClipmapStats"), ComputeShader, PassParameters, FIntVector(1, 1, 1) ); } } } class FSelectPagesForHZBCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FSelectPagesForHZBCS); SHADER_USE_PARAMETER_STRUCT(FSelectPagesForHZBCS, FVirtualPageManagementShader) class FGenerateStatsDim : SHADER_PERMUTATION_BOOL("VSM_GENERATE_STATS"); using FPermutationDomain = TShaderPermutationDomain< FGenerateStatsDim >; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, PhysicalPageMetaData) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, OutPagesForHZBIndirectArgsBuffer) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer< uint >, OutPhysicalPagesForHZB) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint >, DirtyPageFlags) SHADER_PARAMETER(uint32, bFirstBuildThisFrame) SHADER_PARAMETER(uint32, bForceFullHZBUpdate) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer< uint >, OutStatsBuffer) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FSelectPagesForHZBCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "SelectPagesForHZBCS", SF_Compute); class FVirtualSmBuildHZBPerPageCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FVirtualSmBuildHZBPerPageCS); SHADER_USE_PARAMETER_STRUCT(FVirtualSmBuildHZBPerPageCS, FVirtualPageManagementShader) static constexpr uint32 TotalHZBLevels = FVirtualShadowMap::NumHZBLevels; static constexpr uint32 HZBLevelsBase = TotalHZBLevels - 2U; static_assert(HZBLevelsBase == 5U, "The shader is expecting 5 levels, if the page size is changed, this needs to be massaged"); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) RDG_BUFFER_ACCESS(IndirectArgs, ERHIAccess::IndirectArgs) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint >, PhysicalPagesForHZB) SHADER_PARAMETER_SAMPLER(SamplerState, PhysicalPagePoolSampler) SHADER_PARAMETER_RDG_TEXTURE(Texture2DArray, PhysicalPagePool) SHADER_PARAMETER_RDG_TEXTURE_UAV_ARRAY(RWTexture2D, FurthestHZBOutput, [HZBLevelsBase]) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FVirtualSmBuildHZBPerPageCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "BuildHZBPerPageCS", SF_Compute); class FVirtualSmBBuildHZBPerPageTopCS : public FVirtualPageManagementShader { DECLARE_GLOBAL_SHADER(FVirtualSmBBuildHZBPerPageTopCS); SHADER_USE_PARAMETER_STRUCT(FVirtualSmBBuildHZBPerPageTopCS, FVirtualPageManagementShader) // We need one level less as HZB starts at half-size (not really sure if we really need 1x1 and 2x2 sized levels). static constexpr uint32 HZBLevelsTop = 2; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) RDG_BUFFER_ACCESS(IndirectArgs, ERHIAccess::IndirectArgs) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FVirtualShadowMapUniformParameters, VirtualShadowMap) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< uint >, PhysicalPagesForHZB) SHADER_PARAMETER_SAMPLER(SamplerState, ParentTextureMipSampler) SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, ParentTextureMip) SHADER_PARAMETER(FVector2f, InvHzbInputSize) SHADER_PARAMETER_RDG_TEXTURE_UAV_ARRAY(RWTexture2D, FurthestHZBOutput, [HZBLevelsTop]) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FVirtualSmBBuildHZBPerPageTopCS, "/Engine/Private/VirtualShadowMaps/VirtualShadowMapPageManagement.usf", "BuildHZBPerPageTopCS", SF_Compute); void FVirtualShadowMapArray::UpdateHZB(FRDGBuilder& GraphBuilder) { const FIntRect ViewRect(0, 0, GetPhysicalPoolSize().X, GetPhysicalPoolSize().Y); // 1. Gather up all physical pages that are allocated FRDGBufferRef PagesForHZBIndirectArgsRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(2U * 4U), TEXT("Shadow.Virtual.PagesForHZBIndirectArgs")); // NOTE: Total allocated pages since the shader outputs separate entries for static/dynamic pages FRDGBufferRef PhysicalPagesForHZBRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(int32), GetTotalAllocatedPhysicalPages() + 1), TEXT("Shadow.Virtual.PhysicalPagesForHZB")); // 1. Clear the indirect args buffer (note 2x args) AddClearIndirectDispatchArgs1DPass(GraphBuilder, PagesForHZBIndirectArgsRDG, 2U); // 2. Filter the relevant physical pages and set up the indirect args { FSelectPagesForHZBCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); PassParameters->PhysicalPageMetaData = GraphBuilder.CreateSRV(PhysicalPageMetaDataRDG); PassParameters->OutPagesForHZBIndirectArgsBuffer = GraphBuilder.CreateUAV(PagesForHZBIndirectArgsRDG); PassParameters->OutPhysicalPagesForHZB = GraphBuilder.CreateUAV(PhysicalPagesForHZBRDG); PassParameters->DirtyPageFlags = GraphBuilder.CreateSRV(DirtyPageFlagsRDG); PassParameters->bFirstBuildThisFrame = !bHZBBuiltThisFrame; PassParameters->bForceFullHZBUpdate = CVarShadowsVirtualForceFullHZBUpdate.GetValueOnRenderThread(); FSelectPagesForHZBCS::FPermutationDomain PermutationVector; SetStatsArgsAndPermutation(GraphBuilder, StatsBufferRDG, PassParameters, PermutationVector); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("SelectPagesForHZB"), ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(UniformParameters.MaxPhysicalPages, FSelectPagesForHZBCS::DefaultCSGroupX), 1, 1) ); } // Clear the dirty flags (for subsequent render passes). AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(DirtyPageFlagsRDG), 0); bHZBBuiltThisFrame = true; { FVirtualSmBuildHZBPerPageCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); for (int32 DestMip = 0; DestMip < FVirtualSmBuildHZBPerPageCS::HZBLevelsBase; DestMip++) { PassParameters->FurthestHZBOutput[DestMip] = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(HZBPhysical, DestMip)); } PassParameters->PhysicalPagePool = PhysicalPagePoolRDG; PassParameters->PhysicalPagePoolSampler = TStaticSamplerState::GetRHI(); PassParameters->IndirectArgs = PagesForHZBIndirectArgsRDG; PassParameters->PhysicalPagesForHZB = GraphBuilder.CreateSRV(PhysicalPagesForHZBRDG); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("BuildHZBPerPage"), ComputeShader, PassParameters, PassParameters->IndirectArgs, 0 ); } { FVirtualSmBBuildHZBPerPageTopCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->VirtualShadowMap = GetUniformBuffer(GraphBuilder); uint32 StartDestMip = FVirtualSmBuildHZBPerPageCS::HZBLevelsBase; for (int32 DestMip = 0; DestMip < FVirtualSmBBuildHZBPerPageTopCS::HZBLevelsTop; DestMip++) { PassParameters->FurthestHZBOutput[DestMip] = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(HZBPhysical, StartDestMip + DestMip)); } FIntPoint SrcSize = FIntPoint::DivideAndRoundUp(FIntPoint(HZBPhysical->Desc.GetSize().X, HZBPhysical->Desc.GetSize().Y), 1 << int32(StartDestMip - 1)); PassParameters->InvHzbInputSize = FVector2f(1.0f / SrcSize.X, 1.0f / SrcSize.Y);; PassParameters->ParentTextureMip = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::CreateForMipLevel(HZBPhysical, StartDestMip - 1)); PassParameters->ParentTextureMipSampler = TStaticSamplerState::GetRHI(); PassParameters->IndirectArgs = PagesForHZBIndirectArgsRDG; PassParameters->PhysicalPagesForHZB = GraphBuilder.CreateSRV(PhysicalPagesForHZBRDG); auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("BuildHZBPerPageTop"), ComputeShader, PassParameters, PassParameters->IndirectArgs, // NOTE: offset 4 to get second set of args in the buffer. 4U * sizeof(uint32) ); } } uint32 FVirtualShadowMapArray::AddRenderViews(const TSharedPtr& Clipmap, float LODScaleFactor, bool bSetHZBParams, bool bUpdateHZBMetaData, bool bClampToNearPlane, TArray &OutVirtualShadowViews) { // TODO: Decide if this sort of logic belongs here or in Nanite (as with the mip level view expansion logic) // We're eventually going to want to snap/quantize these rectangles/positions somewhat so probably don't want it // entirely within Nanite, but likely makes sense to have some sort of "multi-viewport" notion in Nanite that can // handle both this and mips. // NOTE: There's still the additional VSM view logic that runs on top of this in Nanite too (see CullRasterize variant) Nanite::FPackedViewParams BaseParams; BaseParams.ViewRect = FIntRect(0, 0, FVirtualShadowMap::VirtualMaxResolutionXY, FVirtualShadowMap::VirtualMaxResolutionXY); BaseParams.HZBTestViewRect = BaseParams.ViewRect; BaseParams.RasterContextSize = GetPhysicalPoolSize(); BaseParams.LODScaleFactor = LODScaleFactor; BaseParams.PrevTargetLayerIndex = INDEX_NONE; BaseParams.TargetMipLevel = 0; BaseParams.TargetMipCount = 1; // No mips for clipmaps BaseParams.Flags = bClampToNearPlane ? 0u : NANITE_VIEW_FLAG_NEAR_CLIP; for (int32 ClipmapLevelIndex = 0; ClipmapLevelIndex < Clipmap->GetLevelCount(); ++ClipmapLevelIndex) { FVirtualShadowMap* VirtualShadowMap = Clipmap->GetVirtualShadowMap(ClipmapLevelIndex); Nanite::FPackedViewParams Params = BaseParams; Params.TargetLayerIndex = VirtualShadowMap->ID; Params.ViewMatrices = Clipmap->GetViewMatrices(ClipmapLevelIndex); Params.PrevTargetLayerIndex = INDEX_NONE; Params.PrevViewMatrices = Params.ViewMatrices; // TODO: Clean this up - could be stored in a single structure for the whole clipmap int32 HZBKey = Clipmap->GetHZBKey(ClipmapLevelIndex); if (bSetHZBParams) { CacheManager->SetHZBViewParams(HZBKey, Params); } // If we're going to generate a new HZB this frame, save the associated metadata if (bUpdateHZBMetaData) { FVirtualShadowMapHZBMetadata& HZBMeta = HZBMetadata.FindOrAdd(HZBKey); HZBMeta.TargetLayerIndex = Params.TargetLayerIndex; HZBMeta.ViewMatrices = Params.ViewMatrices; HZBMeta.ViewRect = Params.ViewRect; } Nanite::FPackedView View = Nanite::CreatePackedView(Params); OutVirtualShadowViews.Add(View); // Mark that we rendered to this VSM for caching purposes if (VirtualShadowMap->VirtualShadowMapCacheEntry) { VirtualShadowMap->VirtualShadowMapCacheEntry->MarkRendered(); } } return uint32(Clipmap->GetLevelCount()); } uint32 FVirtualShadowMapArray::AddRenderViews(const FProjectedShadowInfo* ProjectedShadowInfo, float LODScaleFactor, bool bSetHZBParams, bool bUpdateHZBMetaData, bool bClampToNearPlane, TArray& OutVirtualShadowViews) { Nanite::FPackedViewParams BaseParams; BaseParams.ViewRect = ProjectedShadowInfo->GetOuterViewRect(); BaseParams.HZBTestViewRect = BaseParams.ViewRect; BaseParams.RasterContextSize = GetPhysicalPoolSize(); BaseParams.LODScaleFactor = LODScaleFactor; BaseParams.PrevTargetLayerIndex = INDEX_NONE; BaseParams.TargetMipLevel = 0; BaseParams.TargetMipCount = FVirtualShadowMap::MaxMipLevels; BaseParams.Flags = bClampToNearPlane ? 0u : NANITE_VIEW_FLAG_NEAR_CLIP; int32 NumMaps = ProjectedShadowInfo->bOnePassPointLightShadow ? 6 : 1; for (int32 Index = 0; Index < NumMaps; ++Index) { FVirtualShadowMap* VirtualShadowMap = ProjectedShadowInfo->VirtualShadowMaps[Index]; Nanite::FPackedViewParams Params = BaseParams; Params.TargetLayerIndex = VirtualShadowMap->ID; Params.ViewMatrices = ProjectedShadowInfo->GetShadowDepthRenderingViewMatrices(Index, true); int32 HZBKey = ProjectedShadowInfo->GetLightSceneInfo().Id + (Index << 24); if (bSetHZBParams) { CacheManager->SetHZBViewParams(HZBKey, Params); } // If we're going to generate a new HZB this frame, save the associated metadata if (bUpdateHZBMetaData) { FVirtualShadowMapHZBMetadata& HZBMeta = HZBMetadata.FindOrAdd(HZBKey); HZBMeta.TargetLayerIndex = Params.TargetLayerIndex; HZBMeta.ViewMatrices = Params.ViewMatrices; HZBMeta.ViewRect = Params.ViewRect; } OutVirtualShadowViews.Add(Nanite::CreatePackedView(Params)); if (VirtualShadowMap->VirtualShadowMapCacheEntry) { VirtualShadowMap->VirtualShadowMapCacheEntry->MarkRendered(); } } return uint32(NumMaps); } void FVirtualShadowMapArray::AddVisualizePass(FRDGBuilder& GraphBuilder, const FViewInfo& View, int32 ViewIndex, FScreenPassTexture Output) { #if !UE_BUILD_SHIPPING if (!IsAllocated() || DebugVisualizationOutput.IsEmpty()) { return; } const FVirtualShadowMapVisualizationData& VisualizationData = GetVirtualShadowMapVisualizationData(); if (VisualizationData.IsActive() && VisualizeLight.IsValid()) { FCopyRectPS::FParameters* Parameters = GraphBuilder.AllocParameters(); Parameters->InputTexture = DebugVisualizationOutput[ViewIndex]; Parameters->InputSampler = TStaticSamplerState::GetRHI(); Parameters->RenderTargets[0] = FRenderTargetBinding(Output.Texture, ERenderTargetLoadAction::ENoAction); TShaderMapRef PixelShader(View.ShaderMap); FScreenPassTextureViewport InputViewport(DebugVisualizationOutput[ViewIndex]->Desc.Extent); FScreenPassTextureViewport OutputViewport(Output); // See CVarVisualizeLayout documentation const int32 VisualizeLayout = CVarVisualizeLayout.GetValueOnRenderThread(); if (VisualizeLayout == 1) // Thumbnail { const int32 TileWidth = View.UnscaledViewRect.Width() / 3; const int32 TileHeight = View.UnscaledViewRect.Height() / 3; OutputViewport.Rect.Max = OutputViewport.Rect.Min + FIntPoint(TileWidth, TileHeight); } else if (VisualizeLayout == 2) // Split screen { InputViewport.Rect.Max.X = InputViewport.Rect.Min.X + (InputViewport.Rect.Width() / 2); OutputViewport.Rect.Max.X = OutputViewport.Rect.Min.X + (OutputViewport.Rect.Width() / 2); } // Use separate input and output viewports w/ bilinear sampling to properly support dynamic resolution scaling AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("DrawTexture"), View, OutputViewport, InputViewport, PixelShader, Parameters, EScreenPassDrawFlags::None); // Visualization light name { FScreenPassRenderTarget OutputTarget(Output.Texture, View.UnscaledViewRect, ERenderTargetLoadAction::ELoad); AddDrawCanvasPass(GraphBuilder, RDG_EVENT_NAME("Labels"), View, OutputTarget, [&VisualizeLight=VisualizeLight, &OutputViewport=OutputViewport](FCanvas& Canvas) { const FLinearColor LabelColor(1, 1, 0); Canvas.DrawShadowedString( OutputViewport.Rect.Min.X + 8, OutputViewport.Rect.Max.Y - 19, *VisualizeLight.GetLightName(), GetStatsFont(), LabelColor); }); } } #endif }