LCOV - code coverage report
Current view: top level - widget - nsShmImage.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 113 156 72.4 %
Date: 2017-07-14 16:53:18 Functions: 10 11 90.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
       2             :  *
       3             :  * This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "nsShmImage.h"
       8             : 
       9             : #ifdef MOZ_HAVE_SHMIMAGE
      10             : #include "mozilla/X11Util.h"
      11             : #include "mozilla/gfx/gfxVars.h"
      12             : #include "mozilla/ipc/SharedMemory.h"
      13             : #include "gfxPlatform.h"
      14             : #include "nsPrintfCString.h"
      15             : #include "nsTArray.h"
      16             : 
      17             : #include <errno.h>
      18             : #include <string.h>
      19             : #include <sys/ipc.h>
      20             : #include <sys/shm.h>
      21             : 
      22             : extern "C" {
      23             : #include <X11/ImUtil.h>
      24             : }
      25             : 
      26             : using namespace mozilla::ipc;
      27             : using namespace mozilla::gfx;
      28             : 
      29           2 : nsShmImage::nsShmImage(Display* aDisplay,
      30             :                        Drawable aWindow,
      31             :                        Visual* aVisual,
      32           2 :                        unsigned int aDepth)
      33             :   : mDisplay(aDisplay)
      34           2 :   , mConnection(XGetXCBConnection(aDisplay))
      35             :   , mWindow(aWindow)
      36             :   , mVisual(aVisual)
      37             :   , mDepth(aDepth)
      38             :   , mFormat(mozilla::gfx::SurfaceFormat::UNKNOWN)
      39             :   , mSize(0, 0)
      40             :   , mStride(0)
      41             :   , mPixmap(XCB_NONE)
      42             :   , mGC(XCB_NONE)
      43             :   , mRequestPending(false)
      44             :   , mShmSeg(XCB_NONE)
      45             :   , mShmId(-1)
      46           4 :   , mShmAddr(nullptr)
      47             : {
      48           2 :   mozilla::PodZero(&mSyncRequest);
      49           2 : }
      50             : 
      51           0 : nsShmImage::~nsShmImage()
      52             : {
      53           0 :   DestroyImage();
      54           0 : }
      55             : 
      56             : // If XShm isn't available to our client, we'll try XShm once, fail,
      57             : // set this to false and then never try again.
      58             : static bool gShmAvailable = true;
      59           1 : bool nsShmImage::UseShm()
      60             : {
      61           1 :   return gShmAvailable;
      62             : }
      63             : 
      64             : bool
      65           4 : nsShmImage::CreateShmSegment()
      66             : {
      67           4 :   size_t size = SharedMemory::PageAlignedSize(mStride * mSize.height);
      68             : 
      69           4 :   mShmId = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
      70           4 :   if (mShmId == -1) {
      71           0 :     return false;
      72             :   }
      73           4 :   mShmAddr = (uint8_t*) shmat(mShmId, nullptr, 0);
      74           4 :   mShmSeg = xcb_generate_id(mConnection);
      75             : 
      76             :   // Mark the handle removed so that it will destroy the segment when unmapped.
      77           4 :   shmctl(mShmId, IPC_RMID, nullptr);
      78             : 
      79           4 :   if (mShmAddr == (void *)-1) {
      80             :     // Since mapping failed, the segment is already destroyed.
      81           0 :     mShmId = -1;
      82             : 
      83           0 :     nsPrintfCString warning("shmat(): %s (%d)\n", strerror(errno), errno);
      84           0 :     NS_WARNING(warning.get());
      85           0 :     return false;
      86             :   }
      87             : 
      88             : #ifdef DEBUG
      89             :   struct shmid_ds info;
      90           4 :   if (shmctl(mShmId, IPC_STAT, &info) < 0) {
      91           0 :     return false;
      92             :   }
      93             : 
      94           4 :   MOZ_ASSERT(size <= info.shm_segsz,
      95             :              "Segment doesn't have enough space!");
      96             : #endif
      97             : 
      98           4 :   return true;
      99             : }
     100             : 
     101             : void
     102           4 : nsShmImage::DestroyShmSegment()
     103             : {
     104           4 :   if (mShmId != -1) {
     105           2 :     shmdt(mShmAddr);
     106           2 :     mShmId = -1;
     107             :   }
     108           4 : }
     109             : 
     110             : static bool gShmInitialized = false;
     111             : static bool gUseShmPixmaps = false;
     112             : 
     113             : bool
     114           4 : nsShmImage::InitExtension()
     115             : {
     116           4 :   if (gShmInitialized) {
     117           3 :     return gShmAvailable;
     118             :   }
     119             : 
     120           1 :   gShmInitialized = true;
     121             : 
     122             :   const xcb_query_extension_reply_t* extReply;
     123           1 :   extReply = xcb_get_extension_data(mConnection, &xcb_shm_id);
     124           1 :   if (!extReply || !extReply->present) {
     125           0 :     gShmAvailable = false;
     126           0 :     return false;
     127             :   }
     128             : 
     129           1 :   xcb_shm_query_version_reply_t* shmReply = xcb_shm_query_version_reply(
     130             :       mConnection,
     131             :       xcb_shm_query_version(mConnection),
     132           1 :       nullptr);
     133             : 
     134           1 :   if (!shmReply) {
     135           0 :     gShmAvailable = false;
     136           0 :     return false;
     137             :   }
     138             : 
     139           2 :   gUseShmPixmaps = shmReply->shared_pixmaps &&
     140           1 :                    shmReply->pixmap_format == XCB_IMAGE_FORMAT_Z_PIXMAP;
     141             : 
     142           1 :   free(shmReply);
     143             : 
     144           1 :   return true;
     145             : }
     146             : 
     147             : bool
     148           4 : nsShmImage::CreateImage(const IntSize& aSize)
     149             : {
     150           4 :   MOZ_ASSERT(mConnection && mVisual);
     151             : 
     152           4 :   if (!InitExtension()) {
     153           0 :     return false;
     154             :   }
     155             : 
     156           4 :   mSize = aSize;
     157             : 
     158           4 :   BackendType backend = gfxVars::ContentBackend();
     159             : 
     160           4 :   mFormat = SurfaceFormat::UNKNOWN;
     161           4 :   switch (mDepth) {
     162             :   case 32:
     163           0 :     if (mVisual->red_mask == 0xff0000 &&
     164           0 :         mVisual->green_mask == 0xff00 &&
     165           0 :         mVisual->blue_mask == 0xff) {
     166           0 :       mFormat = SurfaceFormat::B8G8R8A8;
     167             :     }
     168           0 :     break;
     169             :   case 24:
     170             :     // Only support the BGRX layout, and report it as BGRA to the compositor.
     171             :     // The alpha channel will be discarded when we put the image.
     172             :     // Cairo/pixman lacks some fast paths for compositing BGRX onto BGRA, so
     173             :     // just report it as BGRX directly in that case.
     174           8 :     if (mVisual->red_mask == 0xff0000 &&
     175           8 :         mVisual->green_mask == 0xff00 &&
     176           4 :         mVisual->blue_mask == 0xff) {
     177           4 :       mFormat = backend == BackendType::CAIRO ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
     178             :     }
     179           4 :     break;
     180             :   case 16:
     181           0 :     if (mVisual->red_mask == 0xf800 &&
     182           0 :         mVisual->green_mask == 0x07e0 &&
     183           0 :         mVisual->blue_mask == 0x1f) {
     184           0 :       mFormat = SurfaceFormat::R5G6B5_UINT16;
     185             :     }
     186           0 :     break;
     187             :   }
     188             : 
     189           4 :   if (mFormat == SurfaceFormat::UNKNOWN) {
     190           0 :     NS_WARNING("Unsupported XShm Image format!");
     191           0 :     gShmAvailable = false;
     192           0 :     return false;
     193             :   }
     194             : 
     195             :   // Round up stride to the display's scanline pad (in bits) as XShm expects.
     196           4 :   int scanlinePad = _XGetScanlinePad(mDisplay, mDepth);
     197           4 :   int bitsPerPixel = _XGetBitsPerPixel(mDisplay, mDepth);
     198           4 :   int bitsPerLine = ((bitsPerPixel * aSize.width + scanlinePad - 1)
     199           4 :                      / scanlinePad) * scanlinePad;
     200           4 :   mStride = bitsPerLine / 8;
     201             : 
     202           4 :   if (!CreateShmSegment()) {
     203           0 :     DestroyImage();
     204           0 :     return false;
     205             :   }
     206             : 
     207             :   xcb_generic_error_t* error;
     208             :   xcb_void_cookie_t cookie;
     209             : 
     210           4 :   cookie = xcb_shm_attach_checked(mConnection, mShmSeg, mShmId, 0);
     211             : 
     212           4 :   if ((error = xcb_request_check(mConnection, cookie))) {
     213           0 :     NS_WARNING("Failed to attach MIT-SHM segment.");
     214           0 :     DestroyImage();
     215           0 :     gShmAvailable = false;
     216           0 :     free(error);
     217           0 :     return false;
     218             :   }
     219             : 
     220           4 :   if (gUseShmPixmaps) {
     221           4 :     mPixmap = xcb_generate_id(mConnection);
     222           4 :     cookie = xcb_shm_create_pixmap_checked(mConnection, mPixmap, mWindow,
     223          12 :                                            aSize.width, aSize.height, mDepth,
     224          20 :                                            mShmSeg, 0);
     225             : 
     226           4 :     if ((error = xcb_request_check(mConnection, cookie))) {
     227             :       // Disable shared pixmaps permanently if creation failed.
     228           0 :       mPixmap = XCB_NONE;
     229           0 :       gUseShmPixmaps = false;
     230           0 :       free(error);
     231             :     }
     232             :   }
     233             : 
     234           4 :   return true;
     235             : }
     236             : 
     237             : void
     238           4 : nsShmImage::DestroyImage()
     239             : {
     240           4 :   if (mGC) {
     241           2 :     xcb_free_gc(mConnection, mGC);
     242           2 :     mGC = XCB_NONE;
     243             :   }
     244           4 :   if (mPixmap != XCB_NONE) {
     245           2 :     xcb_free_pixmap(mConnection, mPixmap);
     246           2 :     mPixmap = XCB_NONE;
     247             :   }
     248           4 :   if (mShmSeg != XCB_NONE) {
     249           2 :     xcb_shm_detach_checked(mConnection, mShmSeg);
     250           2 :     mShmSeg = XCB_NONE;
     251             :   }
     252           4 :   DestroyShmSegment();
     253             :   // Avoid leaking any pending reply.  No real need to wait but CentOS 6 build
     254             :   // machines don't have xcb_discard_reply().
     255           4 :   WaitIfPendingReply();
     256           4 : }
     257             : 
     258             : // Wait for any in-flight shm-affected requests to complete.
     259             : // Typically X clients would wait for a XShmCompletionEvent to be received,
     260             : // but this works as it's sent immediately after the request is sent.
     261             : void
     262          31 : nsShmImage::WaitIfPendingReply()
     263             : {
     264          31 :   if (mRequestPending) {
     265             :     xcb_get_input_focus_reply_t* reply =
     266          25 :       xcb_get_input_focus_reply(mConnection, mSyncRequest, nullptr);
     267          25 :     free(reply);
     268          25 :     mRequestPending = false;
     269             :   }
     270          31 : }
     271             : 
     272             : already_AddRefed<DrawTarget>
     273          27 : nsShmImage::CreateDrawTarget(const mozilla::LayoutDeviceIntRegion& aRegion)
     274             : {
     275          27 :   WaitIfPendingReply();
     276             : 
     277             :   // Due to bug 1205045, we must avoid making GTK calls off the main thread to query window size.
     278             :   // Instead we just track the largest offset within the image we are drawing to and grow the image
     279             :   // to accomodate it. Since usually the entire window is invalidated on the first paint to it,
     280             :   // this should grow the image to the necessary size quickly without many intermediate reallocations.
     281          27 :   IntRect bounds = aRegion.GetBounds().ToUnknownRect();
     282          27 :   IntSize size(bounds.XMost(), bounds.YMost());
     283          27 :   if (size.width > mSize.width || size.height > mSize.height) {
     284           4 :     DestroyImage();
     285           4 :     if (!CreateImage(size)) {
     286           0 :       return nullptr;
     287             :     }
     288             :   }
     289             : 
     290             :   return gfxPlatform::CreateDrawTargetForData(
     291          27 :     reinterpret_cast<unsigned char*>(mShmAddr)
     292          27 :       + bounds.y * mStride + bounds.x * BytesPerPixel(mFormat),
     293          54 :     bounds.Size(),
     294             :     mStride,
     295          54 :     mFormat);
     296             : }
     297             : 
     298             : void
     299          27 : nsShmImage::Put(const mozilla::LayoutDeviceIntRegion& aRegion)
     300             : {
     301          54 :   AutoTArray<xcb_rectangle_t, 32> xrects;
     302          27 :   xrects.SetCapacity(aRegion.GetNumRects());
     303             : 
     304          86 :   for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
     305          59 :     const mozilla::LayoutDeviceIntRect &r = iter.Get();
     306          59 :     xcb_rectangle_t xrect = { (short)r.x, (short)r.y, (unsigned short)r.width, (unsigned short)r.height };
     307          59 :     xrects.AppendElement(xrect);
     308             :   }
     309             : 
     310          27 :   if (!mGC) {
     311           4 :     mGC = xcb_generate_id(mConnection);
     312           4 :     xcb_create_gc(mConnection, mGC, mWindow, 0, nullptr);
     313             :   }
     314             : 
     315             :   xcb_set_clip_rectangles(mConnection, XCB_CLIP_ORDERING_YX_BANDED, mGC, 0, 0,
     316          27 :                           xrects.Length(), xrects.Elements());
     317             : 
     318          27 :   if (mPixmap != XCB_NONE) {
     319          27 :     xcb_copy_area(mConnection, mPixmap, mWindow, mGC,
     320          54 :                   0, 0, 0, 0, mSize.width, mSize.height);
     321             :   } else {
     322           0 :     xcb_shm_put_image(mConnection, mWindow, mGC,
     323           0 :                       mSize.width, mSize.height,
     324           0 :                       0, 0, mSize.width, mSize.height,
     325           0 :                       0, 0, mDepth,
     326             :                       XCB_IMAGE_FORMAT_Z_PIXMAP, 0,
     327           0 :                       mShmSeg, 0);
     328             :   }
     329             : 
     330             :   // Send a request that returns a response so that we don't have to start a
     331             :   // sync in nsShmImage::CreateDrawTarget.
     332          27 :   mSyncRequest = xcb_get_input_focus(mConnection);
     333          27 :   mRequestPending = true;
     334             : 
     335          27 :   xcb_flush(mConnection);
     336          27 : }
     337             : 
     338             : #endif  // MOZ_HAVE_SHMIMAGE

Generated by: LCOV version 1.13