From ea1916f67dcaefd069184c8f09e95e15ce1b6296 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 8 Apr 2024 19:47:26 -0400 Subject: [PATCH] Clean up some video driver stuff --- src/libretro/driver/video/driver.py | 13 +- src/libretro/driver/video/multi.py | 4 +- src/libretro/driver/video/opengl/moderngl.py | 165 ++++++++++++++----- src/libretro/driver/video/software/base.py | 4 +- 4 files changed, 131 insertions(+), 55 deletions(-) diff --git a/src/libretro/driver/video/driver.py b/src/libretro/driver/video/driver.py index 62a8da3..56fa7e2 100644 --- a/src/libretro/driver/video/driver.py +++ b/src/libretro/driver/video/driver.py @@ -39,7 +39,13 @@ def preferred_context(self, context: HardwareContext) -> None: ... def preferred_context(self) -> None: ... @abstractmethod - def init_callback(self, callback: retro_hw_render_callback) -> bool: ... + def set_context(self, callback: retro_hw_render_callback) -> retro_hw_render_callback | None: ... + + @abstractmethod + def context_reset(self) -> None: ... + + @abstractmethod + def context_destroy(self) -> None: ... @property @abstractmethod @@ -106,11 +112,6 @@ def shared_context(self) -> bool: ... @abstractmethod def shared_context(self, value: bool) -> None: ... - @abstractmethod - def context_reset(self) -> None: ... - - @abstractmethod - def context_destroy(self) -> None: ... __all__ = [ diff --git a/src/libretro/driver/video/multi.py b/src/libretro/driver/video/multi.py index a352ce1..59a230b 100644 --- a/src/libretro/driver/video/multi.py +++ b/src/libretro/driver/video/multi.py @@ -39,7 +39,7 @@ def init_callback(self, callback: retro_hw_render_callback) -> bool: del self._current self._current = self._drivers[context_type](callback) - self._current.context_reset() + self._current.set_context() pass @@ -100,7 +100,7 @@ def capture_frame(self) -> array: def set_shared_context(self) -> None: pass - def context_reset(self) -> None: + def set_context(self) -> None: pass def context_destroy(self) -> None: diff --git a/src/libretro/driver/video/opengl/moderngl.py b/src/libretro/driver/video/opengl/moderngl.py index 378a821..515ff35 100644 --- a/src/libretro/driver/video/opengl/moderngl.py +++ b/src/libretro/driver/video/opengl/moderngl.py @@ -1,18 +1,31 @@ from array import array +from collections.abc import Set from copy import deepcopy +from ctypes import c_char_p +from typing import override import moderngl from glcontext.empty import GLContext -from ..driver import * -from libretro.api.video.context import * -from libretro.api.video.memory import * -from libretro.api.video.render import * +from ..driver import VideoDriver +from libretro.api.video import ( + retro_hw_render_callback, + retro_hw_render_interface, + retro_framebuffer, + Rotation, + PixelFormat, + MemoryAccess, + HardwareContext, + retro_hw_get_current_framebuffer_t, + retro_hw_get_proc_address_t, +) + from libretro.api.av import retro_game_geometry, retro_system_av_info from libretro.api.proc import retro_proc_address_t class ModernGlVideoDriver(VideoDriver): + def __init__(self, callback: retro_hw_render_callback | None = None): self._context: GLContext | None = None self._pixel_format: PixelFormat = PixelFormat.RGB1555 @@ -27,31 +40,110 @@ def __del__(self): del self._context - def _refresh(self, data: memoryview | None, width: int, height: int, pitch: int) -> None: + def refresh(self, data: memoryview | None, width: int, height: int, pitch: int) -> None: # TODO: Recreate the frame buffer based on the pixel format and system AV info pass # TODO: Implement - def init_callback(self, callback: retro_hw_render_callback) -> bool: + def supported_contexts(self) -> Set[HardwareContext]: + pass + + @property + def preferred_context(self) -> HardwareContext | None: + pass + + def active_context(self) -> HardwareContext | None: + if self._context: + return HardwareContext.OPENGL + + return HardwareContext.NONE + + @override + def set_context(self, callback: retro_hw_render_callback) -> retro_hw_render_callback | None: if not isinstance(callback, retro_hw_render_callback): raise TypeError(f"Expected a retro_hw_render_callback, got {type(callback).__name__}") - match callback.context_type, callback.version_major, callback.version_minor: - case HardwareContext.OPENGL, _, _: - self._context = moderngl.create_context(standalone=True, share=self._use_shared_context) - # TODO: What do the version numbers do here? - case HardwareContext.OPENGL_CORE, major, minor: - version = major * 100 + minor * 10 - self._context = moderngl.create_context(require=version, standalone=True, share=self._use_shared_context) - case _, _, _: - return False - - callback.get_current_framebuffer = retro_hw_get_current_framebuffer_t(self.get_hw_framebuffer) - callback.get_proc_address = retro_hw_get_proc_address_t(self.get_proc_address) self._hw_render_callback = deepcopy(callback) + self._hw_render_callback.get_current_framebuffer = retro_hw_get_current_framebuffer_t(self.get_hw_framebuffer) + self._hw_render_callback.get_proc_address = retro_hw_get_proc_address_t(self.__get_proc_address) - # + return self._hw_render_callback - return True + @property + @override + def geometry(self) -> retro_game_geometry: + if not self._system_av_info: + raise RuntimeError("No system AV info has been set") + + return deepcopy(self._system_av_info.geometry) + + @geometry.setter + @override + def geometry(self, geometry: retro_game_geometry) -> None: + if not isinstance(geometry, retro_game_geometry): + raise TypeError(f"Expected a retro_game_geometry, got {type(geometry).__name__}") + + self._system_av_info.geometry = geometry + # TODO: Crop the OpenGL texture if necessary + + @property + @override + def pixel_format(self) -> PixelFormat: + return self._pixel_format + + @pixel_format.setter + @override + def pixel_format(self, format: PixelFormat) -> None: + if not isinstance(format, PixelFormat): + raise TypeError(f"Expected a PixelFormat, got {type(format).__name__}") + + if format not in PixelFormat: + raise ValueError(f"Invalid pixel format: {format}") + + if self._pixel_format != format: + self._pixel_format = format + self._needs_recreate = True + + @property + @override + def rotation(self) -> Rotation: + pass + + @rotation.setter + @override + def rotation(self, rotation: Rotation) -> None: + pass # TODO: Implement + + @property + @override + def system_av_info(self) -> retro_system_av_info | None: + return deepcopy(self._system_av_info) if self._system_av_info else None + + @system_av_info.setter + @override + def system_av_info(self, av_info: retro_system_av_info) -> None: + if not isinstance(av_info, retro_system_av_info): + raise TypeError(f"Expected a retro_system_av_info, got {type(av_info).__name__}") + + self._system_av_info = av_info + self._needs_recreate = True + + @property + def frame(self): + pass + + @property + def frame_max(self): + pass + + @property + @override + def shared_context(self) -> bool: + return self._use_shared_context + + @shared_context.setter + @override + def shared_context(self, shared: bool) -> None: + self._use_shared_context = bool(shared) def set_rotation(self, rotation: Rotation) -> bool: raise NotImplementedError() # TODO: Implement @@ -60,27 +152,20 @@ def set_rotation(self, rotation: Rotation) -> bool: def can_dupe(self) -> bool: return True - def set_pixel_format(self, format: PixelFormat) -> bool: - raise NotImplementedError() # TODO: Implement - def get_hw_framebuffer(self) -> int: raise NotImplementedError() # TODO: Implement + def __get_proc_address(self, sym: c_char_p) -> retro_proc_address_t: + return self.get_proc_address(bytes(sym)) + def get_proc_address(self, sym: bytes) -> retro_proc_address_t | None: if not sym: return None - return retro_proc_address_t(self._context.load_opengl_function(sym)) - - def set_system_av_info(self, av_info: retro_system_av_info) -> None: - if not isinstance(av_info, retro_system_av_info): - raise TypeError(f"Expected a retro_system_av_info, got {type(av_info).__name__}") - - # TODO: Set the new AV info, recreate the frame buffer if necessary + if not self._context: + raise RuntimeError("No OpenGL context has been initialized") - def set_geometry(self, geometry: retro_game_geometry) -> None: - pass - # TODO: Crop the OpenGL texture if necessary + return retro_proc_address_t(self._context.load_opengl_function(sym)) def get_software_framebuffer(self, width: int, size: int, flags: MemoryAccess) -> retro_framebuffer | None: # TODO: Map the OpenGL texture to a software framebuffer @@ -88,17 +173,11 @@ def get_software_framebuffer(self, width: int, size: int, flags: MemoryAccess) - @property def hw_render_interface(self) -> retro_hw_render_interface | None: + # libretro doesn't define one of these for OpenGL, so no need return None - def set_shared_context(self) -> None: - self._use_shared_context = True - def context_reset(self) -> None: - if not self._hw_render_callback: - raise RuntimeError("No render callback has been initialized") - - if self._hw_render_callback.context_reset: - self._hw_render_callback.context_reset() + pass # TODO: Implement def context_destroy(self) -> None: if not self._context: @@ -106,7 +185,3 @@ def context_destroy(self) -> None: if self._hw_render_callback.context_destroy: self._hw_render_callback.context_destroy() - - def capture_frame(self) -> array: - raise NotImplementedError() - diff --git a/src/libretro/driver/video/software/base.py b/src/libretro/driver/video/software/base.py index fe55333..b724e6e 100644 --- a/src/libretro/driver/video/software/base.py +++ b/src/libretro/driver/video/software/base.py @@ -13,9 +13,9 @@ class AbstractSoftwareVideoDriver(VideoDriver, ABC): @final - def init_callback(self, callback: retro_hw_render_callback) -> bool: + def set_context(self, callback: retro_hw_render_callback) -> retro_hw_render_callback | None: # Software-rendered drivers don't need retro_hw_render_callback - return False + return None @property @override