obs-studio/libobs-opengl/gl-wayland-egl.c

429 lines
12 KiB
C

/******************************************************************************
Copyright (C) 2019 by Jason Francis <cycl0ps@tuta.io>
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, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "gl-wayland-egl.h"
#include <wayland-client.h>
#include <wayland-egl.h>
#include "gl-egl-common.h"
#include <glad/glad_egl.h>
static const EGLint config_attribs_native[] = {EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_BIT,
EGL_STENCIL_SIZE,
0,
EGL_DEPTH_SIZE,
0,
EGL_BUFFER_SIZE,
32,
EGL_ALPHA_SIZE,
8,
EGL_NATIVE_RENDERABLE,
EGL_TRUE,
EGL_NONE};
static const EGLint config_attribs[] = {EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_BIT,
EGL_STENCIL_SIZE,
0,
EGL_DEPTH_SIZE,
0,
EGL_BUFFER_SIZE,
32,
EGL_ALPHA_SIZE,
8,
EGL_NONE};
static const EGLint ctx_attribs[] = {
#ifdef _DEBUG
EGL_CONTEXT_OPENGL_DEBUG,
EGL_TRUE,
#endif
EGL_CONTEXT_OPENGL_PROFILE_MASK,
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
EGL_CONTEXT_MAJOR_VERSION,
3,
EGL_CONTEXT_MINOR_VERSION,
3,
EGL_NONE};
static const EGLint khr_ctx_attribs[] = {
#ifdef _DEBUG
EGL_CONTEXT_FLAGS_KHR,
EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR,
#endif
EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR,
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
EGL_CONTEXT_MAJOR_VERSION_KHR,
3,
EGL_CONTEXT_MINOR_VERSION_KHR,
3,
EGL_NONE};
struct gl_windowinfo {
struct wl_egl_window *window;
EGLSurface egl_surface;
};
struct gl_platform {
struct wl_display *wl_display;
EGLDisplay display;
EGLConfig config;
EGLContext context;
};
struct gl_windowinfo *
gl_wayland_egl_windowinfo_create(const struct gs_init_data *info)
{
struct wl_egl_window *window =
wl_egl_window_create(info->window.display, info->cx, info->cy);
if (window == NULL) {
blog(LOG_ERROR, "wl_egl_window_create failed");
return NULL;
}
struct gl_windowinfo *wi = bmalloc(sizeof(struct gl_windowinfo));
wi->window = window;
return wi;
}
static void gl_wayland_egl_windowinfo_destroy(struct gl_windowinfo *info)
{
wl_egl_window_destroy(info->window);
bfree(info);
}
static bool egl_make_current(EGLDisplay display, EGLSurface surface,
EGLContext context)
{
if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) {
blog(LOG_ERROR, "eglBindAPI failed");
}
if (!eglMakeCurrent(display, surface, surface, context)) {
blog(LOG_ERROR, "eglMakeCurrent failed");
return false;
}
if (surface != EGL_NO_SURFACE)
glDrawBuffer(GL_BACK);
return true;
}
static bool egl_context_create(struct gl_platform *plat, const EGLint *attribs)
{
bool success = false;
EGLint num_config;
if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) {
blog(LOG_ERROR, "eglBindAPI failed");
}
EGLBoolean result = eglChooseConfig(plat->display,
config_attribs_native,
&plat->config, 1, &num_config);
if (result != EGL_TRUE || num_config == 0) {
result = eglChooseConfig(plat->display, config_attribs,
&plat->config, 1, &num_config);
if (result != EGL_TRUE || num_config == 0) {
blog(LOG_ERROR, "eglChooseConfig failed");
goto error;
}
}
plat->context = eglCreateContext(plat->display, plat->config,
EGL_NO_CONTEXT, attribs);
if (plat->context == EGL_NO_CONTEXT) {
blog(LOG_ERROR, "eglCreateContext failed");
goto error;
}
success =
egl_make_current(plat->display, EGL_NO_SURFACE, plat->context);
error:
return success;
}
static void egl_context_destroy(struct gl_platform *plat)
{
egl_make_current(plat->display, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(plat->display, plat->context);
}
static bool extension_supported(const char *extensions, const char *search)
{
const char *result = strstr(extensions, search);
unsigned long len = strlen(search);
return result != NULL &&
(result == extensions || *(result - 1) == ' ') &&
(result[len] == ' ' || result[len] == '\0');
}
static struct gl_platform *gl_wayland_egl_platform_create(gs_device_t *device,
uint32_t adapter)
{
struct gl_platform *plat = bmalloc(sizeof(struct gl_platform));
plat->wl_display = obs_get_nix_platform_display();
device->plat = plat;
plat->display = eglGetDisplay(plat->wl_display);
if (plat->display == EGL_NO_DISPLAY) {
blog(LOG_ERROR, "eglGetDisplay failed");
goto fail_display_init;
}
EGLint major;
EGLint minor;
if (eglInitialize(plat->display, &major, &minor) == EGL_FALSE) {
blog(LOG_ERROR, "eglInitialize failed");
goto fail_display_init;
}
blog(LOG_INFO, "Initialized EGL %d.%d", major, minor);
const char *extensions = eglQueryString(plat->display, EGL_EXTENSIONS);
blog(LOG_DEBUG, "Supported EGL Extensions: %s", extensions);
const EGLint *attribs = ctx_attribs;
if (major == 1 && minor == 4) {
if (extension_supported(extensions, "EGL_KHR_create_context")) {
attribs = khr_ctx_attribs;
} else {
blog(LOG_ERROR,
"EGL_KHR_create_context extension is required to use EGL 1.4.");
goto fail_context_create;
}
} else if (major < 1 || (major == 1 && minor < 4)) {
blog(LOG_ERROR, "EGL 1.4 or higher is required.");
goto fail_context_create;
}
if (!egl_context_create(plat, attribs)) {
goto fail_context_create;
}
if (!gladLoadGL()) {
blog(LOG_ERROR, "Failed to load OpenGL entry functions.");
goto fail_load_gl;
}
if (!gladLoadEGL()) {
blog(LOG_ERROR, "Unable to load EGL entry functions.");
goto fail_load_egl;
}
goto success;
fail_load_egl:
fail_load_gl:
egl_context_destroy(plat);
fail_context_create:
eglTerminate(plat->display);
fail_display_init:
bfree(plat);
plat = NULL;
success:
UNUSED_PARAMETER(adapter);
return plat;
}
static void gl_wayland_egl_platform_destroy(struct gl_platform *plat)
{
if (plat) {
egl_context_destroy(plat);
eglTerminate(plat->display);
bfree(plat);
}
}
static bool gl_wayland_egl_platform_init_swapchain(struct gs_swap_chain *swap)
{
struct gl_platform *plat = swap->device->plat;
EGLSurface egl_surface = eglCreateWindowSurface(
plat->display, plat->config, swap->wi->window, NULL);
if (egl_surface == EGL_NO_SURFACE) {
blog(LOG_ERROR, "eglCreateWindowSurface failed");
return false;
}
swap->wi->egl_surface = egl_surface;
return true;
}
static void
gl_wayland_egl_platform_cleanup_swapchain(struct gs_swap_chain *swap)
{
struct gl_platform *plat = swap->device->plat;
eglDestroySurface(plat->display, swap->wi->egl_surface);
}
static void gl_wayland_egl_device_enter_context(gs_device_t *device)
{
struct gl_platform *plat = device->plat;
EGLSurface surface = EGL_NO_SURFACE;
if (device->cur_swap != NULL)
surface = device->cur_swap->wi->egl_surface;
egl_make_current(plat->display, surface, plat->context);
}
static void gl_wayland_egl_device_leave_context(gs_device_t *device)
{
struct gl_platform *plat = device->plat;
egl_make_current(plat->display, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
static void *gl_wayland_egl_device_get_device_obj(gs_device_t *device)
{
return device->plat->context;
}
static void gl_wayland_egl_getclientsize(const struct gs_swap_chain *swap,
uint32_t *width, uint32_t *height)
{
wl_egl_window_get_attached_size(swap->wi->window, (void *)width,
(void *)height);
}
static void gl_wayland_egl_clear_context(gs_device_t *device)
{
struct gl_platform *plat = device->plat;
egl_make_current(plat->display, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
static void gl_wayland_egl_update(gs_device_t *device)
{
wl_egl_window_resize(device->cur_swap->wi->window,
device->cur_swap->info.cx,
device->cur_swap->info.cy, 0, 0);
}
static void gl_wayland_egl_device_load_swapchain(gs_device_t *device,
gs_swapchain_t *swap)
{
if (device->cur_swap == swap)
return;
device->cur_swap = swap;
struct gl_platform *plat = device->plat;
if (swap == NULL) {
egl_make_current(plat->display, EGL_NO_SURFACE, EGL_NO_CONTEXT);
} else {
egl_make_current(plat->display, swap->wi->egl_surface,
plat->context);
}
}
static void gl_wayland_egl_device_present(gs_device_t *device)
{
struct gl_platform *plat = device->plat;
struct gl_windowinfo *wi = device->cur_swap->wi;
if (eglSwapInterval(plat->display, 0) == EGL_FALSE) {
blog(LOG_ERROR, "eglSwapInterval failed");
}
if (eglSwapBuffers(plat->display, wi->egl_surface) == EGL_FALSE) {
blog(LOG_ERROR, "eglSwapBuffers failed");
}
}
static struct gs_texture *gl_wayland_egl_device_texture_create_from_dmabuf(
gs_device_t *device, unsigned int width, unsigned int height,
uint32_t drm_format, enum gs_color_format color_format,
uint32_t n_planes, const int *fds, const uint32_t *strides,
const uint32_t *offsets, const uint64_t *modifiers)
{
struct gl_platform *plat = device->plat;
return gl_egl_create_dmabuf_image(plat->display, width, height,
drm_format, color_format, n_planes,
fds, strides, offsets, modifiers);
}
static bool gl_wayland_egl_device_query_dmabuf_capabilities(
gs_device_t *device, enum gs_dmabuf_flags *dmabuf_flags,
uint32_t **drm_formats, size_t *n_formats)
{
struct gl_platform *plat = device->plat;
return gl_egl_query_dmabuf_capabilities(plat->display, dmabuf_flags,
drm_formats, n_formats);
}
static bool gl_wayland_egl_device_query_dmabuf_modifiers_for_format(
gs_device_t *device, uint32_t drm_format, uint64_t **modifiers,
size_t *n_modifiers)
{
struct gl_platform *plat = device->plat;
return gl_egl_query_dmabuf_modifiers_for_format(
plat->display, drm_format, modifiers, n_modifiers);
}
static struct gs_texture *gl_wayland_egl_device_texture_create_from_pixmap(
gs_device_t *device, uint32_t width, uint32_t height,
enum gs_color_format color_format, uint32_t target, void *pixmap)
{
UNUSED_PARAMETER(device);
UNUSED_PARAMETER(width);
UNUSED_PARAMETER(height);
UNUSED_PARAMETER(color_format);
UNUSED_PARAMETER(target);
UNUSED_PARAMETER(pixmap);
return NULL;
}
static const struct gl_winsys_vtable egl_wayland_winsys_vtable = {
.windowinfo_create = gl_wayland_egl_windowinfo_create,
.windowinfo_destroy = gl_wayland_egl_windowinfo_destroy,
.platform_create = gl_wayland_egl_platform_create,
.platform_destroy = gl_wayland_egl_platform_destroy,
.platform_init_swapchain = gl_wayland_egl_platform_init_swapchain,
.platform_cleanup_swapchain = gl_wayland_egl_platform_cleanup_swapchain,
.device_enter_context = gl_wayland_egl_device_enter_context,
.device_leave_context = gl_wayland_egl_device_leave_context,
.device_get_device_obj = gl_wayland_egl_device_get_device_obj,
.getclientsize = gl_wayland_egl_getclientsize,
.clear_context = gl_wayland_egl_clear_context,
.update = gl_wayland_egl_update,
.device_load_swapchain = gl_wayland_egl_device_load_swapchain,
.device_present = gl_wayland_egl_device_present,
.device_texture_create_from_dmabuf =
gl_wayland_egl_device_texture_create_from_dmabuf,
.device_query_dmabuf_capabilities =
gl_wayland_egl_device_query_dmabuf_capabilities,
.device_query_dmabuf_modifiers_for_format =
gl_wayland_egl_device_query_dmabuf_modifiers_for_format,
.device_texture_create_from_pixmap =
gl_wayland_egl_device_texture_create_from_pixmap,
};
const struct gl_winsys_vtable *gl_wayland_egl_get_winsys_vtable(void)
{
return &egl_wayland_winsys_vtable;
}