Skip to content
Merged

GLFW #408

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyboy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def valid_sample_rate(freq):
"--window",
default=defaults["window"],
type=str,
choices=["SDL2", "OpenGL", "null"],
choices=["SDL2", "OpenGL", "GLFW", "null"],
help="Specify window-type to use",
)
parser.add_argument("-s", "--scale", default=defaults["scale"], type=int, help="The scaling multiplier for the window")
Expand Down
2 changes: 2 additions & 0 deletions pyboy/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
"rewind": False,
"screen_recorder": False,
"screenshot_recorder": False,
"window_glfw": False,
"window_null": False,
"window_open_gl": False,
"window_sdl2": False,
"manager": False,
"manager_gen": False,
# docs exclude end
"window_openal": False,
}
3 changes: 3 additions & 0 deletions pyboy/plugins/base_plugin.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ cdef class PyBoyWindowPlugin(PyBoyPlugin):
cdef int[2] _scaledresolution
cdef bint enable_title
cdef Sound sound
cdef bint fullscreen
cdef bint sound_paused

cdef int _get_sound_frames_buffered(self) noexcept
cdef int64_t _ftime
cdef bint frame_limiter(self, int) noexcept
cdef void set_title(self, str) noexcept
Expand Down
28 changes: 21 additions & 7 deletions pyboy/plugins/base_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

ROWS, COLS = 144, 160

SOUND_PREBUFFER_THRESHOLD = 3


class PyBoyPlugin:
argv = []
Expand Down Expand Up @@ -63,6 +65,8 @@ def __init__(self, pyboy, mb, pyboy_argv, *args, **kwargs):

self._ftime = time.perf_counter_ns()
self.sound_support = False
self.fullscreen = False
self.sound_paused = True

if not self.enabled():
return
Expand All @@ -79,15 +83,25 @@ def __init__(self, pyboy, mb, pyboy_argv, *args, **kwargs):
self.renderer = mb.lcd.renderer
self.sound = self.mb.sound

def _get_sound_frames_buffered(self):
return 0

def frame_limiter(self, speed):
self._ftime += int((1.0 / (60.0 * speed)) * 1_000_000_000)
now = time.perf_counter_ns()
if speed > 0 and self._ftime > now:
delay = (self._ftime - now) // 1_000_000
time.sleep(delay / 1000)
if self.sound_support and speed == 1:
frames_buffered = self._get_sound_frames_buffered()
if frames_buffered > SOUND_PREBUFFER_THRESHOLD:
# Sleep for the excees of the prebuffered frames we have
time.sleep(min(1 / 60.0, (frames_buffered - float(SOUND_PREBUFFER_THRESHOLD)) * (1.0 / 60.0)))
return True
else:
self._ftime = now
return True
self._ftime += int((1.0 / (60.0 * speed)) * 1_000_000_000)
now = time.perf_counter_ns()
if speed > 0 and self._ftime > now:
delay = (self._ftime - now) // 1_000_000
time.sleep(delay / 1000)
else:
self._ftime = now
return True

