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 42 static 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