 Chromium Code Reviews
 Chromium Code Reviews Issue 3010054:
  Mac: Well-behaved accelerated plugins, preparation  (Closed)
    
  
    Issue 3010054:
  Mac: Well-behaved accelerated plugins, preparation  (Closed) 
  | Index: chrome/browser/renderer_host/render_widget_host_view_mac.mm | 
| diff --git a/chrome/browser/renderer_host/render_widget_host_view_mac.mm b/chrome/browser/renderer_host/render_widget_host_view_mac.mm | 
| index 647eba676c6e772100cb3936adf101a5cc3d581a..e4ad82cd310ceb7b702df2044033bc33e8aa63d1 100644 | 
| --- a/chrome/browser/renderer_host/render_widget_host_view_mac.mm | 
| +++ b/chrome/browser/renderer_host/render_widget_host_view_mac.mm | 
| @@ -11,6 +11,7 @@ | 
| #include "base/command_line.h" | 
| #include "base/histogram.h" | 
| #include "base/logging.h" | 
| +#import "base/scoped_nsautorelease_pool.h" | 
| #import "base/scoped_nsobject.h" | 
| #include "base/string_util.h" | 
| #include "base/sys_string_conversions.h" | 
| @@ -127,51 +128,152 @@ void DisablePasswordInput() { | 
| } // namespace | 
| -// AcceleratedPluginLayer ------------------------------------------------------ | 
| +// AcceleratedPluginView ------------------------------------------------------ | 
| -// This subclass of CAOpenGLLayer hosts the output of accelerated plugins on | 
| +// This subclass of NSView hosts the output of accelerated plugins on | 
| // the page. | 
| -@interface AcceleratedPluginLayer : CAOpenGLLayer { | 
| +@interface AcceleratedPluginView : NSView { | 
| + scoped_nsobject<NSOpenGLPixelFormat> glPixelFormat_; | 
| + CGLPixelFormatObj cglPixelFormat_; // weak, backed by |glPixelFormat_|. | 
| + scoped_nsobject<NSOpenGLContext> glContext_; | 
| + CGLContextObj cglContext_; // weak, backed by |glContext_|. | 
| + | 
| + CVDisplayLinkRef displayLink_; // Owned by us. | 
| + | 
| RenderWidgetHostViewMac* renderWidgetHostView_; // weak | 
| + gfx::PluginWindowHandle pluginHandle_; // weak | 
| + | 
| + // True if the backing IO surface was updated since we last painted. | 
| + BOOL surfaceWasSwapped_; | 
| } | 
| -- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r; | 
| +- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r | 
| + pluginHandle:(gfx::PluginWindowHandle)pluginHandle; | 
| +- (void)drawView; | 
| + | 
| +// This _must_ be atomic, since it's accessed from several threads. | 
| +@property BOOL surfaceWasSwapped; | 
| @end | 
| -@implementation AcceleratedPluginLayer | 
| -- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r { | 
| - self = [super init]; | 
| - if (self != nil) { | 
| +@implementation AcceleratedPluginView : NSView | 
| +@synthesize surfaceWasSwapped = surfaceWasSwapped_; | 
| + | 
| +- (CVReturn)getFrameForTime:(const CVTimeStamp*)outputTime { | 
| + // There is no autorelease pool when this method is called because it will be | 
| + // called from a background thread. | 
| + base::ScopedNSAutoreleasePool pool; | 
| + | 
| + if (![self surfaceWasSwapped]) | 
| + return kCVReturnSuccess; | 
| + | 
| + [self drawView]; | 
| + [self setSurfaceWasSwapped:NO]; | 
| + return kCVReturnSuccess; | 
| +} | 
| + | 
| +// This is the renderer output callback function | 
| +static CVReturn MyDisplayLinkCallback( | 
| 
stuartmorgan
2010/08/11 16:40:44
Can we give this a better name please?
 
Ken Russell (switch to Gerrit)
2010/08/14 02:12:11
Will change to "DrawOneAcceleratedPluginCallback".
 | 
| + CVDisplayLinkRef displayLink, | 
| + const CVTimeStamp* now, | 
| + const CVTimeStamp* outputTime, | 
| + CVOptionFlags flagsIn, | 
| + CVOptionFlags* flagsOut, | 
| + void* displayLinkContext) { | 
| + CVReturn result = | 
| + [(AcceleratedPluginView*)displayLinkContext getFrameForTime:outputTime]; | 
| + return result; | 
| +} | 
| + | 
| +- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r | 
| + pluginHandle:(gfx::PluginWindowHandle)pluginHandle { | 
| + if ((self = [super initWithFrame:NSZeroRect])) { | 
| renderWidgetHostView_ = r; | 
| + pluginHandle_ = pluginHandle; | 
| + | 
| + [self setAutoresizingMask:NSViewMaxXMargin|NSViewMinYMargin]; | 
| + | 
| + NSOpenGLPixelFormatAttribute attributes[] = | 
| + { NSOpenGLPFAAccelerated, NSOpenGLPFADoubleBuffer, 0}; | 
| + | 
| + glPixelFormat_.reset([[NSOpenGLPixelFormat alloc] | 
| + initWithAttributes:attributes]); | 
| + glContext_.reset([[NSOpenGLContext alloc] initWithFormat:glPixelFormat_ | 
| + shareContext:nil]); | 
| + | 
| + cglContext_ = (CGLContextObj)[glContext_ CGLContextObj]; | 
| + cglPixelFormat_ = (CGLPixelFormatObj)[glPixelFormat_ CGLPixelFormatObj]; | 
| + | 
| + // Draw at beam vsync. | 
| + GLint swapInterval = 1; | 
| + [glContext_ setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval]; | 
| + | 
| + | 
| + // Set up a display link to do OpenGL rendering on a background thread. | 
| + CVDisplayLinkCreateWithActiveCGDisplays(&displayLink_); | 
| + CVDisplayLinkSetOutputCallback(displayLink_, &MyDisplayLinkCallback, self); | 
| + CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext( | 
| + displayLink_, cglContext_, cglPixelFormat_); | 
| + CVDisplayLinkStart(displayLink_); | 
| } | 
| return self; | 
| } | 
| -- (void)drawInCGLContext:(CGLContextObj)glContext | 
| - pixelFormat:(CGLPixelFormatObj)pixelFormat | 
| - forLayerTime:(CFTimeInterval)timeInterval | 
| - displayTime:(const CVTimeStamp *)timeStamp { | 
| - renderWidgetHostView_->DrawAcceleratedSurfaceInstances(glContext); | 
| - [super drawInCGLContext:glContext | 
| - pixelFormat:pixelFormat | 
| - forLayerTime:timeInterval | 
| - displayTime:timeStamp]; | 
| -} | 
| - | 
| -- (void)setFrame:(CGRect)rect { | 
| - // The frame we get when the superlayer resizes doesn't make sense, so ignore | 
| - // it and just match the superlayer's size. See the email thread referenced in | 
| - // ensureAcceleratedPluginLayer for an explanation of why the superlayer | 
| - // isn't trustworthy. | 
| - [CATransaction begin]; | 
| - [CATransaction setValue:[NSNumber numberWithInt:0] | 
| - forKey:kCATransactionAnimationDuration]; | 
| - if ([self superlayer]) | 
| - [super setFrame:[[self superlayer] bounds]]; | 
| - else | 
| - [super setFrame:rect]; | 
| - [CATransaction commit]; | 
| +- (void)drawView { | 
| + // Called on a background thread. Synchronized via the CGL context lock. | 
| + CGLLockContext(cglContext_); | 
| + | 
| + renderWidgetHostView_->DrawAcceleratedSurfaceInstance( | 
| + cglContext_, pluginHandle_); | 
| + | 
| + CGLFlushDrawable(cglContext_); | 
| + CGLUnlockContext(cglContext_); | 
| +} | 
| + | 
| +- (void)drawRect:(NSRect)rect { | 
| + [self drawView]; | 
| +} | 
| + | 
| +- (void)globalFrameDidChange:(NSNotification*)notification { | 
| + CGLLockContext(cglContext_); | 
| + [glContext_ update]; | 
| + | 
| + // You would think that -update updates the viewport. You would be wrong. | 
| + CGLSetCurrentContext(cglContext_); | 
| + NSSize size = [self frame].size; | 
| + glViewport(0, 0, size.width, size.height); | 
| + | 
| + CGLUnlockContext(cglContext_); | 
| +} | 
| + | 
| +- (void)renewGState { | 
| + // Synchronize with window server to avoid flashes or corrupt drawing. | 
| + [[self window] disableScreenUpdatesUntilFlush]; | 
| + [self globalFrameDidChange:nil]; | 
| + [super renewGState]; | 
| +} | 
| + | 
| +- (void)lockFocus { | 
| + [super lockFocus]; | 
| + | 
| + // If we're using OpenGL, make sure it is connected and that the display link | 
| + // is running. | 
| + if ([glContext_ view] != self) { | 
| + [glContext_ setView:self]; | 
| + | 
| + [[NSNotificationCenter defaultCenter] | 
| + addObserver:self | 
| + selector:@selector(globalFrameDidChange:) | 
| + name:NSViewGlobalFrameDidChangeNotification | 
| + object:self]; | 
| + } | 
| + [glContext_ makeCurrentContext]; | 
| +} | 
| + | 
| +- (void)dealloc { | 
| + CVDisplayLinkRelease(displayLink_); | 
| + [[NSNotificationCenter defaultCenter] removeObserver:self]; | 
| + [super dealloc]; | 
| } | 
| @end | 
| @@ -316,12 +418,27 @@ void RenderWidgetHostViewMac::MovePluginWindows( | 
| webkit_glue::WebPluginGeometry geom = *iter; | 
| // Ignore bogus moves which claim to move the plugin to (0, 0) | 
| // with width and height (0, 0) | 
| - if (geom.window_rect.x() != 0 || | 
| - geom.window_rect.y() != 0 || | 
| - geom.window_rect.width() != 0 || | 
| - geom.window_rect.height() != 0) { | 
| - plugin_container_manager_.MovePluginContainer(geom); | 
| + if (geom.window_rect.x() == 0 && | 
| + geom.window_rect.y() == 0 && | 
| + geom.window_rect.IsEmpty()) { | 
| + continue; | 
| + } | 
| + | 
| + gfx::Rect rect = geom.window_rect; | 
| + if (geom.visible) { | 
| + rect.set_x(rect.x() + geom.clip_rect.x()); | 
| + rect.set_y(rect.y() + geom.clip_rect.y()); | 
| + rect.set_width(geom.clip_rect.width()); | 
| + rect.set_height(geom.clip_rect.height()); | 
| } | 
| + | 
| + PluginViewMap::iterator it = plugin_views_.find(geom.window); | 
| + CHECK(plugin_views_.end() != it); | 
| 
stuartmorgan
2010/08/11 16:40:44
CHECK? Really? Crashing end-users doesn't seem lik
 
Ken Russell (switch to Gerrit)
2010/08/14 02:12:11
Will change these to DCHECKs and add if tests for
 | 
| + NSRect new_rect([cocoa_view_ RectToNSRect:rect]); | 
| + [it->second setFrame:new_rect]; | 
| + [it->second setNeedsDisplay:YES]; | 
| + | 
| + plugin_container_manager_.MovePluginContainer(geom); | 
| } | 
| } | 
| } | 
| @@ -470,8 +587,12 @@ void RenderWidgetHostViewMac::Destroy() { | 
| if (!is_popup_menu_) { | 
| // Depth-first destroy all popups. Use ShutdownHost() to enforce | 
| // deepest-first ordering. | 
| - for (RenderWidgetHostViewCocoa* subview in [cocoa_view_ subviews]) { | 
| - [subview renderWidgetHostViewMac]->ShutdownHost(); | 
| + for (NSView* subview in [cocoa_view_ subviews]) { | 
| + if (![subview isKindOfClass:[RenderWidgetHostViewCocoa class]]) | 
| + continue; // Skip plugin views. | 
| 
stuartmorgan
2010/08/11 16:40:44
s/plugin/accelerated/
 
Ken Russell (switch to Gerrit)
2010/08/14 02:12:11
Will fix.
 | 
| + | 
| + [static_cast<RenderWidgetHostViewCocoa*>(subview) | 
| + renderWidgetHostViewMac]->ShutdownHost(); | 
| } | 
| // We've been told to destroy. | 
| @@ -615,14 +736,28 @@ void RenderWidgetHostViewMac::KillSelf() { | 
| gfx::PluginWindowHandle | 
| RenderWidgetHostViewMac::AllocateFakePluginWindowHandle(bool opaque, | 
| bool root) { | 
| - // Make sure we have a layer for the plugin to draw into. | 
| - [cocoa_view_ ensureAcceleratedPluginLayer]; | 
| + // Create an NSView to host the plugin's/compositor's pixels. | 
| + gfx::PluginWindowHandle handle = | 
| + plugin_container_manager_.AllocateFakePluginWindowHandle(opaque, root); | 
| + | 
| + scoped_nsobject<NSView> plugin_view( | 
| + [[AcceleratedPluginView alloc] initWithRenderWidgetHostViewMac:this | 
| + pluginHandle:handle]); | 
| + [plugin_view setHidden:YES]; | 
| + | 
| + [cocoa_view_ addSubview:plugin_view]; | 
| + plugin_views_[handle] = plugin_view; | 
| - return plugin_container_manager_.AllocateFakePluginWindowHandle(opaque, root); | 
| + return handle; | 
| } | 
| void RenderWidgetHostViewMac::DestroyFakePluginWindowHandle( | 
| gfx::PluginWindowHandle window) { | 
| + PluginViewMap::iterator it = plugin_views_.find(window); | 
| + CHECK(plugin_views_.end() != it); | 
| + [it->second removeFromSuperview]; | 
| + plugin_views_.erase(it); | 
| + | 
| plugin_container_manager_.DestroyFakePluginWindowHandle(window); | 
| } | 
| @@ -631,10 +766,26 @@ void RenderWidgetHostViewMac::AcceleratedSurfaceSetIOSurface( | 
| int32 width, | 
| int32 height, | 
| uint64 io_surface_identifier) { | 
| + PluginViewMap::iterator it = plugin_views_.find(window); | 
| + CHECK(plugin_views_.end() != it); | 
| + | 
| plugin_container_manager_.SetSizeAndIOSurface(window, | 
| width, | 
| height, | 
| io_surface_identifier); | 
| + | 
| + if (plugin_container_manager_.IsRootContainer(window)) { | 
| + // Fake up a WebPluginGeometry for the root window to set the | 
| + // container's size; we will never get a notification from the | 
| + // browser about the root window, only plugins. | 
| + webkit_glue::WebPluginGeometry geom; | 
| + gfx::Rect rect(0, 0, width, height); | 
| + geom.window = window; | 
| + geom.window_rect = rect; | 
| + geom.clip_rect = rect; | 
| + geom.visible = true; | 
| + MovePluginWindows(std::vector<webkit_glue::WebPluginGeometry>(1, geom)); | 
| + } | 
| } | 
| void RenderWidgetHostViewMac::AcceleratedSurfaceSetTransportDIB( | 
| @@ -642,6 +793,9 @@ void RenderWidgetHostViewMac::AcceleratedSurfaceSetTransportDIB( | 
| int32 width, | 
| int32 height, | 
| TransportDIB::Handle transport_dib) { | 
| + PluginViewMap::iterator it = plugin_views_.find(window); | 
| + CHECK(plugin_views_.end() != it); | 
| 
stuartmorgan
2010/08/11 16:40:44
Again, why CHECK?
 
Ken Russell (switch to Gerrit)
2010/08/14 02:12:11
Will change.
 | 
| + | 
| plugin_container_manager_.SetSizeAndTransportDIB(window, | 
| width, | 
| height, | 
| @@ -650,32 +804,38 @@ void RenderWidgetHostViewMac::AcceleratedSurfaceSetTransportDIB( | 
| void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped( | 
| gfx::PluginWindowHandle window) { | 
| - [cocoa_view_ drawAcceleratedPluginLayer]; | 
| - if (GetRenderWidgetHost()->is_gpu_rendering_active()) { | 
| - // Additionally dirty the entire region of the view to make AppKit | 
| - // and Core Animation think that our CALayer needs to repaint | 
| - // itself. | 
| - [cocoa_view_ setNeedsDisplayInRect:[cocoa_view_ frame]]; | 
| - } | 
| + PluginViewMap::iterator it = plugin_views_.find(window); | 
| + CHECK(plugin_views_.end() != it); | 
| 
stuartmorgan
2010/08/11 16:40:44
And here, and below, and in DrawAcceleratedSurface
 
Ken Russell (switch to Gerrit)
2010/08/14 02:12:11
Will change.
 | 
| + CHECK([it->second isKindOfClass:[AcceleratedPluginView class]]); | 
| + | 
| + AcceleratedPluginView* view = static_cast<AcceleratedPluginView*>(it->second); | 
| + [view setHidden:NO]; | 
| + [view setSurfaceWasSwapped:YES]; | 
| } | 
| -void RenderWidgetHostViewMac::DrawAcceleratedSurfaceInstances( | 
| - CGLContextObj context) { | 
| +void RenderWidgetHostViewMac::DrawAcceleratedSurfaceInstance( | 
| + CGLContextObj context, gfx::PluginWindowHandle plugin_handle) { | 
| + // Called on the display link thread. | 
| + PluginViewMap::iterator it = plugin_views_.find(plugin_handle); | 
| + CHECK(plugin_views_.end() != it); | 
| + | 
| CGLSetCurrentContext(context); | 
| - gfx::Rect rect = GetWindowRect(); | 
| + // TODO(thakis): Pixel or view coordinates? | 
| + NSSize size = [it->second frame].size; | 
| + | 
| glMatrixMode(GL_PROJECTION); | 
| glLoadIdentity(); | 
| // Note that we place the origin at the upper left corner with +y | 
| // going down | 
| - glOrtho(0, rect.width(), rect.height(), 0, -1, 1); | 
| + glOrtho(0, size.width, size.height, 0, -1, 1); | 
| glMatrixMode(GL_MODELVIEW); | 
| glLoadIdentity(); | 
| plugin_container_manager_.Draw( | 
| - context, GetRenderWidgetHost()->is_gpu_rendering_active()); | 
| + context, plugin_handle, GetRenderWidgetHost()->is_gpu_rendering_active()); | 
| } | 
| -void RenderWidgetHostViewMac::AcceleratedSurfaceContextChanged() { | 
| +void RenderWidgetHostViewMac::ForceTextureReload() { | 
| plugin_container_manager_.ForceTextureReload(); | 
| } | 
| @@ -925,7 +1085,7 @@ void RenderWidgetHostViewMac::SetTextInputActive(bool active) { | 
| [self keyEvent:theEvent wasKeyEquivalent:NO]; | 
| } | 
| -- (void)keyEvent:(NSEvent *)theEvent wasKeyEquivalent:(BOOL)equiv { | 
| +- (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv { | 
| DCHECK([theEvent type] != NSKeyDown || | 
| !equiv == !([theEvent modifierFlags] & NSCommandKeyMask)); | 
| @@ -1127,8 +1287,12 @@ void RenderWidgetHostViewMac::SetTextInputActive(bool active) { | 
| // If this view can be the key view, it is not a popup. Therefore, if it has | 
| // any children, they are popups that need to be canceled. | 
| if (canBeKeyView_) { | 
| - for (RenderWidgetHostViewCocoa* subview in [self subviews]) { | 
| - subview->renderWidgetHostView_->KillSelf(); | 
| + for (NSView* subview in [self subviews]) { | 
| + if (![subview isKindOfClass:[RenderWidgetHostViewCocoa class]]) | 
| + continue; // Skip plugin views. | 
| + | 
| + [static_cast<RenderWidgetHostViewCocoa*>(subview) | 
| + renderWidgetHostViewMac]->KillSelf(); | 
| } | 
| } | 
| } | 
| @@ -1981,9 +2145,12 @@ extern NSString *NSTextInputReplacementRangeAttributeName; | 
| NSWindow* newWindow = [self window]; | 
| // Pointer comparison only, since we don't know if lastWindow_ is still | 
| // valid. | 
| - if (newWindow && (newWindow != lastWindow_)) { | 
| - lastWindow_ = newWindow; | 
| - renderWidgetHostView_->WindowFrameChanged(); | 
| + if (newWindow) { | 
| + if (newWindow != lastWindow_) { | 
| + lastWindow_ = newWindow; | 
| + renderWidgetHostView_->WindowFrameChanged(); | 
| + } | 
| + renderWidgetHostView_->ForceTextureReload(); | 
| } | 
| } | 
| @@ -2049,48 +2216,6 @@ extern NSString *NSTextInputReplacementRangeAttributeName; | 
| } | 
| } | 
| -- (void)ensureAcceleratedPluginLayer { | 
| - if (acceleratedPluginLayer_.get()) | 
| - return; | 
| - | 
| - AcceleratedPluginLayer* pluginLayer = [[AcceleratedPluginLayer alloc] | 
| - initWithRenderWidgetHostViewMac:renderWidgetHostView_.get()]; | 
| - acceleratedPluginLayer_.reset(pluginLayer); | 
| - // Make our layer resize to fit the superlayer | 
| - pluginLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; | 
| - // Make the view layer-backed so that there will be a layer to hang the | 
| - // |layer| off of. This is not the "right" way to host a sublayer in a view, | 
| - // but the right way would require making the whole view's drawing system | 
| - // layer-based (using setLayer:). We don't want to do that (at least not | 
| - // yet) so instead we override setLayer: and re-bind our plugin layer each | 
| - // time the view's layer changes. For discussion see: | 
| - // http://lists.apple.com/archives/Cocoa-dev/2009/Feb/msg01132.html | 
| - [self setWantsLayer:YES]; | 
| - [self attachPluginLayer]; | 
| -} | 
| - | 
| -- (void)attachPluginLayer { | 
| - CALayer* pluginLayer = acceleratedPluginLayer_.get(); | 
| - if (!pluginLayer) | 
| - return; | 
| - | 
| - CALayer* rootLayer = [self layer]; | 
| - DCHECK(rootLayer != nil); | 
| - [pluginLayer setFrame:NSRectToCGRect([self bounds])]; | 
| - [rootLayer addSublayer:pluginLayer]; | 
| - renderWidgetHostView_->AcceleratedSurfaceContextChanged(); | 
| -} | 
| - | 
| -- (void)setLayer:(CALayer *)newLayer { | 
| - CALayer* pluginLayer = acceleratedPluginLayer_.get(); | 
| - if (!newLayer && [pluginLayer superlayer]) | 
| - [pluginLayer removeFromSuperlayer]; | 
| - | 
| - [super setLayer:newLayer]; | 
| - if ([self layer]) | 
| - [self attachPluginLayer]; | 
| -} | 
| - | 
| - (void)drawAcceleratedPluginLayer { | 
| [acceleratedPluginLayer_.get() setNeedsDisplay]; | 
| } |