pyglet 3.0.dev4__py3-none-any.whl → 3.0.dev5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. pyglet/__init__.py +1 -1
  2. pyglet/customtypes.py +11 -5
  3. pyglet/experimental/geoshader_sprite.py +9 -2
  4. pyglet/graphics/__init__.py +8 -0
  5. pyglet/graphics/api/base.py +169 -54
  6. pyglet/graphics/api/gl/buffer.py +36 -4
  7. pyglet/graphics/api/gl/cocoa/context.py +1 -1
  8. pyglet/graphics/api/gl/context.py +46 -7
  9. pyglet/graphics/api/gl/draw.py +17 -435
  10. pyglet/graphics/api/gl/egl/context.py +1 -1
  11. pyglet/graphics/api/gl/gl_info.py +4 -0
  12. pyglet/graphics/api/gl/global_opengl.py +0 -87
  13. pyglet/graphics/api/gl/renderer.py +23 -0
  14. pyglet/graphics/api/gl/shader.py +18 -2
  15. pyglet/graphics/api/gl/state.py +71 -50
  16. pyglet/graphics/api/gl/win32/context.py +1 -1
  17. pyglet/graphics/api/gl/xlib/context.py +1 -1
  18. pyglet/graphics/api/gl2/draw.py +16 -446
  19. pyglet/graphics/api/gl2/global_opengl.py +0 -63
  20. pyglet/graphics/api/gl2/state.py +29 -0
  21. pyglet/graphics/api/webgl/__init__.py +0 -85
  22. pyglet/graphics/api/webgl/buffer.py +30 -1
  23. pyglet/graphics/api/webgl/context.py +5 -2
  24. pyglet/graphics/api/webgl/draw.py +11 -433
  25. pyglet/graphics/api/webgl/gl_info.py +5 -0
  26. pyglet/graphics/api/webgl/renderer.py +23 -0
  27. pyglet/graphics/api/webgl/shader.py +18 -2
  28. pyglet/graphics/api/webgl/state.py +57 -59
  29. pyglet/graphics/buffer.py +297 -10
  30. pyglet/graphics/draw.py +574 -101
  31. pyglet/graphics/shader.py +22 -4
  32. pyglet/graphics/state.py +101 -9
  33. pyglet/graphics/vertexdomain.py +10 -2
  34. pyglet/shapes.py +18 -32
  35. pyglet/sprite.py +12 -21
  36. pyglet/window/__init__.py +56 -36
  37. pyglet/window/camera/__init__.py +28 -0
  38. pyglet/window/camera/base.py +682 -0
  39. pyglet/window/camera/camera2d.py +333 -0
  40. pyglet/window/camera/camera3d.py +318 -0
  41. pyglet/window/cocoa/__init__.py +0 -9
  42. pyglet/window/emscripten/__init__.py +0 -8
  43. pyglet/window/headless/__init__.py +0 -8
  44. pyglet/window/wayland/__init__.py +0 -7
  45. pyglet/window/win32/__init__.py +2 -5
  46. pyglet/window/xlib/__init__.py +2 -10
  47. {pyglet-3.0.dev4.dist-info → pyglet-3.0.dev5.dist-info}/METADATA +2 -2
  48. {pyglet-3.0.dev4.dist-info → pyglet-3.0.dev5.dist-info}/RECORD +50 -43
  49. {pyglet-3.0.dev4.dist-info → pyglet-3.0.dev5.dist-info}/WHEEL +0 -0
  50. {pyglet-3.0.dev4.dist-info → pyglet-3.0.dev5.dist-info}/licenses/LICENSE +0 -0
pyglet/__init__.py CHANGED
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
18
18
  from typing import Any, Callable, ItemsView, Sized
19
19
 
20
20
  #: The release version
21
- version = '3.0.dev4'
21
+ version = '3.0.dev5'
22
22
  __version__ = version
23
23
 
24
24
  MIN_PYTHON_VERSION = 3, 8
pyglet/customtypes.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import ctypes
4
4
  import sys
5
5
 
6
- from typing import Union, Literal, Type, Protocol, Tuple
6
+ from typing import Union, Literal, Type, Protocol, Tuple, runtime_checkable
7
7
 
