1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import <Cocoa/Cocoa.h>
7 #include "content/browser/renderer_host/backing_store_mac.h"
11 #include "base/logging.h"
12 #include "base/mac/mac_util.h"
13 #include "base/mac/scoped_cftyperef.h"
14 #include "content/browser/renderer_host/render_process_host_impl.h"
15 #include "content/browser/renderer_host/render_widget_host_impl.h"
16 #include "content/public/browser/render_widget_host_view.h"
17 #include "skia/ext/platform_canvas.h"
18 #include "third_party/skia/include/core/SkBitmap.h"
19 #include "third_party/skia/include/core/SkCanvas.h"
20 #include "ui/gfx/rect.h"
21 #include "ui/gfx/rect_conversions.h"
22 #include "ui/gfx/size_conversions.h"
23 #include "ui/gfx/scoped_cg_context_save_gstate_mac.h"
24 #include "ui/surface/transport_dib.h"
28 // Mac Backing Stores:
30 // Since backing stores are only ever written to or drawn into windows, we keep
31 // our backing store in a CGLayer that can get cached in GPU memory. This
32 // allows acclerated drawing into the layer and lets scrolling and such happen
33 // all or mostly on the GPU, which is good for performance.
35 BackingStoreMac::BackingStoreMac(RenderWidgetHost* widget,
36 const gfx::Size& size,
37 float device_scale_factor)
38 : BackingStore(widget, size), device_scale_factor_(device_scale_factor) {
39 cg_layer_.reset(CreateCGLayer());
41 // The view isn't in a window yet. Use a CGBitmapContext for now.
42 cg_bitmap_.reset(CreateCGBitmapContext());
43 CGContextScaleCTM(cg_bitmap_, device_scale_factor_, device_scale_factor_);
47 BackingStoreMac::~BackingStoreMac() {
50 void BackingStoreMac::ScaleFactorChanged(float device_scale_factor) {
51 if (device_scale_factor == device_scale_factor_)
54 device_scale_factor_ = device_scale_factor;
56 base::ScopedCFTypeRef<CGLayerRef> new_layer(CreateCGLayer());
57 // If we have a layer, copy the old contents. A pixelated flash is better
58 // than a white flash.
59 if (new_layer && cg_layer_) {
60 CGContextRef layer = CGLayerGetContext(new_layer);
61 CGContextDrawLayerAtPoint(layer, CGPointMake(0, 0), cg_layer_);
64 cg_layer_.swap(new_layer);
66 // The view isn't in a window yet. Use a CGBitmapContext for now.
67 cg_bitmap_.reset(CreateCGBitmapContext());
68 CGContextScaleCTM(cg_bitmap_, device_scale_factor_, device_scale_factor_);
72 size_t BackingStoreMac::MemorySize() {
73 return gfx::ToFlooredSize(
74 gfx::ScaleSize(size(), device_scale_factor_)).GetArea() * 4;
77 void BackingStoreMac::PaintToBackingStore(
78 RenderProcessHost* process,
79 TransportDIB::Id bitmap,
80 const gfx::Rect& bitmap_rect,
81 const std::vector<gfx::Rect>& copy_rects,
83 const base::Closure& completion_callback,
84 bool* scheduled_completion_callback) {
85 *scheduled_completion_callback = false;
86 DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));
88 TransportDIB* dib = process->GetTransportDIB(bitmap);
92 gfx::Size pixel_size = gfx::ToFlooredSize(
93 gfx::ScaleSize(size(), device_scale_factor_));
94 gfx::Rect pixel_bitmap_rect = ToFlooredRectDeprecated(
95 gfx::ScaleRect(bitmap_rect, scale_factor));
97 size_t bitmap_byte_count =
98 pixel_bitmap_rect.width() * pixel_bitmap_rect.height() * 4;
99 DCHECK_GE(dib->size(), bitmap_byte_count);
101 base::ScopedCFTypeRef<CGDataProviderRef> data_provider(
102 CGDataProviderCreateWithData(
103 NULL, dib->memory(), bitmap_byte_count, NULL));
105 base::ScopedCFTypeRef<CGImageRef> bitmap_image(
106 CGImageCreate(pixel_bitmap_rect.width(),
107 pixel_bitmap_rect.height(),
110 4 * pixel_bitmap_rect.width(),
111 base::mac::GetSystemColorSpace(),
112 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
116 kCGRenderingIntentDefault));
118 for (size_t i = 0; i < copy_rects.size(); i++) {
119 const gfx::Rect& copy_rect = copy_rects[i];
120 gfx::Rect pixel_copy_rect = ToFlooredRectDeprecated(
121 gfx::ScaleRect(copy_rect, scale_factor));
123 // Only the subpixels given by copy_rect have pixels to copy.
124 base::ScopedCFTypeRef<CGImageRef> image(CGImageCreateWithImageInRect(
126 CGRectMake(pixel_copy_rect.x() - pixel_bitmap_rect.x(),
127 pixel_copy_rect.y() - pixel_bitmap_rect.y(),
128 pixel_copy_rect.width(),
129 pixel_copy_rect.height())));
132 // The view may have moved to a window. Try to get a CGLayer.
133 cg_layer_.reset(CreateCGLayer());
135 // Now that we have a layer, copy the cached image into it.
136 base::ScopedCFTypeRef<CGImageRef> bitmap_image(
137 CGBitmapContextCreateImage(cg_bitmap_));
138 CGContextDrawImage(CGLayerGetContext(cg_layer()),
139 CGRectMake(0, 0, size().width(), size().height()),
141 // Discard the cache bitmap, since we no longer need it.
142 cg_bitmap_.reset(NULL);
146 DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));
149 // The CGLayer's origin is in the lower left, but flipping the CTM would
150 // cause the image to get drawn upside down. So we move the rectangle
151 // to the right position before drawing the image.
152 CGContextRef layer = CGLayerGetContext(cg_layer());
153 gfx::Rect paint_rect = copy_rect;
154 paint_rect.set_y(size().height() - copy_rect.bottom());
155 CGContextDrawImage(layer, paint_rect.ToCGRect(), image);
157 // The layer hasn't been created yet, so draw into the cache bitmap.
158 gfx::Rect paint_rect = copy_rect;
159 paint_rect.set_y(size().height() - copy_rect.bottom());
160 CGContextDrawImage(cg_bitmap_, paint_rect.ToCGRect(), image);
165 bool BackingStoreMac::CopyFromBackingStore(const gfx::Rect& rect,
166 skia::PlatformBitmap* output) {
167 // TODO(thakis): Make sure this works with HiDPI backing stores.
168 if (!output->Allocate(rect.width(), rect.height(), true))
171 CGContextRef temp_context = output->GetSurface();
172 gfx::ScopedCGContextSaveGState save_gstate(temp_context);
173 CGContextTranslateCTM(temp_context, 0.0, size().height());
174 CGContextScaleCTM(temp_context, 1.0, -1.0);
176 CGContextDrawLayerAtPoint(temp_context, CGPointMake(-rect.x(), -rect.y()),
179 base::ScopedCFTypeRef<CGImageRef> bitmap_image(
180 CGBitmapContextCreateImage(cg_bitmap_));
183 CGRectMake(-rect.x(), -rect.y(), rect.width(), rect.height()),
190 // Scroll the contents of our CGLayer
191 void BackingStoreMac::ScrollBackingStore(const gfx::Vector2d& delta,
192 const gfx::Rect& clip_rect,
193 const gfx::Size& view_size) {
194 DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));
196 // "Scroll" the contents of the layer by creating a new CGLayer,
197 // copying the contents of the old one into the new one offset by the scroll
198 // amount, swapping in the new CGLayer, and then painting in the new data.
200 // The Windows code always sets the whole backing store as the source of the
201 // scroll. Thus, we only have to worry about pixels which will end up inside
202 // the clipping rectangle. (Note that the clipping rectangle is not
203 // translated by the scroll.)
205 // We assume |clip_rect| is contained within the backing store.
206 DCHECK(clip_rect.bottom() <= size().height());
207 DCHECK(clip_rect.right() <= size().width());
209 if ((delta.x() || delta.y()) &&
210 abs(delta.x()) < size().width() && abs(delta.y()) < size().height()) {
212 CGContextRef layer = CGLayerGetContext(cg_layer());
213 gfx::ScopedCGContextSaveGState save_gstate(layer);
214 CGContextClipToRect(layer,
215 CGRectMake(clip_rect.x(),
216 size().height() - clip_rect.bottom(),
218 clip_rect.height()));
219 CGContextDrawLayerAtPoint(layer,
220 CGPointMake(delta.x(), -delta.y()), cg_layer());
222 // We don't have a layer, so scroll the contents of the CGBitmapContext.
223 base::ScopedCFTypeRef<CGImageRef> bitmap_image(
224 CGBitmapContextCreateImage(cg_bitmap_));
225 gfx::ScopedCGContextSaveGState save_gstate(cg_bitmap_);
226 CGContextClipToRect(cg_bitmap_,
227 CGRectMake(clip_rect.x(),
228 size().height() - clip_rect.bottom(),
230 clip_rect.height()));
231 CGContextDrawImage(cg_bitmap_,
232 CGRectMake(delta.x(), -delta.y(),
233 size().width(), size().height()),
239 void BackingStoreMac::CopyFromBackingStoreToCGContext(const CGRect& dest_rect,
240 CGContextRef context) {
241 gfx::ScopedCGContextSaveGState save_gstate(context);
242 CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
244 CGContextDrawLayerInRect(context, dest_rect, cg_layer_);
246 base::ScopedCFTypeRef<CGImageRef> image(
247 CGBitmapContextCreateImage(cg_bitmap_));
248 CGContextDrawImage(context, dest_rect, image);
252 CGLayerRef BackingStoreMac::CreateCGLayer() {
253 // The CGLayer should be optimized for drawing into the containing window,
254 // so extract a CGContext corresponding to the window to be passed to
255 // CGLayerCreateWithContext.
256 NSWindow* window = [render_widget_host()->GetView()->GetNativeView() window];
257 if ([window windowNumber] <= 0) {
258 // This catches a nil |window|, as well as windows that exist but that
259 // aren't yet connected to WindowServer.
263 NSGraphicsContext* ns_context = [window graphicsContext];
266 CGContextRef cg_context = static_cast<CGContextRef>(
267 [ns_context graphicsPort]);
270 // Note: This takes the backingScaleFactor of cg_context into account. The
271 // bitmap backing |layer| will be size() * 2 in HiDPI mode automatically.
272 CGLayerRef layer = CGLayerCreateWithContext(cg_context,
280 CGContextRef BackingStoreMac::CreateCGBitmapContext() {
281 gfx::Size pixel_size = gfx::ToFlooredSize(
282 gfx::ScaleSize(size(), device_scale_factor_));
283 // A CGBitmapContext serves as a stand-in for the layer before the view is
284 // in a containing window.
285 CGContextRef context = CGBitmapContextCreate(NULL,
288 8, pixel_size.width() * 4,
289 base::mac::GetSystemColorSpace(),
290 kCGImageAlphaPremultipliedFirst |
291 kCGBitmapByteOrder32Host);
297 } // namespace content