pyglet 3.0.dev4__py3-none-any.whl → 3.0.dev6__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.
- pyglet/__init__.py +1 -1
- pyglet/customtypes.py +11 -5
- pyglet/event.py +3 -23
- pyglet/experimental/geoshader_sprite.py +9 -2
- pyglet/graphics/__init__.py +10 -0
- pyglet/graphics/api/base.py +169 -54
- pyglet/graphics/api/gl/buffer.py +36 -4
- pyglet/graphics/api/gl/cocoa/context.py +1 -1
- pyglet/graphics/api/gl/context.py +46 -7
- pyglet/graphics/api/gl/draw.py +17 -435
- pyglet/graphics/api/gl/egl/context.py +1 -1
- pyglet/graphics/api/gl/gl_info.py +4 -0
- pyglet/graphics/api/gl/global_opengl.py +0 -87
- pyglet/graphics/api/gl/renderer.py +23 -0
- pyglet/graphics/api/gl/shader.py +18 -2
- pyglet/graphics/api/gl/state.py +90 -52
- pyglet/graphics/api/gl/win32/context.py +1 -1
- pyglet/graphics/api/gl/xlib/context.py +1 -1
- pyglet/graphics/api/gl2/draw.py +16 -446
- pyglet/graphics/api/gl2/global_opengl.py +0 -63
- pyglet/graphics/api/gl2/state.py +29 -0
- pyglet/graphics/api/webgl/__init__.py +0 -85
- pyglet/graphics/api/webgl/buffer.py +30 -1
- pyglet/graphics/api/webgl/context.py +5 -2
- pyglet/graphics/api/webgl/draw.py +11 -433
- pyglet/graphics/api/webgl/gl_info.py +5 -0
- pyglet/graphics/api/webgl/renderer.py +23 -0
- pyglet/graphics/api/webgl/shader.py +18 -2
- pyglet/graphics/api/webgl/state.py +78 -61
- pyglet/graphics/buffer.py +380 -10
- pyglet/graphics/draw.py +588 -101
- pyglet/graphics/shader.py +48 -9
- pyglet/graphics/state.py +102 -8
- pyglet/graphics/vertexdomain.py +10 -2
- pyglet/libs/win32/__init__.py +10 -0
- pyglet/libs/win32/constants.py +13 -0
- pyglet/libs/win32/context_managers.py +66 -1
- pyglet/libs/win32/types.py +71 -0
- pyglet/shapes.py +18 -32
- pyglet/sprite.py +12 -21
- pyglet/window/__init__.py +103 -36
- pyglet/window/camera/__init__.py +28 -0
- pyglet/window/camera/base.py +666 -0
- pyglet/window/camera/camera2d.py +333 -0
- pyglet/window/camera/camera3d.py +318 -0
- pyglet/window/cocoa/__init__.py +0 -9
- pyglet/window/cocoa/pyglet_view.py +69 -20
- pyglet/window/emscripten/__init__.py +0 -8
- pyglet/window/headless/__init__.py +0 -8
- pyglet/window/wayland/__init__.py +0 -7
- pyglet/window/win32/__init__.py +174 -17
- pyglet/window/xlib/__init__.py +60 -10
- {pyglet-3.0.dev4.dist-info → pyglet-3.0.dev6.dist-info}/METADATA +2 -2
- {pyglet-3.0.dev4.dist-info → pyglet-3.0.dev6.dist-info}/RECORD +56 -49
- {pyglet-3.0.dev4.dist-info → pyglet-3.0.dev6.dist-info}/WHEEL +0 -0
- {pyglet-3.0.dev4.dist-info → pyglet-3.0.dev6.dist-info}/licenses/LICENSE +0 -0
pyglet/__init__.py
CHANGED
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
]
|
pyglet/event.py
CHANGED
|
@@ -121,7 +121,6 @@ from __future__ import annotations
|
|
|
121
121
|
import inspect
|
|
122
122
|
import os.path
|
|
123
123
|
|
|
124
|
-
from functools import partial
|
|
125
124
|
from typing import TYPE_CHECKING, Literal, Union
|
|
126
125
|
from weakref import WeakMethod
|
|
127
126
|
|
|
@@ -193,7 +192,7 @@ class EventDispatcher:
|
|
|
193
192
|
msg = f'Unknown event "{name}"'
|
|
194
193
|
raise EventException(msg)
|
|
195
194
|
if inspect.ismethod(obj):
|
|
196
|
-
yield name, WeakMethod(obj
|
|
195
|
+
yield name, WeakMethod(obj)
|
|
197
196
|
else:
|
|
198
197
|
yield name, obj
|
|
199
198
|
else:
|
|
@@ -201,7 +200,7 @@ class EventDispatcher:
|
|
|
201
200
|
for name in dir(obj):
|
|
202
201
|
if name in self.event_types:
|
|
203
202
|
meth = getattr(obj, name)
|
|
204
|
-
yield name, WeakMethod(meth
|
|
203
|
+
yield name, WeakMethod(meth)
|
|
205
204
|
|
|
206
205
|
for name, handler in kwargs.items():
|
|
207
206
|
# Function for handling given event (no magic)
|
|
@@ -209,7 +208,7 @@ class EventDispatcher:
|
|
|
209
208
|
msg = f'Unknown event "{name}"'
|
|
210
209
|
raise EventException(msg)
|
|
211
210
|
if inspect.ismethod(handler):
|
|
212
|
-
yield name, WeakMethod(handler
|
|
211
|
+
yield name, WeakMethod(handler)
|
|
213
212
|
else:
|
|
214
213
|
yield name, handler
|
|
215
214
|
|
|
@@ -303,25 +302,6 @@ class EventDispatcher:
|
|
|
303
302
|
except KeyError:
|
|
304
303
|
pass
|
|
305
304
|
|
|
306
|
-
def _remove_handler(self, name: str, handler: Callable) -> None:
|
|
307
|
-
"""Used internally to remove all handler instances for the given event name.
|
|
308
|
-
|
|
309
|
-
This is normally called from a dead ``WeakMethod`` to remove itself from the
|
|
310
|
-
event stack.
|
|
311
|
-
"""
|
|
312
|
-
# Iterate over a copy as we might mutate the list
|
|
313
|
-
for frame in list(self._event_stack):
|
|
314
|
-
|
|
315
|
-
if name in frame:
|
|
316
|
-
try:
|
|
317
|
-
if frame[name] == handler:
|
|
318
|
-
del frame[name]
|
|
319
|
-
if not frame:
|
|
320
|
-
self._event_stack.remove(frame)
|
|
321
|
-
except TypeError:
|
|
322
|
-
# weakref is already dead
|
|
323
|
-
pass
|
|
324
|
-
|
|
325
305
|
def dispatch_event(self, event_type: str, *args: Any) -> bool | None:
|
|
326
306
|
"""Dispatch an event to the attached event handlers.
|
|
327
307
|
|
|
@@ -768,9 +768,16 @@ class Sprite(event.EventDispatcher):
|
|
|
768
768
|
efficiently.
|
|
769
769
|
"""
|
|
770
770
|
ctx = pyglet.graphics.api.core.current_context
|
|
771
|
-
|
|
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(
|
|
780
|
+
self._group.unset_state_recursive(draw_ctx)
|
|
774
781
|
|
|
775
782
|
def __del__(self):
|
|
776
783
|
try:
|
pyglet/graphics/__init__.py
CHANGED
|
@@ -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."""
|
|
@@ -29,6 +37,7 @@ if TYPE_CHECKING:
|
|
|
29
37
|
from pyglet.graphics.shader import Shader, ShaderProgram, ComputeShaderProgram, TransformFeedbackShaderProgram # noqa: F401
|
|
30
38
|
from pyglet.graphics.state import State # noqa: F401
|
|
31
39
|
from pyglet.graphics.shader import get_default_shader # noqa: F401
|
|
40
|
+
from pyglet.graphics.buffer import UniformBufferRegion # noqa: F401
|
|
32
41
|
from pyglet.graphics.draw import get_default_batch # noqa: F401
|
|
33
42
|
from pyglet.graphics.texture import Texture, TextureGrid, Texture3D, TextureArray # noqa: F401
|
|
34
43
|
from pyglet.graphics.atlas import TextureBin, TextureArrayBin, TextureAtlas # noqa: F401
|
|
@@ -45,6 +54,7 @@ else:
|
|
|
45
54
|
)
|
|
46
55
|
from pyglet.graphics.state import State # noqa: F401
|
|
47
56
|
from pyglet.graphics.api import core # noqa: F401
|
|
57
|
+
from pyglet.graphics.buffer import UniformBufferRegion # noqa: F401
|
|
48
58
|
from pyglet.graphics.texture import Texture, TextureGrid, Texture3D, TextureArray # noqa: F401
|
|
49
59
|
from pyglet.graphics.atlas import TextureBin, TextureArrayBin, TextureAtlas # noqa: F401
|
|
50
60
|
from pyglet.graphics.framebuffer import Framebuffer, Renderbuffer # noqa: F401
|
pyglet/graphics/api/base.py
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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.
|
|
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
|
|
246
|
-
"""
|
|
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
|
-
|
|
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
|
...
|
pyglet/graphics/api/gl/buffer.py
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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.
|