Skip to content

Commit

Permalink
Support creating and writing texture data using the buffer protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
einarf committed Jun 17, 2020
1 parent e754e49 commit c95cf5e
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 17 deletions.
6 changes: 3 additions & 3 deletions arcade/gl/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def blend_func(self, value: Tuple[int, int]):
def buffer(self, *, data: Optional[Any] = None, reserve: int = 0, usage: str = 'static') -> Buffer:
"""Create a new OpenGL Buffer object.
:param bytes data: The buffer data, This can be ``bytes`` or an object supporting the buffer protocol.
:param Any data: The buffer data, This can be ``bytes`` or an object supporting the buffer protocol.
:param int reserve: The number of bytes reserve
:param str usage: Buffer usage. 'static', 'dynamic' or 'stream'
"""
Expand All @@ -192,7 +192,7 @@ def texture(self,
*,
components: int = 4,
dtype: str = 'f1',
data: bytes = None,
data: Any = None,
wrap_x: gl.GLenum = None,
wrap_y: gl.GLenum = None,
filter: Tuple[gl.GLenum, gl.GLenum] = None) -> Texture:
Expand All @@ -208,7 +208,7 @@ def texture(self,
:param Tuple[int, int] size: The size of the texture
:param int components: Number of components (1: R, 2: RG, 3: RGB, 4: RGBA)
:param str dtype: The data type of each component: f1, f2, f4 / i1, i2, i4 / u1, u2, u4
:param buffer data: The texture data (optional)
:param Any data: The texture data (optional). Can be bytes or an object supporting the buffer protocol.
:param GLenum wrap_x: How the texture wraps in x direction
:param GLenum wrap_y: How the texture wraps in y direction
:param Tuple[GLenum, GLenum] filter: Minification and magnification filter
Expand Down
32 changes: 18 additions & 14 deletions arcade/gl/texture.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from ctypes import byref
import weakref
from typing import Tuple, Union, TYPE_CHECKING
from typing import Any, Tuple, Union, TYPE_CHECKING

from pyglet import gl

from .exceptions import ShaderException
from .buffer import Buffer
from .utils import data_to_ctypes

if TYPE_CHECKING: # handle import cycle caused by type hinting
from arcade.gl import Context
Expand Down Expand Up @@ -43,7 +44,7 @@ def __init__(self,
*,
components: int = 4,
dtype: str = 'f1',
data: bytes = None,
data: Any = None,
filter: Tuple[gl.GLuint, gl.GLuint] = None,
wrap_x: gl.GLuint = None,
wrap_y: gl.GLuint = None):
Expand All @@ -57,7 +58,7 @@ def __init__(self,
:param Tuple[int, int] size: The size of the texture
:param int components: The number of components (1: R, 2: RG, 3: RGB, 4: RGBA)
:param str dtype: The data type of each component: f1, f2, f4 / i1, i2, i4 / u1, u2, u4
:param bytes data: The byte data to initialize the texture with
:param Any data: The byte data of the texture. bytes or anything supporting the buffer protocol.
:param Tuple[gl.GLuint, gl.GLuint] filter: The minification/magnification filter of the texture
:param gl.GLuint wrap_s
:param data: The texture data (optional)
Expand Down Expand Up @@ -92,6 +93,10 @@ def __init__(self,
gl.glBindTexture(self._target, self._glo)
gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1)
gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)

if data is not None:
byte_length, data = data_to_ctypes(data)

try:
_format, _internal_format, self._type, self._component_size = format_info
self._format = _format[components]
Expand Down Expand Up @@ -247,7 +252,16 @@ def write(self, data: Union[bytes, Buffer], level: int = 0, viewport=None) -> No
else:
raise ValueError("Viewport must be of length 2 or 4")

if isinstance(data, bytes):
if isinstance(data, Buffer):
gl.glBindBuffer(gl.GL_PIXEL_UNPACK_BUFFER, data.glo)
gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glBindTexture(self._target, self._glo)
gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1)
gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)
gl.glTexSubImage2D(self._target, level, x, y, w, h, self._format, self._type, 0)
gl.glBindBuffer(gl.GL_PIXEL_UNPACK_BUFFER, 0)
else:
byte_size, data = data_to_ctypes(data)
gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glBindTexture(self._target, self._glo)
gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1)
Expand All @@ -263,16 +277,6 @@ def write(self, data: Union[bytes, Buffer], level: int = 0, viewport=None) -> No
self._type, # type
data, # pixel data
)
elif isinstance(data, Buffer):
gl.glBindBuffer(gl.GL_PIXEL_UNPACK_BUFFER, data.glo)
gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glBindTexture(self._target, self._glo)
gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1)
gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)
gl.glTexSubImage2D(self._target, level, x, y, w, h, self._format, self._type, 0)
gl.glBindBuffer(gl.GL_PIXEL_UNPACK_BUFFER, 0)
else:
raise TypeError(f"data must be bytes or a Buffer, not {type(data)}")

def build_mipmaps(self, base=0, max_amount=1000) -> None:
"""Generate mipmaps for this texture.
Expand Down
12 changes: 12 additions & 0 deletions tests/unit2/test_opengl_texture.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ def test_write_read(ctx):
# TODO: Test LODs


def test_write_bufferprotocol(ctx):
"""Test creating texture from data using buffer protocol"""
# In constructor
data = array.array('B', [0, 0, 255, 255])
texture = ctx.texture((2, 2), components=1, data=data)
assert texture.read() == data.tobytes()
# Using write()
texture = ctx.texture((2, 2), components=1)
texture.write(data)
assert texture.read() == data.tobytes()


def test_creation_failed(ctx):
# Make an unreasonable texture
with pytest.raises(Exception):
Expand Down

0 comments on commit c95cf5e

Please sign in to comment.