diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /gfx/layers/basic/BasicLayerManager.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | gfx/layers/basic/BasicLayerManager.cpp | 969 |
1 files changed, 969 insertions, 0 deletions
diff --git a/gfx/layers/basic/BasicLayerManager.cpp b/gfx/layers/basic/BasicLayerManager.cpp new file mode 100644 index 0000000000..83bc8cdffc --- /dev/null +++ b/gfx/layers/basic/BasicLayerManager.cpp @@ -0,0 +1,969 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 <stdint.h> // for uint32_t +#include <stdlib.h> // for rand, RAND_MAX +#include <sys/types.h> // for int32_t +#include <stack> // for stack +#include "BasicContainerLayer.h" // for BasicContainerLayer +#include "BasicLayersImpl.h" // for ToData, BasicReadbackLayer, etc +#include "GeckoProfiler.h" // for AUTO_PROFILER_LABEL +#include "ImageContainer.h" // for ImageFactory +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "ReadbackLayer.h" // for ReadbackLayer +#include "ReadbackProcessor.h" // for ReadbackProcessor +#include "RenderTrace.h" // for RenderTraceLayers, etc +#include "basic/BasicImplData.h" // for BasicImplData +#include "basic/BasicLayers.h" // for BasicLayerManager, etc +#include "gfxASurface.h" // for gfxASurface, etc +#include "gfxContext.h" // for gfxContext, etc +#include "gfxImageSurface.h" // for gfxImageSurface +#include "gfxMatrix.h" // for gfxMatrix +#include "gfxPlatform.h" // for gfxPlatform + +#include "gfxPoint.h" // for IntSize, gfxPoint +#include "gfxRect.h" // for gfxRect +#include "gfxUtils.h" // for gfxUtils +#include "gfx2DGlue.h" // for thebes --> moz2d transition +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/StaticPrefs_nglayout.h" +#include "mozilla/WidgetUtils.h" // for ScreenRotation +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/gfx/Matrix.h" // for Matrix +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/gfx/Rect.h" // for IntRect, Rect +#include "mozilla/layers/BSPTree.h" +#include "mozilla/layers/LayersTypes.h" // for BufferMode::BUFFER_NONE, etc +#include "mozilla/mozalloc.h" // for operator new +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsISupportsImpl.h" // for gfxContext::Release, etc +#include "nsLayoutUtils.h" // for nsLayoutUtils +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion, etc +#include "nsTArray.h" // for AutoTArray +#include "TreeTraversal.h" // for ForEachNode + +class nsIWidget; + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +/** + * Clips to the smallest device-pixel-aligned rectangle containing aRect + * in user space. + * Returns true if the clip is "perfect", i.e. we actually clipped exactly to + * aRect. + */ +static bool ClipToContain(gfxContext* aContext, const IntRect& aRect) { + gfxRect userRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); + gfxRect deviceRect = aContext->UserToDevice(userRect); + deviceRect.RoundOut(); + + Matrix currentMatrix = aContext->CurrentMatrix(); + aContext->SetMatrix(Matrix()); + aContext->NewPath(); + aContext->Rectangle(deviceRect); + aContext->Clip(); + aContext->SetMatrix(currentMatrix); + + return aContext->DeviceToUser(deviceRect).IsEqualInterior(userRect); +} + +bool BasicLayerManager::PushGroupForLayer(gfxContext* aContext, Layer* aLayer, + const nsIntRegion& aRegion, + PushedGroup& aGroupResult) { + aGroupResult.mVisibleRegion = aRegion; + aGroupResult.mFinalTarget = aContext; + aGroupResult.mOperator = GetEffectiveOperator(aLayer); + aGroupResult.mOpacity = aLayer->GetEffectiveOpacity(); + + // If we need to call PushGroup, we should clip to the smallest possible + // area first to minimize the size of the temporary surface. + bool didCompleteClip = ClipToContain(aContext, aRegion.GetBounds()); + + bool canPushGroup = + aGroupResult.mOperator == CompositionOp::OP_OVER || + (aGroupResult.mOperator == CompositionOp::OP_SOURCE && + (aLayer->CanUseOpaqueSurface() || + aLayer->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA)); + + if (!canPushGroup) { + aContext->Save(); + gfxUtils::ClipToRegion(aGroupResult.mFinalTarget, + aGroupResult.mVisibleRegion); + + // PushGroup/PopGroup do not support non operator over. + gfxRect rect = aContext->GetClipExtents(gfxContext::eDeviceSpace); + rect.RoundOut(); + IntRect surfRect; + ToRect(rect).ToIntRect(&surfRect); + + if (!surfRect.IsEmpty()) { + RefPtr<DrawTarget> dt; + if (aContext->GetDrawTarget()->CanCreateSimilarDrawTarget( + surfRect.Size(), SurfaceFormat::B8G8R8A8)) { + dt = aContext->GetDrawTarget()->CreateSimilarDrawTarget( + surfRect.Size(), SurfaceFormat::B8G8R8A8); + } + + RefPtr<gfxContext> ctx = + gfxContext::CreateOrNull(dt, ToRect(rect).TopLeft()); + if (!ctx) { + gfxCriticalNote + << "BasicLayerManager context problem in PushGroupForLayer " + << gfx::hexa(dt); + return false; + } + ctx->SetMatrix(aContext->CurrentMatrix()); + + aGroupResult.mGroupOffset = surfRect.TopLeft(); + aGroupResult.mGroupTarget = ctx; + + aGroupResult.mMaskSurface = + GetMaskForLayer(aLayer, &aGroupResult.mMaskTransform); + return true; + } + aContext->Restore(); + } + + Matrix maskTransform; + RefPtr<SourceSurface> maskSurf = GetMaskForLayer(aLayer, &maskTransform); + + if (maskSurf) { + // The returned transform will transform the mask to device space on the + // destination. Since the User->Device space transform will be applied + // to the mask by PopGroupAndBlend we need to adjust the transform to + // transform the mask to user space. + Matrix currentTransform = aGroupResult.mFinalTarget->CurrentMatrix(); + currentTransform.Invert(); + maskTransform = maskTransform * currentTransform; + } + + if (aLayer->CanUseOpaqueSurface() && + ((didCompleteClip && aRegion.GetNumRects() == 1) || + !aContext->CurrentMatrix().HasNonIntegerTranslation())) { + // If the layer is opaque in its visible region we can push a + // gfxContentType::COLOR group. We need to make sure that only pixels inside + // the layer's visible region are copied back to the destination. Remember + // if we've already clipped precisely to the visible region. + aGroupResult.mNeedsClipToVisibleRegion = + !didCompleteClip || aRegion.GetNumRects() > 1; + if (aGroupResult.mNeedsClipToVisibleRegion) { + aGroupResult.mFinalTarget->Save(); + gfxUtils::ClipToRegion(aGroupResult.mFinalTarget, + aGroupResult.mVisibleRegion); + } + + aContext->PushGroupForBlendBack( + gfxContentType::COLOR, aGroupResult.mOpacity, maskSurf, maskTransform); + } else { + if (aLayer->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA) { + aContext->PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA, + aGroupResult.mOpacity, maskSurf, + maskTransform); + } else { + aContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, + aGroupResult.mOpacity, maskSurf, + maskTransform); + } + } + + aGroupResult.mGroupTarget = aGroupResult.mFinalTarget; + + return true; +} + +void BasicLayerManager::PopGroupForLayer(PushedGroup& group) { + if (group.mFinalTarget == group.mGroupTarget) { + group.mFinalTarget->PopGroupAndBlend(); + if (group.mNeedsClipToVisibleRegion) { + group.mFinalTarget->Restore(); + } + return; + } + + DrawTarget* dt = group.mFinalTarget->GetDrawTarget(); + RefPtr<DrawTarget> sourceDT = group.mGroupTarget->GetDrawTarget(); + group.mGroupTarget = nullptr; + + RefPtr<SourceSurface> src = sourceDT->Snapshot(); + + if (group.mMaskSurface) { + Point finalOffset = group.mFinalTarget->GetDeviceOffset(); + dt->SetTransform(group.mMaskTransform * Matrix::Translation(-finalOffset)); + Matrix surfTransform = group.mMaskTransform; + surfTransform.Invert(); + dt->MaskSurface(SurfacePattern(src, ExtendMode::CLAMP, + surfTransform * Matrix::Translation( + group.mGroupOffset.x, + group.mGroupOffset.y)), + group.mMaskSurface, Point(0, 0), + DrawOptions(group.mOpacity, group.mOperator)); + } else { + // For now this is required since our group offset is in device space of the + // final target, context but that may still have its own device offset. Once + // PushGroup/PopGroup logic is migrated to DrawTargets this can go as + // gfxContext::GetDeviceOffset will essentially always become null. + dt->SetTransform( + Matrix::Translation(-group.mFinalTarget->GetDeviceOffset())); + dt->DrawSurface(src, + Rect(group.mGroupOffset.x, group.mGroupOffset.y, + src->GetSize().width, src->GetSize().height), + Rect(0, 0, src->GetSize().width, src->GetSize().height), + DrawSurfaceOptions(SamplingFilter::POINT), + DrawOptions(group.mOpacity, group.mOperator)); + } + + if (group.mNeedsClipToVisibleRegion) { + dt->PopClip(); + } + + group.mFinalTarget->Restore(); +} + +static IntRect ToInsideIntRect(const gfxRect& aRect) { + return IntRect::RoundIn(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +// A context helper for BasicLayerManager::PaintLayer() that holds all the +// painting context together in a data structure so it can be easily passed +// around. It also uses ensures that the Transform and Opaque rect are restored +// to their former state on destruction. + +class PaintLayerContext { + public: + PaintLayerContext(gfxContext* aTarget, Layer* aLayer, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) + : mTarget(aTarget), + mTargetMatrixSR(aTarget), + mLayer(aLayer), + mCallback(aCallback), + mCallbackData(aCallbackData), + mPushedOpaqueRect(false) {} + + ~PaintLayerContext() { + // Matrix is restored by mTargetMatrixSR + if (mPushedOpaqueRect) { + ClearOpaqueRect(); + } + } + + // Gets the effective transform and returns true if it is a 2D + // transform. + bool Setup2DTransform() { + // Will return an identity matrix for 3d transforms. + return mLayer->GetEffectiveTransformForBuffer().CanDraw2D(&mTransform); + } + + // Applies the effective transform if it's 2D. If it's a 3D transform then + // it applies an identity. + void Apply2DTransform() { mTarget->SetMatrix(mTransform); } + + // Set the opaque rect to match the bounds of the visible region. + void AnnotateOpaqueRect() { + const nsIntRegion visibleRegion = + mLayer->GetLocalVisibleRegion().ToUnknownRegion(); + const IntRect& bounds = visibleRegion.GetBounds(); + + DrawTarget* dt = mTarget->GetDrawTarget(); + const IntRect& targetOpaqueRect = dt->GetOpaqueRect(); + + // Try to annotate currentSurface with a region of pixels that have been + // (or will be) painted opaque, if no such region is currently set. + if (targetOpaqueRect.IsEmpty() && visibleRegion.GetNumRects() == 1 && + (mLayer->GetContentFlags() & Layer::CONTENT_OPAQUE) && + !mTransform.HasNonAxisAlignedTransform()) { + gfx::Rect opaqueRect = dt->GetTransform().TransformBounds( + gfx::Rect(bounds.X(), bounds.Y(), bounds.Width(), bounds.Height())); + opaqueRect.RoundIn(); + IntRect intOpaqueRect; + if (opaqueRect.ToIntRect(&intOpaqueRect)) { + mTarget->GetDrawTarget()->SetOpaqueRect(intOpaqueRect); + mPushedOpaqueRect = true; + } + } + } + + // Clear the Opaque rect. Although this doesn't really restore it to it's + // previous state it will happen on the exit path of the PaintLayer() so when + // painting is complete the opaque rect qill be clear. + void ClearOpaqueRect() { mTarget->GetDrawTarget()->SetOpaqueRect(IntRect()); } + + gfxContext* mTarget; + gfxContextMatrixAutoSaveRestore mTargetMatrixSR; + Layer* mLayer; + LayerManager::DrawPaintedLayerCallback mCallback; + void* mCallbackData; + Matrix mTransform; + bool mPushedOpaqueRect; +}; + +BasicLayerManager::BasicLayerManager(nsIWidget* aWidget) + : mPhase(PHASE_NONE), + mWidget(aWidget), + mDoubleBuffering(BufferMode::BUFFER_NONE), + mType(BLM_WIDGET), + mUsingDefaultTarget(false), + mTransactionIncomplete(false), + mCompositorMightResample(false) { + MOZ_COUNT_CTOR(BasicLayerManager); + NS_ASSERTION(aWidget, "Must provide a widget"); +} + +BasicLayerManager::BasicLayerManager(BasicLayerManagerType aType) + : mPhase(PHASE_NONE), + mWidget(nullptr), + mDoubleBuffering(BufferMode::BUFFER_NONE), + mType(aType), + mUsingDefaultTarget(false), + mTransactionIncomplete(false), + mCompositorMightResample(false) { + MOZ_COUNT_CTOR(BasicLayerManager); + MOZ_ASSERT(mType != BLM_WIDGET); +} + +BasicLayerManager::~BasicLayerManager() { + NS_ASSERTION(!InTransaction(), "Died during transaction?"); + + ClearCachedResources(); + + mRoot = nullptr; + + MOZ_COUNT_DTOR(BasicLayerManager); +} + +void BasicLayerManager::SetDefaultTarget(gfxContext* aContext) { + NS_ASSERTION(!InTransaction(), "Must set default target outside transaction"); + mDefaultTarget = aContext; +} + +void BasicLayerManager::SetDefaultTargetConfiguration( + BufferMode aDoubleBuffering, ScreenRotation aRotation) { + mDoubleBuffering = aDoubleBuffering; +} + +bool BasicLayerManager::BeginTransaction(const nsCString& aURL) { + mInTransaction = true; + mUsingDefaultTarget = true; + return BeginTransactionWithTarget(mDefaultTarget, aURL); +} + +bool BasicLayerManager::BeginTransactionWithTarget(gfxContext* aTarget, + const nsCString& aURL) { + mInTransaction = true; + +#ifdef MOZ_LAYERS_HAVE_LOG + MOZ_LAYERS_LOG(("[----- BeginTransaction")); + Log(); +#endif + + NS_ASSERTION(!InTransaction(), "Nested transactions not allowed"); + mPhase = PHASE_CONSTRUCTION; + mTarget = aTarget; + return true; +} + +static void TransformIntRect(IntRect& aRect, const Matrix& aMatrix, + IntRect (*aRoundMethod)(const gfxRect&)) { + Rect gr = Rect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); + gr = aMatrix.TransformBounds(gr); + aRect = (*aRoundMethod)(ThebesRect(gr)); +} + +/** + * This function assumes that GetEffectiveTransform transforms + * all layers to the same coordinate system (the "root coordinate system"). + * It can't be used as is by accelerated layers because of intermediate + * surfaces. This must set the hidden flag to true or false on *all* layers in + * the subtree. It also sets the operator for all layers to "OVER", and call + * SetDrawAtomically(false). + * It clears mClipToVisibleRegion on all layers. + * @param aClipRect the cliprect, in the root coordinate system. We assume + * that any layer drawing is clipped to this rect. It is therefore not + * allowed to add to the opaque region outside that rect. + * @param aDirtyRect the dirty rect that will be painted, in the root + * coordinate system. Layers outside this rect should be hidden. + * @param aOpaqueRegion the opaque region covering aLayer, in the + * root coordinate system. + */ +enum { + ALLOW_OPAQUE = 0x01, +}; +static void MarkLayersHidden(Layer* aLayer, const IntRect& aClipRect, + const IntRect& aDirtyRect, + nsIntRegion& aOpaqueRegion, uint32_t aFlags) { + IntRect newClipRect(aClipRect); + uint32_t newFlags = aFlags; + + // Allow aLayer or aLayer's descendants to cover underlying layers + // only if it's opaque. + if (aLayer->GetOpacity() != 1.0f) { + newFlags &= ~ALLOW_OPAQUE; + } + + { + const Maybe<ParentLayerIntRect>& clipRect = aLayer->GetLocalClipRect(); + if (clipRect) { + IntRect cr = clipRect->ToUnknownRect(); + // clipRect is in the container's coordinate system. Get it into the + // global coordinate system. + if (aLayer->GetParent()) { + Matrix tr; + if (aLayer->GetParent()->GetEffectiveTransform().CanDraw2D(&tr)) { + // Clip rect is applied after aLayer's transform, i.e., in the + // coordinate system of aLayer's parent. + TransformIntRect(cr, tr, ToInsideIntRect); + } else { + cr.SetRect(0, 0, 0, 0); + } + } + newClipRect.IntersectRect(newClipRect, cr); + } + } + + BasicImplData* data = ToData(aLayer); + data->SetOperator(CompositionOp::OP_OVER); + data->SetClipToVisibleRegion(false); + data->SetDrawAtomically(false); + + if (!aLayer->AsContainerLayer()) { + Matrix transform; + if (!aLayer->GetEffectiveTransform().CanDraw2D(&transform)) { + data->SetHidden(false); + return; + } + + nsIntRegion region = aLayer->GetLocalVisibleRegion().ToUnknownRegion(); + IntRect r = region.GetBounds(); + TransformIntRect(r, transform, ToOutsideIntRect); + r.IntersectRect(r, aDirtyRect); + data->SetHidden(aOpaqueRegion.Contains(r)); + + // Allow aLayer to cover underlying layers only if aLayer's + // content is opaque + if ((aLayer->GetContentFlags() & Layer::CONTENT_OPAQUE) && + (newFlags & ALLOW_OPAQUE)) { + for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) { + r = iter.Get(); + TransformIntRect(r, transform, ToInsideIntRect); + + r.IntersectRect(r, newClipRect); + aOpaqueRegion.Or(aOpaqueRegion, r); + } + } + } else { + Layer* child = aLayer->GetLastChild(); + bool allHidden = true; + for (; child; child = child->GetPrevSibling()) { + MarkLayersHidden(child, newClipRect, aDirtyRect, aOpaqueRegion, newFlags); + if (!ToData(child)->IsHidden()) { + allHidden = false; + } + } + data->SetHidden(allHidden); + } +} + +/** + * This function assumes that GetEffectiveTransform transforms + * all layers to the same coordinate system (the "root coordinate system"). + * MarkLayersHidden must be called before calling this. + * @param aVisibleRect the rectangle of aLayer that is visible (i.e. not + * clipped and in the dirty rect), in the root coordinate system. + */ +static void ApplyDoubleBuffering(Layer* aLayer, const IntRect& aVisibleRect) { + BasicImplData* data = ToData(aLayer); + if (data->IsHidden()) return; + + IntRect newVisibleRect(aVisibleRect); + + { + const Maybe<ParentLayerIntRect>& clipRect = aLayer->GetLocalClipRect(); + if (clipRect) { + IntRect cr = clipRect->ToUnknownRect(); + // clipRect is in the container's coordinate system. Get it into the + // global coordinate system. + if (aLayer->GetParent()) { + Matrix tr; + if (aLayer->GetParent()->GetEffectiveTransform().CanDraw2D(&tr)) { + NS_ASSERTION(!ThebesMatrix(tr).HasNonIntegerTranslation(), + "Parent can only have an integer translation"); + cr += nsIntPoint(int32_t(tr._31), int32_t(tr._32)); + } else { + NS_ERROR("Parent can only have an integer translation"); + } + } + newVisibleRect.IntersectRect(newVisibleRect, cr); + } + } + + BasicContainerLayer* container = + static_cast<BasicContainerLayer*>(aLayer->AsContainerLayer()); + // Layers that act as their own backbuffers should be drawn to the destination + // using OP_SOURCE to ensure that alpha values in a transparent window are + // cleared. This can also be faster than OP_OVER. + if (!container) { + data->SetOperator(CompositionOp::OP_SOURCE); + data->SetDrawAtomically(true); + } else { + if (container->UseIntermediateSurface() || + !container->ChildrenPartitionVisibleRegion(newVisibleRect)) { + // We need to double-buffer this container. + data->SetOperator(CompositionOp::OP_SOURCE); + container->ForceIntermediateSurface(); + } else { + // Tell the children to clip to their visible regions so our assumption + // that they don't paint outside their visible regions is valid! + for (Layer* child = aLayer->GetFirstChild(); child; + child = child->GetNextSibling()) { + ToData(child)->SetClipToVisibleRegion(true); + ApplyDoubleBuffering(child, newVisibleRect); + } + } + } +} + +void BasicLayerManager::EndTransaction(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags aFlags) { + mInTransaction = false; + + EndTransactionInternal(aCallback, aCallbackData, aFlags); +} + +void BasicLayerManager::AbortTransaction() { + NS_ASSERTION(InConstruction(), "Should be in construction phase"); + mPhase = PHASE_NONE; + mUsingDefaultTarget = false; + mInTransaction = false; +} + +bool BasicLayerManager::EndTransactionInternal( + DrawPaintedLayerCallback aCallback, void* aCallbackData, + EndTransactionFlags aFlags) { + AUTO_PROFILER_LABEL("BasicLayerManager::EndTransactionInternal", GRAPHICS); + +#ifdef MOZ_LAYERS_HAVE_LOG + MOZ_LAYERS_LOG((" ----- (beginning paint)")); + Log(); +#endif + + NS_ASSERTION(InConstruction(), "Should be in construction phase"); + mPhase = PHASE_DRAWING; + + SetCompositionTime(TimeStamp::Now()); + + RenderTraceLayers(mRoot, "FF00"); + + mTransactionIncomplete = false; + + std::unordered_set<ScrollableLayerGuid::ViewID> scrollIdsUpdated; + + if (mRoot) { + if (aFlags & END_NO_COMPOSITE) { + // Apply pending tree updates before recomputing effective + // properties. + scrollIdsUpdated = mRoot->ApplyPendingUpdatesToSubtree(); + } + + // Need to do this before we call ApplyDoubleBuffering, + // which depends on correct effective transforms + if (mTarget) { + mSnapEffectiveTransforms = + !mTarget->GetDrawTarget()->GetUserData(&sDisablePixelSnapping); + } else { + mSnapEffectiveTransforms = true; + } + mRoot->ComputeEffectiveTransforms( + mTarget ? Matrix4x4::From2D(mTarget->CurrentMatrix()) : Matrix4x4()); + + ToData(mRoot)->Validate(aCallback, aCallbackData, nullptr); + if (mRoot->GetMaskLayer()) { + ToData(mRoot->GetMaskLayer()) + ->Validate(aCallback, aCallbackData, nullptr); + } + } + + if (mTarget && mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW) && + !(aFlags & END_NO_COMPOSITE)) { + IntRect clipRect = + ToOutsideIntRect(mTarget->GetClipExtents(gfxContext::eDeviceSpace)); + + if (IsRetained()) { + nsIntRegion region; + MarkLayersHidden(mRoot, clipRect, clipRect, region, ALLOW_OPAQUE); + if (mUsingDefaultTarget && mDoubleBuffering != BufferMode::BUFFER_NONE) { + ApplyDoubleBuffering(mRoot, clipRect); + } + } + + PaintLayer(mTarget, mRoot, aCallback, aCallbackData); + if (!mRegionToClear.IsEmpty()) { + for (auto iter = mRegionToClear.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& r = iter.Get(); + mTarget->GetDrawTarget()->ClearRect( + Rect(r.X(), r.Y(), r.Width(), r.Height())); + } + } + if (mWidget) { + FlashWidgetUpdateArea(mTarget); + } + RecordFrame(); + + if (!mTransactionIncomplete) { + // Clear out target if we have a complete transaction. + mTarget = nullptr; + } + } + + if (mRoot) { + mAnimationReadyTime = TimeStamp::Now(); + mRoot->StartPendingAnimations(mAnimationReadyTime); + + // Once we're sure we're not going to fall back to a full paint, + // notify the scroll frames which had pending updates. + if (!mTransactionIncomplete) { + for (ScrollableLayerGuid::ViewID scrollId : scrollIdsUpdated) { + nsLayoutUtils::NotifyPaintSkipTransaction(scrollId); + } + } + } + +#ifdef MOZ_LAYERS_HAVE_LOG + Log(); + MOZ_LAYERS_LOG(("]----- EndTransaction")); +#endif + + // Go back to the construction phase if the transaction isn't complete. + // Layout will update the layer tree and call EndTransaction(). + mPhase = mTransactionIncomplete ? PHASE_CONSTRUCTION : PHASE_NONE; + + if (!mTransactionIncomplete) { + // This is still valid if the transaction was incomplete. + mUsingDefaultTarget = false; + } + + NS_ASSERTION(!aCallback || !mTransactionIncomplete, + "If callback is not null, transaction must be complete"); + + // XXX - We should probably assert here that for an incomplete transaction + // out target is the default target. + + return !mTransactionIncomplete; +} + +void BasicLayerManager::FlashWidgetUpdateArea(gfxContext* aContext) { + if (StaticPrefs::nglayout_debug_widget_update_flashing()) { + float r = float(rand()) / float(RAND_MAX); + float g = float(rand()) / float(RAND_MAX); + float b = float(rand()) / float(RAND_MAX); + aContext->SetDeviceColor(DeviceColor(r, g, b, 0.2f)); + aContext->Paint(); + } +} + +bool BasicLayerManager::EndEmptyTransaction(EndTransactionFlags aFlags) { + mInTransaction = false; + + if (!mRoot) { + return false; + } + + return EndTransactionInternal(nullptr, nullptr, aFlags); +} + +void BasicLayerManager::SetRoot(Layer* aLayer) { + NS_ASSERTION(aLayer, "Root can't be null"); + NS_ASSERTION(aLayer->Manager() == this, "Wrong manager"); + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + mRoot = aLayer; +} + +void BasicLayerManager::PaintSelfOrChildren(PaintLayerContext& aPaintContext, + gfxContext* aGroupTarget) { + MOZ_ASSERT(aGroupTarget); + BasicImplData* data = ToData(aPaintContext.mLayer); + + /* Only paint ourself, or our children - This optimization relies on this! */ + Layer* child = aPaintContext.mLayer->GetFirstChild(); + if (!child) { + if (aPaintContext.mLayer->AsPaintedLayer()) { + data->PaintThebes(aGroupTarget, aPaintContext.mLayer->GetMaskLayer(), + aPaintContext.mCallback, aPaintContext.mCallbackData); + } else { + data->Paint(aGroupTarget->GetDrawTarget(), + aGroupTarget->GetDeviceOffset(), + aPaintContext.mLayer->GetMaskLayer()); + } + } else { + ContainerLayer* container = + static_cast<ContainerLayer*>(aPaintContext.mLayer); + + nsTArray<LayerPolygon> children = container->SortChildrenBy3DZOrder( + ContainerLayer::SortMode::WITHOUT_GEOMETRY); + + for (uint32_t i = 0; i < children.Length(); i++) { + Layer* layer = children.ElementAt(i).layer; + if (layer->IsBackfaceHidden()) { + continue; + } + if (!layer->AsContainerLayer() && !layer->IsVisible()) { + continue; + } + + PaintLayer(aGroupTarget, layer, aPaintContext.mCallback, + aPaintContext.mCallbackData); + if (mTransactionIncomplete) break; + } + } +} + +void BasicLayerManager::FlushGroup(PaintLayerContext& aPaintContext, + bool aNeedsClipToVisibleRegion) { + // If we're doing our own double-buffering, we need to avoid drawing + // the results of an incomplete transaction to the destination surface --- + // that could cause flicker. Double-buffering is implemented using a + // temporary surface for one or more container layers, so we need to stop + // those temporary surfaces from being composited to aTarget. + // ApplyDoubleBuffering guarantees that this container layer can't + // intersect any other leaf layers, so if the transaction is not yet marked + // incomplete, the contents of this container layer are the final contents + // for the window. + if (!mTransactionIncomplete) { + if (aNeedsClipToVisibleRegion) { + gfxUtils::ClipToRegion( + aPaintContext.mTarget, + aPaintContext.mLayer->GetLocalVisibleRegion().ToUnknownRegion()); + } + + CompositionOp op = GetEffectiveOperator(aPaintContext.mLayer); + AutoSetOperator setOperator(aPaintContext.mTarget, op); + + PaintWithMask(aPaintContext.mTarget, + aPaintContext.mLayer->GetEffectiveOpacity(), + aPaintContext.mLayer->GetMaskLayer()); + } +} + +/** + * Install the clip applied to the layer on the given gfxContext. The + * given gfxContext is the buffer that the layer will be painted to. + */ +static void InstallLayerClipPreserves3D(gfxContext* aTarget, Layer* aLayer) { + const Maybe<ParentLayerIntRect>& clipRect = aLayer->GetLocalClipRect(); + + if (!clipRect) { + return; + } + MOZ_ASSERT( + !aLayer->Extend3DContext() || !aLayer->Combines3DTransformWithAncestors(), + "Layers in a preserve 3D context have no clip" + " except leaves and the estabisher!"); + + Layer* parent = aLayer->GetParent(); + Matrix4x4 transform3d = parent && parent->Extend3DContext() + ? parent->GetEffectiveTransform() + : Matrix4x4(); + Matrix transform; + if (!transform3d.CanDraw2D(&transform)) { + gfxDevCrash(LogReason::CannotDraw3D) + << "GFX: We should not have a 3D transform that CanDraw2D() is false!"; + } + Matrix oldTransform = aTarget->CurrentMatrix(); + transform *= oldTransform; + aTarget->SetMatrix(transform); + + aTarget->SnappedClip(gfxRect(clipRect->X(), clipRect->Y(), clipRect->Width(), + clipRect->Height())); + + aTarget->SetMatrix(oldTransform); +} + +void BasicLayerManager::PaintLayer(gfxContext* aTarget, Layer* aLayer, + DrawPaintedLayerCallback aCallback, + void* aCallbackData) { + MOZ_ASSERT(aTarget); + + AUTO_PROFILER_LABEL("BasicLayerManager::PaintLayer", GRAPHICS); + + PaintLayerContext paintLayerContext(aTarget, aLayer, aCallback, + aCallbackData); + + // Don't attempt to paint layers with a singular transform, cairo will + // just throw an error. + if (aLayer->GetEffectiveTransform().IsSingular()) { + return; + } + + RenderTraceScope trace("BasicLayerManager::PaintLayer", "707070"); + + const Maybe<ParentLayerIntRect>& clipRect = aLayer->GetLocalClipRect(); + BasicContainerLayer* container = + static_cast<BasicContainerLayer*>(aLayer->AsContainerLayer()); + bool needsGroup = container && container->UseIntermediateSurface(); + BasicImplData* data = ToData(aLayer); + bool needsClipToVisibleRegion = + data->GetClipToVisibleRegion() && !aLayer->AsPaintedLayer(); + NS_ASSERTION(needsGroup || !container || + container->GetOperator() == CompositionOp::OP_OVER, + "non-OVER operator should have forced UseIntermediateSurface"); + NS_ASSERTION( + !container || !aLayer->GetMaskLayer() || + container->UseIntermediateSurface(), + "ContainerLayer with mask layer should force UseIntermediateSurface"); + + gfxContextAutoSaveRestore contextSR; + gfxMatrix transform; + // Will return an identity matrix for 3d transforms, and is handled separately + // below. + bool is2D = paintLayerContext.Setup2DTransform(); + MOZ_ASSERT(is2D || needsGroup || !container || container->Extend3DContext() || + container->Is3DContextLeaf(), + "Must PushGroup for 3d transforms!"); + + Layer* parent = aLayer->GetParent(); + bool inPreserves3DChain = parent && parent->Extend3DContext(); + bool needsSaveRestore = needsGroup || clipRect || needsClipToVisibleRegion || + !is2D || inPreserves3DChain; + if (needsSaveRestore) { + contextSR.SetContext(aTarget); + + // The clips on ancestors on the preserved3d chain should be + // installed on the aTarget before painting the layer. + InstallLayerClipPreserves3D(aTarget, aLayer); + for (Layer* l = parent; l && l->Extend3DContext(); l = l->GetParent()) { + InstallLayerClipPreserves3D(aTarget, l); + } + } + + paintLayerContext.Apply2DTransform(); + + nsIntRegion visibleRegion = aLayer->GetLocalVisibleRegion().ToUnknownRegion(); + // If needsGroup is true, we'll clip to the visible region after we've popped + // the group + if (needsClipToVisibleRegion && !needsGroup) { + gfxUtils::ClipToRegion(aTarget, visibleRegion); + // Don't need to clip to visible region again + needsClipToVisibleRegion = false; + } + + if (is2D) { + paintLayerContext.AnnotateOpaqueRect(); + } + + bool clipIsEmpty = aTarget->GetClipExtents().IsEmpty(); + if (clipIsEmpty) { + PaintSelfOrChildren(paintLayerContext, aTarget); + return; + } + + if (is2D) { + if (needsGroup) { + PushedGroup pushedGroup; + if (PushGroupForLayer(aTarget, aLayer, + aLayer->GetLocalVisibleRegion().ToUnknownRegion(), + pushedGroup)) { + PaintSelfOrChildren(paintLayerContext, pushedGroup.mGroupTarget); + PopGroupForLayer(pushedGroup); + } + } else { + PaintSelfOrChildren(paintLayerContext, aTarget); + } + } else { + if (!needsGroup && container) { + PaintSelfOrChildren(paintLayerContext, aTarget); + return; + } + + IntRect bounds = visibleRegion.GetBounds(); + // DrawTarget without the 3D transform applied: + RefPtr<DrawTarget> untransformedDT = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + IntSize(bounds.Width(), bounds.Height()), SurfaceFormat::B8G8R8A8); + if (!untransformedDT || !untransformedDT->IsValid()) { + return; + } + untransformedDT->SetTransform( + Matrix::Translation(-Point(bounds.X(), bounds.Y()))); + + RefPtr<gfxContext> groupTarget = + gfxContext::CreatePreservingTransformOrNull(untransformedDT); + MOZ_ASSERT(groupTarget); // already checked the target above + + PaintSelfOrChildren(paintLayerContext, groupTarget); + + // Temporary fast fix for bug 725886 + // Revert these changes when 725886 is ready +#ifdef DEBUG + if (aLayer->GetDebugColorIndex() != 0) { + DeviceColor color((aLayer->GetDebugColorIndex() & 1) ? 1.f : 0.f, + (aLayer->GetDebugColorIndex() & 2) ? 1.f : 0.f, + (aLayer->GetDebugColorIndex() & 4) ? 1.f : 0.f); + untransformedDT->FillRect(Rect(bounds), ColorPattern(color)); + } +#endif + Matrix4x4 effectiveTransform = aLayer->GetEffectiveTransform(); + Rect xformBounds = effectiveTransform.TransformAndClipBounds( + Rect(bounds), ToRect(aTarget->GetClipExtents())); + xformBounds.RoundOut(); + effectiveTransform.PostTranslate(-xformBounds.X(), -xformBounds.Y(), 0); + effectiveTransform.PreTranslate(bounds.X(), bounds.Y(), 0); + + RefPtr<SourceSurface> untransformedSurf = untransformedDT->Snapshot(); + RefPtr<DrawTarget> xformDT = untransformedDT->CreateSimilarDrawTarget( + IntSize::Truncate(xformBounds.Width(), xformBounds.Height()), + SurfaceFormat::B8G8R8A8); + RefPtr<SourceSurface> xformSurf; + if (xformDT && untransformedSurf && + xformDT->Draw3DTransformedSurface(untransformedSurf, + effectiveTransform)) { + xformSurf = xformDT->Snapshot(); + } + + if (xformSurf) { + aTarget->SetPattern(new gfxPattern( + xformSurf, Matrix::Translation(xformBounds.TopLeft()))); + + // Azure doesn't support EXTEND_NONE, so to avoid extending the edges + // of the source surface out to the current clip region, clip to + // the rectangle of the result surface now. + aTarget->SnappedClip(ThebesRect(xformBounds)); + FlushGroup(paintLayerContext, needsClipToVisibleRegion); + } + } +} + +void BasicLayerManager::ClearCachedResources(Layer* aSubtree) { + MOZ_ASSERT(!aSubtree || aSubtree->Manager() == this); + if (aSubtree) { + ClearLayer(aSubtree); + } else if (mRoot) { + ClearLayer(mRoot); + } +} +void BasicLayerManager::ClearLayer(Layer* aLayer) { + ToData(aLayer)->ClearCachedResources(); + for (Layer* child = aLayer->GetFirstChild(); child; + child = child->GetNextSibling()) { + ClearLayer(child); + } +} + +already_AddRefed<ReadbackLayer> BasicLayerManager::CreateReadbackLayer() { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr<ReadbackLayer> layer = new BasicReadbackLayer(this); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla |