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