changeset 553:8f51e34d911c

Refactor graphics backend selection, to make them fallbackable and optional.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Thu, 29 May 2014 12:31:55 +0200
parents aad758aef26d
children 653a9f087673
files pytouhou/ui/opengl/backend.pxd pytouhou/ui/opengl/backend.pyx pytouhou/ui/opengl/gamerenderer.pyx pytouhou/ui/sdl/backend.pyx pytouhou/ui/window.pxd pytouhou/ui/window.pyx scripts/pytouhou setup.py
diffstat 8 files changed, 130 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/pytouhou/ui/opengl/backend.pxd
@@ -0,0 +1,6 @@
+cdef str flavor
+cdef float version
+cdef int major
+cdef int minor
+cdef bint double_buffer
+cdef bint is_legacy
new file mode 100644
--- /dev/null
+++ b/pytouhou/ui/opengl/backend.pyx
@@ -0,0 +1,63 @@
+from pytouhou.lib cimport sdl
+from pytouhou.lib.sdl cimport Window
+
+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
+
+
+GameRenderer = None
+
+
+def init(options):
+    global flavor, version, major, minor, double_buffer, is_legacy, GameRenderer
+
+    flavor = options['flavor']
+    assert flavor in ('core', 'es', 'compatibility', 'legacy')
+
+    version = options['version']
+    major = int(version)
+    minor = <int>(version * 10) % 10
+
+    double_buffer = options['double-buffer']
+    is_legacy = flavor == 'legacy'
+
+    #TODO: check for framebuffer/renderbuffer support.
+
+    from pytouhou.ui.opengl.gamerenderer import GameRenderer
+
+
+def create_window(title, x, y, width, height):
+    sdl.gl_set_attribute(sdl.GL_CONTEXT_MAJOR_VERSION, major)
+    sdl.gl_set_attribute(sdl.GL_CONTEXT_MINOR_VERSION, minor)
+    sdl.gl_set_attribute(sdl.GL_DOUBLEBUFFER, double_buffer)
+    sdl.gl_set_attribute(sdl.GL_DEPTH_SIZE, 24)
+
+    flags = sdl.WINDOW_SHOWN | sdl.WINDOW_OPENGL
+
+    #TODO: legacy can support one of the framebuffer extensions.
+    if not is_legacy:
+        flags |= sdl.WINDOW_RESIZABLE
+
+    window = Window(title, x, y, width, height, flags)
+    window.gl_create_context()
+
+    if USE_GLEW:
+        if glewInit() != 0:
+            raise Exception('GLEW init fail!')
+
+    # Initialize OpenGL
+    glEnable(GL_BLEND)
+    if is_legacy:
+        glEnable(GL_TEXTURE_2D)
+        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)
+
+    return window
--- a/pytouhou/ui/opengl/gamerenderer.pyx
+++ b/pytouhou/ui/opengl/gamerenderer.pyx
@@ -27,6 +27,7 @@ from pytouhou.utils.maths cimport perspe
 from pytouhou.game.text cimport NativeText, GlyphCollection
 from .shaders.eosd import GameShader, BackgroundShader, PassthroughShader
 from .renderer cimport Texture
+from .backend cimport is_legacy
 
 from collections import namedtuple
 Rect = namedtuple('Rect', 'x y w h')
@@ -34,8 +35,8 @@ 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
+    def __init__(self, resource_loader, _):
+        self.use_fixed_pipeline = is_legacy #XXX
 
         Renderer.__init__(self, resource_loader)
 
new file mode 100644
--- /dev/null
+++ b/pytouhou/ui/sdl/backend.pyx
@@ -0,0 +1,16 @@
+from pytouhou.lib cimport sdl
+from pytouhou.lib.sdl cimport Window
+
+
+GameRenderer = None
+
+
+def init(_):
+    global GameRenderer
+    from pytouhou.ui.sdl.gamerenderer import GameRenderer
+
+
+def create_window(title, x, y, width, height):
+    window = Window(title, x, y, width, height, sdl.WINDOW_SHOWN)
+    window.create_renderer(0)
+    return window
--- a/pytouhou/ui/window.pxd
+++ b/pytouhou/ui/window.pxd
@@ -20,7 +20,6 @@ cdef class Runner:
 
 cdef class Window:
     cdef sdl.Window win
-    cdef public bint use_fixed_pipeline
     cdef Runner runner
     cdef Clock clock
 
--- a/pytouhou/ui/window.pyx
+++ b/pytouhou/ui/window.pyx
@@ -14,15 +14,6 @@
 
 cimport cython
 
-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
-
 
 cdef class Clock:
     def __init__(self, long fps=-1):
@@ -86,48 +77,14 @@ cdef class Runner:
 
 
 cdef class Window:
-    def __init__(self, bint double_buffer=True, long fps_limit=-1,
-                 bint fixed_pipeline=False, bint sound=True, bint opengl=True):
-        self.use_fixed_pipeline = fixed_pipeline
+    def __init__(self, backend, long fps_limit=-1):
         self.runner = None
 
