Bug 635373. Disable ThebesLayerBuffer rotation for non-identity resolutions, and add API to disable rotation explicitly. r=cjones a=beltzner
authorRobert O'Callahan <robert@ocallahan.org>
Tue, 01 Mar 2011 14:30:16 -0600
changeset 63213 502114bb31e816f708ac67af94194ad135941c74
parent 63212 7ed9b3bc059e3d5746ec33560c5da24c95bd556b
child 63214 c1d77dbe4193e45e526044c91e6011eb39ddbc3d
push id19081
push usertnikkel@gmail.com
push dateTue, 01 Mar 2011 20:37:26 +0000
treeherdermozilla-central@c1d77dbe4193 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscjones, beltzner
bugs635373
milestone2.0b13pre
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 635373. Disable ThebesLayerBuffer rotation for non-identity resolutions, and add API to disable rotation explicitly. r=cjones a=beltzner
gfx/layers/ThebesLayerBuffer.cpp
gfx/layers/ThebesLayerBuffer.h
gfx/layers/basic/BasicLayers.cpp
gfx/layers/opengl/ThebesLayerOGL.cpp
--- a/gfx/layers/ThebesLayerBuffer.cpp
+++ b/gfx/layers/ThebesLayerBuffer.cpp
@@ -175,116 +175,156 @@ WrapRotationAxis(PRInt32* aRotationPoint
     *aRotationPoint += aSize;
   } else if (*aRotationPoint >= aSize) {
     *aRotationPoint -= aSize;
   }
 }
 
 ThebesLayerBuffer::PaintState
 ThebesLayerBuffer::BeginPaint(ThebesLayer* aLayer, ContentType aContentType,
-                              float aXResolution, float aYResolution)
+                              float aXResolution, float aYResolution,
+                              PRUint32 aFlags)
 {
   PaintState result;
-
-  result.mRegionToDraw.Sub(aLayer->GetVisibleRegion(), aLayer->GetValidRegion());
-
   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();
+
+  nsIntRegion validRegion = aLayer->GetValidRegion();
+
+  ContentType contentType;
+  nsIntRegion neededRegion;
+  nsIntSize destBufferDims;
+  PRBool canReuseBuffer;
+  nsIntRect destBufferRect;
+
+  while (PR_TRUE) {
+    contentType = aContentType;
+    neededRegion = aLayer->GetVisibleRegion();
+    destBufferDims = ScaledSize(neededRegion.GetBounds().Size(),
+                                aXResolution, aYResolution);
+    canReuseBuffer = BufferSizeOkFor(destBufferDims);
+
+    if (canReuseBuffer) {
+      if (mBufferRect.Contains(neededRegion.GetBounds())) {
+        // 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(neededRegion.GetBounds().TopLeft(), mBufferRect.Size());
+      }
+    } else {
+      destBufferRect = neededRegion.GetBounds();
+    }
+
+    if ((aFlags & PAINT_WILL_RESAMPLE) &&
+        (neededRegion.GetBounds() != destBufferRect ||
+         neededRegion.GetNumRects() > 1)) {
+      // The area we add to neededRegion might not be painted opaquely
+      contentType = gfxASurface::CONTENT_COLOR_ALPHA;
+
+      // We need to validate the entire buffer, to make sure that only valid
+      // pixels are sampled
+      neededRegion = destBufferRect;
+      destBufferDims = ScaledSize(neededRegion.GetBounds().Size(),
+                                  aXResolution, aYResolution);
+    }
+
+    if (mBuffer &&
+        (contentType != mBuffer->GetContentType() ||
+         aXResolution != curXRes || aYResolution != curYRes)) {
+      // We're effectively clearing the valid region, so we need to draw
+      // the entire needed 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.mRegionToInvalidate = aLayer->GetValidRegion();
+      validRegion.SetEmpty();
+      Clear();
+      // Restart decision process with the cleared buffer. We can only go
+      // around the loop one more iteration, since mBuffer is null now.
+      continue;
+    }
+
+    break;
   }
 
+  result.mRegionToDraw.Sub(neededRegion, validRegion);
   if (result.mRegionToDraw.IsEmpty())
     return result;
+
+  // If we have non-identity resolution then mBufferRotation might not fall
+  // on a buffer pixel boundary, in which case that row of pixels will contain
+  // a mix of two completely different rows of the layer, which would be
+  // a catastrophe. So disable rotation in that case.
+  // We also need to disable rotation if we're going to be resampled when
+  // drawing, because we might sample across the rotation boundary.
+  PRBool canHaveRotation =
+    !(aFlags & PAINT_WILL_RESAMPLE) && aXResolution == 1.0 && aYResolution == 1.0;
   nsIntRect drawBounds = result.mRegionToDraw.GetBounds();
-
-  nsIntRect visibleBounds = aLayer->GetVisibleRegion().GetBounds();
-  nsIntSize destBufferDims = ScaledSize(visibleBounds.Size(),
-                                        aXResolution, aYResolution);
   nsRefPtr<gfxASurface> destBuffer;
-  nsIntRect destBufferRect;
   PRBool bufferDimsChanged = PR_FALSE;
-
-  if (BufferSizeOkFor(destBufferDims)) {
+  if (canReuseBuffer) {
     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());
-    }
     nsIntRect keepArea;
     if (keepArea.IntersectRect(destBufferRect, mBufferRect)) {
       // Set mBufferRotation so that the pixels currently in mBuffer
       // will still be rendered in the right place when mBufferRect
       // changes to destBufferRect.
       nsIntPoint newRotation = mBufferRotation +
         (destBufferRect.TopLeft() - mBufferRect.TopLeft());
       WrapRotationAxis(&newRotation.x, mBufferRect.width);
       WrapRotationAxis(&newRotation.y, mBufferRect.height);
       NS_ASSERTION(nsIntRect(nsIntPoint(0,0), mBufferRect.Size()).Contains(newRotation),
                    "newRotation out of bounds");
       PRInt32 xBoundary = destBufferRect.XMost() - newRotation.x;
       PRInt32 yBoundary = destBufferRect.YMost() - newRotation.y;
       if ((drawBounds.x < xBoundary && xBoundary < drawBounds.XMost()) ||
-          (drawBounds.y < yBoundary && yBoundary < drawBounds.YMost())) {
+          (drawBounds.y < yBoundary && yBoundary < drawBounds.YMost()) ||
+          (newRotation != nsIntPoint(0,0) && !canHaveRotation)) {
         // The stuff we need to redraw will wrap around an edge of the
         // buffer, so we will need to do a self-copy
         if (mBuffer->SupportsSelfCopy() && 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;
-          destBufferDims = ScaledSize(destBufferRect.Size(),
-                                      aXResolution, aYResolution);
+          destBufferRect = neededRegion.GetBounds();
           bufferDimsChanged = PR_TRUE;
-          destBuffer = CreateBuffer(aContentType, destBufferDims);
+          destBuffer = CreateBuffer(contentType, destBufferDims);
           if (!destBuffer)
             return result;
         }
       } else {
         mBufferRect = destBufferRect;
         mBufferRotation = newRotation;
       }
     } else {
       // No pixels are going to be kept. The whole visible region
       // 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;
-    destBufferDims = ScaledSize(destBufferRect.Size(),
-                                aXResolution, aYResolution);
+    destBufferRect = neededRegion.GetBounds();
     bufferDimsChanged = PR_TRUE;
-    destBuffer = CreateBuffer(aContentType, destBufferDims);
+    destBuffer = CreateBuffer(contentType, destBufferDims);
     if (!destBuffer)
       return result;
   }
+  NS_ASSERTION(!(aFlags & PAINT_WILL_RESAMPLE) || destBufferRect == neededRegion.GetBounds(),
+               "If we're resampling, we need to validate the entire buffer");
 
   // 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
@@ -300,26 +340,28 @@ ThebesLayerBuffer::BeginPaint(ThebesLaye
 
     mBuffer = destBuffer.forget();
     mBufferRect = destBufferRect;
     mBufferRotation = nsIntPoint(0,0);
   }
   if (bufferDimsChanged) {
     mBufferDims = destBufferDims;
   }
+  NS_ASSERTION(canHaveRotation || mBufferRotation == nsIntPoint(0,0),
+               "Rotation disabled, but we have nonzero rotation?");
 
   nsIntRegion invalidate;
   invalidate.Sub(aLayer->GetValidRegion(), destBufferRect);
   result.mRegionToInvalidate.Or(result.mRegionToInvalidate, invalidate);
 
   result.mContext = GetContextForQuadrantUpdate(drawBounds,
                                                 aXResolution, aYResolution);
 
   gfxUtils::ClipToRegionSnapped(result.mContext, result.mRegionToDraw);
-  if (aContentType == gfxASurface::CONTENT_COLOR_ALPHA && !isClear) {
+  if (contentType == gfxASurface::CONTENT_COLOR_ALPHA && !isClear) {
     result.mContext->SetOperator(gfxContext::OPERATOR_CLEAR);
     result.mContext->Paint();
     result.mContext->SetOperator(gfxContext::OPERATOR_OVER);
   }
   return result;
 }
 
 }
