Mac Remote CoreAnimation: Fix flashing at tab-switch
[chromium-blink-merge.git] / content / common / gpu / image_transport_surface_calayer_mac.mm
blob59a3d9108985e4b45bd7dcbcc94fb1f0a426445f
1 // Copyright 2014 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_calayer_mac.h"
7 #include <OpenGL/CGLRenderers.h>
9 #include "base/command_line.h"
10 #include "base/mac/sdk_forward_declarations.h"
11 #include "content/common/gpu/surface_handle_types_mac.h"
12 #include "ui/base/cocoa/animation_utils.h"
13 #include "ui/gfx/geometry/size_conversions.h"
14 #include "ui/gl/gl_gl_api_implementation.h"
15 #include "ui/gl/gl_switches.h"
17 namespace {
18 const size_t kFramesToKeepCAContextAfterDiscard = 2;
21 @interface ImageTransportLayer : CAOpenGLLayer {
22   content::CALayerStorageProvider* storageProvider_;
24 - (id)initWithStorageProvider:(content::CALayerStorageProvider*)storageProvider;
25 - (void)resetStorageProvider;
26 @end
28 @implementation ImageTransportLayer
30 - (id)initWithStorageProvider:
31     (content::CALayerStorageProvider*)storageProvider {
32   if (self = [super init])
33     storageProvider_ = storageProvider;
34   return self;
37 - (void)resetStorageProvider {
38   if (storageProvider_)
39     storageProvider_->LayerResetStorageProvider();
40   storageProvider_ = NULL;
43 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
44   if (!storageProvider_)
45     return NULL;
46   return CGLRetainPixelFormat(CGLGetPixelFormat(
47       storageProvider_->LayerShareGroupContext()));
50 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
51   if (!storageProvider_)
52     return NULL;
53   CGLContextObj context = NULL;
54   CGLError error = CGLCreateContext(
55       pixelFormat, storageProvider_->LayerShareGroupContext(), &context);
56   if (error != kCGLNoError)
57     DLOG(ERROR) << "CGLCreateContext failed with CGL error: " << error;
58   return context;
61 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
62                 pixelFormat:(CGLPixelFormatObj)pixelFormat
63                forLayerTime:(CFTimeInterval)timeInterval
64                 displayTime:(const CVTimeStamp*)timeStamp {
65   if (!storageProvider_)
66     return NO;
67   return storageProvider_->LayerCanDraw();
70 - (void)drawInCGLContext:(CGLContextObj)glContext
71              pixelFormat:(CGLPixelFormatObj)pixelFormat
72             forLayerTime:(CFTimeInterval)timeInterval
73              displayTime:(const CVTimeStamp*)timeStamp {
74   // While in this callback, CoreAnimation has set |glContext| to be current.
75   // Ensure that the GL calls that we make are made against the native GL API.
76   gfx::ScopedSetGLToRealGLApi scoped_set_gl_api;
78   if (storageProvider_) {
79     storageProvider_->LayerDoDraw();
80   } else {
81     glClearColor(1, 1, 1, 1);
82     glClear(GL_COLOR_BUFFER_BIT);
83   }
84   [super drawInCGLContext:glContext
85               pixelFormat:pixelFormat
86              forLayerTime:timeInterval
87               displayTime:timeStamp];
90 @end
92 namespace content {
94 CALayerStorageProvider::CALayerStorageProvider(
95     ImageTransportSurfaceFBO* transport_surface)
96         : transport_surface_(transport_surface),
97           gpu_vsync_disabled_(CommandLine::ForCurrentProcess()->HasSwitch(
98               switches::kDisableGpuVsync)),
99           throttling_disabled_(false),
100           has_pending_draw_(false),
101           can_draw_returned_false_count_(0),
102           fbo_texture_(0),
103           fbo_scale_factor_(1),
104           pending_draw_weak_factory_(this) {}
106 CALayerStorageProvider::~CALayerStorageProvider() {
109 gfx::Size CALayerStorageProvider::GetRoundedSize(gfx::Size size) {
110   return size;
113 bool CALayerStorageProvider::AllocateColorBufferStorage(
114     CGLContextObj context, GLuint texture,
115     gfx::Size pixel_size, float scale_factor) {
116   // Allocate an ordinary OpenGL texture to back the FBO.
117   GLenum error;
118   while ((error = glGetError()) != GL_NO_ERROR) {
119     DLOG(ERROR) << "Error found (and ignored) before allocating buffer "
120                 << "storage: " << error;
121   }
122   glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
123                0,
124                GL_RGBA,
125                pixel_size.width(),
126                pixel_size.height(),
127                0,
128                GL_RGBA,
129                GL_UNSIGNED_BYTE,
130                NULL);
131   error = glGetError();
132   if (error != GL_NO_ERROR) {
133     DLOG(ERROR) << "glTexImage failed with GL error: " << error;
134     return false;
135   }
136   glFlush();
138   // Set the parameters that will be used to allocate the CALayer to draw the
139   // texture into.
140   share_group_context_.reset(CGLRetainContext(context));
141   fbo_texture_ = texture;
142   fbo_pixel_size_ = pixel_size;
143   fbo_scale_factor_ = scale_factor;
144   return true;
147 void CALayerStorageProvider::FreeColorBufferStorage() {
148   // Note that |context_| still holds a reference to |layer_|, and will until
149   // a new frame is swapped in.
150   [layer_ resetStorageProvider];
151   layer_.reset();
153   share_group_context_.reset();
154   fbo_texture_ = 0;
155   fbo_pixel_size_ = gfx::Size();
156   can_draw_returned_false_count_ = 0;
159 void CALayerStorageProvider::SwapBuffers(
160     const gfx::Size& size, float scale_factor) {
161   DCHECK(!has_pending_draw_);
162   has_pending_draw_ = true;
164   // Allocate a CAContext to use to transport the CALayer to the browser
165   // process.
166   if (!context_) {
167     base::scoped_nsobject<NSDictionary> dict([[NSDictionary alloc] init]);
168     CGSConnectionID connection_id = CGSMainConnectionID();
169     context_.reset([CAContext contextWithCGSConnection:connection_id
170                                                options:dict]);
171     [context_ retain];
172   }
174   // Allocate a CALayer to use to draw the content.
175   if (!layer_) {
176     layer_.reset([[ImageTransportLayer alloc] initWithStorageProvider:this]);
177     gfx::Size dip_size(gfx::ToFlooredSize(gfx::ScaleSize(
178         fbo_pixel_size_, 1.0f / fbo_scale_factor_)));
179     [layer_ setContentsScale:fbo_scale_factor_];
180     [layer_ setFrame:CGRectMake(0, 0, dip_size.width(), dip_size.height())];
182     // Make the CALayer current to the CAContext and display its contents
183     // immediately.
184     [context_ setLayer:layer_];
185   }
187   // Tell CoreAnimation to draw our frame.
188   if (gpu_vsync_disabled_ || throttling_disabled_) {
189     DrawImmediatelyAndUnblockBrowser();
190   } else {
191     if (![layer_ isAsynchronous])
192       [layer_ setAsynchronous:YES];
194     // If CoreAnimation doesn't end up drawing our frame, un-block the browser
195     // after a timeout of 1/6th of a second has passed.
196     base::MessageLoop::current()->PostDelayedTask(
197         FROM_HERE,
198         base::Bind(&CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser,
199                    pending_draw_weak_factory_.GetWeakPtr()),
200         base::TimeDelta::FromSeconds(1) / 6);
201   }
204 void CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser() {
205   CHECK(has_pending_draw_);
206   if ([layer_ isAsynchronous])
207     [layer_ setAsynchronous:NO];
208   [layer_ setNeedsDisplay];
209   [layer_ displayIfNeeded];
211   // Sometimes, the setNeedsDisplay+displayIfNeeded pairs have no effect. This
212   // can happen if the NSView that this layer is attached to isn't in the
213   // window hierarchy (e.g, tab capture of a backgrounded tab). In this case,
214   // the frame will never be seen, so drop it.
215   UnblockBrowserIfNeeded();
218 void CALayerStorageProvider::WillWriteToBackbuffer() {
219   // The browser should always throttle itself so that there are no pending
220   // draws when the output surface is written to, but in the event of things
221   // like context lost, or changing context, this will not be true. If there
222   // exists a pending draw, flush it immediately to maintain a consistent
223   // state.
224   if (has_pending_draw_)
225     DrawImmediatelyAndUnblockBrowser();
228 void CALayerStorageProvider::DiscardBackbuffer() {
229   // If this surface's backbuffer is discarded, it is because this surface has
230   // been made non-visible. Ensure that the previous contents are not briefly
231   // flashed when this is made visible by creating a new CALayer and CAContext
232   // at the next swap.
233   [layer_ resetStorageProvider];
234   layer_.reset();
236   // If we remove all references to the CAContext in this process, it will be
237   // blanked-out in the browser process (even if the browser process is inside
238   // a NSDisableScreenUpdates block). Ensure that the context is kept around
239   // until a fixed number of frames (determined empirically) have been acked.
240   // http://crbug.com/425819
241   while (previously_discarded_contexts_.size() <
242       kFramesToKeepCAContextAfterDiscard) {
243     previously_discarded_contexts_.push_back(
244         base::scoped_nsobject<CAContext>());
245   }
246   previously_discarded_contexts_.push_back(context_);
248   context_.reset();
251 void CALayerStorageProvider::SwapBuffersAckedByBrowser(
252     bool disable_throttling) {
253   throttling_disabled_ = disable_throttling;
254   if (!previously_discarded_contexts_.empty())
255     previously_discarded_contexts_.pop_front();
258 CGLContextObj CALayerStorageProvider::LayerShareGroupContext() {
259   return share_group_context_;
262 bool CALayerStorageProvider::LayerCanDraw() {
263   if (has_pending_draw_) {
264     can_draw_returned_false_count_ = 0;
265     return true;
266   } else {
267     if ([layer_ isAsynchronous]) {
268       DCHECK(!gpu_vsync_disabled_);
269       // If we are in asynchronous mode, we will be getting callbacks at every
270       // vsync, asking us if we have anything to draw. If we get 30 of these in
271       // a row, ask that we stop getting these callback for now, so that we
272       // don't waste CPU cycles.
273       if (can_draw_returned_false_count_ == 30)
274         [layer_ setAsynchronous:NO];
275       else
276         can_draw_returned_false_count_ += 1;
277     }
278     return false;
279   }
282 void CALayerStorageProvider::LayerDoDraw() {
283   GLint viewport[4] = {0, 0, 0, 0};
284   glGetIntegerv(GL_VIEWPORT, viewport);
285   gfx::Size viewport_size(viewport[2], viewport[3]);
287   // Set the coordinate system to be one-to-one with pixels.
288   glMatrixMode(GL_PROJECTION);
289   glLoadIdentity();
290   glOrtho(0, viewport_size.width(), 0, viewport_size.height(), -1, 1);
291   glMatrixMode(GL_MODELVIEW);
292   glLoadIdentity();
294   // Draw a fullscreen quad.
295   glColor4f(1, 1, 1, 1);
296   glEnable(GL_TEXTURE_RECTANGLE_ARB);
297   glBindTexture(GL_TEXTURE_RECTANGLE_ARB, fbo_texture_);
298   glBegin(GL_QUADS);
299   {
300     glTexCoord2f(0, 0);
301     glVertex2f(0, 0);
303     glTexCoord2f(0, fbo_pixel_size_.height());
304     glVertex2f(0, fbo_pixel_size_.height());
306     glTexCoord2f(fbo_pixel_size_.width(), fbo_pixel_size_.height());
307     glVertex2f(fbo_pixel_size_.width(), fbo_pixel_size_.height());
309     glTexCoord2f(fbo_pixel_size_.width(), 0);
310     glVertex2f(fbo_pixel_size_.width(), 0);
311   }
312   glEnd();
313   glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
314   glDisable(GL_TEXTURE_RECTANGLE_ARB);
316   GLint current_renderer_id = 0;
317   if (CGLGetParameter(CGLGetCurrentContext(),
318                       kCGLCPCurrentRendererID,
319                       &current_renderer_id) == kCGLNoError) {
320     current_renderer_id &= kCGLRendererIDMatchingMask;
321     transport_surface_->SetRendererID(current_renderer_id);
322   }
324   // Allow forward progress in the context now that the swap is complete.
325   UnblockBrowserIfNeeded();
328 void CALayerStorageProvider::LayerResetStorageProvider() {
329   // If we are providing back-pressure by waiting for a draw, that draw will
330   // now never come, so release the pressure now.
331   UnblockBrowserIfNeeded();
334 void CALayerStorageProvider::UnblockBrowserIfNeeded() {
335   if (!has_pending_draw_)
336     return;
337   pending_draw_weak_factory_.InvalidateWeakPtrs();
338   has_pending_draw_ = false;
339   transport_surface_->SendSwapBuffers(
340       SurfaceHandleFromCAContextID([context_ contextId]),
341       fbo_pixel_size_,
342       fbo_scale_factor_);
345 }  //  namespace content