2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
11 #include "ServiceBroker.h"
12 #include "StringUtils.h"
13 #include "guilib/IDirtyRegionSolver.h"
15 #include "settings/AdvancedSettings.h"
16 #include "settings/SettingsComponent.h"
17 #include "utils/Geometry.h"
22 #include <EGL/eglext.h>
23 #include <EGL/eglplatform.h>
28 #define X(VAL) std::make_pair(VAL, #VAL)
29 std::map
<EGLint
, const char*> eglAttributes
=
31 // please keep attributes in accordance to:
32 // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglGetConfigAttrib.xhtml
34 X(EGL_ALPHA_MASK_SIZE
),
35 X(EGL_BIND_TO_TEXTURE_RGB
),
36 X(EGL_BIND_TO_TEXTURE_RGBA
),
39 X(EGL_COLOR_BUFFER_TYPE
),
46 X(EGL_LUMINANCE_SIZE
),
47 X(EGL_MAX_PBUFFER_WIDTH
),
48 X(EGL_MAX_PBUFFER_HEIGHT
),
49 X(EGL_MAX_PBUFFER_PIXELS
),
50 X(EGL_MAX_SWAP_INTERVAL
),
51 X(EGL_MIN_SWAP_INTERVAL
),
52 X(EGL_NATIVE_RENDERABLE
),
53 X(EGL_NATIVE_VISUAL_ID
),
54 X(EGL_NATIVE_VISUAL_TYPE
),
56 X(EGL_RENDERABLE_TYPE
),
57 X(EGL_SAMPLE_BUFFERS
),
61 X(EGL_TRANSPARENT_TYPE
),
62 X(EGL_TRANSPARENT_RED_VALUE
),
63 X(EGL_TRANSPARENT_GREEN_VALUE
),
64 X(EGL_TRANSPARENT_BLUE_VALUE
)
67 std::map
<EGLenum
, const char*> eglErrors
=
69 // please keep errors in accordance to:
70 // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglGetError.xhtml
72 X(EGL_NOT_INITIALIZED
),
78 X(EGL_BAD_CURRENT_SURFACE
),
81 X(EGL_BAD_NATIVE_PIXMAP
),
82 X(EGL_BAD_NATIVE_WINDOW
),
88 std::map
<EGLint
, const char*> eglErrorType
=
90 X(EGL_DEBUG_MSG_CRITICAL_KHR
),
91 X(EGL_DEBUG_MSG_ERROR_KHR
),
92 X(EGL_DEBUG_MSG_WARN_KHR
),
93 X(EGL_DEBUG_MSG_INFO_KHR
),
99 void EglErrorCallback(EGLenum error
,
102 EGLLabelKHR threadLabel
,
103 EGLLabelKHR objectLabel
,
106 std::string errorStr
;
109 auto eglError
= eglErrors
.find(error
);
110 if (eglError
!= eglErrors
.end())
112 errorStr
= eglError
->second
;
115 auto eglType
= eglErrorType
.find(messageType
);
116 if (eglType
!= eglErrorType
.end())
118 typeStr
= eglType
->second
;
121 CLog::Log(LOGDEBUG
, "EGL Debugging:\nError: {}\nCommand: {}\nType: {}\nMessage: {}", errorStr
,
122 command
, typeStr
, message
? message
: "");
125 std::set
<std::string
> CEGLUtils::GetClientExtensions()
127 const char* extensions
= eglQueryString(EGL_NO_DISPLAY
, EGL_EXTENSIONS
);
132 std::set
<std::string
> result
;
133 StringUtils::SplitTo(std::inserter(result
, result
.begin()), extensions
, " ");
137 std::set
<std::string
> CEGLUtils::GetExtensions(EGLDisplay eglDisplay
)
139 const char* extensions
= eglQueryString(eglDisplay
, EGL_EXTENSIONS
);
142 throw std::runtime_error("Could not query EGL for extensions");
144 std::set
<std::string
> result
;
145 StringUtils::SplitTo(std::inserter(result
, result
.begin()), extensions
, " ");
149 bool CEGLUtils::HasExtension(EGLDisplay eglDisplay
, const std::string
& name
)
151 auto exts
= GetExtensions(eglDisplay
);
152 return (exts
.find(name
) != exts
.end());
155 bool CEGLUtils::HasClientExtension(const std::string
& name
)
157 auto exts
= GetClientExtensions();
158 return (exts
.find(name
) != exts
.end());
161 void CEGLUtils::Log(int logLevel
, const std::string
& what
)
163 EGLenum error
= eglGetError();
164 std::string errorStr
= StringUtils::Format("{:#04X}", error
);
166 auto eglError
= eglErrors
.find(error
);
167 if (eglError
!= eglErrors
.end())
169 errorStr
= eglError
->second
;
172 CLog::Log(logLevel
, "{} ({})", what
, errorStr
);
175 CEGLContextUtils::CEGLContextUtils(EGLenum platform
, std::string
const& platformExtension
)
176 : m_platform
{platform
}
178 if (CEGLUtils::HasClientExtension("EGL_KHR_debug"))
180 auto eglDebugMessageControl
= CEGLUtils::GetRequiredProcAddress
<PFNEGLDEBUGMESSAGECONTROLKHRPROC
>("eglDebugMessageControlKHR");
182 EGLAttrib eglDebugAttribs
[] = {EGL_DEBUG_MSG_CRITICAL_KHR
, EGL_TRUE
,
183 EGL_DEBUG_MSG_ERROR_KHR
, EGL_TRUE
,
184 EGL_DEBUG_MSG_WARN_KHR
, EGL_TRUE
,
185 EGL_DEBUG_MSG_INFO_KHR
, EGL_TRUE
,
188 eglDebugMessageControl(EglErrorCallback
, eglDebugAttribs
);
191 m_platformSupported
= CEGLUtils::HasClientExtension("EGL_EXT_platform_base") &&
192 CEGLUtils::HasClientExtension(platformExtension
);
195 bool CEGLContextUtils::IsPlatformSupported() const
197 return m_platformSupported
;
200 CEGLContextUtils::~CEGLContextUtils()
205 bool CEGLContextUtils::CreateDisplay(EGLNativeDisplayType nativeDisplay
)
207 if (m_eglDisplay
!= EGL_NO_DISPLAY
)
209 throw std::logic_error("Do not call CreateDisplay when display has already been created");
212 m_eglDisplay
= eglGetDisplay(nativeDisplay
);
213 if (m_eglDisplay
== EGL_NO_DISPLAY
)
215 CEGLUtils::Log(LOGERROR
, "failed to get EGL display");
222 bool CEGLContextUtils::CreatePlatformDisplay(void* nativeDisplay
, EGLNativeDisplayType nativeDisplayLegacy
)
224 if (m_eglDisplay
!= EGL_NO_DISPLAY
)
226 throw std::logic_error("Do not call CreateDisplay when display has already been created");
229 #if defined(EGL_EXT_platform_base)
230 if (IsPlatformSupported())
232 // Theoretically it is possible to use eglGetDisplay() and eglCreateWindowSurface,
233 // but then the EGL library basically has to guess which platform we want
234 // if it supports multiple which is usually the case -
235 // it's better and safer to make it explicit
237 auto getPlatformDisplayEXT
= CEGLUtils::GetRequiredProcAddress
<PFNEGLGETPLATFORMDISPLAYEXTPROC
>("eglGetPlatformDisplayEXT");
238 m_eglDisplay
= getPlatformDisplayEXT(m_platform
, nativeDisplay
, nullptr);
240 if (m_eglDisplay
== EGL_NO_DISPLAY
)
242 CEGLUtils::Log(LOGERROR
, "failed to get platform display");
248 if (m_eglDisplay
== EGL_NO_DISPLAY
)
250 return CreateDisplay(nativeDisplayLegacy
);
256 bool CEGLContextUtils::InitializeDisplay(EGLint renderingApi
)
258 if (!eglInitialize(m_eglDisplay
, nullptr, nullptr))
260 CEGLUtils::Log(LOGERROR
, "failed to initialize EGL display");
266 value
= eglQueryString(m_eglDisplay
, EGL_VERSION
);
267 CLog::Log(LOGINFO
, "EGL_VERSION = {}", value
? value
: "NULL");
269 value
= eglQueryString(m_eglDisplay
, EGL_VENDOR
);
270 CLog::Log(LOGINFO
, "EGL_VENDOR = {}", value
? value
: "NULL");
272 value
= eglQueryString(m_eglDisplay
, EGL_EXTENSIONS
);
273 CLog::Log(LOGINFO
, "EGL_EXTENSIONS = {}", value
? value
: "NULL");
275 value
= eglQueryString(EGL_NO_DISPLAY
, EGL_EXTENSIONS
);
276 CLog::Log(LOGINFO
, "EGL_CLIENT_EXTENSIONS = {}", value
? value
: "NULL");
278 if (eglBindAPI(renderingApi
) != EGL_TRUE
)
280 CEGLUtils::Log(LOGERROR
, "failed to bind EGL API");
288 bool CEGLContextUtils::ChooseConfig(EGLint renderableType
, EGLint visualId
, bool hdr
)
290 EGLint numMatched
{0};
292 if (m_eglDisplay
== EGL_NO_DISPLAY
)
294 throw std::logic_error("Choosing an EGLConfig requires an EGL display");
297 EGLint surfaceType
= EGL_WINDOW_BIT
;
299 CEGLAttributesVec attribs
;
300 attribs
.Add({{EGL_RED_SIZE
, 8},
304 {EGL_DEPTH_SIZE
, 16},
305 {EGL_STENCIL_SIZE
, 0},
306 {EGL_SAMPLE_BUFFERS
, 0},
308 {EGL_SURFACE_TYPE
, surfaceType
},
309 {EGL_RENDERABLE_TYPE
, renderableType
}});
311 EGLConfig
* currentConfig(hdr
? &m_eglHDRConfig
: &m_eglConfig
);
314 #if EGL_EXT_pixel_format_float
315 attribs
.Add({{EGL_COLOR_COMPONENT_TYPE_EXT
, EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT
}});
320 const char* errorMsg
= nullptr;
322 if (eglChooseConfig(m_eglDisplay
, attribs
.Get(), nullptr, 0, &numMatched
) != EGL_TRUE
)
323 errorMsg
= "failed to query number of EGL configs";
325 std::vector
<EGLConfig
> eglConfigs(numMatched
);
326 if (eglChooseConfig(m_eglDisplay
, attribs
.Get(), eglConfigs
.data(), numMatched
, &numMatched
) != EGL_TRUE
)
327 errorMsg
= "failed to find EGL configs with appropriate attributes";
333 CEGLUtils::Log(LOGERROR
, errorMsg
);
337 CEGLUtils::Log(LOGINFO
, errorMsg
);
342 for (const auto &eglConfig
: eglConfigs
)
344 *currentConfig
= eglConfig
;
349 if (eglGetConfigAttrib(m_eglDisplay
, *currentConfig
, EGL_NATIVE_VISUAL_ID
, &id
) != EGL_TRUE
)
350 CEGLUtils::Log(LOGERROR
, "failed to query EGL attribute EGL_NATIVE_VISUAL_ID");
356 if (visualId
!= 0 && visualId
!= id
)
358 CLog::Log(LOGDEBUG
, "failed to find EGL config with EGL_NATIVE_VISUAL_ID={}", visualId
);
362 CLog::Log(LOGDEBUG
, "EGL {}Config Attributes:", hdr
? "HDR " : "");
364 for (const auto &eglAttribute
: eglAttributes
)
367 if (eglGetConfigAttrib(m_eglDisplay
, *currentConfig
, eglAttribute
.first
, &value
) != EGL_TRUE
)
368 CEGLUtils::Log(LOGERROR
,
369 StringUtils::Format("failed to query EGL attribute {}", eglAttribute
.second
));
371 // we only need to print the hex value if it's an actual EGL define
372 CLog::Log(LOGDEBUG
, " {}: {}", eglAttribute
.second
,
373 (value
>= 0x3000 && value
<= 0x3200) ? StringUtils::Format("{:#04x}", value
)
374 : std::to_string(value
));
380 EGLint
CEGLContextUtils::GetConfigAttrib(EGLint attribute
) const
383 if (eglGetConfigAttrib(m_eglDisplay
, m_eglConfig
, attribute
, &value
) != EGL_TRUE
)
384 CEGLUtils::Log(LOGERROR
, "failed to query EGL attribute");
388 bool CEGLContextUtils::CreateContext(CEGLAttributesVec contextAttribs
)
390 if (m_eglContext
!= EGL_NO_CONTEXT
)
392 throw std::logic_error("Do not call CreateContext when context has already been created");
395 EGLConfig eglConfig
{m_eglConfig
};
397 if (CEGLUtils::HasExtension(m_eglDisplay
, "EGL_KHR_no_config_context"))
398 eglConfig
= EGL_NO_CONFIG_KHR
;
400 if (CEGLUtils::HasExtension(m_eglDisplay
, "EGL_IMG_context_priority"))
401 contextAttribs
.Add({{EGL_CONTEXT_PRIORITY_LEVEL_IMG
, EGL_CONTEXT_PRIORITY_HIGH_IMG
}});
403 if (CEGLUtils::HasExtension(m_eglDisplay
, "EGL_KHR_create_context") &&
404 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_openGlDebugging
)
406 contextAttribs
.Add({{EGL_CONTEXT_FLAGS_KHR
, EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR
}});
409 m_eglContext
= eglCreateContext(m_eglDisplay
, eglConfig
,
410 EGL_NO_CONTEXT
, contextAttribs
.Get());
412 if (CEGLUtils::HasExtension(m_eglDisplay
, "EGL_IMG_context_priority"))
414 EGLint value
{EGL_CONTEXT_PRIORITY_MEDIUM_IMG
};
416 if (eglQueryContext(m_eglDisplay
, m_eglContext
, EGL_CONTEXT_PRIORITY_LEVEL_IMG
, &value
) != EGL_TRUE
)
417 CEGLUtils::Log(LOGERROR
, "failed to query EGL context attribute EGL_CONTEXT_PRIORITY_LEVEL_IMG");
419 if (value
!= EGL_CONTEXT_PRIORITY_HIGH_IMG
)
420 CLog::Log(LOGDEBUG
, "Failed to obtain a high priority EGL context");
423 if (m_eglContext
== EGL_NO_CONTEXT
)
425 // This is expected to fail under some circumstances, so log as debug
426 CLog::Log(LOGDEBUG
, "Failed to create EGL context (EGL error {})", eglGetError());
431 eglCreateContext(m_eglDisplay
, m_eglConfig
, m_eglContext
, contextAttribs
.Get());
433 if (m_eglUploadContext
== EGL_NO_CONTEXT
)
434 CLog::Log(LOGWARNING
, "Failed to create EGL upload context");
436 m_bufferAgeSupport
= CEGLUtils::HasExtension(m_eglDisplay
, "EGL_EXT_buffer_age");
438 if (CEGLUtils::HasExtension(m_eglDisplay
, "EGL_KHR_partial_update"))
440 m_partialUpdateSupport
= true;
441 m_eglSetDamageRegionKHR
=
442 reinterpret_cast<PFNEGLSETDAMAGEREGIONKHRPROC
>(eglGetProcAddress("eglSetDamageRegionKHR"));
448 bool CEGLContextUtils::BindContext()
450 if (m_eglDisplay
== EGL_NO_DISPLAY
|| m_eglSurface
== EGL_NO_SURFACE
|| m_eglContext
== EGL_NO_CONTEXT
)
452 throw std::logic_error("Activating an EGLContext requires display, surface, and context");
455 if (eglMakeCurrent(m_eglDisplay
, m_eglSurface
, m_eglSurface
, m_eglContext
) != EGL_TRUE
)
457 CLog::Log(LOGERROR
, "Failed to make context current {} {} {}", fmt::ptr(m_eglDisplay
),
458 fmt::ptr(m_eglSurface
), fmt::ptr(m_eglContext
));
465 void CEGLContextUtils::SurfaceAttrib()
467 if (m_eglDisplay
== EGL_NO_DISPLAY
|| m_eglSurface
== EGL_NO_SURFACE
)
469 throw std::logic_error("Setting surface attributes requires a surface");
473 void CEGLContextUtils::SurfaceAttrib(EGLint attribute
, EGLint value
)
475 if (eglSurfaceAttrib(m_eglDisplay
, m_eglSurface
, attribute
, value
) != EGL_TRUE
)
477 CEGLUtils::Log(LOGERROR
, "failed to set EGL_BUFFER_PRESERVED swap behavior");
481 bool CEGLContextUtils::CreateSurface(EGLNativeWindowType nativeWindow
, EGLint HDRcolorSpace
/* = EGL_NONE */)
483 if (m_eglDisplay
== EGL_NO_DISPLAY
)
485 throw std::logic_error("Creating a surface requires a display");
487 if (m_eglSurface
!= EGL_NO_SURFACE
)
489 throw std::logic_error("Do not call CreateSurface when surface has already been created");
492 CEGLAttributesVec attribs
;
493 EGLConfig config
= m_eglConfig
;
495 #ifdef EGL_GL_COLORSPACE
496 if (HDRcolorSpace
!= EGL_NONE
)
498 attribs
.Add({{EGL_GL_COLORSPACE
, HDRcolorSpace
}});
499 config
= m_eglHDRConfig
;
503 m_eglSurface
= eglCreateWindowSurface(m_eglDisplay
, config
, nativeWindow
, attribs
.Get());
505 if (m_eglSurface
== EGL_NO_SURFACE
)
507 CEGLUtils::Log(LOGERROR
, "failed to create window surface");
516 bool CEGLContextUtils::CreatePlatformSurface(void* nativeWindow
, EGLNativeWindowType nativeWindowLegacy
)
518 if (m_eglDisplay
== EGL_NO_DISPLAY
)
520 throw std::logic_error("Creating a surface requires a display");
522 if (m_eglSurface
!= EGL_NO_SURFACE
)
524 throw std::logic_error("Do not call CreateSurface when surface has already been created");
527 #if defined(EGL_EXT_platform_base)
528 if (IsPlatformSupported())
530 auto createPlatformWindowSurfaceEXT
= CEGLUtils::GetRequiredProcAddress
<PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC
>("eglCreatePlatformWindowSurfaceEXT");
531 m_eglSurface
= createPlatformWindowSurfaceEXT(m_eglDisplay
, m_eglConfig
, nativeWindow
, nullptr);
533 if (m_eglSurface
== EGL_NO_SURFACE
)
535 CEGLUtils::Log(LOGERROR
, "failed to create platform window surface");
541 if (m_eglSurface
== EGL_NO_SURFACE
)
543 return CreateSurface(nativeWindowLegacy
);
551 void CEGLContextUtils::Destroy()
556 if (m_eglDisplay
!= EGL_NO_DISPLAY
)
558 eglTerminate(m_eglDisplay
);
559 m_eglDisplay
= EGL_NO_DISPLAY
;
563 void CEGLContextUtils::DestroyContext()
565 if (m_eglContext
!= EGL_NO_CONTEXT
)
567 eglMakeCurrent(m_eglDisplay
, EGL_NO_SURFACE
, EGL_NO_SURFACE
, EGL_NO_CONTEXT
);
568 eglDestroyContext(m_eglDisplay
, m_eglContext
);
569 m_eglContext
= EGL_NO_CONTEXT
;
572 if (m_eglUploadContext
)
574 eglDestroyContext(m_eglDisplay
, m_eglUploadContext
);
575 m_eglUploadContext
= EGL_NO_CONTEXT
;
579 void CEGLContextUtils::DestroySurface()
581 if (m_eglSurface
!= EGL_NO_SURFACE
)
583 eglMakeCurrent(m_eglDisplay
, EGL_NO_SURFACE
, EGL_NO_SURFACE
, EGL_NO_CONTEXT
);
584 eglDestroySurface(m_eglDisplay
, m_eglSurface
);
585 m_eglSurface
= EGL_NO_SURFACE
;
590 bool CEGLContextUtils::SetVSync(bool enable
)
592 if (m_eglDisplay
== EGL_NO_DISPLAY
)
597 return (eglSwapInterval(m_eglDisplay
, enable
) == EGL_TRUE
);
600 bool CEGLContextUtils::TrySwapBuffers()
602 if (m_eglDisplay
== EGL_NO_DISPLAY
|| m_eglSurface
== EGL_NO_SURFACE
)
607 return (eglSwapBuffers(m_eglDisplay
, m_eglSurface
) == EGL_TRUE
);
610 bool CEGLContextUtils::BindTextureUploadContext()
612 if (m_eglDisplay
== EGL_NO_DISPLAY
|| m_eglUploadContext
== EGL_NO_CONTEXT
)
614 CLog::LogF(LOGERROR
, "No texture upload context found.");
618 m_textureUploadLock
.lock();
620 if (!eglMakeCurrent(m_eglDisplay
, EGL_NO_SURFACE
, EGL_NO_SURFACE
, m_eglUploadContext
))
622 m_textureUploadLock
.unlock();
623 CLog::LogF(LOGERROR
, "Couldn't bind texture upload context.");
630 bool CEGLContextUtils::UnbindTextureUploadContext()
632 if (m_eglDisplay
== EGL_NO_DISPLAY
|| m_eglUploadContext
== EGL_NO_CONTEXT
)
634 CLog::LogF(LOGERROR
, "No texture upload context found.");
635 m_textureUploadLock
.unlock();
639 if (!eglMakeCurrent(m_eglDisplay
, EGL_NO_SURFACE
, EGL_NO_SURFACE
, EGL_NO_CONTEXT
))
641 CLog::LogF(LOGERROR
, "Couldn't release texture upload context");
642 m_textureUploadLock
.unlock();
646 m_textureUploadLock
.unlock();
651 bool CEGLContextUtils::HasContext()
653 return eglGetCurrentContext() != EGL_NO_CONTEXT
;
656 void CEGLContextUtils::SetDamagedRegions(const CDirtyRegionList
& dirtyRegions
)
658 if (!m_partialUpdateSupport
)
661 using Rect
= std::array
<EGLint
, 4>;
662 if (dirtyRegions
.empty())
664 // add a single (empty) entry, otherwise the whole frame gets rendered
665 static Rect zeroRect
{};
666 m_eglSetDamageRegionKHR(m_eglDisplay
, m_eglSurface
, zeroRect
.data(), 1);
670 EGLint height
= eglQuerySurface(m_eglDisplay
, m_eglSurface
, EGL_HEIGHT
, &height
);
671 std::vector
<Rect
> rects
;
672 rects
.reserve(dirtyRegions
.size());
673 for (const auto& region
: dirtyRegions
)
675 rects
.push_back({static_cast<EGLint
>(region
.x1
), static_cast<EGLint
>(height
- region
.y2
),
676 static_cast<EGLint
>(region
.Width()), static_cast<EGLint
>(region
.Height())});
678 m_eglSetDamageRegionKHR(m_eglDisplay
, m_eglSurface
, reinterpret_cast<EGLint
*>(rects
.data()),
683 int CEGLContextUtils::GetBufferAge()
685 #ifdef EGL_BUFFER_AGE_KHR
686 if (m_partialUpdateSupport
)
689 eglQuerySurface(m_eglDisplay
, m_eglSurface
, EGL_BUFFER_AGE_KHR
, &age
);
690 return static_cast<int>(age
);
693 #ifdef EGL_BUFFER_AGE_EXT
694 if (m_bufferAgeSupport
)
697 eglQuerySurface(m_eglDisplay
, m_eglSurface
, EGL_BUFFER_AGE_EXT
, &age
);
698 return static_cast<int>(age
);