8
8
  from ctypes import _SimpleCData, _Pointer # type: ignore # noqa: PGH003
9
9
 
@@ -44,11 +44,16 @@ CTypesPointer = _Pointer
44
44
 
45
45
 
46
46
 
47
+ @runtime_checkable
47
48
  class ScissorProtocol(Protocol):
48
- x: int
49
- y: int
50
- width: int
51
- height: int
49
+ """Protocol for an object that holds information for scissor states."""
50
+
51
+ @property
52
+ def area(self) -> tuple[int, int, int, int]:
53
+ """Return the region of the scissor area.
54
+
55
+ Requires the x, y, width, and height in screen space coordinates.
56
+ """
52
57
 
53
58
 
54
59
 
@@ -64,4 +69,5 @@ __all__ = [
64
69
  "MediaTypes",
65
70
  "RGBAColor",
66
71
  "RGBColor",
72
+ "ScissorProtocol",
67
73
  ]
@@ -768,9 +768,16 @@ class Sprite(event.EventDispatcher):
768
768
  efficiently.
769
769
  """
770
770
  ctx = pyglet.graphics.api.core.current_context
771
- self._group.set_state_recursive(ctx)
771
+ draw_ctx = pyglet.graphics.draw.DrawContext(
772
+ surface_ctx=ctx,
773
+ backend_ctx=None,
774
+ draw_pass=pyglet.graphics.draw.BatchDrawOptions().resolve(ctx),
775
+ renderer=ctx.renderer,
776
+ )
777
+ draw_ctx.begin()
778
+ self._group.set_state_recursive(draw_ctx)
772
779
  self._vertex_list.draw(GeometryMode.POINTS)
773
- self._group.unset_state_recursive(ctx)
780
+ self._group.unset_state_recursive(draw_ctx)
774
781
 
775
782
  def __del__(self):
776
783
  try:
@@ -12,6 +12,14 @@ from typing import TYPE_CHECKING
12
12
 
13
13
  import pyglet
14
14
 
15
+ class GraphicsBackendError(Exception):
16
+ """The base exception class for all graphics backends."""
17
+
18
+ class GraphicsAPIError(GraphicsBackendError):
19
+ """An exception that occurs from the graphics API directly."""
20
+
21
+ class GraphicsIntegrationError(GraphicsBackendError):
22
+ """An exception that occurs from pyglets internal graphics integration of the APIs."""
15
23
 
16
24
  class UnsupportedBackendError(Exception):
17
25
  """Raised when a graphics feature is unavailable on the active backend."""
@@ -4,14 +4,19 @@ import atexit
4
4
  import os
5
5
  import weakref
6
6
  from abc import ABC, abstractmethod
7
+ from dataclasses import dataclass, field
7
8
  from typing import TYPE_CHECKING, Any, Protocol, get_type_hints, Sequence, Callable, NoReturn
8
9
 
10
+ from pyglet.graphics import GraphicsIntegrationError, GraphicsBackendError
11
+ from pyglet.util import debug_print
12
+
9
13
  if TYPE_CHECKING:
10
14
  from pyglet.config import SurfaceConfig
11
- from pyglet.math import Mat4
12
15
  from pyglet.graphics.api.gl import ObjectSpace
16
+ from pyglet.graphics.buffer import BufferRange
13
17
  from pyglet.window import Window
14
18
 
19
+ _debug_print = debug_print("debug_api")
15
20
 
16
21
  class BackendGlobalObject(ABC): # Temp name for now.
17
22
  """A container for backend resources and information that are required throughout the code.
@@ -35,7 +40,11 @@ class BackendGlobalObject(ABC): # Temp name for now.
35
40
  ...
36
41
 
37
42
  @abstractmethod
