using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.ShaderGraph;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine.UIElements;
using UnityEditor.ShaderGraph.Legacy;
using UnityEngine.Assertions;
using static UnityEditor.Rendering.Universal.ShaderGraph.SubShaderUtils;
using UnityEngine.Rendering.Universal;
using static Unity.Rendering.Universal.ShaderUtils;

namespace UnityEditor.Rendering.Universal.ShaderGraph
{
    partial class UniversalTerrainLitSubTarget : UniversalSubTarget, ITerrainSubTarget
    {
        private static readonly GUID kSourceCodeGuid = new GUID("ea07e558bc2741a5ba7f1d5470eb242b"); // UniversalTerrainLitSubTarget.cs

        public override int latestVersion => 1;

        [SerializeField]
        bool m_EnableInstancedPerPixelNormal = true;

        [SerializeField]
        NormalDropOffSpace m_NormalDropOffSpace = NormalDropOffSpace.Tangent;

        [SerializeField]
        bool m_BlendModePreserveSpecular = true;

        protected override ShaderID shaderID => ShaderID.SG_TerrainLit;

        public bool enableInstancedPerPixelNormal
        {
            get => m_EnableInstancedPerPixelNormal;
            set => m_EnableInstancedPerPixelNormal = value;
        }

        public NormalDropOffSpace normalDropOffSpace
        {
            get => m_NormalDropOffSpace;
            set => m_NormalDropOffSpace = value;
        }

        public bool blendModePreserveSpecular
        {
            get => m_BlendModePreserveSpecular;
            set => m_BlendModePreserveSpecular = value;
        }

        public UniversalTerrainLitSubTarget()
        {
            displayName = "TerrainLit";
        }

        public override bool IsActive() => true;

        public override void Setup(ref TargetSetupContext context)
        {
            context.AddAssetDependency(kSourceCodeGuid, AssetCollection.Flags.SourceDependency);
            base.Setup(ref context);

            var universalRPType = typeof(UniversalRenderPipelineAsset);
            if (!context.HasCustomEditorForRenderPipeline(universalRPType))
                context.AddCustomEditorForRenderPipeline(typeof(ShaderGraphTerrainLitGUI).FullName, universalRPType);

            // terrain shaders are always opaque, so these values are hardcoded to not inherit from the Universal Target
            var renderTypeOpaque = RenderType.Opaque.ToString();
            var renderQueue = target.alphaClip?RenderQueue.AlphaTest.ToString():RenderQueue.Geometry.ToString();

            context.AddSubShader(PostProcessSubShader(TerrainSubShaders.LitComputeDotsSubShader(target, renderTypeOpaque, renderQueue, blendModePreserveSpecular)));
            context.AddSubShader(PostProcessSubShader(TerrainSubShaders.LitGLESSubShader(target, renderTypeOpaque, renderQueue, blendModePreserveSpecular)));

            context.AddSubShader(PostProcessSubShader(TerrainLitAddSubShaders.LitComputeDotsSubShader(target, renderTypeOpaque, renderQueue, blendModePreserveSpecular)));
            context.AddSubShader(PostProcessSubShader(TerrainLitAddSubShaders.LitGLESSubShader(target, renderTypeOpaque, renderQueue, blendModePreserveSpecular)));

            context.AddSubShader(PostProcessSubShader(TerrainLitBaseMapGenSubShaders.GenerateBaseMap(target, renderTypeOpaque, renderQueue, blendModePreserveSpecular)));
        }

        public override void ProcessPreviewMaterial(Material material)
        {
            // We always need these properties regardless of whether the material is allowed to override
            // Queue control & offset enable correct automatic render queue behavior
            // Control == 0 is automatic, 1 is user-specified render queue
            material.SetFloat(Property.QueueOffset, 0.0f);
            material.SetFloat(Property.QueueControl, (float)BaseShaderGUI.QueueControl.Auto);

            // call the full unlit material setup function
            ShaderGraphLitGUI.UpdateMaterial(material, MaterialUpdateType.CreatedNewMaterial);
        }

        public override void GetFields(ref TargetFieldContext context)
        {
            base.GetFields(ref context);

            // TerrainLit -- always controlled by subtarget
            context.AddField(UniversalFields.NormalDropOffOS, normalDropOffSpace == NormalDropOffSpace.Object);
            context.AddField(UniversalFields.NormalDropOffTS, normalDropOffSpace == NormalDropOffSpace.Tangent);
            context.AddField(UniversalFields.NormalDropOffWS, normalDropOffSpace == NormalDropOffSpace.World);
        }

        public override void GetActiveBlocks(ref TargetActiveBlockContext context)
        {
            context.AddBlock(BlockFields.SurfaceDescription.Smoothness);
            context.AddBlock(BlockFields.SurfaceDescription.NormalOS,    normalDropOffSpace == NormalDropOffSpace.Object);
            context.AddBlock(BlockFields.SurfaceDescription.NormalTS,    normalDropOffSpace == NormalDropOffSpace.Tangent);
            context.AddBlock(BlockFields.SurfaceDescription.NormalWS,    normalDropOffSpace == NormalDropOffSpace.World);
            context.AddBlock(BlockFields.SurfaceDescription.Emission);
            context.AddBlock(BlockFields.SurfaceDescription.Occlusion);

            // when the surface options are material controlled, we must show all of these blocks
            // when target controlled, we can cull the unnecessary blocks
            context.AddBlock(BlockFields.SurfaceDescription.Specular,    target.allowMaterialOverride);
            context.AddBlock(BlockFields.SurfaceDescription.Metallic);
            context.AddBlock(BlockFields.SurfaceDescription.Alpha, target.alphaClip);
            context.AddBlock(BlockFields.SurfaceDescription.AlphaClipThreshold, target.alphaClip);
        }

        public override void CollectShaderProperties(PropertyCollector collector, GenerationMode generationMode)
        {
            collector.AddShaderProperty(new BooleanShaderProperty
            {
                value = enableInstancedPerPixelNormal,
                hidden = false,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_EnableInstancedPerPixelNormal",
                displayName = "Enable Instanced per Pixel Normal",
            });
            collector.AddShaderProperty(new Texture2DShaderProperty
            {
                defaultType = Texture2DShaderProperty.DefaultType.White,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_TerrainHolesTexture",
                displayName = "Holes Map (RGB)",
            });

            collector.AddShaderProperty(new Texture2DShaderProperty
            {
                defaultType = Texture2DShaderProperty.DefaultType.White,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_DefaultWhiteTex",
                displayName = "DefaultWhiteTex",
            });

            collector.AddShaderProperty(new Texture2DShaderProperty
            {
                defaultType = Texture2DShaderProperty.DefaultType.NormalMap,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_NormalBlank",
                displayName = "Empty Normal Map",
            });
            collector.AddShaderProperty(new Texture2DShaderProperty
            {
                defaultType = Texture2DShaderProperty.DefaultType.Black,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_BlackTex",
                displayName = "Empty Texture",
            });
            collector.AddShaderProperty(new Texture2DShaderProperty
            {
                defaultType = Texture2DShaderProperty.DefaultType.White,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Splat0",
                displayName = "Layer 0 (R)",
                useTilingAndOffset = true,
            });
            collector.AddShaderProperty(new Texture2DShaderProperty
            {
                defaultType = Texture2DShaderProperty.DefaultType.White,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Splat1",
                displayName = "Layer 1 (G)",
                useTilingAndOffset = true,
            });
            collector.AddShaderProperty(new Texture2DShaderProperty
            {
                defaultType = Texture2DShaderProperty.DefaultType.White,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Splat2",
                displayName = "Layer 2 (B)",
                useTilingAndOffset = true,
            });
            collector.AddShaderProperty(new Texture2DShaderProperty
            {
                defaultType = Texture2DShaderProperty.DefaultType.White,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Splat3",
                displayName = "Layer 3 (A)",
                useTilingAndOffset = true,
            });
            collector.AddShaderProperty(new Texture2DShaderProperty
            {
                defaultType = Texture2DShaderProperty.DefaultType.Grey,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Mask3",
                displayName = "Layer 3 (A)",
                useTilingAndOffset = true,
            });
            collector.AddShaderProperty(new Texture2DShaderProperty
            {
                defaultType = Texture2DShaderProperty.DefaultType.Grey,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Mask2",
                displayName = "Layer 2 (B)",
                useTilingAndOffset = true,
            });
            collector.AddShaderProperty(new Texture2DShaderProperty
            {
                defaultType = Texture2DShaderProperty.DefaultType.Grey,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Mask1",
                displayName = "Layer 1 (G)",
                useTilingAndOffset = true,
            });
            collector.AddShaderProperty(new Texture2DShaderProperty
            {
                defaultType = Texture2DShaderProperty.DefaultType.Grey,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Mask0",
                displayName = "Layer 0 (R)",
                useTilingAndOffset = true,
            });
            collector.AddShaderProperty(new Vector1ShaderProperty
            {
                floatType = FloatType.Slider,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Metallic0",
                displayName = "Metallic 0",
            });
            collector.AddShaderProperty(new Vector1ShaderProperty
            {
                floatType = FloatType.Slider,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Metallic1",
                displayName = "Metallic 1",
            });
            collector.AddShaderProperty(new Vector1ShaderProperty
            {
                floatType = FloatType.Slider,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Metallic2",
                displayName = "Metallic 2",
            });
            collector.AddShaderProperty(new Vector1ShaderProperty
            {
                floatType = FloatType.Slider,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Metallic3",
                displayName = "Metallic 3",
            });
            collector.AddShaderProperty(new Vector1ShaderProperty
            {
                value = 1.0f,
                floatType = FloatType.Slider,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Smoothness0",
                displayName = "Smoothness 0",
            });
            collector.AddShaderProperty(new Vector1ShaderProperty
            {
                value = 1.0f,
                floatType = FloatType.Slider,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Smoothness1",
                displayName = "Smoothness 1",
            });
            collector.AddShaderProperty(new Vector1ShaderProperty
            {
                value = 1.0f,
                floatType = FloatType.Slider,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Smoothness2",
                displayName = "Smoothness 2",
            });
            collector.AddShaderProperty(new Vector1ShaderProperty
            {
                value = 1.0f,
                floatType = FloatType.Slider,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare,
                overrideReferenceName = "_Smoothness3",
                displayName = "Smoothness 3",
            });

            collector.AddShaderProperty(new Vector1ShaderProperty
            {
                floatType = FloatType.Default,
                value = 0.0f,
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.Global,
                overrideReferenceName = "_DstBlend",
                displayName = "DstBlend",
            });
        }

