-
-
Notifications
You must be signed in to change notification settings - Fork 179
Expand file tree
/
Copy pathshader.py
More file actions
447 lines (340 loc) · 13.7 KB
/
shader.py
File metadata and controls
447 lines (340 loc) · 13.7 KB
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
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) 2009-2016 Nicolas P. Rougier. All rights reserved.
# Distributed under the (new) BSD License.
# -----------------------------------------------------------------------------
"""
A Shader is a user-defined program designed to run on some stage of a
graphics processor. Its purpose is to execute one of the programmable stages of
the rendering pipeline.
Read more on shaders on `OpenGL Wiki <https://www.opengl.org/wiki/Shader>`_
**Example usage**
.. code:: python
vertex = '''
attribute vec2 position;
void main (void)
{
gl_Position = vec4(0.85*position, 0.0, 1.0);
} '''
fragment = '''
void main(void)
{
gl_FragColor = vec4(1.0,1.0,0.0,1.0);
} '''
quad = gloo.Program(vertex, fragment, count=4)
quad['position'] = [(-1,-1), (-1,+1), (+1,-1), (+1,+1)]
"""
import re
import os.path
import numpy as np
from glumpy import gl
from glumpy.log import log
from . snippet import Snippet
from . globject import GLObject
from . parser import (remove_comments, preprocess,
get_uniforms, get_attributes, get_hooks)
# ------------------------------------------------------------ Shader class ---
class Shader(GLObject):
"""
Abstract shader class.
:param gl.GLEnum target:
* gl.GL_VERTEX_SHADER
* gl.GL_FRAGMENT_SHADER
* gl.GL_GEOMETRY_SHADER
:param str code: Shader code or a filename containing shader code
.. note::
If the shader code is actually a filename, the filename must be prefixed
with ``file:``. Note that you can also get shader code from the library
module.
"""
_gtypes = {
'float': gl.GL_FLOAT,
'vec2': gl.GL_FLOAT_VEC2,
'vec3': gl.GL_FLOAT_VEC3,
'vec4': gl.GL_FLOAT_VEC4,
'int': gl.GL_INT,
'ivec2': gl.GL_INT_VEC2,
'ivec3': gl.GL_INT_VEC3,
'ivec4': gl.GL_INT_VEC4,
'bool': gl.GL_BOOL,
'bvec2': gl.GL_BOOL_VEC2,
'bvec3': gl.GL_BOOL_VEC3,
'bvec4': gl.GL_BOOL_VEC4,
'mat2': gl.GL_FLOAT_MAT2,
'mat3': gl.GL_FLOAT_MAT3,
'mat4': gl.GL_FLOAT_MAT4,
'sampler1D': gl.GL_SAMPLER_1D,
'sampler2D': gl.GL_SAMPLER_2D,
'sampler3D': gl.GL_SAMPLER_3D,
'samplerCube': gl.GL_SAMPLER_CUBE,
}
def __init__(self, target, code, version="120"):
"""
Initialize the shader.
"""
GLObject.__init__(self)
self._target = target
self._snippets = {}
self._version = version
if os.path.isfile(code):
with open(code, 'rt') as file:
self._code = preprocess(file.read())
self._source = os.path.basename(code)
else:
self._code = preprocess(code)
self._source = '<string>'
self._hooked = self._code
self._need_update = True
self._program = None
def __setitem__(self, name, snippet):
"""
Set a snippet on the given hook in the source code.
"""
self._snippets[name] = snippet
def _replace_hooks(self, name, snippet):
#re_hook = r"(?P<hook>%s)(\.(?P<subhook>\w+))?" % name
re_hook = r"(?P<hook>%s)(\.(?P<subhook>[\.\w\!]+))?" % name
re_args = r"(\((?P<args>[^<>]+)\))?"
re_hooks = re.compile("\<"+re_hook+re_args+"\>" , re.VERBOSE )
pattern = "\<" + re_hook + re_args + "\>"
# snippet is not a Snippet (it should be a string)
if not isinstance(snippet,Snippet):
def replace(match):
hook = match.group('hook')
subhook = match.group('subhook')
if subhook:
return snippet + '.' + subhook
return snippet
self._hooked = re.sub(pattern, replace, self._hooked)
return
# Store snippet code for later inclusion
# self._snippets.append(snippet)
# Replace expression of type <hook.subhook(args)>
def replace_with_args(match):
hook = match.group('hook')
subhook = match.group('subhook')
args = match.group('args')
if subhook and '.' in subhook:
s = snippet
for item in subhook.split('.')[:-1]:
if isinstance(s[item], Snippet):
s = s[item]
subhook = subhook.split('.')[-1]
# If the last snippet name endswith "!" this means to call
# the snippet with given arguments and not the ones stored.
# If S = A(B(C))("t"):
# <S> -> A(B(C("t")))
# <S!>(t) -> A("t")
override = False
if subhook[-1] == "!":
override = True
subhook = subhook[:-1]
# Do we have a class alias ? We don't return it yet since we
# need its translation from the symbol table
if subhook in s.aliases.keys():
subhook = s.aliases[subhook]
# If subhook is a variable (uniform/attribute/varying)
if subhook in s.globals:
return s.globals[subhook]
return s.mangled_call(subhook, match.group("args"), override=override)
# If subhook is a variable (uniform/attribute/varying)
if subhook in snippet.globals:
return snippet.globals[subhook]
return snippet.mangled_call(subhook, match.group("args"))
self._hooked = re.sub(pattern, replace_with_args, self._hooked)
def reset(self):
""" Reset shader snippets """
self._snippets = {}
@property
def code(self):
""" Shader source code (built from original and snippet codes) """
# Last minute hook settings
self._hooked = self._code
for name,snippet in self._snippets.items():
self._replace_hooks(name,snippet)
snippet_code = "// --- Snippets code : start --- //\n"
deps = []
for snippet in self._snippets.values():
if isinstance(snippet, Snippet):
deps.extend(snippet.dependencies)
for snippet in list(set(deps)):
snippet_code += snippet.mangled_code()
snippet_code += "// --- Snippets code : end --- //\n"
return snippet_code + self._hooked
def _create(self):
""" Create the shader """
log.debug("GPU: Creating shader")
# Check if we have something to compile
if not self.code:
raise RuntimeError("No code has been given")
# Check that shader object has been created
if self._handle <= 0:
self._handle = gl.glCreateShader(self._target)
if self._handle <= 0:
raise RuntimeError("Cannot create shader object")
def _update(self):
""" Compile the source and checks everything's ok """
log.debug("GPU: Compiling shader")
if len(self.hooks):
hooks = [name for name,snippet in self.hooks]
error = "Shader has pending hooks (%s), cannot compile" % hooks
raise RuntimeError(error)
# Set shader version
code = ("#version %s\n" % self._version) + self.code
gl.glShaderSource(self._handle, code)
# Actual compilation
gl.glCompileShader(self._handle)
status = gl.glGetShaderiv(self._handle, gl.GL_COMPILE_STATUS)
if not status:
error = gl.glGetShaderInfoLog(self._handle).decode()
parsed_errors = self._parse_error(error)
for lineno, mesg in parsed_errors:
self._print_error(mesg, lineno - 1)
raise RuntimeError("Shader compilation error")
def _delete(self):
""" Delete shader from GPU memory (if it was present). """
gl.glDeleteShader(self._handle)
_ERROR_RE = [
# Nvidia
# 0(7): error C1008: undefined variable "MV"
# 0(2) : error C0118: macros prefixed with '__' are reserved
re.compile(r'^\s*(\d+)\((?P<line_no>\d+)\)\s*:\s(?P<error_msg>.*)', re.MULTILINE),
# ATI / Intel
# ERROR: 0:131: '{' : syntax error parse error
re.compile(r'^\s*ERROR:\s(\d+):(?P<line_no>\d+):\s(?P<error_msg>.*)', re.MULTILINE),
# Nouveau
# 0:28(16): error: syntax error, unexpected ')', expecting '('
re.compile(r'^\s*(\d+):(?P<line_no>\d+)\((\d+)\):\s(?P<error_msg>.*)', re.MULTILINE)
]
def _parse_error(self, error):
"""
Parses a single GLSL error and extracts the line number and error
description.
Parameters
----------
error : str
An error string as returned by the compilation process
"""
for error_re in self._ERROR_RE:
matches = list(error_re.finditer(error))
if matches:
errors = [(int(m.group('line_no')), m.group('error_msg')) for m in matches]
return sorted(errors, key=lambda elem: elem[0])
else:
raise ValueError('Unknown GLSL error format:\n{}\n'.format(error))
def _print_error(self, error, lineno):
"""
Print error and show the faulty line + some context
Parameters
----------
error : str
An error string as returned byt the compilation process
lineno: int
Line where error occurs
"""
lines = self.code.split('\n')
start = max(0,lineno-3)
end = min(len(lines),lineno+3)
print('Error in %s' % (repr(self)))
print(' -> %s' % error)
print()
if start > 0:
print(' ...')
for i, line in enumerate(lines[start:end]):
if (i+start) == lineno:
print(' %03d %s' % (i+start, line))
else:
if len(line):
print(' %03d %s' % (i+start,line))
if end < len(lines):
print(' ...')
print()
@property
def hooks(self):
""" Shader hooks (place where snippets can be inserted) """
# We get hooks from the original code, not the hooked one
code = remove_comments(self._hooked)
return get_hooks(code)
@property
def uniforms(self):
""" Shader uniforms obtained from source code """
code = remove_comments(self.code)
gtypes = Shader._gtypes
return [ (n,gtypes[t]) for (n,t) in get_uniforms(code) ]
@property
def attributes(self):
""" Shader attributes obtained from source code """
code = remove_comments(self.code)
gtypes = Shader._gtypes
return [(n,gtypes[t]) for (n,t) in get_attributes(code)]
# ------------------------------------------------------ VertexShader class ---
class VertexShader(Shader):
""" Vertex shader class """
def __init__(self, code=None, version="120"):
Shader.__init__(self, gl.GL_VERTEX_SHADER, code, version)
@property
def code(self):
code = super(VertexShader, self).code
code = "#define _GLUMPY__VERTEX_SHADER__\n" + code
return code
def __repr__(self):
return "Vertex shader %d (%s)" % (self._id, self._source)
class FragmentShader(Shader):
""" Fragment shader class """
def __init__(self, code=None, version="120"):
Shader.__init__(self, gl.GL_FRAGMENT_SHADER, code, version)
@property
def code(self):
code = super(FragmentShader, self).code
code = "#define _GLUMPY__FRAGMENT_SHADER__\n" + code
return code
def __repr__(self):
return "Fragment shader %d (%s)" % (self._id, self._source)
class GeometryShader(Shader):
""" Geometry shader class.
:param str code: Shader code or a filename containing shader code
:param int vertices_out: Number of output vertices
:param gl.GLEnum input_type:
* GL_POINTS
* GL_LINES, GL_LINE_STRIP, GL_LINE_LIST
* GL_LINES_ADJACENCY, GL_LINE_STRIP_ADJACENCY
* GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN
* GL_TRIANGLES_ADJACENCY, GL_TRIANGLE_STRIP_ADJACENCY
:param gl.GLEnum output_type:
* GL_POINTS, GL_LINES, GL_LINE_STRIP
* GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN
"""
def __init__(self, code=None,
vertices_out=None, input_type=None, output_type=None, version="120"):
Shader.__init__(self, gl.GL_GEOMETRY_SHADER_EXT, code, version)
self._vertices_out = vertices_out
# GL_POINTS
# GL_LINES, GL_LINE_STRIP, GL_LINE_LIST
# GL_LINES_ADJACENCY, GL_LINE_STRIP_ADJACENCY
# GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN
# GL_TRIANGLES_ADJACENCY, GL_TRIANGLE_STRIP_ADJACENCY
self._input_type = input_type
# GL_POINTS, GL_LINES, GL_LINE_STRIP
# GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN
self._output_type = output_type
@property
def vertices_out(self):
return self._vertices_out
@vertices_out.setter
def vertices_out(self, value):
self._vertices_out = value
@property
def input_type(self):
""" """
return self._input_type
@input_type.setter
def input_type(self, value):
self._input_type = value
@property
def output_type(self):
return self._output_type
@output_type.setter
def output_type(self, value):
self._output_type = value
def __repr__(self):
return "Geometry shader %d (%s)" % (self._id, self._source)