changeset 636:4fa0a8e7d941

Add a GLFW implementation of gui.Window.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sun, 14 May 2017 20:14:03 +0100
parents 80687f258001
children afa012bb8021
files pytouhou/lib/_glfw.pxd pytouhou/lib/glfw.pxd pytouhou/lib/glfw.pyx pytouhou/options.py pytouhou/ui/opengl/backend.pxd pytouhou/ui/opengl/backend.pyx pytouhou/ui/opengl/backend_glfw.pyx scripts/pytouhou setup.py
diffstat 9 files changed, 310 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/pytouhou/lib/_glfw.pxd
@@ -0,0 +1,85 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2016 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.
+##
+
+cdef extern from "GLFW/glfw3.h" nogil:
+    ctypedef void* GLFWmonitor
+    ctypedef void* GLFWwindow
+
+    ctypedef void (* GLFWerrorfun)(int, const char*)
+    ctypedef void (* GLFWwindowclosefun)(GLFWwindow*)
+    ctypedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int)
+    ctypedef void (* GLFWkeyfun)(GLFWwindow*, int, int, int, int)
+
+    int glfwInit()
+    void glfwTerminate()
+
+    GLFWerrorfun glfwSetErrorCallback(GLFWerrorfun cbfun)
+
+    void glfwWindowHint(int hint, int value)
+    GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share)
+    void glfwDestroyWindow(GLFWwindow* window)
+    void glfwSetWindowShouldClose(GLFWwindow* window, int value)
+
+    GLFWmonitor* glfwGetPrimaryMonitor()
+    GLFWmonitor* glfwGetWindowMonitor(GLFWwindow* window)
+    void glfwSetWindowMonitor(GLFWwindow* window, GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate)
+
+    GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwindowclosefun cbfun)
+    GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window, GLFWframebuffersizefun cbfun)
+    void glfwPollEvents()
+
+    bint glfwGetKey(GLFWwindow* window, int key)
+    GLFWkeyfun glfwSetKeyCallback(GLFWwindow* window, GLFWkeyfun cbfun)
+
+    void glfwMakeContextCurrent(GLFWwindow* window)
+    void glfwSwapBuffers(GLFWwindow* window)
+
+    ctypedef enum:
+        GLFW_DONT_CARE
+
+    ctypedef enum:
+        GLFW_KEY_Z
+        GLFW_KEY_X
+        GLFW_KEY_P
+        GLFW_KEY_LEFT_SHIFT
+        GLFW_KEY_UP
+        GLFW_KEY_DOWN
+        GLFW_KEY_LEFT
+        GLFW_KEY_RIGHT
+        GLFW_KEY_LEFT_CONTROL
+        GLFW_KEY_ESCAPE
+        GLFW_KEY_HOME
+        GLFW_KEY_ENTER
+        GLFW_KEY_F11
+
+    ctypedef enum:
+        GLFW_MOD_ALT
+
+    ctypedef enum:
+        GLFW_PRESS
+
+    ctypedef enum:
+        GLFW_CLIENT_API
+        GLFW_OPENGL_PROFILE
+        GLFW_CONTEXT_VERSION_MAJOR
+        GLFW_CONTEXT_VERSION_MINOR
+        GLFW_DEPTH_BITS
+        GLFW_ALPHA_BITS
+        GLFW_DOUBLEBUFFER
+        GLFW_RESIZABLE
+
+    ctypedef enum:
+        GLFW_OPENGL_API
+        GLFW_OPENGL_ES_API
+        GLFW_OPENGL_CORE_PROFILE
new file mode 100644
--- /dev/null
+++ b/pytouhou/lib/glfw.pxd
@@ -0,0 +1,39 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2016 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.lib._glfw cimport *
+cimport pytouhou.lib.gui as gui
+
+cdef int CLIENT_API
+cdef int OPENGL_PROFILE
+cdef int CONTEXT_VERSION_MAJOR
+cdef int CONTEXT_VERSION_MINOR
+cdef int DEPTH_BITS
+cdef int ALPHA_BITS
+cdef int RESIZABLE
+cdef int DOUBLEBUFFER
+
+cdef int OPENGL_API
+cdef int OPENGL_ES_API
+cdef int OPENGL_CORE_PROFILE
+
+cdef void init() except *
+cdef void terminate() nogil
+cdef void window_hint(int hint, int value) nogil
+
+cdef class Window(gui.Window):
+    cdef GLFWwindow* window
+
+cdef class Monitor:
+    cdef GLFWmonitor* monitor
new file mode 100644
--- /dev/null
+++ b/pytouhou/lib/glfw.pyx
@@ -0,0 +1,128 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2016 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 .gui cimport SHOOT, BOMB, FOCUS, UP, DOWN, LEFT, RIGHT, SKIP
+
+CLIENT_API = GLFW_CLIENT_API
+OPENGL_PROFILE = GLFW_OPENGL_PROFILE
+CONTEXT_VERSION_MAJOR = GLFW_CONTEXT_VERSION_MAJOR
+CONTEXT_VERSION_MINOR = GLFW_CONTEXT_VERSION_MINOR
+DEPTH_BITS = GLFW_DEPTH_BITS
+ALPHA_BITS = GLFW_ALPHA_BITS
+RESIZABLE = GLFW_RESIZABLE
+DOUBLEBUFFER = GLFW_DOUBLEBUFFER
+
+OPENGL_API = GLFW_OPENGL_API
+OPENGL_ES_API = GLFW_OPENGL_ES_API
+OPENGL_CORE_PROFILE = GLFW_OPENGL_CORE_PROFILE
+
+cdef void error_callback(int a, const char* b):
+    print('GLFW error 0x%x: %s' % (a, b.decode('utf-8')))
+
+cdef list _global_events = []
+
+cdef void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods):
+    if action != GLFW_PRESS:
+        return
+    if key == GLFW_KEY_ESCAPE:
+        _global_events.append((gui.PAUSE, None))
+    elif key in (GLFW_KEY_P, GLFW_KEY_HOME):
+        _global_events.append((gui.SCREENSHOT, None))
+    elif key == GLFW_KEY_DOWN:
+        _global_events.append((gui.DOWN, None))
+    elif key == GLFW_KEY_F11:
+        _global_events.append((gui.FULLSCREEN, None))
+    elif key == GLFW_KEY_ENTER:
+        if mods & GLFW_MOD_ALT:
+            _global_events.append((gui.FULLSCREEN, None))
+
+cdef void size_callback(GLFWwindow* window, int width, int height):
+    _global_events.append((gui.RESIZE, (width, height)))
+
+cdef void close_callback(GLFWwindow* window):
+    _global_events.append((gui.EXIT, None))
+
+cdef void init() except *:
+    glfwSetErrorCallback(<GLFWerrorfun>error_callback)
+    ret = glfwInit()
+    if not ret:
+        raise Exception('TODO')
+
+cdef void terminate() nogil:
+    glfwTerminate()
+
+cdef void window_hint(int hint, int value) nogil:
+    glfwWindowHint(hint, value)
+
+cdef class Window:
+    def __init__(self, int width, int height, str title, Monitor monitor=None, Window share=None):
+        cdef GLFWmonitor* c_monitor = NULL
+        cdef GLFWwindow* c_share = NULL
+        if monitor is not None:
+            c_monitor = monitor.monitor
+        if share is not None:
+            c_share = share.window
+        self.window = glfwCreateWindow(width, height, title.encode('utf-8'), c_monitor, c_share)
+        if self.window == NULL:
+            raise Exception('TODO')
+        glfwSetFramebufferSizeCallback(self.window, <GLFWframebuffersizefun>size_callback)
+        glfwSetWindowCloseCallback(self.window, <GLFWwindowclosefun>close_callback)
+        glfwSetKeyCallback(self.window, <GLFWkeyfun>key_callback)
+
+    def __del__(self):
+        glfwDestroyWindow(self.window)
+
+    cdef void create_gl_context(self) except *:
+        glfwMakeContextCurrent(self.window)
+
+    cdef void present(self) nogil:
+        glfwSwapBuffers(self.window)
+
+    cdef void set_window_size(self, int width, int height) nogil:
+        pass
+
+    cdef list get_events(self):
+        glfwPollEvents()
+        events = _global_events[:]
+        _global_events.clear()
+        return events
+
+    cdef void toggle_fullscreen(self) nogil:
+        monitor = glfwGetWindowMonitor(self.window)
+        if monitor == NULL:
+            monitor = glfwGetPrimaryMonitor()
+        else:
+            monitor = NULL
+        # TODO: save the previous size.
+        glfwSetWindowMonitor(self.window, monitor, 0, 0, 640, 480, 60)
+
+    cdef int get_keystate(self) nogil:
+        cdef int keystate = 0
+        if glfwGetKey(self.window, GLFW_KEY_Z):
+            keystate |= SHOOT
+        if glfwGetKey(self.window, GLFW_KEY_X):
+            keystate |= BOMB
+        if glfwGetKey(self.window, GLFW_KEY_LEFT_SHIFT):
+            keystate |= FOCUS
+        if glfwGetKey(self.window, GLFW_KEY_UP):
+            keystate |= UP
+        if glfwGetKey(self.window, GLFW_KEY_DOWN):
+            keystate |= DOWN
+        if glfwGetKey(self.window, GLFW_KEY_LEFT):
+            keystate |= LEFT
+        if glfwGetKey(self.window, GLFW_KEY_RIGHT):
+            keystate |= RIGHT
+        if glfwGetKey(self.window, GLFW_KEY_LEFT_CONTROL):
+            keystate |= SKIP
+        return keystate
--- a/pytouhou/options.py
+++ b/pytouhou/options.py
@@ -140,6 +140,7 @@ def parse_arguments(defaults):
     netplay_group.add_argument('--friendly-fire', action='store_true', help='Allow friendly-fire during netplay.')
 
     graphics_group = parser.add_argument_group('Graphics options')