-        flags = sdl.WINDOW_SHOWN
-
-        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))
-            sdl.gl_set_attribute(sdl.GL_DEPTH_SIZE, 24)
-
-            flags |= sdl.WINDOW_OPENGL
-
-            #TODO: implement it in the SDL backend too.
-            if not self.use_fixed_pipeline:
-                flags |= sdl.WINDOW_RESIZABLE
-
-        self.win = sdl.Window('PyTouhou',
-                              sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED,
-                              640, 480, #XXX
-                              flags)
-
-        if USE_OPENGL and opengl:
-            self.win.gl_create_context()
-
-            IF USE_GLEW:
-                if glewInit() != 0:
-                    raise Exception('GLEW init fail!')
-
-            # Initialize OpenGL
-            glEnable(GL_BLEND)
-            if self.use_fixed_pipeline:
-                glEnable(GL_TEXTURE_2D)
-                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)
-        else:
-            self.win.create_renderer(0)
+        if backend is not None:
+            self.win = backend.create_window(
+                'PyTouhou',
+                sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED,
+                640, 480) #XXX
 
         self.clock = Clock(fps_limit)
 
@@ -154,7 +111,8 @@ cdef class Window:
         cdef bint running = False
         if self.runner is not None:
             running = self.runner.update()
-        self.win.present()
+        if self.win is not None:
+            self.win.present()
         self.clock.tick()
         return running
 
--- a/scripts/pytouhou
+++ b/scripts/pytouhou
@@ -52,14 +52,17 @@ netplay_group.add_argument('--remote', m
 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('--backend', metavar='BACKEND', choices=['opengl', 'sdl'], default='opengl', help='Which backend to use (opengl or sdl for now).')
-graphics_group.add_argument('--fixed-pipeline', action='store_true', help='Use the fixed pipeline instead of the new programmable one.')
-graphics_group.add_argument('--single-buffer', action='store_true', help='Disable double buffering.')
+graphics_group.add_argument('--backend', metavar='BACKEND', choices=['opengl', 'sdl'], default=['opengl', 'sdl'], nargs='*', help='Which backend to use (opengl or sdl).')
 graphics_group.add_argument('--fps-limit', metavar='FPS', default=-1, 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('--no-background', action='store_false', help='Disable background display (huge performance boost on slow systems).')
 graphics_group.add_argument('--no-particles', action='store_false', help='Disable particles handling (huge performance boost on slow systems).')
 graphics_group.add_argument('--no-sound', action='store_false', help='Disable music and sound effects.')
 
+opengl_group = parser.add_argument_group('OpenGL backend options')
+opengl_group.add_argument('--single-buffer', action='store_true', help='Disable double buffering.')
+opengl_group.add_argument('--gl-flavor', choices=['core', 'es', 'compatibility', 'legacy'], default='compatibility', help='OpenGL profile to use.')
+opengl_group.add_argument('--gl-version', default=2.1, type=float, help='OpenGL version to use.')
+
 args = parser.parse_args()
 
 
@@ -88,16 +91,34 @@ from pytouhou.formats.hint import Hint
 from pytouhou.network import Network
 
 
-if args.backend == 'opengl':
+from importlib import import_module
+for backend in args.backend:
+    if backend == 'opengl':
+        options = {
+            'double-buffer': not args.single_buffer,
+            'flavor': args.gl_flavor,
+            'version': args.gl_version
+        }
+    else:
+        options = {}
+
     try:
-        from pytouhou.ui.opengl.gamerenderer import GameRenderer
-        opengl = True
-    except ImportError:
-        args.backend = 'sdl'
+        backend = import_module('pytouhou.ui.%s.backend' % backend)
+    except ImportError as e:
+        continue
 
-if args.backend == 'sdl':
-    from pytouhou.ui.sdl.gamerenderer import GameRenderer
-    opengl = False
+    try:
+        backend.init(options)
+    except Exception as e:
+        print('Backend', backend, 'failed to initialize:', e)
+        pass
+    else:
+        GameRenderer = backend.GameRenderer
+        break
+else:
+    show_simple_message_box(u'No graphical backend could be used, continuing with a windowless game.')
+    backend = None
+    GameRenderer = None
 
 
 class GameBossRush(Game):
@@ -196,7 +217,7 @@ def main(window, path, data, stage_num, 
     common = Common(resource_loader, characters, continues, stage_num - 1)
     interface = Interface(resource_loader, common.players[0]) #XXX
     common.interface = interface #XXX
-    renderer = GameRenderer(resource_loader, window)
+    renderer = GameRenderer(resource_loader, window) if GameRenderer is not None else None
     runner = GameRunner(window, renderer, common, resource_loader, skip_replay, con)
     window.set_runner(runner)
 
@@ -276,9 +297,7 @@ def main(window, path, data, stage_num, 
 
 
 with SDL(sound=args.no_sound):
-    window = Window(double_buffer=(not args.single_buffer),
-                    fps_limit=args.fps_limit,
-                    fixed_pipeline=args.fixed_pipeline, opengl=opengl)
+    window = Window(backend, fps_limit=args.fps_limit)
 
     main(window, args.path, tuple(args.data), args.stage, args.rank,
          args.character, args.replay, args.save_replay, args.skip_replay,
--- a/setup.py
+++ b/setup.py
@@ -104,8 +104,6 @@ for directory, _, files in os.walk('pyto
             extension_names.append(extension_name)
             if extension_name == 'pytouhou.lib.sdl':
                 compile_args = sdl_args
-            elif extension_name == 'pytouhou.ui.window' and use_opengl:
-                compile_args = opengl_args
             elif extension_name == 'pytouhou.ui.anmrenderer' and not anmviewer:
                 extension_names.pop()
                 continue