Bug 654950. Fix scissor rect calculations for descendants of container layers with intermediate surfaces. r=bas
authorRobert O'Callahan <robert@ocallahan.org>
Mon, 23 May 2011 12:27:03 +1200
changeset 69990 ed0850a1bbb7a7f8a93f66f253a4a25b76eb28b6
parent 69989 3cb82b65c736a0b198136834864f4a0b29ebc536
child 69991 f147a447fff541ba85386407b9fafa8bec373786
push id20150
push usermbrubeck@mozilla.com
push dateMon, 23 May 2011 15:32:42 +0000
treeherdermozilla-central@b90cbc558b36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbas
bugs654950
milestone6.0a1
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 654950. Fix scissor rect calculations for descendants of container layers with intermediate surfaces. r=bas
gfx/layers/Layers.cpp
gfx/layers/Layers.h
gfx/layers/d3d10/ContainerLayerD3D10.cpp
gfx/layers/d3d9/ContainerLayerD3D9.cpp
gfx/layers/opengl/ContainerLayerOGL.cpp
layout/reftests/bugs/654950-1-ref.html
layout/reftests/bugs/654950-1.html
layout/reftests/bugs/reftest.list
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -40,16 +40,17 @@
 
 #include "mozilla/layers/ShadowLayers.h"
 
 #include "ImageLayers.h"
 #include "Layers.h"
 #include "gfxPlatform.h"
 #include "ReadbackLayer.h"
 #include "gfxUtils.h"
+#include "mozilla/Util.h"
 
 using namespace mozilla::layers;
 
 typedef FrameMetrics::ViewID ViewID;
 const ViewID FrameMetrics::NULL_SCROLL_ID = 0;
 const ViewID FrameMetrics::ROOT_SCROLL_ID = 1;
 const ViewID FrameMetrics::START_SCROLL_ID = 2;
 
@@ -299,54 +300,65 @@ Layer::SnapTransform(const gfx3DMatrix& 
     }
   } else {
     result = aTransform;
   }
   return result;
 }
 
 nsIntRect 
