| 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 |