mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-04 00:45:35 +00:00
all: switch to external shaders in the gioui.org/shaders module
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -1,50 +0,0 @@
|
||||
// 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"} }
|
||||
|
||||
// Convert converts a glsl shader to spirv.
|
||||
func (glsl *GLSLValidator) Convert(path, variant string, hlsl bool, input []byte) ([]byte, error) {
|
||||
base := glsl.WorkDir.Path(filepath.Base(path), variant)
|
||||
pathout := base + ".out"
|
||||
|
||||
cmd := exec.Command(glsl.Bin,
|
||||
"--stdin",
|
||||
"-I"+filepath.Dir(path),
|
||||
"-V", // OpenGL ES 3.1.
|
||||
"-w", // Suppress warnings.
|
||||
"-S", filepath.Ext(path)[1:],
|
||||
"-o", pathout,
|
||||
)
|
||||
if hlsl {
|
||||
cmd.Args = append(cmd.Args, "-DHLSL")
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"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) (string, 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 "", 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 "", err
|
||||
}
|
||||
}
|
||||
|
||||
var profile string
|
||||
switch filepath.Ext(path) {
|
||||
case ".frag":
|
||||
profile = "ps_" + profileVersion
|
||||
case ".vert":
|
||||
profile = "vs_" + profileVersion
|
||||
case ".comp":
|
||||
profile = "cs_" + profileVersion
|
||||
default:
|
||||
return "", 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 "", fmt.Errorf("%s\n%sfailed to run %v: %w", output, info, cmd.Args, err)
|
||||
}
|
||||
|
||||
compiled, err := ioutil.ReadFile(result)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to read output %q: %w", pathout, err)
|
||||
}
|
||||
|
||||
return string(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"} }
|
||||
|
||||
// Compile compiles the input shader.
|
||||
func (dxc *DXC) Compile(path, variant string, input []byte, entryPoint string, profile string) (string, 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 "", fmt.Errorf("unable to write shader to disk: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(dxc.Bin)
|
||||
|
||||
cmd.Args = append(cmd.Args,
|
||||
"-Fo", pathout,
|
||||
"-T", profile,
|
||||
"-E", entryPoint,
|
||||
"-Qstrip_reflect",
|
||||
pathin,
|
||||
)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s\nfailed to run %v: %w", output, cmd.Args, err)
|
||||
}
|
||||
|
||||
compiled, err := ioutil.ReadFile(result)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to read output %q: %w", pathout, err)
|
||||
}
|
||||
|
||||
return string(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 {
|
||||
winepath := exec.Command("winepath", "--windows")
|
||||
for _, path := range paths {
|
||||
winepath.Args = append(winepath.Args, *path)
|
||||
}
|
||||
// Use a pipe instead of Output, because winepath may have left wineserver
|
||||
// running for several seconds as a grandchild.
|
||||
out, err := winepath.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start winepath: %w", err)
|
||||
}
|
||||
if err := winepath.Start(); err != nil {
|
||||
return fmt.Errorf("unable to start winepath: %w", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, out); err != nil {
|
||||
return fmt.Errorf("unable to run winepath: %w", err)
|
||||
}
|
||||
winPaths := strings.Split(strings.TrimSpace(buf.String()), "\n")
|
||||
for i, path := range paths {
|
||||
*path = winPaths[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,418 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"gioui.org/gpu/internal/driver"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
glslvalidator *GLSLValidator
|
||||
spirv *SPIRVCross
|
||||
fxc *FXC
|
||||
}
|
||||
|
||||
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.glslvalidator = NewGLSLValidator()
|
||||
conv.spirv = NewSPIRVCross()
|
||||
conv.fxc = NewFXC()
|
||||
|
||||
verifyBinaryPath(&conv.glslvalidator.Bin)
|
||||
verifyBinaryPath(&conv.spirv.Bin)
|
||||
// We cannot check fxc since it may depend on wine.
|
||||
|
||||
conv.glslvalidator.WorkDir = workDir.Dir("glslvalidator")
|
||||
conv.fxc.WorkDir = workDir.Dir("fxc")
|
||||
conv.spirv.WorkDir = workDir.Dir("spirv")
|
||||
|
||||
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 []driver.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/internal/driver")
|
||||
|
||||
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, "[...]driver.ShaderSources{\n")
|
||||
}
|
||||
|
||||
for _, src := range r.Shaders {
|
||||
fmt.Fprintf(out, "driver.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: driver.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: `%s`,\n", src.GLSL100ES)
|
||||
}
|
||||
if len(src.GLSL300ES) > 0 {
|
||||
fmt.Fprintf(out, "GLSL300ES: `%s`,\n", src.GLSL300ES)
|
||||
}
|
||||
if len(src.GLSL310ES) > 0 {
|
||||
fmt.Fprintf(out, "GLSL310ES: `%s`,\n", src.GLSL310ES)
|
||||
}
|
||||
if len(src.GLSL130) > 0 {
|
||||
fmt.Fprintf(out, "GLSL130: `%s`,\n", src.GLSL130)
|
||||
}
|
||||
if len(src.GLSL150) > 0 {
|
||||
fmt.Fprintf(out, "GLSL150: `%s`,\n", src.GLSL150)
|
||||
}
|
||||
if len(src.HLSL) > 0 {
|
||||
fmt.Fprintf(out, "HLSL: %q,\n", src.HLSL)
|
||||
}
|
||||
if len(src.Hash) > 0 {
|
||||
fmt.Fprintf(out, "Hash: %q,\n", src.Hash)
|
||||
}
|
||||
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) ([]driver.ShaderSources, error) {
|
||||
type Variant struct {
|
||||
FetchColorExpr string
|
||||
Header string
|
||||
}
|
||||
variantArgs := [...]Variant{
|
||||
{
|
||||
FetchColorExpr: `_color.color`,
|
||||
Header: `layout(binding=0) uniform Color { vec4 color; } _color;`,
|
||||
},
|
||||
{
|
||||
FetchColorExpr: `mix(_gradient.color1, _gradient.color2, clamp(vUV.x, 0.0, 1.0))`,
|
||||
Header: `layout(binding=0) uniform Gradient { vec4 color1; vec4 color2; } _gradient;`,
|
||||
},
|
||||
{
|
||||
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 []driver.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 driver.ShaderSources
|
||||
sources.Name = filepath.Base(shaderPath)
|
||||
|
||||
// Ignore error; some shaders are not meant to run in GLSL 1.00.
|
||||
sources.GLSL100ES, _, _ = conv.ShaderVariant(shaderPath, variantName, buf.Bytes(), "es", "100")
|
||||
|
||||
var metadata Metadata
|
||||
sources.GLSL300ES, metadata, err = conv.ShaderVariant(shaderPath, variantName, buf.Bytes(), "es", "300")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert GLSL300ES:\n%w", err)
|
||||
}
|
||||
|
||||
sources.GLSL130, _, err = conv.ShaderVariant(shaderPath, variantName, buf.Bytes(), "glsl", "130")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert GLSL130:\n%w", err)
|
||||
}
|
||||
|
||||
hlsl, _, err := conv.ShaderVariant(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 gpu/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)
|
||||
}
|
||||
}
|
||||
|
||||
sources.GLSL150, _, err = conv.ShaderVariant(shaderPath, variantName, buf.Bytes(), "glsl", "150")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert GLSL150:\n%w", err)
|
||||
}
|
||||
|
||||
sources.Uniforms = metadata.Uniforms
|
||||
sources.Inputs = metadata.Inputs
|
||||
sources.Textures = metadata.Textures
|
||||
|
||||
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) ShaderVariant(shaderPath, variant string, src []byte, lang, profile string) (string, Metadata, error) {
|
||||
spirv, err := conv.glslvalidator.Convert(shaderPath, variant, lang == "hlsl", src)
|
||||
if err != nil {
|
||||
return "", Metadata{}, fmt.Errorf("failed to generate SPIR-V for %q: %w", shaderPath, err)
|
||||
}
|
||||
|
||||
dst, err := conv.spirv.Convert(shaderPath, variant, spirv, lang, profile)
|
||||
if err != nil {
|
||||
return "", Metadata{}, fmt.Errorf("failed to convert shader %q: %w", shaderPath, err)
|
||||
}
|
||||
|
||||
meta, err := conv.spirv.Metadata(shaderPath, variant, spirv)
|
||||
if err != nil {
|
||||
return "", Metadata{}, fmt.Errorf("failed to extract metadata for shader %q: %w", shaderPath, err)
|
||||
}
|
||||
|
||||
return dst, meta, nil
|
||||
}
|
||||
|
||||
func (conv *Converter) ComputeShader(shaderPath string) ([]driver.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.Convert(shaderPath, "", false, shader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert compute shader %q: %w", shaderPath, err)
|
||||
}
|
||||
|
||||
var sources driver.ShaderSources
|
||||
sources.Name = filepath.Base(shaderPath)
|
||||
|
||||
sum := sha256.Sum256(shader)
|
||||
sources.Hash = hex.EncodeToString(sum[:])
|
||||
|
||||
sources.GLSL310ES, err = conv.spirv.Convert(shaderPath, "", 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)
|
||||
|
||||
hlslSource, err := conv.spirv.Convert(shaderPath, "", spirv, "hlsl", "50")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert hlsl compute shader %q: %w", shaderPath, err)
|
||||
}
|
||||
|
||||
dxil, err := conv.fxc.Compile(shaderPath, "0", []byte(hlslSource), "main", "5_0")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compile hlsl compute shader %q: %w", shaderPath, err)
|
||||
}
|
||||
if conv.directCompute {
|
||||
sources.HLSL = dxil
|
||||
}
|
||||
|
||||
return []driver.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")
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gioui.org/gpu/internal/driver"
|
||||
)
|
||||
|
||||
// Metadata contains reflection data about a shader.
|
||||
type Metadata struct {
|
||||
Uniforms driver.UniformsReflection
|
||||
Inputs []driver.InputLocation
|
||||
Textures []driver.TextureBinding
|
||||
}
|
||||
|
||||
// SPIRVCross cross-compiles spirv shaders to es, hlsl and others.
|
||||
type SPIRVCross struct {
|
||||
Bin string
|
||||
WorkDir WorkDir
|
||||
}
|
||||
|
||||
func NewSPIRVCross() *SPIRVCross { return &SPIRVCross{Bin: "spirv-cross"} }
|
||||
|
||||
// Convert converts compute shader from spirv format to a target format.
|
||||
func (spirv *SPIRVCross) Convert(path, variant string, shader []byte, target, version string) (string, error) {
|
||||
base := spirv.WorkDir.Path(filepath.Base(path), variant)
|
||||
|
||||
if err := spirv.WorkDir.WriteFile(base, shader); err != nil {
|
||||
return "", fmt.Errorf("unable to write shader to disk: %w", err)
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
switch target {
|
||||
case "glsl":
|
||||
cmd = exec.Command(spirv.Bin,
|
||||
"--no-es",
|
||||
"--version", version,
|
||||
)
|
||||
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.Args = append(cmd.Args, "--no-420pack-extension", base)
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s\nfailed to run %v: %w", out, cmd.Args, err)
|
||||
}
|
||||
s := string(out)
|
||||
if target != "hlsl" {
|
||||
// Strip Windows \r in line endings.
|
||||
s = unixLineEnding(s)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Metadata extracts metadata for a SPIR-V shader.
|
||||
func (spirv *SPIRVCross) Metadata(path, variant string, shader []byte) (Metadata, error) {
|
||||
base := spirv.WorkDir.Path(filepath.Base(path), variant)
|
||||
|
||||
if err := spirv.WorkDir.WriteFile(base, shader); err != nil {
|
||||
return Metadata{}, fmt.Errorf("unable to write shader to disk: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(spirv.Bin,
|
||||
base,
|
||||
"--reflect",
|
||||
)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return Metadata{}, fmt.Errorf("failed to run %v: %w", cmd.Args, err)
|
||||
}
|
||||
|
||||
meta, err := parseMetadata(out)
|
||||
if err != nil {
|
||||
return Metadata{}, fmt.Errorf("%s\nfailed to parse metadata: %w", out, err)
|
||||
}
|
||||
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
func parseMetadata(data []byte) (Metadata, error) {
|
||||
var reflect struct {
|
||||
Types map[string]struct {
|
||||
Name string `json:"name"`
|
||||
Members []struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Offset int `json:"offset"`
|
||||
} `json:"members"`
|
||||
} `json:"types"`
|
||||
Inputs []struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Location int `json:"location"`
|
||||
} `json:"inputs"`
|
||||
Textures []struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Set int `json:"set"`
|
||||
Binding int `json:"binding"`
|
||||
} `json:"textures"`
|
||||
UBOs []struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
BlockSize int `json:"block_size"`
|
||||
Set int `json:"set"`
|
||||
Binding int `json:"binding"`
|
||||
} `json:"ubos"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &reflect); err != nil {
|
||||
return Metadata{}, fmt.Errorf("failed to parse reflection data: %w", err)
|
||||
}
|
||||
|
||||
var m Metadata
|
||||
|
||||
for _, input := range reflect.Inputs {
|
||||
dataType, dataSize, err := parseDataType(input.Type)
|
||||
if err != nil {
|
||||
return Metadata{}, fmt.Errorf("parseReflection: %v", err)
|
||||
}
|
||||
m.Inputs = append(m.Inputs, driver.InputLocation{
|
||||
Name: input.Name,
|
||||
Location: input.Location,
|
||||
Semantic: "TEXCOORD",
|
||||
SemanticIndex: input.Location,
|
||||
Type: dataType,
|
||||
Size: dataSize,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(m.Inputs, func(i, j int) bool {
|
||||
return m.Inputs[i].Location < m.Inputs[j].Location
|
||||
})
|
||||
|
||||
blockOffset := 0
|
||||
for _, block := range reflect.UBOs {
|
||||
m.Uniforms.Blocks = append(m.Uniforms.Blocks, driver.UniformBlock{
|
||||
Name: block.Name,
|
||||
Binding: block.Binding,
|
||||
})
|
||||
t := reflect.Types[block.Type]
|
||||
// By convention uniform block variables are named by prepending an underscore
|
||||
// and converting to lowercase.
|
||||
blockVar := "_" + strings.ToLower(block.Name)
|
||||
for _, member := range t.Members {
|
||||
dataType, size, err := parseDataType(member.Type)
|
||||
if err != nil {
|
||||
return Metadata{}, fmt.Errorf("failed to parse reflection data: %v", err)
|
||||
}
|
||||
m.Uniforms.Locations = append(m.Uniforms.Locations, driver.UniformLocation{
|
||||
Name: fmt.Sprintf("%s.%s", blockVar, member.Name),
|
||||
Type: dataType,
|
||||
Size: size,
|
||||
Offset: blockOffset + member.Offset,
|
||||
})
|
||||
}
|
||||
blockOffset += block.BlockSize
|
||||
}
|
||||
m.Uniforms.Size = blockOffset
|
||||
|
||||
for _, texture := range reflect.Textures {
|
||||
m.Textures = append(m.Textures, driver.TextureBinding{
|
||||
Name: texture.Name,
|
||||
Binding: texture.Binding,
|
||||
})
|
||||
}
|
||||
|
||||
//return m, fmt.Errorf("not yet!: %+v", reflect)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func parseDataType(t string) (driver.DataType, int, error) {
|
||||
switch t {
|
||||
case "float":
|
||||
return driver.DataTypeFloat, 1, nil
|
||||
case "vec2":
|
||||
return driver.DataTypeFloat, 2, nil
|
||||
case "vec3":
|
||||
return driver.DataTypeFloat, 3, nil
|
||||
case "vec4":
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"gioui.org/gpu/internal/driver"
|
||||
"gioui.org/internal/d3d11"
|
||||
"gioui.org/shader"
|
||||
)
|
||||
|
||||
type Backend struct {
|
||||
@@ -287,7 +288,7 @@ func (b *Backend) NewFramebuffer(tex driver.Texture) (driver.Framebuffer, error)
|
||||
return fbo, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewInputLayout(vertexShader driver.ShaderSources, layout []driver.InputDesc) (driver.InputLayout, error) {
|
||||
func (b *Backend) NewInputLayout(vertexShader shader.Sources, layout []shader.InputDesc) (driver.InputLayout, error) {
|
||||
if len(vertexShader.Inputs) != len(layout) {
|
||||
return nil, fmt.Errorf("NewInputLayout: got %d inputs, expected %d", len(layout), len(vertexShader.Inputs))
|
||||
}
|
||||
@@ -300,7 +301,7 @@ func (b *Backend) NewInputLayout(vertexShader driver.ShaderSources, layout []dri
|
||||
}
|
||||
var format uint32
|
||||
switch l.Type {
|
||||
case driver.DataTypeFloat:
|
||||
case shader.DataTypeFloat:
|
||||
switch l.Size {
|
||||
case 1:
|
||||
format = d3d11.DXGI_FORMAT_R32_FLOAT
|
||||
@@ -313,7 +314,7 @@ func (b *Backend) NewInputLayout(vertexShader driver.ShaderSources, layout []dri
|
||||
default:
|
||||
panic("unsupported data size")
|
||||
}
|
||||
case driver.DataTypeShort:
|
||||
case shader.DataTypeShort:
|
||||
switch l.Size {
|
||||
case 1:
|
||||
format = d3d11.DXGI_FORMAT_R16_SINT
|
||||
@@ -332,7 +333,7 @@ func (b *Backend) NewInputLayout(vertexShader driver.ShaderSources, layout []dri
|
||||
AlignedByteOffset: uint32(l.Offset),
|
||||
}
|
||||
}
|
||||
l, err := b.dev.CreateInputLayout(descs, []byte(vertexShader.HLSL))
|
||||
l, err := b.dev.CreateInputLayout(descs, []byte(vertexShader.DXBC))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -380,16 +381,16 @@ func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (dri
|
||||
return &Buffer{backend: b, buf: buf, bind: bind, immutable: true}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewComputeProgram(shader driver.ShaderSources) (driver.Program, error) {
|
||||
func (b *Backend) NewComputeProgram(shader shader.Sources) (driver.Program, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (b *Backend) NewProgram(vertexShader, fragmentShader driver.ShaderSources) (driver.Program, error) {
|
||||
vs, err := b.dev.CreateVertexShader([]byte(vertexShader.HLSL))
|
||||
func (b *Backend) NewProgram(vertexShader, fragmentShader shader.Sources) (driver.Program, error) {
|
||||
vs, err := b.dev.CreateVertexShader([]byte(vertexShader.DXBC))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ps, err := b.dev.CreatePixelShader([]byte(fragmentShader.HLSL))
|
||||
ps, err := b.dev.CreatePixelShader([]byte(fragmentShader.DXBC))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"errors"
|
||||
"image"
|
||||
"time"
|
||||
|
||||
"gioui.org/shader"
|
||||
)
|
||||
|
||||
// Device represents the abstraction of underlying GPU
|
||||
@@ -23,9 +25,9 @@ type Device interface {
|
||||
NewFramebuffer(tex Texture) (Framebuffer, error)
|
||||
NewImmutableBuffer(typ BufferBinding, data []byte) (Buffer, error)
|
||||
NewBuffer(typ BufferBinding, size int) (Buffer, error)
|
||||
NewComputeProgram(shader ShaderSources) (Program, error)
|
||||
NewProgram(vertexShader, fragmentShader ShaderSources) (Program, error)
|
||||
NewInputLayout(vertexShader ShaderSources, layout []InputDesc) (InputLayout, error)
|
||||
NewComputeProgram(shader shader.Sources) (Program, error)
|
||||
NewProgram(vertexShader, fragmentShader shader.Sources) (Program, error)
|
||||
NewInputLayout(vertexShader shader.Sources, layout []shader.InputDesc) (InputLayout, error)
|
||||
|
||||
Clear(r, g, b, a float32)
|
||||
Viewport(x, y, width, height int)
|
||||
@@ -49,63 +51,6 @@ type Device interface {
|
||||
Release()
|
||||
}
|
||||
|
||||
type ShaderSources struct {
|
||||
Name string
|
||||
GLSL100ES string
|
||||
GLSL300ES string
|
||||
GLSL310ES string
|
||||
GLSL130 string
|
||||
GLSL150 string
|
||||
HLSL string
|
||||
Uniforms UniformsReflection
|
||||
Inputs []InputLocation
|
||||
Textures []TextureBinding
|
||||
Hash string
|
||||
}
|
||||
|
||||
type UniformsReflection struct {
|
||||
Blocks []UniformBlock
|
||||
Locations []UniformLocation
|
||||
Size int
|
||||
}
|
||||
|
||||
type TextureBinding struct {
|
||||
Name string
|
||||
Binding int
|
||||
}
|
||||
|
||||
type UniformBlock struct {
|
||||
Name string
|
||||
Binding int
|
||||
}
|
||||
|
||||
type UniformLocation struct {
|
||||
Name string
|
||||
Type DataType
|
||||
Size int
|
||||
Offset int
|
||||
}
|
||||
|
||||
type InputLocation struct {
|
||||
// For GLSL.
|
||||
Name string
|
||||
Location int
|
||||
// For HLSL.
|
||||
Semantic string
|
||||
SemanticIndex int
|
||||
|
||||
Type DataType
|
||||
Size int
|
||||
}
|
||||
|
||||
// InputDesc describes a vertex attribute as laid out in a Buffer.
|
||||
type InputDesc struct {
|
||||
Type DataType
|
||||
Size int
|
||||
|
||||
Offset int
|
||||
}
|
||||
|
||||
// InputLayout is the driver specific representation of the mapping
|
||||
// between Buffers and shader attributes.
|
||||
type InputLayout interface {
|
||||
@@ -123,8 +68,6 @@ type TextureFormat uint8
|
||||
|
||||
type BufferBinding uint8
|
||||
|
||||
type DataType uint8
|
||||
|
||||
type Features uint
|
||||
|
||||
type Caps struct {
|
||||
@@ -167,12 +110,6 @@ type Texture interface {
|
||||
Release()
|
||||
}
|
||||
|
||||
const (
|
||||
DataTypeFloat DataType = iota
|
||||
DataTypeInt
|
||||
DataTypeShort
|
||||
)
|
||||
|
||||
const (
|
||||
BufferBindingIndices BufferBinding = 1 << iota
|
||||
BufferBindingVertices
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"gioui.org/gpu/internal/driver"
|
||||
"gioui.org/internal/gl"
|
||||
"gioui.org/shader"
|
||||
)
|
||||
|
||||
// Backend implements driver.Device.
|
||||
@@ -139,13 +140,13 @@ type uniformsTracker struct {
|
||||
type uniformLocation struct {
|
||||
uniform gl.Uniform
|
||||
offset int
|
||||
typ driver.DataType
|
||||
typ shader.DataType
|
||||
size int
|
||||
}
|
||||
|
||||
type gpuInputLayout struct {
|
||||
inputs []driver.InputLocation
|
||||
layout []driver.InputDesc
|
||||
inputs []shader.InputLocation
|
||||
layout []shader.InputDesc
|
||||
}
|
||||
|
||||
// textureTriple holds the type settings for
|
||||
@@ -846,7 +847,7 @@ func (b *Backend) Clear(colR, colG, colB, colA float32) {
|
||||
b.funcs.Clear(gl.COLOR_BUFFER_BIT)
|
||||
}
|
||||
|
||||
func (b *Backend) NewInputLayout(vs driver.ShaderSources, layout []driver.InputDesc) (driver.InputLayout, error) {
|
||||
func (b *Backend) NewInputLayout(vs shader.Sources, layout []shader.InputDesc) (driver.InputLayout, error) {
|
||||
if len(vs.Inputs) != len(layout) {
|
||||
return nil, fmt.Errorf("NewInputLayout: got %d inputs, expected %d", len(layout), len(vs.Inputs))
|
||||
}
|
||||
@@ -861,7 +862,7 @@ func (b *Backend) NewInputLayout(vs driver.ShaderSources, layout []driver.InputD
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewComputeProgram(src driver.ShaderSources) (driver.Program, error) {
|
||||
func (b *Backend) NewComputeProgram(src shader.Sources) (driver.Program, error) {
|
||||
p, err := gl.CreateComputeProgram(b.funcs, src.GLSL310ES)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %v", src.Name, err)
|
||||
@@ -873,7 +874,7 @@ func (b *Backend) NewComputeProgram(src driver.ShaderSources) (driver.Program, e
|
||||
return gpuProg, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewProgram(vertShader, fragShader driver.ShaderSources) (driver.Program, error) {
|
||||
func (b *Backend) NewProgram(vertShader, fragShader shader.Sources) (driver.Program, error) {
|
||||
attr := make([]string, len(vertShader.Inputs))
|
||||
for _, inp := range vertShader.Inputs {
|
||||
attr[inp.Location] = inp.Name
|
||||
@@ -937,7 +938,7 @@ func (b *Backend) NewProgram(vertShader, fragShader driver.ShaderSources) (drive
|
||||
return gpuProg, nil
|
||||
}
|
||||
|
||||
func lookupUniform(funcs *gl.Functions, p gl.Program, loc driver.UniformLocation) uniformLocation {
|
||||
func lookupUniform(funcs *gl.Functions, p gl.Program, loc shader.UniformLocation) uniformLocation {
|
||||
u := funcs.GetUniformLocation(p, loc.Name)
|
||||
if !u.Valid() {
|
||||
panic(fmt.Errorf("uniform %q not found", loc.Name))
|
||||
@@ -985,7 +986,7 @@ func (p *gpuProgram) Release() {
|
||||
p.backend.glstate.deleteProgram(p.backend.funcs, p.obj)
|
||||
}
|
||||
|
||||
func (u *uniformsTracker) setup(funcs *gl.Functions, p gl.Program, uniformSize int, uniforms []driver.UniformLocation) {
|
||||
func (u *uniformsTracker) setup(funcs *gl.Functions, p gl.Program, uniformSize int, uniforms []shader.UniformLocation) {
|
||||
u.locs = make([]uniformLocation, len(uniforms))
|
||||
for i, uniform := range uniforms {
|
||||
u.locs[i] = lookupUniform(funcs, p, uniform)
|
||||
@@ -1016,19 +1017,19 @@ func (p *uniformsTracker) update(funcs *gl.Functions) {
|
||||
for _, u := range p.locs {
|
||||
data := data[u.offset:]
|
||||
switch {
|
||||
case u.typ == driver.DataTypeFloat && u.size == 1:
|
||||
case u.typ == shader.DataTypeFloat && u.size == 1:
|
||||
data := data[:4]
|
||||
v := *(*[1]float32)(unsafe.Pointer(&data[0]))
|
||||
funcs.Uniform1f(u.uniform, v[0])
|
||||
case u.typ == driver.DataTypeFloat && u.size == 2:
|
||||
case u.typ == shader.DataTypeFloat && u.size == 2:
|
||||
data := data[:8]
|
||||
v := *(*[2]float32)(unsafe.Pointer(&data[0]))
|
||||
funcs.Uniform2f(u.uniform, v[0], v[1])
|
||||
case u.typ == driver.DataTypeFloat && u.size == 3:
|
||||
case u.typ == shader.DataTypeFloat && u.size == 3:
|
||||
data := data[:12]
|
||||
v := *(*[3]float32)(unsafe.Pointer(&data[0]))
|
||||
funcs.Uniform3f(u.uniform, v[0], v[1], v[2])
|
||||
case u.typ == driver.DataTypeFloat && u.size == 4:
|
||||
case u.typ == shader.DataTypeFloat && u.size == 4:
|
||||
data := data[:16]
|
||||
v := *(*[4]float32)(unsafe.Pointer(&data[0]))
|
||||
funcs.Uniform4f(u.uniform, v[0], v[1], v[2], v[3])
|
||||
@@ -1108,9 +1109,9 @@ func (b *Backend) setupVertexArrays() {
|
||||
l := layout.layout[i]
|
||||
var gltyp gl.Enum
|
||||
switch l.Type {
|
||||
case driver.DataTypeFloat:
|
||||
case shader.DataTypeFloat:
|
||||
gltyp = gl.FLOAT
|
||||
case driver.DataTypeShort:
|
||||
case shader.DataTypeShort:
|
||||
gltyp = gl.SHORT
|
||||
default:
|
||||
panic("unsupported data type")
|
||||
|
||||
Reference in New Issue
Block a user