using Vortice.ShaderCompiler; using Vortice.SPIRV.Reflect; namespace Nerfed.Builder; // Values should match the ShaderStage enum in the runtime. public enum ShaderStage { Vertex, Fragment } // Values should match the ShaderFormat enum in the runtime. public enum ShaderFormat { Invalid, SPIRV, HLSL, DXBC, DXIL, MSL, METALLIB, SECRET } public class ShaderImporter : IImporter { private readonly ShaderStage shaderStage; public ShaderImporter(ShaderStage shaderStage) { this.shaderStage = shaderStage; } public unsafe void Import(string inFile, string outFile) { string name = Path.GetFileNameWithoutExtension(inFile); string nameWithExt = Path.GetFileName(inFile); // Compile the shader. Result compileResult; using (Compiler compiler = new Compiler()) { string shaderSource = File.ReadAllText(inFile); compileResult = compiler.Compile(shaderSource, nameWithExt, ToShaderKind(shaderStage)); } if (compileResult.Status != CompilationStatus.Success) { Console.Error.WriteLine($"Failed to compile {nameWithExt}\n{compileResult.ErrorMessage}"); return; } if (compileResult.ErrorsCount > 0 || compileResult.WarningsCount > 0) { Console.Error.WriteLine(compileResult.ErrorMessage); } Span byteCode = compileResult.GetBytecode(); // Inspect SPIR-V bytecode for information which the runtime requires to create a shader resource. SpvReflectShaderModule module = new SpvReflectShaderModule(); if (!CheckReflectResult(SPIRVReflectApi.spvReflectCreateShaderModule(byteCode, &module), name)) { return; } uint descriptorSetCount = 0; if (!CheckReflectResult(SPIRVReflectApi.spvReflectEnumerateDescriptorSets(&module, &descriptorSetCount, null), name)) { return; } int uniformBufferCount = 0; int storageBufferCount = 0; int storageTextureCount = 0; int samplerCount = 0; if (descriptorSetCount > 0) { SpvReflectDescriptorSet* descriptorSets = stackalloc SpvReflectDescriptorSet[(int)descriptorSetCount]; if (!CheckReflectResult( SPIRVReflectApi.spvReflectEnumerateDescriptorSets(&module, &descriptorSetCount, &descriptorSets), name )) { return; } for (int i = 0; i < descriptorSetCount; i++) { SpvReflectDescriptorSet set = descriptorSets[i]; for (int j = 0; j < set.binding_count; j++) { SpvReflectDescriptorBinding binding = *set.bindings[j]; if (binding.descriptor_type == SpvReflectDescriptorType.UniformBuffer) { uniformBufferCount++; } else if (binding.descriptor_type == SpvReflectDescriptorType.StorageBuffer) { storageBufferCount++; } else if (binding.descriptor_type == SpvReflectDescriptorType.StorageTexelBuffer) { storageTextureCount++; } else if (binding.descriptor_type == SpvReflectDescriptorType.Sampler || binding.descriptor_type == SpvReflectDescriptorType.CombinedImageSampler) { samplerCount++; } } } } //TODO: Convert SPIR-V to other bytecode formats here (DX/Consoles). ShaderFormat format = ShaderFormat.SPIRV; // Write shader meta-data and bytecode to the output file. using (FileStream stream = new FileStream(outFile, FileMode.Create, FileAccess.Write)) { using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Write((int)format); writer.Write((int)shaderStage); writer.Write(uniformBufferCount); writer.Write(storageBufferCount); writer.Write(storageTextureCount); writer.Write(samplerCount); writer.Write(byteCode.Length); writer.Write(byteCode); } } } private bool CheckReflectResult(SpvReflectResult result, string name) { if (result != SpvReflectResult.Success) { Console.Error.WriteLine($"SpirV-Reflect failure for '{name}': {result}"); return false; } return true; } private ShaderKind ToShaderKind(ShaderStage shaderStage) { switch (shaderStage) { case ShaderStage.Vertex: return ShaderKind.VertexShader; case ShaderStage.Fragment: return ShaderKind.FragmentShader; default: throw new ArgumentOutOfRangeException(nameof(shaderStage)); } } }