internal static void Serialize(CompiledShaderData vert, CompiledShaderData frag, string name, ref byte[] serializedVert, ref byte[] serializedFrag) { List <VarName> attributes = new List <VarName>(); serializedVert = CreateSerializedBgfxShader(vert, Stage.Vertex, attributes); serializedFrag = CreateSerializedBgfxShader(frag, Stage.Fragment, attributes); }
/// <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()); } }
static void ExtractShaderStages( CompiledShaderData compiled, bool gles2, string name, out CompiledShaderData compiledVertex, out CompiledShaderData compiledFragment) { var src = Encoding.UTF8.GetString(compiled.shader.outputCode, 0, compiled.shader.outputCode.Length); ExtractShaderStage(src, "VERTEX", out string vertSrc, out string remainder); ExtractShaderStage(remainder, "FRAGMENT", out string fragSrc, out string remainder2); Assert.IsTrue(remainder2.Equals(string.Empty)); compiledVertex = FixupShader(vertSrc, Stage.Vertex, gles2, name, compiled.attributes); compiledFragment = FixupShader(fragSrc, Stage.Fragment, gles2, name, null); }
/// <remarks> /// Matches output file generated by shaderc /// </remarks> static byte[] CreateSerializedBgfxShader(CompiledShaderData compiled, Stage stage) { 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); SerializeUniforms(compiled, bw, stage); bw.Write((uint)compiled.shader.outputCode.Length); bw.Write(compiled.shader.outputCode); byte nul = 0; bw.Write(nul); return(ms.ToArray()); } }
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 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); } }
internal static void Serialize(CompiledShaderData compiled, string name, bool gles2, ref byte[] serializedVert, ref byte[] serializedFrag) { ExtractShaderStages(compiled, gles2, name, out CompiledShaderData compiledVert, out CompiledShaderData compiledFragm); serializedVert = CreateSerializedBgfxShader(compiledVert, Stage.Vertex); serializedFrag = CreateSerializedBgfxShader(compiledFragm, Stage.Fragment); }
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); }
internal static void Serialize(CompiledShaderData vert, CompiledShaderData frag, string name, ref byte[] serializedVert, ref byte[] serializedFrag) { serializedVert = CreateSerializedBgfxShader(vert, Stage.Vertex); serializedFrag = CreateSerializedBgfxShader(frag, Stage.Fragment); }
/// <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()); } }