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/compositor/io_surface_layer_mac.h"
7 #include <CoreFoundation/CoreFoundation.h>
8 #include <OpenGL/CGLIOSurface.h>
9 #include <OpenGL/CGLRenderers.h>
10 #include <OpenGL/gl.h>
11 #include <OpenGL/OpenGL.h>
13 #include "base/mac/mac_util.h"
14 #include "base/mac/sdk_forward_declarations.h"
15 #include "content/browser/compositor/io_surface_context_mac.h"
16 #include "content/browser/compositor/io_surface_texture_mac.h"
17 #include "content/browser/renderer_host/render_widget_host_impl.h"
18 #include "content/browser/renderer_host/render_widget_host_view_mac.h"
19 #include "ui/base/cocoa/animation_utils.h"
20 #include "ui/gfx/size_conversions.h"
21 #include "ui/gl/gpu_switching_manager.h"
23 ////////////////////////////////////////////////////////////////////////////////
24 // IOSurfaceLayerHelper
28 IOSurfaceLayerHelper::IOSurfaceLayerHelper(
29 IOSurfaceLayerClient* client,
30 IOSurfaceLayer* layer)
33 needs_display_(false),
34 has_pending_frame_(false),
35 did_not_draw_counter_(0),
36 is_pumping_frames_(false),
39 base::TimeDelta::FromSeconds(1) / 6,
41 &IOSurfaceLayerHelper::TimerFired) {}
43 IOSurfaceLayerHelper::~IOSurfaceLayerHelper() {
44 // Any acks that were waiting on this layer to draw will not occur, so ack
45 // them now to prevent blocking the renderer.
46 AckPendingFrame(true);
49 void IOSurfaceLayerHelper::GotNewFrame() {
50 // A trace value of 2 indicates that there is a pending swap ack. See
51 // canDrawInCGLContext for other value meanings.
52 TRACE_COUNTER_ID1("browser", "PendingSwapAck", this, 2);
54 has_pending_frame_ = true;
55 needs_display_ = true;
58 // If reqested, draw immediately and don't bother trying to use the
59 // isAsynchronous property to ensure smooth animation. If this is while
60 // frames are being pumped then ack and display immediately to get a
61 // correct-sized frame displayed as soon as possible.
62 if (is_pumping_frames_ || client_->IOSurfaceLayerShouldAckImmediately()) {
63 SetNeedsDisplayAndDisplayAndAck();
65 if (![layer_ isAsynchronous])
66 [layer_ setAsynchronous:YES];
70 void IOSurfaceLayerHelper::SetNeedsDisplay() {
71 needs_display_ = true;
74 bool IOSurfaceLayerHelper::CanDraw() {
75 // If we return NO 30 times in a row, switch to being synchronous to avoid
76 // burning CPU cycles on this callback.
78 did_not_draw_counter_ = 0;
80 did_not_draw_counter_ += 1;
81 if (did_not_draw_counter_ == 30)
82 [layer_ setAsynchronous:NO];
85 // Add an instantaneous blip to the PendingSwapAck state to indicate
86 // that CoreAnimation asked if a frame is ready. A blip up to to 3 (usually
87 // from 2, indicating that a swap ack is pending) indicates that we
88 // requested a draw. A blip up to 1 (usually from 0, indicating there is no
89 // pending swap ack) indicates that we did not request a draw. This would
90 // be more natural to do with a tracing pseudo-thread
91 // http://crbug.com/366300
92 TRACE_COUNTER_ID1("browser", "PendingSwapAck", this, needs_display_ ? 3 : 1);
93 TRACE_COUNTER_ID1("browser", "PendingSwapAck", this,
94 has_pending_frame_ ? 2 : 0);
96 return needs_display_;
99 void IOSurfaceLayerHelper::DidDraw(bool success) {
100 needs_display_ = false;
101 AckPendingFrame(success);
104 void IOSurfaceLayerHelper::AckPendingFrame(bool success) {
105 if (!has_pending_frame_)
107 has_pending_frame_ = false;
109 client_->IOSurfaceLayerDidDrawFrame();
111 client_->IOSurfaceLayerHitError();
112 // A trace value of 0 indicates that there is no longer a pending swap ack.
113 TRACE_COUNTER_ID1("browser", "PendingSwapAck", this, 0);
116 void IOSurfaceLayerHelper::SetNeedsDisplayAndDisplayAndAck() {
117 // Drawing using setNeedsDisplay and displayIfNeeded will result in
118 // subsequent canDrawInCGLContext callbacks getting dropped, and jerky
119 // animation. Disable asynchronous drawing before issuing these calls as a
121 // http://crbug.com/395827
122 if ([layer_ isAsynchronous])
123 [layer_ setAsynchronous:NO];
125 [layer_ setNeedsDisplay];
126 DisplayIfNeededAndAck();
129 void IOSurfaceLayerHelper::DisplayIfNeededAndAck() {
133 // As in SetNeedsDisplayAndDisplayAndAck, disable asynchronous drawing before
134 // issuing displayIfNeeded.
135 // http://crbug.com/395827
136 if ([layer_ isAsynchronous])
137 [layer_ setAsynchronous:NO];
139 // Do not bother drawing while pumping new frames -- wait until the waiting
140 // block ends to draw any of the new frames.
141 if (!is_pumping_frames_)
142 [layer_ displayIfNeeded];
144 // Calls to setNeedsDisplay can sometimes be ignored, especially if issued
145 // rapidly (e.g, with vsync off). This is unacceptable because the failure
146 // to ack a single frame will hang the renderer. Ensure that the renderer
147 // not be blocked by lying and claiming that we drew the frame.
148 AckPendingFrame(true);
151 void IOSurfaceLayerHelper::TimerFired() {
152 DisplayIfNeededAndAck();
155 void IOSurfaceLayerHelper::BeginPumpingFrames() {
156 is_pumping_frames_ = true;
159 void IOSurfaceLayerHelper::EndPumpingFrames() {
160 is_pumping_frames_ = false;
161 DisplayIfNeededAndAck();
164 } // namespace content
166 ////////////////////////////////////////////////////////////////////////////////
169 @implementation IOSurfaceLayer
171 - (id)initWithClient:(content::IOSurfaceLayerClient*)client
172 withScaleFactor:(float)scale_factor {
173 if (self = [super init]) {
174 helper_.reset(new content::IOSurfaceLayerHelper(client, self));
176 iosurface_ = content::IOSurfaceTexture::Create();
177 context_ = content::IOSurfaceContext::Get(
178 content::IOSurfaceContext::kCALayerContext);
179 if (!iosurface_.get() || !context_.get()) {
180 LOG(ERROR) << "Failed create CompositingIOSurface or context";
186 [self setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
187 [self setAnchorPoint:CGPointMake(0, 0)];
188 // Setting contents gravity is necessary to prevent the layer from being
189 // scaled during dyanmic resizes (especially with devtools open).
190 [self setContentsGravity:kCAGravityTopLeft];
191 if ([self respondsToSelector:(@selector(setContentsScale:))]) {
192 [self setContentsScale:scale_factor];
203 - (bool)gotFrameWithIOSurface:(IOSurfaceID)io_surface_id
204 withPixelSize:(gfx::Size)pixel_size
205 withScaleFactor:(float)scale_factor {
206 return iosurface_->SetIOSurface(io_surface_id, pixel_size);
209 - (void)poisonContextAndSharegroup {
210 context_->PoisonContextAndSharegroup();
213 - (bool)hasBeenPoisoned {
214 return context_->HasBeenPoisoned();
217 - (float)scaleFactor {
218 if ([self respondsToSelector:(@selector(contentsScale))])
219 return [self contentsScale];
224 GLint current_renderer_id = -1;
225 if (CGLGetParameter(context_->cgl_context(),
226 kCGLCPCurrentRendererID,
227 ¤t_renderer_id) == kCGLNoError) {
228 return current_renderer_id & kCGLRendererIDMatchingMask;
233 - (void)resetClient {
237 - (void)gotNewFrame {
238 helper_->GotNewFrame();
241 - (void)setNeedsDisplayAndDisplayAndAck {
242 helper_->SetNeedsDisplayAndDisplayAndAck();
245 - (void)displayIfNeededAndAck {
246 helper_->DisplayIfNeededAndAck();
249 - (void)beginPumpingFrames {
250 helper_->BeginPumpingFrames();
253 - (void)endPumpingFrames {
254 helper_->EndPumpingFrames();
257 // The remaining methods implement the CAOpenGLLayer interface.
259 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
261 return [super copyCGLPixelFormatForDisplayMask:mask];
262 return CGLRetainPixelFormat(CGLGetPixelFormat(context_->cgl_context()));
265 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
267 return [super copyCGLContextForPixelFormat:pixelFormat];
268 return CGLRetainContext(context_->cgl_context());
271 - (void)setNeedsDisplay {
273 helper_->SetNeedsDisplay();
274 [super setNeedsDisplay];
277 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
278 pixelFormat:(CGLPixelFormatObj)pixelFormat
279 forLayerTime:(CFTimeInterval)timeInterval
280 displayTime:(const CVTimeStamp*)timeStamp {
282 return helper_->CanDraw();
286 - (void)drawInCGLContext:(CGLContextObj)glContext
287 pixelFormat:(CGLPixelFormatObj)pixelFormat
288 forLayerTime:(CFTimeInterval)timeInterval
289 displayTime:(const CVTimeStamp*)timeStamp {
290 TRACE_EVENT0("browser", "IOSurfaceLayer::drawInCGLContext");
292 bool draw_succeeded = iosurface_->DrawIOSurface();
294 helper_->DidDraw(draw_succeeded);
296 [super drawInCGLContext:glContext
297 pixelFormat:pixelFormat
298 forLayerTime:timeInterval
299 displayTime:timeStamp];