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