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 file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ClientTiledThebesLayer.h" michael@0: #include "FrameMetrics.h" // for FrameMetrics michael@0: #include "Units.h" // for ScreenIntRect, CSSPoint, etc michael@0: #include "UnitTransforms.h" // for TransformTo michael@0: #include "ClientLayerManager.h" // for ClientLayerManager, etc michael@0: #include "gfx3DMatrix.h" // for gfx3DMatrix michael@0: #include "gfxPlatform.h" // for gfxPlatform michael@0: #include "gfxPrefs.h" // for gfxPrefs michael@0: #include "gfxRect.h" // for gfxRect michael@0: #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc michael@0: #include "mozilla/gfx/BaseSize.h" // for BaseSize michael@0: #include "mozilla/gfx/Rect.h" // for Rect, RectTyped michael@0: #include "mozilla/layers/LayersMessages.h" michael@0: #include "mozilla/mozalloc.h" // for operator delete, etc michael@0: #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc michael@0: #include "nsRect.h" // for nsIntRect michael@0: michael@0: namespace mozilla { michael@0: namespace layers { michael@0: michael@0: michael@0: ClientTiledThebesLayer::ClientTiledThebesLayer(ClientLayerManager* const aManager) michael@0: : ThebesLayer(aManager, michael@0: static_cast(MOZ_THIS_IN_INITIALIZER_LIST())) michael@0: , mContentClient() michael@0: { michael@0: MOZ_COUNT_CTOR(ClientTiledThebesLayer); michael@0: mPaintData.mLastScrollOffset = ParentLayerPoint(0, 0); michael@0: mPaintData.mFirstPaint = true; michael@0: } michael@0: michael@0: ClientTiledThebesLayer::~ClientTiledThebesLayer() michael@0: { michael@0: MOZ_COUNT_DTOR(ClientTiledThebesLayer); michael@0: } michael@0: michael@0: void michael@0: ClientTiledThebesLayer::ClearCachedResources() michael@0: { michael@0: if (mContentClient) { michael@0: mContentClient->ClearCachedResources(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ClientTiledThebesLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs) michael@0: { michael@0: aAttrs = ThebesLayerAttributes(GetValidRegion()); michael@0: } michael@0: michael@0: static LayoutDeviceRect michael@0: ApplyParentLayerToLayoutTransform(const gfx3DMatrix& aTransform, const ParentLayerRect& aParentLayerRect) michael@0: { michael@0: return TransformTo(aTransform, aParentLayerRect); michael@0: } michael@0: michael@0: void michael@0: ClientTiledThebesLayer::BeginPaint() michael@0: { michael@0: if (ClientManager()->IsRepeatTransaction()) { michael@0: return; michael@0: } michael@0: michael@0: mPaintData.mLowPrecisionPaintCount = 0; michael@0: mPaintData.mPaintFinished = false; michael@0: mPaintData.mCompositionBounds.SetEmpty(); michael@0: mPaintData.mCriticalDisplayPort.SetEmpty(); michael@0: michael@0: if (!GetBaseTransform().Is2DIntegerTranslation()) { michael@0: // Give up if the layer is transformed. The code below assumes that there michael@0: // is no transform set, and not making that assumption would cause huge michael@0: // complication to handle a quite rare case. michael@0: // michael@0: // FIXME The intention is to bail out of this function when there's a CSS michael@0: // transform set on the layer, but unfortunately there's no way to michael@0: // distinguish transforms due to scrolling from transforms due to michael@0: // CSS transforms. michael@0: // michael@0: // Because of this, there may be unintended behaviour when setting michael@0: // 2d CSS translations on the children of scrollable displayport michael@0: // layers. michael@0: return; michael@0: } michael@0: michael@0: #ifdef MOZ_WIDGET_ANDROID michael@0: // Subframes on Fennec are not async scrollable because they have no displayport. michael@0: // However, the code in RenderLayer() picks up a displayport from the nearest michael@0: // scrollable ancestor container layer anyway, which is incorrect for Fennec. This michael@0: // behaviour results in the subframe getting clipped improperly and perma-blank areas michael@0: // while scrolling the subframe. To work around this, we detect if this layer is michael@0: // the primary scrollable layer and disable the tiling behaviour if it is not. michael@0: bool isPrimaryScrollableThebesLayer = false; michael@0: if (Layer* scrollable = ClientManager()->GetPrimaryScrollableLayer()) { michael@0: if (GetParent() == scrollable) { michael@0: for (Layer* child = scrollable->GetFirstChild(); child; child = child->GetNextSibling()) { michael@0: if (child->GetType() == Layer::TYPE_THEBES) { michael@0: if (child == this) { michael@0: // |this| is the first thebes layer child of the GetPrimaryScrollableLayer() michael@0: isPrimaryScrollableThebesLayer = true; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (!isPrimaryScrollableThebesLayer) { michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: // Get the metrics of the nearest scrollable layer and the nearest layer michael@0: // with a displayport. michael@0: ContainerLayer* displayPortParent = nullptr; michael@0: ContainerLayer* scrollParent = nullptr; michael@0: for (ContainerLayer* parent = GetParent(); parent; parent = parent->GetParent()) { michael@0: const FrameMetrics& metrics = parent->GetFrameMetrics(); michael@0: if (!scrollParent && metrics.GetScrollId() != FrameMetrics::NULL_SCROLL_ID) { michael@0: scrollParent = parent; michael@0: } michael@0: if (!metrics.mDisplayPort.IsEmpty()) { michael@0: displayPortParent = parent; michael@0: // Any layer that has a displayport must be scrollable, so we can break michael@0: // here. michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!displayPortParent || !scrollParent) { michael@0: // No displayport or scroll parent, so we can't do progressive rendering. michael@0: // Just set the composition bounds to empty and return. michael@0: #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_B2G) michael@0: // Both Android and b2g are guaranteed to have a displayport set, so this michael@0: // should never happen. michael@0: NS_WARNING("Tiled Thebes layer with no scrollable container parent"); michael@0: #endif michael@0: return; michael@0: } michael@0: michael@0: // Note, not handling transformed layers lets us assume that LayoutDevice michael@0: // space of the scroll parent layer is the same as LayoutDevice space of michael@0: // this layer. michael@0: const FrameMetrics& scrollMetrics = scrollParent->GetFrameMetrics(); michael@0: const FrameMetrics& displayportMetrics = displayPortParent->GetFrameMetrics(); michael@0: michael@0: // Calculate the transform required to convert ParentLayer space of our michael@0: // display port parent to LayoutDevice space of this layer. michael@0: gfx::Matrix4x4 transform = scrollParent->GetTransform(); michael@0: ContainerLayer* displayPortParentParent = displayPortParent->GetParent() ? michael@0: displayPortParent->GetParent()->GetParent() : nullptr; michael@0: for (ContainerLayer* parent = scrollParent->GetParent(); michael@0: parent != displayPortParentParent; michael@0: parent = parent->GetParent()) { michael@0: transform = transform * parent->GetTransform(); michael@0: } michael@0: gfx3DMatrix layoutDeviceToScrollParentLayer; michael@0: gfx::To3DMatrix(transform, layoutDeviceToScrollParentLayer); michael@0: layoutDeviceToScrollParentLayer.ScalePost(scrollMetrics.mCumulativeResolution.scale, michael@0: scrollMetrics.mCumulativeResolution.scale, michael@0: 1.f); michael@0: michael@0: mPaintData.mTransformParentLayerToLayoutDevice = layoutDeviceToScrollParentLayer.Inverse(); michael@0: michael@0: // Compute the critical display port of the display port layer in michael@0: // LayoutDevice space of this layer. michael@0: ParentLayerRect criticalDisplayPort = michael@0: (displayportMetrics.mCriticalDisplayPort + displayportMetrics.GetScrollOffset()) * michael@0: displayportMetrics.GetZoomToParent(); michael@0: mPaintData.mCriticalDisplayPort = LayoutDeviceIntRect::ToUntyped(RoundedOut( michael@0: ApplyParentLayerToLayoutTransform(mPaintData.mTransformParentLayerToLayoutDevice, michael@0: criticalDisplayPort))); michael@0: michael@0: // Compute the viewport of the display port layer in LayoutDevice space of michael@0: // this layer. michael@0: ParentLayerRect viewport = michael@0: (displayportMetrics.mViewport + displayportMetrics.GetScrollOffset()) * michael@0: displayportMetrics.GetZoomToParent(); michael@0: mPaintData.mViewport = ApplyParentLayerToLayoutTransform( michael@0: mPaintData.mTransformParentLayerToLayoutDevice, viewport); michael@0: michael@0: // Store the scroll parent resolution. Because this is Gecko-side, before any michael@0: // async transforms have occurred, we can use the zoom for this. michael@0: mPaintData.mResolution = displayportMetrics.GetZoomToParent(); michael@0: michael@0: // Store the parent composition bounds in LayoutDevice units. michael@0: // This is actually in LayoutDevice units of the scrollParent's parent layer, michael@0: // but because there is no transform, we can assume that these are the same. michael@0: mPaintData.mCompositionBounds = michael@0: scrollMetrics.mCompositionBounds / scrollMetrics.GetParentResolution(); michael@0: michael@0: // Calculate the scroll offset since the last transaction michael@0: mPaintData.mScrollOffset = displayportMetrics.GetScrollOffset() * displayportMetrics.GetZoomToParent(); michael@0: } michael@0: michael@0: void michael@0: ClientTiledThebesLayer::EndPaint(bool aFinish) michael@0: { michael@0: if (!aFinish && !mPaintData.mPaintFinished) { michael@0: return; michael@0: } michael@0: michael@0: mPaintData.mLastScrollOffset = mPaintData.mScrollOffset; michael@0: mPaintData.mPaintFinished = true; michael@0: mPaintData.mFirstPaint = false; michael@0: } michael@0: michael@0: void michael@0: ClientTiledThebesLayer::RenderLayer() michael@0: { michael@0: LayerManager::DrawThebesLayerCallback callback = michael@0: ClientManager()->GetThebesLayerCallback(); michael@0: void *data = ClientManager()->GetThebesLayerCallbackData(); michael@0: if (!callback) { michael@0: ClientManager()->SetTransactionIncomplete(); michael@0: return; michael@0: } michael@0: michael@0: if (!mContentClient) { michael@0: mContentClient = new TiledContentClient(this, ClientManager()); michael@0: michael@0: mContentClient->Connect(); michael@0: ClientManager()->AsShadowForwarder()->Attach(mContentClient, this); michael@0: MOZ_ASSERT(mContentClient->GetForwarder()); michael@0: } michael@0: michael@0: if (mContentClient->mTiledBuffer.HasFormatChanged()) { michael@0: mValidRegion = nsIntRegion(); michael@0: } michael@0: michael@0: nsIntRegion invalidRegion = mVisibleRegion; michael@0: invalidRegion.Sub(invalidRegion, mValidRegion); michael@0: if (invalidRegion.IsEmpty()) { michael@0: EndPaint(true); michael@0: return; michael@0: } michael@0: michael@0: // Only paint the mask layer on the first transaction. michael@0: if (GetMaskLayer() && !ClientManager()->IsRepeatTransaction()) { michael@0: ToClientLayer(GetMaskLayer())->RenderLayer(); michael@0: } michael@0: michael@0: bool isFixed = GetIsFixedPosition() || GetParent()->GetIsFixedPosition(); michael@0: michael@0: // Fast path for no progressive updates, no low-precision updates and no michael@0: // critical display-port set, or no display-port set, or this is a fixed michael@0: // position layer/contained in a fixed position layer michael@0: const FrameMetrics& parentMetrics = GetParent()->GetFrameMetrics(); michael@0: if ((!gfxPrefs::UseProgressiveTilePainting() && michael@0: !gfxPrefs::UseLowPrecisionBuffer() && michael@0: parentMetrics.mCriticalDisplayPort.IsEmpty()) || michael@0: parentMetrics.mDisplayPort.IsEmpty() || michael@0: isFixed) { michael@0: mValidRegion = mVisibleRegion; michael@0: michael@0: NS_ASSERTION(!ClientManager()->IsRepeatTransaction(), "Didn't paint our mask layer"); michael@0: michael@0: mContentClient->mTiledBuffer.PaintThebes(mValidRegion, invalidRegion, michael@0: callback, data); michael@0: michael@0: ClientManager()->Hold(this); michael@0: mContentClient->UseTiledLayerBuffer(TiledContentClient::TILED_BUFFER); michael@0: michael@0: return; michael@0: } michael@0: michael@0: // Calculate everything we need to perform the paint. michael@0: BeginPaint(); michael@0: if (mPaintData.mPaintFinished) { michael@0: return; michael@0: } michael@0: michael@0: // Make sure that tiles that fall outside of the visible region are michael@0: // discarded on the first update. michael@0: if (!ClientManager()->IsRepeatTransaction()) { michael@0: mValidRegion.And(mValidRegion, mVisibleRegion); michael@0: if (!mPaintData.mCriticalDisplayPort.IsEmpty()) { michael@0: // Make sure that tiles that fall outside of the critical displayport are michael@0: // discarded on the first update. michael@0: mValidRegion.And(mValidRegion, mPaintData.mCriticalDisplayPort); michael@0: } michael@0: } michael@0: michael@0: nsIntRegion lowPrecisionInvalidRegion; michael@0: if (!mPaintData.mCriticalDisplayPort.IsEmpty()) { michael@0: if (gfxPrefs::UseLowPrecisionBuffer()) { michael@0: // Calculate the invalid region for the low precision buffer michael@0: lowPrecisionInvalidRegion.Sub(mVisibleRegion, mLowPrecisionValidRegion); michael@0: michael@0: // Remove the valid region from the low precision valid region (we don't michael@0: // validate this part of the low precision buffer). michael@0: lowPrecisionInvalidRegion.Sub(lowPrecisionInvalidRegion, mValidRegion); michael@0: } michael@0: michael@0: // Clip the invalid region to the critical display-port michael@0: invalidRegion.And(invalidRegion, mPaintData.mCriticalDisplayPort); michael@0: if (invalidRegion.IsEmpty() && lowPrecisionInvalidRegion.IsEmpty()) { michael@0: EndPaint(true); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (!invalidRegion.IsEmpty() && mPaintData.mLowPrecisionPaintCount == 0) { michael@0: bool updatedBuffer = false; michael@0: // Only draw progressively when the resolution is unchanged. michael@0: if (gfxPrefs::UseProgressiveTilePainting() && michael@0: !ClientManager()->HasShadowTarget() && michael@0: mContentClient->mTiledBuffer.GetFrameResolution() == mPaintData.mResolution) { michael@0: // Store the old valid region, then clear it before painting. michael@0: // We clip the old valid region to the visible region, as it only gets michael@0: // used to decide stale content (currently valid and previously visible) michael@0: nsIntRegion oldValidRegion = mContentClient->mTiledBuffer.GetValidRegion(); michael@0: oldValidRegion.And(oldValidRegion, mVisibleRegion); michael@0: if (!mPaintData.mCriticalDisplayPort.IsEmpty()) { michael@0: oldValidRegion.And(oldValidRegion, mPaintData.mCriticalDisplayPort); michael@0: } michael@0: michael@0: updatedBuffer = michael@0: mContentClient->mTiledBuffer.ProgressiveUpdate(mValidRegion, invalidRegion, michael@0: oldValidRegion, &mPaintData, michael@0: callback, data); michael@0: } else { michael@0: updatedBuffer = true; michael@0: mValidRegion = mVisibleRegion; michael@0: if (!mPaintData.mCriticalDisplayPort.IsEmpty()) { michael@0: mValidRegion.And(mValidRegion, mPaintData.mCriticalDisplayPort); michael@0: } michael@0: mContentClient->mTiledBuffer.SetFrameResolution(mPaintData.mResolution); michael@0: mContentClient->mTiledBuffer.PaintThebes(mValidRegion, invalidRegion, michael@0: callback, data); michael@0: } michael@0: michael@0: if (updatedBuffer) { michael@0: ClientManager()->Hold(this); michael@0: mContentClient->UseTiledLayerBuffer(TiledContentClient::TILED_BUFFER); michael@0: michael@0: // If there are low precision updates, mark the paint as unfinished and michael@0: // request a repeat transaction. michael@0: if (!lowPrecisionInvalidRegion.IsEmpty() && mPaintData.mPaintFinished) { michael@0: ClientManager()->SetRepeatTransaction(); michael@0: mPaintData.mLowPrecisionPaintCount = 1; michael@0: mPaintData.mPaintFinished = false; michael@0: } michael@0: michael@0: // Return so that low precision updates aren't performed in the same michael@0: // transaction as high-precision updates. michael@0: EndPaint(false); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Render the low precision buffer, if there's area to invalidate and the michael@0: // visible region is larger than the critical display port. michael@0: bool updatedLowPrecision = false; michael@0: if (!lowPrecisionInvalidRegion.IsEmpty() && michael@0: !nsIntRegion(mPaintData.mCriticalDisplayPort).Contains(mVisibleRegion)) { michael@0: nsIntRegion oldValidRegion = michael@0: mContentClient->mLowPrecisionTiledBuffer.GetValidRegion(); michael@0: oldValidRegion.And(oldValidRegion, mVisibleRegion); michael@0: michael@0: // If the frame resolution or format have changed, invalidate the buffer michael@0: if (mContentClient->mLowPrecisionTiledBuffer.GetFrameResolution() != mPaintData.mResolution || michael@0: mContentClient->mLowPrecisionTiledBuffer.HasFormatChanged()) { michael@0: if (!mLowPrecisionValidRegion.IsEmpty()) { michael@0: updatedLowPrecision = true; michael@0: } michael@0: oldValidRegion.SetEmpty(); michael@0: mLowPrecisionValidRegion.SetEmpty(); michael@0: mContentClient->mLowPrecisionTiledBuffer.SetFrameResolution(mPaintData.mResolution); michael@0: lowPrecisionInvalidRegion = mVisibleRegion; michael@0: } michael@0: michael@0: // Invalidate previously valid content that is no longer visible michael@0: if (mPaintData.mLowPrecisionPaintCount == 1) { michael@0: mLowPrecisionValidRegion.And(mLowPrecisionValidRegion, mVisibleRegion); michael@0: } michael@0: mPaintData.mLowPrecisionPaintCount++; michael@0: michael@0: // Remove the valid high-precision region from the invalid low-precision michael@0: // region. We don't want to spend time drawing things twice. michael@0: lowPrecisionInvalidRegion.Sub(lowPrecisionInvalidRegion, mValidRegion); michael@0: michael@0: if (!lowPrecisionInvalidRegion.IsEmpty()) { michael@0: updatedLowPrecision = mContentClient->mLowPrecisionTiledBuffer michael@0: .ProgressiveUpdate(mLowPrecisionValidRegion, michael@0: lowPrecisionInvalidRegion, michael@0: oldValidRegion, &mPaintData, michael@0: callback, data); michael@0: } michael@0: } else if (!mLowPrecisionValidRegion.IsEmpty()) { michael@0: // Clear the low precision tiled buffer michael@0: updatedLowPrecision = true; michael@0: mLowPrecisionValidRegion.SetEmpty(); michael@0: mContentClient->mLowPrecisionTiledBuffer.PaintThebes(mLowPrecisionValidRegion, michael@0: mLowPrecisionValidRegion, michael@0: callback, data); michael@0: } michael@0: michael@0: // We send a Painted callback if we clear the valid region of the low michael@0: // precision buffer, so that the shadow buffer's valid region can be updated michael@0: // and the associated resources can be freed. michael@0: if (updatedLowPrecision) { michael@0: ClientManager()->Hold(this); michael@0: mContentClient->UseTiledLayerBuffer(TiledContentClient::LOW_PRECISION_TILED_BUFFER); michael@0: } michael@0: michael@0: EndPaint(false); michael@0: } michael@0: michael@0: } // mozilla michael@0: } // layers