38
- def get_surface_context(self, window: Window, config: SurfaceConfig, shared: SurfaceContext | None) -> SurfaceContext:
43
+ def get_surface_context(
44
+ self,
45
+ window: Window,
46
+ config: SurfaceConfig,
47
+ shared: SurfaceContext | None) -> SurfaceContext:
39
48
  """After a window is created, this will be called.
40
49
 
41
50
  This must return a BackendWindowObject object.
@@ -45,14 +54,29 @@ class BackendGlobalObject(ABC): # Temp name for now.
45
54
  def get_default_configs(self) -> Sequence:
46
55
  """Configs to use if none specified."""
47
56
 
48
- @abstractmethod
49
- def initialize_matrices(self, window: Window) -> WindowTransformations:
50
- """Initialize the global matrices."""
51
-
52
57
  @abstractmethod
53
58
  def set_viewport(self, window, x: int, y: int, width: int, height: int) -> None:
54
59
  """Set the global viewport."""
55
60
 
61
+
62
+ class BackendRenderer(ABC):
63
+ """Backend-specific rendering helpers used by draw-context state transitions."""
64
+
65
+ def __init__(self, surface_ctx: SurfaceContext) -> None:
66
+ self.surface_ctx = surface_ctx
67
+
68
+ @abstractmethod
69
+ def set_viewport(self, x: int, y: int, width: int, height: int) -> None:
70
+ """Set the active viewport."""
71
+
72
+ @abstractmethod
73
+ def set_scissor(self, scissor: Any | None) -> None:
74
+ """Set or clear the active scissor rectangle."""
75
+
76
+ @abstractmethod
77
+ def set_clear_color(self, r: float, g: float, b: float, a: float) -> None:
78
+ """Set the active clear color."""
79
+
56
80
  @staticmethod
57
81
  def load_package_shader(package, resource_name):
58
82
  """Reads a binary resource from the given package or subpackage without external dependencies.
@@ -72,11 +96,19 @@ class BackendGlobalObject(ABC): # Temp name for now.
72
96
  with open(resource_path, 'rb') as file:
73
97
  return file.read()
74
98
 
99
+
100
+ class UnavailableBackendError(GraphicsBackendError):
101
+ """An exception that occurs when a backend is not yet available."""
102
+ def __init__(self) -> None: # noqa: D107
103
+ super().__init__(
104
+ "A backend is not currently available. Ensure the backend choice is correct."
105
+ "If your environment variable is set to no backend, then backend-required resources may have been imported."
106
+ )
107
+
108
+
75
109
  class NullBackend(BackendGlobalObject): # noqa: D101
76
110
  def _raise_no_backend(self) -> NoReturn:
77
- msg = ("A backend is not currently available. Ensure the backend choice is correct. "
78
- "If your environment variable is set to no backend, then backend-required resources may have been imported.")
79
- raise RuntimeError(msg)
111
+ raise UnavailableBackendError
80
112
 
81
113
  @property
82
114
  def object_space(self) -> ObjectSpace:
@@ -89,9 +121,6 @@ class NullBackend(BackendGlobalObject): # noqa: D101
89
121
  def get_default_configs(self) -> Sequence:
90
122
  self._raise_no_backend()
91
123
 
92
- def initialize_matrices(self, window: Window) -> None:
93
- self._raise_no_backend()
94
-
95
124
  def set_viewport(self, window, x: int, y: int, width: int, height: int) -> None:
96
125
  self._raise_no_backend()
97
126
 
@@ -120,6 +149,7 @@ class SurfaceInfo(ABC):
120
149
  MAX_TEXTURE_IMAGE_UNITS: int
121
150
  MAX_COMBINED_TEXTURE_IMAGE_UNITS: int
122
151
  MAX_UNIFORM_BUFFER_BINDINGS: int
152
+ MAX_UNIFORM_BUFFER_OFFSET_ALIGNMENT: int
123
153
  MAX_UNIFORM_BLOCK_SIZE: int
124
154
  MAX_VERTEX_ATTRIBS: int
125
155
 
@@ -142,6 +172,7 @@ class SurfaceInfo(ABC):
142
172
  self.MAX_TEXTURE_IMAGE_UNITS = 0
143
173
  self.MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0
144
174
  self.MAX_UNIFORM_BUFFER_BINDINGS = 0
175
+ self.MAX_UNIFORM_BUFFER_OFFSET_ALIGNMENT = 0
145
176
  self.MAX_UNIFORM_BLOCK_SIZE = 0
