Bug 635373. ThebesLayerOGL needs to make sure we only sample valid pixels too. r=mattwoodrow
authorRobert O'Callahan <robert@ocallahan.org>
Fri, 01 Apr 2011 10:33:46 +1300
changeset 64538 844579d342003efcb2baaf3ec4d5bf0d63cae619
parent 64537 c686a9fb1b50ca9faf055b3edf79e48b7578ee6c
child 64539 bec3f251f53a43a08a13d31f190cf2877bc99f23
push idunknown
push userunknown
push dateunknown
reviewersmattwoodrow
bugs635373
milestone2.2a1pre
Bug 635373. ThebesLayerOGL needs to make sure we only sample valid pixels too. r=mattwoodrow
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
@@ -220,16 +220,24 @@ ThebesLayerBuffer::PaintState
 ThebesLayerBuffer::BeginPaint(ThebesLayer* aLayer, ContentType aContentType,
                               float aXResolution, float aYResolution,
                               PRUint32 aFlags)
 {
   PaintState result;
   result.mDidSelfCopy = PR_FALSE;
   float curXRes = aLayer->GetXResolution();
   float curYRes = aLayer->GetYResolution();
+  // 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;
 
   nsIntRegion validRegion = aLayer->GetValidRegion();
 
   ContentType contentType;
   nsIntRegion neededRegion;
   nsIntSize destBufferDims;
   PRBool canReuseBuffer;
   nsIntRect destBufferRect;
@@ -294,27 +302,20 @@ ThebesLayerBuffer::BeginPaint(ThebesLaye
 
   NS_ASSERTION(destBufferRect.Contains(neededRegion.GetBounds()),
                "Destination rect doesn't contain what we need to paint");
 
   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();
   nsRefPtr<gfxASurface> destBuffer;
   PRBool bufferDimsChanged = PR_FALSE;
+  PRUint32 bufferFlags = canHaveRotation ? ALLOW_REPEAT : 0;
   if (canReuseBuffer) {
     NS_ASSERTION(curXRes == aXResolution && curYRes == aYResolution,
                  "resolution changes must Clear()!");
 
     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
@@ -339,38 +340,36 @@ ThebesLayerBuffer::BeginPaint(ThebesLaye
           MovePixels(mBuffer, srcRect, dest, curXRes, curYRes);
           result.mDidSelfCopy = PR_TRUE;
           // Don't set destBuffer; we special-case self-copies, and
           // just did the necessary work above.
           mBufferRect = destBufferRect;
         } else {
           // We can't do a real self-copy because the buffer is rotated.
           // So allocate a new buffer for the destination.
-          destBufferRect = neededRegion.GetBounds();
           bufferDimsChanged = PR_TRUE;
-          destBuffer = CreateBuffer(contentType, destBufferDims);
+          destBuffer = CreateBuffer(contentType, destBufferDims, bufferFlags);
           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 = neededRegion.GetBounds();
     bufferDimsChanged = PR_TRUE;
-    destBuffer = CreateBuffer(contentType, destBufferDims);
+    destBuffer = CreateBuffer(contentType, destBufferDims, bufferFlags);
     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.
--- a/gfx/layers/ThebesLayerBuffer.h
+++ b/gfx/layers/ThebesLayerBuffer.h
@@ -138,21 +138,26 @@ public:
    * 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,
                         PRUint32 aFlags);
 
+  enum {
+    ALLOW_REPEAT = 0x01
+  };
   /**
    * Return a new surface of |aSize| and |aType|.
+   * @param aFlags if ALLOW_REPEAT is set, then the buffer should be configured
+   * to allow repeat-mode, otherwise it should be in pad (clamp) mode
    */
   virtual already_AddRefed<gfxASurface>
-  CreateBuffer(ContentType aType, const nsIntSize& aSize) = 0;
+  CreateBuffer(ContentType aType, const nsIntSize& aSize, PRUint32 aFlags) = 0;
 
   /**
    * Get the underlying buffer, if any. This is useful because we can pass
    * in the buffer as the default "reference surface" if there is one.
    * Don't use it for anything else!
    */
   gfxASurface* GetBuffer() { return mBuffer; }
 
--- a/gfx/layers/basic/BasicLayers.cpp
+++ b/gfx/layers/basic/BasicLayers.cpp
@@ -320,17 +320,17 @@ public:
   /**
    * Complete the drawing operation. The region to draw must have been
    * drawn before this is called. The contents of the buffer are drawn
    * to aTarget.
    */
   void DrawTo(ThebesLayer* aLayer, gfxContext* aTarget, float aOpacity);
 
   virtual already_AddRefed<gfxASurface>
-  CreateBuffer(ContentType aType, const nsIntSize& aSize);
+  CreateBuffer(ContentType aType, const nsIntSize& aSize, PRUint32 aFlags);
 
   /**
    * Swap out the old backing buffer for |aBuffer| and attributes.
    */
   void SetBackingBuffer(gfxASurface* aBuffer,
                         const nsIntRect& aRect, const nsIntPoint& aRotation)
   {
     gfxIntSize prevSize = gfxIntSize(BufferDims().width, BufferDims().height);
@@ -666,17 +666,17 @@ BasicThebesLayerBuffer::DrawTo(ThebesLay
   }
   DrawBufferWithRotation(aTarget, aOpacity,
                          aLayer->GetXResolution(), aLayer->GetYResolution());
   aTarget->Restore();
 }
 
 already_AddRefed<gfxASurface>
 BasicThebesLayerBuffer::CreateBuffer(ContentType aType, 
-                                     const nsIntSize& aSize)
+                                     const nsIntSize& aSize, PRUint32 aFlags)
 {
   return mLayer->CreateBuffer(aType, aSize);
 }
 
 void
 BasicThebesLayerBuffer::SetBackingBufferAndUpdateFrom(
   gfxASurface* aBuffer,
   gfxASurface* aSource, const nsIntRect& aRect, const nsIntPoint& aRotation,
--- a/gfx/layers/opengl/ThebesLayerOGL.cpp
+++ b/gfx/layers/opengl/ThebesLayerOGL.cpp
@@ -47,28 +47,34 @@
 #include "gfxTeeSurface.h"
 
 namespace mozilla {
 namespace layers {
 
 using gl::GLContext;
 using gl::TextureImage;
 
+static const int ALLOW_REPEAT = ThebesLayerBuffer::ALLOW_REPEAT;
+
 // BindAndDrawQuadWithTextureRect can work with either GL_REPEAT (preferred)
-// or GL_CLAMP_TO_EDGE textures.  We select based on whether REPEAT is
-// valid for non-power-of-two textures -- if we have NPOT support we use it,
-// otherwise we stick with CLAMP_TO_EDGE and decompose.
+// or GL_CLAMP_TO_EDGE textures. If ALLOW_REPEAT is set in aFlags, we
+// select based on whether REPEAT is valid for non-power-of-two textures --
+// if we have NPOT support we use it, otherwise we stick with CLAMP_TO_EDGE and
+// decompose.
+// If ALLOW_REPEAT is not set, we always use GL_CLAMP_TO_EDGE.
 static already_AddRefed<TextureImage>
 CreateClampOrRepeatTextureImage(GLContext *aGl,
                                 const nsIntSize& aSize,
-                                TextureImage::ContentType aContentType)
+                                TextureImage::ContentType aContentType,
+                                PRUint32 aFlags)
 {
   GLenum wrapMode = LOCAL_GL_CLAMP_TO_EDGE;
-  if (aGl->IsExtensionSupported(GLContext::ARB_texture_non_power_of_two) ||
-      aGl->IsExtensionSupported(GLContext::OES_texture_npot))
+  if ((aFlags & ALLOW_REPEAT) &&
+      (aGl->IsExtensionSupported(GLContext::ARB_texture_non_power_of_two) ||
+       aGl->IsExtensionSupported(GLContext::OES_texture_npot)))
   {
     wrapMode = LOCAL_GL_REPEAT;
   }
 
   return aGl->CreateTextureImage(aSize, aContentType, wrapMode);
 }
 
 // |aTexCoordRect| is the rectangle from the texture that we want to
@@ -158,19 +164,21 @@ public:
   typedef ThebesLayerBuffer::PaintState PaintState;
 
   ThebesLayerBufferOGL(ThebesLayer* aLayer, LayerOGL* aOGLLayer)
     : mLayer(aLayer)
     , mOGLLayer(aOGLLayer)
   {}
   virtual ~ThebesLayerBufferOGL() {}
 
+  enum { PAINT_WILL_RESAMPLE = ThebesLayerBuffer::PAINT_WILL_RESAMPLE };
   virtual PaintState BeginPaint(ContentType aContentType,
                                 float aXResolution,
-                                float aYResolution) = 0;
+                                float aYResolution,
+                                PRUint32 aFlags) = 0;
 
   void RenderTo(const nsIntPoint& aOffset, LayerManagerOGL* aManager);
 
   nsIntSize GetSize() {
     if (mTexImage)
       return mTexImage->GetSize();
     return nsIntSize(0, 0);
   }
@@ -235,17 +243,17 @@ ThebesLayerBufferOGL::RenderTo(const nsI
       alphaProgram->Activate();
       alphaProgram->SetBlackTextureUnit(0);
       alphaProgram->SetWhiteTextureUnit(1);
       program = alphaProgram;
     } else {
       // Note BGR: Cairo's image surfaces are always in what
       // OpenGL and our shaders consider BGR format.
       ColorTextureLayerProgram *basicProgram =
-        aManager->GetBasicLayerProgram(mLayer->CanUseOpaqueSurface(),
+        aManager->GetBasicLayerProgram(mTexImage->GetContentType() == gfxASurface::CONTENT_COLOR,
                                        mTexImage->IsRGB());
 
       basicProgram->Activate();
       basicProgram->SetTextureUnit(0);
       program = basicProgram;
     }
 
     program->SetLayerOpacity(mLayer->GetEffectiveOpacity());
@@ -293,33 +301,34 @@ public:
     , ThebesLayerBuffer(SizedToVisibleBounds)
   {
   }
   virtual ~SurfaceBufferOGL() {}
 
   // ThebesLayerBufferOGL interface
   virtual PaintState BeginPaint(ContentType aContentType, 
                                 float aXResolution, 
-                                float aYResolution)
+                                float aYResolution,
+                                PRUint32 aFlags)
   {
     // Let ThebesLayerBuffer do all the hard work for us! :D
     return ThebesLayerBuffer::BeginPaint(mLayer, 
                                          aContentType, 
                                          aXResolution, 
                                          aYResolution,
-                                         0);
+                                         aFlags);
   }
 
   // ThebesLayerBuffer interface
   virtual already_AddRefed<gfxASurface>
-  CreateBuffer(ContentType aType, const nsIntSize& aSize)
+  CreateBuffer(ContentType aType, const nsIntSize& aSize, PRUint32 aFlags)
   {
     NS_ASSERTION(gfxASurface::CONTENT_ALPHA != aType,"ThebesBuffer has color");
 
-    mTexImage = CreateClampOrRepeatTextureImage(gl(), aSize, aType);
+    mTexImage = CreateClampOrRepeatTextureImage(gl(), aSize, aType, aFlags);
     return mTexImage ? mTexImage->GetBackingSurface() : nsnull;
   }
 
 protected:
   virtual nsIntPoint GetOriginOffset() {
     return BufferRect().TopLeft() - BufferRotation();
   }
 };
@@ -336,17 +345,18 @@ public:
     : ThebesLayerBufferOGL(aLayer, aLayer)
     , mBufferRect(0,0,0,0)
     , mBufferRotation(0,0)
   {}
   virtual ~BasicBufferOGL() {}
 
   virtual PaintState BeginPaint(ContentType aContentType,
                                 float aXResolution,
-                                float aYResolution);
+                                float aYResolution,
+                                PRUint32 aFlags);
 
 protected:
   enum XSide {
     LEFT, RIGHT
   };
   enum YSide {
     TOP, BOTTOM
   };
