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 const scoped_refptr<CompositingIOSurfaceContext>& context) { 235 IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize(); 236 if (!io_surface_support) { 237 LOG(ERROR) << "No IOSurface support"; 238 return NULL; 239 } 240 241 return new CompositingIOSurfaceMac(io_surface_support, 242 context); 243 } 244 245 CompositingIOSurfaceMac::CompositingIOSurfaceMac( 246 IOSurfaceSupport* io_surface_support, 247 const scoped_refptr<CompositingIOSurfaceContext>& context) 248 : io_surface_support_(io_surface_support), 249 context_(context), 250 io_surface_handle_(0), 251 scale_factor_(1.f), 252 texture_(0), 253 finish_copy_timer_( 254 FROM_HERE, 255 base::TimeDelta::FromMilliseconds(kFinishCopyPollingPeriodMs), 256 base::Bind(&CompositingIOSurfaceMac::CheckIfAllCopiesAreFinished, 257 base::Unretained(this), 258 false), 259 true), 260 display_link_(0), 261 display_link_stop_timer_(FROM_HERE, base::TimeDelta::FromSeconds(1), 262 this, &CompositingIOSurfaceMac::StopDisplayLink), 263 vsync_interval_numerator_(0), 264 vsync_interval_denominator_(0), 265 initialized_is_intel_(false), 266 is_intel_(false), 267 screen_(0), 268 gl_error_(GL_NO_ERROR) { 269 CHECK(context_); 270 } 271 272 void CompositingIOSurfaceMac::SetupCVDisplayLink() { 273 if (display_link_) { 274 LOG(ERROR) << "DisplayLink already setup"; 275 return; 276 } 277 278 CVDisplayLinkRef display_link; 279 CVReturn ret = CVDisplayLinkCreateWithActiveCGDisplays(&display_link); 280 if (ret != kCVReturnSuccess) { 281 LOG(WARNING) << "CVDisplayLinkCreateWithActiveCGDisplays failed: " << ret; 282 return; 283 } 284 285 display_link_ = display_link; 286 287 ret = CVDisplayLinkSetOutputCallback(display_link_, 288 &DisplayLinkCallback, this); 289 DCHECK(ret == kCVReturnSuccess) 290 << "CVDisplayLinkSetOutputCallback failed: " << ret; 291 292 StartOrContinueDisplayLink(); 293 294 CVTimeStamp cv_time; 295 ret = CVDisplayLinkGetCurrentTime(display_link_, &cv_time); 296 DCHECK(ret == kCVReturnSuccess) 297 << "CVDisplayLinkGetCurrentTime failed: " << ret; 298 299 { 300 base::AutoLock lock(lock_); 301 CalculateVsyncParametersLockHeld(&cv_time); 302 } 303 304 // Stop display link for now, it will be started when needed during Draw. 305 StopDisplayLink(); 306 } 307 308 void CompositingIOSurfaceMac::SetContext( 309 const scoped_refptr<CompositingIOSurfaceContext>& new_context) { 310 CHECK(new_context); 311 312 if (context_ == new_context) 313 return; 314 315 // Asynchronous copies must complete in the same context they started in. 316 CheckIfAllCopiesAreFinished(true); 317 CGLSetCurrentContext(context_->cgl_context()); 318 DestroyAllCopyContextsWithinContext(); 319 CGLSetCurrentContext(0); 320 321 context_ = new_context; 322 } 323 324 bool CompositingIOSurfaceMac::is_vsync_disabled() const { 325 return context_->is_vsync_disabled(); 326 } 327 328 void CompositingIOSurfaceMac::GetVSyncParameters(base::TimeTicks* timebase, 329 uint32* interval_numerator, 330 uint32* interval_denominator) { 331 base::AutoLock lock(lock_); 332 *timebase = vsync_timebase_; 333 *interval_numerator = vsync_interval_numerator_; 334 *interval_denominator = vsync_interval_denominator_; 335 } 336 337 CompositingIOSurfaceMac::~CompositingIOSurfaceMac() { 338 FailAllCopies(); 339 CVDisplayLinkRelease(display_link_); 340 CGLSetCurrentContext(context_->cgl_context()); 341 DestroyAllCopyContextsWithinContext(); 342 UnrefIOSurfaceWithContextCurrent(); 343 CGLSetCurrentContext(0); 344 context_ = NULL; 345 } 346 347 bool CompositingIOSurfaceMac::SetIOSurface( 348 uint64 io_surface_handle, 349 const gfx::Size& size, 350 float scale_factor, 351 const ui::LatencyInfo& latency_info) { 352 pixel_io_surface_size_ = size; 353 scale_factor_ = scale_factor; 354 dip_io_surface_size_ = gfx::ToFlooredSize( 355 gfx::ScaleSize(pixel_io_surface_size_, 1.0 / scale_factor_)); 356 357 CGLError cgl_error = CGLSetCurrentContext(context_->cgl_context()); 358 if (cgl_error != kCGLNoError) { 359 LOG(ERROR) << "CGLSetCurrentContext error in SetIOSurface: " << cgl_error; 360 return false; 361 } 362 bool result = MapIOSurfaceToTexture(io_surface_handle); 363 CGLSetCurrentContext(0); 364 latency_info_.MergeWith(latency_info); 365 return result; 366 } 367 368 int CompositingIOSurfaceMac::GetRendererID() { 369 GLint current_renderer_id = -1; 370 if (CGLGetParameter(context_->cgl_context(), 371 kCGLCPCurrentRendererID, 372 ¤t_renderer_id) == kCGLNoError) 373 return current_renderer_id & kCGLRendererIDMatchingMask; 374 return -1; 375 } 376 377 bool CompositingIOSurfaceMac::DrawIOSurface( 378 const gfx::Size& window_size, 379 float window_scale_factor, 380 RenderWidgetHostViewFrameSubscriber* frame_subscriber, 381 bool using_core_animation) { 382 bool result = true; 383 384 if (display_link_ == NULL) 385 SetupCVDisplayLink(); 386 387 bool has_io_surface = HasIOSurface(); 388 TRACE_EVENT1("browser", "CompositingIOSurfaceMac::DrawIOSurface", 389 "has_io_surface", has_io_surface); 390 391 gfx::Size pixel_window_size = gfx::ToFlooredSize( 392 gfx::ScaleSize(window_size, window_scale_factor)); 393 glViewport(0, 0, pixel_window_size.width(), pixel_window_size.height()); 394 395 SurfaceQuad quad; 396 quad.set_size(dip_io_surface_size_, pixel_io_surface_size_); 397 398 glMatrixMode(GL_PROJECTION); 399 glLoadIdentity(); 400 401 // Note that the projection keeps things in view units, so the use of 402 // window_size / dip_io_surface_size_ (as opposed to the pixel_ variants) 403 // below is correct. 404 glOrtho(0, window_size.width(), window_size.height(), 0, -1, 1); 405 glMatrixMode(GL_MODELVIEW); 406 glLoadIdentity(); 407 408 glDisable(GL_DEPTH_TEST); 409 glDisable(GL_BLEND); 410 411 if (has_io_surface) { 412 context_->shader_program_cache()->UseBlitProgram(); 413 glActiveTexture(GL_TEXTURE0); 414 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_); 415 416 DrawQuad(quad); 417 418 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); CHECK_AND_SAVE_GL_ERROR(); 419 420 // Fill the resize gutters with white. 421 if (window_size.width() > dip_io_surface_size_.width() || 422 window_size.height() > dip_io_surface_size_.height()) { 423 context_->shader_program_cache()->UseSolidWhiteProgram(); 424 SurfaceQuad filler_quad; 425 if (window_size.width() > dip_io_surface_size_.width()) { 426 // Draw right-side gutter down to the bottom of the window. 427 filler_quad.set_rect(dip_io_surface_size_.width(), 0.0f, 428 window_size.width(), window_size.height()); 429 DrawQuad(filler_quad); 430 } 431 if (window_size.height() > dip_io_surface_size_.height()) { 432 // Draw bottom gutter to the width of the IOSurface. 433 filler_quad.set_rect( 434 0.0f, dip_io_surface_size_.height(), 435 dip_io_surface_size_.width(), window_size.height()); 436 DrawQuad(filler_quad); 437 } 438 } 439 440 // Workaround for issue 158469. Issue a dummy draw call with texture_ not 441 // bound to blit_rgb_sampler_location_, in order to shake all references 442 // to the IOSurface out of the driver. 443 glBegin(GL_TRIANGLES); 444 glEnd(); 445 446 glUseProgram(0); CHECK_AND_SAVE_GL_ERROR(); 447 } else { 448 // Should match the clear color of RenderWidgetHostViewMac. 449 glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 450 glClear(GL_COLOR_BUFFER_BIT); 451 } 452 453 static bool initialized_workaround = false; 454 static bool force_on_workaround = false; 455 static bool force_off_workaround = false; 456 if (!initialized_workaround) { 457 force_on_workaround = CommandLine::ForCurrentProcess()->HasSwitch( 458 switches::kForceGLFinishWorkaround); 459 force_off_workaround = CommandLine::ForCurrentProcess()->HasSwitch( 460 switches::kDisableGpuDriverBugWorkarounds); 461 462 initialized_workaround = true; 463 } 464 465 const bool workaround_needed = 466 IsVendorIntel() && !base::mac::IsOSMountainLionOrLater(); 467 const bool use_glfinish_workaround = 468 (workaround_needed || force_on_workaround) && !force_off_workaround; 469 470 if (use_glfinish_workaround) { 471 TRACE_EVENT0("gpu", "glFinish"); 472 // http://crbug.com/123409 : work around bugs in graphics driver on 473 // MacBook Air with Intel HD graphics, and possibly on other models, 474 // by forcing the graphics pipeline to be completely drained at this 475 // point. 476 // This workaround is not necessary on Mountain Lion. 477 glFinish(); 478 } 479 480 base::Closure copy_done_callback; 481 if (frame_subscriber) { 482 const base::Time present_time = base::Time::Now(); 483 scoped_refptr<media::VideoFrame> frame; 484 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback; 485 if (frame_subscriber->ShouldCaptureFrame(present_time, &frame, &callback)) { 486 copy_done_callback = CopyToVideoFrameWithinContext( 487 gfx::Rect(pixel_io_surface_size_), true, frame, 488 base::Bind(callback, present_time)); 489 } 490 } 491 492 if (!using_core_animation) { 493 CGLError cgl_error = CGLFlushDrawable(context_->cgl_context()); 494 if (cgl_error != kCGLNoError) { 495 LOG(ERROR) << "CGLFlushDrawable error in DrawIOSurface: " << cgl_error; 496 result = false; 497 } 498 } 499 500 latency_info_.swap_timestamp = base::TimeTicks::HighResNow(); 501 RenderWidgetHostImpl::CompositorFrameDrawn(latency_info_); 502 latency_info_.Clear(); 503 504 // Try to finish previous copy requests after flush to get better pipelining. 505 std::vector<base::Closure> copy_done_callbacks; 506 CheckIfAllCopiesAreFinishedWithinContext(false, ©_done_callbacks); 507 508 // Check if any of the drawing calls result in an error. 509 GetAndSaveGLError(); 510 if (gl_error_ != GL_NO_ERROR) { 511 LOG(ERROR) << "GL error in DrawIOSurface: " << gl_error_; 512 result = false; 513 } 514 515 if (!using_core_animation) 516 CGLSetCurrentContext(0); 517 518 if (!copy_done_callback.is_null()) 519 copy_done_callbacks.push_back(copy_done_callback); 520 for (size_t i = 0; i < copy_done_callbacks.size(); ++i) 521 copy_done_callbacks[i].Run(); 522 523 StartOrContinueDisplayLink(); 524 525 return result; 526 } 527 528 void CompositingIOSurfaceMac::CopyTo( 529 const gfx::Rect& src_pixel_subrect, 530 const gfx::Size& dst_pixel_size, 531 const base::Callback<void(bool, const SkBitmap&)>& callback) { 532 scoped_ptr<SkBitmap> output(new SkBitmap()); 533 output->setConfig(SkBitmap::kARGB_8888_Config, 534 dst_pixel_size.width(), dst_pixel_size.height()); 535 if (!output->allocPixels()) { 536 DLOG(ERROR) << "Failed to allocate SkBitmap pixels!"; 537 callback.Run(false, *output); 538 return; 539 } 540 DCHECK_EQ(output->rowBytesAsPixels(), dst_pixel_size.width()) 541 << "Stride is required to be equal to width for GPU readback."; 542 output->setIsOpaque(true); 543 544 CGLSetCurrentContext(context_->cgl_context()); 545 const base::Closure copy_done_callback = CopyToSelectedOutputWithinContext( 546 src_pixel_subrect, gfx::Rect(dst_pixel_size), false, 547 output.get(), NULL, 548 base::Bind(&ReverseArgumentOrder, callback, base::Passed(&output))); 549 CGLSetCurrentContext(0); 550 if (!copy_done_callback.is_null()) 551 copy_done_callback.Run(); 552 } 553 554 void CompositingIOSurfaceMac::CopyToVideoFrame( 555 const gfx::Rect& src_pixel_subrect, 556 const scoped_refptr<media::VideoFrame>& target, 557 const base::Callback<void(bool)>& callback) { 558 CGLSetCurrentContext(context_->cgl_context()); 559 const base::Closure copy_done_callback = CopyToVideoFrameWithinContext( 560 src_pixel_subrect, false, target, callback); 561 CGLSetCurrentContext(0); 562 if (!copy_done_callback.is_null()) 563 copy_done_callback.Run(); 564 } 565 566 base::Closure CompositingIOSurfaceMac::CopyToVideoFrameWithinContext( 567 const gfx::Rect& src_pixel_subrect, 568 bool called_within_draw, 569 const scoped_refptr<media::VideoFrame>& target, 570 const base::Callback<void(bool)>& callback) { 571 gfx::Rect region_in_frame = media::ComputeLetterboxRegion( 572 gfx::Rect(target->coded_size()), src_pixel_subrect.size()); 573 // Make coordinates and sizes even because we letterbox in YUV space right 574 // now (see CopyRGBToVideoFrame). They need to be even for the UV samples to 575 // line up correctly. 576 region_in_frame = gfx::Rect(region_in_frame.x() & ~1, 577 region_in_frame.y() & ~1, 578 region_in_frame.width() & ~1, 579 region_in_frame.height() & ~1); 580 DCHECK_LE(region_in_frame.right(), target->coded_size().width()); 581 DCHECK_LE(region_in_frame.bottom(), target->coded_size().height()); 582 583 return CopyToSelectedOutputWithinContext( 584 src_pixel_subrect, region_in_frame, called_within_draw, 585 NULL, target, callback); 586 } 587 588 bool CompositingIOSurfaceMac::MapIOSurfaceToTexture( 589 uint64 io_surface_handle) { 590 if (io_surface_.get() && io_surface_handle == io_surface_handle_) 591 return true; 592 593 TRACE_EVENT0("browser", "CompositingIOSurfaceMac::MapIOSurfaceToTexture"); 594 UnrefIOSurfaceWithContextCurrent(); 595 596 io_surface_.reset(io_surface_support_->IOSurfaceLookup( 597 static_cast<uint32>(io_surface_handle))); 598 // Can fail if IOSurface with that ID was already released by the gpu 599 // process. 600 if (!io_surface_) { 601 UnrefIOSurfaceWithContextCurrent(); 602 return false; 603 } 604 605 io_surface_handle_ = io_surface_handle; 606 607 // Actual IOSurface size is rounded up to reduce reallocations during window 608 // resize. Get the actual size to properly map the texture. 609 gfx::Size rounded_size( 610 io_surface_support_->IOSurfaceGetWidth(io_surface_), 611 io_surface_support_->IOSurfaceGetHeight(io_surface_)); 612 613 glGenTextures(1, &texture_); 614 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_); 615 glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 616 glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 617 CHECK_AND_SAVE_GL_ERROR(); 618 GLuint plane = 0; 619 CGLError cgl_error = io_surface_support_->CGLTexImageIOSurface2D( 620 context_->cgl_context(), 621 GL_TEXTURE_RECTANGLE_ARB, 622 GL_RGBA, 623 rounded_size.width(), 624 rounded_size.height(), 625 GL_BGRA, 626 GL_UNSIGNED_INT_8_8_8_8_REV, 627 io_surface_.get(), 628 plane); 629 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); 630 if (cgl_error != kCGLNoError) { 631 LOG(ERROR) << "CGLTexImageIOSurface2D: " << cgl_error; 632 UnrefIOSurfaceWithContextCurrent(); 633 return false; 634 } 635 GetAndSaveGLError(); 636 if (gl_error_ != GL_NO_ERROR) { 637 LOG(ERROR) << "GL error in MapIOSurfaceToTexture: " << gl_error_; 638 UnrefIOSurfaceWithContextCurrent(); 639 return false; 640 } 641 return true; 642 } 643 644 void CompositingIOSurfaceMac::UnrefIOSurface() { 645 CGLSetCurrentContext(context_->cgl_context()); 646 UnrefIOSurfaceWithContextCurrent(); 647 CGLSetCurrentContext(0); 648 } 649 650 void CompositingIOSurfaceMac::DrawQuad(const SurfaceQuad& quad) { 651 glEnableClientState(GL_VERTEX_ARRAY); CHECK_AND_SAVE_GL_ERROR(); 652 glEnableClientState(GL_TEXTURE_COORD_ARRAY); CHECK_AND_SAVE_GL_ERROR(); 653 654 glVertexPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].x_); 655 glTexCoordPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].tx_); 656 glDrawArrays(GL_QUADS, 0, 4); CHECK_AND_SAVE_GL_ERROR(); 657 658 glDisableClientState(GL_VERTEX_ARRAY); 659 glDisableClientState(GL_TEXTURE_COORD_ARRAY); 660 } 661 662 bool CompositingIOSurfaceMac::IsVendorIntel() { 663 GLint screen; 664 CGLGetVirtualScreen(context_->cgl_context(), &screen); 665 if (screen != screen_) 666 initialized_is_intel_ = false; 667 screen_ = screen; 668 if (!initialized_is_intel_) { 669 is_intel_ = strstr(reinterpret_cast<const char*>(glGetString(GL_VENDOR)), 670 "Intel") != NULL; 671 initialized_is_intel_ = true; 672 } 673 return is_intel_; 674 } 675 676 void CompositingIOSurfaceMac::UnrefIOSurfaceWithContextCurrent() { 677 if (texture_) { 678 glDeleteTextures(1, &texture_); 679 texture_ = 0; 680 } 681 682 io_surface_.reset(); 683 684 // Forget the ID, because even if it is still around when we want to use it 685 // again, OSX may have reused the same ID for a new tab and we don't want to 686 // blit random tab contents. 687 io_surface_handle_ = 0; 688 } 689 690 void CompositingIOSurfaceMac::DisplayLinkTick(CVDisplayLinkRef display_link, 691 const CVTimeStamp* time) { 692 TRACE_EVENT0("gpu", "CompositingIOSurfaceMac::DisplayLinkTick"); 693 base::AutoLock lock(lock_); 694 CalculateVsyncParametersLockHeld(time); 695 } 696 697 void CompositingIOSurfaceMac::CalculateVsyncParametersLockHeld( 698 const CVTimeStamp* time) { 699 lock_.AssertAcquired(); 700 vsync_interval_numerator_ = static_cast<uint32>(time->videoRefreshPeriod); 701 vsync_interval_denominator_ = time->videoTimeScale; 702 // Verify that videoRefreshPeriod is 32 bits. 703 DCHECK((time->videoRefreshPeriod & ~0xffffFFFFull) == 0ull); 704 705 vsync_timebase_ = 706 base::TimeTicks::FromInternalValue(time->hostTime / 1000); 707 } 708 709 void CompositingIOSurfaceMac::StartOrContinueDisplayLink() { 710 if (display_link_ == NULL) 711 return; 712 713 if (!CVDisplayLinkIsRunning(display_link_)) { 714 CVDisplayLinkStart(display_link_); 715 } 716 display_link_stop_timer_.Reset(); 717 } 718 719 void CompositingIOSurfaceMac::StopDisplayLink() { 720 if (display_link_ == NULL) 721 return; 722 723 if (CVDisplayLinkIsRunning(display_link_)) 724 CVDisplayLinkStop(display_link_); 725 } 726 727 bool CompositingIOSurfaceMac::IsAsynchronousReadbackSupported() { 728 // Using PBO crashes on Intel drivers but not on newer Mountain Lion 729 // systems. See bug http://crbug.com/152225. 730 const bool forced_synchronous = CommandLine::ForCurrentProcess()->HasSwitch( 731 switches::kForceSynchronousGLReadPixels); 732 return (!forced_synchronous && 733 HasAppleFenceExtension() && 734 HasPixelBufferObjectExtension() && 735 (base::mac::IsOSMountainLionOrLater() || !IsVendorIntel())); 736 } 737 738 base::Closure CompositingIOSurfaceMac::CopyToSelectedOutputWithinContext( 739 const gfx::Rect& src_pixel_subrect, 740 const gfx::Rect& dst_pixel_rect, 741 bool called_within_draw, 742 const SkBitmap* bitmap_output, 743 const scoped_refptr<media::VideoFrame>& video_frame_output, 744 const base::Callback<void(bool)>& done_callback) { 745 DCHECK_NE(bitmap_output != NULL, video_frame_output.get() != NULL); 746 DCHECK(!done_callback.is_null()); 747 748 // SWIZZLE_RGBA_FOR_ASYNC_READPIXELS workaround: Fall-back to synchronous 749 // readback for SkBitmap output since the Blit shader program doesn't support 750 // switchable output formats. 751 const bool require_sync_copy_for_workaround = bitmap_output && 752 context_->shader_program_cache()->rgb_to_yv12_output_format() == GL_RGBA; 753 const bool async_copy = !require_sync_copy_for_workaround && 754 IsAsynchronousReadbackSupported(); 755 TRACE_EVENT2( 756 "browser", "CompositingIOSurfaceMac::CopyToSelectedOutputWithinContext", 757 "output", bitmap_output ? "SkBitmap (ARGB)" : "VideoFrame (YV12)", 758 "async_readback", async_copy); 759 760 const gfx::Rect src_rect = IntersectWithIOSurface(src_pixel_subrect); 761 if (src_rect.IsEmpty() || dst_pixel_rect.IsEmpty()) 762 return base::Bind(done_callback, false); 763 764 CopyContext* copy_context; 765 if (copy_context_pool_.empty()) { 766 // Limit the maximum number of simultaneous copies to two. Rationale: 767 // Really, only one should ever be in-progress at a time, as we should 768 // depend on the speed of the hardware to rate-limit the copying naturally. 769 // In the asynchronous read-back case, the one currently in-flight copy is 770 // highly likely to have finished by this point (i.e., it's just waiting for 771 // us to make a glMapBuffer() call). Therefore, we allow a second copy to 772 // be started here. 773 if (copy_requests_.size() >= 2) 774 return base::Bind(done_callback, false); 775 copy_context = new CopyContext(context_); 776 } else { 777 copy_context = copy_context_pool_.back(); 778 copy_context_pool_.pop_back(); 779 } 780 781 if (!HasIOSurface()) 782 return base::Bind(done_callback, false); 783 784 // Send transform commands to the GPU. 785 copy_context->num_outputs = 0; 786 if (bitmap_output) { 787 if (copy_context->transformer->ResizeBilinear( 788 texture_, src_rect, dst_pixel_rect.size(), 789 ©_context->output_textures[0])) { 790 copy_context->output_readback_format = GL_BGRA; 791 copy_context->num_outputs = 1; 792 copy_context->output_texture_sizes[0] = dst_pixel_rect.size(); 793 } 794 } else { 795 if (copy_context->transformer->TransformRGBToYV12( 796 texture_, src_rect, dst_pixel_rect.size(), 797 ©_context->output_textures[0], 798 ©_context->output_textures[1], 799 ©_context->output_textures[2], 800 ©_context->output_texture_sizes[0], 801 ©_context->output_texture_sizes[1])) { 802 copy_context->output_readback_format = 803 context_->shader_program_cache()->rgb_to_yv12_output_format(); 804 copy_context->num_outputs = 3; 805 copy_context->output_texture_sizes[2] = 806 copy_context->output_texture_sizes[1]; 807 } 808 } 809 if (!copy_context->num_outputs) 810 return base::Bind(done_callback, false); 811 812 // In the asynchronous case, issue commands to the GPU and return a null 813 // closure here. In the synchronous case, perform a blocking readback and 814 // return a callback to be run outside the CGL context to indicate success. 815 if (async_copy) { 816 copy_context->done_callback = done_callback; 817 AsynchronousReadbackForCopy( 818 dst_pixel_rect, called_within_draw, copy_context, bitmap_output, 819 video_frame_output); 820 copy_requests_.push_back(copy_context); 821 if (!finish_copy_timer_.IsRunning()) 822 finish_copy_timer_.Reset(); 823 return base::Closure(); 824 } else { 825 const bool success = SynchronousReadbackForCopy( 826 dst_pixel_rect, copy_context, bitmap_output, video_frame_output); 827 return base::Bind(done_callback, success); 828 } 829 } 830 831 void CompositingIOSurfaceMac::AsynchronousReadbackForCopy( 832 const gfx::Rect& dst_pixel_rect, 833 bool called_within_draw, 834 CopyContext* copy_context, 835 const SkBitmap* bitmap_output, 836 const scoped_refptr<media::VideoFrame>& video_frame_output) { 837 copy_context->PrepareForAsynchronousReadback(); 838 839 // Copy the textures to their corresponding PBO. 840 for (int i = 0; i < copy_context->num_outputs; ++i) { 841 TRACE_EVENT1( 842 "browser", "CompositingIOSurfaceMac::AsynchronousReadbackForCopy", 843 "plane", i); 844 845 // Attach the output texture to the FBO. 846 glBindFramebufferEXT( 847 GL_READ_FRAMEBUFFER_EXT, copy_context->frame_buffers[i]); 848 glFramebufferTexture2DEXT( 849 GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, 850 GL_TEXTURE_RECTANGLE_ARB, copy_context->output_textures[i], 0); 851 DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) == 852 GL_FRAMEBUFFER_COMPLETE_EXT); 853 854 // Create a PBO and issue an asynchronous read-back. 855 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context->pixel_buffers[i]); 856 CHECK_AND_SAVE_GL_ERROR(); 857 glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, 858 copy_context->output_texture_sizes[i].GetArea() * 4, 859 NULL, GL_STREAM_READ_ARB); 860 CHECK_AND_SAVE_GL_ERROR(); 861 glReadPixels(0, 0, 862 copy_context->output_texture_sizes[i].width(), 863 copy_context->output_texture_sizes[i].height(), 864 copy_context->output_readback_format, 865 GL_UNSIGNED_INT_8_8_8_8_REV, 0); 866 CHECK_AND_SAVE_GL_ERROR(); 867 } 868 869 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_AND_SAVE_GL_ERROR(); 870 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); CHECK_AND_SAVE_GL_ERROR(); 871 872 glSetFenceAPPLE(copy_context->fence); CHECK_GL_ERROR(); 873 copy_context->cycles_elapsed = 0; 874 875 // When this asynchronous copy happens in a draw operaton there is no need 876 // to explicitly flush because there will be a swap buffer and this flush 877 // hurts performance. 878 if (!called_within_draw) { 879 glFlush(); CHECK_AND_SAVE_GL_ERROR(); 880 } 881 882 copy_context->map_buffer_callback = bitmap_output ? 883 base::Bind(&MapBufferToSkBitmap, bitmap_output) : 884 base::Bind(&MapBufferToVideoFrame, video_frame_output, dst_pixel_rect); 885 } 886 887 void CompositingIOSurfaceMac::CheckIfAllCopiesAreFinished( 888 bool block_until_finished) { 889 std::vector<base::Closure> done_callbacks; 890 CGLSetCurrentContext(context_->cgl_context()); 891 CheckIfAllCopiesAreFinishedWithinContext( 892 block_until_finished, &done_callbacks); 893 CGLSetCurrentContext(0); 894 for (size_t i = 0; i < done_callbacks.size(); ++i) 895 done_callbacks[i].Run(); 896 } 897 898 void CompositingIOSurfaceMac::CheckIfAllCopiesAreFinishedWithinContext( 899 bool block_until_finished, 900 std::vector<base::Closure>* done_callbacks) { 901 while (!copy_requests_.empty()) { 902 CopyContext* const copy_context = copy_requests_.front(); 903 904 if (copy_context->fence && !glTestFenceAPPLE(copy_context->fence)) { 905 CHECK_AND_SAVE_GL_ERROR(); 906 // Doing a glFinishFenceAPPLE can cause transparent window flashes when 907 // switching tabs, so only do it when required. 908 if (block_until_finished) { 909 glFinishFenceAPPLE(copy_context->fence); 910 CHECK_AND_SAVE_GL_ERROR(); 911 } else if (copy_context->cycles_elapsed < kFinishCopyRetryCycles) { 912 ++copy_context->cycles_elapsed; 913 // This copy has not completed there is no need to test subsequent 914 // requests. 915 break; 916 } 917 } 918 CHECK_AND_SAVE_GL_ERROR(); 919 920 bool success = true; 921 for (int i = 0; success && i < copy_context->num_outputs; ++i) { 922 TRACE_EVENT1( 923 "browser", 924 "CompositingIOSurfaceMac::CheckIfAllCopiesAreFinishedWithinContext", 925 "plane", i); 926 927 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context->pixel_buffers[i]); 928 CHECK_AND_SAVE_GL_ERROR(); 929 930 void* buf = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB); 931 CHECK_AND_SAVE_GL_ERROR(); 932 success &= copy_context->map_buffer_callback.Run(buf, i); 933 glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); CHECK_AND_SAVE_GL_ERROR(); 934 } 935 copy_context->map_buffer_callback.Reset(); 936 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_AND_SAVE_GL_ERROR(); 937 938 copy_requests_.pop_front(); 939 done_callbacks->push_back(base::Bind(copy_context->done_callback, success)); 940 copy_context->done_callback.Reset(); 941 copy_context_pool_.push_back(copy_context); 942 } 943 if (copy_requests_.empty()) 944 finish_copy_timer_.Stop(); 945 946 CHECK(copy_requests_.empty() || !block_until_finished); 947 } 948 949 bool CompositingIOSurfaceMac::SynchronousReadbackForCopy( 950 const gfx::Rect& dst_pixel_rect, 951 CopyContext* copy_context, 952 const SkBitmap* bitmap_output, 953 const scoped_refptr<media::VideoFrame>& video_frame_output) { 954 bool success = true; 955 copy_context->PrepareReadbackFramebuffers(); 956 for (int i = 0; i < copy_context->num_outputs; ++i) { 957 TRACE_EVENT1( 958 "browser", "CompositingIOSurfaceMac::SynchronousReadbackForCopy", 959 "plane", i); 960 961 // Attach the output texture to the FBO. 962 glBindFramebufferEXT( 963 GL_READ_FRAMEBUFFER_EXT, copy_context->frame_buffers[i]); 964 glFramebufferTexture2DEXT( 965 GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, 966 GL_TEXTURE_RECTANGLE_ARB, copy_context->output_textures[i], 0); 967 DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) == 968 GL_FRAMEBUFFER_COMPLETE_EXT); 969 970 // Blocking read-back of pixels from textures. 971 void* buf; 972 // When data must be transferred into a VideoFrame one scanline at a time, 973 // it is necessary to allocate a separate buffer for glReadPixels() that can 974 // be populated one-shot. 975 // 976 // TODO(miu): Don't keep allocating/deleting this buffer for every frame. 977 // Keep it cached, allocated on first use. 978 scoped_ptr<uint32[]> temp_readback_buffer; 979 if (bitmap_output) { 980 // The entire SkBitmap is populated, never a region within. So, read the 981 // texture directly into the bitmap's pixel memory. 982 buf = bitmap_output->getPixels(); 983 } else { 984 // Optimization: If the VideoFrame is letterboxed (not pillarboxed), and 985 // its stride is equal to the stride of the data being read back, then 986 // readback directly into the VideoFrame's buffer to save a round of 987 // memcpy'ing. 988 // 989 // TODO(miu): Move these calculations into VideoFrame (need a CalcOffset() 990 // method). http://crbug.com/219779 991 const int src_stride = copy_context->output_texture_sizes[i].width() * 4; 992 const int dst_stride = video_frame_output->stride(i); 993 if (src_stride == dst_stride && dst_pixel_rect.x() == 0) { 994 const int y_offset = dst_pixel_rect.y() / (i == 0 ? 1 : 2); 995 buf = video_frame_output->data(i) + y_offset * dst_stride; 996 } else { 997 // Create and readback into a temporary buffer because the data must be 998 // transferred to VideoFrame's pixel memory one scanline at a time. 999 temp_readback_buffer.reset( 1000 new uint32[copy_context->output_texture_sizes[i].GetArea()]); 1001 buf = temp_readback_buffer.get(); 1002 } 1003 } 1004 glReadPixels(0, 0, 1005 copy_context->output_texture_sizes[i].width(), 1006 copy_context->output_texture_sizes[i].height(), 1007 copy_context->output_readback_format, 1008 GL_UNSIGNED_INT_8_8_8_8_REV, buf); 1009 CHECK_AND_SAVE_GL_ERROR(); 1010 if (video_frame_output.get()) { 1011 if (!temp_readback_buffer) { 1012 // Apply letterbox black-out around view region. 1013 media::LetterboxYUV(video_frame_output.get(), dst_pixel_rect); 1014 } else { 1015 // Copy from temporary buffer and fully render the VideoFrame. 1016 success &= MapBufferToVideoFrame(video_frame_output, dst_pixel_rect, 1017 temp_readback_buffer.get(), i); 1018 } 1019 } 1020 } 1021 1022 glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); CHECK_AND_SAVE_GL_ERROR(); 1023 copy_context_pool_.push_back(copy_context); 1024 return success; 1025 } 1026 1027 void CompositingIOSurfaceMac::FailAllCopies() { 1028 for (size_t i = 0; i < copy_requests_.size(); ++i) { 1029 copy_requests_[i]->map_buffer_callback.Reset(); 1030 1031 base::Callback<void(bool)>& done_callback = 1032 copy_requests_[i]->done_callback; 1033 if (!done_callback.is_null()) { 1034 done_callback.Run(false); 1035 done_callback.Reset(); 1036 } 1037 } 1038 } 1039 1040 void CompositingIOSurfaceMac::DestroyAllCopyContextsWithinContext() { 1041 // Move all in-flight copies, if any, back into the pool. Then, destroy all 1042 // the CopyContexts in the pool. 1043 copy_context_pool_.insert(copy_context_pool_.end(), 1044 copy_requests_.begin(), copy_requests_.end()); 1045 copy_requests_.clear(); 1046 while (!copy_context_pool_.empty()) { 1047 scoped_ptr<CopyContext> copy_context(copy_context_pool_.back()); 1048 copy_context_pool_.pop_back(); 1049 copy_context->ReleaseCachedGLObjects(); 1050 } 1051 } 1052 1053 gfx::Rect CompositingIOSurfaceMac::IntersectWithIOSurface( 1054 const gfx::Rect& rect) const { 1055 return gfx::IntersectRects(rect, 1056 gfx::ToEnclosingRect(gfx::Rect(pixel_io_surface_size_))); 1057 } 1058 1059 GLenum CompositingIOSurfaceMac::GetAndSaveGLError() { 1060 GLenum gl_error = glGetError(); 1061 if (gl_error_ == GL_NO_ERROR) 1062 gl_error_ = gl_error; 1063 return gl_error; 1064 } 1065 1066 } // namespace content 1067