diff options
Diffstat (limited to '')
-rw-r--r-- | xbmc/windowing/Resolution.cpp | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/xbmc/windowing/Resolution.cpp b/xbmc/windowing/Resolution.cpp new file mode 100644 index 0000000..8d9af06 --- /dev/null +++ b/xbmc/windowing/Resolution.cpp @@ -0,0 +1,474 @@ +/* + * 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 "Resolution.h" + +#include "GraphicContext.h" +#include "ServiceBroker.h" +#include "settings/AdvancedSettings.h" +#include "settings/DisplaySettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/MathUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <cstdlib> +#include <limits> + +namespace +{ + +const char* SETTING_VIDEOSCREEN_WHITELIST_PULLDOWN{"videoscreen.whitelistpulldown"}; +const char* SETTING_VIDEOSCREEN_WHITELIST_DOUBLEREFRESHRATE{ + "videoscreen.whitelistdoublerefreshrate"}; + +} // namespace + +EdgeInsets::EdgeInsets(float l, float t, float r, float b) : left(l), top(t), right(r), bottom(b) +{ +} + +RESOLUTION_INFO::RESOLUTION_INFO(int width, int height, float aspect, const std::string &mode) : + strMode(mode) +{ + iWidth = width; + iHeight = height; + iBlanking = 0; + iScreenWidth = width; + iScreenHeight = height; + fPixelRatio = aspect ? ((float)width)/height / aspect : 1.0f; + bFullScreen = true; + fRefreshRate = 0; + dwFlags = iSubtitles = 0; +} + +RESOLUTION_INFO::RESOLUTION_INFO(const RESOLUTION_INFO& res) + : Overscan(res.Overscan), + guiInsets(res.guiInsets), + strMode(res.strMode), + strOutput(res.strOutput), + strId(res.strId) +{ + bFullScreen = res.bFullScreen; + iWidth = res.iWidth; iHeight = res.iHeight; + iScreenWidth = res.iScreenWidth; iScreenHeight = res.iScreenHeight; + iSubtitles = res.iSubtitles; dwFlags = res.dwFlags; + fPixelRatio = res.fPixelRatio; fRefreshRate = res.fRefreshRate; + iBlanking = res.iBlanking; +} + +float RESOLUTION_INFO::DisplayRatio() const +{ + return iWidth * fPixelRatio / iHeight; +} + +RESOLUTION CResolutionUtils::ChooseBestResolution(float fps, int width, int height, bool is3D) +{ + RESOLUTION res = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(); + float weight = 0.0f; + + if (!FindResolutionFromOverride(fps, width, is3D, res, weight, false)) //find a refreshrate from overrides + { + if (!FindResolutionFromOverride(fps, width, is3D, res, weight, true)) //if that fails find it from a fallback + { + FindResolutionFromWhitelist(fps, width, height, is3D, res); //find a refreshrate from whitelist + } + } + + CLog::Log(LOGINFO, "Display resolution ADJUST : {} ({}) (weight: {:.3f})", + CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(res).strMode, res, weight); + return res; +} + +void CResolutionUtils::FindResolutionFromWhitelist(float fps, int width, int height, bool is3D, RESOLUTION &resolution) +{ + RESOLUTION_INFO curr = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(resolution); + CLog::Log(LOGINFO, + "[WHITELIST] Searching the whitelist for: width: {}, height: {}, fps: {:0.3f}, 3D: {}", + width, height, fps, is3D ? "true" : "false"); + + std::vector<CVariant> indexList = CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(CSettings::SETTING_VIDEOSCREEN_WHITELIST); + + bool noWhiteList = indexList.empty(); + + if (noWhiteList) + { + CLog::Log(LOGDEBUG, + "[WHITELIST] Using the default whitelist because the user whitelist is empty"); + std::vector<RESOLUTION> candidates; + RESOLUTION_INFO info; + std::string resString; + CServiceBroker::GetWinSystem()->GetGfxContext().GetAllowedResolutions(candidates); + for (const auto& c : candidates) + { + info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(c); + if (info.iScreenHeight >= curr.iScreenHeight && info.iScreenWidth >= curr.iScreenWidth && + (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == (curr.dwFlags & D3DPRESENTFLAG_MODEMASK)) + { + // do not add half refreshrates (25, 29.97 by default) as kodi cannot cope with + // them on playback start. Especially interlaced content is not properly detected + // and this causes ugly double switching. + // This won't allow 25p / 30p playback on native refreshrate by default + if ((info.fRefreshRate > 30) || (MathUtils::FloatEquals(info.fRefreshRate, 24.0f, 0.1f))) + { + resString = CDisplaySettings::GetInstance().GetStringFromRes(c); + indexList.emplace_back(resString); + } + } + } + } + + CLog::Log(LOGDEBUG, "[WHITELIST] Searching for an exact resolution with an exact refresh rate"); + + unsigned int penalty = std::numeric_limits<unsigned int>::max(); + bool found = false; + + for (const auto& mode : indexList) + { + auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString()); + const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i); + + // allow resolutions that are exact and have the correct refresh rate + // allow macroblock alignment / padding errors (e.g. 1080 mod16 == 8) + if (((height == info.iScreenHeight && width <= info.iScreenWidth + 8) || + (width == info.iScreenWidth && height <= info.iScreenHeight + 8)) && + (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == (curr.dwFlags & D3DPRESENTFLAG_MODEMASK) && + MathUtils::FloatEquals(info.fRefreshRate, fps, 0.01f)) + { + CLog::Log(LOGDEBUG, + "[WHITELIST] Matched an exact resolution with an exact refresh rate {} ({})", + info.strMode, i); + unsigned int pen = abs(info.iScreenHeight - height) + abs(info.iScreenWidth - width); + if (pen < penalty) + { + resolution = i; + found = true; + penalty = pen; + } + } + } + + if (!found) + CLog::Log(LOGDEBUG, "[WHITELIST] No match for an exact resolution with an exact refresh rate"); + + if (noWhiteList || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + SETTING_VIDEOSCREEN_WHITELIST_DOUBLEREFRESHRATE)) + { + CLog::Log(LOGDEBUG, + "[WHITELIST] Searching for an exact resolution with double the refresh rate"); + + for (const auto& mode : indexList) + { + auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString()); + const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i); + + // allow resolutions that are exact and have double the refresh rate + // allow macroblock alignment / padding errors (e.g. 1080 mod16 == 8) + if (((height == info.iScreenHeight && width <= info.iScreenWidth + 8) || + (width == info.iScreenWidth && height <= info.iScreenHeight + 8)) && + (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == (curr.dwFlags & D3DPRESENTFLAG_MODEMASK) && + MathUtils::FloatEquals(info.fRefreshRate, fps * 2, 0.01f)) + { + CLog::Log(LOGDEBUG, + "[WHITELIST] Matched an exact resolution with double the refresh rate {} ({})", + info.strMode, i); + unsigned int pen = abs(info.iScreenHeight - height) + abs(info.iScreenWidth - width); + if (pen < penalty) + { + resolution = i; + found = true; + penalty = pen; + } + } + } + if (found) + return; + + CLog::Log(LOGDEBUG, + "[WHITELIST] No match for an exact resolution with double the refresh rate"); + } + else if (found) + return; + + if (noWhiteList || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + SETTING_VIDEOSCREEN_WHITELIST_PULLDOWN)) + { + CLog::Log(LOGDEBUG, + "[WHITELIST] Searching for an exact resolution with a 3:2 pulldown refresh rate"); + + for (const auto& mode : indexList) + { + auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString()); + const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i); + + // allow resolutions that are exact and have 2.5 times the refresh rate + // allow macroblock alignment / padding errors (e.g. 1080 mod16 == 8) + if (((height == info.iScreenHeight && width <= info.iScreenWidth + 8) || + (width == info.iScreenWidth && height <= info.iScreenHeight + 8)) && + (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == (curr.dwFlags & D3DPRESENTFLAG_MODEMASK) && + MathUtils::FloatEquals(info.fRefreshRate, fps * 2.5f, 0.01f)) + { + CLog::Log( + LOGDEBUG, + "[WHITELIST] Matched an exact resolution with a 3:2 pulldown refresh rate {} ({})", + info.strMode, i); + unsigned int pen = abs(info.iScreenHeight - height) + abs(info.iScreenWidth - width); + if (pen < penalty) + { + resolution = i; + found = true; + penalty = pen; + } + } + } + if (found) + return; + + CLog::Log(LOGDEBUG, "[WHITELIST] No match for a resolution with a 3:2 pulldown refresh rate"); + } + + + CLog::Log(LOGDEBUG, "[WHITELIST] Searching for a desktop resolution with an exact refresh rate"); + + const RESOLUTION_INFO desktop_info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(CDisplaySettings::GetInstance().GetCurrentResolution()); + + for (const auto& mode : indexList) + { + auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString()); + const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i); + + // allow resolutions that are desktop resolution but have the correct refresh rate + if (info.iScreenWidth == desktop_info.iScreenWidth && + (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == (desktop_info.dwFlags & D3DPRESENTFLAG_MODEMASK) && + MathUtils::FloatEquals(info.fRefreshRate, fps, 0.01f)) + { + CLog::Log(LOGDEBUG, + "[WHITELIST] Matched a desktop resolution with an exact refresh rate {} ({})", + info.strMode, i); + resolution = i; + return; + } + } + + CLog::Log(LOGDEBUG, "[WHITELIST] No match for a desktop resolution with an exact refresh rate"); + + if (noWhiteList || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + SETTING_VIDEOSCREEN_WHITELIST_DOUBLEREFRESHRATE)) + { + CLog::Log(LOGDEBUG, + "[WHITELIST] Searching for a desktop resolution with double the refresh rate"); + + for (const auto& mode : indexList) + { + auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString()); + const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i); + + // allow resolutions that are desktop resolution but have double the refresh rate + if (info.iScreenWidth == desktop_info.iScreenWidth && + (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == + (desktop_info.dwFlags & D3DPRESENTFLAG_MODEMASK) && + MathUtils::FloatEquals(info.fRefreshRate, fps * 2, 0.01f)) + { + CLog::Log(LOGDEBUG, + "[WHITELIST] Matched a desktop resolution with double the refresh rate {} ({})", + info.strMode, i); + resolution = i; + return; + } + } + + CLog::Log(LOGDEBUG, + "[WHITELIST] No match for a desktop resolution with double the refresh rate"); + } + + if (noWhiteList || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + SETTING_VIDEOSCREEN_WHITELIST_PULLDOWN)) + { + CLog::Log(LOGDEBUG, + "[WHITELIST] Searching for a desktop resolution with a 3:2 pulldown refresh rate"); + + for (const auto& mode : indexList) + { + auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString()); + const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i); + + // allow resolutions that are desktop resolution but have 2.5 times the refresh rate + if (info.iScreenWidth == desktop_info.iScreenWidth && + (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == + (desktop_info.dwFlags & D3DPRESENTFLAG_MODEMASK) && + MathUtils::FloatEquals(info.fRefreshRate, fps * 2.5f, 0.01f)) + { + CLog::Log( + LOGDEBUG, + "[WHITELIST] Matched a desktop resolution with a 3:2 pulldown refresh rate {} ({})", + info.strMode, i); + resolution = i; + return; + } + } + + CLog::Log(LOGDEBUG, + "[WHITELIST] No match for a desktop resolution with a 3:2 pulldown refresh rate"); + } + + CLog::Log(LOGDEBUG, "[WHITELIST] No resolution matched"); +} + +bool CResolutionUtils::FindResolutionFromOverride(float fps, int width, bool is3D, RESOLUTION &resolution, float& weight, bool fallback) +{ + RESOLUTION_INFO curr = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(resolution); + + //try to find a refreshrate from the override + for (int i = 0; i < (int)CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoAdjustRefreshOverrides.size(); i++) + { + RefreshOverride& override = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoAdjustRefreshOverrides[i]; + + if (override.fallback != fallback) + continue; + + //if we're checking for overrides, check if the fps matches + if (!fallback && (fps < override.fpsmin || fps > override.fpsmax)) + continue; + + for (size_t j = (int)RES_DESKTOP; j < CDisplaySettings::GetInstance().ResolutionInfoSize(); j++) + { + RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo((RESOLUTION)j); + + if (info.iScreenWidth == curr.iScreenWidth && + info.iScreenHeight == curr.iScreenHeight && + (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == (curr.dwFlags & D3DPRESENTFLAG_MODEMASK)) + { + if (info.fRefreshRate <= override.refreshmax && + info.fRefreshRate >= override.refreshmin) + { + resolution = (RESOLUTION)j; + + if (fallback) + { + CLog::Log( + LOGDEBUG, + "Found Resolution {} ({}) from fallback (refreshmin:{:.3f} refreshmax:{:.3f})", + info.strMode, resolution, override.refreshmin, override.refreshmax); + } + else + { + CLog::Log(LOGDEBUG, + "Found Resolution {} ({}) from override of fps {:.3f} (fpsmin:{:.3f} " + "fpsmax:{:.3f} refreshmin:{:.3f} refreshmax:{:.3f})", + info.strMode, resolution, fps, override.fpsmin, override.fpsmax, + override.refreshmin, override.refreshmax); + } + + weight = RefreshWeight(info.fRefreshRate, fps); + + return true; //fps and refresh match with this override, use this resolution + } + } + } + } + + return false; //no override found +} + +//distance of refresh to the closest multiple of fps (multiple is 1 or higher), as a multiplier of fps +float CResolutionUtils::RefreshWeight(float refresh, float fps) +{ + float div = refresh / fps; + int round = MathUtils::round_int(static_cast<double>(div)); + + float weight = 0.0f; + + if (round < 1) + weight = (fps - refresh) / fps; + else + weight = fabs(div / round - 1.0f); + + // punish higher refreshrates and prefer better matching + // e.g. 30 fps content at 60 hz is better than + // 30 fps at 120 hz - as we sometimes don't know if + // the content is interlaced at the start, only + // punish when refreshrate > 60 hz to not have to switch + // twice for 30i content + if (refresh > 60 && round > 1) + weight += round / 10000.0f; + + return weight; +} + +bool CResolutionUtils::HasWhitelist() +{ + std::vector<CVariant> indexList = CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(CSettings::SETTING_VIDEOSCREEN_WHITELIST); + return !indexList.empty(); +} + +void CResolutionUtils::PrintWhitelist() +{ + std::string modeStr; + auto indexList = CServiceBroker::GetSettingsComponent()->GetSettings()->GetList( + CSettings::SETTING_VIDEOSCREEN_WHITELIST); + if (!indexList.empty()) + { + for (const auto& mode : indexList) + { + auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString()); + const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i); + modeStr.append("\n" + info.strMode); + } + + CLog::Log(LOGDEBUG, "[WHITELIST] whitelisted modes:{}", modeStr); + } +} + +void CResolutionUtils::GetMaxAllowedResolution(unsigned int& width, unsigned int& height) +{ + if (!CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenRoot()) + return; + + std::vector<RESOLUTION_INFO> resList; + + auto indexList = CServiceBroker::GetSettingsComponent()->GetSettings()->GetList( + CSettings::SETTING_VIDEOSCREEN_WHITELIST); + + unsigned int maxWidth{0}; + unsigned int maxHeight{0}; + + if (!indexList.empty()) + { + for (const auto& mode : indexList) + { + RESOLUTION res = CDisplaySettings::GetInstance().GetResFromString(mode.asString()); + RESOLUTION_INFO resInfo{CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(res)}; + if (static_cast<unsigned int>(resInfo.iScreenWidth) > maxWidth && + static_cast<unsigned int>(resInfo.iScreenHeight) > maxHeight) + { + maxWidth = static_cast<unsigned int>(resInfo.iScreenWidth); + maxHeight = static_cast<unsigned int>(resInfo.iScreenHeight); + } + } + } + else + { + std::vector<RESOLUTION> resList; + CServiceBroker::GetWinSystem()->GetGfxContext().GetAllowedResolutions(resList); + + for (const auto& res : resList) + { + RESOLUTION_INFO resInfo{CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(res)}; + if (static_cast<unsigned int>(resInfo.iScreenWidth) > maxWidth && + static_cast<unsigned int>(resInfo.iScreenHeight) > maxHeight) + { + maxWidth = static_cast<unsigned int>(resInfo.iScreenWidth); + maxHeight = static_cast<unsigned int>(resInfo.iScreenHeight); + } + } + } + + width = maxWidth; + height = maxHeight; +} |