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,1346 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import warnings
5
+ import weakref
6
+ from collections import defaultdict
7
+ from ctypes import (
8
+ POINTER,
9
+ Array,
10
+ Structure,
11
+ c_buffer,
12
+ c_byte,
13
+ c_double,
14
+ c_float,
15
+ c_int,
16
+ c_short,
17
+ c_ubyte,
18
+ c_uint,
19
+ c_ushort,
20
+ cast,
21
+ pointer,
22
+ sizeof,
23
+ )
24
+ from typing import TYPE_CHECKING, Any, Callable, Sequence, Type, Union
25
+
26
+ import pyglet
27
+
28
+ # from pyglet.graphics.api.webgl.buffer import BufferObject
29
+ from pyglet.graphics import GeometryMode
30
+ from pyglet.graphics.api.webgl import gl
31
+ from pyglet.graphics.api.webgl.buffer import BufferObject
32
+ from pyglet.graphics.api.webgl.gl import (
33
+ GL_FALSE,
34
+ GL_INFO_LOG_LENGTH,
35
+ GL_LINK_STATUS,
36
+ GL_TRUE,
37
+ GL_UNIFORM_BUFFER,
38
+ )
39
+ from pyglet.graphics.shader import (
40
+ Attribute,
41
+ ShaderBase,
42
+ ShaderException,
43
+ ShaderProgramBase,
44
+ ShaderSource,
45
+ ShaderType,
46
+ UniformBufferObjectBase,
47
+ AttributeView,
48
+ GraphicsAttribute,
49
+ )
50
+
51
+ try:
52
+ import js
53
+ except ImportError:
54
+ pass
55
+
56
+
57
+ class GLException(Exception):
58
+ pass
59
+
60
+
61
+ if TYPE_CHECKING:
62
+ from _weakref import CallableProxyType
63
+
64
+ from pyglet.customtypes import CTypesPointer, DataTypes
65
+ from pyglet.graphics import Batch, Group
66
+ from pyglet.graphics.api.webgl.context import OpenGLSurfaceContext
67
+ from pyglet.graphics.api.webgl.webgl_js import WebGL2RenderingContext, WebGLProgram, WebGLRenderingContext
68
+ from pyglet.graphics.shader import CTypesDataType
69
+ from pyglet.graphics.vertexdomain import IndexedVertexList, VertexList
70
+
71
+ _debug_api_shaders = pyglet.options.debug_api_shaders
72
+
73
+
74
+ GLDataType = Union[Type[gl.GLint], Type[gl.GLfloat], Type[gl.GLboolean], int]
75
+ GLFunc = Callable
76
+
77
+ _c_types: dict[int, CTypesDataType] = {
78
+ gl.GL_BYTE: c_byte,
79
+ gl.GL_UNSIGNED_BYTE: c_ubyte,
80
+ gl.GL_SHORT: c_short,
81
+ gl.GL_UNSIGNED_SHORT: c_ushort,
82
+ gl.GL_INT: c_int,
83
+ gl.GL_UNSIGNED_INT: c_uint,
84
+ gl.GL_FLOAT: c_float,
85
+ gl.GL_DOUBLE: c_double,
86
+ }
87
+
88
+ _data_type_to_gl_type: dict[DataTypes, int] = {
89
+ 'b': gl.GL_BYTE, # signed byte
90
+ 'B': gl.GL_UNSIGNED_BYTE, # unsigned byte
91
+ 'h': gl.GL_SHORT, # signed short
92
+ 'H': gl.GL_UNSIGNED_SHORT, # unsigned short
93
+ 'i': gl.GL_INT, # signed int
94
+ 'I': gl.GL_UNSIGNED_INT, # unsigned int
95
+ 'f': gl.GL_FLOAT, # float
96
+ 'q': gl.GL_INT, # signed long long (Requires GL_INT64_NV if available. Just use int.)
97
+ 'Q': gl.GL_UNSIGNED_INT, # unsigned long long (Requires GL_UNSIGNED_INT64_NV if available. Just use uint.)
98
+ }
99
+
100
+ _shader_types: dict[ShaderType, int] = {
101
+ 'compute': gl.GL_COMPUTE_SHADER,
102
+ 'fragment': gl.GL_FRAGMENT_SHADER,
103
+ 'geometry': gl.GL_GEOMETRY_SHADER,
104
+ 'tesscontrol': gl.GL_TESS_CONTROL_SHADER,
105
+ 'tessevaluation': gl.GL_TESS_EVALUATION_SHADER,
106
+ 'vertex': gl.GL_VERTEX_SHADER,
107
+ }
108
+
109
+ _attribute_types: dict[int, tuple[int, DataTypes]] = {
110
+ gl.GL_BOOL: (1, '?'),
111
+ gl.GL_BOOL_VEC2: (2, '?'),
112
+ gl.GL_BOOL_VEC3: (3, '?'),
113
+ gl.GL_BOOL_VEC4: (4, '?'),
114
+ gl.GL_INT: (1, 'i'),
115
+ gl.GL_INT_VEC2: (2, 'i'),
116
+ gl.GL_INT_VEC3: (3, 'i'),
117
+ gl.GL_INT_VEC4: (4, 'i'),
118
+ gl.GL_UNSIGNED_INT: (1, 'I'),
119
+ gl.GL_UNSIGNED_INT_VEC2: (2, 'I'),
120
+ gl.GL_UNSIGNED_INT_VEC3: (3, 'I'),
121
+ gl.GL_UNSIGNED_INT_VEC4: (4, 'I'),
122
+ gl.GL_FLOAT: (1, 'f'),
123
+ gl.GL_FLOAT_VEC2: (2, 'f'),
124
+ gl.GL_FLOAT_VEC3: (3, 'f'),
125
+ gl.GL_FLOAT_VEC4: (4, 'f'),
126
+ gl.GL_DOUBLE: (1, 'd'),
127
+ gl.GL_DOUBLE_VEC2: (2, 'd'),
128
+ gl.GL_DOUBLE_VEC3: (3, 'd'),
129
+ gl.GL_DOUBLE_VEC4: (4, 'd'),
130
+ }
131
+
132
+
133
+ # Accessor classes:
134
+
135
+ class GLAttribute(GraphicsAttribute):
136
+ """Abstract accessor for an attribute in a mapped buffer."""
137
+ gl_type: int
138
+ """OpenGL type enumerant; for example, ``GL_FLOAT``"""
139
+
140
+ def __init__(self, attribute: Attribute, view: AttributeView) -> None:
141
+ """Create the attribute accessor.
142
+
143
+ Args:
144
+ attribute: The base shader Attribute object.
145
+ view: The view intended for the buffer of this Attribute.
146
+ """
147
+ self._gl = pyglet.graphics.api.core.current_context.gl
148
+ super().__init__(attribute, view)
149
+ data_type = self.attribute.fmt.data_type
150
+ self.gl_type = _data_type_to_gl_type[data_type]
151
+
152
+ # If the data type is not normalized and is not a float, consider it an int pointer.
153
+ self._is_int = data_type != "f" and self.attribute.fmt.normalized is False
154
+
155
+ def enable(self) -> None:
156
+ """Enable the attribute."""
157
+ self._gl.enableVertexAttribArray(self.attribute.location)
158
+
159
+ def disable(self) -> None:
160
+ self._gl.disableVertexAttribArray(self.attribute.location)
161
+
162
+ def set_pointer(self) -> None:
163
+ """Setup this attribute to point to the currently bound buffer at the given offset."""
164
+ if self._is_int:
165
+ self._gl.vertexAttribIPointer(
166
+ self.attribute.location,
167
+ self.attribute.fmt.components,
168
+ self.gl_type,
169
+ self.view.stride,
170
+ self.view.offset,
171
+ )
172
+ else:
173
+ self._gl.vertexAttribPointer(
174
+ self.attribute.location,
175
+ self.attribute.fmt.components,
176
+ self.gl_type,
177
+ self.attribute.fmt.normalized,
178
+ self.view.stride,
179
+ self.view.offset,
180
+ )
181
+
182
+ def set_divisor(self) -> None:
183
+ self._gl.vertexAttribDivisor(self.attribute.location, self.attribute.fmt.divisor)
184
+
185
+
186
+
187
+ class _UniformArray:
188
+ """Wrapper of the GLSL array data inside a Uniform.
189
+
190
+ Allows access to get and set items for a more Pythonic implementation.
191
+ Types with a length longer than 1 will be returned as tuples as an inner list would not support individual value
192
+ reassignment. Array data must either be set in full, or by indexing.
193
+ """
194
+
195
+ _gl: WebGLRenderingContext
196
+ _uniform: _Uniform
197
+ _gl_type: GLDataType
198
+ _gl_getter: GLFunc
199
+ _gl_setter: GLFunc
200
+ _is_matrix: bool
201
+ _c_array: Array[GLDataType]
202
+ _ptr: CTypesPointer[GLDataType]
203
+ _idx_to_loc: dict[int, int]
204
+
205
+ __slots__ = (
206
+ '_c_array',
207
+ '_gl',
208
+ '_gl_getter',
209
+ '_gl_setter',
210
+ '_gl_type',
211
+ '_idx_to_loc',
212
+ '_is_matrix',
213
+ '_ptr',
214
+ '_uniform',
215
+ )
216
+
217
+ def __init__(
218
+ self, uniform: _Uniform, gl_getter: GLFunc, gl_setter: GLFunc, gl_type: GLDataType, is_matrix: bool,
219
+ ) -> None:
220
+ self._uniform = uniform
221
+ self._gl_type = gl_type
222
+ self._gl_getter = gl_getter
223
+ self._gl_setter = gl_setter
224
+ self._is_matrix = is_matrix
225
+ self._idx_to_loc = {} # Array index to uniform location mapping.
226
+ self._gl = pyglet.graphics.api.core.current_context.gl
227
+
228
+ if self._uniform.length > 1:
229
+ self._c_array = (gl_type * self._uniform.length * self._uniform.size)()
230
+ else:
231
+ self._c_array = (gl_type * self._uniform.size)()
232
+
233
+ self._ptr = cast(self._c_array, POINTER(gl_type))
234
+
235
+ def _get_location_for_index(self, index: int) -> int:
236
+ """Get the location for the array name.
237
+
238
+ It is not guaranteed that the location ID's of the uniform in the shader program will be a contiguous offset.
239
+
240
+ On MacOS, the location ID of index 0 may be 1, and then index 2 might be 5. Whereas on Windows it may be a 1:1
241
+ offset from 0 to index. Here, we store the location ID's of each index to ensure we are setting data on the
242
+ right location.
243
+ """
244
+ loc = self._gl.getUniformLocation(self._uniform.program, f"{self._uniform.name}[{index}]")
245
+ return loc
246
+
247
+ def _get_array_loc(self, index: int) -> int:
248
+ try:
249
+ return self._idx_to_loc[index]
250
+ except KeyError:
251
+ loc = self._idx_to_loc[index] = self._get_location_for_index(index)
252
+
253
+ if loc == -1:
254
+ msg = f"{self._uniform.name}[{index}] not found.\nThis may have been optimized out by the OpenGL driver if unused."
255
+ raise ShaderException(msg)
256
+
257
+ return loc
258
+
259
+ def __len__(self) -> int:
260
+ return self._uniform.size
261
+
262
+ def __delitem__(self, key: int) -> None:
263
+ msg = "Deleting items is not support for UniformArrays."
264
+ raise ShaderException(msg)
265
+
266
+ def __getitem__(self, key: slice | int) -> list[tuple] | tuple:
267
+ # Return as a tuple. Returning as a list may imply setting inner list elements will update values.
268
+ if isinstance(key, slice):
269
+ sliced_data = self._c_array[key]
270
+ if self._uniform.length > 1:
271
+ return [tuple(data) for data in sliced_data]
272
+
273
+ return tuple([data for data in sliced_data]) # noqa: C416
274
+
275
+ try:
276
+ value = self._c_array[key]
277
+ return tuple(value) if self._uniform.length > 1 else value
278
+ except IndexError:
279
+ msg = f"{self._uniform.name}[{key}] not found. This may have been optimized out by the OpenGL driver if unused."
280
+ raise ShaderException(msg)
281
+
282
+ def __setitem__(self, key: slice | int, value: Sequence) -> None:
283
+ if isinstance(key, slice):
284
+ self._c_array[key] = value
285
+ self._update_uniform(self._ptr)
286
+ return
287
+
288
+ self._c_array[key] = value
289
+
290
+ if self._uniform.length > 1:
291
+ assert len(value) == self._uniform.length, (
292
+ f"Setting this key requires {self._uniform.length} values, received {len(value)}."
293
+ )
294
+ data = (self._gl_type * self._uniform.length)(*value)
295
+ else:
296
+ data = self._gl_type(value)
297
+
298
+ self._update_uniform(data, offset=key)
299
+
300
+ def get(self) -> _UniformArray:
301
+ self._gl_getter(self._uniform.program, self._uniform.location, self._ptr)
302
+ return self
303
+
304
+ def set(self, values: Sequence) -> None:
305
+ assert len(self._c_array) == len(values), (
306
+ f"Size of data ({len(values)}) does not match size of the uniform: {len(self._c_array)}."
307
+ )
308
+
309
+ self._c_array[:] = values
310
+ self._update_uniform(self._ptr)
311
+
312
+ def _update_uniform(self, data: Sequence, offset: int = 0) -> None:
313
+ if offset != 0:
314
+ size = 1
315
+ else:
316
+ size = self._uniform.size
317
+
318
+ location = self._get_location_for_index(offset)
319
+
320
+ self._gl.useProgram(self._uniform.program)
321
+ if self._is_matrix:
322
+ self._gl_setter(location, GL_FALSE, data)
323
+ else:
324
+ self._gl_setter(location, data)
325
+
326
+ def __repr__(self) -> str:
327
+ data = [tuple(data) if self._uniform.length > 1 else data for data in self._c_array]
328
+ return f"UniformArray(uniform={self._uniform}, data={data})"
329
+
330
+
331
+ _gl_matrices: tuple[int, ...] = (
332
+ gl.GL_FLOAT_MAT2,
333
+ gl.GL_FLOAT_MAT2x3,
334
+ gl.GL_FLOAT_MAT2x4,
335
+ gl.GL_FLOAT_MAT3,
336
+ gl.GL_FLOAT_MAT3x2,
337
+ gl.GL_FLOAT_MAT3x4,
338
+ gl.GL_FLOAT_MAT4,
339
+ gl.GL_FLOAT_MAT4x2,
340
+ gl.GL_FLOAT_MAT4x3,
341
+ )
342
+
343
+
344
+ class _Uniform:
345
+ _ctx: OpenGLSurfaceContext | None
346
+ type: int
347
+ size: int
348
+ location: int
349
+ program: int | WebGLProgram
350
+ name: str
351
+ length: int
352
+ get: Callable[[], Array[GLDataType] | GLDataType]
353
+ set: Callable[[float], None] | Callable[[Sequence], None]
354
+
355
+ __slots__ = '_ctx', 'count', 'get', 'length', 'location', 'name', 'program', 'set', 'size', 'type'
356
+
357
+ def __init__(self, program: int | WebGLProgram, name: str, uniform_type: int, size: int, location: int) -> None:
358
+ self._ctx = pyglet.graphics.api.core.current_context
359
+ self.name = name
360
+ self.type = uniform_type
361
+ self.size = size
362
+ self.location = location
363
+ self.program = program
364
+
365
+ gl_type, gl_setter, length = self._ctx._uniform_setters[uniform_type]
366
+ gl_getter = self._ctx.gl.getUniform
367
+
368
+ # Argument length of data
369
+ self.length = length
370
+
371
+ is_matrix = uniform_type in _gl_matrices
372
+
373
+ # If it's an array, use the wrapper object.
374
+ if size > 1:
375
+ array = _UniformArray(self, gl_getter, gl_setter, gl_type, is_matrix)
376
+ self.get = array.get
377
+ self.set = array.set
378
+ else:
379
+ c_array: Array[GLDataType] = (gl_type * length)()
380
+ ptr = cast(c_array, POINTER(gl_type))
381
+
382
+ self.get = self._create_getter_func(program, location, gl_getter, length)
383
+ self.set = self._create_setter_func(
384
+ self._ctx.gl, program, location, gl_setter, c_array, length, ptr, is_matrix,
385
+ )
386
+
387
+ @staticmethod
388
+ def _create_getter_func(
389
+ program_id: int | WebGLRenderingContext,
390
+ location: int,
391
+ gl_getter: GLFunc,
392
+ length: int,
393
+ ) -> Callable[[], Array[GLDataType] | GLDataType]:
394
+ """Factory function for creating simplified Uniform getters."""
395
+ def getter_func() -> GLDataType:
396
+ return gl_getter(program_id, location)
397
+
398
+ return getter_func
399
+
400
+ @staticmethod
401
+ def _create_setter_func(
402
+ gl_ctx: WebGLRenderingContext,
403
+ program_id: int | WebGLProgram,
404
+ location: int,
405
+ gl_setter: GLFunc,
406
+ c_array: Array[GLDataType],
407
+ length: int,
408
+ ptr: CTypesPointer[GLDataType],
409
+ is_matrix: bool,
410
+ ) -> Callable[[float], None]:
411
+ """Factory function for creating simplified Uniform setters."""
412
+ if is_matrix:
413
+
414
+ def setter_func(value: float) -> None:
415
+ gl_ctx.useProgram(program_id)
416
+ gl_setter(location, GL_FALSE, value)
417
+ elif length == 1:
418
+
419
+ def setter_func(value: float) -> None:
420
+ gl_ctx.useProgram(program_id)
421
+ gl_setter(location, value)
422
+ elif length > 1:
423
+
424
+ def setter_func(values: float) -> None:
425
+ gl_ctx.useProgram(program_id)
426
+ gl_setter(location, values)
427
+ else:
428
+ msg = "Uniform type not yet supported."
429
+ raise ShaderException(msg)
430
+
431
+ return setter_func
432
+
433
+ def __repr__(self) -> str:
434
+ return f"Uniform(type={self.type}, size={self.size}, location={self.location}, length={self.length})"
435
+
436
+
437
+ def get_maximum_binding_count() -> int:
438
+ """The maximum binding value that can be used for this hardware."""
439
+ return gl.glGetParameter(gl.GL_MAX_UNIFORM_BUFFER_BINDINGS)
440
+
441
+
442
+ class _UBOBindingManager:
443
+ """Manages the global Uniform Block binding assignments in the OpenGL context."""
444
+
445
+ _in_use: set[int]
446
+ _pool: list[int]
447
+ _max_binding_count: int
448
+ _ubo_names: dict[str, int]
449
+ _ubo_programs: defaultdict[Any, weakref.WeakSet[ShaderProgram]]
450
+
451
+ def __init__(self) -> None:
452
+ self._ubo_programs = defaultdict(weakref.WeakSet)
453
+ # Reserve 'WindowBlock' for 0.
454
+ self._ubo_names = {'WindowBlock': 0}
455
+ self._max_binding_count = 5 # get_maximum_binding_count()
456
+ self._pool = list(range(1, self._max_binding_count))
457
+ self._in_use = {0}
458
+
459
+ @property
460
+ def max_value(self) -> int:
461
+ return self._max_binding_count
462
+
463
+ def get_name(self, binding: int) -> str | None:
464
+ """Return the uniform name associated with the binding number."""
465
+ for name, current_binding in self._ubo_names.items():
466
+ if binding == current_binding:
467
+ return name
468
+ return None
469
+
470
+ def binding_exists(self, binding: int) -> bool:
471
+ """Check if a binding index value is in use."""
472
+ return binding in self._in_use
473
+
474
+ def add_explicit_binding(self, shader_program: ShaderProgram, ub_name: str, binding: int) -> None:
475
+ """Used when a uniform block has set its own binding point."""
476
+ self._ubo_programs[ub_name].add(shader_program)
477
+ self._ubo_names[ub_name] = binding
478
+ if binding in self._pool:
479
+ self._pool.remove(binding)
480
+ self._in_use.add(binding)
481
+
482
+ def get_binding(self, shader_program: ShaderProgram, ub_name: str) -> int:
483
+ """Retrieve a global Uniform Block Binding ID value."""
484
+ self._ubo_programs[ub_name].add(shader_program)
485
+
486
+ if ub_name in self._ubo_names:
487
+ return self._ubo_names[ub_name]
488
+
489
+ self._check_freed_bindings()
490
+
491
+ binding = self._get_new_binding()
492
+ self._ubo_names[ub_name] = binding
493
+ return binding
494
+
495
+ def _check_freed_bindings(self) -> None:
496
+ """Find and remove any Uniform Block names that no longer have a shader in use."""
497
+ for ubo_name in list(self._ubo_programs):
498
+ if ubo_name != 'WindowBlock' and not self._ubo_programs[ubo_name]:
499
+ del self._ubo_programs[ubo_name]
500
+ # Return the binding number to the pool.
501
+ self.return_binding(self._ubo_names[ubo_name])
502
+ del self._ubo_names[ubo_name]
503
+
504
+ def _get_new_binding(self) -> int:
505
+ if not self._pool:
506
+ msg = "All Uniform Buffer Bindings are in use."
507
+ raise ValueError(msg)
508
+
509
+ number = self._pool.pop(0)
510
+ self._in_use.add(number)
511
+ return number
512
+
513
+ def return_binding(self, index: int) -> None:
514
+ if index in self._in_use:
515
+ self._pool.append(index)
516
+ self._in_use.remove(index)
517
+ else:
518
+ msg = f"Uniform binding point: {index} is not in use."
519
+ raise ValueError(msg)
520
+
521
+
522
+ # Regular expression to detect array indices like [0], [1], etc.
523
+ array_regex = re.compile(r"(\w+)\[(\d+)\]")
524
+
525
+
526
+ def get_std140_size_and_alignment(gl_type):
527
+ """Returns (size, alignment) for a given WebGL uniform type."""
528
+ std140_layout = {
529
+ c_float: (4, 4),
530
+ c_int: (4, 4),
531
+ c_uint: (4, 4),
532
+ gl.FLOAT_VEC2: (8, 8),
533
+ gl.FLOAT_VEC3: (12, 16), # Vec3 is padded to 16
534
+ gl.FLOAT_VEC4: (16, 16),
535
+ gl.INT_VEC2: (8, 8),
536
+ gl.INT_VEC3: (12, 16),
537
+ gl.INT_VEC4: (16, 16),
538
+ gl.UNSIGNED_INT_VEC2: (8, 8),
539
+ gl.UNSIGNED_INT_VEC3: (12, 16),
540
+ gl.UNSIGNED_INT_VEC4: (16, 16),
541
+ gl.FLOAT_MAT2: (32, 16), # 2 * vec4
542
+ gl.FLOAT_MAT3: (48, 16), # 3 * vec4
543
+ gl.FLOAT_MAT4: (64, 16), # 4 * vec4
544
+ }
545
+ return std140_layout.get(gl_type, (0, 0))
546
+
547
+
548
+ def compute_std140_offsets(uniforms):
549
+ """Manually computes std140-compliant offsets for uniforms in a UBO."""
550
+ offset = 0
551
+ offsets = {}
552
+
553
+ for name, c_type, length, size in uniforms.values():
554
+ type_size = sizeof(c_type * length)
555
+ align_size = min(16, type_size) # Align to std140
556
+
557
+ # Align offset
558
+ if offset % align_size != 0:
559
+ offset += align_size - (offset % align_size)
560
+
561
+ # Store computed offset
562
+ offsets[name] = offset
563
+ offset += type_size * size # Account for array sizes
564
+
565
+ return offsets
566
+
567
+
568
+ class UniformBlock: # noqa: D101
569
+ ctx: OpenGLSurfaceContext | None
570
+ program: CallableProxyType[Callable[..., Any] | Any] | Any
571
+ name: str
572
+ index: int
573
+ size: int
574
+ binding: int
575
+ uniforms: dict[int, tuple[str, GLDataType, int, int]]
576
+ view_cls: type[Structure] | None
577
+ __slots__ = 'binding', 'ctx', 'index', 'name', 'program', 'size', 'uniform_count', 'uniforms', 'view_cls'
578
+
579
+ def __init__(
580
+ self,
581
+ program: ShaderProgram,
582
+ name: str,
583
+ index: int,
584
+ size: int,
585
+ binding: int,
586
+ uniforms: dict[int, tuple[str, GLDataType, int, int]],
587
+ uniform_count: int,
588
+ ) -> None:
589
+ """Initialize a uniform block for a ShaderProgram."""
590
+ self.ctx = pyglet.graphics.api.core.current_context
591
+ assert self.ctx
592
+ self.program = weakref.proxy(program)
593
+ self.name = name
594
+ self.index = index
595
+ self.size = size
596
+ self.binding = binding
597
+ self.uniforms = uniforms
598
+ self.uniform_count = uniform_count
599
+ self.view_cls = self._create_structure()
600
+
601
+ def _create_structure(self):
602
+ return self._introspect_uniforms()
603
+
604
+ def bind(self, ubo: UniformBufferObject) -> None:
605
+ """Bind buffer to the binding point."""
606
+ self.ctx.gl.bindBufferBase(GL_UNIFORM_BUFFER, self.binding, ubo.buffer.id)
607
+
608
+ def create_ubo(self) -> UniformBufferObject:
609
+ """Create a new UniformBufferObject from this uniform block."""
610
+ return UniformBufferObject(self.ctx, self.view_cls, self.size, self.binding)
611
+
612
+ def set_binding(self, binding: int) -> None:
613
+ """Rebind the Uniform Block to a new binding index number.
614
+
615
+ This only affects the program this Uniform Block is derived from.
616
+
617
+ Binding value of 0 is reserved for the Pyglet's internal uniform block named ``WindowBlock``.
618
+
619
+ .. warning:: By setting a binding manually, the user is expected to manage all Uniform Block bindings
620
+ for all shader programs manually. Since the internal global ID's will be unaware of changes set
621
+ by this function, collisions may occur if you use a lower number.
622
+
623
+ .. note:: You must call ``create_ubo`` to get another Uniform Buffer Object after calling this,
624
+ as the previous buffers are still bound to the old binding point.
625
+ """
626
+ assert binding != 0, "Binding 0 is reserved for the internal Pyglet 'WindowBlock'."
627
+ assert pyglet.graphics.api.core.current_context is not None, "No context available."
628
+ manager: _UBOBindingManager = pyglet.graphics.api.core.current_context.ubo_manager
629
+ if binding >= manager.max_value:
630
+ msg = f"Binding value exceeds maximum allowed by hardware: {manager.max_value}"
631
+ raise ShaderException(msg)
632
+ existing_name = manager.get_name(binding)
633
+ if existing_name and existing_name != self.name:
634
+ msg = f"Binding: {binding} was in use by {existing_name}, and has been overridden."
635
+ warnings.warn(msg)
636
+
637
+ self.binding = binding
638
+ self.ctx.gl.uniformBlockBinding(self.program.id, self.index, self.binding)
639
+
640
+ def _introspect_uniforms(self) -> type[Structure]:
641
+ """Introspect the block's structure and return a ctypes struct for manipulating the uniform block's members."""
642
+ p_id = self.program.id
643
+ index = self.index
644
+
645
+ active_count = self.uniform_count
646
+
647
+ # Query the uniform index order and each uniform's offset:
648
+ # indices = gl.glGetActiveUniformBlockiv(p_id, index, gl.GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, indices_ptr)
649
+ # offsets = gl.glGetActiveUniformsiv(p_id, active_count, indices, gl.GL_UNIFORM_OFFSET, offsets_ptr)
650
+
651
+ # Offsets may be returned in non-ascending order, sort them with the corresponding index:
652
+ # _oi = sorted(zip(offsets, indices), key=lambda x: x[0])
653
+ # offsets = [x[0] for x in _oi] + [self.size]
654
+ # indices = (gl.GLuint * active_count)(*(x[1] for x in _oi))
655
+
656
+ # # Query other uniform information:
657
+ # gl_types = (gl.GLint * active_count)()
658
+ # mat_stride = (gl.GLint * active_count)()
659
+ # gl_types_ptr = cast(addressof(gl_types), POINTER(gl.GLint))
660
+ # stride_ptr = cast(addressof(mat_stride), POINTER(gl.GLint))
661
+ # gl.glGetActiveUniformsiv(p_id, active_count, indices, gl.GL_UNIFORM_TYPE, gl_types_ptr)
662
+ # gl.glGetActiveUniformsiv(p_id, active_count, indices, gl.GL_UNIFORM_MATRIX_STRIDE, stride_ptr)
663
+
664
+ array_sizes = {}
665
+ dynamic_structs = {}
666
+ p_count = 0
667
+
668
+ rep_func = lambda s: str(dict(s._fields_))
669
+
670
+ def build_ctypes_struct(name, uniform_offsets):
671
+ """Dynamically constructs a ctypes structure for UBO representation."""
672
+ fields = []
673
+ prev_offset = 0
674
+
675
+ for uniform_name, offset in sorted(uniform_offsets.items(), key=lambda x: x[1]):
676
+ parts = uniform_name.split(".")
677
+ for uniform in self.uniforms.values():
678
+ if uniform[0] == uniform_name:
679
+ _, base_ctype, length, size = uniform
680
+ break
681
+
682
+ if size > 1:
683
+ c_type *= (base_ctype * length) * size # Make it an array
684
+ else:
685
+ c_type = base_ctype * length
686
+
687
+ # Handle padding for std140 alignment
688
+ padding = offset - prev_offset
689
+ if padding > 0:
690
+ fields.append((f'_padding{prev_offset}', c_byte * padding))
691
+
692
+ fields.append((uniform_name, c_type))
693
+ prev_offset = offset + sizeof(c_type)
694
+
695
+ return type(name, (Structure,), {"_fields_": fields, "__repr__": rep_func})
696
+
697
+ uniform_offsets = compute_std140_offsets(self.uniforms)
698
+
699
+ # Custom ctypes Structure for Uniform access:
700
+ return build_ctypes_struct('View', uniform_offsets)
701
+
702
+ def _actual_binding_point(self) -> int:
703
+ """Queries OpenGL to find what the bind point currently is."""
704
+ return self.ctx.gl.getActiveUniformBlockParameter(self.program.id, self.index, gl.GL_UNIFORM_BLOCK_BINDING)
705
+
706
+ def __repr__(self) -> str:
707
+ return (
708
+ f"{self.__class__.__name__}(program={self.program.id}, location={self.index}, size={self.size}, "
709
+ f"binding={self.binding})"
710
+ )
711
+
712
+
713
+ class UniformBufferObject(UniformBufferObjectBase):
714
+ buffer: BufferObject
715
+ view: Structure
716
+ _view_ptr: CTypesPointer[Structure]
717
+ binding: int
718
+
719
+ __slots__ = '_view_ptr', 'binding', 'buffer', 'view'
720
+
721
+ def __init__(self, context: OpenGLSurfaceContext, view_class: type[Structure], buffer_size: int, binding: int) -> None:
722
+ """Initialize the Uniform Buffer Object with the specified Structure."""
723
+ self.buffer = BufferObject(context, buffer_size, target=GL_UNIFORM_BUFFER)
724
+ self.view = view_class()
725
+ self._view_ptr = pointer(self.view)
726
+ self.binding = binding
727
+
728
+ @property
729
+ def id(self) -> int:
730
+ """The buffer ID associated with this UBO."""
731
+ return self.buffer.id
732
+
733
+ def read(self) -> Array:
734
+ """Read the byte contents of the buffer."""
735
+ return self.buffer.get_data()
736
+
737
+ def __enter__(self) -> Structure:
738
+ return self.view
739
+
740
+ def __exit__(self, _exc_type, _exc_val, _exc_tb) -> None: # noqa: ANN001
741
+ self.buffer.set_data(self._view_ptr)
742
+
743
+ def __repr__(self) -> str:
744
+ return f"{self.__class__.__name__}(id={self.buffer.id}, binding={self.binding})"
745
+
746
+
747
+ # Utility functions:
748
+
749
+
750
+ def _get_number(gl_ctx: WebGLRenderingContext, program_id: WebGLProgram, variable_type: int) -> int:
751
+ """Get the number of active variables of the passed GL type."""
752
+ return gl_ctx.getProgramParameter(program_id, variable_type)
753
+
754
+
755
+ def _introspect_attributes(program_id: WebGLProgram) -> dict[str, Attribute]:
756
+ """Introspect a Program's Attributes, and return a dict of accessors."""
757
+ _gl = pyglet.graphics.api.core.current_context.gl
758
+ attributes = {}
759
+
760
+ if not _gl.getProgramParameter(program_id, gl.GL_LINK_STATUS):
761
+ print("Shader program not linked!")
762
+ return {}
763
+
764
+ count = _get_number(_gl, program_id, gl.GL_ACTIVE_ATTRIBUTES)
765
+ for index in range(count):
766
+ idx_attribs = _gl.getActiveAttrib(program_id, index)
767
+ if not idx_attribs:
768
+ raise ShaderException(f"Attribute Index {index} not found in Program.")
769
+
770
+ a_name, a_type, a_size = idx_attribs.name, idx_attribs.type, idx_attribs.size
771
+ loc = _gl.getAttribLocation(program_id, a_name)
772
+ if loc == -1: # not a user defined attribute
773
+ continue
774
+ count, fmt = _attribute_types[a_type]
775
+ attributes[a_name] = Attribute(a_name, loc, count, fmt)
776
+
777
+ if _debug_api_shaders:
778
+ for attribute in attributes.values():
779
+ print(f" Found attribute: {attribute}")
780
+
781
+ return attributes
782
+
783
+
784
+ def _link_program(gl: WebGLRenderingContext, *shaders: Shader) -> int:
785
+ """Link one or more Shaders into a ShaderProgram.
786
+
787
+ Returns:
788
+ The ID assigned to the linked ShaderProgram.
789
+ """
790
+ program_id = gl.createProgram()
791
+ for shader in shaders:
792
+ gl.attachShader(program_id, shader.id)
793
+ gl.linkProgram(program_id)
794
+
795
+ # Check the link status of program
796
+ status = gl.getProgramParameter(program_id, GL_LINK_STATUS)
797
+ if not status:
798
+ length = gl.getProgramParameter(program_id, GL_INFO_LOG_LENGTH)
799
+ log = c_buffer(length.value)
800
+ gl.getProgramInfoLog(program_id, len(log), None, log)
801
+ msg = f"Error linking shader program:\n{log.value.decode()}"
802
+ raise ShaderException(msg)
803
+
804
+ # Shader objects no longer needed
805
+ for shader in shaders:
806
+ gl.detachShader(program_id, shader.id)
807
+
808
+ return program_id
809
+
810
+
811
+ def _query_uniform(gl_ctx, program_id: WebGLProgram, index: int) -> tuple[str, int, int]:
812
+ """Query the name, type, and size of a Uniform by index."""
813
+ try:
814
+ info = gl_ctx.getActiveUniform(program_id, index)
815
+ return info.name, info.type, info.size
816
+
817
+ except GLException as exc:
818
+ raise ShaderException from exc
819
+
820
+
821
+ def _introspect_uniforms(gl_ctx: WebGLRenderingContext, program_id: WebGLProgram) -> dict[str, _Uniform]:
822
+ """Introspect a Program's uniforms, and return a dict of accessors."""
823
+ uniforms = {}
824
+
825
+ for index in range(_get_number(gl_ctx, program_id, gl.GL_ACTIVE_UNIFORMS)):
826
+ u_name, u_type, u_size = _query_uniform(gl_ctx, program_id, index)
827
+
828
+ # Multidimensional arrays cannot be fully inspected via OpenGL calls and compile errors with 3.3.
829
+ array_count = u_name.count("[0]")
830
+ if array_count > 1 and u_name.count("[0][0]") != 0:
831
+ msg = "Multidimensional arrays are not currently supported."
832
+ raise ShaderException(msg)
833
+
834
+ loc = gl_ctx.getUniformLocation(program_id, u_name)
835
+ if not loc or loc == -1: # Skip uniforms that may be inside a Uniform Block
836
+ continue
837
+
838
+ # Strip [0] from array name for a more user-friendly name.
839
+ if array_count != 0:
840
+ u_name = u_name.strip('[0]')
841
+
842
+ assert u_name not in uniforms, f"{u_name} exists twice in the shader. Possible name clash with an array."
843
+ uniforms[u_name] = _Uniform(program_id, u_name, u_type, u_size, loc)
844
+
845
+ if _debug_api_shaders:
846
+ for uniform in uniforms.values():
847
+ print(f" Found uniform: {uniform}")
848
+
849
+ return uniforms
850
+
851
+
852
+ def _introspect_uniform_blocks(
853
+ ctx: OpenGLSurfaceContext, program: ShaderProgram | ComputeShaderProgram,
854
+ ) -> dict[str, UniformBlock]:
855
+ uniform_blocks = {}
856
+ gl_ctx: WebGL2RenderingContext = ctx.gl
857
+ program_id: WebGLProgram = program.id
858
+
859
+ for index in range(_get_number(gl_ctx, program_id, gl.GL_ACTIVE_UNIFORM_BLOCKS)):
860
+ name = gl_ctx.getActiveUniformBlockName(program_id, index)
861
+ if not name:
862
+ msg = f"Unable to query UniformBlock name at index: {index}"
863
+ raise ShaderException(msg)
864
+
865
+ # num_active = gl.glGetActiveUniformBlockParameter(program_id, index, gl.GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS)
866
+ block_data_size = gl_ctx.getActiveUniformBlockParameter(program_id, index, gl.GL_UNIFORM_BLOCK_DATA_SIZE)
867
+ binding = gl_ctx.getActiveUniformBlockParameter(program_id, index, gl.GL_UNIFORM_BLOCK_BINDING)
868
+ indices = gl_ctx.getActiveUniformBlockParameter(program_id, index, gl.GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES)
869
+
870
+ uniforms: dict[int, tuple[str, GLDataType, int, int]] = {}
871
+
872
+ if not hasattr(pyglet.graphics.api.core.current_context, "ubo_manager"):
873
+ pyglet.graphics.api.core.current_context.ubo_manager = _UBOBindingManager()
874
+
875
+ manager = pyglet.graphics.api.core.current_context.ubo_manager
876
+
877
+ for block_uniform_index in indices:
878
+ uniform_name, u_type, u_size = _query_uniform(gl_ctx, program_id, block_uniform_index)
879
+
880
+ # Remove block name.
881
+ if uniform_name.startswith(f"{name}."):
882
+ uniform_name = uniform_name[len(name) + 1 :] # Strip 'block_name.' part
883
+
884
+ if uniform_name.count("[0][0]") > 0:
885
+ msg = "Multidimensional arrays are not currently supported."
886
+ raise ShaderException(msg)
887
+
888
+ gl_type, _, length = ctx._uniform_setters[u_type]
889
+ uniforms[block_uniform_index] = (uniform_name, gl_type, length, u_size)
890
+
891
+ binding_index = binding
892
+ if pyglet.options.shader_bind_management:
893
+ # If no binding is specified in GLSL, then assign it internally.
894
+ if binding == 0:
895
+ binding_index = manager.get_binding(program, name)
896
+
897
+ # This might cause an error if index > GL_MAX_UNIFORM_BUFFER_BINDINGS, but surely no
898
+ # one would be crazy enough to use more than 36 uniform blocks, right?
899
+ gl_ctx.uniformBlockBinding(program_id, index, binding_index)
900
+ else:
901
+ # If a binding was manually set in GLSL, just check if the values collide to warn the user.
902
+ _block_name = manager.get_name(binding)
903
+ if _block_name and _block_name != name:
904
+ msg = (
905
+ f"{program} explicitly set '{name}' to {binding} in the shader. '{_block_name}' has "
906
+ f"been overridden."
907
+ )
908
+ warnings.warn(msg)
909
+ manager.add_explicit_binding(program, name, binding)
910
+
911
+ uniform_blocks[name] = UniformBlock(
912
+ program, name, index, block_data_size, binding_index, uniforms, len(indices),
913
+ )
914
+
915
+ if _debug_api_shaders:
916
+ for block in uniform_blocks.values():
917
+ print(f" Found uniform block: {block}")
918
+
919
+ return uniform_blocks
920
+
921
+
922
+ # Shader & program classes:
923
+
924
+
925
+ class GLShaderSource(ShaderSource):
926
+ """GLSL source container for making source parsing simpler.
927
+
928
+ We support locating out attributes and applying #defines values.
929
+
930
+ .. note:: We do assume the source is neat enough to be parsed this way and doesn't contain several statements in
931
+ one line.
932
+ """
933
+
934
+ _type: gl.GLenum
935
+ _lines: list[str]
936
+
937
+ def __init__(self, source: str, source_type: gl.GLenum | int) -> None:
938
+ """Create a shader source wrapper."""
939
+ self._lines = source.strip().splitlines()
940
+ self._type = source_type
941
+
942
+ if not self._lines:
943
+ msg = "Shader source is empty"
944
+ raise ShaderException(msg)
945
+
946
+ self._version = self._find_glsl_version()
947
+
948
+ self._lines[0] = "#version 300 es"
949
+ self._lines.insert(1, "precision mediump float;")
950
+
951
+ if self._type == gl.GL_GEOMETRY_SHADER:
952
+ self._lines.insert(1, "#extension GL_EXT_geometry_shader : require")
953
+
954
+ if self._type == gl.GL_COMPUTE_SHADER:
955
+ self._lines.insert(1, "precision mediump image2D;")
956
+
957
+ self._version = self._find_glsl_version()
958
+
959
+ def validate(self) -> str:
960
+ """Return the validated shader source."""
961
+ return "\n".join(self._lines)
962
+
963
+ def _find_glsl_version(self) -> int:
964
+ if self._lines[0].strip().startswith("#version"):
965
+ try:
966
+ return int(self._lines[0].split()[1])
967
+ except (ValueError, IndexError):
968
+ pass
969
+
970
+ source = "\n".join(f"{str(i + 1).zfill(3)}: {line} " for i, line in enumerate(self._lines))
971
+
972
+ msg = (
973
+ "Cannot find #version flag in shader source. "
974
+ "A #version statement is required on the first line.\n"
975
+ "------------------------------------\n"
976
+ f"{source}"
977
+ )
978
+ raise ShaderException(msg)
979
+
980
+
981
+ class Shader(ShaderBase):
982
+ """OpenGL shader.
983
+
984
+ Shader objects are compiled on instantiation.
985
+ You can reuse a Shader object in multiple ShaderPrograms.
986
+ """
987
+
988
+ _context: OpenGLSurfaceContext | None
989
+ _id: int | None
990
+ type: ShaderType
991
+
992
+ def __init__(self, source_string: str, shader_type: ShaderType) -> None:
993
+ """Initialize a shader type.
994
+
995
+ Args:
996
+ source_string:
997
+ A string containing the source of your shader program.
998
+
999
+ shader_type:
1000
+ A string containing the type of shader program:
1001
+
1002
+ * ``'vertex'``
1003
+ * ``'fragment'``
1004
+ * ``'geometry'``
1005
+ * ``'compute'``
1006
+ * ``'tesscontrol'``
1007
+ * ``'tessevaluation'``
1008
+ """
1009
+ super().__init__(source_string, shader_type)
1010
+ self._context = pyglet.graphics.api.core.current_context
1011
+ self._gl = self._context.gl
1012
+ self._id = None
1013
+ self.type = shader_type
1014
+
1015
+ shader_gl_type = _shader_types[shader_type]
1016
+ source_string = self.get_string_class()(source_string, shader_gl_type).validate()
1017
+ # shader_source_utf8 = source_string.encode("utf8")
1018
+ # source_buffer_pointer = cast(c_char_p(shader_source_utf8), POINTER(c_char))
1019
+ # source_length = c_int(len(shader_source_utf8))
1020
+
1021
+ shader_id = self._gl.createShader(shader_gl_type)
1022
+ self._id = shader_id
1023
+ self._gl.shaderSource(shader_id, source_string)
1024
+ self._gl.compileShader(shader_id)
1025
+
1026
+ status = self._gl.getShaderParameter(shader_id, gl.GL_COMPILE_STATUS)
1027
+
1028
+ if status != GL_TRUE:
1029
+ source = source_string
1030
+ source_lines = "{0}".format(
1031
+ "\n".join(f"{str(i + 1).zfill(3)}: {line} " for i, line in enumerate(source.split("\n"))),
1032
+ )
1033
+
1034
+ msg = (
1035
+ f"\n------------------------------------------------------------\n"
1036
+ f"{source_lines}"
1037
+ f"\n------------------------------------------------------------\n"
1038
+ f"Shader compilation failed. Please review the error on the specified line.\n"
1039
+ f"{self._get_shader_log(shader_id)}"
1040
+ )
1041
+
1042
+ raise ShaderException(msg)
1043
+
1044
+ if _debug_api_shaders:
1045
+ print(self._get_shader_log(shader_id))
1046
+
1047
+ @staticmethod
1048
+ def get_string_class() -> type[GLShaderSource]:
1049
+ return GLShaderSource
1050
+
1051
+ @classmethod
1052
+ def supported_shaders(cls) -> tuple[ShaderType, ...]:
1053
+ return 'vertex', 'fragment', 'compute', 'geometry', 'tesscontrol', 'tessevaluation'
1054
+
1055
+ @property
1056
+ def id(self) -> int | WebGLProgram:
1057
+ return self._id
1058
+
1059
+ def _get_shader_log(self, shader_id: int) -> str:
1060
+ info_log = self._gl.getShaderInfoLog(shader_id)
1061
+ if info_log:
1062
+ return f"OpenGL returned the following message when compiling the '{self.type}' shader: \n{info_log}"
1063
+
1064
+ return f"{self.type.capitalize()} Shader '{shader_id}' compiled successfully."
1065
+
1066
+ def delete(self) -> None:
1067
+ """Deletes the shader.
1068
+
1069
+ This cannot be undone.
1070
+ """
1071
+ self._gl.deleteShader(self._id)
1072
+ self._id = None
1073
+
1074
+ def __del__(self) -> None:
1075
+ if self._id is not None:
1076
+ try:
1077
+ self._context.delete_shader(self._id)
1078
+ if _debug_api_shaders:
1079
+ print(f"Destroyed {self.type} Shader '{self._id}'")
1080
+ self._id = None
1081
+ except (AttributeError, ImportError):
1082
+ pass # Interpreter is shutting down
1083
+
1084
+ def __repr__(self) -> str:
1085
+ return f"{self.__class__.__name__}(id={self.id}, type={self.type})"
1086
+
1087
+
1088
+ class ShaderProgram(ShaderProgramBase):
1089
+ """OpenGL shader program."""
1090
+
1091
+ _id: int | WebGLProgram | None
1092
+ _context: OpenGLSurfaceContext | None
1093
+ _uniforms: dict[str, _Uniform]
1094
+ _uniform_blocks: dict[str, UniformBlock]
1095
+
1096
+ __slots__ = '_attributes', '_context', '_id', '_uniform_blocks', '_uniforms'
1097
+
1098
+ def __init__(self, *shaders: Shader) -> None:
1099
+ """Initialize the ShaderProgram using at least two Shader instances."""
1100
+ super().__init__(*shaders)
1101
+ self._context = pyglet.graphics.api.core.current_context
1102
+ self._gl = self._context.gl
1103
+ self._id = _link_program(self._gl, *shaders)
1104
+
1105
+ if _debug_api_shaders:
1106
+ """Query a ShaderProgram link logs."""
1107
+ result = self._gl.getProgramInfoLog(self._id)
1108
+ if result:
1109
+ print(f"OpenGL returned the following message when linking the program: \n{result}")
1110
+ else:
1111
+ js.console.log(f"Program '{self._id}' linked successfully.")
1112
+
1113
+ # Query if Direct State Access is available:
1114
+ self.use()
1115
+
1116
+ self._attributes = _introspect_attributes(self._id)
1117
+ self._uniforms = _introspect_uniforms(self._gl, self._id)
1118
+ self._uniform_blocks = self._get_uniform_blocks()
1119
+ self.stop()
1120
+
1121
+ def _get_uniform_blocks(self) -> dict[str, UniformBlock]:
1122
+ """Return Uniform Block information."""
1123
+ return _introspect_uniform_blocks(self._context, self)
1124
+
1125
+ @property
1126
+ def id(self) -> int:
1127
+ return self._id
1128
+
1129
+ @property
1130
+ def uniforms(self) -> dict[str, Any]:
1131
+ """Uniform metadata dictionary.
1132
+
1133
+ This property returns a dictionary containing metadata of all
1134
+ Uniforms that were introspected in this ShaderProgram. Modifying
1135
+ this dictionary has no effect. To set or get a uniform, the uniform
1136
+ name is used as a key on the ShaderProgram instance. For example::
1137
+
1138
+ my_shader_program[uniform_name] = 123
1139
+ value = my_shader_program[uniform_name]
1140
+
1141
+ """
1142
+ return {n: {'location': u.location, 'length': u.length, 'size': u.size} for n, u in self._uniforms.items()}
1143
+
1144
+ def use(self) -> None:
1145
+ self._gl.useProgram(self._id)
1146
+
1147
+ def stop(self) -> None:
1148
+ self._gl.useProgram(None)
1149
+
1150
+ __enter__ = use
1151
+ bind = use
1152
+ unbind = stop
1153
+
1154
+ def __exit__(self, *_) -> None: # noqa: ANN002
1155
+ self._gl.useProgram(None)
1156
+
1157
+ def delete(self) -> None:
1158
+ self._gl.deleteProgram(self._id)
1159
+ self._id = None
1160
+
1161
+ def __del__(self) -> None:
1162
+ if self._id is not None:
1163
+ try:
1164
+ self._context.delete_shader_program(self._id)
1165
+ self._id = None
1166
+ except (AttributeError, ImportError):
1167
+ pass # Interpreter is shutting down
1168
+
1169
+ def __setitem__(self, key: str, value: Any) -> None:
1170
+ try:
1171
+ uniform = self._uniforms[key]
1172
+ except KeyError as err:
1173
+ msg = (
1174
+ f"A Uniform with the name `{key}` was not found.\n"
1175
+ f"The spelling may be incorrect or, if not in use, it "
1176
+ f"may have been optimized out by the OpenGL driver."
1177
+ )
1178
+ if _debug_api_shaders:
1179
+ warnings.warn(msg)
1180
+ return
1181
+ raise ShaderException(msg) from err
1182
+ try:
1183
+ uniform.set(value)
1184
+ except GLException as err:
1185
+ raise ShaderException from err
1186
+
1187
+ def __getitem__(self, item: str) -> Any:
1188
+ try:
1189
+ uniform = self._uniforms[item]
1190
+ except KeyError as err:
1191
+ msg = (
1192
+ f"A Uniform with the name `{item}` was not found.\n"
1193
+ f"The spelling may be incorrect or, if not in use, it "
1194
+ f"may have been optimized out by the OpenGL driver."
1195
+ )
1196
+ if _debug_api_shaders:
1197
+ warnings.warn(msg)
1198
+ return None
1199
+
1200
+ raise ShaderException from err
1201
+ try:
1202
+ return uniform.get()
1203
+ except GLException as err:
1204
+ raise ShaderException from err
1205
+
1206
+
1207
+ def _vertex_list_create(self, count: int, mode: GeometryMode, indices: Sequence[int] | None = None,
1208
+ instances: dict[str, int] | None = None, batch: Batch = None, group: Group = None,
1209
+ **data: Any) -> VertexList | InstanceVertexList | IndexedVertexList | InstanceIndexedVertexList:
1210
+ assert isinstance(mode, GeometryMode), f"Mode {mode} is not geometry mode."
1211
+ attributes = {}
1212
+ initial_arrays = []
1213
+
1214
+ indexed = indices is not None
1215
+
1216
+ # Probably just remove all of this?
1217
+ for name, fmt in data.items():
1218
+ current_attrib = self._attributes[name]
1219
+ try:
1220
+ if isinstance(fmt, tuple):
1221
+ fmt, array = fmt # noqa: PLW2901
1222
+ initial_arrays.append((name, array))
1223
+ normalize = len(fmt) == 2
1224
+ current_attrib.set_data_type(fmt[0], normalize)
1225
+
1226
+ attributes[name] = current_attrib#, 'format': fmt, 'instance': name in instances if instances else False}
1227
+ except KeyError:
1228
+ if _debug_api_shaders:
1229
+ msg = (f"The attribute `{name}` was not found in the Shader Program.\n"
1230
+ f"Please check the spelling, or it may have been optimized out by the OpenGL driver.\n"
1231
+ f"Valid names: {list(attributes)}")
1232
+ warnings.warn(msg)
1233
+ continue
1234
+
1235
+ if instances:
1236
+ for name, divisor in instances.items():
1237
+ attributes[name].set_divisor(divisor)
1238
+
1239
+ if _debug_api_shaders:
1240
+ if missing_data := [key for key in attributes if key not in data]:
1241
+ msg = (
1242
+ f"No data was supplied for the following found attributes: `{missing_data}`.\n"
1243
+ )
1244
+ warnings.warn(msg)
1245
+
1246
+ batch = batch or pyglet.graphics.get_default_batch()
1247
+ group = group or pyglet.graphics.ShaderGroup(program=self)
1248
+ domain = batch.get_domain(indexed, bool(instances), mode, group, attributes)
1249
+
1250
+ # Create vertex list and initialize
1251
+ vlist = domain.create(group, count, indices)
1252
+
1253
+ for name, array in initial_arrays:
1254
+ vlist.set_attribute_data(name, array)
1255
+
1256
+ return vlist
1257
+
1258
+
1259
+ def vertex_list(
1260
+ self, count: int, mode: GeometryMode, batch: Batch = None, group: Group = None, **data: Any,
1261
+ ) -> VertexList:
1262
+ """Create a VertexList.
1263
+
1264
+ Args:
1265
+ count:
1266
+ The number of vertices in the list.
1267
+ mode:
1268
+ OpenGL drawing mode enumeration; for example, one of
1269
+ ``GL_POINTS``, ``GL_LINES``, ``GL_TRIANGLES``, etc.
1270
+ This determines how the list is drawn in the given batch.
1271
+ batch:
1272
+ Batch to add the VertexList to, or ``None`` if a Batch will not be used.
1273
+ Using a Batch is strongly recommended.
1274
+ group:
1275
+ Group to add the VertexList to, or ``None`` if no group is required.
1276
+ data:
1277
+ Attribute formats and initial data for the vertex list.
1278
+
1279
+ """
1280
+ return self._vertex_list_create(count, mode, None, None, batch=batch, group=group, **data)
1281
+
1282
+ def vertex_list_instanced(
1283
+ self,
1284
+ count: int,
1285
+ mode: GeometryMode,
1286
+ instance_attributes: Sequence[str],
1287
+ batch: Batch | None = None,
1288
+ group: Group | None = None,
1289
+ **data: Any,
1290
+ ) -> VertexList:
1291
+ assert len(instance_attributes) > 0, "You must provide at least one attribute name to be instanced."
1292
+ return self._vertex_list_create(count, mode, None, instance_attributes, batch=batch, group=group, **data)
1293
+
1294
+ def vertex_list_indexed(
1295
+ self,
1296
+ count: int,
1297
+ mode: GeometryMode,
1298
+ indices: Sequence[int],
1299
+ batch: Batch | None = None,
1300
+ group: Group | None = None,
1301
+ **data: Any,
1302
+ ) -> IndexedVertexList:
1303
+ """Create a IndexedVertexList.
1304
+
1305
+ Args:
1306
+ count:
1307
+ The number of vertices in the list.
1308
+ mode:
1309
+ OpenGL drawing mode enumeration; for example, one of
1310
+ ``GL_POINTS``, ``GL_LINES``, ``GL_TRIANGLES``, etc.
1311
+ This determines how the list is drawn in the given batch.
1312
+ indices:
1313
+ Sequence of integers giving indices into the vertex list.
1314
+ batch:
1315
+ Batch to add the VertexList to, or ``None`` if a Batch will not be used.
1316
+ Using a Batch is strongly recommended.
1317
+ group:
1318
+ Group to add the VertexList to, or ``None`` if no group is required.
1319
+ data:
1320
+ Attribute formats and initial data for the vertex list.
1321
+ """
1322
+ return self._vertex_list_create(count, mode, indices, None, batch=batch, group=group, **data)
1323
+
1324
+ def vertex_list_instanced_indexed(
1325
+ self,
1326
+ count: int,
1327
+ mode: GeometryMode,
1328
+ indices: Sequence[int],
1329
+ instance_attributes: Sequence[str],
1330
+ batch: Batch | None = None,
1331
+ group: Group | None = None,
1332
+ **data: Any,
1333
+ ) -> IndexedVertexList:
1334
+ assert len(instance_attributes) > 0, "You must provide at least one attribute name to be instanced."
1335
+ return self._vertex_list_create(count, mode, indices, instance_attributes, batch=batch, group=group, **data)
1336
+
1337
+ def __repr__(self) -> str:
1338
+ return f"{self.__class__.__name__}(id={self.id})"
1339
+
1340
+
1341
+ class ComputeShaderProgram:
1342
+ """OpenGL Compute Shader Program."""
1343
+
1344
+ def __init__(self, source: str) -> None:
1345
+ """Create an OpenGL ComputeShaderProgram from source."""
1346
+ raise ShaderException("Compute Shaders are not supported in WebGL.")