Refactors gesture conversion functions to ui/events/blink
[chromium-blink-merge.git] / content / common / gpu / image_transport_surface_calayer_mac.mm
blobafcfc569c794d6254b4f3a911726441feea7f827
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 "ui/accelerated_widget_mac/surface_handle_types.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"
16 #include "ui/gl/gpu_switching_manager.h"
18 namespace {
19 const size_t kFramesToKeepCAContextAfterDiscard = 2;
20 const size_t kCanDrawFalsesBeforeSwitchFromAsync = 4;
23 @interface ImageTransportLayer : CAOpenGLLayer {
24   content::CALayerStorageProvider* storageProvider_;
25   base::Closure didDrawCallback_;
27 - (id)initWithStorageProvider:(content::CALayerStorageProvider*)storageProvider;
28 - (void)resetStorageProvider;
29 @end
31 @implementation ImageTransportLayer
33 - (id)initWithStorageProvider:
34     (content::CALayerStorageProvider*)storageProvider {
35   if (self = [super init])
36     storageProvider_ = storageProvider;
37   return self;
40 - (void)resetStorageProvider {
41   if (storageProvider_)
42     storageProvider_->LayerResetStorageProvider();
43   storageProvider_ = NULL;
46 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
47   if (!storageProvider_)
48     return NULL;
49   return CGLRetainPixelFormat(CGLGetPixelFormat(
50       storageProvider_->LayerShareGroupContext()));
53 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
54   if (!storageProvider_)
55     return NULL;
56   didDrawCallback_ = storageProvider_->LayerShareGroupContextDirtiedCallback();
57   return CGLRetainContext(storageProvider_->LayerShareGroupContext());
60 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
61                 pixelFormat:(CGLPixelFormatObj)pixelFormat
62                forLayerTime:(CFTimeInterval)timeInterval
63                 displayTime:(const CVTimeStamp*)timeStamp {
64   if (!storageProvider_)
65     return NO;
66   return storageProvider_->LayerCanDraw();
69 - (void)drawInCGLContext:(CGLContextObj)glContext
70              pixelFormat:(CGLPixelFormatObj)pixelFormat
71             forLayerTime:(CFTimeInterval)timeInterval
72              displayTime:(const CVTimeStamp*)timeStamp {
73   // While in this callback, CoreAnimation has set |glContext| to be current.
74   // Ensure that the GL calls that we make are made against the native GL API.
75   gfx::ScopedSetGLToRealGLApi scoped_set_gl_api;
77   if (storageProvider_) {
78     storageProvider_->LayerDoDraw();
79   } else {
80     glClearColor(1, 1, 1, 1);
81     glClear(GL_COLOR_BUFFER_BIT);
82   }
83   [super drawInCGLContext:glContext
84               pixelFormat:pixelFormat
85              forLayerTime:timeInterval
86               displayTime:timeStamp];
89   DCHECK(!didDrawCallback_.is_null());
90   didDrawCallback_.Run();
93 @end
95 namespace content {
97 CALayerStorageProvider::CALayerStorageProvider(
98     ImageTransportSurfaceFBO* transport_surface)
99     : transport_surface_(transport_surface),
100       gpu_vsync_disabled_(base::CommandLine::ForCurrentProcess()->HasSwitch(
101           switches::kDisableGpuVsync)),
102       throttling_disabled_(false),
103       has_pending_draw_(false),
104       can_draw_returned_false_count_(0),
105       fbo_texture_(0),
106       fbo_scale_factor_(1),
107       recreate_layer_after_gpu_switch_(false),
108       pending_draw_weak_factory_(this) {
109   ui::GpuSwitchingManager::GetInstance()->AddObserver(this);
112 CALayerStorageProvider::~CALayerStorageProvider() {
113   ui::GpuSwitchingManager::GetInstance()->RemoveObserver(this);
116 gfx::Size CALayerStorageProvider::GetRoundedSize(gfx::Size size) {
117   return size;
120 bool CALayerStorageProvider::AllocateColorBufferStorage(
121     CGLContextObj context, const base::Closure& context_dirtied_callback,
122     GLuint texture, gfx::Size pixel_size, float scale_factor) {
123   // Allocate an ordinary OpenGL texture to back the FBO.
124   GLenum error;
125   while ((error = glGetError()) != GL_NO_ERROR) {
126     LOG(ERROR) << "OpenGL error hit but ignored before allocating buffer "
127                << "storage: " << error;
128   }
129   glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
130                0,
131                GL_RGBA,
132                pixel_size.width(),
133                pixel_size.height(),
134                0,
135                GL_RGBA,
136                GL_UNSIGNED_BYTE,
137                NULL);
138   glFlush();
140   bool hit_error = false;
141   while ((error = glGetError()) != GL_NO_ERROR) {
142     LOG(ERROR) << "OpenGL error hit while trying to allocate buffer storage: "
143                << error;
144     hit_error = true;
145   }
146   if (hit_error)
147     return false;
149   // Set the parameters that will be used to allocate the CALayer to draw the
150   // texture into.
151   share_group_context_.reset(CGLRetainContext(context));
152   share_group_context_dirtied_callback_ = context_dirtied_callback;
153   fbo_texture_ = texture;
154   fbo_pixel_size_ = pixel_size;
155   fbo_scale_factor_ = scale_factor;
156   return true;
159 void CALayerStorageProvider::FreeColorBufferStorage() {
160   // Note that |context_| still holds a reference to |layer_|, and will until
161   // a new frame is swapped in.
162   [layer_ resetStorageProvider];
163   layer_.reset();
165   share_group_context_.reset();
166   share_group_context_dirtied_callback_ = base::Closure();
167   fbo_texture_ = 0;
168   fbo_pixel_size_ = gfx::Size();
169   can_draw_returned_false_count_ = 0;
172 void CALayerStorageProvider::SwapBuffers(
173     const gfx::Size& size, float scale_factor) {
174   DCHECK(!has_pending_draw_);
176   // Recreate the CALayer on the new GPU if a GPU switch has occurred. Note
177   // that the CAContext will retain a reference to the old CALayer until the
178   // call to -[CAContext setLayer:] replaces the old CALayer with the new one.
179   if (recreate_layer_after_gpu_switch_) {
180     [layer_ resetStorageProvider];
181     layer_.reset();
182     recreate_layer_after_gpu_switch_ = false;
183   }
185   // Set the pending draw flag only after destroying the old layer (otherwise
186   // destroying it will un-set the flag).
187   has_pending_draw_ = true;
189   // Allocate a CAContext to use to transport the CALayer to the browser
190   // process, if needed.
191   if (!context_) {
192     base::scoped_nsobject<NSDictionary> dict([[NSDictionary alloc] init]);
193     CGSConnectionID connection_id = CGSMainConnectionID();
194     context_.reset([CAContext contextWithCGSConnection:connection_id
195                                                options:dict]);
196     [context_ retain];
197   }
199   // Allocate a CALayer to use to draw the content and make it current to the
200   // CAContext, if needed.
201   if (!layer_) {
202     layer_.reset([[ImageTransportLayer alloc] initWithStorageProvider:this]);
203     gfx::Size dip_size(gfx::ToFlooredSize(gfx::ScaleSize(
204         fbo_pixel_size_, 1.0f / fbo_scale_factor_)));
205     [layer_ setContentsScale:fbo_scale_factor_];
206     [layer_ setFrame:CGRectMake(0, 0, dip_size.width(), dip_size.height())];
208     [context_ setLayer:layer_];
209   }
211   // Replacing the CAContext's CALayer will sometimes results in an immediate
212   // draw.
213   if (!has_pending_draw_)
214     return;
216   // Tell CoreAnimation to draw our frame.
217   if (gpu_vsync_disabled_ || throttling_disabled_) {
218     DrawImmediatelyAndUnblockBrowser();
219   } else {
220     if (![layer_ isAsynchronous])
221       [layer_ setAsynchronous:YES];
223     // If CoreAnimation doesn't end up drawing our frame, un-block the browser
224     // after a timeout of 1/6th of a second has passed.
225     base::MessageLoop::current()->PostDelayedTask(
226         FROM_HERE,
227         base::Bind(&CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser,
228                    pending_draw_weak_factory_.GetWeakPtr()),
229         base::TimeDelta::FromSeconds(1) / 6);
230   }
233 void CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser() {
234   CHECK(has_pending_draw_);
235   if ([layer_ isAsynchronous])
236     [layer_ setAsynchronous:NO];
237   [layer_ setNeedsDisplay];
238   [layer_ displayIfNeeded];
240   // Sometimes, the setNeedsDisplay+displayIfNeeded pairs have no effect. This
241   // can happen if the NSView that this layer is attached to isn't in the
242   // window hierarchy (e.g, tab capture of a backgrounded tab). In this case,
243   // the frame will never be seen, so drop it.
244   UnblockBrowserIfNeeded();
247 void CALayerStorageProvider::WillWriteToBackbuffer() {
248   // The browser should always throttle itself so that there are no pending
249   // draws when the output surface is written to, but in the event of things
250   // like context lost, or changing context, this will not be true. If there
251   // exists a pending draw, flush it immediately to maintain a consistent
252   // state.
253   if (has_pending_draw_)
254     DrawImmediatelyAndUnblockBrowser();
257 void CALayerStorageProvider::DiscardBackbuffer() {
258   // If this surface's backbuffer is discarded, it is because this surface has
259   // been made non-visible. Ensure that the previous contents are not briefly
260   // flashed when this is made visible by creating a new CALayer and CAContext
261   // at the next swap.
262   [layer_ resetStorageProvider];
263   layer_.reset();
265   // If we remove all references to the CAContext in this process, it will be
266   // blanked-out in the browser process (even if the browser process is inside
267   // a NSDisableScreenUpdates block). Ensure that the context is kept around
268   // until a fixed number of frames (determined empirically) have been acked.
269   // http://crbug.com/425819
270   while (previously_discarded_contexts_.size() <
271       kFramesToKeepCAContextAfterDiscard) {
272     previously_discarded_contexts_.push_back(
273         base::scoped_nsobject<CAContext>());
274   }
275   previously_discarded_contexts_.push_back(context_);
277   context_.reset();
280 void CALayerStorageProvider::SwapBuffersAckedByBrowser(
281     bool disable_throttling) {
282   throttling_disabled_ = disable_throttling;
283   if (!previously_discarded_contexts_.empty())
284     previously_discarded_contexts_.pop_front();
287 CGLContextObj CALayerStorageProvider::LayerShareGroupContext() {
288   return share_group_context_;
291 base::Closure CALayerStorageProvider::LayerShareGroupContextDirtiedCallback() {
292   return share_group_context_dirtied_callback_;
295 bool CALayerStorageProvider::LayerCanDraw() {
296   if (has_pending_draw_) {
297     can_draw_returned_false_count_ = 0;
298     return true;
299   } else {
300     if ([layer_ isAsynchronous]) {
301       DCHECK(!gpu_vsync_disabled_);
302       // If we are in asynchronous mode, we will be getting callbacks at every
303       // vsync, asking us if we have anything to draw. If we get many of these
304       // in a row, ask that we stop getting these callback for now, so that we
305       // don't waste CPU cycles.
306       if (can_draw_returned_false_count_ >= kCanDrawFalsesBeforeSwitchFromAsync)
307         [layer_ setAsynchronous:NO];
308       else
309         can_draw_returned_false_count_ += 1;
310     }
311     return false;
312   }
315 void CALayerStorageProvider::LayerDoDraw() {
316   GLint viewport[4] = {0, 0, 0, 0};
317   glGetIntegerv(GL_VIEWPORT, viewport);
318   gfx::Size viewport_size(viewport[2], viewport[3]);
320   // Set the coordinate system to be one-to-one with pixels.
321   glMatrixMode(GL_PROJECTION);
322   glLoadIdentity();
323   glOrtho(0, viewport_size.width(), 0, viewport_size.height(), -1, 1);
324   glMatrixMode(GL_MODELVIEW);
325   glLoadIdentity();
327   // Reset drawing state and draw a fullscreen quad.
328   glUseProgram(0);
329   glDisable(GL_BLEND);
330   glDisable(GL_CULL_FACE);
331   glDisable(GL_DEPTH_TEST);
332   glDisable(GL_STENCIL_TEST);
333   glDisable(GL_SCISSOR_TEST);
334   glColor4f(1, 1, 1, 1);
335   glActiveTexture(GL_TEXTURE0);
336   glEnable(GL_TEXTURE_RECTANGLE_ARB);
337   glBindTexture(GL_TEXTURE_RECTANGLE_ARB, fbo_texture_);
338   glBegin(GL_QUADS);
339   {
340     glTexCoord2f(0, 0);
341     glVertex2f(0, 0);
343     glTexCoord2f(0, fbo_pixel_size_.height());
344     glVertex2f(0, fbo_pixel_size_.height());
346     glTexCoord2f(fbo_pixel_size_.width(), fbo_pixel_size_.height());
347     glVertex2f(fbo_pixel_size_.width(), fbo_pixel_size_.height());
349     glTexCoord2f(fbo_pixel_size_.width(), 0);
350     glVertex2f(fbo_pixel_size_.width(), 0);
351   }
352   glEnd();
353   glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
354   glDisable(GL_TEXTURE_RECTANGLE_ARB);
356   GLint current_renderer_id = 0;
357   if (CGLGetParameter(CGLGetCurrentContext(),
358                       kCGLCPCurrentRendererID,
359                       &current_renderer_id) == kCGLNoError) {
360     current_renderer_id &= kCGLRendererIDMatchingMask;
361     transport_surface_->SetRendererID(current_renderer_id);
362   }
364   GLenum error;
365   while ((error = glGetError()) != GL_NO_ERROR) {
366     LOG(ERROR) << "OpenGL error hit while drawing frame: " << error;
367   }
369   // Allow forward progress in the context now that the swap is complete.
370   UnblockBrowserIfNeeded();
373 void CALayerStorageProvider::LayerResetStorageProvider() {
374   // If we are providing back-pressure by waiting for a draw, that draw will
375   // now never come, so release the pressure now.
376   UnblockBrowserIfNeeded();
379 void CALayerStorageProvider::OnGpuSwitched() {
380   recreate_layer_after_gpu_switch_ = true;
383 void CALayerStorageProvider::UnblockBrowserIfNeeded() {
384   if (!has_pending_draw_)
385     return;
386   pending_draw_weak_factory_.InvalidateWeakPtrs();
387   has_pending_draw_ = false;
388   transport_surface_->SendSwapBuffers(
389       ui::SurfaceHandleFromCAContextID([context_ contextId]),
390       fbo_pixel_size_,
391       fbo_scale_factor_);
394 }  //  namespace content