+    graphics_group.add_argument('--frontend', metavar='FRONTEND', choices=['glfw', 'sdl'], help='Which windowing library to use (glfw or sdl).')
     graphics_group.add_argument('--backend', metavar='BACKEND', choices=['opengl', 'sdl'], nargs='*', help='Which backend to use (opengl or sdl).')
     graphics_group.add_argument('--fps-limit', metavar='FPS', type=int, help='Set fps limit. A value of 0 disables fps limiting, while a negative value limits to 60 fps if and only if vsync doesn’t work.')
     graphics_group.add_argument('--frameskip', metavar='FRAMESKIP', type=int, help='Set the frameskip, as 1/FRAMESKIP, or disabled if 0.')
--- a/pytouhou/ui/opengl/backend.pxd
+++ b/pytouhou/ui/opengl/backend.pxd
@@ -1,5 +1,6 @@
 from pytouhou.lib.opengl cimport GLenum_mode
 
+cdef bint use_glfw
 cdef str profile
 cdef int major
 cdef int minor
--- a/pytouhou/ui/opengl/backend.pyx
+++ b/pytouhou/ui/opengl/backend.pyx
@@ -1,6 +1,7 @@
 cimport pytouhou.lib.gui as gui
 from pytouhou.lib.gui import Error as GUIError
 from .backend_sdl import create_sdl_window
