Mac: Actually enable NSCGLSurface API
[chromium-blink-merge.git] / content / common / gpu / image_transport_surface_calayer_mac.mm
blob23bb714d5fd00c46831cb0c1fc9be1a98da3e880
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 "base/trace_event/trace_event.h"
12 #include "gpu/config/gpu_info_collector.h"
13 #include "ui/accelerated_widget_mac/surface_handle_types.h"
14 #include "ui/base/cocoa/animation_utils.h"
15 #include "ui/base/ui_base_switches.h"
16 #include "ui/gfx/geometry/dip_util.h"
17 #include "ui/gfx/geometry/size_conversions.h"
18 #include "ui/gl/gl_gl_api_implementation.h"
19 #include "ui/gl/gl_switches.h"
20 #include "ui/gl/gpu_switching_manager.h"
22 namespace {
23 const size_t kFramesToKeepCAContextAfterDiscard = 2;
24 const size_t kCanDrawFalsesBeforeSwitchFromAsync = 4;
25 const base::TimeDelta kMinDeltaToSwitchToAsync =
26     base::TimeDelta::FromSecondsD(1. / 15.);
28 bool CanUseNSCGLSurface(const gpu::gles2::FeatureInfo* feature_info) {
29   // Respect command line flags for the API's usage.
30   static bool forced_at_command_line =
31       base::CommandLine::ForCurrentProcess()->HasSwitch(
32           switches::kForceNSCGLSurfaceApi);
33   if (forced_at_command_line)
34     return true;
35   static bool disabled_at_command_line =
36       base::CommandLine::ForCurrentProcess()->HasSwitch(
37           switches::kDisableNSCGLSurfaceApi);
38   if (disabled_at_command_line)
39     return false;
41   // If there are multiple displays connected, then it is possible that we will
42   // end up on the slow path, where -[NSCGLSurface layerContents] will return
43   // a CGImage that is a dearly-made copy of the surface. Since we don't yet
44   // know how to avoid those sharp edges, just avoid using NSCGLSurface when
45   // multiple screens are present.
46   uint32_t count = 0;
47   CGError cg_error = CGGetActiveDisplayList(count, NULL, &count);
48   if (cg_error != kCGErrorSuccess) {
49     LOG(ERROR) << "Failed to query the number of displays.";
50     return false;
51   }
52   if (count != 1)
53     return false;
55   // Systems with multiple GPUs can exhibit problems where incorrect content
56   // will briefly flash during resize, and especially during transitions between
57   // the iGPU and the dGPU. These problems are exhibited by layer-backed
58   // NSOpenGLViews as well.
59   if (feature_info->workarounds().disable_ns_cgl_surface_api)
60     return false;
62   return true;
65 }  // namespace
67 // Private NSCGLSurface API.
68 @interface NSCGLSurface : NSObject
69 - (void)flushRect:(CGRect)rect;
70 - (void)attachToCGLContext:(CGLContextObj)cglContext;
71 - (id)initWithSize:(CGSize)size
72         colorSpace:(CGColorSpaceRef)colorSpace
73             atomic:(BOOL)atomic;
74 @property(readonly) CGImageRef image;
75 @property(readonly) id layerContents;
76 @end
78 // Private CALayer API.
79 @interface CALayer (Private)
80 - (void)setContentsChanged;
81 @end
83 @interface ImageTransportCAOpenGLLayer : CAOpenGLLayer <ImageTransportLayer> {
84   content::CALayerStorageProvider* storageProvider_;
85   base::Closure didDrawCallback_;
87   // Used to determine if we should use setNeedsDisplay or setAsynchronous to
88   // animate. If the last swap time happened very recently, then
89   // setAsynchronous is used (which allows smooth animation, but comes with the
90   // penalty of the canDrawInCGLContext function waking up the process every
91   // vsync).
92   base::TimeTicks lastSynchronousSwapTime_;
94   // A counter that is incremented whenever LayerCanDraw returns false. If this
95   // reaches a threshold, then |layer_| is switched to synchronous drawing to
96   // save CPU work.
97   uint32 canDrawReturnedFalseCount_;
99   gfx::Size pixelSize_;
102 - (id)initWithStorageProvider:(content::CALayerStorageProvider*)storageProvider
103                     pixelSize:(gfx::Size)pixelSize
104                   scaleFactor:(float)scaleFactor;
105 - (void)drawNewFrame:(gfx::Rect)dirtyRect;
106 - (void)drawPendingFrameImmediately;
107 - (void)resetStorageProvider;
108 @end
110 @interface ImageTransportNSCGLSurface : CALayer <ImageTransportLayer> {
111   content::CALayerStorageProvider* storageProvider_;
112   base::ScopedTypeRef<CGLContextObj> cglContext_;
113   base::scoped_nsobject<NSCGLSurface> surface_;
114   gfx::Size pixelSize_;
117 - (id)initWithStorageProvider:(content::CALayerStorageProvider*)storageProvider
118                     pixelSize:(gfx::Size)pixelSize
119                   scaleFactor:(float)scaleFactor;
120 - (void)drawNewFrame:(gfx::Rect)dirtyRect;
121 - (void)drawPendingFrameImmediately;
122 - (void)resetStorageProvider;
123 @end
125 @implementation ImageTransportCAOpenGLLayer
127 - (id)initWithStorageProvider:
128     (content::CALayerStorageProvider*)storageProvider
129                     pixelSize:(gfx::Size)pixelSize
130                   scaleFactor:(float)scaleFactor {
131   if (self = [super init]) {
132     gfx::Size dipSize = gfx::ConvertSizeToDIP(scaleFactor, pixelSize);
133     [self setContentsScale:scaleFactor];
134     [self setFrame:CGRectMake(0, 0, dipSize.width(), dipSize.height())];
135     storageProvider_ = storageProvider;
136     pixelSize_ = pixelSize;
138     // -[CAOpenGLLayer drawInCGLContext] won't get called until we're in the
139     // visible layer hierarchy, so call setLayer: immediately, to make this
140     // happen.
141     [storageProvider_->LayerCAContext() setLayer:self];
142   }
143   return self;
146 - (void)drawNewFrame:(gfx::Rect)dirtyRect {
147   // This tracing would be more natural to do with a pseudo-thread for each
148   // layer, rather than a counter.
149   // http://crbug.com/366300
150   // A trace value of 2 indicates that there is a pending swap ack. See
151   // canDrawInCGLContext for other value meanings.
152   TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 2);
154   if (![self isAsynchronous]) {
155     // Switch to asynchronous drawing only if we get two frames in rapid
156     // succession.
157     base::TimeTicks this_swap_time = base::TimeTicks::Now();
158     base::TimeDelta delta = this_swap_time - lastSynchronousSwapTime_;
159     if (delta <= kMinDeltaToSwitchToAsync) {
160       lastSynchronousSwapTime_ = base::TimeTicks();
161       [self setAsynchronous:YES];
162     } else {
163       lastSynchronousSwapTime_ = this_swap_time;
164       [self setNeedsDisplay];
165     }
166   }
169 - (void)drawPendingFrameImmediately {
170   DCHECK(storageProvider_->LayerHasPendingDraw());
171   if ([self isAsynchronous])
172     [self setAsynchronous:NO];
173   [self setNeedsDisplay];
174   [self displayIfNeeded];
177 - (void)resetStorageProvider {
178   storageProvider_ = NULL;
181 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
182   if (!storageProvider_)
183     return NULL;
184   return CGLRetainPixelFormat(CGLGetPixelFormat(
185       storageProvider_->LayerShareGroupContext()));
188 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
189   if (!storageProvider_)
190     return NULL;
191   didDrawCallback_ = storageProvider_->LayerShareGroupContextDirtiedCallback();
192   return CGLRetainContext(storageProvider_->LayerShareGroupContext());
195 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
196                 pixelFormat:(CGLPixelFormatObj)pixelFormat
197                forLayerTime:(CFTimeInterval)timeInterval
198                 displayTime:(const CVTimeStamp*)timeStamp {
199   TRACE_EVENT0("gpu", "CALayerStorageProvider::LayerCanDraw");
201   if (!storageProvider_)
202     return NO;
204   if (storageProvider_->LayerHasPendingDraw()) {
205     // If there is a draw pending then increase the signal from 2 to 3, to
206     // indicate that there is a swap pending, and CoreAnimation has asked to
207     // draw it.
208     TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 3);
210     canDrawReturnedFalseCount_ = 0;
211     return YES;
212   } else {
213     // If there is not a draw pending, then give an instantaneous blip up from
214     // 0 to 1, indicating that CoreAnimation was ready to draw a frame but we
215     // were not (or didn't have new content to draw).
216     TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 1);
217     TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 0);
219     if ([self isAsynchronous]) {
220       // If we are in asynchronous mode, we will be getting callbacks at every
221       // vsync, asking us if we have anything to draw. If we get many of these
222       // in a row, ask that we stop getting these callback for now, so that we
223       // don't waste CPU cycles.
224       if (canDrawReturnedFalseCount_ >= kCanDrawFalsesBeforeSwitchFromAsync)
225         [self setAsynchronous:NO];
226       else
227         canDrawReturnedFalseCount_ += 1;
228     }
229     return NO;
230   }
233 - (void)drawInCGLContext:(CGLContextObj)glContext
234              pixelFormat:(CGLPixelFormatObj)pixelFormat
235             forLayerTime:(CFTimeInterval)timeInterval
236              displayTime:(const CVTimeStamp*)timeStamp {
237   // While in this callback, CoreAnimation has set |glContext| to be current.
238   // Ensure that the GL calls that we make are made against the native GL API.
239   gfx::ScopedSetGLToRealGLApi scoped_set_gl_api;
241   if (storageProvider_) {
242     storageProvider_->LayerDoDraw(gfx::Rect(pixelSize_));
244     // A trace value of 0 indicates that there is no longer a pending swap ack.
245     TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 0);
246   } else {
247     glClearColor(1, 1, 1, 1);
248     glClear(GL_COLOR_BUFFER_BIT);
249   }
250   [super drawInCGLContext:glContext
251               pixelFormat:pixelFormat
252              forLayerTime:timeInterval
253               displayTime:timeStamp];
255   DCHECK(!didDrawCallback_.is_null());
256   didDrawCallback_.Run();
259 @end
261 @implementation ImageTransportNSCGLSurface
263 - (id)initWithStorageProvider:
264     (content::CALayerStorageProvider*)storageProvider
265                     pixelSize:(gfx::Size)pixelSize
266                   scaleFactor:(float)scaleFactor {
267   if (self = [super init]) {
268     ScopedCAActionDisabler disabler;
269     gfx::Size dipSize = gfx::ConvertSizeToDIP(scaleFactor, pixelSize);
270     [self setContentsScale:scaleFactor];
271     [self setFrame:CGRectMake(0, 0, dipSize.width(), dipSize.height())];
272     storageProvider_ = storageProvider;
274     // Allocate the NSCGLSurface to render into.
275     base::ScopedCFTypeRef<CGColorSpaceRef> cgColorSpace(
276         CGDisplayCopyColorSpace(CGMainDisplayID()));
277     Class NSCGLSurface_class = NSClassFromString(@"NSCGLSurface");
278     surface_.reset([[NSCGLSurface_class alloc] initWithSize:pixelSize.ToCGSize()
279                                                  colorSpace:cgColorSpace
280                                                      atomic:NO]);
282     // Create a context in the share group of the storage provider. We will
283     // draw content using this context.
284     CGLError cglError = kCGLNoError;
285     cglError = CGLCreateContext(
286         CGLGetPixelFormat(storageProvider_->LayerShareGroupContext()),
287         storageProvider_->LayerShareGroupContext(),
288         cglContext_.InitializeInto());
289     LOG_IF(ERROR, cglError != kCGLNoError) <<
290         "Failed to create CGL context for NSCGL surface.";
292     pixelSize_ = pixelSize;
293   }
294   return self;
297 - (void)drawNewFrame:(gfx::Rect)dirtyRect {
298   // Draw the first frame to the layer as covering the full layer. Subsequent
299   // frames may use partial damage.
300   if (![self contents])
301     dirtyRect = gfx::Rect(pixelSize_);
303   // Make the context current to the thread, make the surface be the current
304   // drawable for the context, and draw.
305   [surface_ attachToCGLContext:cglContext_];
306   {
307     gfx::ScopedCGLSetCurrentContext scopedSetCurrentContext(cglContext_);
308     gfx::ScopedSetGLToRealGLApi scopedSetRealGLApi;
309     glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
310     glViewport(0, 0, pixelSize_.width(), pixelSize_.height());
311     storageProvider_->LayerDoDraw(dirtyRect);
312     glFlush();
313   }
314   [surface_ attachToCGLContext:NULL];
315   [surface_ flushRect:dirtyRect.ToCGRect()];
317   if (![self contents]) {
318     // The first time we draw, set the layer contents and the CAContext's layer
319     {
320       ScopedCAActionDisabler disabler;
321       [self setContents:[surface_ layerContents]];
322     }
323     [storageProvider_->LayerCAContext() setLayer:self];
324   } else {
325     // For subsequent draws, just indicate that the layer contents has changed.
326     // This has lower power usage than calling -[CALayer setContents:].
327     [self setContentsChanged];
328   }
331 - (void)drawPendingFrameImmediately {
332   [self drawNewFrame:gfx::Rect(pixelSize_)];
335 - (void)resetStorageProvider {
336   storageProvider_ = NULL;
339 @end
341 namespace content {
343 CALayerStorageProvider::CALayerStorageProvider(
344     ImageTransportSurfaceFBO* transport_surface)
345     : transport_surface_(transport_surface),
346       gpu_vsync_disabled_(base::CommandLine::ForCurrentProcess()->HasSwitch(
347           switches::kDisableGpuVsync)),
348       throttling_disabled_(false),
349       has_pending_draw_(false),
350       fbo_texture_(0),
351       fbo_scale_factor_(1),
352       program_(0),
353       vertex_shader_(0),
354       fragment_shader_(0),
355       position_location_(0),
356       tex_location_(0),
357       vertex_buffer_(0),
358       vertex_array_(0),
359       recreate_layer_after_gpu_switch_(false),
360       pending_draw_weak_factory_(this) {
361   ui::GpuSwitchingManager::GetInstance()->AddObserver(this);
364 CALayerStorageProvider::~CALayerStorageProvider() {
365   ui::GpuSwitchingManager::GetInstance()->RemoveObserver(this);
368 gfx::Size CALayerStorageProvider::GetRoundedSize(gfx::Size size) {
369   return size;
372 bool CALayerStorageProvider::AllocateColorBufferStorage(
373     CGLContextObj context, const base::Closure& context_dirtied_callback,
374     GLuint texture, gfx::Size pixel_size, float scale_factor) {
375   // Allocate an ordinary OpenGL texture to back the FBO.
376   GLenum error;
377   while ((error = glGetError()) != GL_NO_ERROR) {
378     LOG(ERROR) << "OpenGL error hit but ignored before allocating buffer "
379                << "storage: " << error;
380   }
382   if (gfx::GetGLImplementation() ==
383       gfx::kGLImplementationDesktopGLCoreProfile) {
384     glTexImage2D(GL_TEXTURE_2D,
385                  0,
386                  GL_RGBA,
387                  pixel_size.width(),
388                  pixel_size.height(),
389                  0,
390                  GL_RGBA,
391                  GL_UNSIGNED_BYTE,
392                  NULL);
393     glFlush();
395     if (!vertex_shader_) {
396       const char* source =
397           "#version 150\n"
398           "in vec4 position;\n"
399           "out vec2 texcoord;\n"
400           "void main() {\n"
401           "    texcoord = vec2(position.x, position.y);\n"
402           "    gl_Position = vec4(2*position.x-1, 2*position.y-1,\n"
403           "        position.z, position.w);\n"
404           "}\n";
405       vertex_shader_ = glCreateShader(GL_VERTEX_SHADER);
406       glShaderSource(vertex_shader_, 1, &source, NULL);
407       glCompileShader(vertex_shader_);
408 #if DCHECK_IS_ON()
409       GLint status = GL_FALSE;
410       glGetShaderiv(vertex_shader_, GL_COMPILE_STATUS, &status);
411       DCHECK(status == GL_TRUE);
412 #endif
413     }
414     if (!fragment_shader_) {
415       const char* source =
416           "#version 150\n"
417           "uniform sampler2D tex;\n"
418           "in vec2 texcoord;\n"
419           "out vec4 frag_color;\n"
420           "void main() {\n"
421           "    frag_color = texture(tex, texcoord);\n"
422           "}\n";
423       fragment_shader_ = glCreateShader(GL_FRAGMENT_SHADER);
424       glShaderSource(fragment_shader_, 1, &source, NULL);
425       glCompileShader(fragment_shader_);
426 #if DCHECK_IS_ON()
427       GLint status = GL_FALSE;
428       glGetShaderiv(fragment_shader_, GL_COMPILE_STATUS, &status);
429       DCHECK(status == GL_TRUE);
430 #endif
431     }
432     if (!program_) {
433       program_ = glCreateProgram();
434       glAttachShader(program_, vertex_shader_);
435       glAttachShader(program_, fragment_shader_);
436       glBindFragDataLocation(program_, 0, "frag_color");
437       glLinkProgram(program_);
438 #if DCHECK_IS_ON()
439       GLint status = GL_FALSE;
440       glGetProgramiv(program_, GL_LINK_STATUS, &status);
441       DCHECK(status == GL_TRUE);
442 #endif
443       position_location_ = glGetAttribLocation(program_, "position");
444       tex_location_ = glGetUniformLocation(program_, "tex");
445     }
446     if (!vertex_buffer_) {
447       GLfloat vertex_data[24] = {
448         0, 0, 0, 1,
449         1, 0, 0, 1,
450         1, 1, 0, 1,
451         1, 1, 0, 1,
452         0, 1, 0, 1,
453         0, 0, 0, 1,
454       };
455       glGenBuffersARB(1, &vertex_buffer_);
456       // If the allocation path used GLContext::RestoreStateIfDirtiedExternally
457       // as the draw path does, this manual state restoration would not be
458       // necessary.
459       GLint bound_buffer = 0;
460       glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &bound_buffer);
461       glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
462       glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data),
463                    vertex_data, GL_STATIC_DRAW);
464       glBindBuffer(GL_ARRAY_BUFFER, bound_buffer);
465     }
466     if (!vertex_array_) {
467       // If the allocation path used GLContext::RestoreStateIfDirtiedExternally
468       // as the draw path does, this manual state restoration would not be
469       // necessary.
470       GLint bound_vao = 0;
471       glGetIntegerv(GL_VERTEX_ARRAY_BINDING_OES, &bound_vao);
472       glGenVertexArraysOES(1, &vertex_array_);
473       glBindVertexArrayOES(vertex_array_);
474       {
475         glEnableVertexAttribArray(position_location_);
476         GLint bound_buffer = 0;
477         glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &bound_buffer);
478         glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
479         glVertexAttribPointer(position_location_, 4, GL_FLOAT, GL_FALSE, 0, 0);
480         glBindBuffer(GL_ARRAY_BUFFER, bound_buffer);
481       }
482       glBindVertexArrayOES(bound_vao);
483     }
484   } else {
485     glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
486                  0,
487                  GL_RGBA,
488                  pixel_size.width(),
489                  pixel_size.height(),
490                  0,
491                  GL_RGBA,
492                  GL_UNSIGNED_BYTE,
493                  NULL);
494     glFlush();
495   }
497   bool hit_error = false;
498   while ((error = glGetError()) != GL_NO_ERROR) {
499     LOG(ERROR) << "OpenGL error hit while trying to allocate buffer storage: "
500                << error;
501     hit_error = true;
502   }
503   if (hit_error)
504     return false;
506   // Set the parameters that will be used to allocate the CALayer to draw the
507   // texture into.
508   share_group_context_.reset(CGLRetainContext(context));
509   share_group_context_dirtied_callback_ = context_dirtied_callback;
510   fbo_texture_ = texture;
511   fbo_pixel_size_ = pixel_size;
512   fbo_scale_factor_ = scale_factor;
513   return true;
516 void CALayerStorageProvider::FreeColorBufferStorage() {
517   if (gfx::GetGLImplementation() ==
518       gfx::kGLImplementationDesktopGLCoreProfile) {
519     if (vertex_shader_)
520       glDeleteShader(vertex_shader_);
521     if (fragment_shader_)
522       glDeleteShader(fragment_shader_);
523     if (program_)
524       glDeleteProgram(program_);
525     if (vertex_buffer_)
526       glDeleteBuffersARB(1, &vertex_buffer_);
527     if (vertex_array_)
528       glDeleteVertexArraysOES(1, &vertex_array_);
529     vertex_shader_ = 0;
530     fragment_shader_ = 0;
531     program_ = 0;
532     vertex_buffer_ = 0;
533     vertex_array_ = 0;
534   }
536   // Note that |context_| still holds a reference to |layer_|, and will until
537   // a new frame is swapped in.
538   ResetLayer();
540   share_group_context_.reset();
541   share_group_context_dirtied_callback_ = base::Closure();
542   fbo_texture_ = 0;
543   fbo_pixel_size_ = gfx::Size();
546 void CALayerStorageProvider::FrameSizeChanged(const gfx::Size& pixel_size,
547                                               float scale_factor) {
548   DCHECK_EQ(fbo_pixel_size_.ToString(), pixel_size.ToString());
549   DCHECK_EQ(fbo_scale_factor_, scale_factor);
552 void CALayerStorageProvider::SwapBuffers(const gfx::Rect& dirty_rect) {
553   TRACE_EVENT0("gpu", "CALayerStorageProvider::SwapBuffers");
554   DCHECK(!has_pending_draw_);
556   // Recreate the CALayer on the new GPU if a GPU switch has occurred. Note
557   // that the CAContext will retain a reference to the old CALayer until the
558   // call to -[CAContext setLayer:] replaces the old CALayer with the new one.
559   if (recreate_layer_after_gpu_switch_) {
560     ResetLayer();
561     recreate_layer_after_gpu_switch_ = false;
562   }
564   // Determine if it is safe to use an NSCGLSurface, or if we should use the
565   // CAOpenGLLayer fallback. If we're not using the preferred type of layer,
566   // then reset the layer and re-create one of the preferred type.
567   bool can_use_ns_cgl_surface =
568       CanUseNSCGLSurface(transport_surface_->GetFeatureInfo());
569   Class expected_layer_class = can_use_ns_cgl_surface ?
570       [ImageTransportNSCGLSurface class] : [ImageTransportCAOpenGLLayer class];
571   if (![layer_ isKindOfClass:expected_layer_class])
572     ResetLayer();
574   // Set the pending draw flag only after destroying the old layer (otherwise
575   // destroying it will un-set the flag).
576   has_pending_draw_ = true;
578   // Allocate a CAContext to use to transport the CALayer to the browser
579   // process, if needed.
580   if (!context_) {
581     base::scoped_nsobject<NSDictionary> dict([[NSDictionary alloc] init]);
582     CGSConnectionID connection_id = CGSMainConnectionID();
583     context_.reset([CAContext contextWithCGSConnection:connection_id
584                                                options:dict]);
585     [context_ retain];
586   }
588   // Allocate a CALayer to use to draw the content and make it current to the
589   // CAContext, if needed.
590   if (!layer_) {
591     if (can_use_ns_cgl_surface) {
592       layer_.reset([[ImageTransportNSCGLSurface alloc]
593           initWithStorageProvider:this
594                         pixelSize:fbo_pixel_size_
595                       scaleFactor:fbo_scale_factor_]);
596     } else {
597       layer_.reset([[ImageTransportCAOpenGLLayer alloc]
598           initWithStorageProvider:this
599                         pixelSize:fbo_pixel_size_
600                       scaleFactor:fbo_scale_factor_]);
601     }
602   }
604   // Replacing the CAContext's CALayer will sometimes results in an immediate
605   // draw.
606   if (!has_pending_draw_)
607     return;
609   // Tell CoreAnimation to draw our frame.
610   if (gpu_vsync_disabled_ || throttling_disabled_) {
611     DrawImmediatelyAndUnblockBrowser();
612   } else {
613     [layer_ drawNewFrame:dirty_rect];
614   }
616   if (has_pending_draw_) {
617     // If CoreAnimation doesn't end up drawing our frame, un-block the browser
618     // after a timeout of 1/6th of a second has passed.
619     base::MessageLoop::current()->PostDelayedTask(
620         FROM_HERE,
621         base::Bind(&CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser,
622                    pending_draw_weak_factory_.GetWeakPtr()),
623         base::TimeDelta::FromSeconds(1) / 6);
624   }
627 void CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser() {
628   CHECK(has_pending_draw_);
629   [layer_ drawPendingFrameImmediately];
631   // Sometimes, the setNeedsDisplay+displayIfNeeded pairs have no effect. This
632   // can happen if the NSView that this layer is attached to isn't in the
633   // window hierarchy (e.g, tab capture of a backgrounded tab). In this case,
634   // the frame will never be seen, so drop it.
635   UnblockBrowserIfNeeded();
638 void CALayerStorageProvider::WillWriteToBackbuffer() {
639   // The browser should always throttle itself so that there are no pending
640   // draws when the output surface is written to, but in the event of things
641   // like context lost, or changing context, this will not be true. If there
642   // exists a pending draw, flush it immediately to maintain a consistent
643   // state.
644   if (has_pending_draw_)
645     DrawImmediatelyAndUnblockBrowser();
648 void CALayerStorageProvider::DiscardBackbuffer() {
649   // If this surface's backbuffer is discarded, it is because this surface has
650   // been made non-visible. Ensure that the previous contents are not briefly
651   // flashed when this is made visible by creating a new CALayer and CAContext
652   // at the next swap.
653   ResetLayer();
655   // If we remove all references to the CAContext in this process, it will be
656   // blanked-out in the browser process (even if the browser process is inside
657   // a NSDisableScreenUpdates block). Ensure that the context is kept around
658   // until a fixed number of frames (determined empirically) have been acked.
659   // http://crbug.com/425819
660   while (previously_discarded_contexts_.size() <
661       kFramesToKeepCAContextAfterDiscard) {
662     previously_discarded_contexts_.push_back(
663         base::scoped_nsobject<CAContext>());
664   }
665   previously_discarded_contexts_.push_back(context_);
667   context_.reset();
670 void CALayerStorageProvider::SwapBuffersAckedByBrowser(
671     bool disable_throttling) {
672   TRACE_EVENT0("gpu", "CALayerStorageProvider::SwapBuffersAckedByBrowser");
673   throttling_disabled_ = disable_throttling;
674   if (!previously_discarded_contexts_.empty())
675     previously_discarded_contexts_.pop_front();
678 CGLContextObj CALayerStorageProvider::LayerShareGroupContext() {
679   return share_group_context_;
682 base::Closure CALayerStorageProvider::LayerShareGroupContextDirtiedCallback() {
683   return share_group_context_dirtied_callback_;
686 void CALayerStorageProvider::LayerDoDraw(const gfx::Rect& dirty_rect) {
687   TRACE_EVENT0("gpu", "CALayerStorageProvider::LayerDoDraw");
688   if (gfx::GetGLImplementation() ==
689       gfx::kGLImplementationDesktopGLCoreProfile) {
690     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
691     glClearColor(1, 0, 1, 1);
692     glClear(GL_COLOR_BUFFER_BIT);
693     glDisable(GL_BLEND);
694     glDisable(GL_CULL_FACE);
695     glDisable(GL_DEPTH_TEST);
696     glDisable(GL_STENCIL_TEST);
697     glDisable(GL_SCISSOR_TEST);
699     DCHECK(glIsProgram(program_));
700     glUseProgram(program_);
701     glBindVertexArrayOES(vertex_array_);
703     glActiveTexture(GL_TEXTURE0);
704     glBindTexture(GL_TEXTURE_2D, fbo_texture_);
705     glUniform1i(tex_location_, 0);
707     glDisable(GL_CULL_FACE);
708     glDrawArrays(GL_TRIANGLES, 0, 6);
709     glBindVertexArrayOES(0);
710     glUseProgram(0);
711   } else {
712     GLint viewport[4] = {0, 0, 0, 0};
713     glGetIntegerv(GL_VIEWPORT, viewport);
714     gfx::Size viewport_size(viewport[2], viewport[3]);
716     // Set the coordinate system to be one-to-one with pixels.
717     glMatrixMode(GL_PROJECTION);
718     glLoadIdentity();
719     glOrtho(0, viewport_size.width(), 0, viewport_size.height(), -1, 1);
720     glMatrixMode(GL_MODELVIEW);
721     glLoadIdentity();
723     // Reset drawing state and draw a fullscreen quad.
724     glUseProgram(0);
725     glDisable(GL_BLEND);
726     glDisable(GL_CULL_FACE);
727     glDisable(GL_DEPTH_TEST);
728     glDisable(GL_STENCIL_TEST);
729     glDisable(GL_SCISSOR_TEST);
730     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
731     glColor4f(1, 1, 1, 1);
732     glActiveTexture(GL_TEXTURE0);
733     glEnable(GL_TEXTURE_RECTANGLE_ARB);
734     glBindTexture(GL_TEXTURE_RECTANGLE_ARB, fbo_texture_);
735     glBegin(GL_QUADS);
736     {
737       glTexCoord2f(dirty_rect.x(), dirty_rect.y());
738       glVertex2f(dirty_rect.x(), dirty_rect.y());
740       glTexCoord2f(dirty_rect.x(), dirty_rect.bottom());
741       glVertex2f(dirty_rect.x(), dirty_rect.bottom());
743       glTexCoord2f(dirty_rect.right(), dirty_rect.bottom());
744       glVertex2f(dirty_rect.right(), dirty_rect.bottom());
746       glTexCoord2f(dirty_rect.right(), dirty_rect.y());
747       glVertex2f(dirty_rect.right(), dirty_rect.y());
748     }
749     glEnd();
750     glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
751     glDisable(GL_TEXTURE_RECTANGLE_ARB);
752   }
754   GLint current_renderer_id = 0;
755   if (CGLGetParameter(CGLGetCurrentContext(),
756                       kCGLCPCurrentRendererID,
757                       &current_renderer_id) == kCGLNoError) {
758     current_renderer_id &= kCGLRendererIDMatchingMask;
759     transport_surface_->SetRendererID(current_renderer_id);
760   }
762   GLenum error;
763   while ((error = glGetError()) != GL_NO_ERROR) {
764     LOG(ERROR) << "OpenGL error hit while drawing frame: " << error;
765   }
767   // Allow forward progress in the context now that the swap is complete.
768   UnblockBrowserIfNeeded();
771 bool CALayerStorageProvider::LayerHasPendingDraw() const {
772   return has_pending_draw_;
775 void CALayerStorageProvider::OnGpuSwitched() {
776   recreate_layer_after_gpu_switch_ = true;
779 void CALayerStorageProvider::UnblockBrowserIfNeeded() {
780   if (!has_pending_draw_)
781     return;
782   pending_draw_weak_factory_.InvalidateWeakPtrs();
783   has_pending_draw_ = false;
784   transport_surface_->SendSwapBuffers(
785       ui::SurfaceHandleFromCAContextID([context_ contextId]),
786       fbo_pixel_size_,
787       fbo_scale_factor_);
790 void CALayerStorageProvider::ResetLayer() {
791   [layer_ resetStorageProvider];
793   // If we are providing back-pressure by waiting for a draw, that draw will
794   // now never come, so release the pressure now.
795   UnblockBrowserIfNeeded();
797   // This should only ever be called by the active layer.
798   layer_.reset();
801 }  //  namespace content