Spaces:
Runtime error
Runtime error
from ctypes import * | |
from weakref import proxy | |
import pyglet | |
from pyglet.gl import * | |
from pyglet.graphics.vertexbuffer import BufferObject | |
_debug_gl_shaders = pyglet.options['debug_gl_shaders'] | |
class ShaderException(BaseException): | |
pass | |
_c_types = { | |
GL_BYTE: c_byte, | |
GL_UNSIGNED_BYTE: c_ubyte, | |
GL_SHORT: c_short, | |
GL_UNSIGNED_SHORT: c_ushort, | |
GL_INT: c_int, | |
GL_UNSIGNED_INT: c_uint, | |
GL_FLOAT: c_float, | |
GL_DOUBLE: c_double, | |
} | |
_shader_types = { | |
'compute': GL_COMPUTE_SHADER, | |
'fragment': GL_FRAGMENT_SHADER, | |
'geometry': GL_GEOMETRY_SHADER, | |
'tesscontrol': GL_TESS_CONTROL_SHADER, | |
'tessevaluation': GL_TESS_EVALUATION_SHADER, | |
'vertex': GL_VERTEX_SHADER, | |
} | |
_uniform_getters = { | |
GLint: glGetUniformiv, | |
GLfloat: glGetUniformfv, | |
GLboolean: glGetUniformiv, | |
} | |
_uniform_setters = { | |
# 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, 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, 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, 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, glProgramUniform1iv, 1, 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, 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, | |
GL_IMAGE_1D: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1), | |
GL_IMAGE_2D: (GLint, glUniform1iv, glProgramUniform1iv, 2, 1), | |
GL_IMAGE_2D_RECT: (GLint, glUniform1iv, glProgramUniform1iv, 3, 1), | |
GL_IMAGE_3D: (GLint, glUniform1iv, glProgramUniform1iv, 3, 1), | |
GL_IMAGE_1D_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 2, 1), | |
GL_IMAGE_2D_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 3, 1), | |
GL_IMAGE_2D_MULTISAMPLE: (GLint, glUniform1iv, glProgramUniform1iv, 2, 1), | |
GL_IMAGE_2D_MULTISAMPLE_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 3, 1), | |
GL_IMAGE_BUFFER: (GLint, glUniform1iv, glProgramUniform1iv, 3, 1), | |
GL_IMAGE_CUBE: (GLint, glUniform1iv, glProgramUniform1iv, 1, 1), | |
GL_IMAGE_CUBE_MAP_ARRAY: (GLint, glUniform1iv, glProgramUniform1iv, 3, 1), | |
} | |
_attribute_types = { | |
GL_BOOL: (1, '?'), | |
GL_BOOL_VEC2: (2, '?'), | |
GL_BOOL_VEC3: (3, '?'), | |
GL_BOOL_VEC4: (4, '?'), | |
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_VEC2: (2, 'I'), | |
GL_UNSIGNED_INT_VEC3: (3, 'I'), | |
GL_UNSIGNED_INT_VEC4: (4, 'I'), | |
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_VEC2: (2, 'd'), | |
GL_DOUBLE_VEC3: (3, 'd'), | |
GL_DOUBLE_VEC4: (4, 'd'), | |
} | |
# Accessor classes: | |
class Attribute: | |
"""Abstract accessor for an attribute in a mapped buffer.""" | |
def __init__(self, name, location, count, gl_type, normalize): | |
"""Create the attribute accessor. | |
:Parameters: | |
`name` : str | |
Name of the vertex attribute. | |
`location` : int | |
Location (index) of the vertex attribute. | |
`count` : int | |
Number of components in the attribute. | |
`gl_type` : int | |
OpenGL type enumerant; for example, ``GL_FLOAT`` | |
`normalize`: bool | |
True if OpenGL should normalize the values | |
""" | |
self.name = name | |
self.location = location | |
self.count = count | |
self.gl_type = gl_type | |
self.c_type = _c_types[gl_type] | |
self.normalize = normalize | |
self.align = sizeof(self.c_type) | |
self.size = count * self.align | |
self.stride = self.size | |
def enable(self): | |
"""Enable the attribute.""" | |
glEnableVertexAttribArray(self.location) | |
def set_pointer(self, ptr): | |
"""Setup this attribute to point to the currently bound buffer at | |
the given offset. | |
``offset`` should be based on the currently bound buffer's ``ptr`` | |
member. | |
:Parameters: | |
`offset` : int | |
Pointer offset to the currently bound buffer for this | |
attribute. | |
""" | |
glVertexAttribPointer(self.location, self.count, self.gl_type, self.normalize, self.stride, ptr) | |
def get_region(self, buffer, start, count): | |
"""Map a buffer region using this attribute as an accessor. | |
The returned region consists of a contiguous array of component | |
data elements. For example, if this attribute uses 3 floats per | |
vertex, and the `count` parameter is 4, the number of floats mapped | |
will be ``3 * 4 = 12``. | |
:Parameters: | |
`buffer` : `AbstractMappable` | |
The buffer to map. | |
`start` : int | |
Offset of the first vertex to map. | |
`count` : int | |
Number of vertices to map | |
:rtype: `AbstractBufferRegion` | |
""" | |
byte_start = self.stride * start | |
byte_size = self.stride * count | |
array_count = self.count * count | |
ptr_type = POINTER(self.c_type * array_count) | |
return buffer.get_region(byte_start, byte_size, ptr_type) | |
def set_region(self, buffer, start, count, data): | |
"""Set the data over a region of the buffer. | |
:Parameters: | |
`buffer` : AbstractMappable` | |
The buffer to modify. | |
`start` : int | |
Offset of the first vertex to set. | |
`count` : int | |
Number of vertices to set. | |
`data` : A sequence of data components. | |
""" | |
byte_start = self.stride * start | |
byte_size = self.stride * count | |
array_count = self.count * count | |
data = (self.c_type * array_count)(*data) | |
buffer.set_data_region(data, byte_start, byte_size) | |
def __repr__(self): | |
return f"Attribute(name='{self.name}', location={self.location}, count={self.count})" | |
class _Uniform: | |
__slots__ = 'program', 'name', 'type', 'location', 'length', 'count', 'get', 'set' | |
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 | |
is_matrix = uniform_type in (GL_FLOAT_MAT2, GL_FLOAT_MAT2x3, GL_FLOAT_MAT2x4, | |
GL_FLOAT_MAT3, GL_FLOAT_MAT3x2, GL_FLOAT_MAT3x4, | |
GL_FLOAT_MAT4, GL_FLOAT_MAT4x2, GL_FLOAT_MAT4x3) | |
c_array = (gl_type * length)() | |
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(program, location, gl_setter, c_array, length, count, ptr, is_matrix, dsa) | |
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, location, c_array) | |
return c_array[0] | |
else: | |
def getter_func(): | |
gl_getter(program, location, c_array) | |
return c_array[:] | |
return getter_func | |
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(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 ShaderException("Uniform type not yet supported.") | |
return setter_func | |
else: | |
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 ShaderException("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})" | |
class UniformBlock: | |
__slots__ = 'program', 'name', 'index', 'size', 'uniforms', 'view_cls' | |
def __init__(self, program, name, index, size, uniforms): | |
self.program = proxy(program) | |
self.name = name | |
self.index = index | |
self.size = size | |
self.uniforms = uniforms | |
self.view_cls = None | |
def create_ubo(self, index=0): | |
""" | |
Create a new UniformBufferObject from this uniform block. | |
:Parameters: | |
`index` : int | |
The uniform buffer index the returned UBO will bind itself to. | |
By default, this is 0. | |
:rtype: :py:class:`~pyglet.graphics.shader.UniformBufferObject` | |
""" | |
if self.view_cls is None: | |
self.view_cls = self._introspect_uniforms() | |
return UniformBufferObject(self.view_cls, self.size, index) | |
def _introspect_uniforms(self): | |
"""Introspect the block's structure and return a ctypes struct for | |
manipulating the uniform block's members. | |
""" | |
p_id = self.program.id | |
index = self.index | |
active_count = len(self.uniforms) | |
# Query the uniform index order and each uniform's offset: | |
indices = (GLuint * active_count)() | |
offsets = (GLint * active_count)() | |
indices_ptr = cast(addressof(indices), POINTER(GLint)) | |
offsets_ptr = cast(addressof(offsets), POINTER(GLint)) | |
glGetActiveUniformBlockiv(p_id, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, indices_ptr) | |
glGetActiveUniformsiv(p_id, active_count, indices, GL_UNIFORM_OFFSET, offsets_ptr) | |
# Offsets may be returned in non-ascending order, sort them with the corresponding index: | |
_oi = sorted(zip(offsets, indices), key=lambda x: x[0]) | |
offsets = [x[0] for x in _oi] + [self.size] | |
indices = (GLuint * active_count)(*(x[1] for x in _oi)) | |
# # Query other uniform information: | |
# gl_types = (GLint * active_count)() | |
# mat_stride = (GLint * active_count)() | |
# gl_types_ptr = cast(addressof(gl_types), POINTER(GLint)) | |
# stride_ptr = cast(addressof(mat_stride), POINTER(GLint)) | |
# glGetActiveUniformsiv(p_id, active_count, indices, GL_UNIFORM_TYPE, gl_types_ptr) | |
# glGetActiveUniformsiv(p_id, active_count, indices, GL_UNIFORM_MATRIX_STRIDE, stride_ptr) | |
view_fields = [] | |
for i in range(active_count): | |
u_name, gl_type, length = self.uniforms[indices[i]] | |
size = offsets[i+1] - offsets[i] | |
c_type_size = sizeof(gl_type) | |
actual_size = c_type_size * length | |
padding = size - actual_size | |
# TODO: handle stride for multiple matrixes in the same UBO (crashes now) | |
# m_stride = mat_stride[i] | |
arg = (u_name, gl_type * length) if length > 1 else (u_name, gl_type) | |
view_fields.append(arg) | |
if padding > 0: | |
padding_bytes = padding // c_type_size | |
view_fields.append((f'_padding{i}', gl_type * padding_bytes)) | |
# Custom ctypes Structure for Uniform access: | |
class View(Structure): | |
_fields_ = view_fields | |
def __repr__(self): | |
return str(dict(self._fields_)) | |
return View | |
def __repr__(self): | |
return f"{self.__class__.__name__}(name={self.name}, index={self.index})" | |
class UniformBufferObject: | |
__slots__ = 'buffer', 'view', '_view_ptr', 'index' | |
def __init__(self, view_class, buffer_size, index): | |
self.buffer = BufferObject(buffer_size) | |
self.view = view_class() | |
self._view_ptr = pointer(self.view) | |
self.index = index | |
def id(self): | |
return self.buffer.id | |
def bind(self, index=None): | |
glBindBufferBase(GL_UNIFORM_BUFFER, self.index if index is None else index, self.buffer.id) | |
def read(self): | |
"""Read the byte contents of the buffer""" | |
glBindBuffer(GL_ARRAY_BUFFER, self.buffer.id) | |
ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0, self.buffer.size, GL_MAP_READ_BIT) | |
data = string_at(ptr, size=self.buffer.size) | |
glUnmapBuffer(GL_ARRAY_BUFFER) | |
return data | |
def __enter__(self): | |
# Return the view to the user in a `with` context: | |
return self.view | |
def __exit__(self, exc_type, exc_val, exc_tb): | |
self.bind() | |
self.buffer.set_data(self._view_ptr) | |
def __repr__(self): | |
return "{0}(id={1})".format(self.__class__.__name__, self.buffer.id) | |
# Utility functions: | |
def _get_number(program_id: int, variable_type: int) -> int: | |
"""Get the number of active variables of the passed GL type.""" | |
number = GLint(0) | |
glGetProgramiv(program_id, variable_type, byref(number)) | |
return number.value | |
def _query_attribute(program_id: int, index: int): | |
"""Query the name, type, and size of an Attribute by index.""" | |
asize = GLint() | |
atype = GLenum() | |
buf_size = 192 | |
aname = create_string_buffer(buf_size) | |
try: | |
glGetActiveAttrib(program_id, index, buf_size, None, asize, atype, aname) | |
return aname.value.decode(), atype.value, asize.value | |
except GLException as exc: | |
raise ShaderException from exc | |
def _introspect_attributes(program_id: int) -> dict: | |
"""Introspect a Program's Attributes, and return a dict of accessors.""" | |
attributes = {} | |
for index in range(_get_number(program_id, GL_ACTIVE_ATTRIBUTES)): | |
a_name, a_type, a_size = _query_attribute(program_id, index) | |
loc = glGetAttribLocation(program_id, 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) | |
if _debug_gl_shaders: | |
for attribute in attributes.values(): | |
print(f" Found attribute: {attribute}") | |
return attributes | |
def _link_program(*shaders) -> int: | |
"""Link one or more Shaders into a ShaderProgram.""" | |
program_id = glCreateProgram() | |
for shader in shaders: | |
glAttachShader(program_id, shader.id) | |
glLinkProgram(program_id) | |
# Check the link status of program | |
status = c_int() | |
glGetProgramiv(program_id, GL_LINK_STATUS, byref(status)) | |
if not status.value: | |
length = c_int() | |
glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, length) | |
log = c_buffer(length.value) | |
glGetProgramInfoLog(program_id, len(log), None, log) | |
raise ShaderException("Error linking shader program:\n{}".format(log.value.decode())) | |
# Shader objects no longer needed | |
for shader in shaders: | |
glDetachShader(program_id, shader.id) | |
return program_id | |
def _get_program_log(program_id: int) -> str: | |
"""Query a ShaderProgram link logs.""" | |
result = c_int(0) | |
glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, byref(result)) | |
result_str = create_string_buffer(result.value) | |
glGetProgramInfoLog(program_id, result, None, result_str) | |
if result_str.value: | |
return f"OpenGL returned the following message when linking the program: \n{result_str.value}" | |
else: | |
return f"Program '{program_id}' linked successfully." | |
def _query_uniform(program_id: int, index: int): | |
"""Query the name, type, and size of a Uniform by index.""" | |
usize = GLint() | |
utype = GLenum() | |
buf_size = 192 | |
uname = create_string_buffer(buf_size) | |
try: | |
glGetActiveUniform(program_id, index, buf_size, None, usize, utype, uname) | |
return uname.value.decode(), utype.value, usize.value | |
except GLException as exc: | |
raise ShaderException from exc | |
def _introspect_uniforms(program_id: int, have_dsa: bool) -> dict: | |
"""Introspect a Program's uniforms, and return a dict of accessors.""" | |
uniforms = {} | |
for index in range(_get_number(program_id, GL_ACTIVE_UNIFORMS)): | |
u_name, u_type, u_size = _query_uniform(program_id, index) | |
loc = glGetUniformLocation(program_id, 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_id, u_name, u_type, loc, have_dsa) | |
if _debug_gl_shaders: | |
for uniform in uniforms.values(): | |
print(f" Found uniform: {uniform}") | |
return uniforms | |
def _get_uniform_block_name(program_id: int, index: int) -> str: | |
"""Query the name of a Uniform Block, by index""" | |
buf_size = 128 | |
size = c_int(0) | |
name_buf = create_string_buffer(buf_size) | |
try: | |
glGetActiveUniformBlockName(program_id, index, buf_size, size, name_buf) | |
return name_buf.value.decode() | |
except GLException: | |
raise ShaderException(f"Unable to query UniformBlock name at index: {index}") | |
def _introspect_uniform_blocks(program) -> dict: | |
uniform_blocks = {} | |
program_id = program.id | |
for index in range(_get_number(program_id, GL_ACTIVE_UNIFORM_BLOCKS)): | |
name = _get_uniform_block_name(program_id, index) | |
num_active = GLint() | |
block_data_size = GLint() | |
glGetActiveUniformBlockiv(program_id, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, num_active) | |
glGetActiveUniformBlockiv(program_id, index, GL_UNIFORM_BLOCK_DATA_SIZE, block_data_size) | |
indices = (GLuint * num_active.value)() | |
indices_ptr = cast(addressof(indices), POINTER(GLint)) | |
glGetActiveUniformBlockiv(program_id, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, indices_ptr) | |
uniforms = {} | |
for block_uniform_index in indices: | |
uniform_name, u_type, u_size = _query_uniform(program_id, block_uniform_index) | |
# 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] | |
uniforms[block_uniform_index] = (uniform_name, gl_type, length) | |
uniform_blocks[name] = UniformBlock(program, name, index, block_data_size.value, uniforms) | |
# This might cause an error if index > GL_MAX_UNIFORM_BUFFER_BINDINGS, but surely no | |
# one would be crazy enough to use more than 36 uniform blocks, right? | |
glUniformBlockBinding(program_id, index, index) | |
if _debug_gl_shaders: | |
for block in uniform_blocks.values(): | |
print(f" Found uniform block: {block}") | |
return uniform_blocks | |
# Program definitions: | |
class ShaderSource: | |
"""GLSL source container for making source parsing simpler. | |
We support locating out attributes and applying #defines values. | |
NOTE: We do assume the source is neat enough to be parsed | |
this way and don't contain several statements in one line. | |
""" | |
def __init__(self, source: str, source_type: GLenum): | |
"""Create a shader source wrapper.""" | |
self._lines = source.strip().splitlines() | |
self._type = source_type | |
if not self._lines: | |
raise ShaderException("Shader source is empty") | |
self._version = self._find_glsl_version() | |
if pyglet.gl.current_context.get_info().get_opengl_api() == "gles": | |
self._lines[0] = "#version 310 es" | |
self._lines.insert(1, "precision mediump float;") | |
if self._type == GL_GEOMETRY_SHADER: | |
self._lines.insert(1, "#extension GL_EXT_geometry_shader : require") | |
if self._type == GL_COMPUTE_SHADER: | |
self._lines.insert(1, "precision mediump image2D;") | |
self._version = self._find_glsl_version() | |
def validate(self) -> str: | |
"""Return the validated shader source.""" | |
return "\n".join(self._lines) | |
def _find_glsl_version(self) -> int: | |
if self._lines[0].strip().startswith("#version"): | |
try: | |
return int(self._lines[0].split()[1]) | |
except (ValueError, IndexError): | |
pass | |
source = "\n".join(f"{str(i+1).zfill(3)}: {line} " for i, line in enumerate(self._lines)) | |
raise ShaderException(("Cannot find #version flag in shader source. " | |
"A #version statement is required on the first line.\n" | |
"------------------------------------\n" | |
f"{source}")) | |
class Shader: | |
"""OpenGL shader. | |
Shader objects are compiled on instantiation. | |
You can reuse a Shader object in multiple ShaderPrograms. | |
`shader_type` is one of ``'compute'``, ``'fragment'``, ``'geometry'``, | |
``'tesscontrol'``, ``'tessevaluation'``, or ``'vertex'``. | |
""" | |
def __init__(self, source_string: str, shader_type: str): | |
self._id = None | |
self.type = shader_type | |
try: | |
shader_type = _shader_types[shader_type] | |
except KeyError as err: | |
raise ShaderException(f"shader_type '{shader_type}' is invalid." | |
f"Valid types are: {list(_shader_types)}") from err | |
source_string = ShaderSource(source_string, shader_type).validate() | |
shader_source_utf8 = source_string.encode("utf8") | |
source_buffer_pointer = cast(c_char_p(shader_source_utf8), POINTER(c_char)) | |
source_length = c_int(len(shader_source_utf8)) | |
shader_id = glCreateShader(shader_type) | |
glShaderSource(shader_id, 1, byref(source_buffer_pointer), source_length) | |
glCompileShader(shader_id) | |
status = c_int(0) | |
glGetShaderiv(shader_id, GL_COMPILE_STATUS, byref(status)) | |
if status.value != GL_TRUE: | |
source = self._get_shader_source(shader_id) | |
source_lines = "{0}".format("\n".join(f"{str(i+1).zfill(3)}: {line} " | |
for i, line in enumerate(source.split("\n")))) | |
raise ShaderException(f"Shader compilation failed.\n" | |
f"{self._get_shader_log(shader_id)}" | |
"------------------------------------------------------------\n" | |
f"{source_lines}\n" | |
"------------------------------------------------------------") | |
elif _debug_gl_shaders: | |
print(self._get_shader_log(shader_id)) | |
self._id = shader_id | |
def id(self): | |
return self._id | |
def _get_shader_log(self, shader_id): | |
log_length = c_int(0) | |
glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, byref(log_length)) | |
result_str = create_string_buffer(log_length.value) | |
glGetShaderInfoLog(shader_id, log_length, None, result_str) | |
if result_str.value: | |
return ("OpenGL returned the following message when compiling the '{0}' shader: " | |
"\n{1}".format(self.type, result_str.value.decode('utf8'))) | |
else: | |
return f"{self.type.capitalize()} Shader '{shader_id}' compiled successfully." | |
def _get_shader_source(shader_id): | |
"""Get the shader source from the shader object""" | |
source_length = c_int(0) | |
glGetShaderiv(shader_id, GL_SHADER_SOURCE_LENGTH, source_length) | |
source_str = create_string_buffer(source_length.value) | |
glGetShaderSource(shader_id, source_length, None, source_str) | |
return source_str.value.decode('utf8') | |
def __del__(self): | |
try: | |
glDeleteShader(self._id) | |
if _debug_gl_shaders: | |
print(f"Destroyed {self.type} Shader '{self._id}'") | |
except Exception: | |
# Interpreter is shutting down, | |
# or Shader failed to compile. | |
pass | |
def __repr__(self): | |
return "{0}(id={1}, type={2})".format(self.__class__.__name__, self.id, self.type) | |
class ShaderProgram: | |
"""OpenGL shader program.""" | |
__slots__ = '_id', '_context', '_attributes', '_uniforms', '_uniform_blocks', '__weakref__' | |
def __init__(self, *shaders: Shader): | |
assert shaders, "At least one Shader object is required." | |
self._id = _link_program(*shaders) | |
self._context = pyglet.gl.current_context | |
if _debug_gl_shaders: | |
print(_get_program_log(self._id)) | |
# Query if Direct State Access is available: | |
have_dsa = gl_info.have_version(4, 1) or gl_info.have_extension("GL_ARB_separate_shader_objects") | |
self._attributes = _introspect_attributes(self._id) | |
self._uniforms = _introspect_uniforms(self._id, have_dsa) | |
self._uniform_blocks = _introspect_uniform_blocks(self) | |
def id(self): | |
return self._id | |
def attributes(self): | |
return self._attributes | |
def uniforms(self): | |
return self._uniforms | |
def uniform_blocks(self): | |
return self._uniform_blocks | |
def use(self): | |
glUseProgram(self._id) | |
def stop(): | |
glUseProgram(0) | |
__enter__ = use | |
bind = use | |
unbind = stop | |
def __exit__(self, *_): | |
glUseProgram(0) | |
def __del__(self): | |
try: | |
self._context.delete_shader_program(self.id) | |
except Exception: | |
# Interpreter is shutting down, | |
# or ShaderProgram failed to link. | |
pass | |
def __setitem__(self, key, value): | |
try: | |
uniform = self._uniforms[key] | |
except KeyError as err: | |
raise ShaderException(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.") from err | |
try: | |
uniform.set(value) | |
except GLException as err: | |
raise ShaderException from err | |
def __getitem__(self, item): | |
try: | |
uniform = self._uniforms[item] | |
except KeyError as err: | |
raise ShaderException(f"A Uniform with the name `{item}` 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.") from err | |
try: | |
return uniform.get() | |
except GLException as err: | |
raise ShaderException from err | |
def vertex_list(self, count, mode, batch=None, group=None, **data): | |
"""Create a VertexList. | |
:Parameters: | |
`count` : int | |
The number of vertices in the list. | |
`mode` : int | |
OpenGL drawing mode enumeration; for example, one of | |
``GL_POINTS``, ``GL_LINES``, ``GL_TRIANGLES``, etc. | |
This determines how the list is drawn in the given batch. | |
`batch` : `~pyglet.graphics.Batch` | |
Batch to add the VertexList to, or ``None`` if a Batch will not be used. | |
Using a Batch is strongly recommended. | |
`group` : `~pyglet.graphics.Group` | |
Group to add the VertexList to, or ``None`` if no group is required. | |
`**data` : str or tuple | |
Attribute formats and initial data for the vertex list. | |
:rtype: :py:class:`~pyglet.graphics.vertexdomain.VertexList` | |
""" | |
attributes = self._attributes.copy() | |
initial_arrays = [] | |
for name, fmt in data.items(): | |
try: | |
if isinstance(fmt, tuple): | |
fmt, array = fmt | |
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)}") | |
batch = batch or pyglet.graphics.get_default_batch() | |
domain = batch.get_domain(False, mode, group, self, attributes) | |
# Create vertex list and initialize | |
vlist = domain.create(count) | |
for name, array in initial_arrays: | |
vlist.set_attribute_data(name, array) | |
return vlist | |
def vertex_list_indexed(self, count, mode, indices, batch=None, group=None, **data): | |
"""Create a IndexedVertexList. | |
:Parameters: | |
`count` : int | |
The number of vertices in the list. | |
`mode` : int | |
OpenGL drawing mode enumeration; for example, one of | |
``GL_POINTS``, ``GL_LINES``, ``GL_TRIANGLES``, etc. | |
This determines how the list is drawn in the given batch. | |
`indices` : sequence of int | |
Sequence of integers giving indices into the vertex list. | |
`batch` : `~pyglet.graphics.Batch` | |
Batch to add the VertexList to, or ``None`` if a Batch will not be used. | |
Using a Batch is strongly recommended. | |
`group` : `~pyglet.graphics.Group` | |
Group to add the VertexList to, or ``None`` if no group is required. | |
`**data` : str or tuple | |
Attribute formats and initial data for the vertex list. | |
:rtype: :py:class:`~pyglet.graphics.vertexdomain.IndexedVertexList` | |
""" | |
attributes = self._attributes.copy() | |
initial_arrays = [] | |
for name, fmt in data.items(): | |
try: | |
if isinstance(fmt, tuple): | |
fmt, array = fmt | |
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)}") | |
batch = batch or pyglet.graphics.get_default_batch() | |
domain = batch.get_domain(True, mode, group, self, attributes) | |
# Create vertex list and initialize | |
vlist = domain.create(count, len(indices)) | |
start = vlist.start | |
vlist.indices = [i + start for i in indices] | |
for name, array in initial_arrays: | |
vlist.set_attribute_data(name, array) | |
return vlist | |
def __repr__(self): | |
return "{0}(id={1})".format(self.__class__.__name__, self.id) | |
class ComputeShaderProgram: | |
"""OpenGL Compute Shader Program""" | |
__slots__ = '_shader', '_id', '_context', '_uniforms', '_uniform_blocks', '__weakref__', 'limits' | |
def __init__(self, source: str): | |
"""Create an OpenGL ComputeShaderProgram from source.""" | |
if not (gl_info.have_version(4, 3) or gl_info.have_extension("GL_ARB_compute_shader")): | |
raise ShaderException("Compute Shader not supported. OpenGL Context version must be at least " | |
"4.3 or higher, or 4.2 with the 'GL_ARB_compute_shader' extension.") | |
self._shader = Shader(source, 'compute') | |
self._context = pyglet.gl.current_context | |
self._id = _link_program(self._shader) | |
if _debug_gl_shaders: | |
print(_get_program_log(self._id)) | |
self._uniforms = _introspect_uniforms(self._id, True) | |
self._uniform_blocks = _introspect_uniform_blocks(self) | |
self.limits = { | |
'work_group_count': self._get_tuple(GL_MAX_COMPUTE_WORK_GROUP_COUNT), | |
'work_group_size': self._get_tuple(GL_MAX_COMPUTE_WORK_GROUP_SIZE), | |
'work_group_invocations': self._get_value(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS), | |
'shared_memory_size': self._get_value(GL_MAX_COMPUTE_SHARED_MEMORY_SIZE), | |
} | |
def _get_tuple(parameter: int): | |
val_x = GLint() | |
val_y = GLint() | |
val_z = GLint() | |
for i, value in enumerate((val_x, val_y, val_z)): | |
glGetIntegeri_v(parameter, i, byref(value)) | |
return val_x.value, val_y.value, val_z.value | |
def _get_value(parameter: int) -> int: | |
val = GLint() | |
glGetIntegerv(parameter, byref(val)) | |
return val.value | |
def dispatch(x: int = 1, y: int = 1, z: int = 1, barrier: int = GL_ALL_BARRIER_BITS) -> None: | |
"""Launch one or more compute work groups. | |
The ComputeShaderProgram should be active (bound) before calling | |
this method. The x, y, and z parameters specify the number of local | |
work groups that will be dispatched in the X, Y and Z dimensions. | |
""" | |
glDispatchCompute(x, y, z) | |
if barrier: | |
glMemoryBarrier(barrier) | |
def id(self) -> int: | |
return self._id | |
def uniforms(self) -> dict: | |
return self._uniforms | |
def uniform_blocks(self) -> dict: | |
return self._uniform_blocks | |
def use(self) -> None: | |
glUseProgram(self._id) | |
def stop(): | |
glUseProgram(0) | |
__enter__ = use | |
bind = use | |
unbind = stop | |
def __exit__(self, *_): | |
glUseProgram(0) | |
def __del__(self): | |
try: | |
self._context.delete_shader_program(self.id) | |
except Exception: | |
# Interpreter is shutting down, | |
# or ShaderProgram failed to link. | |
pass | |
def __setitem__(self, key, value): | |
try: | |
uniform = self._uniforms[key] | |
except KeyError as err: | |
raise ShaderException(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.") from err | |
try: | |
uniform.set(value) | |
except GLException as err: | |
raise ShaderException from err | |
def __getitem__(self, item): | |
try: | |
uniform = self._uniforms[item] | |
except KeyError as err: | |
raise ShaderException(f"A Uniform with the name `{item}` 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.") from err | |
try: | |
return uniform.get() | |
except GLException as err: | |
raise ShaderException from err | |