#if (SHADERPASS != SHADERPASS_DEPTHONLY) && (SHADERPASS != SHADERPASS_DBUFFER_PROJECTOR) && (SHADERPASS != SHADERPASS_DBUFFER_MESH) && (SHADERPASS != SHADERPASS_FORWARD_EMISSIVE_PROJECTOR) && (SHADERPASS != SHADERPASS_FORWARD_EMISSIVE_MESH) && (SHADERPASS != SHADERPASS_DECAL_SCREEN_SPACE_PROJECTOR) && (SHADERPASS != SHADERPASS_DECAL_SCREEN_SPACE_MESH) && (SHADERPASS != SHADERPASS_DECAL_GBUFFER_PROJECTOR) && (SHADERPASS != SHADERPASS_DECAL_GBUFFER_MESH)
#error SHADERPASS_is_not_correctly_define
#endif

#if !defined(SHADERPASS)
#error SHADERPASS_is_not_define
#endif

#if (SHADERPASS == SHADERPASS_DBUFFER_PROJECTOR) || (SHADERPASS == SHADERPASS_FORWARD_EMISSIVE_PROJECTOR) || (SHADERPASS == SHADERPASS_DECAL_SCREEN_SPACE_PROJECTOR) || (SHADERPASS == SHADERPASS_DECAL_GBUFFER_PROJECTOR)
#define DECAL_PROJECTOR
#endif

#if (SHADERPASS == SHADERPASS_DBUFFER_MESH) || (SHADERPASS == SHADERPASS_FORWARD_EMISSIVE_MESH) || (SHADERPASS == SHADERPASS_DECAL_SCREEN_SPACE_MESH) || (SHADERPASS == SHADERPASS_DECAL_GBUFFER_MESH)
#define DECAL_MESH
#endif

#if (SHADERPASS == SHADERPASS_DBUFFER_PROJECTOR) || (SHADERPASS == SHADERPASS_DBUFFER_MESH)
#define DECAL_DBUFFER
#endif

#if (SHADERPASS == SHADERPASS_DECAL_SCREEN_SPACE_PROJECTOR) || (SHADERPASS == SHADERPASS_DECAL_SCREEN_SPACE_MESH)
#define DECAL_SCREEN_SPACE
#endif

#if (SHADERPASS == SHADERPASS_DECAL_GBUFFER_PROJECTOR) || (SHADERPASS == SHADERPASS_DECAL_GBUFFER_MESH)
#define DECAL_GBUFFER
#endif

#if (SHADERPASS == SHADERPASS_FORWARD_EMISSIVE_PROJECTOR) || (SHADERPASS == SHADERPASS_FORWARD_EMISSIVE_MESH)
#define DECAL_FORWARD_EMISSIVE
#endif

#if ((!defined(_MATERIAL_AFFECTS_NORMAL) && defined(_MATERIAL_AFFECTS_ALBEDO)) || (defined(_MATERIAL_AFFECTS_NORMAL) && defined(_MATERIAL_AFFECTS_NORMAL_BLEND))) && (defined(DECAL_SCREEN_SPACE) || defined(DECAL_GBUFFER))
#define DECAL_RECONSTRUCT_NORMAL
#elif defined(DECAL_ANGLE_FADE)
#define DECAL_LOAD_NORMAL
#endif

#ifdef _DECAL_LAYERS
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareRenderingLayerTexture.hlsl"
#endif

#if defined(DECAL_LOAD_NORMAL)
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareNormalsTexture.hlsl"
#endif

#if defined(DECAL_PROJECTOR) || defined(DECAL_RECONSTRUCT_NORMAL)
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
#endif

#ifdef DECAL_MESH
#include "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/DecalMeshBiasTypeEnum.cs.hlsl"
#endif
#ifdef DECAL_RECONSTRUCT_NORMAL
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/NormalReconstruction.hlsl"
#endif

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/FoveatedRendering.hlsl"

