Subpixel rendering: Nested layers with subpixel accumulation paint to wrong position.
authorzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 20 Mar 2014 14:20:22 +0000 (14:20 +0000)
committerzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 20 Mar 2014 14:20:22 +0000 (14:20 +0000)
https://bugs.webkit.org/show_bug.cgi?id=130153

Reviewed by Simon Fraser.

Subpixels (fractional device pixels here) can accumulate through nested layers. Subpixels
need to be propagated through the layer tree so that painting coordinates match layout coordinates.

Subpixel accumulation through nesting (absolute positioning, 2x display):
non-compositing case:
(nested boxes)  (layout pos) (norm.paint pos) (translate accumulation, subpixel accumulation, final paint pos)
div -> top: 1.3px   1.3px     1.5px      1.5px 0.2px -> snapped 0.0px -> 1.5px
 div -> top: 1.3px   2.6px     2.5px      3.0px 0.4px -> snapped 0.5px -> 2.5px
  div -> top: 1.3px   3.9px     4.0px      4.5px 0.6px -> snapped 0.5px -> 4.0px
   div -> top: 1.3px   5.2px     5.0px      6.0px 0.8px -> snapped 1.0px -> 5.0px

compositing case:
(nested boxes)  (layout pos) (norm.paint pos) (device pixel offset + fractional offset, final pos)
div -> top: 1.3px   1.3px      1.5px      1.0px + 0.3px -> snapped -> 1.5px
 div -> top: 1.3px   2.6px      2.5px      2.5px + 0.1px -> snapped -> 2.5px
  div -> top: 1.3px   3.9px      4.0px      3.5px + 0.4px -> snapped -> 4.0px
   div -> top: 1.3px   5.2px      5.0px      5.0px + 0.2px -> snapped -> 5.0px

Source/WebCore:

Tests: compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation.html
       fast/layers/hidpi-nested-layers-with-subpixel-accumulation.html

* rendering/RenderLayer.cpp:
(WebCore::RenderLayer::paintLayerByApplyingTransform):
* rendering/RenderLayerBacking.cpp:
(WebCore::RenderLayerBacking::updateGraphicsLayerGeometry):
* rendering/RenderLayerBacking.h:
(WebCore::RenderLayerBacking::devicePixelFractionFromRenderer):

LayoutTests:

* compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation-expected.html: Added.
* compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation.html: Added.
* fast/layers/hidpi-nested-layers-with-subpixel-accumulation-expected.html: Added.
* fast/layers/hidpi-nested-layers-with-subpixel-accumulation.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@165963 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation-expected.html [new file with mode: 0644]
LayoutTests/compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation.html [new file with mode: 0644]
LayoutTests/fast/layers/hidpi-nested-layers-with-subpixel-accumulation-expected.html [new file with mode: 0644]
LayoutTests/fast/layers/hidpi-nested-layers-with-subpixel-accumulation.html [new file with mode: 0644]
LayoutTests/platform/mac-wk2/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/rendering/RenderLayer.cpp
Source/WebCore/rendering/RenderLayerBacking.cpp
Source/WebCore/rendering/RenderLayerBacking.h