+from .backend_glfw import create_glfw_window
 
 from pytouhou.lib.opengl cimport \
          (glEnable, glHint, glEnableClientState, GL_TEXTURE_2D, GL_BLEND,
@@ -23,8 +24,9 @@ def init(options):
 
     cdef str flavor
 
-    global profile, major, minor, double_buffer, is_legacy, GameRenderer
+    global profile, major, minor, double_buffer, is_legacy, GameRenderer, use_glfw
 
+    use_glfw = options['frontend'] == 'glfw'
     flavor = options['flavor']
     assert flavor in ('core', 'es', 'compatibility', 'legacy')
     profile = flavor
@@ -89,7 +91,10 @@ def create_window(title, x, y, width, he
     '''Create a window (using SDL) and an OpenGL context.'''
 
     cdef gui.Window window
-    window = create_sdl_window(title, x, y, width, height)
+    if use_glfw:
+        window = create_glfw_window(title, width, height)
+    else:
+        window = create_sdl_window(title, x, y, width, height)
     window.create_gl_context()
     discover_features()
 
new file mode 100644
--- /dev/null
+++ b/pytouhou/ui/opengl/backend_glfw.pyx
@@ -0,0 +1,41 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2016 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 .backend cimport profile, major, minor, double_buffer, is_legacy
+
+cimport pytouhou.lib.glfw as glfw
+
+
+def create_glfw_window(title, width, height):
+    '''Create a window (using GLFW) and an OpenGL context.'''
+
+    glfw.init()
+
+    if profile == 'core':
+        glfw.window_hint(glfw.CLIENT_API, glfw.OPENGL_API)
+        glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
+    elif profile == 'es':
+        glfw.window_hint(glfw.CLIENT_API, glfw.OPENGL_ES_API)
+    glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, major)
+    glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, minor)
+    glfw.window_hint(glfw.ALPHA_BITS, 0)
+    glfw.window_hint(glfw.DEPTH_BITS, 24 if is_legacy else 0)
+    if double_buffer >= 0:
+        glfw.window_hint(glfw.DOUBLEBUFFER, double_buffer)
+
+    # Legacy contexts don’t support our required extensions for scaling.
+    if not is_legacy:
+        glfw.window_hint(glfw.RESIZABLE, True)
+
+    return glfw.Window(width, height, title)
--- a/scripts/pytouhou
+++ b/scripts/pytouhou
@@ -27,6 +27,7 @@ defaults = {'data': default_data,
             'game': 'eosd',
             'interface': 'eosd',
             'port': 0,
+            'frontend': 'glfw',
             'backend': ['opengl', 'sdl'],
             'gl-flavor': 'compatibility',
             'gl-version': 2.1,
@@ -90,6 +91,7 @@ for backend_name in args.backend:
             'flavor': args.gl_flavor,
             'version': args.gl_version,
             'double-buffer': args.double_buffer,
+            'frontend': args.frontend,
         }
     else:
         options = {}
