Bug 637852. Part 22: Detect when the contents of a ThebesLayer have shifted by a subpixel amount and repaint the entire layer when that happens. r=tnikkel
authorRobert O'Callahan <robert@ocallahan.org>
Thu, 23 Jun 2011 00:11:28 +1200
changeset 71811 6256bcafbdf27f4b8826f13deb082f76aaf3c14c
parent 71810 3d7fda340878b309996c11c7cc0c94f1ac7b11aa
child 71812 dcfa8de9746a5b091de102f1a417e4ca52fdca22
push id209
push userbzbarsky@mozilla.com
push dateTue, 05 Jul 2011 17:42:16 +0000
treeherdermozilla-aurora@cc6e30cce8af [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstnikkel
bugs637852
milestone7.0a1
Bug 637852. Part 22: Detect when the contents of a ThebesLayer have shifted by a subpixel amount and repaint the entire layer when that happens. r=tnikkel This fixes artifacts when we're scrolling inside scaled content, and includes a test for that (which fails without this code change).
layout/base/FrameLayerBuilder.cpp
layout/reftests/scrolling/reftest.list
layout/reftests/scrolling/scrolling.js
layout/reftests/scrolling/transformed-1.html
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -388,27 +388,40 @@ protected:
   PRUint32                         mNextFreeRecycledImageLayer;
   PRPackedBool                     mInvalidateAllThebesContent;
 };
 
 class ThebesDisplayItemLayerUserData : public LayerUserData
 {
 public:
   ThebesDisplayItemLayerUserData() :
-    mForcedBackgroundColor(NS_RGBA(0,0,0,0)) {}
+    mForcedBackgroundColor(NS_RGBA(0,0,0,0)),
+    mXScale(1.f), mYScale(1.f),
+    mActiveScrolledRootPosition(0, 0) {}
 
   /**
    * A color that should be painted over the bounds of the layer's visible
    * region before any other content is painted.
    */
   nscolor mForcedBackgroundColor;
   /**
    * The resolution scale used.
    */
   float mXScale, mYScale;
+  /**
+   * We try to make 0,0 of the ThebesLayer be the top-left of the
+   * border-box of the "active scrolled root" frame (i.e. the nearest ancestor
+   * frame for the display items that is being actively scrolled). But
+   * we force the ThebesLayer transform to be an integer translation, and we may
+   * have a resolution scale, so we have to snap the ThebesLayer transform, so
+   * 0,0 may not be exactly the top-left of the active scrolled root. Here we
+   * store the coordinates in ThebesLayer space of the top-left of the
+   * active scrolled root.
+   */
+  gfxPoint mActiveScrolledRootPosition;
 };
 
 /**
  * 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,
@@ -849,27 +862,38 @@ ContainerState::CreateOrRecycleThebesLay
   // ThebesLayer to ensure that snapping exactly matches the ideal transform.
   layer->SetAllowResidualTranslation(
       mParameters.mInTransformedSubtree && !mParameters.mInActiveTransformedSubtree);
 
   mBuilder->LayerBuilder()->SaveLastPaintOffset(layer);
 
   // Set up transform so that 0,0 in the Thebes layer corresponds to the
   // (pixel-snapped) top-left of the aActiveScrolledRoot.
-  // XXX if the transform has changed, and the difference between the old and
-  // new offsets (not transforms!) is not an integer number of pixels after
-  // scaling, we need to invalidate the entire layer.
   nsPoint offset = mBuilder->ToReferenceFrame(aActiveScrolledRoot);
-  nsIntPoint pixOffset = offset.ScaleToNearestPixels(
-      mParameters.mXScale, mParameters.mYScale,
-      aActiveScrolledRoot->PresContext()->AppUnitsPerDevPixel());
+  nscoord appUnitsPerDevPixel = aActiveScrolledRoot->PresContext()->AppUnitsPerDevPixel();
+  gfxPoint scaledOffset(
+      NSAppUnitsToDoublePixels(offset.x, appUnitsPerDevPixel)*mParameters.mXScale,
+      NSAppUnitsToDoublePixels(offset.y, appUnitsPerDevPixel)*mParameters.mYScale);
+  nsIntPoint pixOffset(NSToIntRoundUp(scaledOffset.x), NSToIntRoundUp(scaledOffset.y));
   gfxMatrix matrix;
   matrix.Translate(gfxPoint(pixOffset.x, pixOffset.y));
   layer->SetTransform(gfx3DMatrix::From2D(matrix));
 
+  // Calculate exact position of the top-left of the active scrolled root.
+  // This might not be 0,0 due to the snapping in ScaleToNearestPixels.
+  gfxPoint activeScrolledRootTopLeft = scaledOffset - matrix.GetTranslation();
+  // If it has changed, then we need to invalidate the entire layer since the
+  // pixels in the layer buffer have the content at a (subpixel) offset
+  // from what we need.
+  if (activeScrolledRootTopLeft != data->mActiveScrolledRootPosition) {
+    data->mActiveScrolledRootPosition = activeScrolledRootTopLeft;
+    nsIntRect invalidate = layer->GetValidRegion().GetBounds();
+    layer->InvalidateRegion(invalidate);
+  }
+
   return layer.forget();
 }
 
 /**
  * Returns the appunits per dev pixel for the item's frame. The item must
  * have a frame because only nsDisplayClip items don't have a frame,
  * and those items are flattened away by ProcessDisplayItems.
  */
