diff --git a/DR.py b/DR.py index 4c6731c..12fca47 100644 --- a/DR.py +++ b/DR.py @@ -38,14 +38,17 @@ if __name__ == '__main__': print(sys.path) print(hi) - DEBUGGING = False + DEBUGGING = True from Difficult_Rocket.api.Exp import * from Difficult_Rocket.crash import crash try: from Difficult_Rocket import main + game = main.Game() - from libs.pyglet.gl import glClearColor - glClearColor(0, 0, 0, 0) + + # from libs.pyglet.gl import glClearColor + # glClearColor(0, 0, 0, 0) + cprofile = False if cprofile: cProfile.run('game.start()', sort='calls') diff --git a/Difficult_Rocket/client/__init__.py b/Difficult_Rocket/client/__init__.py index b0bb204..bbef7ed 100644 --- a/Difficult_Rocket/client/__init__.py +++ b/Difficult_Rocket/client/__init__.py @@ -173,7 +173,7 @@ class ClientWindow(Window): def start_game(self) -> None: self.run_input = True - self.read_input() + # self.read_input() pyglet.app.run() @new_thread('window read_input', daemon=True) @@ -193,11 +193,11 @@ class ClientWindow(Window): @new_thread('window save_info') def save_info(self): - print('save_info start') - config_file = tools.load_file('./config/config.toml') + self.logger.info('save_info start') + config_file = tools.load_file('./configs/main.toml') config_file['window']['width'] = self.width config_file['window']['height'] = self.height - toml.dump(config_file, open('./config/config.toml', 'w')) + toml.dump(config_file, open('./configs/main.toml', 'w')) """ draws and some event @@ -211,9 +211,10 @@ class ClientWindow(Window): def FPS_update(self, tick: Decimal): now_FPS = pyglet.clock.get_fps() self.fps_log.update_tick(tick) - self.fps_label.text = f'FPS: {self.fps_log.fps: >5.1f}({self.fps_log.middle_fps: >5.1f})[{now_FPS}] \n{self.fps_log.max_fps: >7.1f} {self.fps_log.min_fps:>5.1f}' + self.fps_label.text = f'FPS: {self.fps_log.fps: >5.1f}({self.fps_log.middle_fps: >5.1f})[{now_FPS}] {self.fps_log.max_fps: >7.1f} {self.fps_log.min_fps:>5.1f}' def on_draw(self, *dt): + # self.logger.debug('on_draw call dt: {}'.format(dt)) self.clear() self.draw_batch() diff --git a/Difficult_Rocket/main.py b/Difficult_Rocket/main.py index 70a878b..306b54b 100644 --- a/Difficult_Rocket/main.py +++ b/Difficult_Rocket/main.py @@ -70,7 +70,7 @@ class Game: # @new_thread('main') def _start(self): self.server.run() - threaded = True + threaded = False if threaded: try: game_process = multiprocessing.Process(target=self.client.start(), name='pyglet app') diff --git a/configs/main.toml b/configs/main.toml index c1dac4e..5f053d0 100644 --- a/configs/main.toml +++ b/configs/main.toml @@ -2,9 +2,9 @@ fps = 60 version = "0.6.1" language = "zh-CN" -date_fmt = '%Y-%m-%d %H-%M-%S' +date_fmt = "%Y-%m-%d %H-%M-%S" write_py_v = "3.8.10" -fonts_folder = 'libs/fonts' +fonts_folder = "libs/fonts" [window] style = "None" @@ -18,4 +18,3 @@ full_screen = false [window.default] width = 1024 height = 768 - diff --git a/libs/pyglet/font/base.py b/libs/pyglet/font/base.py index 36382d7..1af48fb 100644 --- a/libs/pyglet/font/base.py +++ b/libs/pyglet/font/base.py @@ -452,3 +452,6 @@ class Font: glyphs = glyph_buffer return glyphs + + def __repr__(self): + return f"{self.__class__.__name__}('{self.name}')" diff --git a/libs/pyglet/graphics/__init__.py b/libs/pyglet/graphics/__init__.py index b25df53..aca356d 100644 --- a/libs/pyglet/graphics/__init__.py +++ b/libs/pyglet/graphics/__init__.py @@ -170,7 +170,7 @@ from pyglet.graphics.vertexbuffer import BufferObject _debug_graphics_batch = pyglet.options['debug_graphics_batch'] -def draw(size, mode, **kwargs): +def draw(size, mode, **data): """Draw a primitive immediately. :Parameters: @@ -194,7 +194,7 @@ def draw(size, mode, **kwargs): program.use() buffers = [] - for name, (fmt, array) in kwargs.items(): + for name, (fmt, array) in data.items(): location = program.attributes[name]['location'] count = program.attributes[name]['count'] gl_type = vertexdomain._gl_types[fmt[0]] @@ -340,6 +340,8 @@ class Batch: self._draw_list = [] self._draw_list_dirty = False + self._context = pyglet.gl.current_context + def invalidate(self): """Force the batch to update the draw list. @@ -554,7 +556,7 @@ class Group: """Group of common OpenGL state. Before a VertexList is rendered, its Group's OpenGL state is set. - This can including binding textures, or setting any other parameters. + This includes binding textures, shaders, or setting any other parameters. """ def __init__(self, order=0, parent=None): """Create a Group. @@ -565,6 +567,8 @@ class Group: `parent` : `~pyglet.graphics.Group` Group to contain this Group; its state will be set before this Group's state. + + :Ivariables: `visible` : bool Determines whether this Group is visible in any of the Batches it is assigned to. If False, objects in this Group will not diff --git a/libs/pyglet/graphics/shader.py b/libs/pyglet/graphics/shader.py index 304648e..69517f5 100644 --- a/libs/pyglet/graphics/shader.py +++ b/libs/pyglet/graphics/shader.py @@ -28,65 +28,63 @@ _uniform_getters = { } _uniform_setters = { - # uniform type: (gl_type, setter, length, count) - GL_BOOL: (GLint, glUniform1iv, 1, 1), - GL_BOOL_VEC2: (GLint, glUniform1iv, 2, 1), - GL_BOOL_VEC3: (GLint, glUniform1iv, 3, 1), - GL_BOOL_VEC4: (GLint, glUniform1iv, 4, 1), + # uniform: gl_type, legacy_setter, setter, length, count + GL_BOOL: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1), + GL_BOOL_VEC2: (GLint, glUniform1iv, glProgramUniform1iv, 2, 1), + GL_BOOL_VEC3: (GLint, glUniform1iv, glProgramUniform1iv, 3, 1), + GL_BOOL_VEC4: (GLint, glUniform1iv, glProgramUniform1iv, 4, 1), - GL_INT: (GLint, glUniform1iv, 1, 1), - GL_INT_VEC2: (GLint, glUniform2iv, 2, 1), - GL_INT_VEC3: (GLint, glUniform3iv, 3, 1), - GL_INT_VEC4: (GLint, glUniform4iv, 4, 1), + GL_INT: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1), + GL_INT_VEC2: (GLint, glUniform2iv, glProgramUniform2iv, 2, 1), + GL_INT_VEC3: (GLint, glUniform3iv, glProgramUniform3iv, 3, 1), + GL_INT_VEC4: (GLint, glUniform4iv, glProgramUniform4iv, 4, 1), - GL_FLOAT: (GLfloat, glUniform1fv, 1, 1), - GL_FLOAT_VEC2: (GLfloat, glUniform2fv, 2, 1), - GL_FLOAT_VEC3: (GLfloat, glUniform3fv, 3, 1), - GL_FLOAT_VEC4: (GLfloat, glUniform4fv, 4, 1), + GL_FLOAT: (GLfloat, glUniform1fv, glProgramUniform1fv, 1, 1), + GL_FLOAT_VEC2: (GLfloat, glUniform2fv, glProgramUniform2fv, 2, 1), + GL_FLOAT_VEC3: (GLfloat, glUniform3fv, glProgramUniform3fv, 3, 1), + GL_FLOAT_VEC4: (GLfloat, glUniform4fv, glProgramUniform4fv, 4, 1), - GL_SAMPLER_1D: (GLint, glUniform1iv, 1, 1), - GL_SAMPLER_2D: (GLint, glUniform1iv, 1, 1), - GL_SAMPLER_2D_ARRAY: (GLint, glUniform1iv, 1, 1), + GL_SAMPLER_1D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1), + GL_SAMPLER_2D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1), + GL_SAMPLER_2D_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1), - GL_SAMPLER_3D: (GLint, glUniform1iv, 1, 1), + GL_SAMPLER_3D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1), - GL_FLOAT_MAT2: (GLfloat, glUniformMatrix2fv, 4, 1), - GL_FLOAT_MAT3: (GLfloat, glUniformMatrix3fv, 6, 1), - GL_FLOAT_MAT4: (GLfloat, glUniformMatrix4fv, 16, 1), + GL_FLOAT_MAT2: (GLfloat, glUniformMatrix2fv, glProgramUniformMatrix2fv, 4, 1), + GL_FLOAT_MAT3: (GLfloat, glUniformMatrix3fv, glProgramUniformMatrix3fv, 6, 1), + GL_FLOAT_MAT4: (GLfloat, glUniformMatrix4fv, glProgramUniformMatrix4fv, 16, 1), # TODO: test/implement these: - # GL_FLOAT_MAT2x3: glUniformMatrix2x3fv, - # GL_FLOAT_MAT2x4: glUniformMatrix2x4fv, - # - # GL_FLOAT_MAT3x2: glUniformMatrix3x2fv, - # GL_FLOAT_MAT3x4: glUniformMatrix3x4fv, - # - # GL_FLOAT_MAT4x2: glUniformMatrix4x2fv, - # GL_FLOAT_MAT4x3: glUniformMatrix4x3fv, + # GL_FLOAT_MAT2x3: glUniformMatrix2x3fv, glProgramUniformMatrix2x3fv, + # GL_FLOAT_MAT2x4: glUniformMatrix2x4fv, glProgramUniformMatrix2x4fv, + # GL_FLOAT_MAT3x2: glUniformMatrix3x2fv, glProgramUniformMatrix3x2fv, + # GL_FLOAT_MAT3x4: glUniformMatrix3x4fv, glProgramUniformMatrix3x4fv, + # GL_FLOAT_MAT4x2: glUniformMatrix4x2fv, glProgramUniformMatrix4x2fv, + # GL_FLOAT_MAT4x3: glUniformMatrix4x3fv, glProgramUniformMatrix4x3fv, } _attribute_types = { - GL_BOOL: (1, '?'), + GL_BOOL: (1, '?'), GL_BOOL_VEC2: (2, '?'), GL_BOOL_VEC3: (3, '?'), GL_BOOL_VEC4: (4, '?'), - GL_INT: (1, 'i'), + GL_INT: (1, 'i'), GL_INT_VEC2: (2, 'i'), GL_INT_VEC3: (3, 'i'), GL_INT_VEC4: (4, 'i'), - GL_UNSIGNED_INT: (1, 'I'), + GL_UNSIGNED_INT: (1, 'I'), GL_UNSIGNED_INT_VEC2: (2, 'I'), GL_UNSIGNED_INT_VEC3: (3, 'I'), GL_UNSIGNED_INT_VEC4: (4, 'I'), - GL_FLOAT: (1, 'f'), + GL_FLOAT: (1, 'f'), GL_FLOAT_VEC2: (2, 'f'), GL_FLOAT_VEC3: (3, 'f'), GL_FLOAT_VEC4: (4, 'f'), - GL_DOUBLE: (1, 'd'), + GL_DOUBLE: (1, 'd'), GL_DOUBLE_VEC2: (2, 'd'), GL_DOUBLE_VEC3: (3, 'd'), GL_DOUBLE_VEC4: (4, 'd'), @@ -96,11 +94,16 @@ _attribute_types = { class _Uniform: __slots__ = 'program', 'name', 'type', 'location', 'length', 'count', 'get', 'set' - def __init__(self, program, name, uniform_type, gl_type, location, length, count, gl_setter, gl_getter): + def __init__(self, program, name, uniform_type, location, dsa): self.program = program self.name = name self.type = uniform_type self.location = location + + gl_type, gl_setter_legacy, gl_setter_dsa, length, count = _uniform_setters[uniform_type] + gl_setter = gl_setter_dsa if dsa else gl_setter_legacy + gl_getter = _uniform_getters[gl_type] + self.length = length self.count = count @@ -112,45 +115,66 @@ class _Uniform: ptr = cast(c_array, POINTER(gl_type)) self.get = self._create_getter_func(program, location, gl_getter, c_array, length) - self.set = self._create_setter_func(location, gl_setter, c_array, length, count, ptr, is_matrix) + self.set = self._create_setter_func(program, location, gl_setter, c_array, length, count, ptr, is_matrix, dsa) @staticmethod - def _create_getter_func(program_id, location, gl_getter, c_array, length): + def _create_getter_func(program, location, gl_getter, c_array, length): """Factory function for creating simplified Uniform getters""" if length == 1: def getter_func(): - gl_getter(program_id, location, c_array) + gl_getter(program, location, c_array) return c_array[0] else: def getter_func(): - gl_getter(program_id, location, c_array) + gl_getter(program, location, c_array) return c_array[:] return getter_func @staticmethod - def _create_setter_func(location, gl_setter, c_array, length, count, ptr, is_matrix): + def _create_setter_func(program, location, gl_setter, c_array, length, count, ptr, is_matrix, dsa): """Factory function for creating simplified Uniform setters""" + if dsa: # Bindless updates: - if is_matrix: - def setter_func(value): - c_array[:] = value - gl_setter(location, count, GL_FALSE, ptr) + if is_matrix: + def setter_func(value): + c_array[:] = value + gl_setter(program, location, count, GL_FALSE, ptr) + elif length == 1 and count == 1: + def setter_func(value): + c_array[0] = value + gl_setter(program, location, count, ptr) + elif length > 1 and count == 1: + def setter_func(values): + c_array[:] = values + gl_setter(program, location, count, ptr) + else: + raise NotImplementedError("Uniform type not yet supported.") - elif length == 1 and count == 1: - def setter_func(value): - c_array[0] = value - gl_setter(location, count, ptr) - elif length > 1 and count == 1: - def setter_func(values): - c_array[:] = values - gl_setter(location, count, ptr) + return setter_func else: - raise NotImplementedError("Uniform type not yet supported.") - return setter_func + if is_matrix: + def setter_func(value): + glUseProgram(program) + c_array[:] = value + gl_setter(location, count, GL_FALSE, ptr) + elif length == 1 and count == 1: + def setter_func(value): + glUseProgram(program) + c_array[0] = value + gl_setter(location, count, ptr) + elif length > 1 and count == 1: + def setter_func(values): + glUseProgram(program) + c_array[:] = values + gl_setter(location, count, ptr) + else: + raise NotImplementedError("Uniform type not yet supported.") + + return setter_func def __repr__(self): return f"Uniform('{self.name}', location={self.location}, length={self.length}, count={self.count})" @@ -167,9 +191,9 @@ class Shader: :Parameters: `source_string` : str - A string containing the Shader code. + A string containing the Shader source code. `shader_type` : str - The Shader type, such as "vertex" or "fragment". + The Shader type, such as "vertex", "fragment", "geometry", etc. """ self._id = None @@ -228,7 +252,7 @@ class Shader: class ShaderProgram: """OpenGL Shader Program""" - __slots__ = '_id', '_context', '_active', '_attributes', '_uniforms', '_uniform_blocks', '__weakref__' + __slots__ = '_id', '_context', '_attributes', '_uniforms', '_uniform_blocks', '__weakref__', '_dsa' def __init__(self, *shaders): """Create an OpenGL ShaderProgram, from multiple Shaders. @@ -242,15 +266,13 @@ class ShaderProgram: assert shaders, "At least one Shader object is required." self._id = self._link_program(shaders) self._context = pyglet.gl.current_context - self._active = False - self._attributes = {} - self._uniforms = {} - self._uniform_blocks = {} + # Query if Direct State Access is available: + self._dsa = gl_info.have_version(4, 1) or gl_info.have_extension("GL_ARB_separate_shader_objects") - self._introspect_attributes() - self._introspect_uniforms() - self._introspect_uniform_blocks() + self._attributes = self._introspect_attributes() + self._uniforms = self._introspect_uniforms() + self._uniform_blocks = self._introspect_uniform_blocks() if _debug_gl_shaders: print(self._get_program_log()) @@ -259,10 +281,6 @@ class ShaderProgram: def id(self): return self._id - @property - def is_active(self): - return self._active - @property def attributes(self): return self._attributes @@ -275,10 +293,6 @@ class ShaderProgram: def uniform_blocks(self): return self._uniform_blocks - @property - def formats(self): - return tuple(f"{atr.name}{atr.count}{atr.format}" for atr in self._attributes.values()) - def _get_program_log(self): result = c_int(0) glGetProgramiv(self._id, GL_INFO_LOG_LENGTH, byref(result)) @@ -302,11 +316,9 @@ class ShaderProgram: def use(self): glUseProgram(self._id) - self._active = True def stop(self): glUseProgram(0) - self._active = False __enter__ = use bind = use @@ -314,7 +326,6 @@ class ShaderProgram: def __exit__(self, *_): glUseProgram(0) - self._active = False def __del__(self): try: @@ -325,13 +336,12 @@ class ShaderProgram: pass def __setitem__(self, key, value): - if not self._active: - raise Exception("Shader Program is not active.") - try: uniform = self._uniforms[key] except KeyError: - raise Exception("Uniform with the name `{0}` was not found.".format(key)) + raise Exception(f"A Uniform with the name `{key}` was not found.\n" + f"The spelling may be incorrect, or if not in use it " + f"may have been optimized out by the OpenGL driver.") try: uniform.set(value) @@ -363,68 +373,62 @@ class ShaderProgram: loc = glGetAttribLocation(program, create_string_buffer(a_name.encode('utf-8'))) count, fmt = _attribute_types[a_type] attributes[a_name] = dict(type=a_type, size=a_size, location=loc, count=count, format=fmt) - self._attributes = attributes if _debug_gl_shaders: for attribute in attributes.values(): print(f"Found attribute: {attribute}") + return attributes + def _introspect_uniforms(self): - prg_id = self._id + program = self._id + uniforms = {} for index in range(self._get_number(GL_ACTIVE_UNIFORMS)): u_name, u_type, u_size = self._query_uniform(index) - loc = glGetUniformLocation(prg_id, create_string_buffer(u_name.encode('utf-8'))) - + loc = glGetUniformLocation(program, create_string_buffer(u_name.encode('utf-8'))) if loc == -1: # Skip uniforms that may be inside a Uniform Block continue + uniforms[u_name] = _Uniform(program, u_name, u_type, loc, self._dsa) - try: - gl_type, gl_setter, length, count = _uniform_setters[u_type] - gl_getter = _uniform_getters[gl_type] + if _debug_gl_shaders: + for uniform in self._uniforms.values(): + print(f"Found uniform: {uniform}") - if _debug_gl_shaders: - print("Found uniform: {0}, type: {1}, size: {2}, location: {3}, length: {4}," - " count: {5}".format(u_name, u_type, u_size, loc, length, count)) - - except KeyError: - raise GLException("Unsupported Uniform type {0}".format(u_type)) - - self._uniforms[u_name] = _Uniform(prg_id, u_name, u_type, gl_type, loc, length, count, gl_setter, gl_getter) + return uniforms def _introspect_uniform_blocks(self): - p_id = self._id - + program = self._id uniform_blocks = {} - for index in range(self._get_number(GL_ACTIVE_UNIFORM_BLOCKS)): name = self._get_uniform_block_name(index) - uniform_blocks[name] = {} - num_active = GLint() block_data_size = GLint() - glGetActiveUniformBlockiv(p_id, index, GL_UNIFORM_BLOCK_DATA_SIZE, block_data_size) - glGetActiveUniformBlockiv(p_id, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, num_active) + glGetActiveUniformBlockiv(program, index, GL_UNIFORM_BLOCK_DATA_SIZE, block_data_size) + glGetActiveUniformBlockiv(program, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, num_active) indices = (GLuint * num_active.value)() indices_ptr = cast(addressof(indices), POINTER(GLint)) - glGetActiveUniformBlockiv(p_id, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, indices_ptr) - + glGetActiveUniformBlockiv(program, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, indices_ptr) + + uniforms = {} + for i in range(num_active.value): uniform_name, u_type, u_size = self._query_uniform(indices[i]) - + # Separate uniform name from block name (Only if instance name is provided on the Uniform Block) try: _, uniform_name = uniform_name.split(".") except ValueError: pass - - gl_type, _, length, _ = _uniform_setters[u_type] - - uniform_blocks[name][i] = (uniform_name, gl_type, length) - self._uniform_blocks[name] = UniformBlock(self, name, index, block_data_size.value, uniform_blocks[name]) + gl_type, _, _, length, _ = _uniform_setters[u_type] + uniforms[i] = (uniform_name, gl_type, length) + + uniform_blocks[name] = UniformBlock(self, name, index, block_data_size.value, uniforms) + + return uniform_blocks def _get_uniform_block_name(self, index): buf_size = 128 @@ -484,10 +488,10 @@ class ShaderProgram: try: if isinstance(fmt, tuple): fmt, array = fmt - initial_arrays.append((attributes[name]['location'], array)) + initial_arrays.append((name, array)) attributes[name] = {**attributes[name], **{'format': fmt}} except KeyError: - raise ShaderException(f"\nThe attribute `{name}` doesn't exist. Valid names: \n{attributes.keys()}") + raise ShaderException(f"\nThe attribute `{name}` doesn't exist. Valid names: \n{list(attributes)}") batch = batch or pyglet.graphics.get_default_batch() domain = batch.get_domain(False, mode, group, self._id, attributes) @@ -495,8 +499,8 @@ class ShaderProgram: # Create vertex list and initialize vlist = domain.create(count) - for index, array in initial_arrays: - vlist.set_attribute_data(index, array) + for name, array in initial_arrays: + vlist.set_attribute_data(name, array) return vlist @@ -528,7 +532,7 @@ class ShaderProgram: try: if isinstance(fmt, tuple): fmt, array = fmt - initial_arrays.append((attributes[name]['location'], array)) + initial_arrays.append((name, array)) attributes[name] = {**attributes[name], **{'format': fmt}} except KeyError: raise ShaderException(f"\nThe attribute `{name}` doesn't exist. Valid names: \n{list(attributes)}") @@ -541,8 +545,8 @@ class ShaderProgram: start = vlist.start vlist.indices = [i + start for i in indices] - for index, array in initial_arrays: - vlist.set_attribute_data(index, array) + for name, array in initial_arrays: + vlist.set_attribute_data(name, array) return vlist @@ -578,7 +582,6 @@ class UniformBufferObject: self.view = self._introspect_uniforms() self._view_ptr = pointer(self.view) self.index = index - # glUniformBlockBinding(self.block.program.id, self.block.index, self.index) @property def id(self): diff --git a/libs/pyglet/graphics/vertexdomain.py b/libs/pyglet/graphics/vertexdomain.py index a129634..b362c76 100644 --- a/libs/pyglet/graphics/vertexdomain.py +++ b/libs/pyglet/graphics/vertexdomain.py @@ -329,8 +329,8 @@ class VertexList: for version in self._cache_versions: self._cache_versions[version] = None - def set_attribute_data(self, i, data): - attribute = self.domain.attributes[i] + def set_attribute_data(self, name, data): + attribute = self.domain.attribute_names[name] attribute.set_region(attribute.buffer, self.start, self.count, data) def __getattr__(self, name): diff --git a/libs/pyglet/input/controller.py b/libs/pyglet/input/controller.py index bcb9dd5..44119da 100644 --- a/libs/pyglet/input/controller.py +++ b/libs/pyglet/input/controller.py @@ -81,15 +81,15 @@ class _Relation: def _map_pair(raw_relation): inverted = False relation_string = raw_relation.split(":")[1] - if relation_string.startswith("+"): - relation_string = relation_string[1:] + if "+" in relation_string: + relation_string = relation_string.strip('+') inverted = False - elif relation_string.startswith("-"): - relation_string = relation_string[1:] + elif "-" in relation_string: + relation_string = relation_string.strip('-') inverted = True if "~" in relation_string: - # TODO: handle this - return None + relation_string = relation_string.strip('~') + inverted = True if relation_string.startswith("b"): # Button return _Relation("button", int(relation_string[1:]), inverted) elif relation_string.startswith("a"): # Axis @@ -189,7 +189,6 @@ def add_mappings_from_file(filename) -> None: `filename` : str A file path. """ - assert os.path.exists(filename), f"Invalid path: {filename}" with open(filename) as f: add_mappings_from_string(f.read()) diff --git a/libs/pyglet/input/evdev.py b/libs/pyglet/input/evdev.py index 6e0894f..411756e 100644 --- a/libs/pyglet/input/evdev.py +++ b/libs/pyglet/input/evdev.py @@ -34,11 +34,15 @@ # ---------------------------------------------------------------------------- import os -import errno import fcntl -import struct import ctypes +from ctypes import c_uint16 as _u16 +from ctypes import c_int16 as _s16 +from ctypes import c_uint32 as _u32 +from ctypes import c_int32 as _s32 +from ctypes import c_int64 as _s64 + import pyglet from pyglet.app.xlib import XlibSelectDevice @@ -47,18 +51,11 @@ from .base import DeviceOpenException from .evdev_constants import * from .controller import get_mapping -c = pyglet.lib.load_library('c') - _IOC_NRBITS = 8 _IOC_TYPEBITS = 8 _IOC_SIZEBITS = 14 _IOC_DIRBITS = 2 -_IOC_NRMASK = ((1 << _IOC_NRBITS) - 1) -_IOC_TYPEMASK = ((1 << _IOC_TYPEBITS) - 1) -_IOC_SIZEMASK = ((1 << _IOC_SIZEBITS) - 1) -_IOC_DIRMASK = ((1 << _IOC_DIRBITS) - 1) - _IOC_NRSHIFT = 0 _IOC_TYPESHIFT = (_IOC_NRSHIFT + _IOC_NRBITS) _IOC_SIZESHIFT = (_IOC_TYPESHIFT + _IOC_TYPEBITS) @@ -81,9 +78,7 @@ def _IOR(type, nr, struct): def f(fileno): buffer = struct() - if c.ioctl(fileno, request, ctypes.byref(buffer)) < 0: - err = ctypes.c_int.in_dll(c, 'errno').value - raise OSError(err, errno.errorcode[err]) + fcntl.ioctl(fileno, request, buffer) return buffer return f @@ -92,9 +87,7 @@ def _IOR(type, nr, struct): def _IOR_len(type, nr): def f(fileno, buffer): request = _IOC(_IOC_READ, ord(type), nr, ctypes.sizeof(buffer)) - if c.ioctl(fileno, request, ctypes.byref(buffer)) < 0: - err = ctypes.c_int.in_dll(c, 'errno').value - raise OSError(err, errno.errorcode[err]) + fcntl.ioctl(fileno, request, buffer) return buffer return f @@ -103,56 +96,154 @@ def _IOR_len(type, nr): def _IOR_str(type, nr): g = _IOR_len(type, nr) - def f(fileno, len=256): - return g(fileno, ctypes.create_string_buffer(len)).value + def f(fileno, length=256): + return g(fileno, ctypes.create_string_buffer(length)).value return f -time_t = ctypes.c_long -suseconds_t = ctypes.c_long +def _IOW(type, nr): + + def f(fileno, buffer): + request = _IOC(_IOC_WRITE, ord(type), nr, ctypes.sizeof(buffer)) + fcntl.ioctl(fileno, request, buffer) + + return f -class timeval(ctypes.Structure): +# Structures from /linux/blob/master/include/uapi/linux/input.h + +class Timeval(ctypes.Structure): _fields_ = ( - ('tv_sec', time_t), - ('tv_usec', suseconds_t) + ('tv_sec', _s64), + ('tv_usec', _s64) ) -class input_event(ctypes.Structure): +class InputEvent(ctypes.Structure): _fields_ = ( - ('time', timeval), - ('type', ctypes.c_uint16), - ('code', ctypes.c_uint16), - ('value', ctypes.c_int32) + ('time', Timeval), + ('type', _u16), + ('code', _u16), + ('value', _s32) ) -class input_id(ctypes.Structure): +class InputID(ctypes.Structure): _fields_ = ( - ('bustype', ctypes.c_uint16), - ('vendor', ctypes.c_uint16), - ('product', ctypes.c_uint16), - ('version', ctypes.c_uint16), + ('bustype', _u16), + ('vendor', _u16), + ('product', _u16), + ('version', _u16), ) -class input_absinfo(ctypes.Structure): +class InputABSInfo(ctypes.Structure): _fields_ = ( - ('value', ctypes.c_int32), - ('minimum', ctypes.c_int32), - ('maximum', ctypes.c_int32), - ('fuzz', ctypes.c_int32), - ('flat', ctypes.c_int32), + ('value', _s32), + ('minimum', _s32), + ('maximum', _s32), + ('fuzz', _s32), + ('flat', _s32), + ) + + +class FFReplay(ctypes.Structure): + _fields_ = ( + ('length', _u16), + ('delay', _u16) + ) + + +class FFTrigger(ctypes.Structure): + _fields_ = ( + ('button', _u16), + ('interval', _u16) + ) + + +class FFEnvelope(ctypes.Structure): + _fields_ = [ + ('attack_length', _u16), + ('attack_level', _u16), + ('fade_length', _u16), + ('fade_level', _u16), + ] + + +class FFConstantEffect(ctypes.Structure): + _fields_ = [ + ('level', _s16), + ('ff_envelope', FFEnvelope), + ] + + +class FFRampEffect(ctypes.Structure): + _fields_ = [ + ('start_level', _s16), + ('end_level', _s16), + ('ff_envelope', FFEnvelope), + ] + + +class FFConditionEffect(ctypes.Structure): + _fields_ = [ + ('right_saturation', _u16), + ('left_saturation', _u16), + ('right_coeff', _s16), + ('left_coeff', _s16), + ('deadband', _u16), + ('center', _s16), + ] + + +class FFPeriodicEffect(ctypes.Structure): + _fields_ = [ + ('waveform', _u16), + ('period', _u16), + ('magnitude', _s16), + ('offset', _s16), + ('phase', _u16), + ('envelope', FFEnvelope), + ('custom_len', _u32), + ('custom_data', ctypes.POINTER(_s16)), + ] + + +class FFRumbleEffect(ctypes.Structure): + _fields_ = ( + ('strong_magnitude', _u16), + ('weak_magnitude', _u16) + ) + + +class FFEffectType(ctypes.Union): + _fields_ = ( + ('ff_constant_effect', FFConstantEffect), + ('ff_ramp_effect', FFRampEffect), + ('ff_periodic_effect', FFPeriodicEffect), + ('ff_condition_effect', FFConditionEffect * 2), + ('ff_rumble_effect', FFRumbleEffect), + ) + + +class FFEvent(ctypes.Structure): + _fields_ = ( + ('type', _u16), + ('id', _s16), + ('direction', _u16), + ('ff_trigger', FFTrigger), + ('ff_replay', FFReplay), + ('u', FFEffectType) ) EVIOCGVERSION = _IOR('E', 0x01, ctypes.c_int) -EVIOCGID = _IOR('E', 0x02, input_id) +EVIOCGID = _IOR('E', 0x02, InputID) EVIOCGNAME = _IOR_str('E', 0x06) EVIOCGPHYS = _IOR_str('E', 0x07) EVIOCGUNIQ = _IOR_str('E', 0x08) +EVIOCSFF = _IOW('E', 0x80) def EVIOCGBIT(fileno, ev, buffer): @@ -160,14 +251,14 @@ def EVIOCGBIT(fileno, ev, buffer): def EVIOCGABS(fileno, abs): - buffer = input_absinfo() + buffer = InputABSInfo() return _IOR_len('E', 0x40 + abs)(fileno, buffer) -def get_set_bits(bytes): +def get_set_bits(bytestring): bits = set() j = 0 - for byte in bytes: + for byte in bytestring: for i in range(8): if byte & 1: bits.add(j + i) @@ -208,7 +299,6 @@ def _create_control(fileno, event_type, event_code): maximum = absinfo.maximum control = AbsoluteAxis(name, minimum, maximum, raw_name) control.value = value - if name == 'hat_y': control.inverted = True elif event_type == EV_REL: @@ -235,6 +325,7 @@ event_types = { EV_MSC: MSC_MAX, EV_LED: LED_MAX, EV_SND: SND_MAX, + EV_FF: FF_MAX, } @@ -273,6 +364,7 @@ class EvdevDevice(XlibSelectDevice, Device): self.controls = [] self.control_map = {} + self.ff_types = [] event_types_bits = (ctypes.c_byte * 4)() EVIOCGBIT(fileno, 0, event_types_bits) @@ -283,11 +375,14 @@ class EvdevDevice(XlibSelectDevice, Device): nbytes = max_code // 8 + 1 event_codes_bits = (ctypes.c_byte * nbytes)() EVIOCGBIT(fileno, event_type, event_codes_bits) - for event_code in get_set_bits(event_codes_bits): - control = _create_control(fileno, event_type, event_code) - if control: - self.control_map[(event_type, event_code)] = control - self.controls.append(control) + if event_type == EV_FF: + self.ff_types.extend(get_set_bits(event_codes_bits)) + else: + for event_code in get_set_bits(event_codes_bits): + control = _create_control(fileno, event_type, event_code) + if control: + self.control_map[(event_type, event_code)] = control + self.controls.append(control) os.close(fileno) @@ -308,7 +403,7 @@ class EvdevDevice(XlibSelectDevice, Device): hex_product, shifted_product, hex_version, shifted_version) def open(self, window=None, exclusive=False): - super(EvdevDevice, self).open(window, exclusive) + super().open(window, exclusive) try: self._fileno = os.open(self._filename, os.O_RDWR | os.O_NONBLOCK) @@ -318,7 +413,7 @@ class EvdevDevice(XlibSelectDevice, Device): pyglet.app.platform_event_loop.select_devices.add(self) def close(self): - super(EvdevDevice, self).close() + super().close() if not self._fileno: return @@ -332,33 +427,8 @@ class EvdevDevice(XlibSelectDevice, Device): # Force Feedback methods - def supports_ff(self): - try: - self._fileno = os.open(self._filename, os.O_RDWR | os.O_NONBLOCK) - self.ff_create_effect(0, 0, 0) - os.close(self._fileno) - return True - except OSError: - os.close(self._fileno) - return False - - def ff_create_effect(self, weak, strong, duration, effect=-1): - weak = int(max(min(1, weak), 0) * 0xFFFF) # Clamp range from 0-1, convert to 16bit - strong = int(max(min(1, strong), 0) * 0xFFFF) # Clamp range from 0-1, convert to 16bit - duration = int(duration * 1000) - effect = bytearray(struct.pack('HhHHHHHxHH', FF_RUMBLE, effect, 0, 0, 0, duration, 0, strong, weak)) - view = memoryview(effect).cast('h') - - fcntl.ioctl(self._fileno, 0x40304580, view, True) - return view[1] # effect ID - - def ff_play(self, effect): - ev_play = struct.pack('LLHHi', 0, 0, EV_FF, effect, 1) - os.write(self._fileno, ev_play) - - def ff_stop(self, effect): - ev_stop = struct.pack('LLHHi', 0, 0, EV_FF, effect, 0) - os.write(self._fileno, ev_stop) + def ff_upload_effect(self, structure): + os.write(self._fileno, structure) # XlibSelectDevice interface @@ -372,12 +442,14 @@ class EvdevDevice(XlibSelectDevice, Device): if not self._fileno: return - events = (input_event * 64)() - bytes_read = c.read(self._fileno, events, ctypes.sizeof(events)) - if bytes_read < 0: + try: + events = (InputEvent * 64)() + bytes_read = os.readv(self._fileno, events) + except OSError: + self.close() return - n_events = bytes_read // ctypes.sizeof(input_event) + n_events = bytes_read // ctypes.sizeof(InputEvent) for event in events[:n_events]: try: control = self.control_map[(event.type, event.code)] @@ -386,32 +458,54 @@ class EvdevDevice(XlibSelectDevice, Device): pass -class EvdevController(Controller): - - _rumble_weak = -1 - _rumble_strong = -1 +class FFController(Controller): + """Controller that supports force-feedback""" + _fileno = None + _weak_effect = None + _play_weak_event = None + _stop_weak_event = None + _strong_effect = None + _play_strong_event = None + _stop_strong_event = None def open(self, window=None, exclusive=False): super().open(window, exclusive) - # Create Force Feedback effects when the device is opened: - self._rumble_weak = self.device.ff_create_effect(0, 0, 0) - self._rumble_strong = self.device.ff_create_effect(0, 0, 0) + self._fileno = self.device.fileno() + # Create Force Feedback effects & events when opened: + # https://www.kernel.org/doc/html/latest/input/ff.html + self._weak_effect = self._create_effect() + self._play_weak_event = InputEvent(Timeval(), EV_FF, self._weak_effect.id, 1) + self._stop_weak_event = InputEvent(Timeval(), EV_FF, self._weak_effect.id, 0) + self._strong_effect = self._create_effect() + self._play_strong_event = InputEvent(Timeval(), EV_FF, self._strong_effect.id, 1) + self._stop_strong_event = InputEvent(Timeval(), EV_FF, self._strong_effect.id, 0) + + def _create_effect(self): + event = FFEvent(FF_RUMBLE, -1) + EVIOCSFF(self._fileno, event) + return event def rumble_play_weak(self, strength=1.0, duration=0.5): - effect = self.device.ff_create_effect(strength, 0, duration, self._rumble_weak) - self.device.ff_play(effect) + effect = self._weak_effect + effect.u.ff_rumble_effect.weak_magnitude = int(max(min(1.0, strength), 0) * 0xFFFF) + effect.ff_replay.length = int(duration * 1000) + EVIOCSFF(self._fileno, effect) + self.device.ff_upload_effect(self._play_weak_event) def rumble_play_strong(self, strength=1.0, duration=0.5): - effect = self.device.ff_create_effect(0, strength, duration, self._rumble_strong) - self.device.ff_play(effect) + effect = self._strong_effect + effect.u.ff_rumble_effect.strong_magnitude = int(max(min(1.0, strength), 0) * 0xFFFF) + effect.ff_replay.length = int(duration * 1000) + EVIOCSFF(self._fileno, effect) + self.device.ff_upload_effect(self._play_strong_event) def rumble_stop_weak(self): """Stop playing rumble effects on the weak motor.""" - self.device.ff_stop(self._rumble_weak) + self.device.ff_upload_effect(self._stop_weak_event) def rumble_stop_strong(self): """Stop playing rumble effects on the strong motor.""" - self.device.ff_stop(self._rumble_strong) + self.device.ff_upload_effect(self._stop_strong_event) def get_devices(display=None): @@ -470,8 +564,8 @@ def _create_controller(device): if have_button is False or mapping is None: return - if device.supports_ff(): - return EvdevController(device, mapping) + if FF_RUMBLE in device.ff_types: + return FFController(device, mapping) else: return Controller(device, mapping) diff --git a/libs/pyglet/math.py b/libs/pyglet/math.py index 39efcd7..2523c6d 100644 --- a/libs/pyglet/math.py +++ b/libs/pyglet/math.py @@ -36,14 +36,19 @@ """Matrix and Vector math. This module provides Vector and Matrix objects, include Vec2, Vec3, Vec4, -Mat3 and Mat4. Most common operations are supported, and many helper -methods are included for rotating, scaling, and transforming. -The :py:class:`~pyglet.matrix.Mat4` includes class methods -for creating orthographic and perspective projection matrixes. +Mat3, and Mat4. Most common operations are supported, and many helper +methods are included for rotating, scaling, and transforming. The +:py:class:`~pyglet.matrix.Mat4` includes class methods for creating +orthographic and perspective projection matrixes. + +:note: For performance, these objects' subclass the `tuple` type. They +are therefore immutable - all operations return a new object; the +object is not updated in-place. """ import math as _math import warnings as _warnings + from operator import mul as _mul @@ -52,7 +57,7 @@ def clamp(num, min_val, max_val): class Vec2(tuple): - """A two dimensional vector represented as an X Y coordinate pair. + """A two-dimensional vector represented as an X Y coordinate pair. :parameters: `x` : int or float : @@ -60,7 +65,8 @@ class Vec2(tuple): `y` : int or float : The Y coordinate of the vector. - Vectors must be created with either 0 or 2 values. If no arguments are provided a vector with the coordinates 0, 0 is created. + Vectors must be created with either 0 or 2 values. If no + arguments are provided a vector with the coordinates 0, 0 is created. Vectors are stored as a tuple and therefore immutable and cannot be modified directly """ @@ -148,7 +154,8 @@ class Vec2(tuple): return self.__add__(other) def from_magnitude(self, magnitude): - """Create a new Vector of the given magnitude by normalizing, then scaling the vector. The heading remains unchanged. + """Create a new Vector of the given magnitude by normalizing, + then scaling the vector. The heading remains unchanged. :parameters: `magnitude` : int or float : @@ -172,18 +179,18 @@ class Vec2(tuple): mag = self.__abs__() return Vec2(mag * _math.cos(heading), mag * _math.sin(heading)) - def limit(self, max): + def limit(self, maximum): """Limit the magnitude of the vector to the value used for the max parameter. :parameters: - `max` : int or float : + `maximum` : int or float : The maximum magnitude for the vector. :returns: Either self or a new vector with the maximum magnitude. :rtype: Vec2 """ - if self[0] ** 2 + self[1] ** 2 > max * max: - return self.from_magnitude(max) + if self[0] ** 2 + self[1] ** 2 > maximum * maximum: + return self.from_magnitude(maximum) return self def lerp(self, other, alpha): @@ -291,7 +298,7 @@ class Vec2(tuple): class Vec3(tuple): - """A three dimensional vector represented as a X Y Z coordinates. + """A three-dimensional vector represented as X Y Z coordinates. :parameters: `x` : int or float : @@ -301,7 +308,8 @@ class Vec3(tuple): `z` : int or float : The Z coordinate of the vector. - 3D Vectors must be created with either 0 or 3 values. If no arguments are provided a vector with the coordinates 0, 0, 0 is created. + 3D Vectors must be created with either 0 or 3 values. If no arguments + are provided, a vector with the coordinates 0, 0, 0 is created. Vectors are stored as a tuple and therefore immutable and cannot be modified directly """ @@ -374,7 +382,8 @@ class Vec3(tuple): return self.__add__(other) def from_magnitude(self, magnitude): - """Create a new Vector of the given magnitude by normalizing, then scaling the vector. The rotation remains unchanged. + """Create a new Vector of the given magnitude by normalizing, + then scaling the vector. The rotation remains unchanged. :parameters: `magnitude` : int or float : @@ -385,17 +394,17 @@ class Vec3(tuple): """ return self.normalize().scale(magnitude) - def limit(self, max): + def limit(self, maximum): """Limit the magnitude of the vector to the value used for the max parameter. :parameters: - `max` : int or float : + `maximum` : int or float : The maximum magnitude for the vector. :returns: Either self or a new vector with the maximum magnitude. :rtype: Vec3 """ - if self[0] ** 2 + self[1] ** 2 + self[2] **2 > max * max * max: + if self[0] ** 2 + self[1] ** 2 + self[2] ** 2 > maximum * maximum * maximum: return self.from_magnitude(max) return self @@ -829,7 +838,7 @@ class Mat4(tuple): return Mat4(temp) def translate(self, vector: Vec3) -> 'Mat4': - """Get a translate Matrix along x, y, and z axis.""" + """Get a translation Matrix along x, y, and z axis.""" return Mat4(self) @ Mat4((1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, *vector, 1)) def rotate(self, angle: float, vector: Vec3) -> 'Mat4': diff --git a/libs/pyglet/media/synthesis.py b/libs/pyglet/media/synthesis.py index bde6b85..b09652a 100644 --- a/libs/pyglet/media/synthesis.py +++ b/libs/pyglet/media/synthesis.py @@ -219,7 +219,7 @@ class SynthesisSource(Source): def _generate_data(self, num_bytes): """Generate `num_bytes` bytes of data. - Return data as ctypes array or string. + Return data as ctypes array or bytes. """ raise NotImplementedError('abstract') @@ -451,6 +451,8 @@ class Encoder(SynthesisSource): def __init__(self, duration, generator, **kwargs): super().__init__(duration, **kwargs) self._generator = generator + self._total = int(duration * self.audio_format.sample_rate) + self._consumed = 0 def _generate_data(self, num_bytes): envelope = self._envelope_generator diff --git a/libs/pyglet/model/__init__.py b/libs/pyglet/model/__init__.py index 469e9c1..e81dd26 100644 --- a/libs/pyglet/model/__init__.py +++ b/libs/pyglet/model/__init__.py @@ -81,7 +81,7 @@ instance when loading the Model:: .. versionadded:: 1.4 """ -from math import radians + from io import BytesIO import pyglet @@ -190,8 +190,7 @@ class Model: self.vertex_lists = vertex_lists self.groups = groups self._batch = batch - self._rotation = Vec3() - self._translation = Vec3() + self._modelview_matrix = Mat4() @property def batch(self): @@ -220,24 +219,14 @@ class Model: self._batch = batch @property - def rotation(self): - return self._rotation + def matrix(self): + return self._modelview_matrix - @rotation.setter - def rotation(self, values): - self._rotation = values + @matrix.setter + def matrix(self, matrix): + self._modelview_matrix = matrix for group in self.groups: - group.rotation = values - - @property - def translation(self): - return self._translation - - @translation.setter - def translation(self, values): - self._translation = values - for group in self.groups: - group.translation = values + group.matrix = matrix def draw(self): """Draw the model. @@ -274,28 +263,11 @@ class Material: class BaseMaterialGroup(graphics.ShaderGroup): default_vert_src = None default_frag_src = None + matrix = Mat4() def __init__(self, material, program, order=0, parent=None): super().__init__(program, order, parent) - self.material = material - self.rotation = Vec3() - self.translation = Vec3() - - def set_modelview_matrix(self): - # NOTE: Matrix operations can be optimized later with transform feedback - view = Mat4() - view = view.rotate(radians(self.rotation[2]), Vec3(0, 0, 1)) - view = view.rotate(radians(self.rotation[1]), Vec3(0, 1, 0)) - view = view.rotate(radians(self.rotation[0]), Vec3(1, 0, 0)) - view = view.translate(self.translation) - - # TODO: separate the projection block, and remove this hack - block = self.program.uniform_blocks['WindowBlock'] - ubo = block.create_ubo(0) - with ubo as window_block: - window_block.projection[:] = pyglet.math.Mat4.perspective_projection(0, 720, 0, 480, z_near=0.1, z_far=255) - window_block.view[:] = view class TexturedMaterialGroup(BaseMaterialGroup): @@ -316,12 +288,13 @@ class TexturedMaterialGroup(BaseMaterialGroup): mat4 view; } window; + uniform mat4 model; void main() { - vec4 pos = window.view * vec4(vertices, 1.0); + vec4 pos = window.view * model * vec4(vertices, 1.0); gl_Position = window.projection * pos; - mat3 normal_matrix = transpose(inverse(mat3(window.view))); + mat3 normal_matrix = transpose(inverse(mat3(model))); vertex_position = pos.xyz; vertex_colors = colors; @@ -345,15 +318,15 @@ class TexturedMaterialGroup(BaseMaterialGroup): } """ - def __init__(self, material, texture): - super().__init__(material, get_default_textured_shader()) + def __init__(self, material, program, texture, order=0, parent=None): + super().__init__(material, program, order, parent) self.texture = texture def set_state(self): gl.glActiveTexture(gl.GL_TEXTURE0) gl.glBindTexture(self.texture.target, self.texture.id) self.program.use() - self.set_modelview_matrix() + self.program['model'] = self.matrix def unset_state(self): gl.glBindTexture(self.texture.target, 0) @@ -387,11 +360,13 @@ class MaterialGroup(BaseMaterialGroup): mat4 view; } window; + uniform mat4 model; + void main() { - vec4 pos = window.view * vec4(vertices, 1.0); + vec4 pos = window.view * model * vec4(vertices, 1.0); gl_Position = window.projection * pos; - mat3 normal_matrix = transpose(inverse(mat3(window.view))); + mat3 normal_matrix = transpose(inverse(mat3(model))); vertex_position = pos.xyz; vertex_colors = colors; @@ -411,12 +386,9 @@ class MaterialGroup(BaseMaterialGroup): } """ - def __init__(self, material): - super().__init__(material, get_default_shader()) - def set_state(self): self.program.use() - self.set_modelview_matrix() + self.program['model'] = self.matrix add_default_model_codecs() diff --git a/libs/pyglet/model/codecs/obj.py b/libs/pyglet/model/codecs/obj.py index 0568c20..9dbfef7 100644 --- a/libs/pyglet/model/codecs/obj.py +++ b/libs/pyglet/model/codecs/obj.py @@ -245,17 +245,17 @@ class OBJModelDecoder(ModelDecoder): material = mesh.material count = len(mesh.vertices) // 3 if material.texture_name: - texture = pyglet.resource.texture(material.texture_name) - group = TexturedMaterialGroup(material, texture) program = pyglet.model.get_default_textured_shader() + texture = pyglet.resource.texture(material.texture_name) + group = TexturedMaterialGroup(material, program, texture) vertex_lists.append(program.vertex_list(count, GL_TRIANGLES, batch, group, vertices=('f', mesh.vertices), normals=('f', mesh.normals), tex_coords=('f', mesh.tex_coords), colors=('f', material.diffuse * count))) else: - group = MaterialGroup(material) program = pyglet.model.get_default_shader() + group = MaterialGroup(material, program) vertex_lists.append(program.vertex_list(count, GL_TRIANGLES, batch, group, vertices=('f', mesh.vertices), normals=('f', mesh.normals),