Home | History | Annotate | Download | only in renderer_host
      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