Upstream version 7.36.149.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/CGLRenderers.h>
8 #include <OpenGL/OpenGL.h>
9
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/debug/trace_event.h"
13 #include "base/logging.h"
14 #include "base/mac/mac_util.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/threading/platform_thread.h"
17 #include "content/browser/gpu/gpu_data_manager_impl.h"
18 #include "content/browser/renderer_host/compositing_iosurface_context_mac.h"
19 #include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
20 #include "content/browser/renderer_host/compositing_iosurface_transformer_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 #include "ui/gl/io_surface_support_mac.h"
32
33 #ifdef NDEBUG
34 #define CHECK_GL_ERROR()
35 #define CHECK_AND_SAVE_GL_ERROR()
36 #else
37 #define CHECK_GL_ERROR() do {                                           \
38     GLenum gl_error = glGetError();                                     \
39     LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error: " << gl_error; \
40   } while (0)
41 #define CHECK_AND_SAVE_GL_ERROR() do {                                  \
42     GLenum gl_error = GetAndSaveGLError();                              \
43     LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error: " << gl_error; \
44   } while (0)
45 #endif
46
47 namespace content {
48 namespace {
49
50 // How many times to test if asynchronous copy has completed.
51 // This value is chosen such that we allow at most 1 second to finish a copy.
52 const int kFinishCopyRetryCycles = 100;
53
54 // Time in milliseconds to allow asynchronous copy to finish.
55 // This value is shorter than 16ms such that copy can complete within a vsync.
56 const int kFinishCopyPollingPeriodMs = 10;
57
58 bool HasAppleFenceExtension() {
59   static bool initialized_has_fence = false;
60   static bool has_fence = false;
61
62   if (!initialized_has_fence) {
63     has_fence =
64         strstr(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)),
65                "GL_APPLE_fence") != NULL;
66     initialized_has_fence = true;
67   }
68   return has_fence;
69 }
70
71 bool HasPixelBufferObjectExtension() {
72   static bool initialized_has_pbo = false;
73   static bool has_pbo = false;
74
75   if (!initialized_has_pbo) {
76     has_pbo =
77         strstr(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)),
78                "GL_ARB_pixel_buffer_object") != NULL;
79     initialized_has_pbo = true;
80   }
81   return has_pbo;
82 }
83
84 // Helper function to reverse the argument order.  Also takes ownership of
85 // |bitmap_output| for the life of the binding.
86 void ReverseArgumentOrder(
87     const base::Callback<void(bool, const SkBitmap&)>& callback,
88     scoped_ptr<SkBitmap> bitmap_output, bool success) {
89   callback.Run(success, *bitmap_output);
90 }
91
92 // Called during an async GPU readback with a pointer to the pixel buffer.  In
93 // the snapshot path, we just memcpy the data into our output bitmap since the
94 // width, height, and stride should all be equal.
95 bool MapBufferToSkBitmap(const SkBitmap* output, const void* buf, int ignored) {
96   TRACE_EVENT0("browser", "MapBufferToSkBitmap");
97
98   if (buf) {
99     SkAutoLockPixels output_lock(*output);
100     memcpy(output->getPixels(), buf, output->getSize());
101   }
102   return buf != NULL;
103 }
104
105 // Copies tightly-packed scanlines from |buf| to |region_in_frame| in the given
106 // |target| VideoFrame's |plane|.  Assumption: |buf|'s width is
107 // |region_in_frame.width()| and its stride is always in 4-byte alignment.
108 //
109 // TODO(miu): Refactor by moving this function into media/video_util.
110 // http://crbug.com/219779
111 bool MapBufferToVideoFrame(
112     const scoped_refptr<media::VideoFrame>& target,
113     const gfx::Rect& region_in_frame,
114     const void* buf,
115     int plane) {
116   COMPILE_ASSERT(media::VideoFrame::kYPlane == 0, VideoFrame_kYPlane_mismatch);
117   COMPILE_ASSERT(media::VideoFrame::kUPlane == 1, VideoFrame_kUPlane_mismatch);
118   COMPILE_ASSERT(media::VideoFrame::kVPlane == 2, VideoFrame_kVPlane_mismatch);
119
120   TRACE_EVENT1("browser", "MapBufferToVideoFrame", "plane", plane);
121
122   // Apply black-out in the regions surrounding the view area (for
123   // letterboxing/pillarboxing).  Only do this once, since this is performed on
124   // all planes in the VideoFrame here.
125   if (plane == 0)
126     media::LetterboxYUV(target.get(), region_in_frame);
127
128   if (buf) {
129     int packed_width = region_in_frame.width();
130     int packed_height = region_in_frame.height();
131     // For planes 1 and 2, the width and height are 1/2 size (rounded up).
132     if (plane > 0) {
133       packed_width = (packed_width + 1) / 2;
134       packed_height = (packed_height + 1) / 2;
135     }
136     const uint8* src = reinterpret_cast<const uint8*>(buf);
137     const int src_stride = (packed_width % 4 == 0 ?
138                                 packed_width :
139                                 (packed_width + 4 - (packed_width % 4)));
140     const uint8* const src_end = src + packed_height * src_stride;
141
142     // Calculate starting offset and stride into the destination buffer.
143     const int dst_stride = target->stride(plane);
144     uint8* dst = target->data(plane);
145     if (plane == 0)
146       dst += (region_in_frame.y() * dst_stride) + region_in_frame.x();
147     else
148       dst += (region_in_frame.y() / 2 * dst_stride) + (region_in_frame.x() / 2);
149
150     // Copy each row, accounting for strides in the source and destination.
151     for (; src < src_end; src += src_stride, dst += dst_stride)
152       memcpy(dst, src, packed_width);
153   }
154   return buf != NULL;
155 }
156
157 }  // namespace
158
159 CompositingIOSurfaceMac::CopyContext::CopyContext(
160     const scoped_refptr<CompositingIOSurfaceContext>& context)
161   : transformer(new CompositingIOSurfaceTransformer(
162         GL_TEXTURE_RECTANGLE_ARB, true, context->shader_program_cache())),
163     output_readback_format(GL_BGRA),
164     num_outputs(0),
165     fence(0),
166     cycles_elapsed(0) {
167   memset(output_textures, 0, sizeof(output_textures));
168   memset(frame_buffers, 0, sizeof(frame_buffers));
169   memset(pixel_buffers, 0, sizeof(pixel_buffers));
170 }
171
172 CompositingIOSurfaceMac::CopyContext::~CopyContext() {
173   DCHECK_EQ(frame_buffers[0], 0u) << "Failed to call ReleaseCachedGLObjects().";
174 }
175
176 void CompositingIOSurfaceMac::CopyContext::ReleaseCachedGLObjects() {
177   // No outstanding callbacks should be pending.
178   DCHECK(map_buffer_callback.is_null());
179   DCHECK(done_callback.is_null());
180
181   // For an asynchronous read-back, there are more objects to delete:
182   if (fence) {
183     glDeleteBuffers(arraysize(pixel_buffers), pixel_buffers); CHECK_GL_ERROR();
184     memset(pixel_buffers, 0, sizeof(pixel_buffers));
185     glDeleteFencesAPPLE(1, &fence); CHECK_GL_ERROR();
186     fence = 0;
187   }
188
189   glDeleteFramebuffersEXT(arraysize(frame_buffers), frame_buffers);
190   CHECK_GL_ERROR();
191   memset(frame_buffers, 0, sizeof(frame_buffers));
192
193   // Note: |output_textures| are owned by the transformer.
194   if (transformer)
195     transformer->ReleaseCachedGLObjects();
196 }
197
198 void CompositingIOSurfaceMac::CopyContext::PrepareReadbackFramebuffers() {
199   for (int i = 0; i < num_outputs; ++i) {
200     if (!frame_buffers[i]) {
201       glGenFramebuffersEXT(1, &frame_buffers[i]); CHECK_GL_ERROR();
202     }
203   }
204 }
205
206 void CompositingIOSurfaceMac::CopyContext::PrepareForAsynchronousReadback() {
207   PrepareReadbackFramebuffers();
208   if (!fence) {
209     glGenFencesAPPLE(1, &fence); CHECK_GL_ERROR();
210   }
211   for (int i = 0; i < num_outputs; ++i) {
212     if (!pixel_buffers[i]) {
213       glGenBuffersARB(1, &pixel_buffers[i]); CHECK_GL_ERROR();
214     }
215   }
216 }
217
218
219 // static
220 CompositingIOSurfaceMac* CompositingIOSurfaceMac::Create() {
221   IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize();
222   if (!io_surface_support) {
223     LOG(ERROR) << "No IOSurface support";
224     return NULL;
225   }
226
227   scoped_refptr<CompositingIOSurfaceContext> offscreen_context =
228       CompositingIOSurfaceContext::Get(
229           CompositingIOSurfaceContext::kOffscreenContextWindowNumber);
230   if (!offscreen_context) {
231     LOG(ERROR) << "Failed to create context for offscreen operations";
232     return NULL;
233   }
234
235   return new CompositingIOSurfaceMac(io_surface_support,
236                                      offscreen_context);
237 }
238
239 CompositingIOSurfaceMac::CompositingIOSurfaceMac(
240     IOSurfaceSupport* io_surface_support,
241     const scoped_refptr<CompositingIOSurfaceContext>& offscreen_context)
242     : io_surface_support_(io_surface_support),
243       offscreen_context_(offscreen_context),
244       io_surface_handle_(0),
245       scale_factor_(1.f),
246       texture_(0),
247       finish_copy_timer_(
248           FROM_HERE,
249           base::TimeDelta::FromMilliseconds(kFinishCopyPollingPeriodMs),
250           base::Bind(&CompositingIOSurfaceMac::CheckIfAllCopiesAreFinished,
251                      base::Unretained(this),
252                      false),
253           true),
254       gl_error_(GL_NO_ERROR),
255       eviction_queue_iterator_(eviction_queue_.Get().end()),
256       eviction_has_been_drawn_since_updated_(false) {
257   CHECK(offscreen_context_);
258 }
259
260 CompositingIOSurfaceMac::~CompositingIOSurfaceMac() {
261   FailAllCopies();
262   {
263     gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
264         offscreen_context_->cgl_context());
265     DestroyAllCopyContextsWithinContext();
266     UnrefIOSurfaceWithContextCurrent();
267   }
268   offscreen_context_ = NULL;
269   DCHECK(eviction_queue_iterator_ == eviction_queue_.Get().end());
270 }
271
272 bool CompositingIOSurfaceMac::SetIOSurfaceWithContextCurrent(
273     scoped_refptr<CompositingIOSurfaceContext> current_context,
274     uint64 io_surface_handle,
275     const gfx::Size& size,
276     float scale_factor) {
277   bool result = MapIOSurfaceToTextureWithContextCurrent(
278       current_context, size, scale_factor, io_surface_handle);
279   EvictionMarkUpdated();
280   return result;
281 }
282
283 int CompositingIOSurfaceMac::GetRendererID() {
284   GLint current_renderer_id = -1;
285   if (CGLGetParameter(offscreen_context_->cgl_context(),
286                       kCGLCPCurrentRendererID,
287                       &current_renderer_id) == kCGLNoError)
288     return current_renderer_id & kCGLRendererIDMatchingMask;
289   return -1;
290 }
291
292 bool CompositingIOSurfaceMac::DrawIOSurface(
293     scoped_refptr<CompositingIOSurfaceContext> drawing_context,
294     const gfx::Rect& window_rect,
295     float window_scale_factor,
296     bool flush_drawable) {
297   DCHECK_EQ(CGLGetCurrentContext(), drawing_context->cgl_context());
298
299   bool has_io_surface = HasIOSurface();
300   TRACE_EVENT1("browser", "CompositingIOSurfaceMac::DrawIOSurface",
301                "has_io_surface", has_io_surface);
302
303   gfx::Rect pixel_window_rect =
304       ToNearestRect(gfx::ScaleRect(window_rect, window_scale_factor));
305   glViewport(
306       pixel_window_rect.x(), pixel_window_rect.y(),
307       pixel_window_rect.width(), pixel_window_rect.height());
308
309   SurfaceQuad quad;
310   quad.set_size(dip_io_surface_size_, pixel_io_surface_size_);
311
312   glMatrixMode(GL_PROJECTION);
313   glLoadIdentity();
314
315   // Note that the projection keeps things in view units, so the use of
316   // window_rect / dip_io_surface_size_ (as opposed to the pixel_ variants)
317   // below is correct.
318   glOrtho(0, window_rect.width(), window_rect.height(), 0, -1, 1);
319   glMatrixMode(GL_MODELVIEW);
320   glLoadIdentity();
321
322   glDisable(GL_DEPTH_TEST);
323   glDisable(GL_BLEND);
324
325   if (has_io_surface) {
326     drawing_context->shader_program_cache()->UseBlitProgram();
327     glActiveTexture(GL_TEXTURE0);
328     glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_);
329
330     DrawQuad(quad);
331
332     glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); CHECK_AND_SAVE_GL_ERROR();
333
334     // Fill the resize gutters with white.
335     if (window_rect.width() > dip_io_surface_size_.width() ||
336         window_rect.height() > dip_io_surface_size_.height()) {
337       drawing_context->shader_program_cache()->UseSolidWhiteProgram();
338       SurfaceQuad filler_quad;
339       if (window_rect.width() > dip_io_surface_size_.width()) {
340         // Draw right-side gutter down to the bottom of the window.
341         filler_quad.set_rect(dip_io_surface_size_.width(), 0.0f,
342                              window_rect.width(), window_rect.height());
343         DrawQuad(filler_quad);
344       }
345       if (window_rect.height() > dip_io_surface_size_.height()) {
346         // Draw bottom gutter to the width of the IOSurface.
347         filler_quad.set_rect(
348             0.0f, dip_io_surface_size_.height(),
349             dip_io_surface_size_.width(), window_rect.height());
350         DrawQuad(filler_quad);
351       }
352     }
353
354     // Workaround for issue 158469. Issue a dummy draw call with texture_ not
355     // bound to blit_rgb_sampler_location_, in order to shake all references
356     // to the IOSurface out of the driver.
357     glBegin(GL_TRIANGLES);
358     glEnd();
359
360     glUseProgram(0); CHECK_AND_SAVE_GL_ERROR();
361   } else {
362     // Should match the clear color of RenderWidgetHostViewMac.
363     glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
364     glClear(GL_COLOR_BUFFER_BIT);
365   }
366
367   bool workaround_needed =
368       GpuDataManagerImpl::GetInstance()->IsDriverBugWorkaroundActive(
369           gpu::FORCE_GL_FINISH_AFTER_COMPOSITING);
370   // Note that this is not necessary when not flushing the drawable in Mavericks
371   // or later if we are in one of the two following situations:
372   // - we are drawing an underlay, and we will call glFinish() when drawing
373   //   the overlay.
374   // - we are using CoreAnimation, where this bug does not manifest.
375   if (workaround_needed && !flush_drawable && base::mac::IsOSMavericksOrLater())
376     workaround_needed = false;
377
378   if (workaround_needed) {
379     TRACE_EVENT0("gpu", "glFinish");
380     glFinish();
381   }
382
383   bool result = true;
384   if (flush_drawable) {
385     TRACE_EVENT0("gpu", "flushBuffer");
386     [drawing_context->nsgl_context() flushBuffer];
387   }
388
389   // Check if any of the drawing calls result in an error.
390   GetAndSaveGLError();
391   if (gl_error_ != GL_NO_ERROR) {
392     LOG(ERROR) << "GL error in DrawIOSurface: " << gl_error_;
393     result = false;
394     // If there was an error, clear the screen to a light grey to avoid
395     // rendering artifacts. If we're in a really bad way, this too may
396     // generate an error. Clear the GL error afterwards just in case.
397     glClearColor(0.8, 0.8, 0.8, 1.0);
398     glClear(GL_COLOR_BUFFER_BIT);
399     glGetError();
400   }
401
402   eviction_has_been_drawn_since_updated_ = true;
403   return result;
404 }
405
406 void CompositingIOSurfaceMac::CopyTo(
407       const gfx::Rect& src_pixel_subrect,
408       const gfx::Size& dst_pixel_size,
409       const base::Callback<void(bool, const SkBitmap&)>& callback) {
410   scoped_ptr<SkBitmap> output(new SkBitmap());
411   output->setConfig(SkBitmap::kARGB_8888_Config,
412                     dst_pixel_size.width(),
413                     dst_pixel_size.height(),
414                     0,
415                     kOpaque_SkAlphaType);
416
417   if (!output->allocPixels()) {
418     DLOG(ERROR) << "Failed to allocate SkBitmap pixels!";
419     callback.Run(false, *output);
420     return;
421   }
422   DCHECK_EQ(output->rowBytesAsPixels(), dst_pixel_size.width())
423       << "Stride is required to be equal to width for GPU readback.";
424
425   base::Closure copy_done_callback;
426   {
427     gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
428         offscreen_context_->cgl_context());
429     copy_done_callback = CopyToSelectedOutputWithinContext(
430         src_pixel_subrect, gfx::Rect(dst_pixel_size), false,
431         output.get(), NULL,
432         base::Bind(&ReverseArgumentOrder, callback, base::Passed(&output)));
433   }
434   if (!copy_done_callback.is_null())
435     copy_done_callback.Run();
436 }
437
438 void CompositingIOSurfaceMac::CopyToVideoFrame(
439     const gfx::Rect& src_pixel_subrect,
440     const scoped_refptr<media::VideoFrame>& target,
441     const base::Callback<void(bool)>& callback) {
442   base::Closure copy_done_callback;
443   {
444     gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
445         offscreen_context_->cgl_context());
446     copy_done_callback = CopyToVideoFrameWithinContext(
447         src_pixel_subrect, false, target, callback);
448   }
449   if (!copy_done_callback.is_null())
450     copy_done_callback.Run();
451 }
452
453 base::Closure CompositingIOSurfaceMac::CopyToVideoFrameWithinContext(
454     const gfx::Rect& src_pixel_subrect,
455     bool called_within_draw,
456     const scoped_refptr<media::VideoFrame>& target,
457     const base::Callback<void(bool)>& callback) {
458   gfx::Rect region_in_frame = media::ComputeLetterboxRegion(
459       gfx::Rect(target->coded_size()), src_pixel_subrect.size());
460   // Make coordinates and sizes even because we letterbox in YUV space right
461   // now (see CopyRGBToVideoFrame). They need to be even for the UV samples to
462   // line up correctly.
463   region_in_frame = gfx::Rect(region_in_frame.x() & ~1,
464                               region_in_frame.y() & ~1,
465                               region_in_frame.width() & ~1,
466                               region_in_frame.height() & ~1);
467   DCHECK_LE(region_in_frame.right(), target->coded_size().width());
468   DCHECK_LE(region_in_frame.bottom(), target->coded_size().height());
469
470   return CopyToSelectedOutputWithinContext(
471       src_pixel_subrect, region_in_frame, called_within_draw,
472       NULL, target, callback);
473 }
474
475 bool CompositingIOSurfaceMac::MapIOSurfaceToTextureWithContextCurrent(
476     const scoped_refptr<CompositingIOSurfaceContext>& current_context,
477     const gfx::Size pixel_size,
478     float scale_factor,
479     uint64 io_surface_handle) {
480   TRACE_EVENT0("browser", "CompositingIOSurfaceMac::MapIOSurfaceToTexture");
481
482   if (!io_surface_ || io_surface_handle != io_surface_handle_)
483     UnrefIOSurfaceWithContextCurrent();
484
485   pixel_io_surface_size_ = pixel_size;
486   scale_factor_ = scale_factor;
487   dip_io_surface_size_ = gfx::ToFlooredSize(
488       gfx::ScaleSize(pixel_io_surface_size_, 1.0 / scale_factor_));
489
490   // Early-out if the IOSurface has not changed. Note that because IOSurface
491   // sizes are rounded, the same IOSurface may have two different sizes
492   // associated with it.
493   if (io_surface_ && io_surface_handle == io_surface_handle_)
494     return true;
495
496   io_surface_.reset(io_surface_support_->IOSurfaceLookup(
497       static_cast<uint32>(io_surface_handle)));
498   // Can fail if IOSurface with that ID was already released by the gpu
499   // process.
500   if (!io_surface_) {
501     UnrefIOSurfaceWithContextCurrent();
502     return false;
503   }
504
505   io_surface_handle_ = io_surface_handle;
506
507   // Actual IOSurface size is rounded up to reduce reallocations during window
508   // resize. Get the actual size to properly map the texture.
509   gfx::Size rounded_size(
510       io_surface_support_->IOSurfaceGetWidth(io_surface_),
511       io_surface_support_->IOSurfaceGetHeight(io_surface_));
512
513   glGenTextures(1, &texture_);
514   glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_);
515   glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
516   glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
517   CHECK_AND_SAVE_GL_ERROR();
518   GLuint plane = 0;
519   CGLError cgl_error = io_surface_support_->CGLTexImageIOSurface2D(
520       current_context->cgl_context(),
521       GL_TEXTURE_RECTANGLE_ARB,
522       GL_RGBA,
523       rounded_size.width(),
524       rounded_size.height(),
525       GL_BGRA,
526       GL_UNSIGNED_INT_8_8_8_8_REV,
527       io_surface_.get(),
528       plane);
529   glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
530   if (cgl_error != kCGLNoError) {
531     LOG(ERROR) << "CGLTexImageIOSurface2D: " << cgl_error;
532     UnrefIOSurfaceWithContextCurrent();
533     return false;
534   }
535   GetAndSaveGLError();
536   if (gl_error_ != GL_NO_ERROR) {
537     LOG(ERROR) << "GL error in MapIOSurfaceToTexture: " << gl_error_;
538     UnrefIOSurfaceWithContextCurrent();
539     return false;
540   }
541   return true;
542 }
543
544 void CompositingIOSurfaceMac::UnrefIOSurface() {
545   gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
546       offscreen_context_->cgl_context());
547   UnrefIOSurfaceWithContextCurrent();
548 }
549
550 void CompositingIOSurfaceMac::DrawQuad(const SurfaceQuad& quad) {
551   TRACE_EVENT0("gpu", "CompositingIOSurfaceMac::DrawQuad");
552
553   glEnableClientState(GL_VERTEX_ARRAY); CHECK_AND_SAVE_GL_ERROR();
554   glEnableClientState(GL_TEXTURE_COORD_ARRAY); CHECK_AND_SAVE_GL_ERROR();
555
556   glVertexPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].x_);
557   glTexCoordPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].tx_);
558   glDrawArrays(GL_QUADS, 0, 4); CHECK_AND_SAVE_GL_ERROR();
559
560   glDisableClientState(GL_VERTEX_ARRAY);
561   glDisableClientState(GL_TEXTURE_COORD_ARRAY);
562 }
563
564 void CompositingIOSurfaceMac::UnrefIOSurfaceWithContextCurrent() {
565   if (texture_) {
566     glDeleteTextures(1, &texture_);
567     texture_ = 0;
568   }
569   pixel_io_surface_size_ = gfx::Size();
570   scale_factor_ = 1;
571   dip_io_surface_size_ = gfx::Size();
572   io_surface_.reset();
573
574   // Forget the ID, because even if it is still around when we want to use it
575   // again, OSX may have reused the same ID for a new tab and we don't want to
576   // blit random tab contents.
577   io_surface_handle_ = 0;
578
579   EvictionMarkEvicted();
580 }
581
582 bool CompositingIOSurfaceMac::IsAsynchronousReadbackSupported() {
583   if (!HasAppleFenceExtension() && HasPixelBufferObjectExtension())
584     return false;
585   if (GpuDataManagerImpl::GetInstance()->IsDriverBugWorkaroundActive(
586           gpu::DISABLE_ASYNC_READPIXELS)) {
587     return false;
588   }
589   return true;
590 }
591
592 bool CompositingIOSurfaceMac::HasBeenPoisoned() const {
593   return offscreen_context_->HasBeenPoisoned();
594 }
595
596 base::Closure CompositingIOSurfaceMac::CopyToSelectedOutputWithinContext(
597     const gfx::Rect& src_pixel_subrect,
598     const gfx::Rect& dst_pixel_rect,
599     bool called_within_draw,
600     const SkBitmap* bitmap_output,
601     const scoped_refptr<media::VideoFrame>& video_frame_output,
602     const base::Callback<void(bool)>& done_callback) {
603   DCHECK_NE(bitmap_output != NULL, video_frame_output.get() != NULL);
604   DCHECK(!done_callback.is_null());
605
606   // SWIZZLE_RGBA_FOR_ASYNC_READPIXELS workaround: Fall-back to synchronous
607   // readback for SkBitmap output since the Blit shader program doesn't support
608   // switchable output formats.
609   const bool require_sync_copy_for_workaround = bitmap_output &&
610       offscreen_context_->shader_program_cache()->rgb_to_yv12_output_format() ==
611           GL_RGBA;
612   const bool async_copy = !require_sync_copy_for_workaround &&
613       IsAsynchronousReadbackSupported();
614   TRACE_EVENT2(
615       "browser", "CompositingIOSurfaceMac::CopyToSelectedOutputWithinContext",
616       "output", bitmap_output ? "SkBitmap (ARGB)" : "VideoFrame (YV12)",
617       "async_readback", async_copy);
618
619   const gfx::Rect src_rect = IntersectWithIOSurface(src_pixel_subrect);
620   if (src_rect.IsEmpty() || dst_pixel_rect.IsEmpty())
621     return base::Bind(done_callback, false);
622
623   CopyContext* copy_context;
624   if (copy_context_pool_.empty()) {
625     // Limit the maximum number of simultaneous copies to two.  Rationale:
626     // Really, only one should ever be in-progress at a time, as we should
627     // depend on the speed of the hardware to rate-limit the copying naturally.
628     // In the asynchronous read-back case, the one currently in-flight copy is
629     // highly likely to have finished by this point (i.e., it's just waiting for
630     // us to make a glMapBuffer() call).  Therefore, we allow a second copy to
631     // be started here.
632     if (copy_requests_.size() >= 2)
633       return base::Bind(done_callback, false);
634     copy_context = new CopyContext(offscreen_context_);
635   } else {
636     copy_context = copy_context_pool_.back();
637     copy_context_pool_.pop_back();
638   }
639
640   if (!HasIOSurface())
641     return base::Bind(done_callback, false);
642
643   // Send transform commands to the GPU.
644   copy_context->num_outputs = 0;
645   if (bitmap_output) {
646     if (copy_context->transformer->ResizeBilinear(
647             texture_, src_rect, dst_pixel_rect.size(),
648             &copy_context->output_textures[0])) {
649       copy_context->output_readback_format = GL_BGRA;
650       copy_context->num_outputs = 1;
651       copy_context->output_texture_sizes[0] = dst_pixel_rect.size();
652     }
653   } else {
654     if (copy_context->transformer->TransformRGBToYV12(
655             texture_, src_rect, dst_pixel_rect.size(),
656             &copy_context->output_textures[0],
657             &copy_context->output_textures[1],
658             &copy_context->output_textures[2],
659             &copy_context->output_texture_sizes[0],
660             &copy_context->output_texture_sizes[1])) {
661       copy_context->output_readback_format =
662           offscreen_context_->shader_program_cache()->
663               rgb_to_yv12_output_format();
664       copy_context->num_outputs = 3;
665       copy_context->output_texture_sizes[2] =
666           copy_context->output_texture_sizes[1];
667     }
668   }
669   if (!copy_context->num_outputs)
670     return base::Bind(done_callback, false);
671
672   // In the asynchronous case, issue commands to the GPU and return a null
673   // closure here.  In the synchronous case, perform a blocking readback and
674   // return a callback to be run outside the CGL context to indicate success.
675   if (async_copy) {
676     copy_context->done_callback = done_callback;
677     AsynchronousReadbackForCopy(
678         dst_pixel_rect, called_within_draw, copy_context, bitmap_output,
679         video_frame_output);
680     copy_requests_.push_back(copy_context);
681     if (!finish_copy_timer_.IsRunning())
682       finish_copy_timer_.Reset();
683     return base::Closure();
684   } else {
685     const bool success = SynchronousReadbackForCopy(
686         dst_pixel_rect, copy_context, bitmap_output, video_frame_output);
687     return base::Bind(done_callback, success);
688   }
689 }
690
691 void CompositingIOSurfaceMac::AsynchronousReadbackForCopy(
692     const gfx::Rect& dst_pixel_rect,
693     bool called_within_draw,
694     CopyContext* copy_context,
695     const SkBitmap* bitmap_output,
696     const scoped_refptr<media::VideoFrame>& video_frame_output) {
697   copy_context->PrepareForAsynchronousReadback();
698
699   // Copy the textures to their corresponding PBO.
700   for (int i = 0; i < copy_context->num_outputs; ++i) {
701     TRACE_EVENT1(
702         "browser", "CompositingIOSurfaceMac::AsynchronousReadbackForCopy",
703         "plane", i);
704
705     // Attach the output texture to the FBO.
706     glBindFramebufferEXT(
707         GL_READ_FRAMEBUFFER_EXT, copy_context->frame_buffers[i]);
708     glFramebufferTexture2DEXT(
709         GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
710         GL_TEXTURE_RECTANGLE_ARB, copy_context->output_textures[i], 0);
711     DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) ==
712                GL_FRAMEBUFFER_COMPLETE_EXT);
713
714     // Create a PBO and issue an asynchronous read-back.
715     glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context->pixel_buffers[i]);
716     CHECK_AND_SAVE_GL_ERROR();
717     glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB,
718                     copy_context->output_texture_sizes[i].GetArea() * 4,
719                     NULL, GL_STREAM_READ_ARB);
720     CHECK_AND_SAVE_GL_ERROR();
721     glReadPixels(0, 0,
722                  copy_context->output_texture_sizes[i].width(),
723                  copy_context->output_texture_sizes[i].height(),
724                  copy_context->output_readback_format,
725                  GL_UNSIGNED_INT_8_8_8_8_REV, 0);
726     CHECK_AND_SAVE_GL_ERROR();
727   }
728
729   glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_AND_SAVE_GL_ERROR();
730   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); CHECK_AND_SAVE_GL_ERROR();
731
732   glSetFenceAPPLE(copy_context->fence); CHECK_GL_ERROR();
733   copy_context->cycles_elapsed = 0;
734
735   // When this asynchronous copy happens in a draw operaton there is no need
736   // to explicitly flush because there will be a swap buffer and this flush
737   // hurts performance.
738   if (!called_within_draw) {
739     glFlush(); CHECK_AND_SAVE_GL_ERROR();
740   }
741
742   copy_context->map_buffer_callback = bitmap_output ?
743       base::Bind(&MapBufferToSkBitmap, bitmap_output) :
744       base::Bind(&MapBufferToVideoFrame, video_frame_output, dst_pixel_rect);
745 }
746
747 void CompositingIOSurfaceMac::CheckIfAllCopiesAreFinished(
748     bool block_until_finished) {
749   if (copy_requests_.empty())
750     return;
751
752   std::vector<base::Closure> done_callbacks;
753   {
754     gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
755         offscreen_context_->cgl_context());
756     CheckIfAllCopiesAreFinishedWithinContext(
757         block_until_finished, &done_callbacks);
758   }
759   for (size_t i = 0; i < done_callbacks.size(); ++i)
760     done_callbacks[i].Run();
761 }
762
763 void CompositingIOSurfaceMac::CheckIfAllCopiesAreFinishedWithinContext(
764     bool block_until_finished,
765     std::vector<base::Closure>* done_callbacks) {
766   while (!copy_requests_.empty()) {
767     CopyContext* const copy_context = copy_requests_.front();
768
769     if (copy_context->fence && !glTestFenceAPPLE(copy_context->fence)) {
770       CHECK_AND_SAVE_GL_ERROR();
771       // Doing a glFinishFenceAPPLE can cause transparent window flashes when
772       // switching tabs, so only do it when required.
773       if (block_until_finished) {
774         glFinishFenceAPPLE(copy_context->fence);
775         CHECK_AND_SAVE_GL_ERROR();
776       } else if (copy_context->cycles_elapsed < kFinishCopyRetryCycles) {
777         ++copy_context->cycles_elapsed;
778         // This copy has not completed there is no need to test subsequent
779         // requests.
780         break;
781       }
782     }
783     CHECK_AND_SAVE_GL_ERROR();
784
785     bool success = true;
786     for (int i = 0; success && i < copy_context->num_outputs; ++i) {
787       TRACE_EVENT1(
788         "browser",
789         "CompositingIOSurfaceMac::CheckIfAllCopiesAreFinishedWithinContext",
790         "plane", i);
791
792       glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context->pixel_buffers[i]);
793       CHECK_AND_SAVE_GL_ERROR();
794
795       void* buf = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB);
796       CHECK_AND_SAVE_GL_ERROR();
797       success &= copy_context->map_buffer_callback.Run(buf, i);
798       glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); CHECK_AND_SAVE_GL_ERROR();
799     }
800     copy_context->map_buffer_callback.Reset();
801     glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_AND_SAVE_GL_ERROR();
802
803     copy_requests_.pop_front();
804     done_callbacks->push_back(base::Bind(copy_context->done_callback, success));
805     copy_context->done_callback.Reset();
806     copy_context_pool_.push_back(copy_context);
807   }
808   if (copy_requests_.empty())
809     finish_copy_timer_.Stop();
810
811   CHECK(copy_requests_.empty() || !block_until_finished);
812 }
813
814 bool CompositingIOSurfaceMac::SynchronousReadbackForCopy(
815     const gfx::Rect& dst_pixel_rect,
816     CopyContext* copy_context,
817     const SkBitmap* bitmap_output,
818     const scoped_refptr<media::VideoFrame>& video_frame_output) {
819   bool success = true;
820   copy_context->PrepareReadbackFramebuffers();
821   for (int i = 0; i < copy_context->num_outputs; ++i) {
822     TRACE_EVENT1(
823         "browser", "CompositingIOSurfaceMac::SynchronousReadbackForCopy",
824         "plane", i);
825
826     // Attach the output texture to the FBO.
827     glBindFramebufferEXT(
828         GL_READ_FRAMEBUFFER_EXT, copy_context->frame_buffers[i]);
829     glFramebufferTexture2DEXT(
830         GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
831         GL_TEXTURE_RECTANGLE_ARB, copy_context->output_textures[i], 0);
832     DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) ==
833                GL_FRAMEBUFFER_COMPLETE_EXT);
834
835     // Blocking read-back of pixels from textures.
836     void* buf;
837     // When data must be transferred into a VideoFrame one scanline at a time,
838     // it is necessary to allocate a separate buffer for glReadPixels() that can
839     // be populated one-shot.
840     //
841     // TODO(miu): Don't keep allocating/deleting this buffer for every frame.
842     // Keep it cached, allocated on first use.
843     scoped_ptr<uint32[]> temp_readback_buffer;
844     if (bitmap_output) {
845       // The entire SkBitmap is populated, never a region within.  So, read the
846       // texture directly into the bitmap's pixel memory.
847       buf = bitmap_output->getPixels();
848     } else {
849       // Optimization: If the VideoFrame is letterboxed (not pillarboxed), and
850       // its stride is equal to the stride of the data being read back, then
851       // readback directly into the VideoFrame's buffer to save a round of
852       // memcpy'ing.
853       //
854       // TODO(miu): Move these calculations into VideoFrame (need a CalcOffset()
855       // method).  http://crbug.com/219779
856       const int src_stride = copy_context->output_texture_sizes[i].width() * 4;
857       const int dst_stride = video_frame_output->stride(i);
858       if (src_stride == dst_stride && dst_pixel_rect.x() == 0) {
859         const int y_offset = dst_pixel_rect.y() / (i == 0 ? 1 : 2);
860         buf = video_frame_output->data(i) + y_offset * dst_stride;
861       } else {
862         // Create and readback into a temporary buffer because the data must be
863         // transferred to VideoFrame's pixel memory one scanline at a time.
864         temp_readback_buffer.reset(
865             new uint32[copy_context->output_texture_sizes[i].GetArea()]);
866         buf = temp_readback_buffer.get();
867       }
868     }
869     glReadPixels(0, 0,
870                  copy_context->output_texture_sizes[i].width(),
871                  copy_context->output_texture_sizes[i].height(),
872                  copy_context->output_readback_format,
873                  GL_UNSIGNED_INT_8_8_8_8_REV, buf);
874     CHECK_AND_SAVE_GL_ERROR();
875     if (video_frame_output.get()) {
876       if (!temp_readback_buffer) {
877         // Apply letterbox black-out around view region.
878         media::LetterboxYUV(video_frame_output.get(), dst_pixel_rect);
879       } else {
880         // Copy from temporary buffer and fully render the VideoFrame.
881         success &= MapBufferToVideoFrame(video_frame_output, dst_pixel_rect,
882                                          temp_readback_buffer.get(), i);
883       }
884     }
885   }
886
887   glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); CHECK_AND_SAVE_GL_ERROR();
888   copy_context_pool_.push_back(copy_context);
889   return success;
890 }
891
892 void CompositingIOSurfaceMac::FailAllCopies() {
893   for (size_t i = 0; i < copy_requests_.size(); ++i) {
894     copy_requests_[i]->map_buffer_callback.Reset();
895
896     base::Callback<void(bool)>& done_callback =
897         copy_requests_[i]->done_callback;
898     if (!done_callback.is_null()) {
899       done_callback.Run(false);
900       done_callback.Reset();
901     }
902   }
903 }
904
905 void CompositingIOSurfaceMac::DestroyAllCopyContextsWithinContext() {
906   // Move all in-flight copies, if any, back into the pool.  Then, destroy all
907   // the CopyContexts in the pool.
908   copy_context_pool_.insert(copy_context_pool_.end(),
909                             copy_requests_.begin(), copy_requests_.end());
910   copy_requests_.clear();
911   while (!copy_context_pool_.empty()) {
912     scoped_ptr<CopyContext> copy_context(copy_context_pool_.back());
913     copy_context_pool_.pop_back();
914     copy_context->ReleaseCachedGLObjects();
915   }
916 }
917
918 gfx::Rect CompositingIOSurfaceMac::IntersectWithIOSurface(
919     const gfx::Rect& rect) const {
920   return gfx::IntersectRects(rect,
921       gfx::ToEnclosingRect(gfx::Rect(pixel_io_surface_size_)));
922 }
923
924 GLenum CompositingIOSurfaceMac::GetAndSaveGLError() {
925   GLenum gl_error = glGetError();
926   if (gl_error_ == GL_NO_ERROR)
927     gl_error_ = gl_error;
928   return gl_error;
929 }
930
931 void CompositingIOSurfaceMac::EvictionMarkUpdated() {
932   EvictionMarkEvicted();
933   eviction_queue_.Get().push_back(this);
934   eviction_queue_iterator_ = --eviction_queue_.Get().end();
935   eviction_has_been_drawn_since_updated_ = false;
936   EvictionScheduleDoEvict();
937 }
938
939 void CompositingIOSurfaceMac::EvictionMarkEvicted() {
940   if (eviction_queue_iterator_ == eviction_queue_.Get().end())
941     return;
942   eviction_queue_.Get().erase(eviction_queue_iterator_);
943   eviction_queue_iterator_ = eviction_queue_.Get().end();
944   eviction_has_been_drawn_since_updated_ = false;
945 }
946
947 // static
948 void CompositingIOSurfaceMac::EvictionScheduleDoEvict() {
949   if (GetCoreAnimationStatus() == CORE_ANIMATION_DISABLED)
950     return;
951   if (eviction_scheduled_)
952     return;
953   if (eviction_queue_.Get().size() <= kMaximumUnevictedSurfaces)
954     return;
955
956   eviction_scheduled_ = true;
957   base::MessageLoop::current()->PostTask(
958       FROM_HERE,
959       base::Bind(&CompositingIOSurfaceMac::EvictionDoEvict));
960 }
961
962 // static
963 void CompositingIOSurfaceMac::EvictionDoEvict() {
964   eviction_scheduled_ = false;
965   // Walk the list of allocated surfaces from least recently used to most
966   // recently used.
967   for (EvictionQueue::iterator it = eviction_queue_.Get().begin();
968        it != eviction_queue_.Get().end();) {
969     CompositingIOSurfaceMac* surface = *it;
970     ++it;
971
972     // If the number of IOSurfaces allocated is less than the threshold,
973     // stop walking the list of surfaces.
974     if (eviction_queue_.Get().size() <= kMaximumUnevictedSurfaces)
975       break;
976
977     // Don't evict anything that has not yet been drawn.
978     if (!surface->eviction_has_been_drawn_since_updated_)
979       continue;
980
981     // Don't evict anything with pending copy requests.
982     if (!surface->copy_requests_.empty())
983       continue;
984
985     // Evict the surface.
986     surface->UnrefIOSurface();
987   }
988 }
989
990 // static
991 base::LazyInstance<CompositingIOSurfaceMac::EvictionQueue>
992     CompositingIOSurfaceMac::eviction_queue_;
993
994 // static
995 bool CompositingIOSurfaceMac::eviction_scheduled_ = false;
996
997 }  // namespace content