// Copyright Epic Games, Inc. All Rights Reserved. #include "Nanite.h" #include "RHI.h" #include "SceneUtils.h" #include "ScenePrivate.h" #include "PixelShaderUtils.h" #include "ShaderPrintParameters.h" #include "ShadowRendering.h" #include "Rendering/NaniteStreamingManager.h" #include "NaniteVisualizationData.h" #include "NaniteShading.h" #include "VirtualShadowMaps/VirtualShadowMapCacheManager.h" #define NUM_PRINT_STATS_PASSES 6 int32 GNaniteShowStats = 0; FAutoConsoleVariableRef CVarNaniteShowStats( TEXT("r.Nanite.ShowStats"), GNaniteShowStats, TEXT("") ); FString GNaniteStatsFilter; FAutoConsoleVariableRef CVarNaniteStatsFilter( TEXT("r.Nanite.StatsFilter"), GNaniteStatsFilter, TEXT("Sets the name of a specific Nanite raster pass to capture stats from - enumerate available filters with `NaniteStats List` cmd."), ECVF_RenderThreadSafe ); extern TAutoConsoleVariable CVarNaniteShadows; bool bNaniteListStatFilters = false; void NaniteStatsFilterExec(const TCHAR* Cmd, FOutputDevice& Ar) { check(IsInGameThread()); FlushRenderingCommands(); uint32 ParameterCount = 0; // Convenience, force on Nanite debug/stats and also shader printing. GNaniteShowStats = 1; ShaderPrint::SetEnabled(true); // parse parameters for (;;) { FString Parameter = FParse::Token(Cmd, 0); if (Parameter.IsEmpty()) { break; } if (Parameter == TEXT("list")) { // We don't have access to all the scene data here, so we'll set a flag // to print out every filter comparison for the next frame. bNaniteListStatFilters = true; } else if (Parameter == TEXT("primary")) { // Empty filter name denotes the primary raster view. ParameterCount = 0; break; } else if (Parameter == TEXT("off")) { // disable stats GNaniteShowStats = 0; return; } else { GNaniteStatsFilter = Parameter; } ++ParameterCount; } if (!ParameterCount) { // Default to showing stats for the primary view GNaniteStatsFilter.Empty(); } } class FEmitShadowMapPS : public FNaniteGlobalShader { DECLARE_GLOBAL_SHADER(FEmitShadowMapPS); SHADER_USE_PARAMETER_STRUCT(FEmitShadowMapPS, FNaniteGlobalShader); class FDepthOutputTypeDim : SHADER_PERMUTATION_INT("DEPTH_OUTPUT_TYPE", 3); using FPermutationDomain = TShaderPermutationDomain< FDepthOutputTypeDim >; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportNanite(Parameters.Platform); } static void ModifyCompilationEnvironment( const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment ) { FNaniteGlobalShader::ModifyCompilationEnvironment( Parameters, OutEnvironment ); FVirtualShadowMapArray::SetShaderDefines( OutEnvironment ); } BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER( FIntPoint, SourceOffset ) SHADER_PARAMETER( float, ViewToClip22 ) SHADER_PARAMETER( float, DepthBias ) SHADER_PARAMETER( uint32, ShadowMapID ) SHADER_PARAMETER_RDG_TEXTURE( Texture2D, DepthBuffer ) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FEmitShadowMapPS, "/Engine/Private/Nanite/NaniteEmitShadow.usf", "EmitShadowMapPS", SF_Pixel); BEGIN_SHADER_PARAMETER_STRUCT(FEmitCubemapShadowParameters, ) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, DepthBuffer) SHADER_PARAMETER( uint32, CubemapFaceIndex ) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() class FEmitCubemapShadowVS : public FNaniteGlobalShader { DECLARE_GLOBAL_SHADER(FEmitCubemapShadowVS); SHADER_USE_PARAMETER_STRUCT(FEmitCubemapShadowVS, FNaniteGlobalShader); class FUseGeometryShader : SHADER_PERMUTATION_BOOL("USE_GEOMETRY_SHADER"); using FPermutationDomain = TShaderPermutationDomain; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { if (DoesPlatformSupportNanite(Parameters.Platform)) { FPermutationDomain PermutationVector(Parameters.PermutationId); const bool bSupportsVertexShaderLayer = FDataDrivenShaderPlatformInfo::GetSupportsVertexShaderLayer(Parameters.Platform); return (PermutationVector.Get() || bSupportsVertexShaderLayer); } return false; } static void ModifyCompilationEnvironment( const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment ) { FNaniteGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); FVirtualShadowMapArray::SetShaderDefines(OutEnvironment); FPermutationDomain PermutationVector(Parameters.PermutationId); if (PermutationVector.Get()) { OutEnvironment.CompilerFlags.Add(CFLAG_VertexToGeometryShader); } } using FParameters = FEmitCubemapShadowParameters; }; IMPLEMENT_GLOBAL_SHADER(FEmitCubemapShadowVS, "/Engine/Private/Nanite/NaniteEmitShadow.usf", "EmitCubemapShadowVS", SF_Vertex); class FEmitCubemapShadowGS : public FNaniteGlobalShader { DECLARE_GLOBAL_SHADER(FEmitCubemapShadowGS); SHADER_USE_PARAMETER_STRUCT(FEmitCubemapShadowGS, FNaniteGlobalShader); using FParameters = FEmitCubemapShadowParameters; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return RHISupportsGeometryShaders(Parameters.Platform) && DoesPlatformSupportNanite(Parameters.Platform); } static void ModifyCompilationEnvironment( const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment ) { FNaniteGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); FVirtualShadowMapArray::SetShaderDefines(OutEnvironment); OutEnvironment.SetDefine(TEXT("USE_GEOMETRY_SHADER"), 1); } }; IMPLEMENT_GLOBAL_SHADER(FEmitCubemapShadowGS, "/Engine/Private/Nanite/NaniteEmitShadow.usf", "EmitCubemapShadowGS", SF_Geometry); class FEmitCubemapShadowPS : public FNaniteGlobalShader { DECLARE_GLOBAL_SHADER(FEmitCubemapShadowPS); SHADER_USE_PARAMETER_STRUCT(FEmitCubemapShadowPS, FNaniteGlobalShader); static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportNanite(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FNaniteGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); FVirtualShadowMapArray::SetShaderDefines(OutEnvironment); } using FParameters = FEmitCubemapShadowParameters; }; IMPLEMENT_GLOBAL_SHADER(FEmitCubemapShadowPS, "/Engine/Private/Nanite/NaniteEmitShadow.usf", "EmitCubemapShadowPS", SF_Pixel); // Gather shading stats class FCalculateShadingStatsCS : public FNaniteGlobalShader { DECLARE_GLOBAL_SHADER(FCalculateShadingStatsCS); SHADER_USE_PARAMETER_STRUCT(FCalculateShadingStatsCS, FNaniteGlobalShader); static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportNanite(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FNaniteGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("SHADER_CALCULATE_STATS"), 1); } BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(uint32, RenderFlags) SHADER_PARAMETER(uint32, NumShadingBins) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, OutStatsBuffer) SHADER_PARAMETER_RDG_BUFFER_SRV(ByteAddressBuffer, ShadingBinData) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, ShadingBinStats) SHADER_PARAMETER_RDG_BUFFER_SRV(ByteAddressBuffer, ShadingBinArgs) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FCalculateShadingStatsCS, "/Engine/Private/Nanite/NanitePrintStats.usf", "CalculateShadingStats", SF_Compute); class FPrintStatsCS : public FNaniteGlobalShader { DECLARE_GLOBAL_SHADER( FPrintStatsCS ); SHADER_USE_PARAMETER_STRUCT( FPrintStatsCS, FNaniteGlobalShader); class FTwoPassCullingDim : SHADER_PERMUTATION_BOOL( "TWO_PASS_CULLING" ); class FPassDim : SHADER_PERMUTATION_INT("PRINT_PASS", NUM_PRINT_STATS_PASSES); using FPermutationDomain = TShaderPermutationDomain; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return DoesPlatformSupportNanite(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FNaniteGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("SHADER_PRINT_STATS"), 1); } BEGIN_SHADER_PARAMETER_STRUCT( FParameters, ) SHADER_PARAMETER(uint32, PackedClusterSize) SHADER_PARAMETER(uint32, RenderFlags) SHADER_PARAMETER(uint32, DebugFlags) SHADER_PARAMETER_STRUCT_INCLUDE( ShaderPrint::FShaderParameters, ShaderPrintStruct ) SHADER_PARAMETER_RDG_BUFFER_SRV( StructuredBuffer, InStatsBuffer ) SHADER_PARAMETER_RDG_BUFFER_SRV( Buffer< uint >, MainPassRasterizeArgsSWHW ) SHADER_PARAMETER_RDG_BUFFER_SRV( Buffer< uint >, PostPassRasterizeArgsSWHW ) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FPrintStatsCS, "/Engine/Private/Nanite/NanitePrintStats.usf", "PrintStats", SF_Compute); namespace Nanite { FString GetFilterNameForLight(const FLightSceneProxy* LightProxy) { FString LightFilterName; { FString FullLevelName = LightProxy->GetLevelName().ToString(); const int32 LastSlashIndex = FullLevelName.Find(TEXT("/"), ESearchCase::CaseSensitive, ESearchDir::FromEnd); if (LastSlashIndex != INDEX_NONE) { FullLevelName.MidInline(LastSlashIndex + 1, FullLevelName.Len() - (LastSlashIndex + 1), EAllowShrinking::No); } LightFilterName = FullLevelName + TEXT(".") + LightProxy->GetOwnerNameOrLabel(); } return LightFilterName; } bool IsStatFilterActive(const FString& FilterName) { if (GNaniteShowStats == 0) { // Stats are disabled, do nothing. return false; } return (GNaniteStatsFilter == FilterName); } bool IsStatFilterActiveForLight(const FLightSceneProxy* LightProxy) { if (GNaniteShowStats == 0) { return false; } const FString LightFilterName = Nanite::GetFilterNameForLight(LightProxy); return IsStatFilterActive(LightFilterName); } void ListStatFilters(FSceneRenderer* SceneRenderer) { if (bNaniteListStatFilters && SceneRenderer) { UE_LOG(LogNanite, Warning, TEXT("** Available Filters **")); // Primary view is always available. UE_LOG(LogNanite, Warning, TEXT("Primary")); const bool bListShadows = CVarNaniteShadows.GetValueOnRenderThread() != 0; // Virtual shadow maps if (bListShadows) { if (SceneRenderer->VirtualShadowMapArray.GetNumShadowMaps() > 0) { UE_LOG(LogNanite, Warning, TEXT("VirtualShadowMaps")); } } // Shadow map atlases if (bListShadows) { const auto& ShadowMapAtlases = SceneRenderer->SortedShadowsForShadowDepthPass.ShadowMapAtlases; for (int32 AtlasIndex = 0; AtlasIndex < ShadowMapAtlases.Num(); AtlasIndex++) { UE_LOG(LogNanite, Warning, TEXT("ShadowAtlas%d"), AtlasIndex); } } // Shadow cube maps if (bListShadows) { const auto& ShadowCubeMaps = SceneRenderer->SortedShadowsForShadowDepthPass.ShadowMapCubemaps; for (int32 CubemapIndex = 0; CubemapIndex < ShadowCubeMaps.Num(); CubemapIndex++) { const FSortedShadowMapAtlas& ShadowMap = ShadowCubeMaps[CubemapIndex]; check(ShadowMap.Shadows.Num() == 1); FProjectedShadowInfo* ProjectedShadowInfo = ShadowMap.Shadows[0]; if (ProjectedShadowInfo->bNaniteGeometry && ProjectedShadowInfo->CacheMode != SDCM_MovablePrimitivesOnly) { // Get the base light filter name. FString CubeFilterName = GetFilterNameForLight(ProjectedShadowInfo->GetLightSceneInfo().Proxy); CubeFilterName.Append(TEXT("_Face_")); for (int32 CubemapFaceIndex = 0; CubemapFaceIndex < 6; CubemapFaceIndex++) { FString CubeFaceFilterName = CubeFilterName; CubeFaceFilterName.AppendInt(CubemapFaceIndex); UE_LOG(LogNanite, Warning, TEXT("Shadow Cube Map: %s"), *CubeFaceFilterName); } } } } } bNaniteListStatFilters = false; } void ExtractShadingDebug( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FShadeBinning& ShadeBinning, uint32 NumShadingBins ) { LLM_SCOPE_BYTAG(Nanite); const FNaniteVisualizationData& VisualizationData = GetNaniteVisualizationData(); if (VisualizationData.IsActive()) { FRDGBufferRef ShadingBinData = nullptr; if (ShadeBinning.ShadingBinData) { ShadingBinData = ShadeBinning.ShadingBinData; } else { ShadingBinData = GSystemTextures.GetDefaultByteAddressBuffer(GraphBuilder, 4u); } FRDGTextureRef FastTileVis = nullptr; if (ShadeBinning.FastClearVisualize) { FastTileVis = ShadeBinning.FastClearVisualize; } else { FastTileVis = GSystemTextures.GetZeroUIntDummy(GraphBuilder); } Nanite::GGlobalResources.GetShadingBinDataBufferRef() = GraphBuilder.ConvertToExternalBuffer(ShadingBinData); Nanite::GGlobalResources.GetFastClearTileVisRef() = GraphBuilder.ConvertToExternalTexture(FastTileVis); } if (GNaniteShowStats != 0 && Nanite::GGlobalResources.GetStatsBufferRef()) { FCalculateShadingStatsCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->RenderFlags = Nanite::GGlobalResources.StatsRenderFlags; PassParameters->OutStatsBuffer = GraphBuilder.CreateUAV(GraphBuilder.RegisterExternalBuffer(Nanite::GGlobalResources.GetStatsBufferRef())); PassParameters->NumShadingBins = NumShadingBins; PassParameters->ShadingBinData = GraphBuilder.CreateSRV(ShadeBinning.ShadingBinData); PassParameters->ShadingBinStats = GraphBuilder.CreateSRV(ShadeBinning.ShadingBinStats); PassParameters->ShadingBinArgs = GraphBuilder.CreateSRV(ShadeBinning.ShadingBinArgs); auto ComputeShader = View.ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("CalculateShadingStatsArgs"), ComputeShader, PassParameters, FIntVector(1, 1, 1) ); } } void PrintStats( FRDGBuilder& GraphBuilder, const FViewInfo& View) { LLM_SCOPE_BYTAG(Nanite); // Print stats if (GNaniteShowStats != 0 && Nanite::GGlobalResources.GetStatsBufferRef()) { auto& MainPassBuffers = Nanite::GGlobalResources.GetMainPassBuffers(); auto& PostPassBuffers = Nanite::GGlobalResources.GetPostPassBuffers(); // Shader compilers have a hard time handling the size of the full PrintStats shader, so we split it into multiple passes. // This reduces the FXC compilation time from 2-3 minutes to just a few seconds. for (uint32 Pass = 0; Pass < NUM_PRINT_STATS_PASSES; Pass++) { FPrintStatsCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); ShaderPrint::SetParameters(GraphBuilder, View.ShaderPrintData, PassParameters->ShaderPrintStruct); PassParameters->PackedClusterSize = sizeof(Nanite::FPackedCluster); PassParameters->RenderFlags = Nanite::GGlobalResources.StatsRenderFlags; PassParameters->DebugFlags = Nanite::GGlobalResources.StatsDebugFlags; PassParameters->InStatsBuffer = GraphBuilder.CreateSRV(GraphBuilder.RegisterExternalBuffer(Nanite::GGlobalResources.GetStatsBufferRef())); PassParameters->MainPassRasterizeArgsSWHW = GraphBuilder.CreateSRV(GraphBuilder.RegisterExternalBuffer(MainPassBuffers.StatsRasterizeArgsSWHWBuffer)); const bool bTwoPass = (PostPassBuffers.StatsRasterizeArgsSWHWBuffer != nullptr); if( bTwoPass ) { PassParameters->PostPassRasterizeArgsSWHW = GraphBuilder.CreateSRV( GraphBuilder.RegisterExternalBuffer( PostPassBuffers.StatsRasterizeArgsSWHWBuffer ) ); } FPrintStatsCS::FPermutationDomain PermutationVector; PermutationVector.Set(bTwoPass); PermutationVector.Set(Pass); auto ComputeShader = View.ShaderMap->GetShader(PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("Print Stats"), ComputeShader, PassParameters, FIntVector(1, 1, 1) ); } } } void EmitShadowMap( FRDGBuilder& GraphBuilder, const FSharedContext& SharedContext, const FRasterContext& RasterContext, const FRDGTextureRef DepthBuffer, const FIntRect& SourceRect, const FIntPoint DestOrigin, const FMatrix& ProjectionMatrix, float DepthBias, bool bOrtho ) { LLM_SCOPE_BYTAG(Nanite); auto* PassParameters = GraphBuilder.AllocParameters< FEmitShadowMapPS::FParameters >(); PassParameters->SourceOffset = SourceRect.Min - DestOrigin; PassParameters->ViewToClip22 = ProjectionMatrix.M[2][2]; PassParameters->DepthBias = DepthBias; PassParameters->DepthBuffer = RasterContext.DepthBuffer; PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding( DepthBuffer, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthWrite_StencilNop ); FEmitShadowMapPS::FPermutationDomain PermutationVector; PermutationVector.Set< FEmitShadowMapPS::FDepthOutputTypeDim >( bOrtho ? 1 : 2 ); auto PixelShader = SharedContext.ShaderMap->GetShader< FEmitShadowMapPS >( PermutationVector ); FIntRect DestRect; DestRect.Min = DestOrigin; DestRect.Max = DestRect.Min + SourceRect.Max - SourceRect.Min; FPixelShaderUtils::AddFullscreenPass( GraphBuilder, SharedContext.ShaderMap, RDG_EVENT_NAME("EmitShadowMap"), PixelShader, PassParameters, DestRect, nullptr, nullptr, TStaticDepthStencilState::GetRHI() ); } void EmitCubemapShadow( FRDGBuilder& GraphBuilder, const FSharedContext& SharedContext, const FRasterContext& RasterContext, const FRDGTextureRef CubemapDepthBuffer, const FIntRect& ViewRect, uint32 CubemapFaceIndex, bool bUseGeometryShader ) { LLM_SCOPE_BYTAG(Nanite); FEmitCubemapShadowVS::FPermutationDomain VertexPermutationVector; VertexPermutationVector.Set(bUseGeometryShader); TShaderMapRef VertexShader(SharedContext.ShaderMap, VertexPermutationVector); TShaderRef GeometryShader; TShaderMapRef PixelShader(SharedContext.ShaderMap); // VS output of RT array index on D3D11 requires a caps bit. Use GS fallback if set. if (bUseGeometryShader) { GeometryShader = TShaderMapRef(SharedContext.ShaderMap); } FEmitCubemapShadowParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->CubemapFaceIndex = CubemapFaceIndex; PassParameters->DepthBuffer = RasterContext.DepthBuffer; PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(CubemapDepthBuffer, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthWrite_StencilNop); GraphBuilder.AddPass( RDG_EVENT_NAME("Emit Cubemap Shadow"), PassParameters, ERDGPassFlags::Raster, [PassParameters, VertexShader, GeometryShader, PixelShader, ViewRect, CubemapFaceIndex](FRDGAsyncTask, FRHICommandList& RHICmdList) { RHICmdList.SetViewport(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, ViewRect.Max.X, ViewRect.Max.Y, 1.0f); FGraphicsPipelineStateInitializer GraphicsPSOInit; RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI(); GraphicsPSOInit.RasterizerState = TStaticRasterizerState::GetRHI(); // NOTE: Shadow cubemaps are reverse Z GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); GraphicsPSOInit.PrimitiveType = PT_TriangleList; GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GEmptyVertexDeclaration.VertexDeclarationRHI; GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader(); GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); if (GeometryShader.GetGeometryShader()) { GraphicsPSOInit.BoundShaderState.SetGeometryShader(GeometryShader.GetGeometryShader()); } SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), *PassParameters); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters); if (GeometryShader.GetGeometryShader()) { SetShaderParameters(RHICmdList, GeometryShader, GeometryShader.GetGeometryShader(), *PassParameters); } RHICmdList.SetStreamSource(0, nullptr, 0); RHICmdList.DrawPrimitive(0, 1, 1); } ); } } // namespace Nanite