update pyglet

fix crash
This commit is contained in:
shenjackyuanjie 2022-03-22 23:20:07 +08:00
parent dc8ee0b4a5
commit 8c96116afb
14 changed files with 395 additions and 306 deletions

9
DR.py
View File

@ -38,14 +38,17 @@ if __name__ == '__main__':
print(sys.path) print(sys.path)
print(hi) print(hi)
DEBUGGING = False DEBUGGING = True
from Difficult_Rocket.api.Exp import * from Difficult_Rocket.api.Exp import *
from Difficult_Rocket.crash import crash from Difficult_Rocket.crash import crash
try: try:
from Difficult_Rocket import main from Difficult_Rocket import main
game = main.Game() 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 cprofile = False
if cprofile: if cprofile:
cProfile.run('game.start()', sort='calls') cProfile.run('game.start()', sort='calls')

View File

@ -173,7 +173,7 @@ class ClientWindow(Window):
def start_game(self) -> None: def start_game(self) -> None:
self.run_input = True self.run_input = True
self.read_input() # self.read_input()
pyglet.app.run() pyglet.app.run()
@new_thread('window read_input', daemon=True) @new_thread('window read_input', daemon=True)
@ -193,11 +193,11 @@ class ClientWindow(Window):
@new_thread('window save_info') @new_thread('window save_info')
def save_info(self): def save_info(self):
print('save_info start') self.logger.info('save_info start')
config_file = tools.load_file('./config/config.toml') config_file = tools.load_file('./configs/main.toml')
config_file['window']['width'] = self.width config_file['window']['width'] = self.width
config_file['window']['height'] = self.height 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 draws and some event
@ -211,9 +211,10 @@ class ClientWindow(Window):
def FPS_update(self, tick: Decimal): def FPS_update(self, tick: Decimal):
now_FPS = pyglet.clock.get_fps() now_FPS = pyglet.clock.get_fps()
self.fps_log.update_tick(tick) 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): def on_draw(self, *dt):
# self.logger.debug('on_draw call dt: {}'.format(dt))
self.clear() self.clear()
self.draw_batch() self.draw_batch()

View File

@ -70,7 +70,7 @@ class Game:
# @new_thread('main') # @new_thread('main')
def _start(self): def _start(self):
self.server.run() self.server.run()
threaded = True threaded = False
if threaded: if threaded:
try: try:
game_process = multiprocessing.Process(target=self.client.start(), name='pyglet app') game_process = multiprocessing.Process(target=self.client.start(), name='pyglet app')

View File

@ -2,9 +2,9 @@
fps = 60 fps = 60
version = "0.6.1" version = "0.6.1"
language = "zh-CN" 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" write_py_v = "3.8.10"
fonts_folder = 'libs/fonts' fonts_folder = "libs/fonts"
[window] [window]
style = "None" style = "None"
@ -18,4 +18,3 @@ full_screen = false
[window.default] [window.default]
width = 1024 width = 1024
height = 768 height = 768

View File

@ -452,3 +452,6 @@ class Font:
glyphs = glyph_buffer glyphs = glyph_buffer
return glyphs return glyphs
def __repr__(self):
return f"{self.__class__.__name__}('{self.name}')"

View File

@ -170,7 +170,7 @@ from pyglet.graphics.vertexbuffer import BufferObject
_debug_graphics_batch = pyglet.options['debug_graphics_batch'] _debug_graphics_batch = pyglet.options['debug_graphics_batch']
def draw(size, mode, **kwargs): def draw(size, mode, **data):
"""Draw a primitive immediately. """Draw a primitive immediately.
:Parameters: :Parameters:
@ -194,7 +194,7 @@ def draw(size, mode, **kwargs):
program.use() program.use()
buffers = [] buffers = []
for name, (fmt, array) in kwargs.items(): for name, (fmt, array) in data.items():
location = program.attributes[name]['location'] location = program.attributes[name]['location']
count = program.attributes[name]['count'] count = program.attributes[name]['count']
gl_type = vertexdomain._gl_types[fmt[0]] gl_type = vertexdomain._gl_types[fmt[0]]
@ -340,6 +340,8 @@ class Batch:
self._draw_list = [] self._draw_list = []
self._draw_list_dirty = False self._draw_list_dirty = False
self._context = pyglet.gl.current_context
def invalidate(self): def invalidate(self):
"""Force the batch to update the draw list. """Force the batch to update the draw list.
@ -554,7 +556,7 @@ class Group:
"""Group of common OpenGL state. """Group of common OpenGL state.
Before a VertexList is rendered, its Group's OpenGL state is set. 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): def __init__(self, order=0, parent=None):
"""Create a Group. """Create a Group.
@ -565,6 +567,8 @@ class Group:
`parent` : `~pyglet.graphics.Group` `parent` : `~pyglet.graphics.Group`
Group to contain this Group; its state will be set before this Group to contain this Group; its state will be set before this
Group's state. Group's state.
:Ivariables:
`visible` : bool `visible` : bool
Determines whether this Group is visible in any of the Batches Determines whether this Group is visible in any of the Batches
it is assigned to. If False, objects in this Group will not it is assigned to. If False, objects in this Group will not

View File

