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