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 idunknown
push userunknown
push dateunknown
reviewersstechz, roc
bugs593310
milestone2.0b11pre
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
 {