gl01/internal/render/shader.go
2022-01-20 21:58:50 +08:00

258 lines
6.3 KiB
Go

package render
import (
"fmt"
"log"
"strings"
"edgaru089.ml/go/gl01/internal/util/itype"
"github.com/go-gl/gl/all-core/gl"
"github.com/go-gl/mathgl/mgl32"
)
// returns max texture unit count
func getMaxTextureUnits() int32 {
var cnt int32
gl.GetIntegerv(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS, &cnt)
return cnt
}
// Shader contains a shader program, with a vertex and a fragment (pixel) shader.
type Shader struct {
prog uint32
uniforms map[string]int32
textures map[int32]*Texture // maps uniform location to *Texture
}
// helper construct to get uniforms and restore previous glUseProgram
func (s *Shader) uniformBlender(name string) (location int32, restore func()) {
if s.prog != 0 {
var saved int32
// Use program object
gl.GetIntegerv(gl.CURRENT_PROGRAM, &saved)
if uint32(saved) != s.prog {
gl.UseProgram(s.prog)
}
return s.UniformLocation(name), func() {
if uint32(saved) != s.prog {
gl.UseProgram(uint32(saved))
}
}
}
return 0, func() {}
}
func compileShader(src string, stype uint32) (prog uint32, err error) {
prog = gl.CreateShader(stype)
strs, free := gl.Strs(src, "\x00")
gl.ShaderSource(prog, 1, strs, nil)
free()
gl.CompileShader(prog)
var status int32
gl.GetShaderiv(prog, gl.COMPILE_STATUS, &status)
if status == gl.FALSE {
var len int32
gl.GetShaderiv(prog, gl.INFO_LOG_LENGTH, &len)
log := strings.Repeat("\x00", int(len+1))
gl.GetShaderInfoLog(prog, len, nil, gl.Str(log))
gl.DeleteShader(prog)
switch stype {
case gl.VERTEX_SHADER:
return 0, fmt.Errorf("failed to compile Vertex Shader: %s", log)
case gl.FRAGMENT_SHADER:
return 0, fmt.Errorf("failed to compile Fragment Shader: %s", log)
default:
return 0, fmt.Errorf("failed to compile Unknown(%d) Shader: %s", stype, log)
}
}
return
}
// NewShader compiles and links a Vertex and a Fragment (Pixel) shader into one program.
// The source code does not need to be terminated with \x00.
func NewShader(vert, frag string) (s *Shader, err error) {
vertid, err := compileShader(vert, gl.VERTEX_SHADER)
if err != nil {
return nil, err
}
fragid, err := compileShader(frag, gl.FRAGMENT_SHADER)
if err != nil {
gl.DeleteShader(vertid)
return nil, err
}
s = &Shader{}
s.uniforms = make(map[string]int32)
s.textures = make(map[int32]*Texture)
s.prog = gl.CreateProgram()
gl.AttachShader(s.prog, vertid)
gl.AttachShader(s.prog, fragid)
gl.LinkProgram(s.prog)
var status int32
gl.GetProgramiv(s.prog, gl.LINK_STATUS, &status)
if status == gl.FALSE {
var len int32
gl.GetProgramiv(s.prog, gl.INFO_LOG_LENGTH, &len)
log := strings.Repeat("\x00", int(len+1))
gl.GetProgramInfoLog(s.prog, len, nil, gl.Str(log))
gl.DeleteProgram(s.prog)
gl.DeleteShader(vertid)
gl.DeleteShader(fragid)
return nil, fmt.Errorf("failed to link Program: %s", log)
}
gl.DeleteShader(vertid)
gl.DeleteShader(fragid)
return
}
// UniformLocation returns the location id of the given uniform.
// it returns -1 if the uniform is not found.
func (s *Shader) UniformLocation(name string) int32 {
if id, ok := s.uniforms[name]; ok {
return id
} else {
location := gl.GetUniformLocation(s.prog, gl.Str(name+"\x00"))
s.uniforms[name] = location
if location == -1 {
log.Printf("Shader: uniform \"%s\" not found", name)
}
return location
}
}
// UseProgram calls glUseProgram.
func (s *Shader) UseProgram() {
gl.UseProgram(s.prog)
}
// BindTextures calls glActiveTexture and glBindTexture, updating the texture unit slots.
func (s *Shader) BindTextures() {
var i int
for loc, tex := range s.textures {
index := int32(i + 1)
gl.Uniform1i(loc, index)
gl.ActiveTexture(uint32(gl.TEXTURE0 + index))
gl.BindTexture(gl.TEXTURE_2D, tex.tex)
i++
}
gl.ActiveTexture(gl.TEXTURE0)
}
// Handle returns the OpenGL handle of the program.
func (s *Shader) Handle() uint32 {
return s.prog
}
func (s *Shader) SetUniformTexture(name string, tex *Texture) {
if s.prog == 0 {
return
}
loc := s.UniformLocation(name)
if loc == -1 {
return
}
// Store the location to texture map
_, ok := s.textures[loc]
if !ok {
// new texture, make sure there are enough texture units
if len(s.textures)+1 >= int(getMaxTextureUnits()) {
log.Printf("Shader: Warning: Impossible to use texture \"%s\" for shader: all available texture units are used", name)
return
}
}
s.textures[loc] = tex
}
// SetUniformTextureHandle sets a uniform as a sampler2D from an external OpenGL texture.
// tex is the OpenGL texture handle (the one you get with glGenTextures())
func (s *Shader) SetUniformTextureHandle(name string, tex uint32) {
s.SetUniformTexture(name, &Texture{tex: tex})
}
func (s *Shader) SetUniformMat4(name string, value mgl32.Mat4) {
loc, restore := s.uniformBlender(name)
defer restore()
gl.UniformMatrix4fv(loc, 1, false, &value[0])
}
func (s *Shader) SetUniformFloat(name string, value float32) {
loc, restore := s.uniformBlender(name)
defer restore()
gl.Uniform1f(loc, value)
}
func (s *Shader) SetUniformVec2f(name string, value itype.Vec2f) {
loc, restore := s.uniformBlender(name)
defer restore()
gl.Uniform2f(loc, value[0], value[1])
}
func (s *Shader) SetUniformVec3f(name string, value itype.Vec3f) {
loc, restore := s.uniformBlender(name)
defer restore()
gl.Uniform3f(loc, value[0], value[1], value[2])
}
func (s *Shader) SetUniformVec4f(name string, value itype.Vec4f) {
loc, restore := s.uniformBlender(name)
defer restore()
gl.Uniform4f(loc, value[0], value[1], value[2], value[3])
}
func (s *Shader) SetUniformInt(name string, value int32) {
loc, restore := s.uniformBlender(name)
defer restore()
gl.Uniform1i(loc, value)
}
func (s *Shader) SetUniformVec2i(name string, value itype.Vec2i) {
loc, restore := s.uniformBlender(name)
defer restore()
gl.Uniform2i(loc, int32(value[0]), int32(value[1]))
}
func (s *Shader) SetUniformVec3i(name string, value itype.Vec3i) {
loc, restore := s.uniformBlender(name)
defer restore()
gl.Uniform3i(loc, int32(value[0]), int32(value[1]), int32(value[2]))
}
func (s *Shader) SetUniformVec4i(name string, value itype.Vec4i) {
loc, restore := s.uniformBlender(name)
defer restore()
gl.Uniform4i(loc, int32(value[0]), int32(value[1]), int32(value[2]), int32(value[3]))
}
func (s *Shader) GetAttribLocation(name string) uint32 {
name = name + "\x00"
return uint32(gl.GetAttribLocation(s.prog, gl.Str(name)))
}