forked from g3n/engine
/
shaman.go
355 lines (299 loc) · 10.5 KB
/
shaman.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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
// 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 renderer
import (
"fmt"
"regexp"
"strings"
"github.com/g3n/engine/gls"
"github.com/g3n/engine/material"
"github.com/g3n/engine/renderer/shaders"
"strconv"
)
const GLSL_VERSION = "330 core"
// Regular expression to parse #include <name> [quantity] directive
var rexInclude *regexp.Regexp
const indexParameter = "{i}"
func init() {
rexInclude = regexp.MustCompile(`#include\s+<(.*)>\s*(?:\[(.*)]|)`)
}
// ShaderSpecs describes the specification of a compiled shader program
type ShaderSpecs struct {
Name string // Shader name
Version string // GLSL version
ShaderUnique bool // indicates if shader is independent of lights and textures
UseLights material.UseLights // Bitmask indicating which lights to consider
AmbientLightsMax int // Current number of ambient lights
DirLightsMax int // Current Number of directional lights
PointLightsMax int // Current Number of point lights
SpotLightsMax int // Current Number of spot lights
MatTexturesMax int // Current Number of material textures
Defines gls.ShaderDefines // Additional shader defines
}
// ProgSpecs represents a compiled shader program along with its specs
type ProgSpecs struct {
program *gls.Program // program object
specs ShaderSpecs // associated specs
}
// Shaman is the shader manager
type Shaman struct {
gs *gls.GLS
includes map[string]string // include files sources
shadersm map[string]string // maps shader name to its template
proginfo map[string]shaders.ProgramInfo // maps name of the program to ProgramInfo
programs []ProgSpecs // list of compiled programs with specs
specs ShaderSpecs // Current shader specs
}
// NewShaman creates and returns a pointer to a new shader manager
func NewShaman(gs *gls.GLS) *Shaman {
sm := new(Shaman)
sm.Init(gs)
return sm
}
// Init initializes the shader manager
func (sm *Shaman) Init(gs *gls.GLS) {
sm.gs = gs
sm.includes = make(map[string]string)
sm.shadersm = make(map[string]string)
sm.proginfo = make(map[string]shaders.ProgramInfo)
}
// AddDefaultShaders adds to this shader manager all default
// include chunks, shaders and programs statically registered.
func (sm *Shaman) AddDefaultShaders() error {
for _, name := range shaders.Includes() {
sm.AddChunk(name, shaders.IncludeSource(name))
}
for _, name := range shaders.Shaders() {
sm.AddShader(name, shaders.ShaderSource(name))
}
for _, name := range shaders.Programs() {
sm.proginfo[name] = shaders.GetProgramInfo(name)
}
return nil
}
// AddChunk adds a shader chunk with the specified name and source code
func (sm *Shaman) AddChunk(name, source string) {
sm.includes[name] = source
}
// AddShader adds a shader program with the specified name and source code
func (sm *Shaman) AddShader(name, source string) {
sm.shadersm[name] = source
}
// AddProgram adds a program with the specified name and associated vertex
// and fragment shaders names (previously registered)
func (sm *Shaman) AddProgram(name, vertexName, fragName string, others ...string) {
geomName := ""
if len(others) > 0 {
geomName = others[0]
}
sm.proginfo[name] = shaders.ProgramInfo{
Vertex: vertexName,
Fragment: fragName,
Geometry: geomName,
}
}
// SetProgram sets the shader program to satisfy the specified specs.
// Returns an indication if the current shader has changed and a possible error
// when creating a new shader program.
// Receives a copy of the specs because it changes the fields which specify the
// number of lights depending on the UseLights flags.
func (sm *Shaman) SetProgram(s *ShaderSpecs) (bool, error) {
// Checks material use lights bit mask
var specs ShaderSpecs
specs.copy(s)
if (specs.UseLights & material.UseLightAmbient) == 0 {
specs.AmbientLightsMax = 0
}
if (specs.UseLights & material.UseLightDirectional) == 0 {
specs.DirLightsMax = 0
}
if (specs.UseLights & material.UseLightPoint) == 0 {
specs.PointLightsMax = 0
}
if (specs.UseLights & material.UseLightSpot) == 0 {
specs.SpotLightsMax = 0
}
// If current shader specs are the same as the specified specs, nothing to do.
if sm.specs.equals(&specs) {
return false, nil
}
// Search for compiled program with the specified specs
for _, pinfo := range sm.programs {
if pinfo.specs.equals(&specs) {
sm.gs.UseProgram(pinfo.program)
sm.specs = specs
return true, nil
}
}
// Generates new program with the specified specs
prog, err := sm.GenProgram(&specs)
if err != nil {
return false, err
}
log.Debug("Created new shader:%v", specs.Name)
// Save specs as current specs, adds new program to the list and activates the program
sm.specs = specs
sm.programs = append(sm.programs, ProgSpecs{prog, specs})
sm.gs.UseProgram(prog)
return true, nil
}
// GenProgram generates shader program from the specified specs
func (sm *Shaman) GenProgram(specs *ShaderSpecs) (*gls.Program, error) {
// Get info for the specified shader program
progInfo, ok := sm.proginfo[specs.Name]
if !ok {
return nil, fmt.Errorf("Program:%s not found", specs.Name)
}
// Sets the defines map
defines := map[string]string{}
defines["AMB_LIGHTS"] = strconv.Itoa(specs.AmbientLightsMax)
defines["DIR_LIGHTS"] = strconv.Itoa(specs.DirLightsMax)
defines["POINT_LIGHTS"] = strconv.Itoa(specs.PointLightsMax)
defines["SPOT_LIGHTS"] = strconv.Itoa(specs.SpotLightsMax)
defines["MAT_TEXTURES"] = strconv.Itoa(specs.MatTexturesMax)
// Adds additional material and geometry defines from the specs parameter
for name, value := range specs.Defines {
defines[name] = value
}
// Get vertex shader source
vertexSource, ok := sm.shadersm[progInfo.Vertex]
if !ok {
return nil, fmt.Errorf("Vertex shader:%s not found", progInfo.Vertex)
}
// Pre-process vertex shader source
vertexSource, err := sm.preprocess(vertexSource, defines)
if err != nil {
return nil, err
}
//fmt.Printf("vertexSource:%s\n", vertexSource)
// Get fragment shader source
fragSource, ok := sm.shadersm[progInfo.Fragment]
if err != nil {
return nil, fmt.Errorf("Fragment shader:%s not found", progInfo.Fragment)
}
// Pre-process fragment shader source
fragSource, err = sm.preprocess(fragSource, defines)
if err != nil {
return nil, err
}
//fmt.Printf("fragSource:%s\n", fragSource)
// Checks for optional geometry shader compiled template
var geomSource = ""
if progInfo.Geometry != "" {
// Get geometry shader source
geomSource, ok = sm.shadersm[progInfo.Geometry]
if !ok {
return nil, fmt.Errorf("Geometry shader:%s not found", progInfo.Geometry)
}
// Pre-process geometry shader source
geomSource, err = sm.preprocess(geomSource, defines)
if err != nil {
return nil, err
}
}
// Creates shader program
prog := sm.gs.NewProgram()
prog.AddShader(gls.VERTEX_SHADER, vertexSource)
prog.AddShader(gls.FRAGMENT_SHADER, fragSource)
if progInfo.Geometry != "" {
prog.AddShader(gls.GEOMETRY_SHADER, geomSource)
}
err = prog.Build()
if err != nil {
return nil, err
}
return prog, nil
}
func (sm *Shaman) preprocess(source string, defines map[string]string) (string, error) {
// If defines map supplied, generate prefix with glsl version directive first,
// followed by "#define" directives
var prefix = ""
if defines != nil { // This is only true for the outer call
prefix = fmt.Sprintf("#version %s\n", GLSL_VERSION)
for name, value := range defines {
prefix = prefix + fmt.Sprintf("#define %s %s\n", name, value)
}
}
return sm.processIncludes(prefix + source, defines)
}
// preprocess preprocesses the specified source prefixing it with optional defines directives
// contained in "defines" parameter and replaces '#include <name>' directives
// by the respective source code of include chunk of the specified name.
// The included "files" are also processed recursively.
func (sm *Shaman) processIncludes(source string, defines map[string]string) (string, error) {
// Find all string submatches for the "#include <name>" directive
matches := rexInclude.FindAllStringSubmatch(source, 100)
if len(matches) == 0 {
return source, nil
}
// For each directive found, replace the name by the respective include chunk source code
//var newSource = source
for _, m := range matches {
incFullMatch := m[0]
incName := m[1]
incQuantityVariable := m[2]
// Get the source of the include chunk with the match <name>
incSource := sm.includes[incName]
if len(incSource) == 0 {
return "", fmt.Errorf("Include:[%s] not found", incName)
}
// Preprocess the include chunk source code
incSource, err := sm.processIncludes(incSource, defines)
if err != nil {
return "", err
}
// Skip line
incSource = "\n" + incSource
// Process include quantity variable if provided
if incQuantityVariable != "" {
incQuantityString, defined := defines[incQuantityVariable]
if defined { // Only process #include if quantity variable is defined
incQuantity, err := strconv.Atoi(incQuantityString)
if err != nil {
return "", err
}
// Check for iterated includes and populate index parameter
if incQuantity > 0 {
repeatedIncludeSource := ""
for i := 0; i < incQuantity; i++ {
// Replace all occurrences of the index parameter with the current index i.
repeatedIncludeSource += strings.Replace(incSource, indexParameter, strconv.Itoa(i), -1)
}
incSource = repeatedIncludeSource
}
} else {
incSource = ""
}
}
// Replace all occurrences of the include directive with its processed source code
source = strings.Replace(source, incFullMatch, incSource, -1)
}
return source, nil
}
// copy copies other spec into this
func (ss *ShaderSpecs) copy(other *ShaderSpecs) {
*ss = *other
if other.Defines != nil {
ss.Defines = *gls.NewShaderDefines()
ss.Defines.Add(&other.Defines)
}
}
// equals compares two ShaderSpecs and returns true if they are effectively equal.
func (ss *ShaderSpecs) equals(other *ShaderSpecs) bool {
if ss.Name != other.Name {
return false
}
if other.ShaderUnique {
return true
}
if ss.AmbientLightsMax == other.AmbientLightsMax &&
ss.DirLightsMax == other.DirLightsMax &&
ss.PointLightsMax == other.PointLightsMax &&
ss.SpotLightsMax == other.SpotLightsMax &&
ss.MatTexturesMax == other.MatTexturesMax &&
ss.Defines.Equals(&other.Defines) {
return true
}
return false
}