@ -28,65 +28,63 @@ _uniform_getters = {
} }
_uniform_setters = { _uniform_setters = {
# uniform type: (gl_type, setter, length, count) # uniform: gl_type, legacy_setter, setter, length, count
GL_BOOL: (GLint, glUniform1iv, 1, 1), GL_BOOL: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_BOOL_VEC2: (GLint, glUniform1iv, 2, 1), GL_BOOL_VEC2: (GLint, glUniform1iv, glProgramUniform1iv, 2, 1),
GL_BOOL_VEC3: (GLint, glUniform1iv, 3, 1), GL_BOOL_VEC3: (GLint, glUniform1iv, glProgramUniform1iv, 3, 1),
GL_BOOL_VEC4: (GLint, glUniform1iv, 4, 1), GL_BOOL_VEC4: (GLint, glUniform1iv, glProgramUniform1iv, 4, 1),
GL_INT: (GLint, glUniform1iv, 1, 1), GL_INT: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_INT_VEC2: (GLint, glUniform2iv, 2, 1), GL_INT_VEC2: (GLint, glUniform2iv, glProgramUniform2iv, 2, 1),
GL_INT_VEC3: (GLint, glUniform3iv, 3, 1), GL_INT_VEC3: (GLint, glUniform3iv, glProgramUniform3iv, 3, 1),
GL_INT_VEC4: (GLint, glUniform4iv, 4, 1), GL_INT_VEC4: (GLint, glUniform4iv, glProgramUniform4iv, 4, 1),
GL_FLOAT: (GLfloat, glUniform1fv, 1, 1), GL_FLOAT: (GLfloat, glUniform1fv, glProgramUniform1fv, 1, 1),
GL_FLOAT_VEC2: (GLfloat, glUniform2fv, 2, 1), GL_FLOAT_VEC2: (GLfloat, glUniform2fv, glProgramUniform2fv, 2, 1),
GL_FLOAT_VEC3: (GLfloat, glUniform3fv, 3, 1), GL_FLOAT_VEC3: (GLfloat, glUniform3fv, glProgramUniform3fv, 3, 1),
GL_FLOAT_VEC4: (GLfloat, glUniform4fv, 4, 1), GL_FLOAT_VEC4: (GLfloat, glUniform4fv, glProgramUniform4fv, 4, 1),
GL_SAMPLER_1D: (GLint, glUniform1iv, 1, 1), GL_SAMPLER_1D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_SAMPLER_2D: (GLint, glUniform1iv, 1, 1), GL_SAMPLER_2D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1),
GL_SAMPLER_2D_ARRAY: (GLint, glUniform1iv, 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_MAT2: (GLfloat, glUniformMatrix2fv, glProgramUniformMatrix2fv, 4, 1),
GL_FLOAT_MAT3: (GLfloat, glUniformMatrix3fv, 6, 1), GL_FLOAT_MAT3: (GLfloat, glUniformMatrix3fv, glProgramUniformMatrix3fv, 6, 1),
GL_FLOAT_MAT4: (GLfloat, glUniformMatrix4fv, 16, 1), GL_FLOAT_MAT4: (GLfloat, glUniformMatrix4fv, glProgramUniformMatrix4fv, 16, 1),
# TODO: test/implement these: # TODO: test/implement these:
# GL_FLOAT_MAT2x3: glUniformMatrix2x3fv, # GL_FLOAT_MAT2x3: glUniformMatrix2x3fv, glProgramUniformMatrix2x3fv,
# GL_FLOAT_MAT2x4: glUniformMatrix2x4fv, # GL_FLOAT_MAT2x4: glUniformMatrix2x4fv, glProgramUniformMatrix2x4fv,
# # GL_FLOAT_MAT3x2: glUniformMatrix3x2fv, glProgramUniformMatrix3x2fv,
# GL_FLOAT_MAT3x2: glUniformMatrix3x2fv, # GL_FLOAT_MAT3x4: glUniformMatrix3x4fv, glProgramUniformMatrix3x4fv,
# GL_FLOAT_MAT3x4: glUniformMatrix3x4fv, # GL_FLOAT_MAT4x2: glUniformMatrix4x2fv, glProgramUniformMatrix4x2fv,
# # GL_FLOAT_MAT4x3: glUniformMatrix4x3fv, glProgramUniformMatrix4x3fv,
# GL_FLOAT_MAT4x2: glUniformMatrix4x2fv,
# GL_FLOAT_MAT4x3: glUniformMatrix4x3fv,
} }
_attribute_types = { _attribute_types = {
GL_BOOL: (1, '?'), GL_BOOL: (1, '?'),
GL_BOOL_VEC2: (2, '?'), GL_BOOL_VEC2: (2, '?'),
GL_BOOL_VEC3: (3, '?'), GL_BOOL_VEC3: (3, '?'),
GL_BOOL_VEC4: (4, '?'), GL_BOOL_VEC4: (4, '?'),
GL_INT: (1, 'i'), GL_INT: (1, 'i'),
GL_INT_VEC2: (2, 'i'), GL_INT_VEC2: (2, 'i'),
GL_INT_VEC3: (3, 'i'), GL_INT_VEC3: (3, 'i'),
GL_INT_VEC4: (4, '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_VEC2: (2, 'I'),
GL_UNSIGNED_INT_VEC3: (3, 'I'), GL_UNSIGNED_INT_VEC3: (3, 'I'),
GL_UNSIGNED_INT_VEC4: (4, 'I'), GL_UNSIGNED_INT_VEC4: (4, 'I'),
GL_FLOAT: (1, 'f'), GL_FLOAT: (1, 'f'),
GL_FLOAT_VEC2: (2, 'f'), GL_FLOAT_VEC2: (2, 'f'),
GL_FLOAT_VEC3: (3, 'f'), GL_FLOAT_VEC3: (3, 'f'),
GL_FLOAT_VEC4: (4, 'f'), GL_FLOAT_VEC4: (4, 'f'),
GL_DOUBLE: (1, 'd'), GL_DOUBLE: (1, 'd'),
GL_DOUBLE_VEC2: (2, 'd'), GL_DOUBLE_VEC2: (2, 'd'),
GL_DOUBLE_VEC3: (3, 'd'), GL_DOUBLE_VEC3: (3, 'd'),
GL_DOUBLE_VEC4: (4, 'd'), GL_DOUBLE_VEC4: (4, 'd'),
@ -96,11 +94,16 @@ _attribute_types = {
class _Uniform: class _Uniform:
__slots__ = 'program', 'name', 'type', 'location', 'length', 'count', 'get', 'set' __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.program = program
self.name = name self.name = name
self.type = uniform_type self.type = uniform_type
self.location = location 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.length = length
self.count = count self.count = count
@ -112,45 +115,66 @@ class _Uniform:
ptr = cast(c_array, POINTER(gl_type)) ptr = cast(c_array, POINTER(gl_type))
self.get = self._create_getter_func(program, location, gl_getter, c_array, length) 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 @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""" """Factory function for creating simplified Uniform getters"""
if length == 1: if length == 1:
def getter_func(): def getter_func():
gl_getter(program_id, location, c_array) gl_getter(program, location, c_array)
return c_array[0] return c_array[0]
else: else:
def getter_func(): def getter_func():
gl_getter(program_id, location, c_array) gl_getter(program, location, c_array)
return c_array[:] return c_array[:]
return getter_func return getter_func
@staticmethod @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""" """Factory function for creating simplified Uniform setters"""
if dsa: # Bindless updates:
if is_matrix: if is_matrix:
def setter_func(value): def setter_func(value):
c_array[:] = value c_array[:] = value
gl_setter(location, count, GL_FALSE, ptr) 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: return setter_func
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)
else: 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): def __repr__(self):
return f"Uniform('{self.name}', location={self.location}, length={self.length}, count={self.count})" return f"Uniform('{self.name}', location={self.location}, length={self.length}, count={self.count})"
@ -167,9 +191,9 @@ class Shader:
:Parameters: :Parameters:
`source_string` : str `source_string` : str
A string containing the Shader code. A string containing the Shader source code.
`shader_type` : str `shader_type` : str
The Shader type, such as "vertex" or "fragment". The Shader type, such as "vertex", "fragment", "geometry", etc.
""" """
self._id = None self._id = None
@ -228,7 +252,7 @@ class Shader:
class ShaderProgram: class ShaderProgram:
"""OpenGL Shader Program""" """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): def __init__(self, *shaders):
"""Create an OpenGL ShaderProgram, from multiple Shaders. """Create an OpenGL ShaderProgram, from multiple Shaders.
@ -242,15 +266,13 @@ class ShaderProgram:
assert shaders, "At least one Shader object is required." assert shaders, "At least one Shader object is required."
self._id = self._link_program(shaders) self._id = self._link_program(shaders)
self._context = pyglet.gl.current_context self._context = pyglet.gl.current_context
self._active = False
self._attributes = {} # Query if Direct State Access is available:
self._uniforms = {} self._dsa = gl_info.have_version(4, 1) or gl_info.have_extension("GL_ARB_separate_shader_objects")
self._uniform_blocks = {}
self._introspect_attributes() self._attributes = self._introspect_attributes()
self._introspect_uniforms() self._uniforms = self._introspect_uniforms()
self._introspect_uniform_blocks() self._uniform_blocks = self._introspect_uniform_blocks()
if _debug_gl_shaders: if _debug_gl_shaders:
print(self._get_program_log()) print(self._get_program_log())
@ -259,10 +281,6 @@ class ShaderProgram:
def id(self): def id(self):
return self._id return self._id
@property
def is_active(self):
return self._active
@property @property
def attributes(self): def attributes(self):
return self._attributes return self._attributes
@ -275,10 +293,6 @@ class ShaderProgram:
def uniform_blocks(self): def uniform_blocks(self):
return self._uniform_blocks 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): def _get_program_log(self):
result = c_int(0) result = c_int(0)
glGetProgramiv(self._id, GL_INFO_LOG_LENGTH, byref(result)) glGetProgramiv(self._id, GL_INFO_LOG_LENGTH, byref(result))
@ -302,11 +316,9 @@ class ShaderProgram:
def use(self): def use(self):
glUseProgram(self._id) glUseProgram(self._id)
self._active = True
def stop(self): def stop(self):
glUseProgram(0) glUseProgram(0)
self._active = False
__enter__ = use __enter__ = use
bind = use bind = use
@ -314,7 +326,6 @@ class ShaderProgram:
def __exit__(self, *_): def __exit__(self, *_):
glUseProgram(0) glUseProgram(0)
self._active = False
def __del__(self): def __del__(self):
try: try:
@ -325,13 +336,12 @@ class ShaderProgram:
pass pass
def __setitem__(self, key, value): def __setitem__(self, key, value):
if not self._active:
raise Exception("Shader Program is not active.")
try: try:
uniform = self._uniforms[key] uniform = self._uniforms[key]
except KeyError: 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: try:
uniform.set(value) uniform.set(value)
@ -363,53 +373,46 @@ class ShaderProgram:
loc = glGetAttribLocation(program, create_string_buffer(a_name.encode('utf-8'))) loc = glGetAttribLocation(program, create_string_buffer(a_name.encode('utf-8')))
count, fmt = _attribute_types[a_type] count, fmt = _attribute_types[a_type]
attributes[a_name] = dict(type=a_type, size=a_size, location=loc, count=count, format=fmt) attributes[a_name] = dict(type=a_type, size=a_size, location=loc, count=count, format=fmt)
self._attributes = attributes
if _debug_gl_shaders: if _debug_gl_shaders:
for attribute in attributes.values(): for attribute in attributes.values():
print(f"Found attribute: {attribute}") print(f"Found attribute: {attribute}")
return attributes
def _introspect_uniforms(self): def _introspect_uniforms(self):
prg_id = self._id program = self._id
uniforms = {}
for index in range(self._get_number(GL_ACTIVE_UNIFORMS)): for index in range(self._get_number(GL_ACTIVE_UNIFORMS)):
u_name, u_type, u_size = self._query_uniform(index) 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 if loc == -1: # Skip uniforms that may be inside a Uniform Block
continue continue
uniforms[u_name] = _Uniform(program, u_name, u_type, loc, self._dsa)
try: if _debug_gl_shaders:
gl_type, gl_setter, length, count = _uniform_setters[u_type] for uniform in self._uniforms.values():
gl_getter = _uniform_getters[gl_type] print(f"Found uniform: {uniform}")
if _debug_gl_shaders: return uniforms
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)
def _introspect_uniform_blocks(self): def _introspect_uniform_blocks(self):
p_id = self._id program = self._id
uniform_blocks = {} uniform_blocks = {}
for index in range(self._get_number(GL_ACTIVE_UNIFORM_BLOCKS)): for index in range(self._get_number(GL_ACTIVE_UNIFORM_BLOCKS)):
name = self._get_uniform_block_name(index) name = self._get_uniform_block_name(index)
uniform_blocks[name] = {}
num_active = GLint() num_active = GLint()
block_data_size = GLint() block_data_size = GLint()
glGetActiveUniformBlockiv(p_id, index, GL_UNIFORM_BLOCK_DATA_SIZE, block_data_size) glGetActiveUniformBlockiv(program, 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_ACTIVE_UNIFORMS, num_active)
indices = (GLuint * num_active.value)() indices = (GLuint * num_active.value)()
indices_ptr = cast(addressof(indices), POINTER(GLint)) 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): for i in range(num_active.value):
uniform_name, u_type, u_size = self._query_uniform(indices[i]) uniform_name, u_type, u_size = self._query_uniform(indices[i])
@ -420,11 +423,12 @@ class ShaderProgram:
except ValueError: except ValueError:
pass pass
gl_type, _, length, _ = _uniform_setters[u_type] gl_type, _, _, length, _ = _uniform_setters[u_type]
uniforms[i] = (uniform_name, gl_type, length)
uniform_blocks[name][i] = (uniform_name, gl_type, length) uniform_blocks[name] = UniformBlock(self, name, index, block_data_size.value, uniforms)
self._uniform_blocks[name] = UniformBlock(self, name, index, block_data_size.value, uniform_blocks[name]) return uniform_blocks
def _get_uniform_block_name(self, index): def _get_uniform_block_name(self, index):
buf_size = 128 buf_size = 128
@ -484,10 +488,10 @@ class ShaderProgram:
try: try:
if isinstance(fmt, tuple): if isinstance(fmt, tuple):
fmt, array = fmt fmt, array = fmt
initial_arrays.append((attributes[name]['location'], array)) initial_arrays.append((name, array))
attributes[name] = {**attributes[name], **{'format': fmt}} attributes[name] = {**attributes[name], **{'format': fmt}}
except KeyError: 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() batch = batch or pyglet.graphics.get_default_batch()
domain = batch.get_domain(False, mode, group, self._id, attributes) domain = batch.get_domain(False, mode, group, self._id, attributes)
@ -495,8 +499,8 @@ class ShaderProgram:
# Create vertex list and initialize # Create vertex list and initialize
vlist = domain.create(count) vlist = domain.create(count)
for index, array in initial_arrays: for name, array in initial_arrays:
vlist.set_attribute_data(index, array) vlist.set_attribute_data(name, array)
return vlist return vlist
@ -528,7 +532,7 @@ class ShaderProgram:
try: try:
if isinstance(fmt, tuple): if isinstance(fmt, tuple):
fmt, array = fmt fmt, array = fmt
initial_arrays.append((attributes[name]['location'], array)) initial_arrays.append((name, array))
attributes[name] = {**attributes[name], **{'format': fmt}} attributes[name] = {**attributes[name], **{'format': fmt}}
except KeyError: except KeyError:
raise ShaderException(f"\nThe attribute `{name}` doesn't exist. Valid names: \n{list(attributes)}") raise ShaderException(f"\nThe attribute `{name}` doesn't exist. Valid names: \n{list(attributes)}")
@ -541,8 +545,8 @@ class ShaderProgram:
start = vlist.start start = vlist.start
vlist.indices = [i + start for i in indices] vlist.indices = [i + start for i in indices]
for index, array in initial_arrays: for name, array in initial_arrays:
vlist.set_attribute_data(index, array) vlist.set_attribute_data(name, array)
return vlist return vlist
@ -578,7 +582,6 @@ class UniformBufferObject:
self.view = self._introspect_uniforms() self.view = self._introspect_uniforms()
self._view_ptr = pointer(self.view) self._view_ptr = pointer(self.view)
self.index = index self.index = index
# glUniformBlockBinding(self.block.program.id, self.block.index, self.index)
@property @property
def id(self): def id(self):

View File

@ -329,8 +329,8 @@ class VertexList:
for version in self._cache_versions: for version in self._cache_versions:
self._cache_versions[version] = None self._cache_versions[version] = None
def set_attribute_data(self, i, data): def set_attribute_data(self, name, data):
attribute = self.domain.attributes[i] attribute = self.domain.attribute_names[name]
attribute.set_region(attribute.buffer, self.start, self.count, data) attribute.set_region(attribute.buffer, self.start, self.count, data)
def __getattr__(self, name): def __getattr__(self, name):

View File

@ -81,15 +81,15 @@ class _Relation:
def _map_pair(raw_relation): def _map_pair(raw_relation):
inverted = False inverted = False
relation_string = raw_relation.split(":")[1] relation_string = raw_relation.split(":")[1]
if relation_string.startswith("+"): if "+" in relation_string:
relation_string = relation_string[1:] relation_string = relation_string.strip('+')
inverted = False inverted = False
elif relation_string.startswith("-"): elif "-" in relation_string:
relation_string = relation_string[1:] relation_string = relation_string.strip('-')
inverted = True inverted = True
if "~" in relation_string: if "~" in relation_string:
# TODO: handle this relation_string = relation_string.strip('~')
return None inverted = True
if relation_string.startswith("b"): # Button if relation_string.startswith("b"): # Button
return _Relation("button", int(relation_string[1:]), inverted) return _Relation("button", int(relation_string[1:]), inverted)
elif relation_string.startswith("a"): # Axis elif relation_string.startswith("a"): # Axis
@ -189,7 +189,6 @@ def add_mappings_from_file(filename) -> None:
`filename` : str `filename` : str
A file path. A file path.
""" """
assert os.path.exists(filename), f"Invalid path: {filename}"
with open(filename) as f: with open(filename) as f:
add_mappings_from_string(f.read()) add_mappings_from_string(f.read())

View File

@ -34,11 +34,15 @@
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
import os import os
import errno
import fcntl import fcntl
import struct
import ctypes 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 import pyglet
from pyglet.app.xlib import XlibSelectDevice from pyglet.app.xlib import XlibSelectDevice
@ -47,18 +51,11 @@ from .base import DeviceOpenException
from .evdev_constants import * from .evdev_constants import *
from .controller import get_mapping from .controller import get_mapping
c = pyglet.lib.load_library('c')
_IOC_NRBITS = 8 _IOC_NRBITS = 8
_IOC_TYPEBITS = 8 _IOC_TYPEBITS = 8
_IOC_SIZEBITS = 14 _IOC_SIZEBITS = 14
_IOC_DIRBITS = 2 _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_NRSHIFT = 0
_IOC_TYPESHIFT = (_IOC_NRSHIFT + _IOC_NRBITS) _IOC_TYPESHIFT = (_IOC_NRSHIFT + _IOC_NRBITS)
_IOC_SIZESHIFT = (_IOC_TYPESHIFT + _IOC_TYPEBITS) _IOC_SIZESHIFT = (_IOC_TYPESHIFT + _IOC_TYPEBITS)
@ -81,9 +78,7 @@ def _IOR(type, nr, struct):
def f(fileno): def f(fileno):
buffer = struct() buffer = struct()
if c.ioctl(fileno, request, ctypes.byref(buffer)) < 0: fcntl.ioctl(fileno, request, buffer)
err = ctypes.c_int.in_dll(c, 'errno').value
raise OSError(err, errno.errorcode[err])
return buffer return buffer
return f return f
@ -92,9 +87,7 @@ def _IOR(type, nr, struct):
def _IOR_len(type, nr): def _IOR_len(type, nr):
def f(fileno, buffer): def f(fileno, buffer):
request = _IOC(_IOC_READ, ord(type), nr, ctypes.sizeof(buffer)) request = _IOC(_IOC_READ, ord(type), nr, ctypes.sizeof(buffer))
if c.ioctl(fileno, request, ctypes.byref(buffer)) < 0: fcntl.ioctl(fileno, request, buffer)
err = ctypes.c_int.in_dll(c, 'errno').value
raise OSError(err, errno.errorcode[err])
return buffer return buffer
return f return f
@ -103,56 +96,154 @@ def _IOR_len(type, nr):
def _IOR_str(type, nr): def _IOR_str(type, nr):
g = _IOR_len(type, nr) g = _IOR_len(type, nr)
def f(fileno, len=256): def f(fileno, length=256):
return g(fileno, ctypes.create_string_buffer(len)).value return g(fileno, ctypes.create_string_buffer(length)).value
return f return f
time_t = ctypes.c_long def _IOW(type, nr):
suseconds_t = ctypes.c_long
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_ = ( _fields_ = (
('tv_sec', time_t), ('tv_sec', _s64),
('tv_usec', suseconds_t) ('tv_usec', _s64)
) )
class input_event(ctypes.Structure): class InputEvent(ctypes.Structure):
_fields_ = ( _fields_ = (
('time', timeval), ('time', Timeval),
('type', ctypes.c_uint16), ('type', _u16),
('code', ctypes.c_uint16), ('code', _u16),
('value', ctypes.c_int32) ('value', _s32)
) )
class input_id(ctypes.Structure): class InputID(ctypes.Structure):
_fields_ = ( _fields_ = (
('bustype', ctypes.c_uint16), ('bustype', _u16),
('vendor', ctypes.c_uint16), ('vendor', _u16),
('product', ctypes.c_uint16), ('product', _u16),
('version', ctypes.c_uint16), ('version', _u16),
) )
class input_absinfo(ctypes.Structure): class InputABSInfo(ctypes.Structure):
_fields_ = ( _fields_ = (
('value', ctypes.c_int32), ('value', _s32),
('minimum', ctypes.c_int32), ('minimum', _s32),
('maximum', ctypes.c_int32), ('maximum', _s32),
('fuzz', ctypes.c_int32), ('fuzz', _s32),
('flat', ctypes.c_int32), ('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) EVIOCGVERSION = _IOR('E', 0x01, ctypes.c_int)
EVIOCGID = _IOR('E', 0x02, input_id) EVIOCGID = _IOR('E', 0x02, InputID)
EVIOCGNAME = _IOR_str('E', 0x06) EVIOCGNAME = _IOR_str('E', 0x06)
EVIOCGPHYS = _IOR_str('E', 0x07) EVIOCGPHYS = _IOR_str('E', 0x07)
EVIOCGUNIQ = _IOR_str('E', 0x08) EVIOCGUNIQ = _IOR_str('E', 0x08)
EVIOCSFF = _IOW('E', 0x80)
def EVIOCGBIT(fileno, ev, buffer): def EVIOCGBIT(fileno, ev, buffer):
@ -160,14 +251,14 @@ def EVIOCGBIT(fileno, ev, buffer):
def EVIOCGABS(fileno, abs): def EVIOCGABS(fileno, abs):
buffer = input_absinfo() buffer = InputABSInfo()
return _IOR_len('E', 0x40 + abs)(fileno, buffer) return _IOR_len('E', 0x40 + abs)(fileno, buffer)
def get_set_bits(bytes): def get_set_bits(bytestring):
bits = set() bits = set()
j = 0 j = 0
for byte in bytes: for byte in bytestring:
for i in range(8): for i in range(8):
if byte & 1: if byte & 1:
bits.add(j + i) bits.add(j + i)
@ -208,7 +299,6 @@ def _create_control(fileno, event_type, event_code):
maximum = absinfo.maximum maximum = absinfo.maximum
control = AbsoluteAxis(name, minimum, maximum, raw_name) control = AbsoluteAxis(name, minimum, maximum, raw_name)
control.value = value control.value = value
if name == 'hat_y': if name == 'hat_y':
control.inverted = True control.inverted = True
elif event_type == EV_REL: elif event_type == EV_REL:
@ -235,6 +325,7 @@ event_types = {
EV_MSC: MSC_MAX, EV_MSC: MSC_MAX,
EV_LED: LED_MAX, EV_LED: LED_MAX,
EV_SND: SND_MAX, EV_SND: SND_MAX,
EV_FF: FF_MAX,
} }
@ -273,6 +364,7 @@ class EvdevDevice(XlibSelectDevice, Device):
self.controls = [] self.controls = []
self.control_map = {} self.control_map = {}
self.ff_types = []
event_types_bits = (ctypes.c_byte * 4)() event_types_bits = (ctypes.c_byte * 4)()
EVIOCGBIT(fileno, 0, event_types_bits) EVIOCGBIT(fileno, 0, event_types_bits)
@ -283,11 +375,14 @@ class EvdevDevice(XlibSelectDevice, Device):
nbytes = max_code // 8 + 1 nbytes = max_code // 8 + 1
event_codes_bits = (ctypes.c_byte * nbytes)() event_codes_bits = (ctypes.c_byte * nbytes)()
EVIOCGBIT(fileno, event_type, event_codes_bits) EVIOCGBIT(fileno, event_type, event_codes_bits)
for event_code in get_set_bits(event_codes_bits): if event_type == EV_FF:
control = _create_control(fileno, event_type, event_code) self.ff_types.extend(get_set_bits(event_codes_bits))
if control: else:
self.control_map[(event_type, event_code)] = control for event_code in get_set_bits(event_codes_bits):
self.controls.append(control) 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) os.close(fileno)
@ -308,7 +403,7 @@ class EvdevDevice(XlibSelectDevice, Device):
hex_product, shifted_product, hex_version, shifted_version) hex_product, shifted_product, hex_version, shifted_version)
def open(self, window=None, exclusive=False): def open(self, window=None, exclusive=False):
super(EvdevDevice, self).open(window, exclusive) super().open(window, exclusive)
try: try:
self._fileno = os.open(self._filename, os.O_RDWR | os.O_NONBLOCK) 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) pyglet.app.platform_event_loop.select_devices.add(self)
def close(self): def close(self):
super(EvdevDevice, self).close() super().close()
if not self._fileno: if not self._fileno:
return return
@ -332,33 +427,8 @@ class EvdevDevice(XlibSelectDevice, Device):
# Force Feedback methods # Force Feedback methods
def supports_ff(self): def ff_upload_effect(self, structure):
try: os.write(self._fileno, structure)
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)
# XlibSelectDevice interface # XlibSelectDevice interface
@ -372,12 +442,14 @@ class EvdevDevice(XlibSelectDevice, Device):
if not self._fileno: if not self._fileno:
return return
events = (input_event * 64)() try:
bytes_read = c.read(self._fileno, events, ctypes.sizeof(events)) events = (InputEvent * 64)()
if bytes_read < 0: bytes_read = os.readv(self._fileno, events)
except OSError:
self.close()
return return
n_events = bytes_read // ctypes.sizeof(input_event) n_events = bytes_read // ctypes.sizeof(InputEvent)
for event in events[:n_events]: for event in events[:n_events]:
try: try:
control = self.control_map[(event.type, event.code)] control = self.control_map[(event.type, event.code)]
@ -386,32 +458,54 @@ class EvdevDevice(XlibSelectDevice, Device):
pass pass
class EvdevController(Controller): class FFController(Controller):
"""Controller that supports force-feedback"""
_rumble_weak = -1 _fileno = None
_rumble_strong = -1 _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): def open(self, window=None, exclusive=False):
super().open(window, exclusive) super().open(window, exclusive)
# Create Force Feedback effects when the device is opened: self._fileno = self.device.fileno()
self._rumble_weak = self.device.ff_create_effect(0, 0, 0) # Create Force Feedback effects & events when opened:
self._rumble_strong = self.device.ff_create_effect(0, 0, 0) # 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): def rumble_play_weak(self, strength=1.0, duration=0.5):
effect = self.device.ff_create_effect(strength, 0, duration, self._rumble_weak) effect = self._weak_effect
self.device.ff_play(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): def rumble_play_strong(self, strength=1.0, duration=0.5):
effect = self.device.ff_create_effect(0, strength, duration, self._rumble_strong) effect = self._strong_effect
self.device.ff_play(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): def rumble_stop_weak(self):
"""Stop playing rumble effects on the weak motor.""" """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): def rumble_stop_strong(self):
"""Stop playing rumble effects on the strong motor.""" """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): def get_devices(display=None):
@ -470,8 +564,8 @@ def _create_controller(device):
if have_button is False or mapping is None: if have_button is False or mapping is None:
return return
if device.supports_ff(): if FF_RUMBLE in device.ff_types:
return EvdevController(device, mapping) return FFController(device, mapping)
else: else:
return Controller(device, mapping) return Controller(device, mapping)

View File

@ -36,14 +36,19 @@
"""Matrix and Vector math. """Matrix and Vector math.
This module provides Vector and Matrix objects, include Vec2, Vec3, Vec4, This module provides Vector and Matrix objects, include Vec2, Vec3, Vec4,
Mat3 and Mat4. Most common operations are supported, and many helper Mat3, and Mat4. Most common operations are supported, and many helper
methods are included for rotating, scaling, and transforming. methods are included for rotating, scaling, and transforming. The
The :py:class:`~pyglet.matrix.Mat4` includes class methods :py:class:`~pyglet.matrix.Mat4` includes class methods for creating
for creating orthographic and perspective projection matrixes. 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 math as _math
import warnings as _warnings import warnings as _warnings
from operator import mul as _mul from operator import mul as _mul
@ -52,7 +57,7 @@ def clamp(num, min_val, max_val):
class Vec2(tuple): 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: :parameters:
`x` : int or float : `x` : int or float :
@ -60,7 +65,8 @@ class Vec2(tuple):
`y` : int or float : `y` : int or float :
The Y coordinate of the vector. 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 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) return self.__add__(other)
def from_magnitude(self, magnitude): 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: :parameters:
`magnitude` : int or float : `magnitude` : int or float :
@ -172,18 +179,18 @@ class Vec2(tuple):
mag = self.__abs__() mag = self.__abs__()
return Vec2(mag * _math.cos(heading), mag * _math.sin(heading)) 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. """Limit the magnitude of the vector to the value used for the max parameter.
:parameters: :parameters:
`max` : int or float : `maximum` : int or float :
The maximum magnitude for the vector. The maximum magnitude for the vector.
:returns: Either self or a new vector with the maximum magnitude. :returns: Either self or a new vector with the maximum magnitude.
:rtype: Vec2 :rtype: Vec2
""" """
if self[0] ** 2 + self[1] ** 2 > max * max: if self[0] ** 2 + self[1] ** 2 > maximum * maximum:
return self.from_magnitude(max) return self.from_magnitude(maximum)
return self return self
def lerp(self, other, alpha): def lerp(self, other, alpha):
@ -291,7 +298,7 @@ class Vec2(tuple):
class Vec3(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: :parameters:
`x` : int or float : `x` : int or float :
@ -301,7 +308,8 @@ class Vec3(tuple):
`z` : int or float : `z` : int or float :
The Z coordinate of the vector. 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 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) return self.__add__(other)
def from_magnitude(self, magnitude): 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: :parameters:
`magnitude` : int or float : `magnitude` : int or float :
@ -385,17 +394,17 @@ class Vec3(tuple):
""" """
return self.normalize().scale(magnitude) 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. """Limit the magnitude of the vector to the value used for the max parameter.
:parameters: :parameters:
`max` : int or float : `maximum` : int or float :
The maximum magnitude for the vector. The maximum magnitude for the vector.
:returns: Either self or a new vector with the maximum magnitude. :returns: Either self or a new vector with the maximum magnitude.
:rtype: Vec3 :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.from_magnitude(max)
return self return self
@ -829,7 +838,7 @@ class Mat4(tuple):
return Mat4(temp) return Mat4(temp)
def translate(self, vector: Vec3) -> 'Mat4': 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)) 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': def rotate(self, angle: float, vector: Vec3) -> 'Mat4':

View File

@ -219,7 +219,7 @@ class SynthesisSource(Source):
def _generate_data(self, num_bytes): def _generate_data(self, num_bytes):
"""Generate `num_bytes` bytes of data. """Generate `num_bytes` bytes of data.
Return data as ctypes array or string. Return data as ctypes array or bytes.
""" """
raise NotImplementedError('abstract') raise NotImplementedError('abstract')
@ -451,6 +451,8 @@ class Encoder(SynthesisSource):
def __init__(self, duration, generator, **kwargs): def __init__(self, duration, generator, **kwargs):
super().__init__(duration, **kwargs) super().__init__(duration, **kwargs)
self._generator = generator self._generator = generator
self._total = int(duration * self.audio_format.sample_rate)
self._consumed = 0
def _generate_data(self, num_bytes): def _generate_data(self, num_bytes):
envelope = self._envelope_generator envelope = self._envelope_generator

View File

@ -81,7 +81,7 @@ instance when loading the Model::
.. versionadded:: 1.4 .. versionadded:: 1.4
""" """
from math import radians
from io import BytesIO from io import BytesIO
import pyglet import pyglet
@ -190,8 +190,7 @@ class Model:
self.vertex_lists = vertex_lists self.vertex_lists = vertex_lists
self.groups = groups self.groups = groups
self._batch = batch self._batch = batch
self._rotation = Vec3() self._modelview_matrix = Mat4()
self._translation = Vec3()
@property @property
def batch(self): def batch(self):
@ -220,24 +219,14 @@ class Model:
self._batch = batch self._batch = batch
@property @property
def rotation(self): def matrix(self):
return self._rotation return self._modelview_matrix
@rotation.setter @matrix.setter
def rotation(self, values): def matrix(self, matrix):
self._rotation = values self._modelview_matrix = matrix
for group in self.groups: for group in self.groups:
group.rotation = values group.matrix = matrix
@property
def translation(self):
return self._translation
@translation.setter
def translation(self, values):
self._translation = values
for group in self.groups:
group.translation = values
def draw(self): def draw(self):
"""Draw the model. """Draw the model.
@ -274,28 +263,11 @@ class Material:
class BaseMaterialGroup(graphics.ShaderGroup): class BaseMaterialGroup(graphics.ShaderGroup):
default_vert_src = None default_vert_src = None
default_frag_src = None default_frag_src = None
matrix = Mat4()
def __init__(self, material, program, order=0, parent=None): def __init__(self, material, program, order=0, parent=None):
super().__init__(program, order, parent) super().__init__(program, order, parent)
self.material = material 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): class TexturedMaterialGroup(BaseMaterialGroup):
@ -316,12 +288,13 @@ class TexturedMaterialGroup(BaseMaterialGroup):
mat4 view; mat4 view;
} window; } window;
uniform mat4 model;
void main() void main()
{ {
vec4 pos = window.view * vec4(vertices, 1.0); vec4 pos = window.view * model * vec4(vertices, 1.0);
gl_Position = window.projection * pos; 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_position = pos.xyz;
vertex_colors = colors; vertex_colors = colors;
@ -345,15 +318,15 @@ class TexturedMaterialGroup(BaseMaterialGroup):
} }
""" """
def __init__(self, material, texture): def __init__(self, material, program, texture, order=0, parent=None):
super().__init__(material, get_default_textured_shader()) super().__init__(material, program, order, parent)
self.texture = texture self.texture = texture
def set_state(self): def set_state(self):
gl.glActiveTexture(gl.GL_TEXTURE0) gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glBindTexture(self.texture.target, self.texture.id) gl.glBindTexture(self.texture.target, self.texture.id)
self.program.use() self.program.use()
self.set_modelview_matrix() self.program['model'] = self.matrix
def unset_state(self): def unset_state(self):
gl.glBindTexture(self.texture.target, 0) gl.glBindTexture(self.texture.target, 0)
@ -387,11 +360,13 @@ class MaterialGroup(BaseMaterialGroup):
mat4 view; mat4 view;
} window; } window;
uniform mat4 model;
void main() void main()
{ {
vec4 pos = window.view * vec4(vertices, 1.0); vec4 pos = window.view * model * vec4(vertices, 1.0);
gl_Position = window.projection * pos; 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_position = pos.xyz;
vertex_colors = colors; vertex_colors = colors;
@ -411,12 +386,9 @@ class MaterialGroup(BaseMaterialGroup):
} }
""" """
def __init__(self, material):
super().__init__(material, get_default_shader())
def set_state(self): def set_state(self):
self.program.use() self.program.use()
self.set_modelview_matrix() self.program['model'] = self.matrix
add_default_model_codecs() add_default_model_codecs()

View File

@ -245,17 +245,17 @@ class OBJModelDecoder(ModelDecoder):
material = mesh.material material = mesh.material
count = len(mesh.vertices) // 3 count = len(mesh.vertices) // 3
if material.texture_name: if material.texture_name:
texture = pyglet.resource.texture(material.texture_name)
group = TexturedMaterialGroup(material, texture)
program = pyglet.model.get_default_textured_shader() 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, vertex_lists.append(program.vertex_list(count, GL_TRIANGLES, batch, group,
vertices=('f', mesh.vertices), vertices=('f', mesh.vertices),
normals=('f', mesh.normals), normals=('f', mesh.normals),
tex_coords=('f', mesh.tex_coords), tex_coords=('f', mesh.tex_coords),
colors=('f', material.diffuse * count))) colors=('f', material.diffuse * count)))
else: else:
group = MaterialGroup(material)
program = pyglet.model.get_default_shader() program = pyglet.model.get_default_shader()
group = MaterialGroup(material, program)
vertex_lists.append(program.vertex_list(count, GL_TRIANGLES, batch, group, vertex_lists.append(program.vertex_list(count, GL_TRIANGLES, batch, group,
vertices=('f', mesh.vertices), vertices=('f', mesh.vertices),
normals=('f', mesh.normals), normals=('f', mesh.normals),