mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
c799452c57
There are no longer any importers of package backend outside of gioui.org/gpu. Move it internally, and rename it to the slightly more specific "driver" while we're at it. Signed-off-by: Elias Naur <mail@eliasnaur.com>
399 lines
10 KiB
Go
399 lines
10 KiB
Go
// 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")
|
|
}
|