1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
7 #include "nsShmImage.h"
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"
24 # include <X11/ImUtil.h>
27 using namespace mozilla::ipc
;
28 using namespace mozilla::gfx
;
30 nsShmImage::nsShmImage(Display
* aDisplay
, Drawable aWindow
, Visual
* aVisual
,
33 mConnection(XGetXCBConnection(aDisplay
)),
37 mFormat(mozilla::gfx::SurfaceFormat::UNKNOWN
),
42 mRequestPending(false),
46 mozilla::PodZero(&mSyncRequest
);
49 nsShmImage::~nsShmImage() { DestroyImage(); }
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;
54 bool nsShmImage::UseShm() { return gShmAvailable
; }
56 bool nsShmImage::CreateShmSegment() {
57 size_t size
= SharedMemory::PageAlignedSize(mStride
* mSize
.height
);
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__
));
65 mShmId
= shmget(IPC_PRIVATE
, size
, IPC_CREAT
| 0600);
69 mShmAddr
= (uint8_t*)shmat(mShmId
, nullptr, 0);
70 mShmSeg
= xcb_generate_id(mConnection
);
72 // Mark the handle removed so that it will destroy the segment when unmapped.
73 shmctl(mShmId
, IPC_RMID
, nullptr);
75 if (mShmAddr
== (void*)-1) {
76 // Since mapping failed, the segment is already destroyed.
79 nsPrintfCString
warning("shmat(): %s (%d)\n", strerror(errno
), errno
);
80 NS_WARNING(warning
.get());
86 if (shmctl(mShmId
, IPC_STAT
, &info
) < 0) {
90 MOZ_ASSERT(size
<= info
.shm_segsz
, "Segment doesn't have enough space!");
96 void nsShmImage::DestroyShmSegment() {
103 static bool gShmInitialized
= false;
104 static bool gUseShmPixmaps
= false;
106 bool nsShmImage::InitExtension() {
107 if (gShmInitialized
) {
108 return gShmAvailable
;
111 gShmInitialized
= true;
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;
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;
131 xcb_shm_query_version_reply_t
* shmReply
= xcb_shm_query_version_reply(
132 mConnection
, xcb_shm_query_version(mConnection
), nullptr);
135 gShmAvailable
= false;
139 gUseShmPixmaps
= shmReply
->shared_pixmaps
&&
140 shmReply
->pixmap_format
== XCB_IMAGE_FORMAT_Z_PIXMAP
;
147 bool nsShmImage::CreateImage(const IntSize
& aSize
) {
148 MOZ_ASSERT(mConnection
&& mVisual
);
150 if (!InitExtension()) {
156 BackendType backend
= gfxVars::ContentBackend();
158 mFormat
= SurfaceFormat::UNKNOWN
;
161 if (mVisual
->red_mask
== 0xff0000 && mVisual
->green_mask
== 0xff00 &&
162 mVisual
->blue_mask
== 0xff) {
163 mFormat
= SurfaceFormat::B8G8R8A8
;
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
;
178 if (mVisual
->red_mask
== 0xf800 && mVisual
->green_mask
== 0x07e0 &&
179 mVisual
->blue_mask
== 0x1f) {
180 mFormat
= SurfaceFormat::R5G6B5_UINT16
;
185 if (mFormat
== SurfaceFormat::UNKNOWN
) {
186 NS_WARNING("Unsupported XShm Image format!");
187 gShmAvailable
= false;
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
);
195 ((bitsPerPixel
* aSize
.width
+ scanlinePad
- 1) / scanlinePad
) *
197 mStride
= bitsPerLine
/ 8;
199 if (!CreateShmSegment()) {
204 xcb_generic_error_t
* error
;
205 xcb_void_cookie_t cookie
;
207 cookie
= xcb_shm_attach_checked(mConnection
, mShmSeg
, mShmId
, 0);
209 if ((error
= xcb_request_check(mConnection
, cookie
))) {
210 NS_WARNING("Failed to attach MIT-SHM segment.");
212 gShmAvailable
= false;
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
,
223 if ((error
= xcb_request_check(mConnection
, cookie
))) {
224 // Disable shared pixmaps permanently if creation failed.
226 gUseShmPixmaps
= false;
234 void nsShmImage::DestroyImage() {
236 xcb_free_gc(mConnection
, mGC
);
239 if (mPixmap
!= XCB_NONE
) {
240 xcb_free_pixmap(mConnection
, mPixmap
);
243 if (mShmSeg
!= XCB_NONE
) {
244 xcb_shm_detach_checked(mConnection
, mShmSeg
);
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();
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.
256 void nsShmImage::WaitIfPendingReply() {
257 if (mRequestPending
) {
258 xcb_get_input_focus_reply_t
* reply
=
259 xcb_get_input_focus_reply(mConnection
, mSyncRequest
, nullptr);
261 mRequestPending
= false;
265 already_AddRefed
<DrawTarget
> nsShmImage::CreateDrawTarget(
266 const mozilla::LayoutDeviceIntRegion
& aRegion
) {
267 WaitIfPendingReply();
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
275 IntRect bounds
= aRegion
.GetBounds().ToUnknownRect();
276 IntSize
size(bounds
.XMost(), bounds
.YMost());
277 if (size
.width
> mSize
.width
|| size
.height
> mSize
.height
) {
279 if (!CreateImage(size
)) {
284 return gfxPlatform::CreateDrawTargetForData(
285 reinterpret_cast<unsigned char*>(mShmAddr
) + bounds
.y
* mStride
+
286 bounds
.x
* BytesPerPixel(mFormat
),
287 bounds
.Size(), mStride
, mFormat
);
290 void nsShmImage::Put(const mozilla::LayoutDeviceIntRegion
& aRegion
) {
291 AutoTArray
<xcb_rectangle_t
, 32> xrects
;
292 xrects
.SetCapacity(aRegion
.GetNumRects());
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
);
302 mGC
= xcb_generate_id(mConnection
);
303 xcb_create_gc(mConnection
, mGC
, mWindow
, 0, nullptr);
306 xcb_set_clip_rectangles(mConnection
, XCB_CLIP_ORDERING_YX_BANDED
, mGC
, 0, 0,
307 xrects
.Length(), xrects
.Elements());
309 if (mPixmap
!= XCB_NONE
) {
310 xcb_copy_area(mConnection
, mPixmap
, mWindow
, mGC
, 0, 0, 0, 0, mSize
.width
,
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);
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;
323 xcb_flush(mConnection
);
326 #endif // MOZ_HAVE_SHMIMAGE