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/windowing/android | |
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/windowing/android')
-rw-r--r-- | xbmc/windowing/android/AndroidUtils.cpp | 440 | ||||
-rw-r--r-- | xbmc/windowing/android/AndroidUtils.h | 56 | ||||
-rw-r--r-- | xbmc/windowing/android/CMakeLists.txt | 18 | ||||
-rw-r--r-- | xbmc/windowing/android/OSScreenSaverAndroid.cpp | 21 | ||||
-rw-r--r-- | xbmc/windowing/android/OSScreenSaverAndroid.h | 19 | ||||
-rw-r--r-- | xbmc/windowing/android/VideoSyncAndroid.cpp | 78 | ||||
-rw-r--r-- | xbmc/windowing/android/VideoSyncAndroid.h | 34 | ||||
-rw-r--r-- | xbmc/windowing/android/WinEventsAndroid.cpp | 191 | ||||
-rw-r--r-- | xbmc/windowing/android/WinEventsAndroid.h | 43 | ||||
-rw-r--r-- | xbmc/windowing/android/WinSystemAndroid.cpp | 329 | ||||
-rw-r--r-- | xbmc/windowing/android/WinSystemAndroid.h | 85 | ||||
-rw-r--r-- | xbmc/windowing/android/WinSystemAndroidGLESContext.cpp | 270 | ||||
-rw-r--r-- | xbmc/windowing/android/WinSystemAndroidGLESContext.h | 63 |
13 files changed, 1647 insertions, 0 deletions
diff --git a/xbmc/windowing/android/AndroidUtils.cpp b/xbmc/windowing/android/AndroidUtils.cpp new file mode 100644 index 0000000..8c61fae --- /dev/null +++ b/xbmc/windowing/android/AndroidUtils.cpp @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2011-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 "AndroidUtils.h" + +#include "ServiceBroker.h" +#include "settings/DisplaySettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/SettingsManager.h" +#include "utils/StringUtils.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +#include "platform/android/activity/XBMCApp.h" + +#include <androidjni/MediaCodecInfo.h> +#include <androidjni/MediaCodecList.h> +#include <androidjni/System.h> +#include <androidjni/SystemProperties.h> +#include <androidjni/View.h> +#include <androidjni/Window.h> +#include <androidjni/WindowManager.h> + +static bool s_hasModeApi = false; +static std::vector<RESOLUTION_INFO> s_res_displayModes; +static RESOLUTION_INFO s_res_cur_displayMode; + +static float currentRefreshRate() +{ + if (s_hasModeApi) + return s_res_cur_displayMode.fRefreshRate; + + CJNIWindow window = CXBMCApp::getWindow(); + if (window) + { + float preferredRate = window.getAttributes().getpreferredRefreshRate(); + if (preferredRate > 20.0f) + { + CLog::Log(LOGINFO, "CAndroidUtils: Preferred refresh rate: {:f}", preferredRate); + return preferredRate; + } + CJNIView view(window.getDecorView()); + if (view) + { + CJNIDisplay display(view.getDisplay()); + if (display) + { + float reportedRate = display.getRefreshRate(); + if (reportedRate > 20.0f) + { + CLog::Log(LOGINFO, "CAndroidUtils: Current display refresh rate: {:f}", reportedRate); + return reportedRate; + } + } + } + } + CLog::Log(LOGDEBUG, "found no refresh rate"); + return 60.0; +} + +static void fetchDisplayModes() +{ + s_hasModeApi = false; + s_res_displayModes.clear(); + + CJNIDisplay display = CXBMCApp::getWindow().getDecorView().getDisplay(); + + if (display) + { + CJNIDisplayMode m = display.getMode(); + if (m) + { + if (m.getPhysicalWidth() > m.getPhysicalHeight()) // Assume unusable if portrait is returned + { + s_hasModeApi = true; + + CLog::Log(LOGDEBUG, "CAndroidUtils: current mode: {}: {}x{}@{:f}", m.getModeId(), + m.getPhysicalWidth(), m.getPhysicalHeight(), m.getRefreshRate()); + s_res_cur_displayMode.strId = std::to_string(m.getModeId()); + s_res_cur_displayMode.iWidth = s_res_cur_displayMode.iScreenWidth = m.getPhysicalWidth(); + s_res_cur_displayMode.iHeight = s_res_cur_displayMode.iScreenHeight = m.getPhysicalHeight(); + s_res_cur_displayMode.fRefreshRate = m.getRefreshRate(); + s_res_cur_displayMode.dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + s_res_cur_displayMode.bFullScreen = true; + s_res_cur_displayMode.iSubtitles = s_res_cur_displayMode.iHeight; + s_res_cur_displayMode.fPixelRatio = 1.0f; + s_res_cur_displayMode.strMode = StringUtils::Format( + "{}x{} @ {:.6f}{} - Full Screen", s_res_cur_displayMode.iScreenWidth, + s_res_cur_displayMode.iScreenHeight, s_res_cur_displayMode.fRefreshRate, + s_res_cur_displayMode.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : ""); + + std::vector<CJNIDisplayMode> modes = display.getSupportedModes(); + for (CJNIDisplayMode& m : modes) + { + CLog::Log(LOGDEBUG, "CAndroidUtils: available mode: {}: {}x{}@{:f}", m.getModeId(), + m.getPhysicalWidth(), m.getPhysicalHeight(), m.getRefreshRate()); + + RESOLUTION_INFO res; + res.strId = std::to_string(m.getModeId()); + res.iWidth = res.iScreenWidth = m.getPhysicalWidth(); + res.iHeight = res.iScreenHeight = m.getPhysicalHeight(); + res.fRefreshRate = m.getRefreshRate(); + res.dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + res.bFullScreen = true; + res.iSubtitles = res.iHeight; + res.fPixelRatio = 1.0f; + res.strMode = StringUtils::Format("{}x{} @ {:.6f}{} - Full Screen", res.iScreenWidth, + res.iScreenHeight, res.fRefreshRate, + res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : ""); + + s_res_displayModes.push_back(res); + } + } + } + } +} + +namespace +{ +const std::map<int, std::string> hdrTypeMap = { + {CAndroidUtils::HDRTypes::HDR10, "HDR10"}, + {CAndroidUtils::HDRTypes::HLG, "HLG"}, + {CAndroidUtils::HDRTypes::HDR10_PLUS, "HDR10+"}, + {CAndroidUtils::HDRTypes::DOLBY_VISION, "Dolby Vision"}}; + +std::string HdrTypeString(int type) +{ + auto hdr = hdrTypeMap.find(type); + if (hdr != hdrTypeMap.end()) + return hdr->second; + + return "Unknown"; +} +} // unnamed namespace + +const std::string CAndroidUtils::SETTING_LIMITGUI = "videoscreen.limitgui"; + +CAndroidUtils::CAndroidUtils() +{ + std::string displaySize; + m_width = m_height = 0; + + if (CJNIBase::GetSDKVersion() >= 23) + { + fetchDisplayModes(); + for (const RESOLUTION_INFO& res : s_res_displayModes) + { + if (res.iWidth > m_width || res.iHeight > m_height) + { + m_width = res.iWidth; + m_height = res.iHeight; + } + } + } + + if (!m_width || !m_height) + { + // Property available on some devices + displaySize = CJNISystemProperties::get("sys.display-size", ""); + if (!displaySize.empty()) + { + std::vector<std::string> aSize = StringUtils::Split(displaySize, "x"); + if (aSize.size() == 2) + { + m_width = StringUtils::IsInteger(aSize[0]) ? atoi(aSize[0].c_str()) : 0; + m_height = StringUtils::IsInteger(aSize[1]) ? atoi(aSize[1].c_str()) : 0; + } + CLog::Log(LOGDEBUG, "CAndroidUtils: display-size: {}({}x{})", displaySize, m_width, m_height); + } + } + + CLog::Log(LOGDEBUG, "CAndroidUtils: maximum/current resolution: {}x{}", m_width, m_height); + int limit = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CAndroidUtils::SETTING_LIMITGUI); + switch (limit) + { + case 0: // auto + m_width = 0; + m_height = 0; + break; + + case 9999: // unlimited + break; + + case 720: + if (m_height > 720) + { + m_width = 1280; + m_height = 720; + } + break; + + case 1080: + if (m_height > 1080) + { + m_width = 1920; + m_height = 1080; + } + break; + } + CLog::Log(LOGDEBUG, "CAndroidUtils: selected resolution: {}x{}", m_width, m_height); + + CServiceBroker::GetSettingsComponent()->GetSettings()->GetSettingsManager()->RegisterCallback( + this, {CAndroidUtils::SETTING_LIMITGUI}); + + LogDisplaySupportedHdrTypes(); +} + +bool CAndroidUtils::GetNativeResolution(RESOLUTION_INFO* res) const +{ + const std::shared_ptr<CNativeWindow> nativeWindow = CXBMCApp::Get().GetNativeWindow(30000); + if (!nativeWindow) + return false; + + if (!m_width || !m_height) + { + m_width = nativeWindow->GetWidth(); + m_height = nativeWindow->GetHeight(); + CLog::Log(LOGINFO, "CAndroidUtils: window resolution: {}x{}", m_width, m_height); + } + + if (s_hasModeApi) + { + *res = s_res_cur_displayMode; + res->iWidth = m_width; + res->iHeight = m_height; + } + else + { + res->strId = "-1"; + res->fRefreshRate = currentRefreshRate(); + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + res->bFullScreen = true; + res->iWidth = m_width; + res->iHeight = m_height; + res->fPixelRatio = 1.0f; + res->iScreenWidth = res->iWidth; + res->iScreenHeight = res->iHeight; + } + res->iSubtitles = res->iHeight; + res->strMode = + StringUtils::Format("{}x{} @ {:.6f}{} - Full Screen", res->iScreenWidth, res->iScreenHeight, + res->fRefreshRate, res->dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : ""); + CLog::Log(LOGINFO, "CAndroidUtils: Current resolution: {}x{} {}", res->iWidth, res->iHeight, + res->strMode); + return true; +} + +bool CAndroidUtils::SetNativeResolution(const RESOLUTION_INFO& res) +{ + CLog::Log(LOGINFO, "CAndroidUtils: SetNativeResolution: {}: {}x{} {}x{}@{:f}", res.strId, + res.iWidth, res.iHeight, res.iScreenWidth, res.iScreenHeight, res.fRefreshRate); + + if (s_hasModeApi) + { + CXBMCApp::Get().SetDisplayMode(std::atoi(res.strId.c_str()), res.fRefreshRate); + s_res_cur_displayMode = res; + } + else + CXBMCApp::Get().SetRefreshRate(res.fRefreshRate); + + CXBMCApp::Get().SetBuffersGeometry(res.iWidth, res.iHeight, 0); + + return true; +} + +bool CAndroidUtils::ProbeResolutions(std::vector<RESOLUTION_INFO>& resolutions) +{ + RESOLUTION_INFO cur_res; + bool ret = GetNativeResolution(&cur_res); + + CLog::Log(LOGDEBUG, "CAndroidUtils: ProbeResolutions: {}x{}", m_width, m_height); + + if (s_hasModeApi) + { + for (RESOLUTION_INFO res : s_res_displayModes) + { + if (m_width && m_height) + { + res.iWidth = std::min(res.iWidth, m_width); + res.iHeight = std::min(res.iHeight, m_height); + res.iSubtitles = res.iHeight; + } + resolutions.push_back(res); + } + return true; + } + + if (ret && cur_res.iWidth > 1 && cur_res.iHeight > 1) + { + std::vector<float> refreshRates; + CJNIWindow window = CXBMCApp::getWindow(); + if (window) + { + CJNIView view = window.getDecorView(); + if (view) + { + CJNIDisplay display = view.getDisplay(); + if (display) + { + refreshRates = display.getSupportedRefreshRates(); + } + } + + if (!refreshRates.empty()) + { + for (unsigned int i = 0; i < refreshRates.size(); i++) + { + if (refreshRates[i] < 20.0f) + continue; + cur_res.fRefreshRate = refreshRates[i]; + cur_res.strMode = StringUtils::Format( + "{}x{} @ {:.6f}{} - Full Screen", cur_res.iScreenWidth, cur_res.iScreenHeight, + cur_res.fRefreshRate, cur_res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : ""); + resolutions.push_back(cur_res); + } + } + } + if (resolutions.empty()) + { + /* No valid refresh rates available, just provide the current one */ + resolutions.push_back(cur_res); + } + return true; + } + return false; +} + +bool CAndroidUtils::UpdateDisplayModes() +{ + if (CJNIBase::GetSDKVersion() >= 23) + fetchDisplayModes(); + 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: {}", ret ? "true" : "false"); + return ret; +} + +void CAndroidUtils::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + const std::string& settingId = setting->GetId(); + /* Calibration (overscan / subtitles) are based on GUI size -> reset required */ + if (settingId == CAndroidUtils::SETTING_LIMITGUI) + CDisplaySettings::GetInstance().ClearCalibrations(); +} + +std::vector<int> CAndroidUtils::GetDisplaySupportedHdrTypes() +{ + CJNIWindow window = CXBMCApp::getWindow(); + + if (window) + { + CJNIView view = window.getDecorView(); + if (view) + { + CJNIDisplay display = view.getDisplay(); + if (display) + { + CJNIDisplayHdrCapabilities caps = display.getHdrCapabilities(); + if (caps) + return caps.getSupportedHdrTypes(); + } + } + } + + return {}; +} + +void CAndroidUtils::LogDisplaySupportedHdrTypes() +{ + const std::vector<int> hdrTypes = GetDisplaySupportedHdrTypes(); + std::string text; + + for (const int& type : hdrTypes) + { + text += " " + HdrTypeString(type); + } + + CLog::Log(LOGDEBUG, "CAndroidUtils: Display supported HDR types:{}", + text.empty() ? " None" : text); +} + +CHDRCapabilities CAndroidUtils::GetDisplayHDRCapabilities() +{ + CHDRCapabilities caps; + const std::vector<int> types = GetDisplaySupportedHdrTypes(); + + if (std::find(types.begin(), types.end(), CAndroidUtils::HDRTypes::HDR10) != types.end()) + caps.SetHDR10(); + + if (std::find(types.begin(), types.end(), CAndroidUtils::HDRTypes::HLG) != types.end()) + caps.SetHLG(); + + if (std::find(types.begin(), types.end(), CAndroidUtils::HDRTypes::HDR10_PLUS) != types.end()) + caps.SetHDR10Plus(); + + if (std::find(types.begin(), types.end(), CAndroidUtils::HDRTypes::DOLBY_VISION) != types.end()) + caps.SetDolbyVision(); + + return caps; +} + +bool CAndroidUtils::SupportsMediaCodecMimeType(const std::string& mimeType) +{ + const std::vector<CJNIMediaCodecInfo> codecInfos = + CJNIMediaCodecList(CJNIMediaCodecList::REGULAR_CODECS).getCodecInfos(); + + for (const CJNIMediaCodecInfo& codec_info : codecInfos) + { + if (codec_info.isEncoder()) + continue; + + std::vector<std::string> types = codec_info.getSupportedTypes(); + if (std::find(types.begin(), types.end(), mimeType) != types.end()) + return true; + } + + return false; +} diff --git a/xbmc/windowing/android/AndroidUtils.h b/xbmc/windowing/android/AndroidUtils.h new file mode 100644 index 0000000..426baee --- /dev/null +++ b/xbmc/windowing/android/AndroidUtils.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011-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. + */ + +#pragma once + +#include "settings/lib/ISettingCallback.h" +#include "utils/HDRCapabilities.h" +#include "windowing/Resolution.h" + +#include <string> +#include <vector> + +#include <androidjni/Display.h> + +class CAndroidUtils : public ISettingCallback +{ +public: + CAndroidUtils(); + ~CAndroidUtils() override = default; + 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; + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + static bool SupportsMediaCodecMimeType(const std::string& mimeType); + + // Android specific HDR type mapping + // https://developer.android.com/reference/android/view/Display.HdrCapabilities#constants_1 + enum HDRTypes + { + DOLBY_VISION = 1, + HDR10 = 2, + HLG = 3, + HDR10_PLUS = 4 + }; + + static std::vector<int> GetDisplaySupportedHdrTypes(); + static CHDRCapabilities GetDisplayHDRCapabilities(); + +protected: + mutable int m_width; + mutable int m_height; + +private: + static void LogDisplaySupportedHdrTypes(); +}; diff --git a/xbmc/windowing/android/CMakeLists.txt b/xbmc/windowing/android/CMakeLists.txt new file mode 100644 index 0000000..fd489d1 --- /dev/null +++ b/xbmc/windowing/android/CMakeLists.txt @@ -0,0 +1,18 @@ +set(SOURCES OSScreenSaverAndroid.cpp + WinEventsAndroid.cpp + WinSystemAndroid.cpp + AndroidUtils.cpp + VideoSyncAndroid.cpp) + +set(HEADERS OSScreenSaverAndroid.h + WinEventsAndroid.h + WinSystemAndroid.h + AndroidUtils.h + VideoSyncAndroid.h) + +if(OPENGLES_FOUND) + list(APPEND SOURCES WinSystemAndroidGLESContext.cpp) + list(APPEND HEADERS WinSystemAndroidGLESContext.h) +endif() + +core_add_library(windowing_android) diff --git a/xbmc/windowing/android/OSScreenSaverAndroid.cpp b/xbmc/windowing/android/OSScreenSaverAndroid.cpp new file mode 100644 index 0000000..aa6dfc5 --- /dev/null +++ b/xbmc/windowing/android/OSScreenSaverAndroid.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2018 Chris Browet + * 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 "OSScreenSaverAndroid.h" + +#include "platform/android/activity/XBMCApp.h" + +void COSScreenSaverAndroid::Inhibit() +{ + CXBMCApp::Get().EnableWakeLock(true); +} + +void COSScreenSaverAndroid::Uninhibit() +{ + CXBMCApp::Get().EnableWakeLock(false); +} diff --git a/xbmc/windowing/android/OSScreenSaverAndroid.h b/xbmc/windowing/android/OSScreenSaverAndroid.h new file mode 100644 index 0000000..1b67d85 --- /dev/null +++ b/xbmc/windowing/android/OSScreenSaverAndroid.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 Chris Browet + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "../OSScreenSaver.h" + +class COSScreenSaverAndroid : public KODI::WINDOWING::IOSScreenSaver +{ +public: + explicit COSScreenSaverAndroid() = default; + void Inhibit() override; + void Uninhibit() override; +}; diff --git a/xbmc/windowing/android/VideoSyncAndroid.cpp b/xbmc/windowing/android/VideoSyncAndroid.cpp new file mode 100644 index 0000000..cee6f3e --- /dev/null +++ b/xbmc/windowing/android/VideoSyncAndroid.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015-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 "VideoSyncAndroid.h" + +#include "ServiceBroker.h" +#include "cores/VideoPlayer/VideoReferenceClock.h" +#include "utils/MathUtils.h" +#include "utils/TimeUtils.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" +#include "windowing/WinSystem.h" + +#include "platform/android/activity/XBMCApp.h" + + +bool CVideoSyncAndroid::Setup(PUPDATECLOCK func) +{ + CLog::Log(LOGDEBUG, "CVideoSyncAndroid::{} setting up", __FUNCTION__); + + //init the vblank timestamp + m_LastVBlankTime = CurrentHostCounter(); + UpdateClock = func; + m_abortEvent.Reset(); + + CXBMCApp::Get().InitFrameCallback(this); + CServiceBroker::GetWinSystem()->Register(this); + + return true; +} + +void CVideoSyncAndroid::Run(CEvent& stopEvent) +{ + XbmcThreads::CEventGroup waitGroup{&stopEvent, &m_abortEvent}; + waitGroup.wait(); +} + +void CVideoSyncAndroid::Cleanup() +{ + CLog::Log(LOGDEBUG, "CVideoSyncAndroid::{} cleaning up", __FUNCTION__); + CXBMCApp::Get().DeinitFrameCallback(); + CServiceBroker::GetWinSystem()->Unregister(this); +} + +float CVideoSyncAndroid::GetFps() +{ + m_fps = CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS(); + CLog::Log(LOGDEBUG, "CVideoSyncAndroid::{} Detected refreshrate: {:f} hertz", __FUNCTION__, + m_fps); + return m_fps; +} + +void CVideoSyncAndroid::OnResetDisplay() +{ + m_abortEvent.Set(); +} + +void CVideoSyncAndroid::FrameCallback(int64_t frameTimeNanos) +{ + int NrVBlanks; + double VBlankTime; + + //calculate how many vblanks happened + VBlankTime = (double)(frameTimeNanos - m_LastVBlankTime) / (double)CurrentHostFrequency(); + NrVBlanks = MathUtils::round_int(VBlankTime * static_cast<double>(m_fps)); + + //save the timestamp of this vblank so we can calculate how many happened next time + m_LastVBlankTime = frameTimeNanos; + + //update the vblank timestamp, update the clock and send a signal that we got a vblank + UpdateClock(NrVBlanks, frameTimeNanos, m_refClock); +} diff --git a/xbmc/windowing/android/VideoSyncAndroid.h b/xbmc/windowing/android/VideoSyncAndroid.h new file mode 100644 index 0000000..6bf76c0 --- /dev/null +++ b/xbmc/windowing/android/VideoSyncAndroid.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015-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. + */ + +#pragma once + +#include "guilib/DispResource.h" +#include "windowing/VideoSync.h" + +class CVideoSyncAndroid : public CVideoSync, IDispResource +{ +public: + CVideoSyncAndroid(void *clock) : CVideoSync(clock), m_LastVBlankTime(0) {} + + // CVideoSync interface + bool Setup(PUPDATECLOCK func) override; + void Run(CEvent& stop) override; + void Cleanup() override; + float GetFps() override; + + // IDispResource interface + void OnResetDisplay() override; + + // Choreographer callback + void FrameCallback(int64_t frameTimeNanos); + +private: + int64_t m_LastVBlankTime; //timestamp of the last vblank, used for calculating how many vblanks happened + CEvent m_abortEvent; +}; diff --git a/xbmc/windowing/android/WinEventsAndroid.cpp b/xbmc/windowing/android/WinEventsAndroid.cpp new file mode 100644 index 0000000..a384902 --- /dev/null +++ b/xbmc/windowing/android/WinEventsAndroid.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2010-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 "WinEventsAndroid.h" + +#include "ServiceBroker.h" +#include "application/AppInboundProtocol.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/InputManager.h" +#include "input/XBMC_vkeys.h" +#include "utils/log.h" + +#include <mutex> + +#define ALMOST_ZERO 0.125f +enum { + EVENT_STATE_TEST, + EVENT_STATE_HOLD, + EVENT_STATE_REPEAT +}; + +/************************************************************************/ +/************************************************************************/ +static bool different_event(XBMC_Event &curEvent, XBMC_Event &newEvent) +{ + // different type + if (curEvent.type != newEvent.type) + return true; + + return false; +} + +/************************************************************************/ +/************************************************************************/ +CWinEventsAndroid::CWinEventsAndroid() +: CThread("CWinEventsAndroid") +{ + CLog::Log(LOGDEBUG, "CWinEventsAndroid::CWinEventsAndroid"); + Create(); +} + +CWinEventsAndroid::~CWinEventsAndroid() +{ + m_bStop = true; + StopThread(true); +} + +void CWinEventsAndroid::MessagePush(XBMC_Event *newEvent) +{ + std::unique_lock<CCriticalSection> lock(m_eventsCond); + + m_events.push_back(*newEvent); +} + +void CWinEventsAndroid::MessagePushRepeat(XBMC_Event *repeatEvent) +{ + std::unique_lock<CCriticalSection> lock(m_eventsCond); + + std::list<XBMC_Event>::iterator itt; + for (itt = m_events.begin(); itt != m_events.end(); ++itt) + { + // we have events pending, if we we just + // repush, we might push the repeat event + // in back of a canceling non-active event. + // do not repush if pending are different event. + if (different_event(*itt, *repeatEvent)) + return; + } + + // is a repeat, push it + m_events.push_back(*repeatEvent); +} + +bool CWinEventsAndroid::MessagePump() +{ + bool ret = false; + + // Do not always loop, only pump the initial queued count events. else if ui keep pushing + // events the loop won't finish then it will block xbmc main message loop. + for (size_t pumpEventCount = GetQueueSize(); pumpEventCount > 0; --pumpEventCount) + { + // Pop up only one event per time since in App::OnEvent it may init modal dialog which init + // deeper message loop and call the deeper MessagePump from there. + XBMC_Event pumpEvent; + { + std::unique_lock<CCriticalSection> lock(m_eventsCond); + if (m_events.empty()) + return ret; + pumpEvent = m_events.front(); + m_events.pop_front(); + } + + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + ret |= appPort->OnEvent(pumpEvent); + + if (pumpEvent.type == XBMC_MOUSEBUTTONUP) + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_UNFOCUS_ALL, 0, 0, 0, 0); + } + + return ret; +} + +size_t CWinEventsAndroid::GetQueueSize() +{ + std::unique_lock<CCriticalSection> lock(m_eventsCond); + return m_events.size(); +} + +void CWinEventsAndroid::Process() +{ + uint32_t timeout = 10; + uint32_t holdTimeout = 500; + uint32_t repeatTimeout = 100; + uint32_t repeatDuration = 0; + + XBMC_Event cur_event; + int state = EVENT_STATE_TEST; + while (!m_bStop) + { + // run a 10ms (timeout) wait cycle + CThread::Sleep(std::chrono::milliseconds(timeout)); + + std::unique_lock<CCriticalSection> lock(m_lasteventCond); + + switch(state) + { + default: + case EVENT_STATE_TEST: + // non-active event, eat it + if (!m_lastevent.empty()) + m_lastevent.pop(); + break; + + case EVENT_STATE_HOLD: + repeatDuration += timeout; + if (!m_lastevent.empty()) + { + if (different_event(cur_event, m_lastevent.front())) + { + // different event, cycle back to test + state = EVENT_STATE_TEST; + break; + } + + // same event, eat it + m_lastevent.pop(); + } + + if (repeatDuration >= holdTimeout) + { + CLog::Log(LOGDEBUG, "hold ->repeat, size({}), repeatDuration({})", m_lastevent.size(), + repeatDuration); + state = EVENT_STATE_REPEAT; + } + break; + + case EVENT_STATE_REPEAT: + repeatDuration += timeout; + if (!m_lastevent.empty()) + { + if (different_event(cur_event, m_lastevent.front())) + { + // different event, cycle back to test + state = EVENT_STATE_TEST; + break; + } + + // same event, eat it + m_lastevent.pop(); + } + + if (repeatDuration >= holdTimeout) + { + // this is a repeat, push it + MessagePushRepeat(&cur_event); + // assuming holdTimeout > repeatTimeout, + // just subtract the repeatTimeout + // to get the next cycle time + repeatDuration -= repeatTimeout; + } + break; + } + } +} diff --git a/xbmc/windowing/android/WinEventsAndroid.h b/xbmc/windowing/android/WinEventsAndroid.h new file mode 100644 index 0000000..62b04b5 --- /dev/null +++ b/xbmc/windowing/android/WinEventsAndroid.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2010-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. + */ + +#pragma once + +#include "threads/CriticalSection.h" +#include "threads/Event.h" +#include "threads/Thread.h" +#include "windowing/WinEvents.h" + +#include <list> +#include <queue> +#include <string> +#include <vector> + +class CWinEventsAndroid : public IWinEvents, public CThread +{ +public: + CWinEventsAndroid(); + ~CWinEventsAndroid() override; + + void MessagePush(XBMC_Event *newEvent); + void MessagePushRepeat(XBMC_Event *repeatEvent); + bool MessagePump() override; + +private: + size_t GetQueueSize(); + + // for CThread + void Process() override; + + CCriticalSection m_eventsCond; + std::list<XBMC_Event> m_events; + + CCriticalSection m_lasteventCond; + std::queue<XBMC_Event> m_lastevent; +}; + diff --git a/xbmc/windowing/android/WinSystemAndroid.cpp b/xbmc/windowing/android/WinSystemAndroid.cpp new file mode 100644 index 0000000..d8ddbb0 --- /dev/null +++ b/xbmc/windowing/android/WinSystemAndroid.cpp @@ -0,0 +1,329 @@ +/* + * 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. + */ + +#include "WinSystemAndroid.h" + +#include "OSScreenSaverAndroid.h" +#include "ServiceBroker.h" +#include "WinEventsAndroid.h" +#include "addons/interfaces/platform/android/System.h" +#include "cores/RetroPlayer/process/android/RPProcessInfoAndroid.h" +#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h" +#include "cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.h" +#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.h" +#include "cores/VideoPlayer/Process/android/ProcessInfoAndroid.h" +#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodec.h" +#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodecSurface.h" +#include "guilib/DispResource.h" +#include "rendering/gles/ScreenshotSurfaceGLES.h" +#include "settings/DisplaySettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "system_egl.h" +#include "utils/HDRCapabilities.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" +#include "windowing/Resolution.h" + +#include "platform/android/activity/XBMCApp.h" +#include "platform/android/media/decoderfilter/MediaCodecDecoderFilterManager.h" +#include "platform/android/media/drm/MediaDrmCryptoSession.h" + +#include <float.h> +#include <mutex> +#include <string.h> + +#include <EGL/eglplatform.h> + +using namespace KODI; +using namespace std::chrono_literals; + +CWinSystemAndroid::CWinSystemAndroid() +{ + m_displayWidth = 0; + m_displayHeight = 0; + + m_stereo_mode = RENDER_STEREO_MODE_OFF; + + m_dispResetTimer = new CTimer(this); + + m_android = nullptr; + + m_winEvents.reset(new CWinEventsAndroid()); +} + +CWinSystemAndroid::~CWinSystemAndroid() +{ + delete m_dispResetTimer; +} + +bool CWinSystemAndroid::InitWindowSystem() +{ + m_nativeDisplay = EGL_DEFAULT_DISPLAY; + + m_android = new CAndroidUtils(); + + m_decoderFilterManager = new(CMediaCodecDecoderFilterManager); + CServiceBroker::RegisterDecoderFilterManager(m_decoderFilterManager); + + CDVDVideoCodecAndroidMediaCodec::Register(); + CDVDAudioCodecAndroidMediaCodec::Register(); + + CLinuxRendererGLES::Register(); + RETRO::CRPProcessInfoAndroid::Register(); + RETRO::CRPProcessInfoAndroid::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGLES); + CRendererMediaCodec::Register(); + CRendererMediaCodecSurface::Register(); + ADDON::Interface_Android::Register(); + DRM::CMediaDrmCryptoSession::Register(); + VIDEOPLAYER::CProcessInfoAndroid::Register(); + + CScreenshotSurfaceGLES::Register(); + + return CWinSystemBase::InitWindowSystem(); +} + +bool CWinSystemAndroid::DestroyWindowSystem() +{ + CLog::Log(LOGINFO, "CWinSystemAndroid::{}", __FUNCTION__); + + delete m_android; + m_android = nullptr; + + CServiceBroker::RegisterDecoderFilterManager(nullptr); + delete m_decoderFilterManager; + m_decoderFilterManager = nullptr; + + return true; +} + +bool CWinSystemAndroid::CreateNewWindow(const std::string& name, + bool fullScreen, + RESOLUTION_INFO& res) +{ + RESOLUTION_INFO current_resolution; + current_resolution.iWidth = current_resolution.iHeight = 0; + RENDER_STEREO_MODE stereo_mode = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode(); + + m_nWidth = res.iWidth; + m_nHeight = res.iHeight; + m_displayWidth = res.iScreenWidth; + m_displayHeight = res.iScreenHeight; + m_fRefreshRate = res.fRefreshRate; + + if ((m_bWindowCreated && m_android->GetNativeResolution(¤t_resolution)) && + current_resolution.iWidth == res.iWidth && current_resolution.iHeight == res.iHeight && + current_resolution.iScreenWidth == res.iScreenWidth && current_resolution.iScreenHeight == res.iScreenHeight && + m_bFullScreen == fullScreen && current_resolution.fRefreshRate == res.fRefreshRate && + (current_resolution.dwFlags & D3DPRESENTFLAG_MODEMASK) == (res.dwFlags & D3DPRESENTFLAG_MODEMASK) && + m_stereo_mode == stereo_mode) + { + CLog::Log(LOGDEBUG, "CWinSystemAndroid::CreateNewWindow: No need to create a new window"); + return true; + } + + m_dispResetTimer->Stop(); + m_HdmiModeTriggered = false; + + m_stereo_mode = stereo_mode; + m_bFullScreen = fullScreen; + + m_nativeWindow = CXBMCApp::Get().GetNativeWindow(2000); + if (!m_nativeWindow) + { + CLog::Log(LOGERROR, "CWinSystemAndroid::CreateNewWindow: failed"); + return false; + } + + m_android->SetNativeResolution(res); + + return true; +} + +bool CWinSystemAndroid::DestroyWindow() +{ + CLog::Log(LOGINFO, "CWinSystemAndroid::{}", __FUNCTION__); + m_nativeWindow.reset(); + m_bWindowCreated = false; + return true; +} + +void CWinSystemAndroid::UpdateResolutions() +{ + UpdateResolutions(true); +} + +void CWinSystemAndroid::UpdateResolutions(bool bUpdateDesktopRes) +{ + CWinSystemBase::UpdateResolutions(); + + std::vector<RESOLUTION_INFO> resolutions; + if (!m_android->ProbeResolutions(resolutions) || resolutions.empty()) + { + CLog::Log(LOGWARNING, "CWinSystemAndroid::{} failed.", __FUNCTION__); + } + + const RESOLUTION_INFO resWindow = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW); + + RESOLUTION_INFO resDesktop; + if (bUpdateDesktopRes) + { + // ProbeResolutions includes already all resolutions. + // Only get desktop resolution so we can replace Kodi's desktop res. + RESOLUTION_INFO curDisplay; + if (m_android->GetNativeResolution(&curDisplay)) + resDesktop = curDisplay; + } + else + { + // Do not replace Kodi's desktop res, just update the data. + resDesktop = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP); + } + + CDisplaySettings::GetInstance().ClearCustomResolutions(); + + for (auto& res : resolutions) + { + CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(res); + CDisplaySettings::GetInstance().AddResolutionInfo(res); + + if (resDesktop.iScreenWidth == res.iScreenWidth && + resDesktop.iScreenHeight == res.iScreenHeight && + (resDesktop.dwFlags & D3DPRESENTFLAG_MODEMASK) == (res.dwFlags & D3DPRESENTFLAG_MODEMASK) && + std::fabs(resDesktop.fRefreshRate - res.fRefreshRate) < FLT_EPSILON) + { + CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP) = res; + } + + if (resWindow.iScreenWidth == res.iScreenWidth && + resWindow.iScreenHeight == res.iScreenHeight && + (resWindow.dwFlags & D3DPRESENTFLAG_MODEMASK) == (res.dwFlags & D3DPRESENTFLAG_MODEMASK) && + std::fabs(resWindow.fRefreshRate - res.fRefreshRate) < FLT_EPSILON) + { + CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW) = res; + } + } + + CDisplaySettings::GetInstance().ApplyCalibrations(); +} + +void CWinSystemAndroid::OnTimeout() +{ + m_HdmiModeTriggered = true; +} + +void CWinSystemAndroid::InitiateModeChange() +{ + auto delay = + std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + "videoscreen.delayrefreshchange") * + 100); + + if (delay < 2000ms) + delay = 2000ms; + m_dispResetTimer->Stop(); + m_dispResetTimer->Start(delay); + + SetHdmiState(false); +} + +void CWinSystemAndroid::SetHdmiState(bool connected) +{ + std::unique_lock<CCriticalSection> lock(m_resourceSection); + CLog::Log(LOGDEBUG, "CWinSystemAndroid::SetHdmiState: state: {}", static_cast<int>(connected)); + + if (connected) + { + if (m_dispResetTimer->IsRunning()) + { + // We stop the timer if OS supports HDMI_AUDIO_PLUG intent + // and configured delay is smaller than the time HDMI_PLUG took. + // Note that timer is always started with minimum of 2 seconds + // regardless if the configured delay is smaller + if (m_dispResetTimer->GetElapsedMilliseconds() >= + CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + "videoscreen.delayrefreshchange") * + 100) + m_dispResetTimer->Stop(); + else + return; + } + + for (auto resource : m_resources) + resource->OnResetDisplay(); + m_dispResetTimer->Stop(); + m_HdmiModeTriggered = false; + } + else + { + for (auto resource : m_resources) + resource->OnLostDisplay(); + } +} + +void CWinSystemAndroid::UpdateDisplayModes() +{ + // re-fetch display modes + m_android->UpdateDisplayModes(); + + if (m_nativeWindow) + { + // update display settings + UpdateResolutions(false); + } +} + +bool CWinSystemAndroid::Hide() +{ + return false; +} + +bool CWinSystemAndroid::Show(bool raise) +{ + return false; +} + +void CWinSystemAndroid::Register(IDispResource *resource) +{ + std::unique_lock<CCriticalSection> lock(m_resourceSection); + m_resources.push_back(resource); +} + +void CWinSystemAndroid::Unregister(IDispResource *resource) +{ + std::unique_lock<CCriticalSection> lock(m_resourceSection); + std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource); + if (i != m_resources.end()) + m_resources.erase(i); +} + +void CWinSystemAndroid::MessagePush(XBMC_Event *newEvent) +{ + dynamic_cast<CWinEventsAndroid&>(*m_winEvents).MessagePush(newEvent); +} + +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()); + return ret; +} + +CHDRCapabilities CWinSystemAndroid::GetDisplayHDRCapabilities() const +{ + return CAndroidUtils::GetDisplayHDRCapabilities(); +} diff --git a/xbmc/windowing/android/WinSystemAndroid.h b/xbmc/windowing/android/WinSystemAndroid.h new file mode 100644 index 0000000..0e39193 --- /dev/null +++ b/xbmc/windowing/android/WinSystemAndroid.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#pragma once + +#include "AndroidUtils.h" +#include "rendering/gles/RenderSystemGLES.h" +#include "system_egl.h" +#include "threads/CriticalSection.h" +#include "threads/Timer.h" +#include "utils/HDRCapabilities.h" +#include "windowing/WinSystem.h" + +#include <memory> + +class CDecoderFilterManager; +class IDispResource; +class CNativeWindow; + +class CWinSystemAndroid : public CWinSystemBase, public ITimerCallback +{ +public: + CWinSystemAndroid(); + ~CWinSystemAndroid() override; + + bool InitWindowSystem() override; + bool DestroyWindowSystem() override; + + bool CreateNewWindow(const std::string& name, + bool fullScreen, + RESOLUTION_INFO& res) override; + + bool DestroyWindow() override; + void UpdateResolutions() override; + + void InitiateModeChange(); + bool IsHdmiModeTriggered() const { return m_HdmiModeTriggered; } + void SetHdmiState(bool connected); + + void UpdateDisplayModes(); + + bool HasCursor() override { return false; } + + bool Hide() override; + bool Show(bool raise = true) override; + void Register(IDispResource *resource) override; + void Unregister(IDispResource *resource) override; + + void MessagePush(XBMC_Event *newEvent); + + // winevents override + bool MessagePump() override; + bool IsHDRDisplay() override; + + CHDRCapabilities GetDisplayHDRCapabilities() const override; + +protected: + std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> GetOSScreenSaverImpl() override; + void OnTimeout() override; + + CAndroidUtils *m_android; + + EGLDisplay m_nativeDisplay = EGL_NO_DISPLAY; + std::shared_ptr<CNativeWindow> m_nativeWindow; + + int m_displayWidth; + int m_displayHeight; + + RENDER_STEREO_MODE m_stereo_mode; + + CTimer *m_dispResetTimer; + + CCriticalSection m_resourceSection; + std::vector<IDispResource*> m_resources; + CDecoderFilterManager *m_decoderFilterManager; + +private: + bool m_HdmiModeTriggered = false; + void UpdateResolutions(bool bUpdateDesktopRes); +}; diff --git a/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp b/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp new file mode 100644 index 0000000..ac562ae --- /dev/null +++ b/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp @@ -0,0 +1,270 @@ +/* + * 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. + */ + +#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 "windowing/WindowSystemFactory.h" + +#include "platform/android/activity/XBMCApp.h" + +#include <EGL/eglext.h> +#include <unistd.h> + +#include "PlatformDefs.h" + +void CWinSystemAndroidGLESContext::Register() +{ + KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem); +} + +std::unique_ptr<CWinSystemBase> CWinSystemAndroidGLESContext::CreateWinSystem() +{ + return std::make_unique<CWinSystemAndroidGLESContext>(); +} + +bool CWinSystemAndroidGLESContext::InitWindowSystem() +{ + if (!CWinSystemAndroid::InitWindowSystem()) + { + return false; + } + + if (!m_pGLContext.CreateDisplay(m_nativeDisplay)) + { + return false; + } + + if (!m_pGLContext.InitializeDisplay(EGL_OPENGL_ES_API)) + { + return false; + } + + if (!m_pGLContext.ChooseConfig(EGL_OPENGL_ES2_BIT)) + { + return false; + } + + m_hasHDRConfig = m_pGLContext.ChooseConfig(EGL_OPENGL_ES2_BIT, 0, true); + + m_hasEGL_BT2020_PQ_Colorspace_Extension = + CEGLUtils::HasExtension(m_pGLContext.GetEGLDisplay(), "EGL_EXT_gl_colorspace_bt2020_pq"); + m_hasEGL_ST2086_Extension = + CEGLUtils::HasExtension(m_pGLContext.GetEGLDisplay(), "EGL_EXT_surface_SMPTE2086_metadata"); + + bool hasEGLHDRExtensions = m_hasEGL_BT2020_PQ_Colorspace_Extension && m_hasEGL_ST2086_Extension; + + CLog::Log(LOGDEBUG, + "CWinSystemAndroidGLESContext::InitWindowSystem: HDRConfig: {}, HDRExtensions: {}", + static_cast<int>(m_hasHDRConfig), static_cast<int>(hasEGLHDRExtensions)); + + CEGLAttributesVec contextAttribs; + contextAttribs.Add({{EGL_CONTEXT_CLIENT_VERSION, 2}}); + + if (!m_pGLContext.CreateContext(contextAttribs)) + { + return false; + } + + return true; +} + +bool CWinSystemAndroidGLESContext::CreateNewWindow(const std::string& name, + bool fullScreen, + RESOLUTION_INFO& res) +{ + m_pGLContext.DestroySurface(); + + if (!CWinSystemAndroid::CreateNewWindow(name, fullScreen, res)) + { + return false; + } + + if (!CreateSurface()) + { + return false; + } + + if (!m_pGLContext.BindContext()) + { + return false; + } + + return true; +} + +bool CWinSystemAndroidGLESContext::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) +{ + CRenderSystemGLES::ResetRenderSystem(newWidth, newHeight); + return true; +} + +bool CWinSystemAndroidGLESContext::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) +{ + CreateNewWindow("", fullScreen, res); + CRenderSystemGLES::ResetRenderSystem(res.iWidth, res.iHeight); + return true; +} + +void CWinSystemAndroidGLESContext::SetVSyncImpl(bool enable) +{ + // We use Choreographer for timing + m_pGLContext.SetVSync(false); +} + +void CWinSystemAndroidGLESContext::PresentRenderImpl(bool rendered) +{ + if (!m_nativeWindow) + { + usleep(10000); + return; + } + + // Mode change finalization was triggered by timer + if (IsHdmiModeTriggered()) + SetHdmiState(true); + + // 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::Log(LOGERROR, "eglSwapBuffers failed"); + + CXBMCApp::Get().WaitVSync(1000); +} + +float CWinSystemAndroidGLESContext::GetFrameLatencyAdjustment() +{ + return CXBMCApp::Get().GetFrameLatencyMs(); +} + +EGLDisplay CWinSystemAndroidGLESContext::GetEGLDisplay() const +{ + return m_pGLContext.GetEGLDisplay(); +} + +EGLSurface CWinSystemAndroidGLESContext::GetEGLSurface() const +{ + return m_pGLContext.GetEGLSurface(); +} + +EGLContext CWinSystemAndroidGLESContext::GetEGLContext() const +{ + return m_pGLContext.GetEGLContext(); +} + +EGLConfig CWinSystemAndroidGLESContext::GetEGLConfig() const +{ + return m_pGLContext.GetEGLConfig(); +} + +std::unique_ptr<CVideoSync> CWinSystemAndroidGLESContext::GetVideoSync(void *clock) +{ + std::unique_ptr<CVideoSync> pVSync(new CVideoSyncAndroid(clock)); + return pVSync; +} + +bool CWinSystemAndroidGLESContext::CreateSurface() +{ + if (!m_pGLContext.CreateSurface(static_cast<EGLNativeWindowType>(m_nativeWindow->m_window), + m_HDRColorSpace)) + { + if (m_HDRColorSpace != EGL_NONE) + { + m_HDRColorSpace = EGL_NONE; + m_displayMetadata = nullptr; + m_lightMetadata = nullptr; + if (!m_pGLContext.CreateSurface(static_cast<EGLNativeWindowType>(m_nativeWindow->m_window))) + 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_hasEGL_BT2020_PQ_Colorspace_Extension || m_hasEGL_ST2086_Extension) && + 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_hasEGL_BT2020_PQ_Colorspace_Extension && m_hasEGL_ST2086_Extension) + { + 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: {}", 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 new file mode 100644 index 0000000..b04ff41 --- /dev/null +++ b/xbmc/windowing/android/WinSystemAndroidGLESContext.h @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#pragma once + +#include "WinSystemAndroid.h" +#include "rendering/gles/RenderSystemGLES.h" +#include "utils/EGLUtils.h" +#include "utils/GlobalsHandling.h" + +struct AVMasteringDisplayMetadata; +struct AVContentLightMetadata; + +class CWinSystemAndroidGLESContext : public CWinSystemAndroid, public CRenderSystemGLES +{ +public: + CWinSystemAndroidGLESContext() = default; + ~CWinSystemAndroidGLESContext() override = default; + + static void Register(); + static std::unique_ptr<CWinSystemBase> CreateWinSystem(); + + // Implementation of CWinSystemBase via CWinSystemAndroid + CRenderSystemBase *GetRenderSystem() override { return this; } + bool InitWindowSystem() override; + bool CreateNewWindow(const std::string& name, + bool fullScreen, + RESOLUTION_INFO& res) override; + + bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override; + bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override; + + 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; + EGLContext GetEGLContext() const; + EGLConfig GetEGLConfig() const; +protected: + void SetVSyncImpl(bool enable) override; + 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_hasEGL_ST2086_Extension = false; + bool m_hasEGL_BT2020_PQ_Colorspace_Extension = false; +}; |