1/*
2 *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
12
13#include <assert.h>
14#include <string.h>
15#include <sys/shm.h>
16
17#include "webrtc/modules/desktop_capture/desktop_frame.h"
18#include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
19#include "webrtc/system_wrappers/interface/logging.h"
20
21namespace {
22
23// Returns the number of bits |mask| has to be shifted left so its last
24// (most-significant) bit set becomes the most-significant bit of the word.
25// When |mask| is 0 the function returns 31.
26uint32_t MaskToShift(uint32_t mask) {
27  int shift = 0;
28  if ((mask & 0xffff0000u) == 0) {
29    mask <<= 16;
30    shift += 16;
31  }
32  if ((mask & 0xff000000u) == 0) {
33    mask <<= 8;
34    shift += 8;
35  }
36  if ((mask & 0xf0000000u) == 0) {
37    mask <<= 4;
38    shift += 4;
39  }
40  if ((mask & 0xc0000000u) == 0) {
41    mask <<= 2;
42    shift += 2;
43  }
44  if ((mask & 0x80000000u) == 0)
45    shift += 1;
46
47  return shift;
48}
49
50// Returns true if |image| is in RGB format.
51bool IsXImageRGBFormat(XImage* image) {
52  return image->bits_per_pixel == 32 &&
53      image->red_mask == 0xff0000 &&
54      image->green_mask == 0xff00 &&
55      image->blue_mask == 0xff;
56}
57
58}  // namespace
59
60namespace webrtc {
61
62XServerPixelBuffer::XServerPixelBuffer()
63    : display_(NULL), window_(0),
64      x_image_(NULL),
65      shm_segment_info_(NULL), shm_pixmap_(0), shm_gc_(NULL) {
66}
67
68XServerPixelBuffer::~XServerPixelBuffer() {
69  Release();
70}
71
72void XServerPixelBuffer::Release() {
73  if (x_image_) {
74    XDestroyImage(x_image_);
75    x_image_ = NULL;
76  }
77  if (shm_pixmap_) {
78    XFreePixmap(display_, shm_pixmap_);
79    shm_pixmap_ = 0;
80  }
81  if (shm_gc_) {
82    XFreeGC(display_, shm_gc_);
83    shm_gc_ = NULL;
84  }
85  if (shm_segment_info_) {
86    if (shm_segment_info_->shmaddr != reinterpret_cast<char*>(-1))
87      shmdt(shm_segment_info_->shmaddr);
88    if (shm_segment_info_->shmid != -1)
89      shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
90    delete shm_segment_info_;
91    shm_segment_info_ = NULL;
92  }
93  window_ = 0;
94}
95
96bool XServerPixelBuffer::Init(Display* display, Window window) {
97  Release();
98  display_ = display;
99
100  XWindowAttributes attributes;
101  {
102    XErrorTrap error_trap(display_);
103    if (!XGetWindowAttributes(display_, window, &attributes) ||
104        error_trap.GetLastErrorAndDisable() != 0) {
105      return false;
106    }
107  }
108
109  window_size_ = DesktopSize(attributes.width, attributes.height);
110  window_ = window;
111  InitShm(attributes);
112
113  return true;
114}
115
116void XServerPixelBuffer::InitShm(const XWindowAttributes& attributes) {
117  Visual* default_visual = attributes.visual;
118  int default_depth = attributes.depth;
119
120  int major, minor;
121  Bool have_pixmaps;
122  if (!XShmQueryVersion(display_, &major, &minor, &have_pixmaps)) {
123    // Shared memory not supported. CaptureRect will use the XImage API instead.
124    return;
125  }
126
127  bool using_shm = false;
128  shm_segment_info_ = new XShmSegmentInfo;
129  shm_segment_info_->shmid = -1;
130  shm_segment_info_->shmaddr = reinterpret_cast<char*>(-1);
131  shm_segment_info_->readOnly = False;
132  x_image_ = XShmCreateImage(display_, default_visual, default_depth, ZPixmap,
133                             0, shm_segment_info_, window_size_.width(),
134                             window_size_.height());
135  if (x_image_) {
136    shm_segment_info_->shmid = shmget(
137        IPC_PRIVATE, x_image_->bytes_per_line * x_image_->height,
138        IPC_CREAT | 0600);
139    if (shm_segment_info_->shmid != -1) {
140      shm_segment_info_->shmaddr = x_image_->data =
141          reinterpret_cast<char*>(shmat(shm_segment_info_->shmid, 0, 0));
142      if (x_image_->data != reinterpret_cast<char*>(-1)) {
143        XErrorTrap error_trap(display_);
144        using_shm = XShmAttach(display_, shm_segment_info_);
145        XSync(display_, False);
146        if (error_trap.GetLastErrorAndDisable() != 0)
147          using_shm = false;
148        if (using_shm) {
149          LOG(LS_VERBOSE) << "Using X shared memory segment "
150                          << shm_segment_info_->shmid;
151        }
152      }
153    } else {
154      LOG(LS_WARNING) << "Failed to get shared memory segment. "
155                      "Performance may be degraded.";
156    }
157  }
158
159  if (!using_shm) {
160    LOG(LS_WARNING) << "Not using shared memory. Performance may be degraded.";
161    Release();
162    return;
163  }
164
165  if (have_pixmaps)
166    have_pixmaps = InitPixmaps(default_depth);
167
168  shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
169  shm_segment_info_->shmid = -1;
170
171  LOG(LS_VERBOSE) << "Using X shared memory extension v"
172                  << major << "." << minor
173                  << " with" << (have_pixmaps ? "" : "out") << " pixmaps.";
174}
175
176bool XServerPixelBuffer::InitPixmaps(int depth) {
177  if (XShmPixmapFormat(display_) != ZPixmap)
178    return false;
179
180  {
181    XErrorTrap error_trap(display_);
182    shm_pixmap_ = XShmCreatePixmap(display_, window_,
183                                   shm_segment_info_->shmaddr,
184                                   shm_segment_info_,
185                                   window_size_.width(),
186                                   window_size_.height(), depth);
187    XSync(display_, False);
188    if (error_trap.GetLastErrorAndDisable() != 0) {
189      // |shm_pixmap_| is not not valid because the request was not processed
190      // by the X Server, so zero it.
191      shm_pixmap_ = 0;
192      return false;
193    }
194  }
195
196  {
197    XErrorTrap error_trap(display_);
198    XGCValues shm_gc_values;
199    shm_gc_values.subwindow_mode = IncludeInferiors;
200    shm_gc_values.graphics_exposures = False;
201    shm_gc_ = XCreateGC(display_, window_,
202                        GCSubwindowMode | GCGraphicsExposures,
203                        &shm_gc_values);
204    XSync(display_, False);
205    if (error_trap.GetLastErrorAndDisable() != 0) {
206      XFreePixmap(display_, shm_pixmap_);
207      shm_pixmap_ = 0;
208      shm_gc_ = 0;  // See shm_pixmap_ comment above.
209      return false;
210    }
211  }
212
213  return true;
214}
215
216bool XServerPixelBuffer::IsWindowValid() const {
217  XWindowAttributes attributes;
218  {
219    XErrorTrap error_trap(display_);
220    if (!XGetWindowAttributes(display_, window_, &attributes) ||
221        error_trap.GetLastErrorAndDisable() != 0) {
222      return false;
223    }
224  }
225  return true;
226}
227
228void XServerPixelBuffer::Synchronize() {
229  if (shm_segment_info_ && !shm_pixmap_) {
230    // XShmGetImage can fail if the display is being reconfigured.
231    XErrorTrap error_trap(display_);
232    XShmGetImage(display_, window_, x_image_, 0, 0, AllPlanes);
233  }
234}
235
236void XServerPixelBuffer::CaptureRect(const DesktopRect& rect,
237                                     DesktopFrame* frame) {
238  assert(rect.right() <= window_size_.width());
239  assert(rect.bottom() <= window_size_.height());
240
241  uint8_t* data;
242
243  if (shm_segment_info_) {
244    if (shm_pixmap_) {
245      XCopyArea(display_, window_, shm_pixmap_, shm_gc_,
246                rect.left(), rect.top(), rect.width(), rect.height(),
247                rect.left(), rect.top());
248      XSync(display_, False);
249    }
250    data = reinterpret_cast<uint8_t*>(x_image_->data) +
251        rect.top() * x_image_->bytes_per_line +
252        rect.left() * x_image_->bits_per_pixel / 8;
253  } else {
254    if (x_image_)
255      XDestroyImage(x_image_);
256    x_image_ = XGetImage(display_, window_, rect.left(), rect.top(),
257                         rect.width(), rect.height(), AllPlanes, ZPixmap);
258    data = reinterpret_cast<uint8_t*>(x_image_->data);
259  }
260
261  if (IsXImageRGBFormat(x_image_)) {
262    FastBlit(data, rect, frame);
263  } else {
264    SlowBlit(data, rect, frame);
265  }
266}
267
268void XServerPixelBuffer::FastBlit(uint8_t* image,
269                                  const DesktopRect& rect,
270                                  DesktopFrame* frame) {
271  uint8_t* src_pos = image;
272  int src_stride = x_image_->bytes_per_line;
273  int dst_x = rect.left(), dst_y = rect.top();
274
275  uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
276  dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
277
278  int height = rect.height();
279  int row_bytes = rect.width() * DesktopFrame::kBytesPerPixel;
280  for (int y = 0; y < height; ++y) {
281    memcpy(dst_pos, src_pos, row_bytes);
282    src_pos += src_stride;
283    dst_pos += frame->stride();
284  }
285}
286
287void XServerPixelBuffer::SlowBlit(uint8_t* image,
288                                  const DesktopRect& rect,
289                                  DesktopFrame* frame) {
290  int src_stride = x_image_->bytes_per_line;
291  int dst_x = rect.left(), dst_y = rect.top();
292  int width = rect.width(), height = rect.height();
293
294  uint32_t red_mask = x_image_->red_mask;
295  uint32_t green_mask = x_image_->red_mask;
296  uint32_t blue_mask = x_image_->blue_mask;
297
298  uint32_t red_shift = MaskToShift(red_mask);
299  uint32_t green_shift = MaskToShift(green_mask);
300  uint32_t blue_shift = MaskToShift(blue_mask);
301
302  int bits_per_pixel = x_image_->bits_per_pixel;
303
304  uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
305  uint8_t* src_pos = image;
306  dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
307  // TODO(hclam): Optimize, perhaps using MMX code or by converting to
308  // YUV directly.
309  // TODO(sergeyu): This code doesn't handle XImage byte order properly and
310  // won't work with 24bpp images. Fix it.
311  for (int y = 0; y < height; y++) {
312    uint32_t* dst_pos_32 = reinterpret_cast<uint32_t*>(dst_pos);
313    uint32_t* src_pos_32 = reinterpret_cast<uint32_t*>(src_pos);
314    uint16_t* src_pos_16 = reinterpret_cast<uint16_t*>(src_pos);
315    for (int x = 0; x < width; x++) {
316      // Dereference through an appropriately-aligned pointer.
317      uint32_t pixel;
318      if (bits_per_pixel == 32) {
319        pixel = src_pos_32[x];
320      } else if (bits_per_pixel == 16) {
321        pixel = src_pos_16[x];
322      } else {
323        pixel = src_pos[x];
324      }
325      uint32_t r = (pixel & red_mask) << red_shift;
326      uint32_t g = (pixel & green_mask) << green_shift;
327      uint32_t b = (pixel & blue_mask) << blue_shift;
328      // Write as 32-bit RGB.
329      dst_pos_32[x] = ((r >> 8) & 0xff0000) | ((g >> 16) & 0xff00) |
330          ((b >> 24) & 0xff);
331    }
332    dst_pos += frame->stride();
333    src_pos += src_stride;
334  }
335}
336
337}  // namespace webrtc
338