Files
grd/grd-egl-thread.c
2026-02-13 13:06:50 +09:00

1326 lines
37 KiB
C

/*
* 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 <epoxy/egl.h>
#include <epoxy/gl.h>
#include <gio/gio.h>
#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;
}