- Added resource manager
- Shader building now inspects the spir-v for descriptor sets and writes the info to the output binary
This commit is contained in:
@ -1,33 +1,157 @@
|
||||
using System.Diagnostics;
|
||||
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
|
||||
{
|
||||
public void Import(string inFile, string outFile)
|
||||
private readonly ShaderStage shaderStage;
|
||||
|
||||
public ShaderImporter(ShaderStage shaderStage)
|
||||
{
|
||||
using (Process proc = new Process())
|
||||
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 glslc;
|
||||
if (OperatingSystem.IsWindows())
|
||||
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
|
||||
))
|
||||
{
|
||||
glslc = "Win64/glslc.exe";
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
glslc = "Linux/glslc";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException("No shader compiler found for current platform");
|
||||
return;
|
||||
}
|
||||
|
||||
proc.StartInfo.FileName = glslc;
|
||||
proc.StartInfo.Arguments = @$"""{inFile}"" -o ""{outFile}"" -c";
|
||||
proc.StartInfo.CreateNoWindow = true;
|
||||
proc.StartInfo.UseShellExecute = false;
|
||||
proc.Start();
|
||||
proc.WaitForExit();
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user