void MeshDecalsPositionZBias(inout Varyings input)
{
#if UNITY_REVERSED_Z
    input.positionCS.z -= _DecalMeshDepthBias;
#else
    input.positionCS.z += _DecalMeshDepthBias;
#endif
}

void InitializeInputData(Varyings input, float3 positionWS, half3 normalWS, half3 viewDirectionWS, out InputData inputData)
{
    inputData = (InputData)0;

    inputData.positionWS = positionWS;
    inputData.normalWS = normalWS;
    inputData.viewDirectionWS = viewDirectionWS;

#if defined(VARYINGS_NEED_SHADOW_COORD) && defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
    inputData.shadowCoord = input.shadowCoord;
#elif defined(MAIN_LIGHT_CALCULATE_SHADOWS)
    inputData.shadowCoord = TransformWorldToShadowCoord(positionWS);
#else
    inputData.shadowCoord = float4(0, 0, 0, 0);
#endif

#ifdef VARYINGS_NEED_FOG_AND_VERTEX_LIGHT
    inputData.fogCoord = InitializeInputDataFog(float4(positionWS, 1.0), input.fogFactorAndVertexLight.x);
    inputData.vertexLighting = input.fogFactorAndVertexLight.yzw;
#endif

#if defined(_SCREEN_SPACE_IRRADIANCE)
    inputData.bakedGI = SAMPLE_GI(_ScreenSpaceIrradiance, input.positionCS.xy);
#elif defined(VARYINGS_NEED_DYNAMIC_LIGHTMAP_UV) && defined(DYNAMICLIGHTMAP_ON)
    inputData.bakedGI = SAMPLE_GI(input.staticLightmapUV, input.dynamicLightmapUV.xy, half3(input.sh), normalWS);
    #if defined(VARYINGS_NEED_STATIC_LIGHTMAP_UV)
    inputData.shadowMask = SAMPLE_SHADOWMASK(input.staticLightmapUV);
    #endif
#elif defined(VARYINGS_NEED_STATIC_LIGHTMAP_UV)
#if !defined(LIGHTMAP_ON) && (defined(PROBE_VOLUMES_L1) || defined(PROBE_VOLUMES_L2))
    inputData.bakedGI = SAMPLE_GI(input.sh,
        GetAbsolutePositionWS(inputData.positionWS),
        inputData.normalWS,
        inputData.viewDirectionWS,
        input.positionCS.xy,
        input.probeOcclusion,
        inputData.shadowMask);
#else
    inputData.bakedGI = SAMPLE_GI(input.staticLightmapUV, half3(input.sh), normalWS);
    #if defined(VARYINGS_NEED_STATIC_LIGHTMAP_UV)
    inputData.shadowMask = SAMPLE_SHADOWMASK(input.staticLightmapUV);
    #endif
#endif
#endif

    #if defined(DEBUG_DISPLAY)
    #if defined(VARYINGS_NEED_DYNAMIC_LIGHTMAP_UV) && defined(DYNAMICLIGHTMAP_ON)
    inputData.dynamicLightmapUV = input.dynamicLightmapUV.xy;
    #endif
    #if defined(VARYINGS_NEED_STATIC_LIGHTMAP_UV) && defined(LIGHTMAP_ON)
    inputData.staticLightmapUV = input.staticLightmapUV;
    #elif defined(VARYINGS_NEED_SH)
    inputData.vertexSH = input.sh;
    #endif
    #if defined(USE_APV_PROBE_OCCLUSION)
    inputData.probeOcclusion = input.probeOcclusion;
    #endif
    #endif

    inputData.normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(input.positionCS);
}

void GetSurface(DecalSurfaceData decalSurfaceData, inout SurfaceData surfaceData)
{
    surfaceData.albedo = decalSurfaceData.baseColor.rgb;
    surfaceData.metallic = saturate(decalSurfaceData.metallic);
    surfaceData.specular = 0;
    surfaceData.smoothness = saturate(decalSurfaceData.smoothness);
    surfaceData.occlusion = decalSurfaceData.occlusion;
    surfaceData.emission = decalSurfaceData.emissive;
    surfaceData.alpha = saturate(decalSurfaceData.baseColor.w);
    surfaceData.clearCoatMask = 0;
    surfaceData.clearCoatSmoothness = 1;
}

