using System; using System.IO; using System.Diagnostics; partial class Program { struct CompileShaderData { public string glslPath; public string outputDir; public bool preserveTemp; public bool vulkan; public bool d3d11; public bool ps5; } private static void DisplayHelpText() { Console.WriteLine("Usage: refreshc "); Console.WriteLine("Options:"); Console.WriteLine(" --vulkan Emit shader compatible with the Refresh Vulkan backend"); Console.WriteLine(" --d3d11 Emit shader compatible with the Refresh D3D11 backend"); Console.WriteLine(" --ps5 Emit shader compatible with the Refresh PS5 backend"); Console.WriteLine(" --out dir Write output file(s) to the directory `dir`"); Console.WriteLine(" --preserve-temp Do not delete the temp directory after compilation. Useful for debugging."); } public static int Main(string[] args) { if (args.Length == 0) { DisplayHelpText(); return 1; } CompileShaderData data = new CompileShaderData(); string inputPath = null; for (int i = 0; i < args.Length; i += 1) { switch (args[i]) { case "--vulkan": data.vulkan = true; break; case "--d3d11": data.d3d11 = true; break; case "--ps5": data.ps5 = true; break; case "--out": i += 1; data.outputDir = args[i]; break; case "--preserve-temp": data.preserveTemp = true; break; default: if (inputPath == null) { inputPath = args[i]; } else { Console.WriteLine($"refreshc: Unknown parameter {args[i]}"); return 1; } break; } } if (!data.vulkan && !data.d3d11 && !data.ps5) { Console.WriteLine($"refreshc: No Refresh platforms selected!"); return 1; } #if !PS5 if (data.ps5) { Console.WriteLine($"refreshc: `PS5` must be defined in the to target the PS5 backend!"); return 1; } #endif if (data.outputDir == null) { data.outputDir = Directory.GetCurrentDirectory(); } else if (!Directory.Exists(data.outputDir)) { Console.WriteLine($"refreshc: Output directory {data.outputDir} does not exist"); return 1; } if (Directory.Exists(inputPath)) { // Loop over and compile each file in the directory string[] files = Directory.GetFiles(inputPath); foreach (string file in files) { Console.WriteLine($"Compiling {file}"); data.glslPath = file; int res = CompileShader(ref data); if (res != 0) { return res; } } } else { if (!File.Exists(inputPath)) { Console.WriteLine($"refreshc: glsl source file or directory ({inputPath}) does not exist"); return 1; } data.glslPath = inputPath; int res = CompileShader(ref data); if (res != 0) { return res; } } return 0; } static int CompileShader(ref CompileShaderData data) { int res = 0; string shaderName = Path.GetFileNameWithoutExtension(data.glslPath); string shaderType = Path.GetExtension(data.glslPath); if (shaderType != ".vert" && shaderType != ".frag" && shaderType != ".comp") { Console.WriteLine("refreshc: Expected glsl source file with extension '.vert', '.frag', or '.comp'"); return 1; } // Create the temp directory, if needed string tempDir = Path.Combine(Directory.GetCurrentDirectory(), "temp"); if (!Directory.Exists(tempDir)) { Directory.CreateDirectory(tempDir); } // Compile to spirv string spirvPath = Path.Combine(tempDir, $"{shaderName}.spv"); res = CompileGlslToSpirv(data.glslPath, shaderName, spirvPath); if (res != 0) { goto cleanup; } if (data.d3d11 || data.ps5) { // Transpile to hlsl string hlslPath = Path.Combine(tempDir, $"{shaderName}.hlsl"); res = TranslateSpirvToHlsl(spirvPath, hlslPath); if (res != 0) { goto cleanup; } // FIXME: Is there a cross-platform way to compile HLSL to DXBC? #if PS5 // Transpile to ps5, if requested if (data.ps5) { res = TranslateHlslToPS5(hlslPath, shaderName, shaderType, tempDir); if (res != 0) { goto cleanup; } } #endif } // Create the output blob file string outputFilepath = Path.Combine(data.outputDir, $"{shaderName}{shaderType}.refresh"); using (FileStream fs = File.Create(outputFilepath)) { using (BinaryWriter writer = new BinaryWriter(fs)) { // Magic writer.Write(new char[] { 'R', 'F', 'S', 'H'}); if (data.vulkan) { string inputPath = Path.Combine(tempDir, $"{shaderName}.spv"); WriteShaderBlob(writer, inputPath, 1); } #if PS5 if (data.ps5) { string ext = GetPS5ShaderFileExtension(); string inputPath = Path.Combine(tempDir, $"{shaderName}{ext}"); WriteShaderBlob(writer, inputPath, 2); } #endif if (data.d3d11) { string inputPath = Path.Combine(tempDir, $"{shaderName}.hlsl"); WriteShaderBlob(writer, inputPath, 3); } } } cleanup: // Clean up the temp directory if (!data.preserveTemp) { Directory.Delete(tempDir, true); } return res; } static void WriteShaderBlob(BinaryWriter writer, string inputPath, byte backend) { byte[] shaderBlob = File.ReadAllBytes(inputPath); writer.Write(backend); // Corresponds to Refresh_Backend writer.Write(shaderBlob.Length); writer.Write(shaderBlob); } static int CompileGlslToSpirv(string glslPath, string shaderName, string outputPath) { Process glslc = Process.Start( "glslc", $"{glslPath} -o {outputPath}" ); glslc.WaitForExit(); if (glslc.ExitCode != 0) { Console.WriteLine($"refreshc: Could not compile GLSL code"); return 1; } return 0; } static int TranslateSpirvToHlsl(string spirvPath, string outputPath) { Process spirvcross = Process.Start( "spirv-cross", $"{spirvPath} --hlsl --shader-model 50 --output {outputPath}" ); spirvcross.WaitForExit(); if (spirvcross.ExitCode != 0) { Console.WriteLine($"refreshc: Could not translate SPIR-V to HLSL"); return 1; } return 0; } }