def instance(self, program: moderngl.Program) -> moderngl.VertexArray: """ Obtain the ``moderngl.VertexArray`` instance for the program. The instance is only created once and cached internally. Returns: ``moderngl.VertexArray`` instance """ vao = self.vaos.get(program.glo) if vao: return vao program_attributes = [ name for name, attr in program._members.items() if isinstance(attr, moderngl.Attribute) ] # Make sure all attributes are covered for attrib_name in program_attributes: # Ignore built in attributes for now if attrib_name.startswith('gl_'): continue # Do we have a buffer mapping to this attribute? if not sum( buffer.has_attribute(attrib_name) for buffer in self.buffers): raise VAOError( "VAO {} doesn't have attribute {} for program {}".format( self.name, attrib_name, program.name)) vao_content = [] # Pick out the attributes we can actually map for buffer in self.buffers: content = buffer.content(program_attributes) if content: vao_content.append(content) # Any attribute left is not accounted for if program_attributes: for attrib_name in program_attributes: if attrib_name.startswith('gl_'): continue raise VAOError("Did not find a buffer mapping for {}".format( [n for n in program_attributes])) # Create the vao if self._index_buffer: vao = context.ctx().vertex_array(program, vao_content, self._index_buffer, self._index_element_size) else: vao = context.ctx().vertex_array(program, vao_content) self.vaos[program.glo] = vao return vao
def _create_vao_instance(self, shader): """ Create a VAO based on the shader's attribute specification. This is called by ``bind(shader)`` and should not be messed with unless you are absolutely sure about what you are doing. :param shader: The shader we are generating the combo for :return: A new VAOCombo object with the correct attribute binding """ # Return the combo if already generated vao = self.vaos.get(shader.vao_key) if vao: return vao # Make sure all attributes are covered for attrib in shader.attribute_list: if attrib.name in SYSTEM_ATTRIBS: continue if not sum(b.has_attribute(attrib.name) for b in self.buffers): raise VAOError( "VAO {} doesn't have attribute {} for program {}".format( self.name, attrib.name, shader.name)) attributes = [a.name for a in shader.attribute_list] vao_content = [] for buffer in self.buffers: content = buffer.content(attributes) if content: vao_content.append(content) if len(attributes) > 0: for attrib in attributes: if attrib not in SYSTEM_ATTRIBS: raise VAOError( "Did not find a buffer mapping for {}".format( [n for n in attributes])) if self._index_buffer: vao = context.ctx().vertex_array(shader.program, vao_content, self._index_buffer, self._index_element_size) else: vao = context.ctx().vertex_array(shader.program, vao_content) self.vaos[shader.vao_key] = vao return vao
def _init_texture2d_draw(self): """Initialize geometry and shader for drawing FBO layers""" if not TextureHelper._quad: TextureHelper._quad = geometry.quad_fs() # Shader for drawing color layers TextureHelper._texture2d_shader = context.ctx().program( vertex_shader=""" #version 330 in vec3 in_position; in vec2 in_uv; out vec2 uv; uniform vec2 offset; uniform vec2 scale; void main() { uv = in_uv; gl_Position = vec4((in_position.xy + vec2(1.0, 1.0)) * scale + offset, 0.0, 1.0); } """, fragment_shader=""" #version 330 out vec4 out_color; in vec2 uv; uniform sampler2D texture0; void main() { out_color = texture(texture0, uv); } """) TextureHelper._texture2d_sampler = self.ctx.sampler( filter=(moderngl.LINEAR, moderngl.LINEAR), )
class DemosysTestCase(TestCase): window = context.window() ctx = context.ctx() def create_shader(self, source=None, path=None): """ Create a shader from source or file """ program = ShaderProgram(name="test", path=path) if source: program.set_source(source) program.prepare() if path: resources.shaders.load_shader(program) return program def get_texture(self, path): return resources.textures.get(path, create=True) def get_texture_array(self, path, layers=0): return resources.textures.get(path, create=True, cls=TextureArray, layers=layers) def get_track(self, name): return resources.tracks.get(name)
def __init__(self, path=None, name=None): """ Create a shader using either a file path or a name :param path: Full file path to the shader :param name: Name of the shader (debug purposes) """ self.ctx = context.ctx() if not path and not name: raise ShaderError("Shader must have a path or a name") self.path = path if not name: self.name = os.path.basename(path) else: self.name = name self._vertex_source = None self._fragment_source = None self._geometry_source = None self.program = None # Shader inputs self.uniform_map = {} self.attribute_list = [] self.attribute_map = {} # A string of concatenated attribute names self.attribute_key = None # Unique key for VAO instances containing shader id and attributes self.vao_key = None
def create(size, components=4, depth=False, dtype='f1', layers=1) -> 'FBO': """ Create a single or multi layer FBO :param size: (tuple) with and height :param components: (tuple) number of components. 1, 2, 3, 4 :param depth: (bool) Create a depth attachment :param dtype: (string) data type per r, g, b, a ... :param layers: (int) number of color attachments :return: A new FBO """ instance = FBO() # Add N layers of color attachments for _ in range(layers): tex = Texture2D.create(size, components, dtype=dtype) instance.color_buffers.append(tex) # Set depth attachment is specified if depth: instance.depth_buffer = DepthTexture(size) instance.fbo = context.ctx().framebuffer( color_attachments=[b.mglo for b in instance.color_buffers], depth_attachment=instance.depth_buffer.mglo if instance.depth_buffer is not None else None) return instance
class DemosysTestCase(TestCase): window = context.window() ctx = context.ctx() project = project.instance timeline = None apply_mocks() def load_program(self, path): return resources.programs.load( ProgramDescription(label=path, path=path)) def load_texture(self, path): return resources.textures.load( TextureDescription(label=path, path=path)) def load_texture_array(self, path, layers=0): return resources.textures.load( TextureDescription(label=path, path=path, loader='array', layers=layers)) def load_scene(self, path): return resources.scenes.load(SceneDescription(label=path, path=path)) def load_data(self, path, loader=None): return resources.data.load( DataDescription(label=path, path=path, loader=loader)) def get_track(self, name): return resources.tracks.get(name)
def create(cls, size, components=4, data=None, alignment=1, dtype='f1', mipmap=False) -> 'TextureArray': """ :param size: (x, y, layers) size and layers of the texture :param components: The number of components 1, 2, 3 or 4 :param data: (bytes) Content of the texture :param alignment: The byte alignment 1, 2, 4 or 8 :param dtype: (str) The data type :param mipmap: (bool) Generate mipmaps """ texture = TextureArray("create", mipmap=False, layers=size[2]) texture.mglo = context.ctx().texture_array( size, components, data=data, alignment=alignment, dtype=dtype, ) if mipmap: texture.build_mipmaps() return texture
def load(self, materials): name_map = { 'POSITION': 'in_position', 'NORMAL': 'in_normal', 'TEXCOORD_0': 'in_uv', 'TANGENT': 'in_tangent', 'JOINTS_0': 'in_joints', 'WEIGHTS_0': 'in_heights', 'COLOR_0': 'in_color0', } meshes = [] # Read all primitives as separate meshes for now # According to the spec they can have different materials and vertex format for primitive in self.primitives: vao = VAO(self.name, mode=primitive.mode or moderngl.TRIANGLES) # Index buffer component_type, index_vbo = self.load_indices(primitive) if index_vbo is not None: vao.index_buffer(context.ctx().buffer(index_vbo.tobytes()), index_element_size=component_type.size) attributes = {} vbos = self.prepare_attrib_mapping(primitive) for vbo_info in vbos: dtype, buffer = vbo_info.create() vao.buffer( buffer, " ".join([ "{}{}".format(attr[1], DTYPE_BUFFER_TYPE[dtype]) for attr in vbo_info.attributes ]), [name_map[attr[0]] for attr in vbo_info.attributes], ) for attr in vbo_info.attributes: attributes[attr[0]] = { 'name': name_map[attr[0]], 'components': attr[1], 'type': vbo_info.component_type.value, } bbox_min, bbox_max = self.get_bbox(primitive) meshes.append( Mesh( self.name, vao=vao, attributes=attributes, material=materials[primitive.material] if primitive.material is not None else None, bbox_min=bbox_min, bbox_max=bbox_max, )) return meshes
def __init__(self, width, height, gbuffer=None, lightbuffer=None): self.ctx = context.ctx() self.width = width self.height = height self.size = (width, height) self.depth_sampler = samplers.create(texture_compare_mode=False, min_filter=moderngl.LINEAR, mag_filter=moderngl.LINEAR) # FBOs self.gbuffer = gbuffer self.lightbuffer = lightbuffer # Light Info self.point_lights = [] # Create geometry buffer if not supplied depth_buffer = DepthTexture(self.size) if not self.gbuffer: self.gbuffer = FBO.create_from_textures( [ Texture2D.create(self.size, 4, dtype='f1'), Texture2D.create(self.size, 3, dtype='f2'), ], depth_buffer=depth_buffer, ) if not self.lightbuffer: self.lightbuffer = FBO.create_from_textures( [Texture2D.create(self.size, 4)], # depth_buffer=depth_buffer, ) # Unit cube for point lights (cube with radius 1.0) self.unit_cube = geometry.cube(width=2, height=2, depth=2) self.point_light_shader = resources.shaders.get( "deferred/light_point.glsl", create=True) # Debug draw lights self.debug_shader = resources.shaders.get("deferred/debug.glsl", create=True) # Combine shader self.combine_shader = resources.shaders.get("deferred/combine.glsl", create=True) self.quad = geometry.quad_fs()
def index_buffer(self, buffer, index_element_size=4): """ Set the index buffer for this VAO :param buffer: Buffer object or ndarray :param index_element_size: Byte size of each element. 1, 2 or 4 """ if not isinstance(buffer, moderngl.Buffer) and not isinstance( buffer, numpy.ndarray): raise VAOError( "buffer parameter must be a moderngl.Buffer or numpy.ndarray instance" ) if isinstance(buffer, numpy.ndarray): buffer = context.ctx().buffer(buffer.tobytes()) self._index_buffer = buffer self._index_element_size = index_element_size
def _init_depth_texture_draw(self): """Initialize geometry and shader for drawing FBO layers""" from demosys import geometry if not TextureHelper._quad: TextureHelper._quad = geometry.quad_fs() # Shader for drawing depth layers TextureHelper._depth_shader = context.ctx().program(vertex_shader=""" #version 330 in vec3 in_position; in vec2 in_uv; out vec2 uv; uniform vec2 offset; uniform vec2 scale; void main() { uv = in_uv; gl_Position = vec4((in_position.xy + vec2(1.0, 1.0)) * scale + offset, 0.0, 1.0); } """, fragment_shader=""" #version 330 out vec4 out_color; in vec2 uv; uniform sampler2D texture0; uniform float near; uniform float far; void main() { float z = texture(texture0, uv).r; float d = (2.0 * near) / (far + near - z * (far - near)); out_color = vec4(d); } """) TextureHelper._depth_sampler = self.ctx.sampler( filter=(moderngl.LINEAR, moderngl.LINEAR), compare_func='', )
def buffer(self, buffer, buffer_format: str, attribute_names, per_instance=False): """ Register a buffer/vbo for the VAO. This can be called multiple times. adding multiple buffers (interleaved or not) :param buffer: The buffer object. Can be ndarray or Buffer :param buffer_format: The format of the buffer ('f', 'u', 'i') :returns: The buffer object """ if not isinstance(attribute_names, list): attribute_names = [ attribute_names, ] if not isinstance(buffer, moderngl.Buffer) and not isinstance( buffer, numpy.ndarray): raise VAOError( "buffer parameter must be a moderngl.Buffer or numpy.ndarray instance" ) if isinstance(buffer, numpy.ndarray): buffer = context.ctx().buffer(buffer.tobytes()) formats = buffer_format.split() if len(formats) != len(attribute_names): raise VAOError( "Format '{}' does not describe attributes {}".format( buffer_format, attribute_names)) self.buffers.append( BufferInfo(buffer, buffer_format, attribute_names, per_instance=per_instance)) self.vertex_count = self.buffers[-1].vertices return buffer
def create_from_textures(color_buffers: List[Texture2D], depth_buffer: DepthTexture = None) -> 'FBO': """ Create FBO from existing textures :param color_buffers: List of textures :param depth_buffer: Depth texture :return: FBO instance """ instance = FBO() instance.color_buffers = color_buffers instance.depth_buffer = depth_buffer instance.fbo = context.ctx().framebuffer( color_attachments=[b.mglo for b in color_buffers], depth_attachment=depth_buffer.mglo if depth_buffer is not None else None, ) return instance
def __init__(self, name="", mode=moderngl.TRIANGLES): """ Create and empty VAO Keyword Args: name (str): The name for debug purposes mode (int): Default draw mode """ self.ctx = context.ctx() self.name = name self.mode = mode try: DRAW_MODES[self.mode] except KeyError: raise VAOError("Invalid draw mode. Options are {}".format(DRAW_MODES.values())) self.buffers = [] self._index_buffer = None self._index_element_size = None self.vertex_count = 0 self.vaos = {}
def __init__(self, program=None, **kwargs): self.program = program self.ctx = context.ctx()
def ctx(self): """ModernGL context""" return context.ctx()
def ctx(self): return context.ctx()
def ctx(self) -> moderngl.Context: """The moderngl context""" return context.ctx()
def quad_2d(width, height, xpos=0.0, ypos=0.0) -> VAO: """ Creates a 2D quad VAO using 2 triangles. :param width: Width of the quad :param height: Height of the quad :param xpos: Center position x :param ypos: Center position y """ pos = context.ctx().buffer( numpy.array([ xpos - width / 2.0, ypos + height / 2.0, 0.0, xpos - width / 2.0, ypos - height / 2.0, 0.0, xpos + width / 2.0, ypos - height / 2.0, 0.0, xpos - width / 2.0, ypos + height / 2.0, 0.0, xpos + width / 2.0, ypos - height / 2.0, 0.0, xpos + width / 2.0, ypos + height / 2.0, 0.0, ], dtype=numpy.float32).tobytes()) normals = context.ctx().buffer( numpy.array([ 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, ], dtype=numpy.float32).tobytes()) uvs = context.ctx().buffer( numpy.array([ 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, ], dtype=numpy.float32).tobytes()) vao = VAO("geometry:quad", mode=moderngl.TRIANGLES) vao.buffer(pos, '3f', ["in_position"]) vao.buffer(normals, '3f', ["in_normal"]) vao.buffer(uvs, '2f', ["in_uv"]) return vao
def __init__(self, shader=None, **kwargs): self.shader = shader self.ctx = context.ctx()
def __init__(self): self.mglo = None # Type: Union[moderngl.Texture, moderngl.TextureArray] self._ctx = context.ctx()
def ctx(self) -> moderngl.Context: """The MondernGL context""" return context.ctx()
def sphere(radius=0.5, sectors=32, rings=16) -> VAO: """ Generate a sphere :param radius: Radius or the sphere :param rings: number or horizontal rings :param sectors: number of vertical segments :return: VAO containing the sphere """ R = 1.0 / (rings - 1) S = 1.0 / (sectors - 1) vertices = [0] * (rings * sectors * 3) normals = [0] * (rings * sectors * 3) uvs = [0] * (rings * sectors * 2) v, n, t = 0, 0, 0 for r in range(rings): for s in range(sectors): y = math.sin(-math.pi / 2 + math.pi * r * R) x = math.cos(2 * math.pi * s * S) * math.sin(math.pi * r * R) z = math.sin(2 * math.pi * s * S) * math.sin(math.pi * r * R) uvs[t] = s * S uvs[t + 1] = r * R vertices[v] = x * radius vertices[v + 1] = y * radius vertices[v + 2] = z * radius normals[n] = x normals[n + 1] = y normals[n + 2] = z t += 2 v += 3 n += 3 indices = [0] * rings * sectors * 6 i = 0 for r in range(rings - 1): for s in range(sectors - 1): indices[i] = r * sectors + s indices[i + 1] = (r + 1) * sectors + (s + 1) indices[i + 2] = r * sectors + (s + 1) indices[i + 3] = r * sectors + s indices[i + 4] = (r + 1) * sectors + s indices[i + 5] = (r + 1) * sectors + (s + 1) i += 6 vbo_vertices = context.ctx().buffer( numpy.array(vertices, dtype=numpy.float32).tobytes()) vbo_normals = context.ctx().buffer( numpy.array(normals, dtype=numpy.float32).tobytes()) vbo_uvs = context.ctx().buffer( numpy.array(uvs, dtype=numpy.float32).tobytes()) vbo_elements = context.ctx().buffer( numpy.array(indices, dtype=numpy.uint32).tobytes()) vao = VAO("sphere", mode=mlg.TRIANGLES) # VBOs vao.buffer(vbo_vertices, '3f', ['in_position']) vao.buffer(vbo_normals, '3f', ['in_normal']) vao.buffer(vbo_uvs, '2f', ['in_uv']) vao.index_buffer(vbo_elements, index_element_size=4) return vao