// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/renderer_host/compositing_iosurface_mac.h" #include #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/command_line.h" #include "base/debug/trace_event.h" #include "base/logging.h" #include "base/mac/mac_util.h" #include "base/message_loop/message_loop.h" #include "base/threading/platform_thread.h" #include "content/browser/gpu/gpu_data_manager_impl.h" #include "content/browser/renderer_host/compositing_iosurface_context_mac.h" #include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h" #include "content/browser/renderer_host/compositing_iosurface_transformer_mac.h" #include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/browser/renderer_host/render_widget_host_view_mac.h" #include "content/common/content_constants_internal.h" #include "gpu/command_buffer/service/gpu_switches.h" #include "gpu/config/gpu_driver_bug_workaround_type.h" #include "media/base/video_util.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/rect.h" #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" #include "ui/gfx/size_conversions.h" #include "ui/gl/gl_context.h" #include "ui/gl/io_surface_support_mac.h" #ifdef NDEBUG #define CHECK_GL_ERROR() #define CHECK_AND_SAVE_GL_ERROR() #else #define CHECK_GL_ERROR() do { \ GLenum gl_error = glGetError(); \ LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error: " << gl_error; \ } while (0) #define CHECK_AND_SAVE_GL_ERROR() do { \ GLenum gl_error = GetAndSaveGLError(); \ LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error: " << gl_error; \ } while (0) #endif namespace content { namespace { // How many times to test if asynchronous copy has completed. // This value is chosen such that we allow at most 1 second to finish a copy. const int kFinishCopyRetryCycles = 100; // Time in milliseconds to allow asynchronous copy to finish. // This value is shorter than 16ms such that copy can complete within a vsync. const int kFinishCopyPollingPeriodMs = 10; bool HasAppleFenceExtension() { static bool initialized_has_fence = false; static bool has_fence = false; if (!initialized_has_fence) { has_fence = strstr(reinterpret_cast(glGetString(GL_EXTENSIONS)), "GL_APPLE_fence") != NULL; initialized_has_fence = true; } return has_fence; } bool HasPixelBufferObjectExtension() { static bool initialized_has_pbo = false; static bool has_pbo = false; if (!initialized_has_pbo) { has_pbo = strstr(reinterpret_cast(glGetString(GL_EXTENSIONS)), "GL_ARB_pixel_buffer_object") != NULL; initialized_has_pbo = true; } return has_pbo; } // Helper function to reverse the argument order. Also takes ownership of // |bitmap_output| for the life of the binding. void ReverseArgumentOrder( const base::Callback& callback, scoped_ptr bitmap_output, bool success) { callback.Run(success, *bitmap_output); } // Called during an async GPU readback with a pointer to the pixel buffer. In // the snapshot path, we just memcpy the data into our output bitmap since the // width, height, and stride should all be equal. bool MapBufferToSkBitmap(const SkBitmap* output, const void* buf, int ignored) { TRACE_EVENT0("browser", "MapBufferToSkBitmap"); if (buf) { SkAutoLockPixels output_lock(*output); memcpy(output->getPixels(), buf, output->getSize()); } return buf != NULL; } // Copies tightly-packed scanlines from |buf| to |region_in_frame| in the given // |target| VideoFrame's |plane|. Assumption: |buf|'s width is // |region_in_frame.width()| and its stride is always in 4-byte alignment. // // TODO(miu): Refactor by moving this function into media/video_util. // http://crbug.com/219779 bool MapBufferToVideoFrame( const scoped_refptr& target, const gfx::Rect& region_in_frame, const void* buf, int plane) { COMPILE_ASSERT(media::VideoFrame::kYPlane == 0, VideoFrame_kYPlane_mismatch); COMPILE_ASSERT(media::VideoFrame::kUPlane == 1, VideoFrame_kUPlane_mismatch); COMPILE_ASSERT(media::VideoFrame::kVPlane == 2, VideoFrame_kVPlane_mismatch); TRACE_EVENT1("browser", "MapBufferToVideoFrame", "plane", plane); // Apply black-out in the regions surrounding the view area (for // letterboxing/pillarboxing). Only do this once, since this is performed on // all planes in the VideoFrame here. if (plane == 0) media::LetterboxYUV(target.get(), region_in_frame); if (buf) { int packed_width = region_in_frame.width(); int packed_height = region_in_frame.height(); // For planes 1 and 2, the width and height are 1/2 size (rounded up). if (plane > 0) { packed_width = (packed_width + 1) / 2; packed_height = (packed_height + 1) / 2; } const uint8* src = reinterpret_cast(buf); const int src_stride = (packed_width % 4 == 0 ? packed_width : (packed_width + 4 - (packed_width % 4))); const uint8* const src_end = src + packed_height * src_stride; // Calculate starting offset and stride into the destination buffer. const int dst_stride = target->stride(plane); uint8* dst = target->data(plane); if (plane == 0) dst += (region_in_frame.y() * dst_stride) + region_in_frame.x(); else dst += (region_in_frame.y() / 2 * dst_stride) + (region_in_frame.x() / 2); // Copy each row, accounting for strides in the source and destination. for (; src < src_end; src += src_stride, dst += dst_stride) memcpy(dst, src, packed_width); } return buf != NULL; } } // namespace CompositingIOSurfaceMac::CopyContext::CopyContext( const scoped_refptr& context) : transformer(new CompositingIOSurfaceTransformer( GL_TEXTURE_RECTANGLE_ARB, true, context->shader_program_cache())), output_readback_format(GL_BGRA), num_outputs(0), fence(0), cycles_elapsed(0) { memset(output_textures, 0, sizeof(output_textures)); memset(frame_buffers, 0, sizeof(frame_buffers)); memset(pixel_buffers, 0, sizeof(pixel_buffers)); } CompositingIOSurfaceMac::CopyContext::~CopyContext() { DCHECK_EQ(frame_buffers[0], 0u) << "Failed to call ReleaseCachedGLObjects()."; } void CompositingIOSurfaceMac::CopyContext::ReleaseCachedGLObjects() { // No outstanding callbacks should be pending. DCHECK(map_buffer_callback.is_null()); DCHECK(done_callback.is_null()); // For an asynchronous read-back, there are more objects to delete: if (fence) { glDeleteBuffers(arraysize(pixel_buffers), pixel_buffers); CHECK_GL_ERROR(); memset(pixel_buffers, 0, sizeof(pixel_buffers)); glDeleteFencesAPPLE(1, &fence); CHECK_GL_ERROR(); fence = 0; } glDeleteFramebuffersEXT(arraysize(frame_buffers), frame_buffers); CHECK_GL_ERROR(); memset(frame_buffers, 0, sizeof(frame_buffers)); // Note: |output_textures| are owned by the transformer. if (transformer) transformer->ReleaseCachedGLObjects(); } void CompositingIOSurfaceMac::CopyContext::PrepareReadbackFramebuffers() { for (int i = 0; i < num_outputs; ++i) { if (!frame_buffers[i]) { glGenFramebuffersEXT(1, &frame_buffers[i]); CHECK_GL_ERROR(); } } } void CompositingIOSurfaceMac::CopyContext::PrepareForAsynchronousReadback() { PrepareReadbackFramebuffers(); if (!fence) { glGenFencesAPPLE(1, &fence); CHECK_GL_ERROR(); } for (int i = 0; i < num_outputs; ++i) { if (!pixel_buffers[i]) { glGenBuffersARB(1, &pixel_buffers[i]); CHECK_GL_ERROR(); } } } // static CompositingIOSurfaceMac* CompositingIOSurfaceMac::Create() { IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize(); if (!io_surface_support) { LOG(ERROR) << "No IOSurface support"; return NULL; } scoped_refptr offscreen_context = CompositingIOSurfaceContext::Get( CompositingIOSurfaceContext::kOffscreenContextWindowNumber); if (!offscreen_context) { LOG(ERROR) << "Failed to create context for offscreen operations"; return NULL; } return new CompositingIOSurfaceMac(io_surface_support, offscreen_context); } CompositingIOSurfaceMac::CompositingIOSurfaceMac( IOSurfaceSupport* io_surface_support, const scoped_refptr& offscreen_context) : io_surface_support_(io_surface_support), offscreen_context_(offscreen_context), io_surface_handle_(0), scale_factor_(1.f), texture_(0), finish_copy_timer_( FROM_HERE, base::TimeDelta::FromMilliseconds(kFinishCopyPollingPeriodMs), base::Bind(&CompositingIOSurfaceMac::CheckIfAllCopiesAreFinished, base::Unretained(this), false), true), gl_error_(GL_NO_ERROR) { CHECK(offscreen_context_); } CompositingIOSurfaceMac::~CompositingIOSurfaceMac() { FailAllCopies(); { gfx::ScopedCGLSetCurrentContext scoped_set_current_context( offscreen_context_->cgl_context()); DestroyAllCopyContextsWithinContext(); UnrefIOSurfaceWithContextCurrent(); } offscreen_context_ = NULL; } bool CompositingIOSurfaceMac::SetIOSurfaceWithContextCurrent( scoped_refptr current_context, uint64 io_surface_handle, const gfx::Size& size, float scale_factor) { pixel_io_surface_size_ = size; scale_factor_ = scale_factor; dip_io_surface_size_ = gfx::ToFlooredSize( gfx::ScaleSize(pixel_io_surface_size_, 1.0 / scale_factor_)); bool result = MapIOSurfaceToTextureWithContextCurrent( current_context, io_surface_handle); return result; } int CompositingIOSurfaceMac::GetRendererID() { GLint current_renderer_id = -1; if (CGLGetParameter(offscreen_context_->cgl_context(), kCGLCPCurrentRendererID, ¤t_renderer_id) == kCGLNoError) return current_renderer_id & kCGLRendererIDMatchingMask; return -1; } bool CompositingIOSurfaceMac::DrawIOSurface( scoped_refptr drawing_context, const gfx::Rect& window_rect, float window_scale_factor, bool flush_drawable) { DCHECK_EQ(CGLGetCurrentContext(), drawing_context->cgl_context()); bool has_io_surface = HasIOSurface(); TRACE_EVENT1("browser", "CompositingIOSurfaceMac::DrawIOSurface", "has_io_surface", has_io_surface); gfx::Rect pixel_window_rect = ToNearestRect(gfx::ScaleRect(window_rect, window_scale_factor)); glViewport( pixel_window_rect.x(), pixel_window_rect.y(), pixel_window_rect.width(), pixel_window_rect.height()); SurfaceQuad quad; quad.set_size(dip_io_surface_size_, pixel_io_surface_size_); glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Note that the projection keeps things in view units, so the use of // window_rect / dip_io_surface_size_ (as opposed to the pixel_ variants) // below is correct. glOrtho(0, window_rect.width(), window_rect.height(), 0, -1, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glDisable(GL_DEPTH_TEST); glDisable(GL_BLEND); if (has_io_surface) { drawing_context->shader_program_cache()->UseBlitProgram(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_); DrawQuad(quad); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); CHECK_AND_SAVE_GL_ERROR(); // Fill the resize gutters with white. if (window_rect.width() > dip_io_surface_size_.width() || window_rect.height() > dip_io_surface_size_.height()) { drawing_context->shader_program_cache()->UseSolidWhiteProgram(); SurfaceQuad filler_quad; if (window_rect.width() > dip_io_surface_size_.width()) { // Draw right-side gutter down to the bottom of the window. filler_quad.set_rect(dip_io_surface_size_.width(), 0.0f, window_rect.width(), window_rect.height()); DrawQuad(filler_quad); } if (window_rect.height() > dip_io_surface_size_.height()) { // Draw bottom gutter to the width of the IOSurface. filler_quad.set_rect( 0.0f, dip_io_surface_size_.height(), dip_io_surface_size_.width(), window_rect.height()); DrawQuad(filler_quad); } } // Workaround for issue 158469. Issue a dummy draw call with texture_ not // bound to blit_rgb_sampler_location_, in order to shake all references // to the IOSurface out of the driver. glBegin(GL_TRIANGLES); glEnd(); glUseProgram(0); CHECK_AND_SAVE_GL_ERROR(); } else { // Should match the clear color of RenderWidgetHostViewMac. glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); } bool workaround_needed = GpuDataManagerImpl::GetInstance()->IsDriverBugWorkaroundActive( gpu::FORCE_GL_FINISH_AFTER_COMPOSITING); // Note that this is not necessary when flushing the drawable in Mavericks // or later if we are in one of the two following situations: // - we are drawing an underlay, and we will call glFinish() when drawing // the overlay. // - we are using CoreAnimation, where this bug does not manifest. if (workaround_needed && !flush_drawable && base::mac::IsOSMavericksOrLater()) workaround_needed = false; if (workaround_needed) { TRACE_EVENT0("gpu", "glFinish"); glFinish(); } bool result = true; if (flush_drawable) { TRACE_EVENT0("gpu", "flushBuffer"); [drawing_context->nsgl_context() flushBuffer]; } // Check if any of the drawing calls result in an error. GetAndSaveGLError(); if (gl_error_ != GL_NO_ERROR) { LOG(ERROR) << "GL error in DrawIOSurface: " << gl_error_; result = false; // If there was an error, clear the screen to a light grey to avoid // rendering artifacts. If we're in a really bad way, this too may // generate an error. Clear the GL error afterwards just in case. glClearColor(0.8, 0.8, 0.8, 1.0); glClear(GL_COLOR_BUFFER_BIT); glGetError(); } // Try to finish previous copy requests after flush to get better pipelining. CheckIfAllCopiesAreFinished(false); return result; } void CompositingIOSurfaceMac::CopyTo( const gfx::Rect& src_pixel_subrect, const gfx::Size& dst_pixel_size, const base::Callback& callback) { scoped_ptr output(new SkBitmap()); output->setConfig(SkBitmap::kARGB_8888_Config, dst_pixel_size.width(), dst_pixel_size.height(), 0, kOpaque_SkAlphaType); if (!output->allocPixels()) { DLOG(ERROR) << "Failed to allocate SkBitmap pixels!"; callback.Run(false, *output); return; } DCHECK_EQ(output->rowBytesAsPixels(), dst_pixel_size.width()) << "Stride is required to be equal to width for GPU readback."; base::Closure copy_done_callback; { gfx::ScopedCGLSetCurrentContext scoped_set_current_context( offscreen_context_->cgl_context()); copy_done_callback = CopyToSelectedOutputWithinContext( src_pixel_subrect, gfx::Rect(dst_pixel_size), false, output.get(), NULL, base::Bind(&ReverseArgumentOrder, callback, base::Passed(&output))); } if (!copy_done_callback.is_null()) copy_done_callback.Run(); } void CompositingIOSurfaceMac::CopyToVideoFrame( const gfx::Rect& src_pixel_subrect, const scoped_refptr& target, const base::Callback& callback) { base::Closure copy_done_callback; { gfx::ScopedCGLSetCurrentContext scoped_set_current_context( offscreen_context_->cgl_context()); copy_done_callback = CopyToVideoFrameWithinContext( src_pixel_subrect, false, target, callback); } if (!copy_done_callback.is_null()) copy_done_callback.Run(); } base::Closure CompositingIOSurfaceMac::CopyToVideoFrameWithinContext( const gfx::Rect& src_pixel_subrect, bool called_within_draw, const scoped_refptr& target, const base::Callback& callback) { gfx::Rect region_in_frame = media::ComputeLetterboxRegion( gfx::Rect(target->coded_size()), src_pixel_subrect.size()); // Make coordinates and sizes even because we letterbox in YUV space right // now (see CopyRGBToVideoFrame). They need to be even for the UV samples to // line up correctly. region_in_frame = gfx::Rect(region_in_frame.x() & ~1, region_in_frame.y() & ~1, region_in_frame.width() & ~1, region_in_frame.height() & ~1); DCHECK_LE(region_in_frame.right(), target->coded_size().width()); DCHECK_LE(region_in_frame.bottom(), target->coded_size().height()); return CopyToSelectedOutputWithinContext( src_pixel_subrect, region_in_frame, called_within_draw, NULL, target, callback); } bool CompositingIOSurfaceMac::MapIOSurfaceToTextureWithContextCurrent( const scoped_refptr& current_context, uint64 io_surface_handle) { if (io_surface_.get() && io_surface_handle == io_surface_handle_) return true; TRACE_EVENT0("browser", "CompositingIOSurfaceMac::MapIOSurfaceToTexture"); UnrefIOSurfaceWithContextCurrent(); io_surface_.reset(io_surface_support_->IOSurfaceLookup( static_cast(io_surface_handle))); // Can fail if IOSurface with that ID was already released by the gpu // process. if (!io_surface_) { UnrefIOSurfaceWithContextCurrent(); return false; } io_surface_handle_ = io_surface_handle; // Actual IOSurface size is rounded up to reduce reallocations during window // resize. Get the actual size to properly map the texture. gfx::Size rounded_size( io_surface_support_->IOSurfaceGetWidth(io_surface_), io_surface_support_->IOSurfaceGetHeight(io_surface_)); glGenTextures(1, &texture_); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_); glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); CHECK_AND_SAVE_GL_ERROR(); GLuint plane = 0; CGLError cgl_error = io_surface_support_->CGLTexImageIOSurface2D( current_context->cgl_context(), GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, rounded_size.width(), rounded_size.height(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, io_surface_.get(), plane); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); if (cgl_error != kCGLNoError) { LOG(ERROR) << "CGLTexImageIOSurface2D: " << cgl_error; UnrefIOSurfaceWithContextCurrent(); return false; } GetAndSaveGLError(); if (gl_error_ != GL_NO_ERROR) { LOG(ERROR) << "GL error in MapIOSurfaceToTexture: " << gl_error_; UnrefIOSurfaceWithContextCurrent(); return false; } return true; } void CompositingIOSurfaceMac::UnrefIOSurface() { gfx::ScopedCGLSetCurrentContext scoped_set_current_context( offscreen_context_->cgl_context()); UnrefIOSurfaceWithContextCurrent(); } void CompositingIOSurfaceMac::DrawQuad(const SurfaceQuad& quad) { TRACE_EVENT0("gpu", "CompositingIOSurfaceMac::DrawQuad"); glEnableClientState(GL_VERTEX_ARRAY); CHECK_AND_SAVE_GL_ERROR(); glEnableClientState(GL_TEXTURE_COORD_ARRAY); CHECK_AND_SAVE_GL_ERROR(); glVertexPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].x_); glTexCoordPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].tx_); glDrawArrays(GL_QUADS, 0, 4); CHECK_AND_SAVE_GL_ERROR(); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } void CompositingIOSurfaceMac::UnrefIOSurfaceWithContextCurrent() { if (texture_) { glDeleteTextures(1, &texture_); texture_ = 0; } io_surface_.reset(); // Forget the ID, because even if it is still around when we want to use it // again, OSX may have reused the same ID for a new tab and we don't want to // blit random tab contents. io_surface_handle_ = 0; } bool CompositingIOSurfaceMac::IsAsynchronousReadbackSupported() { const bool forced_synchronous = CommandLine::ForCurrentProcess()->HasSwitch( switches::kForceSynchronousGLReadPixels); if (forced_synchronous) return false; if (!HasAppleFenceExtension() && HasPixelBufferObjectExtension()) return false; // Using PBO crashes or generates invalid output for machines using // Snow Leopard (10.6). // See bug crbug.com/152225 and crbug.com/348256. if (!base::mac::IsOSMountainLionOrLater()) return false; return true; } bool CompositingIOSurfaceMac::HasBeenPoisoned() const { return offscreen_context_->HasBeenPoisoned(); } base::Closure CompositingIOSurfaceMac::CopyToSelectedOutputWithinContext( const gfx::Rect& src_pixel_subrect, const gfx::Rect& dst_pixel_rect, bool called_within_draw, const SkBitmap* bitmap_output, const scoped_refptr& video_frame_output, const base::Callback& done_callback) { DCHECK_NE(bitmap_output != NULL, video_frame_output.get() != NULL); DCHECK(!done_callback.is_null()); // SWIZZLE_RGBA_FOR_ASYNC_READPIXELS workaround: Fall-back to synchronous // readback for SkBitmap output since the Blit shader program doesn't support // switchable output formats. const bool require_sync_copy_for_workaround = bitmap_output && offscreen_context_->shader_program_cache()->rgb_to_yv12_output_format() == GL_RGBA; const bool async_copy = !require_sync_copy_for_workaround && IsAsynchronousReadbackSupported(); TRACE_EVENT2( "browser", "CompositingIOSurfaceMac::CopyToSelectedOutputWithinContext", "output", bitmap_output ? "SkBitmap (ARGB)" : "VideoFrame (YV12)", "async_readback", async_copy); const gfx::Rect src_rect = IntersectWithIOSurface(src_pixel_subrect); if (src_rect.IsEmpty() || dst_pixel_rect.IsEmpty()) return base::Bind(done_callback, false); CopyContext* copy_context; if (copy_context_pool_.empty()) { // Limit the maximum number of simultaneous copies to two. Rationale: // Really, only one should ever be in-progress at a time, as we should // depend on the speed of the hardware to rate-limit the copying naturally. // In the asynchronous read-back case, the one currently in-flight copy is // highly likely to have finished by this point (i.e., it's just waiting for // us to make a glMapBuffer() call). Therefore, we allow a second copy to // be started here. if (copy_requests_.size() >= 2) return base::Bind(done_callback, false); copy_context = new CopyContext(offscreen_context_); } else { copy_context = copy_context_pool_.back(); copy_context_pool_.pop_back(); } if (!HasIOSurface()) return base::Bind(done_callback, false); // Send transform commands to the GPU. copy_context->num_outputs = 0; if (bitmap_output) { if (copy_context->transformer->ResizeBilinear( texture_, src_rect, dst_pixel_rect.size(), ©_context->output_textures[0])) { copy_context->output_readback_format = GL_BGRA; copy_context->num_outputs = 1; copy_context->output_texture_sizes[0] = dst_pixel_rect.size(); } } else { if (copy_context->transformer->TransformRGBToYV12( texture_, src_rect, dst_pixel_rect.size(), ©_context->output_textures[0], ©_context->output_textures[1], ©_context->output_textures[2], ©_context->output_texture_sizes[0], ©_context->output_texture_sizes[1])) { copy_context->output_readback_format = offscreen_context_->shader_program_cache()-> rgb_to_yv12_output_format(); copy_context->num_outputs = 3; copy_context->output_texture_sizes[2] = copy_context->output_texture_sizes[1]; } } if (!copy_context->num_outputs) return base::Bind(done_callback, false); // In the asynchronous case, issue commands to the GPU and return a null // closure here. In the synchronous case, perform a blocking readback and // return a callback to be run outside the CGL context to indicate success. if (async_copy) { copy_context->done_callback = done_callback; AsynchronousReadbackForCopy( dst_pixel_rect, called_within_draw, copy_context, bitmap_output, video_frame_output); copy_requests_.push_back(copy_context); if (!finish_copy_timer_.IsRunning()) finish_copy_timer_.Reset(); return base::Closure(); } else { const bool success = SynchronousReadbackForCopy( dst_pixel_rect, copy_context, bitmap_output, video_frame_output); return base::Bind(done_callback, success); } } void CompositingIOSurfaceMac::AsynchronousReadbackForCopy( const gfx::Rect& dst_pixel_rect, bool called_within_draw, CopyContext* copy_context, const SkBitmap* bitmap_output, const scoped_refptr& video_frame_output) { copy_context->PrepareForAsynchronousReadback(); // Copy the textures to their corresponding PBO. for (int i = 0; i < copy_context->num_outputs; ++i) { TRACE_EVENT1( "browser", "CompositingIOSurfaceMac::AsynchronousReadbackForCopy", "plane", i); // Attach the output texture to the FBO. glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, copy_context->frame_buffers[i]); glFramebufferTexture2DEXT( GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, copy_context->output_textures[i], 0); DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT); // Create a PBO and issue an asynchronous read-back. glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context->pixel_buffers[i]); CHECK_AND_SAVE_GL_ERROR(); glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context->output_texture_sizes[i].GetArea() * 4, NULL, GL_STREAM_READ_ARB); CHECK_AND_SAVE_GL_ERROR(); glReadPixels(0, 0, copy_context->output_texture_sizes[i].width(), copy_context->output_texture_sizes[i].height(), copy_context->output_readback_format, GL_UNSIGNED_INT_8_8_8_8_REV, 0); CHECK_AND_SAVE_GL_ERROR(); } glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_AND_SAVE_GL_ERROR(); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); CHECK_AND_SAVE_GL_ERROR(); glSetFenceAPPLE(copy_context->fence); CHECK_GL_ERROR(); copy_context->cycles_elapsed = 0; // When this asynchronous copy happens in a draw operaton there is no need // to explicitly flush because there will be a swap buffer and this flush // hurts performance. if (!called_within_draw) { glFlush(); CHECK_AND_SAVE_GL_ERROR(); } copy_context->map_buffer_callback = bitmap_output ? base::Bind(&MapBufferToSkBitmap, bitmap_output) : base::Bind(&MapBufferToVideoFrame, video_frame_output, dst_pixel_rect); } void CompositingIOSurfaceMac::CheckIfAllCopiesAreFinished( bool block_until_finished) { if (copy_requests_.empty()) return; std::vector done_callbacks; { gfx::ScopedCGLSetCurrentContext scoped_set_current_context( offscreen_context_->cgl_context()); CheckIfAllCopiesAreFinishedWithinContext( block_until_finished, &done_callbacks); } for (size_t i = 0; i < done_callbacks.size(); ++i) done_callbacks[i].Run(); } void CompositingIOSurfaceMac::CheckIfAllCopiesAreFinishedWithinContext( bool block_until_finished, std::vector* done_callbacks) { while (!copy_requests_.empty()) { CopyContext* const copy_context = copy_requests_.front(); if (copy_context->fence && !glTestFenceAPPLE(copy_context->fence)) { CHECK_AND_SAVE_GL_ERROR(); // Doing a glFinishFenceAPPLE can cause transparent window flashes when // switching tabs, so only do it when required. if (block_until_finished) { glFinishFenceAPPLE(copy_context->fence); CHECK_AND_SAVE_GL_ERROR(); } else if (copy_context->cycles_elapsed < kFinishCopyRetryCycles) { ++copy_context->cycles_elapsed; // This copy has not completed there is no need to test subsequent // requests. break; } } CHECK_AND_SAVE_GL_ERROR(); bool success = true; for (int i = 0; success && i < copy_context->num_outputs; ++i) { TRACE_EVENT1( "browser", "CompositingIOSurfaceMac::CheckIfAllCopiesAreFinishedWithinContext", "plane", i); glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context->pixel_buffers[i]); CHECK_AND_SAVE_GL_ERROR(); void* buf = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB); CHECK_AND_SAVE_GL_ERROR(); success &= copy_context->map_buffer_callback.Run(buf, i); glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); CHECK_AND_SAVE_GL_ERROR(); } copy_context->map_buffer_callback.Reset(); glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_AND_SAVE_GL_ERROR(); copy_requests_.pop_front(); done_callbacks->push_back(base::Bind(copy_context->done_callback, success)); copy_context->done_callback.Reset(); copy_context_pool_.push_back(copy_context); } if (copy_requests_.empty()) finish_copy_timer_.Stop(); CHECK(copy_requests_.empty() || !block_until_finished); } bool CompositingIOSurfaceMac::SynchronousReadbackForCopy( const gfx::Rect& dst_pixel_rect, CopyContext* copy_context, const SkBitmap* bitmap_output, const scoped_refptr& video_frame_output) { bool success = true; copy_context->PrepareReadbackFramebuffers(); for (int i = 0; i < copy_context->num_outputs; ++i) { TRACE_EVENT1( "browser", "CompositingIOSurfaceMac::SynchronousReadbackForCopy", "plane", i); // Attach the output texture to the FBO. glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, copy_context->frame_buffers[i]); glFramebufferTexture2DEXT( GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, copy_context->output_textures[i], 0); DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT); // Blocking read-back of pixels from textures. void* buf; // When data must be transferred into a VideoFrame one scanline at a time, // it is necessary to allocate a separate buffer for glReadPixels() that can // be populated one-shot. // // TODO(miu): Don't keep allocating/deleting this buffer for every frame. // Keep it cached, allocated on first use. scoped_ptr temp_readback_buffer; if (bitmap_output) { // The entire SkBitmap is populated, never a region within. So, read the // texture directly into the bitmap's pixel memory. buf = bitmap_output->getPixels(); } else { // Optimization: If the VideoFrame is letterboxed (not pillarboxed), and // its stride is equal to the stride of the data being read back, then // readback directly into the VideoFrame's buffer to save a round of // memcpy'ing. // // TODO(miu): Move these calculations into VideoFrame (need a CalcOffset() // method). http://crbug.com/219779 const int src_stride = copy_context->output_texture_sizes[i].width() * 4; const int dst_stride = video_frame_output->stride(i); if (src_stride == dst_stride && dst_pixel_rect.x() == 0) { const int y_offset = dst_pixel_rect.y() / (i == 0 ? 1 : 2); buf = video_frame_output->data(i) + y_offset * dst_stride; } else { // Create and readback into a temporary buffer because the data must be // transferred to VideoFrame's pixel memory one scanline at a time. temp_readback_buffer.reset( new uint32[copy_context->output_texture_sizes[i].GetArea()]); buf = temp_readback_buffer.get(); } } glReadPixels(0, 0, copy_context->output_texture_sizes[i].width(), copy_context->output_texture_sizes[i].height(), copy_context->output_readback_format, GL_UNSIGNED_INT_8_8_8_8_REV, buf); CHECK_AND_SAVE_GL_ERROR(); if (video_frame_output.get()) { if (!temp_readback_buffer) { // Apply letterbox black-out around view region. media::LetterboxYUV(video_frame_output.get(), dst_pixel_rect); } else { // Copy from temporary buffer and fully render the VideoFrame. success &= MapBufferToVideoFrame(video_frame_output, dst_pixel_rect, temp_readback_buffer.get(), i); } } } glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); CHECK_AND_SAVE_GL_ERROR(); copy_context_pool_.push_back(copy_context); return success; } void CompositingIOSurfaceMac::FailAllCopies() { for (size_t i = 0; i < copy_requests_.size(); ++i) { copy_requests_[i]->map_buffer_callback.Reset(); base::Callback& done_callback = copy_requests_[i]->done_callback; if (!done_callback.is_null()) { done_callback.Run(false); done_callback.Reset(); } } } void CompositingIOSurfaceMac::DestroyAllCopyContextsWithinContext() { // Move all in-flight copies, if any, back into the pool. Then, destroy all // the CopyContexts in the pool. copy_context_pool_.insert(copy_context_pool_.end(), copy_requests_.begin(), copy_requests_.end()); copy_requests_.clear(); while (!copy_context_pool_.empty()) { scoped_ptr copy_context(copy_context_pool_.back()); copy_context_pool_.pop_back(); copy_context->ReleaseCachedGLObjects(); } } gfx::Rect CompositingIOSurfaceMac::IntersectWithIOSurface( const gfx::Rect& rect) const { return gfx::IntersectRects(rect, gfx::ToEnclosingRect(gfx::Rect(pixel_io_surface_size_))); } GLenum CompositingIOSurfaceMac::GetAndSaveGLError() { GLenum gl_error = glGetError(); if (gl_error_ == GL_NO_ERROR) gl_error_ = gl_error; return gl_error; } } // namespace content