summaryrefslogtreecommitdiffstats
path: root/gfx/thebes/gfxPlatformGtk.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/thebes/gfxPlatformGtk.cpp')
-rw-r--r--gfx/thebes/gfxPlatformGtk.cpp906
1 files changed, 906 insertions, 0 deletions
diff --git a/gfx/thebes/gfxPlatformGtk.cpp b/gfx/thebes/gfxPlatformGtk.cpp
new file mode 100644
index 000000000..9d7f512f2
--- /dev/null
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -0,0 +1,906 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define PANGO_ENABLE_BACKEND
+#define PANGO_ENABLE_ENGINE
+
+#include "gfxPlatformGtk.h"
+#include "prenv.h"
+
+#include "nsUnicharUtils.h"
+#include "nsUnicodeProperties.h"
+#include "gfx2DGlue.h"
+#include "gfxFcPlatformFontList.h"
+#include "gfxFontconfigUtils.h"
+#include "gfxFontconfigFonts.h"
+#include "gfxConfig.h"
+#include "gfxContext.h"
+#include "gfxUserFontSet.h"
+#include "gfxUtils.h"
+#include "gfxFT2FontBase.h"
+#include "gfxPrefs.h"
+#include "VsyncSource.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Monitor.h"
+#include "base/task.h"
+#include "base/thread.h"
+#include "base/message_loop.h"
+#include "mozilla/gfx/Logging.h"
+
+#include "mozilla/gfx/2D.h"
+
+#include "cairo.h"
+#include <gtk/gtk.h>
+
+#include "gfxImageSurface.h"
+#ifdef MOZ_X11
+#include <gdk/gdkx.h>
+#include "gfxXlibSurface.h"
+#include "cairo-xlib.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/X11Util.h"
+
+#ifdef GL_PROVIDER_GLX
+#include "GLContextProvider.h"
+#include "GLContextGLX.h"
+#include "GLXLibrary.h"
+#endif
+
+/* Undefine the Status from Xlib since it will conflict with system headers on OSX */
+#if defined(__APPLE__) && defined(Status)
+#undef Status
+#endif
+
+#endif /* MOZ_X11 */
+
+#include <fontconfig/fontconfig.h>
+
+#include "nsMathUtils.h"
+
+#define GDK_PIXMAP_SIZE_MAX 32767
+
+#define GFX_PREF_MAX_GENERIC_SUBSTITUTIONS "gfx.font_rendering.fontconfig.max_generic_substitutions"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+
+gfxFontconfigUtils *gfxPlatformGtk::sFontconfigUtils = nullptr;
+
+#if (MOZ_WIDGET_GTK == 2)
+static cairo_user_data_key_t cairo_gdk_drawable_key;
+#endif
+
+bool gfxPlatformGtk::sUseFcFontList = false;
+
+gfxPlatformGtk::gfxPlatformGtk()
+{
+ gtk_init(nullptr, nullptr);
+
+ sUseFcFontList = mozilla::Preferences::GetBool("gfx.font_rendering.fontconfig.fontlist.enabled");
+ if (!sUseFcFontList && !sFontconfigUtils) {
+ sFontconfigUtils = gfxFontconfigUtils::GetFontconfigUtils();
+ }
+
+ mMaxGenericSubstitutions = UNINITIALIZED_VALUE;
+
+#ifdef MOZ_X11
+ if (XRE_IsParentProcess()) {
+ if (GDK_IS_X11_DISPLAY(gdk_display_get_default()) &&
+ mozilla::Preferences::GetBool("gfx.xrender.enabled"))
+ {
+ gfxVars::SetUseXRender(true);
+ }
+ }
+#endif
+
+ uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO);
+ uint32_t contentMask = BackendTypeBit(BackendType::CAIRO);
+#ifdef USE_SKIA
+ canvasMask |= BackendTypeBit(BackendType::SKIA);
+ contentMask |= BackendTypeBit(BackendType::SKIA);
+#endif
+ InitBackendPrefs(canvasMask, BackendType::CAIRO,
+ contentMask, BackendType::CAIRO);
+
+#ifdef MOZ_X11
+ if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ mCompositorDisplay = XOpenDisplay(nullptr);
+ MOZ_ASSERT(mCompositorDisplay, "Failed to create compositor display!");
+ } else {
+ mCompositorDisplay = nullptr;
+ }
+#endif // MOZ_X11
+}
+
+gfxPlatformGtk::~gfxPlatformGtk()
+{
+ if (!sUseFcFontList) {
+ gfxFontconfigUtils::Shutdown();
+ sFontconfigUtils = nullptr;
+ gfxPangoFontGroup::Shutdown();
+ }
+
+#ifdef MOZ_X11
+ if (mCompositorDisplay) {
+ XCloseDisplay(mCompositorDisplay);
+ }
+#endif // MOZ_X11
+}
+
+void
+gfxPlatformGtk::FlushContentDrawing()
+{
+ if (gfxVars::UseXRender()) {
+ XFlush(DefaultXDisplay());
+ }
+}
+
+already_AddRefed<gfxASurface>
+gfxPlatformGtk::CreateOffscreenSurface(const IntSize& aSize,
+ gfxImageFormat aFormat)
+{
+ if (!Factory::AllowedSurfaceSize(aSize)) {
+ return nullptr;
+ }
+
+ RefPtr<gfxASurface> newSurface;
+ bool needsClear = true;
+#ifdef MOZ_X11
+ // XXX we really need a different interface here, something that passes
+ // in more context, including the display and/or target surface type that
+ // we should try to match
+ GdkScreen *gdkScreen = gdk_screen_get_default();
+ if (gdkScreen) {
+ // When forcing PaintedLayers to use image surfaces for content,
+ // force creation of gfxImageSurface surfaces.
+ if (gfxVars::UseXRender() && !UseImageOffscreenSurfaces()) {
+ Screen *screen = gdk_x11_screen_get_xscreen(gdkScreen);
+ XRenderPictFormat* xrenderFormat =
+ gfxXlibSurface::FindRenderFormat(DisplayOfScreen(screen),
+ aFormat);
+
+ if (xrenderFormat) {
+ newSurface = gfxXlibSurface::Create(screen, xrenderFormat,
+ aSize);
+ }
+ } else {
+ // We're not going to use XRender, so we don't need to
+ // search for a render format
+ newSurface = new gfxImageSurface(aSize, aFormat);
+ // The gfxImageSurface ctor zeroes this for us, no need to
+ // waste time clearing again
+ needsClear = false;
+ }
+ }
+#endif
+
+ if (!newSurface) {
+ // We couldn't create a native surface for whatever reason;
+ // e.g., no display, no RENDER, bad size, etc.
+ // Fall back to image surface for the data.
+ newSurface = new gfxImageSurface(aSize, aFormat);
+ }
+
+ if (newSurface->CairoStatus()) {
+ newSurface = nullptr; // surface isn't valid for some reason
+ }
+
+ if (newSurface && needsClear) {
+ gfxUtils::ClearThebesSurface(newSurface);
+ }
+
+ return newSurface.forget();
+}
+
+nsresult
+gfxPlatformGtk::GetFontList(nsIAtom *aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts)
+{
+ if (sUseFcFontList) {
+ gfxPlatformFontList::PlatformFontList()->GetFontList(aLangGroup,
+ aGenericFamily,
+ aListOfFonts);
+ return NS_OK;
+ }
+
+ return sFontconfigUtils->GetFontList(aLangGroup,
+ aGenericFamily,
+ aListOfFonts);
+}
+
+nsresult
+gfxPlatformGtk::UpdateFontList()
+{
+ if (sUseFcFontList) {
+ gfxPlatformFontList::PlatformFontList()->UpdateFontList();
+ return NS_OK;
+ }
+
+ return sFontconfigUtils->UpdateFontList();
+}
+
+// xxx - this is ubuntu centric, need to go through other distros and flesh
+// out a more general list
+static const char kFontDejaVuSans[] = "DejaVu Sans";
+static const char kFontDejaVuSerif[] = "DejaVu Serif";
+static const char kFontEmojiOneMozilla[] = "EmojiOne Mozilla";
+static const char kFontFreeSans[] = "FreeSans";
+static const char kFontFreeSerif[] = "FreeSerif";
+static const char kFontTakaoPGothic[] = "TakaoPGothic";
+static const char kFontDroidSansFallback[] = "Droid Sans Fallback";
+static const char kFontWenQuanYiMicroHei[] = "WenQuanYi Micro Hei";
+static const char kFontNanumGothic[] = "NanumGothic";
+
+void
+gfxPlatformGtk::GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ nsTArray<const char*>& aFontList)
+{
+ if (aNextCh == 0xfe0fu) {
+ // if char is followed by VS16, try for a color emoji glyph
+ aFontList.AppendElement(kFontEmojiOneMozilla);
+ }
+
+ aFontList.AppendElement(kFontDejaVuSerif);
+ aFontList.AppendElement(kFontFreeSerif);
+ aFontList.AppendElement(kFontDejaVuSans);
+ aFontList.AppendElement(kFontFreeSans);
+
+ if (!IS_IN_BMP(aCh)) {
+ uint32_t p = aCh >> 16;
+ if (p == 1) { // try color emoji font, unless VS15 (text style) present
+ if (aNextCh != 0xfe0fu && aNextCh != 0xfe0eu) {
+ aFontList.AppendElement(kFontEmojiOneMozilla);
+ }
+ }
+ }
+
+ // add fonts for CJK ranges
+ // xxx - this isn't really correct, should use the same CJK font ordering
+ // as the pref font code
+ if (aCh >= 0x3000 &&
+ ((aCh < 0xe000) ||
+ (aCh >= 0xf900 && aCh < 0xfff0) ||
+ ((aCh >> 16) == 2))) {
+ aFontList.AppendElement(kFontTakaoPGothic);
+ aFontList.AppendElement(kFontDroidSansFallback);
+ aFontList.AppendElement(kFontWenQuanYiMicroHei);
+ aFontList.AppendElement(kFontNanumGothic);
+ }
+}
+
+gfxPlatformFontList*
+gfxPlatformGtk::CreatePlatformFontList()
+{
+ gfxPlatformFontList* list = new gfxFcPlatformFontList();
+ if (NS_SUCCEEDED(list->InitFontList())) {
+ return list;
+ }
+ gfxPlatformFontList::Shutdown();
+ return nullptr;
+}
+
+nsresult
+gfxPlatformGtk::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
+{
+ if (sUseFcFontList) {
+ gfxPlatformFontList::PlatformFontList()->
+ GetStandardFamilyName(aFontName, aFamilyName);
+ return NS_OK;
+ }
+
+ return sFontconfigUtils->GetStandardFamilyName(aFontName, aFamilyName);
+}
+
+gfxFontGroup *
+gfxPlatformGtk::CreateFontGroup(const FontFamilyList& aFontFamilyList,
+ const gfxFontStyle* aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet* aUserFontSet,
+ gfxFloat aDevToCssSize)
+{
+ if (sUseFcFontList) {
+ return new gfxFontGroup(aFontFamilyList, aStyle, aTextPerf,
+ aUserFontSet, aDevToCssSize);
+ }
+
+ return new gfxPangoFontGroup(aFontFamilyList, aStyle,
+ aUserFontSet, aDevToCssSize);
+}
+
+gfxFontEntry*
+gfxPlatformGtk::LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+{
+ if (sUseFcFontList) {
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ return pfl->LookupLocalFont(aFontName, aWeight, aStretch,
+ aStyle);
+ }
+
+ return gfxPangoFontGroup::NewFontEntry(aFontName, aWeight,
+ aStretch, aStyle);
+}
+
+gfxFontEntry*
+gfxPlatformGtk::MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength)
+{
+ if (sUseFcFontList) {
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ return pfl->MakePlatformFont(aFontName, aWeight, aStretch,
+ aStyle, aFontData, aLength);
+ }
+
+ // passing ownership of the font data to the new font entry
+ return gfxPangoFontGroup::NewFontEntry(aFontName, aWeight,
+ aStretch, aStyle,
+ aFontData, aLength);
+}
+
+bool
+gfxPlatformGtk::IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags)
+{
+ // check for strange format flags
+ NS_ASSERTION(!(aFormatFlags & gfxUserFontSet::FLAG_FORMAT_NOT_USED),
+ "strange font format hint set");
+
+ // accept supported formats
+ // Pango doesn't apply features from AAT TrueType extensions.
+ // Assume that if this is the only SFNT format specified,
+ // then AAT extensions are required for complex script support.
+ if (aFormatFlags & gfxUserFontSet::FLAG_FORMATS_COMMON) {
+ return true;
+ }
+
+ // reject all other formats, known and unknown
+ if (aFormatFlags != 0) {
+ return false;
+ }
+
+ // no format hint set, need to look at data
+ return true;
+}
+
+static int32_t sDPI = 0;
+
+int32_t
+gfxPlatformGtk::GetDPI()
+{
+ if (!sDPI) {
+ // Make sure init is run so we have a resolution
+ GdkScreen *screen = gdk_screen_get_default();
+ gtk_settings_get_for_screen(screen);
+ sDPI = int32_t(round(gdk_screen_get_resolution(screen)));
+ if (sDPI <= 0) {
+ // Fall back to something sane
+ sDPI = 96;
+ }
+ }
+ return sDPI;
+}
+
+double
+gfxPlatformGtk::GetDPIScale()
+{
+ // Integer scale factors work well with GTK window scaling, image scaling,
+ // and pixel alignment, but there is a range where 1 is too small and 2 is
+ // too big. An additional step of 1.5 is added because this is common
+ // scale on WINNT and at this ratio the advantages of larger rendering
+ // outweigh the disadvantages from scaling and pixel mis-alignment.
+ int32_t dpi = GetDPI();
+ if (dpi < 144) {
+ return 1.0;
+ } else if (dpi < 168) {
+ return 1.5;
+ } else {
+ return round(dpi/96.0);
+ }
+}
+
+bool
+gfxPlatformGtk::UseImageOffscreenSurfaces()
+{
+ return GetDefaultContentBackend() != mozilla::gfx::BackendType::CAIRO ||
+ gfxPrefs::UseImageOffscreenSurfaces();
+}
+
+gfxImageFormat
+gfxPlatformGtk::GetOffscreenFormat()
+{
+ // Make sure there is a screen
+ GdkScreen *screen = gdk_screen_get_default();
+ if (screen && gdk_visual_get_depth(gdk_visual_get_system()) == 16) {
+ return SurfaceFormat::R5G6B5_UINT16;
+ }
+
+ return SurfaceFormat::X8R8G8B8_UINT32;
+}
+
+void gfxPlatformGtk::FontsPrefsChanged(const char *aPref)
+{
+ // only checking for generic substitions, pass other changes up
+ if (strcmp(GFX_PREF_MAX_GENERIC_SUBSTITUTIONS, aPref)) {
+ gfxPlatform::FontsPrefsChanged(aPref);
+ return;
+ }
+
+ mMaxGenericSubstitutions = UNINITIALIZED_VALUE;
+ if (sUseFcFontList) {
+ gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList();
+ pfl->ClearGenericMappings();
+ FlushFontAndWordCaches();
+ }
+}
+
+uint32_t gfxPlatformGtk::MaxGenericSubstitions()
+{
+ if (mMaxGenericSubstitutions == UNINITIALIZED_VALUE) {
+ mMaxGenericSubstitutions =
+ Preferences::GetInt(GFX_PREF_MAX_GENERIC_SUBSTITUTIONS, 3);
+ if (mMaxGenericSubstitutions < 0) {
+ mMaxGenericSubstitutions = 3;
+ }
+ }
+
+ return uint32_t(mMaxGenericSubstitutions);
+}
+
+void
+gfxPlatformGtk::GetPlatformCMSOutputProfile(void *&mem, size_t &size)
+{
+ mem = nullptr;
+ size = 0;
+
+#ifdef MOZ_X11
+ GdkDisplay *display = gdk_display_get_default();
+ if (!GDK_IS_X11_DISPLAY(display))
+ return;
+
+ const char EDID1_ATOM_NAME[] = "XFree86_DDC_EDID1_RAWDATA";
+ const char ICC_PROFILE_ATOM_NAME[] = "_ICC_PROFILE";
+
+ Atom edidAtom, iccAtom;
+ Display *dpy = GDK_DISPLAY_XDISPLAY(display);
+ // In xpcshell tests, we never initialize X and hence don't have a Display.
+ // In this case, there's no output colour management to be done, so we just
+ // return with nullptr.
+ if (!dpy)
+ return;
+
+ Window root = gdk_x11_get_default_root_xwindow();
+
+ Atom retAtom;
+ int retFormat;
+ unsigned long retLength, retAfter;
+ unsigned char *retProperty ;
+
+ iccAtom = XInternAtom(dpy, ICC_PROFILE_ATOM_NAME, TRUE);
+ if (iccAtom) {
+ // read once to get size, once for the data
+ if (Success == XGetWindowProperty(dpy, root, iccAtom,
+ 0, INT_MAX /* length */,
+ False, AnyPropertyType,
+ &retAtom, &retFormat, &retLength,
+ &retAfter, &retProperty)) {
+
+ if (retLength > 0) {
+ void *buffer = malloc(retLength);
+ if (buffer) {
+ memcpy(buffer, retProperty, retLength);
+ mem = buffer;
+ size = retLength;
+ }
+ }
+
+ XFree(retProperty);
+ if (size > 0) {
+#ifdef DEBUG_tor
+ fprintf(stderr,
+ "ICM profile read from %s successfully\n",
+ ICC_PROFILE_ATOM_NAME);
+#endif
+ return;
+ }
+ }
+ }
+
+ edidAtom = XInternAtom(dpy, EDID1_ATOM_NAME, TRUE);
+ if (edidAtom) {
+ if (Success == XGetWindowProperty(dpy, root, edidAtom, 0, 32,
+ False, AnyPropertyType,
+ &retAtom, &retFormat, &retLength,
+ &retAfter, &retProperty)) {
+ double gamma;
+ qcms_CIE_xyY whitePoint;
+ qcms_CIE_xyYTRIPLE primaries;
+
+ if (retLength != 128) {
+#ifdef DEBUG_tor
+ fprintf(stderr, "Short EDID data\n");
+#endif
+ return;
+ }
+
+ // Format documented in "VESA E-EDID Implementation Guide"
+
+ gamma = (100 + retProperty[0x17]) / 100.0;
+ whitePoint.x = ((retProperty[0x21] << 2) |
+ (retProperty[0x1a] >> 2 & 3)) / 1024.0;
+ whitePoint.y = ((retProperty[0x22] << 2) |
+ (retProperty[0x1a] >> 0 & 3)) / 1024.0;
+ whitePoint.Y = 1.0;
+
+ primaries.red.x = ((retProperty[0x1b] << 2) |
+ (retProperty[0x19] >> 6 & 3)) / 1024.0;
+ primaries.red.y = ((retProperty[0x1c] << 2) |
+ (retProperty[0x19] >> 4 & 3)) / 1024.0;
+ primaries.red.Y = 1.0;
+
+ primaries.green.x = ((retProperty[0x1d] << 2) |
+ (retProperty[0x19] >> 2 & 3)) / 1024.0;
+ primaries.green.y = ((retProperty[0x1e] << 2) |
+ (retProperty[0x19] >> 0 & 3)) / 1024.0;
+ primaries.green.Y = 1.0;
+
+ primaries.blue.x = ((retProperty[0x1f] << 2) |
+ (retProperty[0x1a] >> 6 & 3)) / 1024.0;
+ primaries.blue.y = ((retProperty[0x20] << 2) |
+ (retProperty[0x1a] >> 4 & 3)) / 1024.0;
+ primaries.blue.Y = 1.0;
+
+ XFree(retProperty);
+
+#ifdef DEBUG_tor
+ fprintf(stderr, "EDID gamma: %f\n", gamma);
+ fprintf(stderr, "EDID whitepoint: %f %f %f\n",
+ whitePoint.x, whitePoint.y, whitePoint.Y);
+ fprintf(stderr, "EDID primaries: [%f %f %f] [%f %f %f] [%f %f %f]\n",
+ primaries.Red.x, primaries.Red.y, primaries.Red.Y,
+ primaries.Green.x, primaries.Green.y, primaries.Green.Y,
+ primaries.Blue.x, primaries.Blue.y, primaries.Blue.Y);
+#endif
+
+ qcms_data_create_rgb_with_gamma(whitePoint, primaries, gamma, &mem, &size);
+
+#ifdef DEBUG_tor
+ if (size > 0) {
+ fprintf(stderr,
+ "ICM profile read from %s successfully\n",
+ EDID1_ATOM_NAME);
+ }
+#endif
+ }
+ }
+#endif
+}
+
+
+#if (MOZ_WIDGET_GTK == 2)
+void
+gfxPlatformGtk::SetGdkDrawable(cairo_surface_t *target,
+ GdkDrawable *drawable)
+{
+ if (cairo_surface_status(target))
+ return;
+
+ g_object_ref(drawable);
+
+ cairo_surface_set_user_data (target,
+ &cairo_gdk_drawable_key,
+ drawable,
+ g_object_unref);
+}
+
+GdkDrawable *
+gfxPlatformGtk::GetGdkDrawable(cairo_surface_t *target)
+{
+ if (cairo_surface_status(target))
+ return nullptr;
+
+ GdkDrawable *result;
+
+ result = (GdkDrawable*) cairo_surface_get_user_data (target,
+ &cairo_gdk_drawable_key);
+ if (result)
+ return result;
+
+#ifdef MOZ_X11
+ if (cairo_surface_get_type(target) != CAIRO_SURFACE_TYPE_XLIB)
+ return nullptr;
+
+ // try looking it up in gdk's table
+ result = (GdkDrawable*) gdk_xid_table_lookup(cairo_xlib_surface_get_drawable(target));
+ if (result) {
+ SetGdkDrawable(target, result);
+ return result;
+ }
+#endif
+
+ return nullptr;
+}
+#endif
+
+already_AddRefed<ScaledFont>
+gfxPlatformGtk::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
+{
+ switch (aTarget->GetBackendType()) {
+ case BackendType::CAIRO:
+ case BackendType::SKIA:
+ if (aFont->GetType() == gfxFont::FONT_TYPE_FONTCONFIG) {
+ gfxFontconfigFontBase* fcFont = static_cast<gfxFontconfigFontBase*>(aFont);
+ return Factory::CreateScaledFontForFontconfigFont(
+ fcFont->GetCairoScaledFont(),
+ fcFont->GetPattern(),
+ fcFont->GetAdjustedSize());
+ }
+ MOZ_FALLTHROUGH;
+ default:
+ return GetScaledFontForFontWithCairoSkia(aTarget, aFont);
+ }
+}
+
+#ifdef GL_PROVIDER_GLX
+
+class GLXVsyncSource final : public VsyncSource
+{
+public:
+ GLXVsyncSource()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mGlobalDisplay = new GLXDisplay();
+ }
+
+ virtual ~GLXVsyncSource()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ virtual Display& GetGlobalDisplay() override
+ {
+ return *mGlobalDisplay;
+ }
+
+ class GLXDisplay final : public VsyncSource::Display
+ {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GLXDisplay)
+
+ public:
+ GLXDisplay() : mGLContext(nullptr)
+ , mXDisplay(nullptr)
+ , mSetupLock("GLXVsyncSetupLock")
+ , mVsyncThread("GLXVsyncThread")
+ , mVsyncTask(nullptr)
+ , mVsyncEnabledLock("GLXVsyncEnabledLock")
+ , mVsyncEnabled(false)
+ {
+ }
+
+ // Sets up the display's GL context on a worker thread.
+ // Required as GLContexts may only be used by the creating thread.
+ // Returns true if setup was a success.
+ bool Setup()
+ {
+ MonitorAutoLock lock(mSetupLock);
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mVsyncThread.Start())
+ return false;
+
+ RefPtr<Runnable> vsyncSetup = NewRunnableMethod(this, &GLXDisplay::SetupGLContext);
+ mVsyncThread.message_loop()->PostTask(vsyncSetup.forget());
+ // Wait until the setup has completed.
+ lock.Wait();
+ return mGLContext != nullptr;
+ }
+
+ // Called on the Vsync thread to setup the GL context.
+ void SetupGLContext()
+ {
+ MonitorAutoLock lock(mSetupLock);
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!mGLContext, "GLContext already setup!");
+
+ // Create video sync timer on a separate Display to prevent locking the
+ // main thread X display.
+ mXDisplay = XOpenDisplay(nullptr);
+ if (!mXDisplay) {
+ lock.NotifyAll();
+ return;
+ }
+
+ // Most compositors wait for vsync events on the root window.
+ Window root = DefaultRootWindow(mXDisplay);
+ int screen = DefaultScreen(mXDisplay);
+
+ ScopedXFree<GLXFBConfig> cfgs;
+ GLXFBConfig config;
+ int visid;
+ if (!gl::GLContextGLX::FindFBConfigForWindow(mXDisplay, screen, root,
+ &cfgs, &config, &visid)) {
+ lock.NotifyAll();
+ return;
+ }
+
+ mGLContext = gl::GLContextGLX::CreateGLContext(
+ gl::CreateContextFlags::NONE,
+ gl::SurfaceCaps::Any(),
+ nullptr,
+ false,
+ mXDisplay,
+ root,
+ config,
+ false);
+
+ if (!mGLContext) {
+ lock.NotifyAll();
+ return;
+ }
+
+ mGLContext->MakeCurrent();
+
+ // Test that SGI_video_sync lets us get the counter.
+ unsigned int syncCounter = 0;
+ if (gl::sGLXLibrary.xGetVideoSync(&syncCounter) != 0) {
+ mGLContext = nullptr;
+ }
+
+ lock.NotifyAll();
+ }
+
+ virtual void EnableVsync() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mGLContext, "GLContext not setup!");
+
+ MonitorAutoLock lock(mVsyncEnabledLock);
+ if (mVsyncEnabled) {
+ return;
+ }
+ mVsyncEnabled = true;
+
+ // If the task has not nulled itself out, it hasn't yet realized
+ // that vsync was disabled earlier, so continue its execution.
+ if (!mVsyncTask) {
+ mVsyncTask = NewRunnableMethod(this, &GLXDisplay::RunVsync);
+ RefPtr<Runnable> addrefedTask = mVsyncTask;
+ mVsyncThread.message_loop()->PostTask(addrefedTask.forget());
+ }
+ }
+
+ virtual void DisableVsync() override
+ {
+ MonitorAutoLock lock(mVsyncEnabledLock);
+ mVsyncEnabled = false;
+ }
+
+ virtual bool IsVsyncEnabled() override
+ {
+ MonitorAutoLock lock(mVsyncEnabledLock);
+ return mVsyncEnabled;
+ }
+
+ virtual void Shutdown() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ DisableVsync();
+
+ // Cleanup thread-specific resources before shutting down.
+ RefPtr<Runnable> shutdownTask = NewRunnableMethod(this, &GLXDisplay::Cleanup);
+ mVsyncThread.message_loop()->PostTask(shutdownTask.forget());
+
+ // Stop, waiting for the cleanup task to finish execution.
+ mVsyncThread.Stop();
+ }
+
+ private:
+ virtual ~GLXDisplay()
+ {
+ }
+
+ void RunVsync()
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mGLContext->MakeCurrent();
+
+ unsigned int syncCounter = 0;
+ gl::sGLXLibrary.xGetVideoSync(&syncCounter);
+ for (;;) {
+ {
+ MonitorAutoLock lock(mVsyncEnabledLock);
+ if (!mVsyncEnabled) {
+ mVsyncTask = nullptr;
+ return;
+ }
+ }
+
+ TimeStamp lastVsync = TimeStamp::Now();
+ bool useSoftware = false;
+
+ // Wait until the video sync counter reaches the next value by waiting
+ // until the parity of the counter value changes.
+ unsigned int nextSync = syncCounter + 1;
+ int status;
+ if ((status = gl::sGLXLibrary.xWaitVideoSync(2, nextSync % 2, &syncCounter)) != 0) {
+ gfxWarningOnce() << "glXWaitVideoSync returned " << status;
+ useSoftware = true;
+ }
+
+ if (syncCounter == (nextSync - 1)) {
+ gfxWarningOnce() << "glXWaitVideoSync failed to increment the sync counter.";
+ useSoftware = true;
+ }
+
+ if (useSoftware) {
+ double remaining = (1000.f / 60.f) -
+ (TimeStamp::Now() - lastVsync).ToMilliseconds();
+ if (remaining > 0) {
+ PlatformThread::Sleep(remaining);
+ }
+ }
+
+ lastVsync = TimeStamp::Now();
+ NotifyVsync(lastVsync);
+ }
+ }
+
+ void Cleanup() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mGLContext = nullptr;
+ XCloseDisplay(mXDisplay);
+ }
+
+ // Owned by the vsync thread.
+ RefPtr<gl::GLContextGLX> mGLContext;
+ _XDisplay* mXDisplay;
+ Monitor mSetupLock;
+ base::Thread mVsyncThread;
+ RefPtr<Runnable> mVsyncTask;
+ Monitor mVsyncEnabledLock;
+ bool mVsyncEnabled;
+ };
+private:
+ // We need a refcounted VsyncSource::Display to use chromium IPC runnables.
+ RefPtr<GLXDisplay> mGlobalDisplay;
+};
+
+already_AddRefed<gfx::VsyncSource>
+gfxPlatformGtk::CreateHardwareVsyncSource()
+{
+ // Only use GLX vsync when the OpenGL compositor is being used.
+ // The extra cost of initializing a GLX context while blocking the main
+ // thread is not worth it when using basic composition.
+ if (gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
+ if (gl::sGLXLibrary.SupportsVideoSync()) {
+ RefPtr<VsyncSource> vsyncSource = new GLXVsyncSource();
+ VsyncSource::Display& display = vsyncSource->GetGlobalDisplay();
+ if (!static_cast<GLXVsyncSource::GLXDisplay&>(display).Setup()) {
+ NS_WARNING("Failed to setup GLContext, falling back to software vsync.");
+ return gfxPlatform::CreateHardwareVsyncSource();
+ }
+ return vsyncSource.forget();
+ }
+ NS_WARNING("SGI_video_sync unsupported. Falling back to software vsync.");
+ }
+ return gfxPlatform::CreateHardwareVsyncSource();
+}
+
+bool
+gfxPlatformGtk::SupportsApzTouchInput() const
+{
+ int value = gfxPrefs::TouchEventsEnabled();
+ return value == 1 || value == 2;
+}
+
+#endif