diff options
Diffstat (limited to 'webrtc/modules/desktop_capture/x11')
6 files changed, 701 insertions, 0 deletions
diff --git a/webrtc/modules/desktop_capture/x11/shared_x_display.cc b/webrtc/modules/desktop_capture/x11/shared_x_display.cc new file mode 100644 index 0000000000..3eb5eb10a9 --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/shared_x_display.cc @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/x11/shared_x_display.h" + +#include <algorithm> + +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +SharedXDisplay::SharedXDisplay(Display* display) + : display_(display) { + assert(display_); +} + +SharedXDisplay::~SharedXDisplay() { + assert(event_handlers_.empty()); + XCloseDisplay(display_); +} + +// static +rtc::scoped_refptr<SharedXDisplay> SharedXDisplay::Create( + const std::string& display_name) { + Display* display = + XOpenDisplay(display_name.empty() ? NULL : display_name.c_str()); + if (!display) { + LOG(LS_ERROR) << "Unable to open display"; + return NULL; + } + return new SharedXDisplay(display); +} + +// static +rtc::scoped_refptr<SharedXDisplay> SharedXDisplay::CreateDefault() { + return Create(std::string()); +} + +void SharedXDisplay::AddEventHandler(int type, XEventHandler* handler) { + event_handlers_[type].push_back(handler); +} + +void SharedXDisplay::RemoveEventHandler(int type, XEventHandler* handler) { + EventHandlersMap::iterator handlers = event_handlers_.find(type); + if (handlers == event_handlers_.end()) + return; + + std::vector<XEventHandler*>::iterator new_end = + std::remove(handlers->second.begin(), handlers->second.end(), handler); + handlers->second.erase(new_end, handlers->second.end()); + + // Check if no handlers left for this event. + if (handlers->second.empty()) + event_handlers_.erase(handlers); +} + +void SharedXDisplay::ProcessPendingXEvents() { + // Hold reference to |this| to prevent it from being destroyed while + // processing events. + rtc::scoped_refptr<SharedXDisplay> self(this); + + // Find the number of events that are outstanding "now." We don't just loop + // on XPending because we want to guarantee this terminates. + int events_to_process = XPending(display()); + XEvent e; + + for (int i = 0; i < events_to_process; i++) { + XNextEvent(display(), &e); + EventHandlersMap::iterator handlers = event_handlers_.find(e.type); + if (handlers == event_handlers_.end()) + continue; + for (std::vector<XEventHandler*>::iterator it = handlers->second.begin(); + it != handlers->second.end(); ++it) { + if ((*it)->HandleXEvent(e)) + break; + } + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/x11/shared_x_display.h b/webrtc/modules/desktop_capture/x11/shared_x_display.h new file mode 100644 index 0000000000..d905b9e51c --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/shared_x_display.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_X11_SHARED_X_DISPLAY_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_X11_SHARED_X_DISPLAY_H_ + +#include <map> +#include <vector> + +#include <assert.h> +#include <X11/Xlib.h> + +#include <string> + +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/system_wrappers/include/atomic32.h" + +namespace webrtc { + +// A ref-counted object to store XDisplay connection. +class SharedXDisplay { + public: + class XEventHandler { + public: + virtual ~XEventHandler() {} + + // Processes XEvent. Returns true if the event has been handled. + virtual bool HandleXEvent(const XEvent& event) = 0; + }; + + // Takes ownership of |display|. + explicit SharedXDisplay(Display* display); + + // Creates a new X11 Display for the |display_name|. NULL is returned if X11 + // connection failed. Equivalent to CreateDefault() when |display_name| is + // empty. + static rtc::scoped_refptr<SharedXDisplay> Create( + const std::string& display_name); + + // Creates X11 Display connection for the default display (e.g. specified in + // DISPLAY). NULL is returned if X11 connection failed. + static rtc::scoped_refptr<SharedXDisplay> CreateDefault(); + + void AddRef() { ++ref_count_; } + void Release() { + if (--ref_count_ == 0) + delete this; + } + + Display* display() { return display_; } + + // Adds a new event |handler| for XEvent's of |type|. + void AddEventHandler(int type, XEventHandler* handler); + + // Removes event |handler| added using |AddEventHandler|. Doesn't do anything + // if |handler| is not registered. + void RemoveEventHandler(int type, XEventHandler* handler); + + // Processes pending XEvents, calling corresponding event handlers. + void ProcessPendingXEvents(); + + private: + typedef std::map<int, std::vector<XEventHandler*> > EventHandlersMap; + + ~SharedXDisplay(); + + Atomic32 ref_count_; + Display* display_; + + EventHandlersMap event_handlers_; + + RTC_DISALLOW_COPY_AND_ASSIGN(SharedXDisplay); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_X11_SHARED_X_DISPLAY_H_ diff --git a/webrtc/modules/desktop_capture/x11/x_error_trap.cc b/webrtc/modules/desktop_capture/x11/x_error_trap.cc new file mode 100644 index 0000000000..458fbe61a0 --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/x_error_trap.cc @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/x11/x_error_trap.h" + +#include <assert.h> + +#if defined(TOOLKIT_GTK) +#include <gdk/gdk.h> +#endif // !defined(TOOLKIT_GTK) + +namespace webrtc { + +namespace { + +#if !defined(TOOLKIT_GTK) + +// TODO(sergeyu): This code is not thread safe. Fix it. Bug 2202. +static bool g_xserver_error_trap_enabled = false; +static int g_last_xserver_error_code = 0; + +int XServerErrorHandler(Display* display, XErrorEvent* error_event) { + assert(g_xserver_error_trap_enabled); + g_last_xserver_error_code = error_event->error_code; + return 0; +} + +#endif // !defined(TOOLKIT_GTK) + +} // namespace + +XErrorTrap::XErrorTrap(Display* display) + : original_error_handler_(NULL), + enabled_(true) { +#if defined(TOOLKIT_GTK) + gdk_error_trap_push(); +#else // !defined(TOOLKIT_GTK) + assert(!g_xserver_error_trap_enabled); + original_error_handler_ = XSetErrorHandler(&XServerErrorHandler); + g_xserver_error_trap_enabled = true; + g_last_xserver_error_code = 0; +#endif // !defined(TOOLKIT_GTK) +} + +int XErrorTrap::GetLastErrorAndDisable() { + enabled_ = false; +#if defined(TOOLKIT_GTK) + return gdk_error_trap_push(); +#else // !defined(TOOLKIT_GTK) + assert(g_xserver_error_trap_enabled); + XSetErrorHandler(original_error_handler_); + g_xserver_error_trap_enabled = false; + return g_last_xserver_error_code; +#endif // !defined(TOOLKIT_GTK) +} + +XErrorTrap::~XErrorTrap() { + if (enabled_) + GetLastErrorAndDisable(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/x11/x_error_trap.h b/webrtc/modules/desktop_capture/x11/x_error_trap.h new file mode 100644 index 0000000000..f1f6e11c63 --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/x_error_trap.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_ERROR_TRAP_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_ERROR_TRAP_H_ + +#include <X11/Xlib.h> + +#include "webrtc/base/constructormagic.h" + +namespace webrtc { + +// Helper class that registers X Window error handler. Caller can use +// GetLastErrorAndDisable() to get the last error that was caught, if any. +class XErrorTrap { + public: + explicit XErrorTrap(Display* display); + ~XErrorTrap(); + + // Returns last error and removes unregisters the error handler. + int GetLastErrorAndDisable(); + + private: + XErrorHandler original_error_handler_; + bool enabled_; + + RTC_DISALLOW_COPY_AND_ASSIGN(XErrorTrap); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_ERROR_TRAP_H_ diff --git a/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc new file mode 100644 index 0000000000..bcfcb7e027 --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h" + +#include <assert.h> +#include <string.h> +#include <sys/shm.h> + +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/x11/x_error_trap.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace { + +// Returns the number of bits |mask| has to be shifted left so its last +// (most-significant) bit set becomes the most-significant bit of the word. +// When |mask| is 0 the function returns 31. +uint32_t MaskToShift(uint32_t mask) { + int shift = 0; + if ((mask & 0xffff0000u) == 0) { + mask <<= 16; + shift += 16; + } + if ((mask & 0xff000000u) == 0) { + mask <<= 8; + shift += 8; + } + if ((mask & 0xf0000000u) == 0) { + mask <<= 4; + shift += 4; + } + if ((mask & 0xc0000000u) == 0) { + mask <<= 2; + shift += 2; + } + if ((mask & 0x80000000u) == 0) + shift += 1; + + return shift; +} + +// Returns true if |image| is in RGB format. +bool IsXImageRGBFormat(XImage* image) { + return image->bits_per_pixel == 32 && + image->red_mask == 0xff0000 && + image->green_mask == 0xff00 && + image->blue_mask == 0xff; +} + +} // namespace + +namespace webrtc { + +XServerPixelBuffer::XServerPixelBuffer() + : display_(NULL), window_(0), + x_image_(NULL), + shm_segment_info_(NULL), shm_pixmap_(0), shm_gc_(NULL) { +} + +XServerPixelBuffer::~XServerPixelBuffer() { + Release(); +} + +void XServerPixelBuffer::Release() { + if (x_image_) { + XDestroyImage(x_image_); + x_image_ = NULL; + } + if (shm_pixmap_) { + XFreePixmap(display_, shm_pixmap_); + shm_pixmap_ = 0; + } + if (shm_gc_) { + XFreeGC(display_, shm_gc_); + shm_gc_ = NULL; + } + if (shm_segment_info_) { + if (shm_segment_info_->shmaddr != reinterpret_cast<char*>(-1)) + shmdt(shm_segment_info_->shmaddr); + if (shm_segment_info_->shmid != -1) + shmctl(shm_segment_info_->shmid, IPC_RMID, 0); + delete shm_segment_info_; + shm_segment_info_ = NULL; + } + window_ = 0; +} + +bool XServerPixelBuffer::Init(Display* display, Window window) { + Release(); + display_ = display; + + XWindowAttributes attributes; + { + XErrorTrap error_trap(display_); + if (!XGetWindowAttributes(display_, window, &attributes) || + error_trap.GetLastErrorAndDisable() != 0) { + return false; + } + } + + window_size_ = DesktopSize(attributes.width, attributes.height); + window_ = window; + InitShm(attributes); + + return true; +} + +void XServerPixelBuffer::InitShm(const XWindowAttributes& attributes) { + Visual* default_visual = attributes.visual; + int default_depth = attributes.depth; + + int major, minor; + Bool have_pixmaps; + if (!XShmQueryVersion(display_, &major, &minor, &have_pixmaps)) { + // Shared memory not supported. CaptureRect will use the XImage API instead. + return; + } + + bool using_shm = false; + shm_segment_info_ = new XShmSegmentInfo; + shm_segment_info_->shmid = -1; + shm_segment_info_->shmaddr = reinterpret_cast<char*>(-1); + shm_segment_info_->readOnly = False; + x_image_ = XShmCreateImage(display_, default_visual, default_depth, ZPixmap, + 0, shm_segment_info_, window_size_.width(), + window_size_.height()); + if (x_image_) { + shm_segment_info_->shmid = shmget( + IPC_PRIVATE, x_image_->bytes_per_line * x_image_->height, + IPC_CREAT | 0600); + if (shm_segment_info_->shmid != -1) { + shm_segment_info_->shmaddr = x_image_->data = + reinterpret_cast<char*>(shmat(shm_segment_info_->shmid, 0, 0)); + if (x_image_->data != reinterpret_cast<char*>(-1)) { + XErrorTrap error_trap(display_); + using_shm = XShmAttach(display_, shm_segment_info_); + XSync(display_, False); + if (error_trap.GetLastErrorAndDisable() != 0) + using_shm = false; + if (using_shm) { + LOG(LS_VERBOSE) << "Using X shared memory segment " + << shm_segment_info_->shmid; + } + } + } else { + LOG(LS_WARNING) << "Failed to get shared memory segment. " + "Performance may be degraded."; + } + } + + if (!using_shm) { + LOG(LS_WARNING) << "Not using shared memory. Performance may be degraded."; + Release(); + return; + } + + if (have_pixmaps) + have_pixmaps = InitPixmaps(default_depth); + + shmctl(shm_segment_info_->shmid, IPC_RMID, 0); + shm_segment_info_->shmid = -1; + + LOG(LS_VERBOSE) << "Using X shared memory extension v" + << major << "." << minor + << " with" << (have_pixmaps ? "" : "out") << " pixmaps."; +} + +bool XServerPixelBuffer::InitPixmaps(int depth) { + if (XShmPixmapFormat(display_) != ZPixmap) + return false; + + { + XErrorTrap error_trap(display_); + shm_pixmap_ = XShmCreatePixmap(display_, window_, + shm_segment_info_->shmaddr, + shm_segment_info_, + window_size_.width(), + window_size_.height(), depth); + XSync(display_, False); + if (error_trap.GetLastErrorAndDisable() != 0) { + // |shm_pixmap_| is not not valid because the request was not processed + // by the X Server, so zero it. + shm_pixmap_ = 0; + return false; + } + } + + { + XErrorTrap error_trap(display_); + XGCValues shm_gc_values; + shm_gc_values.subwindow_mode = IncludeInferiors; + shm_gc_values.graphics_exposures = False; + shm_gc_ = XCreateGC(display_, window_, + GCSubwindowMode | GCGraphicsExposures, + &shm_gc_values); + XSync(display_, False); + if (error_trap.GetLastErrorAndDisable() != 0) { + XFreePixmap(display_, shm_pixmap_); + shm_pixmap_ = 0; + shm_gc_ = 0; // See shm_pixmap_ comment above. + return false; + } + } + + return true; +} + +bool XServerPixelBuffer::IsWindowValid() const { + XWindowAttributes attributes; + { + XErrorTrap error_trap(display_); + if (!XGetWindowAttributes(display_, window_, &attributes) || + error_trap.GetLastErrorAndDisable() != 0) { + return false; + } + } + return true; +} + +void XServerPixelBuffer::Synchronize() { + if (shm_segment_info_ && !shm_pixmap_) { + // XShmGetImage can fail if the display is being reconfigured. + XErrorTrap error_trap(display_); + XShmGetImage(display_, window_, x_image_, 0, 0, AllPlanes); + } +} + +void XServerPixelBuffer::CaptureRect(const DesktopRect& rect, + DesktopFrame* frame) { + assert(rect.right() <= window_size_.width()); + assert(rect.bottom() <= window_size_.height()); + + uint8_t* data; + + if (shm_segment_info_) { + if (shm_pixmap_) { + XCopyArea(display_, window_, shm_pixmap_, shm_gc_, + rect.left(), rect.top(), rect.width(), rect.height(), + rect.left(), rect.top()); + XSync(display_, False); + } + data = reinterpret_cast<uint8_t*>(x_image_->data) + + rect.top() * x_image_->bytes_per_line + + rect.left() * x_image_->bits_per_pixel / 8; + } else { + if (x_image_) + XDestroyImage(x_image_); + x_image_ = XGetImage(display_, window_, rect.left(), rect.top(), + rect.width(), rect.height(), AllPlanes, ZPixmap); + data = reinterpret_cast<uint8_t*>(x_image_->data); + } + + if (IsXImageRGBFormat(x_image_)) { + FastBlit(data, rect, frame); + } else { + SlowBlit(data, rect, frame); + } +} + +void XServerPixelBuffer::FastBlit(uint8_t* image, + const DesktopRect& rect, + DesktopFrame* frame) { + uint8_t* src_pos = image; + int src_stride = x_image_->bytes_per_line; + int dst_x = rect.left(), dst_y = rect.top(); + + uint8_t* dst_pos = frame->data() + frame->stride() * dst_y; + dst_pos += dst_x * DesktopFrame::kBytesPerPixel; + + int height = rect.height(); + int row_bytes = rect.width() * DesktopFrame::kBytesPerPixel; + for (int y = 0; y < height; ++y) { + memcpy(dst_pos, src_pos, row_bytes); + src_pos += src_stride; + dst_pos += frame->stride(); + } +} + +void XServerPixelBuffer::SlowBlit(uint8_t* image, + const DesktopRect& rect, + DesktopFrame* frame) { + int src_stride = x_image_->bytes_per_line; + int dst_x = rect.left(), dst_y = rect.top(); + int width = rect.width(), height = rect.height(); + + uint32_t red_mask = x_image_->red_mask; + uint32_t green_mask = x_image_->red_mask; + uint32_t blue_mask = x_image_->blue_mask; + + uint32_t red_shift = MaskToShift(red_mask); + uint32_t green_shift = MaskToShift(green_mask); + uint32_t blue_shift = MaskToShift(blue_mask); + + int bits_per_pixel = x_image_->bits_per_pixel; + + uint8_t* dst_pos = frame->data() + frame->stride() * dst_y; + uint8_t* src_pos = image; + dst_pos += dst_x * DesktopFrame::kBytesPerPixel; + // TODO(hclam): Optimize, perhaps using MMX code or by converting to + // YUV directly. + // TODO(sergeyu): This code doesn't handle XImage byte order properly and + // won't work with 24bpp images. Fix it. + for (int y = 0; y < height; y++) { + uint32_t* dst_pos_32 = reinterpret_cast<uint32_t*>(dst_pos); + uint32_t* src_pos_32 = reinterpret_cast<uint32_t*>(src_pos); + uint16_t* src_pos_16 = reinterpret_cast<uint16_t*>(src_pos); + for (int x = 0; x < width; x++) { + // Dereference through an appropriately-aligned pointer. + uint32_t pixel; + if (bits_per_pixel == 32) { + pixel = src_pos_32[x]; + } else if (bits_per_pixel == 16) { + pixel = src_pos_16[x]; + } else { + pixel = src_pos[x]; + } + uint32_t r = (pixel & red_mask) << red_shift; + uint32_t g = (pixel & green_mask) << green_shift; + uint32_t b = (pixel & blue_mask) << blue_shift; + // Write as 32-bit RGB. + dst_pos_32[x] = ((r >> 8) & 0xff0000) | ((g >> 16) & 0xff00) | + ((b >> 24) & 0xff); + } + dst_pos += frame->stride(); + src_pos += src_stride; + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h new file mode 100644 index 0000000000..d1e6632f08 --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Don't include this file in any .h files because it pulls in some X headers. + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_SERVER_PIXEL_BUFFER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_SERVER_PIXEL_BUFFER_H_ + +#include "webrtc/modules/desktop_capture/desktop_geometry.h" + +#include <X11/Xutil.h> +#include <X11/extensions/XShm.h> + +namespace webrtc { + +class DesktopFrame; + +// A class to allow the X server's pixel buffer to be accessed as efficiently +// as possible. +class XServerPixelBuffer { + public: + XServerPixelBuffer(); + ~XServerPixelBuffer(); + + void Release(); + + // Allocate (or reallocate) the pixel buffer for |window|. Returns false in + // case of an error (e.g. window doesn't exist). + bool Init(Display* display, Window window); + + bool is_initialized() { return window_ != 0; } + + // Returns the size of the window the buffer was initialized for. + const DesktopSize& window_size() { return window_size_; } + + // Returns true if the window can be found. + bool IsWindowValid() const; + + // If shared memory is being used without pixmaps, synchronize this pixel + // buffer with the root window contents (otherwise, this is a no-op). + // This is to avoid doing a full-screen capture for each individual + // rectangle in the capture list, when it only needs to be done once at the + // beginning. + void Synchronize(); + + // Capture the specified rectangle and stores it in the |frame|. In the case + // where the full-screen data is captured by Synchronize(), this simply + // returns the pointer without doing any more work. The caller must ensure + // that |rect| is not larger than window_size(). + void CaptureRect(const DesktopRect& rect, DesktopFrame* frame); + + private: + void InitShm(const XWindowAttributes& attributes); + bool InitPixmaps(int depth); + + // We expose two forms of blitting to handle variations in the pixel format. + // In FastBlit(), the operation is effectively a memcpy. + void FastBlit(uint8_t* image, + const DesktopRect& rect, + DesktopFrame* frame); + void SlowBlit(uint8_t* image, + const DesktopRect& rect, + DesktopFrame* frame); + + Display* display_; + Window window_; + DesktopSize window_size_; + XImage* x_image_; + XShmSegmentInfo* shm_segment_info_; + Pixmap shm_pixmap_; + GC shm_gc_; + + RTC_DISALLOW_COPY_AND_ASSIGN(XServerPixelBuffer); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_SERVER_PIXEL_BUFFER_H_ |