Bug 534425. Part 4: Implement nsDisplayList::Paint using layers. r=mats,sr=dbaron
authorRobert O'Callahan <robert@ocallahan.org>
Mon, 01 Mar 2010 20:56:19 +1300
changeset 38803 9958e7a9771e4a05afd6165cf63b43aad8a3af61
parent 38802 038a75a355ecda9edd81a2d98c1f70453b0bb1f3
child 38804 97ad975ec651f8c0ffa8cc507765e14ba6c97d76
push id11865
push userrocallahan@mozilla.com
push dateMon, 01 Mar 2010 08:06:43 +0000
treeherderautoland@c85d57ea1d37 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmats, dbaron
bugs534425
milestone1.9.3a3pre
Bug 534425. Part 4: Implement nsDisplayList::Paint using layers. r=mats,sr=dbaron
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/build/Makefile.in
toolkit/library/libxul-config.mk
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -55,16 +55,19 @@
 #include "gfxMatrix.h"
 #ifdef MOZ_SVG
 #include "nsSVGIntegrationUtils.h"
 #endif
 #include "nsLayoutUtils.h"
 
 #include "imgIContainer.h"
 #include "nsIInterfaceRequestorUtils.h"
+#include "BasicLayers.h"
+
+using namespace mozilla::layers;
 
 nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame,
     PRBool aIsForEvents, PRBool aBuildCaret)
     : mReferenceFrame(aReferenceFrame),
       mMovingFrame(nsnull),
       mSaveVisibleRegionOfMovingContent(nsnull),
       mIgnoreScrollFrame(nsnull),
       mCurrentTableItem(nsnull),
@@ -396,30 +399,440 @@ nsDisplayList::ComputeVisibility(nsDispl
     }
   }
 
   aBuilder->AccumulateVisibleRegionOfMovingContent(
     nsRegion(movingContentAccumulatedBounds),
     movingContentVisibleRegionBeforeMove,
     movingContentVisibleRegionAfterMove);
 
+  mIsOpaque = aVisibleRegion->IsEmpty();
 #ifdef DEBUG
   mDidComputeVisibility = PR_TRUE;
 #endif
 }
 
