Bug 590294, part 8: Implement resolution-scaled drawing for basic layers. r=roc
authorChris Jones <jones.chris.g@gmail.com>
Fri, 03 Sep 2010 15:10:46 -0500
changeset 54083 8321155e8dad2fb39353be91023c23f85600f215
parent 54082 3467965f4d9f4e10d7c49a596e7715f5d5ff5bdb
child 54084 a1526ab475c9a7f5c5e484e622bbbd2abfe43828
push id15768
push userdougt@mozilla.com
push dateThu, 16 Sep 2010 01:40:23 +0000
treeherdermozilla-central@cdb90b48f19f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs590294
milestone2.0b6pre
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 590294, part 8: Implement resolution-scaled drawing for basic layers. r=roc
gfx/layers/ThebesLayerBuffer.cpp
gfx/layers/ThebesLayerBuffer.h
gfx/layers/basic/BasicLayers.cpp
gfx/layers/opengl/ThebesLayerOGL.cpp
gfx/src/nsRect.cpp
gfx/src/nsRect.h
layout/base/nsDisplayList.cpp
--- a/gfx/layers/ThebesLayerBuffer.cpp
+++ b/gfx/layers/ThebesLayerBuffer.cpp
@@ -39,16 +39,29 @@
 #include "Layers.h"
 #include "gfxContext.h"
 #include "gfxPlatform.h"
 #include "gfxUtils.h"
 
 namespace mozilla {
 namespace layers {
 
+static nsIntSize
+ScaledSize(const nsIntSize& aSize, float aXScale, float aYScale)
+{
+  if (aXScale == 1.0 && aYScale == 1.0) {
+    return aSize;
+  }
+
+  gfxRect rect(0, 0, aSize.width, aSize.height);
+  rect.Scale(aXScale, aYScale);
+  rect.RoundOut();
+  return nsIntSize(rect.size.width, rect.size.height);
+}
+
 nsIntRect
 ThebesLayerBuffer::GetQuadrantRectangle(XSide aXSide, YSide aYSide)
 {
   // quadrantTranslation is the amount we translate the top-left
   // of the quadrant by to get coordinates relative to the layer
   nsIntPoint quadrantTranslation = -mBufferRotation;
   quadrantTranslation.x += aXSide == LEFT ? mBufferRect.width : 0;
   quadrantTranslation.y += aYSide == TOP ? mBufferRect.height : 0;
@@ -62,86 +75,118 @@ ThebesLayerBuffer::GetQuadrantRectangle(
  * mBufferRect).
  * @param aYSide TOP means we draw from the top side of the buffer (which
  * is drawn on the bottom side of mBufferRect). BOTTOM means we draw from
  * the bottom side of the buffer (which is drawn on the top side of
  * mBufferRect).
  */
 void
 ThebesLayerBuffer::DrawBufferQuadrant(gfxContext* aTarget,
-                                      XSide aXSide, YSide aYSide, float aOpacity)
+                                      XSide aXSide, YSide aYSide,
+                                      float aOpacity,
+                                      float aXRes, float aYRes)
 {
   // The rectangle that we're going to fill. Basically we're going to
   // render the buffer at mBufferRect + quadrantTranslation to get the
   // pixels in the right place, but we're only going to paint within
   // mBufferRect
   nsIntRect quadrantRect = GetQuadrantRectangle(aXSide, aYSide);
   nsIntRect fillRect;
   if (!fillRect.IntersectRect(mBufferRect, quadrantRect))
     return;
 
   aTarget->NewPath();
-  aTarget->Rectangle(gfxRect(fillRect.x, fillRect.y, fillRect.width, fillRect.height),
+  aTarget->Rectangle(gfxRect(fillRect.x, fillRect.y,
+                             fillRect.width, fillRect.height),
                      PR_TRUE);
-  aTarget->SetSource(mBuffer, gfxPoint(quadrantRect.x, quadrantRect.y));
+
+  gfxPoint quadrantTranslation(quadrantRect.x, quadrantRect.y);
+  nsRefPtr<gfxPattern> pattern = new gfxPattern(mBuffer);
+
+  // Transform from user -> buffer space.
+  gfxMatrix transform;
+  transform.Scale(aXRes, aYRes);
+  transform.Translate(-quadrantTranslation);
+
+  pattern->SetMatrix(transform);
+  aTarget->SetPattern(pattern);
+
   if (aOpacity != 1.0) {
     aTarget->Save();
     aTarget->Clip();
     aTarget->Paint(aOpacity);
     aTarget->Restore();
   } else {
     aTarget->Fill();
   }
 }
 
 void
-ThebesLayerBuffer::DrawBufferWithRotation(gfxContext* aTarget, float aOpacity)
+ThebesLayerBuffer::DrawBufferWithRotation(gfxContext* aTarget, float aOpacity,
+                                          float aXRes, float aYRes)
 {
   // Draw four quadrants. We could use REPEAT_, but it's probably better
   // not to, to be performance-safe.
-  DrawBufferQuadrant(aTarget, LEFT, TOP, aOpacity);
-  DrawBufferQuadrant(aTarget, RIGHT, TOP, aOpacity);
-  DrawBufferQuadrant(aTarget, LEFT, BOTTOM, aOpacity);
-  DrawBufferQuadrant(aTarget, RIGHT, BOTTOM, aOpacity);
+  DrawBufferQuadrant(aTarget, LEFT, TOP, aOpacity, aXRes, aYRes);
+  DrawBufferQuadrant(aTarget, RIGHT, TOP, aOpacity, aXRes, aYRes);
+  DrawBufferQuadrant(aTarget, LEFT, BOTTOM, aOpacity, aXRes, aYRes);
+  DrawBufferQuadrant(aTarget, RIGHT, BOTTOM, aOpacity, aXRes, aYRes);
 }
 
 static void
 WrapRotationAxis(PRInt32* aRotationPoint, PRInt32 aSize)
 {
   if (*aRotationPoint < 0) {
     *aRotationPoint += aSize;
   } else if (*aRotationPoint >= aSize) {
     *aRotationPoint -= aSize;
   }
 }
 
 ThebesLayerBuffer::PaintState
-ThebesLayerBuffer::BeginPaint(ThebesLayer* aLayer, ContentType aContentType)
+ThebesLayerBuffer::BeginPaint(ThebesLayer* aLayer, ContentType aContentType,
+                              float aXResolution, float aYResolution)
 {
   PaintState result;
 
   result.mRegionToDraw.Sub(aLayer->GetVisibleRegion(), aLayer->GetValidRegion());
 
-  if (mBuffer && aContentType != mBuffer->GetContentType()) {
+  float curXRes = aLayer->GetXResolution();
+  float curYRes = aLayer->GetYResolution();
+  if (mBuffer &&
+      (aContentType != mBuffer->GetContentType() ||
+       aXResolution != curXRes || aYResolution != curYRes)) {
     // We're effectively clearing the valid region, so we need to draw
     // the entire visible region now.
+    //
+    // XXX/cjones: a possibly worthwhile optimization to keep in mind
+    // is to re-use buffers when the resolution and visible region
+    // have changed in such a way that the buffer size stays the same.
+    // It might make even more sense to allocate buffers from a
+    // recyclable pool, so that we could keep this logic simple and
+    // still get back the same buffer.
     result.mRegionToDraw = aLayer->GetVisibleRegion();
     result.mRegionToInvalidate = aLayer->GetValidRegion();
     Clear();
   }
 
   if (result.mRegionToDraw.IsEmpty())
     return result;
   nsIntRect drawBounds = result.mRegionToDraw.GetBounds();
 
   nsIntRect visibleBounds = aLayer->GetVisibleRegion().GetBounds();
+  nsIntSize scaledBufferSize = ScaledSize(visibleBounds.Size(),
+                                          aXResolution, aYResolution);
   nsRefPtr<gfxASurface> destBuffer;
   nsIntRect destBufferRect;
 
-  if (BufferSizeOkFor(visibleBounds.Size())) {
+  if (BufferSizeOkFor(scaledBufferSize)) {
+    NS_ASSERTION(curXRes == aXResolution && curYRes == aYResolution,
+                 "resolution changes must Clear()!");
+
     // The current buffer is big enough to hold the visible area.
     if (mBufferRect.Contains(visibleBounds)) {
       // We don't need to adjust mBufferRect.
       destBufferRect = mBufferRect;
     } else {
       // The buffer's big enough but doesn't contain everything that's
       // going to be visible. We'll move it.
       destBufferRect = nsIntRect(visibleBounds.TopLeft(), mBufferRect.Size());
@@ -164,17 +209,19 @@ ThebesLayerBuffer::BeginPaint(ThebesLaye
         // The stuff we need to redraw will wrap around an edge of the
         // buffer, so we will need to do a self-copy
         if (mBufferRotation == nsIntPoint(0,0)) {
           destBuffer = mBuffer;
         } else {
           // We can't do a real self-copy because the buffer is rotated.
           // So allocate a new buffer for the destination.
           destBufferRect = visibleBounds;
-          destBuffer = CreateBuffer(aContentType, destBufferRect.Size());
+          destBuffer = CreateBuffer(aContentType,
+                                    ScaledSize(destBufferRect.Size(),
+                                               aXResolution, aYResolution));
           if (!destBuffer)
             return result;
         }
       } else {
         mBufferRect = destBufferRect;
         mBufferRotation = newRotation;
       }
     } else {
@@ -182,33 +229,38 @@ ThebesLayerBuffer::BeginPaint(ThebesLaye
       // will be redrawn, so we don't need to copy anything, so we don't
       // set destBuffer.
       mBufferRect = destBufferRect;
       mBufferRotation = nsIntPoint(0,0);
     }
   } else {
     // The buffer's not big enough, so allocate a new one
     destBufferRect = visibleBounds;
-    destBuffer = CreateBuffer(aContentType, destBufferRect.Size());
+    destBuffer = CreateBuffer(aContentType,
+                              ScaledSize(destBufferRect.Size(),
+                                         aXResolution, aYResolution));
     if (!destBuffer)
       return result;
   }
 
   // If we have no buffered data already, then destBuffer will be a fresh buffer
   // and we do not need to clear it below.
   PRBool isClear = mBuffer == nsnull;
 
   if (destBuffer) {
     if (mBuffer) {
       // Copy the bits
       nsRefPtr<gfxContext> tmpCtx = new gfxContext(destBuffer);
       nsIntPoint offset = -destBufferRect.TopLeft();
       tmpCtx->SetOperator(gfxContext::OPERATOR_SOURCE);
+      tmpCtx->Scale(aXResolution, aYResolution);
       tmpCtx->Translate(gfxPoint(offset.x, offset.y));
-      DrawBufferWithRotation(tmpCtx, 1.0);
+      NS_ASSERTION(curXRes == aXResolution && curYRes == aYResolution,
+                   "resolution changes must Clear()!");
+      DrawBufferWithRotation(tmpCtx, 1.0, aXResolution, aYResolution);
     }
 
     mBuffer = destBuffer.forget();
     mBufferRect = destBufferRect;
     mBufferRotation = nsIntPoint(0,0);
   }
 
   nsIntRegion invalidate;
@@ -219,16 +271,17 @@ ThebesLayerBuffer::BeginPaint(ThebesLaye
 
   // Figure out which quadrant to draw in
   PRInt32 xBoundary = mBufferRect.XMost() - mBufferRotation.x;
   PRInt32 yBoundary = mBufferRect.YMost() - mBufferRotation.y;
   XSide sideX = drawBounds.XMost() <= xBoundary ? RIGHT : LEFT;
   YSide sideY = drawBounds.YMost() <= yBoundary ? BOTTOM : TOP;
   nsIntRect quadrantRect = GetQuadrantRectangle(sideX, sideY);
   NS_ASSERTION(quadrantRect.Contains(drawBounds), "Messed up quadrants");
+  result.mContext->Scale(aXResolution, aYResolution);
   result.mContext->Translate(-gfxPoint(quadrantRect.x, quadrantRect.y));
 
   gfxUtils::ClipToRegion(result.mContext, result.mRegionToDraw);
   if (aContentType == gfxASurface::CONTENT_COLOR_ALPHA && !isClear) {
     result.mContext->SetOperator(gfxContext::OPERATOR_CLEAR);
     result.mContext->Paint();
     result.mContext->SetOperator(gfxContext::OPERATOR_OVER);
   }
--- a/gfx/layers/ThebesLayerBuffer.h
+++ b/gfx/layers/ThebesLayerBuffer.h
@@ -119,17 +119,18 @@ public:
   /**
    * Start a drawing operation. This returns a PaintState describing what
    * needs to be drawn to bring the buffer up to date in the visible region.
    * This queries aLayer to get the currently valid and visible regions.
    * The returned mContext may be null if mRegionToDraw is empty.
    * Otherwise it must not be null.
    * mRegionToInvalidate will contain mRegionToDraw.
    */
-  PaintState BeginPaint(ThebesLayer* aLayer, ContentType aContentType);
+  PaintState BeginPaint(ThebesLayer* aLayer, ContentType aContentType,
+                        float aXResolution, float aYResolution);
 
   /**
    * Return a new surface of |aSize| and |aType|.
    */
   virtual already_AddRefed<gfxASurface>
   CreateBuffer(ContentType aType, const nsIntSize& aSize) = 0;
 
   /**
@@ -142,18 +143,20 @@ public:
 protected:
   enum XSide {
     LEFT, RIGHT
   };
   enum YSide {
     TOP, BOTTOM
   };
   nsIntRect GetQuadrantRectangle(XSide aXSide, YSide aYSide);
-  void DrawBufferQuadrant(gfxContext* aTarget, XSide aXSide, YSide aYSide, float aOpacity);
-  void DrawBufferWithRotation(gfxContext* aTarget, float aOpacity);
+  void DrawBufferQuadrant(gfxContext* aTarget, XSide aXSide, YSide aYSide,
+                          float aOpacity, float aXRes, float aYRes);
+  void DrawBufferWithRotation(gfxContext* aTarget, float aOpacity,
+                              float aXRes, float aYRes);
 
   const nsIntRect& BufferRect() const { return mBufferRect; }
   const nsIntPoint& BufferRotation() const { return mBufferRotation; }
 
   already_AddRefed<gfxASurface>
   SetBuffer(gfxASurface* aBuffer,
             const nsIntRect& aBufferRect, const nsIntPoint& aBufferRotation)
   {
--- a/gfx/layers/basic/BasicLayers.cpp
+++ b/gfx/layers/basic/BasicLayers.cpp
@@ -419,29 +419,36 @@ BasicThebesLayer::Paint(gfxContext* aCon
       target->PopGroupToSource();
       target->Paint(aOpacity);
     }
     target->Restore();
     return;
   }
 
   {
-    Buffer::PaintState state = mBuffer.BeginPaint(this, contentType);
+    float paintXRes = BasicManager()->XResolution();
+    float paintYRes = BasicManager()->YResolution();
+    Buffer::PaintState state =
+      mBuffer.BeginPaint(this, contentType, paintXRes, paintYRes);
     mValidRegion.Sub(mValidRegion, state.mRegionToInvalidate);
 
     if (state.mContext) {
       // The area that became invalid and is visible needs to be repainted
       // (this could be the whole visible area if our buffer switched
       // from RGB to RGBA, because we might need to repaint with
       // subpixel AA)
       state.mRegionToInvalidate.And(state.mRegionToInvalidate, mVisibleRegion);
       InheritContextFlags(target, state.mContext);
       PaintBuffer(state.mContext,
                   state.mRegionToDraw, state.mRegionToInvalidate,
                   aCallback, aCallbackData);
+
+      mXResolution = paintXRes;
+      mYResolution = paintYRes;
+      Mutated();
     } else {
       // It's possible that state.mRegionToInvalidate is nonempty here,
       // if we are shrinking the valid region to nothing.
       NS_ASSERTION(state.mRegionToDraw.IsEmpty(),
                    "If we need to draw, we should have a context");
     }
   }
 
@@ -454,17 +461,18 @@ BasicThebesLayerBuffer::DrawTo(ThebesLay
                                gfxContext* aTarget,
                                float aOpacity)
 {
   aTarget->Save();
   gfxUtils::ClipToRegion(aTarget, aLayer->GetVisibleRegion());
   if (aIsOpaqueContent) {
     aTarget->SetOperator(gfxContext::OPERATOR_SOURCE);
   }
-  DrawBufferWithRotation(aTarget, aOpacity);
+  DrawBufferWithRotation(aTarget, aOpacity,
+                         aLayer->GetXResolution(), aLayer->GetYResolution());
   aTarget->Restore();
 }
 
 already_AddRefed<gfxASurface>
 BasicThebesLayerBuffer::CreateBuffer(ContentType aType, 
                                      const nsIntSize& aSize)
 {
   return mLayer->CreateBuffer(aType, aSize);
--- a/gfx/layers/opengl/ThebesLayerOGL.cpp
+++ b/gfx/layers/opengl/ThebesLayerOGL.cpp
@@ -183,17 +183,17 @@ public:
     NS_ABORT_IF_FALSE(mTmpSurface, "SurfaceBuffer without backing surface??");
   }
   virtual ~SurfaceBufferOGL() {}
 
   // ThebesLayerBufferOGL interface
   virtual PaintState BeginPaint(ContentType aContentType)
   {
     // Let ThebesLayerBuffer do all the hard work for us! :D
-    return ThebesLayerBuffer::BeginPaint(mLayer, aContentType);
+    return ThebesLayerBuffer::BeginPaint(mLayer, aContentType, 1.0, 1.0);
   }
 
   // ThebesLayerBuffer interface
   virtual already_AddRefed<gfxASurface>
   CreateBuffer(ContentType aType, const nsIntSize& aSize)
   {
     NS_ASSERTION(gfxASurface::CONTENT_ALPHA != aType,"ThebesBuffer has color");
 
--- a/gfx/src/nsRect.cpp
+++ b/gfx/src/nsRect.cpp
@@ -184,22 +184,22 @@ nsMargin nsRect::operator-(const nsRect&
   margin.left = aRect.x - x;
   margin.right = XMost() - aRect.XMost();
   margin.top = aRect.y - y;
   margin.bottom = YMost() - aRect.YMost();
   return margin;
 }
 
 // scale the rect but round to smallest containing rect
-nsRect& nsRect::ScaleRoundOut(float aScale)
+nsRect& nsRect::ScaleRoundOut(float aXScale, float aYScale)
 {
-  nscoord right = NSToCoordCeil(float(XMost()) * aScale);
-  nscoord bottom = NSToCoordCeil(float(YMost()) * aScale);
-  x = NSToCoordFloor(float(x) * aScale);
-  y = NSToCoordFloor(float(y) * aScale);
+  nscoord right = NSToCoordCeil(float(XMost()) * aXScale);
+  nscoord bottom = NSToCoordCeil(float(YMost()) * aYScale);
+  x = NSToCoordFloor(float(x) * aXScale);
+  y = NSToCoordFloor(float(y) * aYScale);
   width = (right - x);
   height = (bottom - y);
   return *this;
 }
 
 #ifdef DEBUG
 // Diagnostics
 
--- a/gfx/src/nsRect.h
+++ b/gfx/src/nsRect.h
@@ -175,17 +175,18 @@ struct NS_GFX nsRect {
   nsMargin operator-(const nsRect& aRect) const; // Find difference as nsMargin
   nsRect& operator+=(const nsMargin& aMargin) { Inflate(aMargin); return *this; }
   nsRect& operator-=(const nsMargin& aMargin) { Deflate(aMargin); return *this; }
   nsRect  operator+(const nsMargin& aMargin) const { return nsRect(*this) += aMargin; }
   nsRect  operator-(const nsMargin& aMargin) const { return nsRect(*this) -= aMargin; }
 
   // Scale by aScale, converting coordinates to integers so that the result is
   // the smallest integer-coordinate rectangle containing the unrounded result.
-  nsRect& ScaleRoundOut(float aScale);
+  nsRect& ScaleRoundOut(float aScale) { return ScaleRoundOut(aScale, aScale); }
+  nsRect& ScaleRoundOut(float aXScale, float aYScale);
 
   // Converts this rect from aFromAPP, an appunits per pixel ratio, to aToAPP.
   // In the RoundOut version we make the rect the smallest rect containing the
   // unrounded result. In the RoundIn version we make the rect the largest rect
   // contained in the unrounded result.
   inline nsRect ConvertAppUnitsRoundOut(PRInt32 aFromAPP, PRInt32 aToAPP) const;
   inline nsRect ConvertAppUnitsRoundIn(PRInt32 aFromAPP, PRInt32 aToAPP) const;
 
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -429,16 +429,26 @@ void nsDisplayList::PaintForFrame(nsDisp
     presShell->GetRootScrollFrameAsScrollable();
   if (rootScrollableFrame) {
     metrics.mViewportScrollOffset =
       rootScrollableFrame->GetScrollPosition().ToNearestPixels(auPerCSSPixel);
   }
 
   root->SetFrameMetrics(metrics);
 
+  // If the layer manager supports resolution scaling, set that up
+  if (LayerManager::LAYERS_BASIC == layerManager->GetBackendType()) {
+    BasicLayerManager* basicManager =
+      static_cast<BasicLayerManager*>(layerManager.get());
+    // This is free if both resolutions are 1.0, or neither resolution
+    // has changed since the last transaction
+    basicManager->SetResolution(presShell->GetXResolution(),
+                                presShell->GetYResolution());
+  }
+
   layerManager->SetRoot(root);
   aBuilder->LayerBuilder()->WillEndTransaction(layerManager);
   layerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer,
                                aBuilder);
   aBuilder->LayerBuilder()->DidEndTransaction(layerManager);
 
   if (aFlags & PAINT_FLUSH_LAYERS) {
     FrameLayerBuilder::InvalidateAllLayers(layerManager);