tofu

Making something with OpenGL in Go
Log | Files | Refs

shader.go (5986B)


      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 		errmsg := gl.GoStr(l)
     37 		if strings.HasPrefix(errmsg, "0:") {
     38 			errmsg = errmsg[2:]
     39 		}
     40 		return 0, fmt.Errorf("%v", errmsg)
     41 	}
     42 	return shaderID, nil
     43 }
     44 
     45 // LinkShaders links vertex shader vsID and fragment shader fsID and returns the
     46 // resulting shader program.
     47 func linkShaders(vsID, fsID uint32) (shaderID uint32, err error) {
     48 	shaderID = gl.CreateProgram()
     49 	gl.AttachShader(shaderID, vsID)
     50 	gl.AttachShader(shaderID, fsID)
     51 	gl.LinkProgram(shaderID)
     52 
     53 	var success int32
     54 	gl.GetProgramiv(shaderID, gl.LINK_STATUS, &success)
     55 	if success == gl.FALSE {
     56 		var logLength int32
     57 		gl.GetProgramiv(shaderID, gl.INFO_LOG_LENGTH, &logLength)
     58 		l := gl.Str(strings.Repeat("\x00", int(logLength)))
     59 		gl.GetProgramInfoLog(shaderID, logLength, nil, l)
     60 		return 0, fmt.Errorf("%v", gl.GoStr(l))
     61 	}
     62 	return shaderID, nil
     63 }
     64 
     65 type Program struct {
     66 	id              uint32
     67 	Uniforms        any
     68 	nextTextureUnit uint32 // 0-indexed
     69 }
     70 
     71 // NewProgram creates a shader program from a vertex shader source file
     72 // specified by vpath and a fragment shader source file
     73 // specified by fpath, and returns the resulting Program object.
     74 func NewProgram(vpath, fpath string) (*Program, error) {
     75 	vid, err := compileShader(vpath, gl.VERTEX_SHADER)
     76 	if err != nil {
     77 		return nil, fmt.Errorf("compile %s:%v", vpath, err)
     78 	}
     79 	fid, err := compileShader(fpath, gl.FRAGMENT_SHADER)
     80 	if err != nil {
     81 		return nil, fmt.Errorf("compile %s:%v", fpath, err)
     82 	}
     83 	pid, err := linkShaders(vid, fid)
     84 	if err != nil {
     85 		return nil, fmt.Errorf("link shaders: %v", err)
     86 	}
     87 	gl.DeleteShader(vid)
     88 	gl.DeleteShader(fid)
     89 	return &Program{id: pid}, nil
     90 }
     91 
     92 // Use activates the shader.
     93 func (p *Program) Use() {
     94 	gl.UseProgram(p.id)
     95 }
     96 
     97 func (p *Program) SetUniforms() error {
     98 	p.nextTextureUnit = 0
     99 	return p.setUniforms(p.Uniforms, "")
    100 }
    101 
    102 func (p *Program) setUniforms(uniforms any, prefix string) error {
    103 	if uniforms == nil {
    104 		return nil
    105 	}
    106 	if reflect.TypeOf(uniforms).Kind() == reflect.Pointer {
    107 		uniforms = reflect.ValueOf(uniforms).Elem().Interface()
    108 	}
    109 	s := reflect.ValueOf(uniforms)
    110 	fields := reflect.VisibleFields(s.Type())
    111 	for i := 0; i < s.NumField(); i++ {
    112 		fv := s.Field(i)
    113 		ft := fields[i]
    114 		tag, ok := ft.Tag.Lookup("tofu")
    115 		if !ok {
    116 			tag = ft.Name
    117 		}
    118 		if prefix != "" {
    119 			tag = prefix + "." + tag
    120 		}
    121 		switch ft.Type.String() {
    122 		case "tofu.Mat4":
    123 			if err := p.SetMat4(tag, fv.Interface().(Mat4)); err != nil {
    124 				return fmt.Errorf("set %s: %v", tag, err)
    125 			}
    126 		case "tofu.Vec3":
    127 			if err := p.SetVec3(tag, fv.Interface().(Vec3)); err != nil {
    128 				return fmt.Errorf("set %s: %v", tag, err)
    129 			}
    130 		case "float32":
    131 			if err := p.SetFloat32(tag, fv.Interface().(float32)); err != nil {
    132 				return fmt.Errorf("set %s: %v", tag, err)
    133 			}
    134 		case "*tofu.Texture":
    135 			if err := p.SetTexture(tag, fv.Interface().(*Texture)); err != nil {
    136 				return fmt.Errorf("set %s: %v", tag, err)
    137 			}
    138 		default:
    139 			switch fv.Kind() {
    140 			case reflect.Struct:
    141 				if err := p.setUniforms(fv.Interface(), tag); err != nil {
    142 					return err
    143 				}
    144 			case reflect.Pointer:
    145 				if err := p.setUniforms(fv.Elem().Interface(), tag); err != nil {
    146 					return err
    147 				}
    148 			case reflect.Slice:
    149 				for i := 0; i < fv.Len(); i++ {
    150 					item := fv.Index(i).Interface()
    151 					tagi := fmt.Sprintf("%s[%d]", tag, i)
    152 					if err := p.setUniforms(item, tagi); err != nil {
    153 						return err
    154 					}
    155 				}
    156 			default:
    157 				return fmt.Errorf("unknown uniform type: %s", ft.Type)
    158 			}
    159 		}
    160 	}
    161 	return nil
    162 }
    163 
    164 // SetFloat32 set the value of the uniform name.
    165 func (p *Program) SetFloat32(name string, val float32) error {
    166 	l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00"))
    167 	if l == -1 {
    168 		return fmt.Errorf("uniform %s not found", name)
    169 	}
    170 	gl.Uniform1f(l, val)
    171 	return nil
    172 }
    173 
    174 func (p *Program) SetVec2(name string, val Vec2) error {
    175 	l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00"))
    176 	if l == -1 {
    177 		return fmt.Errorf("uniform %s not found", name)
    178 	}
    179 	gl.Uniform2fv(l, 1, &val[0])
    180 	return nil
    181 }
    182 
    183 func (p *Program) SetVec3(name string, val Vec3) error {
    184 	l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00"))
    185 	if l == -1 {
    186 		return fmt.Errorf("uniform %s not found", name)
    187 	}
    188 	gl.Uniform3fv(l, 1, &val[0])
    189 	return nil
    190 }
    191 
    192 func (p *Program) SetMat4(name string, val Mat4) error {
    193 	l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00"))
    194 	if l == -1 {
    195 		return fmt.Errorf("uniform %s not found", name)
    196 	}
    197 	gl.UniformMatrix4fv(l, 1, false, &val[0])
    198 	return nil
    199 }
    200 
    201 func (p *Program) SetTexture(name string, t *Texture) error {
    202 	p.Use()
    203 	gl.ActiveTexture(p.nextTextureUnit + gl.TEXTURE0)
    204 	gl.BindTexture(gl.TEXTURE_2D, t.id)
    205 	l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00"))
    206 	if l == -1 {
    207 		return fmt.Errorf("no such uniform: %s", name)
    208 	}
    209 	gl.Uniform1i(l, int32(p.nextTextureUnit))
    210 	p.nextTextureUnit++
    211 	return nil
    212 }
    213 
    214 func (p *Program) SetBool(name string, t bool) error {
    215 	l := gl.GetUniformLocation(p.id, gl.Str(name+"\x00"))
    216 	if l == -1 {
    217 		return fmt.Errorf("no such uniform: %s", name)
    218 	}
    219 	if t {
    220 		gl.Uniform1i(l, gl.TRUE)
    221 	} else {
    222 		gl.Uniform1i(l, gl.FALSE)
    223 	}
    224 	return nil
    225 }