Merge branch 'github/fork/emersion/pbo' into 'master'

Draft: render: asynchronous read_pixels

See merge request wlroots/wlroots!2099
This commit is contained in:
Simon Ser 2022-11-12 14:25:20 +00:00
commit 54769e21db
4 changed files with 189 additions and 5 deletions

View file

@ -38,6 +38,11 @@ struct wlr_egl {
PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT;
PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT;
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT;
PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;
PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR;
PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID;
PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR;
PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;
} procs;
bool has_modifiers;
@ -114,4 +119,12 @@ bool wlr_egl_unset_current(struct wlr_egl *egl);
bool wlr_egl_is_current(struct wlr_egl *egl);
EGLSyncKHR wlr_egl_create_sync(struct wlr_egl *egl, int fence_fd);
void wlr_egl_destroy_sync(struct wlr_egl *egl, EGLSyncKHR sync);
int wlr_egl_dup_fence_fd(struct wlr_egl *egl, EGLSyncKHR sync);
bool wlr_egl_wait_sync(struct wlr_egl *egl, EGLSyncKHR sync);
#endif

View file

@ -48,6 +48,9 @@ struct wlr_gles2_renderer {
bool EXT_texture_type_2_10_10_10_REV;
bool OES_texture_half_float_linear;
bool EXT_texture_norm16;
bool NV_pixel_buffer_object;
bool OES_mapbuffer;
bool EXT_map_buffer_range;
} exts;
struct {
@ -57,6 +60,8 @@ struct wlr_gles2_renderer {
PFNGLPOPDEBUGGROUPKHRPROC glPopDebugGroupKHR;
PFNGLPUSHDEBUGGROUPKHRPROC glPushDebugGroupKHR;
PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES;
PFNGLMAPBUFFERRANGEEXTPROC glMapBufferRangeEXT;
PFNGLUNMAPBUFFEROESPROC glUnmapBufferOES;
} procs;
struct {

View file

@ -344,6 +344,19 @@ static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) {
egl->exts.IMG_context_priority =
check_egl_ext(display_exts_str, "EGL_IMG_context_priority");
if (check_egl_ext(display_exts_str, "EGL_KHR_fence_sync") &&
check_egl_ext(display_exts_str, "EGL_ANDROID_native_fence_sync")) {
load_egl_proc(&egl->procs.eglCreateSyncKHR, "eglCreateSyncKHR");
load_egl_proc(&egl->procs.eglDestroySyncKHR, "eglDestroySyncKHR");
load_egl_proc(&egl->procs.eglDupNativeFenceFDANDROID,
"eglDupNativeFenceFDANDROID");
load_egl_proc(&egl->procs.eglClientWaitSyncKHR, "eglClientWaitSyncKHR");
}
if (check_egl_ext(display_exts_str, "EGL_KHR_wait_sync")) {
load_egl_proc(&egl->procs.eglWaitSyncKHR, "eglWaitSyncKHR");
}
wlr_log(WLR_INFO, "Using EGL %d.%d", (int)major, (int)minor);
wlr_log(WLR_INFO, "Supported EGL display extensions: %s", display_exts_str);
if (device_exts_str != NULL) {
@ -986,3 +999,65 @@ int wlr_egl_dup_drm_fd(struct wlr_egl *egl) {
return render_fd;
}
EGLSyncKHR wlr_egl_create_sync(struct wlr_egl *egl, int fence_fd) {
if (!egl->procs.eglCreateSyncKHR) {
return EGL_NO_SYNC_KHR;
}
EGLint attribs[3] = { EGL_NONE };
int dup_fd = -1;
if (fence_fd >= 0) {
dup_fd = fcntl(fence_fd, F_DUPFD_CLOEXEC, 0);
if (dup_fd < 0) {
wlr_log_errno(WLR_ERROR, "dup failed");
return EGL_NO_SYNC_KHR;
}
attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID;
attribs[1] = dup_fd;
attribs[2] = EGL_NONE;
}
EGLSyncKHR sync = egl->procs.eglCreateSyncKHR(egl->display,
EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
if (sync == EGL_NO_SYNC_KHR) {
wlr_log(WLR_ERROR, "eglCreateSyncKHR failed");
if (dup_fd >= 0) {
close(dup_fd);
}
}
return sync;
}
void wlr_egl_destroy_sync(struct wlr_egl *egl, EGLSyncKHR sync) {
if (sync == EGL_NO_SYNC_KHR) {
return;
}
assert(egl->procs.eglDestroySyncKHR);
if (egl->procs.eglDestroySyncKHR(egl->display, sync) != EGL_TRUE) {
wlr_log(WLR_ERROR, "eglDestroySyncKHR failed");
}
}
int wlr_egl_dup_fence_fd(struct wlr_egl *egl, EGLSyncKHR sync) {
if (!egl->procs.eglDupNativeFenceFDANDROID) {
return -1;
}
int fd = egl->procs.eglDupNativeFenceFDANDROID(egl->display, sync);
if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
wlr_log(WLR_ERROR, "eglDupNativeFenceFDANDROID failed");
return -1;
}
return fd;
}
bool wlr_egl_wait_sync(struct wlr_egl *egl, EGLSyncKHR sync) {
if (egl->procs.eglWaitSyncKHR(egl->display, sync, 0) != EGL_TRUE) {
wlr_log(WLR_ERROR, "eglWaitSyncKHR failed");
return false;
}
return true;
}

View file

@ -2,6 +2,7 @@
#include <drm_fourcc.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@ -443,28 +444,105 @@ static bool gles2_read_pixels(struct wlr_renderer *wlr_renderer,
push_gles2_debug(renderer);
// Make sure any pending drawing is finished before we try to read it
glFinish();
glGetError(); // Clear the error flag
unsigned char *p = (unsigned char *)data + dst_y * stride;
uint32_t pack_stride = width * drm_fmt->bpp / 8;
unsigned char *out_p = p;
#if 1
GLuint pbo = 0;
#else
static GLuint pbo = 0;
#endif
bool use_pbo = true;
if (renderer->exts.NV_pixel_buffer_object && renderer->exts.OES_mapbuffer &&
renderer->exts.EXT_map_buffer_range && use_pbo) {
// Ideally we should use the GL_STREAM_READ usage instead of
// GL_STREAM_DRAW, however it's not available in GLES2. It's just a
// hint and it's in general ignored by GL implementations, see:
// https://github.com/KhronosGroup/OpenGL-API/issues/66
if (pbo == 0) {
wlr_log(WLR_DEBUG, "create PBO");
glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_PACK_BUFFER_NV, pbo);
glBufferData(GL_PIXEL_PACK_BUFFER_NV, stride * height, NULL,
GL_STREAM_DRAW);
} else {
glBindBuffer(GL_PIXEL_PACK_BUFFER_NV, pbo);
}
out_p = NULL;
}
if (pack_stride == stride && dst_x == 0) {
// Under these particular conditions, we can read the pixels with only
// one glReadPixels call
glReadPixels(src_x, src_y, width, height, fmt->gl_format, fmt->gl_type, p);
wlr_log(WLR_DEBUG, "glReadPixels start");
glReadPixels(src_x, src_y, width, height, fmt->gl_format, fmt->gl_type, out_p);
wlr_log(WLR_DEBUG, "glReadPixels end");
} else {
// Unfortunately GLES2 doesn't support GL_PACK_*, so we have to read
// the lines out row by row
for (size_t i = 0; i < height; ++i) {
uint32_t y = src_y + i;
glReadPixels(src_x, y, width, 1, fmt->gl_format,
fmt->gl_type, p + i * stride + dst_x * drm_fmt->bpp / 8);
fmt->gl_type, NULL /* TODO */);
}
}
if (pbo != 0) {
glBindBuffer(GL_PIXEL_PACK_BUFFER_NV, 0);
glFlush();
EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, -1);
#if 0
int fence_fd = wlr_egl_dup_fence_fd(renderer->egl, sync);
wlr_log(WLR_DEBUG, "poll start %d", fence_fd);
struct pollfd pollfd = { .fd = fence_fd, .events = POLLIN };
if (poll(&pollfd, 1, -1) <= 0) {
wlr_log(WLR_ERROR, "poll failed");
}
wlr_log(WLR_DEBUG, "poll end");
close(fence_fd);
#else
wlr_log(WLR_DEBUG, "eglClientWaitSyncKHR start");
EGLint ret = renderer->egl->procs.eglClientWaitSyncKHR(
renderer->egl->display, sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
EGL_FOREVER_KHR);
if (ret != EGL_CONDITION_SATISFIED_KHR) {
wlr_log(WLR_ERROR, "eglClientWaitSyncKHR failed");
return false;
}
wlr_log(WLR_DEBUG, "eglClientWaitSyncKHR end");
#endif
wlr_egl_destroy_sync(renderer->egl, sync);
wlr_log(WLR_DEBUG, "glMapBufferRangeEXT");
// glMapBufferOES doesn't allow for read access, so we need to use
// glMapBufferRangeEXT instead
glBindBuffer(GL_PIXEL_PACK_BUFFER_NV, pbo);
unsigned char *in = renderer->procs.glMapBufferRangeEXT(
GL_PIXEL_PACK_BUFFER_NV, 0, stride * height, GL_MAP_READ_BIT_EXT);
// TODO: error handling
wlr_log(WLR_DEBUG, "memcpy");
memcpy(p, in, stride * height);
wlr_log(WLR_DEBUG, "glUnmapBufferOES");
renderer->procs.glUnmapBufferOES(GL_PIXEL_PACK_BUFFER_NV);
glBindBuffer(GL_PIXEL_PACK_BUFFER_NV, 0);
wlr_log(WLR_DEBUG, "done!");
}
pop_gles2_debug(renderer);
return glGetError() == GL_NO_ERROR;
@ -769,6 +847,19 @@ struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) {
"glEGLImageTargetRenderbufferStorageOES");
}
renderer->exts.NV_pixel_buffer_object =
check_gl_ext(exts_str, "GL_NV_pixel_buffer_object");
if (check_gl_ext(exts_str, "GL_OES_mapbuffer")) {
renderer->exts.OES_mapbuffer = true;
load_gl_proc(&renderer->procs.glUnmapBufferOES, "glUnmapBufferOES");
}
if (check_gl_ext(exts_str, "GL_EXT_map_buffer_range")) {
renderer->exts.EXT_map_buffer_range = true;
load_gl_proc(&renderer->procs.glMapBufferRangeEXT,
"glMapBufferRangeEXT");
}
if (renderer->exts.KHR_debug) {
glEnable(GL_DEBUG_OUTPUT_KHR);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);