--- a/gfx/layers/ThebesLayerBuffer.h
+++ b/gfx/layers/ThebesLayerBuffer.h
@@ -113,26 +113,38 @@ public:
    * depend on the buffer type.
    */
   struct PaintState {
     nsRefPtr<gfxContext> mContext;
     nsIntRegion mRegionToDraw;
     nsIntRegion mRegionToInvalidate;
   };
 
+  enum {
+    PAINT_WILL_RESAMPLE = 0x01
+  };
   /**
    * 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.
+   * @param aFlags when PAINT_WILL_RESAMPLE is passed, this indicates that
+   * buffer will be resampled when rendering (i.e the effective transform
+   * combined with the scale for the resolution is not just an integer
+   * translation). This will disable buffer rotation (since we don't want
+   * to resample across the rotation boundary) and will ensure that we
+   * make the entire buffer contents valid (since we don't want to sample
+   * invalid pixels outside the visible region, if the visible region doesn't
+   * fill the buffer bounds).
    */
   PaintState BeginPaint(ThebesLayer* aLayer, ContentType aContentType,
-                        float aXResolution, float aYResolution);
+                        float aXResolution, float aYResolution,
+                        PRUint32 aFlags);
 
   /**
    * Return a new surface of |aSize| and |aType|.
    */
   virtual already_AddRefed<gfxASurface>
   CreateBuffer(ContentType aType, const nsIntSize& aSize) = 0;
 
   /**
--- a/gfx/layers/basic/BasicLayers.cpp
+++ b/gfx/layers/basic/BasicLayers.cpp
@@ -303,17 +303,18 @@ ContainerRemoveChild(Layer* aChild, Cont
 class BasicThebesLayer;
 class BasicThebesLayerBuffer : public ThebesLayerBuffer {
   typedef ThebesLayerBuffer Base;
 
 public:
   BasicThebesLayerBuffer(BasicThebesLayer* aLayer)
     : Base(ContainsVisibleBounds)
     , mLayer(aLayer)
-  {}
+  {
+  }
 
   virtual ~BasicThebesLayerBuffer()
   {}
 
   using Base::BufferRect;
   using Base::BufferRotation;
 
   /**
@@ -430,21 +431,23 @@ protected:
               void* aCallbackData)
   {
     if (!aCallback) {
       BasicManager()->SetTransactionIncomplete();
       return;
     }
     aCallback(this, aContext, aRegionToDraw, aRegionToInvalidate,
               aCallbackData);
-    // Everything that's visible has been validated. Do this instead of
+    // Everything that's visible has been validated. Do this instead of just
     // OR-ing with aRegionToDraw, since that can lead to a very complex region
     // here (OR doesn't automatically simplify to the simplest possible
     // representation of a region.)
-    mValidRegion.Or(mValidRegion, mVisibleRegion);
+    nsIntRegion tmp;
+    tmp.Or(mVisibleRegion, aRegionToDraw);
+    mValidRegion.Or(mValidRegion, tmp);
   }
 
   Buffer mBuffer;
 };
 
 /**
  * Clips to the smallest device-pixel-aligned rectangle containing aRect
  * in user space.
@@ -572,18 +575,24 @@ BasicThebesLayer::PaintThebes(gfxContext
     }
     return;
   }
 
   {
     gfxSize scale = aContext->CurrentMatrix().ScaleFactors(PR_TRUE);
     float paintXRes = BasicManager()->XResolution() * gfxUtils::ClampToScaleFactor(scale.width);
     float paintYRes = BasicManager()->YResolution() * gfxUtils::ClampToScaleFactor(scale.height);
+    PRUint32 flags = 0;
+    gfxMatrix transform;
+    if (!GetEffectiveTransform().Is2D(&transform) ||
+        transform.HasNonIntegerTranslation()) {
+      flags |= ThebesLayerBuffer::PAINT_WILL_RESAMPLE;
+    }
     Buffer::PaintState state =
-      mBuffer.BeginPaint(this, contentType, paintXRes, paintYRes);
+      mBuffer.BeginPaint(this, contentType, paintXRes, paintYRes, flags);
     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);
--- a/gfx/layers/opengl/ThebesLayerOGL.cpp
+++ b/gfx/layers/opengl/ThebesLayerOGL.cpp
@@ -299,17 +299,18 @@ public:
   virtual PaintState BeginPaint(ContentType aContentType, 
                                 float aXResolution, 
                                 float aYResolution)
   {
     // Let ThebesLayerBuffer do all the hard work for us! :D
     return ThebesLayerBuffer::BeginPaint(mLayer, 
                                          aContentType, 
                                          aXResolution, 
-                                         aYResolution);
+                                         aYResolution,
+                                         0);
   }
 
   // ThebesLayerBuffer interface
   virtual already_AddRefed<gfxASurface>
   CreateBuffer(ContentType aType, const nsIntSize& aSize)
   {
     NS_ASSERTION(gfxASurface::CONTENT_ALPHA != aType,"ThebesBuffer has color");
 
@@ -742,21 +743,23 @@ ThebesLayerOGL::RenderLayer(int aPreviou
       mOGLManager->GetThebesLayerCallback();
     if (!callback) {
       NS_ERROR("GL should never need to update ThebesLayers in an empty transaction");
     } else {
       void* callbackData = mOGLManager->GetThebesLayerCallbackData();
       SetAntialiasingFlags(this, state.mContext);
       callback(this, state.mContext, state.mRegionToDraw,
                state.mRegionToInvalidate, callbackData);
-      // Everything that's visible has been validated. Do this instead of
+      // Everything that's visible has been validated. Do this instead of just
       // OR-ing with aRegionToDraw, since that can lead to a very complex region
       // here (OR doesn't automatically simplify to the simplest possible
       // representation of a region.)
-      mValidRegion.Or(mValidRegion, mVisibleRegion);
+      nsIntRegion tmp;
+      tmp.Or(mVisibleRegion, state.mRegionToDraw);
+      mValidRegion.Or(mValidRegion, tmp);
     }
   }
 
   // Drawing thebes layers can change the current context, reset it.
   gl()->MakeCurrent();
 
   gl()->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, aPreviousFrameBuffer);
   mBuffer->RenderTo(aOffset, mOGLManager);