Bug 418201. Restrict filter processing to the dirty area. r=longsonr,sr=mats
authorRobert O'Callahan <robert@ocallahan.org>
Tue, 15 Jul 2008 15:08:47 +1200
changeset 15936 8ffe8a29e52b0c9a74dcb868e6ed6e0581475503
parent 15935 b87a131fa00847a354991159420e29d2dafd9474
child 15937 4624fef36355572e0e4ce040cef34f6f378da39c
push id618
push userrocallahan@mozilla.com
push dateTue, 15 Jul 2008 03:09:29 +0000
treeherdermozilla-central@8ffe8a29e52b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslongsonr, mats
bugs418201
milestone1.9.1a1pre
Bug 418201. Restrict filter processing to the dirty area. r=longsonr,sr=mats
content/svg/content/src/nsSVGFilters.cpp
content/svg/content/src/nsSVGFilters.h
layout/reftests/svg/filters/feGaussianBlur-3-ref.svg
layout/reftests/svg/filters/feGaussianBlur-3.svg
layout/reftests/svg/filters/feGaussianBlur-4-ref.svg
layout/reftests/svg/filters/feGaussianBlur-4.svg
layout/reftests/svg/filters/feGaussianBlur-5-ref.svg
layout/reftests/svg/filters/feGaussianBlur-5.svg
layout/reftests/svg/filters/feGaussianBlur-6.svg
layout/reftests/svg/filters/reftest.list
layout/svg/base/src/nsSVGFilterFrame.cpp
layout/svg/base/src/nsSVGFilterFrame.h
layout/svg/base/src/nsSVGFilterInstance.cpp
layout/svg/base/src/nsSVGFilterInstance.h
layout/svg/base/src/nsSVGUtils.cpp
--- a/content/svg/content/src/nsSVGFilters.cpp
+++ b/content/svg/content/src/nsSVGFilters.cpp
@@ -567,23 +567,25 @@ AreAllColorChannelsZero(const nsSVGFE::I
 {
   return aTarget->mConstantColorChannels &&
          aTarget->mImage->GetDataSize() >= 4 &&
          (*reinterpret_cast<PRUint32*>(aTarget->mImage->Data()) & 0x00FFFFFF) == 0;
 }
 
 void
 nsSVGFEGaussianBlurElement::GaussianBlur(const Image *aSource,
-                                         const Image *aTarget,                                         const nsIntRect& aDataRect,
+                                         const Image *aTarget,                                         
+                                         const nsIntRect& aDataRect,
                                          PRUint32 aDX, PRUint32 aDY)
 {
   NS_ASSERTION(nsIntRect(0,0,aTarget->mImage->Width(),aTarget->mImage->Height()).Contains(aDataRect),
                "aDataRect out of bounds");
 
-  nsAutoArrayPtr<PRUint8> tmp(new PRUint8[aTarget->mImage->GetDataSize()]);  if (!tmp)
+  nsAutoArrayPtr<PRUint8> tmp(new PRUint8[aTarget->mImage->GetDataSize()]);
+  if (!tmp)
     return;
   memset(tmp, 0, aTarget->mImage->GetDataSize());
 
   PRBool alphaOnly = AreAllColorChannelsZero(aTarget);
   
   const PRUint8* sourceData = aSource->mImage->Data();
   PRUint8* targetData = aTarget->mImage->Data();
   PRUint32 stride = aTarget->mImage->Stride();
@@ -610,46 +612,102 @@ nsSVGFEGaussianBlurElement::GaussianBlur
       PRInt32 ms = major*4;
       BoxBlur(tmp + ms, targetData + ms, stride, aDataRect.y, aDataRect.YMost(), longLobe, shortLobe, alphaOnly);
       BoxBlur(targetData + ms, tmp + ms, stride, aDataRect.y, aDataRect.YMost(), shortLobe, longLobe, alphaOnly);
       BoxBlur(tmp + ms, targetData + ms, stride, aDataRect.y, aDataRect.YMost(), longLobe, longLobe, alphaOnly);
     }
   }
 }
 
