mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-04 00:45:35 +00:00
gpu/internal/convertshaders: move internal/cmd/convertshaders
The convert program is only used by the shaders from package gpu, and we're about to make the backend package imported by the program internal to package gpu. Move the converter below package gpu. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gioui.org/gpu/backend"
|
||||
)
|
||||
|
||||
// GLSLCC is a shader cross-compilation tool.
|
||||
type GLSLCC struct {
|
||||
Bin string
|
||||
IncludeDir string
|
||||
WorkDir WorkDir
|
||||
}
|
||||
|
||||
func NewGLSLCC() *GLSLCC { return &GLSLCC{Bin: "glslcc"} }
|
||||
|
||||
// Metadata contains reflection data about the shader.
|
||||
type Metadata struct {
|
||||
Uniforms backend.UniformsReflection
|
||||
Inputs []backend.InputLocation
|
||||
Textures []backend.TextureBinding
|
||||
StorageBuffers []backend.StorageBufferBinding
|
||||
}
|
||||
|
||||
// Convert converts input data to the target shader.
|
||||
func (glslcc *GLSLCC) Convert(path, variant string, input []byte, lang, profile string) (_ string, _ Metadata, err error) {
|
||||
base := glslcc.WorkDir.Path(filepath.Base(path), variant, lang, profile)
|
||||
pathin := base + ".in"
|
||||
pathout := base + ".out"
|
||||
reflectout := base + ".json"
|
||||
|
||||
if err := glslcc.WorkDir.WriteFile(pathin, input); err != nil {
|
||||
return "", Metadata{}, fmt.Errorf("unable to write shader to disk: %w", err)
|
||||
}
|
||||
|
||||
var progFlag, progSuffix string
|
||||
switch filepath.Ext(path) {
|
||||
case ".vert":
|
||||
progFlag = "--vert"
|
||||
progSuffix = "vs"
|
||||
case ".frag":
|
||||
progFlag = "--frag"
|
||||
progSuffix = "fs"
|
||||
case ".comp":
|
||||
progFlag = "--compute"
|
||||
progSuffix = "cs"
|
||||
default:
|
||||
return "", Metadata{}, fmt.Errorf("unrecognized shader type: %q", path)
|
||||
}
|
||||
|
||||
cmd := exec.Command(glslcc.Bin,
|
||||
"--silent",
|
||||
"--optimize",
|
||||
"--include-dirs", glslcc.IncludeDir,
|
||||
"--reflect="+reflectout,
|
||||
"--output", pathout,
|
||||
"--lang", lang,
|
||||
"--profile", profile,
|
||||
progFlag, pathin,
|
||||
)
|
||||
if lang == "hlsl" {
|
||||
cmd.Args = append(cmd.Args, "--defines=HLSL")
|
||||
}
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", Metadata{}, fmt.Errorf("%s\nfailed to run %v: %w", output, cmd.Args, err)
|
||||
}
|
||||
|
||||
// glslcc modifies the output path
|
||||
p := strings.IndexByte(pathout, '.')
|
||||
pathout = pathout[:p] + "_" + progSuffix + pathout[p:]
|
||||
shader, err := ioutil.ReadFile(pathout)
|
||||
if err != nil {
|
||||
return "", Metadata{}, fmt.Errorf("missing shader output %q: %w", pathout, err)
|
||||
}
|
||||
|
||||
reflectdata, err := ioutil.ReadFile(reflectout)
|
||||
if err != nil {
|
||||
return "", Metadata{}, fmt.Errorf("missing reflection output %q: %w", pathout, err)
|
||||
}
|
||||
|
||||
metadata, err := glslcc.parseReflection(reflectdata)
|
||||
if err != nil {
|
||||
return "", Metadata{}, fmt.Errorf("invalid reflection output %q: %w", reflectout, err)
|
||||
}
|
||||
|
||||
return string(shader), metadata, nil
|
||||
}
|
||||
|
||||
// parseReflection parses glslcc -reflect output.
|
||||
func (glslcc *GLSLCC) parseReflection(jsonData []byte) (Metadata, error) {
|
||||
type (
|
||||
Input struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Location int `json:"location"`
|
||||
Semantic string `json:"semantic"`
|
||||
SemanticIndex int `json:"semantic_index"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
UniformMember struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Offset int `json:"offset"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
UniformBuffer struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Set int `json:"set"`
|
||||
Binding int `json:"binding"`
|
||||
Size int `json:"block_size"`
|
||||
Members []UniformMember `json:"members"`
|
||||
}
|
||||
Texture struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Set int `json:"set"`
|
||||
Binding int `json:"binding"`
|
||||
Dimension string `json:"dimension"`
|
||||
Format string `json:"format"`
|
||||
}
|
||||
StorageBuffer struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Set int `json:"set"`
|
||||
Binding int `json:"binding"`
|
||||
BlockSize int `json:"block_size"`
|
||||
}
|
||||
Shader struct {
|
||||
Inputs []Input `json:"inputs"`
|
||||
UniformBuffers []UniformBuffer `json:"uniform_buffers"`
|
||||
Textures []Texture `json:"textures"`
|
||||
StorageBuffers []StorageBuffer `json:"storage_buffers"`
|
||||
}
|
||||
ReflectMetadata struct {
|
||||
VS Shader `json:"vs"`
|
||||
FS Shader `json:"fs"`
|
||||
CS Shader `json:"cs"`
|
||||
}
|
||||
)
|
||||
|
||||
var info Metadata
|
||||
|
||||
var reflect ReflectMetadata
|
||||
if err := json.Unmarshal(jsonData, &reflect); err != nil {
|
||||
return info, fmt.Errorf("parseReflection: %v", err)
|
||||
}
|
||||
|
||||
inputRef := reflect.VS.Inputs
|
||||
for _, input := range inputRef {
|
||||
dataType, dataSize, err := parseDataType(input.Type)
|
||||
if err != nil {
|
||||
return info, fmt.Errorf("parseReflection: %v", err)
|
||||
}
|
||||
info.Inputs = append(info.Inputs, backend.InputLocation{
|
||||
Name: input.Name,
|
||||
Location: input.Location,
|
||||
Semantic: input.Semantic,
|
||||
SemanticIndex: input.SemanticIndex,
|
||||
Type: dataType,
|
||||
Size: dataSize,
|
||||
})
|
||||
}
|
||||
sort.Slice(info.Inputs, func(i, j int) bool {
|
||||
return info.Inputs[i].Location < info.Inputs[j].Location
|
||||
})
|
||||
|
||||
shaderBlocks := reflect.VS.UniformBuffers
|
||||
if len(shaderBlocks) == 0 {
|
||||
shaderBlocks = reflect.FS.UniformBuffers
|
||||
}
|
||||
|
||||
blockOffset := 0
|
||||
for _, block := range shaderBlocks {
|
||||
info.Uniforms.Blocks = append(info.Uniforms.Blocks, backend.UniformBlock{
|
||||
Name: block.Name,
|
||||
Binding: block.Binding,
|
||||
})
|
||||
for _, member := range block.Members {
|
||||
dataType, size, err := parseDataType(member.Type)
|
||||
if err != nil {
|
||||
return info, fmt.Errorf("parseReflection: %v", err)
|
||||
}
|
||||
info.Uniforms.Locations = append(info.Uniforms.Locations, backend.UniformLocation{
|
||||
// Synthetic name generated by glslcc.
|
||||
Name: fmt.Sprintf("_%d.%s", block.ID, member.Name),
|
||||
Type: dataType,
|
||||
Size: size,
|
||||
Offset: blockOffset + member.Offset,
|
||||
})
|
||||
}
|
||||
blockOffset += block.Size
|
||||
}
|
||||
info.Uniforms.Size = blockOffset
|
||||
|
||||
textures := reflect.VS.Textures
|
||||
if len(textures) == 0 {
|
||||
textures = reflect.FS.Textures
|
||||
}
|
||||
for _, texture := range textures {
|
||||
info.Textures = append(info.Textures, backend.TextureBinding{
|
||||
Name: texture.Name,
|
||||
Binding: texture.Binding,
|
||||
})
|
||||
}
|
||||
|
||||
for _, sb := range reflect.CS.StorageBuffers {
|
||||
info.StorageBuffers = append(info.StorageBuffers, backend.StorageBufferBinding{
|
||||
Binding: sb.Binding,
|
||||
BlockSize: sb.BlockSize,
|
||||
})
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func parseDataType(t string) (backend.DataType, int, error) {
|
||||
switch t {
|
||||
case "float":
|
||||
return backend.DataTypeFloat, 1, nil
|
||||
case "float2":
|
||||
return backend.DataTypeFloat, 2, nil
|
||||
case "float3":
|
||||
return backend.DataTypeFloat, 3, nil
|
||||
case "float4":
|
||||
return backend.DataTypeFloat, 4, nil
|
||||
case "int":
|
||||
return backend.DataTypeInt, 1, nil
|
||||
case "int2":
|
||||
return backend.DataTypeInt, 2, nil
|
||||
case "int3":
|
||||
return backend.DataTypeInt, 3, nil
|
||||
case "int4":
|
||||
return backend.DataTypeInt, 4, nil
|
||||
default:
|
||||
return 0, 0, fmt.Errorf("unsupported input data type: %s", t)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// GLSLValidator is OpenGL reference compiler.
|
||||
type GLSLValidator struct {
|
||||
Bin string
|
||||
WorkDir WorkDir
|
||||
}
|
||||
|
||||
func NewGLSLValidator() *GLSLValidator { return &GLSLValidator{Bin: "glslangValidator"} }
|
||||
|
||||
// ConvertCompute converts glsl compute shader to spirv.
|
||||
func (glsl *GLSLValidator) ConvertCompute(path string, input []byte) ([]byte, error) {
|
||||
base := glsl.WorkDir.Path(filepath.Base(path))
|
||||
pathout := base + ".out"
|
||||
|
||||
cmd := exec.Command(glsl.Bin,
|
||||
"-G100", // OpenGL ES 3.1.
|
||||
"-w", // Suppress warnings.
|
||||
"-S", "comp",
|
||||
"-o", pathout,
|
||||
path,
|
||||
)
|
||||
cmd.Stdin = bytes.NewBuffer(input)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s\nfailed to run %v: %w", out, cmd.Args, err)
|
||||
}
|
||||
|
||||
compiled, err := ioutil.ReadFile(pathout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read output %q: %w", pathout, err)
|
||||
}
|
||||
|
||||
return compiled, nil
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FXC is hlsl compiler that targets ShaderModel 5.x and lower.
|
||||
type FXC struct {
|
||||
Bin string
|
||||
WorkDir WorkDir
|
||||
}
|
||||
|
||||
func NewFXC() *FXC { return &FXC{Bin: "fxc.exe"} }
|
||||
|
||||
// Compile compiles the input shader.
|
||||
func (fxc *FXC) Compile(path, variant string, input []byte, entryPoint string, profileVersion string) ([]byte, error) {
|
||||
base := fxc.WorkDir.Path(filepath.Base(path), variant, profileVersion)
|
||||
pathin := base + ".in"
|
||||
pathout := base + ".out"
|
||||
result := pathout
|
||||
|
||||
if err := fxc.WorkDir.WriteFile(pathin, input); err != nil {
|
||||
return nil, fmt.Errorf("unable to write shader to disk: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(fxc.Bin)
|
||||
if runtime.GOOS != "windows" {
|
||||
cmd = exec.Command("wine", fxc.Bin)
|
||||
if err := winepath(&pathin, &pathout); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var profile string
|
||||
switch filepath.Ext(path) {
|
||||
case ".frag":
|
||||
profile = "ps_" + profileVersion
|
||||
case ".vert":
|
||||
profile = "vs_" + profileVersion
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized shader type %s", path)
|
||||
}
|
||||
|
||||
cmd.Args = append(cmd.Args,
|
||||
"/Fo", pathout,
|
||||
"/T", profile,
|
||||
"/E", entryPoint,
|
||||
pathin,
|
||||
)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
info := ""
|
||||
if runtime.GOOS != "windows" {
|
||||
info = "If the fxc tool cannot be found, set WINEPATH to the Windows path for the Windows SDK.\n"
|
||||
}
|
||||
return nil, fmt.Errorf("%s\n%sfailed to run %v: %w", output, info, cmd.Args, err)
|
||||
}
|
||||
|
||||
compiled, err := ioutil.ReadFile(result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read output %q: %w", pathout, err)
|
||||
}
|
||||
|
||||
return compiled, nil
|
||||
}
|
||||
|
||||
// DXC is hlsl compiler that targets ShaderModel 6.0 and newer.
|
||||
type DXC struct {
|
||||
Bin string
|
||||
WorkDir WorkDir
|
||||
}
|
||||
|
||||
func NewDXC() *DXC { return &DXC{Bin: "dxc.exe"} }
|
||||
|
||||
// Compile compiles the input shader.
|
||||
func (dxc *DXC) Compile(path, variant string, input []byte, entryPoint string, profile string) ([]byte, error) {
|
||||
base := dxc.WorkDir.Path(filepath.Base(path), variant, profile)
|
||||
pathin := base + ".in"
|
||||
pathout := base + ".out"
|
||||
result := pathout
|
||||
|
||||
if err := dxc.WorkDir.WriteFile(pathin, input); err != nil {
|
||||
return nil, fmt.Errorf("unable to write shader to disk: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(dxc.Bin)
|
||||
if runtime.GOOS != "windows" {
|
||||
cmd = exec.Command("wine", dxc.Bin)
|
||||
if err := winepath(&pathin, &pathout); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Args = append(cmd.Args,
|
||||
"-Fo", pathout,
|
||||
"-T", profile,
|
||||
"-E", entryPoint,
|
||||
"-Qstrip_reflect",
|
||||
pathin,
|
||||
)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
info := ""
|
||||
if runtime.GOOS != "windows" {
|
||||
info = "If the dxc tool cannot be found, set WINEPATH to the Windows path for the Windows SDK.\n"
|
||||
}
|
||||
return nil, fmt.Errorf("%s\n%sfailed to run %v: %w", output, info, cmd.Args, err)
|
||||
}
|
||||
|
||||
compiled, err := ioutil.ReadFile(result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read output %q: %w", pathout, err)
|
||||
}
|
||||
|
||||
return compiled, nil
|
||||
}
|
||||
|
||||
// winepath uses the winepath tool to convert a paths to Windows format.
|
||||
// The returned path can be used as arguments for Windows command line tools.
|
||||
func winepath(paths ...*string) error {
|
||||
for _, path := range paths {
|
||||
out, err := exec.Command("winepath", "--windows", *path).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to run `winepath --windows %q`: %w", *path, err)
|
||||
}
|
||||
*path = strings.TrimSpace(string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
// 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/backend"
|
||||
)
|
||||
|
||||
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 []backend.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/backend")
|
||||
|
||||
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, "[...]backend.ShaderSources{\n")
|
||||
}
|
||||
|
||||
for _, src := range r.Shaders {
|
||||
fmt.Fprintf(out, "backend.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: backend.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) ([]backend.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 []backend.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 backend.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) ([]backend.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 backend.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 []backend.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")
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// SPIRVCross cross-compiles spirv shaders to es, hlsl and others.
|
||||
type SPIRVCross struct {
|
||||
Bin string
|
||||
}
|
||||
|
||||
func NewSPIRVCross() *SPIRVCross { return &SPIRVCross{Bin: "spirv-cross"} }
|
||||
|
||||
// Convert converts compute shader from spirv format to a target format.
|
||||
func (spirv *SPIRVCross) Convert(input []byte, target, version string) (string, error) {
|
||||
var cmd *exec.Cmd
|
||||
switch target {
|
||||
case "es":
|
||||
cmd = exec.Command(spirv.Bin,
|
||||
"--es",
|
||||
"--version", version,
|
||||
"-",
|
||||
)
|
||||
case "hlsl":
|
||||
cmd = exec.Command(spirv.Bin,
|
||||
"--hlsl",
|
||||
"--shader-model", version,
|
||||
"-",
|
||||
)
|
||||
default:
|
||||
return "", fmt.Errorf("unknown target %q", target)
|
||||
}
|
||||
|
||||
cmd.Stdin = bytes.NewBuffer(input)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to run %v: %w", cmd.Args, err)
|
||||
}
|
||||
|
||||
return string(out), nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type WorkDir string
|
||||
|
||||
func (wd WorkDir) Dir(path string) WorkDir {
|
||||
dirname := filepath.Join(string(wd), path)
|
||||
if err := os.Mkdir(dirname, 0755); err != nil {
|
||||
if !os.IsExist(err) {
|
||||
fmt.Fprintf(os.Stderr, "failed to create %q: %v\n", dirname, err)
|
||||
}
|
||||
}
|
||||
return WorkDir(dirname)
|
||||
}
|
||||
|
||||
func (wd WorkDir) Path(path ...string) (fullpath string) {
|
||||
return filepath.Join(string(wd), strings.Join(path, "."))
|
||||
}
|
||||
|
||||
func (wd WorkDir) WriteFile(path string, data []byte) error {
|
||||
err := ioutil.WriteFile(path, data, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create %v: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user