diff --git a/dom/ipc/DOMTypes.ipdlh b/dom/ipc/DOMTypes.ipdlh index faee4e861ceb..1be3537e6853 100644 --- a/dom/ipc/DOMTypes.ipdlh +++ b/dom/ipc/DOMTypes.ipdlh @@ -114,6 +114,7 @@ struct ScreenDetails { ScreenOrientation orientation; uint16_t orientationAngle; bool isPseudoDisplay; + bool isHDR; }; struct DimensionInfo diff --git a/gfx/thebes/gfxWindowsPlatform.cpp b/gfx/thebes/gfxWindowsPlatform.cpp index 93de20f4451e..1e343f95845b 100644 --- a/gfx/thebes/gfxWindowsPlatform.cpp +++ b/gfx/thebes/gfxWindowsPlatform.cpp @@ -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(); } diff --git a/widget/Screen.cpp b/widget/Screen.cpp index 71a162462480..ccc78a9413f1 100644 --- a/widget/Screen.cpp +++ b/widget/Screen.cpp @@ -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 diff --git a/widget/Screen.h b/widget/Screen.h index f3f8b4a628af..ca2efa1f5996 100644 --- a/widget/Screen.h +++ b/widget/Screen.h @@ -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 diff --git a/widget/ScreenManager.cpp b/widget/ScreenManager.cpp index 58e20806ebea..84d7316af7e5 100644 --- a/widget/ScreenManager.cpp +++ b/widget/ScreenManager.cpp @@ -139,7 +139,8 @@ already_AddRefed ScreenManager::ScreenForRect( auto screen = MakeRefPtr( 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 ScreenManager::GetPrimaryScreen() { if (mScreenList.IsEmpty()) { MOZ_LOG(sScreenLog, LogLevel::Warning, ("No screen available. This can happen in xpcshell.")); - return MakeAndAddRef( - LayoutDeviceIntRect(), LayoutDeviceIntRect(), 0, 0, 0, - DesktopToLayoutDeviceScale(), CSSToLayoutDeviceScale(), 96 /* dpi */, - Screen::IsPseudoDisplay::No, hal::ScreenOrientation::None, 0); + return MakeAndAddRef(LayoutDeviceIntRect(), LayoutDeviceIntRect(), + 0, 0, 0, DesktopToLayoutDeviceScale(), + CSSToLayoutDeviceScale(), 96 /* dpi */, + Screen::IsPseudoDisplay::No, Screen::IsHDR::No, + hal::ScreenOrientation::None, 0); } return do_AddRef(mScreenList[0]); diff --git a/widget/android/ScreenHelperAndroid.cpp b/widget/android/ScreenHelperAndroid.cpp index 33c91fe6e036..141b38e01b50 100644 --- a/widget/android/ScreenHelperAndroid.cpp +++ b/widget/android/ScreenHelperAndroid.cpp @@ -38,14 +38,15 @@ static already_AddRefed 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(bounds, bounds, depth, depth, refreshRate, - DesktopToLayoutDeviceScale(density), - CSSToLayoutDeviceScale(1.0f), dpi, - Screen::IsPseudoDisplay::No, orientation, angle); + return MakeAndAddRef( + bounds, bounds, depth, depth, refreshRate, + DesktopToLayoutDeviceScale(density), CSSToLayoutDeviceScale(1.0f), dpi, + Screen::IsPseudoDisplay::No, Screen::IsHDR(isHDR), orientation, angle); } ScreenHelperAndroid::ScreenHelperAndroid() { diff --git a/widget/cocoa/ScreenHelperCocoa.mm b/widget/cocoa/ScreenHelperCocoa.mm index 57e1313320c5..96a0275c5c4e 100644 --- a/widget/cocoa/ScreenHelperCocoa.mm +++ b/widget/cocoa/ScreenHelperCocoa.mm @@ -9,6 +9,7 @@ #import #include "mozilla/Logging.h" +#include "nsCocoaFeatures.h" #include "nsCocoaUtils.h" #include "nsObjCExceptions.h" @@ -108,6 +109,13 @@ static already_AddRefed 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 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 = new Screen(rect, availRect, pixelDepth, pixelDepth, 0, - contentsScaleFactor, defaultCssScaleFactor, - dpi, Screen::IsPseudoDisplay::No); + RefPtr 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); diff --git a/widget/gtk/ScreenHelperGTK.cpp b/widget/gtk/ScreenHelperGTK.cpp index 60be234c6029..313bf54b1e79 100644 --- a/widget/gtk/ScreenHelperGTK.cpp +++ b/widget/gtk/ScreenHelperGTK.cpp @@ -269,7 +269,7 @@ static already_AddRefed MakeScreenGtk(GdkScreen* aScreen, contentsScale.scale, defaultCssScale.scale, dpi, refreshRate); return MakeAndAddRef(rect, availRect, pixelDepth, pixelDepth, refreshRate, contentsScale, defaultCssScale, dpi, - Screen::IsPseudoDisplay::No); + Screen::IsPseudoDisplay::No, Screen::IsHDR::No); } void ScreenGetterGtk::RefreshScreens() { diff --git a/widget/headless/HeadlessScreenHelper.cpp b/widget/headless/HeadlessScreenHelper.cpp index 4d8dbfa0d123..5a9643c57288 100644 --- a/widget/headless/HeadlessScreenHelper.cpp +++ b/widget/headless/HeadlessScreenHelper.cpp @@ -32,9 +32,10 @@ LayoutDeviceIntRect HeadlessScreenHelper::GetScreenRect() { HeadlessScreenHelper::HeadlessScreenHelper() { AutoTArray, 1> screenList; LayoutDeviceIntRect rect = GetScreenRect(); - auto ret = MakeRefPtr( - rect, rect, 24, 24, 0, DesktopToLayoutDeviceScale(), - CSSToLayoutDeviceScale(), 96.0f, Screen::IsPseudoDisplay::No); + auto ret = + MakeRefPtr(rect, rect, 24, 24, 0, DesktopToLayoutDeviceScale(), + CSSToLayoutDeviceScale(), 96.0f, + Screen::IsPseudoDisplay::No, Screen::IsHDR::No); screenList.AppendElement(ret.forget()); ScreenManager::Refresh(std::move(screenList)); } diff --git a/widget/windows/ScreenHelperWin.cpp b/widget/windows/ScreenHelperWin.cpp index 8a0ec3b60838..cc6ea58b8502 100644 --- a/widget/windows/ScreenHelperWin.cpp +++ b/widget/windows/ScreenHelperWin.cpp @@ -7,9 +7,12 @@ #include "ScreenHelperWin.h" #include "mozilla/Logging.h" +#include "mozilla/gfx/DeviceManagerDx.h" #include "nsTArray.h" #include "WinUtils.h" +#include + static mozilla::LazyLogModule sScreenLog("WidgetScreen"); namespace mozilla { @@ -74,8 +77,14 @@ static void GetDisplayInfo(const char16ptr_t aName, } } +struct CollectMonitorsParam { + nsTArray> screens; + nsTArray outputs; +}; + BOOL CALLBACK CollectMonitors(HMONITOR aMon, HDC, LPRECT, LPARAM ioParam) { - auto screens = reinterpret_cast>*>(ioParam); + CollectMonitorsParam* cmParam = + reinterpret_cast(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( 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, 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 diff --git a/widget/windows/moz.build b/widget/windows/moz.build index f19a46caf13c..eeda8d12577a 100644 --- a/widget/windows/moz.build +++ b/widget/windows/moz.build @@ -39,6 +39,7 @@ EXPORTS += [ ] EXPORTS.mozilla += [ + "ScreenHelperWin.h", "ShellHeaderOnlyUtils.h", "ToastNotificationHeaderOnlyUtils.h", "UrlmonHeaderOnlyUtils.h",