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 "content/common/gpu/surface_handle_types_mac.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"
18 const size_t kFramesToKeepCAContextAfterDiscard = 2;
21 @interface ImageTransportLayer : CAOpenGLLayer {
22 content::CALayerStorageProvider* storageProvider_;
24 - (id)initWithStorageProvider:(content::CALayerStorageProvider*)storageProvider;
25 - (void)resetStorageProvider;
28 @implementation ImageTransportLayer
30 - (id)initWithStorageProvider:
31 (content::CALayerStorageProvider*)storageProvider {
32 if (self = [super init])
33 storageProvider_ = storageProvider;
37 - (void)resetStorageProvider {
39 storageProvider_->LayerResetStorageProvider();
40 storageProvider_ = NULL;
43 - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
44 if (!storageProvider_)
46 return CGLRetainPixelFormat(CGLGetPixelFormat(
47 storageProvider_->LayerShareGroupContext()));
50 - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
51 if (!storageProvider_)
53 CGLContextObj context = NULL;
54 CGLError error = CGLCreateContext(
55 pixelFormat, storageProvider_->LayerShareGroupContext(), &context);
56 if (error != kCGLNoError)
57 DLOG(ERROR) << "CGLCreateContext failed with CGL error: " << error;
61 - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
62 pixelFormat:(CGLPixelFormatObj)pixelFormat
63 forLayerTime:(CFTimeInterval)timeInterval
64 displayTime:(const CVTimeStamp*)timeStamp {
65 if (!storageProvider_)
67 return storageProvider_->LayerCanDraw();
70 - (void)drawInCGLContext:(CGLContextObj)glContext
71 pixelFormat:(CGLPixelFormatObj)pixelFormat
72 forLayerTime:(CFTimeInterval)timeInterval
73 displayTime:(const CVTimeStamp*)timeStamp {
74 // While in this callback, CoreAnimation has set |glContext| to be current.
75 // Ensure that the GL calls that we make are made against the native GL API.
76 gfx::ScopedSetGLToRealGLApi scoped_set_gl_api;
78 if (storageProvider_) {
79 storageProvider_->LayerDoDraw();
81 glClearColor(1, 1, 1, 1);
82 glClear(GL_COLOR_BUFFER_BIT);
84 [super drawInCGLContext:glContext
85 pixelFormat:pixelFormat
86 forLayerTime:timeInterval
87 displayTime:timeStamp];
94 CALayerStorageProvider::CALayerStorageProvider(
95 ImageTransportSurfaceFBO* transport_surface)
96 : transport_surface_(transport_surface),
97 gpu_vsync_disabled_(CommandLine::ForCurrentProcess()->HasSwitch(
98 switches::kDisableGpuVsync)),
99 throttling_disabled_(false),
100 has_pending_draw_(false),
101 can_draw_returned_false_count_(0),
103 fbo_scale_factor_(1),
104 pending_draw_weak_factory_(this) {}
106 CALayerStorageProvider::~CALayerStorageProvider() {
109 gfx::Size CALayerStorageProvider::GetRoundedSize(gfx::Size size) {
113 bool CALayerStorageProvider::AllocateColorBufferStorage(
114 CGLContextObj context, GLuint texture,
115 gfx::Size pixel_size, float scale_factor) {
116 // Allocate an ordinary OpenGL texture to back the FBO.
118 while ((error = glGetError()) != GL_NO_ERROR) {
119 DLOG(ERROR) << "Error found (and ignored) before allocating buffer "
120 << "storage: " << error;
122 glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
131 error = glGetError();
132 if (error != GL_NO_ERROR) {
133 DLOG(ERROR) << "glTexImage failed with GL error: " << error;
138 // Set the parameters that will be used to allocate the CALayer to draw the
140 share_group_context_.reset(CGLRetainContext(context));
141 fbo_texture_ = texture;
142 fbo_pixel_size_ = pixel_size;
143 fbo_scale_factor_ = scale_factor;
147 void CALayerStorageProvider::FreeColorBufferStorage() {
148 // Note that |context_| still holds a reference to |layer_|, and will until
149 // a new frame is swapped in.
150 [layer_ resetStorageProvider];
153 share_group_context_.reset();
155 fbo_pixel_size_ = gfx::Size();
156 can_draw_returned_false_count_ = 0;
159 void CALayerStorageProvider::SwapBuffers(
160 const gfx::Size& size, float scale_factor) {
161 DCHECK(!has_pending_draw_);
162 has_pending_draw_ = true;
164 // Allocate a CAContext to use to transport the CALayer to the browser
167 base::scoped_nsobject<NSDictionary> dict([[NSDictionary alloc] init]);
168 CGSConnectionID connection_id = CGSMainConnectionID();
169 context_.reset([CAContext contextWithCGSConnection:connection_id
174 // Allocate a CALayer to use to draw the content.
176 layer_.reset([[ImageTransportLayer alloc] initWithStorageProvider:this]);
177 gfx::Size dip_size(gfx::ToFlooredSize(gfx::ScaleSize(
178 fbo_pixel_size_, 1.0f / fbo_scale_factor_)));
179 [layer_ setContentsScale:fbo_scale_factor_];
180 [layer_ setFrame:CGRectMake(0, 0, dip_size.width(), dip_size.height())];
182 // Make the CALayer current to the CAContext and display its contents
184 [context_ setLayer:layer_];
187 // Tell CoreAnimation to draw our frame.
188 if (gpu_vsync_disabled_ || throttling_disabled_) {
189 DrawImmediatelyAndUnblockBrowser();
191 if (![layer_ isAsynchronous])
192 [layer_ setAsynchronous:YES];
194 // If CoreAnimation doesn't end up drawing our frame, un-block the browser
195 // after a timeout of 1/6th of a second has passed.
196 base::MessageLoop::current()->PostDelayedTask(
198 base::Bind(&CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser,
199 pending_draw_weak_factory_.GetWeakPtr()),
200 base::TimeDelta::FromSeconds(1) / 6);
204 void CALayerStorageProvider::DrawImmediatelyAndUnblockBrowser() {
205 CHECK(has_pending_draw_);
206 if ([layer_ isAsynchronous])
207 [layer_ setAsynchronous:NO];
208 [layer_ setNeedsDisplay];
209 [layer_ displayIfNeeded];
211 // Sometimes, the setNeedsDisplay+displayIfNeeded pairs have no effect. This
212 // can happen if the NSView that this layer is attached to isn't in the
213 // window hierarchy (e.g, tab capture of a backgrounded tab). In this case,
214 // the frame will never be seen, so drop it.
215 UnblockBrowserIfNeeded();
218 void CALayerStorageProvider::WillWriteToBackbuffer() {
219 // The browser should always throttle itself so that there are no pending
220 // draws when the output surface is written to, but in the event of things
221 // like context lost, or changing context, this will not be true. If there
222 // exists a pending draw, flush it immediately to maintain a consistent
224 if (has_pending_draw_)
225 DrawImmediatelyAndUnblockBrowser();
228 void CALayerStorageProvider::DiscardBackbuffer() {
229 // If this surface's backbuffer is discarded, it is because this surface has
230 // been made non-visible. Ensure that the previous contents are not briefly
231 // flashed when this is made visible by creating a new CALayer and CAContext
233 [layer_ resetStorageProvider];
236 // If we remove all references to the CAContext in this process, it will be
237 // blanked-out in the browser process (even if the browser process is inside
238 // a NSDisableScreenUpdates block). Ensure that the context is kept around
239 // until a fixed number of frames (determined empirically) have been acked.
240 // http://crbug.com/425819
241 while (previously_discarded_contexts_.size() <
242 kFramesToKeepCAContextAfterDiscard) {
243 previously_discarded_contexts_.push_back(
244 base::scoped_nsobject<CAContext>());
246 previously_discarded_contexts_.push_back(context_);
251 void CALayerStorageProvider::SwapBuffersAckedByBrowser(
252 bool disable_throttling) {
253 throttling_disabled_ = disable_throttling;
254 if (!previously_discarded_contexts_.empty())
255 previously_discarded_contexts_.pop_front();
258 CGLContextObj CALayerStorageProvider::LayerShareGroupContext() {
259 return share_group_context_;
262 bool CALayerStorageProvider::LayerCanDraw() {
263 if (has_pending_draw_) {
264 can_draw_returned_false_count_ = 0;
267 if ([layer_ isAsynchronous]) {
268 DCHECK(!gpu_vsync_disabled_);
269 // If we are in asynchronous mode, we will be getting callbacks at every
270 // vsync, asking us if we have anything to draw. If we get 30 of these in
271 // a row, ask that we stop getting these callback for now, so that we
272 // don't waste CPU cycles.
273 if (can_draw_returned_false_count_ == 30)
274 [layer_ setAsynchronous:NO];
276 can_draw_returned_false_count_ += 1;
282 void CALayerStorageProvider::LayerDoDraw() {
283 GLint viewport[4] = {0, 0, 0, 0};
284 glGetIntegerv(GL_VIEWPORT, viewport);
285 gfx::Size viewport_size(viewport[2], viewport[3]);
287 // Set the coordinate system to be one-to-one with pixels.
288 glMatrixMode(GL_PROJECTION);
290 glOrtho(0, viewport_size.width(), 0, viewport_size.height(), -1, 1);
291 glMatrixMode(GL_MODELVIEW);
294 // Draw a fullscreen quad.
295 glColor4f(1, 1, 1, 1);
296 glEnable(GL_TEXTURE_RECTANGLE_ARB);
297 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, fbo_texture_);
303 glTexCoord2f(0, fbo_pixel_size_.height());
304 glVertex2f(0, fbo_pixel_size_.height());
306 glTexCoord2f(fbo_pixel_size_.width(), fbo_pixel_size_.height());
307 glVertex2f(fbo_pixel_size_.width(), fbo_pixel_size_.height());
309 glTexCoord2f(fbo_pixel_size_.width(), 0);
310 glVertex2f(fbo_pixel_size_.width(), 0);
313 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
314 glDisable(GL_TEXTURE_RECTANGLE_ARB);
316 GLint current_renderer_id = 0;
317 if (CGLGetParameter(CGLGetCurrentContext(),
318 kCGLCPCurrentRendererID,
319 ¤t_renderer_id) == kCGLNoError) {
320 current_renderer_id &= kCGLRendererIDMatchingMask;
321 transport_surface_->SetRendererID(current_renderer_id);
324 // Allow forward progress in the context now that the swap is complete.
325 UnblockBrowserIfNeeded();
328 void CALayerStorageProvider::LayerResetStorageProvider() {
329 // If we are providing back-pressure by waiting for a draw, that draw will
330 // now never come, so release the pressure now.
331 UnblockBrowserIfNeeded();
334 void CALayerStorageProvider::UnblockBrowserIfNeeded() {
335 if (!has_pending_draw_)
337 pending_draw_weak_factory_.InvalidateWeakPtrs();
338 has_pending_draw_ = false;
339 transport_surface_->SendSwapBuffers(
340 SurfaceHandleFromCAContextID([context_ contextId]),
345 } // namespace content