pyglet 2.1.13__py3-none-any.whl → 3.0.dev1__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 (267) hide show
  1. pyglet/__init__.py +67 -61
  2. pyglet/__init__.pyi +15 -8
  3. pyglet/app/__init__.py +22 -13
  4. pyglet/app/async_app.py +212 -0
  5. pyglet/app/base.py +2 -1
  6. pyglet/app/{xlib.py → linux.py} +3 -3
  7. pyglet/config/__init__.py +101 -0
  8. pyglet/config/gl/__init__.py +30 -0
  9. pyglet/config/gl/egl.py +120 -0
  10. pyglet/config/gl/macos.py +262 -0
  11. pyglet/config/gl/windows.py +267 -0
  12. pyglet/config/gl/x11.py +142 -0
  13. pyglet/customtypes.py +43 -2
  14. pyglet/display/__init__.py +8 -6
  15. pyglet/display/base.py +3 -63
  16. pyglet/display/cocoa.py +12 -17
  17. pyglet/display/emscripten.py +39 -0
  18. pyglet/display/headless.py +23 -30
  19. pyglet/display/wayland.py +157 -0
  20. pyglet/display/win32.py +5 -21
  21. pyglet/display/xlib.py +19 -27
  22. pyglet/display/xlib_vidmoderestore.py +2 -2
  23. pyglet/enums.py +183 -0
  24. pyglet/event.py +0 -1
  25. pyglet/experimental/geoshader_sprite.py +15 -13
  26. pyglet/experimental/hidraw.py +6 -15
  27. pyglet/experimental/multitexture_sprite.py +31 -19
  28. pyglet/experimental/particles.py +13 -35
  29. pyglet/font/__init__.py +251 -85
  30. pyglet/font/base.py +116 -61
  31. pyglet/font/dwrite/__init__.py +349 -204
  32. pyglet/font/dwrite/dwrite_lib.py +27 -5
  33. pyglet/font/fontconfig.py +14 -6
  34. pyglet/font/freetype.py +138 -87
  35. pyglet/font/freetype_lib.py +19 -0
  36. pyglet/font/group.py +179 -0
  37. pyglet/font/harfbuzz/__init__.py +3 -3
  38. pyglet/font/pyodide_js.py +310 -0
  39. pyglet/font/quartz.py +319 -126
  40. pyglet/font/ttf.py +45 -3
  41. pyglet/font/user.py +14 -19
  42. pyglet/font/win32.py +45 -21
  43. pyglet/graphics/__init__.py +8 -787
  44. pyglet/graphics/allocation.py +115 -1
  45. pyglet/graphics/api/__init__.py +77 -0
  46. pyglet/graphics/api/base.py +299 -0
  47. pyglet/graphics/api/gl/__init__.py +58 -0
  48. pyglet/graphics/api/gl/base.py +24 -0
  49. pyglet/graphics/{vertexbuffer.py → api/gl/buffer.py} +104 -159
  50. pyglet/graphics/api/gl/cocoa/context.py +76 -0
  51. pyglet/graphics/api/gl/context.py +391 -0
  52. pyglet/graphics/api/gl/default_shaders.py +0 -0
  53. pyglet/graphics/api/gl/draw.py +627 -0
  54. pyglet/graphics/api/gl/egl/__init__.py +0 -0
  55. pyglet/graphics/api/gl/egl/context.py +92 -0
  56. pyglet/graphics/api/gl/enums.py +76 -0
  57. pyglet/graphics/api/gl/framebuffer.py +315 -0
  58. pyglet/graphics/api/gl/gl.py +5463 -0
  59. pyglet/graphics/api/gl/gl_info.py +188 -0
  60. pyglet/graphics/api/gl/global_opengl.py +226 -0
  61. pyglet/{gl → graphics/api/gl}/lib.py +34 -18
  62. pyglet/graphics/api/gl/shader.py +1476 -0
  63. pyglet/graphics/api/gl/shapes.py +55 -0
  64. pyglet/graphics/api/gl/sprite.py +102 -0
  65. pyglet/graphics/api/gl/state.py +219 -0
  66. pyglet/graphics/api/gl/text.py +190 -0
  67. pyglet/graphics/api/gl/texture.py +1526 -0
  68. pyglet/graphics/{vertexarray.py → api/gl/vertexarray.py} +11 -13
  69. pyglet/graphics/api/gl/vertexdomain.py +751 -0
  70. pyglet/graphics/api/gl/win32/__init__.py +0 -0
  71. pyglet/graphics/api/gl/win32/context.py +108 -0
  72. pyglet/graphics/api/gl/win32/wgl_info.py +24 -0
  73. pyglet/graphics/api/gl/xlib/__init__.py +0 -0
  74. pyglet/graphics/api/gl/xlib/context.py +174 -0
  75. pyglet/{gl → graphics/api/gl/xlib}/glx_info.py +26 -31
  76. pyglet/graphics/api/gl1/__init__.py +0 -0
  77. pyglet/{gl → graphics/api/gl1}/gl_compat.py +3 -2
  78. pyglet/graphics/api/gl2/__init__.py +0 -0
  79. pyglet/graphics/api/gl2/buffer.py +320 -0
  80. pyglet/graphics/api/gl2/draw.py +600 -0
  81. pyglet/graphics/api/gl2/global_opengl.py +122 -0
  82. pyglet/graphics/api/gl2/shader.py +200 -0
  83. pyglet/graphics/api/gl2/shapes.py +51 -0
  84. pyglet/graphics/api/gl2/sprite.py +79 -0
  85. pyglet/graphics/api/gl2/text.py +175 -0
  86. pyglet/graphics/api/gl2/vertexdomain.py +364 -0
  87. pyglet/graphics/api/webgl/__init__.py +233 -0
  88. pyglet/graphics/api/webgl/buffer.py +302 -0
  89. pyglet/graphics/api/webgl/context.py +234 -0
  90. pyglet/graphics/api/webgl/draw.py +590 -0
  91. pyglet/graphics/api/webgl/enums.py +76 -0
  92. pyglet/graphics/api/webgl/framebuffer.py +360 -0
  93. pyglet/graphics/api/webgl/gl.py +1537 -0
  94. pyglet/graphics/api/webgl/gl_info.py +130 -0
  95. pyglet/graphics/api/webgl/shader.py +1346 -0
  96. pyglet/graphics/api/webgl/shapes.py +92 -0
  97. pyglet/graphics/api/webgl/sprite.py +102 -0
  98. pyglet/graphics/api/webgl/state.py +227 -0
  99. pyglet/graphics/api/webgl/text.py +187 -0
  100. pyglet/graphics/api/webgl/texture.py +1227 -0
  101. pyglet/graphics/api/webgl/vertexarray.py +54 -0
  102. pyglet/graphics/api/webgl/vertexdomain.py +616 -0
  103. pyglet/graphics/api/webgl/webgl_js.pyi +307 -0
  104. pyglet/{image → graphics}/atlas.py +33 -32
  105. pyglet/graphics/base.py +10 -0
  106. pyglet/graphics/buffer.py +245 -0
  107. pyglet/graphics/draw.py +578 -0
  108. pyglet/graphics/framebuffer.py +26 -0
  109. pyglet/graphics/instance.py +178 -69
  110. pyglet/graphics/shader.py +267 -1553
  111. pyglet/graphics/state.py +83 -0
  112. pyglet/graphics/texture.py +703 -0
  113. pyglet/graphics/vertexdomain.py +695 -538
  114. pyglet/gui/ninepatch.py +10 -10
  115. pyglet/gui/widgets.py +120 -10
  116. pyglet/image/__init__.py +20 -1973
  117. pyglet/image/animation.py +12 -12
  118. pyglet/image/base.py +730 -0
  119. pyglet/image/codecs/__init__.py +9 -0
  120. pyglet/image/codecs/bmp.py +53 -30
  121. pyglet/image/codecs/dds.py +53 -31
  122. pyglet/image/codecs/gdiplus.py +38 -14
  123. pyglet/image/codecs/gdkpixbuf2.py +0 -2
  124. pyglet/image/codecs/js_image.py +99 -0
  125. pyglet/image/codecs/ktx2.py +161 -0
  126. pyglet/image/codecs/pil.py +1 -1
  127. pyglet/image/codecs/png.py +1 -1
  128. pyglet/image/codecs/wic.py +11 -2
  129. pyglet/info.py +26 -24
  130. pyglet/input/__init__.py +8 -0
  131. pyglet/input/base.py +163 -105
  132. pyglet/input/controller.py +13 -19
  133. pyglet/input/controller_db.py +39 -24
  134. pyglet/input/emscripten/__init__.py +18 -0
  135. pyglet/input/emscripten/gamepad_js.py +397 -0
  136. pyglet/input/linux/__init__.py +11 -5
  137. pyglet/input/linux/evdev.py +10 -11
  138. pyglet/input/linux/x11_xinput.py +2 -2
  139. pyglet/input/linux/x11_xinput_tablet.py +1 -1
  140. pyglet/input/macos/__init__.py +7 -2
  141. pyglet/input/macos/darwin_gc.py +559 -0
  142. pyglet/input/win32/__init__.py +1 -1
  143. pyglet/input/win32/directinput.py +34 -29
  144. pyglet/input/win32/xinput.py +11 -61
  145. pyglet/lib.py +3 -3
  146. pyglet/libs/__init__.py +1 -1
  147. pyglet/{gl → libs/darwin}/agl.py +1 -1
  148. pyglet/libs/darwin/cocoapy/__init__.py +2 -2
  149. pyglet/libs/darwin/cocoapy/cocoahelpers.py +181 -0
  150. pyglet/libs/darwin/cocoapy/cocoalibs.py +31 -0
  151. pyglet/libs/darwin/cocoapy/cocoatypes.py +27 -0
  152. pyglet/libs/darwin/cocoapy/runtime.py +81 -45
  153. pyglet/libs/darwin/coreaudio.py +4 -4
  154. pyglet/{gl → libs/darwin}/lib_agl.py +9 -8
  155. pyglet/libs/darwin/quartzkey.py +1 -3
  156. pyglet/libs/egl/__init__.py +2 -0
  157. pyglet/libs/egl/egl_lib.py +576 -0
  158. pyglet/libs/egl/eglext.py +51 -5
  159. pyglet/libs/linux/__init__.py +0 -0
  160. pyglet/libs/linux/egl/__init__.py +0 -0
  161. pyglet/libs/linux/egl/eglext.py +22 -0
  162. pyglet/libs/linux/glx/__init__.py +0 -0
  163. pyglet/{gl → libs/linux/glx}/glx.py +13 -14
  164. pyglet/{gl → libs/linux/glx}/glxext_arb.py +408 -192
  165. pyglet/{gl → libs/linux/glx}/glxext_mesa.py +1 -1
  166. pyglet/{gl → libs/linux/glx}/glxext_nv.py +345 -164
  167. pyglet/{gl → libs/linux/glx}/lib_glx.py +3 -2
  168. pyglet/libs/linux/wayland/__init__.py +0 -0
  169. pyglet/libs/linux/wayland/client.py +1068 -0
  170. pyglet/libs/linux/wayland/lib_wayland.py +207 -0
  171. pyglet/libs/linux/wayland/wayland_egl.py +38 -0
  172. pyglet/libs/{wayland → linux/wayland}/xkbcommon.py +26 -0
  173. pyglet/libs/{x11 → linux/x11}/xf86vmode.py +4 -4
  174. pyglet/libs/{x11 → linux/x11}/xinerama.py +2 -2
  175. pyglet/libs/{x11 → linux/x11}/xinput.py +10 -10
  176. pyglet/libs/linux/x11/xrandr.py +0 -0
  177. pyglet/libs/{x11 → linux/x11}/xrender.py +1 -1
  178. pyglet/libs/shared/__init__.py +0 -0
  179. pyglet/libs/shared/spirv/__init__.py +0 -0
  180. pyglet/libs/shared/spirv/lib_shaderc.py +85 -0
  181. pyglet/libs/shared/spirv/lib_spirv_cross.py +126 -0
  182. pyglet/libs/win32/__init__.py +28 -8
  183. pyglet/libs/win32/constants.py +59 -48
  184. pyglet/libs/win32/context_managers.py +20 -3
  185. pyglet/libs/win32/dinput.py +105 -88
  186. pyglet/{gl → libs/win32}/lib_wgl.py +52 -26
  187. pyglet/libs/win32/types.py +58 -23
  188. pyglet/{gl → libs/win32}/wgl.py +32 -25
  189. pyglet/{gl → libs/win32}/wglext_arb.py +364 -2
  190. pyglet/media/__init__.py +9 -10
  191. pyglet/media/codecs/__init__.py +12 -1
  192. pyglet/media/codecs/base.py +99 -96
  193. pyglet/media/codecs/ffmpeg.py +2 -2
  194. pyglet/media/codecs/ffmpeg_lib/libavformat.py +3 -8
  195. pyglet/media/codecs/webaudio_pyodide.py +111 -0
  196. pyglet/media/drivers/__init__.py +9 -4
  197. pyglet/media/drivers/base.py +4 -4
  198. pyglet/media/drivers/openal/__init__.py +1 -1
  199. pyglet/media/drivers/openal/adaptation.py +3 -3
  200. pyglet/media/drivers/pulse/__init__.py +1 -1
  201. pyglet/media/drivers/pulse/adaptation.py +3 -3
  202. pyglet/media/drivers/pyodide_js/__init__.py +8 -0
  203. pyglet/media/drivers/pyodide_js/adaptation.py +288 -0
  204. pyglet/media/drivers/xaudio2/adaptation.py +3 -3
  205. pyglet/media/player.py +276 -193
  206. pyglet/media/player_worker_thread.py +1 -1
  207. pyglet/model/__init__.py +39 -29
  208. pyglet/model/codecs/base.py +4 -4
  209. pyglet/model/codecs/gltf.py +3 -3
  210. pyglet/model/codecs/obj.py +71 -43
  211. pyglet/resource.py +129 -78
  212. pyglet/shapes.py +154 -194
  213. pyglet/sprite.py +47 -164
  214. pyglet/text/__init__.py +44 -54
  215. pyglet/text/caret.py +12 -7
  216. pyglet/text/document.py +19 -17
  217. pyglet/text/formats/html.py +2 -2
  218. pyglet/text/formats/structured.py +10 -40
  219. pyglet/text/layout/__init__.py +20 -13
  220. pyglet/text/layout/base.py +176 -287
  221. pyglet/text/layout/incremental.py +9 -10
  222. pyglet/text/layout/scrolling.py +7 -95
  223. pyglet/window/__init__.py +183 -172
  224. pyglet/window/cocoa/__init__.py +62 -51
  225. pyglet/window/cocoa/pyglet_delegate.py +2 -25
  226. pyglet/window/cocoa/pyglet_view.py +9 -8
  227. pyglet/window/dialog/__init__.py +184 -0
  228. pyglet/window/dialog/base.py +99 -0
  229. pyglet/window/dialog/darwin.py +121 -0
  230. pyglet/window/dialog/linux.py +72 -0
  231. pyglet/window/dialog/windows.py +194 -0
  232. pyglet/window/emscripten/__init__.py +779 -0
  233. pyglet/window/headless/__init__.py +44 -28
  234. pyglet/window/key.py +2 -0
  235. pyglet/window/mouse.py +2 -2
  236. pyglet/window/wayland/__init__.py +377 -0
  237. pyglet/window/win32/__init__.py +101 -46
  238. pyglet/window/xlib/__init__.py +104 -66
  239. {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/METADATA +2 -3
  240. pyglet-3.0.dev1.dist-info/RECORD +322 -0
  241. {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/WHEEL +1 -1
  242. pyglet/gl/__init__.py +0 -208
  243. pyglet/gl/base.py +0 -499
  244. pyglet/gl/cocoa.py +0 -309
  245. pyglet/gl/gl.py +0 -4625
  246. pyglet/gl/gl.pyi +0 -2320
  247. pyglet/gl/gl_compat.pyi +0 -3097
  248. pyglet/gl/gl_info.py +0 -190
  249. pyglet/gl/headless.py +0 -166
  250. pyglet/gl/wgl_info.py +0 -36
  251. pyglet/gl/wglext_nv.py +0 -1096
  252. pyglet/gl/win32.py +0 -268
  253. pyglet/gl/xlib.py +0 -295
  254. pyglet/image/buffer.py +0 -274
  255. pyglet/image/codecs/s3tc.py +0 -354
  256. pyglet/libs/x11/xrandr.py +0 -166
  257. pyglet-2.1.13.dist-info/RECORD +0 -234
  258. /pyglet/{libs/wayland → graphics/api/gl/cocoa}/__init__.py +0 -0
  259. /pyglet/libs/{egl → linux/egl}/egl.py +0 -0
  260. /pyglet/libs/{egl → linux/egl}/lib.py +0 -0
  261. /pyglet/libs/{ioctl.py → linux/ioctl.py} +0 -0
  262. /pyglet/libs/{wayland → linux/wayland}/gbm.py +0 -0
  263. /pyglet/libs/{x11 → linux/x11}/__init__.py +0 -0
  264. /pyglet/libs/{x11 → linux/x11}/cursorfont.py +0 -0
  265. /pyglet/libs/{x11 → linux/x11}/xlib.py +0 -0
  266. /pyglet/libs/{x11 → linux/x11}/xsync.py +0 -0
  267. {pyglet-2.1.13.dist-info/licenses → pyglet-3.0.dev1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,578 @@
1
+ from __future__ import annotations
2
+
3
+ import weakref
4
+ from dataclasses import dataclass
5
+ from typing import Any, Callable, Sequence, TYPE_CHECKING
6
+
7
+ import pyglet
8
+ from pyglet.enums import BlendFactor, BlendOp, CompareOp
9
+
10
+ from pyglet.graphics.state import (
11
+ State,
12
+ TextureState,
13
+ ShaderProgramState,
14
+ BlendState,
15
+ ShaderUniformState,
16
+ UniformBufferState,
17
+ DepthBufferComparison,
18
+ ScissorState,
19
+ ViewportState,
20
+ _expand_states_in_order,
21
+ )
22
+
23
+ if TYPE_CHECKING:
24
+ from pyglet.customtypes import ScissorProtocol
25
+ from pyglet.graphics import GeometryMode
26
+ from pyglet.graphics.api.base import SurfaceContext
27
+ from pyglet.graphics.texture import TextureBase
28
+ from pyglet.graphics.vertexdomain import VertexDomain, VertexList, IndexedVertexList
29
+ from pyglet.graphics.shader import ShaderProgramBase
30
+
31
+
32
+
33
+ class Group:
34
+ """Group of common state.
35
+
36
+ ``Group`` provides extra control over how drawables are handled within a
37
+ ``Batch``. When a batch draws a drawable, it ensures its group's state is set;
38
+ this can include binding textures, shaders, or setting any other parameters.
39
+ It also sorts the groups before drawing.
40
+
41
+ In the following example, the background sprite is guaranteed to be drawn
42
+ before the car and the boat::
43
+
44
+ batch = pyglet.graphics.Batch()
45
+ background = pyglet.graphics.Group(order=0)
46
+ foreground = pyglet.graphics.Group(order=1)
47
+
48
+ background = pyglet.sprite.Sprite(background_image, batch=batch, group=background)
49
+ car = pyglet.sprite.Sprite(car_image, batch=batch, group=foreground)
50
+ boat = pyglet.sprite.Sprite(boat_image, batch=batch, group=foreground)
51
+
52
+ def on_draw():
53
+ batch.draw()
54
+ """
55
+ states: list[State]
56
+ _hash: int
57
+ _hashable_states: tuple
58
+
59
+ def __init__(self, order: int = 0, parent: Group | None = None) -> None:
60
+ """Initialize a rendering group.
61
+
62
+ Args:
63
+ order:
64
+ Set the order to render above or below other Groups.
65
+ Lower orders are drawn first.
66
+ parent:
67
+ Group to contain this Group; its state will be set before this Group's state.
68
+ """
69
+ self._order = order
70
+ self.parent = parent
71
+ self._visible = True
72
+ self._assigned_batches = weakref.WeakSet()
73
+
74
+ self._state_names = {}
75
+ self._states = []
76
+ self._expanded_states = []
77
+ self._comparisons = []
78
+
79
+ # Default hash
80
+ self._hashable_states = ()
81
+ self._hash = hash((self._order, self.parent))
82
+
83
+ def add_state(self, state: State) -> None:
84
+ self._states.append(state)
85
+ self._state_names[state.__class__.__name__] = state
86
+ self._expanded_states = _expand_states_in_order(self._states)
87
+
88
+ self._hashable_states = tuple({state for state in self._states if state.group_hash is True})
89
+ self._hash = hash((self._order, self.parent, self._hashable_states))
90
+
91
+ def add_comparison(self, value):
92
+ self._comparisons.append(value)
93
+
94
+ def set_scissor(self, scissor_object: ScissorProtocol) -> None:
95
+ self.add_state(ScissorState(scissor_object))
96
+
97
+ def set_blend(self, blend_src: BlendFactor, blend_dst: BlendFactor, blend_op: BlendOp = BlendOp.ADD):
98
+ self.add_state(BlendState(blend_src, blend_dst, blend_op))
99
+
100
+ def set_depth_test(self, func: CompareOp) -> None:
101
+ self.add_state(DepthBufferComparison(func))
102
+
103
+ def set_viewport(self, x, y, width, height):
104
+ self.add_state(ViewportState(x, y, width, height))
105
+
106
+ def set_shader_program(self, program: ShaderProgramBase):
107
+ self.add_state(ShaderProgramState(program))
108
+
109
+ def set_shader_uniforms(self, program: ShaderProgramBase, uniforms: dict[str, Any]):
110
+ self.add_state(ShaderUniformState(program, uniforms))
111
+
112
+ def set_uniform_buffer(self, ubo: str, binding: int):
113
+ self.add_state(UniformBufferState(ubo, binding))
114
+
115
+ def set_texture(self, texture: TextureBase, texture_unit: int=0, set_id: int=0) -> None:
116
+ """Set the texture state.
117
+
118
+ Args:
119
+ texture:
120
+ The Texture instance that this draw call uses.
121
+ texture_unit:
122
+ The binding unit this Texture/Sampler is bound to.
123
+ In OpenGL this is the Active Texture (glActiveTexture).
124
+ In Vulkan this is the Sampler binding number in the descriptor.
125
+ set_id:
126
+ The set that the sampler belongs to. Only applicable in Vulkan.
127
+ """
128
+ self.add_state(TextureState.from_texture(texture, texture_unit, set_id))
129
+
130
+ @property
131
+ def order(self) -> int:
132
+ """Rendering order of this group compared to others.
133
+
134
+ Lower numbers are drawn first.
135
+ """
136
+ return self._order
137
+
138
+ @property
139
+ def visible(self) -> bool:
140
+ """Visibility of the group in the rendering pipeline.
141
+
142
+ Determines whether this Group is visible in any of the Batches
143
+ it is assigned to. If ``False``, objects in this Group will not
144
+ be rendered.
145
+ """
146
+ return self._visible
147
+
148
+ @visible.setter
149
+ def visible(self, value: bool) -> None:
150
+ self._visible = value
151
+
152
+ for batch in self._assigned_batches:
153
+ batch.invalidate()
154
+
155
+ @property
156
+ def batches(self) -> tuple[BatchBase, ...]:
157
+ """Which graphics Batches this Group is a part of.
158
+
159
+ Read Only.
160
+ """
161
+ return tuple(self._assigned_batches)
162
+
163
+ def __lt__(self, other: Group) -> bool:
164
+ return self._order < other.order
165
+
166
+ def __eq__(self, other: Group) -> bool:
167
+ """Comparison function used to determine if another Group is providing the same state.
168
+
169
+ When the same state is determined, those groups will be consolidated into one draw call.
170
+
171
+ If subclassing, then care must be taken to ensure this function can compare to another of the same group.
172
+
173
+ :see: ``__hash__`` function, both must be implemented.
174
+ """
175
+ return (self.__class__ is other.__class__ and
176
+ self._order == other.order and
177
+ self.parent == other.parent and
178
+ self._hashable_states == other._hashable_states and
179
+ self._comparisons == other._comparisons)
180
+
181
+ def __hash__(self) -> int:
182
+ """This is an immutable return to establish the permanent identity of the object.
183
+
184
+ This is used by Python with ``__eq__`` to determine if something is unique.
185
+
186
+ For simplicity, the hash should be a tuple containing your unique identifiers of your Group.
187
+
188
+ By default, this is (``order``, ``parent``).
189
+
190
+ :see: ``__eq__`` function, both must be implemented.
191
+ """
192
+ return self._hash
193
+
194
+ def __repr__(self) -> str:
195
+ return f"{self.__class__.__name__}(order={self._order})"
196
+
197
+ def set_state_all(self, ctx: SurfaceContext) -> None:
198
+ """Calls all set states of the underlying Group."""
199
+ for state in self._expanded_states:
200
+ if state.sets_state:
201
+ state.set_state(ctx)
202
+
203
+ def unset_state_all(self, ctx: SurfaceContext) -> None:
204
+ """Calls all unset states of the underlying Group."""
205
+ for state in self._expanded_states:
206
+ if state.unsets_state:
207
+ state.unset_state(ctx)
208
+
209
+ def set_state_recursive(self, ctx: SurfaceContext) -> None:
210
+ """Set this group and its ancestry.
211
+
212
+ Call this method if you are using a group in isolation: the
213
+ parent groups will be called in top-down order, with this class's
214
+ ``set`` being called last.
215
+ """
216
+ if self.parent:
217
+ self.parent.set_state_recursive(ctx)
218
+ self.set_state_all(ctx)
219
+
220
+ def unset_state_recursive(self, ctx: SurfaceContext) -> None:
221
+ """Unset this group and its ancestry.
222
+
223
+ The inverse of ``set_state_recursive``.
224
+ """
225
+ self.unset_state_all(ctx)
226
+ if self.parent:
227
+ self.parent.unset_state_recursive(ctx)
228
+
229
+
230
+ _debug_graphics_batch = pyglet.options.debug_graphics_batch
231
+ _domain_class_map: dict[tuple[bool, bool], type[VertexDomain]] = {
232
+ # Indexed, Instanced : Domain
233
+ # (False, False): vertexdomain.VertexDomain,
234
+ # (True, False): vertexdomain.IndexedVertexDomain,
235
+ # (False, True): vertexdomain.InstancedVertexDomain,
236
+ # (True, True): vertexdomain.InstancedIndexedVertexDomain,
237
+ }
238
+
239
+ @dataclass(frozen=True)
240
+ class _DomainKey:
241
+ indexed: bool
242
+ instanced: bool
243
+ mode: GeometryMode
244
+ attributes: str
245
+
246
+ class BatchBase:
247
+ """Manage a collection of drawables for batched rendering.
248
+
249
+ Many drawable pyglet objects accept an optional `Batch` argument in their
250
+ constructors. By giving a `Batch` to multiple objects, you can tell pyglet
251
+ that you expect to draw all of these objects at once, so it can optimise its
252
+ use of OpenGL. Hence, drawing a `Batch` is often much faster than drawing
253
+ each contained drawable separately.
254
+
255
+ The following example creates a batch, adds two sprites to the batch, and
256
+ then draws the entire batch::
257
+
258
+ batch = pyglet.graphics.Batch()
259
+ car = pyglet.sprite.Sprite(car_image, batch=batch)
260
+ boat = pyglet.sprite.Sprite(boat_image, batch=batch)
261
+
262
+ def on_draw():
263
+ batch.draw()
264
+
265
+ While any drawables can be added to a `Batch`, only those with the same
266
+ draw mode, shader program, and group can be optimised together.
267
+
268
+ Internally, a `Batch` manages a set of VertexDomains along with
269
+ information about how the domains are to be drawn. To implement batching on
270
+ a custom drawable, get your vertex domains from the given batch instead of
271
+ setting them up yourself.
272
+ """
273
+ _empty_domains: set[_DomainKey]
274
+ _domain_registry: dict[_DomainKey, Any]
275
+ _draw_list: list[Callable]
276
+ top_groups: list[Group]
277
+ group_children: dict[Group, list[Group]]
278
+ group_map: dict[Group, dict[_DomainKey, VertexDomain]]
279
+ initial_count: int
280
+
281
+ def __init__(self, initial_count: int = 32) -> None:
282
+ """Initialize a graphics batch.
283
+
284
+ Args:
285
+ initial_count:
286
+ The initial vertex count of the buffers created by this batch.
287
+ """
288
+ # Mapping to find domain.
289
+ # group -> (attributes, mode, indexed) -> domain
290
+ self.group_map = {}
291
+
292
+ # Mapping of group to list of children.
293
+ self.group_children = {}
294
+
295
+ # List of top-level groups
296
+ self.top_groups = []
297
+
298
+ self._draw_list = []
299
+ self._draw_list_dirty = False
300
+
301
+ # Mapping of DomainKey to a VertexDomain
302
+ self._domain_registry = {}
303
+
304
+ # Keep empty domains around for a little to prevent possible.
305
+ self._empty_domains = set()
306
+
307
+ self.initial_count = initial_count
308
+
309
+ def invalidate(self) -> None:
310
+ """Force the batch to update the draw list.
311
+
312
+ This method can be used to force the batch to re-compute the draw list
313
+ when the ordering of groups has changed.
314
+
315
+ .. versionadded:: 1.2
316
+ """
317
+ self._draw_list_dirty = True
318
+
319
+ def delete_empty_domains(self) -> None:
320
+ """Deletes all empty domains and all of their buffers.
321
+
322
+ Should not need to be called through normal usage, as this will occur periodically.
323
+ """
324
+ for domain_key in self._empty_domains:
325
+ domain = self._domain_registry[domain_key]
326
+ # It's possible this domain was re-used before being deleted, check one last time before removal.
327
+ if domain.is_empty:
328
+ del self._domain_registry[domain_key]
329
+ self._empty_domains.clear()
330
+
331
+ def update_shader(self, vertex_list: VertexList | IndexedVertexList, mode: GeometryMode, group: Group,
332
+ program: ShaderProgramBase) -> bool:
333
+ """Migrate a vertex list to another domain that has the specified shader attributes.
334
+
335
+ The results are undefined if `mode` is not correct or if `vertex_list`
336
+ does not belong to this batch (they are not checked and will not
337
+ necessarily throw an exception immediately).
338
+
339
+ Args:
340
+ vertex_list:
341
+ A vertex list currently belonging to this batch.
342
+ mode:
343
+ The current GL drawing mode of the vertex list.
344
+ group:
345
+ The new group to migrate to.
346
+ program:
347
+ The new shader program to migrate to.
348
+
349
+ Returns:
350
+ False if the domain's no longer match. The caller should handle this scenario.
351
+ """
352
+ # No new attributes.
353
+ attributes = program.attributes.copy()
354
+
355
+ # Formats may differ (normalization) than what is declared in the shader.
356
+ # Make those adjustments and attempt to get a domain.
357
+ for a_name in attributes:
358
+ if (a_name in vertex_list.initial_attribs and
359
+ vertex_list.initial_attribs[a_name]['format'] != attributes[a_name]['format']):
360
+ attributes[a_name]['format'] = vertex_list.initial_attribs[a_name]['format']
361
+
362
+ domain = self.get_domain(vertex_list.indexed, vertex_list.instanced, mode, group, attributes)
363
+
364
+ # TODO: Allow migration if we can restore original vertices somehow. Much faster.
365
+ # If the domain's don't match, we need to re-create the vertex list. Tell caller no match.
366
+ if domain != vertex_list.domain:
367
+ return False
368
+
369
+ return True
370
+
371
+ def migrate(self, vertex_list: VertexList | IndexedVertexList, mode: GeometryMode, group: Group, batch: BatchBase) -> None:
372
+ """Migrate a vertex list to another batch and/or group.
373
+
374
+ `vertex_list` and `mode` together identify the vertex list to migrate.
375
+ `group` and `batch` are new owners of the vertex list after migration.
376
+
377
+ The results are undefined if `mode` is not correct or if `vertex_list`
378
+ does not belong to this batch (they are not checked and will not
379
+ necessarily throw an exception immediately).
380
+
381
+ ``batch`` can remain unchanged if only a group change is desired.
382
+
383
+ Args:
384
+ vertex_list:
385
+ A vertex list currently belonging to this batch.
386
+ mode:
387
+ The current GL drawing mode of the vertex list.
388
+ group:
389
+ The new group to migrate to.
390
+ batch:
391
+ The batch to migrate to (or the current batch).
392
+
393
+ """
394
+ attributes = vertex_list.domain.attribute_meta
395
+ domain = batch.get_domain(vertex_list.indexed, vertex_list.instanced, mode, group, attributes)
396
+ vertex_list.migrate(domain)
397
+
398
+ def get_domain(self, indexed: bool, instanced: bool, mode: GeometryMode, group: Group,
399
+ attributes: dict[str, Any]) -> VertexDomain:
400
+ """Get, or create, the vertex domain corresponding to the given arguments.
401
+
402
+ mode is the render mode such as GL_LINES or GL_TRIANGLES
403
+ """
404
+ # Batch group
405
+ if group not in self.group_map:
406
+ self._add_group(group)
407
+
408
+ domain_map = self.group_map[group]
409
+
410
+ # If instanced, ensure a separate domain, as multiple instance sources can match the key.
411
+ if instanced:
412
+ self._instance_count += 1
413
+ key = (indexed, self._instance_count, mode, str(attributes))
414
+ else:
415
+ # Find domain given formats, indices and mode
416
+ key = (indexed, 0, mode, str(attributes))
417
+
418
+ try:
419
+ domain = domain_map[key]
420
+ except KeyError:
421
+ # Create domain
422
+ domain = _domain_class_map[(indexed, instanced)](attributes)
423
+ domain_map[key] = domain
424
+ self._draw_list_dirty = True
425
+
426
+ return domain
427
+
428
+ def _add_group(self, group: Group) -> None:
429
+ self.group_map[group] = {}
430
+ if group.parent is None:
431
+ self.top_groups.append(group)
432
+ else:
433
+ if group.parent not in self.group_map:
434
+ self._add_group(group.parent)
435
+ if group.parent not in self.group_children:
436
+ self.group_children[group.parent] = []
437
+ self.group_children[group.parent].append(group)
438
+
439
+ group._assigned_batches.add(self) # noqa: SLF001
440
+ self._draw_list_dirty = True
441
+
442
+ def _update_draw_list(self) -> None:
443
+ """Visit group tree in preorder and create a list of bound methods to call."""
444
+
445
+ def visit(group: Group) -> list:
446
+ draw_list = []
447
+
448
+ # Draw domains using this group
449
+ domain_map = self.group_map[group]
450
+
451
+ # indexed, instanced, mode, program, str(attributes))
452
+ for (indexed, instanced, mode, formats), domain in list(domain_map.items()):
453
+ # Remove unused domains from batch
454
+ if domain.is_empty:
455
+ del domain_map[(indexed, instanced, mode, formats)]
456
+ continue
457
+ draw_list.append((lambda d, m: lambda: d.draw(m))(domain, mode)) # noqa: PLC3002
458
+
459
+ # Sort and visit child groups of this group
460
+ children = self.group_children.get(group)
461
+ if children:
462
+ children.sort()
463
+ for child in list(children):
464
+ if child.visible:
465
+ draw_list.extend(visit(child))
466
+
467
+ if children or domain_map:
468
+ return [group.set_state, *draw_list, group.unset_state]
469
+
470
+ # Remove unused group from batch
471
+ del self.group_map[group]
472
+ group._assigned_batches.remove(self) # noqa: SLF001
473
+ if group.parent:
474
+ self.group_children[group.parent].remove(group)
475
+ try:
476
+ del self.group_children[group]
477
+ except KeyError:
478
+ pass
479
+ try:
480
+ self.top_groups.remove(group)
481
+ except ValueError:
482
+ pass
483
+
484
+ return []
485
+
486
+ self._draw_list = []
487
+
488
+ self.top_groups.sort()
489
+ for top_group in list(self.top_groups):
490
+ if top_group.visible:
491
+ self._draw_list.extend(visit(top_group))
492
+
493
+ self._draw_list_dirty = False
494
+
495
+ if _debug_graphics_batch:
496
+ self._dump_draw_list()
497
+
498
+ def _dump_draw_list(self) -> None:
499
+ def dump(group: Group, indent: str = '') -> None:
500
+ print(indent, 'Begin group', group)
501
+ domain_map = self.group_map[group]
502
+ for domain in domain_map.values():
503
+ print(indent, ' ', domain)
504
+ for start, size in zip(*domain.allocator.get_allocated_regions()):
505
+ print(indent, ' ', 'Region %d size %d:' % (start, size))
506
+ for key, buffer in domain.attrib_name_buffers.items():
507
+ print(indent, ' ', end=' ')
508
+ try:
509
+ region = buffer.get_region(start, size)
510
+ print(key, region.array[:])
511
+ except: # noqa: E722
512
+ print(key, '(unmappable)')
513
+ for child in self.group_children.get(group, ()):
514
+ dump(child, indent + ' ')
515
+ print(indent, 'End group', group)
516
+
517
+ print(f'Draw list for {self!r}:')
518
+ for group in self.top_groups:
519
+ dump(group)
520
+
521
+ def draw(self) -> None:
522
+ """Draw the batch."""
523
+ if self._draw_list_dirty:
524
+ self._update_draw_list()
525
+
526
+ for func in self._draw_list:
527
+ func()
528
+
529
+ def draw_subset(self, vertex_lists: Sequence[VertexList | IndexedVertexList]) -> None:
530
+ """Draw only some vertex lists in the batch.
531
+
532
+ The use of this method is highly discouraged, as it is quite
533
+ inefficient. Usually an application can be redesigned so that batches
534
+ can always be drawn in their entirety, using `draw`.
535
+
536
+ The given vertex lists must belong to this batch; behaviour is
537
+ undefined if this condition is not met.
538
+
539
+ Args:
540
+ vertex_lists:
541
+ Vertex lists to draw.
542
+
543
+ """
544
+
545
+ # Horrendously inefficient.
546
+ def visit(group: Group) -> None:
547
+ group.set_state()
548
+
549
+ # Draw domains using this group
550
+ domain_map = self.group_map[group]
551
+ for (_, _, mode, _, _), domain in domain_map.items():
552
+ for alist in vertex_lists:
553
+ if alist.domain is domain:
554
+ alist.draw(mode)
555
+
556
+ # Sort and visit child groups of this group
557
+ children = self.group_children.get(group)
558
+ if children:
559
+ children.sort()
560
+ for child in children:
561
+ if child.visible:
562
+ visit(child)
563
+
564
+ group.unset_state()
565
+
566
+ self.top_groups.sort()
567
+ for top_group in self.top_groups:
568
+ if top_group.visible:
569
+ visit(top_group)
570
+
571
+
572
+ class ShaderGroup(Group):
573
+ """A group that enables and binds a ShaderProgram."""
574
+
575
+ def __init__(self, program: ShaderProgramBase, order: int = 0, parent: Group | None = None) -> None:
576
+ super().__init__(order, parent)
577
+ self.set_shader_program(program)
578
+
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import pyglet
6
+
7
+ if TYPE_CHECKING:
8
+ from pyglet.image import ImageData
9
+
10
+
11
+ def get_screenshot() -> ImageData:
12
+ """Read the pixel data from the default color buffer into ImageData.
13
+
14
+ This provides a simplistic screenshot of the default frame buffer.
15
+
16
+ This may be inaccurate if you utilize multiple frame buffers in your program.
17
+
18
+ .. versionadded:: 3.0
19
+ """
20
+ raise NotImplementedError
21
+
22
+
23
+ if pyglet.options.backend in ("opengl", "gles3", "gl2", "gles2"):
24
+ from pyglet.graphics.api.gl.framebuffer import Framebuffer, Renderbuffer
25
+ elif pyglet.options.backend == "webgl":
26
+ from pyglet.graphics.api.webgl.framebuffer import Framebuffer, Renderbuffer # noqa: F401