OLD | NEW |
| (Empty) |
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 #include "content/browser/renderer_host/compositing_iosurface_mac.h" | |
6 | |
7 #include <OpenGL/CGLIOSurface.h> | |
8 #include <OpenGL/CGLRenderers.h> | |
9 #include <OpenGL/OpenGL.h> | |
10 | |
11 #include "base/bind.h" | |
12 #include "base/bind_helpers.h" | |
13 #include "base/debug/trace_event.h" | |
14 #include "base/logging.h" | |
15 #include "base/mac/mac_util.h" | |
16 #include "base/message_loop/message_loop.h" | |
17 #include "base/threading/platform_thread.h" | |
18 #include "content/browser/gpu/gpu_data_manager_impl.h" | |
19 #include "content/browser/renderer_host/compositing_iosurface_context_mac.h" | |
20 #include "content/browser/renderer_host/render_widget_host_impl.h" | |
21 #include "content/browser/renderer_host/render_widget_host_view_mac.h" | |
22 #include "content/common/content_constants_internal.h" | |
23 #include "gpu/config/gpu_driver_bug_workaround_type.h" | |
24 #include "media/base/video_util.h" | |
25 #include "third_party/skia/include/core/SkBitmap.h" | |
26 #include "ui/gfx/rect.h" | |
27 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" | |
28 #include "ui/gfx/size_conversions.h" | |
29 #include "ui/gl/gl_context.h" | |
30 | |
31 #ifdef NDEBUG | |
32 #define CHECK_GL_ERROR() | |
33 #define CHECK_AND_SAVE_GL_ERROR() | |
34 #else | |
35 #define CHECK_GL_ERROR() do { \ | |
36 GLenum gl_error = glGetError(); \ | |
37 LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error: " << gl_error; \ | |
38 } while (0) | |
39 #define CHECK_AND_SAVE_GL_ERROR() do { \ | |
40 GLenum gl_error = GetAndSaveGLError(); \ | |
41 LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error: " << gl_error; \ | |
42 } while (0) | |
43 #endif | |
44 | |
45 namespace content { | |
46 | |
47 // static | |
48 scoped_refptr<CompositingIOSurfaceMac> CompositingIOSurfaceMac::Create() { | |
49 scoped_refptr<CompositingIOSurfaceContext> offscreen_context = | |
50 CompositingIOSurfaceContext::Get( | |
51 CompositingIOSurfaceContext::kOffscreenContextWindowNumber); | |
52 if (!offscreen_context) { | |
53 LOG(ERROR) << "Failed to create context for offscreen operations"; | |
54 return NULL; | |
55 } | |
56 | |
57 return new CompositingIOSurfaceMac(offscreen_context); | |
58 } | |
59 | |
60 CompositingIOSurfaceMac::CompositingIOSurfaceMac( | |
61 const scoped_refptr<CompositingIOSurfaceContext>& offscreen_context) | |
62 : offscreen_context_(offscreen_context), | |
63 io_surface_handle_(0), | |
64 scale_factor_(1.f), | |
65 texture_(0), | |
66 gl_error_(GL_NO_ERROR), | |
67 eviction_queue_iterator_(eviction_queue_.Get().end()), | |
68 eviction_has_been_drawn_since_updated_(false) { | |
69 CHECK(offscreen_context_); | |
70 } | |
71 | |
72 CompositingIOSurfaceMac::~CompositingIOSurfaceMac() { | |
73 { | |
74 gfx::ScopedCGLSetCurrentContext scoped_set_current_context( | |
75 offscreen_context_->cgl_context()); | |
76 UnrefIOSurfaceWithContextCurrent(); | |
77 } | |
78 offscreen_context_ = NULL; | |
79 DCHECK(eviction_queue_iterator_ == eviction_queue_.Get().end()); | |
80 } | |
81 | |
82 bool CompositingIOSurfaceMac::SetIOSurfaceWithContextCurrent( | |
83 scoped_refptr<CompositingIOSurfaceContext> current_context, | |
84 IOSurfaceID io_surface_handle, | |
85 const gfx::Size& size, | |
86 float scale_factor) { | |
87 bool result = MapIOSurfaceToTextureWithContextCurrent( | |
88 current_context, size, scale_factor, io_surface_handle); | |
89 EvictionMarkUpdated(); | |
90 return result; | |
91 } | |
92 | |
93 int CompositingIOSurfaceMac::GetRendererID() { | |
94 GLint current_renderer_id = -1; | |
95 if (CGLGetParameter(offscreen_context_->cgl_context(), | |
96 kCGLCPCurrentRendererID, | |
97 ¤t_renderer_id) == kCGLNoError) | |
98 return current_renderer_id & kCGLRendererIDMatchingMask; | |
99 return -1; | |
100 } | |
101 | |
102 bool CompositingIOSurfaceMac::DrawIOSurface( | |
103 scoped_refptr<CompositingIOSurfaceContext> drawing_context, | |
104 const gfx::Rect& window_rect, | |
105 float window_scale_factor) { | |
106 DCHECK_EQ(CGLGetCurrentContext(), drawing_context->cgl_context()); | |
107 | |
108 bool has_io_surface = HasIOSurface(); | |
109 TRACE_EVENT1("browser", "CompositingIOSurfaceMac::DrawIOSurface", | |
110 "has_io_surface", has_io_surface); | |
111 | |
112 gfx::Rect pixel_window_rect = | |
113 ToNearestRect(gfx::ScaleRect(window_rect, window_scale_factor)); | |
114 glViewport( | |
115 pixel_window_rect.x(), pixel_window_rect.y(), | |
116 pixel_window_rect.width(), pixel_window_rect.height()); | |
117 | |
118 SurfaceQuad quad; | |
119 quad.set_size(dip_io_surface_size_, pixel_io_surface_size_); | |
120 | |
121 glMatrixMode(GL_PROJECTION); | |
122 glLoadIdentity(); | |
123 | |
124 // Note that the projection keeps things in view units, so the use of | |
125 // window_rect / dip_io_surface_size_ (as opposed to the pixel_ variants) | |
126 // below is correct. | |
127 glOrtho(0, window_rect.width(), window_rect.height(), 0, -1, 1); | |
128 glMatrixMode(GL_MODELVIEW); | |
129 glLoadIdentity(); | |
130 | |
131 glDisable(GL_DEPTH_TEST); | |
132 glDisable(GL_BLEND); | |
133 | |
134 glColor4f(1, 1, 1, 1); | |
135 if (has_io_surface) { | |
136 glEnable(GL_TEXTURE_RECTANGLE_ARB); | |
137 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_); | |
138 DrawQuad(quad); | |
139 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); | |
140 glDisable(GL_TEXTURE_RECTANGLE_ARB); | |
141 CHECK_AND_SAVE_GL_ERROR(); | |
142 | |
143 // Fill the resize gutters with white. | |
144 if (window_rect.width() > dip_io_surface_size_.width() || | |
145 window_rect.height() > dip_io_surface_size_.height()) { | |
146 SurfaceQuad filler_quad; | |
147 if (window_rect.width() > dip_io_surface_size_.width()) { | |
148 // Draw right-side gutter down to the bottom of the window. | |
149 filler_quad.set_rect(dip_io_surface_size_.width(), 0.0f, | |
150 window_rect.width(), window_rect.height()); | |
151 DrawQuad(filler_quad); | |
152 } | |
153 if (window_rect.height() > dip_io_surface_size_.height()) { | |
154 // Draw bottom gutter to the width of the IOSurface. | |
155 filler_quad.set_rect( | |
156 0.0f, dip_io_surface_size_.height(), | |
157 dip_io_surface_size_.width(), window_rect.height()); | |
158 DrawQuad(filler_quad); | |
159 } | |
160 } | |
161 | |
162 // Workaround for issue 158469. Issue a dummy draw call with texture_ not | |
163 // bound to a texture, in order to shake all references to the IOSurface out | |
164 // of the driver. | |
165 glBegin(GL_TRIANGLES); | |
166 glEnd(); | |
167 CHECK_AND_SAVE_GL_ERROR(); | |
168 } else { | |
169 // Should match the clear color of RenderWidgetHostViewMac. | |
170 glClearColor(1.0f, 1.0f, 1.0f, 1.0f); | |
171 glClear(GL_COLOR_BUFFER_BIT); | |
172 } | |
173 | |
174 bool workaround_needed = | |
175 GpuDataManagerImpl::GetInstance()->IsDriverBugWorkaroundActive( | |
176 gpu::FORCE_GL_FINISH_AFTER_COMPOSITING); | |
177 if (workaround_needed) { | |
178 TRACE_EVENT0("gpu", "glFinish"); | |
179 glFinish(); | |
180 } | |
181 | |
182 // Check if any of the drawing calls result in an error. | |
183 GetAndSaveGLError(); | |
184 bool result = true; | |
185 if (gl_error_ != GL_NO_ERROR) { | |
186 LOG(ERROR) << "GL error in DrawIOSurface: " << gl_error_; | |
187 result = false; | |
188 // If there was an error, clear the screen to a light grey to avoid | |
189 // rendering artifacts. If we're in a really bad way, this too may | |
190 // generate an error. Clear the GL error afterwards just in case. | |
191 glClearColor(0.8, 0.8, 0.8, 1.0); | |
192 glClear(GL_COLOR_BUFFER_BIT); | |
193 glGetError(); | |
194 } | |
195 | |
196 eviction_has_been_drawn_since_updated_ = true; | |
197 return result; | |
198 } | |
199 | |
200 bool CompositingIOSurfaceMac::MapIOSurfaceToTextureWithContextCurrent( | |
201 const scoped_refptr<CompositingIOSurfaceContext>& current_context, | |
202 const gfx::Size pixel_size, | |
203 float scale_factor, | |
204 IOSurfaceID io_surface_handle) { | |
205 TRACE_EVENT0("browser", "CompositingIOSurfaceMac::MapIOSurfaceToTexture"); | |
206 | |
207 if (!io_surface_ || io_surface_handle != io_surface_handle_) | |
208 UnrefIOSurfaceWithContextCurrent(); | |
209 | |
210 pixel_io_surface_size_ = pixel_size; | |
211 scale_factor_ = scale_factor; | |
212 dip_io_surface_size_ = gfx::ToFlooredSize( | |
213 gfx::ScaleSize(pixel_io_surface_size_, 1.0 / scale_factor_)); | |
214 | |
215 // Early-out if the IOSurface has not changed. Note that because IOSurface | |
216 // sizes are rounded, the same IOSurface may have two different sizes | |
217 // associated with it. | |
218 if (io_surface_ && io_surface_handle == io_surface_handle_) | |
219 return true; | |
220 | |
221 io_surface_.reset(IOSurfaceLookup(io_surface_handle)); | |
222 // Can fail if IOSurface with that ID was already released by the gpu | |
223 // process. | |
224 if (!io_surface_) { | |
225 UnrefIOSurfaceWithContextCurrent(); | |
226 return false; | |
227 } | |
228 | |
229 io_surface_handle_ = io_surface_handle; | |
230 | |
231 // Actual IOSurface size is rounded up to reduce reallocations during window | |
232 // resize. Get the actual size to properly map the texture. | |
233 gfx::Size rounded_size(IOSurfaceGetWidth(io_surface_), | |
234 IOSurfaceGetHeight(io_surface_)); | |
235 | |
236 glGenTextures(1, &texture_); | |
237 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_); | |
238 glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
239 glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
240 CHECK_AND_SAVE_GL_ERROR(); | |
241 GLuint plane = 0; | |
242 CGLError cgl_error = CGLTexImageIOSurface2D( | |
243 current_context->cgl_context(), | |
244 GL_TEXTURE_RECTANGLE_ARB, | |
245 GL_RGBA, | |
246 rounded_size.width(), | |
247 rounded_size.height(), | |
248 GL_BGRA, | |
249 GL_UNSIGNED_INT_8_8_8_8_REV, | |
250 io_surface_.get(), | |
251 plane); | |
252 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); | |
253 if (cgl_error != kCGLNoError) { | |
254 LOG(ERROR) << "CGLTexImageIOSurface2D: " << cgl_error; | |
255 UnrefIOSurfaceWithContextCurrent(); | |
256 return false; | |
257 } | |
258 GetAndSaveGLError(); | |
259 if (gl_error_ != GL_NO_ERROR) { | |
260 LOG(ERROR) << "GL error in MapIOSurfaceToTexture: " << gl_error_; | |
261 UnrefIOSurfaceWithContextCurrent(); | |
262 return false; | |
263 } | |
264 return true; | |
265 } | |
266 | |
267 void CompositingIOSurfaceMac::UnrefIOSurface() { | |
268 gfx::ScopedCGLSetCurrentContext scoped_set_current_context( | |
269 offscreen_context_->cgl_context()); | |
270 UnrefIOSurfaceWithContextCurrent(); | |
271 } | |
272 | |
273 void CompositingIOSurfaceMac::DrawQuad(const SurfaceQuad& quad) { | |
274 TRACE_EVENT0("gpu", "CompositingIOSurfaceMac::DrawQuad"); | |
275 | |
276 glEnableClientState(GL_VERTEX_ARRAY); CHECK_AND_SAVE_GL_ERROR(); | |
277 glEnableClientState(GL_TEXTURE_COORD_ARRAY); CHECK_AND_SAVE_GL_ERROR(); | |
278 | |
279 glVertexPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].x_); | |
280 glTexCoordPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].tx_); | |
281 glDrawArrays(GL_QUADS, 0, 4); CHECK_AND_SAVE_GL_ERROR(); | |
282 | |
283 glDisableClientState(GL_VERTEX_ARRAY); | |
284 glDisableClientState(GL_TEXTURE_COORD_ARRAY); | |
285 } | |
286 | |
287 void CompositingIOSurfaceMac::UnrefIOSurfaceWithContextCurrent() { | |
288 if (texture_) { | |
289 glDeleteTextures(1, &texture_); | |
290 texture_ = 0; | |
291 } | |
292 pixel_io_surface_size_ = gfx::Size(); | |
293 scale_factor_ = 1; | |
294 dip_io_surface_size_ = gfx::Size(); | |
295 io_surface_.reset(); | |
296 | |
297 // Forget the ID, because even if it is still around when we want to use it | |
298 // again, OSX may have reused the same ID for a new tab and we don't want to | |
299 // blit random tab contents. | |
300 io_surface_handle_ = 0; | |
301 | |
302 EvictionMarkEvicted(); | |
303 } | |
304 | |
305 bool CompositingIOSurfaceMac::HasBeenPoisoned() const { | |
306 return offscreen_context_->HasBeenPoisoned(); | |
307 } | |
308 | |
309 GLenum CompositingIOSurfaceMac::GetAndSaveGLError() { | |
310 GLenum gl_error = glGetError(); | |
311 if (gl_error_ == GL_NO_ERROR) | |
312 gl_error_ = gl_error; | |
313 return gl_error; | |
314 } | |
315 | |
316 void CompositingIOSurfaceMac::EvictionMarkUpdated() { | |
317 EvictionMarkEvicted(); | |
318 eviction_queue_.Get().push_back(this); | |
319 eviction_queue_iterator_ = --eviction_queue_.Get().end(); | |
320 eviction_has_been_drawn_since_updated_ = false; | |
321 EvictionScheduleDoEvict(); | |
322 } | |
323 | |
324 void CompositingIOSurfaceMac::EvictionMarkEvicted() { | |
325 if (eviction_queue_iterator_ == eviction_queue_.Get().end()) | |
326 return; | |
327 eviction_queue_.Get().erase(eviction_queue_iterator_); | |
328 eviction_queue_iterator_ = eviction_queue_.Get().end(); | |
329 eviction_has_been_drawn_since_updated_ = false; | |
330 } | |
331 | |
332 // static | |
333 void CompositingIOSurfaceMac::EvictionScheduleDoEvict() { | |
334 if (eviction_scheduled_) | |
335 return; | |
336 if (eviction_queue_.Get().size() <= kMaximumUnevictedSurfaces) | |
337 return; | |
338 | |
339 eviction_scheduled_ = true; | |
340 base::MessageLoop::current()->PostTask( | |
341 FROM_HERE, | |
342 base::Bind(&CompositingIOSurfaceMac::EvictionDoEvict)); | |
343 } | |
344 | |
345 // static | |
346 void CompositingIOSurfaceMac::EvictionDoEvict() { | |
347 eviction_scheduled_ = false; | |
348 // Walk the list of allocated surfaces from least recently used to most | |
349 // recently used. | |
350 for (EvictionQueue::iterator it = eviction_queue_.Get().begin(); | |
351 it != eviction_queue_.Get().end();) { | |
352 CompositingIOSurfaceMac* surface = *it; | |
353 ++it; | |
354 | |
355 // If the number of IOSurfaces allocated is less than the threshold, | |
356 // stop walking the list of surfaces. | |
357 if (eviction_queue_.Get().size() <= kMaximumUnevictedSurfaces) | |
358 break; | |
359 | |
360 // Don't evict anything that has not yet been drawn. | |
361 if (!surface->eviction_has_been_drawn_since_updated_) | |
362 continue; | |
363 | |
364 // Evict the surface. | |
365 surface->UnrefIOSurface(); | |
366 } | |
367 } | |
368 | |
369 // static | |
370 base::LazyInstance<CompositingIOSurfaceMac::EvictionQueue> | |
371 CompositingIOSurfaceMac::eviction_queue_; | |
372 | |
373 // static | |
374 bool CompositingIOSurfaceMac::eviction_scheduled_ = false; | |
375 | |
376 } // namespace content | |
OLD | NEW |