mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-03 00:16:15 +00:00
internal/cmd/convertshaders: move shader converter to separate package
To use the converter from other packages, make the converter a runnable command. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
-404
@@ -1,404 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// This program generates shader variants for
|
||||
// multiple GPU backends (OpenGL ES, Direct3D 11...)
|
||||
// from a single source.
|
||||
|
||||
type shaderArgs struct {
|
||||
FetchColorExpr string
|
||||
Header string
|
||||
}
|
||||
|
||||
// TextureBinding matches gpu.TextureBinding.
|
||||
type TextureBinding struct {
|
||||
Name string
|
||||
Binding int
|
||||
}
|
||||
|
||||
// InputLocation matches gpu.InputLocation.
|
||||
type InputLocation struct {
|
||||
Name string
|
||||
Location int
|
||||
Semantic string
|
||||
SemanticIndex int
|
||||
|
||||
Type DataType
|
||||
Size int
|
||||
}
|
||||
|
||||
type DataType uint8
|
||||
|
||||
// UniformLocation matches gpu.UniformLocation.
|
||||
type UniformLocation struct {
|
||||
Name string
|
||||
Type DataType
|
||||
Size int
|
||||
Offset int
|
||||
}
|
||||
|
||||
const (
|
||||
DataTypeFloat DataType = iota
|
||||
DataTypeShort
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := generate(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "gpu generate: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func generate() error {
|
||||
tmp, err := ioutil.TempDir("", "gpu-generate")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
glslcc, err := exec.LookPath("glslcc")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fxc, err := exec.LookPath("fxc")
|
||||
fxcFound := err == nil
|
||||
shaders, err := filepath.Glob("shaders/*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var out bytes.Buffer
|
||||
out.WriteString("// Code generated by build.go. DO NOT EDIT.\n\n")
|
||||
out.WriteString("package gpu\n\n")
|
||||
fmt.Fprintf(&out, "import %q\n\n", "gioui.org/gpu/backend")
|
||||
|
||||
out.WriteString("var (\n")
|
||||
|
||||
for _, shader := range shaders {
|
||||
const nvariants = 2
|
||||
var variants [nvariants]struct {
|
||||
gles2 string
|
||||
hlslSrc string
|
||||
hlsl []byte
|
||||
inputs []InputLocation
|
||||
uniforms []UniformLocation
|
||||
textures []TextureBinding
|
||||
uniformSize int
|
||||
}
|
||||
args := [nvariants]shaderArgs{
|
||||
{
|
||||
FetchColorExpr: `_color`,
|
||||
Header: `layout(binding=0) uniform Color { vec4 _color; };`,
|
||||
},
|
||||
{
|
||||
FetchColorExpr: `texture(tex, vUV)`,
|
||||
Header: `layout(binding=0) uniform sampler2D tex;`,
|
||||
},
|
||||
}
|
||||
for i := range args {
|
||||
gles2, reflect, err := convertShader(tmp, glslcc, shader, "gles", "100", &args[i], false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Make the GL ES 2 source compatible with desktop GL 3.
|
||||
gles2 = "#version 100\n" + gles2
|
||||
inputs, uniforms, textures, uniformSize, err := parseReflection(reflect)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hlsl, _, err := convertShader(tmp, glslcc, shader, "hlsl", "40", &args[i], false)
|
||||
if 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
|
||||
if fxcFound {
|
||||
hlslc, err = compileHLSL(tmp, fxc, hlsl, "main", hlslProf+"_4_0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
variants[i].gles2 = gles2
|
||||
variants[i].hlslSrc = hlsl
|
||||
variants[i].hlsl = hlslc
|
||||
variants[i].inputs = inputs
|
||||
variants[i].uniforms = uniforms
|
||||
variants[i].textures = textures
|
||||
variants[i].uniformSize = uniformSize
|
||||
}
|
||||
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].gles2 != variants[1].gles2
|
||||
if multiVariant {
|
||||
fmt.Fprintf(&out, "[...]backend.ShaderSources{\n")
|
||||
}
|
||||
for _, src := range variants {
|
||||
fmt.Fprintf(&out, "backend.ShaderSources{\n")
|
||||
if len(src.inputs) > 0 {
|
||||
fmt.Fprintf(&out, "Inputs: []backend.InputLocation{\n")
|
||||
for _, inp := range src.inputs {
|
||||
fmt.Fprintf(&out, "{Name: %q, Location: %d, Semantic: %q, ", inp.Name, inp.Location, inp.Semantic)
|
||||
fmt.Fprintf(&out, "SemanticIndex: %d, Type: %d, Size: %d},\n", inp.SemanticIndex, inp.Type, inp.Size)
|
||||
}
|
||||
fmt.Fprintf(&out, "},\n")
|
||||
}
|
||||
if len(src.uniforms) > 0 {
|
||||
fmt.Fprintf(&out, "Uniforms: []backend.UniformLocation{\n")
|
||||
for _, u := range src.uniforms {
|
||||
fmt.Fprintf(&out, "{Name: %q, Type: %d, Size: %d, Offset: %d},\n", u.Name, u.Type, u.Size, u.Offset)
|
||||
}
|
||||
fmt.Fprintf(&out, "},\n")
|
||||
}
|
||||
if src.uniformSize != 0 {
|
||||
fmt.Fprintf(&out, "UniformSize: %d,\n", src.uniformSize)
|
||||
}
|
||||
if len(src.textures) > 0 {
|
||||
fmt.Fprintf(&out, "Textures: []backend.TextureBinding{\n")
|
||||
for _, t := range src.textures {
|
||||
fmt.Fprintf(&out, "{Name: %q, Binding: %d},\n", t.Name, t.Binding)
|
||||
}
|
||||
fmt.Fprintf(&out, "},\n")
|
||||
}
|
||||
fmt.Fprintf(&out, "GLES2: %#v,\n", src.gles2)
|
||||
fmt.Fprintf(&out, "/*\n%s\n*/\n", src.hlslSrc)
|
||||
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")
|
||||
}
|
||||
}
|
||||
out.WriteString(")")
|
||||
gosrc, err := format.Source(out.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("shader.go: %v", err)
|
||||
}
|
||||
return ioutil.WriteFile("shaders.go", gosrc, 0644)
|
||||
}
|
||||
|
||||
func parseReflection(jsonData []byte) ([]InputLocation, []UniformLocation, []TextureBinding, int, 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 shaderReflection struct {
|
||||
Inputs []InputReflection `json:"inputs"`
|
||||
UniformBuffers []UniformBufferReflection `json:"uniform_buffers"`
|
||||
Textures []TextureReflection `json:"textures"`
|
||||
}
|
||||
type shaderMetadata struct {
|
||||
VS shaderReflection `json:"vs"`
|
||||
FS shaderReflection `json:"fs"`
|
||||
}
|
||||
var reflect shaderMetadata
|
||||
if err := json.Unmarshal(jsonData, &reflect); err != nil {
|
||||
return nil, nil, nil, 0, fmt.Errorf("parseReflection: %v", err)
|
||||
}
|
||||
var inputs []InputLocation
|
||||
inputRef := reflect.VS.Inputs
|
||||
for _, input := range inputRef {
|
||||
dataType, dataSize, err := parseDataType(input.Type)
|
||||
if err != nil {
|
||||
return nil, nil, nil, 0, fmt.Errorf("parseReflection: %v", err)
|
||||
}
|
||||
inputs = append(inputs, InputLocation{
|
||||
Name: input.Name,
|
||||
Location: input.Location,
|
||||
Semantic: input.Semantic,
|
||||
SemanticIndex: input.SemanticIndex,
|
||||
Type: dataType,
|
||||
Size: dataSize,
|
||||
})
|
||||
}
|
||||
sort.Slice(inputs, func(i, j int) bool {
|
||||
return inputs[i].Location < inputs[j].Location
|
||||
})
|
||||
var ublocks []UniformLocation
|
||||
shaderBlocks := reflect.VS.UniformBuffers
|
||||
if len(shaderBlocks) == 0 {
|
||||
shaderBlocks = reflect.FS.UniformBuffers
|
||||
}
|
||||
blockOffset := 0
|
||||
for _, block := range shaderBlocks {
|
||||
for _, member := range block.Members {
|
||||
dataType, size, err := parseDataType(member.Type)
|
||||
if err != nil {
|
||||
return nil, nil, nil, 0, fmt.Errorf("parseReflection: %v", err)
|
||||
}
|
||||
ublocks = append(ublocks, 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
|
||||
}
|
||||
textures := reflect.VS.Textures
|
||||
if len(textures) == 0 {
|
||||
textures = reflect.FS.Textures
|
||||
}
|
||||
var texBinds []TextureBinding
|
||||
for _, texture := range textures {
|
||||
texBinds = append(texBinds, TextureBinding{
|
||||
Name: texture.Name,
|
||||
Binding: texture.Binding,
|
||||
})
|
||||
}
|
||||
return inputs, ublocks, texBinds, blockOffset, nil
|
||||
}
|
||||
|
||||
func parseDataType(t string) (DataType, int, error) {
|
||||
switch t {
|
||||
case "float":
|
||||
return DataTypeFloat, 1, nil
|
||||
case "float2":
|
||||
return DataTypeFloat, 2, nil
|
||||
case "float3":
|
||||
return DataTypeFloat, 3, nil
|
||||
case "float4":
|
||||
return DataTypeFloat, 4, nil
|
||||
default:
|
||||
return 0, 0, fmt.Errorf("unsupported input data type: %s", t)
|
||||
}
|
||||
}
|
||||
|
||||
func compileHLSL(tmp, fxc, src, entry, profile string) ([]byte, error) {
|
||||
tmpfile := filepath.Join(tmp, "shader.hlsl")
|
||||
if err := ioutil.WriteFile(tmpfile, []byte(src), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outFile := filepath.Join(tmp, "shader.bin")
|
||||
cmd := exec.Command(fxc,
|
||||
"/T", profile,
|
||||
"/E", entry,
|
||||
"/nologo",
|
||||
"/Fo", outFile,
|
||||
tmpfile,
|
||||
)
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutil.ReadFile(outFile)
|
||||
}
|
||||
|
||||
func convertShader(tmp, glslcc, path, lang, profile string, args *shaderArgs, flattenUBOs bool) (string, []byte, error) {
|
||||
shaderTmpl, err := template.ParseFiles(path)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := shaderTmpl.Execute(&buf, args); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
tmppath := filepath.Join(tmp, filepath.Base(path))
|
||||
if err := ioutil.WriteFile(tmppath, buf.Bytes(), 0644); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer os.Remove(tmppath)
|
||||
var progFlag string
|
||||
var progSuffix string
|
||||
switch filepath.Ext(path) {
|
||||
case ".vert":
|
||||
progFlag = "--vert"
|
||||
progSuffix = "vs"
|
||||
case ".frag":
|
||||
progFlag = "--frag"
|
||||
progSuffix = "fs"
|
||||
default:
|
||||
return "", nil, fmt.Errorf("unrecognized shader type: %s", path)
|
||||
}
|
||||
cmd := exec.Command(glslcc,
|
||||
"--silent",
|
||||
"--optimize",
|
||||
"--reflect",
|
||||
"--output", filepath.Join(tmp, "shader"),
|
||||
"--lang", lang,
|
||||
"--profile", profile,
|
||||
progFlag, tmppath,
|
||||
)
|
||||
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", path, err)
|
||||
}
|
||||
f, err := os.Open(filepath.Join(tmp, "shader_"+progSuffix))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
src, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
reflect, err := ioutil.ReadFile(filepath.Join(tmp, "shader_"+progSuffix+".json"))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return string(src), reflect, nil
|
||||
}
|
||||
+1
-1
@@ -2,4 +2,4 @@
|
||||
|
||||
package gpu
|
||||
|
||||
//go:generate go run build.go
|
||||
//go:generate go run ../internal/cmd/convertshaders -package gpu
|
||||
|
||||
Reference in New Issue
Block a user