-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathshader.py
154 lines (134 loc) · 6.37 KB
/
shader.py
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
import os.path, time, platform
from OpenGL import GL
from OpenGL.GL import shaders
SHADER_PATH = 'shaders/'
class ShaderLord:
# time in ms between checks for hot reload
hot_reload_check_interval = 2 * 1000
def __init__(self, app):
"AWAKENS THE SHADERLORD"
self.app = app
self.shaders = []
def new_shader(self, vert_source_file, frag_source_file):
self.last_check = 0
for shader in self.shaders:
if shader.vert_source_file == vert_source_file and shader.frag_source_file == frag_source_file:
#self.app.log('%s already uses same source' % shader)
return shader
s = Shader(self, vert_source_file, frag_source_file)
self.shaders.append(s)
return s
def check_hot_reload(self):
if self.app.get_elapsed_time() - self.last_check < self.hot_reload_check_interval:
return
self.last_check = self.app.get_elapsed_time()
for shader in self.shaders:
vert_shader_updated, frag_shader_updated = shader.has_updated()
if vert_shader_updated:
shader.recompile(GL.GL_VERTEX_SHADER)
if frag_shader_updated:
shader.recompile(GL.GL_FRAGMENT_SHADER)
def destroy(self):
for shader in self.shaders:
shader.destroy()
class Shader:
log_compile = False
"If True, log shader compilation"
# per-platform shader versions, declared here for easier CFG fiddling
glsl_version_windows = 130
glsl_version_unix = 130
glsl_version_macos = 150
glsl_version_es = 100
def __init__(self, shader_lord, vert_source_file, frag_source_file):
self.sl = shader_lord
# vertex shader
self.vert_source_file = vert_source_file
self.last_vert_change = time.time()
vert_source = self.get_shader_source(self.vert_source_file)
if self.log_compile:
self.sl.app.log('Compiling vertex shader %s...' % self.vert_source_file)
self.vert_shader = self.try_compile_shader(vert_source, GL.GL_VERTEX_SHADER, self.vert_source_file)
if self.log_compile and self.vert_shader:
self.sl.app.log('Compiled vertex shader %s in %.6f seconds' % (self.vert_source_file, time.time() - self.last_vert_change))
# fragment shader
self.frag_source_file = frag_source_file
self.last_frag_change = time.time()
frag_source = self.get_shader_source(self.frag_source_file)
if self.log_compile:
self.sl.app.log('Compiling fragment shader %s...' % self.frag_source_file)
self.frag_shader = self.try_compile_shader(frag_source, GL.GL_FRAGMENT_SHADER, self.frag_source_file)
if self.log_compile and self.frag_shader:
self.sl.app.log('Compiled fragment shader %s in %.6f seconds' % (self.frag_source_file, time.time() - self.last_frag_change))
# shader program
if self.vert_shader and self.frag_shader:
self.program = shaders.compileProgram(self.vert_shader, self.frag_shader)
def get_shader_source(self, source_file):
src = open(SHADER_PATH + source_file, 'rb').read()
# prepend shader version for different platforms
if self.sl.app.context_es:
shader_version = self.glsl_version_es
elif platform.system() == 'Windows':
shader_version = self.glsl_version_windows
elif platform.system() == 'Darwin':
shader_version = self.glsl_version_macos
else:
shader_version = self.glsl_version_unix
version_string = '#version %s\n' % shader_version
src = bytes(version_string, 'utf-8') + src
return src
def try_compile_shader(self, source, shader_type, source_filename):
"Catch and print shader compilation exceptions"
try:
shader = shaders.compileShader(source, shader_type)
except Exception as e:
self.sl.app.log('%s: ' % source_filename)
lines = e.args[0].split('\\n')
# salvage block after "shader compile failure" enclosed in b""
pre = lines.pop(0).split('b"')
for line in pre + lines[:-1]:
self.sl.app.log(' ' + line)
return
return shader
def has_updated(self):
vert_mod_time = os.path.getmtime(SHADER_PATH + self.vert_source_file)
frag_mod_time = os.path.getmtime(SHADER_PATH + self.frag_source_file)
# return two values: vert shader changed, frag shader changed
vert_changed = vert_mod_time > self.last_vert_change
frag_changed = frag_mod_time > self.last_frag_change
# store new last modified time if changed
if vert_changed:
self.last_vert_change = time.time()
if frag_changed:
self.last_frag_change = time.time()
return vert_changed, frag_changed
def recompile(self, shader_type):
file_to_reload = self.vert_source_file
if shader_type == GL.GL_FRAGMENT_SHADER:
file_to_reload = self.frag_source_file
new_shader_source = self.get_shader_source(file_to_reload)
try:
new_shader = shaders.compileShader(new_shader_source, shader_type)
# TODO: use try_compile_shader instead here, make sure exception passes thru ok
self.sl.app.log('ShaderLord: success reloading %s' % file_to_reload)
except:
self.sl.app.log('ShaderLord: failed reloading %s' % file_to_reload)
return
# recompile program with new shader
if shader_type == GL.GL_VERTEX_SHADER:
self.vert_shader = new_shader
else:
self.frag_shader = new_shader
self.program = shaders.compileProgram(self.vert_shader, self.frag_shader)
def get_uniform_location(self, uniform_name):
return GL.glGetUniformLocation(self.program, uniform_name)
def get_attrib_location(self, attrib_name):
return GL.glGetAttribLocation(self.program, attrib_name)
def destroy(self):
GL.glDeleteProgram(self.program)
class ShaderUniform:
# MAYBE-TODO: class for remembering uniform name, type, index.
# a Shader keeps a list of these, Renderables tell their Shader to set
# them. set methods use correct type and "try" to avoid crashes on
# hot-reload when a uniform is commented out of live GLSL.
# (try same for attributes? more data)
pass