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/gfx/geometry/dip_util.h"
18 #include "ui/gfx/geometry/size_conversions.h"
19 #include "ui/gl/gl_switches.h"
20 #include "ui/gl/gpu_switching_manager.h"
21 #include "ui/gl/scoped_api.h"
24 const size_t kFramesToKeepCAContextAfterDiscard = 2;
25 const size_t kCanDrawFalsesBeforeSwitchFromAsync = 4;
26 const base::TimeDelta kMinDeltaToSwitchToAsync =
27 base::TimeDelta::FromSecondsD(1. / 15.);
32 @interface ImageTransportCAOpenGLLayer : CAOpenGLLayer {
33 content::CALayerStorageProvider* storageProvider_;
34 base::Closure didDrawCallback_;
36 // Used to determine if we should use setNeedsDisplay or setAsynchronous to
37 // animate. If the last swap time happened very recently, then
38 // setAsynchronous is used (which allows smooth animation, but comes with the
39 // penalty of the canDrawInCGLContext function waking up the process every
41 base::TimeTicks lastSynchronousSwapTime_;
43 // A counter that is incremented whenever LayerCanDraw returns false. If this
44 // reaches a threshold, then |layer_| is switched to synchronous drawing to
46 uint32 canDrawReturnedFalseCount_;
51 - (id)initWithStorageProvider:(content::CALayerStorageProvider*)storageProvider
52 pixelSize:(gfx::Size)pixelSize
53 scaleFactor:(float)scaleFactor;
54 - (void)requestDrawNewFrame;
55 - (void)drawPendingFrameImmediately;
56 - (void)resetStorageProvider;
59 @implementation ImageTransportCAOpenGLLayer
61 - (id)initWithStorageProvider:
62 (content::CALayerStorageProvider*)storageProvider
63 pixelSize:(gfx::Size)pixelSize
64 scaleFactor:(float)scaleFactor {
65 if (self = [super init]) {
66 gfx::Size dipSize = gfx::ConvertSizeToDIP(scaleFactor, pixelSize);
67 [self setContentsScale:scaleFactor];
68 [self setFrame:CGRectMake(0, 0, dipSize.width(), dipSize.height())];
69 storageProvider_ = storageProvider;
70 pixelSize_ = pixelSize;
75 - (void)requestDrawNewFrame {
76 // This tracing would be more natural to do with a pseudo-thread for each
77 // layer, rather than a counter.
78 // http://crbug.com/366300
79 // A trace value of 2 indicates that there is a pending swap ack. See
80 // canDrawInCGLContext for other value meanings.
81 TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 2);
83 if (![self isAsynchronous]) {
84 // Switch to asynchronous drawing only if we get two frames in rapid
86 base::TimeTicks this_swap_time = base::TimeTicks::Now();
87 base::TimeDelta delta = this_swap_time - lastSynchronousSwapTime_;
88 if (delta <= kMinDeltaToSwitchToAsync) {
89 lastSynchronousSwapTime_ = base::TimeTicks();
90 [self setAsynchronous:YES];
92 lastSynchronousSwapTime_ = this_swap_time;
93 [self setNeedsDisplay];
98 - (void)drawPendingFrameImmediately {
99 DCHECK(storageProvider_->LayerHasPendingDraw());
100 if ([self isAsynchronous])
101 [self setAsynchronous:NO];
102 [self setNeedsDisplay];
103 [self displayIfNeeded];
106 - (void)resetStorageProvider {
107 storageProvider_ = NULL;
110 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
111 if (!storageProvider_)
113 return CGLRetainPixelFormat(CGLGetPixelFormat(
114 storageProvider_->LayerShareGroupContext()));
117 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
118 if (!storageProvider_)
120 CGLContextObj context = NULL;
121 CGLError error = CGLCreateContext(
122 pixelFormat, storageProvider_->LayerShareGroupContext(), &context);
123 if (error != kCGLNoError)
124 LOG(ERROR) << "CGLCreateContext failed with CGL error: " << error;
128 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
129 pixelFormat:(CGLPixelFormatObj)pixelFormat
130 forLayerTime:(CFTimeInterval)timeInterval
131 displayTime:(const CVTimeStamp*)timeStamp {
132 TRACE_EVENT0("gpu", "CALayerStorageProvider::LayerCanDraw");
134 if (!storageProvider_)
137 if (storageProvider_->LayerHasPendingDraw()) {
138 // If there is a draw pending then increase the signal from 2 to 3, to
139 // indicate that there is a swap pending, and CoreAnimation has asked to
141 TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 3);
143 canDrawReturnedFalseCount_ = 0;
146 // If there is not a draw pending, then give an instantaneous blip up from
147 // 0 to 1, indicating that CoreAnimation was ready to draw a frame but we
148 // were not (or didn't have new content to draw).
149 TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 1);
150 TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 0);
152 if ([self isAsynchronous]) {
153 // If we are in asynchronous mode, we will be getting callbacks at every
154 // vsync, asking us if we have anything to draw. If we get many of these
155 // in a row, ask that we stop getting these callback for now, so that we
156 // don't waste CPU cycles.
157 if (canDrawReturnedFalseCount_ >= kCanDrawFalsesBeforeSwitchFromAsync)
158 [self setAsynchronous:NO];
160 canDrawReturnedFalseCount_ += 1;
166 - (void)drawInCGLContext:(CGLContextObj)glContext
167 pixelFormat:(CGLPixelFormatObj)pixelFormat
168 forLayerTime:(CFTimeInterval)timeInterval
169 displayTime:(const CVTimeStamp*)timeStamp {
170 // While in this callback, CoreAnimation has set |glContext| to be current.
171 // Ensure that the GL calls that we make are made against the native GL API.
172 gfx::ScopedSetGLToRealGLApi scoped_set_gl_api;
174 if (storageProvider_) {
175 storageProvider_->LayerDoDraw(gfx::Rect(pixelSize_), false);
176 storageProvider_->LayerUnblockBrowserIfNeeded();
177 // A trace value of 0 indicates that there is no longer a pending swap ack.
178 TRACE_COUNTER_ID1("gpu", "CALayerPendingSwap", self, 0);
180 glClearColor(1, 1, 1, 1);
181 glClear(GL_COLOR_BUFFER_BIT);
183 [super drawInCGLContext:glContext
184 pixelFormat:pixelFormat
185 forLayerTime:timeInterval
186 displayTime:timeStamp];
193 CALayerStorageProvider::CALayerStorageProvider(
194 ImageTransportSurfaceFBO* transport_surface)
195 : transport_surface_(transport_surface),
196 gpu_vsync_disabled_(base::CommandLine::ForCurrentProcess()->HasSwitch(
197 switches::kDisableGpuVsync)),
198 throttling_disabled_(false),
199 has_pending_ack_(false),
201 fbo_scale_factor_(1),
205 position_location_(0),
209 recreate_layer_after_gpu_switch_(false),
210 pending_draw_weak_factory_(this) {
211 ui::GpuSwitchingManager::GetInstance()->AddObserver(this);
214 CALayerStorageProvider::~CALayerStorageProvider() {
215 ui::GpuSwitchingManager::GetInstance()->RemoveObserver(this);
218 gfx::Size CALayerStorageProvider::GetRoundedSize(gfx::Size size) {
222 bool CALayerStorageProvider::AllocateColorBufferStorage(
223 CGLContextObj context, const base::Closure& context_dirtied_callback,
224 GLuint texture, gfx::Size pixel_size, float scale_factor) {
225 // Allocate an ordinary OpenGL texture to back the FBO.
227 while ((error = glGetError()) != GL_NO_ERROR) {
228 LOG(ERROR) << "OpenGL error hit but ignored before allocating buffer "
229 << "storage: " << error;
232 if (gfx::GetGLImplementation() ==
233 gfx::kGLImplementationDesktopGLCoreProfile) {
234 glTexImage2D(GL_TEXTURE_2D,
245 if (!vertex_shader_) {
248 "in vec4 position;\n"
249 "out vec2 texcoord;\n"
251 " texcoord = vec2(position.x, position.y);\n"
252 " gl_Position = vec4(2*position.x-1, 2*position.y-1,\n"
253 " position.z, position.w);\n"
255 vertex_shader_ = glCreateShader(GL_VERTEX_SHADER);
256 glShaderSource(vertex_shader_, 1, &source, NULL);
257 glCompileShader(vertex_shader_);
259 GLint status = GL_FALSE;
260 glGetShaderiv(vertex_shader_, GL_COMPILE_STATUS, &status);
261 DCHECK(status == GL_TRUE);
264 if (!fragment_shader_) {
267 "uniform sampler2D tex;\n"
268 "in vec2 texcoord;\n"
269 "out vec4 frag_color;\n"
271 " frag_color = texture(tex, texcoord);\n"
273 fragment_shader_ = glCreateShader(GL_FRAGMENT_SHADER);
274 glShaderSource(fragment_shader_, 1, &source, NULL);
275 glCompileShader(fragment_shader_);
277 GLint status = GL_FALSE;
278 glGetShaderiv(fragment_shader_, GL_COMPILE_STATUS, &status);
279 DCHECK(status == GL_TRUE);
283 program_ = glCreateProgram();
284 glAttachShader(program_, vertex_shader_);
285 glAttachShader(program_, fragment_shader_);
286 glBindFragDataLocation(program_, 0, "frag_color");
287 glLinkProgram(program_);
289 GLint status = GL_FALSE;
290 glGetProgramiv(program_, GL_LINK_STATUS, &status);
291 DCHECK(status == GL_TRUE);
293 position_location_ = glGetAttribLocation(program_, "position");
294 tex_location_ = glGetUniformLocation(program_, "tex");
296 if (!vertex_buffer_) {
297 GLfloat vertex_data[24] = {
305 glGenBuffersARB(1, &vertex_buffer_);
306 // If the allocation path used GLContext::RestoreStateIfDirtiedExternally
307 // as the draw path does, this manual state restoration would not be
309 GLint bound_buffer = 0;
310 glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &bound_buffer);
311 glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
312 glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data),
313 vertex_data, GL_STATIC_DRAW);
314 glBindBuffer(GL_ARRAY_BUFFER, bound_buffer);
316 if (!vertex_array_) {
317 // If the allocation path used GLContext::RestoreStateIfDirtiedExternally
318 // as the draw path does, this manual state restoration would not be
321 glGetIntegerv(GL_VERTEX_ARRAY_BINDING_OES, &bound_vao);
322 glGenVertexArraysOES(1, &vertex_array_);
323 glBindVertexArrayOES(vertex_array_);
325 glEnableVertexAttribArray(position_location_);
326 GLint bound_buffer = 0;
327 glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &bound_buffer);
328 glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
329 glVertexAttribPointer(position_location_, 4, GL_FLOAT, GL_FALSE, 0, 0);
330 glBindBuffer(GL_ARRAY_BUFFER, bound_buffer);
332 glBindVertexArrayOES(bound_vao);
335 glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
347 bool hit_error = false;
348 while ((error = glGetError()) != GL_NO_ERROR) {
349 LOG(ERROR) << "OpenGL error hit while trying to allocate buffer storage: "
356 // Set the parameters that will be used to allocate the CALayer to draw the
358 share_group_context_.reset(CGLRetainContext(context));
359 share_group_context_dirtied_callback_ = context_dirtied_callback;
360 fbo_texture_ = texture;
361 fbo_pixel_size_ = pixel_size;
362 fbo_scale_factor_ = scale_factor;
366 void CALayerStorageProvider::FreeColorBufferStorage() {
367 if (gfx::GetGLImplementation() ==
368 gfx::kGLImplementationDesktopGLCoreProfile) {
370 glDeleteShader(vertex_shader_);
371 if (fragment_shader_)
372 glDeleteShader(fragment_shader_);
374 glDeleteProgram(program_);
376 glDeleteBuffersARB(1, &vertex_buffer_);
378 glDeleteVertexArraysOES(1, &vertex_array_);
380 fragment_shader_ = 0;
386 // Note that |context_| still holds a reference to |layer_|, and will until
387 // a new frame is swapped in.
390 share_group_context_.reset();
391 share_group_context_dirtied_callback_ = base::Closure();
393 fbo_pixel_size_ = gfx::Size();
396 void CALayerStorageProvider::FrameSizeChanged(const gfx::Size& pixel_size,
397 float scale_factor) {
398 DCHECK_EQ(fbo_pixel_size_.ToString(), pixel_size.ToString());
399 DCHECK_EQ(fbo_scale_factor_, scale_factor);
402 void CALayerStorageProvider::SwapBuffers(const gfx::Rect& dirty_rect) {
403 TRACE_EVENT0("gpu", "CALayerStorageProvider::SwapBuffers");
404 DCHECK(!has_pending_ack_);
406 // Recreate the CALayer on the new GPU if a GPU switch has occurred. Note
407 // that the CAContext will retain a reference to the old CALayer until the
408 // call to -[CAContext setLayer:] replaces the old CALayer with the new one.
409 if (recreate_layer_after_gpu_switch_) {
411 recreate_layer_after_gpu_switch_ = false;
414 // Set the pending draw flag only after potentially destroying the old layer
415 // (otherwise destroying it will un-set the flag).
416 has_pending_ack_ = true;
418 // Allocate a CAContext to use to transport the CALayer to the browser
419 // process, if needed.
421 base::scoped_nsobject<NSDictionary> dict([[NSDictionary alloc] init]);
422 CGSConnectionID connection_id = CGSMainConnectionID();
423 context_.reset([CAContext contextWithCGSConnection:connection_id
428 // Create the appropriate CALayer (if needed) and request that it draw.
429 bool should_draw_immediately = gpu_vsync_disabled_ || throttling_disabled_;
430 CreateLayerAndRequestDraw(should_draw_immediately, dirty_rect);
432 // CoreAnimation may not call the function to un-block the browser in a
433 // timely manner (or ever). Post a task to force the draw and un-block
434 // the browser (at the next cycle through the run-loop if drawing is to
435 // be immediate, and at a timeout of 1/6th of a second otherwise).
436 if (has_pending_ack_) {
437 base::MessageLoop::current()->PostDelayedTask(
439 base::Bind(&CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser,
440 pending_draw_weak_factory_.GetWeakPtr()),
441 should_draw_immediately ? base::TimeDelta() :
442 base::TimeDelta::FromSeconds(1) / 6);
446 void CALayerStorageProvider::CreateLayerAndRequestDraw(
447 bool should_draw_immediately, const gfx::Rect& dirty_rect) {
448 if (!ca_opengl_layer_) {
449 ca_opengl_layer_.reset([[ImageTransportCAOpenGLLayer alloc]
450 initWithStorageProvider:this
451 pixelSize:fbo_pixel_size_
452 scaleFactor:fbo_scale_factor_]);
455 // -[CAOpenGLLayer drawInCGLContext] won't get called until we're in the
456 // visible layer hierarchy, so call setLayer: immediately, to make this
458 [context_ setLayer:ca_opengl_layer_];
460 // Tell CoreAnimation to draw our frame. Note that sometimes, calling
461 // -[CAContext setLayer:] will result in the layer getting an immediate
462 // draw. If that happend, we're done.
463 if (!should_draw_immediately && has_pending_ack_) {
464 [ca_opengl_layer_ requestDrawNewFrame];
468 void CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser() {
469 DCHECK(has_pending_ack_);
471 if (ca_opengl_layer_) {
472 // Beware that sometimes, the setNeedsDisplay+displayIfNeeded pairs have no
473 // effect. This can happen if the NSView that this layer is attached to
474 // isn't in the window hierarchy (e.g, tab capture of a backgrounded tab).
475 // In this case, the frame will never be seen, so drop it.
476 [ca_opengl_layer_ drawPendingFrameImmediately];
479 UnblockBrowserIfNeeded();
482 void CALayerStorageProvider::WillWriteToBackbuffer() {
483 // The browser should always throttle itself so that there are no pending
484 // draws when the output surface is written to, but in the event of things
485 // like context lost, or changing context, this will not be true. If there
486 // exists a pending draw, flush it immediately to maintain a consistent
488 if (has_pending_ack_)
489 DrawImmediatelyAndUnblockBrowser();
492 void CALayerStorageProvider::DiscardBackbuffer() {
493 // If this surface's backbuffer is discarded, it is because this surface has
494 // been made non-visible. Ensure that the previous contents are not briefly
495 // flashed when this is made visible by creating a new CALayer and CAContext
499 // If we remove all references to the CAContext in this process, it will be
500 // blanked-out in the browser process (even if the browser process is inside
501 // a disable screen updates block). Ensure that the context is kept around
502 // until a fixed number of frames (determined empirically) have been acked.
503 // http://crbug.com/425819
504 while (previously_discarded_contexts_.size() <
505 kFramesToKeepCAContextAfterDiscard) {
506 previously_discarded_contexts_.push_back(
507 base::scoped_nsobject<CAContext>());
509 previously_discarded_contexts_.push_back(context_);
514 void CALayerStorageProvider::SwapBuffersAckedByBrowser(
515 bool disable_throttling) {
516 TRACE_EVENT0("gpu", "CALayerStorageProvider::SwapBuffersAckedByBrowser");
517 throttling_disabled_ = disable_throttling;
518 if (!previously_discarded_contexts_.empty())
519 previously_discarded_contexts_.pop_front();
522 CGLContextObj CALayerStorageProvider::LayerShareGroupContext() {
523 return share_group_context_;
526 base::Closure CALayerStorageProvider::LayerShareGroupContextDirtiedCallback() {
527 return share_group_context_dirtied_callback_;
530 void CALayerStorageProvider::LayerDoDraw(
531 const gfx::Rect& dirty_rect, bool flipped) {
532 TRACE_EVENT0("gpu", "CALayerStorageProvider::LayerDoDraw");
533 if (gfx::GetGLImplementation() ==
534 gfx::kGLImplementationDesktopGLCoreProfile) {
535 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
536 glClearColor(1, 0, 1, 1);
537 glClear(GL_COLOR_BUFFER_BIT);
539 glDisable(GL_CULL_FACE);
540 glDisable(GL_DEPTH_TEST);
541 glDisable(GL_STENCIL_TEST);
542 glDisable(GL_SCISSOR_TEST);
544 DCHECK(glIsProgram(program_));
545 glUseProgram(program_);
546 glBindVertexArrayOES(vertex_array_);
548 glActiveTexture(GL_TEXTURE0);
549 glBindTexture(GL_TEXTURE_2D, fbo_texture_);
550 glUniform1i(tex_location_, 0);
552 glDisable(GL_CULL_FACE);
553 glDrawArrays(GL_TRIANGLES, 0, 6);
554 glBindVertexArrayOES(0);
557 GLint viewport[4] = {0, 0, 0, 0};
558 glGetIntegerv(GL_VIEWPORT, viewport);
559 gfx::Size viewport_size(viewport[2], viewport[3]);
561 // Set the coordinate system to be one-to-one with pixels.
562 glMatrixMode(GL_PROJECTION);
565 glOrtho(0, viewport_size.width(), viewport_size.height(), 0, -1, 1);
567 glOrtho(0, viewport_size.width(), 0, viewport_size.height(), -1, 1);
568 glMatrixMode(GL_MODELVIEW);
571 // Reset drawing state and draw a fullscreen quad.
574 glDisable(GL_CULL_FACE);
575 glDisable(GL_DEPTH_TEST);
576 glDisable(GL_STENCIL_TEST);
577 glDisable(GL_SCISSOR_TEST);
578 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
579 glColor4f(1, 1, 1, 1);
580 glActiveTexture(GL_TEXTURE0);
581 glEnable(GL_TEXTURE_RECTANGLE_ARB);
582 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, fbo_texture_);
585 glTexCoord2f(dirty_rect.x(), dirty_rect.y());
586 glVertex2f(dirty_rect.x(), dirty_rect.y());
588 glTexCoord2f(dirty_rect.x(), dirty_rect.bottom());
589 glVertex2f(dirty_rect.x(), dirty_rect.bottom());
591 glTexCoord2f(dirty_rect.right(), dirty_rect.bottom());
592 glVertex2f(dirty_rect.right(), dirty_rect.bottom());
594 glTexCoord2f(dirty_rect.right(), dirty_rect.y());
595 glVertex2f(dirty_rect.right(), dirty_rect.y());
598 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
599 glDisable(GL_TEXTURE_RECTANGLE_ARB);
602 GLint current_renderer_id = 0;
603 if (CGLGetParameter(CGLGetCurrentContext(),
604 kCGLCPCurrentRendererID,
605 ¤t_renderer_id) == kCGLNoError) {
606 current_renderer_id &= kCGLRendererIDMatchingMask;
607 transport_surface_->SetRendererID(current_renderer_id);
611 while ((error = glGetError()) != GL_NO_ERROR) {
612 LOG(ERROR) << "OpenGL error hit while drawing frame: " << error;
616 void CALayerStorageProvider::LayerUnblockBrowserIfNeeded() {
617 UnblockBrowserIfNeeded();
620 bool CALayerStorageProvider::LayerHasPendingDraw() const {
621 return has_pending_ack_;
624 void CALayerStorageProvider::OnGpuSwitched() {
625 recreate_layer_after_gpu_switch_ = true;
628 void CALayerStorageProvider::UnblockBrowserIfNeeded() {
629 if (!has_pending_ack_)
631 pending_draw_weak_factory_.InvalidateWeakPtrs();
632 has_pending_ack_ = false;
633 transport_surface_->SendSwapBuffers(
634 ui::SurfaceHandleFromCAContextID([context_ contextId]),
639 void CALayerStorageProvider::ResetLayer() {
640 if (ca_opengl_layer_) {
641 [ca_opengl_layer_ resetStorageProvider];
642 // If we are providing back-pressure by waiting for a draw, that draw will
643 // now never come, so release the pressure now.
644 UnblockBrowserIfNeeded();
645 ca_opengl_layer_.reset();
649 } // namespace content