- 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:
2024-07-13 13:45:12 +02:00
parent cce6e00960
commit 92cf24fe9f
28 changed files with 420 additions and 978 deletions

View File

@ -5,12 +5,15 @@ public class BuildArgs
[Argument("-build")]
public bool Build { get; set; }
[Argument("-projectPath")]
public string ProjectPath { get; set; }
[Argument("-resourcePath")]
public string ResourcePath { get; set; }
[Argument("-resourceOutPath")]
public string ResourceOutPath { get; set; }
[Argument("-platform")]
public string Platform { get; set; }
[Argument("-content")]
public List<string> ContentFiles { get; set; }
[Argument("-resourceFiles")]
public List<string> ResourceFiles { get; set; }
}

View File

@ -11,13 +11,12 @@ public class Builder : IDisposable
{
rawFileImporter = new RawFileImporter();
ShaderImporter shaderImporter = new ShaderImporter();
importers.Add(".vert", shaderImporter); // Vertex shader
importers.Add(".frag", shaderImporter); // Fragment shader
importers.Add(".tesc", shaderImporter); // Tessellation control shader
importers.Add(".tese", shaderImporter); // Tessellation evaluation shader
importers.Add(".geom", shaderImporter); // Geometry shader
importers.Add(".comp", shaderImporter); // Compute shader
importers.Add(".vert", new ShaderImporter(ShaderStage.Vertex)); // Vertex shader
importers.Add(".frag", new ShaderImporter(ShaderStage.Fragment)); // Fragment shader
//importers.Add(".tesc", shaderImporter); // Tessellation control shader
//importers.Add(".tese", shaderImporter); // Tessellation evaluation shader
//importers.Add(".geom", shaderImporter); // Geometry shader
//importers.Add(".comp", shaderImporter); // Compute shader
}
public void Run(BuildArgs args)
@ -25,22 +24,19 @@ public class Builder : IDisposable
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
CopyLibs(args.ProjectPath);
//CopyLibs(args.ResourcePath);
List<string> contentFiles = args.ContentFiles;
string absContentPath = $"{args.ProjectPath}/{PathUtil.ContentFolderName}";
List<string> contentFiles = args.ResourceFiles;
// If no files are provided, build all content.
if (args.ContentFiles == null)
if (args.ResourceFiles == null)
{
contentFiles = [];
CollectAssetFiles(absContentPath, absContentPath, ref contentFiles);
CollectAssetFiles(args.ResourcePath, args.ResourcePath, ref contentFiles);
}
if (contentFiles.Count > 0)
{
string importPath = $"{args.ProjectPath}/{PathUtil.ImportFolderName}";
ParallelOptions parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = contentFiles.Count
@ -50,7 +46,7 @@ public class Builder : IDisposable
{
try
{
string inFile = $"{args.ProjectPath}/{PathUtil.ContentFolderName}/{relativeFile}";
string inFile = $"{args.ResourcePath}/{relativeFile}";
if (!File.Exists(inFile))
{
@ -58,7 +54,7 @@ public class Builder : IDisposable
return;
}
string outFile = $"{importPath}/{relativeFile}{PathUtil.ImportedFileExtension}";
string outFile = $"{args.ResourceOutPath}/{relativeFile}{PathUtil.ImportedFileExtension}";
FileInfo inFileInfo = new FileInfo(inFile);
FileInfo outFileInfo = new FileInfo(outFile);
@ -97,7 +93,7 @@ public class Builder : IDisposable
Console.WriteLine($"Build content completed in {stopwatch.Elapsed.TotalSeconds:F2} seconds");
}
private void CopyLibs(string projectPath)
/*private void CopyLibs(string projectPath)
{
string libDir = $"{AppDomain.CurrentDomain.BaseDirectory}/../../Native/";
if (OperatingSystem.IsWindows())
@ -123,17 +119,12 @@ public class Builder : IDisposable
FileUtil.Copy(srcFileInfo, dstFileInfo);
}
}
}
}*/
private void CollectAssetFiles(string assetDir, string dir, ref List<string> files)
{
foreach (string file in Directory.EnumerateFiles(dir))
{
if (Path.GetExtension(file).Equals(PathUtil.ImportFileExtension, StringComparison.CurrentCultureIgnoreCase))
{
continue;
}
string relativeFile = file.Substring(assetDir.Length, file.Length - assetDir.Length);
if (relativeFile[0] == Path.DirectorySeparatorChar || relativeFile[0] == Path.AltDirectorySeparatorChar)
{

View File

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

View File

@ -12,14 +12,22 @@
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Test|x64' ">
<Optimize>true</Optimize>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
<Optimize>true</Optimize>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Vortice.ShaderCompiler" Version="1.7.3" />
<PackageReference Include="Vortice.SPIRV.Reflect" Version="1.0.3" />
</ItemGroup>
</Project>

View File

@ -3,8 +3,4 @@ namespace Nerfed.Builder;
public static class PathUtil
{
public const string ImportedFileExtension = ".bin";
public const string BuildFolderName = ".build";
public const string ImportFileExtension = ".import";
public const string ImportFolderName = $"{BuildFolderName}/Import";
public const string ContentFolderName = "Content";
}