        public override void GetPropertiesGUI(ref TargetPropertyGUIContext context, Action onChange, Action<String> registerUndo)
        {
            context.AddProperty("Depth Write", new EnumField(ZWriteControl.Auto) { value = target.zWriteControl }, (evt) =>
            {
                if (Equals(target.zWriteControl, evt.newValue))
                    return;

                registerUndo("Change Depth Write Control");
                target.zWriteControl = (ZWriteControl)evt.newValue;
                onChange();
            });

            context.AddProperty("Depth Test", new EnumField(ZTestModeForUI.LEqual) { value = (ZTestModeForUI)target.zTestMode }, (evt) =>
            {
                if (Equals(target.zTestMode, evt.newValue))
                    return;

                registerUndo("Change Depth Test");
                target.zTestMode = (ZTestMode)evt.newValue;
                onChange();
            });

            context.AddProperty("Alpha Clipping", new Toggle() { value = target.alphaClip }, (evt) =>
            {
                if (Equals(target.alphaClip, evt.newValue))
                    return;

                registerUndo("Change Alpha Clip");
                target.alphaClip = evt.newValue;
                onChange();
            });

            context.AddProperty("Cast Shadows", new Toggle() { value = target.castShadows }, (evt) =>
            {
                if (Equals(target.castShadows, evt.newValue))
                    return;

                registerUndo("Change Cast Shadows");
                target.castShadows = evt.newValue;
                onChange();
            });

            context.AddProperty("Receive Shadows", new Toggle() {value = target.receiveShadows}, (evt) =>
            {
                if (Equals(target.receiveShadows, evt.newValue))
                    return;

                registerUndo("Change Receive Shadows");
                target.receiveShadows = evt.newValue;
                onChange();
            });

            context.AddProperty("Fragment Normal Space", new EnumField(NormalDropOffSpace.Tangent) { value = normalDropOffSpace }, (evt) =>
            {
                if (Equals(normalDropOffSpace, evt.newValue))
                    return;

                registerUndo("Change Fragment Normal Space");
                normalDropOffSpace = (NormalDropOffSpace)evt.newValue;
                onChange();
            });
        }

        protected override int ComputeMaterialNeedsUpdateHash()
        {
            int hash = base.ComputeMaterialNeedsUpdateHash();
            hash = hash * 23 + target.allowMaterialOverride.GetHashCode();
            return hash;
        }

        // this is a copy of ZTestMode, but hides the "Disabled" option, which is invalid
        enum ZTestModeForUI
        {
            Never = 1,
            Less = 2,
            Equal = 3,
            LEqual = 4,     // default for most rendering
            Greater = 5,
            NotEqual = 6,
            GEqual = 7,
            Always = 8,
        };

        internal override void OnAfterParentTargetDeserialized()
        {
            Assert.IsNotNull(target);

            if (this.sgVersion < latestVersion)
            {
                // Upgrade old incorrect Premultiplied blend into
                // equivalent Alpha + Preserve Specular blend mode.
                if (this.sgVersion < 1)
                {
                    if (target.alphaMode == AlphaMode.Premultiply)
                    {
                        target.alphaMode = AlphaMode.Alpha;
                        blendModePreserveSpecular = true;
                    }
                    else
                        blendModePreserveSpecular = false;
                }
                ChangeVersion(latestVersion);
            }
        }

        #region Template
        static class TerrainLitTemplate
        {
            public static readonly string kPassTemplate = "Packages/com.unity.render-pipelines.universal/Editor/Terrain/TerrainPass.template";
            public static readonly string[] kSharedTemplateDirectories;

            static TerrainLitTemplate()
            {
                kSharedTemplateDirectories = new string[UniversalTarget.kSharedTemplateDirectories.Length + 1];
                Array.Copy(UniversalTarget.kSharedTemplateDirectories, kSharedTemplateDirectories, UniversalTarget.kSharedTemplateDirectories.Length);
                kSharedTemplateDirectories[^1] = "Packages/com.unity.render-pipelines.universal/Editor/Terrain/";
            }
        }
        #endregion

        #region StructCollections
        static class TerrainStructFields
        {
            public struct Varyings
            {
                public static FieldDescriptor uvSplat01 = new FieldDescriptor(
                    StructFields.Varyings.name, "uvSplat01", "", ShaderValueType.Float4, "TEXCOORD1", preprocessor: "defined(UNIVERSAL_TERRAIN_SPLAT01)", subscriptOptions: StructFieldOptions.Optional);
                public static FieldDescriptor uvSplat23 = new FieldDescriptor(
                    StructFields.Varyings.name, "uvSplat23", "", ShaderValueType.Float4, "TEXCOORD2", preprocessor: "defined(UNIVERSAL_TERRAIN_SPLAT23)", subscriptOptions: StructFieldOptions.Optional);
                public static FieldDescriptor normalViewDir = new FieldDescriptor(
                    StructFields.Varyings.name, "normalViewDir", "", ShaderValueType.Float4, "TEXCOORD3", "defined(_NORMALMAP) && !defined(ENABLE_TERRAIN_PERPIXEL_NORMAL)", StructFieldOptions.Optional);
                public static FieldDescriptor tangentViewDir = new FieldDescriptor(
                    StructFields.Varyings.name, "tangentViewDir", "", ShaderValueType.Float4, "TEXCOORD4", "defined(_NORMALMAP) && !defined(ENABLE_TERRAIN_PERPIXEL_NORMAL)", StructFieldOptions.Optional);
                public static FieldDescriptor bitangentViewDir = new FieldDescriptor(
                    StructFields.Varyings.name, "bitangentViewDir", "", ShaderValueType.Float4, "TEXCOORD5", "defined(_NORMALMAP) && !defined(ENABLE_TERRAIN_PERPIXEL_NORMAL)", StructFieldOptions.Optional);
            }

            public struct SurfaceDescriptionInputs
            {
                public static FieldDescriptor uvSplat01 = new FieldDescriptor(
                    StructFields.SurfaceDescriptionInputs.name, "uvSplat01", "", ShaderValueType.Float4, "TEXCOORD1", "defined(UNIVERSAL_TERRAIN_SPLAT01)");
                public static FieldDescriptor uvSplat23 = new FieldDescriptor(
                    StructFields.SurfaceDescriptionInputs.name, "uvSplat23", "", ShaderValueType.Float4, "TEXCOORD2", "defined(UNIVERSAL_TERRAIN_SPLAT23)");
            }
        }

