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/CGLRenderers.h>
8 #include <OpenGL/OpenGL.h>
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/command_line.h"
13 #include "base/debug/trace_event.h"
14 #include "base/logging.h"
15 #include "base/mac/mac_util.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/threading/platform_thread.h"
18 #include "content/browser/gpu/gpu_data_manager_impl.h"
19 #include "content/browser/renderer_host/compositing_iosurface_context_mac.h"
20 #include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
21 #include "content/browser/renderer_host/compositing_iosurface_transformer_mac.h"
22 #include "content/browser/renderer_host/render_widget_host_impl.h"
23 #include "content/browser/renderer_host/render_widget_host_view_mac.h"
24 #include "content/common/content_constants_internal.h"
25 #include "gpu/command_buffer/service/gpu_switches.h"
26 #include "gpu/config/gpu_driver_bug_workaround_type.h"
27 #include "media/base/video_util.h"
28 #include "third_party/skia/include/core/SkBitmap.h"
29 #include "ui/gfx/rect.h"
30 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
31 #include "ui/gfx/size_conversions.h"
32 #include "ui/gl/gl_context.h"
33 #include "ui/gl/io_surface_support_mac.h"
36 #define CHECK_GL_ERROR()
37 #define CHECK_AND_SAVE_GL_ERROR()
39 #define CHECK_GL_ERROR() do { \
40 GLenum gl_error = glGetError(); \
41 LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error: " << gl_error; \
43 #define CHECK_AND_SAVE_GL_ERROR() do { \
44 GLenum gl_error = GetAndSaveGLError(); \
45 LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error: " << gl_error; \
52 // How many times to test if asynchronous copy has completed.
53 // This value is chosen such that we allow at most 1 second to finish a copy.
54 const int kFinishCopyRetryCycles = 100;
56 // Time in milliseconds to allow asynchronous copy to finish.
57 // This value is shorter than 16ms such that copy can complete within a vsync.
58 const int kFinishCopyPollingPeriodMs = 10;
60 bool HasAppleFenceExtension() {
61 static bool initialized_has_fence = false;
62 static bool has_fence = false;
64 if (!initialized_has_fence) {
66 strstr(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)),
67 "GL_APPLE_fence") != NULL;
68 initialized_has_fence = true;
73 bool HasPixelBufferObjectExtension() {
74 static bool initialized_has_pbo = false;
75 static bool has_pbo = false;
77 if (!initialized_has_pbo) {
79 strstr(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)),
80 "GL_ARB_pixel_buffer_object") != NULL;
81 initialized_has_pbo = true;
86 // Helper function to reverse the argument order. Also takes ownership of
87 // |bitmap_output| for the life of the binding.
88 void ReverseArgumentOrder(
89 const base::Callback<void(bool, const SkBitmap&)>& callback,
90 scoped_ptr<SkBitmap> bitmap_output, bool success) {
91 callback.Run(success, *bitmap_output);
94 // Called during an async GPU readback with a pointer to the pixel buffer. In
95 // the snapshot path, we just memcpy the data into our output bitmap since the
96 // width, height, and stride should all be equal.
97 bool MapBufferToSkBitmap(const SkBitmap* output, const void* buf, int ignored) {
98 TRACE_EVENT0("browser", "MapBufferToSkBitmap");
101 SkAutoLockPixels output_lock(*output);
102 memcpy(output->getPixels(), buf, output->getSize());
107 // Copies tightly-packed scanlines from |buf| to |region_in_frame| in the given
108 // |target| VideoFrame's |plane|. Assumption: |buf|'s width is
109 // |region_in_frame.width()| and its stride is always in 4-byte alignment.
111 // TODO(miu): Refactor by moving this function into media/video_util.
112 // http://crbug.com/219779
113 bool MapBufferToVideoFrame(
114 const scoped_refptr<media::VideoFrame>& target,
115 const gfx::Rect& region_in_frame,
118 COMPILE_ASSERT(media::VideoFrame::kYPlane == 0, VideoFrame_kYPlane_mismatch);
119 COMPILE_ASSERT(media::VideoFrame::kUPlane == 1, VideoFrame_kUPlane_mismatch);
120 COMPILE_ASSERT(media::VideoFrame::kVPlane == 2, VideoFrame_kVPlane_mismatch);
122 TRACE_EVENT1("browser", "MapBufferToVideoFrame", "plane", plane);
124 // Apply black-out in the regions surrounding the view area (for
125 // letterboxing/pillarboxing). Only do this once, since this is performed on
126 // all planes in the VideoFrame here.
128 media::LetterboxYUV(target.get(), region_in_frame);
131 int packed_width = region_in_frame.width();
132 int packed_height = region_in_frame.height();
133 // For planes 1 and 2, the width and height are 1/2 size (rounded up).
135 packed_width = (packed_width + 1) / 2;
136 packed_height = (packed_height + 1) / 2;
138 const uint8* src = reinterpret_cast<const uint8*>(buf);
139 const int src_stride = (packed_width % 4 == 0 ?
141 (packed_width + 4 - (packed_width % 4)));
142 const uint8* const src_end = src + packed_height * src_stride;
144 // Calculate starting offset and stride into the destination buffer.
145 const int dst_stride = target->stride(plane);
146 uint8* dst = target->data(plane);
148 dst += (region_in_frame.y() * dst_stride) + region_in_frame.x();
150 dst += (region_in_frame.y() / 2 * dst_stride) + (region_in_frame.x() / 2);
152 // Copy each row, accounting for strides in the source and destination.
153 for (; src < src_end; src += src_stride, dst += dst_stride)
154 memcpy(dst, src, packed_width);
161 CompositingIOSurfaceMac::CopyContext::CopyContext(
162 const scoped_refptr<CompositingIOSurfaceContext>& context)
163 : transformer(new CompositingIOSurfaceTransformer(
164 GL_TEXTURE_RECTANGLE_ARB, true, context->shader_program_cache())),
165 output_readback_format(GL_BGRA),
169 memset(output_textures, 0, sizeof(output_textures));
170 memset(frame_buffers, 0, sizeof(frame_buffers));
171 memset(pixel_buffers, 0, sizeof(pixel_buffers));
174 CompositingIOSurfaceMac::CopyContext::~CopyContext() {
175 DCHECK_EQ(frame_buffers[0], 0u) << "Failed to call ReleaseCachedGLObjects().";
178 void CompositingIOSurfaceMac::CopyContext::ReleaseCachedGLObjects() {
179 // No outstanding callbacks should be pending.
180 DCHECK(map_buffer_callback.is_null());
181 DCHECK(done_callback.is_null());
183 // For an asynchronous read-back, there are more objects to delete:
185 glDeleteBuffers(arraysize(pixel_buffers), pixel_buffers); CHECK_GL_ERROR();
186 memset(pixel_buffers, 0, sizeof(pixel_buffers));
187 glDeleteFencesAPPLE(1, &fence); CHECK_GL_ERROR();
191 glDeleteFramebuffersEXT(arraysize(frame_buffers), frame_buffers);
193 memset(frame_buffers, 0, sizeof(frame_buffers));
195 // Note: |output_textures| are owned by the transformer.
197 transformer->ReleaseCachedGLObjects();
200 void CompositingIOSurfaceMac::CopyContext::PrepareReadbackFramebuffers() {
201 for (int i = 0; i < num_outputs; ++i) {
202 if (!frame_buffers[i]) {
203 glGenFramebuffersEXT(1, &frame_buffers[i]); CHECK_GL_ERROR();
208 void CompositingIOSurfaceMac::CopyContext::PrepareForAsynchronousReadback() {
209 PrepareReadbackFramebuffers();
211 glGenFencesAPPLE(1, &fence); CHECK_GL_ERROR();
213 for (int i = 0; i < num_outputs; ++i) {
214 if (!pixel_buffers[i]) {
215 glGenBuffersARB(1, &pixel_buffers[i]); CHECK_GL_ERROR();
222 CompositingIOSurfaceMac* CompositingIOSurfaceMac::Create() {
223 IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize();
224 if (!io_surface_support) {
225 LOG(ERROR) << "No IOSurface support";
229 scoped_refptr<CompositingIOSurfaceContext> offscreen_context =
230 CompositingIOSurfaceContext::Get(
231 CompositingIOSurfaceContext::kOffscreenContextWindowNumber);
232 if (!offscreen_context) {
233 LOG(ERROR) << "Failed to create context for offscreen operations";
237 return new CompositingIOSurfaceMac(io_surface_support,
241 CompositingIOSurfaceMac::CompositingIOSurfaceMac(
242 IOSurfaceSupport* io_surface_support,
243 const scoped_refptr<CompositingIOSurfaceContext>& offscreen_context)
244 : io_surface_support_(io_surface_support),
245 offscreen_context_(offscreen_context),
246 io_surface_handle_(0),
251 base::TimeDelta::FromMilliseconds(kFinishCopyPollingPeriodMs),
252 base::Bind(&CompositingIOSurfaceMac::CheckIfAllCopiesAreFinished,
253 base::Unretained(this),
256 gl_error_(GL_NO_ERROR) {
257 CHECK(offscreen_context_);
260 CompositingIOSurfaceMac::~CompositingIOSurfaceMac() {
263 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
264 offscreen_context_->cgl_context());
265 DestroyAllCopyContextsWithinContext();
266 UnrefIOSurfaceWithContextCurrent();
268 offscreen_context_ = NULL;
271 bool CompositingIOSurfaceMac::SetIOSurfaceWithContextCurrent(
272 scoped_refptr<CompositingIOSurfaceContext> current_context,
273 uint64 io_surface_handle,
274 const gfx::Size& size,
275 float scale_factor) {
276 pixel_io_surface_size_ = size;
277 scale_factor_ = scale_factor;
278 dip_io_surface_size_ = gfx::ToFlooredSize(
279 gfx::ScaleSize(pixel_io_surface_size_, 1.0 / scale_factor_));
280 bool result = MapIOSurfaceToTextureWithContextCurrent(
281 current_context, io_surface_handle);
285 int CompositingIOSurfaceMac::GetRendererID() {
286 GLint current_renderer_id = -1;
287 if (CGLGetParameter(offscreen_context_->cgl_context(),
288 kCGLCPCurrentRendererID,
289 ¤t_renderer_id) == kCGLNoError)
290 return current_renderer_id & kCGLRendererIDMatchingMask;
294 bool CompositingIOSurfaceMac::DrawIOSurface(
295 scoped_refptr<CompositingIOSurfaceContext> drawing_context,
296 const gfx::Rect& window_rect,
297 float window_scale_factor,
298 bool flush_drawable) {
299 DCHECK_EQ(CGLGetCurrentContext(), drawing_context->cgl_context());
301 bool has_io_surface = HasIOSurface();
302 TRACE_EVENT1("browser", "CompositingIOSurfaceMac::DrawIOSurface",
303 "has_io_surface", has_io_surface);
305 gfx::Rect pixel_window_rect =
306 ToNearestRect(gfx::ScaleRect(window_rect, window_scale_factor));
308 pixel_window_rect.x(), pixel_window_rect.y(),
309 pixel_window_rect.width(), pixel_window_rect.height());
312 quad.set_size(dip_io_surface_size_, pixel_io_surface_size_);
314 glMatrixMode(GL_PROJECTION);
317 // Note that the projection keeps things in view units, so the use of
318 // window_rect / dip_io_surface_size_ (as opposed to the pixel_ variants)
320 glOrtho(0, window_rect.width(), window_rect.height(), 0, -1, 1);
321 glMatrixMode(GL_MODELVIEW);
324 glDisable(GL_DEPTH_TEST);
327 if (has_io_surface) {
328 drawing_context->shader_program_cache()->UseBlitProgram();
329 glActiveTexture(GL_TEXTURE0);
330 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_);
334 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); CHECK_AND_SAVE_GL_ERROR();
336 // Fill the resize gutters with white.
337 if (window_rect.width() > dip_io_surface_size_.width() ||
338 window_rect.height() > dip_io_surface_size_.height()) {
339 drawing_context->shader_program_cache()->UseSolidWhiteProgram();
340 SurfaceQuad filler_quad;
341 if (window_rect.width() > dip_io_surface_size_.width()) {
342 // Draw right-side gutter down to the bottom of the window.
343 filler_quad.set_rect(dip_io_surface_size_.width(), 0.0f,
344 window_rect.width(), window_rect.height());
345 DrawQuad(filler_quad);
347 if (window_rect.height() > dip_io_surface_size_.height()) {
348 // Draw bottom gutter to the width of the IOSurface.
349 filler_quad.set_rect(
350 0.0f, dip_io_surface_size_.height(),
351 dip_io_surface_size_.width(), window_rect.height());
352 DrawQuad(filler_quad);
356 // Workaround for issue 158469. Issue a dummy draw call with texture_ not
357 // bound to blit_rgb_sampler_location_, in order to shake all references
358 // to the IOSurface out of the driver.
359 glBegin(GL_TRIANGLES);
362 glUseProgram(0); CHECK_AND_SAVE_GL_ERROR();
364 // Should match the clear color of RenderWidgetHostViewMac.
365 glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
366 glClear(GL_COLOR_BUFFER_BIT);
369 bool workaround_needed =
370 GpuDataManagerImpl::GetInstance()->IsDriverBugWorkaroundActive(
371 gpu::FORCE_GL_FINISH_AFTER_COMPOSITING);
372 // Note that this is not necessary when flushing the drawable in Mavericks
373 // or later if we are in one of the two following situations:
374 // - we are drawing an underlay, and we will call glFinish() when drawing
376 // - we are using CoreAnimation, where this bug does not manifest.
377 if (workaround_needed && !flush_drawable && base::mac::IsOSMavericksOrLater())
378 workaround_needed = false;
380 if (workaround_needed) {
381 TRACE_EVENT0("gpu", "glFinish");
386 if (flush_drawable) {
387 TRACE_EVENT0("gpu", "flushBuffer");
388 [drawing_context->nsgl_context() flushBuffer];
391 // Check if any of the drawing calls result in an error.
393 if (gl_error_ != GL_NO_ERROR) {
394 LOG(ERROR) << "GL error in DrawIOSurface: " << gl_error_;
396 // If there was an error, clear the screen to a light grey to avoid
397 // rendering artifacts. If we're in a really bad way, this too may
398 // generate an error. Clear the GL error afterwards just in case.
399 glClearColor(0.8, 0.8, 0.8, 1.0);
400 glClear(GL_COLOR_BUFFER_BIT);
404 // Try to finish previous copy requests after flush to get better pipelining.
405 CheckIfAllCopiesAreFinished(false);
410 void CompositingIOSurfaceMac::CopyTo(
411 const gfx::Rect& src_pixel_subrect,
412 const gfx::Size& dst_pixel_size,
413 const base::Callback<void(bool, const SkBitmap&)>& callback) {
414 scoped_ptr<SkBitmap> output(new SkBitmap());
415 output->setConfig(SkBitmap::kARGB_8888_Config,
416 dst_pixel_size.width(),
417 dst_pixel_size.height(),
419 kOpaque_SkAlphaType);
421 if (!output->allocPixels()) {
422 DLOG(ERROR) << "Failed to allocate SkBitmap pixels!";
423 callback.Run(false, *output);
426 DCHECK_EQ(output->rowBytesAsPixels(), dst_pixel_size.width())
427 << "Stride is required to be equal to width for GPU readback.";
429 base::Closure copy_done_callback;
431 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
432 offscreen_context_->cgl_context());
433 copy_done_callback = CopyToSelectedOutputWithinContext(
434 src_pixel_subrect, gfx::Rect(dst_pixel_size), false,
436 base::Bind(&ReverseArgumentOrder, callback, base::Passed(&output)));
438 if (!copy_done_callback.is_null())
439 copy_done_callback.Run();
442 void CompositingIOSurfaceMac::CopyToVideoFrame(
443 const gfx::Rect& src_pixel_subrect,
444 const scoped_refptr<media::VideoFrame>& target,
445 const base::Callback<void(bool)>& callback) {
446 base::Closure copy_done_callback;
448 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
449 offscreen_context_->cgl_context());
450 copy_done_callback = CopyToVideoFrameWithinContext(
451 src_pixel_subrect, false, target, callback);
453 if (!copy_done_callback.is_null())
454 copy_done_callback.Run();
457 base::Closure CompositingIOSurfaceMac::CopyToVideoFrameWithinContext(
458 const gfx::Rect& src_pixel_subrect,
459 bool called_within_draw,
460 const scoped_refptr<media::VideoFrame>& target,
461 const base::Callback<void(bool)>& callback) {
462 gfx::Rect region_in_frame = media::ComputeLetterboxRegion(
463 gfx::Rect(target->coded_size()), src_pixel_subrect.size());
464 // Make coordinates and sizes even because we letterbox in YUV space right
465 // now (see CopyRGBToVideoFrame). They need to be even for the UV samples to
466 // line up correctly.
467 region_in_frame = gfx::Rect(region_in_frame.x() & ~1,
468 region_in_frame.y() & ~1,
469 region_in_frame.width() & ~1,
470 region_in_frame.height() & ~1);
471 DCHECK_LE(region_in_frame.right(), target->coded_size().width());
472 DCHECK_LE(region_in_frame.bottom(), target->coded_size().height());
474 return CopyToSelectedOutputWithinContext(
475 src_pixel_subrect, region_in_frame, called_within_draw,
476 NULL, target, callback);
479 bool CompositingIOSurfaceMac::MapIOSurfaceToTextureWithContextCurrent(
480 const scoped_refptr<CompositingIOSurfaceContext>& current_context,
481 uint64 io_surface_handle) {
482 if (io_surface_.get() && io_surface_handle == io_surface_handle_)
485 TRACE_EVENT0("browser", "CompositingIOSurfaceMac::MapIOSurfaceToTexture");
486 UnrefIOSurfaceWithContextCurrent();
488 io_surface_.reset(io_surface_support_->IOSurfaceLookup(
489 static_cast<uint32>(io_surface_handle)));
490 // Can fail if IOSurface with that ID was already released by the gpu
493 UnrefIOSurfaceWithContextCurrent();
497 io_surface_handle_ = io_surface_handle;
499 // Actual IOSurface size is rounded up to reduce reallocations during window
500 // resize. Get the actual size to properly map the texture.
501 gfx::Size rounded_size(
502 io_surface_support_->IOSurfaceGetWidth(io_surface_),
503 io_surface_support_->IOSurfaceGetHeight(io_surface_));
505 glGenTextures(1, &texture_);
506 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_);
507 glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
508 glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
509 CHECK_AND_SAVE_GL_ERROR();
511 CGLError cgl_error = io_surface_support_->CGLTexImageIOSurface2D(
512 current_context->cgl_context(),
513 GL_TEXTURE_RECTANGLE_ARB,
515 rounded_size.width(),
516 rounded_size.height(),
518 GL_UNSIGNED_INT_8_8_8_8_REV,
521 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
522 if (cgl_error != kCGLNoError) {
523 LOG(ERROR) << "CGLTexImageIOSurface2D: " << cgl_error;
524 UnrefIOSurfaceWithContextCurrent();
528 if (gl_error_ != GL_NO_ERROR) {
529 LOG(ERROR) << "GL error in MapIOSurfaceToTexture: " << gl_error_;
530 UnrefIOSurfaceWithContextCurrent();
536 void CompositingIOSurfaceMac::UnrefIOSurface() {
537 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
538 offscreen_context_->cgl_context());
539 UnrefIOSurfaceWithContextCurrent();
542 void CompositingIOSurfaceMac::DrawQuad(const SurfaceQuad& quad) {
543 TRACE_EVENT0("gpu", "CompositingIOSurfaceMac::DrawQuad");
545 glEnableClientState(GL_VERTEX_ARRAY); CHECK_AND_SAVE_GL_ERROR();
546 glEnableClientState(GL_TEXTURE_COORD_ARRAY); CHECK_AND_SAVE_GL_ERROR();
548 glVertexPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].x_);
549 glTexCoordPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].tx_);
550 glDrawArrays(GL_QUADS, 0, 4); CHECK_AND_SAVE_GL_ERROR();
552 glDisableClientState(GL_VERTEX_ARRAY);
553 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
556 void CompositingIOSurfaceMac::UnrefIOSurfaceWithContextCurrent() {
558 glDeleteTextures(1, &texture_);
564 // Forget the ID, because even if it is still around when we want to use it
565 // again, OSX may have reused the same ID for a new tab and we don't want to
566 // blit random tab contents.
567 io_surface_handle_ = 0;
570 bool CompositingIOSurfaceMac::IsAsynchronousReadbackSupported() {
571 const bool forced_synchronous = CommandLine::ForCurrentProcess()->HasSwitch(
572 switches::kForceSynchronousGLReadPixels);
573 if (forced_synchronous)
575 if (!HasAppleFenceExtension() && HasPixelBufferObjectExtension())
577 // Using PBO crashes or generates invalid output for machines using
578 // Snow Leopard (10.6).
579 // See bug crbug.com/152225 and crbug.com/348256.
580 if (!base::mac::IsOSMountainLionOrLater())
585 bool CompositingIOSurfaceMac::HasBeenPoisoned() const {
586 return offscreen_context_->HasBeenPoisoned();
589 base::Closure CompositingIOSurfaceMac::CopyToSelectedOutputWithinContext(
590 const gfx::Rect& src_pixel_subrect,
591 const gfx::Rect& dst_pixel_rect,
592 bool called_within_draw,
593 const SkBitmap* bitmap_output,
594 const scoped_refptr<media::VideoFrame>& video_frame_output,
595 const base::Callback<void(bool)>& done_callback) {
596 DCHECK_NE(bitmap_output != NULL, video_frame_output.get() != NULL);
597 DCHECK(!done_callback.is_null());
599 // SWIZZLE_RGBA_FOR_ASYNC_READPIXELS workaround: Fall-back to synchronous
600 // readback for SkBitmap output since the Blit shader program doesn't support
601 // switchable output formats.
602 const bool require_sync_copy_for_workaround = bitmap_output &&
603 offscreen_context_->shader_program_cache()->rgb_to_yv12_output_format() ==
605 const bool async_copy = !require_sync_copy_for_workaround &&
606 IsAsynchronousReadbackSupported();
608 "browser", "CompositingIOSurfaceMac::CopyToSelectedOutputWithinContext",
609 "output", bitmap_output ? "SkBitmap (ARGB)" : "VideoFrame (YV12)",
610 "async_readback", async_copy);
612 const gfx::Rect src_rect = IntersectWithIOSurface(src_pixel_subrect);
613 if (src_rect.IsEmpty() || dst_pixel_rect.IsEmpty())
614 return base::Bind(done_callback, false);
616 CopyContext* copy_context;
617 if (copy_context_pool_.empty()) {
618 // Limit the maximum number of simultaneous copies to two. Rationale:
619 // Really, only one should ever be in-progress at a time, as we should
620 // depend on the speed of the hardware to rate-limit the copying naturally.
621 // In the asynchronous read-back case, the one currently in-flight copy is
622 // highly likely to have finished by this point (i.e., it's just waiting for
623 // us to make a glMapBuffer() call). Therefore, we allow a second copy to
625 if (copy_requests_.size() >= 2)
626 return base::Bind(done_callback, false);
627 copy_context = new CopyContext(offscreen_context_);
629 copy_context = copy_context_pool_.back();
630 copy_context_pool_.pop_back();
634 return base::Bind(done_callback, false);
636 // Send transform commands to the GPU.
637 copy_context->num_outputs = 0;
639 if (copy_context->transformer->ResizeBilinear(
640 texture_, src_rect, dst_pixel_rect.size(),
641 ©_context->output_textures[0])) {
642 copy_context->output_readback_format = GL_BGRA;
643 copy_context->num_outputs = 1;
644 copy_context->output_texture_sizes[0] = dst_pixel_rect.size();
647 if (copy_context->transformer->TransformRGBToYV12(
648 texture_, src_rect, dst_pixel_rect.size(),
649 ©_context->output_textures[0],
650 ©_context->output_textures[1],
651 ©_context->output_textures[2],
652 ©_context->output_texture_sizes[0],
653 ©_context->output_texture_sizes[1])) {
654 copy_context->output_readback_format =
655 offscreen_context_->shader_program_cache()->
656 rgb_to_yv12_output_format();
657 copy_context->num_outputs = 3;
658 copy_context->output_texture_sizes[2] =
659 copy_context->output_texture_sizes[1];
662 if (!copy_context->num_outputs)
663 return base::Bind(done_callback, false);
665 // In the asynchronous case, issue commands to the GPU and return a null
666 // closure here. In the synchronous case, perform a blocking readback and
667 // return a callback to be run outside the CGL context to indicate success.
669 copy_context->done_callback = done_callback;
670 AsynchronousReadbackForCopy(
671 dst_pixel_rect, called_within_draw, copy_context, bitmap_output,
673 copy_requests_.push_back(copy_context);
674 if (!finish_copy_timer_.IsRunning())
675 finish_copy_timer_.Reset();
676 return base::Closure();
678 const bool success = SynchronousReadbackForCopy(
679 dst_pixel_rect, copy_context, bitmap_output, video_frame_output);
680 return base::Bind(done_callback, success);
684 void CompositingIOSurfaceMac::AsynchronousReadbackForCopy(
685 const gfx::Rect& dst_pixel_rect,
686 bool called_within_draw,
687 CopyContext* copy_context,
688 const SkBitmap* bitmap_output,
689 const scoped_refptr<media::VideoFrame>& video_frame_output) {
690 copy_context->PrepareForAsynchronousReadback();
692 // Copy the textures to their corresponding PBO.
693 for (int i = 0; i < copy_context->num_outputs; ++i) {
695 "browser", "CompositingIOSurfaceMac::AsynchronousReadbackForCopy",
698 // Attach the output texture to the FBO.
699 glBindFramebufferEXT(
700 GL_READ_FRAMEBUFFER_EXT, copy_context->frame_buffers[i]);
701 glFramebufferTexture2DEXT(
702 GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
703 GL_TEXTURE_RECTANGLE_ARB, copy_context->output_textures[i], 0);
704 DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) ==
705 GL_FRAMEBUFFER_COMPLETE_EXT);
707 // Create a PBO and issue an asynchronous read-back.
708 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context->pixel_buffers[i]);
709 CHECK_AND_SAVE_GL_ERROR();
710 glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB,
711 copy_context->output_texture_sizes[i].GetArea() * 4,
712 NULL, GL_STREAM_READ_ARB);
713 CHECK_AND_SAVE_GL_ERROR();
715 copy_context->output_texture_sizes[i].width(),
716 copy_context->output_texture_sizes[i].height(),
717 copy_context->output_readback_format,
718 GL_UNSIGNED_INT_8_8_8_8_REV, 0);
719 CHECK_AND_SAVE_GL_ERROR();
722 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_AND_SAVE_GL_ERROR();
723 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); CHECK_AND_SAVE_GL_ERROR();
725 glSetFenceAPPLE(copy_context->fence); CHECK_GL_ERROR();
726 copy_context->cycles_elapsed = 0;
728 // When this asynchronous copy happens in a draw operaton there is no need
729 // to explicitly flush because there will be a swap buffer and this flush
730 // hurts performance.
731 if (!called_within_draw) {
732 glFlush(); CHECK_AND_SAVE_GL_ERROR();
735 copy_context->map_buffer_callback = bitmap_output ?
736 base::Bind(&MapBufferToSkBitmap, bitmap_output) :
737 base::Bind(&MapBufferToVideoFrame, video_frame_output, dst_pixel_rect);
740 void CompositingIOSurfaceMac::CheckIfAllCopiesAreFinished(
741 bool block_until_finished) {
742 if (copy_requests_.empty())
745 std::vector<base::Closure> done_callbacks;
747 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(
748 offscreen_context_->cgl_context());
749 CheckIfAllCopiesAreFinishedWithinContext(
750 block_until_finished, &done_callbacks);
752 for (size_t i = 0; i < done_callbacks.size(); ++i)
753 done_callbacks[i].Run();
756 void CompositingIOSurfaceMac::CheckIfAllCopiesAreFinishedWithinContext(
757 bool block_until_finished,
758 std::vector<base::Closure>* done_callbacks) {
759 while (!copy_requests_.empty()) {
760 CopyContext* const copy_context = copy_requests_.front();
762 if (copy_context->fence && !glTestFenceAPPLE(copy_context->fence)) {
763 CHECK_AND_SAVE_GL_ERROR();
764 // Doing a glFinishFenceAPPLE can cause transparent window flashes when
765 // switching tabs, so only do it when required.
766 if (block_until_finished) {
767 glFinishFenceAPPLE(copy_context->fence);
768 CHECK_AND_SAVE_GL_ERROR();
769 } else if (copy_context->cycles_elapsed < kFinishCopyRetryCycles) {
770 ++copy_context->cycles_elapsed;
771 // This copy has not completed there is no need to test subsequent
776 CHECK_AND_SAVE_GL_ERROR();
779 for (int i = 0; success && i < copy_context->num_outputs; ++i) {
782 "CompositingIOSurfaceMac::CheckIfAllCopiesAreFinishedWithinContext",
785 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context->pixel_buffers[i]);
786 CHECK_AND_SAVE_GL_ERROR();
788 void* buf = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB);
789 CHECK_AND_SAVE_GL_ERROR();
790 success &= copy_context->map_buffer_callback.Run(buf, i);
791 glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); CHECK_AND_SAVE_GL_ERROR();
793 copy_context->map_buffer_callback.Reset();
794 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_AND_SAVE_GL_ERROR();
796 copy_requests_.pop_front();
797 done_callbacks->push_back(base::Bind(copy_context->done_callback, success));
798 copy_context->done_callback.Reset();
799 copy_context_pool_.push_back(copy_context);
801 if (copy_requests_.empty())
802 finish_copy_timer_.Stop();
804 CHECK(copy_requests_.empty() || !block_until_finished);
807 bool CompositingIOSurfaceMac::SynchronousReadbackForCopy(
808 const gfx::Rect& dst_pixel_rect,
809 CopyContext* copy_context,
810 const SkBitmap* bitmap_output,
811 const scoped_refptr<media::VideoFrame>& video_frame_output) {
813 copy_context->PrepareReadbackFramebuffers();
814 for (int i = 0; i < copy_context->num_outputs; ++i) {
816 "browser", "CompositingIOSurfaceMac::SynchronousReadbackForCopy",
819 // Attach the output texture to the FBO.
820 glBindFramebufferEXT(
821 GL_READ_FRAMEBUFFER_EXT, copy_context->frame_buffers[i]);
822 glFramebufferTexture2DEXT(
823 GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
824 GL_TEXTURE_RECTANGLE_ARB, copy_context->output_textures[i], 0);
825 DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) ==
826 GL_FRAMEBUFFER_COMPLETE_EXT);
828 // Blocking read-back of pixels from textures.
830 // When data must be transferred into a VideoFrame one scanline at a time,
831 // it is necessary to allocate a separate buffer for glReadPixels() that can
832 // be populated one-shot.
834 // TODO(miu): Don't keep allocating/deleting this buffer for every frame.
835 // Keep it cached, allocated on first use.
836 scoped_ptr<uint32[]> temp_readback_buffer;
838 // The entire SkBitmap is populated, never a region within. So, read the
839 // texture directly into the bitmap's pixel memory.
840 buf = bitmap_output->getPixels();
842 // Optimization: If the VideoFrame is letterboxed (not pillarboxed), and
843 // its stride is equal to the stride of the data being read back, then
844 // readback directly into the VideoFrame's buffer to save a round of
847 // TODO(miu): Move these calculations into VideoFrame (need a CalcOffset()
848 // method). http://crbug.com/219779
849 const int src_stride = copy_context->output_texture_sizes[i].width() * 4;
850 const int dst_stride = video_frame_output->stride(i);
851 if (src_stride == dst_stride && dst_pixel_rect.x() == 0) {
852 const int y_offset = dst_pixel_rect.y() / (i == 0 ? 1 : 2);
853 buf = video_frame_output->data(i) + y_offset * dst_stride;
855 // Create and readback into a temporary buffer because the data must be
856 // transferred to VideoFrame's pixel memory one scanline at a time.
857 temp_readback_buffer.reset(
858 new uint32[copy_context->output_texture_sizes[i].GetArea()]);
859 buf = temp_readback_buffer.get();
863 copy_context->output_texture_sizes[i].width(),
864 copy_context->output_texture_sizes[i].height(),
865 copy_context->output_readback_format,
866 GL_UNSIGNED_INT_8_8_8_8_REV, buf);
867 CHECK_AND_SAVE_GL_ERROR();
868 if (video_frame_output.get()) {
869 if (!temp_readback_buffer) {
870 // Apply letterbox black-out around view region.
871 media::LetterboxYUV(video_frame_output.get(), dst_pixel_rect);
873 // Copy from temporary buffer and fully render the VideoFrame.
874 success &= MapBufferToVideoFrame(video_frame_output, dst_pixel_rect,
875 temp_readback_buffer.get(), i);
880 glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); CHECK_AND_SAVE_GL_ERROR();
881 copy_context_pool_.push_back(copy_context);
885 void CompositingIOSurfaceMac::FailAllCopies() {
886 for (size_t i = 0; i < copy_requests_.size(); ++i) {
887 copy_requests_[i]->map_buffer_callback.Reset();
889 base::Callback<void(bool)>& done_callback =
890 copy_requests_[i]->done_callback;
891 if (!done_callback.is_null()) {
892 done_callback.Run(false);
893 done_callback.Reset();
898 void CompositingIOSurfaceMac::DestroyAllCopyContextsWithinContext() {
899 // Move all in-flight copies, if any, back into the pool. Then, destroy all
900 // the CopyContexts in the pool.
901 copy_context_pool_.insert(copy_context_pool_.end(),
902 copy_requests_.begin(), copy_requests_.end());
903 copy_requests_.clear();
904 while (!copy_context_pool_.empty()) {
905 scoped_ptr<CopyContext> copy_context(copy_context_pool_.back());
906 copy_context_pool_.pop_back();
907 copy_context->ReleaseCachedGLObjects();
911 gfx::Rect CompositingIOSurfaceMac::IntersectWithIOSurface(
912 const gfx::Rect& rect) const {
913 return gfx::IntersectRects(rect,
914 gfx::ToEnclosingRect(gfx::Rect(pixel_io_surface_size_)));
917 GLenum CompositingIOSurfaceMac::GetAndSaveGLError() {
918 GLenum gl_error = glGetError();
919 if (gl_error_ == GL_NO_ERROR)
920 gl_error_ = gl_error;
924 } // namespace content