diff options
author | peak3d <pfau@peak3d.de> | 2019-09-05 10:18:56 +0200 |
---|---|---|
committer | peak3d <pfau@peak3d.de> | 2019-09-05 10:18:56 +0200 |
commit | c40d3ce0304e08c13420dfdfca61bd98fc0866fe (patch) | |
tree | bb86e0689968ae46b9dd8ae283731d1f19b28273 | |
parent | cdcabff354da8fdb95238361ddf5cf8c6dd174b7 (diff) |
[Android] Implement HDR display enable for ffmpeg decoded streams
23 files changed, 302 insertions, 67 deletions
diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 3835cef8d1..77beba8a53 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -7075,7 +7075,10 @@ msgctxt "#13435" msgid "Enable HQ scalers for scaling above" msgstr "" -#empty string with id 13436 +#: system/settings/settings.xml +msgctxt "#13436" +msgid "Use display HDR capabilities" +msgstr "" #: system/settings/settings.xml msgctxt "#13437" @@ -19055,7 +19058,13 @@ msgctxt "#36298" msgid "If enabled, a recording for the programme to remind will be scheduled when auto-closing the reminder popup, if supported by the PVR add-on and backend." msgstr "" -#empty strings from id 36299 to 36301 +#. Description of setting with label #13436 "Use display HDR capabilities" +#: system/settings/settings.xml +msgctxt "#36299" +msgid "Switch display into HDR mode if media with HDR information is played.\nIf disabled, HDR information are applied using kodi's internal HDR path" +msgstr "" + +#empty strings from id 36300 to 36301 #: system/settings/settings.xml msgctxt "#36302" diff --git a/system/settings/settings.xml b/system/settings/settings.xml index 5f858f1a27..27f896d889 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -143,6 +143,16 @@ <default>true</default> <control type="toggle" /> </setting> + <setting id="winsystem.ishdrdisplay" type="boolean" label="13436" help="36299"> + <dependencies> + <dependency type="visible"> + <condition on="property" name="ishdrdisplay" /> + </dependency> + </dependencies> + <level>2</level> + <default>true</default> + <control type="toggle" /> + </setting> </group> <group id="4" label="14232"> <setting id="videoplayer.stereoscopicplaybackmode" type="integer" label="36520" help="36537"> diff --git a/tools/depends/target/libandroidjni/Makefile b/tools/depends/target/libandroidjni/Makefile index 3147829540..efec568a3c 100644 --- a/tools/depends/target/libandroidjni/Makefile +++ b/tools/depends/target/libandroidjni/Makefile @@ -3,7 +3,7 @@ DEPS= ../../Makefile.include Makefile # lib name, version LIBNAME=libandroidjni -VERSION=0c172c8869170d397dea3e3475a4bc1b5fe96345 +VERSION=62dabc1041fcf47aed483f8e2dea15c3cf0122db SOURCE=archive ARCHIVE=$(VERSION).tar.gz GIT_BASE_URL=https://github.com/xbmc diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.cpp index c73079d536..3b5d1756d7 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecFFmpeg.cpp @@ -965,10 +965,11 @@ bool CDVDVideoCodecFFmpeg::GetPictureCommon(VideoPicture* pVideoPicture) } pVideoPicture->chroma_position = m_pCodecContext->chroma_sample_location; - pVideoPicture->color_primaries = m_pCodecContext->color_primaries; - pVideoPicture->color_transfer = m_pCodecContext->color_trc; - pVideoPicture->color_space = m_pCodecContext->colorspace; + pVideoPicture->color_primaries = m_pCodecContext->color_primaries == AVCOL_PRI_UNSPECIFIED ? m_hints.colorPrimaries : m_pCodecContext->color_primaries; + pVideoPicture->color_transfer = m_pCodecContext->color_trc == AVCOL_TRC_UNSPECIFIED ? m_hints.colorTransferCharacteristic : m_pCodecContext->color_trc; + pVideoPicture->color_space = m_pCodecContext->colorspace == AVCOL_SPC_UNSPECIFIED ? m_hints.colorSpace : m_pCodecContext->colorspace; pVideoPicture->colorBits = 8; + // determine how number of bits of encoded video if (m_pCodecContext->pix_fmt == AV_PIX_FMT_YUV420P12) pVideoPicture->colorBits = 12; @@ -986,7 +987,7 @@ bool CDVDVideoCodecFFmpeg::GetPictureCommon(VideoPicture* pVideoPicture) m_pCodecContext->pix_fmt == AV_PIX_FMT_YUVJ420P) pVideoPicture->color_range = 1; else - pVideoPicture->color_range = 0; + pVideoPicture->color_range = m_hints.colorRange == AVCOL_RANGE_JPEG ? 1 : 0; pVideoPicture->qp_table = av_frame_get_qp_table(m_pFrame, &pVideoPicture->qstride, @@ -1002,12 +1003,22 @@ bool CDVDVideoCodecFFmpeg::GetPictureCommon(VideoPicture* pVideoPicture) pVideoPicture->displayMetadata = *(AVMasteringDisplayMetadata *)sd->data; pVideoPicture->hasDisplayMetadata = true; } + else if (m_hints.masteringMetadata) + { + pVideoPicture->displayMetadata = *m_hints.masteringMetadata.get(); + pVideoPicture->hasDisplayMetadata = true; + } sd = av_frame_get_side_data(m_pFrame, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL); if (sd) { pVideoPicture->lightMetadata = *(AVContentLightMetadata *)sd->data; pVideoPicture->hasLightMetadata = true; } + else if (m_hints.contentLightMetadata) + { + pVideoPicture->lightMetadata = *m_hints.contentLightMetadata.get(); + pVideoPicture->hasLightMetadata = true; + } if (pVideoPicture->iRepeatPicture) pVideoPicture->dts = DVD_NOPTS_VALUE; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VaapiEGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VaapiEGL.cpp index 83c6f1fe8d..2fbf50b8d7 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VaapiEGL.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VaapiEGL.cpp @@ -539,7 +539,7 @@ bool CVaapi2Texture::Map(CVaapiRenderPicture* pic) attribs.Get()); if (!texture->eglImage) { - CEGLUtils::LogError("Failed to import VA DRM surface into EGL image"); + CEGLUtils::Log(LOGERROR, "Failed to import VA DRM surface into EGL image"); return false; } diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp index ea36e5a05f..34bb888793 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp @@ -29,6 +29,7 @@ #include "utils/log.h" #include "windowing/WinSystem.h" + using namespace Shaders; CLinuxRendererGLES::CLinuxRendererGLES() @@ -116,6 +117,7 @@ bool CLinuxRendererGLES::ValidateRenderTarget() bool CLinuxRendererGLES::Configure(const VideoPicture &picture, float fps, unsigned int orientation) { + CLog::Log(LOGDEBUG, "LinuxRendererGLES::Configure: fps: %0.3f", fps); m_format = picture.videoBuffer->GetFormat(); m_sourceWidth = picture.iWidth; m_sourceHeight = picture.iHeight; @@ -140,6 +142,12 @@ bool CLinuxRendererGLES::Configure(const VideoPicture &picture, float fps, unsig // setup the background colour m_clearColour = CServiceBroker::GetWinSystem()->UseLimitedColor() ? (16.0f / 0xff) : 0.0f; + if (picture.hasDisplayMetadata && picture.hasLightMetadata) + { + m_passthroughHDR = CServiceBroker::GetWinSystem()->SetHDR(&picture); + CLog::Log(LOGDEBUG, "LinuxRendererGLES::Configure: HDR passthrough: %s", m_passthroughHDR ? "on" : "off"); + } + return true; } @@ -574,9 +582,9 @@ void CLinuxRendererGLES::LoadShaders(int field) CLog::Log(LOGNOTICE, "GLES: Selecting YUV 2 RGB shader"); EShaderFormat shaderFormat = GetShaderFormat(); - m_pYUVProgShader = new YUV2RGBProgressiveShader(shaderFormat, AVColorPrimaries::AVCOL_PRI_BT709, m_srcPrimaries, m_toneMap); + m_pYUVProgShader = new YUV2RGBProgressiveShader(shaderFormat, m_passthroughHDR ? m_srcPrimaries : AVColorPrimaries::AVCOL_PRI_BT709, m_srcPrimaries, m_toneMap); m_pYUVProgShader->SetConvertFullColorRange(m_fullRange); - m_pYUVBobShader = new YUV2RGBBobShader(shaderFormat, AVColorPrimaries::AVCOL_PRI_BT709, m_srcPrimaries, m_toneMap); + m_pYUVBobShader = new YUV2RGBBobShader(shaderFormat, m_passthroughHDR ? m_srcPrimaries : AVColorPrimaries::AVCOL_PRI_BT709, m_srcPrimaries, m_toneMap); m_pYUVBobShader->SetConvertFullColorRange(m_fullRange); if ((m_pYUVProgShader && m_pYUVProgShader->CompileAndLink()) @@ -638,6 +646,8 @@ void CLinuxRendererGLES::UnInit() m_fbo.fbo.Cleanup(); m_bValidated = false; m_bConfigured = false; + + CServiceBroker::GetWinSystem()->SetHDR(nullptr); } bool CLinuxRendererGLES::CreateTexture(int index) @@ -769,7 +779,7 @@ void CLinuxRendererGLES::RenderSinglePass(int index, int field) bool toneMap = false; - if (m_videoSettings.m_ToneMapMethod != VS_TONEMAPMETHOD_OFF) + if (!m_passthroughHDR && m_videoSettings.m_ToneMapMethod != VS_TONEMAPMETHOD_OFF) { if (buf.hasLightMetadata || (buf.hasDisplayMetadata && buf.displayMetadata.has_luminance)) { diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h index ecb4a9f8c7..b9150f68af 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h +++ b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h @@ -202,6 +202,7 @@ protected: bool m_fullRange; AVColorPrimaries m_srcPrimaries; bool m_toneMap = false; + bool m_passthroughHDR = false; unsigned char* m_planeBuffer = nullptr; size_t m_planeBufferSize = 0; diff --git a/xbmc/settings/SettingConditions.cpp b/xbmc/settings/SettingConditions.cpp index bd16c49059..46f45bcb5f 100644 --- a/xbmc/settings/SettingConditions.cpp +++ b/xbmc/settings/SettingConditions.cpp @@ -83,6 +83,11 @@ bool IsFullscreen(const std::string &condition, const std::string &value, Settin return CServiceBroker::GetWinSystem()->IsFullScreen(); } +bool IsHDRDisplay(const std::string& condition, const std::string& value, SettingConstPtr setting, void* data) +{ + return CServiceBroker::GetWinSystem()->IsHDRDisplay(); +} + bool IsMasterUser(const std::string &condition, const std::string &value, SettingConstPtr setting, void *data) { return g_passwordManager.bMasterUser; @@ -333,6 +338,7 @@ void CSettingConditions::Initialize() m_complexConditions.insert(std::pair<std::string, SettingConditionCheck>("hasrumblecontroller", HasRumbleController)); m_complexConditions.insert(std::pair<std::string, SettingConditionCheck>("haspowerofffeature", HasPowerOffFeature)); m_complexConditions.insert(std::pair<std::string, SettingConditionCheck>("isfullscreen", IsFullscreen)); + m_complexConditions.insert(std::pair<std::string, SettingConditionCheck>("ishdrdisplay", IsHDRDisplay)); m_complexConditions.insert(std::pair<std::string, SettingConditionCheck>("ismasteruser", IsMasterUser)); m_complexConditions.insert(std::pair<std::string, SettingConditionCheck>("isusingttfsubtitles", IsUsingTTFSubtitles)); m_complexConditions.insert(std::pair<std::string, SettingConditionCheck>("profilecanwritedatabase", ProfileCanWriteDatabase)); diff --git a/xbmc/utils/EGLFence.cpp b/xbmc/utils/EGLFence.cpp index bc9f35e173..369c40a5f1 100644 --- a/xbmc/utils/EGLFence.cpp +++ b/xbmc/utils/EGLFence.cpp @@ -9,6 +9,7 @@ #include "EGLFence.h" #include "EGLUtils.h" +#include "utils/log.h" using namespace KODI::UTILS::EGL; @@ -25,7 +26,7 @@ void CEGLFence::CreateFence() m_fence = m_eglCreateSyncKHR(m_display, EGL_SYNC_FENCE_KHR, nullptr); if (m_fence == EGL_NO_SYNC_KHR) { - CEGLUtils::LogError("failed to create egl sync fence"); + CEGLUtils::Log(LOGERROR, "failed to create egl sync fence"); throw std::runtime_error("failed to create egl sync fence"); } } @@ -39,7 +40,7 @@ void CEGLFence::DestroyFence() if (m_eglDestroySyncKHR(m_display, m_fence) != EGL_TRUE) { - CEGLUtils::LogError("failed to destroy egl sync fence"); + CEGLUtils::Log(LOGERROR, "failed to destroy egl sync fence"); } m_fence = EGL_NO_SYNC_KHR; @@ -56,7 +57,7 @@ bool CEGLFence::IsSignaled() EGLint status = EGL_UNSIGNALED_KHR; if (m_eglGetSyncAttribKHR(m_display, m_fence, EGL_SYNC_STATUS_KHR, &status) != EGL_TRUE) { - CEGLUtils::LogError("failed to query egl sync fence"); + CEGLUtils::Log(LOGERROR, "failed to query egl sync fence"); return false; } diff --git a/xbmc/utils/EGLUtils.cpp b/xbmc/utils/EGLUtils.cpp index 9dfb082774..1fdc9dedaf 100644 --- a/xbmc/utils/EGLUtils.cpp +++ b/xbmc/utils/EGLUtils.cpp @@ -111,7 +111,7 @@ std::map<EGLint, const char*> eglErrorType = //! @todo remove when Raspberry Pi updates their EGL headers #if !defined(TARGET_RASPBERRY_PI) -void EglErrorCallback(EGLenum error, const char *command, EGLint messageType, EGLLabelKHR threadLabel, EGLLabelKHR objectLabel, const char* message) +void EglErrorCallback(EGLenum error, const char* command, EGLint messageType, EGLLabelKHR threadLabel, EGLLabelKHR objectLabel, const char* message) { std::string errorStr; std::string typeStr; @@ -168,7 +168,7 @@ bool CEGLUtils::HasClientExtension(const std::string& name) return (exts.find(name) != exts.end()); } -void CEGLUtils::LogError(const std::string& what) +void CEGLUtils::Log(int logLevel, const std::string& what) { EGLenum error = eglGetError(); std::string errorStr = StringUtils::Format("0x%04X", error); @@ -179,7 +179,7 @@ void CEGLUtils::LogError(const std::string& what) errorStr = eglError->second; } - CLog::Log(LOGERROR, "{} ({})", what.c_str(), errorStr); + CLog::Log(logLevel, "{} ({})", what.c_str(), errorStr); } CEGLContextUtils::CEGLContextUtils() @@ -228,7 +228,7 @@ bool CEGLContextUtils::CreateDisplay(EGLNativeDisplayType nativeDisplay) m_eglDisplay = eglGetDisplay(nativeDisplay); if (m_eglDisplay == EGL_NO_DISPLAY) { - CEGLUtils::LogError("failed to get EGL display"); + CEGLUtils::Log(LOGERROR, "failed to get EGL display"); return false; } @@ -255,7 +255,7 @@ bool CEGLContextUtils::CreatePlatformDisplay(void* nativeDisplay, EGLNativeDispl if (m_eglDisplay == EGL_NO_DISPLAY) { - CEGLUtils::LogError("failed to get platform display"); + CEGLUtils::Log(LOGERROR, "failed to get platform display"); return false; } } @@ -273,12 +273,12 @@ bool CEGLContextUtils::InitializeDisplay(EGLint renderingApi) { if (!eglInitialize(m_eglDisplay, nullptr, nullptr)) { - CEGLUtils::LogError("failed to initialize EGL display"); + CEGLUtils::Log(LOGERROR, "failed to initialize EGL display"); Destroy(); return false; } - const char *value; + const char* value; value = eglQueryString(m_eglDisplay, EGL_VERSION); CLog::Log(LOGNOTICE, "EGL_VERSION = %s", value ? value : "NULL"); @@ -293,7 +293,7 @@ bool CEGLContextUtils::InitializeDisplay(EGLint renderingApi) if (eglBindAPI(renderingApi) != EGL_TRUE) { - CEGLUtils::LogError("failed to bind EGL API"); + CEGLUtils::Log(LOGERROR, "failed to bind EGL API"); Destroy(); return false; } @@ -301,7 +301,7 @@ bool CEGLContextUtils::InitializeDisplay(EGLint renderingApi) return true; } -bool CEGLContextUtils::ChooseConfig(EGLint renderableType, EGLint visualId) +bool CEGLContextUtils::ChooseConfig(EGLint renderableType, EGLint visualId, bool hdr) { EGLint numMatched{0}; @@ -310,18 +310,18 @@ bool CEGLContextUtils::ChooseConfig(EGLint renderableType, EGLint visualId) throw std::logic_error("Choosing an EGLConfig requires an EGL display"); } - EGLint surfaceType = EGL_WINDOW_BIT; + EGLint surfaceType = EGL_WINDOW_BIT | EGL_PBUFFER_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; - CEGLAttributes<10> attribs; + CEGLAttributesVec attribs; attribs.Add({{EGL_RED_SIZE, 8}, {EGL_GREEN_SIZE, 8}, {EGL_BLUE_SIZE, 8}, - {EGL_ALPHA_SIZE, 2}, + {EGL_ALPHA_SIZE, 8}, {EGL_DEPTH_SIZE, 16}, {EGL_STENCIL_SIZE, 0}, {EGL_SAMPLE_BUFFERS, 0}, @@ -329,32 +329,46 @@ bool CEGLContextUtils::ChooseConfig(EGLint renderableType, EGLint visualId) {EGL_SURFACE_TYPE, surfaceType}, {EGL_RENDERABLE_TYPE, renderableType}}); - if (eglChooseConfig(m_eglDisplay, attribs.Get(), nullptr, 0, &numMatched) != EGL_TRUE) - { - CEGLUtils::LogError("failed to query number of EGL configs"); - Destroy(); + 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 - std::vector<EGLConfig> eglConfigs(numMatched); + 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) { - CEGLUtils::LogError("failed to find EGL configs with appropriate attributes"); - Destroy(); + if (!hdr) + { + CEGLUtils::Log(LOGERROR, errorMsg); + Destroy(); + } + else + CEGLUtils::Log(LOGINFO, errorMsg); return false; } EGLint id{0}; for (const auto &eglConfig: eglConfigs) { - m_eglConfig = eglConfig; + *currentConfig = eglConfig; if (visualId == 0) break; - if (eglGetConfigAttrib(m_eglDisplay, m_eglConfig, EGL_NATIVE_VISUAL_ID, &id) != EGL_TRUE) - CEGLUtils::LogError("failed to query EGL attibute EGL_NATIVE_VISUAL_ID"); + if (eglGetConfigAttrib(m_eglDisplay, *currentConfig, EGL_NATIVE_VISUAL_ID, &id) != EGL_TRUE) + CEGLUtils::Log(LOGERROR, "failed to query EGL attibute EGL_NATIVE_VISUAL_ID"); if (visualId == id) break; @@ -366,13 +380,13 @@ bool CEGLContextUtils::ChooseConfig(EGLint renderableType, EGLint visualId) return false; } - CLog::Log(LOGDEBUG, "EGL Config Attributes:"); + CLog::Log(LOGDEBUG, "EGL %sConfig Attributes:", hdr ? "HDR " : ""); for (const auto &eglAttribute : eglAttributes) { EGLint value{0}; - if (eglGetConfigAttrib(m_eglDisplay, m_eglConfig, eglAttribute.first, &value) != EGL_TRUE) - CEGLUtils::LogError(StringUtils::Format("failed to query EGL attibute %s", eglAttribute.second)); + if (eglGetConfigAttrib(m_eglDisplay, *currentConfig, eglAttribute.first, &value) != EGL_TRUE) + CEGLUtils::Log(LOGERROR, StringUtils::Format("failed to query EGL attibute %s", eglAttribute.second)); // we only need to print the hex value if it's an actual EGL define CLog::Log(LOGDEBUG, " %s: %s", eglAttribute.second, (value >= 0x3000 && value <= 0x3200) ? StringUtils::Format("0x%04x", value) : StringUtils::Format("%d", value)); @@ -385,7 +399,7 @@ EGLint CEGLContextUtils::GetConfigAttrib(EGLint attribute) const { EGLint value{0}; if (eglGetConfigAttrib(m_eglDisplay, m_eglConfig, attribute, &value) != EGL_TRUE) - CEGLUtils::LogError("failed to query EGL attibute"); + CEGLUtils::Log(LOGERROR, "failed to query EGL attibute"); return value; } @@ -421,7 +435,7 @@ bool CEGLContextUtils::CreateContext(CEGLAttributesVec contextAttribs) EGLint value{EGL_CONTEXT_PRIORITY_MEDIUM_IMG}; if (eglQueryContext(m_eglDisplay, m_eglContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value) != EGL_TRUE) - CEGLUtils::LogError("failed to query EGL context attribute EGL_CONTEXT_PRIORITY_LEVEL_IMG"); + 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"); @@ -468,12 +482,20 @@ void CEGLContextUtils::SurfaceAttrib() { if (eglSurfaceAttrib(m_eglDisplay, m_eglSurface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED) != EGL_TRUE) { - CEGLUtils::LogError("failed to set EGL_BUFFER_PRESERVED swap behavior"); + CEGLUtils::Log(LOGERROR, "failed to set EGL_BUFFER_PRESERVED swap behavior"); } } } -bool CEGLContextUtils::CreateSurface(EGLNativeWindowType nativeWindow) +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) { @@ -484,11 +506,22 @@ bool CEGLContextUtils::CreateSurface(EGLNativeWindowType nativeWindow) throw std::logic_error("Do not call CreateSurface when surface has already been created"); } - m_eglSurface = eglCreateWindowSurface(m_eglDisplay, m_eglConfig, nativeWindow, nullptr); + 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::LogError("failed to create window surface"); + CEGLUtils::Log(LOGERROR, "failed to create window surface"); return false; } @@ -516,7 +549,7 @@ bool CEGLContextUtils::CreatePlatformSurface(void* nativeWindow, EGLNativeWindow if (m_eglSurface == EGL_NO_SURFACE) { - CEGLUtils::LogError("failed to create platform window surface"); + CEGLUtils::Log(LOGERROR, "failed to create platform window surface"); return false; } } diff --git a/xbmc/utils/EGLUtils.h b/xbmc/utils/EGLUtils.h index deff00d238..1de3c72ef1 100644 --- a/xbmc/utils/EGLUtils.h +++ b/xbmc/utils/EGLUtils.h @@ -22,8 +22,8 @@ public: static std::set<std::string> GetClientExtensions(); static std::set<std::string> GetExtensions(EGLDisplay eglDisplay); static bool HasExtension(EGLDisplay eglDisplay, std::string const & name); - static bool HasClientExtension(std::string const & name); - static void LogError(std::string const & what); + static bool HasClientExtension(std::string const& name); + static void Log(int logLevel, std::string const& what); template<typename T> static T GetRequiredProcAddress(const char * procname) { @@ -187,10 +187,11 @@ public: */ bool CreatePlatformDisplay(void* nativeDisplay, EGLNativeDisplayType nativeDisplayLegacy); - bool CreateSurface(EGLNativeWindowType nativeWindow); + void SurfaceAttrib(EGLint attribute, EGLint value); + bool CreateSurface(EGLNativeWindowType nativeWindow, EGLint HDRcolorSpace = EGL_NONE); bool CreatePlatformSurface(void* nativeWindow, EGLNativeWindowType nativeWindowLegacy); bool InitializeDisplay(EGLint renderingApi); - bool ChooseConfig(EGLint renderableType, EGLint visualId = 0); + bool ChooseConfig(EGLint renderableType, EGLint visualId = 0, bool hdr = false); bool CreateContext(CEGLAttributesVec contextAttribs); bool BindContext(); void Destroy(); @@ -227,5 +228,5 @@ private: EGLDisplay m_eglDisplay{EGL_NO_DISPLAY}; EGLSurface m_eglSurface{EGL_NO_SURFACE}; EGLContext m_eglContext{EGL_NO_CONTEXT}; - EGLConfig m_eglConfig{}; + EGLConfig m_eglConfig{}, m_eglHDRConfig{}; }; diff --git a/xbmc/windowing/WinSystem.cpp b/xbmc/windowing/WinSystem.cpp index 3f42262ac6..a75faddb43 100644 --- a/xbmc/windowing/WinSystem.cpp +++ b/xbmc/windowing/WinSystem.cpp @@ -20,6 +20,8 @@ #include "guilib/GUIFontTTFGL.h" #endif +const char* CWinSystemBase::SETTING_WINSYSTEM_IS_HDR_DISPLAY = "winsystem.ishdrdisplay"; + CWinSystemBase::CWinSystemBase() { m_gfxContext.reset(new CGraphicContext()); diff --git a/xbmc/windowing/WinSystem.h b/xbmc/windowing/WinSystem.h index e1907f2795..bc7cb07cb7 100644 --- a/xbmc/windowing/WinSystem.h +++ b/xbmc/windowing/WinSystem.h @@ -36,6 +36,8 @@ class CGraphicContext; class CRenderSystemBase; class IRenderLoop; +struct VideoPicture; + class CWinSystemBase { public: @@ -153,6 +155,10 @@ public: virtual void* GetHWContext() { return nullptr; } std::shared_ptr<CDPMSSupport> GetDPMSManager(); + virtual bool SetHDR(const VideoPicture* videoPicture) { return false; }; + virtual bool IsHDRDisplay() { return false; }; + + static const char* SETTING_WINSYSTEM_IS_HDR_DISPLAY; protected: void UpdateDesktopResolution(RESOLUTION_INFO& newRes, const std::string &output, int width, int height, float refreshRate, uint32_t dwFlags); diff --git a/xbmc/windowing/android/AndroidUtils.cpp b/xbmc/windowing/android/AndroidUtils.cpp index e5d84f8ad0..f25791e8d5 100644 --- a/xbmc/windowing/android/AndroidUtils.cpp +++ b/xbmc/windowing/android/AndroidUtils.cpp @@ -194,7 +194,7 @@ CAndroidUtils::~CAndroidUtils() { } -bool CAndroidUtils::GetNativeResolution(RESOLUTION_INFO *res) const +bool CAndroidUtils::GetNativeResolution(RESOLUTION_INFO* res) const { EGLNativeWindowType nativeWindow = (EGLNativeWindowType)CXBMCApp::GetNativeWindow(30000); if (!nativeWindow) @@ -234,7 +234,7 @@ bool CAndroidUtils::GetNativeResolution(RESOLUTION_INFO *res) const return true; } -bool CAndroidUtils::SetNativeResolution(const RESOLUTION_INFO &res) +bool CAndroidUtils::SetNativeResolution(const RESOLUTION_INFO& res) { CLog::Log(LOGNOTICE, "CAndroidUtils: SetNativeResolution: %s: %dx%d %dx%d@%f", res.strId.c_str(), res.iWidth, res.iHeight, res.iScreenWidth, res.iScreenHeight, res.fRefreshRate); @@ -250,7 +250,7 @@ bool CAndroidUtils::SetNativeResolution(const RESOLUTION_INFO &res) return true; } -bool CAndroidUtils::ProbeResolutions(std::vector<RESOLUTION_INFO> &resolutions) +bool CAndroidUtils::ProbeResolutions(std::vector<RESOLUTION_INFO>& resolutions) { RESOLUTION_INFO cur_res; bool ret = GetNativeResolution(&cur_res); @@ -317,6 +317,25 @@ bool CAndroidUtils::UpdateDisplayModes() return true; } +bool CAndroidUtils::IsHDRDisplay() +{ + CJNIWindow window = CXBMCApp::getWindow(); + bool ret = false; + + if (window) + { + CJNIView view = window.getDecorView(); + if (view) + { + CJNIDisplay display = view.getDisplay(); + if (display) + ret = display.isHdr(); + } + } + CLog::Log(LOGDEBUG, "CAndroidUtils: IsHDRDisplay: %s", ret ? "true" : "false"); + return ret; +} + void CAndroidUtils::OnSettingChanged(std::shared_ptr<const CSetting> setting) { const std::string &settingId = setting->GetId(); diff --git a/xbmc/windowing/android/AndroidUtils.h b/xbmc/windowing/android/AndroidUtils.h index 649dcf0274..f34dd72d55 100644 --- a/xbmc/windowing/android/AndroidUtils.h +++ b/xbmc/windowing/android/AndroidUtils.h @@ -21,10 +21,11 @@ class CAndroidUtils : public ISettingCallback public: CAndroidUtils(); virtual ~CAndroidUtils(); - virtual bool GetNativeResolution(RESOLUTION_INFO *res) const; - virtual bool SetNativeResolution(const RESOLUTION_INFO &res); - virtual bool ProbeResolutions(std::vector<RESOLUTION_INFO> &resolutions); - virtual bool UpdateDisplayModes(); + bool GetNativeResolution(RESOLUTION_INFO* res) const; + bool SetNativeResolution(const RESOLUTION_INFO& res); + bool ProbeResolutions(std::vector<RESOLUTION_INFO>& resolutions); + bool UpdateDisplayModes(); + bool IsHDRDisplay(); // Implementation of ISettingCallback static const std::string SETTING_LIMITGUI; diff --git a/xbmc/windowing/android/WinSystemAndroid.cpp b/xbmc/windowing/android/WinSystemAndroid.cpp index d86fe5b5c8..6a1ffdbea5 100644 --- a/xbmc/windowing/android/WinSystemAndroid.cpp +++ b/xbmc/windowing/android/WinSystemAndroid.cpp @@ -306,6 +306,11 @@ bool CWinSystemAndroid::MessagePump() return m_winEvents->MessagePump(); } +bool CWinSystemAndroid::IsHDRDisplay() +{ + return m_android->IsHDRDisplay(); +} + std::unique_ptr<WINDOWING::IOSScreenSaver> CWinSystemAndroid::GetOSScreenSaverImpl() { std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> ret(new COSScreenSaverAndroid()); diff --git a/xbmc/windowing/android/WinSystemAndroid.h b/xbmc/windowing/android/WinSystemAndroid.h index 23eac63de1..0024188ae0 100644 --- a/xbmc/windowing/android/WinSystemAndroid.h +++ b/xbmc/windowing/android/WinSystemAndroid.h @@ -50,6 +50,7 @@ public: // winevents override bool MessagePump() override; + bool IsHDRDisplay() override; protected: std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> GetOSScreenSaverImpl() override; diff --git a/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp b/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp index 29590b9482..6b97e054aa 100644 --- a/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp +++ b/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp @@ -8,12 +8,19 @@ #include "WinSystemAndroidGLESContext.h" +#include "ServiceBroker.h" #include "VideoSyncAndroid.h" +#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" #include "threads/SingleLock.h" #include "utils/log.h" #include "platform/android/activity/XBMCApp.h" +#include <EGL/eglext.h> + + std::unique_ptr<CWinSystemBase> CWinSystemBase::CreateWinSystem() { std::unique_ptr<CWinSystemBase> winSystem(new CWinSystemAndroidGLESContext()); @@ -42,6 +49,14 @@ bool CWinSystemAndroidGLESContext::InitWindowSystem() return false; } + m_hasHDRConfig = m_pGLContext.ChooseConfig(EGL_OPENGL_ES2_BIT, 0, true); + + m_hasEGLHDRExtensions = CEGLUtils::HasExtension(m_pGLContext.GetEGLDisplay(), "EGL_EXT_gl_colorspace_bt2020_pq") + && CEGLUtils::HasExtension(m_pGLContext.GetEGLDisplay(), "EGL_EXT_surface_SMPTE2086_metadata"); + + CLog::Log(LOGDEBUG, "CWinSystemAndroidGLESContext::InitWindowSystem: HDRConfig: %d, HDRExtensions: %d", + static_cast<int>(m_hasHDRConfig), static_cast<int>(m_hasEGLHDRExtensions)); + CEGLAttributesVec contextAttribs; contextAttribs.Add({{EGL_CONTEXT_CLIENT_VERSION, 2}}); @@ -64,7 +79,7 @@ bool CWinSystemAndroidGLESContext::CreateNewWindow(const std::string& name, return false; } - if (!m_pGLContext.CreateSurface(m_nativeWindow)) + if (!CreateSurface()) { return false; } @@ -107,7 +122,7 @@ void CWinSystemAndroidGLESContext::PresentRenderImpl(bool rendered) // Ignore EGL_BAD_SURFACE: It seems to happen during/after mode changes, but // we can't actually do anything about it if (rendered && !m_pGLContext.TrySwapBuffers()) - CEGLUtils::LogError("eglSwapBuffers failed"); + CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed"); CXBMCApp::get()->WaitVSync(1000); } @@ -142,3 +157,95 @@ std::unique_ptr<CVideoSync> CWinSystemAndroidGLESContext::GetVideoSync(void *clo return pVSync; } +bool CWinSystemAndroidGLESContext::CreateSurface() +{ + if (!m_pGLContext.CreateSurface(m_nativeWindow, m_HDRColorSpace)) + { + if (m_HDRColorSpace != EGL_NONE) + { + m_HDRColorSpace = EGL_NONE; + m_displayMetadata = nullptr; + m_lightMetadata = nullptr; + if (!m_pGLContext.CreateSurface(m_nativeWindow)) + return false; + } + else + return false; + } + +#if EGL_EXT_surface_SMPTE2086_metadata + if (m_displayMetadata) + { + m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_DISPLAY_PRIMARY_RX_EXT, static_cast<int>(av_q2d(m_displayMetadata->display_primaries[0][0]) * EGL_METADATA_SCALING_EXT + 0.5)); + m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_DISPLAY_PRIMARY_RY_EXT, static_cast<int>(av_q2d(m_displayMetadata->display_primaries[0][1]) * EGL_METADATA_SCALING_EXT + 0.5)); + m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_DISPLAY_PRIMARY_GX_EXT, static_cast<int>(av_q2d(m_displayMetadata->display_primaries[1][0]) * EGL_METADATA_SCALING_EXT + 0.5)); + m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_DISPLAY_PRIMARY_GY_EXT, static_cast<int>(av_q2d(m_displayMetadata->display_primaries[1][1]) * EGL_METADATA_SCALING_EXT + 0.5)); + m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_DISPLAY_PRIMARY_BX_EXT, static_cast<int>(av_q2d(m_displayMetadata->display_primaries[2][0]) * EGL_METADATA_SCALING_EXT + 0.5)); + m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_DISPLAY_PRIMARY_BY_EXT, static_cast<int>(av_q2d(m_displayMetadata->display_primaries[2][1]) * EGL_METADATA_SCALING_EXT + 0.5)); + m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_WHITE_POINT_X_EXT, static_cast<int>(av_q2d(m_displayMetadata->white_point[0]) * EGL_METADATA_SCALING_EXT + 0.5)); + m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_WHITE_POINT_Y_EXT, static_cast<int>(av_q2d(m_displayMetadata->white_point[1]) * EGL_METADATA_SCALING_EXT + 0.5)); + m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_MAX_LUMINANCE_EXT, static_cast<int>(av_q2d(m_displayMetadata->max_luminance) * EGL_METADATA_SCALING_EXT + 0.5)); + m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_MIN_LUMINANCE_EXT, static_cast<int>(av_q2d(m_displayMetadata->min_luminance) * EGL_METADATA_SCALING_EXT + 0.5)); + } + if (m_lightMetadata) + { + m_pGLContext.SurfaceAttrib(EGL_CTA861_3_MAX_CONTENT_LIGHT_LEVEL_EXT, static_cast<int>(m_lightMetadata->MaxCLL * EGL_METADATA_SCALING_EXT)); + m_pGLContext.SurfaceAttrib(EGL_CTA861_3_MAX_FRAME_AVERAGE_LEVEL_EXT, static_cast<int>(m_lightMetadata->MaxFALL * EGL_METADATA_SCALING_EXT)); + } +#endif + return true; +} + +bool CWinSystemAndroidGLESContext::IsHDRDisplay() +{ + return m_hasHDRConfig && m_hasEGLHDRExtensions && CWinSystemAndroid::IsHDRDisplay(); +} + +bool CWinSystemAndroidGLESContext::SetHDR(const VideoPicture* videoPicture) +{ + if (!CWinSystemAndroid::IsHDRDisplay() || !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(SETTING_WINSYSTEM_IS_HDR_DISPLAY)) + return false; + + EGLint HDRColorSpace = 0; + +#if EGL_EXT_gl_colorspace_bt2020_linear + if (m_hasHDRConfig && m_hasEGLHDRExtensions) + { + HDRColorSpace = EGL_NONE; + if (videoPicture && videoPicture->hasDisplayMetadata) + { + switch (videoPicture->color_space) + { + case AVCOL_SPC_BT2020_NCL: + case AVCOL_SPC_BT2020_CL: + case AVCOL_SPC_BT709: + HDRColorSpace = EGL_GL_COLORSPACE_BT2020_PQ_EXT; + break; + default: + m_displayMetadata = nullptr; + m_lightMetadata = nullptr; + } + } + else + { + m_displayMetadata = nullptr; + m_lightMetadata = nullptr; + } + + if (HDRColorSpace != m_HDRColorSpace) + { + CLog::Log(LOGDEBUG, "CWinSystemAndroidGLESContext::SetHDR: ColorSpace: %d", HDRColorSpace); + + m_HDRColorSpace = HDRColorSpace; + m_displayMetadata = m_HDRColorSpace == EGL_NONE ? nullptr : std::unique_ptr<AVMasteringDisplayMetadata>(new AVMasteringDisplayMetadata(videoPicture->displayMetadata)); + // TODO: discuss with NVIDIA why this prevent turning HDR display off + //m_lightMetadata = !videoPicture || m_HDRColorSpace == EGL_NONE ? nullptr : std::unique_ptr<AVContentLightMetadata>(new AVContentLightMetadata(videoPicture->lightMetadata)); + m_pGLContext.DestroySurface(); + CreateSurface(); + m_pGLContext.BindContext(); + } + } +#endif + + return m_HDRColorSpace == HDRColorSpace; +} diff --git a/xbmc/windowing/android/WinSystemAndroidGLESContext.h b/xbmc/windowing/android/WinSystemAndroidGLESContext.h index 7194217b1a..de1fe8bf0c 100644 --- a/xbmc/windowing/android/WinSystemAndroidGLESContext.h +++ b/xbmc/windowing/android/WinSystemAndroidGLESContext.h @@ -13,6 +13,9 @@ #include "utils/EGLUtils.h" #include "utils/GlobalsHandling.h" +struct AVMasteringDisplayMetadata; +struct AVContentLightMetadata; + class CWinSystemAndroidGLESContext : public CWinSystemAndroid, public CRenderSystemGLES { public: @@ -32,6 +35,8 @@ public: virtual std::unique_ptr<CVideoSync> GetVideoSync(void *clock) override; float GetFrameLatencyAdjustment() override; + bool IsHDRDisplay() override; + bool SetHDR(const VideoPicture* videoPicture) override; EGLDisplay GetEGLDisplay() const; EGLSurface GetEGLSurface() const; @@ -42,6 +47,13 @@ protected: void PresentRenderImpl(bool rendered) override; private: + bool CreateSurface(); + CEGLContextUtils m_pGLContext; + bool m_hasHDRConfig = false; + std::unique_ptr<AVMasteringDisplayMetadata> m_displayMetadata; + std::unique_ptr<AVContentLightMetadata> m_lightMetadata; + EGLint m_HDRColorSpace = EGL_NONE; + bool m_hasEGLHDRExtensions = false; }; diff --git a/xbmc/windowing/gbm/WinSystemGbmGLContext.cpp b/xbmc/windowing/gbm/WinSystemGbmGLContext.cpp index 76b8eb85bf..5c050f7e85 100644 --- a/xbmc/windowing/gbm/WinSystemGbmGLContext.cpp +++ b/xbmc/windowing/gbm/WinSystemGbmGLContext.cpp @@ -74,7 +74,7 @@ bool CWinSystemGbmGLContext::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res if (!m_eglContext.TrySwapBuffers()) { - CEGLUtils::LogError("eglSwapBuffers failed"); + CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed"); throw std::runtime_error("eglSwapBuffers failed"); } @@ -103,7 +103,7 @@ void CWinSystemGbmGLContext::PresentRender(bool rendered, bool videoLayer) { if (!m_eglContext.TrySwapBuffers()) { - CEGLUtils::LogError("eglSwapBuffers failed"); + CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed"); throw std::runtime_error("eglSwapBuffers failed"); } } diff --git a/xbmc/windowing/gbm/WinSystemGbmGLESContext.cpp b/xbmc/windowing/gbm/WinSystemGbmGLESContext.cpp index 219ef7dcd0..338ea1c304 100644 --- a/xbmc/windowing/gbm/WinSystemGbmGLESContext.cpp +++ b/xbmc/windowing/gbm/WinSystemGbmGLESContext.cpp @@ -85,7 +85,7 @@ bool CWinSystemGbmGLESContext::SetFullScreen(bool fullScreen, RESOLUTION_INFO& r if (!m_eglContext.TrySwapBuffers()) { - CEGLUtils::LogError("eglSwapBuffers failed"); + CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed"); throw std::runtime_error("eglSwapBuffers failed"); } @@ -114,7 +114,7 @@ void CWinSystemGbmGLESContext::PresentRender(bool rendered, bool videoLayer) { if (!m_eglContext.TrySwapBuffers()) { - CEGLUtils::LogError("eglSwapBuffers failed"); + CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed"); throw std::runtime_error("eglSwapBuffers failed"); } } diff --git a/xbmc/windowing/rpi/WinSystemRpiGLESContext.cpp b/xbmc/windowing/rpi/WinSystemRpiGLESContext.cpp index 3ec0da452c..7a3c7ee25a 100644 --- a/xbmc/windowing/rpi/WinSystemRpiGLESContext.cpp +++ b/xbmc/windowing/rpi/WinSystemRpiGLESContext.cpp @@ -156,7 +156,7 @@ void CWinSystemRpiGLESContext::PresentRenderImpl(bool rendered) if (!m_pGLContext.TrySwapBuffers()) { - CEGLUtils::LogError("eglSwapBuffers failed"); + CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed"); throw std::runtime_error("eglSwapBuffers failed"); } } diff --git a/xbmc/windowing/wayland/WinSystemWaylandEGLContext.cpp b/xbmc/windowing/wayland/WinSystemWaylandEGLContext.cpp index 8d48e736de..29984bc8f0 100644 --- a/xbmc/windowing/wayland/WinSystemWaylandEGLContext.cpp +++ b/xbmc/windowing/wayland/WinSystemWaylandEGLContext.cpp @@ -129,7 +129,7 @@ void CWinSystemWaylandEGLContext::PresentFrame(bool rendered) // For now we just hard fail if this fails // Theoretically, EGL_CONTEXT_LOST could be handled, but it needs to be checked // whether egl implementations actually use it (mesa does not) - CEGLUtils::LogError("eglSwapBuffers failed"); + CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed"); throw std::runtime_error("eglSwapBuffers failed"); } // eglSwapBuffers() (hopefully) calls commit on the surface and flushes |