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.
- pyglet/__init__.py +67 -61
- pyglet/__init__.pyi +15 -8
- pyglet/app/__init__.py +22 -13
- pyglet/app/async_app.py +212 -0
- pyglet/app/base.py +2 -1
- pyglet/app/{xlib.py → linux.py} +3 -3
- pyglet/config/__init__.py +101 -0
- pyglet/config/gl/__init__.py +30 -0
- pyglet/config/gl/egl.py +120 -0
- pyglet/config/gl/macos.py +262 -0
- pyglet/config/gl/windows.py +267 -0
- pyglet/config/gl/x11.py +142 -0
- pyglet/customtypes.py +43 -2
- pyglet/display/__init__.py +8 -6
- pyglet/display/base.py +3 -63
- pyglet/display/cocoa.py +12 -17
- pyglet/display/emscripten.py +39 -0
- pyglet/display/headless.py +23 -30
- pyglet/display/wayland.py +157 -0
- pyglet/display/win32.py +4 -17
- pyglet/display/xlib.py +19 -27
- pyglet/display/xlib_vidmoderestore.py +2 -2
- pyglet/enums.py +183 -0
- pyglet/event.py +0 -1
- pyglet/experimental/geoshader_sprite.py +15 -13
- pyglet/experimental/hidraw.py +6 -15
- pyglet/experimental/multitexture_sprite.py +31 -19
- pyglet/experimental/particles.py +13 -35
- pyglet/font/__init__.py +251 -85
- pyglet/font/base.py +116 -61
- pyglet/font/dwrite/__init__.py +349 -204
- pyglet/font/dwrite/dwrite_lib.py +27 -5
- pyglet/font/fontconfig.py +14 -6
- pyglet/font/freetype.py +138 -87
- pyglet/font/freetype_lib.py +19 -0
- pyglet/font/group.py +179 -0
- pyglet/font/harfbuzz/__init__.py +3 -3
- pyglet/font/pyodide_js.py +310 -0
- pyglet/font/quartz.py +319 -126
- pyglet/font/ttf.py +45 -3
- pyglet/font/user.py +14 -19
- pyglet/font/win32.py +45 -21
- pyglet/graphics/__init__.py +8 -787
- pyglet/graphics/allocation.py +115 -1
- pyglet/graphics/api/__init__.py +77 -0
- pyglet/graphics/api/base.py +299 -0
- pyglet/graphics/api/gl/__init__.py +58 -0
- pyglet/graphics/api/gl/base.py +24 -0
- pyglet/graphics/{vertexbuffer.py → api/gl/buffer.py} +104 -159
- pyglet/graphics/api/gl/cocoa/context.py +76 -0
- pyglet/graphics/api/gl/context.py +391 -0
- pyglet/graphics/api/gl/default_shaders.py +0 -0
- pyglet/graphics/api/gl/draw.py +627 -0
- pyglet/graphics/api/gl/egl/__init__.py +0 -0
- pyglet/graphics/api/gl/egl/context.py +92 -0
- pyglet/graphics/api/gl/enums.py +76 -0
- pyglet/graphics/api/gl/framebuffer.py +315 -0
- pyglet/graphics/api/gl/gl.py +5463 -0
- pyglet/graphics/api/gl/gl_info.py +188 -0
- pyglet/graphics/api/gl/global_opengl.py +226 -0
- pyglet/{gl → graphics/api/gl}/lib.py +34 -18
- pyglet/graphics/api/gl/shader.py +1476 -0
- pyglet/graphics/api/gl/shapes.py +55 -0
- pyglet/graphics/api/gl/sprite.py +102 -0
- pyglet/graphics/api/gl/state.py +219 -0
- pyglet/graphics/api/gl/text.py +190 -0
- pyglet/graphics/api/gl/texture.py +1526 -0
- pyglet/graphics/{vertexarray.py → api/gl/vertexarray.py} +11 -13
- pyglet/graphics/api/gl/vertexdomain.py +751 -0
- pyglet/graphics/api/gl/win32/__init__.py +0 -0
- pyglet/graphics/api/gl/win32/context.py +108 -0
- pyglet/graphics/api/gl/win32/wgl_info.py +24 -0
- pyglet/graphics/api/gl/xlib/__init__.py +0 -0
- pyglet/graphics/api/gl/xlib/context.py +174 -0
- pyglet/{gl → graphics/api/gl/xlib}/glx_info.py +26 -31
- pyglet/graphics/api/gl1/__init__.py +0 -0
- pyglet/{gl → graphics/api/gl1}/gl_compat.py +3 -2
- pyglet/graphics/api/gl2/__init__.py +0 -0
- pyglet/graphics/api/gl2/buffer.py +320 -0
- pyglet/graphics/api/gl2/draw.py +600 -0
- pyglet/graphics/api/gl2/global_opengl.py +122 -0
- pyglet/graphics/api/gl2/shader.py +200 -0
- pyglet/graphics/api/gl2/shapes.py +51 -0
- pyglet/graphics/api/gl2/sprite.py +79 -0
- pyglet/graphics/api/gl2/text.py +175 -0
- pyglet/graphics/api/gl2/vertexdomain.py +364 -0
- pyglet/graphics/api/webgl/__init__.py +233 -0
- pyglet/graphics/api/webgl/buffer.py +302 -0
- pyglet/graphics/api/webgl/context.py +234 -0
- pyglet/graphics/api/webgl/draw.py +590 -0
- pyglet/graphics/api/webgl/enums.py +76 -0
- pyglet/graphics/api/webgl/framebuffer.py +360 -0
- pyglet/graphics/api/webgl/gl.py +1537 -0
- pyglet/graphics/api/webgl/gl_info.py +130 -0
- pyglet/graphics/api/webgl/shader.py +1346 -0
- pyglet/graphics/api/webgl/shapes.py +92 -0
- pyglet/graphics/api/webgl/sprite.py +102 -0
- pyglet/graphics/api/webgl/state.py +227 -0
- pyglet/graphics/api/webgl/text.py +187 -0
- pyglet/graphics/api/webgl/texture.py +1227 -0
- pyglet/graphics/api/webgl/vertexarray.py +54 -0
- pyglet/graphics/api/webgl/vertexdomain.py +616 -0
- pyglet/graphics/api/webgl/webgl_js.pyi +307 -0
- pyglet/{image → graphics}/atlas.py +33 -32
- pyglet/graphics/base.py +10 -0
- pyglet/graphics/buffer.py +245 -0
- pyglet/graphics/draw.py +578 -0
- pyglet/graphics/framebuffer.py +26 -0
- pyglet/graphics/instance.py +178 -69
- pyglet/graphics/shader.py +267 -1553
- pyglet/graphics/state.py +83 -0
- pyglet/graphics/texture.py +703 -0
- pyglet/graphics/vertexdomain.py +695 -538
- pyglet/gui/ninepatch.py +10 -10
- pyglet/gui/widgets.py +120 -10
- pyglet/image/__init__.py +20 -1973
- pyglet/image/animation.py +12 -12
- pyglet/image/base.py +730 -0
- pyglet/image/codecs/__init__.py +9 -0
- pyglet/image/codecs/bmp.py +53 -30
- pyglet/image/codecs/dds.py +53 -31
- pyglet/image/codecs/gdiplus.py +38 -14
- pyglet/image/codecs/gdkpixbuf2.py +0 -2
- pyglet/image/codecs/js_image.py +99 -0
- pyglet/image/codecs/ktx2.py +161 -0
- pyglet/image/codecs/pil.py +1 -1
- pyglet/image/codecs/png.py +1 -1
- pyglet/image/codecs/wic.py +11 -2
- pyglet/info.py +26 -24
- pyglet/input/__init__.py +8 -0
- pyglet/input/base.py +163 -105
- pyglet/input/controller.py +13 -19
- pyglet/input/controller_db.py +39 -24
- pyglet/input/emscripten/__init__.py +18 -0
- pyglet/input/emscripten/gamepad_js.py +397 -0
- pyglet/input/linux/__init__.py +11 -5
- pyglet/input/linux/evdev.py +10 -11
- pyglet/input/linux/x11_xinput.py +2 -2
- pyglet/input/linux/x11_xinput_tablet.py +1 -1
- pyglet/input/macos/__init__.py +7 -2
- pyglet/input/macos/darwin_gc.py +559 -0
- pyglet/input/win32/__init__.py +1 -1
- pyglet/input/win32/directinput.py +34 -29
- pyglet/input/win32/xinput.py +11 -61
- pyglet/lib.py +3 -3
- pyglet/libs/__init__.py +1 -1
- pyglet/{gl → libs/darwin}/agl.py +1 -1
- pyglet/libs/darwin/cocoapy/__init__.py +2 -2
- pyglet/libs/darwin/cocoapy/cocoahelpers.py +181 -0
- pyglet/libs/darwin/cocoapy/cocoalibs.py +31 -0
- pyglet/libs/darwin/cocoapy/cocoatypes.py +27 -0
- pyglet/libs/darwin/cocoapy/runtime.py +81 -45
- pyglet/libs/darwin/coreaudio.py +4 -4
- pyglet/{gl → libs/darwin}/lib_agl.py +9 -8
- pyglet/libs/darwin/quartzkey.py +1 -3
- pyglet/libs/egl/__init__.py +2 -0
- pyglet/libs/egl/egl_lib.py +576 -0
- pyglet/libs/egl/eglext.py +51 -5
- pyglet/libs/linux/__init__.py +0 -0
- pyglet/libs/linux/egl/__init__.py +0 -0
- pyglet/libs/linux/egl/eglext.py +22 -0
- pyglet/libs/linux/glx/__init__.py +0 -0
- pyglet/{gl → libs/linux/glx}/glx.py +13 -14
- pyglet/{gl → libs/linux/glx}/glxext_arb.py +408 -192
- pyglet/{gl → libs/linux/glx}/glxext_mesa.py +1 -1
- pyglet/{gl → libs/linux/glx}/glxext_nv.py +345 -164
- pyglet/{gl → libs/linux/glx}/lib_glx.py +3 -2
- pyglet/libs/linux/wayland/__init__.py +0 -0
- pyglet/libs/linux/wayland/client.py +1068 -0
- pyglet/libs/linux/wayland/lib_wayland.py +207 -0
- pyglet/libs/linux/wayland/wayland_egl.py +38 -0
- pyglet/libs/{wayland → linux/wayland}/xkbcommon.py +26 -0
- pyglet/libs/{x11 → linux/x11}/xf86vmode.py +4 -4
- pyglet/libs/{x11 → linux/x11}/xinerama.py +2 -2
- pyglet/libs/{x11 → linux/x11}/xinput.py +10 -10
- pyglet/libs/linux/x11/xrandr.py +0 -0
- pyglet/libs/{x11 → linux/x11}/xrender.py +1 -1
- pyglet/libs/shared/__init__.py +0 -0
- pyglet/libs/shared/spirv/__init__.py +0 -0
- pyglet/libs/shared/spirv/lib_shaderc.py +85 -0
- pyglet/libs/shared/spirv/lib_spirv_cross.py +126 -0
- pyglet/libs/win32/__init__.py +27 -5
- pyglet/libs/win32/constants.py +59 -48
- pyglet/libs/win32/context_managers.py +20 -3
- pyglet/libs/win32/dinput.py +105 -88
- pyglet/{gl → libs/win32}/lib_wgl.py +52 -26
- pyglet/libs/win32/types.py +58 -23
- pyglet/{gl → libs/win32}/wgl.py +32 -25
- pyglet/{gl → libs/win32}/wglext_arb.py +364 -2
- pyglet/media/__init__.py +9 -10
- pyglet/media/codecs/__init__.py +12 -1
- pyglet/media/codecs/base.py +99 -96
- pyglet/media/codecs/ffmpeg.py +2 -2
- pyglet/media/codecs/ffmpeg_lib/libavformat.py +3 -8
- pyglet/media/codecs/webaudio_pyodide.py +111 -0
- pyglet/media/drivers/__init__.py +9 -4
- pyglet/media/drivers/base.py +4 -4
- pyglet/media/drivers/openal/__init__.py +1 -1
- pyglet/media/drivers/openal/adaptation.py +3 -3
- pyglet/media/drivers/pulse/__init__.py +1 -1
- pyglet/media/drivers/pulse/adaptation.py +3 -3
- pyglet/media/drivers/pyodide_js/__init__.py +8 -0
- pyglet/media/drivers/pyodide_js/adaptation.py +288 -0
- pyglet/media/drivers/xaudio2/adaptation.py +3 -3
- pyglet/media/player.py +276 -193
- pyglet/media/player_worker_thread.py +1 -1
- pyglet/model/__init__.py +39 -29
- pyglet/model/codecs/base.py +4 -4
- pyglet/model/codecs/gltf.py +3 -3
- pyglet/model/codecs/obj.py +71 -43
- pyglet/resource.py +129 -78
- pyglet/shapes.py +147 -177
- pyglet/sprite.py +47 -164
- pyglet/text/__init__.py +44 -54
- pyglet/text/caret.py +12 -7
- pyglet/text/document.py +19 -17
- pyglet/text/formats/html.py +2 -2
- pyglet/text/formats/structured.py +10 -40
- pyglet/text/layout/__init__.py +20 -13
- pyglet/text/layout/base.py +176 -287
- pyglet/text/layout/incremental.py +9 -10
- pyglet/text/layout/scrolling.py +7 -95
- pyglet/window/__init__.py +183 -172
- pyglet/window/cocoa/__init__.py +62 -51
- pyglet/window/cocoa/pyglet_delegate.py +2 -25
- pyglet/window/cocoa/pyglet_view.py +9 -8
- pyglet/window/dialog/__init__.py +184 -0
- pyglet/window/dialog/base.py +99 -0
- pyglet/window/dialog/darwin.py +121 -0
- pyglet/window/dialog/linux.py +72 -0
- pyglet/window/dialog/windows.py +194 -0
- pyglet/window/emscripten/__init__.py +779 -0
- pyglet/window/headless/__init__.py +44 -28
- pyglet/window/key.py +2 -0
- pyglet/window/mouse.py +2 -2
- pyglet/window/wayland/__init__.py +377 -0
- pyglet/window/win32/__init__.py +101 -46
- pyglet/window/xlib/__init__.py +104 -66
- {pyglet-2.1.12.dist-info → pyglet-3.0.dev1.dist-info}/METADATA +2 -3
- pyglet-3.0.dev1.dist-info/RECORD +322 -0
- {pyglet-2.1.12.dist-info → pyglet-3.0.dev1.dist-info}/WHEEL +1 -1
- pyglet/gl/__init__.py +0 -208
- pyglet/gl/base.py +0 -499
- pyglet/gl/cocoa.py +0 -309
- pyglet/gl/gl.py +0 -4625
- pyglet/gl/gl.pyi +0 -2320
- pyglet/gl/gl_compat.pyi +0 -3097
- pyglet/gl/gl_info.py +0 -190
- pyglet/gl/headless.py +0 -166
- pyglet/gl/wgl_info.py +0 -36
- pyglet/gl/wglext_nv.py +0 -1096
- pyglet/gl/win32.py +0 -268
- pyglet/gl/xlib.py +0 -295
- pyglet/image/buffer.py +0 -274
- pyglet/image/codecs/s3tc.py +0 -354
- pyglet/libs/x11/xrandr.py +0 -166
- pyglet-2.1.12.dist-info/RECORD +0 -234
- /pyglet/{libs/wayland → graphics/api/gl/cocoa}/__init__.py +0 -0
- /pyglet/libs/{egl → linux/egl}/egl.py +0 -0
- /pyglet/libs/{egl → linux/egl}/lib.py +0 -0
- /pyglet/libs/{ioctl.py → linux/ioctl.py} +0 -0
- /pyglet/libs/{wayland → linux/wayland}/gbm.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/__init__.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/cursorfont.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/xlib.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/xsync.py +0 -0
- {pyglet-2.1.12.dist-info/licenses → pyglet-3.0.dev1.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,1227 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Callable, Iterator, Literal, Union
|
|
4
|
+
|
|
5
|
+
import js
|
|
6
|
+
import pyodide
|
|
7
|
+
|
|
8
|
+
import pyglet
|
|
9
|
+
from pyglet.enums import TextureFilter, TextureType, ComponentFormat, \
|
|
10
|
+
AddressMode
|
|
11
|
+
from pyglet.graphics.api.webgl.enums import texture_map
|
|
12
|
+
from pyglet.graphics.api.webgl.gl import (
|
|
13
|
+
GL_BGR,
|
|
14
|
+
GL_BGR_INTEGER,
|
|
15
|
+
GL_BGRA,
|
|
16
|
+
GL_BGRA_INTEGER,
|
|
17
|
+
GL_COLOR_ATTACHMENT0,
|
|
18
|
+
GL_DEPTH_COMPONENT,
|
|
19
|
+
GL_DEPTH_STENCIL,
|
|
20
|
+
GL_FRAMEBUFFER,
|
|
21
|
+
GL_FRAMEBUFFER_COMPLETE,
|
|
22
|
+
GL_LINEAR_MIPMAP_LINEAR,
|
|
23
|
+
GL_PACK_ALIGNMENT,
|
|
24
|
+
GL_READ_WRITE,
|
|
25
|
+
GL_RED,
|
|
26
|
+
GL_RED_INTEGER,
|
|
27
|
+
GL_RG,
|
|
28
|
+
GL_RG_INTEGER,
|
|
29
|
+
GL_RGB,
|
|
30
|
+
GL_RGB_INTEGER,
|
|
31
|
+
GL_RGBA,
|
|
32
|
+
GL_RGBA8, # noqa: F401
|
|
33
|
+
GL_RGBA32F,
|
|
34
|
+
GL_RGBA_INTEGER,
|
|
35
|
+
GL_TEXTURE0,
|
|
36
|
+
GL_TEXTURE_2D,
|
|
37
|
+
GL_TEXTURE_2D_ARRAY,
|
|
38
|
+
GL_TEXTURE_3D,
|
|
39
|
+
GL_TEXTURE_MAG_FILTER,
|
|
40
|
+
GL_TEXTURE_MIN_FILTER,
|
|
41
|
+
GL_UNPACK_ALIGNMENT,
|
|
42
|
+
GL_UNPACK_ROW_LENGTH,
|
|
43
|
+
GL_UNPACK_SKIP_PIXELS,
|
|
44
|
+
GL_UNPACK_SKIP_ROWS,
|
|
45
|
+
GL_UNSIGNED_BYTE,
|
|
46
|
+
)
|
|
47
|
+
from pyglet.graphics.texture import TextureBase, TextureRegionBase, UniformTextureSequence, TextureArraySizeExceeded, \
|
|
48
|
+
TextureArrayDepthExceeded
|
|
49
|
+
from pyglet.image.base import (
|
|
50
|
+
CompressedImageData,
|
|
51
|
+
ImageData,
|
|
52
|
+
ImageDataRegion,
|
|
53
|
+
ImageException,
|
|
54
|
+
ImageGrid,
|
|
55
|
+
T,
|
|
56
|
+
_AbstractGrid,
|
|
57
|
+
_AbstractImage,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# from pyglet.image.buffer import Framebuffer, Renderbuffer, get_max_color_attachments
|
|
61
|
+
|
|
62
|
+
_api_base_internal_formats = {
|
|
63
|
+
'R': 'GL_R',
|
|
64
|
+
'RG': 'GL_RG',
|
|
65
|
+
'RGB': 'GL_RGB',
|
|
66
|
+
'RGBA': 'GL_RGBA',
|
|
67
|
+
'BGR': 'GL_RGB',
|
|
68
|
+
'BGRA': 'GL_RGBA',
|
|
69
|
+
'D': 'GL_DEPTH_COMPONENT',
|
|
70
|
+
'DS': 'GL_DEPTH_STENCIL',
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_api_base_pixel_formats = {
|
|
74
|
+
'R': 'GL_RED',
|
|
75
|
+
'RG': 'GL_RG',
|
|
76
|
+
'RGB': 'GL_RGB',
|
|
77
|
+
'RGBA': 'GL_RGBA',
|
|
78
|
+
'D': 'GL_DEPTH_COMPONENT',
|
|
79
|
+
'DS': 'GL_DEPTH_STENCIL',
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _get_base_format(component_format: ComponentFormat) -> int:
|
|
84
|
+
return globals()[_api_base_pixel_formats[component_format]]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _get_internal_format(component_format: ComponentFormat, bit_size: int = 8, data_type: str = "B") -> int:
|
|
88
|
+
"""Convert our internal format class to the GL equivalent with size and type."""
|
|
89
|
+
# Base format based on components
|
|
90
|
+
base_format = _api_base_internal_formats.get(component_format.upper())
|
|
91
|
+
|
|
92
|
+
if base_format is None:
|
|
93
|
+
raise ValueError(f"Unknown format: {component_format}")
|
|
94
|
+
|
|
95
|
+
# Type suffix based on data type (integer, float, or default)
|
|
96
|
+
if data_type == "I":
|
|
97
|
+
type_suffix = "UI"
|
|
98
|
+
elif data_type == "i":
|
|
99
|
+
type_suffix = 'I'
|
|
100
|
+
elif data_type == "f":
|
|
101
|
+
type_suffix = 'F'
|
|
102
|
+
else:
|
|
103
|
+
type_suffix = '' # No suffix for unsigned normalized formats
|
|
104
|
+
|
|
105
|
+
# Construct the final GL format string.
|
|
106
|
+
# For example. Base_format: GL_RGBA, size: 32, "type": float -> GL_RGBA32F
|
|
107
|
+
gl_format = f"{base_format}{bit_size}{type_suffix}"
|
|
108
|
+
|
|
109
|
+
# Get the integer value of the GL constant using globals()
|
|
110
|
+
if gl_format in globals():
|
|
111
|
+
return globals()[gl_format]
|
|
112
|
+
raise ValueError(f"GL constant '{gl_format}' not defined.")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if TYPE_CHECKING:
|
|
116
|
+
from collections.abc import Iterator
|
|
117
|
+
from typing import Callable, Literal
|
|
118
|
+
|
|
119
|
+
from pyglet.graphics.api.webgl import OpenGLSurfaceContext
|
|
120
|
+
from pyglet.graphics.api.webgl.webgl_js import WebGL2RenderingContext
|
|
121
|
+
|
|
122
|
+
_api_pixel_formats = {
|
|
123
|
+
'R': GL_RED,
|
|
124
|
+
'RG': GL_RG,
|
|
125
|
+
'RGB': GL_RGB,
|
|
126
|
+
'BGR': GL_BGR,
|
|
127
|
+
'RGBA': GL_RGBA,
|
|
128
|
+
'BGRA': GL_BGRA,
|
|
129
|
+
'RI': GL_RED_INTEGER,
|
|
130
|
+
'RGI': GL_RG_INTEGER,
|
|
131
|
+
'RGBI': GL_RGB_INTEGER,
|
|
132
|
+
'BGRI': GL_BGR_INTEGER,
|
|
133
|
+
'RGBAI': GL_RGBA_INTEGER,
|
|
134
|
+
'BGRAI': GL_BGRA_INTEGER,
|
|
135
|
+
'D': GL_DEPTH_COMPONENT,
|
|
136
|
+
'DS': GL_DEPTH_STENCIL,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_max_texture_size() -> int:
|
|
141
|
+
"""Query the maximum texture size available"""
|
|
142
|
+
return pyglet.graphics.api.core.current_context.get_info().MAX_ARRAY_TEXTURE_LAYERS
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def get_max_array_texture_layers() -> int:
|
|
146
|
+
return pyglet.graphics.api.core.current_context.get_info().MAX_ARRAY_TEXTURE_LAYERS
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _get_gl_format_and_type(fmt: str):
|
|
150
|
+
fmt = _api_pixel_formats.get(fmt)
|
|
151
|
+
if fmt:
|
|
152
|
+
return fmt, GL_UNSIGNED_BYTE # Eventually support others through ImageData.
|
|
153
|
+
|
|
154
|
+
return None, None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class GLCompressedImageData(CompressedImageData):
|
|
158
|
+
"""Compressed image data suitable for direct uploading to GPU."""
|
|
159
|
+
|
|
160
|
+
# TODO: Finish compressed.
|
|
161
|
+
|
|
162
|
+
_current_texture = None
|
|
163
|
+
_current_mipmap_texture = None
|
|
164
|
+
|
|
165
|
+
def __init__(
|
|
166
|
+
self,
|
|
167
|
+
width: int,
|
|
168
|
+
height: int,
|
|
169
|
+
gl_format: int,
|
|
170
|
+
data: bytes,
|
|
171
|
+
extension: str | None = None,
|
|
172
|
+
decoder: Callable[[bytes, int, int], _AbstractImage] | None = None,
|
|
173
|
+
) -> None:
|
|
174
|
+
"""Construct a CompressedImageData with the given compressed data.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
width:
|
|
178
|
+
The width of the image.
|
|
179
|
+
height:
|
|
180
|
+
The height of the image.
|
|
181
|
+
gl_format:
|
|
182
|
+
GL constant giving the format of the compressed data.
|
|
183
|
+
For example: ``GL_COMPRESSED_RGBA_S3TC_DXT5_EXT``.
|
|
184
|
+
data:
|
|
185
|
+
An array of bytes containing the compressed image data.
|
|
186
|
+
extension:
|
|
187
|
+
If specified, gives the name of a GL extension to check for
|
|
188
|
+
before creating a texture.
|
|
189
|
+
decoder:
|
|
190
|
+
An optional fallback function used to decode the compressed data.
|
|
191
|
+
This function is called if the required extension is not present.
|
|
192
|
+
"""
|
|
193
|
+
super().__init__(width, height)
|
|
194
|
+
self.data = data
|
|
195
|
+
self.gl_format = gl_format
|
|
196
|
+
self.extension = extension
|
|
197
|
+
self.decoder = decoder
|
|
198
|
+
self.mipmap_data = []
|
|
199
|
+
|
|
200
|
+
def set_mipmap_data(self, level: int, data: bytes) -> None:
|
|
201
|
+
"""Set compressed image data for a mipmap level.
|
|
202
|
+
|
|
203
|
+
Supplied data gives a compressed image for the given mipmap level.
|
|
204
|
+
This image data must be in the same format as was used in the
|
|
205
|
+
constructor. The image data must also be of the correct dimensions for
|
|
206
|
+
the level (i.e., width >> level, height >> level); but this is not checked.
|
|
207
|
+
If *any* mipmap levels are specified, they are used; otherwise, mipmaps for
|
|
208
|
+
``mipmapped_texture`` are generated automatically.
|
|
209
|
+
"""
|
|
210
|
+
# Extend mipmap_data list to required level
|
|
211
|
+
self.mipmap_data += [None] * (level - len(self.mipmap_data))
|
|
212
|
+
self.mipmap_data[level - 1] = data
|
|
213
|
+
|
|
214
|
+
def _have_extension(self) -> bool:
|
|
215
|
+
from pyglet.graphics.api import core
|
|
216
|
+
|
|
217
|
+
return self.extension is None or core.have_extension(self.extension)
|
|
218
|
+
|
|
219
|
+
def get_texture(self) -> TextureBase:
|
|
220
|
+
if self._current_texture:
|
|
221
|
+
return self._current_texture
|
|
222
|
+
|
|
223
|
+
texture = TextureBase.create(self.width, self.height, blank_data=False)
|
|
224
|
+
|
|
225
|
+
if self.anchor_x or self.anchor_y:
|
|
226
|
+
texture.anchor_x = self.anchor_x
|
|
227
|
+
texture.anchor_y = self.anchor_y
|
|
228
|
+
|
|
229
|
+
gl = pyglet.graphics.api.core.current_context.gl
|
|
230
|
+
gl.bindTexture(texture.target, texture.id)
|
|
231
|
+
gl.texParameteri(texture.target, GL_TEXTURE_MIN_FILTER, texture.min_filter)
|
|
232
|
+
gl.texParameteri(texture.target, GL_TEXTURE_MAG_FILTER, texture.mag_filter)
|
|
233
|
+
|
|
234
|
+
if self._have_extension():
|
|
235
|
+
gl.compressedTexImage2D(
|
|
236
|
+
texture.target, texture.level, self.gl_format, self.width, self.height, 0, len(self.data), self.data,
|
|
237
|
+
)
|
|
238
|
+
elif self.decoder:
|
|
239
|
+
image = self.decoder(self.data, self.width, self.height)
|
|
240
|
+
texture = image.get_texture()
|
|
241
|
+
assert texture.width == self.width
|
|
242
|
+
assert texture.height == self.height
|
|
243
|
+
else:
|
|
244
|
+
msg = f"No extension or fallback decoder is available to decode {self}"
|
|
245
|
+
raise ImageException(msg)
|
|
246
|
+
|
|
247
|
+
gl.flush()
|
|
248
|
+
self._current_texture = texture
|
|
249
|
+
return texture
|
|
250
|
+
|
|
251
|
+
def get_mipmapped_texture(self) -> TextureBase:
|
|
252
|
+
if self._current_mipmap_texture:
|
|
253
|
+
return self._current_mipmap_texture
|
|
254
|
+
|
|
255
|
+
if not self._have_extension():
|
|
256
|
+
# TODO: mip-mapped software decoded compressed textures.
|
|
257
|
+
# For now, just return a non-mipmapped texture.
|
|
258
|
+
return self.get_texture()
|
|
259
|
+
|
|
260
|
+
texture = TextureBase.create(self.width, self.height, GL_TEXTURE_2D, None)
|
|
261
|
+
|
|
262
|
+
if self.anchor_x or self.anchor_y:
|
|
263
|
+
texture.anchor_x = self.anchor_x
|
|
264
|
+
texture.anchor_y = self.anchor_y
|
|
265
|
+
|
|
266
|
+
gl = pyglet.graphics.api.core.current_context.gl
|
|
267
|
+
gl.bindTexture(texture.target, texture.id)
|
|
268
|
+
|
|
269
|
+
gl.texParameteri(texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
|
|
270
|
+
|
|
271
|
+
if not self.mipmap_data:
|
|
272
|
+
gl.generateMipmap(texture.target)
|
|
273
|
+
|
|
274
|
+
gl.compressedTexImage2D(
|
|
275
|
+
texture.target, texture.level, self.gl_format, self.width, self.height, 0, len(self.data), self.data,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
width, height = self.width, self.height
|
|
279
|
+
level = 0
|
|
280
|
+
for data in self.mipmap_data:
|
|
281
|
+
width >>= 1
|
|
282
|
+
height >>= 1
|
|
283
|
+
level += 1
|
|
284
|
+
gl.compressedTexImage2D(texture.target, level, self.gl_format, width, height, 0, len(data), data)
|
|
285
|
+
|
|
286
|
+
gl.flush()
|
|
287
|
+
|
|
288
|
+
self._current_mipmap_texture = texture
|
|
289
|
+
return texture
|
|
290
|
+
|
|
291
|
+
def blit_to_texture(self, target: int, level: int, x: int, y: int, z: int, internalformat: int = None):
|
|
292
|
+
if not self._have_extension():
|
|
293
|
+
raise ImageException(f"{self.extension} is required to decode {self}")
|
|
294
|
+
|
|
295
|
+
# TODO: use glCompressedTexImage2D/3D if `internalformat` is specified.
|
|
296
|
+
|
|
297
|
+
gl = pyglet.graphics.api.core.current_context.gl
|
|
298
|
+
if target == GL_TEXTURE_3D:
|
|
299
|
+
gl.compressedTexSubImage3D(
|
|
300
|
+
target,
|
|
301
|
+
level,
|
|
302
|
+
x - self.anchor_x,
|
|
303
|
+
y - self.anchor_y,
|
|
304
|
+
z,
|
|
305
|
+
self.width,
|
|
306
|
+
self.height,
|
|
307
|
+
1,
|
|
308
|
+
self.gl_format,
|
|
309
|
+
len(self.data),
|
|
310
|
+
self.data,
|
|
311
|
+
)
|
|
312
|
+
else:
|
|
313
|
+
gl.compressedTexSubImage2D(
|
|
314
|
+
target,
|
|
315
|
+
level,
|
|
316
|
+
x - self.anchor_x,
|
|
317
|
+
y - self.anchor_y,
|
|
318
|
+
self.width,
|
|
319
|
+
self.height,
|
|
320
|
+
self.gl_format,
|
|
321
|
+
len(self.data),
|
|
322
|
+
self.data,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def get_image_data(self) -> CompressedImageData:
|
|
326
|
+
return self
|
|
327
|
+
|
|
328
|
+
def get_region(self, x: int, y: int, width: int, height: int) -> _AbstractImage:
|
|
329
|
+
raise NotImplementedError(f"Not implemented for {self}")
|
|
330
|
+
|
|
331
|
+
def blit(self, x: int, y: int, z: int = 0) -> None:
|
|
332
|
+
self.get_texture().blit(x, y, z)
|
|
333
|
+
|
|
334
|
+
def blit_into(self, source, x: int, y: int, z: int) -> None:
|
|
335
|
+
raise NotImplementedError(f"Not implemented for {self}")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _get_pixel_format(image_data: ImageData) -> tuple[int, int]:
|
|
339
|
+
"""Determine the pixel format from format string for the Graphics API."""
|
|
340
|
+
data_format = image_data.format
|
|
341
|
+
fmt, gl_type = _get_gl_format_and_type(data_format)
|
|
342
|
+
|
|
343
|
+
if fmt is None:
|
|
344
|
+
# Need to convert data to a standard form
|
|
345
|
+
data_format = {
|
|
346
|
+
1: 'R',
|
|
347
|
+
2: 'RG',
|
|
348
|
+
3: 'RGB',
|
|
349
|
+
4: 'RGBA',
|
|
350
|
+
}.get(len(data_format))
|
|
351
|
+
fmt, gl_type = _get_gl_format_and_type(data_format)
|
|
352
|
+
|
|
353
|
+
return fmt, gl_type
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class Texture(TextureBase):
|
|
357
|
+
"""An image loaded into GPU memory.
|
|
358
|
+
|
|
359
|
+
Typically, you will get an instance of Texture by accessing calling
|
|
360
|
+
the ``get_texture()`` method of any AbstractImage class (such as ImageData).
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
region_class: TextureRegion # Set to TextureRegion after it's defined
|
|
364
|
+
"""The class to use when constructing regions of this texture.
|
|
365
|
+
The class should be a subclass of TextureRegion.
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
tex_coords = (0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0)
|
|
369
|
+
"""12-tuple of float, named (u1, v1, r1, u2, v2, r2, ...).
|
|
370
|
+
``u, v, r`` give the 3D texture coordinates for vertices 1-4. The vertices
|
|
371
|
+
are specified in the order bottom-left, bottom-right, top-right and top-left.
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
tex_coords_order: tuple[int, int, int, int] = (0, 1, 2, 3)
|
|
375
|
+
"""The default vertex winding order for a quad.
|
|
376
|
+
This defaults to counter-clockwise, starting at the bottom-left.
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
# If this backend supports pixel data conversion.
|
|
380
|
+
# If False, will force data to be RGBA, even if CPU is used to order it.
|
|
381
|
+
pixel_conversion = True
|
|
382
|
+
|
|
383
|
+
level: int = 0
|
|
384
|
+
"""The mipmap level of this texture."""
|
|
385
|
+
|
|
386
|
+
images = 1
|
|
387
|
+
|
|
388
|
+
default_filters: TextureFilter | tuple[TextureFilter, TextureFilter] = TextureFilter.LINEAR, TextureFilter.LINEAR
|
|
389
|
+
"""The default minification and magnification filters, as a tuple.
|
|
390
|
+
Both default to LINEAR. If a texture is created without specifying
|
|
391
|
+
a filter, these defaults will be used.
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
x: int = 0
|
|
395
|
+
y: int = 0
|
|
396
|
+
z: int = 0
|
|
397
|
+
|
|
398
|
+
_ctx: OpenGLSurfaceContext
|
|
399
|
+
_gl: WebGL2RenderingContext
|
|
400
|
+
|
|
401
|
+
def __init__(self, context: OpenGLSurfaceContext, width: int, height: int, tex_id: int,
|
|
402
|
+
tex_type: TextureType = TextureType.TYPE_2D,
|
|
403
|
+
internal_format: ComponentFormat = ComponentFormat.RGBA,
|
|
404
|
+
internal_format_size: int = 8,
|
|
405
|
+
internal_format_type: str = "B",
|
|
406
|
+
filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
|
|
407
|
+
address_mode: AddressMode = AddressMode.REPEAT,
|
|
408
|
+
anisotropic_level: int = 0):
|
|
409
|
+
super().__init__(width, height, tex_id, tex_type, internal_format, internal_format_size, internal_format_type,
|
|
410
|
+
filters, address_mode, anisotropic_level)
|
|
411
|
+
self._context = context
|
|
412
|
+
self._gl = self._context.gl
|
|
413
|
+
self.target = texture_map[self.tex_type]
|
|
414
|
+
self._gl_min_filter = texture_map[self.min_filter]
|
|
415
|
+
self._gl_mag_filter = texture_map[self.mag_filter]
|
|
416
|
+
self._gl_internal_format = _get_internal_format(internal_format, internal_format_size, internal_format_type)
|
|
417
|
+
|
|
418
|
+
def delete(self) -> None:
|
|
419
|
+
"""Delete this texture and the memory it occupies.
|
|
420
|
+
|
|
421
|
+
Textures are invalid after deletion, and may no longer be used.
|
|
422
|
+
"""
|
|
423
|
+
self._gl.deleteTexture(self.id)
|
|
424
|
+
self.id = None
|
|
425
|
+
|
|
426
|
+
def __del__(self):
|
|
427
|
+
if self.id is not None:
|
|
428
|
+
try:
|
|
429
|
+
self._context.delete_texture(self.id)
|
|
430
|
+
self.id = None
|
|
431
|
+
except (AttributeError, ImportError):
|
|
432
|
+
pass # Interpreter is shutting down
|
|
433
|
+
|
|
434
|
+
def bind(self, texture_unit: int = 0) -> None:
|
|
435
|
+
"""Bind to a specific Texture Unit by number."""
|
|
436
|
+
self._gl.activeTexture(GL_TEXTURE0 + texture_unit)
|
|
437
|
+
self._gl.bindTexture(self.target, self.id)
|
|
438
|
+
|
|
439
|
+
def bind_image_texture(
|
|
440
|
+
self,
|
|
441
|
+
unit: int,
|
|
442
|
+
level: int = 0,
|
|
443
|
+
layered: bool = False,
|
|
444
|
+
layer: int = 0,
|
|
445
|
+
access: int = GL_READ_WRITE,
|
|
446
|
+
fmt: int = GL_RGBA32F,
|
|
447
|
+
):
|
|
448
|
+
"""Bind as an ImageTexture for use with a :py:class:`~pyglet.shader.ComputeShaderProgram`.
|
|
449
|
+
|
|
450
|
+
.. note:: OpenGL 4.3, or 4.2 with the GL_ARB_compute_shader extention is required.
|
|
451
|
+
"""
|
|
452
|
+
raise NotImplementedError("Not supported.")
|
|
453
|
+
|
|
454
|
+
@staticmethod
|
|
455
|
+
def _get_image_alignment(image_data: ImageData) -> tuple[int, int]:
|
|
456
|
+
"""Image alignment and row length information on an Image to upload.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
image_data: The image data to get the alignment from.
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
(align, row_length)
|
|
463
|
+
align: 1, 2, 4, or 8 \
|
|
464
|
+
row_length: 0 if tightly packed.
|
|
465
|
+
"""
|
|
466
|
+
components = len(image_data.format)
|
|
467
|
+
width = image_data.width
|
|
468
|
+
packed_row_bytes = width * components
|
|
469
|
+
pitch = abs(image_data._current_pitch)
|
|
470
|
+
if packed_row_bytes % 8 == 0:
|
|
471
|
+
align = 8
|
|
472
|
+
elif packed_row_bytes % 4 == 0:
|
|
473
|
+
align = 4
|
|
474
|
+
elif packed_row_bytes % 2 == 0:
|
|
475
|
+
align = 2
|
|
476
|
+
else:
|
|
477
|
+
align = 1
|
|
478
|
+
|
|
479
|
+
if pitch == packed_row_bytes:
|
|
480
|
+
row_length = 0
|
|
481
|
+
else:
|
|
482
|
+
row_length = pitch // components
|
|
483
|
+
|
|
484
|
+
return align, row_length
|
|
485
|
+
@classmethod
|
|
486
|
+
def create_from_image(cls,
|
|
487
|
+
image_data: ImageData | ImageDataRegion,
|
|
488
|
+
tex_type: TextureType = TextureType.TYPE_2D,
|
|
489
|
+
internal_format_size: int = 8,
|
|
490
|
+
filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
|
|
491
|
+
address_mode: AddressMode = AddressMode.REPEAT,
|
|
492
|
+
anisotropic_level: int = 0,
|
|
493
|
+
context: OpenGLSurfaceContext | None = None,
|
|
494
|
+
) -> Texture:
|
|
495
|
+
"""Create a Texture from image data.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
image_data:
|
|
499
|
+
The image instance.
|
|
500
|
+
tex_type:
|
|
501
|
+
The texture enum type.
|
|
502
|
+
internal_format_size:
|
|
503
|
+
Byte size of the internal format.
|
|
504
|
+
filters:
|
|
505
|
+
Texture format filter, passed as a list of min/mag filters, or a single filter to apply both.
|
|
506
|
+
address_mode:
|
|
507
|
+
Texture address mode.
|
|
508
|
+
anisotropic_level:
|
|
509
|
+
The maximum anisotropic level.
|
|
510
|
+
context:
|
|
511
|
+
A specific OpenGL Surface context, otherwise the current active context.
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
A currently bound texture.
|
|
515
|
+
"""
|
|
516
|
+
ctx = context or pyglet.graphics.api.core.current_context
|
|
517
|
+
gl = pyglet.graphics.api.core.current_context.gl
|
|
518
|
+
|
|
519
|
+
tex_id = gl.createTexture()
|
|
520
|
+
target = texture_map[tex_type]
|
|
521
|
+
gl.bindTexture(target, tex_id)
|
|
522
|
+
|
|
523
|
+
texture = cls(ctx, image_data.width, image_data.height, tex_id, tex_type,
|
|
524
|
+
ComponentFormat(image_data.format), internal_format_size, image_data.data_type, filters,
|
|
525
|
+
address_mode, anisotropic_level)
|
|
526
|
+
|
|
527
|
+
gl.texParameteri(target, GL_TEXTURE_MIN_FILTER, texture._gl_min_filter)
|
|
528
|
+
gl.texParameteri(target, GL_TEXTURE_MAG_FILTER, texture._gl_mag_filter)
|
|
529
|
+
|
|
530
|
+
pixel_fmt = image_data.format
|
|
531
|
+
image_bytes = image_data.get_bytes(pixel_fmt, image_data.width * len(pixel_fmt))
|
|
532
|
+
buffer = pyodide.ffi.to_js(memoryview(image_bytes))
|
|
533
|
+
js_array = js.Uint8Array.new(buffer)
|
|
534
|
+
gl_pfmt, gl_type = _get_pixel_format(image_data)
|
|
535
|
+
|
|
536
|
+
align, row_length = texture._get_image_alignment(image_data)
|
|
537
|
+
|
|
538
|
+
gl.pixelStorei(GL_PACK_ALIGNMENT, align)
|
|
539
|
+
gl.pixelStorei(GL_UNPACK_ROW_LENGTH, row_length)
|
|
540
|
+
|
|
541
|
+
gl.texImage2D(
|
|
542
|
+
target,
|
|
543
|
+
0,
|
|
544
|
+
texture._gl_internal_format,
|
|
545
|
+
image_data.width,
|
|
546
|
+
image_data.height,
|
|
547
|
+
0,
|
|
548
|
+
gl_pfmt,
|
|
549
|
+
gl_type,
|
|
550
|
+
js_array,
|
|
551
|
+
)
|
|
552
|
+
gl.flush()
|
|
553
|
+
return texture
|
|
554
|
+
|
|
555
|
+
@classmethod
|
|
556
|
+
def create(cls, width: int, height: int,
|
|
557
|
+
tex_type: TextureType = TextureType.TYPE_2D,
|
|
558
|
+
internal_format: ComponentFormat = ComponentFormat.RGBA,
|
|
559
|
+
internal_format_size: int = 8,
|
|
560
|
+
internal_format_type: str = "B",
|
|
561
|
+
filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
|
|
562
|
+
address_mode: AddressMode = AddressMode.REPEAT,
|
|
563
|
+
anisotropic_level: int = 0,
|
|
564
|
+
blank_data: bool = True, context: OpenGLSurfaceContext | None = None) -> TextureBase:
|
|
565
|
+
"""Create a Texture.
|
|
566
|
+
|
|
567
|
+
Create a Texture with the specified dimensions, and attributes.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
width:
|
|
571
|
+
Width of texture in pixels.
|
|
572
|
+
height:
|
|
573
|
+
Height of texture in pixels.
|
|
574
|
+
tex_type:
|
|
575
|
+
The texture enum type.
|
|
576
|
+
internal_format:
|
|
577
|
+
Component format of the image data.
|
|
578
|
+
internal_format_size:
|
|
579
|
+
Byte size of the internal format.
|
|
580
|
+
internal_format_type:
|
|
581
|
+
Internal format type in struct format.
|
|
582
|
+
filters:
|
|
583
|
+
Texture format filter, passed as a list of min/mag filter or a single filter to apply both.
|
|
584
|
+
address_mode:
|
|
585
|
+
Texture address mode.
|
|
586
|
+
anisotropic_level:
|
|
587
|
+
The maximum anisotropic level.
|
|
588
|
+
blank_data:
|
|
589
|
+
If True, initialize the texture data with all zeros. If False, do not pass initial data.
|
|
590
|
+
context:
|
|
591
|
+
A specific OpenGL Surface context, otherwise the current active context.
|
|
592
|
+
|
|
593
|
+
Returns:
|
|
594
|
+
A currently bound texture.
|
|
595
|
+
"""
|
|
596
|
+
ctx = context or pyglet.graphics.api.core.current_context
|
|
597
|
+
gl = ctx.gl
|
|
598
|
+
|
|
599
|
+
tex_id = gl.createTexture()
|
|
600
|
+
target = texture_map[tex_type]
|
|
601
|
+
|
|
602
|
+
texture = cls(ctx, width, height, tex_id, tex_type, internal_format, internal_format_size, internal_format_type, filters, address_mode, anisotropic_level)
|
|
603
|
+
gl.bindTexture(target, tex_id)
|
|
604
|
+
|
|
605
|
+
gl.texParameteri(target, GL_TEXTURE_MIN_FILTER, texture._gl_min_filter)
|
|
606
|
+
gl.texParameteri(target, GL_TEXTURE_MAG_FILTER, texture._gl_mag_filter)
|
|
607
|
+
|
|
608
|
+
data = js.Uint8Array.new(width * height * len(internal_format)) if blank_data else None
|
|
609
|
+
texture._allocate(data)
|
|
610
|
+
return texture
|
|
611
|
+
|
|
612
|
+
def _allocate(self, data: None | js.Uint8Array) -> None:
|
|
613
|
+
self._gl.texImage2D(self.target, 0,
|
|
614
|
+
self._gl_internal_format,
|
|
615
|
+
self.width, self.height,
|
|
616
|
+
0,
|
|
617
|
+
_get_base_format(self.internal_format),
|
|
618
|
+
GL_UNSIGNED_BYTE,
|
|
619
|
+
data)
|
|
620
|
+
self._gl.flush()
|
|
621
|
+
|
|
622
|
+
def _attach_texture_to_fbo(self, z: int = 0) -> None:
|
|
623
|
+
self._gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.id, self.level)
|
|
624
|
+
|
|
625
|
+
def fetch(self, z: int = 0) -> ImageData:
|
|
626
|
+
"""Fetch the image data of this texture from the GPU.
|
|
627
|
+
|
|
628
|
+
Bind the texture, and read the pixel data back from the GPU.
|
|
629
|
+
This can be a somewhat costly operation.
|
|
630
|
+
Modifying the returned ImageData object has no effect on the
|
|
631
|
+
texture itself. Uploading ImageData back to the GPU/texture
|
|
632
|
+
can be done with the :py:meth:`~Texture.upload` method.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
z:
|
|
636
|
+
For 3D textures, the image slice to retrieve.
|
|
637
|
+
"""
|
|
638
|
+
self._gl.bindTexture(self.target, self.id)
|
|
639
|
+
|
|
640
|
+
# Always extract complete RGBA data. Could check internalformat
|
|
641
|
+
# to only extract used channels. XXX
|
|
642
|
+
fmt = 'RGBA'
|
|
643
|
+
gl_format = GL_RGBA
|
|
644
|
+
|
|
645
|
+
buffer_size = self.width * self.height * self.images * len(fmt)
|
|
646
|
+
|
|
647
|
+
self._gl.pixelStorei(GL_PACK_ALIGNMENT, 1)
|
|
648
|
+
fbo = self._gl.createFramebuffer()
|
|
649
|
+
self._gl.bindFramebuffer(GL_FRAMEBUFFER, fbo)
|
|
650
|
+
self._attach_texture_to_fbo(z)
|
|
651
|
+
|
|
652
|
+
if self._gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE:
|
|
653
|
+
raise Exception("Framebuffer is incomplete.")
|
|
654
|
+
|
|
655
|
+
pixel_buf = js.Uint8Array(buffer_size)
|
|
656
|
+
self._gl.readPixels(0, 0, self.width, self.height, gl_format, GL_UNSIGNED_BYTE, pixel_buf)
|
|
657
|
+
self._gl.bindFramebuffer(GL_FRAMEBUFFER, None)
|
|
658
|
+
self._gl.deleteFramebuffer(fbo)
|
|
659
|
+
|
|
660
|
+
data = ImageData(self.width, self.height, fmt, pixel_buf)
|
|
661
|
+
return data
|
|
662
|
+
|
|
663
|
+
def get_image_data(self, z: int = 0) -> ImageData:
|
|
664
|
+
"""Get the image data of this texture.
|
|
665
|
+
|
|
666
|
+
Bind the texture, and read the pixel data back from the GPU.
|
|
667
|
+
This can be a somewhat costly operation.
|
|
668
|
+
Modifying the returned ImageData object has no effect on the
|
|
669
|
+
texture itself. Uploading ImageData back to the GPU/texture
|
|
670
|
+
can be done with the :py:meth:`~Texture.upload` method.
|
|
671
|
+
|
|
672
|
+
Args:
|
|
673
|
+
z:
|
|
674
|
+
For 3D textures, the image slice to retrieve.
|
|
675
|
+
"""
|
|
676
|
+
return self.fetch(z)
|
|
677
|
+
|
|
678
|
+
def _apply_region_unpack(self, image_data: ImageData | ImageDataRegion) -> None:
|
|
679
|
+
if isinstance(image_data, ImageDataRegion):
|
|
680
|
+
self._gl.pixelStorei(GL_UNPACK_SKIP_PIXELS, image_data.x)
|
|
681
|
+
self._gl.pixelStorei(GL_UNPACK_SKIP_ROWS, image_data.y)
|
|
682
|
+
|
|
683
|
+
def _default_region_unpack(self, image_data: ImageData | ImageDataRegion) -> None:
|
|
684
|
+
if isinstance(image_data, ImageDataRegion):
|
|
685
|
+
self._gl.pixelStorei(GL_UNPACK_SKIP_PIXELS, 0)
|
|
686
|
+
self._gl.pixelStorei(GL_UNPACK_SKIP_ROWS, 0)
|
|
687
|
+
|
|
688
|
+
def upload(self, image_data: ImageData | ImageDataRegion, x: int, y: int, z: int) -> None:
|
|
689
|
+
"""Upload image data into the Texture at specific coordinates.
|
|
690
|
+
|
|
691
|
+
You must have this texture bound before uploading data.
|
|
692
|
+
|
|
693
|
+
The image's anchor point will be aligned to the given ``x`` and ``y``
|
|
694
|
+
coordinates. If this texture is a 3D texture, the ``z``
|
|
695
|
+
parameter gives the image slice to blit into.
|
|
696
|
+
"""
|
|
697
|
+
x -= self.anchor_x
|
|
698
|
+
y -= self.anchor_y
|
|
699
|
+
|
|
700
|
+
data_format = image_data.format
|
|
701
|
+
data_pitch = abs(image_data._current_pitch)
|
|
702
|
+
|
|
703
|
+
fmt, gl_type = _get_pixel_format(image_data)
|
|
704
|
+
|
|
705
|
+
# Get data in required format (hopefully will be the same format it's already
|
|
706
|
+
# in, unless that's an obscure format, upside-down or the driver is old).
|
|
707
|
+
data = image_data.convert(data_format, data_pitch)
|
|
708
|
+
|
|
709
|
+
js_array = js.Uint8Array.new(data)
|
|
710
|
+
|
|
711
|
+
if data_pitch & 0x1:
|
|
712
|
+
align = 1
|
|
713
|
+
elif data_pitch & 0x2:
|
|
714
|
+
align = 2
|
|
715
|
+
else:
|
|
716
|
+
align = 4
|
|
717
|
+
row_length = data_pitch // len(data_format)
|
|
718
|
+
|
|
719
|
+
gl = self._gl
|
|
720
|
+
gl.pixelStorei(GL_UNPACK_ALIGNMENT, align)
|
|
721
|
+
gl.pixelStorei(GL_UNPACK_ROW_LENGTH, row_length)
|
|
722
|
+
self._apply_region_unpack(image_data)
|
|
723
|
+
|
|
724
|
+
if self.target == GL_TEXTURE_3D or self.target == GL_TEXTURE_2D_ARRAY:
|
|
725
|
+
gl.texSubImage3D(
|
|
726
|
+
self.target, self.level, x, y, z, image_data.width, image_data.height, 1, fmt, gl_type, js_array,
|
|
727
|
+
)
|
|
728
|
+
else:
|
|
729
|
+
gl.texSubImage2D(self.target, self.level, x, y, image_data.width, image_data.height, fmt, gl_type, js_array)
|
|
730
|
+
|
|
731
|
+
gl.pixelStorei(GL_UNPACK_ROW_LENGTH, 0)
|
|
732
|
+
self._default_region_unpack(image_data)
|
|
733
|
+
|
|
734
|
+
# Flush image upload before data gets GC'd:
|
|
735
|
+
gl.flush()
|
|
736
|
+
|
|
737
|
+
def get_texture(self) -> TextureBase:
|
|
738
|
+
return self
|
|
739
|
+
|
|
740
|
+
def get_mipmapped_texture(self) -> TextureBase:
|
|
741
|
+
raise NotImplementedError(f"Not implemented for {self}.")
|
|
742
|
+
|
|
743
|
+
def get_region(self, x: int, y: int, width: int, height: int) -> TextureRegionBase:
|
|
744
|
+
return self.region_class(x, y, 0, width, height, self)
|
|
745
|
+
|
|
746
|
+
def get_transform(
|
|
747
|
+
self, flip_x: bool = False, flip_y: bool = False, rotate: Literal[0, 90, 180, 270, 360] = 0,
|
|
748
|
+
) -> TextureRegionBase:
|
|
749
|
+
"""Create a copy of this image applying a simple transformation.
|
|
750
|
+
|
|
751
|
+
The transformation is applied to the texture coordinates only;
|
|
752
|
+
:py:meth:`~pyglet.image.AbstractImage.get_image_data` will return the
|
|
753
|
+
untransformed data. The transformation is applied around the anchor point.
|
|
754
|
+
|
|
755
|
+
Args:
|
|
756
|
+
flip_x:
|
|
757
|
+
If True, the returned image will be flipped horizontally.
|
|
758
|
+
flip_y:
|
|
759
|
+
If True, the returned image will be flipped vertically.
|
|
760
|
+
rotate:
|
|
761
|
+
Degrees of clockwise rotation of the returned image. Only
|
|
762
|
+
90-degree increments are supported.
|
|
763
|
+
"""
|
|
764
|
+
transform = self.get_region(0, 0, self.width, self.height)
|
|
765
|
+
bl, br, tr, tl = 0, 1, 2, 3
|
|
766
|
+
transform.anchor_x = self.anchor_x
|
|
767
|
+
transform.anchor_y = self.anchor_y
|
|
768
|
+
if flip_x:
|
|
769
|
+
bl, br, tl, tr = br, bl, tr, tl
|
|
770
|
+
transform.anchor_x = self.width - self.anchor_x
|
|
771
|
+
if flip_y:
|
|
772
|
+
bl, br, tl, tr = tl, tr, bl, br
|
|
773
|
+
transform.anchor_y = self.height - self.anchor_y
|
|
774
|
+
rotate %= 360
|
|
775
|
+
if rotate < 0:
|
|
776
|
+
rotate += 360
|
|
777
|
+
if rotate == 0:
|
|
778
|
+
pass
|
|
779
|
+
elif rotate == 90:
|
|
780
|
+
bl, br, tr, tl = br, tr, tl, bl
|
|
781
|
+
transform.anchor_x, transform.anchor_y = transform.anchor_y, transform.width - transform.anchor_x
|
|
782
|
+
elif rotate == 180:
|
|
783
|
+
bl, br, tr, tl = tr, tl, bl, br
|
|
784
|
+
transform.anchor_x = transform.width - transform.anchor_x
|
|
785
|
+
transform.anchor_y = transform.height - transform.anchor_y
|
|
786
|
+
elif rotate == 270:
|
|
787
|
+
bl, br, tr, tl = tl, bl, br, tr
|
|
788
|
+
transform.anchor_x, transform.anchor_y = transform.height - transform.anchor_y, transform.anchor_x
|
|
789
|
+
else:
|
|
790
|
+
raise ImageException("Only 90 degree rotations are supported.")
|
|
791
|
+
if rotate in (90, 270):
|
|
792
|
+
transform.width, transform.height = transform.height, transform.width
|
|
793
|
+
transform._set_tex_coords_order(bl, br, tr, tl)
|
|
794
|
+
return transform
|
|
795
|
+
|
|
796
|
+
def _set_tex_coords_order(self, bl, br, tr, tl) -> None:
|
|
797
|
+
tex_coords = (self.tex_coords[:3], self.tex_coords[3:6], self.tex_coords[6:9], self.tex_coords[9:])
|
|
798
|
+
self.tex_coords = tex_coords[bl] + tex_coords[br] + tex_coords[tr] + tex_coords[tl]
|
|
799
|
+
|
|
800
|
+
order = self.tex_coords_order
|
|
801
|
+
self.tex_coords_order = (order[bl], order[br], order[tr], order[tl])
|
|
802
|
+
|
|
803
|
+
@property
|
|
804
|
+
def uv(self) -> tuple[float, float, float, float]:
|
|
805
|
+
"""Tuple containing the left, bottom, right, top 2D texture coordinates."""
|
|
806
|
+
tex_coords = self.tex_coords
|
|
807
|
+
return tex_coords[0], tex_coords[1], tex_coords[3], tex_coords[7]
|
|
808
|
+
|
|
809
|
+
def __repr__(self) -> str:
|
|
810
|
+
return f"{self.__class__.__name__}(id={self.id}, size={self.width}x{self.height})"
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
class TextureRegion(Texture):
|
|
814
|
+
"""A rectangular region of a texture, presented as if it were a separate texture."""
|
|
815
|
+
|
|
816
|
+
def __init__(self, x: int, y: int, z: int, width: int, height: int, owner: TextureBase):
|
|
817
|
+
super().__init__(owner._context, width, height, owner.id, owner.tex_type, owner.internal_format,
|
|
818
|
+
owner.internal_format_size, owner.internal_format_type, owner.filters, owner.address_mode,
|
|
819
|
+
owner.anisotropic_level)
|
|
820
|
+
|
|
821
|
+
self.x = x
|
|
822
|
+
self.y = y
|
|
823
|
+
self.z = z
|
|
824
|
+
self._width = width
|
|
825
|
+
self._height = height
|
|
826
|
+
self.owner = owner
|
|
827
|
+
owner_u1 = owner.tex_coords[0]
|
|
828
|
+
owner_v1 = owner.tex_coords[1]
|
|
829
|
+
owner_u2 = owner.tex_coords[3]
|
|
830
|
+
owner_v2 = owner.tex_coords[7]
|
|
831
|
+
scale_u = owner_u2 - owner_u1
|
|
832
|
+
scale_v = owner_v2 - owner_v1
|
|
833
|
+
u1 = x / owner.width * scale_u + owner_u1
|
|
834
|
+
v1 = y / owner.height * scale_v + owner_v1
|
|
835
|
+
u2 = (x + width) / owner.width * scale_u + owner_u1
|
|
836
|
+
v2 = (y + height) / owner.height * scale_v + owner_v1
|
|
837
|
+
r = z / owner.images + owner.tex_coords[2]
|
|
838
|
+
self.tex_coords = (u1, v1, r, u2, v1, r, u2, v2, r, u1, v2, r)
|
|
839
|
+
|
|
840
|
+
def fetch(self, _z: int = 0) -> ImageDataRegion:
|
|
841
|
+
image_data = self.owner.fetch(self.z)
|
|
842
|
+
return image_data.get_region(self.x, self.y, self.width, self.height)
|
|
843
|
+
|
|
844
|
+
def get_image_data(self):
|
|
845
|
+
return self.fetch()
|
|
846
|
+
|
|
847
|
+
def get_region(self, x: int, y: int, width: int, height: int) -> TextureRegionBase:
|
|
848
|
+
x += self.x
|
|
849
|
+
y += self.y
|
|
850
|
+
region = self.region_class(x, y, self.z, width, height, self.owner)
|
|
851
|
+
region._set_tex_coords_order(*self.tex_coords_order)
|
|
852
|
+
return region
|
|
853
|
+
|
|
854
|
+
def upload(self, source: _AbstractImage, x: int, y: int, z: int) -> None:
|
|
855
|
+
assert source.width <= self._width and source.height <= self._height, f"{source} is larger than {self}"
|
|
856
|
+
self.owner.blit_into(source, x + self.x, y + self.y, z + self.z)
|
|
857
|
+
|
|
858
|
+
def __repr__(self) -> str:
|
|
859
|
+
return (
|
|
860
|
+
f"{self.__class__.__name__}(id={self.id},"
|
|
861
|
+
f" size={self.width}x{self.height}, owner={self.owner.width}x{self.owner.height})"
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
def delete(self) -> None:
|
|
865
|
+
"""Deleting a TextureRegion has no effect. Operate on the owning texture instead."""
|
|
866
|
+
|
|
867
|
+
def __del__(self):
|
|
868
|
+
pass
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
Texture.region_class = TextureRegion
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
class Texture3D(Texture, UniformTextureSequence):
|
|
875
|
+
"""A texture with more than one image slice.
|
|
876
|
+
|
|
877
|
+
Use the :py:meth:`create_for_images` or :py:meth:`create_for_image_grid`
|
|
878
|
+
classmethod to construct a Texture3D.
|
|
879
|
+
"""
|
|
880
|
+
|
|
881
|
+
item_width: int = 0
|
|
882
|
+
item_height: int = 0
|
|
883
|
+
items: tuple
|
|
884
|
+
|
|
885
|
+
@classmethod
|
|
886
|
+
def create_for_images(cls, images,
|
|
887
|
+
internal_format_size: int = 8,
|
|
888
|
+
internal_format_type: str = "b",
|
|
889
|
+
filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
|
|
890
|
+
address_mode: AddressMode = AddressMode.REPEAT,
|
|
891
|
+
anisotropic_level: int = 0,
|
|
892
|
+
context: OpenGLSurfaceContext | None = None) -> Texture3D:
|
|
893
|
+
ctx = context or pyglet.graphics.api.core.current_context
|
|
894
|
+
gl = ctx.gl
|
|
895
|
+
item_width = images[0].width
|
|
896
|
+
item_height = images[0].height
|
|
897
|
+
pixel_fmt = images[0].format
|
|
898
|
+
internal_format = ComponentFormat(pixel_fmt)
|
|
899
|
+
|
|
900
|
+
if not all(img.width == item_width and img.height == item_height for img in images):
|
|
901
|
+
raise ImageException('Images do not have same dimensions.')
|
|
902
|
+
|
|
903
|
+
tex_id = gl.createTexture()
|
|
904
|
+
target = texture_map[TextureType.TYPE_3D]
|
|
905
|
+
gl.bindTexture(target, tex_id)
|
|
906
|
+
texture = cls(ctx, item_width, item_height, tex_id, TextureType.TYPE_3D, internal_format, internal_format_size,
|
|
907
|
+
internal_format_type, filters, address_mode, anisotropic_level)
|
|
908
|
+
gl.texParameteri(target, GL_TEXTURE_MIN_FILTER, texture._gl_min_filter)
|
|
909
|
+
gl.texParameteri(target, GL_TEXTURE_MAG_FILTER, texture._gl_mag_filter)
|
|
910
|
+
|
|
911
|
+
base_image = images[0]
|
|
912
|
+
if base_image.anchor_x or base_image.anchor_y:
|
|
913
|
+
texture.anchor_x = base_image.anchor_x
|
|
914
|
+
texture.anchor_y = base_image.anchor_y
|
|
915
|
+
|
|
916
|
+
texture.images = len(images)
|
|
917
|
+
|
|
918
|
+
size = (texture.width * texture.height * texture.images * len(internal_format))
|
|
919
|
+
data = js.Uint8Array.new(size)
|
|
920
|
+
texture._allocate(data)
|
|
921
|
+
|
|
922
|
+
items = []
|
|
923
|
+
for i, image in enumerate(images):
|
|
924
|
+
item = cls.region_class(0, 0, i, item_width, item_height, texture)
|
|
925
|
+
items.append(item)
|
|
926
|
+
texture.upload(image, image.anchor_x, image.anchor_y, z=i)
|
|
927
|
+
gl.flush()
|
|
928
|
+
|
|
929
|
+
texture.items = items
|
|
930
|
+
texture.item_width = item_width
|
|
931
|
+
texture.item_height = item_height
|
|
932
|
+
return texture
|
|
933
|
+
|
|
934
|
+
def _allocate(self, data: None | js.Uint8Array) -> None:
|
|
935
|
+
self._gl.texImage3D(self.target, 0,
|
|
936
|
+
self._gl_internal_format,
|
|
937
|
+
self.width, self.height, self.images,
|
|
938
|
+
0,
|
|
939
|
+
_get_base_format(self.internal_format),
|
|
940
|
+
GL_UNSIGNED_BYTE,
|
|
941
|
+
0)
|
|
942
|
+
|
|
943
|
+
def _attach_texture_to_fbo(self, z: int = 0) -> None:
|
|
944
|
+
self._gl.framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, self.id, self.level, z)
|
|
945
|
+
|
|
946
|
+
def __len__(self):
|
|
947
|
+
return len(self.items)
|
|
948
|
+
|
|
949
|
+
def __getitem__(self, index):
|
|
950
|
+
return self.items[index]
|
|
951
|
+
|
|
952
|
+
def __setitem__(self, index, value):
|
|
953
|
+
if type(index) is slice:
|
|
954
|
+
self._gl.bindTexture(self.target, self.id)
|
|
955
|
+
|
|
956
|
+
for item, image in zip(self[index], value):
|
|
957
|
+
self.upload(self, image, image.anchor_x, image.anchor_y, item.z)
|
|
958
|
+
else:
|
|
959
|
+
self.upload(value, value.anchor_x, value.anchor_y, self[index].z)
|
|
960
|
+
|
|
961
|
+
def __iter__(self) -> Iterator[TextureRegionBase]:
|
|
962
|
+
return iter(self.items)
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
class TextureArrayRegion(TextureRegion):
|
|
966
|
+
"""A region of a TextureArray, presented as if it were a separate texture."""
|
|
967
|
+
|
|
968
|
+
def __repr__(self):
|
|
969
|
+
return f"{self.__class__.__name__}(id={self.id}, size={self.width}x{self.height}, layer={self.z})"
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
class TextureArray(Texture, UniformTextureSequence):
|
|
973
|
+
items: list[TextureArrayRegion]
|
|
974
|
+
|
|
975
|
+
def __init__(self, context: OpenGLSurfaceContext, width, height, tex_id, max_depth,
|
|
976
|
+
internal_format: ComponentFormat = ComponentFormat.RGBA,
|
|
977
|
+
internal_format_size: int = 8,
|
|
978
|
+
internal_format_type: str = "b",
|
|
979
|
+
filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
|
|
980
|
+
address_mode: AddressMode = AddressMode.REPEAT,
|
|
981
|
+
anisotropic_level: int = 0):
|
|
982
|
+
super().__init__(context, width, height, tex_id, TextureType.TYPE_2D_ARRAY, internal_format, internal_format_size,
|
|
983
|
+
internal_format_type, filters, address_mode, anisotropic_level)
|
|
984
|
+
self.max_depth = max_depth
|
|
985
|
+
self.items = []
|
|
986
|
+
|
|
987
|
+
@classmethod
|
|
988
|
+
def create(cls, width: int, height: int,
|
|
989
|
+
max_depth: int = 256,
|
|
990
|
+
internal_format: ComponentFormat = ComponentFormat.RGBA,
|
|
991
|
+
internal_format_size: int = 8,
|
|
992
|
+
internal_format_type: str = "b",
|
|
993
|
+
filters: TextureFilter | tuple[TextureFilter, TextureFilter] | None = None,
|
|
994
|
+
address_mode: AddressMode = AddressMode.REPEAT,
|
|
995
|
+
anisotropic_level: int = 0,
|
|
996
|
+
context: OpenGLSurfaceContext | None = None) -> TextureArray:
|
|
997
|
+
"""Create an empty TextureArray.
|
|
998
|
+
|
|
999
|
+
You may specify the maximum depth, or layers, the Texture Array should have. This defaults
|
|
1000
|
+
to 256, but will be hardware and driver dependent.
|
|
1001
|
+
|
|
1002
|
+
Args:
|
|
1003
|
+
width:
|
|
1004
|
+
Width of the texture.
|
|
1005
|
+
height:
|
|
1006
|
+
Height of the texture.
|
|
1007
|
+
descriptor:
|
|
1008
|
+
Texture description.
|
|
1009
|
+
max_depth:
|
|
1010
|
+
The number of layers in the texture array.
|
|
1011
|
+
|
|
1012
|
+
.. versionadded:: 2.0
|
|
1013
|
+
"""
|
|
1014
|
+
ctx = pyglet.graphics.api.core.current_context or context
|
|
1015
|
+
|
|
1016
|
+
max_depth_limit = get_max_array_texture_layers()
|
|
1017
|
+
assert max_depth <= max_depth_limit, f"TextureArray max_depth supported is {max_depth_limit}."
|
|
1018
|
+
|
|
1019
|
+
gl = ctx.gl
|
|
1020
|
+
|
|
1021
|
+
tex_id = gl.createTexture()
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
texture = cls(ctx, width, height, tex_id, max_depth, internal_format, internal_format_size,
|
|
1025
|
+
internal_format_type, filters, address_mode, anisotropic_level)
|
|
1026
|
+
|
|
1027
|
+
gl.bindTexture(texture.target, tex_id)
|
|
1028
|
+
gl.texParameteri(texture.target, GL_TEXTURE_MIN_FILTER, texture._gl_min_filter)
|
|
1029
|
+
gl.texParameteri(texture.target, GL_TEXTURE_MAG_FILTER, texture._gl_mag_filter)
|
|
1030
|
+
|
|
1031
|
+
texture._allocate(None)
|
|
1032
|
+
return texture
|
|
1033
|
+
|
|
1034
|
+
def _update_subregion(self, image_data: ImageData, x: int, y: int, z: int):
|
|
1035
|
+
data_pitch = abs(image_data._current_pitch)
|
|
1036
|
+
|
|
1037
|
+
data = image_data.convert(image_data.format, data_pitch)
|
|
1038
|
+
|
|
1039
|
+
fmt, gl_type = _get_pixel_format(image_data)
|
|
1040
|
+
|
|
1041
|
+
self._gl.texSubImage3D(self.target, self.level,
|
|
1042
|
+
x, y, z,
|
|
1043
|
+
image_data.width, image_data.height, 1,
|
|
1044
|
+
fmt, gl_type,
|
|
1045
|
+
data)
|
|
1046
|
+
|
|
1047
|
+
def _attach_texture_to_fbo(self, z: int = 0) -> None:
|
|
1048
|
+
self._gl.framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, self.id, self.level, z)
|
|
1049
|
+
|
|
1050
|
+
def _allocate(self, data: None | js.Uint8Array) -> None:
|
|
1051
|
+
self._gl.texImage3D(self.target, 0,
|
|
1052
|
+
self._gl_internal_format,
|
|
1053
|
+
self.width, self.height, self.max_depth,
|
|
1054
|
+
0,
|
|
1055
|
+
_get_base_format(self.internal_format),
|
|
1056
|
+
GL_UNSIGNED_BYTE,
|
|
1057
|
+
0)
|
|
1058
|
+
|
|
1059
|
+
def _verify_size(self, image: _AbstractImage) -> None:
|
|
1060
|
+
if image.width > self.width or image.height > self.height:
|
|
1061
|
+
raise TextureArraySizeExceeded(
|
|
1062
|
+
f'Image ({image.width}x{image.height}) exceeds the size of the TextureArray ({self.width}x'
|
|
1063
|
+
f'{self.height})',
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
def add(self, image: pyglet.image.ImageData) -> TextureArrayRegion:
|
|
1067
|
+
if len(self.items) >= self.max_depth:
|
|
1068
|
+
raise TextureArrayDepthExceeded("TextureArray is full.")
|
|
1069
|
+
|
|
1070
|
+
self._verify_size(image)
|
|
1071
|
+
start_length = len(self.items)
|
|
1072
|
+
item = self.region_class(0, 0, start_length, image.width, image.height, self)
|
|
1073
|
+
|
|
1074
|
+
self.upload(image, image.anchor_x, image.anchor_y, start_length)
|
|
1075
|
+
self.items.append(item)
|
|
1076
|
+
return item
|
|
1077
|
+
|
|
1078
|
+
def allocate(self, *images: _AbstractImage) -> list[TextureArrayRegion]:
|
|
1079
|
+
"""Allocates multiple images at once."""
|
|
1080
|
+
if len(self.items) + len(images) > self.max_depth:
|
|
1081
|
+
raise TextureArrayDepthExceeded("The amount of images being added exceeds the depth of this TextureArray.")
|
|
1082
|
+
|
|
1083
|
+
self._gl.bindTexture(self.target, self.id)
|
|
1084
|
+
|
|
1085
|
+
start_length = len(self.items)
|
|
1086
|
+
for i, image in enumerate(images):
|
|
1087
|
+
self._verify_size(image)
|
|
1088
|
+
item = self.region_class(0, 0, start_length + i, image.width, image.height, self)
|
|
1089
|
+
self.items.append(item)
|
|
1090
|
+
image.blit_to_texture(self.target, self.level, image.anchor_x, image.anchor_y, start_length + i)
|
|
1091
|
+
|
|
1092
|
+
return self.items[start_length:]
|
|
1093
|
+
|
|
1094
|
+
@classmethod
|
|
1095
|
+
def create_for_image_grid(cls, grid) -> TextureArray:
|
|
1096
|
+
texture_array = cls.create(grid[0].width, grid[0].height,
|
|
1097
|
+
internal_format=ComponentFormat(grid[0].format),
|
|
1098
|
+
max_depth=len(grid))
|
|
1099
|
+
texture_array.allocate(*grid[:])
|
|
1100
|
+
return texture_array
|
|
1101
|
+
|
|
1102
|
+
def __len__(self) -> int:
|
|
1103
|
+
return len(self.items)
|
|
1104
|
+
|
|
1105
|
+
def __getitem__(self, index) -> TextureArrayRegion:
|
|
1106
|
+
return self.items[index]
|
|
1107
|
+
|
|
1108
|
+
def __setitem__(self, index, value) -> None:
|
|
1109
|
+
if type(index) is slice:
|
|
1110
|
+
self._gl.bindTexture(self.target, self.id)
|
|
1111
|
+
|
|
1112
|
+
for old_item, image in zip(self[index], value):
|
|
1113
|
+
self._verify_size(image)
|
|
1114
|
+
item = self.region_class(0, 0, old_item.z, image.width, image.height, self)
|
|
1115
|
+
image.blit_to_texture(self.target, self.level, image.anchor_x, image.anchor_y, old_item.z)
|
|
1116
|
+
self.items[old_item.z] = item
|
|
1117
|
+
else:
|
|
1118
|
+
self._verify_size(value)
|
|
1119
|
+
item = self.region_class(0, 0, index, value.width, value.height, self)
|
|
1120
|
+
self.upload(value, value.anchor_x, value.anchor_y, index)
|
|
1121
|
+
self.items[index] = item
|
|
1122
|
+
|
|
1123
|
+
def __iter__(self) -> Iterator[TextureRegionBase]:
|
|
1124
|
+
return iter(self.items)
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
TextureArray.region_class = TextureArrayRegion
|
|
1128
|
+
TextureArrayRegion.region_class = TextureArrayRegion
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
class TextureGrid(_AbstractGrid[Union[Texture, TextureRegion]]):
|
|
1132
|
+
"""A texture containing a regular grid of texture regions.
|
|
1133
|
+
|
|
1134
|
+
To construct, create an :py:class:`~pyglet.image.ImageGrid` first::
|
|
1135
|
+
|
|
1136
|
+
image_grid = ImageGrid(...)
|
|
1137
|
+
texture_grid = TextureGrid(image_grid)
|
|
1138
|
+
|
|
1139
|
+
The texture grid can be accessed as a single texture, or as a sequence
|
|
1140
|
+
of :py:class:`~pyglet.graphics.TextureRegion`. When accessing as a sequence, you can specify
|
|
1141
|
+
integer indexes, in which the images are arranged in rows from the
|
|
1142
|
+
bottom-left to the top-right::
|
|
1143
|
+
|
|
1144
|
+
# assume the texture_grid is 3x3:
|
|
1145
|
+
current_texture = texture_grid[3] # get the middle-left image
|
|
1146
|
+
|
|
1147
|
+
You can also specify tuples in the sequence methods, which are addressed
|
|
1148
|
+
as ``row, column``::
|
|
1149
|
+
|
|
1150
|
+
# equivalent to the previous example:
|
|
1151
|
+
current_texture = texture_grid[1, 0]
|
|
1152
|
+
|
|
1153
|
+
When using tuples in a slice, the returned sequence is over the
|
|
1154
|
+
rectangular region defined by the slice::
|
|
1155
|
+
|
|
1156
|
+
# returns center, center-right, center-top, top-right images in that
|
|
1157
|
+
# order:
|
|
1158
|
+
images = texture_grid[(1,1):]
|
|
1159
|
+
# equivalent to
|
|
1160
|
+
images = texture_grid[(1,1):(3,3)]
|
|
1161
|
+
|
|
1162
|
+
"""
|
|
1163
|
+
|
|
1164
|
+
def __init__(
|
|
1165
|
+
self,
|
|
1166
|
+
texture: Texture | TextureRegion,
|
|
1167
|
+
rows: int,
|
|
1168
|
+
columns: int,
|
|
1169
|
+
item_width: int,
|
|
1170
|
+
item_height: int,
|
|
1171
|
+
row_padding: int = 0,
|
|
1172
|
+
column_padding: int = 0,
|
|
1173
|
+
) -> None:
|
|
1174
|
+
"""Construct a grid for the given image.
|
|
1175
|
+
|
|
1176
|
+
You can specify parameters for the grid, for example setting
|
|
1177
|
+
the padding between cells. Grids are always aligned to the
|
|
1178
|
+
bottom-left corner of the image.
|
|
1179
|
+
|
|
1180
|
+
Args:
|
|
1181
|
+
texture:
|
|
1182
|
+
A texture or region over which to construct the grid.
|
|
1183
|
+
rows:
|
|
1184
|
+
Number of rows in the grid.
|
|
1185
|
+
columns:
|
|
1186
|
+
Number of columns in the grid.
|
|
1187
|
+
item_width:
|
|
1188
|
+
Width of each column. If unspecified, is calculated such
|
|
1189
|
+
that the entire texture width is used.
|
|
1190
|
+
item_height:
|
|
1191
|
+
Height of each row. If unspecified, is calculated such that
|
|
1192
|
+
the entire texture height is used.
|
|
1193
|
+
row_padding:
|
|
1194
|
+
Pixels separating adjacent rows. The padding is only
|
|
1195
|
+
inserted between rows, not at the edges of the grid.
|
|
1196
|
+
column_padding:
|
|
1197
|
+
Pixels separating adjacent columns. The padding is only
|
|
1198
|
+
inserted between columns, not at the edges of the grid.
|
|
1199
|
+
"""
|
|
1200
|
+
if isinstance(texture, TextureRegion):
|
|
1201
|
+
owner = texture.owner
|
|
1202
|
+
else:
|
|
1203
|
+
owner = texture
|
|
1204
|
+
|
|
1205
|
+
item_width = item_width or (texture.width - column_padding * (columns - 1)) // columns
|
|
1206
|
+
item_height = item_height or (texture.height - row_padding * (rows - 1)) // rows
|
|
1207
|
+
self.texture = owner
|
|
1208
|
+
super().__init__(rows, columns, item_width, item_height, row_padding, column_padding)
|
|
1209
|
+
|
|
1210
|
+
@classmethod
|
|
1211
|
+
def from_image_grid(cls, image_grid: ImageGrid) -> TextureGrid:
|
|
1212
|
+
texture = image_grid.image.get_texture()
|
|
1213
|
+
return cls(
|
|
1214
|
+
texture,
|
|
1215
|
+
image_grid.rows,
|
|
1216
|
+
image_grid.columns,
|
|
1217
|
+
image_grid.item_width,
|
|
1218
|
+
image_grid.item_height,
|
|
1219
|
+
image_grid.row_padding,
|
|
1220
|
+
image_grid.column_padding,
|
|
1221
|
+
)
|
|
1222
|
+
|
|
1223
|
+
def _create_item(self, x: int, y: int, width: int, height: int) -> TextureRegion:
|
|
1224
|
+
return self.texture.get_region(x, y, width, height)
|
|
1225
|
+
|
|
1226
|
+
def _update_item(self, existing_item: T, new_item: T) -> None:
|
|
1227
|
+
existing_item.upload(new_item, new_item.anchor_x, new_item.anchor_y, 0)
|