changeset 513:5e3e0b09a531

Move the OpenGL backend to its own package.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Thu, 05 Dec 2013 02:16:31 +0100
parents b39ad30c6620
children 3d4410de78e1
files eosd pytouhou/ui/background.pxd pytouhou/ui/background.pyx pytouhou/ui/gamerenderer.pxd pytouhou/ui/gamerenderer.pyx pytouhou/ui/opengl/__init__.py pytouhou/ui/opengl/background.pxd pytouhou/ui/opengl/background.pyx pytouhou/ui/opengl/gamerenderer.pxd pytouhou/ui/opengl/gamerenderer.pyx pytouhou/ui/opengl/renderer.pxd pytouhou/ui/opengl/renderer.pyx pytouhou/ui/opengl/shader.pxd pytouhou/ui/opengl/shader.pyx pytouhou/ui/opengl/shaders/__init__.py pytouhou/ui/opengl/shaders/eosd.py pytouhou/ui/opengl/sprite.pxd pytouhou/ui/opengl/sprite.pyx pytouhou/ui/opengl/texture.pxd pytouhou/ui/opengl/texture.pyx pytouhou/ui/renderer.pxd pytouhou/ui/renderer.pyx pytouhou/ui/shader.pxd pytouhou/ui/shader.pyx pytouhou/ui/shaders/__init__.py pytouhou/ui/shaders/eosd.py pytouhou/ui/sprite.pxd pytouhou/ui/sprite.pyx pytouhou/ui/texture.pxd pytouhou/ui/texture.pyx pytouhou/ui/window.pyx setup.py
diffstat 29 files changed, 1276 insertions(+), 1243 deletions(-) [+]
line wrap: on
line diff
--- a/eosd	Thu Dec 05 01:55:39 2013 +0100
+++ b/eosd	Thu Dec 05 02:16:31 2013 +0100
@@ -58,7 +58,7 @@
 import logging
 
 if args.backend == 'opengl':
-    from pytouhou.ui.gamerenderer import GameRenderer
+    from pytouhou.ui.opengl.gamerenderer import GameRenderer
     opengl = True
 elif args.backend == 'sdl':
     from pytouhou.ui.sdl.gamerenderer import GameRenderer
