def __init__(self, block, index): assert type(block) == UniformBlock, "Must be a UniformBlock instance" self.block = block self.buffer = BufferObject(self.block.size, GL_UNIFORM_BUFFER) self.buffer.bind() self.view = self._introspect_uniforms() self._view_ptr = pointer(self.view) self.index = index
def __init__(self, program, attribute_meta, index_gl_type=GL_UNSIGNED_INT): super(IndexedVertexDomain, self).__init__(program, attribute_meta) self.index_allocator = allocation.Allocator(self._initial_index_count) self.index_gl_type = index_gl_type self.index_c_type = vertexattribute._c_types[index_gl_type] self.index_element_size = ctypes.sizeof(self.index_c_type) self.index_buffer = BufferObject( self.index_allocator.capacity * self.index_element_size, GL_ELEMENT_ARRAY_BUFFER)
def draw(size, mode, **kwargs): """Draw a primitive immediately. :Parameters: `size` : int Number of vertices given `mode` : gl primitive type OpenGL drawing mode, e.g. ``GL_TRIANGLES``, avoiding quotes. `**data` : keyword arguments for passing vertex attribute data. The keyword should be the vertex attribute name, and the argument should be a tuple of (format, data). For example: `position=('f', array)` """ # Create and bind a throwaway VAO vao_id = GLuint() glGenVertexArrays(1, vao_id) glBindVertexArray(vao_id) # Activate shader program: program = get_default_shader() program.use() buffers = [] for name, (fmt, array) in kwargs.items(): location = program.attributes[name]['location'] count = program.attributes[name]['count'] gl_type = vertexdomain._gl_types[fmt[0]] normalize = 'n' in fmt attribute = vertexattribute.VertexAttribute(name, location, count, gl_type, normalize) assert size == len( array) // attribute.count, 'Data for %s is incorrect length' % fmt buffer = BufferObject(size * attribute.stride, GL_ARRAY_BUFFER) attribute.set_region(buffer, 0, size, array) attribute.enable() attribute.set_pointer(buffer.ptr) buffers.append(buffer) # Don't garbage collect it. glDrawArrays(mode, 0, size) # Deactivate shader program: program.stop() # Discard everything after drawing: del buffers glBindVertexArray(0) glDeleteVertexArrays(1, vao_id)
def draw_indexed(size, mode, indices, **data): """Draw a primitive with indexed vertices immediately. :Parameters: `size` : int Number of vertices given `mode` : int OpenGL drawing mode, e.g. ``GL_TRIANGLES`` `indices` : sequence of int Sequence of integers giving indices into the vertex list. `**data` : keyword arguments for passing vertex attribute data. The keyword should be the vertex attribute name, and the argument should be a tuple of (format, data). For example: `position=('f', array)` """ # Create and bind a throwaway VAO vao_id = GLuint() glGenVertexArrays(1, vao_id) glBindVertexArray(vao_id) # Activate shader program: program = get_default_shader() program.use() buffers = [] for name, (fmt, array) in data.items(): location = program.attributes[name]['location'] count = program.attributes[name]['count'] gl_type = vertexdomain._gl_types[fmt[0]] normalize = 'n' in fmt attribute = vertexattribute.VertexAttribute(name, location, count, gl_type, normalize) assert size == len(array) // attribute.count, 'Data for %s is incorrect length' % fmt buffer = BufferObject(size * attribute.stride, GL_ARRAY_BUFFER) attribute.set_region(buffer, 0, size, array) attribute.enable() attribute.set_pointer(buffer.ptr) buffers.append(buffer) if size <= 0xff: index_type = GL_UNSIGNED_BYTE index_c_type = ctypes.c_ubyte elif size <= 0xffff: index_type = GL_UNSIGNED_SHORT index_c_type = ctypes.c_ushort else: index_type = GL_UNSIGNED_INT index_c_type = ctypes.c_uint # With GL 3.3 vertex arrays indices needs to be in a buffer # bound to the ELEMENT_ARRAY slot index_array = (index_c_type * len(indices))(*indices) index_buffer = BufferObject(ctypes.sizeof(index_array), GL_ELEMENT_ARRAY_BUFFER) index_buffer.set_data(index_array) glDrawElements(mode, len(indices), index_type, 0) glFlush() # Deactivate shader program: program.stop() # Discard everything after drawing: del buffers del index_buffer glBindVertexArray(0) glDeleteVertexArrays(1, vao_id)
class UniformBufferObject: __slots__ = 'block', 'buffer', 'view', '_view', '_view_ptr', 'index' def __init__(self, block, index): assert type(block) == UniformBlock, "Must be a UniformBlock instance" self.block = block self.buffer = BufferObject(self.block.size, GL_UNIFORM_BUFFER) self.buffer.bind() 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): return self.buffer.id def _introspect_uniforms(self): p_id = self.block.program.id index = self.block.index # Query the number of active Uniforms: num_active = GLint() glGetActiveUniformBlockiv(p_id, index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, num_active) # Query the uniform index order and each uniform's offset: indices = (GLuint * num_active.value)() offsets = (GLint * num_active.value)() 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, num_active.value, 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.block.size] indices = (GLuint * num_active.value)(*(x[1] for x in _oi)) # Query other uniform information: gl_types = (GLint * num_active.value)() mat_stride = (GLint * num_active.value)() gl_types_ptr = cast(addressof(gl_types), POINTER(GLint)) stride_ptr = cast(addressof(mat_stride), POINTER(GLint)) glGetActiveUniformsiv(p_id, num_active.value, indices, GL_UNIFORM_TYPE, gl_types_ptr) glGetActiveUniformsiv(p_id, num_active.value, indices, GL_UNIFORM_MATRIX_STRIDE, stride_ptr) args = [] for i in range(num_active.value): u_name, gl_type, length = self.block.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) args.append(arg) if padding > 0: padding_bytes = padding // c_type_size args.append((f'_padding{i}', gl_type * padding_bytes)) # Custom ctypes Structure for Uniform access: class View(Structure): _fields_ = args __repr__ = lambda self: str(dict(self._fields_)) return View() def bind(self, index=None): glUniformBlockBinding(self.block.program.id, self.block.index, index or self.index) glBindBufferBase(GL_UNIFORM_BUFFER, index or self.index, self.buffer.id) def read(self): """Read the byte contents of the buffer""" glBindBuffer(GL_UNIFORM_BUFFER, self.buffer.id) ptr = glMapBufferRange(GL_UNIFORM_BUFFER, 0, self.buffer.size, GL_MAP_READ_BIT) data = string_at(ptr, size=self.buffer.size) glUnmapBuffer(GL_UNIFORM_BUFFER) return data def __enter__(self): # Return the view to the user in a `with` context: glUniformBlockBinding(self.block.program.id, self.block.index, self.index) glBindBufferBase(GL_UNIFORM_BUFFER, self.index, self.buffer.id) return self.view def __exit__(self, exc_type, exc_val, exc_tb): self.buffer.set_data(self._view_ptr) def __repr__(self): return "{0}(id={1})".format(self.block.name + 'Buffer', self.buffer.id)
class IndexedVertexDomain(VertexDomain): """Management of a set of indexed vertex lists. Construction of an indexed vertex domain is usually done with the :py:func:`create_domain` function. """ _initial_index_count = 16 def __init__(self, program, attribute_meta, index_gl_type=GL_UNSIGNED_INT): super(IndexedVertexDomain, self).__init__(program, attribute_meta) self.index_allocator = allocation.Allocator(self._initial_index_count) self.index_gl_type = index_gl_type self.index_c_type = vertexattribute._c_types[index_gl_type] self.index_element_size = ctypes.sizeof(self.index_c_type) self.index_buffer = BufferObject( self.index_allocator.capacity * self.index_element_size, GL_ELEMENT_ARRAY_BUFFER) def safe_index_alloc(self, count): """Allocate indices, resizing the buffers if necessary.""" try: return self.index_allocator.alloc(count) except allocation.AllocatorMemoryException as e: capacity = _nearest_pow2(e.requested_capacity) self.version += 1 self.index_buffer.resize(capacity * self.index_element_size) self.index_allocator.set_capacity(capacity) return self.index_allocator.alloc(count) def safe_index_realloc(self, start, count, new_count): """Reallocate indices, resizing the buffers if necessary.""" try: return self.index_allocator.realloc(start, count, new_count) except allocation.AllocatorMemoryException as e: capacity = _nearest_pow2(e.requested_capacity) self.version += 1 self.index_buffer.resize(capacity * self.index_element_size) self.index_allocator.set_capacity(capacity) return self.index_allocator.realloc(start, count, new_count) def create(self, count, index_count): """Create an :py:class:`IndexedVertexList` in this domain. :Parameters: `count` : int Number of vertices to create `index_count` Number of indices to create """ start = self.safe_alloc(count) index_start = self.safe_index_alloc(index_count) return IndexedVertexList(self, start, count, index_start, index_count) def get_index_region(self, start, count): """Get a data from a region of the index buffer. :Parameters: `start` : int Start of the region to map. `count` : int Number of indices to map. :rtype: Array of int """ byte_start = self.index_element_size * start byte_count = self.index_element_size * count ptr_type = ctypes.POINTER(self.index_c_type * count) map_ptr = self.index_buffer.map_range(byte_start, byte_count, ptr_type) data = map_ptr[:] self.index_buffer.unmap() return data def set_index_region(self, start, count, data): byte_start = self.index_element_size * start byte_count = self.index_element_size * count ptr_type = ctypes.POINTER(self.index_c_type * count) map_ptr = self.index_buffer.map_range(byte_start, byte_count, ptr_type) map_ptr[:] = data self.index_buffer.unmap() def draw(self, mode): """Draw all vertices in the domain. All vertices in the domain are drawn at once. This is the most efficient way to render primitives. :Parameters: `mode` : int OpenGL drawing mode, e.g. ``GL_POINTS``, ``GL_LINES``, etc. """ self.vao.bind() for buffer, attributes in self.buffer_attributes: buffer.bind() for attribute in attributes: attribute.enable() attribute.set_pointer(attribute.buffer.ptr) self.index_buffer.bind() starts, sizes = self.index_allocator.get_allocated_regions() primcount = len(starts) if primcount == 0: pass elif primcount == 1: # Common case glDrawElements( mode, sizes[0], self.index_gl_type, self.index_buffer.ptr + starts[0] * self.index_element_size) else: starts = [ s * self.index_element_size + self.index_buffer.ptr for s in starts ] starts = (ctypes.POINTER(GLvoid) * primcount)(*(GLintptr * primcount)(*starts)) sizes = (GLsizei * primcount)(*sizes) glMultiDrawElements(mode, sizes, self.index_gl_type, starts, primcount) self.index_buffer.unbind() for buffer, _ in self.buffer_attributes: buffer.unbind() def draw_subset(self, mode, vertex_list): """Draw a specific IndexedVertexList in the domain. The `vertex_list` parameter specifies a :py:class:`IndexedVertexList` to draw. Only primitives in that list will be drawn. :Parameters: `mode` : int OpenGL drawing mode, e.g. ``GL_POINTS``, ``GL_LINES``, etc. `vertex_list` : `IndexedVertexList` Vertex list to draw. """ self.vao.bind() for buffer, attributes in self.buffer_attributes: buffer.bind() for attribute in attributes: attribute.enable() attribute.set_pointer(attribute.buffer.ptr) self.index_buffer.bind() glDrawElements( mode, vertex_list.index_count, self.index_gl_type, self.index_buffer.ptr + vertex_list.index_start * self.index_element_size) self.index_buffer.unbind() for buffer, _ in self.buffer_attributes: buffer.unbind()