// 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); }
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 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 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); }