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