index ed8c934..892e4e7 100644 (file)
@@ -1,3 +1,33 @@
+2014-03-20  Zalan Bujtas  <zalan@apple.com>
+
+        Subpixel rendering: Nested layers with subpixel accumulation paint to wrong position.
+        https://bugs.webkit.org/show_bug.cgi?id=130153
+
+        Reviewed by Simon Fraser.
+
+        Subpixels (fractional device pixels here) can accumulate through nested layers. Subpixels
+        need to be propagated through the layer tree so that painting coordinates match layout coordinates.
+
+        Subpixel accumulation through nesting (absolute positioning, 2x display):
+        non-compositing case:
+        (nested boxes)  (layout pos) (norm.paint pos) (translate accumulation, subpixel accumulation, final paint pos)
+        div -> top: 1.3px   1.3px     1.5px      1.5px 0.2px -> snapped 0.0px -> 1.5px
+         div -> top: 1.3px   2.6px     2.5px      3.0px 0.4px -> snapped 0.5px -> 2.5px
+          div -> top: 1.3px   3.9px     4.0px      4.5px 0.6px -> snapped 0.5px -> 4.0px
+           div -> top: 1.3px   5.2px     5.0px      6.0px 0.8px -> snapped 1.0px -> 5.0px
+
+        compositing case:
+        (nested boxes)  (layout pos) (norm.paint pos) (device pixel offset + fractional offset, final pos)
+        div -> top: 1.3px   1.3px      1.5px      1.0px + 0.3px -> snapped -> 1.5px
+         div -> top: 1.3px   2.6px      2.5px      2.5px + 0.1px -> snapped -> 2.5px
+          div -> top: 1.3px   3.9px      4.0px      3.5px + 0.4px -> snapped -> 4.0px
+           div -> top: 1.3px   5.2px      5.0px      5.0px + 0.2px -> snapped -> 5.0px
+
+        * compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation-expected.html: Added.
+        * compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation.html: Added.
+        * fast/layers/hidpi-nested-layers-with-subpixel-accumulation-expected.html: Added.
+        * fast/layers/hidpi-nested-layers-with-subpixel-accumulation.html: Added.
+
 2014-03-20  Krzysztof Czech  <k.czech@samsung.com>
 
         Unreviewed EFL gardening