+namespace {
+/**
+ * This class iterates through a display list tree, descending only into
+ * nsDisplayClip items, and returns each display item encountered during
+ * such iteration. Along with each item we also return the clip rect
+ * accumulated for the item.
+ */
+class ClippedItemIterator {
+public:
+  ClippedItemIterator(const nsDisplayList* aList)
+  {
+    DescendIntoList(aList, nsnull, nsnull);
+    AdvanceToItem();
+  }
+  PRBool IsDone()
+  {
+    return mStack.IsEmpty();
+  }
+  void Next()
+  {
+    State* top = StackTop();
+    top->mItem = top->mItem->GetAbove();
+    AdvanceToItem();
+  }
+  // Returns null if there is no clipping affecting the item. The
+  // clip rect is in device pixels
+  const gfxRect* GetEffectiveClipRect()
+  {
+    State* top = StackTop();
+    return top->mHasClipRect ? &top->mEffectiveClipRect : nsnull;
+  }
+  nsDisplayItem* Item()
+  {
+    return StackTop()->mItem;
+  }
+
+private:
+  // We maintain a stack of state objects. Each State object represents
+  // where we're up to in the iteration of a list.
+  struct State {
+    // The current item we're at in the list
+    nsDisplayItem* mItem;
+    // The effective clip rect applying to all the items in this list
+    gfxRect mEffectiveClipRect;
+    PRPackedBool mHasClipRect;
+  };
+
+  State* StackTop()
+  {
+    return &mStack[mStack.Length() - 1];
+  }
+  void DescendIntoList(const nsDisplayList* aList,
+                       nsPresContext* aPresContext,
+                       const nsRect* aClipRect)
+  {
+    State* state = mStack.AppendElement();
+    if (!state)
+      return;
+    if (mStack.Length() >= 2) {
+      *state = mStack[mStack.Length() - 2];
+    } else {
+      state->mHasClipRect = PR_FALSE;
+    }
+    state->mItem = aList->GetBottom();
+    if (aClipRect) {
+      gfxRect r(aClipRect->x, aClipRect->y, aClipRect->width, aClipRect->height);
+      r.ScaleInverse(aPresContext->AppUnitsPerDevPixel());
+      if (state->mHasClipRect) {
+        state->mEffectiveClipRect = state->mEffectiveClipRect.Intersect(r);
+      } else {
+        state->mEffectiveClipRect = r;
+        state->mHasClipRect = PR_TRUE;
+      }
+    }
+  }
+  // Advances to an item that the iterator should return.
+  void AdvanceToItem()
+  {
+    while (!mStack.IsEmpty()) {
+      State* top = StackTop();
+      if (!top->mItem) {
+        mStack.SetLength(mStack.Length() - 1);
+        if (!mStack.IsEmpty()) {
+          top = StackTop();
+          top->mItem = top->mItem->GetAbove();
+        }
+        continue;
+      }
+      if (top->mItem->GetType() != nsDisplayItem::TYPE_CLIP)
+        return;
+      nsDisplayClip* clipItem = static_cast<nsDisplayClip*>(top->mItem);
+      nsRect clip = clipItem->GetClipRect();
+      DescendIntoList(clipItem->GetList(),
+                      clipItem->GetClippingFrame()->PresContext(),
+                      &clip);
+    }
+  }
+
+  nsAutoTArray<State,10> mStack;
+};
+}
+
+/**
+ * Given a (possibly clipped) display item in aItem, try to append it to
+ * the items in aGroup. If aItem is the next item in the sublist in
+ * aGroup, and the clipping matches, we can just update aGroup in-place,
+ * otherwise we'll allocate a new ItemGroup, add it to the linked list,
+ * and put aItem in the new ItemGroup. We return the ItemGroup into which
+ * aItem was inserted.
+ */
+static nsDisplayList::ItemGroup*
+AddToItemGroup(nsDisplayListBuilder* aBuilder,
+               nsDisplayList::ItemGroup* aGroup, nsDisplayItem* aItem,
+               const gfxRect* aClipRect) {
+  NS_ASSERTION(!aGroup->mNextItemsForLayer,
+               "aGroup must be the last group in the chain");
+
+  if (!aGroup->mStartItem) {
+    aGroup->mStartItem = aItem;
+    aGroup->mEndItem = aItem->GetAbove();
+    aGroup->mHasClipRect = aClipRect != nsnull;
+    if (aClipRect) {
+      aGroup->mClipRect = *aClipRect;
+    }
+    return aGroup;
+  }
+
+  if (aGroup->mEndItem == aItem &&
+      (aGroup->mHasClipRect
+       ? (aClipRect && aGroup->mClipRect == *aClipRect)
+       : !aClipRect))  {
+    aGroup->mEndItem = aItem->GetAbove();
+    return aGroup;
+  }
+
+  nsDisplayList::ItemGroup* itemGroup =
+    new (aBuilder) nsDisplayList::ItemGroup();
+  if (!itemGroup)
+    return aGroup;
+  aGroup->mNextItemsForLayer = itemGroup;
+  return AddToItemGroup(aBuilder, itemGroup, aItem, aClipRect);
+}
+
+/**
+ * Create an empty Thebes layer, with an empty ItemGroup associated with
+ * it, and append it to aLayers.
+ */
+static nsDisplayList::ItemGroup*
+CreateEmptyThebesLayer(nsDisplayListBuilder* aBuilder,
+                       LayerManager* aManager,
+                       nsTArray<nsDisplayList::LayerItems>* aLayers) {
+  nsDisplayList::ItemGroup* itemGroup =
+    new (aBuilder) nsDisplayList::ItemGroup();
+  if (!itemGroup)
+    return nsnull;
+  nsRefPtr<ThebesLayer> thebesLayer =
+    aManager->CreateThebesLayer();
+  if (!thebesLayer)
+    return nsnull;
+  nsDisplayList::LayerItems* layerItems =
+    aLayers->AppendElement(nsDisplayList::LayerItems(itemGroup));
+  layerItems->mThebesLayer = thebesLayer;
+  layerItems->mLayer = thebesLayer.forget();
+  return itemGroup;
+}
+
+/**
+ * This is the heart of layout's integration with layers. We
+ * use a ClippedItemIterator to iterate through descendant display
+ * items. Each item either has its own layer or is assigned to a
+ * ThebesLayer. We create ThebesLayers as necessary, although we try
+ * to put items in the bottom-most ThebesLayer because that is most
+ * likely to be able to render with an opaque background, which will often
+ * be required for subpixel text antialiasing to work.
+ */
+void nsDisplayList::BuildLayers(nsDisplayListBuilder* aBuilder,
+                                LayerManager* aManager,
+                                nsTArray<LayerItems>* aLayers) const {
+  NS_ASSERTION(aLayers->IsEmpty(), "aLayers must be initially empty");
+
+  // Create "bottom" Thebes layer. We'll try to put as much content
+  // as possible in this layer because if the container is filled with
+  // opaque content, this bottommost layer can also be treated as opaque,
+  // which means content in this layer can have subpixel AA.
+  // firstThebesLayerItems always points to the last ItemGroup for the
+  // first Thebes layer.
+  ItemGroup* firstThebesLayerItems =
+    CreateEmptyThebesLayer(aBuilder, aManager, aLayers);
+  if (!firstThebesLayerItems)
+    return;
+  // lastThebesLayerItems always points to the last ItemGroup for the
+  // topmost layer, if it's a ThebesLayer. If the top layer is not a
+  // Thebes layer, this is null.
+  ItemGroup* lastThebesLayerItems = firstThebesLayerItems;
+  // This region contains the bounds of all the content that is above
+  // the first Thebes layer.
+  nsRegion areaAboveFirstThebesLayer;
+
+  for (ClippedItemIterator iter(this); !iter.IsDone(); iter.Next()) {
+    nsDisplayItem* item = iter.Item();
+    const gfxRect* clipRect = iter.GetEffectiveClipRect();
+    // Ask the item if it manages its own layer
+    nsRefPtr<Layer> layer = item->BuildLayer(aBuilder, aManager);
+    nsRect bounds = item->GetBounds(aBuilder);
+    // We set layerItems to point to the LayerItems object where the
+    // item ends up.
+    LayerItems* layerItems = nsnull;
+    if (layer) {
+      // item has a dedicated layer. Add it to the list, with an ItemGroup
+      // covering this item only.
+      ItemGroup* itemGroup = new (aBuilder) ItemGroup();
+      if (itemGroup) {
+        AddToItemGroup(aBuilder, itemGroup, item, clipRect);
+        layerItems = aLayers->AppendElement(LayerItems(itemGroup));
+        if (layerItems) {
+          if (itemGroup->mHasClipRect) {
+            gfxRect r = itemGroup->mClipRect;
+            r.Round();
+            nsIntRect intRect(r.X(), r.Y(), r.Width(), r.Height());
+            layer->IntersectClipRect(intRect);
+          }
+          layerItems->mLayer = layer.forget();
+        }
+      }
+      // This item is above the first Thebes layer.
+      areaAboveFirstThebesLayer.Or(areaAboveFirstThebesLayer, bounds);
+      lastThebesLayerItems = nsnull;
+    } else {
+      // No dedicated layer. Add it to a Thebes layer. First try to add
+      // it to the first Thebes layer, which we can do if there's no
+      // content between the first Thebes layer and our display item that
+      // overlaps our display item.
+      if (!areaAboveFirstThebesLayer.Intersects(bounds)) {
+        firstThebesLayerItems =
+          AddToItemGroup(aBuilder, firstThebesLayerItems, item, clipRect);
+        layerItems = &aLayers->ElementAt(0);
+      } else if (lastThebesLayerItems) {
+        // Try to add to the last Thebes layer
+        lastThebesLayerItems =
+          AddToItemGroup(aBuilder, lastThebesLayerItems, item, clipRect);
+        // This item is above the first Thebes layer.
+        areaAboveFirstThebesLayer.Or(areaAboveFirstThebesLayer, bounds);
+        layerItems = &aLayers->ElementAt(aLayers->Length() - 1);
+      } else {
+        // Create a new Thebes layer
+        ItemGroup* itemGroup =
+          CreateEmptyThebesLayer(aBuilder, aManager, aLayers);
+        if (itemGroup) {
+          lastThebesLayerItems =
+            AddToItemGroup(aBuilder, itemGroup, item, clipRect);
+          NS_ASSERTION(lastThebesLayerItems == itemGroup,
+                       "AddToItemGroup shouldn't create a new group if the "
+                       "initial group is empty");
+          // This item is above the first Thebes layer.
+          areaAboveFirstThebesLayer.Or(areaAboveFirstThebesLayer, bounds);
+        }
+      }
+    }
+
+    if (layerItems) {
+      // Update the visible region of the layer to account for the new
+      // item
+      nscoord appUnitsPerDevPixel =
+        item->GetUnderlyingFrame()->PresContext()->AppUnitsPerDevPixel();
+      layerItems->mVisibleRect.UnionRect(layerItems->mVisibleRect,
+        item->mVisibleRect.ToOutsidePixels(appUnitsPerDevPixel));
+      layerItems->mLayer->SetVisibleRegion(nsIntRegion(layerItems->mVisibleRect));
+    }
+  }
+
+  if (!firstThebesLayerItems->mStartItem) {
+    // The first Thebes layer has nothing in it. Delete the layer.
+    aLayers->RemoveElementAt(0);
+  }
+}
+
+/**
+ * We build a single layer by first building a list of layers needed for
+ * all the display items, and then if there's not just one layer in the
+ * list, we build a container layer to hold them.
+ */
+already_AddRefed<Layer>
+nsDisplayList::BuildLayer(nsDisplayListBuilder* aBuilder,
+                          LayerManager* aManager,
+                          nsTArray<LayerItems>* aLayers) const {
+  BuildLayers(aBuilder, aManager, aLayers);
+
+  nsRefPtr<Layer> layer;
+  if (aLayers->Length() == 1) {
+    // We can just return the one layer
+    layer = aLayers->ElementAt(0).mLayer;
+  } else {
+    // We need to group multiple layers together into a container
+    nsRefPtr<ContainerLayer> container =
+      aManager->CreateContainerLayer();
+    if (!container)
+      return nsnull;
+    
+    Layer* lastChild = nsnull;
+    nsIntRect visibleRect;
+    for (PRUint32 i = 0; i < aLayers->Length(); ++i) {
+      LayerItems* layerItems = &aLayers->ElementAt(i);
+      visibleRect.UnionRect(visibleRect, layerItems->mVisibleRect);
+      Layer* child = layerItems->mLayer;
+      container->InsertAfter(child, lastChild);
+      lastChild = child;
+    }
+    container->SetVisibleRegion(nsIntRegion(visibleRect));
+    layer = container.forget();
+  }
+  layer->SetIsOpaqueContent(mIsOpaque);
+  return layer.forget();
+}
+
+/**
+ * We paint by executing a layer manager transaction, constructing a
+ * single layer representing the display list, and then making it the
+ * root of the layer manager, drawing into the ThebesLayers.
+ */
 void nsDisplayList::Paint(nsDisplayListBuilder* aBuilder,
                           nsIRenderingContext* aCtx) const {
   NS_ASSERTION(mDidComputeVisibility,
                "Must call ComputeVisibility before calling Paint");
 
-  for (nsDisplayItem* i = GetBottom(); i != nsnull; i = i->GetAbove()) {
-    i->Paint(aBuilder, aCtx);
+  // To paint a display list, we construct a BasicLayerManager and
+  // a layer tree for it, and ask the BasicLayerManager to render.
+  nsRefPtr<LayerManager> layerManager = new BasicLayerManager();
+  if (!layerManager)
+    return;
+  layerManager->BeginTransactionWithTarget(aCtx->ThebesContext());
+
+  nsAutoTArray<LayerItems,10> layers;
+  nsRefPtr<Layer> root = BuildLayer(aBuilder, layerManager, &layers);
+  if (!root)
+    return;
+
+  layerManager->SetRoot(root);
+  layerManager->EndConstruction();
+  PaintThebesLayers(aBuilder, layers);
+  layerManager->EndTransaction();
+
+  nsCSSRendering::DidPaint();
+}
+
+void
+nsDisplayList::PaintThebesLayers(nsDisplayListBuilder* aBuilder,
+                                 const nsTArray<LayerItems>& aLayers) const
+{
+  for (PRUint32 i = 0; i < aLayers.Length(); ++i) {
+    ThebesLayer* thebesLayer = aLayers[i].mThebesLayer;
+    if (!thebesLayer) {
+      // Just ask the display item to paint any Thebes layers that it
+      // used to construct its layer
+      aLayers[i].mItems->mStartItem->PaintThebesLayers(aBuilder);
+      continue;
+    }
+
+    // OK, we have a real Thebes layer. Start drawing into it.
+    nsIntRegion toDraw;
+    gfxContext* ctx = thebesLayer->BeginDrawing(&toDraw);
+    if (!ctx)
+      continue;
+    // For now, we'll ignore toDraw and just draw the entire visible
+    // area, because the "visible area" is already confined to just the
+    // area that needs to be repainted. Later, when we start reusing layers
+    // from paint to paint, we'll need to pay attention to toDraw and
+    // actually try to avoid drawing stuff that's not in it.
+
+    // Our list may contain content with different prescontexts at
+    // different zoom levels. 'rc' contains the nsIRenderingContext
+    // used for the previous display item, and lastPresContext is the
+    // prescontext for that item. We also cache the clip state for that
+    // item.
+    nsRefPtr<nsIRenderingContext> rc;
+    nsPresContext* lastPresContext = nsnull;
+    gfxRect currentClip;
+    PRBool setClipRect = PR_FALSE;
+    NS_ASSERTION(aLayers[i].mItems, "No empty layers allowed");
+    for (ItemGroup* group = aLayers[i].mItems; group;
+         group = group->mNextItemsForLayer) {
+      // If the new desired clip state is different from the current state,
+      // update the clip.
+      if (setClipRect != group->mHasClipRect ||
+          (group->mHasClipRect && group->mClipRect != currentClip)) {
+        if (setClipRect) {
+          ctx->Restore();
+        }
+        setClipRect = group->mHasClipRect;
+        if (setClipRect) {
+          ctx->Save();
+          ctx->NewPath();
+          ctx->Rectangle(group->mClipRect, PR_TRUE);
+          ctx->Clip();
+          currentClip = group->mClipRect;
+        }
+      }
+      NS_ASSERTION(group->mStartItem, "No empty groups allowed");
+      for (nsDisplayItem* item = group->mStartItem; item != group->mEndItem;
+           item = item->GetAbove()) {
+        nsPresContext* presContext = item->GetUnderlyingFrame()->PresContext();
+        if (presContext != lastPresContext) {
+          // Create a new rendering context with the right
+          // appunits-per-dev-pixel.
+          nsresult rv =
+            presContext->DeviceContext()->CreateRenderingContextInstance(*getter_AddRefs(rc));
+          if (NS_FAILED(rv))
+            break;
+          rc->Init(presContext->DeviceContext(), ctx);
+          lastPresContext = presContext;
+        }
+        item->Paint(aBuilder, rc);
+      }
+    }
+    if (setClipRect) {
+      ctx->Restore();
+    }
+    thebesLayer->EndDrawing();
   }
-  nsCSSRendering::DidPaint();
 }
 
 PRUint32 nsDisplayList::Count() const {
   PRUint32 count = 0;
   for (nsDisplayItem* i = GetBottom(); i; i = i->GetAbove()) {
     ++count;
   }
   return count;
@@ -991,17 +1404,17 @@ PRBool nsDisplayWrapList::IsVaryingRelat
   // this on wrapped lists.
   NS_WARNING("nsDisplayWrapList::IsVaryingRelativeToMovingFrame called unexpectedly");
   // We could try to do something but let's conservatively just return PR_TRUE.
   return PR_TRUE;
 }
 
 void nsDisplayWrapList::Paint(nsDisplayListBuilder* aBuilder,
                               nsIRenderingContext* aCtx) {
-  mList.Paint(aBuilder, aCtx);
+  NS_ERROR("nsDisplayWrapList should have been flattened away for painting");
 }
 
 static nsresult
 WrapDisplayList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                 nsDisplayList* aList, nsDisplayWrapper* aWrapper) {
   if (!aList->GetTop() && !aBuilder->HasMovingFrames())
     return NS_OK;
   nsDisplayItem* item = aWrapper->WrapList(aBuilder, aFrame, aList);
@@ -1066,93 +1479,71 @@ nsresult nsDisplayWrapper::WrapListsInPl
   // The positioned descendants may not be in-flow
   rv = WrapEachDisplayItem(aBuilder, aLists.PositionedDescendants(), this);
   NS_ENSURE_SUCCESS(rv, rv);
   // The outlines may not be in-flow
   return WrapEachDisplayItem(aBuilder, aLists.Outlines(), this);
 }
 
 nsDisplayOpacity::nsDisplayOpacity(nsIFrame* aFrame, nsDisplayList* aList)
