| 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/compositor/io_surface_texture_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/callback_helpers.h" | |
| 15 #include "base/debug/trace_event.h" | |
| 16 #include "base/logging.h" | |
| 17 #include "base/mac/bind_objc_block.h" | |
| 18 #include "base/mac/mac_util.h" | |
| 19 #include "base/message_loop/message_loop.h" | |
| 20 #include "base/threading/platform_thread.h" | |
| 21 #include "content/browser/compositor/io_surface_context_mac.h" | |
| 22 #include "content/browser/gpu/gpu_data_manager_impl.h" | |
| 23 #include "content/browser/renderer_host/render_widget_host_impl.h" | |
| 24 #include "content/browser/renderer_host/render_widget_host_view_mac.h" | |
| 25 #include "content/common/content_constants_internal.h" | |
| 26 #include "gpu/config/gpu_driver_bug_workaround_type.h" | |
| 27 #include "media/base/video_util.h" | |
| 28 #include "third_party/skia/include/core/SkBitmap.h" | |
| 29 #include "ui/gfx/rect.h" | |
| 30 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" | |
| 31 #include "ui/gfx/size_conversions.h" | |
| 32 #include "ui/gl/gl_context.h" | |
| 33 | |
| 34 namespace content { | |
| 35 | |
| 36 // static | |
| 37 scoped_refptr<IOSurfaceTexture> IOSurfaceTexture::Create() { | |
| 38 scoped_refptr<IOSurfaceContext> offscreen_context = | |
| 39 IOSurfaceContext::Get( | |
| 40 IOSurfaceContext::kOffscreenContext); | |
| 41 if (!offscreen_context.get()) { | |
| 42 LOG(ERROR) << "Failed to create context for offscreen operations"; | |
| 43 return NULL; | |
| 44 } | |
| 45 | |
| 46 return new IOSurfaceTexture(offscreen_context); | |
| 47 } | |
| 48 | |
| 49 IOSurfaceTexture::IOSurfaceTexture( | |
| 50 const scoped_refptr<IOSurfaceContext>& offscreen_context) | |
| 51 : offscreen_context_(offscreen_context), | |
| 52 texture_(0), | |
| 53 gl_error_(GL_NO_ERROR), | |
| 54 eviction_queue_iterator_(eviction_queue_.Get().end()), | |
| 55 eviction_has_been_drawn_since_updated_(false) { | |
| 56 CHECK(offscreen_context_.get()); | |
| 57 } | |
| 58 | |
| 59 IOSurfaceTexture::~IOSurfaceTexture() { | |
| 60 ReleaseIOSurfaceAndTexture(); | |
| 61 offscreen_context_ = NULL; | |
| 62 DCHECK(eviction_queue_iterator_ == eviction_queue_.Get().end()); | |
| 63 } | |
| 64 | |
| 65 bool IOSurfaceTexture::DrawIOSurface() { | |
| 66 TRACE_EVENT0("browser", "IOSurfaceTexture::DrawIOSurface"); | |
| 67 | |
| 68 // If we have release the IOSurface, clear the screen to light grey and | |
| 69 // early-out. | |
| 70 if (!io_surface_) { | |
| 71 glClearColor(0.9, 0.9, 0.9, 1); | |
| 72 glClear(GL_COLOR_BUFFER_BIT); | |
| 73 return false; | |
| 74 } | |
| 75 | |
| 76 // The viewport is the size of the CALayer, which should always match the | |
| 77 // IOSurface pixel size. | |
| 78 GLint viewport[4]; | |
| 79 glGetIntegerv(GL_VIEWPORT, viewport); | |
| 80 gfx::Rect viewport_rect(viewport[0], viewport[1], viewport[2], viewport[3]); | |
| 81 DCHECK_EQ(pixel_size_.ToString(), viewport_rect.size().ToString()); | |
| 82 | |
| 83 // Set the projection matrix to match 1 unit to 1 pixel. | |
| 84 glMatrixMode(GL_PROJECTION); | |
| 85 glLoadIdentity(); | |
| 86 glOrtho(0, viewport_rect.width(), 0, viewport_rect.height(), -1, 1); | |
| 87 glMatrixMode(GL_MODELVIEW); | |
| 88 glLoadIdentity(); | |
| 89 | |
| 90 // Draw a quad the size of the IOSurface. This should cover the full viewport. | |
| 91 glColor4f(1, 1, 1, 1); | |
| 92 glEnable(GL_TEXTURE_RECTANGLE_ARB); | |
| 93 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_); | |
| 94 glBegin(GL_QUADS); | |
| 95 glTexCoord2f(0, 0); | |
| 96 glVertex2f(0, 0); | |
| 97 glTexCoord2f(pixel_size_.width(), 0); | |
| 98 glVertex2f(pixel_size_.width(), 0); | |
| 99 glTexCoord2f(pixel_size_.width(), pixel_size_.height()); | |
| 100 glVertex2f(pixel_size_.width(), pixel_size_.height()); | |
| 101 glTexCoord2f(0, pixel_size_.height()); | |
| 102 glVertex2f(0, pixel_size_.height()); | |
| 103 glEnd(); | |
| 104 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); | |
| 105 glDisable(GL_TEXTURE_RECTANGLE_ARB); | |
| 106 | |
| 107 // Workaround for issue 158469. Issue a dummy draw call with texture_ not | |
| 108 // bound to a texture, in order to shake all references to the IOSurface out | |
| 109 // of the driver. | |
| 110 glBegin(GL_TRIANGLES); | |
| 111 glEnd(); | |
| 112 | |
| 113 bool workaround_needed = | |
| 114 GpuDataManagerImpl::GetInstance()->IsDriverBugWorkaroundActive( | |
| 115 gpu::FORCE_GL_FINISH_AFTER_COMPOSITING); | |
| 116 if (workaround_needed) { | |
| 117 TRACE_EVENT0("gpu", "glFinish"); | |
| 118 glFinish(); | |
| 119 } | |
| 120 | |
| 121 // Check if any of the drawing calls result in an error. | |
| 122 GetAndSaveGLError(); | |
| 123 bool result = true; | |
| 124 if (gl_error_ != GL_NO_ERROR) { | |
| 125 LOG(ERROR) << "GL error in DrawIOSurface: " << gl_error_; | |
| 126 result = false; | |
| 127 // If there was an error, clear the screen to a light grey to avoid | |
| 128 // rendering artifacts. | |
| 129 glClearColor(0.8, 0.8, 0.8, 1.0); | |
| 130 glClear(GL_COLOR_BUFFER_BIT); | |
| 131 } | |
| 132 | |
| 133 eviction_has_been_drawn_since_updated_ = true; | |
| 134 return result; | |
| 135 } | |
| 136 | |
| 137 bool IOSurfaceTexture::SetIOSurface( | |
| 138 IOSurfaceID io_surface_id, | |
| 139 const gfx::Size& pixel_size) { | |
| 140 TRACE_EVENT0("browser", "IOSurfaceTexture::MapIOSurfaceToTexture"); | |
| 141 | |
| 142 // Destroy the old IOSurface and texture if it is no longer needed. | |
| 143 bool needs_new_iosurface = | |
| 144 !io_surface_ || io_surface_id != IOSurfaceGetID(io_surface_); | |
| 145 if (needs_new_iosurface) | |
| 146 ReleaseIOSurfaceAndTexture(); | |
| 147 | |
| 148 // Note that because IOSurface sizes are rounded, the same IOSurface may have | |
| 149 // two different sizes associated with it, so update the sizes before the | |
| 150 // early-out. | |
| 151 pixel_size_ = pixel_size; | |
| 152 | |
| 153 // Early-out if the IOSurface has not changed. | |
| 154 if (!needs_new_iosurface) | |
| 155 return true; | |
| 156 | |
| 157 // If we early-out at any point from now on, it's because of an error, and we | |
| 158 // should destroy the texture and release the IOSurface. | |
| 159 base::ScopedClosureRunner error_runner(base::BindBlock(^{ | |
| 160 ReleaseIOSurfaceAndTexture(); | |
| 161 })); | |
| 162 | |
| 163 // Open the IOSurface handle. | |
| 164 io_surface_.reset(IOSurfaceLookup(io_surface_id)); | |
| 165 if (!io_surface_) | |
| 166 return false; | |
| 167 | |
| 168 // Actual IOSurface size is rounded up to reduce reallocations during window | |
| 169 // resize. Get the actual size to properly map the texture. | |
| 170 gfx::Size rounded_size(IOSurfaceGetWidth(io_surface_), | |
| 171 IOSurfaceGetHeight(io_surface_)); | |
| 172 | |
| 173 // Create the GL texture and set it to be backed by the IOSurface. | |
| 174 CGLError cgl_error = kCGLNoError; | |
| 175 { | |
| 176 gfx::ScopedCGLSetCurrentContext scoped_set_current_context( | |
| 177 offscreen_context_->cgl_context()); | |
| 178 glGenTextures(1, &texture_); | |
| 179 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_); | |
| 180 glTexParameterf( | |
| 181 GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| 182 glTexParameterf( | |
| 183 GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| 184 cgl_error = CGLTexImageIOSurface2D( | |
| 185 offscreen_context_->cgl_context(), | |
| 186 GL_TEXTURE_RECTANGLE_ARB, | |
| 187 GL_RGBA, | |
| 188 rounded_size.width(), | |
| 189 rounded_size.height(), | |
| 190 GL_BGRA, | |
| 191 GL_UNSIGNED_INT_8_8_8_8_REV, | |
| 192 io_surface_.get(), | |
| 193 0 /* plane */); | |
| 194 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); | |
| 195 GetAndSaveGLError(); | |
| 196 } | |
| 197 | |
| 198 // Return failure if an error was encountered by CGL or GL. | |
| 199 if (cgl_error != kCGLNoError) { | |
| 200 LOG(ERROR) << "CGLTexImageIOSurface2D failed with CGL error: " << cgl_error; | |
| 201 return false; | |
| 202 } | |
| 203 if (gl_error_ != GL_NO_ERROR) { | |
| 204 LOG(ERROR) << "Hit GL error in SetIOSurface: " << gl_error_; | |
| 205 return false; | |
| 206 } | |
| 207 | |
| 208 ignore_result(error_runner.Release()); | |
| 209 return true; | |
| 210 } | |
| 211 | |
| 212 void IOSurfaceTexture::ReleaseIOSurfaceAndTexture() { | |
| 213 gfx::ScopedCGLSetCurrentContext scoped_set_current_context( | |
| 214 offscreen_context_->cgl_context()); | |
| 215 | |
| 216 if (texture_) { | |
| 217 glDeleteTextures(1, &texture_); | |
| 218 texture_ = 0; | |
| 219 } | |
| 220 pixel_size_ = gfx::Size(); | |
| 221 io_surface_.reset(); | |
| 222 | |
| 223 EvictionMarkEvicted(); | |
| 224 } | |
| 225 | |
| 226 bool IOSurfaceTexture::HasBeenPoisoned() const { | |
| 227 return offscreen_context_->HasBeenPoisoned(); | |
| 228 } | |
| 229 | |
| 230 GLenum IOSurfaceTexture::GetAndSaveGLError() { | |
| 231 GLenum gl_error = glGetError(); | |
| 232 if (gl_error_ == GL_NO_ERROR) | |
| 233 gl_error_ = gl_error; | |
| 234 return gl_error; | |
| 235 } | |
| 236 | |
| 237 void IOSurfaceTexture::EvictionMarkUpdated() { | |
| 238 EvictionMarkEvicted(); | |
| 239 eviction_queue_.Get().push_back(this); | |
| 240 eviction_queue_iterator_ = --eviction_queue_.Get().end(); | |
| 241 eviction_has_been_drawn_since_updated_ = false; | |
| 242 EvictionScheduleDoEvict(); | |
| 243 } | |
| 244 | |
| 245 void IOSurfaceTexture::EvictionMarkEvicted() { | |
| 246 if (eviction_queue_iterator_ == eviction_queue_.Get().end()) | |
| 247 return; | |
| 248 eviction_queue_.Get().erase(eviction_queue_iterator_); | |
| 249 eviction_queue_iterator_ = eviction_queue_.Get().end(); | |
| 250 eviction_has_been_drawn_since_updated_ = false; | |
| 251 } | |
| 252 | |
| 253 // static | |
| 254 void IOSurfaceTexture::EvictionScheduleDoEvict() { | |
| 255 if (eviction_scheduled_) | |
| 256 return; | |
| 257 if (eviction_queue_.Get().size() <= kMaximumUnevictedSurfaces) | |
| 258 return; | |
| 259 | |
| 260 eviction_scheduled_ = true; | |
| 261 base::MessageLoop::current()->PostTask( | |
| 262 FROM_HERE, | |
| 263 base::Bind(&IOSurfaceTexture::EvictionDoEvict)); | |
| 264 } | |
| 265 | |
| 266 // static | |
| 267 void IOSurfaceTexture::EvictionDoEvict() { | |
| 268 eviction_scheduled_ = false; | |
| 269 // Walk the list of allocated surfaces from least recently used to most | |
| 270 // recently used. | |
| 271 for (EvictionQueue::iterator it = eviction_queue_.Get().begin(); | |
| 272 it != eviction_queue_.Get().end();) { | |
| 273 IOSurfaceTexture* surface = *it; | |
| 274 ++it; | |
| 275 | |
| 276 // If the number of IOSurfaces allocated is less than the threshold, | |
| 277 // stop walking the list of surfaces. | |
| 278 if (eviction_queue_.Get().size() <= kMaximumUnevictedSurfaces) | |
| 279 break; | |
| 280 | |
| 281 // Don't evict anything that has not yet been drawn. | |
| 282 if (!surface->eviction_has_been_drawn_since_updated_) | |
| 283 continue; | |
| 284 | |
| 285 // Evict the surface. | |
| 286 surface->ReleaseIOSurfaceAndTexture(); | |
| 287 } | |
| 288 } | |
| 289 | |
| 290 // static | |
| 291 base::LazyInstance<IOSurfaceTexture::EvictionQueue> | |
| 292 IOSurfaceTexture::eviction_queue_; | |
| 293 | |
| 294 // static | |
| 295 bool IOSurfaceTexture::eviction_scheduled_ = false; | |
| 296 | |
| 297 } // namespace content | |
| OLD | NEW |