| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "chrome/browser/renderer_host/accelerated_plugin_view_mac.h" |
| |
| #include "base/command_line.h" |
| #import "base/mac/scoped_nsautorelease_pool.h" |
| #include "chrome/browser/renderer_host/render_widget_host_view_mac.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "ui/gfx/gl/gl_switches.h" |
| |
| @implementation AcceleratedPluginView |
| @synthesize cachedSize = cachedSize_; |
| |
| - (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::mac::ScopedNSAutoreleasePool pool; |
| |
| bool sendAck = (rendererId_ != 0 || routeId_ != 0); |
| uint64 currentSwapBuffersCount = swapBuffersCount_; |
| if (currentSwapBuffersCount == acknowledgedSwapBuffersCount_) { |
| return kCVReturnSuccess; |
| } |
| |
| [self drawView]; |
| |
| acknowledgedSwapBuffersCount_ = currentSwapBuffersCount; |
| if (sendAck && renderWidgetHostView_) { |
| renderWidgetHostView_->AcknowledgeSwapBuffers( |
| rendererId_, |
| routeId_, |
| gpuHostId_, |
| acknowledgedSwapBuffersCount_); |
| } |
| |
| return kCVReturnSuccess; |
| } |
| |
| // This is the renderer output callback function |
| static CVReturn 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; |
| cachedSize_ = NSZeroSize; |
| swapBuffersCount_ = 0; |
| acknowledgedSwapBuffersCount_ = 0; |
| rendererId_ = 0; |
| routeId_ = 0; |
| gpuHostId_ = 0; |
| |
| [self setAutoresizingMask:NSViewMaxXMargin|NSViewMinYMargin]; |
| |
| NSOpenGLPixelFormatAttribute attributes[] = |
| { NSOpenGLPFAAccelerated, NSOpenGLPFADoubleBuffer, 0}; |
| |
| glPixelFormat_.reset([[NSOpenGLPixelFormat alloc] |
| initWithAttributes:attributes]); |
| glContext_.reset([[NSOpenGLContext alloc] initWithFormat:glPixelFormat_ |
| shareContext:nil]); |
| |
| // We "punch a hole" in the window, and have the WindowServer render the |
| // OpenGL surface underneath so we can draw over it. |
| GLint belowWindow = -1; |
| [glContext_ setValues:&belowWindow forParameter:NSOpenGLCPSurfaceOrder]; |
| |
| cglContext_ = (CGLContextObj)[glContext_ CGLContextObj]; |
| cglPixelFormat_ = (CGLPixelFormatObj)[glPixelFormat_ CGLPixelFormatObj]; |
| |
| // Draw at beam vsync. |
| GLint swapInterval; |
| if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync)) |
| swapInterval = 0; |
| else |
| swapInterval = 1; |
| [glContext_ setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval]; |
| |
| // Set up a display link to do OpenGL rendering on a background thread. |
| CVDisplayLinkCreateWithActiveCGDisplays(&displayLink_); |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| CVDisplayLinkRelease(displayLink_); |
| if (renderWidgetHostView_) |
| renderWidgetHostView_->DeallocFakePluginWindowHandle(pluginHandle_); |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| [super dealloc]; |
| } |
| |
| - (void)drawView { |
| // Called on a background thread. Synchronized via the CGL context lock. |
| CGLLockContext(cglContext_); |
| |
| if (renderWidgetHostView_) { |
| // TODO(thakis): Pixel or view coordinates for size? |
| renderWidgetHostView_->DrawAcceleratedSurfaceInstance( |
| cglContext_, pluginHandle_, [self cachedSize]); |
| } |
| |
| CGLFlushDrawable(cglContext_); |
| CGLSetCurrentContext(0); |
| CGLUnlockContext(cglContext_); |
| } |
| |
| - (void)setCutoutRects:(NSArray*)cutout_rects { |
| cutoutRects_.reset([cutout_rects copy]); |
| } |
| |
| - (void)updateSwapBuffersCount:(uint64)count |
| fromRenderer:(int)rendererId |
| routeId:(int32)routeId |
| gpuHostId:(int)gpuHostId { |
| if (rendererId == 0 && routeId == 0) { |
| // This notification is coming from a plugin process, for which we |
| // don't have flow control implemented right now. Fake up a swap |
| // buffers count so that we can at least skip useless renders. |
| ++swapBuffersCount_; |
| } else { |
| rendererId_ = rendererId; |
| routeId_ = routeId; |
| gpuHostId_ = gpuHostId; |
| swapBuffersCount_ = count; |
| } |
| } |
| |
| - (void)onRenderWidgetHostViewGone { |
| if (!renderWidgetHostView_) |
| return; |
| |
| CGLLockContext(cglContext_); |
| // Deallocate the plugin handle while we still can. |
| renderWidgetHostView_->DeallocFakePluginWindowHandle(pluginHandle_); |
| renderWidgetHostView_ = NULL; |
| CGLUnlockContext(cglContext_); |
| } |
| |
| - (void)drawRect:(NSRect)rect { |
| const NSRect* dirtyRects; |
| int dirtyRectCount; |
| [self getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount]; |
| |
| [NSGraphicsContext saveGraphicsState]; |
| |
| // Mask out any cutout rects--somewhat counterintuitively cutout rects are |
| // places where clearColor is *not* drawn. The trick is that drawing nothing |
| // lets the parent view (i.e., the web page) show through, whereas drawing |
| // clearColor punches a hole in the window (letting OpenGL show through). |
| if ([cutoutRects_.get() count] > 0) { |
| NSBezierPath* path = [NSBezierPath bezierPath]; |
| // Trace the bounds clockwise to give a base clip rect of the whole view. |
| NSRect bounds = [self bounds]; |
| [path moveToPoint:bounds.origin]; |
| [path lineToPoint:NSMakePoint(NSMinX(bounds), NSMaxY(bounds))]; |
| [path lineToPoint:NSMakePoint(NSMaxX(bounds), NSMaxY(bounds))]; |
| [path lineToPoint:NSMakePoint(NSMaxX(bounds), NSMinY(bounds))]; |
| [path closePath]; |
| |
| // Then trace each cutout rect counterclockwise to remove that region from |
| // the clip region. |
| for (NSValue* rectWrapper in cutoutRects_.get()) { |
| [path appendBezierPathWithRect:[rectWrapper rectValue]]; |
| } |
| |
| [path addClip]; |
| |
| [NSGraphicsContext restoreGraphicsState]; |
| } |
| |
| // Punch a hole so that the OpenGL view shows through. |
| [[NSColor clearColor] set]; |
| NSRectFillList(dirtyRects, dirtyRectCount); |
| |
| [NSGraphicsContext restoreGraphicsState]; |
| |
| [self drawView]; |
| } |
| |
| - (void)rightMouseDown:(NSEvent*)event { |
| // The NSResponder documentation: "Note: The NSView implementation of this |
| // method does not pass the message up the responder chain, it handles it |
| // directly." |
| // That's bad, we want the next responder (RWHVMac) to handle this event to |
| // dispatch it to the renderer. |
| [[self nextResponder] rightMouseDown:event]; |
| } |
| |
| - (void)globalFrameDidChange:(NSNotification*)notification { |
| globalFrameDidChangeCGLLockCount_++; |
| CGLLockContext(cglContext_); |
| // This call to -update can call -globalFrameDidChange: again, see |
| // http://crbug.com/55754 comments 22 and 24. |
| [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); |
| |
| CGLSetCurrentContext(0); |
| CGLUnlockContext(cglContext_); |
| globalFrameDidChangeCGLLockCount_--; |
| |
| if (globalFrameDidChangeCGLLockCount_ == 0) { |
| // Make sure the view is synchronized with the correct display. |
| CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext( |
| displayLink_, cglContext_, cglPixelFormat_); |
| } |
| } |
| |
| - (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]; |
| CVDisplayLinkSetOutputCallback( |
| displayLink_, &DrawOneAcceleratedPluginCallback, self); |
| CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext( |
| displayLink_, cglContext_, cglPixelFormat_); |
| CVDisplayLinkStart(displayLink_); |
| } |
| [glContext_ makeCurrentContext]; |
| } |
| |
| - (void)viewWillMoveToWindow:(NSWindow*)newWindow { |
| // Stop the display link thread while the view is not visible. |
| if (newWindow) { |
| if (displayLink_ && !CVDisplayLinkIsRunning(displayLink_)) |
| CVDisplayLinkStart(displayLink_); |
| } else { |
| if (displayLink_ && CVDisplayLinkIsRunning(displayLink_)) |
| CVDisplayLinkStop(displayLink_); |
| } |
| |
| // Inform the window hosting this accelerated view that it needs to be |
| // transparent. |
| if (![self isHiddenOrHasHiddenAncestor]) { |
| if ([[self window] respondsToSelector:@selector(underlaySurfaceRemoved)]) |
| [static_cast<id>([self window]) underlaySurfaceRemoved]; |
| if ([newWindow respondsToSelector:@selector(underlaySurfaceAdded)]) |
| [static_cast<id>(newWindow) underlaySurfaceAdded]; |
| } |
| } |
| |
| - (void)viewDidHide { |
| [super viewDidHide]; |
| |
| if ([[self window] respondsToSelector:@selector(underlaySurfaceRemoved)]) { |
| [static_cast<id>([self window]) underlaySurfaceRemoved]; |
| } |
| } |
| |
| - (void)viewDidUnhide { |
| [super viewDidUnhide]; |
| |
| if ([[self window] respondsToSelector:@selector(underlaySurfaceRemoved)]) { |
| [static_cast<id>([self window]) underlaySurfaceAdded]; |
| } |
| } |
| |
| - (void)setFrame:(NSRect)frameRect { |
| [self setCachedSize:frameRect.size]; |
| [super setFrame:frameRect]; |
| } |
| |
| - (void)setFrameSize:(NSSize)newSize { |
| [self setCachedSize:newSize]; |
| [super setFrameSize:newSize]; |
| } |
| |
| - (BOOL)acceptsFirstResponder { |
| // Accept first responder if the first responder isn't the RWHVMac, and if the |
| // RWHVMac accepts first responder. If the RWHVMac does not accept first |
| // responder, do not accept on its behalf. |
| return ([[self window] firstResponder] != [self superview] && |
| [[self superview] acceptsFirstResponder]); |
| } |
| |
| - (BOOL)becomeFirstResponder { |
| // Delegate first responder to the RWHVMac. |
| [[self window] makeFirstResponder:[self superview]]; |
| return YES; |
| } |
| |
| - (void)viewDidMoveToSuperview { |
| if (![self superview]) |
| [self onRenderWidgetHostViewGone]; |
| } |
| @end |
| |