- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / renderer_host / backing_store_mac.mm
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.
4
5 #import <Cocoa/Cocoa.h>
6
7 #include "content/browser/renderer_host/backing_store_mac.h"
8
9 #include <cmath>
10
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"
25
26 namespace content {
27
28 // Mac Backing Stores:
29 //
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.
34
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());
40   if (!cg_layer_) {
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_);
44   }
45 }
46
47 BackingStoreMac::~BackingStoreMac() {
48 }
49
50 void BackingStoreMac::ScaleFactorChanged(float device_scale_factor) {
51   if (device_scale_factor == device_scale_factor_)
52     return;
53
54   device_scale_factor_ = device_scale_factor;
55
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_);
62   }
63
64   cg_layer_.swap(new_layer);
65   if (!cg_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_);
69   }
70 }
71
72 size_t BackingStoreMac::MemorySize() {
73   return gfx::ToFlooredSize(
74       gfx::ScaleSize(size(), device_scale_factor_)).GetArea() * 4;
75 }
76
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,
82     float scale_factor,
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()));
87
88   TransportDIB* dib = process->GetTransportDIB(bitmap);
89   if (!dib)
90     return;
91
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));
96
97   size_t bitmap_byte_count =
98       pixel_bitmap_rect.width() * pixel_bitmap_rect.height() * 4;
99   DCHECK_GE(dib->size(), bitmap_byte_count);
100
101   base::ScopedCFTypeRef<CGDataProviderRef> data_provider(
102       CGDataProviderCreateWithData(
103           NULL, dib->memory(), bitmap_byte_count, NULL));
104
105   base::ScopedCFTypeRef<CGImageRef> bitmap_image(
106       CGImageCreate(pixel_bitmap_rect.width(),
107                     pixel_bitmap_rect.height(),
108                     8,
109                     32,
110                     4 * pixel_bitmap_rect.width(),
111                     base::mac::GetSystemColorSpace(),
112                     kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
113                     data_provider,
114                     NULL,
115                     false,
116                     kCGRenderingIntentDefault));
117
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));
122
123     // Only the subpixels given by copy_rect have pixels to copy.
124     base::ScopedCFTypeRef<CGImageRef> image(CGImageCreateWithImageInRect(
125         bitmap_image,
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())));
130
131     if (!cg_layer()) {
132       // The view may have moved to a window.  Try to get a CGLayer.
133       cg_layer_.reset(CreateCGLayer());
134       if (cg_layer()) {
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()),
140                            bitmap_image);
141         // Discard the cache bitmap, since we no longer need it.
142         cg_bitmap_.reset(NULL);
143       }
144     }
145
146     DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));
147
148     if (cg_layer()) {
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);
156     } else {
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);
161     }
162   }
163 }
164
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))
169     return false;
170
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);
175   if (cg_layer()) {
176     CGContextDrawLayerAtPoint(temp_context, CGPointMake(-rect.x(), -rect.y()),
177                               cg_layer());
178   } else {
179     base::ScopedCFTypeRef<CGImageRef> bitmap_image(
180         CGBitmapContextCreateImage(cg_bitmap_));
181     CGContextDrawImage(
182         temp_context,
183         CGRectMake(-rect.x(), -rect.y(), rect.width(), rect.height()),
184         bitmap_image);
185   }
186
187   return true;
188 }
189
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()));
195
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.
199   //
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.)
204
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());
208
209   if ((delta.x() || delta.y()) &&
210        abs(delta.x()) < size().width() && abs(delta.y()) < size().height()) {
211     if (cg_layer()) {
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(),
217                                      clip_rect.width(),
218                                      clip_rect.height()));
219       CGContextDrawLayerAtPoint(layer,
220                                 CGPointMake(delta.x(), -delta.y()), cg_layer());
221     } else {
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(),
229                                      clip_rect.width(),
230                                      clip_rect.height()));
231       CGContextDrawImage(cg_bitmap_,
232                          CGRectMake(delta.x(), -delta.y(),
233                                     size().width(), size().height()),
234                          bitmap_image);
235     }
236   }
237 }
238
239 void BackingStoreMac::CopyFromBackingStoreToCGContext(const CGRect& dest_rect,
240                                                       CGContextRef context) {
241   gfx::ScopedCGContextSaveGState save_gstate(context);
242   CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
243   if (cg_layer_) {
244     CGContextDrawLayerInRect(context, dest_rect, cg_layer_);
245   } else {
246     base::ScopedCFTypeRef<CGImageRef> image(
247         CGBitmapContextCreateImage(cg_bitmap_));
248     CGContextDrawImage(context, dest_rect, image);
249   }
250 }
251
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.
260     return NULL;
261   }
262
263   NSGraphicsContext* ns_context = [window graphicsContext];
264   DCHECK(ns_context);
265
266   CGContextRef cg_context = static_cast<CGContextRef>(
267       [ns_context graphicsPort]);
268   DCHECK(cg_context);
269
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,
273                                               size().ToCGSize(),
274                                               NULL);
275   DCHECK(layer);
276
277   return layer;
278 }
279
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,
286                                                pixel_size.width(),
287                                                pixel_size.height(),
288                                                8, pixel_size.width() * 4,
289                                                base::mac::GetSystemColorSpace(),
290                                                kCGImageAlphaPremultipliedFirst |
291                                                    kCGBitmapByteOrder32Host);
292   DCHECK(context);
293
294   return context;
295 }
296
297 }  // namespace content