/* * Copyright (C) 2021 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #include "config.h" #include "grd-egl-thread.h" #include #include #include #ifndef EGL_DRM_RENDER_NODE_FILE_EXT #define EGL_DRM_RENDER_NODE_FILE_EXT 0x3377 #endif struct _GrdEglThread { GThread *thread; GMutex mutex; GCond cond; GAsyncQueue *task_queue; GHashTable *slot_table; GHashTable *modifiers; struct { gboolean initialized; GError *error; GMainContext *main_context; GMainLoop *main_loop; EGLDisplay egl_display; EGLContext egl_context; const char *drm_render_node; GSource *egl_thread_source; } impl; }; typedef struct _GrdEglTask { GFunc func; GDestroyNotify destroy; GrdEglThreadCallback callback; gpointer callback_user_data; GDestroyNotify callback_destroy; } GrdEglTask; typedef struct { GrdEglTask base; GMutex task_mutex; GrdEglTask *task; } GrdReplaceableTask; typedef struct _GrdEglTaskCustom { GrdEglTask base; GrdEglThreadCustomFunc custom_func; gpointer user_data; } GrdEglTaskCustom; typedef struct _GrdEglTaskSync { GrdEglTask base; } GrdEglTaskSync; typedef struct _GrdEglTaskUpload { GrdEglTask base; GLuint pbo; uint32_t height; uint32_t stride; uint8_t *src_data; GrdEglThreadAllocBufferFunc allocate_func; gpointer allocate_user_data; GDestroyNotify allocate_user_data_destroy; GrdEglThreadCustomFunc realize_func; gpointer realize_user_data; GDestroyNotify realize_user_data_destroy; } GrdEglTaskUpload; typedef struct _GrdEglTaskDeallocateMemory { GrdEglTask base; GLuint pbo; GrdEglThreadDeallocBufferFunc deallocate_func; gpointer deallocate_user_data; } GrdEglTaskDeallocateMemory; typedef struct _GrdEglTaskAllocateMemory { GrdEglTask base; uint32_t height; uint32_t stride; GrdEglThreadAllocBufferFunc allocate_func; gpointer allocate_user_data; } GrdEglTaskAllocateMemory; typedef struct _GrdEglTaskDownload { GrdEglTask base; uint8_t *dst_data; int dst_row_width; uint32_t format; unsigned int width; unsigned int height; uint32_t n_planes; int *fds; uint32_t *strides; uint32_t *offsets; uint64_t *modifiers; } GrdEglTaskDownload; static void grd_egl_task_free (GrdEglTask *task) { if (task->callback_destroy) g_clear_pointer (&task->callback_user_data, task->callback_destroy); g_free (task); } static void run_replaceable_task_in_impl (gpointer data, gpointer user_data) { GrdEglThread *egl_thread = data; GrdReplaceableTask *replaceable_task = user_data; g_autoptr (GMutexLocker) locker = NULL; GrdEglTask *task; locker = g_mutex_locker_new (&replaceable_task->task_mutex); task = g_steal_pointer (&replaceable_task->task); if (!task) return; g_clear_pointer (&locker, g_mutex_locker_free); task->func (egl_thread, task); task->destroy (task); } static void replaceable_task_dummy_free (GrdEglTask *task_base) { } static GrdReplaceableTask * replaceable_task_new (void) { GrdReplaceableTask *replaceable_task; replaceable_task = g_new0 (GrdReplaceableTask, 1); replaceable_task->base.func = run_replaceable_task_in_impl; replaceable_task->base.destroy = (GDestroyNotify) replaceable_task_dummy_free; g_mutex_init (&replaceable_task->task_mutex); return replaceable_task; } static void replaceable_task_replace_task (GrdReplaceableTask *replaceable_task, GrdEglTask *new_task) { GrdEglTask *task; g_mutex_lock (&replaceable_task->task_mutex); task = g_steal_pointer (&replaceable_task->task); if (task) { if (task->callback) task->callback (FALSE, task->callback_user_data); task->destroy (task); } replaceable_task->task = new_task; g_mutex_unlock (&replaceable_task->task_mutex); } static void replaceable_task_free (GrdReplaceableTask *replaceable_task) { replaceable_task_replace_task (replaceable_task, NULL); g_mutex_clear (&replaceable_task->task_mutex); grd_egl_task_free (&replaceable_task->base); } static gboolean is_hardware_accelerated (void) { const char *renderer; gboolean is_software; renderer = (const char *) glGetString (GL_RENDERER); g_assert_cmpuint (glGetError (), ==, GL_NO_ERROR); if (!renderer) { g_warning ("OpenGL driver returned NULL as the renderer, " "something is wrong"); return FALSE; } is_software = (strstr (renderer, "llvmpipe") || strstr (renderer, "softpipe") || strstr (renderer, "software rasterizer") || strstr (renderer, "Software Rasterizer") || strstr (renderer, "SWR")); return !is_software; } static gboolean query_format_modifiers (GrdEglThread *egl_thread, EGLDisplay egl_display, GError **error) { EGLint n_formats = 0; g_autofree EGLint *formats = NULL; EGLint i; if (!eglQueryDmaBufFormatsEXT (egl_display, 0, NULL, &n_formats)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to query number of DMA buffer formats"); return FALSE; } if (n_formats == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No available DMA buffer formats"); return FALSE; } formats = g_new0 (EGLint, n_formats); if (!eglQueryDmaBufFormatsEXT (egl_display, n_formats, formats, &n_formats)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to query DMA buffer formats"); return FALSE; } for (i = 0; i < n_formats; i++) { EGLint n_modifiers; GArray *modifiers; if (!eglQueryDmaBufModifiersEXT (egl_display, formats[i], 0, NULL, NULL, &n_modifiers)) { g_warning ("Failed to query number of modifiers for format %d", formats[0]); continue; } if (n_modifiers == 0) { g_debug ("No modifiers available for format %d", formats[0]); continue; } modifiers = g_array_sized_new (FALSE, FALSE, sizeof (EGLuint64KHR), n_modifiers); g_array_set_size (modifiers, n_modifiers); if (!eglQueryDmaBufModifiersEXT (egl_display, formats[i], n_modifiers, (EGLuint64KHR *) modifiers->data, NULL, &n_modifiers)) { g_warning ("Failed to query modifiers for format %d", formats[0]); continue; } g_hash_table_insert (egl_thread->modifiers, GINT_TO_POINTER (formats[i]), modifiers); } return TRUE; } static gboolean lookup_drm_render_node (GrdEglThread *egl_thread, EGLDisplay egl_display, GError **error) { EGLAttrib egl_attrib = 0; EGLDeviceEXT egl_device; const char *egl_extensions; if (!eglQueryDisplayAttribEXT (egl_display, EGL_DEVICE_EXT, &egl_attrib)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Failed to retrieve EGL device"); return FALSE; } g_assert (egl_attrib); egl_device = (EGLDeviceEXT) egl_attrib; egl_extensions = eglQueryDeviceStringEXT (egl_device, EGL_EXTENSIONS); if (!egl_extensions) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Failed to retrieve EGL device extensions"); return FALSE; } if (!strstr (egl_extensions, "EGL_EXT_device_drm_render_node")) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Missing extension 'EGL_EXT_device_drm_render_node'"); return FALSE; } egl_thread->impl.drm_render_node = eglQueryDeviceStringEXT (egl_device, EGL_DRM_RENDER_NODE_FILE_EXT); if (!egl_thread->impl.drm_render_node) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Failed to retrieve EGL device extensions"); return FALSE; } return TRUE; } static gboolean initialize_egl_context (GrdEglThread *egl_thread, EGLenum egl_platform, GError **error) { EGLDisplay egl_display; EGLint major, minor; EGLint *attrs; EGLContext egl_context; g_clear_error (error); egl_display = eglGetPlatformDisplayEXT (egl_platform, NULL, NULL); if (egl_display == EGL_NO_DISPLAY) { int code = egl_platform == EGL_PLATFORM_DEVICE_EXT ? G_IO_ERROR_NOT_SUPPORTED : G_IO_ERROR_FAILED; g_set_error (error, G_IO_ERROR, code, "Failed to get EGL display"); return FALSE; } if (!eglInitialize (egl_display, &major, &minor)) { eglTerminate (egl_display); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to initialize EGL display"); return FALSE; } if (!eglBindAPI (EGL_OPENGL_ES_API)) { eglTerminate (egl_display); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to bind OpenGL ES API"); return FALSE; } if (egl_platform == EGL_PLATFORM_WAYLAND_EXT) { if (!epoxy_has_egl_extension (egl_display, "EGL_MESA_configless_context")) { eglTerminate (egl_display); g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Missing extension 'EGL_MESA_configless_context'"); return FALSE; } } if (!epoxy_has_egl_extension (egl_display, "EGL_EXT_image_dma_buf_import_modifiers")) { eglTerminate (egl_display); g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Missing extension 'EGL_EXT_image_dma_buf_import_modifiers'"); return FALSE; } attrs = (EGLint[]) { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; egl_context = eglCreateContext (egl_display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs); if (egl_context == EGL_NO_CONTEXT) { eglTerminate (egl_display); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to create EGL context"); return FALSE; } eglMakeCurrent (egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context); if (!query_format_modifiers (egl_thread, egl_display, error)) { eglMakeCurrent (egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext (egl_display, egl_context); eglTerminate (egl_display); return FALSE; } if (g_hash_table_size (egl_thread->modifiers) == 0) { eglMakeCurrent (egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext (egl_display, egl_context); eglTerminate (egl_display); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No DMA buffer modifiers / format pair found"); return FALSE; } if (!is_hardware_accelerated ()) { eglMakeCurrent (egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext (egl_display, egl_context); eglTerminate (egl_display); g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "EGL context not hardware accelerated"); return FALSE; } if (!lookup_drm_render_node (egl_thread, egl_display, error)) { eglMakeCurrent (egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext (egl_display, egl_context); eglTerminate (egl_display); return FALSE; } g_debug ("[EGL] DRM render node path: %s", egl_thread->impl.drm_render_node); egl_thread->impl.egl_display = egl_display; egl_thread->impl.egl_context = egl_context; return TRUE; } static gboolean grd_egl_init_in_impl (GrdEglThread *egl_thread, GError **error) { gboolean initialized; if (!epoxy_has_egl_extension (NULL, "EGL_EXT_platform_base")) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Missing extension 'EGL_EXT_platform_base'"); return FALSE; } initialized = initialize_egl_context (egl_thread, EGL_PLATFORM_WAYLAND_EXT, error); if (initialized) { g_debug ("EGL context initialized with platform " "EGL_PLATFORM_WAYLAND_EXT"); } else { initialized = initialize_egl_context (egl_thread, EGL_PLATFORM_DEVICE_EXT, error); if (initialized) { g_debug ("EGL context initialized with platform " "EGL_PLATFORM_DEVICE_EXT"); } } return initialized; } static void grd_egl_task_download_free (GrdEglTask *task_base) { GrdEglTaskDownload *task = (GrdEglTaskDownload *) task_base; g_free (task->fds); g_free (task->strides); g_free (task->offsets); g_free (task->modifiers); grd_egl_task_free (task_base); } static void grd_egl_task_upload_free (GrdEglTask *task_base) { GrdEglTaskUpload *task = (GrdEglTaskUpload *) task_base; task->allocate_user_data_destroy (task->allocate_user_data); task->realize_user_data_destroy (task->realize_user_data); grd_egl_task_free (task_base); } static void grd_thread_dispatch_in_impl (GrdEglThread *egl_thread) { GrdEglTask *task; task = g_async_queue_try_pop (egl_thread->task_queue); if (!task) return; task->func (egl_thread, task); task->destroy (task); } typedef struct _EglTaskSource { GSource base; GrdEglThread *egl_thread; } EglTaskSource; static gboolean egl_task_source_prepare (GSource *source, int *timeout) { EglTaskSource *task_source = (EglTaskSource *) source; GrdEglThread *egl_thread = task_source->egl_thread; g_assert (g_source_get_context (source) == egl_thread->impl.main_context); *timeout = -1; return g_async_queue_length (egl_thread->task_queue) > 0; } static gboolean egl_task_source_check (GSource *source) { EglTaskSource *task_source = (EglTaskSource *) source; GrdEglThread *egl_thread = task_source->egl_thread; g_assert (g_source_get_context (source) == egl_thread->impl.main_context); return g_async_queue_length (egl_thread->task_queue) > 0; } static gboolean egl_task_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { EglTaskSource *task_source = (EglTaskSource *) source; GrdEglThread *egl_thread = task_source->egl_thread; g_assert (g_source_get_context (source) == egl_thread->impl.main_context); grd_thread_dispatch_in_impl (egl_thread); return G_SOURCE_CONTINUE; } static GSourceFuncs egl_task_source_funcs = { .prepare = egl_task_source_prepare, .check = egl_task_source_check, .dispatch = egl_task_source_dispatch, }; static gpointer grd_egl_thread_func (gpointer user_data) { GrdEglThread *egl_thread = user_data; GError *error = NULL; egl_thread->impl.main_context = g_main_context_new (); if (!grd_egl_init_in_impl (egl_thread, &error)) { g_propagate_error (&egl_thread->impl.error, error); g_mutex_lock (&egl_thread->mutex); egl_thread->impl.initialized = TRUE; g_cond_signal (&egl_thread->cond); g_mutex_unlock (&egl_thread->mutex); return NULL; } egl_thread->impl.egl_thread_source = g_source_new (&egl_task_source_funcs, sizeof (EglTaskSource)); ((EglTaskSource *) egl_thread->impl.egl_thread_source)->egl_thread = egl_thread; g_source_set_name (egl_thread->impl.egl_thread_source, "EGL thread source"); g_source_set_ready_time (egl_thread->impl.egl_thread_source, -1); g_source_attach (egl_thread->impl.egl_thread_source, egl_thread->impl.main_context); egl_thread->task_queue = g_async_queue_new (); egl_thread->impl.main_loop = g_main_loop_new (egl_thread->impl.main_context, FALSE); g_mutex_lock (&egl_thread->mutex); egl_thread->impl.initialized = TRUE; g_cond_signal (&egl_thread->cond); g_mutex_unlock (&egl_thread->mutex); g_main_loop_run (egl_thread->impl.main_loop); g_source_destroy (egl_thread->impl.egl_thread_source); g_source_unref (egl_thread->impl.egl_thread_source); g_clear_pointer (&egl_thread->impl.main_loop, g_main_loop_unref); while (TRUE) { GrdEglTask *task; task = g_async_queue_try_pop (egl_thread->task_queue); if (!task) break; task->destroy (task); } g_async_queue_unref (egl_thread->task_queue); eglMakeCurrent (egl_thread->impl.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext (egl_thread->impl.egl_display, egl_thread->impl.egl_context); eglTerminate (egl_thread->impl.egl_display); return NULL; } GrdEglThread * grd_egl_thread_new (GError **error) { g_autoptr (GrdEglThread) egl_thread = NULL; egl_thread = g_new0 (GrdEglThread, 1); egl_thread->slot_table = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) replaceable_task_free); egl_thread->modifiers = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_array_unref); g_mutex_init (&egl_thread->mutex); g_cond_init (&egl_thread->cond); g_mutex_lock (&egl_thread->mutex); egl_thread->thread = g_thread_new ("GRD EGL thread", grd_egl_thread_func, egl_thread); while (!egl_thread->impl.initialized) g_cond_wait (&egl_thread->cond, &egl_thread->mutex); g_mutex_unlock (&egl_thread->mutex); if (egl_thread->impl.error) { g_propagate_error (error, egl_thread->impl.error); return NULL; } return g_steal_pointer (&egl_thread); } static gboolean quit_main_loop_in_impl (gpointer user_data) { GrdEglThread *egl_thread = user_data; if (egl_thread->impl.main_loop) g_main_loop_quit (egl_thread->impl.main_loop); return G_SOURCE_REMOVE; } void grd_egl_thread_free (GrdEglThread *egl_thread) { GSource *source; source = g_idle_source_new (); g_source_set_callback (source, quit_main_loop_in_impl, egl_thread, NULL); g_source_attach (source, egl_thread->impl.main_context); g_source_unref (source); g_thread_join (egl_thread->thread); g_mutex_clear (&egl_thread->mutex); g_cond_clear (&egl_thread->cond); g_clear_pointer (&egl_thread->modifiers, g_hash_table_unref); g_clear_pointer (&egl_thread->impl.main_context, g_main_context_unref); g_assert (g_hash_table_size (egl_thread->slot_table) == 0); g_clear_pointer (&egl_thread->slot_table, g_hash_table_unref); g_free (egl_thread); } const char * grd_egl_thread_get_drm_render_node (GrdEglThread *egl_thread) { return egl_thread->impl.drm_render_node; } void * grd_egl_thread_acquire_slot (GrdEglThread *egl_thread) { GrdReplaceableTask *replaceable_task; replaceable_task = replaceable_task_new (); g_async_queue_lock (egl_thread->task_queue); g_hash_table_add (egl_thread->slot_table, replaceable_task); g_async_queue_unlock (egl_thread->task_queue); return replaceable_task; } void grd_egl_thread_release_slot (GrdEglThread *egl_thread, GrdEglThreadSlot slot) { g_async_queue_lock (egl_thread->task_queue); g_hash_table_remove (egl_thread->slot_table, slot); g_async_queue_unlock (egl_thread->task_queue); } static EGLImageKHR create_dmabuf_image (GrdEglThread *egl_thread, unsigned int width, unsigned int height, uint32_t format, uint32_t n_planes, const int *fds, const uint32_t *strides, const uint32_t *offsets, const uint64_t *modifiers) { EGLint attribs[37]; int atti = 0; EGLImageKHR egl_image; attribs[atti++] = EGL_WIDTH; attribs[atti++] = width; attribs[atti++] = EGL_HEIGHT; attribs[atti++] = height; attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; attribs[atti++] = format; if (n_planes > 0) { attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT; attribs[atti++] = fds[0]; attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; attribs[atti++] = offsets[0]; attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; attribs[atti++] = strides[0]; if (modifiers) { attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; attribs[atti++] = modifiers[0] & 0xFFFFFFFF; attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; attribs[atti++] = modifiers[0] >> 32; } } if (n_planes > 1) { attribs[atti++] = EGL_DMA_BUF_PLANE1_FD_EXT; attribs[atti++] = fds[1]; attribs[atti++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT; attribs[atti++] = offsets[1]; attribs[atti++] = EGL_DMA_BUF_PLANE1_PITCH_EXT; attribs[atti++] = strides[1]; if (modifiers) { attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT; attribs[atti++] = modifiers[1] & 0xFFFFFFFF; attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT; attribs[atti++] = modifiers[1] >> 32; } } if (n_planes > 2) { attribs[atti++] = EGL_DMA_BUF_PLANE2_FD_EXT; attribs[atti++] = fds[2]; attribs[atti++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT; attribs[atti++] = offsets[2]; attribs[atti++] = EGL_DMA_BUF_PLANE2_PITCH_EXT; attribs[atti++] = strides[2]; if (modifiers) { attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT; attribs[atti++] = modifiers[2] & 0xFFFFFFFF; attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT; attribs[atti++] = modifiers[2] >> 32; } } attribs[atti++] = EGL_NONE; g_assert (atti <= G_N_ELEMENTS (attribs)); egl_image = eglCreateImageKHR (egl_thread->impl.egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); if (egl_image == EGL_NO_IMAGE) { g_warning ("Failed to import DMA buffer as EGL image: %d", eglGetError ()); } return egl_image; } static gboolean bind_egl_image (GrdEglThread *egl_thread, EGLImageKHR egl_image, int dst_row_width, GLuint *tex, GLuint *fbo) { GLenum error; glGenTextures (1, tex); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindTexture (GL_TEXTURE_2D, *tex); glPixelStorei (GL_PACK_ROW_LENGTH, dst_row_width); glEGLImageTargetTexture2DOES (GL_TEXTURE_2D, egl_image); error = glGetError (); if (error != GL_NO_ERROR) { g_warning ("[EGL Thread] Failed to bind DMA buf texture: %u", error); return FALSE; } if (!fbo) return TRUE; glGenFramebuffers (1, fbo); glBindFramebuffer (GL_FRAMEBUFFER, *fbo); glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *tex, 0); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { g_warning ("Failed to bind DMA buf framebuffer"); return FALSE; } return TRUE; } static void read_pixels (uint8_t *dst_data, unsigned int width, unsigned int height) { glReadPixels (0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, dst_data); } static void download_in_impl (gpointer data, gpointer user_data) { GrdEglThread *egl_thread = data; GrdEglTaskDownload *task = user_data; EGLImageKHR egl_image = EGL_NO_IMAGE; gboolean success = FALSE; GLuint tex = 0; GLuint fbo = 0; egl_image = create_dmabuf_image (egl_thread, task->width, task->height, task->format, task->n_planes, task->fds, task->strides, task->offsets, task->modifiers); if (egl_image == EGL_NO_IMAGE) goto out; if (!bind_egl_image (egl_thread, egl_image, task->dst_row_width, &tex, task->dst_data ? &fbo : NULL)) goto out; if (task->dst_data) read_pixels (task->dst_data, task->width, task->height); success = TRUE; out: glBindBuffer (GL_PIXEL_PACK_BUFFER, 0); if (fbo) { glBindFramebuffer (GL_FRAMEBUFFER, 0); glDeleteFramebuffers (1, &fbo); } if (tex) { glBindTexture (GL_TEXTURE_2D, 0); glDeleteTextures (1, &tex); } if (egl_image != EGL_NO_IMAGE) eglDestroyImageKHR (egl_thread->impl.egl_display, egl_image); task->base.callback (success, task->base.callback_user_data); } static void allocate_in_impl (gpointer data, gpointer user_data) { GrdEglTaskAllocateMemory *task = user_data; gboolean success = FALSE; uint32_t buffer_size; GLuint pbo = 0; buffer_size = task->stride * task->height * sizeof (uint8_t); glGenBuffers (1, &pbo); glBindBuffer (GL_PIXEL_PACK_BUFFER, pbo); glBufferData (GL_PIXEL_PACK_BUFFER, buffer_size, NULL, GL_DYNAMIC_DRAW); glBindBuffer (GL_PIXEL_PACK_BUFFER, 0); if (task->allocate_func (task->allocate_user_data, pbo)) success = TRUE; if (!success && pbo) { glDeleteBuffers (1, &pbo); pbo = 0; } task->base.callback (success, task->base.callback_user_data); } static void deallocate_in_impl (gpointer data, gpointer user_data) { GrdEglTaskDeallocateMemory *task = user_data; task->deallocate_func (task->deallocate_user_data); if (task->pbo) glDeleteBuffers (1, &task->pbo); if (task->base.callback) task->base.callback (TRUE, task->base.callback_user_data); } static void upload_in_impl (gpointer data, gpointer user_data) { GrdEglTaskUpload *task = user_data; gboolean success = FALSE; uint32_t buffer_size; GLenum error; buffer_size = task->stride * task->height * sizeof (uint8_t); if (!task->pbo) { GLuint pbo = 0; glGenBuffers (1, &pbo); glBindBuffer (GL_PIXEL_PACK_BUFFER, pbo); glBufferData (GL_PIXEL_PACK_BUFFER, buffer_size, NULL, GL_DYNAMIC_DRAW); glBindBuffer (GL_PIXEL_PACK_BUFFER, 0); if (!task->allocate_func (task->allocate_user_data, pbo)) { g_warning ("[EGL Thread] Failed to allocate GL resources"); glDeleteBuffers (1, &pbo); goto out; } g_debug ("[EGL Thread] Allocating GL resources was successful"); task->pbo = pbo; } glBindBuffer (GL_PIXEL_PACK_BUFFER, task->pbo); glBufferData (GL_PIXEL_PACK_BUFFER, buffer_size, NULL, GL_DYNAMIC_DRAW); glBufferSubData (GL_PIXEL_PACK_BUFFER, 0, buffer_size, task->src_data); error = glGetError (); if (error != GL_NO_ERROR) { g_warning ("[EGL Thread] Failed to update buffer data: %u", error); goto out; } if (!task->realize_func (task->realize_user_data)) goto out; success = TRUE; out: glBindBuffer (GL_PIXEL_PACK_BUFFER, 0); task->base.callback (success, task->base.callback_user_data); } static void sync_in_impl (gpointer data, gpointer user_data) { GrdEglTaskSync *task = user_data; task->base.callback (TRUE, task->base.callback_user_data); } static void run_custom_task_in_impl (gpointer data, gpointer user_data) { GrdEglTaskCustom *task = user_data; gboolean success; success = task->custom_func (task->user_data); if (task->base.callback) task->base.callback (success, task->base.callback_user_data); } static void push_replaceable_task (GrdEglThread *egl_thread, GrdEglThreadSlot slot, GrdEglTask *task) { GrdReplaceableTask *replaceable_task = NULL; g_async_queue_lock (egl_thread->task_queue); if (!g_hash_table_lookup_extended (egl_thread->slot_table, slot, NULL, (gpointer *) &replaceable_task)) g_assert_not_reached (); g_assert (replaceable_task); replaceable_task_replace_task (replaceable_task, task); g_async_queue_push_unlocked (egl_thread->task_queue, replaceable_task); g_async_queue_unlock (egl_thread->task_queue); } void grd_egl_thread_download (GrdEglThread *egl_thread, GrdEglThreadSlot slot, uint8_t *dst_data, int dst_row_width, uint32_t format, unsigned int width, unsigned int height, uint32_t n_planes, const int *fds, const uint32_t *strides, const uint32_t *offsets, const uint64_t *modifiers, GrdEglThreadCallback callback, gpointer user_data, GDestroyNotify destroy) { GrdEglTaskDownload *task; task = g_new0 (GrdEglTaskDownload, 1); task->dst_data = dst_data; task->dst_row_width = dst_row_width; task->format = format; task->width = width; task->height = height; task->n_planes = n_planes; task->fds = g_memdup2 (fds, n_planes * sizeof (int)); task->strides = g_memdup2 (strides, n_planes * sizeof (uint32_t)); task->offsets = g_memdup2 (offsets, n_planes * sizeof (uint32_t)); task->modifiers = g_memdup2 (modifiers, n_planes * sizeof (uint64_t)); task->base.func = download_in_impl; task->base.destroy = (GDestroyNotify) grd_egl_task_download_free; task->base.callback = callback; task->base.callback_user_data = user_data; task->base.callback_destroy = destroy; if (slot) push_replaceable_task (egl_thread, slot, &task->base); else g_async_queue_push (egl_thread->task_queue, task); g_main_context_wakeup (egl_thread->impl.main_context); } void grd_egl_thread_allocate (GrdEglThread *egl_thread, uint32_t height, uint32_t stride, GrdEglThreadAllocBufferFunc allocate_func, gpointer allocate_user_data, GrdEglThreadCallback callback, gpointer user_data, GDestroyNotify destroy) { GrdEglTaskAllocateMemory *task; task = g_new0 (GrdEglTaskAllocateMemory, 1); task->height = height; task->stride = stride; task->allocate_func = allocate_func; task->allocate_user_data = allocate_user_data; task->base.func = allocate_in_impl; task->base.destroy = (GDestroyNotify) grd_egl_task_free; task->base.callback = callback; task->base.callback_user_data = user_data; task->base.callback_destroy = destroy; g_async_queue_push (egl_thread->task_queue, task); g_main_context_wakeup (egl_thread->impl.main_context); } void grd_egl_thread_deallocate (GrdEglThread *egl_thread, uint32_t pbo, GrdEglThreadDeallocBufferFunc deallocate_func, gpointer deallocate_user_data, GrdEglThreadCallback callback, gpointer user_data, GDestroyNotify destroy) { GrdEglTaskDeallocateMemory *task; task = g_new0 (GrdEglTaskDeallocateMemory, 1); task->pbo = pbo; task->deallocate_func = deallocate_func; task->deallocate_user_data = deallocate_user_data; task->base.func = deallocate_in_impl; task->base.destroy = (GDestroyNotify) grd_egl_task_free; task->base.callback = callback; task->base.callback_user_data = user_data; task->base.callback_destroy = destroy; g_async_queue_push (egl_thread->task_queue, task); g_main_context_wakeup (egl_thread->impl.main_context); } void grd_egl_thread_upload (GrdEglThread *egl_thread, GrdEglThreadSlot slot, uint32_t pbo, uint32_t height, uint32_t stride, uint8_t *src_data, GrdEglThreadAllocBufferFunc allocate_func, gpointer allocate_user_data, GDestroyNotify allocate_user_data_destroy, GrdEglThreadCustomFunc realize_func, gpointer realize_user_data, GDestroyNotify realize_user_data_destroy, GrdEglThreadCallback callback, gpointer user_data, GDestroyNotify destroy) { GrdEglTaskUpload *task; task = g_new0 (GrdEglTaskUpload, 1); task->pbo = pbo; task->height = height; task->stride = stride; task->src_data = src_data; task->allocate_func = allocate_func; task->allocate_user_data = allocate_user_data; task->allocate_user_data_destroy = allocate_user_data_destroy; task->realize_func = realize_func; task->realize_user_data = realize_user_data; task->realize_user_data_destroy = realize_user_data_destroy; task->base.func = upload_in_impl; task->base.destroy = (GDestroyNotify) grd_egl_task_upload_free; task->base.callback = callback; task->base.callback_user_data = user_data; task->base.callback_destroy = destroy; if (slot) push_replaceable_task (egl_thread, slot, &task->base); else g_async_queue_push (egl_thread->task_queue, task); g_main_context_wakeup (egl_thread->impl.main_context); } void grd_egl_thread_sync (GrdEglThread *egl_thread, GrdEglThreadCallback callback, gpointer user_data, GDestroyNotify destroy) { GrdEglTaskSync *task; task = g_new0 (GrdEglTaskSync, 1); task->base.func = sync_in_impl; task->base.destroy = (GDestroyNotify) grd_egl_task_free; task->base.callback = callback; task->base.callback_user_data = user_data; task->base.callback_destroy = destroy; g_async_queue_push (egl_thread->task_queue, task); g_main_context_wakeup (egl_thread->impl.main_context); } void grd_egl_thread_run_custom_task (GrdEglThread *egl_thread, GrdEglThreadCustomFunc custom_func, gpointer custom_func_data, GrdEglThreadCallback callback, gpointer user_data, GDestroyNotify destroy) { GrdEglTaskCustom *task; task = g_new0 (GrdEglTaskCustom, 1); task->custom_func = custom_func; task->user_data = custom_func_data; task->base.func = run_custom_task_in_impl; task->base.destroy = (GDestroyNotify) grd_egl_task_free; task->base.callback = callback; task->base.callback_user_data = user_data; task->base.callback_destroy = destroy; g_async_queue_push (egl_thread->task_queue, task); g_main_context_wakeup (egl_thread->impl.main_context); } gboolean grd_egl_thread_get_modifiers_for_format (GrdEglThread *egl_thread, uint32_t format, int *out_n_modifiers, uint64_t **out_modifiers) { GArray *modifiers; modifiers = g_hash_table_lookup (egl_thread->modifiers, GINT_TO_POINTER (format)); if (!modifiers) return FALSE; *out_n_modifiers = modifiers->len; *out_modifiers = g_memdup2 (modifiers->data, sizeof (uint64_t) * modifiers->len); return TRUE; }