--- a/pytouhou/ui/background.pxd	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-from pytouhou.lib.opengl cimport GLuint
-
-cdef struct Vertex:
-    float x, y, z
-    float u, v
-    unsigned char r, g, b, a
-
-
-cdef class BackgroundRenderer:
-    cdef GLuint texture
-    cdef unsigned short blendfunc, nb_vertices
-    cdef Vertex *vertex_buffer
-    cdef unsigned int use_fixed_pipeline, vbo
-    cdef object background
-
-    cdef void render_background(self) except *
-    cdef void load(self, background) except *
--- a/pytouhou/ui/background.pyx	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-# -*- encoding: utf-8 -*-
-##
-## Copyright (C) 2013 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
-##
-## 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, realloc
-
-from pytouhou.lib.opengl cimport \
-         (glVertexPointer, glTexCoordPointer, glColorPointer,
-          glVertexAttribPointer, glEnableVertexAttribArray, glBlendFunc,
-          glBindTexture, glBindBuffer, glBufferData, GL_ARRAY_BUFFER,
-          GL_STATIC_DRAW, GL_UNSIGNED_BYTE, GL_FLOAT, GL_SRC_ALPHA,
-          GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_TEXTURE_2D, glGenBuffers,
-          glEnable, glDisable, GL_DEPTH_TEST, glDrawArrays, GL_QUADS)
-
-from .sprite cimport get_sprite_rendering_data
-
-
-cdef class BackgroundRenderer:
-    def __cinit__(self):
-        # Allocate buffers
-        self.vertex_buffer = <Vertex*> malloc(65536 * sizeof(Vertex))
-
-
-    def __dealloc__(self):
-        free(self.vertex_buffer)
-
-
-    def __init__(self, use_fixed_pipeline):
-        self.use_fixed_pipeline = use_fixed_pipeline
-
-        if not use_fixed_pipeline:
-            glGenBuffers(1, &self.vbo)
-
-
-    cdef void render_background(self):
-        if self.use_fixed_pipeline:
-            glVertexPointer(3, GL_FLOAT, 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)
-
-            #TODO: find a way to use offsetof() instead of those ugly hardcoded values.
-            glVertexAttribPointer(0, 3, GL_FLOAT, False, sizeof(Vertex), <void*>0)
-            glEnableVertexAttribArray(0)
-            glVertexAttribPointer(1, 2, GL_FLOAT, False, sizeof(Vertex), <void*>12)
-            glEnableVertexAttribArray(1)
-            glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, True, sizeof(Vertex), <void*>20)
-            glEnableVertexAttribArray(2)
-
-        glEnable(GL_DEPTH_TEST)
-        glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[self.blendfunc])
-        glBindTexture(GL_TEXTURE_2D, self.texture)
-        glDrawArrays(GL_QUADS, 0, self.nb_vertices)
-        glDisable(GL_DEPTH_TEST)
-
-        if not self.use_fixed_pipeline:
-            glBindBuffer(GL_ARRAY_BUFFER, 0)
-
-
-    cdef void load(self, background):
-        cdef float ox, oy, oz, ox2, oy2, oz2
-        cdef unsigned short nb_vertices = 0
-        cdef Vertex* vertex_buffer
-
-        self.background = background
-
-        vertex_buffer = self.vertex_buffer
-
-        for ox, oy, oz, model_id, model in background.object_instances:
-            for ox2, oy2, oz2, width_override, height_override, sprite in model:
-                #TODO: view frustum culling
-                key, (vertices, uvs, colors) = get_sprite_rendering_data(sprite)
-                x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4 = vertices
-                left, right, bottom, top = uvs
-                r, g, b, a = colors
-
-                vertex_buffer[nb_vertices] = Vertex(x1 + ox + ox2, y1 + oy + oy2, z1 + oz + oz2, left, bottom, r, g, b, a)
-                vertex_buffer[nb_vertices+1] = Vertex(x2 + ox + ox2, y2 + oy + oy2, z2 + oz + oz2, right, bottom, r, g, b, a)
-                vertex_buffer[nb_vertices+2] = Vertex(x3 + ox + ox2, y3 + oy + oy2, z3 + oz + oz2, right, top, r, g, b, a)
-                vertex_buffer[nb_vertices+3] = Vertex(x4 + ox + ox2, y4 + oy + oy2, z4 + oz + oz2, left, top, r, g, b, a)
-
-                nb_vertices += 4
-
-        self.texture = key >> 1
-        self.blendfunc = key & 1
-        self.nb_vertices = nb_vertices
-        self.vertex_buffer = <Vertex*> realloc(vertex_buffer, nb_vertices * sizeof(Vertex))
-
-        if not self.use_fixed_pipeline:
-            glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
-            glBufferData(GL_ARRAY_BUFFER, nb_vertices * sizeof(Vertex), &self.vertex_buffer[0], GL_STATIC_DRAW)
-            glBindBuffer(GL_ARRAY_BUFFER, 0)
--- a/pytouhou/ui/gamerenderer.pxd	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-from pytouhou.utils.matrix cimport Matrix
-from pytouhou.game.game cimport Game
-from .background cimport BackgroundRenderer
-from .renderer cimport Renderer, Framebuffer
-from .shader cimport Shader
-
-cdef class GameRenderer(Renderer):
-    cdef Matrix game_mvp, interface_mvp, proj
-    cdef Shader game_shader, background_shader, interface_shader, passthrough_shader
-    cdef Framebuffer framebuffer
-    cdef BackgroundRenderer background_renderer
-
-    cdef void render_game(self, Game game) except *
-    cdef void render_text(self, texts) except *
-    cdef void render_interface(self, interface, game_boss) except *
--- a/pytouhou/ui/gamerenderer.pyx	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,250 +0,0 @@
-# -*- 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 itertools import chain
-
-from pytouhou.lib.opengl cimport \
-         (glClear, glMatrixMode, glLoadIdentity, glLoadMatrixf, glDisable,
-          glEnable, glFogi, glFogf, glFogfv, GL_PROJECTION, GL_MODELVIEW,
-          GL_FOG, GL_FOG_MODE, GL_LINEAR, GL_FOG_START, GL_FOG_END,
-          GL_FOG_COLOR, GL_COLOR_BUFFER_BIT, GLfloat, glViewport, glScissor,
-          GL_SCISSOR_TEST, GL_DEPTH_BUFFER_BIT)
-
-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')
-Color = namedtuple('Color', 'r g b a')
-
-
-cdef class GameRenderer(Renderer):
-    def __init__(self, resource_loader, window):
-        self.use_fixed_pipeline = window.use_fixed_pipeline #XXX
-
-        Renderer.__init__(self, resource_loader)
-
-        if not self.use_fixed_pipeline:
-            self.game_shader = GameShader()
-            self.background_shader = BackgroundShader()
-            self.interface_shader = self.game_shader
-            self.passthrough_shader = PassthroughShader()
-
-            self.framebuffer = Framebuffer(0, 0, 640, 480)
-
-
-    property size:
-        # We never need to get back the computed size, so size is write-only.
-        def __set__(self, tuple size):
-            self.x, self.y, self.width, self.height = size
-
-
-    def load_textures(self, dict anms):
-        self.texture_manager.load(anms)
-
-
-    def load_background(self, background):
-        if background is not None:
-            self.background_renderer = BackgroundRenderer(self.use_fixed_pipeline)
-            self.background_renderer.load(background)
-        else:
-            self.background_renderer = None
-
-
-    def start(self, common):
-        self.proj = perspective(30, float(common.width) / float(common.height),
-                                101010101./2010101., 101010101./10101.)
-        game_view = setup_camera(0, 0, 1)
-        self.game_mvp = game_view * self.proj
-        self.interface_mvp = ortho_2d(0., float(common.interface.width),
-                                      float(common.interface.height), 0.)
-
-
-    def render(self, Game game):
-        if not self.use_fixed_pipeline:
-            self.framebuffer.bind()
-
-        self.render_game(game)
-        self.render_text(game.texts + game.native_texts)
-        self.render_interface(game.interface, game.boss)
-
-        if not self.use_fixed_pipeline:
-            self.passthrough_shader.bind()
-            self.passthrough_shader.uniform_matrix('mvp', self.interface_mvp)
-            self.render_framebuffer(self.framebuffer)
-
-
-    cdef void render_game(self, Game game):
-        cdef long game_x, game_y
-        cdef float x, y, z, dx, dy, dz, fog_data[4], fog_start, fog_end
-        cdef unsigned char fog_r, fog_g, fog_b
-        cdef Matrix mvp
-
-        game_x, game_y = game.interface.game_pos
-        glViewport(game_x, game_y, game.width, game.height)
-        glClear(GL_DEPTH_BUFFER_BIT)
-        glScissor(game_x, game_y, game.width, game.height)
-        glEnable(GL_SCISSOR_TEST)
-
-        if self.use_fixed_pipeline:
-            glMatrixMode(GL_PROJECTION)
-            glLoadIdentity()
-
-        if game is not None and game.spellcard_effect is not None:
-            if self.use_fixed_pipeline:
-                glMatrixMode(GL_MODELVIEW)
-                glLoadMatrixf(self.game_mvp.data)
-                glDisable(GL_FOG)
-            else:
-                self.game_shader.bind()
-                self.game_shader.uniform_matrix('mvp', self.game_mvp)
-
-            self.render_elements([game.spellcard_effect])
-        elif self.background_renderer is not None:
-            back = self.background_renderer.background
-            x, y, z = back.position_interpolator.values
-            dx, dy, dz = back.position2_interpolator.values
-            fog_b, fog_g, fog_r, fog_start, fog_end = back.fog_interpolator.values
-
-            # Those two lines may come from the difference between Direct3D and
-            # OpenGL’s distance handling.  The first one seem to calculate fog
-            # from the eye, while the second does that starting from the near
-            # plane.
-            #TODO: investigate, and use a variable to keep the near plane
-            # distance at a single place.
-            fog_start -= 101010101./2010101.
-            fog_end -= 101010101./2010101.
-
-            model = Matrix()
-            model.data[12] = -x
-            model.data[13] = -y
-            model.data[14] = -z
-            view = setup_camera(dx, dy, dz)
-            mvp = model * view * self.proj
-
-            if self.use_fixed_pipeline:
-                glMatrixMode(GL_MODELVIEW)
-                glLoadMatrixf(mvp.data)
-
-                glEnable(GL_FOG)
-                glFogi(GL_FOG_MODE, GL_LINEAR)
-                glFogf(GL_FOG_START, fog_start)
-                glFogf(GL_FOG_END,  fog_end)
-
-                fog_data[0] = fog_r / 255.
-                fog_data[1] = fog_g / 255.
-                fog_data[2] = fog_b / 255.
-                fog_data[3] = 1.
-                glFogfv(GL_FOG_COLOR, fog_data)
-            else:
-                self.background_shader.bind()
-                self.background_shader.uniform_matrix('mvp', mvp)
-
-                self.background_shader.uniform_1('fog_scale', 1. / (fog_end - fog_start))
-                self.background_shader.uniform_1('fog_end', fog_end)
-                self.background_shader.uniform_4('fog_color', fog_r / 255., fog_g / 255., fog_b / 255., 1.)
-
-            self.background_renderer.render_background()
-        else:
-            glClear(GL_COLOR_BUFFER_BIT)
-
-        if game is not None:
-            if self.use_fixed_pipeline:
-                glMatrixMode(GL_MODELVIEW)
-                glLoadMatrixf(self.game_mvp.data)
-                glDisable(GL_FOG)
-            else:
-                self.game_shader.bind()
-                self.game_shader.uniform_matrix('mvp', self.game_mvp)
-
-            self.render_elements([enemy for enemy in game.enemies if enemy.visible])
-            self.render_elements(game.effects)
-            self.render_elements(chain(game.players_bullets,
-                                       game.lasers_sprites(),
-                                       game.players,
-                                       game.msg_sprites()))
-            self.render_elements(chain(game.bullets, game.lasers,
-                                       game.cancelled_bullets, game.items,
-                                       game.labels))
-
-        if game.msg_runner is not None:
-            rect = Rect(48, 368, 288, 48)
-            color1 = Color(0, 0, 0, 192)
-            color2 = Color(0, 0, 0, 128)
-            self.render_quads([rect], [(color1, color1, color2, color2)], 0)
-
-        glDisable(GL_SCISSOR_TEST)
-
-
-    cdef void render_text(self, texts):
-        cdef NativeText label
-
-        if self.font_manager is None:
-            return
-
-        labels = [label for label in texts if label is not None]
-        self.font_manager.load(labels)
-
-        black = Color(0, 0, 0, 255)
-
-        for label in labels:
-            if label is None:
-                continue
-
-            rect = Rect(label.x, label.y, label.width, label.height)
-            gradient = [Color(*color, a=label.alpha) for color in label.gradient]
-
-            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], (<Texture>label.texture).texture)
-            else:
-                self.render_quads([rect], [gradient], (<Texture>label.texture).texture)
-
-
-    cdef void render_interface(self, interface, game_boss):
-        cdef GlyphCollection label
-
-        elements = []
-
-        if self.use_fixed_pipeline:
-            glMatrixMode(GL_MODELVIEW)
-            glLoadMatrixf(self.interface_mvp.data)
-            glDisable(GL_FOG)
-        else:
-            self.interface_shader.bind()
-            self.interface_shader.uniform_matrix('mvp', self.interface_mvp)
-        glViewport(0, 0, interface.width, interface.height)
-
-        items = [item for item in interface.items if item.anmrunner and item.anmrunner.running]
-        labels = interface.labels.values()
-
-        if items:
-            # Redraw all the interface
-            elements.extend(items)
-        else:
-            # Redraw only changed labels
-            labels = [label for label in labels if label.changed]
-
-        elements.extend(interface.level_start)
-
-        if game_boss is not None:
-            elements.extend(interface.boss_items)
-
-        elements.extend(labels)
-        self.render_elements(elements)
-        for label in labels:
-            label.changed = False
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/background.pxd	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,17 @@
+from pytouhou.lib.opengl cimport GLuint
+
+cdef struct Vertex:
+    float x, y, z
+    float u, v
+    unsigned char r, g, b, a
+
+
+cdef class BackgroundRenderer:
+    cdef GLuint texture
+    cdef unsigned short blendfunc, nb_vertices
+    cdef Vertex *vertex_buffer
+    cdef unsigned int use_fixed_pipeline, vbo
+    cdef object background
+
+    cdef void render_background(self) except *
+    cdef void load(self, background) except *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/background.pyx	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,103 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2013 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+##
+## 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, realloc
+
+from pytouhou.lib.opengl cimport \
+         (glVertexPointer, glTexCoordPointer, glColorPointer,
+          glVertexAttribPointer, glEnableVertexAttribArray, glBlendFunc,
+          glBindTexture, glBindBuffer, glBufferData, GL_ARRAY_BUFFER,
+          GL_STATIC_DRAW, GL_UNSIGNED_BYTE, GL_FLOAT, GL_SRC_ALPHA,
+          GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_TEXTURE_2D, glGenBuffers,
+          glEnable, glDisable, GL_DEPTH_TEST, glDrawArrays, GL_QUADS)
+
+from .sprite cimport get_sprite_rendering_data
+
+
+cdef class BackgroundRenderer:
+    def __cinit__(self):
+        # Allocate buffers
+        self.vertex_buffer = <Vertex*> malloc(65536 * sizeof(Vertex))
+
+
+    def __dealloc__(self):
+        free(self.vertex_buffer)
+
+
+    def __init__(self, use_fixed_pipeline):
+        self.use_fixed_pipeline = use_fixed_pipeline
+
+        if not use_fixed_pipeline:
+            glGenBuffers(1, &self.vbo)
+
+
+    cdef void render_background(self):
+        if self.use_fixed_pipeline:
+            glVertexPointer(3, GL_FLOAT, 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)
+
+            #TODO: find a way to use offsetof() instead of those ugly hardcoded values.
+            glVertexAttribPointer(0, 3, GL_FLOAT, False, sizeof(Vertex), <void*>0)
+            glEnableVertexAttribArray(0)
+            glVertexAttribPointer(1, 2, GL_FLOAT, False, sizeof(Vertex), <void*>12)
+            glEnableVertexAttribArray(1)
+            glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, True, sizeof(Vertex), <void*>20)
+            glEnableVertexAttribArray(2)
+
+        glEnable(GL_DEPTH_TEST)
+        glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[self.blendfunc])
+        glBindTexture(GL_TEXTURE_2D, self.texture)
+        glDrawArrays(GL_QUADS, 0, self.nb_vertices)
+        glDisable(GL_DEPTH_TEST)
+
+        if not self.use_fixed_pipeline:
+            glBindBuffer(GL_ARRAY_BUFFER, 0)
+
+
+    cdef void load(self, background):
+        cdef float ox, oy, oz, ox2, oy2, oz2
+        cdef unsigned short nb_vertices = 0
+        cdef Vertex* vertex_buffer
+
+        self.background = background
+
+        vertex_buffer = self.vertex_buffer
+
+        for ox, oy, oz, model_id, model in background.object_instances:
+            for ox2, oy2, oz2, width_override, height_override, sprite in model:
+                #TODO: view frustum culling
+                key, (vertices, uvs, colors) = get_sprite_rendering_data(sprite)
+                x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4 = vertices
+                left, right, bottom, top = uvs
+                r, g, b, a = colors
+
+                vertex_buffer[nb_vertices] = Vertex(x1 + ox + ox2, y1 + oy + oy2, z1 + oz + oz2, left, bottom, r, g, b, a)
+                vertex_buffer[nb_vertices+1] = Vertex(x2 + ox + ox2, y2 + oy + oy2, z2 + oz + oz2, right, bottom, r, g, b, a)
+                vertex_buffer[nb_vertices+2] = Vertex(x3 + ox + ox2, y3 + oy + oy2, z3 + oz + oz2, right, top, r, g, b, a)
+                vertex_buffer[nb_vertices+3] = Vertex(x4 + ox + ox2, y4 + oy + oy2, z4 + oz + oz2, left, top, r, g, b, a)
+
+                nb_vertices += 4
+
+        self.texture = key >> 1
+        self.blendfunc = key & 1
+        self.nb_vertices = nb_vertices
+        self.vertex_buffer = <Vertex*> realloc(vertex_buffer, nb_vertices * sizeof(Vertex))
+
+        if not self.use_fixed_pipeline:
+            glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
+            glBufferData(GL_ARRAY_BUFFER, nb_vertices * sizeof(Vertex), &self.vertex_buffer[0], GL_STATIC_DRAW)
+            glBindBuffer(GL_ARRAY_BUFFER, 0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/gamerenderer.pxd	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,15 @@
+from pytouhou.utils.matrix cimport Matrix
+from pytouhou.game.game cimport Game
+from .background cimport BackgroundRenderer
+from .renderer cimport Renderer, Framebuffer
+from .shader cimport Shader
+
+cdef class GameRenderer(Renderer):
+    cdef Matrix game_mvp, interface_mvp, proj
+    cdef Shader game_shader, background_shader, interface_shader, passthrough_shader
+    cdef Framebuffer framebuffer
+    cdef BackgroundRenderer background_renderer
+
+    cdef void render_game(self, Game game) except *
+    cdef void render_text(self, texts) except *
+    cdef void render_interface(self, interface, game_boss) except *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/gamerenderer.pyx	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,250 @@
+# -*- 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 itertools import chain
+
+from pytouhou.lib.opengl cimport \
+         (glClear, glMatrixMode, glLoadIdentity, glLoadMatrixf, glDisable,
+          glEnable, glFogi, glFogf, glFogfv, GL_PROJECTION, GL_MODELVIEW,
+          GL_FOG, GL_FOG_MODE, GL_LINEAR, GL_FOG_START, GL_FOG_END,
+          GL_FOG_COLOR, GL_COLOR_BUFFER_BIT, GLfloat, glViewport, glScissor,
+          GL_SCISSOR_TEST, GL_DEPTH_BUFFER_BIT)
+
+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')
+Color = namedtuple('Color', 'r g b a')
+
+
+cdef class GameRenderer(Renderer):
+    def __init__(self, resource_loader, window):
+        self.use_fixed_pipeline = window.use_fixed_pipeline #XXX
+
+        Renderer.__init__(self, resource_loader)
+
+        if not self.use_fixed_pipeline:
+            self.game_shader = GameShader()
+            self.background_shader = BackgroundShader()
+            self.interface_shader = self.game_shader
+            self.passthrough_shader = PassthroughShader()
+
+            self.framebuffer = Framebuffer(0, 0, 640, 480)
+
+
+    property size:
+        # We never need to get back the computed size, so size is write-only.
+        def __set__(self, tuple size):
+            self.x, self.y, self.width, self.height = size
+
+
+    def load_textures(self, dict anms):
+        self.texture_manager.load(anms)
+
+
+    def load_background(self, background):
+        if background is not None:
+            self.background_renderer = BackgroundRenderer(self.use_fixed_pipeline)
+            self.background_renderer.load(background)
+        else:
+            self.background_renderer = None
+
+
+    def start(self, common):
+        self.proj = perspective(30, float(common.width) / float(common.height),
+                                101010101./2010101., 101010101./10101.)
+        game_view = setup_camera(0, 0, 1)
+        self.game_mvp = game_view * self.proj
+        self.interface_mvp = ortho_2d(0., float(common.interface.width),
+                                      float(common.interface.height), 0.)
+
+
+    def render(self, Game game):
+        if not self.use_fixed_pipeline:
+            self.framebuffer.bind()
+
+        self.render_game(game)
+        self.render_text(game.texts + game.native_texts)
+        self.render_interface(game.interface, game.boss)
+
+        if not self.use_fixed_pipeline:
+            self.passthrough_shader.bind()
+            self.passthrough_shader.uniform_matrix('mvp', self.interface_mvp)
+            self.render_framebuffer(self.framebuffer)
+
+
+    cdef void render_game(self, Game game):
+        cdef long game_x, game_y
+        cdef float x, y, z, dx, dy, dz, fog_data[4], fog_start, fog_end
+        cdef unsigned char fog_r, fog_g, fog_b
+        cdef Matrix mvp
+
+        game_x, game_y = game.interface.game_pos
+        glViewport(game_x, game_y, game.width, game.height)
+        glClear(GL_DEPTH_BUFFER_BIT)
+        glScissor(game_x, game_y, game.width, game.height)
+        glEnable(GL_SCISSOR_TEST)
+
+        if self.use_fixed_pipeline:
+            glMatrixMode(GL_PROJECTION)
+            glLoadIdentity()
+
+        if game is not None and game.spellcard_effect is not None:
+            if self.use_fixed_pipeline:
+                glMatrixMode(GL_MODELVIEW)
+                glLoadMatrixf(self.game_mvp.data)
+                glDisable(GL_FOG)
+            else:
+                self.game_shader.bind()
+                self.game_shader.uniform_matrix('mvp', self.game_mvp)
+
+            self.render_elements([game.spellcard_effect])
+        elif self.background_renderer is not None:
+            back = self.background_renderer.background
+            x, y, z = back.position_interpolator.values
+            dx, dy, dz = back.position2_interpolator.values
+            fog_b, fog_g, fog_r, fog_start, fog_end = back.fog_interpolator.values
+
+            # Those two lines may come from the difference between Direct3D and
+            # OpenGL’s distance handling.  The first one seem to calculate fog
+            # from the eye, while the second does that starting from the near
+            # plane.
+            #TODO: investigate, and use a variable to keep the near plane
+            # distance at a single place.
+            fog_start -= 101010101./2010101.
+            fog_end -= 101010101./2010101.
+
+            model = Matrix()
+            model.data[12] = -x
+            model.data[13] = -y
+            model.data[14] = -z
+            view = setup_camera(dx, dy, dz)
+            mvp = model * view * self.proj
+
+            if self.use_fixed_pipeline:
+                glMatrixMode(GL_MODELVIEW)
+                glLoadMatrixf(mvp.data)
+
+                glEnable(GL_FOG)
+                glFogi(GL_FOG_MODE, GL_LINEAR)
+                glFogf(GL_FOG_START, fog_start)
+                glFogf(GL_FOG_END,  fog_end)
+
+                fog_data[0] = fog_r / 255.
+                fog_data[1] = fog_g / 255.
+                fog_data[2] = fog_b / 255.
+                fog_data[3] = 1.
+                glFogfv(GL_FOG_COLOR, fog_data)
+            else:
+                self.background_shader.bind()
+                self.background_shader.uniform_matrix('mvp', mvp)
+
+                self.background_shader.uniform_1('fog_scale', 1. / (fog_end - fog_start))
+                self.background_shader.uniform_1('fog_end', fog_end)
+                self.background_shader.uniform_4('fog_color', fog_r / 255., fog_g / 255., fog_b / 255., 1.)
+
+            self.background_renderer.render_background()
+        else:
+            glClear(GL_COLOR_BUFFER_BIT)
+
+        if game is not None:
+            if self.use_fixed_pipeline:
+                glMatrixMode(GL_MODELVIEW)
+                glLoadMatrixf(self.game_mvp.data)
+                glDisable(GL_FOG)
+            else:
+                self.game_shader.bind()
+                self.game_shader.uniform_matrix('mvp', self.game_mvp)
+
+            self.render_elements([enemy for enemy in game.enemies if enemy.visible])
+            self.render_elements(game.effects)
+            self.render_elements(chain(game.players_bullets,
+                                       game.lasers_sprites(),
+                                       game.players,
+                                       game.msg_sprites()))
+            self.render_elements(chain(game.bullets, game.lasers,
+                                       game.cancelled_bullets, game.items,
+                                       game.labels))
+
+        if game.msg_runner is not None:
+            rect = Rect(48, 368, 288, 48)
+            color1 = Color(0, 0, 0, 192)
+            color2 = Color(0, 0, 0, 128)
+            self.render_quads([rect], [(color1, color1, color2, color2)], 0)
+
+        glDisable(GL_SCISSOR_TEST)
+
+
+    cdef void render_text(self, texts):
+        cdef NativeText label
+
+        if self.font_manager is None:
+            return
+
+        labels = [label for label in texts if label is not None]
+        self.font_manager.load(labels)
+
+        black = Color(0, 0, 0, 255)
+
+        for label in labels:
+            if label is None:
+                continue
+
+            rect = Rect(label.x, label.y, label.width, label.height)
+            gradient = [Color(*color, a=label.alpha) for color in label.gradient]
+
+            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], (<Texture>label.texture).texture)
+            else:
+                self.render_quads([rect], [gradient], (<Texture>label.texture).texture)
+
+
+    cdef void render_interface(self, interface, game_boss):
+        cdef GlyphCollection label
+
+        elements = []
+
+        if self.use_fixed_pipeline:
+            glMatrixMode(GL_MODELVIEW)
+            glLoadMatrixf(self.interface_mvp.data)
+            glDisable(GL_FOG)
+        else:
+            self.interface_shader.bind()
+            self.interface_shader.uniform_matrix('mvp', self.interface_mvp)
+        glViewport(0, 0, interface.width, interface.height)
+
+        items = [item for item in interface.items if item.anmrunner and item.anmrunner.running]
+        labels = interface.labels.values()
+
+        if items:
+            # Redraw all the interface
+            elements.extend(items)
+        else:
+            # Redraw only changed labels
+            labels = [label for label in labels if label.changed]
+
+        elements.extend(interface.level_start)
+
+        if game_boss is not None:
+            elements.extend(interface.boss_items)
+
+        elements.extend(labels)
+        self.render_elements(elements)
+        for label in labels:
+            label.changed = False
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/renderer.pxd	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,43 @@
+from cpython cimport PyObject
+from pytouhou.lib.opengl cimport GLuint
+from .texture cimport TextureManager, FontManager
+
+cdef struct Vertex:
+    int x, y, z
+    float u, v
+    unsigned char r, g, b, a
+
+
+cdef struct PassthroughVertex:
+    int x, y
+    float u, v
+
+
+cdef class Texture:
+    cdef GLuint texture
+    cdef unsigned short indices[2][65536]
+
+
+cdef class Renderer:
+    cdef TextureManager texture_manager
+    cdef FontManager font_manager
+    cdef GLuint vbo, framebuffer_vbo
+    cdef Vertex vertex_buffer[MAX_ELEMENTS]
+    cdef long x, y, width, height
+
+    cdef bint use_fixed_pipeline #XXX
+
+    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, GLuint texture) except *
+    cdef void render_framebuffer(self, Framebuffer fb) except *
+
+
+cdef class Framebuffer:
+    cdef GLuint fbo, texture, rbo
+    cdef int x, y, width, height
+
+    cpdef bind(self)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/renderer.pyx	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,286 @@
+# -*- 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_INT, GL_FLOAT, GL_SRC_ALPHA,
+          GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO, GL_TEXTURE_2D, GL_TRIANGLES,
+          glGenBuffers, glBindFramebuffer, glViewport, glDeleteBuffers,
+          glGenTextures, glTexParameteri, glTexImage2D, glGenRenderbuffers,
+          glBindRenderbuffer, glRenderbufferStorage, glGenFramebuffers,
+          glFramebufferTexture2D, glFramebufferRenderbuffer,
+          glCheckFramebufferStatus, GL_FRAMEBUFFER, GL_TEXTURE_MIN_FILTER,
+          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, GLuint, glDeleteTextures)
+
+from pytouhou.lib.sdl import SDLError
+
+from pytouhou.game.element cimport Element
+from .sprite cimport get_sprite_rendering_data
+
+from pytouhou.utils.helpers import get_logger
+
+logger = get_logger(__name__)
+
+
+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:
+    # 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 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, 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 self.use_fixed_pipeline:
+            glGenBuffers(1, &self.vbo)
+            glGenBuffers(1, &self.framebuffer_vbo)
+
+
+    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))
+
+        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 & 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 = vertices
+            left, right, bottom, top = uvs
+            r, g, b, a = colors
+            self.vertex_buffer[nb_vertices] = Vertex(x1 + ox, y1 + oy, z1, left, bottom, r, g, b, a)
+            self.vertex_buffer[nb_vertices+1] = Vertex(x2 + ox, y2 + oy, z2, right, bottom, r, g, b, a)
+            self.vertex_buffer[nb_vertices+2] = Vertex(x3 + ox, y3 + oy, z3, right, top, r, g, b, a)
+            self.vertex_buffer[nb_vertices+3] = Vertex(x4 + ox, y4 + oy, z4, left, top, r, g, b, a)
+
+            # Add indices
+            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 + 2
+            rec[next_indice+4] = nb_vertices + 3
+            rec[next_indice+5] = nb_vertices
+            self.last_indices[key] += 6
+
+            nb_vertices += 4
+
+        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)
+            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)
+
+            #TODO: find a way to use offsetof() instead of those ugly hardcoded values.
+            glVertexAttribPointer(0, 3, GL_INT, False, sizeof(Vertex), <void*>0)
+            glEnableVertexAttribArray(0)
+            glVertexAttribPointer(1, 2, GL_FLOAT, False, sizeof(Vertex), <void*>12)
+            glEnableVertexAttribArray(1)
+            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 & 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, 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, 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]
+        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] = Vertex(r.x, r.y, 0, 0, 0, c1.r, c1.g, c1.b, c1.a)
+            buf[4*i+1] = Vertex(r.x + r.w, r.y, 0, 1, 0, c2.r, c2.g, c2.b, c2.a)
+            buf[4*i+2] = Vertex(r.x + r.w, r.y + r.h, 0, 1, 1, c3.r, c3.g, c3.b, c3.a)
+            buf[4*i+3] = Vertex(r.x, r.y + r.h, 0, 0, 1, c4.r, c4.g, c4.b, c4.a)
+
+        if self.use_fixed_pipeline:
+            glVertexPointer(3, GL_INT, sizeof(Vertex), &buf[0].x)
+            glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &buf[0].u)
+            glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), &buf[0].r)
+        else:
+            glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
+            glBufferData(GL_ARRAY_BUFFER, 4 * length * sizeof(Vertex), buf, GL_DYNAMIC_DRAW)
+
+            #TODO: find a way to use offsetof() instead of those ugly hardcoded values.
+            glVertexAttribPointer(0, 3, GL_INT, False, sizeof(Vertex), <void*>0)
+            glEnableVertexAttribArray(0)
+            glVertexAttribPointer(1, 2, GL_FLOAT, False, sizeof(Vertex), <void*>12)
+            glEnableVertexAttribArray(1)
+            glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, True, sizeof(Vertex), <void*>20)
+            glEnableVertexAttribArray(2)
+
+        glBindTexture(GL_TEXTURE_2D, texture)
+        glDrawElements(GL_TRIANGLES, 6 * length, GL_UNSIGNED_SHORT, indices)
+
+        if not self.use_fixed_pipeline:
+            glBindBuffer(GL_ARRAY_BUFFER, 0)
+
+
+    cdef void render_framebuffer(self, Framebuffer fb):
+        cdef PassthroughVertex[4] buf
+        cdef unsigned short indices[6]
+        indices[:] = [0, 1, 2, 2, 3, 0]
+
+        assert not self.use_fixed_pipeline
+
+        glBindFramebuffer(GL_FRAMEBUFFER, 0)
+        glViewport(self.x, self.y, self.width, self.height)
+        glBlendFunc(GL_ONE, GL_ZERO)
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+
+        glBindBuffer(GL_ARRAY_BUFFER, self.framebuffer_vbo)
+
+        #TODO: find a way to use offsetof() instead of those ugly hardcoded values.
+        glVertexAttribPointer(0, 2, GL_INT, False, sizeof(PassthroughVertex), <void*>0)
+        glEnableVertexAttribArray(0)
+        glVertexAttribPointer(1, 2, GL_FLOAT, False, sizeof(PassthroughVertex), <void*>8)
+        glEnableVertexAttribArray(1)
+
+        buf[0] = PassthroughVertex(fb.x, fb.y, 0, 1)
+        buf[1] = PassthroughVertex(fb.x + fb.width, fb.y, 1, 1)
+        buf[2] = PassthroughVertex(fb.x + fb.width, fb.y + fb.height, 1, 0)
+        buf[3] = PassthroughVertex(fb.x, fb.y + fb.height, 0, 0)
+        glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(PassthroughVertex), buf, GL_DYNAMIC_DRAW)
+
+        glBindTexture(GL_TEXTURE_2D, fb.texture)
+        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices)
+        glBindTexture(GL_TEXTURE_2D, 0)
+
+        glBindBuffer(GL_ARRAY_BUFFER, 0)
+
+
+cdef class Framebuffer:
+    def __init__(self, int x, int y, int width, int height):
+        self.x = x
+        self.y = y
+        self.width = width
+        self.height = height
+
+        glGenTextures(1, &self.texture)
+        glBindTexture(GL_TEXTURE_2D, self.texture)
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
+        glTexImage2D(GL_TEXTURE_2D, 0,
+                     GL_RGBA,
+                     width, height,
+                     0,
+                     GL_RGBA, GL_UNSIGNED_BYTE,
+                     NULL)
+        glBindTexture(GL_TEXTURE_2D, 0)
+
+        glGenRenderbuffers(1, &self.rbo)
+        glBindRenderbuffer(GL_RENDERBUFFER, self.rbo)
+        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height)
+        glBindRenderbuffer(GL_RENDERBUFFER, 0)
+
+        glGenFramebuffers(1, &self.fbo)
+        glBindFramebuffer(GL_FRAMEBUFFER, self.fbo)
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.texture, 0)
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, self.rbo)
+        assert glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE
+        glBindFramebuffer(GL_FRAMEBUFFER, 0)
+
+    cpdef bind(self):
+        glBindFramebuffer(GL_FRAMEBUFFER, self.fbo)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/shader.pxd	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,15 @@
+from pytouhou.lib.opengl cimport GLuint, GLint, GLchar, GLenum, GLfloat
+from pytouhou.utils.matrix cimport Matrix
+
+cdef class Shader:
+    cdef GLuint handle
+    cdef bint linked
+    cdef dict location_cache
+
+    cdef void create_shader(self, const GLchar *string, GLenum shader_type) except *
+    cdef void link(self) except *
+    cdef GLint get_uniform_location(self, name) except -1
+    cdef void bind(self) nogil
+    cdef void uniform_1(self, name, GLfloat val) except *
+    cdef void uniform_4(self, name, GLfloat a, GLfloat b, GLfloat c, GLfloat d) except *
+    cdef void uniform_matrix(self, name, Matrix mat) except *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/shader.pyx	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,142 @@
+# -*- encoding: utf-8 -*-
+#
+# Copyright Tristam Macdonald 2008.
+# Copyright Emmanuel Gil Peyrot 2012.
+#
+# Distributed under the Boost Software License, Version 1.0
+# (see http://www.boost.org/LICENSE_1_0.txt)
+#
+# Source: https://swiftcoder.wordpress.com/2008/12/19/simple-glsl-wrapper-for-pyglet/
+#
+
+from pytouhou.lib.opengl cimport \
+         (glCreateProgram, glCreateShader, GL_VERTEX_SHADER,
+          GL_FRAGMENT_SHADER, glShaderSource, glCompileShader, glGetShaderiv,
+          GL_COMPILE_STATUS, GL_INFO_LOG_LENGTH, glGetShaderInfoLog,
+          glAttachShader, glLinkProgram, glGetProgramiv, glGetProgramInfoLog,
+          GL_LINK_STATUS, glUseProgram, glGetUniformLocation, glUniform1fv,
+          glUniform4fv, glUniformMatrix4fv, glBindAttribLocation)
+
+from libc.stdlib cimport malloc, free
+
+
+class GLSLException(Exception):
+    pass
+
+
+cdef class Shader:
+    # vert and frag take arrays of source strings the arrays will be
+    # concattenated into one string by OpenGL
+    def __init__(self, vert=None, frag=None):
+        # create the program handle
+        self.handle = glCreateProgram()
+        # we are not linked yet
+        self.linked = False
+
+        # cache the uniforms location
+        self.location_cache = {}
+
+        # create the vertex shader
+        self.create_shader(vert[0], GL_VERTEX_SHADER)
+        # create the fragment shader
+        self.create_shader(frag[0], GL_FRAGMENT_SHADER)
+
+        #TODO: put those elsewhere.
+        glBindAttribLocation(self.handle, 0, 'in_position')
+        glBindAttribLocation(self.handle, 1, 'in_texcoord')
+        glBindAttribLocation(self.handle, 2, 'in_color')
+
+        # attempt to link the program
+        self.link()
+
+    cdef void create_shader(self, const GLchar *string, GLenum shader_type):
+        cdef GLint temp
+        cdef const GLchar **strings = &string
+
+        # create the shader handle
+        shader = glCreateShader(shader_type)
+
+        # upload the source strings
+        glShaderSource(shader, 1, strings, NULL)
+
+        # compile the shader
+        glCompileShader(shader)
+
+        # retrieve the compile status
+        glGetShaderiv(shader, GL_COMPILE_STATUS, &temp)
+
+        # if compilation failed, print the log
+        if not temp:
+            # retrieve the log length
+            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &temp)
+            # create a buffer for the log
+            temp_buf = <GLchar*>malloc(temp * sizeof(GLchar))
+            # retrieve the log text
+            glGetShaderInfoLog(shader, temp, NULL, temp_buf)
+            buf = temp_buf[:temp]
+            free(temp_buf)
+            # print the log to the console
+            raise GLSLException(buf)
+        else:
+            # all is well, so attach the shader to the program
+            glAttachShader(self.handle, shader)
+
+    cdef void link(self):
+        cdef GLint temp
+
+        # link the program
+        glLinkProgram(self.handle)
+
+        # retrieve the link status
+        glGetProgramiv(self.handle, GL_LINK_STATUS, &temp)
+
+        # if linking failed, print the log
+        if not temp:
+            #   retrieve the log length
+            glGetProgramiv(self.handle, GL_INFO_LOG_LENGTH, &temp)
+            # create a buffer for the log
+            temp_buf = <GLchar*>malloc(temp * sizeof(GLchar))
+            # retrieve the log text
+            glGetProgramInfoLog(self.handle, temp, NULL, temp_buf)
+            buf = temp_buf[:temp]
+            free(temp_buf)
+            # print the log to the console
+            raise GLSLException(buf)
+        else:
+            # all is well, so we are linked
+            self.linked = True
+
+    cdef GLint get_uniform_location(self, name):
+        if name not in self.location_cache:
+            loc = glGetUniformLocation(self.handle, name)
+            if loc == -1:
+                raise GLSLException('Undefined {} uniform.'.format(name))
+            self.location_cache[name] = loc
+        return self.location_cache[name]
+
+    cdef void bind(self) nogil:
+        # bind the program
+        glUseProgram(self.handle)
+
+    # upload a floating point uniform
+    # this program must be currently bound
+    cdef void uniform_1(self, name, GLfloat val):
+        glUniform1fv(self.get_uniform_location(name), 1, &val)
+
+    # upload a vec4 uniform
+    cdef void uniform_4(self, name, GLfloat a, GLfloat b, GLfloat c, GLfloat d):
+        cdef GLfloat vals[4]
+        vals[0] = a
+        vals[1] = b
+        vals[2] = c
+        vals[3] = d
+        glUniform4fv(self.get_uniform_location(name), 1, vals)
+
+    # upload a uniform matrix
+    # works with matrices stored as lists,
+    # as well as euclid matrices
+    cdef void uniform_matrix(self, name, Matrix mat):
+        # obtain the uniform location
+        loc = self.get_uniform_location(name)
+        # uplaod the 4x4 floating point matrix
+        glUniformMatrix4fv(loc, 1, False, mat.data)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/shaders/eosd.py	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,123 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2012 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+##
+## 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 ..shader import Shader
+
+
+class GameShader(Shader):
+    def __init__(self):
+        Shader.__init__(self, ['''
+            #version 120
+
+            attribute vec3 in_position;
+            attribute vec2 in_texcoord;
+            attribute vec4 in_color;
+
+            uniform mat4 mvp;
+
+            varying vec2 texcoord;
+            varying vec4 color;
+
+            void main()
+            {
+                gl_Position = mvp * vec4(in_position, 1.0);
+                texcoord = in_texcoord;
+                color = in_color;
+            }
+        '''], ['''
+            #version 120
+
+            varying vec2 texcoord;
+            varying vec4 color;
+
+            uniform sampler2D color_map;
+
+            void main()
+            {
+                gl_FragColor = texture2D(color_map, texcoord) * color;
+            }
+        '''])
+
+
+class BackgroundShader(Shader):
+    def __init__(self):
+        Shader.__init__(self, ['''
+            #version 120
+
+            attribute vec3 in_position;
+            attribute vec2 in_texcoord;
+            attribute vec4 in_color;
+
+            uniform mat4 mvp;
+
+            varying vec2 texcoord;
+            varying vec4 color;
+
+            void main()
+            {
+                gl_Position = mvp * vec4(in_position, 1.0);
+                texcoord = in_texcoord;
+                color = in_color;
+            }
+        '''], ['''
+            #version 120
+
+            varying vec2 texcoord;
+            varying vec4 color;
+
+            uniform sampler2D color_map;
+            uniform float fog_scale;
+            uniform float fog_end;
+            uniform vec4 fog_color;
+
+            void main()
+            {
+                vec4 temp_color = texture2D(color_map, texcoord) * color;
+                float depth = gl_FragCoord.z / gl_FragCoord.w;
+                float fog_density = clamp((fog_end - depth) * fog_scale, 0.0f, 1.0f);
+                gl_FragColor = vec4(mix(fog_color, temp_color, fog_density).rgb, temp_color.a);
+            }
+        '''])
+
+
+class PassthroughShader(Shader):
+    def __init__(self):
+        Shader.__init__(self, ['''
+            #version 120
+
+            attribute vec2 in_position;
+            attribute vec2 in_texcoord;
+
+            uniform mat4 mvp;
+
+            varying vec2 texcoord;
+
+            void main()
+            {
+                gl_Position = mvp * vec4(in_position, 0.0, 1.0);
+                texcoord = in_texcoord;
+            }
+        '''], ['''
+            #version 120
+
+            varying vec2 texcoord;
+
+            uniform sampler2D color_map;
+
+            void main()
+            {
+                gl_FragColor = texture2D(color_map, texcoord);
+            }
+        '''])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/sprite.pxd	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,3 @@
+from pytouhou.game.sprite cimport Sprite
+
+cpdef object get_sprite_rendering_data(Sprite sprite)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/sprite.pyx	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,76 @@
+# -*- 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.math cimport M_PI as pi
+
+from pytouhou.utils.matrix cimport Matrix
+from .renderer cimport Texture #XXX
+
+
+cpdef object get_sprite_rendering_data(Sprite sprite):
+    cdef double tx, ty, tw, th, sx, sy, rx, ry, rz, tox, toy
+    cdef object tmp1, tmp2
+
+    if not sprite.changed:
+        return sprite._rendering_data
+
+    tmp1 = .5
+    tmp2 = -.5
+    vertmat = Matrix([tmp2, tmp1, tmp1, tmp2,
+                      tmp2, tmp2, tmp1, tmp1,
+                      0,    0,    0,    0,
+                      1,    1,    1,    1])
+
+    tx, ty, tw, th = sprite.texcoords
+    sx, sy = sprite.rescale
+    width = sprite.width_override or (tw * sx)
+    height = sprite.height_override or (th * sy)
+
+    vertmat.scale2d(width, height)
+    if sprite.mirrored:
+        vertmat.flip()
+
+    rx, ry, rz = sprite.rotations_3d
+    if sprite.automatic_orientation:
+        rz += pi/2. - sprite.angle
+    elif sprite.force_rotation:
+        rz += sprite.angle
+
+    if rx:
+        vertmat.rotate_x(-rx)
+    if ry:
+        vertmat.rotate_y(ry)
+    if rz:
+        vertmat.rotate_z(-rz) #TODO: minus, really?
+    if sprite.allow_dest_offset:
+        vertmat.translate(sprite.dest_offset[0], sprite.dest_offset[1], sprite.dest_offset[2])
+    if sprite.corner_relative_placement: # Reposition
+        vertmat.translate(width / 2, height / 2, 0)
+
+    x_1 = 1 / <double>sprite.anm.size[0]
+    y_1 = 1 / <double>sprite.anm.size[1]
+    tox, toy = sprite.texoffsets
+    uvs = (tx * x_1 + tox,
+           (tx + tw) * x_1 + tox,
+           ty * y_1 + toy,
+           (ty + th) * y_1 + toy)
+
+    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
+    sprite.changed = False
+
+    return sprite._rendering_data
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/texture.pxd	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,12 @@
+from pytouhou.lib.sdl cimport Font
+
+cdef class TextureManager:
+    cdef object loader, renderer, texture_class
+
+    cdef void load(self, dict anms) except *
+
+cdef class FontManager:
+    cdef Font font
+    cdef object renderer, texture_class
+
+    cdef void load(self, list labels) except *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/ui/opengl/texture.pyx	Thu Dec 05 02:16:31 2013 +0100
@@ -0,0 +1,135 @@
+# -*- 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 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)
+
+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
+
+
+cdef class TextureManager:
+    def __init__(self, loader=None, renderer=None, texture_class=None):
+        self.loader = loader
+        self.renderer = renderer
+        self.texture_class = texture_class
+
+
+    cdef void 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)
+                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:
+    def __init__(self, fontname, fontsize=16, renderer=None, texture_class=None):
+        self.font = Font(fontname, fontsize)
+        self.renderer = renderer
+        self.texture_class = texture_class
+
+
+    cdef void load(self, list labels):
+        cdef NativeText label
+
+        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
+
+                if label.align == 'center':
+                    label.x -= label.width // 2
+                elif label.align == 'right':
+                    label.x -= label.width
+                else:
+                    assert label.align == 'left'
+
+                texture = Texture(label.width, label.height, -4, surface.pixels)
+                label.texture = self.texture_class(load_texture(texture), self.renderer)
+
+
+cdef decode_png(loader, first_name, secondary_name):
+    image_file = load_png(loader.get_file(os.path.basename(first_name)))
+    width, height = image_file.surface.w, image_file.surface.h
+
+    # Support only 32 bits RGBA. Paletted surfaces are awful to work with.
+    #TODO: verify it doesn’t blow up on big-endian systems.
+    new_image = create_rgb_surface(width, height, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)
+    new_image.blit(image_file)
+
+    if secondary_name:
+        alpha_file = load_png(loader.get_file(os.path.basename(secondary_name)))
+        assert (width == alpha_file.surface.w and height == alpha_file.surface.h)
+
+        new_alpha_file = create_rgb_surface(width, height, 24)
+        new_alpha_file.blit(alpha_file)
+
+        new_image.set_alpha(new_alpha_file)
+
+    return Texture(width, height, -4, new_image.pixels)
+
+
+cdef GLuint load_texture(thtx):
+    cdef GLuint texture
+
+    if thtx.fmt == 1:
+        format_ = GL_BGRA
+        type_ = GL_UNSIGNED_BYTE
+        composants = GL_RGBA
+    elif thtx.fmt == 3:
+        format_ = GL_RGB
+        type_ = GL_UNSIGNED_SHORT_5_6_5
+        composants = GL_RGB
+    elif thtx.fmt == 5:
+        format_ = GL_BGRA
+        type_ = GL_UNSIGNED_SHORT_4_4_4_4_REV
+        composants = GL_RGBA
+    elif thtx.fmt == 7:
+        format_ = GL_LUMINANCE
+        type_ = GL_UNSIGNED_BYTE
+        composants = GL_LUMINANCE
+    elif thtx.fmt == -4: #XXX: non-standard
+        format_ = GL_RGBA
+        type_ = GL_UNSIGNED_BYTE
+        composants = GL_RGBA
+    else:
+        raise Exception('Unknown texture type')
+
+    glGenTextures(1, &texture)
+    glBindTexture(GL_TEXTURE_2D, texture)
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
+
+    glTexImage2D(GL_TEXTURE_2D, 0,
+                 composants,
+                 thtx.width, thtx.height,
+                 0,
+                 format_, type_,
+                 <char*>thtx.data)
+
+    return texture
--- a/pytouhou/ui/renderer.pxd	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-from cpython cimport PyObject
-from pytouhou.lib.opengl cimport GLuint
-from .texture cimport TextureManager, FontManager
-
-cdef struct Vertex:
-    int x, y, z
-    float u, v
-    unsigned char r, g, b, a
-
-
-cdef struct PassthroughVertex:
-    int x, y
-    float u, v
-
-
-cdef class Texture:
-    cdef GLuint texture
-    cdef unsigned short indices[2][65536]
-
-
-cdef class Renderer:
-    cdef TextureManager texture_manager
-    cdef FontManager font_manager
-    cdef GLuint vbo, framebuffer_vbo
-    cdef Vertex vertex_buffer[MAX_ELEMENTS]
-    cdef long x, y, width, height
-
-    cdef bint use_fixed_pipeline #XXX
-
-    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, GLuint texture) except *
-    cdef void render_framebuffer(self, Framebuffer fb) except *
-
-
-cdef class Framebuffer:
-    cdef GLuint fbo, texture, rbo
-    cdef int x, y, width, height
-
-    cpdef bind(self)
--- a/pytouhou/ui/renderer.pyx	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,286 +0,0 @@
-# -*- 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_INT, GL_FLOAT, GL_SRC_ALPHA,
-          GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO, GL_TEXTURE_2D, GL_TRIANGLES,
-          glGenBuffers, glBindFramebuffer, glViewport, glDeleteBuffers,
-          glGenTextures, glTexParameteri, glTexImage2D, glGenRenderbuffers,
-          glBindRenderbuffer, glRenderbufferStorage, glGenFramebuffers,
-          glFramebufferTexture2D, glFramebufferRenderbuffer,
-          glCheckFramebufferStatus, GL_FRAMEBUFFER, GL_TEXTURE_MIN_FILTER,
-          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, GLuint, glDeleteTextures)
-
-from pytouhou.lib.sdl import SDLError
-
-from pytouhou.game.element cimport Element
-from .sprite cimport get_sprite_rendering_data
-
-from pytouhou.utils.helpers import get_logger
-
-logger = get_logger(__name__)
-
-
-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:
-    # 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 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, 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 self.use_fixed_pipeline:
-            glGenBuffers(1, &self.vbo)
-            glGenBuffers(1, &self.framebuffer_vbo)
-
-
-    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))
-
-        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 & 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 = vertices
-            left, right, bottom, top = uvs
-            r, g, b, a = colors
-            self.vertex_buffer[nb_vertices] = Vertex(x1 + ox, y1 + oy, z1, left, bottom, r, g, b, a)
-            self.vertex_buffer[nb_vertices+1] = Vertex(x2 + ox, y2 + oy, z2, right, bottom, r, g, b, a)
-            self.vertex_buffer[nb_vertices+2] = Vertex(x3 + ox, y3 + oy, z3, right, top, r, g, b, a)
-            self.vertex_buffer[nb_vertices+3] = Vertex(x4 + ox, y4 + oy, z4, left, top, r, g, b, a)
-
-            # Add indices
-            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 + 2
-            rec[next_indice+4] = nb_vertices + 3
-            rec[next_indice+5] = nb_vertices
-            self.last_indices[key] += 6
-
-            nb_vertices += 4
-
-        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)
-            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)
-
-            #TODO: find a way to use offsetof() instead of those ugly hardcoded values.
-            glVertexAttribPointer(0, 3, GL_INT, False, sizeof(Vertex), <void*>0)
-            glEnableVertexAttribArray(0)
-            glVertexAttribPointer(1, 2, GL_FLOAT, False, sizeof(Vertex), <void*>12)
-            glEnableVertexAttribArray(1)
-            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 & 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, 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, 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]
-        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] = Vertex(r.x, r.y, 0, 0, 0, c1.r, c1.g, c1.b, c1.a)
-            buf[4*i+1] = Vertex(r.x + r.w, r.y, 0, 1, 0, c2.r, c2.g, c2.b, c2.a)
-            buf[4*i+2] = Vertex(r.x + r.w, r.y + r.h, 0, 1, 1, c3.r, c3.g, c3.b, c3.a)
-            buf[4*i+3] = Vertex(r.x, r.y + r.h, 0, 0, 1, c4.r, c4.g, c4.b, c4.a)
-
-        if self.use_fixed_pipeline:
-            glVertexPointer(3, GL_INT, sizeof(Vertex), &buf[0].x)
-            glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &buf[0].u)
-            glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), &buf[0].r)
-        else:
-            glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
-            glBufferData(GL_ARRAY_BUFFER, 4 * length * sizeof(Vertex), buf, GL_DYNAMIC_DRAW)
-
-            #TODO: find a way to use offsetof() instead of those ugly hardcoded values.
-            glVertexAttribPointer(0, 3, GL_INT, False, sizeof(Vertex), <void*>0)
-            glEnableVertexAttribArray(0)
-            glVertexAttribPointer(1, 2, GL_FLOAT, False, sizeof(Vertex), <void*>12)
-            glEnableVertexAttribArray(1)
-            glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, True, sizeof(Vertex), <void*>20)
-            glEnableVertexAttribArray(2)
-
-        glBindTexture(GL_TEXTURE_2D, texture)
-        glDrawElements(GL_TRIANGLES, 6 * length, GL_UNSIGNED_SHORT, indices)
-
-        if not self.use_fixed_pipeline:
-            glBindBuffer(GL_ARRAY_BUFFER, 0)
-
-
-    cdef void render_framebuffer(self, Framebuffer fb):
-        cdef PassthroughVertex[4] buf
-        cdef unsigned short indices[6]
-        indices[:] = [0, 1, 2, 2, 3, 0]
-
-        assert not self.use_fixed_pipeline
-
-        glBindFramebuffer(GL_FRAMEBUFFER, 0)
-        glViewport(self.x, self.y, self.width, self.height)
-        glBlendFunc(GL_ONE, GL_ZERO)
-        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
-
-        glBindBuffer(GL_ARRAY_BUFFER, self.framebuffer_vbo)
-
-        #TODO: find a way to use offsetof() instead of those ugly hardcoded values.
-        glVertexAttribPointer(0, 2, GL_INT, False, sizeof(PassthroughVertex), <void*>0)
-        glEnableVertexAttribArray(0)
-        glVertexAttribPointer(1, 2, GL_FLOAT, False, sizeof(PassthroughVertex), <void*>8)
-        glEnableVertexAttribArray(1)
-
-        buf[0] = PassthroughVertex(fb.x, fb.y, 0, 1)
-        buf[1] = PassthroughVertex(fb.x + fb.width, fb.y, 1, 1)
-        buf[2] = PassthroughVertex(fb.x + fb.width, fb.y + fb.height, 1, 0)
-        buf[3] = PassthroughVertex(fb.x, fb.y + fb.height, 0, 0)
-        glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(PassthroughVertex), buf, GL_DYNAMIC_DRAW)
-
-        glBindTexture(GL_TEXTURE_2D, fb.texture)
-        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices)
-        glBindTexture(GL_TEXTURE_2D, 0)
-
-        glBindBuffer(GL_ARRAY_BUFFER, 0)
-
-
-cdef class Framebuffer:
-    def __init__(self, int x, int y, int width, int height):
-        self.x = x
-        self.y = y
-        self.width = width
-        self.height = height
-
-        glGenTextures(1, &self.texture)
-        glBindTexture(GL_TEXTURE_2D, self.texture)
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
-        glTexImage2D(GL_TEXTURE_2D, 0,
-                     GL_RGBA,
-                     width, height,
-                     0,
-                     GL_RGBA, GL_UNSIGNED_BYTE,
-                     NULL)
-        glBindTexture(GL_TEXTURE_2D, 0)
-
-        glGenRenderbuffers(1, &self.rbo)
-        glBindRenderbuffer(GL_RENDERBUFFER, self.rbo)
-        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height)
-        glBindRenderbuffer(GL_RENDERBUFFER, 0)
-
-        glGenFramebuffers(1, &self.fbo)
-        glBindFramebuffer(GL_FRAMEBUFFER, self.fbo)
-        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.texture, 0)
-        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, self.rbo)
-        assert glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE
-        glBindFramebuffer(GL_FRAMEBUFFER, 0)
-
-    cpdef bind(self):
-        glBindFramebuffer(GL_FRAMEBUFFER, self.fbo)
--- a/pytouhou/ui/shader.pxd	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-from pytouhou.lib.opengl cimport GLuint, GLint, GLchar, GLenum, GLfloat
-from pytouhou.utils.matrix cimport Matrix
-
-cdef class Shader:
-    cdef GLuint handle
-    cdef bint linked
-    cdef dict location_cache
-
-    cdef void create_shader(self, const GLchar *string, GLenum shader_type) except *
-    cdef void link(self) except *
-    cdef GLint get_uniform_location(self, name) except -1
-    cdef void bind(self) nogil
-    cdef void uniform_1(self, name, GLfloat val) except *
-    cdef void uniform_4(self, name, GLfloat a, GLfloat b, GLfloat c, GLfloat d) except *
-    cdef void uniform_matrix(self, name, Matrix mat) except *
--- a/pytouhou/ui/shader.pyx	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-# -*- encoding: utf-8 -*-
-#
-# Copyright Tristam Macdonald 2008.
-# Copyright Emmanuel Gil Peyrot 2012.
-#
-# Distributed under the Boost Software License, Version 1.0
-# (see http://www.boost.org/LICENSE_1_0.txt)
-#
-# Source: https://swiftcoder.wordpress.com/2008/12/19/simple-glsl-wrapper-for-pyglet/
-#
-
-from pytouhou.lib.opengl cimport \
-         (glCreateProgram, glCreateShader, GL_VERTEX_SHADER,
-          GL_FRAGMENT_SHADER, glShaderSource, glCompileShader, glGetShaderiv,
-          GL_COMPILE_STATUS, GL_INFO_LOG_LENGTH, glGetShaderInfoLog,
-          glAttachShader, glLinkProgram, glGetProgramiv, glGetProgramInfoLog,
-          GL_LINK_STATUS, glUseProgram, glGetUniformLocation, glUniform1fv,
-          glUniform4fv, glUniformMatrix4fv, glBindAttribLocation)
-
-from libc.stdlib cimport malloc, free
-
-
-class GLSLException(Exception):
-    pass
-
-
-cdef class Shader:
-    # vert and frag take arrays of source strings the arrays will be
-    # concattenated into one string by OpenGL
-    def __init__(self, vert=None, frag=None):
-        # create the program handle
-        self.handle = glCreateProgram()
-        # we are not linked yet
-        self.linked = False
-
-        # cache the uniforms location
-        self.location_cache = {}
-
-        # create the vertex shader
-        self.create_shader(vert[0], GL_VERTEX_SHADER)
-        # create the fragment shader
-        self.create_shader(frag[0], GL_FRAGMENT_SHADER)
-
-        #TODO: put those elsewhere.
-        glBindAttribLocation(self.handle, 0, 'in_position')
-        glBindAttribLocation(self.handle, 1, 'in_texcoord')
-        glBindAttribLocation(self.handle, 2, 'in_color')
-
-        # attempt to link the program
-        self.link()
-
-    cdef void create_shader(self, const GLchar *string, GLenum shader_type):
-        cdef GLint temp
-        cdef const GLchar **strings = &string
-
-        # create the shader handle
-        shader = glCreateShader(shader_type)
-
-        # upload the source strings
-        glShaderSource(shader, 1, strings, NULL)
-
-        # compile the shader
-        glCompileShader(shader)
-
-        # retrieve the compile status
-        glGetShaderiv(shader, GL_COMPILE_STATUS, &temp)
-
-        # if compilation failed, print the log
-        if not temp:
-            # retrieve the log length
-            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &temp)
-            # create a buffer for the log
-            temp_buf = <GLchar*>malloc(temp * sizeof(GLchar))
-            # retrieve the log text
-            glGetShaderInfoLog(shader, temp, NULL, temp_buf)
-            buf = temp_buf[:temp]
-            free(temp_buf)
-            # print the log to the console
-            raise GLSLException(buf)
-        else:
-            # all is well, so attach the shader to the program
-            glAttachShader(self.handle, shader)
-
-    cdef void link(self):
-        cdef GLint temp
-
-        # link the program
-        glLinkProgram(self.handle)
-
-        # retrieve the link status
-        glGetProgramiv(self.handle, GL_LINK_STATUS, &temp)
-
-        # if linking failed, print the log
-        if not temp:
-            #   retrieve the log length
-            glGetProgramiv(self.handle, GL_INFO_LOG_LENGTH, &temp)
-            # create a buffer for the log
-            temp_buf = <GLchar*>malloc(temp * sizeof(GLchar))
-            # retrieve the log text
-            glGetProgramInfoLog(self.handle, temp, NULL, temp_buf)
-            buf = temp_buf[:temp]
-            free(temp_buf)
-            # print the log to the console
-            raise GLSLException(buf)
-        else:
-            # all is well, so we are linked
-            self.linked = True
-
-    cdef GLint get_uniform_location(self, name):
-        if name not in self.location_cache:
-            loc = glGetUniformLocation(self.handle, name)
-            if loc == -1:
-                raise GLSLException('Undefined {} uniform.'.format(name))
-            self.location_cache[name] = loc
-        return self.location_cache[name]
-
-    cdef void bind(self) nogil:
-        # bind the program
-        glUseProgram(self.handle)
-
-    # upload a floating point uniform
-    # this program must be currently bound
-    cdef void uniform_1(self, name, GLfloat val):
-        glUniform1fv(self.get_uniform_location(name), 1, &val)
-
-    # upload a vec4 uniform
-    cdef void uniform_4(self, name, GLfloat a, GLfloat b, GLfloat c, GLfloat d):
-        cdef GLfloat vals[4]
-        vals[0] = a
-        vals[1] = b
-        vals[2] = c
-        vals[3] = d
-        glUniform4fv(self.get_uniform_location(name), 1, vals)
-
-    # upload a uniform matrix
-    # works with matrices stored as lists,
-    # as well as euclid matrices
-    cdef void uniform_matrix(self, name, Matrix mat):
-        # obtain the uniform location
-        loc = self.get_uniform_location(name)
-        # uplaod the 4x4 floating point matrix
-        glUniformMatrix4fv(loc, 1, False, mat.data)
--- a/pytouhou/ui/shaders/eosd.py	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +0,0 @@
-# -*- encoding: utf-8 -*-
-##
-## Copyright (C) 2012 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
-##
-## 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 pytouhou.ui.shader import Shader
-
-
-class GameShader(Shader):
-    def __init__(self):
-        Shader.__init__(self, ['''
-            #version 120
-
-            attribute vec3 in_position;
-            attribute vec2 in_texcoord;
-            attribute vec4 in_color;
-
-            uniform mat4 mvp;
-
-            varying vec2 texcoord;
-            varying vec4 color;
-
-            void main()
-            {
-                gl_Position = mvp * vec4(in_position, 1.0);
-                texcoord = in_texcoord;
-                color = in_color;
-            }
-        '''], ['''
-            #version 120
-
-            varying vec2 texcoord;
-            varying vec4 color;
-
-            uniform sampler2D color_map;
-
-            void main()
-            {
-                gl_FragColor = texture2D(color_map, texcoord) * color;
-            }
-        '''])
-
-
-class BackgroundShader(Shader):
-    def __init__(self):
-        Shader.__init__(self, ['''
-            #version 120
-
-            attribute vec3 in_position;
-            attribute vec2 in_texcoord;
-            attribute vec4 in_color;
-
-            uniform mat4 mvp;
-
-            varying vec2 texcoord;
-            varying vec4 color;
-
-            void main()
-            {
-                gl_Position = mvp * vec4(in_position, 1.0);
-                texcoord = in_texcoord;
-                color = in_color;
-            }
-        '''], ['''
-            #version 120
-
-            varying vec2 texcoord;
-            varying vec4 color;
-
-            uniform sampler2D color_map;
-            uniform float fog_scale;
-            uniform float fog_end;
-            uniform vec4 fog_color;
-
-            void main()
-            {
-                vec4 temp_color = texture2D(color_map, texcoord) * color;
-                float depth = gl_FragCoord.z / gl_FragCoord.w;
-                float fog_density = clamp((fog_end - depth) * fog_scale, 0.0f, 1.0f);
-                gl_FragColor = vec4(mix(fog_color, temp_color, fog_density).rgb, temp_color.a);
-            }
-        '''])
-
-
-class PassthroughShader(Shader):
-    def __init__(self):
-        Shader.__init__(self, ['''
-            #version 120
-
-            attribute vec2 in_position;
-            attribute vec2 in_texcoord;
-
-            uniform mat4 mvp;
-
-            varying vec2 texcoord;
-
-            void main()
-            {
-                gl_Position = mvp * vec4(in_position, 0.0, 1.0);
-                texcoord = in_texcoord;
-            }
-        '''], ['''
-            #version 120
-
-            varying vec2 texcoord;
-
-            uniform sampler2D color_map;
-
-            void main()
-            {
-                gl_FragColor = texture2D(color_map, texcoord);
-            }
-        '''])
--- a/pytouhou/ui/sprite.pxd	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-from pytouhou.game.sprite cimport Sprite
-
-cpdef object get_sprite_rendering_data(Sprite sprite)
--- a/pytouhou/ui/sprite.pyx	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-# -*- 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.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):
-    cdef double tx, ty, tw, th, sx, sy, rx, ry, rz, tox, toy
-    cdef object tmp1, tmp2
-
-    if not sprite.changed:
-        return sprite._rendering_data
-
-    tmp1 = .5
-    tmp2 = -.5
-    vertmat = Matrix([tmp2, tmp1, tmp1, tmp2,
-                      tmp2, tmp2, tmp1, tmp1,
-                      0,    0,    0,    0,
-                      1,    1,    1,    1])
-
-    tx, ty, tw, th = sprite.texcoords
-    sx, sy = sprite.rescale
-    width = sprite.width_override or (tw * sx)
-    height = sprite.height_override or (th * sy)
-
-    vertmat.scale2d(width, height)
-    if sprite.mirrored:
-        vertmat.flip()
-
-    rx, ry, rz = sprite.rotations_3d
-    if sprite.automatic_orientation:
-        rz += pi/2. - sprite.angle
-    elif sprite.force_rotation:
-        rz += sprite.angle
-
-    if rx:
-        vertmat.rotate_x(-rx)
-    if ry:
-        vertmat.rotate_y(ry)
-    if rz:
-        vertmat.rotate_z(-rz) #TODO: minus, really?
-    if sprite.allow_dest_offset:
-        vertmat.translate(sprite.dest_offset[0], sprite.dest_offset[1], sprite.dest_offset[2])
-    if sprite.corner_relative_placement: # Reposition
-        vertmat.translate(width / 2, height / 2, 0)
-
-    x_1 = 1 / <double>sprite.anm.size[0]
-    y_1 = 1 / <double>sprite.anm.size[1]
-    tox, toy = sprite.texoffsets
-    uvs = (tx * x_1 + tox,
-           (tx + tw) * x_1 + tox,
-           ty * y_1 + toy,
-           (ty + th) * y_1 + toy)
-
-    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
-    sprite.changed = False
-
-    return sprite._rendering_data
--- a/pytouhou/ui/texture.pxd	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-from pytouhou.lib.sdl cimport Font
-
-cdef class TextureManager:
-    cdef object loader, renderer, texture_class
-
-    cdef void load(self, dict anms) except *
-
-cdef class FontManager:
-    cdef Font font
-    cdef object renderer, texture_class
-
-    cdef void load(self, list labels) except *
--- a/pytouhou/ui/texture.pyx	Thu Dec 05 01:55:39 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-# -*- 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 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)
-
-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
-
-
-cdef class TextureManager:
-    def __init__(self, loader=None, renderer=None, texture_class=None):
-        self.loader = loader
-        self.renderer = renderer
-        self.texture_class = texture_class
-
-
-    cdef void 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)
-                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:
-    def __init__(self, fontname, fontsize=16, renderer=None, texture_class=None):
-        self.font = Font(fontname, fontsize)
-        self.renderer = renderer
-        self.texture_class = texture_class
-
-
-    cdef void load(self, list labels):
-        cdef NativeText label
-
-        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
-
-                if label.align == 'center':
-                    label.x -= label.width // 2
-                elif label.align == 'right':
-                    label.x -= label.width
-                else:
-                    assert label.align == 'left'
-
-                texture = Texture(label.width, label.height, -4, surface.pixels)
-                label.texture = self.texture_class(load_texture(texture), self.renderer)
-
-
-cdef decode_png(loader, first_name, secondary_name):
-    image_file = load_png(loader.get_file(os.path.basename(first_name)))
-    width, height = image_file.surface.w, image_file.surface.h
-
-    # Support only 32 bits RGBA. Paletted surfaces are awful to work with.
-    #TODO: verify it doesn’t blow up on big-endian systems.
-    new_image = create_rgb_surface(width, height, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)
-    new_image.blit(image_file)
-
-    if secondary_name:
-        alpha_file = load_png(loader.get_file(os.path.basename(secondary_name)))
-        assert (width == alpha_file.surface.w and height == alpha_file.surface.h)
-
-        new_alpha_file = create_rgb_surface(width, height, 24)
-        new_alpha_file.blit(alpha_file)
-
-        new_image.set_alpha(new_alpha_file)
-
-    return Texture(width, height, -4, new_image.pixels)
-
-
-cdef GLuint load_texture(thtx):
-    cdef GLuint texture
-
-    if thtx.fmt == 1:
-        format_ = GL_BGRA
-        type_ = GL_UNSIGNED_BYTE
-        composants = GL_RGBA
-    elif thtx.fmt == 3:
-        format_ = GL_RGB
-        type_ = GL_UNSIGNED_SHORT_5_6_5
-        composants = GL_RGB
-    elif thtx.fmt == 5:
-        format_ = GL_BGRA
-        type_ = GL_UNSIGNED_SHORT_4_4_4_4_REV
-        composants = GL_RGBA
-    elif thtx.fmt == 7:
-        format_ = GL_LUMINANCE
-        type_ = GL_UNSIGNED_BYTE
-        composants = GL_LUMINANCE
-    elif thtx.fmt == -4: #XXX: non-standard
-        format_ = GL_RGBA
-        type_ = GL_UNSIGNED_BYTE
-        composants = GL_RGBA
-    else:
-        raise Exception('Unknown texture type')
-
-    glGenTextures(1, &texture)
-    glBindTexture(GL_TEXTURE_2D, texture)
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
-
-    glTexImage2D(GL_TEXTURE_2D, 0,
-                 composants,
-                 thtx.width, thtx.height,
-                 0,
-                 format_, type_,
-                 <char*>thtx.data)
-
-    return texture
--- a/pytouhou/ui/window.pyx	Thu Dec 05 01:55:39 2013 +0100
+++ b/pytouhou/ui/window.pyx	Thu Dec 05 02:16:31 2013 +0100
@@ -12,13 +12,14 @@
 ## GNU General Public License for more details.
 ##
 
