# HG changeset patch # User Emmanuel Gil Peyrot # Date 1494789243 -3600 # Node ID 4fa0a8e7d941de5f06ac6e19acf4c8ef37cb9a8c # Parent 80687f2580018a0f29b152060ecdb5c8416effbb Add a GLFW implementation of gui.Window. diff --git a/pytouhou/lib/_glfw.pxd b/pytouhou/lib/_glfw.pxd new file mode 100644 --- /dev/null +++ b/pytouhou/lib/_glfw.pxd @@ -0,0 +1,85 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2016 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 "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 diff --git a/pytouhou/lib/glfw.pxd b/pytouhou/lib/glfw.pxd new file mode 100644 --- /dev/null +++ b/pytouhou/lib/glfw.pxd @@ -0,0 +1,39 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2016 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. +## + +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 diff --git a/pytouhou/lib/glfw.pyx b/pytouhou/lib/glfw.pyx new file mode 100644 --- /dev/null +++ b/pytouhou/lib/glfw.pyx @@ -0,0 +1,128 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2016 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. +## + +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(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, size_callback) + glfwSetWindowCloseCallback(self.window, close_callback) + glfwSetKeyCallback(self.window, 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 diff --git a/pytouhou/options.py b/pytouhou/options.py --- 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.') diff --git a/pytouhou/ui/opengl/backend.pxd b/pytouhou/ui/opengl/backend.pxd --- 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 diff --git a/pytouhou/ui/opengl/backend.pyx b/pytouhou/ui/opengl/backend.pyx --- 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() diff --git a/pytouhou/ui/opengl/backend_glfw.pyx b/pytouhou/ui/opengl/backend_glfw.pyx 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 +## +## 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) diff --git a/scripts/pytouhou b/scripts/pytouhou --- 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 = {} diff --git a/setup.py b/setup.py --- 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'