/* * This file is part of mpv. * * mpv 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. * * mpv 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 mpv. If not, see . */ #include "config.h" #if HAVE_LIBDL #include #endif #include "common/common.h" #include "egl_helpers.h" #include "common.h" #include "utils.h" #include "context.h" #if HAVE_EGL_ANGLE // On Windows, egl_helpers.c is only used by ANGLE, where the EGL functions may // be loaded dynamically from ANGLE DLLs #include "angle_dynamic.h" #endif // EGL 1.5 #ifndef EGL_CONTEXT_OPENGL_PROFILE_MASK #define EGL_CONTEXT_MAJOR_VERSION 0x3098 #define EGL_CONTEXT_MINOR_VERSION 0x30FB #define EGL_CONTEXT_OPENGL_PROFILE_MASK 0x30FD #define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT 0x00000001 #define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE 0x31B1 typedef intptr_t EGLAttrib; #endif // Not every EGL provider (like RPI) has these. #ifndef EGL_CONTEXT_FLAGS_KHR #define EGL_CONTEXT_FLAGS_KHR EGL_NONE #endif #ifndef EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR #define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0 #endif struct mp_egl_config_attr { int attrib; const char *name; }; #define MP_EGL_ATTRIB(id) {id, # id} static const struct mp_egl_config_attr mp_egl_attribs[] = { MP_EGL_ATTRIB(EGL_CONFIG_ID), MP_EGL_ATTRIB(EGL_RED_SIZE), MP_EGL_ATTRIB(EGL_GREEN_SIZE), MP_EGL_ATTRIB(EGL_BLUE_SIZE), MP_EGL_ATTRIB(EGL_ALPHA_SIZE), MP_EGL_ATTRIB(EGL_COLOR_BUFFER_TYPE), MP_EGL_ATTRIB(EGL_CONFIG_CAVEAT), MP_EGL_ATTRIB(EGL_CONFORMANT), MP_EGL_ATTRIB(EGL_NATIVE_VISUAL_ID), }; static void dump_egl_config(struct mp_log *log, int msgl, EGLDisplay display, EGLConfig config) { for (int n = 0; n < MP_ARRAY_SIZE(mp_egl_attribs); n++) { const char *name = mp_egl_attribs[n].name; EGLint v = -1; if (eglGetConfigAttrib(display, config, mp_egl_attribs[n].attrib, &v)) { mp_msg(log, msgl, " %s=0x%x\n", name, v); } else { mp_msg(log, msgl, " %s=\n", name); } } } static void *mpegl_get_proc_address(void *ctx, const char *name) { void *p = eglGetProcAddress(name); #if defined(__GLIBC__) && HAVE_LIBDL // Some crappy ARM/Linux things do not provide EGL 1.5, so above call does // not necessarily return function pointers for core functions. Try to get // them from a loaded GLES lib. As POSIX leaves RTLD_DEFAULT "reserved", // use it only with glibc. if (!p) p = dlsym(RTLD_DEFAULT, name); #endif return p; } static bool create_context(struct ra_ctx *ctx, EGLDisplay display, bool es, struct mpegl_cb cb, EGLContext *out_context, EGLConfig *out_config) { int msgl = ctx->opts.probing ? MSGL_V : MSGL_FATAL; EGLenum api; EGLint rend; const char *name; if (!es) { api = EGL_OPENGL_API; rend = EGL_OPENGL_BIT; name = "Desktop OpenGL"; } else { api = EGL_OPENGL_ES_API; rend = EGL_OPENGL_ES2_BIT; name = "GLES 2.x +"; } MP_VERBOSE(ctx, "Trying to create %s context.\n", name); if (!eglBindAPI(api)) { MP_VERBOSE(ctx, "Could not bind API!\n"); return false; } EGLint attributes[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, ctx->opts.want_alpha ? 8 : 0, EGL_RENDERABLE_TYPE, rend, EGL_NONE }; EGLint num_configs; if (!eglChooseConfig(display, attributes, NULL, 0, &num_configs)) num_configs = 0; EGLConfig *configs = talloc_array(NULL, EGLConfig, num_configs); if (!eglChooseConfig(display, attributes, configs, num_configs, &num_configs)) num_configs = 0; if (!num_configs) { talloc_free(configs); MP_MSG(ctx, msgl, "Could not choose EGLConfig for %s!\n", name); return false; } for (int n = 0; n < num_configs; n++) dump_egl_config(ctx->log, MSGL_TRACE, display, configs[n]); int chosen = 0; if (cb.refine_config) chosen = cb.refine_config(cb.user_data, configs, num_configs); if (chosen < 0) { talloc_free(configs); MP_MSG(ctx, msgl, "Could not refine EGLConfig for %s!\n", name); return false; } EGLConfig config = configs[chosen]; talloc_free(configs); MP_DBG(ctx, "Chosen EGLConfig:\n"); dump_egl_config(ctx->log, MSGL_DEBUG, display, config); int ctx_flags = ctx->opts.debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0; EGLContext *egl_ctx = NULL; if (!es) { for (int n = 0; mpgl_min_required_gl_versions[n]; n++) { int ver = mpgl_min_required_gl_versions[n]; EGLint attrs[] = { EGL_CONTEXT_MAJOR_VERSION, MPGL_VER_GET_MAJOR(ver), EGL_CONTEXT_MINOR_VERSION, MPGL_VER_GET_MINOR(ver), EGL_CONTEXT_OPENGL_PROFILE_MASK, ver >= 320 ? EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT : 0, EGL_CONTEXT_FLAGS_KHR, ctx_flags, EGL_NONE }; egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs); if (egl_ctx) break; } } if (!egl_ctx) { // Fallback for EGL 1.4 without EGL_KHR_create_context or GLES EGLint attrs[] = { EGL_CONTEXT_FLAGS_KHR, ctx_flags, es ? EGL_CONTEXT_CLIENT_VERSION : EGL_NONE, 2, EGL_NONE }; egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs); if (!egl_ctx) egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, &attrs[2]); } if (!egl_ctx) { MP_MSG(ctx, msgl, "Could not create EGL context for %s (error=%d)!\n", name, eglGetError()); return false; } *out_context = egl_ctx; *out_config = config; return true; } #define STR_OR_ERR(s) ((s) ? (s) : "(error)") // Create a context and return it and the config it was created with. If it // returns false, the out_* pointers are set to NULL. // vo_flags is a combination of VOFLAG_* values. bool mpegl_create_context(struct ra_ctx *ctx, EGLDisplay display, EGLContext *out_context, EGLConfig *out_config) { return mpegl_create_context_cb(ctx, display, (struct mpegl_cb){0}, out_context, out_config); } // Create a context and return it and the config it was created with. If it // returns false, the out_* pointers are set to NULL. bool mpegl_create_context_cb(struct ra_ctx *ctx, EGLDisplay display, struct mpegl_cb cb, EGLContext *out_context, EGLConfig *out_config) { *out_context = NULL; *out_config = NULL; const char *version = eglQueryString(display, EGL_VERSION); const char *vendor = eglQueryString(display, EGL_VENDOR); const char *apis = eglQueryString(display, EGL_CLIENT_APIS); MP_VERBOSE(ctx, "EGL_VERSION=%s\nEGL_VENDOR=%s\nEGL_CLIENT_APIS=%s\n", STR_OR_ERR(version), STR_OR_ERR(vendor), STR_OR_ERR(apis)); enum gles_mode mode = ra_gl_ctx_get_glesmode(ctx); if ((mode == GLES_NO || mode == GLES_AUTO) && create_context(ctx, display, false, cb, out_context, out_config)) return true; if ((mode == GLES_YES || mode == GLES_AUTO) && create_context(ctx, display, true, cb, out_context, out_config)) return true; int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR; MP_MSG(ctx, msgl, "Could not create a GL context.\n"); return false; } static int GLAPIENTRY swap_interval(int interval) { EGLDisplay display = eglGetCurrentDisplay(); if (!display) return 1; return !eglSwapInterval(display, interval); } // Load gl version and function pointers into *gl. // Expects a current EGL context set. void mpegl_load_functions(struct GL *gl, struct mp_log *log) { const char *egl_exts = ""; EGLDisplay display = eglGetCurrentDisplay(); if (display != EGL_NO_DISPLAY) egl_exts = eglQueryString(display, EGL_EXTENSIONS); mpgl_load_functions2(gl, mpegl_get_proc_address, NULL, egl_exts, log); if (!gl->SwapInterval) gl->SwapInterval = swap_interval; } static bool is_egl15(void) { // It appears that EGL 1.4 is specified to _require_ an initialized display // for EGL_VERSION, while EGL 1.5 is _required_ to return the EGL version. const char *ver = eglQueryString(EGL_NO_DISPLAY, EGL_VERSION); // Of course we have to go through the excruciating pain of parsing a // version string, since EGL provides no other way without a display. In // theory version!=NULL is already proof enough that it's 1.5, but be // extra defensive, since this should have been true for EGL_EXTENSIONS as // well, but then they added an extension that modified standard behavior. int ma = 0, mi = 0; return ver && sscanf(ver, "%d.%d", &ma, &mi) == 2 && (ma > 1 || mi >= 5); } // This is similar to eglGetPlatformDisplay(platform, native_display, NULL), // except that it 1. may use eglGetPlatformDisplayEXT, 2. checks for the // platform client extension platform_ext_name, and 3. does not support passing // an attrib list, because the type for that parameter is different in the EXT // and standard functions (EGL can't not fuck up, no matter what). // platform: e.g. EGL_PLATFORM_X11_KHR // platform_ext_name: e.g. "EGL_KHR_platform_x11" // native_display: e.g. X11 Display* // Returns EGL_NO_DISPLAY on failure. // Warning: the EGL version can be different at runtime depending on the chosen // platform, so this might return a display corresponding to some older EGL // version (often 1.4). // Often, there are two extension variants of a platform (KHR and EXT). If you // need to check both, call this function twice. (Why do they define them twice? // They're crazy.) EGLDisplay mpegl_get_display(EGLenum platform, const char *platform_ext_name, void *native_display) { // EGL is awful. Designed as ultra-portable library, it fails at dealing // with slightly more complex environment than its short-sighted design // could deal with. So they invented an awful, awful kludge that modifies // EGL standard behavior, the EGL_EXT_client_extensions extension. EGL 1.4 // normally is to return NULL when querying EGL_EXTENSIONS on EGL_NO_DISPLAY, // however, with that extension, it'll return the set of "client extensions", // which may include EGL_EXT_platform_base. // Prerequisite: check the platform extension. // If this is either EGL 1.5, or 1.4 with EGL_EXT_client_extensions, then // this must return a valid extension string. const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (!gl_check_extension(exts, platform_ext_name)) return EGL_NO_DISPLAY; // Before we go through the EGL 1.4 BS, try if we can use native EGL 1.5 if (is_egl15()) { // This is EGL 1.5. It must support querying standard functions through // eglGetProcAddress(). Note that on EGL 1.4, even if the function is // unknown, it could return non-NULL anyway (because EGL is crazy). EGLDisplay (EGLAPIENTRYP GetPlatformDisplay) (EGLenum, void *, const EGLAttrib *) = (void *)eglGetProcAddress("eglGetPlatformDisplay"); // (It should be impossible to be NULL, but uh.) if (GetPlatformDisplay) return GetPlatformDisplay(platform, native_display, NULL); } if (!gl_check_extension(exts, "EGL_EXT_platform_base")) return EGL_NO_DISPLAY; EGLDisplay (EGLAPIENTRYP GetPlatformDisplayEXT)(EGLenum, void*, const EGLint*) = (void *)eglGetProcAddress("eglGetPlatformDisplayEXT"); // (It should be impossible to be NULL, but uh.) if (GetPlatformDisplayEXT) return GetPlatformDisplayEXT(platform, native_display, NULL); return EGL_NO_DISPLAY; } // The same mess but with eglCreatePlatformWindowSurface(EXT) // again no support for an attribute list because the type differs // Returns EGL_NO_SURFACE on failure. EGLSurface mpegl_create_window_surface(EGLDisplay dpy, EGLConfig config, void *native_window) { // Use the EGL 1.5 function if possible if (is_egl15()) { EGLSurface (EGLAPIENTRYP CreatePlatformWindowSurface) (EGLDisplay, EGLConfig, void *, const EGLAttrib *) = (void *)eglGetProcAddress("eglCreatePlatformWindowSurface"); // (It should be impossible to be NULL, but uh.) if (CreatePlatformWindowSurface) return CreatePlatformWindowSurface(dpy, config, native_window, NULL); } // Check the extension that provides the *EXT function const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (!gl_check_extension(exts, "EGL_EXT_platform_base")) return EGL_NO_SURFACE; EGLSurface (EGLAPIENTRYP CreatePlatformWindowSurfaceEXT) (EGLDisplay, EGLConfig, void *, const EGLint *) = (void *)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); // (It should be impossible to be NULL, but uh.) if (CreatePlatformWindowSurfaceEXT) return CreatePlatformWindowSurfaceEXT(dpy, config, native_window, NULL); return EGL_NO_SURFACE; }