// SPDX-License-Identifier: Unlicense OR MIT package main import ( "bytes" "errors" "flag" "fmt" "io" "io/ioutil" "os" "os/exec" "path/filepath" "sort" "strconv" "strings" "sync" "text/template" "gioui.org/gpu/internal/driver" ) func main() { packageName := flag.String("package", "", "specify Go package name") workdir := flag.String("work", "", "temporary working directory (default TEMP)") shadersDir := flag.String("dir", "shaders", "shaders directory") directCompute := flag.Bool("directcompute", false, "enable compiling DirectCompute shaders") flag.Parse() var work WorkDir cleanup := func() {} if *workdir == "" { tempdir, err := ioutil.TempDir("", "shader-convert") if err != nil { fmt.Fprintf(os.Stderr, "failed to create tempdir: %v\n", err) os.Exit(1) } cleanup = func() { os.RemoveAll(tempdir) } defer cleanup() work = WorkDir(tempdir) } else { if abs, err := filepath.Abs(*workdir); err == nil { *workdir = abs } work = WorkDir(*workdir) } var out bytes.Buffer conv := NewConverter(work, *packageName, *shadersDir, *directCompute) if err := conv.Run(&out); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) cleanup() os.Exit(1) } if err := ioutil.WriteFile("shaders.go", out.Bytes(), 0644); err != nil { fmt.Fprintf(os.Stderr, "failed to create shaders: %v\n", err) cleanup() os.Exit(1) } cmd := exec.Command("gofmt", "-s", "-w", "shaders.go") cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr if err := cmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "formatting shaders.go failed: %v\n", err) cleanup() os.Exit(1) } } type Converter struct { workDir WorkDir shadersDir string directCompute bool packageName string glslcc *GLSLCC glslvalidator *GLSLValidator spirv *SPIRVCross fxc *FXC dxc *DXC } func NewConverter(workDir WorkDir, packageName, shadersDir string, directCompute bool) *Converter { if abs, err := filepath.Abs(shadersDir); err == nil { shadersDir = abs } conv := &Converter{} conv.workDir = workDir conv.shadersDir = shadersDir conv.directCompute = directCompute conv.packageName = packageName conv.glslcc = NewGLSLCC() conv.glslvalidator = NewGLSLValidator() conv.spirv = NewSPIRVCross() conv.fxc = NewFXC() conv.dxc = NewDXC() verifyBinaryPath(&conv.glslcc.Bin) verifyBinaryPath(&conv.glslvalidator.Bin) verifyBinaryPath(&conv.spirv.Bin) // we cannot check fxc/dxc, since they may depend on wine conv.glslcc.IncludeDir = shadersDir conv.glslcc.WorkDir = workDir.Dir("glslcc") conv.glslvalidator.WorkDir = workDir.Dir("glslvalidator") conv.fxc.WorkDir = workDir.Dir("fxc") conv.dxc.WorkDir = workDir.Dir("dxc") return conv } func verifyBinaryPath(bin *string) { new, err := exec.LookPath(*bin) if err != nil { fmt.Fprintf(os.Stderr, "unable to find %q: %v\n", *bin, err) } else { *bin = new } } func (conv *Converter) Run(out io.Writer) error { shaders, err := filepath.Glob(filepath.Join(conv.shadersDir, "*")) if len(shaders) == 0 || err != nil { return fmt.Errorf("failed to list shaders in %q: %w", conv.shadersDir, err) } sort.Strings(shaders) var workers Workers type ShaderResult struct { Path string Shaders []driver.ShaderSources Error error } shaderResults := make([]ShaderResult, len(shaders)) for i, shaderPath := range shaders { i, shaderPath := i, shaderPath switch filepath.Ext(shaderPath) { case ".vert", ".frag": workers.Go(func() { shaders, err := conv.Shader(shaderPath) shaderResults[i] = ShaderResult{ Path: shaderPath, Shaders: shaders, Error: err, } }) case ".comp": workers.Go(func() { shaders, err := conv.ComputeShader(shaderPath) shaderResults[i] = ShaderResult{ Path: shaderPath, Shaders: shaders, Error: err, } }) default: continue } } workers.Wait() var allErrors string for _, r := range shaderResults { if r.Error != nil { if len(allErrors) > 0 { allErrors += "\n\n" } allErrors += "--- " + r.Path + " --- \n\n" + r.Error.Error() + "\n" } } if len(allErrors) > 0 { return errors.New(allErrors) } fmt.Fprintf(out, "// Code generated by build.go. DO NOT EDIT.\n\n") fmt.Fprintf(out, "package %s\n\n", conv.packageName) fmt.Fprintf(out, "import %q\n\n", "gioui.org/gpu/internal/driver") fmt.Fprintf(out, "var (\n") for _, r := range shaderResults { if len(r.Shaders) == 0 { continue } name := filepath.Base(r.Path) name = strings.ReplaceAll(name, ".", "_") fmt.Fprintf(out, "\tshader_%s = ", name) multiVariant := len(r.Shaders) > 1 if multiVariant { fmt.Fprintf(out, "[...]driver.ShaderSources{\n") } for _, src := range r.Shaders { fmt.Fprintf(out, "driver.ShaderSources{\n") fmt.Fprintf(out, "Name: %#v,\n", src.Name) if len(src.Inputs) > 0 { fmt.Fprintf(out, "Inputs: %#v,\n", src.Inputs) } if u := src.Uniforms; len(u.Blocks) > 0 { fmt.Fprintf(out, "Uniforms: driver.UniformsReflection{\n") fmt.Fprintf(out, "Blocks: %#v,\n", u.Blocks) fmt.Fprintf(out, "Locations: %#v,\n", u.Locations) fmt.Fprintf(out, "Size: %d,\n", u.Size) fmt.Fprintf(out, "},\n") } if len(src.Textures) > 0 { fmt.Fprintf(out, "Textures: %#v,\n", src.Textures) } if len(src.GLSL100ES) > 0 { fmt.Fprintf(out, "GLSL100ES: %#v,\n", src.GLSL100ES) } if len(src.GLSL300ES) > 0 { fmt.Fprintf(out, "GLSL300ES: %#v,\n", src.GLSL300ES) } if len(src.GLSL310ES) > 0 { fmt.Fprintf(out, "GLSL310ES: %#v,\n", src.GLSL310ES) } if len(src.GLSL130) > 0 { fmt.Fprintf(out, "GLSL130: %#v,\n", src.GLSL130) } if len(src.GLSL150) > 0 { fmt.Fprintf(out, "GLSL150: %#v,\n", src.GLSL150) } if len(src.HLSL) > 0 { fmt.Fprintf(out, "HLSL: %#v,\n", src.HLSL) } fmt.Fprintf(out, "}") if multiVariant { fmt.Fprintf(out, ",") } fmt.Fprintf(out, "\n") } if multiVariant { fmt.Fprintf(out, "}\n") } } fmt.Fprintf(out, ")\n") return nil } func (conv *Converter) Shader(shaderPath string) ([]driver.ShaderSources, error) { type Variant struct { FetchColorExpr string Header string } variantArgs := [...]Variant{ { FetchColorExpr: `_color`, Header: `layout(binding=0) uniform Color { vec4 _color; };`, }, { FetchColorExpr: `mix(_color1, _color2, clamp(vUV.x, 0.0, 1.0))`, Header: `layout(binding=0) uniform Gradient { vec4 _color1; vec4 _color2; };`, }, { FetchColorExpr: `texture(tex, vUV)`, Header: `layout(binding=0) uniform sampler2D tex;`, }, } shaderTemplate, err := template.ParseFiles(shaderPath) if err != nil { return nil, fmt.Errorf("failed to parse template %q: %w", shaderPath, err) } var variants []driver.ShaderSources for i, variantArg := range variantArgs { variantName := strconv.Itoa(i) var buf bytes.Buffer err := shaderTemplate.Execute(&buf, variantArg) if err != nil { return nil, fmt.Errorf("failed to execute template %q with %#v: %w", shaderPath, variantArg, err) } var sources driver.ShaderSources sources.Name = filepath.Base(shaderPath) // Ignore error; some shaders are not meant to run in GLSL 1.00. sources.GLSL100ES, _, _ = conv.glslcc.Convert(shaderPath, variantName, buf.Bytes(), "gles", "100") var metadata Metadata sources.GLSL300ES, metadata, err = conv.glslcc.Convert(shaderPath, variantName, buf.Bytes(), "gles", "300") if err != nil { return nil, fmt.Errorf("failed to convert GLSL300ES:\n%w", err) } sources.GLSL130, _, err = conv.glslcc.Convert(shaderPath, variantName, buf.Bytes(), "glsl", "130") if err != nil { return nil, fmt.Errorf("failed to convert GLSL130:\n%w", err) } hlsl, _, err := conv.glslcc.Convert(shaderPath, variantName, buf.Bytes(), "hlsl", "40") if err != nil { return nil, fmt.Errorf("failed to convert HLSL:\n%w", err) } sources.HLSL, err = conv.fxc.Compile(shaderPath, variantName, []byte(hlsl), "main", "4_0_level_9_1") if err != nil { // Attempt shader model 4.0. Only the app/headless // test shaders use features not supported by level // 9.1. sources.HLSL, err = conv.fxc.Compile(shaderPath, variantName, []byte(hlsl), "main", "4_0") if err != nil { return nil, fmt.Errorf("failed to compile HLSL: %w", err) } } // OpenGL 3.2 Core only accepts GLSL version 1.50, but is // otherwise compatible with version 1.30. sources.GLSL150 = strings.Replace(sources.GLSL130, "#version 130", "#version 150", 1) sources.Uniforms = metadata.Uniforms sources.Inputs = metadata.Inputs sources.Textures = metadata.Textures sources.StorageBuffers = metadata.StorageBuffers variants = append(variants, sources) } // If the shader don't use the variant arguments, output only a single version. if variants[0].GLSL100ES == variants[1].GLSL100ES { variants = variants[:1] } return variants, nil } func (conv *Converter) ComputeShader(shaderPath string) ([]driver.ShaderSources, error) { shader, err := ioutil.ReadFile(shaderPath) if err != nil { return nil, fmt.Errorf("failed to load shader %q: %w", shaderPath, err) } spirv, err := conv.glslvalidator.ConvertCompute(shaderPath, shader) if err != nil { return nil, fmt.Errorf("failed to convert compute shader %q: %w", shaderPath, err) } var sources driver.ShaderSources sources.Name = filepath.Base(shaderPath) sources.GLSL310ES, err = conv.spirv.Convert(spirv, "es", "310") if err != nil { return nil, fmt.Errorf("failed to convert es compute shader %q: %w", shaderPath, err) } sources.GLSL310ES = unixLineEnding(sources.GLSL310ES) if conv.directCompute { hlslSource, err := conv.spirv.Convert(spirv, "hlsl", "50") if err != nil { return nil, fmt.Errorf("failed to convert hlsl compute shader %q: %w", shaderPath, err) } sources.HLSL, err = conv.dxc.Compile(shaderPath, "0", []byte(hlslSource), "main", "cs_5_0") if err != nil { return nil, fmt.Errorf("failed to compile hlsl compute shader %q: %w", shaderPath, err) } } return []driver.ShaderSources{sources}, nil } // Workers implements wait group with synchronous logging. type Workers struct { running sync.WaitGroup } func (lg *Workers) Go(fn func()) { lg.running.Add(1) go func() { defer lg.running.Done() fn() }() } func (lg *Workers) Wait() { lg.running.Wait() } func unixLineEnding(s string) string { return strings.ReplaceAll(s, "\r\n", "\n") }