Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / content / browser / renderer_host / compositing_iosurface_mac.mm
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