// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/renderer_host/backing_store_gtk.h" #include #include #include #include #include #include #if defined(OS_OPENBSD) || defined(OS_FREEBSD) #include #endif #include #include #include #include #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 { // Assume that somewhere along the line, someone will do width * height * 4 // with signed numbers. If the maximum value is 2**31, then 2**31 / 4 = // 2**29 and floor(sqrt(2**29)) = 23170. // Max height and width for layers static const int kMaxVideoLayerSize = 23170; // X Backing Stores: // // Unlike Windows, where the backing store is kept in heap memory, we keep our // backing store in the X server, as a pixmap. Thus expose events just require // instructing the X server to copy from the backing store to the window. // // The backing store is in the same format as the visual which our main window // is using. Bitmaps from the renderer are uploaded to the X server, either via // shared memory or over the wire, and XRENDER is used to convert them to the // correct format for the backing store. // Destroys the image and the associated shared memory structures. This is a // helper function for code using shared memory. void DestroySharedImage(XDisplay* display, XImage* image, XShmSegmentInfo* shminfo) { XShmDetach(display, shminfo); XDestroyImage(image); shmdt(shminfo->shmaddr); } // So we don't don't want to call XSync(), which can block the UI loop for // ~100ms on first paint and is generally slow. We optionally use the // XSyncExtension to push a callback into the X11 event queue and get a // callback instead of blocking until the event queue is cleared. // // TODO(erg): If gfx::GetXDisplay() ever gets fixed to handle multiple Displays, // this must be modified to be per Display instead of a Singleton. class XSyncHandler { public: static XSyncHandler* GetInstance() { return Singleton::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; // A struct that has cleanup and callback tasks that were queued into the // future and are run on |g_backing_store_sync_alarm| firing. 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; // The display we're running on. XDisplay* display; // Data to delete. Picture picture; Pixmap pixmap; // Callback once everything else is done. base::Closure closure; }; XSyncHandler(); ~XSyncHandler(); // An event filter notified about all XEvents. We then filter out XSync // events that are on counters that we made. CHROMEG_CALLBACK_1(XSyncHandler, GdkFilterReturn, OnEvent, GdkXEvent*, GdkEvent*); // Whether we successfully loaded XSyncExtension. bool loaded_extension_; // The event ids returned to us by XSyncQueryExtension(). int xsync_event_base_; int xsync_error_base_; XSyncCounter backing_store_sync_counter_; XSyncAlarm backing_store_sync_alarm_; // A queue of pending paints that we clean up after as alarms fire. std::queue 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)); // Push a change counter event into the X11 event queue that will trigger our // alarm when it is processed. 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_)) { // Create our monotonically increasing counter. XSyncValue value; XSyncIntToValue(&value, 0); backing_store_sync_counter_ = XSyncCreateCounter(display, value); // Cerate our alarm that watches for changes to our counter. XSyncAlarmAttributes attributes; attributes.trigger.counter = backing_store_sync_counter_; backing_store_sync_alarm_ = XSyncCreateAlarm(display, XSyncCACounter, &attributes); // Add our filter to the message loop to handle alarm triggers. 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()) { // We delete the X11 resources we're holding onto. We don't run the // callbacks because we are shutting down. 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(gdkxevent); if (xevent->type == xsync_event_base_ + XSyncAlarmNotify) { XSyncAlarmNotifyEvent* alarm_event = reinterpret_cast(xevent); if (alarm_event->alarm == backing_store_sync_alarm_) { if (alarm_event->counter_value.hi == 0 && alarm_event->counter_value.lo == 0) { // We receive an event about the initial state of the counter during // alarm creation. We must ignore this event instead of responding to // it. return GDK_FILTER_REMOVE; } DCHECK(!backing_store_events_.empty()); BackingStoreEvents* data = backing_store_events_.front(); backing_store_events_.pop(); // We are responsible for deleting all the data in the struct now that // we are finished with it. XRenderFreePicture(data->display, data->picture); XFreePixmap(data->display, data->pixmap); // Dispatch the closure we were given. data->closure.Run(); data->dib->DecreaseInFlightCounter(); delete data; return GDK_FILTER_REMOVE; } } return GDK_FILTER_CONTINUE; } } // namespace 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)), 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() { // In unit tests, display_ may be NULL. if (!display_) return; XRenderFreePicture(display_, picture_); XFreePixmap(display_, pixmap_); XFreeGC(display_, static_cast(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& copy_rects) { const int width = bitmap_rect.width(); const int height = bitmap_rect.height(); Pixmap pixmap = XCreatePixmap(display_, root_window_, width, height, visual_depth_); // Draw ARGB transport DIB onto our pixmap. gfx::PutARGBImage(display_, visual_, visual_depth_, pixmap, pixmap_gc_, static_cast(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, // src pixmap_, // dest static_cast(pixmap_gc_), // gc copy_rect.x() - bitmap_rect.x(), // src_x copy_rect.y() - bitmap_rect.y(), // src_y copy_rect.width(), // width copy_rect.height(), // height copy_rect.x(), // dest_x copy_rect.y()); // dest_y } XFreePixmap(display_, pixmap); } void BackingStoreGtk::PaintToBackingStore( RenderProcessHost* process, TransportDIB::Id bitmap, const gfx::Rect& bitmap_rect, const std::vector& 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_); // The NULL in the following is the |data| pointer: this is an artifact of // Xlib trying to be helpful, rather than just exposing the X protocol. It // assumes that we have the shared memory segment mapped into our memory, // which we don't, and it's trying to calculate an offset by taking the // difference between the |data| pointer and the address of the mapping in // |shminfo|. Since both are NULL, the offset will be calculated to be 0, // which is correct for us. pixmap = XShmCreatePixmap(display_, root_window_, NULL, &shminfo, width, height, 32); } else { // We don't have shared memory pixmaps. Fall back to creating a pixmap // ourselves and putting an image on it. 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(dib->memory()); XImage* image = XShmCreateImage(display_, static_cast(visual_), 32, ZPixmap, shminfo.shmaddr, &shminfo, width, height); // This code path is important for performance and we have found that // different techniques work better on different platforms. See // http://code.google.com/p/chromium/issues/detail?id=44124. // // Checking for ARM is an approximation, but it seems to be a good one so // far. #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(), /* source x */ pixel_copy_rect.y() - pixel_bitmap_rect.y(), /* source y */ pixel_copy_rect.x() - pixel_bitmap_rect.x(), /* dest x */ pixel_copy_rect.y() - pixel_bitmap_rect.y(), /* dest y */ pixel_copy_rect.width(), pixel_copy_rect.height(), False /* send_event */); } #else XShmPutImage(display_, pixmap, gc, image, 0, 0 /* source x, y */, 0, 0 /* dest x, y */, width, height, False /* send_event */); #endif XDestroyImage(image); } else { // case SHARED_MEMORY_NONE // No shared memory support, we have to copy the bitmap contents // to the X server. Xlib wraps the underlying PutImage call // behind several layers of functions which try to convert the // image into the format which the X server expects. The // following values hopefully disable all conversions. 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(dib->memory()); XPutImage(display_, pixmap, gc, &image, 0, 0 /* source x, y */, 0, 0 /* dest x, y */, 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, // op picture, // src 0, // mask picture_, // dest copy_rect.x() - bitmap_rect.x(), // src_x copy_rect.y() - bitmap_rect.y(), // src_y 0, // mask_x 0, // mask_y copy_rect.x(), // dest_x copy_rect.y(), // dest_y copy_rect.width(), // width copy_rect.height()); // height } // In the case of shared memory, we wait for the composite to complete so that // we are sure that the X server has finished reading from the shared memory // segment. 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) { // If we didn't schedule a callback, we need to delete our resources now. 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) { // CopyFromBackingStore() copies pixels out of the XImage // in a way that assumes that each component (red, green, // blue) is a byte. This doesn't work on visuals which // encode a pixel color with less than a byte per color. return false; } const int width = std::min(size().width(), rect.width()); const int height = std::min(size().height(), rect.height()); XImage* image; XShmSegmentInfo shminfo; // Used only when shared memory is enabled. if (shared_memory_support_ != ui::SHARED_MEMORY_NONE) { // Use shared memory for faster copies when it's available. Visual* visual = static_cast(visual_); memset(&shminfo, 0, sizeof(shminfo)); image = XShmCreateImage(display_, visual, 32, ZPixmap, NULL, &shminfo, width, height); if (!image) { return false; } // Create the shared memory segment for the image and map it. if (image->bytes_per_line == 0 || image->height == 0 || static_cast(image->height) > (std::numeric_limits::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(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."; // Non-shared memory case just copy the image from the server. image = XGetImage(display_, pixmap_, rect.x(), rect.y(), width, height, AllPlanes, ZPixmap); } // TODO(jhawkins): Need to convert the image data if the image bits per pixel // is not 32. // Note that this also initializes the output bitmap as opaque. 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; } // The X image might have a different row stride, so iterate through // it and copy each row out, only up to the pixels we're actually // using. This code assumes a visual mode where a pixel is // represented using a 32-bit unsigned int, with a byte per component. const SkBitmap& bitmap = output->GetBitmap(); SkAutoLockPixels alp(bitmap); for (int y = 0; y < height; y++) { const uint32* src_row = reinterpret_cast( &image->data[image->bytes_per_line * y]); uint32* dest_row = bitmap.getAddr32(0, y); for (int x = 0; x < width; ++x, ++dest_row) { // Force alpha to be 0xff, because otherwise it causes rendering problems. *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; // We only support scrolling in one direction at a time. DCHECK(delta.x() == 0 || delta.y() == 0); if (delta.y()) { // Positive values of |delta|.y() scroll up if (abs(delta.y()) < clip_rect.height()) { XCopyArea(display_, pixmap_, pixmap_, static_cast(pixmap_gc_), clip_rect.x() /* source x */, std::max(clip_rect.y(), clip_rect.y() - delta.y()), clip_rect.width(), clip_rect.height() - abs(delta.y()), clip_rect.x() /* dest x */, std::max(clip_rect.y(), clip_rect.y() + delta.y()) /* dest y */ ); } } else if (delta.x()) { // Positive values of |delta|.x() scroll right if (abs(delta.x()) < clip_rect.width()) { XCopyArea(display_, pixmap_, pixmap_, static_cast(pixmap_gc_), std::max(clip_rect.x(), clip_rect.x() - delta.x()), clip_rect.y() /* source y */, clip_rect.width() - abs(delta.x()), clip_rect.height(), std::max(clip_rect.x(), clip_rect.x() + delta.x()) /* dest x */, clip_rect.y() /* dest x */); } } } void BackingStoreGtk::XShowRect(const gfx::Point &origin, const gfx::Rect& rect, XID target) { XCopyArea(display_, pixmap_, target, static_cast(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_), size().width(), size().height()); cairo_t* cr = gdk_cairo_create(target); cairo_translate(cr, rect.x(), rect.y()); double x_scale = static_cast(rect.width()) / size().width(); double y_scale = static_cast(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 } // namespace content