146
177
  self.MAX_VERTEX_ATTRIBS = 0
147
178
 
@@ -196,17 +227,89 @@ class SurfaceInfo(ABC):
196
227
  return self.api
197
228
 
198
229
 
230
+ @dataclass
231
+ class FrameSlotData:
232
+ """Resources and fence state for one frame-in-flight slot."""
233
+
234
+ fence: Any | None = None
235
+ ubo_ranges: list[BufferRange] = field(default_factory=list)
236
+
237
+
238
+ class FrameResourceManager:
239
+ """Tracks in-flight GPU-visible resource ranges by frame."""
240
+
241
+ def __init__(self, surface_ctx: SurfaceContext, slots: int = 3) -> None:
242
+ self.surface_ctx = surface_ctx
243
+ self.slots = [FrameSlotData() for _ in range(max(1, int(slots)))]
244
+ self._active_slot = self.slots[0]
245
+
246
+ @property
247
+ def active_slot(self) -> FrameSlotData:
248
+ return self._active_slot
249
+
250
+ def _release_slot(self, slot: FrameSlotData) -> None:
251
+ if slot.fence is not None:
252
+ self.surface_ctx.delete_frame_fence(slot.fence)
253
+ slot.fence = None
254
+ for ubo_range in slot.ubo_ranges:
255
+ ubo_range.frame_use_count = max(0, ubo_range.frame_use_count - 1)
256
+ ubo_range.in_use = ubo_range.frame_use_count > 0
257
+ slot.ubo_ranges.clear()
258
+
259
+ def frame_begin(self, frame_index: int) -> None:
260
+ slot_index = frame_index % len(self.slots)
261
+ slot = self.slots[slot_index]
262
+
263
+ if slot.fence is not None:
264
+ if not self.surface_ctx.poll_frame_fence(slot.fence):
265
+ # The frame resources are still in use by the GPU after we've wrapped around.
266
+ # At this point the CPU has caught up to the GPU. Wait for frame to become available.
267
+ assert _debug_print(f"CPU caught up to GPU on frame slot: {slot_index}, frame index: {frame_index}.")
268
+ self.surface_ctx.wait_frame_fence(slot.fence)
269
+ self._release_slot(slot)
270
+
271
+ self._active_slot = slot
272
+
273
+ def allocate_ubo(self, ubo_range: BufferRange) -> None:
274
+ if ubo_range.in_use:
275
+ msg = "Trying to allocate a UBO range that is already in use by a submitted frame."
276
+ raise GraphicsIntegrationError(msg)
277
+
278
+ self.use_ubo(ubo_range)
279
+
280
+ def use_ubo(self, ubo_range: BufferRange) -> None:
281
+ if any(active_range is ubo_range for active_range in self._active_slot.ubo_ranges):
282
+ return
283
+
284
+ ubo_range.frame_use_count += 1
285
+ ubo_range.in_use = True
286
+ self._active_slot.ubo_ranges.append(ubo_range)
287
+
288
+ def frame_submit(self) -> None:
289
+ if not self._active_slot.ubo_ranges:
290
+ return
291
+ self._active_slot.fence = self.surface_ctx.create_frame_fence()
292
+
293
+ def delete(self) -> None:
294
+ for slot in self.slots:
295
+ self._release_slot(slot)
296
+
297
+
199
298
  class SurfaceContext(ABC): # Temp name for now.
200
299
  """A container for backend resources and information that are tied to a specific Window.
201
300
 
202
301
  In OpenGL this would be something like an OpenGL Context, or in Vulkan, a Surface.
203
302
  """
204
- clear_color = (0.2, 0.2, 0.2, 1.0)
303
+ clear_color = (0.0, 0.0, 0.0, 1.0)
304
+ renderer: BackendRenderer
205
305
 
206
306
  def __init__(self, global_ctx: BackendGlobalObject, window: Window, config: Any) -> None:
207
307
  self.core = global_ctx
208
308
  self.window = window
209
309
  self.config = config
310
+ self.frame_index = 0
311
+ self._frame_active = False
312
+ self.frame_resources = FrameResourceManager(self)
210
313
 
