forked from g3n/engine
/
program.go
227 lines (187 loc) · 5.8 KB
/
program.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// 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"
"io"
"strconv"
"strings"
)
// Program represents an OpenGL program.
// It must have Vertex and Fragment shaders.
// It can also have a Geometry shader.
type Program struct {
gs *GLS // OpenGL state
ShowSource bool // Show source code in error messages
handle uint32 // OpenGL program handle
shaders []shaderInfo // List of shaders for this program
uniforms map[string]int32 // List of uniforms
}
// shaderInfo contains OpenGL-related shader information.
type shaderInfo struct {
stype uint32 // OpenGL shader type (VERTEX_SHADER, FRAGMENT_SHADER, or GEOMETRY_SHADER)
source string // Shader source code
handle uint32 // OpenGL shader handle
}
// Map from shader types to names.
var shaderNames = map[uint32]string{
VERTEX_SHADER: "Vertex Shader",
FRAGMENT_SHADER: "Fragment Shader",
GEOMETRY_SHADER: "Geometry Shader",
}
// NewProgram creates and returns 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
}
// Handle returns the OpenGL handle of this program.
func (prog *Program) Handle() uint32 {
return prog.handle
}
// AddShader adds a shader to this program.
// This must be done before the program is built.
func (prog *Program) AddShader(stype uint32, source string) {
// Check if program already built
if prog.handle != 0 {
log.Fatal("Program already built")
}
prog.shaders = append(prog.shaders, shaderInfo{stype, source, 0})
}
// DeleteShaders deletes all of this program's shaders from OpenGL.
func (prog *Program) DeleteShaders() {
for _, shaderInfo := range prog.shaders {
if shaderInfo.handle != 0 {
prog.gs.DeleteShader(shaderInfo.handle)
shaderInfo.handle = 0
}
}
}
// Build builds the program, compiling and linking the previously supplied shaders.
func (prog *Program) Build() error {
// Check if program already built
if prog.handle != 0 {
return fmt.Errorf("program already built")
}
// Check if shaders were provided
if len(prog.shaders) == 0 {
return fmt.Errorf("no shaders supplied")
}
// Create program
prog.handle = prog.gs.CreateProgram()
if prog.handle == 0 {
return fmt.Errorf("error creating program")
}
// Clean unused GL allocated resources
defer prog.DeleteShaders()
// Compile and attach shaders
for _, sinfo := range prog.shaders {
shader, err := prog.CompileShader(sinfo.stype, sinfo.source)
if err != nil {
prog.gs.DeleteProgram(prog.handle)
prog.handle = 0
msg := fmt.Sprintf("error compiling %s: %s", shaderNames[sinfo.stype], err)
if prog.ShowSource {
msg += FormatSource(sinfo.source)
}
return errors.New(msg)
}
sinfo.handle = shader
prog.gs.AttachShader(prog.handle, shader)
}
// Link program and check for errors
prog.gs.LinkProgram(prog.handle)
var status int32
prog.gs.GetProgramiv(prog.handle, LINK_STATUS, &status)
if status == FALSE {
log := prog.gs.GetProgramInfoLog(prog.handle)
prog.handle = 0
return fmt.Errorf("error linking program: %v", log)
}
return nil
}
// GetAttribLocation returns the location of the specified attribute
// in this program. This location is internally cached.
func (prog *Program) GetAttribLocation(name string) int32 {
return prog.gs.GetAttribLocation(prog.handle, name)
}
// 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 {
prog.gs.stats.UnilocHits++
return loc
}
// Get location from OpenGL
loc = prog.gs.GetUniformLocation(prog.handle, name)
prog.gs.stats.UnilocMiss++
// Cache result
prog.uniforms[name] = loc
if loc < 0 {
log.Warn("Program.GetUniformLocation(%s): NOT FOUND", name)
}
return loc
}
// CompileShader creates and compiles an OpenGL shader of the specified type, with
// the specified source code, and returns a non-zero value by which it can be referenced.
func (prog *Program) CompileShader(stype uint32, source string) (uint32, error) {
// Create shader object
shader := prog.gs.CreateShader(stype)
if shader == 0 {
return 0, fmt.Errorf("error creating shader")
}
// Set shader source and compile it
prog.gs.ShaderSource(shader, source)
prog.gs.CompileShader(shader)
// Get the shader compiler log
slog := prog.gs.GetShaderInfoLog(shader)
// Get the shader compile status
var status int32
prog.gs.GetShaderiv(shader, COMPILE_STATUS, &status)
if status == FALSE {
return shader, fmt.Errorf("%s", slog)
}
// If the shader compiled OK but the log has data,
// log this data instead of returning error
if len(slog) > 2 {
log.Warn("%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")
}