/****************************************************************************** Copyright (C) 2019 by Jason Francis 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 . ******************************************************************************/ #include "gl-wayland-egl.h" #include #include #include "gl-egl-common.h" #include 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; }