/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * 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 // for uint32_t #include // for rand, RAND_MAX #include // for int32_t #include // for stack #include "BasicContainerLayer.h" // for BasicContainerLayer #include "BasicLayersImpl.h" // for ToData, BasicReadbackLayer, etc #include "GeckoProfiler.h" // for 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 "gfxPrefs.h" // for gfxPrefs #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/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/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 "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(); gfxMatrix currentMatrix = aContext->CurrentMatrix(); aContext->SetMatrix(gfxMatrix()); 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. gfxMatrix oldMat = aContext->CurrentMatrix(); aContext->SetMatrix(gfxMatrix()); gfxRect rect = aContext->GetClipExtents(); aContext->SetMatrix(oldMat); rect.RoundOut(); IntRect surfRect; ToRect(rect).ToIntRect(&surfRect); if (!surfRect.IsEmpty()) { RefPtr dt = aContext->GetDrawTarget()->CreateSimilarDrawTarget(surfRect.Size(), SurfaceFormat::B8G8R8A8); RefPtr ctx = gfxContext::CreateOrNull(dt, ToRect(rect).TopLeft()); if (!ctx) { gfxCriticalNote << "BasicLayerManager context problem in PushGroupForLayer " << gfx::hexa(dt); return false; } ctx->SetMatrix(oldMat); aGroupResult.mGroupOffset = surfRect.TopLeft(); aGroupResult.mGroupTarget = ctx; aGroupResult.mMaskSurface = GetMaskForLayer(aLayer, &aGroupResult.mMaskTransform); return true; } aContext->Restore(); } Matrix maskTransform; RefPtr 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 = ToMatrix(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 sourceDT = group.mGroupTarget->GetDrawTarget(); group.mGroupTarget = nullptr; RefPtr 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(ThebesMatrix(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) { 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() { mInTransaction = true; mUsingDefaultTarget = true; return BeginTransactionWithTarget(mDefaultTarget); } bool BasicLayerManager::BeginTransactionWithTarget(gfxContext* aTarget) { 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& 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& 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(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) { PROFILER_LABEL("BasicLayerManager", "EndTransactionInternal", js::ProfileEntry::Category::GRAPHICS); #ifdef MOZ_LAYERS_HAVE_LOG MOZ_LAYERS_LOG((" ----- (beginning paint)")); Log(); #endif NS_ASSERTION(InConstruction(), "Should be in construction phase"); mPhase = PHASE_DRAWING; RenderTraceLayers(mRoot, "FF00"); mTransactionIncomplete = false; if (mRoot) { if (aFlags & END_NO_COMPOSITE) { // Apply pending tree updates before recomputing effective // properties. 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(ToMatrix(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; { gfxContextMatrixAutoSaveRestore save(mTarget); mTarget->SetMatrix(gfxMatrix()); clipRect = ToOutsideIntRect(mTarget->GetClipExtents()); } 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(); PostPresent(); if (!mTransactionIncomplete) { // Clear out target if we have a complete transaction. mTarget = nullptr; } } if (mRoot) { mAnimationReadyTime = TimeStamp::Now(); mRoot->StartPendingAnimations(mAnimationReadyTime); } #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 (gfxPrefs::WidgetUpdateFlashing()) { float r = float(rand()) / RAND_MAX; float g = float(rand()) / RAND_MAX; float b = float(rand()) / RAND_MAX; aContext->SetColor(Color(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(aPaintContext.mLayer); AutoTArray children; container->SortChildrenBy3DZOrder(children); for (uint32_t i = 0; i < children.Length(); i++) { Layer* layer = children.ElementAt(i); 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 &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!"; } gfxMatrix oldTransform = aTarget->CurrentMatrix(); transform *= ToMatrix(oldTransform); aTarget->SetMatrix(ThebesMatrix(transform)); aTarget->NewPath(); aTarget->SnappedRectangle(gfxRect(clipRect->x, clipRect->y, clipRect->width, clipRect->height)); aTarget->Clip(); aTarget->SetMatrix(oldTransform); } void BasicLayerManager::PaintLayer(gfxContext* aTarget, Layer* aLayer, DrawPaintedLayerCallback aCallback, void* aCallbackData) { MOZ_ASSERT(aTarget); PROFILER_LABEL("BasicLayerManager", "PaintLayer", js::ProfileEntry::Category::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& clipRect = aLayer->GetLocalClipRect(); BasicContainerLayer* container = static_cast(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 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 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) { Color 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 untransformedSurf = untransformedDT->Snapshot(); RefPtr xformDT = untransformedDT->CreateSimilarDrawTarget(IntSize::Truncate(xformBounds.width, xformBounds.height), SurfaceFormat::B8G8R8A8); RefPtr 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->NewPath(); aTarget->SnappedRectangle(ThebesRect(xformBounds)); aTarget->Clip(); 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 BasicLayerManager::CreateReadbackLayer() { NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); RefPtr layer = new BasicReadbackLayer(this); return layer.forget(); } } // namespace layers } // namespace mozilla