Files
gio/gpu/internal/convertshaders/glslcc.go
T
Elias Naur 2c7aba9e7c 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>
2021-03-06 14:26:47 +01:00

250 lines
6.6 KiB
Go

// 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)
}
}