Bug 593604. Part 9: Support component alpha in ThebesLayerD3D9. r=bas,a=blocking
authorRobert O'Callahan <robert@ocallahan.org>
Mon, 03 Jan 2011 14:48:09 +1300
changeset 59775 b4e0f47b628df4c655025dc9bdfe8364857577cd
parent 59774 02c4dac4ae7d9653ad485c5b0a275cb8e2437f8e
child 59776 58aa602af75c43a9887d5ef949b12876a5fac30d
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersbas, blocking
bugs593604
milestone2.0b9pre
Bug 593604. Part 9: Support component alpha in ThebesLayerD3D9. r=bas,a=blocking
gfx/layers/d3d9/ThebesLayerD3D9.cpp
gfx/layers/d3d9/ThebesLayerD3D9.h
gfx/src/nsRect.h
--- a/gfx/layers/d3d9/ThebesLayerD3D9.cpp
+++ b/gfx/layers/d3d9/ThebesLayerD3D9.cpp
@@ -37,16 +37,18 @@
 
 #include "ThebesLayerD3D9.h"
 #include "gfxPlatform.h"
 
 #include "gfxWindowsPlatform.h"
 #ifdef CAIRO_HAS_D2D_SURFACE
 #include "gfxD2DSurface.h"
 #endif