        internal static class TerrainStructs
        {
            public static StructDescriptor Attributes = new StructDescriptor()
            {
                name = "Attributes",
                packFields = false,
                fields = new FieldDescriptor[]
                {
                    StructFields.Attributes.positionOS,
                    StructFields.Attributes.normalOS,
                    StructFields.Attributes.uv0,
                    StructFields.Attributes.uv1,
                    StructFields.Attributes.uv2,
                    StructFields.Attributes.uv3,
                    StructFields.Attributes.color,
                    StructFields.Attributes.instanceID,
                    StructFields.Attributes.vertexID,
                }
            };

            public static StructDescriptor MetaAttributes = new StructDescriptor()
            {
                name = "Attributes",
                packFields = false,
                fields = new FieldDescriptor[]
                {
                    StructFields.Attributes.positionOS,
                    StructFields.Attributes.normalOS,
                    StructFields.Attributes.uv0,
                    StructFields.Attributes.uv1,
                    StructFields.Attributes.uv2,
                    StructFields.Attributes.uv3,
                    StructFields.Attributes.color,
                    StructFields.Attributes.instanceID,
                    StructFields.Attributes.vertexID,
                }
            };

            public static StructDescriptor Varyings = new StructDescriptor()
            {
                name = "Varyings",
                packFields = true,
                populateWithCustomInterpolators = true,
                fields = new FieldDescriptor[]
                {
                    StructFields.Varyings.positionCS,
                    StructFields.Varyings.positionWS,
                    StructFields.Varyings.normalWS,
                    StructFields.Varyings.tangentWS,
                    StructFields.Varyings.texCoord0,
                    StructFields.Varyings.texCoord1,
                    StructFields.Varyings.texCoord2,
                    StructFields.Varyings.texCoord3,
                    StructFields.Varyings.color,
                    StructFields.Varyings.screenPosition,
                    TerrainStructFields.Varyings.uvSplat01,
                    TerrainStructFields.Varyings.uvSplat23,
                    TerrainStructFields.Varyings.normalViewDir,
                    TerrainStructFields.Varyings.tangentViewDir,
                    TerrainStructFields.Varyings.bitangentViewDir,
                    UniversalStructFields.Varyings.staticLightmapUV,
                    UniversalStructFields.Varyings.dynamicLightmapUV,
                    UniversalStructFields.Varyings.sh,
                    UniversalStructFields.Varyings.fogFactorAndVertexLight,
                    UniversalStructFields.Varyings.shadowCoord,
                    StructFields.Varyings.instanceID,
                    UniversalStructFields.Varyings.stereoTargetEyeIndexAsBlendIdx0,
                    UniversalStructFields.Varyings.stereoTargetEyeIndexAsRTArrayIdx,
                    StructFields.Varyings.cullFace,
                }
            };

            public static StructDescriptor MetaVaryings = new StructDescriptor()
            {
                name = "Varyings",
                packFields = true,
                populateWithCustomInterpolators = true,
                fields = new FieldDescriptor[]
                {
                    StructFields.Varyings.positionCS,
                    StructFields.Varyings.positionWS,
                    StructFields.Varyings.normalWS,
                    StructFields.Varyings.tangentWS,
                    StructFields.Varyings.texCoord0,
                    StructFields.Varyings.texCoord1,
                    StructFields.Varyings.texCoord2,
                    StructFields.Varyings.texCoord3,
                    StructFields.Varyings.color,
                    StructFields.Varyings.screenPosition,
                    TerrainStructFields.Varyings.uvSplat01,
                    TerrainStructFields.Varyings.uvSplat23,
                    TerrainStructFields.Varyings.normalViewDir,
                    TerrainStructFields.Varyings.tangentViewDir,
                    TerrainStructFields.Varyings.bitangentViewDir,
                    UniversalStructFields.Varyings.staticLightmapUV,
                    UniversalStructFields.Varyings.dynamicLightmapUV,
                    UniversalStructFields.Varyings.sh,
                    UniversalStructFields.Varyings.fogFactorAndVertexLight,
                    UniversalStructFields.Varyings.shadowCoord,
                    StructFields.Varyings.instanceID,
                    UniversalStructFields.Varyings.stereoTargetEyeIndexAsBlendIdx0,
                    UniversalStructFields.Varyings.stereoTargetEyeIndexAsRTArrayIdx,
                    StructFields.Varyings.cullFace,
                }
            };

            private static StructDescriptor SurfaceDescriptionInputsImpl()
            {
                var surfaceDescriptionInputs = Structs.SurfaceDescriptionInputs;
                var terrainFieldList = new List<FieldDescriptor>();

                for (int i = 0; i < surfaceDescriptionInputs.fields.Length; ++i)
                    terrainFieldList.Add(surfaceDescriptionInputs.fields[i]);

                terrainFieldList.Add(TerrainStructFields.SurfaceDescriptionInputs.uvSplat01);
                terrainFieldList.Add(TerrainStructFields.SurfaceDescriptionInputs.uvSplat23);

                return new StructDescriptor()
                {
                    name = "SurfaceDescriptionInputs",
                    packFields = false,
                    populateWithCustomInterpolators = true,
                    fields = terrainFieldList.ToArray(),
                };
            }

            public static StructDescriptor SurfaceDescriptionInputs => SurfaceDescriptionInputsImpl();
        }

        static class TerrainStructCollections
        {
            public static readonly StructCollection Default = new StructCollection
            {
                { TerrainStructs.Attributes },
                { TerrainStructs.Varyings },
                { Structs.VertexDescriptionInputs },
                { TerrainStructs.SurfaceDescriptionInputs },
            };

            public static readonly StructCollection Meta = new StructCollection()
            {
                { TerrainStructs.MetaAttributes },
                { TerrainStructs.MetaVaryings },
                { Structs.VertexDescriptionInputs },
                { TerrainStructs.SurfaceDescriptionInputs },
            };
        }
        #endregion

        #region SubShader
        static class TerrainSubShaders
        {
            public static readonly KeywordDescriptor AlphaTestOn = new KeywordDescriptor()
            {
                displayName = ShaderKeywordStrings._ALPHATEST_ON,
                referenceName = ShaderKeywordStrings._ALPHATEST_ON,
                type = KeywordType.Boolean,
                definition = KeywordDefinition.MultiCompile,
                scope = KeywordScope.Global,
            };

            // SM 4.5, compute with dots instancing
            public static SubShaderDescriptor LitComputeDotsSubShader(UniversalTarget target, string renderType, string renderQueue, bool blendModePreserveSpecular)
            {
                SubShaderDescriptor result = new SubShaderDescriptor()
                {
                    pipelineTag = UniversalTarget.kPipelineTag,
                    customTags = string.Concat(UniversalTarget.kLitMaterialTypeTag, UniversalTarget.kTerrainMaterialTypeTag),
                    renderType = renderType,
                    renderQueue = renderQueue,
                    generatesPreview = true,
                    passes = new PassCollection(),
                    shaderDependencies = new List<ShaderDependency>(),
                };

                result.passes.Add(TerrainLitPasses.Forward(target, blendModePreserveSpecular, TerrainCorePragmas.DOTSForward));
                result.passes.Add(TerrainLitPasses.GBuffer(target, blendModePreserveSpecular));

                // cull the shadowcaster pass if we know it will never be used
                if (target.castShadows || target.allowMaterialOverride)
                    result.passes.Add(PassVariant(TerrainLitPasses.ShadowCaster(target), TerrainCorePragmas.DOTSInstanced));

                if (target.mayWriteDepth)
                    result.passes.Add(PassVariant(TerrainLitPasses.DepthOnly(target), TerrainCorePragmas.DOTSInstanced));

                result.passes.Add(PassVariant(TerrainLitPasses.DepthNormal(target), TerrainCorePragmas.DOTSInstanced));
                result.passes.Add(PassVariant(TerrainLitPasses.Meta(target), TerrainCorePragmas.DOTSInstanced));

                // Currently neither of these passes (selection/picking) can be last for the game view for
                // UI shaders to render correctly. Verify [1352225] before changing this order.
                result.passes.Add(PassVariant(TerrainLitPasses.SceneSelection(target), TerrainCorePragmas.DOTSDefault));
                result.passes.Add(PassVariant(TerrainLitPasses.ScenePicking(target), TerrainCorePragmas.DOTSDefault));

                result.shaderDependencies.Add(TerrainDependencies.AddPassShader());
                result.shaderDependencies.Add(TerrainDependencies.BaseMapShader());
                result.shaderDependencies.Add(TerrainDependencies.BaseMapGenShader());

                return result;
            }

