pyglet 2.1.13__py3-none-any.whl → 3.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. pyglet/__init__.py +67 -61
  2. pyglet/__init__.pyi +15 -8
  3. pyglet/app/__init__.py +22 -13
  4. pyglet/app/async_app.py +212 -0
  5. pyglet/app/base.py +2 -1
  6. pyglet/app/{xlib.py → linux.py} +3 -3
  7. pyglet/config/__init__.py +101 -0
  8. pyglet/config/gl/__init__.py +30 -0
  9. pyglet/config/gl/egl.py +120 -0
  10. pyglet/config/gl/macos.py +262 -0
  11. pyglet/config/gl/windows.py +267 -0
  12. pyglet/config/gl/x11.py +142 -0
  13. pyglet/customtypes.py +43 -2
  14. pyglet/display/__init__.py +8 -6
  15. pyglet/display/base.py +3 -63
  16. pyglet/display/cocoa.py +12 -17
  17. pyglet/display/emscripten.py +39 -0
  18. pyglet/display/headless.py +23 -30
  19. pyglet/display/wayland.py +157 -0
  20. pyglet/display/win32.py +5 -21
  21. pyglet/display/xlib.py +19 -27
  22. pyglet/display/xlib_vidmoderestore.py +2 -2
  23. pyglet/enums.py +183 -0
  24. pyglet/event.py +0 -1
  25. pyglet/experimental/geoshader_sprite.py +15 -13
  26. pyglet/experimental/hidraw.py +6 -15
  27. pyglet/experimental/multitexture_sprite.py +31 -19
  28. pyglet/experimental/particles.py +13 -35
  29. pyglet/font/__init__.py +251 -85
  30. pyglet/font/base.py +116 -61
  31. pyglet/font/dwrite/__init__.py +349 -204
  32. pyglet/font/dwrite/dwrite_lib.py +27 -5
  33. pyglet/font/fontconfig.py +14 -6
  34. pyglet/font/freetype.py +138 -87
  35. pyglet/font/freetype_lib.py +19 -0
  36. pyglet/font/group.py +179 -0
  37. pyglet/font/harfbuzz/__init__.py +3 -3
  38. pyglet/font/pyodide_js.py +310 -0
  39. pyglet/font/quartz.py +319 -126
  40. pyglet/font/ttf.py +45 -3
  41. pyglet/font/user.py +14 -19
  42. pyglet/font/win32.py +45 -21
  43. pyglet/graphics/__init__.py +8 -787
  44. pyglet/graphics/allocation.py +115 -1
  45. pyglet/graphics/api/__init__.py +77 -0
  46. pyglet/graphics/api/base.py +299 -0
  47. pyglet/graphics/api/gl/__init__.py +58 -0
  48. pyglet/graphics/api/gl/base.py +24 -0
  49. pyglet/graphics/{vertexbuffer.py → api/gl/buffer.py} +104 -159
  50. pyglet/graphics/api/gl/cocoa/context.py +76 -0
  51. pyglet/graphics/api/gl/context.py +391 -0
  52. pyglet/graphics/api/gl/default_shaders.py +0 -0
  53. pyglet/graphics/api/gl/draw.py +627 -0
  54. pyglet/graphics/api/gl/egl/__init__.py +0 -0
  55. pyglet/graphics/api/gl/egl/context.py +92 -0
  56. pyglet/graphics/api/gl/enums.py +76 -0
  57. pyglet/graphics/api/gl/framebuffer.py +315 -0
  58. pyglet/graphics/api/gl/gl.py +5463 -0
  59. pyglet/graphics/api/gl/gl_info.py +188 -0
  60. pyglet/graphics/api/gl/global_opengl.py +226 -0
  61. pyglet/{gl → graphics/api/gl}/lib.py +34 -18
  62. pyglet/graphics/api/gl/shader.py +1476 -0
  63. pyglet/graphics/api/gl/shapes.py +55 -0
  64. pyglet/graphics/api/gl/sprite.py +102 -0
  65. pyglet/graphics/api/gl/state.py +219 -0
  66. pyglet/graphics/api/gl/text.py +190 -0
  67. pyglet/graphics/api/gl/texture.py +1526 -0
  68. pyglet/graphics/{vertexarray.py → api/gl/vertexarray.py} +11 -13
  69. pyglet/graphics/api/gl/vertexdomain.py +751 -0
  70. pyglet/graphics/api/gl/win32/__init__.py +0 -0
  71. pyglet/graphics/api/gl/win32/context.py +108 -0
  72. pyglet/graphics/api/gl/win32/wgl_info.py +24 -0
  73. pyglet/graphics/api/gl/xlib/__init__.py +0 -0
  74. pyglet/graphics/api/gl/xlib/context.py +174 -0
  75. pyglet/{gl → graphics/api/gl/xlib}/glx_info.py +26 -31
  76. pyglet/graphics/api/gl1/__init__.py +0 -0
  77. pyglet/{gl → graphics/api/gl1}/gl_compat.py +3 -2
  78. pyglet/graphics/api/gl2/__init__.py +0 -0
  79. pyglet/graphics/api/gl2/buffer.py +320 -0
  80. pyglet/graphics/api/gl2/draw.py +600 -0
  81. pyglet/graphics/api/gl2/global_opengl.py +122 -0
  82. pyglet/graphics/api/gl2/shader.py +200 -0
  83. pyglet/graphics/api/gl2/shapes.py +51 -0
  84. pyglet/graphics/api/gl2/sprite.py +79 -0
  85. pyglet/graphics/api/gl2/text.py +175 -0
  86. pyglet/graphics/api/gl2/vertexdomain.py +364 -0
  87. pyglet/graphics/api/webgl/__init__.py +233 -0
  88. pyglet/graphics/api/webgl/buffer.py +302 -0
  89. pyglet/graphics/api/webgl/context.py +234 -0
  90. pyglet/graphics/api/webgl/draw.py +590 -0
  91. pyglet/graphics/api/webgl/enums.py +76 -0
  92. pyglet/graphics/api/webgl/framebuffer.py +360 -0
  93. pyglet/graphics/api/webgl/gl.py +1537 -0
  94. pyglet/graphics/api/webgl/gl_info.py +130 -0
  95. pyglet/graphics/api/webgl/shader.py +1346 -0
  96. pyglet/graphics/api/webgl/shapes.py +92 -0
  97. pyglet/graphics/api/webgl/sprite.py +102 -0
  98. pyglet/graphics/api/webgl/state.py +227 -0
  99. pyglet/graphics/api/webgl/text.py +187 -0
  100. pyglet/graphics/api/webgl/texture.py +1227 -0
  101. pyglet/graphics/api/webgl/vertexarray.py +54 -0
  102. pyglet/graphics/api/webgl/vertexdomain.py +616 -0
  103. pyglet/graphics/api/webgl/webgl_js.pyi +307 -0
  104. pyglet/{image → graphics}/atlas.py +33 -32
  105. pyglet/graphics/base.py +10 -0
  106. pyglet/graphics/buffer.py +245 -0
  107. pyglet/graphics/draw.py +578 -0
  108. pyglet/graphics/framebuffer.py +26 -0
  109. pyglet/graphics/instance.py +178 -69
  110. pyglet/graphics/shader.py +267 -1553
  111. pyglet/graphics/state.py +83 -0
  112. pyglet/graphics/texture.py +703 -0
  113. pyglet/graphics/vertexdomain.py +695 -538
  114. pyglet/gui/ninepatch.py +10 -10
  115. pyglet/gui/widgets.py +120 -10
  116. pyglet/image/__init__.py +20 -1973
  117. pyglet/image/animation.py +12 -12
  118. pyglet/image/base.py +730 -0
  119. pyglet/image/codecs/__init__.py +9 -0
  120. pyglet/image/codecs/bmp.py +53 -30
  121. pyglet/image/codecs/dds.py +53 -31
  122. pyglet/image/codecs/gdiplus.py +38 -14
  123. pyglet/image/codecs/gdkpixbuf2.py +0 -2
  124. pyglet/image/codecs/js_image.py +99 -0
  125. pyglet/image/codecs/ktx2.py +161 -0
  126. pyglet/image/codecs/pil.py +1 -1
  127. pyglet/image/codecs/png.py +1 -1
  128. pyglet/image/codecs/wic.py +11 -2
  129. pyglet/info.py +26 -24
  130. pyglet/input/__init__.py +8 -0
  131. pyglet/input/base.py +163 -105
  132. pyglet/input/controller.py +13 -19
  133. pyglet/input/controller_db.py +39 -24
  134. pyglet/input/emscripten/__init__.py +18 -0
  135. pyglet/input/emscripten/gamepad_js.py +397 -0
  136. pyglet/input/linux/__init__.py +11 -5
  137. pyglet/input/linux/evdev.py +10 -11
  138. pyglet/input/linux/x11_xinput.py +2 -2
  139. pyglet/input/linux/x11_xinput_tablet.py +1 -1
  140. pyglet/input/macos/__init__.py +7 -2
  141. pyglet/input/macos/darwin_gc.py +559 -0
  142. pyglet/input/win32/__init__.py +1 -1
  143. pyglet/input/win32/directinput.py +34 -29
  144. pyglet/input/win32/xinput.py +11 -61
  145. pyglet/lib.py +3 -3
  146. pyglet/libs/__init__.py +1 -1
  147. pyglet/{gl → libs/darwin}/agl.py +1 -1
  148. pyglet/libs/darwin/cocoapy/__init__.py +2 -2
  149. pyglet/libs/darwin/cocoapy/cocoahelpers.py +181 -0
  150. pyglet/libs/darwin/cocoapy/cocoalibs.py +31 -0
  151. pyglet/libs/darwin/cocoapy/cocoatypes.py +27 -0
  152. pyglet/libs/darwin/cocoapy/runtime.py +81 -45
  153. pyglet/libs/darwin/coreaudio.py +4 -4
  154. pyglet/{gl → libs/darwin}/lib_agl.py +9 -8
  155. pyglet/libs/darwin/quartzkey.py +1 -3
  156. pyglet/libs/egl/__init__.py +2 -0
  157. pyglet/libs/egl/egl_lib.py +576 -0
  158. pyglet/libs/egl/eglext.py +51 -5
  159. pyglet/libs/linux/__init__.py +0 -0
  160. pyglet/libs/linux/egl/__init__.py +0 -0
  161. pyglet/libs/linux/egl/eglext.py +22 -0
  162. pyglet/libs/linux/glx/__init__.py +0 -0
  163. pyglet/{gl → libs/linux/glx}/glx.py +13 -14
  164. pyglet/{gl → libs/linux/glx}/glxext_arb.py +408 -192
  165. pyglet/{gl → libs/linux/glx}/glxext_mesa.py +1 -1
  166. pyglet/{gl → libs/linux/glx}/glxext_nv.py +345 -164
  167. pyglet/{gl → libs/linux/glx}/lib_glx.py +3 -2
  168. pyglet/libs/linux/wayland/__init__.py +0 -0
  169. pyglet/libs/linux/wayland/client.py +1068 -0
  170. pyglet/libs/linux/wayland/lib_wayland.py +207 -0
  171. pyglet/libs/linux/wayland/wayland_egl.py +38 -0
  172. pyglet/libs/{wayland → linux/wayland}/xkbcommon.py +26 -0
  173. pyglet/libs/{x11 → linux/x11}/xf86vmode.py +4 -4
  174. pyglet/libs/{x11 → linux/x11}/xinerama.py +2 -2
  175. pyglet/libs/{x11 → linux/x11}/xinput.py +10 -10
  176. pyglet/libs/linux/x11/xrandr.py +0 -0
  177. pyglet/libs/{x11 → linux/x11}/xrender.py +1 -1
  178. pyglet/libs/shared/__init__.py +0 -0
  179. pyglet/libs/shared/spirv/__init__.py +0 -0
  180. pyglet/libs/shared/spirv/lib_shaderc.py +85 -0
  181. pyglet/libs/shared/spirv/lib_spirv_cross.py +126 -0
  182. pyglet/libs/win32/__init__.py +28 -8
  183. pyglet/libs/win32/constants.py +59 -48
  184. pyglet/libs/win32/context_managers.py +20 -3
  185. pyglet/libs/win32/dinput.py +105 -88
  186. pyglet/{gl → libs/win32}/lib_wgl.py +52 -26
  187. pyglet/libs/win32/types.py +58 -23
  188. pyglet/{gl → libs/win32}/wgl.py +32 -25
  189. pyglet/{gl → libs/win32}/wglext_arb.py +364 -2
  190. pyglet/media/__init__.py +9 -10
  191. pyglet/media/codecs/__init__.py +12 -1
  192. pyglet/media/codecs/base.py +99 -96
  193. pyglet/media/codecs/ffmpeg.py +2 -2
  194. pyglet/media/codecs/ffmpeg_lib/libavformat.py +3 -8
  195. pyglet/media/codecs/webaudio_pyodide.py +111 -0
  196. pyglet/media/drivers/__init__.py +9 -4
  197. pyglet/media/drivers/base.py +4 -4
  198. pyglet/media/drivers/openal/__init__.py +1 -1
  199. pyglet/media/drivers/openal/adaptation.py +3 -3
  200. pyglet/media/drivers/pulse/__init__.py +1 -1
  201. pyglet/media/drivers/pulse/adaptation.py +3 -3
  202. pyglet/media/drivers/pyodide_js/__init__.py +8 -0
  203. pyglet/media/drivers/pyodide_js/adaptation.py +288 -0
  204. pyglet/media/drivers/xaudio2/adaptation.py +3 -3
  205. pyglet/media/player.py +276 -193
  206. pyglet/media/player_worker_thread.py +1 -1
  207. pyglet/model/__init__.py +39 -29
  208. pyglet/model/codecs/base.py +4 -4
  209. pyglet/model/codecs/gltf.py +3 -3
  210. pyglet/model/codecs/obj.py +71 -43
  211. pyglet/resource.py +129 -78
  212. pyglet/shapes.py +154 -194
  213. pyglet/sprite.py +47 -164
  214. pyglet/text/__init__.py +44 -54
  215. pyglet/text/caret.py +12 -7
  216. pyglet/text/document.py +19 -17
  217. pyglet/text/formats/html.py +2 -2
  218. pyglet/text/formats/structured.py +10 -40
  219. pyglet/text/layout/__init__.py +20 -13
  220. pyglet/text/layout/base.py +176 -287
  221. pyglet/text/layout/incremental.py +9 -10
  222. pyglet/text/layout/scrolling.py +7 -95
  223. pyglet/window/__init__.py +183 -172
  224. pyglet/window/cocoa/__init__.py +62 -51
  225. pyglet/window/cocoa/pyglet_delegate.py +2 -25
  226. pyglet/window/cocoa/pyglet_view.py +9 -8
  227. pyglet/window/dialog/__init__.py +184 -0
  228. pyglet/window/dialog/base.py +99 -0
  229. pyglet/window/dialog/darwin.py +121 -0
  230. pyglet/window/dialog/linux.py +72 -0
  231. pyglet/window/dialog/windows.py +194 -0
  232. pyglet/window/emscripten/__init__.py +779 -0
  233. pyglet/window/headless/__init__.py +44 -28
  234. pyglet/window/key.py +2 -0
  235. pyglet/window/mouse.py +2 -2
  236. pyglet/window/wayland/__init__.py +377 -0
  237. pyglet/window/win32/__init__.py +101 -46
  238. pyglet/window/xlib/__init__.py +104 -66
  239. {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/METADATA +2 -3
  240. pyglet-3.0.dev1.dist-info/RECORD +322 -0
  241. {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/WHEEL +1 -1
  242. pyglet/gl/__init__.py +0 -208
  243. pyglet/gl/base.py +0 -499
  244. pyglet/gl/cocoa.py +0 -309
  245. pyglet/gl/gl.py +0 -4625
  246. pyglet/gl/gl.pyi +0 -2320
  247. pyglet/gl/gl_compat.pyi +0 -3097
  248. pyglet/gl/gl_info.py +0 -190
  249. pyglet/gl/headless.py +0 -166
  250. pyglet/gl/wgl_info.py +0 -36
  251. pyglet/gl/wglext_nv.py +0 -1096
  252. pyglet/gl/win32.py +0 -268
  253. pyglet/gl/xlib.py +0 -295
  254. pyglet/image/buffer.py +0 -274
  255. pyglet/image/codecs/s3tc.py +0 -354
  256. pyglet/libs/x11/xrandr.py +0 -166
  257. pyglet-2.1.13.dist-info/RECORD +0 -234
  258. /pyglet/{libs/wayland → graphics/api/gl/cocoa}/__init__.py +0 -0
  259. /pyglet/libs/{egl → linux/egl}/egl.py +0 -0
  260. /pyglet/libs/{egl → linux/egl}/lib.py +0 -0
  261. /pyglet/libs/{ioctl.py → linux/ioctl.py} +0 -0
  262. /pyglet/libs/{wayland → linux/wayland}/gbm.py +0 -0
  263. /pyglet/libs/{x11 → linux/x11}/__init__.py +0 -0
  264. /pyglet/libs/{x11 → linux/x11}/cursorfont.py +0 -0
  265. /pyglet/libs/{x11 → linux/x11}/xlib.py +0 -0
  266. /pyglet/libs/{x11 → linux/x11}/xsync.py +0 -0
  267. {pyglet-2.1.13.dist-info/licenses → pyglet-3.0.dev1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,1526 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ from ctypes import byref, Array
5
+ from typing import Literal, Iterator, Union, Sequence
6
+
7
+ import pyglet
8
+ from pyglet.enums import TextureType, TextureFilter, ComponentFormat, AddressMode
9
+ from pyglet.graphics.api.gl import OpenGLSurfaceContext, GL_COMPRESSED_RGB8_ETC2
10
+ from pyglet.graphics.api.gl.gl import (
11
+ GL_RED,
12
+ GL_RG,
13
+ GL_RGB,
14
+ GL_BGR,
15
+ GL_RGBA,
16
+ GL_BGRA,
17
+ GL_RED_INTEGER,
18
+ GL_RG_INTEGER,
19
+ GL_RGB_INTEGER,
20
+ GL_BGR_INTEGER,
21
+ GL_RGBA_INTEGER,
22
+ GL_BGRA_INTEGER,
23
+ GL_DEPTH_COMPONENT,
24
+ GL_DEPTH_STENCIL,
25
+ GL_UNSIGNED_BYTE,
26
+ GL_TEXTURE_MIN_FILTER,
27
+ GL_TEXTURE_MAG_FILTER,
28
+ GL_TEXTURE_2D,
29
+ GLuint,
30
+ GL_TEXTURE0,
31
+ GL_READ_WRITE,
32
+ GL_RGBA32F,
33
+ GLubyte,
34
+ GL_PACK_ALIGNMENT,
35
+ GL_UNPACK_SKIP_PIXELS,
36
+ GL_UNPACK_SKIP_ROWS,
37
+ GL_UNPACK_ALIGNMENT,
38
+ GL_UNPACK_ROW_LENGTH,
39
+ GL_TEXTURE_2D_ARRAY, # noqa: F401
40
+ GL_TRIANGLES, # noqa: F401
41
+ GL_RGBA8, # noqa: F401
42
+ GL_R8, # noqa: F401
43
+ GL_RG8, # noqa: F401
44
+ GL_RGB8, # noqa: F401
45
+ GL_BYTE, # noqa: F401
46
+ GL_INT, # noqa: F401
47
+ GL_DEPTH_COMPONENT16, # noqa: F401
48
+ GL_DEPTH_COMPONENT24, # noqa: F401
49
+ GL_DEPTH_COMPONENT32, # noqa: F401
50
+ GL_DEPTH_COMPONENT32F, # noqa: F401
51
+ GL_FRAMEBUFFER,
52
+ GL_COLOR_ATTACHMENT0,
53
+ GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
54
+ GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,
55
+ GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
56
+ GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
57
+ GL_TEXTURE_SWIZZLE_R,
58
+ GL_TEXTURE_SWIZZLE_G,
59
+ GL_TEXTURE_SWIZZLE_B,
60
+ GL_TEXTURE_SWIZZLE_A,
61
+ GL_GREEN,
62
+ GL_COMPRESSED_RED_RGTC1,
63
+ GL_COMPRESSED_SIGNED_RED_RGTC1,
64
+ GL_COMPRESSED_RG_RGTC2,
65
+ GL_COMPRESSED_SIGNED_RG_RGTC2,
66
+ )
67
+
68
+ from pyglet.graphics.api.gl import gl
69
+ from pyglet.graphics.api.gl.enums import texture_map
70
+ from pyglet.image.base import _AbstractImage, ImageData, ImageDataRegion, ImageGrid, _AbstractGrid, T, CompressionFormat, \
71
+ CompressedImageData
72
+ from pyglet.image.base import ImageException
73
+ from pyglet.graphics.texture import TextureBase, TextureRegionBase, UniformTextureSequence, TextureArraySizeExceeded, \
74
+ TextureArrayDepthExceeded, CompressedTextureBase
75
+
76
+ _api_base_internal_formats = {
77
+ 'R': 'GL_R',
78
+ 'RG': 'GL_RG',
79
+ 'RGB': 'GL_RGB',
80
+ 'RGBA': 'GL_RGBA',
81
+ 'BGR': 'GL_RGB',
82
+ 'BGRA': 'GL_RGBA',
83
+ 'D': 'GL_DEPTH_COMPONENT',
84
+ 'DS': 'GL_DEPTH_STENCIL',
85
+ 'L': 'GL_R',
86
+ 'LA': 'GL_RG',
87
+ }
88
+
89
+ _api_base_pixel_formats = {
90
+ 'R': 'GL_RED',
91
+ 'RG': 'GL_RG',
92
+ 'RGB': 'GL_RGB',
93
+ 'RGBA': 'GL_RGBA',
94
+ 'D': 'GL_DEPTH_COMPONENT',
95
+ 'DS': 'GL_DEPTH_STENCIL',
96
+ 'L': 'GL_RED',
97
+ 'LA': 'GL_RG',
98
+ }
99
+
100
+
101
+ def _get_base_format(component_format: ComponentFormat) -> int:
102
+ return globals()[_api_base_pixel_formats[component_format]]
103
+
104
+
105
+ def _get_internal_format(component_format: ComponentFormat, bit_size: int = 8, data_type: str = "B") -> int:
106
+ """Convert our internal format class to the GL equivalent with size and type."""
107
+ # Base format based on components
108
+ base_format = _api_base_internal_formats.get(component_format.upper())
109
+
110
+ if base_format is None:
111
+ raise ValueError(f"Unknown format: {component_format}")
112
+
113
+ # Type suffix based on data type (integer, float, or default)
114
+ if data_type == "I" and component_format != ComponentFormat.D:
115
+ type_suffix = "UI"
116
+ elif data_type == "i":
117
+ type_suffix = 'I'
118
+ elif data_type == "f":
119
+ type_suffix = 'F'
120
+ else:
121
+ type_suffix = '' # No suffix for unsigned normalized formats
122
+
123
+ # Construct the final GL format string.
124
+ # For example. Base_format: GL_RGBA, size: 32, "type": float -> GL_RGBA32F
125
+ gl_format = f"{base_format}{bit_size}{type_suffix}"
126
+
127
+ # Get the integer value of the GL constant using globals()
128
+ if gl_format in globals():
129
+ return globals()[gl_format]
130
+ raise ValueError(f"GL constant '{gl_format}' not defined.")
131
+
132
+
133
+ _api_pixel_formats = {
134
+ 'R': GL_RED,
135
+ 'RG': GL_RG,
136
+ 'RGB': GL_RGB,
137
+ 'BGR': GL_BGR,
138
+ 'RGBA': GL_RGBA,
139
+ 'BGRA': GL_BGRA,
140
+ 'RI': GL_RED_INTEGER,
141
+ 'RGI': GL_RG_INTEGER,
142
+ 'RGBI': GL_RGB_INTEGER,
143
+ 'BGRI': GL_BGR_INTEGER,
144
+ 'RGBAI': GL_RGBA_INTEGER,
145
+ 'BGRAI': GL_BGRA_INTEGER,
146
+ 'D': GL_DEPTH_COMPONENT,
147
+ 'DS': GL_DEPTH_STENCIL,
148
+ 'L': GL_RED,
149
+ 'LA': GL_RG,
150
+ }
151
+
152
+
153
+ _data_types = {
154
+ "b": gl.GL_BYTE,
155
+ "B": gl.GL_UNSIGNED_BYTE,
156
+ "h": gl.GL_SHORT,
157
+ "H": gl.GL_UNSIGNED_SHORT,
158
+ "i": gl.GL_INT,
159
+ "I": gl.GL_UNSIGNED_INT,
160
+ "f": gl.GL_FLOAT,
161
+ "f.5": gl.GL_HALF_FLOAT,
162
+ "d": gl.GL_DOUBLE,
163
+ }
164
+
165
+
166
+ def get_max_texture_size() -> int:
167
+ """Return the maximum texture size available."""
168
+ return pyglet.graphics.api.core.current_context.get_info().MAX_TEXTURE_SIZE
169
+
170
+
171
+ def get_max_array_texture_layers() -> int:
172
+ """Return the maximum TextureArray depth."""
173
+ return pyglet.graphics.api.core.current_context.get_info().MAX_ARRAY_TEXTURE_LAYERS
174
+
175
+
176
+ def _get_gl_format_and_type(fmt: str, data_type: str) -> tuple[int | None, int | None]:
177
+ fmt = _api_pixel_formats.get(fmt)
178
+ if fmt:
179
+ return fmt, _data_types.get(data_type) # Eventually support others through ImageData.
180
+
181
+ return None, None
182
+
183
+
184
+ def _get_pixel_format(image_data: ImageData) -> tuple[int, int]:
185
+ """Determine the pixel format from format string for the Graphics API."""
186
+ data_format = image_data.format
187
+ fmt, gl_type = _get_gl_format_and_type(data_format, image_data.data_type)
188
+
189
+ if fmt is None:
190
+ # Need to convert data to a standard form
191
+ data_format = {
192
+ 1: 'R',
193
+ 2: 'RG',
194
+ 3: 'RGB',
195
+ 4: 'RGBA',
196
+ }.get(len(data_format))
197
+ fmt, gl_type = _get_gl_format_and_type(data_format, image_data.data_type)
198
+
199
+ return fmt, gl_type
200
+
201
+
202
+ class Texture(TextureBase):
203
+ """An image loaded into GPU memory.
204
+
205
+ Typically, you will get an instance of Texture by accessing calling
206
+ the ``get_texture()`` method of any AbstractImage class (such as ImageData).
207
+ """
208
+
209
+ region_class: type[TextureRegion] # Set to TextureRegion after it's defined
210
+ """The class to use when constructing regions of this texture.
211
+ The class should be a subclass of TextureRegion.
212
+ """
213
+
214
+ tex_coords = (0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0)
215
+ """12-tuple of float, named (u1, v1, r1, u2, v2, r2, ...).
216
+ ``u, v, r`` give the 3D texture coordinates for vertices 1-4. The vertices
217
+ are specified in the order bottom-left, bottom-right, top-right and top-left.
218
+ """
219
+
220
+ tex_coords_order: tuple[int, int, int, int] = (0, 1, 2, 3)
221
+ """The default vertex winding order for a quad.
222
+ This defaults to counter-clockwise, starting at the bottom-left.
223
+ """
224
+
225
+ # If this backend supports pixel data conversion.
226
+ # If False, will force data to be RGBA, even if CPU is used to order it.
227
+ pixel_conversion = True
228
+
229
+ level: int = 0
230
+ """The mipmap level of this texture."""
231
+
232
+ images = 1
233
+
234
+ default_filters: TextureFilter | tuple[TextureFilter, TextureFilter] = TextureFilter.LINEAR, TextureFilter.LINEAR
235
+ """The default minification and magnification filters, as a tuple.
236
+ Both default to LINEAR. If a texture is created without specifying
237
+ a filter, these defaults will be used.
238
+ """
239
+
240
+ x: int = 0
241
+ y: int = 0
242
+ z: int = 0
243
+
244
+ def __init__(self, context: OpenGLSurfaceContext, width: int, height: int, tex_id: int,
245
+ tex_type: TextureType = TextureType.TYPE_2D,
246
+ internal_format: ComponentFormat = ComponentFormat.RGBA,
247
+ internal_format_size: int = 8,
248
+ internal_format_type: str = "B",
249
+ filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
250
+ address_mode: AddressMode = AddressMode.REPEAT,
251
+ anisotropic_level: int = 0):
252
+ super().__init__(width, height, tex_id, tex_type, internal_format, internal_format_size, internal_format_type,
253
+ filters, address_mode, anisotropic_level)
254
+ self._context = context
255
+ self.target = texture_map[self.tex_type]
256
+ self._gl_min_filter = texture_map[self.min_filter]
257
+ self._gl_mag_filter = texture_map[self.mag_filter]
258
+ self._gl_internal_format = _get_internal_format(internal_format, internal_format_size, internal_format_type)
259
+
260
+ def delete(self) -> None:
261
+ """Delete this texture and the memory it occupies.
262
+
263
+ Textures are invalid after deletion, and may no longer be used.
264
+ """
265
+ self._context.glDeleteTextures(1, GLuint(self.id))
266
+ self.id = None
267
+
268
+ def __del__(self):
269
+ if self.id is not None:
270
+ try:
271
+ self._context.delete_texture(self.id)
272
+ self.id = None
273
+ except (AttributeError, ImportError):
274
+ pass # Interpreter is shutting down
275
+
276
+ def bind(self, texture_unit: int = 0) -> None:
277
+ """Bind to a specific Texture Unit by number."""
278
+ self._context.glActiveTexture(GL_TEXTURE0 + texture_unit)
279
+ self._context.glBindTexture(self.target, self.id)
280
+
281
+ def bind_image_texture(self, unit: int, level: int = 0, layered: bool = False,
282
+ layer: int = 0, access: int = GL_READ_WRITE, fmt: int = GL_RGBA32F):
283
+ """Bind as an ImageTexture for use with a :py:class:`~pyglet.shader.ComputeShaderProgram`.
284
+
285
+ .. note:: OpenGL 4.3, or 4.2 with the GL_ARB_compute_shader extention is required.
286
+ """
287
+ self._context.glBindImageTexture(unit, self.id, level, layered, layer, access, fmt)
288
+
289
+ @classmethod
290
+ def create_from_image(cls,
291
+ image_data: ImageData | ImageDataRegion,
292
+ tex_type: TextureType = TextureType.TYPE_2D,
293
+ internal_format_size: int = 8,
294
+ filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
295
+ address_mode: AddressMode = AddressMode.REPEAT,
296
+ anisotropic_level: int = 0,
297
+ context: OpenGLSurfaceContext | None = None,
298
+ ) -> Texture:
299
+ """Create a Texture from image data.
300
+
301
+ Args:
302
+ image_data:
303
+ The image instance.
304
+ tex_type:
305
+ The texture enum type.
306
+ internal_format_size:
307
+ Byte size of the internal format.
308
+ filters:
309
+ Texture format filter, passed as a list of min/mag filters, or a single filter to apply both.
310
+ address_mode:
311
+ Texture address mode.
312
+ anisotropic_level:
313
+ The maximum anisotropic level.
314
+ context:
315
+ A specific OpenGL Surface context, otherwise the current active context.
316
+
317
+ Returns:
318
+ A currently bound texture.
319
+ """
320
+ ctx = context or pyglet.graphics.api.core.current_context
321
+
322
+ tex_id = GLuint()
323
+ ctx.glGenTextures(1, byref(tex_id))
324
+ target = texture_map[tex_type]
325
+ ctx.glBindTexture(target, tex_id.value)
326
+
327
+ texture = cls(ctx, image_data.width, image_data.height, tex_id.value, tex_type,
328
+ ComponentFormat(image_data.format), internal_format_size, image_data.data_type, filters,
329
+ address_mode, anisotropic_level)
330
+
331
+ ctx.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, texture._gl_min_filter)
332
+ ctx.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, texture._gl_mag_filter)
333
+
334
+ pixel_fmt = image_data.format
335
+ image_bytes = image_data.get_bytes(pixel_fmt, image_data.width * len(pixel_fmt))
336
+ gl_pfmt, gl_type = _get_pixel_format(image_data)
337
+
338
+ # !!! Better place for this?
339
+ if pixel_fmt in ("L", "LA"):
340
+ texture._swizzle_legacy_fmts(pixel_fmt)
341
+
342
+ align, row_length = texture._get_image_alignment(image_data)
343
+
344
+ ctx.glPixelStorei(GL_UNPACK_ALIGNMENT, align)
345
+ ctx.glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length)
346
+
347
+ ctx.glTexImage2D(target, 0,
348
+ texture._gl_internal_format,
349
+ image_data.width, image_data.height,
350
+ 0,
351
+ gl_pfmt,
352
+ gl_type,
353
+ image_bytes)
354
+ ctx.glFlush()
355
+ return texture
356
+
357
+ def _swizzle_legacy_fmts(self, pixel_fmt: str) -> None:
358
+ # LUMINANCE and LUMINANCE_ALPHA are legacy and removed in core GL. Use swizzle to emulate.
359
+ if pixel_fmt.startswith("L"):
360
+ self._context.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED)
361
+ self._context.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_RED)
362
+ self._context.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED)
363
+ if pixel_fmt == "LA":
364
+ self._context.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_GREEN)
365
+
366
+ @classmethod
367
+ def create(cls, width: int, height: int,
368
+ tex_type: TextureType = TextureType.TYPE_2D,
369
+ internal_format: ComponentFormat = ComponentFormat.RGBA,
370
+ internal_format_size: int = 8,
371
+ internal_format_type: str = "B",
372
+ filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
373
+ address_mode: AddressMode = AddressMode.REPEAT,
374
+ anisotropic_level: int = 0,
375
+ blank_data: bool = True, context: OpenGLSurfaceContext | None = None) -> TextureBase:
376
+ """Create a Texture.
377
+
378
+ Create a Texture with the specified dimensions, and attributes.
379
+
380
+ Args:
381
+ width:
382
+ Width of texture in pixels.
383
+ height:
384
+ Height of texture in pixels.
385
+ tex_type:
386
+ The texture enum type.
387
+ internal_format:
388
+ Component format of the image data.
389
+ internal_format_size:
390
+ Byte size of the internal format.
391
+ internal_format_type:
392
+ Internal format type in struct format.
393
+ filters:
394
+ Texture format filter, passed as a list of min/mag filter or a single filter to apply both.
395
+ address_mode:
396
+ Texture address mode.
397
+ anisotropic_level:
398
+ The maximum anisotropic level.
399
+ blank_data:
400
+ If True, initialize the texture data with all zeros. If False, do not pass initial data.
401
+ context:
402
+ A specific OpenGL Surface context, otherwise the current active context.
403
+
404
+ Returns:
405
+ A currently bound texture.
406
+ """
407
+ ctx = context or pyglet.graphics.api.core.current_context
408
+
409
+ tex_id = GLuint()
410
+ target = texture_map[tex_type]
411
+ ctx.glGenTextures(1, byref(tex_id))
412
+
413
+ texture = cls(ctx, width, height, tex_id.value, tex_type, internal_format, internal_format_size, internal_format_type, filters, address_mode, anisotropic_level)
414
+ ctx.glBindTexture(target, tex_id.value)
415
+ ctx.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, texture._gl_min_filter)
416
+ ctx.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, texture._gl_mag_filter)
417
+
418
+ data = (GLubyte * (width * height * len(internal_format)))() if blank_data else None
419
+ texture._allocate(data)
420
+ return texture
421
+
422
+ def _allocate(self, data: None | Array) -> None:
423
+ self._context.glTexImage2D(self.target, 0,
424
+ self._gl_internal_format,
425
+ self.width, self.height,
426
+ 0,
427
+ _get_base_format(self.internal_format),
428
+ GL_UNSIGNED_BYTE,
429
+ data)
430
+ self._context.glFlush()
431
+
432
+ def _attach_gles_fbo_texture(self, _z: int = 0) -> None:
433
+ self._context.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.id, self.level)
434
+
435
+ def fetch(self, z: int = 0) -> ImageData:
436
+ """Fetch the image data of this texture from the GPU.
437
+
438
+ Binds the texture and reads the pixel data back from the GPU, as such, can be a costly operation.
439
+
440
+ Modifying the returned ImageData object has no effect on the
441
+ texture itself. Uploading ImageData back to the GPU/texture
442
+ can be done with the :py:meth:`~Texture.upload` method.
443
+
444
+ Args:
445
+ z:
446
+ For 3D textures, the image slice to retrieve.
447
+ """
448
+ self._context.glBindTexture(self.target, self.id)
449
+
450
+ # Some tests seem to rely on this always being RGBA
451
+ fmt = 'RGBA'
452
+ gl_format = GL_RGBA
453
+
454
+ size = self.width * self.height * self.images * len(fmt)
455
+ buf = (GLubyte * size)()
456
+
457
+ if self._context.get_info().get_opengl_api() == "gles":
458
+ self._context.gles_pixel_fbo.bind()
459
+ self._context.glPixelStorei(GL_PACK_ALIGNMENT, 1)
460
+ self._attach_gles_fbo_texture(z)
461
+ self._context.glReadPixels(0, 0, self.width, self.height, gl_format, GL_UNSIGNED_BYTE, buf)
462
+ self._context.gles_pixel_fbo.unbind()
463
+ data = ImageData(self.width, self.height, fmt, buf)
464
+ else:
465
+ self._context.glPixelStorei(GL_PACK_ALIGNMENT, 1)
466
+ self._context.glGetTexImage(self.target, self.level, gl_format, GL_UNSIGNED_BYTE, buf)
467
+
468
+ data = ImageData(self.width, self.height, fmt, buf)
469
+ if self.images > 1:
470
+ data = data.get_region(0, z * self.height, self.width, self.height)
471
+ return data
472
+
473
+ def get_image_data(self, z: int = 0) -> ImageData:
474
+ """Get the image data of this texture.
475
+
476
+ Bind the texture, and read the pixel data back from the GPU.
477
+ This can be a somewhat costly operation.
478
+ Modifying the returned ImageData object has no effect on the
479
+ texture itself. Uploading ImageData back to the GPU/texture
480
+ can be done with the :py:meth:`~Texture.upload` method.
481
+
482
+ Args:
483
+ z:
484
+ For 3D textures, the image slice to retrieve.
485
+ """
486
+ return self.fetch(z)
487
+
488
+ def _apply_region_unpack(self, image_data: ImageData | ImageDataRegion) -> None:
489
+ if isinstance(image_data, ImageDataRegion):
490
+ self._context.glPixelStorei(GL_UNPACK_SKIP_PIXELS, image_data.x)
491
+ self._context.glPixelStorei(GL_UNPACK_SKIP_ROWS, image_data.y)
492
+
493
+ def _default_region_unpack(self, image_data: ImageData | ImageDataRegion) -> None:
494
+ if isinstance(image_data, ImageDataRegion):
495
+ self._context.glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0)
496
+ self._context.glPixelStorei(GL_UNPACK_SKIP_ROWS, 0)
497
+
498
+ @staticmethod
499
+ def _get_image_alignment(image_data: ImageData) -> tuple[int, int]:
500
+ """Image alignment and row length information on an Image to upload.
501
+
502
+ Args:
503
+ image_data: The image data to get the alignment from.
504
+
505
+ Returns:
506
+ (align, row_length)
507
+ align: 1, 2, 4, or 8 \
508
+ row_length: 0 if tightly packed.
509
+ """
510
+ components = len(image_data.format)
511
+ width = image_data.width
512
+ packed_row_bytes = width * components
513
+ pitch = abs(image_data._current_pitch)
514
+ if packed_row_bytes % 8 == 0:
515
+ align = 8
516
+ elif packed_row_bytes % 4 == 0:
517
+ align = 4
518
+ elif packed_row_bytes % 2 == 0:
519
+ align = 2
520
+ else:
521
+ align = 1
522
+
523
+ if pitch == packed_row_bytes:
524
+ row_length = 0
525
+ else:
526
+ row_length = pitch // components
527
+
528
+ return align, row_length
529
+
530
+ def _update_subregion(self, image_data: ImageData, x: int, y: int, z: int) -> None:
531
+ data_pitch = abs(image_data._current_pitch)
532
+
533
+ # Get data in required format (hopefully will be the same format it's already
534
+ # in, unless that's an obscure format, upside-down or the driver is old).
535
+ data = image_data.convert(image_data.format, data_pitch)
536
+
537
+ fmt, gl_type = _get_pixel_format(image_data)
538
+
539
+ self._context.glTexSubImage2D(self.target, self.level,
540
+ x, y,
541
+ image_data.width, image_data.height,
542
+ fmt, gl_type,
543
+ data)
544
+
545
+ def upload(self, image_data: ImageData | ImageDataRegion, x: int, y: int, z: int) -> None:
546
+ """Upload image data into the Texture at specific coordinates.
547
+
548
+ You must have this texture bound and allocated before uploading data.
549
+
550
+ The image's anchor point will be aligned to the given ``x`` and ``y``
551
+ coordinates. If this texture is a 3D texture, the ``z``
552
+ parameter gives the image slice to blit into.
553
+ """
554
+ x -= self.anchor_x
555
+ y -= self.anchor_y
556
+
557
+ align, row_length = self._get_image_alignment(image_data)
558
+
559
+ self._context.glPixelStorei(GL_UNPACK_ALIGNMENT, align)
560
+ self._context.glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length)
561
+ self._apply_region_unpack(image_data)
562
+
563
+ self._update_subregion(image_data, x, y, z)
564
+
565
+ self._context.glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)
566
+ self._default_region_unpack(image_data)
567
+
568
+ # Flush image upload before data gets GC'd:
569
+ self._context.glFlush()
570
+
571
+ def get_texture(self) -> TextureBase:
572
+ return self
573
+
574
+ def get_mipmapped_texture(self) -> TextureBase:
575
+ raise NotImplementedError(f"Not implemented for {self}.")
576
+
577
+ def get_region(self, x: int, y: int, width: int, height: int) -> TextureRegionBase:
578
+ return self.region_class(x, y, 0, width, height, self)
579
+
580
+ def get_transform(self, flip_x: bool = False, flip_y: bool = False,
581
+ rotate: Literal[0, 90, 180, 270, 360] = 0) -> TextureRegionBase:
582
+ """Create a copy of this image applying a simple transformation.
583
+
584
+ The transformation is applied to the texture coordinates only;
585
+ :py:meth:`~pyglet.image.AbstractImage.get_image_data` will return the
586
+ untransformed data. The transformation is applied around the anchor point.
587
+
588
+ Args:
589
+ flip_x:
590
+ If True, the returned image will be flipped horizontally.
591
+ flip_y:
592
+ If True, the returned image will be flipped vertically.
593
+ rotate:
594
+ Degrees of clockwise rotation of the returned image. Only
595
+ 90-degree increments are supported.
596
+ """
597
+ transform = self.get_region(0, 0, self.width, self.height)
598
+ bl, br, tr, tl = 0, 1, 2, 3
599
+ transform.anchor_x = self.anchor_x
600
+ transform.anchor_y = self.anchor_y
601
+ if flip_x:
602
+ bl, br, tl, tr = br, bl, tr, tl
603
+ transform.anchor_x = self.width - self.anchor_x
604
+ if flip_y:
605
+ bl, br, tl, tr = tl, tr, bl, br
606
+ transform.anchor_y = self.height - self.anchor_y
607
+ rotate %= 360
608
+ if rotate < 0:
609
+ rotate += 360
610
+ if rotate == 0:
611
+ pass
612
+ elif rotate == 90:
613
+ bl, br, tr, tl = br, tr, tl, bl
614
+ transform.anchor_x, transform.anchor_y = transform.anchor_y, transform.width - transform.anchor_x
615
+ elif rotate == 180:
616
+ bl, br, tr, tl = tr, tl, bl, br
617
+ transform.anchor_x = transform.width - transform.anchor_x
618
+ transform.anchor_y = transform.height - transform.anchor_y
619
+ elif rotate == 270:
620
+ bl, br, tr, tl = tl, bl, br, tr
621
+ transform.anchor_x, transform.anchor_y = transform.height - transform.anchor_y, transform.anchor_x
622
+ else:
623
+ raise ImageException("Only 90 degree rotations are supported.")
624
+ if rotate in (90, 270):
625
+ transform.width, transform.height = transform.height, transform.width
626
+ transform._set_tex_coords_order(bl, br, tr, tl)
627
+ return transform
628
+
629
+ def _set_tex_coords_order(self, bl: int, br: int, tr: int, tl: int) -> None:
630
+ tex_coords = (self.tex_coords[:3],
631
+ self.tex_coords[3:6],
632
+ self.tex_coords[6:9],
633
+ self.tex_coords[9:])
634
+ self.tex_coords = tex_coords[bl] + tex_coords[br] + tex_coords[tr] + tex_coords[tl]
635
+
636
+ order = self.tex_coords_order
637
+ self.tex_coords_order = (order[bl], order[br], order[tr], order[tl])
638
+
639
+ @property
640
+ def uv(self) -> tuple[float, float, float, float]:
641
+ """Tuple containing the left, bottom, right, top 2D texture coordinates."""
642
+ tex_coords = self.tex_coords
643
+ return tex_coords[0], tex_coords[1], tex_coords[3], tex_coords[7]
644
+
645
+ @property
646
+ def filters(self) -> tuple[TextureFilter, TextureFilter]:
647
+ """The current Texture filters.
648
+
649
+ Providing a single TextureFilter will adjust both minification and magnification filters. Otherwise, a tuple
650
+ can be provided to adjust each individually.
651
+ """
652
+ return self.min_filter, self.mag_filter
653
+
654
+ @filters.setter
655
+ def filters(self, filters: TextureFilter | tuple[TextureFilter, TextureFilter]) -> None:
656
+ if isinstance(filters, TextureFilter):
657
+ self.min_filter = filters
658
+ self.mag_filter = filters
659
+ else:
660
+ self.min_filter, self.mag_filter = filters
661
+
662
+ self._gl_min_filter = texture_map[self.min_filter]
663
+ self._gl_mag_filter = texture_map[self.mag_filter]
664
+
665
+ self.bind()
666
+ self._context.glTexParameteri(self.target, GL_TEXTURE_MIN_FILTER, self._gl_min_filter)
667
+ self._context.glTexParameteri(self.target, GL_TEXTURE_MAG_FILTER, self._gl_mag_filter)
668
+
669
+ def __repr__(self) -> str:
670
+ return f"{self.__class__.__name__}(id={self.id}, size={self.width}x{self.height})"
671
+
672
+
673
+ class TextureRegion(Texture):
674
+ """A rectangular region of a texture, presented as if it were a separate texture."""
675
+
676
+ def __init__(self, x: int, y: int, z: int, width: int, height: int, owner: TextureBase):
677
+ super().__init__(owner._context, width, height, owner.id, owner.tex_type, owner.internal_format,
678
+ owner.internal_format_size, owner.internal_format_type, owner.filters, owner.address_mode,
679
+ owner.anisotropic_level)
680
+
681
+ self.x = x
682
+ self.y = y
683
+ self.z = z
684
+ self._width = width
685
+ self._height = height
686
+ self.owner = owner
687
+ owner_u1 = owner.tex_coords[0]
688
+ owner_v1 = owner.tex_coords[1]
689
+ owner_u2 = owner.tex_coords[3]
690
+ owner_v2 = owner.tex_coords[7]
691
+ scale_u = owner_u2 - owner_u1
692
+ scale_v = owner_v2 - owner_v1
693
+ u1 = x / owner.width * scale_u + owner_u1
694
+ v1 = y / owner.height * scale_v + owner_v1
695
+ u2 = (x + width) / owner.width * scale_u + owner_u1
696
+ v2 = (y + height) / owner.height * scale_v + owner_v1
697
+ r = z / owner.images + owner.tex_coords[2]
698
+ self.tex_coords = (u1, v1, r, u2, v1, r, u2, v2, r, u1, v2, r)
699
+
700
+ def fetch(self, _z: int = 0) -> ImageDataRegion:
701
+ image_data = self.owner.fetch(self.z)
702
+ return image_data.get_region(self.x, self.y, self.width, self.height)
703
+
704
+ def get_image_data(self):
705
+ return self.fetch()
706
+
707
+ def get_region(self, x: int, y: int, width: int, height: int) -> TextureRegionBase:
708
+ x += self.x
709
+ y += self.y
710
+ region = self.region_class(x, y, self.z, width, height, self.owner)
711
+ region._set_tex_coords_order(*self.tex_coords_order)
712
+ return region
713
+
714
+ def upload(self, source: _AbstractImage, x: int, y: int, z: int) -> None:
715
+ assert source.width <= self._width and source.height <= self._height, f"{source} is larger than {self}"
716
+ self.owner.upload(source, x + self.x, y + self.y, z + self.z)
717
+
718
+ def __repr__(self) -> str:
719
+ return (f"{self.__class__.__name__}(id={self.id},"
720
+ f" size={self.width}x{self.height}, owner={self.owner.width}x{self.owner.height})")
721
+
722
+ def delete(self) -> None:
723
+ """Deleting a TextureRegion has no effect. Operate on the owning texture instead."""
724
+
725
+ def __del__(self):
726
+ pass
727
+
728
+
729
+ Texture.region_class = TextureRegion
730
+
731
+
732
+ class Texture3D(Texture, UniformTextureSequence):
733
+ """A texture with more than one image slice.
734
+
735
+ Use the :py:meth:`create_for_images` or :py:meth:`create_for_image_grid`
736
+ classmethod to construct a Texture3D.
737
+ """
738
+ item_width: int = 0
739
+ item_height: int = 0
740
+ items: tuple
741
+
742
+ def _allocate(self, data: None | Array):
743
+ self._context.glTexImage3D(self.target, 0,
744
+ self._gl_internal_format,
745
+ self.width, self.height, self.images,
746
+ 0,
747
+ _get_base_format(self.internal_format),
748
+ GL_UNSIGNED_BYTE,
749
+ data)
750
+ self._context.glFlush()
751
+
752
+ @classmethod
753
+ def create_for_images(cls, images,
754
+ internal_format_size: int = 8,
755
+ internal_format_type: str = "b",
756
+ filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
757
+ address_mode: AddressMode = AddressMode.REPEAT,
758
+ anisotropic_level: int = 0,
759
+ context: OpenGLSurfaceContext | None = None) -> Texture3D:
760
+ ctx = context or pyglet.graphics.api.core.current_context
761
+ item_width = images[0].width
762
+ item_height = images[0].height
763
+ pixel_fmt = images[0].format
764
+ internal_format = ComponentFormat(pixel_fmt)
765
+
766
+ if not all(img.width == item_width and img.height == item_height for img in images):
767
+ raise ImageException('Images do not have same dimensions.')
768
+
769
+ tex_id = GLuint()
770
+ target = texture_map[TextureType.TYPE_3D]
771
+ ctx.glGenTextures(1, byref(tex_id))
772
+ ctx.glBindTexture(target, tex_id.value)
773
+ texture = cls(
774
+ ctx,
775
+ item_width,
776
+ item_height,
777
+ tex_id.value,
778
+ TextureType.TYPE_3D,
779
+ internal_format,
780
+ internal_format_size,
781
+ internal_format_type,
782
+ filters,
783
+ address_mode,
784
+ anisotropic_level,
785
+ )
786
+ ctx.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, texture._gl_min_filter)
787
+ ctx.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, texture._gl_mag_filter)
788
+
789
+ base_image = images[0]
790
+ if base_image.anchor_x or base_image.anchor_y:
791
+ texture.anchor_x = base_image.anchor_x
792
+ texture.anchor_y = base_image.anchor_y
793
+
794
+ texture.images = len(images)
795
+
796
+ size = texture.width * texture.height * texture.images * len(internal_format)
797
+ data = (GLubyte * size)()
798
+
799
+ ctx.glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
800
+
801
+ texture._allocate(data)
802
+
803
+ items = []
804
+ for i, image in enumerate(images):
805
+ item = cls.region_class(0, 0, i, item_width, item_height, texture)
806
+ items.append(item)
807
+ texture.upload(image, image.anchor_x, image.anchor_y, z=i)
808
+ ctx.glFlush()
809
+
810
+ texture.items = items
811
+ texture.item_width = item_width
812
+ texture.item_height = item_height
813
+ return texture
814
+
815
+ @classmethod
816
+ def create_for_image_grid(cls, grid):
817
+ return cls.create_for_images(grid[:])
818
+
819
+ def _attach_gles_fbo_texture(self, z: int = 0) -> None:
820
+ self._context.glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, self.id, self.level, z)
821
+
822
+ def _update_subregion(self, image_data: ImageData, x: int, y: int, z: int):
823
+ data_pitch = abs(image_data._current_pitch)
824
+
825
+ data = image_data.convert(image_data.format, data_pitch)
826
+
827
+ fmt, gl_type = _get_pixel_format(image_data)
828
+
829
+ self._context.glTexSubImage3D(self.target, self.level,
830
+ x, y, z,
831
+ image_data.width, image_data.height, 1,
832
+ fmt, gl_type,
833
+ data)
834
+
835
+ def __len__(self):
836
+ return len(self.items)
837
+
838
+ def __getitem__(self, index):
839
+ return self.items[index]
840
+
841
+ def __setitem__(self, index, value):
842
+ if type(index) is slice:
843
+ self._context.glBindTexture(self.target, self.id)
844
+
845
+ for item, image in zip(self[index], value): # Needs a test.
846
+ self.upload(self, image, image.anchor_x, image.anchor_y, item.z)
847
+ else:
848
+ self.upload(value, value.anchor_x, value.anchor_y, self[index].z)
849
+
850
+ def __iter__(self) -> Iterator[TextureRegionBase]:
851
+ return iter(self.items)
852
+
853
+
854
+ class TextureArrayRegion(TextureRegion):
855
+ """A region of a TextureArray, presented as if it were a separate texture."""
856
+
857
+ def __repr__(self):
858
+ return f"{self.__class__.__name__}(id={self.id}, size={self.width}x{self.height}, layer={self.z})"
859
+
860
+
861
+ class TextureArray(Texture, UniformTextureSequence):
862
+ items: list[TextureArrayRegion]
863
+
864
+ def __init__(self, context: OpenGLSurfaceContext, width, height, tex_id, max_depth,
865
+ internal_format: ComponentFormat = ComponentFormat.RGBA,
866
+ internal_format_size: int = 8,
867
+ internal_format_type: str = "b",
868
+ filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
869
+ address_mode: AddressMode = AddressMode.REPEAT,
870
+ anisotropic_level: int = 0):
871
+ super().__init__(context, width, height, tex_id, TextureType.TYPE_2D_ARRAY, internal_format, internal_format_size,
872
+ internal_format_type, filters, address_mode, anisotropic_level)
873
+ self.max_depth = max_depth
874
+ self.items = []
875
+
876
+ @classmethod
877
+ def create(cls, width: int, height: int,
878
+ max_depth: int = 256,
879
+ internal_format: ComponentFormat = ComponentFormat.RGBA,
880
+ internal_format_size: int = 8,
881
+ internal_format_type: str = "b",
882
+ filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
883
+ address_mode: AddressMode = AddressMode.REPEAT,
884
+ anisotropic_level: int = 0,
885
+ context: OpenGLSurfaceContext | None = None) -> TextureArray:
886
+ """Create an empty TextureArray.
887
+
888
+ You may specify the maximum depth, or layers, the Texture Array should have. This defaults
889
+ to 256, but will be hardware and driver dependent.
890
+
891
+ Args:
892
+ width:
893
+ Width of the texture.
894
+ height:
895
+ Height of the texture.
896
+ descriptor:
897
+ Texture description.
898
+ max_depth:
899
+ The number of layers in the texture array.
900
+ context:
901
+ A specific OpenGL Surface context, otherwise the current active context.
902
+ .. versionadded:: 2.0
903
+ """
904
+ ctx = context or pyglet.graphics.api.core.current_context
905
+
906
+ max_depth_limit = get_max_array_texture_layers()
907
+ assert max_depth <= max_depth_limit, f"TextureArray max_depth supported is {max_depth_limit}."
908
+
909
+ tex_id = GLuint()
910
+ ctx.glGenTextures(1, byref(tex_id))
911
+
912
+ texture = cls(ctx, width, height, tex_id.value, max_depth, internal_format, internal_format_size,
913
+ internal_format_type, filters, address_mode, anisotropic_level)
914
+
915
+ ctx.glBindTexture(texture.target, tex_id.value)
916
+ ctx.glTexParameteri(texture.target, GL_TEXTURE_MIN_FILTER, texture._gl_min_filter)
917
+ ctx.glTexParameteri(texture.target, GL_TEXTURE_MAG_FILTER, texture._gl_min_filter)
918
+
919
+ texture._allocate(None)
920
+ return texture
921
+
922
+ def _attach_gles_fbo_texture(self, z: int = 0) -> None:
923
+ self._context.glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, self.id, self.level, z)
924
+
925
+ def _update_subregion(self, image_data: ImageData, x: int, y: int, z: int):
926
+ data_pitch = abs(image_data._current_pitch)
927
+
928
+ data = image_data.convert(image_data.format, data_pitch)
929
+
930
+ fmt, gl_type = _get_pixel_format(image_data)
931
+
932
+ self._context.glTexSubImage3D(self.target, self.level,
933
+ x, y, z,
934
+ image_data.width, image_data.height, 1,
935
+ fmt, gl_type,
936
+ data)
937
+
938
+ def _allocate(self, data: None | Array) -> None:
939
+ self._context.glTexImage3D(self.target, 0,
940
+ self._gl_internal_format,
941
+ self.width, self.height, self.max_depth,
942
+ 0,
943
+ _get_base_format(self.internal_format),
944
+ GL_UNSIGNED_BYTE,
945
+ data)
946
+
947
+ def _verify_size(self, image: _AbstractImage) -> None:
948
+ if image.width > self.width or image.height > self.height:
949
+ raise TextureArraySizeExceeded(
950
+ f'Image ({image.width}x{image.height}) exceeds the size of the TextureArray ({self.width}x'
951
+ f'{self.height})')
952
+
953
+ def add(self, image: ImageData) -> TextureArrayRegion:
954
+ if len(self.items) >= self.max_depth:
955
+ raise TextureArrayDepthExceeded("TextureArray is full.")
956
+
957
+ self._verify_size(image)
958
+ start_length = len(self.items)
959
+ item = self.region_class(0, 0, start_length, image.width, image.height, self)
960
+
961
+ self.upload(image, image.anchor_x, image.anchor_y, start_length)
962
+ self.items.append(item)
963
+ return item
964
+
965
+ def allocate(self, *images: ImageData) -> list[TextureArrayRegion]:
966
+ """Allocates multiple images at once."""
967
+ if len(self.items) + len(images) > self.max_depth:
968
+ raise TextureArrayDepthExceeded("The amount of images being added exceeds the depth of this TextureArray.")
969
+
970
+ self._context.glBindTexture(self.target, self.id)
971
+
972
+ start_length = len(self.items)
973
+ for i, image in enumerate(images):
974
+ self._verify_size(image)
975
+ item = self.region_class(0, 0, start_length + i, image.width, image.height, self)
976
+ self.items.append(item)
977
+ self.upload(image, image.anchor_x, image.anchor_y, start_length + i)
978
+
979
+ return self.items[start_length:]
980
+
981
+ @classmethod
982
+ def create_for_image_grid(cls, grid: ImageGrid) -> TextureArray:
983
+ texture_array = cls.create(grid[0].width, grid[0].height,
984
+ internal_format=ComponentFormat(grid[0].format),
985
+ max_depth=len(grid))
986
+ texture_array.allocate(*grid[:])
987
+ return texture_array
988
+
989
+ @classmethod
990
+ def create_for_images(cls, images: Sequence[ImageData],
991
+ max_depth: int | None = None,
992
+ internal_format_size: int = 8,
993
+ internal_format_type: str = "b",
994
+ filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
995
+ address_mode: AddressMode = AddressMode.REPEAT,
996
+ anisotropic_level: int = 0,
997
+ context: OpenGLSurfaceContext | None = None) -> TextureArray:
998
+ ctx = context or pyglet.graphics.api.core.current_context
999
+ item_width = images[0].width
1000
+ item_height = images[0].height
1001
+ pixel_fmt = images[0].format
1002
+ internal_format = ComponentFormat(pixel_fmt)
1003
+
1004
+ if not all(img.width == item_width and img.height == item_height for img in images):
1005
+ raise ImageException('Images do not have same dimensions.')
1006
+
1007
+ if max_depth is None:
1008
+ max_depth = len(images)
1009
+
1010
+ tex_id = GLuint()
1011
+ target = texture_map[TextureType.TYPE_2D_ARRAY]
1012
+ ctx.glGenTextures(1, byref(tex_id))
1013
+ ctx.glBindTexture(target, tex_id.value)
1014
+ texture = cls(ctx, item_width, item_height, tex_id.value, max_depth, internal_format,
1015
+ internal_format_size, internal_format_type, filters, address_mode, anisotropic_level)
1016
+ ctx.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, texture._gl_min_filter)
1017
+ ctx.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, texture._gl_mag_filter)
1018
+
1019
+ base_image = images[0]
1020
+ if base_image.anchor_x or base_image.anchor_y:
1021
+ texture.anchor_x = base_image.anchor_x
1022
+ texture.anchor_y = base_image.anchor_y
1023
+
1024
+ texture.images = len(images)
1025
+
1026
+ size = (texture.width * texture.height * texture.images * len(internal_format))
1027
+ data = (GLubyte * size)()
1028
+ texture._allocate(data)
1029
+
1030
+ items = []
1031
+ for i, image in enumerate(images):
1032
+ item = cls.region_class(0, 0, i, item_width, item_height, texture)
1033
+ items.append(item)
1034
+ texture.upload(image, image.anchor_x, image.anchor_y, z=i)
1035
+ ctx.glFlush()
1036
+
1037
+ texture.items = items
1038
+ texture.item_width = item_width
1039
+ texture.item_height = item_height
1040
+ return texture
1041
+
1042
+ def __len__(self) -> int:
1043
+ return len(self.items)
1044
+
1045
+ def __getitem__(self, index) -> TextureArrayRegion:
1046
+ return self.items[index]
1047
+
1048
+ def __setitem__(self, index, value) -> None:
1049
+ if type(index) is slice:
1050
+ self._context.glBindTexture(self.target, self.id)
1051
+
1052
+ for old_item, image in zip(self[index], value):
1053
+ self._verify_size(image)
1054
+ item = self.region_class(0, 0, old_item.z, image.width, image.height, self)
1055
+ image.blit_to_texture(self.target, self.level, image.anchor_x, image.anchor_y, old_item.z)
1056
+ self.items[old_item.z] = item
1057
+ else:
1058
+ self._verify_size(value)
1059
+ item = self.region_class(0, 0, index, value.width, value.height, self)
1060
+ self.upload(value, value.anchor_x, value.anchor_y, index)
1061
+ self.items[index] = item
1062
+
1063
+ def __iter__(self) -> Iterator[TextureRegionBase]:
1064
+ return iter(self.items)
1065
+
1066
+
1067
+ TextureArray.region_class = TextureArrayRegion
1068
+ TextureArrayRegion.region_class = TextureArrayRegion
1069
+
1070
+
1071
+ class TextureGrid(_AbstractGrid[Union[Texture, TextureRegion]]):
1072
+ """A texture containing a regular grid of texture regions.
1073
+
1074
+ To construct, create an :py:class:`~pyglet.image.ImageGrid` first::
1075
+
1076
+ image_grid = ImageGrid(...)
1077
+ texture_grid = TextureGrid(image_grid)
1078
+
1079
+ The texture grid can be accessed as a single texture, or as a sequence
1080
+ of :py:class:`~pyglet.graphics.TextureRegion`. When accessing as a sequence, you can specify
1081
+ integer indexes, in which the images are arranged in rows from the
1082
+ bottom-left to the top-right::
1083
+
1084
+ # assume the texture_grid is 3x3:
1085
+ current_texture = texture_grid[3] # get the middle-left image
1086
+
1087
+ You can also specify tuples in the sequence methods, which are addressed
1088
+ as ``row, column``::
1089
+
1090
+ # equivalent to the previous example:
1091
+ current_texture = texture_grid[1, 0]
1092
+
1093
+ When using tuples in a slice, the returned sequence is over the
1094
+ rectangular region defined by the slice::
1095
+
1096
+ # returns center, center-right, center-top, top-right images in that
1097
+ # order:
1098
+ images = texture_grid[(1,1):]
1099
+ # equivalent to
1100
+ images = texture_grid[(1,1):(3,3)]
1101
+
1102
+ """
1103
+
1104
+ def __init__(self, texture: Texture | TextureRegion, rows: int, columns: int, item_width: int,
1105
+ item_height: int, row_padding: int = 0, column_padding: int = 0) -> None:
1106
+ """Construct a grid for the given image.
1107
+
1108
+ You can specify parameters for the grid, for example setting
1109
+ the padding between cells. Grids are always aligned to the
1110
+ bottom-left corner of the image.
1111
+
1112
+ Args:
1113
+ texture:
1114
+ A texture or region over which to construct the grid.
1115
+ rows:
1116
+ Number of rows in the grid.
1117
+ columns:
1118
+ Number of columns in the grid.
1119
+ item_width:
1120
+ Width of each column. If unspecified, is calculated such
1121
+ that the entire texture width is used.
1122
+ item_height:
1123
+ Height of each row. If unspecified, is calculated such that
1124
+ the entire texture height is used.
1125
+ row_padding:
1126
+ Pixels separating adjacent rows. The padding is only
1127
+ inserted between rows, not at the edges of the grid.
1128
+ column_padding:
1129
+ Pixels separating adjacent columns. The padding is only
1130
+ inserted between columns, not at the edges of the grid.
1131
+ """
1132
+ if isinstance(texture, TextureRegion):
1133
+ owner = texture.owner
1134
+ else:
1135
+ owner = texture
1136
+
1137
+ item_width = item_width or (texture.width - column_padding * (columns - 1)) // columns
1138
+ item_height = item_height or (texture.height - row_padding * (rows - 1)) // rows
1139
+ self.texture = owner
1140
+ super().__init__(rows, columns, item_width, item_height, row_padding, column_padding)
1141
+
1142
+ @classmethod
1143
+ def from_image_grid(cls, image_grid: ImageGrid) -> TextureGrid:
1144
+ texture = image_grid.image.get_texture()
1145
+ return cls(
1146
+ texture,
1147
+ image_grid.rows,
1148
+ image_grid.columns,
1149
+ image_grid.item_width,
1150
+ image_grid.item_height,
1151
+ image_grid.row_padding,
1152
+ image_grid.column_padding,
1153
+ )
1154
+
1155
+ def _create_item(self, x: int, y: int, width: int, height: int) -> TextureRegion:
1156
+ return self.texture.get_region(x, y, width, height)
1157
+
1158
+ def _update_item(self, existing_item: T, new_item: T) -> None:
1159
+ existing_item.upload(new_item, new_item.anchor_x, new_item.anchor_y, 0)
1160
+
1161
+ def get_texture_sequence(self) -> TextureGrid:
1162
+ return self
1163
+
1164
+ # DDS compression formats based on DirectX.
1165
+ _dxgi_to_gl_format: dict[int, int] = {
1166
+ # --- BC1 ---
1167
+ 71: GL_COMPRESSED_RGB_S3TC_DXT1_EXT, # DXGI_FORMAT_BC1_UNORM
1168
+ 72: GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, # DXGI_FORMAT_BC1_UNORM_SRGB
1169
+
1170
+ # --- BC2 ---
1171
+ 74: GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, # DXGI_FORMAT_BC2_UNORM
1172
+ 75: GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, # DXGI_FORMAT_BC2_UNORM_SRGB
1173
+
1174
+ # --- BC3 ---
1175
+ 77: GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, # DXGI_FORMAT_BC3_UNORM
1176
+ 78: GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, # DXGI_FORMAT_BC3_UNORM_SRGB
1177
+
1178
+ # --- BC4 ---
1179
+ 80: GL_COMPRESSED_RED_RGTC1, # DXGI_FORMAT_BC4_UNORM
1180
+ 81: GL_COMPRESSED_SIGNED_RED_RGTC1, # DXGI_FORMAT_BC4_SNORM
1181
+
1182
+ # --- BC5 ---
1183
+ 83: GL_COMPRESSED_RG_RGTC2, # DXGI_FORMAT_BC5_UNORM
1184
+ 84: GL_COMPRESSED_SIGNED_RG_RGTC2, # DXGI_FORMAT_BC5_SNORM
1185
+
1186
+ # --- BC6H ---
1187
+ 95: gl.GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB, # DXGI_FORMAT_BC6H_UF16
1188
+ 96: gl.GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB, # DXGI_FORMAT_BC6H_SF16
1189
+
1190
+ # --- BC7 ---
1191
+ 98: gl.GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, # DXGI_FORMAT_BC7_UNORM
1192
+ 99: gl.GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB, # DXGI_FORMAT_BC7_UNORM_SRGB
1193
+ }
1194
+
1195
+ # KTX2 compression formats based on Vulkan.
1196
+ vk_to_gl_format: dict[int, int] = {
1197
+ # --- BC1 ---
1198
+ 131: GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, # VK_FORMAT_BC1_RGBA_UNORM_BLOCK
1199
+ 132: GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, # VK_FORMAT_BC1_RGBA_SRGB_BLOCK
1200
+ # --- BC2 ---
1201
+ 133: GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, # VK_FORMAT_BC2_UNORM_BLOCK
1202
+ 134: GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, # VK_FORMAT_BC2_SRGB_BLOCK
1203
+ # --- BC3 ---
1204
+ 135: GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, # VK_FORMAT_BC3_UNORM_BLOCK
1205
+ 136: GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, # VK_FORMAT_BC3_SRGB_BLOCK
1206
+ # --- BC4 ---
1207
+ 137: GL_COMPRESSED_RED_RGTC1, # VK_FORMAT_BC4_UNORM_BLOCK
1208
+ 138: GL_COMPRESSED_SIGNED_RED_RGTC1, # VK_FORMAT_BC4_SNORM_BLOCK
1209
+ # --- BC5 ---
1210
+ 139: GL_COMPRESSED_RG_RGTC2, # VK_FORMAT_BC5_UNORM_BLOCK
1211
+ 140: GL_COMPRESSED_SIGNED_RG_RGTC2, # VK_FORMAT_BC5_SNORM_BLOCK
1212
+ # --- BC6H ---
1213
+ 141: gl.GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB, # VK_FORMAT_BC6H_UFLOAT_BLOCK
1214
+ 142: gl.GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB, # VK_FORMAT_BC6H_SFLOAT_BLOCK
1215
+ # --- BC7 ---
1216
+ 146: gl.GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, # VK_FORMAT_BC7_UNORM_BLOCK
1217
+ 147: gl.GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB, # VK_FORMAT_BC7_SRGB_BLOCK
1218
+ # --- ETC2 / EAC ---
1219
+ 74: GL_COMPRESSED_RGB8_ETC2, # VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK
1220
+ 75: gl.GL_COMPRESSED_SRGB8_ETC2, # VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK
1221
+ 76: gl.GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, # VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK
1222
+ 77: gl.GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, # VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK
1223
+ 78: gl.GL_COMPRESSED_RGBA8_ETC2_EAC, # VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK
1224
+ 79: gl.GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, # VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK
1225
+ 67: gl.GL_COMPRESSED_R11_EAC, # VK_FORMAT_EAC_R11_UNORM_BLOCK
1226
+ 68: gl.GL_COMPRESSED_SIGNED_R11_EAC, # VK_FORMAT_EAC_R11_SNORM_BLOCK
1227
+ 69: gl.GL_COMPRESSED_RG11_EAC, # VK_FORMAT_EAC_R11G11_UNORM_BLOCK
1228
+ 70: gl.GL_COMPRESSED_SIGNED_RG11_EAC, # VK_FORMAT_EAC_R11G11_SNORM_BLOCK
1229
+ # --- ASTC (LDR) ---
1230
+ 157: gl.GL_COMPRESSED_RGBA_ASTC_4x4_KHR, # VK_FORMAT_ASTC_4x4_UNORM_BLOCK
1231
+ 158: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR, # VK_FORMAT_ASTC_4x4_SRGB_BLOCK
1232
+ 159: gl.GL_COMPRESSED_RGBA_ASTC_5x4_KHR,
1233
+ 160: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR,
1234
+ 161: gl.GL_COMPRESSED_RGBA_ASTC_5x5_KHR,
1235
+ 162: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR,
1236
+ 163: gl.GL_COMPRESSED_RGBA_ASTC_6x5_KHR,
1237
+ 164: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR,
1238
+ 165: gl.GL_COMPRESSED_RGBA_ASTC_6x6_KHR,
1239
+ 166: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR,
1240
+ 167: gl.GL_COMPRESSED_RGBA_ASTC_8x5_KHR,
1241
+ 168: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR,
1242
+ 169: gl.GL_COMPRESSED_RGBA_ASTC_8x6_KHR,
1243
+ 170: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR,
1244
+ 171: gl.GL_COMPRESSED_RGBA_ASTC_8x8_KHR,
1245
+ 172: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR,
1246
+ 173: gl.GL_COMPRESSED_RGBA_ASTC_10x5_KHR,
1247
+ 174: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR,
1248
+ 175: gl.GL_COMPRESSED_RGBA_ASTC_10x6_KHR,
1249
+ 176: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR,
1250
+ 177: gl.GL_COMPRESSED_RGBA_ASTC_10x8_KHR,
1251
+ 178: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR,
1252
+ 179: gl.GL_COMPRESSED_RGBA_ASTC_10x10_KHR,
1253
+ 180: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR,
1254
+ 181: gl.GL_COMPRESSED_RGBA_ASTC_12x10_KHR,
1255
+ 182: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR,
1256
+ 183: gl.GL_COMPRESSED_RGBA_ASTC_12x12_KHR,
1257
+ 184: gl.GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR,
1258
+ }
1259
+
1260
+ _required_exts = {
1261
+ "S3TC": ["GL_EXT_texture_compression_s3tc", "GL_EXT_texture_compression_dxt1"],
1262
+ "RGTC": ["GL_EXT_texture_compression_rgtc"],
1263
+ "BPTC": ["GL_ARB_texture_compression_bptc"],
1264
+ "ETC2": ["GL_ARB_ES3_compatibility", "GL_OES_compressed_ETC2_RGB8_texture"],
1265
+ "ASTC": ["GL_KHR_texture_compression_astc_ldr"],
1266
+ }
1267
+
1268
+ def _get_format_family(gl_internal_format: int) -> str | None:
1269
+ """Get the format family of the given format to check extension support.
1270
+
1271
+ Args:
1272
+ gl_internal_format: The OpenGL enum format.
1273
+ EX: GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
1274
+ """
1275
+ if 33776 <= gl_internal_format <= 33780:
1276
+ return "S3TC"
1277
+ if 36283 <= gl_internal_format <= 36290:
1278
+ return "RGTC"
1279
+ if 36492 <= gl_internal_format <= 36495:
1280
+ return "BPTC"
1281
+ if 37492 <= gl_internal_format <= 37499:
1282
+ return "ETC2"
1283
+ if 37808 <= gl_internal_format <= 37855:
1284
+ return "ASTC"
1285
+ return None
1286
+
1287
+ def _get_extension_from_compression(cf: CompressionFormat):
1288
+ if cf.fmt in (b'DXT1', b'BC1 '):
1289
+ return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT if cf.alpha else GL_COMPRESSED_RGB_S3TC_DXT1_EXT
1290
+ if cf.fmt in (b'DXT3', b'BC2 '):
1291
+ return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
1292
+ if cf.fmt in (b'DXT5', b'BC3 '):
1293
+ return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
1294
+
1295
+ # Newer DDS formats:
1296
+ if cf.fmt == b'DX10' and (fmt := _dxgi_to_gl_format.get(cf.dxgi_format)):
1297
+ return fmt
1298
+
1299
+ # KTX2 formats:
1300
+ if cf.fmt == b'KTX2' and (fmt := vk_to_gl_format.get(cf.vk_format)):
1301
+ return fmt
1302
+
1303
+ def _get_gl_compression_format(cf: CompressionFormat) -> int:
1304
+ # Old formats:: dwFourCC
1305
+ if cf.fmt in (b'DXT1', b'BC1 '):
1306
+ return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT if cf.alpha else GL_COMPRESSED_RGB_S3TC_DXT1_EXT
1307
+ if cf.fmt in (b'DXT3', b'BC2 '):
1308
+ return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
1309
+ if cf.fmt in (b'DXT5', b'BC3 '):
1310
+ return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
1311
+
1312
+ # Newer DDS formats:
1313
+ if cf.fmt == b'DX10' and (fmt := _dxgi_to_gl_format.get(cf.dxgi_format)):
1314
+ return fmt
1315
+
1316
+ # KTX2 formats:
1317
+ if cf.fmt == b'KTX2' and (fmt := vk_to_gl_format.get(cf.vk_format)):
1318
+ return fmt
1319
+
1320
+ msg = f"Unsupported compression format: {cf!r}"
1321
+ raise ValueError(msg)
1322
+
1323
+ class CompressedTexture(CompressedTextureBase):
1324
+ """A compressed texture created from CompressedImageData."""
1325
+
1326
+ def __init__(self, context: OpenGLSurfaceContext, width: int, height: int,
1327
+ tex_id: int,
1328
+ compression_fmt: CompressionFormat,
1329
+ tex_type: TextureType = TextureType.TYPE_2D,
1330
+ filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
1331
+ address_mode: AddressMode = AddressMode.REPEAT, anisotropic_level: int = 0) -> None:
1332
+ super().__init__(width, height, tex_id, compression_fmt, tex_type, filters, address_mode, anisotropic_level)
1333
+ self._context = context
1334
+ self.target = texture_map[self.tex_type]
1335
+ self._gl_min_filter = texture_map[self.min_filter]
1336
+ self._gl_mag_filter = texture_map[self.mag_filter]
1337
+ self._gl_internal_format = _get_gl_compression_format(compression_fmt)
1338
+ self._compression_fmt = compression_fmt
1339
+ self._can_gpu_decode = self._is_format_supported()
1340
+ self.mipmap_data = []
1341
+
1342
+ def _is_format_supported(self) -> bool:
1343
+ family = _get_format_family(self._gl_internal_format)
1344
+ if not family:
1345
+ msg = f"Unsupported compression format: {self._compression_fmt}"
1346
+ raise ValueError(msg)
1347
+
1348
+ required = _required_exts.get(family, [])
1349
+ return any(self._context.core.have_extension(ext) for ext in required)
1350
+
1351
+ @classmethod
1352
+ def create(cls, width: int, height: int,
1353
+ fmt: CompressionFormat,
1354
+ tex_type: TextureType = TextureType.TYPE_2D,
1355
+ internal_format: ComponentFormat = ComponentFormat.RGBA,
1356
+ internal_format_size: int = 8,
1357
+ internal_format_type: str = "B",
1358
+ filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
1359
+ address_mode: AddressMode = AddressMode.REPEAT,
1360
+ anisotropic_level: int = 0,
1361
+ blank_data: bool = True,
1362
+ context: OpenGLSurfaceContext | None = None) -> CompressedTexture:
1363
+ ctx = context or pyglet.graphics.api.core.current_context
1364
+
1365
+ tex_id = GLuint()
1366
+ print("tex_type", tex_type)
1367
+ target = texture_map[tex_type]
1368
+ ctx.glGenTextures(1, byref(tex_id))
1369
+
1370
+ texture = cls(ctx, width, height, tex_id.value, fmt, tex_type, filters, address_mode, anisotropic_level)
1371
+ ctx.glBindTexture(target, tex_id.value)
1372
+ ctx.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, texture._gl_min_filter)
1373
+ ctx.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, texture._gl_mag_filter)
1374
+
1375
+ data = (GLubyte * (width * height * len(internal_format)))() if blank_data else None
1376
+ texture._allocate(data)
1377
+ return texture
1378
+
1379
+
1380
+ @classmethod
1381
+ def create_from_image(cls,
1382
+ image_data: CompressedImageData,
1383
+ tex_type: TextureType = TextureType.TYPE_2D,
1384
+ filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
1385
+ address_mode: AddressMode = AddressMode.REPEAT,
1386
+ anisotropic_level: int = 0,
1387
+ context: OpenGLSurfaceContext | None = None,
1388
+ ) -> CompressedTexture:
1389
+ """Create a Texture from image data.
1390
+
1391
+ Args:
1392
+ image_data:
1393
+ The image instance.
1394
+ tex_type:
1395
+ The texture enum type.
1396
+ internal_format_size:
1397
+ Byte size of the internal format.
1398
+ filters:
1399
+ Texture format filter, passed as a list of min/mag filters, or a single filter to apply both.
1400
+ address_mode:
1401
+ Texture address mode.
1402
+ anisotropic_level:
1403
+ The maximum anisotropic level.
1404
+ context:
1405
+ A specific OpenGL Surface context, otherwise the current active context.
1406
+
1407
+ Returns:
1408
+ A currently bound texture.
1409
+ """
1410
+ ctx = context or pyglet.graphics.api.core.current_context
1411
+
1412
+ tex_id = GLuint()
1413
+ ctx.glGenTextures(1, byref(tex_id))
1414
+ target = texture_map[tex_type]
1415
+ ctx.glBindTexture(target, tex_id.value)
1416
+
1417
+ texture = cls(ctx, image_data.width, image_data.height, tex_id.value, image_data.fmt, tex_type,
1418
+ filters, address_mode, anisotropic_level)
1419
+
1420
+ ctx.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, texture._gl_min_filter)
1421
+ ctx.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, texture._gl_mag_filter)
1422
+ texture._allocate(image_data.data)
1423
+ return texture
1424
+
1425
+ def set_mipmap_data(self, level: int, data: bytes) -> None:
1426
+ """Set compressed image data for a mipmap level.
1427
+
1428
+ Supplied data gives a compressed image for the given mipmap level.
1429
+ This image data must be in the same format as was used in the
1430
+ constructor. The image data must also be of the correct dimensions for
1431
+ the level (i.e., width >> level, height >> level); but this is not checked.
1432
+ If *any* mipmap levels are specified, they are used; otherwise, mipmaps for
1433
+ ``mipmapped_texture`` are generated automatically.
1434
+ """
1435
+ # Extend mipmap_data list to required level
1436
+ self.mipmap_data += [None] * (level - len(self.mipmap_data))
1437
+ self.mipmap_data[level - 1] = data
1438
+
1439
+ def _allocate(self, data: None | Array) -> None:
1440
+ if self._can_gpu_decode:
1441
+ self._context.glCompressedTexImage2D(self.target, self.level,
1442
+ self._gl_internal_format,
1443
+ self.width, self.height, 0,
1444
+ len(data), data)
1445
+ elif self.decoder:
1446
+ image = self.decoder(data, self.width, self.height)
1447
+ texture = image.get_texture()
1448
+ assert texture.width == self.width
1449
+ assert texture.height == self.height
1450
+ else:
1451
+ msg = f"No extension or fallback decoder is available to decode {self}"
1452
+ raise ImageException(msg)
1453
+
1454
+ self._context.glFlush()
1455
+ #
1456
+ # def get_mipmapped_texture(self) -> TextureBase:
1457
+ # if self._current_mipmap_texture:
1458
+ # return self._current_mipmap_texture
1459
+ #
1460
+ # if not self._have_extension():
1461
+ # # TODO: mip-mapped software decoded compressed textures.
1462
+ # # For now, just return a non-mipmapped texture.
1463
+ # return self.get_texture()
1464
+ #
1465
+ # texture = TextureBase.create(self.width, self.height, GL_TEXTURE_2D, None)
1466
+ #
1467
+ # if self.anchor_x or self.anchor_y:
1468
+ # texture.anchor_x = self.anchor_x
1469
+ # texture.anchor_y = self.anchor_y
1470
+ #
1471
+ # self._context.glBindTexture(texture.target, texture.id)
1472
+ #
1473
+ # self._context.glTexParameteri(texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
1474
+ #
1475
+ # if not self.mipmap_data:
1476
+ # self._context.glGenerateMipmap(texture.target)
1477
+ #
1478
+ # self._context.glCompressedTexImage2D(texture.target, texture.level,
1479
+ # self.gl_format,
1480
+ # self.width, self.height, 0,
1481
+ # len(self.data), self.data)
1482
+ #
1483
+ # width, height = self.width, self.height
1484
+ # level = 0
1485
+ # for data in self.mipmap_data:
1486
+ # width >>= 1
1487
+ # height >>= 1
1488
+ # level += 1
1489
+ # self._context.glCompressedTexImage2D(texture.target, level, self.gl_format, width, height, 0, len(data),
1490
+ # data)
1491
+ #
1492
+ # self._context.glFlush()
1493
+ #
1494
+ # self._current_mipmap_texture = texture
1495
+ # return texture
1496
+ #
1497
+ # def blit_to_texture(self, target: int, level: int, x: int, y: int, z: int, internalformat: int = None):
1498
+ # if not self._have_extension():
1499
+ # raise ImageException(f"{self.extension} is required to decode {self}")
1500
+ #
1501
+ # # TODO: use glCompressedTexImage2D/3D if `internalformat` is specified.
1502
+ #
1503
+ # if target == GL_TEXTURE_3D:
1504
+ # self._context.glCompressedTexSubImage3D(target, level,
1505
+ # x - self.anchor_x, y - self.anchor_y, z,
1506
+ # self.width, self.height, 1,
1507
+ # self.gl_format,
1508
+ # len(self.data), self.data)
1509
+ # else:
1510
+ # self._context.glCompressedTexSubImage2D(target, level,
1511
+ # x - self.anchor_x, y - self.anchor_y,
1512
+ # self.width, self.height,
1513
+ # self.gl_format,
1514
+ # len(self.data), self.data)
1515
+
1516
+ def get_image_data(self) -> CompressedImageData:
1517
+ return self
1518
+
1519
+ def get_region(self, x: int, y: int, width: int, height: int) -> _AbstractImage:
1520
+ raise NotImplementedError(f"Not implemented for {self}")
1521
+
1522
+ def blit(self, x: int, y: int, z: int = 0) -> None:
1523
+ self.get_texture().blit(x, y, z)
1524
+
1525
+ def blit_into(self, source, x: int, y: int, z: int) -> None:
1526
+ raise NotImplementedError(f"Not implemented for {self}")