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