def paused(self, pause):
pass
Expand Down
3 changes: 3 additions & 0 deletions pyboy/plugins/manager.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ from pyboy.plugins.base_plugin cimport PyBoyGameWrapper
# imports
from pyboy.plugins.window_sdl2 cimport WindowSDL2
from pyboy.plugins.window_open_gl cimport WindowOpenGL
from pyboy.plugins.window_glfw cimport WindowGLFW
from pyboy.plugins.window_null cimport WindowNull
from pyboy.plugins.debug cimport Debug
from pyboy.plugins.auto_pause cimport AutoPause
Expand All @@ -33,6 +34,7 @@ cdef class PluginManager:
# plugin_cdef
cdef public WindowSDL2 window_sdl2
cdef public WindowOpenGL window_open_gl
cdef public WindowGLFW window_glfw
cdef public WindowNull window_null
cdef public Debug debug
cdef public AutoPause auto_pause
Expand All @@ -48,6 +50,7 @@ cdef class PluginManager:
cdef public GameWrapperPokemonPinball game_wrapper_pokemon_pinball
cdef bint window_sdl2_enabled
cdef bint window_open_gl_enabled
cdef bint window_glfw_enabled
cdef bint window_null_enabled
cdef bint debug_enabled
cdef bint auto_pause_enabled
Expand Down
19 changes: 19 additions & 0 deletions pyboy/plugins/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# imports
from pyboy.plugins.window_sdl2 import WindowSDL2 # noqa
from pyboy.plugins.window_open_gl import WindowOpenGL # noqa
from pyboy.plugins.window_glfw import WindowGLFW # noqa
from pyboy.plugins.window_null import WindowNull # noqa
from pyboy.plugins.debug import Debug # noqa
from pyboy.plugins.auto_pause import AutoPause # noqa
Expand All @@ -28,6 +29,7 @@ def parser_arguments():
# yield_plugins
yield WindowSDL2.argv
yield WindowOpenGL.argv
yield WindowGLFW.argv
yield WindowNull.argv
yield Debug.argv
yield AutoPause.argv
Expand Down Expand Up @@ -56,6 +58,8 @@ def __init__(self, pyboy, mb, pyboy_argv):
self.window_sdl2_enabled = self.window_sdl2.enabled()
self.window_open_gl = WindowOpenGL(pyboy, mb, pyboy_argv)
self.window_open_gl_enabled = self.window_open_gl.enabled()
self.window_glfw = WindowGLFW(pyboy, mb, pyboy_argv)
self.window_glfw_enabled = self.window_glfw.enabled()
self.window_null = WindowNull(pyboy, mb, pyboy_argv)
self.window_null_enabled = self.window_null.enabled()
self.debug = Debug(pyboy, mb, pyboy_argv)
Expand Down Expand Up @@ -101,6 +105,8 @@ def handle_events(self, events):
events = self.window_sdl2.handle_events(events)
if self.window_open_gl_enabled:
events = self.window_open_gl.handle_events(events)
if self.window_glfw_enabled:
events = self.window_glfw.handle_events(events)
if self.window_null_enabled:
events = self.window_null.handle_events(events)
if self.debug_enabled:
Expand Down Expand Up @@ -170,6 +176,8 @@ def _set_title(self):
self.window_sdl2.set_title(self.pyboy.window_title)
if self.window_open_gl_enabled:
self.window_open_gl.set_title(self.pyboy.window_title)
if self.window_glfw_enabled:
self.window_glfw.set_title(self.pyboy.window_title)
if self.window_null_enabled:
self.window_null.set_title(self.pyboy.window_title)
if self.debug_enabled:
Expand All @@ -183,6 +191,8 @@ def _post_tick_windows(self):
self.window_sdl2.post_tick()
if self.window_open_gl_enabled:
self.window_open_gl.post_tick()
if self.window_glfw_enabled:
self.window_glfw.post_tick()
if self.window_null_enabled:
self.window_null.post_tick()
if self.debug_enabled:
Expand All @@ -196,6 +206,8 @@ def paused(self, pause):
self.window_sdl2.paused(pause)
if self.window_open_gl_enabled:
self.window_open_gl.paused(pause)
if self.window_glfw_enabled:
self.window_glfw.paused(pause)
if self.window_null_enabled:
self.window_null.paused(pause)
if self.debug_enabled:
Expand All @@ -213,6 +225,9 @@ def frame_limiter(self, speed):
if self.window_open_gl_enabled:
done = self.window_open_gl.frame_limiter(speed)
if done: return
if self.window_glfw_enabled:
done = self.window_glfw.frame_limiter(speed)
if done: return
if self.window_null_enabled:
done = self.window_null.frame_limiter(speed)
if done: return
Expand All @@ -228,6 +243,8 @@ def window_title(self):
title += self.window_sdl2.window_title()
if self.window_open_gl_enabled:
title += self.window_open_gl.window_title()
if self.window_glfw_enabled:
title += self.window_glfw.window_title()
if self.window_null_enabled:
title += self.window_null.window_title()
if self.debug_enabled:
Expand Down Expand Up @@ -265,6 +282,8 @@ def stop(self):
self.window_sdl2.stop()
if self.window_open_gl_enabled:
self.window_open_gl.stop()
if self.window_glfw_enabled:
self.window_glfw.stop()
if self.window_null_enabled:
self.window_null.stop()
if self.debug_enabled:
Expand Down
2 changes: 1 addition & 1 deletion pyboy/plugins/manager_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

