Supervised user whitelists: Cleanup
[chromium-blink-merge.git] / content / common / gpu / image_transport_surface_calayer_mac.mm
blob1aec294ce0ded5808170eac63d376a3c63638c39
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;
21 const base::TimeDelta kMinDeltaToSwitchToAsync =
22     base::TimeDelta::FromSecondsD(1. / 15.);
25 @interface ImageTransportLayer : CAOpenGLLayer {
26   content::CALayerStorageProvider* storageProvider_;
27   base::Closure didDrawCallback_;
29 - (id)initWithStorageProvider:(content::CALayerStorageProvider*)storageProvider;
30 - (void)resetStorageProvider;
31 @end
33 @implementation ImageTransportLayer
35 - (id)initWithStorageProvider:
36     (content::CALayerStorageProvider*)storageProvider {
37   if (self = [super init])
38     storageProvider_ = storageProvider;
39   return self;
42 - (void)resetStorageProvider {
43   if (storageProvider_)
44     storageProvider_->LayerResetStorageProvider();
45   storageProvider_ = NULL;
48 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
49   if (!storageProvider_)
50     return NULL;
51   return CGLRetainPixelFormat(CGLGetPixelFormat(
52       storageProvider_->LayerShareGroupContext()));
55 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
56   if (!storageProvider_)
57     return NULL;
58   didDrawCallback_ = storageProvider_->LayerShareGroupContextDirtiedCallback();
59   return CGLRetainContext(storageProvider_->LayerShareGroupContext());
62 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
63                 pixelFormat:(CGLPixelFormatObj)pixelFormat
64                forLayerTime:(CFTimeInterval)timeInterval
65                 displayTime:(const CVTimeStamp*)timeStamp {
66   if (!storageProvider_)
67     return NO;
68   return storageProvider_->LayerCanDraw();
71 - (void)drawInCGLContext:(CGLContextObj)glContext
72              pixelFormat:(CGLPixelFormatObj)pixelFormat
73             forLayerTime:(CFTimeInterval)timeInterval
74              displayTime:(const CVTimeStamp*)timeStamp {
75   // While in this callback, CoreAnimation has set |glContext| to be current.
76   // Ensure that the GL calls that we make are made against the native GL API.
77   gfx::ScopedSetGLToRealGLApi scoped_set_gl_api;
79   if (storageProvider_) {
80     storageProvider_->LayerDoDraw();
81   } else {
82     glClearColor(1, 1, 1, 1);
83     glClear(GL_COLOR_BUFFER_BIT);
84   }
85   [super drawInCGLContext:glContext
86               pixelFormat:pixelFormat
87              forLayerTime:timeInterval
88               displayTime:timeStamp];
91   DCHECK(!didDrawCallback_.is_null());
92   didDrawCallback_.Run();
95 @end
97 namespace content {
99 CALayerStorageProvider::CALayerStorageProvider(
100     ImageTransportSurfaceFBO* transport_surface)
101     : transport_surface_(transport_surface),
102       gpu_vsync_disabled_(base::CommandLine::ForCurrentProcess()->HasSwitch(
103           switches::kDisableGpuVsync)),
104       throttling_disabled_(false),
105       has_pending_draw_(false),
106       can_draw_returned_false_count_(0),
107       fbo_texture_(0),
108       fbo_scale_factor_(1),
109       program_(0),
110       vertex_shader_(0),
111       fragment_shader_(0),
112       position_location_(0),
113       tex_location_(0),
114       vertex_buffer_(0),
115       vertex_array_(0),
116       recreate_layer_after_gpu_switch_(false),
117       pending_draw_weak_factory_(this) {
118   ui::GpuSwitchingManager::GetInstance()->AddObserver(this);
121 CALayerStorageProvider::~CALayerStorageProvider() {
122   ui::GpuSwitchingManager::GetInstance()->RemoveObserver(this);
125 gfx::Size CALayerStorageProvider::GetRoundedSize(gfx::Size size) {
126   return size;
129 bool CALayerStorageProvider::AllocateColorBufferStorage(
130     CGLContextObj context, const base::Closure& context_dirtied_callback,
131     GLuint texture, gfx::Size pixel_size, float scale_factor) {
132   // Allocate an ordinary OpenGL texture to back the FBO.
133   GLenum error;
134   while ((error = glGetError()) != GL_NO_ERROR) {
135     LOG(ERROR) << "OpenGL error hit but ignored before allocating buffer "
136                << "storage: " << error;
137   }
139   if (gfx::GetGLImplementation() ==
140       gfx::kGLImplementationDesktopGLCoreProfile) {
141     glTexImage2D(GL_TEXTURE_2D,
142                  0,
143                  GL_RGBA,
144                  pixel_size.width(),
145                  pixel_size.height(),
146                  0,
147                  GL_RGBA,
148                  GL_UNSIGNED_BYTE,
149                  NULL);
150     glFlush();
152     if (!vertex_shader_) {
153       const char* source =
154           "#version 150\n"
155           "in vec4 position;\n"
156           "out vec2 texcoord;\n"
157           "void main() {\n"
158           "    texcoord = vec2(position.x, position.y);\n"
159           "    gl_Position = vec4(2*position.x-1, 2*position.y-1,\n"
160           "        position.z, position.w);\n"
161           "}\n";
162       vertex_shader_ = glCreateShader(GL_VERTEX_SHADER);
163       glShaderSource(vertex_shader_, 1, &source, NULL);
164       glCompileShader(vertex_shader_);
165 #if DCHECK_IS_ON()
166       GLint status = GL_FALSE;
167       glGetShaderiv(vertex_shader_, GL_COMPILE_STATUS, &status);
168       DCHECK(status == GL_TRUE);
169 #endif
170     }
171     if (!fragment_shader_) {
172       const char* source =
173           "#version 150\n"
174           "uniform sampler2D tex;\n"
175           "in vec2 texcoord;\n"
176           "out vec4 frag_color;\n"
177           "void main() {\n"
178           "    frag_color = texture(tex, texcoord);\n"
179           "}\n";
180       fragment_shader_ = glCreateShader(GL_FRAGMENT_SHADER);
181       glShaderSource(fragment_shader_, 1, &source, NULL);
182       glCompileShader(fragment_shader_);
183 #if DCHECK_IS_ON()
184       GLint status = GL_FALSE;
185       glGetShaderiv(fragment_shader_, GL_COMPILE_STATUS, &status);
186       DCHECK(status == GL_TRUE);
187 #endif
188     }
189     if (!program_) {
190       program_ = glCreateProgram();
191       glAttachShader(program_, vertex_shader_);
192       glAttachShader(program_, fragment_shader_);
193       glBindFragDataLocation(program_, 0, "frag_color");
194       glLinkProgram(program_);
195 #if DCHECK_IS_ON()
196       GLint status = GL_FALSE;
197       glGetProgramiv(program_, GL_LINK_STATUS, &status);
198       DCHECK(status == GL_TRUE);
199 #endif
200       position_location_ = glGetAttribLocation(program_, "position");
201       tex_location_ = glGetUniformLocation(program_, "tex");
202     }
203     if (!vertex_buffer_) {
204       GLfloat vertex_data[24] = {
205         0, 0, 0, 1,
206         1, 0, 0, 1,
207         1, 1, 0, 1,
208         1, 1, 0, 1,
209         0, 1, 0, 1,
210         0, 0, 0, 1,
211       };
212       glGenBuffersARB(1, &vertex_buffer_);
213       glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
214       glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data),
215                    vertex_data, GL_STATIC_DRAW);
216       glBindBuffer(GL_ARRAY_BUFFER, 0);
217     }
218     if (!vertex_array_) {
219       glGenVertexArraysOES(1, &vertex_array_);
220       glBindVertexArrayOES(vertex_array_);
221       {
222         glEnableVertexAttribArray(position_location_);
223         glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
224         glVertexAttribPointer(position_location_, 4, GL_FLOAT, GL_FALSE, 0, 0);
225         glBindBuffer(GL_ARRAY_BUFFER, 0);
226       }
227       glBindVertexArrayOES(0);
228     }
229   } else {
230     glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
231                  0,
232                  GL_RGBA,
233                  pixel_size.width(),
234                  pixel_size.height(),
235                  0,
236                  GL_RGBA,
237                  GL_UNSIGNED_BYTE,
238                  NULL);
239     glFlush();
240   }
242   bool hit_error = false;
243   while ((error = glGetError()) != GL_NO_ERROR) {
244     LOG(ERROR) << "OpenGL error hit while trying to allocate buffer storage: "
245                << error;
246     hit_error = true;
247   }
248   if (hit_error)
249     return false;
251   // Set the parameters that will be used to allocate the CALayer to draw the
252   // texture into.
253   share_group_context_.reset(CGLRetainContext(context));
254   share_group_context_dirtied_callback_ = context_dirtied_callback;
255   fbo_texture_ = texture;
256   fbo_pixel_size_ = pixel_size;
257   fbo_scale_factor_ = scale_factor;
258   return true;
261 void CALayerStorageProvider::FreeColorBufferStorage() {
262   if (gfx::GetGLImplementation() ==
263       gfx::kGLImplementationDesktopGLCoreProfile) {
264     if (vertex_shader_)
265       glDeleteShader(vertex_shader_);
266     if (fragment_shader_)
267       glDeleteShader(fragment_shader_);
268     if (program_)
269       glDeleteProgram(program_);
270     if (vertex_buffer_)
271       glDeleteBuffersARB(1, &vertex_buffer_);
272     if (vertex_array_)
273       glDeleteVertexArraysOES(1, &vertex_array_);
274     vertex_shader_ = 0;
275     fragment_shader_ = 0;
276     program_ = 0;
277     vertex_buffer_ = 0;
278     vertex_array_ = 0;
279   }
281   // Note that |context_| still holds a reference to |layer_|, and will until
282   // a new frame is swapped in.
283   [layer_ resetStorageProvider];
284   layer_.reset();
286   share_group_context_.reset();
287   share_group_context_dirtied_callback_ = base::Closure();
288   fbo_texture_ = 0;
289   fbo_pixel_size_ = gfx::Size();
290   can_draw_returned_false_count_ = 0;
293 void CALayerStorageProvider::FrameSizeChanged(const gfx::Size& pixel_size,
294                                               float scale_factor) {
295   DCHECK_EQ(fbo_pixel_size_.ToString(), pixel_size.ToString());
296   DCHECK_EQ(fbo_scale_factor_, scale_factor);
299 void CALayerStorageProvider::SwapBuffers() {
300   DCHECK(!has_pending_draw_);
302   // Recreate the CALayer on the new GPU if a GPU switch has occurred. Note
303   // that the CAContext will retain a reference to the old CALayer until the
304   // call to -[CAContext setLayer:] replaces the old CALayer with the new one.
305   if (recreate_layer_after_gpu_switch_) {
306     [layer_ resetStorageProvider];
307     layer_.reset();
308     recreate_layer_after_gpu_switch_ = false;
309   }
311   // Set the pending draw flag only after destroying the old layer (otherwise
312   // destroying it will un-set the flag).
313   has_pending_draw_ = true;
315   // Allocate a CAContext to use to transport the CALayer to the browser
316   // process, if needed.
317   if (!context_) {
318     base::scoped_nsobject<NSDictionary> dict([[NSDictionary alloc] init]);
319     CGSConnectionID connection_id = CGSMainConnectionID();
320     context_.reset([CAContext contextWithCGSConnection:connection_id
321                                                options:dict]);
322     [context_ retain];
323   }
325   // Allocate a CALayer to use to draw the content and make it current to the
326   // CAContext, if needed.
327   if (!layer_) {
328     layer_.reset([[ImageTransportLayer alloc] initWithStorageProvider:this]);
329     gfx::Size dip_size(gfx::ToFlooredSize(gfx::ScaleSize(
330         fbo_pixel_size_, 1.0f / fbo_scale_factor_)));
331     [layer_ setContentsScale:fbo_scale_factor_];
332     [layer_ setFrame:CGRectMake(0, 0, dip_size.width(), dip_size.height())];
334     [context_ setLayer:layer_];
335   }
337   // Replacing the CAContext's CALayer will sometimes results in an immediate
338   // draw.
339   if (!has_pending_draw_)
340     return;
342   // Tell CoreAnimation to draw our frame.
343   if (gpu_vsync_disabled_ || throttling_disabled_) {
344     DrawImmediatelyAndUnblockBrowser();
345   } else {
346     if (![layer_ isAsynchronous]) {
347       // Switch to asynchronous drawing only if we get two frames in rapid
348       // succession.
349       base::TimeTicks this_swap_time = base::TimeTicks::Now();
350       base::TimeDelta delta = this_swap_time - last_synchronous_swap_time_;
351       if (delta <= kMinDeltaToSwitchToAsync) {
352         last_synchronous_swap_time_ = base::TimeTicks();
353         [layer_ setAsynchronous:YES];
354       } else {
355         last_synchronous_swap_time_ = this_swap_time;
356         [layer_ setNeedsDisplay];
357       }
358     }
360     // If CoreAnimation doesn't end up drawing our frame, un-block the browser
361     // after a timeout of 1/6th of a second has passed.
362     base::MessageLoop::current()->PostDelayedTask(
363         FROM_HERE,
364         base::Bind(&CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser,
365                    pending_draw_weak_factory_.GetWeakPtr()),
366         base::TimeDelta::FromSeconds(1) / 6);
367   }
370 void CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser() {
371   CHECK(has_pending_draw_);
372   if ([layer_ isAsynchronous])
373     [layer_ setAsynchronous:NO];
374   [layer_ setNeedsDisplay];
375   [layer_ displayIfNeeded];
377   // Sometimes, the setNeedsDisplay+displayIfNeeded pairs have no effect. This
378   // can happen if the NSView that this layer is attached to isn't in the
379   // window hierarchy (e.g, tab capture of a backgrounded tab). In this case,
380   // the frame will never be seen, so drop it.
381   UnblockBrowserIfNeeded();
384 void CALayerStorageProvider::WillWriteToBackbuffer() {
385   // The browser should always throttle itself so that there are no pending
386   // draws when the output surface is written to, but in the event of things
387   // like context lost, or changing context, this will not be true. If there
388   // exists a pending draw, flush it immediately to maintain a consistent
389   // state.
390   if (has_pending_draw_)
391     DrawImmediatelyAndUnblockBrowser();
394 void CALayerStorageProvider::DiscardBackbuffer() {
395   // If this surface's backbuffer is discarded, it is because this surface has
396   // been made non-visible. Ensure that the previous contents are not briefly
397   // flashed when this is made visible by creating a new CALayer and CAContext
398   // at the next swap.
399   [layer_ resetStorageProvider];
400   layer_.reset();
402   // If we remove all references to the CAContext in this process, it will be
403   // blanked-out in the browser process (even if the browser process is inside
404   // a NSDisableScreenUpdates block). Ensure that the context is kept around
405   // until a fixed number of frames (determined empirically) have been acked.
406   // http://crbug.com/425819
407   while (previously_discarded_contexts_.size() <
408       kFramesToKeepCAContextAfterDiscard) {
409     previously_discarded_contexts_.push_back(
410         base::scoped_nsobject<CAContext>());
411   }
412   previously_discarded_contexts_.push_back(context_);
414   context_.reset();
417 void CALayerStorageProvider::SwapBuffersAckedByBrowser(
418     bool disable_throttling) {
419   throttling_disabled_ = disable_throttling;
420   if (!previously_discarded_contexts_.empty())
421     previously_discarded_contexts_.pop_front();
424 CGLContextObj CALayerStorageProvider::LayerShareGroupContext() {
425   return share_group_context_;
428 base::Closure CALayerStorageProvider::LayerShareGroupContextDirtiedCallback() {
429   return share_group_context_dirtied_callback_;
432 bool CALayerStorageProvider::LayerCanDraw() {
433   if (has_pending_draw_) {
434     can_draw_returned_false_count_ = 0;
435     return true;
436   } else {
437     if ([layer_ isAsynchronous]) {
438       DCHECK(!gpu_vsync_disabled_);
439       // If we are in asynchronous mode, we will be getting callbacks at every
440       // vsync, asking us if we have anything to draw. If we get many of these
441       // in a row, ask that we stop getting these callback for now, so that we
442       // don't waste CPU cycles.
443       if (can_draw_returned_false_count_ >= kCanDrawFalsesBeforeSwitchFromAsync)
444         [layer_ setAsynchronous:NO];
445       else
446         can_draw_returned_false_count_ += 1;
447     }
448     return false;
449   }
452 void CALayerStorageProvider::LayerDoDraw() {
453   if (gfx::GetGLImplementation() ==
454       gfx::kGLImplementationDesktopGLCoreProfile) {
455     glClearColor(1, 0, 1, 1);
456     glClear(GL_COLOR_BUFFER_BIT);
457     glDisable(GL_BLEND);
458     glDisable(GL_CULL_FACE);
459     glDisable(GL_DEPTH_TEST);
460     glDisable(GL_STENCIL_TEST);
461     glDisable(GL_SCISSOR_TEST);
463     DCHECK(glIsProgram(program_));
464     glUseProgram(program_);
465     glBindVertexArrayOES(vertex_array_);
467     glActiveTexture(GL_TEXTURE0);
468     glBindTexture(GL_TEXTURE_2D, fbo_texture_);
469     glUniform1i(tex_location_, 0);
471     glDisable(GL_CULL_FACE);
472     glDrawArrays(GL_TRIANGLES, 0, 6);
473     glBindVertexArrayOES(0);
474     glUseProgram(0);
475   } else {
476     GLint viewport[4] = {0, 0, 0, 0};
477     glGetIntegerv(GL_VIEWPORT, viewport);
478     gfx::Size viewport_size(viewport[2], viewport[3]);
480     // Set the coordinate system to be one-to-one with pixels.
481     glMatrixMode(GL_PROJECTION);
482     glLoadIdentity();
483     glOrtho(0, viewport_size.width(), 0, viewport_size.height(), -1, 1);
484     glMatrixMode(GL_MODELVIEW);
485     glLoadIdentity();
487     // Reset drawing state and draw a fullscreen quad.
488     glUseProgram(0);
489     glDisable(GL_BLEND);
490     glDisable(GL_CULL_FACE);
491     glDisable(GL_DEPTH_TEST);
492     glDisable(GL_STENCIL_TEST);
493     glDisable(GL_SCISSOR_TEST);
494     glColor4f(1, 1, 1, 1);
495     glActiveTexture(GL_TEXTURE0);
496     glEnable(GL_TEXTURE_RECTANGLE_ARB);
497     glBindTexture(GL_TEXTURE_RECTANGLE_ARB, fbo_texture_);
498     glBegin(GL_QUADS);
499     {
500       glTexCoord2f(0, 0);
501       glVertex2f(0, 0);
503       glTexCoord2f(0, fbo_pixel_size_.height());
504       glVertex2f(0, fbo_pixel_size_.height());
506       glTexCoord2f(fbo_pixel_size_.width(), fbo_pixel_size_.height());
507       glVertex2f(fbo_pixel_size_.width(), fbo_pixel_size_.height());
509       glTexCoord2f(fbo_pixel_size_.width(), 0);
510       glVertex2f(fbo_pixel_size_.width(), 0);
511     }
512     glEnd();
513     glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
514     glDisable(GL_TEXTURE_RECTANGLE_ARB);
515   }
517   GLint current_renderer_id = 0;
518   if (CGLGetParameter(CGLGetCurrentContext(),
519                       kCGLCPCurrentRendererID,
520                       &current_renderer_id) == kCGLNoError) {
521     current_renderer_id &= kCGLRendererIDMatchingMask;
522     transport_surface_->SetRendererID(current_renderer_id);
523   }
525   GLenum error;
526   while ((error = glGetError()) != GL_NO_ERROR) {
527     LOG(ERROR) << "OpenGL error hit while drawing frame: " << error;
528   }
530   // Allow forward progress in the context now that the swap is complete.
531   UnblockBrowserIfNeeded();
534 void CALayerStorageProvider::LayerResetStorageProvider() {
535   // If we are providing back-pressure by waiting for a draw, that draw will
536   // now never come, so release the pressure now.
537   UnblockBrowserIfNeeded();
540 void CALayerStorageProvider::OnGpuSwitched() {
541   recreate_layer_after_gpu_switch_ = true;
544 void CALayerStorageProvider::UnblockBrowserIfNeeded() {
545   if (!has_pending_draw_)
546     return;
547   pending_draw_weak_factory_.InvalidateWeakPtrs();
548   has_pending_draw_ = false;
549   transport_surface_->SendSwapBuffers(
550       ui::SurfaceHandleFromCAContextID([context_ contextId]),
551       fbo_pixel_size_,
552       fbo_scale_factor_);
555 }  //  namespace content