Bug 1764201 Part 1: Make widget::Screen track if the screen is HDR capable. r=geckoview-reviewers,ahale,m_kato

This duplicates existing HDR checks in gfxPlatformMac and
gfxWindowsPlatform. A later part of this patch series will remove that
redundant code, as it replaces calls to gfxPlatform functions with calls
to ScreenManager functions.

Differential Revision: https://phabricator.services.mozilla.com/D203670
This commit is contained in:
Brad Werth 2024-03-22 00:55:46 +00:00
parent 72c98c7f24
commit 2f4d447f5f
11 changed files with 101 additions and 29 deletions

View file

@ -114,6 +114,7 @@ struct ScreenDetails {
ScreenOrientation orientation; ScreenOrientation orientation;
uint16_t orientationAngle; uint16_t orientationAngle;
bool isPseudoDisplay; bool isPseudoDisplay;
bool isHDR;
}; };
struct DimensionInfo struct DimensionInfo

View file

@ -82,6 +82,7 @@
#include "mozilla/layers/DeviceAttachmentsD3D11.h" #include "mozilla/layers/DeviceAttachmentsD3D11.h"
#include "mozilla/WindowsProcessMitigations.h" #include "mozilla/WindowsProcessMitigations.h"
#include "D3D11Checks.h" #include "D3D11Checks.h"
#include "mozilla/ScreenHelperWin.h"
using namespace mozilla; using namespace mozilla;
using namespace mozilla::gfx; using namespace mozilla::gfx;
@ -402,6 +403,11 @@ void gfxWindowsPlatform::InitAcceleration() {
UpdateCanUseHardwareVideoDecoding(); UpdateCanUseHardwareVideoDecoding();
UpdateSupportsHDR(); UpdateSupportsHDR();
// Our ScreenHelperWin also depends on DeviceManagerDx state.
if (XRE_IsParentProcess() && !gfxPlatform::IsHeadless()) {
ScreenHelperWin::RefreshScreens();
}
RecordStartupTelemetry(); RecordStartupTelemetry();
} }

View file