211
314
  @property
212
315
  @abstractmethod
@@ -237,19 +340,69 @@ class SurfaceContext(ABC): # Temp name for now.
237
340
  This function is called automatically when the operating system Window has been created.
238
341
  """
239
342
 
343
+ @property
344
+ def frame_active(self) -> bool:
345
+ return self._frame_active
346
+
240
347
  @abstractmethod
241
348
  def destroy(self) -> None:
242
349
  """Destroys the graphical context."""
243
350
 
351
+ def frame_begin(self) -> None:
352
+ """Begin a frame draw/update scope for this context."""
353
+ self.frame_resources.frame_begin(self.frame_index)
354
+ self._frame_active = True
355
+
356
+ def frame_submit(self) -> None:
357
+ """Submit queued GPU work for the current frame."""
358
+ self.frame_resources.frame_submit()
359
+
360
+ def create_frame_fence(self) -> Any | None:
361
+ """Create a backend fence for submitted frame resources."""
362
+ return None
363
+
364
+ def poll_frame_fence(self, fence: Any) -> bool:
365
+ """Return whether a submitted frame fence has completed."""
366
+ return True
367
+
368
+ def wait_frame_fence(self, fence: Any) -> None:
369
+ """Waits until a submitted frame fence has completed.
370
+
371
+ This is a blocking operation.
372
+ """
373
+
374
+ def delete_frame_fence(self, fence: Any) -> None:
375
+ """Delete a submitted frame fence."""
376
+
377
+ def frame_end(self) -> None:
378
+ """Finalize the current frame, present, and advance frame index."""
379
+ if not self._frame_active:
380
+ return
381
+ try:
382
+ self.frame_submit()
383
+ self.present()
384
+ self.frame_index += 1
385
+ finally:
386
+ self._frame_active = False
387
+
244
388
  @abstractmethod
245
- def flip(self) -> None:
246
- """Flips the buffers in the graphical context."""
389
+ def present(self) -> None:
390
+ """Present rendered frame output."""
247
391
 
248
392
  @abstractmethod
249
393
  def clear(self) -> None:
250
394
  """Clears the framebuffer."""
251
395
 
252
396
 
397
+ class UnavailableContextError(GraphicsBackendError):
398
+ """An exception that occurs when a backend context is not yet available."""
399
+ def __init__(self) -> None: # noqa: D107
400
+ super().__init__(
401
+ "A Rendering Context has not yet been created, or has already been deleted. Please ensure "
402
+ "a context exists (create a Window) before attempting to create objects with GPU resources.",
403
+ )
404
+
405
+
253
406
  class NullContext:
254
407
  """Used to represent a Null state before a Context is created.
255
408
 
@@ -264,9 +417,7 @@ class NullContext:
264
417
 
265
418
  @staticmethod
266
419
  def _raise_no_context() -> NoReturn:
267
- msg = ("A rendering Context has not yet been created, or has already been deleted. Please ensure "
268
- "a context exists (create a Window) before attempting to perform any GPU related activities.")
269
- raise RuntimeError(msg)
420
+ raise UnavailableContextError
270
421
 
271
422
  def __getattribute__(self, item):
272
423
  object.__getattribute__(self, "_raise_no_context")()
@@ -391,42 +542,6 @@ class GraphicsConfig:
391
542
  return self._user_set_attributes
392
543
 
393
544
 
394
- class WindowTransformations:
395
- def __init__(self, window, projection, view, model):
396
- self._window = window
397
- self._projection = projection
398
- self._view = view
399
- self._model = model
400
-
401
- @property
402
- def projection(self) -> Mat4:
403
- return self._projection
404
-
405
- @projection.setter
406
- def projection(self, projection: Mat4) -> None:
407
- self._projection = projection
408
-
409
- @property
410
- def view(self) -> Mat4:
411
- return self._view
412
-
413
- @view.setter
414
- def view(self, view: Mat4) -> None:
415
- self._view = view
416
-
417
- @property
418
- def model(self) -> Mat4:
419
- return self._model
420
-
421
- @model.setter
422
- def model(self, model: Mat4) -> None:
423
- self._model = model
424
-
425
-
426
- class UBOMatrixTransformations(WindowTransformations): # noqa: D101
427
- ...
428
-
429
-
430
545
  class GraphicsResource(Protocol): # noqa: D101
