Nerfed/Nerfed.Builder/Builder/Importers/ShaderImporter.cs
robert 92cf24fe9f - Added resource manager
- Shader building now inspects the spir-v for descriptor sets and writes the info to the output binary
2024-07-13 13:45:12 +02:00

158 lines
5.0 KiB
C#

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