1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/renderer_host/compositing_iosurface_layer_mac.h"
7 #include <CoreFoundation/CoreFoundation.h>
10 #include "base/mac/mac_util.h"
11 #include "base/mac/sdk_forward_declarations.h"
12 #include "content/browser/renderer_host/render_widget_host_impl.h"
13 #include "content/browser/renderer_host/render_widget_host_view_mac.h"
14 #include "content/browser/renderer_host/compositing_iosurface_context_mac.h"
15 #include "content/browser/renderer_host/compositing_iosurface_mac.h"
16 #include "ui/base/cocoa/animation_utils.h"
17 #include "ui/gfx/size_conversions.h"
18 #include "ui/gl/gpu_switching_manager.h"
20 @interface CompositingIOSurfaceLayer(Private)
21 - (void)ackPendingFrame:(bool)success;
27 // The base::DelayTimer needs a C++ class to operate on, rather than Objective C
28 // class. This helper class provides a bridge between the two.
29 class CompositingIOSurfaceLayerHelper {
31 CompositingIOSurfaceLayerHelper(CompositingIOSurfaceLayer* layer)
37 CompositingIOSurfaceLayer* layer_;
40 } // namespace content
42 @implementation CompositingIOSurfaceLayer
44 - (content::CompositingIOSurfaceMac*)iosurface {
45 return iosurface_.get();
48 - (content::CompositingIOSurfaceContext*)context {
49 return context_.get();
52 - (id)initWithIOSurface:(scoped_refptr<content::CompositingIOSurfaceMac>)
54 withScaleFactor:(float)scale_factor
55 withClient:(content::CompositingIOSurfaceLayerClient*)client {
56 if (self = [super init]) {
57 iosurface_ = iosurface;
59 helper_.reset(new content::CompositingIOSurfaceLayerHelper(self));
60 timer_.reset(new base::DelayTimer<content::CompositingIOSurfaceLayerHelper>(
62 base::TimeDelta::FromSeconds(1) / 6,
64 &content::CompositingIOSurfaceLayerHelper::TimerFired));
66 context_ = content::CompositingIOSurfaceContext::Get(
67 content::CompositingIOSurfaceContext::kCALayerContextWindowNumber);
70 has_pending_frame_ = NO;
71 did_not_draw_counter_ = 0;
73 [self setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
74 [self setAnchorPoint:CGPointMake(0, 0)];
75 // Setting contents gravity is necessary to prevent the layer from being
76 // scaled during dyanmic resizes (especially with devtools open).
77 [self setContentsGravity:kCAGravityTopLeft];
78 if ([self respondsToSelector:(@selector(setContentsScale:))]) {
79 [self setContentsScale:scale_factor];
86 // Any acks that were waiting on this layer to draw will not occur, so ack
87 // them now to prevent blocking the renderer.
88 [self ackPendingFrame:true];
93 has_pending_frame_ = YES;
96 // A trace value of 2 indicates that there is a pending swap ack. See
97 // canDrawInCGLContext for other value meanings.
98 TRACE_COUNTER_ID1("browser", "PendingSwapAck", self, 2);
100 if (context_ && context_->is_vsync_disabled()) {
101 // If vsync is disabled, draw immediately and don't bother trying to use
102 // the isAsynchronous property to ensure smooth animation.
103 [self setNeedsDisplayAndDisplayAndAck];
105 needs_display_ = YES;
106 if (![self isAsynchronous])
107 [self setAsynchronous:YES];
113 - (void)setNeedsDisplayAndDisplayAndAck {
114 // Workaround for crbug.com/395827
115 if ([self isAsynchronous])
116 [self setAsynchronous:NO];
118 [self setNeedsDisplay];
119 [self displayIfNeededAndAck];
122 - (void)displayIfNeededAndAck {
123 // Workaround for crbug.com/395827
124 if ([self isAsynchronous])
125 [self setAsynchronous:NO];
127 [self displayIfNeeded];
129 // Calls to setNeedsDisplay can sometimes be ignored, especially if issued
130 // rapidly (e.g, with vsync off). This is unacceptable because the failure
131 // to ack a single frame will hang the renderer. Ensure that the renderer
132 // not be blocked by lying and claiming that we drew the frame.
133 [self ackPendingFrame:true];
136 - (void)ackPendingFrame:(bool)success {
137 if (!has_pending_frame_)
140 TRACE_COUNTER_ID1("browser", "PendingSwapAck", self, 0);
141 has_pending_frame_ = NO;
143 client_->AcceleratedLayerDidDrawFrame(success);
147 if (has_pending_frame_)
148 [self setNeedsDisplayAndDisplayAndAck];
151 // The remaining methods implement the CAOpenGLLayer interface.
153 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
155 return [super copyCGLPixelFormatForDisplayMask:mask];
156 return CGLRetainPixelFormat(CGLGetPixelFormat(context_->cgl_context()));
159 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
161 return [super copyCGLContextForPixelFormat:pixelFormat];
162 return CGLRetainContext(context_->cgl_context());
165 - (void)setNeedsDisplay {
166 needs_display_ = YES;
167 [super setNeedsDisplay];
170 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
171 pixelFormat:(CGLPixelFormatObj)pixelFormat
172 forLayerTime:(CFTimeInterval)timeInterval
173 displayTime:(const CVTimeStamp*)timeStamp {
174 // Add an instantaneous blip to the PendingSwapAck state to indicate
175 // that CoreAnimation asked if a frame is ready. A blip up to to 3 (usually
176 // from 2, indicating that a swap ack is pending) indicates that we requested
177 // a draw. A blip up to 1 (usually from 0, indicating there is no pending swap
178 // ack) indicates that we did not request a draw. This would be more natural
179 // to do with a tracing pseudo-thread
180 // http://crbug.com/366300
181 TRACE_COUNTER_ID1("browser", "PendingSwapAck", self, needs_display_ ? 3 : 1);
182 TRACE_COUNTER_ID1("browser", "PendingSwapAck", self,
183 has_pending_frame_ ? 2 : 0);
185 // If we return NO 30 times in a row, switch to being synchronous to avoid
186 // burning CPU cycles on this callback.
187 if (needs_display_) {
188 did_not_draw_counter_ = 0;
190 did_not_draw_counter_ += 1;
191 if (did_not_draw_counter_ > 30)
192 [self setAsynchronous:NO];
195 return needs_display_;
198 - (void)drawInCGLContext:(CGLContextObj)glContext
199 pixelFormat:(CGLPixelFormatObj)pixelFormat
200 forLayerTime:(CFTimeInterval)timeInterval
201 displayTime:(const CVTimeStamp*)timeStamp {
202 TRACE_EVENT0("browser", "CompositingIOSurfaceLayer::drawInCGLContext");
204 if (!iosurface_->HasIOSurface() || context_->cgl_context() != glContext) {
205 glClearColor(1, 1, 1, 1);
206 glClear(GL_COLOR_BUFFER_BIT);
210 // The correct viewport to cover the layer will be set up by the caller.
211 // Transform this into a window size for DrawIOSurface, where it will be
212 // transformed back into this viewport.
214 glGetIntegerv(GL_VIEWPORT, viewport);
215 gfx::Rect window_rect(viewport[0], viewport[1], viewport[2], viewport[3]);
216 float window_scale_factor = 1.f;
217 if ([self respondsToSelector:(@selector(contentsScale))])
218 window_scale_factor = [self contentsScale];
219 window_rect = ToNearestRect(
220 gfx::ScaleRect(window_rect, 1.f/window_scale_factor));
222 bool draw_succeeded = iosurface_->DrawIOSurface(
223 context_, window_rect, window_scale_factor);
225 [self ackPendingFrame:draw_succeeded];
228 [super drawInCGLContext:glContext
229 pixelFormat:pixelFormat
230 forLayerTime:timeInterval
231 displayTime:timeStamp];