tofu

Making something with OpenGL in Go
Log | Files | Refs

shader.go (5329B)


      1 package tofu
      2 
      3 import (
      4 	"fmt"
      5 	"os"
      6 	"reflect"
      7 	"strings"
      8 
      9 	"github.com/go-gl/gl/v3.3-core/gl"
     10 )
     11 
     12 // CompileShader compiles a shader of type xtype using the source file specified by path.
     13 // It returns the resulting shader ID.
     14 func compileShader(path string, xtype uint32) (shaderID uint32, err error) {
     15 	src, err := os.ReadFile(path)
     16 	if err != nil {
     17 		return 0, fmt.Errorf("readfile %s: %v", path, err)
     18 	}
     19 	shaderID = gl.CreateShader(xtype)
     20 	if shaderID == 0 {
     21 		return 0, fmt.Errorf("can't create shader")
     22 	}
     23 	cstrs, free := gl.Strs(string(src) + "\x00")
     24 	defer free()
     25 	gl.ShaderSource(shaderID, 1, cstrs, nil)
     26 	gl.CompileShader(shaderID)
     27 
     28 	var success int32
     29 	gl.GetShaderiv(shaderID, gl.COMPILE_STATUS, &success)
     30 	if success == gl.FALSE {
     31 		var logLength int32
     32 		gl.GetShaderiv(shaderID, gl.INFO_LOG_LENGTH, &logLength)
     33 		l := gl.Str(strings.Repeat("\x00", int(logLength)))
     34 		gl.GetShaderInfoLog(shaderID, logLength, nil, l)
     35 		gl.DeleteShader(shaderID)
     36 		return 0, fmt.Errorf("%v", gl.GoStr(l))
     37 	}
     38 	return shaderID, nil
     39 }
     40 
     41 // LinkShaders links vertex shader vsID and fragment shader fsID and returns the
     42 // resulting shader program.
     43 func linkShaders(vsID, fsID uint32) (shaderID uint32, err error) {
     44 	shaderID = gl.CreateProgram()
     45 	gl.AttachShader(shaderID, vsID)
     46 	gl.AttachShader(shaderID, fsID)
     47 	gl.LinkProgram(shaderID)
     48 
     49 	var success int32
     50 	gl.GetProgramiv(shaderID, gl.LINK_STATUS, &success)
     51 	if success == gl.FALSE {
     52 		var logLength int32
     53 		gl.GetProgramiv(shaderID, gl.INFO_LOG_LENGTH, &logLength)
     54 		l := gl.Str(strings.Repeat("\x00", int(logLength)))
     55 		gl.GetProgramInfoLog(shaderID, logLength, nil, l)
     56 		return 0, fmt.Errorf("%v", gl.GoStr(l))
     57 	}
     58 	return shaderID, nil
     59 }
     60 
     61 type Program struct {
     62 	id       uint32
     63 	Uniforms any
     64 }
     65 
     66 // NewProgram creates a shader program from a vertex shader source file
     67 // specified by vpath and a fragment shader source file
     68 // specified by fpath, and returns the resulting Program object.
     69 func NewProgram(vpath, fpath string) (*Program, error) {
     70 	vid, err := compileShader(vpath, gl.VERTEX_SHADER)
     71 	if err != nil {
     72 		return nil, fmt.Errorf("compile vertex shader: %v", err)
     73 	}
     74 	fid, err := compileShader(fpath, gl.FRAGMENT_SHADER)
     75 	if err != nil {
     76 		return nil, fmt.Errorf("compile fragment shader: %v", err)
     77 	}
     78 	pid, err := linkShaders(vid, fid)
     79 	if err != nil {
     80 		return nil, fmt.Errorf("link shaders: %v", err)
     81 	}
     82 	gl.DeleteShader(vid)
     83 	gl.DeleteShader(fid)
     84 	return &Program{id: pid}, nil
     85 }
     86 
     87 // Use activates the shader.
     88 func (p *Program) Use() {
     89 	gl.UseProgram(p.id)
     90 }
     91 
     92 func (p *Program) SetUniforms() error {
     93 	return p.setUniforms(p.Uniforms, "")
     94 }
     95 
     96 func (p *Program) setUniforms(uniforms any, prefix string) error {
     97 	if uniforms == nil {
     98 		return nil
     99 	}
    100 	if reflect.TypeOf(uniforms).Kind() == reflect.Pointer {
    101 		uniforms = reflect.ValueOf(uniforms).Elem().Interface()
    102 	}
    103 	s := reflect.ValueOf(uniforms)
    104 	fields := reflect.VisibleFields(s.Type())
    105 	for i := 0; i < s.NumField(); i++ {
    106 		fv := s.Field(i)
    107 		ft := fields[i]
    108 		tag, ok := ft.Tag.Lookup("tofu")
    109 		if !ok {
    110 			tag = ft.Name
    111 		}
    112 		if prefix != "" {
    113 			tag = prefix + "." + tag
    114 		}
    115 		switch ft.Type.String() {
    116 		case "tofu.Mat4":
    117 			if err := p.SetMat4(tag, fv.Interface().(Mat4)); err != nil {
    118 				return fmt.Errorf("set %s: %v", tag, err)
    119 			}
    120 		case "tofu.Vec3":
    121 			if err := p.SetVec3(tag, fv.Interface().(Vec3)); err != nil {
    122 				return fmt.Errorf("set %s: %v", tag, err)
    123 			}
    124 		case "float32":
    125 			if err := p.SetFloat32(tag, fv.Interface().(float32)); err != nil {
    126 				return fmt.Errorf("set %s: %v", tag, err)
    127 			}
    128 		case "*tofu.Texture":
    129 			if err := p.SetTexture(tag, fv.Interface().(*Texture)); err != nil {
    130 				return fmt.Errorf("set %s: %v", tag, err)
    131 			}
    132 		default:
    133 			switch fv.Kind() {
    134 			case reflect.Struct:
    135 				if err := p.setUniforms(fv.Interface(), tag); err != nil {
    136 					return err
    137 				}
    138 				continue
    139 			case reflect.Pointer:
    140 				if err := p.setUniforms(fv.Elem().Interface(), tag); err != nil {
    141 					return err
    142 				}
    143 				continue
    144 			default:
    145 				return fmt.Errorf("unknown uniform type: %s", ft.Type)
    146 			}
    147 		}
    148 	}
    149 	return nil
    150 }
    151 
    152 // SetFloat32 set the value of the uniform name.
    153 func (p *Program) SetFloat32(name string, val float32) error {
    154 	l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00"))
    155 	if l == -1 {
    156 		return fmt.Errorf("uniform %s not found", name)
    157 	}
    158 	gl.Uniform1f(l, val)
    159 	return nil
    160 }
    161 
    162 func (p *Program) SetVec2(name string, val Vec2) error {
    163 	l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00"))
    164 	if l == -1 {
    165 		return fmt.Errorf("uniform %s not found", name)
    166 	}
    167 	gl.Uniform2fv(l, 1, &val[0])
    168 	return nil
    169 }
    170 
    171 func (p *Program) SetVec3(name string, val Vec3) error {
    172 	l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00"))
    173 	if l == -1 {
    174 		return fmt.Errorf("uniform %s not found", name)
    175 	}
    176 	gl.Uniform3fv(l, 1, &val[0])
    177 	return nil
    178 }
    179 
    180 func (p *Program) SetMat4(name string, val Mat4) error {
    181 	l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00"))
    182 	if l == -1 {
    183 		return fmt.Errorf("uniform %s not found", name)
    184 	}
    185 	gl.UniformMatrix4fv(l, 1, false, &val[0])
    186 	return nil
    187 }
    188 
    189 func (p *Program) SetTexture(name string, t *Texture) error {
    190 	p.Use()
    191 	gl.ActiveTexture(t.unit)
    192 	gl.BindTexture(gl.TEXTURE_2D, t.id)
    193 	l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00"))
    194 	if l == -1 {
    195 		return fmt.Errorf("no such uniform: %s", name)
    196 	}
    197 	gl.Uniform1i(l, int32(t.unit-gl.TEXTURE0))
    198 	return nil
    199 }