            public static SubShaderDescriptor LitGLESSubShader(UniversalTarget target, string renderType, string renderQueue, bool blendModePreserveSpecular)
            {
                // SM 2.0, GLES

                // ForwardOnly pass is used as complex Lit SM 2.0 fallback for GLES.
                // Drops advanced features and renders materials as Lit.

                SubShaderDescriptor result = new SubShaderDescriptor()
                {
                    pipelineTag = UniversalTarget.kPipelineTag,
                    customTags = string.Concat(UniversalTarget.kLitMaterialTypeTag, UniversalTarget.kTerrainMaterialTypeTag),
                    renderType = renderType,
                    renderQueue = renderQueue,
                    generatesPreview = true,
                    passes = new PassCollection(),
                    shaderDependencies = new List<ShaderDependency>(),
                };

                result.passes.Add(TerrainLitPasses.Forward(target, blendModePreserveSpecular));

                // cull the shadowcaster pass if we know it will never be used
                if (target.castShadows || target.allowMaterialOverride)
                    result.passes.Add(TerrainLitPasses.ShadowCaster(target));

                if (target.mayWriteDepth)
                    result.passes.Add(TerrainLitPasses.DepthOnly(target));

                result.passes.Add(TerrainLitPasses.DepthNormal(target));
                result.passes.Add(TerrainLitPasses.Meta(target));

                // Currently neither of these passes (selection/picking) can be last for the game view for
                // UI shaders to render correctly. Verify [1352225] before changing this order.
                result.passes.Add(TerrainLitPasses.SceneSelection(target));
                result.passes.Add(TerrainLitPasses.ScenePicking(target));

                result.shaderDependencies.Add(TerrainDependencies.AddPassShader());
                result.shaderDependencies.Add(TerrainDependencies.BaseMapShader());
                result.shaderDependencies.Add(TerrainDependencies.BaseMapGenShader());

                return result;
            }
        }
        #endregion

        #region Pragmas
        static class TerrainCorePragmas
        {
            private static InstancingOptions[] InstancingOptionList()
            {
                return new []
                {
                    InstancingOptions.AssumeUniformScaling,
                    InstancingOptions.NoMatrices,
                    InstancingOptions.NoLightProbe,
                    InstancingOptions.NoLightmap,
                };
            }

            public static readonly PragmaCollection Default = new PragmaCollection
            {
                { Pragma.Target(ShaderModel.Target20) },
                { Pragma.OnlyRenderers(new[] { Platform.GLES3, Platform.GLCore, Platform.D3D11 }) },
                { Pragma.MultiCompileInstancing },
                { Pragma.InstancingOptions(InstancingOptionList()) },
                { Pragma.Vertex("vert") },
                { Pragma.Fragment("frag") },
            };

            public static readonly PragmaCollection Instanced = new PragmaCollection
            {
                { Pragma.Target(ShaderModel.Target20) },
                { Pragma.OnlyRenderers(new[] { Platform.GLES3, Platform.GLCore, Platform.D3D11 }) },
                { Pragma.MultiCompileInstancing },
                { Pragma.InstancingOptions(InstancingOptionList()) },
                { Pragma.Vertex("vert") },
                { Pragma.Fragment("frag") },
            };

            public static readonly PragmaCollection Forward = new PragmaCollection
            {
                { Pragma.Target(ShaderModel.Target20) },
                { Pragma.OnlyRenderers(new[] { Platform.GLES3, Platform.GLCore, Platform.D3D11 }) },
                { Pragma.MultiCompileInstancing },
                { Pragma.MultiCompileFog },
                { Pragma.InstancingOptions(InstancingOptionList()) },
                { Pragma.Vertex("vert") },
                { Pragma.Fragment("frag") },
            };

            public static readonly PragmaCollection DOTSDefault = new PragmaCollection
            {
                { Pragma.Target(ShaderModel.Target45) },
                { Pragma.ExcludeRenderers(new[] { Platform.GLES3, Platform.GLCore }) },
                { Pragma.MultiCompileInstancing },
                { Pragma.InstancingOptions(InstancingOptionList()) },
                { Pragma.Vertex("vert") },
                { Pragma.Fragment("frag") },
            };

            public static readonly PragmaCollection DOTSInstanced = new PragmaCollection
            {
                { Pragma.Target(ShaderModel.Target45) },
                { Pragma.ExcludeRenderers(new[] { Platform.GLES3, Platform.GLCore }) },
                { Pragma.MultiCompileInstancing },
                { Pragma.InstancingOptions(InstancingOptionList()) },
                { Pragma.DOTSInstancing },
                { Pragma.Vertex("vert") },
                { Pragma.Fragment("frag") },
            };

            public static readonly PragmaCollection DOTSForward = new PragmaCollection
            {
                { Pragma.Target(ShaderModel.Target45) },
                { Pragma.ExcludeRenderers(new[] {Platform.GLES3, Platform.GLCore}) },
                { Pragma.MultiCompileInstancing },
                { Pragma.MultiCompileFog },
                { Pragma.InstancingOptions(InstancingOptionList()) },
                { Pragma.DOTSInstancing },
                { Pragma.Vertex("vert") },
                { Pragma.Fragment("frag") },
            };

            public static readonly PragmaCollection DOTSGBuffer = new PragmaCollection
            {
                { Pragma.Target(ShaderModel.Target45) },
                { Pragma.ExcludeRenderers(new[] { Platform.GLES3, Platform.GLCore }) },
                { Pragma.MultiCompileInstancing },
                { Pragma.MultiCompileFog },
                { Pragma.InstancingOptions(InstancingOptionList()) },
                { Pragma.DOTSInstancing },
                { Pragma.Vertex("vert") },
                { Pragma.Fragment("frag") },
            };
        }
        #endregion

        private static RenderStateCollection GetTerrainRenderState(UniversalTarget target)
        {
            var result = new RenderStateCollection
            {
                RenderState.ZTest(target.zTestMode.ToString()),
                RenderState.Cull(Cull.Back),
                RenderState.Blend(Blend.One, Blend.Zero)
            };
            switch (target.zWriteControl)
            {
                case ZWriteControl.Auto:
                case ZWriteControl.ForceEnabled:
                    result.Add(RenderState.ZWrite(ZWrite.On));
                    break;
                default:
                    result.Add(RenderState.ZWrite(ZWrite.Off));
                    break;
            }
            return result;
        }

        #region Passes
        internal static class TerrainLitPasses
        {
            private static void AddReceiveShadowsControlToPass(ref PassDescriptor pass, UniversalTarget target, bool receiveShadows)
            {
                if (!receiveShadows)
                    pass.defines.Add(TerrainLitKeywords.ReceiveShadowsOff, 1);
            }

            public static PassDescriptor Forward(UniversalTarget target, bool blendModePreserveSpecular, PragmaCollection pragmas = null)
            {
                var result = new PassDescriptor()
                {
                    // Definition
                    displayName = "Universal Forward",
                    referenceName = "SHADERPASS_FORWARD",
                    lightMode = "UniversalForward",
                    useInPreview = true,

                    // Template
                    passTemplatePath = TerrainLitTemplate.kPassTemplate,
                    sharedTemplateDirectories = TerrainLitTemplate.kSharedTemplateDirectories,

                    // Port Mask
                    validVertexBlocks = TerrainBlockMasks.Vertex,
                    validPixelBlocks = TerrainBlockMasks.FragmentLit,

                    // Fields
                    structs = TerrainStructCollections.Default,
                    requiredFields = TerrainRequiredFields.Forward,
                    fieldDependencies = CoreFieldDependencies.Default,

                    // Conditional State
                    renderStates = GetTerrainRenderState(target),
                    pragmas = pragmas ?? TerrainCorePragmas.Forward,
                    defines = new DefineCollection() { CoreDefines.UseFragmentFog, },
                    keywords = new KeywordCollection() { TerrainLitKeywords.Forward },
                    includes = TerrainCoreIncludes.Forward,

                    // Custom Interpolator Support
                    customInterpolators = CoreCustomInterpDescriptors.Common
                };
                result.defines.Add(TerrainDefines.TerrainEnabled, 1);
                result.defines.Add(TerrainDefines.TerrainSplat01, 1);
                result.defines.Add(TerrainDefines.TerrainSplat23, 1);
                result.keywords.Add(TerrainDefines.TerrainNormalmap);
                result.keywords.Add(TerrainDefines.TerrainMaskmap);
                result.keywords.Add(TerrainDefines.TerrainInstancedPerPixelNormal);
                result.defines.Add(TerrainDefines.MetallicSpecGlossMap, 1);
                result.defines.Add(TerrainDefines.SmoothnessTextureAlbedoChannelA, 1);
                result.defines.Add(TerrainDefines.TerrainAlphaClipEnable, target.alphaClip?1:0);

                CorePasses.AddAlphaClipControlToPass(ref result, target);
                AddReceiveShadowsControlToPass(ref result, target, target.receiveShadows);

                return result;
            }

