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