Bug 829284 - Unity plugin doesn't display in HiDPI mode. r=bgirard a=lsblakk
authorSteven Michaud <smichaud@pobox.com>
Mon, 04 Feb 2013 19:02:01 -0600
changeset 127401 83ecaa8b253423bd5dfb6cb2796a59ecc97fcc50
parent 127400 f76aba08813d591f201d10adea68bf0886c96446
child 127402 c0eea5c5526a55222f8a9041ea391675e49beae7
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgirard, lsblakk
bugs829284
milestone20.0a2
Bug 829284 - Unity plugin doesn't display in HiDPI mode. r=bgirard a=lsblakk
dom/plugins/ipc/PluginInstanceChild.cpp
dom/plugins/ipc/PluginUtilsOSX.h
dom/plugins/ipc/PluginUtilsOSX.mm
gfx/2d/QuartzSupport.h
gfx/2d/QuartzSupport.mm
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -3045,17 +3045,18 @@ PluginInstanceChild::EnsureCurrentBuffer
 
     if (!mDoubleBufferCARenderer.HasCALayer()) {
         void *caLayer = nullptr;
         if (mDrawingModel == NPDrawingModelCoreGraphics) {
             if (!mCGLayer) {
                 bool avoidCGCrashes = !nsCocoaFeatures::OnMountainLionOrLater() &&
                   (GetQuirks() & PluginModuleChild::QUIRK_FLASH_AVOID_CGMODE_CRASHES);
                 caLayer = mozilla::plugins::PluginUtilsOSX::GetCGLayer(CallCGDraw, this,
-                                                                       avoidCGCrashes);
+                                                                       avoidCGCrashes,
+                                                                       mContentsScaleFactor);
 
                 if (!caLayer) {
                     PLUGIN_LOG_DEBUG(("GetCGLayer failed."));
                     return false;
                 }
             }
             mCGLayer = caLayer;
         } else {
--- a/dom/plugins/ipc/PluginUtilsOSX.h
+++ b/dom/plugins/ipc/PluginUtilsOSX.h
@@ -20,17 +20,18 @@ typedef void (*RemoteProcessEvents) (voi
 
 NPError ShowCocoaContextMenu(void* aMenu, int aX, int aY, void* pluginModule, RemoteProcessEvents remoteEvent);
 
 void InvokeNativeEventLoop();
 
 // Need to call back and send a cocoa draw event to the plugin.
 typedef void (*DrawPluginFunc) (CGContextRef, void*, nsIntRect aUpdateRect);
 
-void* GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance, bool aAvoidCGCrashes);
+void* GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance,
+                 bool aAvoidCGCrashes, double aContentsScaleFactor);
 void ReleaseCGLayer(void* cgLayer);
 void Repaint(void* cgLayer, nsIntRect aRect);
 
 bool SetProcessName(const char* aProcessName);
 
 /*
  * Provides a wrapper around nsCARenderer to manage double buffering
  * without having to unbind nsCARenderer on every surface swaps.
--- a/dom/plugins/ipc/PluginUtilsOSX.mm
+++ b/dom/plugins/ipc/PluginUtilsOSX.mm
@@ -129,19 +129,46 @@ CGBitmapContextSetDataFunc CGBitmapConte
     ::CGContextRelease(mLastCGContext);
   }
   [super dealloc];
 }
 
 @end
 
 void* mozilla::plugins::PluginUtilsOSX::GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance,
-                                                   bool aAvoidCGCrashes)
+                                                   bool aAvoidCGCrashes, double aContentsScaleFactor)
 {
-  CGBridgeLayer *bridgeLayer = [[CGBridgeLayer alloc] init ];
+  CGBridgeLayer *bridgeLayer = [[CGBridgeLayer alloc] init];
+
+  // We need to make bridgeLayer behave properly when its superlayer changes
+  // size (in nsCARenderer::SetBounds()).
+  bridgeLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
+  bridgeLayer.needsDisplayOnBoundsChange = YES;
+  NSNull *nullValue = [NSNull null];
+  NSDictionary *actions = [NSDictionary dictionaryWithObjectsAndKeys:
+                             nullValue, @"bounds",
+                             nullValue, @"contents",
+                             nullValue, @"contentsRect",
+                             nullValue, @"position",
+                             nil];
+  [bridgeLayer setStyle:[NSDictionary dictionaryWithObject:actions forKey:@"actions"]];
+
+  // For reasons that aren't clear (perhaps one or more OS bugs), we can only
+  // use full HiDPI resolution here if the tree is built with the 10.7 SDK or
+  // up.  If we build with the 10.6 SDK, changing the contentsScale property
+  // of bridgeLayer (even to the same value) causes it to stop working (go
+  // blank).  This doesn't happen with objects that are members of the CALayer
+  // class (as opposed to one of its subclasses).
+#if defined(MAC_OS_X_VERSION_10_7) && \
+    MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+  if ([bridgeLayer respondsToSelector:@selector(setContentsScale:)]) {
+    bridgeLayer.contentsScale = aContentsScaleFactor;
+  }
+#endif
+
   [bridgeLayer setDrawFunc:aFunc
             pluginInstance:aPluginInstance
             avoidCGCrashes:aAvoidCGCrashes];
   return bridgeLayer;
 }
 
 void mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(void *cgLayer) {
   CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)cgLayer;
--- a/gfx/2d/QuartzSupport.h
+++ b/gfx/2d/QuartzSupport.h
@@ -20,19 +20,19 @@ CGColorSpaceRef THEBES_API CreateSystemC
 // Manages a CARenderer
 struct _CGLPBufferObject;
 struct _CGLContextObject;
 
 enum AllowOfflineRendererEnum { ALLOW_OFFLINE_RENDERER, DISALLOW_OFFLINE_RENDERER };
 
 class nsCARenderer : public mozilla::RefCounted<nsCARenderer> {
 public:
-  nsCARenderer() : mCARenderer(nullptr), mFBOTexture(0), mOpenGLContext(nullptr),
-                   mCGImage(nullptr), mCGData(nullptr), mIOSurface(nullptr), mFBO(0),
-                   mIOTexture(0),
+  nsCARenderer() : mCARenderer(nullptr), mWrapperCALayer(nullptr), mFBOTexture(0),
+                   mOpenGLContext(nullptr), mCGImage(nullptr), mCGData(nullptr),
+                   mIOSurface(nullptr), mFBO(0), mIOTexture(0),
                    mUnsupportedWidth(UINT32_MAX), mUnsupportedHeight(UINT32_MAX),
                    mAllowOfflineRenderer(DISALLOW_OFFLINE_RENDERER),
                    mContentsScaleFactor(1.0) {}
   ~nsCARenderer();
   // aWidth and aHeight are in "display pixels".  A "display pixel" is the
   // smallest fully addressable part of a display.  But in HiDPI modes each
   // "display pixel" corresponds to more than one device pixel.  Multiply
   // display pixels by aContentsScaleFactor to get device pixels.
@@ -57,31 +57,32 @@ public:
   static nsresult DrawSurfaceToCGContext(CGContextRef aContext,
                                          MacIOSurface *surf,
                                          CGColorSpaceRef aColorSpace,
                                          int aX, int aY,
                                          size_t aWidth, size_t aHeight);
 
   // Remove & Add the layer without destroying
   // the renderer for fast back buffer swapping.
-  void DettachCALayer();
+  void DetachCALayer();
   void AttachCALayer(void *aCALayer);
 #ifdef DEBUG
   static void SaveToDisk(MacIOSurface *surf);
 #endif
 private:
   // aWidth and aHeight are in "display pixels".  Multiply by
   // mContentsScaleFactor to get device pixels.
   void SetBounds(int aWidth, int aHeight);
   // aWidth and aHeight are in "display pixels".  Multiply by
   // mContentsScaleFactor to get device pixels.
   void SetViewport(int aWidth, int aHeight);
   void Destroy();
 
   void *mCARenderer;
+  void *mWrapperCALayer;
   GLuint                    mFBOTexture;
   _CGLContextObject        *mOpenGLContext;
   CGImageRef                mCGImage;
   void                     *mCGData;
   mozilla::RefPtr<MacIOSurface> mIOSurface;
   uint32_t                  mFBO;
   uint32_t                  mIOTexture;
   uint32_t                  mUnsupportedWidth;
--- a/gfx/2d/QuartzSupport.mm
+++ b/gfx/2d/QuartzSupport.mm
@@ -484,16 +484,20 @@ void nsCARenderer::Destroy() {
   if (mCARenderer) {
     CARenderer* caRenderer = (CARenderer*)mCARenderer;
     // Bug 556453:
     // Explicitly remove the layer from the renderer
     // otherwise it does not always happen right away.
     caRenderer.layer = nullptr;
     [caRenderer release];
   }
+  if (mWrapperCALayer) {
+    CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+    [wrapperLayer release];
+  }
   if (mOpenGLContext) {
     if (mFBO || mIOTexture || mFBOTexture) {
       // Release these resources with the context that allocated them
       CGLContextObj oldContext = ::CGLGetCurrentContext();
       ::CGLSetCurrentContext(mOpenGLContext);
 
       if (mFBOTexture) {
         ::glDeleteTextures(1, &mFBOTexture);
@@ -512,16 +516,17 @@ void nsCARenderer::Destroy() {
     ::CGLDestroyContext((CGLContextObj)mOpenGLContext);
   }
   if (mCGImage) {
     ::CGImageRelease(mCGImage);
   }
   // mCGData is deallocated by cgdata_release_callback
 
   mCARenderer = nil;
+  mWrapperCALayer = nil;
   mFBOTexture = 0;
   mOpenGLContext = nullptr;
   mCGImage = nullptr;
   mIOSurface = nullptr;
   mFBO = 0;
   mIOTexture = 0;
 }
 
@@ -533,19 +538,16 @@ nsresult nsCARenderer::SetupRenderer(voi
   if (aWidth == 0 || aHeight == 0 || aContentsScaleFactor <= 0)
     return NS_ERROR_FAILURE;
 
   if (aWidth == mUnsupportedWidth &&
       aHeight == mUnsupportedHeight) {
     return NS_ERROR_FAILURE;
   }
 
-  CALayer* layer = (CALayer*)aCALayer;
-  CARenderer* caRenderer = nullptr;
-
   CGLPixelFormatAttribute attributes[] = {
     kCGLPFAAccelerated,
     kCGLPFADepthSize, (CGLPixelFormatAttribute)24,
     kCGLPFAAllowOfflineRenderers,
     (CGLPixelFormatAttribute)0
   };
 
   if (mAllowOfflineRenderer == DISALLOW_OFFLINE_RENDERER) {
@@ -564,27 +566,37 @@ nsresult nsCARenderer::SetupRenderer(voi
   if (::CGLCreateContext(format, nullptr, &mOpenGLContext) != kCGLNoError) {
     mUnsupportedWidth = aWidth;
     mUnsupportedHeight = aHeight;
     Destroy();
     return NS_ERROR_FAILURE;
   }
   ::CGLDestroyPixelFormat(format);
 
-  caRenderer = [[CARenderer rendererWithCGLContext:mOpenGLContext 
-                            options:nil] retain];
-  mCARenderer = caRenderer;
+  CARenderer* caRenderer = [[CARenderer rendererWithCGLContext:mOpenGLContext 
+                                                       options:nil] retain];
   if (caRenderer == nil) {
     mUnsupportedWidth = aWidth;
     mUnsupportedHeight = aHeight;
     Destroy();
     return NS_ERROR_FAILURE;
   }
+  CALayer* wrapperCALayer = [[CALayer layer] retain];
+  if (wrapperCALayer == nil) {
+    [caRenderer release];
+    mUnsupportedWidth = aWidth;
+    mUnsupportedHeight = aHeight;
+    Destroy();
+    return NS_ERROR_FAILURE;
+  }
 
-  caRenderer.layer = layer;
+  mCARenderer = caRenderer;
+  mWrapperCALayer = wrapperCALayer;
+  caRenderer.layer = wrapperCALayer;
+  [wrapperCALayer addSublayer:(CALayer*)aCALayer];
   mContentsScaleFactor = aContentsScaleFactor;
   size_t intScaleFactor = ceil(mContentsScaleFactor);
   SetBounds(aWidth, aHeight);
 
   // We target rendering to a CGImage if no shared IOSurface are given.
   if (!mIOSurface) {
     mCGData = malloc(aWidth*intScaleFactor*aHeight*4*intScaleFactor);
     if (!mCGData) {
@@ -691,92 +703,64 @@ nsresult nsCARenderer::SetupRenderer(voi
   if (oldContext)
     ::CGLSetCurrentContext(oldContext);
 
   return NS_OK;
 }
 
 void nsCARenderer::SetBounds(int aWidth, int aHeight) {
   CARenderer* caRenderer = (CARenderer*)mCARenderer;
-  CALayer* layer = [mCARenderer layer];
+  CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+  NSArray* sublayers = [wrapperLayer sublayers];
+  CALayer* pluginLayer = (CALayer*) [sublayers objectAtIndex:0];
 
   // Create a transaction and disable animations
   // to make the position update instant.
   [CATransaction begin];
-  NSMutableDictionary *newActions = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNull null], @"onOrderIn",
-                                   [NSNull null], @"onOrderOut",
-                                   [NSNull null], @"sublayers",
-                                   [NSNull null], @"contents",
-                                   [NSNull null], @"position",
-                                   [NSNull null], @"bounds",
-                                   nil];
-  layer.actions = newActions;
+  NSMutableDictionary *newActions =
+    [[NSMutableDictionary alloc] initWithObjectsAndKeys:
+      [NSNull null], @"onOrderIn",
+      [NSNull null], @"onOrderOut",
+      [NSNull null], @"sublayers",
+      [NSNull null], @"contents",
+      [NSNull null], @"position",
+      [NSNull null], @"bounds",
+      nil];
+  wrapperLayer.actions = newActions;
   [newActions release];
 
   // If we're in HiDPI mode, mContentsScaleFactor will (presumably) be 2.0.
   // For some reason, to make things work properly in HiDPI mode we need to
   // make caRenderer's 'bounds' and 'layer' different sizes -- to set 'bounds'
-  // to the size of 'layer's backing store.  To make plugins display at HiDPI
-  // resolution we also need to set 'layer's contentScale to
-  // mContentsScaleFactor.
+  // to the size of 'layer's backing store.  And to avoid this possibly
+  // confusing the plugin, we need to hide it's effects from the plugin by
+  // making pluginLayer (usually the CALayer* provided by the plugin) a
+  // sublayer of our own wrapperLayer (see bug 829284).
   size_t intScaleFactor = ceil(mContentsScaleFactor);
   [CATransaction setValue: [NSNumber numberWithFloat:0.0f] forKey: kCATransactionAnimationDuration];
   [CATransaction setValue: (id) kCFBooleanTrue forKey: kCATransactionDisableActions];
-  [layer setBounds:CGRectMake(0, 0, aWidth, aHeight)];
-  [layer setPosition:CGPointMake(aWidth/2.0, aHeight/2.0)];
+  [wrapperLayer setBounds:CGRectMake(0, 0, aWidth, aHeight)];
+  [wrapperLayer setPosition:CGPointMake(aWidth/2.0, aHeight/2.0)];
+  [pluginLayer setBounds:CGRectMake(0, 0, aWidth, aHeight)];
+  [pluginLayer setFrame:CGRectMake(0, 0, aWidth, aHeight)];
   caRenderer.bounds = CGRectMake(0, 0, aWidth * intScaleFactor, aHeight * intScaleFactor);
   if (mContentsScaleFactor != 1.0) {
-    CGAffineTransform affineTransform = [layer affineTransform];
+    CGAffineTransform affineTransform = [wrapperLayer affineTransform];
     affineTransform.a = mContentsScaleFactor;
     affineTransform.d = mContentsScaleFactor;
     affineTransform.tx = ((double)aWidth)/mContentsScaleFactor;
     affineTransform.ty = ((double)aHeight)/mContentsScaleFactor;
-    [layer setAffineTransform:affineTransform];
-    if ([layer respondsToSelector:@selector(setContentsScale:)]) {
-      // For reasons that aren't clear (perhaps one or more OS bugs), if layer
-      // belongs to a subclass of CALayer we can only use full HiDPI resolution
-      // here if the tree is built with the 10.7 SDK or up:  If we change
-      // layer.contentsScale (even to the same value), layer simply stops
-      // working (goes blank).  And even if we're building with the 10.7 SDK,
-      // we can't use full HiDPI resolution if layer belongs to CAOpenGLLayer
-      // or a subclass:  Changing layer.contentsScale to values higher than
-      // 1.0 makes it display only in the lower left part of its "box".
-      // We use CGBridgeLayer (a subclass of CALayer) to implement CoreGraphics
-      // mode for OOP plugins.  Shockwave uses a subclass of CAOpenGLLayer
-      // (SWRenderer) to implement CoreAnimation mode.  The SlingPlayer plugin
-      // uses another subclass of CAOpenGLLayer (CoreAnimationLayer).
-#if !defined(MAC_OS_X_VERSION_10_7) || \
-    MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
-      if ([layer isMemberOfClass:[CALayer class]])
-#else
-      if (![layer isKindOfClass:[CAOpenGLLayer class]])
-#endif
-      {
-        layer.contentsScale = mContentsScaleFactor;
-      }
-    }
+    [wrapperLayer setAffineTransform:affineTransform];
   } else {
     // These settings are the default values.  But they might have been
     // changed as above if we were previously running in a HiDPI mode
     // (i.e. if we just switched from that to a non-HiDPI mode).
-    [layer setAffineTransform:CGAffineTransformIdentity];
-    if ([layer respondsToSelector:@selector(setContentsScale:)]) {
-#if !defined(MAC_OS_X_VERSION_10_7) || \
-    MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
-      if ([layer isMemberOfClass:[CALayer class]])
-#else
-      if (![layer isKindOfClass:[CAOpenGLLayer class]])
-#endif
-      {
-        layer.contentsScale = 1.0;
-      }
-    }
+    [wrapperLayer setAffineTransform:CGAffineTransformIdentity];
   }
   [CATransaction commit];
-
 }
 
 void nsCARenderer::SetViewport(int aWidth, int aHeight) {
   size_t intScaleFactor = ceil(mContentsScaleFactor);
   aWidth *= intScaleFactor;
   aHeight *= intScaleFactor;
 
   ::glViewport(0.0, 0.0, aWidth, aHeight);
@@ -850,32 +834,34 @@ nsresult nsCARenderer::Render(int aWidth
     // We are expected to return a CGImageRef, we will set
     // it to nullptr in case we fail before the image is ready.
     *aOutCGImage = nullptr;
   }
 
   if (aWidth == 0 || aHeight == 0 || aContentsScaleFactor <= 0)
     return NS_OK;
 
-  if (!mCARenderer) {
+  if (!mCARenderer || !mWrapperCALayer) {
     return NS_ERROR_FAILURE;
   }
 
   CARenderer* caRenderer = (CARenderer*)mCARenderer;
+  CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
   size_t intScaleFactor = ceil(aContentsScaleFactor);
   int renderer_width = caRenderer.bounds.size.width / intScaleFactor;
   int renderer_height = caRenderer.bounds.size.height / intScaleFactor;
 
   if (renderer_width != aWidth || renderer_height != aHeight ||
       mContentsScaleFactor != aContentsScaleFactor) {
     // XXX: This should be optimized to not rescale the buffer
     //      if we are resizing down.
-    // caLayer is the CALayer* provided by the plugin, so we need to preserve
-    // it across the call to Destroy().
-    CALayer* caLayer = [caRenderer layer];
+    // caLayer may be the CALayer* provided by the plugin, so we need to
+    // preserve it across the call to Destroy().
+    NSArray* sublayers = [wrapperLayer sublayers];
+    CALayer* caLayer = (CALayer*) [sublayers objectAtIndex:0];
     // mIOSurface is set by AttachIOSurface(), not by SetupRenderer().  So
     // since it may have been set by a prior call to AttachIOSurface(), we
     // need to preserve it across the call to Destroy().
     mozilla::RefPtr<MacIOSurface> ioSurface = mIOSurface;
     Destroy();
     mIOSurface = ioSurface;
     if (SetupRenderer(caLayer, aWidth, aHeight, aContentsScaleFactor,
                       mAllowOfflineRenderer) != NS_OK) {
@@ -1000,27 +986,29 @@ nsresult nsCARenderer::DrawSurfaceToCGCo
                        subImage);
 
   ::CGImageRelease(subImage);
   ::CGImageRelease(cgImage);
   surf->Unlock();
   return NS_OK;
 }
 
-void nsCARenderer::DettachCALayer() {
-  CARenderer* caRenderer = (CARenderer*)mCARenderer;
-
-  caRenderer.layer = nil;
+void nsCARenderer::DetachCALayer() {
+  CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+  NSArray* sublayers = [wrapperLayer sublayers];
+  CALayer* oldLayer = (CALayer*) [sublayers objectAtIndex:0];
+  [oldLayer removeFromSuperlayer];
 }
 
 void nsCARenderer::AttachCALayer(void *aCALayer) {
-  CARenderer* caRenderer = (CARenderer*)mCARenderer;
-
-  CALayer* caLayer = (CALayer*)aCALayer;
-  caRenderer.layer = caLayer;
+  CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+  NSArray* sublayers = [wrapperLayer sublayers];
+  CALayer* oldLayer = (CALayer*) [sublayers objectAtIndex:0];
+  [oldLayer removeFromSuperlayer];
+  [wrapperLayer addSublayer:(CALayer*)aCALayer];
 }
 
 #ifdef DEBUG
 
 int sSaveToDiskSequence = 0;
 void nsCARenderer::SaveToDisk(MacIOSurface *surf) {
   surf->Lock();
   size_t bytesPerRow = surf->GetBytesPerRow();