• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
46namespace content {
47
48// static
49scoped_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
61CompositingIOSurfaceMac::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
73CompositingIOSurfaceMac::~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
83bool 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
94int 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
103bool 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
201bool 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
268void CompositingIOSurfaceMac::UnrefIOSurface() {
269  gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
270      offscreen_context_->cgl_context());
271  UnrefIOSurfaceWithContextCurrent();
272}
273
274void 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
288void 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
306bool CompositingIOSurfaceMac::HasBeenPoisoned() const {
307  return offscreen_context_->HasBeenPoisoned();
308}
309
310GLenum 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
317void 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
325void 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
334void 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
347void 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
371base::LazyInstance<CompositingIOSurfaceMac::EvictionQueue>
372    CompositingIOSurfaceMac::eviction_queue_;
373
374// static
375bool CompositingIOSurfaceMac::eviction_scheduled_ = false;
376
377}  // namespace content
378