PackedVaryings Vert(Attributes inputMesh)
{
    Varyings output = (Varyings)0;
    UNITY_SETUP_INSTANCE_ID(inputMesh);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
#ifdef DECAL_MESH
    if (_DecalMeshBiasType == DECALMESHDEPTHBIASTYPE_VIEW_BIAS) // TODO: Check performance of branch
    {
        float3 viewDirectionOS = GetObjectSpaceNormalizeViewDir(inputMesh.positionOS);
        inputMesh.positionOS += viewDirectionOS * (_DecalMeshViewBias);
    }
    output = BuildVaryings(inputMesh);
    if (_DecalMeshBiasType == DECALMESHDEPTHBIASTYPE_DEPTH_BIAS) // TODO: Check performance of branch
    {
        MeshDecalsPositionZBias(output);
    }
#else
    output = BuildVaryings(inputMesh);
#endif

#if defined(VARYINGS_NEED_STATIC_LIGHTMAP_UV)
    OUTPUT_LIGHTMAP_UV(inputMesh.uv1, unity_LightmapST, output.staticLightmapUV);
#endif

#if defined(VARYINGS_NEED_DYNAMIC_LIGHTMAP_UV) && defined(DYNAMICLIGHTMAP_ON)
    output.dynamicLightmapUV.xy = inputMesh.uv2.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
#endif

#if defined(VARYINGS_NEED_SH) && !defined(LIGHTMAP_ON)
    output.sh = float3(SampleSHVertex(half3(output.normalWS)));
#endif

    PackedVaryings packedOutput = (PackedVaryings)0;
    packedOutput = PackVaryings(output);

    return packedOutput;
}

void Frag(PackedVaryings packedInput,
#if defined(DECAL_DBUFFER)
    OUTPUT_DBUFFER(outDBuffer)
#elif defined(DECAL_SCREEN_SPACE)
    out half4 outColor : SV_Target0
#elif defined(DECAL_GBUFFER)
    out GBufferFragOutput fragmentOutput
#elif defined(DECAL_FORWARD_EMISSIVE)
    out half4 outEmissive : SV_Target0
#elif defined(SCENEPICKINGPASS)
    out float4 outColor : SV_Target0
#else
#error SHADERPASS_is_not_correctly_define
#endif
)
{
#ifdef SCENEPICKINGPASS
    outColor = _SelectionID;
#else
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(packedInput);
    UNITY_SETUP_INSTANCE_ID(packedInput);
    Varyings input = UnpackVaryings(packedInput);

    half angleFadeFactor = 1.0;

    float2 positionCS = input.positionCS.xy;

    // Only screen space needs flip logic, other passes do not setup needed properties so we skip here
#if defined(DECAL_SCREEN_SPACE)
    TransformScreenUV(positionCS, _ScreenSize.y);
#endif

#ifdef _DECAL_LAYERS
#ifdef _RENDER_PASS_ENABLED
    uint surfaceRenderingLayer = LOAD_FRAMEBUFFER_X_INPUT(GBUFFER4, positionCS.xy).r;
#else
    uint surfaceRenderingLayer = LoadSceneRenderingLayer(positionCS.xy);
#endif
    uint projectorRenderingLayer = asuint(UNITY_ACCESS_INSTANCED_PROP(Decal, _DecalLayerMaskFromDecal));
    // This is simple trick to clip if there is no matching layers
    // Part (surfaceRenderingLayer & projectorRenderingLayer) will produce 0, 1, 2 ...
    // Finally we subtract with small value to remmap only zero to negative value
    clip((surfaceRenderingLayer & projectorRenderingLayer) - 0.1);
#endif

#if defined(DECAL_PROJECTOR)
#if UNITY_REVERSED_Z
#if _RENDER_PASS_ENABLED
    float depth = LOAD_FRAMEBUFFER_X_INPUT(GBUFFER3, positionCS.xy).x;
#else
    float depth = LoadSceneDepth(positionCS.xy);
#endif
#else
#if _RENDER_PASS_ENABLED
    float depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, LOAD_FRAMEBUFFER_X_INPUT(GBUFFER3, positionCS.xy));
