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
|