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 }