// Copyright 2016 The G3N Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package gls import ( "bytes" "errors" "fmt" "github.com/g3n/engine/math32" "github.com/go-gl/gl/v3.3-core/gl" "io" "strconv" "strings" ) // Shader Program Object type Program struct { // Shows source code in error messages ShowSource bool gs *GLS handle uint32 shaders []shaderInfo uniforms map[string]int32 Specs interface{} } type shaderInfo struct { stype uint32 source string defines map[string]interface{} handle uint32 } // Map shader types to names var shaderNames = map[uint32]string{ gl.VERTEX_SHADER: "Vertex Shader", gl.FRAGMENT_SHADER: "Fragment Shader", } // NewProgram creates a new empty shader program object. // Use this type methods to add shaders and build the final program. func (gs *GLS) NewProgram() *Program { prog := new(Program) prog.gs = gs prog.shaders = make([]shaderInfo, 0) prog.uniforms = make(map[string]int32) prog.ShowSource = true return prog } // AddShaders adds a shader to this program. // This must be done before the program is built. func (prog *Program) AddShader(stype uint32, source string, defines map[string]interface{}) { if prog.handle != 0 { log.Fatal("Program already built") } prog.shaders = append(prog.shaders, shaderInfo{stype, source, defines, 0}) } // Build builds the program compiling and linking the previously supplied shaders. func (prog *Program) Build() error { if prog.handle != 0 { return fmt.Errorf("Program already built") } // Checks if shaders were provided if len(prog.shaders) == 0 { return fmt.Errorf("No shaders supplied") } // Create program prog.handle = gl.CreateProgram() if prog.handle == 0 { return fmt.Errorf("Error creating program") } // Clean unused GL allocated resources defer func() { for _, sinfo := range prog.shaders { if sinfo.handle != 0 { gl.DeleteShader(sinfo.handle) sinfo.handle = 0 } } }() // Compiles and attach each shader for _, sinfo := range prog.shaders { // Creates string with defines from specified parameters deflines := make([]string, 0) if sinfo.defines != nil { for pname, pval := range sinfo.defines { line := "#define " + pname + " " switch val := pval.(type) { case bool: if val { deflines = append(deflines, line) } case float32: line += strconv.FormatFloat(float64(val), 'f', -1, 32) deflines = append(deflines, line) default: panic("Parameter type not supported") } } } deftext := strings.Join(deflines, "\n") // Compile shader shader, err := CompileShader(sinfo.stype, sinfo.source+deftext) if err != nil { gl.DeleteProgram(prog.handle) prog.handle = 0 msg := fmt.Sprintf("Error compiling %s: %s", shaderNames[sinfo.stype], err) if prog.ShowSource { source := FormatSource(sinfo.source + deftext) msg += source } return errors.New(msg) } sinfo.handle = shader gl.AttachShader(prog.handle, shader) } // Link program and checks for errors gl.LinkProgram(prog.handle) var status int32 gl.GetProgramiv(prog.handle, gl.LINK_STATUS, &status) if status == gl.FALSE { var logLength int32 gl.GetProgramiv(prog.handle, gl.INFO_LOG_LENGTH, &logLength) log := strings.Repeat("\x00", int(logLength+1)) gl.GetProgramInfoLog(prog.handle, logLength, nil, gl.Str(log)) prog.handle = 0 return fmt.Errorf("Error linking program: %v", log) } return nil } // Handle returns the handle of this program func (prog *Program) Handle() uint32 { return prog.handle } // GetActiveUniformBlockSize returns the minimum number of bytes // to contain the data for the uniform block specified by its index. func (prog *Program) GetActiveUniformBlockSize(ubindex uint32) int32 { var uboSize int32 gl.GetActiveUniformBlockiv(prog.handle, ubindex, gl.UNIFORM_BLOCK_DATA_SIZE, &uboSize) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("GetUniformBlockSize(%v) error: %d", ubindex, ecode) } } return uboSize } // GetActiveUniformsiv returns information about the specified uniforms // specified by its indices func (prog *Program) GetActiveUniformsiv(indices []uint32, pname uint32) []int32 { data := make([]int32, len(indices)) gl.GetActiveUniformsiv(prog.handle, int32(len(indices)), &indices[0], pname, &data[0]) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("GetActiveUniformsiv() error: %d", ecode) } } return data } // GetAttributeLocation returns the location of the specified attribute // in this program. This location is internally cached. func (prog *Program) GetAttribLocation(name string) int32 { loc := gl.GetAttribLocation(prog.handle, gl.Str(name+"\x00")) prog.gs.checkError("GetAttribLocation") return loc } // GetUniformBlockIndex returns the index of the named uniform block. // If the supplied name is not valid, the function returns gl.INVALID_INDEX func (prog *Program) GetUniformBlockIndex(name string) uint32 { index := gl.GetUniformBlockIndex(prog.handle, gl.Str(name+"\x00")) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("GetUniformBlockIndex(%s) error", name) } } return index } // GetUniformIndices returns the indices for each specified named // uniform. If an specified name is not valid the corresponding // index value will be gl.INVALID_INDEX func (prog *Program) GetUniformIndices(names []string) []uint32 { // Add C terminators to uniform names for _, s := range names { s += "\x00" } unames, freefunc := gl.Strs(names...) indices := make([]uint32, len(names)) gl.GetUniformIndices(prog.handle, int32(len(names)), unames, &indices[0]) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("GetUniformIndices() error: %d", ecode) } } freefunc() return indices } // GetUniformLocation returns the location of the specified uniform in this program. // This location is internally cached. func (prog *Program) GetUniformLocation(name string) int32 { // Try to get from the cache loc, ok := prog.uniforms[name] if ok { return loc } // Get location from GL loc = gl.GetUniformLocation(prog.handle, gl.Str(name+"\x00")) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("GetUniformLocation(%s) error: %d", name, ecode) } } // Cache result prog.uniforms[name] = loc if loc < 0 { log.Warn("GetUniformLocation(%s) NOT FOUND", name) } return loc } // SetUniformInt sets this program uniform variable specified by // its location to the the value of the specified int func (prog *Program) SetUniformInt(loc int32, v int) { gl.Uniform1i(loc, int32(v)) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformInt() error: %d", ecode) } } } // SetUniformFloat sets this program uniform variable specified by // its location to the the value of the specified float func (prog *Program) SetUniformFloat(loc int32, v float32) { gl.Uniform1f(loc, v) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformFloat() error: %d", ecode) } } } // SetUniformVector2 sets this program uniform variable specified by // its location to the the value of the specified Vector2 func (prog *Program) SetUniformVector2(loc int32, v *math32.Vector2) { gl.Uniform2f(loc, v.X, v.Y) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformVector2() error: %d", ecode) } } } // SetUniformVector3 sets this program uniform variable specified by // its location to the the value of the specified Vector3 func (prog *Program) SetUniformVector3(loc int32, v *math32.Vector3) { gl.Uniform3f(loc, v.X, v.Y, v.Z) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformVector3() error: %d", ecode) } } } // SetUniformVector4 sets this program uniform variable specified by // its location to the the value of the specified Vector4 func (prog *Program) SetUniformVector4(loc int32, v *math32.Vector4) { gl.Uniform4f(loc, v.X, v.Y, v.Z, v.W) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformVector4() error: %d", ecode) } } } // SetUniformMatrix3 sets this program uniform variable specified by // its location with the values from the specified Matrix3. func (prog *Program) SetUniformMatrix3(loc int32, m *math32.Matrix3) { gl.UniformMatrix3fv(loc, 1, false, &m[0]) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformMatrix3() error: %d", ecode) } } } // SetUniformMatrix4 sets this program uniform variable specified by // its location with the values from the specified Matrix4. func (prog *Program) SetUniformMatrix4(loc int32, m *math32.Matrix4) { gl.UniformMatrix4fv(loc, 1, false, &m[0]) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformMatrix4() error: %d", ecode) } } } // SetUniformIntByName sets this program uniform variable specified by // its name to the value of the specified int. // The specified name location is cached internally. func (prog *Program) SetUniformIntByName(name string, v int) { gl.Uniform1i(prog.GetUniformLocation(name), int32(v)) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("GetUniformIntByName(%s) error: %d", name, ecode) } } } // SetUniformFloatByName sets this program uniform variable specified by // its name to the value of the specified float32. // The specified name location is cached internally. func (prog *Program) SetUniformFloatByName(name string, v float32) { gl.Uniform1f(prog.GetUniformLocation(name), v) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformFloatByName(%s) error: %d", name, ecode) } } } // SetUniformVector2ByName sets this program uniform variable specified by // its name to the values from the specified Vector2. // The specified name location is cached internally. func (prog *Program) SetUniformVector2ByName(name string, v *math32.Vector2) { gl.Uniform2f(prog.GetUniformLocation(name), v.X, v.Y) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformVector2ByName(%s) error: %d", name, ecode) } } } // SetUniformVector3ByName sets this program uniform variable specified by // its name to the values from the specified Vector3. // The specified name location is cached internally. func (prog *Program) SetUniformVector3ByName(name string, v *math32.Vector3) { gl.Uniform3f(prog.GetUniformLocation(name), v.X, v.Y, v.Z) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformVector3ByName(%s) error: %d", name, ecode) } } } // SetUniformVector4ByName sets this program uniform variable specified by // its name to the values from the specified Vector4. // The specified name location is cached internally. func (prog *Program) SetUniformVector4ByName(name string, v *math32.Vector4) { gl.Uniform4f(prog.GetUniformLocation(name), v.X, v.Y, v.Z, v.W) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformVector4ByName(%s) error: %d", name, ecode) } } } // SetUniformMatrix3ByName sets this program uniform variable specified by // its name with the values from the specified Matrix3. // The specified name location is cached internally. func (prog *Program) SetUniformMatrix3ByName(name string, m *math32.Matrix3) { gl.UniformMatrix3fv(prog.GetUniformLocation(name), 1, false, &m[0]) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformMatrix3ByName(%s) error: %d", name, ecode) } } } // SetUniformMatrix4ByName sets this program uniform variable specified by // its name with the values from the specified Matrix4. // The location of the name is cached internally. func (prog *Program) SetUniformMatrix4ByName(name string, m *math32.Matrix4) { gl.UniformMatrix4fv(prog.GetUniformLocation(name), 1, false, &m[0]) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformMatrix4ByName(%s) error: %d", name, ecode) } } } // SetUniformColorByName set this program uniform variable specified by // its name to the values from the specified Color // The specified name location is cached internally. func (prog *Program) SetUniformColorByName(name string, c *math32.Color) { gl.Uniform3f(prog.GetUniformLocation(name), c.R, c.G, c.B) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformColorByName(%s) error: %d", name, ecode) } } } // SetUniformColor4ByName set this program uniform variable specified by // its name to the values from the specified Color4 // The specified name location is cached internally. func (prog *Program) SetUniformColor4ByName(name string, c *math32.Color4) { gl.Uniform4f(prog.GetUniformLocation(name), c.R, c.G, c.B, c.A) if prog.gs.CheckErrors { ecode := gl.GetError() if ecode != 0 { log.Fatal("SetUniformColor4ByName(%s) error: %d", name, ecode) } } } // CompileShader creates and compiles a shader of the specified type and with // the specified source code and returns a non-zero value by which // it can be referenced. func CompileShader(stype uint32, source string) (uint32, error) { shader := gl.CreateShader(stype) if shader == 0 { return 0, fmt.Errorf("Error creating shader") } // Allocates C string to store the source csource, freeSource := gl.Strs(source + "\x00") defer freeSource() // Set shader source and compile it gl.ShaderSource(shader, 1, csource, nil) gl.CompileShader(shader) // Get the shader compiler log var logLength int32 gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength) slog := strings.Repeat("\x00", int(logLength+1)) gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(slog)) // Get the shader compile status var status int32 gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status) if status == gl.FALSE { return shader, fmt.Errorf("%s", slog) } // Even if the shader compiled OK, if the log has data, // return error to see warnings if len(slog) > 2 { return shader, fmt.Errorf("%s", slog) } return shader, nil } // FormatSource returns the supplied program source code with // line numbers prepended. func FormatSource(source string) string { // Reads all lines from the source string lines := make([]string, 0) buf := bytes.NewBuffer([]byte(source)) for { line, err := buf.ReadBytes('\n') if err != nil { if err == io.EOF { break } panic(err) } lines = append(lines, string(line[:len(line)-1])) } // Adds a final line terminator lines = append(lines, "\n") // Prepends the line number for each line ndigits := len(strconv.Itoa(len(lines))) format := "%0" + strconv.Itoa(ndigits) + "d:%s" formatted := make([]string, 0) for pos, l := range lines { fline := fmt.Sprintf(format, pos+1, l) formatted = append(formatted, fline) } return strings.Join(formatted, "\n") }