180 lines
4.5 KiB
Go
Executable file
180 lines
4.5 KiB
Go
Executable file
package xmobilebackend
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"golang.org/x/mobile/gl"
|
|
)
|
|
|
|
type shaderProgram struct {
|
|
b *XMobileBackend
|
|
ID gl.Program
|
|
vs, fs gl.Shader
|
|
|
|
attribs map[string]gl.Attrib
|
|
uniforms map[string]gl.Uniform
|
|
}
|
|
|
|
func loadShader(b *XMobileBackend, vs, fs string, sp *shaderProgram) error {
|
|
sp.b = b
|
|
glError(b) // clear the current error
|
|
|
|
// compile vertex shader
|
|
{
|
|
sp.vs = b.glctx.CreateShader(gl.VERTEX_SHADER)
|
|
b.glctx.ShaderSource(sp.vs, vs)
|
|
b.glctx.CompileShader(sp.vs)
|
|
|
|
status := b.glctx.GetShaderi(sp.vs, gl.COMPILE_STATUS)
|
|
if status != gl.TRUE {
|
|
clog := b.glctx.GetShaderInfoLog(sp.vs)
|
|
b.glctx.DeleteShader(sp.vs)
|
|
return fmt.Errorf("failed to compile vertex shader:\n\n%s", clog)
|
|
}
|
|
if err := glError(b); err != nil {
|
|
return fmt.Errorf("gl error after compiling vertex shader: %v", err)
|
|
}
|
|
}
|
|
|
|
// compile fragment shader
|
|
{
|
|
sp.fs = b.glctx.CreateShader(gl.FRAGMENT_SHADER)
|
|
b.glctx.ShaderSource(sp.fs, fs)
|
|
b.glctx.CompileShader(sp.fs)
|
|
|
|
status := b.glctx.GetShaderi(sp.fs, gl.COMPILE_STATUS)
|
|
if status != gl.TRUE {
|
|
clog := b.glctx.GetShaderInfoLog(sp.fs)
|
|
b.glctx.DeleteShader(sp.fs)
|
|
return fmt.Errorf("failed to compile fragment shader:\n\n%s", clog)
|
|
}
|
|
if err := glError(b); err != nil {
|
|
return fmt.Errorf("gl error after compiling fragment shader: %v", err)
|
|
}
|
|
}
|
|
|
|
// link shader program
|
|
{
|
|
sp.ID = b.glctx.CreateProgram()
|
|
b.glctx.AttachShader(sp.ID, sp.vs)
|
|
b.glctx.AttachShader(sp.ID, sp.fs)
|
|
b.glctx.LinkProgram(sp.ID)
|
|
|
|
status := b.glctx.GetProgrami(sp.ID, gl.LINK_STATUS)
|
|
if status != gl.TRUE {
|
|
clog := b.glctx.GetProgramInfoLog(sp.ID)
|
|
b.glctx.DeleteProgram(sp.ID)
|
|
b.glctx.DeleteShader(sp.vs)
|
|
b.glctx.DeleteShader(sp.fs)
|
|
return fmt.Errorf("failed to link shader program:\n\n%s", clog)
|
|
}
|
|
if err := glError(b); err != nil {
|
|
return fmt.Errorf("gl error after linking shader: %v", err)
|
|
}
|
|
}
|
|
|
|
b.glctx.UseProgram(sp.ID)
|
|
// load the attributes
|
|
count := b.glctx.GetProgrami(sp.ID, gl.ACTIVE_ATTRIBUTES)
|
|
sp.attribs = make(map[string]gl.Attrib, int(count))
|
|
for i := 0; i < count; i++ {
|
|
name, _, _ := b.glctx.GetActiveAttrib(sp.ID, uint32(i))
|
|
sp.attribs[name] = b.glctx.GetAttribLocation(sp.ID, name)
|
|
}
|
|
|
|
// load the uniforms
|
|
count = b.glctx.GetProgrami(sp.ID, gl.ACTIVE_UNIFORMS)
|
|
sp.uniforms = make(map[string]gl.Uniform, int(count))
|
|
for i := 0; i < count; i++ {
|
|
name, _, _ := b.glctx.GetActiveUniform(sp.ID, uint32(i))
|
|
sp.uniforms[name] = b.glctx.GetUniformLocation(sp.ID, name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sp *shaderProgram) use() {
|
|
sp.b.glctx.UseProgram(sp.ID)
|
|
}
|
|
|
|
func (sp *shaderProgram) delete() {
|
|
sp.b.glctx.DeleteProgram(sp.ID)
|
|
sp.b.glctx.DeleteShader(sp.vs)
|
|
sp.b.glctx.DeleteShader(sp.fs)
|
|
}
|
|
|
|
func (sp *shaderProgram) loadLocations(target interface{}) error {
|
|
val := reflect.ValueOf(target)
|
|
if val.Kind() != reflect.Ptr {
|
|
panic("target must be a pointer to a struct")
|
|
}
|
|
val = val.Elem()
|
|
if val.Kind() != reflect.Struct {
|
|
panic("target must be a pointer to a struct")
|
|
}
|
|
|
|
sp.b.glctx.UseProgram(sp.ID)
|
|
|
|
var errs strings.Builder
|
|
|
|
for name, loc := range sp.attribs {
|
|
field := val.FieldByName(sp.structName(name))
|
|
if field == (reflect.Value{}) {
|
|
fmt.Fprintf(&errs, "field for attribute \"%s\" not found; ", name)
|
|
} else if field.Type() != reflect.TypeOf(gl.Attrib{}) {
|
|
fmt.Fprintf(&errs, "field for attribute \"%s\" must have type gl.Attrib; ", name)
|
|
} else {
|
|
field.Set(reflect.ValueOf(loc))
|
|
}
|
|
}
|
|
|
|
for name, loc := range sp.uniforms {
|
|
field := val.FieldByName(sp.structName(name))
|
|
if field == (reflect.Value{}) {
|
|
fmt.Fprintf(&errs, "field for uniform \"%s\" not found; ", name)
|
|
} else if field.Type() != reflect.TypeOf(gl.Uniform{}) {
|
|
fmt.Fprintf(&errs, "field for uniform \"%s\" must have type gl.Uniform; ", name)
|
|
} else {
|
|
field.Set(reflect.ValueOf(loc))
|
|
}
|
|
}
|
|
|
|
if errs.Len() > 0 {
|
|
return errors.New(strings.TrimSpace(errs.String()))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sp *shaderProgram) structName(name string) string {
|
|
rn, sz := utf8.DecodeRuneInString(name)
|
|
name = fmt.Sprintf("%c%s", unicode.ToUpper(rn), name[sz:])
|
|
idx := strings.IndexByte(name, '[')
|
|
if idx > 0 {
|
|
name = name[:idx]
|
|
}
|
|
return name
|
|
}
|
|
|
|
func (sp *shaderProgram) mustLoadLocations(target interface{}) {
|
|
err := sp.loadLocations(target)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (sp *shaderProgram) enableAllVertexAttribArrays() {
|
|
for _, loc := range sp.attribs {
|
|
sp.b.glctx.EnableVertexAttribArray(loc)
|
|
}
|
|
}
|
|
|
|
func (sp *shaderProgram) disableAllVertexAttribArrays() {
|
|
for _, loc := range sp.attribs {
|
|
sp.b.glctx.DisableVertexAttribArray(loc)
|
|
}
|
|
}
|