From 97a21332071aceeef6f5035178a3523177570448 Mon Sep 17 00:00:00 2001
From: Mary <me@thog.eu>
Date: Sun, 18 Jul 2021 12:49:39 +0200
Subject: [PATCH] shadertools: Prepare for new target Languages and APIs
 (#2465)

* shadertools: Prepare for new target Langugaes and APIs

This improves shader tools command line by adding support for target
language and api.

* Address gdkchan's comments
---
 Ryujinx.Graphics.Shader/ShaderProgram.cs      | 14 +++-
 .../Translation/TargetLanguage.cs             |  3 +-
 .../Translation/Translator.cs                 | 14 +++-
 Ryujinx.ShaderTools/Program.cs                | 71 +++++++++++++++----
 .../Ryujinx.ShaderTools.csproj                |  4 ++
 5 files changed, 87 insertions(+), 19 deletions(-)

diff --git a/Ryujinx.Graphics.Shader/ShaderProgram.cs b/Ryujinx.Graphics.Shader/ShaderProgram.cs
index 4a6bfea9..dd87b67d 100644
--- a/Ryujinx.Graphics.Shader/ShaderProgram.cs
+++ b/Ryujinx.Graphics.Shader/ShaderProgram.cs
@@ -7,11 +7,21 @@ namespace Ryujinx.Graphics.Shader
         public ShaderStage Stage { get; }
 
         public string Code { get; private set; }
+        public byte[] BinaryCode { get; }
 
-        public ShaderProgram(ShaderStage stage, string code)
+        private ShaderProgram(ShaderStage stage)
         {
             Stage = stage;
-            Code  = code;
+        }
+
+        public ShaderProgram(ShaderStage stage, string code) : this(stage)
+        {
+            Code = code;
+        }
+
+        public ShaderProgram(ShaderStage stage, byte[] binaryCode) : this(stage)
+        {
+            BinaryCode = binaryCode;
         }
 
         public void Prepend(string line)
diff --git a/Ryujinx.Graphics.Shader/Translation/TargetLanguage.cs b/Ryujinx.Graphics.Shader/Translation/TargetLanguage.cs
index db839e94..8314b223 100644
--- a/Ryujinx.Graphics.Shader/Translation/TargetLanguage.cs
+++ b/Ryujinx.Graphics.Shader/Translation/TargetLanguage.cs
@@ -3,6 +3,7 @@ namespace Ryujinx.Graphics.Shader.Translation
     public enum TargetLanguage
     {
         Glsl,
-        Spirv
+        Spirv,
+        Arb
     }
 }
diff --git a/Ryujinx.Graphics.Shader/Translation/Translator.cs b/Ryujinx.Graphics.Shader/Translation/Translator.cs
index 0f71b599..685b6a20 100644
--- a/Ryujinx.Graphics.Shader/Translation/Translator.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Translator.cs
@@ -3,6 +3,7 @@ using Ryujinx.Graphics.Shader.Decoders;
 using Ryujinx.Graphics.Shader.IntermediateRepresentation;
 using Ryujinx.Graphics.Shader.StructuredIr;
 using Ryujinx.Graphics.Shader.Translation.Optimizations;
+using System;
 using System.Collections.Generic;
 
 using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
@@ -87,7 +88,16 @@ namespace Ryujinx.Graphics.Shader.Translation
 
             StructuredProgramInfo sInfo = StructuredProgram.MakeStructuredProgram(funcs, config);
 
-            string glslCode = GlslGenerator.Generate(sInfo, config);
+            ShaderProgram program;
+
+            switch (config.Options.TargetLanguage)
+            {
+                case TargetLanguage.Glsl:
+                    program = new ShaderProgram(config.Stage, GlslGenerator.Generate(sInfo, config));
+                    break;
+                default:
+                    throw new NotImplementedException(config.Options.TargetLanguage.ToString());
+            }
 
             shaderProgramInfo = new ShaderProgramInfo(
                 config.GetConstantBufferDescriptors(),
@@ -97,7 +107,7 @@ namespace Ryujinx.Graphics.Shader.Translation
                 config.UsedFeatures.HasFlag(FeatureFlags.InstanceId),
                 config.ClipDistancesWritten);
 
