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
pyglet/graphics/vertexdomain.py
CHANGED
|
@@ -12,7 +12,7 @@ relation to the attribute buffers.
|
|
|
12
12
|
|
|
13
13
|
Applications can create vertices (and optionally, indices) within a domain
|
|
14
14
|
with the :py:meth:`VertexDomain.create` method. This returns a
|
|
15
|
-
:py:class:`
|
|
15
|
+
:py:class:`VertexListBase` representing the list of vertices created. The vertex
|
|
16
16
|
attribute data within the group can be modified, and the changes will be made
|
|
17
17
|
to the underlying buffers automatically.
|
|
18
18
|
|
|
@@ -22,41 +22,20 @@ primitives of the same OpenGL primitive mode.
|
|
|
22
22
|
"""
|
|
23
23
|
from __future__ import annotations
|
|
24
24
|
|
|
25
|
-
import
|
|
26
|
-
from typing import TYPE_CHECKING, Any,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
from pyglet.
|
|
31
|
-
GL_BYTE,
|
|
32
|
-
GL_DOUBLE,
|
|
33
|
-
GL_FLOAT,
|
|
34
|
-
GL_INT,
|
|
35
|
-
GL_SHORT,
|
|
36
|
-
GL_UNSIGNED_BYTE,
|
|
37
|
-
GL_UNSIGNED_INT,
|
|
38
|
-
GL_UNSIGNED_SHORT,
|
|
39
|
-
GLint,
|
|
40
|
-
GLintptr,
|
|
41
|
-
GLsizei,
|
|
42
|
-
GLvoid,
|
|
43
|
-
glDrawArrays,
|
|
44
|
-
glDrawArraysInstanced,
|
|
45
|
-
glDrawElements,
|
|
46
|
-
glDrawElementsInstanced,
|
|
47
|
-
glMultiDrawArrays,
|
|
48
|
-
glMultiDrawElements,
|
|
49
|
-
)
|
|
50
|
-
from pyglet.graphics import allocation, shader, vertexarray
|
|
51
|
-
from pyglet.graphics.vertexbuffer import AttributeBufferObject, IndexedBufferObject
|
|
52
|
-
|
|
53
|
-
CTypesDataType = Type[_SimpleCData]
|
|
54
|
-
CTypesPointer = _Pointer
|
|
25
|
+
from abc import ABC, abstractmethod
|
|
26
|
+
from typing import TYPE_CHECKING, Any, Sequence, Protocol, Iterable, NoReturn
|
|
27
|
+
|
|
28
|
+
import pyglet
|
|
29
|
+
from pyglet.graphics import allocation
|
|
30
|
+
from pyglet.graphics.shader import Attribute, AttributeView, GraphicsAttribute, DataTypeTuple
|
|
55
31
|
|
|
56
32
|
if TYPE_CHECKING:
|
|
57
|
-
from
|
|
58
|
-
from pyglet.
|
|
59
|
-
from pyglet.graphics.
|
|
33
|
+
from ctypes import Array
|
|
34
|
+
from pyglet.customtypes import DataTypes
|
|
35
|
+
from pyglet.graphics.api.base import SurfaceContext
|
|
36
|
+
from pyglet.graphics.instance import InstanceBucket, VertexInstanceBase, BaseInstanceDomain
|
|
37
|
+
from pyglet.graphics.buffer import AttributeBufferObject, IndexedBufferObject
|
|
38
|
+
from pyglet.graphics import GeometryMode, Group
|
|
60
39
|
|
|
61
40
|
|
|
62
41
|
def _nearest_pow2(v: int) -> int:
|
|
@@ -71,62 +50,41 @@ def _nearest_pow2(v: int) -> int:
|
|
|
71
50
|
return v + 1
|
|
72
51
|
|
|
73
52
|
|
|
74
|
-
_c_types = {
|
|
75
|
-
GL_BYTE: ctypes.c_byte,
|
|
76
|
-
GL_UNSIGNED_BYTE: ctypes.c_ubyte,
|
|
77
|
-
GL_SHORT: ctypes.c_short,
|
|
78
|
-
GL_UNSIGNED_SHORT: ctypes.c_ushort,
|
|
79
|
-
GL_INT: ctypes.c_int,
|
|
80
|
-
GL_UNSIGNED_INT: ctypes.c_uint,
|
|
81
|
-
GL_FLOAT: ctypes.c_float,
|
|
82
|
-
GL_DOUBLE: ctypes.c_double,
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
_gl_types = {
|
|
86
|
-
'b': GL_BYTE,
|
|
87
|
-
'B': GL_UNSIGNED_BYTE,
|
|
88
|
-
'h': GL_SHORT,
|
|
89
|
-
'H': GL_UNSIGNED_SHORT,
|
|
90
|
-
'i': GL_INT,
|
|
91
|
-
'I': GL_UNSIGNED_INT,
|
|
92
|
-
'f': GL_FLOAT,
|
|
93
|
-
'd': GL_DOUBLE,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
53
|
def _make_attribute_property(name: str) -> property:
|
|
98
|
-
def _attribute_getter(self:
|
|
54
|
+
def _attribute_getter(self: VertexListBase) -> Array[float | int]:
|
|
99
55
|
buffer = self.domain.attrib_name_buffers[name]
|
|
100
|
-
region = buffer.
|
|
101
|
-
buffer.
|
|
56
|
+
region = buffer.get_attribute_region(name, self.start, self.count)
|
|
57
|
+
buffer.invalidate_attribute_region(name, self.start, self.count)
|
|
102
58
|
return region
|
|
103
59
|
|
|
104
|
-
def _attribute_setter(self:
|
|
60
|
+
def _attribute_setter(self: VertexListBase, data: Any) -> None:
|
|
105
61
|
buffer = self.domain.attrib_name_buffers[name]
|
|
106
62
|
buffer.set_region(self.start, self.count, data)
|
|
107
63
|
|
|
108
64
|
return property(_attribute_getter, _attribute_setter)
|
|
109
65
|
|
|
110
66
|
|
|
111
|
-
class
|
|
67
|
+
class VertexListBase:
|
|
112
68
|
"""A list of vertices within a :py:class:`VertexDomain`.
|
|
113
69
|
|
|
114
70
|
Use :py:meth:`VertexDomain.create` to construct this list.
|
|
115
71
|
"""
|
|
116
72
|
count: int
|
|
117
73
|
start: int
|
|
118
|
-
domain:
|
|
74
|
+
domain: VertexDomainBase
|
|
119
75
|
indexed: bool = False
|
|
120
76
|
instanced: bool = False
|
|
121
77
|
initial_attribs: dict
|
|
122
78
|
|
|
123
|
-
def __init__(self, domain:
|
|
79
|
+
def __init__(self, domain: VertexDomainBase, group: Group, start: int, count: int) -> None: # noqa: D107
|
|
124
80
|
self.domain = domain
|
|
81
|
+
self.group = group
|
|
125
82
|
self.start = start
|
|
126
83
|
self.count = count
|
|
127
84
|
self.initial_attribs = domain.attribute_meta
|
|
85
|
+
self.bucket = None
|
|
128
86
|
|
|
129
|
-
def draw(self, mode:
|
|
87
|
+
def draw(self, mode: GeometryMode) -> None:
|
|
130
88
|
"""Draw this vertex list in the given OpenGL mode.
|
|
131
89
|
|
|
132
90
|
Args:
|
|
@@ -156,31 +114,10 @@ class VertexList:
|
|
|
156
114
|
|
|
157
115
|
def delete(self) -> None:
|
|
158
116
|
"""Delete this group."""
|
|
159
|
-
self.domain.allocator.dealloc(self.start, self.count)
|
|
160
|
-
|
|
161
|
-
def set_instance_source(self, domain: InstancedVertexDomain, instance_attributes: Sequence[str]) -> None:
|
|
162
|
-
assert self.instanced is False, "Vertex list is already an instance."
|
|
163
|
-
assert list(domain.attribute_names.keys()) == list(self.domain.attribute_names.keys()), \
|
|
164
|
-
'Domain attributes must match.'
|
|
117
|
+
self.domain.vertex_buffers.allocator.dealloc(self.start, self.count)
|
|
118
|
+
self.domain.dealloc_from_group(self)
|
|
165
119
|
|
|
166
|
-
|
|
167
|
-
for key, current_buffer in self.domain.attrib_name_buffers.items():
|
|
168
|
-
new_buffer = domain.attrib_name_buffers[key]
|
|
169
|
-
old_data = current_buffer.get_region(self.start, self.count)
|
|
170
|
-
if key in instance_attributes:
|
|
171
|
-
attrib = domain.attribute_names[key]
|
|
172
|
-
count = 1
|
|
173
|
-
old_data = old_data[:attrib.count]
|
|
174
|
-
else:
|
|
175
|
-
count = self.count
|
|
176
|
-
new_buffer.set_region(new_start, count, old_data)
|
|
177
|
-
|
|
178
|
-
self.domain.allocator.dealloc(self.start, self.count)
|
|
179
|
-
self.domain = domain
|
|
180
|
-
self.start = new_start
|
|
181
|
-
self.instanced = True
|
|
182
|
-
|
|
183
|
-
def migrate(self, domain: VertexDomain | InstancedVertexDomain) -> None:
|
|
120
|
+
def migrate(self, domain: VertexDomainBase, group: Group) -> None:
|
|
184
121
|
"""Move this group from its current domain and add to the specified one.
|
|
185
122
|
|
|
186
123
|
Attributes on domains must match.
|
|
@@ -189,275 +126,422 @@ class VertexList:
|
|
|
189
126
|
Args:
|
|
190
127
|
domain:
|
|
191
128
|
Domain to migrate this vertex list to.
|
|
192
|
-
|
|
129
|
+
group:
|
|
130
|
+
The group this vertex list belongs to.
|
|
193
131
|
"""
|
|
194
|
-
assert list(domain.attribute_names.keys()) == list(self.domain.attribute_names.keys()),
|
|
132
|
+
assert list(domain.attribute_names.keys()) == list(self.domain.attribute_names.keys()), (
|
|
195
133
|
'Domain attributes must match.'
|
|
134
|
+
)
|
|
196
135
|
|
|
197
136
|
new_start = domain.safe_alloc(self.count)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
self.domain.allocator.dealloc(self.start, self.count)
|
|
137
|
+
# Copy data to new stream.
|
|
138
|
+
self.domain.dealloc_from_group(self)
|
|
139
|
+
self.domain.vertex_buffers.copy_data(new_start, domain.vertex_buffers, self.start, self.count)
|
|
140
|
+
self.domain.vertex_buffers.allocator.dealloc(self.start, self.count)
|
|
204
141
|
self.domain = domain
|
|
205
142
|
self.start = new_start
|
|
143
|
+
domain.alloc_to_group(self, group)
|
|
144
|
+
assert self.bucket is not None
|
|
145
|
+
|
|
146
|
+
def update_group(self, group: Group) -> None:
|
|
147
|
+
current_bucket = self.bucket
|
|
148
|
+
self.domain.dealloc_from_group(self)
|
|
149
|
+
new_bucket = self.domain.alloc_to_group(self, group)
|
|
150
|
+
assert new_bucket != current_bucket, "Changing group resulted in the same bucket."
|
|
206
151
|
|
|
207
152
|
def set_attribute_data(self, name: str, data: Any) -> None:
|
|
208
|
-
|
|
209
|
-
|
|
153
|
+
stream = self.domain.attrib_name_buffers[name]
|
|
154
|
+
buffer = stream.attrib_name_buffers[name]
|
|
210
155
|
|
|
211
|
-
array_start = buffer.
|
|
212
|
-
array_end = buffer.
|
|
156
|
+
array_start = buffer.element_count * self.start
|
|
157
|
+
array_end = buffer.element_count * self.count + array_start
|
|
213
158
|
try:
|
|
214
159
|
buffer.data[array_start:array_end] = data
|
|
215
|
-
buffer.invalidate_region(self.start, count)
|
|
160
|
+
buffer.invalidate_region(self.start, self.count)
|
|
216
161
|
except ValueError:
|
|
217
162
|
msg = f"Invalid data size for '{name}'. Expected {array_end - array_start}, got {len(data)}."
|
|
218
163
|
raise ValueError(msg) from None
|
|
219
164
|
|
|
220
|
-
def add_instance(self, **kwargs: Any) -> VertexInstance:
|
|
221
|
-
assert self.instanced
|
|
222
|
-
self.domain._instances += 1 # noqa: SLF001
|
|
223
165
|
|
|
224
|
-
|
|
166
|
+
class InstanceVertexListBase(VertexListBase):
|
|
167
|
+
"""A list of vertices within an :py:class:`InstancedVertexDomain` that are not indexed."""
|
|
168
|
+
domain: InstancedVertexDomainBase
|
|
169
|
+
instanced: bool = True
|
|
225
170
|
|
|
226
|
-
|
|
171
|
+
def __init__(self, domain: VertexDomainBase, group: Group, start: int, count: int, bucket: InstanceBucket) -> None: # noqa: D107
|
|
172
|
+
super().__init__(domain, group, start, count)
|
|
173
|
+
self.instance_bucket = bucket
|
|
174
|
+
self.instance_bucket.create_instance()
|
|
227
175
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
assert attribute.name in kwargs, (f"{attribute.name} is defined as an instance attribute, "
|
|
231
|
-
f"keyword argument not found.")
|
|
232
|
-
buffer.set_region(instance_id - 1, 1, kwargs[attribute.name])
|
|
176
|
+
def create_instance(self, **attributes: Any) -> VertexInstanceBase:
|
|
177
|
+
return self.instance_bucket.create_instance(**attributes)
|
|
233
178
|
|
|
234
|
-
|
|
179
|
+
def set_attribute_data(self, name: str, data: Any) -> None:
|
|
180
|
+
if self.initial_attribs[name].fmt.is_instanced:
|
|
181
|
+
stream = self.instance_bucket.stream
|
|
182
|
+
count = 1
|
|
183
|
+
start = 0
|
|
184
|
+
else:
|
|
185
|
+
stream = self.domain.attrib_name_buffers[name]
|
|
186
|
+
count = self.count
|
|
187
|
+
start = self.start
|
|
188
|
+
buffer = stream.attrib_name_buffers[name]
|
|
235
189
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
190
|
+
array_start = buffer.element_count * start
|
|
191
|
+
array_end = buffer.element_count * count + array_start
|
|
192
|
+
try:
|
|
193
|
+
buffer.data[array_start:array_end] = data
|
|
194
|
+
buffer.invalidate_region(start, count)
|
|
195
|
+
except ValueError:
|
|
196
|
+
msg = f"Invalid data size for '{buffer}'. Expected {array_end - array_start}, got {len(data)}."
|
|
197
|
+
raise ValueError(msg) from None
|
|
241
198
|
|
|
242
|
-
self.domain._instances -= 1 # noqa: SLF001
|
|
243
199
|
|
|
244
|
-
|
|
200
|
+
class _IndexSupportBase:
|
|
201
|
+
domain: IndexedVertexDomainBase | InstancedIndexedVertexDomainBase
|
|
202
|
+
start: int
|
|
203
|
+
count: int
|
|
204
|
+
bucket: None
|
|
245
205
|
|
|
206
|
+
def migrate(self, domain: IndexedVertexDomainBase | InstancedIndexedVertexDomainBase, group: Group):
|
|
207
|
+
self.domain.dealloc_from_group(self)
|
|
208
|
+
new_start = domain.safe_alloc(self.count)
|
|
209
|
+
# Copy data to new stream.
|
|
210
|
+
self.domain.vertex_buffers.copy_data(new_start, domain.vertex_buffers, self.start, self.count)
|
|
211
|
+
self.domain.vertex_buffers.allocator.dealloc(self.start, self.count)
|
|
212
|
+
self.domain = domain
|
|
213
|
+
self.start = new_start
|
|
246
214
|
|
|
247
|
-
class
|
|
248
|
-
"""
|
|
215
|
+
class _LocalIndexSupport(_IndexSupportBase):
|
|
216
|
+
"""When BaseVertex is supported by the version, this class will be mixed in.
|
|
249
217
|
|
|
250
|
-
|
|
218
|
+
Will allow the class to use local index values instead of incrementing each mesh.
|
|
251
219
|
"""
|
|
252
|
-
|
|
253
|
-
|
|
220
|
+
__slots__: tuple[str, ...] = ()
|
|
221
|
+
|
|
222
|
+
supports_base_vertex: bool = True
|
|
254
223
|
|
|
224
|
+
domain: IndexedVertexDomainBase | InstancedIndexedVertexDomainBase
|
|
255
225
|
index_count: int
|
|
256
226
|
index_start: int
|
|
257
227
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
self.index_start = index_start
|
|
262
|
-
self.index_count = index_count
|
|
228
|
+
@property
|
|
229
|
+
def indices(self) -> list[int]:
|
|
230
|
+
return self.domain.index_stream.get_region(self.index_start, self.index_count)[:]
|
|
263
231
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
self.domain.index_allocator.dealloc(self.index_start, self.index_count)
|
|
232
|
+
@indices.setter
|
|
233
|
+
def indices(self, local: Sequence[int]) -> None: # type: ignore[override]
|
|
234
|
+
self.domain.index_stream.set_region(self.index_start, self.index_count, local)
|
|
268
235
|
|
|
269
|
-
def migrate(self, domain:
|
|
270
|
-
|
|
236
|
+
def migrate(self, domain: IndexedVertexDomainBase | InstancedIndexedVertexDomainBase, group: Group) -> None: # type: ignore[override]
|
|
237
|
+
old_dom = self.domain
|
|
238
|
+
src_idx_start = self.index_start
|
|
239
|
+
src_idx_count = self.index_count
|
|
271
240
|
|
|
272
|
-
|
|
273
|
-
to change parent state of some vertices).
|
|
241
|
+
super().migrate(domain, group)
|
|
274
242
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
Indexed domain to migrate this vertex list to.
|
|
278
|
-
"""
|
|
279
|
-
old_start = self.start
|
|
280
|
-
old_domain = self.domain
|
|
281
|
-
super().migrate(domain)
|
|
243
|
+
data = old_dom.index_stream.get_region(src_idx_start, src_idx_count)
|
|
244
|
+
old_dom.index_stream.allocator.dealloc(src_idx_start, src_idx_count)
|
|
282
245
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
old_indices = old_domain.index_buffer.get_region(self.index_start, self.index_count)
|
|
288
|
-
old_domain.index_buffer.set_region(self.index_start, self.index_count, [i + diff for i in old_indices])
|
|
246
|
+
new_idx_start = self.domain.safe_index_alloc(src_idx_count)
|
|
247
|
+
self.domain.index_stream.set_region(new_idx_start, src_idx_count, data)
|
|
248
|
+
self.index_start = new_idx_start
|
|
249
|
+
domain.alloc_to_group(self, group) # Allocate after new index start.
|
|
289
250
|
|
|
290
|
-
# copy indices to new domain
|
|
291
|
-
old_array = old_domain.index_buffer.get_region(self.index_start, self.index_count)
|
|
292
|
-
# must delloc before calling safe_index_alloc or else problems when same
|
|
293
|
-
# batch is migrated to because index_start changes after dealloc
|
|
294
|
-
old_domain.index_allocator.dealloc(self.index_start, self.index_count)
|
|
295
251
|
|
|
296
|
-
|
|
297
|
-
|
|
252
|
+
class _RunningIndexSupport(_IndexSupportBase):
|
|
253
|
+
"""Used to mixin an IndexedVertexListBase class.
|
|
298
254
|
|
|
299
|
-
|
|
255
|
+
Keeps an incrementing count for indices in the buffer.
|
|
256
|
+
"""
|
|
300
257
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
old_domain = self.domain
|
|
306
|
-
super().set_instance_source(domain, instance_attributes)
|
|
258
|
+
domain: IndexedVertexDomainBase | InstancedIndexedVertexDomainBase
|
|
259
|
+
index_count: int
|
|
260
|
+
index_start: int
|
|
261
|
+
start: int
|
|
307
262
|
|
|
308
|
-
|
|
309
|
-
|
|
263
|
+
supports_base_vertex: bool = False
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def indices(self) -> list[int]:
|
|
267
|
+
stored = self.domain.index_stream.get_region(self.index_start, self.index_count)
|
|
268
|
+
base = self.start
|
|
269
|
+
return [i - base for i in stored]
|
|
270
|
+
|
|
271
|
+
@indices.setter
|
|
272
|
+
def indices(self, local: Sequence[int]) -> None:
|
|
273
|
+
base: int = self.start
|
|
274
|
+
stored: list[int] = [i + base for i in local]
|
|
275
|
+
self.domain.index_stream.set_region(self.index_start, self.index_count, stored)
|
|
310
276
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
old_domain.index_buffer.set_region(self.index_start, self.index_count, [i + diff for i in old_indices])
|
|
277
|
+
def migrate(self, domain: IndexedVertexDomainBase | InstancedIndexedVertexDomainBase, group: Group) -> None:
|
|
278
|
+
old_dom = self.domain
|
|
279
|
+
old_start: int = self.start
|
|
280
|
+
src_idx_start = self.index_start
|
|
281
|
+
src_idx_count = self.index_count
|
|
317
282
|
|
|
318
|
-
|
|
319
|
-
old_array = old_domain.index_buffer.get_region(self.index_start, self.index_count)
|
|
320
|
-
# must delloc before calling safe_index_alloc or else problems when same
|
|
321
|
-
# batch is migrated to because index_start changes after dealloc
|
|
322
|
-
old_domain.index_allocator.dealloc(self.index_start, self.index_count)
|
|
283
|
+
super().migrate(domain, group)
|
|
323
284
|
|
|
324
|
-
|
|
325
|
-
|
|
285
|
+
data = old_dom.index_stream.get_region(src_idx_start, src_idx_count)
|
|
286
|
+
old_dom.index_stream.allocator.dealloc(src_idx_start, src_idx_count)
|
|
326
287
|
|
|
327
|
-
self.
|
|
288
|
+
delta: int = self.start - old_start
|
|
289
|
+
if delta:
|
|
290
|
+
data = [i + delta for i in data]
|
|
291
|
+
|
|
292
|
+
new_idx_start = self.domain.safe_index_alloc(src_idx_count)
|
|
293
|
+
self.domain.index_stream.set_region(new_idx_start, src_idx_count, data)
|
|
294
|
+
self.index_start = new_idx_start
|
|
295
|
+
domain.alloc_to_group(self, group) # Allocate after new index start.
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class IndexedVertexListBase(VertexListBase):
|
|
299
|
+
"""A list of vertices within an :py:class:`IndexedVertexDomainBase` that are indexed.
|
|
300
|
+
|
|
301
|
+
Use :py:meth:`IndexedVertexDomainBase.create` to construct this list.
|
|
302
|
+
"""
|
|
303
|
+
domain: IndexedVertexDomainBase
|
|
304
|
+
indexed: bool = True
|
|
305
|
+
|
|
306
|
+
index_count: int
|
|
307
|
+
index_start: int
|
|
308
|
+
|
|
309
|
+
def __init__(self, domain: IndexedVertexDomainBase, group: Group, start: int, count: int, index_start: int, # noqa: D107
|
|
310
|
+
index_count: int) -> None:
|
|
311
|
+
super().__init__(domain, group, start, count)
|
|
312
|
+
self.index_start = index_start
|
|
313
|
+
self.index_count = index_count
|
|
314
|
+
|
|
315
|
+
def delete(self) -> None:
|
|
316
|
+
"""Delete this group."""
|
|
317
|
+
super().delete()
|
|
318
|
+
self.domain.index_stream.dealloc(self.index_start, self.index_count)
|
|
328
319
|
|
|
329
320
|
@property
|
|
330
321
|
def indices(self) -> list[int]:
|
|
331
322
|
"""Array of index data."""
|
|
332
|
-
|
|
333
|
-
return [i - start for i in self.domain.index_buffer.get_region(self.index_start, self.index_count)]
|
|
323
|
+
return self.domain.index_stream.get_region(self.index_start, self.index_count)
|
|
334
324
|
|
|
335
325
|
@indices.setter
|
|
336
326
|
def indices(self, data: Sequence[int]) -> None:
|
|
337
|
-
start = self.start
|
|
338
327
|
# The vertex data is offset in the buffer, so offset the index values to match. Ex:
|
|
339
328
|
# vertex_buffer: [_, _, _, _, 1, 2, 3, 4]
|
|
340
|
-
self.domain.
|
|
329
|
+
self.domain.index_stream.set_region(self.index_start, self.index_count, data)
|
|
341
330
|
|
|
342
331
|
|
|
343
|
-
class
|
|
344
|
-
|
|
345
|
-
start: int
|
|
346
|
-
_vertex_list: VertexList | IndexedVertexList
|
|
332
|
+
class InstanceIndexedVertexListBase(VertexListBase):
|
|
333
|
+
"""A list of vertices within an :py:class:`IndexedVertexDomain` that are indexed.
|
|
347
334
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
335
|
+
Use :py:meth:`IndexedVertexDomain.create` to construct this list.
|
|
336
|
+
"""
|
|
337
|
+
domain: IndexedVertexDomainBase | InstancedIndexedVertexDomainBase
|
|
338
|
+
indexed: bool = True
|
|
339
|
+
instanced: bool = True
|
|
352
340
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
341
|
+
index_count: int
|
|
342
|
+
index_start: int
|
|
343
|
+
|
|
344
|
+
instance_bucket: InstanceBucket
|
|
345
|
+
supports_base_vertex: bool = False
|
|
346
|
+
|
|
347
|
+
def __init__(self, domain: InstancedIndexedVertexDomainBase, group: Group, start: int, count: int,
|
|
348
|
+
index_start: int, index_count: int, index_type: DataTypes, base_vertex: int,
|
|
349
|
+
instance_bucket: InstanceBucket) -> None:
|
|
350
|
+
self.index_start = index_start
|
|
351
|
+
self.index_count = index_count
|
|
352
|
+
self.index_type = index_type
|
|
353
|
+
self.base_vertex = base_vertex
|
|
354
|
+
self.instance_bucket = instance_bucket
|
|
355
|
+
self.instance_bucket.create_instance()
|
|
356
|
+
self.start_base_vertex = start if self.supports_base_vertex else 0
|
|
357
|
+
super().__init__(domain, group, start, count)
|
|
356
358
|
|
|
357
359
|
def delete(self) -> None:
|
|
358
|
-
|
|
359
|
-
|
|
360
|
+
"""Delete this group."""
|
|
361
|
+
raise Exception
|
|
362
|
+
super().delete()
|
|
363
|
+
self.domain.index_allocator.dealloc(self.index_start, self.index_count)
|
|
364
|
+
|
|
365
|
+
def migrate(self, domain: InstancedIndexedVertexDomainBase) -> None:
|
|
366
|
+
old_domain = self.domain
|
|
367
|
+
|
|
368
|
+
# Moved vertex data here.
|
|
369
|
+
super().migrate(domain)
|
|
370
|
+
|
|
371
|
+
# Remove from bucket and enter into new bucket.
|
|
372
|
+
new_bucket = domain.instance_domain.get_elements_bucket(mode=0,
|
|
373
|
+
first_index=self.index_start,
|
|
374
|
+
index_count=self.index_count,
|
|
375
|
+
index_type="I")
|
|
360
376
|
|
|
377
|
+
# Move instance data.
|
|
378
|
+
old_domain.instance_domain.move_all(self.instance_bucket, new_bucket)
|
|
361
379
|
|
|
362
|
-
|
|
380
|
+
def create_instance(self, **attributes: Any) -> None:
|
|
381
|
+
return self.instance_bucket.create_instance(**attributes)
|
|
382
|
+
|
|
383
|
+
def set_attribute_data(self, name: str, data: Any) -> None:
|
|
384
|
+
if self.initial_attribs[name].fmt.is_instanced:
|
|
385
|
+
stream = self.instance_bucket.stream
|
|
386
|
+
count = 1
|
|
387
|
+
start = 0
|
|
388
|
+
else:
|
|
389
|
+
stream = self.domain.attrib_name_buffers[name]
|
|
390
|
+
count = self.count
|
|
391
|
+
start = self.start
|
|
392
|
+
buffer = stream.attrib_name_buffers[name]
|
|
393
|
+
|
|
394
|
+
array_start = buffer.element_count * start
|
|
395
|
+
array_end = buffer.element_count * count + array_start
|
|
396
|
+
try:
|
|
397
|
+
buffer.data[array_start:array_end] = data
|
|
398
|
+
buffer.invalidate_region(start, count)
|
|
399
|
+
except ValueError:
|
|
400
|
+
msg = f"Invalid data size for '{buffer}'. Expected {array_end - array_start}, got {len(data)}."
|
|
401
|
+
raise ValueError(msg) from None
|
|
402
|
+
|
|
403
|
+
def dealloc_from_group(self, vertex_list):
|
|
404
|
+
"""Removes a vertex list from a specific state in this domain."""
|
|
405
|
+
vertex_list.bucket.remove_vertex_list(vertex_list)
|
|
406
|
+
|
|
407
|
+
class VertexDomainBase(ABC):
|
|
363
408
|
"""Management of a set of vertex lists.
|
|
364
409
|
|
|
365
410
|
Construction of a vertex domain is usually done with the
|
|
366
411
|
:py:func:`create_domain` function.
|
|
367
412
|
"""
|
|
368
413
|
|
|
369
|
-
attribute_meta: dict[str,
|
|
370
|
-
allocator: Allocator
|
|
414
|
+
attribute_meta: dict[str, Attribute]
|
|
371
415
|
buffer_attributes: list[tuple[AttributeBufferObject, Attribute]]
|
|
372
|
-
vao: VertexArray
|
|
373
416
|
attribute_names: dict[str, Attribute]
|
|
374
|
-
attrib_name_buffers: dict[str,
|
|
417
|
+
attrib_name_buffers: dict[str, VertexStream | InstanceStream]
|
|
418
|
+
vertex_stream: VertexStream
|
|
375
419
|
|
|
376
420
|
_property_dict: dict[str, property]
|
|
377
421
|
_vertexlist_class: type
|
|
378
422
|
|
|
379
|
-
|
|
380
|
-
_vertex_class: type[VertexList] = VertexList
|
|
423
|
+
_vertex_class: type[VertexListBase] = VertexListBase
|
|
381
424
|
|
|
382
|
-
def __init__(self,
|
|
425
|
+
def __init__(self, context: SurfaceContext, initial_count: int, attribute_meta: dict[str, Attribute]) -> None:
|
|
426
|
+
self._context = context or pyglet.graphics.api.core.current_context
|
|
383
427
|
self.attribute_meta = attribute_meta
|
|
384
|
-
self.
|
|
385
|
-
self.
|
|
428
|
+
self.attrib_name_buffers = {}
|
|
429
|
+
self._supports_multi_draw = self._has_multi_draw_extension(self._context)
|
|
430
|
+
|
|
431
|
+
# Separate attributes.
|
|
432
|
+
self.per_vertex: list[Attribute] = []
|
|
433
|
+
self.per_instance: list[Attribute] = []
|
|
434
|
+
for attrib in attribute_meta.values():
|
|
435
|
+
if not attrib.fmt.is_instanced:
|
|
436
|
+
self.per_vertex.append(attrib)
|
|
437
|
+
else:
|
|
438
|
+
self.per_instance.append(attrib)
|
|
386
439
|
|
|
387
|
-
self.
|
|
388
|
-
self.buffer_attributes = [] # list of (buffer, attribute)
|
|
389
|
-
self.attrib_name_buffers = {} # dict of AttributeName: AttributeBufferObject (for VertexLists)
|
|
440
|
+
self.vertex_buffers = None
|
|
390
441
|
|
|
391
|
-
|
|
442
|
+
# This function should set vertex_buffers
|
|
443
|
+
self._streams = self._create_streams(initial_count)
|
|
444
|
+
self.vao = self._create_vao()
|
|
445
|
+
self._vertex_buckets = {}
|
|
392
446
|
|
|
393
|
-
for name,
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
count = meta['count']
|
|
397
|
-
gl_type = _gl_types[meta['format'][0]]
|
|
398
|
-
normalize = 'n' in meta['format']
|
|
399
|
-
instanced = meta['instance']
|
|
447
|
+
for name, attrib in attribute_meta.items():
|
|
448
|
+
if not attrib.fmt.is_instanced:
|
|
449
|
+
self.attrib_name_buffers[name] = self.vertex_buffers
|
|
400
450
|
|
|
401
|
-
|
|
402
|
-
|
|
451
|
+
# Make a custom VertexListBase class w/ properties for each attribute
|
|
452
|
+
self._vertexlist_class = self._create_vertex_class()
|
|
403
453
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
# TODO: use persistent buffer if we have GL support for it:
|
|
408
|
-
# attribute.buffer = PersistentBufferObject(attribute.stride * self.allocator.capacity, attribute, self.vao)
|
|
454
|
+
@abstractmethod
|
|
455
|
+
def _has_multi_draw_extension(self, ctx: SurfaceContext) -> bool:
|
|
456
|
+
raise NotImplementedError
|
|
409
457
|
|
|
410
|
-
|
|
458
|
+
@abstractmethod
|
|
459
|
+
def _create_streams(self, size: int) -> list[VertexStream | IndexStream | InstanceStream]:
|
|
460
|
+
...
|
|
411
461
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
# Make a custom VertexList class w/ properties for each attribute in the ShaderProgram:
|
|
416
|
-
self._vertexlist_class = type(self._vertex_class.__name__, (self._vertex_class,), self._property_dict)
|
|
462
|
+
@abstractmethod
|
|
463
|
+
def _create_vao(self) -> VertexArrayBinding:
|
|
464
|
+
...
|
|
417
465
|
|
|
466
|
+
def bind_vao(self) -> None:
|
|
467
|
+
"""Binds the VAO as well as commit any pending buffer changes to the GPU."""
|
|
418
468
|
self.vao.bind()
|
|
419
|
-
for
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
self.vao.unbind()
|
|
469
|
+
for stream in self._streams:
|
|
470
|
+
stream.commit()
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def attribute_names(self):
|
|
474
|
+
return self.vertex_buffers.attribute_names
|
|
426
475
|
|
|
427
476
|
def safe_alloc(self, count: int) -> int:
|
|
428
477
|
"""Allocate vertices, resizing the buffers if necessary."""
|
|
429
|
-
|
|
430
|
-
return self.allocator.alloc(count)
|
|
431
|
-
except allocation.AllocatorMemoryException as e:
|
|
432
|
-
capacity = _nearest_pow2(e.requested_capacity)
|
|
433
|
-
for buffer, _ in self.buffer_attributes:
|
|
434
|
-
buffer.resize(capacity * buffer.stride)
|
|
435
|
-
self.allocator.set_capacity(capacity)
|
|
436
|
-
return self.allocator.alloc(count)
|
|
478
|
+
return self.vertex_buffers.alloc(count)
|
|
437
479
|
|
|
438
480
|
def safe_realloc(self, start: int, count: int, new_count: int) -> int:
|
|
439
481
|
"""Reallocate vertices, resizing the buffers if necessary."""
|
|
440
|
-
|
|
441
|
-
return self.allocator.realloc(start, count, new_count)
|
|
442
|
-
except allocation.AllocatorMemoryException as e:
|
|
443
|
-
capacity = _nearest_pow2(e.requested_capacity)
|
|
444
|
-
for buffer, _ in self.buffer_attributes:
|
|
445
|
-
buffer.resize(capacity * buffer.stride)
|
|
446
|
-
self.allocator.set_capacity(capacity)
|
|
447
|
-
return self.allocator.realloc(start, count, new_count)
|
|
482
|
+
return self.vertex_buffers.realloc(start, count, new_count)
|
|
448
483
|
|
|
449
|
-
def create(self, count: int,
|
|
450
|
-
"""Create a :py:class:`
|
|
484
|
+
def create(self, group: Group, count: int, indices: Sequence[int] | None = None) -> VertexListBase: # noqa: ARG002
|
|
485
|
+
"""Create a :py:class:`VertexListBase` in this domain.
|
|
451
486
|
|
|
452
487
|
Args:
|
|
488
|
+
group:
|
|
489
|
+
The :py:class:`Group` the resulting vertex list will be drawn with.
|
|
453
490
|
count:
|
|
454
491
|
Number of vertices to create.
|
|
455
|
-
|
|
492
|
+
indices:
|
|
456
493
|
Ignored for non indexed VertexDomains
|
|
457
494
|
"""
|
|
458
495
|
start = self.safe_alloc(count)
|
|
459
|
-
|
|
496
|
+
vlist = self._vertexlist_class(self, group, start, count)
|
|
497
|
+
self.alloc_to_group(vlist, group)
|
|
498
|
+
return vlist
|
|
460
499
|
|
|
500
|
+
def get_drawable_bucket(self, group: Group) -> VertexGroupBucket | None:
|
|
501
|
+
"""Get a bucket that exists and has vertices to draw (not empty)."""
|
|
502
|
+
bucket = self._vertex_buckets.get(group)
|
|
503
|
+
if bucket is None or (bucket and bucket.is_empty):
|
|
504
|
+
return None
|
|
505
|
+
|
|
506
|
+
return bucket
|
|
507
|
+
|
|
508
|
+
def alloc_to_group(self, vertex_list, group) -> VertexGroupBucket:
|
|
509
|
+
"""Assigns a vertex list to a specific state in this domain.
|
|
510
|
+
|
|
511
|
+
A state bucket does not allocate any vertices or allocates any GPU resources, it is simply to track the
|
|
512
|
+
data required for drawing in a specific state.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
vertex_list:
|
|
516
|
+
The vertex list to allocate.
|
|
517
|
+
group:
|
|
518
|
+
The group affecting the vertices.
|
|
519
|
+
|
|
520
|
+
Returns:
|
|
521
|
+
The new state bucket object.
|
|
522
|
+
"""
|
|
523
|
+
state_bucket = self._get_state_bucket(group)
|
|
524
|
+
state_bucket.add_vertex_list(vertex_list)
|
|
525
|
+
vertex_list.bucket = state_bucket
|
|
526
|
+
return state_bucket
|
|
527
|
+
|
|
528
|
+
def dealloc_from_group(self, vertex_list):
|
|
529
|
+
"""Removes a vertex list from a specific state in this domain."""
|
|
530
|
+
assert vertex_list.bucket is not None
|
|
531
|
+
vertex_list.bucket.remove_vertex_list(vertex_list)
|
|
532
|
+
vertex_list.bucket = None
|
|
533
|
+
|
|
534
|
+
def _get_state_bucket(self, group: Group) -> VertexGroupBucket:
|
|
535
|
+
"""Get a drawable bucket to assign vertex list information to a specific group."""
|
|
536
|
+
bucket = self._vertex_buckets.get(group)
|
|
537
|
+
if bucket is None:
|
|
538
|
+
bucket = self._vertex_buckets[group] = VertexGroupBucket()
|
|
539
|
+
return bucket
|
|
540
|
+
|
|
541
|
+
def has_bucket(self, group: Group) -> bool:
|
|
542
|
+
return group in self._vertex_buckets
|
|
543
|
+
|
|
544
|
+
@abstractmethod
|
|
461
545
|
def draw(self, mode: int) -> None:
|
|
462
546
|
"""Draw all vertices in the domain.
|
|
463
547
|
|
|
@@ -469,26 +553,12 @@ class VertexDomain:
|
|
|
469
553
|
OpenGL drawing mode, e.g. ``GL_POINTS``, ``GL_LINES``, etc.
|
|
470
554
|
|
|
471
555
|
"""
|
|
472
|
-
self.vao.bind()
|
|
473
|
-
for buffer, _ in self.buffer_attributes:
|
|
474
|
-
buffer.commit()
|
|
475
|
-
|
|
476
|
-
starts, sizes = self.allocator.get_allocated_regions()
|
|
477
|
-
primcount = len(starts)
|
|
478
|
-
if primcount == 0:
|
|
479
|
-
pass
|
|
480
|
-
elif primcount == 1:
|
|
481
|
-
# Common case
|
|
482
|
-
glDrawArrays(mode, starts[0], sizes[0])
|
|
483
|
-
else:
|
|
484
|
-
starts = (GLint * primcount)(*starts)
|
|
485
|
-
sizes = (GLsizei * primcount)(*sizes)
|
|
486
|
-
glMultiDrawArrays(mode, starts, sizes, primcount)
|
|
487
556
|
|
|
488
|
-
|
|
489
|
-
|
|
557
|
+
@abstractmethod
|
|
558
|
+
def draw_subset(self, mode: GeometryMode, vertex_list: VertexListBase) -> None:
|
|
559
|
+
"""Draw a specific VertexListBase in the domain.
|
|
490
560
|
|
|
491
|
-
The `vertex_list` parameter specifies a :py:class:`
|
|
561
|
+
The `vertex_list` parameter specifies a :py:class:`VertexListBase`
|
|
492
562
|
to draw. Only primitives in that list will be drawn.
|
|
493
563
|
|
|
494
564
|
Args:
|
|
@@ -498,98 +568,73 @@ class VertexDomain:
|
|
|
498
568
|
Vertex list to draw.
|
|
499
569
|
|
|
500
570
|
"""
|
|
501
|
-
self.vao.bind()
|
|
502
|
-
for buffer, _ in self.buffer_attributes:
|
|
503
|
-
buffer.commit()
|
|
504
|
-
|
|
505
|
-
glDrawArrays(mode, vertex_list.start, vertex_list.count)
|
|
506
571
|
|
|
507
572
|
@property
|
|
508
573
|
def is_empty(self) -> bool:
|
|
509
|
-
|
|
574
|
+
"""If the domain has no vertices."""
|
|
575
|
+
return not self.vertex_buffers.allocator.starts
|
|
510
576
|
|
|
511
577
|
def __repr__(self) -> str:
|
|
512
|
-
return f'<{self.__class__.__name__}@{id(self):x} {self.allocator}>'
|
|
513
|
-
|
|
578
|
+
return f'<{self.__class__.__name__}@{id(self):x} vertex_alloc={self.vertex_buffers.allocator}>'
|
|
514
579
|
|
|
515
|
-
def _make_instance_attribute_property(name: str) -> property:
|
|
516
|
-
def _attribute_getter(self: VertexInstance) -> Array[CTypesDataType]:
|
|
517
|
-
buffer = self.domain.attrib_name_buffers[name]
|
|
518
|
-
region = buffer.get_region(self.id - 1, 1)
|
|
519
|
-
buffer.invalidate_region(self.id - 1, 1)
|
|
520
|
-
return region
|
|
521
580
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
buffer.set_region(self.id - 1, 1, data)
|
|
525
|
-
|
|
526
|
-
return property(_attribute_getter, _attribute_setter)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
def _make_restricted_instance_attribute_property(name: str) -> property:
|
|
530
|
-
def _attribute_getter(self: VertexInstance) -> Array[CTypesDataType]:
|
|
531
|
-
buffer = self.domain.attrib_name_buffers[name]
|
|
532
|
-
return buffer.get_region(self.id - 1, 1)
|
|
533
|
-
|
|
534
|
-
def _attribute_setter(_self: VertexInstance, _data: Any) -> NoReturn:
|
|
535
|
-
msg = f"Attribute '{name}' is not an instanced attribute."
|
|
536
|
-
raise Exception(msg)
|
|
537
|
-
|
|
538
|
-
return property(_attribute_getter, _attribute_setter)
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
class InstancedVertexDomain(VertexDomain): # noqa: D101
|
|
542
|
-
instance_allocator: Allocator
|
|
543
|
-
_instances: int
|
|
544
|
-
_instance_properties: dict[str, property]
|
|
545
|
-
_vertexinstance_class: type
|
|
581
|
+
class IndexedVertexDomainBase(VertexDomainBase):
|
|
582
|
+
"""Management of a set of indexed vertex lists.
|
|
546
583
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
584
|
+
Construction of an indexed vertex domain is usually done with the
|
|
585
|
+
:py:func:`create_domain` function.
|
|
586
|
+
"""
|
|
587
|
+
_initial_index_count = 16
|
|
588
|
+
_vertex_class = IndexedVertexListBase
|
|
589
|
+
index_stream: IndexStream
|
|
590
|
+
|
|
591
|
+
def __init__(self, context: SurfaceContext, initial_count: int, attribute_meta: dict[str, Attribute],
|
|
592
|
+
index_type: DataTypes = "I") -> None:
|
|
593
|
+
self.index_type = index_type
|
|
594
|
+
self._supports_base_vertex = context.get_info().have_extension("GL_ARB_draw_elements_base_vertex")
|
|
595
|
+
super().__init__(context, initial_count, attribute_meta)
|
|
596
|
+
|
|
597
|
+
def get_group_bucket(self, group: Group) -> IndexedVertexGroupBucket:
|
|
598
|
+
"""Get a drawable bucket to assign vertex list information to a specific group."""
|
|
599
|
+
bucket = self._vertex_buckets.get(group)
|
|
600
|
+
if bucket is None:
|
|
601
|
+
bucket = self._vertex_buckets[group] = IndexedVertexGroupBucket()
|
|
602
|
+
return bucket
|
|
551
603
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
self._instance_properties[name] = _make_instance_attribute_property(name)
|
|
556
|
-
else:
|
|
557
|
-
self._instance_properties[name] = _make_restricted_instance_attribute_property(name)
|
|
604
|
+
def safe_index_alloc(self, count: int) -> int:
|
|
605
|
+
"""Allocate indices, resizing the buffers if necessary."""
|
|
606
|
+
return self.index_stream.alloc(count)
|
|
558
607
|
|
|
559
|
-
|
|
608
|
+
def safe_index_realloc(self, start: int, count: int, new_count: int) -> int:
|
|
609
|
+
"""Reallocate indices, resizing the buffers if necessary."""
|
|
610
|
+
return self.index_stream.realloc(start, count, new_count)
|
|
560
611
|
|
|
561
|
-
def
|
|
562
|
-
|
|
563
|
-
return self.instance_allocator.alloc(count)
|
|
564
|
-
except allocation.AllocatorMemoryException as e:
|
|
565
|
-
capacity = _nearest_pow2(e.requested_capacity)
|
|
566
|
-
for buffer, attribute in self.buffer_attributes:
|
|
567
|
-
if attribute.instance:
|
|
568
|
-
buffer.resize(capacity * buffer.stride)
|
|
569
|
-
self.instance_allocator.set_capacity(capacity)
|
|
570
|
-
return self.instance_allocator.alloc(count)
|
|
612
|
+
def create(self, group: Group, count: int, indices: Sequence[int] | None = None) -> IndexedVertexListBase:
|
|
613
|
+
"""Create an :py:class:`IndexedVertexList` in this domain.
|
|
571
614
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
buffer.resize(capacity * buffer.stride)
|
|
580
|
-
self.allocator.set_capacity(capacity)
|
|
581
|
-
return self.allocator.alloc(count)
|
|
615
|
+
Args:
|
|
616
|
+
group:
|
|
617
|
+
The :py:class:`Group` the resulting vertex list will be drawn with.
|
|
618
|
+
count:
|
|
619
|
+
Number of vertices to create
|
|
620
|
+
indices:
|
|
621
|
+
The indices used for this vertex list.
|
|
582
622
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
623
|
+
"""
|
|
624
|
+
index_count = len(indices)
|
|
625
|
+
start = self.safe_alloc(count)
|
|
626
|
+
index_start = self.safe_index_alloc(index_count)
|
|
627
|
+
vertex_list = self._vertexlist_class(self, group, start, count, index_start, index_count)
|
|
628
|
+
vertex_list.indices = indices # Move into class at some point?
|
|
629
|
+
self.alloc_to_group(vertex_list, group)
|
|
630
|
+
return vertex_list
|
|
631
|
+
|
|
632
|
+
def _get_state_bucket(self, group: Group) -> IndexedVertexGroupBucket:
|
|
633
|
+
"""Get a drawable bucket to assign vertex list information to a specific group."""
|
|
634
|
+
bucket = self._vertex_buckets.get(group)
|
|
635
|
+
if bucket is None:
|
|
636
|
+
bucket = self._vertex_buckets[group] = IndexedVertexGroupBucket()
|
|
637
|
+
return bucket
|
|
593
638
|
|
|
594
639
|
def draw(self, mode: int) -> None:
|
|
595
640
|
"""Draw all vertices in the domain.
|
|
@@ -602,17 +647,11 @@ class InstancedVertexDomain(VertexDomain): # noqa: D101
|
|
|
602
647
|
OpenGL drawing mode, e.g. ``GL_POINTS``, ``GL_LINES``, etc.
|
|
603
648
|
|
|
604
649
|
"""
|
|
605
|
-
self.vao.bind()
|
|
606
|
-
for buffer, _ in self.buffer_attributes:
|
|
607
|
-
buffer.commit()
|
|
608
650
|
|
|
609
|
-
|
|
610
|
-
|
|
651
|
+
def draw_subset(self, mode: GeometryMode, vertex_list: IndexedVertexListBase) -> None:
|
|
652
|
+
"""Draw a specific IndexedVertexListBase in the domain.
|
|
611
653
|
|
|
612
|
-
|
|
613
|
-
"""Draw a specific VertexList in the domain.
|
|
614
|
-
|
|
615
|
-
The `vertex_list` parameter specifies a :py:class:`VertexList`
|
|
654
|
+
The `vertex_list` parameter specifies a :py:class:`IndexedVertexListBase`
|
|
616
655
|
to draw. Only primitives in that list will be drawn.
|
|
617
656
|
|
|
618
657
|
Args:
|
|
@@ -621,227 +660,345 @@ class InstancedVertexDomain(VertexDomain): # noqa: D101
|
|
|
621
660
|
vertex_list:
|
|
622
661
|
Vertex list to draw.
|
|
623
662
|
"""
|
|
624
|
-
self.vao.bind()
|
|
625
|
-
for buffer, _ in self.buffer_attributes:
|
|
626
|
-
buffer.commit()
|
|
627
663
|
|
|
628
|
-
glDrawArraysInstanced(mode, vertex_list.start, vertex_list.count, self._instances)
|
|
629
|
-
|
|
630
|
-
@property
|
|
631
|
-
def is_empty(self) -> bool:
|
|
632
|
-
return not self.allocator.starts
|
|
633
664
|
|
|
665
|
+
class InstancedVertexDomainBase(VertexDomainBase):
|
|
666
|
+
def __init__(self, context: SurfaceContext, initial_count: int, attribute_meta: dict[str, Attribute]) -> None:
|
|
667
|
+
super().__init__(context, initial_count, attribute_meta)
|
|
668
|
+
self.instance_domain = self.create_instance_domain(initial_count)
|
|
669
|
+
self._instance_map = {}
|
|
634
670
|
|
|
635
|
-
|
|
636
|
-
|
|
671
|
+
@abstractmethod
|
|
672
|
+
def create_instance_domain(self, size: int) -> BaseInstanceDomain:
|
|
673
|
+
...
|
|
637
674
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
"""
|
|
641
|
-
index_allocator: Allocator
|
|
642
|
-
index_gl_type: int
|
|
643
|
-
index_c_type: CTypesDataType
|
|
644
|
-
index_element_size: int
|
|
645
|
-
index_buffer: IndexedBufferObject
|
|
646
|
-
_initial_index_count = 16
|
|
647
|
-
_vertex_class = IndexedVertexList
|
|
675
|
+
def _create_vao(self) -> None:
|
|
676
|
+
"""Handled by buckets."""
|
|
648
677
|
|
|
649
|
-
def
|
|
650
|
-
|
|
651
|
-
super().__init__(attribute_meta)
|
|
678
|
+
def bind_vao(self):
|
|
679
|
+
self.vertex_buffers.commit()
|
|
652
680
|
|
|
653
|
-
|
|
681
|
+
def alloc_to_group(self, vertex_list, group):
|
|
682
|
+
super().alloc_to_group(vertex_list, group)
|
|
683
|
+
key = (vertex_list.start, vertex_list.count)
|
|
684
|
+
self._instance_map[key] = vertex_list.instance_bucket
|
|
654
685
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
self.
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
686
|
+
def create(self, group: Group, count: int, indices: Sequence[int] | None = None) -> VertexListBase: # noqa: ARG002
|
|
687
|
+
start = self.safe_alloc(count)
|
|
688
|
+
bucket = self.instance_domain.get_arrays_bucket(mode=0, first_vertex=start, vertex_count=count)
|
|
689
|
+
vlist = self._vertexlist_class(self, group, start, count, bucket)
|
|
690
|
+
self.alloc_to_group(vlist, group)
|
|
691
|
+
return vlist
|
|
692
|
+
|
|
693
|
+
class InstancedIndexedVertexDomainBase(IndexedVertexDomainBase):
|
|
694
|
+
def __init__(self, context: SurfaceContext, initial_count: int, attribute_meta: dict[str, Attribute],
|
|
695
|
+
index_type: DataTypes = "I") -> None:
|
|
696
|
+
super().__init__(context, initial_count, attribute_meta, index_type)
|
|
697
|
+
self.instance_domain = self.create_instance_domain(initial_count)
|
|
698
|
+
self._instance_map = {}
|
|
699
|
+
|
|
700
|
+
@abstractmethod
|
|
701
|
+
def create_instance_domain(self, size: int) -> BaseInstanceDomain:
|
|
702
|
+
...
|
|
703
|
+
|
|
704
|
+
def _create_vao(self) -> None:
|
|
705
|
+
"""Handled by buckets."""
|
|
706
|
+
|
|
707
|
+
def bind_vao(self):
|
|
708
|
+
# VAO's are actually bound when instance buckets are drawn, but we can update the shared buffers atleast.
|
|
709
|
+
self.vertex_buffers.commit()
|
|
710
|
+
self.index_stream.commit()
|
|
711
|
+
|
|
712
|
+
def alloc_to_group(self, vertex_list, group):
|
|
713
|
+
super().alloc_to_group(vertex_list, group)
|
|
714
|
+
key = (vertex_list.index_start, vertex_list.index_count)
|
|
715
|
+
self._instance_map[key] = vertex_list.instance_bucket
|
|
716
|
+
|
|
717
|
+
def create(self, group: Group, count: int, indices: Sequence[int] | None) -> InstanceIndexedVertexListBase:
|
|
718
|
+
"""Create an :py:class:`IndexedVertexList` in this domain.
|
|
662
719
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
720
|
+
Args:
|
|
721
|
+
group:
|
|
722
|
+
The :py:class:`Group` the resulting vertex list will be drawn with.
|
|
723
|
+
count:
|
|
724
|
+
Number of vertices to create
|
|
725
|
+
indices:
|
|
726
|
+
Indices used for this vertex list.
|
|
666
727
|
|
|
728
|
+
"""
|
|
729
|
+
index_count = len(indices)
|
|
730
|
+
start = self.safe_alloc(count)
|
|
731
|
+
index_start = self.safe_index_alloc(index_count)
|
|
732
|
+
base_vertex = start if self._supports_base_vertex else 0
|
|
733
|
+
bucket = self.instance_domain.get_elements_bucket(
|
|
734
|
+
mode=0, # Separate Mode from draw call into bucket at some point?
|
|
735
|
+
first_index=index_start,
|
|
736
|
+
index_count=index_count,
|
|
737
|
+
index_type=self.index_type,
|
|
738
|
+
base_vertex=base_vertex,
|
|
739
|
+
)
|
|
740
|
+
vertex_list = self._vertexlist_class(self, group, start, count, index_start, index_count, self.index_type, base_vertex, bucket)
|
|
741
|
+
vertex_list.indices = indices
|
|
742
|
+
self.alloc_to_group(vertex_list, group)
|
|
743
|
+
return vertex_list
|
|
744
|
+
|
|
745
|
+
def _create_vertex_class(self) -> type:
|
|
746
|
+
mixin = _LocalIndexSupport if self._supports_base_vertex else _RunningIndexSupport
|
|
667
747
|
# Make a custom VertexList class w/ properties for each attribute in the ShaderProgram:
|
|
668
|
-
|
|
669
|
-
self._property_dict)
|
|
748
|
+
return type(self._vertex_class.__name__, (mixin, self._vertex_class),
|
|
749
|
+
self.vertex_buffers._property_dict) # noqa: SLF001
|
|
670
750
|
|
|
671
|
-
|
|
672
|
-
|
|
751
|
+
class BaseStream(ABC):
|
|
752
|
+
"""A container that handles a set of buffers to be used with domains."""
|
|
753
|
+
def __init__(self, size: int) -> None:
|
|
754
|
+
"""Initialize the stream and create an allocator.
|
|
755
|
+
|
|
756
|
+
Args:
|
|
757
|
+
size: Initial allocator and buffer size.
|
|
758
|
+
"""
|
|
759
|
+
self._capacity = size
|
|
760
|
+
self.allocator = allocation.Allocator(size)
|
|
761
|
+
self.buffers = []
|
|
762
|
+
|
|
763
|
+
def commit(self) -> None:
|
|
764
|
+
"""Binds buffers and commits all pending data to the graphics API."""
|
|
765
|
+
for buf in self.buffers:
|
|
766
|
+
buf.commit()
|
|
767
|
+
|
|
768
|
+
@abstractmethod
|
|
769
|
+
def bind_into(self, vao) -> None:
|
|
770
|
+
"""Record this stream into the VAO.
|
|
771
|
+
|
|
772
|
+
The VAO should be bound before this function is called.
|
|
773
|
+
"""
|
|
774
|
+
|
|
775
|
+
def alloc(self, count: int) -> int:
|
|
776
|
+
"""Allocate a region of data, resizing the buffers if necessary."""
|
|
673
777
|
try:
|
|
674
|
-
return self.
|
|
778
|
+
return self.allocator.alloc(count)
|
|
675
779
|
except allocation.AllocatorMemoryException as e:
|
|
676
780
|
capacity = _nearest_pow2(e.requested_capacity)
|
|
677
|
-
self.
|
|
678
|
-
self.
|
|
679
|
-
return self.index_allocator.alloc(count)
|
|
781
|
+
self.resize(capacity)
|
|
782
|
+
return self.allocator.alloc(count)
|
|
680
783
|
|
|
681
|
-
def
|
|
682
|
-
"""
|
|
784
|
+
def resize(self, capacity: int) -> None:
|
|
785
|
+
"""Resize all buffers to the specified capacity.
|
|
786
|
+
|
|
787
|
+
Size is passed as capacity * stride.
|
|
788
|
+
"""
|
|
789
|
+
if capacity <= self.allocator.capacity:
|
|
790
|
+
return
|
|
791
|
+
self.allocator.set_capacity(capacity)
|
|
792
|
+
for buf in self.buffers:
|
|
793
|
+
buf.resize(capacity * buf.stride)
|
|
794
|
+
|
|
795
|
+
def dealloc(self, start: int, count: int) -> None:
|
|
796
|
+
self.allocator.dealloc(start, count)
|
|
797
|
+
|
|
798
|
+
def realloc(self, start: int, count: int, new_count: int) -> int:
|
|
799
|
+
"""Reallocate a region of data, resizing the buffers if necessary."""
|
|
683
800
|
try:
|
|
684
|
-
return self.
|
|
801
|
+
return self.allocator.realloc(start, count, new_count)
|
|
685
802
|
except allocation.AllocatorMemoryException as e:
|
|
686
803
|
capacity = _nearest_pow2(e.requested_capacity)
|
|
687
|
-
self.
|
|
688
|
-
self.
|
|
689
|
-
|
|
804
|
+
self.resize(capacity)
|
|
805
|
+
return self.allocator.realloc(start, count, new_count)
|
|
806
|
+
@abstractmethod
|
|
807
|
+
def set_region(self, start: int, count: int, data) -> None: ...
|
|
690
808
|
|
|
691
|
-
def create(self, count: int, index_count: int) -> IndexedVertexList:
|
|
692
|
-
"""Create an :py:class:`IndexedVertexList` in this domain.
|
|
693
809
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
810
|
+
class VertexStream(BaseStream):
|
|
811
|
+
"""A stream of buffers to be used with per-vertex attributes."""
|
|
812
|
+
attrib_name_buffers: dict[str, AttributeBufferObject]
|
|
813
|
+
attribute_meta: Sequence[Attribute]
|
|
814
|
+
def __init__(self, ctx: SurfaceContext, initial_size: int, attrs: Sequence[Attribute], *, divisor: int = 0):
|
|
815
|
+
super().__init__(initial_size)
|
|
816
|
+
self._ctx = ctx
|
|
817
|
+
self.attribute_names = {} # name: attribute
|
|
818
|
+
self.buffers = []
|
|
819
|
+
self.attrib_name_buffers = {} # dict of AttributeName: AttributeBufferObject (for VertexLists)
|
|
699
820
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
821
|
+
self._property_dict = {}
|
|
822
|
+
self.attribute_meta = attrs
|
|
823
|
+
self._allocate_buffers()
|
|
824
|
+
|
|
825
|
+
def get_buffer(self, size, attribute):
|
|
826
|
+
raise NotImplementedError
|
|
827
|
+
|
|
828
|
+
def get_graphics_attribute(self, attribute: Attribute, view: AttributeView) -> GraphicsAttribute:
|
|
829
|
+
raise NotImplementedError
|
|
830
|
+
|
|
831
|
+
def _create_separate_buffers(self, attributes: Sequence[Attribute]) -> None:
|
|
832
|
+
"""Takes the attributes and creates a separate buffer for each attribute."""
|
|
833
|
+
for attribute in attributes:
|
|
834
|
+
name = attribute.fmt.name
|
|
835
|
+
|
|
836
|
+
stride = attribute.fmt.components * attribute.element_size
|
|
837
|
+
view = AttributeView(offset=0, stride=stride)
|
|
838
|
+
self.attribute_names[name] = attribute = self.get_graphics_attribute(attribute, view)
|
|
839
|
+
|
|
840
|
+
self.attrib_name_buffers[name] = buffer = self.get_buffer(stride * self.allocator.capacity, attribute)
|
|
841
|
+
|
|
842
|
+
self.buffers.append(buffer)
|
|
843
|
+
|
|
844
|
+
# Create custom property to be used in the VertexListBase:
|
|
845
|
+
self._property_dict[name] = _make_attribute_property(name)
|
|
846
|
+
|
|
847
|
+
def _create_interleaved_buffers(self) -> NoReturn:
|
|
848
|
+
"""Creates a single buffer for all passed attributes."""
|
|
849
|
+
raise NotImplementedError
|
|
850
|
+
|
|
851
|
+
def _allocate_buffers(self) -> None:
|
|
852
|
+
for attrib in self.attribute_meta:
|
|
853
|
+
fmt_dt = attrib.fmt.data_type
|
|
854
|
+
assert fmt_dt in DataTypeTuple, f"'{fmt_dt}' is not a valid attribute format for '{attrib.fmt.name}'."
|
|
855
|
+
|
|
856
|
+
# Only support separate buffers per attrib currently.
|
|
857
|
+
self._create_separate_buffers(self.attribute_meta)
|
|
858
|
+
|
|
859
|
+
def set_region(self, start: int, count: int, data_by_attr: dict[str, Any]):
|
|
860
|
+
for name, buf in self.attrib_name_buffers.items():
|
|
861
|
+
buf.set_region(start, count, data_by_attr[name])
|
|
862
|
+
|
|
863
|
+
def set_attribute_region(self, name: str, start: int, count: int, data: Any):
|
|
864
|
+
buf = self.attrib_name_buffers[name]
|
|
865
|
+
return buf.set_region(start, count)
|
|
866
|
+
|
|
867
|
+
def get_attribute_region(self, name: str, start: int, count: int):
|
|
868
|
+
buf = self.attrib_name_buffers[name]
|
|
869
|
+
return buf.get_region(start, count)
|
|
870
|
+
|
|
871
|
+
def invalidate_attribute_region(self, name: str, start: int, count: int):
|
|
872
|
+
buf = self.attrib_name_buffers[name]
|
|
873
|
+
buf.invalidate_region(start, count)
|
|
874
|
+
|
|
875
|
+
def copy_data(
|
|
876
|
+
self,
|
|
877
|
+
dst_slot: int,
|
|
878
|
+
dst_stream: VertexStream | InstanceStream,
|
|
879
|
+
src_slot: int,
|
|
880
|
+
count: int = 1,
|
|
881
|
+
attrs: Iterable[str] | None = None,
|
|
882
|
+
*,
|
|
883
|
+
strict: bool = False,
|
|
884
|
+
) -> None:
|
|
885
|
+
if attrs is None:
|
|
886
|
+
dst_names = set(dst_stream.attrib_name_buffers.keys())
|
|
887
|
+
src_names = set(self.attrib_name_buffers.keys())
|
|
888
|
+
names = dst_names & src_names
|
|
889
|
+
if strict and dst_names != src_names:
|
|
890
|
+
err = (f"Attribute layout mismatch: missing in dst={sorted(src_names - dst_names)}, "
|
|
891
|
+
f"missing in src={sorted(dst_names - src_names)}")
|
|
892
|
+
raise ValueError(err)
|
|
893
|
+
else:
|
|
894
|
+
names = [n for n in attrs if n in self.attrib_name_buffers and n in dst_stream.attrib_name_buffers]
|
|
895
|
+
if strict and len(names) != len(list(attrs)):
|
|
896
|
+
err = f"Requested attribute not present in both streams. {names}, {attrs}"
|
|
897
|
+
raise ValueError(err)
|
|
704
898
|
|
|
705
|
-
|
|
706
|
-
|
|
899
|
+
for name in names:
|
|
900
|
+
dst_buf = dst_stream.attrib_name_buffers[name]
|
|
901
|
+
src_buf = self.attrib_name_buffers[name]
|
|
707
902
|
|
|
708
|
-
|
|
709
|
-
|
|
903
|
+
data = src_buf.get_region(src_slot, count)
|
|
904
|
+
dst_buf.set_region(dst_slot, count, data)
|
|
710
905
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
OpenGL drawing mode, e.g. ``GL_POINTS``, ``GL_LINES``, etc.
|
|
906
|
+
def __repr__(self):
|
|
907
|
+
return f'{self.__class__.__name__}(attributes={list(self.attribute_meta)}, alloc={self.allocator})'
|
|
714
908
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
for buffer, _ in self.buffer_attributes:
|
|
718
|
-
buffer.commit()
|
|
719
|
-
|
|
720
|
-
self.index_buffer.commit()
|
|
721
|
-
|
|
722
|
-
starts, sizes = self.index_allocator.get_allocated_regions()
|
|
723
|
-
primcount = len(starts)
|
|
724
|
-
if primcount == 0:
|
|
725
|
-
pass
|
|
726
|
-
elif primcount == 1:
|
|
727
|
-
# Common case
|
|
728
|
-
glDrawElements(mode, sizes[0], self.index_gl_type,
|
|
729
|
-
self.index_buffer.ptr + starts[0] * self.index_element_size)
|
|
730
|
-
else:
|
|
731
|
-
starts = [s * self.index_element_size + self.index_buffer.ptr for s in starts]
|
|
732
|
-
starts = (ctypes.POINTER(GLvoid) * primcount)(*(GLintptr * primcount)(*starts))
|
|
733
|
-
sizes = (GLsizei * primcount)(*sizes)
|
|
734
|
-
glMultiDrawElements(mode, sizes, self.index_gl_type, starts, primcount)
|
|
909
|
+
class InstanceStream(VertexStream):
|
|
910
|
+
"""Handles a stream of buffers to be used with the per-instance attributes."""
|
|
735
911
|
|
|
736
|
-
|
|
737
|
-
|
|
912
|
+
class IndexStream(BaseStream):
|
|
913
|
+
"""A container to manage an index buffer for a domain."""
|
|
738
914
|
|
|
739
|
-
|
|
740
|
-
|
|
915
|
+
def __init__(self, ctx, data_type: DataTypes, initial_elems: int):
|
|
916
|
+
super().__init__(initial_elems)
|
|
917
|
+
self.ctx = ctx
|
|
918
|
+
self.data_type = data_type
|
|
919
|
+
self.buffer = self._create_buffer()
|
|
920
|
+
self.buffers = [self.buffer]
|
|
741
921
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
OpenGL drawing mode, e.g. ``GL_POINTS``, ``GL_LINES``, etc.
|
|
745
|
-
vertex_list:
|
|
746
|
-
Vertex list to draw.
|
|
747
|
-
"""
|
|
748
|
-
self.vao.bind()
|
|
749
|
-
for buffer, _ in self.buffer_attributes:
|
|
750
|
-
buffer.commit()
|
|
922
|
+
def _create_buffer(self) -> IndexedBufferObject:
|
|
923
|
+
raise NotImplementedError
|
|
751
924
|
|
|
752
|
-
|
|
925
|
+
def commit(self) -> None:
|
|
926
|
+
self.buffer.commit()
|
|
753
927
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
vertex_list.index_start * self.index_element_size)
|
|
928
|
+
def get_region(self, start: int, count: int) -> Any:
|
|
929
|
+
return self.buffer.get_region(start, count)
|
|
757
930
|
|
|
931
|
+
def bind_into(self, vao) -> None:
|
|
932
|
+
self.buffer.bind_to_index_buffer()
|
|
758
933
|
|
|
759
|
-
|
|
760
|
-
|
|
934
|
+
def set_region(self, start: int, count: int, data) -> None:
|
|
935
|
+
self.buffer.set_region(start, count, data)
|
|
761
936
|
|
|
762
|
-
|
|
763
|
-
|
|
937
|
+
def copy_region(self, dst: int, src: int, count: int) -> None:
|
|
938
|
+
self.buffer.copy_region(dst, src, count)
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
class VertexArrayProtocol(Protocol):
|
|
942
|
+
def bind(self): ...
|
|
943
|
+
def unbind(self): ...
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
class VertexArrayBinding:
|
|
947
|
+
"""A wrapper for a Vertex Array Object that binds streams.
|
|
948
|
+
|
|
949
|
+
VAO's store which attribute layouts are used, as well as which buffer object each attribute pulls from.
|
|
950
|
+
|
|
951
|
+
In the case of instanced drawing, each instance needs its own VAO as their per-instance data are separate buffers.
|
|
764
952
|
"""
|
|
765
|
-
|
|
953
|
+
streams: list[VertexStream | InstanceStream | IndexStream]
|
|
766
954
|
|
|
767
|
-
def __init__(self,
|
|
768
|
-
|
|
769
|
-
|
|
955
|
+
def __init__(self, ctx: SurfaceContext, streams: list[VertexStream | InstanceStream | IndexStream]):
|
|
956
|
+
# attr_map: semantic/name -> location (from ShaderProgram inspection)
|
|
957
|
+
self._ctx = ctx
|
|
958
|
+
self.vao = self._create_vao()
|
|
959
|
+
self.streams = streams
|
|
960
|
+
self._link()
|
|
770
961
|
|
|
771
|
-
def
|
|
772
|
-
|
|
962
|
+
def bind(self):
|
|
963
|
+
raise NotImplementedError
|
|
773
964
|
|
|
774
|
-
|
|
775
|
-
The starting index of the allocated region.
|
|
776
|
-
"""
|
|
777
|
-
try:
|
|
778
|
-
return self.index_allocator.alloc(count)
|
|
779
|
-
except allocation.AllocatorMemoryException as e:
|
|
780
|
-
capacity = _nearest_pow2(e.requested_capacity)
|
|
781
|
-
self.index_buffer.resize(capacity * self.index_element_size)
|
|
782
|
-
self.index_allocator.set_capacity(capacity)
|
|
783
|
-
return self.index_allocator.alloc(count)
|
|
965
|
+
def _create_vao(self) -> VertexArrayProtocol: ...
|
|
784
966
|
|
|
785
|
-
def
|
|
786
|
-
"""
|
|
787
|
-
try:
|
|
788
|
-
return self.index_allocator.realloc(start, count, new_count)
|
|
789
|
-
except allocation.AllocatorMemoryException as e:
|
|
790
|
-
capacity = _nearest_pow2(e.requested_capacity)
|
|
791
|
-
self.index_buffer.resize(capacity * self.index_element_size)
|
|
792
|
-
self.index_allocator.set_capacity(capacity)
|
|
793
|
-
return self.index_allocator.realloc(start, count, new_count)
|
|
967
|
+
def _link(self):
|
|
968
|
+
"""Link the all streams to the VAO."""
|
|
794
969
|
|
|
795
|
-
def
|
|
796
|
-
|
|
970
|
+
def __repr__(self):
|
|
971
|
+
return f'<{self.__class__.__name__}@{id(self):x} vao={self.vao}, streams={self.streams}>'
|
|
797
972
|
|
|
798
|
-
Args:
|
|
799
|
-
count:
|
|
800
|
-
Number of vertices to create
|
|
801
|
-
index_count:
|
|
802
|
-
Number of indices to create
|
|
803
973
|
|
|
804
|
-
"""
|
|
805
|
-
start = self.safe_alloc(count)
|
|
806
|
-
index_start = self.safe_index_alloc(index_count)
|
|
807
|
-
return self._vertexlist_class(self, start, count, index_start, index_count)
|
|
808
974
|
|
|
809
|
-
|
|
810
|
-
|
|
975
|
+
class VertexGroupBucket(allocation.RangeAllocator):
|
|
976
|
+
"""A grouping of vertex lists belonging to a single group in a domain.
|
|
811
977
|
|
|
812
|
-
|
|
813
|
-
|
|
978
|
+
Vertex lists are still owned by the domain, but this allows states to be rendered together if possible.
|
|
979
|
+
"""
|
|
814
980
|
|
|
815
|
-
|
|
816
|
-
mode:
|
|
817
|
-
OpenGL drawing mode, e.g. ``GL_POINTS``, ``GL_LINES``, etc.
|
|
981
|
+
__slots__ = ("_merged", "_ranges", "is_dirty")
|
|
818
982
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
for buffer, _ in self.buffer_attributes:
|
|
822
|
-
buffer.commit()
|
|
983
|
+
def __init__(self) -> None:
|
|
984
|
+
super().__init__()
|
|
823
985
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
self.index_buffer.ptr + starts[0] * self.index_element_size, self._instances)
|
|
986
|
+
def add_vertex_list(self, vl: VertexListBase) -> None:
|
|
987
|
+
self.add(vl.start, vl.count)
|
|
827
988
|
|
|
828
|
-
def
|
|
829
|
-
|
|
989
|
+
def remove_vertex_list(self, vl: VertexListBase) -> None:
|
|
990
|
+
self.remove(vl.start, vl.count)
|
|
830
991
|
|
|
831
|
-
The ``vertex_list`` parameter specifies a :py:class:`IndexedVertexList`
|
|
832
|
-
to draw. Only primitives in that list will be drawn.
|
|
833
992
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
OpenGL drawing mode, e.g. ``GL_POINTS``, ``GL_LINES``, etc.
|
|
837
|
-
vertex_list:
|
|
838
|
-
Vertex list to draw.
|
|
993
|
+
class IndexedVertexGroupBucket(allocation.RangeAllocator):
|
|
994
|
+
"""A grouping of indexed vertex lists belonging to a single group in a domain.
|
|
839
995
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
996
|
+
Vertex lists are still owned by the domain, but this allows states to be rendered together if possible.
|
|
997
|
+
"""
|
|
998
|
+
__slots__ = ("_merged", "_ranges", "is_dirty")
|
|
999
|
+
|
|
1000
|
+
def add_vertex_list(self, vl: IndexedVertexListBase) -> None:
|
|
1001
|
+
self.add(vl.index_start, vl.index_count)
|
|
844
1002
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
vertex_list.index_start * self.index_element_size, self._instances)
|
|
1003
|
+
def remove_vertex_list(self, vl: IndexedVertexListBase) -> None:
|
|
1004
|
+
self.remove(vl.index_start, vl.index_count)
|