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:
Elias Naur
2021-03-05 16:06:57 +01:00
parent 2a66bfb2b4
commit 2c7aba9e7c
8 changed files with 2 additions and 2 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
package gpu
//go:generate go run ../internal/cmd/convertshaders -package gpu
//go:generate go run ./internal/convertshaders -package gpu
+1 -1
View File
@@ -2,4 +2,4 @@
package headless
//go:generate go run ../../internal/cmd/convertshaders -package headless
//go:generate go run ../internal/convertshaders -package headless
+249
View File
@@ -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
}
+138
View File
@@ -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
}
+398
View File
@@ -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")
}
+45
View File
@@ -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
}
+35
View File
@@ -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
}