summaryrefslogtreecommitdiffstats
path: root/widget/gtk/nsShmImage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gtk/nsShmImage.cpp')
-rw-r--r--widget/gtk/nsShmImage.cpp326
1 files changed, 326 insertions, 0 deletions
diff --git a/widget/gtk/nsShmImage.cpp b/widget/gtk/nsShmImage.cpp
new file mode 100644
index 0000000000..7208b7075f
--- /dev/null
+++ b/widget/gtk/nsShmImage.cpp
@@ -0,0 +1,326 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsShmImage.h"
+
+#ifdef MOZ_HAVE_SHMIMAGE
+# include "mozilla/X11Util.h"
+# include "mozilla/gfx/gfxVars.h"
+# include "mozilla/ipc/SharedMemory.h"
+# include "gfxPlatform.h"
+# include "nsPrintfCString.h"
+# include "nsTArray.h"
+
+# include <dlfcn.h>
+# include <errno.h>
+# include <string.h>
+# include <sys/ipc.h>
+# include <sys/shm.h>
+
+extern "C" {
+# include <X11/ImUtil.h>
+}
+
+using namespace mozilla::ipc;
+using namespace mozilla::gfx;
+
+nsShmImage::nsShmImage(Display* aDisplay, Drawable aWindow, Visual* aVisual,
+ unsigned int aDepth)
+ : mDisplay(aDisplay),
+ mConnection(XGetXCBConnection(aDisplay)),
+ mWindow(aWindow),
+ mVisual(aVisual),
+ mDepth(aDepth),
+ mFormat(mozilla::gfx::SurfaceFormat::UNKNOWN),
+ mSize(0, 0),
+ mStride(0),
+ mPixmap(XCB_NONE),
+ mGC(XCB_NONE),
+ mRequestPending(false),
+ mShmSeg(XCB_NONE),
+ mShmId(-1),
+ mShmAddr(nullptr) {
+ mozilla::PodZero(&mSyncRequest);
+}
+
+nsShmImage::~nsShmImage() { DestroyImage(); }
+
+// If XShm isn't available to our client, we'll try XShm once, fail,
+// set this to false and then never try again.
+static bool gShmAvailable = true;
+bool nsShmImage::UseShm() { return gShmAvailable; }
+
+bool nsShmImage::CreateShmSegment() {
+ size_t size = SharedMemory::PageAlignedSize(mStride * mSize.height);
+
+# if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
+ static mozilla::LazyLogModule sPledgeLog("SandboxPledge");
+ MOZ_LOG(sPledgeLog, mozilla::LogLevel::Debug,
+ ("%s called when pledged, returning false\n", __func__));
+ return false;
+# endif
+ mShmId = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
+ if (mShmId == -1) {
+ return false;
+ }
+ mShmAddr = (uint8_t*)shmat(mShmId, nullptr, 0);
+ mShmSeg = xcb_generate_id(mConnection);
+
+ // Mark the handle removed so that it will destroy the segment when unmapped.
+ shmctl(mShmId, IPC_RMID, nullptr);
+
+ if (mShmAddr == (void*)-1) {
+ // Since mapping failed, the segment is already destroyed.
+ mShmId = -1;
+
+ nsPrintfCString warning("shmat(): %s (%d)\n", strerror(errno), errno);
+ NS_WARNING(warning.get());
+ return false;
+ }
+
+# ifdef DEBUG
+ struct shmid_ds info;
+ if (shmctl(mShmId, IPC_STAT, &info) < 0) {
+ return false;
+ }
+
+ MOZ_ASSERT(size <= info.shm_segsz, "Segment doesn't have enough space!");
+# endif
+
+ return true;
+}
+
+void nsShmImage::DestroyShmSegment() {
+ if (mShmId != -1) {
+ shmdt(mShmAddr);
+ mShmId = -1;
+ }
+}
+
+static bool gShmInitialized = false;
+static bool gUseShmPixmaps = false;
+
+bool nsShmImage::InitExtension() {
+ if (gShmInitialized) {
+ return gShmAvailable;
+ }
+
+ gShmInitialized = true;
+
+ // Bugs 1397918, 1293474 - race condition in libxcb fixed upstream as of
+ // version 1.11. Since we can't query libxcb's version directly, the only
+ // other option is to check for symbols that were added after 1.11.
+ // xcb_discard_reply64 was added in 1.11.1, so check for existence of
+ // that to verify we are using a version of libxcb with the bug fixed.
+ // Otherwise, we can't risk using libxcb due to aforementioned crashes.
+ if (!dlsym(RTLD_DEFAULT, "xcb_discard_reply64")) {
+ gShmAvailable = false;
+ return false;
+ }
+
+ const xcb_query_extension_reply_t* extReply;
+ extReply = xcb_get_extension_data(mConnection, &xcb_shm_id);
+ if (!extReply || !extReply->present) {
+ gShmAvailable = false;
+ return false;
+ }
+
+ xcb_shm_query_version_reply_t* shmReply = xcb_shm_query_version_reply(
+ mConnection, xcb_shm_query_version(mConnection), nullptr);
+
+ if (!shmReply) {
+ gShmAvailable = false;
+ return false;
+ }
+
+ gUseShmPixmaps = shmReply->shared_pixmaps &&
+ shmReply->pixmap_format == XCB_IMAGE_FORMAT_Z_PIXMAP;
+
+ free(shmReply);
+
+ return true;
+}
+
+bool nsShmImage::CreateImage(const IntSize& aSize) {
+ MOZ_ASSERT(mConnection && mVisual);
+
+ if (!InitExtension()) {
+ return false;
+ }
+
+ mSize = aSize;
+
+ BackendType backend = gfxVars::ContentBackend();
+
+ mFormat = SurfaceFormat::UNKNOWN;
+ switch (mDepth) {
+ case 32:
+ if (mVisual->red_mask == 0xff0000 && mVisual->green_mask == 0xff00 &&
+ mVisual->blue_mask == 0xff) {
+ mFormat = SurfaceFormat::B8G8R8A8;
+ }
+ break;
+ case 24:
+ // Only support the BGRX layout, and report it as BGRA to the compositor.
+ // The alpha channel will be discarded when we put the image.
+ // Cairo/pixman lacks some fast paths for compositing BGRX onto BGRA, so
+ // just report it as BGRX directly in that case.
+ if (mVisual->red_mask == 0xff0000 && mVisual->green_mask == 0xff00 &&
+ mVisual->blue_mask == 0xff) {
+ mFormat = backend == BackendType::CAIRO ? SurfaceFormat::B8G8R8X8
+ : SurfaceFormat::B8G8R8A8;
+ }
+ break;
+ case 16:
+ if (mVisual->red_mask == 0xf800 && mVisual->green_mask == 0x07e0 &&
+ mVisual->blue_mask == 0x1f) {
+ mFormat = SurfaceFormat::R5G6B5_UINT16;
+ }
+ break;
+ }
+
+ if (mFormat == SurfaceFormat::UNKNOWN) {
+ NS_WARNING("Unsupported XShm Image format!");
+ gShmAvailable = false;
+ return false;
+ }
+
+ // Round up stride to the display's scanline pad (in bits) as XShm expects.
+ int scanlinePad = _XGetScanlinePad(mDisplay, mDepth);
+ int bitsPerPixel = _XGetBitsPerPixel(mDisplay, mDepth);
+ int bitsPerLine =
+ ((bitsPerPixel * aSize.width + scanlinePad - 1) / scanlinePad) *
+ scanlinePad;
+ mStride = bitsPerLine / 8;
+
+ if (!CreateShmSegment()) {
+ DestroyImage();
+ return false;
+ }
+
+ xcb_generic_error_t* error;
+ xcb_void_cookie_t cookie;
+
+ cookie = xcb_shm_attach_checked(mConnection, mShmSeg, mShmId, 0);
+
+ if ((error = xcb_request_check(mConnection, cookie))) {
+ NS_WARNING("Failed to attach MIT-SHM segment.");
+ DestroyImage();
+ gShmAvailable = false;
+ free(error);
+ return false;
+ }
+
+ if (gUseShmPixmaps) {
+ mPixmap = xcb_generate_id(mConnection);
+ cookie = xcb_shm_create_pixmap_checked(mConnection, mPixmap, mWindow,
+ aSize.width, aSize.height, mDepth,
+ mShmSeg, 0);
+
+ if ((error = xcb_request_check(mConnection, cookie))) {
+ // Disable shared pixmaps permanently if creation failed.
+ mPixmap = XCB_NONE;
+ gUseShmPixmaps = false;
+ free(error);
+ }
+ }
+
+ return true;
+}
+
+void nsShmImage::DestroyImage() {
+ if (mGC) {
+ xcb_free_gc(mConnection, mGC);
+ mGC = XCB_NONE;
+ }
+ if (mPixmap != XCB_NONE) {
+ xcb_free_pixmap(mConnection, mPixmap);
+ mPixmap = XCB_NONE;
+ }
+ if (mShmSeg != XCB_NONE) {
+ xcb_shm_detach_checked(mConnection, mShmSeg);
+ mShmSeg = XCB_NONE;
+ }
+ DestroyShmSegment();
+ // Avoid leaking any pending reply. No real need to wait but CentOS 6 build
+ // machines don't have xcb_discard_reply().
+ WaitIfPendingReply();
+}
+
+// Wait for any in-flight shm-affected requests to complete.
+// Typically X clients would wait for a XShmCompletionEvent to be received,
+// but this works as it's sent immediately after the request is sent.
+void nsShmImage::WaitIfPendingReply() {
+ if (mRequestPending) {
+ xcb_get_input_focus_reply_t* reply =
+ xcb_get_input_focus_reply(mConnection, mSyncRequest, nullptr);
+ free(reply);
+ mRequestPending = false;
+ }
+}
+
+already_AddRefed<DrawTarget> nsShmImage::CreateDrawTarget(
+ const mozilla::LayoutDeviceIntRegion& aRegion) {
+ WaitIfPendingReply();
+
+ // Due to bug 1205045, we must avoid making GTK calls off the main thread to
+ // query window size. Instead we just track the largest offset within the
+ // image we are drawing to and grow the image to accomodate it. Since usually
+ // the entire window is invalidated on the first paint to it, this should grow
+ // the image to the necessary size quickly without many intermediate
+ // reallocations.
+ IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+ IntSize size(bounds.XMost(), bounds.YMost());
+ if (size.width > mSize.width || size.height > mSize.height) {
+ DestroyImage();
+ if (!CreateImage(size)) {
+ return nullptr;
+ }
+ }
+
+ return gfxPlatform::CreateDrawTargetForData(
+ reinterpret_cast<unsigned char*>(mShmAddr) + bounds.y * mStride +
+ bounds.x * BytesPerPixel(mFormat),
+ bounds.Size(), mStride, mFormat);
+}
+
+void nsShmImage::Put(const mozilla::LayoutDeviceIntRegion& aRegion) {
+ AutoTArray<xcb_rectangle_t, 32> xrects;
+ xrects.SetCapacity(aRegion.GetNumRects());
+
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const mozilla::LayoutDeviceIntRect& r = iter.Get();
+ xcb_rectangle_t xrect = {(short)r.x, (short)r.y, (unsigned short)r.width,
+ (unsigned short)r.height};
+ xrects.AppendElement(xrect);
+ }
+
+ if (!mGC) {
+ mGC = xcb_generate_id(mConnection);
+ xcb_create_gc(mConnection, mGC, mWindow, 0, nullptr);
+ }
+
+ xcb_set_clip_rectangles(mConnection, XCB_CLIP_ORDERING_YX_BANDED, mGC, 0, 0,
+ xrects.Length(), xrects.Elements());
+
+ if (mPixmap != XCB_NONE) {
+ xcb_copy_area(mConnection, mPixmap, mWindow, mGC, 0, 0, 0, 0, mSize.width,
+ mSize.height);
+ } else {
+ xcb_shm_put_image(mConnection, mWindow, mGC, mSize.width, mSize.height, 0,
+ 0, mSize.width, mSize.height, 0, 0, mDepth,
+ XCB_IMAGE_FORMAT_Z_PIXMAP, 0, mShmSeg, 0);
+ }
+
+ // Send a request that returns a response so that we don't have to start a
+ // sync in nsShmImage::CreateDrawTarget.
+ mSyncRequest = xcb_get_input_focus(mConnection);
+ mRequestPending = true;
+
+ xcb_flush(mConnection);
+}
+
+#endif // MOZ_HAVE_SHMIMAGE