Bug 593310: Add initial support for drawing a moving, default background where shadow-layer content is undefined (as efficiently as possible). r=stechz,roc
authorChris Jones <jones.chris.g@gmail.com>
Wed, 26 Jan 2011 00:26:37 -0600
changeset 61313 8d160fb8f2db3f7929f656732c4f479b13b6a420
parent 61312 73bfa3627d0c9cf0e21d3c630760926543285b92
child 61314 bfd44071cce9c3a35c059915959c876ad61d2365
push id18307
push usercjones@mozilla.com
push dateWed, 26 Jan 2011 06:27:01 +0000
treeherdermozilla-central@0f592a972482 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersstechz, roc
bugs593310
milestone2.0b11pre
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 593310: Add initial support for drawing a moving, default background where shadow-layer content is undefined (as efficiently as possible). r=stechz,roc
layout/ipc/RenderFrameParent.cpp
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -95,22 +95,16 @@ static double GetYScale(const gfx3DMatri
 }
 
 static void Scale(gfx3DMatrix& aTransform, double aXScale, double aYScale)
 {
   aTransform._11 *= aXScale;
   aTransform._22 *= aYScale;
 }
 
-static void Translate(gfx3DMatrix& aTransform, nsIntPoint aTranslate)
-{
-  aTransform._41 += aTranslate.x;
-  aTransform._42 += aTranslate.y;
-}
-
 static void ApplyTransform(nsRect& aRect,
                            gfx3DMatrix& aTransform,
                            nscoord auPerDevPixel)
 {
   aRect.x = aRect.x * aTransform._11 + aTransform._41 * auPerDevPixel;
   aRect.y = aRect.y * aTransform._22 + aTransform._42 * auPerDevPixel;
   aRect.width = aRect.width * aTransform._11;
   aRect.height = aRect.height * aTransform._22;
@@ -130,27 +124,16 @@ AssertInTopLevelChromeDoc(ContainerLayer
 // Return view for given ID in aArray, NULL if not found.
 static nsContentView*
 FindViewForId(const ViewMap& aMap, ViewID aId)
 {
   ViewMap::const_iterator iter = aMap.find(aId);
   return iter != aMap.end() ? iter->second : NULL;
 }
 
-static void
-AssertValidContainerOfShadowTree(ContainerLayer* aContainer,
-                                 Layer* aShadowRoot)
-{
-  NS_ABORT_IF_FALSE(
-    !aContainer || (aShadowRoot &&
-                    aShadowRoot == aContainer->GetFirstChild() &&
-                    nsnull == aShadowRoot->GetNextSibling()),
-    "container of shadow tree may only be null or have 1 child that is the shadow root");
-}
-
 static const FrameMetrics*
 GetFrameMetrics(Layer* aLayer)
 {
   // Children are not container layers, so they don't have frame metrics. Give
   // them a blank metric.
   if (!aLayer->GetFirstChild())
     return NULL;
 
@@ -176,17 +159,16 @@ GetRootFrameOffset(nsIFrame* aContainerF
 // subprocess layer manager renders to a different top-left than where
 // the shadow tree is drawn here and because a scale can be set on the
 // shadow tree.
 static ViewTransform
 ComputeShadowTreeTransform(nsIFrame* aContainerFrame,
                            nsFrameLoader* aRootFrameLoader,
                            const FrameMetrics* aMetrics,
                            const ViewConfig& aConfig,
-                           nsDisplayListBuilder* aBuilder,
                            float aInverseScaleX,
                            float aInverseScaleY)
 {
   // |aMetrics->mViewportScrollOffset| The frame's scroll offset when it was
   //                                   painted, in content document pixels.
   // |aConfig.mScrollOffset|           What our user expects, or wants, the
   //                                   frame scroll offset to be in chrome
   //                                   document app units.
@@ -230,17 +212,17 @@ BuildListForLayer(Layer* aLayer,
     // shadow layer tree from the remote process. The metrics viewport is
     // defined based on all the transformations of its parent layers and
     // the scale of the current layer.
 
     // Calculate transform for this layer.
     nsContentView* view =
       aRootFrameLoader->GetCurrentRemoteFrame()->GetContentView(scrollId);
     gfx3DMatrix applyTransform = ComputeShadowTreeTransform(
-      aSubdocFrame, aRootFrameLoader, metrics, view->GetViewConfig(), aBuilder,
+      aSubdocFrame, aRootFrameLoader, metrics, view->GetViewConfig(),
       1 / GetXScale(aTransform), 1 / GetYScale(aTransform));
     transform = applyTransform * aLayer->GetTransform() * aTransform;
 
     // As mentioned above, bounds calculation also depends on the scale
     // of this layer.
     Scale(aTransform, GetXScale(applyTransform), GetYScale(applyTransform));
 
     // Calculate rect for this layer based on aTransform.
@@ -282,17 +264,17 @@ TransformShadowTree(nsDisplayListBuilder
 
   if (metrics && metrics->IsScrollable()) {
     const ViewID scrollId = metrics->mScrollId;
     const nsContentView* view =
       aFrameLoader->GetCurrentRemoteFrame()->GetContentView(scrollId);
     NS_ABORT_IF_FALSE(view, "Array of views should be consistent with layer tree");
 
     ViewTransform viewTransform = ComputeShadowTreeTransform(
-      aFrame, aFrameLoader, metrics, view->GetViewConfig(), aBuilder,
+      aFrame, aFrameLoader, metrics, view->GetViewConfig(),
       1 / aXScale, 1 / aYScale
     );
 
     if (metrics->IsRootScrollable()) {
       viewTransform.mTranslation += GetRootFrameOffset(aFrame, aBuilder);
     }
 
     shadowTransform = gfx3DMatrix(viewTransform) * aLayer->GetTransform();
@@ -306,25 +288,22 @@ TransformShadowTree(nsDisplayListBuilder
   aYScale *= GetYScale(shadowTransform);
 
   for (Layer* child = aLayer->GetFirstChild();
        child; child = child->GetNextSibling()) {
     TransformShadowTree(aBuilder, aFrameLoader, aFrame, child, aXScale, aYScale);
   }
 }
 
-static Layer*
-ShadowRootOf(ContainerLayer* aContainer)
+static void
+ClearContainer(ContainerLayer* aContainer)
 {
-  NS_ABORT_IF_FALSE(aContainer, "need a non-null container");
-
-  Layer* shadowRoot = aContainer->GetFirstChild();
-  NS_ABORT_IF_FALSE(!shadowRoot || nsnull == shadowRoot->GetNextSibling(),
-                    "shadow root container may only have 0 or 1 children");
-  return shadowRoot;
+  while (Layer* layer = aContainer->GetFirstChild()) {
+    aContainer->RemoveChild(layer);
+  }
 }
 
 // Return true iff |aManager| is a "temporary layer manager".  They're
 // used for small software rendering tasks, like drawWindow.  That's
 // currently implemented by a BasicLayerManager without a backing
 // widget, and hence in non-retained mode.
 static PRBool
 IsTempLayerManager(LayerManager* aManager)
@@ -336,18 +315,18 @@ IsTempLayerManager(LayerManager* aManage
 // Recursively create a new array of scrollables, preserving any scrollables
 // that are still in the layer tree.
 //
 // aXScale and aYScale are used to calculate any values that need to be in
 // chrome-document CSS pixels and aren't part of the rendering loop, such as
 // the initial scroll offset for a new view.
 static void
 BuildViewMap(ViewMap& oldContentViews, ViewMap& newContentViews,
-                   nsFrameLoader* aFrameLoader, Layer* aLayer,
-                   float aXScale = 1, float aYScale = 1)
+             nsFrameLoader* aFrameLoader, Layer* aLayer,
+             float aXScale = 1, float aYScale = 1)
 {
   if (!aLayer->GetFirstChild())
     return;
 
   ContainerLayer* container = static_cast<ContainerLayer*>(aLayer);
   const FrameMetrics metrics = container->GetFrameMetrics();
   const ViewID scrollId = metrics.mScrollId;
 
@@ -375,37 +354,155 @@ BuildViewMap(ViewMap& oldContentViews, V
 
     view->mViewportSize = nsSize(
       NSIntPixelsToAppUnits(metrics.mViewport.width, auPerDevPixel) * aXScale,
       NSIntPixelsToAppUnits(metrics.mViewport.height, auPerDevPixel) * aYScale);
     view->mContentSize = nsSize(
       NSIntPixelsToAppUnits(metrics.mContentSize.width, auPerDevPixel) * aXScale,
       NSIntPixelsToAppUnits(metrics.mContentSize.height, auPerDevPixel) * aYScale);
 
-    newContentViews.insert(ViewMap::value_type(scrollId, view));
+    newContentViews[scrollId] = view;
   }
 
   for (Layer* child = aLayer->GetFirstChild();
        child; child = child->GetNextSibling()) {
     const gfx3DMatrix transform = aLayer->GetTransform();
     aXScale *= GetXScale(transform);
     aYScale *= GetYScale(transform);
     BuildViewMap(oldContentViews, newContentViews, aFrameLoader, child,
                  aXScale, aYScale);
   }
 }
 
+already_AddRefed<gfxASurface>
+GetBackgroundImage()
+{
+  // XXX TODO FIXME/bug XXXXXX: this is obviously a hacky placeloader
+  // impl.  Unclear how the background pattern source should be set.
+#define WHT 0xffff
+#define GRY 0xD69A
+#define WLINE8 WHT,WHT,WHT,WHT,WHT,WHT,WHT,WHT
+#define GLINE8 GRY,GRY,GRY,GRY,GRY,GRY,GRY,GRY
+#define WROW16 WLINE8, GLINE8
+#define GROW16 GLINE8, WLINE8
+  static const unsigned short kCheckerboard[] = {
+    WROW16,WROW16,WROW16,WROW16,WROW16,WROW16,WROW16,WROW16,
+    GROW16,GROW16,GROW16,GROW16,GROW16,GROW16,GROW16,GROW16
+  };
+
+  nsRefPtr<gfxASurface> s =
+    new gfxImageSurface((unsigned char*)kCheckerboard,
+                        gfxIntSize(16, 16),
+                        16 * 2,
+                        gfxASurface::ImageFormatRGB16_565);
+  return s.forget();
+}
+
+static void
+BuildBackgroundPatternFor(ContainerLayer* aContainer,
+                          ContainerLayer* aShadowRoot,
+                          const FrameMetrics& aMetrics,
+                          const ViewConfig& aConfig,
+                          LayerManager* aManager,
+                          nsIFrame* aFrame,
+                          nsDisplayListBuilder* aBuilder)
+{
+  // We tile a visible region that is the frame's area \setminus the
+  // rect in our frame onto which valid pixels from remote content
+  // will be drawn.  It's just a waste of CPU cycles to draw a
+  // checkerboard behind that content.
+  //
+  // We want to give the background the illusion of moving while the
+  // user pans, so we nudge the tiling area a bit based on the
+  // "desired" scroll offset.
+  //
+  // The background-image layer is added to the layer tree "behind"
+  // the shadow tree.  It doesn't matter in theory which is behind/in
+  // front, except that having the background in front of content
+  // means we have to be more careful about snapping boundaries,
+  // whereas having it behind allows us to trade off simplicity for
+  // "wasted" drawing of a few extra pixels.
+  ShadowLayer* shadowRoot = aShadowRoot->AsShadowLayer();
+  gfxMatrix t;
+  if (!shadowRoot->GetShadowTransform().Is2D(&t)) {
+    return;
+  }
+
+  // Get the rect bounding the shadow content, transformed into the
+  // same space as |aFrame|
+  nsIntRect contentBounds = shadowRoot->GetShadowVisibleRegion().GetBounds();
+  gfxRect contentVis(contentBounds.x, contentBounds.y,
+                     contentBounds.width, contentBounds.height);
+  gfxRect localContentVis(t.Transform(contentVis));
+  // Round *in* here because this area is punched out of the background
+  localContentVis.RoundIn();
+  nsIntRect localIntContentVis(localContentVis.pos.x, localContentVis.pos.y,
+                               localContentVis.size.width, localContentVis.size.height);
+
+  // Get the frame's rect
+  nscoord auPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+  nsIntRect frameRect = aFrame->GetRect().ToOutsidePixels(auPerDevPixel);
+
+  // If the shadow tree covers the frame rect, don't bother building
+  // the background, it wouldn't be visible
+  if (localIntContentVis.Contains(frameRect)) {
+    return;
+  }
+
+  nsRefPtr<gfxASurface> bgImage = GetBackgroundImage();
+  gfxIntSize bgImageSize = bgImage->GetSize();
+
+  // Set up goop needed to get a cairo image into its own layer
+  nsRefPtr<ImageContainer> c = aManager->CreateImageContainer();
+  const Image::Format fmts[] = { Image::CAIRO_SURFACE };
+  nsRefPtr<Image> img = c->CreateImage(fmts, 1);
+  CairoImage::Data data = { bgImage.get(), bgImageSize };
+  static_cast<CairoImage*>(img.get())->SetData(data);
+  c->SetCurrentImage(img);
+
+  nsRefPtr<ImageLayer> layer = aManager->CreateImageLayer();
+  layer->SetContainer(c);
+
+  // The tile source is the entire background image
+  nsIntRect tileSource(0, 0, bgImageSize.width, bgImageSize.height);
+  layer->SetTileSourceRect(&tileSource);
+
+  // The origin of the tiling plane, top-left of the tile source rect,
+  // is at layer-space point <0,0>.  Set up a translation from that
+  // origin to the frame top-left, with the little nudge included.
+  nsIntPoint translation = frameRect.TopLeft();
+  nsIntPoint panNudge = aConfig.mScrollOffset.ToNearestPixels(auPerDevPixel);
+  // This offset must be positive to ensure that the tiling rect
+  // contains the frame's visible rect.  The "desired" scroll offset
+  // is allowed to be negative, however, so we fix that up here.
+  panNudge.x = (panNudge.x % bgImageSize.width);
+  if (panNudge.x < 0) panNudge.x += bgImageSize.width;
+  panNudge.y = (panNudge.y % bgImageSize.height);
+  if (panNudge.y < 0) panNudge.y += bgImageSize.height;
+
+  translation -= panNudge;
+  layer->SetTransform(gfx3DMatrix::Translation(translation.x, translation.y, 0));
+
+  // The visible area of the background is the frame's area minus the
+  // content area
+  nsIntRegion bgRgn(frameRect);
+  bgRgn.Sub(bgRgn, localIntContentVis);
+  bgRgn.MoveBy(-translation);
+  layer->SetVisibleRegion(bgRgn);
+      
+  aContainer->InsertAfter(layer, nsnull);
+}
+
 RenderFrameParent::RenderFrameParent(nsFrameLoader* aFrameLoader)
   : mFrameLoader(aFrameLoader)
 {
   NS_ABORT_IF_FALSE(aFrameLoader, "Need a frameloader here");
-  mContentViews.insert(ViewMap::value_type(
-    FrameMetrics::ROOT_SCROLL_ID,
-    new nsContentView(aFrameLoader->GetOwnerContent(), FrameMetrics::ROOT_SCROLL_ID)
-  ));
+  mContentViews[FrameMetrics::ROOT_SCROLL_ID] =
+    new nsContentView(aFrameLoader->GetOwnerContent(),
+                      FrameMetrics::ROOT_SCROLL_ID);
 }
 
 RenderFrameParent::~RenderFrameParent()
 {}
 
 void
 RenderFrameParent::Destroy()
 {
@@ -477,50 +574,49 @@ RenderFrameParent::BuildLayer(nsDisplayL
     // widget's layer manager changed out from under us.  We need to
     // FIXME handle the former case somehow, probably with an API to
     // draw a manager's subtree.  The latter is bad bad bad, but the
     // the NS_ABORT_IF_FALSE() above will flag it.  Returning NULL
     // here will just cause the shadow subtree not to be rendered.
     return nsnull;
   }
 
-  Layer* containerShadowRoot = mContainer ? ShadowRootOf(mContainer) : nsnull;
+  if (mContainer) {
+    ClearContainer(mContainer);
+  }
+
   ContainerLayer* shadowRoot = GetRootLayer();
+  if (!shadowRoot) {
+    mContainer = nsnull;
+    return nsnull;
+  }
+
   NS_ABORT_IF_FALSE(!shadowRoot || shadowRoot->Manager() == aManager,
                     "retaining manager changed out from under us ... HELP!");
 
-  if (mContainer && shadowRoot != containerShadowRoot) {
-    // Shadow root changed.  Remove it from the container, if it
-    // existed.
-    if (containerShadowRoot) {
-      mContainer->RemoveChild(containerShadowRoot);
-    }
+  // Wrap the shadow layer tree in mContainer.
+  if (!mContainer) {
+    mContainer = aManager->CreateContainerLayer();
   }
+  NS_ABORT_IF_FALSE(!mContainer->GetFirstChild(),
+                    "container of shadow tree shouldn't have a 'root' here");
+
+  mContainer->InsertAfter(shadowRoot, nsnull);
 
-  if (!shadowRoot) {
-    // No shadow layer tree at the moment.
-    mContainer = nsnull;
-  } else if (shadowRoot != containerShadowRoot) {
-    // Wrap the shadow layer tree in mContainer.
-    if (!mContainer) {
-      mContainer = aManager->CreateContainerLayer();
-    }
-    NS_ABORT_IF_FALSE(!mContainer->GetFirstChild(),
-                      "container of shadow tree shouldn't have a 'root' here");
+  AssertInTopLevelChromeDoc(mContainer, aFrame);
+  TransformShadowTree(aBuilder, mFrameLoader, aFrame, shadowRoot);
+  mContainer->SetClipRect(nsnull);
 
-    mContainer->InsertAfter(shadowRoot, nsnull);
-  }
+  const nsContentView* view = GetContentView(FrameMetrics::ROOT_SCROLL_ID);
+  BuildBackgroundPatternFor(mContainer,
+                            shadowRoot,
+                            shadowRoot->GetFrameMetrics(),
+                            view->GetViewConfig(),
+                            aManager, aFrame, aBuilder);
 
-  if (mContainer) {
-    AssertInTopLevelChromeDoc(mContainer, aFrame);
-    TransformShadowTree(aBuilder, mFrameLoader, aFrame, shadowRoot);
-    mContainer->SetClipRect(nsnull);
-  }
-
-  AssertValidContainerOfShadowTree(mContainer, shadowRoot);
   return nsRefPtr<Layer>(mContainer).forget();
 }
 
 void
 RenderFrameParent::OwnerContentChanged(nsIContent* aContent)
 {
   NS_ABORT_IF_FALSE(mFrameLoader->GetOwnerContent() == aContent,
                     "Don't build new map if owner is same!");
@@ -591,20 +687,18 @@ RenderFrameParent::BuildViewMap()
     mozilla::layout::BuildViewMap(mContentViews, newContentViews, mFrameLoader, GetRootLayer());
   }
 
   // Here, we guarantee that *only* the root view is preserved in
   // case we couldn't build a new view map above. This is important because
   // the content view map should only contain the root view and content
   // views that are present in the layer tree.
   if (newContentViews.empty()) {
-    newContentViews.insert(ViewMap::value_type(
-      FrameMetrics::ROOT_SCROLL_ID,
-      FindViewForId(mContentViews, FrameMetrics::ROOT_SCROLL_ID)
-    ));
+    newContentViews[FrameMetrics::ROOT_SCROLL_ID] =
+      FindViewForId(mContentViews, FrameMetrics::ROOT_SCROLL_ID);
   }
   
   mContentViews = newContentViews;
 }
 
 LayerManager*
 RenderFrameParent::GetLayerManager() const
 {