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