1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/common/gpu/image_transport_surface_calayer_mac.h"
7 #include <OpenGL/CGLRenderers.h>
9 #include "base/command_line.h"
10 #include "base/mac/sdk_forward_declarations.h"
11 #include "ui/accelerated_widget_mac/surface_handle_types.h"
12 #include "ui/base/cocoa/animation_utils.h"
13 #include "ui/gfx/geometry/size_conversions.h"
14 #include "ui/gl/gl_gl_api_implementation.h"
15 #include "ui/gl/gl_switches.h"
16 #include "ui/gl/gpu_switching_manager.h"
19 const size_t kFramesToKeepCAContextAfterDiscard = 2;
20 const size_t kCanDrawFalsesBeforeSwitchFromAsync = 4;
21 const base::TimeDelta kMinDeltaToSwitchToAsync =
22 base::TimeDelta::FromSecondsD(1. / 15.);
25 @interface ImageTransportLayer : CAOpenGLLayer {
26 content::CALayerStorageProvider* storageProvider_;
27 base::Closure didDrawCallback_;
29 - (id)initWithStorageProvider:(content::CALayerStorageProvider*)storageProvider;
30 - (void)resetStorageProvider;
33 @implementation ImageTransportLayer
35 - (id)initWithStorageProvider:
36 (content::CALayerStorageProvider*)storageProvider {
37 if (self = [super init])
38 storageProvider_ = storageProvider;
42 - (void)resetStorageProvider {
44 storageProvider_->LayerResetStorageProvider();
45 storageProvider_ = NULL;
48 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
49 if (!storageProvider_)
51 return CGLRetainPixelFormat(CGLGetPixelFormat(
52 storageProvider_->LayerShareGroupContext()));
55 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
56 if (!storageProvider_)
58 didDrawCallback_ = storageProvider_->LayerShareGroupContextDirtiedCallback();
59 return CGLRetainContext(storageProvider_->LayerShareGroupContext());
62 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
63 pixelFormat:(CGLPixelFormatObj)pixelFormat
64 forLayerTime:(CFTimeInterval)timeInterval
65 displayTime:(const CVTimeStamp*)timeStamp {
66 if (!storageProvider_)
68 return storageProvider_->LayerCanDraw();
71 - (void)drawInCGLContext:(CGLContextObj)glContext
72 pixelFormat:(CGLPixelFormatObj)pixelFormat
73 forLayerTime:(CFTimeInterval)timeInterval
74 displayTime:(const CVTimeStamp*)timeStamp {
75 // While in this callback, CoreAnimation has set |glContext| to be current.
76 // Ensure that the GL calls that we make are made against the native GL API.
77 gfx::ScopedSetGLToRealGLApi scoped_set_gl_api;
79 if (storageProvider_) {
80 storageProvider_->LayerDoDraw();
82 glClearColor(1, 1, 1, 1);
83 glClear(GL_COLOR_BUFFER_BIT);
85 [super drawInCGLContext:glContext
86 pixelFormat:pixelFormat
87 forLayerTime:timeInterval
88 displayTime:timeStamp];
91 DCHECK(!didDrawCallback_.is_null());
92 didDrawCallback_.Run();
99 CALayerStorageProvider::CALayerStorageProvider(
100 ImageTransportSurfaceFBO* transport_surface)
101 : transport_surface_(transport_surface),
102 gpu_vsync_disabled_(base::CommandLine::ForCurrentProcess()->HasSwitch(
103 switches::kDisableGpuVsync)),
104 throttling_disabled_(false),
105 has_pending_draw_(false),
106 can_draw_returned_false_count_(0),
108 fbo_scale_factor_(1),
112 position_location_(0),
116 recreate_layer_after_gpu_switch_(false),
117 pending_draw_weak_factory_(this) {
118 ui::GpuSwitchingManager::GetInstance()->AddObserver(this);
121 CALayerStorageProvider::~CALayerStorageProvider() {
122 ui::GpuSwitchingManager::GetInstance()->RemoveObserver(this);
125 gfx::Size CALayerStorageProvider::GetRoundedSize(gfx::Size size) {
129 bool CALayerStorageProvider::AllocateColorBufferStorage(
130 CGLContextObj context, const base::Closure& context_dirtied_callback,
131 GLuint texture, gfx::Size pixel_size, float scale_factor) {
132 // Allocate an ordinary OpenGL texture to back the FBO.
134 while ((error = glGetError()) != GL_NO_ERROR) {
135 LOG(ERROR) << "OpenGL error hit but ignored before allocating buffer "
136 << "storage: " << error;
139 if (gfx::GetGLImplementation() ==
140 gfx::kGLImplementationDesktopGLCoreProfile) {
141 glTexImage2D(GL_TEXTURE_2D,
152 if (!vertex_shader_) {
155 "in vec4 position;\n"
156 "out vec2 texcoord;\n"
158 " texcoord = vec2(position.x, position.y);\n"
159 " gl_Position = vec4(2*position.x-1, 2*position.y-1,\n"
160 " position.z, position.w);\n"
162 vertex_shader_ = glCreateShader(GL_VERTEX_SHADER);
163 glShaderSource(vertex_shader_, 1, &source, NULL);
164 glCompileShader(vertex_shader_);
166 GLint status = GL_FALSE;
167 glGetShaderiv(vertex_shader_, GL_COMPILE_STATUS, &status);
168 DCHECK(status == GL_TRUE);
171 if (!fragment_shader_) {
174 "uniform sampler2D tex;\n"
175 "in vec2 texcoord;\n"
176 "out vec4 frag_color;\n"
178 " frag_color = texture(tex, texcoord);\n"
180 fragment_shader_ = glCreateShader(GL_FRAGMENT_SHADER);
181 glShaderSource(fragment_shader_, 1, &source, NULL);
182 glCompileShader(fragment_shader_);
184 GLint status = GL_FALSE;
185 glGetShaderiv(fragment_shader_, GL_COMPILE_STATUS, &status);
186 DCHECK(status == GL_TRUE);
190 program_ = glCreateProgram();
191 glAttachShader(program_, vertex_shader_);
192 glAttachShader(program_, fragment_shader_);
193 glBindFragDataLocation(program_, 0, "frag_color");
194 glLinkProgram(program_);
196 GLint status = GL_FALSE;
197 glGetProgramiv(program_, GL_LINK_STATUS, &status);
198 DCHECK(status == GL_TRUE);
200 position_location_ = glGetAttribLocation(program_, "position");
201 tex_location_ = glGetUniformLocation(program_, "tex");
203 if (!vertex_buffer_) {
204 GLfloat vertex_data[24] = {
212 glGenBuffersARB(1, &vertex_buffer_);
213 glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
214 glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data),
215 vertex_data, GL_STATIC_DRAW);
216 glBindBuffer(GL_ARRAY_BUFFER, 0);
218 if (!vertex_array_) {
219 glGenVertexArraysOES(1, &vertex_array_);
220 glBindVertexArrayOES(vertex_array_);
222 glEnableVertexAttribArray(position_location_);
223 glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
224 glVertexAttribPointer(position_location_, 4, GL_FLOAT, GL_FALSE, 0, 0);
225 glBindBuffer(GL_ARRAY_BUFFER, 0);
227 glBindVertexArrayOES(0);
230 glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
242 bool hit_error = false;
243 while ((error = glGetError()) != GL_NO_ERROR) {
244 LOG(ERROR) << "OpenGL error hit while trying to allocate buffer storage: "
251 // Set the parameters that will be used to allocate the CALayer to draw the
253 share_group_context_.reset(CGLRetainContext(context));
254 share_group_context_dirtied_callback_ = context_dirtied_callback;
255 fbo_texture_ = texture;
256 fbo_pixel_size_ = pixel_size;
257 fbo_scale_factor_ = scale_factor;
261 void CALayerStorageProvider::FreeColorBufferStorage() {
262 if (gfx::GetGLImplementation() ==
263 gfx::kGLImplementationDesktopGLCoreProfile) {
265 glDeleteShader(vertex_shader_);
266 if (fragment_shader_)
267 glDeleteShader(fragment_shader_);
269 glDeleteProgram(program_);
271 glDeleteBuffersARB(1, &vertex_buffer_);
273 glDeleteVertexArraysOES(1, &vertex_array_);
275 fragment_shader_ = 0;
281 // Note that |context_| still holds a reference to |layer_|, and will until
282 // a new frame is swapped in.
283 [layer_ resetStorageProvider];
286 share_group_context_.reset();
287 share_group_context_dirtied_callback_ = base::Closure();
289 fbo_pixel_size_ = gfx::Size();
290 can_draw_returned_false_count_ = 0;
293 void CALayerStorageProvider::FrameSizeChanged(const gfx::Size& pixel_size,
294 float scale_factor) {
295 DCHECK_EQ(fbo_pixel_size_.ToString(), pixel_size.ToString());
296 DCHECK_EQ(fbo_scale_factor_, scale_factor);
299 void CALayerStorageProvider::SwapBuffers() {
300 DCHECK(!has_pending_draw_);
302 // Recreate the CALayer on the new GPU if a GPU switch has occurred. Note
303 // that the CAContext will retain a reference to the old CALayer until the
304 // call to -[CAContext setLayer:] replaces the old CALayer with the new one.
305 if (recreate_layer_after_gpu_switch_) {
306 [layer_ resetStorageProvider];
308 recreate_layer_after_gpu_switch_ = false;
311 // Set the pending draw flag only after destroying the old layer (otherwise
312 // destroying it will un-set the flag).
313 has_pending_draw_ = true;
315 // Allocate a CAContext to use to transport the CALayer to the browser
316 // process, if needed.
318 base::scoped_nsobject<NSDictionary> dict([[NSDictionary alloc] init]);
319 CGSConnectionID connection_id = CGSMainConnectionID();
320 context_.reset([CAContext contextWithCGSConnection:connection_id
325 // Allocate a CALayer to use to draw the content and make it current to the
326 // CAContext, if needed.
328 layer_.reset([[ImageTransportLayer alloc] initWithStorageProvider:this]);
329 gfx::Size dip_size(gfx::ToFlooredSize(gfx::ScaleSize(
330 fbo_pixel_size_, 1.0f / fbo_scale_factor_)));
331 [layer_ setContentsScale:fbo_scale_factor_];
332 [layer_ setFrame:CGRectMake(0, 0, dip_size.width(), dip_size.height())];
334 [context_ setLayer:layer_];
337 // Replacing the CAContext's CALayer will sometimes results in an immediate
339 if (!has_pending_draw_)
342 // Tell CoreAnimation to draw our frame.
343 if (gpu_vsync_disabled_ || throttling_disabled_) {
344 DrawImmediatelyAndUnblockBrowser();
346 if (![layer_ isAsynchronous]) {
347 // Switch to asynchronous drawing only if we get two frames in rapid
349 base::TimeTicks this_swap_time = base::TimeTicks::Now();
350 base::TimeDelta delta = this_swap_time - last_synchronous_swap_time_;
351 if (delta <= kMinDeltaToSwitchToAsync) {
352 last_synchronous_swap_time_ = base::TimeTicks();
353 [layer_ setAsynchronous:YES];
355 last_synchronous_swap_time_ = this_swap_time;
356 [layer_ setNeedsDisplay];
360 // If CoreAnimation doesn't end up drawing our frame, un-block the browser
361 // after a timeout of 1/6th of a second has passed.
362 base::MessageLoop::current()->PostDelayedTask(
364 base::Bind(&CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser,
365 pending_draw_weak_factory_.GetWeakPtr()),
366 base::TimeDelta::FromSeconds(1) / 6);
370 void CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser() {
371 CHECK(has_pending_draw_);
372 if ([layer_ isAsynchronous])
373 [layer_ setAsynchronous:NO];
374 [layer_ setNeedsDisplay];
375 [layer_ displayIfNeeded];
377 // Sometimes, the setNeedsDisplay+displayIfNeeded pairs have no effect. This
378 // can happen if the NSView that this layer is attached to isn't in the
379 // window hierarchy (e.g, tab capture of a backgrounded tab). In this case,
380 // the frame will never be seen, so drop it.
381 UnblockBrowserIfNeeded();
384 void CALayerStorageProvider::WillWriteToBackbuffer() {
385 // The browser should always throttle itself so that there are no pending
386 // draws when the output surface is written to, but in the event of things
387 // like context lost, or changing context, this will not be true. If there
388 // exists a pending draw, flush it immediately to maintain a consistent
390 if (has_pending_draw_)
391 DrawImmediatelyAndUnblockBrowser();
394 void CALayerStorageProvider::DiscardBackbuffer() {
395 // If this surface's backbuffer is discarded, it is because this surface has
396 // been made non-visible. Ensure that the previous contents are not briefly
397 // flashed when this is made visible by creating a new CALayer and CAContext
399 [layer_ resetStorageProvider];
402 // If we remove all references to the CAContext in this process, it will be
403 // blanked-out in the browser process (even if the browser process is inside
404 // a NSDisableScreenUpdates block). Ensure that the context is kept around
405 // until a fixed number of frames (determined empirically) have been acked.
406 // http://crbug.com/425819
407 while (previously_discarded_contexts_.size() <
408 kFramesToKeepCAContextAfterDiscard) {
409 previously_discarded_contexts_.push_back(
410 base::scoped_nsobject<CAContext>());
412 previously_discarded_contexts_.push_back(context_);
417 void CALayerStorageProvider::SwapBuffersAckedByBrowser(
418 bool disable_throttling) {
419 throttling_disabled_ = disable_throttling;
420 if (!previously_discarded_contexts_.empty())
421 previously_discarded_contexts_.pop_front();
424 CGLContextObj CALayerStorageProvider::LayerShareGroupContext() {
425 return share_group_context_;
428 base::Closure CALayerStorageProvider::LayerShareGroupContextDirtiedCallback() {
429 return share_group_context_dirtied_callback_;
432 bool CALayerStorageProvider::LayerCanDraw() {
433 if (has_pending_draw_) {
434 can_draw_returned_false_count_ = 0;
437 if ([layer_ isAsynchronous]) {
438 DCHECK(!gpu_vsync_disabled_);
439 // If we are in asynchronous mode, we will be getting callbacks at every
440 // vsync, asking us if we have anything to draw. If we get many of these
441 // in a row, ask that we stop getting these callback for now, so that we
442 // don't waste CPU cycles.
443 if (can_draw_returned_false_count_ >= kCanDrawFalsesBeforeSwitchFromAsync)
444 [layer_ setAsynchronous:NO];
446 can_draw_returned_false_count_ += 1;
452 void CALayerStorageProvider::LayerDoDraw() {
453 if (gfx::GetGLImplementation() ==
454 gfx::kGLImplementationDesktopGLCoreProfile) {
455 glClearColor(1, 0, 1, 1);
456 glClear(GL_COLOR_BUFFER_BIT);
458 glDisable(GL_CULL_FACE);
459 glDisable(GL_DEPTH_TEST);
460 glDisable(GL_STENCIL_TEST);
461 glDisable(GL_SCISSOR_TEST);
463 DCHECK(glIsProgram(program_));
464 glUseProgram(program_);
465 glBindVertexArrayOES(vertex_array_);
467 glActiveTexture(GL_TEXTURE0);
468 glBindTexture(GL_TEXTURE_2D, fbo_texture_);
469 glUniform1i(tex_location_, 0);
471 glDisable(GL_CULL_FACE);
472 glDrawArrays(GL_TRIANGLES, 0, 6);
473 glBindVertexArrayOES(0);
476 GLint viewport[4] = {0, 0, 0, 0};
477 glGetIntegerv(GL_VIEWPORT, viewport);
478 gfx::Size viewport_size(viewport[2], viewport[3]);
480 // Set the coordinate system to be one-to-one with pixels.
481 glMatrixMode(GL_PROJECTION);
483 glOrtho(0, viewport_size.width(), 0, viewport_size.height(), -1, 1);
484 glMatrixMode(GL_MODELVIEW);
487 // Reset drawing state and draw a fullscreen quad.
490 glDisable(GL_CULL_FACE);
491 glDisable(GL_DEPTH_TEST);
492 glDisable(GL_STENCIL_TEST);
493 glDisable(GL_SCISSOR_TEST);
494 glColor4f(1, 1, 1, 1);
495 glActiveTexture(GL_TEXTURE0);
496 glEnable(GL_TEXTURE_RECTANGLE_ARB);
497 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, fbo_texture_);
503 glTexCoord2f(0, fbo_pixel_size_.height());
504 glVertex2f(0, fbo_pixel_size_.height());
506 glTexCoord2f(fbo_pixel_size_.width(), fbo_pixel_size_.height());
507 glVertex2f(fbo_pixel_size_.width(), fbo_pixel_size_.height());
509 glTexCoord2f(fbo_pixel_size_.width(), 0);
510 glVertex2f(fbo_pixel_size_.width(), 0);
513 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
514 glDisable(GL_TEXTURE_RECTANGLE_ARB);
517 GLint current_renderer_id = 0;
518 if (CGLGetParameter(CGLGetCurrentContext(),
519 kCGLCPCurrentRendererID,
520 ¤t_renderer_id) == kCGLNoError) {
521 current_renderer_id &= kCGLRendererIDMatchingMask;
522 transport_surface_->SetRendererID(current_renderer_id);
526 while ((error = glGetError()) != GL_NO_ERROR) {
527 LOG(ERROR) << "OpenGL error hit while drawing frame: " << error;
530 // Allow forward progress in the context now that the swap is complete.
531 UnblockBrowserIfNeeded();
534 void CALayerStorageProvider::LayerResetStorageProvider() {
535 // If we are providing back-pressure by waiting for a draw, that draw will
536 // now never come, so release the pressure now.
537 UnblockBrowserIfNeeded();
540 void CALayerStorageProvider::OnGpuSwitched() {
541 recreate_layer_after_gpu_switch_ = true;
544 void CALayerStorageProvider::UnblockBrowserIfNeeded() {
545 if (!has_pending_draw_)
547 pending_draw_weak_factory_.InvalidateWeakPtrs();
548 has_pending_draw_ = false;
549 transport_surface_->SendSwapBuffers(
550 ui::SurfaceHandleFromCAContextID([context_ contextId]),
555 } // namespace content