changeset 505:bfea9e9a6845

Manage the texture-specific indices in the Texture, and some more renderer optimisations.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Fri, 01 Nov 2013 14:45:53 +0100
parents 69c73023f7a0
children 08d9e6730364
files pytouhou/ui/background.pyx pytouhou/ui/gamerenderer.pyx pytouhou/ui/renderer.pxd pytouhou/ui/renderer.pyx pytouhou/ui/sprite.pyx pytouhou/ui/texture.pxd pytouhou/ui/texture.pyx setup.py
diffstat 8 files changed, 89 insertions(+), 74 deletions(-) [+]
line wrap: on
line diff
--- a/pytouhou/ui/background.pyx
+++ b/pytouhou/ui/background.pyx
@@ -92,8 +92,8 @@ cdef class BackgroundRenderer:
 
                 nb_vertices += 4
 
-        self.texture = key % MAX_TEXTURES
-        self.blendfunc = key // MAX_TEXTURES
+        self.texture = key >> 1
+        self.blendfunc = key & 1
         self.nb_vertices = nb_vertices
         self.vertex_buffer = <Vertex*> realloc(vertex_buffer, nb_vertices * sizeof(Vertex))
 
--- a/pytouhou/ui/gamerenderer.pyx
+++ b/pytouhou/ui/gamerenderer.pyx
@@ -24,6 +24,7 @@ from pytouhou.lib.opengl cimport \
 from pytouhou.utils.maths cimport perspective, setup_camera, ortho_2d
 from pytouhou.game.text cimport NativeText, GlyphCollection
 from .shaders.eosd import GameShader, BackgroundShader, PassthroughShader
+from .renderer cimport Texture
 
 from collections import namedtuple
 Rect = namedtuple('Rect', 'x y w h')
@@ -199,9 +200,9 @@ cdef class GameRenderer(Renderer):
             if label.shadow:
                 shadow_rect = Rect(label.x + 1, label.y + 1, label.width, label.height)
                 shadow = [black._replace(a=label.alpha)] * 4
-                self.render_quads([shadow_rect, rect], [shadow, gradient], label.texture)
+                self.render_quads([shadow_rect, rect], [shadow, gradient], (<Texture>label.texture).texture)
             else:
-                self.render_quads([rect], [gradient], label.texture)
+                self.render_quads([rect], [gradient], (<Texture>label.texture).texture)
 
 
     cdef void render_interface(self, interface, game_boss):
--- a/pytouhou/ui/renderer.pxd
+++ b/pytouhou/ui/renderer.pxd
@@ -1,5 +1,6 @@
 from cpython cimport PyObject
 from pytouhou.lib.opengl cimport GLuint
+from .texture cimport TextureManager, FontManager
 
 cdef struct Vertex:
     int x, y, z
@@ -12,20 +13,26 @@ cdef struct PassthroughVertex:
     float u, v
 
 
+cdef class Texture:
+    cdef GLuint texture
+    cdef unsigned short indices[2][65536]
+
+
 cdef class Renderer:
-    cdef public texture_manager, font_manager
+    cdef TextureManager texture_manager
+    cdef FontManager font_manager
     cdef GLuint vbo, framebuffer_vbo
-    cdef Vertex *vertex_buffer
+    cdef Vertex vertex_buffer[MAX_ELEMENTS]
     cdef long x, y, width, height
 
     cdef bint use_fixed_pipeline #XXX
 
-    cdef unsigned short *indices[2][MAX_TEXTURES]
+    cdef unsigned short *indices[MAX_TEXTURES][2]
     cdef unsigned short last_indices[2 * MAX_TEXTURES]
     cdef PyObject *elements[640*3]
 
     cdef void render_elements(self, elements) except *
-    cdef void render_quads(self, rects, colors, texture) except *
+    cdef void render_quads(self, rects, colors, GLuint texture) except *
     cdef void render_framebuffer(self, Framebuffer fb) except *
 
 
--- a/pytouhou/ui/renderer.pyx
+++ b/pytouhou/ui/renderer.pyx
@@ -31,20 +31,26 @@ from pytouhou.lib.opengl cimport \
           GL_LINEAR, GL_TEXTURE_MAG_FILTER, GL_RGBA, GL_RENDERBUFFER,
           GL_DEPTH_COMPONENT, GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT,
           GL_FRAMEBUFFER_COMPLETE, glClear, GL_COLOR_BUFFER_BIT,
-          GL_DEPTH_BUFFER_BIT)
+          GL_DEPTH_BUFFER_BIT, GLuint, glDeleteTextures)
 
 from pytouhou.lib.sdl import SDLError
 
 from pytouhou.game.element cimport Element
 from .sprite cimport get_sprite_rendering_data
-from .texture import TextureManager, FontManager
 
 from pytouhou.utils.helpers import get_logger
 
 logger = get_logger(__name__)
 
 
-DEF MAX_ELEMENTS = 640*4*3
+cdef class Texture:
+    def __cinit__(self, GLuint texture, Renderer renderer):
+        self.texture = texture
+        for i in xrange(2):
+            renderer.indices[texture][i] = self.indices[i]
+
+    def __dealloc__(self):
+        glDeleteTextures(1, &self.texture)
 
 
 cdef long find_objects(Renderer self, object elements) except -1:
