• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2011 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.
4
5#import "chrome/browser/renderer_host/accelerated_plugin_view_mac.h"
6
7#include "base/command_line.h"
8#import "base/mac/scoped_nsautorelease_pool.h"
9#include "chrome/browser/renderer_host/render_widget_host_view_mac.h"
10#include "chrome/common/chrome_switches.h"
11#include "ui/gfx/gl/gl_switches.h"
12
13@implementation AcceleratedPluginView
14@synthesize cachedSize = cachedSize_;
15
16- (CVReturn)getFrameForTime:(const CVTimeStamp*)outputTime {
17  // There is no autorelease pool when this method is called because it will be
18  // called from a background thread.
19  base::mac::ScopedNSAutoreleasePool pool;
20
21  bool sendAck = (rendererId_ != 0 || routeId_ != 0);
22  uint64 currentSwapBuffersCount = swapBuffersCount_;
23  if (currentSwapBuffersCount == acknowledgedSwapBuffersCount_) {
24    return kCVReturnSuccess;
25  }
26
27  [self drawView];
28
29  acknowledgedSwapBuffersCount_ = currentSwapBuffersCount;
30  if (sendAck && renderWidgetHostView_) {
31    renderWidgetHostView_->AcknowledgeSwapBuffers(
32        rendererId_,
33        routeId_,
34        gpuHostId_,
35        acknowledgedSwapBuffersCount_);
36  }
37
38  return kCVReturnSuccess;
39}
40
41// This is the renderer output callback function
42static CVReturn DrawOneAcceleratedPluginCallback(
43    CVDisplayLinkRef displayLink,
44    const CVTimeStamp* now,
45    const CVTimeStamp* outputTime,
46    CVOptionFlags flagsIn,
47    CVOptionFlags* flagsOut,
48    void* displayLinkContext) {
49  CVReturn result =
50      [(AcceleratedPluginView*)displayLinkContext getFrameForTime:outputTime];
51  return result;
52}
53
54- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r
55                         pluginHandle:(gfx::PluginWindowHandle)pluginHandle {
56  if ((self = [super initWithFrame:NSZeroRect])) {
57    renderWidgetHostView_ = r;
58    pluginHandle_ = pluginHandle;
59    cachedSize_ = NSZeroSize;
60    swapBuffersCount_ = 0;
61    acknowledgedSwapBuffersCount_ = 0;
62    rendererId_ = 0;
63    routeId_ = 0;
64    gpuHostId_ = 0;
65
66    [self setAutoresizingMask:NSViewMaxXMargin|NSViewMinYMargin];
67
68    NSOpenGLPixelFormatAttribute attributes[] =
69        { NSOpenGLPFAAccelerated, NSOpenGLPFADoubleBuffer, 0};
70
71    glPixelFormat_.reset([[NSOpenGLPixelFormat alloc]
72        initWithAttributes:attributes]);
73    glContext_.reset([[NSOpenGLContext alloc] initWithFormat:glPixelFormat_
74                                                shareContext:nil]);
75
76    // We "punch a hole" in the window, and have the WindowServer render the
77    // OpenGL surface underneath so we can draw over it.
78    GLint belowWindow = -1;
79    [glContext_ setValues:&belowWindow forParameter:NSOpenGLCPSurfaceOrder];
80
81    cglContext_ = (CGLContextObj)[glContext_ CGLContextObj];
82    cglPixelFormat_ = (CGLPixelFormatObj)[glPixelFormat_ CGLPixelFormatObj];
83
84    // Draw at beam vsync.
85    GLint swapInterval;
86    if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync))
87      swapInterval = 0;
88    else
89      swapInterval = 1;
90    [glContext_ setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
91
92    // Set up a display link to do OpenGL rendering on a background thread.
93    CVDisplayLinkCreateWithActiveCGDisplays(&displayLink_);
94  }
95  return self;
96}
97
98- (void)dealloc {
99  CVDisplayLinkRelease(displayLink_);
100  if (renderWidgetHostView_)
101    renderWidgetHostView_->DeallocFakePluginWindowHandle(pluginHandle_);
102  [[NSNotificationCenter defaultCenter] removeObserver:self];
103  [super dealloc];
104}
105
106- (void)drawView {
107  // Called on a background thread. Synchronized via the CGL context lock.
108  CGLLockContext(cglContext_);
109
110  if (renderWidgetHostView_) {
111    // TODO(thakis): Pixel or view coordinates for size?
112    renderWidgetHostView_->DrawAcceleratedSurfaceInstance(
113        cglContext_, pluginHandle_, [self cachedSize]);
114  }
115
116  CGLFlushDrawable(cglContext_);
117  CGLSetCurrentContext(0);
118  CGLUnlockContext(cglContext_);
119}
120
121- (void)setCutoutRects:(NSArray*)cutout_rects {
122  cutoutRects_.reset([cutout_rects copy]);
123}
124
125- (void)updateSwapBuffersCount:(uint64)count
126                  fromRenderer:(int)rendererId
127                       routeId:(int32)routeId
128                     gpuHostId:(int)gpuHostId {
129  if (rendererId == 0 && routeId == 0) {
130    // This notification is coming from a plugin process, for which we
131    // don't have flow control implemented right now. Fake up a swap
132    // buffers count so that we can at least skip useless renders.
133    ++swapBuffersCount_;
134  } else {
135    rendererId_ = rendererId;
136    routeId_ = routeId;
137    gpuHostId_ = gpuHostId;
138    swapBuffersCount_ = count;
139  }
140}
141
142- (void)onRenderWidgetHostViewGone {
143  if (!renderWidgetHostView_)
144    return;
145
146  CGLLockContext(cglContext_);
147  // Deallocate the plugin handle while we still can.
148  renderWidgetHostView_->DeallocFakePluginWindowHandle(pluginHandle_);
149  renderWidgetHostView_ = NULL;
150  CGLUnlockContext(cglContext_);
151}
152
153- (void)drawRect:(NSRect)rect {
154  const NSRect* dirtyRects;
155  int dirtyRectCount;
156  [self getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount];
157
158  [NSGraphicsContext saveGraphicsState];
159
160  // Mask out any cutout rects--somewhat counterintuitively cutout rects are
161  // places where clearColor is *not* drawn. The trick is that drawing nothing
162  // lets the parent view (i.e., the web page) show through, whereas drawing
163  // clearColor punches a hole in the window (letting OpenGL show through).
164  if ([cutoutRects_.get() count] > 0) {
165    NSBezierPath* path = [NSBezierPath bezierPath];
166    // Trace the bounds clockwise to give a base clip rect of the whole view.
167    NSRect bounds = [self bounds];
168    [path moveToPoint:bounds.origin];
169    [path lineToPoint:NSMakePoint(NSMinX(bounds), NSMaxY(bounds))];
170    [path lineToPoint:NSMakePoint(NSMaxX(bounds), NSMaxY(bounds))];
171    [path lineToPoint:NSMakePoint(NSMaxX(bounds), NSMinY(bounds))];
172    [path closePath];
173
174    // Then trace each cutout rect counterclockwise to remove that region from
175    // the clip region.
176    for (NSValue* rectWrapper in cutoutRects_.get()) {
177      [path appendBezierPathWithRect:[rectWrapper rectValue]];
178    }
179
180    [path addClip];
181
182    [NSGraphicsContext restoreGraphicsState];
183  }
184
185  // Punch a hole so that the OpenGL view shows through.
186  [[NSColor clearColor] set];
187  NSRectFillList(dirtyRects, dirtyRectCount);
188
189  [NSGraphicsContext restoreGraphicsState];
190
191  [self drawView];
192}
193
194- (void)rightMouseDown:(NSEvent*)event {
195  // The NSResponder documentation: "Note: The NSView implementation of this
196  // method does not pass the message up the responder chain, it handles it
197  // directly."
198  // That's bad, we want the next responder (RWHVMac) to handle this event to
199  // dispatch it to the renderer.
200  [[self nextResponder] rightMouseDown:event];
201}
202
203- (void)globalFrameDidChange:(NSNotification*)notification {
204  globalFrameDidChangeCGLLockCount_++;
205  CGLLockContext(cglContext_);
206  // This call to -update can call -globalFrameDidChange: again, see
207  // http://crbug.com/55754 comments 22 and 24.
208  [glContext_ update];
209
210  // You would think that -update updates the viewport. You would be wrong.
211  CGLSetCurrentContext(cglContext_);
212  NSSize size = [self frame].size;
213  glViewport(0, 0, size.width, size.height);
214
215  CGLSetCurrentContext(0);
216  CGLUnlockContext(cglContext_);
217  globalFrameDidChangeCGLLockCount_--;
218
219  if (globalFrameDidChangeCGLLockCount_ == 0) {
220    // Make sure the view is synchronized with the correct display.
221    CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(
222       displayLink_, cglContext_, cglPixelFormat_);
223  }
224}
225
226- (void)renewGState {
227  // Synchronize with window server to avoid flashes or corrupt drawing.
228  [[self window] disableScreenUpdatesUntilFlush];
229  [self globalFrameDidChange:nil];
230  [super renewGState];
231}
232
233- (void)lockFocus {
234  [super lockFocus];
235
236  // If we're using OpenGL, make sure it is connected and that the display link
237  // is running.
238  if ([glContext_ view] != self) {
239    [glContext_ setView:self];
240
241    [[NSNotificationCenter defaultCenter]
242         addObserver:self
243            selector:@selector(globalFrameDidChange:)
244                name:NSViewGlobalFrameDidChangeNotification
245              object:self];
246    CVDisplayLinkSetOutputCallback(
247        displayLink_, &DrawOneAcceleratedPluginCallback, self);
248    CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(
249        displayLink_, cglContext_, cglPixelFormat_);
250    CVDisplayLinkStart(displayLink_);
251  }
252  [glContext_ makeCurrentContext];
253}
254
255- (void)viewWillMoveToWindow:(NSWindow*)newWindow {
256  // Stop the display link thread while the view is not visible.
257  if (newWindow) {
258    if (displayLink_ && !CVDisplayLinkIsRunning(displayLink_))
259      CVDisplayLinkStart(displayLink_);
260  } else {
261    if (displayLink_ && CVDisplayLinkIsRunning(displayLink_))
262      CVDisplayLinkStop(displayLink_);
263  }
264
265  // Inform the window hosting this accelerated view that it needs to be
266  // transparent.
267  if (![self isHiddenOrHasHiddenAncestor]) {
268    if ([[self window] respondsToSelector:@selector(underlaySurfaceRemoved)])
269      [static_cast<id>([self window]) underlaySurfaceRemoved];
270    if ([newWindow respondsToSelector:@selector(underlaySurfaceAdded)])
271      [static_cast<id>(newWindow) underlaySurfaceAdded];
272  }
273}
274
275- (void)viewDidHide {
276  [super viewDidHide];
277
278  if ([[self window] respondsToSelector:@selector(underlaySurfaceRemoved)]) {
279    [static_cast<id>([self window]) underlaySurfaceRemoved];
280  }
281}
282
283- (void)viewDidUnhide {
284  [super viewDidUnhide];
285
286  if ([[self window] respondsToSelector:@selector(underlaySurfaceRemoved)]) {
287    [static_cast<id>([self window]) underlaySurfaceAdded];
288  }
289}
290
291- (void)setFrame:(NSRect)frameRect {
292  [self setCachedSize:frameRect.size];
293  [super setFrame:frameRect];
294}
295
296- (void)setFrameSize:(NSSize)newSize {
297  [self setCachedSize:newSize];
298  [super setFrameSize:newSize];
299}
300
301- (BOOL)acceptsFirstResponder {
302  // Accept first responder if the first responder isn't the RWHVMac, and if the
303  // RWHVMac accepts first responder.  If the RWHVMac does not accept first
304  // responder, do not accept on its behalf.
305  return ([[self window] firstResponder] != [self superview] &&
306          [[self superview] acceptsFirstResponder]);
307}
308
309- (BOOL)becomeFirstResponder {
310  // Delegate first responder to the RWHVMac.
311  [[self window] makeFirstResponder:[self superview]];
312  return YES;
313}
314
315- (void)viewDidMoveToSuperview {
316  if (![self superview])
317    [self onRenderWidgetHostViewGone];
318}
319@end
320
321