pyrender-maintained 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,283 @@
1
+ """OpenGL shader program wrapper.
2
+ """
3
+ import numpy as np
4
+ import os
5
+ import re
6
+
7
+ import OpenGL
8
+ from OpenGL.GL import *
9
+ from OpenGL.GL import shaders as gl_shader_utils
10
+
11
+
12
+ class ShaderProgramCache(object):
13
+ """A cache for shader programs.
14
+ """
15
+
16
+ def __init__(self, shader_dir=None):
17
+ self._program_cache = {}
18
+ self.shader_dir = shader_dir
19
+ if self.shader_dir is None:
20
+ base_dir, _ = os.path.split(os.path.realpath(__file__))
21
+ self.shader_dir = os.path.join(base_dir, 'shaders')
22
+
23
+ def get_program(self, vertex_shader, fragment_shader,
24
+ geometry_shader=None, defines=None):
25
+ """Get a program via a list of shader files to include in the program.
26
+
27
+ Parameters
28
+ ----------
29
+ vertex_shader : str
30
+ The vertex shader filename.
31
+ fragment_shader : str
32
+ The fragment shader filename.
33
+ geometry_shader : str
34
+ The geometry shader filename.
35
+ defines : dict
36
+ Defines and their values for the shader.
37
+
38
+ Returns
39
+ -------
40
+ program : :class:`.ShaderProgram`
41
+ The program.
42
+ """
43
+ shader_names = []
44
+ if defines is None:
45
+ defines = {}
46
+ shader_filenames = [
47
+ x for x in [vertex_shader, fragment_shader, geometry_shader]
48
+ if x is not None
49
+ ]
50
+ for fn in shader_filenames:
51
+ if fn is None:
52
+ continue
53
+ _, name = os.path.split(fn)
54
+ shader_names.append(name)
55
+ cid = OpenGL.contextdata.getContext()
56
+ key = tuple([cid] + sorted(
57
+ [(s,1) for s in shader_names] + [(d, defines[d]) for d in defines]
58
+ ))
59
+
60
+ if key not in self._program_cache:
61
+ shader_filenames = [
62
+ os.path.join(self.shader_dir, fn) for fn in shader_filenames
63
+ ]
64
+ if len(shader_filenames) == 2:
65
+ shader_filenames.append(None)
66
+ vs, fs, gs = shader_filenames
67
+ self._program_cache[key] = ShaderProgram(
68
+ vertex_shader=vs, fragment_shader=fs,
69
+ geometry_shader=gs, defines=defines
70
+ )
71
+ return self._program_cache[key]
72
+
73
+ def clear(self):
74
+ for key in self._program_cache:
75
+ self._program_cache[key].delete()
76
+ self._program_cache = {}
77
+
78
+
79
+ class ShaderProgram(object):
80
+ """A thin wrapper about OpenGL shader programs that supports easy creation,
81
+ binding, and uniform-setting.
82
+
83
+ Parameters
84
+ ----------
85
+ vertex_shader : str
86
+ The vertex shader filename.
87
+ fragment_shader : str
88
+ The fragment shader filename.
89
+ geometry_shader : str
90
+ The geometry shader filename.
91
+ defines : dict
92
+ Defines and their values for the shader.
93
+ """
94
+
95
+ def __init__(self, vertex_shader, fragment_shader,
96
+ geometry_shader=None, defines=None):
97
+
98
+ self.vertex_shader = vertex_shader
99
+ self.fragment_shader = fragment_shader
100
+ self.geometry_shader = geometry_shader
101
+
102
+ self.defines = defines
103
+ if self.defines is None:
104
+ self.defines = {}
105
+
106
+ self._program_id = None
107
+ self._vao_id = None # PYOPENGL BUG
108
+
109
+ # DEBUG
110
+ # self._unif_map = {}
111
+
112
+ def _add_to_context(self):
113
+ if self._program_id is not None:
114
+ raise ValueError('Shader program already in context')
115
+ shader_ids = []
116
+
117
+ # Load vert shader
118
+ shader_ids.append(gl_shader_utils.compileShader(
119
+ self._load(self.vertex_shader), GL_VERTEX_SHADER)
120
+ )
121
+ # Load frag shader
122
+ shader_ids.append(gl_shader_utils.compileShader(
123
+ self._load(self.fragment_shader), GL_FRAGMENT_SHADER)
124
+ )
125
+ # Load geometry shader
126
+ if self.geometry_shader is not None:
127
+ shader_ids.append(gl_shader_utils.compileShader(
128
+ self._load(self.geometry_shader), GL_GEOMETRY_SHADER)
129
+ )
130
+
131
+ # Bind empty VAO PYOPENGL BUG
132
+ if self._vao_id is None:
133
+ self._vao_id = glGenVertexArrays(1)
134
+ glBindVertexArray(self._vao_id)
135
+
136
+ # Compile program
137
+ self._program_id = gl_shader_utils.compileProgram(*shader_ids)
138
+
139
+ # Unbind empty VAO PYOPENGL BUG
140
+ glBindVertexArray(0)
141
+
142
+ def _in_context(self):
143
+ return self._program_id is not None
144
+
145
+ def _remove_from_context(self):
146
+ if self._program_id is not None:
147
+ glDeleteProgram(self._program_id)
148
+ glDeleteVertexArrays(1, [self._vao_id])
149
+ self._program_id = None
150
+ self._vao_id = None
151
+
152
+ def _load(self, shader_filename):
153
+ path, _ = os.path.split(shader_filename)
154
+
155
+ with open(shader_filename) as f:
156
+ text = f.read()
157
+
158
+ def ifdef(matchobj):
159
+ if matchobj.group(1) in self.defines:
160
+ return '#if 1'
161
+ else:
162
+ return '#if 0'
163
+
164
+ def ifndef(matchobj):
165
+ if matchobj.group(1) in self.defines:
166
+ return '#if 0'
167
+ else:
168
+ return '#if 1'
169
+
170
+ ifdef_regex = re.compile(
171
+ '#ifdef\\s+([a-zA-Z_][a-zA-Z_0-9]*)\\s*$', re.MULTILINE
172
+ )
173
+ ifndef_regex = re.compile(
174
+ '#ifndef\\s+([a-zA-Z_][a-zA-Z_0-9]*)\\s*$', re.MULTILINE
175
+ )
176
+ text = re.sub(ifdef_regex, ifdef, text)
177
+ text = re.sub(ifndef_regex, ifndef, text)
178
+
179
+ for define in self.defines:
180
+ value = str(self.defines[define])
181
+ text = text.replace(define, value)
182
+
183
+ return text
184
+
185
+ def _bind(self):
186
+ """Bind this shader program to the current OpenGL context.
187
+ """
188
+ if self._program_id is None:
189
+ raise ValueError('Cannot bind program that is not in context')
190
+ # glBindVertexArray(self._vao_id)
191
+ glUseProgram(self._program_id)
192
+
193
+ def _unbind(self):
194
+ """Unbind this shader program from the current OpenGL context.
195
+ """
196
+ glUseProgram(0)
197
+
198
+ def delete(self):
199
+ """Delete this shader program from the current OpenGL context.
200
+ """
201
+ self._remove_from_context()
202
+
203
+ def set_uniform(self, name, value, unsigned=False):
204
+ """Set a uniform value in the current shader program.
205
+
206
+ Parameters
207
+ ----------
208
+ name : str
209
+ Name of the uniform to set.
210
+ value : int, float, or ndarray
211
+ Value to set the uniform to.
212
+ unsigned : bool
213
+ If True, ints will be treated as unsigned values.
214
+ """
215
+ try:
216
+ # DEBUG
217
+ # self._unif_map[name] = 1, (1,)
218
+ loc = glGetUniformLocation(self._program_id, name)
219
+
220
+ if loc == -1:
221
+ raise ValueError('Invalid shader variable: {}'.format(name))
222
+
223
+ if isinstance(value, np.ndarray):
224
+ # DEBUG
225
+ # self._unif_map[name] = value.size, value.shape
226
+ if value.ndim == 1:
227
+ if (np.issubdtype(value.dtype, np.unsignedinteger) or
228
+ unsigned):
229
+ dtype = 'u'
230
+ value = value.astype(np.uint32)
231
+ elif np.issubdtype(value.dtype, np.integer):
232
+ dtype = 'i'
233
+ value = value.astype(np.int32)
234
+ else:
235
+ dtype = 'f'
236
+ value = value.astype(np.float32)
237
+ self._FUNC_MAP[(value.shape[0], dtype)](loc, 1, value)
238
+ else:
239
+ self._FUNC_MAP[(value.shape[0], value.shape[1])](
240
+ loc, 1, GL_TRUE, value
241
+ )
242
+
243
+ # Call correct uniform function
244
+ elif isinstance(value, float):
245
+ glUniform1f(loc, value)
246
+ elif isinstance(value, int):
247
+ if unsigned:
248
+ glUniform1ui(loc, value)
249
+ else:
250
+ glUniform1i(loc, value)
251
+ elif isinstance(value, bool):
252
+ if unsigned:
253
+ glUniform1ui(loc, int(value))
254
+ else:
255
+ glUniform1i(loc, int(value))
256
+ else:
257
+ raise ValueError('Invalid data type')
258
+ except Exception:
259
+ pass
260
+
261
+ _FUNC_MAP = {
262
+ (1,'u'): glUniform1uiv,
263
+ (2,'u'): glUniform2uiv,
264
+ (3,'u'): glUniform3uiv,
265
+ (4,'u'): glUniform4uiv,
266
+ (1,'i'): glUniform1iv,
267
+ (2,'i'): glUniform2iv,
268
+ (3,'i'): glUniform3iv,
269
+ (4,'i'): glUniform4iv,
270
+ (1,'f'): glUniform1fv,
271
+ (2,'f'): glUniform2fv,
272
+ (3,'f'): glUniform3fv,
273
+ (4,'f'): glUniform4fv,
274
+ (2,2): glUniformMatrix2fv,
275
+ (2,3): glUniformMatrix2x3fv,
276
+ (2,4): glUniformMatrix2x4fv,
277
+ (3,2): glUniformMatrix3x2fv,
278
+ (3,3): glUniformMatrix3fv,
279
+ (3,4): glUniformMatrix3x4fv,
280
+ (4,2): glUniformMatrix4x2fv,
281
+ (4,3): glUniformMatrix4x3fv,
282
+ (4,4): glUniformMatrix4fv,
283
+ }
pyrender/texture.py ADDED
@@ -0,0 +1,259 @@
1
+ """Textures, conforming to the glTF 2.0 standards as specified in
2
+ https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-texture
3
+
4
+ Author: Matthew Matl
5
+ """
6
+ import numpy as np
7
+
8
+ from OpenGL.GL import *
9
+
10
+ from .utils import format_texture_source
11
+ from .sampler import Sampler
12
+
13
+
14
+ class Texture(object):
15
+ """A texture and its sampler.
16
+
17
+ Parameters
18
+ ----------
19
+ name : str, optional
20
+ The user-defined name of this object.
21
+ sampler : :class:`Sampler`
22
+ The sampler used by this texture.
23
+ source : (h,w,c) uint8 or (h,w,c) float or :class:`PIL.Image.Image`
24
+ The image used by this texture. If None, the texture is created
25
+ empty and width and height must be specified.
26
+ source_channels : str
27
+ Either `D`, `R`, `RG`, `GB`, `RGB`, or `RGBA`. Indicates the
28
+ channels to extract from `source`. Any missing channels will be filled
29
+ with `1.0`.
30
+ width : int, optional
31
+ For empty textures, the width of the texture buffer.
32
+ height : int, optional
33
+ For empty textures, the height of the texture buffer.
34
+ tex_type : int
35
+ Either GL_TEXTURE_2D or GL_TEXTURE_CUBE.
36
+ data_format : int
37
+ For now, just GL_FLOAT.
38
+ """
39
+
40
+ def __init__(self,
41
+ name=None,
42
+ sampler=None,
43
+ source=None,
44
+ source_channels=None,
45
+ width=None,
46
+ height=None,
47
+ tex_type=GL_TEXTURE_2D,
48
+ data_format=GL_UNSIGNED_BYTE):
49
+ self.source_channels = source_channels
50
+ self.name = name
51
+ self.sampler = sampler
52
+ self.source = source
53
+ self.width = width
54
+ self.height = height
55
+ self.tex_type = tex_type
56
+ self.data_format = data_format
57
+
58
+ self._texid = None
59
+ self._is_transparent = False
60
+
61
+ @property
62
+ def name(self):
63
+ """str : The user-defined name of this object.
64
+ """
65
+ return self._name
66
+
67
+ @name.setter
68
+ def name(self, value):
69
+ if value is not None:
70
+ value = str(value)
71
+ self._name = value
72
+
73
+ @property
74
+ def sampler(self):
75
+ """:class:`Sampler` : The sampler used by this texture.
76
+ """
77
+ return self._sampler
78
+
79
+ @sampler.setter
80
+ def sampler(self, value):
81
+ if value is None:
82
+ value = Sampler()
83
+ self._sampler = value
84
+
85
+ @property
86
+ def source(self):
87
+ """(h,w,c) uint8 or float or :class:`PIL.Image.Image` : The image
88
+ used in this texture.
89
+ """
90
+ return self._source
91
+
92
+ @source.setter
93
+ def source(self, value):
94
+ if value is None:
95
+ self._source = None
96
+ else:
97
+ self._source = format_texture_source(value, self.source_channels)
98
+ self._is_transparent = False
99
+
100
+ @property
101
+ def source_channels(self):
102
+ """str : The channels that were extracted from the original source.
103
+ """
104
+ return self._source_channels
105
+
106
+ @source_channels.setter
107
+ def source_channels(self, value):
108
+ self._source_channels = value
109
+
110
+ @property
111
+ def width(self):
112
+ """int : The width of the texture buffer.
113
+ """
114
+ return self._width
115
+
116
+ @width.setter
117
+ def width(self, value):
118
+ self._width = value
119
+
120
+ @property
121
+ def height(self):
122
+ """int : The height of the texture buffer.
123
+ """
124
+ return self._height
125
+
126
+ @height.setter
127
+ def height(self, value):
128
+ self._height = value
129
+
130
+ @property
131
+ def tex_type(self):
132
+ """int : The type of the texture.
133
+ """
134
+ return self._tex_type
135
+
136
+ @tex_type.setter
137
+ def tex_type(self, value):
138
+ self._tex_type = value
139
+
140
+ @property
141
+ def data_format(self):
142
+ """int : The format of the texture data.
143
+ """
144
+ return self._data_format
145
+
146
+ @data_format.setter
147
+ def data_format(self, value):
148
+ self._data_format = value
149
+
150
+ def is_transparent(self, cutoff=1.0):
151
+ """bool : If True, the texture is partially transparent.
152
+ """
153
+ if self._is_transparent is None:
154
+ self._is_transparent = False
155
+ if self.source_channels == 'RGBA' and self.source is not None:
156
+ if np.any(self.source[:,:,3] < cutoff):
157
+ self._is_transparent = True
158
+ return self._is_transparent
159
+
160
+ def delete(self):
161
+ """Remove this texture from the OpenGL context.
162
+ """
163
+ self._unbind()
164
+ self._remove_from_context()
165
+
166
+ ##################
167
+ # OpenGL code
168
+ ##################
169
+ def _add_to_context(self):
170
+ if self._texid is not None:
171
+ raise ValueError('Texture already loaded into OpenGL context')
172
+
173
+ fmt = GL_DEPTH_COMPONENT
174
+ if self.source_channels == 'R':
175
+ fmt = GL_RED
176
+ elif self.source_channels == 'RG' or self.source_channels == 'GB':
177
+ fmt = GL_RG
178
+ elif self.source_channels == 'RGB':
179
+ fmt = GL_RGB
180
+ elif self.source_channels == 'RGBA':
181
+ fmt = GL_RGBA
182
+
183
+ # Generate the OpenGL texture
184
+ self._texid = glGenTextures(1)
185
+ glBindTexture(self.tex_type, self._texid)
186
+
187
+ # Flip data for OpenGL buffer
188
+ data = None
189
+ width = self.width
190
+ height = self.height
191
+ if self.source is not None:
192
+ data = np.ascontiguousarray(np.flip(self.source, axis=0).flatten())
193
+ width = self.source.shape[1]
194
+ height = self.source.shape[0]
195
+
196
+ # Bind texture and generate mipmaps
197
+ glTexImage2D(
198
+ self.tex_type, 0, fmt, width, height, 0, fmt,
199
+ self.data_format, data
200
+ )
201
+ if self.source is not None:
202
+ glGenerateMipmap(self.tex_type)
203
+
204
+ if self.sampler.magFilter is not None:
205
+ glTexParameteri(
206
+ self.tex_type, GL_TEXTURE_MAG_FILTER, self.sampler.magFilter
207
+ )
208
+ else:
209
+ if self.source is not None:
210
+ glTexParameteri(self.tex_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
211
+ else:
212
+ glTexParameteri(self.tex_type, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
213
+ if self.sampler.minFilter is not None:
214
+ glTexParameteri(
215
+ self.tex_type, GL_TEXTURE_MIN_FILTER, self.sampler.minFilter
216
+ )
217
+ else:
218
+ if self.source is not None:
219
+ glTexParameteri(self.tex_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
220
+ else:
221
+ glTexParameteri(self.tex_type, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
222
+
223
+ glTexParameteri(self.tex_type, GL_TEXTURE_WRAP_S, self.sampler.wrapS)
224
+ glTexParameteri(self.tex_type, GL_TEXTURE_WRAP_T, self.sampler.wrapT)
225
+ border_color = 255 * np.ones(4).astype(np.uint8)
226
+ if self.data_format == GL_FLOAT:
227
+ border_color = np.ones(4).astype(np.float32)
228
+ glTexParameterfv(
229
+ self.tex_type, GL_TEXTURE_BORDER_COLOR,
230
+ border_color
231
+ )
232
+
233
+ # Unbind texture
234
+ glBindTexture(self.tex_type, 0)
235
+
236
+ def _remove_from_context(self):
237
+ if self._texid is not None:
238
+ # TODO OPENGL BUG?
239
+ # glDeleteTextures(1, [self._texid])
240
+ glDeleteTextures([self._texid])
241
+ self._texid = None
242
+
243
+ def _in_context(self):
244
+ return self._texid is not None
245
+
246
+ def _bind(self):
247
+ # TODO HANDLE INDEXING INTO OTHER UV's
248
+ glBindTexture(self.tex_type, self._texid)
249
+
250
+ def _unbind(self):
251
+ glBindTexture(self.tex_type, 0)
252
+
253
+ def _bind_as_depth_attachment(self):
254
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
255
+ self.tex_type, self._texid, 0)
256
+
257
+ def _bind_as_color_attachment(self):
258
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
259
+ self.tex_type, self._texid, 0)