Mercurial > touhou
view pytouhou/ui/opengl/renderer.pyx @ 792:11bc22bad1bf
python: Replace the image crate with png
We weren’t using any of its features anyway, so the png crate is exactly what
we need, without the many heavy dependencies of image.
https://github.com/image-rs/image-png/pull/670 will eventually make it even
faster to build.
| author | Link Mauve <linkmauve@linkmauve.fr> |
|---|---|
| date | Sat, 17 Jan 2026 22:22:25 +0100 |
| parents | ec972eb44391 |
| children |
line wrap: on
line source
# -*- encoding: utf-8 -*- ## ## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com> ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published ## by the Free Software Foundation; version 3 only. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## from libc.stdlib cimport malloc, free from libc.string cimport memset from os.path import join from pytouhou.lib.opengl cimport \ (glVertexPointer, glTexCoordPointer, glColorPointer, glVertexAttribPointer, glEnableVertexAttribArray, glBlendFunc, glBindTexture, glDrawElements, glBindBuffer, glBufferData, GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW, GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, GL_FLOAT, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_TEXTURE_2D, glGenBuffers, glDeleteBuffers, GLuint, glDeleteTextures, glGenVertexArrays, glDeleteVertexArrays, glBindVertexArray, glPushDebugGroup, GL_DEBUG_SOURCE_APPLICATION, glPopDebugGroup, glDrawArrays) from pytouhou.lib.sdl import SDLError from pytouhou.game.element cimport Element from .sprite cimport get_sprite_rendering_data from .backend cimport primitive_mode, is_legacy, use_debug_group, use_vao, use_primitive_restart from pytouhou.utils.helpers import get_logger logger = get_logger(__name__) cdef class Texture: def __cinit__(self, GLuint texture, Renderer renderer): self.texture = texture # Find an unused key in the textures array. for key in range(MAX_TEXTURES): if renderer.textures[key] == 0: break else: raise MemoryError('Too many textures currently loaded, consider increasing MAX_TEXTURES (currently %d).' % MAX_TEXTURES) self.key = key self.pointer = &renderer.textures[key] self.pointer[0] = texture for i in range(2): renderer.indices[key][i] = self.indices[i] #XXX: keep a reference so that when __dealloc__ is called self.pointer is still valid. self.renderer = renderer def __dealloc__(self): if self.texture: glDeleteTextures(1, &self.texture) if self.pointer != NULL: self.pointer[0] = 0 # The dangling pointers in renderer.indices doesn’t matter, since we # won’t use them if no texture is loaded in that slot. cdef long find_objects(Renderer self, object elements) except -1: # Don’t type element as Element, or else the overriding of objects won’t work. cdef Element obj cdef long i = 0 for element in elements: for obj in element.objects: sprite = obj.sprite if sprite and sprite.visible: # warning: no reference is preserved on the object—assuming the object will not die accidentally self.elements[i] = <PyObject*>obj i += 1 if i >= 640*3-4: return i return i cdef class Renderer: def __dealloc__(self): if not is_legacy: glDeleteBuffers(1, &self.vbo) if use_vao: glDeleteVertexArrays(1, &self.vao) glDeleteVertexArrays(1, &self.text_vao) def __init__(self, resource_loader): self.texture_manager = TextureManager(resource_loader, self, Texture) font_name = join(resource_loader.game_dir, 'font.ttf') try: self.font_manager = FontManager(font_name, 16, self, Texture) except SDLError: self.font_manager = None logger.error('Font file “%s” not found, disabling text rendering altogether.', font_name) if not is_legacy: if use_debug_group: glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, "Renderer creation") glGenBuffers(1, &self.vbo) glGenBuffers(1, &self.text_vbo) if use_vao: glGenVertexArrays(1, &self.vao) glBindVertexArray(self.vao) self.set_state() glGenVertexArrays(1, &self.text_vao) glBindVertexArray(self.text_vao) self.set_text_state() if use_debug_group: glPopDebugGroup() cdef void set_state(self) nogil: glBindBuffer(GL_ARRAY_BUFFER, self.vbo) #TODO: find a way to use offsetof() instead of those ugly substractions. glVertexAttribPointer(0, 3, GL_SHORT, False, sizeof(Vertex), <void*>0) glEnableVertexAttribArray(0) glVertexAttribPointer(1, 2, GL_FLOAT, False, sizeof(Vertex), <void*>8) glEnableVertexAttribArray(1) glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, True, sizeof(Vertex), <void*>16) glEnableVertexAttribArray(2) glBindBuffer(GL_ARRAY_BUFFER, 0) cdef void set_text_state(self) nogil: glBindBuffer(GL_ARRAY_BUFFER, self.text_vbo) #TODO: find a way to use offsetof() instead of those ugly substractions. glVertexAttribPointer(0, 2, GL_SHORT, False, sizeof(Vertex), <void*>0) glEnableVertexAttribArray(0) glVertexAttribPointer(1, 2, GL_FLOAT, False, sizeof(Vertex), <void*>4) glEnableVertexAttribArray(1) glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, True, sizeof(Vertex), <void*>12) glEnableVertexAttribArray(2) glBindBuffer(GL_ARRAY_BUFFER, 0) cdef bint render_elements(self, elements) except True: cdef Element element nb_elements = find_objects(self, elements) if not nb_elements: return False nb_vertices = 0 memset(self.last_indices, 0, sizeof(self.last_indices)) for element_idx in range(nb_elements): element = <object>self.elements[element_idx] ox, oy = <short>element.x, <short>element.y data = get_sprite_rendering_data(element.sprite) key = data.key blendfunc = key & 1 texture = key >> 1 rec = self.indices[texture][blendfunc] next_indice = self.last_indices[key] # Pack data in buffer x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4 = <short>data.pos[0], <short>data.pos[1], <short>data.pos[2], <short>data.pos[3], <short>data.pos[4], <short>data.pos[5], <short>data.pos[6], <short>data.pos[7], <short>data.pos[8], <short>data.pos[9], <short>data.pos[10], <short>data.pos[11] r, g, b, a = data.color[0], data.color[1], data.color[2], data.color[3] self.vertex_buffer[nb_vertices] = Vertex(x1 + ox, y1 + oy, z1, 0, data.left, data.bottom, r, g, b, a) self.vertex_buffer[nb_vertices+1] = Vertex(x2 + ox, y2 + oy, z2, 0, data.right, data.bottom, r, g, b, a) self.vertex_buffer[nb_vertices+2] = Vertex(x4 + ox, y4 + oy, z4, 0, data.left, data.top, r, g, b, a) self.vertex_buffer[nb_vertices+3] = Vertex(x3 + ox, y3 + oy, z3, 0, data.right, data.top, r, g, b, a) # Add indices if is_legacy: rec[next_indice] = nb_vertices rec[next_indice+1] = nb_vertices + 1 rec[next_indice+2] = nb_vertices + 3 rec[next_indice+3] = nb_vertices + 2 self.last_indices[key] += 4 elif use_primitive_restart: rec[next_indice] = nb_vertices rec[next_indice+1] = nb_vertices + 1 rec[next_indice+2] = nb_vertices + 2 rec[next_indice+3] = nb_vertices + 3 rec[next_indice+4] = 0xFFFF self.last_indices[key] += 5 else: rec[next_indice] = nb_vertices rec[next_indice+1] = nb_vertices + 1 rec[next_indice+2] = nb_vertices + 2 rec[next_indice+3] = nb_vertices + 1 rec[next_indice+4] = nb_vertices + 2 rec[next_indice+5] = nb_vertices + 3 self.last_indices[key] += 6 nb_vertices += 4 if use_debug_group: glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, "Elements drawing") if is_legacy: glVertexPointer(3, GL_SHORT, sizeof(Vertex), &self.vertex_buffer[0].x) glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &self.vertex_buffer[0].u) glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), &self.vertex_buffer[0].r) else: glBindBuffer(GL_ARRAY_BUFFER, self.vbo) glBufferData(GL_ARRAY_BUFFER, nb_vertices * sizeof(Vertex), &self.vertex_buffer[0], GL_DYNAMIC_DRAW) glBindBuffer(GL_ARRAY_BUFFER, 0) if use_vao: glBindVertexArray(self.vao) else: self.set_state() # Don’t change the state when it’s not needed. previous_blendfunc = -1 previous_texture = -1 for key in range(2 * MAX_TEXTURES): nb_indices = self.last_indices[key] if not nb_indices: continue blendfunc = key & 1 texture = key >> 1 if blendfunc != previous_blendfunc: glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[blendfunc]) if texture != previous_texture: glBindTexture(GL_TEXTURE_2D, self.textures[texture]) glDrawElements(primitive_mode, nb_indices, GL_UNSIGNED_SHORT, self.indices[texture][blendfunc]) previous_blendfunc = blendfunc previous_texture = texture glBindTexture(GL_TEXTURE_2D, 0) if not is_legacy and use_vao: glBindVertexArray(0) if use_debug_group: glPopDebugGroup() cdef bint render_quads(self, rects, colors, GLuint texture) except True: # There is nothing that batch more than two quads on the same texture, currently. cdef TextVertex buf[8] cdef unsigned short indices[12] if not is_legacy: if use_primitive_restart: indices[:] = [0, 1, 2, 3, 0xffff, 4, 5, 6, 7, 0, 0, 0] else: indices[:] = [0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4] length = len(rects) assert length == len(colors) for i, r in enumerate(rects): c1, c2, c3, c4 = colors[i] buf[4*i] = TextVertex(r.x, r.y, 0, 0, c1.r, c1.g, c1.b, c1.a) buf[4*i+1] = TextVertex(r.x + r.w, r.y, 1, 0, c2.r, c2.g, c2.b, c2.a) buf[4*i+2] = TextVertex(r.x + r.w, r.y + r.h, 1, 1, c3.r, c3.g, c3.b, c3.a) buf[4*i+3] = TextVertex(r.x, r.y + r.h, 0, 1, c4.r, c4.g, c4.b, c4.a) if use_debug_group: glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, "Quads drawing") if is_legacy: glVertexPointer(2, GL_SHORT, sizeof(TextVertex), &buf[0].x) glTexCoordPointer(2, GL_FLOAT, sizeof(TextVertex), &buf[0].u) glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(TextVertex), &buf[0].r) else: glBindBuffer(GL_ARRAY_BUFFER, self.text_vbo) glBufferData(GL_ARRAY_BUFFER, 4 * length * sizeof(TextVertex), buf, GL_DYNAMIC_DRAW) glBindBuffer(GL_ARRAY_BUFFER, 0) if use_vao: glBindVertexArray(self.text_vao) else: self.set_text_state() glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glBindTexture(GL_TEXTURE_2D, texture) if is_legacy: glDrawArrays(primitive_mode, 0, 4 * length) else: nb_indices = 5 * length - 1 if use_primitive_restart else 6 * length glDrawElements(primitive_mode, nb_indices, GL_UNSIGNED_SHORT, indices) if use_debug_group: glPopDebugGroup()