-    : nsDisplayWrapList(aFrame, aList), mNeedAlpha(PR_TRUE) {
+    : nsDisplayWrapList(aFrame, aList) {
   MOZ_COUNT_CTOR(nsDisplayOpacity);
 }
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayOpacity::~nsDisplayOpacity() {
   MOZ_COUNT_DTOR(nsDisplayOpacity);
 }
 #endif
 
 PRBool nsDisplayOpacity::IsOpaque(nsDisplayListBuilder* aBuilder) {
   // We are never opaque, if our opacity was < 1 then we wouldn't have
   // been created.
   return PR_FALSE;
 }
 
-void nsDisplayOpacity::Paint(nsDisplayListBuilder* aBuilder,
-                             nsIRenderingContext* aCtx)
-{
-  float opacity = mFrame->GetStyleDisplay()->mOpacity;
-
-  nsCOMPtr<nsIDeviceContext> devCtx;
-  aCtx->GetDeviceContext(*getter_AddRefs(devCtx));
-
-  gfxContext* ctx = aCtx->ThebesContext();
-
-  ctx->Save();
+// nsDisplayOpacity uses layers for rendering
+already_AddRefed<Layer>
+nsDisplayOpacity::BuildLayer(nsDisplayListBuilder* aBuilder,
+                             LayerManager* aManager) {
+  nsRefPtr<Layer> layer =
+    mList.BuildLayer(aBuilder, aManager, &mChildLayers);
+  if (!layer)
+    return nsnull;
 
-  ctx->NewPath();
-  gfxRect r(mVisibleRect.x, mVisibleRect.y,
-            mVisibleRect.width, mVisibleRect.height);
-  r.ScaleInverse(devCtx->AppUnitsPerDevPixel());
-  ctx->Rectangle(r, PR_TRUE);
-  ctx->Clip();
+  layer->SetOpacity(mFrame->GetStyleDisplay()->mOpacity*layer->GetOpacity());
+  return layer.forget();
+}
 
-  if (mNeedAlpha)
-    ctx->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA);
-  else
-    ctx->PushGroup(gfxASurface::CONTENT_COLOR);
-
-  nsDisplayWrapList::Paint(aBuilder, aCtx);
-
-  ctx->PopGroupToSource();
-  ctx->SetOperator(gfxContext::OPERATOR_OVER);
-  ctx->Paint(opacity);
-
-  ctx->Restore();
+void
+nsDisplayOpacity::PaintThebesLayers(nsDisplayListBuilder* aBuilder) {
+  mList.PaintThebesLayers(aBuilder, mChildLayers);
 }
 
 PRBool nsDisplayOpacity::ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                            nsRegion* aVisibleRegion,
                                            nsRegion* aVisibleRegionBeforeMove) {
   NS_ASSERTION((aVisibleRegionBeforeMove != nsnull) == aBuilder->HasMovingFrames(),
                "Should have aVisibleRegionBeforeMove when there are moving frames");
 
   // Our children are translucent so we should not allow them to subtract
   // area from aVisibleRegion. We do need to find out what is visible under
   // our children in the temporary compositing buffer, because if our children
   // paint our entire bounds opaquely then we don't need an alpha channel in
   // the temporary compositing buffer.
-  nsRegion visibleUnderChildren = *aVisibleRegion;
+  nsRect bounds = GetBounds(aBuilder);
+  nsRegion visibleUnderChildren;
+  visibleUnderChildren.And(*aVisibleRegion, bounds);
   nsRegion visibleUnderChildrenBeforeMove;
   if (aVisibleRegionBeforeMove) {
-    visibleUnderChildrenBeforeMove = *aVisibleRegionBeforeMove;
+    visibleUnderChildrenBeforeMove.And(*aVisibleRegionBeforeMove, bounds);
   }
-  PRBool anyVisibleChildren =
+  return
     nsDisplayWrapList::ComputeVisibility(aBuilder, &visibleUnderChildren,
       aVisibleRegionBeforeMove ? &visibleUnderChildrenBeforeMove : nsnull);
-  if (!anyVisibleChildren)
-    return PR_FALSE;
-
-  // If we're analyzing moving content, then it doesn't really matter
-  // what we set mNeedAlpha to, so let's conservatively set it to true so
-  // we don't have to worry about getting it "correct" for the moving case.
-  mNeedAlpha = aVisibleRegionBeforeMove ||
-    visibleUnderChildren.Intersects(mVisibleRect);
-  return PR_TRUE;
 }
 
 PRBool nsDisplayOpacity::TryMerge(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) {
   if (aItem->GetType() != TYPE_OPACITY)
     return PR_FALSE;
   // items for the same content element should be merged into a single
   // compositing group
   // aItem->GetUnderlyingFrame() returns non-null because it's nsDisplayOpacity
@@ -1185,20 +1576,17 @@ nsRect nsDisplayClip::GetBounds(nsDispla
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayClip::~nsDisplayClip() {
   MOZ_COUNT_DTOR(nsDisplayClip);
 }
 #endif
 
 void nsDisplayClip::Paint(nsDisplayListBuilder* aBuilder,
                           nsIRenderingContext* aCtx) {
-  aCtx->PushState();
-  aCtx->SetClipRect(mClip, nsClipCombine_kIntersect);
-  nsDisplayWrapList::Paint(aBuilder, aCtx);
-  aCtx->PopState();
+  NS_ERROR("nsDisplayClip should have been flattened away for painting");
 }
 
 PRBool nsDisplayClip::ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                         nsRegion* aVisibleRegion,
                                         nsRegion* aVisibleRegionBeforeMove) {
   NS_ASSERTION((aVisibleRegionBeforeMove != nsnull) == aBuilder->HasMovingFrames(),
                "Should have aVisibleRegionBeforeMove when there are moving frames");
 
@@ -1436,17 +1824,17 @@ void nsDisplayTransform::Paint(nsDisplay
 
   /* Set the matrix for the transform based on the old matrix and the new
    * transform data.
    */
   gfx->SetMatrix(newTransformMatrix);
 
   /* Now, send the paint call down.
    */    
-  mStoredList.Paint(aBuilder, aCtx);
+  mStoredList.GetList()->Paint(aBuilder, aCtx);
 
   /* The AutoSaveRestore object will clean things up. */
 }
 
 PRBool nsDisplayTransform::ComputeVisibility(nsDisplayListBuilder *aBuilder,
                                              nsRegion *aVisibleRegion,
                                              nsRegion *aVisibleRegionBeforeMove)
 {
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -47,16 +47,17 @@
 
 #include "nsCOMPtr.h"
 #include "nsIFrame.h"
 #include "nsPoint.h"
 #include "nsRect.h"
 #include "nsISelection.h"
 #include "nsCaret.h"
 #include "plarena.h"
+#include "Layers.h"
 
 #include <stdlib.h>
 
 class nsIPresShell;
 class nsIContent;
 class nsRegion;
 class nsIRenderingContext;
 class nsIDeviceContext;
@@ -450,16 +451,19 @@ protected:
  * subclass would just have a frame pointer, so its object would be just three
  * pointers (vtable, next-item, frame).
  * 
  * Display items belong to a list at all times (except temporarily as they
  * move from one list to another).
  */
 class nsDisplayItem : public nsDisplayItemLink {
 public:
+  typedef mozilla::layers::Layer Layer;
+  typedef mozilla::layers::LayerManager LayerManager;
+
   // This is never instantiated directly (it has pure virtual methods), so no
   // need to count constructors and destructors.
   nsDisplayItem(nsIFrame* aFrame) : mFrame(aFrame) {}
   virtual ~nsDisplayItem() {}
   
   void* operator new(size_t aSize,
                      nsDisplayListBuilder* aBuilder) CPP_THROW_NEW {
     return aBuilder->Allocate(aSize);
@@ -543,16 +547,38 @@ public:
    */
   virtual PRBool IsVaryingRelativeToMovingFrame(nsDisplayListBuilder* aBuilder)
   { return PR_FALSE; }
   /**
    * Actually paint this item to some rendering context.
    * Content outside mVisibleRect need not be painted.
    */
   virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx) {}
+  /**
+   * Get the layer drawn by this display item, if any. If this display
+   * item doesn't have its own layer, then Paint will be called on it
+   * later. If it returns a layer here then Paint will not be called.
+   * This is called while aManager is in the construction phase.
+   * This is where content can decide to be rendered by the layer
+   * system (with the possibility of accelerated or off-main-thread
+   * rendering) instead of cairo.
+   * 
+   * The caller (nsDisplayList) is responsible for setting the visible
+   * region of the layer.
+   */
+  virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+                                             LayerManager* aManager)
+  { return nsnull; }
+  /**
+   * If BuildLayer returned non-null, then this method is called to
+   * paint any ThebesLayers which are descendants of the returned layer.
+   */
+  virtual void PaintThebesLayers(nsDisplayListBuilder* aBuilder)
+  {
+  }
 
   /**
    * On entry, aVisibleRegion contains the region (relative to ReferenceFrame())
    * which may be visible. If the display item opaquely covers an area, it
    * can remove that area from aVisibleRegion before returning.
    * If we're doing scroll analysis with moving frames, then
    * aVisibleRegionBeforeMove will be non-null and contains the region that
    * would have been visible before the move. aVisibleRegion contains the
@@ -630,20 +656,26 @@ protected:
  * slow so we don't support it. The methods that need to step downward
  * (HitTest(), ComputeVisibility()) internally build a temporary array of all
  * the items while they do the downward traversal, so overall they're still
  * linear time. We have optimized for efficient AppendToTop() of both
  * items and lists, with minimal codesize. AppendToBottom() is efficient too.
  */
 class nsDisplayList {
 public:
+  typedef mozilla::layers::Layer Layer;
+  typedef mozilla::layers::LayerManager LayerManager;
+  typedef mozilla::layers::ThebesLayer ThebesLayer;
+
   /**
    * Create an empty list.
    */
-  nsDisplayList() {
+  nsDisplayList() :
+    mIsOpaque(PR_FALSE)
+  {
     mTop = &mSentinel;
     mSentinel.mAbove = nsnull;
 #ifdef DEBUG
     mDidComputeVisibility = PR_FALSE;
 #endif
   }
   ~nsDisplayList() {
     if (mSentinel.mAbove) {
@@ -788,24 +820,33 @@ public:
 
   /**
    * Optimize the display list for visibility, removing any elements that
    * are not visible. We put this logic here so it can be shared by top-level
    * painting and also display items that maintain child lists.
    * This is also a good place to put ComputeVisibility-related logic
    * that must be applied to every display item. In particular, this
    * sets mVisibleRect on each display item.
+   * This also sets mIsOpaque to whether aVisibleRegion is empty on return.
    * 
    * @param aVisibleRegion the area that is visible, relative to the
    * reference frame; on return, this contains the area visible under the list
    */
   void ComputeVisibility(nsDisplayListBuilder* aBuilder,
                          nsRegion* aVisibleRegion,
                          nsRegion* aVisibleRegionBeforeMove);
   /**
+   * Returns true if the visible region output from ComputeVisiblity was
+   * empty, i.e. everything visible in this list is opaque.
+   */
+  PRBool IsOpaque() const {
+    NS_ASSERTION(mDidComputeVisibility, "Need to have called ComputeVisibility");
+    return mIsOpaque;
+  }
+  /**
    * Paint the list to the rendering context. We assume that (0,0) in aCtx
    * corresponds to the origin of the reference frame. For best results,
    * aCtx's current transform should make (0,0) pixel-aligned. The
    * rectangle in aDirtyRect is painted, which *must* be contained in the
    * dirty rect used to construct the display list.
    * 
    * ComputeVisibility must be called before Paint.
    */
@@ -816,30 +857,108 @@ public:
   nsRect GetBounds(nsDisplayListBuilder* aBuilder) const;
   /**
    * Find the topmost display item that returns a non-null frame, and return
    * the frame.
    */
   nsIFrame* HitTest(nsDisplayListBuilder* aBuilder, nsPoint aPt,
                     nsDisplayItem::HitTestState* aState) const;
 
+  /**
+   * This class represents a sublist of consecutive items in an nsDisplayList.
+   * The first item in the sublist is mStartItem and the last item
+   * is the item before mEndItem.
+   * 
+   * These sublists are themselves organized into a linked list of all
+   * the ItemGroups associated with a given layer, via mNextItemsForLayer.
+   * This list will have more than one element if the display items in a layer
+   * come from different nsDisplayLists, or if they come from the same
+   * nsDisplayList but they aren't consecutive in that list.
+   * 
+   * These objects are allocated from the nsDisplayListBuilder arena.
+   */
+  struct ItemGroup {
+    // If null, then the item group is empty.
+    nsDisplayItem* mStartItem;
+    nsDisplayItem* mEndItem;
+    ItemGroup* mNextItemsForLayer;
+    // The clipping (if any) that needs to be applied to all these items.
+    gfxRect mClipRect;
+    PRPackedBool mHasClipRect;
+
+    ItemGroup() : mStartItem(nsnull), mEndItem(nsnull),
+      mNextItemsForLayer(nsnull), mHasClipRect(PR_FALSE) {}
+
+    void* operator new(size_t aSize,
+                       nsDisplayListBuilder* aBuilder) CPP_THROW_NEW {
+      return aBuilder->Allocate(aSize);
+    }
+  };
+  /**
+   * This class represents a layer and the display item(s) it
+   * will render. The items are stored in a linked list of ItemGroups.
+   */
+  struct LayerItems {
+    nsRefPtr<Layer> mLayer;
+    // equal to mLayer, or null if mLayer is not a ThebesLayer
+    ThebesLayer* mThebesLayer;
+    ItemGroup* mItems;
+    // The bounds of the visible region for this layer, in device pixels
+    nsIntRect mVisibleRect;
+
+    LayerItems(ItemGroup* aItems) :
+      mThebesLayer(nsnull), mItems(aItems)
+    {
+    }
+  };
+  /**
+   * Compute a list of layers needed to render this display list. The layers
+   * are added to aLayers, which must be empty on entry. This
+   * must be called while aManager is in the construction phase, because
+   * we construct layers belonging to aManager. The layers used to
+   * construct the layer tree (along with the display items associated
+   * with each layer) are returned in aLayers.
+   */
+  void BuildLayers(nsDisplayListBuilder* aBuilder,
+                   LayerManager* aManager,
+                   nsTArray<LayerItems>* aLayers) const;
+  /**
+   * Return a single layer which renders this display list. This
+   * must be called while aManager is in the construction phase, because
+   * we construct layers belonging to aManager. The layers used to
+   * construct the layer tree (along with the display items associated
+   * with each layer) are returned in aLayers.
+   */
+  already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+                                     LayerManager* aManager,
+                                     nsTArray<LayerItems>* aLayers) const;
+  /**
+   * Paint the ThebesLayers in the list of layers.
+   */
+  void PaintThebesLayers(nsDisplayListBuilder* aBuilder,
+                         const nsTArray<LayerItems>& aLayers) const;
+
 private:
   // This class is only used on stack, so we don't have to worry about leaking
   // it.  Don't let us be heap-allocated!
   void* operator new(size_t sz) CPP_THROW_NEW;
   
   // Utility function used to massage the list during ComputeVisibility.
   void FlattenTo(nsTArray<nsDisplayItem*>* aElements);
   // Utility function used to massage the list during sorting, to rewrite
   // any wrapper items with null GetUnderlyingFrame
   void ExplodeAnonymousChildLists(nsDisplayListBuilder* aBuilder);
   
   nsDisplayItemLink  mSentinel;
   nsDisplayItemLink* mTop;
 
+  // This is set to true by ComputeVisibility if the final visible region
+  // is empty (i.e. everything that was visible is covered by some
+  // opaque content in this list).
+  PRPackedBool mIsOpaque;
 #ifdef DEBUG
   PRPackedBool mDidComputeVisibility;
 #endif
 };
 
 /**
  * This is passed as a parameter to nsIFrame::BuildDisplayList. That method
  * will put any generated items onto the appropriate list given here. It's
@@ -1379,30 +1498,27 @@ class nsDisplayOpacity : public nsDispla
 public:
   nsDisplayOpacity(nsIFrame* aFrame, nsDisplayList* aList);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayOpacity();
 #endif
   
   virtual Type GetType() { return TYPE_OPACITY; }
   virtual PRBool IsOpaque(nsDisplayListBuilder* aBuilder);
-  virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx);
+  virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+                                             LayerManager* aManager);
+  virtual void PaintThebesLayers(nsDisplayListBuilder* aBuilder);
   virtual PRBool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                    nsRegion* aVisibleRegion,
                                    nsRegion* aVisibleRegionBeforeMove);  
   virtual PRBool TryMerge(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem);
   NS_DISPLAY_DECL_NAME("Opacity")
 
 private:
-  /**
-   * We set this to PR_FALSE if we can prove that our children cover our bounds
-   * completely and opaquely, therefore no alpha channel is required in the
-   * intermediate surface.
-   */
-  PRPackedBool mNeedAlpha;
+  nsTArray<nsDisplayList::LayerItems> mChildLayers;
 };
 
 /**
  * nsDisplayClip can clip a list of items, but we take a single item
  * initially and then later merge other items into it when we merge
  * adjacent matching nsDisplayClips
  */
 class nsDisplayClip : public nsDisplayWrapList {
--- a/layout/build/Makefile.in
+++ b/layout/build/Makefile.in
@@ -56,17 +56,17 @@ EXPORT_LIBRARY = 1
 IS_COMPONENT	= 1
 MODULE_NAME	= nsLayoutModule
 GRE_MODULE	= 1
 LIBXUL_LIBRARY	= 1
 
 ifndef MOZ_ENABLE_LIBXUL
 EXTRA_DSO_LIBS	= gkgfx
 endif
-EXTRA_DSO_LIBS	+= thebes
+EXTRA_DSO_LIBS	+= thebes layers
 
 
 CPPSRCS		= \
 		nsLayoutModule.cpp \
 		nsContentDLF.cpp \
 		nsLayoutStatics.cpp \
 		$(NULL)
 
--- a/toolkit/library/libxul-config.mk
+++ b/toolkit/library/libxul-config.mk
@@ -315,17 +315,17 @@ endif
 
 ifneq (,$(filter icon,$(MOZ_IMG_DECODERS)))
 ifndef MOZ_ENABLE_GTK2
 DEFINES += -DICON_DECODER
 COMPONENT_LIBS += imgicon
 endif
 endif
 
-STATIC_LIBS += thebes
+STATIC_LIBS += thebes layers
 COMPONENT_LIBS += gkgfxthebes
 
 ifeq (windows,$(MOZ_WIDGET_TOOLKIT))
 COMPONENT_LIBS += gkwidget
 endif
 ifeq (beos,$(MOZ_WIDGET_TOOLKIT))
 COMPONENT_LIBS += widget_beos
 endif