Bug 635373. If we need to resample a ThebesLayer texture, make sure we make all the pixels of the texture valid in case they get sampled. r=bas
authorRobert O'Callahan <robert@ocallahan.org>
Fri, 01 Apr 2011 10:33:46 +1300
changeset 64536 4096a34495a596e838926ab635f9a10766104995
parent 64535 e36b387929f0e67b070a87f1d4b4cd82b8af0e34
child 64537 c686a9fb1b50ca9faf055b3edf79e48b7578ee6c
push idunknown
push userunknown
push dateunknown
reviewersbas
bugs635373
milestone2.2a1pre
Bug 635373. If we need to resample a ThebesLayer texture, make sure we make all the pixels of the texture valid in case they get sampled. r=bas
gfx/layers/d3d10/ThebesLayerD3D10.cpp
gfx/layers/d3d10/ThebesLayerD3D10.h
gfx/layers/d3d9/ThebesLayerD3D9.cpp
--- a/gfx/layers/d3d10/ThebesLayerD3D10.cpp
+++ b/gfx/layers/d3d10/ThebesLayerD3D10.cpp
@@ -49,16 +49,17 @@
 #include "ReadbackProcessor.h"
 
 namespace mozilla {
 namespace layers {
 
 ThebesLayerD3D10::ThebesLayerD3D10(LayerManagerD3D10 *aManager)
   : ThebesLayer(aManager, NULL)
   , LayerD3D10(aManager)
+  , mCurrentSurfaceMode(SURFACE_OPAQUE)
 {
   mImplData = static_cast<LayerD3D10*>(this);
 }
 
 ThebesLayerD3D10::~ThebesLayerD3D10()
 {
 }
 
@@ -114,30 +115,34 @@ void ThebesLayerD3D10::CopyRegion(ID3D10
 
 void
 ThebesLayerD3D10::RenderLayer()
 {
   if (!mTexture) {
     return;
   }
 
-  nsIntRect visibleRect = mVisibleRegion.GetBounds();
-
   SetEffectTransformAndOpacity();
 
   ID3D10EffectTechnique *technique;
-  if (mTextureOnWhite) {
+  switch (mCurrentSurfaceMode) {
+  case SURFACE_COMPONENT_ALPHA:
     technique = effect()->GetTechniqueByName("RenderComponentAlphaLayer");
-  } else if (CanUseOpaqueSurface()) {
+    break;
+  case SURFACE_OPAQUE:
     technique = effect()->GetTechniqueByName("RenderRGBLayerPremul");
-  } else {
+    break;
+  case SURFACE_SINGLE_CHANNEL_ALPHA:
     technique = effect()->GetTechniqueByName("RenderRGBALayerPremul");
+    break;
+  default:
+    NS_ERROR("Unknown mode");
+    return;
   }
 
-
   nsIntRegionRectIterator iter(mVisibleRegion);
 
   const nsIntRect *iterRect;
   if (mSRView) {
     effect()->GetVariableByName("tRGB")->AsShaderResource()->SetResource(mSRView);
   }
   if (mSRViewOnWhite) {
     effect()->GetVariableByName("tRGBWhite")->AsShaderResource()->SetResource(mSRViewOnWhite);
@@ -149,20 +154,20 @@ ThebesLayerD3D10::RenderLayer()
         (float)iterRect->x,
         (float)iterRect->y,
         (float)iterRect->width,
         (float)iterRect->height)
       );
 
     effect()->GetVariableByName("vTextureCoords")->AsVector()->SetFloatVector(
       ShaderConstantRectD3D10(
-        (float)(iterRect->x - visibleRect.x) / (float)visibleRect.width,
-        (float)(iterRect->y - visibleRect.y) / (float)visibleRect.height,
-        (float)iterRect->width / (float)visibleRect.width,
-        (float)iterRect->height / (float)visibleRect.height)
+        (float)(iterRect->x - mTextureRect.x) / (float)mTextureRect.width,
+        (float)(iterRect->y - mTextureRect.y) / (float)mTextureRect.height,
+        (float)iterRect->width / (float)mTextureRect.width,
+        (float)iterRect->height / (float)mTextureRect.height)
       );
 
     technique->GetPassByIndex(0)->Apply(0);
     device()->Draw(4, 0);
   }
 
   // Set back to default.
   effect()->GetVariableByName("vTextureCoords")->AsVector()->
@@ -171,21 +176,44 @@ ThebesLayerD3D10::RenderLayer()
 
 void
 ThebesLayerD3D10::Validate(ReadbackProcessor *aReadback)
 {
   if (mVisibleRegion.IsEmpty()) {
     return;
   }
 
+  nsIntRect newTextureRect = mVisibleRegion.GetBounds();
+
   SurfaceMode mode = GetSurfaceMode();
   if (mode == SURFACE_COMPONENT_ALPHA &&
       (!mParent || !mParent->SupportsComponentAlphaChildren())) {
     mode = SURFACE_SINGLE_CHANNEL_ALPHA;
   }
+  // If we have a transform that requires resampling of our texture, then
+  // we need to make sure we don't sample pixels that haven't been drawn.
+  // We clamp sample coordinates to the texture rect, but when the visible region
+  // doesn't fill the entire texture rect we need to make sure we draw all the
+  // pixels in the texture rect anyway in case they get sampled.
+  nsIntRegion neededRegion = mVisibleRegion;
+  if (neededRegion.GetBounds() != newTextureRect ||
+      neededRegion.GetNumRects() > 1) {
+    gfxMatrix transform2d;
+    if (!GetEffectiveTransform().Is2D(&transform2d) ||
+        transform2d.HasNonIntegerTranslation()) {
+      neededRegion = newTextureRect;
+      if (mode == SURFACE_OPAQUE) {
+        // We're going to paint outside the visible region, but layout hasn't
+        // promised that it will paint opaquely there, so we'll have to
+        // treat this layer as transparent.
+        mode = SURFACE_SINGLE_CHANNEL_ALPHA;
+      }
+    }
+  }
+  mCurrentSurfaceMode = mode;
 
   VerifyContentType(mode);
 
   float xres, yres;
   GetDesiredResolutions(xres, yres);
 
   // If our resolution changed, we need new sized textures, delete the old ones.
   if (ResolutionChanged(xres, yres)) {
@@ -194,107 +222,96 @@ ThebesLayerD3D10::Validate(ReadbackProce
   }
 
   nsTArray<ReadbackProcessor::Update> readbackUpdates;
   nsIntRegion readbackRegion;
   if (aReadback && UsedForReadback()) {
     aReadback->GetThebesLayerUpdates(this, &readbackUpdates, &readbackRegion);
   }
 
-  nsIntRect visibleRect = mVisibleRegion.GetBounds();
-
   if (mTexture) {
-    if (!mTextureRegion.IsEqual(mVisibleRegion)) {
+    if (mTextureRect != newTextureRect) {
       nsRefPtr<ID3D10Texture2D> oldTexture = mTexture;
       mTexture = nsnull;
       nsRefPtr<ID3D10Texture2D> oldTextureOnWhite = mTextureOnWhite;
       mTextureOnWhite = nsnull;
 
-      nsIntRegion retainRegion = mTextureRegion;
-      nsIntRect oldBounds = mTextureRegion.GetBounds();
-      nsIntRect newBounds = mVisibleRegion.GetBounds();
-
-      CreateNewTextures(gfxIntSize(newBounds.width, newBounds.height), mode);
-
+      nsIntRegion retainRegion = mTextureRect;
       // Old visible region will become the region that is covered by both the
       // old and the new visible region.
       retainRegion.And(retainRegion, mVisibleRegion);
       // No point in retaining parts which were not valid.
       retainRegion.And(retainRegion, mValidRegion);
 
+      CreateNewTextures(gfxIntSize(newTextureRect.width, newTextureRect.height), mode);
+
       nsIntRect largeRect = retainRegion.GetLargestRectangle();
 
       // If we had no hardware texture before, have no retained area larger than
       // the retention threshold or the requested resolution has changed, 
       // we're not retaining and are done here. If our texture creation failed 
       // this can mean a device reset is pending and we should silently ignore the   
       // failure. In the future when device failures are properly handled we 
       // should test for the type of failure and gracefully handle different 
       // failures. See bug 569081.
       if (!oldTexture || !mTexture ||
           largeRect.width * largeRect.height < RETENTION_THRESHOLD ||
           ResolutionChanged(xres, yres)) {
         mValidRegion.SetEmpty();
       } else {
-        CopyRegion(oldTexture, oldBounds.TopLeft(),
-                   mTexture, newBounds.TopLeft(),
+        CopyRegion(oldTexture, mTextureRect.TopLeft(),
+                   mTexture, newTextureRect.TopLeft(),
                    retainRegion, &mValidRegion,
                    xres, yres);
         if (oldTextureOnWhite) {
-          CopyRegion(oldTextureOnWhite, oldBounds.TopLeft(),
-                     mTextureOnWhite, newBounds.TopLeft(),
+          CopyRegion(oldTextureOnWhite, mTextureRect.TopLeft(),
+                     mTextureOnWhite, newTextureRect.TopLeft(),
                      retainRegion, &mValidRegion,
                      xres, yres);
         }
       }
     }
   }
-  mTextureRegion = mVisibleRegion;
+  mTextureRect = newTextureRect;
 
   if (!mTexture || (mode == SURFACE_COMPONENT_ALPHA && !mTextureOnWhite)) {
-    CreateNewTextures(gfxIntSize(visibleRect.width, visibleRect.height), mode);
+    CreateNewTextures(gfxIntSize(newTextureRect.width, newTextureRect.height), mode);
     mValidRegion.SetEmpty();
   }
 
-  if (!mValidRegion.IsEqual(mVisibleRegion)) {
+  nsIntRegion drawRegion;
+  drawRegion.Sub(neededRegion, mValidRegion);
+
+  if (!drawRegion.IsEmpty()) {
     LayerManagerD3D10::CallbackInfo cbInfo = mD3DManager->GetCallbackInfo();
     if (!cbInfo.Callback) {
       NS_ERROR("D3D10 should never need to update ThebesLayers in an empty transaction");
       return;
     }
 
-    /* We use the bounds of the visible region because we draw the bounds of
-     * this region when we draw this entire texture. We have to make sure that
-     * the areas that aren't filled with content get their background drawn.
-     * This is an issue for opaque surfaces, which otherwise won't get their
-     * background painted.
-     */
-    nsIntRegion region;
-    region.Sub(mVisibleRegion, mValidRegion);
-
-    DrawRegion(region, mode);
+    DrawRegion(drawRegion, mode);
 
     if (readbackUpdates.Length() > 0) {
       CD3D10_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM,
-                                 visibleRect.width, visibleRect.height,
+                                 newTextureRect.width, newTextureRect.height,
                                  1, 1, 0, D3D10_USAGE_STAGING,
                                  D3D10_CPU_ACCESS_READ);
 
       nsRefPtr<ID3D10Texture2D> readbackTexture;
       device()->CreateTexture2D(&desc, NULL, getter_AddRefs(readbackTexture));
       device()->CopyResource(readbackTexture, mTexture);
 
       for (int i = 0; i < readbackUpdates.Length(); i++) {
         mD3DManager->readbackManager()->PostTask(readbackTexture,
                                                  &readbackUpdates[i],
-                                                 gfxPoint(visibleRect.x, visibleRect.y));
+                                                 gfxPoint(newTextureRect.x, newTextureRect.y));
       }
     }
 
-    mValidRegion = mVisibleRegion;
+    mValidRegion = neededRegion;
   }
 }
 
 void
 ThebesLayerD3D10::LayerManagerDestroyed()
 {
   mD3DManager = nsnull;
 }
--- a/gfx/layers/d3d10/ThebesLayerD3D10.h
+++ b/gfx/layers/d3d10/ThebesLayerD3D10.h
@@ -70,18 +70,21 @@ private:
   nsRefPtr<ID3D10ShaderResourceView> mSRView;
 
   /* Texture for render-on-whitew when doing component alpha */
   nsRefPtr<ID3D10Texture2D> mTextureOnWhite;
 
   /* Shader resource view for our render-on-white texture */
   nsRefPtr<ID3D10ShaderResourceView> mSRViewOnWhite;
 
-  /* Visible region used when we drew the contents of the textures */
-  nsIntRegion mTextureRegion;
+  /* Area of layer currently stored in texture(s) */
+  nsIntRect mTextureRect;
+
+  /* Last surface mode set in Validate() */
+  SurfaceMode mCurrentSurfaceMode;
 
   /* Checks if our D2D surface has the right content type */
   void VerifyContentType(SurfaceMode aMode);
 
   /* This contains the thebes surface */
   nsRefPtr<gfxASurface> mD2DSurface;
 
   /* This contains the thebes surface for our render-on-white texture */
--- a/gfx/layers/d3d9/ThebesLayerD3D9.cpp
+++ b/gfx/layers/d3d9/ThebesLayerD3D9.cpp
@@ -203,21 +203,44 @@ ThebesLayerD3D9::RenderVisibleRegion()
 
 void
 ThebesLayerD3D9::RenderThebesLayer(ReadbackProcessor* aReadback)
 {
   if (mVisibleRegion.IsEmpty()) {
     return;
   }
 
+  nsIntRect newTextureRect = mVisibleRegion.GetBounds();
+
   SurfaceMode mode = GetSurfaceMode();
   if (mode == SURFACE_COMPONENT_ALPHA &&
       (!mParent || !mParent->SupportsComponentAlphaChildren())) {
     mode = SURFACE_SINGLE_CHANNEL_ALPHA;
   }
+  // If we have a transform that requires resampling of our texture, then
+  // we need to make sure we don't sample pixels that haven't been drawn.
+  // We clamp sample coordinates to the texture rect, but when the visible region
+  // doesn't fill the entire texture rect we need to make sure we draw all the
+  // pixels in the texture rect anyway in case they get sampled.
+  nsIntRegion neededRegion = mVisibleRegion;
+  if (neededRegion.GetBounds() != newTextureRect ||
+      neededRegion.GetNumRects() > 1) {
+    gfxMatrix transform2d;
+    if (!GetEffectiveTransform().Is2D(&transform2d) ||
+        transform2d.HasNonIntegerTranslation()) {
+      neededRegion = newTextureRect;
+      if (mode == SURFACE_OPAQUE) {
+        // We're going to paint outside the visible region, but layout hasn't
+        // promised that it will paint opaquely there, so we'll have to
+        // treat this layer as transparent.
+        mode = SURFACE_SINGLE_CHANNEL_ALPHA;
+      }
+    }
+  }
+
   VerifyContentType(mode);
   UpdateTextures(mode);
   if (!HaveTextures(mode)) {
     NS_WARNING("Texture creation failed");
     return;
   }
 
   nsTArray<ReadbackProcessor::Update> readbackUpdates;
@@ -227,30 +250,30 @@ ThebesLayerD3D9::RenderThebesLayer(Readb
   }
 
   // Because updates to D3D9 ThebesLayers are rendered with the CPU, we don't
   // have to do readback from D3D9 surfaces. Instead we make sure that any area
   // needed for readback is included in the drawRegion we ask layout to render.
   // Then the readback areas we need can be copied out of the temporary
   // destinationSurface in DrawRegion.
   nsIntRegion drawRegion;
-  drawRegion.Sub(mVisibleRegion, mValidRegion);
+  drawRegion.Sub(neededRegion, mValidRegion);
   drawRegion.Or(drawRegion, readbackRegion);
   // NS_ASSERTION(mVisibleRegion.Contains(region), "Bad readback region!");
 
   if (!drawRegion.IsEmpty()) {
     LayerManagerD3D9::CallbackInfo cbInfo = mD3DManager->GetCallbackInfo();
     if (!cbInfo.Callback) {
       NS_ERROR("D3D9 should never need to update ThebesLayers in an empty transaction");
       return;
     }
 
     DrawRegion(drawRegion, mode, readbackUpdates);
 
-    mValidRegion = mVisibleRegion;
+    mValidRegion = neededRegion;
   }
 
   SetShaderTransformAndOpacity();
 
   if (mode == SURFACE_COMPONENT_ALPHA) {
     mD3DManager->SetShaderMode(DeviceManagerD3D9::COMPONENTLAYERPASS1);
     device()->SetTexture(0, mTexture);
     device()->SetTexture(1, mTextureOnWhite);