# HG changeset patch # User Thibaut Girka # Date 1315699379 -7200 # Node ID fab7ad2f0d8b558c6205a4be33c74d52f3910e4d # Parent 11ab06f4c4c6fb216b670d5e1795d8219a9359f3 Use Cython, improve performances! diff --git a/README b/README --- a/README +++ b/README @@ -11,10 +11,16 @@ Additionally, this project aims for a pe Dependencies: ------------- +Running: * Python2 (>= 2.6) + * Cython * Pyglet +Building sample data: + * thtk (http://code.google.com/p/thtk/) + + Documentation: -------------- diff --git a/eclviewer.py b/eclviewer.py --- a/eclviewer.py +++ b/eclviewer.py @@ -16,9 +16,12 @@ import sys import os +import pyximport +pyximport.install() + from pytouhou.resource.loader import Loader from pytouhou.game.background import Background -from pytouhou.opengl.gamerenderer import GameRenderer +from pytouhou.opengl.gamerunner import GameRunner from pytouhou.game.games import EoSDGame from pytouhou.game.player import PlayerState @@ -39,8 +42,8 @@ def main(path, stage_num): print(stage.name) # Main loop - renderer = GameRenderer(resource_loader, game, background) - renderer.start() + runner = GameRunner(resource_loader, game, background) + runner.start() try: diff --git a/pytouhou/opengl/gamerenderer.py b/pytouhou/opengl/gamerenderer.pyx rename from pytouhou/opengl/gamerenderer.py rename to pytouhou/opengl/gamerenderer.pyx --- a/pytouhou/opengl/gamerenderer.py +++ b/pytouhou/opengl/gamerenderer.pyx @@ -12,116 +12,55 @@ ## GNU General Public License for more details. ## -import struct -from itertools import chain +from libc.stdlib cimport malloc, free + import ctypes -import pyglet +import struct + from pyglet.gl import * from pytouhou.opengl.texture import TextureManager -from pytouhou.opengl.sprite import get_sprite_rendering_data +from pytouhou.opengl.sprite cimport get_sprite_rendering_data from pytouhou.opengl.background import get_background_rendering_data MAX_ELEMENTS = 10000 -class GameRenderer(pyglet.window.Window): +cdef struct Vertex: + float x, y, z + float u, v + unsigned char r, g, b, a + + +cdef class GameRenderer: + cdef public texture_manager + cdef public game + cdef public background + + cdef Vertex *vertex_buffer + + + def __cinit__(self, resource_loader, game=None, background=None): + # Allocate buffers + self.vertex_buffer = malloc(MAX_ELEMENTS * sizeof(Vertex)) + + + def __dealloc__(self): + free(self.vertex_buffer) + + def __init__(self, resource_loader, game=None, background=None): - pyglet.window.Window.__init__(self, caption='PyTouhou', resizable=False) - self.keys = pyglet.window.key.KeyStateHandler() - self.push_handlers(self.keys) - self.texture_manager = TextureManager(resource_loader) - self.fps_display = pyglet.clock.ClockDisplay() - self.game = game self.background = background - def start(self, width=384, height=448): - self.set_size(width, height) - - # Initialize OpenGL - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - gluPerspective(30, float(width)/float(height), - 101010101./2010101., 101010101./10101.) - - glEnable(GL_BLEND) - glEnable(GL_TEXTURE_2D) - glEnable(GL_FOG) - glHint(GL_FOG_HINT, GL_NICEST) - glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) - glEnableClientState(GL_COLOR_ARRAY) - glEnableClientState(GL_VERTEX_ARRAY) - glEnableClientState(GL_TEXTURE_COORD_ARRAY) - - # Allocate buffers - buff = ctypes.c_buffer(MAX_ELEMENTS * 4 * (3 * 4 + 2 * 4 + 4)) - self.buffers = (buff, - ctypes.byref(buff, 3 * 4), - ctypes.byref(buff, 3 * 4 + 2 * 4)) - - # Use our own loop to ensure 60 (for now, 120) fps - pyglet.clock.set_fps_limit(120) - while not self.has_exit: - pyglet.clock.tick() - self.dispatch_events() - self.update() - self.on_draw() - self.flip() - - - def on_resize(self, width, height): - glViewport(0, 0, width, height) - + cdef render_elements(self, elements): + cdef unsigned short nb_vertices = 0 - def on_key_press(self, symbol, modifiers): - if symbol == pyglet.window.key.ESCAPE: - self.has_exit = True - # XXX: Fullscreen will be enabled the day pyglet stops sucking - elif symbol == pyglet.window.key.F11: - self.set_fullscreen(not self.fullscreen) - - - def update(self): - if self.background: - self.background.update(self.game.game_state.frame) - if self.game: - #TODO: allow user settings - keystate = 0 - if self.keys[pyglet.window.key.W]: - keystate |= 1 - if self.keys[pyglet.window.key.X]: - keystate |= 2 - #TODO: on some configurations, LSHIFT is Shift_L when pressed - # and ISO_Prev_Group when released, confusing the hell out of pyglet - # and leading to a always-on LSHIFT... - if self.keys[pyglet.window.key.LSHIFT]: - keystate |= 4 - if self.keys[pyglet.window.key.UP]: - keystate |= 16 - if self.keys[pyglet.window.key.DOWN]: - keystate |= 32 - if self.keys[pyglet.window.key.LEFT]: - keystate |= 64 - if self.keys[pyglet.window.key.RIGHT]: - keystate |= 128 - if self.keys[pyglet.window.key.LCTRL]: - keystate |= 256 - self.game.run_iter(keystate) #TODO: self.keys... - - - def render_elements(self, elements): - texture_manager = self.texture_manager - - pack_data = struct.Struct('fff ff BBBB' * 4).pack_into - _vertices, _uvs, _colors = self.buffers - - nb_vertices = 0 indices_by_texture = {} for element in elements: @@ -135,22 +74,10 @@ class GameRenderer(pyglet.window.Window) (x1, y1, z1), (x2, y2, z2), (x3, y3, z3), (x4, y4, z4) = vertices r1, g1, b1, a1, r2, g2, b2, a2, r3, g3, b3, a3, r4, g4, b4, a4 = colors u1, v1, u2, v2, u3, v3, u4, v4 = uvs - pack_data(_vertices, nb_vertices * (3 * 4 + 2 * 4 + 4), - x1 + ox, y1 + oy, z1, - u1, v1, - r1, g1, b1, a1, - - x2 + ox, y2 + oy, z2, - u2, v2, - r2, g2, b2, a2, - - x3 + ox, y3 + oy, z3, - u3, v3, - r3, g3, b3, a3, - - x4 + ox, y4 + oy, z4, - u4, v4, - r4, g4, b4, a4) + self.vertex_buffer[nb_vertices] = Vertex(x1 + ox, y1 + oy, z1, u1, v1, r1, g1, b1, a1) + self.vertex_buffer[nb_vertices+1] = Vertex(x2 + ox, y2 + oy, z2, u2, v2, r2, g2, b2, a2) + self.vertex_buffer[nb_vertices+2] = Vertex(x3 + ox, y3 + oy, z3, u3, v3, r3, g3, b3, a3) + self.vertex_buffer[nb_vertices+3] = Vertex(x4 + ox, y4 + oy, z4, u4, v4, r4, g4, b4, a4) # Add indices index = nb_vertices @@ -158,19 +85,19 @@ class GameRenderer(pyglet.window.Window) nb_vertices += 4 - glVertexPointer(3, GL_FLOAT, 24, _vertices) - glTexCoordPointer(2, GL_FLOAT, 24, _uvs) - glColorPointer(4, GL_UNSIGNED_BYTE, 24, _colors) + glVertexPointer(3, GL_FLOAT, 24, &self.vertex_buffer[0].x) + glTexCoordPointer(2, GL_FLOAT, 24, &self.vertex_buffer[0].u) + glColorPointer(4, GL_UNSIGNED_BYTE, 24, &self.vertex_buffer[0].r) for (texture_key, blendfunc), indices in indices_by_texture.items(): nb_indices = len(indices) indices = struct.pack(str(nb_indices) + 'H', *indices) glBlendFunc(GL_SRC_ALPHA, (GL_ONE_MINUS_SRC_ALPHA, GL_ONE)[blendfunc]) - glBindTexture(GL_TEXTURE_2D, texture_manager[texture_key].id) + glBindTexture(GL_TEXTURE_2D, self.texture_manager[texture_key].id) glDrawElements(GL_QUADS, nb_indices, GL_UNSIGNED_SHORT, indices) - def on_draw(self): + def render(self): glClear(GL_DEPTH_BUFFER_BIT) back = self.background @@ -227,10 +154,3 @@ class GameRenderer(pyglet.window.Window) self.render_elements(game.players) glEnable(GL_FOG) - #TODO - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() - gluLookAt(192., 224., 835.979370, - 192, 224., 0., 0., 1., 0.) - self.fps_display.draw() - diff --git a/pytouhou/opengl/gamerunner.py b/pytouhou/opengl/gamerunner.py new file mode 100644 --- /dev/null +++ b/pytouhou/opengl/gamerunner.py @@ -0,0 +1,111 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2011 Thibaut Girka +## +## 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. +## + +import pyglet +from pyglet.gl import * + +from pytouhou.opengl.texture import TextureManager +from pytouhou.opengl.sprite import get_sprite_rendering_data #TODO: cimport? +from pytouhou.opengl.background import get_background_rendering_data +from pytouhou.opengl.gamerenderer import GameRenderer + + +class GameRunner(pyglet.window.Window, GameRenderer): + def __init__(self, resource_loader, game=None, background=None): + GameRenderer.__init__(self, resource_loader, game, background) + pyglet.window.Window.__init__(self, caption='PyTouhou', resizable=False) + self.keys = pyglet.window.key.KeyStateHandler() + self.push_handlers(self.keys) + + self.fps_display = pyglet.clock.ClockDisplay() + + + def start(self, width=384, height=448): + self.set_size(width, height) + + # Initialize OpenGL + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + gluPerspective(30, float(width)/float(height), + 101010101./2010101., 101010101./10101.) + + glEnable(GL_BLEND) + glEnable(GL_TEXTURE_2D) + glEnable(GL_FOG) + glHint(GL_FOG_HINT, GL_NICEST) + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) + glEnableClientState(GL_COLOR_ARRAY) + glEnableClientState(GL_VERTEX_ARRAY) + glEnableClientState(GL_TEXTURE_COORD_ARRAY) + + # Use our own loop to ensure 60 (for now, 120) fps + pyglet.clock.set_fps_limit(120) + while not self.has_exit: + pyglet.clock.tick() + self.dispatch_events() + self.update() + self.on_draw() + self.flip() + + + def on_resize(self, width, height): + glViewport(0, 0, width, height) + + + def on_key_press(self, symbol, modifiers): + if symbol == pyglet.window.key.ESCAPE: + self.has_exit = True + # XXX: Fullscreen will be enabled the day pyglet stops sucking + elif symbol == pyglet.window.key.F11: + self.set_fullscreen(not self.fullscreen) + + + def update(self): + if self.background: + self.background.update(self.game.game_state.frame) + if self.game: + #TODO: allow user settings + keystate = 0 + if self.keys[pyglet.window.key.W]: + keystate |= 1 + if self.keys[pyglet.window.key.X]: + keystate |= 2 + #TODO: on some configurations, LSHIFT is Shift_L when pressed + # and ISO_Prev_Group when released, confusing the hell out of pyglet + # and leading to a always-on LSHIFT... + if self.keys[pyglet.window.key.LSHIFT]: + keystate |= 4 + if self.keys[pyglet.window.key.UP]: + keystate |= 16 + if self.keys[pyglet.window.key.DOWN]: + keystate |= 32 + if self.keys[pyglet.window.key.LEFT]: + keystate |= 64 + if self.keys[pyglet.window.key.RIGHT]: + keystate |= 128 + if self.keys[pyglet.window.key.LCTRL]: + keystate |= 256 + self.game.run_iter(keystate) #TODO: self.keys... + + + def on_draw(self): + GameRenderer.render(self) + + #TODO + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + gluLookAt(192., 224., 835.979370, + 192, 224., 0., 0., 1., 0.) + self.fps_display.draw() + diff --git a/pytouhou/opengl/sprite.pxd b/pytouhou/opengl/sprite.pxd new file mode 100644 --- /dev/null +++ b/pytouhou/opengl/sprite.pxd @@ -0,0 +1,1 @@ +cpdef object get_sprite_rendering_data(object sprite) diff --git a/pytouhou/opengl/sprite.py b/pytouhou/opengl/sprite.pyx rename from pytouhou/opengl/sprite.py rename to pytouhou/opengl/sprite.pyx --- a/pytouhou/opengl/sprite.py +++ b/pytouhou/opengl/sprite.pyx @@ -15,10 +15,12 @@ from math import pi -from pytouhou.utils.matrix import Matrix +from pytouhou.utils.matrix cimport Matrix -def get_sprite_rendering_data(sprite): +cpdef object get_sprite_rendering_data(object sprite): + cdef Matrix vertmat + if not sprite._changed: return sprite._rendering_data @@ -52,7 +54,7 @@ def get_sprite_rendering_data(sprite): if sprite.corner_relative_placement: # Reposition vertmat.translate(width / 2., height / 2., 0.) if sprite.allow_dest_offset: - vertmat.translate(*sprite.dest_offset) + vertmat.translate(sprite.dest_offset[0], sprite.dest_offset[1], sprite.dest_offset[2]) x_1 = 1. / sprite.anm.size[0] y_1 = 1. / sprite.anm.size[1] diff --git a/pytouhou/utils/bitstream.py b/pytouhou/utils/bitstream.pyx rename from pytouhou/utils/bitstream.py rename to pytouhou/utils/bitstream.pyx --- a/pytouhou/utils/bitstream.py +++ b/pytouhou/utils/bitstream.pyx @@ -12,8 +12,12 @@ ## GNU General Public License for more details. ## -class BitStream(object): - def __init__(self, io): +cdef class BitStream: + cdef public io + cdef public int bits + cdef public int byte + + def __init__(BitStream self, io): self.io = io self.bits = 0 self.byte = 0 @@ -27,21 +31,21 @@ class BitStream(object): return self.io.__exit__(type, value, traceback) - def seek(self, offset, whence=0): + def seek(BitStream self, offset, whence=0): self.io.seek(offset, whence) self.byte = 0 self.bits = 0 - def tell(self): + def tell(BitStream self): return self.io.tell() - def tell2(self): + def tell2(BitStream self): return self.io.tell(), self.bits - def read_bit(self): + cpdef unsigned char read_bit(BitStream self): if not self.bits: self.byte = ord(self.io.read(1)) self.bits = 8 @@ -49,14 +53,15 @@ class BitStream(object): return (self.byte >> self.bits) & 0x01 - def read(self, nb_bits): + def read(BitStream self, nb_bits): + cdef unsigned int value value = 0 for i in range(nb_bits - 1, -1, -1): value |= self.read_bit() << i return value - def write_bit(self, bit): + cpdef write_bit(BitStream self, bit): if self.bits == 8: self.io.write(chr(self.byte)) self.bits = 0 @@ -66,12 +71,12 @@ class BitStream(object): self.bits += 1 - def write(self, bits, nb_bits): + def write(BitStream self, bits, nb_bits): for i in range(nb_bits): self.write_bit(bits >> (nb_bits - 1 - i) & 0x01) - def flush(self): + def flush(BitStream self): self.io.write(chr(self.byte)) self.bits = 0 self.byte = 0 diff --git a/pytouhou/utils/interpolator.py b/pytouhou/utils/interpolator.pyx rename from pytouhou/utils/interpolator.py rename to pytouhou/utils/interpolator.pyx --- a/pytouhou/utils/interpolator.py +++ b/pytouhou/utils/interpolator.pyx @@ -56,6 +56,6 @@ class Interpolator(object): self.start_frame = frame else: coeff = self._formula(float(frame - self.start_frame) / float(self.end_frame - self.start_frame)) - self.values = tuple(start_value + coeff * (end_value - start_value) - for (start_value, end_value) in zip(self.start_values, self.end_values)) + self.values = [start_value + coeff * (end_value - start_value) + for (start_value, end_value) in zip(self.start_values, self.end_values)] diff --git a/pytouhou/utils/matrix.pxd b/pytouhou/utils/matrix.pxd new file mode 100644 --- /dev/null +++ b/pytouhou/utils/matrix.pxd @@ -0,0 +1,10 @@ +cdef class Matrix: + cdef public data + + cpdef flip(Matrix self) + cpdef scale(Matrix self, x, y, z) + cpdef scale2d(Matrix self, x, y) + cpdef translate(Matrix self, x, y, z) + cpdef rotate_x(Matrix self, angle) + cpdef rotate_y(Matrix self, angle) + cpdef rotate_z(Matrix self, angle) diff --git a/pytouhou/utils/matrix.py b/pytouhou/utils/matrix.pyx rename from pytouhou/utils/matrix.py rename to pytouhou/utils/matrix.pyx --- a/pytouhou/utils/matrix.py +++ b/pytouhou/utils/matrix.pyx @@ -12,35 +12,28 @@ ## GNU General Public License for more details. ## -#TODO: find/learn to use a proper lib +from libc.math cimport sin, cos + -from math import sin, cos - -class Matrix(object): - def __init__(self, data=None): +cdef class Matrix: + def __init__(Matrix self, data=None): self.data = data or [[0] * 4 for i in xrange(4)] - def mult(self, other_matrix): - d1 = self.data - d2 = other_matrix.data - return Matrix([[sum(d1[i][a] * d2[a][j] for a in xrange(4)) for j in xrange(4)] for i in xrange(4)]) - - - def flip(self): + cpdef flip(Matrix self): data = self.data a, b, c, d = data[0] data[0] = [-a, -b, -c, -d] - def scale(self, x, y, z): + cpdef scale(Matrix self, x, y, z): d1 = self.data d1[0] = [a * x for a in d1[0]] d1[1] = [a * y for a in d1[1]] d1[2] = [a * z for a in d1[2]] - def scale2d(self, x, y): + cpdef scale2d(Matrix self, x, y): data = self.data d1a, d1b, d1c, d1d = data[0] d2a, d2b, d2c, d2d = data[1] @@ -48,7 +41,7 @@ class Matrix(object): data[1] = [d2a * y, d2b * y, d2c * y, d2d * y] - def translate(self, x, y, z): + cpdef translate(Matrix self, x, y, z): data = self.data a, b, c = data[3][:3] a, b, c = a * x, b * y, c * z @@ -60,7 +53,7 @@ class Matrix(object): data[2] = [d3a + c, d3b + c, d3c + c, d3d + c] - def rotate_x(self, angle): + cpdef rotate_x(Matrix self, angle): d1 = self.data cos_a = cos(angle) sin_a = sin(angle) @@ -68,7 +61,7 @@ class Matrix(object): [sin_a * d1[1][i] + cos_a * d1[2][i] for i in range(4)]) - def rotate_y(self, angle): + cpdef rotate_y(Matrix self, angle): d1 = self.data cos_a = cos(angle) sin_a = sin(angle) @@ -76,50 +69,10 @@ class Matrix(object): [- sin_a * d1[0][i] + cos_a * d1[2][i] for i in range(4)]) - def rotate_z(self, angle): + cpdef rotate_z(Matrix self, angle): d1 = self.data cos_a = cos(angle) sin_a = sin(angle) d1[0], d1[1] = ([cos_a * d1[0][i] - sin_a * d1[1][i] for i in range(4)], [sin_a * d1[0][i] + cos_a * d1[1][i] for i in range(4)]) - - @classmethod - def get_translation_matrix(cls, x, y, z): - return cls([[1., 0., 0., x], - [0., 1., 0., y], - [0., 0., 1., z], - [0., 0., 0., 1.]]) - - - @classmethod - def get_scaling_matrix(cls, x, y, z): - return cls([[x, 0., 0., 0.], - [0., y, 0., 0.], - [0., 0., z, 0.], - [0., 0., 0., 1.]]) - - - @classmethod - def get_rotation_matrix(cls, angle, axis): - """Only handles axis = x, y or z.""" - cos_a = cos(angle) - sin_a = sin(angle) - if axis == 'x': - return Matrix([[ 1., 0., 0., 0.], - [ 0., cos_a, -sin_a, 0.], - [ 0., sin_a, cos_a, 0.], - [ 0., 0., 0., 1.]]) - elif axis == 'y': - return Matrix([[ cos_a, 0., sin_a, 0.], - [ 0., 1., 0., 0.], - [-sin_a, 0., cos_a, 0.], - [ 0., 0., 0., 1.]]) - elif axis == 'z': - return Matrix([[ cos_a, -sin_a, 0., 0.], - [ sin_a, cos_a, 0., 0.], - [ 0., 0., 1., 0.], - [ 0., 0., 0., 1.]]) - else: - raise Exception -