Expose `glReadnPixels` function to Cython, and add a new Python API `glReadPixels_inplace`
Maintainer merge checklist
- [x] Title is descriptive/clear for inclusion in release notes.
- [x] Applied a
Component: xxxlabel. - [ ] Applied the
api-deprecationorapi-breaklabel. - [ ] Applied the
release-highlightlabel 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,versionchangedas 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)
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
I just realized that
glReadnPixelsisn’t available in OpenGL ES 2.0 — it was introduced in OpenGL ES 3.2. Would it be better to implementglReadPixels_inplacewithout 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.
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_inplaceinto two functions instead of using a single function that checks the availability ofglReadnPixelsinside 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.
@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
@misl6 Can I have your opinion?
The current implementation checks for the availability of
glReadnPixelsat 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.