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 "ui/accelerated_widget_mac/io_surface_layer.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/sdk_forward_declarations.h"
14 #include "base/trace_event/trace_event.h"
15 #include "ui/accelerated_widget_mac/io_surface_context.h"
16 #include "ui/accelerated_widget_mac/io_surface_texture.h"
17 #include "ui/base/cocoa/animation_utils.h"
18 #include "ui/gfx/geometry/size_conversions.h"
19 #include "ui/gl/gpu_switching_manager.h"
21 ////////////////////////////////////////////////////////////////////////////////
22 // IOSurfaceLayerHelper
26 IOSurfaceLayerHelper::IOSurfaceLayerHelper(
27 IOSurfaceLayerClient* client,
28 IOSurfaceLayer* layer)
31 needs_display_(false),
32 has_pending_frame_(false),
33 did_not_draw_counter_(0),
34 is_pumping_frames_(false),
37 base::TimeDelta::FromSeconds(1) / 6,
39 &IOSurfaceLayerHelper::TimerFired) {}
41 IOSurfaceLayerHelper::~IOSurfaceLayerHelper() {
42 // Any acks that were waiting on this layer to draw will not occur, so ack
43 // them now to prevent blocking the renderer.
44 AckPendingFrame(true);
47 void IOSurfaceLayerHelper::GotNewFrame() {
48 // A trace value of 2 indicates that there is a pending swap ack. See
49 // canDrawInCGLContext for other value meanings.
50 TRACE_COUNTER_ID1("browser", "PendingSwapAck", this, 2);
52 has_pending_frame_ = true;
53 needs_display_ = true;
56 // If reqested, draw immediately and don't bother trying to use the
57 // isAsynchronous property to ensure smooth animation. If this is while
58 // frames are being pumped then ack and display immediately to get a
59 // correct-sized frame displayed as soon as possible.
60 if (is_pumping_frames_ || client_->IOSurfaceLayerShouldAckImmediately()) {
61 SetNeedsDisplayAndDisplayAndAck();
63 if (![layer_ isAsynchronous])
64 [layer_ setAsynchronous:YES];
68 void IOSurfaceLayerHelper::SetNeedsDisplay() {
69 needs_display_ = true;
72 bool IOSurfaceLayerHelper::CanDraw() {
73 // If we return NO 30 times in a row, switch to being synchronous to avoid
74 // burning CPU cycles on this callback.
76 did_not_draw_counter_ = 0;
78 did_not_draw_counter_ += 1;
79 if (did_not_draw_counter_ == 30)
80 [layer_ setAsynchronous:NO];
83 // Add an instantaneous blip to the PendingSwapAck state to indicate
84 // that CoreAnimation asked if a frame is ready. A blip up to to 3 (usually
85 // from 2, indicating that a swap ack is pending) indicates that we
86 // requested a draw. A blip up to 1 (usually from 0, indicating there is no
87 // pending swap ack) indicates that we did not request a draw. This would
88 // be more natural to do with a tracing pseudo-thread
89 // http://crbug.com/366300
90 TRACE_COUNTER_ID1("browser", "PendingSwapAck", this, needs_display_ ? 3 : 1);
91 TRACE_COUNTER_ID1("browser", "PendingSwapAck", this,
92 has_pending_frame_ ? 2 : 0);
94 return needs_display_;
97 void IOSurfaceLayerHelper::DidDraw(bool success) {
98 needs_display_ = false;
99 AckPendingFrame(success);
102 void IOSurfaceLayerHelper::AckPendingFrame(bool success) {
103 if (!has_pending_frame_)
105 has_pending_frame_ = false;
107 client_->IOSurfaceLayerDidDrawFrame();
109 client_->IOSurfaceLayerHitError();
110 // A trace value of 0 indicates that there is no longer a pending swap ack.
111 TRACE_COUNTER_ID1("browser", "PendingSwapAck", this, 0);
114 void IOSurfaceLayerHelper::SetNeedsDisplayAndDisplayAndAck() {
115 // Drawing using setNeedsDisplay and displayIfNeeded will result in
116 // subsequent canDrawInCGLContext callbacks getting dropped, and jerky
117 // animation. Disable asynchronous drawing before issuing these calls as a
119 // http://crbug.com/395827
120 if ([layer_ isAsynchronous])
121 [layer_ setAsynchronous:NO];
123 [layer_ setNeedsDisplay];
124 DisplayIfNeededAndAck();
127 void IOSurfaceLayerHelper::DisplayIfNeededAndAck() {
131 // As in SetNeedsDisplayAndDisplayAndAck, disable asynchronous drawing before
132 // issuing displayIfNeeded.
133 // http://crbug.com/395827
134 if ([layer_ isAsynchronous])
135 [layer_ setAsynchronous:NO];
137 // Do not bother drawing while pumping new frames -- wait until the waiting
138 // block ends to draw any of the new frames.
139 if (!is_pumping_frames_)
140 [layer_ displayIfNeeded];
142 // Calls to setNeedsDisplay can sometimes be ignored, especially if issued
143 // rapidly (e.g, with vsync off). This is unacceptable because the failure
144 // to ack a single frame will hang the renderer. Ensure that the renderer
145 // not be blocked by lying and claiming that we drew the frame.
146 AckPendingFrame(true);
149 void IOSurfaceLayerHelper::TimerFired() {
150 DisplayIfNeededAndAck();
153 void IOSurfaceLayerHelper::BeginPumpingFrames() {
154 is_pumping_frames_ = true;
157 void IOSurfaceLayerHelper::EndPumpingFrames() {
158 is_pumping_frames_ = false;
159 DisplayIfNeededAndAck();
164 ////////////////////////////////////////////////////////////////////////////////
167 @implementation IOSurfaceLayer
169 - (id)initWithClient:(ui::IOSurfaceLayerClient*)client
170 withScaleFactor:(float)scale_factor
171 needsGLFinishWorkaround:(bool)needs_gl_finish_workaround {
172 if (self = [super init]) {
173 helper_.reset(new ui::IOSurfaceLayerHelper(client, self));
175 iosurface_ = ui::IOSurfaceTexture::Create(needs_gl_finish_workaround);
176 context_ = ui::IOSurfaceContext::Get(
177 ui::IOSurfaceContext::kCALayerContext);
178 if (!iosurface_.get() || !context_.get()) {
179 LOG(ERROR) << "Failed create CompositingIOSurface or context";
185 [self setAnchorPoint:CGPointMake(0, 0)];
186 // Setting contents gravity is necessary to prevent the layer from being
187 // scaled during dyanmic resizes (especially with devtools open).
188 [self setContentsGravity:kCAGravityTopLeft];
189 if ([self respondsToSelector:(@selector(setContentsScale:))]) {
190 [self setContentsScale:scale_factor];
201 - (bool)gotFrameWithIOSurface:(IOSurfaceID)io_surface_id
202 withPixelSize:(gfx::Size)pixel_size
203 withScaleFactor:(float)scale_factor {
204 return iosurface_->SetIOSurface(io_surface_id, pixel_size);
207 - (void)poisonContextAndSharegroup {
208 context_->PoisonContextAndSharegroup();
211 - (bool)hasBeenPoisoned {
212 return context_->HasBeenPoisoned();
215 - (float)scaleFactor {
216 if ([self respondsToSelector:(@selector(contentsScale))])
217 return [self contentsScale];
222 GLint current_renderer_id = -1;
223 if (CGLGetParameter(context_->cgl_context(),
224 kCGLCPCurrentRendererID,
225 ¤t_renderer_id) == kCGLNoError) {
226 return current_renderer_id & kCGLRendererIDMatchingMask;
231 - (void)resetClient {
235 - (void)gotNewFrame {
236 helper_->GotNewFrame();
239 - (void)setNeedsDisplayAndDisplayAndAck {
240 helper_->SetNeedsDisplayAndDisplayAndAck();
243 - (void)displayIfNeededAndAck {
244 helper_->DisplayIfNeededAndAck();
247 - (void)beginPumpingFrames {
248 helper_->BeginPumpingFrames();
251 - (void)endPumpingFrames {
252 helper_->EndPumpingFrames();
255 // The remaining methods implement the CAOpenGLLayer interface.
257 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
259 return [super copyCGLPixelFormatForDisplayMask:mask];
260 return CGLRetainPixelFormat(CGLGetPixelFormat(context_->cgl_context()));
263 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
265 return [super copyCGLContextForPixelFormat:pixelFormat];
266 return CGLRetainContext(context_->cgl_context());
269 - (void)setNeedsDisplay {
271 helper_->SetNeedsDisplay();
272 [super setNeedsDisplay];
275 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
276 pixelFormat:(CGLPixelFormatObj)pixelFormat
277 forLayerTime:(CFTimeInterval)timeInterval
278 displayTime:(const CVTimeStamp*)timeStamp {
280 return helper_->CanDraw();
284 - (void)drawInCGLContext:(CGLContextObj)glContext
285 pixelFormat:(CGLPixelFormatObj)pixelFormat
286 forLayerTime:(CFTimeInterval)timeInterval
287 displayTime:(const CVTimeStamp*)timeStamp {
288 TRACE_EVENT0("browser", "IOSurfaceLayer::drawInCGLContext");
290 bool draw_succeeded = iosurface_->DrawIOSurface();
292 helper_->DidDraw(draw_succeeded);
294 [super drawInCGLContext:glContext
295 pixelFormat:pixelFormat
296 forLayerTime:timeInterval
297 displayTime:timeStamp];