@@ -403,143 +413,196 @@ ScaledSize(const nsIntSize& aSize, float
   nsIntRect rect(0, 0, aSize.width, aSize.height);
   rect.ScaleRoundOut(aXScale, aYScale);
   return rect.Size();
 }
 
 BasicBufferOGL::PaintState
 BasicBufferOGL::BeginPaint(ContentType aContentType,
                            float aXResolution,
-                           float aYResolution)
+                           float aYResolution,
+                           PRUint32 aFlags)
 {
   PaintState result;
-
-  result.mRegionToDraw.Sub(mLayer->GetVisibleRegion(), mLayer->GetValidRegion());
-  
   float curXRes = mLayer->GetXResolution();
   float curYRes = mLayer->GetYResolution();
-  Layer::SurfaceMode mode = mLayer->GetSurfaceMode();
+  // 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;
+
+  nsIntRegion validRegion = mLayer->GetValidRegion();
+
+  Layer::SurfaceMode mode;
+  ContentType contentType;
+  nsIntRegion neededRegion;
+  nsIntSize destBufferDims;
+  PRBool canReuseBuffer;
+  nsIntRect destBufferRect;
 
-  if (mode == Layer::SURFACE_COMPONENT_ALPHA) {
+  while (PR_TRUE) {
+    mode = mLayer->GetSurfaceMode();
+    contentType = aContentType;
+    neededRegion = mLayer->GetVisibleRegion();
+    destBufferDims = ScaledSize(neededRegion.GetBounds().Size(),
+                                aXResolution, aYResolution);
+    // If we're going to resample, we need a buffer that's in clamp mode.
+    canReuseBuffer = neededRegion.GetBounds().Size() <= mBufferRect.Size() &&
+      mTexImage &&
+      (!(aFlags & PAINT_WILL_RESAMPLE) ||
+       mTexImage->GetWrapMode() == LOCAL_GL_CLAMP_TO_EDGE);
+
+    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 (mode == Layer::SURFACE_COMPONENT_ALPHA) {
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
-    mode = Layer::SURFACE_SINGLE_CHANNEL_ALPHA;
+      mode = Layer::SURFACE_SINGLE_CHANNEL_ALPHA;
 #else
-    if (!mLayer->GetParent() || !mLayer->GetParent()->SupportsComponentAlphaChildren()) {
-      mode = Layer::SURFACE_SINGLE_CHANNEL_ALPHA;
-    } else {
-      aContentType = gfxASurface::CONTENT_COLOR;
+      if (!mLayer->GetParent() || !mLayer->GetParent()->SupportsComponentAlphaChildren()) {
+        mode = Layer::SURFACE_SINGLE_CHANNEL_ALPHA;
+      } else {
+        contentType = gfxASurface::CONTENT_COLOR;
+      }
+ #endif
+    }
+ 
+    if ((aFlags & PAINT_WILL_RESAMPLE) &&
+        (neededRegion.GetBounds() != destBufferRect ||
+         neededRegion.GetNumRects() > 1)) {
+      // The area we add to neededRegion might not be painted opaquely
+      if (mode == Layer::SURFACE_OPAQUE) {
+        contentType = gfxASurface::CONTENT_COLOR_ALPHA;
+        mode = Layer::SURFACE_SINGLE_CHANNEL_ALPHA;
+      }
+      // For component alpha layers, we leave contentType as CONTENT_COLOR.
+
+      // 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);
     }
-#endif
+
+    if (mTexImage &&
+        (mTexImage->GetContentType() != contentType ||
+         aXResolution != curXRes || aYResolution != curYRes ||
+         (mode == Layer::SURFACE_COMPONENT_ALPHA) != (mTexImageOnWhite != nsnull))) {
+      // 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 = mLayer->GetValidRegion();
+      validRegion.SetEmpty();
+      mTexImage = nsnull;
+      mTexImageOnWhite = nsnull;
+      mBufferRect.SetRect(0, 0, 0, 0);
+      mBufferRotation.MoveTo(0, 0);
+      // Restart decision process with the cleared buffer. We can only go
+      // around the loop one more iteration, since mTexImage is null now.
+      continue;
+    }
+
+    break;
   }
 
-  if (!mTexImage || mTexImage->GetContentType() != aContentType ||
-      aXResolution != curXRes || aYResolution != curYRes ||
-      (mode == Layer::SURFACE_COMPONENT_ALPHA) != (mTexImageOnWhite != nsnull)) {
-    // 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 = mLayer->GetVisibleRegion();
-    result.mRegionToInvalidate = mLayer->GetValidRegion();
-    mTexImage = nsnull;
-    mTexImageOnWhite = nsnull;
-    mBufferRect.SetRect(0, 0, 0, 0);
-    mBufferRotation.MoveTo(0, 0);
-  }
-
+  result.mRegionToDraw.Sub(neededRegion, validRegion);
   if (result.mRegionToDraw.IsEmpty())
     return result;
 
-  nsIntRect visibleBounds = mLayer->GetVisibleRegion().GetBounds();
-  nsIntRect drawBounds = result.mRegionToDraw.GetBounds();
-  nsIntSize destBufferDims = ScaledSize(visibleBounds.Size(),
-                                        aXResolution, aYResolution);
-  
   if (destBufferDims.width > gl()->GetMaxTextureSize() ||
       destBufferDims.height > gl()->GetMaxTextureSize()) {
     return result;
   }
 
+  nsIntRect drawBounds = result.mRegionToDraw.GetBounds();
   nsRefPtr<TextureImage> destBuffer;
   nsRefPtr<TextureImage> destBufferOnWhite;
-  nsIntRect destBufferRect;
 
-  if (visibleBounds.Size() <= mBufferRect.Size()) {
+  PRUint32 bufferFlags = canHaveRotation ? ALLOW_REPEAT : 0;
+  if (canReuseBuffer) {
     NS_ASSERTION(curXRes == aXResolution && curYRes == aYResolution,
                  "resolution changes must clear the buffer!");
-    // 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 mBufferRotation == nsIntPoint(0,0) we could do a real
         // self-copy but we're not going to do that in GL yet.
         // We can't do a real self-copy because the buffer is rotated.
         // So allocate a new buffer for the destination.
-        destBufferRect = visibleBounds;
-        destBuffer = CreateClampOrRepeatTextureImage(gl(), destBufferDims, aContentType);
+        destBuffer = CreateClampOrRepeatTextureImage(gl(), destBufferDims, contentType, bufferFlags);
         if (!destBuffer)
           return result;
         if (mode == Layer::SURFACE_COMPONENT_ALPHA) {
           destBufferOnWhite =
-            CreateClampOrRepeatTextureImage(gl(), destBufferDims, aContentType);
+            CreateClampOrRepeatTextureImage(gl(), destBufferDims, contentType, bufferFlags);
           if (!destBufferOnWhite)
             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;
-    destBuffer = CreateClampOrRepeatTextureImage(gl(), destBufferDims, aContentType);
+    destBuffer = CreateClampOrRepeatTextureImage(gl(), destBufferDims, contentType, bufferFlags);
     if (!destBuffer)
       return result;
 
     if (mode == Layer::SURFACE_COMPONENT_ALPHA) {
       destBufferOnWhite = 
-        CreateClampOrRepeatTextureImage(gl(), destBufferDims, aContentType);
+        CreateClampOrRepeatTextureImage(gl(), destBufferDims, contentType, bufferFlags);
       if (!destBufferOnWhite)
         return result;
     }
   }
+  NS_ASSERTION(!(aFlags & PAINT_WILL_RESAMPLE) || destBufferRect == neededRegion.GetBounds(),
+               "If we're resampling, we need to validate the entire buffer");
 
   if (!destBuffer && !mTexImage) {
     return result;
   }
 
   if (destBuffer) {
     if (mTexImage && (mode != Layer::SURFACE_COMPONENT_ALPHA || mTexImageOnWhite)) {
       // BlitTextureImage depends on the FBO texture target being
@@ -561,32 +624,33 @@ BasicBufferOGL::BeginPaint(ContentType a
                                destBuffer, dstRect);
         if (mode == Layer::SURFACE_COMPONENT_ALPHA) {
           destBufferOnWhite->Resize(size);
           gl()->BlitTextureImage(mTexImageOnWhite, srcRect,
                                  destBufferOnWhite, dstRect);
         }
       } else {
         // can't blit, just draw everything
-        destBufferRect = visibleBounds;
-        destBuffer = CreateClampOrRepeatTextureImage(gl(), destBufferDims, aContentType);
+        destBuffer = CreateClampOrRepeatTextureImage(gl(), destBufferDims, contentType, bufferFlags);
         if (mode == Layer::SURFACE_COMPONENT_ALPHA) {
           destBufferOnWhite = 
-            CreateClampOrRepeatTextureImage(gl(), destBufferDims, aContentType);
+            CreateClampOrRepeatTextureImage(gl(), destBufferDims, contentType, bufferFlags);
         }
       }
     }
 
     mTexImage = destBuffer.forget();
     if (mode == Layer::SURFACE_COMPONENT_ALPHA) {
       mTexImageOnWhite = destBufferOnWhite.forget();
     }
     mBufferRect = destBufferRect;
     mBufferRotation = nsIntPoint(0,0);
   }
+  NS_ASSERTION(canHaveRotation || mBufferRotation == nsIntPoint(0,0),
+               "Rotation disabled, but we have nonzero rotation?");
 
   nsIntRegion invalidate;
   invalidate.Sub(mLayer->GetValidRegion(), destBufferRect);
   result.mRegionToInvalidate.Or(result.mRegionToInvalidate, invalidate);
 
   // Figure out which quadrant to draw in
   PRInt32 xBoundary = mBufferRect.XMost() - mBufferRotation.x;
   PRInt32 yBoundary = mBufferRect.YMost() - mBufferRotation.y;
@@ -626,17 +690,17 @@ BasicBufferOGL::BeginPaint(ContentType a
     // be incorrect.
     surf->SetAllowUseAsSource(PR_FALSE);
     result.mContext = new gfxContext(surf);
   } else {
     result.mContext = new gfxContext(mTexImage->BeginUpdate(result.mRegionToDraw));
     if (mTexImage->GetContentType() == gfxASurface::CONTENT_COLOR_ALPHA) {
       gfxUtils::ClipToRegion(result.mContext, result.mRegionToDraw);
       result.mContext->SetOperator(gfxContext::OPERATOR_CLEAR);
-      result.mContext->Fill();
+      result.mContext->Paint();
       result.mContext->SetOperator(gfxContext::OPERATOR_OVER);
     }
   }
   if (!result.mContext) {
     NS_WARNING("unable to get context for update");
     return result;
   }
   result.mContext->Scale(aXResolution, aYResolution);
@@ -717,26 +781,35 @@ ThebesLayerOGL::RenderLayer(int aPreviou
 
   mOGLManager->MakeCurrent();
   gl()->fActiveTexture(LOCAL_GL_TEXTURE0);
 
   TextureImage::ContentType contentType =
     CanUseOpaqueSurface() ? gfxASurface::CONTENT_COLOR :
                             gfxASurface::CONTENT_COLOR_ALPHA;
 
-  const gfx3DMatrix& transform = GetEffectiveTransform();
   gfxMatrix transform2d;
   gfxSize scale(1.0, 1.0);
-  if (transform.Is2D(&transform2d)) {
+  float paintXRes = 1.0;
+  float paintYRes = 1.0;
+  PRUint32 flags = 0;
+  if (GetEffectiveTransform().Is2D(&transform2d)) {
     scale = transform2d.ScaleFactors(PR_TRUE);
+    paintXRes = gfxUtils::ClampToScaleFactor(scale.width);
+    paintYRes = gfxUtils::ClampToScaleFactor(scale.height);
+    transform2d.Scale(1.0/paintXRes, 1.0/paintYRes);
+    if (transform2d.HasNonIntegerTranslation()) {
+      flags |= ThebesLayerBufferOGL::PAINT_WILL_RESAMPLE;
+    }
+  } else {
+    flags |= ThebesLayerBufferOGL::PAINT_WILL_RESAMPLE;
   }
-  float paintXRes = gfxUtils::ClampToScaleFactor(scale.width);
-  float paintYRes = gfxUtils::ClampToScaleFactor(scale.height);
 
-  Buffer::PaintState state = mBuffer->BeginPaint(contentType, paintXRes, paintYRes);
+  Buffer::PaintState state =
+    mBuffer->BeginPaint(contentType, paintXRes, paintYRes, flags);
   mValidRegion.Sub(mValidRegion, state.mRegionToInvalidate);
 
   if (state.mContext) {
     state.mRegionToInvalidate.And(state.mRegionToInvalidate, mVisibleRegion);
     mXResolution = paintXRes;
     mYResolution = paintYRes;
 
     LayerManager::DrawThebesLayerCallback callback =
@@ -782,29 +855,22 @@ ThebesLayerOGL::IsEmpty()
 
 class ShadowBufferOGL : public ThebesLayerBufferOGL
 {
 public:
   ShadowBufferOGL(ShadowThebesLayerOGL* aLayer)
     : ThebesLayerBufferOGL(aLayer, aLayer)
   {}
 
-  virtual PaintState BeginPaint(ContentType aContentType, float, float) {
+  virtual PaintState BeginPaint(ContentType aContentType,
+                                float, float, PRUint32) {
     NS_RUNTIMEABORT("can't BeginPaint for a shadow layer");
     return PaintState();
   }
 
-  void
-  CreateTexture(ContentType aType, const nsIntSize& aSize)
-  {
-    NS_ASSERTION(gfxASurface::CONTENT_ALPHA != aType,"ThebesBuffer has color");
-
-    mTexImage = CreateClampOrRepeatTextureImage(gl(), aSize, aType);
-  }
-
   void Upload(gfxASurface* aUpdate, const nsIntRegion& aUpdated,
               const nsIntRect& aRect, const nsIntPoint& aRotation);
 
 protected:
   virtual nsIntPoint GetOriginOffset() {
     return mBufferRect.TopLeft() - mBufferRotation;
   }
 
@@ -814,18 +880,20 @@ private:
 };
 
 void
 ShadowBufferOGL::Upload(gfxASurface* aUpdate, const nsIntRegion& aUpdated,
                         const nsIntRect& aRect, const nsIntPoint& aRotation)
 {
   gfxIntSize size = aUpdate->GetSize();
   if (GetSize() != nsIntSize(size.width, size.height)) {
-    CreateTexture(aUpdate->GetContentType(),
-                  nsIntSize(size.width, size.height));
+    // XXX we should do something here to decide whether to use REPEAT or not,
+    // but I'm not sure what
+    mTexImage = CreateClampOrRepeatTextureImage(gl(),
+      nsIntSize(size.width, size.height), aUpdate->GetContentType(), ALLOW_REPEAT);
   }
 
   nsIntRegion destRegion(aUpdated);
   // aUpdated is in screen coordinates.  Move it so that the layer's
   // top-left is 0,0
   nsIntPoint visTopLeft = mLayer->GetVisibleRegion().GetBounds().TopLeft();
   destRegion.MoveBy(-visTopLeft);