Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / content / common / gpu / image_transport_surface_calayer_mac.mm
blob75b735551037730fa4e87d953b514ff9c40e837a
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 <IOSurface/IOSurface.h>
8 #include <OpenGL/CGLRenderers.h>
9 #include <OpenGL/CGLIOSurface.h>
11 #include "base/command_line.h"
12 #include "base/mac/sdk_forward_declarations.h"
13 #include "base/trace_event/trace_event.h"
14 #include "gpu/config/gpu_info_collector.h"
15 #include "ui/accelerated_widget_mac/surface_handle_types.h"
16 #include "ui/base/cocoa/animation_utils.h"
17 #include "ui/base/ui_base_switches.h"
18 #include "ui/gfx/geometry/dip_util.h"
19 #include "ui/gfx/geometry/size_conversions.h"
20 #include "ui/gl/gl_gl_api_implementation.h"
21 #include "ui/gl/gl_switches.h"
22 #include "ui/gl/gpu_switching_manager.h"
24 namespace {
25 const size_t kFramesToKeepCAContextAfterDiscard = 2;
26 const size_t kCanDrawFalsesBeforeSwitchFromAsync = 4;
27 const base::TimeDelta kMinDeltaToSwitchToAsync =
28     base::TimeDelta::FromSecondsD(1. / 15.);
30 bool CanUseIOSurface() {
31   // Respect command line flags for the API's usage.
32   static bool forced_at_command_line =
33       base::CommandLine::ForCurrentProcess()->HasSwitch(
34           switches::kForceNSCGLSurfaceApi);
35   if (forced_at_command_line)
36     return true;
37   static bool disabled_at_command_line =
38       base::CommandLine::ForCurrentProcess()->HasSwitch(
39           switches::kDisableNSCGLSurfaceApi);
40   if (disabled_at_command_line)
41     return false;
43   // Ignore blacklist settings.
44   // TODO(ccameron): Remove fields for blacklist settings.
45   return true;
48 }  // namespace
50 // Private IOSurface API.
51 @interface IOSurface : NSObject
52 - (void)flushRect:(CGRect)rect;
53 - (void)attachToCGLContext:(CGLContextObj)cglContext;
54 - (id)initWithSize:(CGSize)size
55         colorSpace:(CGColorSpaceRef)colorSpace
56             atomic:(BOOL)atomic;
57 @property(readonly) CGImageRef image;
58 @property(readonly) id layerContents;
59 @end
61 // Private CALayer API.
62 @interface CALayer (Private)
63 - (void)setContentsChanged;
64 - (void)_didCommitLayer:(CATransaction*)transaction;
65 @end
67 @interface ImageTransportCAOpenGLLayer : CAOpenGLLayer {
68   content::CALayerStorageProvider* storageProvider_;
69   base::Closure didDrawCallback_;
71   // Used to determine if we should use setNeedsDisplay or setAsynchronous to
72   // animate. If the last swap time happened very recently, then
73   // setAsynchronous is used (which allows smooth animation, but comes with the
74   // penalty of the canDrawInCGLContext function waking up the process every
75   // vsync).
76   base::TimeTicks lastSynchronousSwapTime_;
78   // A counter that is incremented whenever LayerCanDraw returns false. If this
79   // reaches a threshold, then |layer_| is switched to synchronous drawing to
80   // save CPU work.
81   uint32 canDrawReturnedFalseCount_;
83   gfx::Size pixelSize_;
86 - (id)initWithStorageProvider:(content::CALayerStorageProvider*)storageProvider
87                     pixelSize:(gfx::Size)pixelSize
88                   scaleFactor:(float)scaleFactor;
89 - (void)requestDrawNewFrame;
90 - (void)drawPendingFrameImmediately;
91 - (void)resetStorageProvider;
92 @end
94 @interface ImageTransportIOSurface : CALayer {
95   content::CALayerStorageProvider* storageProvider_;
96   base::ScopedTypeRef<CGLContextObj> cglContext_;
97   base::ScopedCFTypeRef<IOSurfaceRef> ioSurface_;
98   GLuint glTexture_;
99   GLuint glFbo_;
100   gfx::Size pixelSize_;
103 - (id)initWithStorageProvider:(content::CALayerStorageProvider*)storageProvider
104                     pixelSize:(gfx::Size)pixelSize
105                   scaleFactor:(float)scaleFactor;
106 - (BOOL)drawNewFrame:(gfx::Rect)dirtyRect;
107 - (void)resetStorageProvider;
108 @end
110 @implementation ImageTransportCAOpenGLLayer
112 - (id)initWithStorageProvider:
113     (content::CALayerStorageProvider*)storageProvider
114                     pixelSize:(gfx::Size)pixelSize
115                   scaleFactor:(float)scaleFactor {
116   if (self = [super init]) {
117     gfx::Size dipSize = gfx::ConvertSizeToDIP(scaleFactor, pixelSize);
118     [self setContentsScale:scaleFactor];
119     [self setFrame:CGRectMake(0, 0, dipSize.width(), dipSize.height())];
120     storageProvider_ = storageProvider;
121     pixelSize_ = pixelSize;
122   }
123   return self;
126 - (void)requestDrawNewFrame {
127   // This tracing would be more natural to do with a pseudo-thread for each
128   // layer, rather than a counter.
129   // http://crbug.com/366300
130   // A trace value of 2 indicates that there is a pending swap ack. See
131   // canDrawInCGLContext for other value meanings.
132   TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 2);
134   if (![self isAsynchronous]) {
135     // Switch to asynchronous drawing only if we get two frames in rapid
136     // succession.
137     base::TimeTicks this_swap_time = base::TimeTicks::Now();
138     base::TimeDelta delta = this_swap_time - lastSynchronousSwapTime_;
139     if (delta <= kMinDeltaToSwitchToAsync) {
140       lastSynchronousSwapTime_ = base::TimeTicks();
141       [self setAsynchronous:YES];
142     } else {
143       lastSynchronousSwapTime_ = this_swap_time;
144       [self setNeedsDisplay];
145     }
146   }
149 - (void)drawPendingFrameImmediately {
150   DCHECK(storageProvider_->LayerHasPendingDraw());
151   if ([self isAsynchronous])
152     [self setAsynchronous:NO];
153   [self setNeedsDisplay];
154   [self displayIfNeeded];
157 - (void)resetStorageProvider {
158   storageProvider_ = NULL;
161 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
162   if (!storageProvider_)
163     return NULL;
164   return CGLRetainPixelFormat(CGLGetPixelFormat(
165       storageProvider_->LayerShareGroupContext()));
168 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
169   if (!storageProvider_)
170     return NULL;
171   CGLContextObj context = NULL;
172   CGLError error = CGLCreateContext(
173       pixelFormat, storageProvider_->LayerShareGroupContext(), &context);
174   if (error != kCGLNoError)
175     LOG(ERROR) << "CGLCreateContext failed with CGL error: " << error;
176   return context;
179 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
180                 pixelFormat:(CGLPixelFormatObj)pixelFormat
181                forLayerTime:(CFTimeInterval)timeInterval
182                 displayTime:(const CVTimeStamp*)timeStamp {
183   TRACE_EVENT0("gpu", "CALayerStorageProvider::LayerCanDraw");
185   if (!storageProvider_)
186     return NO;
188   if (storageProvider_->LayerHasPendingDraw()) {
189     // If there is a draw pending then increase the signal from 2 to 3, to
190     // indicate that there is a swap pending, and CoreAnimation has asked to
191     // draw it.
192     TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 3);
194     canDrawReturnedFalseCount_ = 0;
195     return YES;
196   } else {
197     // If there is not a draw pending, then give an instantaneous blip up from
198     // 0 to 1, indicating that CoreAnimation was ready to draw a frame but we
199     // were not (or didn't have new content to draw).
200     TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 1);
201     TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 0);
203     if ([self isAsynchronous]) {
204       // If we are in asynchronous mode, we will be getting callbacks at every
205       // vsync, asking us if we have anything to draw. If we get many of these
206       // in a row, ask that we stop getting these callback for now, so that we
207       // don't waste CPU cycles.
208       if (canDrawReturnedFalseCount_ >= kCanDrawFalsesBeforeSwitchFromAsync)
209         [self setAsynchronous:NO];
210       else
211         canDrawReturnedFalseCount_ += 1;
212     }
213     return NO;
214   }
217 - (void)drawInCGLContext:(CGLContextObj)glContext
218              pixelFormat:(CGLPixelFormatObj)pixelFormat
219             forLayerTime:(CFTimeInterval)timeInterval
220              displayTime:(const CVTimeStamp*)timeStamp {
221   // While in this callback, CoreAnimation has set |glContext| to be current.
222   // Ensure that the GL calls that we make are made against the native GL API.
223   gfx::ScopedSetGLToRealGLApi scoped_set_gl_api;
225   if (storageProvider_) {
226     storageProvider_->LayerDoDraw(gfx::Rect(pixelSize_), false);
227     storageProvider_->LayerUnblockBrowserIfNeeded();
228     // A trace value of 0 indicates that there is no longer a pending swap ack.
229     TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 0);
230   } else {
231     glClearColor(1, 1, 1, 1);
232     glClear(GL_COLOR_BUFFER_BIT);
233   }
234   [super drawInCGLContext:glContext
235               pixelFormat:pixelFormat
236              forLayerTime:timeInterval
237               displayTime:timeStamp];
240 @end
242 @implementation ImageTransportIOSurface
244 - (id)initWithStorageProvider:
245     (content::CALayerStorageProvider*)storageProvider
246                     pixelSize:(gfx::Size)pixelSize
247                   scaleFactor:(float)scaleFactor {
248   if (self = [super init]) {
249     ScopedCAActionDisabler disabler;
250     pixelSize_ = pixelSize;
251     gfx::Size dipSize = gfx::ConvertSizeToDIP(scaleFactor, pixelSize);
252     [self setContentsScale:scaleFactor];
253     [self setFrame:CGRectMake(0, 0, dipSize.width(), dipSize.height())];
254     storageProvider_ = storageProvider;
256     // Create a context in the share group of the storage provider. We will
257     // draw content using this context.
258     CGLError cglError = kCGLNoError;
259     cglError = CGLCreateContext(
260         CGLGetPixelFormat(storageProvider_->LayerShareGroupContext()),
261         storageProvider_->LayerShareGroupContext(),
262         cglContext_.InitializeInto());
263     LOG_IF(ERROR, cglError != kCGLNoError) <<
264         "Failed to create CGL context for IOSurface.";
266     // Create the IOSurface to set as the CALayer's contents.
267     uint32_t ioSurfacePixelFormat = 'BGRA';
268     NSDictionary* properties = @{
269         static_cast<NSString*>(kIOSurfaceWidth) : @(pixelSize.width()),
270         static_cast<NSString*>(kIOSurfaceHeight) : @(pixelSize.height()),
271         static_cast<NSString*>(kIOSurfacePixelFormat) : @(ioSurfacePixelFormat),
272         static_cast<NSString*>(kIOSurfaceBytesPerElement) : @(4),
273     };
274     ioSurface_.reset(IOSurfaceCreate(static_cast<CFDictionaryRef>(properties)));
275     if (!ioSurface_) {
276       LOG(ERROR) << "Failed to allocate IOSurface";
277       [self release];
278       return nil;
279     }
280     // Create a framebuffer view of the IOSurface.
281     {
282       gfx::ScopedCGLSetCurrentContext scopedSetCurrentContext(cglContext_);
283       gfx::ScopedSetGLToRealGLApi scopedSetRealGLApi;
284       bool gl_init_failed = false;
286       glGenTextures(1, &glTexture_);
287       glBindTexture(GL_TEXTURE_RECTANGLE_ARB, glTexture_);
288       CGLError cglError = CGLTexImageIOSurface2D(
289           cglContext_,
290           GL_TEXTURE_RECTANGLE_ARB,
291           GL_RGBA,
292           pixelSize.width(),
293           pixelSize.height(),
294           GL_BGRA,
295           GL_UNSIGNED_INT_8_8_8_8_REV,
296           ioSurface_,
297           0);
298       glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
299       if (cglError != kCGLNoError) {
300         DLOG(ERROR) << "CGLTexImageIOSurface2D failed with CGL error: "
301                     << cglError;
302         gl_init_failed = true;
303       }
304       glGenFramebuffersEXT(1, &glFbo_);
305       glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, glFbo_);
306       glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
307                                 GL_COLOR_ATTACHMENT0_EXT,
308                                 GL_TEXTURE_RECTANGLE_ARB,
309                                 glTexture_,
310                                 0);
311       GLenum fboStatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
312       glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
313       if (fboStatus != GL_FRAMEBUFFER_COMPLETE_EXT) {
314         DLOG(ERROR) << "Framebuffer was incomplete: " << fboStatus;
315         gl_init_failed = true;
316       }
318       // If initialization failed, ensure that the partially initialized GL
319       // objects do not leak into the sharegroup.
320       if (gl_init_failed) {
321         if (glTexture_)
322           glDeleteTextures(1, &glTexture_);
323         if (glFbo_)
324           glDeleteFramebuffersEXT(1, &glFbo_);
325         [self release];
326         return nil;
327       }
328     }
329   }
330   return self;
333 - (void)_didCommitLayer:(CATransaction*)transaction {
334   if (storageProvider_)
335     storageProvider_->LayerUnblockBrowserIfNeeded();
336   [super _didCommitLayer:transaction];
339 - (BOOL)drawNewFrame:(gfx::Rect)dirtyRect {
340   // Draw the first frame to the layer as covering the full layer. Subsequent
341   // frames may use partial damage.
342   if (![self contents])
343     dirtyRect = gfx::Rect(pixelSize_);
345   // Make the context current to the thread, make the surface be the current
346   // drawable for the context, and draw.
347   BOOL framebuffer_was_complete = YES;
348   {
349     gfx::ScopedCGLSetCurrentContext scopedSetCurrentContext(cglContext_);
350     gfx::ScopedSetGLToRealGLApi scopedSetRealGLApi;
351     glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, glFbo_);
353     // Another silent failure mechanism for the IOSurface API is to provide
354     // a backbuffer that is not framebuffer complete.
355     GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
356     if (status == GL_FRAMEBUFFER_COMPLETE_EXT) {
357       glViewport(0, 0, pixelSize_.width(), pixelSize_.height());
358       storageProvider_->LayerDoDraw(dirtyRect, true);
359       framebuffer_was_complete = YES;
360     } else {
361       framebuffer_was_complete = NO;
362     }
363     glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
364     glFlush();
365   }
366   if (!framebuffer_was_complete)
367     return NO;
369   if (![self contents]) {
370     // The first time we draw, set the layer contents and the CAContext's layer
371     {
372       ScopedCAActionDisabler disabler;
373       [self setContents:(id)ioSurface_.get()];
374     }
375     [storageProvider_->LayerCAContext() setLayer:self];
376   } else {
377     // For subsequent draws, just indicate that the layer contents has changed.
378     // This has lower power usage than calling -[CALayer setContents:].
379     [self setContentsChanged];
380   }
382   return YES;
385 - (void)resetStorageProvider {
386   {
387     gfx::ScopedCGLSetCurrentContext scopedSetCurrentContext(cglContext_);
388     gfx::ScopedSetGLToRealGLApi scopedSetRealGLApi;
389     glDeleteTextures(1, &glTexture_);
390     glDeleteFramebuffersEXT(1, &glFbo_);
391     glBegin(GL_TRIANGLES);
392     glEnd();
393   }
396 @end
398 namespace content {
400 CALayerStorageProvider::CALayerStorageProvider(
401     ImageTransportSurfaceFBO* transport_surface)
402     : transport_surface_(transport_surface),
403       gpu_vsync_disabled_(base::CommandLine::ForCurrentProcess()->HasSwitch(
404           switches::kDisableGpuVsync)),
405       throttling_disabled_(false),
406       has_pending_ack_(false),
407       fbo_texture_(0),
408       fbo_scale_factor_(1),
409       program_(0),
410       vertex_shader_(0),
411       fragment_shader_(0),
412       position_location_(0),
413       tex_location_(0),
414       vertex_buffer_(0),
415       vertex_array_(0),
416       recreate_layer_after_gpu_switch_(false),
417       pending_draw_weak_factory_(this) {
418   ui::GpuSwitchingManager::GetInstance()->AddObserver(this);
421 CALayerStorageProvider::~CALayerStorageProvider() {
422   ui::GpuSwitchingManager::GetInstance()->RemoveObserver(this);
425 gfx::Size CALayerStorageProvider::GetRoundedSize(gfx::Size size) {
426   return size;
429 bool CALayerStorageProvider::AllocateColorBufferStorage(
430     CGLContextObj context, const base::Closure& context_dirtied_callback,
431     GLuint texture, gfx::Size pixel_size, float scale_factor) {
432   // Allocate an ordinary OpenGL texture to back the FBO.
433   GLenum error;
434   while ((error = glGetError()) != GL_NO_ERROR) {
435     LOG(ERROR) << "OpenGL error hit but ignored before allocating buffer "
436                << "storage: " << error;
437   }
439   if (gfx::GetGLImplementation() ==
440       gfx::kGLImplementationDesktopGLCoreProfile) {
441     glTexImage2D(GL_TEXTURE_2D,
442                  0,
443                  GL_RGBA,
444                  pixel_size.width(),
445                  pixel_size.height(),
446                  0,
447                  GL_RGBA,
448                  GL_UNSIGNED_BYTE,
449                  NULL);
450     glFlush();
452     if (!vertex_shader_) {
453       const char* source =
454           "#version 150\n"
455           "in vec4 position;\n"
456           "out vec2 texcoord;\n"
457           "void main() {\n"
458           "    texcoord = vec2(position.x, position.y);\n"
459           "    gl_Position = vec4(2*position.x-1, 2*position.y-1,\n"
460           "        position.z, position.w);\n"
461           "}\n";
462       vertex_shader_ = glCreateShader(GL_VERTEX_SHADER);
463       glShaderSource(vertex_shader_, 1, &source, NULL);
464       glCompileShader(vertex_shader_);
465 #if DCHECK_IS_ON()
466       GLint status = GL_FALSE;
467       glGetShaderiv(vertex_shader_, GL_COMPILE_STATUS, &status);
468       DCHECK(status == GL_TRUE);
469 #endif
470     }
471     if (!fragment_shader_) {
472       const char* source =
473           "#version 150\n"
474           "uniform sampler2D tex;\n"
475           "in vec2 texcoord;\n"
476           "out vec4 frag_color;\n"
477           "void main() {\n"
478           "    frag_color = texture(tex, texcoord);\n"
479           "}\n";
480       fragment_shader_ = glCreateShader(GL_FRAGMENT_SHADER);
481       glShaderSource(fragment_shader_, 1, &source, NULL);
482       glCompileShader(fragment_shader_);
483 #if DCHECK_IS_ON()
484       GLint status = GL_FALSE;
485       glGetShaderiv(fragment_shader_, GL_COMPILE_STATUS, &status);
486       DCHECK(status == GL_TRUE);
487 #endif
488     }
489     if (!program_) {
490       program_ = glCreateProgram();
491       glAttachShader(program_, vertex_shader_);
492       glAttachShader(program_, fragment_shader_);
493       glBindFragDataLocation(program_, 0, "frag_color");
494       glLinkProgram(program_);
495 #if DCHECK_IS_ON()
496       GLint status = GL_FALSE;
497       glGetProgramiv(program_, GL_LINK_STATUS, &status);
498       DCHECK(status == GL_TRUE);
499 #endif
500       position_location_ = glGetAttribLocation(program_, "position");
501       tex_location_ = glGetUniformLocation(program_, "tex");
502     }
503     if (!vertex_buffer_) {
504       GLfloat vertex_data[24] = {
505         0, 0, 0, 1,
506         1, 0, 0, 1,
507         1, 1, 0, 1,
508         1, 1, 0, 1,
509         0, 1, 0, 1,
510         0, 0, 0, 1,
511       };
512       glGenBuffersARB(1, &vertex_buffer_);
513       // If the allocation path used GLContext::RestoreStateIfDirtiedExternally
514       // as the draw path does, this manual state restoration would not be
515       // necessary.
516       GLint bound_buffer = 0;
517       glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &bound_buffer);
518       glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
519       glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data),
520                    vertex_data, GL_STATIC_DRAW);
521       glBindBuffer(GL_ARRAY_BUFFER, bound_buffer);
522     }
523     if (!vertex_array_) {
524       // If the allocation path used GLContext::RestoreStateIfDirtiedExternally
525       // as the draw path does, this manual state restoration would not be
526       // necessary.
527       GLint bound_vao = 0;
528       glGetIntegerv(GL_VERTEX_ARRAY_BINDING_OES, &bound_vao);
529       glGenVertexArraysOES(1, &vertex_array_);
530       glBindVertexArrayOES(vertex_array_);
531       {
532         glEnableVertexAttribArray(position_location_);
533         GLint bound_buffer = 0;
534         glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &bound_buffer);
535         glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
536         glVertexAttribPointer(position_location_, 4, GL_FLOAT, GL_FALSE, 0, 0);
537         glBindBuffer(GL_ARRAY_BUFFER, bound_buffer);
538       }
539       glBindVertexArrayOES(bound_vao);
540     }
541   } else {
542     glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
543                  0,
544                  GL_RGBA,
545                  pixel_size.width(),
546                  pixel_size.height(),
547                  0,
548                  GL_RGBA,
549                  GL_UNSIGNED_BYTE,
550                  NULL);
551     glFlush();
552   }
554   bool hit_error = false;
555   while ((error = glGetError()) != GL_NO_ERROR) {
556     LOG(ERROR) << "OpenGL error hit while trying to allocate buffer storage: "
557                << error;
558     hit_error = true;
559   }
560   if (hit_error)
561     return false;
563   // Set the parameters that will be used to allocate the CALayer to draw the
564   // texture into.
565   share_group_context_.reset(CGLRetainContext(context));
566   share_group_context_dirtied_callback_ = context_dirtied_callback;
567   fbo_texture_ = texture;
568   fbo_pixel_size_ = pixel_size;
569   fbo_scale_factor_ = scale_factor;
570   return true;
573 void CALayerStorageProvider::FreeColorBufferStorage() {
574   if (gfx::GetGLImplementation() ==
575       gfx::kGLImplementationDesktopGLCoreProfile) {
576     if (vertex_shader_)
577       glDeleteShader(vertex_shader_);
578     if (fragment_shader_)
579       glDeleteShader(fragment_shader_);
580     if (program_)
581       glDeleteProgram(program_);
582     if (vertex_buffer_)
583       glDeleteBuffersARB(1, &vertex_buffer_);
584     if (vertex_array_)
585       glDeleteVertexArraysOES(1, &vertex_array_);
586     vertex_shader_ = 0;
587     fragment_shader_ = 0;
588     program_ = 0;
589     vertex_buffer_ = 0;
590     vertex_array_ = 0;
591   }
593   // Note that |context_| still holds a reference to |layer_|, and will until
594   // a new frame is swapped in.
595   ResetLayer();
597   share_group_context_.reset();
598   share_group_context_dirtied_callback_ = base::Closure();
599   fbo_texture_ = 0;
600   fbo_pixel_size_ = gfx::Size();
603 void CALayerStorageProvider::FrameSizeChanged(const gfx::Size& pixel_size,
604                                               float scale_factor) {
605   DCHECK_EQ(fbo_pixel_size_.ToString(), pixel_size.ToString());
606   DCHECK_EQ(fbo_scale_factor_, scale_factor);
609 void CALayerStorageProvider::SwapBuffers(const gfx::Rect& dirty_rect) {
610   TRACE_EVENT0("gpu", "CALayerStorageProvider::SwapBuffers");
611   DCHECK(!has_pending_ack_);
613   // Recreate the CALayer on the new GPU if a GPU switch has occurred. Note
614   // that the CAContext will retain a reference to the old CALayer until the
615   // call to -[CAContext setLayer:] replaces the old CALayer with the new one.
616   if (recreate_layer_after_gpu_switch_) {
617     ResetLayer();
618     recreate_layer_after_gpu_switch_ = false;
619   }
621   // Set the pending draw flag only after potentially destroying the old layer
622   // (otherwise destroying it will un-set the flag).
623   has_pending_ack_ = true;
625   // Allocate a CAContext to use to transport the CALayer to the browser
626   // process, if needed.
627   if (!context_) {
628     base::scoped_nsobject<NSDictionary> dict([[NSDictionary alloc] init]);
629     CGSConnectionID connection_id = CGSMainConnectionID();
630     context_.reset([CAContext contextWithCGSConnection:connection_id
631                                                options:dict]);
632     [context_ retain];
633   }
635   // Create the appropriate CALayer (if needed) and request that it draw.
636   bool should_draw_immediately = gpu_vsync_disabled_ || throttling_disabled_;
637   CreateLayerAndRequestDraw(should_draw_immediately, dirty_rect);
639   // CoreAnimation may not call the function to un-block the browser in a
640   // timely manner (or ever). Post a task to force the draw and un-block
641   // the browser (at the next cycle through the run-loop if drawing is to
642   // be immediate, and at a timeout of 1/6th of a second otherwise).
643   if (has_pending_ack_) {
644     base::MessageLoop::current()->PostDelayedTask(
645         FROM_HERE,
646         base::Bind(&CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser,
647                    pending_draw_weak_factory_.GetWeakPtr()),
648         should_draw_immediately ? base::TimeDelta() :
649                                   base::TimeDelta::FromSeconds(1) / 6);
650   }
653 void CALayerStorageProvider::CreateLayerAndRequestDraw(
654     bool should_draw_immediately, const gfx::Rect& dirty_rect) {
655   // Use the IOSurface API unless it is explicitly disabled.
656   if (CanUseIOSurface()) {
657     if (!io_surface_layer_) {
658       io_surface_layer_.reset([[ImageTransportIOSurface alloc]
659           initWithStorageProvider:this
660                         pixelSize:fbo_pixel_size_
661                       scaleFactor:fbo_scale_factor_]);
662     }
663     if (io_surface_layer_ && [io_surface_layer_ drawNewFrame:dirty_rect])
664       return;
666     // If the draw fails, destroy everything and try again next frame. The
667     // likely cause for this is video memory stress.
668     LOG(ERROR) << "Failed to allocate or draw IOSurface layer, "
669                << "page will be blank";
670     ResetLayer();
671   } else {
672     if (!ca_opengl_layer_) {
673       ca_opengl_layer_.reset([[ImageTransportCAOpenGLLayer alloc]
674           initWithStorageProvider:this
675                         pixelSize:fbo_pixel_size_
676                       scaleFactor:fbo_scale_factor_]);
677     }
679     // -[CAOpenGLLayer drawInCGLContext] won't get called until we're in the
680     // visible layer hierarchy, so call setLayer: immediately, to make this
681     // happen.
682     [context_ setLayer:ca_opengl_layer_];
684     // Tell CoreAnimation to draw our frame. Note that sometimes, calling
685     // -[CAContext setLayer:] will result in the layer getting an immediate
686     // draw. If that happend, we're done.
687     if (!should_draw_immediately && has_pending_ack_) {
688       [ca_opengl_layer_ requestDrawNewFrame];
689     }
690   }
693 void CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser() {
694   DCHECK(has_pending_ack_);
696   if (ca_opengl_layer_) {
697     // Beware that sometimes, the setNeedsDisplay+displayIfNeeded pairs have no
698     // effect. This can happen if the NSView that this layer is attached to
699     // isn't in the window hierarchy (e.g, tab capture of a backgrounded tab).
700     // In this case, the frame will never be seen, so drop it.
701     [ca_opengl_layer_ drawPendingFrameImmediately];
702   }
704   UnblockBrowserIfNeeded();
707 void CALayerStorageProvider::WillWriteToBackbuffer() {
708   // The browser should always throttle itself so that there are no pending
709   // draws when the output surface is written to, but in the event of things
710   // like context lost, or changing context, this will not be true. If there
711   // exists a pending draw, flush it immediately to maintain a consistent
712   // state.
713   if (has_pending_ack_)
714     DrawImmediatelyAndUnblockBrowser();
717 void CALayerStorageProvider::DiscardBackbuffer() {
718   // If this surface's backbuffer is discarded, it is because this surface has
719   // been made non-visible. Ensure that the previous contents are not briefly
720   // flashed when this is made visible by creating a new CALayer and CAContext
721   // at the next swap.
722   ResetLayer();
724   // If we remove all references to the CAContext in this process, it will be
725   // blanked-out in the browser process (even if the browser process is inside
726   // a disable screen updates block). Ensure that the context is kept around
727   // until a fixed number of frames (determined empirically) have been acked.
728   // http://crbug.com/425819
729   while (previously_discarded_contexts_.size() <
730       kFramesToKeepCAContextAfterDiscard) {
731     previously_discarded_contexts_.push_back(
732         base::scoped_nsobject<CAContext>());
733   }
734   previously_discarded_contexts_.push_back(context_);
736   context_.reset();
739 void CALayerStorageProvider::SwapBuffersAckedByBrowser(
740     bool disable_throttling) {
741   TRACE_EVENT0("gpu", "CALayerStorageProvider::SwapBuffersAckedByBrowser");
742   throttling_disabled_ = disable_throttling;
743   if (!previously_discarded_contexts_.empty())
744     previously_discarded_contexts_.pop_front();
747 CGLContextObj CALayerStorageProvider::LayerShareGroupContext() {
748   return share_group_context_;
751 base::Closure CALayerStorageProvider::LayerShareGroupContextDirtiedCallback() {
752   return share_group_context_dirtied_callback_;
755 void CALayerStorageProvider::LayerDoDraw(
756     const gfx::Rect& dirty_rect, bool flipped) {
757   TRACE_EVENT0("gpu", "CALayerStorageProvider::LayerDoDraw");
758   if (gfx::GetGLImplementation() ==
759       gfx::kGLImplementationDesktopGLCoreProfile) {
760     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
761     glClearColor(1, 0, 1, 1);
762     glClear(GL_COLOR_BUFFER_BIT);
763     glDisable(GL_BLEND);
764     glDisable(GL_CULL_FACE);
765     glDisable(GL_DEPTH_TEST);
766     glDisable(GL_STENCIL_TEST);
767     glDisable(GL_SCISSOR_TEST);
769     DCHECK(glIsProgram(program_));
770     glUseProgram(program_);
771     glBindVertexArrayOES(vertex_array_);
773     glActiveTexture(GL_TEXTURE0);
774     glBindTexture(GL_TEXTURE_2D, fbo_texture_);
775     glUniform1i(tex_location_, 0);
777     glDisable(GL_CULL_FACE);
778     glDrawArrays(GL_TRIANGLES, 0, 6);
779     glBindVertexArrayOES(0);
780     glUseProgram(0);
781   } else {
782     GLint viewport[4] = {0, 0, 0, 0};
783     glGetIntegerv(GL_VIEWPORT, viewport);
784     gfx::Size viewport_size(viewport[2], viewport[3]);
786     // Set the coordinate system to be one-to-one with pixels.
787     glMatrixMode(GL_PROJECTION);
788     glLoadIdentity();
789     if (flipped)
790       glOrtho(0, viewport_size.width(), viewport_size.height(), 0, -1, 1);
791     else
792       glOrtho(0, viewport_size.width(), 0, viewport_size.height(), -1, 1);
793     glMatrixMode(GL_MODELVIEW);
794     glLoadIdentity();
796     // Reset drawing state and draw a fullscreen quad.
797     glUseProgram(0);
798     glDisable(GL_BLEND);
799     glDisable(GL_CULL_FACE);
800     glDisable(GL_DEPTH_TEST);
801     glDisable(GL_STENCIL_TEST);
802     glDisable(GL_SCISSOR_TEST);
803     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
804     glColor4f(1, 1, 1, 1);
805     glActiveTexture(GL_TEXTURE0);
806     glEnable(GL_TEXTURE_RECTANGLE_ARB);
807     glBindTexture(GL_TEXTURE_RECTANGLE_ARB, fbo_texture_);
808     glBegin(GL_QUADS);
809     {
810       glTexCoord2f(dirty_rect.x(), dirty_rect.y());
811       glVertex2f(dirty_rect.x(), dirty_rect.y());
813       glTexCoord2f(dirty_rect.x(), dirty_rect.bottom());
814       glVertex2f(dirty_rect.x(), dirty_rect.bottom());
816       glTexCoord2f(dirty_rect.right(), dirty_rect.bottom());
817       glVertex2f(dirty_rect.right(), dirty_rect.bottom());
819       glTexCoord2f(dirty_rect.right(), dirty_rect.y());
820       glVertex2f(dirty_rect.right(), dirty_rect.y());
821     }
822     glEnd();
823     glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
824     glDisable(GL_TEXTURE_RECTANGLE_ARB);
825   }
827   GLint current_renderer_id = 0;
828   if (CGLGetParameter(CGLGetCurrentContext(),
829                       kCGLCPCurrentRendererID,
830                       &current_renderer_id) == kCGLNoError) {
831     current_renderer_id &= kCGLRendererIDMatchingMask;
832     transport_surface_->SetRendererID(current_renderer_id);
833   }
835   GLenum error;
836   while ((error = glGetError()) != GL_NO_ERROR) {
837     LOG(ERROR) << "OpenGL error hit while drawing frame: " << error;
838   }
841 void CALayerStorageProvider::LayerUnblockBrowserIfNeeded() {
842   UnblockBrowserIfNeeded();
845 bool CALayerStorageProvider::LayerHasPendingDraw() const {
846   return has_pending_ack_;
849 void CALayerStorageProvider::OnGpuSwitched() {
850   recreate_layer_after_gpu_switch_ = true;
853 void CALayerStorageProvider::UnblockBrowserIfNeeded() {
854   if (!has_pending_ack_)
855     return;
856   pending_draw_weak_factory_.InvalidateWeakPtrs();
857   has_pending_ack_ = false;
858   transport_surface_->SendSwapBuffers(
859       ui::SurfaceHandleFromCAContextID([context_ contextId]),
860       fbo_pixel_size_,
861       fbo_scale_factor_);
864 void CALayerStorageProvider::ResetLayer() {
865   if (ca_opengl_layer_) {
866     [ca_opengl_layer_ resetStorageProvider];
867     // If we are providing back-pressure by waiting for a draw, that draw will
868     // now never come, so release the pressure now.
869     UnblockBrowserIfNeeded();
870     ca_opengl_layer_.reset();
871   }
872   if (io_surface_layer_) {
873     [io_surface_layer_ resetStorageProvider];
875     io_surface_layer_.reset();
876     return;
877   }
880 }  //  namespace content