8000 Extremely WIP protocol based abstraction of arcade.gl · pythonarcade/arcade@6fec97d · GitHub
[go: up one dir, main page]

Skip to content

Commit 6fec97d

Browse files
committed
Extremely WIP protocol based abstraction of arcade.gl
1 parent 4f940df commit 6fec97d

File tree

15 files changed

+4478
-293
lines changed

15 files changed

+4478
-293
lines changed

arcade/gl/backends/gl/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .context import GLContext
2+
from .buffer import GLBuffer

arcade/gl/backends/gl/buffer.py

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
from __future__ import annotations
2+
3+
import weakref
4+
from ctypes import byref, string_at
5+
from typing import TYPE_CHECKING
6+
7+
from pyglet import gl
8+
9+
from arcade.types import BufferProtocol
10+
from arcade.gl.new import Buffer, Context
11+
12+
from .utils import data_to_ctypes
13+
14+
if TYPE_CHECKING:
15+
from arcade.gl.backends.gl import GLContext
16+
17+
18+
class GLBuffer(Buffer):
19+
"""OpenGL buffer object. Buffers store byte data and upload it
20+
to graphics memory so shader programs can process the data.
21+
They are used for storage of vertex data,
22+
element data (vertex indexing), uniform block data etc.
23+
24+
The ``data`` parameter can be anything that implements the
25+
`Buffer Protocol <https://docs.python.org/3/c-api/buffer.html>`_.
26+
27+
This includes ``bytes``, ``bytearray``, ``array.array``, and
28+
more. You may need to use typing workarounds for non-builtin
29+
types. See :ref:`prog-guide-gl-buffer-protocol-typing` for more
30+
information.
31+
32+
.. warning:: Buffer objects should be created using :py:meth:`arcade.gl.Context.buffer`
33+
34+
Args:
35+
ctx:
36+
The context this buffer belongs to
37+
data:
38+
The data this buffer should contain. It can be a ``bytes`` instance or any
39+
object supporting the buffer protocol.
40+
reserve:
41+
Create a buffer of a specific byte size
42+
usage:
43+
A hit of this buffer is ``static`` or ``dynamic`` (can mostly be ignored)
44+
"""
45+
46+
__slots__ = "_ctx", "_glo", "_size", "_usage", "__weakref__"
47+
_usages = {
48+
"static": gl.GL_STATIC_DRAW,
49+
"dynamic": gl.GL_DYNAMIC_DRAW,
50+
"stream": gl.GL_STREAM_DRAW,
51+
}
52+
53+
def __init__(
54+
self,
55+
ctx: Context,
56+
data: BufferProtocol | None = None,
57+
reserve: int = 0,
58+
usage: str = "static",
59+
) -> None:
60+
assert isinstance(ctx, GLContext)
61+
self._ctx = ctx
62+
self._glo = glo = gl.GLuint()
63+
self._size = -1
64+
self._usage = GLBuffer._usages[usage]
65+
66+
gl.glGenBuffers(1, byref(self._glo))
67+
# print(f"glGenBuffers() -> {self._glo.value}")
68+
if self._glo.value == 0:
69+
raise RuntimeError("Cannot create Buffer object.")
70+
71+
# print(f"glBindBuffer({self._glo.value})")
72+
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._glo)
73+
# print(f"glBufferData(gl.GL_ARRAY_BUFFER, {self._size}, data, {self._usage})")
74+
75+
if data is not None and len(data) > 0: # type: ignore
76+
self._size, data = data_to_ctypes(data)
77+
gl.glBufferData(gl.GL_ARRAY_BUFFER, self._size, data, self._usage)
78+
elif reserve > 0:
79+
self._size = reserve
80+
# populate the buffer with zero byte values
81+
data = (gl.GLubyte * self._size)()
82+
gl.glBufferData(gl.GL_ARRAY_BUFFER, self._size, data, self._usage)
83+
else:
84+
raise ValueError("Buffer takes byte data or number of reserved bytes")
85+
86+
if self._ctx.gc_mode == "auto":
87+
weakref.finalize(self, GLBuffer.delete_glo, self.ctx, glo)
88+
89+
self._ctx.stats.incr("buffer")
90+
91+
def __repr__(self):
92+
return f"<Buffer {self._glo.value}>"
93+
94+
def __del__(self):
95+
# Intercept garbage collection if we are using Context.gc()
96+
if self._ctx.gc_mode == "context_gc" and self._glo.value > 0:
97+
self._ctx.objects.append(self)
98+
99+
@property
100+
def size(self) -> int:
101+
"""The byte size of the buffer."""
102+
return self._size
103+
104+
@property
105+
def ctx(self) -> GLContext:
106+
"""The context this resource belongs to."""
107+
return self._ctx
108+
109+
@property
110+
def glo(self) -> gl.GLuint:
111+
"""The OpenGL resource id."""
112+
return self._glo
113+
114+
def delete(self) -> None:
115+
"""
116+
Destroy the underlying OpenGL resource.
117+
118+
.. warning:: Don't use this unless you know exactly what you are doing.
119+
"""
120+
GLBuffer.delete_glo(self._ctx, self._glo)
121+
self._glo.value = 0
122+
123+
@staticmethod
124+
def delete_glo(ctx: GLContext, glo: gl.GLuint):
125+
"""
126+
Release/delete open gl buffer.
127+
128+
This is automatically called when the object is garbage collected.
129+
130+
Args:
131+
ctx:
132+
The context the buffer belongs to
133+
glo:
134+
The OpenGL buffer id
135+
"""
136+
# If we have no context, then we are shutting down, so skip this
137+
if gl.current_context is None:
138+
return
139+
140+
if glo.value != 0:
141+
gl.glDeleteBuffers(1, byref(glo))
142+
glo.value = 0
143+
144+
ctx.stats.decr("buffer")
145+
146+
def read(self, size: int = -1, offset: int = 0) -> bytes:
147+
"""Read data from the buffer.
148+
149+
Args:
150+
size:
151+
The bytes to read. -1 means the entire buffer (default)
152+
offset:
153+
Byte read offset
154+
"""
155+
if size == -1:
156+
size = self._size - offset
157+
158+
# Catch this before confusing INVALID_OPERATION is raised
159+
if size < 1:
160+
raise ValueError(
161+
"Attempting to read 0 or less bytes from buffer: "
162+
f"buffer size={self._size} | params: size={size}, offset={offset}"
163+
)
164+
165+
# Manually detect this so it doesn't raise a confusing INVALID_VALUE error
166+
if size + offset > self._size:
167+
raise ValueError(
168+
(
169+
"Attempting to read outside the buffer. "
170+
f"Buffer size: {self._size} "
171+
f"Reading from {offset} to {size + offset}"
172+
)
173+
)
174+
175+
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._glo)
176+
ptr = gl.glMapBufferRange(gl.GL_ARRAY_BUFFER, offset, size, gl.GL_MAP_READ_BIT)
177+
data = string_at(ptr, size=size)
178+
gl.glUnmapBuffer(gl.GL_ARRAY_BUFFER)
179+
return data
180+
181+
def write(self, data: BufferProtocol, offset: int = 0):
182+
"""Write byte data to the buffer from a buffer protocol object.
183+
184+
The ``data`` value can be anything that implements the
185+
`Buffer Protocol <https://docs.python.org/3/c-api/buffer.html>`_.
186+
187+
This includes ``bytes``, ``bytearray``, ``array.array``, and
188+
more. You may need to use typing workarounds for non-builtin
189+
types. See :ref:`prog-guide-gl-buffer-protocol-typing` for more
190+
information.
191+
192+
If the supplied data is larger than the buffer, it will be
193+
truncated to fit. If the supplied data is smaller than the
194+
buffer, the remaining bytes will be left unchanged.
195+
196+
Args:
197+
data:
198+
The byte data to write. This can be bytes or any object
199+
supporting the buffer protocol.
200+
offset:
201+
The byte offset
202+
"""
203+
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._glo)
204+
size, data = data_to_ctypes(data)
205+
# Ensure we don't write outside the buffer
206+
size = min(size, self._size - offset)
207+
if size < 0:
208+
raise ValueError("Attempting to write negative number bytes to buffer")
209+
gl.glBufferSubData(gl.GL_ARRAY_BUFFER, gl.GLintptr(offset), size, data)
210+
211+
def copy_from_buffer(self, source: Buffer, size=-1, offset=0, source_offset=0) -> None:
212+
"""Copy data into this buffer from another buffer.
213+
214+
Args:
215+
source:
216+
The buffer to copy from
217+
size:
218+
The amount of bytes to copy
219+
offset:
220+
The byte offset to write the data in this buffer
221+
source_offset:
222+
The byte offset to read from the source buffer
223+
"""
224+
assert isinstance(source, GLBuffer)
225+
# Read the entire source buffer into this buffer
226+
if size == -1:
227+
size = source.size
228+
229+
# TODO: Check buffer bounds
230+
if size + source_offset > source.size:
231+
raise ValueError("Attempting to read outside the source buffer")
232+
233+
if size + offset > self._size:
234+
raise ValueError("Attempting to write outside the buffer")
235+
236+
gl.glBindBuffer(gl.GL_COPY_READ_BUFFER, source.glo)
237+
gl.glBindBuffer(gl.GL_COPY_WRITE_BUFFER, self._glo)
238+
gl.glCopyBufferSubData(
239+
gl.GL_COPY_READ_ F438 BUFFER,
240+
gl.GL_COPY_WRITE_BUFFER,
241+
gl.GLintptr(source_offset), # readOffset
242+
gl.GLintptr(offset), # writeOffset
243+
size, # size (number of bytes to copy)
244+
)
245+
246+
def orphan(self, size: int = -1, double: bool = False):
247+
"""
248+
Re-allocate the entire buffer memory. This can be used to resize
249+
a buffer or for re-specification (orphan the buffer to avoid blocking).
250+
251+
If the current buffer is busy in rendering operations
252+
it will be deallocated by OpenGL when completed.
253+
254+
Args:
255+
size:
256+
New size of buffer. -1 will retain the current size.
257+
Takes precedence over ``double`` parameter if specified.
258+
double:
259+
Is passed in with `True` the buffer size will be doubled
260+
from its current size.
261+
"""
262+
if size > 0:
263+
self._size = size
264+
elif double is True:
265+
self._size *= 2
266+
267+
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._glo)
268+
gl.glBufferData(gl.GL_ARRAY_BUFFER, self._size, None, self._usage)
269+
270+
def bind_to_uniform_block(self, binding: int = 0, offset: int = 0, size: int = -1):
271+
"""Bind this buffer to a uniform block location.
272+
In most cases it will be sufficient to only provide a binding location.
273+
274+
Args:
275+
binding:
276+
The binding location
277+
offset:
278+
Byte offset
279+
size:
280+
Size of the buffer to bind.
281+
""&qu AD16 ot;
282+
if size < 0:
283+
size = self.size
284+
285+
gl.glBindBufferRange(gl.GL_UNIFORM_BUFFER, binding, self._glo, offset, size)
286+
287+
def bind_to_storage_buffer(self, binding: int = 0, offset: int = 0, size: int = -1) -> None:
288+
"""
289+
Bind this buffer as a shader storage buffer.
290+
291+
Args:
292+
binding:
293+
The binding location
294+
offset:
295+
Byte offset in the buffer
296+
size:
297+
The size in bytes. The entire buffer will be mapped by default.
298+
"""
299+
if size < 0:
300+
size = self.size
301+
302+
gl.glBindBufferRange(gl.GL_SHADER_STORAGE_BUFFER, binding, self._glo, offset, size)

0 commit comments

Comments
 (0)
0