Skip to content

Commit 5b963f5

Browse files
committed
Clean up OpenGL error handling
- Remove message callbacks for now, I'll reimplement them later in a way that doesn't step on the core - Clear errors originating from moderngl or the core - Unpolled errors were tripping up PyOpenGL, causing it attribute errors to the wrong functions - Use context.enable_direct instead of GL.glEnable
1 parent 9b80382 commit 5b963f5

File tree

1 file changed

+46
-74
lines changed

1 file changed

+46
-74
lines changed

src/libretro/drivers/video/opengl/moderngl.py

Lines changed: 46 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import struct
22
import warnings
33
from array import array
4-
from collections.abc import Callable, Sequence, Set
4+
from collections.abc import Callable, Iterator, Sequence, Set
55
from contextlib import contextmanager
66
from copy import deepcopy
77
from importlib import resources
88
from sys import modules
99
from typing import final
1010

1111
import moderngl
12+
from OpenGL import GL
1213

1314
try:
1415
import moderngl_window
@@ -32,7 +33,6 @@
3233
VertexArray,
3334
create_context,
3435
)
35-
from OpenGL import GL
3636

3737
from libretro._typing import override
3838
from libretro.api.av import retro_game_geometry, retro_system_av_info
@@ -123,68 +123,27 @@ def _create_orthogonal_projection(
123123
)
124124

125125

126-
def _debug_source(source: int) -> str:
127-
match source:
128-
case GL.GL_DEBUG_SOURCE_API:
129-
return "API"
130-
case GL.GL_DEBUG_SOURCE_WINDOW_SYSTEM:
131-
return "WINDOW_SYSTEM"
132-
case GL.GL_DEBUG_SOURCE_SHADER_COMPILER:
133-
return "SHADER_COMPILER"
134-
case GL.GL_DEBUG_SOURCE_THIRD_PARTY:
135-
return "THIRD_PARTY"
136-
case GL.GL_DEBUG_SOURCE_APPLICATION:
137-
return "APPLICATION"
138-
case GL.GL_DEBUG_SOURCE_OTHER:
139-
return "OTHER"
140-
case _:
141-
return f"<unknown({source})>"
142-
143-
144-
def _debug_type(type: int) -> str:
145-
match type:
146-
case GL.GL_DEBUG_TYPE_ERROR:
147-
return "ERROR"
148-
case GL.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
149-
return "DEPRECATED_BEHAVIOR"
150-
case GL.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
151-
return "UNDEFINED_BEHAVIOR"
152-
case GL.GL_DEBUG_TYPE_PORTABILITY:
153-
return "PORTABILITY"
154-
case GL.GL_DEBUG_TYPE_PERFORMANCE:
155-
return "PERFORMANCE"
156-
case GL.GL_DEBUG_TYPE_OTHER:
157-
return "OTHER"
158-
case GL.GL_DEBUG_TYPE_MARKER:
159-
return "MARKER"
160-
case GL.GL_DEBUG_TYPE_PUSH_GROUP:
161-
return "PUSH_GROUP"
162-
case GL.GL_DEBUG_TYPE_POP_GROUP:
163-
return "POP_GROUP"
164-
case _:
165-
return f"<unknown({type})>"
166-
167-
168-
def _debug_severity(severity: int) -> str:
169-
match severity:
170-
case GL.GL_DEBUG_SEVERITY_HIGH:
171-
return "HIGH"
172-
case GL.GL_DEBUG_SEVERITY_MEDIUM:
173-
return "MEDIUM"
174-
case GL.GL_DEBUG_SEVERITY_LOW:
175-
return "LOW"
176-
case GL.GL_DEBUG_SEVERITY_NOTIFICATION:
177-
return "NOTIFICATION"
178-
case _:
179-
return f"<unknown({severity})>"
180-
181-
182-
def _debug_message_callback(
183-
source: int, type: int, id: int, severity: int, length: int, message: bytes, userParam: int
184-
):
185-
print(
186-
f"GL_CALLBACK({_debug_source(source)}, {_debug_type(type)}, {_debug_severity(severity)}, {id}): {message!r}"
187-
)
126+
def _extract_gl_errors() -> Iterator[int]:
127+
# glGetError does _not_ return the most error;
128+
# it turns out OpenGL maintains a queue of errors,
129+
# and glGetError pops the most recent one.
130+
err: int
131+
while (err := GL.glGetError()) != GL.GL_NO_ERROR:
132+
yield err
133+
134+
135+
def _warn_unhandled_gl_errors():
136+
# Should be called as soon as possible after entering Python from the core;
137+
# unhandled OpenGL errors from the core must not hamper the frontend.
138+
unhandled_errors = tuple(_extract_gl_errors())
139+
if unhandled_errors:
140+
error_string = ", ".join(f"0x{e:X}" for e in unhandled_errors)
141+
warnings.warn(f"Core did not handle the following OpenGL errors: {error_string}")
142+
143+
144+
def _clear_gl_errors():
145+
for i in _extract_gl_errors():
146+
pass
188147

189148

