// 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