// Copyright Epic Games, Inc. All Rights Reserved. #include "SceneRendering.h" #include "DataDrivenShaderPlatformInfo.h" #include "BasePassRendering.h" #include "PixelShaderUtils.h" #include "MobileBasePassRendering.h" #include "RendererPrivateUtils.h" #include "GlobalRenderResources.h" #include "ScenePrivate.h" #include "LightRendering.h" #include "LightFunctionRendering.h" #include "Materials/MaterialRenderProxy.h" static bool CompileShaderPermutationsForMobileLocalLightsBuffer(const FStaticShaderPlatform Platform) { return !IsMobileDeferredShadingEnabled(Platform) && IsMobilePlatform(Platform) && MobileLocalLightsBufferEnabled(Platform) && MobileUsesFullDepthPrepass(Platform); } const int32 GLocalLightPrepassTileSizeX = 8; class FLocalLightBufferCS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FLocalLightBufferCS); public: SHADER_USE_PARAMETER_STRUCT(FLocalLightBufferCS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, RWTileInfo) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FForwardLightUniformParameters, ForwardLightStruct) SHADER_PARAMETER(FIntPoint, GroupSize) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return CompileShaderPermutationsForMobileLocalLightsBuffer(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FForwardLightingParameters::ModifyCompilationEnvironment(Parameters.Platform, OutEnvironment); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), GLocalLightPrepassTileSizeX); OutEnvironment.SetDefine(TEXT("COMPUTE_SHADER"), 1); OutEnvironment.SetDefine(TEXT("LIGHT_FUNCTION"), 0); } }; IMPLEMENT_GLOBAL_SHADER(FLocalLightBufferCS, "/Engine/Private/MobileLocalLightsBuffer.usf", "MainCS", SF_Compute); class FLocalLightBufferVS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FLocalLightBufferVS); SHADER_USE_PARAMETER_STRUCT(FLocalLightBufferVS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, TileInfo) SHADER_PARAMETER(int32, LightGridPixelSize) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return CompileShaderPermutationsForMobileLocalLightsBuffer(Parameters.Platform); } }; IMPLEMENT_GLOBAL_SHADER(FLocalLightBufferVS, "/Engine/Private/MobileLocalLightsBuffer.usf", "MainVS", SF_Vertex); class FLocalLightBufferPS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FLocalLightBufferPS); SHADER_USE_PARAMETER_STRUCT(FLocalLightBufferPS, FGlobalShader) class FGenerateLightFunctionDepthStencil : SHADER_PERMUTATION_BOOL("GENERATE_LIGHT_FUNCTION_DEPTH_STENCIL"); using FPermutationDomain = TShaderPermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FForwardLightUniformParameters, ForwardLightStruct) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return CompileShaderPermutationsForMobileLocalLightsBuffer(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FForwardLightingParameters::ModifyCompilationEnvironment(Parameters.Platform, OutEnvironment); OutEnvironment.SetDefine(TEXT("LIGHT_FUNCTION"), 0); OutEnvironment.SetRenderTargetOutputFormat(0, PF_FloatR11G11B10); OutEnvironment.SetRenderTargetOutputFormat(1, PF_B8G8R8A8); } }; IMPLEMENT_GLOBAL_SHADER(FLocalLightBufferPS, "/Engine/Private/MobileLocalLightsBuffer.usf", "Main", SF_Pixel); BEGIN_SHADER_PARAMETER_STRUCT(FLocalLightBufferPrepassParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FLocalLightBufferVS::FParameters, VS) SHADER_PARAMETER_STRUCT_INCLUDE(FLocalLightBufferPS::FParameters, PS) SHADER_PARAMETER_STRUCT_INCLUDE(FSceneTextureShaderParameters, SceneTextures) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() /** * A pixel shader for projecting a light function onto the scene and blending with the color from the previously calculated lights in the prepass. */ class FMobileLocalLightFunctionPS : public FMaterialShader { DECLARE_SHADER_TYPE(FMobileLocalLightFunctionPS, Material); SHADER_USE_PARAMETER_STRUCT_WITH_LEGACY_BASE(FMobileLocalLightFunctionPS, FMaterialShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(FMatrix44f, SvPositionToLight) SHADER_PARAMETER(FVector4f, LightFunctionParameters) SHADER_PARAMETER(FVector2f, LightFunctionParameters2) SHADER_PARAMETER(FVector3f, CameraRelativeLightPosition) SHADER_PARAMETER_STRUCT_REF(FDeferredLightUniformStruct, DeferredLightUniforms) END_SHADER_PARAMETER_STRUCT() public: /** * Makes sure only shaders for materials that are explicitly flagged * as 'UsedAsLightFunction' in the Material Editor gets compiled into * the shader cache. **/ static bool ShouldCompilePermutation(const FMaterialShaderPermutationParameters& Parameters) { return Parameters.MaterialParameters.MaterialDomain == MD_LightFunction && CompileShaderPermutationsForMobileLocalLightsBuffer(Parameters.Platform); } static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FMaterialShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("LIGHT_FUNCTION"), 1); OutEnvironment.SetDefine(TEXT("COMPUTE_SHADER"), 0); OutEnvironment.SetDefine(TEXT("SUBSTRATE_INLINE_SHADING"), 1); OutEnvironment.SetRenderTargetOutputFormat(0, PF_FloatR11G11B10); OutEnvironment.SetRenderTargetOutputFormat(1, PF_B8G8R8A8); } void SetParameters(FRHIBatchedShaderParameters& BatchedParameters, const FViewInfo& View, const FMaterialRenderProxy* MaterialProxy, const FMaterial& Material) { FMaterialShader::SetViewParameters(BatchedParameters, View, View.ViewUniformBuffer); FMaterialShader::SetParameters(BatchedParameters, MaterialProxy, Material, View); } FParameters GetParameters(const FViewInfo& View, const FLightSceneInfo* LightSceneInfo, const float FadeAlpha) { FParameters PS; LightFunctionSvPositionToLightTransform(PS.SvPositionToLight, View, *LightSceneInfo); PS.CameraRelativeLightPosition = GetCamRelativeLightPosition(View.ViewMatrices, *LightSceneInfo); PS.LightFunctionParameters = FLightFunctionSharedParameters::GetLightFunctionSharedParameters(LightSceneInfo, FadeAlpha); PS.LightFunctionParameters2 = FVector2f(LightSceneInfo->Proxy->GetLightFunctionFadeDistance(), LightSceneInfo->Proxy->GetLightFunctionDisabledBrightness()); PS.DeferredLightUniforms = TUniformBufferRef::CreateUniformBufferImmediate(GetDeferredLightParameters(View, *LightSceneInfo), EUniformBufferUsage::UniformBuffer_SingleFrame); return PS; } }; IMPLEMENT_MATERIAL_SHADER_TYPE(, FMobileLocalLightFunctionPS, TEXT("/Engine/Private/MobileLocalLightsBuffer.usf"), TEXT("MainLightFunction"), SF_Pixel); BEGIN_SHADER_PARAMETER_STRUCT(FLocalLightFunctionParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FSceneTextureShaderParameters, SceneTextures) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() static bool TryGetLightFunctionShaders(FMaterialRenderProxy const*& OutMaterialProxy, FMaterial const*& OutMaterial, FMaterialShaders& OutShaders) { while (OutMaterialProxy) { OutMaterial = OutMaterialProxy->GetMaterialNoFallback(ERHIFeatureLevel::ES3_1); if (OutMaterial && OutMaterial->IsLightFunction()) { FMaterialShaderTypes ShaderTypes; ShaderTypes.AddShaderType(); if (OutMaterial->TryGetShaders(ShaderTypes, nullptr, OutShaders)) { return true; } } OutMaterialProxy = OutMaterialProxy->GetFallback(ERHIFeatureLevel::ES3_1); } return false; } template using LightFunctionMainPassDepthStencilState = TStaticDepthStencilState< DepthWrite, CF_Always, true, CF_Always, SO_Keep, SO_Keep, SO_Replace, true, CF_Always, SO_Keep, SO_Keep, SO_Replace, 0, STENCIL_MOBILE_LIGHTFUNCTION_MASK>; template using LightFunctionMaterialPassDepthStencilState = TStaticDepthStencilState< false, CompareFunction, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep, STENCIL_MOBILE_LIGHTFUNCTION_MASK, 0>; void FMobileSceneRenderer::RenderMobileLocalLightsBuffer(FRDGBuilder& GraphBuilder, FSceneTextures& SceneTextures, const FSortedLightSetSceneInfo& SortedLights) { if (!CompileShaderPermutationsForMobileLocalLightsBuffer(ShaderPlatform) || IsMobileDeferredShadingEnabled(ShaderPlatform)) { return; } RDG_EVENT_SCOPE(GraphBuilder, "RenderMobileLocalLightsBuffer"); QUICK_SCOPE_CYCLE_COUNTER(STAT_RenderMobileLocalLightsBuffer); static const auto LightGridPixelSizeCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Forward.LightGridPixelSize")); check(LightGridPixelSizeCVar != nullptr); int32 LightGridPixelSize = LightGridPixelSizeCVar->GetInt(); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { FViewInfo& View = Views[ViewIndex]; bool bHasNoLocalLights = (!View.ForwardLightingResources.ForwardLightUniformParameters) || (View.ForwardLightingResources.ForwardLightUniformParameters->NumLocalLights == 0); if (!View.ShouldRenderView() || bHasNoLocalLights) { continue; } const FIntPoint GroupSize( FMath::DivideAndRoundUp(View.ViewRect.Size().X, LightGridPixelSize), FMath::DivideAndRoundUp(View.ViewRect.Size().Y, LightGridPixelSize)); FRDGBufferRef TileInfoBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 2 * GroupSize.X * GroupSize.Y), TEXT("TileInfoBuffer")); FRDGBufferUAVRef TileInfoBufferUAV = GraphBuilder.CreateUAV(TileInfoBuffer, PF_R32_UINT); FRDGBufferSRVRef TileInfoBufferSRV = GraphBuilder.CreateSRV(TileInfoBuffer, PF_R32_UINT); { auto* PassParameters = GraphBuilder.AllocParameters(); PassParameters->RWTileInfo = TileInfoBufferUAV; PassParameters->ForwardLightStruct = View.ForwardLightingResources.ForwardLightUniformBuffer; PassParameters->GroupSize = GroupSize; auto ComputeShader = View.ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("RenderMobileLocalLights_TiledInfoCS"), ERDGPassFlags::Compute, ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(GroupSize.Y * GroupSize.X, GLocalLightPrepassTileSizeX), 1, 1)); } bool bRenderLightFunctions = false; { // Check if there are any light functions for (const FSortedLightSceneInfo& SortedLightSceneInfo : SortedLights.SortedLights) { // Directional lights are currently not supported if (SortedLightSceneInfo.SortKey.Fields.LightType == LightType_Directional) { continue; } if (!SortedLightSceneInfo.SortKey.Fields.bLightFunction) { continue; } bRenderLightFunctions = true; break; } } { FLocalLightBufferPrepassParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->RenderTargets[0] = FRenderTargetBinding(SceneTextures.MobileLocalLightTextureA, ERenderTargetLoadAction::EClear); PassParameters->RenderTargets[1] = FRenderTargetBinding(SceneTextures.MobileLocalLightTextureB, ERenderTargetLoadAction::EClear); bool bRestoreDepthBuffer = false; if (bRenderLightFunctions) { if (MobileRequiresSceneDepthAux(ShaderPlatform)) { // In this case the depth buffer is typically memoryless and not preserved, so we restore it from SceneDepthAux PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(SceneTextures.Depth.Resolve, ERenderTargetLoadAction::EClear, ERenderTargetLoadAction::EClear, FExclusiveDepthStencil::DepthWrite_StencilWrite); bRestoreDepthBuffer = true; } else { // If the main depth buffer is not memoryless we rely on STENCIL_MOBILE_LIGHTFUNCTION_MASK being cleared to 0 already const ERenderTargetLoadAction LoadAction = EnumHasAnyFlags(SceneTextures.Depth.Resolve->Desc.Flags, TexCreate_Memoryless) ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad; PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(SceneTextures.Depth.Resolve, LoadAction, LoadAction, FExclusiveDepthStencil::DepthRead_StencilWrite); } } PassParameters->SceneTextures = SceneTextures.GetSceneTextureShaderParameters(View.FeatureLevel); PassParameters->VS.View = GetShaderBinding(View.ViewUniformBuffer); PassParameters->VS.TileInfo = TileInfoBufferSRV; PassParameters->VS.LightGridPixelSize = LightGridPixelSize; PassParameters->PS.ForwardLightStruct = View.ForwardLightingResources.ForwardLightUniformBuffer; PassParameters->PS.View = GetShaderBinding(View.ViewUniformBuffer); auto VertexShader = View.ShaderMap->GetShader(); FLocalLightBufferPS::FPermutationDomain PermutationVectorPS; PermutationVectorPS.Set(bRenderLightFunctions); auto PixelShader = View.ShaderMap->GetShader(PermutationVectorPS); GraphBuilder.AddPass( RDG_EVENT_NAME("RenderMobileLocalLightsBuffer Prepass"), PassParameters, ERDGPassFlags::Raster, [PassParameters, VertexShader, PixelShader, &View, GroupSize, &SortedLights, bRenderLightFunctions, bRestoreDepthBuffer](FRHICommandList& RHICmdList) { FGraphicsPipelineStateInitializer GraphicsPSOInit; RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0.0f, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1.0f); GraphicsPSOInit.RasterizerState = TStaticRasterizerState::GetRHI(); GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI(); // The main non-lightfunction pass creates a stencil mask of the lit area // Later lightfunction passes use it for stencil test to avoid redundant PS execution uint32 StencilRef = 0; if (bRenderLightFunctions) { StencilRef = STENCIL_MOBILE_LIGHTFUNCTION_MASK; if (bRestoreDepthBuffer) { GraphicsPSOInit.DepthStencilState = LightFunctionMainPassDepthStencilState::GetRHI(); } else { GraphicsPSOInit.DepthStencilState = LightFunctionMainPassDepthStencilState::GetRHI(); } } else { GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); } GraphicsPSOInit.PrimitiveType = PT_TriangleList; GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GTileVertexDeclaration.VertexDeclarationRHI; GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader(); GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, StencilRef); SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), PassParameters->VS); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), PassParameters->PS); RHICmdList.SetStreamSource(0, GetOneTileQuadVertexBuffer(), 0); RHICmdList.DrawIndexedPrimitive(GetOneTileQuadIndexBuffer(), 0, 0, 4, 0, 2, GroupSize.X * GroupSize.Y); if (!bRenderLightFunctions) { return; } // Draws a pass for each visible light with a light function and blends on top of the color texture // generated by last pass for (const FSortedLightSceneInfo& SortedLightSceneInfo : SortedLights.SortedLights) { // Directional lights are currently not supported if (SortedLightSceneInfo.SortKey.Fields.LightType == LightType_Directional) { continue; } if (!SortedLightSceneInfo.SortKey.Fields.bLightFunction) { continue; } const FLightSceneInfo* const LightSceneInfo = SortedLightSceneInfo.LightSceneInfo; const FSphere LightBounds = LightSceneInfo->Proxy->GetBoundingSphere(); const float FadeAlpha = GetLightFunctionFadeFraction(View, LightBounds); // Don't draw the light function if it has completely faded out if (FadeAlpha < 1.0f / 256.0f) { continue; } FMaterialShaders MaterialShaders; const FMaterialRenderProxy* MaterialProxyForRendering = LightSceneInfo->Proxy->GetLightFunctionMaterial(); const FMaterial* MaterialForRendering = nullptr; if (!TryGetLightFunctionShaders(MaterialProxyForRendering, MaterialForRendering, MaterialShaders)) { UE_LOG(LogTemp, Error, TEXT("Light function shader for light %d not found."), LightSceneInfo->Id); continue; } FDeferredLightVS::FPermutationDomain PermutationVectorVS; PermutationVectorVS.Set(true); TShaderMapRef LightFunctionVertexShader(View.ShaderMap, PermutationVectorVS); FDeferredLightVS::FParameters ParametersVS = FDeferredLightVS::GetParameters(View, LightSceneInfo, false); ParametersVS.View = TUniformBufferBinding(View.ViewUniformBuffer, EUniformBufferBindingFlags::Shader); TShaderRef LightFunctionPixelShader; MaterialShaders.TryGetPixelShader(LightFunctionPixelShader); FMobileLocalLightFunctionPS::FParameters ParametersPS = LightFunctionPixelShader->GetParameters(View, LightSceneInfo, FadeAlpha); // Directional RT was already generated in main pass GraphicsPSOInit.BlendState = TStaticBlendState::GetRHI(); GraphicsPSOInit.PrimitiveType = PT_TriangleList; GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4(); GraphicsPSOInit.BoundShaderState.VertexShaderRHI = LightFunctionVertexShader.GetVertexShader(); GraphicsPSOInit.BoundShaderState.PixelShaderRHI = LightFunctionPixelShader.GetPixelShader(); if (((FVector)View.ViewMatrices.GetViewOrigin() - LightBounds.Center).SizeSquared() < FMath::Square(LightBounds.W * 1.05f + View.NearClippingDistance * 2.0f)) { // Render backfaces with depth tests disabled since the camera is inside (or close to inside) the light function geometry GraphicsPSOInit.RasterizerState = View.bReverseCulling ? TStaticRasterizerState::GetRHI() : TStaticRasterizerState::GetRHI(); GraphicsPSOInit.DepthStencilState = LightFunctionMaterialPassDepthStencilState::GetRHI(); } else { // Render frontfaces with depth test so that less pixel are shaded GraphicsPSOInit.RasterizerState = View.bReverseCulling ? TStaticRasterizerState::GetRHI() : TStaticRasterizerState::GetRHI(); GraphicsPSOInit.DepthStencilState = LightFunctionMaterialPassDepthStencilState::GetRHI(); } // Set the light's scissor rectangle. LightSceneInfo->Proxy->SetScissorRect(RHICmdList, View, View.ViewRect); SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, StencilRef); SetShaderParameters(RHICmdList, LightFunctionVertexShader, LightFunctionVertexShader.GetVertexShader(), ParametersVS); SetShaderParametersMixedPS(RHICmdList, LightFunctionPixelShader, ParametersPS, View, MaterialProxyForRendering, *MaterialForRendering); // Project the light function using a sphere around the light if (SortedLightSceneInfo.SortKey.Fields.LightType == LightType_Spot) { StencilingGeometry::DrawCone(RHICmdList); } else { StencilingGeometry::DrawSphere(RHICmdList); } } }); } } }