Files
gio/gpu/internal/convertshaders/glslcc.go
T
Elias Naur c799452c57 gpu/internal/driver: rename gpu/backend
There are no longer any importers of package backend outside of
gioui.org/gpu. Move it internally, and rename it to the slightly more
specific "driver" while we're at it.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2021-03-06 14:27:34 +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/internal/driver"
)
// 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 driver.UniformsReflection
Inputs []driver.InputLocation
Textures []driver.TextureBinding
StorageBuffers []driver.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, driver.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, driver.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, driver.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, driver.TextureBinding{
Name: texture.Name,
Binding: texture.Binding,
})
}
for _, sb := range reflect.CS.StorageBuffers {
info.StorageBuffers = append(info.StorageBuffers, driver.StorageBufferBinding{
Binding: sb.Binding,
BlockSize: sb.BlockSize,
})
}
return info, nil
}
func parseDataType(t string) (driver.DataType, int, error) {
switch t {
case "float":
return driver.DataTypeFloat, 1, nil
case "float2":
return driver.DataTypeFloat, 2, nil
case "float3":
return driver.DataTypeFloat, 3, nil
case "float4":
return driver.DataTypeFloat, 4, nil
case "int":
return driver.DataTypeInt, 1, nil
case "int2":
return driver.DataTypeInt, 2, nil
case "int3":
return driver.DataTypeInt, 3, nil
case "int4":
return driver.DataTypeInt, 4, nil
default:
return 0, 0, fmt.Errorf("unsupported input data type: %s", t)
}
}