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