@ -30,7 +30,7 @@ Screen::Screen(LayoutDeviceIntRect aRect, LayoutDeviceIntRect aAvailRect,
uint32_t aPixelDepth, uint32_t aColorDepth, uint32_t aPixelDepth, uint32_t aColorDepth,
uint32_t aRefreshRate, DesktopToLayoutDeviceScale aContentsScale, uint32_t aRefreshRate, DesktopToLayoutDeviceScale aContentsScale,
CSSToLayoutDeviceScale aDefaultCssScale, float aDPI, CSSToLayoutDeviceScale aDefaultCssScale, float aDPI,
IsPseudoDisplay aIsPseudoDisplay, IsPseudoDisplay aIsPseudoDisplay, IsHDR aIsHDR,
hal::ScreenOrientation aOrientation, hal::ScreenOrientation aOrientation,
OrientationAngle aOrientationAngle) OrientationAngle aOrientationAngle)
: mRect(aRect), : mRect(aRect),
@ -45,7 +45,8 @@ Screen::Screen(LayoutDeviceIntRect aRect, LayoutDeviceIntRect aAvailRect,
mDPI(aDPI), mDPI(aDPI),
mScreenOrientation(EffectiveOrientation(aOrientation, aRect)), mScreenOrientation(EffectiveOrientation(aOrientation, aRect)),
mOrientationAngle(aOrientationAngle), mOrientationAngle(aOrientationAngle),
mIsPseudoDisplay(aIsPseudoDisplay == IsPseudoDisplay::Yes) {} mIsPseudoDisplay(aIsPseudoDisplay == IsPseudoDisplay::Yes),
mIsHDR(aIsHDR == IsHDR::Yes) {}
Screen::Screen(const dom::ScreenDetails& aScreen) Screen::Screen(const dom::ScreenDetails& aScreen)
: mRect(aScreen.rect()), : mRect(aScreen.rect()),
@ -60,7 +61,8 @@ Screen::Screen(const dom::ScreenDetails& aScreen)
mDPI(aScreen.dpi()), mDPI(aScreen.dpi()),
mScreenOrientation(aScreen.orientation()), mScreenOrientation(aScreen.orientation()),
mOrientationAngle(aScreen.orientationAngle()), mOrientationAngle(aScreen.orientationAngle()),
mIsPseudoDisplay(aScreen.isPseudoDisplay()) {} mIsPseudoDisplay(aScreen.isPseudoDisplay()),
mIsHDR(aScreen.isHDR()) {}
Screen::Screen(const Screen& aOther) Screen::Screen(const Screen& aOther)
: mRect(aOther.mRect), : mRect(aOther.mRect),
@ -75,13 +77,14 @@ Screen::Screen(const Screen& aOther)
mDPI(aOther.mDPI), mDPI(aOther.mDPI),
mScreenOrientation(aOther.mScreenOrientation), mScreenOrientation(aOther.mScreenOrientation),
mOrientationAngle(aOther.mOrientationAngle), mOrientationAngle(aOther.mOrientationAngle),
mIsPseudoDisplay(aOther.mIsPseudoDisplay) {} mIsPseudoDisplay(aOther.mIsPseudoDisplay),
mIsHDR(aOther.mIsHDR) {}
dom::ScreenDetails Screen::ToScreenDetails() const { dom::ScreenDetails Screen::ToScreenDetails() const {
return dom::ScreenDetails( return dom::ScreenDetails(
mRect, mRectDisplayPix, mAvailRect, mAvailRectDisplayPix, mPixelDepth, mRect, mRectDisplayPix, mAvailRect, mAvailRectDisplayPix, mPixelDepth,
mColorDepth, mRefreshRate, mContentsScale, mDefaultCssScale, mDPI, mColorDepth, mRefreshRate, mContentsScale, mDefaultCssScale, mDPI,
mScreenOrientation, mOrientationAngle, mIsPseudoDisplay); mScreenOrientation, mOrientationAngle, mIsPseudoDisplay, mIsHDR);
} }
NS_IMETHODIMP NS_IMETHODIMP

View file

@ -26,12 +26,13 @@ class Screen final : public nsIScreen {
using OrientationAngle = uint16_t; using OrientationAngle = uint16_t;
enum class IsPseudoDisplay : bool { No, Yes }; enum class IsPseudoDisplay : bool { No, Yes };
enum class IsHDR : bool { No, Yes };
Screen(LayoutDeviceIntRect aRect, LayoutDeviceIntRect aAvailRect, Screen(LayoutDeviceIntRect aRect, LayoutDeviceIntRect aAvailRect,
uint32_t aPixelDepth, uint32_t aColorDepth, uint32_t aRefreshRate, uint32_t aPixelDepth, uint32_t aColorDepth, uint32_t aRefreshRate,
DesktopToLayoutDeviceScale aContentsScale, DesktopToLayoutDeviceScale aContentsScale,
CSSToLayoutDeviceScale aDefaultCssScale, float aDpi, IsPseudoDisplay, CSSToLayoutDeviceScale aDefaultCssScale, float aDpi, IsPseudoDisplay,
hal::ScreenOrientation = hal::ScreenOrientation::None, IsHDR, hal::ScreenOrientation = hal::ScreenOrientation::None,
OrientationAngle = 0); OrientationAngle = 0);
explicit Screen(const dom::ScreenDetails& aScreenDetails); explicit Screen(const dom::ScreenDetails& aScreenDetails);
Screen(const Screen& aOther); Screen(const Screen& aOther);
@ -60,6 +61,8 @@ class Screen final : public nsIScreen {
enum class IncludeOSZoom : bool { No, Yes }; enum class IncludeOSZoom : bool { No, Yes };
CSSToLayoutDeviceScale GetCSSToLayoutDeviceScale(IncludeOSZoom) const; CSSToLayoutDeviceScale GetCSSToLayoutDeviceScale(IncludeOSZoom) const;
bool GetIsHDR() const { return mIsHDR; }
private: private:
virtual ~Screen() = default; virtual ~Screen() = default;
@ -76,6 +79,7 @@ class Screen final : public nsIScreen {
const hal::ScreenOrientation mScreenOrientation; const hal::ScreenOrientation mScreenOrientation;
const OrientationAngle mOrientationAngle; const OrientationAngle mOrientationAngle;
const bool mIsPseudoDisplay; const bool mIsPseudoDisplay;
const bool mIsHDR;
}; };
} // namespace widget } // namespace widget

View file

@ -139,7 +139,8 @@ already_AddRefed<Screen> ScreenManager::ScreenForRect(
auto screen = MakeRefPtr<Screen>( auto screen = MakeRefPtr<Screen>(
LayoutDeviceIntRect(), LayoutDeviceIntRect(), 0, 0, 0, LayoutDeviceIntRect(), LayoutDeviceIntRect(), 0, 0, 0,
DesktopToLayoutDeviceScale(), CSSToLayoutDeviceScale(), 96 /* dpi */, DesktopToLayoutDeviceScale(), CSSToLayoutDeviceScale(), 96 /* dpi */,
Screen::IsPseudoDisplay::No, hal::ScreenOrientation::None, 0); Screen::IsPseudoDisplay::No, Screen::IsHDR::No,
hal::ScreenOrientation::None, 0);
return screen.forget(); return screen.forget();
} }
@ -219,10 +220,11 @@ already_AddRefed<Screen> ScreenManager::GetPrimaryScreen() {
if (mScreenList.IsEmpty()) { if (mScreenList.IsEmpty()) {
MOZ_LOG(sScreenLog, LogLevel::Warning, MOZ_LOG(sScreenLog, LogLevel::Warning,
("No screen available. This can happen in xpcshell.")); ("No screen available. This can happen in xpcshell."));
return MakeAndAddRef<Screen>( return MakeAndAddRef<Screen>(LayoutDeviceIntRect(), LayoutDeviceIntRect(),
LayoutDeviceIntRect(), LayoutDeviceIntRect(), 0, 0, 0, 0, 0, 0, DesktopToLayoutDeviceScale(),
DesktopToLayoutDeviceScale(), CSSToLayoutDeviceScale(), 96 /* dpi */, CSSToLayoutDeviceScale(), 96 /* dpi */,
Screen::IsPseudoDisplay::No, hal::ScreenOrientation::None, 0); Screen::IsPseudoDisplay::No, Screen::IsHDR::No,
hal::ScreenOrientation::None, 0);
} }
return do_AddRef(mScreenList[0]); return do_AddRef(mScreenList[0]);

View file

@ -38,14 +38,15 @@ static already_AddRefed<Screen> MakePrimaryScreen() {
uint32_t depth = java::GeckoAppShell::GetScreenDepth(); uint32_t depth = java::GeckoAppShell::GetScreenDepth();
float density = java::GeckoAppShell::GetDensity(); float density = java::GeckoAppShell::GetDensity();
float dpi = java::GeckoAppShell::GetDpi(); float dpi = java::GeckoAppShell::GetDpi();
bool isHDR = false; // Bug 1884960: report this accurately
auto orientation = auto orientation =
hal::ScreenOrientation(java::GeckoAppShell::GetScreenOrientation()); hal::ScreenOrientation(java::GeckoAppShell::GetScreenOrientation());
uint16_t angle = java::GeckoAppShell::GetScreenAngle(); uint16_t angle = java::GeckoAppShell::GetScreenAngle();
float refreshRate = java::GeckoAppShell::GetScreenRefreshRate(); float refreshRate = java::GeckoAppShell::GetScreenRefreshRate();
return MakeAndAddRef<Screen>(bounds, bounds, depth, depth, refreshRate, return MakeAndAddRef<Screen>(
DesktopToLayoutDeviceScale(density), bounds, bounds, depth, depth, refreshRate,
CSSToLayoutDeviceScale(1.0f), dpi, DesktopToLayoutDeviceScale(density), CSSToLayoutDeviceScale(1.0f), dpi,
Screen::IsPseudoDisplay::No, orientation, angle); Screen::IsPseudoDisplay::No, Screen::IsHDR(isHDR), orientation, angle);
} }
ScreenHelperAndroid::ScreenHelperAndroid() { ScreenHelperAndroid::ScreenHelperAndroid() {

View file

@ -9,6 +9,7 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#include "mozilla/Logging.h" #include "mozilla/Logging.h"
#include "nsCocoaFeatures.h"
#include "nsCocoaUtils.h" #include "nsCocoaUtils.h"
#include "nsObjCExceptions.h" #include "nsObjCExceptions.h"
@ -108,6 +109,13 @@ static already_AddRefed<Screen> MakeScreen(NSScreen* aScreen) {
if (pixelDepth > MAX_REPORTED_PIXEL_DEPTH) { if (pixelDepth > MAX_REPORTED_PIXEL_DEPTH) {
pixelDepth = MAX_REPORTED_PIXEL_DEPTH; pixelDepth = MAX_REPORTED_PIXEL_DEPTH;
} }
// Should we treat this as HDR? Based on spec at
// https://drafts.csswg.org/mediaqueries-5/#dynamic-range, we'll consider it
// HDR if it has pixel depth greater than 24.
bool isHDR = pixelDepth > 24;
// Double-check HDR against the platform capabilities.
isHDR &= nsCocoaFeatures::OnBigSurOrLater();
float dpi = 96.0f; float dpi = 96.0f;
CGDirectDisplayID displayID = CGDirectDisplayID displayID =
@ -125,9 +133,10 @@ static already_AddRefed<Screen> MakeScreen(NSScreen* aScreen) {
// Getting the refresh rate is a little hard on OS X. We could use // Getting the refresh rate is a little hard on OS X. We could use
// CVDisplayLinkGetNominalOutputVideoRefreshPeriod, but that's a little // CVDisplayLinkGetNominalOutputVideoRefreshPeriod, but that's a little
// involved. Ideally we could query it from vsync. For now, we leave it out. // involved. Ideally we could query it from vsync. For now, we leave it out.
RefPtr<Screen> screen = new Screen(rect, availRect, pixelDepth, pixelDepth, 0, RefPtr<Screen> screen =
contentsScaleFactor, defaultCssScaleFactor, new Screen(rect, availRect, pixelDepth, pixelDepth, 0,
dpi, Screen::IsPseudoDisplay::No); contentsScaleFactor, defaultCssScaleFactor, dpi,
Screen::IsPseudoDisplay::No, Screen::IsHDR(isHDR));
return screen.forget(); return screen.forget();
NS_OBJC_END_TRY_BLOCK_RETURN(nullptr); NS_OBJC_END_TRY_BLOCK_RETURN(nullptr);

View file

@ -269,7 +269,7 @@ static already_AddRefed<Screen> MakeScreenGtk(GdkScreen* aScreen,
contentsScale.scale, defaultCssScale.scale, dpi, refreshRate); contentsScale.scale, defaultCssScale.scale, dpi, refreshRate);
return MakeAndAddRef<Screen>(rect, availRect, pixelDepth, pixelDepth, return MakeAndAddRef<Screen>(rect, availRect, pixelDepth, pixelDepth,
refreshRate, contentsScale, defaultCssScale, dpi, refreshRate, contentsScale, defaultCssScale, dpi,
Screen::IsPseudoDisplay::No); Screen::IsPseudoDisplay::No, Screen::IsHDR::No);
} }
void ScreenGetterGtk::RefreshScreens() { void ScreenGetterGtk::RefreshScreens() {

View file

@ -32,9 +32,10 @@ LayoutDeviceIntRect HeadlessScreenHelper::GetScreenRect() {
HeadlessScreenHelper::HeadlessScreenHelper() { HeadlessScreenHelper::HeadlessScreenHelper() {
AutoTArray<RefPtr<Screen>, 1> screenList; AutoTArray<RefPtr<Screen>, 1> screenList;
LayoutDeviceIntRect rect = GetScreenRect(); LayoutDeviceIntRect rect = GetScreenRect();
auto ret = MakeRefPtr<Screen>( auto ret =
rect, rect, 24, 24, 0, DesktopToLayoutDeviceScale(), MakeRefPtr<Screen>(rect, rect, 24, 24, 0, DesktopToLayoutDeviceScale(),
CSSToLayoutDeviceScale(), 96.0f, Screen::IsPseudoDisplay::No); CSSToLayoutDeviceScale(), 96.0f,
Screen::IsPseudoDisplay::No, Screen::IsHDR::No);
screenList.AppendElement(ret.forget()); screenList.AppendElement(ret.forget());
ScreenManager::Refresh(std::move(screenList)); ScreenManager::Refresh(std::move(screenList));
} }

View file

@ -7,9 +7,12 @@
#include "ScreenHelperWin.h" #include "ScreenHelperWin.h"
#include "mozilla/Logging.h" #include "mozilla/Logging.h"
#include "mozilla/gfx/DeviceManagerDx.h"
#include "nsTArray.h" #include "nsTArray.h"
#include "WinUtils.h" #include "WinUtils.h"
#include <dxgi.h>
static mozilla::LazyLogModule sScreenLog("WidgetScreen"); static mozilla::LazyLogModule sScreenLog("WidgetScreen");
namespace mozilla { namespace mozilla {
@ -74,8 +77,14 @@ static void GetDisplayInfo(const char16ptr_t aName,
} }
} }
struct CollectMonitorsParam {
nsTArray<RefPtr<Screen>> screens;
nsTArray<DXGI_OUTPUT_DESC1> outputs;
};
BOOL CALLBACK CollectMonitors(HMONITOR aMon, HDC, LPRECT, LPARAM ioParam) { BOOL CALLBACK CollectMonitors(HMONITOR aMon, HDC, LPRECT, LPARAM ioParam) {
auto screens = reinterpret_cast<nsTArray<RefPtr<Screen>>*>(ioParam); CollectMonitorsParam* cmParam =
reinterpret_cast<CollectMonitorsParam*>(ioParam);
BOOL success = FALSE; BOOL success = FALSE;
MONITORINFOEX info; MONITORINFOEX info;
info.cbSize = sizeof(MONITORINFOEX); info.cbSize = sizeof(MONITORINFOEX);
@ -123,6 +132,37 @@ BOOL CALLBACK CollectMonitors(HMONITOR aMon, HDC, LPRECT, LPARAM ioParam) {
GetDisplayInfo(info.szDevice, orientation, angle, isPseudoDisplay, GetDisplayInfo(info.szDevice, orientation, angle, isPseudoDisplay,
refreshRate); refreshRate);
// Is this an HDR screen? Determine this by enumerating the DeviceManager
// outputs (adapters) and correlating the monitor associated with the
// the output with aMon, the monitor we are considering here.
bool isHDR = false;
for (auto& output : cmParam->outputs) {
if (output.Monitor == aMon) {
// Set isHDR to true if the output has a BT2020 colorspace with EOTF2084
// gamma curve, this indicates the system is sending an HDR format to
// this monitor. The colorspace returned by DXGI is very vague - we only
// see DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 for HDR and
// DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 for SDR modes, even if the
// monitor is using something like YCbCr444 according to Settings
// (System -> Display Settings -> Advanced Display). To get more specific
// info we would need to query the DISPLAYCONFIG values in WinGDI.
//
// Note that we don't check bit depth here, since as of Windows 11 22H2,
// HDR is supported with 8bpc for lower bandwidth, where DWM converts to
// dithered RGB8 rather than RGB10, which doesn't really matter here.
//
// Since RefreshScreens(), the caller of this function, is triggered
// by WM_DISPLAYCHANGE, this will pick up changes to the monitors in
// all the important cases (resolution/color changes by the user).
//
// Further reading:
// https://learn.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range
// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-displayconfig_sdr_white_level
isHDR = (output.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
break;
}
}
MOZ_LOG(sScreenLog, LogLevel::Debug, MOZ_LOG(sScreenLog, LogLevel::Debug,
("New screen [%s (%s) %d %u %f %f %f %d %d %d]", ("New screen [%s (%s) %d %u %f %f %f %d %d %d]",
ToString(rect).c_str(), ToString(availRect).c_str(), pixelDepth, ToString(rect).c_str(), ToString(availRect).c_str(), pixelDepth,
@ -131,12 +171,12 @@ BOOL CALLBACK CollectMonitors(HMONITOR aMon, HDC, LPRECT, LPARAM ioParam) {
auto screen = MakeRefPtr<Screen>( auto screen = MakeRefPtr<Screen>(
rect, availRect, pixelDepth, pixelDepth, refreshRate, contentsScaleFactor, rect, availRect, pixelDepth, pixelDepth, refreshRate, contentsScaleFactor,
defaultCssScaleFactor, dpi, Screen::IsPseudoDisplay(isPseudoDisplay), defaultCssScaleFactor, dpi, Screen::IsPseudoDisplay(isPseudoDisplay),
orientation, angle); Screen::IsHDR(isHDR), orientation, angle);
if (info.dwFlags & MONITORINFOF_PRIMARY) { if (info.dwFlags & MONITORINFOF_PRIMARY) {
// The primary monitor must be the first element of the screen list. // The primary monitor must be the first element of the screen list.
screens->InsertElementAt(0, std::move(screen)); cmParam->screens.InsertElementAt(0, std::move(screen));
} else { } else {
screens->AppendElement(std::move(screen)); cmParam->screens.AppendElement(std::move(screen));
} }
return TRUE; return TRUE;
} }
@ -144,13 +184,17 @@ BOOL CALLBACK CollectMonitors(HMONITOR aMon, HDC, LPRECT, LPARAM ioParam) {
void ScreenHelperWin::RefreshScreens() { void ScreenHelperWin::RefreshScreens() {
MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens")); MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens"));
AutoTArray<RefPtr<Screen>, 4> screens; CollectMonitorsParam cmParam;
if (auto* dx = gfx::DeviceManagerDx::Get()) {
// Get the adapters to pass as an arg to our monitor enumeration callback.
cmParam.outputs = dx->EnumerateOutputs();
}
BOOL result = ::EnumDisplayMonitors( BOOL result = ::EnumDisplayMonitors(
nullptr, nullptr, (MONITORENUMPROC)CollectMonitors, (LPARAM)&screens); nullptr, nullptr, (MONITORENUMPROC)CollectMonitors, (LPARAM)&cmParam);
if (!result) { if (!result) {
NS_WARNING("Unable to EnumDisplayMonitors"); NS_WARNING("Unable to EnumDisplayMonitors");
} }
ScreenManager::Refresh(std::move(screens)); ScreenManager::Refresh(std::move(cmParam.screens));
} }
} // namespace widget } // namespace widget

View file

@ -39,6 +39,7 @@ EXPORTS += [
] ]
EXPORTS.mozilla += [ EXPORTS.mozilla += [
"ScreenHelperWin.h",
"ShellHeaderOnlyUtils.h", "ShellHeaderOnlyUtils.h",
"ToastNotificationHeaderOnlyUtils.h", "ToastNotificationHeaderOnlyUtils.h",
"UrlmonHeaderOnlyUtils.h", "UrlmonHeaderOnlyUtils.h",