/* -*- 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 <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); 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; 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