Bug 579276. Part 2: If a transparent layer is over a solid background color, hoist that color into the layer to make it opaque. r=tnikkel
authorRobert O'Callahan <robert@ocallahan.org>
Thu, 02 Sep 2010 21:18:39 +1200
changeset 51947 2c1237769e8f4cd2ee4d31686528517948338d93
parent 51946 c9298e3a5837274fe7c6a2bf589877af61cfe8f5
child 51948 83cbb8c8ead2ec0d0eba274b06dbb1a9b2f5c23f
push id15477
push userrocallahan@mozilla.com
push dateFri, 03 Sep 2010 03:53:00 +0000
treeherdermozilla-central@1be7580c5c57 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstnikkel
bugs579276
milestone2.0b6pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 579276. Part 2: If a transparent layer is over a solid background color, hoist that color into the layer to make it opaque. r=tnikkel
layout/base/FrameLayerBuilder.cpp
layout/base/FrameLayerBuilder.h
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -59,20 +59,23 @@ namespace {
  */
 class LayerManagerData : public LayerUserData {
 public:
   LayerManagerData() :
     mInvalidateAllThebesContent(PR_FALSE),
     mInvalidateAllLayers(PR_FALSE)
   {
     MOZ_COUNT_CTOR(LayerManagerData);
-
     mFramesWithLayers.Init();
   }
   ~LayerManagerData() {
+    // Remove display item data properties now, since we won't be able
+    // to find these frames again without mFramesWithLayers.
+    mFramesWithLayers.EnumerateEntries(
+        FrameLayerBuilder::RemoveDisplayItemDataForFrame, nsnull);
     MOZ_COUNT_DTOR(LayerManagerData);
   }
 
   /**
    * Tracks which frames have layers associated with them.
    */
   nsTHashtable<nsPtrHashKey<nsIFrame> > mFramesWithLayers;
   PRPackedBool mInvalidateAllThebesContent;
@@ -246,16 +249,22 @@ protected:
   void CollectOldLayers();
   /**
    * If aItem used to belong to a ThebesLayer, invalidates the area of
    * aItem in that layer. If aNewLayer is a ThebesLayer, invalidates the area of
    * aItem in that layer.
    */
   void InvalidateForLayerChange(nsDisplayItem* aItem, Layer* aNewLayer);
   /**
+   * Try to determine whether the ThebesLayer at aThebesLayerIndex
+   * has an opaque single color covering the visible area behind it.
+   * If successful, return that color, otherwise return NS_RGBA(0,0,0,0).
+   */
+  nscolor FindOpaqueBackgroundColorFor(PRInt32 aThebesLayerIndex);
+  /**
    * Indicate that we are done adding items to the ThebesLayer at the top of
    * mThebesLayerDataStack. Set the final visible region and opaque-content
    * flag, and pop it off the stack.
    */
   void PopThebesLayerData();
   /**
    * Find the ThebesLayer to which we should assign the next display item.
    * We scan the ThebesLayerData stack to find the topmost ThebesLayer
@@ -302,24 +311,33 @@ protected:
   nsAutoTArray<nsRefPtr<Layer>,1>  mNewChildLayers;
   nsTArray<nsRefPtr<ThebesLayer> > mRecycledThebesLayers;
   nsTArray<nsRefPtr<ColorLayer> >  mRecycledColorLayers;
   PRUint32                         mNextFreeRecycledThebesLayer;
   PRUint32                         mNextFreeRecycledColorLayer;
   PRPackedBool                     mInvalidateAllThebesContent;
 };
 
+class ThebesDisplayItemLayerUserData : public LayerUserData
+{
+public:
+  ThebesDisplayItemLayerUserData() :
+    mForcedBackgroundColor(NS_RGBA(0,0,0,0)) {}
+
+  nscolor mForcedBackgroundColor;
+};
+
 /**
  * The address of gThebesDisplayItemLayerUserData is used as the user
  * data key for ThebesLayers created by FrameLayerBuilder.
  * It identifies ThebesLayers used to draw non-layer content, which are
  * therefore eligible for recycling. We want display items to be able to
  * create their own dedicated ThebesLayers in BuildLayer, if necessary,
  * and we wouldn't want to accidentally recycle those.
- * The user data is null.
+ * The user data is a ThebesDisplayItemLayerUserData.
  */
 PRUint8 gThebesDisplayItemLayerUserData;
 /**
  * The address of gColorLayerUserData is used as the user
  * data key for ColorLayers created by FrameLayerBuilder.
  * The user data is null.
  */
 PRUint8 gColorLayerUserData;
@@ -457,17 +475,17 @@ FrameLayerBuilder::WillEndTransaction(La
 /* static */ PLDHashOperator
 FrameLayerBuilder::UpdateDisplayItemDataForFrame(nsPtrHashKey<nsIFrame>* aEntry,
                                                  void* aUserArg)
 {
   FrameLayerBuilder* builder = static_cast<FrameLayerBuilder*>(aUserArg);
   nsIFrame* f = aEntry->GetKey();
   FrameProperties props = f->Properties();
   DisplayItemDataEntry* newDisplayItems =
-    builder->mNewDisplayItemData.GetEntry(f);
+    builder ? builder->mNewDisplayItemData.GetEntry(f) : nsnull;
   if (!newDisplayItems) {
     // This frame was visible, but isn't anymore.
     PRBool found;
     void* prop = props.Remove(DisplayItemDataProperty(), &found);
     NS_ASSERTION(found, "How can the frame property be missing?");
     // Pass PR_FALSE to not remove from mFramesWithLayers, we'll remove it
     // by returning PL_DHASH_REMOVE below.
     // Note that DestroyDisplayItemData would delete the user data
@@ -644,17 +662,18 @@ ContainerState::CreateOrRecycleThebesLay
     // InvalidateAllThebesLayerContents has ensured
     // the area is invalidated in the widget.
   } else {
     // Create a new thebes layer
     layer = mManager->CreateThebesLayer();
     if (!layer)
       return nsnull;
     // Mark this layer as being used for Thebes-painting display items
-    layer->SetUserData(&gThebesDisplayItemLayerUserData, nsnull);
+    layer->SetUserData(&gThebesDisplayItemLayerUserData,
+        new ThebesDisplayItemLayerUserData());
   }
 
   // Set up transform so that 0,0 in the Thebes layer corresponds to the
   // (pixel-snapped) top-left of the aActiveScrolledRoot.
   nsPoint offset = mBuilder->ToReferenceFrame(aActiveScrolledRoot);
   nsIntPoint pixOffset = offset.ToNearestPixels(
       aActiveScrolledRoot->PresContext()->AppUnitsPerDevPixel());
   gfxMatrix matrix;
@@ -704,39 +723,57 @@ SetVisibleRectForLayer(Layer* aLayer, co
       NS_WARNING("Visible rect transformed out of bounds");
     }
     aLayer->SetVisibleRegion(visibleRect);
   } else {
     NS_ERROR("Only 2D transformations currently supported");
   }
 }
 
+nscolor
+ContainerState::FindOpaqueBackgroundColorFor(PRInt32 aThebesLayerIndex)
+{
+  ThebesLayerData* target = mThebesLayerDataStack[aThebesLayerIndex];
+  for (PRInt32 i = aThebesLayerIndex - 1; i >= 0; --i) {
+    ThebesLayerData* candidate = mThebesLayerDataStack[i];
+    nsIntRegion visibleAboveIntersection;
+    visibleAboveIntersection.And(candidate->mVisibleAboveRegion, target->mVisibleRegion);
+    if (!visibleAboveIntersection.IsEmpty()) {
+      // Some non-Thebes content between target and candidate; this is
+      // hopeless
+      break;
+    }
+
+    nsIntRegion intersection;
+    intersection.And(candidate->mVisibleRegion, target->mVisibleRegion);
+    if (intersection.IsEmpty()) {
+      // The layer doesn't intersect our target, ignore it and move on
+      continue;
+    }
+ 
+    // The candidate intersects our target. If any layer has a solid-color
+    // area behind our target, this must be it. Scan its display items.
+    nsPresContext* presContext = mContainerFrame->PresContext();
+    nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+    nsRect rect =
+      target->mVisibleRegion.GetBounds().ToAppUnits(appUnitsPerDevPixel);
+    return mBuilder->LayerBuilder()->
+      FindOpaqueColorCovering(mBuilder, candidate->mLayer, rect);
+  }
+  return NS_RGBA(0,0,0,0);
+}
+
 void
 ContainerState::PopThebesLayerData()
 {
   NS_ASSERTION(!mThebesLayerDataStack.IsEmpty(), "Can't pop");
 
   PRInt32 lastIndex = mThebesLayerDataStack.Length() - 1;
   ThebesLayerData* data = mThebesLayerDataStack[lastIndex];
 
-  if (lastIndex > 0) {
-    // Since we're going to pop off the last ThebesLayerData, the
-    // mVisibleAboveRegion of the second-to-last item will need to include
-    // the regions of the last item.
-    ThebesLayerData* nextData = mThebesLayerDataStack[lastIndex - 1];
-    nextData->mVisibleAboveRegion.Or(nextData->mVisibleAboveRegion,
-                                     data->mVisibleAboveRegion);
-    nextData->mVisibleAboveRegion.Or(nextData->mVisibleAboveRegion,
-                                     data->mVisibleRegion);
-    nextData->mDrawAboveRegion.Or(nextData->mDrawAboveRegion,
-                                     data->mDrawAboveRegion);
-    nextData->mDrawAboveRegion.Or(nextData->mDrawAboveRegion,
-                                     data->mDrawRegion);
-  }
-
   Layer* layer;
   if (data->mIsSolidColorInVisibleRegion) {
     nsRefPtr<ColorLayer> colorLayer = CreateOrRecycleColorLayer();
     colorLayer->SetColor(data->mSolidColor);
 
     NS_ASSERTION(!mNewChildLayers.Contains(colorLayer), "Layer already in list???");
     nsTArray_base::index_type index = mNewChildLayers.IndexOf(data->mLayer);
     NS_ASSERTION(index != nsTArray_base::NoIndex, "Thebes layer not found?");
@@ -773,17 +810,57 @@ ContainerState::PopThebesLayerData()
     rgn.MoveBy(-nsIntPoint(PRInt32(transform.x0), PRInt32(transform.y0)));
     layer->SetVisibleRegion(rgn);
   } else {
     NS_ERROR("Only 2D transformations currently supported");
   }
 
   nsIntRegion transparentRegion;
   transparentRegion.Sub(data->mVisibleRegion, data->mOpaqueRegion);
-  layer->SetIsOpaqueContent(transparentRegion.IsEmpty());
+  PRBool isOpaque = transparentRegion.IsEmpty();
+  // For translucent ThebesLayers, try to find an opaque background
+  // color that covers the entire area beneath it so we can pull that
+  // color into this layer to make it opaque.
+  if (layer == data->mLayer) {
+    nscolor backgroundColor = NS_RGBA(0,0,0,0);
+    if (!isOpaque) {
+      backgroundColor = FindOpaqueBackgroundColorFor(lastIndex);
+      if (NS_GET_A(backgroundColor) == 255) {
+        isOpaque = PR_TRUE;
+      }
+    }
+
+    // Store the background color
+    ThebesDisplayItemLayerUserData* userData =
+      static_cast<ThebesDisplayItemLayerUserData*>
+        (data->mLayer->GetUserData(&gThebesDisplayItemLayerUserData));
+    NS_ASSERTION(userData, "where did our user data go?");
+    if (userData->mForcedBackgroundColor != backgroundColor) {
+      // Invalidate the entire target ThebesLayer since we're changing
+      // the background color
+      data->mLayer->InvalidateRegion(data->mLayer->GetValidRegion());
+    }
+    userData->mForcedBackgroundColor = backgroundColor;
+  }
+  layer->SetIsOpaqueContent(isOpaque);
+
+  if (lastIndex > 0) {
+    // Since we're going to pop off the last ThebesLayerData, the
+    // mVisibleAboveRegion of the second-to-last item will need to include
+    // the regions of the last item.
+    ThebesLayerData* nextData = mThebesLayerDataStack[lastIndex - 1];
+    nextData->mVisibleAboveRegion.Or(nextData->mVisibleAboveRegion,
+                                     data->mVisibleAboveRegion);
+    nextData->mVisibleAboveRegion.Or(nextData->mVisibleAboveRegion,
+                                     data->mVisibleRegion);
+    nextData->mDrawAboveRegion.Or(nextData->mDrawAboveRegion,
+                                     data->mDrawAboveRegion);
+    nextData->mDrawAboveRegion.Or(nextData->mDrawAboveRegion,
+                                     data->mDrawRegion);
+  }
 
   mThebesLayerDataStack.RemoveElementAt(lastIndex);
 }
 
 void
 ContainerState::ThebesLayerData::Accumulate(const nsIntRect& aVisibleRect,
                                             const nsIntRect& aDrawRect,
                                             const nsIntRect* aOpaqueRect,
@@ -1121,16 +1198,38 @@ FrameLayerBuilder::AddLayerDisplayItem(L
 
   nsIFrame* f = aItem->GetUnderlyingFrame();
   DisplayItemDataEntry* entry = mNewDisplayItemData.PutEntry(f);
   if (entry) {
     entry->mData.AppendElement(DisplayItemData(aLayer, aItem->GetPerFrameKey()));
   }
 }
 
+nscolor
+FrameLayerBuilder::FindOpaqueColorCovering(nsDisplayListBuilder* aBuilder,
+                                           ThebesLayer* aLayer,
+                                           const nsRect& aRect)
+{
+  ThebesLayerItemsEntry* entry = mThebesLayerItems.GetEntry(aLayer);
+  NS_ASSERTION(entry, "Must know about this layer!");
+  for (PRInt32 i = entry->mItems.Length() - 1; i >= 0; --i) {
+    nsDisplayItem* item = entry->mItems[i].mItem;
+    const nsRect& visible = item->GetVisibleRect();
+    if (!visible.Intersects(aRect))
+      continue;
+
+    nscolor color;
+    if (visible.Contains(aRect) && item->IsUniform(aBuilder, &color) &&
+        NS_GET_A(color) == 255)
+      return color;
+    break;
+  }
+  return NS_RGBA(0,0,0,0);
+}
+
 void
 ContainerState::CollectOldLayers()
 {
   for (Layer* layer = mContainerLayer->GetFirstChild(); layer;
        layer = layer->GetNextSibling()) {
     if (layer->HasUserData(&gColorLayerUserData)) {
       mRecycledColorLayers.AppendElement(static_cast<ColorLayer*>(layer));
     } else if (layer->HasUserData(&gThebesDisplayItemLayerUserData)) {
@@ -1417,16 +1516,25 @@ FrameLayerBuilder::DrawThebesLayer(Thebe
     NS_ASSERTION(entry, "We shouldn't be drawing into a layer with no items!");
     items.SwapElements(entry->mItems);
     containerLayerFrame = entry->mContainerLayerFrame;
     // Later after this point, due to calls to DidEndTransaction
     // for temporary layer managers, mThebesLayerItems can change,
     // so 'entry' could become invalid.
   }
 
+  ThebesDisplayItemLayerUserData* userData =
+    static_cast<ThebesDisplayItemLayerUserData*>
+      (aLayer->GetUserData(&gThebesDisplayItemLayerUserData));
+  NS_ASSERTION(userData, "where did our user data go?");
+  if (NS_GET_A(userData->mForcedBackgroundColor) > 0) {
+    aContext->SetColor(gfxRGBA(userData->mForcedBackgroundColor));
+    aContext->Paint();
+  }
+
   gfxMatrix transform;
   if (!aLayer->GetTransform().Is2D(&transform)) {
     NS_ERROR("non-2D transform in our Thebes layer!");
     return;
   }
   NS_ASSERTION(!transform.HasNonIntegerTranslation(),
                "Matrix not just an integer translation?");
   // make the origin of the context coincide with the origin of the
--- a/layout/base/FrameLayerBuilder.h
+++ b/layout/base/FrameLayerBuilder.h
@@ -238,16 +238,36 @@ public:
    * Given a frame and a display item key that uniquely identifies a
    * display item for the frame, find the layer that was last used to
    * render that display item. Returns null if there is no such layer.
    * This could be a dedicated layer for the display item, or a ThebesLayer
    * that renders many display items.
    */
   Layer* GetOldLayerFor(nsIFrame* aFrame, PRUint32 aDisplayItemKey);
 
+  /**
+   * A useful hashtable iteration function that removes the
+   * DisplayItemData property for the frame, clears its
+   * NS_FRAME_HAS_CONTAINER_LAYER bit and returns PL_DHASH_REMOVE.
+   * aClosure is ignored.
+   */
+  static PLDHashOperator RemoveDisplayItemDataForFrame(nsPtrHashKey<nsIFrame>* aEntry,
+                                                       void* aClosure)
+  {
+    return UpdateDisplayItemDataForFrame(aEntry, nsnull);
+  }
+
+  /**
+   * Try to determine whether the ThebesLayer aLayer paints an opaque
+   * single color everywhere it's visible in aRect.
+   * If successful, return that color, otherwise return NS_RGBA(0,0,0,0).
+   */
+  nscolor FindOpaqueColorCovering(nsDisplayListBuilder* aBuilder,
+                                  ThebesLayer* aLayer, const nsRect& aRect);
+
 protected:
   /**
    * We store an array of these for each frame that is associated with
    * one or more retained layers. Each DisplayItemData records the layer
    * used to render one of the frame's display items.
    */
   class DisplayItemData {
   public: