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 idunknown
push userunknown
push dateunknown
reviewersroc
bugs590294
milestone2.0b6pre
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);