11import struct
22import warnings
33from array import array
4- from collections .abc import Callable , Sequence , Set
4+ from collections .abc import Callable , Iterator , Sequence , Set
55from contextlib import contextmanager
66from copy import deepcopy
77from importlib import resources
88from sys import modules
99from typing import final
1010
1111import moderngl
12+ from OpenGL import GL
1213
1314try :
1415 import moderngl_window
3233 VertexArray ,
3334 create_context ,
3435)
35- from OpenGL import GL
3636
3737from libretro ._typing import override
3838from 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