gfxPlatformGtk.cpp (33217B)
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #define PANGO_ENABLE_BACKEND 7 #define PANGO_ENABLE_ENGINE 8 9 #include "gfxPlatformGtk.h" 10 11 #include <gtk/gtk.h> 12 #include <fontconfig/fontconfig.h> 13 14 #include "base/task.h" 15 #include "base/thread.h" 16 #include "base/message_loop.h" 17 #include "cairo.h" 18 #include "gfx2DGlue.h" 19 #include "gfxFcPlatformFontList.h" 20 #include "gfxConfig.h" 21 #include "gfxContext.h" 22 #include "gfxImageSurface.h" 23 #include "gfxUserFontSet.h" 24 #include "gfxUtils.h" 25 #include "gfxFT2FontBase.h" 26 #include "gfxTextRun.h" 27 #include "GLContextProvider.h" 28 #include "mozilla/Components.h" 29 #include "mozilla/dom/ContentChild.h" 30 #include "mozilla/FontPropertyTypes.h" 31 #include "mozilla/gfx/2D.h" 32 #include "mozilla/gfx/Logging.h" 33 #include "mozilla/Monitor.h" 34 #include "mozilla/Preferences.h" 35 #include "mozilla/StaticPrefs_gfx.h" 36 #include "mozilla/StaticPrefs_layers.h" 37 #include "mozilla/StaticPrefs_media.h" 38 #include "nsAppRunner.h" 39 #include "nsIGfxInfo.h" 40 #include "nsMathUtils.h" 41 #include "nsUnicharUtils.h" 42 #include "nsUnicodeProperties.h" 43 #include "prenv.h" 44 #include "VsyncSource.h" 45 #include "mozilla/WidgetUtilsGtk.h" 46 47 #ifdef MOZ_X11 48 # include "mozilla/gfx/XlibDisplay.h" 49 # include <gdk/gdkx.h> 50 # include <X11/extensions/Xrandr.h> 51 # include "cairo-xlib.h" 52 # include "gfxXlibSurface.h" 53 # include "GLContextGLX.h" 54 # include "GLXLibrary.h" 55 # include "mozilla/X11Util.h" 56 # include "SoftwareVsyncSource.h" 57 58 /* Undefine the Status from Xlib since it will conflict with system headers on 59 * OSX */ 60 # if defined(__APPLE__) && defined(Status) 61 # undef Status 62 # endif 63 #endif /* MOZ_X11 */ 64 65 #ifdef MOZ_WAYLAND 66 # include <gdk/gdkwayland.h> 67 # include "mozilla/widget/nsWaylandDisplay.h" 68 #endif 69 #ifdef MOZ_WIDGET_GTK 70 # include "mozilla/widget/DMABufDevice.h" 71 # include "mozilla/StaticPrefs_widget.h" 72 #endif 73 74 #define GDK_PIXMAP_SIZE_MAX 32767 75 76 #define GFX_PREF_MAX_GENERIC_SUBSTITUTIONS \ 77 "gfx.font_rendering.fontconfig.max_generic_substitutions" 78 79 using namespace mozilla; 80 using namespace mozilla::gfx; 81 using namespace mozilla::unicode; 82 using namespace mozilla::widget; 83 84 static FT_Library gPlatformFTLibrary = nullptr; 85 86 #if defined(MOZ_X11) 87 // TODO(aosmond): The envvar is deprecated. We should remove it once EGL is the 88 // default in release. 89 static bool IsX11EGLEnvvarEnabled() { 90 const char* eglPref = PR_GetEnv("MOZ_X11_EGL"); 91 return (eglPref && *eglPref); 92 } 93 #endif 94 95 gfxPlatformGtk::gfxPlatformGtk() { 96 if (!gfxPlatform::IsHeadless()) { 97 if (!gtk_init_check(nullptr, nullptr)) { 98 gfxCriticalNote << "Failed to init Gtk, missing display? DISPLAY=" 99 << getenv("DISPLAY") 100 << " WAYLAND_DISPLAY=" << getenv("WAYLAND_DISPLAY") 101 << "\n"; 102 abort(); 103 } 104 } 105 106 mIsX11Display = gfxPlatform::IsHeadless() ? false : GdkIsX11Display(); 107 if (XRE_IsParentProcess()) { 108 InitX11EGLConfig(); 109 if (IsWaylandDisplay() || gfxConfig::IsEnabled(Feature::X11_EGL)) { 110 gfxVars::SetUseEGL(true); 111 } 112 InitDmabufConfig(); 113 if (gfxConfig::IsEnabled(Feature::DMABUF)) { 114 gfxVars::SetUseDMABuf(true); 115 } 116 } 117 118 InitBackendPrefs(GetBackendPrefs()); 119 120 gPlatformFTLibrary = Factory::NewFTLibrary(); 121 MOZ_RELEASE_ASSERT(gPlatformFTLibrary); 122 Factory::SetFTLibrary(gPlatformFTLibrary); 123 124 // Bug 1714483: Force disable FXAA Antialiasing on NV drivers. This is a 125 // temporary workaround for a driver bug. 126 PR_SetEnv("__GL_ALLOW_FXAA_USAGE=0"); 127 128 InitMesaThreading(); 129 } 130 131 gfxPlatformGtk::~gfxPlatformGtk() { 132 Factory::ReleaseFTLibrary(gPlatformFTLibrary); 133 gPlatformFTLibrary = nullptr; 134 } 135 136 void gfxPlatformGtk::InitAcceleration() { 137 gfxPlatform::InitAcceleration(); 138 139 if (XRE_IsContentProcess()) { 140 ImportCachedContentDeviceData(); 141 } 142 } 143 144 void gfxPlatformGtk::InitX11EGLConfig() { 145 FeatureState& feature = gfxConfig::GetFeature(Feature::X11_EGL); 146 #ifdef MOZ_X11 147 feature.EnableByDefault(); 148 149 if (StaticPrefs::gfx_x11_egl_force_enabled_AtStartup()) { 150 feature.UserForceEnable("Force enabled by pref"); 151 } else if (IsX11EGLEnvvarEnabled()) { 152 feature.UserForceEnable("Force enabled by envvar"); 153 } else if (StaticPrefs::gfx_x11_egl_force_disabled_AtStartup()) { 154 feature.UserDisable("Force disabled by pref", 155 "FEATURE_FAILURE_USER_FORCE_DISABLED"_ns); 156 } 157 158 nsCString failureId; 159 int32_t status; 160 nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); 161 if (NS_FAILED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_X11_EGL, 162 failureId, &status))) { 163 feature.Disable(FeatureStatus::BlockedNoGfxInfo, "gfxInfo is broken", 164 "FEATURE_FAILURE_NO_GFX_INFO"_ns); 165 } else if (status != nsIGfxInfo::FEATURE_STATUS_OK) { 166 feature.Disable(FeatureStatus::Blocklisted, "Blocklisted by gfxInfo", 167 failureId); 168 } 169 170 nsAutoString testType; 171 gfxInfo->GetTestType(testType); 172 // We can only use X11/EGL if we actually found the EGL library and 173 // successfully use it to determine system information in glxtest. 174 if (testType != u"EGL") { 175 feature.ForceDisable(FeatureStatus::Broken, "glxtest could not use EGL", 176 "FEATURE_FAILURE_GLXTEST_NO_EGL"_ns); 177 } 178 #else 179 feature.DisableByDefault(FeatureStatus::Unavailable, "X11 support missing", 180 "FEATURE_FAILURE_NO_X11"_ns); 181 #endif 182 } 183 184 void gfxPlatformGtk::InitDmabufConfig() { 185 FeatureState& feature = gfxConfig::GetFeature(Feature::DMABUF); 186 feature.EnableByDefault(); 187 188 nsCString failureId; 189 int32_t status; 190 nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); 191 if (NS_FAILED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DMABUF, failureId, 192 &status))) { 193 feature.Disable(FeatureStatus::BlockedNoGfxInfo, "gfxInfo is broken", 194 "FEATURE_FAILURE_NO_GFX_INFO"_ns); 195 } else if (status != nsIGfxInfo::FEATURE_STATUS_OK) { 196 feature.Disable(FeatureStatus::Blocklisted, "Blocklisted by gfxInfo", 197 failureId); 198 } 199 200 if (StaticPrefs::widget_dmabuf_force_enabled_AtStartup()) { 201 feature.UserForceEnable("Force enabled by pref"); 202 } else if (!StaticPrefs::widget_dmabuf_enabled_AtStartup()) { 203 feature.UserDisable("Force disable by pref", 204 "FEATURE_FAILURE_USER_FORCE_DISABLED"_ns); 205 } 206 207 if (!gfxVars::UseEGL()) { 208 feature.ForceDisable(FeatureStatus::Unavailable, "Requires EGL", 209 "FEATURE_FAILURE_REQUIRES_EGL"_ns); 210 } 211 212 if (!gfxVars::WebglUseHardware()) { 213 feature.Disable(FeatureStatus::Blocklisted, 214 "DMABuf disabled with software rendering", failureId); 215 } 216 217 nsAutoCString drmRenderDevice; 218 gfxInfo->GetDrmRenderDevice(drmRenderDevice); 219 gfxVars::SetDrmRenderDevice(drmRenderDevice); 220 221 if (feature.IsEnabled()) { 222 DMABufDeviceLock device; 223 if (!device.GetDMABufDevice()->IsEnabled(failureId)) { 224 feature.ForceDisable(FeatureStatus::Failed, "Failed to configure", 225 failureId); 226 } 227 // Make sure we have DMABuf formats available. 228 (void)GetGlobalDMABufFormats(); 229 } 230 } 231 232 void gfxPlatformGtk::InitPlatformHardwareVideoConfig() { 233 FeatureState& featureDec = 234 gfxConfig::GetFeature(Feature::HARDWARE_VIDEO_DECODING); 235 if (!gfxVars::UseEGL()) { 236 featureDec.ForceDisable(FeatureStatus::Unavailable, "Requires EGL", 237 "FEATURE_FAILURE_REQUIRES_EGL"_ns); 238 gfxConfig::ForceDisable(Feature::HARDWARE_VIDEO_ENCODING, 239 FeatureStatus::Unavailable, "Requires EGL", 240 "FEATURE_FAILURE_REQUIRES_EGL"_ns); 241 } 242 243 if (!featureDec.IsEnabled()) { 244 return; 245 } 246 247 // Configure zero-copy playback feature. 248 FeatureState& featureZeroCopy = 249 gfxConfig::GetFeature(Feature::HW_DECODED_VIDEO_ZERO_COPY); 250 251 featureZeroCopy.EnableByDefault(); 252 uint32_t state = 253 StaticPrefs::media_ffmpeg_vaapi_force_surface_zero_copy_AtStartup(); 254 if (state == 0) { 255 featureZeroCopy.UserDisable("Force disable by pref", 256 "FEATURE_FAILURE_USER_FORCE_DISABLED"_ns); 257 } else if (state == 1) { 258 featureZeroCopy.UserEnable("Force enabled by pref"); 259 } else { 260 nsCString failureId; 261 int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN; 262 nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); 263 if (NS_FAILED(gfxInfo->GetFeatureStatus( 264 nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY, failureId, 265 &status))) { 266 featureZeroCopy.Disable(FeatureStatus::BlockedNoGfxInfo, 267 "gfxInfo is broken", 268 "FEATURE_FAILURE_NO_GFX_INFO"_ns); 269 } else if (status == nsIGfxInfo::FEATURE_BLOCKED_PLATFORM_TEST) { 270 featureZeroCopy.ForceDisable(FeatureStatus::Unavailable, 271 "Force disabled by gfxInfo", failureId); 272 } else if (status != nsIGfxInfo::FEATURE_ALLOW_ALWAYS) { 273 featureZeroCopy.Disable(FeatureStatus::Blocklisted, 274 "Blocklisted by gfxInfo", failureId); 275 } 276 } 277 if (featureZeroCopy.IsEnabled()) { 278 gfxVars::SetHwDecodedVideoZeroCopy(true); 279 } 280 } 281 282 void gfxPlatformGtk::InitWebRenderConfig() { 283 gfxPlatform::InitWebRenderConfig(); 284 285 if (!XRE_IsParentProcess()) { 286 return; 287 } 288 289 FeatureState& feature = gfxConfig::GetFeature(Feature::WEBRENDER_COMPOSITOR); 290 // HDR requires compositor to work 291 #if defined(MOZ_WAYLAND) 292 if (feature.IsEnabled()) { 293 if (!IsWaylandDisplay()) { 294 feature.ForceDisable(FeatureStatus::Unavailable, 295 "Wayland support missing", 296 "FEATURE_FAILURE_NO_WAYLAND"_ns); 297 } else if (gfxConfig::IsEnabled(Feature::WEBRENDER) && 298 !gfxConfig::IsEnabled(Feature::DMABUF)) { 299 // We use zwp_linux_dmabuf_v1 and GBM directly to manage FBOs. In theory 300 // this is also possible vie EGLstreams, but we don't bother to implement 301 // it as recent NVidia drivers support GBM and DMABuf as well. 302 feature.ForceDisable(FeatureStatus::Unavailable, 303 "Hardware Webrender requires DMAbuf support", 304 "FEATURE_FAILURE_NO_DMABUF"_ns); 305 } else if (!widget::WaylandDisplayGet()->GetViewporter()) { 306 feature.ForceDisable(FeatureStatus::Unavailable, 307 "Requires wp_viewporter protocol support", 308 "FEATURE_FAILURE_REQUIRES_WPVIEWPORTER"_ns); 309 } 310 } 311 #else // MOZ_WAYLAND 312 feature.ForceDisable(FeatureStatus::Unavailable, "Not available on X11", 313 "FEATURE_FAILURE_NO_WAYLAND"_ns); 314 #endif 315 316 gfxVars::SetUseWebRenderCompositor(feature.IsEnabled()); 317 } 318 319 void gfxPlatformGtk::InitPlatformGPUProcessPrefs() { 320 #ifdef MOZ_WAYLAND 321 if (IsWaylandDisplay()) { 322 FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS); 323 gpuProc.ForceDisable(FeatureStatus::Blocked, 324 "Wayland does not work in the GPU process", 325 "FEATURE_FAILURE_WAYLAND"_ns); 326 } 327 #endif 328 } 329 330 void gfxPlatformGtk::InitMesaThreading() { 331 FeatureState& featureMesaThreading = 332 gfxConfig::GetFeature(Feature::MESA_THREADING); 333 featureMesaThreading.EnableByDefault(); 334 335 nsCString failureId; 336 int32_t status; 337 nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); 338 if (NS_FAILED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_MESA_THREADING, 339 failureId, &status))) { 340 featureMesaThreading.Disable(FeatureStatus::BlockedNoGfxInfo, 341 "gfxInfo is broken", 342 "FEATURE_FAILURE_NO_GFX_INFO"_ns); 343 } else if (status != nsIGfxInfo::FEATURE_STATUS_OK) { 344 featureMesaThreading.Disable(FeatureStatus::Blocklisted, 345 "Blocklisted by gfxInfo", failureId); 346 } 347 348 // Enabling glthread crashes on X11/EGL, see bug 1670545 349 if (gfxConfig::IsEnabled(Feature::X11_EGL) && IsX11Display()) { 350 featureMesaThreading.Disable(FeatureStatus::Failed, 351 "No glthread with EGL and X11", 352 "FEATURE_FAILURE_EGL_X11"_ns); 353 } 354 355 if (!featureMesaThreading.IsEnabled()) { 356 PR_SetEnv("mesa_glthread=false"); 357 } 358 } 359 360 already_AddRefed<gfxASurface> gfxPlatformGtk::CreateOffscreenSurface( 361 const IntSize& aSize, gfxImageFormat aFormat) { 362 if (!Factory::AllowedSurfaceSize(aSize)) { 363 return nullptr; 364 } 365 366 RefPtr<gfxASurface> newSurface; 367 bool needsClear = true; 368 // XXX we really need a different interface here, something that passes 369 // in more context, including the display and/or target surface type that 370 // we should try to match 371 GdkScreen* gdkScreen = gdk_screen_get_default(); 372 if (gdkScreen) { 373 newSurface = new gfxImageSurface(aSize, aFormat); 374 // The gfxImageSurface ctor zeroes this for us, no need to 375 // waste time clearing again 376 needsClear = false; 377 } 378 379 if (!newSurface) { 380 // We couldn't create a native surface for whatever reason; 381 // e.g., no display, no RENDER, bad size, etc. 382 // Fall back to image surface for the data. 383 newSurface = new gfxImageSurface(aSize, aFormat); 384 } 385 386 if (newSurface->CairoStatus()) { 387 newSurface = nullptr; // surface isn't valid for some reason 388 } 389 390 if (newSurface && needsClear) { 391 gfxUtils::ClearThebesSurface(newSurface); 392 } 393 394 return newSurface.forget(); 395 } 396 397 nsresult gfxPlatformGtk::GetFontList(nsAtom* aLangGroup, 398 const nsACString& aGenericFamily, 399 nsTArray<nsString>& aListOfFonts) { 400 gfxPlatformFontList::PlatformFontList()->GetFontList( 401 aLangGroup, aGenericFamily, aListOfFonts); 402 return NS_OK; 403 } 404 405 // xxx - this is ubuntu centric, need to go through other distros and flesh 406 // out a more general list 407 static const char kFontDejaVuSans[] = "DejaVu Sans"; 408 static const char kFontDejaVuSerif[] = "DejaVu Serif"; 409 static const char kFontFreeSans[] = "FreeSans"; 410 static const char kFontFreeSerif[] = "FreeSerif"; 411 static const char kFontTakaoPGothic[] = "TakaoPGothic"; 412 static const char kFontTwemojiMozilla[] = "Twemoji Mozilla"; 413 static const char kFontDroidSansFallback[] = "Droid Sans Fallback"; 414 static const char kFontWenQuanYiMicroHei[] = "WenQuanYi Micro Hei"; 415 static const char kFontNanumGothic[] = "NanumGothic"; 416 static const char kFontSymbola[] = "Symbola"; 417 static const char kFontNotoSansSymbols[] = "Noto Sans Symbols"; 418 static const char kFontNotoSansSymbols2[] = "Noto Sans Symbols2"; 419 420 void gfxPlatformGtk::GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, 421 FontPresentation aPresentation, 422 nsTArray<const char*>& aFontList) { 423 if (PrefersColor(aPresentation)) { 424 aFontList.AppendElement(kFontTwemojiMozilla); 425 } 426 427 aFontList.AppendElement(kFontDejaVuSerif); 428 aFontList.AppendElement(kFontFreeSerif); 429 aFontList.AppendElement(kFontDejaVuSans); 430 aFontList.AppendElement(kFontFreeSans); 431 aFontList.AppendElement(kFontSymbola); 432 aFontList.AppendElement(kFontNotoSansSymbols); 433 aFontList.AppendElement(kFontNotoSansSymbols2); 434 435 // add fonts for CJK ranges 436 // xxx - this isn't really correct, should use the same CJK font ordering 437 // as the pref font code 438 if (aCh >= 0x3000 && ((aCh < 0xe000) || (aCh >= 0xf900 && aCh < 0xfff0) || 439 ((aCh >> 16) == 2))) { 440 aFontList.AppendElement(kFontTakaoPGothic); 441 aFontList.AppendElement(kFontDroidSansFallback); 442 aFontList.AppendElement(kFontWenQuanYiMicroHei); 443 aFontList.AppendElement(kFontNanumGothic); 444 } 445 } 446 447 void gfxPlatformGtk::ReadSystemFontList( 448 mozilla::dom::SystemFontList* retValue) { 449 gfxFcPlatformFontList::PlatformFontList()->ReadSystemFontList(retValue); 450 } 451 452 bool gfxPlatformGtk::CreatePlatformFontList() { 453 return gfxPlatformFontList::Initialize(new gfxFcPlatformFontList); 454 } 455 456 gfxImageFormat gfxPlatformGtk::GetOffscreenFormat() { 457 // Make sure there is a screen 458 GdkScreen* screen = gdk_screen_get_default(); 459 if (screen && gdk_visual_get_depth(gdk_visual_get_system()) == 16) { 460 return SurfaceFormat::R5G6B5_UINT16; 461 } 462 463 return SurfaceFormat::X8R8G8B8_UINT32; 464 } 465 466 void gfxPlatformGtk::FontsPrefsChanged(const char* aPref) { 467 // only checking for generic substitions, pass other changes up 468 if (strcmp(GFX_PREF_MAX_GENERIC_SUBSTITUTIONS, aPref) != 0) { 469 gfxPlatform::FontsPrefsChanged(aPref); 470 return; 471 } 472 473 gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList(); 474 pfl->ClearGenericMappings(); 475 FlushFontAndWordCaches(); 476 } 477 478 bool gfxPlatformGtk::AccelerateLayersByDefault() { return true; } 479 480 #if defined(MOZ_X11) 481 482 static nsTArray<uint8_t> GetDisplayICCProfile(Display* dpy, Window& root) { 483 const char kIccProfileAtomName[] = "_ICC_PROFILE"; 484 Atom iccAtom = XInternAtom(dpy, kIccProfileAtomName, TRUE); 485 if (!iccAtom) { 486 return nsTArray<uint8_t>(); 487 } 488 489 Atom retAtom; 490 int retFormat; 491 unsigned long retLength, retAfter; 492 unsigned char* retProperty; 493 494 if (XGetWindowProperty(dpy, root, iccAtom, 0, INT_MAX /* length */, X11False, 495 AnyPropertyType, &retAtom, &retFormat, &retLength, 496 &retAfter, &retProperty) != Success) { 497 return nsTArray<uint8_t>(); 498 } 499 500 nsTArray<uint8_t> result; 501 502 if (retLength > 0) { 503 result.AppendElements(static_cast<uint8_t*>(retProperty), retLength); 504 } 505 506 XFree(retProperty); 507 508 return result; 509 } 510 511 nsTArray<uint8_t> gfxPlatformGtk::GetPlatformCMSOutputProfileData() { 512 nsTArray<uint8_t> prefProfileData = GetPrefCMSOutputProfileData(); 513 if (!prefProfileData.IsEmpty()) { 514 return prefProfileData; 515 } 516 517 if (XRE_IsContentProcess()) { 518 auto& cmsOutputProfileData = GetCMSOutputProfileData(); 519 // We should have set our profile data when we received our initial 520 // ContentDeviceData. 521 MOZ_ASSERT(cmsOutputProfileData.isSome(), 522 "Should have created output profile data when we received " 523 "initial content device data."); 524 if (cmsOutputProfileData.isSome()) { 525 return cmsOutputProfileData.ref().Clone(); 526 } 527 return nsTArray<uint8_t>(); 528 } 529 530 if (!mIsX11Display) { 531 return nsTArray<uint8_t>(); 532 } 533 534 GdkDisplay* display = gdk_display_get_default(); 535 Display* dpy = GDK_DISPLAY_XDISPLAY(display); 536 // In xpcshell tests, we never initialize X and hence don't have a Display. 537 // In this case, there's no output colour management to be done, so we just 538 // return with nullptr. 539 if (!dpy) { 540 return nsTArray<uint8_t>(); 541 } 542 543 Window root = gdk_x11_get_default_root_xwindow(); 544 545 // First try ICC Profile 546 nsTArray<uint8_t> iccResult = GetDisplayICCProfile(dpy, root); 547 if (!iccResult.IsEmpty()) { 548 return iccResult; 549 } 550 551 // If ICC doesn't work, then try EDID 552 const char kEdid1AtomName[] = "XFree86_DDC_EDID1_RAWDATA"; 553 Atom edidAtom = XInternAtom(dpy, kEdid1AtomName, TRUE); 554 if (!edidAtom) { 555 return nsTArray<uint8_t>(); 556 } 557 558 Atom retAtom; 559 int retFormat; 560 unsigned long retLength, retAfter; 561 unsigned char* retProperty; 562 563 if (XGetWindowProperty(dpy, root, edidAtom, 0, 32, X11False, AnyPropertyType, 564 &retAtom, &retFormat, &retLength, &retAfter, 565 &retProperty) != Success) { 566 return nsTArray<uint8_t>(); 567 } 568 569 if (retLength != 128) { 570 return nsTArray<uint8_t>(); 571 } 572 573 // Format documented in "VESA E-EDID Implementation Guide" 574 float gamma = (100 + (float)retProperty[0x17]) / 100.0f; 575 576 qcms_CIE_xyY whitePoint; 577 whitePoint.x = 578 ((retProperty[0x21] << 2) | (retProperty[0x1a] >> 2 & 3)) / 1024.0; 579 whitePoint.y = 580 ((retProperty[0x22] << 2) | (retProperty[0x1a] >> 0 & 3)) / 1024.0; 581 whitePoint.Y = 1.0; 582 583 qcms_CIE_xyYTRIPLE primaries; 584 primaries.red.x = 585 ((retProperty[0x1b] << 2) | (retProperty[0x19] >> 6 & 3)) / 1024.0; 586 primaries.red.y = 587 ((retProperty[0x1c] << 2) | (retProperty[0x19] >> 4 & 3)) / 1024.0; 588 primaries.red.Y = 1.0; 589 590 primaries.green.x = 591 ((retProperty[0x1d] << 2) | (retProperty[0x19] >> 2 & 3)) / 1024.0; 592 primaries.green.y = 593 ((retProperty[0x1e] << 2) | (retProperty[0x19] >> 0 & 3)) / 1024.0; 594 primaries.green.Y = 1.0; 595 596 primaries.blue.x = 597 ((retProperty[0x1f] << 2) | (retProperty[0x1a] >> 6 & 3)) / 1024.0; 598 primaries.blue.y = 599 ((retProperty[0x20] << 2) | (retProperty[0x1a] >> 4 & 3)) / 1024.0; 600 primaries.blue.Y = 1.0; 601 602 XFree(retProperty); 603 604 void* mem = nullptr; 605 size_t size = 0; 606 qcms_data_create_rgb_with_gamma(whitePoint, primaries, gamma, &mem, &size); 607 if (!mem) { 608 return nsTArray<uint8_t>(); 609 } 610 611 nsTArray<uint8_t> result; 612 result.AppendElements(static_cast<uint8_t*>(mem), size); 613 free(mem); 614 615 // XXX: It seems like we get wrong colors when using this constructed profile: 616 // See bug 1696819. For now just forget that we made it. 617 return nsTArray<uint8_t>(); 618 } 619 620 #else // defined(MOZ_X11) 621 622 nsTArray<uint8_t> gfxPlatformGtk::GetPlatformCMSOutputProfileData() { 623 return nsTArray<uint8_t>(); 624 } 625 626 #endif 627 628 bool gfxPlatformGtk::CheckVariationFontSupport() { 629 // Although there was some variation/multiple-master support in FreeType 630 // in older versions, it seems too incomplete/unstable for us to use 631 // until at least 2.7.1. 632 FT_Int major, minor, patch; 633 FT_Library_Version(Factory::GetFTLibrary(), &major, &minor, &patch); 634 return major * 1000000 + minor * 1000 + patch >= 2007001; 635 } 636 637 #ifdef MOZ_X11 638 639 class GtkVsyncSource final : public VsyncSource { 640 public: 641 GtkVsyncSource() 642 : mGLContext(nullptr), 643 mXDisplay(nullptr), 644 mSetupLock("GLXVsyncSetupLock"), 645 mVsyncThread("GLXVsyncThread"), 646 mVsyncTask(nullptr), 647 mVsyncEnabledLock("GLXVsyncEnabledLock"), 648 mVsyncEnabled(false) { 649 MOZ_ASSERT(NS_IsMainThread()); 650 } 651 652 virtual ~GtkVsyncSource() { MOZ_ASSERT(NS_IsMainThread()); } 653 654 // Sets up the display's GL context on a worker thread. 655 // Required as GLContexts may only be used by the creating thread. 656 // Returns true if setup was a success. 657 bool Setup() { 658 MonitorAutoLock lock(mSetupLock); 659 MOZ_ASSERT(NS_IsMainThread()); 660 if (!mVsyncThread.Start()) return false; 661 662 RefPtr<Runnable> vsyncSetup = 663 NewRunnableMethod("GtkVsyncSource::SetupGLContext", this, 664 &GtkVsyncSource::SetupGLContext); 665 mVsyncThread.message_loop()->PostTask(vsyncSetup.forget()); 666 // Wait until the setup has completed. 667 lock.Wait(); 668 return mGLContext != nullptr; 669 } 670 671 // Called on the Vsync thread to setup the GL context. 672 void SetupGLContext() { 673 MonitorAutoLock lock(mSetupLock); 674 MOZ_ASSERT(!NS_IsMainThread()); 675 MOZ_ASSERT(!mGLContext, "GLContext already setup!"); 676 677 // Create video sync timer on a separate Display to prevent locking the 678 // main thread X display. 679 mXDisplay = XOpenDisplay(nullptr); 680 if (!mXDisplay) { 681 lock.NotifyAll(); 682 return; 683 } 684 685 // Most compositors wait for vsync events on the root window. 686 Window root = DefaultRootWindow(mXDisplay); 687 int screen = DefaultScreen(mXDisplay); 688 689 GLXFBConfig config; 690 int visid; 691 bool forWebRender = false; 692 if (!gl::GLContextGLX::FindFBConfigForWindow( 693 mXDisplay, screen, root, &config, &visid, forWebRender)) { 694 lock.NotifyAll(); 695 return; 696 } 697 698 mGLContext = gl::GLContextGLX::CreateGLContext( 699 {}, gfx::XlibDisplay::Borrow(mXDisplay), root, config); 700 701 if (!mGLContext) { 702 lock.NotifyAll(); 703 return; 704 } 705 706 mGLContext->MakeCurrent(); 707 708 // Test that SGI_video_sync lets us get the counter. 709 unsigned int syncCounter = 0; 710 if (gl::sGLXLibrary.fGetVideoSync(&syncCounter) != 0) { 711 mGLContext = nullptr; 712 } 713 714 lock.NotifyAll(); 715 } 716 717 virtual void EnableVsync() override { 718 MOZ_ASSERT(NS_IsMainThread()); 719 MOZ_ASSERT(mGLContext, "GLContext not setup!"); 720 721 MonitorAutoLock lock(mVsyncEnabledLock); 722 if (mVsyncEnabled) { 723 return; 724 } 725 mVsyncEnabled = true; 726 727 // If the task has not nulled itself out, it hasn't yet realized 728 // that vsync was disabled earlier, so continue its execution. 729 if (!mVsyncTask) { 730 mVsyncTask = NewRunnableMethod("GtkVsyncSource::RunVsync", this, 731 &GtkVsyncSource::RunVsync); 732 RefPtr<Runnable> addrefedTask = mVsyncTask; 733 mVsyncThread.message_loop()->PostTask(addrefedTask.forget()); 734 } 735 } 736 737 virtual void DisableVsync() override { 738 MonitorAutoLock lock(mVsyncEnabledLock); 739 mVsyncEnabled = false; 740 } 741 742 virtual bool IsVsyncEnabled() override { 743 MonitorAutoLock lock(mVsyncEnabledLock); 744 return mVsyncEnabled; 745 } 746 747 virtual void Shutdown() override { 748 MOZ_ASSERT(NS_IsMainThread()); 749 DisableVsync(); 750 751 // Cleanup thread-specific resources before shutting down. 752 RefPtr<Runnable> shutdownTask = NewRunnableMethod( 753 "GtkVsyncSource::Cleanup", this, &GtkVsyncSource::Cleanup); 754 mVsyncThread.message_loop()->PostTask(shutdownTask.forget()); 755 756 // Stop, waiting for the cleanup task to finish execution. 757 mVsyncThread.Stop(); 758 } 759 760 private: 761 void RunVsync() { 762 MOZ_ASSERT(!NS_IsMainThread()); 763 764 mGLContext->MakeCurrent(); 765 766 unsigned int syncCounter = 0; 767 gl::sGLXLibrary.fGetVideoSync(&syncCounter); 768 for (;;) { 769 { 770 MonitorAutoLock lock(mVsyncEnabledLock); 771 if (!mVsyncEnabled) { 772 mVsyncTask = nullptr; 773 return; 774 } 775 } 776 777 TimeStamp lastVsync = TimeStamp::Now(); 778 bool useSoftware = false; 779 780 // Wait until the video sync counter reaches the next value by waiting 781 // until the parity of the counter value changes. 782 unsigned int nextSync = syncCounter + 1; 783 int status; 784 if ((status = gl::sGLXLibrary.fWaitVideoSync(2, (int)nextSync % 2, 785 &syncCounter)) != 0) { 786 gfxWarningOnce() << "glXWaitVideoSync returned " << status; 787 useSoftware = true; 788 } 789 790 if (syncCounter == (nextSync - 1)) { 791 gfxWarningOnce() 792 << "glXWaitVideoSync failed to increment the sync counter."; 793 useSoftware = true; 794 } 795 796 if (useSoftware) { 797 double remaining = 798 (1000.f / 60.f) - (TimeStamp::Now() - lastVsync).ToMilliseconds(); 799 if (remaining > 0) { 800 AUTO_PROFILER_THREAD_SLEEP; 801 PlatformThread::Sleep((int)remaining); 802 } 803 } 804 805 lastVsync = TimeStamp::Now(); 806 TimeStamp outputTime = lastVsync + GetVsyncRate(); 807 NotifyVsync(lastVsync, outputTime); 808 } 809 } 810 811 void Cleanup() { 812 MOZ_ASSERT(!NS_IsMainThread()); 813 814 mGLContext = nullptr; 815 if (mXDisplay) XCloseDisplay(mXDisplay); 816 } 817 818 // Owned by the vsync thread. 819 RefPtr<gl::GLContextGLX> mGLContext; 820 _XDisplay* mXDisplay; 821 Monitor mSetupLock MOZ_UNANNOTATED; 822 base::Thread mVsyncThread; 823 RefPtr<Runnable> mVsyncTask; 824 Monitor mVsyncEnabledLock MOZ_UNANNOTATED; 825 bool mVsyncEnabled; 826 }; 827 828 class XrandrSoftwareVsyncSource final 829 : public mozilla::gfx::SoftwareVsyncSource { 830 public: 831 XrandrSoftwareVsyncSource() : SoftwareVsyncSource(ComputeVsyncRate()) { 832 MOZ_ASSERT(NS_IsMainThread()); 833 834 GdkScreen* defaultScreen = gdk_screen_get_default(); 835 g_signal_connect(defaultScreen, "monitors-changed", 836 G_CALLBACK(monitors_changed), this); 837 } 838 839 private: 840 // Request the current refresh rate via xrandr. It is hard to find the 841 // "correct" one, thus choose the highest one, assuming this will usually 842 // give the best user experience. 843 static mozilla::TimeDuration ComputeVsyncRate() { 844 struct _XDisplay* dpy = gdk_x11_get_default_xdisplay(); 845 846 // Use the default software refresh rate as lower bound. Allowing lower 847 // rates makes a bunch of tests start to fail on CI. The main goal of this 848 // VsyncSource is to support refresh rates greater than the default one. 849 double highestRefreshRate = gfxPlatform::GetSoftwareVsyncRate(); 850 851 // When running on remote X11 the xrandr version may be stuck on an 852 // ancient version. There are still setups using remote X11 out there, so 853 // make sure we don't crash. 854 int eventBase, errorBase, major, minor; 855 if (XRRQueryExtension(dpy, &eventBase, &errorBase) && 856 XRRQueryVersion(dpy, &major, &minor) && 857 (major > 1 || (major == 1 && minor >= 3))) { 858 Window root = gdk_x11_get_default_root_xwindow(); 859 XRRScreenResources* res = XRRGetScreenResourcesCurrent(dpy, root); 860 861 if (res) { 862 // We can't use refresh rates far below the default one (60Hz) because 863 // otherwise random CI tests start to fail. However, many users have 864 // screens just below the default rate, e.g. 59.95Hz. So slightly 865 // decrease the lower bound. 866 highestRefreshRate -= 1.0; 867 868 for (int i = 0; i < res->noutput; i++) { 869 XRROutputInfo* outputInfo = 870 XRRGetOutputInfo(dpy, res, res->outputs[i]); 871 if (outputInfo) { 872 if (outputInfo->crtc) { 873 XRRCrtcInfo* crtcInfo = 874 XRRGetCrtcInfo(dpy, res, outputInfo->crtc); 875 if (crtcInfo) { 876 for (int j = 0; j < res->nmode; j++) { 877 if (res->modes[j].id == crtcInfo->mode) { 878 double refreshRate = mode_refresh(&res->modes[j]); 879 if (refreshRate > highestRefreshRate) { 880 highestRefreshRate = refreshRate; 881 } 882 break; 883 } 884 } 885 886 XRRFreeCrtcInfo(crtcInfo); 887 } 888 } 889 890 XRRFreeOutputInfo(outputInfo); 891 } 892 } 893 } 894 XRRFreeScreenResources(res); 895 } 896 897 const double rate = 1000.0 / highestRefreshRate; 898 return mozilla::TimeDuration::FromMilliseconds(rate); 899 } 900 901 static void monitors_changed(GdkScreen* aScreen, gpointer aClosure) { 902 XrandrSoftwareVsyncSource* self = 903 static_cast<XrandrSoftwareVsyncSource*>(aClosure); 904 self->SetVsyncRate(ComputeVsyncRate()); 905 } 906 907 // from xrandr.c 908 static double mode_refresh(const XRRModeInfo* mode_info) { 909 double rate; 910 double vTotal = mode_info->vTotal; 911 912 if (mode_info->modeFlags & RR_DoubleScan) { 913 /* doublescan doubles the number of lines */ 914 vTotal *= 2; 915 } 916 917 if (mode_info->modeFlags & RR_Interlace) { 918 /* interlace splits the frame into two fields */ 919 /* the field rate is what is typically reported by monitors */ 920 vTotal /= 2; 921 } 922 923 if (mode_info->hTotal && vTotal) { 924 rate = ((double)mode_info->dotClock / 925 ((double)mode_info->hTotal * (double)vTotal)); 926 } else { 927 rate = 0; 928 } 929 return rate; 930 } 931 }; 932 #endif 933 934 already_AddRefed<gfx::VsyncSource> 935 gfxPlatformGtk::CreateGlobalHardwareVsyncSource() { 936 #ifdef MOZ_X11 937 if (IsHeadless() || IsWaylandDisplay()) { 938 // On Wayland we can not create a global hardware based vsync source, thus 939 // use a software based one here. We create window specific ones later. 940 return GetSoftwareVsyncSource(); 941 } 942 943 nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); 944 nsString windowProtocol; 945 gfxInfo->GetWindowProtocol(windowProtocol); 946 bool isXwayland = windowProtocol.Find(u"xwayland") != -1; 947 nsString adapterDriverVendor; 948 gfxInfo->GetAdapterDriverVendor(adapterDriverVendor); 949 bool isMesa = adapterDriverVendor.Find(u"mesa") != -1; 950 951 // Only use GLX vsync when the OpenGL compositor / WebRender is being used. 952 // The extra cost of initializing a GLX context while blocking the main thread 953 // is not worth it when using basic composition. Do not use it on Xwayland, as 954 // Xwayland will give us a software timer as we are listening for the root 955 // window, which does not have a Wayland equivalent. Don't call 956 // gl::sGLXLibrary.SupportsVideoSync() when EGL is used as NVIDIA drivers 957 // refuse to use EGL GL context when GLX was initialized first and fail 958 // silently. 959 if (StaticPrefs::gfx_x11_glx_sgi_video_sync_AtStartup() && 960 gfxConfig::IsEnabled(Feature::HW_COMPOSITING) && !isXwayland && 961 (!gfxVars::UseEGL() || isMesa) && 962 gl::sGLXLibrary.SupportsVideoSync(DefaultXDisplay())) { 963 RefPtr<GtkVsyncSource> vsyncSource = new GtkVsyncSource(); 964 if (!vsyncSource->Setup()) { 965 NS_WARNING("Failed to setup GLContext, falling back to software vsync."); 966 return GetSoftwareVsyncSource(); 967 } 968 return vsyncSource.forget(); 969 } 970 971 RefPtr<VsyncSource> softwareVsync = new XrandrSoftwareVsyncSource(); 972 return softwareVsync.forget(); 973 #else 974 return GetSoftwareVsyncSource(); 975 #endif 976 } 977 978 void gfxPlatformGtk::BuildContentDeviceData(ContentDeviceData* aOut) { 979 gfxPlatform::BuildContentDeviceData(aOut); 980 981 aOut->cmsOutputProfileData() = GetPlatformCMSOutputProfileData(); 982 } 983 984 // Wrapper for third party code (WebRTC for instance) where 985 // gfxVars can't be included. 986 namespace mozilla::gfx { 987 bool IsDMABufEnabled() { return gfxVars::UseDMABuf(); } 988 } // namespace mozilla::gfx