1
1
import struct
2
2
import warnings
3
3
from array import array
4
- from collections .abc import Callable , Sequence , Set
4
+ from collections .abc import Callable , Iterator , Sequence , Set
5
5
from contextlib import contextmanager
6
6
from copy import deepcopy
7
7
from importlib import resources
8
8
from sys import modules
9
9
from typing import final
10
10
11
11
import moderngl
12
+ from OpenGL import GL
12
13
13
14
try :
14
15
import moderngl_window
32
33
VertexArray ,
33
34
create_context ,
34
35
)
35
- from OpenGL import GL
36
36
37
37
from libretro ._typing import override
38
38
from libretro .api .av import retro_game_geometry , retro_system_av_info
@@ -123,68 +123,27 @@ def _create_orthogonal_projection(
123
123
)
124
124
125
125
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
188
147
189
148
190
149
@final
@@ -195,6 +154,9 @@ def __init__(
195
154
fragment_shader : str | None = None ,
196
155
varyings : Sequence [str ] = ("transformedTexCoord" ,),
197
156
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
198
160
):
199
161
"""
200
162
Initializes the video driver.
@@ -285,7 +247,6 @@ def __init__(
285
247
self .__gl_push_debug_group : Callable [[int , int , int , bytes ], None ] | None = None
286
248
self .__gl_pop_debug_group : Callable [[], None ] | None = None
287
249
self .__gl_object_label : Callable [[int , int , int , bytes ], None ] | None = None
288
- self ._debug_callback : GL .GLDEBUGPROC | None = None
289
250
290
251
def __del__ (self ):
291
252
if self ._cpu_color :
@@ -366,6 +327,8 @@ def get_proc_address(self, sym: bytes) -> int | None:
366
327
def refresh (
367
328
self , data : memoryview | FrameBufferSpecial , width : int , height : int , pitch : int
368
329
) -> None :
330
+ _clear_gl_errors ()
331
+
369
332
with self .__debug_group (b"libretro.ModernGlVideoDriver.refresh" ):
370
333
match data :
371
334
case FrameBufferSpecial .DUPE :
@@ -426,11 +389,14 @@ def reinit(self) -> None:
426
389
427
390
# TODO: Honor cache_context; try to avoid reinitializing the context
428
391
if self ._context :
392
+ _clear_gl_errors ()
429
393
if self ._callback and self ._callback .context_destroy :
430
394
# If the core wants to clean up before the context is destroyed...
431
395
with self .__debug_group (b"libretro.ModernGlVideoDriver.reinit.context_destroy" ):
432
396
self ._callback .context_destroy ()
433
397
398
+ _warn_unhandled_gl_errors ()
399
+
434
400
if self ._window :
435
401
self ._window .destroy ()
436
402
del self ._window
@@ -496,27 +462,26 @@ def reinit(self) -> None:
496
462
ver = self ._callback .version_major * 100 + self ._callback .version_minor * 10
497
463
self ._context = create_context (require = ver , standalone = True , share = self ._shared )
498
464
465
+ _clear_gl_errors ()
499
466
if self ._context .version_code >= 430 :
500
467
self .__gl_push_debug_group = GL .glPushDebugGroup
501
468
self .__gl_pop_debug_group = GL .glPopDebugGroup
502
469
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 :
508
473
self .__gl_push_debug_group = GL .glPushDebugGroupKHR
509
474
self .__gl_pop_debug_group = GL .glPopDebugGroupKHR
510
475
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
515
479
else :
516
480
self .__gl_push_debug_group = None
517
481
self .__gl_pop_debug_group = None
518
482
self .__gl_object_label = None
519
483
484
+ _clear_gl_errors ()
520
485
with self .__debug_group (b"libretro.ModernGlVideoDriver.reinit" ):
521
486
self ._context .gc_mode = "auto"
522
487
self .__init_fbo ()
@@ -551,9 +516,12 @@ def reinit(self) -> None:
551
516
552
517
if self ._callback .context_reset :
553
518
# If the core wants to set up resources after the context is created...
519
+ _clear_gl_errors ()
554
520
with self .__debug_group (b"libretro.ModernGlVideoDriver.reinit.context_reset" ):
555
521
self ._callback .context_reset ()
556
522
523
+ _warn_unhandled_gl_errors ()
524
+
557
525
@override
558
526
@property
559
527
def supported_contexts (self ) -> Set [HardwareContext ]:
@@ -648,6 +616,7 @@ def screenshot(self, prerotate: bool = True) -> Screenshot | None:
648
616
if self ._system_av_info is None :
649
617
return None
650
618
619
+ _clear_gl_errors ()
651
620
with self .__debug_group (b"libretro.ModernGlVideoDriver.screenshot" ):
652
621
size = (self ._last_width , self ._last_height )
653
622
if self ._window :
@@ -658,6 +627,7 @@ def screenshot(self, prerotate: bool = True) -> Screenshot | None:
658
627
if frame is None :
659
628
return None
660
629
630
+ _clear_gl_errors ()
661
631
if not self ._callback or not self ._callback .bottom_left_origin :
662
632
# If we're using software rendering or the origin is at the bottom-left...
663
633
bytes_per_row = self ._last_width * self ._pixel_format .bytes_per_pixel
@@ -756,6 +726,8 @@ def __init_fbo(self):
756
726
self ._fbo .scissor = (0 , 0 , geometry .base_width , geometry .base_height )
757
727
self ._fbo .clear ()
758
728
729
+ _clear_gl_errors ()
730
+
759
731
def __init_hw_render (self ):
760
732
with self .__debug_group (b"libretro.ModernGlVideoDriver.__init_hw_render" ):
761
733
assert self ._context is not None
0 commit comments