--- a/setup.py
+++ b/setup.py
@@ -16,6 +16,7 @@ except ImportError:
 
 
 COMMAND = 'pkg-config'
+GLFW_LIBRARIES = ['glfw3']
 SDL_LIBRARIES = ['sdl2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf']
 GL_LIBRARIES = ['epoxy']
 
@@ -64,6 +65,7 @@ if use_opengl:
 
 
 default_libs = {
+    'glfw3': '-lglfw',
     'sdl2': '-lSDL2',
     'SDL2_image': '-lSDL2_image',
     'SDL2_mixer': '-lSDL2_mixer',
@@ -85,6 +87,8 @@ def get_arguments(arg, libraries):
         return [default_libs[library] for library in libraries]
 
 
+glfw_args = {'extra_compile_args': get_arguments('--cflags', GLFW_LIBRARIES),
+             'extra_link_args': get_arguments('--libs', GLFW_LIBRARIES)}
 sdl_args = {'extra_compile_args': get_arguments('--cflags', SDL_LIBRARIES),
             'extra_link_args': get_arguments('--libs', SDL_LIBRARIES)}
 
@@ -128,6 +132,8 @@ def extract_module_types(packages):
             if '.pyx' in extensions or '.pxd' in extensions or compile_everything:
                 if fully_qualified_name == 'pytouhou.lib.sdl':
                     compile_args = sdl_args
+                elif fully_qualified_name == 'pytouhou.lib.glfw':
+                    compile_args = glfw_args
                 else:
                     compile_args = package_args
                 ext = 'pyx' if '.pyx' in extensions else 'py'