#else
    // Adjust z to match NDC for OpenGL
    float depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, LoadSceneDepth(positionCS.xy));
#endif
#endif
#endif

#if defined(DECAL_RECONSTRUCT_NORMAL)
    #if defined(_RENDER_PASS_ENABLED)
        half3 normalWS = half3(ReconstructNormalDerivative(input.positionCS.xy, LOAD_FRAMEBUFFER_X_INPUT(GBUFFER3, positionCS.xy).x));
    #elif defined(_DECAL_NORMAL_BLEND_HIGH)
        half3 normalWS = half3(ReconstructNormalTap9(positionCS.xy));
    #elif defined(_DECAL_NORMAL_BLEND_MEDIUM)
        half3 normalWS = half3(ReconstructNormalTap5(positionCS.xy));
    #else
        half3 normalWS = half3(ReconstructNormalDerivative(input.positionCS.xy));
    #endif
#elif defined(DECAL_LOAD_NORMAL)
    #if defined(_RENDER_PASS_ENABLED)
    half3 normalWS = normalize(LOAD_FRAMEBUFFER_X_INPUT(GBUFFER2, positionCS.xy).rgb);
    #else
    half3 normalWS = normalize(LoadSceneNormals(positionCS.xy).rgb);
    #endif
#endif

    float2 positionSS = FoveatedRemapNonUniformToLinearCS(input.positionCS.xy) * _ScreenSize.zw;

#ifdef DECAL_PROJECTOR
    float3 positionWS = ComputeWorldSpacePosition(positionSS, depth, UNITY_MATRIX_I_VP);

#ifdef VARYINGS_NEED_POSITION_WS
    input.positionWS = positionWS;
#endif

    // Transform from relative world space to decal space (DS) to clip the decal
    float3 positionDS = TransformWorldToObject(positionWS);
    positionDS = positionDS * float3(1.0, -1.0, 1.0);

    // call clip as early as possible
    float clipValue = 0.5 - Max3(abs(positionDS).x, abs(positionDS).y, abs(positionDS).z);
    clip(clipValue);

    float2 texCoord = positionDS.xz + float2(0.5, 0.5);
#ifdef VARYINGS_NEED_TEXCOORD0
    input.texCoord0.xy = texCoord;
#endif
#ifdef VARYINGS_NEED_TEXCOORD1
    input.texCoord1.xy = texCoord;
#endif
#ifdef VARYINGS_NEED_TEXCOORD2
    input.texCoord2.xy = texCoord;
#endif
#ifdef VARYINGS_NEED_TEXCOORD3
    input.texCoord3.xy = texCoord;
#endif
#ifdef VARYINGS_NEED_TEXCOORD4
    input.texCoord4.xy = texCoord;
#endif
#ifdef VARYINGS_NEED_TEXCOORD5
    input.texCoord5.xy = texCoord;
#endif
#ifdef VARYINGS_NEED_TEXCOORD6
    input.texCoord6.xy = texCoord;
#endif
#ifdef VARYINGS_NEED_TEXCOORD7
    input.texCoord7.xy = texCoord;
#endif

#ifdef DECAL_ANGLE_FADE
    // Check if this decal projector require angle fading
    half4x4 normalToWorld = UNITY_ACCESS_INSTANCED_PROP(Decal, _NormalToWorld);
    half2 angleFade = half2(normalToWorld[1][3], normalToWorld[2][3]);

    if (angleFade.y < 0.0f) // if angle fade is enabled
    {
        half3 decalNormal = half3(normalToWorld[0].z, normalToWorld[1].z, normalToWorld[2].z);
        half dotAngle = dot(normalWS, decalNormal);
        // See equation in DecalCreateDrawCallSystem.cs - simplified to a madd mul add here
        angleFadeFactor = saturate(angleFade.x + angleFade.y * (dotAngle * (dotAngle - 2.0)));
    }
