This source file includes following definitions.
- DestroySharedImage
- GetInstance
- Enabled
- PushPaintCounter
- backing_store_sync_alarm_
- OnEvent
- root_window_
- pixmap_gc_
- MemorySize
- PaintRectWithoutXrender
- PaintToBackingStore
- CopyFromBackingStore
- ScrollBackingStore
- XShowRect
- PaintToRect
#include "content/browser/renderer_host/backing_store_gtk.h"
#include <cairo-xlib.h>
#include <gtk/gtk.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/sync.h>
#if defined(OS_OPENBSD) || defined(OS_FREEBSD)
#include <sys/endian.h>
#endif
#include <algorithm>
#include <limits>
#include <queue>
#include <utility>
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram.h"
#include "base/time/time.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/gtk/gtk_signal.h"
#include "ui/base/x/x11_util_internal.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/rect_conversions.h"
#include "ui/gfx/x/x11_types.h"
#include "ui/surface/transport_dib.h"
namespace content {
namespace {
static const int kMaxVideoLayerSize = 23170;
void DestroySharedImage(XDisplay* display,
XImage* image,
XShmSegmentInfo* shminfo) {
XShmDetach(display, shminfo);
XDestroyImage(image);
shmdt(shminfo->shmaddr);
}
class XSyncHandler {
public:
static XSyncHandler* GetInstance() {
return Singleton<XSyncHandler>::get();
}
bool Enabled() {
return loaded_extension_;
}
void PushPaintCounter(TransportDIB* dib,
XDisplay* display,
Picture picture,
Pixmap pixmap,
const base::Closure& completion_callback);
private:
friend struct DefaultSingletonTraits<XSyncHandler>;
struct BackingStoreEvents {
BackingStoreEvents(TransportDIB* dib, XDisplay* d, Picture pic, Pixmap pix,
const base::Closure& c)
: dib(dib),
display(d),
picture(pic),
pixmap(pix),
closure(c) {
dib->IncreaseInFlightCounter();
}
TransportDIB* dib;
XDisplay* display;
Picture picture;
Pixmap pixmap;
base::Closure closure;
};
XSyncHandler();
~XSyncHandler();
CHROMEG_CALLBACK_1(XSyncHandler, GdkFilterReturn, OnEvent, GdkXEvent*,
GdkEvent*);
bool loaded_extension_;
int xsync_event_base_;
int xsync_error_base_;
XSyncCounter backing_store_sync_counter_;
XSyncAlarm backing_store_sync_alarm_;
std::queue<BackingStoreEvents*> backing_store_events_;
};
void XSyncHandler::PushPaintCounter(TransportDIB* dib,
XDisplay* display,
Picture picture,
Pixmap pixmap,
const base::Closure& completion_callback) {
backing_store_events_.push(new BackingStoreEvents(
dib, display, picture, pixmap, completion_callback));
XSyncValue value;
XSyncIntToValue(&value, 1);
XSyncChangeCounter(gfx::GetXDisplay(),
backing_store_sync_counter_,
value);
}
XSyncHandler::XSyncHandler()
: loaded_extension_(false),
xsync_event_base_(0),
xsync_error_base_(0),
backing_store_sync_counter_(0),
backing_store_sync_alarm_(0) {
XDisplay* display = gfx::GetXDisplay();
if (XSyncQueryExtension(display,
&xsync_event_base_,
&xsync_error_base_)) {
XSyncValue value;
XSyncIntToValue(&value, 0);
backing_store_sync_counter_ = XSyncCreateCounter(display, value);
XSyncAlarmAttributes attributes;
attributes.trigger.counter = backing_store_sync_counter_;
backing_store_sync_alarm_ = XSyncCreateAlarm(display,
XSyncCACounter,
&attributes);
gdk_window_add_filter(NULL, &OnEventThunk, this);
loaded_extension_ = true;
}
}
XSyncHandler::~XSyncHandler() {
if (loaded_extension_)
gdk_window_remove_filter(NULL, &OnEventThunk, this);
XSync(gfx::GetXDisplay(), False);
while (!backing_store_events_.empty()) {
BackingStoreEvents* data = backing_store_events_.front();
backing_store_events_.pop();
XRenderFreePicture(data->display, data->picture);
XFreePixmap(data->display, data->pixmap);
data->dib->DecreaseInFlightCounter();
delete data;
}
}
GdkFilterReturn XSyncHandler::OnEvent(GdkXEvent* gdkxevent,
GdkEvent* event) {
XEvent* xevent = reinterpret_cast<XEvent*>(gdkxevent);
if (xevent->type == xsync_event_base_ + XSyncAlarmNotify) {
XSyncAlarmNotifyEvent* alarm_event =
reinterpret_cast<XSyncAlarmNotifyEvent*>(xevent);
if (alarm_event->alarm == backing_store_sync_alarm_) {
if (alarm_event->counter_value.hi == 0 &&
alarm_event->counter_value.lo == 0) {
return GDK_FILTER_REMOVE;
}
DCHECK(!backing_store_events_.empty());
BackingStoreEvents* data = backing_store_events_.front();
backing_store_events_.pop();
XRenderFreePicture(data->display, data->picture);
XFreePixmap(data->display, data->pixmap);
data->closure.Run();
data->dib->DecreaseInFlightCounter();
delete data;
return GDK_FILTER_REMOVE;
}
}
return GDK_FILTER_CONTINUE;
}
}
BackingStoreGtk::BackingStoreGtk(RenderWidgetHost* widget,
const gfx::Size& size,
void* visual,
int depth)
: BackingStore(widget, size),
display_(gfx::GetXDisplay()),
shared_memory_support_(ui::QuerySharedMemorySupport(display_)),
use_render_(ui::QueryRenderSupport(display_)),
visual_(visual),
visual_depth_(depth),
root_window_(ui::GetX11RootWindow()) {
#if defined(OS_OPENBSD) || defined(OS_FREEBSD)
COMPILE_ASSERT(_BYTE_ORDER == _LITTLE_ENDIAN, assumes_little_endian);
#else
COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN, assumes_little_endian);
#endif
pixmap_ = XCreatePixmap(display_, root_window_,
size.width(), size.height(), depth);
if (use_render_) {
picture_ = XRenderCreatePicture(
display_, pixmap_,
ui::GetRenderVisualFormat(display_,
static_cast<Visual*>(visual)),
0, NULL);
pixmap_bpp_ = 0;
} else {
picture_ = 0;
pixmap_bpp_ = gfx::BitsPerPixelForPixmapDepth(display_, depth);
}
pixmap_gc_ = XCreateGC(display_, pixmap_, 0, NULL);
}
BackingStoreGtk::BackingStoreGtk(RenderWidgetHost* widget,
const gfx::Size& size)
: BackingStore(widget, size),
display_(NULL),
shared_memory_support_(ui::SHARED_MEMORY_NONE),
use_render_(false),
pixmap_bpp_(0),
visual_(NULL),
visual_depth_(-1),
root_window_(0),
pixmap_(0),
picture_(0),
pixmap_gc_(NULL) {
}
BackingStoreGtk::~BackingStoreGtk() {
if (!display_)
return;
XRenderFreePicture(display_, picture_);
XFreePixmap(display_, pixmap_);
XFreeGC(display_, static_cast<GC>(pixmap_gc_));
}
size_t BackingStoreGtk::MemorySize() {
if (!use_render_)
return size().GetArea() * (pixmap_bpp_ / 8);
else
return size().GetArea() * 4;
}
void BackingStoreGtk::PaintRectWithoutXrender(
TransportDIB* bitmap,
const gfx::Rect& bitmap_rect,
const std::vector<gfx::Rect>& copy_rects) {
const int width = bitmap_rect.width();
const int height = bitmap_rect.height();
Pixmap pixmap = XCreatePixmap(display_, root_window_, width, height,
visual_depth_);
gfx::PutARGBImage(display_, visual_, visual_depth_, pixmap,
pixmap_gc_, static_cast<uint8*>(bitmap->memory()),
width, height);
for (size_t i = 0; i < copy_rects.size(); i++) {
const gfx::Rect& copy_rect = copy_rects[i];
XCopyArea(display_,
pixmap,
pixmap_,
static_cast<GC>(pixmap_gc_),
copy_rect.x() - bitmap_rect.x(),
copy_rect.y() - bitmap_rect.y(),
copy_rect.width(),
copy_rect.height(),
copy_rect.x(),
copy_rect.y());
}
XFreePixmap(display_, pixmap);
}
void BackingStoreGtk::PaintToBackingStore(
RenderProcessHost* process,
TransportDIB::Id bitmap,
const gfx::Rect& bitmap_rect,
const std::vector<gfx::Rect>& copy_rects,
float scale_factor,
const base::Closure& completion_callback,
bool* scheduled_completion_callback) {
*scheduled_completion_callback = false;
if (!display_)
return;
if (bitmap_rect.IsEmpty())
return;
gfx::Rect pixel_bitmap_rect = gfx::ToEnclosedRect(
gfx::ScaleRect(bitmap_rect, scale_factor));
const int width = pixel_bitmap_rect.width();
const int height = pixel_bitmap_rect.height();
if (width <= 0 || width > kMaxVideoLayerSize ||
height <= 0 || height > kMaxVideoLayerSize)
return;
TransportDIB* dib = process->GetTransportDIB(bitmap);
if (!dib)
return;
if (!use_render_)
return PaintRectWithoutXrender(dib, bitmap_rect, copy_rects);
Picture picture;
Pixmap pixmap;
if (shared_memory_support_ == ui::SHARED_MEMORY_PIXMAP) {
XShmSegmentInfo shminfo = {0};
shminfo.shmseg = dib->MapToX(display_);
pixmap = XShmCreatePixmap(display_, root_window_, NULL, &shminfo,
width, height, 32);
} else {
pixmap = XCreatePixmap(display_, root_window_, width, height, 32);
GC gc = XCreateGC(display_, pixmap, 0, NULL);
if (shared_memory_support_ == ui::SHARED_MEMORY_PUTIMAGE) {
const XID shmseg = dib->MapToX(display_);
XShmSegmentInfo shminfo;
memset(&shminfo, 0, sizeof(shminfo));
shminfo.shmseg = shmseg;
shminfo.shmaddr = static_cast<char*>(dib->memory());
XImage* image = XShmCreateImage(display_, static_cast<Visual*>(visual_),
32, ZPixmap,
shminfo.shmaddr, &shminfo,
width, height);
#if defined(ARCH_CPU_ARM_FAMILY)
for (size_t i = 0; i < copy_rects.size(); i++) {
const gfx::Rect& copy_rect = copy_rects[i];
gfx::Rect pixel_copy_rect = gfx::ToEnclosedRect(
gfx::ScaleRect(copy_rect, scale_factor));
XShmPutImage(display_, pixmap, gc, image,
pixel_copy_rect.x() - pixel_bitmap_rect.x(),
pixel_copy_rect.y() - pixel_bitmap_rect.y(),
pixel_copy_rect.x() - pixel_bitmap_rect.x(),
pixel_copy_rect.y() - pixel_bitmap_rect.y(),
pixel_copy_rect.width(), pixel_copy_rect.height(),
False );
}
#else
XShmPutImage(display_, pixmap, gc, image,
0, 0 , 0, 0 ,
width, height, False );
#endif
XDestroyImage(image);
} else {
XImage image;
memset(&image, 0, sizeof(image));
image.width = width;
image.height = height;
image.depth = 32;
image.bits_per_pixel = 32;
image.format = ZPixmap;
image.byte_order = LSBFirst;
image.bitmap_unit = 8;
image.bitmap_bit_order = LSBFirst;
image.bytes_per_line = width * 4;
image.red_mask = 0xff;
image.green_mask = 0xff00;
image.blue_mask = 0xff0000;
image.data = static_cast<char*>(dib->memory());
XPutImage(display_, pixmap, gc, &image,
0, 0 , 0, 0 ,
width, height);
}
XFreeGC(display_, gc);
}
picture = ui::CreatePictureFromSkiaPixmap(display_, pixmap);
if (scale_factor != 1.0) {
float up_scale = 1.0 / scale_factor;
XTransform scaling = { {
{ XDoubleToFixed(1), XDoubleToFixed(0), XDoubleToFixed(0) },
{ XDoubleToFixed(0), XDoubleToFixed(1), XDoubleToFixed(0) },
{ XDoubleToFixed(0), XDoubleToFixed(0), XDoubleToFixed(up_scale) }
} };
XRenderSetPictureTransform(display_, picture, &scaling);
XRenderSetPictureFilter(display_, picture, FilterGood, NULL, 0);
}
for (size_t i = 0; i < copy_rects.size(); i++) {
const gfx::Rect& copy_rect = copy_rects[i];
XRenderComposite(display_,
PictOpSrc,
picture,
0,
picture_,
copy_rect.x() - bitmap_rect.x(),
copy_rect.y() - bitmap_rect.y(),
0,
0,
copy_rect.x(),
copy_rect.y(),
copy_rect.width(),
copy_rect.height());
}
if (shared_memory_support_ != ui::SHARED_MEMORY_NONE) {
XSyncHandler* handler = XSyncHandler::GetInstance();
if (handler->Enabled()) {
*scheduled_completion_callback = true;
handler->PushPaintCounter(
dib, display_, picture, pixmap, completion_callback);
} else {
XSync(display_, False);
}
}
if (*scheduled_completion_callback == false) {
XRenderFreePicture(display_, picture);
XFreePixmap(display_, pixmap);
}
}
bool BackingStoreGtk::CopyFromBackingStore(const gfx::Rect& rect,
skia::PlatformBitmap* output) {
base::TimeTicks begin_time = base::TimeTicks::Now();
if (visual_depth_ < 24) {
return false;
}
const int width = std::min(size().width(), rect.width());
const int height = std::min(size().height(), rect.height());
XImage* image;
XShmSegmentInfo shminfo;
if (shared_memory_support_ != ui::SHARED_MEMORY_NONE) {
Visual* visual = static_cast<Visual*>(visual_);
memset(&shminfo, 0, sizeof(shminfo));
image = XShmCreateImage(display_, visual, 32,
ZPixmap, NULL, &shminfo, width, height);
if (!image) {
return false;
}
if (image->bytes_per_line == 0 || image->height == 0 ||
static_cast<size_t>(image->height) >
(std::numeric_limits<size_t>::max() / image->bytes_per_line)) {
XDestroyImage(image);
return false;
}
shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height,
IPC_CREAT|0600);
if (shminfo.shmid == -1) {
XDestroyImage(image);
LOG(WARNING) << "Failed to get shared memory segment. "
"Performance may be degraded.";
return false;
} else {
VLOG(1) << "Got shared memory segment " << shminfo.shmid;
}
void* mapped_memory = shmat(shminfo.shmid, NULL, SHM_RDONLY);
shmctl(shminfo.shmid, IPC_RMID, 0);
if (mapped_memory == (void*)-1) {
XDestroyImage(image);
return false;
}
shminfo.shmaddr = image->data = static_cast<char*>(mapped_memory);
if (!XShmAttach(display_, &shminfo) ||
!XShmGetImage(display_, pixmap_, image, rect.x(), rect.y(),
AllPlanes)) {
DestroySharedImage(display_, image, &shminfo);
LOG(WARNING) << "X failed to get shared memory segment. "
"Performance may be degraded.";
return false;
}
VLOG(1) << "Using X shared memory segment " << shminfo.shmid;
} else {
LOG(WARNING) << "Not using X shared memory.";
image = XGetImage(display_, pixmap_,
rect.x(), rect.y(), width, height,
AllPlanes, ZPixmap);
}
if (!output->Allocate(width, height, true) ||
image->bits_per_pixel != 32) {
if (shared_memory_support_ != ui::SHARED_MEMORY_NONE)
DestroySharedImage(display_, image, &shminfo);
else
XDestroyImage(image);
return false;
}
const SkBitmap& bitmap = output->GetBitmap();
SkAutoLockPixels alp(bitmap);
for (int y = 0; y < height; y++) {
const uint32* src_row = reinterpret_cast<uint32*>(
&image->data[image->bytes_per_line * y]);
uint32* dest_row = bitmap.getAddr32(0, y);
for (int x = 0; x < width; ++x, ++dest_row) {
*dest_row = src_row[x] | 0xff000000;
}
}
if (shared_memory_support_ != ui::SHARED_MEMORY_NONE)
DestroySharedImage(display_, image, &shminfo);
else
XDestroyImage(image);
HISTOGRAM_TIMES("BackingStore.RetrievalFromX",
base::TimeTicks::Now() - begin_time);
return true;
}
void BackingStoreGtk::ScrollBackingStore(const gfx::Vector2d& delta,
const gfx::Rect& clip_rect,
const gfx::Size& view_size) {
if (!display_)
return;
DCHECK(delta.x() == 0 || delta.y() == 0);
if (delta.y()) {
if (abs(delta.y()) < clip_rect.height()) {
XCopyArea(display_, pixmap_, pixmap_, static_cast<GC>(pixmap_gc_),
clip_rect.x() ,
std::max(clip_rect.y(), clip_rect.y() - delta.y()),
clip_rect.width(),
clip_rect.height() - abs(delta.y()),
clip_rect.x() ,
std::max(clip_rect.y(), clip_rect.y() + delta.y())
);
}
} else if (delta.x()) {
if (abs(delta.x()) < clip_rect.width()) {
XCopyArea(display_, pixmap_, pixmap_, static_cast<GC>(pixmap_gc_),
std::max(clip_rect.x(), clip_rect.x() - delta.x()),
clip_rect.y() ,
clip_rect.width() - abs(delta.x()),
clip_rect.height(),
std::max(clip_rect.x(), clip_rect.x() + delta.x()) ,
clip_rect.y() );
}
}
}
void BackingStoreGtk::XShowRect(const gfx::Point &origin,
const gfx::Rect& rect, XID target) {
XCopyArea(display_, pixmap_, target, static_cast<GC>(pixmap_gc_),
rect.x(), rect.y(), rect.width(), rect.height(),
rect.x() + origin.x(), rect.y() + origin.y());
}
#if defined(TOOLKIT_GTK)
void BackingStoreGtk::PaintToRect(const gfx::Rect& rect, GdkDrawable* target) {
cairo_surface_t* surface = cairo_xlib_surface_create(
display_, pixmap_, static_cast<Visual*>(visual_),
size().width(), size().height());
cairo_t* cr = gdk_cairo_create(target);
cairo_translate(cr, rect.x(), rect.y());
double x_scale = static_cast<double>(rect.width()) / size().width();
double y_scale = static_cast<double>(rect.height()) / size().height();
cairo_scale(cr, x_scale, y_scale);
cairo_pattern_t* pattern = cairo_pattern_create_for_surface(surface);
cairo_pattern_set_filter(pattern, CAIRO_FILTER_BEST);
cairo_set_source(cr, pattern);
cairo_pattern_destroy(pattern);
cairo_identity_matrix(cr);
cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
cairo_fill(cr);
cairo_destroy(cr);
}
#endif
}