pyglet 2.1.12__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 +4 -17
  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 +27 -5
  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 +147 -177
  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.12.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.12.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.12.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.12.dist-info/licenses → pyglet-3.0.dev1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,590 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Callable, Sequence
4
+
5
+ import pyglet
6
+ from pyglet.graphics.api.webgl import vertexdomain
7
+ from pyglet.graphics.api.webgl.enums import geometry_map
8
+ from pyglet.graphics.draw import BatchBase, _DomainKey, Group
9
+
10
+ _debug_graphics_batch = pyglet.options.debug_graphics_batch
11
+
12
+ if TYPE_CHECKING:
13
+ from pyglet.graphics import GeometryMode
14
+ from pyglet.graphics.api.webgl.vertexdomain import IndexedVertexList, VertexList
15
+ from pyglet.graphics.api.webgl.shader import ShaderProgram
16
+ from pyglet.graphics.api.webgl.context import OpenGLSurfaceContext
17
+ from pyglet.graphics.state import State
18
+
19
+
20
+ # Default Shader source:
21
+
22
+ _vertex_source: str = """#version 330 core
23
+ in vec3 position;
24
+ in vec4 colors;
25
+
26
+ out vec4 vertex_colors;
27
+
28
+ uniform WindowBlock
29
+ {
30
+ mat4 projection;
31
+ mat4 view;
32
+ } window;
33
+
34
+ void main()
35
+ {
36
+ gl_Position = window.projection * window.view * vec4(position, 1.0);
37
+
38
+ vertex_colors = colors;
39
+ }
40
+ """
41
+
42
+ _fragment_source: str = """#version 330 core
43
+ in vec4 vertex_colors;
44
+ out vec4 final_colors;
45
+
46
+ void main()
47
+ {
48
+ final_colors = vertex_colors;
49
+ }
50
+ """
51
+
52
+
53
+ def get_default_batch() -> Batch:
54
+ """Batch used globally for objects that have no Batch specified."""
55
+ return pyglet.graphics.api.core.get_default_batch()
56
+ # try:
57
+ # return pyglet.graphics.api.core.current_context.pyglet_graphics_default_batch
58
+ # except AttributeError:
59
+ # pyglet.graphics.api.core.current_context.pyglet_graphics_default_batch = Batch()
60
+ # return pyglet.graphics.api.core.current_context.pyglet_graphics_default_batch
61
+
62
+
63
+ def get_default_shader() -> ShaderProgram:
64
+ """A default basic shader for default batches."""
65
+ return pyglet.graphics.api.core.get_cached_shader(
66
+ "default_graphics",
67
+ (_vertex_source, 'vertex'),
68
+ (_fragment_source, 'fragment'),
69
+ )
70
+
71
+
72
+ _domain_class_map: dict[tuple[bool, bool], type[vertexdomain.VertexDomain]] = {
73
+ # Indexed, Instanced : Domain
74
+ (False, False): vertexdomain.VertexDomain,
75
+ (True, False): vertexdomain.IndexedVertexDomain,
76
+ (False, True): vertexdomain.InstancedVertexDomain,
77
+ (True, True): vertexdomain.InstancedIndexedVertexDomain,
78
+ }
79
+
80
+
81
+ class Batch(BatchBase):
82
+ """Manage a collection of drawables for batched rendering.
83
+
84
+ Many drawable pyglet objects accept an optional `Batch` argument in their
85
+ constructors. By giving a `Batch` to multiple objects, you can tell pyglet
86
+ that you expect to draw all of these objects at once, so it can optimise its
87
+ use of OpenGL. Hence, drawing a `Batch` is often much faster than drawing
88
+ each contained drawable separately.
89
+
90
+ The following example creates a batch, adds two sprites to the batch, and
91
+ then draws the entire batch::
92
+
93
+ batch = pyglet.graphics.Batch()
94
+ car = pyglet.sprite.Sprite(car_image, batch=batch)
95
+ boat = pyglet.sprite.Sprite(boat_image, batch=batch)
96
+
97
+ def on_draw():
98
+ batch.draw()
99
+
100
+ While any drawables can be added to a `Batch`, only those with the same
101
+ draw mode, shader program, and group can be optimised together.
102
+
103
+ Internally, a `Batch` manages a set of VertexDomains along with
104
+ information about how the domains are to be drawn. To implement batching on
105
+ a custom drawable, get your vertex domains from the given batch instead of
106
+ setting them up yourself.
107
+ """
108
+ _draw_list: list[Callable]
109
+ top_groups: list[Group]
110
+ group_children: dict[Group, list[Group]]
111
+ group_map: dict[Group, dict[_DomainKey, vertexdomain.VertexDomain]]
112
+
113
+ def __init__(self, context: OpenGLSurfaceContext | None = None, initial_count: int = 32) -> None:
114
+ """Initialize the batch for use.
115
+
116
+ Args:
117
+ context:
118
+ The OpenGL Surface context this batch will be a part of.
119
+ initial_count:
120
+ The initial element count of the buffers created by the domains in the batch.
121
+
122
+ Increase this value if you plan to load large amounts of vertices, as it will reduce the need for
123
+ resizing buffers, which can be slow.
124
+ """
125
+ # Mapping to find domain.
126
+ # group -> (attributes, mode, indexed) -> domain
127
+ super().__init__(initial_count)
128
+ self._context = context or pyglet.graphics.api.core.current_context
129
+ assert self._context is not None, "A context needs to exist before you create this."
130
+
131
+ def invalidate(self) -> None:
132
+ """Force the batch to update the draw list.
133
+
134
+ This method can be used to force the batch to re-compute the draw list
135
+ when the ordering of groups has changed.
136
+
137
+ .. versionadded:: 1.2
138
+ """
139
+ self._draw_list_dirty = True
140
+
141
+ def update_shader(self, vertex_list: VertexList | IndexedVertexList, mode: GeometryMode, group: Group,
142
+ program: ShaderProgram) -> bool:
143
+ """Migrate a vertex list to another domain that has the specified shader attributes.
144
+
145
+ The results are undefined if `mode` is not correct or if `vertex_list`
146
+ does not belong to this batch (they are not checked and will not
147
+ necessarily throw an exception immediately).
148
+
149
+ Args:
150
+ vertex_list:
151
+ A vertex list currently belonging to this batch.
152
+ mode:
153
+ The current GL drawing mode of the vertex list.
154
+ group:
155
+ The new group to migrate to.
156
+ program:
157
+ The new shader program to migrate to.
158
+
159
+ Returns:
160
+ False if the domain's no longer match. The caller should handle this scenario.
161
+ """
162
+ # No new attributes.
163
+ attributes = program.attributes.copy()
164
+
165
+ # Formats may differ (normalization) than what is declared in the shader.
166
+ # Make those adjustments and attempt to get a domain.
167
+ for a_name in attributes:
168
+ if (a_name in vertex_list.initial_attribs and
169
+ vertex_list.initial_attribs[a_name]['format'] != attributes[a_name]['format']):
170
+ attributes[a_name]['format'] = vertex_list.initial_attribs[a_name]['format']
171
+
172
+ domain = self.get_domain(vertex_list.indexed, vertex_list.instanced, mode, group, attributes)
173
+
174
+ # TODO: Allow migration if we can restore original vertices somehow. Much faster.
175
+ # If the domain's don't match, we need to re-create the vertex list. Tell caller no match.
176
+ if domain != vertex_list.domain:
177
+ return False
178
+
179
+ return True
180
+
181
+ def migrate(self, vertex_list: VertexList | IndexedVertexList, mode: GeometryMode, group: Group,
182
+ batch: Batch) -> None:
183
+ """Migrate a vertex list to another batch and/or group.
184
+
185
+ `vertex_list` and `mode` together identify the vertex list to migrate.
186
+ `group` and `batch` are new owners of the vertex list after migration.
187
+
188
+ The results are undefined if `mode` is not correct or if `vertex_list`
189
+ does not belong to this batch (they are not checked and will not
190
+ necessarily throw an exception immediately).
191
+
192
+ ``batch`` can remain unchanged if only a group change is desired.
193
+
194
+ Args:
195
+ vertex_list:
196
+ A vertex list currently belonging to this batch.
197
+ mode:
198
+ The current GL drawing mode of the vertex list.
199
+ group:
200
+ The new group to migrate to.
201
+ batch:
202
+ The batch to migrate to (or the current batch).
203
+
204
+ """
205
+ attributes = vertex_list.domain.attribute_meta
206
+ domain = batch.get_domain(vertex_list.indexed, vertex_list.instanced, mode, group, attributes)
207
+
208
+ if domain != vertex_list.domain:
209
+ vertex_list.migrate(domain, group)
210
+ else:
211
+ # If the same domain, no need to move vertices, just update the group.
212
+ vertex_list.update_group(group)
213
+
214
+ # Updating group can potentially change draw order though.
215
+ self._draw_list_dirty = True
216
+
217
+ def get_domain(self, indexed: bool, instanced: bool, mode: GeometryMode, group: Group,
218
+ attributes: dict[str, Any]) -> vertexdomain.VertexDomain | vertexdomain.IndexedVertexDomain | vertexdomain.InstancedVertexDomain | vertexdomain.InstancedIndexedVertexDomain:
219
+ """Get, or create, the vertex domain corresponding to the given arguments.
220
+
221
+ mode is the render mode such as GL_LINES or GL_TRIANGLES
222
+ """
223
+ # Group map just used for group lookup now, not domains.
224
+ if group not in self.group_map:
225
+ self._add_group(group)
226
+
227
+ # If instanced, ensure a separate domain, as multiple instance sources can match the key.
228
+ # Find domain given formats, indices and mode
229
+ key = _DomainKey(indexed, instanced, mode, str(attributes))
230
+
231
+ try:
232
+ domain = self._domain_registry[key]
233
+ except KeyError:
234
+ # Create domain
235
+ domain = _domain_class_map[(indexed, instanced)](self._context, self.initial_count, attributes)
236
+ self._domain_registry[key] = domain
237
+ self._draw_list_dirty = True
238
+ return domain
239
+
240
+ def _add_group(self, group: Group) -> None:
241
+ self.group_map[group] = {}
242
+ if group.parent is None:
243
+ self.top_groups.append(group)
244
+ else:
245
+ if group.parent not in self.group_map:
246
+ self._add_group(group.parent)
247
+ if group.parent not in self.group_children:
248
+ self.group_children[group.parent] = []
249
+ self.group_children[group.parent].append(group)
250
+
251
+ group._assigned_batches.add(self) # noqa: SLF001
252
+ self._draw_list_dirty = True
253
+
254
+ def _cleanup_groups(self, group: Group) -> None:
255
+ """Safely remove empty groups from all tracking structures."""
256
+ del self.group_map[group]
257
+ group._assigned_batches.remove(self) # noqa: SLF001
258
+ if group.parent:
259
+ self.group_children[group.parent].remove(group)
260
+ try: # noqa: SIM105
261
+ del self.group_children[group]
262
+ except KeyError:
263
+ pass
264
+ try: # noqa: SIM105
265
+ self.top_groups.remove(group)
266
+ except ValueError:
267
+ pass
268
+ # Remove bucket from all domains.
269
+ for domain in self._domain_registry.values():
270
+ if domain.has_bucket(group):
271
+ del domain._vertex_buckets[group] # noqa: SLF001
272
+
273
+ def _create_draw_list(self) -> list[Callable]:
274
+ """Rebuild draw list by walking the group tree and minimizing state transitions."""
275
+ def visit(group: Group) -> list[Callable]:
276
+ draw_list = []
277
+ if not group.visible:
278
+ return draw_list
279
+
280
+ # Determine if any vertices are drawable.
281
+ is_drawable = False
282
+ for dkey, domain in self._domain_registry.items():
283
+ if domain.is_empty or not domain.get_drawable_bucket(group):
284
+ self._empty_domains.add(dkey)
285
+ continue
286
+ is_drawable = True
287
+ break
288
+
289
+ # State has nothing drawable and no children to affect.
290
+ # Even if it has states, nothing to apply it to. Clean them and return early.
291
+ if not is_drawable and not self.group_children.get(group):
292
+ self._cleanup_groups(group)
293
+ return []
294
+
295
+ # If drawable, then find the domains to draw.
296
+ if is_drawable:
297
+ for dkey, domain in self._domain_registry.items():
298
+ bucket = domain.get_drawable_bucket(group)
299
+ if not bucket:
300
+ continue
301
+
302
+ draw_list.append((domain, dkey.mode, group))
303
+
304
+ # Recurse into visible children
305
+ children = self.group_children.get(group, [])
306
+ for child in sorted(children):
307
+ if child.visible:
308
+ draw_list.extend(visit(child))
309
+
310
+ if children or is_drawable:
311
+ return [(None, 'set', group), *draw_list, (None, 'unset', group)]
312
+
313
+ return draw_list
314
+
315
+ _draw_list = []
316
+
317
+ self.top_groups.sort()
318
+
319
+ for top in list(self.top_groups):
320
+ if top.visible:
321
+ _draw_list.extend(visit(top))
322
+
323
+ return _draw_list
324
+
325
+ @staticmethod
326
+ def _vao_bind_fn(domain): # noqa: ANN001, ANN205
327
+ def _bind_vao(_ctx) -> None: # noqa: ANN001
328
+ domain.bind_vao()
329
+
330
+ return _bind_vao
331
+
332
+ @staticmethod
333
+ def _draw_bucket_fn(domain, buckets, mode_func): # noqa: ANN001, ANN205
334
+ def _draw(_ctx) -> None: # noqa: ANN001
335
+ domain.draw_buckets(mode_func, buckets)
336
+
337
+ return _draw
338
+
339
+ def _optimize_draw_list(self, draw_list: list[tuple]) -> list[Callable]:
340
+ """Turn a flattened (domain/mode/group) list into optimized callables.
341
+
342
+ States that are equal (by __eq__) are treated as the same logical GPU state.
343
+ Intermediate unset/set pairs for identical states are removed, preserving
344
+ dependency ordering and teardown safety.
345
+ """
346
+ calls: list[Callable] = []
347
+ active_states: dict[type, State] = {} # (type, key) -> instance
348
+
349
+ def _next_same_type_set(idx: int, state_type: type) -> None | State:
350
+ for j in range(idx + 1, len(draw_list)):
351
+ dom2, mode2, group2 = draw_list[j]
352
+ if dom2 is None and mode2 == "set":
353
+ for s2 in group2._expanded_states: # noqa: SLF001
354
+ if type(s2) is state_type:
355
+ return s2 # first same-type set we see in the future
356
+ return None
357
+
358
+ def flush_buckets() -> None:
359
+ nonlocal current_buckets, last_mode, last_domain
360
+ if not current_buckets:
361
+ return
362
+
363
+ calls.append(self._draw_bucket_fn(last_domain, list(current_buckets), geometry_map[last_mode]))
364
+ current_buckets.clear()
365
+
366
+ def _emit_set(_state: State) -> None:
367
+ stype = type(_state)
368
+ current = active_states.get(stype)
369
+
370
+ # state is same
371
+ if current == _state:
372
+ return
373
+
374
+ # state changed
375
+ if current_buckets:
376
+ flush_buckets()
377
+
378
+ # unset previous
379
+ if current and current.unsets_state:
380
+ calls.append(current.unset_state)
381
+
382
+ # set new state
383
+ if _state.sets_state:
384
+ calls.append(_state.set_state)
385
+
386
+ active_states[stype] = _state
387
+
388
+ def _emit_unset(_state: State, idx: int) -> None:
389
+ stype = type(_state)
390
+ current = active_states.get(stype)
391
+ if current is None:
392
+ return
393
+
394
+ next_set = _next_same_type_set(idx, stype)
395
+ if next_set == current:
396
+ return # will remain active
397
+
398
+ # state is about to end
399
+ flush_buckets()
400
+
401
+ if current.unsets_state:
402
+ calls.append(current.unset_state)
403
+ active_states.pop(stype, None)
404
+
405
+ last_domain = None
406
+ last_mode = None
407
+ current_buckets = []
408
+
409
+ for i, (domain, mode, group) in enumerate(draw_list):
410
+ if domain is None:
411
+ # This is a state boundary. See if it *actually* changes state; if so, flush first.
412
+ if mode == "set":
413
+ for s in group._expanded_states:
414
+ _emit_set(s)
415
+
416
+ elif mode == "unset":
417
+ for s in reversed(group._expanded_states):
418
+ _emit_unset(s, i)
419
+
420
+ continue
421
+
422
+ # Drawable
423
+ bucket = domain.get_drawable_bucket(group)
424
+ if not bucket or bucket.is_empty:
425
+ continue
426
+
427
+ if last_domain is None:
428
+ calls.append(self._vao_bind_fn(domain))
429
+ elif domain != last_domain:
430
+ # New VAO: flush previous batch, bind new VAO
431
+ flush_buckets()
432
+ calls.append(self._vao_bind_fn(domain))
433
+ elif mode != last_mode:
434
+ # Same VAO but different primitive mode: flush
435
+ flush_buckets()
436
+
437
+ current_buckets.append(bucket)
438
+ last_domain = domain
439
+ last_mode = mode
440
+
441
+ # Final flush
442
+ flush_buckets()
443
+
444
+ return calls
445
+
446
+ def _dump_draw_list_call_order(self, draw_list: list[Callable], include_dc: bool=True) -> None:
447
+ import inspect
448
+
449
+ print("=== DRAW ORDER ===")
450
+
451
+ def fn_label(_fn: Callable) -> str:
452
+ r = repr(_fn)
453
+ if "unset_state" in r:
454
+ return "UNSET"
455
+ if "set_state" in r:
456
+ return "SET"
457
+ if "_vao_call_fn" in r:
458
+ return "VAO"
459
+ if "make_draw_buckets_fn" in r:
460
+ return "DRAW"
461
+ return "CALL"
462
+
463
+ def fn_name(_fn: Callable) -> str:
464
+ if inspect.ismethod(_fn):
465
+ dc_info = f" ({_fn.__self__})" if include_dc else ""
466
+ return f"{_fn.__self__.__class__.__name__}.{_fn.__name__}{dc_info}"
467
+ if inspect.isfunction(_fn):
468
+ return _fn.__name__
469
+ return _fn.__class__.__name__
470
+
471
+ for i, fn in enumerate(draw_list):
472
+ label = fn_label(fn)
473
+ name = fn_name(fn)
474
+ print(f"{i:03d} ({label}): {name}")
475
+ print("==================")
476
+
477
+ def _set_draw_functions(self, draw_list: list) -> list[Callable]:
478
+ """Takes a draw list and turns them into function calls.
479
+
480
+ Does not optimize any states. All states are called.
481
+ """
482
+ calls: list[Callable] = []
483
+ last_domain = None
484
+
485
+ for i, (domain, mode, group) in enumerate(draw_list):
486
+ if domain is None:
487
+ # This is a state boundary. See if it *actually* changes state; if so, flush first.
488
+ if mode == "set":
489
+ calls.extend([s.set_state for s in group._expanded_states if s.sets_state])
490
+
491
+ elif mode == "unset":
492
+ calls.extend(reversed([s.unset_state for s in group._expanded_states if s.unsets_state]))
493
+
494
+ continue
495
+
496
+ # Drawable
497
+ bucket = domain.get_drawable_bucket(group)
498
+ if not bucket or bucket.is_empty:
499
+ continue
500
+
501
+ if last_domain is None or domain != last_domain:
502
+ calls.append(self._vao_bind_fn(domain))
503
+
504
+ calls.append(self._draw_bucket_fn(domain, [bucket], geometry_map[mode]))
505
+ last_domain = domain
506
+
507
+ return calls
508
+
509
+ def _dump_draw_list(self) -> None:
510
+ def dump(group: Group, indent: str = '') -> None:
511
+ print(indent, 'Begin group', group)
512
+ for domain in self._domain_registry.values():
513
+ if domain.has_bucket(group):
514
+ # Domain header
515
+ domain_info = repr(domain).split('@')[-1].replace('>', '')
516
+ print(f"{indent} > Domain: {domain.__class__.__name__}@{domain_info}")
517
+
518
+ # Regions
519
+ starts, sizes = domain.vertex_buffers.allocator.get_allocated_regions()
520
+ for start, size in zip(starts, sizes):
521
+ print(f"{indent} - Region start={start:<4} size={size:<4}")
522
+ attribs = ', '.join(domain.attrib_name_buffers.keys())
523
+ print(f"{indent} (Attributes: {attribs})")
524
+ for child in self.group_children.get(group, ()):
525
+ dump(child, indent + ' ')
526
+ print(indent, 'End group', group)
527
+
528
+ print(f'Draw list for {self!r}:')
529
+ for group in self.top_groups:
530
+ dump(group)
531
+
532
+ def _update_draw_list(self) -> None:
533
+ if self._draw_list_dirty:
534
+ draw_list = self._create_draw_list()
535
+ if pyglet.options.optimize_states:
536
+ self._draw_list = self._optimize_draw_list(draw_list)
537
+ else:
538
+ self._draw_list = self._set_draw_functions(draw_list)
539
+ self._draw_list_dirty = False
540
+
541
+ def draw(self) -> None:
542
+ """Draw the batch."""
543
+ self._update_draw_list()
544
+
545
+ for func in self._draw_list:
546
+ func(self._context)
547
+
548
+ self.delete_empty_domains()
549
+
550
+ def draw_subset(self, vertex_lists: Sequence[VertexList | IndexedVertexList]) -> None:
551
+ """Draw only some vertex lists in the batch.
552
+
553
+ The use of this method is highly discouraged, as it is quite
554
+ inefficient. Usually an application can be redesigned so that batches
555
+ can always be drawn in their entirety, using `draw`.
556
+
557
+ The given vertex lists must belong to this batch; behaviour is
558
+ undefined if this condition is not met.
559
+
560
+ Args:
561
+ vertex_lists:
562
+ Vertex lists to draw.
563
+
564
+ """
565
+
566
+ # Horrendously inefficient.
567
+ def visit(group: Group) -> None:
568
+ group.set_state_all(self._context)
569
+
570
+ # Draw domains using this group
571
+ domain_map = self.group_map[group]
572
+ for (_, _, mode, _, _), domain in domain_map.items():
573
+ for alist in vertex_lists:
574
+ if alist.domain is domain:
575
+ alist.draw(mode)
576
+
577
+ # Sort and visit child groups of this group
578
+ children = self.group_children.get(group)
579
+ if children:
580
+ children.sort()
581
+ for child in children:
582
+ if child.visible:
583
+ visit(child)
584
+
585
+ group.unset_state_all(self._context)
586
+
587
+ self.top_groups.sort()
588
+ for top_group in self.top_groups:
589
+ if top_group.visible:
590
+ visit(top_group)
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ from pyglet.graphics import GeometryMode
4
+ from pyglet.graphics.api.webgl.gl import (
5
+ GL_LINEAR, GL_NEAREST, GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D, \
6
+ GL_TEXTURE_CUBE_MAP, \
7
+ GL_TEXTURE_1D_ARRAY, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_R, GL_TEXTURE_WRAP_S, \
8
+ GL_TEXTURE_WRAP_T,
9
+ GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_TRIANGLES, GL_TRIANGLE_STRIP,
10
+ GL_TRIANGLE_FAN,
11
+ GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR,
12
+ GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, \
13
+ GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR,
14
+ GL_ONE_MINUS_CONSTANT_COLOR, \
15
+ GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL,
16
+ GL_GEQUAL, GL_ALWAYS)
17
+
18
+ from pyglet.enums import BlendFactor, TextureFilter, TextureType, TextureWrapping, CompareOp
19
+
20
+ geometry_map = {
21
+ GeometryMode.POINTS: GL_POINTS,
22
+ GeometryMode.LINES: GL_LINES,
23
+ GeometryMode.LINE_STRIP: GL_LINE_STRIP,
24
+ GeometryMode.TRIANGLES: GL_TRIANGLES,
25
+ GeometryMode.TRIANGLE_STRIP: GL_TRIANGLE_STRIP,
26
+ GeometryMode.TRIANGLE_FAN: GL_TRIANGLE_FAN,
27
+ }
28
+
29
+ blend_factor_map = {
30
+ BlendFactor.ZERO: GL_ZERO,
31
+ BlendFactor.ONE: GL_ONE,
32
+ BlendFactor.SRC_COLOR: GL_SRC_COLOR,
33
+ BlendFactor.ONE_MINUS_SRC_COLOR: GL_ONE_MINUS_SRC_COLOR,
34
+ BlendFactor.DST_COLOR: GL_DST_COLOR,
35
+ BlendFactor.ONE_MINUS_DST_COLOR: GL_ONE_MINUS_DST_COLOR,
36
+ BlendFactor.SRC_ALPHA: GL_SRC_ALPHA,
37
+ BlendFactor.ONE_MINUS_SRC_ALPHA: GL_ONE_MINUS_SRC_ALPHA,
38
+ BlendFactor.DST_ALPHA: GL_DST_ALPHA,
39
+ BlendFactor.ONE_MINUS_DST_ALPHA: GL_ONE_MINUS_DST_ALPHA,
40
+ BlendFactor.CONSTANT_COLOR: GL_CONSTANT_COLOR,
41
+ BlendFactor.ONE_MINUS_CONSTANT_COLOR: GL_ONE_MINUS_CONSTANT_COLOR,
42
+ BlendFactor.CONSTANT_ALPHA: GL_CONSTANT_ALPHA,
43
+ BlendFactor.ONE_MINUS_CONSTANT_ALPHA: GL_ONE_MINUS_CONSTANT_ALPHA,
44
+ }
45
+
46
+ texture_map = {
47
+ # Filters
48
+ TextureFilter.LINEAR: GL_LINEAR,
49
+ TextureFilter.NEAREST: GL_NEAREST,
50
+
51
+ # Texture Types
52
+ TextureType.TYPE_1D: GL_TEXTURE_1D,
53
+ TextureType.TYPE_2D: GL_TEXTURE_2D,
54
+ TextureType.TYPE_3D: GL_TEXTURE_3D,
55
+ TextureType.TYPE_CUBE_MAP: GL_TEXTURE_CUBE_MAP,
56
+ TextureType.TYPE_1D_ARRAY: GL_TEXTURE_1D_ARRAY,
57
+ TextureType.TYPE_2D_ARRAY: GL_TEXTURE_2D_ARRAY,
58
+ TextureType.TYPE_CUBE_MAP_ARRAY: GL_TEXTURE_CUBE_MAP_ARRAY,
59
+
60
+ # Wrapping
61
+ TextureWrapping.WRAP_R: GL_TEXTURE_WRAP_R,
62
+ TextureWrapping.WRAP_S: GL_TEXTURE_WRAP_S,
63
+ TextureWrapping.WRAP_T: GL_TEXTURE_WRAP_T,
64
+ }
65
+
66
+
67
+ compare_op_map = {
68
+ CompareOp.NEVER: GL_NEVER,
69
+ CompareOp.LESS: GL_LESS,
70
+ CompareOp.EQUAL: GL_EQUAL,
71
+ CompareOp.LESS_OR_EQUAL: GL_LEQUAL,
72
+ CompareOp.GREATER: GL_GREATER,
73
+ CompareOp.NOT_EQUAL: GL_NOTEQUAL,
74
+ CompareOp.GREATER_OR_EQUAL: GL_GEQUAL,
75
+ CompareOp.ALWAYS: GL_ALWAYS,
76
+ }