190149
@final
@@ -195,6 +154,9 @@ def __init__(
195154
fragment_shader: str | None = None,
196155
varyings: Sequence[str] = ("transformedTexCoord",),
197156
window: str | None = None,
157+
# TODO: Add ability to configure the OpenGL message callback
158+
# TODO: Add ability to configure the OpenGL debug group
159+
# TODO: Add ability to force an OpenGL version
198160
):
199161
"""
200162
Initializes the video driver.
@@ -285,7 +247,6 @@ def __init__(
285247
self.__gl_push_debug_group: Callable[[int, int, int, bytes], None] | None = None
286248
self.__gl_pop_debug_group: Callable[[], None] | None = None
287249
self.__gl_object_label: Callable[[int, int, int, bytes], None] | None = None
288-
self._debug_callback: GL.GLDEBUGPROC | None = None
289250

290251
def __del__(self):
291252
if self._cpu_color:
@@ -366,6 +327,8 @@ def get_proc_address(self, sym: bytes) -> int | None:
366327
def refresh(
367328
self, data: memoryview | FrameBufferSpecial, width: int, height: int, pitch: int
368329
) -> None:
330+
_clear_gl_errors()
331+
369332
with self.__debug_group(b"libretro.ModernGlVideoDriver.refresh"):
370333
match data:
371334
case FrameBufferSpecial.DUPE:
@@ -426,11 +389,14 @@ def reinit(self) -> None:
426389

427390
# TODO: Honor cache_context; try to avoid reinitializing the context
428391
if self._context:
392+
_clear_gl_errors()
429393
if self._callback and self._callback.context_destroy:
430394
# If the core wants to clean up before the context is destroyed...
431395
with self.__debug_group(b"libretro.ModernGlVideoDriver.reinit.context_destroy"):
432396
self._callback.context_destroy()
433397

398+
_warn_unhandled_gl_errors()
399+
434400
if self._window:
435401
self._window.destroy()
436402
del self._window
@@ -496,27 +462,26 @@ def reinit(self) -> None:
496462
ver = self._callback.version_major * 100 + self._callback.version_minor * 10
497463
self._context = create_context(require=ver, standalone=True, share=self._shared)
498464

465+
_clear_gl_errors()
499466
if self._context.version_code >= 430:
500467
self.__gl_push_debug_group = GL.glPushDebugGroup
501468
self.__gl_pop_debug_group = GL.glPopDebugGroup
502469
self.__gl_object_label = GL.glObjectLabel
503-
GL.glEnable(GL.GL_DEBUG_OUTPUT)
504-
GL.glEnable(GL.GL_DEBUG_OUTPUT_SYNCHRONOUS)
505-
self._debug_callback = GL.GLDEBUGPROC(_debug_message_callback)
506-
# GL.glDebugMessageCallback(self._debug_callback, None)
507-
elif "GL_KHR_debug" in self._context.extensions:
470+
self._context.enable_direct(GL.GL_DEBUG_OUTPUT)
471+
self._context.enable_direct(GL.GL_DEBUG_OUTPUT_SYNCHRONOUS)
472+
elif "GL_KHR_debug" in self._context.extensions and GL.glPushDebugGroupKHR:
508473
self.__gl_push_debug_group = GL.glPushDebugGroupKHR
509474
self.__gl_pop_debug_group = GL.glPopDebugGroupKHR
510475
self.__gl_object_label = GL.glObjectLabelKHR
511-
GL.glEnable(GL.GL_DEBUG_OUTPUT_KHR)
512-
GL.glEnable(GL.GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR)
513-
self._debug_callback = GL.GLDEBUGPROCKHR(_debug_message_callback)
514-
# GL.glDebugMessageCallbackKHR(self._debug_callback, None)
476+
self._context.enable_direct(GL.GL_DEBUG_OUTPUT_KHR)
477+
self._context.enable_direct(GL.GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR)
478+
# TODO: Contribute this stuff to moderngl
515479
else:
516480
self.__gl_push_debug_group = None
517481
self.__gl_pop_debug_group = None
518482
self.__gl_object_label = None
519483

484+
_clear_gl_errors()
520485
with self.__debug_group(b"libretro.ModernGlVideoDriver.reinit"):
521486
self._context.gc_mode = "auto"
522487
self.__init_fbo()
@@ -551,9 +516,12 @@ def reinit(self) -> None:
551516

552517
if self._callback.context_reset:
553518
# If the core wants to set up resources after the context is created...
519+
_clear_gl_errors()
554520
with self.__debug_group(b"libretro.ModernGlVideoDriver.reinit.context_reset"):
555521
self._callback.context_reset()
556522

523+
_warn_unhandled_gl_errors()
524+
557525
@override
558526
@property
559527
def supported_contexts(self) -> Set[HardwareContext]:
@@ -648,6 +616,7 @@ def screenshot(self, prerotate: bool = True) -> Screenshot | None:
648616
if self._system_av_info is None:
649617
return None
650618

619+
_clear_gl_errors()
651620
with self.__debug_group(b"libretro.ModernGlVideoDriver.screenshot"):
652621
size = (self._last_width, self._last_height)
653622
if self._window:
@@ -658,6 +627,7 @@ def screenshot(self, prerotate: bool = True) -> Screenshot | None:
658627
if frame is None:
659628
return None
660629

630+
_clear_gl_errors()
661631
if not self._callback or not self._callback.bottom_left_origin:
662632
# If we're using software rendering or the origin is at the bottom-left...
663633
bytes_per_row = self._last_width * self._pixel_format.bytes_per_pixel
@@ -756,6 +726,8 @@ def __init_fbo(self):
756726
self._fbo.scissor = (0, 0, geometry.base_width, geometry.base_height)
757727
self._fbo.clear()
758728

729+
_clear_gl_errors()
730+
759731
def __init_hw_render(self):
760732
with self.__debug_group(b"libretro.ModernGlVideoDriver.__init_hw_render"):
761733
assert self._context is not None

0 commit comments

Comments
 (0)