-Layer::CalculateScissorRect(bool aIntermediate,
-                            const nsIntRect& aVisibleRect,
-                            const nsIntRect& aParentScissor,
-                            const gfxMatrix& aTransform)
+Layer::CalculateScissorRect(const nsIntRect& aCurrentScissorRect,
+                            const gfxMatrix* aWorldTransform)
 {
-  nsIntRect scissorRect(aVisibleRect);
+  ContainerLayer* container = GetParent();
+  NS_ASSERTION(container, "This can't be called on the root!");
+
+  // Establish initial clip rect: it's either the one passed in, or
+  // if the parent has an intermediate surface, it's the extents of that surface.
+  nsIntRect currentClip;
+  if (container->UseIntermediateSurface()) {
+    currentClip.SizeTo(container->GetIntermediateSurfaceRect().Size());
+  } else {
+    currentClip = aCurrentScissorRect;
+  }
 
   const nsIntRect *clipRect = GetEffectiveClipRect();
+  if (!clipRect)
+    return currentClip;
 
-  if (!aIntermediate && !clipRect) {
-    return aParentScissor;
+  if (clipRect->IsEmpty()) {
+    // We might have a non-translation transform in the container so we can't
+    // use the code path below.
+    return nsIntRect(currentClip.TopLeft(), nsIntSize(0, 0));
   }
 
-  if (clipRect) {
-    if (clipRect->IsEmpty()) {
-      return *clipRect;
-    }
-    scissorRect = *clipRect;
-    if (!aIntermediate) {
-      gfxRect r(scissorRect.x, scissorRect.y, scissorRect.width, scissorRect.height);
-      gfxRect trScissor = aTransform.TransformBounds(r);
-      trScissor.Round();
-      if (!gfxUtils::GfxRectToIntRect(trScissor, &scissorRect)) {
-        scissorRect = aVisibleRect;
-      }
-    }
+  nsIntRect scissor = *clipRect;
+  if (!container->UseIntermediateSurface()) {
+    gfxMatrix matrix;
+    DebugOnly<bool> is2D = container->GetEffectiveTransform().Is2D(&matrix);
+    // See DefaultComputeEffectiveTransforms below
+    NS_ASSERTION(is2D && !matrix.HasNonIntegerTranslation(),
+                 "Non-integer-translation transform with clipped child should have forced intermediate surface");
+    scissor.MoveBy(nsIntPoint(PRInt32(matrix.x0), PRInt32(matrix.y0)));
+
+    // Find the nearest ancestor with an intermediate surface
+    do {
+      container = container->GetParent();
+    } while (container && !container->UseIntermediateSurface());
   }
-    
-  if (aIntermediate) {
-    scissorRect.MoveBy(- aVisibleRect.TopLeft());
-  } else if (clipRect) {
-    scissorRect.IntersectRect(scissorRect, aParentScissor);
+  if (container) {
+    scissor.MoveBy(-container->GetIntermediateSurfaceRect().TopLeft());
+  } else if (aWorldTransform) {
+    gfxRect r(scissor.x, scissor.y, scissor.width, scissor.height);
+    gfxRect trScissor = aWorldTransform->TransformBounds(r);
+    trScissor.Round();
+    if (!gfxUtils::GfxRectToIntRect(trScissor, &scissor))
+      return nsIntRect(currentClip.TopLeft(), nsIntSize(0, 0));
   }
-
-  NS_ASSERTION(scissorRect.x >= 0 && scissorRect.y >= 0,
-               "Attempting to scissor out of bounds!");
-
-  return scissorRect;
+  return currentClip.Intersect(scissor);
 }
 
 const gfx3DMatrix&
 Layer::GetLocalTransform()
 {
   if (ShadowLayer* shadow = AsShadowLayer())
     return shadow->GetShadowTransform();
   return mTransform;
@@ -391,22 +403,22 @@ ContainerLayer::DefaultComputeEffectiveT
   PRBool useIntermediateSurface;
   float opacity = GetEffectiveOpacity();
   if (opacity != 1.0f && HasMultipleChildren()) {
     useIntermediateSurface = PR_TRUE;
   } else {
     useIntermediateSurface = PR_FALSE;
     gfxMatrix contTransform;
     if (!mEffectiveTransform.Is2D(&contTransform) ||
-        !contTransform.PreservesAxisAlignedRectangles()) {
+        contTransform.HasNonIntegerTranslation()) {
       for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) {
         const nsIntRect *clipRect = child->GetEffectiveClipRect();
         /* We can't (easily) forward our transform to children with a non-empty clip
-         * rect since it would need to be adjusted for the transform.
-         * TODO: This is easily solvable for translation/scaling transforms.
+         * rect since it would need to be adjusted for the transform. See
+         * the calculations performed by CalculateScissorRect above.
          */
         if (clipRect && !clipRect->IsEmpty() && !child->GetVisibleRegion().IsEmpty()) {
           useIntermediateSurface = PR_TRUE;
           break;
         }
       }
     }
   }
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -804,27 +804,26 @@ public:
    * 
    * We promise that when this is called on a layer, all ancestor layers
    * have already had ComputeEffectiveTransforms called.
    */
   virtual void ComputeEffectiveTransforms(const gfx3DMatrix& aTransformToSurface) = 0;
   
   /**
    * Calculate the scissor rect required when rendering this layer.
-   *
-   * @param aIntermediate true if the layer is being rendered to an
-   * intermediate surface, false otherwise.
-   * @param aVisibleRect The bounds of the parent's visible region.
-   * @param aParentScissor The existing scissor rect set for the parent.
-   * @param aTransform The current 2d transform of the parent.
+   * Returns a rectangle relative to the intermediate surface belonging to the
+   * nearest ancestor that has an intermediate surface, or relative to the root
+   * viewport if no ancestor has an intermediate surface, corresponding to the
+   * clip rect for this layer intersected with aCurrentScissorRect.
+   * If no ancestor has an intermediate surface, the clip rect is transformed
+   * by aWorldTransform before being combined with aCurrentScissorRect, if
+   * aWorldTransform is non-null.
    */
-  nsIntRect CalculateScissorRect(bool aIntermediate,
-                                 const nsIntRect& aVisibleRect,
-                                 const nsIntRect& aParentScissor,
-                                 const gfxMatrix& aTransform);
+  nsIntRect CalculateScissorRect(const nsIntRect& aCurrentScissorRect,
+                                 const gfxMatrix* aWorldTransform);
 
   virtual const char* Name() const =0;
   virtual LayerType GetType() const =0;
 
   /**
    * Only the implementation should call this. This is per-implementation
    * private data. Normally, all layers with a given layer manager
    * use the same type of ImplData.
@@ -1055,16 +1054,26 @@ public:
    * Call this only after ComputeEffectiveTransforms has been invoked
    * on this layer.
    * Returns true if this will use an intermediate surface. This is largely
    * backend-dependent, but it affects the operation of GetEffectiveOpacity().
    */
   PRBool UseIntermediateSurface() { return mUseIntermediateSurface; }
 
   /**
+   * Returns the rectangle covered by the intermediate surface,
+   * in this layer's coordinate system
+   */
+  nsIntRect GetIntermediateSurfaceRect()
+  {
+    NS_ASSERTION(mUseIntermediateSurface, "Must have intermediate surface");
+    return mVisibleRegion.GetBounds();
+  }
+
+  /**
    * Returns true if this container has more than one non-empty child
    */
   PRBool HasMultipleChildren();
 
   /**
    * Returns true if this container supports children with component alpha.
    * Should only be called while painting a child of this layer.
    */
--- a/gfx/layers/d3d10/ContainerLayerD3D10.cpp
+++ b/gfx/layers/d3d10/ContainerLayerD3D10.cpp
@@ -183,17 +183,16 @@ ContainerLayerD3D10::RenderLayer()
   nsRefPtr<ID3D10RenderTargetView> previousRTView;
   nsRefPtr<ID3D10Texture2D> renderTexture;
   nsRefPtr<ID3D10RenderTargetView> rtView;
   float previousRenderTargetOffset[2];
   nsIntSize previousViewportSize;
 
   gfx3DMatrix oldViewMatrix;
 
-  gfxMatrix contTransform;
   if (useIntermediate) {
     device()->OMGetRenderTargets(1, getter_AddRefs(previousRTView), NULL);
  
     D3D10_TEXTURE2D_DESC desc;
     memset(&desc, 0, sizeof(D3D10_TEXTURE2D_DESC));
     desc.ArraySize = 1;
     desc.MipLevels = 1;
     desc.Width = visibleRect.width;
@@ -248,19 +247,16 @@ ContainerLayerD3D10::RenderLayer()
 
     renderTargetOffset[0] = (float)visibleRect.x;
     renderTargetOffset[1] = (float)visibleRect.y;
     effect()->GetVariableByName("vRenderTargetOffset")->
       SetRawValue(renderTargetOffset, 0, 8);
 
     previousViewportSize = mD3DManager->GetViewport();
     mD3DManager->SetViewport(nsIntSize(visibleRect.Size()));
-  } else {
-    PRBool is2d = GetEffectiveTransform().Is2D(&contTransform);
-    NS_ASSERTION(is2d, "Transform must be 2D");
   }
     
   D3D10_RECT oldD3D10Scissor;
   UINT numRects = 1;
   device()->RSGetScissorRects(&numRects, &oldD3D10Scissor);
   // Convert scissor to an nsIntRect. D3D10_RECT's are exclusive
   // on the bottom and right values.
   nsIntRect oldScissor(oldD3D10Scissor.left,
@@ -275,35 +271,31 @@ ContainerLayerD3D10::RenderLayer()
        layerToRender != nsnull;
        layerToRender = GetNextSiblingD3D10(layerToRender)) {
 
     if (layerToRender->GetLayer()->GetEffectiveVisibleRegion().IsEmpty()) {
       continue;
     }
     
     nsIntRect scissorRect =
-      layerToRender->GetLayer()->CalculateScissorRect(useIntermediate,
-                                                      visibleRect,
-                                                      oldScissor,
-                                                      contTransform);
-
+        layerToRender->GetLayer()->CalculateScissorRect(oldScissor, nsnull);
     if (scissorRect.IsEmpty()) {
       continue;
     }
 
     D3D10_RECT d3drect;
     d3drect.left = scissorRect.x;
     d3drect.top = scissorRect.y;
     d3drect.right = scissorRect.x + scissorRect.width;
     d3drect.bottom = scissorRect.y + scissorRect.height;
     device()->RSSetScissorRects(1, &d3drect);
 
     layerToRender->RenderLayer();
   }
-      
+
   device()->RSSetScissorRects(1, &oldD3D10Scissor);
 
   if (useIntermediate) {
     mD3DManager->SetViewport(previousViewportSize);
     ID3D10RenderTargetView *rtView = previousRTView;
     device()->OMSetRenderTargets(1, &rtView, NULL);
     effect()->GetVariableByName("vRenderTargetOffset")->
       SetRawValue(previousRenderTargetOffset, 0, 8);
--- a/gfx/layers/d3d9/ContainerLayerD3D9.cpp
+++ b/gfx/layers/d3d9/ContainerLayerD3D9.cpp
@@ -189,17 +189,16 @@ ContainerLayerD3D9::RenderLayer()
 
   ReadbackProcessor readback;
   readback.BuildUpdates(this);
 
   nsIntRect visibleRect = mVisibleRegion.GetBounds();
   PRBool useIntermediate = UseIntermediateSurface();
 
   mSupportsComponentAlphaChildren = PR_FALSE;
-  gfxMatrix contTransform;
   if (useIntermediate) {
     device()->GetRenderTarget(0, getter_AddRefs(previousRenderTarget));
     device()->CreateTexture(visibleRect.width, visibleRect.height, 1,
                             D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8,
                             D3DPOOL_DEFAULT, getter_AddRefs(renderTexture),
                             NULL);
     nsRefPtr<IDirect3DSurface9> renderSurface;
     renderTexture->GetSurfaceLevel(0, getter_AddRefs(renderSurface));
@@ -247,43 +246,33 @@ ContainerLayerD3D9::RenderLayer()
     viewMatrix._11 = 2.0f / visibleRect.width;
     viewMatrix._22 = -2.0f / visibleRect.height;
     viewMatrix._41 = -1.0f;
     viewMatrix._42 = 1.0f;
 
     device()->GetVertexShaderConstantF(CBmProjection, &oldViewMatrix[0][0], 4);
     device()->SetVertexShaderConstantF(CBmProjection, &viewMatrix._11, 4);
   } else {
-#ifdef DEBUG
-    PRBool is2d =
-#endif
-    GetEffectiveTransform().Is2D(&contTransform);
-    NS_ASSERTION(is2d, "Transform must be 2D");
     mSupportsComponentAlphaChildren = (GetContentFlags() & CONTENT_OPAQUE) ||
         (mParent && mParent->SupportsComponentAlphaChildren());
   }
 
   /*
    * Render this container's contents.
    */
   for (LayerD3D9* layerToRender = GetFirstChildD3D9();
        layerToRender != nsnull;
        layerToRender = GetNextSiblingD3D9(layerToRender)) {
 
     if (layerToRender->GetLayer()->GetEffectiveVisibleRegion().IsEmpty()) {
       continue;
     }
     
     nsIntRect scissorRect =
-      layerToRender->GetLayer()->CalculateScissorRect(useIntermediate,
-                                                      visibleRect,
-                                                      oldScissor,
-                                                      contTransform);
-
-
+      layerToRender->GetLayer()->CalculateScissorRect(oldScissor, nsnull);
     if (scissorRect.IsEmpty()) {
       continue;
     }
 
     RECT d3drect;
     d3drect.left = scissorRect.x;
     d3drect.top = scissorRect.y;
     d3drect.right = scissorRect.x + scissorRect.width;
--- a/gfx/layers/opengl/ContainerLayerOGL.cpp
+++ b/gfx/layers/opengl/ContainerLayerOGL.cpp
@@ -163,18 +163,16 @@ ContainerRender(Container* aContainer,
    * Setup our temporary texture for rendering the contents of this container.
    */
   GLuint containerSurface;
   GLuint frameBuffer;
 
   nsIntPoint childOffset(aOffset);
   nsIntRect visibleRect = aContainer->GetEffectiveVisibleRegion().GetBounds();
 
-  gfxMatrix worldTransform = aManager->GetWorldTransform();
-
   nsIntRect cachedScissor = aContainer->gl()->ScissorRect();
   aContainer->gl()->PushScissorRect();
   aContainer->mSupportsComponentAlphaChildren = PR_FALSE;
 
   float opacity = aContainer->GetEffectiveOpacity();
   const gfx3DMatrix& transform = aContainer->GetEffectiveTransform();
   bool needsFramebuffer = aContainer->UseIntermediateSurface();
   gfxMatrix contTransform;
@@ -228,22 +226,18 @@ ContainerRender(Container* aContainer,
   for (LayerOGL* layerToRender = aContainer->GetFirstChildOGL();
        layerToRender != nsnull;
        layerToRender = GetNextSibling(layerToRender)) {
 
     if (layerToRender->GetLayer()->GetEffectiveVisibleRegion().IsEmpty()) {
       continue;
     }
 
-    nsIntRect scissorRect = 
-      layerToRender->GetLayer()->CalculateScissorRect(needsFramebuffer,
-                                                      visibleRect,
-                                                      cachedScissor,
-                                                      contTransform * worldTransform);
-
+    nsIntRect scissorRect = layerToRender->GetLayer()->
+        CalculateScissorRect(cachedScissor, &aManager->GetWorldTransform());
     if (scissorRect.IsEmpty()) {
       continue;
     }
 
     aContainer->gl()->fScissor(scissorRect.x, 
                                scissorRect.y, 
                                scissorRect.width, 
                                scissorRect.height);
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/654950-1-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div style="opacity:0.8; background-color: gray;">
+  <span style="display:inline-block; width:256px; height:256px; background:lime; position:relative; top:10px;">
+    <div style="background:yellow; height:50px;"></div>
+  </span>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/654950-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div style="opacity:0.8; background-color: gray;">
+  <div style="overflow: hidden; -moz-transform: translate(0,10px);">
+    <canvas id="canvas" width="256" height="256"></canvas>
+  </div>
+</div>
+<script>
+var ctx = document.getElementById("canvas").getContext("2d");
+ctx.fillStyle = "yellow";
+ctx.fillRect(0, 0, 256, 50);
+ctx.fillStyle = "lime";
+ctx.fillRect(0, 50, 256, 206);
+</script>
+</body>
+</html>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1626,13 +1626,14 @@ fails-if(Android) == 635302-1.html 63530
 == 635373-1.html 635373-1-ref.html
 == 635373-2.html 635373-2-ref.html
 fails-if(http.platform=="X11"&&!layersGPUAccelerated) == 635373-3.html 635373-3-ref.html
 HTTP(..) == 635639-1.html 635639-1-ref.html
 HTTP(..) == 635639-2.html 635639-2-ref.html
 == 641770-1.html 641770-1-ref.html
 == 641856-1.html 641856-1-ref.html
 == 645491-1.html 645491-1-ref.html
+fails-if(layersGPUAccelerated&&cocoaWidget) == 650228-1.html 650228-1-ref.html # Quartz alpha blending doesn't match GL alpha blending
 == 653930-1.html 653930-1-ref.html
-fails-if(layersGPUAccelerated&&cocoaWidget) == 650228-1.html 650228-1-ref.html # Quartz alpha blending doesn't match GL alpha blending
 HTTP(..) == 654057-1.html 654057-1-ref.html
+== 654950-1.html 654950-1-ref.html
 == 652775-1.html 652775-1-ref.html
 != 656875.html about:blank