michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/layers/ContentClient.h" michael@0: #include "BasicLayers.h" // for BasicLayerManager michael@0: #include "gfxColor.h" // for gfxRGBA michael@0: #include "gfxContext.h" // for gfxContext, etc michael@0: #include "gfxPlatform.h" // for gfxPlatform michael@0: #include "gfxPrefs.h" // for gfxPrefs michael@0: #include "gfxPoint.h" // for gfxIntSize, gfxPoint michael@0: #include "gfxTeeSurface.h" // for gfxTeeSurface michael@0: #include "gfxUtils.h" // for gfxUtils michael@0: #include "ipc/ShadowLayers.h" // for ShadowLayerForwarder michael@0: #include "mozilla/ArrayUtils.h" // for ArrayLength michael@0: #include "mozilla/gfx/2D.h" // for DrawTarget, Factory michael@0: #include "mozilla/gfx/BasePoint.h" // for BasePoint michael@0: #include "mozilla/gfx/BaseSize.h" // for BaseSize michael@0: #include "mozilla/gfx/Rect.h" // for Rect michael@0: #include "mozilla/gfx/Types.h" michael@0: #include "mozilla/layers/LayerManagerComposite.h" michael@0: #include "mozilla/layers/LayersMessages.h" // for ThebesBufferData michael@0: #include "mozilla/layers/LayersTypes.h" michael@0: #include "nsAutoPtr.h" // for nsRefPtr michael@0: #include "nsDebug.h" // for NS_ASSERTION, NS_WARNING, etc michael@0: #include "nsISupportsImpl.h" // for gfxContext::Release, etc michael@0: #include "nsIWidget.h" // for nsIWidget michael@0: #include "prenv.h" // for PR_GetEnv michael@0: #ifdef XP_WIN michael@0: #include "gfxWindowsPlatform.h" michael@0: #endif michael@0: #include "gfx2DGlue.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: using namespace gfx; michael@0: michael@0: namespace layers { michael@0: michael@0: static TextureFlags TextureFlagsForRotatedContentBufferFlags(uint32_t aBufferFlags) michael@0: { michael@0: TextureFlags result = 0; michael@0: michael@0: if (aBufferFlags & RotatedContentBuffer::BUFFER_COMPONENT_ALPHA) { michael@0: result |= TEXTURE_COMPONENT_ALPHA; michael@0: } michael@0: michael@0: if (aBufferFlags & RotatedContentBuffer::ALLOW_REPEAT) { michael@0: result |= TEXTURE_ALLOW_REPEAT; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: /* static */ TemporaryRef michael@0: ContentClient::CreateContentClient(CompositableForwarder* aForwarder) michael@0: { michael@0: LayersBackend backend = aForwarder->GetCompositorBackendType(); michael@0: if (backend != LayersBackend::LAYERS_OPENGL && michael@0: backend != LayersBackend::LAYERS_D3D9 && michael@0: backend != LayersBackend::LAYERS_D3D11 && michael@0: backend != LayersBackend::LAYERS_BASIC) { michael@0: return nullptr; michael@0: } michael@0: michael@0: bool useDoubleBuffering = false; michael@0: michael@0: #ifdef XP_WIN michael@0: if (backend == LayersBackend::LAYERS_D3D11) { michael@0: useDoubleBuffering = !!gfxWindowsPlatform::GetPlatform()->GetD2DDevice(); michael@0: } else michael@0: #endif michael@0: { michael@0: useDoubleBuffering = (LayerManagerComposite::SupportsDirectTexturing() && michael@0: backend != LayersBackend::LAYERS_D3D9) || michael@0: backend == LayersBackend::LAYERS_BASIC; michael@0: } michael@0: michael@0: if (useDoubleBuffering || PR_GetEnv("MOZ_FORCE_DOUBLE_BUFFERING")) { michael@0: return new ContentClientDoubleBuffered(aForwarder); michael@0: } michael@0: #ifdef XP_MACOSX michael@0: if (backend == LayersBackend::LAYERS_OPENGL) { michael@0: return new ContentClientIncremental(aForwarder); michael@0: } michael@0: #endif michael@0: return new ContentClientSingleBuffered(aForwarder); michael@0: } michael@0: michael@0: void michael@0: ContentClient::EndPaint() michael@0: { michael@0: // It is very important that this is called after any overridden EndPaint behaviour, michael@0: // because destroying textures is a three stage process: michael@0: // 1. We are done with the buffer and move it to ContentClient::mOldTextures, michael@0: // that happens in DestroyBuffers which is may be called indirectly from michael@0: // PaintThebes. michael@0: // 2. The content client calls RemoveTextureClient on the texture clients in michael@0: // mOldTextures and forgets them. They then become invalid. The compositable michael@0: // client keeps a record of IDs. This happens in EndPaint. michael@0: // 3. An IPC message is sent to destroy the corresponding texture host. That michael@0: // happens from OnTransaction. michael@0: // It is important that these steps happen in order. michael@0: OnTransaction(); michael@0: } michael@0: michael@0: // We pass a null pointer for the ContentClient Forwarder argument, which means michael@0: // this client will not have a ContentHost on the other side. michael@0: ContentClientBasic::ContentClientBasic() michael@0: : ContentClient(nullptr) michael@0: , RotatedContentBuffer(ContainsVisibleBounds) michael@0: {} michael@0: michael@0: void michael@0: ContentClientBasic::CreateBuffer(ContentType aType, michael@0: const nsIntRect& aRect, michael@0: uint32_t aFlags, michael@0: RefPtr* aBlackDT, michael@0: RefPtr* aWhiteDT) michael@0: { michael@0: MOZ_ASSERT(!(aFlags & BUFFER_COMPONENT_ALPHA)); michael@0: gfxImageFormat format = michael@0: gfxPlatform::GetPlatform()->OptimalFormatForContent(aType); michael@0: michael@0: *aBlackDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( michael@0: IntSize(aRect.width, aRect.height), michael@0: ImageFormatToSurfaceFormat(format)); michael@0: } michael@0: michael@0: void michael@0: ContentClientRemoteBuffer::DestroyBuffers() michael@0: { michael@0: if (!mTextureClient) { michael@0: return; michael@0: } michael@0: michael@0: mOldTextures.AppendElement(mTextureClient); michael@0: mTextureClient = nullptr; michael@0: if (mTextureClientOnWhite) { michael@0: mOldTextures.AppendElement(mTextureClientOnWhite); michael@0: mTextureClientOnWhite = nullptr; michael@0: } michael@0: michael@0: DestroyFrontBuffer(); michael@0: } michael@0: michael@0: void michael@0: ContentClientRemoteBuffer::BeginPaint() michael@0: { michael@0: // XXX: So we might not have a TextureClient yet.. because it will michael@0: // only be created by CreateBuffer.. which will deliver a locked surface!. michael@0: if (mTextureClient) { michael@0: SetBufferProvider(mTextureClient); michael@0: } michael@0: if (mTextureClientOnWhite) { michael@0: SetBufferProviderOnWhite(mTextureClientOnWhite); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ContentClientRemoteBuffer::EndPaint() michael@0: { michael@0: // XXX: We might still not have a texture client if PaintThebes michael@0: // decided we didn't need one yet because the region to draw was empty. michael@0: SetBufferProvider(nullptr); michael@0: SetBufferProviderOnWhite(nullptr); michael@0: for (unsigned i = 0; i< mOldTextures.Length(); ++i) { michael@0: if (mOldTextures[i]->IsLocked()) { michael@0: mOldTextures[i]->Unlock(); michael@0: } michael@0: } michael@0: mOldTextures.Clear(); michael@0: michael@0: if (mTextureClient && mTextureClient->IsLocked()) { michael@0: mTextureClient->Unlock(); michael@0: } michael@0: if (mTextureClientOnWhite && mTextureClientOnWhite->IsLocked()) { michael@0: mTextureClientOnWhite->Unlock(); michael@0: } michael@0: ContentClientRemote::EndPaint(); michael@0: } michael@0: michael@0: bool michael@0: ContentClientRemoteBuffer::CreateAndAllocateTextureClient(RefPtr& aClient, michael@0: TextureFlags aFlags) michael@0: { michael@0: // gfx::BackendType::NONE means fallback to the content backend michael@0: aClient = CreateTextureClientForDrawing(mSurfaceFormat, michael@0: mTextureInfo.mTextureFlags | aFlags, michael@0: gfx::BackendType::NONE, michael@0: mSize); michael@0: if (!aClient) { michael@0: return false; michael@0: } michael@0: michael@0: if (!aClient->AllocateForSurface(mSize, ALLOC_CLEAR_BUFFER)) { michael@0: aClient = CreateTextureClientForDrawing(mSurfaceFormat, michael@0: mTextureInfo.mTextureFlags | TEXTURE_ALLOC_FALLBACK | aFlags, michael@0: gfx::BackendType::NONE, michael@0: mSize); michael@0: if (!aClient) { michael@0: return false; michael@0: } michael@0: if (!aClient->AllocateForSurface(mSize, ALLOC_CLEAR_BUFFER)) { michael@0: NS_WARNING("Could not allocate texture client"); michael@0: aClient = nullptr; michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: NS_WARN_IF_FALSE(aClient->IsValid(), "Created an invalid texture client"); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: ContentClientRemoteBuffer::BuildTextureClients(SurfaceFormat aFormat, michael@0: const nsIntRect& aRect, michael@0: uint32_t aFlags) michael@0: { michael@0: // If we hit this assertion, then it might be due to an empty transaction michael@0: // followed by a real transaction. Our buffers should be created (but not michael@0: // painted in the empty transaction) and then painted (but not created) in the michael@0: // real transaction. That is kind of fragile, and this assert will catch michael@0: // circumstances where we screw that up, e.g., by unnecessarily recreating our michael@0: // buffers. michael@0: NS_ABORT_IF_FALSE(!mIsNewBuffer, michael@0: "Bad! Did we create a buffer twice without painting?"); michael@0: michael@0: mIsNewBuffer = true; michael@0: michael@0: DestroyBuffers(); michael@0: michael@0: mSurfaceFormat = aFormat; michael@0: mSize = gfx::IntSize(aRect.width, aRect.height); michael@0: mTextureInfo.mTextureFlags = TextureFlagsForRotatedContentBufferFlags(aFlags); michael@0: michael@0: if (!CreateAndAllocateTextureClient(mTextureClient, TEXTURE_ON_BLACK) || michael@0: !AddTextureClient(mTextureClient)) { michael@0: AbortTextureClientCreation(); michael@0: return; michael@0: } michael@0: michael@0: if (aFlags & BUFFER_COMPONENT_ALPHA) { michael@0: if (!CreateAndAllocateTextureClient(mTextureClientOnWhite, TEXTURE_ON_WHITE) || michael@0: !AddTextureClient(mTextureClientOnWhite)) { michael@0: AbortTextureClientCreation(); michael@0: return; michael@0: } michael@0: mTextureInfo.mTextureFlags |= TEXTURE_COMPONENT_ALPHA; michael@0: } michael@0: michael@0: CreateFrontBuffer(aRect); michael@0: } michael@0: michael@0: void michael@0: ContentClientRemoteBuffer::CreateBuffer(ContentType aType, michael@0: const nsIntRect& aRect, michael@0: uint32_t aFlags, michael@0: RefPtr* aBlackDT, michael@0: RefPtr* aWhiteDT) michael@0: { michael@0: BuildTextureClients(gfxPlatform::GetPlatform()->Optimal2DFormatForContent(aType), aRect, aFlags); michael@0: if (!mTextureClient) { michael@0: return; michael@0: } michael@0: michael@0: // We just created the textures and we are about to get their draw targets michael@0: // so we have to lock them here. michael@0: DebugOnly locked = mTextureClient->Lock(OPEN_READ_WRITE); michael@0: MOZ_ASSERT(locked, "Could not lock the TextureClient"); michael@0: michael@0: *aBlackDT = mTextureClient->GetAsDrawTarget(); michael@0: if (aFlags & BUFFER_COMPONENT_ALPHA) { michael@0: locked = mTextureClientOnWhite->Lock(OPEN_READ_WRITE); michael@0: MOZ_ASSERT(locked, "Could not lock the second TextureClient for component alpha"); michael@0: michael@0: *aWhiteDT = mTextureClientOnWhite->GetAsDrawTarget(); michael@0: } michael@0: } michael@0: michael@0: nsIntRegion michael@0: ContentClientRemoteBuffer::GetUpdatedRegion(const nsIntRegion& aRegionToDraw, michael@0: const nsIntRegion& aVisibleRegion, michael@0: bool aDidSelfCopy) michael@0: { michael@0: nsIntRegion updatedRegion; michael@0: if (mIsNewBuffer || aDidSelfCopy) { michael@0: // A buffer reallocation clears both buffers. The front buffer has all the michael@0: // content by now, but the back buffer is still clear. Here, in effect, we michael@0: // are saying to copy all of the pixels of the front buffer to the back. michael@0: // Also when we self-copied in the buffer, the buffer space michael@0: // changes and some changed buffer content isn't reflected in the michael@0: // draw or invalidate region (on purpose!). When this happens, we michael@0: // need to read back the entire buffer too. michael@0: updatedRegion = aVisibleRegion; michael@0: mIsNewBuffer = false; michael@0: } else { michael@0: updatedRegion = aRegionToDraw; michael@0: } michael@0: michael@0: NS_ASSERTION(BufferRect().Contains(aRegionToDraw.GetBounds()), michael@0: "Update outside of buffer rect!"); michael@0: NS_ABORT_IF_FALSE(mTextureClient, "should have a back buffer by now"); michael@0: michael@0: return updatedRegion; michael@0: } michael@0: michael@0: void michael@0: ContentClientRemoteBuffer::Updated(const nsIntRegion& aRegionToDraw, michael@0: const nsIntRegion& aVisibleRegion, michael@0: bool aDidSelfCopy) michael@0: { michael@0: nsIntRegion updatedRegion = GetUpdatedRegion(aRegionToDraw, michael@0: aVisibleRegion, michael@0: aDidSelfCopy); michael@0: michael@0: MOZ_ASSERT(mTextureClient); michael@0: if (mTextureClientOnWhite) { michael@0: mForwarder->UseComponentAlphaTextures(this, mTextureClient, michael@0: mTextureClientOnWhite); michael@0: } else { michael@0: mForwarder->UseTexture(this, mTextureClient); michael@0: } michael@0: mForwarder->UpdateTextureRegion(this, michael@0: ThebesBufferData(BufferRect(), michael@0: BufferRotation()), michael@0: updatedRegion); michael@0: } michael@0: michael@0: void michael@0: ContentClientRemoteBuffer::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) michael@0: { michael@0: MOZ_ASSERT(mTextureClient); michael@0: mFrontAndBackBufferDiffer = true; michael@0: } michael@0: michael@0: void michael@0: ContentClientDoubleBuffered::CreateFrontBuffer(const nsIntRect& aBufferRect) michael@0: { michael@0: if (!CreateAndAllocateTextureClient(mFrontClient, TEXTURE_ON_BLACK) || michael@0: !AddTextureClient(mFrontClient)) { michael@0: AbortTextureClientCreation(); michael@0: return; michael@0: } michael@0: if (mTextureInfo.mTextureFlags & TEXTURE_COMPONENT_ALPHA) { michael@0: if (!CreateAndAllocateTextureClient(mFrontClientOnWhite, TEXTURE_ON_WHITE) || michael@0: !AddTextureClient(mFrontClientOnWhite)) { michael@0: AbortTextureClientCreation(); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: mFrontBufferRect = aBufferRect; michael@0: mFrontBufferRotation = nsIntPoint(); michael@0: } michael@0: michael@0: void michael@0: ContentClientDoubleBuffered::DestroyFrontBuffer() michael@0: { michael@0: MOZ_ASSERT(mFrontClient); michael@0: michael@0: mOldTextures.AppendElement(mFrontClient); michael@0: mFrontClient = nullptr; michael@0: if (mFrontClientOnWhite) { michael@0: mOldTextures.AppendElement(mFrontClientOnWhite); michael@0: mFrontClientOnWhite = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: ContentClientDoubleBuffered::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) michael@0: { michael@0: mFrontUpdatedRegion = aFrontUpdatedRegion; michael@0: michael@0: RefPtr oldBack = mTextureClient; michael@0: mTextureClient = mFrontClient; michael@0: mFrontClient = oldBack; michael@0: michael@0: oldBack = mTextureClientOnWhite; michael@0: mTextureClientOnWhite = mFrontClientOnWhite; michael@0: mFrontClientOnWhite = oldBack; michael@0: michael@0: nsIntRect oldBufferRect = mBufferRect; michael@0: mBufferRect = mFrontBufferRect; michael@0: mFrontBufferRect = oldBufferRect; michael@0: michael@0: nsIntPoint oldBufferRotation = mBufferRotation; michael@0: mBufferRotation = mFrontBufferRotation; michael@0: mFrontBufferRotation = oldBufferRotation; michael@0: michael@0: MOZ_ASSERT(mFrontClient); michael@0: michael@0: ContentClientRemoteBuffer::SwapBuffers(aFrontUpdatedRegion); michael@0: } michael@0: michael@0: void michael@0: ContentClientDoubleBuffered::BeginPaint() michael@0: { michael@0: ContentClientRemoteBuffer::BeginPaint(); michael@0: michael@0: mIsNewBuffer = false; michael@0: michael@0: if (!mFrontAndBackBufferDiffer) { michael@0: return; michael@0: } michael@0: michael@0: if (mDidSelfCopy) { michael@0: // We can't easily draw our front buffer into us, since we're going to be michael@0: // copying stuff around anyway it's easiest if we just move our situation michael@0: // to non-rotated while we're at it. If this situation occurs we'll have michael@0: // hit a self-copy path in PaintThebes before as well anyway. michael@0: mBufferRect.MoveTo(mFrontBufferRect.TopLeft()); michael@0: mBufferRotation = nsIntPoint(); michael@0: return; michael@0: } michael@0: mBufferRect = mFrontBufferRect; michael@0: mBufferRotation = mFrontBufferRotation; michael@0: } michael@0: michael@0: // Sync front/back buffers content michael@0: // After executing, the new back buffer has the same (interesting) pixels as michael@0: // the new front buffer, and mValidRegion et al. are correct wrt the new michael@0: // back buffer (i.e. as they were for the old back buffer) michael@0: void michael@0: ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw) michael@0: { michael@0: if (mTextureClient) { michael@0: DebugOnly locked = mTextureClient->Lock(OPEN_READ_WRITE); michael@0: MOZ_ASSERT(locked); michael@0: } michael@0: if (mTextureClientOnWhite) { michael@0: DebugOnly locked = mTextureClientOnWhite->Lock(OPEN_READ_WRITE); michael@0: MOZ_ASSERT(locked); michael@0: } michael@0: michael@0: if (!mFrontAndBackBufferDiffer) { michael@0: MOZ_ASSERT(!mDidSelfCopy, "If we have to copy the world, then our buffers are different, right?"); michael@0: return; michael@0: } michael@0: MOZ_ASSERT(mFrontClient); michael@0: michael@0: MOZ_LAYERS_LOG(("BasicShadowableThebes(%p): reading back ", michael@0: this, michael@0: mFrontUpdatedRegion.GetBounds().x, michael@0: mFrontUpdatedRegion.GetBounds().y, michael@0: mFrontUpdatedRegion.GetBounds().width, michael@0: mFrontUpdatedRegion.GetBounds().height)); michael@0: michael@0: mFrontAndBackBufferDiffer = false; michael@0: michael@0: nsIntRegion updateRegion = mFrontUpdatedRegion; michael@0: if (mDidSelfCopy) { michael@0: mDidSelfCopy = false; michael@0: updateRegion = mBufferRect; michael@0: } michael@0: michael@0: // No point in sync'ing what we are going to draw over anyway. And if there is michael@0: // nothing to sync at all, there is nothing to do and we can go home early. michael@0: updateRegion.Sub(updateRegion, aRegionToDraw); michael@0: if (updateRegion.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: // We need to ensure that we lock these two buffers in the same michael@0: // order as the compositor to prevent deadlocks. michael@0: if (!mFrontClient->Lock(OPEN_READ_ONLY)) { michael@0: return; michael@0: } michael@0: if (mFrontClientOnWhite && michael@0: !mFrontClientOnWhite->Lock(OPEN_READ_ONLY)) { michael@0: mFrontClient->Unlock(); michael@0: return; michael@0: } michael@0: { michael@0: // Restrict the DrawTargets and frontBuffer to a scope to make michael@0: // sure there is no more external references to the DrawTargets michael@0: // when we Unlock the TextureClients. michael@0: RefPtr dt = mFrontClient->GetAsDrawTarget(); michael@0: RefPtr dtOnWhite = mFrontClientOnWhite michael@0: ? mFrontClientOnWhite->GetAsDrawTarget() michael@0: : nullptr; michael@0: RotatedBuffer frontBuffer(dt, michael@0: dtOnWhite, michael@0: mFrontBufferRect, michael@0: mFrontBufferRotation); michael@0: UpdateDestinationFrom(frontBuffer, updateRegion); michael@0: } michael@0: michael@0: mFrontClient->Unlock(); michael@0: if (mFrontClientOnWhite) { michael@0: mFrontClientOnWhite->Unlock(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ContentClientDoubleBuffered::UpdateDestinationFrom(const RotatedBuffer& aSource, michael@0: const nsIntRegion& aUpdateRegion) michael@0: { michael@0: DrawIterator iter; michael@0: while (DrawTarget* destDT = michael@0: BorrowDrawTargetForQuadrantUpdate(aUpdateRegion.GetBounds(), BUFFER_BLACK, &iter)) { michael@0: bool isClippingCheap = IsClippingCheap(destDT, iter.mDrawRegion); michael@0: if (isClippingCheap) { michael@0: gfxUtils::ClipToRegion(destDT, iter.mDrawRegion); michael@0: } michael@0: michael@0: aSource.DrawBufferWithRotation(destDT, BUFFER_BLACK, 1.0, CompositionOp::OP_SOURCE); michael@0: if (isClippingCheap) { michael@0: destDT->PopClip(); michael@0: } michael@0: // Flush the destination before the sources become inaccessible (Unlock). michael@0: destDT->Flush(); michael@0: ReturnDrawTargetToBuffer(destDT); michael@0: } michael@0: michael@0: if (aSource.HaveBufferOnWhite()) { michael@0: MOZ_ASSERT(HaveBufferOnWhite()); michael@0: DrawIterator whiteIter; michael@0: while (DrawTarget* destDT = michael@0: BorrowDrawTargetForQuadrantUpdate(aUpdateRegion.GetBounds(), BUFFER_WHITE, &whiteIter)) { michael@0: bool isClippingCheap = IsClippingCheap(destDT, whiteIter.mDrawRegion); michael@0: if (isClippingCheap) { michael@0: gfxUtils::ClipToRegion(destDT, whiteIter.mDrawRegion); michael@0: } michael@0: michael@0: aSource.DrawBufferWithRotation(destDT, BUFFER_WHITE, 1.0, CompositionOp::OP_SOURCE); michael@0: if (isClippingCheap) { michael@0: destDT->PopClip(); michael@0: } michael@0: // Flush the destination before the sources become inaccessible (Unlock). michael@0: destDT->Flush(); michael@0: ReturnDrawTargetToBuffer(destDT); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: ContentClientSingleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw) michael@0: { michael@0: if (mTextureClient) { michael@0: DebugOnly locked = mTextureClient->Lock(OPEN_READ_WRITE); michael@0: MOZ_ASSERT(locked); michael@0: } michael@0: if (mTextureClientOnWhite) { michael@0: DebugOnly locked = mTextureClientOnWhite->Lock(OPEN_READ_WRITE); michael@0: MOZ_ASSERT(locked); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: WrapRotationAxis(int32_t* aRotationPoint, int32_t aSize) michael@0: { michael@0: if (*aRotationPoint < 0) { michael@0: *aRotationPoint += aSize; michael@0: } else if (*aRotationPoint >= aSize) { michael@0: *aRotationPoint -= aSize; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: FillSurface(DrawTarget* aDT, const nsIntRegion& aRegion, michael@0: const nsIntPoint& aOffset, const gfxRGBA& aColor) michael@0: { michael@0: nsIntRegionRectIterator iter(aRegion); michael@0: const nsIntRect* r; michael@0: while ((r = iter.Next()) != nullptr) { michael@0: aDT->FillRect(Rect(r->x - aOffset.x, r->y - aOffset.y, michael@0: r->width, r->height), michael@0: ColorPattern(ToColor(aColor))); michael@0: } michael@0: } michael@0: michael@0: RotatedContentBuffer::PaintState michael@0: ContentClientIncremental::BeginPaintBuffer(ThebesLayer* aLayer, michael@0: uint32_t aFlags) michael@0: { michael@0: mTextureInfo.mDeprecatedTextureHostFlags = 0; michael@0: PaintState result; michael@0: // We need to disable rotation if we're going to be resampled when michael@0: // drawing, because we might sample across the rotation boundary. michael@0: bool canHaveRotation = !(aFlags & RotatedContentBuffer::PAINT_WILL_RESAMPLE); michael@0: michael@0: nsIntRegion validRegion = aLayer->GetValidRegion(); michael@0: michael@0: bool canUseOpaqueSurface = aLayer->CanUseOpaqueSurface(); michael@0: ContentType contentType = michael@0: canUseOpaqueSurface ? gfxContentType::COLOR : michael@0: gfxContentType::COLOR_ALPHA; michael@0: michael@0: SurfaceMode mode; michael@0: nsIntRegion neededRegion; michael@0: bool canReuseBuffer; michael@0: nsIntRect destBufferRect; michael@0: michael@0: while (true) { michael@0: mode = aLayer->GetSurfaceMode(); michael@0: neededRegion = aLayer->GetVisibleRegion(); michael@0: // If we're going to resample, we need a buffer that's in clamp mode. michael@0: canReuseBuffer = neededRegion.GetBounds().Size() <= mBufferRect.Size() && michael@0: mHasBuffer && michael@0: (!(aFlags & RotatedContentBuffer::PAINT_WILL_RESAMPLE) || michael@0: !(mTextureInfo.mTextureFlags & TEXTURE_ALLOW_REPEAT)); michael@0: michael@0: if (canReuseBuffer) { michael@0: if (mBufferRect.Contains(neededRegion.GetBounds())) { michael@0: // We don't need to adjust mBufferRect. michael@0: destBufferRect = mBufferRect; michael@0: } else { michael@0: // The buffer's big enough but doesn't contain everything that's michael@0: // going to be visible. We'll move it. michael@0: destBufferRect = nsIntRect(neededRegion.GetBounds().TopLeft(), mBufferRect.Size()); michael@0: } michael@0: } else { michael@0: destBufferRect = neededRegion.GetBounds(); michael@0: } michael@0: michael@0: if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { michael@0: if (!gfxPrefs::ComponentAlphaEnabled() || michael@0: !aLayer->GetParent() || michael@0: !aLayer->GetParent()->SupportsComponentAlphaChildren()) { michael@0: mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; michael@0: } else { michael@0: contentType = gfxContentType::COLOR; michael@0: } michael@0: } michael@0: michael@0: if ((aFlags & RotatedContentBuffer::PAINT_WILL_RESAMPLE) && michael@0: (!neededRegion.GetBounds().IsEqualInterior(destBufferRect) || michael@0: neededRegion.GetNumRects() > 1)) { michael@0: // The area we add to neededRegion might not be painted opaquely michael@0: if (mode == SurfaceMode::SURFACE_OPAQUE) { michael@0: contentType = gfxContentType::COLOR_ALPHA; michael@0: mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; michael@0: } michael@0: // For component alpha layers, we leave contentType as gfxContentType::COLOR. michael@0: michael@0: // We need to validate the entire buffer, to make sure that only valid michael@0: // pixels are sampled michael@0: neededRegion = destBufferRect; michael@0: } michael@0: michael@0: if (mHasBuffer && michael@0: (mContentType != contentType || michael@0: (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != mHasBufferOnWhite)) { michael@0: // We're effectively clearing the valid region, so we need to draw michael@0: // the entire needed region now. michael@0: result.mRegionToInvalidate = aLayer->GetValidRegion(); michael@0: validRegion.SetEmpty(); michael@0: mHasBuffer = false; michael@0: mHasBufferOnWhite = false; michael@0: mBufferRect.SetRect(0, 0, 0, 0); michael@0: mBufferRotation.MoveTo(0, 0); michael@0: // Restart decision process with the cleared buffer. We can only go michael@0: // around the loop one more iteration, since mTexImage is null now. michael@0: continue; michael@0: } michael@0: michael@0: break; michael@0: } michael@0: michael@0: result.mRegionToDraw.Sub(neededRegion, validRegion); michael@0: if (result.mRegionToDraw.IsEmpty()) michael@0: return result; michael@0: michael@0: if (destBufferRect.width > mForwarder->GetMaxTextureSize() || michael@0: destBufferRect.height > mForwarder->GetMaxTextureSize()) { michael@0: return result; michael@0: } michael@0: michael@0: // BlitTextureImage depends on the FBO texture target being michael@0: // TEXTURE_2D. This isn't the case on some older X1600-era Radeons. michael@0: if (!mForwarder->SupportsTextureBlitting() || michael@0: !mForwarder->SupportsPartialUploads()) { michael@0: result.mRegionToDraw = neededRegion; michael@0: validRegion.SetEmpty(); michael@0: mHasBuffer = false; michael@0: mHasBufferOnWhite = false; michael@0: mBufferRect.SetRect(0, 0, 0, 0); michael@0: mBufferRotation.MoveTo(0, 0); michael@0: canReuseBuffer = false; michael@0: } michael@0: michael@0: nsIntRect drawBounds = result.mRegionToDraw.GetBounds(); michael@0: bool createdBuffer = false; michael@0: michael@0: uint32_t bufferFlags = canHaveRotation ? TEXTURE_ALLOW_REPEAT : 0; michael@0: if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { michael@0: bufferFlags |= TEXTURE_COMPONENT_ALPHA; michael@0: } michael@0: if (canReuseBuffer) { michael@0: nsIntRect keepArea; michael@0: if (keepArea.IntersectRect(destBufferRect, mBufferRect)) { michael@0: // Set mBufferRotation so that the pixels currently in mBuffer michael@0: // will still be rendered in the right place when mBufferRect michael@0: // changes to destBufferRect. michael@0: nsIntPoint newRotation = mBufferRotation + michael@0: (destBufferRect.TopLeft() - mBufferRect.TopLeft()); michael@0: WrapRotationAxis(&newRotation.x, mBufferRect.width); michael@0: WrapRotationAxis(&newRotation.y, mBufferRect.height); michael@0: NS_ASSERTION(nsIntRect(nsIntPoint(0,0), mBufferRect.Size()).Contains(newRotation), michael@0: "newRotation out of bounds"); michael@0: int32_t xBoundary = destBufferRect.XMost() - newRotation.x; michael@0: int32_t yBoundary = destBufferRect.YMost() - newRotation.y; michael@0: if ((drawBounds.x < xBoundary && xBoundary < drawBounds.XMost()) || michael@0: (drawBounds.y < yBoundary && yBoundary < drawBounds.YMost()) || michael@0: (newRotation != nsIntPoint(0,0) && !canHaveRotation)) { michael@0: // The stuff we need to redraw will wrap around an edge of the michael@0: // buffer, so we will need to do a self-copy michael@0: // If mBufferRotation == nsIntPoint(0,0) we could do a real michael@0: // self-copy but we're not going to do that in GL yet. michael@0: // We can't do a real self-copy because the buffer is rotated. michael@0: // So allocate a new buffer for the destination. michael@0: destBufferRect = neededRegion.GetBounds(); michael@0: createdBuffer = true; michael@0: } else { michael@0: mBufferRect = destBufferRect; michael@0: mBufferRotation = newRotation; michael@0: } michael@0: } else { michael@0: // No pixels are going to be kept. The whole visible region michael@0: // will be redrawn, so we don't need to copy anything, so we don't michael@0: // set destBuffer. michael@0: mBufferRect = destBufferRect; michael@0: mBufferRotation = nsIntPoint(0,0); michael@0: } michael@0: } else { michael@0: // The buffer's not big enough, so allocate a new one michael@0: createdBuffer = true; michael@0: } michael@0: NS_ASSERTION(!(aFlags & RotatedContentBuffer::PAINT_WILL_RESAMPLE) || michael@0: destBufferRect == neededRegion.GetBounds(), michael@0: "If we're resampling, we need to validate the entire buffer"); michael@0: michael@0: if (!createdBuffer && !mHasBuffer) { michael@0: return result; michael@0: } michael@0: michael@0: if (createdBuffer) { michael@0: if (mHasBuffer && michael@0: (mode != SurfaceMode::SURFACE_COMPONENT_ALPHA || mHasBufferOnWhite)) { michael@0: mTextureInfo.mDeprecatedTextureHostFlags = TEXTURE_HOST_COPY_PREVIOUS; michael@0: } michael@0: michael@0: mHasBuffer = true; michael@0: if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { michael@0: mHasBufferOnWhite = true; michael@0: } michael@0: mBufferRect = destBufferRect; michael@0: mBufferRotation = nsIntPoint(0,0); michael@0: NotifyBufferCreated(contentType, bufferFlags); michael@0: } michael@0: michael@0: NS_ASSERTION(canHaveRotation || mBufferRotation == nsIntPoint(0,0), michael@0: "Rotation disabled, but we have nonzero rotation?"); michael@0: michael@0: nsIntRegion invalidate; michael@0: invalidate.Sub(aLayer->GetValidRegion(), destBufferRect); michael@0: result.mRegionToInvalidate.Or(result.mRegionToInvalidate, invalidate); michael@0: michael@0: // If we do partial updates, we have to clip drawing to the regionToDraw. michael@0: // If we don't clip, background images will be fillrect'd to the region correctly, michael@0: // while text or lines will paint outside of the regionToDraw. This becomes apparent michael@0: // with concave regions. Right now the scrollbars invalidate a narrow strip of the bar michael@0: // although they never cover it. This leads to two draw rects, the narow strip and the actually michael@0: // newly exposed area. It would be wise to fix this glitch in any way to have simpler michael@0: // clip and draw regions. michael@0: result.mClip = DrawRegionClip::DRAW; michael@0: result.mMode = mode; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: DrawTarget* michael@0: ContentClientIncremental::BorrowDrawTargetForPainting(const PaintState& aPaintState, michael@0: RotatedContentBuffer::DrawIterator* aIter) michael@0: { michael@0: if (aPaintState.mMode == SurfaceMode::SURFACE_NONE) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (aIter) { michael@0: if (aIter->mCount++ > 0) { michael@0: return nullptr; michael@0: } michael@0: aIter->mDrawRegion = aPaintState.mRegionToDraw; michael@0: } michael@0: michael@0: DrawTarget* result = nullptr; michael@0: michael@0: nsIntRect drawBounds = aPaintState.mRegionToDraw.GetBounds(); michael@0: MOZ_ASSERT(!mLoanedDrawTarget); michael@0: michael@0: // BeginUpdate is allowed to modify the given region, michael@0: // if it wants more to be repainted than we request. michael@0: if (aPaintState.mMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { michael@0: nsIntRegion drawRegionCopy = aPaintState.mRegionToDraw; michael@0: RefPtr onBlack = GetUpdateSurface(BUFFER_BLACK, drawRegionCopy); michael@0: RefPtr onWhite = GetUpdateSurface(BUFFER_WHITE, aPaintState.mRegionToDraw); michael@0: if (onBlack && onWhite) { michael@0: NS_ASSERTION(aPaintState.mRegionToDraw == drawRegionCopy, michael@0: "BeginUpdate should always modify the draw region in the same way!"); michael@0: FillSurface(onBlack, aPaintState.mRegionToDraw, nsIntPoint(drawBounds.x, drawBounds.y), gfxRGBA(0.0, 0.0, 0.0, 1.0)); michael@0: FillSurface(onWhite, aPaintState.mRegionToDraw, nsIntPoint(drawBounds.x, drawBounds.y), gfxRGBA(1.0, 1.0, 1.0, 1.0)); michael@0: mLoanedDrawTarget = Factory::CreateDualDrawTarget(onBlack, onWhite); michael@0: } else { michael@0: mLoanedDrawTarget = nullptr; michael@0: } michael@0: } else { michael@0: mLoanedDrawTarget = GetUpdateSurface(BUFFER_BLACK, aPaintState.mRegionToDraw); michael@0: } michael@0: if (!mLoanedDrawTarget) { michael@0: NS_WARNING("unable to get context for update"); michael@0: return nullptr; michael@0: } michael@0: michael@0: result = mLoanedDrawTarget; michael@0: mLoanedTransform = mLoanedDrawTarget->GetTransform(); michael@0: mLoanedTransform.Translate(-drawBounds.x, -drawBounds.y); michael@0: result->SetTransform(mLoanedTransform); michael@0: mLoanedTransform.Translate(drawBounds.x, drawBounds.y); michael@0: michael@0: if (mContentType == gfxContentType::COLOR_ALPHA) { michael@0: gfxUtils::ClipToRegion(result, aPaintState.mRegionToDraw); michael@0: nsIntRect bounds = aPaintState.mRegionToDraw.GetBounds(); michael@0: result->ClearRect(Rect(bounds.x, bounds.y, bounds.width, bounds.height)); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: void michael@0: ContentClientIncremental::Updated(const nsIntRegion& aRegionToDraw, michael@0: const nsIntRegion& aVisibleRegion, michael@0: bool aDidSelfCopy) michael@0: { michael@0: if (IsSurfaceDescriptorValid(mUpdateDescriptor)) { michael@0: mForwarder->UpdateTextureIncremental(this, michael@0: TextureFront, michael@0: mUpdateDescriptor, michael@0: aRegionToDraw, michael@0: mBufferRect, michael@0: mBufferRotation); michael@0: mUpdateDescriptor = SurfaceDescriptor(); michael@0: } michael@0: if (IsSurfaceDescriptorValid(mUpdateDescriptorOnWhite)) { michael@0: mForwarder->UpdateTextureIncremental(this, michael@0: TextureOnWhiteFront, michael@0: mUpdateDescriptorOnWhite, michael@0: aRegionToDraw, michael@0: mBufferRect, michael@0: mBufferRotation); michael@0: mUpdateDescriptorOnWhite = SurfaceDescriptor(); michael@0: } michael@0: michael@0: } michael@0: michael@0: TemporaryRef michael@0: ContentClientIncremental::GetUpdateSurface(BufferType aType, michael@0: const nsIntRegion& aUpdateRegion) michael@0: { michael@0: nsIntRect rgnSize = aUpdateRegion.GetBounds(); michael@0: if (!mBufferRect.Contains(rgnSize)) { michael@0: NS_ERROR("update outside of image"); michael@0: return nullptr; michael@0: } michael@0: SurfaceDescriptor desc; michael@0: if (!mForwarder->AllocSurfaceDescriptor(rgnSize.Size().ToIntSize(), michael@0: mContentType, michael@0: &desc)) { michael@0: NS_WARNING("creating SurfaceDescriptor failed!"); michael@0: Clear(); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (aType == BUFFER_BLACK) { michael@0: MOZ_ASSERT(!IsSurfaceDescriptorValid(mUpdateDescriptor)); michael@0: mUpdateDescriptor = desc; michael@0: } else { michael@0: MOZ_ASSERT(!IsSurfaceDescriptorValid(mUpdateDescriptorOnWhite)); michael@0: MOZ_ASSERT(aType == BUFFER_WHITE); michael@0: mUpdateDescriptorOnWhite = desc; michael@0: } michael@0: michael@0: return GetDrawTargetForDescriptor(desc, gfx::BackendType::COREGRAPHICS); michael@0: } michael@0: michael@0: } michael@0: }