Bug 1155249 - Prescale image with an intermediate surface if repeated often on OS X. r=mstange
authorMason Chang <mchang@mozilla.com>
Thu, 30 Jul 2015 08:14:00 -0400
changeset 287204 f8e637f308e1fb1d6173e8983c156689ec506c5f
parent 287203 4ddd82cb5c0b58d14ceb732ee6f9bcb4f83edc9a
child 287205 0ad329ed192e16cc7d021d3dc25887b5d9cff086
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1155249
milestone42.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 1155249 - Prescale image with an intermediate surface if repeated often on OS X. r=mstange
gfx/thebes/gfxUtils.cpp
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -599,16 +599,112 @@ static GraphicsFilter ReduceResamplingFi
                                              int aImgWidth, int aImgHeight,
                                              int aSourceWidth, int aSourceHeight)
 {
     // Just pass the filter through unchanged
     return aFilter;
 }
 #endif
 
+#ifdef MOZ_WIDGET_COCOA
+// Only prescale a temporary surface if we're going to repeat it often.
+// Scaling is expensive on OS X and without prescaling, we'd scale
+// every tile of the repeated rect. However, using a temp surface also potentially uses
+// more memory if the scaled image is large. So only prescale on a temp
+// surface if we know we're going to repeat the image in either the X or Y axis
+// multiple times.
+static bool
+ShouldUseTempSurface(Rect aImageRect, Rect aNeededRect)
+{
+  int repeatX = aNeededRect.width / aImageRect.width;
+  int repeatY = aNeededRect.height / aImageRect.height;
+  return (repeatX >= 5) || (repeatY >= 5);
+}
+
+static bool
+PrescaleAndTileDrawable(gfxDrawable* aDrawable,
+                        gfxContext* aContext,
+                        const ImageRegion& aRegion,
+                        Rect aImageRect,
+                        const GraphicsFilter& aFilter,
+                        const SurfaceFormat aFormat,
+                        gfxFloat aOpacity)
+{
+  gfxSize scaleFactor = aContext->CurrentMatrix().ScaleFactors(true);
+  gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleFactor.width, scaleFactor.height);
+  const float fuzzFactor = 0.01;
+
+  // If we aren't scaling or translating, don't go down this path
+  if ((FuzzyEqual(scaleFactor.width, 1.0, fuzzFactor) &&
+      FuzzyEqual(scaleFactor.width, 1.0, fuzzFactor)) ||
+      aContext->CurrentMatrix().HasNonAxisAlignedTransform()) {
+    return false;
+  }
+
+  gfxRect clipExtents = aContext->GetClipExtents();
+
+  // Inflate by one pixel because bilinear filtering will sample at most
+  // one pixel beyond the computed image pixel coordinate.
+  clipExtents.Inflate(1.0);
+
+  gfxRect needed = aRegion.IntersectAndRestrict(clipExtents);
+  Rect scaledNeededRect = ToMatrix(scaleMatrix).TransformBounds(ToRect(needed));
+  scaledNeededRect.RoundOut();
+  if (scaledNeededRect.IsEmpty()) {
+    return false;
+  }
+
+  Rect scaledImageRect = ToMatrix(scaleMatrix).TransformBounds(aImageRect);
+  if (!ShouldUseTempSurface(scaledImageRect, scaledNeededRect)) {
+    return false;
+  }
+
+  gfxIntSize scaledImageSize((int32_t)scaledImageRect.width,
+                             (int32_t)scaledImageRect.height);
+  if (scaledImageSize.width != scaledImageRect.width ||
+      scaledImageSize.height != scaledImageRect.height) {
+    // If the scaled image isn't pixel aligned, we'll get artifacts
+    // so we have to take the slow path.
+    return false;
+  }
+
+  RefPtr<DrawTarget> scaledDT =
+    gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(scaledImageSize, aFormat);
+  if (!scaledDT) {
+    return false;
+  }
+
+  nsRefPtr<gfxContext> tmpCtx = new gfxContext(scaledDT);
+  scaledDT->SetTransform(ToMatrix(scaleMatrix));
+  gfxRect gfxImageRect(aImageRect.x, aImageRect.y, aImageRect.width, aImageRect.height);
+  aDrawable->Draw(tmpCtx, gfxImageRect, true, aFilter, 1.0, gfxMatrix());
+
+  RefPtr<SourceSurface> scaledImage = scaledDT->Snapshot();
+
+  {
+    gfxContextMatrixAutoSaveRestore autoSR(aContext);
+    Matrix withoutScale = ToMatrix(aContext->CurrentMatrix());
+    DrawTarget* destDrawTarget = aContext->GetDrawTarget();
+
+    // The translation still is in scaled units
+    withoutScale.PreScale(1.0 / scaleFactor.width, 1.0 / scaleFactor.height);
+    aContext->SetMatrix(ThebesMatrix(withoutScale));
+
+    DrawOptions drawOptions(aOpacity,
+        CompositionOpForOp(aContext->CurrentOperator()),
+        aContext->CurrentAntialiasMode());
+
+    SurfacePattern scaledImagePattern(scaledImage, ExtendMode::REPEAT,
+                                      Matrix(), ToFilter(aFilter));
+    destDrawTarget->FillRect(scaledNeededRect, scaledImagePattern, drawOptions);
+  }
+  return true;
+}
+#endif // MOZ_WIDGET_COCOA
+
 /* static */ void
 gfxUtils::DrawPixelSnapped(gfxContext*         aContext,
                            gfxDrawable*        aDrawable,
                            const gfxSize&      aImageSize,
                            const ImageRegion&  aRegion,
                            const SurfaceFormat aFormat,
                            GraphicsFilter      aFilter,
                            uint32_t            aImageFlags,
@@ -644,16 +740,24 @@ gfxUtils::DrawPixelSnapped(gfxContext*  
     // XXX if only we had source-clipping in cairo!
     if (aContext->CurrentMatrix().HasNonIntegerTranslation()) {
         if (doTile || !aRegion.RestrictionContains(imageRect)) {
             if (drawable->DrawWithSamplingRect(aContext, aRegion.Rect(), aRegion.Restriction(),
                                                doTile, aFilter, aOpacity)) {
               return;
             }
 
+#ifdef MOZ_WIDGET_COCOA
+            if (PrescaleAndTileDrawable(aDrawable, aContext, aRegion,
+                                        ToRect(imageRect), aFilter,
+                                        aFormat, aOpacity)) {
+              return;
+            }
+#endif
+
             // On Mobile, we don't ever want to do this; it has the potential for
             // allocating very large temporary surfaces, especially since we'll
             // do full-page snapshots often (see bug 749426).
 #ifndef MOZ_GFX_OPTIMIZE_MOBILE
             nsRefPtr<gfxDrawable> restrictedDrawable =
               CreateSamplingRestrictedDrawable(aDrawable, aContext,
                                                aRegion, aFormat);
             if (restrictedDrawable) {