From 584adb3b3cff20717501da1d99d8693655838a05 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Mon, 9 Mar 2020 17:35:49 -0300 Subject: [PATCH 04/25] libobs-opengl: Introduce the X11/EGL winsys Introduce the EGL/X11 winsys, and use it when the OBS_USE_EGL environment variable is defined. This variable is only temporary, for future commits will add a proper concept of platform. All the EGL/X11 code is authored by Ivan Avdeev . --- libobs-opengl/CMakeLists.txt | 1 + libobs-opengl/gl-nix.c | 8 +- libobs-opengl/gl-x11-egl.c | 651 +++++++++++++++++++++++++++++++++++ libobs-opengl/gl-x11-egl.h | 22 ++ 4 files changed, 681 insertions(+), 1 deletion(-) create mode 100644 libobs-opengl/gl-x11-egl.c create mode 100644 libobs-opengl/gl-x11-egl.h diff --git a/libobs-opengl/CMakeLists.txt b/libobs-opengl/CMakeLists.txt index 6a4bb97d..973c5da3 100644 --- a/libobs-opengl/CMakeLists.txt +++ b/libobs-opengl/CMakeLists.txt @@ -50,6 +50,7 @@ else() #This needs to change to be more specific to get ready for Wayland set(libobs-opengl_PLATFORM_SOURCES gl-nix.c + gl-x11-egl.c gl-x11-glx.c) endif() diff --git a/libobs-opengl/gl-nix.c b/libobs-opengl/gl-nix.c index 574d4f77..4b616ef1 100644 --- a/libobs-opengl/gl-nix.c +++ b/libobs-opengl/gl-nix.c @@ -17,6 +17,7 @@ #include "gl-nix.h" #include "gl-x11-glx.h" +#include "gl-x11-egl.h" static const struct gl_winsys_vtable *gl_vtable = NULL; @@ -24,7 +25,12 @@ static void init_winsys(void) { assert(gl_vtable == NULL); - gl_vtable = gl_x11_glx_get_winsys_vtable(); + if (getenv("OBS_USE_EGL")) { + gl_vtable = gl_x11_egl_get_winsys_vtable(); + blog(LOG_INFO, "Using EGL/X11"); + } else { + gl_vtable = gl_x11_glx_get_winsys_vtable(); + } assert(gl_vtable != NULL); } diff --git a/libobs-opengl/gl-x11-egl.c b/libobs-opengl/gl-x11-egl.c new file mode 100644 index 00000000..5b413995 --- /dev/null +++ b/libobs-opengl/gl-x11-egl.c @@ -0,0 +1,651 @@ +/****************************************************************************** + Copyright (C) 2019 by Ivan Avdeev + + 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 . +******************************************************************************/ + +/* GL context initialization using EGL instead of GLX + * Which is essential for improved and more performant screen grabbing and + * VA-API feeding techniques. + * + * Note: most of x11-related functionality was taken from gl-x11.c + */ + +#include +#include + +#include + +#include + +#include "gl-x11-egl.h" + +#include + +typedef EGLDisplay(EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC)( + EGLenum platform, void *native_display, const EGLint *attrib_list); + +static const int 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 int ctx_pbuffer_attribs[] = {EGL_WIDTH, 2, EGL_HEIGHT, 2, EGL_NONE}; + +static const EGLint ctx_config_attribs[] = {EGL_STENCIL_SIZE, + 0, + EGL_DEPTH_SIZE, + 0, + EGL_BUFFER_SIZE, + 32, + EGL_ALPHA_SIZE, + 8, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_BIT, + EGL_SURFACE_TYPE, + EGL_WINDOW_BIT | EGL_PBUFFER_BIT, + EGL_NONE}; + +struct gl_windowinfo { + EGLConfig config; + + /* Windows in X11 are defined with integers (XID). + * xcb_window_t is a define for this... they are + * compatible with Xlib as well. + */ + xcb_window_t window; + EGLSurface surface; + + /* We can't fetch screen without a request so we cache it. */ + int screen; +}; + +struct gl_platform { + Display *xdisplay; + EGLDisplay edisplay; + EGLConfig config; + EGLContext context; + EGLSurface pbuffer; +}; + +/* The following utility functions are copied verbatim from GLX code. */ + +/* + * Since we cannot take advantage of the asynchronous nature of xcb, + * all of the helper functions are synchronous but thread-safe. + * + * They check for errors and will return 0 on problems + * with the exception of when 0 is a valid return value... in which case + * read the specific function comments. + */ + +/* Returns -1 on invalid screen. */ +static int get_screen_num_from_xcb_screen(xcb_connection_t *xcb_conn, + xcb_screen_t *screen) +{ + xcb_screen_iterator_t iter = + xcb_setup_roots_iterator(xcb_get_setup(xcb_conn)); + int screen_num = 0; + + for (; iter.rem; xcb_screen_next(&iter), ++screen_num) + if (iter.data == screen) + return screen_num; + + return -1; +} + +static xcb_screen_t *get_screen_from_root(xcb_connection_t *xcb_conn, + xcb_window_t root) +{ + xcb_screen_iterator_t iter = + xcb_setup_roots_iterator(xcb_get_setup(xcb_conn)); + + while (iter.rem) { + if (iter.data->root == root) + return iter.data; + + xcb_screen_next(&iter); + } + + return 0; +} + +static inline int get_screen_num_from_root(xcb_connection_t *xcb_conn, + xcb_window_t root) +{ + xcb_screen_t *screen = get_screen_from_root(xcb_conn, root); + + if (!screen) + return -1; + + return get_screen_num_from_xcb_screen(xcb_conn, screen); +} + +static xcb_get_geometry_reply_t *get_window_geometry(xcb_connection_t *xcb_conn, + xcb_drawable_t drawable) +{ + xcb_get_geometry_cookie_t cookie; + xcb_generic_error_t *error; + xcb_get_geometry_reply_t *reply; + + cookie = xcb_get_geometry(xcb_conn, drawable); + reply = xcb_get_geometry_reply(xcb_conn, cookie, &error); + + if (error) { + blog(LOG_ERROR, "Failed to fetch parent window geometry!"); + free(error); + free(reply); + return 0; + } + + free(error); + return reply; +} + +static const char *get_egl_error_string2(const EGLint error) +{ + switch (error) { +#define OBS_EGL_CASE_ERROR(e) \ + case e: \ + return #e; + OBS_EGL_CASE_ERROR(EGL_SUCCESS) + OBS_EGL_CASE_ERROR(EGL_NOT_INITIALIZED) + OBS_EGL_CASE_ERROR(EGL_BAD_ACCESS) + OBS_EGL_CASE_ERROR(EGL_BAD_ALLOC) + OBS_EGL_CASE_ERROR(EGL_BAD_ATTRIBUTE) + OBS_EGL_CASE_ERROR(EGL_BAD_CONTEXT) + OBS_EGL_CASE_ERROR(EGL_BAD_CONFIG) + OBS_EGL_CASE_ERROR(EGL_BAD_CURRENT_SURFACE) + OBS_EGL_CASE_ERROR(EGL_BAD_DISPLAY) + OBS_EGL_CASE_ERROR(EGL_BAD_SURFACE) + OBS_EGL_CASE_ERROR(EGL_BAD_MATCH) + OBS_EGL_CASE_ERROR(EGL_BAD_PARAMETER) + OBS_EGL_CASE_ERROR(EGL_BAD_NATIVE_PIXMAP) + OBS_EGL_CASE_ERROR(EGL_BAD_NATIVE_WINDOW) + OBS_EGL_CASE_ERROR(EGL_CONTEXT_LOST) +#undef OBS_EGL_CASE_ERROR + default: + return "Unknown"; + } +} +static const char *get_egl_error_string() +{ + return get_egl_error_string2(eglGetError()); +} + +static EGLDisplay get_egl_display(struct gl_platform *plat) +{ + Display *display = plat->xdisplay; + EGLDisplay edisplay = EGL_NO_DISPLAY; + const char *egl_client_extensions = NULL; + + egl_client_extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = + (PFNEGLGETPLATFORMDISPLAYEXTPROC)( + strstr(egl_client_extensions, "EGL_EXT_platform_base") + ? eglGetProcAddress("eglGetPlatformDisplayEXT") + : NULL); + + if (eglGetPlatformDisplayEXT) { + edisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT, + display, NULL); + if (EGL_NO_DISPLAY == edisplay) + blog(LOG_ERROR, "Failed to get EGL/X11 display"); + } + + if (EGL_NO_DISPLAY == edisplay) + edisplay = eglGetDisplay(display); + + return edisplay; +} + +static bool gl_context_create(struct gl_platform *plat) +{ + Display *display = plat->xdisplay; + int frame_buf_config_count = 0; + EGLDisplay edisplay = EGL_NO_DISPLAY; + EGLConfig config = NULL; + EGLContext context = EGL_NO_CONTEXT; + int egl_min = 0, egl_maj = 0; + bool success = false; + + eglBindAPI(EGL_OPENGL_API); + + edisplay = get_egl_display(plat); + + if (EGL_NO_DISPLAY == edisplay) { + blog(LOG_ERROR, + "Failed to get EGL display using eglGetDisplay"); + return false; + } + + if (!eglInitialize(edisplay, &egl_maj, &egl_min)) { + blog(LOG_ERROR, "Failed to initialize EGL: %s", + get_egl_error_string()); + return false; + } + + if (!eglChooseConfig(edisplay, ctx_config_attribs, &config, 1, + &frame_buf_config_count)) { + blog(LOG_ERROR, "Unable to find suitable EGL config: %s", + get_egl_error_string()); + goto error; + } + + context = + eglCreateContext(edisplay, config, EGL_NO_CONTEXT, ctx_attribs); +#ifdef _DEBUG + if (EGL_NO_CONTEXT == context) { + const EGLint error = eglGetError(); + if (error == EGL_BAD_ATTRIBUTE) { + /* Sometimes creation fails because debug gl is not supported */ + blog(LOG_ERROR, + "Unable to create EGL context with DEBUG attrib, trying without"); + context = eglCreateContext(edisplay, config, + EGL_NO_CONTEXT, + ctx_attribs + 2); + } else { + blog(LOG_ERROR, "Unable to create EGL context: %s", + get_egl_error_string2(error)); + goto error; + } + } +#endif + if (EGL_NO_CONTEXT == context) { + blog(LOG_ERROR, "Unable to create EGL context: %s", + get_egl_error_string()); + goto error; + } + + plat->pbuffer = + eglCreatePbufferSurface(edisplay, config, ctx_pbuffer_attribs); + if (EGL_NO_SURFACE == plat->pbuffer) { + blog(LOG_ERROR, "Failed to create OpenGL pbuffer: %s", + get_egl_error_string()); + goto error; + } + + plat->edisplay = edisplay; + plat->config = config; + plat->context = context; + + success = true; + blog(LOG_DEBUG, "Created EGLDisplay %p", plat->edisplay); + +error: + if (!success) { + if (EGL_NO_CONTEXT != context) + eglDestroyContext(edisplay, context); + eglTerminate(edisplay); + } + + XSync(display, false); + return success; +} + +static void gl_context_destroy(struct gl_platform *plat) +{ + eglMakeCurrent(plat->edisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + eglDestroyContext(plat->edisplay, plat->context); +} + +static struct gl_windowinfo * +gl_x11_egl_windowinfo_create(const struct gs_init_data *info) +{ + UNUSED_PARAMETER(info); + return bmalloc(sizeof(struct gl_windowinfo)); +} + +static void gl_x11_egl_windowinfo_destroy(struct gl_windowinfo *info) +{ + UNUSED_PARAMETER(info); + bfree(info); +} + +static Display *open_windowless_display(void) +{ + Display *display = XOpenDisplay(NULL); + xcb_connection_t *xcb_conn; + xcb_screen_iterator_t screen_iterator; + xcb_screen_t *screen; + int screen_num; + + if (!display) { + blog(LOG_ERROR, "Unable to open new X connection!"); + return NULL; + } + + xcb_conn = XGetXCBConnection(display); + if (!xcb_conn) { + blog(LOG_ERROR, "Unable to get XCB connection to main display"); + goto error; + } + + screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(xcb_conn)); + screen = screen_iterator.data; + if (!screen) { + blog(LOG_ERROR, "Unable to get screen root"); + goto error; + } + + screen_num = get_screen_num_from_root(xcb_conn, screen->root); + if (screen_num == -1) { + blog(LOG_ERROR, "Unable to get screen number from root"); + goto error; + } + + if (!gladLoadEGL()) { + blog(LOG_ERROR, "Unable to load EGL entry functions."); + goto error; + } + + return display; + +error: + if (display) + XCloseDisplay(display); + return NULL; +} + +static int x_error_handler(Display *display, XErrorEvent *error) +{ + char str1[512]; + char str2[512]; + char str3[512]; + XGetErrorText(display, error->error_code, str1, sizeof(str1)); + XGetErrorText(display, error->request_code, str2, sizeof(str2)); + XGetErrorText(display, error->minor_code, str3, sizeof(str3)); + + blog(LOG_ERROR, + "X Error: %s, Major opcode: %s, " + "Minor opcode: %s, Serial: %lu", + str1, str2, str3, error->serial); + return 0; +} + +static struct gl_platform *gl_x11_egl_platform_create(gs_device_t *device, + uint32_t adapter) +{ + /* There's some trickery here... we're mixing libX11, xcb, and EGL + For an explanation see here: http://xcb.freedesktop.org/MixingCalls/ + Essentially, EGL requires Xlib. Everything else we use xcb. */ + struct gl_platform *plat = bmalloc(sizeof(struct gl_platform)); + Display *display = open_windowless_display(); + + if (!display) { + goto fail_display_open; + } + + XSetEventQueueOwner(display, XCBOwnsEventQueue); + XSetErrorHandler(x_error_handler); + + /* We assume later that cur_swap is already set. */ + device->plat = plat; + + plat->xdisplay = display; + + if (!gl_context_create(plat)) { + blog(LOG_ERROR, "Failed to create context!"); + goto fail_context_create; + } + + if (!eglMakeCurrent(plat->edisplay, plat->pbuffer, plat->pbuffer, + plat->context)) { + blog(LOG_ERROR, "Failed to make context current: %s", + get_egl_error_string()); + goto fail_make_current; + } + + if (!gladLoadGL()) { + blog(LOG_ERROR, "Failed to load OpenGL entry functions."); + goto fail_load_gl; + } + + goto success; + +fail_make_current: + gl_context_destroy(plat); +fail_context_create: +fail_load_gl: + XCloseDisplay(display); +fail_display_open: + bfree(plat); + plat = NULL; +success: + UNUSED_PARAMETER(adapter); + return plat; +} + +static void gl_x11_egl_platform_destroy(struct gl_platform *plat) +{ + if (!plat) + return; + + gl_context_destroy(plat); + eglTerminate(plat->edisplay); + bfree(plat); +} + +static bool gl_x11_egl_platform_init_swapchain(struct gs_swap_chain *swap) +{ + const struct gl_platform *plat = swap->device->plat; + Display *display = plat->xdisplay; + xcb_connection_t *xcb_conn = XGetXCBConnection(display); + xcb_window_t wid = xcb_generate_id(xcb_conn); + xcb_window_t parent = swap->info.window.id; + xcb_get_geometry_reply_t *geometry = + get_window_geometry(xcb_conn, parent); + bool status = false; + + int screen_num; + int visual; + + if (!geometry) + goto fail_geometry_request; + + screen_num = get_screen_num_from_root(xcb_conn, geometry->root); + if (screen_num == -1) { + goto fail_screen; + } + + { + if (!eglGetConfigAttrib(plat->edisplay, plat->config, + EGL_NATIVE_VISUAL_ID, + (EGLint *)&visual)) { + blog(LOG_ERROR, + "Cannot get visual id for EGL context: %s", + get_egl_error_string()); + goto fail_visual_id; + } + } + + xcb_colormap_t colormap = xcb_generate_id(xcb_conn); + uint32_t mask = XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP; + uint32_t mask_values[] = {0, colormap, 0}; + + xcb_create_colormap(xcb_conn, XCB_COLORMAP_ALLOC_NONE, colormap, parent, + visual); + + xcb_create_window(xcb_conn, 24 /* Hardcoded? */, wid, parent, 0, 0, + geometry->width, geometry->height, 0, 0, visual, mask, + mask_values); + + const EGLSurface surface = + eglCreateWindowSurface(plat->edisplay, plat->config, wid, 0); + if (EGL_NO_SURFACE == surface) { + blog(LOG_ERROR, "Cannot get window EGL surface: %s", + get_egl_error_string()); + goto fail_window_surface; + } + + swap->wi->config = plat->config; + swap->wi->window = wid; + swap->wi->surface = surface; + swap->wi->screen = screen_num; + + xcb_map_window(xcb_conn, wid); + + status = true; + goto success; + +fail_window_surface: +fail_visual_id: +fail_screen: +fail_geometry_request: +success: + free(geometry); + return status; +} + +static void gl_x11_egl_platform_cleanup_swapchain(struct gs_swap_chain *swap) +{ + UNUSED_PARAMETER(swap); + /* Really nothing to clean up? */ +} + +static void gl_x11_egl_device_enter_context(gs_device_t *device) +{ + const EGLContext context = device->plat->context; + const EGLDisplay display = device->plat->edisplay; + const EGLSurface surface = (device->cur_swap) + ? device->cur_swap->wi->surface + : device->plat->pbuffer; + + if (!eglMakeCurrent(display, surface, surface, context)) + blog(LOG_ERROR, "Failed to make context current: %s", + get_egl_error_string()); +} + +static void gl_x11_egl_device_leave_context(gs_device_t *device) +{ + const EGLDisplay display = device->plat->edisplay; + + if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)) { + blog(LOG_ERROR, "Failed to reset current context: %s", + get_egl_error_string()); + } +} + +static void *gl_x11_egl_device_get_device_obj(gs_device_t *device) +{ + return device->plat->context; +} + +static void gl_x11_egl_getclientsize(const struct gs_swap_chain *swap, + uint32_t *width, uint32_t *height) +{ + xcb_connection_t *xcb_conn = + XGetXCBConnection(swap->device->plat->xdisplay); + xcb_window_t window = swap->wi->window; + + xcb_get_geometry_reply_t *geometry = + get_window_geometry(xcb_conn, window); + if (geometry) { + *width = geometry->width; + *height = geometry->height; + } + + free(geometry); +} + +static void gl_x11_egl_update(gs_device_t *device) +{ + Display *display = device->plat->xdisplay; + xcb_window_t window = device->cur_swap->wi->window; + + uint32_t values[] = {device->cur_swap->info.cx, + device->cur_swap->info.cy}; + + xcb_configure_window(XGetXCBConnection(display), window, + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, + values); +} + +static void gl_x11_egl_clear_context(gs_device_t *device) +{ + Display *display = device->plat->edisplay; + + if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)) { + blog(LOG_ERROR, "Failed to reset current context."); + } +} + +static void gl_x11_egl_device_load_swapchain(gs_device_t *device, + gs_swapchain_t *swap) +{ + if (device->cur_swap == swap) + return; + + device->cur_swap = swap; + + device_enter_context(device); +} + +enum swap_type { + SWAP_TYPE_NORMAL, + SWAP_TYPE_EXT, + SWAP_TYPE_MESA, + SWAP_TYPE_SGI, +}; + +static void gl_x11_egl_device_present(gs_device_t *device) +{ + Display *display = device->plat->xdisplay; + + xcb_connection_t *xcb_conn = XGetXCBConnection(display); + xcb_generic_event_t *xcb_event; + while ((xcb_event = xcb_poll_for_event(xcb_conn))) { + free(xcb_event); + } + + if (!eglSwapBuffers(device->plat->edisplay, + device->cur_swap->wi->surface)) + blog(LOG_ERROR, "Cannot swap EGL buffers: %s", + get_egl_error_string()); +} + +static const struct gl_winsys_vtable egl_x11_winsys_vtable = { + .windowinfo_create = gl_x11_egl_windowinfo_create, + .windowinfo_destroy = gl_x11_egl_windowinfo_destroy, + .platform_create = gl_x11_egl_platform_create, + .platform_destroy = gl_x11_egl_platform_destroy, + .platform_init_swapchain = gl_x11_egl_platform_init_swapchain, + .platform_cleanup_swapchain = gl_x11_egl_platform_cleanup_swapchain, + .device_enter_context = gl_x11_egl_device_enter_context, + .device_leave_context = gl_x11_egl_device_leave_context, + .device_get_device_obj = gl_x11_egl_device_get_device_obj, + .getclientsize = gl_x11_egl_getclientsize, + .clear_context = gl_x11_egl_clear_context, + .update = gl_x11_egl_update, + .device_load_swapchain = gl_x11_egl_device_load_swapchain, + .device_present = gl_x11_egl_device_present, +}; + +const struct gl_winsys_vtable *gl_x11_egl_get_winsys_vtable(void) +{ + return &egl_x11_winsys_vtable; +} diff --git a/libobs-opengl/gl-x11-egl.h b/libobs-opengl/gl-x11-egl.h new file mode 100644 index 00000000..44ab3111 --- /dev/null +++ b/libobs-opengl/gl-x11-egl.h @@ -0,0 +1,22 @@ +/****************************************************************************** + Copyright (C) 2019 by Ivan Avdeev + + 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 . +******************************************************************************/ + +#pragma once + +#include "gl-nix.h" + +const struct gl_winsys_vtable *gl_x11_egl_get_winsys_vtable(void); -- 2.28.0