            // Deferred only in SM4.5, MRT not supported in GLES2
            public static PassDescriptor GBuffer(UniversalTarget target, bool blendModePreserveSpecular)
            {
                var result = new PassDescriptor
                {
                    // Definition
                    displayName = "GBuffer",
                    referenceName = "SHADERPASS_GBUFFER",
                    lightMode = "UniversalGBuffer",

                    // Template
                    passTemplatePath = TerrainLitTemplate.kPassTemplate,
                    sharedTemplateDirectories = TerrainLitTemplate.kSharedTemplateDirectories,

                    // Port Mask
                    validVertexBlocks = TerrainBlockMasks.Vertex,
                    validPixelBlocks = TerrainBlockMasks.FragmentLit,

                    // Fields
                    structs = TerrainStructCollections.Default,
                    requiredFields = TerrainRequiredFields.GBuffer,
                    fieldDependencies = CoreFieldDependencies.Default,

                    // Conditional State
                    renderStates = GetTerrainRenderState(target),
                    pragmas = TerrainCorePragmas.DOTSGBuffer,
                    defines = new DefineCollection() { CoreDefines.UseFragmentFog },
                    keywords = new KeywordCollection() { TerrainLitKeywords.GBuffer },
                    includes = TerrainCoreIncludes.GBuffer,

                    // Custom Interpolator Support
                    customInterpolators = CoreCustomInterpDescriptors.Common
                };

                result.defines.Add(TerrainDefines.TerrainEnabled, 1);
                result.defines.Add(TerrainDefines.TerrainSplat01, 1);
                result.defines.Add(TerrainDefines.TerrainSplat23, 1);
                result.keywords.Add(TerrainDefines.TerrainNormalmap);
                result.keywords.Add(TerrainDefines.TerrainMaskmap);
                result.keywords.Add(TerrainDefines.TerrainInstancedPerPixelNormal);
                result.defines.Add(TerrainDefines.MetallicSpecGlossMap, 1);
                result.defines.Add(TerrainDefines.SmoothnessTextureAlbedoChannelA, 1);
                result.defines.Add(TerrainDefines.TerrainAlphaClipEnable, target.alphaClip?1:0);

                CorePasses.AddAlphaClipControlToPass(ref result, target);
                AddReceiveShadowsControlToPass(ref result, target, target.receiveShadows);

                return result;
            }

            public static PassDescriptor ShadowCaster(UniversalTarget target)
            {
                var result = new PassDescriptor()
                {
                    // Definition
                    displayName = "ShadowCaster",
                    referenceName = "SHADERPASS_SHADOWCASTER",
                    lightMode = "ShadowCaster",

                    // Template
                    passTemplatePath = TerrainLitTemplate.kPassTemplate,
                    sharedTemplateDirectories = TerrainLitTemplate.kSharedTemplateDirectories,

                    // Port Mask
                    validVertexBlocks = TerrainBlockMasks.Vertex,
                    validPixelBlocks = CoreBlockMasks.FragmentAlphaOnly,

                    // Fields
                    structs = TerrainStructCollections.Default,
                    requiredFields = TerrainRequiredFields.ShadowCaster,
                    fieldDependencies = CoreFieldDependencies.Default,

                    // Conditional State
                    renderStates = CoreRenderStates.ShadowCaster(target),
                    pragmas = TerrainCorePragmas.Instanced,
                    defines = new DefineCollection(),
                    keywords = new KeywordCollection() { CoreKeywords.ShadowCaster,  },
                    includes = TerrainCoreIncludes.ShadowCaster,

                    // Custom Interpolator Support
                    customInterpolators = CoreCustomInterpDescriptors.Common
                };

                result.defines.Add(TerrainDefines.TerrainEnabled, 1);
                result.defines.Add(TerrainDefines.TerrainAlphaClipEnable, target.alphaClip?1:0);

                if (target.alphaClip)
                    result.defines.Add(CoreKeywordDescriptors.AlphaTestOn, 1);

                return result;
            }

            public static PassDescriptor DepthOnly(UniversalTarget target)
            {
                var result = new PassDescriptor()
                {
                    // Definition
                    displayName = "DepthOnly",
                    referenceName = "SHADERPASS_DEPTHONLY",
                    lightMode = "DepthOnly",
                    useInPreview = true,

                    // Template
                    passTemplatePath = TerrainLitTemplate.kPassTemplate,
                    sharedTemplateDirectories = TerrainLitTemplate.kSharedTemplateDirectories,

                    // Port Mask
                    validVertexBlocks = TerrainBlockMasks.Vertex,
                    validPixelBlocks = CoreBlockMasks.FragmentAlphaOnly,

                    // Fields
                    structs = TerrainStructCollections.Default,
                    requiredFields = TerrainRequiredFields.DepthOnly,
                    fieldDependencies = CoreFieldDependencies.Default,

                    // Conditional State
                    renderStates = CoreRenderStates.DepthOnly(target),
                    pragmas = TerrainCorePragmas.Instanced,
                    defines = new DefineCollection(),
                    keywords = new KeywordCollection() { },
                    includes = TerrainCoreIncludes.DepthOnly,

                    // Custom Interpolator Support
                    customInterpolators = CoreCustomInterpDescriptors.Common
                };

                result.defines.Add(TerrainDefines.TerrainEnabled, 1);
                result.defines.Add(TerrainDefines.TerrainAlphaClipEnable, target.alphaClip?1:0);

                if (target.alphaClip)
                    result.defines.Add(CoreKeywordDescriptors.AlphaTestOn, 1);

                return result;
            }

            public static PassDescriptor DepthNormal(UniversalTarget target)
            {
                var result = new PassDescriptor()
                {
                    // Definition
                    displayName = "DepthNormals",
                    referenceName = "SHADERPASS_DEPTHNORMALS",
                    lightMode = "DepthNormals",
                    useInPreview = false,

                    // Template
                    passTemplatePath = TerrainLitTemplate.kPassTemplate,
                    sharedTemplateDirectories = TerrainLitTemplate.kSharedTemplateDirectories,

                    // Port Mask
                    validVertexBlocks = TerrainBlockMasks.Vertex,
                    validPixelBlocks = CoreBlockMasks.FragmentDepthNormals,

                    // Fields
                    structs = TerrainStructCollections.Default,
                    requiredFields = TerrainRequiredFields.DepthNormals,
                    fieldDependencies = CoreFieldDependencies.Default,

                    // Conditional State
                    renderStates = CoreRenderStates.DepthNormalsOnly(target),
                    pragmas = TerrainCorePragmas.Instanced,
                    defines = new DefineCollection(),
                    keywords = new KeywordCollection() { TerrainSubShaders.AlphaTestOn, },
                    includes = TerrainCoreIncludes.DepthNormalsOnly,

                    // Custom Interpolator Support
                    customInterpolators = CoreCustomInterpDescriptors.Common
                };

                result.defines.Add(TerrainDefines.TerrainEnabled, 1);
                result.defines.Add(TerrainDefines.TerrainSplat01, 1);
                result.defines.Add(TerrainDefines.TerrainSplat23, 1);
                result.defines.Add(TerrainDefines.TerrainAlphaClipEnable, target.alphaClip?1:0);
                result.keywords.Add(TerrainDefines.TerrainNormalmap);
                result.keywords.Add(TerrainDefines.TerrainInstancedPerPixelNormal);

                if (target.alphaClip)
                    result.defines.Add(CoreKeywordDescriptors.AlphaTestOn, 1);

                return result;
            }

