blob: 8b3fda9840d37e2fec8850337ffe0675369b8e57 [file] [log] [blame]
// 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.
#import <Cocoa/Cocoa.h>
#include "content/browser/renderer_host/backing_store_mac.h"
#include <cmath>
#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/public/browser/render_widget_host_view.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/rect_conversions.h"
#include "ui/gfx/size_conversions.h"
#include "ui/gfx/scoped_cg_context_save_gstate_mac.h"
#include "ui/surface/transport_dib.h"
namespace content {
// Mac Backing Stores:
//
// Since backing stores are only ever written to or drawn into windows, we keep
// our backing store in a CGLayer that can get cached in GPU memory. This
// allows acclerated drawing into the layer and lets scrolling and such happen
// all or mostly on the GPU, which is good for performance.
BackingStoreMac::BackingStoreMac(RenderWidgetHost* widget,
const gfx::Size& size,
float device_scale_factor)
: BackingStore(widget, size), device_scale_factor_(device_scale_factor) {
cg_layer_.reset(CreateCGLayer());
if (!cg_layer_) {
// The view isn't in a window yet. Use a CGBitmapContext for now.
cg_bitmap_.reset(CreateCGBitmapContext());
CGContextScaleCTM(cg_bitmap_, device_scale_factor_, device_scale_factor_);
}
}
BackingStoreMac::~BackingStoreMac() {
}
void BackingStoreMac::ScaleFactorChanged(float device_scale_factor) {
if (device_scale_factor == device_scale_factor_)
return;
device_scale_factor_ = device_scale_factor;
base::ScopedCFTypeRef<CGLayerRef> new_layer(CreateCGLayer());
// If we have a layer, copy the old contents. A pixelated flash is better
// than a white flash.
if (new_layer && cg_layer_) {
CGContextRef layer = CGLayerGetContext(new_layer);
CGContextDrawLayerAtPoint(layer, CGPointMake(0, 0), cg_layer_);
}
cg_layer_.swap(new_layer);
if (!cg_layer_) {
// The view isn't in a window yet. Use a CGBitmapContext for now.
cg_bitmap_.reset(CreateCGBitmapContext());
CGContextScaleCTM(cg_bitmap_, device_scale_factor_, device_scale_factor_);
}
}
size_t BackingStoreMac::MemorySize() {
return gfx::ToFlooredSize(
gfx::ScaleSize(size(), device_scale_factor_)).GetArea() * 4;
}
void BackingStoreMac::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;
DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));
TransportDIB* dib = process->GetTransportDIB(bitmap);
if (!dib)
return;
gfx::Size pixel_size = gfx::ToFlooredSize(
gfx::ScaleSize(size(), device_scale_factor_));
gfx::Rect pixel_bitmap_rect = ToFlooredRectDeprecated(
gfx::ScaleRect(bitmap_rect, scale_factor));
size_t bitmap_byte_count =
pixel_bitmap_rect.width() * pixel_bitmap_rect.height() * 4;
DCHECK_GE(dib->size(), bitmap_byte_count);
base::ScopedCFTypeRef<CGDataProviderRef> data_provider(
CGDataProviderCreateWithData(
NULL, dib->memory(), bitmap_byte_count, NULL));
base::ScopedCFTypeRef<CGImageRef> bitmap_image(
CGImageCreate(pixel_bitmap_rect.width(),
pixel_bitmap_rect.height(),
8,
32,
4 * pixel_bitmap_rect.width(),
base::mac::GetSystemColorSpace(),
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
data_provider,
NULL,
false,
kCGRenderingIntentDefault));
for (size_t i = 0; i < copy_rects.size(); i++) {
const gfx::Rect& copy_rect = copy_rects[i];
gfx::Rect pixel_copy_rect = ToFlooredRectDeprecated(
gfx::ScaleRect(copy_rect, scale_factor));
// Only the subpixels given by copy_rect have pixels to copy.
base::ScopedCFTypeRef<CGImageRef> image(CGImageCreateWithImageInRect(
bitmap_image,
CGRectMake(pixel_copy_rect.x() - pixel_bitmap_rect.x(),
pixel_copy_rect.y() - pixel_bitmap_rect.y(),
pixel_copy_rect.width(),
pixel_copy_rect.height())));
if (!cg_layer()) {
// The view may have moved to a window. Try to get a CGLayer.
cg_layer_.reset(CreateCGLayer());
if (cg_layer()) {
// Now that we have a layer, copy the cached image into it.
base::ScopedCFTypeRef<CGImageRef> bitmap_image(
CGBitmapContextCreateImage(cg_bitmap_));
CGContextDrawImage(CGLayerGetContext(cg_layer()),
CGRectMake(0, 0, size().width(), size().height()),
bitmap_image);
// Discard the cache bitmap, since we no longer need it.
cg_bitmap_.reset(NULL);
}
}
DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));
if (cg_layer()) {
// The CGLayer's origin is in the lower left, but flipping the CTM would
// cause the image to get drawn upside down. So we move the rectangle
// to the right position before drawing the image.
CGContextRef layer = CGLayerGetContext(cg_layer());
gfx::Rect paint_rect = copy_rect;
paint_rect.set_y(size().height() - copy_rect.bottom());
CGContextDrawImage(layer, paint_rect.ToCGRect(), image);
} else {
// The layer hasn't been created yet, so draw into the cache bitmap.
gfx::Rect paint_rect = copy_rect;
paint_rect.set_y(size().height() - copy_rect.bottom());
CGContextDrawImage(cg_bitmap_, paint_rect.ToCGRect(), image);
}
}
}
bool BackingStoreMac::CopyFromBackingStore(const gfx::Rect& rect,
skia::PlatformBitmap* output) {
// TODO(thakis): Make sure this works with HiDPI backing stores.
if (!output->Allocate(rect.width(), rect.height(), true))
return false;
CGContextRef temp_context = output->GetSurface();
gfx::ScopedCGContextSaveGState save_gstate(temp_context);
CGContextTranslateCTM(temp_context, 0.0, size().height());
CGContextScaleCTM(temp_context, 1.0, -1.0);
if (cg_layer()) {
CGContextDrawLayerAtPoint(temp_context, CGPointMake(-rect.x(), -rect.y()),
cg_layer());
} else {
base::ScopedCFTypeRef<CGImageRef> bitmap_image(
CGBitmapContextCreateImage(cg_bitmap_));
CGContextDrawImage(
temp_context,
CGRectMake(-rect.x(), -rect.y(), rect.width(), rect.height()),
bitmap_image);
}
return true;
}
// Scroll the contents of our CGLayer
void BackingStoreMac::ScrollBackingStore(const gfx::Vector2d& delta,
const gfx::Rect& clip_rect,
const gfx::Size& view_size) {
DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));
// "Scroll" the contents of the layer by creating a new CGLayer,
// copying the contents of the old one into the new one offset by the scroll
// amount, swapping in the new CGLayer, and then painting in the new data.
//
// The Windows code always sets the whole backing store as the source of the
// scroll. Thus, we only have to worry about pixels which will end up inside
// the clipping rectangle. (Note that the clipping rectangle is not
// translated by the scroll.)
// We assume |clip_rect| is contained within the backing store.
DCHECK(clip_rect.bottom() <= size().height());
DCHECK(clip_rect.right() <= size().width());
if ((delta.x() || delta.y()) &&
abs(delta.x()) < size().width() && abs(delta.y()) < size().height()) {
if (cg_layer()) {
CGContextRef layer = CGLayerGetContext(cg_layer());
gfx::ScopedCGContextSaveGState save_gstate(layer);
CGContextClipToRect(layer,
CGRectMake(clip_rect.x(),
size().height() - clip_rect.bottom(),
clip_rect.width(),
clip_rect.height()));
CGContextDrawLayerAtPoint(layer,
CGPointMake(delta.x(), -delta.y()), cg_layer());
} else {
// We don't have a layer, so scroll the contents of the CGBitmapContext.
base::ScopedCFTypeRef<CGImageRef> bitmap_image(
CGBitmapContextCreateImage(cg_bitmap_));
gfx::ScopedCGContextSaveGState save_gstate(cg_bitmap_);
CGContextClipToRect(cg_bitmap_,
CGRectMake(clip_rect.x(),
size().height() - clip_rect.bottom(),
clip_rect.width(),
clip_rect.height()));
CGContextDrawImage(cg_bitmap_,
CGRectMake(delta.x(), -delta.y(),
size().width(), size().height()),
bitmap_image);
}
}
}
void BackingStoreMac::CopyFromBackingStoreToCGContext(const CGRect& dest_rect,
CGContextRef context) {
gfx::ScopedCGContextSaveGState save_gstate(context);
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
if (cg_layer_) {
CGContextDrawLayerInRect(context, dest_rect, cg_layer_);
} else {
base::ScopedCFTypeRef<CGImageRef> image(
CGBitmapContextCreateImage(cg_bitmap_));
CGContextDrawImage(context, dest_rect, image);
}
}
CGLayerRef BackingStoreMac::CreateCGLayer() {
// The CGLayer should be optimized for drawing into the containing window,
// so extract a CGContext corresponding to the window to be passed to
// CGLayerCreateWithContext.
NSWindow* window = [render_widget_host()->GetView()->GetNativeView() window];
if ([window windowNumber] <= 0) {
// This catches a nil |window|, as well as windows that exist but that
// aren't yet connected to WindowServer.
return NULL;
}
NSGraphicsContext* ns_context = [window graphicsContext];
DCHECK(ns_context);
CGContextRef cg_context = static_cast<CGContextRef>(
[ns_context graphicsPort]);
DCHECK(cg_context);
// Note: This takes the backingScaleFactor of cg_context into account. The
// bitmap backing |layer| will be size() * 2 in HiDPI mode automatically.
CGLayerRef layer = CGLayerCreateWithContext(cg_context,
size().ToCGSize(),
NULL);
DCHECK(layer);
return layer;
}
CGContextRef BackingStoreMac::CreateCGBitmapContext() {
gfx::Size pixel_size = gfx::ToFlooredSize(
gfx::ScaleSize(size(), device_scale_factor_));
// A CGBitmapContext serves as a stand-in for the layer before the view is
// in a containing window.
CGContextRef context = CGBitmapContextCreate(NULL,
pixel_size.width(),
pixel_size.height(),
8, pixel_size.width() * 4,
base::mac::GetSystemColorSpace(),
kCGImageAlphaPremultipliedFirst |
kCGBitmapByteOrder32Host);
DCHECK(context);
return context;
}
} // namespace content