-from pytouhou.lib.opengl cimport \
-         (glEnable, glHint, glEnableClientState, GL_TEXTURE_2D, GL_BLEND,
-          GL_PERSPECTIVE_CORRECTION_HINT, GL_FOG_HINT, GL_NICEST,
-          GL_COLOR_ARRAY, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY)
+IF USE_OPENGL:
+    from pytouhou.lib.opengl cimport \
+             (glEnable, glHint, glEnableClientState, GL_TEXTURE_2D, GL_BLEND,
+              GL_PERSPECTIVE_CORRECTION_HINT, GL_FOG_HINT, GL_NICEST,
+              GL_COLOR_ARRAY, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY)
 
-IF USE_GLEW:
-    from pytouhou.lib.opengl cimport glewInit
+    IF USE_GLEW:
+        from pytouhou.lib.opengl cimport glewInit
 
 
 cdef class Clock:
@@ -94,7 +95,7 @@
 
         flags = sdl.WINDOW_SHOWN
 
-        if opengl:
+        if USE_OPENGL and opengl:
             sdl.gl_set_attribute(sdl.GL_CONTEXT_MAJOR_VERSION, 2)
             sdl.gl_set_attribute(sdl.GL_CONTEXT_MINOR_VERSION, 1)
             sdl.gl_set_attribute(sdl.GL_DOUBLEBUFFER, int(double_buffer))