            public static PassDescriptor Meta(UniversalTarget target)
            {
                var result = new PassDescriptor()
                {
                    // Definition
                    displayName = "Meta",
                    referenceName = "SHADERPASS_META",
                    lightMode = "Meta",

                    // Template
                    passTemplatePath = TerrainLitTemplate.kPassTemplate,
                    sharedTemplateDirectories = TerrainLitTemplate.kSharedTemplateDirectories,

                    // Port Mask
                    validVertexBlocks = TerrainBlockMasks.Vertex,
                    validPixelBlocks = TerrainBlockMasks.FragmentMeta,

                    // Fields
                    structs = TerrainStructCollections.Meta,
                    requiredFields = TerrainRequiredFields.Meta,
                    fieldDependencies = CoreFieldDependencies.Default,

                    // Conditional State
                    renderStates = CoreRenderStates.Meta,
                    pragmas = TerrainCorePragmas.Default,
                    defines = new DefineCollection() { CoreDefines.UseFragmentFog },
                    keywords = new KeywordCollection() { CoreKeywordDescriptors.EditorVisualization, TerrainSubShaders.AlphaTestOn, },
                    includes = TerrainCoreIncludes.Meta,

                    // Custom Interpolator Support
                    customInterpolators = CoreCustomInterpDescriptors.Common
                };

                result.defines.Add(TerrainDefines.TerrainEnabled, 1);
                result.defines.Add(TerrainDefines.TerrainSplat01, 1);
                result.defines.Add(TerrainDefines.TerrainSplat23, 1);
                result.defines.Add(TerrainDefines.MetallicSpecGlossMap, 1);
                result.defines.Add(TerrainDefines.SmoothnessTextureAlbedoChannelA, 1);
                result.defines.Add(TerrainDefines.TerrainAlphaClipEnable, target.alphaClip?1:0);

                if (target.alphaClip)
                    result.defines.Add(CoreKeywordDescriptors.AlphaTestOn, 1);

                return result;
            }

            public static PassDescriptor SceneSelection(UniversalTarget target)
            {
                var result = new PassDescriptor()
                {
                    // Definition
                    displayName = "SceneSelectionPass",
                    referenceName = "SHADERPASS_DEPTHONLY",
                    lightMode = "SceneSelectionPass",
                    useInPreview = false,

                    // Template
                    passTemplatePath = TerrainLitTemplate.kPassTemplate,
                    sharedTemplateDirectories = TerrainLitTemplate.kSharedTemplateDirectories,

                    // Port Mask
                    validVertexBlocks = TerrainBlockMasks.Vertex,
                    validPixelBlocks = CoreBlockMasks.FragmentAlphaOnly,

                    // Fields
                    structs = TerrainStructCollections.Default,
                    requiredFields = TerrainRequiredFields.DepthOnly,
                    fieldDependencies = CoreFieldDependencies.Default,

                    // Conditional State
                    renderStates = CoreRenderStates.SceneSelection(target),
                    pragmas = TerrainCorePragmas.Default,
                    defines = new DefineCollection { CoreDefines.SceneSelection, },
                    keywords = new KeywordCollection() { TerrainSubShaders.AlphaTestOn, },
                    includes = TerrainCoreIncludes.SceneSelection,

                    // Custom Interpolator Support
                    customInterpolators = CoreCustomInterpDescriptors.Common
                };

                result.defines.Add(TerrainDefines.TerrainEnabled, 1);
                result.defines.Add(TerrainDefines.TerrainAlphaClipEnable, target.alphaClip?1:0);

                if (target.alphaClip)
                    result.defines.Add(CoreKeywordDescriptors.AlphaTestOn, 1);

                return result;
            }

            public static PassDescriptor ScenePicking(UniversalTarget target)
            {
                var result = new PassDescriptor()
                {
                    // Definition
                    displayName = "ScenePickingPass",
                    referenceName = "SHADERPASS_DEPTHONLY",
                    lightMode = "Picking",
                    useInPreview = false,

                    // Template
                    passTemplatePath = TerrainLitTemplate.kPassTemplate,
                    sharedTemplateDirectories = TerrainLitTemplate.kSharedTemplateDirectories,

                    // Port Mask
                    validVertexBlocks = TerrainBlockMasks.Vertex,
                    validPixelBlocks = CoreBlockMasks.FragmentAlphaOnly,

                    // Fields
                    structs = TerrainStructCollections.Default,
                    requiredFields = TerrainRequiredFields.DepthOnly,
                    fieldDependencies = CoreFieldDependencies.Default,

                    // Conditional State
                    renderStates = CoreRenderStates.ScenePicking(target),
                    pragmas = TerrainCorePragmas.Default,
                    defines = new DefineCollection { CoreDefines.ScenePicking, },
                    keywords = new KeywordCollection() { TerrainSubShaders.AlphaTestOn, },
                    includes = TerrainCoreIncludes.ScenePicking,

                    // Custom Interpolator Support
                    customInterpolators = CoreCustomInterpDescriptors.Common
                };

                result.defines.Add(TerrainDefines.TerrainEnabled, 1);
                result.defines.Add(TerrainDefines.TerrainAlphaClipEnable, target.alphaClip?1:0);

                if (target.alphaClip)
                    result.defines.Add(CoreKeywordDescriptors.AlphaTestOn, 1);

                return result;
            }
        }
        #endregion

        #region Dependencies
        static class TerrainDependencies
        {
            public static ShaderDependency AddPassShader()
            {
                return new ShaderDependency()
                {
                    dependencyName = "AddPassShader",
                    shaderName = "Hidden/{Name}_AddPass",
                };
            }
            public static ShaderDependency BaseMapShader()
            {
                return new ShaderDependency()
                {
                    dependencyName = "BaseMapShader",
                    shaderName = "Hidden/Universal Render Pipeline/Terrain/Lit (Base Pass)",
                };
            }
            public static ShaderDependency BaseMapGenShader()
            {
                return new ShaderDependency()
                {
                    dependencyName = "BaseMapGenShader",
                    shaderName = "Hidden/{Name}_BaseMapGen",
                };
            }
        }
        #endregion

        #region PortMasks
        static class TerrainBlockMasks
        {
            public static readonly BlockFieldDescriptor[] Vertex = new BlockFieldDescriptor[]
            {
                BlockFields.VertexDescription.Position,
            };

            public static readonly BlockFieldDescriptor[] FragmentLit = new BlockFieldDescriptor[]
            {
                BlockFields.SurfaceDescription.BaseColor,
                BlockFields.SurfaceDescription.NormalOS,
                BlockFields.SurfaceDescription.NormalTS,
                BlockFields.SurfaceDescription.NormalWS,
                BlockFields.SurfaceDescription.Emission,
                BlockFields.SurfaceDescription.Metallic,
                BlockFields.SurfaceDescription.Smoothness,
                BlockFields.SurfaceDescription.Occlusion,
                BlockFields.SurfaceDescription.Alpha,
                BlockFields.SurfaceDescription.AlphaClipThreshold,
            };

            public static readonly BlockFieldDescriptor[] FragmentMeta = new BlockFieldDescriptor[]
            {
                BlockFields.SurfaceDescription.BaseColor,
                BlockFields.SurfaceDescription.Emission,
                BlockFields.SurfaceDescription.Alpha,
                BlockFields.SurfaceDescription.AlphaClipThreshold,
            };
        }
        #endregion

        #region RequiredField
        static class TerrainRequiredFields
        {
            public static readonly FieldCollection Forward = new FieldCollection()
            {
                StructFields.Attributes.uv0,
                StructFields.Attributes.instanceID,
                StructFields.Varyings.positionWS,
                StructFields.Varyings.normalWS,
                StructFields.Varyings.texCoord0,
                StructFields.Varyings.instanceID,
                TerrainStructFields.Varyings.uvSplat01,
                TerrainStructFields.Varyings.uvSplat23,
                TerrainStructFields.Varyings.normalViewDir,
                TerrainStructFields.Varyings.tangentViewDir,
                TerrainStructFields.Varyings.bitangentViewDir,
                UniversalStructFields.Varyings.staticLightmapUV,
                UniversalStructFields.Varyings.dynamicLightmapUV,
                UniversalStructFields.Varyings.sh,
                UniversalStructFields.Varyings.fogFactorAndVertexLight, // fog and vertex lighting, vert input is dependency
                UniversalStructFields.Varyings.shadowCoord,             // shadow coord, vert input is dependency
                TerrainStructFields.SurfaceDescriptionInputs.uvSplat01,
                TerrainStructFields.SurfaceDescriptionInputs.uvSplat23,
            };

