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 setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
186 [self setAnchorPoint:CGPointMake(0, 0)];
187 // Setting contents gravity is necessary to prevent the layer from being
188 // scaled during dyanmic resizes (especially with devtools open).
189 [self setContentsGravity:kCAGravityTopLeft];
190 if ([self respondsToSelector:(@selector(setContentsScale:))]) {
191 [self setContentsScale:scale_factor];
202 - (bool)gotFrameWithIOSurface:(IOSurfaceID)io_surface_id
203 withPixelSize:(gfx::Size)pixel_size
204 withScaleFactor:(float)scale_factor {
205 return iosurface_->SetIOSurface(io_surface_id, pixel_size);
208 - (void)poisonContextAndSharegroup {
209 context_->PoisonContextAndSharegroup();
212 - (bool)hasBeenPoisoned {
213 return context_->HasBeenPoisoned();
216 - (float)scaleFactor {
217 if ([self respondsToSelector:(@selector(contentsScale))])
218 return [self contentsScale];
223 GLint current_renderer_id = -1;
224 if (CGLGetParameter(context_->cgl_context(),
225 kCGLCPCurrentRendererID,
226 ¤t_renderer_id) == kCGLNoError) {
227 return current_renderer_id & kCGLRendererIDMatchingMask;
232 - (void)resetClient {
236 - (void)gotNewFrame {
237 helper_->GotNewFrame();
240 - (void)setNeedsDisplayAndDisplayAndAck {
241 helper_->SetNeedsDisplayAndDisplayAndAck();
244 - (void)displayIfNeededAndAck {
245 helper_->DisplayIfNeededAndAck();
248 - (void)beginPumpingFrames {
249 helper_->BeginPumpingFrames();
252 - (void)endPumpingFrames {
253 helper_->EndPumpingFrames();
256 // The remaining methods implement the CAOpenGLLayer interface.
258 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
260 return [super copyCGLPixelFormatForDisplayMask:mask];
261 return CGLRetainPixelFormat(CGLGetPixelFormat(context_->cgl_context()));
264 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
266 return [super copyCGLContextForPixelFormat:pixelFormat];
267 return CGLRetainContext(context_->cgl_context());
270 - (void)setNeedsDisplay {
272 helper_->SetNeedsDisplay();
273 [super setNeedsDisplay];
276 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
277 pixelFormat:(CGLPixelFormatObj)pixelFormat
278 forLayerTime:(CFTimeInterval)timeInterval
279 displayTime:(const CVTimeStamp*)timeStamp {
281 return helper_->CanDraw();
285 - (void)drawInCGLContext:(CGLContextObj)glContext
286 pixelFormat:(CGLPixelFormatObj)pixelFormat
287 forLayerTime:(CFTimeInterval)timeInterval
288 displayTime:(const CVTimeStamp*)timeStamp {
289 TRACE_EVENT0("browser", "IOSurfaceLayer::drawInCGLContext");
291 bool draw_succeeded = iosurface_->DrawIOSurface();
293 helper_->DidDraw(draw_succeeded);
295 [super drawInCGLContext:glContext
296 pixelFormat:pixelFormat
297 forLayerTime:timeInterval
298 displayTime:timeStamp];