+static void
+InflateRectForBlurDXY(nsIntRect* aRect, PRUint32 aDX, PRUint32 aDY)
+{
+  aRect->Inflate(3*(aDX/2), 3*(aDY/2));
+}
+
+static void
+ClearRect(gfxImageSurface* aSurface, PRInt32 aX, PRInt32 aY,
+          PRInt32 aXMost, PRInt32 aYMost)
+{
+  NS_ASSERTION(aX <= aXMost && aY <= aYMost, "Invalid rectangle");
+  NS_ASSERTION(aX >= 0 && aY >= 0 && aXMost <= aSurface->Width() && aYMost <= aSurface->Height(),
+               "Rectangle out of bounds");
+
+  if (aX == aXMost || aY == aYMost)
+    return;
+  for (PRInt32 y = aY; y < aYMost; ++y) {
+    memset(aSurface->Data() + aSurface->Stride()*y + aX*4, 0, (aXMost - aX)*4);
+  }
+}
+
+// Clip aTarget's image to its filter primitive subregion.
+// aModifiedRect contains all the pixels which might not be RGBA(0,0,0,0),
+// it's relative to the surface data.
+static void
+ClipTarget(nsSVGFilterInstance* aInstance, const nsSVGFE::Image* aTarget,
+           const nsIntRect& aModifiedRect)
+{
+  nsIntPoint surfaceTopLeft = aInstance->GetSurfaceRect().TopLeft();
+
+  NS_ASSERTION(aInstance->GetSurfaceRect().Contains(aModifiedRect + surfaceTopLeft),
+               "Modified data area overflows the surface?");
+
+  nsIntRect clip = aModifiedRect;
+  nsSVGUtils::ClipToGfxRect(&clip,
+    aTarget->mFilterPrimitiveSubregion - gfxPoint(surfaceTopLeft.x, surfaceTopLeft.y));
+
+  ClearRect(aTarget->mImage, aModifiedRect.x, aModifiedRect.y, aModifiedRect.XMost(), clip.y);
+  ClearRect(aTarget->mImage, aModifiedRect.x, clip.y, clip.x, clip.YMost());
+  ClearRect(aTarget->mImage, clip.XMost(), clip.y, aModifiedRect.XMost(), clip.YMost());
+  ClearRect(aTarget->mImage, aModifiedRect.x, clip.YMost(), aModifiedRect.XMost(), aModifiedRect.YMost());
+}
+
+static void
+ClipComputationRectToSurface(nsSVGFilterInstance* aInstance,
+                             nsIntRect* aDataRect)
+{
+  aDataRect->IntersectRect(*aDataRect,
+          nsRect(nsPoint(0, 0), aInstance->GetSurfaceRect().Size()));
+}
+
 nsresult
 nsSVGFEGaussianBlurElement::Filter(nsSVGFilterInstance* aInstance,
                                    const nsTArray<const Image*>& aSources,
                                    const Image* aTarget,
                                    const nsIntRect& rect)
 {
   PRUint32 dx, dy;
   nsresult rv = GetDXY(&dx, &dy, *aInstance);
   if (rv == NS_ERROR_UNEXPECTED) // zero std deviation
     return NS_OK;
   if (NS_FAILED(rv))
     return rv;
-  GaussianBlur(aSources[0], aTarget, rect, dx, dy);
+
+  nsIntRect computationRect = rect;
+  InflateRectForBlurDXY(&computationRect, dx, dy);
+  ClipComputationRectToSurface(aInstance, &computationRect);
+  GaussianBlur(aSources[0], aTarget, computationRect, dx, dy);
+  ClipTarget(aInstance, aTarget, computationRect);
   return NS_OK;
 }
 
 void
 nsSVGFEGaussianBlurElement::GetSourceImageNames(nsTArray<nsSVGString*>* aSources)
 {
   aSources->AppendElement(&mStringAttributes[IN1]);
 }
 
 void
 nsSVGFEGaussianBlurElement::InflateRectForBlur(nsRect* aRect,
                                                const nsSVGFilterInstance& aInstance)
 {
   PRUint32 dX, dY;
   nsresult rv = GetDXY(&dX, &dY, aInstance);
   if (NS_SUCCEEDED(rv)) {
-    aRect->Inflate(3*(dX/2), 3*(dY/2));
+    InflateRectForBlurDXY(aRect, dX, dY);
   }
 }
 
 nsRect
 nsSVGFEGaussianBlurElement::ComputeTargetBBox(const nsTArray<nsRect>& aSourceBBoxes,
         const nsSVGFilterInstance& aInstance)
 {
   nsRect r = aSourceBBoxes[0];
@@ -2762,16 +2820,18 @@ nsSVGFETileElement::Filter(nsSVGFilterIn
           rect.x, rect.y, rect.width, rect.height);
 #endif
   // XXX This code depends on the surface rect containing the filter
   // primitive subregion. ComputeTargetBBox, ComputeNeededSourceBBoxes
   // and ComputeChangeBBox are all pessimal, so that will normally be OK,
   // but nothing clips mFilterPrimitiveSubregion so this should be changed.
 
   const gfxRect& tileGfx = aSources[0]->mFilterPrimitiveSubregion;
+  // XXX this is bad, technically the filter primitive subregion could be
+  // out of PRInt32 bounds
   nsIntRect tile(PRInt32(tileGfx.X()), PRInt32(tileGfx.Y()),
                  PRInt32(tileGfx.Width()), PRInt32(tileGfx.Height()));
   if (tile.IsEmpty())
     return NS_OK;
   NS_ASSERTION(instance->GetSurfaceRect().Contains(tile),
                "Tile overflows surface rect, this code can't handle it");
   // Get it into surface space
   tile -= instance->GetSurfaceRect().TopLeft();
@@ -3554,18 +3614,18 @@ nsSVGFEMorphologyElement::Filter(nsSVGFi
    * Thus, we can avoid iterating over the entire kernel by comparing the
    * leading edge of the new kernel against the extrema found in the previous
    * kernel.   We must still scan the entire kernel if the previous extrema do
    * not fall within the current kernel or if we are starting a new row.
    */
   for (PRInt32 y = rect.y; y < rect.YMost(); y++) {
     PRUint32 startY = PR_MAX(0, y - ry);
     // We need to read pixels not just in 'rect', which is limited to
-    // our filter primitive subregion, but all pixels in the given radii
-    // from the source surface, so use fr.GetHeight/fr.GetWidth here.
+    // the dirty part of our filter primitive subregion, but all pixels in
+    // the given radii from the source surface, so use the surface size here.
     PRUint32 endY = PR_MIN(y + ry, instance->GetSurfaceHeight() - 1);
     for (PRInt32 x = rect.x; x < rect.XMost(); x++) {
       PRUint32 startX = PR_MAX(0, x - rx);
       PRUint32 endX = PR_MIN(x + rx, instance->GetSurfaceWidth() - 1);
       PRUint32 targIndex = y * stride + 4 * x;
 
       // We need to scan the entire kernel
       if (x == rect.x || xExt[0]  <= startX || xExt[1] <= startX ||
@@ -4533,17 +4593,18 @@ Convolve3x3(const PRUint8 *index, PRInt3
       if (k)
         sum += k * index[4 * (x - 1) + stride * (y - 1)];
     }
   }
   return sum;
 }
 
 static void
-GenerateNormal(float *N, const PRUint8 *data, PRInt32 stride, nsRect rect,
+GenerateNormal(float *N, const PRUint8 *data, PRInt32 stride,
+               PRInt32 surfaceWidth, PRInt32 surfaceHeight,
                PRInt32 x, PRInt32 y, float surfaceScale)
 {
   // See this for source of constants:
   //   http://www.w3.org/TR/SVG11/filters.html#feDiffuseLighting
   static const PRInt8 Kx[3][3][3][3] =
     { { { {  0,  0,  0}, { 0, -2,  2}, { 0, -1,  1} },
         { {  0,  0,  0}, {-2,  0,  2}, {-1,  0,  1} },
         { {  0,  0,  0}, {-2,  2,  0}, {-1,  1,  0} } },
@@ -4570,24 +4631,24 @@ GenerateNormal(float *N, const PRUint8 *
   static const float FACTORy[3][3] =
     { { 2.0 / 3.0, 1.0 / 2.0, 2.0 / 3.0 },
       { 1.0 / 3.0, 1.0 / 4.0, 1.0 / 3.0 },
       { 2.0 / 3.0, 1.0 / 2.0, 2.0 / 3.0 } };
 
   PRInt8 xflag, yflag;
   if (x == 0) {
     xflag = 0;
-  } else if (x == rect.width - 1) {
+  } else if (x == surfaceWidth - 1) {
     xflag = 2;
   } else {
     xflag = 1;
   }
   if (y == 0) {
     yflag = 0;
-  } else if (y == rect.height - 1) {
+  } else if (y == surfaceHeight - 1) {
     yflag = 2;
   } else {
     yflag = 1;
   }
 
   const PRUint8 *index = data + y * stride + 4 * x + GFX_ARGB32_OFFSET_A;
 
   N[0] = -surfaceScale * FACTORx[yflag][xflag] *
@@ -4676,23 +4737,26 @@ nsSVGFELightingElement::Filter(nsSVGFilt
   }
 
   float surfaceScale = mNumberAttributes[SURFACE_SCALE].GetAnimValue();
 
   const nsIntRect& dataRect = info.mDataRect;
   PRInt32 stride = info.mSource->Stride();
   PRUint8 *sourceData = info.mSource->Data();
   PRUint8 *targetData = info.mTarget->Data();
-
+  PRInt32 surfaceWidth = info.mSource->Width();
+  PRInt32 surfaceHeight = info.mSource->Height();
+  
   for (PRInt32 y = dataRect.y; y < dataRect.YMost(); y++) {
     for (PRInt32 x = dataRect.x; x < dataRect.XMost(); x++) {
       PRInt32 index = y * stride + x * 4;
 
       float N[3];
-      GenerateNormal(N, sourceData, stride, rect, x, y, surfaceScale);
+      GenerateNormal(N, sourceData, stride, surfaceWidth, surfaceHeight,
+                     x, y, surfaceScale);
 
       if (pointLight || spotLight) {
         float Z =
           surfaceScale * sourceData[index + GFX_ARGB32_OFFSET_A] / 255;
 
         L[0] = lightPos[0] - x;
         L[1] = lightPos[1] - y;
         L[2] = lightPos[2] - Z;
@@ -5520,18 +5584,18 @@ nsSVGFEDisplacementMapElement::Filter(ns
   float scale = mNumberAttributes[SCALE].GetAnimValue();
   if (scale == 0.0f) {
     CopyRect(aTarget, aSources[0], rect);
     return NS_OK;
   }
 
   NS_ASSERTION(instance->GetSurfaceRect().Size() == instance->GetFilterSpaceSize(),
                "Surface size optimization should have been disabled, see ComputeNeededSourceBBoxes");
-  PRInt32 width = instance->GetSurfaceRect().width;
-  PRInt32 height = instance->GetSurfaceRect().height;
+  PRInt32 width = instance->GetSurfaceWidth();
+  PRInt32 height = instance->GetSurfaceHeight();
 
   PRUint8* sourceData = aSources[0]->mImage->Data();
   PRUint8* displacementData = aSources[1]->mImage->Data();
   PRUint8* targetData = aTarget->mImage->Data();
   PRUint32 stride = aTarget->mImage->Stride();
 
   nsSVGLength2 val;
   val.Init(nsSVGUtils::XY, 0xff, scale, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER);
--- a/content/svg/content/src/nsSVGFilters.h
+++ b/content/svg/content/src/nsSVGFilters.h
@@ -159,19 +159,20 @@ public:
           nsTArray<nsIntRect>& aSourceBBoxes, const nsSVGFilterInstance& aInstance);
 
   // Perform the actual filter operation.
   // We guarantee that every mImage from aSources and aTarget has the
   // same width, height, stride and device offset.
   // aTarget is already filled in. This function just needs to fill in the
   // pixels of aTarget->mImage (which have already been cleared).
   // @param aDataRect the destination rectangle that needs to be painted,
-  // relative to aTarget's surface data. Output must be clipped to this
-  // rectangle. This is the intersection of the filter primitive subregion
-  // for this filter element and the temporary surface area.
+  // relative to aTarget's surface data. This is the intersection of the
+  // filter primitive subregion for this filter element and the
+  // temporary surface area. Output need not be clipped to this rect but
+  // it must be clipped to aTarget->mFilterPrimitiveSubregion.
   virtual nsresult Filter(nsSVGFilterInstance* aInstance,
                           const nsTArray<const Image*>& aSources,
                           const Image* aTarget,
                           const nsIntRect& aDataRect) = 0;
 
   static nsIntRect GetMaxRect() {
     // Try to avoid overflow errors dealing with this rect. It will
     // be intersected with some other reasonable-sized rect eventually.
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/filters/feGaussianBlur-3-ref.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+
+<rect x="100" y="100" width="100" height="100" fill="#00ff00"/>
+
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/filters/feGaussianBlur-3.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+
+<filter id="f1" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+  <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur"/>
+  <feFlood flood-opacity="0" flood-color="black"/>
+  <feComposite in="blur" operator="over" x="100" y="100" width="100" height="100"/> 
+</filter>
+<g filter="url(#f1)">
+  <rect x="100" y="100" width="100" height="100" fill="#00ff00"/>
+</g>
+
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/filters/feGaussianBlur-4-ref.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<filter id="f1" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+  <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur"/>
+  <feFlood flood-opacity="0" flood-color="black"/>
+  <feComposite in="blur" operator="over" x="100" y="100" width="100" height="100"/> 
+</filter>
+<g filter="url(#f1)">
+  <rect x="100" y="100" width="100" height="100" fill="#00ff00"/>
+</g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/filters/feGaussianBlur-4.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<filter id="f1" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+  <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur" x="100" y="100" width="100" height="100"/>
+</filter>
+<g filter="url(#f1)">
+  <rect x="100" y="100" width="100" height="100" fill="#00ff00"/>
+</g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/filters/feGaussianBlur-5-ref.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<g>
+  <rect x="100" y="100" width="100" height="100" fill="#00ff00"/>
+</g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/filters/feGaussianBlur-5.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<filter id="f1" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+  <feGaussianBlur stdDeviation="0.0001"/>
+</filter>
+<g filter="url(#f1)">
+  <rect x="100" y="100" width="100" height="100" fill="#00ff00"/>
+</g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/filters/feGaussianBlur-6.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<filter id="f1" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+  <feGaussianBlur stdDeviation="0"/>
+</filter>
+<g filter="url(#f1)">
+  <rect x="100" y="100" width="100" height="100" fill="#00ff00"/>
+</g>
+</svg>
--- a/layout/reftests/svg/filters/reftest.list
+++ b/layout/reftests/svg/filters/reftest.list
@@ -20,16 +20,20 @@
 == feDisplacementMap-1.svg feDisplacementMap-1-ref.svg
 == feDisplacementMap-2.svg feDisplacementMap-2-ref.svg
 
 == feFlood-1.svg feFlood-1-ref.svg
 == feFlood-2.svg feFlood-2-ref.svg
 
 == feGaussianBlur-1.svg feGaussianBlur-1-ref.svg
 == feGaussianBlur-2.svg feGaussianBlur-2-ref.svg
+!= feGaussianBlur-3.svg feGaussianBlur-3-ref.svg
+== feGaussianBlur-4.svg feGaussianBlur-4-ref.svg
+== feGaussianBlur-5.svg feGaussianBlur-5-ref.svg
+== feGaussianBlur-6.svg about:blank
 
 == feImage-1.svg feImage-1-ref.svg
 
 == feMerge-1.svg feMerge-1-ref.svg
 == feMerge-2.svg feMerge-2-ref.svg
 
 == feMorphology-1.svg feMorphology-1-ref.svg
 == feMorphology-2.svg feMorphology-2-ref.svg
--- a/layout/svg/base/src/nsSVGFilterFrame.cpp
+++ b/layout/svg/base/src/nsSVGFilterFrame.cpp
@@ -80,17 +80,18 @@ nsSVGFilterFrame::FilterFailCleanup(nsSV
   aTarget->SetMatrixPropagation(PR_TRUE);
   aTarget->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
                             nsISVGChildFrame::TRANSFORM_CHANGED);
   aTarget->PaintSVG(aContext, nsnull);
 }
 
 nsresult
 nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
-                              nsISVGChildFrame *aTarget)
+                              nsISVGChildFrame *aTarget,
+                              const nsRect *aDirtyRect)
 {
   nsCOMPtr<nsIDOMSVGFilterElement> aFilter = do_QueryInterface(mContent);
   NS_ASSERTION(aFilter, "Wrong content element (not filter)");
 
   nsIFrame *frame;
   CallQueryInterface(aTarget, &frame);
 
   nsCOMPtr<nsIDOMSVGMatrix> ctm = nsSVGUtils::GetCanvasTM(frame);
@@ -178,40 +179,55 @@ nsSVGFilterFrame::FilterPaint(nsSVGRende
   NS_NewSVGMatrix(getter_AddRefs(filterTransform),
                   filterRes.width / width,      0.0f,
                   0.0f,                         filterRes.height / height,
                   -x * filterRes.width / width, -y * filterRes.height / height);
   aTarget->SetOverrideCTM(filterTransform);
   aTarget->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
                             nsISVGChildFrame::TRANSFORM_CHANGED);
 
+  // 'fini' is the matrix we will finally use to transform filter space
+  // to surface space for drawing
+  nsCOMPtr<nsIDOMSVGMatrix> scale, fini;
+  NS_NewSVGMatrix(getter_AddRefs(scale),
+                  width / filterRes.width, 0.0f,
+                  0.0f, height / filterRes.height,
+                  x, y);
+  ctm->Multiply(scale, getter_AddRefs(fini));
+  
+  nsIntRect dirtyRect(0, 0, filterRes.width, filterRes.height);
+  if (aDirtyRect) {
+    gfxMatrix finiM = nsSVGUtils::ConvertSVGMatrixToThebes(fini);
+    // fini is always invertible.
+    finiM.Invert();
+    gfxRect r = finiM.TransformBounds(gfxRect(aDirtyRect->x, aDirtyRect->y,
+                                              aDirtyRect->width, aDirtyRect->height));
+    r.RoundOut();
+    nsIntRect intRect;
+    if (NS_SUCCEEDED(nsSVGUtils::GfxRectToIntRect(r, &intRect))) {
+      dirtyRect = intRect;
+    }
+  }
+
   // Setup instance data
   PRUint16 primitiveUnits =
     filter->mEnumAttributes[nsSVGFilterElement::PRIMITIVEUNITS].GetAnimValue();
   nsSVGFilterInstance instance(aTarget, mContent, bbox,
                                gfxRect(x, y, width, height),
                                nsIntSize(filterRes.width, filterRes.height),
-                               primitiveUnits);
+                               dirtyRect, primitiveUnits);
 
   nsRefPtr<gfxASurface> result;
   nsresult rv = instance.Render(getter_AddRefs(result));
   if (NS_FAILED(rv)) {
     FilterFailCleanup(aContext, aTarget);
     return NS_OK;
   }
 
   if (result) {
-    nsCOMPtr<nsIDOMSVGMatrix> scale, fini;
-    NS_NewSVGMatrix(getter_AddRefs(scale),
-                    width / filterRes.width, 0.0f,
-                    0.0f, height / filterRes.height,
-                    x, y);
-
-    ctm->Multiply(scale, getter_AddRefs(fini));
-
     nsSVGUtils::CompositeSurfaceMatrix(aContext->GetGfxContext(),
                                        result, fini, 1.0);
   }
 
   aTarget->SetOverrideCTM(nsnull);
   aTarget->SetMatrixPropagation(PR_TRUE);
   aTarget->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
                             nsISVGChildFrame::TRANSFORM_CHANGED);
@@ -271,16 +287,19 @@ nsSVGFilterFrame::GetInvalidationRegion(
     height = nsSVGUtils::UserSpace(targetContent, tmpHeight);
   }
 
 #ifdef DEBUG_tor
   fprintf(stderr, "invalidate box: %f,%f %fx%f\n", x, y, width, height);
 #endif
 
   // transform back
+  // XXXroc this should use nsSVGUtils::ConvertSVGMatrixToThebes
+  // and gfxMatrix::TransformBounds and gfxRect::RoundOut and
+  // nsSVGUtils::GfxRectToIntRect
   float xx[4], yy[4];
   xx[0] = x;          yy[0] = y;
   xx[1] = x + width;  yy[1] = y;
   xx[2] = x + width;  yy[2] = y + height;
   xx[3] = x;          yy[3] = y + height;
 
   nsSVGUtils::TransformPoint(ctm, &xx[0], &yy[0]);
   nsSVGUtils::TransformPoint(ctm, &xx[1], &yy[1]);
--- a/layout/svg/base/src/nsSVGFilterFrame.h
+++ b/layout/svg/base/src/nsSVGFilterFrame.h
@@ -47,17 +47,18 @@ class nsSVGFilterFrame : public nsSVGFil
 {
   friend nsIFrame*
   NS_NewSVGFilterFrame(nsIPresShell* aPresShell, nsIContent* aContent, nsStyleContext* aContext);
 protected:
   nsSVGFilterFrame(nsStyleContext* aContext) : nsSVGFilterFrameBase(aContext) {}
 
 public:    
   nsresult FilterPaint(nsSVGRenderState *aContext,
-                       nsISVGChildFrame *aTarget);
+                       nsISVGChildFrame *aTarget,
+                       const nsRect* aDirtyRect);
 
   // Returns invalidation region for filter (can be bigger than the
   // referencing geometry to filter region sizing) in device pixels
   // relative to the origin of the outer svg.
   nsRect GetInvalidationRegion(nsIFrame *aTarget);
 
   /**
    * Get the "type" of the frame
--- a/layout/svg/base/src/nsSVGFilterInstance.cpp
+++ b/layout/svg/base/src/nsSVGFilterInstance.cpp
@@ -267,19 +267,18 @@ nsSVGFilterInstance::ComputeResultBoundi
 
 void
 nsSVGFilterInstance::ComputeNeededBoxes()
 {
   if (mPrimitives.IsEmpty())
     return;
 
   // In the end, we need whatever the final filter primitive will draw.
-  // XXX we could optimize this by intersecting with the dirty rect here!!!
-  mPrimitives[mPrimitives.Length() - 1].mResultNeededBox
-    = mPrimitives[mPrimitives.Length() - 1].mResultBoundingBox;
+  mPrimitives[mPrimitives.Length() - 1].mResultNeededBox.IntersectRect(
+    mPrimitives[mPrimitives.Length() - 1].mResultBoundingBox, mDirtyRect);
 
   for (PRInt32 i = mPrimitives.Length() - 1; i >= 0; --i) {
     PrimitiveInfo* info = &mPrimitives[i];
     nsAutoTArray<nsIntRect,2> sourceBBoxes;
     for (PRUint32 j = 0; j < info->mInputs.Length(); ++j) {
       sourceBBoxes.AppendElement(info->mInputs[j]->mResultBoundingBox);
     }
     
--- a/layout/svg/base/src/nsSVGFilterInstance.h
+++ b/layout/svg/base/src/nsSVGFilterInstance.h
@@ -64,22 +64,24 @@ class NS_STACK_CLASS nsSVGFilterInstance
 public:
   float GetPrimitiveLength(nsSVGLength2 *aLength) const;
 
   nsSVGFilterInstance(nsISVGChildFrame *aTargetFrame,
                       nsIContent* aFilterElement,
                       nsIDOMSVGRect *aTargetBBox,
                       const gfxRect& aFilterRect,
                       const nsIntSize& aFilterSpaceSize,
+                      const nsIntRect& aDirtyRect,
                       PRUint16 aPrimitiveUnits) :
     mTargetFrame(aTargetFrame),
     mFilterElement(aFilterElement),
     mTargetBBox(aTargetBBox),
     mFilterRect(aFilterRect),
     mFilterSpaceSize(aFilterSpaceSize),
+    mDirtyRect(aDirtyRect),
     mSurfaceRect(nsIntPoint(0, 0), aFilterSpaceSize),
     mPrimitiveUnits(aPrimitiveUnits) {
   }
   
   // The area covered by temporary images, in filter space
   void SetSurfaceRect(const nsIntRect& aRect) { mSurfaceRect = aRect; }
 
   gfxRect GetFilterRect() const { return mFilterRect; }
@@ -157,16 +159,17 @@ private:
     return static_cast<nsSVGElement*>(f->GetContent());
   }
 
   nsISVGChildFrame*       mTargetFrame;
   nsIContent*             mFilterElement;
   nsCOMPtr<nsIDOMSVGRect> mTargetBBox;
   gfxRect                 mFilterRect;
   nsIntSize               mFilterSpaceSize;
+  nsIntRect               mDirtyRect;
   nsIntRect               mSurfaceRect;
   PRUint16                mPrimitiveUnits;
 
   PrimitiveInfo           mSourceColorAlpha;
   PrimitiveInfo           mSourceAlpha;
   nsTArray<PrimitiveInfo> mPrimitives;
 };
 
--- a/layout/svg/base/src/nsSVGUtils.cpp
+++ b/layout/svg/base/src/nsSVGUtils.cpp
@@ -1370,17 +1370,17 @@ nsSVGUtils::PaintChildWithEffects(nsSVGR
   if (clipPathFrame && isTrivialClip) {
     gfx->Save();
     clipPathFrame->ClipPaint(aContext, svgChildFrame, matrix);
   }
 
   /* Paint the child */
   nsSVGFilterFrame *filterFrame = GetFilterFrame(state, aFrame);
   if (filterFrame) {
-    filterFrame->FilterPaint(aContext, svgChildFrame);
+    filterFrame->FilterPaint(aContext, svgChildFrame, aDirtyRect);
   } else {
     svgChildFrame->PaintSVG(aContext, aDirtyRect);
   }
 
   if (clipPathFrame && isTrivialClip) {
     gfx->Restore();
   }