Home | History | Annotate | Download | only in renderer_host
      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                       &current_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