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"
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)
37 static bool disabled_at_command_line =
38 base::CommandLine::ForCurrentProcess()->HasSwitch(
39 switches::kDisableNSCGLSurfaceApi);
40 if (disabled_at_command_line)
43 // Ignore blacklist settings.
44 // TODO(ccameron): Remove fields for blacklist settings.
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
57 @property(readonly) CGImageRef image;
58 @property(readonly) id layerContents;
61 // Private CALayer API.
62 @interface CALayer (Private)
63 - (void)setContentsChanged;
64 - (void)_didCommitLayer:(CATransaction*)transaction;
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
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
81 uint32 canDrawReturnedFalseCount_;
86 - (id)initWithStorageProvider:(content::CALayerStorageProvider*)storageProvider
87 pixelSize:(gfx::Size)pixelSize
88 scaleFactor:(float)scaleFactor;
89 - (void)requestDrawNewFrame;
90 - (void)drawPendingFrameImmediately;
91 - (void)resetStorageProvider;
94 @interface ImageTransportIOSurface : CALayer {
95 content::CALayerStorageProvider* storageProvider_;
96 base::ScopedTypeRef<CGLContextObj> cglContext_;
97 base::ScopedCFTypeRef<IOSurfaceRef> ioSurface_;
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;
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;
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
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];
143 lastSynchronousSwapTime_ = this_swap_time;
144 [self setNeedsDisplay];
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_)
164 return CGLRetainPixelFormat(CGLGetPixelFormat(
165 storageProvider_->LayerShareGroupContext()));
168 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
169 if (!storageProvider_)
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;
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_)
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
192 TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 3);
194 canDrawReturnedFalseCount_ = 0;
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];
211 canDrawReturnedFalseCount_ += 1;
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);
231 glClearColor(1, 1, 1, 1);
232 glClear(GL_COLOR_BUFFER_BIT);
234 [super drawInCGLContext:glContext
235 pixelFormat:pixelFormat
236 forLayerTime:timeInterval
237 displayTime:timeStamp];
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),
274 ioSurface_.reset(IOSurfaceCreate(static_cast<CFDictionaryRef>(properties)));
276 LOG(ERROR) << "Failed to allocate IOSurface";
280 // Create a framebuffer view of the IOSurface.
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(
290 GL_TEXTURE_RECTANGLE_ARB,
295 GL_UNSIGNED_INT_8_8_8_8_REV,
298 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
299 if (cglError != kCGLNoError) {
300 DLOG(ERROR) << "CGLTexImageIOSurface2D failed with CGL error: "
302 gl_init_failed = true;
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,
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;
318 // If initialization failed, ensure that the partially initialized GL
319 // objects do not leak into the sharegroup.
320 if (gl_init_failed) {
322 glDeleteTextures(1, &glTexture_);
324 glDeleteFramebuffersEXT(1, &glFbo_);
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;
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;
361 framebuffer_was_complete = NO;
363 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
366 if (!framebuffer_was_complete)
369 if (![self contents]) {
370 // The first time we draw, set the layer contents and the CAContext's layer
372 ScopedCAActionDisabler disabler;
373 [self setContents:(id)ioSurface_.get()];
375 [storageProvider_->LayerCAContext() setLayer:self];
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];
385 - (void)resetStorageProvider {
387 gfx::ScopedCGLSetCurrentContext scopedSetCurrentContext(cglContext_);
388 gfx::ScopedSetGLToRealGLApi scopedSetRealGLApi;
389 glDeleteTextures(1, &glTexture_);
390 glDeleteFramebuffersEXT(1, &glFbo_);
391 glBegin(GL_TRIANGLES);
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),
408 fbo_scale_factor_(1),
412 position_location_(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) {
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.
434 while ((error = glGetError()) != GL_NO_ERROR) {
435 LOG(ERROR) << "OpenGL error hit but ignored before allocating buffer "
436 << "storage: " << error;
439 if (gfx::GetGLImplementation() ==
440 gfx::kGLImplementationDesktopGLCoreProfile) {
441 glTexImage2D(GL_TEXTURE_2D,
452 if (!vertex_shader_) {
455 "in vec4 position;\n"
456 "out vec2 texcoord;\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"
462 vertex_shader_ = glCreateShader(GL_VERTEX_SHADER);
463 glShaderSource(vertex_shader_, 1, &source, NULL);
464 glCompileShader(vertex_shader_);
466 GLint status = GL_FALSE;
467 glGetShaderiv(vertex_shader_, GL_COMPILE_STATUS, &status);
468 DCHECK(status == GL_TRUE);
471 if (!fragment_shader_) {
474 "uniform sampler2D tex;\n"
475 "in vec2 texcoord;\n"
476 "out vec4 frag_color;\n"
478 " frag_color = texture(tex, texcoord);\n"
480 fragment_shader_ = glCreateShader(GL_FRAGMENT_SHADER);
481 glShaderSource(fragment_shader_, 1, &source, NULL);
482 glCompileShader(fragment_shader_);
484 GLint status = GL_FALSE;
485 glGetShaderiv(fragment_shader_, GL_COMPILE_STATUS, &status);
486 DCHECK(status == GL_TRUE);
490 program_ = glCreateProgram();
491 glAttachShader(program_, vertex_shader_);
492 glAttachShader(program_, fragment_shader_);
493 glBindFragDataLocation(program_, 0, "frag_color");
494 glLinkProgram(program_);
496 GLint status = GL_FALSE;
497 glGetProgramiv(program_, GL_LINK_STATUS, &status);
498 DCHECK(status == GL_TRUE);
500 position_location_ = glGetAttribLocation(program_, "position");
501 tex_location_ = glGetUniformLocation(program_, "tex");
503 if (!vertex_buffer_) {
504 GLfloat vertex_data[24] = {
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
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);
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
528 glGetIntegerv(GL_VERTEX_ARRAY_BINDING_OES, &bound_vao);
529 glGenVertexArraysOES(1, &vertex_array_);
530 glBindVertexArrayOES(vertex_array_);
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);
539 glBindVertexArrayOES(bound_vao);
542 glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
554 bool hit_error = false;
555 while ((error = glGetError()) != GL_NO_ERROR) {
556 LOG(ERROR) << "OpenGL error hit while trying to allocate buffer storage: "
563 // Set the parameters that will be used to allocate the CALayer to draw the
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;
573 void CALayerStorageProvider::FreeColorBufferStorage() {
574 if (gfx::GetGLImplementation() ==
575 gfx::kGLImplementationDesktopGLCoreProfile) {
577 glDeleteShader(vertex_shader_);
578 if (fragment_shader_)
579 glDeleteShader(fragment_shader_);
581 glDeleteProgram(program_);
583 glDeleteBuffersARB(1, &vertex_buffer_);
585 glDeleteVertexArraysOES(1, &vertex_array_);
587 fragment_shader_ = 0;
593 // Note that |context_| still holds a reference to |layer_|, and will until
594 // a new frame is swapped in.
597 share_group_context_.reset();
598 share_group_context_dirtied_callback_ = base::Closure();
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_) {
618 recreate_layer_after_gpu_switch_ = false;
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.
628 base::scoped_nsobject<NSDictionary> dict([[NSDictionary alloc] init]);
629 CGSConnectionID connection_id = CGSMainConnectionID();
630 context_.reset([CAContext contextWithCGSConnection:connection_id
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(
646 base::Bind(&CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser,
647 pending_draw_weak_factory_.GetWeakPtr()),
648 should_draw_immediately ? base::TimeDelta() :
649 base::TimeDelta::FromSeconds(1) / 6);
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_]);
663 if (io_surface_layer_ && [io_surface_layer_ drawNewFrame:dirty_rect])
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";
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_]);
679 // -[CAOpenGLLayer drawInCGLContext] won't get called until we're in the
680 // visible layer hierarchy, so call setLayer: immediately, to make this
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];
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];
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
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
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>());
734 previously_discarded_contexts_.push_back(context_);
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);
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);
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);
790 glOrtho(0, viewport_size.width(), viewport_size.height(), 0, -1, 1);
792 glOrtho(0, viewport_size.width(), 0, viewport_size.height(), -1, 1);
793 glMatrixMode(GL_MODELVIEW);
796 // Reset drawing state and draw a fullscreen quad.
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_);
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());
823 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
824 glDisable(GL_TEXTURE_RECTANGLE_ARB);
827 GLint current_renderer_id = 0;
828 if (CGLGetParameter(CGLGetCurrentContext(),
829 kCGLCPCurrentRendererID,
830 ¤t_renderer_id) == kCGLNoError) {
831 current_renderer_id &= kCGLRendererIDMatchingMask;
832 transport_surface_->SetRendererID(current_renderer_id);
836 while ((error = glGetError()) != GL_NO_ERROR) {
837 LOG(ERROR) << "OpenGL error hit while drawing frame: " << error;
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_)
856 pending_draw_weak_factory_.InvalidateWeakPtrs();
857 has_pending_ack_ = false;
858 transport_surface_->SendSwapBuffers(
859 ui::SurfaceHandleFromCAContextID([context_ contextId]),
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();
872 if (io_surface_layer_) {
873 [io_surface_layer_ resetStorageProvider];
875 io_surface_layer_.reset();
880 } // namespace content