diff --git a/LayoutTests/compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation-expected.html b/LayoutTests/compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation-expected.html
new file mode 100644 (file)
index 0000000..51cccfb
--- /dev/null
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>This tests that nested compositing layers with subpixel accumulation paint to the same position as regular, non-compositing content.</title>
+<style>
+  div {
+    width: 50px;
+    height: 50px;
+    left: 0px;
+    border: 1px solid;
+    position: absolute;
+  }
+</style>
+</head>
+<body>
+<p id="container"></p>
+<script>
+  left = 0;
+  for (i = 0; i < 6; ++i, left+=55.3) {
+    var container = document.getElementById("container");
+    dimension = 50;
+    for (j = 0; j < 10; ++j, dimension+=-4) {
+      var e = document.createElement("div");
+      e.style.top = "1.3px";
+      e.style.width = dimension + "px";
+      e.style.height = dimension + "px";
+      if (j == 0)
+        e.style.left = left + "px";
+      color = (j * 20);
+      e.style.borderColor = "rgb(" + color + ", " +  color + ", " +  color + ")";
+      container.appendChild(e);
+      container = e;
+    }
+  }
+</script>
+</body>
+</html>
diff --git a/LayoutTests/compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation.html b/LayoutTests/compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation.html
new file mode 100644 (file)
index 0000000..434d081
--- /dev/null
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>This tests that nested compositing layers with subpixel accumulation paint to the same position as regular, non-compositing content.</title>
+<style>
+  div {
+    width: 50px;
+    height: 50px;
+    left: 0px;
+    border: 1px solid;
+    position: absolute;
+    -webkit-transform: translateZ(0);
+  }
+</style>
+</head>
+<body>
+<p id="container"></p>
+<script>
+  left = 0;
+  for (i = 0; i < 6; ++i, left+=55.3) {
+    var container = document.getElementById("container");
+    dimension = 50;
+    for (j = 0; j < 10; ++j, dimension+=-4) {
+      var e = document.createElement("div");
+      e.style.top = "1.3px";
+      e.style.width = dimension + "px";
+      e.style.height = dimension + "px";
+      if (j == 0)
+        e.style.left = left + "px";
+      color = (j * 20);
+      e.style.borderColor = "rgb(" + color + ", " +  color + ", " +  color + ")";
+      container.appendChild(e);
+      container = e;
+    }
+  }
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/layers/hidpi-nested-layers-with-subpixel-accumulation-expected.html b/LayoutTests/fast/layers/hidpi-nested-layers-with-subpixel-accumulation-expected.html
new file mode 100644 (file)
index 0000000..09cbf5b
--- /dev/null
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>This tests that nested non-compositing layers with subpixel accumulation paint to the same position as regular content.</title>
+<style>
+  div {
+    width: 50px;
+    height: 50px;
+    left: 0px;
+    position: absolute;
+    border: 1px solid;
+  }
+</style>
+</head>
+<body>
+<p id="container"></p>
+<script>
+  left = 0;
+  for (i = 0; i < 6; ++i, left+=55.3) {
+    var container = document.getElementById("container");
+    dimension = 50;
+    for (j = 0; j < 10; ++j, dimension+=-4) {
+      var e = document.createElement("div");
+      e.style.top = "1.3px";
+      e.style.width = dimension + "px";
+      e.style.height = dimension + "px";
+      if (j == 0)
+        e.style.left = left + "px";
+      color = (j * 20);
+      e.style.borderColor = "rgb(" + color + ", " +  color + ", " +  color + ")";
+      container.appendChild(e);
+      container = e;
+    }
+  }
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/layers/hidpi-nested-layers-with-subpixel-accumulation.html b/LayoutTests/fast/layers/hidpi-nested-layers-with-subpixel-accumulation.html
new file mode 100644 (file)
index 0000000..23985ef
--- /dev/null
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>This tests that nested non-compositing layers with subpixel accumulation paint to the same position as regular content.</title>
+<style>
+  div {
+    width: 50px;
+    height: 50px;
+    left: 0px;
+    position: absolute;
+    -webkit-transform: rotate(0deg);
+    border: 1px solid;
+  }
+</style>
+</head>
+<body>
+<p id="container"></p>
+<script>
+  left = 0;
+  for (i = 0; i < 6; ++i, left+=55.3) {
+    var container = document.getElementById("container");
+    dimension = 50;
+    for (j = 0; j < 10; ++j, dimension+=-4) {
+      var e = document.createElement("div");
+      e.style.top = "1.3px";
+      e.style.width = dimension + "px";
+      e.style.height = dimension + "px";
+      if (j == 0)
+        e.style.left = left + "px";
+      color = (j * 20);
+      e.style.borderColor = "rgb(" + color + ", " +  color + ", " +  color + ")";
+      container.appendChild(e);
+      container = e;
+    }
+  }
+</script>
+</body>
+</html>
index 41aaefc..b78fc16 100644 (file)
@@ -318,6 +318,7 @@ webkit.org/b/118925 svg/repaint/buffered-rendering-static-image.html  [ Pass Ima
 # hidpi subpixel WebKitTestRunner failure. -blurry generated images.
 webkit.org/b/130175 [ MountainLion Debug ] compositing/hidpi-simple-container-layer-on-device-pixel.html [ ImageOnlyFailure ]
 webkit.org/b/130175 [ MountainLion Debug ] compositing/hidpi-transform-with-render-layer-on-fractional-pixel-value.html [ ImageOnlyFailure ]
+webkit.org/b/130175 [ MountainLion Debug ] compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation.html [ ImageOnlyFailure ]
 
 # Only failing on WK1
 webkit.org/b/104104 fast/overflow/scrollbar-click-retains-focus.html [ Pass ]
index 26bd798..3081d12 100644 (file)
@@ -1,3 +1,38 @@
+2014-03-20  Zalan Bujtas  <zalan@apple.com>
+
+        Subpixel rendering: Nested layers with subpixel accumulation paint to wrong position.
+        https://bugs.webkit.org/show_bug.cgi?id=130153
+
+        Reviewed by Simon Fraser.
+
+        Subpixels (fractional device pixels here) can accumulate through nested layers. Subpixels
+        need to be propagated through the layer tree so that painting coordinates match layout coordinates.
+
+        Subpixel accumulation through nesting (absolute positioning, 2x display):
+        non-compositing case:
+        (nested boxes)  (layout pos) (norm.paint pos) (translate accumulation, subpixel accumulation, final paint pos)
+        div -> top: 1.3px   1.3px     1.5px      1.5px 0.2px -> snapped 0.0px -> 1.5px
+         div -> top: 1.3px   2.6px     2.5px      3.0px 0.4px -> snapped 0.5px -> 2.5px
+          div -> top: 1.3px   3.9px     4.0px      4.5px 0.6px -> snapped 0.5px -> 4.0px
+           div -> top: 1.3px   5.2px     5.0px      6.0px 0.8px -> snapped 1.0px -> 5.0px
+
+        compositing case:
+        (nested boxes)  (layout pos) (norm.paint pos) (device pixel offset + fractional offset, final pos)
+        div -> top: 1.3px   1.3px      1.5px      1.0px + 0.3px -> snapped -> 1.5px
+         div -> top: 1.3px   2.6px      2.5px      2.5px + 0.1px -> snapped -> 2.5px
+          div -> top: 1.3px   3.9px      4.0px      3.5px + 0.4px -> snapped -> 4.0px
+           div -> top: 1.3px   5.2px      5.0px      5.0px + 0.2px -> snapped -> 5.0px
+
+        Tests: compositing/hidpi-nested-compositing-layers-with-subpixel-accumulation.html
+               fast/layers/hidpi-nested-layers-with-subpixel-accumulation.html
+
+        * rendering/RenderLayer.cpp:
+        (WebCore::RenderLayer::paintLayerByApplyingTransform):
+        * rendering/RenderLayerBacking.cpp:
+        (WebCore::RenderLayerBacking::updateGraphicsLayerGeometry):
+        * rendering/RenderLayerBacking.h:
+        (WebCore::RenderLayerBacking::devicePixelFractionFromRenderer):
+
 2014-03-20  Thiago de Barros Lacerda  <thiago.lacerda@openbossa.org>
 
         [EFL][GTK] Get CMake to find Freetype2 properly
index da54d8c..ad65830 100644 (file)
@@ -4167,14 +4167,18 @@ void RenderLayer::paintLayerByApplyingTransform(GraphicsContext* context, const
     // This involves subtracting out the position of the layer in our current coordinate space, but preserving
     // the accumulated error for sub-pixel layout.
     float deviceScaleFactor = renderer().document().deviceScaleFactor();
-    LayoutPoint delta;
-    convertToLayerCoords(paintingInfo.rootLayer, delta);
-    delta.moveBy(translationOffset);
+    LayoutPoint offsetFromParent;
+    convertToLayerCoords(paintingInfo.rootLayer, offsetFromParent);
+    offsetFromParent.moveBy(translationOffset);
     TransformationMatrix transform(renderableTransform(paintingInfo.paintBehavior));
-    FloatPoint roundedDelta = roundedForPainting(delta, deviceScaleFactor);
-    transform.translateRight(roundedDelta.x(), roundedDelta.y());
-    LayoutSize adjustedSubPixelAccumulation = paintingInfo.subPixelAccumulation + (delta - LayoutPoint(roundedDelta));
-
+    FloatPoint devicePixelSnappeddOffsetFromParent = roundedForPainting(offsetFromParent, deviceScaleFactor);
+    // Translate the graphics context to the snapping position to avoid off-device-pixel positing.
+    transform.translateRight(devicePixelSnappeddOffsetFromParent.x(), devicePixelSnappeddOffsetFromParent.y());
+    // We handle accumulated subpixels through nested layers here. Since the context gets translated to device pixels,
+    // all we need to do is add the delta to the accumulated pixels coming from ancestor layers. With deep nesting of subpixel positioned
+    // boxes, this could grow to a relatively large number, but the translateRight() balances it.
+    FloatSize delta = offsetFromParent - devicePixelSnappeddOffsetFromParent;
+    LayoutSize adjustedSubPixelAccumulation = paintingInfo.subPixelAccumulation + LayoutSize(delta);
     // Apply the transform.
     GraphicsContextStateSaver stateSaver(*context);
     context->concatCTM(transform.toAffineTransform());
index c8c5036..fa05ab5 100644 (file)
@@ -739,6 +739,8 @@ void RenderLayerBacking::updateGraphicsLayerGeometry()
 
     LayoutPoint offsetFromParent;
     m_owningLayer.convertToLayerCoords(compAncestor, offsetFromParent, RenderLayer::AdjustForColumns);
+    // Device pixel fractions get accumulated through ancestor layers. Our painting offset is layout offset + parent's painting offset.
+    offsetFromParent = offsetFromParent + (compAncestor ? compAncestor->backing()->devicePixelFractionFromRenderer() : LayoutSize());
     relativeCompositingBounds.moveBy(offsetFromParent);
 
     LayoutRect enclosingRelativeCompositingBounds = LayoutRect(enclosingRectForPainting(relativeCompositingBounds, deviceScaleFactor));
index 7824f9b..25ce7fc 100644 (file)
@@ -215,6 +215,8 @@ public:
     void setBlendMode(BlendMode);
 #endif
 
+    LayoutSize devicePixelFractionFromRenderer() const { return m_devicePixelFractionFromRenderer; }
+
 private:
     FloatRect backgroundBoxForPainting() const;