blob: bc874ab01d7ab9db6c01863dfe4930c528c5b418 [file] [log] [blame]
// Copyright (c) 2012 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 "content/browser/renderer_host/accelerated_plugin_view_mac.h"
#include <vector>
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "content/browser/renderer_host/render_widget_host_view_mac.h"
#include "content/public/browser/browser_thread.h"
#import "ui/base/cocoa/underlay_opengl_hosting_window.h"
#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_switches.h"
using content::BrowserThread;
using content::RenderWidgetHostViewMac;
@interface AcceleratedPluginView(Private)
// Initialize the OpenGL context.
- (void)initOpenGLContext;
@end // AcceleratedPluginView(Private)
@implementation AcceleratedPluginView
- (void)drawView {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TRACE_EVENT0("browser", "AcceleratedPluginViewMac::drawView");
if (renderWidgetHostView_ && cglContext_) {
// TODO(thakis): Pixel or view coordinates for size?
renderWidgetHostView_->DrawAcceleratedSurfaceInstance(
cglContext_, pluginHandle_, [super frame].size);
CGLFlushDrawable(cglContext_);
}
CGLSetCurrentContext(0);
}
- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r
pluginHandle:(gfx::PluginWindowHandle)pluginHandle {
if ((self = [super initWithFrame:NSZeroRect])) {
renderWidgetHostView_ = r;
pluginHandle_ = pluginHandle;
[self setAutoresizingMask:NSViewMaxXMargin|NSViewMinYMargin];
handlingGlobalFrameDidChange_ = NO;
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(globalFrameDidChange:)
name:NSViewGlobalFrameDidChangeNotification
object:self];
}
return self;
}
- (void)dealloc {
if (renderWidgetHostView_)
renderWidgetHostView_->DeallocFakePluginWindowHandle(pluginHandle_);
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)setCutoutRects:(NSArray*)cutout_rects {
cutoutRects_.reset([cutout_rects copy]);
}
- (void)onRenderWidgetHostViewGone {
if (!renderWidgetHostView_)
return;
// Deallocate the plugin handle while we still can.
renderWidgetHostView_->DeallocFakePluginWindowHandle(pluginHandle_);
renderWidgetHostView_ = NULL;
}
- (void)drawRect:(NSRect)rect {
TRACE_EVENT0("browser", "AcceleratedPluginView::drawRect");
const NSRect* dirtyRects;
int dirtyRectCount;
[self getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount];
{
gfx::ScopedNSGraphicsContextSaveGState scopedGState;
// 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];
}
// Punch a hole so that the OpenGL view shows through.
[[NSColor clearColor] set];
NSRectFillList(dirtyRects, dirtyRectCount);
}
[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 {
// Short-circuit recursive calls.
if (handlingGlobalFrameDidChange_)
return;
handlingGlobalFrameDidChange_ = YES;
if (glContext_) {
// 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);
}
handlingGlobalFrameDidChange_ = NO;
}
- (void)prepareForGLRendering {
TRACE_EVENT0("browser", "AcceleratedPluginView::prepareForGLRendering");
// If we're using OpenGL, make sure it is connected.
if ([glContext_ view] != self) {
[glContext_ setView:self];
}
}
- (void)renewGState {
TRACE_EVENT0("browser", "AcceleratedPluginView::renewGState");
// Synchronize with window server to avoid flashes or corrupt drawing.
[[self window] disableScreenUpdatesUntilFlush];
[self globalFrameDidChange:nil];
[super renewGState];
}
- (void)setUpGState {
TRACE_EVENT0("browser", "AcceleratedPluginView::setUpGState");
[self prepareForGLRendering];
}
// TODO(jbates): is lockFocus needed anymore? it seems redundant with
// setUpGState in traces.
- (void)lockFocus {
TRACE_EVENT0("browser", "AcceleratedPluginView::lockFocus");
[super lockFocus];
[self prepareForGLRendering];
[glContext_ makeCurrentContext];
}
- (void)viewDidUnhide {
TRACE_EVENT0("browser", "AcceleratedPluginView::viewDidUnhide");
[super viewDidUnhide];
// Delay context creation until view unhide, see http://crbug.com/109151
if (renderWidgetHostView_ && !glContext_) {
[self initOpenGLContext];
}
}
- (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 {
TRACE_EVENT0("browser", "AcceleratedPluginView::viewDidMoveToSuperview");
if (![self superview])
[self onRenderWidgetHostViewGone];
}
@end // @implementation AcceleratedPluginView
@implementation AcceleratedPluginView(Private)
- (void)initOpenGLContext {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TRACE_EVENT0("browser", "AcceleratedPluginViewMac::initOpenGLContext");
std::vector<NSOpenGLPixelFormatAttribute> attributes;
attributes.push_back(NSOpenGLPFADoubleBuffer);
if (gfx::GLContext::SupportsDualGpus())
attributes.push_back(NSOpenGLPFAAllowOfflineRenderers);
attributes.push_back(0);
glPixelFormat_.reset([[NSOpenGLPixelFormat alloc]
initWithAttributes:&attributes.front()]);
if (!glPixelFormat_)
LOG(ERROR) << "NSOpenGLPixelFormat initWithAttributes failed";
glContext_.reset([[NSOpenGLContext alloc] initWithFormat:glPixelFormat_
shareContext:nil]);
if (!glContext_)
LOG(ERROR) << "NSOpenGLContext initWithFormat failed";
// 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];
if (!cglContext_)
LOG(ERROR) << "CGLContextObj failed";
cglPixelFormat_ = (CGLPixelFormatObj)[glPixelFormat_ CGLPixelFormatObj];
if (!cglPixelFormat_)
LOG(ERROR) << "CGLPixelFormatObj failed";
// Draw at beam vsync.
GLint swapInterval;
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync))
swapInterval = 0;
else
swapInterval = 1;
[glContext_ setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
}
@end // @implementation AcceleratedPluginView(Private)