/*****************************************************************************
* egl_gbm.c
* egl for wayland using GBM allocated buffers
*****************************************************************************
* Copyright (C) 2023 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <wayland-egl.h>
#include "video_output/wayland/registry.h"
#include "video_output/wayland/linux-dmabuf-client-protocol.h"
#include <gbm.h>
#include <fcntl.h>
#include <xf86drm.h>
#include "gl_common.h"
#include <vlc_common.h>
#include <vlc_threads.h>
#include <vlc_plugin.h>
#include <vlc_opengl.h>
#include <vlc_window.h>
#include <vlc_fs.h>
#define BO_DATA_PREALLOCATED_COUNT 4
static const char *clientExts;
static PFNGLFINISHPROC Finish;
#ifdef EGL_EXT_platform_base
static PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;
static PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC CreatePlatformWindowSurfaceEXT;
#endif
typedef struct bo_data_s
{
struct wl_buffer *wl_buffer;
size_t plane_count;
int fd[4];
bool used;
} bo_data_t;
typedef struct vlc_gl_sys_t
{
struct {
EGLDisplay display;
EGLSurface surface;
EGLContext context;
EGLConfig config;
bool is_current;
} egl;
struct {
struct wl_event_queue* wl_queue;
bool need_roundtrip;
struct zwp_linux_dmabuf_v1* dmabuf;
struct zwp_linux_dmabuf_feedback_v1* feedback;
} wayland;
struct {
struct gbm_device* device;
struct gbm_surface* surface;
struct gbm_bo* bo_prev;
struct gbm_bo* bo_next;
bo_data_t bo_data[BO_DATA_PREALLOCATED_COUNT];
uint32_t format;
} gbm;
} vlc_gl_sys_t;
static bool CheckAPI(EGLDisplay dpy, const char* api)
{
const char* apis = eglQueryString(dpy, EGL_CLIENT_APIS);
return vlc_gl_StrHasToken(apis, api);
}
static bool CheckClientExt(const char* name)
{
return vlc_gl_StrHasToken(clientExts, name);
}
static bool CheckDisplayExt(EGLDisplay display, const char *name)
{
const char *exts = eglQueryString(display, EGL_EXTENSIONS);
return vlc_gl_StrHasToken(exts, name);
}
static void LogDeviceInfo(vlc_gl_t *gl,
PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT,
EGLDeviceEXT device)
{
const char *device_ext = eglQueryDeviceStringEXT(device, EGL_EXTENSIONS);
if (device_ext == NULL)
device_ext = "";
/* EGL_EXT_device_query_name is a device extension, so it must be tested
* against the device's own extension string, not the client one. */
if (vlc_gl_StrHasToken(device_ext, "EGL_EXT_device_query_name"))
{
const char *vendor = eglQueryDeviceStringEXT(device, EGL_VENDOR);
const char *renderer = eglQueryDeviceStringEXT(device, EGL_RENDERER_EXT);
msg_Dbg(gl, " vendor='%s' renderer='%s'",
vendor ? vendor : "unknown", renderer ? renderer : "unknown");
}
msg_Dbg(gl, " extensions: %s", device_ext);
if (vlc_gl_StrHasToken(device_ext, "EGL_EXT_device_drm"))
{
const char *path = eglQueryDeviceStringEXT(device, EGL_DRM_DEVICE_FILE_EXT);
if (path != NULL)
msg_Dbg(gl, " drm device: %s", path);
}
if (vlc_gl_StrHasToken(device_ext, "EGL_EXT_device_drm_render_node"))
{
const char *path = eglQueryDeviceStringEXT(device, EGL_DRM_RENDER_NODE_FILE_EXT);
if (path != NULL)
msg_Dbg(gl, " drm render node: %s", path);
}
}
static void DisplayAvailableDevices(vlc_gl_t *gl)
{
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT =
(PFNEGLQUERYDEVICESEXTPROC)eglGetProcAddress("eglQueryDevicesEXT");
PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT =
(PFNEGLQUERYDEVICESTRINGEXTPROC)eglGetProcAddress("eglQueryDeviceStringEXT");
EGLint device_count = 0;
if (eglQueryDevicesEXT(0, NULL, &device_count) != EGL_TRUE
|| device_count <= 0)
{
msg_Dbg(gl, "could not enumerate EGL devices");
return;
}
EGLDeviceEXT *devices = vlc_alloc(device_count, sizeof(*devices));
if (devices == NULL)
return;
if (eglQueryDevicesEXT(device_count, devices, &device_count) != EGL_TRUE)
{
free(devices);
return;
}
msg_Dbg(gl, "EGL has %d devices:", device_count);
for (EGLint i = 0; i < device_count; ++i)
{
msg_Dbg(gl, " - device %d:", i);
LogDeviceInfo(gl, eglQueryDeviceStringEXT, devices[i]);
}
free(devices);
}
struct gl_api
{
const char name[10];
EGLenum api;
EGLint min_minor;
EGLint render_bit;
EGLint attr[3];
};
static int MakeCurrent (vlc_gl_t* gl)
{
vlc_gl_sys_t* sys = gl->sys;
if (sys->egl.surface == EGL_NO_SURFACE)
{
msg_Err(gl, "no surface to make current");
return VLC_EGENERIC;
}
if (eglMakeCurrent(sys->egl.display, sys->egl.surface, sys->egl.surface,
sys->egl.context) != EGL_TRUE)
{
msg_Err(gl, "failed to make current");
return VLC_EGENERIC;
}
sys->egl.is_current = true;
return VLC_SUCCESS;
}
static void ReleaseCurrent (vlc_gl_t* gl)
{
vlc_gl_sys_t* sys = gl->sys;
eglMakeCurrent(sys->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
sys->egl.is_current = false;
}
static EGLSurface CreateSurface(vlc_gl_t* gl, unsigned int width, unsigned int height)
{
vlc_gl_sys_t* sys = gl->sys;
sys->gbm.surface = gbm_surface_create(
sys->gbm.device,
width, height,
sys->gbm.format,
GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT);
if (!sys->gbm.surface)
{
// Nvidia proprietary driver doesn't support creation flags
sys->gbm.surface = gbm_surface_create(
sys->gbm.device,
width, height,
sys->gbm.format,
0);
}
if (!sys->gbm.surface)
{
msg_Err(gl, "can't create gbm surface");
return EGL_NO_SURFACE;
}
#ifdef EGL_EXT_platform_base
return CreatePlatformWindowSurfaceEXT(sys->egl.display, sys->egl.config, sys->gbm.surface, NULL);
#else
return eglCreateWindowSurface(sys->egl.display, sys->egl.config, (EGLNativeWindowType)sys->gbm.surface, NULL);
#endif
}
static void Resize(vlc_gl_t* gl, unsigned width, unsigned height)
{
vlc_gl_sys_t* sys = gl->sys;
if (sys->egl.surface)
eglDestroySurface(sys->egl.display, sys->egl.surface);
if (sys->gbm.surface)
gbm_surface_destroy(sys->gbm.surface);
sys->gbm.surface = NULL;
sys->gbm.bo_prev = NULL;
sys->gbm.bo_next = NULL;
sys->egl.surface = CreateSurface(gl, width, height);
}
static void ReleaseBoPrivateData(struct gbm_bo* bo, void* data)
{
VLC_UNUSED(bo);
bo_data_t* const bo_data = data;
assert(bo_data);
if (likely(bo_data->wl_buffer))
wl_buffer_destroy(bo_data->wl_buffer);
for (size_t i = 0; i < bo_data->plane_count; ++i)
close(bo_data->fd[i]);
assert(bo_data->used);
bo_data->used = false;
}
static void ReleaseAndFreeBoPrivateData(struct gbm_bo* bo, void *data)
{
ReleaseBoPrivateData(bo, data);
free(data);
}
static void LockNext(vlc_gl_t* gl)
{
vlc_gl_sys_t* sys = gl->sys;
struct gbm_bo* bo = gbm_surface_lock_front_buffer(sys->gbm.surface);
sys->gbm.bo_prev = sys->gbm.bo_next;
sys->gbm.bo_next = bo;
}
static void BindBuffer(vlc_gl_t* gl)
{
vlc_gl_sys_t* sys = gl->sys;
struct gbm_bo* bo = sys->gbm.bo_next;
if (!bo)
return;
uint32_t height = gbm_bo_get_height(bo);
uint32_t width = gbm_bo_get_width(bo);
bo_data_t *bo_data = gbm_bo_get_user_data(bo);
if (!bo_data)
{
void (*bo_data_release_func)(struct gbm_bo *, void *);
for (size_t i = 0; i < ARRAY_SIZE(sys->gbm.bo_data); ++i)
{
if (!sys->gbm.bo_data[i].used)
{
bo_data = &sys->gbm.bo_data[i];
bo_data_release_func = ReleaseBoPrivateData;
break;
}
}
if (!bo_data)
{
if (ARRAY_SIZE(sys->gbm.bo_data) > 0)
msg_Dbg(gl, "no available preallocated private bo data, allocating a new one");
bo_data = malloc(sizeof(bo_data_t));
if (unlikely(!bo_data))
return;
bo_data_release_func = ReleaseAndFreeBoPrivateData;
}
uint64_t modifier = gbm_bo_get_modifier(bo);
uint32_t format = gbm_bo_get_format(bo);
struct zwp_linux_buffer_params_v1* params = zwp_linux_dmabuf_v1_create_params(sys->wayland.dmabuf);
const size_t plane_count = gbm_bo_get_plane_count(bo);
assert(plane_count <= ARRAY_SIZE(bo_data->fd));
bo_data->plane_count = plane_count;
for (size_t i = 0; i < plane_count; ++i)
{
int fd = gbm_bo_get_fd_for_plane(bo, i);
bo_data->fd[i] = fd;
uint32_t stride = gbm_bo_get_stride_for_plane(bo, i);
uint32_t offset = gbm_bo_get_offset(bo, i);
if (fd <= 0)
{
msg_Warn(gl, "no fd for plane");
continue;
}
zwp_linux_buffer_params_v1_add(params, fd, i, offset,
stride,
modifier >> 32,
modifier & 0xffffffff);
}
bo_data->wl_buffer = zwp_linux_buffer_params_v1_create_immed(params, width, height, format, 0);
zwp_linux_buffer_params_v1_destroy(params);
bo_data->used = true;
if (unlikely(!bo_data->wl_buffer))
{
bo_data_release_func(bo, bo_data);
return;
}
gbm_bo_set_user_data(bo, bo_data, bo_data_release_func);
}
wl_surface_attach(gl->surface->handle.wl, bo_data->wl_buffer, 0, 0);
wl_surface_damage(gl->surface->handle.wl, 0, 0, width, height);
wl_surface_commit(gl->surface->handle.wl);
wl_display_roundtrip_queue(gl->surface->display.wl, sys->wayland.wl_queue);
}
static void FreePrev(vlc_gl_t* gl)
{
vlc_gl_sys_t* sys = gl->sys;
if (sys->gbm.bo_prev)
{
gbm_surface_release_buffer(sys->gbm.surface, sys->gbm.bo_prev);
sys->gbm.bo_prev = NULL;
}
}
static void SwapBuffers(vlc_gl_t* gl)
{
vlc_gl_sys_t* sys = gl->sys;
if (!gbm_surface_has_free_buffers(sys->gbm.surface))
{
msg_Warn(gl, "no free buffers");
return;
}
if (!sys->egl.is_current)
{
EGLSurface s_read = eglGetCurrentSurface(EGL_READ);
EGLSurface s_draw = eglGetCurrentSurface(EGL_DRAW);
EGLContext previous_context = eglGetCurrentContext();
eglMakeCurrent(sys->egl.display, sys->egl.surface, sys->egl.surface, sys->egl.context);
eglSwapBuffers(sys->egl.display, sys->egl.surface);
Finish();
eglMakeCurrent(sys->egl.display, s_read, s_draw, previous_context);
}
else
{
eglSwapBuffers(sys->egl.display, sys->egl.surface);
Finish();
}
LockNext(gl);
BindBuffer(gl);
FreePrev(gl);
}
static void DestroySurface(vlc_gl_t* gl)
{
vlc_gl_sys_t* sys = gl->sys;
gbm_surface_destroy(sys->gbm.surface);
}
static EGLConfig MatchEglConfigToGBM(vlc_gl_t* gl, EGLint cfgc, EGLConfig* cfgv, EGLint targetFormat )
{
vlc_gl_sys_t* sys = gl->sys;
for (int i = 0; i < cfgc; ++i) {
EGLint nativeFormat;
if (!eglGetConfigAttrib(sys->egl.display, cfgv[i], EGL_NATIVE_VISUAL_ID, &nativeFormat))
continue;
if (nativeFormat == targetFormat)
return cfgv[i];
}
return EGL_CAST(EGLConfig,0);
}
static bool FindEglConfig(vlc_gl_t* gl, bool need_alpha)
{
vlc_gl_sys_t* sys = gl->sys;
//we require a finer config type
const EGLint conf_attr[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_BUFFER_SIZE, 32,
EGL_ALPHA_SIZE, need_alpha ? 8 : 0,
EGL_RENDERABLE_TYPE, gl->api_type == VLC_OPENGL_ES2 ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_NONE
};
EGLint cfgc;
if (!eglGetConfigs(sys->egl.display, NULL, 0, &cfgc) || cfgc == 0)
return false;
EGLConfig* cfgv = malloc(cfgc * sizeof(EGLConfig));
if (eglChooseConfig(sys->egl.display, conf_attr, cfgv, cfgc, &cfgc) != EGL_TRUE
|| cfgc == 0)
{
msg_Err (gl, "cannot choose EGL configuration");
return false;
}
static const EGLint rgbaFormats[] = {
GBM_FORMAT_ARGB8888,
0
};
static const EGLint rgbFormats[] = {
GBM_FORMAT_XRGB8888,
GBM_FORMAT_ARGB8888,
0
};
const EGLint* gbmFormat = need_alpha ? rgbaFormats : rgbFormats;
EGLConfig config = 0;
while (*gbmFormat != 0)
{
config = MatchEglConfigToGBM(gl, cfgc, cfgv, *gbmFormat);
if (config != 0)
break;
gbmFormat++;
}
free(cfgv);
if (config == 0)
{
msg_Err(gl, "cannot choose EGL configuration matching gbm format");
return false;
}
sys->gbm.format = *gbmFormat;
sys->egl.config = config;
return true;
}
static bool OpenDrmDevice(vlc_gl_t* gl, drmDevice* device)
{
vlc_gl_sys_t* sys = gl->sys;
drmDevice* devices[64];
char* render_node = NULL;
int n = drmGetDevices2(0, devices, ARRAY_SIZE(devices));
for (int i = 0; i < n; ++i)
{
drmDevice* dev = devices[i];
if (device && !drmDevicesEqual(device, dev))
continue;
if (!(dev->available_nodes & (1 << DRM_NODE_RENDER)))
continue;
render_node = strdup(dev->nodes[DRM_NODE_RENDER]);
break;
}
drmFreeDevices(devices, n);
if (!render_node)
return false;
msg_Dbg(gl, "open drm device %s", render_node);
int fd = vlc_open(render_node, O_RDWR);
if (fd < 0)
{
msg_Err(gl, "can't open drm device %s", render_node);
free(render_node);
return false;
}
free(render_node);
sys->gbm.device = gbm_create_device(fd);
return sys->gbm.device != NULL;
}
static void linux_dmabuf_feedback_handle_done(void* data, struct zwp_linux_dmabuf_feedback_v1* feedback)
{
VLC_UNUSED(data);
VLC_UNUSED(feedback);
}
static void linux_dmabuf_feedback_handle_format_table(void* data, struct zwp_linux_dmabuf_feedback_v1* feedback, int32_t fd, uint32_t size)
{
VLC_UNUSED(data);
VLC_UNUSED(feedback);
VLC_UNUSED(fd);
VLC_UNUSED(size);
}
static void linux_dmabuf_feedback_handle_main_device(void* data, struct zwp_linux_dmabuf_feedback_v1* feedback, struct wl_array* device_arr)
{
VLC_UNUSED(feedback);
VLC_UNUSED(device_arr);
vlc_gl_t* gl = data;
vlc_gl_sys_t* sys = gl->sys;
dev_t device;
assert(device_arr->size == sizeof(device));
memcpy(&device, device_arr->data, sizeof(device));
drmDevice* drmDev;
if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0)
{
msg_Warn(gl, "unable to open main device");
return;
}
assert(drmDev);
assert(sys->gbm.device == NULL);
OpenDrmDevice(gl, drmDev);
drmFreeDevice(&drmDev);
}
static void linux_dmabuf_feedback_handle_tranche_done(void* data, struct zwp_linux_dmabuf_feedback_v1* feedback)
{
VLC_UNUSED(data);
VLC_UNUSED(feedback);
}
static void linux_dmabuf_feedback_handle_tranche_target_device(void* data, struct zwp_linux_dmabuf_feedback_v1* feedback, struct wl_array* device_arr)
{
VLC_UNUSED(data);
VLC_UNUSED(feedback);
VLC_UNUSED(device_arr);
}
static void linux_dmabuf_feedback_handle_tranche_formats(void* data, struct zwp_linux_dmabuf_feedback_v1* feedback, struct wl_array* indices)
{
VLC_UNUSED(data);
VLC_UNUSED(feedback);
VLC_UNUSED(indices);
}
static void linux_dmabuf_feedback_handle_tranche_flags(void* data, struct zwp_linux_dmabuf_feedback_v1* feedback, uint32_t flags)
{
VLC_UNUSED(data);
VLC_UNUSED(feedback);
VLC_UNUSED(flags);
}
static const struct zwp_linux_dmabuf_feedback_v1_listener linux_dmabuf_feedback_listener = {
linux_dmabuf_feedback_handle_done,
linux_dmabuf_feedback_handle_format_table,
linux_dmabuf_feedback_handle_main_device,
linux_dmabuf_feedback_handle_tranche_done,
linux_dmabuf_feedback_handle_tranche_target_device,
linux_dmabuf_feedback_handle_tranche_formats,
linux_dmabuf_feedback_handle_tranche_flags,
};
static void registry_global_cb(void* data, struct wl_registry* registry,
uint32_t id, const char* iface, uint32_t version)
{
vlc_gl_t* gl = data;
vlc_gl_sys_t* sys = gl->sys;
if (!strcmp(iface, "zwp_linux_dmabuf_v1"))
{
if (version <= 3)
{
msg_Warn(gl, "unsupported zwp_linux_dmabuf_v1 version");
}
else
{
sys->wayland.dmabuf = wl_registry_bind(registry,
id, &zwp_linux_dmabuf_v1_interface, 4);
sys->wayland.feedback = zwp_linux_dmabuf_v1_get_default_feedback(sys->wayland.dmabuf);
zwp_linux_dmabuf_feedback_v1_add_listener(sys->wayland.feedback,
&linux_dmabuf_feedback_listener,
gl);
sys->wayland.need_roundtrip = true;
}
}
}
static void registry_global_remove_cb(void* data, struct wl_registry* registry, uint32_t id)
{
VLC_UNUSED(data);
VLC_UNUSED(registry);
VLC_UNUSED(id);
}
static const struct wl_registry_listener registry_cbs =
{
registry_global_cb,
registry_global_remove_cb,
};
static bool GetWaylandRegistryInfo(vlc_gl_t* gl)
{
vlc_gl_sys_t* sys = gl->sys;
vlc_window_t* surface = gl->surface;
struct wl_display* display = surface->display.wl;
sys->wayland.wl_queue = wl_display_create_queue(surface->display.wl);
if (sys->wayland.wl_queue == NULL)
return false;
struct wl_display* wrapper = wl_proxy_create_wrapper(display);
wl_proxy_set_queue((struct wl_proxy*)wrapper, sys->wayland.wl_queue);
struct wl_registry* registry = wl_display_get_registry(wrapper);
if (!registry)
return false;
wl_proxy_wrapper_destroy(wrapper);
wl_registry_add_listener(registry, ®istry_cbs, gl);
sys->wayland.need_roundtrip = true;
while (sys->wayland.need_roundtrip)
{
sys->wayland.need_roundtrip = false;
wl_display_roundtrip_queue(display, sys->wayland.wl_queue);
}
wl_registry_destroy(registry);
if (!sys->gbm.device)
return false;
return true;
}
static EGLDisplay OpenDisplay(vlc_gl_t* gl)
{
vlc_gl_sys_t* sys = gl->sys;
if (!CheckClientExt("EGL_KHR_platform_gbm"))
return EGL_NO_DISPLAY;
bool ret;
ret = GetWaylandRegistryInfo(gl);
if (!ret) {
msg_Info(gl, "can't find gbm device");
return false;
}
if (!sys->gbm.device)
{
msg_Err(gl, "can't create GBM device");
return EGL_NO_DISPLAY;
}
EGLDisplay display = eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, sys->gbm.device,
NULL);
if (display == EGL_NO_DISPLAY)
{
msg_Err(gl, "can't get egl display from gbm device");
if (sys->gbm.device)
{
int fd = gbm_device_get_fd(sys->gbm.device);
gbm_device_destroy(sys->gbm.device);
close(fd);
}
return EGL_NO_DISPLAY;
}
return display;
}
static void ReleaseDisplay(vlc_gl_t* gl)
{
vlc_gl_sys_t* sys = gl->sys;
if (sys->wayland.feedback != NULL)
zwp_linux_dmabuf_feedback_v1_destroy(sys->wayland.feedback);
if (sys->wayland.dmabuf != NULL)
zwp_linux_dmabuf_v1_destroy(sys->wayland.dmabuf);
if (sys->gbm.device)
{
int fd = gbm_device_get_fd(sys->gbm.device);
gbm_device_destroy(sys->gbm.device);
close(fd);
}
if (sys->wayland.wl_queue)
wl_event_queue_destroy(sys->wayland.wl_queue);
}
static void* GetSymbol(vlc_gl_t* gl, const char* procname)
{
VLC_UNUSED(gl);
return (void*)eglGetProcAddress(procname);
}
static void Close(vlc_gl_t* gl)
{
vlc_gl_sys_t* sys = gl->sys;
if (sys->egl.context != EGL_NO_CONTEXT)
eglDestroyContext(sys->egl.display, sys->egl.context);
if (sys->egl.surface != EGL_NO_SURFACE)
{
eglDestroySurface(sys->egl.display, sys->egl.surface);
DestroySurface(gl);
}
eglTerminate(sys->egl.display);
ReleaseDisplay(gl);
free (sys);
}
static void InitEGL(void)
{
static vlc_once_t once = VLC_STATIC_ONCE;
if (unlikely(!vlc_once_begin(&once)))
{
clientExts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (!clientExts)
clientExts = "";
#ifdef EGL_EXT_platform_base
GetPlatformDisplayEXT =
(void*) eglGetProcAddress("eglGetPlatformDisplayEXT");
CreatePlatformWindowSurfaceEXT =
(void*) eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
#endif
Finish = eglGetProcAddress("glFinish");
vlc_once_complete(&once);
}
}
/**
* Probe EGL display availability
*/
static int Open(vlc_gl_t* gl, const struct gl_api* api,
unsigned width, unsigned height,
const struct vlc_gl_cfg* gl_cfg)
{
//only wayland surface are supported
if (gl->surface->type != VLC_WINDOW_TYPE_WAYLAND)
return VLC_EGENERIC;
InitEGL();
int ret = VLC_EGENERIC;
vlc_object_t* obj = VLC_OBJECT(gl);
vlc_gl_sys_t* sys = calloc(1, sizeof(*sys));
if (unlikely(sys == NULL))
return VLC_ENOMEM;
msg_Dbg(obj, "EGL client extensions: %s", clientExts);
gl->sys = sys;
sys->egl.display = EGL_NO_DISPLAY;
sys->egl.surface = EGL_NO_SURFACE;
sys->egl.context = EGL_NO_CONTEXT;
sys->egl.is_current = false;
sys->egl.display = OpenDisplay(gl);
if (sys->egl.display == EGL_NO_DISPLAY)
{
free(sys);
return VLC_ENOTSUP;
}
/* Initialize EGL display */
EGLint major, minor;
if (eglInitialize(sys->egl.display, &major, &minor) != EGL_TRUE)
goto error;
msg_Dbg(obj, "EGL version %s by %s",
eglQueryString(sys->egl.display, EGL_VERSION),
eglQueryString(sys->egl.display, EGL_VENDOR));
const char* ext = eglQueryString(sys->egl.display, EGL_EXTENSIONS);
if (*ext)
msg_Dbg(obj, "EGL display extensions: %s", ext);
if (major != 1 || minor < api->min_minor
|| !CheckAPI(sys->egl.display, api->name))
{
msg_Err(obj, "cannot select %s API", api->name);
goto error;
}
bool has_device_enum = CheckClientExt("EGL_EXT_device_enumeration");
bool has_device_query = CheckClientExt("EGL_EXT_device_query");
if (has_device_query && has_device_enum)
{
DisplayAvailableDevices(gl);
PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT =
(PFNEGLQUERYDISPLAYATTRIBEXTPROC)eglGetProcAddress("eglQueryDisplayAttribEXT");
PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT =
(PFNEGLQUERYDEVICESTRINGEXTPROC)eglGetProcAddress("eglQueryDeviceStringEXT");
EGLAttrib device_attr;
if (eglQueryDisplayAttribEXT(sys->egl.display, EGL_DEVICE_EXT,
&device_attr) == EGL_TRUE)
{
EGLDeviceEXT device = (EGLDeviceEXT)device_attr;
msg_Dbg(gl, "Using EGL device:");
LogDeviceInfo(gl, eglQueryDeviceStringEXT, device);
}
else
msg_Dbg(gl, "could not query the EGL device in use");
}
bool has_query_driver =
CheckDisplayExt(sys->egl.display, "EGL_MESA_query_driver");
if (has_query_driver)
{
PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName =
(PFNEGLGETDISPLAYDRIVERNAMEPROC) eglGetProcAddress("eglGetDisplayDriverName");
const char *driver = eglGetDisplayDriverName(sys->egl.display);
msg_Dbg(gl, "Using EGL driver: %s", driver ? driver : "unknown");
}
else
msg_Dbg(gl, "EGL_MESA_query_driver is unavailable");
ret = FindEglConfig(gl, gl_cfg->need_alpha);
if (!ret)
goto error;
/* Create a drawing surface */
sys->egl.surface = CreateSurface(gl, width, height);
if (sys->egl.surface == EGL_NO_SURFACE)
{
msg_Err(obj, "cannot create EGL window surface");
goto error;
}
if (eglBindAPI(api->api) != EGL_TRUE)
{
msg_Err(obj, "cannot bind EGL API");
goto error;
}
EGLContext ctx = eglCreateContext(sys->egl.display, sys->egl.config, EGL_NO_CONTEXT,
api->attr);
if (ctx == EGL_NO_CONTEXT)
{
msg_Err(obj, "cannot create EGL context");
goto error;
}
sys->egl.context = ctx;
/* Initialize OpenGL callbacks */
static const struct vlc_gl_operations ops = {
.make_current = MakeCurrent,
.release_current = ReleaseCurrent,
.resize = Resize,
.swap = SwapBuffers,
.get_proc_address = GetSymbol,
.close = Close,
};
gl->ops = &ops;
return VLC_SUCCESS;
error:
Close(gl);
return ret;
}
static int OpenGLES2(vlc_gl_t* gl, unsigned width, unsigned height,
const struct vlc_gl_cfg* gl_cfg)
{
static const struct gl_api api = {
"OpenGL_ES", EGL_OPENGL_ES_API, 4, EGL_OPENGL_ES2_BIT,
{ EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE },
};
return Open(gl, &api, width, height, gl_cfg);
}
static int OpenGL(vlc_gl_t* gl, unsigned width, unsigned height,
const struct vlc_gl_cfg* gl_cfg)
{
static const struct gl_api api = {
"OpenGL", EGL_OPENGL_API, 4, EGL_OPENGL_BIT,
{ EGL_NONE },
};
return Open(gl, &api, width, height, gl_cfg);
}
vlc_module_begin ()
set_shortname (N_("EGL GBM Wayland"))
set_description (N_("EGL GBM wayland extension for OpenGL"))
set_subcategory (SUBCAT_VIDEO_VOUT)
set_callback_opengl(OpenGL, 40) //lower priority than egl+wayland
add_shortcut ("egl")
add_submodule ()
set_callback_opengl_es2(OpenGLES2, 40) //lower priority than egl+wayland
add_shortcut ("egl")
vlc_module_end ()
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|