-            return new ShaderProgram(config.Stage, glslCode);
+            return program;
         }
 
         private static Block[][] DecodeShader(
diff --git a/Ryujinx.ShaderTools/Program.cs b/Ryujinx.ShaderTools/Program.cs
index 17c242a9..fba0b3ad 100644
--- a/Ryujinx.ShaderTools/Program.cs
+++ b/Ryujinx.ShaderTools/Program.cs
@@ -1,4 +1,5 @@
-using Ryujinx.Graphics.Shader;
+using CommandLine;
+using Ryujinx.Graphics.Shader;
 using Ryujinx.Graphics.Shader.Translation;
 using System;
 using System.IO;
@@ -23,28 +24,70 @@ namespace Ryujinx.ShaderTools
             }
         }
 
-        static void Main(string[] args)
+        private class Options
         {
-            if (args.Length == 1 || args.Length == 2)
+            [Option("compute", Required = false, Default = false, HelpText = "Indicate that the shader is a compute shader.")]
+            public bool Compute { get; set; }
+
+            [Option("target-language", Required = false, Default = TargetLanguage.Glsl, HelpText = "Indicate the target shader language to use.")]
+            public TargetLanguage TargetLanguage { get; set; }
+
+            [Option("target-api", Required = false, Default = TargetApi.OpenGL, HelpText = "Indicate the target graphics api to use.")]
+            public TargetApi TargetApi { get; set; }
+
+            [Value(0, MetaName = "input", HelpText = "Binary Maxwell shader input path.", Required = true)]
+            public string InputPath { get; set; }
+
+            [Value(1, MetaName = "output", HelpText = "Decompiled shader output path.", Required = false)]
+            public string OutputPath { get; set; }
+        }
+
+        static void HandleArguments(Options options)
+        {
+            TranslationFlags flags = TranslationFlags.DebugMode;
+
+            if (options.Compute)
             {
-                TranslationFlags flags = TranslationFlags.DebugMode;
+                flags |= TranslationFlags.Compute;
+            }
 
-                if (args.Length == 2 && args[0] == "--compute")
+            byte[] data = File.ReadAllBytes(options.InputPath);
+
+            TranslationOptions translationOptions = new TranslationOptions(options.TargetLanguage, options.TargetApi, flags);
+
+            ShaderProgram program = Translator.CreateContext(0, new GpuAccessor(data), translationOptions).Translate(out _);
+
+            if (options.OutputPath == null)
+            {
+                if (program.BinaryCode != null)
                 {
-                    flags |= TranslationFlags.Compute;
+                    using Stream outputStream = Console.OpenStandardOutput();
+
+                    outputStream.Write(program.BinaryCode);
+                }
+                else
+                {
+                    Console.WriteLine(program.Code);
                 }
-
-                byte[] data = File.ReadAllBytes(args[^1]);
-
-                TranslationOptions options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags);
-                string code = Translator.CreateContext(0, new GpuAccessor(data), options).Translate(out _).Code;
-
-                Console.WriteLine(code);
             }
             else
             {
-                Console.WriteLine("Usage: Ryujinx.ShaderTools [--compute] shader.bin");
+                if (program.BinaryCode != null)
+                {
+                    File.WriteAllBytes(options.OutputPath, program.BinaryCode);
+                }
+                else
+                {
+                    File.WriteAllText(options.OutputPath, program.Code);
+                }
             }
         }
+
+        static void Main(string[] args)
+        {
+            Parser.Default.ParseArguments<Options>(args)
+            .WithParsed(options => HandleArguments(options))
+            .WithNotParsed(errors => errors.Output());
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj b/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj
index 03872c45..e11559d5 100644
--- a/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj
+++ b/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj
@@ -11,4 +11,8 @@
     <ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
   </ItemGroup>
 
+  <ItemGroup>
+    <PackageReference Include="CommandLineParser" Version="2.8.0" />
+  </ItemGroup>
+
 </Project>