/* * Copyright (C) 2005-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ // always define GL_GLEXT_PROTOTYPES before include gl headers #if !defined(GL_GLEXT_PROTOTYPES) #define GL_GLEXT_PROTOTYPES #endif #include "GLContextEGL.h" #include "ServiceBroker.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/log.h" #include #include #include #include #include "PlatformDefs.h" #include "system_gl.h" #define EGL_NO_CONFIG (EGLConfig)0 CGLContextEGL::CGLContextEGL(Display* dpy, EGLint renderingApi) : CGLContext(dpy), m_renderingApi(renderingApi) { m_extPrefix = "EGL_"; m_eglConfig = EGL_NO_CONFIG; m_eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); const auto settings = CServiceBroker::GetSettingsComponent(); if (settings) { m_omlSync = settings->GetAdvancedSettings()->m_omlSync; } } CGLContextEGL::~CGLContextEGL() { Destroy(); } bool CGLContextEGL::Refresh(bool force, int screen, Window glWindow, bool &newContext) { m_sync.cont = 0; // refresh context if (m_eglContext && !force) { if (m_eglSurface == EGL_NO_SURFACE) { m_eglSurface = eglCreateWindowSurface(m_eglDisplay, m_eglConfig, glWindow, NULL); if (m_eglSurface == EGL_NO_SURFACE) { CLog::Log(LOGERROR, "failed to create EGL window surface {}", eglGetError()); return false; } } CLog::Log(LOGDEBUG, "CWinSystemX11::RefreshEGLContext: refreshing context"); eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext); return true; } Destroy(); newContext = true; if (m_eglGetPlatformDisplayEXT) { EGLint attribs[] = { EGL_PLATFORM_X11_SCREEN_EXT, screen, EGL_NONE }; m_eglDisplay = m_eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT,(EGLNativeDisplayType)m_dpy, attribs); } else m_eglDisplay = eglGetDisplay((EGLNativeDisplayType)m_dpy); if (m_eglDisplay == EGL_NO_DISPLAY) { CLog::Log(LOGERROR, "failed to get egl display"); return false; } if (!eglInitialize(m_eglDisplay, NULL, NULL)) { CLog::Log(LOGERROR, "failed to initialize egl"); Destroy(); return false; } if (!eglBindAPI(m_renderingApi)) { CLog::Log(LOGERROR, "failed to bind rendering API"); Destroy(); return false; } // create context XVisualInfo vMask; XVisualInfo *vInfo = nullptr; int availableVisuals = 0; vMask.screen = screen; XWindowAttributes winAttr; if (!XGetWindowAttributes(m_dpy, glWindow, &winAttr)) { CLog::Log(LOGWARNING, "Failed to get window attributes"); Destroy(); return false; } vMask.visualid = XVisualIDFromVisual(winAttr.visual); vInfo = XGetVisualInfo(m_dpy, VisualScreenMask | VisualIDMask, &vMask, &availableVisuals); if (!vInfo) { CLog::Log(LOGERROR, "Failed to get VisualInfo of visual 0x{:x}", (unsigned)vMask.visualid); Destroy(); return false; } unsigned int visualid = static_cast(vInfo->visualid); m_eglConfig = GetEGLConfig(m_eglDisplay, vInfo); XFree(vInfo); if (m_eglConfig == EGL_NO_CONFIG) { CLog::Log(LOGERROR, "failed to get suitable eglconfig for visual 0x{:x}", visualid); Destroy(); return false; } CLog::Log(LOGINFO, "Using visual 0x{:x}", visualid); m_eglSurface = eglCreateWindowSurface(m_eglDisplay, m_eglConfig, glWindow, NULL); if (m_eglSurface == EGL_NO_SURFACE) { CLog::Log(LOGERROR, "failed to create EGL window surface {}", eglGetError()); Destroy(); return false; } EGLint contextAttributes[] = { EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 2, EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, EGL_NONE }; m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttributes); if (m_eglContext == EGL_NO_CONTEXT) { EGLint contextAttributes[] = { EGL_CONTEXT_MAJOR_VERSION_KHR, 2, EGL_NONE }; m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttributes); if (m_eglContext == EGL_NO_CONTEXT) { CLog::Log(LOGERROR, "failed to create EGL context"); Destroy(); return false; } CLog::Log(LOGWARNING, "Failed to get an OpenGL context supporting core profile 3.2, " "using legacy mode with reduced feature set"); } if (!eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)) { CLog::Log(LOGERROR, "Failed to make context current {} {} {}", fmt::ptr(m_eglDisplay), fmt::ptr(m_eglSurface), fmt::ptr(m_eglContext)); Destroy(); return false; } m_eglGetSyncValuesCHROMIUM = (PFNEGLGETSYNCVALUESCHROMIUMPROC)eglGetProcAddress("eglGetSyncValuesCHROMIUM"); m_usePB = false; return true; } bool CGLContextEGL::CreatePB() { const EGLint configAttribs[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_DEPTH_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE }; const EGLint pbufferAttribs[] = { EGL_WIDTH, 9, EGL_HEIGHT, 9, EGL_NONE, }; Destroy(); if (m_eglGetPlatformDisplayEXT) { m_eglDisplay = m_eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT,(EGLNativeDisplayType)m_dpy, NULL); } else m_eglDisplay = eglGetDisplay((EGLNativeDisplayType)m_dpy); if (m_eglDisplay == EGL_NO_DISPLAY) { CLog::Log(LOGERROR, "failed to get egl display"); return false; } if (!eglInitialize(m_eglDisplay, NULL, NULL)) { CLog::Log(LOGERROR, "failed to initialize egl"); Destroy(); return false; } if (!eglBindAPI(m_renderingApi)) { CLog::Log(LOGERROR, "failed to bind rendering API"); Destroy(); return false; } EGLint numConfigs; eglChooseConfig(m_eglDisplay, configAttribs, &m_eglConfig, 1, &numConfigs); m_eglSurface = eglCreatePbufferSurface(m_eglDisplay, m_eglConfig, pbufferAttribs); if (m_eglSurface == EGL_NO_SURFACE) { CLog::Log(LOGERROR, "failed to create EGL window surface {}", eglGetError()); Destroy(); return false; } EGLint contextAttributes[] = { EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 2, EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, EGL_NONE }; m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttributes); if (m_eglContext == EGL_NO_CONTEXT) { EGLint contextAttributes[] = { EGL_CONTEXT_MAJOR_VERSION_KHR, 2, EGL_NONE }; m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttributes); if (m_eglContext == EGL_NO_CONTEXT) { CLog::Log(LOGERROR, "failed to create EGL context"); Destroy(); return false; } } if (!eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)) { CLog::Log(LOGERROR, "Failed to make context current {} {} {}", fmt::ptr(m_eglDisplay), fmt::ptr(m_eglSurface), fmt::ptr(m_eglContext)); Destroy(); return false; } m_usePB = true; return true; } void CGLContextEGL::Destroy() { if (m_eglContext) { glFinish(); eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(m_eglDisplay, m_eglContext); m_eglContext = EGL_NO_CONTEXT; } if (m_eglSurface) { eglDestroySurface(m_eglDisplay, m_eglSurface); m_eglSurface = EGL_NO_SURFACE; } if (m_eglDisplay) { eglTerminate(m_eglDisplay); m_eglDisplay = EGL_NO_DISPLAY; } m_eglConfig = EGL_NO_CONFIG; } void CGLContextEGL::Detach() { if (m_eglContext) { glFinish(); eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } if (m_eglSurface) { eglDestroySurface(m_eglDisplay, m_eglSurface); m_eglSurface = EGL_NO_SURFACE; } } bool CGLContextEGL::SuitableCheck(EGLDisplay eglDisplay, EGLConfig config) { if (config == EGL_NO_CONFIG) return false; EGLint value; if (!eglGetConfigAttrib(eglDisplay, config, EGL_RED_SIZE, &value) || value < 8) return false; if (!eglGetConfigAttrib(eglDisplay, config, EGL_GREEN_SIZE, &value) || value < 8) return false; if (!eglGetConfigAttrib(eglDisplay, config, EGL_BLUE_SIZE, &value) || value < 8) return false; if (!eglGetConfigAttrib(eglDisplay, config, EGL_DEPTH_SIZE, &value) || value < 24) return false; return true; } EGLConfig CGLContextEGL::GetEGLConfig(EGLDisplay eglDisplay, XVisualInfo *vInfo) { EGLint numConfigs; if (!eglGetConfigs(eglDisplay, nullptr, 0, &numConfigs)) { CLog::Log(LOGERROR, "Failed to query number of egl configs"); return EGL_NO_CONFIG; } if (numConfigs == 0) { CLog::Log(LOGERROR, "No suitable egl configs found"); return EGL_NO_CONFIG; } EGLConfig *eglConfigs; eglConfigs = (EGLConfig*)malloc(numConfigs * sizeof(EGLConfig)); if (!eglConfigs) { CLog::Log(LOGERROR, "eglConfigs malloc failed"); return EGL_NO_CONFIG; } EGLConfig eglConfig = EGL_NO_CONFIG; if (!eglGetConfigs(eglDisplay, eglConfigs, numConfigs, &numConfigs)) { CLog::Log(LOGERROR, "Failed to query egl configs"); goto Exit; } for (EGLint i = 0; i < numConfigs; ++i) { if (!SuitableCheck(eglDisplay, eglConfigs[i])) continue; EGLint value; if (!eglGetConfigAttrib(eglDisplay, eglConfigs[i], EGL_NATIVE_VISUAL_ID, &value)) { CLog::Log(LOGERROR, "Failed to query EGL_NATIVE_VISUAL_ID for egl config."); break; } if (value == (EGLint)vInfo->visualid) { eglConfig = eglConfigs[i]; break; } } Exit: free(eglConfigs); return eglConfig; } void CGLContextEGL::SetVSync(bool enable) { eglSwapInterval(m_eglDisplay, enable ? 1 : 0); } void CGLContextEGL::SwapBuffers() { if ((m_eglDisplay == EGL_NO_DISPLAY) || (m_eglSurface == EGL_NO_SURFACE)) return; if (m_usePB) { eglSwapBuffers(m_eglDisplay, m_eglSurface); usleep(20 * 1000); return; } uint64_t ust1, ust2; uint64_t msc1, msc2; uint64_t sbc1, sbc2; struct timespec nowTs; uint64_t now; uint64_t cont = m_sync.cont; uint64_t interval = m_sync.interval; if (m_eglGetSyncValuesCHROMIUM) { m_eglGetSyncValuesCHROMIUM(m_eglDisplay, m_eglSurface, &ust1, &msc1, &sbc1); } eglSwapBuffers(m_eglDisplay, m_eglSurface); if (!m_eglGetSyncValuesCHROMIUM) return; clock_gettime(CLOCK_MONOTONIC, &nowTs); now = static_cast(nowTs.tv_sec) * 1000000000ULL + nowTs.tv_nsec; m_eglGetSyncValuesCHROMIUM(m_eglDisplay, m_eglSurface, &ust2, &msc2, &sbc2); if ((msc1 - m_sync.msc1) > 2) { cont = 0; } // we want to block in SwapBuffers // if a vertical retrace occurs 5 times in a row outside // of this function, we take action if (m_sync.cont < 5) { if ((msc1 - m_sync.msc1) == 2) { cont = 0; } else if ((msc1 - m_sync.msc1) == 1) { interval = (ust1 - m_sync.ust1) / (msc1 - m_sync.msc1); cont++; } } else if (m_sync.cont == 5 && m_omlSync) { CLog::Log(LOGDEBUG, "CGLContextEGL::SwapBuffers: sync check blocking"); if (msc2 == msc1) { // if no vertical retrace has occurred in eglSwapBuffers, // sleep until next vertical retrace uint64_t lastIncrement = (now / 1000 - ust2); if (lastIncrement > m_sync.interval) { lastIncrement = m_sync.interval; CLog::Log(LOGWARNING, "CGLContextEGL::SwapBuffers: last msc time greater than interval"); } uint64_t sleeptime = m_sync.interval - lastIncrement; usleep(sleeptime); cont++; msc2++; CLog::Log(LOGDEBUG, "CGLContextEGL::SwapBuffers: sync sleep: {}", sleeptime); } } else if ((m_sync.cont > 5) && (msc2 == m_sync.msc2)) { // sleep until next vertical retrace // this avoids blocking outside of this function uint64_t lastIncrement = (now / 1000 - ust2); if (lastIncrement > m_sync.interval) { lastIncrement = m_sync.interval; CLog::Log(LOGWARNING, "CGLContextEGL::SwapBuffers: last msc time greater than interval (1)"); } uint64_t sleeptime = m_sync.interval - lastIncrement; usleep(sleeptime); msc2++; } { std::unique_lock lock(m_syncLock); m_sync.ust1 = ust1; m_sync.ust2 = ust2; m_sync.msc1 = msc1; m_sync.msc2 = msc2; m_sync.interval = interval; m_sync.cont = cont; } } uint64_t CGLContextEGL::GetVblankTiming(uint64_t &msc, uint64_t &interval) { struct timespec nowTs; uint64_t now; clock_gettime(CLOCK_MONOTONIC, &nowTs); now = static_cast(nowTs.tv_sec) * 1000000000ULL + nowTs.tv_nsec; now /= 1000; std::unique_lock lock(m_syncLock); msc = m_sync.msc2; interval = (m_sync.cont >= 5) ? m_sync.interval : m_sync.ust2 - m_sync.ust1; if (interval == 0) return 0; if (now < m_sync.ust2) { return 0; } uint64_t ret = now - m_sync.ust2; while (ret > interval) { ret -= interval; msc++; } return ret; } void CGLContextEGL::QueryExtensions() { std::string extensions = eglQueryString(m_eglDisplay, EGL_EXTENSIONS); m_extensions = std::string(" ") + extensions + " "; CLog::Log(LOGDEBUG, "EGL_EXTENSIONS:{}", m_extensions); }