diff --git a/Checker/compression_test.fs b/Checker/compression_test.fs index 602d5cba..1785a153 100644 --- a/Checker/compression_test.fs +++ b/Checker/compression_test.fs @@ -1,5 +1,6 @@ module CompressionTests +open ShaderMinifier open System.Runtime.InteropServices open System.IO open System.Text @@ -52,8 +53,8 @@ let compressionTest args filenames = let minified = use out = new StringWriter() let files = [|for f in filenames -> f, File.ReadAllText("tests/real/" + f)|] - let shaders, exportedNames = ShaderMinifier.minify options files - ShaderMinifier.format options out shaders exportedNames + let minifier = Minifier(options, files) + minifier.Format(out) out.ToString().ToCharArray() let pointer = &&minified.[0] diff --git a/Checker/main.fs b/Checker/main.fs index 57c6de38..b899d915 100644 --- a/Checker/main.fs +++ b/Checker/main.fs @@ -5,6 +5,7 @@ open System.IO open Argu open System.Text.RegularExpressions +open ShaderMinifier type CliArguments = | Update_Golden @@ -135,8 +136,10 @@ let canBeCompiled lang stage content = canBeCompiledByGlslang lang stage fullsrc && ((lang = "hlsl") || canBeCompiledByDriver stage fullsrc) let doMinify options file content = - let arr = ShaderMinifier.minify options [|file, content|] |> fst |> Array.map (fun s -> s.code) - ShaderMinifier.print arr.[0] + let minifier = Minifier(options, [|file, content|]) + use tw = new System.IO.StringWriter() + minifier.Format(tw) + tw.ToString() let testMinifyAndCompile options lang (file: string) = try @@ -164,7 +167,7 @@ let testPerformance files = let contents = files |> Array.map File.ReadAllText let stopwatch = Stopwatch.StartNew() for str in contents do - let options = Options.init([|"--format"; "text"; "--no-remove-unused"; "fake.frag"|]) + let options = Options.init([|"--format"; "text"; "--no-remove-unused"|]) doMinify options "perf test" str |> ignore let time = stopwatch.Elapsed printfn "%i files minified in %f seconds." files.Length time.TotalSeconds @@ -177,22 +180,24 @@ let runCommand argv = let cleanString (s: string) = let s = s.Replace("\r\n", "\n").Trim() versionRegex.Replace(s, "") - let options, filenames = Options.initFiles argv + let options, filenames = Minifier.ParseOptionsWithFiles(argv) let expected = try File.ReadAllText options.outputName |> cleanString with _ when cliArgs.Contains(Update_Golden) -> "" | _ -> reraise () let files = [|for f in filenames -> f, File.ReadAllText(f)|] - let shaders, exportedNames = ShaderMinifier.minify options files + let minifier = Minifier(options, files) let result = use out = new StringWriter() - ShaderMinifier.format options out shaders exportedNames + minifier.Format(out) out.ToString() |> cleanString + let options = { options with outputFormat = Options.OutputFormat.IndentedText; exportKkpSymbolMaps = false} - for shader in shaders do + if filenames.Length = 1 then + let shader = minifier.GetShaders[0] let resultindented = use out = new StringWriter() - ShaderMinifier.format options out [|shader|] exportedNames + minifier.Format(out, options) out.ToString() |> cleanString let outdir = "tests/out/" + Regex.Replace(options.outputName, @"^tests/(.*)/[^/]*$", @"$1") + "/" let split = Regex.Match(shader.mangledFilename, @"(^.*)_([^_]+)$").Groups @@ -200,6 +205,7 @@ let runCommand argv = let ext = split[2].Value Directory.CreateDirectory outdir |> ignore File.WriteAllText(outdir + name + ".minind." + ext, resultindented + "\n") + if result = expected then printfn "Success: %s" options.outputName 0 @@ -265,7 +271,7 @@ let main argv = let realTests = Directory.GetFiles("tests/real", "*.frag") for f in unitTests do // tests with no #version default to 110 - let options = Options.init([|"--format"; "text"; "--no-remove-unused"; "fake.frag"|]) + let options = Options.init([|"--format"; "text"; "--no-remove-unused"|]) if not (testMinifyAndCompile options "110" f) then failures <- failures + 1 testPerformance (Seq.concat [realTests; unitTests] |> Seq.toArray) diff --git a/Example/Example.csproj b/Example/Example.csproj new file mode 100644 index 00000000..2a1316b1 --- /dev/null +++ b/Example/Example.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/Example/Program.cs b/Example/Program.cs new file mode 100644 index 00000000..aeb15274 --- /dev/null +++ b/Example/Program.cs @@ -0,0 +1,18 @@ +/** + * Example of use of the ShaderMinifier library from C#. + */ + +using ShaderMinifier; + +var shader = """ +out vec4 fragColor; +void main() +{ + fragColor = vec4(1., 1., 1., 1.); +} +"""; + +var file = Tuple.Create("filename.frag", shader); +var options = Minifier.ParseOptions(new[] { "--format", "text" }); +var minifier = new Minifier(options, new[] { file }); +minifier.Format(System.Console.Out); diff --git a/README.md b/README.md index 3000f389..9f359e0b 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,12 @@ shader is minified by hand by experienced demosceners, Shader Minifier is often able to optimize it further. See this [2010 report](https://www.ctrl-alt-test.fr/2010/glsl-minifier-smaller-and-smaller/). -To be notified of new releases, use the watch feature of GitHub. +Shader Minifier is available: +* As an online website: https://ctrl-alt-test.fr/minifier/ +* As a command-line tool (download from [Releases](https://github.com/laurentlb/shader-minifier/releases)) +* As a .NET library (see [Example](https://github.com/laurentlb/shader-minifier/tree/master/Example)) -Try the online version here: https://ctrl-alt-test.fr/minifier/ +To be notified of new releases, use the watch feature of GitHub. ## Features diff --git a/SMBolero.Client/Main.fs b/SMBolero.Client/Main.fs index d38a9e1e..826ee787 100644 --- a/SMBolero.Client/Main.fs +++ b/SMBolero.Client/Main.fs @@ -5,6 +5,7 @@ open Bolero open Bolero.Html open Bolero.Remoting.Client open Bolero.Templating.Client +open ShaderMinifier /// Routing endpoints definition. type Page = @@ -48,15 +49,15 @@ type Message = | ClearError let minify flags content = - let options = Options.init flags - let shaders, exportedNames = ShaderMinifier.minify options [|"input", content|] + let options = Minifier.ParseOptions(flags) + let minifier = Minifier(options, [|"input", content|]) let out = new System.IO.StringWriter() - ShaderMinifier.format options out shaders exportedNames + minifier.Format(out) let withLoc = new System.IO.StringWriter() - ShaderMinifier.formatWithLocations options withLoc shaders exportedNames + minifier.FormatWithLocations(withLoc) - out.ToString(), ShaderMinifier.getSize shaders, withLoc.ToString() + out.ToString(), minifier.GetSize, withLoc.ToString() module API = [] @@ -74,7 +75,7 @@ let update (jsRuntime: Microsoft.JSInterop.IJSRuntime) message model = if not model.flags.inlining then yield "--no-inlining" if not model.flags.removeUnused then yield "--no-remove-unused" if not model.flags.renaming then yield "--no-renaming" - yield! model.flags.other.Split(' ') + yield! model.flags.other.Split([|' '|], System.StringSplitOptions.RemoveEmptyEntries) |] printfn "Minify %s" (String.concat " " allFlags) try diff --git a/Shader Minifier.sln b/Shader Minifier.sln index 5bb3b99c..b14344bd 100644 --- a/Shader Minifier.sln +++ b/Shader Minifier.sln @@ -16,6 +16,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "SMBolero.Client", "SMBolero EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "SMBolero.Server", "SMBolero.Server\SMBolero.Server.fsproj", "{C1A4652F-0F92-4530-B1C8-2AAC9C383650}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "Example\Example.csproj", "{1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -84,6 +86,18 @@ Global {C1A4652F-0F92-4530-B1C8-2AAC9C383650}.Release|x64.Build.0 = Release|Any CPU {C1A4652F-0F92-4530-B1C8-2AAC9C383650}.Release|x86.ActiveCfg = Release|Any CPU {C1A4652F-0F92-4530-B1C8-2AAC9C383650}.Release|x86.Build.0 = Release|Any CPU + {1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}.Debug|x64.ActiveCfg = Debug|Any CPU + {1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}.Debug|x64.Build.0 = Debug|Any CPU + {1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}.Debug|x86.ActiveCfg = Debug|Any CPU + {1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}.Debug|x86.Build.0 = Debug|Any CPU + {1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}.Release|Any CPU.Build.0 = Release|Any CPU + {1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}.Release|x64.ActiveCfg = Release|Any CPU + {1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}.Release|x64.Build.0 = Release|Any CPU + {1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}.Release|x86.ActiveCfg = Release|Any CPU + {1C6C7E5F-41CF-456E-AEB2-8D501D43D2C5}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/api.fs b/src/api.fs index ed9465be..fd8a6c0f 100644 --- a/src/api.fs +++ b/src/api.fs @@ -1,41 +1,51 @@ -module ShaderMinifier +namespace ShaderMinifier open System.IO -let getSize (shaders: Ast.Shader[]) = - shaders |> Array.sumBy (fun s -> Printer.print s.code |> String.length) - -let minify (options: Options.Options) (files: (string*string)[]) = - // like printfn when verbose option is set - let vprintf fmt = fprintf (if options.verbose then stdout else TextWriter.Null) fmt - - let printSize (shaders: Ast.Shader[]) = - if options.verbose then - let length = getSize shaders - vprintf "Shader size is: %d\n" length - - let names = String.concat "," [for n, c in files -> $"'{n}' ({c.Length}b)"] - options.trace $"----- minifying {names}" - vprintf "Input file size is: %d\n" (files |> Array.sumBy (fun (_, s) -> s.Length)) - - let parseAndRewrite (filename, content) = - let shader = Parse.runParser options filename content - let code = - if shader.reorderFunctions then - Rewriter.reorderFunctions options shader.code - else shader.code - { shader with code = Rewriter.simplify options code } - - let shaders = Array.Parallel.map parseAndRewrite files - vprintf "Rewrite tricks applied. "; printSize shaders - - if options.noRenaming then - shaders, [] - else - let exportedNames = Renamer.rename options shaders - vprintf "Identifiers renamed. "; printSize shaders - shaders, exportedNames - -let format = Formatter.print -let formatWithLocations = Formatter.printWithLocations -let print = Printer.print +type Minifier(options, files) = + let getSize (shaders: Ast.Shader[]) = + shaders |> Seq.sumBy (fun s -> Printer.print s.code |> String.length) + + let minify (options: Options.Options) (files: (string*string)[]) = + // like printfn when verbose option is set + let vprintf fmt = fprintf (if options.verbose then stdout else TextWriter.Null) fmt + + let printSize (shaders: Ast.Shader[]) = + if options.verbose then + let length = getSize shaders + vprintf "Shader size is: %d\n" length + + let names = String.concat "," [for n, c in files -> $"'{n}' ({c.Length}b)"] + options.trace $"----- minifying {names}" + vprintf "Input file size is: %d\n" (files |> Array.sumBy (fun (_, s) -> s.Length)) + + let parseAndRewrite (filename, content) = + let shader = Parse.runParser options filename content + let code = + if shader.reorderFunctions then + Rewriter.reorderFunctions options shader.code + else shader.code + { shader with code = Rewriter.simplify options code } + + let shaders = Array.Parallel.map parseAndRewrite files + vprintf "Rewrite tricks applied. "; printSize shaders + + if options.noRenaming then + shaders, [||] + else + let exportedNames = Renamer.rename options (Seq.toArray shaders) |> List.toArray + vprintf "Identifiers renamed. "; printSize shaders + shaders, exportedNames + + let shaders, exportedNames = minify options files + + static member ParseOptions(flags) = Options.init flags + static member ParseOptionsWithFiles(flags) = Options.initFiles flags + + member _.GetSize = getSize shaders + member _.GetShaders = shaders + + member _.Format(writer) = Formatter.print options writer shaders exportedNames + member _.Format(writer, options) = + Formatter.print options writer shaders exportedNames + member _.FormatWithLocations(writer) = Formatter.printWithLocations options writer shaders exportedNames diff --git a/src/formatter.fs b/src/formatter.fs index fc9aa129..44c9ffe6 100644 --- a/src/formatter.fs +++ b/src/formatter.fs @@ -49,7 +49,7 @@ type private Impl(options: Options.Options, withLocations) = fprintfn out "#ifndef %s" macroName fprintfn out "# define %s" macroName - for value: Ast.ExportedName in List.sort exportedNames do + for value: Ast.ExportedName in Seq.sort exportedNames do fprintfn out "# define %s_%s \"%s\"" ((formatPrefix value.prefix).ToUpper()) value.name value.newName fprintfn out "" @@ -70,7 +70,7 @@ type private Impl(options: Options.Options, withLocations) = fprintfn out "#ifndef SHADER_MINIFIER_HEADER" fprintfn out "# define SHADER_MINIFIER_HEADER" - for value: Ast.ExportedName in List.sort exportedNames do + for value: Ast.ExportedName in Seq.sort exportedNames do fprintfn out "# define %s_%s \"%s\"" ((formatPrefix value.prefix).ToUpper()) value.name value.newName fprintfn out "#endif" @@ -106,7 +106,7 @@ type private Impl(options: Options.Options, withLocations) = let printJSHeader out (shaders: Ast.Shader[]) exportedNames = fprintfn out "// Generated with Shader Minifier %s (https://github.com/laurentlb/Shader_Minifier/)" Options.version - for value: Ast.ExportedName in List.sort exportedNames do + for value: Ast.ExportedName in Seq.sort exportedNames do fprintfn out "var %s_%s = \"%s\"" (formatPrefix value.prefix) (value.name.ToUpper()) value.newName fprintfn out "" @@ -120,7 +120,7 @@ type private Impl(options: Options.Options, withLocations) = fprintfn out "; Generated with Shader Minifier %s (https://github.com/laurentlb/Shader_Minifier/)" Options.version - for value: Ast.ExportedName in List.sort exportedNames do + for value: Ast.ExportedName in Seq.sort exportedNames do fprintfn out "_%s_%s: db '%s', 0" (formatPrefix value.prefix) (value.name.ToUpper()) value.newName fprintfn out "" @@ -138,7 +138,7 @@ type private Impl(options: Options.Options, withLocations) = let printRustHeader out (shaders: Ast.Shader[]) exportedNames = fprintfn out "// Generated with Shader Minifier %s (https://github.com/laurentlb/Shader_Minifier/)" Options.version - for value: Ast.ExportedName in List.sort exportedNames do + for value: Ast.ExportedName in Seq.sort exportedNames do fprintfn out "pub const %s_%s: &'static [u8] = b\"%s\\0\";" ((formatPrefix value.prefix).ToUpper()) (value.name.ToUpper()) value.newName for shader in shaders do diff --git a/src/main.fs b/src/main.fs index 6cdf377d..2237d1b1 100644 --- a/src/main.fs +++ b/src/main.fs @@ -1,5 +1,6 @@ module Main +open ShaderMinifier open System.IO let readFile file = @@ -8,21 +9,21 @@ let readFile file = else new StreamReader(file) stream.ReadToEnd() -let minifyFiles (options: Options.Options) filenames = +let minifyFiles (options: Options.Options) filenames out = let files = [| for f in filenames do let content = readFile f let filename = if f = "" then "stdin" else f yield filename, content |] - ShaderMinifier.minify options files + let minifier = Minifier(options, files) + minifier.Format(out) let run (options: Options.Options) filenames = use out = if Options.debugMode || options.outputName = "" || options.outputName = "-" then stdout else new StreamWriter(options.outputName) :> TextWriter try - let shaders, exportedNames = minifyFiles options filenames - ShaderMinifier.format options out shaders exportedNames + minifyFiles options filenames out 0 with exn -> printfn "%s" (exn.ToString()) diff --git a/src/options.fs b/src/options.fs index adf1b41a..ffa9c15d 100644 --- a/src/options.fs +++ b/src/options.fs @@ -128,7 +128,12 @@ let private initPrivate argv = let flagsHelp = lazy (argParser.Value.PrintUsage(message = helpTextMessage)) -let init argv = initPrivate argv |> fst +let init argv = + let options, filenames = initPrivate argv + if filenames.Length > 0 then + failwithf "Unexpected arguments: %A" (String.concat " " filenames) + options + let initFiles argv = let options, filenames = initPrivate argv if filenames.Length = 0 then diff --git a/tests/compile.txt b/tests/compile.txt index e2d821f1..c6d2075f 100644 --- a/tests/compile.txt +++ b/tests/compile.txt @@ -20,9 +20,6 @@ hlsl frag tests/out/real/elevated.minind.hlsl # illegal input (bad preprocessing?) - 110 frag tests/out/real/ohanami.minind.frag 430 comp tests/out/real/terrarium.minind.frag 430 comp tests/out/real/from-the-seas-to-the-stars.minind.frag -110 vert tests/out/real/mouton/mouton.minind.vert -110 frag tests/out/real/mouton/mouton.minind.frag -110 frag tests/out/real/mouton/fxaa.minind.frag shadertoy frag tests/out/real/ed-209.minind.frag shadertoy frag tests/out/real/frozen-wasteland.minind.frag