--- a/layout/reftests/scrolling/reftest.list
+++ b/layout/reftests/scrolling/reftest.list
@@ -2,10 +2,12 @@ HTTP == fixed-1.html fixed-1.html?ref
 HTTP == fixed-opacity-1.html fixed-opacity-1.html?ref
 HTTP == fixed-opacity-2.html fixed-opacity-2.html?ref
 HTTP == fixed-text-1.html fixed-text-1.html?ref
 HTTP == fixed-text-2.html fixed-text-2.html?ref
 HTTP == opacity-mixed-scrolling-1.html opacity-mixed-scrolling-1.html?ref
 random-if(cocoaWidget) HTTP == opacity-mixed-scrolling-2.html opacity-mixed-scrolling-2.html?ref # see bug 625357
 HTTP == simple-1.html simple-1.html?ref
 HTTP == text-1.html text-1.html?ref
+HTTP == transformed-1.html transformed-1.html?ref
+HTTP == transformed-1.html?up transformed-1.html?ref
 == uncovering-1.html uncovering-1-ref.html
 == uncovering-2.html uncovering-2-ref.html
--- a/layout/reftests/scrolling/scrolling.js
+++ b/layout/reftests/scrolling/scrolling.js
@@ -19,16 +19,23 @@ function doScroll(d)
           " (Random number: " + Math.random() + ")";
       failed = true;
     }
   }
 }
 
 if (document.location.search == '?ref') {
   doScroll(20);
+} else if (document.location.search == '?up') {
+  doScroll(40);
+  document.documentElement.setAttribute("class", "reftest-wait");
+  window.addEventListener("MozReftestInvalidate", function() {
+    document.documentElement.removeAttribute("class");
+    doScroll(20);
+  }, false);
 } else {
   doScroll(1);
   document.documentElement.setAttribute("class", "reftest-wait");
   window.addEventListener("MozReftestInvalidate", function() {
     document.documentElement.removeAttribute("class");
     doScroll(20);
   }, false);
 }
new file mode 100644
--- /dev/null
+++ b/layout/reftests/scrolling/transformed-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div class="scrollTop" style="height:100px; width:100px; overflow:hidden;
+                              -moz-transform:scale(2.7); transform:scale(2.7); -moz-transform-origin:top left; transform-origin:top left;">
+  <div style="background:yellow;">
+    <div>Hello Kitty</div>
+    <div>Hello Kitty</div>
+    <div>Hello Kitty</div>
+    <div>Hello Kitty</div>
+    <div>Hello Kitty</div>
+    <div>Hello Kitty</div>
+    <div>Hello Kitty</div>
+    <div>Hello Kitty</div>
+    <div>Hello Kitty</div>
+    <div>Hello Kitty</div>
+    <div>Hello Kitty</div>
+    <div>Hello Kitty</div>
+    <div>Hello Kitty</div>
+  </div>
+</div>
+<script>document.body.getBoundingClientRect();</script>
+<script src="scrolling.js"></script>
+</body>
+</html>