community icon indicating copy to clipboard operation
community copied to clipboard

Expose `glReadnPixels` function to Cython, and add a new Python API `glReadPixels_inplace`

Open gottadiveintopython opened this issue 4 months ago • 5 comments

Maintainer merge checklist

  • [x] Title is descriptive/clear for inclusion in release notes.
  • [x] Applied a Component: xxx label.
  • [ ] Applied the api-deprecation or api-break label.
  • [ ] Applied the release-highlight label to be highlighted in release notes.
  • [ ] Added to the milestone version it was merged into.
  • [x] Unittests are included in PR.
  • [ ] Properly documented, including versionadded, versionchanged as needed.

Hi, I want to use Kivy in a way similar to Manim — that is, to write animations in Kivy and generate video files from them. However, the features Kivy currently provides make this workflow somewhat inconvenient, so I’d like to add a new one.

Why Kivy is currently inconvenient for this purpose

Kivy already has a Python API that captures the current frame: the glReadPixels(). However, each call to this function allocates and frees a memory buffer large enough to hold the screen’s pixels, which is inefficient. https://github.com/kivy/kivy/blob/91dc72d7cc979f37e2b1344bf9edd5db7957bbd1/kivy/graphics/opengl.pyx#L1154-L1185

Proposed functionality

This PR introduces glReadPixels_inplace(), which writes pixel data directly into a preallocated memory buffer.

from kivy.graphics.opengl import glReadPixels_inplace, GL_RGB, GL_UNSIGNED_BYTE

out_buf = bytearray(screen_width * screen_height * 3)
glReadPixels_inplace(0, 0, screen_width, screen_height, GL_RGB, GL_UNSIGNED_BYTE, out_buf)

gottadiveintopython avatar Sep 18 '25 03:09 gottadiveintopython

I just realized that glReadnPixels isn’t available in OpenGL ES 2.0 — it was introduced in OpenGL ES 3.2. Would it be better to implement glReadPixels_inplace without relying on it? https://registry.khronos.org/OpenGL/api/GLES2/gl2.h https://registry.khronos.org/OpenGL/api/GLES3/gl32.h

gottadiveintopython avatar Sep 23 '25 23:09 gottadiveintopython

I just realized that glReadnPixels isn’t available in OpenGL ES 2.0 — it was introduced in OpenGL ES 3.2. Would it be better to implement glReadPixels_inplace without relying on it? https://registry.khronos.org/OpenGL/api/GLES2/gl2.h https://registry.khronos.org/OpenGL/api/GLES3/gl32.h

We can add a guard about the availability on OpenGL ES 3.2 (and above), and if not available we can decide to use another (slower I guess) implementation.

misl6 avatar Oct 12 '25 10:10 misl6

I don't think I can fix the unit test failures on Windows because I don't have a Windows machine.

Also, here are the reasons why I implemented things the way I did:

  • I didn't use the pytest parametrization feature because it doesn't work with unittest-based tests.
  • I split glReadPixels_inplace into two functions instead of using a single function that checks the availability of glReadnPixels inside of it, so that both implementations can be tested.
# If we do this, we cannot test both paths, right?
def glReadPixels_inplace(...):
    if cgl.glReadnPixels == NULL:
        ...
    else:
        ...

However, I'm not sure this was the right choice because it makes the implementation uglier. Let me hear your opinion.

gottadiveintopython avatar Oct 17 '25 13:10 gottadiveintopython

@misl6 Can I have your opinion?

The current implementation checks for the availability of glReadnPixels at import time. Should this check instead be performed at call time, like so?

def glReadPixels_inplace(...):
    if cgl.glReadnPixels == NULL:
        # Implementation that does not rely on glReadnPixels
    else:
        # Implementation that relies on glReadnPixels

gottadiveintopython avatar Nov 09 '25 05:11 gottadiveintopython

@misl6 Can I have your opinion?

The current implementation checks for the availability of glReadnPixels at import time. Should this check instead be performed at call time, like so?

def glReadPixels_inplace(...):
    if cgl.glReadnPixels == NULL:
        # Implementation that does not rely on glReadnPixels
    else:
        # Implementation that relies on glReadnPixels

Checked the current implementation in this PR, and I do not see any potential drawbacks if kept during import time. Maybe, just explain with a few comments what you're doing in that chunk of code.

Style side: At call time looks cleaner, but is also less efficient. But I prefer efficiency, if well documented.

misl6 avatar Nov 09 '25 09:11 misl6