OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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 "chrome/browser/renderer_host/backing_store_mac.h" | |
8 | |
9 #include "app/surface/transport_dib.h" | |
10 #include "base/logging.h" | |
11 #include "base/mac/mac_util.h" | |
12 #include "base/mac/scoped_cftyperef.h" | |
13 #include "base/sys_info.h" | |
14 #include "chrome/browser/renderer_host/render_process_host.h" | |
15 #include "chrome/browser/renderer_host/render_widget_host.h" | |
16 #include "chrome/browser/renderer_host/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 | |
22 // Mac Backing Stores: | |
23 // | |
24 // Since backing stores are only ever written to or drawn into windows, we keep | |
25 // our backing store in a CGLayer that can get cached in GPU memory. This | |
26 // allows acclerated drawing into the layer and lets scrolling and such happen | |
27 // all or mostly on the GPU, which is good for performance. | |
28 | |
29 namespace { | |
30 | |
31 // Returns whether this version of OS X has broken CGLayers, see | |
32 // http://crbug.com/45553 , comments 5 and 6. | |
33 bool NeedsLayerWorkaround() { | |
34 int32 os_major, os_minor, os_bugfix; | |
35 base::SysInfo::OperatingSystemVersionNumbers( | |
36 &os_major, &os_minor, &os_bugfix); | |
37 return os_major == 10 && os_minor == 5; | |
38 } | |
39 | |
40 } // namespace | |
41 | |
42 BackingStoreMac::BackingStoreMac(RenderWidgetHost* widget, | |
43 const gfx::Size& size) | |
44 : BackingStore(widget, size) { | |
45 cg_layer_.reset(CreateCGLayer()); | |
46 if (!cg_layer_) { | |
47 // The view isn't in a window yet. Use a CGBitmapContext for now. | |
48 cg_bitmap_.reset(CreateCGBitmapContext()); | |
49 } | |
50 } | |
51 | |
52 BackingStoreMac::~BackingStoreMac() { | |
53 } | |
54 | |
55 void BackingStoreMac::PaintToBackingStore( | |
56 RenderProcessHost* process, | |
57 TransportDIB::Id bitmap, | |
58 const gfx::Rect& bitmap_rect, | |
59 const std::vector<gfx::Rect>& copy_rects) { | |
60 DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap())); | |
61 | |
62 TransportDIB* dib = process->GetTransportDIB(bitmap); | |
63 if (!dib) | |
64 return; | |
65 | |
66 base::mac::ScopedCFTypeRef<CGDataProviderRef> data_provider( | |
67 CGDataProviderCreateWithData(NULL, dib->memory(), | |
68 bitmap_rect.width() * bitmap_rect.height() * 4, NULL)); | |
69 | |
70 base::mac::ScopedCFTypeRef<CGImageRef> bitmap_image( | |
71 CGImageCreate(bitmap_rect.width(), bitmap_rect.height(), 8, 32, | |
72 4 * bitmap_rect.width(), base::mac::GetSystemColorSpace(), | |
73 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, | |
74 data_provider, NULL, false, kCGRenderingIntentDefault)); | |
75 | |
76 for (size_t i = 0; i < copy_rects.size(); i++) { | |
77 const gfx::Rect& copy_rect = copy_rects[i]; | |
78 | |
79 // Only the subpixels given by copy_rect have pixels to copy. | |
80 base::mac::ScopedCFTypeRef<CGImageRef> image( | |
81 CGImageCreateWithImageInRect(bitmap_image, CGRectMake( | |
82 copy_rect.x() - bitmap_rect.x(), | |
83 copy_rect.y() - bitmap_rect.y(), | |
84 copy_rect.width(), | |
85 copy_rect.height()))); | |
86 | |
87 if (!cg_layer()) { | |
88 // The view may have moved to a window. Try to get a CGLayer. | |
89 cg_layer_.reset(CreateCGLayer()); | |
90 if (cg_layer()) { | |
91 // now that we have a layer, copy the cached image into it | |
92 base::mac::ScopedCFTypeRef<CGImageRef> bitmap_image( | |
93 CGBitmapContextCreateImage(cg_bitmap_)); | |
94 CGContextDrawImage(CGLayerGetContext(cg_layer()), | |
95 CGRectMake(0, 0, size().width(), size().height()), | |
96 bitmap_image); | |
97 // Discard the cache bitmap, since we no longer need it. | |
98 cg_bitmap_.reset(NULL); | |
99 } | |
100 } | |
101 | |
102 DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap())); | |
103 | |
104 if (cg_layer()) { | |
105 // The CGLayer's origin is in the lower left, but flipping the CTM would | |
106 // cause the image to get drawn upside down. So we move the rectangle | |
107 // to the right position before drawing the image. | |
108 CGContextRef layer = CGLayerGetContext(cg_layer()); | |
109 gfx::Rect paint_rect = copy_rect; | |
110 paint_rect.set_y(size().height() - copy_rect.bottom()); | |
111 CGContextDrawImage(layer, paint_rect.ToCGRect(), image); | |
112 } else { | |
113 // The layer hasn't been created yet, so draw into the cache bitmap. | |
114 gfx::Rect paint_rect = copy_rect; | |
115 paint_rect.set_y(size().height() - copy_rect.bottom()); | |
116 CGContextDrawImage(cg_bitmap_, paint_rect.ToCGRect(), image); | |
117 } | |
118 } | |
119 } | |
120 | |
121 bool BackingStoreMac::CopyFromBackingStore(const gfx::Rect& rect, | |
122 skia::PlatformCanvas* output) { | |
123 if (!output->initialize(rect.width(), rect.height(), true)) | |
124 return false; | |
125 | |
126 CGContextRef temp_context = output->beginPlatformPaint(); | |
127 CGContextSaveGState(temp_context); | |
128 CGContextTranslateCTM(temp_context, 0.0, size().height()); | |
129 CGContextScaleCTM(temp_context, 1.0, -1.0); | |
130 CGContextDrawLayerAtPoint(temp_context, CGPointMake(rect.x(), rect.y()), | |
131 cg_layer()); | |
132 CGContextRestoreGState(temp_context); | |
133 output->endPlatformPaint(); | |
134 return true; | |
135 } | |
136 | |
137 // Scroll the contents of our CGLayer | |
138 void BackingStoreMac::ScrollBackingStore(int dx, int dy, | |
139 const gfx::Rect& clip_rect, | |
140 const gfx::Size& view_size) { | |
141 DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap())); | |
142 | |
143 // "Scroll" the contents of the layer by creating a new CGLayer, | |
144 // copying the contents of the old one into the new one offset by the scroll | |
145 // amount, swapping in the new CGLayer, and then painting in the new data. | |
146 // | |
147 // The Windows code always sets the whole backing store as the source of the | |
148 // scroll. Thus, we only have to worry about pixels which will end up inside | |
149 // the clipping rectangle. (Note that the clipping rectangle is not | |
150 // translated by the scroll.) | |
151 | |
152 // We assume |clip_rect| is contained within the backing store. | |
153 DCHECK(clip_rect.bottom() <= size().height()); | |
154 DCHECK(clip_rect.right() <= size().width()); | |
155 | |
156 if ((dx || dy) && abs(dx) < size().width() && abs(dy) < size().height()) { | |
157 if (cg_layer()) { | |
158 // See http://crbug.com/45553 , comments 5 and 6. | |
159 static bool needs_layer_workaround = NeedsLayerWorkaround(); | |
160 | |
161 base::mac::ScopedCFTypeRef<CGLayerRef> new_layer; | |
162 CGContextRef layer; | |
163 | |
164 if (needs_layer_workaround) { | |
165 new_layer.reset(CreateCGLayer()); | |
166 // If the current view is in a window, the replacement must be too. | |
167 DCHECK(new_layer); | |
168 | |
169 layer = CGLayerGetContext(new_layer); | |
170 CGContextDrawLayerAtPoint(layer, CGPointMake(0, 0), cg_layer()); | |
171 } else { | |
172 layer = CGLayerGetContext(cg_layer()); | |
173 } | |
174 | |
175 CGContextSaveGState(layer); | |
176 CGContextClipToRect(layer, | |
177 CGRectMake(clip_rect.x(), | |
178 size().height() - clip_rect.bottom(), | |
179 clip_rect.width(), | |
180 clip_rect.height())); | |
181 CGContextDrawLayerAtPoint(layer, CGPointMake(dx, -dy), cg_layer()); | |
182 CGContextRestoreGState(layer); | |
183 | |
184 if (needs_layer_workaround) | |
185 cg_layer_.swap(new_layer); | |
186 } else { | |
187 // We don't have a layer, so scroll the contents of the CGBitmapContext. | |
188 base::mac::ScopedCFTypeRef<CGImageRef> bitmap_image( | |
189 CGBitmapContextCreateImage(cg_bitmap_)); | |
190 CGContextSaveGState(cg_bitmap_); | |
191 CGContextClipToRect(cg_bitmap_, | |
192 CGRectMake(clip_rect.x(), | |
193 size().height() - clip_rect.bottom(), | |
194 clip_rect.width(), | |
195 clip_rect.height())); | |
196 CGContextDrawImage(cg_bitmap_, | |
197 CGRectMake(dx, -dy, size().width(), size().height()), | |
198 bitmap_image); | |
199 CGContextRestoreGState(cg_bitmap_); | |
200 } | |
201 } | |
202 } | |
203 | |
204 CGLayerRef BackingStoreMac::CreateCGLayer() { | |
205 // The CGLayer should be optimized for drawing into the containing window, | |
206 // so extract a CGContext corresponding to the window to be passed to | |
207 // CGLayerCreateWithContext. | |
208 NSWindow* window = [render_widget_host()->view()->GetNativeView() window]; | |
209 if ([window windowNumber] <= 0) { | |
210 // This catches a nil |window|, as well as windows that exist but that | |
211 // aren't yet connected to WindowServer. | |
212 return NULL; | |
213 } | |
214 | |
215 NSGraphicsContext* ns_context = [window graphicsContext]; | |
216 DCHECK(ns_context); | |
217 | |
218 CGContextRef cg_context = static_cast<CGContextRef>( | |
219 [ns_context graphicsPort]); | |
220 DCHECK(cg_context); | |
221 | |
222 CGLayerRef layer = CGLayerCreateWithContext(cg_context, | |
223 size().ToCGSize(), | |
224 NULL); | |
225 DCHECK(layer); | |
226 | |
227 return layer; | |
228 } | |
229 | |
230 CGContextRef BackingStoreMac::CreateCGBitmapContext() { | |
231 // A CGBitmapContext serves as a stand-in for the layer before the view is | |
232 // in a containing window. | |
233 CGContextRef context = CGBitmapContextCreate(NULL, | |
234 size().width(), size().height(), | |
235 8, size().width() * 4, | |
236 base::mac::GetSystemColorSpace(), | |
237 kCGImageAlphaPremultipliedFirst | | |
238 kCGBitmapByteOrder32Host); | |
239 DCHECK(context); | |
240 | |
241 return context; | |
242 } | |
OLD | NEW |