- 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:
@ -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; }
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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";
|
||||
}
|
||||
|
Reference in New Issue
Block a user