diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/utils/EGLUtils.cpp | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/utils/EGLUtils.cpp')
-rw-r--r-- | xbmc/utils/EGLUtils.cpp | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/xbmc/utils/EGLUtils.cpp b/xbmc/utils/EGLUtils.cpp new file mode 100644 index 0000000..7c5e709 --- /dev/null +++ b/xbmc/utils/EGLUtils.cpp @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2017-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. + */ + +#include "EGLUtils.h" + +#include "ServiceBroker.h" +#include "StringUtils.h" +#include "guilib/IDirtyRegionSolver.h" +#include "log.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" + +#include <map> + +#include <EGL/eglext.h> + +namespace +{ + +#define X(VAL) std::make_pair(VAL, #VAL) +std::map<EGLint, const char*> eglAttributes = +{ + // please keep attributes in accordance to: + // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglGetConfigAttrib.xhtml + X(EGL_ALPHA_SIZE), + X(EGL_ALPHA_MASK_SIZE), + X(EGL_BIND_TO_TEXTURE_RGB), + X(EGL_BIND_TO_TEXTURE_RGBA), + X(EGL_BLUE_SIZE), + X(EGL_BUFFER_SIZE), + X(EGL_COLOR_BUFFER_TYPE), + X(EGL_CONFIG_CAVEAT), + X(EGL_CONFIG_ID), + X(EGL_CONFORMANT), + X(EGL_DEPTH_SIZE), + X(EGL_GREEN_SIZE), + X(EGL_LEVEL), + X(EGL_LUMINANCE_SIZE), + X(EGL_MAX_PBUFFER_WIDTH), + X(EGL_MAX_PBUFFER_HEIGHT), + X(EGL_MAX_PBUFFER_PIXELS), + X(EGL_MAX_SWAP_INTERVAL), + X(EGL_MIN_SWAP_INTERVAL), + X(EGL_NATIVE_RENDERABLE), + X(EGL_NATIVE_VISUAL_ID), + X(EGL_NATIVE_VISUAL_TYPE), + X(EGL_RED_SIZE), + X(EGL_RENDERABLE_TYPE), + X(EGL_SAMPLE_BUFFERS), + X(EGL_SAMPLES), + X(EGL_STENCIL_SIZE), + X(EGL_SURFACE_TYPE), + X(EGL_TRANSPARENT_TYPE), + X(EGL_TRANSPARENT_RED_VALUE), + X(EGL_TRANSPARENT_GREEN_VALUE), + X(EGL_TRANSPARENT_BLUE_VALUE) +}; + +std::map<EGLenum, const char*> eglErrors = +{ + // please keep errors in accordance to: + // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglGetError.xhtml + X(EGL_SUCCESS), + X(EGL_NOT_INITIALIZED), + X(EGL_BAD_ACCESS), + X(EGL_BAD_ALLOC), + X(EGL_BAD_ATTRIBUTE), + X(EGL_BAD_CONFIG), + X(EGL_BAD_CONTEXT), + X(EGL_BAD_CURRENT_SURFACE), + X(EGL_BAD_DISPLAY), + X(EGL_BAD_MATCH), + X(EGL_BAD_NATIVE_PIXMAP), + X(EGL_BAD_NATIVE_WINDOW), + X(EGL_BAD_PARAMETER), + X(EGL_BAD_SURFACE), + X(EGL_CONTEXT_LOST), +}; + +std::map<EGLint, const char*> eglErrorType = +{ + X(EGL_DEBUG_MSG_CRITICAL_KHR), + X(EGL_DEBUG_MSG_ERROR_KHR), + X(EGL_DEBUG_MSG_WARN_KHR), + X(EGL_DEBUG_MSG_INFO_KHR), +}; +#undef X + +} // namespace + +void EglErrorCallback(EGLenum error, + const char* command, + EGLint messageType, + EGLLabelKHR threadLabel, + EGLLabelKHR objectLabel, + const char* message) +{ + std::string errorStr; + std::string typeStr; + + auto eglError = eglErrors.find(error); + if (eglError != eglErrors.end()) + { + errorStr = eglError->second; + } + + auto eglType = eglErrorType.find(messageType); + if (eglType != eglErrorType.end()) + { + typeStr = eglType->second; + } + + CLog::Log(LOGDEBUG, "EGL Debugging:\nError: {}\nCommand: {}\nType: {}\nMessage: {}", errorStr, command, typeStr, message); +} + +std::set<std::string> CEGLUtils::GetClientExtensions() +{ + const char* extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (!extensions) + { + return {}; + } + std::set<std::string> result; + StringUtils::SplitTo(std::inserter(result, result.begin()), extensions, " "); + return result; +} + +std::set<std::string> CEGLUtils::GetExtensions(EGLDisplay eglDisplay) +{ + const char* extensions = eglQueryString(eglDisplay, EGL_EXTENSIONS); + if (!extensions) + { + throw std::runtime_error("Could not query EGL for extensions"); + } + std::set<std::string> result; + StringUtils::SplitTo(std::inserter(result, result.begin()), extensions, " "); + return result; +} + +bool CEGLUtils::HasExtension(EGLDisplay eglDisplay, const std::string& name) +{ + auto exts = GetExtensions(eglDisplay); + return (exts.find(name) != exts.end()); +} + +bool CEGLUtils::HasClientExtension(const std::string& name) +{ + auto exts = GetClientExtensions(); + return (exts.find(name) != exts.end()); +} + +void CEGLUtils::Log(int logLevel, const std::string& what) +{ + EGLenum error = eglGetError(); + std::string errorStr = StringUtils::Format("{:#04X}", error); + + auto eglError = eglErrors.find(error); + if (eglError != eglErrors.end()) + { + errorStr = eglError->second; + } + + CLog::Log(logLevel, "{} ({})", what, errorStr); +} + +CEGLContextUtils::CEGLContextUtils(EGLenum platform, std::string const& platformExtension) +: m_platform{platform} +{ + if (CEGLUtils::HasClientExtension("EGL_KHR_debug")) + { + auto eglDebugMessageControl = CEGLUtils::GetRequiredProcAddress<PFNEGLDEBUGMESSAGECONTROLKHRPROC>("eglDebugMessageControlKHR"); + + EGLAttrib eglDebugAttribs[] = {EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, + EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, + EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, + EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, + EGL_NONE}; + + eglDebugMessageControl(EglErrorCallback, eglDebugAttribs); + } + + m_platformSupported = CEGLUtils::HasClientExtension("EGL_EXT_platform_base") && CEGLUtils::HasClientExtension(platformExtension); +} + +bool CEGLContextUtils::IsPlatformSupported() const +{ + return m_platformSupported; +} + +CEGLContextUtils::~CEGLContextUtils() +{ + Destroy(); +} + +bool CEGLContextUtils::CreateDisplay(EGLNativeDisplayType nativeDisplay) +{ + if (m_eglDisplay != EGL_NO_DISPLAY) + { + throw std::logic_error("Do not call CreateDisplay when display has already been created"); + } + + m_eglDisplay = eglGetDisplay(nativeDisplay); + if (m_eglDisplay == EGL_NO_DISPLAY) + { + CEGLUtils::Log(LOGERROR, "failed to get EGL display"); + return false; + } + + return true; +} + +bool CEGLContextUtils::CreatePlatformDisplay(void* nativeDisplay, EGLNativeDisplayType nativeDisplayLegacy) +{ + if (m_eglDisplay != EGL_NO_DISPLAY) + { + throw std::logic_error("Do not call CreateDisplay when display has already been created"); + } + +#if defined(EGL_EXT_platform_base) + if (IsPlatformSupported()) + { + // Theoretically it is possible to use eglGetDisplay() and eglCreateWindowSurface, + // but then the EGL library basically has to guess which platform we want + // if it supports multiple which is usually the case - + // it's better and safer to make it explicit + + auto getPlatformDisplayEXT = CEGLUtils::GetRequiredProcAddress<PFNEGLGETPLATFORMDISPLAYEXTPROC>("eglGetPlatformDisplayEXT"); + m_eglDisplay = getPlatformDisplayEXT(m_platform, nativeDisplay, nullptr); + + if (m_eglDisplay == EGL_NO_DISPLAY) + { + CEGLUtils::Log(LOGERROR, "failed to get platform display"); + return false; + } + } +#endif + + if (m_eglDisplay == EGL_NO_DISPLAY) + { + return CreateDisplay(nativeDisplayLegacy); + } + + return true; +} + +bool CEGLContextUtils::InitializeDisplay(EGLint renderingApi) +{ + if (!eglInitialize(m_eglDisplay, nullptr, nullptr)) + { + CEGLUtils::Log(LOGERROR, "failed to initialize EGL display"); + Destroy(); + return false; + } + + const char* value; + value = eglQueryString(m_eglDisplay, EGL_VERSION); + CLog::Log(LOGINFO, "EGL_VERSION = {}", value ? value : "NULL"); + + value = eglQueryString(m_eglDisplay, EGL_VENDOR); + CLog::Log(LOGINFO, "EGL_VENDOR = {}", value ? value : "NULL"); + + value = eglQueryString(m_eglDisplay, EGL_EXTENSIONS); + CLog::Log(LOGINFO, "EGL_EXTENSIONS = {}", value ? value : "NULL"); + + value = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + CLog::Log(LOGINFO, "EGL_CLIENT_EXTENSIONS = {}", value ? value : "NULL"); + + if (eglBindAPI(renderingApi) != EGL_TRUE) + { + CEGLUtils::Log(LOGERROR, "failed to bind EGL API"); + Destroy(); + return false; + } + + return true; +} + +bool CEGLContextUtils::ChooseConfig(EGLint renderableType, EGLint visualId, bool hdr) +{ + EGLint numMatched{0}; + + if (m_eglDisplay == EGL_NO_DISPLAY) + { + throw std::logic_error("Choosing an EGLConfig requires an EGL display"); + } + + EGLint surfaceType = EGL_WINDOW_BIT; + // for the non-trivial dirty region modes, we need the EGL buffer to be preserved across updates + int guiAlgorithmDirtyRegions = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiAlgorithmDirtyRegions; + if (guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_COST_REDUCTION || + guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_UNION) + surfaceType |= EGL_SWAP_BEHAVIOR_PRESERVED_BIT; + + CEGLAttributesVec attribs; + attribs.Add({{EGL_RED_SIZE, 8}, + {EGL_GREEN_SIZE, 8}, + {EGL_BLUE_SIZE, 8}, + {EGL_ALPHA_SIZE, 2}, + {EGL_DEPTH_SIZE, 16}, + {EGL_STENCIL_SIZE, 0}, + {EGL_SAMPLE_BUFFERS, 0}, + {EGL_SAMPLES, 0}, + {EGL_SURFACE_TYPE, surfaceType}, + {EGL_RENDERABLE_TYPE, renderableType}}); + + EGLConfig* currentConfig(hdr ? &m_eglHDRConfig : &m_eglConfig); + + if (hdr) +#if EGL_EXT_pixel_format_float + attribs.Add({{EGL_COLOR_COMPONENT_TYPE_EXT, EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT}}); +#else + return false; +#endif + + const char* errorMsg = nullptr; + + if (eglChooseConfig(m_eglDisplay, attribs.Get(), nullptr, 0, &numMatched) != EGL_TRUE) + errorMsg = "failed to query number of EGL configs"; + + std::vector<EGLConfig> eglConfigs(numMatched); + if (eglChooseConfig(m_eglDisplay, attribs.Get(), eglConfigs.data(), numMatched, &numMatched) != EGL_TRUE) + errorMsg = "failed to find EGL configs with appropriate attributes"; + + if (errorMsg) + { + if (!hdr) + { + CEGLUtils::Log(LOGERROR, errorMsg); + Destroy(); + } + else + CEGLUtils::Log(LOGINFO, errorMsg); + return false; + } + + EGLint id{0}; + for (const auto &eglConfig: eglConfigs) + { + *currentConfig = eglConfig; + + if (visualId == 0) + break; + + if (eglGetConfigAttrib(m_eglDisplay, *currentConfig, EGL_NATIVE_VISUAL_ID, &id) != EGL_TRUE) + CEGLUtils::Log(LOGERROR, "failed to query EGL attribute EGL_NATIVE_VISUAL_ID"); + + if (visualId == id) + break; + } + + if (visualId != 0 && visualId != id) + { + CLog::Log(LOGDEBUG, "failed to find EGL config with EGL_NATIVE_VISUAL_ID={}", visualId); + return false; + } + + CLog::Log(LOGDEBUG, "EGL {}Config Attributes:", hdr ? "HDR " : ""); + + for (const auto &eglAttribute : eglAttributes) + { + EGLint value{0}; + if (eglGetConfigAttrib(m_eglDisplay, *currentConfig, eglAttribute.first, &value) != EGL_TRUE) + CEGLUtils::Log(LOGERROR, + StringUtils::Format("failed to query EGL attribute {}", eglAttribute.second)); + + // we only need to print the hex value if it's an actual EGL define + CLog::Log(LOGDEBUG, " {}: {}", eglAttribute.second, + (value >= 0x3000 && value <= 0x3200) ? StringUtils::Format("{:#04x}", value) + : std::to_string(value)); + } + + return true; +} + +EGLint CEGLContextUtils::GetConfigAttrib(EGLint attribute) const +{ + EGLint value{0}; + if (eglGetConfigAttrib(m_eglDisplay, m_eglConfig, attribute, &value) != EGL_TRUE) + CEGLUtils::Log(LOGERROR, "failed to query EGL attribute"); + return value; +} + +bool CEGLContextUtils::CreateContext(CEGLAttributesVec contextAttribs) +{ + if (m_eglContext != EGL_NO_CONTEXT) + { + throw std::logic_error("Do not call CreateContext when context has already been created"); + } + + EGLConfig eglConfig{m_eglConfig}; + + if (CEGLUtils::HasExtension(m_eglDisplay, "EGL_KHR_no_config_context")) + eglConfig = EGL_NO_CONFIG_KHR; + + if (CEGLUtils::HasExtension(m_eglDisplay, "EGL_IMG_context_priority")) + contextAttribs.Add({{EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG}}); + + if (CEGLUtils::HasExtension(m_eglDisplay, "EGL_KHR_create_context") && + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_openGlDebugging) + { + contextAttribs.Add({{EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR}}); + } + + m_eglContext = eglCreateContext(m_eglDisplay, eglConfig, + EGL_NO_CONTEXT, contextAttribs.Get()); + + if (CEGLUtils::HasExtension(m_eglDisplay, "EGL_IMG_context_priority")) + { + EGLint value{EGL_CONTEXT_PRIORITY_MEDIUM_IMG}; + + if (eglQueryContext(m_eglDisplay, m_eglContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value) != EGL_TRUE) + CEGLUtils::Log(LOGERROR, "failed to query EGL context attribute EGL_CONTEXT_PRIORITY_LEVEL_IMG"); + + if (value != EGL_CONTEXT_PRIORITY_HIGH_IMG) + CLog::Log(LOGDEBUG, "Failed to obtain a high priority EGL context"); + } + + if (m_eglContext == EGL_NO_CONTEXT) + { + // This is expected to fail under some circumstances, so log as debug + CLog::Log(LOGDEBUG, "Failed to create EGL context (EGL error {})", eglGetError()); + return false; + } + + return true; +} + +bool CEGLContextUtils::BindContext() +{ + if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE || m_eglContext == EGL_NO_CONTEXT) + { + throw std::logic_error("Activating an EGLContext requires display, surface, and context"); + } + + if (eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext) != EGL_TRUE) + { + CLog::Log(LOGERROR, "Failed to make context current {} {} {}", fmt::ptr(m_eglDisplay), + fmt::ptr(m_eglSurface), fmt::ptr(m_eglContext)); + return false; + } + + return true; +} + +void CEGLContextUtils::SurfaceAttrib() +{ + if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE) + { + throw std::logic_error("Setting surface attributes requires a surface"); + } + + // for the non-trivial dirty region modes, we need the EGL buffer to be preserved across updates + int guiAlgorithmDirtyRegions = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiAlgorithmDirtyRegions; + if (guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_COST_REDUCTION || + guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_UNION) + { + if (eglSurfaceAttrib(m_eglDisplay, m_eglSurface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED) != EGL_TRUE) + { + CEGLUtils::Log(LOGERROR, "failed to set EGL_BUFFER_PRESERVED swap behavior"); + } + } +} + +void CEGLContextUtils::SurfaceAttrib(EGLint attribute, EGLint value) +{ + if (eglSurfaceAttrib(m_eglDisplay, m_eglSurface, attribute, value) != EGL_TRUE) + { + CEGLUtils::Log(LOGERROR, "failed to set EGL_BUFFER_PRESERVED swap behavior"); + } +} + +bool CEGLContextUtils::CreateSurface(EGLNativeWindowType nativeWindow, EGLint HDRcolorSpace /* = EGL_NONE */) +{ + if (m_eglDisplay == EGL_NO_DISPLAY) + { + throw std::logic_error("Creating a surface requires a display"); + } + if (m_eglSurface != EGL_NO_SURFACE) + { + throw std::logic_error("Do not call CreateSurface when surface has already been created"); + } + + CEGLAttributesVec attribs; + EGLConfig config = m_eglConfig; + +#ifdef EGL_GL_COLORSPACE + if (HDRcolorSpace != EGL_NONE) + { + attribs.Add({{EGL_GL_COLORSPACE, HDRcolorSpace}}); + config = m_eglHDRConfig; + } +#endif + + m_eglSurface = eglCreateWindowSurface(m_eglDisplay, config, nativeWindow, attribs.Get()); + + if (m_eglSurface == EGL_NO_SURFACE) + { + CEGLUtils::Log(LOGERROR, "failed to create window surface"); + return false; + } + + SurfaceAttrib(); + + return true; +} + +bool CEGLContextUtils::CreatePlatformSurface(void* nativeWindow, EGLNativeWindowType nativeWindowLegacy) +{ + if (m_eglDisplay == EGL_NO_DISPLAY) + { + throw std::logic_error("Creating a surface requires a display"); + } + if (m_eglSurface != EGL_NO_SURFACE) + { + throw std::logic_error("Do not call CreateSurface when surface has already been created"); + } + +#if defined(EGL_EXT_platform_base) + if (IsPlatformSupported()) + { + auto createPlatformWindowSurfaceEXT = CEGLUtils::GetRequiredProcAddress<PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC>("eglCreatePlatformWindowSurfaceEXT"); + m_eglSurface = createPlatformWindowSurfaceEXT(m_eglDisplay, m_eglConfig, nativeWindow, nullptr); + + if (m_eglSurface == EGL_NO_SURFACE) + { + CEGLUtils::Log(LOGERROR, "failed to create platform window surface"); + return false; + } + } +#endif + + if (m_eglSurface == EGL_NO_SURFACE) + { + return CreateSurface(nativeWindowLegacy); + } + + SurfaceAttrib(); + + return true; +} + +void CEGLContextUtils::Destroy() +{ + DestroyContext(); + DestroySurface(); + + if (m_eglDisplay != EGL_NO_DISPLAY) + { + eglTerminate(m_eglDisplay); + m_eglDisplay = EGL_NO_DISPLAY; + } +} + +void CEGLContextUtils::DestroyContext() +{ + if (m_eglContext != EGL_NO_CONTEXT) + { + eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(m_eglDisplay, m_eglContext); + m_eglContext = EGL_NO_CONTEXT; + } +} + +void CEGLContextUtils::DestroySurface() +{ + if (m_eglSurface != EGL_NO_SURFACE) + { + eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(m_eglDisplay, m_eglSurface); + m_eglSurface = EGL_NO_SURFACE; + } +} + + +bool CEGLContextUtils::SetVSync(bool enable) +{ + if (m_eglDisplay == EGL_NO_DISPLAY) + { + return false; + } + + return (eglSwapInterval(m_eglDisplay, enable) == EGL_TRUE); +} + +bool CEGLContextUtils::TrySwapBuffers() +{ + if (m_eglDisplay == EGL_NO_DISPLAY || m_eglSurface == EGL_NO_SURFACE) + { + return false; + } + + return (eglSwapBuffers(m_eglDisplay, m_eglSurface) == EGL_TRUE); +} |