@@ -111,7 +112,7 @@
                               640, 480, #XXX
                               flags)
 
-        if opengl:
+        if USE_OPENGL and opengl:
             self.win.gl_create_context()
 
             IF USE_GLEW:
--- a/setup.py	Thu Dec 05 01:55:39 2013 +0100
+++ b/setup.py	Thu Dec 05 02:16:31 2013 +0100
@@ -4,7 +4,7 @@
 import sys
 from distutils.core import setup
 from distutils.extension import Extension
-from subprocess import check_output
+from subprocess import check_output, CalledProcessError
 
 # Cython is needed
 try:
@@ -17,15 +17,28 @@
 
 COMMAND = 'pkg-config'
 SDL_LIBRARIES = ['sdl2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf']
+GL_LIBRARIES = ['gl']
 
 packages = []
 extension_names = []
 extensions = []
 
 
+# Check for gl.pc, and don’t compile the OpenGL backend if it isn’t present.
+try:
+    check_output([COMMAND] + GL_LIBRARIES)
+except CalledProcessError:
+    use_opengl = False
+else:
+    use_opengl = True
+
+
 def get_arguments(arg, libraries):
     try:
         return check_output([COMMAND, arg] + libraries).split()
+    except CalledProcessError:
+        # The error has already been displayed, just exit.
+        sys.exit(1)
     except OSError:
         print('You don’t seem to have pkg-config installed. Please get a copy '
               'from http://pkg-config.freedesktop.org/ and install it.\n'
@@ -34,32 +47,50 @@
         sys.exit(1)
 
 
+try:
+    sdl_args = {'extra_compile_args': get_arguments('--cflags', SDL_LIBRARIES),
+                'extra_link_args': get_arguments('--libs', SDL_LIBRARIES)}
+
+    if use_opengl:
+        opengl_args = {'extra_compile_args': get_arguments('--cflags', GL_LIBRARIES + SDL_LIBRARIES),
+                       'extra_link_args': get_arguments('--libs', GL_LIBRARIES + SDL_LIBRARIES)}
+except CalledProcessError:
+    # The error has already been displayed, just exit.
+    sys.exit(1)
+except OSError:
+    # pkg-config is needed too.
+    print('You don’t seem to have pkg-config installed. Please get a copy '
+          'from http://pkg-config.freedesktop.org/ and install it.\n'
+          'If you prefer to use it from somewhere else, just modify the '
+          'setup.py script.')
+    sys.exit(1)
+
+
 for directory, _, files in os.walk('pytouhou'):
     package = directory.replace(os.path.sep, '.')
     packages.append(package)
-    if package not in ('pytouhou.game', 'pytouhou.lib', 'pytouhou.ui', 'pytouhou.utils', 'pytouhou.ui.sdl'):
+    if package not in ('pytouhou.game', 'pytouhou.lib', 'pytouhou.utils') and not package.startswith('pytouhou.ui'):
         continue
-    if package == 'pytouhou.ui':
-        compile_args = get_arguments('--cflags', ['gl'] + SDL_LIBRARIES)
-        link_args = get_arguments('--libs', ['gl'] + SDL_LIBRARIES)
-    elif package == 'pytouhou.ui.sdl':
-        compile_args = get_arguments('--cflags', SDL_LIBRARIES)
-        link_args = get_arguments('--libs', SDL_LIBRARIES)
+    if package == 'pytouhou.ui' or package == 'pytouhou.ui.sdl':
+        package_args = sdl_args
+    elif package == 'pytouhou.ui.opengl':
+        package_args = opengl_args
     else:
-        compile_args = None
-        link_args = None
+        package_args = {}
     for filename in files:
         if (filename.endswith('.pyx') or filename.endswith('.py') and
                 not filename == '__init__.py'):
             extension_name = '%s.%s' % (package, os.path.splitext(filename)[0])
             extension_names.append(extension_name)
             if extension_name == 'pytouhou.lib.sdl':
-                compile_args = get_arguments('--cflags', SDL_LIBRARIES)
-                link_args = get_arguments('--libs', SDL_LIBRARIES)
+                compile_args = sdl_args
+            elif extension_name == 'pytouhou.ui.window' and use_opengl:
+                compile_args = opengl_args
+            else:
+                compile_args = package_args
             extensions.append(Extension(extension_name,
                                         [os.path.join(directory, filename)],
-                                        extra_compile_args=compile_args,
-                                        extra_link_args=link_args))
+                                        **compile_args))
 
 
 # TODO: find a less-intrusive, cleaner way to do this...
@@ -87,6 +118,7 @@
                             compile_time_env={'MAX_TEXTURES': 1024,
                                               'MAX_ELEMENTS': 640 * 4 * 3,
                                               'MAX_CHANNELS': 26,
+                                              'USE_OPENGL': use_opengl,
                                               'USE_GLEW': is_windows}),
       scripts=['eosd', 'anmviewer'],
       **extra)