// 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 #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" #include "ui/gl/gpu_switching_manager.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; NSInteger 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 attributes; attributes.push_back(NSOpenGLPFADoubleBuffer); if (ui::GpuSwitchingManager::GetInstance()->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)