@@ -64,23 +70,17 @@ cdef long find_objects(Renderer self, ob
 
 
 cdef class Renderer:
-    def __cinit__(self):
-        self.vertex_buffer = <Vertex*> malloc(MAX_ELEMENTS * sizeof(Vertex))
-
-
     def __dealloc__(self):
-        free(self.vertex_buffer)
-
         if not self.use_fixed_pipeline:
             glDeleteBuffers(1, &self.framebuffer_vbo)
             glDeleteBuffers(1, &self.vbo)
 
 
     def __init__(self, resource_loader):
-        self.texture_manager = TextureManager(resource_loader, self)
+        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)
+            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)
@@ -90,36 +90,29 @@ cdef class Renderer:
             glGenBuffers(1, &self.framebuffer_vbo)
 
 
-    def add_texture(self, int texture):
-        for i in xrange(2):
-            self.indices[i][texture] = <unsigned short*> malloc(65536 * sizeof(unsigned short))
-
-
-    def remove_texture(self, int texture):
-        for i in xrange(2):
-            free(self.indices[i][texture])
-
-
     cdef void render_elements(self, elements):
         cdef int key
         cdef int x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, ox, oy
         cdef float left, right, bottom, top
         cdef unsigned char r, g, b, a
 
+        nb_elements = find_objects(self, elements)
+        if not nb_elements:
+            return
+
         nb_vertices = 0
         memset(self.last_indices, 0, sizeof(self.last_indices))
 
-        nb_elements = find_objects(self, elements)
         for element_idx in xrange(nb_elements):
             element = <object>self.elements[element_idx]
             sprite = element.sprite
             ox, oy = element.x, element.y
             key, (vertices, uvs, colors) = get_sprite_rendering_data(sprite)
 
-            blendfunc = key // MAX_TEXTURES
-            texture = key % MAX_TEXTURES
+            blendfunc = key & 1
+            texture = key >> 1
 
-            rec = self.indices[blendfunc][texture]
+            rec = self.indices[texture][blendfunc]
             next_indice = self.last_indices[key]
 
             # Pack data in buffer
@@ -142,9 +135,6 @@ cdef class Renderer:
 
             nb_vertices += 4
 
-        if nb_vertices == 0:
-            return
-
         if self.use_fixed_pipeline:
             glVertexPointer(3, GL_INT, sizeof(Vertex), &self.vertex_buffer[0].x)
             glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &self.vertex_buffer[0].u)
@@ -161,23 +151,34 @@ cdef class Renderer:
             glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, True, sizeof(Vertex), <void*>20)
             glEnableVertexAttribArray(2)
 
+        # Don’t change the state when it’s not needed.
+        previous_blendfunc = -1
+        previous_texture = -1
+
         for key in xrange(2 * MAX_TEXTURES):
             nb_indices = self.last_indices[key]
             if not nb_indices:
                 continue
 
-            blendfunc = key // MAX_TEXTURES
-            texture = key % MAX_TEXTURES
+            blendfunc = key & 1
+            texture = key >> 1
 
-            glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[blendfunc])
-            glBindTexture(GL_TEXTURE_2D, texture)
-            glDrawElements(GL_TRIANGLES, nb_indices, GL_UNSIGNED_SHORT, self.indices[blendfunc][texture])
+            if blendfunc != previous_blendfunc:
+                glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[blendfunc])
+            if texture != previous_texture:
+                glBindTexture(GL_TEXTURE_2D, texture)
+            glDrawElements(GL_TRIANGLES, nb_indices, GL_UNSIGNED_SHORT, self.indices[texture][blendfunc])
+
+            previous_blendfunc = blendfunc
+            previous_texture = texture
+
+        glBindTexture(GL_TEXTURE_2D, 0)
 
         if not self.use_fixed_pipeline:
             glBindBuffer(GL_ARRAY_BUFFER, 0)
 
 
-    cdef void render_quads(self, rects, colors, texture):
+    cdef void render_quads(self, rects, colors, GLuint texture):
         # There is nothing that batch more than two quads on the same texture, currently.
         cdef Vertex buf[8]
         cdef unsigned short indices[12]
--- a/pytouhou/ui/sprite.pyx
+++ b/pytouhou/ui/sprite.pyx
@@ -16,6 +16,7 @@
 from libc.math cimport M_PI as pi
 
 from pytouhou.utils.matrix cimport Matrix
+from pytouhou.ui.renderer cimport Texture #XXX
 
 
 cpdef object get_sprite_rendering_data(Sprite sprite):
@@ -67,7 +68,7 @@ cpdef object get_sprite_rendering_data(S
            ty * y_1 + toy,
            (ty + th) * y_1 + toy)
 
