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