1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/common/gpu/image_transport_surface_overlay_mac.h"
8 #include <IOSurface/IOSurface.h>
9 #include <OpenGL/CGLRenderers.h>
10 #include <OpenGL/CGLTypes.h>
11 #include <OpenGL/gl.h>
13 // This type consistently causes problem on Mac, and needs to be dealt with
15 // http://crbug.com/517208
16 #ifndef GL_OES_EGL_image
17 typedef void* GLeglImageOES;
20 #include "base/command_line.h"
21 #include "base/mac/scoped_cftyperef.h"
22 #include "content/common/gpu/gpu_messages.h"
23 #include "ui/accelerated_widget_mac/io_surface_context.h"
24 #include "ui/accelerated_widget_mac/surface_handle_types.h"
25 #include "ui/base/cocoa/animation_utils.h"
26 #include "ui/base/cocoa/remote_layer_api.h"
27 #include "ui/base/ui_base_switches.h"
28 #include "ui/gfx/geometry/dip_util.h"
29 #include "ui/gl/gl_context.h"
30 #include "ui/gl/gl_fence.h"
31 #include "ui/gl/gl_image_io_surface.h"
32 #include "ui/gl/gpu_switching_manager.h"
33 #include "ui/gl/scoped_api.h"
34 #include "ui/gl/scoped_cgl.h"
38 // Don't let a frame draw until 5% of the way through the next vsync interval
39 // after the call to SwapBuffers. This slight offset is to ensure that skew
40 // doesn't result in the frame being presented to the previous vsync interval.
41 const double kVSyncIntervalFractionForEarliestDisplay = 0.05;
43 // After doing a glFlush and putting in a fence in SwapBuffers, post a task to
44 // query the fence 50% of the way through the next vsync interval. If we are
45 // trying to animate smoothly, then want to query the fence at the next
46 // SwapBuffers. For this reason we schedule the callback for a long way into
48 const double kVSyncIntervalFractionForDisplayCallback = 0.5;
50 // If swaps arrive regularly and nearly at the vsync rate, then attempt to
51 // make animation smooth (each frame is shown for one vsync interval) by sending
52 // them to the window server only when their GL work completes. If frames are
53 // not coming in with each vsync, then just throw them at the window server as
55 const double kMaximumVSyncsBetweenSwapsForSmoothAnimation = 1.5;
57 void CheckGLErrors(const char* msg) {
59 while ((gl_error = glGetError()) != GL_NO_ERROR) {
60 LOG(ERROR) << "OpenGL error hit " << msg << ": " << gl_error;
64 void IOSurfaceContextNoOp(scoped_refptr<ui::IOSurfaceContext>) {
69 @interface CALayer(Private)
70 -(void)setContentsChanged;
75 class ImageTransportSurfaceOverlayMac::OverlayPlane {
79 ROOT_PARTIAL_DAMAGE = 1,
86 base::ScopedCFTypeRef<IOSurfaceRef> io_surface,
87 gfx::Rect dip_frame_rect,
88 gfx::RectF contents_rect)
89 : type(type), z_order(z_order), io_surface(io_surface),
90 dip_frame_rect(dip_frame_rect), contents_rect(contents_rect) {}
93 static bool Compare(const linked_ptr<OverlayPlane>& a,
94 const linked_ptr<OverlayPlane>& b) {
95 // Sort by z_order first.
96 if (a->z_order < b->z_order)
98 if (a->z_order > b->z_order)
100 // Then ensure that the root partial damage is after the root.
101 if (a->type < b->type)
103 if (a->type > b->type)
106 if (a->dip_frame_rect.x() < b->dip_frame_rect.x())
108 if (a->dip_frame_rect.x() > b->dip_frame_rect.x())
111 if (a->dip_frame_rect.y() < b->dip_frame_rect.y())
113 if (a->dip_frame_rect.y() > b->dip_frame_rect.y())
121 // The IOSurface to set the CALayer's contents to. This can be nil for the
122 // root layer, indicating that the layer's content has not been damaged since
123 // the last time it was set.
124 base::ScopedCFTypeRef<IOSurfaceRef> io_surface;
125 const gfx::Rect dip_frame_rect;
126 const gfx::RectF contents_rect;
129 class ImageTransportSurfaceOverlayMac::PendingSwap {
132 ~PendingSwap() { DCHECK(!gl_fence); }
134 gfx::Size pixel_size;
137 std::vector<linked_ptr<OverlayPlane>> overlay_planes;
138 std::vector<ui::LatencyInfo> latency_info;
140 // A fence object, and the CGL context it was issued in.
141 base::ScopedTypeRef<CGLContextObj> cgl_context;
142 scoped_ptr<gfx::GLFence> gl_fence;
144 // The earliest time that this frame may be drawn. A frame is not allowed
145 // to draw until a fraction of the way through the vsync interval after its
146 // This extra latency is to allow wiggle-room for smoothness.
147 base::TimeTicks earliest_display_time_allowed;
149 // The time that this will wake up and draw, if a following swap does not
150 // cause it to draw earlier.
151 base::TimeTicks target_display_time;
154 ImageTransportSurfaceOverlayMac::ImageTransportSurfaceOverlayMac(
155 GpuChannelManager* manager,
156 GpuCommandBufferStub* stub,
157 gfx::PluginWindowHandle handle)
158 : scale_factor_(1), gl_renderer_id_(0), vsync_parameters_valid_(false),
159 display_pending_swap_timer_(true, false), weak_factory_(this) {
160 helper_.reset(new ImageTransportHelper(this, manager, stub, handle));
161 ui::GpuSwitchingManager::GetInstance()->AddObserver(this);
164 ImageTransportSurfaceOverlayMac::~ImageTransportSurfaceOverlayMac() {
165 ui::GpuSwitchingManager::GetInstance()->RemoveObserver(this);
169 bool ImageTransportSurfaceOverlayMac::Initialize() {
170 if (!helper_->Initialize())
173 // Create the CAContext to send this to the GPU process, and the layer for
175 CGSConnectionID connection_id = CGSMainConnectionID();
177 [[CAContext contextWithCGSConnection:connection_id options:@{}] retain]);
178 layer_.reset([[CALayer alloc] init]);
179 [layer_ setGeometryFlipped:YES];
180 [layer_ setOpaque:YES];
181 [ca_context_ setLayer:layer_];
185 void ImageTransportSurfaceOverlayMac::Destroy() {
186 DisplayAndClearAllPendingSwaps();
189 bool ImageTransportSurfaceOverlayMac::IsOffscreen() {
193 void ImageTransportSurfaceOverlayMac::ScheduleOverlayPlaneForPartialDamage(
194 const gfx::Rect& this_damage_pixel_rect) {
195 // Find the root plane. If none is present, then we're hosed.
196 linked_ptr<OverlayPlane> root_plane;
197 for (linked_ptr<OverlayPlane>& plane : pending_overlay_planes_) {
198 if (plane->type == OverlayPlane::ROOT) {
203 if (!root_plane.get())
206 gfx::Rect this_damage_dip_rect = gfx::ConvertRectToDIP(
207 scale_factor_, this_damage_pixel_rect);
209 for (const linked_ptr<OverlayPlane>& plane : pending_overlay_planes_) {
210 if (plane->type == OverlayPlane::OVERLAY) {
211 this_damage_dip_rect.Subtract(plane->dip_frame_rect);
212 accumulated_damage_dip_rect_.Subtract(plane->dip_frame_rect);
216 if (this_damage_dip_rect.IsEmpty()) {
217 // Keep existing root layer unchanged.
218 root_plane->io_surface.reset();
219 if (!accumulated_damage_dip_rect_.IsEmpty()) {
220 // Keep existing partial damage layer unchanged.
221 pending_overlay_planes_.push_back(linked_ptr<OverlayPlane>(
222 new OverlayPlane(OverlayPlane::ROOT_PARTIAL_DAMAGE, 0,
223 base::ScopedCFTypeRef<IOSurfaceRef>(),
224 accumulated_damage_dip_rect_, gfx::Rect())));
229 // Grow the partial damage rect to include the new damage.
230 accumulated_damage_dip_rect_.Union(this_damage_dip_rect);
232 if (accumulated_damage_dip_rect_ == root_plane->dip_frame_rect) {
233 accumulated_damage_dip_rect_ = gfx::Rect();
237 // Compute the fraction of the accumulated partial damage rect that has been
238 // damaged. If this gets too small (<75%), just re-damage the full window,
239 // so we can re-create a smaller partial damage layer next frame.
240 const double kMinimumFractionOfPartialDamage = 0.75;
241 double fraction_of_damage =
242 this_damage_dip_rect.size().GetArea() / static_cast<double>(
243 accumulated_damage_dip_rect_.size().GetArea());
244 if (fraction_of_damage <= kMinimumFractionOfPartialDamage) {
245 // Early-out if we decided to damage the full window.
246 accumulated_damage_dip_rect_ = gfx::Rect();
250 // Create a new overlay plane for the partial damage, and un-set the root
251 // plane's damage by un-setting its IOSurface.
252 gfx::RectF contents_rect = gfx::RectF(accumulated_damage_dip_rect_);
253 contents_rect.Scale(1. / root_plane->dip_frame_rect.width(),
254 1. / root_plane->dip_frame_rect.height());
255 pending_overlay_planes_.push_back(linked_ptr<OverlayPlane>(new OverlayPlane(
256 OverlayPlane::ROOT_PARTIAL_DAMAGE, 0, root_plane->io_surface,
257 accumulated_damage_dip_rect_, contents_rect)));
259 root_plane->io_surface.reset();
262 gfx::SwapResult ImageTransportSurfaceOverlayMac::SwapBuffersInternal() {
263 TRACE_EVENT0("gpu", "ImageTransportSurfaceOverlayMac::SwapBuffersInternal");
265 // Use the same concept of 'now' for the entire function. The duration of
266 // this function only affect the result if this function lasts across a vsync
267 // boundary, in which case smooth animation is out the window anyway.
268 const base::TimeTicks now = base::TimeTicks::Now();
270 // Decide if the frame should be drawn immediately, or if we should wait until
271 // its work finishes before drawing immediately.
272 bool display_immediately = false;
273 if (vsync_parameters_valid_ &&
274 now - last_swap_time_ >
275 kMaximumVSyncsBetweenSwapsForSmoothAnimation * vsync_interval_) {
276 display_immediately = true;
278 last_swap_time_ = now;
280 // If the previous swap is ready to display, do it before flushing the
281 // new swap. It is desirable to always be hitting this path when trying to
282 // animate smoothly with vsync.
283 if (!pending_swaps_.empty()) {
284 if (IsFirstPendingSwapReadyToDisplay(now))
285 DisplayFirstPendingSwapImmediately();
288 // The remainder of the function will populate the PendingSwap structure and
290 linked_ptr<PendingSwap> new_swap(new PendingSwap);
291 new_swap->pixel_size = pixel_size_;
292 new_swap->scale_factor = scale_factor_;
293 new_swap->overlay_planes.swap(pending_overlay_planes_);
294 new_swap->latency_info.swap(latency_info_);
296 // A flush is required to ensure that all content appears in the layer.
298 gfx::ScopedSetGLToRealGLApi scoped_set_gl_api;
299 TRACE_EVENT0("gpu", "ImageTransportSurfaceOverlayMac::glFlush");
300 CheckGLErrors("before flushing frame");
301 new_swap->cgl_context.reset(CGLGetCurrentContext(),
302 base::scoped_policy::RETAIN);
303 if (gfx::GLFence::IsSupported() && !display_immediately)
304 new_swap->gl_fence.reset(gfx::GLFence::Create());
307 CheckGLErrors("while flushing frame");
310 // Compute the deadlines for drawing this frame.
311 if (display_immediately) {
312 new_swap->earliest_display_time_allowed = now;
313 new_swap->target_display_time = now;
315 new_swap->earliest_display_time_allowed =
316 GetNextVSyncTimeAfter(now, kVSyncIntervalFractionForEarliestDisplay);
317 new_swap->target_display_time =
318 GetNextVSyncTimeAfter(now, kVSyncIntervalFractionForDisplayCallback);
321 pending_swaps_.push_back(new_swap);
322 if (display_immediately)
323 DisplayFirstPendingSwapImmediately();
325 PostCheckPendingSwapsCallbackIfNeeded(now);
326 return gfx::SwapResult::SWAP_ACK;
329 bool ImageTransportSurfaceOverlayMac::IsFirstPendingSwapReadyToDisplay(
330 const base::TimeTicks& now) {
331 DCHECK(!pending_swaps_.empty());
332 linked_ptr<PendingSwap> swap = pending_swaps_.front();
334 // Frames are disallowed from drawing until the vsync interval after their
336 if (now < swap->earliest_display_time_allowed)
339 // If we've passed that marker, then wait for the work behind the fence to
341 if (swap->gl_fence) {
342 gfx::ScopedSetGLToRealGLApi scoped_set_gl_api;
343 gfx::ScopedCGLSetCurrentContext scoped_set_current(swap->cgl_context);
345 CheckGLErrors("before waiting on fence");
346 if (!swap->gl_fence->HasCompleted()) {
347 TRACE_EVENT0("gpu", "ImageTransportSurfaceOverlayMac::ClientWait");
348 swap->gl_fence->ClientWait();
350 swap->gl_fence.reset();
351 CheckGLErrors("after waiting on fence");
356 void ImageTransportSurfaceOverlayMac::DisplayFirstPendingSwapImmediately() {
358 "ImageTransportSurfaceOverlayMac::DisplayFirstPendingSwapImmediately");
359 DCHECK(!pending_swaps_.empty());
360 linked_ptr<PendingSwap> swap = pending_swaps_.front();
362 // If there is a fence for this object, delete it.
363 if (swap->gl_fence) {
364 gfx::ScopedSetGLToRealGLApi scoped_set_gl_api;
365 gfx::ScopedCGLSetCurrentContext scoped_set_current(swap->cgl_context);
367 CheckGLErrors("before deleting active fence");
368 swap->gl_fence.reset();
369 CheckGLErrors("while deleting active fence");
372 // Update the CALayer hierarchy.
373 UpdateCALayerTree(layer_.get(), swap.get());
375 // Send acknowledgement to the browser.
376 GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params params;
377 params.surface_handle =
378 ui::SurfaceHandleFromCAContextID([ca_context_ contextId]);
379 params.size = swap->pixel_size;
380 params.scale_factor = swap->scale_factor;
381 params.latency_info.swap(swap->latency_info);
382 helper_->SendAcceleratedSurfaceBuffersSwapped(params);
384 // Remove this from the queue, and reset any callback timers.
385 pending_swaps_.pop_front();
389 void ImageTransportSurfaceOverlayMac::UpdateCALayerTree(
390 CALayer* root_layer, PendingSwap* swap) {
391 TRACE_EVENT0("gpu", "ImageTransportSurfaceOverlayMac::UpdateCALayerTree");
392 ScopedCAActionDisabler disabler;
394 // Sort the planes by z-index.
395 std::sort(swap->overlay_planes.begin(), swap->overlay_planes.end(),
396 OverlayPlane::Compare);
398 NSUInteger child_index = 0;
399 for (linked_ptr<OverlayPlane>& plane : swap->overlay_planes) {
400 // Look up or create the CALayer for this plane.
401 CALayer* plane_layer = nil;
402 if (plane->type == OverlayPlane::ROOT) {
403 plane_layer = root_layer;
405 if (child_index >= [[root_layer sublayers] count]) {
406 base::scoped_nsobject<CALayer> new_layer([[CALayer alloc] init]);
407 [new_layer setOpaque:YES];
408 [root_layer addSublayer:new_layer];
410 plane_layer = [[root_layer sublayers] objectAtIndex:child_index];
414 // Update layer contents if needed.
415 if (plane->io_surface) {
416 // Note that calling setContents with the same IOSurface twice will result
417 // in the screen not being updated, even if the IOSurface's content has
418 // changed. This can be avoided by calling setContentsChanged. Only call
419 // this on the root layer, because it is the only layer that will ignore
421 id new_contents = static_cast<id>(plane->io_surface.get());
422 if ([plane_layer contents] == new_contents) {
423 if (plane->type == OverlayPlane::ROOT)
424 [plane_layer setContentsChanged];
426 [plane_layer setContents:new_contents];
429 // No content update is needed for this layer.
430 DCHECK(plane->type == OverlayPlane::ROOT ||
431 plane->type == OverlayPlane::ROOT_PARTIAL_DAMAGE);
434 static bool show_borders =
435 base::CommandLine::ForCurrentProcess()->HasSwitch(
436 switches::kShowMacOverlayBorders);
438 base::ScopedCFTypeRef<CGColorRef> color;
439 if (!plane->io_surface) {
440 // Green represents contents that are unchanged across frames.
441 color.reset(CGColorCreateGenericRGB(0, 1, 0, 1));
442 } else if (plane->type == OverlayPlane::OVERLAY) {
443 // Pink represents overlay planes
444 color.reset(CGColorCreateGenericRGB(1, 0, 1, 1));
446 // Red represents damaged contents.
447 color.reset(CGColorCreateGenericRGB(1, 0, 0, 1));
449 [plane_layer setBorderWidth:plane_layer == root_layer ? 1 : 2];
450 [plane_layer setBorderColor:color];
453 [plane_layer setFrame:plane->dip_frame_rect.ToCGRect()];
454 [plane_layer setContentsRect:plane->contents_rect.ToCGRect()];
457 // Remove any now-obsolete children.
458 while ([[root_layer sublayers] count] > child_index) {
459 CALayer* layer = [[root_layer sublayers] objectAtIndex:child_index];
460 [layer setContents:nil];
461 [layer removeFromSuperlayer];
465 void ImageTransportSurfaceOverlayMac::DisplayAndClearAllPendingSwaps() {
467 "ImageTransportSurfaceOverlayMac::DisplayAndClearAllPendingSwaps");
468 while (!pending_swaps_.empty())
469 DisplayFirstPendingSwapImmediately();
472 void ImageTransportSurfaceOverlayMac::CheckPendingSwapsCallback() {
474 "ImageTransportSurfaceOverlayMac::CheckPendingSwapsCallback");
476 if (pending_swaps_.empty())
479 const base::TimeTicks now = base::TimeTicks::Now();
480 if (IsFirstPendingSwapReadyToDisplay(now))
481 DisplayFirstPendingSwapImmediately();
482 PostCheckPendingSwapsCallbackIfNeeded(now);
485 void ImageTransportSurfaceOverlayMac::PostCheckPendingSwapsCallbackIfNeeded(
486 const base::TimeTicks& now) {
488 "ImageTransportSurfaceOverlayMac::PostCheckPendingSwapsCallbackIfNeeded");
490 if (pending_swaps_.empty()) {
491 display_pending_swap_timer_.Stop();
493 display_pending_swap_timer_.Start(
495 pending_swaps_.front()->target_display_time - now,
496 base::Bind(&ImageTransportSurfaceOverlayMac::CheckPendingSwapsCallback,
497 weak_factory_.GetWeakPtr()));
501 gfx::SwapResult ImageTransportSurfaceOverlayMac::SwapBuffers() {
502 return PostSubBuffer(0, 0, pixel_size_.width(), pixel_size_.height());
505 gfx::SwapResult ImageTransportSurfaceOverlayMac::PostSubBuffer(int x,
509 ScheduleOverlayPlaneForPartialDamage(gfx::Rect(x, y, width, height));
510 return SwapBuffersInternal();
513 bool ImageTransportSurfaceOverlayMac::SupportsPostSubBuffer() {
517 gfx::Size ImageTransportSurfaceOverlayMac::GetSize() {
521 void* ImageTransportSurfaceOverlayMac::GetHandle() {
525 bool ImageTransportSurfaceOverlayMac::OnMakeCurrent(gfx::GLContext* context) {
526 // Ensure that the context is on the appropriate GL renderer. The GL renderer
527 // will generally only change when the GPU changes.
528 if (gl_renderer_id_ && context)
529 context->share_group()->SetRendererID(gl_renderer_id_);
533 bool ImageTransportSurfaceOverlayMac::SetBackbufferAllocation(bool allocated) {
535 DisplayAndClearAllPendingSwaps();
536 last_swap_time_ = base::TimeTicks();
541 bool ImageTransportSurfaceOverlayMac::ScheduleOverlayPlane(
543 gfx::OverlayTransform transform,
545 const gfx::Rect& bounds_rect,
546 const gfx::RectF& crop_rect) {
547 DCHECK_GE(z_order, 0);
548 DCHECK_EQ(transform, gfx::OVERLAY_TRANSFORM_NONE);
549 if (z_order < 0 || transform != gfx::OVERLAY_TRANSFORM_NONE)
552 OverlayPlane::Type type = z_order == 0 ?
553 OverlayPlane::ROOT : OverlayPlane::OVERLAY;
554 gfx::Rect dip_frame_rect = gfx::ConvertRectToDIP(
555 scale_factor_, bounds_rect);
556 gfx::RectF contents_rect = crop_rect;
558 gfx::GLImageIOSurface* image_io_surface =
559 static_cast<gfx::GLImageIOSurface*>(image);
561 pending_overlay_planes_.push_back(linked_ptr<OverlayPlane>(
563 type, z_order, image_io_surface->io_surface(), dip_frame_rect,
568 bool ImageTransportSurfaceOverlayMac::IsSurfaceless() const {
572 void ImageTransportSurfaceOverlayMac::OnBufferPresented(
573 const AcceleratedSurfaceMsg_BufferPresented_Params& params) {
574 vsync_timebase_ = params.vsync_timebase;
575 vsync_interval_ = params.vsync_interval;
576 vsync_parameters_valid_ = (vsync_interval_ != base::TimeDelta());
578 // Compute |vsync_timebase_| to be the first vsync after time zero.
579 if (vsync_parameters_valid_) {
582 ((vsync_timebase_ - base::TimeTicks()) / vsync_interval_);
586 void ImageTransportSurfaceOverlayMac::OnResize(gfx::Size pixel_size,
587 float scale_factor) {
588 // Flush through any pending frames.
589 DisplayAndClearAllPendingSwaps();
590 pixel_size_ = pixel_size;
591 scale_factor_ = scale_factor;
594 void ImageTransportSurfaceOverlayMac::SetLatencyInfo(
595 const std::vector<ui::LatencyInfo>& latency_info) {
596 latency_info_.insert(
597 latency_info_.end(), latency_info.begin(), latency_info.end());
600 void ImageTransportSurfaceOverlayMac::WakeUpGpu() {}
602 void ImageTransportSurfaceOverlayMac::OnGpuSwitched() {
603 // Create a new context, and use the GL renderer ID that the new context gets.
604 scoped_refptr<ui::IOSurfaceContext> context_on_new_gpu =
605 ui::IOSurfaceContext::Get(ui::IOSurfaceContext::kCALayerContext);
606 if (!context_on_new_gpu)
608 GLint context_renderer_id = -1;
609 if (CGLGetParameter(context_on_new_gpu->cgl_context(),
610 kCGLCPCurrentRendererID,
611 &context_renderer_id) != kCGLNoError) {
612 LOG(ERROR) << "Failed to create test context after GPU switch";
615 gl_renderer_id_ = context_renderer_id & kCGLRendererIDMatchingMask;
617 // Post a task holding a reference to the new GL context. The reason for
618 // this is to avoid creating-then-destroying the context for every image
619 // transport surface that is observing the GPU switch.
620 base::MessageLoop::current()->PostTask(
621 FROM_HERE, base::Bind(&IOSurfaceContextNoOp, context_on_new_gpu));
624 base::TimeTicks ImageTransportSurfaceOverlayMac::GetNextVSyncTimeAfter(
625 const base::TimeTicks& from, double interval_fraction) {
626 if (!vsync_parameters_valid_)
629 // Compute the previous vsync time.
630 base::TimeTicks previous_vsync =
631 vsync_interval_ * ((from - vsync_timebase_) / vsync_interval_) +
634 // Return |interval_fraction| through the next vsync.
635 return previous_vsync + (1 + interval_fraction) * vsync_interval_;
638 } // namespace content