forked from joejulian/gio
internal/cmd/convertshaders: parallelize compilation
Signed-off-by: Egon Elbre <egonelbre@gmail.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -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
|
||||
}
|
||||
+309
-502
@@ -4,7 +4,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -12,116 +12,254 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"gioui.org/gpu/backend"
|
||||
)
|
||||
|
||||
// This program generates shader variants for
|
||||
// multiple GPU backends (OpenGL ES, Direct3D 11...)
|
||||
// from a single source.
|
||||
|
||||
var (
|
||||
packageName = flag.String("package", "", "specify Go package name")
|
||||
shadersDir = flag.String("dir", "shaders", "specify shader directory")
|
||||
directCompute = flag.Bool("directcompute", false, "enable compiling DirectCompute shaders")
|
||||
absShadersDir string
|
||||
)
|
||||
|
||||
type shaderArgs struct {
|
||||
FetchColorExpr string
|
||||
Header string
|
||||
}
|
||||
|
||||
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()
|
||||
if err := generate(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "generate: %v\n", err)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func generate() error {
|
||||
tmp, err := ioutil.TempDir("", "shader-convert")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
glslcc, err := exec.LookPath("glslcc")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
absShadersDir, err = filepath.Abs(*shadersDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
shaders, err := filepath.Glob(filepath.Join(absShadersDir, "*"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var out bytes.Buffer
|
||||
out.WriteString("// Code generated by build.go. DO NOT EDIT.\n\n")
|
||||
fmt.Fprintf(&out, "package %s\n\n", *packageName)
|
||||
fmt.Fprintf(&out, "import %q\n\n", "gioui.org/gpu/backend")
|
||||
type Converter struct {
|
||||
workDir WorkDir
|
||||
shadersDir string
|
||||
directCompute bool
|
||||
|
||||
out.WriteString("var (\n")
|
||||
packageName string
|
||||
|
||||
for _, shader := range shaders {
|
||||
var err error
|
||||
switch filepath.Ext(shader) {
|
||||
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":
|
||||
err = generateShader(glslcc, tmp, &out, shader)
|
||||
workers.Go(func() {
|
||||
shaders, err := conv.Shader(shaderPath)
|
||||
shaderResults[i] = ShaderResult{
|
||||
Path: shaderPath,
|
||||
Shaders: shaders,
|
||||
Error: err,
|
||||
}
|
||||
})
|
||||
case ".comp":
|
||||
err = generateComputeShader(tmp, &out, shader)
|
||||
workers.Go(func() {
|
||||
shaders, err := conv.ComputeShader(shaderPath)
|
||||
shaderResults[i] = ShaderResult{
|
||||
Path: shaderPath,
|
||||
Shaders: shaders,
|
||||
Error: err,
|
||||
}
|
||||
})
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
out.WriteString(")")
|
||||
if err := ioutil.WriteFile("shaders.go", out.Bytes(), 0644); err != nil {
|
||||
return err
|
||||
if len(allErrors) > 0 {
|
||||
return errors.New(allErrors)
|
||||
}
|
||||
cmd := exec.Command("gofmt", "-s", "-w", "shaders.go")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func generateComputeShader(tmp string, out io.Writer, shader string) error {
|
||||
glslangValidator, err := exec.LookPath("glslangValidator")
|
||||
if err != nil {
|
||||
return err
|
||||
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")
|
||||
}
|
||||
}
|
||||
spirv_cross, err := exec.LookPath("spirv-cross")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
shaders, err := convertComputeShader(tmp, glslangValidator, spirv_cross, shader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filename := filepath.Base(shader)
|
||||
varname := strings.ReplaceAll(filename, ".", "_")
|
||||
fmt.Fprintf(out, "\tshader_%s = ", varname)
|
||||
fmt.Fprintf(out, "backend.ShaderSources{\n")
|
||||
fmt.Fprintf(out, "Name: %#v,\n", filename)
|
||||
fmt.Fprintf(out, "GLSL310ES: %#v,\n", shaders.glsl310es)
|
||||
fmt.Fprintf(out, "HLSL: %#v,\n", shaders.hlsl)
|
||||
fmt.Fprintf(out, "}")
|
||||
fmt.Fprintf(out, "\n")
|
||||
fmt.Fprintf(out, ")\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateShader(glslcc, tmp string, out io.Writer, shader string) error {
|
||||
const nvariants = 3
|
||||
var variants [nvariants]backend.ShaderSources
|
||||
args := [nvariants]shaderArgs{
|
||||
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; };`,
|
||||
@@ -135,455 +273,124 @@ func generateShader(glslcc, tmp string, out io.Writer, shader string) error {
|
||||
Header: `layout(binding=0) uniform sampler2D tex;`,
|
||||
},
|
||||
}
|
||||
for i := range args {
|
||||
|
||||
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.
|
||||
glsl100es, _, _ := convertShader(tmp, glslcc, shader, "gles", "100", &args[i], false)
|
||||
glsl300es, reflect, err := convertShader(tmp, glslcc, shader, "gles", "300", &args[i], false)
|
||||
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 err
|
||||
return nil, fmt.Errorf("failed to convert GLSL300ES:\n%w", err)
|
||||
}
|
||||
glsl130, _, err := convertShader(tmp, glslcc, shader, "glsl", "130", &args[i], false)
|
||||
|
||||
sources.GLSL130, _, err = conv.glslcc.Convert(shaderPath, variantName, buf.Bytes(), "glsl", "130")
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, fmt.Errorf("failed to convert GLSL130:\n%w", err)
|
||||
}
|
||||
hlsl, _, err := convertShader(tmp, glslcc, shader, "hlsl", "40", &args[i], false)
|
||||
|
||||
hlsl, _, err := conv.glslcc.Convert(shaderPath, variantName, buf.Bytes(), "hlsl", "40")
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, fmt.Errorf("failed to convert HLSL:\n%w", err)
|
||||
}
|
||||
if err := parseReflection(reflect, &variants[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
var hlslProf string
|
||||
switch filepath.Ext(shader) {
|
||||
case ".frag":
|
||||
hlslProf = "ps"
|
||||
case ".vert":
|
||||
hlslProf = "vs"
|
||||
default:
|
||||
return fmt.Errorf("unrecognized shader type %s", shader)
|
||||
}
|
||||
var hlslc []byte
|
||||
hlslc, err = compileHLSL_FXC(tmp, shader, hlsl, "main", hlslProf+"_4_0_level_9_1")
|
||||
|
||||
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.
|
||||
hlslc, err = compileHLSL_FXC(tmp, shader, hlsl, "main", hlslProf+"_4_0")
|
||||
sources.HLSL, err = conv.fxc.Compile(shaderPath, variantName, []byte(hlsl), "main", "4_0")
|
||||
if err != nil {
|
||||
return err
|
||||
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.
|
||||
glsl150 := strings.Replace(glsl130, "#version 130", "#version 150", 1)
|
||||
variants[i].Name = filepath.Base(shader)
|
||||
variants[i].GLSL100ES = glsl100es
|
||||
variants[i].GLSL300ES = glsl300es
|
||||
variants[i].GLSL130 = glsl130
|
||||
variants[i].GLSL150 = glsl150
|
||||
variants[i].HLSL = hlslc
|
||||
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)
|
||||
}
|
||||
name := filepath.Base(shader)
|
||||
name = strings.ReplaceAll(name, ".", "_")
|
||||
fmt.Fprintf(out, "\tshader_%s = ", name)
|
||||
// If the shader don't use the variant arguments, output
|
||||
// only a single version.
|
||||
multiVariant := variants[0].GLSL100ES != variants[1].GLSL100ES
|
||||
if multiVariant {
|
||||
fmt.Fprintf(out, "[...]backend.ShaderSources{\n")
|
||||
|
||||
// If the shader don't use the variant arguments, output only a single version.
|
||||
if variants[0].GLSL100ES == variants[1].GLSL100ES {
|
||||
variants = variants[:1]
|
||||
}
|
||||
for _, src := range variants {
|
||||
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)
|
||||
}
|
||||
fmt.Fprintf(out, "GLSL100ES: %#v,\n", src.GLSL100ES)
|
||||
fmt.Fprintf(out, "GLSL300ES: %#v,\n", src.GLSL300ES)
|
||||
fmt.Fprintf(out, "GLSL130: %#v,\n", src.GLSL130)
|
||||
fmt.Fprintf(out, "GLSL150: %#v,\n", src.GLSL150)
|
||||
fmt.Fprintf(out, "HLSL: %#v,\n", src.HLSL)
|
||||
fmt.Fprintf(out, "}")
|
||||
if multiVariant {
|
||||
fmt.Fprintf(out, ",")
|
||||
}
|
||||
fmt.Fprintf(out, "\n")
|
||||
if !multiVariant {
|
||||
break
|
||||
}
|
||||
}
|
||||
if multiVariant {
|
||||
fmt.Fprintf(out, "}\n")
|
||||
}
|
||||
return nil
|
||||
|
||||
return variants, nil
|
||||
}
|
||||
|
||||
func parseReflection(jsonData []byte, info *backend.ShaderSources) error {
|
||||
type InputReflection 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"`
|
||||
}
|
||||
type UniformMemberReflection struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Offset int `json:"offset"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
type UniformBufferReflection struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Set int `json:"set"`
|
||||
Binding int `json:"binding"`
|
||||
Size int `json:"block_size"`
|
||||
Members []UniformMemberReflection `json:"members"`
|
||||
}
|
||||
type TextureReflection 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"`
|
||||
}
|
||||
type StorageBufferReflection struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Set int `json:"set"`
|
||||
Binding int `json:"binding"`
|
||||
BlockSize int `json:"block_size"`
|
||||
}
|
||||
type shaderReflection struct {
|
||||
Inputs []InputReflection `json:"inputs"`
|
||||
UniformBuffers []UniformBufferReflection `json:"uniform_buffers"`
|
||||
Textures []TextureReflection `json:"textures"`
|
||||
StorageBuffers []StorageBufferReflection `json:"storage_buffers"`
|
||||
}
|
||||
type shaderMetadata struct {
|
||||
VS shaderReflection `json:"vs"`
|
||||
FS shaderReflection `json:"fs"`
|
||||
CS shaderReflection `json:"cs"`
|
||||
}
|
||||
var reflect shaderMetadata
|
||||
if err := json.Unmarshal(jsonData, &reflect); err != nil {
|
||||
return fmt.Errorf("parseReflection: %v", err)
|
||||
}
|
||||
inputRef := reflect.VS.Inputs
|
||||
for _, input := range inputRef {
|
||||
dataType, dataSize, err := parseDataType(input.Type)
|
||||
if err != nil {
|
||||
return 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 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 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)
|
||||
}
|
||||
}
|
||||
|
||||
func compileHLSL_FXC(tmp, path, src, entry, profile string) ([]byte, error) {
|
||||
base := filepath.Base(path)
|
||||
tmppath := filepath.Join(tmp, base)
|
||||
defer os.Remove(tmppath)
|
||||
if err := ioutil.WriteFile(tmppath, []byte(src), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outfile := filepath.Join(tmp, base+".obj")
|
||||
defer os.Remove(outfile)
|
||||
fxcInput := tmppath
|
||||
fxcOutput := outfile
|
||||
var fxc *exec.Cmd
|
||||
if runtime.GOOS == "windows" {
|
||||
fxc = exec.Command("fxc.exe")
|
||||
} else {
|
||||
// Convert paths to wine Windows format.
|
||||
var err error
|
||||
fxcInput, err = windowsPath(fxcInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fxcOutput, err = windowsPath(fxcOutput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fxc = exec.Command("wine", "fxc.exe")
|
||||
}
|
||||
var stdout, stderr bytes.Buffer
|
||||
fxc.Stderr = &stderr
|
||||
fxc.Stdout = &stdout
|
||||
fxc.Args = append(fxc.Args, "/Fo", fxcOutput, "/T", profile, "/E", entry, fxcInput)
|
||||
if err := fxc.Run(); 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%s\n%s%s: %v", stderr.Bytes(), stdout.Bytes(), info, fxc, err)
|
||||
}
|
||||
return ioutil.ReadFile(outfile)
|
||||
}
|
||||
|
||||
func compileHLSL_DXC(tmp, path, src, entry, profile string) ([]byte, error) {
|
||||
base := filepath.Base(path)
|
||||
tmppath := filepath.Join(tmp, base)
|
||||
defer os.Remove(tmppath)
|
||||
if err := ioutil.WriteFile(tmppath, []byte(src), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outfile := filepath.Join(tmp, base+".obj")
|
||||
defer os.Remove(outfile)
|
||||
dxcInput := tmppath
|
||||
dxcOutput := outfile
|
||||
var dxc *exec.Cmd
|
||||
if runtime.GOOS == "windows" {
|
||||
dxc = exec.Command("dxc.exe")
|
||||
} else {
|
||||
// Convert paths to wine Windows format.
|
||||
var err error
|
||||
dxcInput, err = windowsPath(dxcInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dxcOutput, err = windowsPath(dxcOutput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dxc = exec.Command("wine", "dxc.exe")
|
||||
}
|
||||
var stdout, stderr bytes.Buffer
|
||||
dxc.Stderr = &stderr
|
||||
dxc.Stdout = &stdout
|
||||
dxc.Args = append(dxc.Args, "-Fo", dxcOutput, "-T", profile, "-Qstrip_reflect", "-E", entry, dxcInput)
|
||||
if err := dxc.Run(); 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%s\n%s%s: %v", stderr.Bytes(), stdout.Bytes(), info, dxc, err)
|
||||
}
|
||||
return ioutil.ReadFile(outfile)
|
||||
}
|
||||
|
||||
// windowsPath uses the winepath tool to convert a path to Windows format. The returned
|
||||
// path can be used as arguments for Windows command line tools.
|
||||
func windowsPath(path string) (string, error) {
|
||||
var out bytes.Buffer
|
||||
winepath := exec.Command("winepath", "--windows", path)
|
||||
winepath.Stdout = &out
|
||||
if err := winepath.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(out.String()), nil
|
||||
}
|
||||
|
||||
func convertShader(tmp, glslcc, path, lang, profile string, args *shaderArgs, flattenUBOs bool) (string, []byte, error) {
|
||||
shaderTmpl, err := template.ParseFiles(path)
|
||||
func (conv *Converter) ComputeShader(shaderPath string) ([]backend.ShaderSources, error) {
|
||||
shader, err := ioutil.ReadFile(shaderPath)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, fmt.Errorf("failed to load shader %q: %w", shaderPath, err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := shaderTmpl.Execute(&buf, args); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
tmppath := filepath.Join(tmp, filepath.Base(path))
|
||||
defer os.Remove(tmppath)
|
||||
if err := ioutil.WriteFile(tmppath, buf.Bytes(), 0644); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
var progFlag string
|
||||
var 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 "", nil, fmt.Errorf("unrecognized shader type: %s", path)
|
||||
}
|
||||
cmd := exec.Command(glslcc,
|
||||
"--silent",
|
||||
"--optimize",
|
||||
"--include-dirs", absShadersDir,
|
||||
"--reflect",
|
||||
"--output", filepath.Join(tmp, "shader"),
|
||||
"--lang", lang,
|
||||
"--profile", profile,
|
||||
progFlag, tmppath,
|
||||
)
|
||||
if lang == "hlsl" {
|
||||
cmd.Args = append(cmd.Args, "--defines=HLSL")
|
||||
}
|
||||
if flattenUBOs {
|
||||
cmd.Args = append(cmd.Args, "--flatten-ubos")
|
||||
}
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", nil, fmt.Errorf("%s: %v", strings.Join(cmd.Args, " "), err)
|
||||
}
|
||||
f, err := os.Open(filepath.Join(tmp, "shader_"+progSuffix))
|
||||
|
||||
spirv, err := conv.glslvalidator.ConvertCompute(shaderPath, shader)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, fmt.Errorf("failed to convert compute shader %q: %w", shaderPath, err)
|
||||
}
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
src, err := ioutil.ReadAll(f)
|
||||
|
||||
var sources backend.ShaderSources
|
||||
sources.Name = filepath.Base(shaderPath)
|
||||
|
||||
sources.GLSL310ES, err = conv.spirv.Convert(spirv, "es", "310")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, fmt.Errorf("failed to convert es compute shader %q: %w", shaderPath, err)
|
||||
}
|
||||
reflect, err := ioutil.ReadFile(filepath.Join(tmp, "shader_"+progSuffix+".json"))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return string(src), reflect, nil
|
||||
}
|
||||
sources.GLSL310ES = unixLineEnding(sources.GLSL310ES)
|
||||
|
||||
type computeShaders struct {
|
||||
glsl310es string
|
||||
hlsl []byte
|
||||
}
|
||||
|
||||
func convertComputeShader(tmp, glslangValidator, spirv_cross, path string) (computeShaders, error) {
|
||||
spvFile := filepath.Join(tmp, "shader.spv")
|
||||
cmd := exec.Command(glslangValidator,
|
||||
"-G100", // OpenGL ES 3.1.
|
||||
"-w", // Suppress warnings.
|
||||
"-o", spvFile,
|
||||
path,
|
||||
)
|
||||
defer os.Remove(spvFile)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return computeShaders{}, fmt.Errorf("%s: %v", strings.Join(cmd.Args, " "), err)
|
||||
}
|
||||
|
||||
var output computeShaders
|
||||
{
|
||||
cmd = exec.Command(spirv_cross,
|
||||
"--es",
|
||||
"--version", "310",
|
||||
spvFile,
|
||||
)
|
||||
var shaderDump bytes.Buffer
|
||||
cmd.Stdout = &shaderDump
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return output, fmt.Errorf("%s: %v", strings.Join(cmd.Args, " "), err)
|
||||
}
|
||||
output.glsl310es = unixLineEnding(shaderDump.String())
|
||||
}
|
||||
if *directCompute {
|
||||
cmd = exec.Command(spirv_cross,
|
||||
"--hlsl",
|
||||
"--shader-model", "50",
|
||||
spvFile,
|
||||
)
|
||||
var shaderDump bytes.Buffer
|
||||
cmd.Stdout = &shaderDump
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return output, fmt.Errorf("%s: %v", strings.Join(cmd.Args, " "), err)
|
||||
}
|
||||
hlslSrc := shaderDump.String()
|
||||
|
||||
var err error
|
||||
output.hlsl, err = compileHLSL_DXC(tmp, path, hlslSrc, "main", "cs_5_0")
|
||||
if conv.directCompute {
|
||||
hlslSource, err := conv.spirv.Convert(spirv, "hlsl", "50")
|
||||
if err != nil {
|
||||
return output, err
|
||||
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 output, nil
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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