            public static readonly FieldCollection GBuffer = new FieldCollection()
            {
                StructFields.Attributes.uv0,
                StructFields.Attributes.instanceID,
                StructFields.Varyings.positionWS,
                StructFields.Varyings.normalWS,
                StructFields.Varyings.texCoord0,
                StructFields.Varyings.instanceID,
                TerrainStructFields.Varyings.uvSplat01,
                TerrainStructFields.Varyings.uvSplat23,
                TerrainStructFields.Varyings.normalViewDir,
                TerrainStructFields.Varyings.tangentViewDir,
                TerrainStructFields.Varyings.bitangentViewDir,
                UniversalStructFields.Varyings.staticLightmapUV,
                UniversalStructFields.Varyings.dynamicLightmapUV,
                UniversalStructFields.Varyings.sh,
                UniversalStructFields.Varyings.fogFactorAndVertexLight, // fog and vertex lighting, vert input is dependency
                UniversalStructFields.Varyings.shadowCoord,             // shadow coord, vert input is dependency
                TerrainStructFields.SurfaceDescriptionInputs.uvSplat01,
                TerrainStructFields.SurfaceDescriptionInputs.uvSplat23,
            };

            public static readonly FieldCollection ShadowCaster = new FieldCollection()
            {
                StructFields.Attributes.uv0,
                StructFields.Attributes.instanceID,
                StructFields.Varyings.texCoord0,
                StructFields.Varyings.instanceID,
            };

            public static readonly FieldCollection DepthOnly = new FieldCollection()
            {
                StructFields.Attributes.uv0,
                StructFields.Attributes.instanceID,
                StructFields.Varyings.texCoord0,
                StructFields.Varyings.instanceID,
            };

            public static readonly FieldCollection DepthNormals = new FieldCollection()
            {
                StructFields.Attributes.uv0,
                StructFields.Attributes.instanceID,
                StructFields.Varyings.normalWS,
                StructFields.Varyings.texCoord0,
                StructFields.Varyings.instanceID,
                TerrainStructFields.Varyings.uvSplat01,
                TerrainStructFields.Varyings.uvSplat23,
                TerrainStructFields.Varyings.normalViewDir,
                TerrainStructFields.Varyings.tangentViewDir,
                TerrainStructFields.Varyings.bitangentViewDir,
                TerrainStructFields.SurfaceDescriptionInputs.uvSplat01,
                TerrainStructFields.SurfaceDescriptionInputs.uvSplat23,
            };

            public static readonly FieldCollection Meta = new FieldCollection()
            {
                StructFields.Attributes.positionOS,
                StructFields.Attributes.normalOS,
                StructFields.Attributes.uv0,                            //
                StructFields.Attributes.uv1,                            // needed for meta vertex position
                StructFields.Attributes.uv2,                            // needed for meta UVs
                StructFields.Attributes.instanceID,                     // needed for rendering instanced terrain
                TerrainStructFields.Varyings.uvSplat01,
                TerrainStructFields.Varyings.uvSplat23,
                StructFields.Varyings.positionCS,
                StructFields.Varyings.texCoord0,                        // needed for meta UVs
                StructFields.Varyings.texCoord1,                        // VizUV
                StructFields.Varyings.texCoord2,                        // LightCoord
                StructFields.Varyings.instanceID,
                TerrainStructFields.SurfaceDescriptionInputs.uvSplat01,
                TerrainStructFields.SurfaceDescriptionInputs.uvSplat23,
            };
        }
        #endregion

        #region Defines
        static class TerrainDefines
        {
            public static readonly KeywordDescriptor TerrainEnabled = new KeywordDescriptor()
            {
                displayName = "Universal Terrain",
                referenceName = "UNIVERSAL_TERRAIN_ENABLED",
                type = KeywordType.Boolean,
                definition = KeywordDefinition.Predefined,
                scope = KeywordScope.Local,
            };

            public static readonly KeywordDescriptor TerrainSplat01 = new KeywordDescriptor()
            {
                displayName = "Universal Terrain Splat01",
                referenceName = "UNIVERSAL_TERRAIN_SPLAT01",
                type = KeywordType.Boolean,
                definition = KeywordDefinition.Predefined,
                scope = KeywordScope.Local,
            };

            public static readonly KeywordDescriptor TerrainSplat23 = new KeywordDescriptor()
            {
                displayName = "Universal Terrain Splat23",
                referenceName = "UNIVERSAL_TERRAIN_SPLAT23",
                type = KeywordType.Boolean,
                definition = KeywordDefinition.Predefined,
                scope = KeywordScope.Local,
            };

            public static readonly KeywordDescriptor TerrainAddPass = new KeywordDescriptor()
            {
                displayName = "Universal Terrain AddPass",
                referenceName = "TERRAIN_SPLAT_ADDPASS",
                type = KeywordType.Boolean,
                definition = KeywordDefinition.Predefined,
                scope = KeywordScope.Local,
            };

            public static KeywordDescriptor TerrainNormalmap = new KeywordDescriptor()
            {
                displayName = "Terrain Normal Map",
                referenceName = "_NORMALMAP",
                type = KeywordType.Boolean,
                definition = KeywordDefinition.ShaderFeature,
                scope = KeywordScope.Local,
            };

            public static KeywordDescriptor TerrainMaskmap = new KeywordDescriptor()
            {
                displayName = "Terrain Mask Map",
                referenceName = "_MASKMAP",
                type = KeywordType.Boolean,
                definition = KeywordDefinition.ShaderFeature,
                scope = KeywordScope.Local,
                stages = KeywordShaderStage.Fragment,
            };

            public static KeywordDescriptor TerrainInstancedPerPixelNormal = new KeywordDescriptor()
            {
                displayName = "Instanced PerPixel Normal",
                referenceName = "_TERRAIN_INSTANCED_PERPIXEL_NORMAL",
                type = KeywordType.Boolean,
                definition = KeywordDefinition.ShaderFeature,
                scope = KeywordScope.Local,
            };

            public static readonly KeywordDescriptor MetallicSpecGlossMap = new KeywordDescriptor()
            {
                displayName = "Metallic SpecGloss Map",
                referenceName = "_METALLICSPECGLOSSMAP",
                type = KeywordType.Boolean,
                definition = KeywordDefinition.Predefined,
                scope = KeywordScope.Local,
            };

            public static readonly KeywordDescriptor SmoothnessTextureAlbedoChannelA = new KeywordDescriptor()
            {
                displayName = "Smoothness Texture Albedo Channel A",
                referenceName = "_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A",
                type = KeywordType.Boolean,
                definition = KeywordDefinition.Predefined,
                scope = KeywordScope.Local,
            };

            public static readonly KeywordDescriptor TerrainBaseMapGen = new KeywordDescriptor()
            {
                displayName = "Terrain Base Map Generation",
                referenceName = "_TERRAIN_BASEMAP_GEN",
                type = KeywordType.Boolean,
                definition = KeywordDefinition.Predefined,
                scope = KeywordScope.Local,
            };

            public static readonly KeywordDescriptor TerrainAlphaClipEnable = new KeywordDescriptor()
            {
                displayName = "Terrain Alpha Clip Enable",
                referenceName = "_TERRAIN_SG_ALPHA_CLIP",
                type = KeywordType.Boolean,
                definition = KeywordDefinition.ShaderFeature,
                scope = KeywordScope.Local,
                stages = KeywordShaderStage.Fragment,
            };
        }
        #endregion

        #region Keywords
        static class TerrainLitKeywords
        {
            public static readonly KeywordDescriptor ReceiveShadowsOff = new KeywordDescriptor()
            {
                displayName = "Receive Shadows Off",
                referenceName = ShaderKeywordStrings._RECEIVE_SHADOWS_OFF,
                type = KeywordType.Boolean,
                definition = KeywordDefinition.ShaderFeature,
                scope = KeywordScope.Local,
            };

            public static readonly KeywordDescriptor ScreenSpaceAmbientOcclusion = new KeywordDescriptor()
            {
                displayName = "Screen Space Ambient Occlusion",
                referenceName = "_SCREEN_SPACE_OCCLUSION",
                type = KeywordType.Boolean,
                definition = KeywordDefinition.MultiCompile,
                scope = KeywordScope.Global,
            };

