summaryrefslogtreecommitdiffstats
path: root/gfx/thebes/gfxXlibSurface.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/thebes/gfxXlibSurface.cpp')
-rw-r--r--gfx/thebes/gfxXlibSurface.cpp614
1 files changed, 614 insertions, 0 deletions
diff --git a/gfx/thebes/gfxXlibSurface.cpp b/gfx/thebes/gfxXlibSurface.cpp
new file mode 100644
index 000000000..12891c454
--- /dev/null
+++ b/gfx/thebes/gfxXlibSurface.cpp
@@ -0,0 +1,614 @@
+/* -*- 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/. */
+
+#include "gfxXlibSurface.h"
+
+#include "cairo.h"
+#include "cairo-xlib.h"
+#include "cairo-xlib-xrender.h"
+#include <X11/Xlibint.h> /* For XESetCloseDisplay */
+#undef max // Xlibint.h defines this and it breaks std::max
+#undef min // Xlibint.h defines this and it breaks std::min
+
+#include "nsTArray.h"
+#include "nsAlgorithm.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/Preferences.h"
+#include <algorithm>
+#include "mozilla/CheckedInt.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+gfxXlibSurface::gfxXlibSurface(Display *dpy, Drawable drawable, Visual *visual)
+ : mPixmapTaken(false), mDisplay(dpy), mDrawable(drawable)
+#if defined(GL_PROVIDER_GLX)
+ , mGLXPixmap(X11None)
+#endif
+{
+ const gfx::IntSize size = DoSizeQuery();
+ cairo_surface_t *surf = cairo_xlib_surface_create(dpy, drawable, visual, size.width, size.height);
+ Init(surf);
+}
+
+gfxXlibSurface::gfxXlibSurface(Display *dpy, Drawable drawable, Visual *visual, const gfx::IntSize& size)
+ : mPixmapTaken(false), mDisplay(dpy), mDrawable(drawable)
+#if defined(GL_PROVIDER_GLX)
+ , mGLXPixmap(X11None)
+#endif
+{
+ NS_ASSERTION(Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT),
+ "Bad size");
+
+ cairo_surface_t *surf = cairo_xlib_surface_create(dpy, drawable, visual, size.width, size.height);
+ Init(surf);
+}
+
+gfxXlibSurface::gfxXlibSurface(Screen *screen, Drawable drawable, XRenderPictFormat *format,
+ const gfx::IntSize& size)
+ : mPixmapTaken(false), mDisplay(DisplayOfScreen(screen)),
+ mDrawable(drawable)
+#if defined(GL_PROVIDER_GLX)
+ , mGLXPixmap(X11None)
+#endif
+{
+ NS_ASSERTION(Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT),
+ "Bad Size");
+
+ cairo_surface_t *surf =
+ cairo_xlib_surface_create_with_xrender_format(mDisplay, drawable,
+ screen, format,
+ size.width, size.height);
+ Init(surf);
+}
+
+gfxXlibSurface::gfxXlibSurface(cairo_surface_t *csurf)
+ : mPixmapTaken(false)
+#if defined(GL_PROVIDER_GLX)
+ , mGLXPixmap(X11None)
+#endif
+{
+ NS_PRECONDITION(cairo_surface_status(csurf) == 0,
+ "Not expecting an error surface");
+
+ mDrawable = cairo_xlib_surface_get_drawable(csurf);
+ mDisplay = cairo_xlib_surface_get_display(csurf);
+
+ Init(csurf, true);
+}
+
+gfxXlibSurface::~gfxXlibSurface()
+{
+ // gfxASurface's destructor calls RecordMemoryFreed().
+ if (mPixmapTaken) {
+#if defined(GL_PROVIDER_GLX)
+ if (mGLXPixmap) {
+ gl::sGLXLibrary.DestroyPixmap(mDisplay, mGLXPixmap);
+ }
+#endif
+ XFreePixmap (mDisplay, mDrawable);
+ }
+}
+
+static Drawable
+CreatePixmap(Screen *screen, const gfx::IntSize& size, unsigned int depth,
+ Drawable relatedDrawable)
+{
+ if (!Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT))
+ return X11None;
+
+ if (relatedDrawable == X11None) {
+ relatedDrawable = RootWindowOfScreen(screen);
+ }
+ Display *dpy = DisplayOfScreen(screen);
+ // X gives us a fatal error if we try to create a pixmap of width
+ // or height 0
+ return XCreatePixmap(dpy, relatedDrawable,
+ std::max(1, size.width), std::max(1, size.height),
+ depth);
+}
+
+void
+gfxXlibSurface::TakePixmap()
+{
+ NS_ASSERTION(!mPixmapTaken, "I already own the Pixmap!");
+ mPixmapTaken = true;
+
+ // The bit depth returned from Cairo is technically int, but this is
+ // the last place we'd be worried about that scenario.
+ unsigned int bitDepth = cairo_xlib_surface_get_depth(CairoSurface());
+ MOZ_ASSERT((bitDepth % 8) == 0, "Memory used not recorded correctly");
+
+ // Divide by 8 because surface_get_depth gives us the number of *bits* per
+ // pixel.
+ gfx::IntSize size = GetSize();
+ CheckedInt32 totalBytes = CheckedInt32(size.width) * CheckedInt32(size.height) * (bitDepth/8);
+
+ // Don't do anything in the "else" case. We could add INT32_MAX, but that
+ // would overflow the memory used counter. It would also mean we tried for
+ // a 2G image. For now, we'll just assert,
+ MOZ_ASSERT(totalBytes.isValid(),"Did not expect to exceed 2Gb image");
+ if (totalBytes.isValid()) {
+ RecordMemoryUsed(totalBytes.value());
+ }
+}
+
+Drawable
+gfxXlibSurface::ReleasePixmap() {
+ NS_ASSERTION(mPixmapTaken, "I don't own the Pixmap!");
+ mPixmapTaken = false;
+ RecordMemoryFreed();
+ return mDrawable;
+}
+
+static cairo_user_data_key_t gDestroyPixmapKey;
+
+struct DestroyPixmapClosure {
+ DestroyPixmapClosure(Drawable d, Screen *s) : mPixmap(d), mScreen(s) {}
+ Drawable mPixmap;
+ Screen *mScreen;
+};
+
+static void
+DestroyPixmap(void *data)
+{
+ DestroyPixmapClosure *closure = static_cast<DestroyPixmapClosure*>(data);
+ XFreePixmap(DisplayOfScreen(closure->mScreen), closure->mPixmap);
+ delete closure;
+}
+
+/* static */
+cairo_surface_t *
+gfxXlibSurface::CreateCairoSurface(Screen *screen, Visual *visual,
+ const gfx::IntSize& size, Drawable relatedDrawable)
+{
+ Drawable drawable =
+ CreatePixmap(screen, size, DepthOfVisual(screen, visual),
+ relatedDrawable);
+ if (!drawable)
+ return nullptr;
+
+ cairo_surface_t* surface =
+ cairo_xlib_surface_create(DisplayOfScreen(screen), drawable, visual,
+ size.width, size.height);
+ if (cairo_surface_status(surface)) {
+ cairo_surface_destroy(surface);
+ XFreePixmap(DisplayOfScreen(screen), drawable);
+ return nullptr;
+ }
+
+ DestroyPixmapClosure *closure = new DestroyPixmapClosure(drawable, screen);
+ cairo_surface_set_user_data(surface, &gDestroyPixmapKey,
+ closure, DestroyPixmap);
+ return surface;
+}
+
+/* static */
+already_AddRefed<gfxXlibSurface>
+gfxXlibSurface::Create(Screen *screen, Visual *visual,
+ const gfx::IntSize& size, Drawable relatedDrawable)
+{
+ Drawable drawable =
+ CreatePixmap(screen, size, DepthOfVisual(screen, visual),
+ relatedDrawable);
+ if (!drawable)
+ return nullptr;
+
+ RefPtr<gfxXlibSurface> result =
+ new gfxXlibSurface(DisplayOfScreen(screen), drawable, visual, size);
+ result->TakePixmap();
+
+ if (result->CairoStatus() != 0)
+ return nullptr;
+
+ return result.forget();
+}
+
+/* static */
+already_AddRefed<gfxXlibSurface>
+gfxXlibSurface::Create(Screen *screen, XRenderPictFormat *format,
+ const gfx::IntSize& size, Drawable relatedDrawable)
+{
+ Drawable drawable =
+ CreatePixmap(screen, size, format->depth, relatedDrawable);
+ if (!drawable)
+ return nullptr;
+
+ RefPtr<gfxXlibSurface> result =
+ new gfxXlibSurface(screen, drawable, format, size);
+ result->TakePixmap();
+
+ if (result->CairoStatus() != 0)
+ return nullptr;
+
+ return result.forget();
+}
+
+static bool GetForce24bppPref()
+{
+ return Preferences::GetBool("mozilla.widget.force-24bpp", false);
+}
+
+already_AddRefed<gfxASurface>
+gfxXlibSurface::CreateSimilarSurface(gfxContentType aContent,
+ const gfx::IntSize& aSize)
+{
+ if (!mSurface || !mSurfaceValid) {
+ return nullptr;
+ }
+
+ if (aContent == gfxContentType::COLOR) {
+ // cairo_surface_create_similar will use a matching visual if it can.
+ // However, systems with 16-bit or indexed default visuals may benefit
+ // from rendering with 24-bit formats.
+ static bool force24bpp = GetForce24bppPref();
+ if (force24bpp
+ && cairo_xlib_surface_get_depth(CairoSurface()) != 24) {
+ XRenderPictFormat* format =
+ XRenderFindStandardFormat(mDisplay, PictStandardRGB24);
+ if (format) {
+ // Cairo only performs simple self-copies as desired if it
+ // knows that this is a Pixmap surface. It only knows that
+ // surfaces are pixmap surfaces if it creates the Pixmap
+ // itself, so we use cairo_surface_create_similar with a
+ // temporary reference surface to indicate the format.
+ Screen* screen = cairo_xlib_surface_get_screen(CairoSurface());
+ RefPtr<gfxXlibSurface> depth24reference =
+ gfxXlibSurface::Create(screen, format,
+ gfx::IntSize(1, 1), mDrawable);
+ if (depth24reference)
+ return depth24reference->
+ gfxASurface::CreateSimilarSurface(aContent, aSize);
+ }
+ }
+ }
+
+ return gfxASurface::CreateSimilarSurface(aContent, aSize);
+}
+
+void
+gfxXlibSurface::Finish()
+{
+#if defined(GL_PROVIDER_GLX)
+ if (mPixmapTaken && mGLXPixmap) {
+ gl::sGLXLibrary.DestroyPixmap(mDisplay, mGLXPixmap);
+ mGLXPixmap = X11None;
+ }
+#endif
+ gfxASurface::Finish();
+}
+
+const gfx::IntSize
+gfxXlibSurface::GetSize() const
+{
+ if (!mSurfaceValid)
+ return gfx::IntSize(0,0);
+
+ return gfx::IntSize(cairo_xlib_surface_get_width(mSurface),
+ cairo_xlib_surface_get_height(mSurface));
+}
+
+const gfx::IntSize
+gfxXlibSurface::DoSizeQuery()
+{
+ // figure out width/height/depth
+ Window root_ignore;
+ int x_ignore, y_ignore;
+ unsigned int bwidth_ignore, width, height, depth;
+
+ XGetGeometry(mDisplay,
+ mDrawable,
+ &root_ignore, &x_ignore, &y_ignore,
+ &width, &height,
+ &bwidth_ignore, &depth);
+
+ return gfx::IntSize(width, height);
+}
+
+class DisplayTable {
+public:
+ static bool GetColormapAndVisual(Screen* screen,
+ XRenderPictFormat* format,
+ Visual* visual, Colormap* colormap,
+ Visual** visualForColormap);
+
+private:
+ struct ColormapEntry {
+ XRenderPictFormat* mFormat;
+ // The Screen is needed here because colormaps (and their visuals) may
+ // only be used on one Screen, but XRenderPictFormats are not unique
+ // to any one Screen.
+ Screen* mScreen;
+ Visual* mVisual;
+ Colormap mColormap;
+ };
+
+ class DisplayInfo {
+ public:
+ explicit DisplayInfo(Display* display) : mDisplay(display) { }
+ Display* mDisplay;
+ nsTArray<ColormapEntry> mColormapEntries;
+ };
+
+ // Comparator for finding the DisplayInfo
+ class FindDisplay {
+ public:
+ bool Equals(const DisplayInfo& info, const Display *display) const
+ {
+ return info.mDisplay == display;
+ }
+ };
+
+ static int DisplayClosing(Display *display, XExtCodes* codes);
+
+ nsTArray<DisplayInfo> mDisplays;
+ static DisplayTable* sDisplayTable;
+};
+
+DisplayTable* DisplayTable::sDisplayTable;
+
+// Pixmaps don't have a particular associated visual but the pixel values are
+// interpreted according to a visual/colormap pairs.
+//
+// cairo is designed for surfaces with either TrueColor visuals or the
+// default visual (which may not be true color). TrueColor visuals don't
+// really need a colormap because the visual indicates the pixel format,
+// and cairo uses the default visual with the default colormap, so cairo
+// surfaces don't need an explicit colormap.
+//
+// However, some toolkits (e.g. GDK) need a colormap even with TrueColor
+// visuals. We can create a colormap for these visuals, but it will use about
+// 20kB of memory in the server, so we use the default colormap when
+// suitable and share colormaps between surfaces. Another reason for
+// minimizing colormap turnover is that the plugin process must leak resources
+// for each new colormap id when using older GDK libraries (bug 569775).
+//
+// Only the format of the pixels is important for rendering to Pixmaps, so if
+// the format of a visual matches that of the surface, then that visual can be
+// used for rendering to the surface. Multiple visuals can match the same
+// format (but have different GLX properties), so the visual returned may
+// differ from the visual passed in. Colormaps are tied to a visual, so
+// should only be used with their visual.
+
+/* static */ bool
+DisplayTable::GetColormapAndVisual(Screen* aScreen, XRenderPictFormat* aFormat,
+ Visual* aVisual, Colormap* aColormap,
+ Visual** aVisualForColormap)
+
+{
+ Display* display = DisplayOfScreen(aScreen);
+
+ // Use the default colormap if the default visual matches.
+ Visual *defaultVisual = DefaultVisualOfScreen(aScreen);
+ if (aVisual == defaultVisual
+ || (aFormat
+ && aFormat == XRenderFindVisualFormat(display, defaultVisual)))
+ {
+ *aColormap = DefaultColormapOfScreen(aScreen);
+ *aVisualForColormap = defaultVisual;
+ return true;
+ }
+
+ // Only supporting TrueColor non-default visuals
+ if (!aVisual || aVisual->c_class != TrueColor)
+ return false;
+
+ if (!sDisplayTable) {
+ sDisplayTable = new DisplayTable();
+ }
+
+ nsTArray<DisplayInfo>* displays = &sDisplayTable->mDisplays;
+ size_t d = displays->IndexOf(display, 0, FindDisplay());
+
+ if (d == displays->NoIndex) {
+ d = displays->Length();
+ // Register for notification of display closing, when this info
+ // becomes invalid.
+ XExtCodes *codes = XAddExtension(display);
+ if (!codes)
+ return false;
+
+ XESetCloseDisplay(display, codes->extension, DisplayClosing);
+ // Add a new DisplayInfo.
+ displays->AppendElement(display);
+ }
+
+ nsTArray<ColormapEntry>* entries =
+ &displays->ElementAt(d).mColormapEntries;
+
+ // Only a small number of formats are expected to be used, so just do a
+ // simple linear search.
+ for (uint32_t i = 0; i < entries->Length(); ++i) {
+ const ColormapEntry& entry = entries->ElementAt(i);
+ // Only the format and screen need to match. (The visual may differ.)
+ // If there is no format (e.g. no RENDER extension) then just compare
+ // the visual.
+ if ((aFormat && entry.mFormat == aFormat && entry.mScreen == aScreen)
+ || aVisual == entry.mVisual) {
+ *aColormap = entry.mColormap;
+ *aVisualForColormap = entry.mVisual;
+ return true;
+ }
+ }
+
+ // No existing entry. Create a colormap and add an entry.
+ Colormap colormap = XCreateColormap(display, RootWindowOfScreen(aScreen),
+ aVisual, AllocNone);
+ ColormapEntry* newEntry = entries->AppendElement();
+ newEntry->mFormat = aFormat;
+ newEntry->mScreen = aScreen;
+ newEntry->mVisual = aVisual;
+ newEntry->mColormap = colormap;
+
+ *aColormap = colormap;
+ *aVisualForColormap = aVisual;
+ return true;
+}
+
+/* static */ int
+DisplayTable::DisplayClosing(Display *display, XExtCodes* codes)
+{
+ // No need to free the colormaps explicitly as they will be released when
+ // the connection is closed.
+ sDisplayTable->mDisplays.RemoveElement(display, FindDisplay());
+ if (sDisplayTable->mDisplays.Length() == 0) {
+ delete sDisplayTable;
+ sDisplayTable = nullptr;
+ }
+ return 0;
+}
+
+/* static */
+bool
+gfxXlibSurface::GetColormapAndVisual(cairo_surface_t* aXlibSurface,
+ Colormap* aColormap, Visual** aVisual)
+{
+ XRenderPictFormat* format =
+ cairo_xlib_surface_get_xrender_format(aXlibSurface);
+ Screen* screen = cairo_xlib_surface_get_screen(aXlibSurface);
+ Visual* visual = cairo_xlib_surface_get_visual(aXlibSurface);
+
+ return DisplayTable::GetColormapAndVisual(screen, format, visual,
+ aColormap, aVisual);
+}
+
+bool
+gfxXlibSurface::GetColormapAndVisual(Colormap* aColormap, Visual** aVisual)
+{
+ if (!mSurfaceValid)
+ return false;
+
+ return GetColormapAndVisual(CairoSurface(), aColormap, aVisual);
+}
+
+/* static */
+int
+gfxXlibSurface::DepthOfVisual(const Screen* screen, const Visual* visual)
+{
+ for (int d = 0; d < screen->ndepths; d++) {
+ const Depth& d_info = screen->depths[d];
+ if (visual >= &d_info.visuals[0]
+ && visual < &d_info.visuals[d_info.nvisuals])
+ return d_info.depth;
+ }
+
+ NS_ERROR("Visual not on Screen.");
+ return 0;
+}
+
+/* static */
+Visual*
+gfxXlibSurface::FindVisual(Screen *screen, gfxImageFormat format)
+{
+ int depth;
+ unsigned long red_mask, green_mask, blue_mask;
+ switch (format) {
+ case gfx::SurfaceFormat::A8R8G8B8_UINT32:
+ depth = 32;
+ red_mask = 0xff0000;
+ green_mask = 0xff00;
+ blue_mask = 0xff;
+ break;
+ case gfx::SurfaceFormat::X8R8G8B8_UINT32:
+ depth = 24;
+ red_mask = 0xff0000;
+ green_mask = 0xff00;
+ blue_mask = 0xff;
+ break;
+ case gfx::SurfaceFormat::R5G6B5_UINT16:
+ depth = 16;
+ red_mask = 0xf800;
+ green_mask = 0x7e0;
+ blue_mask = 0x1f;
+ break;
+ case gfx::SurfaceFormat::A8:
+ default:
+ return nullptr;
+ }
+
+ for (int d = 0; d < screen->ndepths; d++) {
+ const Depth& d_info = screen->depths[d];
+ if (d_info.depth != depth)
+ continue;
+
+ for (int v = 0; v < d_info.nvisuals; v++) {
+ Visual* visual = &d_info.visuals[v];
+
+ if (visual->c_class == TrueColor &&
+ visual->red_mask == red_mask &&
+ visual->green_mask == green_mask &&
+ visual->blue_mask == blue_mask)
+ return visual;
+ }
+ }
+
+ return nullptr;
+}
+
+/* static */
+XRenderPictFormat*
+gfxXlibSurface::FindRenderFormat(Display *dpy, gfxImageFormat format)
+{
+ switch (format) {
+ case gfx::SurfaceFormat::A8R8G8B8_UINT32:
+ return XRenderFindStandardFormat (dpy, PictStandardARGB32);
+ case gfx::SurfaceFormat::X8R8G8B8_UINT32:
+ return XRenderFindStandardFormat (dpy, PictStandardRGB24);
+ case gfx::SurfaceFormat::R5G6B5_UINT16: {
+ // PictStandardRGB16_565 is not standard Xrender format
+ // we should try to find related visual
+ // and find xrender format by visual
+ Visual *visual = FindVisual(DefaultScreenOfDisplay(dpy), format);
+ if (!visual)
+ return nullptr;
+ return XRenderFindVisualFormat(dpy, visual);
+ }
+ case gfx::SurfaceFormat::A8:
+ return XRenderFindStandardFormat (dpy, PictStandardA8);
+ default:
+ break;
+ }
+
+ return nullptr;
+}
+
+Screen*
+gfxXlibSurface::XScreen()
+{
+ return cairo_xlib_surface_get_screen(CairoSurface());
+}
+
+XRenderPictFormat*
+gfxXlibSurface::XRenderFormat()
+{
+ return cairo_xlib_surface_get_xrender_format(CairoSurface());
+}
+
+#if defined(GL_PROVIDER_GLX)
+GLXPixmap
+gfxXlibSurface::GetGLXPixmap()
+{
+ if (!mGLXPixmap) {
+#ifdef DEBUG
+ // cairo_surface_has_show_text_glyphs is used solely for the
+ // side-effect of setting the error on surface if
+ // cairo_surface_finish() has been called.
+ cairo_surface_has_show_text_glyphs(CairoSurface());
+ NS_ASSERTION(CairoStatus() != CAIRO_STATUS_SURFACE_FINISHED,
+ "GetGLXPixmap called after surface finished");
+#endif
+ mGLXPixmap = gl::sGLXLibrary.CreatePixmap(this);
+ }
+ return mGLXPixmap;
+}
+
+void
+gfxXlibSurface::BindGLXPixmap(GLXPixmap aPixmap)
+{
+ MOZ_ASSERT(!mGLXPixmap, "A GLXPixmap is already bound!");
+ mGLXPixmap = aPixmap;
+}
+
+#endif