Bug 1083672 - Avoid copying more than needed when drawing a canvas onto itself. r=jrmuizel
authorMarkus Stange <mstange@themasta.com>
Fri, 24 Oct 2014 18:32:17 +0200
changeset 212271 853612ddc6861ea705848979a03ed23f35d66f35
parent 212259 5fcda0c427935b3ead2b8e8cbfab2374efc7562d
child 212272 2d37465635cb60341dc02428d8d67cb514e6b356
push id27704
push userkwierso@gmail.com
push dateSat, 25 Oct 2014 01:25:30 +0000
treeherdermozilla-central@e37231060eb4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1083672
milestone36.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 1083672 - Avoid copying more than needed when drawing a canvas onto itself. r=jrmuizel
dom/canvas/CanvasRenderingContext2D.cpp
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3849,16 +3849,42 @@ bool CanvasRenderingContext2D::IsPointIn
                               state.miterLimit,
                               state.dash.Length(),
                               state.dash.Elements(),
                               state.dashOffset);
 
   return tempPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform());
 }
 
+// Returns a surface that contains only the part needed to draw aSourceRect.
+// On entry, aSourceRect is relative to aSurface, and on return aSourceRect is
+// relative to the returned surface.
+static TemporaryRef<SourceSurface>
+ExtractSubrect(SourceSurface* aSurface, mgfx::Rect* aSourceRect, DrawTarget* aTargetDT)
+{
+  mgfx::Rect roundedOutSourceRect = *aSourceRect;
+  roundedOutSourceRect.RoundOut();
+  mgfx::IntRect roundedOutSourceRectInt;
+  if (!roundedOutSourceRect.ToIntRect(&roundedOutSourceRectInt)) {
+    return aSurface;
+  }
+
+  RefPtr<DrawTarget> subrectDT =
+    aTargetDT->CreateSimilarDrawTarget(roundedOutSourceRectInt.Size(), SurfaceFormat::B8G8R8A8);
+
+  if (!subrectDT) {
+    return aSurface;
+  }
+
+  *aSourceRect -= roundedOutSourceRect.TopLeft();
+
+  subrectDT->CopySurface(aSurface, roundedOutSourceRectInt, IntPoint());
+  return subrectDT->Snapshot();
+}
+
 //
 // image
 //
 
 // drawImage(in HTMLImageElement image, in float dx, in float dy);
 //   -- render image from 0,0 at dx,dy top-left coords
 // drawImage(in HTMLImageElement image, in float dx, in float dy, in float sw, in float sh);
 //   -- render image from 0,0 at dx,dy top-left coords clipping it to sw,sh
@@ -3994,20 +4020,29 @@ CanvasRenderingContext2D::DrawImage(cons
   mgfx::Rect bounds;
 
   if (NeedToCalculateBounds()) {
     bounds = mgfx::Rect(dx, dy, dw, dh);
     bounds = mTarget->GetTransform().TransformBounds(bounds);
   }
 
   if (srcSurf) {
+    mgfx::Rect sourceRect(sx, sy, sw, sh);
+    if (element == mCanvasElement) {
+      // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll
+      // trigger a COW copy of the whole canvas into srcSurf. That's a huge
+      // waste if sourceRect doesn't cover the whole canvas.
+      // We avoid copying the whole canvas by manually copying just the part
+      // that we need.
+      srcSurf = ExtractSubrect(srcSurf, &sourceRect, mTarget);
+    }
     AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
       DrawSurface(srcSurf,
                   mgfx::Rect(dx, dy, dw, dh),
-                  mgfx::Rect(sx, sy, sw, sh),
+                  sourceRect,
                   DrawSurfaceOptions(filter),
                   DrawOptions(CurrentState().globalAlpha, UsedOperation()));
   } else {
     DrawDirectlyToCanvas(drawInfo, &bounds,
                          mgfx::Rect(dx, dy, dw, dh),
                          mgfx::Rect(sx, sy, sw, sh),
                          imgSize);
   }