#endif


#else // Decal mesh
    float3 positionWS = input.positionWS.xyz;
#endif

    half3 viewDirectionWS = GetWorldSpaceNormalizeViewDir(positionWS);

    DecalSurfaceData surfaceData;
    GetSurfaceData(input, input.positionCS, angleFadeFactor, surfaceData);

#if defined(DECAL_DBUFFER)
    ENCODE_INTO_DBUFFER(surfaceData, outDBuffer);
#elif defined(DECAL_SCREEN_SPACE)

    // Blend normal with background
#ifdef DECAL_RECONSTRUCT_NORMAL
    surfaceData.normalWS.xyz = normalize(lerp(normalWS.xyz, surfaceData.normalWS.xyz, surfaceData.normalWS.w));
#endif

    InputData inputData;
    InitializeInputData(input, positionWS, surfaceData.normalWS.xyz, viewDirectionWS, inputData);

    SurfaceData surface = (SurfaceData)0;
    GetSurface(surfaceData, surface);

    half4 color = UniversalFragmentPBR(inputData, surface);

    color.rgb = MixFog(color.rgb, inputData.fogCoord);

    outColor = color;
#elif defined(DECAL_GBUFFER)

    // Need to reconstruct normal here for inputData.bakedGI, but also save off surfaceData.normalWS for correct GBuffer blending
    half3 normalToPack = surfaceData.normalWS.xyz;
#ifdef DECAL_RECONSTRUCT_NORMAL
    surfaceData.normalWS.xyz = normalize(lerp(normalWS.xyz, surfaceData.normalWS.xyz, surfaceData.normalWS.w));
#endif

    InputData inputData;
    InitializeInputData(input, positionWS, surfaceData.normalWS.xyz, viewDirectionWS, inputData);

    SurfaceData surface = (SurfaceData)0;
    GetSurface(surfaceData, surface);

    BRDFData brdfData;
    InitializeBRDFData(surface.albedo, surface.metallic, 0, surface.smoothness, surface.alpha, brdfData);

    // Skip GI if there is no abledo
#ifdef _MATERIAL_AFFECTS_ALBEDO
    Light mainLight = GetMainLight(inputData.shadowCoord, inputData.positionWS, inputData.shadowMask);
    MixRealtimeAndBakedGI(mainLight, surfaceData.normalWS.xyz, inputData.bakedGI, inputData.shadowMask);
    half3 color = GlobalIllumination(brdfData, inputData.bakedGI, surface.occlusion, surfaceData.normalWS.xyz, inputData.viewDirectionWS);
#else
    half3 color = 0;
#endif

    // We can not use usual GBuffer functions (etc. BRDFDataToGbuffer) as we use alpha for blending
    #pragma warning (disable : 3578) // The output value isn't completely initialized.
    half3 packedNormalWS = PackGBufferNormal(normalToPack);
    fragmentOutput.gBuffer0 = half4(surfaceData.baseColor.rgb, surfaceData.baseColor.a);
    fragmentOutput.gBuffer1 = 0;
    fragmentOutput.gBuffer2 = half4(packedNormalWS, surfaceData.normalWS.a);
    fragmentOutput.color = half4(surfaceData.emissive + color, surfaceData.baseColor.a);

#if defined(GBUFFER_FEATURE_SHADOWMASK)
    fragmentOutput.shadowMask = inputData.shadowMask; // will have unity_ProbesOcclusion value if subtractive lighting is used (baked)
#endif

    #pragma warning (default : 3578) // Restore output value isn't completely initialized.

#elif defined(DECAL_FORWARD_EMISSIVE)
    // Emissive need to be pre-exposed
    outEmissive.rgb = surfaceData.emissive * GetCurrentExposureMultiplier();
    outEmissive.a = surfaceData.baseColor.a;
#else
#endif
#endif
}