# Plugins and priority!
# E.g. DisableInput first
windows = ["WindowSDL2", "WindowOpenGL", "WindowNull", "Debug"]
windows = ["WindowSDL2", "WindowOpenGL", "WindowGLFW", "WindowNull", "Debug"]
game_wrappers = [
"GameWrapperSuperMarioLand",
"GameWrapperTetris",
Expand Down
27 changes: 27 additions & 0 deletions pyboy/plugins/window_glfw.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#
# License: See LICENSE.md file
# GitHub: https://github.com/Baekalfen/PyBoy
#

import cython

cimport cython

from pyboy.logging.logging cimport Logger
from pyboy.plugins.window_openal cimport WindowOpenAL


cdef Logger logger

cdef int ROWS, COLS

cdef class WindowGLFW(WindowOpenAL):
cdef list events
cdef object window

cdef void _key_callback(self, object, int, int, int, int) noexcept

# TODO: Callbacks don't really work, when Cythonized
cpdef void _gldraw(self) noexcept
@cython.locals(scale=double)
cpdef void _window_resize(self, object, int, int) noexcept
145 changes: 145 additions & 0 deletions pyboy/plugins/window_glfw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import numpy as np

import pyboy
from pyboy.plugins.window_openal import WindowOpenAL
from pyboy.utils import WindowEvent, PyBoyException, PyBoyDependencyError

logger = pyboy.logging.get_logger(__name__)

try:
import glfw
from OpenGL.GL import (
GL_COLOR_BUFFER_BIT,
GL_DEPTH_BUFFER_BIT,
GL_RGBA,
GL_UNSIGNED_INT_8_8_8_8_REV,
glClear,
glDrawPixels,
glFlush,
glPixelZoom,
)

glfw_enabled = True
except (ImportError, AttributeError):
glfw_enabled = False

ROWS, COLS = 144, 160


class WindowGLFW(WindowOpenAL):
def __init__(self, pyboy, mb, pyboy_argv):
super().__init__(pyboy, mb, pyboy_argv)

if not self.enabled():
return

if not glfw.init():
raise PyBoyException("GLFW couldn't initialize!")
self._scaledresolution = (COLS * self.scale, ROWS * self.scale)

# Fix scaling on macOS Retina displays. Call before 'glfw.create_window'!
glfw.window_hint(glfw.COCOA_RETINA_FRAMEBUFFER, glfw.FALSE)

self.window = glfw.create_window(*self._scaledresolution, "PyBoy", None, None)
if not self.window:
glfw.terminate()
raise PyBoyException("GLFW couldn't open window!")
glfw.make_context_current(self.window)
glfw.set_key_callback(self.window, self._key_callback)
glfw.set_window_size_callback(self.window, self._window_resize)
self.events = []

glPixelZoom(self.scale, self.scale)

def set_title(self, title):
glfw.set_window_title(self.window, title)

def handle_events(self, events):
events += self.events
self.events = []
return events

def _key_callback(self, window, key, scancode, action, mods):
# Map GLFW keys to PyBoy events
if action == glfw.PRESS:
if key == glfw.KEY_A:
self.events.append(WindowEvent(WindowEvent.PRESS_BUTTON_A))
elif key == glfw.KEY_S:
self.events.append(WindowEvent(WindowEvent.PRESS_BUTTON_B))
elif key == glfw.KEY_ESCAPE:
self.events.append(WindowEvent(WindowEvent.QUIT))
elif key == glfw.KEY_SPACE:
self.events.append(WindowEvent(WindowEvent.PRESS_SPEED_UP))
elif key == glfw.KEY_I:
self.events.append(WindowEvent(WindowEvent.SCREEN_RECORDING_TOGGLE))
elif key == glfw.KEY_BACKSPACE:
self.events.append(WindowEvent(WindowEvent.PRESS_BUTTON_SELECT))
elif key == glfw.KEY_ENTER:
self.events.append(WindowEvent(WindowEvent.PRESS_BUTTON_START))
elif key == glfw.KEY_UP:
self.events.append(WindowEvent(WindowEvent.PRESS_ARROW_UP))
elif key == glfw.KEY_DOWN:
self.events.append(WindowEvent(WindowEvent.PRESS_ARROW_DOWN))
elif key == glfw.KEY_LEFT:
self.events.append(WindowEvent(WindowEvent.PRESS_ARROW_LEFT))
elif key == glfw.KEY_RIGHT:
self.events.append(WindowEvent(WindowEvent.PRESS_ARROW_RIGHT))
elif key == glfw.KEY_Z:
self.events.append(WindowEvent(WindowEvent.STATE_SAVE))
elif key == glfw.KEY_X:
self.events.append(WindowEvent(WindowEvent.STATE_LOAD))
elif key == glfw.KEY_O:
self.events.append(WindowEvent(WindowEvent.SCREENSHOT_RECORD))
elif action == glfw.RELEASE:
if key == glfw.KEY_A:
self.events.append(WindowEvent(WindowEvent.RELEASE_BUTTON_A))
elif key == glfw.KEY_S:
self.events.append(WindowEvent(WindowEvent.RELEASE_BUTTON_B))
elif key == glfw.KEY_P:
self.events.append(WindowEvent(WindowEvent.PAUSE_TOGGLE))
elif key == glfw.KEY_SPACE:
self.events.append(WindowEvent(WindowEvent.RELEASE_SPEED_UP))
elif key == glfw.KEY_BACKSPACE:
self.events.append(WindowEvent(WindowEvent.RELEASE_BUTTON_SELECT))
elif key == glfw.KEY_ENTER:
self.events.append(WindowEvent(WindowEvent.RELEASE_BUTTON_START))
elif key == glfw.KEY_UP:
self.events.append(WindowEvent(WindowEvent.RELEASE_ARROW_UP))
elif key == glfw.KEY_DOWN:
self.events.append(WindowEvent(WindowEvent.RELEASE_ARROW_DOWN))
elif key == glfw.KEY_LEFT:
self.events.append(WindowEvent(WindowEvent.RELEASE_ARROW_LEFT))
elif key == glfw.KEY_RIGHT:
self.events.append(WindowEvent(WindowEvent.RELEASE_ARROW_RIGHT))

def _window_resize(self, window, width, height):
scale = max(min(height / ROWS, width / COLS), 1)
self._scaledresolution = (round(scale * COLS), round(scale * ROWS))
glPixelZoom(scale, scale)

def _gldraw(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
buf = np.asarray(self.renderer._screenbuffer)[::-1, :]
glDrawPixels(COLS, ROWS, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, buf)
glFlush()
glfw.swap_buffers(self.window)

def enabled(self):
if self.pyboy_argv.get("window") == "GLFW":
if glfw_enabled:
return True
else:
raise PyBoyDependencyError('Missing dependency "PyOpenGL" or "glfw"')
return False

def post_tick(self):
self._gldraw()
glfw.poll_events()
if glfw.window_should_close(self.window):
raise PyBoyException("Window closed")
WindowOpenAL.post_tick(self)

def stop(self):
glfw.set_window_should_close(self.window, True)
glfw.destroy_window(self.window)
glfw.terminate()
5 changes: 2 additions & 3 deletions pyboy/plugins/window_open_gl.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@
import cython

cimport cython
from libc.stdint cimport int64_t, uint8_t, uint16_t, uint32_t

from pyboy.logging.logging cimport Logger
from pyboy.plugins.base_plugin cimport PyBoyWindowPlugin
from pyboy.plugins.window_openal cimport WindowOpenAL


cdef Logger logger

cdef int ROWS, COLS

cdef class WindowOpenGL(PyBoyWindowPlugin):
cdef class WindowOpenGL(WindowOpenAL):
cdef list events

cdef void _glkeyboard(self, str, int, int, bint) noexcept
Expand Down
Loading
Loading