431
546
  def delete(self) -> None:
432
547
  ...
@@ -13,12 +13,12 @@ from pyglet.graphics.api.gl import (
13
13
  GL_DRAW_INDIRECT_BUFFER,
14
14
  GL_DYNAMIC_DRAW,
15
15
  GL_ELEMENT_ARRAY_BUFFER,
16
+ GL_MAP_READ_BIT,
17
+ GL_MAP_WRITE_BIT,
16
18
  GL_READ_ONLY,
17
19
  GL_READ_WRITE,
18
20
  GL_MAP_COHERENT_BIT,
19
21
  GL_MAP_PERSISTENT_BIT,
20
- GL_MAP_READ_BIT,
21
- GL_MAP_WRITE_BIT,
22
22
  GL_PIXEL_PACK_BUFFER,
23
23
  GL_PIXEL_UNPACK_BUFFER,
24
24
  GL_TEXTURE_BUFFER,
@@ -491,11 +491,40 @@ class GLUniformBufferObject(UniformBufferObject):
491
491
  """OpenGL Uniform Buffer Object wrapper."""
492
492
 
493
493
  buffer: GLBufferObject
494
- __slots__ = ()
494
+ minimum_alignment: int
495
+ __slots__ = ("minimum_alignment",)
496
+
497
+ def __init__(
498
+ self,
499
+ context: OpenGLSurfaceContext,
500
+ view_class: type[ctypes.Structure],
501
+ buffer_size: int,
502
+ binding: int,
503
+ *,
504
+ alignment: int | None = None,
505
+ copies_per_resource: int = 3,
506
+ strict: bool = False,
507
+ ) -> None:
508
+ self._context = context
509
+ self.minimum_alignment = max(1, context.info.MAX_UNIFORM_BUFFER_OFFSET_ALIGNMENT)
510
+ requested_alignment = (
511
+ self.minimum_alignment if alignment is None else max(int(alignment), self.minimum_alignment)
512
+ )
513
+ super().__init__(
514
+ context,
515
+ view_class,
516
+ buffer_size,
517
+ binding,
518
+ alignment=requested_alignment,
519
+ copies_per_resource=copies_per_resource,
520
+ strict=strict,
521
+ )
495
522
 
496
523
  def _create_buffer(self, context: OpenGLSurfaceContext, buffer_size: int) -> GLBufferObject:
497
524
  return GLBufferObject(context, buffer_size, target=GL_UNIFORM_BUFFER)
498
525
 
526
+ def _bind_range(self, binding: int, offset: int, size: int) -> None:
527
+ self._context.glBindBufferRange(GL_UNIFORM_BUFFER, binding, self.buffer.id, offset, size)
499
528
 
500
529
  class PersistentBufferObject(BaseMappedBufferObject):
501
530
  """A persistently mapped OpenGL buffer.
@@ -559,7 +588,10 @@ class PersistentBufferObject(BaseMappedBufferObject):
559
588
  self._context.glBufferStorage(self.target, storage_size, data, self.flags)
560
589
 
561
590
  ptr_type = ctypes.POINTER(ctypes.c_byte * storage_size)
562
- mapped = ctypes.cast(self._context.glMapBufferRange(self.target, 0, storage_size, self.flags), ptr_type).contents
591
+ mapped = ctypes.cast(
592
+ self._context.glMapBufferRange(self.target, 0, storage_size, self.flags),
593
+ ptr_type,
594
+ ).contents
563
595
  mapped_ptr = ctypes.addressof(mapped)
564
596
  return new_id, mapped, mapped_ptr, storage_size
565
597
 
@@ -72,5 +72,5 @@ class CocoaContext(OpenGLSurfaceContext):
72
72
  self._nscontext.getValues_forParameter_(byref(vals), cocoapy.NSOpenGLCPSwapInterval)
73
73
  return bool(vals.value)
74
74
 
75
- def flip(self) -> None:
75
+ def present(self) -> None:
76
76
  self._nscontext.flushBuffer()
@@ -5,10 +5,23 @@ import weakref
5
5
  from typing import Callable, TYPE_CHECKING
6
6
 
7
7
  from pyglet.enums import GraphicsAPI
8
+ from pyglet.graphics import GraphicsAPIError, GraphicsIntegrationError
8
9
  from pyglet.graphics.api.gl import gl, gl_info, ObjectSpace
9
10
  from pyglet.graphics.api.base import SurfaceContext, NullContext
10
- from pyglet.graphics.api.gl.gl import GLFunctions, GLuint, GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT
11
-
11
+ from pyglet.graphics.api.gl.gl import (
12
+ GL_ALREADY_SIGNALED,
13
+ GL_COLOR_BUFFER_BIT,
14
+ GL_CONDITION_SATISFIED,
15
+ GL_DEPTH_BUFFER_BIT,
16
+ GL_SYNC_GPU_COMMANDS_COMPLETE,
17
+ GL_TIMEOUT_EXPIRED,
18
+ GL_WAIT_FAILED,
19
+ GLFunctions,
20
+ GLuint,
21
+ GL_SYNC_FLUSH_COMMANDS_BIT,
22
+ GL_TIMEOUT_IGNORED,
23
+ )
24
+ from pyglet.graphics.api.gl.renderer import GLRenderer
12
25
 
13
26
  if TYPE_CHECKING:
14
27
  from pyglet.config import SurfaceConfig
@@ -50,10 +63,7 @@ class OpenGLSurfaceContext(SurfaceContext, GLFunctions):
50
63
  A context to share objects with. Use ``None`` to disable sharing.
51
64
  """
52
65
  self._context = None
53
- self.core = core
54
- self.window = window
55
- self.config = config
56
- #super().__init__(core, window, config)
66
+ super().__init__(core, window, config)
57
67
  self.context_share = context_share or None # Use a real NoneType instead of NullContext
58
68
  self.is_current = False
59
69
  self._info = gl_info.GLInfo(platform_info)
@@ -69,6 +79,7 @@ class OpenGLSurfaceContext(SurfaceContext, GLFunctions):
69
79
  self.object_space = ObjectSpace()
70
80
 
71
81
  self.cached_programs = weakref.WeakValueDictionary()
82
+ self.renderer = GLRenderer(self)
72
83
 
73
84
  # GLES needs an FBO to read pixel data.
74
85
  self.gles_pixel_fbo = None
@@ -108,8 +119,36 @@ class OpenGLSurfaceContext(SurfaceContext, GLFunctions):
108
119
  # raise RuntimeError(msg)
109
120
  self.window = window
110
121
 
111
- def before_draw(self) -> None:
122
+ def frame_begin(self) -> None:
112
123
  self.set_current()
124
+ super().frame_begin()
125
+
126
+ def create_frame_fence(self) -> object | None:
127
+ if not (self.info.have_version(3, 2) or self.info.have_extension("GL_ARB_sync")):
128
+ return None
129
+ return self.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)
130
+
131
+ def wait_frame_fence(self, fence) -> None:
132
+ self.glClientWaitSync(fence, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED)
133
+
134
+ def poll_frame_fence(self, fence) -> bool:
135
+ if fence is None:
136
+ return True
137
+
138
+ result = self.glClientWaitSync(fence, 0, 0)
139
+ if result in (GL_ALREADY_SIGNALED, GL_CONDITION_SATISFIED):
140
+ return True
141
+ if result == GL_TIMEOUT_EXPIRED:
142
+ return False
143
+ if result == GL_WAIT_FAILED:
144
+ raise GraphicsAPIError("glClientWaitSync failed")
145
+
146
+ msg = f"Unexpected glClientWaitSync result: {result}"
147
+ raise GraphicsIntegrationError(msg)
148
+
149
+ def delete_frame_fence(self, fence) -> None:
150
+ if fence is not None:
151
+ self.glDeleteSync(fence)
113
152
 
114
153
  def set_current(self) -> None:
115
154
  """Make this the active Context.