            public static readonly KeywordCollection Forward = new KeywordCollection
            {
                { ScreenSpaceAmbientOcclusion },
                { CoreKeywordDescriptors.StaticLightmap },
                { CoreKeywordDescriptors.DynamicLightmap },
                { CoreKeywordDescriptors.DirectionalLightmapCombined },
                { CoreKeywordDescriptors.MainLightShadows },
                { CoreKeywordDescriptors.AdditionalLights },
                { CoreKeywordDescriptors.AdditionalLightShadows },
                { CoreKeywordDescriptors.ReflectionProbeBlending },
                { CoreKeywordDescriptors.ReflectionProbeBoxProjection },
                { CoreKeywordDescriptors.ShadowsSoft },
                { CoreKeywordDescriptors.LightmapShadowMixing },
                { CoreKeywordDescriptors.ShadowsShadowmask },
                { CoreKeywordDescriptors.ClusterLightLoop },
                { CoreKeywordDescriptors.DBuffer },
                { CoreKeywordDescriptors.LightLayers },
                { CoreKeywordDescriptors.DebugDisplay },
                { CoreKeywordDescriptors.LightCookies },
            };

            public static readonly KeywordCollection GBuffer = new KeywordCollection
            {
                { CoreKeywordDescriptors.StaticLightmap },
                { CoreKeywordDescriptors.DynamicLightmap },
                { CoreKeywordDescriptors.DirectionalLightmapCombined },
                { CoreKeywordDescriptors.MainLightShadows },
                { CoreKeywordDescriptors.ShadowsSoft },
                { CoreKeywordDescriptors.LightmapShadowMixing },
                { CoreKeywordDescriptors.MixedLightingSubtractive },
                { CoreKeywordDescriptors.DBuffer },
                { CoreKeywordDescriptors.GBufferNormalsOct },
                { CoreKeywordDescriptors.LightLayers },
                { CoreKeywordDescriptors.RenderPassEnabled },
                { CoreKeywordDescriptors.DebugDisplay },
            };
        }
        #endregion

        #region Includes
        static class TerrainCoreIncludes
        {
            public static readonly string kVaryings = "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/Varyings.hlsl";
            public static readonly string kShaderPass = "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/ShaderPass.hlsl";
            public static readonly string kShadows = "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl";
            public static readonly string kMetaInput = "Packages/com.unity.render-pipelines.universal/ShaderLibrary/MetaInput.hlsl";
            public static readonly string kDepthOnlyPass = "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/DepthOnlyPass.hlsl";
            public static readonly string kDepthNormalsOnlyPass = "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/Terrain/DepthNormalsOnlyPass.hlsl";
            public static readonly string kShadowCasterPass = "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/ShadowCasterPass.hlsl";
            public static readonly string kForwardPass = "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/Terrain/PBRForwardPass.hlsl";
            public static readonly string kGBuffer = "Packages/com.unity.render-pipelines.universal/ShaderLibrary/GBufferOutput.hlsl";
            public static readonly string kPBRGBufferPass = "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/Terrain/PBRGBufferPass.hlsl";
            public static readonly string kLightingMetaPass = "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/LightingMetaPass.hlsl";
            public static readonly string kSelectionPickingPass = "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/SelectionPickingPass.hlsl";

            public static readonly string kTerrainLitInput = "Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLitInput.hlsl";
            public static readonly string kTerrainPassUtils = "Packages/com.unity.render-pipelines.universal/Editor/Terrain/TerrainPassUtils.hlsl";
            public static readonly string kRenderingLayers = "Packages/com.unity.render-pipelines.universal/ShaderLibrary/RenderingLayers.hlsl";

            public static readonly IncludeCollection CorePostgraph = new IncludeCollection
            {
                { kShaderPass, IncludeLocation.Pregraph },
                { kRenderingLayers, IncludeLocation.Pregraph, true },
                { kVaryings, IncludeLocation.Postgraph },
            };

            public static readonly IncludeCollection Forward = new IncludeCollection
            {
                // Pre-graph
                { CoreIncludes.CorePregraph },
                { TerrainCoreIncludes.kShadows, IncludeLocation.Pregraph },
                { CoreIncludes.ShaderGraphPregraph },
                { CoreIncludes.DBufferPregraph },
                { TerrainCoreIncludes.kTerrainLitInput, IncludeLocation.Pregraph },
                { TerrainCoreIncludes.kTerrainPassUtils, IncludeLocation.Pregraph },

                // Post-graph
                { TerrainCoreIncludes.CorePostgraph },
                { TerrainCoreIncludes.kForwardPass, IncludeLocation.Postgraph },
            };

            public static readonly IncludeCollection DepthOnly = new IncludeCollection
            {
                // Pre-graph
                { CoreIncludes.CorePregraph },
                { CoreIncludes.ShaderGraphPregraph },
                { TerrainCoreIncludes.kTerrainLitInput, IncludeLocation.Pregraph },
                { TerrainCoreIncludes.kTerrainPassUtils, IncludeLocation.Pregraph },

                // Post-graph
                { TerrainCoreIncludes.CorePostgraph },
                { TerrainCoreIncludes.kDepthOnlyPass, IncludeLocation.Postgraph },
            };

            public static readonly IncludeCollection DepthNormalsOnly = new IncludeCollection
            {
                // Pre-graph
                { CoreIncludes.CorePregraph },
                { CoreIncludes.ShaderGraphPregraph },
                { TerrainCoreIncludes.kTerrainLitInput, IncludeLocation.Pregraph },
                { TerrainCoreIncludes.kTerrainPassUtils, IncludeLocation.Pregraph },

                // Post-graph
                { TerrainCoreIncludes.CorePostgraph },
                { TerrainCoreIncludes.kDepthNormalsOnlyPass, IncludeLocation.Postgraph },
            };

            public static readonly IncludeCollection ShadowCaster = new IncludeCollection
            {
                // Pre-graph
                { CoreIncludes.CorePregraph },
                { CoreIncludes.ShaderGraphPregraph },
                { TerrainCoreIncludes.kTerrainLitInput, IncludeLocation.Pregraph },
                { TerrainCoreIncludes.kTerrainPassUtils, IncludeLocation.Pregraph },

                // Post-graph
                { TerrainCoreIncludes.CorePostgraph },
                { TerrainCoreIncludes.kShadowCasterPass, IncludeLocation.Postgraph },
            };

            public static readonly IncludeCollection GBuffer = new IncludeCollection
            {
                // Pre-graph
                { CoreIncludes.CorePregraph },
                { TerrainCoreIncludes.kShadows, IncludeLocation.Pregraph },
                { CoreIncludes.ShaderGraphPregraph },
                { CoreIncludes.DBufferPregraph },
                { TerrainCoreIncludes.kTerrainLitInput, IncludeLocation.Pregraph },
                { TerrainCoreIncludes.kTerrainPassUtils, IncludeLocation.Pregraph },

                // Post-graph
                { TerrainCoreIncludes.CorePostgraph },
                { TerrainCoreIncludes.kGBuffer, IncludeLocation.Postgraph },
                { TerrainCoreIncludes.kPBRGBufferPass, IncludeLocation.Postgraph },
            };

            public static readonly IncludeCollection Meta = new IncludeCollection
            {
                // Pre-graph
                { CoreIncludes.CorePregraph },
                { CoreIncludes.ShaderGraphPregraph },
                { TerrainCoreIncludes.kMetaInput, IncludeLocation.Pregraph },
                { TerrainCoreIncludes.kTerrainLitInput, IncludeLocation.Pregraph },
                { TerrainCoreIncludes.kTerrainPassUtils, IncludeLocation.Pregraph },

                // Post-graph
                { TerrainCoreIncludes.CorePostgraph },
                { TerrainCoreIncludes.kLightingMetaPass, IncludeLocation.Postgraph },
            };

            public static readonly IncludeCollection SceneSelection = new IncludeCollection
            {
                // Pre-graph
                { CoreIncludes.CorePregraph },
                { CoreIncludes.ShaderGraphPregraph },
                { TerrainCoreIncludes.kTerrainLitInput, IncludeLocation.Pregraph },
                { TerrainCoreIncludes.kTerrainPassUtils, IncludeLocation.Pregraph },

                // Post-graph
                { TerrainCoreIncludes.CorePostgraph },
                { TerrainCoreIncludes.kSelectionPickingPass, IncludeLocation.Postgraph },
            };

            public static readonly IncludeCollection ScenePicking = new IncludeCollection
            {
                // Pre-graph
                { CoreIncludes.CorePregraph },
                { CoreIncludes.ShaderGraphPregraph },
                { TerrainCoreIncludes.kTerrainLitInput, IncludeLocation.Pregraph },
                { TerrainCoreIncludes.kTerrainPassUtils, IncludeLocation.Pregraph },

                // Post-graph
                { TerrainCoreIncludes.CorePostgraph },
                { TerrainCoreIncludes.kSelectionPickingPass, IncludeLocation.Postgraph },
            };
        }
        #endregion
    }
}