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;
uint16_t orientationAngle;
bool isPseudoDisplay;
bool isHDR;
};
struct DimensionInfo

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,6 +9,7 @@
#import <Cocoa/Cocoa.h>
#include "mozilla/Logging.h"
#include "nsCocoaFeatures.h"
#include "nsCocoaUtils.h"
#include "nsObjCExceptions.h"
@ -108,6 +109,13 @@ static already_AddRefed<Screen> MakeScreen(NSScreen* aScreen) {
if (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;
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
// CVDisplayLinkGetNominalOutputVideoRefreshPeriod, but that's a little
// involved. Ideally we could query it from vsync. For now, we leave it out.
RefPtr<Screen> screen = new Screen(rect, availRect, pixelDepth, pixelDepth, 0,
contentsScaleFactor, defaultCssScaleFactor,
dpi, Screen::IsPseudoDisplay::No);
RefPtr<Screen> screen =
new Screen(rect, availRect, pixelDepth, pixelDepth, 0,
contentsScaleFactor, defaultCssScaleFactor, dpi,
Screen::IsPseudoDisplay::No, Screen::IsHDR(isHDR));
return screen.forget();
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);
return MakeAndAddRef<Screen>(rect, availRect, pixelDepth, pixelDepth,
refreshRate, contentsScale, defaultCssScale, dpi,
Screen::IsPseudoDisplay::No);
Screen::IsPseudoDisplay::No, Screen::IsHDR::No);
}
void ScreenGetterGtk::RefreshScreens() {

View file

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

View file

@ -7,9 +7,12 @@
#include "ScreenHelperWin.h"
#include "mozilla/Logging.h"
#include "mozilla/gfx/DeviceManagerDx.h"
#include "nsTArray.h"
#include "WinUtils.h"
#include <dxgi.h>
static mozilla::LazyLogModule sScreenLog("WidgetScreen");
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) {
auto screens = reinterpret_cast<nsTArray<RefPtr<Screen>>*>(ioParam);
CollectMonitorsParam* cmParam =
reinterpret_cast<CollectMonitorsParam*>(ioParam);
BOOL success = FALSE;
MONITORINFOEX info;
info.cbSize = sizeof(MONITORINFOEX);
@ -123,6 +132,37 @@ BOOL CALLBACK CollectMonitors(HMONITOR aMon, HDC, LPRECT, LPARAM ioParam) {
GetDisplayInfo(info.szDevice, orientation, angle, isPseudoDisplay,
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,
("New screen [%s (%s) %d %u %f %f %f %d %d %d]",
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>(
rect, availRect, pixelDepth, pixelDepth, refreshRate, contentsScaleFactor,
defaultCssScaleFactor, dpi, Screen::IsPseudoDisplay(isPseudoDisplay),
orientation, angle);
Screen::IsHDR(isHDR), orientation, angle);
if (info.dwFlags & MONITORINFOF_PRIMARY) {
// 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 {
screens->AppendElement(std::move(screen));
cmParam->screens.AppendElement(std::move(screen));
}
return TRUE;
}
@ -144,13 +184,17 @@ BOOL CALLBACK CollectMonitors(HMONITOR aMon, HDC, LPRECT, LPARAM ioParam) {
void ScreenHelperWin::RefreshScreens() {
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(
nullptr, nullptr, (MONITORENUMPROC)CollectMonitors, (LPARAM)&screens);
nullptr, nullptr, (MONITORENUMPROC)CollectMonitors, (LPARAM)&cmParam);
if (!result) {
NS_WARNING("Unable to EnumDisplayMonitors");
}
ScreenManager::Refresh(std::move(screens));
ScreenManager::Refresh(std::move(cmParam.screens));
}
} // namespace widget

View file

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