-    key = MAX_TEXTURES * sprite.blendfunc + <long>sprite.anm.texture
+    key = ((<Texture>sprite.anm.texture).texture << 1) | sprite.blendfunc
     r, g, b = sprite.color
     values = tuple([x for x in vertmat.data[:12]]), uvs, (r, g, b, sprite.alpha)
     sprite._rendering_data = key, values
new file mode 100644
--- /dev/null
+++ b/pytouhou/ui/texture.pxd
@@ -0,0 +1,12 @@
+from pytouhou.lib.sdl cimport Font
+
+cdef class TextureManager:
+    cdef object loader, renderer, texture_class
+
+    cdef load(self, dict anms)
+
+cdef class FontManager:
+    cdef Font font
+    cdef object renderer, texture_class
+
+    cdef load(self, list labels)
--- a/pytouhou/ui/texture.pyx
+++ b/pytouhou/ui/texture.pyx
@@ -16,55 +16,48 @@ from pytouhou.lib.opengl cimport \
          (glTexParameteri, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER,
           GL_LINEAR, GL_BGRA, GL_RGBA, GL_RGB, GL_LUMINANCE, GL_UNSIGNED_BYTE,
           GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_4_4_4_4_REV,
-          glGenTextures, glBindTexture, glTexImage2D, GL_TEXTURE_2D, GLuint,
-          glDeleteTextures)
+          glGenTextures, glBindTexture, glTexImage2D, GL_TEXTURE_2D, GLuint)
 
-from pytouhou.lib.sdl cimport load_png, create_rgb_surface, Font
+from pytouhou.lib.sdl cimport load_png, create_rgb_surface
 from pytouhou.formats.thtx import Texture #TODO: perhaps define that elsewhere?
 from pytouhou.game.text cimport NativeText
 
 import os
 
 
-class TextureId(int):
-    def __del__(self):
-        cdef GLuint texture = self
-        glDeleteTextures(1, &texture)
-        self.renderer.remove_texture(self)
+cdef class TextureManager:
+    def __init__(self, loader=None, renderer=None, texture_class=None):
+        self.loader = loader
+        self.renderer = renderer
+        self.texture_class = texture_class
 
 
-class TextureManager(object):
-    def __init__(self, loader=None, renderer=None):
-        self.loader = loader
-        self.renderer = renderer
-
-
-    def load(self, anms):
-        for anm in sorted(anms.values(), key=lambda x: x[0].first_name.endswith('ascii.png')):
+    cdef load(self, dict anms):
+        for anm in sorted(anms.values(), key=is_ascii):
             for entry in anm:
                 if not hasattr(entry, 'texture'):
                     texture = decode_png(self.loader, entry.first_name, entry.secondary_name)
-                    entry.texture = load_texture(texture)
-                elif not isinstance(entry.texture, TextureId):
-                    entry.texture = load_texture(entry.texture)
-                self.renderer.add_texture(entry.texture)
-                entry.texture.renderer = self.renderer
+                elif not isinstance(entry.texture, self.texture_class):
+                    texture = entry.texture
+                entry.texture = self.texture_class(load_texture(texture), self.renderer)
         anms.clear()
 
 
+def is_ascii(anm):
+    return anm[0].first_name.endswith('ascii.png')
+
+
 cdef class FontManager:
-    cdef Font font
-    cdef object renderer
-
-    def __init__(self, fontname, fontsize=16, renderer=None):
+    def __init__(self, fontname, fontsize=16, renderer=None, texture_class=None):
         self.font = Font(fontname, fontsize)
         self.renderer = renderer
+        self.texture_class = texture_class
 
 
-    def load(self, label_list):
+    cdef load(self, list labels):
         cdef NativeText label
 
-        for label in label_list:
+        for label in labels:
             if label.texture is None:
                 surface = self.font.render(label.text)
                 label.width, label.height = surface.surface.w, surface.surface.h
@@ -77,8 +70,7 @@ cdef class FontManager:
                     assert label.align == 'left'
 
                 texture = Texture(label.width, label.height, -4, surface.pixels)
-                label.texture = load_texture(texture)
-                label.texture.renderer = self.renderer
+                label.texture = self.texture_class(load_texture(texture), self.renderer)
 
 
 cdef decode_png(loader, first_name, secondary_name):
@@ -102,7 +94,7 @@ cdef decode_png(loader, first_name, seco
     return Texture(width, height, -4, new_image.pixels)
 
 
-cdef load_texture(thtx):
+cdef GLuint load_texture(thtx):
     cdef GLuint texture
 
     if thtx.fmt == 1:
@@ -140,4 +132,4 @@ cdef load_texture(thtx):
                  format_, type_,
                  <char*>thtx.data)
 
-    return TextureId(texture)
+    return texture
--- a/setup.py
+++ b/setup.py
@@ -82,6 +82,7 @@ setup(name='PyTouhou',
                             compiler_directives={'infer_types': True,
                                                  'infer_types.verbose': True},
                             compile_time_env={'MAX_TEXTURES': 1024,
+                                              'MAX_ELEMENTS': 640 * 4 * 3,
                                               'MAX_CHANNELS': 26,
                                               'USE_GLEW': is_windows}),
       scripts=['eosd', 'anmviewer'],