Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* 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
#undef Data
#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(XlibDisplay::Borrow(dpy)),
mDrawable(drawable),
mGLXPixmap(X11None) {
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)
: gfxXlibSurface(XlibDisplay::Borrow(dpy), drawable, visual, size) {}
gfxXlibSurface::gfxXlibSurface(const std::shared_ptr<XlibDisplay>& dpy,
Drawable drawable, Visual* visual,
const gfx::IntSize& size)
: mPixmapTaken(false),
mDisplay(dpy),
mDrawable(drawable),
mGLXPixmap(X11None) {
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(XlibDisplay::Borrow(DisplayOfScreen(screen))),
mDrawable(drawable),
mGLXPixmap(X11None) {
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), mGLXPixmap(X11None) {
MOZ_ASSERT(cairo_surface_status(csurf) == 0,
"Not expecting an error surface");
mDrawable = cairo_xlib_surface_get_drawable(csurf);
mDisplay = XlibDisplay::Borrow(cairo_xlib_surface_get_display(csurf));
Init(csurf, true);
}
gfxXlibSurface::~gfxXlibSurface() {
// gfxASurface's destructor calls RecordMemoryFreed().
if (mPixmapTaken) {
if (mGLXPixmap) {
gl::sGLXLibrary.DestroyPixmap(*mDisplay, mGLXPixmap);
}
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) {
return Create(XlibDisplay::Borrow(DisplayOfScreen(screen)), screen, visual,
size, relatedDrawable);
};
/* static */
already_AddRefed<gfxXlibSurface> gfxXlibSurface::Create(
const std::shared_ptr<XlibDisplay>& display, Screen* screen, Visual* visual,
const gfx::IntSize& size, Drawable relatedDrawable) {
MOZ_ASSERT(*display == DisplayOfScreen(screen));
Drawable drawable = CreatePixmap(screen, size, DepthOfVisual(screen, visual),
relatedDrawable);
if (!drawable) return nullptr;
RefPtr<gfxXlibSurface> result =
new gfxXlibSurface(display, 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 (mPixmapTaken && mGLXPixmap) {
gl::sGLXLibrary.DestroyPixmap(*mDisplay, mGLXPixmap);
mGLXPixmap = X11None;
}
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());
}
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;
}