/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Corporation code. * * The Initial Developer of the Original Code is Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bas Schouten * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "mozilla/layers/PLayers.h" /* This must occur *after* layers/PLayers.h to avoid typedefs conflicts. */ #include "mozilla/Util.h" #include "mozilla/layers/ShadowLayers.h" #include "ShadowBufferD3D9.h" #include "ThebesLayerD3D9.h" #include "gfxPlatform.h" #include "gfxWindowsPlatform.h" #include "gfxTeeSurface.h" #include "gfxUtils.h" #include "ReadbackProcessor.h" namespace mozilla { namespace layers { ThebesLayerD3D9::ThebesLayerD3D9(LayerManagerD3D9 *aManager) : ThebesLayer(aManager, NULL) , LayerD3D9(aManager) { mImplData = static_cast(this); aManager->deviceManager()->mLayersWithResources.AppendElement(this); } ThebesLayerD3D9::~ThebesLayerD3D9() { if (mD3DManager) { mD3DManager->deviceManager()->mLayersWithResources.RemoveElement(this); } } /** * 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::InvalidateRegion(const nsIntRegion &aRegion) { mValidRegion.Sub(mValidRegion, aRegion); } void ThebesLayerD3D9::CopyRegion(IDirect3DTexture9* aSrc, const nsIntPoint &aSrcOffset, IDirect3DTexture9* aDest, const nsIntPoint &aDestOffset, const nsIntRegion &aCopyRegion, nsIntRegion* aValidRegion) { nsRefPtr srcSurface, dstSurface; aSrc->GetSurfaceLevel(0, getter_AddRefs(srcSurface)); aDest->GetSurfaceLevel(0, getter_AddRefs(dstSurface)); nsIntRegion retainedRegion; 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 - aSrcOffset.x; oldRect.top = r->y - aSrcOffset.y; oldRect.right = oldRect.left + r->width; oldRect.bottom = oldRect.top + r->height; 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 aValidRegion->And(*aValidRegion, retainedRegion); } static PRUint64 RectArea(const nsIntRect& aRect) { return aRect.width*PRUint64(aRect.height); } void ThebesLayerD3D9::UpdateTextures(SurfaceMode aMode) { nsIntRect visibleRect = mVisibleRegion.GetBounds(); if (HaveTextures(aMode)) { if (!mTextureRect.IsEqualInterior(visibleRect)) { nsRefPtr oldTexture = mTexture; nsRefPtr oldTextureOnWhite = mTextureOnWhite; 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); CreateNewTextures(gfxIntSize(visibleRect.width, visibleRect.height), aMode); // 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); } } mTextureRect = visibleRect; } } else { CreateNewTextures(gfxIntSize(visibleRect.width, visibleRect.height), aMode); mTextureRect = visibleRect; NS_ASSERTION(mValidRegion.IsEmpty(), "Someone forgot to empty the region"); } } void ThebesLayerD3D9::RenderRegion(const nsIntRegion& aRegion) { nsIntRegionRectIterator iter(aRegion); 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 - 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::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().IsEqualInterior(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 readbackUpdates; nsIntRegion readbackRegion; if (aReadback && UsedForReadback()) { aReadback->GetThebesLayerUpdates(this, &readbackUpdates, &readbackRegion); } // 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(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 = neededRegion; } 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); RenderRegion(neededRegion); mD3DManager->SetShaderMode(DeviceManagerD3D9::COMPONENTLAYERPASS2); device()->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); device()->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE); RenderRegion(neededRegion); // Restore defaults device()->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); device()->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); device()->SetTexture(1, NULL); } else { mD3DManager->SetShaderMode(DeviceManagerD3D9::RGBALAYER); device()->SetTexture(0, mTexture); RenderRegion(neededRegion); } // Set back to default. device()->SetVertexShaderConstantF(CBvTextureCoords, ShaderConstantRect(0, 0, 1.0f, 1.0f), 1); } void ThebesLayerD3D9::CleanResources() { mTexture = nsnull; mTextureOnWhite = nsnull; mValidRegion.SetEmpty(); } void ThebesLayerD3D9::LayerManagerDestroyed() { mD3DManager->deviceManager()->mLayersWithResources.RemoveElement(this); mD3DManager = nsnull; } Layer* ThebesLayerD3D9::GetLayer() { return this; } bool ThebesLayerD3D9::IsEmpty() { return !mTexture; } void ThebesLayerD3D9::VerifyContentType(SurfaceMode aMode) { 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) {} ~OpaqueRenderer() { End(); } already_AddRefed Begin(LayerD3D9* aLayer); void End(); IDirect3DTexture9* GetTexture() { return mTmpTexture; } private: const nsIntRegion& mUpdateRegion; nsRefPtr mTmpTexture; nsRefPtr mSurface; HDC mDC; }; already_AddRefed 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 result = new gfxWindowsSurface(mDC); return result.forget(); } void OpaqueRenderer::End() { if (mSurface && mDC) { mSurface->ReleaseDC(mDC); mSurface = NULL; mDC = NULL; } } static void FillSurface(gfxASurface* aSurface, const nsIntRegion& aRegion, const nsIntPoint& aOffset, const gfxRGBA& aColor) { nsRefPtr ctx = new gfxContext(aSurface); ctx->Translate(-gfxPoint(aOffset.x, aOffset.y)); gfxUtils::ClipToRegion(ctx, aRegion); ctx->SetColor(aColor); ctx->Paint(); } void ThebesLayerD3D9::DrawRegion(nsIntRegion &aRegion, SurfaceMode aMode, const nsTArray& aReadbackUpdates) { HRESULT hr; nsIntRect visibleRect = mVisibleRegion.GetBounds(); nsRefPtr destinationSurface; nsIntRect bounds = aRegion.GetBounds(); nsRefPtr tmpTexture; OpaqueRenderer opaqueRenderer(aRegion); OpaqueRenderer opaqueRendererOnWhite(aRegion); switch (aMode) { case SURFACE_OPAQUE: destinationSurface = opaqueRenderer.Begin(this); break; 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; } // 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 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; } case SURFACE_COMPONENT_ALPHA: { nsRefPtr onBlack = opaqueRenderer.Begin(this); nsRefPtr onWhite = opaqueRendererOnWhite.Begin(this); if (onBlack && onWhite) { 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, ArrayLength(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(false); } break; } } if (!destinationSurface) return; nsRefPtr context = new gfxContext(destinationSurface); context->Translate(gfxPoint(-bounds.x, -bounds.y)); LayerManagerD3D9::CallbackInfo cbInfo = mD3DManager->GetCallbackInfo(); cbInfo.Callback(this, context, aRegion, nsIntRegion(), cbInfo.CallbackData); for (PRUint32 i = 0; i < aReadbackUpdates.Length(); ++i) { NS_ASSERTION(aMode == SURFACE_OPAQUE, "Transparent surfaces should not be used for readback"); const ReadbackProcessor::Update& update = aReadbackUpdates[i]; nsIntPoint offset = update.mLayer->GetBackgroundLayerOffset(); nsRefPtr ctx = update.mLayer->GetSink()->BeginUpdate(update.mUpdateRect + offset, update.mSequenceCounter); if (ctx) { ctx->Translate(gfxPoint(offset.x, offset.y)); ctx->SetSource(destinationSurface, gfxPoint(bounds.x, bounds.y)); ctx->Paint(); update.mLayer->GetSink()->EndUpdate(ctx, update.mUpdateRect + offset); } } nsAutoTArray srcTextures; nsAutoTArray destTextures; switch (aMode) { case SURFACE_OPAQUE: opaqueRenderer.End(); srcTextures.AppendElement(opaqueRenderer.GetTexture()); destTextures.AppendElement(mTexture); break; case SURFACE_SINGLE_CHANNEL_ALPHA: { LockTextureRectD3D9 textureLock(tmpTexture); if (!textureLock.HasLock()) { NS_WARNING("Failed to lock ThebesLayer tmpTexture texture."); return; } D3DLOCKED_RECT r = textureLock.GetLockRect(); nsRefPtr imgSurface = new gfxImageSurface((unsigned char *)r.pBits, bounds.Size(), r.Pitch, gfxASurface::ImageFormatARGB32); if (destinationSurface) { nsRefPtr context = new gfxContext(imgSurface); context->SetSource(destinationSurface); context->SetOperator(gfxContext::OPERATOR_SOURCE); context->Paint(); } imgSurface = NULL; srcTextures.AppendElement(tmpTexture); destTextures.AppendElement(mTexture); break; } 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"); // Copy to the texture. for (PRUint32 i = 0; i < srcTextures.Length(); ++i) { nsRefPtr srcSurface; nsRefPtr 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 = iterRect->XMost() - bounds.x; rect.bottom = iterRect->YMost() - bounds.y; POINT point; point.x = iterRect->x - visibleRect.x; point.y = iterRect->y - visibleRect.y; device()->UpdateSurface(srcSurface, &rect, dstSurface, &point); } } } void ThebesLayerD3D9::CreateNewTextures(const gfxIntSize &aSize, SurfaceMode aMode) { if (aSize.width == 0 || aSize.height == 0) { // Nothing to do. return; } mTexture = nsnull; mTextureOnWhite = nsnull; HRESULT hr = device()->CreateTexture(aSize.width, aSize.height, 1, D3DUSAGE_RENDERTARGET, aMode != SURFACE_SINGLE_CHANNEL_ALPHA ? D3DFMT_X8R8G8B8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, getter_AddRefs(mTexture), NULL); if (FAILED(hr)) { ReportFailure(NS_LITERAL_CSTRING("ThebesLayerD3D9::CreateNewTextures(): Failed to create texture"), hr); return; } if (aMode == SURFACE_COMPONENT_ALPHA) { hr = device()->CreateTexture(aSize.width, aSize.height, 1, D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, getter_AddRefs(mTextureOnWhite), NULL); if (FAILED(hr)) { ReportFailure(NS_LITERAL_CSTRING("ThebesLayerD3D9::CreateNewTextures(): Failed to create texture (2)"), hr); return; } } } ShadowThebesLayerD3D9::ShadowThebesLayerD3D9(LayerManagerD3D9 *aManager) : ShadowThebesLayer(aManager, nsnull) , LayerD3D9(aManager) { mImplData = static_cast(this); } ShadowThebesLayerD3D9::~ShadowThebesLayerD3D9() {} void ShadowThebesLayerD3D9::Swap(const ThebesBuffer& aNewFront, const nsIntRegion& aUpdatedRegion, OptionalThebesBuffer* aNewBack, nsIntRegion* aNewBackValidRegion, OptionalThebesBuffer* aReadOnlyFront, nsIntRegion* aFrontUpdatedRegion) { if (!mBuffer) { mBuffer = new ShadowBufferD3D9(this); } if (mBuffer) { nsRefPtr surf = ShadowLayerForwarder::OpenDescriptor(aNewFront.buffer()); mBuffer->Upload(surf, GetVisibleRegion().GetBounds()); } *aNewBack = aNewFront; *aNewBackValidRegion = mValidRegion; *aReadOnlyFront = null_t(); aFrontUpdatedRegion->SetEmpty(); } void ShadowThebesLayerD3D9::DestroyFrontBuffer() { mBuffer = nsnull; } void ShadowThebesLayerD3D9::Disconnect() { mBuffer = nsnull; } Layer* ShadowThebesLayerD3D9::GetLayer() { return this; } bool ShadowThebesLayerD3D9::IsEmpty() { return !mBuffer; } void ShadowThebesLayerD3D9::RenderThebesLayer() { if (!mBuffer) { return; } NS_ABORT_IF_FALSE(mBuffer, "should have a buffer here"); mBuffer->RenderTo(mD3DManager, GetEffectiveVisibleRegion()); } void ShadowThebesLayerD3D9::CleanResources() { mBuffer = nsnull; mValidRegion.SetEmpty(); } void ShadowThebesLayerD3D9::LayerManagerDestroyed() { mD3DManager->deviceManager()->mLayersWithResources.RemoveElement(this); mD3DManager = nsnull; } } /* namespace layers */ } /* namespace mozilla */