# HG changeset patch # User Emmanuel Gil Peyrot # Date 1374001635 -7200 # Node ID 63f59be04a54178ef35dfcfd1c12840db33bef1b # Parent efae61ad6efe15f823278dab67ec3b8fd1536ff4 Replace Pyglet with SDL2 for window creation and events; disables framerate control/display and sound. diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -1,1 +1,8 @@ -.pyc +\.pyc$ +\.pyxbldc$ +\.c$ +\.o$ +\.so$ +\.pyd$ +^build$ +^scripts$ diff --git a/README b/README --- a/README +++ b/README @@ -15,6 +15,7 @@ Running: * Python2 (>= 2.6) * Cython * Pyglet + * SDL2 Building sample data: @@ -27,7 +28,7 @@ Documentation: The code should be sufficiently documented for anyone interested to learn how the EoSD engine work, but additional documentation is available at: -http://linkmauve.fr/doc/touhou/ +http://pytouhou.linkmauve.fr/ diff --git a/pytouhou/lib/__init__.py b/pytouhou/lib/__init__.py new file mode 100644 diff --git a/pytouhou/lib/sdl.pxd b/pytouhou/lib/sdl.pxd new file mode 100644 --- /dev/null +++ b/pytouhou/lib/sdl.pxd @@ -0,0 +1,88 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2013 Emmanuel Gil Peyrot +## +## 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. +## + +cdef extern from "SDL.h": + ctypedef unsigned int Uint32 + ctypedef unsigned short Uint16 + ctypedef unsigned char Uint8 + + int SDL_INIT_VIDEO + + int SDL_Init(Uint32 flags) + void SDL_Quit() + + +cdef extern from "SDL_error.h": + const char *SDL_GetError() + + +cdef extern from "SDL_video.h": + ctypedef enum SDL_GLattr: + SDL_GL_CONTEXT_MAJOR_VERSION + SDL_GL_CONTEXT_MINOR_VERSION + SDL_GL_DOUBLEBUFFER + SDL_GL_DEPTH_SIZE + + ctypedef enum SDL_WindowFlags: + SDL_WINDOWPOS_CENTERED + SDL_WINDOW_OPENGL + SDL_WINDOW_SHOWN + + ctypedef struct SDL_Window: + pass + + ctypedef void *SDL_GLContext + + int SDL_GL_SetAttribute(SDL_GLattr attr, int value) + SDL_Window *SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags) + SDL_GLContext SDL_GL_CreateContext(SDL_Window *window) + void SDL_GL_SwapWindow(SDL_Window *window) + void SDL_GL_DeleteContext(SDL_GLContext context) + void SDL_DestroyWindow(SDL_Window *window) + + +cdef extern from "SDL_scancode.h": + ctypedef enum SDL_Scancode: + SDL_SCANCODE_Z + SDL_SCANCODE_X + SDL_SCANCODE_LSHIFT + SDL_SCANCODE_UP + SDL_SCANCODE_DOWN + SDL_SCANCODE_LEFT + SDL_SCANCODE_RIGHT + SDL_SCANCODE_LCTRL + SDL_SCANCODE_ESCAPE + + +cdef extern from "SDL_events.h": + ctypedef enum SDL_EventType: + SDL_KEYDOWN + SDL_QUIT + + ctypedef struct SDL_Keysym: + SDL_Scancode scancode + + ctypedef struct SDL_KeyboardEvent: + Uint32 type + SDL_Keysym keysym + + ctypedef union SDL_Event: + Uint32 type + SDL_KeyboardEvent key + + int SDL_PollEvent(SDL_Event *event) + + +cdef extern from "SDL_keyboard.h": + const Uint8 *SDL_GetKeyboardState(int *numkeys) diff --git a/pytouhou/lib/sdl.pyx b/pytouhou/lib/sdl.pyx new file mode 100644 --- /dev/null +++ b/pytouhou/lib/sdl.pyx @@ -0,0 +1,96 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2013 Emmanuel Gil Peyrot +## +## 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. +## + +INIT_VIDEO = SDL_INIT_VIDEO + +GL_CONTEXT_MAJOR_VERSION = SDL_GL_CONTEXT_MAJOR_VERSION +GL_CONTEXT_MINOR_VERSION = SDL_GL_CONTEXT_MINOR_VERSION +GL_DOUBLEBUFFER = SDL_GL_DOUBLEBUFFER +GL_DEPTH_SIZE = SDL_GL_DEPTH_SIZE + +WINDOWPOS_CENTERED = SDL_WINDOWPOS_CENTERED +WINDOW_OPENGL = SDL_WINDOW_OPENGL +WINDOW_SHOWN = SDL_WINDOW_SHOWN + +SCANCODE_Z = SDL_SCANCODE_Z +SCANCODE_X = SDL_SCANCODE_X +SCANCODE_LSHIFT = SDL_SCANCODE_LSHIFT +SCANCODE_UP = SDL_SCANCODE_UP +SCANCODE_DOWN = SDL_SCANCODE_DOWN +SCANCODE_LEFT = SDL_SCANCODE_LEFT +SCANCODE_RIGHT = SDL_SCANCODE_RIGHT +SCANCODE_LCTRL = SDL_SCANCODE_LCTRL +SCANCODE_ESCAPE = SDL_SCANCODE_ESCAPE + +KEYDOWN = SDL_KEYDOWN +QUIT = SDL_QUIT + + +class SDLError(Exception): + pass + + +cdef class Window: + cdef SDL_Window *window + cdef SDL_GLContext context + + def __init__(self, const char *title, int x, int y, int w, int h, Uint32 flags): + self.window = SDL_CreateWindow(title, x, y, w, h, flags) + if self.window == NULL: + raise SDLError(SDL_GetError()) + + def destroy_window(self): + SDL_DestroyWindow(self.window) + + def gl_create_context(self): + self.context = SDL_GL_CreateContext(self.window) + + def gl_swap_window(self): + SDL_GL_SwapWindow(self.window) + + def gl_delete_context(self): + SDL_GL_DeleteContext(self.context) + + +def init(Uint32 flags): + if SDL_Init(flags) < 0: + raise SDLError(SDL_GetError()) + + +def quit(): + SDL_Quit() + + +def gl_set_attribute(SDL_GLattr attr, int value): + if SDL_GL_SetAttribute(attr, value) < 0: + raise SDLError(SDL_GetError()) + + +def poll_events(): + cdef SDL_Event event + ret = [] + while SDL_PollEvent(&event): + if event.type == SDL_KEYDOWN: + ret.append((event.type, event.key.keysym.scancode)) + elif event.type == SDL_QUIT: + ret.append((event.type,)) + return ret + + +def get_keyboard_state(): + cdef int numkeys + cdef bint k + cdef const Uint8 *state + state = SDL_GetKeyboardState(&numkeys) + return tuple([k is not False for k in state[:numkeys]]) diff --git a/pytouhou/lib/sdl.pyxbld b/pytouhou/lib/sdl.pyxbld new file mode 100644 --- /dev/null +++ b/pytouhou/lib/sdl.pyxbld @@ -0,0 +1,30 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2013 Emmanuel Gil Peyrot +## +## 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. +## + +""" Build instructions for the SDL module. """ + +from distutils.extension import Extension +from subprocess import check_output + +COMMAND = 'pkg-config' +LIBRARIES = ['sdl2'] + +def make_ext(modname, pyxfilename): + """ Compile and link with the corrects options. """ + compile_args = check_output([COMMAND, '--cflags'] + LIBRARIES).split() + link_args = check_output([COMMAND, '--libs'] + LIBRARIES).split() + return Extension(name=modname, + sources=[pyxfilename], + extra_compile_args=compile_args, + extra_link_args=link_args) diff --git a/pytouhou/ui/gamerunner.py b/pytouhou/ui/gamerunner.py --- a/pytouhou/ui/gamerunner.py +++ b/pytouhou/ui/gamerunner.py @@ -12,8 +12,7 @@ ## GNU General Public License for more details. ## -import pyglet -import traceback +from pytouhou.lib import sdl from pyglet.gl import (glMatrixMode, glLoadIdentity, glEnable, glDisable, glHint, glEnableClientState, glViewport, glScissor, @@ -37,20 +36,29 @@ from ctypes import c_uint, byref logger = get_logger(__name__) -class GameRunner(pyglet.window.Window, GameRenderer): +class GameRunner(GameRenderer): def __init__(self, resource_loader, game=None, background=None, replay=None, double_buffer=True, fps_limit=60, fixed_pipeline=False, skip=False): GameRenderer.__init__(self, resource_loader, game, background) - config = pyglet.gl.Config(double_buffer=double_buffer) - width, height = (game.interface.width, game.interface.height) if game else (None, None) - pyglet.window.Window.__init__(self, width=width, height=height, - caption='PyTouhou', resizable=False, - config=config) + sdl.init(sdl.INIT_VIDEO) + 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)) + sdl.gl_set_attribute(sdl.GL_DEPTH_SIZE, 24) + + self.width, self.height = (game.interface.width, game.interface.height) if game else (640, 480) + self.win = sdl.Window('PyTouhou', + sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, + self.width, self.height, + sdl.WINDOW_OPENGL | sdl.WINDOW_SHOWN) + self.win.gl_create_context() self.fps_limit = fps_limit self.use_fixed_pipeline = fixed_pipeline self.replay_level = None self.skip = skip + self.has_exit = False + self.keystate = 0 if not self.use_fixed_pipeline: self.game_shader = GameShader() @@ -64,7 +72,7 @@ class GameRunner(pyglet.window.Window, G if game: self.load_game(game, background, replay) - self.clock = pyglet.clock.get_default() + #self.clock = pyglet.clock.get_default() def load_game(self, game=None, background=None, bgms=None, replay=None, save_keystates=None): @@ -85,14 +93,16 @@ class GameRunner(pyglet.window.Window, G def set_input(self, replay=None): if not replay or not replay.levels[self.game.stage-1]: - self.keys = pyglet.window.key.KeyStateHandler() - self.push_handlers(self.keys) self.replay_level = None else: self.replay_level = replay.levels[self.game.stage-1] self.keys = self.replay_level.iter_keystates() + def set_size(self, width, height): + self.win.set_window_size(width, height) + + def start(self, width=None, height=None): width = width or (self.game.interface.width if self.game else 640) height = height or (self.game.interface.height if self.game else 480) @@ -115,16 +125,14 @@ class GameRunner(pyglet.window.Window, G self.game_mvp = game_view * self.proj self.interface_mvp = ortho_2d(0., float(self.width), float(self.height), 0.) - if self.fps_limit > 0: - pyglet.clock.set_fps_limit(self.fps_limit) + #if self.fps_limit > 0: + # pyglet.clock.set_fps_limit(self.fps_limit) while not self.has_exit: if not self.skip: - pyglet.clock.tick() - self.dispatch_events() self.update() self.render_game() self.render_interface() - self.flip() + self.win.gl_swap_window() else: self.update() @@ -132,50 +140,42 @@ class GameRunner(pyglet.window.Window, G vbo_array = (c_uint * 2)(self.vbo, self.back_vbo) glDeleteBuffers(2, vbo_array) - - def _event_text_symbol(self, ev): - # XXX: Ugly workaround to a pyglet bug on X11 - #TODO: fix that bug in pyglet - try: - return pyglet.window.Window._event_text_symbol(self, ev) - except Exception as exc: - logger.warn('Pyglet error: %s', traceback.format_exc(exc)) - return None, None - - - 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) + self.win.gl_delete_context() + self.win.destroy_window() + sdl.quit() def update(self): if self.background: self.background.update(self.game.frame) + for event in sdl.poll_events(): + type_ = event[0] + if type_ == sdl.KEYDOWN: + scancode = event[1] + if scancode == sdl.SCANCODE_ESCAPE: + self.has_exit = True #TODO: implement the pause. + elif type_ == sdl.QUIT: + self.has_exit = True if self.game: if not self.replay_level: #TODO: allow user settings + keys = sdl.get_keyboard_state() keystate = 0 - if self.keys[pyglet.window.key.W] or self.keys[pyglet.window.key.Z]: + if keys[sdl.SCANCODE_Z]: keystate |= 1 - if self.keys[pyglet.window.key.X]: + if keys[sdl.SCANCODE_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]: + if keys[sdl.SCANCODE_LSHIFT]: keystate |= 4 - if self.keys[pyglet.window.key.UP]: + if keys[sdl.SCANCODE_UP]: keystate |= 16 - if self.keys[pyglet.window.key.DOWN]: + if keys[sdl.SCANCODE_DOWN]: keystate |= 32 - if self.keys[pyglet.window.key.LEFT]: + if keys[sdl.SCANCODE_LEFT]: keystate |= 64 - if self.keys[pyglet.window.key.RIGHT]: + if keys[sdl.SCANCODE_RIGHT]: keystate |= 128 - if self.keys[pyglet.window.key.LCTRL]: + if keys[sdl.SCANCODE_LCTRL]: keystate |= 256 else: try: @@ -208,7 +208,7 @@ class GameRunner(pyglet.window.Window, G def render_interface(self): interface = self.game.interface - interface.labels['framerate'].set_text('%.2ffps' % self.clock.get_fps()) + #interface.labels['framerate'].set_text('%.2ffps' % self.clock.get_ticks()) if self.use_fixed_pipeline: glMatrixMode(GL_MODELVIEW) diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ from distutils.extension import Extensio from distutils.command.build_scripts import build_scripts from distutils.dep_util import newer from distutils import log +from subprocess import check_output # Cython is needed try: @@ -16,6 +17,9 @@ except ImportError: sys.exit(1) +COMMAND = 'pkg-config' +LIBRARIES = ['sdl2'] + packages = [] extension_names = [] extensions = [] @@ -47,8 +51,16 @@ for directory, _, files in os.walk('pyto if filename.endswith('.pyx'): extension_name = '%s.%s' % (package, os.path.splitext(filename)[0]) extension_names.append(extension_name) + if extension_name == 'pytouhou.lib.sdl': + compile_args = check_output([COMMAND, '--cflags'] + LIBRARIES).split() + link_args = check_output([COMMAND, '--libs'] + LIBRARIES).split() + else: + compile_args = None + link_args = None extensions.append(Extension(extension_name, - [os.path.join(directory, filename)])) + [os.path.join(directory, filename)], + extra_compile_args=compile_args, + extra_link_args=link_args))