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 }