// TODO can determine which uniform buffers are used in which stage based on binding index (see 'Create' in GpuProgramsVk.cpp)
        // if (binding.GetShaderStageFlags() != VK_SHADER_STAGE_FRAGMENT_BIT) // *only* used in fragment shader
        static uint SerializeUniforms(CompiledShader compiled, BinaryWriter writer)
        {
            int totalConstants = 0;

            foreach (var constantBuffer in compiled.constantBuffers)
            {
                totalConstants += constantBuffer.usedConstants;
            }
            writer.Write((ushort)totalConstants);

            int size = 0;

            foreach (var constantBuffer in compiled.constantBuffers)
            {
                for (int i = 0; i < constantBuffer.usedConstants; i++)
                {
                    var constant = constantBuffer.constants[i];
                    UnityToBgfx.ConvertBuiltInUniform(constant.name, out string uniformName);
                    writer.Write((byte)uniformName.Length);
                    writer.Write(Encoding.UTF8.GetBytes(uniformName));

                    if (constant.dataType != UnityHelper.ShaderParamType.kShaderParamFloat)
                    {
                        throw new NotSupportedException("Only float types are supported for uniforms.");
                    }

                    // Data type
                    var type = UnityToBgfx.GetBgfxUniformDataType(constant);
                    writer.Write((byte)type); // TODO bx::write(_writer, uint8_t(un.type | fragmentBit));

                    //  array size
                    writer.Write((byte)constant.arraySize); // Should be 0 if not an array

                    // regIndex - bgfx puts all constants into a single buffer
                    writer.Write((ushort)(size + constant.idx)); // TODO

                    // regCount
                    var regCount = constant.arraySize;
                    if (type == BgfxHelper.UniformType.Mat3)
                    {
                        regCount *= 3;
                    }
                    else if (type == BgfxHelper.UniformType.Mat4)
                    {
                        regCount *= 4;
                    }
                    writer.Write((ushort)regCount);

                    // TODO this is only used in renderer_webgpu.cpp?
                    // texComponent
                    // texDimension
//                    writer.Write((byte)0);
//                    writer.Write((byte)0);
                }

                size += constantBuffer.size;
            }

            return((uint)size);
        }
        /// <remarks>
        /// Matches output file generated by shaderc
        /// </remarks>
        static byte[] CreateSerializedBgfxShader(CompiledShaderData compiled, Stage stage, List <VarName> attributes)
        {
            Assert.IsTrue(stage == Stage.Vertex && compiled.shader.type == UnityHelper.ShaderGpuProgramType.kShaderGpuProgramMetalVS ||
                          stage == Stage.Fragment && compiled.shader.type == UnityHelper.ShaderGpuProgramType.kShaderGpuProgramMetalFS);

            using (var ms = new MemoryStream())
                using (var bw = new BinaryWriter(ms))
                {
                    bool isVertexShader = stage == Stage.Vertex;

                    // header
                    bw.Write(isVertexShader ? BgfxHelper.BGFX_CHUNK_MAGIC_VSH : BgfxHelper.BGFX_CHUNK_MAGIC_FSH);

                    // input/output hashes used for validating that vertex output matches fragment input
                    bw.Write(0u);
                    bw.Write(0u);

                    uint uniformBufferSize = SerializeUniforms(compiled, bw, stage);

                    var src  = FixupShaderSource(compiled, stage, attributes);
                    var code = Encoding.UTF8.GetBytes(src);
                    bw.Write((uint)code.Length);
                    bw.Write(code);
                    byte nul = 0;
                    bw.Write(nul);

                    // vertex attributes
                    // Note: Shaderc writes the attributes but they are not actually used
                    if (isVertexShader)
                    {
                        bw.Write((byte)compiled.attributes.Count);
                        foreach (var attribute in compiled.attributes)
                        {
                            BgfxHelper.Attrib attrib = UnityToBgfx.ConvertAttribute(attribute);
                            if (attrib != BgfxHelper.Attrib.Count)
                            {
                                bw.Write(BgfxHelper.s_AttribToId[attrib]);
                            }
                            else
                            {
                                bw.Write(ushort.MaxValue);
                            }
                        }
                    }
                    else
                    {
                        bw.Write((byte)0);
                    }

                    // constant buffer size
                    bw.Write(uniformBufferSize);

                    return(ms.ToArray());
                }
        }
        internal static byte[] CreateSerializedBgfxShader(CompiledShader compiled, Stage stage)
        {
            // TODO SMOL-V compression and shader stage extraction
            throw new NotImplementedException();

#if false
            // Export bgfx file
            bool isVertexShader = stage == Stage.Vertex;
            using (var ms = new MemoryStream())
                using (var bw = new BinaryWriter(ms))
                {
                    // Header
                    bw.Write(isVertexShader ? BgfxHelper.BGFX_CHUNK_MAGIC_VSH : BgfxHelper.BGFX_CHUNK_MAGIC_FSH);

                    // Input/output hashes used for validating that vertex output matches fragment input
                    bw.Write(0u);
                    bw.Write(0u);

                    uint uniformBufferSize = SerializeUniforms(compiled, bw);

                    bw.Write((uint)compiled.shader.outputCode.Length);
                    bw.Write(compiled.shader.outputCode);
                    byte nul = 0;
                    bw.Write(nul);

                    // vertex attributes
                    if (isVertexShader)
                    {
                        bw.Write((byte)compiled.inputs.Count);
                        foreach (var input in compiled.inputs)
                        {
                            BgfxHelper.Attrib attrib = UnityToBgfx.ConvertAttribute(input.src);
                            if (attrib != BgfxHelper.Attrib.Count)
                            {
                                bw.Write(BgfxHelper.s_attribToId[attrib]);
                            }
                            else
                            {
                                bw.Write(ushort.MaxValue);
                            }
                        }
                    }
                    else
                    {
                        bw.Write((byte)0);
                    }

                    // constant buffer size
                    bw.Write(uniformBufferSize);

                    return(ms.ToArray());
                }
#endif
        }
        static uint SerializeUniforms(CompiledShaderData compiled, BinaryWriter writer, Stage stage)
        {
            if (compiled.constantBuffers.Count == 0 && compiled.samplers.Count == 0)
            {
                writer.Write((ushort)0);
                return(0);
            }

            uint fragmentBit   = stage == Stage.Fragment ? BgfxHelper.BGFX_UNIFORM_FRAGMENTBIT : 0u;
            var  uniformsCount = compiled.samplers.Count;

            foreach (var constantBuffer in compiled.constantBuffers)
            {
                uniformsCount += constantBuffer.constants.Count;
            }
            writer.Write((ushort)uniformsCount);

            uint constantBufferSize = 0;

            foreach (var constantBuffer in compiled.constantBuffers)
            {
                foreach (var constant in constantBuffer.constants)
                {
                    UnityToBgfx.ConvertBuiltInUniform(constant.name, out string uniformName);
                    writer.Write((byte)uniformName.Length);
                    writer.Write(Encoding.UTF8.GetBytes(uniformName));

                    if (constant.dataType != UnityHelper.ShaderParamType.kShaderParamFloat)
                    {
                        throw new NotSupportedException("Only float types are supported for uniforms.");
                    }

                    // data type
                    var type = UnityToBgfx.GetBgfxUniformDataType(constant);
                    writer.Write((byte)((uint)type | fragmentBit));

                    //  array size
                    var arraySize = Math.Max(constant.arraySize, 1); // Unity passes a 0 if not an array
                    writer.Write((byte)arraySize);

                    // regIndex - merge all constants into a single buffer
                    writer.Write((ushort)(constantBufferSize + constant.idx));

                    // regCount
                    var regCount = (ushort)arraySize;
                    if (type == BgfxHelper.UniformType.Mat3)
                    {
                        regCount *= 3;
                    }
                    else if (type == BgfxHelper.UniformType.Mat4)
                    {
                        regCount *= 4;
                    }
                    writer.Write(regCount);

                    // Unused
                    writer.Write((byte)0); // texComponent
                    writer.Write((byte)0); // texDimension
                }

                constantBufferSize += (uint)constantBuffer.size;
            }

            foreach (var sampler in compiled.samplers)
            {
                writer.Write((byte)sampler.name.Length);
                writer.Write(Encoding.UTF8.GetBytes(sampler.name));
                writer.Write((byte)((ushort)BgfxHelper.UniformType.Sampler | BgfxHelper.BGFX_UNIFORM_SAMPLERBIT | fragmentBit));

                // array size
                writer.Write((byte)0);

                // regIndex
                writer.Write((ushort)0);

                // regCount
                writer.Write((ushort)0);

                // texComponent
                writer.Write((byte)0);

                // texDimension
                writer.Write((byte)0);
            }

            return(constantBufferSize);
        }
        static string FixupShaderSource(CompiledShaderData compiled, Stage stage, List <VarName> attributes)
        {
            // See CreateMTLLibraryFromSource in Unity
            var stream = new MemoryStream(compiled.shader.outputCode);

            using (BinaryReader br = new BinaryReader(stream))
            {
                // Remove header inserted by Unity. See ExtractMetalShaderBlobDesc in MetalShaderBlobHeader.h
                var magic = br.ReadUInt32();
                if (magic != k_MetalShaderBlobHeaderMagic)
                {
                    throw new InvalidDataException("SerializeShaderMSL: Unsupported Metal shader blob header.");
                }

                var size       = br.ReadUInt32();
                var version    = br.ReadUInt32();
                var codeOffset = br.ReadUInt32();
                if (codeOffset >= compiled.shader.outputCode.Length)
                {
                    throw new InvalidDataException("SerializeShaderMSL: Invalid shader code offset.");
                }

                long codeLength = compiled.shader.outputCode.Length - codeOffset;
                br.BaseStream.Seek(codeOffset, SeekOrigin.Begin);
                var code = br.ReadBytes((int)codeLength);
                Assert.IsTrue(br.PeekChar() == -1);

                // Patch up the vertex attribute names so bgfx can recognize them (it uses reflection data for attributes from the shader and ignores the ones we send)
                var codeString = Encoding.UTF8.GetString(code);
                if (stage == Stage.Vertex)
                {
                    using (StringReader sr = new StringReader(codeString))
                    {
                        while (sr.Peek() >= 0)
                        {
                            var line = sr.ReadLine();
                            if (Regex.IsMatch(line, @"^\s*\b(struct)\b\s+\b(Mtl_VertexIn)\b")) // starts with words 'struct Mtl_VertexIn', ignoring leading whitespace
                            {
                                while (!line.Contains("{"))
                                {
                                    if (sr.Peek() < 0)
                                    {
                                        throw new InvalidDataException("SerializeShaderMSL: Unable to parse vertex attributes.");
                                    }

                                    line = sr.ReadLine();
                                }

                                while (!line.Contains("}"))
                                {
                                    if (sr.Peek() < 0)
                                    {
                                        throw new InvalidDataException("SerializeShaderMSL: Unable to parse vertex attributes.");
                                    }

                                    // e.g. float3 POSITION0 [[ attribute(0) ]] ;
                                    line = sr.ReadLine();
                                    var split = line.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
                                    if (split.Length > 2)
                                    {
                                        string unityName = split[1]; // TODO precision qualifiers?
                                        UnityHelper.ShaderChannel channel = UnityHelper.ConvertHLSLccAttributeName(unityName);
                                        Assert.IsTrue(compiled.attributes.Contains(channel));
                                        BgfxHelper.Attrib bgfxAttrib = UnityToBgfx.ConvertAttribute(channel);
                                        attributes.Add(new VarName {
                                            unityName = unityName, bgfxName = BgfxHelper.GetName(bgfxAttrib)
                                        });
                                    }
                                }

                                break;
                            }
                        }
                    }
                }

                List <string> uniformDecls = new List <string>();
                using (StringReader sr = new StringReader(codeString))
                {
                    while (sr.Peek() >= 0)
                    {
                        var line = sr.ReadLine();
                        for (int i = 1; i < compiled.constantBuffers.Count; i++)
                        {
                            if (Regex.IsMatch(line, $@"^\s*\b(struct)\b\s+\b({compiled.constantBuffers[i].name}_Type)\b")) // starts with words 'struct <constant buffer name>_Type', ignoring leading whitespace
                            {
                                while (!line.Contains("{"))
                                {
                                    if (sr.Peek() < 0)
                                    {
                                        throw new InvalidDataException("SerializeShaderMSL: Unable to parse uniform buffer definitions.");
                                    }

                                    line = sr.ReadLine();
                                }
                                line = sr.ReadLine();

                                while (!line.Contains("}"))
                                {
                                    if (sr.Peek() < 0)
                                    {
                                        throw new InvalidDataException("SerializeShaderMSL: Unable to parse uniform buffer definitions.");
                                    }

                                    // Store uniform declaration strings so they can be added to the first uniform buffer definition
                                    // e.g. float4x4 unity_ObjectToWorld;
                                    uniformDecls.Add(line);

                                    line = sr.ReadLine();
                                }
                            }
                        }
                    }
                }

                // Patch attribute names in the fragment shader if the vertex output names were modified
                StringBuilder sb = new StringBuilder();
                foreach (var attribute in attributes)
                {
                    sb.AppendLine($"#define {attribute.unityName} {attribute.bgfxName}");
                }

                foreach (var constantBuffer in compiled.constantBuffers)
                {
                    // bgfx requires that the constant buffer name be "_mtl_u"
                    sb.AppendLine($"#define {constantBuffer.name} _mtl_u");

                    // Patch predefined uniform names
                    foreach (var uniform in constantBuffer.constants)
                    {
                        bool patchHlslccName = uniform.constantType == UnityHelper.ConstantType.kConstantTypeMatrix;
                        if (UnityToBgfx.ConvertBuiltInUniform(uniform.name, out string bgfxName))
                        {
                            // Note: uniform names in Unity reflection data don't contain the hlslcc prefix but the uniforms in the shader code do
                            string unityName = patchHlslccName ? $"hlslcc_mtx4x4{uniform.name}" : uniform.name; // TODO support mtx3x3
                            sb.AppendLine($"#define {unityName} {bgfxName}");
                        }
                        else
                        {
                            if (uniform.name != null && patchHlslccName)
                            {
                                sb.AppendLine($"#define hlslcc_mtx4x4{uniform.name} {uniform.name}");
                            }
                        }
                    }
                }

                if (compiled.constantBuffers.Count > 1 || compiled.texToSampler.Count > 0)
                {
                    string cb0Name         = compiled.constantBuffers.Count > 1 ? compiled.constantBuffers[0].name : string.Empty;
                    var    constantBuffers = compiled.constantBuffers.Count > 1 ? compiled.constantBuffers.GetRange(1, compiled.constantBuffers.Count - 1) : new List <ConstantBuffer>();
                    using (StringReader sr = new StringReader(codeString))
                        using (StringWriter sw = new StringWriter())
                        {
                            sw.Write(sb.ToString());

                            while (sr.Peek() >= 0)
                            {
                                var line = sr.ReadLine();
                                if (Regex.IsMatch(line, $@"^\s*\b(struct)\b\s+\b({cb0Name}_Type)\b")) // starts with words 'struct <cb0 name>_Type', ignoring leading whitespace
                                {
                                    // Combine all uniform buffers into a single buffer definition
                                    while (!line.Contains("{"))
                                    {
                                        sw.WriteLine(line);
                                        if (sr.Peek() < 0)
                                        {
                                            throw new InvalidDataException("SerializeShaderMSL: Unable to parse uniform buffer definitions.");
                                        }

                                        line = sr.ReadLine();
                                    }

                                    while (!line.Contains("}"))
                                    {
                                        sw.WriteLine(line);

                                        if (sr.Peek() < 0)
                                        {
                                            throw new InvalidDataException("SerializeShaderMSL: Unable to parse uniform buffer definitions.");
                                        }

                                        // e.g. float4x4 unity_ObjectToWorld;
                                        line = sr.ReadLine();
                                    }

                                    foreach (var uniformDecl in uniformDecls)
                                    {
                                        sw.WriteLine(uniformDecl);
                                    }

                                    sw.WriteLine("};");
                                }
                                else
                                {
                                    if (constantBuffers.Any(cb => Regex.IsMatch(line, $@"^\s*\b(struct)\b\s+\b({cb.name}_Type)\b")))
                                    {
                                        // Remove (don't write) uniform buffer definition if not cb0
                                        while (!line.Contains("{"))
                                        {
                                            if (sr.Peek() < 0)
                                            {
                                                throw new InvalidDataException("SerializeShaderMSL: Unable to parse uniform buffer definitions.");
                                            }

                                            line = sr.ReadLine();
                                        }

                                        while (!line.Contains("}"))
                                        {
                                            if (sr.Peek() < 0)
                                            {
                                                throw new InvalidDataException("SerializeShaderMSL: Unable to parse uniform buffer definitions.");
                                            }

                                            line = sr.ReadLine();
                                        }
                                    }
                                    else if (Regex.IsMatch(line, @"^\s*(texture|depth).*(access::sample).*"))
                                    {
                                        // Patch texture register to match sampler register in main function texture parameters
                                        // This is needed because bgfx expects them to be the same and the Unity shader compiler uses automatic assignment, which for samplers is based on order of declaration and for textures is based on order of usage

                                        // Extract register from texture declaration - e.g. texture2d<float, access::sample > s_texAlbedo [[ texture(0) ]]
                                        string pattern = @"texture\s*\(\s*([0-9]+)\s*\)"; // find 'texture(<number>)' and capture <number> in a separate group
                                        var    match   = Regex.Match(line, pattern);
                                        if (match.Groups.Count != 2)
                                        {
                                            throw new InvalidDataException("SerializeShaderMSL: Unable to parse texture declaration.");
                                        }

                                        uint reg = uint.Parse(match.Groups[1].Value);
                                        if (compiled.texToSampler.ContainsKey(reg))
                                        {
                                            line = Regex.Replace(line, pattern, $"texture({compiled.texToSampler[reg]})");
                                        }

                                        sw.WriteLine(line);
                                    }
                                    else
                                    {
                                        // Remove comments
                                        if (Regex.IsMatch(line, @"^\s*//"))
                                        {
                                            continue;
                                        }

                                        // Remove (don't write) uniform buffers parameters in main function if not cb0
                                        // e.g. constant Uniforms_Type& Uniforms [[ buffer(1) ]]
                                        if (constantBuffers.Any(cb => Regex.IsMatch(line, $@"^\s*\b(constant)\b\s+\b({cb.name}_Type)\b&\s+\b({cb.name})\b"))) // starts with words 'constant <cb0 name>_Type& <cb0 name>', ignoring leading whitespace
                                        {
                                            continue;
                                        }

                                        sw.WriteLine(line);
                                    }
                                }
                            }

                            return(sw.ToString());
                        }
                }

                // No patching needed for uniforms or textures
                return(sb.Append(codeString).ToString());
            }
        }
        static void SerializeUniforms(CompiledShaderData compiled, BinaryWriter writer, Stage stage)
        {
            if (compiled.constantBuffers.Count == 0 && compiled.samplers.Count == 0)
            {
                writer.Write((ushort)0);
                return;
            }

            uint fragmentBit   = stage == Stage.Fragment ? BgfxHelper.BGFX_UNIFORM_FRAGMENTBIT : 0u;
            var  uniformsCount = compiled.samplers.Count;

            foreach (var constantBuffer in compiled.constantBuffers)
            {
                uniformsCount += constantBuffer.constants.Count;
            }
            writer.Write((ushort)uniformsCount);

            foreach (var constantBuffer in compiled.constantBuffers)
            {
                foreach (var constant in constantBuffer.constants)
                {
                    writer.Write((byte)constant.name.Length);
                    writer.Write(Encoding.UTF8.GetBytes(constant.name));

                    if (constant.dataType != UnityHelper.ShaderParamType.kShaderParamFloat)
                    {
                        throw new NotSupportedException("Only float types are supported for uniforms.");
                    }

                    // Data type
                    var type = UnityToBgfx.GetBgfxUniformDataType(constant);
                    writer.Write((byte)((uint)type | fragmentBit));

                    //  array size
                    int arraySize = Math.Max(constant.arraySize, 1); // Unity passes a 0 if not an array
                    writer.Write((byte)arraySize);

                    // regIndex is not used for GL
                    writer.Write((ushort)0);

                    // regCount
                    writer.Write((ushort)arraySize);

                    // Unused
                    writer.Write((byte)0); // texComponent
                    writer.Write((byte)0); // texDimension
                }
            }

            foreach (var sampler in compiled.samplers)
            {
                writer.Write((byte)sampler.name.Length);
                writer.Write(Encoding.UTF8.GetBytes(sampler.name));
                writer.Write((byte)((ushort)BgfxHelper.UniformType.Sampler | BgfxHelper.BGFX_UNIFORM_SAMPLERBIT | fragmentBit));

                // array size
                int arraySize = Math.Max(sampler.arraySize, 1);
                writer.Write((byte)arraySize);

                // regIndex
                writer.Write((ushort)0);

                // regCount
                writer.Write((ushort)arraySize);

                // texComponent
                writer.Write((byte)0);

                // texDimension
                writer.Write((byte)0);
            }
        }
        static void ProcessUniformDecl(string line, List <VarName> predefinedUniforms, List <VarName> hlslccUniforms, List <string> shadowSamplers, List <Constant> constants, List <TextureSampler> samplers)
        {
            // e.g. "uniform mat4x4 uniformName;"
            // e.g. "uniform sampler2D samplerName;"
            var split = line.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);

            if (split.Length < 3) // "uniform <type> <name>;"
            {
                throw new InvalidDataException($"SerializeShaderGLSL: Unable to parse uniform declaration '{line}'");
            }

            // Unity shader compiler does not give reflection data for uniforms in all cases so we generate it manually
            string name      = split[split.Length - 1].Remove(split[split.Length - 1].Length - 1, 1); // remove semicolon
            int    arraySize = 0;
            var    i         = name.IndexOf('[');                                                     // e.g. "uniformName[4];"

            if (i >= 0)
            {
                string pattern = @".*\[([0-9]+)\]"; // Capture number between brackets in a separate group
                var    match   = Regex.Match(name, pattern);
                if (match.Groups.Count != 2)
                {
                    throw new InvalidDataException("SerializeShaderGLSL: Unable to parse array declaration.");
                }
                arraySize = int.Parse(match.Groups[1].Value);

                name = name.Substring(0, i); // remove array brackets from name
            }

            bool mtxAsVectors = name.StartsWith(UnityToBgfx.k_HlslccMtxPrefix);

            if (UnityToBgfx.ConvertBuiltInUniform(name, out var bgfxName))
            {
                predefinedUniforms.Add(new VarName {
                    unityName = name, bgfxName = bgfxName
                });
            }
            else
            {
                // TODO fix and expose HLSLCC_FLAG_TRANSLATE_MATRICES flag
                if (mtxAsVectors) // e.g. "hlslcc_mtx4x4uniformName"
                {
                    bgfxName = name.Remove(0, UnityToBgfx.k_HlslccMtxPrefix.Length);
                    hlslccUniforms.Add(new VarName {
                        unityName = name, bgfxName = bgfxName
                    });
                }
                else if (name.StartsWith(UnityToBgfx.k_HlslccZcmpPrefix)) // e.g. "hlslcc_zcmpsamplerName"
                {
                    bgfxName = name.Remove(0, UnityToBgfx.k_HlslccZcmpPrefix.Length);
                    hlslccUniforms.Add(new VarName {
                        unityName = name, bgfxName = bgfxName
                    });
                    shadowSamplers.Add(bgfxName);
                }
                else
                {
                    bgfxName = name;
                }
            }

            string type = split[split.Length - 2];

            if (type.Contains("sampler"))
            {
                samplers.Add(GetTextureSampler(bgfxName, arraySize));
            }
            else
            {
                constants.Add(GetConstant(type, bgfxName, arraySize, mtxAsVectors));
            }
        }
        static CompiledShaderData FixupShader(string shaderSrc, Stage stage, bool GLES2, string name, List <UnityHelper.ShaderChannel> attributes)
        {
            List <VarName>     predefinedUniforms = new List <VarName>();
            List <VarName>     hlslccUniforms     = new List <VarName>();
            List <VarName>     attributesNames    = new List <VarName>();
            List <string>      shadowSamplers     = new List <string>();
            List <Constant>    constants          = new List <Constant>();
            CompiledShaderData shaderData         = new CompiledShaderData();

            shaderData.Init();
            if (attributes != null)
            {
                shaderData.attributes = attributes;
            }

            using (StringReader reader = new StringReader(shaderSrc))
            {
                while (reader.Peek() >= 0)
                {
                    var line = reader.ReadLine();
                    if (Regex.IsMatch(line, @"^\s*(\buniform\b|UNITY_LOCATION\s*\(\s*[0-9]\s*\)\s+\buniform\b)")) // starts with word 'uniform' or 'UNITY_LOCATION(<index>) uniform', ignoring leading whitespace
                    {
                        ProcessUniformDecl(line, predefinedUniforms, hlslccUniforms, shadowSamplers, constants, shaderData.samplers);
                    }
                    else if (Regex.IsMatch(line, @"^\s*(\bUNITY_BINDING\b\s*\(\s*[0-9]\s*\)\s+\buniform\b)")) // starts with word 'UNITY_BINDING(<index>) uniform', ignoring leading whitespace
                    {
                        UnityEngine.Debug.LogWarning($"Uniform buffer object found in GLSL shader {name}. Use 'CBUFFER_START' and 'CBUFFER_END' macros in place of raw cbuffer declarations.");
                    }
                    else if (stage == Stage.Vertex && Regex.IsMatch(line, @"^\s*\b(in|attribute)\b")) // starts with word 'in' or 'attribute', ignoring leading whitespaceS
                    {
                        // e.g. "in vec4 in_POSITION0;"
                        var    split     = line.Split(null);
                        string unityName = split[split.Length - 1].Remove(split[split.Length - 1].Length - 1, 1); // remove semicolon
                        UnityHelper.ShaderChannel channel = UnityHelper.ConvertHLSLccAttributeName(unityName);
                        Assert.IsTrue(shaderData.attributes.Contains(channel));
                        BgfxHelper.Attrib bgfxAttrib = UnityToBgfx.ConvertAttribute(channel);
                        attributesNames.Add(new VarName {
                            unityName = unityName, bgfxName = BgfxHelper.GetName(bgfxAttrib)
                        });
                    }
                }
            }

            // Disable UBO and Uniform Locations
            shaderSrc = shaderSrc.Replace("#define HLSLCC_ENABLE_UNIFORM_BUFFERS 1", string.Empty);
            shaderSrc = shaderSrc.Replace("#define UNITY_SUPPORTS_UNIFORM_LOCATION 1", string.Empty);

            using (StringReader reader = new StringReader(shaderSrc))
                using (StringWriter writer = new StringWriter())
                {
                    // Remove everything before the '#version' line because bgfx looks for this when determining if it needs to inject the version
                    while (reader.Peek() >= 0)
                    {
                        var line = reader.ReadLine();
                        if (line.StartsWith("#version "))
                        {
                            writer.WriteLine(line);
                            break;
                        }
                    }

                    // Why doesn't HLSLcc handle this?
                    // Note: this isn't needed if matrix as vectors is enabled
                    if (GLES2)
                    {
                        writer.WriteLine("#define mat2x2 mat2");
                        writer.WriteLine("#define mat3x3 mat3");
                        writer.WriteLine("#define mat4x4 mat4");
                    }

                    // inject fixups for uniform and attribute names after the version
                    foreach (var uniform in predefinedUniforms)
                    {
                        // e.g. #define unity_ObjectToWorld u_model
                        writer.WriteLine($"#define {uniform.unityName} {uniform.bgfxName}");
                    }
                    foreach (var uniform in hlslccUniforms)
                    {
                        writer.WriteLine($"#define {uniform.unityName} {uniform.bgfxName}");
                    }

                    // Note this shouldn't modify the vertex output because the names are unique
                    foreach (var attribute in attributesNames)
                    {
                        // e.g. #define in_POSITION0 a_position
                        writer.WriteLine($"#define {attribute.unityName} {attribute.bgfxName}");
                    }

                    while (reader.Peek() >= 0)
                    {
                        var line = reader.ReadLine();

                        if (Regex.IsMatch(line, @"^\s*(\buniform\b|UNITY_LOCATION\s*\(\s*[0-9]\s*\)\s+\buniform\b)")) // starts with word 'uniform' or 'UNITY_LOCATION(<index>) uniform', ignoring leading whitespace
                        {
                            // Remove duplicate sampler created for shadow texture
                            // HLSLcc creates a shadow sampler and a regular sampler which results in issues when using the sampler names to bind them
                            if (shadowSamplers.Any(samplerName => Regex.IsMatch(line, $@"\b{samplerName}\b")))
                            {
                                continue;
                            }

                            PatchPredefinedUniformDecl(predefinedUniforms, ref line);
                        }

                        // Remove comments
                        if (Regex.IsMatch(line, @"^\s*//"))
                        {
                            continue;
                        }

                        writer.WriteLine(line);
                    }

                    string newSrc = writer.ToString();
                    shaderData.shader = new CompiledShader {
                        outputCode = Encoding.UTF8.GetBytes(newSrc)
                    };
                    shaderData.constantBuffers.Add(new ConstantBuffer {
                        constants = constants
                    });
                    return(shaderData);
                }
        }
        static uint SerializeUniforms(CompiledShaderData compiled, BinaryWriter writer, Stage stage)
        {
            if (compiled.constantBuffers.Count == 0 && compiled.samplers.Count == 0)
            {
                writer.Write((ushort)0);
                return(0);
            }

            uint fragmentBit   = stage == Stage.Fragment ? BgfxHelper.BGFX_UNIFORM_FRAGMENTBIT : 0u;
            var  uniformsCount = compiled.samplers.Count;

            foreach (var constantBuffer in compiled.constantBuffers)
            {
                uniformsCount += constantBuffer.constants.Count;
            }
            writer.Write((ushort)uniformsCount);

            uint globalConstantBufferSize = 0;

            foreach (var constantBuffer in compiled.constantBuffers)
            {
                uint constantBufferSize = 0;
                foreach (var constant in constantBuffer.constants)
                {
                    UnityToBgfx.ConvertBuiltInUniform(constant.name, out string uniformName);
                    writer.Write((byte)uniformName.Length);
                    writer.Write(Encoding.UTF8.GetBytes(uniformName));

                    if (constant.dataType != UnityHelper.ShaderParamType.kShaderParamFloat)
                    {
                        throw new NotSupportedException("Only float types are supported for uniforms.");
                    }

                    // Data type
                    uint parameterSize;
                    var  type = UnityToBgfx.GetBgfxUniformDataType(constant);
                    writer.Write((byte)((uint)type | fragmentBit));
                    if (type == BgfxHelper.UniformType.Vec4)
                    {
                        parameterSize = 16;
                    }
                    else if (type == BgfxHelper.UniformType.Mat4)
                    {
                        parameterSize = 64;
                    }
                    else
                    {
                        // bgfx.UniformType.Mat3. Size includes padding
                        parameterSize = 44;
                    }

                    // array size
                    writer.Write((byte)constant.arraySize); // Should be 0 if not an array

                    // regIndex - merge all constants into a single buffer
                    writer.Write((ushort)(globalConstantBufferSize + constant.idx));

                    // regCount
                    parameterSize = constant.arraySize > 0 ? parameterSize * (uint)constant.arraySize : parameterSize;
                    parameterSize = AlignUp(parameterSize, 16);
                    writer.Write((ushort)(parameterSize / 16));

                    // Unused
                    writer.Write((byte)0); // texComponent
                    writer.Write((byte)0); // texDimension

                    // Actual size of this constant buffer. All unused constants after the last used constant are removed
                    constantBufferSize = (uint)constant.idx + parameterSize;
                }

                globalConstantBufferSize += constantBufferSize;
            }

            foreach (var sampler in compiled.samplers)
            {
                writer.Write((byte)sampler.name.Length);
                writer.Write(Encoding.UTF8.GetBytes(sampler.name));
                writer.Write((byte)((ushort)BgfxHelper.UniformType.Sampler | BgfxHelper.BGFX_UNIFORM_SAMPLERBIT | fragmentBit));

                // array size
                writer.Write((byte)sampler.arraySize); // this will always be zero

                // regIndex
                writer.Write((ushort)sampler.register);

                // regCount
                writer.Write((ushort)1);

                // Unused
                writer.Write((byte)0); // texComponent
                writer.Write((byte)0); // texDimension
            }

            return(globalConstantBufferSize);
        }
        /// <remarks>
        /// Matches output file generated by shaderc
        /// </remarks>
        static byte[] CreateSerializedBgfxShader(CompiledShaderData compiled, Stage stage)
        {
            // Strip root signature from shader blob and fix constant buffer bindings
            int       index = 0;
            const int kD3DRootSignatureHeaderSize = 5;

            if (compiled.shader.outputCode[index] != 0)
            {
                index++;
                index += kD3DRootSignatureHeaderSize;
            }
            else
            {
                index++;
            }

            if (index >= compiled.shader.outputCode.Length)
            {
                throw new Exception("D3D shader bytecode missing from blob!");
            }

            byte[] stripped = new byte[compiled.shader.outputCode.Length - index];
            Array.Copy(compiled.shader.outputCode, index, stripped, 0, stripped.Length);
            byte[] shader = DXBCHelper.PatchConstantBuffersAndTextures(stripped, compiled.texToSampler);

            // Export bgfx file
            using (var ms = new MemoryStream())
                using (var bw = new BinaryWriter(ms))
                {
                    // Header
                    bw.Write(GetBgfxHeader(compiled.shader.type));

                    // Input/output hashes used for validating that vertex output matches fragment input
                    bw.Write(0u);
                    bw.Write(0u);

                    var uniformBufferSize = SerializeUniforms(compiled, bw, stage);

                    bw.Write((uint)shader.Length);
                    bw.Write(shader);
                    byte nul = 0;
                    bw.Write(nul);

                    // Attributes
                    if (compiled.shader.type == UnityHelper.ShaderGpuProgramType.kShaderGpuProgramDX11VertexSM40 || compiled.shader.type == UnityHelper.ShaderGpuProgramType.kShaderGpuProgramDX11VertexSM50 ||
                        compiled.shader.type == UnityHelper.ShaderGpuProgramType.kShaderGpuProgramDX11PixelSM40 || compiled.shader.type == UnityHelper.ShaderGpuProgramType.kShaderGpuProgramDX11PixelSM50)
                    {
                        // vertex attributes
                        bw.Write((byte)compiled.attributes.Count);
                        foreach (var attribute in compiled.attributes)
                        {
                            BgfxHelper.Attrib attrib = UnityToBgfx.ConvertAttribute(attribute);
                            bw.Write(BgfxHelper.s_AttribToId[attrib]);
                        }

                        // constant buffer size
                        bw.Write(uniformBufferSize);
                    }

                    return(ms.ToArray());
                }
        }