diff --git a/README.md b/README.md index 2f27f4f..a15e6ec 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,7 @@ Features: - Load textures into OpenGL. - Bind and unbind textures for rendering. +- Update a texture with an equivalent shaped image. - Delete textures when no longer needed. Example: @@ -270,6 +271,34 @@ texture.bind() texture.unbind() ``` +Another example when speed and memory management is crucial: + +```python +from tachypy import Texture +import numpy as np + +H, W = [256]*2 # say we have 256 pixels square images + +images = np.random.rand(n_insequence, H, W, 3) # that are also RGB. + +n_insequence = 200 +blank = np.zeros((H, W, 3), dtype=np.uint8) +textures = [Texture(blank.copy()) for _ in range(n_in_sequence)] +``` +In the code above, we pre-initialise a set of 200 textures, perhaps an RSVP or other psychophysics sequence. + +```python +for im, ct in enumerate(images): + textures[ct].update(im) +``` +Now whenever we start a new trial, in the waiting time, we can simply update the texture buffer with the new set of images for that trial, preventing any GPU leakage that could otherwise happen if we don't perform rigid garbage collection on every trial loop. The update method prevents our GPU from overload. This is especially useful when working with high refresh rate monitors (e.g. 480Hz>) + +```python +for t in textures: + t.delete() +``` +When the experiment is done, simply delete the textures, and voila. + ## Shapes Provides classes for drawing basic shapes and stimuli. diff --git a/src/tachypy/textures.py b/src/tachypy/textures.py index 273df21..25ba46f 100644 --- a/src/tachypy/textures.py +++ b/src/tachypy/textures.py @@ -54,9 +54,40 @@ def unbind(self): glBindTexture(GL_TEXTURE_2D, 0) # Reset the modelview matrix to avoid any transformations being carried over glLoadIdentity() + + def update(self, image): + """ + Update pixel data of an existing texture without reallocating GPU object. + image: np.ndarray (H, W, 3), uint8 + """ + glBindTexture(GL_TEXTURE_2D, self.texture_id) + glPixelStorei(GL_UNPACK_ALIGNMENT, 1) + + # If size matches, do sub-image update (fast, no reallocation) + w, h = image.shape[1], image.shape[0] + glTexSubImage2D( + GL_TEXTURE_2D, + 0, + 0, + 0, + w, + h, + GL_RGB, + GL_UNSIGNED_BYTE, + image + ) + glBindTexture(GL_TEXTURE_2D, 0) def delete(self): - glDeleteTextures([self.texture_id]) + """ + Texture deletion from GPU. + + :param self: Texture instance + :return: None + """ + if getattr(self, "texture_id", 0): + glDeleteTextures([self.texture_id]) + self.texture_id = 0 # ---------- Gestion du rect / position ---------- @@ -132,4 +163,4 @@ def draw(self, a_rect=None): glEnd() # unbind the texture - self.unbind() \ No newline at end of file + self.unbind()