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.
5 #include "content/browser/renderer_host/compositing_iosurface_mac.h"
7 #include <OpenGL/CGLIOSurface.h>
8 #include <OpenGL/CGLRenderers.h>
9 #include <OpenGL/OpenGL.h>
10 #include <OpenGL/gl.h>
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"
33 #define CHECK_GL_ERROR()
34 #define CHECK_AND_SAVE_GL_ERROR()
36 #define CHECK_GL_ERROR() do { \
37 GLenum gl_error = glGetError(); \
38 LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error: " << gl_error; \
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; \
49 scoped_refptr<CompositingIOSurfaceMac> CompositingIOSurfaceMac::Create() {
50 scoped_refptr<CompositingIOSurfaceContext> offscreen_context =
51 CompositingIOSurfaceContext::Get(
52 CompositingIOSurfaceContext::kOffscreenContextWindowNumber);
53 if (!offscreen_context) {
54 LOG(ERROR) << "Failed to create context for offscreen operations";
58 return new CompositingIOSurfaceMac(offscreen_context);
61 CompositingIOSurfaceMac::CompositingIOSurfaceMac(
62 const scoped_refptr<CompositingIOSurfaceContext>& offscreen_context)
63 : offscreen_context_(offscreen_context),
64 io_surface_handle_(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_);
73 CompositingIOSurfaceMac::~CompositingIOSurfaceMac() {
75 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
76 offscreen_context_->cgl_context());
77 UnrefIOSurfaceWithContextCurrent();
79 offscreen_context_ = NULL;
80 DCHECK(eviction_queue_iterator_ == eviction_queue_.Get().end());
83 bool CompositingIOSurfaceMac::SetIOSurfaceWithContextCurrent(
84 scoped_refptr<CompositingIOSurfaceContext> current_context,
85 IOSurfaceID io_surface_handle,
86 const gfx::Size& size,
88 bool result = MapIOSurfaceToTextureWithContextCurrent(
89 current_context, size, scale_factor, io_surface_handle);
90 EvictionMarkUpdated();
94 int CompositingIOSurfaceMac::GetRendererID() {
95 GLint current_renderer_id = -1;
96 if (CGLGetParameter(offscreen_context_->cgl_context(),
97 kCGLCPCurrentRendererID,
98 ¤t_renderer_id) == kCGLNoError)
99 return current_renderer_id & kCGLRendererIDMatchingMask;
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());
109 bool has_io_surface = HasIOSurface();
110 TRACE_EVENT1("browser", "CompositingIOSurfaceMac::DrawIOSurface",
111 "has_io_surface", has_io_surface);
113 gfx::Rect pixel_window_rect =
114 ToNearestRect(gfx::ScaleRect(window_rect, window_scale_factor));
116 pixel_window_rect.x(), pixel_window_rect.y(),
117 pixel_window_rect.width(), pixel_window_rect.height());
120 quad.set_size(dip_io_surface_size_, pixel_io_surface_size_);
122 glMatrixMode(GL_PROJECTION);
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)
128 glOrtho(0, window_rect.width(), window_rect.height(), 0, -1, 1);
129 glMatrixMode(GL_MODELVIEW);
132 glDisable(GL_DEPTH_TEST);
135 glColor4f(1, 1, 1, 1);
136 if (has_io_surface) {
137 glEnable(GL_TEXTURE_RECTANGLE_ARB);
138 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_);
140 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
141 glDisable(GL_TEXTURE_RECTANGLE_ARB);
142 CHECK_AND_SAVE_GL_ERROR();
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);
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);
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
166 glBegin(GL_TRIANGLES);
168 CHECK_AND_SAVE_GL_ERROR();
170 // Should match the clear color of RenderWidgetHostViewMac.
171 glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
172 glClear(GL_COLOR_BUFFER_BIT);
175 bool workaround_needed =
176 GpuDataManagerImpl::GetInstance()->IsDriverBugWorkaroundActive(
177 gpu::FORCE_GL_FINISH_AFTER_COMPOSITING);
178 if (workaround_needed) {
179 TRACE_EVENT0("gpu", "glFinish");
183 // Check if any of the drawing calls result in an error.
186 if (gl_error_ != GL_NO_ERROR) {
187 LOG(ERROR) << "GL error in DrawIOSurface: " << gl_error_;
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);
197 eviction_has_been_drawn_since_updated_ = true;
201 bool CompositingIOSurfaceMac::MapIOSurfaceToTextureWithContextCurrent(
202 const scoped_refptr<CompositingIOSurfaceContext>& current_context,
203 const gfx::Size pixel_size,
205 IOSurfaceID io_surface_handle) {
206 TRACE_EVENT0("browser", "CompositingIOSurfaceMac::MapIOSurfaceToTexture");
208 if (!io_surface_ || io_surface_handle != io_surface_handle_)
209 UnrefIOSurfaceWithContextCurrent();
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_));
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_)
222 io_surface_.reset(IOSurfaceLookup(io_surface_handle));
223 // Can fail if IOSurface with that ID was already released by the gpu
226 UnrefIOSurfaceWithContextCurrent();
230 io_surface_handle_ = io_surface_handle;
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_));
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();
243 CGLError cgl_error = CGLTexImageIOSurface2D(
244 current_context->cgl_context(),
245 GL_TEXTURE_RECTANGLE_ARB,
247 rounded_size.width(),
248 rounded_size.height(),
250 GL_UNSIGNED_INT_8_8_8_8_REV,
253 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
254 if (cgl_error != kCGLNoError) {
255 LOG(ERROR) << "CGLTexImageIOSurface2D: " << cgl_error;
256 UnrefIOSurfaceWithContextCurrent();
260 if (gl_error_ != GL_NO_ERROR) {
261 LOG(ERROR) << "GL error in MapIOSurfaceToTexture: " << gl_error_;
262 UnrefIOSurfaceWithContextCurrent();
268 void CompositingIOSurfaceMac::UnrefIOSurface() {
269 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
270 offscreen_context_->cgl_context());
271 UnrefIOSurfaceWithContextCurrent();
274 void CompositingIOSurfaceMac::DrawQuad(const SurfaceQuad& quad) {
275 TRACE_EVENT0("gpu", "CompositingIOSurfaceMac::DrawQuad");
277 glEnableClientState(GL_VERTEX_ARRAY); CHECK_AND_SAVE_GL_ERROR();
278 glEnableClientState(GL_TEXTURE_COORD_ARRAY); CHECK_AND_SAVE_GL_ERROR();
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();
284 glDisableClientState(GL_VERTEX_ARRAY);
285 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
288 void CompositingIOSurfaceMac::UnrefIOSurfaceWithContextCurrent() {
290 glDeleteTextures(1, &texture_);
293 pixel_io_surface_size_ = gfx::Size();
295 dip_io_surface_size_ = gfx::Size();
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;
303 EvictionMarkEvicted();
306 bool CompositingIOSurfaceMac::HasBeenPoisoned() const {
307 return offscreen_context_->HasBeenPoisoned();
310 GLenum CompositingIOSurfaceMac::GetAndSaveGLError() {
311 GLenum gl_error = glGetError();
312 if (gl_error_ == GL_NO_ERROR)
313 gl_error_ = gl_error;
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();
325 void CompositingIOSurfaceMac::EvictionMarkEvicted() {
326 if (eviction_queue_iterator_ == eviction_queue_.Get().end())
328 eviction_queue_.Get().erase(eviction_queue_iterator_);
329 eviction_queue_iterator_ = eviction_queue_.Get().end();
330 eviction_has_been_drawn_since_updated_ = false;
334 void CompositingIOSurfaceMac::EvictionScheduleDoEvict() {
335 if (eviction_scheduled_)
337 if (eviction_queue_.Get().size() <= kMaximumUnevictedSurfaces)
340 eviction_scheduled_ = true;
341 base::MessageLoop::current()->PostTask(
343 base::Bind(&CompositingIOSurfaceMac::EvictionDoEvict));
347 void CompositingIOSurfaceMac::EvictionDoEvict() {
348 eviction_scheduled_ = false;
349 // Walk the list of allocated surfaces from least recently used to most
351 for (EvictionQueue::iterator it = eviction_queue_.Get().begin();
352 it != eviction_queue_.Get().end();) {
353 CompositingIOSurfaceMac* surface = *it;
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)
361 // Don't evict anything that has not yet been drawn.
362 if (!surface->eviction_has_been_drawn_since_updated_)
365 // Evict the surface.
366 surface->UnrefIOSurface();
371 base::LazyInstance<CompositingIOSurfaceMac::EvictionQueue>
372 CompositingIOSurfaceMac::eviction_queue_;
375 bool CompositingIOSurfaceMac::eviction_scheduled_ = false;
377 } // namespace content