summaryrefslogtreecommitdiffstats
path: root/gfx/layers/basic/BasicLayerManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/basic/BasicLayerManager.cpp')
-rw-r--r--gfx/layers/basic/BasicLayerManager.cpp969
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