diff --git a/include/render/egl.h b/include/render/egl.h index 36b70ae34..4ae82127b 100644 --- a/include/render/egl.h +++ b/include/render/egl.h @@ -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 diff --git a/include/render/gles2.h b/include/render/gles2.h index 6631d34ce..804916bf6 100644 --- a/include/render/gles2.h +++ b/include/render/gles2.h @@ -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 { diff --git a/render/egl.c b/render/egl.c index 0c25e0364..1013bbd9f 100644 --- a/render/egl.c +++ b/render/egl.c @@ -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; +} diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c index 8d0601504..c1e3af1f9 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -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);