+#include "gfxTeeSurface.h"
+#include "gfxUtils.h"
 
 namespace mozilla {
 namespace layers {
 
 ThebesLayerD3D9::ThebesLayerD3D9(LayerManagerD3D9 *aManager)
   : ThebesLayer(aManager, NULL)
   , LayerD3D9(aManager)
   , mD2DSurfaceInitialized(false)
@@ -65,213 +67,211 @@ ThebesLayerD3D9::~ThebesLayerD3D9()
 /**
  * Retention threshold - amount of pixels intersection required to enable
  * layer content retention. This is a guesstimate. Profiling could be done to
  * figure out the optimal threshold.
  */
 #define RETENTION_THRESHOLD 16384
 
 void
-ThebesLayerD3D9::SetVisibleRegion(const nsIntRegion &aRegion)
+ThebesLayerD3D9::InvalidateRegion(const nsIntRegion &aRegion)
 {
-  if (aRegion.IsEqual(mVisibleRegion)) {
-    return;
-  }
-
-  nsIntRegion oldVisibleRegion = mVisibleRegion;
-  ThebesLayer::SetVisibleRegion(aRegion);
-
-  if (!mTexture) {
-    // If we don't need to retain content initialize lazily. This is good also
-    // because we might get mIsOpaqueSurface set later than the first call to
-    // SetVisibleRegion.
-    return;
-  }
-
-  D3DFORMAT fmt = (CanUseOpaqueSurface() && !mD2DSurface) ?
-                    D3DFMT_X8R8G8B8 : D3DFMT_A8R8G8B8;
-
-  D3DSURFACE_DESC desc;
-  mTexture->GetLevelDesc(0, &desc);
-
-  if (fmt != desc.Format) {
-    // The new format isn't compatible with the old texture, toss out the old
-    // texture.
-    mTexture = nsnull;
-  }
-
-  VerifyContentType();
+  mValidRegion.Sub(mValidRegion, aRegion);
+}
 
-  nsRefPtr<IDirect3DTexture9> oldTexture = mTexture;
-
-  nsIntRect oldBounds = oldVisibleRegion.GetBounds();
-  nsIntRect newBounds = mVisibleRegion.GetBounds();
-
-  CreateNewTexture(gfxIntSize(newBounds.width, newBounds.height));
-
-  // Old visible region will become the region that is covered by both the
-  // old and the new visible region.
-  oldVisibleRegion.And(oldVisibleRegion, mVisibleRegion);
-  // No point in retaining parts which were not valid.
-  oldVisibleRegion.And(oldVisibleRegion, mValidRegion);
-
-  nsIntRect largeRect = oldVisibleRegion.GetLargestRectangle();
-
-  // If we had no hardware texture before or have no retained area larger than
-  // the retention threshold, 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) {
-    mValidRegion.SetEmpty();
-    return;
-  }
-
+void
+ThebesLayerD3D9::CopyRegion(IDirect3DTexture9* aSrc, const nsIntPoint &aSrcOffset,
+                            IDirect3DTexture9* aDest, const nsIntPoint &aDestOffset,
+                            const nsIntRegion &aCopyRegion, nsIntRegion* aValidRegion)
+{
   nsRefPtr<IDirect3DSurface9> srcSurface, dstSurface;
-  oldTexture->GetSurfaceLevel(0, getter_AddRefs(srcSurface));
-  mTexture->GetSurfaceLevel(0, getter_AddRefs(dstSurface));
+  aSrc->GetSurfaceLevel(0, getter_AddRefs(srcSurface));
+  aDest->GetSurfaceLevel(0, getter_AddRefs(dstSurface));
 
   nsIntRegion retainedRegion;
-  nsIntRegionRectIterator iter(oldVisibleRegion);
+  nsIntRegionRectIterator iter(aCopyRegion);
   const nsIntRect *r;
   while ((r = iter.Next())) {
     if (r->width * r->height > RETENTION_THRESHOLD) {
       RECT oldRect, newRect;
 
       // Calculate the retained rectangle's position on the old and the new
       // surface.
-      oldRect.left = r->x - oldBounds.x;
-      oldRect.top = r->y - oldBounds.y;
+      oldRect.left = r->x - aSrcOffset.x;
+      oldRect.top = r->y - aSrcOffset.y;
       oldRect.right = oldRect.left + r->width;
       oldRect.bottom = oldRect.top + r->height;
 
-      newRect.left = r->x - newBounds.x;
-      newRect.top = r->y - newBounds.y;
+      newRect.left = r->x - aDestOffset.x;
+      newRect.top = r->y - aDestOffset.y;
       newRect.right = newRect.left + r->width;
       newRect.bottom = newRect.top + r->height;
 
       // Copy data from our old texture to the new one
       HRESULT hr = device()->
         StretchRect(srcSurface, &oldRect, dstSurface, &newRect, D3DTEXF_NONE);
 
       if (SUCCEEDED(hr)) {
         retainedRegion.Or(retainedRegion, *r);
       }
     }
   }
 
   // Areas which were valid and were retained are still valid
-  mValidRegion.And(mValidRegion, retainedRegion);  
+  aValidRegion->And(*aValidRegion, retainedRegion);
 }
 
-
-void
-ThebesLayerD3D9::InvalidateRegion(const nsIntRegion &aRegion)
+static PRUint64 RectArea(const nsIntRect& aRect)
 {
-  mValidRegion.Sub(mValidRegion, aRegion);
+  return aRect.width*PRUint64(aRect.height);
 }
 
 void
-ThebesLayerD3D9::RenderLayer()
+ThebesLayerD3D9::UpdateTextures(SurfaceMode aMode)
 {
-  if (mVisibleRegion.IsEmpty()) {
-    return;
-  }
-
   nsIntRect visibleRect = mVisibleRegion.GetBounds();
 
-  // We differentiate between these formats since D3D9 will only allow us to
-  // call GetDC on an opaque surface.
-  D3DFORMAT fmt = (CanUseOpaqueSurface() && !mD2DSurface) ?
-                    D3DFMT_X8R8G8B8 : D3DFMT_A8R8G8B8;
-
-  if (mTexture) {
-    D3DSURFACE_DESC desc;
-    mTexture->GetLevelDesc(0, &desc);
+  if (HaveTextures(aMode)) {
+    if (mTextureRect != visibleRect) {
+      nsRefPtr<IDirect3DTexture9> oldTexture = mTexture;
+      nsRefPtr<IDirect3DTexture9> oldTextureOnWhite = mTextureOnWhite;
 
-    if (fmt != desc.Format) {
-      // The new format isn't compatible with the old texture, toss out the old
-      // texture.
-      mTexture = nsnull;
-      mValidRegion.SetEmpty();
-    }
-  }
+      NS_ASSERTION(mTextureRect.Contains(mValidRegion.GetBounds()),
+                   "How can we have valid data outside the texture?");
+      nsIntRegion retainRegion;
+      // The region we want to retain is the valid data that is inside
+      // the new visible region
+      retainRegion.And(mValidRegion, mVisibleRegion);
 
-  VerifyContentType();
-
-  if (!mTexture) {
-    CreateNewTexture(gfxIntSize(visibleRect.width, visibleRect.height));
-    
-    if (!mTexture) {
-	NS_WARNING("Failed to create texture for thebes layer - not drawing.");
-	return;
-    }
+      CreateNewTextures(gfxIntSize(visibleRect.width, visibleRect.height), aMode);
 
-    mValidRegion.SetEmpty();
-  }
-
-  if (!mValidRegion.IsEqual(mVisibleRegion)) {
-    /* 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);
+      // 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 (!HaveTextures(aMode)) {
+        mValidRegion.SetEmpty();
+      } else {
+        CopyRegion(oldTexture, mTextureRect.TopLeft(), mTexture, visibleRect.TopLeft(),
+                   retainRegion, &mValidRegion);
+        if (aMode == SURFACE_COMPONENT_ALPHA) {
+          CopyRegion(oldTextureOnWhite, mTextureRect.TopLeft(), mTextureOnWhite, visibleRect.TopLeft(),
+                     retainRegion, &mValidRegion);
+        }
+      }
 
-    mValidRegion = mVisibleRegion;
+      mTextureRect = visibleRect;
+    }
+  } else {
+    CreateNewTextures(gfxIntSize(visibleRect.width, visibleRect.height), aMode);
+    mTextureRect = visibleRect;
+    
+    NS_ASSERTION(mValidRegion.IsEmpty(), "Someone forgot to empty the region");
   }
-
-  SetShaderTransformAndOpacity();
+}
 
-#ifdef CAIRO_HAS_D2D_SURFACE
-  if (mD2DSurface && CanUseOpaqueSurface()) {
-    mD3DManager->SetShaderMode(DeviceManagerD3D9::RGBLAYER);
-  } else
-#endif
-  mD3DManager->SetShaderMode(DeviceManagerD3D9::RGBALAYER);
-
-  device()->SetTexture(0, mTexture);
-
+void
+ThebesLayerD3D9::RenderVisibleRegion()
+{
   nsIntRegionRectIterator iter(mVisibleRegion);
 
   const nsIntRect *iterRect;
   while ((iterRect = iter.Next())) {
     device()->SetVertexShaderConstantF(CBvLayerQuad,
                                        ShaderConstantRect(iterRect->x,
                                                           iterRect->y,
                                                           iterRect->width,
                                                           iterRect->height),
                                        1);
 
     device()->SetVertexShaderConstantF(CBvTextureCoords,
       ShaderConstantRect(
-        (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), 1);
+        (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), 1);
 
     device()->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
   }
+}
+
+void
+ThebesLayerD3D9::RenderLayer()
+{
+  if (mVisibleRegion.IsEmpty()) {
+    return;
+  }
+
+  SurfaceMode mode = GetSurfaceMode();
+  if (mode == SURFACE_COMPONENT_ALPHA &&
+      (!mParent || !mParent->SupportsComponentAlphaChildren())) {
+    mode = SURFACE_SINGLE_CHANNEL_ALPHA;
+  }
+  VerifyContentType(mode);
+  UpdateTextures(mode);
+  if (!HaveTextures(mode)) {
+    NS_WARNING("Texture creation failed");
+    return;
+  }
+
+  if (!mValidRegion.IsEqual(mVisibleRegion)) {
+    /* 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);
+
+    mValidRegion = mVisibleRegion;
+  }
+
+  SetShaderTransformAndOpacity();
+
+  if (mode == SURFACE_COMPONENT_ALPHA) {
+    mD3DManager->SetShaderMode(DeviceManagerD3D9::COMPONENTLAYERPASS1);
+    device()->SetTexture(0, mTexture);
+    device()->SetTexture(1, mTextureOnWhite);
+    device()->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
+    device()->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR);
+    RenderVisibleRegion();
+
+    mD3DManager->SetShaderMode(DeviceManagerD3D9::COMPONENTLAYERPASS2);
+    device()->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
+    device()->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
+    RenderVisibleRegion();
+
+    // Restore defaults
+    device()->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
+    device()->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
+    device()->SetTexture(1, NULL);
+  } else {
+#ifdef CAIRO_HAS_D2D_SURFACE
+    if (mD2DSurface && CanUseOpaqueSurface()) {
+      mD3DManager->SetShaderMode(DeviceManagerD3D9::RGBLAYER);
+    } else
+#endif
+    mD3DManager->SetShaderMode(DeviceManagerD3D9::RGBALAYER);
+    device()->SetTexture(0, mTexture);
+    RenderVisibleRegion();
+  }
 
   // Set back to default.
   device()->SetVertexShaderConstantF(CBvTextureCoords,
                                      ShaderConstantRect(0, 0, 1.0f, 1.0f),
                                      1);
 }
 
 void
 ThebesLayerD3D9::CleanResources()
 {
   mTexture = nsnull;
+  mTextureOnWhite = nsnull;
 }
 
 void
 ThebesLayerD3D9::LayerManagerDestroyed()
 {
   mD3DManager->deviceManager()->mLayersWithResources.RemoveElement(this);
   mD3DManager = nsnull;
 }
@@ -284,35 +284,134 @@ ThebesLayerD3D9::GetLayer()
 
 PRBool
 ThebesLayerD3D9::IsEmpty()
 {
   return !mTexture;
 }
 
 void
-ThebesLayerD3D9::VerifyContentType()
+ThebesLayerD3D9::VerifyContentType(SurfaceMode aMode)
 {
 #ifdef CAIRO_HAS_D2D_SURFACE
   if (mD2DSurface) {
+    NS_ASSERTION(!mTextureOnWhite, "No component-alpha textures should be around with D2D");
+
     gfxASurface::gfxContentType type = CanUseOpaqueSurface() ?
       gfxASurface::CONTENT_COLOR : gfxASurface::CONTENT_COLOR_ALPHA;
 
     if (type != mD2DSurface->GetContentType()) {
       // We could choose to recreate only the D2D surface, but since we can't
       // use retention the synchronisation overhead probably isn't worth it.
       mD2DSurface = nsnull;
       mTexture = nsnull;
+      mValidRegion.SetEmpty();
     }
+    return;
   }
 #endif
+
+  if (!mTexture)
+    return;
+
+  D3DSURFACE_DESC desc;
+  mTexture->GetLevelDesc(0, &desc);
+
+  switch (aMode) {
+  case SURFACE_OPAQUE:
+    if (desc.Format == D3DFMT_X8R8G8B8 && !mTextureOnWhite)
+      return;
+    break;
+
+  case SURFACE_SINGLE_CHANNEL_ALPHA:
+    if (desc.Format == D3DFMT_A8R8G8B8 && !mTextureOnWhite)
+      return;
+    break;
+
+  case SURFACE_COMPONENT_ALPHA:
+    if (mTextureOnWhite) {
+      NS_ASSERTION(desc.Format == D3DFMT_X8R8G8B8, "Wrong format for component alpha texture");
+      return;
+    }
+    break;
+  }
+
+  // The new format isn't compatible with the old texture(s), toss out the old
+  // texture(s).
+  mTexture = nsnull;
+  mTextureOnWhite = nsnull;
+  mValidRegion.SetEmpty();
+}
+
+class OpaqueRenderer {
+public:
+  OpaqueRenderer(const nsIntRegion& aUpdateRegion) :
+    mUpdateRegion(aUpdateRegion), mDC(NULL) {}
+  already_AddRefed<gfxWindowsSurface> Begin(LayerD3D9* aLayer);
+  void End();
+  IDirect3DTexture9* GetTexture() { return mTmpTexture; }
+
+private:
+  const nsIntRegion& mUpdateRegion;
+  nsRefPtr<IDirect3DTexture9> mTmpTexture;
+  nsRefPtr<IDirect3DSurface9> mSurface;
+  HDC mDC;
+};
+
+already_AddRefed<gfxWindowsSurface>
+OpaqueRenderer::Begin(LayerD3D9* aLayer)
+{
+  nsIntRect bounds = mUpdateRegion.GetBounds();
+
+  HRESULT hr = aLayer->device()->
+      CreateTexture(bounds.width, bounds.height, 1, 0, D3DFMT_X8R8G8B8,
+                    D3DPOOL_SYSTEMMEM, getter_AddRefs(mTmpTexture), NULL);
+
+  if (FAILED(hr)) {
+    aLayer->ReportFailure(NS_LITERAL_CSTRING("Failed to create temporary texture in system memory."), hr);
+    return nsnull;
+  }
+
+  hr = mTmpTexture->GetSurfaceLevel(0, getter_AddRefs(mSurface));
+
+  if (FAILED(hr)) {
+    // Uh-oh, bail.
+    NS_WARNING("Failed to get texture surface level.");
+    return nsnull;
+  }
+
+  hr = mSurface->GetDC(&mDC);
+  if (FAILED(hr)) {
+    NS_WARNING("Failed to get device context for texture surface.");
+    return nsnull;
+  }
+
+  nsRefPtr<gfxWindowsSurface> result = new gfxWindowsSurface(mDC);
+  return result.forget();
 }
 
 void
-ThebesLayerD3D9::DrawRegion(const nsIntRegion &aRegion)
+OpaqueRenderer::End()
+{
+  mSurface->ReleaseDC(mDC);
+}
+
+static void
+FillSurface(gfxASurface* aSurface, const nsIntRegion& aRegion,
+            const nsIntPoint& aOffset, const gfxRGBA& aColor)
+{
+  nsRefPtr<gfxContext> ctx = new gfxContext(aSurface);
+  ctx->Translate(-gfxPoint(aOffset.x, aOffset.y));
+  gfxUtils::ClipToRegion(ctx, aRegion);
+  ctx->SetColor(aColor);
+  ctx->Paint();
+}
+
+void
+ThebesLayerD3D9::DrawRegion(const nsIntRegion &aRegion, SurfaceMode aMode)
 {
   HRESULT hr;
   nsIntRect visibleRect = mVisibleRegion.GetBounds();
   nsRefPtr<gfxContext> context;
 
 #ifdef CAIRO_HAS_D2D_SURFACE
   if (mD2DSurface) {
     context = new gfxContext(mD2DSurface);
@@ -341,144 +440,182 @@ ThebesLayerD3D9::DrawRegion(const nsIntR
     // drawing in a seperate 'validation' iteration. And then flushing once for
     // all the D2D surfaces we might have drawn, before doing our D3D9 rendering
     // loop.
     cairo_d2d_finish_device(gfxWindowsPlatform::GetPlatform()->GetD2DDevice());
     return;
   }
 #endif
 
-  D3DFORMAT fmt = CanUseOpaqueSurface() ? D3DFMT_X8R8G8B8 : D3DFMT_A8R8G8B8;
+  nsRefPtr<gfxASurface> destinationSurface;
   nsIntRect bounds = aRegion.GetBounds();
+  nsRefPtr<IDirect3DTexture9> tmpTexture;
+  OpaqueRenderer opaqueRenderer(aRegion);
+  OpaqueRenderer opaqueRendererOnWhite(aRegion);
 
-  gfxASurface::gfxImageFormat imageFormat = gfxASurface::ImageFormatARGB32;
-  nsRefPtr<gfxASurface> destinationSurface;
+  switch (aMode)
+  {
+    case SURFACE_OPAQUE:
+      destinationSurface = opaqueRenderer.Begin(this);
+      break;
 
-  nsRefPtr<IDirect3DTexture9> tmpTexture;
-  hr = device()->CreateTexture(bounds.width, bounds.height, 1,
-                               0, fmt,
-                               D3DPOOL_SYSTEMMEM, getter_AddRefs(tmpTexture), NULL);
+    case SURFACE_SINGLE_CHANNEL_ALPHA: {
+      hr = device()->CreateTexture(bounds.width, bounds.height, 1,
+                                   0, D3DFMT_A8R8G8B8,
+                                   D3DPOOL_SYSTEMMEM, getter_AddRefs(tmpTexture), NULL);
 
-  if (FAILED(hr)) {
-    ReportFailure(NS_LITERAL_CSTRING("Failed to create temporary texture in system memory."), hr);
-    return;
-  }
+      if (FAILED(hr)) {
+        ReportFailure(NS_LITERAL_CSTRING("Failed to create temporary texture in system memory."), hr);
+        return;
+      }
 
-  nsRefPtr<IDirect3DSurface9> surf;
-  HDC dc;
-  if (CanUseOpaqueSurface()) {
-    hr = tmpTexture->GetSurfaceLevel(0, getter_AddRefs(surf));
-
-    if (FAILED(hr)) {
-      // Uh-oh, bail.
-      NS_WARNING("Failed to get texture surface level.");
-      return;
+      // XXX - We may consider retaining a SYSTEMMEM texture texture the size
+      // of our DEFAULT texture and then use UpdateTexture and add dirty rects
+      // to update in a single call.
+      nsRefPtr<gfxWindowsSurface> dest = new gfxWindowsSurface(
+          gfxIntSize(bounds.width, bounds.height), gfxASurface::ImageFormatARGB32);
+      // If the contents of this layer don't require component alpha in the
+      // end of rendering, it's safe to enable Cleartype since all the Cleartype
+      // glyphs must be over (or under) opaque pixels.
+      dest->SetSubpixelAntialiasingEnabled(!(mContentFlags & CONTENT_COMPONENT_ALPHA));
+      destinationSurface = dest.forget();
+      break;
     }
 
-    hr = surf->GetDC(&dc);
-
-    if (FAILED(hr)) {
-      NS_WARNING("Failed to get device context for texture surface.");
-      return;
+    case SURFACE_COMPONENT_ALPHA: {
+      nsRefPtr<gfxWindowsSurface> onBlack = opaqueRenderer.Begin(this);
+      nsRefPtr<gfxWindowsSurface> onWhite = opaqueRendererOnWhite.Begin(this);
+      FillSurface(onBlack, aRegion, bounds.TopLeft(), gfxRGBA(0.0, 0.0, 0.0, 1.0));
+      FillSurface(onWhite, aRegion, bounds.TopLeft(), gfxRGBA(1.0, 1.0, 1.0, 1.0));
+      gfxASurface* surfaces[2] = { onBlack.get(), onWhite.get() };
+      destinationSurface = new gfxTeeSurface(surfaces, NS_ARRAY_LENGTH(surfaces));
+      // Using this surface as a source will likely go horribly wrong, since
+      // only the onBlack surface will really be used, so alpha information will
+      // be incorrect.
+      destinationSurface->SetAllowUseAsSource(PR_FALSE);
+      break;
     }
-
-    destinationSurface = new gfxWindowsSurface(dc);
-  } else {
-    // XXX - We may consider retaining a SYSTEMMEM texture texture the size
-    // of our DEFAULT texture and then use UpdateTexture and add dirty rects
-    // to update in a single call.
-    destinationSurface =
-    gfxPlatform::GetPlatform()->
-      CreateOffscreenSurface(gfxIntSize(bounds.width,
-                                        bounds.height),
-                             gfxASurface::ContentFromFormat(imageFormat));
   }
 
   context = new gfxContext(destinationSurface);
   context->Translate(gfxPoint(-bounds.x, -bounds.y));
   LayerManagerD3D9::CallbackInfo cbInfo = mD3DManager->GetCallbackInfo();
   cbInfo.Callback(this, context, aRegion, nsIntRegion(), cbInfo.CallbackData);
 
-  if (CanUseOpaqueSurface()) {
-    surf->ReleaseDC(dc);
-  } else {
-    D3DLOCKED_RECT r;
-    tmpTexture->LockRect(0, &r, NULL, 0);
+  nsAutoTArray<IDirect3DTexture9*,2> srcTextures;
+  nsAutoTArray<IDirect3DTexture9*,2> destTextures;
+  switch (aMode)
+  {
+    case SURFACE_OPAQUE:
+      opaqueRenderer.End();
+      srcTextures.AppendElement(opaqueRenderer.GetTexture());
+      destTextures.AppendElement(mTexture);
+      break;
+
+    case SURFACE_SINGLE_CHANNEL_ALPHA: {
+      D3DLOCKED_RECT r;
+      tmpTexture->LockRect(0, &r, NULL, 0);
 
-    nsRefPtr<gfxImageSurface> imgSurface =
-    new gfxImageSurface((unsigned char *)r.pBits,
-                        gfxIntSize(bounds.width,
-                                   bounds.height),
-                        r.Pitch,
-                        imageFormat);
+      nsRefPtr<gfxImageSurface> imgSurface =
+        new gfxImageSurface((unsigned char *)r.pBits,
+                            gfxIntSize(bounds.width,
+                                       bounds.height),
+                            r.Pitch,
+                            gfxASurface::ImageFormatARGB32);
 
-    context = new gfxContext(imgSurface);
-    context->SetSource(destinationSurface);
-    context->SetOperator(gfxContext::OPERATOR_SOURCE);
-    context->Paint();
+      context = new gfxContext(imgSurface);
+      context->SetSource(destinationSurface);
+      context->SetOperator(gfxContext::OPERATOR_SOURCE);
+      context->Paint();
 
-    imgSurface = NULL;
+      imgSurface = NULL;
+
+      tmpTexture->UnlockRect(0);
 
-    tmpTexture->UnlockRect(0);
-  }
+      srcTextures.AppendElement(tmpTexture);
+      destTextures.AppendElement(mTexture);
+      break;
+    }
 
-  nsRefPtr<IDirect3DSurface9> srcSurface;
-  nsRefPtr<IDirect3DSurface9> dstSurface;
-
-  mTexture->GetSurfaceLevel(0, getter_AddRefs(dstSurface));
-  tmpTexture->GetSurfaceLevel(0, getter_AddRefs(srcSurface));
+    case SURFACE_COMPONENT_ALPHA: {
+      opaqueRenderer.End();
+      opaqueRendererOnWhite.End();
+      srcTextures.AppendElement(opaqueRenderer.GetTexture());
+      destTextures.AppendElement(mTexture);
+      srcTextures.AppendElement(opaqueRendererOnWhite.GetTexture());
+      destTextures.AppendElement(mTextureOnWhite);
+      break;
+    }
+  }
+  NS_ASSERTION(srcTextures.Length() == destTextures.Length(), "Mismatched lengths");
 
-  nsIntRegionRectIterator iter(aRegion);
-  const nsIntRect *iterRect;
-  while ((iterRect = iter.Next())) {
-    RECT rect;
-    rect.left = iterRect->x - bounds.x;
-    rect.top = iterRect->y - bounds.y;
-    rect.right = rect.left + iterRect->width;
-    rect.bottom = rect.top + iterRect->height;
-    POINT point;
-    point.x = iterRect->x - visibleRect.x;
-    point.y = iterRect->y - visibleRect.y;
-    device()->UpdateSurface(srcSurface, &rect, dstSurface, &point);
+  for (PRUint32 i = 0; i < srcTextures.Length(); ++i) {
+    nsRefPtr<IDirect3DSurface9> srcSurface;
+    nsRefPtr<IDirect3DSurface9> dstSurface;
+
+    destTextures[i]->GetSurfaceLevel(0, getter_AddRefs(dstSurface));
+    srcTextures[i]->GetSurfaceLevel(0, getter_AddRefs(srcSurface));
+
+    nsIntRegionRectIterator iter(aRegion);
+    const nsIntRect *iterRect;
+    while ((iterRect = iter.Next())) {
+      RECT rect;
+      rect.left = iterRect->x - bounds.x;
+      rect.top = iterRect->y - bounds.y;
+      rect.right = rect.left + iterRect->width;
+      rect.bottom = rect.top + iterRect->height;
+      POINT point;
+      point.x = iterRect->x - visibleRect.x;
+      point.y = iterRect->y - visibleRect.y;
+      device()->UpdateSurface(srcSurface, &rect, dstSurface, &point);
+    }
   }
 }
 
 void
-ThebesLayerD3D9::CreateNewTexture(const gfxIntSize &aSize)
+ThebesLayerD3D9::CreateNewTextures(const gfxIntSize &aSize,
+                                   SurfaceMode aMode)
 {
-  if (aSize.width == 0 | aSize.height == 0) {
+  if (aSize.width == 0 || aSize.height == 0) {
     // Nothing to do.
     return;
   }
 
   mTexture = nsnull;
-  PRBool canUseOpaqueSurface = CanUseOpaqueSurface();
+  mTextureOnWhite = nsnull;
 #ifdef CAIRO_HAS_D2D_SURFACE
   if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() ==
       gfxWindowsPlatform::RENDER_DIRECT2D) {
         if (mD3DManager->deviceManager()->IsD3D9Ex()) {
           // We should have D3D9Ex where we have D2D.
           HANDLE sharedHandle = 0;
           device()->CreateTexture(aSize.width, aSize.height, 1,
                                   D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8,
                                   D3DPOOL_DEFAULT, getter_AddRefs(mTexture), &sharedHandle);
 
           mD2DSurfaceInitialized = false;
-          mD2DSurface = new gfxD2DSurface(sharedHandle, canUseOpaqueSurface ?
+          mD2DSurface = new gfxD2DSurface(sharedHandle, aMode == SURFACE_OPAQUE ?
             gfxASurface::CONTENT_COLOR : gfxASurface::CONTENT_COLOR_ALPHA);
 
           // If there's an error, go on and do what we always do.
           if (mD2DSurface->CairoStatus()) {
             mD2DSurface = nsnull;
             mTexture = nsnull;
           }
         }
   }
 #endif
   if (!mTexture) {
     device()->CreateTexture(aSize.width, aSize.height, 1,
-                            D3DUSAGE_RENDERTARGET, canUseOpaqueSurface ? D3DFMT_X8R8G8B8 : D3DFMT_A8R8G8B8,
+                            D3DUSAGE_RENDERTARGET,
+                            aMode != SURFACE_SINGLE_CHANNEL_ALPHA ? D3DFMT_X8R8G8B8 : D3DFMT_A8R8G8B8,
                             D3DPOOL_DEFAULT, getter_AddRefs(mTexture), NULL);
+    if (aMode == SURFACE_COMPONENT_ALPHA) {
+      device()->CreateTexture(aSize.width, aSize.height, 1,
+                              D3DUSAGE_RENDERTARGET,
+                              D3DFMT_X8R8G8B8,
+                              D3DPOOL_DEFAULT, getter_AddRefs(mTextureOnWhite), NULL);
+    }
   }
 }
 
 } /* namespace layers */
 } /* namespace mozilla */
--- a/gfx/layers/d3d9/ThebesLayerD3D9.h
+++ b/gfx/layers/d3d9/ThebesLayerD3D9.h
@@ -47,45 +47,70 @@ namespace layers {
 
 class ThebesLayerD3D9 : public ThebesLayer,
                         public LayerD3D9
 {
 public:
   ThebesLayerD3D9(LayerManagerD3D9 *aManager);
   virtual ~ThebesLayerD3D9();
 
-  /* Layer implementation */
-  void SetVisibleRegion(const nsIntRegion& aRegion);
-
   /* ThebesLayer implementation */
   void InvalidateRegion(const nsIntRegion& aRegion);
 
   /* LayerD3D9 implementation */
   Layer* GetLayer();
   virtual PRBool IsEmpty();
   virtual void RenderLayer();
   virtual void CleanResources();
   virtual void LayerManagerDestroyed();
 
 private:
   /*
    * D3D9 texture
    */
   nsRefPtr<IDirect3DTexture9> mTexture;
-
-  /* Checks if our D2D surface has the right content type */
-  void VerifyContentType();
+  /*
+   * D3D9 texture for render-on-white when doing component alpha
+   */
+  nsRefPtr<IDirect3DTexture9> mTextureOnWhite;
+  /**
+   * Visible region bounds used when we drew the contents of the textures
+   */
+  nsIntRect mTextureRect;
 
   /* This contains the D2D surface if we have one */
   nsRefPtr<gfxASurface> mD2DSurface;
 
   bool mD2DSurfaceInitialized;
 
+  bool HaveTextures(SurfaceMode aMode)
+  {
+    return mTexture && (aMode != SURFACE_COMPONENT_ALPHA || mTextureOnWhite);
+  }
+
+  /* Checks if our surface has the right content type */
+  void VerifyContentType(SurfaceMode aMode);
+
+  /* Ensures we have the necessary texture object(s) and that they correspond
+   * to mVisibleRegion.GetBounds(). This creates new texture objects as
+   * necessary and also copies existing valid texture data if necessary.
+   */
+  void UpdateTextures(SurfaceMode aMode);
+
+  /* Render the rectangles of mVisibleRegion with D3D9 using the currently
+   * bound textures, target, shaders, etc.
+   */
+  void RenderVisibleRegion();
+
   /* Have a region of our layer drawn */
-  void DrawRegion(const nsIntRegion &aRegion);
+  void DrawRegion(const nsIntRegion &aRegion, SurfaceMode aMode);
 
   /* Create a new texture */
-  void CreateNewTexture(const gfxIntSize &aSize);
+  void CreateNewTextures(const gfxIntSize &aSize, SurfaceMode aMode);
+
+  void CopyRegion(IDirect3DTexture9* aSrc, const nsIntPoint &aSrcOffset,
+                  IDirect3DTexture9* aDest, const nsIntPoint &aDestOffset,
+                  const nsIntRegion &aCopyRegion, nsIntRegion* aValidRegion);
 };
 
 } /* layers */
 } /* mozilla */
 #endif /* GFX_THEBESLAYERD3D9_H */
--- a/gfx/src/nsRect.h
+++ b/gfx/src/nsRect.h
@@ -287,18 +287,19 @@ struct NS_GFX nsIntRect {
   void MoveBy(const nsIntPoint& aPoint) {x += aPoint.x; y += aPoint.y;}
   void SizeTo(PRInt32 aWidth, PRInt32 aHeight) {width = aWidth; height = aHeight;}
   void SizeTo(const nsIntSize& aSize) {SizeTo(aSize.width, aSize.height);}
   void SizeBy(PRInt32 aDeltaWidth, PRInt32 aDeltaHeight) {width += aDeltaWidth;
                                                           height += aDeltaHeight;}
 
   PRBool Contains(const nsIntRect& aRect) const
   {
-    return (PRBool) ((aRect.x >= x) && (aRect.y >= y) &&
-                     (aRect.XMost() <= XMost()) && (aRect.YMost() <= YMost()));
+    return aRect.IsEmpty() ||
+        (PRBool) ((aRect.x >= x) && (aRect.y >= y) &&
+                  (aRect.XMost() <= XMost()) && (aRect.YMost() <= YMost()));
   }
   PRBool Contains(PRInt32 aX, PRInt32 aY) const
   {
     return (PRBool) ((aX >= x) && (aY >= y) &&
                      (aX < XMost()) && (aY < YMost()));
   }
   PRBool Contains(const nsIntPoint& aPoint) const { return Contains(aPoint.x, aPoint.y); }