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/composite | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/layers/composite')
43 files changed, 14444 insertions, 0 deletions
diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp new file mode 100644 index 0000000000..3c155d0df0 --- /dev/null +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -0,0 +1,1314 @@ +/* -*- 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 "mozilla/layers/AsyncCompositionManager.h" +#include <stdint.h> // for uint32_t +#include "LayerManagerComposite.h" // for LayerManagerComposite, etc +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "gfxPoint.h" // for gfxPoint, gfxSize +#include "mozilla/ServoBindings.h" // for Servo_AnimationValue_GetOpacity, etc +#include "mozilla/ScopeExit.h" // for MakeScopeExit +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/WidgetUtils.h" // for ComputeTransformForRotation +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/gfx/Point.h" // for RoundedToInt, PointTyped +#include "mozilla/gfx/Rect.h" // for RoundedToInt, RectTyped +#include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor +#include "mozilla/layers/AnimationHelper.h" +#include "mozilla/layers/APZSampler.h" // for APZSampler +#include "mozilla/layers/APZUtils.h" // for CompleteAsyncTransform +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorAnimationStorage.h" // for CompositorAnimationStorage +#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/LayerAnimationUtils.h" // for TimingFunctionToComputedTimingFunction +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/SampleTime.h" +#include "nsCoord.h" // for NSAppUnitsToFloatPixels, etc +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsDeviceContext.h" // for nsDeviceContext +#include "nsDisplayList.h" // for nsDisplayTransform, etc +#include "nsMathUtils.h" // for NS_round +#include "nsPoint.h" // for nsPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc +#include "nsTArrayForwardDeclare.h" // for nsTArray +#include "UnitTransforms.h" // for TransformTo +#if defined(MOZ_WIDGET_ANDROID) +# include <android/log.h> +# include "mozilla/layers/UiCompositorControllerParent.h" +# include "mozilla/widget/AndroidCompositorWidget.h" +#endif +#include "GeckoProfiler.h" +#include "FrameUniformityData.h" +#include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch +#include "VsyncSource.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +static bool IsSameDimension(hal::ScreenOrientation o1, + hal::ScreenOrientation o2) { + bool isO1portrait = (o1 == hal::eScreenOrientation_PortraitPrimary || + o1 == hal::eScreenOrientation_PortraitSecondary); + bool isO2portrait = (o2 == hal::eScreenOrientation_PortraitPrimary || + o2 == hal::eScreenOrientation_PortraitSecondary); + return !(isO1portrait ^ isO2portrait); +} + +static bool ContentMightReflowOnOrientationChange(const IntRect& rect) { + return rect.Width() != rect.Height(); +} + +AsyncCompositionManager::AsyncCompositionManager( + CompositorBridgeParent* aParent, HostLayerManager* aManager) + : mLayerManager(aManager), + mIsFirstPaint(true), + mLayersUpdated(false), + mReadyForCompose(true), + mCompositorBridge(aParent) { + MOZ_ASSERT(mCompositorBridge); +} + +AsyncCompositionManager::~AsyncCompositionManager() = default; + +void AsyncCompositionManager::ResolveRefLayers( + CompositorBridgeParent* aCompositor, bool* aHasRemoteContent, + bool* aResolvePlugins) { + if (aHasRemoteContent) { + *aHasRemoteContent = false; + } + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + // If valid *aResolvePlugins indicates if we need to update plugin geometry + // when we walk the tree. + bool resolvePlugins = (aCompositor && aResolvePlugins && *aResolvePlugins); +#endif + + if (!mLayerManager->GetRoot()) { + // Updated the return value since this result controls completing + // composition. + if (aResolvePlugins) { + *aResolvePlugins = false; + } + return; + } + + mReadyForCompose = true; + bool hasRemoteContent = false; + bool didResolvePlugins = false; + + ForEachNode<ForwardIterator>(mLayerManager->GetRoot(), [&](Layer* layer) { + RefLayer* refLayer = layer->AsRefLayer(); + if (!refLayer) { + return; + } + + hasRemoteContent = true; + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree( + refLayer->GetReferentId()); + if (!state) { + return; + } + + Layer* referent = state->mRoot; + if (!referent) { + return; + } + + if (!refLayer->GetLocalVisibleRegion().IsEmpty()) { + hal::ScreenOrientation chromeOrientation = mTargetConfig.orientation(); + hal::ScreenOrientation contentOrientation = + state->mTargetConfig.orientation(); + if (!IsSameDimension(chromeOrientation, contentOrientation) && + ContentMightReflowOnOrientationChange( + mTargetConfig.naturalBounds())) { + mReadyForCompose = false; + } + } + + refLayer->ConnectReferentLayer(referent); + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + if (resolvePlugins) { + didResolvePlugins |= + aCompositor->UpdatePluginWindowState(refLayer->GetReferentId()); + } +#endif + }); + + if (aHasRemoteContent) { + *aHasRemoteContent = hasRemoteContent; + } + if (aResolvePlugins) { + *aResolvePlugins = didResolvePlugins; + } +} + +void AsyncCompositionManager::DetachRefLayers() { + if (!mLayerManager->GetRoot()) { + return; + } + + mReadyForCompose = false; + + ForEachNodePostOrder<ForwardIterator>( + mLayerManager->GetRoot(), [&](Layer* layer) { + RefLayer* refLayer = layer->AsRefLayer(); + if (!refLayer) { + return; + } + + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree( + refLayer->GetReferentId()); + if (!state) { + return; + } + + Layer* referent = state->mRoot; + if (referent) { + refLayer->DetachReferentLayer(referent); + } + }); +} + +void AsyncCompositionManager::ComputeRotation() { + if (!mTargetConfig.naturalBounds().IsEmpty()) { + mWorldTransform = ComputeTransformForRotation(mTargetConfig.naturalBounds(), + mTargetConfig.rotation()); + } +} + +static void GetBaseTransform(Layer* aLayer, Matrix4x4* aTransform) { + // Start with the animated transform if there is one + *aTransform = (aLayer->AsHostLayer()->GetShadowTransformSetByAnimation() + ? aLayer->GetLocalTransform() + : aLayer->GetTransform()); +} + +static void TransformClipRect( + Layer* aLayer, const ParentLayerToParentLayerMatrix4x4& aTransform) { + MOZ_ASSERT(aTransform.Is2D()); + const Maybe<ParentLayerIntRect>& clipRect = + aLayer->AsHostLayer()->GetShadowClipRect(); + if (clipRect) { + ParentLayerIntRect transformed = TransformBy(aTransform, *clipRect); + aLayer->AsHostLayer()->SetShadowClipRect(Some(transformed)); + } +} + +// Similar to TransformFixedClip(), but only transforms the fixed part of the +// clip. +static void TransformFixedClip( + Layer* aLayer, const ParentLayerToParentLayerMatrix4x4& aTransform, + AsyncCompositionManager::ClipParts& aClipParts) { + MOZ_ASSERT(aTransform.Is2D()); + if (aClipParts.mFixedClip) { + *aClipParts.mFixedClip = TransformBy(aTransform, *aClipParts.mFixedClip); + aLayer->AsHostLayer()->SetShadowClipRect(aClipParts.Intersect()); + } +} + +/** + * Set the given transform as the shadow transform on the layer, assuming + * that the given transform already has the pre- and post-scales applied. + * That is, this function cancels out the pre- and post-scales from aTransform + * before setting it as the shadow transform on the layer, so that when + * the layer's effective transform is computed, the pre- and post-scales will + * only be applied once. + */ +static void SetShadowTransform(Layer* aLayer, + LayerToParentLayerMatrix4x4 aTransform) { + if (ContainerLayer* c = aLayer->AsContainerLayer()) { + aTransform.PreScale(1.0f / c->GetPreXScale(), 1.0f / c->GetPreYScale(), 1); + } + aTransform.PostScale(1.0f / aLayer->GetPostXScale(), + 1.0f / aLayer->GetPostYScale(), 1); + aLayer->AsHostLayer()->SetShadowBaseTransform(aTransform.ToUnknownMatrix()); +} + +static void TranslateShadowLayer( + Layer* aLayer, const ParentLayerPoint& aTranslation, bool aAdjustClipRect, + AsyncCompositionManager::ClipPartsCache* aClipPartsCache) { + // This layer might also be a scrollable layer and have an async transform. + // To make sure we don't clobber that, we start with the shadow transform. + // (i.e. GetLocalTransform() instead of GetTransform()). + // Note that the shadow transform is reset on every frame of composition so + // we don't have to worry about the adjustments compounding over successive + // frames. + LayerToParentLayerMatrix4x4 layerTransform = aLayer->GetLocalTransformTyped(); + + // Apply the translation to the layer transform. + layerTransform.PostTranslate(aTranslation); + + SetShadowTransform(aLayer, layerTransform); + aLayer->AsHostLayer()->SetShadowTransformSetByAnimation(false); + + if (aAdjustClipRect) { + auto transform = + ParentLayerToParentLayerMatrix4x4::Translation(aTranslation); + // If we're passed a clip parts cache, only transform the fixed part of + // the clip. + if (aClipPartsCache) { + auto iter = aClipPartsCache->find(aLayer); + MOZ_ASSERT(iter != aClipPartsCache->end()); + TransformFixedClip(aLayer, transform, iter->second); + } else { + TransformClipRect(aLayer, transform); + } + + // If a fixed- or sticky-position layer has a mask layer, that mask should + // move along with the layer, so apply the translation to the mask layer + // too. + if (Layer* maskLayer = aLayer->GetMaskLayer()) { + TranslateShadowLayer(maskLayer, aTranslation, false, aClipPartsCache); + } + } +} + +static void AccumulateLayerTransforms(Layer* aLayer, Layer* aAncestor, + Matrix4x4& aMatrix) { + // Accumulate the transforms between this layer and the subtree root layer. + for (Layer* l = aLayer; l && l != aAncestor; l = l->GetParent()) { + Matrix4x4 transform; + GetBaseTransform(l, &transform); + aMatrix *= transform; + } +} + +/** + * Finds the metrics on |aLayer| with scroll id |aScrollId|, and returns a + * LayerMetricsWrapper representing the (layer, metrics) pair, or the null + * LayerMetricsWrapper if no matching metrics could be found. + */ +static LayerMetricsWrapper FindMetricsWithScrollId( + Layer* aLayer, ScrollableLayerGuid::ViewID aScrollId) { + for (uint64_t i = 0; i < aLayer->GetScrollMetadataCount(); ++i) { + if (aLayer->GetFrameMetrics(i).GetScrollId() == aScrollId) { + return LayerMetricsWrapper(aLayer, i); + } + } + return LayerMetricsWrapper(); +} + +/** + * Checks whether the (layer, metrics) pair (aTransformedLayer, + * aTransformedMetrics) is on the path from |aFixedLayer| to the metrics with + * scroll id |aFixedWithRespectTo|, inclusive. + */ +static bool AsyncTransformShouldBeUnapplied( + Layer* aFixedLayer, ScrollableLayerGuid::ViewID aFixedWithRespectTo, + Layer* aTransformedLayer, ScrollableLayerGuid::ViewID aTransformedMetrics) { + LayerMetricsWrapper transformed = + FindMetricsWithScrollId(aTransformedLayer, aTransformedMetrics); + if (!transformed.IsValid()) { + return false; + } + // It's important to start at the bottom, because the fixed layer itself + // could have the transformed metrics, and they can be at the bottom. + LayerMetricsWrapper current(aFixedLayer, + LayerMetricsWrapper::StartAt::BOTTOM); + bool encounteredTransformedLayer = false; + // The transformed layer is on the path from |aFixedLayer| to the fixed-to + // layer if as we walk up the (layer, metrics) tree starting from + // |aFixedLayer|, we *first* encounter the transformed layer, and *then* (or + // at the same time) the fixed-to layer. + while (current) { + if (!encounteredTransformedLayer && current == transformed) { + encounteredTransformedLayer = true; + } + if (current.Metrics().GetScrollId() == aFixedWithRespectTo) { + return encounteredTransformedLayer; + } + current = current.GetParent(); + // It's possible that we reach a layers id boundary before we reach an + // ancestor with the scroll id |aFixedWithRespectTo| (this could happen + // e.g. if the scroll frame with that scroll id uses containerless + // scrolling). In such a case, stop the walk, as a new layers id could + // have a different layer with scroll id |aFixedWithRespectTo| which we + // don't intend to match. + if (current && current.AsRefLayer() != nullptr) { + break; + } + } + return false; +} + +// If |aLayer| is fixed or sticky, returns the scroll id of the scroll frame +// that it's fixed or sticky to. Otherwise, returns Nothing(). +static Maybe<ScrollableLayerGuid::ViewID> IsFixedOrSticky(Layer* aLayer) { + bool isRootOfFixedSubtree = aLayer->GetIsFixedPosition() && + !aLayer->GetParent()->GetIsFixedPosition(); + if (isRootOfFixedSubtree) { + return Some(aLayer->GetFixedPositionScrollContainerId()); + } + if (aLayer->GetIsStickyPosition()) { + return Some(aLayer->GetStickyScrollContainerId()); + } + return Nothing(); +} + +void AsyncCompositionManager::AlignFixedAndStickyLayers( + Layer* aTransformedSubtreeRoot, Layer* aStartTraversalAt, + SideBits aStuckSides, ScrollableLayerGuid::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aFixedLayerMargins, ClipPartsCache& aClipPartsCache, + const ScreenMargin& aGeckoFixedLayerMargins) { + Layer* layer = aStartTraversalAt; + bool needsAsyncTransformUnapplied = false; + if (Maybe<ScrollableLayerGuid::ViewID> fixedTo = IsFixedOrSticky(layer)) { + needsAsyncTransformUnapplied = AsyncTransformShouldBeUnapplied( + layer, *fixedTo, aTransformedSubtreeRoot, aTransformScrollId); + } + + // We want to process all the fixed and sticky descendants of + // aTransformedSubtreeRoot. Once we do encounter such a descendant, we don't + // need to recurse any deeper because the adjustment to the fixed or sticky + // layer will apply to its subtree. + if (!needsAsyncTransformUnapplied) { + for (Layer* child = layer->GetFirstChild(); child; + child = child->GetNextSibling()) { + AlignFixedAndStickyLayers(aTransformedSubtreeRoot, child, aStuckSides, + aTransformScrollId, aPreviousTransformForRoot, + aCurrentTransformForRoot, aFixedLayerMargins, + aClipPartsCache, aGeckoFixedLayerMargins); + } + return; + } + + AdjustFixedOrStickyLayer(aTransformedSubtreeRoot, layer, aStuckSides, + aTransformScrollId, aPreviousTransformForRoot, + aCurrentTransformForRoot, aFixedLayerMargins, + aClipPartsCache, aGeckoFixedLayerMargins); +} + +// Determine the amount of overlap between the 1D vector |aTranslation| +// and the interval [aMin, aMax]. +static gfxFloat IntervalOverlap(gfxFloat aTranslation, gfxFloat aMin, + gfxFloat aMax) { + if (aTranslation > 0) { + return std::max(0.0, std::min(aMax, aTranslation) - std::max(aMin, 0.0)); + } + + return std::min(0.0, std::max(aMin, aTranslation) - std::min(aMax, 0.0)); +} + +void AsyncCompositionManager::AdjustFixedOrStickyLayer( + Layer* aTransformedSubtreeRoot, Layer* aFixedOrSticky, SideBits aStuckSides, + ScrollableLayerGuid::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aFixedLayerMargins, ClipPartsCache& aClipPartsCache, + const ScreenMargin& aGeckoFixedLayerMargins) { + Layer* layer = aFixedOrSticky; + + // Insert a translation so that the position of the anchor point is the same + // before and after the change to the transform of aTransformedSubtreeRoot. + + // Accumulate the transforms between this layer and the subtree root layer. + Matrix4x4 ancestorTransform; + if (layer != aTransformedSubtreeRoot) { + AccumulateLayerTransforms(layer->GetParent(), aTransformedSubtreeRoot, + ancestorTransform); + } + ancestorTransform.NudgeToIntegersFixedEpsilon(); + + // A transform creates a containing block for fixed-position descendants, + // so there shouldn't be a transform in between the fixed layer and + // the subtree root layer. + if (layer->GetIsFixedPosition()) { + MOZ_ASSERT(ancestorTransform.IsIdentity()); + } + + // Calculate the cumulative transforms between the subtree root with the + // old transform and the current transform. + // For coordinate purposes, we'll treat the subtree root layer as the + // "parent" layer, even though it could be a farther ancestor. + auto oldCumulativeTransform = ViewAs<LayerToParentLayerMatrix4x4>( + ancestorTransform * aPreviousTransformForRoot.ToUnknownMatrix()); + auto newCumulativeTransform = ViewAs<LayerToParentLayerMatrix4x4>( + ancestorTransform * aCurrentTransformForRoot.ToUnknownMatrix()); + + // We're going to be inverting |newCumulativeTransform|. If it's singular, + // there's nothing we can do. + if (newCumulativeTransform.IsSingular()) { + return; + } + + // Since we create container layers for fixed layers, there shouldn't + // a local CSS or OMTA transform on the fixed layer, either (any local + // transform would go onto a descendant layer inside the container + // layer). +#ifdef DEBUG + Matrix4x4 localTransform; + GetBaseTransform(layer, &localTransform); + localTransform.NudgeToIntegersFixedEpsilon(); + MOZ_ASSERT(localTransform.IsIdentity()); +#endif + + // Now work out the translation necessary to make sure the layer doesn't + // move given the new sub-tree root transform. + + // Get the layer's fixed anchor point, in the layer's local coordinate space + // (before any transform is applied). + LayerPoint anchor = layer->GetFixedPositionAnchor(); + + SideBits sideBits = layer->GetFixedPositionSides(); + if (layer->GetIsStickyPosition()) { + // For sticky items, it may be that only some of the sides are actively + // stuck. Only take into account those sides. + sideBits &= aStuckSides; + } + + // Offset the layer's anchor point to make sure fixed position content + // respects content document fixed position margins. + ScreenPoint offset = apz::ComputeFixedMarginsOffset( + aFixedLayerMargins, sideBits, + // For sticky layers, we don't need to factor aGeckoFixedLayerMargins + // because Gecko doesn't shift the position of sticky elements for dynamic + // toolbar movements. + layer->GetIsStickyPosition() ? ScreenMargin() : aGeckoFixedLayerMargins); + + // Fixed margins only apply to layers fixed to the root, so we can view + // the offset in layer space. + LayerPoint offsetAnchor = + anchor + ViewAs<LayerPixel>( + offset, PixelCastJustification::ScreenIsParentLayerForRoot); + + // Additionally transform the anchor to compensate for the change + // from the old transform to the new transform. We do + // this by using the old transform to take the offset anchor back into + // subtree root space, and then the inverse of the new transform + // to bring it back to layer space. + ParentLayerPoint offsetAnchorInSubtreeRootSpace = + oldCumulativeTransform.TransformPoint(offsetAnchor); + LayerPoint transformedAnchor = + newCumulativeTransform.Inverse().TransformPoint( + offsetAnchorInSubtreeRootSpace); + + // We want to translate the layer by the difference between + // |transformedAnchor| and |anchor|. + LayerPoint translation = transformedAnchor - anchor; + + // A fixed layer will "consume" (be unadjusted by) the entire translation + // calculated above. A sticky layer may consume all, part, or none of it, + // depending on where we are relative to its sticky scroll range. + // The remainder of the translation (the unconsumed portion) needs to + // be propagated to descendant fixed/sticky layers. + LayerPoint unconsumedTranslation; + + if (layer->GetIsStickyPosition()) { + // For sticky positioned layers, the difference between the two rectangles + // defines a pair of translation intervals in each dimension through which + // the layer should not move relative to the scroll container. To + // accomplish this, we limit each dimension of the |translation| to that + // part of it which overlaps those intervals. + const LayerRectAbsolute& stickyOuter = layer->GetStickyScrollRangeOuter(); + const LayerRectAbsolute& stickyInner = layer->GetStickyScrollRangeInner(); + + LayerPoint originalTranslation = translation; + translation.y = + IntervalOverlap(translation.y, stickyOuter.Y(), stickyOuter.YMost()) - + IntervalOverlap(translation.y, stickyInner.Y(), stickyInner.YMost()); + translation.x = + IntervalOverlap(translation.x, stickyOuter.X(), stickyOuter.XMost()) - + IntervalOverlap(translation.x, stickyInner.X(), stickyInner.XMost()); + unconsumedTranslation = translation - originalTranslation; + } + + // Finally, apply the translation to the layer transform. Note that in cases + // where the async transform on |aTransformedSubtreeRoot| affects this layer's + // clip rect, we need to apply the same translation to said clip rect, so + // that the effective transform on the clip rect takes it back to where it was + // originally, had there been no async scroll. + TranslateShadowLayer( + layer, + ViewAs<ParentLayerPixel>(translation, + PixelCastJustification::NoTransformOnLayer), + true, &aClipPartsCache); + + // Propragate the unconsumed portion of the translation to descendant + // fixed/sticky layers. + if (unconsumedTranslation != LayerPoint()) { + // Take the computations we performed to derive |translation| from + // |aCurrentTransformForRoot|, and perform them in reverse, keeping other + // quantities fixed, to come up with a new transform |newTransform| that + // would produce |unconsumedTranslation|. + LayerPoint newTransformedAnchor = unconsumedTranslation + anchor; + ParentLayerPoint newTransformedAnchorInSubtreeRootSpace = + oldCumulativeTransform.TransformPoint(newTransformedAnchor); + LayerToParentLayerMatrix4x4 newTransform = aPreviousTransformForRoot; + newTransform.PostTranslate(newTransformedAnchorInSubtreeRootSpace - + offsetAnchorInSubtreeRootSpace); + + // Propagate this new transform to our descendants as the new value of + // |aCurrentTransformForRoot|. This allows them to consume the unconsumed + // translation. + for (Layer* child = layer->GetFirstChild(); child; + child = child->GetNextSibling()) { + AlignFixedAndStickyLayers(aTransformedSubtreeRoot, child, aStuckSides, + aTransformScrollId, aPreviousTransformForRoot, + newTransform, aFixedLayerMargins, + aClipPartsCache, aGeckoFixedLayerMargins); + } + } +} + +bool AsyncCompositionManager::SampleAnimations(Layer* aLayer, + TimeStamp aCurrentFrameTime) { + CompositorAnimationStorage* storage = + mCompositorBridge->GetAnimationStorage(); + MOZ_ASSERT(storage); + + return storage->SampleAnimations(aLayer, mCompositorBridge, + mPreviousFrameTimeStamp, aCurrentFrameTime); +} + +void AsyncCompositionManager::RecordShadowTransforms(Layer* aLayer) { + MOZ_ASSERT(StaticPrefs::gfx_vsync_collect_scroll_transforms()); + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + ForEachNodePostOrder<ForwardIterator>(aLayer, [this](Layer* layer) { + for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) { + if (!layer->GetFrameMetrics(i).IsScrollable()) { + continue; + } + gfx::Matrix4x4 shadowTransform = + layer->AsHostLayer()->GetShadowBaseTransform(); + if (!shadowTransform.Is2D()) { + continue; + } + + Matrix transform = shadowTransform.As2D(); + if (transform.IsTranslation() && !shadowTransform.IsIdentity()) { + Point translation = transform.GetTranslation(); + mLayerTransformRecorder.RecordTransform(layer, translation); + return; + } + } + }); +} + +static AsyncTransformComponentMatrix AdjustForClip( + const AsyncTransformComponentMatrix& asyncTransform, Layer* aLayer) { + AsyncTransformComponentMatrix result = asyncTransform; + + // Container layers start at the origin, but they are clipped to where they + // actually have content on the screen. The tree transform is meant to apply + // to the clipped area. If the tree transform includes a scale component, + // then applying it to container as-is will produce incorrect results. To + // avoid this, translate the layer so that the clip rect starts at the origin, + // apply the tree transform, and translate back. + if (const Maybe<ParentLayerIntRect>& shadowClipRect = + aLayer->AsHostLayer()->GetShadowClipRect()) { + if (shadowClipRect->TopLeft() != + ParentLayerIntPoint()) { // avoid a gratuitous change of basis + result.ChangeBasis(shadowClipRect->X(), shadowClipRect->Y(), 0); + } + } + return result; +} + +static void ExpandRootClipRect(Layer* aLayer, + const ScreenMargin& aFixedLayerMargins) { + // For Fennec we want to expand the root scrollable layer clip rect based on + // the fixed position margins. In particular, we want this while the dynamic + // toolbar is in the process of sliding offscreen and the area of the + // LayerView visible to the user is larger than the viewport size that Gecko + // knows about (and therefore larger than the clip rect). We could also just + // clear the clip rect on aLayer entirely but this seems more precise. + Maybe<ParentLayerIntRect> rootClipRect = + aLayer->AsHostLayer()->GetShadowClipRect(); + if (rootClipRect && aFixedLayerMargins != ScreenMargin()) { +#ifndef MOZ_WIDGET_ANDROID + // We should never enter here on anything other than Fennec, since + // aFixedLayerMargins should be empty everywhere else. + MOZ_ASSERT(false); +#endif + ParentLayerRect rect(rootClipRect.value()); + rect.Deflate(ViewAs<ParentLayerPixel>( + aFixedLayerMargins, + PixelCastJustification::ScreenIsParentLayerForRoot)); + aLayer->AsHostLayer()->SetShadowClipRect(Some(RoundedOut(rect))); + } +} + +#ifdef MOZ_WIDGET_ANDROID +static void MoveScrollbarForLayerMargin( + Layer* aRoot, ScrollableLayerGuid::ViewID aRootScrollId, + const ScreenMargin& aFixedLayerMargins) { + // See bug 1223928 comment 9 - once we can detect the RCD with just the + // isRootContent flag on the metrics, we can probably move this code into + // ApplyAsyncTransformToScrollbar rather than having it as a separate + // adjustment on the layer tree. + Layer* scrollbar = + BreadthFirstSearch<ReverseIterator>(aRoot, [aRootScrollId](Layer* aNode) { + return (aNode->GetScrollbarData().IsThumb() && + aNode->GetScrollbarData().mDirection.isSome() && + *aNode->GetScrollbarData().mDirection == + ScrollDirection::eHorizontal && + aNode->GetScrollbarData().mTargetViewId == aRootScrollId); + }); + if (scrollbar) { + // Shift the horizontal scrollbar down into the new space exposed by the + // dynamic toolbar hiding. Technically we should also scale the vertical + // scrollbar a bit to expand into the new space but it's not as noticeable + // and it would add a lot more complexity, so we're going with the "it's not + // worth it" justification. + TranslateShadowLayer(scrollbar, + ParentLayerPoint(0, -aFixedLayerMargins.bottom), true, + nullptr); + if (scrollbar->GetParent()) { + // The layer that has the HORIZONTAL direction sits inside another + // ContainerLayer. This ContainerLayer also has a clip rect that causes + // the scrollbar to get clipped. We need to expand that clip rect to + // prevent that from happening. This is kind of ugly in that we're + // assuming a particular layer tree structure but short of adding more + // flags to the layer there doesn't appear to be a good way to do this. + ExpandRootClipRect(scrollbar->GetParent(), aFixedLayerMargins); + } + } +} +#endif + +bool AsyncCompositionManager::ApplyAsyncContentTransformToTree( + Layer* aLayer, bool* aOutFoundRoot) { + bool appliedTransform = false; + std::stack<Maybe<ParentLayerIntRect>> stackDeferredClips; + std::stack<LayersId> layersIds; + layersIds.push(mCompositorBridge->RootLayerTreeId()); + + // Maps layers to their ClipParts. The parts are not stored individually + // on the layer, but during AlignFixedAndStickyLayers we need access to + // the individual parts for descendant layers. + ClipPartsCache clipPartsCache; + + Layer* zoomContainer = nullptr; + Maybe<LayerMetricsWrapper> zoomedMetrics; + + ForEachNode<ForwardIterator>( + aLayer, + [&](Layer* layer) { + if (layer->AsRefLayer()) { + layersIds.push(layer->AsRefLayer()->GetReferentId()); + } + + stackDeferredClips.push(Maybe<ParentLayerIntRect>()); + + // If we encounter the async zoom container, find the corresponding + // APZC and stash it into |zoomedMetrics|. + // (We stash it in the form of a LayerMetricsWrapper because + // APZSampler requires going through that rather than using the APZC + // directly.) + // We do this on the way down the tree (i.e. here in the pre-action) + // so that by the time we encounter the layers with the RCD-RSF's + // scroll metadata (which will be descendants of the async zoom + // container), we can check for it and know we should only apply the + // scroll portion of the async transform to those layers (as the zoom + // portion will go on the async zoom container). + if (Maybe<ScrollableLayerGuid::ViewID> zoomedScrollId = + layer->IsAsyncZoomContainer()) { + zoomContainer = layer; + ForEachNode<ForwardIterator>( + LayerMetricsWrapper(layer), + [zoomedScrollId, &zoomedMetrics](LayerMetricsWrapper aWrapper) { + // Do not descend into layer subtrees with a different layers + // id. + if (aWrapper.AsRefLayer()) { + return TraversalFlag::Skip; + } + + if (aWrapper.Metrics().GetScrollId() == *zoomedScrollId) { + zoomedMetrics = Some(aWrapper); + MOZ_ASSERT(zoomedMetrics->GetApzc()); + return TraversalFlag::Abort; + } + + return TraversalFlag::Continue; + }); + } + }, + [&](Layer* layer) { + Maybe<ParentLayerIntRect> clipDeferredFromChildren = + stackDeferredClips.top(); + stackDeferredClips.pop(); + MOZ_ASSERT(!layersIds.empty()); + LayersId currentLayersId = layersIds.top(); + LayerToParentLayerMatrix4x4 oldTransform = + layer->GetTransformTyped() * AsyncTransformMatrix(); + + AsyncTransformComponentMatrix combinedAsyncTransform; + bool hasAsyncTransform = false; + // Only set on the root layer for Android. + ScreenMargin fixedLayerMargins; + + // Each layer has multiple clips: + // - Its local clip, which is fixed to the layer contents, i.e. it + // moves with those async transforms which the layer contents move + // with. + // - Its scrolled clip, which moves with all async transforms. + // - For each ScrollMetadata on the layer, a scroll clip. This + // includes the composition bounds and any other clips induced by + // layout. This moves with async transforms from ScrollMetadatas + // above it. + // In this function, these clips are combined into two shadow clip + // parts: + // - The fixed clip, which consists of the local clip only, initially + // transformed by all async transforms. + // - The scrolled clip, which consists of the other clips, transformed + // by the appropriate transforms. + // These two parts are kept separate for now, because for fixed layers, + // we need to adjust the fixed clip (to cancel out some async + // transforms). The parts are kept in a cache which is cleared at the + // beginning of every composite. The final shadow clip for the layer is + // the intersection of the (possibly adjusted) fixed clip and the + // scrolled clip. + ClipParts& clipParts = clipPartsCache[layer]; + clipParts.mFixedClip = layer->GetClipRect(); + clipParts.mScrolledClip = layer->GetScrolledClipRect(); + + // If we are a perspective transform ContainerLayer, apply the clip + // deferred from our child (if there is any) before we iterate over our + // frame metrics, because this clip is subject to all async transforms + // of this layer. Since this clip came from the a scroll clip on the + // child, it becomes part of our scrolled clip. + clipParts.mScrolledClip = IntersectMaybeRects(clipDeferredFromChildren, + clipParts.mScrolledClip); + + // The transform of a mask layer is relative to the masked layer's + // parent layer. So whenever we apply an async transform to a layer, we + // need to apply that same transform to the layer's own mask layer. A + // layer can also have "ancestor" mask layers for any rounded clips from + // its ancestor scroll frames. A scroll frame mask layer only needs to + // be async transformed for async scrolls of this scroll frame's + // ancestor scroll frames, not for async scrolls of this scroll frame + // itself. In the loop below, we iterate over scroll frames from inside + // to outside. At each iteration, this array contains the layer's + // ancestor mask layers of all scroll frames inside the current one. + nsTArray<Layer*> ancestorMaskLayers; + + // The layer's scrolled clip can have an ancestor mask layer as well, + // which is moved by all async scrolls on this layer. + if (const Maybe<LayerClip>& scrolledClip = layer->GetScrolledClip()) { + if (scrolledClip->GetMaskLayerIndex()) { + ancestorMaskLayers.AppendElement(layer->GetAncestorMaskLayerAt( + *scrolledClip->GetMaskLayerIndex())); + } + } + + if (RefPtr<APZSampler> sampler = mCompositorBridge->GetAPZSampler()) { + for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) { + LayerMetricsWrapper wrapper(layer, i); + if (!wrapper.GetApzc()) { + continue; + } + + const FrameMetrics& metrics = wrapper.Metrics(); + MOZ_ASSERT(metrics.IsScrollable()); + + hasAsyncTransform = true; + + AsyncTransformComponents asyncTransformComponents = + (zoomedMetrics && + sampler->GetGuid(*zoomedMetrics) == sampler->GetGuid(wrapper)) + ? AsyncTransformComponents{AsyncTransformComponent::eLayout} + : LayoutAndVisual; + AsyncTransform asyncTransformWithoutOverscroll = + sampler->GetCurrentAsyncTransform(wrapper, + asyncTransformComponents); + Maybe<CompositionPayload> payload = + sampler->NotifyScrollSampling(wrapper); + // The scroll latency should be measured between composition and the + // first scrolling event. Otherwise we observe metrics with <16ms + // latency even when frame.delay is enabled. + if (payload.isSome()) { + mLayerManager->RegisterPayload(*payload); + } + + AsyncTransformComponentMatrix overscrollTransform = + sampler->GetOverscrollTransform(wrapper); + AsyncTransformComponentMatrix asyncTransform = + AsyncTransformComponentMatrix(asyncTransformWithoutOverscroll) * + overscrollTransform; + + if (!layer->IsScrollableWithoutContent()) { + sampler->MarkAsyncTransformAppliedToContent(wrapper); + } + + const ScrollMetadata& scrollMetadata = wrapper.Metadata(); + +#if defined(MOZ_WIDGET_ANDROID) + // If we find a metrics which is the root content doc, use that. If + // not, use the root layer. Since this function recurses on children + // first we should only end up using the root layer if the entire + // tree was devoid of a root content metrics. This is a temporary + // solution; in the long term we should not need the root content + // metrics at all. See bug 1201529 comment 6 for details. + if (!(*aOutFoundRoot)) { + *aOutFoundRoot = + metrics.IsRootContent() || /* RCD */ + (layer->GetParent() == nullptr && /* rootmost metrics */ + i + 1 >= layer->GetScrollMetadataCount()); + if (*aOutFoundRoot) { + mRootScrollableId = metrics.GetScrollId(); + Compositor* compositor = mLayerManager->GetCompositor(); + if (CompositorBridgeParent* bridge = + compositor->GetCompositorBridgeParent()) { + LayersId rootLayerTreeId = bridge->RootLayerTreeId(); + GeckoViewMetrics gvMetrics = + sampler->GetGeckoViewMetrics(wrapper); + if (mIsFirstPaint || GeckoViewMetricsHaveUpdated(gvMetrics)) { + if (RefPtr<UiCompositorControllerParent> uiController = + UiCompositorControllerParent:: + GetFromRootLayerTreeId(rootLayerTreeId)) { + uiController->NotifyUpdateScreenMetrics(gvMetrics); + } + mLastMetrics = gvMetrics; + } + if (mIsFirstPaint) { + if (RefPtr<UiCompositorControllerParent> uiController = + UiCompositorControllerParent:: + GetFromRootLayerTreeId(rootLayerTreeId)) { + uiController->NotifyFirstPaint(); + } + mIsFirstPaint = false; + } + if (mLayersUpdated) { + LayersId rootLayerTreeId = bridge->RootLayerTreeId(); + if (RefPtr<UiCompositorControllerParent> uiController = + UiCompositorControllerParent:: + GetFromRootLayerTreeId(rootLayerTreeId)) { + uiController->NotifyLayersUpdated(); + } + mLayersUpdated = false; + } + } + fixedLayerMargins = GetFixedLayerMargins(); + } + } +#else + *aOutFoundRoot = false; + // Non-Android platforms still care about this flag being cleared + // after the first call to TransformShadowTree(). + mIsFirstPaint = false; +#endif + + // Transform the current local clips by this APZC's async transform. + MOZ_ASSERT(asyncTransform.Is2D()); + if (clipParts.mFixedClip) { + *clipParts.mFixedClip = + TransformBy(asyncTransform, *clipParts.mFixedClip); + } + if (clipParts.mScrolledClip) { + *clipParts.mScrolledClip = + TransformBy(asyncTransform, *clipParts.mScrolledClip); + } + // Note: we don't set the layer's shadow clip rect property yet; + // AlignFixedAndStickyLayers will use the clip parts from the clip + // parts cache. + + combinedAsyncTransform *= asyncTransform; + + // For the purpose of aligning fixed and sticky layers, we disregard + // the overscroll transform as well as any OMTA transform when + // computing the 'aCurrentTransformForRoot' parameter. This ensures + // that the overscroll and OMTA transforms are not unapplied, and + // therefore that the visual effects apply to fixed and sticky + // layers. We do this by using GetTransform() as the base transform + // rather than GetLocalTransform(), which would include those + // factors. + LayerToParentLayerMatrix4x4 transformWithoutOverscrollOrOmta = + layer->GetTransformTyped() * + CompleteAsyncTransform(AdjustForClip(asyncTransform, layer)); + // See bug 1630274 for why we pass eNone here; fixing that bug will + // probably end up changing this to be more correct. + AlignFixedAndStickyLayers(layer, layer, SideBits::eNone, + metrics.GetScrollId(), oldTransform, + transformWithoutOverscrollOrOmta, + fixedLayerMargins, clipPartsCache, + sampler->GetGeckoFixedLayerMargins()); + + // Combine the local clip with the ancestor scrollframe clip. This + // is not included in the async transform above, since the ancestor + // clip should not move with this APZC. + if (scrollMetadata.HasScrollClip()) { + ParentLayerIntRect clip = + scrollMetadata.ScrollClip().GetClipRect(); + if (layer->GetParent() && + layer->GetParent()->GetTransformIsPerspective()) { + // If our parent layer has a perspective transform, we want to + // apply our scroll clip to it instead of to this layer (see bug + // 1168263). A layer with a perspective transform shouldn't have + // multiple children with FrameMetrics, nor a child with + // multiple FrameMetrics. (A child with multiple FrameMetrics + // would mean that there's *another* scrollable element between + // the one with the CSS perspective and the transformed element. + // But you'd have to use preserve-3d on the inner scrollable + // element in order to have the perspective apply to the + // transformed child, and preserve-3d is not supported on + // scrollable elements, so this case can't occur.) + MOZ_ASSERT(!stackDeferredClips.top()); + stackDeferredClips.top().emplace(clip); + } else { + clipParts.mScrolledClip = + IntersectMaybeRects(Some(clip), clipParts.mScrolledClip); + } + } + + // Do the same for the ancestor mask layers: ancestorMaskLayers + // contains the ancestor mask layers for scroll frames *inside* the + // current scroll frame, so these are the ones we need to shift by + // our async transform. + for (Layer* ancestorMaskLayer : ancestorMaskLayers) { + SetShadowTransform( + ancestorMaskLayer, + ancestorMaskLayer->GetLocalTransformTyped() * asyncTransform); + } + + // Append the ancestor mask layer for this scroll frame to + // ancestorMaskLayers. + if (scrollMetadata.HasScrollClip()) { + const LayerClip& scrollClip = scrollMetadata.ScrollClip(); + if (scrollClip.GetMaskLayerIndex()) { + size_t maskLayerIndex = scrollClip.GetMaskLayerIndex().value(); + Layer* ancestorMaskLayer = + layer->GetAncestorMaskLayerAt(maskLayerIndex); + ancestorMaskLayers.AppendElement(ancestorMaskLayer); + } + } + } + + if (Maybe<ScrollableLayerGuid::ViewID> zoomedScrollId = + layer->IsAsyncZoomContainer()) { + if (zoomedMetrics) { + AsyncTransform zoomTransform = sampler->GetCurrentAsyncTransform( + *zoomedMetrics, {AsyncTransformComponent::eVisual}); + hasAsyncTransform = true; + combinedAsyncTransform *= + AsyncTransformComponentMatrix(zoomTransform); + } else { + // TODO: Is this normal? It happens on some pages, such as + // about:config on mobile, for just one frame or so, before the + // scroll metadata for zoomedScrollId appears in the layer tree. + } + } + + auto IsFixedToZoomContainer = [&](Layer* aFixedLayer) { + if (!zoomedMetrics) { + return false; + } + ScrollableLayerGuid::ViewID targetId = + aFixedLayer->GetFixedPositionScrollContainerId(); + MOZ_ASSERT(targetId != ScrollableLayerGuid::NULL_SCROLL_ID); + ScrollableLayerGuid rootContent = sampler->GetGuid(*zoomedMetrics); + return rootContent.mScrollId == targetId && + rootContent.mLayersId == currentLayersId; + }; + + auto SidesStuckToZoomContainer = [&](Layer* aLayer) -> SideBits { + SideBits result = SideBits::eNone; + if (!zoomedMetrics) { + return result; + } + if (!aLayer->GetIsStickyPosition()) { + return result; + } + + ScrollableLayerGuid::ViewID targetId = + aLayer->GetStickyScrollContainerId(); + if (targetId == ScrollableLayerGuid::NULL_SCROLL_ID) { + return result; + } + + ScrollableLayerGuid rootContent = sampler->GetGuid(*zoomedMetrics); + if (rootContent.mScrollId != targetId || + rootContent.mLayersId != currentLayersId) { + return result; + } + + ParentLayerPoint translation = + sampler + ->GetCurrentAsyncTransform( + *zoomedMetrics, {AsyncTransformComponent::eLayout}) + .mTranslation; + + if (apz::IsStuckAtBottom(translation.y, + aLayer->GetStickyScrollRangeInner(), + aLayer->GetStickyScrollRangeOuter())) { + result |= SideBits::eBottom; + } + if (apz::IsStuckAtTop(translation.y, + aLayer->GetStickyScrollRangeInner(), + aLayer->GetStickyScrollRangeOuter())) { + result |= SideBits::eTop; + } + return result; + }; + + // Layers fixed to the RCD-RSF no longer need + // AdjustFixedOrStickyLayer() to scroll them by the eVisual transform, + // as that's now applied to the async zoom container itself. However, + // we still need to adjust them by the fixed layer margins to + // account for dynamic toolbar transitions. This is also handled by + // AdjustFixedOrStickyLayer(), so we now call it with empty transforms + // to get it to perform just the fixed margins adjustment. + SideBits stuckSides = SidesStuckToZoomContainer(layer); + if (zoomedMetrics && ((layer->GetIsFixedPosition() && + !layer->GetParent()->GetIsFixedPosition() && + IsFixedToZoomContainer(layer)) || + stuckSides != SideBits::eNone)) { + LayerToParentLayerMatrix4x4 emptyTransform; + ScreenMargin marginsForFixedLayer = GetFixedLayerMargins(); + AdjustFixedOrStickyLayer(zoomContainer, layer, stuckSides, + sampler->GetGuid(*zoomedMetrics).mScrollId, + emptyTransform, emptyTransform, + marginsForFixedLayer, clipPartsCache, + sampler->GetGeckoFixedLayerMargins()); + } + } + + bool clipChanged = (hasAsyncTransform || clipDeferredFromChildren || + layer->GetScrolledClipRect()); + if (clipChanged) { + // Intersect the two clip parts and apply them to the layer. + // During ApplyAsyncContentTransformTree on an ancestor layer, + // AlignFixedAndStickyLayers may overwrite this with a new clip it + // computes from the clip parts, but if that doesn't happen, this + // is the layer's final clip rect. + layer->AsHostLayer()->SetShadowClipRect(clipParts.Intersect()); + } + + if (hasAsyncTransform) { + // Apply the APZ transform on top of GetLocalTransform() here (rather + // than GetTransform()) in case the OMTA code in SampleAnimations + // already set a shadow transform; in that case we want to apply ours + // on top of that one rather than clobber it. + SetShadowTransform(layer, + layer->GetLocalTransformTyped() * + AdjustForClip(combinedAsyncTransform, layer)); + + // Do the same for the layer's own mask layer, if it has one. + if (Layer* maskLayer = layer->GetMaskLayer()) { + SetShadowTransform(maskLayer, maskLayer->GetLocalTransformTyped() * + combinedAsyncTransform); + } + + appliedTransform = true; + } + + ExpandRootClipRect(layer, fixedLayerMargins); + + if (layer->GetScrollbarData().mScrollbarLayerType == + layers::ScrollbarLayerType::Thumb) { + ApplyAsyncTransformToScrollbar(layer); + } + + if (layer->AsRefLayer()) { + MOZ_ASSERT(layersIds.size() > 1); + layersIds.pop(); + } + }); + + return appliedTransform; +} + +#if defined(MOZ_WIDGET_ANDROID) +bool AsyncCompositionManager::GeckoViewMetricsHaveUpdated( + const GeckoViewMetrics& aMetrics) { + return RoundedToInt(mLastMetrics.mVisualScrollOffset) != + RoundedToInt(aMetrics.mVisualScrollOffset) || + mLastMetrics.mZoom != aMetrics.mZoom; +} +#endif + +static bool LayerIsScrollbarTarget(const LayerMetricsWrapper& aTarget, + Layer* aScrollbar) { + if (!aTarget.GetApzc()) { + return false; + } + const FrameMetrics& metrics = aTarget.Metrics(); + MOZ_ASSERT(metrics.IsScrollable()); + if (metrics.GetScrollId() != aScrollbar->GetScrollbarData().mTargetViewId) { + return false; + } + return !metrics.IsScrollInfoLayer(); +} + +static void ApplyAsyncTransformToScrollbarForContent( + const RefPtr<APZSampler>& aSampler, Layer* aScrollbar, + const LayerMetricsWrapper& aContent, bool aScrollbarIsDescendant) { + AsyncTransformComponentMatrix clipTransform; + + MOZ_ASSERT(aSampler); + LayerToParentLayerMatrix4x4 transform = + aSampler->ComputeTransformForScrollThumb( + aScrollbar->GetLocalTransformTyped(), aContent, + aScrollbar->GetScrollbarData(), aScrollbarIsDescendant, + &clipTransform); + + if (aScrollbarIsDescendant) { + // We also need to make a corresponding change on the clip rect of all the + // layers on the ancestor chain from the scrollbar layer up to but not + // including the layer with the async transform. Otherwise the scrollbar + // shifts but gets clipped and so appears to flicker. + for (Layer* ancestor = aScrollbar; ancestor != aContent.GetLayer(); + ancestor = ancestor->GetParent()) { + TransformClipRect(ancestor, clipTransform); + } + } + + SetShadowTransform(aScrollbar, transform); +} + +static LayerMetricsWrapper FindScrolledLayerForScrollbar(Layer* aScrollbar, + bool* aOutIsAncestor) { + // First check if the scrolled layer is an ancestor of the scrollbar layer. + LayerMetricsWrapper root(aScrollbar->Manager()->GetRoot()); + LayerMetricsWrapper prevAncestor(aScrollbar); + LayerMetricsWrapper scrolledLayer; + + for (LayerMetricsWrapper ancestor(aScrollbar); ancestor; + ancestor = ancestor.GetParent()) { + // Don't walk into remote layer trees; the scrollbar will always be in + // the same layer space. + if (ancestor.AsRefLayer()) { + root = prevAncestor; + break; + } + prevAncestor = ancestor; + + if (LayerIsScrollbarTarget(ancestor, aScrollbar)) { + *aOutIsAncestor = true; + return ancestor; + } + } + + // Search the entire layer space of the scrollbar. + ForEachNode<ForwardIterator>(root, [&root, &scrolledLayer, &aScrollbar]( + LayerMetricsWrapper aLayerMetrics) { + // Do not recurse into RefLayers, since our initial aSubtreeRoot is the + // root (or RefLayer root) of a single layer space to search. + if (root != aLayerMetrics && aLayerMetrics.AsRefLayer()) { + return TraversalFlag::Skip; + } + if (LayerIsScrollbarTarget(aLayerMetrics, aScrollbar)) { + scrolledLayer = aLayerMetrics; + return TraversalFlag::Abort; + } + return TraversalFlag::Continue; + }); + return scrolledLayer; +} + +void AsyncCompositionManager::ApplyAsyncTransformToScrollbar(Layer* aLayer) { + // If this layer corresponds to a scrollbar, then there should be a layer that + // is a previous sibling or a parent that has a matching ViewID on its + // FrameMetrics. That is the content that this scrollbar is for. We pick up + // the transient async transform from that layer and use it to update the + // scrollbar position. Note that it is possible that the content layer is no + // longer there; in this case we don't need to do anything because there can't + // be an async transform on the content. + bool isAncestor = false; + const LayerMetricsWrapper& scrollTarget = + FindScrolledLayerForScrollbar(aLayer, &isAncestor); + if (scrollTarget) { + ApplyAsyncTransformToScrollbarForContent(mCompositorBridge->GetAPZSampler(), + aLayer, scrollTarget, isAncestor); + } +} + +void AsyncCompositionManager::GetFrameUniformity( + FrameUniformityData* aOutData) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + mLayerTransformRecorder.EndTest(aOutData); +} + +bool AsyncCompositionManager::TransformShadowTree( + const SampleTime& aCurrentFrame, TimeDuration aVsyncRate, + CompositorBridgeParentBase::TransformsToSkip aSkip) { + AUTO_PROFILER_LABEL("AsyncCompositionManager::TransformShadowTree", GRAPHICS); + + Layer* root = mLayerManager->GetRoot(); + if (!root) { + return false; + } + + // First, compute and set the shadow transforms from OMT animations. + // NB: we must sample animations *before* sampling pan/zoom + // transforms. + bool wantNextFrame = SampleAnimations(root, aCurrentFrame.Time()); + + // Advance animations to the next expected vsync timestamp, if we can + // get it. + SampleTime nextFrame = aCurrentFrame; + + MOZ_ASSERT(aVsyncRate != TimeDuration::Forever()); + if (aVsyncRate != TimeDuration::Forever()) { + nextFrame = nextFrame + aVsyncRate; + } + + // Reset the previous time stamp if we don't already have any running + // animations to avoid using the time which is far behind for newly + // started animations. + mPreviousFrameTimeStamp = wantNextFrame ? aCurrentFrame.Time() : TimeStamp(); + + if (!(aSkip & CompositorBridgeParentBase::TransformsToSkip::APZ)) { + bool apzAnimating = false; + if (RefPtr<APZSampler> apz = mCompositorBridge->GetAPZSampler()) { + apzAnimating = apz->AdvanceAnimations(nextFrame); + } + wantNextFrame |= apzAnimating; + + // Apply an async content transform to any layer that has + // an async pan zoom controller. + bool foundRoot = false; + if (ApplyAsyncContentTransformToTree(root, &foundRoot)) { +#if defined(MOZ_WIDGET_ANDROID) + MOZ_ASSERT(foundRoot); + if (foundRoot && GetFixedLayerMargins() != ScreenMargin()) { + MoveScrollbarForLayerMargin(root, mRootScrollableId, + GetFixedLayerMargins()); + } +#endif + } + } + + HostLayer* rootComposite = root->AsHostLayer(); + + gfx::Matrix4x4 trans = rootComposite->GetShadowBaseTransform(); + trans *= gfx::Matrix4x4::From2D(mWorldTransform); + rootComposite->SetShadowBaseTransform(trans); + + if (StaticPrefs::gfx_vsync_collect_scroll_transforms()) { + RecordShadowTransforms(root); + } + + return wantNextFrame; +} + +void AsyncCompositionManager::SetFixedLayerMargins(ScreenIntCoord aTop, + ScreenIntCoord aBottom) { + mFixedLayerMargins.top = aTop; + mFixedLayerMargins.bottom = aBottom; +} +ScreenMargin AsyncCompositionManager::GetFixedLayerMargins() const { + ScreenMargin result = mFixedLayerMargins; + if (StaticPrefs::apz_fixed_margin_override_enabled()) { + result.top = StaticPrefs::apz_fixed_margin_override_top(); + result.bottom = StaticPrefs::apz_fixed_margin_override_bottom(); + } + return result; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/AsyncCompositionManager.h b/gfx/layers/composite/AsyncCompositionManager.h new file mode 100644 index 0000000000..474f653f99 --- /dev/null +++ b/gfx/layers/composite/AsyncCompositionManager.h @@ -0,0 +1,273 @@ +/* -*- 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/. */ + +#ifndef GFX_ASYNCCOMPOSITIONMANAGER_H +#define GFX_ASYNCCOMPOSITIONMANAGER_H + +#include "Units.h" // for ScreenPoint, etc +#include "FrameMetrics.h" // for FrameMetrics +#include "mozilla/layers/APZUtils.h" // for GeckoViewMetrics +#include "mozilla/layers/CompositorBridgeParent.h" // for TransformsToSkip +#include "mozilla/layers/LayerManagerComposite.h" // for LayerManagerComposite +#include "mozilla/Attributes.h" // for final, etc +#include "mozilla/RefPtr.h" // for RefCounted +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/HalScreenConfiguration.h" // For ScreenOrientation +#include "mozilla/layers/FrameUniformityData.h" // For FrameUniformityData +#include "mozilla/layers/LayersMessages.h" // for TargetConfig +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsISupportsImpl.h" // for LayerManager::AddRef, etc + +namespace mozilla { +namespace layers { + +class Layer; +class LayerManagerComposite; +class AutoResolveRefLayers; +class CompositorBridgeParent; +class SampleTime; + +/** + * Manage async composition effects. This class is only used with OMTC and only + * lives on the compositor thread. It is a layer on top of the layer manager + * (LayerManagerComposite) which deals with elements of composition which are + * usually dealt with by dom or layout when main thread rendering, but which can + * short circuit that stuff to directly affect layers as they are composited, + * for example, off-main thread animation, async video, async pan/zoom. + */ +class AsyncCompositionManager final { + friend class AutoResolveRefLayers; + ~AsyncCompositionManager(); + + public: + NS_INLINE_DECL_REFCOUNTING(AsyncCompositionManager) + + AsyncCompositionManager(CompositorBridgeParent* aParent, + HostLayerManager* aManager); + + /** + * This forces the is-first-paint flag to true. This is intended to + * be called by the widget code when it loses its viewport information + * (or for whatever reason wants to refresh the viewport information). + * The information refresh happens because the compositor will call + * AndroidDynamicToolbarAnimator::FirstPaint() on the next frame of + * composition. + */ + void ForceIsFirstPaint() { mIsFirstPaint = true; } + + // Sample transforms for layer trees. Return true to request + // another animation frame. + bool TransformShadowTree( + const SampleTime& aCurrentFrame, TimeDuration aVsyncRate, + CompositorBridgeParentBase::TransformsToSkip aSkip = + CompositorBridgeParentBase::TransformsToSkip::NoneOfThem); + + // Calculates the correct rotation and applies the transform to + // our layer manager + void ComputeRotation(); + + // Call after updating our layer tree. + void Updated(bool isFirstPaint, const TargetConfig& aTargetConfig) { + mIsFirstPaint |= isFirstPaint; + mLayersUpdated = true; + mTargetConfig = aTargetConfig; + } + + bool RequiresReorientation(hal::ScreenOrientation aOrientation) const { + return mTargetConfig.orientation() != aOrientation; + } + + // True if the underlying layer tree is ready to be composited. + bool ReadyForCompose() { return mReadyForCompose; } + + // Returns true if the next composition will be the first for a + // particular document. + bool IsFirstPaint() { return mIsFirstPaint; } + + // GetFrameUniformity will return the frame uniformity for each layer attached + // to an APZ from the recorded data in RecordShadowTransform + void GetFrameUniformity(FrameUniformityData* aFrameUniformityData); + + // Stores the clip rect of a layer in two parts: a fixed part and a scrolled + // part. When a layer is fixed, the clip needs to be adjusted to account for + // async transforms. Only the fixed part needs to be adjusted, so we need + // to store the two parts separately. + struct ClipParts { + Maybe<ParentLayerIntRect> mFixedClip; + Maybe<ParentLayerIntRect> mScrolledClip; + + Maybe<ParentLayerIntRect> Intersect() const { + return IntersectMaybeRects(mFixedClip, mScrolledClip); + } + }; + + typedef std::map<Layer*, ClipParts> ClipPartsCache; + + private: + // Return true if an AsyncPanZoomController content transform was + // applied for |aLayer|. |*aOutFoundRoot| is set to true on Android only, if + // one of the metrics on one of the layers was determined to be the "root" + // and its state was synced to the Java front-end. |aOutFoundRoot| must be + // non-null. + bool ApplyAsyncContentTransformToTree(Layer* aLayer, bool* aOutFoundRoot); + /** + * Update the shadow transform for aLayer assuming that is a scrollbar, + * so that it stays in sync with the content that is being scrolled by APZ. + */ + void ApplyAsyncTransformToScrollbar(Layer* aLayer); + + /** + * Adds a translation to the transform of any fixed position (whose parent + * layer is not fixed) or sticky position layer descendant of + * |aTransformedSubtreeRoot|. The translation is chosen so that the layer's + * anchor point relative to |aTransformedSubtreeRoot|'s parent layer is the + * same as it was when |aTransformedSubtreeRoot|'s GetLocalTransform() was + * |aPreviousTransformForRoot|. |aCurrentTransformForRoot| is + * |aTransformedSubtreeRoot|'s current GetLocalTransform() modulo any + * overscroll-related transform, which we don't want to adjust for. + * For sticky position layers, the translation is further intersected with + * the layer's sticky scroll ranges. + * This function will also adjust layers so that the given content document + * fixed position margins will be respected during asynchronous panning and + * zooming. + * |aTransformScrollId| is the scroll id of the scroll frame that scrolls + * |aTransformedSubtreeRoot|. + * |aClipPartsCache| maps layers to separate fixed and scrolled + * clips, so we can only adjust the fixed portion. + * This function has a recursive implementation; aStartTraversalAt specifies + * where to start the current recursion of the traversal. For the initial + * call, it should be the same as aTrasnformedSubtreeRoot. + */ + void AlignFixedAndStickyLayers( + Layer* aTransformedSubtreeRoot, Layer* aStartTraversalAt, + SideBits aStuckSides, ScrollableLayerGuid::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aCompositorFixedLayerMargins, + ClipPartsCache& aClipPartsCache, + const ScreenMargin& aGeckoFixedLayerMargins); + + /** + * Helper function for AlignFixedAndStickyLayers() to perform a transform + * adjustment for a single fixed or sticky layer, rather than all such + * layers rooted at a subtree. May also be called directly. + */ + void AdjustFixedOrStickyLayer( + Layer* aTransformedSubtreeRoot, Layer* aFixedOrSticky, + SideBits aStuckSides, ScrollableLayerGuid::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aCompositorFixedLayerMargins, + ClipPartsCache& aClipPartsCache, + const ScreenMargin& aGeckoFixedLayerMargins); + + /** + * DRAWING PHASE ONLY + * + * For reach RefLayer in our layer tree, look up its referent and connect it + * to the layer tree, if found. + * aHasRemoteContent - indicates if the layer tree contains a remote reflayer. + * May be null. + * aResolvePlugins - incoming value indicates if plugin windows should be + * updated through a call on aCompositor's UpdatePluginWindowState. Applies + * to linux and windows only, may be null. On return value indicates + * if any updates occured. + */ + void ResolveRefLayers(CompositorBridgeParent* aCompositor, + bool* aHasRemoteContent, bool* aResolvePlugins); + + /** + * Detaches all referents resolved by ResolveRefLayers. + * Assumes that mLayerManager->GetRoot() and mTargetConfig have not changed + * since ResolveRefLayers was called. + */ + void DetachRefLayers(); + + // Records the shadow transforms for the tree of layers rooted at the given + // layer + void RecordShadowTransforms(Layer* aLayer); + + bool SampleAnimations(Layer* aLayer, TimeStamp aCurrentFrameTime); + + TargetConfig mTargetConfig; + CSSRect mContentRect; + + RefPtr<HostLayerManager> mLayerManager; + // When this flag is set, the next composition will be the first for a + // particular document (i.e. the document displayed on the screen will + // change). This happens when loading a new page or switching tabs. We notify + // the front-end (e.g. Java on Android) about this so that it take the new + // page size and zoom into account when providing us with the next view + // transform. + bool mIsFirstPaint; + + // This flag is set during a layers update, so that the first composition + // after a layers update has it set. It is cleared after that first + // composition. + bool mLayersUpdated; + + bool mReadyForCompose; + + gfx::Matrix mWorldTransform; + LayerTransformRecorder mLayerTransformRecorder; + + TimeStamp mPreviousFrameTimeStamp; + + MOZ_NON_OWNING_REF CompositorBridgeParent* mCompositorBridge; + + public: + void SetFixedLayerMargins(ScreenIntCoord aTop, ScreenIntCoord aBottom); + ScreenMargin GetFixedLayerMargins() const; + + private: + ScreenMargin mFixedLayerMargins; + +#ifdef MOZ_WIDGET_ANDROID + private: + // This calculates whether GeckoView metrics should be sent to Java. + bool GeckoViewMetricsHaveUpdated(const GeckoViewMetrics& aMetrics); + // This holds the most recent GeckoView metrics sent to Java, and is used + // to send new updates when it changes. + GeckoViewMetrics mLastMetrics; + // The following two fields are only needed on Fennec with C++ APZ, because + // then we need to reposition the gecko scrollbar to deal with the + // dynamic toolbar shifting content around. + ScrollableLayerGuid::ViewID mRootScrollableId; +#endif +}; + +class MOZ_STACK_CLASS AutoResolveRefLayers { + public: + explicit AutoResolveRefLayers(AsyncCompositionManager* aManager, + CompositorBridgeParent* aCompositor = nullptr, + bool* aHasRemoteContent = nullptr, + bool* aResolvePlugins = nullptr) + : mManager(aManager) { + if (mManager) { + mManager->ResolveRefLayers(aCompositor, aHasRemoteContent, + aResolvePlugins); + } + } + + ~AutoResolveRefLayers() { + if (mManager) { + mManager->DetachRefLayers(); + } + } + + private: + AsyncCompositionManager* mManager; + + AutoResolveRefLayers(const AutoResolveRefLayers&) = delete; + AutoResolveRefLayers& operator=(const AutoResolveRefLayers&) = delete; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/CanvasLayerComposite.cpp b/gfx/layers/composite/CanvasLayerComposite.cpp new file mode 100644 index 0000000000..9b1d4aa78b --- /dev/null +++ b/gfx/layers/composite/CanvasLayerComposite.cpp @@ -0,0 +1,141 @@ +/* -*- 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 "CanvasLayerComposite.h" +#include "composite/CompositableHost.h" // for CompositableHost +#include "gfx2DGlue.h" // for ToFilter +#include "gfxEnv.h" // for gfxEnv, etc +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/Effects.h" // for EffectChain +#include "mozilla/layers/LayerManagerCompositeUtils.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "nsAString.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsString.h" // for nsAutoCString + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +CanvasLayerComposite::CanvasLayerComposite(LayerManagerComposite* aManager) + : CanvasLayer(aManager, nullptr), + LayerComposite(aManager), + mCompositableHost(nullptr) { + MOZ_COUNT_CTOR(CanvasLayerComposite); + mImplData = static_cast<LayerComposite*>(this); +} + +CanvasLayerComposite::~CanvasLayerComposite() { + MOZ_COUNT_DTOR(CanvasLayerComposite); + + CleanupResources(); +} + +bool CanvasLayerComposite::SetCompositableHost(CompositableHost* aHost) { + switch (aHost->GetType()) { + case CompositableType::IMAGE: { + if (mCompositableHost && aHost != mCompositableHost) { + mCompositableHost->Detach(this); + } + mCompositableHost = aHost; + return true; + } + default: + return false; + } +} + +Layer* CanvasLayerComposite::GetLayer() { return this; } + +void CanvasLayerComposite::SetLayerManager(HostLayerManager* aManager) { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + if (mCompositableHost && mCompositor) { + mCompositableHost->SetTextureSourceProvider(mCompositor); + } +} + +void CanvasLayerComposite::RenderLayer(const IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) { + if (!mCompositableHost || !mCompositableHost->IsAttached()) { + return; + } + + mCompositor->MakeCurrent(); + +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpCompositorTextures()) { + RefPtr<gfx::DataSourceSurface> surf = mCompositableHost->GetAsSurface(); + if (surf) { + WriteSnapshotToDumpFile(this, surf); + } + } +#endif + + RenderWithAllMasks(this, mCompositor, aClipRect, + [&](EffectChain& effectChain, const IntRect& clipRect) { + mCompositableHost->Composite( + mCompositor, this, effectChain, + GetEffectiveOpacity(), GetEffectiveTransform(), + GetSamplingFilter(), clipRect); + }); + + mCompositableHost->BumpFlashCounter(); +} + +CompositableHost* CanvasLayerComposite::GetCompositableHost() { + if (mCompositableHost && mCompositableHost->IsAttached()) { + return mCompositableHost.get(); + } + + return nullptr; +} + +void CanvasLayerComposite::CleanupResources() { + if (mCompositableHost) { + mCompositableHost->Detach(this); + } + mCompositableHost = nullptr; +} + +gfx::SamplingFilter CanvasLayerComposite::GetSamplingFilter() { + gfx::SamplingFilter filter = mSamplingFilter; +#ifdef ANDROID + // Bug 691354 + // Using the LINEAR filter we get unexplained artifacts. + // Use NEAREST when no scaling is required. + Matrix matrix; + bool is2D = GetEffectiveTransform().Is2D(&matrix); + if (is2D && !ThebesMatrix(matrix).HasNonTranslationOrFlip()) { + filter = SamplingFilter::POINT; + } +#endif + return filter; +} + +void CanvasLayerComposite::GenEffectChain(EffectChain& aEffect) { + aEffect.mLayerRef = this; + aEffect.mPrimaryEffect = mCompositableHost->GenEffect(GetSamplingFilter()); +} + +void CanvasLayerComposite::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + CanvasLayer::PrintInfo(aStream, aPrefix); + aStream << "\n"; + if (mCompositableHost && mCompositableHost->IsAttached()) { + nsAutoCString pfx(aPrefix); + pfx += " "; + mCompositableHost->PrintInfo(aStream, pfx.get()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/CanvasLayerComposite.h b/gfx/layers/composite/CanvasLayerComposite.h new file mode 100644 index 0000000000..dbe29fbb6d --- /dev/null +++ b/gfx/layers/composite/CanvasLayerComposite.h @@ -0,0 +1,72 @@ +/* -*- 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/. */ + +#ifndef GFX_CanvasLayerComposite_H +#define GFX_CanvasLayerComposite_H + +#include "Layers.h" // for CanvasLayer, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/LayerManagerComposite.h" // for LayerComposite, etc +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nscore.h" // for nsACString + +namespace mozilla { +namespace layers { + +class CompositableHost; +// Canvas layers use ImageHosts (but CanvasClients) because compositing a +// canvas is identical to compositing an image. +class ImageHost; + +class CanvasLayerComposite : public CanvasLayer, public LayerComposite { + public: + explicit CanvasLayerComposite(LayerManagerComposite* aManager); + + protected: + virtual ~CanvasLayerComposite(); + + public: + bool SetCompositableHost(CompositableHost* aHost) override; + + void Disconnect() override { Destroy(); } + + void SetLayerManager(HostLayerManager* aManager) override; + + Layer* GetLayer() override; + void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) override; + + void CleanupResources() override; + + void GenEffectChain(EffectChain& aEffect) override; + + CompositableHost* GetCompositableHost() override; + + HostLayer* AsHostLayer() override { return this; } + + const char* Name() const override { return "CanvasLayerComposite"; } + + protected: + RefPtr<CanvasRenderer> CreateCanvasRendererInternal() override { + MOZ_CRASH("Incompatible surface type"); + return nullptr; + } + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + private: + gfx::SamplingFilter GetSamplingFilter(); + + private: + RefPtr<CompositableHost> mCompositableHost; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_CanvasLayerComposite_H */ diff --git a/gfx/layers/composite/ColorLayerComposite.cpp b/gfx/layers/composite/ColorLayerComposite.cpp new file mode 100644 index 0000000000..4b17a216e8 --- /dev/null +++ b/gfx/layers/composite/ColorLayerComposite.cpp @@ -0,0 +1,63 @@ +/* -*- 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 "ColorLayerComposite.h" +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for Color +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorTypes.h" // for DiagnosticFlags::COLOR +#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc +#include "mozilla/layers/LayerManagerCompositeUtils.h" +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "UnitTransforms.h" // for ViewAs + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +void ColorLayerComposite::RenderLayer(const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) { + const Matrix4x4& transform = GetEffectiveTransform(); + +#ifdef MOZ_GFX_OPTIMIZE_MOBILE + // On desktop we want to draw a single rectangle to avoid possible + // seams if we're resampling. On mobile we'd prefer to use the accurate + // region for better performance. + LayerIntRegion drawRegion = GetLocalVisibleRegion(); +#else + LayerIntRegion drawRegion = ViewAs<LayerPixel>(GetBounds()); +#endif + + for (auto iter = drawRegion.RectIter(); !iter.Done(); iter.Next()) { + const LayerIntRect& rect = iter.Get(); + Rect graphicsRect(rect.X(), rect.Y(), rect.Width(), rect.Height()); + + RenderWithAllMasks(this, mCompositor, aClipRect, + [&](EffectChain& effectChain, const IntRect& clipRect) { + GenEffectChain(effectChain); + + mCompositor->DrawGeometry( + graphicsRect, clipRect, effectChain, + GetEffectiveOpacity(), transform, aGeometry); + }); + } + + Rect rect(GetBounds()); + mCompositor->DrawDiagnostics(DiagnosticFlags::COLOR, rect, aClipRect, + transform); +} + +void ColorLayerComposite::GenEffectChain(EffectChain& aEffect) { + aEffect.mLayerRef = this; + aEffect.mPrimaryEffect = new EffectSolidColor(GetColor()); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ColorLayerComposite.h b/gfx/layers/composite/ColorLayerComposite.h new file mode 100644 index 0000000000..b499112486 --- /dev/null +++ b/gfx/layers/composite/ColorLayerComposite.h @@ -0,0 +1,62 @@ +/* -*- 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/. */ + +#ifndef GFX_ColorLayerComposite_H +#define GFX_ColorLayerComposite_H + +#include "Layers.h" // for ColorLayer, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/layers/LayerManagerComposite.h" // for LayerComposite, etc +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc + +namespace mozilla { +namespace layers { + +class CompositableHost; + +class ColorLayerComposite : public ColorLayer, public LayerComposite { + public: + explicit ColorLayerComposite(LayerManagerComposite* aManager) + : ColorLayer(aManager, nullptr), LayerComposite(aManager) { + MOZ_COUNT_CTOR(ColorLayerComposite); + mImplData = static_cast<LayerComposite*>(this); + } + + protected: + virtual ~ColorLayerComposite() { + MOZ_COUNT_DTOR(ColorLayerComposite); + Destroy(); + } + + public: + // LayerComposite Implementation + Layer* GetLayer() override { return this; } + + void SetLayerManager(HostLayerManager* aManager) override { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + } + + void Destroy() override { mDestroyed = true; } + + void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) override; + + void CleanupResources() override{}; + + void GenEffectChain(EffectChain& aEffect) override; + + CompositableHost* GetCompositableHost() override { return nullptr; } + + HostLayer* AsHostLayer() override { return this; } + + const char* Name() const override { return "ColorLayerComposite"; } +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_ColorLayerComposite_H */ diff --git a/gfx/layers/composite/CompositableHost.cpp b/gfx/layers/composite/CompositableHost.cpp new file mode 100644 index 0000000000..056f26de04 --- /dev/null +++ b/gfx/layers/composite/CompositableHost.cpp @@ -0,0 +1,167 @@ +/* -*- 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 "CompositableHost.h" +#include <map> // for _Rb_tree_iterator, map, etc +#include <utility> // for pair +#include "ContentHost.h" // for ContentHostDoubleBuffered, etc +#include "Effects.h" // for EffectMask, Effect, etc +#include "gfxUtils.h" +#include "ImageHost.h" // for ImageHostBuffered, etc +#include "Layers.h" +#include "TiledContentHost.h" // for TiledContentHost +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/TextureHost.h" // for TextureHost, etc +#include "mozilla/layers/WebRenderImageHost.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsDebug.h" // for NS_WARNING +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "gfxPlatform.h" // for gfxPlatform +#include "IPDLActor.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +class Compositor; + +CompositableHost::CompositableHost(const TextureInfo& aTextureInfo) + : mTextureInfo(aTextureInfo), + mCompositorBridgeID(0), + mLayer(nullptr), + mFlashCounter(0), + mAttached(false), + mKeepAttached(false) { + MOZ_COUNT_CTOR(CompositableHost); +} + +CompositableHost::~CompositableHost() { MOZ_COUNT_DTOR(CompositableHost); } + +void CompositableHost::UseTextureHost(const nsTArray<TimedTexture>& aTextures) { + if (mTextureSourceProvider) { + for (auto& texture : aTextures) { + texture.mTexture->SetTextureSourceProvider(mTextureSourceProvider); + } + } +} + +void CompositableHost::UseComponentAlphaTextures(TextureHost* aTextureOnBlack, + TextureHost* aTextureOnWhite) { + MOZ_ASSERT(aTextureOnBlack && aTextureOnWhite); + if (mTextureSourceProvider) { + aTextureOnBlack->SetTextureSourceProvider(mTextureSourceProvider); + aTextureOnWhite->SetTextureSourceProvider(mTextureSourceProvider); + } +} + +void CompositableHost::RemoveTextureHost(TextureHost* aTexture) {} + +void CompositableHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + MOZ_ASSERT(aProvider); + mTextureSourceProvider = aProvider; +} + +bool CompositableHost::AddMaskEffect(EffectChain& aEffects, + const gfx::Matrix4x4& aTransform) { + CompositableTextureSourceRef source; + RefPtr<TextureHost> host = GetAsTextureHost(); + + if (!host) { + NS_WARNING("Using compositable with no valid TextureHost as mask"); + return false; + } + + if (!host->Lock()) { + NS_WARNING("Failed to lock the mask texture"); + return false; + } + + if (!host->BindTextureSource(source)) { + NS_WARNING( + "The TextureHost was successfully locked but can't provide a " + "TextureSource"); + host->Unlock(); + return false; + } + MOZ_ASSERT(source); + + RefPtr<EffectMask> effect = + new EffectMask(source, source->GetSize(), aTransform); + aEffects.mSecondaryEffects[EffectTypes::MASK] = effect; + return true; +} + +void CompositableHost::RemoveMaskEffect() { + RefPtr<TextureHost> host = GetAsTextureHost(); + if (host) { + host->Unlock(); + } +} + +/* static */ +already_AddRefed<CompositableHost> CompositableHost::Create( + const TextureInfo& aTextureInfo, bool aUseWebRender) { + RefPtr<CompositableHost> result; + switch (aTextureInfo.mCompositableType) { + case CompositableType::IMAGE_BRIDGE: + NS_ERROR("Cannot create an image bridge compositable this way"); + break; + case CompositableType::CONTENT_TILED: + result = new TiledContentHost(aTextureInfo); + break; + case CompositableType::IMAGE: + if (aUseWebRender) { + result = new WebRenderImageHost(aTextureInfo); + } else { + result = new ImageHost(aTextureInfo); + } + break; + case CompositableType::CONTENT_SINGLE: + if (aUseWebRender) { + result = new WebRenderImageHost(aTextureInfo); + } else { + result = new ContentHostSingleBuffered(aTextureInfo); + } + break; + case CompositableType::CONTENT_DOUBLE: + MOZ_ASSERT(!aUseWebRender); + result = new ContentHostDoubleBuffered(aTextureInfo); + break; + default: + NS_ERROR("Unknown CompositableType"); + } + return result.forget(); +} + +void CompositableHost::DumpTextureHost(std::stringstream& aStream, + TextureHost* aTexture) { + if (!aTexture) { + return; + } + RefPtr<gfx::DataSourceSurface> dSurf = aTexture->GetAsSurface(); + if (!dSurf) { + return; + } + aStream << gfxUtils::GetAsDataURI(dSurf).get(); +} + +HostLayerManager* CompositableHost::GetLayerManager() const { + if (!mLayer || !mLayer->Manager()) { + return nullptr; + } + return mLayer->Manager()->AsHostLayerManager(); +} + +TextureSourceProvider* CompositableHost::GetTextureSourceProvider() const { + return mTextureSourceProvider; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/CompositableHost.h b/gfx/layers/composite/CompositableHost.h new file mode 100644 index 0000000000..6f749d76b8 --- /dev/null +++ b/gfx/layers/composite/CompositableHost.h @@ -0,0 +1,278 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_BUFFERHOST_H +#define MOZILLA_GFX_BUFFERHOST_H + +#include <stdint.h> // for uint64_t +#include <stdio.h> // for FILE +#include "gfxRect.h" // for gfxRect +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, RefCounted, etc +#include "mozilla/gfx/MatrixFwd.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Polygon.h" // for Polygon +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for SamplingFilter +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/Effects.h" // for Texture Effect +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "mozilla/layers/LayersMessages.h" +#include "mozilla/layers/TextureHost.h" // for TextureHost +#include "mozilla/mozalloc.h" // for operator delete +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsRegion.h" // for nsIntRegion +#include "nscore.h" // for nsACString +#include "Units.h" // for CSSToScreenScale + +namespace mozilla { +namespace gfx { +class DataSourceSurface; +} // namespace gfx + +namespace layers { + +class Layer; +class LayerComposite; +class ImageHost; +class Compositor; +class ThebesBufferData; +class TiledContentHost; +class CompositableParentManager; +class WebRenderImageHost; +class ContentHost; +class ContentHostTexture; +class HostLayerManager; +struct EffectChain; + +struct ImageCompositeNotificationInfo { + base::ProcessId mImageBridgeProcessId; + ImageCompositeNotification mNotification; +}; + +struct AsyncCompositableRef { + AsyncCompositableRef() : mProcessId(mozilla::ipc::kInvalidProcessId) {} + AsyncCompositableRef(base::ProcessId aProcessId, + const CompositableHandle& aHandle) + : mProcessId(aProcessId), mHandle(aHandle) {} + explicit operator bool() const { return !!mHandle; } + base::ProcessId mProcessId; + CompositableHandle mHandle; +}; + +/** + * The compositor-side counterpart to CompositableClient. Responsible for + * updating textures and data about textures from IPC and how textures are + * composited (tiling, double buffering, etc.). + * + * Update (for images/canvases) and UpdateThebes (for Thebes) are called during + * the layers transaction to update the Compositbale's textures from the + * content side. The actual update (and any syncronous upload) is done by the + * TextureHost, but it is coordinated by the CompositableHost. + * + * Composite is called by the owning layer when it is composited. + * CompositableHost will use its TextureHost(s) and call Compositor::DrawQuad to + * do the actual rendering. + */ +class CompositableHost { + protected: + virtual ~CompositableHost(); + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositableHost) + explicit CompositableHost(const TextureInfo& aTextureInfo); + + static already_AddRefed<CompositableHost> Create( + const TextureInfo& aTextureInfo, bool aUseWebRender); + + virtual CompositableType GetType() = 0; + + // If base class overrides, it should still call the parent implementation + virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider); + + // composite the contents of this buffer host to the compositor's surface + virtual void Composite(Compositor* aCompositor, LayerComposite* aLayer, + EffectChain& aEffectChain, float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr, + const Maybe<gfx::Polygon>& aGeometry = Nothing()) = 0; + + /** + * Update the content host. + * aUpdated is the region which should be updated. + */ + virtual bool UpdateThebes(const ThebesBufferData& aData, + const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) { + NS_ERROR("should be implemented or not used"); + return false; + } + + /** + * Returns the front buffer. + * *aPictureRect (if non-null, and the returned TextureHost is non-null) + * is set to the picture rect. + */ + virtual TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) { + return nullptr; + } + + virtual gfx::IntSize GetImageSize() { + MOZ_ASSERT(false, "Should have been overridden"); + return gfx::IntSize(); + } + + const TextureInfo& GetTextureInfo() const { return mTextureInfo; } + + /** + * Adds a mask effect using this texture as the mask, if possible. + * @return true if the effect was added, false otherwise. + */ + bool AddMaskEffect(EffectChain& aEffects, const gfx::Matrix4x4& aTransform); + + void RemoveMaskEffect(); + + TextureSourceProvider* GetTextureSourceProvider() const; + + Layer* GetLayer() const { return mLayer; } + void SetLayer(Layer* aLayer) { mLayer = aLayer; } + + virtual ContentHost* AsContentHost() { return nullptr; } + virtual ContentHostTexture* AsContentHostTexture() { return nullptr; } + virtual ImageHost* AsImageHost() { return nullptr; } + virtual TiledContentHost* AsTiledContentHost() { return nullptr; } + virtual WebRenderImageHost* AsWebRenderImageHost() { return nullptr; } + + typedef uint32_t AttachFlags; + static const AttachFlags NO_FLAGS = 0; + static const AttachFlags ALLOW_REATTACH = 1; + static const AttachFlags KEEP_ATTACHED = 2; + static const AttachFlags FORCE_DETACH = 2; + + virtual void Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags = NO_FLAGS) { + MOZ_ASSERT(aProvider); + NS_ASSERTION(aFlags & ALLOW_REATTACH || !mAttached, + "Re-attaching compositables must be explicitly authorised"); + SetTextureSourceProvider(aProvider); + SetLayer(aLayer); + mAttached = true; + mKeepAttached = aFlags & KEEP_ATTACHED; + } + // Detach this compositable host from its layer. + // If we are used for async video, then it is not safe to blindly detach since + // we might be re-attached to a different layer. aLayer is the layer which the + // caller expects us to be attached to, we will only detach if we are in fact + // attached to that layer. If we are part of a normal layer, then we will be + // detached in any case. if aLayer is null, then we will only detach if we are + // not async. + // Only force detach if the IPDL tree is being shutdown. + virtual void Detach(Layer* aLayer = nullptr, AttachFlags aFlags = NO_FLAGS) { + if (!mKeepAttached || aLayer == mLayer || aFlags & FORCE_DETACH) { + SetLayer(nullptr); + mAttached = false; + mKeepAttached = false; + } + } + bool IsAttached() { return mAttached; } + + virtual void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false) {} + static void DumpTextureHost(std::stringstream& aStream, + TextureHost* aTexture); + + virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() { + return nullptr; + } + + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) = 0; + + struct TimedTexture { + CompositableTextureHostRef mTexture; + TimeStamp mTimeStamp; + gfx::IntRect mPictureRect; + int32_t mFrameID; + int32_t mProducerID; + }; + virtual void UseTextureHost(const nsTArray<TimedTexture>& aTextures); + virtual void UseComponentAlphaTextures(TextureHost* aTextureOnBlack, + TextureHost* aTextureOnWhite); + virtual void RemoveTextureHost(TextureHost* aTexture); + + // Called every time this is composited + void BumpFlashCounter() { + mFlashCounter = mFlashCounter >= DIAGNOSTIC_FLASH_COUNTER_MAX + ? DIAGNOSTIC_FLASH_COUNTER_MAX + : mFlashCounter + 1; + } + + uint64_t GetCompositorBridgeID() const { return mCompositorBridgeID; } + + const AsyncCompositableRef& GetAsyncRef() const { return mAsyncRef; } + void SetAsyncRef(const AsyncCompositableRef& aRef) { mAsyncRef = aRef; } + + void SetCompositorBridgeID(uint64_t aID) { mCompositorBridgeID = aID; } + + virtual bool Lock() { return false; } + + virtual void Unlock() {} + + virtual already_AddRefed<TexturedEffect> GenEffect( + const gfx::SamplingFilter aSamplingFilter) { + return nullptr; + } + + /// Called when shutting down the layer tree. + /// This is a good place to clear all potential gpu resources before the + /// widget is is destroyed. + virtual void CleanupResources() {} + + virtual void BindTextureSource() {} + + virtual uint32_t GetDroppedFrames() { return 0; } + + protected: + HostLayerManager* GetLayerManager() const; + + protected: + TextureInfo mTextureInfo; + AsyncCompositableRef mAsyncRef; + uint64_t mCompositorBridgeID; + RefPtr<TextureSourceProvider> mTextureSourceProvider; + Layer* mLayer; + uint32_t mFlashCounter; // used when the pref "layers.flash-borders" is true. + bool mAttached; + bool mKeepAttached; +}; + +class AutoLockCompositableHost final { + public: + explicit AutoLockCompositableHost(CompositableHost* aHost) : mHost(aHost) { + mSucceeded = (mHost && mHost->Lock()); + } + + ~AutoLockCompositableHost() { + if (mSucceeded && mHost) { + mHost->Unlock(); + } + } + + bool Failed() const { return !mSucceeded; } + + private: + RefPtr<CompositableHost> mHost; + bool mSucceeded; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/ConsolasFontData.h b/gfx/layers/composite/ConsolasFontData.h new file mode 100644 index 0000000000..a01c417f14 --- /dev/null +++ b/gfx/layers/composite/ConsolasFontData.h @@ -0,0 +1,457 @@ +/* -*- 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/. */ + +// This is explicitly not guarded as we want only 1 file to include this and +// it's good if things break if someone else does. + +namespace mozilla { +namespace layers { +namespace consolas_font { +const unsigned char sFontPNG[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, + 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, + 0x8, 0x0, 0x0, 0x0, 0x0, 0x79, 0x19, 0xf7, 0xba, 0x0, 0x0, 0x0, + 0x4, 0x67, 0x41, 0x4d, 0x41, 0x0, 0x0, 0xb1, 0x8f, 0xb, 0xfc, 0x61, + 0x5, 0x0, 0x0, 0x0, 0x20, 0x63, 0x48, 0x52, 0x4d, 0x0, 0x0, 0x7a, + 0x26, 0x0, 0x0, 0x80, 0x84, 0x0, 0x0, 0xfa, 0x0, 0x0, 0x0, 0x80, + 0xe8, 0x0, 0x0, 0x75, 0x30, 0x0, 0x0, 0xea, 0x60, 0x0, 0x0, 0x3a, + 0x98, 0x0, 0x0, 0x17, 0x70, 0x9c, 0xba, 0x51, 0x3c, 0x0, 0x0, 0x0, + 0x2, 0x62, 0x4b, 0x47, 0x44, 0x0, 0xff, 0x87, 0x8f, 0xcc, 0xbf, 0x0, + 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0x19, 0xd6, 0x0, + 0x0, 0x19, 0xd6, 0x1, 0x18, 0xd1, 0xca, 0xed, 0x0, 0x0, 0x0, 0x7, + 0x74, 0x49, 0x4d, 0x45, 0x7, 0xe1, 0x4, 0x2, 0x12, 0x27, 0x25, 0xc0, + 0x26, 0x1d, 0xf0, 0x0, 0x0, 0x12, 0x53, 0x49, 0x44, 0x41, 0x54, 0x78, + 0xda, 0xed, 0x9d, 0x3d, 0x9a, 0xe4, 0xaa, 0xe, 0x86, 0xb5, 0x41, 0xf6, + 0xc1, 0x22, 0x58, 0x2, 0x2b, 0xf0, 0x6, 0x9c, 0x3b, 0x27, 0x76, 0x4a, + 0xea, 0x90, 0xd0, 0x19, 0x3b, 0xf0, 0x1, 0x5c, 0x75, 0xee, 0x3d, 0xa5, + 0x4f, 0x65, 0xe8, 0xb2, 0x87, 0x9e, 0x6e, 0xeb, 0x99, 0xe9, 0xc0, 0xe5, + 0xc2, 0xf2, 0x5b, 0xfc, 0x4a, 0x42, 0xd0, 0xf6, 0xcb, 0x85, 0x7a, 0x2b, + 0xd0, 0x5b, 0x6e, 0x0, 0x7f, 0xe2, 0x21, 0xd1, 0xc5, 0xab, 0x9f, 0x50, + 0x7d, 0xb1, 0x5, 0x80, 0xb7, 0x13, 0xba, 0x6c, 0xed, 0x36, 0x59, 0xdf, + 0xa2, 0x5e, 0xa0, 0x95, 0x5f, 0x1c, 0xd5, 0x50, 0x9e, 0xc1, 0xef, 0x9e, + 0x86, 0xb9, 0x45, 0x9f, 0x52, 0x16, 0x78, 0x80, 0x41, 0x17, 0x9b, 0x0, + 0x38, 0xb2, 0xf0, 0x2b, 0xb4, 0xd, 0x4, 0x54, 0xf4, 0x76, 0xdd, 0x6, + 0xa0, 0xa2, 0x1f, 0x34, 0x71, 0x60, 0x4e, 0x39, 0x35, 0x6e, 0x51, 0xb9, + 0xd7, 0xf, 0x16, 0x4a, 0x62, 0xd0, 0x83, 0x67, 0xac, 0x4f, 0x56, 0x14, + 0xdd, 0xef, 0x49, 0x5f, 0x6, 0xc0, 0x51, 0x40, 0xb7, 0xa7, 0xcf, 0x6, + 0x76, 0x79, 0xcc, 0x2f, 0xc4, 0x4b, 0x4a, 0xf5, 0x28, 0xff, 0xe3, 0x3a, + 0xea, 0x54, 0x5d, 0xc, 0x2a, 0x3f, 0x55, 0x24, 0xc, 0x20, 0x8, 0x6f, + 0x3a, 0x1, 0x6d, 0x9a, 0x0, 0x78, 0x1, 0x80, 0x81, 0x0, 0xc2, 0x40, + 0x21, 0xd0, 0xf8, 0xfa, 0x41, 0x24, 0x5a, 0xd3, 0x9b, 0xb2, 0xf6, 0x38, + 0xea, 0xa8, 0x87, 0x19, 0x94, 0x93, 0xf8, 0x26, 0x0, 0xa8, 0xf6, 0x6, + 0xc2, 0x4d, 0xc0, 0x42, 0x5c, 0x85, 0xa5, 0xf0, 0x41, 0x25, 0x0, 0x81, + 0xb8, 0xf7, 0x5b, 0xe0, 0xbd, 0x5a, 0xa0, 0x87, 0xf0, 0xeb, 0xab, 0x6, + 0x8a, 0xaf, 0x8a, 0xd4, 0xa2, 0xc0, 0x7, 0x3, 0x4d, 0x13, 0x7e, 0x70, + 0x74, 0xf0, 0x7d, 0x56, 0xdc, 0x60, 0xb6, 0xdc, 0x66, 0xc6, 0x2b, 0x0, + 0x48, 0x77, 0x1b, 0x13, 0x66, 0x9a, 0x99, 0x8e, 0x3a, 0x41, 0x81, 0x7a, + 0x4, 0xd4, 0x0, 0xd2, 0x6b, 0x2a, 0xaa, 0xd0, 0xfb, 0xff, 0xc4, 0x91, + 0x13, 0x3e, 0x89, 0xa4, 0x3e, 0x4, 0xd0, 0xa4, 0x48, 0xc2, 0xe5, 0x41, + 0x9d, 0x8b, 0x36, 0x11, 0x50, 0x68, 0xd4, 0x98, 0x61, 0xd, 0x4d, 0x7d, + 0x86, 0x5a, 0x57, 0xb1, 0xc3, 0xe7, 0x62, 0xe5, 0x8a, 0x4e, 0xc7, 0xa3, + 0xfc, 0x7b, 0x0, 0xee, 0xf0, 0xfb, 0xff, 0xca, 0x1a, 0x68, 0x8, 0x23, + 0xcd, 0x60, 0xf0, 0x4d, 0xdd, 0x31, 0x21, 0x32, 0x6a, 0x72, 0x4a, 0x2f, + 0xec, 0x66, 0x9b, 0x3a, 0xf5, 0x85, 0x4e, 0x1, 0x60, 0x3f, 0x3, 0xb0, + 0xb6, 0x8c, 0xf6, 0xf6, 0xd1, 0x5, 0x0, 0x66, 0x69, 0x78, 0x30, 0xe0, + 0xfa, 0xa0, 0x13, 0x61, 0xab, 0x58, 0x39, 0x4b, 0x19, 0x38, 0xea, 0x1f, + 0xfd, 0x6, 0x80, 0xfa, 0xc, 0xc0, 0xe6, 0x1b, 0x26, 0x70, 0xeb, 0x4c, + 0x53, 0x48, 0xc3, 0x17, 0xfb, 0x4a, 0xdc, 0x46, 0x93, 0x0, 0xb0, 0x89, + 0xc3, 0x42, 0x8b, 0xa3, 0x32, 0x76, 0xbe, 0xbc, 0x8e, 0xcf, 0xa3, 0xc0, + 0x71, 0xe3, 0xfd, 0x57, 0xe4, 0x3e, 0x60, 0xfb, 0xb0, 0xf, 0x98, 0xab, + 0x66, 0x12, 0x4f, 0x9, 0xe9, 0x87, 0xb0, 0x60, 0x5e, 0x47, 0x5a, 0x29, + 0x5, 0x3a, 0xea, 0x34, 0x11, 0xf4, 0xb4, 0x8c, 0xaf, 0x2a, 0xce, 0x94, + 0x5a, 0x86, 0x6e, 0xa9, 0x1, 0x8b, 0x38, 0xa, 0xf8, 0xf, 0x47, 0x81, + 0x59, 0x2c, 0x19, 0x49, 0x9e, 0x1b, 0x28, 0xe, 0x60, 0xcd, 0x6d, 0x3, + 0x8c, 0x83, 0x43, 0x7e, 0x73, 0x3, 0x5e, 0x74, 0xca, 0xf7, 0x27, 0xc, + 0x35, 0xf3, 0xd8, 0x5d, 0xf4, 0x55, 0xf3, 0x80, 0xad, 0x5e, 0x87, 0xf7, + 0xcf, 0x98, 0xc5, 0x8f, 0xe0, 0x13, 0x42, 0xe3, 0x73, 0x17, 0xa1, 0xa6, + 0xce, 0x35, 0xc3, 0xf8, 0x1f, 0x58, 0xd, 0x86, 0x8a, 0xdf, 0xe1, 0x33, + 0x19, 0xe1, 0x9c, 0x37, 0x54, 0x75, 0x24, 0x3f, 0xc3, 0x1e, 0x0, 0xd7, + 0xe, 0xf6, 0xe3, 0xd5, 0xe0, 0x5f, 0x24, 0x78, 0x8e, 0x5c, 0xf3, 0xcd, + 0x1f, 0x2, 0xe0, 0xeb, 0x72, 0x3, 0xe8, 0xad, 0x40, 0x6f, 0xb9, 0x1, + 0xf4, 0x56, 0xa0, 0xb7, 0xdc, 0x0, 0x7a, 0x2b, 0xd0, 0x5b, 0x6e, 0x0, + 0x5b, 0xe3, 0xba, 0xff, 0x5c, 0x99, 0xec, 0xd5, 0x2e, 0x93, 0x63, 0x0, + 0x31, 0x6c, 0x69, 0x41, 0xdd, 0x7, 0x41, 0x44, 0xcb, 0x98, 0x25, 0x2d, + 0x86, 0x7, 0xc4, 0xc5, 0x61, 0xfb, 0xc6, 0xa8, 0x48, 0x81, 0x65, 0xaf, + 0x4f, 0xc5, 0xa8, 0x63, 0xbb, 0x52, 0x76, 0x72, 0x4c, 0x34, 0xa9, 0x6, + 0x3, 0xc4, 0x89, 0x32, 0x81, 0x65, 0x7f, 0x5a, 0x9, 0x5b, 0xd, 0xc0, + 0xe4, 0x17, 0x42, 0x0, 0x2c, 0x69, 0x8b, 0xcc, 0xa8, 0x4e, 0x5b, 0xab, + 0x8e, 0xd, 0x2, 0xb4, 0x97, 0x3c, 0x22, 0xe2, 0x82, 0x19, 0xfa, 0x44, + 0x41, 0xdc, 0x55, 0xf6, 0x9, 0x58, 0xbe, 0x84, 0x36, 0xca, 0x41, 0x0, + 0xeb, 0x9c, 0xa1, 0xe1, 0xbe, 0x6c, 0x39, 0x5e, 0x10, 0xd2, 0x16, 0x8a, + 0xc5, 0x2, 0x35, 0x81, 0x26, 0xa3, 0xe8, 0x57, 0xc4, 0x3, 0xd3, 0xe7, + 0x52, 0x16, 0xf1, 0x86, 0x2f, 0xe5, 0x43, 0x94, 0x8d, 0xbc, 0x41, 0x4, + 0x70, 0x68, 0x11, 0xa0, 0xd4, 0xb2, 0x2, 0x79, 0x68, 0xa1, 0xbf, 0x1c, + 0x80, 0x26, 0x5e, 0xf1, 0xb2, 0x85, 0x2f, 0x28, 0x82, 0x36, 0xe, 0x11, + 0x80, 0x47, 0xb6, 0xab, 0xe0, 0x9c, 0x52, 0x87, 0x75, 0xb8, 0x14, 0x99, + 0x9e, 0xc9, 0x35, 0x9, 0x61, 0xa6, 0x31, 0x34, 0x58, 0x67, 0x5c, 0x96, + 0x96, 0x4e, 0x3d, 0x20, 0x3b, 0x46, 0x2, 0x30, 0x91, 0x50, 0xdb, 0x25, + 0x0, 0x51, 0xd1, 0x82, 0x4a, 0x22, 0x32, 0x55, 0x0, 0x60, 0x5b, 0x7f, + 0xba, 0xba, 0xea, 0x9d, 0x43, 0xe5, 0xf6, 0x96, 0x5e, 0x63, 0xc0, 0x2e, + 0xd6, 0xfc, 0xcc, 0xd0, 0x54, 0x3, 0xc, 0xec, 0xeb, 0x62, 0x8, 0xa9, + 0xb0, 0xa3, 0x5f, 0x44, 0x9e, 0x8, 0x5d, 0x5e, 0x3, 0xe0, 0x18, 0x98, + 0xfb, 0xb3, 0x59, 0x6a, 0xbc, 0x2, 0x0, 0x23, 0xf7, 0xf5, 0xf6, 0xb0, + 0x15, 0x7f, 0xc5, 0x33, 0x34, 0xb5, 0xb9, 0xcc, 0xd2, 0xfd, 0x6, 0x5f, + 0x46, 0x3d, 0xef, 0xee, 0xcf, 0xb3, 0xd0, 0x90, 0x8a, 0x1, 0x98, 0x37, + 0x5e, 0x24, 0x7d, 0x68, 0x5e, 0xff, 0xa, 0x80, 0x34, 0x6c, 0x34, 0xd9, + 0x6d, 0x85, 0x96, 0x21, 0xcc, 0x3d, 0x6, 0xd2, 0x6e, 0x0, 0xc8, 0x26, + 0x9b, 0x9e, 0x6b, 0xf9, 0xa4, 0xd5, 0x8, 0x1e, 0xa9, 0xc9, 0xb9, 0x49, + 0xd7, 0xc, 0x83, 0xb2, 0x48, 0xf3, 0x80, 0xb5, 0xad, 0xa9, 0xa7, 0x9f, + 0x1a, 0xd5, 0x75, 0x2f, 0xfd, 0x70, 0xd9, 0x31, 0x6, 0x66, 0xc8, 0x56, + 0x78, 0x51, 0xc9, 0x25, 0x57, 0xee, 0x1f, 0xf, 0x9b, 0xe4, 0x97, 0x16, + 0x43, 0x2d, 0x8e, 0xab, 0x42, 0xc, 0x5d, 0xd4, 0x72, 0xff, 0x74, 0xce, + 0xfc, 0x2b, 0x86, 0x9a, 0xe, 0xa9, 0x1d, 0x40, 0x74, 0xb6, 0xc1, 0x6f, + 0x25, 0x17, 0xd3, 0xe0, 0x0, 0xbf, 0x52, 0xda, 0x1, 0x4, 0x3d, 0x5c, + 0x3d, 0x43, 0xfe, 0xde, 0x0, 0x7e, 0x98, 0xdc, 0x0, 0x7a, 0x2b, 0xd0, + 0x5b, 0x6e, 0x0, 0xbd, 0x15, 0xe8, 0x2d, 0x37, 0x80, 0xde, 0xa, 0xf4, + 0x96, 0x1b, 0x40, 0x6f, 0x5, 0x7a, 0xcb, 0xd, 0x20, 0xff, 0x49, 0x2b, + 0x4d, 0xbe, 0x5e, 0xf1, 0x36, 0xb, 0x9a, 0xb0, 0x87, 0x11, 0x5c, 0x5f, + 0xed, 0x2e, 0x7c, 0x99, 0xe0, 0x54, 0x5a, 0xdd, 0x81, 0xf5, 0xd0, 0x84, + 0xed, 0xf9, 0x1d, 0x0, 0x4, 0xb8, 0x60, 0x77, 0x92, 0x45, 0x6c, 0x84, + 0xd7, 0x83, 0xb4, 0x2c, 0x4d, 0x8b, 0x61, 0x37, 0x82, 0xf5, 0x63, 0xb6, + 0xe7, 0xab, 0xa6, 0x70, 0xec, 0xcb, 0x0, 0x68, 0x1a, 0x20, 0x0, 0x6c, + 0xe, 0xf1, 0x6f, 0x2c, 0x8d, 0x11, 0xd8, 0xe0, 0x8a, 0x3b, 0x87, 0x87, + 0xb3, 0xae, 0x99, 0x49, 0xc4, 0xa6, 0x15, 0xc9, 0x1f, 0x21, 0xfa, 0x29, + 0xe6, 0x86, 0xa8, 0x42, 0xe, 0x20, 0xbd, 0xa9, 0x6b, 0x0, 0x60, 0x90, + 0x5, 0xf6, 0x21, 0x23, 0x30, 0x96, 0x95, 0x28, 0x59, 0xc5, 0xc0, 0xec, + 0xdb, 0x51, 0xe0, 0xca, 0x7a, 0x10, 0x4c, 0x99, 0xd2, 0xf5, 0xf4, 0xd8, + 0x63, 0xf3, 0xb7, 0xc, 0x20, 0x2a, 0xbd, 0x61, 0x0, 0xda, 0x8e, 0x40, + 0x3d, 0xa2, 0xd9, 0x5a, 0x6c, 0x69, 0x41, 0x15, 0x20, 0xef, 0x1, 0xb2, + 0xc0, 0x34, 0xb7, 0x47, 0x37, 0xa2, 0x20, 0xea, 0xfc, 0xa2, 0x50, 0x57, + 0xe9, 0xba, 0x25, 0x3, 0x2a, 0xc0, 0xa3, 0x49, 0x1e, 0x91, 0xa1, 0x52, + 0x3b, 0x9d, 0xd8, 0x7, 0xb0, 0x5f, 0xf4, 0xd1, 0xd6, 0x15, 0x22, 0x30, + 0xc2, 0x26, 0x9d, 0xb, 0xd2, 0xfc, 0xf6, 0xd4, 0x5, 0xa6, 0x3e, 0x0, + 0x56, 0xb3, 0x59, 0xd0, 0x1a, 0x5e, 0x5f, 0xd, 0xee, 0x48, 0x1e, 0x9d, + 0xf2, 0x51, 0xdb, 0xa0, 0x25, 0xbf, 0xa3, 0x13, 0x40, 0x79, 0xde, 0x46, + 0x3, 0xed, 0xbe, 0x3b, 0x34, 0x3c, 0x28, 0x54, 0x8c, 0x27, 0x3d, 0x23, + 0xe3, 0x64, 0x1c, 0xb5, 0x1a, 0x8e, 0xad, 0xb6, 0x87, 0xa2, 0x9b, 0x42, + 0xba, 0x39, 0x80, 0x67, 0x9c, 0xbf, 0x64, 0x72, 0x7, 0x9b, 0xa0, 0x36, + 0x61, 0x43, 0x19, 0xde, 0x65, 0x56, 0x9a, 0xbf, 0x11, 0xe2, 0x85, 0x75, + 0x9b, 0x7d, 0x19, 0x49, 0x34, 0xd8, 0xea, 0x5e, 0x5b, 0x3, 0xa6, 0x7c, + 0x97, 0x22, 0x83, 0xc7, 0x23, 0xc5, 0x15, 0x2c, 0x6e, 0xa8, 0x11, 0xd5, + 0x0, 0x58, 0x1, 0x76, 0xbf, 0xe5, 0x84, 0x7b, 0x54, 0xbc, 0x3b, 0x2a, + 0x58, 0xdc, 0xfd, 0x3a, 0x21, 0x7a, 0x7d, 0x80, 0xa1, 0xe5, 0xd5, 0x7d, + 0x40, 0xa1, 0x5, 0xee, 0x9b, 0x26, 0x37, 0x2a, 0xe0, 0xbd, 0x73, 0xa4, + 0xa6, 0x91, 0xa0, 0x5b, 0x13, 0x42, 0x4c, 0x65, 0xb8, 0x89, 0x8f, 0x2, + 0x79, 0xf6, 0xa5, 0x70, 0x50, 0xbc, 0xe4, 0x77, 0x18, 0xa4, 0xd7, 0x99, + 0xf0, 0x9e, 0xa4, 0x2a, 0x91, 0x1, 0x94, 0xbd, 0x4e, 0xe8, 0xa7, 0x28, + 0x1, 0xfd, 0x40, 0x13, 0x85, 0xd5, 0x5b, 0xb3, 0xe7, 0x42, 0xf3, 0xa1, + 0x33, 0x95, 0x6f, 0x26, 0x38, 0x98, 0x8, 0x9e, 0xa4, 0xbc, 0xd3, 0x4e, + 0xf8, 0x3d, 0xfd, 0x47, 0xf3, 0x0, 0x51, 0x44, 0xbb, 0x7a, 0x95, 0xc1, + 0xfd, 0x83, 0xfb, 0xa5, 0xb7, 0x31, 0x87, 0xae, 0xce, 0x73, 0x1, 0x7c, + 0x2f, 0x89, 0x6e, 0x68, 0xd8, 0x4a, 0xf6, 0x3, 0x1, 0x4, 0x35, 0x2c, + 0x9f, 0x97, 0xf2, 0x17, 0x3, 0xb8, 0x48, 0x6e, 0x0, 0xbd, 0x15, 0xe8, + 0x2d, 0x37, 0x80, 0xde, 0xa, 0xf4, 0x96, 0x1b, 0x40, 0x6f, 0x5, 0x7a, + 0xcb, 0xd, 0xa0, 0xb7, 0x2, 0xbd, 0xe5, 0xd, 0x80, 0xc9, 0x4e, 0x78, + 0xea, 0xe5, 0xd, 0xfd, 0x9c, 0x20, 0x91, 0x37, 0x0, 0xac, 0x10, 0x27, + 0x3a, 0x92, 0x75, 0xd0, 0x90, 0x31, 0x40, 0xf3, 0x43, 0x31, 0x4b, 0x40, + 0x1b, 0xe2, 0xa8, 0x89, 0x5b, 0xf3, 0x8a, 0x7f, 0x61, 0x0, 0xab, 0xdb, + 0xdd, 0x4f, 0x61, 0xab, 0xaf, 0x6f, 0x6b, 0x5a, 0xcd, 0x93, 0x39, 0x9a, + 0x3e, 0xbf, 0x1, 0xb0, 0x6, 0x8f, 0xe2, 0xed, 0x8b, 0xd, 0x6d, 0x53, + 0xdc, 0x50, 0x10, 0x71, 0xfe, 0x88, 0x1c, 0xdc, 0xa7, 0x50, 0x5c, 0x59, + 0x5a, 0x26, 0x5b, 0x6e, 0x71, 0xa, 0xbb, 0xa9, 0x10, 0xd9, 0x21, 0xb0, + 0xe1, 0x4a, 0xba, 0xbe, 0xaa, 0x52, 0x7e, 0x45, 0xa4, 0xa8, 0x91, 0x9d, + 0x13, 0xb, 0x28, 0x58, 0x17, 0x13, 0xdc, 0xc8, 0x3f, 0x98, 0x9, 0x1a, + 0xa7, 0x4a, 0x11, 0x60, 0x25, 0x3f, 0x92, 0x41, 0xd5, 0xa2, 0xa4, 0xae, + 0x9, 0x8a, 0xdb, 0xa, 0x25, 0x33, 0xbd, 0x74, 0x5d, 0xef, 0x3f, 0xde, + 0xa1, 0x49, 0x2c, 0xff, 0x12, 0x46, 0x5a, 0x66, 0x3, 0x0, 0x2b, 0xcd, + 0x8b, 0xb5, 0x66, 0xe2, 0x6f, 0x64, 0x9, 0x86, 0xed, 0xd3, 0xee, 0x7b, + 0x59, 0xf9, 0x75, 0x58, 0x3b, 0xf7, 0xdc, 0x3d, 0x20, 0x9, 0x54, 0x23, + 0x80, 0x8a, 0xbd, 0x12, 0x4f, 0x0, 0xd9, 0xaf, 0x0, 0x39, 0x39, 0xa7, + 0x79, 0x13, 0x70, 0x94, 0x93, 0x62, 0xd0, 0xc2, 0x1, 0xa4, 0x27, 0x22, + 0x3f, 0x7, 0x51, 0x58, 0x6, 0x68, 0x5e, 0x87, 0xa, 0xed, 0x0, 0x40, + 0x4e, 0x4, 0x97, 0xba, 0x1e, 0xe7, 0x80, 0xeb, 0x31, 0x87, 0x74, 0xf3, + 0x98, 0x6e, 0x97, 0x11, 0xa6, 0xf, 0xaa, 0xa2, 0xc5, 0x67, 0x9c, 0xb2, + 0x23, 0xb5, 0x2c, 0x6e, 0x81, 0xb0, 0x6a, 0xa6, 0xc5, 0x2a, 0x9e, 0x15, + 0x21, 0xf7, 0xd, 0x33, 0x68, 0xbb, 0x82, 0xcb, 0xf0, 0x3d, 0x0, 0xf0, + 0xa9, 0xe4, 0xab, 0x14, 0xae, 0x97, 0x8d, 0x17, 0xb8, 0x57, 0xe2, 0x0, + 0xb2, 0xbf, 0x13, 0xd4, 0x1, 0x8d, 0x54, 0x34, 0x76, 0xa2, 0x4d, 0x5b, + 0x9e, 0x32, 0x69, 0x4c, 0x10, 0x57, 0x50, 0xef, 0x52, 0xd, 0xc8, 0xbd, + 0xe9, 0xab, 0x59, 0xfc, 0xa8, 0x6, 0xb0, 0xde, 0x44, 0xf8, 0xa5, 0xa5, + 0x9a, 0xe1, 0xd2, 0x8f, 0x97, 0x83, 0x5a, 0xab, 0x0, 0x38, 0x6c, 0x55, + 0x9d, 0x60, 0xcc, 0xbe, 0xb5, 0xb4, 0xe5, 0x64, 0x47, 0xaf, 0x75, 0x2b, + 0x9b, 0xd6, 0x2d, 0x68, 0xd8, 0x82, 0x1f, 0x41, 0xf0, 0x8b, 0x9e, 0xd6, + 0x7, 0xec, 0x19, 0x60, 0x5c, 0x15, 0x0, 0xa9, 0xf, 0x58, 0x60, 0xbb, + 0xb0, 0x3, 0xad, 0xe9, 0xcf, 0xeb, 0x4f, 0x9d, 0x7e, 0xfc, 0x34, 0x1a, + 0x6b, 0xde, 0x68, 0x68, 0xf7, 0xe, 0xb3, 0x4e, 0x40, 0x63, 0x2b, 0xfa, + 0x73, 0x14, 0x60, 0x5d, 0x4c, 0x23, 0x80, 0x58, 0x9a, 0x75, 0xd, 0x0, + 0xc9, 0xb3, 0x92, 0xa3, 0xe2, 0x1, 0x1, 0x63, 0x53, 0x8f, 0x1, 0x7c, + 0x7a, 0xbb, 0xeb, 0x3, 0x24, 0x31, 0xcc, 0xf3, 0x0, 0x94, 0x4a, 0x2b, + 0x8d, 0x30, 0x9, 0x19, 0x9c, 0x7, 0x18, 0xb8, 0xf, 0x90, 0x34, 0x9c, + 0xf0, 0x38, 0x71, 0x5b, 0x47, 0xaa, 0x92, 0xba, 0x6a, 0x1e, 0x20, 0x84, + 0x69, 0x44, 0x85, 0x53, 0x93, 0x6c, 0xf3, 0xbc, 0x3a, 0xbe, 0xad, 0x6f, + 0xaf, 0xd1, 0xbc, 0x62, 0x17, 0x57, 0x2a, 0x9a, 0x3a, 0x87, 0xe2, 0x30, + 0x0, 0xbe, 0x47, 0x22, 0xb4, 0x97, 0x57, 0x9e, 0x8, 0x9, 0xef, 0x98, + 0x23, 0x53, 0x48, 0x7d, 0x30, 0x13, 0xc4, 0x59, 0x49, 0x67, 0x3a, 0xd1, + 0x36, 0x7b, 0xf1, 0x8a, 0x62, 0xad, 0x70, 0x48, 0x34, 0xaf, 0x6, 0x63, + 0xd9, 0x8b, 0xe7, 0x4f, 0xca, 0xb1, 0xd4, 0x5f, 0xda, 0x97, 0xc3, 0xbe, + 0xd4, 0xc4, 0x5f, 0xc, 0x60, 0x8b, 0xb3, 0x6b, 0x49, 0x33, 0xf7, 0xcd, + 0xe5, 0x36, 0x88, 0xf4, 0x56, 0xa0, 0xb7, 0xdc, 0x0, 0x7a, 0x2b, 0xd0, + 0x5b, 0x6e, 0x0, 0xbd, 0x15, 0xe8, 0x2d, 0x37, 0x80, 0xde, 0xa, 0xf4, + 0x96, 0x1b, 0xc0, 0x9e, 0xc4, 0xda, 0xf3, 0xf5, 0xd7, 0xf4, 0xd6, 0xe, + 0xcf, 0x97, 0x6b, 0xc5, 0x29, 0x30, 0xf0, 0xf, 0xa4, 0x2c, 0xd9, 0xd6, + 0x86, 0xfc, 0xc7, 0xb3, 0xdb, 0xf3, 0x25, 0xf4, 0xa5, 0x39, 0xe7, 0x17, + 0x5a, 0x41, 0x39, 0x49, 0x60, 0xc0, 0x2d, 0xbe, 0xff, 0x15, 0xc0, 0x6e, + 0x81, 0x1, 0x6b, 0xca, 0x29, 0x2d, 0xcb, 0x95, 0x0, 0x0, 0x2d, 0xb3, + 0xa9, 0xc4, 0x1c, 0xf3, 0xb0, 0xd5, 0x20, 0x19, 0x3e, 0xd2, 0x9a, 0xdf, + 0x73, 0xbb, 0x42, 0x28, 0xf9, 0x53, 0xc0, 0x97, 0xd2, 0xfa, 0x3e, 0xfb, + 0x17, 0xd8, 0x24, 0x3c, 0x2d, 0xfb, 0xd, 0xca, 0xb9, 0x92, 0x96, 0xcf, + 0x46, 0xe1, 0x98, 0xe6, 0x3a, 0x0, 0xef, 0xb2, 0xcb, 0x3b, 0xc, 0x60, + 0x45, 0x8b, 0x24, 0x11, 0x80, 0x56, 0xd9, 0x37, 0x4, 0x0, 0x90, 0x7, + 0x5f, 0xda, 0x4d, 0x68, 0x6, 0x59, 0x9c, 0xc2, 0x16, 0x35, 0xb3, 0x39, + 0x3e, 0xef, 0x3f, 0xda, 0x93, 0x72, 0x2a, 0x0, 0xd3, 0x50, 0x4a, 0xa0, + 0x91, 0x82, 0x9a, 0x0, 0x80, 0x9c, 0x86, 0x9a, 0x7f, 0x69, 0x37, 0x38, + 0x43, 0x8b, 0x53, 0x40, 0x36, 0x47, 0xf1, 0x7e, 0xe, 0x40, 0x3b, 0x87, + 0xad, 0xa7, 0xad, 0x0, 0x2c, 0x8e, 0xfc, 0x95, 0x0, 0x2c, 0xe9, 0xb, + 0x2b, 0xfb, 0x30, 0x64, 0xab, 0x6b, 0xe0, 0x5f, 0x1a, 0xcb, 0x23, 0x81, + 0x2d, 0xb9, 0x0, 0xe0, 0xb1, 0xcb, 0xe2, 0xfd, 0x1c, 0x80, 0x98, 0x15, + 0xbe, 0x15, 0x0, 0xf6, 0xf5, 0x88, 0x0, 0x52, 0x85, 0xd1, 0x1b, 0x2, + 0xe0, 0xc9, 0xf2, 0x2f, 0x59, 0x19, 0xc0, 0x9c, 0xd3, 0xe5, 0x84, 0xda, + 0xfb, 0x39, 0x0, 0x13, 0x42, 0x18, 0xcf, 0x0, 0x30, 0x40, 0x6f, 0x94, + 0x50, 0x8a, 0xa3, 0x9c, 0xa6, 0x7, 0x2, 0xc8, 0xb9, 0x5f, 0xd8, 0xf5, + 0x41, 0x6, 0x40, 0x28, 0x1b, 0x7f, 0x3, 0x80, 0xf3, 0xfa, 0x0, 0x8b, + 0x52, 0x43, 0xc9, 0x0, 0xd6, 0x34, 0x10, 0x42, 0x0, 0x33, 0x71, 0x9b, + 0x79, 0x71, 0x75, 0x21, 0x97, 0x59, 0x1a, 0x4d, 0x90, 0x81, 0xe6, 0x79, + 0xff, 0x51, 0x7e, 0xf8, 0xaf, 0x1, 0x40, 0x69, 0x7e, 0xf6, 0x51, 0x60, + 0xa9, 0x2d, 0xe5, 0xb1, 0x49, 0x5, 0x2, 0xd8, 0x14, 0xf7, 0x75, 0x2d, + 0x65, 0x44, 0xb3, 0x78, 0x14, 0x0, 0xf2, 0xbc, 0xff, 0xd0, 0x2c, 0xde, + 0x3c, 0xf, 0x70, 0x26, 0x75, 0x99, 0x20, 0x6c, 0x7b, 0x9f, 0x7, 0xf0, + 0x46, 0x10, 0x8a, 0xc7, 0x84, 0x85, 0x4e, 0x3c, 0x0, 0x30, 0xf7, 0x5b, + 0xd1, 0xc7, 0xa1, 0x83, 0x89, 0x8a, 0xc3, 0x0, 0xba, 0xde, 0xe0, 0x9b, + 0x19, 0xe1, 0xfe, 0x6a, 0x0, 0x56, 0xb0, 0xc3, 0x7b, 0x1c, 0xfe, 0xbf, + 0x2b, 0xa2, 0x79, 0x23, 0x8, 0x78, 0xe7, 0xc6, 0xc3, 0xb7, 0x66, 0x21, + 0x80, 0x88, 0xbc, 0xa0, 0x25, 0x62, 0x85, 0xcf, 0x33, 0xc4, 0x4d, 0x21, + 0xc2, 0xfd, 0xaf, 0x5f, 0x3f, 0xba, 0x1, 0x48, 0x6b, 0xf8, 0xff, 0x69, + 0xd2, 0xf8, 0xe0, 0xf0, 0xe9, 0x49, 0x53, 0x7f, 0xbb, 0xa4, 0x7a, 0x34, + 0x8c, 0x87, 0x13, 0xa1, 0xde, 0x5a, 0x5e, 0x29, 0x5e, 0xc1, 0xa4, 0x5c, + 0xbf, 0x7, 0x40, 0xcd, 0x91, 0x83, 0x3f, 0x1c, 0xc0, 0xb1, 0xdc, 0x0, + 0x7a, 0x2b, 0xd0, 0x5b, 0x6e, 0x0, 0xbd, 0x15, 0xe8, 0x2d, 0x37, 0x80, + 0xde, 0xa, 0xf4, 0x96, 0x1b, 0x40, 0x6f, 0x5, 0xae, 0x13, 0x27, 0x66, + 0x45, 0xf8, 0x2f, 0x0, 0xd1, 0xae, 0xee, 0x60, 0x82, 0xfe, 0x67, 0xbe, + 0xa0, 0x89, 0x5d, 0xdf, 0xfd, 0xb, 0xcc, 0x36, 0x67, 0xcb, 0xca, 0xd1, + 0xbe, 0x4e, 0x49, 0x1f, 0x51, 0x98, 0x2c, 0x18, 0x73, 0xb1, 0x79, 0x5, + 0xe3, 0x98, 0x46, 0xce, 0x66, 0x13, 0x44, 0xe4, 0xeb, 0x73, 0x39, 0x7f, + 0x51, 0xe, 0x21, 0xad, 0x0, 0x50, 0x72, 0x9c, 0x80, 0xd5, 0xe7, 0x54, + 0x2, 0xfc, 0xd8, 0xf5, 0x35, 0x7b, 0x5, 0x14, 0x7, 0xf0, 0x5c, 0x56, + 0x73, 0x63, 0x66, 0x7e, 0x21, 0x1e, 0x43, 0x3b, 0x8, 0xcb, 0xe1, 0x2d, + 0x47, 0xca, 0x47, 0x42, 0xf1, 0x83, 0x1b, 0x4c, 0x53, 0x23, 0xda, 0x34, + 0x8f, 0x83, 0x24, 0x9f, 0x0, 0x4a, 0x4c, 0x24, 0x70, 0x38, 0x48, 0xf1, + 0x70, 0x30, 0x53, 0x82, 0x4, 0x60, 0xf, 0x5b, 0x77, 0x4c, 0xf1, 0x87, + 0x41, 0x84, 0x3, 0xc8, 0xc7, 0x3c, 0xa2, 0x74, 0x3c, 0xd9, 0xb8, 0x23, + 0x1d, 0x23, 0x8b, 0x1c, 0x32, 0x4d, 0x0, 0x40, 0x7c, 0xe3, 0x46, 0xe2, + 0x31, 0x81, 0x4d, 0x0, 0xf6, 0xbd, 0x12, 0x3c, 0xf9, 0x50, 0x6, 0xe0, + 0xc2, 0x66, 0xf9, 0x1b, 0x69, 0x82, 0x39, 0xd1, 0x73, 0x15, 0x18, 0xf1, + 0x3b, 0xe1, 0x4c, 0xcf, 0xd, 0x0, 0xe2, 0x8, 0x8, 0xe6, 0xc, 0xf, + 0x18, 0x41, 0x1b, 0x80, 0xb2, 0xeb, 0x1f, 0x45, 0xbf, 0x87, 0x6c, 0xb0, + 0x0, 0x36, 0xb7, 0x45, 0x8, 0x72, 0xb7, 0x34, 0xb, 0x15, 0x0, 0x67, + 0x17, 0xaf, 0x7, 0x20, 0xa4, 0xa0, 0x16, 0xcf, 0xb, 0x6e, 0x3, 0x90, + 0xf, 0x3f, 0xf0, 0x28, 0xf8, 0x3b, 0xa4, 0x5e, 0x6, 0x1, 0xc8, 0x26, + 0x51, 0x7c, 0xf2, 0xb6, 0x10, 0xfd, 0xef, 0xb0, 0xfb, 0xa7, 0x1e, 0x80, + 0x13, 0x3c, 0x68, 0x71, 0xc2, 0xaa, 0xb4, 0x1, 0x48, 0xf, 0x88, 0x23, + 0xb7, 0xdb, 0x7b, 0xa, 0xda, 0x10, 0x4a, 0xa3, 0xef, 0xa5, 0xa3, 0xaa, + 0x2d, 0xae, 0x0, 0x52, 0xaa, 0xf3, 0x96, 0x3e, 0x40, 0xc9, 0xc7, 0x96, + 0xc2, 0x73, 0x84, 0xdf, 0x0, 0xe0, 0xf7, 0x5b, 0xf2, 0x20, 0x8f, 0x78, + 0x48, 0x15, 0xda, 0x13, 0xca, 0x3d, 0xa5, 0xc8, 0xe1, 0x57, 0x12, 0xd2, + 0xd4, 0x48, 0xe9, 0xf5, 0x5b, 0x0, 0x4c, 0x92, 0x7, 0x0, 0x66, 0x7a, + 0x82, 0x8a, 0xac, 0x8f, 0x61, 0x8a, 0x77, 0x1b, 0x78, 0x37, 0x59, 0xf1, + 0xe, 0xf, 0x60, 0x7f, 0x81, 0x2b, 0xa3, 0x0, 0xf2, 0x67, 0x60, 0x0, + 0x52, 0x76, 0xa1, 0x26, 0x0, 0x70, 0xdc, 0x35, 0x65, 0xc4, 0xaf, 0x56, + 0x44, 0x93, 0x81, 0x79, 0x3, 0xcb, 0x7e, 0x42, 0x8e, 0x45, 0x8a, 0xf, + 0x28, 0xaa, 0x44, 0xaa, 0x6, 0x9f, 0x6e, 0xd5, 0xd2, 0x44, 0xa8, 0x61, + 0x18, 0xe4, 0x55, 0x31, 0xef, 0x3b, 0x14, 0xec, 0xea, 0x18, 0xc0, 0x7e, + 0xc0, 0x30, 0xba, 0x5f, 0xe3, 0xb0, 0x81, 0x34, 0x32, 0x46, 0xe, 0x60, + 0xd7, 0x4, 0x92, 0x14, 0x9a, 0xde, 0xa7, 0x13, 0x21, 0x59, 0x42, 0x6b, + 0x38, 0x7f, 0x37, 0x87, 0x1, 0x94, 0xcf, 0x1, 0xfc, 0xe5, 0x52, 0xb6, + 0x99, 0xfd, 0x6e, 0x0, 0x75, 0xab, 0xc1, 0x1f, 0x2b, 0x31, 0x84, 0xdf, + 0x5d, 0x3, 0xea, 0xe4, 0x6, 0xd0, 0x5b, 0x81, 0xde, 0x72, 0x3, 0xe8, + 0xad, 0x40, 0x6f, 0xb9, 0x1, 0xf4, 0x56, 0xa0, 0xb7, 0xdc, 0x0, 0xb6, + 0x51, 0x8e, 0x85, 0x3b, 0xce, 0xbf, 0x50, 0x21, 0x53, 0xe3, 0x99, 0x5a, + 0xd3, 0x71, 0x60, 0xd3, 0x49, 0x5f, 0x7a, 0x0, 0x10, 0xd7, 0xc, 0xde, + 0x9, 0x36, 0xc8, 0xc5, 0x36, 0x71, 0x69, 0x3c, 0x9e, 0xd0, 0xb6, 0x1d, + 0x65, 0xf7, 0xc1, 0x97, 0x1e, 0x0, 0x36, 0x39, 0xa0, 0x5a, 0x38, 0x70, + 0x50, 0xab, 0xb6, 0xc7, 0x2d, 0xae, 0xe5, 0xe7, 0xf9, 0xf3, 0x0, 0x46, + 0x31, 0xbf, 0x37, 0x6, 0xe0, 0x68, 0xd5, 0x47, 0x1, 0xa8, 0x35, 0xe2, + 0x61, 0x52, 0x98, 0xfc, 0x2e, 0xde, 0x41, 0x73, 0x74, 0xc4, 0x5f, 0xc8, + 0x5, 0x69, 0x98, 0xd5, 0xdc, 0x55, 0x1c, 0x9c, 0x5c, 0xbc, 0x22, 0x52, + 0x2f, 0x0, 0x1, 0xc4, 0x7c, 0x50, 0xb7, 0xe8, 0x33, 0xa8, 0x17, 0x21, + 0x7b, 0x7d, 0x2, 0x90, 0x3b, 0x1f, 0x40, 0x78, 0x92, 0x33, 0x46, 0xc1, + 0x14, 0xda, 0x86, 0x24, 0xeb, 0xea, 0xb, 0x80, 0xf4, 0x48, 0xa1, 0x8a, + 0x42, 0x0, 0xc5, 0x95, 0x61, 0xf, 0xb7, 0xa2, 0x1c, 0x4a, 0x4e, 0x8, + 0x83, 0x3c, 0x52, 0x96, 0x94, 0xf, 0x7c, 0x7, 0x48, 0x76, 0x17, 0xb8, + 0x10, 0x78, 0x87, 0x5a, 0x72, 0x1d, 0x19, 0x94, 0xa9, 0x6a, 0xae, 0x49, + 0xde, 0x9e, 0x1, 0x78, 0x89, 0x13, 0x4, 0x50, 0xaa, 0xd5, 0x7a, 0xca, + 0xa9, 0xdc, 0x21, 0x18, 0x94, 0x90, 0xca, 0x3f, 0x63, 0xbd, 0xff, 0x23, + 0x42, 0x5a, 0xe5, 0xf5, 0x71, 0x48, 0x2b, 0xcf, 0xeb, 0x44, 0x69, 0x35, + 0x3c, 0xd4, 0xa5, 0xd1, 0xd1, 0xc2, 0x6c, 0xe0, 0xd2, 0xa3, 0xa7, 0xe3, + 0x80, 0xab, 0xae, 0xc5, 0x5b, 0x60, 0x36, 0x8d, 0xfb, 0xb9, 0xf0, 0x38, + 0xaa, 0x83, 0x9f, 0x61, 0x22, 0x6e, 0x4, 0xe1, 0x0, 0xa4, 0x91, 0xf0, + 0x52, 0x0, 0x39, 0x29, 0x21, 0xae, 0x1, 0x1, 0xd6, 0x0, 0x83, 0xfb, + 0x6a, 0x9, 0x40, 0x2c, 0x35, 0xa0, 0x32, 0x95, 0x96, 0x34, 0x12, 0x5e, + 0xa, 0xe0, 0x91, 0x64, 0xd, 0x3, 0x0, 0x1b, 0x12, 0x6, 0x6c, 0xff, + 0x2f, 0x4d, 0xc0, 0x83, 0x9a, 0xa4, 0xea, 0xd2, 0x7c, 0x3c, 0x53, 0x69, + 0x9, 0x74, 0x2f, 0x4, 0x90, 0xcf, 0x1d, 0x50, 0xb0, 0x9, 0x8c, 0xce, + 0xc2, 0xfd, 0x87, 0xd9, 0x1, 0x80, 0x9c, 0xc9, 0x76, 0x22, 0x98, 0x78, + 0x49, 0x4d, 0xce, 0x1d, 0xc6, 0x8b, 0xd3, 0x3, 0xa3, 0xb0, 0xb1, 0xe9, + 0x42, 0x0, 0xc5, 0x5, 0x8, 0xb2, 0xf2, 0x95, 0xd1, 0xe, 0x85, 0x38, + 0xa7, 0xa1, 0x1, 0x6d, 0x90, 0x5c, 0xd3, 0xe5, 0x9, 0x1d, 0x92, 0x32, + 0x95, 0x3c, 0x42, 0x75, 0x0, 0x84, 0x91, 0xf0, 0x52, 0x0, 0x72, 0x16, + 0xa1, 0x18, 0xda, 0xbe, 0x21, 0x9f, 0x83, 0x50, 0x31, 0x54, 0x3d, 0x0, + 0x2c, 0x96, 0xcf, 0x6c, 0xd6, 0x30, 0x37, 0x0, 0x8, 0x4f, 0xb9, 0x84, + 0xd5, 0x75, 0x72, 0x90, 0x54, 0xb5, 0x1e, 0xc0, 0xf3, 0xb0, 0x9a, 0x2f, + 0x4f, 0xca, 0xbf, 0x1f, 0x80, 0xb5, 0x62, 0x10, 0xf9, 0x9f, 0xb8, 0xa7, + 0x7c, 0x2b, 0xf7, 0xd8, 0x47, 0x0, 0x7e, 0x87, 0xdc, 0x0, 0x7a, 0x2b, + 0xd0, 0x5b, 0x6e, 0x0, 0xbd, 0x15, 0xe8, 0x2d, 0x37, 0x80, 0xde, 0xa, + 0xf4, 0x96, 0x1b, 0x40, 0xfe, 0xb3, 0x58, 0x65, 0xaa, 0x8d, 0xf7, 0xb2, + 0x9, 0x3e, 0x88, 0x9b, 0xf5, 0xd, 0x38, 0x13, 0x54, 0x2a, 0xc7, 0xe3, + 0x64, 0xf9, 0x57, 0x2, 0xc8, 0xa7, 0x82, 0xda, 0x8a, 0x7d, 0xc6, 0xbb, + 0xc8, 0x16, 0x68, 0x27, 0x45, 0xa4, 0x44, 0x14, 0x27, 0x27, 0x95, 0xe3, + 0x73, 0x56, 0xdd, 0x3f, 0xb, 0xc0, 0x36, 0x9d, 0x7b, 0xf9, 0xc6, 0x4, + 0xef, 0xa4, 0x0, 0xfb, 0xc5, 0xb5, 0x94, 0x73, 0x9c, 0xf7, 0xe2, 0x64, + 0x0, 0xc2, 0x2, 0x26, 0x96, 0xa9, 0x3d, 0x48, 0xce, 0x81, 0xed, 0xf6, + 0x21, 0xdf, 0xd, 0x8a, 0xf1, 0xc2, 0xa, 0xa1, 0x0, 0x98, 0x11, 0x33, + 0x8, 0x20, 0xb8, 0x98, 0x9e, 0x3a, 0x3b, 0x50, 0xfe, 0x26, 0xf8, 0x11, + 0x66, 0xe7, 0x8e, 0x53, 0x0, 0x53, 0x5a, 0xc7, 0xe6, 0x3c, 0x2c, 0x7c, + 0xd9, 0xb3, 0x2a, 0x82, 0x87, 0x6c, 0x58, 0x7c, 0x59, 0xc, 0xca, 0xb2, + 0xc2, 0x1a, 0x31, 0x3, 0xc0, 0x47, 0x2b, 0x42, 0x0, 0x8e, 0xf2, 0x89, + 0x14, 0xc0, 0xd0, 0x6f, 0xf3, 0x7, 0x40, 0x9f, 0xdd, 0x5f, 0x70, 0xec, + 0x17, 0x10, 0x23, 0x2d, 0xf3, 0x69, 0xd4, 0x2b, 0x48, 0x41, 0x90, 0xed, + 0xf6, 0xb, 0xb0, 0xe7, 0xc7, 0x30, 0x43, 0x0, 0x79, 0x51, 0x69, 0x30, + 0x0, 0x8f, 0x33, 0x5d, 0x9, 0x0, 0x6c, 0xde, 0x32, 0x1, 0x53, 0x6b, + 0x40, 0x7d, 0x34, 0x8d, 0x61, 0x51, 0x87, 0xad, 0x5b, 0xae, 0x1, 0x65, + 0x33, 0xb, 0x3c, 0x33, 0xdc, 0xb, 0xd, 0x58, 0x6e, 0xbb, 0xf8, 0x48, + 0xcf, 0x59, 0x48, 0xea, 0x2e, 0x0, 0x58, 0x2, 0x4d, 0x10, 0x0, 0xd4, + 0x27, 0x14, 0xeb, 0xd9, 0xb1, 0x63, 0x56, 0xee, 0x3, 0xd2, 0x77, 0xc3, + 0x4, 0x5d, 0x57, 0xe1, 0x24, 0x0, 0x24, 0x5a, 0xe3, 0x21, 0x80, 0x90, + 0x2d, 0x74, 0x8, 0x0, 0xd4, 0x67, 0x7e, 0x9c, 0x53, 0x70, 0x34, 0xa2, + 0xca, 0x0, 0x66, 0xe1, 0xf0, 0xf9, 0xf3, 0x0, 0xcc, 0xe2, 0x11, 0x3, + 0x27, 0x0, 0xd8, 0x53, 0x31, 0xcd, 0x1f, 0x0, 0x50, 0x1a, 0x5b, 0x1b, + 0xcf, 0x3, 0x10, 0x1c, 0x36, 0xda, 0x9e, 0x2, 0x60, 0x77, 0x3b, 0xda, + 0x43, 0x2f, 0xee, 0x1b, 0x0, 0x64, 0x47, 0x7, 0x3c, 0xfb, 0x12, 0x80, + 0x1c, 0x4f, 0x41, 0x82, 0x43, 0x5a, 0x0, 0x90, 0x7b, 0xf0, 0xad, 0xb2, + 0x9c, 0x56, 0x0, 0xe9, 0x92, 0x9e, 0x6c, 0x9d, 0x59, 0x5c, 0xda, 0xa0, + 0x42, 0x30, 0x3f, 0x9a, 0xfc, 0x40, 0xd9, 0x17, 0x27, 0x1, 0xd8, 0x14, + 0x1a, 0xd6, 0x60, 0x39, 0xcd, 0x0, 0x3e, 0xcd, 0x23, 0xb4, 0x96, 0x5e, + 0xf4, 0xb8, 0xd, 0x55, 0x89, 0xe9, 0x63, 0x2b, 0xae, 0xb1, 0xe9, 0x8a, + 0x0, 0xf6, 0x61, 0x64, 0x68, 0x8b, 0xef, 0x41, 0x32, 0x85, 0x30, 0x57, + 0x1e, 0x7a, 0xd4, 0x43, 0xe4, 0x59, 0xb7, 0x11, 0xce, 0xf5, 0x69, 0x7e, + 0x82, 0xb4, 0xfd, 0xf0, 0x7b, 0xc8, 0x9b, 0x65, 0x47, 0x9a, 0xdc, 0xcf, + 0x27, 0x9c, 0xa3, 0xb0, 0xe2, 0xc0, 0x9e, 0xef, 0x22, 0xb7, 0x41, 0xa4, + 0xb7, 0x2, 0xbd, 0xe5, 0x6, 0xd0, 0x5b, 0x81, 0xde, 0x72, 0x3, 0xe8, + 0xad, 0x40, 0x6f, 0xb9, 0x1, 0xf4, 0x56, 0xa0, 0xb7, 0xdc, 0x0, 0xae, + 0x7f, 0xc4, 0x6a, 0xfe, 0xa0, 0x9f, 0xe3, 0x3b, 0x2, 0x8, 0xe7, 0xac, + 0x28, 0x2f, 0x3, 0x20, 0xdb, 0xd5, 0x7d, 0x9a, 0xc5, 0xcf, 0x3c, 0xf7, + 0x85, 0x8b, 0xc1, 0xc1, 0x2d, 0x10, 0xb3, 0x3, 0xd7, 0x83, 0x1b, 0x4b, + 0xfe, 0x7a, 0x50, 0x7a, 0x7c, 0xfc, 0x79, 0xb9, 0x5c, 0x56, 0xce, 0xec, + 0xb, 0x8f, 0xb, 0xe1, 0xf5, 0xb, 0x6b, 0xf9, 0x80, 0x1f, 0x80, 0x28, + 0x95, 0xc3, 0x1, 0xec, 0x76, 0x75, 0x6e, 0x99, 0x91, 0xe2, 0xf0, 0x5d, + 0xf1, 0xb, 0xa8, 0x88, 0xef, 0x77, 0xfc, 0x76, 0x21, 0x41, 0xb3, 0x68, + 0xcc, 0xcc, 0xaa, 0x78, 0xa6, 0x90, 0x7d, 0x66, 0x9e, 0x7a, 0x7d, 0x70, + 0x49, 0x21, 0xa2, 0xaa, 0xcb, 0x41, 0x0, 0x70, 0x7c, 0xbe, 0x14, 0x87, + 0xef, 0x72, 0xd2, 0x9d, 0x1, 0xf9, 0x11, 0xb2, 0x7f, 0x15, 0xba, 0xb, + 0xc, 0xa, 0x1f, 0x94, 0x2c, 0x39, 0xc5, 0x96, 0x37, 0x30, 0x7d, 0x6, + 0x29, 0xf3, 0x54, 0x4e, 0x22, 0x83, 0x42, 0xe9, 0x1f, 0xe5, 0x1c, 0xfb, + 0x5, 0x84, 0xf8, 0x7c, 0x31, 0xe, 0xbf, 0xdc, 0xa, 0xe, 0x65, 0x49, + 0x35, 0x1d, 0x3f, 0x4c, 0xe8, 0x3, 0x24, 0x0, 0xa3, 0x94, 0x78, 0xc9, + 0x2f, 0xe9, 0xc9, 0x60, 0xb3, 0xce, 0x4c, 0xa, 0x5, 0x7b, 0xb, 0xe5, + 0x20, 0x0, 0x30, 0x3e, 0x5f, 0xa, 0x43, 0x17, 0x63, 0xeb, 0x73, 0xcc, + 0x2e, 0xd, 0x28, 0xc6, 0xb7, 0xd, 0x40, 0x36, 0x45, 0xcd, 0xdc, 0x10, + 0xe5, 0x29, 0x1b, 0x4b, 0x37, 0x54, 0x96, 0x21, 0x6c, 0xa3, 0x86, 0xe5, + 0x88, 0x0, 0x6, 0xf6, 0xfd, 0x46, 0x0, 0x49, 0xc7, 0x11, 0xfe, 0x14, + 0x8d, 0x0, 0x72, 0x8, 0xbd, 0x41, 0x5c, 0x46, 0x95, 0x1a, 0x2a, 0x78, + 0x78, 0xee, 0x7c, 0x50, 0x74, 0x3, 0x2e, 0x47, 0x0, 0xc0, 0xed, 0xe7, + 0x62, 0x1c, 0xfe, 0xbb, 0xdc, 0x24, 0x2b, 0xf4, 0xeb, 0xe2, 0xdd, 0x8, + 0xf9, 0xb9, 0xa8, 0xfc, 0x6c, 0x88, 0x45, 0xdf, 0x48, 0x15, 0xdd, 0xa7, + 0xff, 0xfc, 0xe1, 0xa9, 0x17, 0x54, 0x28, 0xf3, 0x9d, 0x50, 0xe, 0x0, + 0x80, 0xe3, 0xf3, 0x4b, 0x1c, 0x7e, 0x3, 0x80, 0x71, 0x9a, 0x97, 0x1, + 0x3e, 0x31, 0x87, 0xb3, 0xf3, 0x9f, 0x68, 0x20, 0x3, 0xe3, 0xfc, 0x8b, + 0x15, 0x71, 0x86, 0x57, 0xd3, 0x77, 0x50, 0x9f, 0x91, 0x8f, 0xa4, 0x40, + 0x56, 0x57, 0xa2, 0x8a, 0xcd, 0x6d, 0xb4, 0xdb, 0xe1, 0x41, 0x7c, 0x7e, + 0x89, 0xc3, 0x6f, 0x68, 0x2, 0x65, 0xb, 0x90, 0x41, 0xf3, 0x83, 0x9c, + 0xe3, 0x1c, 0x6c, 0x77, 0x93, 0xe2, 0xfc, 0xf3, 0x49, 0xe8, 0xa0, 0x94, + 0x7c, 0x7e, 0x87, 0xe7, 0xce, 0x34, 0xff, 0x38, 0x49, 0xc2, 0xd6, 0x96, + 0x3, 0x0, 0x2c, 0x42, 0x43, 0x9, 0xb1, 0x6d, 0x47, 0xa6, 0x1c, 0x2a, + 0x8f, 0x3f, 0xb9, 0x3a, 0xed, 0x90, 0xad, 0xb1, 0xe9, 0xd3, 0xfb, 0x77, + 0xfc, 0xfa, 0x96, 0xd4, 0xde, 0x12, 0xc3, 0x58, 0xe5, 0x8d, 0xf8, 0xb1, + 0x0, 0xd2, 0x7c, 0x4d, 0xd5, 0x18, 0xf5, 0x69, 0x5b, 0xdf, 0xbd, 0xe2, + 0xfa, 0xb7, 0xbe, 0x7f, 0x5a, 0x34, 0xd4, 0x79, 0x23, 0x6e, 0x7b, 0x40, + 0x6f, 0x5, 0x7a, 0xcb, 0xd, 0xa0, 0xb7, 0x2, 0xbd, 0xe5, 0x6, 0xd0, + 0x5b, 0x81, 0xde, 0xf2, 0x15, 0x0, 0xf1, 0x94, 0xe4, 0x1, 0xdf, 0x44, + 0xbe, 0x74, 0xd4, 0xd6, 0xb5, 0x3b, 0x6a, 0xbf, 0x11, 0x0, 0xaf, 0xe0, + 0xab, 0x6, 0xe1, 0x6a, 0x53, 0x38, 0xd, 0x9, 0xa1, 0xc5, 0xa7, 0xca, + 0x68, 0xf4, 0xb1, 0x49, 0x4c, 0x96, 0x95, 0x14, 0xac, 0xec, 0x18, 0x40, + 0x74, 0xc7, 0x47, 0xbc, 0xfe, 0x7, 0x80, 0x68, 0xaf, 0x5a, 0xcc, 0x49, + 0x41, 0x35, 0x69, 0x95, 0x6f, 0x3f, 0x1, 0xe0, 0x48, 0x32, 0xf2, 0x9, + 0x4d, 0xa0, 0xc2, 0xfe, 0x50, 0x1, 0xc0, 0xe7, 0x44, 0x1, 0xea, 0x84, + 0x56, 0x16, 0x6b, 0x56, 0x43, 0xf4, 0x88, 0xe7, 0xc7, 0x9, 0x83, 0x84, + 0xed, 0xea, 0x22, 0x80, 0x86, 0x3a, 0x2d, 0x3, 0x50, 0xc5, 0x8c, 0x71, + 0xc2, 0x52, 0xb9, 0xca, 0x23, 0x43, 0xe2, 0xc1, 0x8a, 0x9d, 0x0, 0xac, + 0x8e, 0x54, 0x8, 0x27, 0x4, 0x67, 0xa1, 0x53, 0x1d, 0x20, 0x80, 0x1c, + 0xcf, 0xaf, 0xe0, 0xad, 0x63, 0x2b, 0x0, 0x98, 0x8d, 0x26, 0xce, 0xb0, + 0x21, 0x49, 0x0, 0xec, 0x49, 0xd1, 0x79, 0x5b, 0x4e, 0xc, 0xae, 0xed, + 0x61, 0xa8, 0x68, 0xd1, 0x1, 0x6e, 0xdc, 0xf0, 0xa3, 0xe4, 0x55, 0x11, + 0x1, 0x38, 0xd2, 0xec, 0x93, 0xa8, 0x8a, 0xdb, 0x89, 0x39, 0x34, 0xc4, + 0x26, 0x70, 0x7c, 0x4e, 0x6a, 0x9d, 0x54, 0xa6, 0x33, 0xd8, 0x13, 0x29, + 0x21, 0xd3, 0x81, 0x15, 0xf, 0x19, 0x11, 0x1, 0x64, 0x33, 0xdf, 0xeb, + 0xb5, 0x99, 0xdc, 0x94, 0xb8, 0xb0, 0xdf, 0x54, 0x2, 0x30, 0xa9, 0xfc, + 0xbb, 0xb5, 0x65, 0xe0, 0x13, 0xa4, 0xca, 0x9a, 0x43, 0xc5, 0xae, 0xe, + 0x2b, 0x69, 0x4, 0xa7, 0x2, 0xbc, 0x7, 0x30, 0x0, 0x64, 0xde, 0xee, + 0xf9, 0x52, 0x5e, 0x3f, 0x91, 0x0, 0x78, 0x9b, 0x6, 0xdf, 0x73, 0x42, + 0x2b, 0xab, 0x12, 0xe9, 0x90, 0x7c, 0x40, 0xc1, 0x89, 0x9d, 0x60, 0xe4, + 0xf6, 0x4f, 0xb1, 0x9, 0xac, 0xcd, 0x59, 0x38, 0xa4, 0x7d, 0x31, 0x55, + 0x7d, 0x32, 0x65, 0xe3, 0xb1, 0xb0, 0x57, 0xb3, 0xd3, 0x30, 0x68, 0xf3, + 0x59, 0xa9, 0xa6, 0xa1, 0x23, 0xb0, 0x78, 0xe7, 0x49, 0xd5, 0x34, 0xa0, + 0xe7, 0x30, 0x28, 0x4e, 0x85, 0x47, 0x78, 0x98, 0xb8, 0x2c, 0x42, 0x9d, + 0xf1, 0x35, 0xa7, 0xee, 0x1e, 0xcc, 0x4, 0x71, 0x2f, 0x28, 0x3, 0x38, + 0x2b, 0x2a, 0xbe, 0x71, 0x1e, 0x80, 0x9e, 0x6b, 0xad, 0xa2, 0xe3, 0x6d, + 0x93, 0xef, 0x1, 0x2c, 0x64, 0x16, 0xbc, 0xaf, 0xf, 0x2, 0x8, 0x43, + 0x15, 0xf2, 0xb3, 0x25, 0x3a, 0xb8, 0xf5, 0x57, 0x93, 0xa9, 0xaa, 0x44, + 0x6f, 0x2b, 0xed, 0x48, 0x6d, 0xab, 0x41, 0xdd, 0x23, 0x85, 0x4e, 0xd0, + 0xc3, 0x27, 0xe6, 0x89, 0xf7, 0xad, 0x36, 0x42, 0xef, 0x95, 0x60, 0x10, + 0xf9, 0xeb, 0x92, 0x48, 0x55, 0x0, 0xf8, 0x5, 0x72, 0x3, 0xe8, 0xad, + 0x40, 0x6f, 0xb9, 0x1, 0xf4, 0x56, 0xa0, 0xb7, 0xdc, 0x0, 0x7a, 0x2b, + 0xd0, 0x5b, 0x6e, 0x0, 0xbd, 0x15, 0xe8, 0x2d, 0x37, 0x80, 0xde, 0xa, + 0xf4, 0x96, 0x1b, 0x40, 0x6f, 0x5, 0x7a, 0xcb, 0xd, 0xa0, 0xb7, 0x2, + 0xbd, 0xe5, 0x6, 0xd0, 0x5b, 0x81, 0xde, 0x72, 0x3, 0xe8, 0xad, 0x40, + 0x6f, 0xf9, 0xf5, 0x0, 0xfe, 0x1, 0x35, 0x69, 0x45, 0xec, 0xa0, 0xf0, + 0xdf, 0x8c, 0x0, 0x0, 0x0, 0x25, 0x74, 0x45, 0x58, 0x74, 0x64, 0x61, + 0x74, 0x65, 0x3a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x0, 0x32, 0x30, + 0x31, 0x37, 0x2d, 0x30, 0x34, 0x2d, 0x30, 0x32, 0x54, 0x31, 0x38, 0x3a, + 0x33, 0x39, 0x3a, 0x33, 0x37, 0x2b, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x98, + 0xbb, 0xf, 0xb3, 0x0, 0x0, 0x0, 0x25, 0x74, 0x45, 0x58, 0x74, 0x64, + 0x61, 0x74, 0x65, 0x3a, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0, 0x32, + 0x30, 0x31, 0x37, 0x2d, 0x30, 0x34, 0x2d, 0x30, 0x32, 0x54, 0x31, 0x38, + 0x3a, 0x33, 0x39, 0x3a, 0x33, 0x37, 0x2b, 0x30, 0x30, 0x3a, 0x30, 0x30, + 0xe9, 0xe6, 0xb7, 0xf, 0x0, 0x0, 0x0, 0x11, 0x74, 0x45, 0x58, 0x74, + 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x0, 0x55, 0x6e, 0x64, 0x65, + 0x66, 0x69, 0x6e, 0x65, 0x64, 0xdb, 0xa5, 0x33, 0x1b, 0x0, 0x0, 0x0, + 0x4a, 0x74, 0x45, 0x58, 0x74, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x0, 0x65, 0x34, 0x39, 0x30, 0x38, 0x64, 0x62, 0x62, 0x30, + 0x62, 0x64, 0x38, 0x62, 0x36, 0x62, 0x66, 0x64, 0x32, 0x38, 0x39, 0x33, + 0x34, 0x39, 0x38, 0x39, 0x35, 0x39, 0x39, 0x62, 0x66, 0x32, 0x66, 0x61, + 0x39, 0x38, 0x63, 0x63, 0x33, 0x64, 0x35, 0x36, 0x35, 0x31, 0x39, 0x66, + 0x36, 0x66, 0x36, 0x35, 0x31, 0x64, 0x61, 0x32, 0x65, 0x34, 0x36, 0x30, + 0x34, 0x38, 0x38, 0x38, 0x33, 0x34, 0x35, 0xfb, 0xa7, 0xec, 0xfc, 0x0, + 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82}; +} // namespace consolas_font +const unsigned int sGlyphWidth = 9; +const unsigned int sTextureWidth = 256; +const unsigned int sTextureHeight = 256; +const unsigned int sCellWidth = 24; +const unsigned int sCellHeight = 24; +const unsigned int sFirstChar = 32; + +const FontBitmapInfo sFixedWidthCompositorFont = { + mozilla::Some(9), + mozilla::Nothing(), + 256, + 256, + 24, + 24, + 32, + consolas_font::sFontPNG, + sizeof(consolas_font::sFontPNG)}; + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ContainerLayerComposite.cpp b/gfx/layers/composite/ContainerLayerComposite.cpp new file mode 100644 index 0000000000..34b91714e0 --- /dev/null +++ b/gfx/layers/composite/ContainerLayerComposite.cpp @@ -0,0 +1,820 @@ +/* -*- 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 "ContainerLayerComposite.h" +#include <algorithm> // for min +#include "FrameMetrics.h" // for FrameMetrics +#include "Units.h" // for LayerRect, LayerPixel, etc +#include "CompositableHost.h" // for CompositableHost +#include "gfxEnv.h" // for gfxEnv +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point, IntPoint +#include "mozilla/gfx/Rect.h" // for IntRect, Rect +#include "mozilla/layers/APZSampler.h" // for APZSampler +#include "mozilla/layers/BSPTree.h" +#include "mozilla/layers/Compositor.h" // for Compositor, etc +#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent +#include "mozilla/layers/CompositorTypes.h" // for DiagnosticFlags::CONTAINER +#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc +#include "mozilla/layers/TextureHost.h" // for CompositingRenderTarget +#include "mozilla/layers/APZUtils.h" // for AsyncTransform +#include "mozilla/layers/LayerManagerCompositeUtils.h" +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/LayersHelpers.h" +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for AutoTArray +#include <stack> +#include "TextRenderer.h" // for TextRenderer +#include <vector> +#include "GeckoProfiler.h" // for GeckoProfiler + +static mozilla::LazyLogModule sGfxCullLog("gfx.culling"); +#define CULLING_LOG(...) MOZ_LOG(sGfxCullLog, LogLevel::Debug, (__VA_ARGS__)) + +#define DUMP(...) \ + do { \ + if (gfxEnv::DumpDebug()) { \ + printf_stderr(__VA_ARGS__); \ + } \ + } while (0) +#define XYWH(k) (k).X(), (k).Y(), (k).Width(), (k).Height() +#define XY(k) (k).X(), (k).Y() +#define WH(k) (k).Width(), (k).Height() + +namespace mozilla { +namespace layers { + +using namespace gfx; + +static void DrawLayerInfo(const RenderTargetIntRect& aClipRect, + LayerManagerComposite* aManager, Layer* aLayer) { + if (aLayer->GetType() == Layer::LayerType::TYPE_CONTAINER) { + // XXX - should figure out a way to render this, but for now this + // is hard to do, since it will often get superimposed over the first + // child of the layer, which is bad. + return; + } + + std::stringstream ss; + aLayer->PrintInfo(ss, ""); + + LayerIntRegion visibleRegion = aLayer->GetVisibleRegion(); + + uint32_t maxWidth = + std::min<uint32_t>(visibleRegion.GetBounds().Width(), 500); + + IntPoint topLeft = visibleRegion.GetBounds().ToUnknownRect().TopLeft(); + aManager->GetTextRenderer()->RenderText( + aManager->GetCompositor(), ss.str().c_str(), topLeft, + aLayer->GetEffectiveTransform(), 16, maxWidth); +} + +static void PrintUniformityInfo(Layer* aLayer) { +#if defined(MOZ_GECKO_PROFILER) + if (!profiler_thread_is_being_profiled()) { + return; + } + + // Don't want to print a log for smaller layers + if (aLayer->GetLocalVisibleRegion().GetBounds().Width() < 300 || + aLayer->GetLocalVisibleRegion().GetBounds().Height() < 300) { + return; + } + + Matrix4x4 transform = aLayer->AsHostLayer()->GetShadowBaseTransform(); + if (!transform.Is2D()) { + return; + } + + Point translation = transform.As2D().GetTranslation(); + + // Contains the translation applied to a 2d layer so we can track the layer + // position at each frame. + struct LayerTranslationMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("LayerTranslation"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter, + ProfileBufferRawPointer<layers::Layer> aLayer, gfx::Point aPoint) { + const size_t bufferSize = 32; + char buffer[bufferSize]; + SprintfLiteral(buffer, "%p", aLayer.mRawPointer); + + aWriter.StringProperty("layer", buffer); + aWriter.IntProperty("x", aPoint.x); + aWriter.IntProperty("y", aPoint.y); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::markerChart, MS::Location::markerTable}; + schema.AddKeyLabelFormat("layer", "Layer", MS::Format::string); + schema.AddKeyLabelFormat("x", "X", MS::Format::integer); + schema.AddKeyLabelFormat("y", "Y", MS::Format::integer); + return schema; + } + }; + + profiler_add_marker("LayerTranslation", geckoprofiler::category::GRAPHICS, {}, + LayerTranslationMarker{}, + WrapProfileBufferRawPointer(aLayer), translation); +#endif +} + +static Maybe<gfx::Polygon> SelectLayerGeometry( + const Maybe<gfx::Polygon>& aParentGeometry, + const Maybe<gfx::Polygon>& aChildGeometry) { + // Both the parent and the child layer were split. + if (aParentGeometry && aChildGeometry) { + return Some(aParentGeometry->ClipPolygon(*aChildGeometry)); + } + + // The parent layer was split. + if (aParentGeometry) { + return aParentGeometry; + } + + // The child layer was split. + if (aChildGeometry) { + return aChildGeometry; + } + + // No split. + return Nothing(); +} + +void TransformLayerGeometry(Layer* aLayer, Maybe<gfx::Polygon>& aGeometry) { + Layer* parent = aLayer; + gfx::Matrix4x4 transform; + + // Collect all parent transforms. + while (parent != nullptr && !parent->Is3DContextLeaf()) { + transform = transform * parent->GetLocalTransform(); + parent = parent->GetParent(); + } + + // Transform the geometry to the parent 3D context leaf coordinate space. + transform = transform.ProjectTo2D(); + + if (!transform.IsSingular()) { + aGeometry->TransformToScreenSpace(transform.Inverse(), transform); + } else { + // Discard the geometry since the result might not be correct. + aGeometry.reset(); + } +} + +template <class ContainerT> +static gfx::IntRect ContainerVisibleRect(ContainerT* aContainer) { + gfx::IntRect surfaceRect = + aContainer->GetLocalVisibleRegion().GetBounds().ToUnknownRect(); + return surfaceRect; +} + +/* all of the per-layer prepared data we need to maintain */ +struct PreparedLayer { + PreparedLayer(Layer* aLayer, RenderTargetIntRect aClipRect, + Maybe<gfx::Polygon>&& aGeometry) + : mLayer(aLayer), mClipRect(aClipRect), mGeometry(std::move(aGeometry)) {} + + RefPtr<Layer> mLayer; + RenderTargetIntRect mClipRect; + Maybe<Polygon> mGeometry; +}; + +/* all of the prepared data that we need in RenderLayer() */ +struct PreparedData { + RefPtr<CompositingRenderTarget> mTmpTarget; + AutoTArray<PreparedLayer, 12> mLayers; + bool mNeedsSurfaceCopy; +}; + +// ContainerPrepare is shared between RefLayer and ContainerLayer +template <class ContainerT> +void ContainerPrepare(ContainerT* aContainer, LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect) { + // We can end up calling prepare multiple times if we duplicated + // layers due to preserve-3d plane splitting. The results + // should be identical, so we only need to do it once. + if (aContainer->mPrepared) { + return; + } + aContainer->mPrepared = MakeUnique<PreparedData>(); + aContainer->mPrepared->mNeedsSurfaceCopy = false; + + const ContainerLayerComposite::SortMode sortMode = + aManager->GetCompositor()->SupportsLayerGeometry() + ? ContainerLayerComposite::SortMode::WITH_GEOMETRY + : ContainerLayerComposite::SortMode::WITHOUT_GEOMETRY; + + nsTArray<LayerPolygon> polygons = + aContainer->SortChildrenBy3DZOrder(sortMode); + + for (LayerPolygon& layer : polygons) { + LayerComposite* layerToRender = + static_cast<LayerComposite*>(layer.layer->ImplData()); + + RenderTargetIntRect clipRect = + layerToRender->GetLayer()->CalculateScissorRect(aClipRect); + + if (layerToRender->GetLayer()->IsBackfaceHidden()) { + continue; + } + + // We don't want to skip container layers because otherwise their mPrepared + // may be null which is not allowed. + if (!layerToRender->GetLayer()->AsContainerLayer()) { + if (!layerToRender->GetLayer()->IsVisible()) { + CULLING_LOG("Sublayer %p has no effective visible region\n", + layerToRender->GetLayer()); + continue; + } + + if (clipRect.IsEmpty()) { + CULLING_LOG("Sublayer %p has an empty world clip rect\n", + layerToRender->GetLayer()); + continue; + } + } + + CULLING_LOG("Preparing sublayer %p\n", layerToRender->GetLayer()); + + layerToRender->Prepare(clipRect); + aContainer->mPrepared->mLayers.AppendElement(PreparedLayer( + layerToRender->GetLayer(), clipRect, std::move(layer.geometry))); + } + + CULLING_LOG("Preparing container layer %p\n", aContainer->GetLayer()); + + /** + * Setup our temporary surface for rendering the contents of this container. + */ + + gfx::IntRect surfaceRect = ContainerVisibleRect(aContainer); + if (surfaceRect.IsEmpty()) { + return; + } + + bool surfaceCopyNeeded; + // DefaultComputeSupportsComponentAlphaChildren can mutate aContainer so call + // it unconditionally + aContainer->DefaultComputeSupportsComponentAlphaChildren(&surfaceCopyNeeded); + if (aContainer->UseIntermediateSurface()) { + if (!surfaceCopyNeeded) { + RefPtr<CompositingRenderTarget> surface = nullptr; + + RefPtr<CompositingRenderTarget>& lastSurf = + aContainer->mLastIntermediateSurface; + if (lastSurf && !aContainer->mChildrenChanged && + lastSurf->GetRect().IsEqualEdges(surfaceRect)) { + surface = lastSurf; + } + + if (!surface) { + // If we don't need a copy we can render to the intermediate now to + // avoid unecessary render target switching. This brings a big perf + // boost on mobile gpus. + surface = CreateOrRecycleTarget(aContainer, aManager); + + MOZ_PERFORMANCE_WARNING( + "gfx", + "[%p] Container layer requires intermediate surface rendering\n", + aContainer); + RenderIntermediate(aContainer, aManager, aClipRect.ToUnknownRect(), + surface); + aContainer->SetChildrenChanged(false); + } + + aContainer->mPrepared->mTmpTarget = surface; + } else { + MOZ_PERFORMANCE_WARNING( + "gfx", "[%p] Container layer requires intermediate surface copy\n", + aContainer); + aContainer->mPrepared->mNeedsSurfaceCopy = true; + aContainer->mLastIntermediateSurface = nullptr; + } + } else { + aContainer->mLastIntermediateSurface = nullptr; + } +} + +template <class ContainerT> +void RenderMinimap(ContainerT* aContainer, const RefPtr<APZSampler>& aSampler, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect, Layer* aLayer) { + Compositor* compositor = aManager->GetCompositor(); + MOZ_ASSERT(aSampler); + + if (aLayer->GetScrollMetadataCount() < 1) { + return; + } + + LayerMetricsWrapper wrapper(aLayer, 0); + if (!wrapper.GetApzc()) { + return; + } + const FrameMetrics& fm = wrapper.Metrics(); + MOZ_ASSERT(fm.IsScrollable()); + + ParentLayerPoint scrollOffset = + aSampler->GetCurrentAsyncScrollOffset(wrapper); + + // Options + const int verticalPadding = 10; + const int horizontalPadding = 5; + gfx::DeviceColor backgroundColor(0.3f, 0.3f, 0.3f, 0.3f); + gfx::DeviceColor tileActiveColor(1, 1, 1, 0.4f); + gfx::DeviceColor tileBorderColor(0, 0, 0, 0.1f); + gfx::DeviceColor pageBorderColor(0, 0, 0); + gfx::DeviceColor criticalDisplayPortColor(1.f, 1.f, 0); + gfx::DeviceColor displayPortColor(0, 1.f, 0); + gfx::DeviceColor layoutPortColor(1.f, 0, 0); + gfx::DeviceColor visualPortColor(0, 0, 1.f, 0.3f); + + // Rects + ParentLayerRect compositionBounds = fm.GetCompositionBounds(); + LayerRect scrollRect = fm.GetScrollableRect() * fm.LayersPixelsPerCSSPixel(); + LayerRect visualRect = + ParentLayerRect(scrollOffset, compositionBounds.Size()) / + LayerToParentLayerScale(1); + LayerRect dp = (fm.GetDisplayPort() + fm.GetLayoutScrollOffset()) * + fm.LayersPixelsPerCSSPixel(); + Maybe<LayerRect> layoutRect; + Maybe<LayerRect> cdp; + if (fm.IsRootContent()) { + CSSRect viewport = aSampler->GetCurrentAsyncLayoutViewport(wrapper); + layoutRect = Some(viewport * fm.LayersPixelsPerCSSPixel()); + } + if (!fm.GetCriticalDisplayPort().IsEmpty()) { + cdp = Some((fm.GetCriticalDisplayPort() + fm.GetLayoutScrollOffset()) * + fm.LayersPixelsPerCSSPixel()); + } + + // Don't render trivial minimap. They can show up from textboxes and other + // tiny frames. + if (visualRect.Width() < 64 && visualRect.Height() < 64) { + return; + } + + // Compute a scale with an appropriate aspect ratio + // We allocate up to 100px of width and the height of this layer. + float scaleFactor; + float scaleFactorX; + float scaleFactorY; + Rect dest = Rect(aClipRect.ToUnknownRect()); + if (aLayer->GetLocalClipRect()) { + dest = Rect(aLayer->GetLocalClipRect().value().ToUnknownRect()); + } else { + dest = aContainer->GetEffectiveTransform().Inverse().TransformBounds(dest); + } + dest = dest.Intersect(compositionBounds.ToUnknownRect()); + scaleFactorX = std::min(100.f, dest.Width() - (2 * horizontalPadding)) / + scrollRect.Width(); + scaleFactorY = (dest.Height() - (2 * verticalPadding)) / scrollRect.Height(); + scaleFactor = std::min(scaleFactorX, scaleFactorY); + if (scaleFactor <= 0) { + return; + } + + Matrix4x4 transform = Matrix4x4::Scaling(scaleFactor, scaleFactor, 1); + transform.PostTranslate(horizontalPadding + dest.X(), + verticalPadding + dest.Y(), 0); + + Rect transformedScrollRect = + transform.TransformBounds(scrollRect.ToUnknownRect()); + + IntRect clipRect = + RoundedOut(aContainer->GetEffectiveTransform().TransformBounds( + transformedScrollRect)); + + // Render the scrollable area. + compositor->FillRect(transformedScrollRect, backgroundColor, clipRect, + aContainer->GetEffectiveTransform()); + compositor->SlowDrawRect(transformedScrollRect, pageBorderColor, clipRect, + aContainer->GetEffectiveTransform()); + + // Render the displayport. + Rect r = transform.TransformBounds(dp.ToUnknownRect()); + compositor->FillRect(r, tileActiveColor, clipRect, + aContainer->GetEffectiveTransform()); + compositor->SlowDrawRect(r, displayPortColor, clipRect, + aContainer->GetEffectiveTransform()); + + // Render the critical displayport if there is one + if (cdp) { + r = transform.TransformBounds(cdp->ToUnknownRect()); + compositor->SlowDrawRect(r, criticalDisplayPortColor, clipRect, + aContainer->GetEffectiveTransform()); + } + + // Render the layout viewport if it exists (which is only in the root + // content APZC). + if (layoutRect) { + r = transform.TransformBounds(layoutRect->ToUnknownRect()); + compositor->SlowDrawRect(r, layoutPortColor, clipRect, + aContainer->GetEffectiveTransform()); + } + + // Render the visual viewport. + r = transform.TransformBounds(visualRect.ToUnknownRect()); + compositor->SlowDrawRect(r, visualPortColor, clipRect, + aContainer->GetEffectiveTransform(), 2); +} + +template <class ContainerT> +void RenderLayers(ContainerT* aContainer, LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) { + Compositor* compositor = aManager->GetCompositor(); + + RefPtr<APZSampler> sampler; + if (CompositorBridgeParent* cbp = compositor->GetCompositorBridgeParent()) { + sampler = cbp->GetAPZSampler(); + } + + for (size_t i = 0u; i < aContainer->mPrepared->mLayers.Length(); i++) { + PreparedLayer& preparedData = aContainer->mPrepared->mLayers[i]; + + const gfx::IntRect clipRect = preparedData.mClipRect.ToUnknownRect(); + LayerComposite* layerToRender = + static_cast<LayerComposite*>(preparedData.mLayer->ImplData()); + const Maybe<gfx::Polygon>& childGeometry = preparedData.mGeometry; + + Layer* layer = layerToRender->GetLayer(); + + if (layerToRender->HasStaleCompositor()) { + continue; + } + + if (StaticPrefs::layers_acceleration_draw_fps()) { + for (const auto& metadata : layer->GetAllScrollMetadata()) { + if (metadata.IsApzForceDisabled()) { + aManager->DisabledApzWarning(); + break; + } + } + } + + if (layerToRender->HasLayerBeenComposited()) { + // Composer2D will compose this layer so skip GPU composition + // this time. The flag will be reset for the next composition phase + // at the beginning of LayerManagerComposite::Rener(). + gfx::IntRect clearRect = layerToRender->GetClearRect(); + if (!clearRect.IsEmpty()) { + // Clear layer's visible rect on FrameBuffer with transparent pixels + gfx::Rect fbRect(clearRect.X(), clearRect.Y(), clearRect.Width(), + clearRect.Height()); + compositor->ClearRect(fbRect); + layerToRender->SetClearRect(gfx::IntRect(0, 0, 0, 0)); + } + } else { + // Since we force an intermediate surface for nested 3D contexts, + // aGeometry and childGeometry are both in the same coordinate space. + Maybe<gfx::Polygon> geometry = + SelectLayerGeometry(aGeometry, childGeometry); + + // If we are dealing with a nested 3D context, we might need to transform + // the geometry back to the coordinate space of the current layer before + // rendering the layer. + ContainerLayer* container = layer->AsContainerLayer(); + const bool isLeafLayer = + !container || container->UseIntermediateSurface(); + + if (geometry && isLeafLayer) { + TransformLayerGeometry(layer, geometry); + } + + layerToRender->RenderLayer(clipRect, geometry); + } + + if (StaticPrefs::layers_uniformity_info_AtStartup()) { + PrintUniformityInfo(layer); + } + + if (StaticPrefs::layers_draw_layer_info()) { + DrawLayerInfo(preparedData.mClipRect, aManager, layer); + } + + // Draw a border around scrollable layers. + // A layer can be scrolled by multiple scroll frames. Draw a border + // for each. + // Within the list of scroll frames for a layer, the layer border for a + // scroll frame lower down is affected by the async transforms on scroll + // frames higher up, so loop from the top down, and accumulate an async + // transform as we go along. + Matrix4x4 asyncTransform; + if (sampler) { + for (uint32_t i = layer->GetScrollMetadataCount(); i > 0; --i) { + LayerMetricsWrapper wrapper(layer, i - 1); + if (wrapper.GetApzc()) { + MOZ_ASSERT(wrapper.Metrics().IsScrollable()); + // Since the composition bounds are in the parent layer's coordinates, + // use the parent's effective transform rather than the layer's own. + ParentLayerRect compositionBounds = + wrapper.Metrics().GetCompositionBounds(); + aManager->GetCompositor()->DrawDiagnostics( + DiagnosticFlags::CONTAINER, compositionBounds.ToUnknownRect(), + aClipRect.ToUnknownRect(), + asyncTransform * aContainer->GetEffectiveTransform()); + asyncTransform = + sampler->GetCurrentAsyncTransformWithOverscroll(wrapper) + .ToUnknownMatrix() * + asyncTransform; + } + } + + if (StaticPrefs::apz_minimap_enabled()) { + RenderMinimap(aContainer, sampler, aManager, aClipRect, layer); + } + } + + // invariant: our GL context should be current here, I don't think we can + // assert it though + } +} + +template <class ContainerT> +RefPtr<CompositingRenderTarget> CreateOrRecycleTarget( + ContainerT* aContainer, LayerManagerComposite* aManager) { + Compositor* compositor = aManager->GetCompositor(); + SurfaceInitMode mode = INIT_MODE_CLEAR; + gfx::IntRect surfaceRect = ContainerVisibleRect(aContainer); + if (aContainer->GetLocalVisibleRegion().GetNumRects() == 1 && + (aContainer->GetContentFlags() & Layer::CONTENT_OPAQUE)) { + mode = INIT_MODE_NONE; + } + + RefPtr<CompositingRenderTarget>& lastSurf = + aContainer->mLastIntermediateSurface; + if (lastSurf && lastSurf->GetRect().IsEqualEdges(surfaceRect)) { + if (mode == INIT_MODE_CLEAR) { + lastSurf->ClearOnBind(); + } + + return lastSurf; + } else { + lastSurf = compositor->CreateRenderTarget(surfaceRect, mode); + + return lastSurf; + } +} + +template <class ContainerT> +RefPtr<CompositingRenderTarget> CreateTemporaryTargetAndCopyFromBackground( + ContainerT* aContainer, LayerManagerComposite* aManager) { + Compositor* compositor = aManager->GetCompositor(); + gfx::IntRect visibleRect = + aContainer->GetLocalVisibleRegion().GetBounds().ToUnknownRect(); + RefPtr<CompositingRenderTarget> previousTarget = + compositor->GetCurrentRenderTarget(); + gfx::IntRect surfaceRect = + gfx::IntRect(visibleRect.X(), visibleRect.Y(), visibleRect.Width(), + visibleRect.Height()); + + gfx::IntPoint sourcePoint = gfx::IntPoint(visibleRect.X(), visibleRect.Y()); + + gfx::Matrix4x4 transform = aContainer->GetEffectiveTransform(); + DebugOnly<gfx::Matrix> transform2d; + MOZ_ASSERT(transform.Is2D(&transform2d) && + !gfx::ThebesMatrix(transform2d).HasNonIntegerTranslation()); + sourcePoint += gfx::IntPoint::Truncate(transform._41, transform._42); + + sourcePoint -= previousTarget->GetOrigin(); + + return compositor->CreateRenderTargetFromSource(surfaceRect, previousTarget, + sourcePoint); +} + +template <class ContainerT> +void RenderIntermediate(ContainerT* aContainer, LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect, + RefPtr<CompositingRenderTarget> surface) { + Compositor* compositor = aManager->GetCompositor(); + RefPtr<CompositingRenderTarget> previousTarget = + compositor->GetCurrentRenderTarget(); + + if (!surface) { + return; + } + + compositor->SetRenderTarget(surface); + // pre-render all of the layers into our temporary + RenderLayers(aContainer, aManager, + RenderTargetIntRect::FromUnknownRect(aClipRect), Nothing()); + + // Unbind the current surface and rebind the previous one. + compositor->SetRenderTarget(previousTarget); +} + +template <class ContainerT> +void ContainerRender(ContainerT* aContainer, LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) { + MOZ_ASSERT(aContainer->mPrepared); + + if (aContainer->UseIntermediateSurface()) { + RefPtr<CompositingRenderTarget> surface; + + if (aContainer->mPrepared->mNeedsSurfaceCopy) { + // we needed to copy the background so we waited until now to render the + // intermediate + surface = + CreateTemporaryTargetAndCopyFromBackground(aContainer, aManager); + RenderIntermediate(aContainer, aManager, aClipRect, surface); + } else { + surface = aContainer->mPrepared->mTmpTarget; + } + + if (!surface) { + return; + } + + gfx::Rect visibleRect( + aContainer->GetLocalVisibleRegion().GetBounds().ToUnknownRect()); + + RefPtr<Compositor> compositor = aManager->GetCompositor(); +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpCompositorTextures()) { + RefPtr<gfx::DataSourceSurface> surf = surface->Dump(compositor); + if (surf) { + WriteSnapshotToDumpFile(aContainer, surf); + } + } +#endif + + RefPtr<ContainerT> container = aContainer; + RenderWithAllMasks(aContainer, compositor, aClipRect, + [&, surface, compositor, container]( + EffectChain& effectChain, const IntRect& clipRect) { + effectChain.mPrimaryEffect = + new EffectRenderTarget(surface); + + compositor->DrawGeometry( + visibleRect, clipRect, effectChain, + container->GetEffectiveOpacity(), + container->GetEffectiveTransform(), aGeometry); + }); + + } else { + RenderLayers(aContainer, aManager, + RenderTargetIntRect::FromUnknownRect(aClipRect), aGeometry); + } + + // If it is a scrollable container layer with no child layers, and one of the + // APZCs attached to it has a nonempty async transform, then that transform is + // not applied to any visible content. Display a warning box (conditioned on + // the FPS display being enabled). + if (StaticPrefs::layers_acceleration_draw_fps() && + aContainer->IsScrollableWithoutContent()) { + RefPtr<APZSampler> sampler = + aManager->GetCompositor()->GetCompositorBridgeParent()->GetAPZSampler(); + // Since aContainer doesn't have any children we can just iterate from the + // top metrics on it down to the bottom using GetFirstChild and not worry + // about walking onto another underlying layer. + for (LayerMetricsWrapper i(aContainer); i; i = i.GetFirstChild()) { + if (sampler->HasUnusedAsyncTransform(i)) { + aManager->UnusedApzTransformWarning(); + break; + } + } + } +} + +ContainerLayerComposite::ContainerLayerComposite( + LayerManagerComposite* aManager) + : ContainerLayer(aManager, nullptr), LayerComposite(aManager) { + MOZ_COUNT_CTOR(ContainerLayerComposite); + mImplData = static_cast<LayerComposite*>(this); +} + +ContainerLayerComposite::~ContainerLayerComposite() { + MOZ_COUNT_DTOR(ContainerLayerComposite); + + // We don't Destroy() on destruction here because this destructor + // can be called after remote content has crashed, and it may not be + // safe to free the IPC resources of our children. Those resources + // are automatically cleaned up by IPDL-generated code. + // + // In the common case of normal shutdown, either + // LayerManagerComposite::Destroy(), a parent + // *ContainerLayerComposite::Destroy(), or Disconnect() will trigger + // cleanup of our resources. + RemoveAllChildren(); +} + +void ContainerLayerComposite::Destroy() { + if (!mDestroyed) { + while (mFirstChild) { + GetFirstChildComposite()->Destroy(); + RemoveChild(mFirstChild); + } + mDestroyed = true; + } +} + +LayerComposite* ContainerLayerComposite::GetFirstChildComposite() { + if (!mFirstChild) { + return nullptr; + } + return static_cast<LayerComposite*>(mFirstChild->AsHostLayer()); +} + +void ContainerLayerComposite::Cleanup() { + mPrepared = nullptr; + + for (Layer* l = GetFirstChild(); l; l = l->GetNextSibling()) { + static_cast<LayerComposite*>(l->AsHostLayer())->Cleanup(); + } +} + +void ContainerLayerComposite::RenderLayer( + const gfx::IntRect& aClipRect, const Maybe<gfx::Polygon>& aGeometry) { + ContainerRender(this, mCompositeManager, aClipRect, aGeometry); +} + +void ContainerLayerComposite::Prepare(const RenderTargetIntRect& aClipRect) { + ContainerPrepare(this, mCompositeManager, aClipRect); +} + +void ContainerLayerComposite::CleanupResources() { + mLastIntermediateSurface = nullptr; + mPrepared = nullptr; + + for (Layer* l = GetFirstChild(); l; l = l->GetNextSibling()) { + static_cast<LayerComposite*>(l->AsHostLayer())->CleanupResources(); + } +} + +const LayerIntRegion& ContainerLayerComposite::GetShadowVisibleRegion() { + if (!UseIntermediateSurface()) { + RecomputeShadowVisibleRegionFromChildren(); + } + + return mShadowVisibleRegion; +} + +const LayerIntRegion& RefLayerComposite::GetShadowVisibleRegion() { + if (!UseIntermediateSurface()) { + RecomputeShadowVisibleRegionFromChildren(); + } + + return mShadowVisibleRegion; +} + +RefLayerComposite::RefLayerComposite(LayerManagerComposite* aManager) + : RefLayer(aManager, nullptr), LayerComposite(aManager) { + mImplData = static_cast<LayerComposite*>(this); +} + +RefLayerComposite::~RefLayerComposite() { Destroy(); } + +void RefLayerComposite::Destroy() { + MOZ_ASSERT(!mFirstChild); + mDestroyed = true; +} + +LayerComposite* RefLayerComposite::GetFirstChildComposite() { + if (!mFirstChild) { + return nullptr; + } + return static_cast<LayerComposite*>(mFirstChild->AsHostLayer()); +} + +void RefLayerComposite::RenderLayer(const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) { + ContainerRender(this, mCompositeManager, aClipRect, aGeometry); +} + +void RefLayerComposite::Prepare(const RenderTargetIntRect& aClipRect) { + ContainerPrepare(this, mCompositeManager, aClipRect); +} + +void RefLayerComposite::Cleanup() { + mPrepared = nullptr; + + for (Layer* l = GetFirstChild(); l; l = l->GetNextSibling()) { + static_cast<LayerComposite*>(l->AsHostLayer())->Cleanup(); + } +} + +void RefLayerComposite::CleanupResources() { + mLastIntermediateSurface = nullptr; + mPrepared = nullptr; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ContainerLayerComposite.h b/gfx/layers/composite/ContainerLayerComposite.h new file mode 100644 index 0000000000..68ad26db2f --- /dev/null +++ b/gfx/layers/composite/ContainerLayerComposite.h @@ -0,0 +1,197 @@ +/* -*- 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/. */ + +#ifndef GFX_ContainerLayerComposite_H +#define GFX_ContainerLayerComposite_H + +#include "Layers.h" // for Layer (ptr only), etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/gfx/Rect.h" + +namespace mozilla { +namespace layers { + +class APZSampler; +class CompositableHost; +class CompositingRenderTarget; +struct PreparedData; + +class ContainerLayerComposite : public ContainerLayer, public LayerComposite { + template <class ContainerT> + friend void ContainerPrepare(ContainerT* aContainer, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect); + template <class ContainerT> + friend void ContainerRender(ContainerT* aContainer, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry); + template <class ContainerT> + friend void RenderLayers(ContainerT* aContainer, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry); + template <class ContainerT> + friend void RenderIntermediate(ContainerT* aContainer, + LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect, + RefPtr<CompositingRenderTarget> surface); + template <class ContainerT> + friend RefPtr<CompositingRenderTarget> + CreateTemporaryTargetAndCopyFromBackground( + ContainerT* aContainer, LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect); + template <class ContainerT> + friend RefPtr<CompositingRenderTarget> CreateOrRecycleTarget( + ContainerT* aContainer, LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect); + + template <class ContainerT> + void RenderMinimap(ContainerT* aContainer, const RefPtr<APZSampler>& aSampler, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect, Layer* aLayer); + + public: + explicit ContainerLayerComposite(LayerManagerComposite* aManager); + + protected: + virtual ~ContainerLayerComposite(); + + public: + // LayerComposite Implementation + Layer* GetLayer() override { return this; } + + void SetLayerManager(HostLayerManager* aManager) override { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + mLastIntermediateSurface = nullptr; + } + + void Destroy() override; + + LayerComposite* GetFirstChildComposite() override; + + void Cleanup() override; + + void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) override; + + void Prepare(const RenderTargetIntRect& aClipRect) override; + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } + + const LayerIntRegion& GetShadowVisibleRegion() override; + + void CleanupResources() override; + + HostLayer* AsHostLayer() override { return this; } + + // container layers don't use a compositable + CompositableHost* GetCompositableHost() override { return nullptr; } + + // If the layer has a pres shell resolution, add a post-scale + // to the layer's transform equal to the pres shell resolution we're + // scaling to. This cancels out the post scale of '1 / resolution' + // added by Layout. TODO: It would be nice to get rid of both of these + // post-scales. + float GetPostXScale() const override { + return mSimpleAttrs.GetPostXScale() * mPresShellResolution; + } + float GetPostYScale() const override { + return mSimpleAttrs.GetPostYScale() * mPresShellResolution; + } + + const char* Name() const override { return "ContainerLayerComposite"; } + UniquePtr<PreparedData> mPrepared; + + RefPtr<CompositingRenderTarget> mLastIntermediateSurface; +}; + +class RefLayerComposite : public RefLayer, public LayerComposite { + template <class ContainerT> + friend void ContainerPrepare(ContainerT* aContainer, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect); + template <class ContainerT> + friend void ContainerRender(ContainerT* aContainer, + LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry); + template <class ContainerT> + friend void RenderLayers(ContainerT* aContainer, + LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry); + template <class ContainerT> + friend void RenderIntermediate(ContainerT* aContainer, + LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect, + RefPtr<CompositingRenderTarget> surface); + template <class ContainerT> + friend RefPtr<CompositingRenderTarget> + CreateTemporaryTargetAndCopyFromBackground(ContainerT* aContainer, + LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect); + template <class ContainerT> + friend RefPtr<CompositingRenderTarget> CreateTemporaryTarget( + ContainerT* aContainer, LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect); + + public: + explicit RefLayerComposite(LayerManagerComposite* aManager); + + protected: + virtual ~RefLayerComposite(); + + public: + /** LayerOGL implementation */ + Layer* GetLayer() override { return this; } + + void SetLayerManager(HostLayerManager* aManager) override { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + mLastIntermediateSurface = nullptr; + } + + void Destroy() override; + + LayerComposite* GetFirstChildComposite() override; + + void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) override; + + void Prepare(const RenderTargetIntRect& aClipRect) override; + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } + + const LayerIntRegion& GetShadowVisibleRegion() override; + + void Cleanup() override; + + void CleanupResources() override; + + HostLayer* AsHostLayer() override { return this; } + + // ref layers don't use a compositable + CompositableHost* GetCompositableHost() override { return nullptr; } + + const char* Name() const override { return "RefLayerComposite"; } + UniquePtr<PreparedData> mPrepared; + RefPtr<CompositingRenderTarget> mLastIntermediateSurface; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_ContainerLayerComposite_H */ diff --git a/gfx/layers/composite/ContentHost.cpp b/gfx/layers/composite/ContentHost.cpp new file mode 100644 index 0000000000..d4be5b0dc7 --- /dev/null +++ b/gfx/layers/composite/ContentHost.cpp @@ -0,0 +1,460 @@ +/* -*- 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 "mozilla/layers/ContentHost.h" +#include "gfx2DGlue.h" // for ContentForFormat +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/Effects.h" // for TexturedEffect, Effect, etc +#include "mozilla/layers/LayersMessages.h" // for ThebesBufferData +#include "nsAString.h" +#include "nsPrintfCString.h" // for nsPrintfCString +#include "nsString.h" // for nsAutoCString +#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL + +namespace mozilla { +using namespace gfx; + +namespace layers { + +ContentHostBase::ContentHostBase(const TextureInfo& aTextureInfo) + : ContentHost(aTextureInfo), mInitialised(false) {} + +ContentHostBase::~ContentHostBase() = default; + +void ContentHostTexture::Composite( + Compositor* aCompositor, LayerComposite* aLayer, EffectChain& aEffectChain, + float aOpacity, const gfx::Matrix4x4& aTransform, + const SamplingFilter aSamplingFilter, const IntRect& aClipRect, + const nsIntRegion* aVisibleRegion, const Maybe<gfx::Polygon>& aGeometry) { + NS_ASSERTION(aVisibleRegion, "Requires a visible region"); + + AutoLockCompositableHost lock(this); + if (lock.Failed()) { + return; + } + + if (!mTextureHost->BindTextureSource(mTextureSource)) { + return; + } + MOZ_ASSERT(mTextureSource.get()); + + if (!mTextureHostOnWhite) { + mTextureSourceOnWhite = nullptr; + } + if (mTextureHostOnWhite && + !mTextureHostOnWhite->BindTextureSource(mTextureSourceOnWhite)) { + return; + } + + RefPtr<TexturedEffect> effect = CreateTexturedEffect( + mTextureSource.get(), mTextureSourceOnWhite.get(), aSamplingFilter, true); + if (!effect) { + return; + } + + aEffectChain.mPrimaryEffect = effect; + + nsIntRegion tmpRegion; + const nsIntRegion* renderRegion; +#ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE + if (PaintWillResample()) { + // If we're resampling, then the texture image will contain exactly the + // entire visible region's bounds, and we should draw it all in one quad + // to avoid unexpected aliasing. + tmpRegion = aVisibleRegion->GetBounds(); + renderRegion = &tmpRegion; + } else { + renderRegion = aVisibleRegion; + } +#else + renderRegion = aVisibleRegion; +#endif + + nsIntRegion region(*renderRegion); + nsIntPoint origin = GetOriginOffset(); + // translate into TexImage space, buffer origin might not be at texture (0,0) + region.MoveBy(-origin); + + // Figure out the intersecting draw region + gfx::IntSize texSize = mTextureSource->GetSize(); + IntRect textureRect = IntRect(0, 0, texSize.width, texSize.height); + textureRect.MoveBy(region.GetBounds().TopLeft()); + nsIntRegion subregion; + subregion.And(region, textureRect); + if (subregion.IsEmpty()) { + // Region is empty, nothing to draw + return; + } + + nsIntRegion screenRects; + nsIntRegion regionRects; + + // Collect texture/screen coordinates for drawing + for (auto iter = subregion.RectIter(); !iter.Done(); iter.Next()) { + IntRect regionRect = iter.Get(); + IntRect screenRect = iter.Get(); + screenRect.MoveBy(origin); + + screenRects.Or(screenRects, screenRect); + regionRects.Or(regionRects, regionRect); + } + + BigImageIterator* bigImgIter = mTextureSource->AsBigImageIterator(); + BigImageIterator* iterOnWhite = nullptr; + if (bigImgIter) { + bigImgIter->BeginBigImageIteration(); + } + + if (mTextureSourceOnWhite) { + iterOnWhite = mTextureSourceOnWhite->AsBigImageIterator(); + MOZ_ASSERT(!bigImgIter || + bigImgIter->GetTileCount() == iterOnWhite->GetTileCount(), + "Tile count mismatch on component alpha texture"); + if (iterOnWhite) { + iterOnWhite->BeginBigImageIteration(); + } + } + + bool usingTiles = (bigImgIter && bigImgIter->GetTileCount() > 1); + do { + if (iterOnWhite && bigImgIter) { + MOZ_ASSERT(iterOnWhite->GetTileRect() == bigImgIter->GetTileRect(), + "component alpha textures should be the same size."); + } + + IntRect texRect = bigImgIter ? bigImgIter->GetTileRect() + : IntRect(0, 0, texSize.width, texSize.height); + + // Draw texture. If we're using tiles, we do repeating manually, as texture + // repeat would cause each individual tile to repeat instead of the + // compound texture as a whole. This involves drawing at most 4 sections, + // 2 for each axis that has texture repeat. + for (int y = 0; y < (usingTiles ? 2 : 1); y++) { + for (int x = 0; x < (usingTiles ? 2 : 1); x++) { + IntRect currentTileRect(texRect); + currentTileRect.MoveBy(x * texSize.width, y * texSize.height); + + for (auto screenIter = screenRects.RectIter(), + regionIter = regionRects.RectIter(); + !screenIter.Done() && !regionIter.Done(); + screenIter.Next(), regionIter.Next()) { + const IntRect& screenRect = screenIter.Get(); + const IntRect& regionRect = regionIter.Get(); + IntRect tileScreenRect(screenRect); + IntRect tileRegionRect(regionRect); + + // When we're using tiles, find the intersection between the tile + // rect and this region rect. Tiling is then handled by the + // outer for-loops and modifying the tile rect. + if (usingTiles) { + tileScreenRect.MoveBy(-origin); + tileScreenRect = tileScreenRect.Intersect(currentTileRect); + tileScreenRect.MoveBy(origin); + + if (tileScreenRect.IsEmpty()) continue; + + tileRegionRect = regionRect.Intersect(currentTileRect); + tileRegionRect.MoveBy(-currentTileRect.TopLeft()); + } + gfx::Rect rect(tileScreenRect.X(), tileScreenRect.Y(), + tileScreenRect.Width(), tileScreenRect.Height()); + + effect->mTextureCoords = + Rect(Float(tileRegionRect.X()) / texRect.Width(), + Float(tileRegionRect.Y()) / texRect.Height(), + Float(tileRegionRect.Width()) / texRect.Width(), + Float(tileRegionRect.Height()) / texRect.Height()); + + aCompositor->DrawGeometry(rect, aClipRect, aEffectChain, aOpacity, + aTransform, aGeometry); + + if (usingTiles) { + DiagnosticFlags diagnostics = + DiagnosticFlags::CONTENT | DiagnosticFlags::BIGIMAGE; + if (iterOnWhite) { + diagnostics |= DiagnosticFlags::COMPONENT_ALPHA; + } + aCompositor->DrawDiagnostics(diagnostics, rect, aClipRect, + aTransform, mFlashCounter); + } + } + } + } + + if (iterOnWhite) { + iterOnWhite->NextTile(); + } + } while (usingTiles && bigImgIter->NextTile()); + + if (bigImgIter) { + bigImgIter->EndBigImageIteration(); + } + if (iterOnWhite) { + iterOnWhite->EndBigImageIteration(); + } + + DiagnosticFlags diagnostics = DiagnosticFlags::CONTENT; + if (iterOnWhite) { + diagnostics |= DiagnosticFlags::COMPONENT_ALPHA; + } + aCompositor->DrawDiagnostics(diagnostics, nsIntRegion(mBufferRect), aClipRect, + aTransform, mFlashCounter); +} + +RefPtr<TextureSource> ContentHostTexture::AcquireTextureSource() { + if (!mTextureHost || !mTextureHost->AcquireTextureSource(mTextureSource)) { + return nullptr; + } + return mTextureSource.get(); +} + +RefPtr<TextureSource> ContentHostTexture::AcquireTextureSourceOnWhite() { + if (!mTextureHostOnWhite || + !mTextureHostOnWhite->AcquireTextureSource(mTextureSourceOnWhite)) { + return nullptr; + } + return mTextureSourceOnWhite.get(); +} + +void ContentHostTexture::UseTextureHost( + const nsTArray<TimedTexture>& aTextures) { + ContentHostBase::UseTextureHost(aTextures); + MOZ_ASSERT(aTextures.Length() == 1); + const TimedTexture& t = aTextures[0]; + MOZ_ASSERT(t.mPictureRect.IsEqualInterior( + nsIntRect(nsIntPoint(0, 0), nsIntSize(t.mTexture->GetSize()))), + "Only default picture rect supported"); + + if (t.mTexture != mTextureHost) { + mReceivedNewHost = true; + } + + mTextureHost = t.mTexture; + mTextureHostOnWhite = nullptr; + mTextureSourceOnWhite = nullptr; + if (mTextureHost) { + mTextureHost->PrepareTextureSource(mTextureSource); + } +} + +void ContentHostTexture::UseComponentAlphaTextures( + TextureHost* aTextureOnBlack, TextureHost* aTextureOnWhite) { + ContentHostBase::UseComponentAlphaTextures(aTextureOnBlack, aTextureOnWhite); + mTextureHost = aTextureOnBlack; + mTextureHostOnWhite = aTextureOnWhite; + if (mTextureHost) { + mTextureHost->PrepareTextureSource(mTextureSource); + } + if (mTextureHostOnWhite) { + mTextureHostOnWhite->PrepareTextureSource(mTextureSourceOnWhite); + } +} + +void ContentHostTexture::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + ContentHostBase::SetTextureSourceProvider(aProvider); + if (mTextureHost) { + mTextureHost->SetTextureSourceProvider(aProvider); + } + if (mTextureHostOnWhite) { + mTextureHostOnWhite->SetTextureSourceProvider(aProvider); + } +} + +void ContentHostTexture::Dump(std::stringstream& aStream, const char* aPrefix, + bool aDumpHtml) { +#ifdef MOZ_DUMP_PAINTING + if (aDumpHtml) { + aStream << "<ul>"; + } + if (mTextureHost) { + aStream << aPrefix; + if (aDumpHtml) { + aStream << "<li> <a href="; + } else { + aStream << "Front buffer: "; + } + DumpTextureHost(aStream, mTextureHost); + if (aDumpHtml) { + aStream << "> Front buffer </a></li> "; + } else { + aStream << "\n"; + } + } + if (mTextureHostOnWhite) { + aStream << aPrefix; + if (aDumpHtml) { + aStream << "<li> <a href="; + } else { + aStream << "Front buffer on white: "; + } + DumpTextureHost(aStream, mTextureHostOnWhite); + if (aDumpHtml) { + aStream << "> Front buffer on white </a> </li> "; + } else { + aStream << "\n"; + } + } + if (aDumpHtml) { + aStream << "</ul>"; + } +#endif +} + +static inline void AddWrappedRegion(const nsIntRegion& aInput, + nsIntRegion& aOutput, const IntSize& aSize, + const nsIntPoint& aShift) { + nsIntRegion tempRegion; + tempRegion.And(IntRect(aShift, aSize), aInput); + tempRegion.MoveBy(-aShift); + aOutput.Or(aOutput, tempRegion); +} + +bool ContentHostSingleBuffered::UpdateThebes( + const ThebesBufferData& aData, const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) { + if (!mTextureHost) { + mInitialised = false; + return true; // FIXME should we return false? Returning true for now + } // to preserve existing behavior of NOT causing IPC errors. + + // updated is in screen coordinates. Convert it to buffer coordinates. + nsIntRegion destRegion(aUpdated); + + if (mReceivedNewHost) { + destRegion.Or(destRegion, aOldValidRegionBack); + mReceivedNewHost = false; + } + destRegion.MoveBy(-aData.rect().TopLeft()); + + if (!aData.rect().Contains(aUpdated.GetBounds()) || + aData.rotation().x > aData.rect().Width() || + aData.rotation().y > aData.rect().Height()) { + NS_ERROR("Invalid update data"); + return false; + } + + // destRegion is now in logical coordinates relative to the buffer, but we + // need to account for rotation. We do that by moving the region to the + // rotation offset and then wrapping any pixels that extend off the + // bottom/right edges. + + // Shift to the rotation point + destRegion.MoveBy(aData.rotation()); + + IntSize bufferSize = aData.rect().Size(); + + // Select only the pixels that are still within the buffer. + nsIntRegion finalRegion; + finalRegion.And(IntRect(IntPoint(), bufferSize), destRegion); + + // For each of the overlap areas (right, bottom-right, bottom), select those + // pixels and wrap them around to the opposite edge of the buffer rect. + AddWrappedRegion(destRegion, finalRegion, bufferSize, + nsIntPoint(aData.rect().Width(), 0)); + AddWrappedRegion(destRegion, finalRegion, bufferSize, + nsIntPoint(aData.rect().Width(), aData.rect().Height())); + AddWrappedRegion(destRegion, finalRegion, bufferSize, + nsIntPoint(0, aData.rect().Height())); + + MOZ_ASSERT(IntRect(0, 0, aData.rect().Width(), aData.rect().Height()) + .Contains(finalRegion.GetBounds())); + + mTextureHost->Updated(&finalRegion); + if (mTextureHostOnWhite) { + mTextureHostOnWhite->Updated(&finalRegion); + } + mInitialised = true; + + mBufferRect = aData.rect(); + mBufferRotation = aData.rotation(); + + return true; +} + +bool ContentHostDoubleBuffered::UpdateThebes( + const ThebesBufferData& aData, const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) { + if (!mTextureHost) { + mInitialised = false; + return true; + } + + // We don't need to calculate an update region because we assume that if we + // are using double buffering then we have render-to-texture and thus no + // upload to do. + mTextureHost->Updated(); + if (mTextureHostOnWhite) { + mTextureHostOnWhite->Updated(); + } + mInitialised = true; + + mBufferRect = aData.rect(); + mBufferRotation = aData.rotation(); + + // Save the current valid region of our front buffer, because if + // we're double buffering, it's going to be the valid region for the + // next back buffer sent back to the renderer. + // + // NB: we rely here on the fact that mValidRegion is initialized to + // empty, and that the first time Swap() is called we don't have a + // valid front buffer that we're going to return to content. + mValidRegionForNextBackBuffer = aOldValidRegionBack; + + return true; +} + +void ContentHostTexture::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("ContentHost (0x%p)", this).get() + << " [buffer-rect=" << mBufferRect << "]" + << " [buffer-rotation=" << mBufferRotation << "]"; + if (PaintWillResample()) { + aStream << " [paint-will-resample]"; + } + + if (mTextureHost) { + nsAutoCString pfx(aPrefix); + pfx += " "; + + aStream << "\n"; + mTextureHost->PrintInfo(aStream, pfx.get()); + } +} + +already_AddRefed<TexturedEffect> ContentHostTexture::GenEffect( + const gfx::SamplingFilter aSamplingFilter) { + if (!mTextureHost) { + return nullptr; + } + if (!mTextureHost->BindTextureSource(mTextureSource)) { + return nullptr; + } + if (!mTextureHostOnWhite) { + mTextureSourceOnWhite = nullptr; + } + if (mTextureHostOnWhite && + !mTextureHostOnWhite->BindTextureSource(mTextureSourceOnWhite)) { + return nullptr; + } + return CreateTexturedEffect(mTextureSource.get(), mTextureSourceOnWhite.get(), + aSamplingFilter, true); +} + +already_AddRefed<gfx::DataSourceSurface> ContentHostTexture::GetAsSurface() { + if (!mTextureHost) { + return nullptr; + } + + return mTextureHost->GetAsSurface(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ContentHost.h b/gfx/layers/composite/ContentHost.h new file mode 100644 index 0000000000..6833ebd74f --- /dev/null +++ b/gfx/layers/composite/ContentHost.h @@ -0,0 +1,236 @@ +/* -*- 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/. */ + +#ifndef GFX_CONTENTHOST_H +#define GFX_CONTENTHOST_H + +#include <stdint.h> // for uint32_t +#include <stdio.h> // for FILE +#include "mozilla-config.h" // for MOZ_DUMP_PAINTING +#include "CompositableHost.h" // for CompositableHost, etc +#include "RotatedBuffer.h" // for RotatedBuffer, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/MatrixFwd.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Polygon.h" // for Polygon +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for SamplingFilter +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/ContentClient.h" // for ContentClient +#include "mozilla/layers/ISurfaceAllocator.h" // for ISurfaceAllocator +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/LayersTypes.h" // for etc +#include "mozilla/layers/TextureHost.h" // for TextureHost +#include "mozilla/mozalloc.h" // for operator delete +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for nsTArray +#include "nscore.h" // for nsACString + +namespace mozilla { +namespace layers { +class Compositor; +class ThebesBufferData; +struct EffectChain; + +struct TexturedEffect; + +/** + * ContentHosts are used for compositing Painted layers, always matched by a + * ContentClient of the same type. + * + * ContentHosts support only UpdateThebes(), not Update(). + */ +class ContentHost : public CompositableHost { + public: + virtual bool UpdateThebes( + const ThebesBufferData& aData, const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) override = 0; + + virtual void SetPaintWillResample(bool aResample) { + mPaintWillResample = aResample; + } + bool PaintWillResample() { return mPaintWillResample; } + + // We use this to allow TiledContentHost to invalidate regions where + // tiles are fading in. + virtual void AddAnimationInvalidation(nsIntRegion& aRegion) {} + + virtual gfx::IntRect GetBufferRect() { + MOZ_ASSERT_UNREACHABLE("Must be implemented in derived class"); + return gfx::IntRect(); + } + + ContentHost* AsContentHost() override { return this; } + + protected: + explicit ContentHost(const TextureInfo& aTextureInfo) + : CompositableHost(aTextureInfo), mPaintWillResample(false) {} + + bool mPaintWillResample; +}; + +/** + * Base class for non-tiled ContentHosts. + * + * Ownership of the SurfaceDescriptor and the resources it represents is passed + * from the ContentClient to the ContentHost when the TextureClient/Hosts are + * created, that is recevied here by SetTextureHosts which assigns one or two + * texture hosts (for single and double buffering) to the ContentHost. + * + * It is the responsibility of the ContentHost to destroy its resources when + * they are recreated or the ContentHost dies. + */ +class ContentHostBase : public ContentHost { + public: + typedef ContentClient::ContentType ContentType; + typedef ContentClient::PaintState PaintState; + + explicit ContentHostBase(const TextureInfo& aTextureInfo); + virtual ~ContentHostBase(); + + gfx::IntRect GetBufferRect() override { return mBufferRect; } + + virtual nsIntPoint GetOriginOffset() { + return mBufferRect.TopLeft() - mBufferRotation; + } + + gfx::IntPoint GetBufferRotation() { return mBufferRotation.ToUnknownPoint(); } + + protected: + gfx::IntRect mBufferRect; + nsIntPoint mBufferRotation; + bool mInitialised; +}; + +/** + * Shared ContentHostBase implementation for content hosts that + * use up to two TextureHosts. + */ +class ContentHostTexture : public ContentHostBase { + public: + explicit ContentHostTexture(const TextureInfo& aTextureInfo) + : ContentHostBase(aTextureInfo), + mLocked(false), + mReceivedNewHost(false) {} + + void Composite(Compositor* aCompositor, LayerComposite* aLayer, + EffectChain& aEffectChain, float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr, + const Maybe<gfx::Polygon>& aGeometry = Nothing()) override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override; + + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false) override; + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void UseTextureHost(const nsTArray<TimedTexture>& aTextures) override; + void UseComponentAlphaTextures(TextureHost* aTextureOnBlack, + TextureHost* aTextureOnWhite) override; + + bool Lock() override { + MOZ_ASSERT(!mLocked); + if (!mTextureHost) { + return false; + } + if (!mTextureHost->Lock()) { + return false; + } + + if (mTextureHostOnWhite && !mTextureHostOnWhite->Lock()) { + return false; + } + + mLocked = true; + return true; + } + void Unlock() override { + MOZ_ASSERT(mLocked); + mTextureHost->Unlock(); + if (mTextureHostOnWhite) { + mTextureHostOnWhite->Unlock(); + } + mLocked = false; + } + + bool HasComponentAlpha() const { return !!mTextureHostOnWhite; } + + RefPtr<TextureSource> AcquireTextureSource(); + RefPtr<TextureSource> AcquireTextureSourceOnWhite(); + + ContentHostTexture* AsContentHostTexture() override { return this; } + + already_AddRefed<TexturedEffect> GenEffect( + const gfx::SamplingFilter aSamplingFilter) override; + + protected: + CompositableTextureHostRef mTextureHost; + CompositableTextureHostRef mTextureHostOnWhite; + CompositableTextureSourceRef mTextureSource; + CompositableTextureSourceRef mTextureSourceOnWhite; + bool mLocked; + bool mReceivedNewHost; +}; + +/** + * Double buffering is implemented by swapping the front and back TextureHosts. + * We assume that whenever we use double buffering, then we have + * render-to-texture and thus no texture upload to do. + */ +class ContentHostDoubleBuffered : public ContentHostTexture { + public: + explicit ContentHostDoubleBuffered(const TextureInfo& aTextureInfo) + : ContentHostTexture(aTextureInfo) {} + + virtual ~ContentHostDoubleBuffered() = default; + + CompositableType GetType() override { + return CompositableType::CONTENT_DOUBLE; + } + + virtual bool UpdateThebes(const ThebesBufferData& aData, + const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) override; + + protected: + nsIntRegion mValidRegionForNextBackBuffer; +}; + +/** + * Single buffered, therefore we must synchronously upload the image from the + * TextureHost in the layers transaction (i.e., in UpdateThebes). + */ +class ContentHostSingleBuffered : public ContentHostTexture { + public: + explicit ContentHostSingleBuffered(const TextureInfo& aTextureInfo) + : ContentHostTexture(aTextureInfo) {} + virtual ~ContentHostSingleBuffered() = default; + + CompositableType GetType() override { + return CompositableType::CONTENT_SINGLE; + } + + bool UpdateThebes(const ThebesBufferData& aData, const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) override; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/Diagnostics.cpp b/gfx/layers/composite/Diagnostics.cpp new file mode 100644 index 0000000000..0be1f978c8 --- /dev/null +++ b/gfx/layers/composite/Diagnostics.cpp @@ -0,0 +1,96 @@ +/* -*- 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 "Diagnostics.h" +#include "mozilla/layers/LayersMessages.h" +#include "nsPrintfCString.h" + +namespace mozilla { +namespace layers { + +float TimedMetric::Average() const { + // We take at most 2 seconds of history. + TimeStamp latest = TimeStamp::Now(); + float total = 0.0f; + size_t count = 0; + for (auto iter = mHistory.rbegin(); iter != mHistory.rend(); iter++) { + if ((latest - iter->second).ToSeconds() > 2.0f) { + break; + } + total += iter->first; + count++; + } + + if (!count) { + return 0.0f; + } + return total / float(count); +} + +Diagnostics::Diagnostics() + : mCompositeFps("Compositor"), mTransactionFps("LayerTransactions") {} + +void Diagnostics::RecordPaintTimes(const PaintTiming& aPaintTimes) { + mDlbMs.Add(aPaintTimes.dlMs()); + mDlb2Ms.Add(aPaintTimes.dl2Ms()); + mFlbMs.Add(aPaintTimes.flbMs()); + mRasterMs.Add(aPaintTimes.rasterMs()); + mSerializeMs.Add(aPaintTimes.serializeMs()); + mSendMs.Add(aPaintTimes.sendMs()); +} + +std::string Diagnostics::GetFrameOverlayString(const GPUStats& aStats) { + TimeStamp now = TimeStamp::Now(); + unsigned fps = unsigned(mCompositeFps.AddFrameAndGetFps(now)); + unsigned txnFps = unsigned(mTransactionFps.GetFPS(now)); + + float pixelFillRatio = + aStats.mInvalidPixels + ? float(aStats.mPixelsFilled) / float(aStats.mInvalidPixels) + : 0.0f; + float screenFillRatio = aStats.mScreenPixels ? float(aStats.mPixelsFilled) / + float(aStats.mScreenPixels) + : 0.0f; + + if (aStats.mDrawTime) { + mGPUDrawMs.Add(aStats.mDrawTime.value()); + } + + std::string gpuTimeString; + if (mGPUDrawMs.Empty()) { + gpuTimeString = "N/A"; + } else { + gpuTimeString = nsPrintfCString("%0.1fms", mGPUDrawMs.Average()).get(); + } + + // DL = nsDisplayListBuilder, p = partial, f = full + // FLB = FrameLayerBuilder + // R = ClientLayerManager::EndTransaction + // CP = ShadowLayerForwarder::EndTransaction (txn build) + // TX = LayerTransactionChild::SendUpdate (IPDL serialize+send) + // UP = LayerTransactionParent::RecvUpdate (IPDL deserialize, update, APZ + // update) + // CC_BUILD = Container prepare/composite frame building + // CC_EXEC = Container render/composite drawing + nsPrintfCString line1("FPS: %d (TXN: %d)", fps, txnFps); + nsPrintfCString line2( + "[CC] Build: %0.1fms Exec: %0.1fms GPU: %s Fill Ratio: %0.1f/%0.1f", + mPrepareMs.Average(), mCompositeMs.Average(), gpuTimeString.c_str(), + pixelFillRatio, screenFillRatio); + nsPrintfCString line3( + "[Content] DL p: %0.1f DL f: %0.1fms FLB: %0.1fms Raster: %0.1fms", + mDlbMs.Average(), mDlb2Ms.Average(), mFlbMs.Average(), + mRasterMs.Average()); + nsPrintfCString line4("[IPDL] Build: %0.1fms Send: %0.1fms Update: %0.1fms", + mSerializeMs.Average(), mSendMs.Average(), + mUpdateMs.Average()); + + return std::string(line1.get()) + "\n" + std::string(line2.get()) + "\n" + + std::string(line3.get()) + "\n" + std::string(line4.get()); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/Diagnostics.h b/gfx/layers/composite/Diagnostics.h new file mode 100644 index 0000000000..4619ff6aa5 --- /dev/null +++ b/gfx/layers/composite/Diagnostics.h @@ -0,0 +1,100 @@ +/* -*- 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/. */ + +#ifndef mozilla_gfx_layers_composite_Diagnostics_h +#define mozilla_gfx_layers_composite_Diagnostics_h + +#include "FPSCounter.h" +#include "mozilla/Maybe.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/TimeStamp.h" +#include <deque> +#include <string> +#include <utility> + +namespace mozilla { +namespace layers { + +class PaintTiming; + +class TimedMetric { + typedef std::pair<float, TimeStamp> Entry; + + public: + void Add(float aValue) { + if (mHistory.size() > kMaxHistory) { + mHistory.pop_front(); + } + mHistory.push_back(Entry(aValue, TimeStamp::Now())); + } + + float Average() const; + bool Empty() const { return mHistory.empty(); } + + private: + static const size_t kMaxHistory = 60; + + std::deque<Entry> mHistory; +}; + +// These statistics are collected by layers backends, preferably by the GPU +struct GPUStats { + GPUStats() : mInvalidPixels(0), mScreenPixels(0), mPixelsFilled(0) {} + + uint32_t mInvalidPixels; + uint32_t mScreenPixels; + uint32_t mPixelsFilled; + Maybe<float> mDrawTime; +}; + +// Collects various diagnostics about layers performance. +class Diagnostics { + public: + Diagnostics(); + + void RecordPaintTimes(const PaintTiming& aPaintTimes); + void RecordUpdateTime(float aValue) { mUpdateMs.Add(aValue); } + void RecordPrepareTime(float aValue) { mPrepareMs.Add(aValue); } + void RecordCompositeTime(float aValue) { mCompositeMs.Add(aValue); } + void AddTxnFrame() { mTransactionFps.AddFrame(TimeStamp::Now()); } + + std::string GetFrameOverlayString(const GPUStats& aStats); + + class Record { + public: + explicit Record(TimeStamp aStart = TimeStamp()) { + if (StaticPrefs::layers_acceleration_draw_fps()) { + mStart = aStart.IsNull() ? TimeStamp::Now() : aStart; + } + } + bool Recording() const { return !mStart.IsNull(); } + float Duration() const { + return (TimeStamp::Now() - mStart).ToMilliseconds(); + } + + private: + TimeStamp mStart; + }; + + private: + FPSCounter mCompositeFps; + FPSCounter mTransactionFps; + TimedMetric mDlbMs; + TimedMetric mDlb2Ms; + TimedMetric mFlbMs; + TimedMetric mRasterMs; + TimedMetric mSerializeMs; + TimedMetric mSendMs; + TimedMetric mUpdateMs; + TimedMetric mPrepareMs; + TimedMetric mCompositeMs; + TimedMetric mGPUDrawMs; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_composite_Diagnostics_h diff --git a/gfx/layers/composite/FPSCounter.cpp b/gfx/layers/composite/FPSCounter.cpp new file mode 100644 index 0000000000..cf3d0725ed --- /dev/null +++ b/gfx/layers/composite/FPSCounter.cpp @@ -0,0 +1,343 @@ +/* -*- 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 <stddef.h> // for size_t +#include "Units.h" // for ScreenIntRect +#include "gfxRect.h" // for gfxRect +#include "mozilla/gfx/Point.h" // for IntSize, Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for Color, SurfaceFormat +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsIFile.h" // for nsIFile +#include "nsDirectoryServiceDefs.h" // for NS_OS_TMP_DIR +#include "mozilla/Sprintf.h" +#include "FPSCounter.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +FPSCounter::FPSCounter(const char* aName) + : mWriteIndex(0), mIteratorIndex(-1), mFPSName(aName) { + Init(); +} + +FPSCounter::~FPSCounter() = default; + +void FPSCounter::Init() { + for (int i = 0; i < kMaxFrames; i++) { + mFrameTimestamps.AppendElement(TimeStamp()); + } + mLastInterval = TimeStamp::Now(); +} + +// Returns true if we captured a full interval of data +bool FPSCounter::CapturedFullInterval(TimeStamp aTimestamp) { + TimeDuration duration = aTimestamp - mLastInterval; + return duration.ToSeconds() >= kFpsDumpInterval; +} + +void FPSCounter::AddFrame(TimeStamp aTimestamp) { + NS_ASSERTION(mWriteIndex < kMaxFrames, + "We probably have a bug with the circular buffer"); + NS_ASSERTION(mWriteIndex >= 0, + "Circular Buffer index should never be negative"); + + int index = mWriteIndex++; + if (mWriteIndex == kMaxFrames) { + mWriteIndex = 0; + } + + mFrameTimestamps[index] = aTimestamp; + + if (CapturedFullInterval(aTimestamp)) { + PrintFPS(); + WriteFrameTimeStamps(); + mLastInterval = aTimestamp; + } +} + +double FPSCounter::AddFrameAndGetFps(TimeStamp aTimestamp) { + AddFrame(aTimestamp); + return GetFPS(aTimestamp); +} + +int FPSCounter::GetLatestReadIndex() { + if (mWriteIndex == 0) { + return kMaxFrames - 1; + } + + return mWriteIndex - 1; +} + +TimeStamp FPSCounter::GetLatestTimeStamp() { + TimeStamp timestamp = mFrameTimestamps[GetLatestReadIndex()]; + MOZ_ASSERT(!timestamp.IsNull(), "Cannot use null timestamps"); + return timestamp; +} + +// Returns true if we iterated over a full interval of data +bool FPSCounter::IteratedFullInterval(TimeStamp aTimestamp, double aDuration) { + MOZ_ASSERT(mIteratorIndex >= 0, "Cannot be negative"); + MOZ_ASSERT(mIteratorIndex < kMaxFrames, + "Iterator index cannot be greater than kMaxFrames"); + + TimeStamp currentStamp = mFrameTimestamps[mIteratorIndex]; + TimeDuration duration = aTimestamp - currentStamp; + return duration.ToSeconds() >= aDuration; +} + +void FPSCounter::ResetReverseIterator() { + mIteratorIndex = GetLatestReadIndex(); +} + +/*** + * Returns true if we have another timestamp that is valid and + * is within the given duration that we're interested in. + * Duration is in seconds + */ +bool FPSCounter::HasNext(TimeStamp aTimestamp, double aDuration) { + // Order of evaluation here has to stay the same + // otherwise IteratedFullInterval reads from mFrameTimestamps which cannot + // be null + return (mIteratorIndex != mWriteIndex) // Didn't loop around the buffer + && !mFrameTimestamps[mIteratorIndex].IsNull() // valid data + && !IteratedFullInterval(aTimestamp, aDuration); +} + +TimeStamp FPSCounter::GetNextTimeStamp() { + TimeStamp timestamp = mFrameTimestamps[mIteratorIndex--]; + MOZ_ASSERT(!timestamp.IsNull(), "Reading Invalid Timestamp Data"); + + if (mIteratorIndex == -1) { + mIteratorIndex = kMaxFrames - 1; + } + return timestamp; +} + +/** + * GetFPS calculates how many frames we've already composited from the current + * frame timestamp and we iterate from the latest timestamp we recorded, + * going back in time. When we hit a frame that is longer than the 1 second + * from the current composited frame, we return how many frames we've counted. + * Just a visualization: + * + * aTimestamp + * Frames: 1 2 3 4 5 6 7 8 9 10 11 12 + * Time --------------------------> + * + * GetFPS iterates from aTimestamp, which is the current frame. + * Then starting at frame 12, going back to frame 11, 10, etc, we calculate + * the duration of the recorded frame timestamp from aTimestamp. + * Once duration is greater than 1 second, we return how many frames + * we composited. + */ +double FPSCounter::GetFPS(TimeStamp aTimestamp) { + int frameCount = 0; + int duration = 1.0; // Only care about the last 1s of data + + ResetReverseIterator(); + while (HasNext(aTimestamp, duration)) { + GetNextTimeStamp(); + frameCount++; + } + + return frameCount; +} + +// Iterate the same way we do in GetFPS() +int FPSCounter::BuildHistogram(std::map<int, int>& aFpsData) { + TimeStamp currentIntervalStart = GetLatestTimeStamp(); + TimeStamp currentTimeStamp = GetLatestTimeStamp(); + TimeStamp startTimeStamp = GetLatestTimeStamp(); + + int frameCount = 0; + int totalFrameCount = 0; + + ResetReverseIterator(); + while (HasNext(startTimeStamp)) { + currentTimeStamp = GetNextTimeStamp(); + TimeDuration interval = currentIntervalStart - currentTimeStamp; + + if (interval.ToSeconds() >= 1.0) { + currentIntervalStart = currentTimeStamp; + aFpsData[frameCount]++; + frameCount = 0; + } + + frameCount++; + totalFrameCount++; + } + + TimeDuration totalTime = currentIntervalStart - currentTimeStamp; + printf_stderr("Discarded %d frames over %f ms in histogram for %s\n", + frameCount, totalTime.ToMilliseconds(), mFPSName); + return totalFrameCount; +} + +// Iterate the same way we do in GetFPS() +void FPSCounter::WriteFrameTimeStamps(PRFileDesc* fd) { + const int bufferSize = 256; + char buffer[bufferSize]; + int writtenCount = SprintfLiteral(buffer, "FPS Data for: %s\n", mFPSName); + MOZ_ASSERT(writtenCount < bufferSize); + if (writtenCount >= bufferSize) { + return; + } + PR_Write(fd, buffer, writtenCount); + + ResetReverseIterator(); + TimeStamp startTimeStamp = GetLatestTimeStamp(); + + MOZ_ASSERT(HasNext(startTimeStamp)); + TimeStamp previousSample = GetNextTimeStamp(); + + MOZ_ASSERT(HasNext(startTimeStamp)); + TimeStamp nextTimeStamp = GetNextTimeStamp(); + + while (HasNext(startTimeStamp)) { + TimeDuration duration = previousSample - nextTimeStamp; + writtenCount = SprintfLiteral(buffer, "%f,\n", duration.ToMilliseconds()); + MOZ_ASSERT(writtenCount < bufferSize); + if (writtenCount >= bufferSize) { + continue; + } + PR_Write(fd, buffer, writtenCount); + + previousSample = nextTimeStamp; + nextTimeStamp = GetNextTimeStamp(); + } +} + +double FPSCounter::GetMean(std::map<int, int> aHistogram) { + double average = 0.0; + double samples = 0.0; + + for (std::map<int, int>::iterator iter = aHistogram.begin(); + iter != aHistogram.end(); ++iter) { + int fps = iter->first; + int count = iter->second; + + average += fps * count; + samples += count; + } + + return average / samples; +} + +double FPSCounter::GetStdDev(std::map<int, int> aHistogram) { + double sumOfDifferences = 0; + double average = GetMean(aHistogram); + double samples = 0.0; + + for (std::map<int, int>::iterator iter = aHistogram.begin(); + iter != aHistogram.end(); ++iter) { + int fps = iter->first; + int count = iter->second; + + double diff = ((double)fps) - average; + diff *= diff; + + for (int i = 0; i < count; i++) { + sumOfDifferences += diff; + } + samples += count; + } + + double stdDev = sumOfDifferences / samples; + return sqrt(stdDev); +} + +void FPSCounter::PrintFPS() { + if (!StaticPrefs::layers_acceleration_draw_fps_print_histogram()) { + return; + } + + std::map<int, int> histogram; + int totalFrames = BuildHistogram(histogram); + + TimeDuration measurementInterval = + mFrameTimestamps[GetLatestReadIndex()] - mLastInterval; + printf_stderr("FPS for %s. Total Frames: %d Time Interval: %f seconds\n", + mFPSName, totalFrames, + measurementInterval.ToSecondsSigDigits()); + + PrintHistogram(histogram); +} + +void FPSCounter::PrintHistogram(std::map<int, int>& aHistogram) { + if (aHistogram.empty()) { + return; + } + + int length = 0; + const int kBufferLength = 512; + int availableSpace = kBufferLength; + char buffer[kBufferLength]; + + for (std::map<int, int>::iterator iter = aHistogram.begin(); + iter != aHistogram.end(); iter++) { + int fps = iter->first; + int count = iter->second; + + int lengthRequired = + snprintf(buffer + length, availableSpace, "FPS: %d = %d. ", fps, count); + // Ran out of buffer space. Oh well - just print what we have. + if (lengthRequired > availableSpace) { + break; + } + length += lengthRequired; + availableSpace -= lengthRequired; + } + + printf_stderr("%s\n", buffer); + printf_stderr("Mean: %f , std dev %f\n", GetMean(aHistogram), + GetStdDev(aHistogram)); +} + +// Write FPS timestamp data to a file only if +// draw-fps.write-to-file is true +nsresult FPSCounter::WriteFrameTimeStamps() { + if (!StaticPrefs::layers_acceleration_draw_fps_write_to_file()) { + return NS_OK; + } + + MOZ_ASSERT(mWriteIndex == 0); + + nsCOMPtr<nsIFile> resultFile; + nsresult rv = + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(resultFile)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!strncmp(mFPSName, "Compositor", strlen(mFPSName))) { + resultFile->Append(u"fps.txt"_ns); + } else { + resultFile->Append(u"txn.txt"_ns); + } + + PRFileDesc* fd = nullptr; + int mode = 644; + int openFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE; + rv = resultFile->OpenNSPRFileDesc(openFlags, mode, &fd); + NS_ENSURE_SUCCESS(rv, rv); + + WriteFrameTimeStamps(fd); + PR_Close(fd); + + printf_stderr("Wrote FPS data to file: %s\n", + resultFile->HumanReadablePath().get()); + return NS_OK; +} + +} // end namespace layers +} // end namespace mozilla diff --git a/gfx/layers/composite/FPSCounter.h b/gfx/layers/composite/FPSCounter.h new file mode 100644 index 0000000000..c61b13a7b5 --- /dev/null +++ b/gfx/layers/composite/FPSCounter.h @@ -0,0 +1,102 @@ +/* -*- 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/. */ + +#ifndef mozilla_layers_opengl_FPSCounter_h_ +#define mozilla_layers_opengl_FPSCounter_h_ + +#include <algorithm> // for min +#include <stddef.h> // for size_t +#include <map> // for std::map +#include "GLDefs.h" // for GLuint +#include "mozilla/RefPtr.h" // for already_AddRefed, RefCounted +#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration +#include "nsTArray.h" // for AutoTArray, nsTArray_Impl, etc +#include "prio.h" // for NSPR file i/o + +namespace mozilla { +namespace layers { + +class DataTextureSource; +class Compositor; + +// Dump the FPS histogram every 10 seconds or kMaxFrameFPS +const int kFpsDumpInterval = 10; + +// On desktop, we can have 240 hz monitors, so 10 seconds +// times 240 frames = 2400 +const int kMaxFrames = 2400; + +/** + * The FPSCounter tracks how often we composite or have a layer transaction. + * At each composite / layer transaction, we record the timestamp. + * After kFpsDumpInterval number of composites / transactions, we calculate + * the average and standard deviation of frames composited. We dump a histogram, + * which allows for more statistically significant measurements. We also dump + * absolute frame composite times to a file on the device. + * The FPS counters displayed on screen are based on how many frames we + * composited within the last ~1 second. The more accurate measurement is to + * grab the histogram from stderr or grab the FPS timestamp dumps written to + * file. + * + * To enable dumping to file, enable + * layers.acceleration.draw-fps.write-to-file pref. + + double AddFrameAndGetFps(TimeStamp aCurrentFrame) { + AddFrame(aCurrentFrame); + return EstimateFps(aCurrentFrame); + } + * To enable printing histogram data to logcat, + * enable layers.acceleration.draw-fps.print-histogram + * + * Use the HasNext(), GetNextTimeStamp() like an iterator to read the data, + * backwards in time. This abstracts away the mechanics of reading the data. + */ +class FPSCounter final { + public: + explicit FPSCounter(const char* aName); + ~FPSCounter(); + + void AddFrame(TimeStamp aTimestamp); + double AddFrameAndGetFps(TimeStamp aTimestamp); + double GetFPS(TimeStamp aTimestamp); + + private: + void Init(); + bool CapturedFullInterval(TimeStamp aTimestamp); + + // Used while iterating backwards over the data + void ResetReverseIterator(); + bool HasNext(TimeStamp aTimestamp, double aDuration = kFpsDumpInterval); + TimeStamp GetNextTimeStamp(); + int GetLatestReadIndex(); + TimeStamp GetLatestTimeStamp(); + void WriteFrameTimeStamps(PRFileDesc* fd); + bool IteratedFullInterval(TimeStamp aTimestamp, double aDuration); + + void PrintFPS(); + int BuildHistogram(std::map<int, int>& aHistogram); + void PrintHistogram(std::map<int, int>& aHistogram); + double GetMean(std::map<int, int> aHistogram); + double GetStdDev(std::map<int, int> aHistogram); + nsresult WriteFrameTimeStamps(); + + /*** + * mFrameTimestamps is a psuedo circular buffer + * Since we have a constant write time and don't + * read at an offset except our latest write + * we don't need an explicit read pointer. + */ + AutoTArray<TimeStamp, kMaxFrames> mFrameTimestamps; + int mWriteIndex; // points to next open write slot + int mIteratorIndex; // used only when iterating + const char* mFPSName; + TimeStamp mLastInterval; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_opengl_FPSCounter_h_ diff --git a/gfx/layers/composite/FontData.h b/gfx/layers/composite/FontData.h new file mode 100644 index 0000000000..aa315f2519 --- /dev/null +++ b/gfx/layers/composite/FontData.h @@ -0,0 +1,304 @@ +/* -*- 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/. */ + +// This is explicitly not guarded as we want only 1 file to include this and +// it's good if things break if someone else does. + +namespace mozilla { +namespace layers { +namespace normal_font { +const unsigned char sFontPNG[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, + 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, + 0x8, 0x0, 0x0, 0x0, 0x0, 0x79, 0x19, 0xf7, 0xba, 0x0, 0x0, 0xb, + 0xfe, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0x5d, 0xd1, 0xb6, 0xe3, + 0x20, 0x8, 0x74, 0x72, 0xf2, 0xff, 0xbf, 0x3c, 0xfb, 0xd0, 0xbb, 0xbd, + 0x51, 0x8, 0x48, 0xd4, 0xc4, 0xde, 0xda, 0xb3, 0x67, 0x77, 0x6d, 0x13, + 0x63, 0x10, 0x81, 0x41, 0x40, 0x30, 0x7d, 0xf7, 0x67, 0x4b, 0x8b, 0x0, + 0xdf, 0xfd, 0xd9, 0x2f, 0xdc, 0x3, 0xc6, 0xda, 0x76, 0x67, 0x29, 0xa5, + 0x94, 0xb8, 0x38, 0x20, 0x42, 0x33, 0xc0, 0x68, 0xaa, 0x37, 0x64, 0x14, + 0x47, 0xc2, 0x67, 0x13, 0x80, 0x5, 0xbf, 0x91, 0xb0, 0x5f, 0x9f, 0x73, + 0x71, 0x40, 0x39, 0x61, 0x1c, 0xcd, 0xff, 0x39, 0x7d, 0x8, 0x1e, 0x29, + 0x72, 0x3f, 0x1, 0x90, 0x12, 0x8a, 0xf9, 0xc, 0x52, 0x20, 0x9f, 0x51, + 0x2, 0x8e, 0xd0, 0x99, 0x8d, 0x3, 0x7a, 0x2f, 0x2, 0x7a, 0x4, 0xa4, + 0x90, 0xba, 0x8, 0x68, 0x1, 0x28, 0x32, 0x1a, 0x87, 0x6f, 0x50, 0x71, + 0x7f, 0x5f, 0x29, 0xf, 0x85, 0xa3, 0x18, 0x5b, 0x10, 0xc, 0x10, 0x80, + 0xf2, 0x1d, 0x79, 0x4e, 0x5e, 0x8f, 0xfc, 0xaf, 0x1, 0xb0, 0x60, 0x50, + 0x4, 0x17, 0x40, 0xc6, 0xd3, 0xf4, 0xd5, 0x2e, 0x3a, 0xdb, 0x1, 0x5d, + 0x85, 0x78, 0x8a, 0xbd, 0x7e, 0x33, 0xc3, 0x24, 0xe4, 0x52, 0x70, 0xff, + 0xbc, 0xf5, 0x8f, 0xf0, 0x82, 0xe2, 0x5c, 0x1c, 0xd0, 0x9b, 0x83, 0x1c, + 0x7a, 0x50, 0xb4, 0x33, 0xa9, 0xf9, 0xf5, 0x58, 0x0, 0xb, 0xe, 0x2f, + 0x2, 0x2c, 0x2, 0x7c, 0x3d, 0x1, 0xea, 0x8c, 0xb9, 0xc, 0x4f, 0x16, + 0x70, 0x14, 0x21, 0xc5, 0x8e, 0x60, 0xfb, 0x1, 0xe, 0x80, 0xf5, 0xc2, + 0x52, 0x8c, 0xb6, 0x9, 0x52, 0xdc, 0xfb, 0xc6, 0x35, 0x4, 0x40, 0xa, + 0x81, 0x8d, 0x66, 0xbd, 0xce, 0xc9, 0x8, 0x80, 0x6f, 0x93, 0x1, 0x6d, + 0xef, 0xcf, 0xf1, 0xf4, 0xc2, 0xc0, 0xf9, 0xd9, 0xdb, 0xe7, 0x7f, 0x30, + 0x5, 0x50, 0x98, 0x6b, 0x48, 0xe8, 0x9, 0xaf, 0x77, 0x14, 0xf8, 0xbe, + 0xf5, 0x7d, 0xa0, 0xfa, 0xb, 0x18, 0xa2, 0x6f, 0x3e, 0x9e, 0xc2, 0x5c, + 0x65, 0x5f, 0xe3, 0x75, 0xa7, 0x30, 0x87, 0x3d, 0xa, 0xd8, 0x42, 0x8b, + 0x8a, 0x3f, 0x81, 0xb6, 0x7f, 0xa1, 0xa6, 0x8f, 0x80, 0x7, 0xa2, 0x5d, + 0x8, 0xd2, 0xd1, 0xfb, 0x7f, 0x4a, 0x4a, 0x6e, 0xfe, 0xc, 0x3c, 0xab, + 0xa6, 0x9e, 0x31, 0x84, 0x4c, 0x9e, 0xa3, 0xfb, 0x45, 0x47, 0x7f, 0xc5, + 0xc2, 0x2, 0xcb, 0x1f, 0xb0, 0x38, 0x60, 0x11, 0x60, 0x7a, 0x2, 0xe0, + 0x6f, 0x11, 0x0, 0x1e, 0x5a, 0x85, 0x3, 0x97, 0x5f, 0xde, 0x1, 0x94, + 0x5f, 0x9c, 0x93, 0x2b, 0xf2, 0x73, 0x77, 0x68, 0xd1, 0xce, 0x1, 0x60, + 0x6e, 0x3b, 0x82, 0x44, 0x26, 0x5c, 0xe1, 0x6d, 0x5f, 0xd3, 0xb2, 0x43, + 0x91, 0xb4, 0xcd, 0xb0, 0x27, 0x97, 0x0, 0x12, 0x4, 0x53, 0xc, 0x54, + 0x25, 0x44, 0x6f, 0x4d, 0x95, 0x77, 0xe7, 0x13, 0x80, 0xd2, 0x30, 0xf4, + 0x2d, 0x1f, 0xf0, 0xb4, 0x45, 0x47, 0x11, 0x13, 0xb7, 0xda, 0x49, 0x7b, + 0xf7, 0x29, 0x22, 0x12, 0xc1, 0x23, 0xa2, 0x3, 0x1b, 0xec, 0xd, 0x30, + 0xc7, 0x7f, 0x3f, 0x4b, 0x82, 0x9d, 0x18, 0xe0, 0x2, 0x1, 0xca, 0xdd, + 0xd9, 0x32, 0xe4, 0xe2, 0x35, 0xb6, 0xdf, 0xab, 0xd0, 0x86, 0xaf, 0x59, + 0xf0, 0x80, 0x4b, 0xcc, 0xe0, 0xd6, 0xe1, 0x7e, 0x41, 0x4, 0xb8, 0xf, + 0x11, 0xf8, 0x9a, 0x37, 0x1a, 0x9c, 0xc1, 0x27, 0x6d, 0xd1, 0xe, 0x5e, + 0x22, 0x80, 0x21, 0x99, 0xcc, 0x89, 0xc, 0xee, 0x72, 0x28, 0xbe, 0x1d, + 0x10, 0x17, 0xd3, 0x60, 0x4a, 0x7c, 0x7, 0xee, 0x10, 0x0, 0x81, 0x71, + 0x73, 0x1c, 0xdd, 0x86, 0xe8, 0xf, 0x86, 0x1a, 0xfb, 0x50, 0xc8, 0x77, + 0xe3, 0xe3, 0xb7, 0xfb, 0x57, 0xdd, 0x5c, 0x78, 0x76, 0x86, 0xd5, 0x99, + 0x8d, 0xe1, 0xee, 0x1, 0x2d, 0x7f, 0xc0, 0x82, 0xc3, 0x8b, 0x0, 0xdf, + 0xfd, 0xd9, 0x15, 0xcb, 0x1a, 0x22, 0xb6, 0x94, 0x56, 0xbb, 0x94, 0x62, + 0xa5, 0x62, 0x80, 0x68, 0x9a, 0xbf, 0xf7, 0xdd, 0xf9, 0xd2, 0xed, 0x82, + 0x2c, 0x52, 0x54, 0x40, 0x15, 0xc8, 0xb7, 0xcb, 0xd0, 0x88, 0xb0, 0xc6, + 0x6d, 0x6c, 0x5c, 0x5e, 0xcf, 0xe2, 0xe, 0xbf, 0xff, 0x36, 0x59, 0xe, + 0x7b, 0x3c, 0x9b, 0x7b, 0x3, 0x3b, 0x68, 0xfd, 0x96, 0x94, 0x92, 0x8a, + 0x0, 0x12, 0x33, 0x5c, 0xbc, 0xb4, 0x42, 0xe9, 0x81, 0xa1, 0xf0, 0xb, + 0x96, 0x70, 0x4f, 0x2e, 0xa9, 0xd4, 0x64, 0xeb, 0x79, 0x33, 0x80, 0xa4, + 0xf0, 0x63, 0xc6, 0x72, 0x16, 0x76, 0xdb, 0x2f, 0x30, 0x98, 0x83, 0x5, + 0x29, 0x4d, 0x1b, 0x7b, 0x86, 0xc2, 0xd6, 0x7d, 0x88, 0x63, 0x93, 0x8c, + 0xa7, 0x6, 0x63, 0x4, 0x90, 0x8f, 0x28, 0xd7, 0xac, 0x39, 0x0, 0xfa, + 0xbd, 0x83, 0x85, 0x43, 0x25, 0x6, 0xc6, 0xfc, 0x68, 0xf1, 0x73, 0x7, + 0xd5, 0xde, 0x24, 0x60, 0xfe, 0xcf, 0x57, 0xc8, 0x9, 0x1, 0xc6, 0x58, + 0xde, 0x5f, 0x22, 0xd9, 0xe3, 0xc5, 0x12, 0x40, 0x63, 0xb0, 0xb4, 0x97, + 0x14, 0xc7, 0xce, 0xc6, 0xfc, 0x85, 0xfb, 0x33, 0x96, 0xa4, 0xdb, 0x9f, + 0x92, 0xae, 0x60, 0xe9, 0x69, 0x57, 0x6f, 0x2b, 0x5a, 0xd1, 0x6e, 0x33, + 0xa5, 0xa6, 0xeb, 0x5b, 0xad, 0x84, 0x47, 0xc1, 0xd7, 0x74, 0xf8, 0x6d, + 0xfb, 0xf2, 0xf7, 0x5f, 0x70, 0x78, 0xa1, 0xc1, 0x45, 0x80, 0x45, 0x80, + 0xaf, 0xf7, 0x7, 0x28, 0xd1, 0x9c, 0xb4, 0xc0, 0x45, 0x91, 0x4c, 0xaa, + 0xda, 0xd, 0x87, 0x6f, 0xdc, 0xfb, 0x99, 0x3f, 0x12, 0x34, 0xf1, 0xbb, + 0x7b, 0xfd, 0xff, 0x76, 0xfe, 0x6f, 0xb9, 0xbd, 0xc8, 0x23, 0x1, 0xde, + 0x9b, 0x78, 0xbf, 0xf0, 0x4e, 0xdb, 0xeb, 0xcb, 0x9e, 0x50, 0x98, 0x5e, + 0x25, 0xe0, 0x37, 0xd1, 0x1b, 0x63, 0xda, 0x47, 0xcb, 0x2c, 0xb5, 0x6e, + 0xff, 0x79, 0x63, 0xdb, 0x90, 0x7a, 0x7f, 0xb9, 0xfd, 0xfe, 0xbf, 0x12, + 0x82, 0xbc, 0x2e, 0xf7, 0xf6, 0xb4, 0x83, 0xf7, 0x8f, 0x54, 0xde, 0x25, + 0x5a, 0x67, 0xf6, 0xe5, 0xf6, 0x42, 0x63, 0xc7, 0x98, 0x87, 0x32, 0xe4, + 0xe3, 0xd3, 0x6c, 0x21, 0x22, 0x42, 0xa2, 0xfd, 0x8d, 0x48, 0x6d, 0x8f, + 0x4c, 0x4, 0xed, 0x29, 0x42, 0x41, 0x1, 0xfc, 0xbc, 0x84, 0xfe, 0xaf, + 0xdc, 0x60, 0xee, 0xcd, 0xee, 0xa, 0x53, 0x6a, 0x3e, 0xbe, 0xd0, 0x9a, + 0xad, 0xf0, 0x50, 0x1c, 0x1e, 0xc2, 0xe0, 0x4b, 0x81, 0x2e, 0xb, 0x50, + 0x44, 0x30, 0xb8, 0x70, 0x18, 0x85, 0x2c, 0x9c, 0xd8, 0x44, 0x66, 0xd7, + 0xfd, 0xf6, 0x2d, 0xfd, 0x6c, 0x60, 0x57, 0xf7, 0x49, 0x28, 0x33, 0x16, + 0x61, 0x10, 0x84, 0x6e, 0x40, 0x8d, 0x60, 0xf3, 0x78, 0xfe, 0x78, 0x79, + 0xf1, 0xfc, 0xfd, 0xcd, 0x84, 0xb9, 0xfa, 0x3c, 0xf4, 0x51, 0xae, 0x69, + 0xc2, 0x5e, 0xe3, 0x5e, 0xcd, 0x8, 0x22, 0x22, 0x53, 0xa8, 0x6f, 0x13, + 0x14, 0xe3, 0x63, 0x15, 0xd5, 0xb5, 0xe7, 0x1f, 0x63, 0x79, 0xbe, 0xd2, + 0x12, 0xfc, 0x55, 0x0, 0x5f, 0x8a, 0x8b, 0x97, 0x3f, 0x60, 0xa1, 0xc1, + 0x45, 0x80, 0x45, 0x80, 0x2f, 0xf7, 0x7, 0x54, 0xe1, 0xfd, 0xf3, 0x8d, + 0x82, 0x12, 0xfe, 0x97, 0x96, 0xa4, 0x5, 0xcf, 0x7f, 0x77, 0xc5, 0x60, + 0xf8, 0x7, 0xe4, 0xe3, 0x2d, 0xbf, 0x3e, 0x82, 0x7b, 0xd1, 0x7b, 0x1d, + 0xde, 0x17, 0xe, 0x88, 0x77, 0xdb, 0xdb, 0xcd, 0x67, 0x79, 0x7d, 0xd1, + 0x13, 0xe5, 0xde, 0x20, 0xa5, 0xbb, 0xc1, 0xc1, 0x16, 0x96, 0xf7, 0xc0, + 0x89, 0xd3, 0x3d, 0x8b, 0xf, 0xf0, 0xf3, 0x67, 0xdb, 0x94, 0x2f, 0x8e, + 0x86, 0x99, 0x48, 0x99, 0x8, 0xc4, 0x4e, 0xb6, 0x22, 0xf7, 0xf1, 0x32, + 0x20, 0x6, 0xcf, 0x47, 0x18, 0x3a, 0x74, 0x6a, 0x89, 0x79, 0x78, 0xbf, + 0xf8, 0x9d, 0x68, 0xe3, 0x80, 0x17, 0x58, 0x3d, 0x40, 0x56, 0x5, 0xba, + 0x34, 0x6, 0xcf, 0xc6, 0x84, 0x60, 0x45, 0x3d, 0x38, 0xb0, 0xc5, 0x3f, + 0x50, 0xb1, 0x1e, 0x22, 0xc1, 0xf7, 0x21, 0x77, 0x87, 0xcc, 0x37, 0xb8, + 0xb4, 0x4, 0x1a, 0x53, 0x1e, 0x30, 0xb3, 0xc1, 0xbd, 0x55, 0xe1, 0xf5, + 0x23, 0x5, 0xd0, 0xba, 0xe6, 0xf9, 0x2b, 0xfd, 0x6b, 0x16, 0x29, 0x38, + 0x74, 0x7a, 0xf6, 0x3a, 0xbc, 0x7e, 0xe0, 0x33, 0x57, 0x6, 0x8, 0x4, + 0x4f, 0xd7, 0x89, 0x6d, 0x5f, 0x31, 0x14, 0xaa, 0xea, 0x4f, 0x9e, 0x19, + 0x23, 0x82, 0xad, 0x17, 0x54, 0xc8, 0x80, 0xbe, 0x19, 0x1e, 0x93, 0x9b, + 0xc2, 0x3, 0xec, 0x9c, 0xc1, 0x2c, 0x60, 0x8e, 0xae, 0x32, 0x81, 0x86, + 0xcb, 0x21, 0xb2, 0xd0, 0xe0, 0x22, 0xc0, 0xf, 0x1, 0xf0, 0xed, 0x4, + 0xf0, 0xec, 0x88, 0xba, 0xd2, 0x4f, 0xd7, 0x3b, 0x40, 0xe7, 0x76, 0x7c, + 0x9, 0xc, 0x16, 0x83, 0x51, 0x31, 0xdb, 0x23, 0x3c, 0xbf, 0x4d, 0x6, + 0x88, 0x3, 0xb, 0x0, 0xf3, 0x77, 0x79, 0x79, 0x79, 0x9e, 0x81, 0xe8, + 0xce, 0xea, 0xa0, 0x66, 0x86, 0x4d, 0xd3, 0x39, 0x1, 0xc7, 0x3e, 0x61, + 0x8f, 0x7f, 0x57, 0x67, 0xcc, 0xa, 0x77, 0x57, 0x32, 0x3c, 0x60, 0xdc, + 0x4f, 0x25, 0x40, 0x43, 0x44, 0x2c, 0x4, 0xb9, 0xc4, 0x45, 0x8f, 0x38, + 0x42, 0x58, 0x7b, 0xfc, 0x15, 0xc9, 0xd3, 0x74, 0xd9, 0xb4, 0xad, 0xd4, + 0x35, 0x3b, 0x2f, 0x2a, 0x25, 0xe1, 0x3e, 0x66, 0x9, 0xb6, 0xaa, 0x85, + 0xd1, 0x6a, 0x65, 0x78, 0x41, 0x45, 0x36, 0x3e, 0x63, 0xf8, 0x89, 0x19, + 0x5d, 0xcd, 0xd7, 0x8a, 0xa2, 0xaa, 0x70, 0x27, 0x1, 0x8d, 0x9a, 0xef, + 0x82, 0x8b, 0x65, 0x20, 0x7, 0x10, 0x5, 0x8d, 0x8b, 0x9c, 0x94, 0x12, + 0xee, 0x97, 0x29, 0x2e, 0x74, 0xf2, 0xfe, 0xa2, 0x29, 0x31, 0xe7, 0x3e, + 0x95, 0xeb, 0xd4, 0xe3, 0xd3, 0xc8, 0x1f, 0x1c, 0x73, 0xed, 0x47, 0x60, + 0x81, 0xc9, 0x4c, 0xef, 0x7, 0x38, 0x20, 0x96, 0x1a, 0xb, 0xfe, 0x39, + 0x2, 0x2c, 0x38, 0xbc, 0x8, 0xb0, 0x8, 0xf0, 0xa7, 0x8, 0x70, 0xbb, + 0x54, 0xb7, 0x4f, 0x33, 0x88, 0xfa, 0xb3, 0x3f, 0xef, 0x98, 0x1d, 0x17, + 0xa, 0x79, 0xa5, 0xc6, 0x52, 0x51, 0x4e, 0x4f, 0xc1, 0xcb, 0x79, 0x81, + 0x3, 0x17, 0xef, 0xcb, 0x19, 0xc1, 0x69, 0xf3, 0xdd, 0x86, 0x71, 0x7d, + 0x51, 0x8f, 0x12, 0x23, 0xeb, 0x50, 0xed, 0x1a, 0x5e, 0x36, 0x2b, 0xe, + 0x78, 0x78, 0xff, 0x82, 0x59, 0xb, 0xaf, 0x58, 0x39, 0xda, 0xd6, 0xa8, + 0xc5, 0x15, 0xaa, 0x4b, 0x8c, 0x41, 0x2a, 0x6, 0x4d, 0x71, 0x58, 0xfb, + 0xc5, 0xda, 0xde, 0x6a, 0x13, 0x8d, 0x41, 0x5a, 0x15, 0x26, 0xb6, 0xb, + 0x6f, 0x0, 0x44, 0x5f, 0xb8, 0xd, 0xd9, 0x4, 0x93, 0x9d, 0x29, 0x18, + 0xd8, 0x3a, 0xd3, 0xf2, 0x82, 0x16, 0xf0, 0xce, 0xf8, 0x8c, 0x4f, 0x11, + 0x4c, 0x8a, 0xd9, 0x45, 0x57, 0x5b, 0x8d, 0xdb, 0x2d, 0x5c, 0xc8, 0x16, + 0x35, 0xab, 0xce, 0x99, 0xf0, 0x88, 0xe0, 0xc0, 0xf0, 0x9a, 0xa2, 0x12, + 0xef, 0x9b, 0x54, 0xd6, 0xfc, 0x1, 0xc9, 0xf4, 0xf, 0xb8, 0x27, 0x82, + 0xd2, 0x3c, 0x42, 0x62, 0x6c, 0x7c, 0xc0, 0x9e, 0x3c, 0x17, 0xa7, 0xeb, + 0x24, 0x55, 0xda, 0xf4, 0xae, 0xe7, 0x69, 0xb3, 0xa7, 0xc0, 0xad, 0x61, + 0xcf, 0x9, 0xd, 0xa1, 0xb1, 0x3c, 0xcf, 0xd9, 0x2d, 0x41, 0xb7, 0x1e, + 0xb, 0xbd, 0xf8, 0x0, 0x4e, 0x44, 0xee, 0x85, 0x6, 0x3b, 0x31, 0x5, + 0xc6, 0xdd, 0xff, 0x11, 0x1c, 0x30, 0xb2, 0x32, 0xcf, 0xf3, 0x4, 0x78, + 0x78, 0x4, 0xfb, 0x15, 0x3d, 0x1b, 0x14, 0x34, 0x66, 0x21, 0x20, 0x50, + 0xad, 0x54, 0x86, 0xf3, 0x2, 0x5, 0xe7, 0x8a, 0x8d, 0xa7, 0xbf, 0x5a, + 0xa9, 0xb3, 0xd1, 0x74, 0xfe, 0xce, 0x73, 0x46, 0x59, 0x6a, 0x2e, 0x87, + 0xa7, 0x6e, 0x2a, 0xaf, 0x53, 0x80, 0xd0, 0x41, 0x93, 0xfb, 0x18, 0x6b, + 0x23, 0x9f, 0x81, 0xb2, 0xd6, 0x17, 0x12, 0xa3, 0x24, 0x66, 0xcb, 0x74, + 0x98, 0xd9, 0xe3, 0xf1, 0xc3, 0xdb, 0x82, 0xb1, 0xcf, 0x32, 0xd8, 0x7c, + 0xaa, 0x52, 0xc8, 0x7b, 0xdc, 0xa1, 0x31, 0xb6, 0x54, 0xfa, 0xeb, 0x78, + 0x2, 0xbc, 0x32, 0xec, 0x99, 0x64, 0x6c, 0x32, 0x4e, 0x57, 0x3a, 0xaf, + 0x50, 0xec, 0xa, 0x7, 0x8c, 0x3e, 0x81, 0x13, 0xc4, 0xfb, 0xaf, 0x54, + 0xe6, 0x27, 0x9c, 0x3c, 0xfd, 0xb2, 0xa2, 0xdb, 0x98, 0xc8, 0x4f, 0xb2, + 0x6, 0x7b, 0x57, 0xf7, 0xd8, 0xaa, 0x41, 0x7e, 0x37, 0xa1, 0x8, 0xc7, + 0x75, 0xad, 0xa0, 0x61, 0x8b, 0x2, 0x6e, 0x98, 0x9f, 0x8b, 0xc6, 0x11, + 0xe4, 0x20, 0xe1, 0xb0, 0x53, 0x3c, 0x78, 0x99, 0x6a, 0x8a, 0x15, 0x4, + 0x7c, 0xa9, 0xe5, 0x5f, 0x19, 0x30, 0xdc, 0xe, 0xe8, 0xae, 0x2, 0x59, + 0x1, 0xf2, 0x3d, 0xbc, 0x7a, 0x2c, 0xe9, 0xd0, 0xe8, 0xa2, 0x65, 0x13, + 0x83, 0x5c, 0x60, 0x80, 0xf, 0xfb, 0x80, 0xe3, 0xef, 0xf8, 0xd3, 0x40, + 0x6b, 0xf9, 0x3, 0x16, 0x1, 0x3e, 0x90, 0xeb, 0x8f, 0xba, 0x75, 0x9b, + 0x60, 0x38, 0x68, 0xbc, 0x20, 0xfa, 0x80, 0xcc, 0x6d, 0xed, 0x9f, 0x3d, + 0xee, 0x9d, 0x3f, 0xd8, 0x38, 0x3e, 0x69, 0x46, 0xbc, 0x77, 0xa7, 0xd1, + 0x7, 0x7b, 0x50, 0x3b, 0xc, 0x0, 0xe7, 0x4, 0x70, 0x2d, 0xa7, 0xbe, + 0x72, 0x53, 0x3f, 0xc4, 0x17, 0xae, 0x7d, 0x13, 0xb5, 0x2c, 0x72, 0xcb, + 0x11, 0x2c, 0xfc, 0x1, 0xd7, 0xd5, 0xc4, 0x18, 0x3b, 0x80, 0xe0, 0x7b, + 0xcb, 0xd0, 0x3, 0x6b, 0x48, 0x66, 0x78, 0xaa, 0xb0, 0x4, 0xf3, 0xfd, + 0x78, 0xf7, 0x7c, 0x1, 0x3a, 0x33, 0x46, 0xaf, 0xde, 0x7f, 0xf, 0x45, + 0x6d, 0x55, 0xb3, 0x7f, 0x7b, 0xb4, 0xc, 0x9c, 0x88, 0x3b, 0xb5, 0x0, + 0xba, 0x40, 0x2b, 0x9e, 0x1e, 0xd2, 0x78, 0x1, 0xe, 0xca, 0xd3, 0xef, + 0x52, 0xaa, 0x5e, 0x2, 0x1d, 0xe8, 0x91, 0xbd, 0x82, 0x5f, 0xe, 0x84, + 0x87, 0x3f, 0xa9, 0x36, 0x17, 0xfb, 0x72, 0xfe, 0xf9, 0x3e, 0x60, 0xf9, + 0x9a, 0xf9, 0x1a, 0x57, 0x8a, 0xe7, 0x57, 0x84, 0x48, 0x5c, 0x77, 0x88, + 0xc, 0x10, 0x60, 0x1c, 0xc1, 0x42, 0xfe, 0xfb, 0xe0, 0xaa, 0x3f, 0xe0, + 0x3, 0xc, 0xb7, 0xae, 0x5a, 0xc0, 0x96, 0xa9, 0xb, 0xb, 0x2c, 0x2, + 0x2c, 0x2, 0x2c, 0x2, 0x2c, 0x2, 0x7c, 0xd3, 0x67, 0xff, 0xb6, 0x17, + 0xbe, 0x52, 0x43, 0xc4, 0x2f, 0xd9, 0x27, 0x2e, 0x86, 0xb1, 0x97, 0x57, + 0xe6, 0xed, 0x81, 0x96, 0xe2, 0x6e, 0x87, 0x56, 0xf6, 0xc6, 0x40, 0xcd, + 0xe6, 0x72, 0x92, 0x25, 0xc, 0x3, 0xdb, 0xf5, 0xce, 0x79, 0x7f, 0x17, + 0x28, 0x78, 0x5, 0x9, 0x9d, 0x76, 0xe2, 0x9f, 0x3c, 0xd, 0x27, 0x7d, + 0x1c, 0x45, 0x82, 0x41, 0x4d, 0xe9, 0x30, 0xfb, 0xfd, 0x81, 0x63, 0xb0, + 0x69, 0x23, 0xb4, 0x14, 0xfd, 0xb9, 0x4, 0xa0, 0x8e, 0xce, 0x2c, 0xe0, + 0x1a, 0xf2, 0x89, 0xb8, 0xd1, 0xe6, 0xc8, 0xa2, 0xb1, 0x65, 0xd7, 0x71, + 0xbc, 0x6d, 0x45, 0x77, 0xef, 0xe1, 0x45, 0x81, 0x82, 0x27, 0x50, 0x54, + 0x7b, 0x87, 0x13, 0x41, 0x51, 0x75, 0xa4, 0x26, 0x4f, 0xfc, 0x37, 0x49, + 0xd6, 0x5b, 0xa8, 0xa8, 0x8b, 0xdd, 0x78, 0xe0, 0xa2, 0xe5, 0x4e, 0x50, + 0xc7, 0xe, 0xb6, 0xe1, 0xdf, 0x54, 0xd4, 0x14, 0x35, 0xdf, 0x5f, 0x1c, + 0x4f, 0xa0, 0xbc, 0x2d, 0xfd, 0x18, 0x21, 0x1b, 0xdf, 0x9b, 0x24, 0x64, + 0x99, 0x72, 0xe3, 0x48, 0x6e, 0x2f, 0xbb, 0xdc, 0xb9, 0xc0, 0x5d, 0x71, + 0x3c, 0x19, 0x63, 0xb5, 0x16, 0x50, 0x17, 0x9d, 0xa1, 0x5, 0xf0, 0xa, + 0x6a, 0x39, 0xed, 0x41, 0xf5, 0xdf, 0x44, 0xb4, 0x80, 0x26, 0xd6, 0x58, + 0xdf, 0xa1, 0xd3, 0x5f, 0xd, 0x1, 0x9c, 0x73, 0x85, 0x9d, 0x3e, 0xa3, + 0x6a, 0x30, 0x35, 0xd6, 0x2c, 0x6d, 0xb6, 0x3, 0x7a, 0x7b, 0x8, 0xbc, + 0x83, 0x93, 0xbe, 0xcf, 0x14, 0x9e, 0xcb, 0x5, 0xb3, 0x4d, 0x3e, 0xbe, + 0xf1, 0xd8, 0x60, 0xb9, 0xc4, 0x16, 0x1, 0x16, 0x1, 0xfa, 0x20, 0xec, + 0xcf, 0x25, 0x80, 0x13, 0xb6, 0xe8, 0xd4, 0x92, 0xab, 0x88, 0x33, 0xb, + 0xa6, 0x7f, 0x47, 0xb, 0x2, 0x94, 0xf9, 0xe6, 0x79, 0xfb, 0xff, 0xb3, + 0x71, 0x2a, 0x4, 0xa5, 0x61, 0xe5, 0x5, 0x36, 0x22, 0x18, 0x5c, 0x1e, + 0xb, 0x71, 0x80, 0x63, 0xfb, 0xeb, 0xd7, 0x83, 0x67, 0x6d, 0xc7, 0xf4, + 0xaa, 0x58, 0x2, 0x22, 0x82, 0x83, 0xee, 0x84, 0xb4, 0xac, 0xa, 0x36, + 0x5e, 0x2f, 0xec, 0x2e, 0xd5, 0xb6, 0x3f, 0x10, 0x80, 0x88, 0x15, 0x77, + 0xe7, 0xc3, 0x87, 0x11, 0xaa, 0xee, 0x0, 0x83, 0x49, 0x49, 0x24, 0x3, + 0xa0, 0x8a, 0x50, 0x59, 0x14, 0xc6, 0xaa, 0x80, 0xff, 0xd1, 0x39, 0x8a, + 0xd2, 0xab, 0xf0, 0x26, 0x88, 0x3c, 0xc9, 0x12, 0xe, 0xe2, 0x10, 0x5d, + 0xfc, 0xdb, 0x3e, 0x34, 0xff, 0x3, 0x66, 0xdd, 0xc9, 0xb6, 0x2b, 0x1c, + 0x85, 0xce, 0xdb, 0xdb, 0x8, 0x49, 0x10, 0xf7, 0x81, 0x5, 0x5, 0xf8, + 0x23, 0xc9, 0x90, 0xb5, 0x7f, 0x4f, 0x8d, 0x4b, 0xc5, 0xef, 0x8f, 0xdb, + 0x1, 0xcd, 0xa6, 0x67, 0x9d, 0xd4, 0x36, 0xfc, 0xb2, 0x59, 0x73, 0x1b, + 0xb4, 0x4c, 0x87, 0x1a, 0xef, 0x9a, 0x58, 0xb3, 0x95, 0x33, 0x4e, 0x4f, + 0xa1, 0xdd, 0x4f, 0x84, 0x38, 0x5b, 0x5e, 0x9f, 0x23, 0x21, 0x6, 0xb5, + 0xb2, 0x2e, 0x2c, 0xce, 0xd9, 0xcd, 0x24, 0x57, 0xf9, 0xfb, 0x60, 0x30, + 0xa4, 0x79, 0x18, 0xa6, 0xca, 0x5d, 0xcd, 0x17, 0xc8, 0xf3, 0x5b, 0x63, + 0xb7, 0xe7, 0x1b, 0xe4, 0x3e, 0xc7, 0x3b, 0x38, 0x60, 0x6a, 0x40, 0xbe, + 0xfc, 0x1, 0xaa, 0xd4, 0xe, 0xc6, 0x4b, 0x77, 0x57, 0x1a, 0x5e, 0xd5, + 0xda, 0x5e, 0x37, 0xff, 0x27, 0x0, 0x1d, 0x1d, 0x22, 0x4c, 0x19, 0x11, + 0x7f, 0x8d, 0xc8, 0x33, 0x5d, 0x5b, 0xd1, 0x7b, 0x7e, 0x76, 0x6c, 0x73, + 0xcb, 0xcd, 0x83, 0x96, 0xc0, 0x47, 0xe5, 0x51, 0xa9, 0xc9, 0xf3, 0xdd, + 0xdb, 0xb9, 0x11, 0xa8, 0x9d, 0x67, 0x78, 0x7e, 0x80, 0xa1, 0xdb, 0x6e, + 0x1b, 0xdc, 0xae, 0xb1, 0x74, 0xe7, 0x76, 0xa1, 0xea, 0xd4, 0xf3, 0xc, + 0x3, 0x60, 0xa0, 0x2f, 0x77, 0x6d, 0x77, 0x71, 0x59, 0xd1, 0x9a, 0x66, + 0x8d, 0x6c, 0xf2, 0xb0, 0xaf, 0xde, 0xed, 0xb9, 0x3f, 0xfb, 0x5d, 0x62, + 0x26, 0x55, 0x7, 0xf3, 0x46, 0xdb, 0xed, 0x4, 0xe8, 0x7d, 0xcc, 0x89, + 0xd6, 0xe6, 0x75, 0x7, 0xc0, 0x58, 0x19, 0xb0, 0xcb, 0x53, 0x5e, 0x47, + 0xb4, 0xe7, 0x5d, 0x12, 0xf, 0x38, 0x44, 0x90, 0x66, 0xa2, 0xc8, 0xaf, + 0xef, 0x6c, 0xe8, 0xbf, 0x98, 0xd6, 0xe, 0x78, 0xc4, 0x66, 0x9b, 0xc9, + 0x50, 0x7c, 0x64, 0x6f, 0x70, 0x26, 0x2d, 0xf9, 0x8, 0x1, 0x66, 0xe2, + 0x80, 0xe5, 0xf, 0xd0, 0x25, 0xa3, 0x3, 0x6f, 0xe1, 0x41, 0xf0, 0xd6, + 0x7c, 0xef, 0x26, 0x88, 0x1f, 0xd2, 0x31, 0x5b, 0xd, 0x9c, 0x75, 0xfc, + 0x5, 0x25, 0xe6, 0x76, 0xfd, 0x5, 0x51, 0x19, 0x11, 0x84, 0xf8, 0x33, + 0x9d, 0xe1, 0xf2, 0x9, 0x58, 0x40, 0x98, 0xda, 0x22, 0xb0, 0x50, 0xc4, + 0xcb, 0x37, 0xb6, 0x35, 0x86, 0x65, 0x51, 0x75, 0xa8, 0xb7, 0x65, 0x7a, + 0x3a, 0x9c, 0x3d, 0xa9, 0xdb, 0xe9, 0x96, 0xcf, 0xa4, 0xb5, 0x5d, 0xe5, + 0x93, 0x69, 0xd1, 0xb1, 0x8c, 0x3c, 0x6e, 0x93, 0xab, 0x4e, 0xdd, 0xcc, + 0x3c, 0xc4, 0xef, 0xb7, 0xb6, 0xb, 0x21, 0xf5, 0x33, 0xa0, 0x7e, 0xa6, + 0x41, 0x99, 0xb0, 0x60, 0x47, 0xa8, 0xdc, 0xbf, 0x31, 0xa2, 0x9d, 0x27, + 0x30, 0xb6, 0x40, 0xdf, 0x99, 0x53, 0xe6, 0xc5, 0x1, 0x4a, 0x69, 0xb3, + 0xc1, 0x86, 0xda, 0xf0, 0xf7, 0xf, 0xd5, 0xeb, 0xdf, 0xa8, 0x64, 0x7b, + 0x73, 0xf0, 0xf6, 0xee, 0xd3, 0x96, 0xf0, 0xf1, 0x64, 0xf1, 0xfb, 0x97, + 0x80, 0xac, 0xae, 0x3f, 0x58, 0x15, 0x2b, 0x1e, 0xa4, 0xc3, 0xf6, 0xf5, + 0xa6, 0x8e, 0x41, 0xca, 0xc4, 0xdf, 0xb, 0x9a, 0xdb, 0x49, 0xd4, 0x34, + 0x91, 0xd7, 0x67, 0x87, 0x98, 0xc4, 0xda, 0x72, 0xbe, 0x69, 0x95, 0x8c, + 0x4, 0x9f, 0xb6, 0x3, 0x5e, 0x23, 0xb0, 0xc2, 0x90, 0xa2, 0x6d, 0xbb, + 0xe0, 0xc4, 0xab, 0x82, 0xe2, 0x3a, 0x7c, 0xfd, 0x51, 0x38, 0xbc, 0x8, + 0xb0, 0x8, 0xb0, 0x8, 0xf0, 0xa7, 0x9, 0x80, 0x4f, 0x23, 0x40, 0xe7, + 0x0, 0x11, 0xc4, 0x83, 0x63, 0x1b, 0x1c, 0x40, 0x3d, 0x22, 0x44, 0x9a, + 0x3, 0x44, 0x10, 0x33, 0xc5, 0x9b, 0x1d, 0x40, 0xb0, 0xcc, 0xf6, 0xd0, + 0xdd, 0xcb, 0xe, 0xf8, 0x39, 0x61, 0xa2, 0xc9, 0xf0, 0xaa, 0x68, 0x67, + 0xb1, 0x79, 0x9d, 0xfb, 0x6f, 0x8e, 0x10, 0xd1, 0x22, 0x8b, 0x7b, 0xb7, + 0x8f, 0x5f, 0xf6, 0xee, 0xbf, 0x59, 0x8, 0xb6, 0xee, 0x4e, 0x5f, 0xd9, + 0xcd, 0xe6, 0x6c, 0x4b, 0xc0, 0x11, 0xfa, 0x1d, 0xda, 0xa8, 0x97, 0xe3, + 0xd1, 0x76, 0x2b, 0x1, 0x40, 0xe9, 0xa3, 0xeb, 0xdd, 0xb6, 0xe1, 0x6a, + 0x7, 0x96, 0x6a, 0x34, 0x84, 0x46, 0xf3, 0x80, 0x60, 0x2, 0xcc, 0xc3, + 0x4, 0xa0, 0x88, 0xef, 0xef, 0xdf, 0x4e, 0x19, 0x4, 0x9f, 0x4b, 0xb, + 0x3c, 0x61, 0x7, 0x4c, 0x65, 0x7b, 0x3c, 0x1, 0x86, 0xa6, 0xa, 0xa2, + 0x7b, 0x82, 0x0, 0x53, 0x71, 0xc0, 0x72, 0x89, 0x2d, 0x7f, 0xc0, 0x22, + 0x80, 0x62, 0x9a, 0xc5, 0x2, 0x44, 0x7a, 0x27, 0x94, 0xdc, 0x19, 0x20, + 0xf2, 0x3a, 0x78, 0xd9, 0xc6, 0xfb, 0xbd, 0xfd, 0x5, 0x63, 0xfd, 0x3, + 0xe2, 0x7a, 0x67, 0x7a, 0x3a, 0x63, 0xab, 0x9, 0x5, 0xaf, 0xf3, 0xb4, + 0x4d, 0x46, 0xf8, 0x24, 0xa7, 0x66, 0x44, 0x6b, 0x5b, 0x24, 0x7e, 0xe6, + 0x17, 0x88, 0xf8, 0x81, 0x58, 0x5b, 0xa9, 0x17, 0x21, 0xba, 0x3f, 0xfe, + 0xfe, 0x8a, 0x16, 0x2f, 0xf7, 0x92, 0x46, 0xfa, 0x7, 0xb4, 0x19, 0xba, + 0x3f, 0xd9, 0xd8, 0x40, 0x83, 0xe9, 0x86, 0xbd, 0x5a, 0xc5, 0x67, 0xd4, + 0xcf, 0xcc, 0xb4, 0xb, 0x0, 0x94, 0xbf, 0xdf, 0xbe, 0x3d, 0xae, 0xd6, + 0x96, 0x7b, 0xd0, 0x36, 0x9e, 0xc0, 0xe, 0xe8, 0x1e, 0x1f, 0x62, 0xf7, + 0x58, 0xfc, 0xfe, 0x8, 0x16, 0xf0, 0x78, 0xa2, 0xaf, 0x7b, 0x0, 0xae, + 0x84, 0x70, 0xaa, 0xe6, 0xf6, 0xf6, 0x12, 0x2b, 0xe3, 0x2b, 0xae, 0x2f, + 0xea, 0x33, 0x6, 0xdb, 0x22, 0x20, 0xa, 0x91, 0xf0, 0x81, 0xbf, 0xf0, + 0xf1, 0x2, 0x70, 0xc6, 0x97, 0xd0, 0x98, 0x5b, 0xa6, 0x38, 0x25, 0x34, + 0x3e, 0xfe, 0xf5, 0x13, 0x43, 0xbf, 0x7f, 0xbd, 0x3f, 0xe0, 0x1f, 0x5f, + 0x4e, 0x4b, 0x19, 0x56, 0xcb, 0xb5, 0x20, 0x0, 0x0, 0x0, 0x0, 0x49, + 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82}; +const unsigned short sGlyphWidths[256] = { + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 4, 3, 5, 7, 7, 12, + 9, 2, 4, 4, 5, 8, 4, 4, 4, 4, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 4, 4, 8, 8, 8, 7, 13, 9, 9, 9, 9, 9, 8, 10, 9, 3, 6, 9, + 7, 11, 9, 10, 9, 10, 9, 9, 7, 9, 9, 13, 7, 9, 7, 4, 4, 4, 5, + 7, 4, 7, 7, 7, 7, 7, 3, 7, 7, 3, 3, 7, 3, 11, 7, 7, 7, 7, + 4, 7, 4, 7, 5, 9, 7, 7, 7, 4, 3, 4, 8, 10, 7, 10, 3, 7, 4, + 13, 7, 7, 4, 14, 9, 4, 13, 10, 7, 10, 10, 3, 3, 4, 4, 5, 7, 13, + 4, 13, 7, 4, 12, 10, 7, 9, 4, 3, 7, 7, 7, 7, 3, 7, 4, 10, 4, + 7, 8, 4, 10, 7, 5, 7, 4, 4, 4, 7, 7, 4, 4, 4, 5, 7, 11, 11, + 11, 8, 9, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 3, 3, 3, 3, 9, + 9, 10, 10, 10, 10, 10, 8, 10, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, + 7, 7, 12, 7, 7, 7, 7, 7, 3, 3, 3, 3, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7}; +} // namespace normal_font + +const FontBitmapInfo sDefaultCompositorFont = { + mozilla::Nothing(), + mozilla::Some(&normal_font::sGlyphWidths[0]), + 256, + 256, + 16, + 16, + 0, + normal_font::sFontPNG, + sizeof(normal_font::sFontPNG)}; + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/FrameUniformityData.cpp b/gfx/layers/composite/FrameUniformityData.cpp new file mode 100644 index 0000000000..397ec0e1dc --- /dev/null +++ b/gfx/layers/composite/FrameUniformityData.cpp @@ -0,0 +1,145 @@ +/* -*- 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 "FrameUniformityData.h" + +#include <map> + +#include "Units.h" +#include "gfxPoint.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/dom/APZTestDataBinding.h" +#include "mozilla/dom/ToJSValue.h" +#include "nsTArray.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +Point LayerTransforms::GetAverage() { + MOZ_ASSERT(!mTransforms.IsEmpty()); + + Point current = mTransforms[0]; + Point average; + size_t length = mTransforms.Length(); + + for (size_t i = 1; i < length; i++) { + Point nextTransform = mTransforms[i]; + Point movement = nextTransform - current; + average += Point(std::fabs(movement.x), std::fabs(movement.y)); + current = nextTransform; + } + + average = average / (float)length; + return average; +} + +Point LayerTransforms::GetStdDev() { + Point average = GetAverage(); + Point stdDev; + Point current = mTransforms[0]; + + for (size_t i = 1; i < mTransforms.Length(); i++) { + Point next = mTransforms[i]; + Point move = next - current; + move.x = fabs(move.x); + move.y = fabs(move.y); + + Point diff = move - average; + diff.x = diff.x * diff.x; + diff.y = diff.y * diff.y; + stdDev += diff; + + current = next; + } + + stdDev = stdDev / mTransforms.Length(); + stdDev.x = sqrt(stdDev.x); + stdDev.y = sqrt(stdDev.y); + return stdDev; +} + +bool LayerTransforms::Sanitize() { + // Remove leading and trailing zeros to isolate the composites that actually + // changed the transform + for (size_t i = 1; i < mTransforms.Length(); i++) { + if (mTransforms[i] != mTransforms[i - 1]) { + mTransforms.RemoveElementsAt(0, i - 1); + break; + } + } + for (size_t i = mTransforms.Length() - 1; i > 0; i--) { + if (mTransforms[i - 1] != mTransforms[i]) { + mTransforms.SetLength(i + 1); + break; + } + } + return !mTransforms.IsEmpty(); +} + +LayerTransformRecorder::~LayerTransformRecorder() { Reset(); } + +void LayerTransformRecorder::RecordTransform(Layer* aLayer, + const Point& aTransform) { + LayerTransforms* layerTransforms = GetLayerTransforms((uintptr_t)aLayer); + layerTransforms->mTransforms.AppendElement(aTransform); +} + +void LayerTransformRecorder::EndTest(FrameUniformityData* aOutData) { + for (const auto& [layer, transforms] : mFrameTransforms) { + (void)transforms; // suppress unused variable warning + + float uniformity = CalculateFrameUniformity(layer); + + std::pair<uintptr_t, float> result(layer, uniformity); + aOutData->mUniformities.insert(result); + } + + Reset(); +} + +LayerTransforms* LayerTransformRecorder::GetLayerTransforms(uintptr_t aLayer) { + auto [iter, inserted] = + mFrameTransforms.insert(FrameTransformMap::value_type{aLayer, nullptr}); + if (inserted) { + iter->second = MakeUnique<LayerTransforms>(); + } + return iter->second.get(); +} + +void LayerTransformRecorder::Reset() { mFrameTransforms.clear(); } + +float LayerTransformRecorder::CalculateFrameUniformity(uintptr_t aLayer) { + LayerTransforms* layerTransform = GetLayerTransforms(aLayer); + float yUniformity = -1; + if (layerTransform->Sanitize()) { + Point stdDev = layerTransform->GetStdDev(); + yUniformity = stdDev.y; + } + return yUniformity; +} + +bool FrameUniformityData::ToJS(JS::MutableHandleValue aOutValue, + JSContext* aContext) { + dom::FrameUniformityResults results; + dom::Sequence<dom::FrameUniformity>& layers = + results.mLayerUniformities.Construct(); + + for (const auto& [layerAddr, uniformity] : mUniformities) { + // FIXME: Make this infallible after bug 968520 is done. + MOZ_ALWAYS_TRUE(layers.AppendElement(fallible)); + dom::FrameUniformity& entry = layers.LastElement(); + + entry.mLayerAddress.Construct() = layerAddr; + entry.mFrameUniformity.Construct() = uniformity; + } + + return dom::ToJSValue(aContext, results, aOutValue); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/FrameUniformityData.h b/gfx/layers/composite/FrameUniformityData.h new file mode 100644 index 0000000000..8cb639e60c --- /dev/null +++ b/gfx/layers/composite/FrameUniformityData.h @@ -0,0 +1,80 @@ +/* -*- 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/. */ + +#ifndef mozilla_layers_FrameUniformityData_h_ +#define mozilla_layers_FrameUniformityData_h_ + +#include "ipc/IPCMessageUtils.h" +#include "js/TypeDecls.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/Point.h" +#include "nsTArray.h" + +namespace mozilla { +namespace layers { +class Layer; + +class FrameUniformityData { + friend struct IPC::ParamTraits<FrameUniformityData>; + + public: + bool ToJS(JS::MutableHandleValue aOutValue, JSContext* aContext); + // Contains the calculated frame uniformities + std::map<uintptr_t, float> mUniformities; +}; + +struct LayerTransforms { + LayerTransforms() = default; + + gfx::Point GetAverage(); + gfx::Point GetStdDev(); + bool Sanitize(); + + // 60 fps * 5 seconds worth of data + AutoTArray<gfx::Point, 300> mTransforms; +}; + +class LayerTransformRecorder { + public: + LayerTransformRecorder() = default; + ~LayerTransformRecorder(); + + void RecordTransform(Layer* aLayer, const gfx::Point& aTransform); + void Reset(); + void EndTest(FrameUniformityData* aOutData); + + private: + float CalculateFrameUniformity(uintptr_t aLayer); + LayerTransforms* GetLayerTransforms(uintptr_t aLayer); + using FrameTransformMap = + std::map<uintptr_t, mozilla::UniquePtr<LayerTransforms>>; + FrameTransformMap mFrameTransforms; +}; + +} // namespace layers +} // namespace mozilla + +namespace IPC { +template <> +struct ParamTraits<mozilla::layers::FrameUniformityData> { + typedef mozilla::layers::FrameUniformityData paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mUniformities); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ParamTraitsStd<std::map<uintptr_t, float>>::Read( + aMsg, aIter, &aResult->mUniformities); + } +}; + +} // namespace IPC + +#endif // mozilla_layers_FrameUniformityData_h_ diff --git a/gfx/layers/composite/GPUVideoTextureHost.cpp b/gfx/layers/composite/GPUVideoTextureHost.cpp new file mode 100644 index 0000000000..ed6be1972d --- /dev/null +++ b/gfx/layers/composite/GPUVideoTextureHost.cpp @@ -0,0 +1,295 @@ +/* -*- 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 "GPUVideoTextureHost.h" + +#include "ImageContainer.h" +#include "mozilla/RemoteDecoderManagerParent.h" +#include "mozilla/layers/ImageBridgeParent.h" +#include "mozilla/layers/VideoBridgeParent.h" +#include "mozilla/webrender/RenderTextureHostWrapper.h" +#include "mozilla/webrender/RenderThread.h" + +namespace mozilla { +namespace layers { + +GPUVideoTextureHost::GPUVideoTextureHost( + TextureFlags aFlags, const SurfaceDescriptorGPUVideo& aDescriptor) + : TextureHost(aFlags), mDescriptor(aDescriptor) { + MOZ_COUNT_CTOR(GPUVideoTextureHost); +} + +GPUVideoTextureHost::~GPUVideoTextureHost() { + MOZ_COUNT_DTOR(GPUVideoTextureHost); +} + +GPUVideoTextureHost* GPUVideoTextureHost::CreateFromDescriptor( + TextureFlags aFlags, const SurfaceDescriptorGPUVideo& aDescriptor) { + return new GPUVideoTextureHost(aFlags, aDescriptor); +} + +TextureHost* GPUVideoTextureHost::EnsureWrappedTextureHost() { + if (mWrappedTextureHost) { + return mWrappedTextureHost; + } + + const auto& sd = + static_cast<const SurfaceDescriptorRemoteDecoder&>(mDescriptor); + VideoBridgeParent* parent = VideoBridgeParent::GetSingleton(sd.source()); + if (!parent) { + // The VideoBridge went away. This can happen if the RDD process + // crashes. + return nullptr; + } + mWrappedTextureHost = parent->LookupTexture(sd.handle()); + + if (!mWrappedTextureHost) { + // The TextureHost hasn't been registered yet. This is due to a race + // between the ImageBridge (content) and the VideoBridge (RDD) and the + // ImageBridge won. See bug + // https://bugzilla.mozilla.org/show_bug.cgi?id=1630733#c14 for more + // details. + return nullptr; + } + + if (mWrappedTextureHost->AsBufferTextureHost()) { + // TODO(miko): This code path is taken when WebRenderTextureHost wraps + // GPUVideoTextureHost, which wraps BufferTextureHost. + // Because this creates additional copies of the texture data, we should not + // do this. + mWrappedTextureHost->AsBufferTextureHost()->DisableExternalTextures(); + } + + if (mExternalImageId.isSome()) { + // External image id is allocated by mWrappedTextureHost. + mWrappedTextureHost->EnsureRenderTexture(Nothing()); + MOZ_ASSERT(mWrappedTextureHost->mExternalImageId.isSome()); + auto wrappedId = mWrappedTextureHost->mExternalImageId.ref(); + + RefPtr<wr::RenderTextureHost> texture = + new wr::RenderTextureHostWrapper(wrappedId); + wr::RenderThread::Get()->RegisterExternalImage( + wr::AsUint64(mExternalImageId.ref()), texture.forget()); + } + + if (mPendingSourceProvider) { + RefPtr<TextureSourceProvider> provider = mPendingSourceProvider.forget(); + mWrappedTextureHost->SetTextureSourceProvider(provider); + } + if (mPendingUpdatedInternal) { + mWrappedTextureHost->UpdatedInternal(mPendingIntRegion.ptrOr(nullptr)); + mPendingIntRegion.reset(); + mPendingUpdatedInternal = false; + } + if (mPendingPrepareTextureSource) { + mWrappedTextureHost->PrepareTextureSource(*mPendingPrepareTextureSource); + mPendingPrepareTextureSource.reset(); + } + + return mWrappedTextureHost; +} + +bool GPUVideoTextureHost::IsValid() { return !!EnsureWrappedTextureHost(); } + +bool GPUVideoTextureHost::Lock() { + if (!EnsureWrappedTextureHost()) { + return false; + } + return EnsureWrappedTextureHost()->Lock(); +} + +void GPUVideoTextureHost::Unlock() { + if (!EnsureWrappedTextureHost()) { + return; + } + EnsureWrappedTextureHost()->Unlock(); +} + +void GPUVideoTextureHost::PrepareTextureSource( + CompositableTextureSourceRef& aTexture) { + if (!EnsureWrappedTextureHost()) { + mPendingPrepareTextureSource = Some(aTexture); + return; + } + EnsureWrappedTextureHost()->PrepareTextureSource(aTexture); +} + +bool GPUVideoTextureHost::BindTextureSource( + CompositableTextureSourceRef& aTexture) { + MOZ_ASSERT(EnsureWrappedTextureHost(), "Image isn't valid yet"); + if (!EnsureWrappedTextureHost()) { + return false; + } + return EnsureWrappedTextureHost()->BindTextureSource(aTexture); +} + +bool GPUVideoTextureHost::AcquireTextureSource( + CompositableTextureSourceRef& aTexture) { + MOZ_ASSERT(EnsureWrappedTextureHost(), "Image isn't valid yet"); + if (!EnsureWrappedTextureHost()) { + return false; + } + return EnsureWrappedTextureHost()->AcquireTextureSource(aTexture); +} + +void GPUVideoTextureHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (!EnsureWrappedTextureHost()) { + mPendingSourceProvider = aProvider; + return; + } + EnsureWrappedTextureHost()->SetTextureSourceProvider(aProvider); +} + +gfx::YUVColorSpace GPUVideoTextureHost::GetYUVColorSpace() const { + MOZ_ASSERT(mWrappedTextureHost, "Image isn't valid yet"); + if (!mWrappedTextureHost) { + return gfx::YUVColorSpace::UNKNOWN; + } + return mWrappedTextureHost->GetYUVColorSpace(); +} + +gfx::ColorDepth GPUVideoTextureHost::GetColorDepth() const { + MOZ_ASSERT(mWrappedTextureHost, "Image isn't valid yet"); + if (!mWrappedTextureHost) { + return TextureHost::GetColorDepth(); + } + return mWrappedTextureHost->GetColorDepth(); +} + +gfx::ColorRange GPUVideoTextureHost::GetColorRange() const { + MOZ_ASSERT(mWrappedTextureHost, "Image isn't valid yet"); + if (!mWrappedTextureHost) { + return TextureHost::GetColorRange(); + } + return mWrappedTextureHost->GetColorRange(); +} + +gfx::IntSize GPUVideoTextureHost::GetSize() const { + MOZ_ASSERT(mWrappedTextureHost, "Image isn't valid yet"); + if (!mWrappedTextureHost) { + return gfx::IntSize(); + } + return mWrappedTextureHost->GetSize(); +} + +gfx::SurfaceFormat GPUVideoTextureHost::GetFormat() const { + MOZ_ASSERT(mWrappedTextureHost, "Image isn't valid yet"); + if (!mWrappedTextureHost) { + return gfx::SurfaceFormat::UNKNOWN; + } + return mWrappedTextureHost->GetFormat(); +} + +bool GPUVideoTextureHost::HasIntermediateBuffer() const { + MOZ_ASSERT(mWrappedTextureHost, "Image isn't valid yet"); + if (!mWrappedTextureHost) { + return false; + } + return mWrappedTextureHost->HasIntermediateBuffer(); +} + +void GPUVideoTextureHost::UpdatedInternal(const nsIntRegion* Region) { + if (!EnsureWrappedTextureHost()) { + mPendingUpdatedInternal = true; + if (Region) { + mPendingIntRegion = Some(*Region); + } else { + mPendingIntRegion.reset(); + } + return; + } + EnsureWrappedTextureHost()->UpdatedInternal(Region); +} + +void GPUVideoTextureHost::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + MOZ_ASSERT(mExternalImageId.isSome()); + + // When mWrappedTextureHost already exist, call CreateRenderTexture() here. + // In other cases, EnsureWrappedTextureHost() handles CreateRenderTexture(). + + if (mWrappedTextureHost) { + // External image id is allocated by mWrappedTextureHost. + mWrappedTextureHost->EnsureRenderTexture(Nothing()); + MOZ_ASSERT(mWrappedTextureHost->mExternalImageId.isSome()); + auto wrappedId = mWrappedTextureHost->mExternalImageId.ref(); + + RefPtr<wr::RenderTextureHost> texture = + new wr::RenderTextureHostWrapper(wrappedId); + wr::RenderThread::Get()->RegisterExternalImage( + wr::AsUint64(mExternalImageId.ref()), texture.forget()); + return; + } + + EnsureWrappedTextureHost(); +} + +void GPUVideoTextureHost::MaybeDestroyRenderTexture() { + if (mExternalImageId.isNothing() || !mWrappedTextureHost) { + // RenderTextureHost was not created + return; + } + // When GPUVideoTextureHost created RenderTextureHost, delete it here. + TextureHost::DestroyRenderTexture(mExternalImageId.ref()); +} + +uint32_t GPUVideoTextureHost::NumSubTextures() { + if (!EnsureWrappedTextureHost()) { + return 0; + } + return EnsureWrappedTextureHost()->NumSubTextures(); +} + +void GPUVideoTextureHost::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, const wr::ExternalImageId& aExtID) { + MOZ_ASSERT(EnsureWrappedTextureHost(), "Image isn't valid yet"); + if (!EnsureWrappedTextureHost()) { + return; + } + EnsureWrappedTextureHost()->PushResourceUpdates(aResources, aOp, aImageKeys, + aExtID); +} + +void GPUVideoTextureHost::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, PushDisplayItemFlagSet aFlags) { + MOZ_ASSERT(EnsureWrappedTextureHost(), "Image isn't valid yet"); + MOZ_ASSERT(aImageKeys.length() > 0); + if (!EnsureWrappedTextureHost()) { + return; + } + + EnsureWrappedTextureHost()->PushDisplayItems(aBuilder, aBounds, aClip, + aFilter, aImageKeys, aFlags); +} + +bool GPUVideoTextureHost::SupportsExternalCompositing() { + if (!EnsureWrappedTextureHost()) { + return false; + } + return EnsureWrappedTextureHost()->SupportsExternalCompositing(); +} + +void GPUVideoTextureHost::UnbindTextureSource() { + if (EnsureWrappedTextureHost()) { + EnsureWrappedTextureHost()->UnbindTextureSource(); + } + // Handle read unlock + TextureHost::UnbindTextureSource(); +} + +void GPUVideoTextureHost::NotifyNotUsed() { + if (EnsureWrappedTextureHost()) { + EnsureWrappedTextureHost()->NotifyNotUsed(); + } + TextureHost::NotifyNotUsed(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/GPUVideoTextureHost.h b/gfx/layers/composite/GPUVideoTextureHost.h new file mode 100644 index 0000000000..1346d61ccd --- /dev/null +++ b/gfx/layers/composite/GPUVideoTextureHost.h @@ -0,0 +1,98 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_GPUVIDEOTEXTUREHOST_H +#define MOZILLA_GFX_GPUVIDEOTEXTUREHOST_H + +#include "mozilla/layers/TextureHost.h" + +namespace mozilla { +namespace layers { + +class GPUVideoTextureHost : public TextureHost { + public: + static GPUVideoTextureHost* CreateFromDescriptor( + TextureFlags aFlags, const SurfaceDescriptorGPUVideo& aDescriptor); + + virtual ~GPUVideoTextureHost(); + + void DeallocateDeviceData() override {} + + virtual void SetTextureSourceProvider( + TextureSourceProvider* aProvider) override; + + bool Lock() override; + + void Unlock() override; + + gfx::SurfaceFormat GetFormat() const override; + + void PrepareTextureSource(CompositableTextureSourceRef& aTexture) override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override; + bool AcquireTextureSource(CompositableTextureSourceRef& aTexture) override; + + already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gfx::YUVColorSpace GetYUVColorSpace() const override; + gfx::ColorDepth GetColorDepth() const override; + gfx::ColorRange GetColorRange() const override; + + gfx::IntSize GetSize() const override; + + bool IsValid() override; + +#ifdef MOZ_LAYERS_HAVE_LOG + const char* Name() override { return "GPUVideoTextureHost"; } +#endif + + bool HasIntermediateBuffer() const override; + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + void MaybeDestroyRenderTexture() override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + bool SupportsExternalCompositing() override; + + void UnbindTextureSource() override; + void NotifyNotUsed() override; + + protected: + GPUVideoTextureHost(TextureFlags aFlags, + const SurfaceDescriptorGPUVideo& aDescriptor); + + TextureHost* EnsureWrappedTextureHost(); + + void UpdatedInternal(const nsIntRegion* Region) override; + + RefPtr<TextureHost> mWrappedTextureHost; + RefPtr<TextureSourceProvider> mPendingSourceProvider; + bool mPendingUpdatedInternal = false; + Maybe<nsIntRegion> mPendingIntRegion; + Maybe<CompositableTextureSourceRef> mPendingPrepareTextureSource; + SurfaceDescriptorGPUVideo mDescriptor; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_GPUVIDEOTEXTUREHOST_H diff --git a/gfx/layers/composite/ImageComposite.cpp b/gfx/layers/composite/ImageComposite.cpp new file mode 100644 index 0000000000..20e4ee2b5c --- /dev/null +++ b/gfx/layers/composite/ImageComposite.cpp @@ -0,0 +1,385 @@ +/* -*- 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 "ImageComposite.h" + +#include <inttypes.h> + +#include "GeckoProfiler.h" +#include "gfxPlatform.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +/* static */ const float ImageComposite::BIAS_TIME_MS = 1.0f; + +ImageComposite::ImageComposite() = default; + +ImageComposite::~ImageComposite() = default; + +TimeStamp ImageComposite::GetBiasedTime(const TimeStamp& aInput) const { + switch (mBias) { + case ImageComposite::BIAS_NEGATIVE: + return aInput - TimeDuration::FromMilliseconds(BIAS_TIME_MS); + case ImageComposite::BIAS_POSITIVE: + return aInput + TimeDuration::FromMilliseconds(BIAS_TIME_MS); + default: + return aInput; + } +} + +void ImageComposite::UpdateBias(size_t aImageIndex, bool aFrameChanged) { + MOZ_ASSERT(aImageIndex < ImagesCount()); + + TimeStamp compositionTime = GetCompositionTime(); + TimeStamp compositedImageTime = mImages[aImageIndex].mTimeStamp; + TimeStamp nextImageTime = aImageIndex + 1 < ImagesCount() + ? mImages[aImageIndex + 1].mTimeStamp + : TimeStamp(); + +#if MOZ_GECKO_PROFILER + if (profiler_can_accept_markers() && compositedImageTime && nextImageTime) { + TimeDuration offsetCurrent = compositedImageTime - compositionTime; + TimeDuration offsetNext = nextImageTime - compositionTime; + nsPrintfCString str("current %.2lfms, next %.2lfms", + offsetCurrent.ToMilliseconds(), + offsetNext.ToMilliseconds()); + PROFILER_MARKER_TEXT("Video frame offsets", GRAPHICS, {}, str); + } +#endif + + if (compositedImageTime.IsNull()) { + mBias = ImageComposite::BIAS_NONE; + return; + } + TimeDuration threshold = TimeDuration::FromMilliseconds(1.5); + if (compositionTime - compositedImageTime < threshold && + compositionTime - compositedImageTime > -threshold) { + // The chosen frame's time is very close to the composition time (probably + // just before the current composition time, but due to previously set + // negative bias, it could be just after the current composition time too). + // If the inter-frame time is almost exactly equal to (a multiple of) + // the inter-composition time, then we're in a dangerous situation because + // jitter might cause frames to fall one side or the other of the + // composition times, causing many frames to be skipped or duplicated. + // Try to prevent that by adding a negative bias to the frame times during + // the next composite; that should ensure the next frame's time is treated + // as falling just before a composite time. + mBias = ImageComposite::BIAS_NEGATIVE; + return; + } + if (!nextImageTime.IsNull() && nextImageTime - compositionTime < threshold && + nextImageTime - compositionTime > -threshold) { + // The next frame's time is very close to our composition time (probably + // just after the current composition time, but due to previously set + // positive bias, it could be just before the current composition time too). + // We're in a dangerous situation because jitter might cause frames to + // fall one side or the other of the composition times, causing many frames + // to be skipped or duplicated. + // Specifically, the next composite is at risk of picking the "next + 1" + // frame rather than the "next" frame, which would cause the "next" frame to + // be skipped. Try to prevent that by adding a positive bias to the frame + // times during the next composite; if the inter-frame time is almost + // exactly equal to the inter-composition time, that should ensure that the + // next + 1 frame falls just *after* the next composition time, and the next + // composite should then pick the next frame rather than the next + 1 frame. + mBias = ImageComposite::BIAS_POSITIVE; + return; + } + if (aFrameChanged) { + // The current and next video frames are a sufficient distance from the + // composition time and we can reliably pick the right frame without bias. + // Reset the bias. + // We only do this when the frame changed. Otherwise, when playing a 30fps + // video on a 60fps display, we'd keep resetting the bias during the "middle + // frames". + mBias = ImageComposite::BIAS_NONE; + } +} + +int ImageComposite::ChooseImageIndex() { + // ChooseImageIndex is called for all images in the layer when it is visible. + // Change to this behaviour would break dropped frames counting calculation: + // We rely on this assumption to determine if during successive runs an + // image is returned that isn't the one following immediately the previous one + if (mImages.IsEmpty()) { + return -1; + } + + TimeStamp compositionTime = GetCompositionTime(); + auto compositionOpportunityId = GetCompositionOpportunityId(); + if (compositionTime && + compositionOpportunityId != mLastChooseImageIndexComposition) { + // We are inside a composition, in the first call to ChooseImageIndex during + // this composition. + // Find the newest frame whose biased timestamp is at or before + // `compositionTime`. + uint32_t imageIndex = 0; + while (imageIndex + 1 < mImages.Length() && + mImages[imageIndex + 1].mTextureHost->IsValid() && + GetBiasedTime(mImages[imageIndex + 1].mTimeStamp) <= + compositionTime) { + ++imageIndex; + } + + if (!mImages[imageIndex].mTextureHost->IsValid()) { + // Still not ready to be shown. + return -1; + } + + bool wasVisibleAtPreviousComposition = + compositionOpportunityId == mLastChooseImageIndexComposition.Next(); + + bool frameChanged = + UpdateCompositedFrame(imageIndex, wasVisibleAtPreviousComposition); + UpdateBias(imageIndex, frameChanged); + + mLastChooseImageIndexComposition = compositionOpportunityId; + + return imageIndex; + } + + // We've been called before during this composition, or we're not in a + // composition. Just return the last image we picked (if it's one of the + // current images). + for (uint32_t i = 0; i < mImages.Length(); ++i) { + if (mImages[i].mFrameID == mLastFrameID && + mImages[i].mProducerID == mLastProducerID) { + return i; + } + } + + return 0; +} + +const ImageComposite::TimedImage* ImageComposite::ChooseImage() { + int index = ChooseImageIndex(); + return index >= 0 ? &mImages[index] : nullptr; +} + +void ImageComposite::RemoveImagesWithTextureHost(TextureHost* aTexture) { + for (int32_t i = mImages.Length() - 1; i >= 0; --i) { + if (mImages[i].mTextureHost == aTexture) { + aTexture->UnbindTextureSource(); + mImages.RemoveElementAt(i); + } + } +} + +void ImageComposite::ClearImages() { mImages.Clear(); } + +void ImageComposite::SetImages(nsTArray<TimedImage>&& aNewImages) { + if (!aNewImages.IsEmpty()) { + DetectTimeStampJitter(&aNewImages[0]); + + // Frames older than the first frame in aNewImages that we haven't shown yet + // will never be shown. + CountSkippedFrames(&aNewImages[0]); + +#if MOZ_GECKO_PROFILER + if (profiler_can_accept_markers()) { + int len = aNewImages.Length(); + const auto& first = aNewImages[0]; + const auto& last = aNewImages.LastElement(); + nsPrintfCString str("%d %s, frameID %" PRId32 " (prod %" PRId32 + ") to frameID %" PRId32 " (prod %" PRId32 ")", + len, len == 1 ? "image" : "images", first.mFrameID, + first.mProducerID, last.mFrameID, last.mProducerID); + PROFILER_MARKER_TEXT("ImageComposite::SetImages", GRAPHICS, {}, str); + } +#endif + } + mImages = std::move(aNewImages); +} + +// Returns whether the frame changed. +bool ImageComposite::UpdateCompositedFrame( + int aImageIndex, bool aWasVisibleAtPreviousComposition) { + MOZ_RELEASE_ASSERT(aImageIndex >= 0); + MOZ_RELEASE_ASSERT(aImageIndex < static_cast<int>(mImages.Length())); + const TimedImage& image = mImages[aImageIndex]; + + auto compositionOpportunityId = GetCompositionOpportunityId(); + TimeStamp compositionTime = GetCompositionTime(); + MOZ_RELEASE_ASSERT(compositionTime, + "Should only be called during a composition"); + +#if MOZ_GECKO_PROFILER + nsCString descr; + if (profiler_can_accept_markers()) { + nsCString relativeTimeString; + if (image.mTimeStamp) { + relativeTimeString.AppendPrintf( + " [relative timestamp %.1lfms]", + (image.mTimeStamp - compositionTime).ToMilliseconds()); + } + int remainingImages = mImages.Length() - 1 - aImageIndex; + static const char* kBiasStrings[] = {"NONE", "NEGATIVE", "POSITIVE"}; + descr.AppendPrintf( + "frameID %" PRId32 " (producerID %" PRId32 ") [composite %" PRIu64 + "] [bias %s] [%d remaining %s]%s", + image.mFrameID, image.mProducerID, compositionOpportunityId.mId, + kBiasStrings[mBias], remainingImages, + remainingImages == 1 ? "image" : "images", relativeTimeString.get()); + if (mLastProducerID != image.mProducerID) { + descr.AppendPrintf(", previous producerID: %" PRId32, mLastProducerID); + } else if (mLastFrameID != image.mFrameID) { + descr.AppendPrintf(", previous frameID: %" PRId32, mLastFrameID); + } else { + descr.AppendLiteral(", no change"); + } + } + PROFILER_MARKER_TEXT("UpdateCompositedFrame", GRAPHICS, {}, descr); +#endif + + if (mLastFrameID == image.mFrameID && mLastProducerID == image.mProducerID) { + // The frame didn't change. + return false; + } + + CountSkippedFrames(&image); + + int32_t dropped = mSkippedFramesSinceLastComposite; + mSkippedFramesSinceLastComposite = 0; + + if (!aWasVisibleAtPreviousComposition) { + // This video was not part of the on-screen scene during the previous + // composition opportunity, for example it may have been scrolled off-screen + // or in a background tab, or compositing might have been paused. + // Ignore any skipped frames and don't count them as dropped. + dropped = 0; + } + + if (dropped > 0) { + mDroppedFrames += dropped; +#if MOZ_GECKO_PROFILER + if (profiler_can_accept_markers()) { + const char* frameOrFrames = dropped == 1 ? "frame" : "frames"; + nsPrintfCString text("%" PRId32 " %s dropped: %" PRId32 " -> %" PRId32 + " (producer %" PRId32 ")", + dropped, frameOrFrames, mLastFrameID, image.mFrameID, + mLastProducerID); + PROFILER_MARKER_TEXT("Video frames dropped", GRAPHICS, {}, text); + } +#endif + } + + mLastFrameID = image.mFrameID; + mLastProducerID = image.mProducerID; + mLastFrameUpdateComposition = compositionOpportunityId; + + return true; +} + +void ImageComposite::OnFinishRendering(int aImageIndex, + const TimedImage* aImage, + base::ProcessId aProcessId, + const CompositableHandle& aHandle) { + if (mLastFrameUpdateComposition != GetCompositionOpportunityId()) { + // The frame did not change in this composition. + return; + } + + if (aHandle) { + ImageCompositeNotificationInfo info; + info.mImageBridgeProcessId = aProcessId; + info.mNotification = ImageCompositeNotification( + aHandle, aImage->mTimeStamp, GetCompositionTime(), mLastFrameID, + mLastProducerID); + AppendImageCompositeNotification(info); + } +} + +const ImageComposite::TimedImage* ImageComposite::GetImage( + size_t aIndex) const { + if (aIndex >= mImages.Length()) { + return nullptr; + } + return &mImages[aIndex]; +} + +void ImageComposite::CountSkippedFrames(const TimedImage* aImage) { + if (aImage->mProducerID != mLastProducerID) { + // Switched producers. + return; + } + + if (mImages.IsEmpty() || aImage->mFrameID <= mLastFrameID + 1) { + // No frames were skipped. + return; + } + + uint32_t targetFrameRate = gfxPlatform::TargetFrameRate(); + if (targetFrameRate == 0) { + // Can't know whether we could have reasonably displayed all video frames. + return; + } + + double targetFrameDurationMS = 1000.0 / targetFrameRate; + + // Count how many images in mImages were skipped between mLastFrameID and + // aImage.mFrameID. Only count frames for which we can estimate a duration by + // looking at the next frame's timestamp, and only if the video frame rate is + // no faster than the target frame rate. + int32_t skipped = 0; + for (size_t i = 0; i + 1 < mImages.Length(); i++) { + const auto& img = mImages[i]; + if (img.mProducerID != aImage->mProducerID || + img.mFrameID <= mLastFrameID || img.mFrameID >= aImage->mFrameID) { + continue; + } + + // We skipped img! Estimate img's time duration. + const auto& next = mImages[i + 1]; + if (next.mProducerID != aImage->mProducerID) { + continue; + } + + MOZ_ASSERT(next.mFrameID > img.mFrameID); + TimeDuration duration = next.mTimeStamp - img.mTimeStamp; + if (floor(duration.ToMilliseconds()) >= floor(targetFrameDurationMS)) { + // Count the frame. + skipped++; + } + } + + mSkippedFramesSinceLastComposite += skipped; +} + +void ImageComposite::DetectTimeStampJitter(const TimedImage* aNewImage) { +#if MOZ_GECKO_PROFILER + if (!profiler_can_accept_markers() || aNewImage->mTimeStamp.IsNull()) { + return; + } + + // Find aNewImage in mImages and compute its timestamp delta, if found. + // Ideally, a given video frame should never change its timestamp (jitter + // should be zero). However, we re-adjust video frame timestamps based on the + // audio clock. If the audio clock drifts compared to the system clock, or if + // there are bugs or inaccuracies in the computation of these timestamps, + // jitter will be non-zero. + Maybe<TimeDuration> jitter; + for (const auto& img : mImages) { + if (img.mProducerID == aNewImage->mProducerID && + img.mFrameID == aNewImage->mFrameID) { + if (!img.mTimeStamp.IsNull()) { + jitter = Some(aNewImage->mTimeStamp - img.mTimeStamp); + } + break; + } + } + if (jitter) { + nsPrintfCString text("%.2lfms", jitter->ToMilliseconds()); + PROFILER_MARKER_TEXT("VideoFrameTimeStampJitter", GRAPHICS, {}, text); + } +#endif +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ImageComposite.h b/gfx/layers/composite/ImageComposite.h new file mode 100644 index 0000000000..05e812c8b3 --- /dev/null +++ b/gfx/layers/composite/ImageComposite.h @@ -0,0 +1,139 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_IMAGECOMPOSITE_H +#define MOZILLA_GFX_IMAGECOMPOSITE_H + +#include "CompositableHost.h" // for CompositableTextureHostRef +#include "mozilla/gfx/2D.h" +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "nsTArray.h" + +namespace mozilla { +namespace layers { + +/** + * Implements Image selection logic. + */ +class ImageComposite { + public: + static const float BIAS_TIME_MS; + + explicit ImageComposite(); + virtual ~ImageComposite(); + + int32_t GetFrameID() { + const TimedImage* img = ChooseImage(); + return img ? img->mFrameID : -1; + } + + int32_t GetProducerID() { + const TimedImage* img = ChooseImage(); + return img ? img->mProducerID : -1; + } + + int32_t GetLastFrameID() const { return mLastFrameID; } + int32_t GetLastProducerID() const { return mLastProducerID; } + uint32_t GetDroppedFramesAndReset() { + uint32_t dropped = mDroppedFrames; + mDroppedFrames = 0; + return dropped; + } + + enum Bias { + // Don't apply bias to frame times + BIAS_NONE, + // Apply a negative bias to frame times to keep them before the vsync time + BIAS_NEGATIVE, + // Apply a positive bias to frame times to keep them after the vsync time + BIAS_POSITIVE, + }; + + protected: + virtual TimeStamp GetCompositionTime() const = 0; + virtual CompositionOpportunityId GetCompositionOpportunityId() const = 0; + virtual void AppendImageCompositeNotification( + const ImageCompositeNotificationInfo& aInfo) const = 0; + + struct TimedImage { + CompositableTextureHostRef mTextureHost; + TimeStamp mTimeStamp; + gfx::IntRect mPictureRect; + int32_t mFrameID; + int32_t mProducerID; + }; + + /** + * ChooseImage is guaranteed to return the same TimedImage every time it's + * called during the same composition, up to the end of Composite() --- + * it depends only on mImages, mCompositor->GetCompositionTime(), and mBias. + * mBias is updated at the end of Composite(). + */ + const TimedImage* ChooseImage(); + int ChooseImageIndex(); + const TimedImage* GetImage(size_t aIndex) const; + size_t ImagesCount() const { return mImages.Length(); } + const nsTArray<TimedImage>& Images() const { return mImages; } + + void RemoveImagesWithTextureHost(TextureHost* aTexture); + void ClearImages(); + void SetImages(nsTArray<TimedImage>&& aNewImages); + + protected: + // Send ImageComposite notifications and update the ChooseImage bias. + void OnFinishRendering(int aImageIndex, const TimedImage* aImage, + base::ProcessId aProcessId, + const CompositableHandle& aHandle); + + int32_t mLastFrameID = -1; + int32_t mLastProducerID = -1; + + private: + nsTArray<TimedImage> mImages; + TimeStamp GetBiasedTime(const TimeStamp& aInput) const; + + // Called when we know that frames older than aImage will never get another + // chance to be displayed. + // Counts frames in mImages that were skipped, and adds the skipped count to + // mSkippedFramesSinceLastComposite. + void CountSkippedFrames(const TimedImage* aImage); + + // Update mLastFrameID and mLastProducerID, and report dropped frames. + // Returns whether the frame changed. + bool UpdateCompositedFrame(int aImageIndex, + bool aWasVisibleAtPreviousComposition); + + void UpdateBias(size_t aImageIndex, bool aFrameUpdated); + + // Emit a profiler marker about video frame timestamp jitter. + void DetectTimeStampJitter(const TimedImage* aNewImage); + + /** + * Bias to apply to the next frame. + */ + Bias mBias = BIAS_NONE; + + // Video frames that were in mImages but that will never reach the screen. + // This count is reset in UpdateCompositedFrame. + int32_t mSkippedFramesSinceLastComposite = 0; + + // The number of dropped frames since the last call to + // GetDroppedFramesAndReset(). Not all skipped frames are considered "dropped" + // - for example, videos that are scrolled out of view or videos in background + // tabs are expected to skip frames, and those skipped frames are not counted + // as dropped frames. + uint32_t mDroppedFrames = 0; + + // The composition opportunity IDs of the last calls to ChooseImageIndex and + // UpdateCompositedFrame, respectively. + CompositionOpportunityId mLastChooseImageIndexComposition; + CompositionOpportunityId mLastFrameUpdateComposition; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_IMAGECOMPOSITE_H diff --git a/gfx/layers/composite/ImageHost.cpp b/gfx/layers/composite/ImageHost.cpp new file mode 100644 index 0000000000..3c23dc432b --- /dev/null +++ b/gfx/layers/composite/ImageHost.cpp @@ -0,0 +1,454 @@ +/* -*- 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 "ImageHost.h" + +#include <utility> + +#include "composite/CompositableHost.h" // for CompositableHost, etc +#include "ipc/IPCMessageUtils.h" // for null_t +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/Effects.h" // for TexturedEffect, Effect, etc +#include "mozilla/layers/LayerManagerComposite.h" // for TexturedEffect, Effect, etc +#include "nsAString.h" +#include "nsDebug.h" // for NS_WARNING, NS_ASSERTION +#include "nsPrintfCString.h" // for nsPrintfCString +#include "nsString.h" // for nsAutoCString + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +class ISurfaceAllocator; + +ImageHost::ImageHost(const TextureInfo& aTextureInfo) + : CompositableHost(aTextureInfo), ImageComposite(), mLocked(false) {} + +ImageHost::~ImageHost() = default; + +void ImageHost::UseTextureHost(const nsTArray<TimedTexture>& aTextures) { + MOZ_ASSERT(!mLocked); + + CompositableHost::UseTextureHost(aTextures); + MOZ_ASSERT(aTextures.Length() >= 1); + + nsTArray<TimedImage> newImages; + + for (uint32_t i = 0; i < aTextures.Length(); ++i) { + const TimedTexture& t = aTextures[i]; + MOZ_ASSERT(t.mTexture); + if (i + 1 < aTextures.Length() && t.mProducerID == mLastProducerID && + t.mFrameID < mLastFrameID) { + // Ignore frames before a frame that we already composited. We don't + // ever want to display these frames. This could be important if + // the frame producer adjusts timestamps (e.g. to track the audio clock) + // and the new frame times are earlier. + continue; + } + TimedImage& img = *newImages.AppendElement(); + img.mTextureHost = t.mTexture; + img.mTimeStamp = t.mTimeStamp; + img.mPictureRect = t.mPictureRect; + img.mFrameID = t.mFrameID; + img.mProducerID = t.mProducerID; + img.mTextureHost->SetCropRect(img.mPictureRect); + img.mTextureHost->Updated(); + } + + SetImages(std::move(newImages)); + + // If we only have one image we can upload it right away, otherwise we'll + // upload on-demand during composition after we have picked the proper + // timestamp. + if (ImagesCount() == 1) { + SetCurrentTextureHost(GetImage(0)->mTextureHost); + } + + HostLayerManager* lm = GetLayerManager(); + + // Video producers generally send replacement images with the same frameID but + // slightly different timestamps in order to sync with the audio clock. This + // means that any CompositeUntil() call we made in Composite() may no longer + // guarantee that we'll composite until the next frame is ready. Fix that + // here. + if (lm && mLastFrameID >= 0) { + for (const auto& img : Images()) { + bool frameComesAfter = + img.mFrameID > mLastFrameID || img.mProducerID != mLastProducerID; + if (frameComesAfter && !img.mTimeStamp.IsNull()) { + lm->CompositeUntil(img.mTimeStamp + + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); + break; + } + } + } +} + +void ImageHost::SetCurrentTextureHost(TextureHost* aTexture) { + if (aTexture == mCurrentTextureHost.get()) { + return; + } + + bool swapTextureSources = !!mCurrentTextureHost && !!mCurrentTextureSource && + mCurrentTextureHost->IsValid() && + mCurrentTextureHost->HasIntermediateBuffer(); + + if (swapTextureSources) { + auto dataSource = mCurrentTextureSource->AsDataTextureSource(); + if (dataSource) { + // The current textureHost has an internal buffer in the form of the + // DataTextureSource. Removing the ownership of the texture source + // will enable the next texture host we bind to the texture source to + // acquire it instead of creating a new one. This is desirable in + // ImageHost because the current texture won't be used again with the + // same content. It wouldn't be desirable with ContentHost for instance, + // because the latter reuses the texture's valid regions. + dataSource->SetOwner(nullptr); + } + + RefPtr<TextureSource> tmp = mExtraTextureSource; + mExtraTextureSource = mCurrentTextureSource.get(); + mCurrentTextureSource = tmp; + } else { + mExtraTextureSource = nullptr; + } + + mCurrentTextureHost = aTexture; + mCurrentTextureHost->PrepareTextureSource(mCurrentTextureSource); +} + +void ImageHost::CleanupResources() { + mExtraTextureSource = nullptr; + mCurrentTextureSource = nullptr; + mCurrentTextureHost = nullptr; +} + +void ImageHost::RemoveTextureHost(TextureHost* aTexture) { + MOZ_ASSERT(!mLocked); + + CompositableHost::RemoveTextureHost(aTexture); + RemoveImagesWithTextureHost(aTexture); +} + +TimeStamp ImageHost::GetCompositionTime() const { + TimeStamp time; + if (HostLayerManager* lm = GetLayerManager()) { + time = lm->GetCompositionTime(); + } + return time; +} + +CompositionOpportunityId ImageHost::GetCompositionOpportunityId() const { + CompositionOpportunityId id; + if (HostLayerManager* lm = GetLayerManager()) { + id = lm->GetCompositionOpportunityId(); + } + return id; +} + +void ImageHost::AppendImageCompositeNotification( + const ImageCompositeNotificationInfo& aInfo) const { + if (HostLayerManager* lm = GetLayerManager()) { + lm->AppendImageCompositeNotification(aInfo); + } +} + +TextureHost* ImageHost::GetAsTextureHost(IntRect* aPictureRect) { + const TimedImage* img = ChooseImage(); + if (!img) { + return nullptr; + } + SetCurrentTextureHost(img->mTextureHost); + if (aPictureRect) { + *aPictureRect = img->mPictureRect; + } + return img->mTextureHost; +} + +void ImageHost::Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags) { + CompositableHost::Attach(aLayer, aProvider, aFlags); + for (const auto& img : Images()) { + img.mTextureHost->SetTextureSourceProvider(aProvider); + img.mTextureHost->Updated(); + } +} + +void ImageHost::Composite(Compositor* aCompositor, LayerComposite* aLayer, + EffectChain& aEffectChain, float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion, + const Maybe<gfx::Polygon>& aGeometry) { + RenderInfo info; + if (!PrepareToRender(aCompositor, &info)) { + return; + } + + const TimedImage* img = info.img; + + { + AutoLockCompositableHost autoLock(this); + if (autoLock.Failed()) { + NS_WARNING("failed to lock front buffer"); + return; + } + + if (!mCurrentTextureHost->BindTextureSource(mCurrentTextureSource)) { + return; + } + + if (!mCurrentTextureSource) { + // BindTextureSource above should have returned false! + MOZ_ASSERT(false); + return; + } + + bool isAlphaPremultiplied = + !(mCurrentTextureHost->GetFlags() & TextureFlags::NON_PREMULTIPLIED); + RefPtr<TexturedEffect> effect = + CreateTexturedEffect(mCurrentTextureHost, mCurrentTextureSource.get(), + aSamplingFilter, isAlphaPremultiplied); + if (!effect) { + return; + } + + if (!aCompositor->SupportsEffect(effect->mType)) { + return; + } + + DiagnosticFlags diagnosticFlags = DiagnosticFlags::IMAGE; + if (effect->mType == EffectTypes::NV12) { + diagnosticFlags |= DiagnosticFlags::NV12; + } else if (effect->mType == EffectTypes::YCBCR) { + diagnosticFlags |= DiagnosticFlags::YCBCR; + } + + aEffectChain.mPrimaryEffect = effect; + gfx::Rect pictureRect(0, 0, img->mPictureRect.Width(), + img->mPictureRect.Height()); + BigImageIterator* it = mCurrentTextureSource->AsBigImageIterator(); + if (it) { + // This iteration does not work if we have multiple texture sources here + // (e.g. 3 YCbCr textures). There's nothing preventing the different + // planes from having different resolutions or tile sizes. For example, a + // YCbCr frame could have Cb and Cr planes that are half the resolution of + // the Y plane, in such a way that the Y plane overflows the maximum + // texture size and the Cb and Cr planes do not. Then the Y plane would be + // split into multiple tiles and the Cb and Cr planes would just be one + // tile each. + // To handle the general case correctly, we'd have to create a grid of + // intersected tiles over all planes, and then draw each grid tile using + // the corresponding source tiles from all planes, with appropriate + // per-plane per-tile texture coords. + // DrawQuad currently assumes that all planes use the same texture coords. + MOZ_ASSERT( + it->GetTileCount() == 1 || !mCurrentTextureSource->GetNextSibling(), + "Can't handle multi-plane BigImages"); + + it->BeginBigImageIteration(); + do { + IntRect tileRect = it->GetTileRect(); + gfx::Rect rect(tileRect.X(), tileRect.Y(), tileRect.Width(), + tileRect.Height()); + rect = rect.Intersect(pictureRect); + effect->mTextureCoords = + Rect(Float(rect.X() - tileRect.X()) / tileRect.Width(), + Float(rect.Y() - tileRect.Y()) / tileRect.Height(), + Float(rect.Width()) / tileRect.Width(), + Float(rect.Height()) / tileRect.Height()); + if (img->mTextureHost->GetFlags() & TextureFlags::ORIGIN_BOTTOM_LEFT) { + effect->mTextureCoords.SetRectY(effect->mTextureCoords.YMost(), + -effect->mTextureCoords.Height()); + } + aCompositor->DrawGeometry(rect, aClipRect, aEffectChain, aOpacity, + aTransform, aGeometry); + aCompositor->DrawDiagnostics( + diagnosticFlags | DiagnosticFlags::BIGIMAGE, rect, aClipRect, + aTransform, mFlashCounter); + } while (it->NextTile()); + it->EndBigImageIteration(); + // layer border + aCompositor->DrawDiagnostics(diagnosticFlags, pictureRect, aClipRect, + aTransform, mFlashCounter); + } else { + IntSize textureSize = mCurrentTextureSource->GetSize(); + effect->mTextureCoords = + Rect(Float(img->mPictureRect.X()) / textureSize.width, + Float(img->mPictureRect.Y()) / textureSize.height, + Float(img->mPictureRect.Width()) / textureSize.width, + Float(img->mPictureRect.Height()) / textureSize.height); + + if (img->mTextureHost->GetFlags() & TextureFlags::ORIGIN_BOTTOM_LEFT) { + effect->mTextureCoords.SetRectY(effect->mTextureCoords.YMost(), + -effect->mTextureCoords.Height()); + } + + aCompositor->DrawGeometry(pictureRect, aClipRect, aEffectChain, aOpacity, + aTransform, aGeometry); + aCompositor->DrawDiagnostics(diagnosticFlags, pictureRect, aClipRect, + aTransform, mFlashCounter); + } + } + + FinishRendering(info); +} + +bool ImageHost::PrepareToRender(TextureSourceProvider* aProvider, + RenderInfo* aOutInfo) { + HostLayerManager* lm = GetLayerManager(); + if (!lm) { + return false; + } + + int imageIndex = ChooseImageIndex(); + if (imageIndex < 0) { + return false; + } + + if (uint32_t(imageIndex) + 1 < ImagesCount()) { + lm->CompositeUntil(GetImage(imageIndex + 1)->mTimeStamp + + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); + } + + const TimedImage* img = GetImage(imageIndex); + img->mTextureHost->SetTextureSourceProvider(aProvider); + SetCurrentTextureHost(img->mTextureHost); + + aOutInfo->imageIndex = imageIndex; + aOutInfo->img = img; + aOutInfo->host = mCurrentTextureHost; + return true; +} + +RefPtr<TextureSource> ImageHost::AcquireTextureSource(const RenderInfo& aInfo) { + MOZ_ASSERT(aInfo.host == mCurrentTextureHost); + if (!aInfo.host->AcquireTextureSource(mCurrentTextureSource)) { + return nullptr; + } + return mCurrentTextureSource.get(); +} + +void ImageHost::FinishRendering(const RenderInfo& aInfo) { + OnFinishRendering(aInfo.imageIndex, aInfo.img, mAsyncRef.mProcessId, + mAsyncRef.mHandle); +} + +void ImageHost::SetTextureSourceProvider(TextureSourceProvider* aProvider) { + if (mTextureSourceProvider != aProvider) { + for (const auto& img : Images()) { + img.mTextureHost->SetTextureSourceProvider(aProvider); + } + } + CompositableHost::SetTextureSourceProvider(aProvider); +} + +void ImageHost::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("ImageHost (0x%p)", this).get(); + + nsAutoCString pfx(aPrefix); + pfx += " "; + for (const auto& img : Images()) { + aStream << "\n"; + img.mTextureHost->PrintInfo(aStream, pfx.get()); + aStream << " [picture-rect=" << img.mPictureRect << "]"; + } +} + +void ImageHost::Dump(std::stringstream& aStream, const char* aPrefix, + bool aDumpHtml) { + for (const auto& img : Images()) { + aStream << aPrefix; + aStream << (aDumpHtml ? "<ul><li>TextureHost: " : "TextureHost: "); + DumpTextureHost(aStream, img.mTextureHost); + aStream << (aDumpHtml ? " </li></ul> " : " "); + } +} + +already_AddRefed<gfx::DataSourceSurface> ImageHost::GetAsSurface() { + const TimedImage* img = ChooseImage(); + if (img) { + return img->mTextureHost->GetAsSurface(); + } + return nullptr; +} + +bool ImageHost::Lock() { + MOZ_ASSERT(!mLocked); + const TimedImage* img = ChooseImage(); + if (!img) { + return false; + } + + SetCurrentTextureHost(img->mTextureHost); + + if (!mCurrentTextureHost->Lock()) { + return false; + } + mLocked = true; + return true; +} + +void ImageHost::Unlock() { + MOZ_ASSERT(mLocked); + + if (mCurrentTextureHost) { + mCurrentTextureHost->Unlock(); + } + mLocked = false; +} + +IntSize ImageHost::GetImageSize() { + const TimedImage* img = ChooseImage(); + if (img) { + return IntSize(img->mPictureRect.Width(), img->mPictureRect.Height()); + } + return IntSize(); +} + +bool ImageHost::IsOpaque() { + const TimedImage* img = ChooseImage(); + if (!img) { + return false; + } + + if (img->mPictureRect.Width() == 0 || img->mPictureRect.Height() == 0 || + !img->mTextureHost) { + return false; + } + + gfx::SurfaceFormat format = img->mTextureHost->GetFormat(); + if (gfx::IsOpaque(format)) { + return true; + } + return false; +} + +already_AddRefed<TexturedEffect> ImageHost::GenEffect( + const gfx::SamplingFilter aSamplingFilter) { + const TimedImage* img = ChooseImage(); + if (!img) { + return nullptr; + } + SetCurrentTextureHost(img->mTextureHost); + if (!mCurrentTextureHost->BindTextureSource(mCurrentTextureSource)) { + return nullptr; + } + bool isAlphaPremultiplied = true; + if (mCurrentTextureHost->GetFlags() & TextureFlags::NON_PREMULTIPLIED) { + isAlphaPremultiplied = false; + } + + return CreateTexturedEffect(mCurrentTextureHost, mCurrentTextureSource, + aSamplingFilter, isAlphaPremultiplied); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ImageHost.h b/gfx/layers/composite/ImageHost.h new file mode 100644 index 0000000000..5d712d5bdd --- /dev/null +++ b/gfx/layers/composite/ImageHost.h @@ -0,0 +1,138 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_IMAGEHOST_H +#define MOZILLA_GFX_IMAGEHOST_H + +#include <stdio.h> // for FILE +#include "CompositableHost.h" // for CompositableHost +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/MatrixFwd.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Polygon.h" // for Polygon +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for SamplingFilter +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/ImageComposite.h" // for ImageComposite +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "mozilla/layers/TextureHost.h" // for TextureHost, etc +#include "mozilla/mozalloc.h" // for operator delete +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegionFwd.h" // for nsIntRegion +#include "nscore.h" // for nsACString + +namespace mozilla { +namespace layers { + +class Compositor; +struct EffectChain; +class HostLayerManager; + +/** + * ImageHost. Works with ImageClientSingle and ImageClientBuffered + */ +class ImageHost : public CompositableHost, public ImageComposite { + public: + explicit ImageHost(const TextureInfo& aTextureInfo); + virtual ~ImageHost(); + + CompositableType GetType() override { return mTextureInfo.mCompositableType; } + ImageHost* AsImageHost() override { return this; } + + void Composite(Compositor* aCompositor, LayerComposite* aLayer, + EffectChain& aEffectChain, float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr, + const Maybe<gfx::Polygon>& aGeometry = Nothing()) override; + + void UseTextureHost(const nsTArray<TimedTexture>& aTextures) override; + + void RemoveTextureHost(TextureHost* aTexture) override; + + TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) override; + + void Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags = NO_FLAGS) override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + gfx::IntSize GetImageSize() override; + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false) override; + + already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override; + + bool Lock() override; + + void Unlock() override; + + already_AddRefed<TexturedEffect> GenEffect( + const gfx::SamplingFilter aSamplingFilter) override; + + void SetCurrentTextureHost(TextureHost* aTexture); + + void CleanupResources() override; + + bool IsOpaque(); + + uint32_t GetDroppedFrames() override { return GetDroppedFramesAndReset(); } + + struct RenderInfo { + int imageIndex; + const TimedImage* img; + RefPtr<TextureHost> host; + + RenderInfo() : imageIndex(-1), img(nullptr) {} + }; + + // Acquire rendering information for the current frame. + bool PrepareToRender(TextureSourceProvider* aProvider, RenderInfo* aOutInfo); + + // Acquire the TextureSource for the currently prepared frame. + RefPtr<TextureSource> AcquireTextureSource(const RenderInfo& aInfo); + + // Send ImageComposite notifications. + void FinishRendering(const RenderInfo& aInfo); + + // This should only be called inside a lock, or during rendering. It is + // infallible to enforce this. + TextureHost* CurrentTextureHost() const { + MOZ_ASSERT(mCurrentTextureHost); + return mCurrentTextureHost; + } + + protected: + // ImageComposite + TimeStamp GetCompositionTime() const override; + CompositionOpportunityId GetCompositionOpportunityId() const override; + void AppendImageCompositeNotification( + const ImageCompositeNotificationInfo& aInfo) const override; + + // Use a simple RefPtr because the same texture is already held by a + // a CompositableTextureHostRef in the array of TimedImage. + // See the comment in CompositableTextureRef for more details. + RefPtr<TextureHost> mCurrentTextureHost; + CompositableTextureSourceRef mCurrentTextureSource; + // When doing texture uploads it's best to alternate between two (or three) + // texture sources so that the texture we upload to isn't being used by + // the GPU to composite the previous frame. + RefPtr<TextureSource> mExtraTextureSource; + + bool mLocked; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/ImageLayerComposite.cpp b/gfx/layers/composite/ImageLayerComposite.cpp new file mode 100644 index 0000000000..9fab48e280 --- /dev/null +++ b/gfx/layers/composite/ImageLayerComposite.cpp @@ -0,0 +1,209 @@ +/* -*- 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 "ImageLayerComposite.h" +#include "CompositableHost.h" // for CompositableHost +#include "Layers.h" // for WriteSnapshotToDumpFile, etc +#include "gfx2DGlue.h" // for ToFilter +#include "gfxEnv.h" // for gfxEnv +#include "gfxRect.h" // for gfxRect +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize, Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/Effects.h" // for EffectChain +#include "mozilla/layers/ImageHost.h" // for ImageHost +#include "mozilla/layers/LayerManagerCompositeUtils.h" +#include "mozilla/layers/TextureHost.h" // for TextureHost, etc +#include "mozilla/mozalloc.h" // for operator delete +#include "nsAString.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsString.h" // for nsAutoCString + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +ImageLayerComposite::ImageLayerComposite(LayerManagerComposite* aManager) + : ImageLayer(aManager, nullptr), + LayerComposite(aManager), + mImageHost(nullptr) { + MOZ_COUNT_CTOR(ImageLayerComposite); + mImplData = static_cast<LayerComposite*>(this); +} + +ImageLayerComposite::~ImageLayerComposite() { + MOZ_COUNT_DTOR(ImageLayerComposite); + MOZ_ASSERT(mDestroyed); + + CleanupResources(); +} + +bool ImageLayerComposite::SetCompositableHost(CompositableHost* aHost) { + switch (aHost->GetType()) { + case CompositableType::IMAGE: { + ImageHost* newImageHost = static_cast<ImageHost*>(aHost); + if (mImageHost && newImageHost != mImageHost) { + mImageHost->Detach(this); + } + mImageHost = newImageHost; + return true; + } + default: + return false; + } +} + +void ImageLayerComposite::Disconnect() { Destroy(); } + +Layer* ImageLayerComposite::GetLayer() { return this; } + +void ImageLayerComposite::SetLayerManager(HostLayerManager* aManager) { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + if (mImageHost) { + mImageHost->SetTextureSourceProvider(mCompositor); + if (aManager && mImageHost->GetAsyncRef()) { + mImageHost->SetCompositorBridgeID(aManager->GetCompositorBridgeID()); + } + } +} + +void ImageLayerComposite::RenderLayer(const IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) { + if (!mImageHost || !mImageHost->IsAttached()) { + return; + } + +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpCompositorTextures()) { + RefPtr<gfx::DataSourceSurface> surf = mImageHost->GetAsSurface(); + if (surf) { + WriteSnapshotToDumpFile(this, surf); + } + } +#endif + + mCompositor->MakeCurrent(); + + RenderWithAllMasks(this, mCompositor, aClipRect, + [&](EffectChain& effectChain, const IntRect& clipRect) { + mImageHost->SetTextureSourceProvider(mCompositor); + mImageHost->Composite(mCompositor, this, effectChain, + GetEffectiveOpacity(), + GetEffectiveTransformForBuffer(), + GetSamplingFilter(), clipRect); + }); + mImageHost->BumpFlashCounter(); +} + +void ImageLayerComposite::ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) { + gfx::Matrix4x4 local = GetLocalTransform(); + + // Snap image edges to pixel boundaries + gfxRect sourceRect(0, 0, 0, 0); + if (mImageHost && mImageHost->IsAttached()) { + IntSize size = mImageHost->GetImageSize(); + sourceRect.SizeTo(size.width, size.height); + } + // Snap our local transform first, and snap the inherited transform as well. + // This makes our snapping equivalent to what would happen if our content + // was drawn into a PaintedLayer (gfxContext would snap using the local + // transform, then we'd snap again when compositing the PaintedLayer). + mEffectiveTransform = SnapTransform(local, sourceRect, nullptr) * + SnapTransformTranslation(aTransformToSurface, nullptr); + + if (mScaleMode != ScaleMode::SCALE_NONE && !sourceRect.IsZeroArea()) { + NS_ASSERTION(mScaleMode == ScaleMode::STRETCH, + "No other scalemodes than stretch and none supported yet."); + local.PreScale(mScaleToSize.width / sourceRect.Width(), + mScaleToSize.height / sourceRect.Height(), 1.0); + + mEffectiveTransformForBuffer = + SnapTransform(local, sourceRect, nullptr) * + SnapTransformTranslation(aTransformToSurface, nullptr); + } else { + mEffectiveTransformForBuffer = mEffectiveTransform; + } + + ComputeEffectiveTransformForMaskLayers(aTransformToSurface); +} + +bool ImageLayerComposite::IsOpaque() { + if (!mImageHost || !mImageHost->IsAttached()) { + return false; + } + + // TODO: Handle ScaleMode::NONE where the image + // still covers the whole Layer. + if (mScaleMode == ScaleMode::STRETCH) { + if ((GetContentFlags() & CONTENT_OPAQUE) && !mImageHost->IsOpaque()) { + NS_WARNING("Must have an opaque ImageHost if we reported CONTENT_OPAQUE"); + } + return mImageHost->IsOpaque(); + } + return false; +} + +nsIntRegion ImageLayerComposite::GetFullyRenderedRegion() { + if (!mImageHost || !mImageHost->IsAttached()) { + return GetShadowVisibleRegion().ToUnknownRegion(); + } + + if (mScaleMode == ScaleMode::STRETCH) { + nsIntRegion shadowVisibleRegion; + shadowVisibleRegion.And(GetShadowVisibleRegion().ToUnknownRegion(), + nsIntRegion(gfx::IntRect(0, 0, mScaleToSize.width, + mScaleToSize.height))); + return shadowVisibleRegion; + } + + return GetShadowVisibleRegion().ToUnknownRegion(); +} + +CompositableHost* ImageLayerComposite::GetCompositableHost() { + if (mImageHost && mImageHost->IsAttached()) { + return mImageHost.get(); + } + + return nullptr; +} + +void ImageLayerComposite::CleanupResources() { + if (mImageHost) { + mImageHost->CleanupResources(); + mImageHost->Detach(this); + } + mImageHost = nullptr; +} + +gfx::SamplingFilter ImageLayerComposite::GetSamplingFilter() { + return mSamplingFilter; +} + +void ImageLayerComposite::GenEffectChain(EffectChain& aEffect) { + aEffect.mLayerRef = this; + aEffect.mPrimaryEffect = mImageHost->GenEffect(GetSamplingFilter()); +} + +void ImageLayerComposite::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + ImageLayer::PrintInfo(aStream, aPrefix); + if (mImageHost && mImageHost->IsAttached()) { + aStream << "\n"; + nsAutoCString pfx(aPrefix); + pfx += " "; + mImageHost->PrintInfo(aStream, pfx.get()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ImageLayerComposite.h b/gfx/layers/composite/ImageLayerComposite.h new file mode 100644 index 0000000000..f0725c2852 --- /dev/null +++ b/gfx/layers/composite/ImageLayerComposite.h @@ -0,0 +1,78 @@ +/* -*- 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/. */ + +#ifndef GFX_ImageLayerComposite_H +#define GFX_ImageLayerComposite_H + +#include "GLTextureImage.h" // for TextureImage +#include "ImageLayers.h" // for ImageLayer +#include "mozilla/Attributes.h" // for override +#include "mozilla/gfx/Rect.h" +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/LayerManagerComposite.h" // for LayerComposite, etc +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "nsISupportsImpl.h" // for TextureImage::AddRef, etc +#include "nscore.h" // for nsACString +#include "CompositableHost.h" // for CompositableHost + +namespace mozilla { +namespace layers { + +class ImageHost; +class Layer; + +class ImageLayerComposite : public ImageLayer, public LayerComposite { + typedef gl::TextureImage TextureImage; + + public: + explicit ImageLayerComposite(LayerManagerComposite* aManager); + + protected: + virtual ~ImageLayerComposite(); + + public: + void Disconnect() override; + + bool SetCompositableHost(CompositableHost* aHost) override; + + Layer* GetLayer() override; + + void SetLayerManager(HostLayerManager* aManager) override; + + void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) override; + + void ComputeEffectiveTransforms( + const mozilla::gfx::Matrix4x4& aTransformToSurface) override; + + void CleanupResources() override; + + CompositableHost* GetCompositableHost() override; + + void GenEffectChain(EffectChain& aEffect) override; + + HostLayer* AsHostLayer() override { return this; } + + const char* Name() const override { return "ImageLayerComposite"; } + + bool IsOpaque() override; + + nsIntRegion GetFullyRenderedRegion() override; + + protected: + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + private: + gfx::SamplingFilter GetSamplingFilter(); + + private: + RefPtr<ImageHost> mImageHost; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_ImageLayerComposite_H */ diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp new file mode 100644 index 0000000000..8ad4e0310d --- /dev/null +++ b/gfx/layers/composite/LayerManagerComposite.cpp @@ -0,0 +1,1780 @@ +/* -*- 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 "LayerManagerComposite.h" +#include <stddef.h> // for size_t +#include <stdint.h> // for uint16_t, uint32_t +#include "CanvasLayerComposite.h" // for CanvasLayerComposite +#include "ColorLayerComposite.h" // for ColorLayerComposite +#include "CompositableHost.h" // for CompositableHost +#include "ContainerLayerComposite.h" // for ContainerLayerComposite, etc +#include "Diagnostics.h" +#include "FPSCounter.h" // for FPSState, FPSCounter +#include "FrameMetrics.h" // for FrameMetrics +#include "GeckoProfiler.h" // for profiler_* +#include "ImageLayerComposite.h" // for ImageLayerComposite +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "LayerScope.h" // for LayerScope Tool +#include "LayerTreeInvalidation.h" +#include "protobuf/LayerScopePacket.pb.h" // for protobuf (LayerScope) +#include "PaintedLayerComposite.h" // for PaintedLayerComposite +#include "TiledContentHost.h" +#include "Units.h" // for ScreenIntRect +#include "UnitTransforms.h" // for ViewAs +#include "apz/src/AsyncPanZoomController.h" // for AsyncPanZoomController +#include "gfxEnv.h" // for gfxEnv + +#ifdef XP_MACOSX +# include "gfxPlatformMac.h" +#endif +#include "gfxRect.h" // for gfxRect +#include "gfxUtils.h" // for frame color util +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize, Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for Color, SurfaceFormat +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/LayersTypes.h" // for etc +#include "mozilla/layers/NativeLayer.h" +#include "mozilla/layers/UiCompositorControllerParent.h" +#include "mozilla/widget/CompositorWidget.h" // for WidgetRenderingContext +#include "ipc/CompositorBench.h" // for CompositorBench +#include "ipc/SurfaceDescriptor.h" +#include "mozilla/mozalloc.h" // for operator new, etc +#include "nsAppRunner.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_WARNING, etc +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion, etc +#if defined(MOZ_WIDGET_ANDROID) +# include <android/log.h> +# include <android/native_window.h> +# include "mozilla/jni/Utils.h" +# include "mozilla/widget/AndroidCompositorWidget.h" +# include "GLConsts.h" +# include "GLContextEGL.h" +# include "GLContextProvider.h" +# include "mozilla/Unused.h" +# include "ScopedGLHelpers.h" +#endif +#include "GeckoProfiler.h" +#include "TextRenderer.h" // for TextRenderer +#include "mozilla/layers/CompositorBridgeParent.h" +#include "TreeTraversal.h" // for ForEachNode +#include "CompositionRecorder.h" + +#ifdef USE_SKIA +# include "PaintCounter.h" // For PaintCounter +#endif + +class gfxContext; + +namespace mozilla { +namespace layers { + +class ImageLayer; + +using namespace mozilla::gfx; +using namespace mozilla::gl; + +static LayerComposite* ToLayerComposite(Layer* aLayer) { + return static_cast<LayerComposite*>(aLayer->ImplData()); +} + +static void ClearSubtree(Layer* aLayer) { + ForEachNode<ForwardIterator>(aLayer, [](Layer* layer) { + ToLayerComposite(layer)->CleanupResources(); + }); +} + +void LayerManagerComposite::ClearCachedResources(Layer* aSubtree) { + MOZ_ASSERT(!aSubtree || aSubtree->Manager() == this); + Layer* subtree = aSubtree ? aSubtree : mRoot.get(); + if (!subtree) { + return; + } + + ClearSubtree(subtree); + // FIXME [bjacob] + // XXX the old LayerManagerOGL code had a mMaybeInvalidTree that it set to + // true here. Do we need that? +} + +HostLayerManager::HostLayerManager() + : mDebugOverlayWantsNextFrame(false), + mWarningLevel(0.0f), + mCompositorBridgeID(0), + mLastPaintTime(TimeDuration::Forever()), + mRenderStartTime(TimeStamp::Now()) {} + +HostLayerManager::~HostLayerManager() = default; + +void HostLayerManager::RecordPaintTimes(const PaintTiming& aTiming) { + mDiagnostics->RecordPaintTimes(aTiming); +} + +void HostLayerManager::RecordUpdateTime(float aValue) { + mDiagnostics->RecordUpdateTime(aValue); +} + +void HostLayerManager::WriteCollectedFrames() { + if (mCompositionRecorder) { + mCompositionRecorder->WriteCollectedFrames(); + mCompositionRecorder = nullptr; + } +} + +Maybe<CollectedFrames> HostLayerManager::GetCollectedFrames() { + Maybe<CollectedFrames> maybeFrames; + + if (mCompositionRecorder) { + maybeFrames.emplace(mCompositionRecorder->GetCollectedFrames()); + mCompositionRecorder = nullptr; + } + + return maybeFrames; +} + +/** + * LayerManagerComposite + */ +LayerManagerComposite::LayerManagerComposite(Compositor* aCompositor) + : mUnusedApzTransformWarning(false), + mDisabledApzWarning(false), + mCompositor(aCompositor), + mInTransaction(false), + mIsCompositorReady(false) +#if defined(MOZ_WIDGET_ANDROID) + , + mScreenPixelsTarget(nullptr) +#endif // defined(MOZ_WIDGET_ANDROID) +{ + mTextRenderer = new TextRenderer(); + mDiagnostics = MakeUnique<Diagnostics>(); + MOZ_ASSERT(aCompositor); + mNativeLayerRoot = aCompositor->GetWidget()->GetNativeLayerRoot(); + if (mNativeLayerRoot) { + mSurfacePoolHandle = aCompositor->GetSurfacePoolHandle(); + MOZ_RELEASE_ASSERT(mSurfacePoolHandle); + } + +#ifdef USE_SKIA + mPaintCounter = nullptr; +#endif +} + +LayerManagerComposite::~LayerManagerComposite() { Destroy(); } + +void LayerManagerComposite::Destroy() { + if (!mDestroyed) { + mCompositor->GetWidget()->CleanupWindowEffects(); + if (mRoot) { + RootLayer()->Destroy(); + } + mCompositor->CancelFrame(); + mRoot = nullptr; + mClonedLayerTreeProperties = nullptr; + mProfilerScreenshotGrabber.Destroy(); + + if (mNativeLayerRoot) { + if (mGPUStatsLayer) { + mNativeLayerRoot->RemoveLayer(mGPUStatsLayer); + mGPUStatsLayer = nullptr; + } + if (mUnusedTransformWarningLayer) { + mNativeLayerRoot->RemoveLayer(mUnusedTransformWarningLayer); + mUnusedTransformWarningLayer = nullptr; + } + if (mDisabledApzWarningLayer) { + mNativeLayerRoot->RemoveLayer(mDisabledApzWarningLayer); + mDisabledApzWarningLayer = nullptr; + } + for (const auto& nativeLayer : mNativeLayers) { + mNativeLayerRoot->RemoveLayer(nativeLayer); + } + mNativeLayers.clear(); + mNativeLayerRoot = nullptr; + } + mDestroyed = true; + +#ifdef USE_SKIA + mPaintCounter = nullptr; +#endif + } +} + +void LayerManagerComposite::UpdateRenderBounds(const IntRect& aRect) { + mRenderBounds = aRect; +} + +bool LayerManagerComposite::AreComponentAlphaLayersEnabled() { + return mCompositor->GetBackendType() != LayersBackend::LAYERS_BASIC && + mCompositor->SupportsEffect(EffectTypes::COMPONENT_ALPHA) && + LayerManager::AreComponentAlphaLayersEnabled(); +} + +bool LayerManagerComposite::BeginTransaction(const nsCString& aURL) { + mInTransaction = true; + + if (!mCompositor->Ready()) { + return false; + } + + mIsCompositorReady = true; + return true; +} + +void LayerManagerComposite::BeginTransactionWithDrawTarget( + DrawTarget* aTarget, const IntRect& aRect) { + mInTransaction = true; + + if (!mCompositor->Ready()) { + return; + } + +#ifdef MOZ_LAYERS_HAVE_LOG + MOZ_LAYERS_LOG(("[----- BeginTransaction")); + Log(); +#endif + + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return; + } + + mIsCompositorReady = true; + mTarget = aTarget; + mTargetBounds = aRect; +} + +template <typename Units> +static IntRectTyped<Units> TransformRect(const IntRectTyped<Units>& aRect, + const Matrix& aTransform, + bool aRoundIn = false) { + if (aRect.IsEmpty()) { + return IntRectTyped<Units>(); + } + + Rect rect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); + rect = aTransform.TransformBounds(rect); + if (aRoundIn) { + MOZ_ASSERT(aTransform.PreservesAxisAlignedRectangles()); + rect.RoundIn(); + } else { + rect.RoundOut(); + } + + IntRect intRect; + if (!rect.ToIntRect(&intRect)) { + intRect = IntRect::MaxIntRect(); + } + + return ViewAs<Units>(intRect); +} + +template <typename Units> +static IntRectTyped<Units> TransformRect(const IntRectTyped<Units>& aRect, + const Matrix4x4& aTransform, + bool aRoundIn = false) { + if (aRect.IsEmpty()) { + return IntRectTyped<Units>(); + } + + Rect rect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); + rect = aTransform.TransformAndClipBounds(rect, Rect::MaxIntRect()); + if (aRoundIn) { + rect.RoundIn(); + } else { + rect.RoundOut(); + } + + IntRect intRect; + if (!rect.ToIntRect(&intRect)) { + intRect = IntRect::MaxIntRect(); + } + + return ViewAs<Units>(intRect); +} + +template <typename Units, typename MatrixType> +static IntRectTyped<Units> TransformRectRoundIn( + const IntRectTyped<Units>& aRect, const MatrixType& aTransform) { + return TransformRect(aRect, aTransform, true); +} + +template <typename Units, typename MatrixType> +static void AddTransformedRegion(IntRegionTyped<Units>& aDest, + const IntRegionTyped<Units>& aSource, + const MatrixType& aTransform) { + for (auto iter = aSource.RectIter(); !iter.Done(); iter.Next()) { + aDest.Or(aDest, TransformRect(iter.Get(), aTransform)); + } + aDest.SimplifyOutward(20); +} + +template <typename Units, typename MatrixType> +static void AddTransformedRegionRoundIn(IntRegionTyped<Units>& aDest, + const IntRegionTyped<Units>& aSource, + const MatrixType& aTransform) { + for (auto iter = aSource.RectIter(); !iter.Done(); iter.Next()) { + aDest.Or(aDest, TransformRectRoundIn(iter.Get(), aTransform)); + } +} + +void LayerManagerComposite::PostProcessLayers(nsIntRegion& aOpaqueRegion) { + LayerIntRegion visible; + LayerComposite* rootComposite = + static_cast<LayerComposite*>(mRoot->AsHostLayer()); + PostProcessLayers( + mRoot, aOpaqueRegion, visible, + ViewAs<RenderTargetPixel>( + rootComposite->GetShadowClipRect(), + PixelCastJustification::RenderTargetIsParentLayerForRoot), + Nothing(), true); +} + +// We want to skip directly through ContainerLayers that don't have an +// intermediate surface. We compute occlusions for leaves and intermediate +// surfaces against the layer that they actually composite into so that we can +// use the final (snapped) effective transform. +static bool ShouldProcessLayer(Layer* aLayer) { + if (!aLayer->AsContainerLayer()) { + return true; + } + + return aLayer->AsContainerLayer()->UseIntermediateSurface(); +} + +void LayerManagerComposite::PostProcessLayers( + Layer* aLayer, nsIntRegion& aOpaqueRegion, LayerIntRegion& aVisibleRegion, + const Maybe<RenderTargetIntRect>& aRenderTargetClip, + const Maybe<ParentLayerIntRect>& aClipFromAncestors, + bool aCanContributeOpaque) { + // Compute a clip that's the combination of our layer clip with the clip + // from our ancestors. + LayerComposite* composite = + static_cast<LayerComposite*>(aLayer->AsHostLayer()); + Maybe<ParentLayerIntRect> layerClip = composite->GetShadowClipRect(); + MOZ_ASSERT(!layerClip || !aLayer->Combines3DTransformWithAncestors(), + "The layer with a clip should not participate " + "a 3D rendering context"); + Maybe<ParentLayerIntRect> outsideClip = + IntersectMaybeRects(layerClip, aClipFromAncestors); + + Maybe<LayerIntRect> insideClip; + if (aLayer->Extend3DContext()) { + // If we're preserve-3d just pass the clip rect down directly, and we'll do + // the conversion at the preserve-3d leaf Layer. + if (outsideClip) { + insideClip = Some(ViewAs<LayerPixel>( + *outsideClip, PixelCastJustification::MovingDownToChildren)); + } + } else if (outsideClip) { + // Convert the combined clip into our pre-transform coordinate space, so + // that it can later be intersected with our visible region. + // If our transform is a perspective, there's no meaningful insideClip rect + // we can compute (it would need to be a cone). + Matrix4x4 localTransform = aLayer->ComputeTransformToPreserve3DRoot(); + if (!localTransform.HasPerspectiveComponent() && localTransform.Invert()) { + LayerRect insideClipFloat = + UntransformBy(ViewAs<ParentLayerToLayerMatrix4x4>(localTransform), + ParentLayerRect(*outsideClip), LayerRect::MaxIntRect()) + .valueOr(LayerRect()); + insideClipFloat.RoundOut(); + LayerIntRect insideClipInt; + if (insideClipFloat.ToIntRect(&insideClipInt)) { + insideClip = Some(insideClipInt); + } + } + } + + Maybe<ParentLayerIntRect> ancestorClipForChildren; + if (insideClip) { + ancestorClipForChildren = Some(ViewAs<ParentLayerPixel>( + *insideClip, PixelCastJustification::MovingDownToChildren)); + } + + nsIntRegion dummy; + nsIntRegion& opaqueRegion = aOpaqueRegion; + if (aLayer->Extend3DContext() || aLayer->Combines3DTransformWithAncestors()) { + opaqueRegion = dummy; + } + + if (!ShouldProcessLayer(aLayer)) { + MOZ_ASSERT(aLayer->AsContainerLayer() && + !aLayer->AsContainerLayer()->UseIntermediateSurface()); + // For layers participating 3D rendering context, their visible + // region should be empty (invisible), so we pass through them + // without doing anything. + for (Layer* child = aLayer->GetLastChild(); child; + child = child->GetPrevSibling()) { + LayerComposite* childComposite = + static_cast<LayerComposite*>(child->AsHostLayer()); + Maybe<RenderTargetIntRect> renderTargetClip = aRenderTargetClip; + if (childComposite->GetShadowClipRect()) { + RenderTargetIntRect clip = TransformBy( + ViewAs<ParentLayerToRenderTargetMatrix4x4>( + aLayer->GetEffectiveTransform(), + PixelCastJustification::RenderTargetIsParentLayerForRoot), + *childComposite->GetShadowClipRect()); + renderTargetClip = IntersectMaybeRects(renderTargetClip, Some(clip)); + } + + PostProcessLayers( + child, opaqueRegion, aVisibleRegion, renderTargetClip, + ancestorClipForChildren, + aCanContributeOpaque & + !(aLayer->GetContentFlags() & Layer::CONTENT_BACKFACE_HIDDEN)); + } + return; + } + + nsIntRegion localOpaque; + // Treat layers on the path to the root of the 3D rendering context as + // a giant layer if it is a leaf. + Matrix4x4 transform = aLayer->GetEffectiveTransform(); + Matrix transform2d; + bool canTransformOpaqueRegion = false; + // If aLayer has a simple transform (only an integer translation) then we + // can easily convert aOpaqueRegion into pre-transform coordinates and include + // that region. + if (aCanContributeOpaque && + !(aLayer->GetContentFlags() & Layer::CONTENT_BACKFACE_HIDDEN) && + transform.Is2D(&transform2d) && + transform2d.PreservesAxisAlignedRectangles()) { + Matrix inverse = transform2d; + inverse.Invert(); + AddTransformedRegionRoundIn(localOpaque, opaqueRegion, inverse); + canTransformOpaqueRegion = true; + } + + // Save the value of localOpaque, which currently stores the region obscured + // by siblings (and uncles and such), before our descendants contribute to it. + nsIntRegion obscured = localOpaque; + + // Recurse on our descendants, in front-to-back order. In this process: + // - Occlusions are computed for them, and they contribute to localOpaque. + // - They recalculate their visible regions, taking ancestorClipForChildren + // into account, and accumulate them into descendantsVisibleRegion. + LayerIntRegion descendantsVisibleRegion; + + bool hasPreserve3DChild = false; + for (Layer* child = aLayer->GetLastChild(); child; + child = child->GetPrevSibling()) { + MOZ_ASSERT(aLayer->AsContainerLayer()->UseIntermediateSurface()); + LayerComposite* childComposite = + static_cast<LayerComposite*>(child->AsHostLayer()); + PostProcessLayers( + child, localOpaque, descendantsVisibleRegion, + ViewAs<RenderTargetPixel>( + childComposite->GetShadowClipRect(), + PixelCastJustification::RenderTargetIsParentLayerForRoot), + ancestorClipForChildren, true); + if (child->Extend3DContext()) { + hasPreserve3DChild = true; + } + } + + // Recalculate our visible region. + LayerIntRegion visible = composite->GetShadowVisibleRegion(); + + // If we have descendants, throw away the visible region stored on this + // layer, and use the region accumulated by our descendants instead. + if (aLayer->GetFirstChild() && !hasPreserve3DChild) { + visible = descendantsVisibleRegion; + } + + // Subtract any areas that we know to be opaque. + if (!obscured.IsEmpty()) { + visible.SubOut(LayerIntRegion::FromUnknownRegion(obscured)); + } + + // Clip the visible region using the combined clip. + if (insideClip) { + visible.AndWith(*insideClip); + } + composite->SetShadowVisibleRegion(visible); + + // Transform the newly calculated visible region into our parent's space, + // apply our clip to it (if any), and accumulate it into |aVisibleRegion| + // for the caller to use. + ParentLayerIntRegion visibleParentSpace = + TransformBy(ViewAs<LayerToParentLayerMatrix4x4>(transform), visible); + aVisibleRegion.OrWith(ViewAs<LayerPixel>( + visibleParentSpace, PixelCastJustification::MovingDownToChildren)); + + // If we have a simple transform, then we can add our opaque area into + // aOpaqueRegion. + if (canTransformOpaqueRegion && !aLayer->HasMaskLayers() && + aLayer->IsOpaqueForVisibility()) { + if (aLayer->IsOpaque()) { + localOpaque.OrWith(composite->GetFullyRenderedRegion()); + } + nsIntRegion parentSpaceOpaque; + AddTransformedRegionRoundIn(parentSpaceOpaque, localOpaque, transform2d); + if (aRenderTargetClip) { + parentSpaceOpaque.AndWith(aRenderTargetClip->ToUnknownRect()); + } + opaqueRegion.OrWith(parentSpaceOpaque); + } +} + +void LayerManagerComposite::EndTransaction(const TimeStamp& aTimeStamp, + EndTransactionFlags aFlags) { + NS_ASSERTION(mInTransaction, "Didn't call BeginTransaction?"); + NS_ASSERTION(!(aFlags & END_NO_COMPOSITE), + "Shouldn't get END_NO_COMPOSITE here"); + mInTransaction = false; + mRenderStartTime = TimeStamp::Now(); + + // Ensure we read unlock textures, even if we end up + // not compositing this frame. + TextureSourceProvider::AutoReadUnlockTextures unlock(mCompositor); + + if (!mIsCompositorReady) { + return; + } + mIsCompositorReady = false; + +#ifdef MOZ_LAYERS_HAVE_LOG + MOZ_LAYERS_LOG((" ----- (beginning paint)")); + Log(); +#endif + + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return; + } + + // Set composition timestamp here because we need it in + // ComputeEffectiveTransforms (so the correct video frame size is picked) and + // also to compute invalid regions properly. + SetCompositionTime(aTimeStamp); + + if (mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW)) { + MOZ_ASSERT(!aTimeStamp.IsNull()); + UpdateAndRender(); + mCompositor->FlushPendingNotifyNotUsed(); + } + + mTarget = nullptr; + +#ifdef MOZ_LAYERS_HAVE_LOG + Log(); + MOZ_LAYERS_LOG(("]----- EndTransaction")); +#endif +} + +void LayerManagerComposite::SetRoot(Layer* aLayer) { mRoot = aLayer; } + +void LayerManagerComposite::UpdateAndRender() { + mCompositionOpportunityId = mCompositionOpportunityId.Next(); + + if (gfxEnv::SkipComposition()) { + mInvalidRegion.SetEmpty(); + return; + } + + // The results of our drawing always go directly into a pixel buffer, + // so we don't need to pass any global transform here. + mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4()); + + nsIntRegion opaque; + PostProcessLayers(opaque); + + if (mClonedLayerTreeProperties) { + // We need to compute layer tree differences even if we're not going to + // immediately use the resulting damage area, since ComputeDifferences + // is also responsible for invalidates intermediate surfaces in + // ContainerLayers. + + nsIntRegion changed; + const bool overflowed = !mClonedLayerTreeProperties->ComputeDifferences( + mRoot, changed, nullptr); + + if (overflowed) { + changed = mRenderBounds; + } + + mInvalidRegion.Or(mInvalidRegion, changed); + } + + nsIntRegion invalid; + if (mTarget) { + // Since we're composing to an external target, we're not going to use + // the damage region from layers changes - we want to composite + // everything in the target bounds. The layers damage region has been + // stored in mInvalidRegion and will be picked up by the next window + // composite. + invalid = mTargetBounds; + } else { + if (!mClonedLayerTreeProperties) { + // If we didn't have a previous layer tree, invalidate the entire render + // area. + mInvalidRegion = mRenderBounds; + } + + invalid = mInvalidRegion; + } + + if (invalid.IsEmpty()) { + // Composition requested, but nothing has changed. Don't do any work. + mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot()); + mProfilerScreenshotGrabber.NotifyEmptyFrame(); + + // Discard the current payloads. These payloads did not require a composite + // (they caused no changes to anything visible), so we don't want to measure + // their latency. + mPayload.Clear(); + + return; + } + + // We don't want our debug overlay to cause more frames to happen + // so we will invalidate after we've decided if something changed. + // Only invalidate if we're not using native layers. When using native layers, + // UpdateDebugOverlayNativeLayers will repaint the appropriate layer areas. + if (!mNativeLayerRoot) { + InvalidateDebugOverlay(invalid, mRenderBounds); + } + + bool rendered = Render(invalid, opaque); +#if defined(MOZ_WIDGET_ANDROID) + RenderToPresentationSurface(); +#endif + + if (!mTarget && rendered) { + mInvalidRegion.SetEmpty(); + } + + // Update cached layer tree information. + mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot()); +} + +already_AddRefed<DrawTarget> LayerManagerComposite::CreateOptimalMaskDrawTarget( + const IntSize& aSize) { + MOZ_CRASH("Should only be called on the drawing side"); + return nullptr; +} + +LayerComposite* LayerManagerComposite::RootLayer() const { + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + + return ToLayerComposite(mRoot); +} + +void LayerManagerComposite::InvalidateDebugOverlay(nsIntRegion& aInvalidRegion, + const IntRect& aBounds) { + bool drawFps = StaticPrefs::layers_acceleration_draw_fps(); + bool drawFrameColorBars = StaticPrefs::gfx_draw_color_bars(); + + if (drawFps) { + aInvalidRegion.Or(aInvalidRegion, nsIntRect(0, 0, 650, 400)); + } + if (drawFrameColorBars) { + aInvalidRegion.Or(aInvalidRegion, nsIntRect(0, 0, 10, aBounds.Height())); + } + +#ifdef USE_SKIA + bool drawPaintTimes = StaticPrefs::gfx_content_always_paint(); + if (drawPaintTimes) { + aInvalidRegion.Or(aInvalidRegion, nsIntRect(PaintCounter::GetPaintRect())); + } +#endif +} + +#ifdef USE_SKIA +void LayerManagerComposite::DrawPaintTimes(Compositor* aCompositor) { + if (!mPaintCounter) { + mPaintCounter = new PaintCounter(); + } + + TimeDuration compositeTime = TimeStamp::Now() - mRenderStartTime; + mPaintCounter->Draw(aCompositor, mLastPaintTime, compositeTime); +} +#endif + +static Rect RectWithEdges(int32_t aTop, int32_t aRight, int32_t aBottom, + int32_t aLeft) { + return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop); +} + +void LayerManagerComposite::DrawBorder(const IntRect& aOuter, + int32_t aBorderWidth, + const DeviceColor& aColor, + const Matrix4x4& aTransform) { + EffectChain effects; + effects.mPrimaryEffect = new EffectSolidColor(aColor); + + IntRect inner(aOuter); + inner.Deflate(aBorderWidth); + // Top and bottom border sides + mCompositor->DrawQuad( + RectWithEdges(aOuter.Y(), aOuter.XMost(), inner.Y(), aOuter.X()), aOuter, + effects, 1, aTransform); + mCompositor->DrawQuad( + RectWithEdges(inner.YMost(), aOuter.XMost(), aOuter.YMost(), aOuter.X()), + aOuter, effects, 1, aTransform); + // Left and right border sides + mCompositor->DrawQuad( + RectWithEdges(inner.Y(), inner.X(), inner.YMost(), aOuter.X()), aOuter, + effects, 1, aTransform); + mCompositor->DrawQuad( + RectWithEdges(inner.Y(), aOuter.XMost(), inner.YMost(), inner.XMost()), + aOuter, effects, 1, aTransform); +} + +void LayerManagerComposite::DrawTranslationWarningOverlay( + const IntRect& aBounds) { + // Black blorder + IntRect blackBorderBounds(aBounds); + blackBorderBounds.Deflate(4); + DrawBorder(blackBorderBounds, 6, DeviceColor(0, 0, 0, 1), Matrix4x4()); + + // Warning border, yellow to red + IntRect warnBorder(aBounds); + warnBorder.Deflate(5); + DrawBorder(warnBorder, 4, DeviceColor(1, 1.f - mWarningLevel, 0, 1), + Matrix4x4()); +} + +static uint16_t sFrameCount = 0; +void LayerManagerComposite::RenderDebugOverlay(const IntRect& aBounds) { + bool drawFps = StaticPrefs::layers_acceleration_draw_fps(); + bool drawFrameColorBars = StaticPrefs::gfx_draw_color_bars(); + + // Don't draw diagnostic overlays if we want to snapshot the output. + if (mTarget) { + return; + } + + if (drawFps) { +#ifdef ANDROID + // Draw a translation delay warning overlay + if (!mWarnTime.IsNull() && (TimeStamp::Now() - mWarnTime).ToMilliseconds() < + kVisualWarningDuration) { + DrawTranslationWarningOverlay(aBounds); + SetDebugOverlayWantsNextFrame(true); + } +#endif + + GPUStats stats; + stats.mScreenPixels = mRenderBounds.Width() * mRenderBounds.Height(); + mCompositor->GetFrameStats(&stats); + + std::string text = mDiagnostics->GetFrameOverlayString(stats); + mTextRenderer->RenderText(mCompositor, text, IntPoint(2, 5), Matrix4x4(), + 24, 600, TextRenderer::FontType::FixedWidth); + + float alpha = 1; + if (mUnusedApzTransformWarning) { + // If we have an unused APZ transform on this composite, draw a 20x20 red + // box in the top-right corner + EffectChain effects; + effects.mPrimaryEffect = + new EffectSolidColor(gfx::DeviceColor(1, 0, 0, 1)); + mCompositor->DrawQuad(gfx::Rect(aBounds.Width() - 20, 0, 20, 20), aBounds, + effects, alpha, gfx::Matrix4x4()); + + mUnusedApzTransformWarning = false; + SetDebugOverlayWantsNextFrame(true); + } + if (mDisabledApzWarning) { + // If we have a disabled APZ on this composite, draw a 20x20 yellow box + // in the top-right corner, to the left of the unused-apz-transform + // warning box + EffectChain effects; + effects.mPrimaryEffect = + new EffectSolidColor(gfx::DeviceColor(1, 1, 0, 1)); + mCompositor->DrawQuad(gfx::Rect(aBounds.Width() - 40, 0, 20, 20), aBounds, + effects, alpha, gfx::Matrix4x4()); + + mDisabledApzWarning = false; + SetDebugOverlayWantsNextFrame(true); + } + } + + if (drawFrameColorBars) { + gfx::IntRect sideRect(0, 0, 10, aBounds.Height()); + + EffectChain effects; + effects.mPrimaryEffect = + new EffectSolidColor(gfxUtils::GetColorForFrameNumber(sFrameCount)); + mCompositor->DrawQuad(Rect(sideRect), sideRect, effects, 1.0, + gfx::Matrix4x4()); + + // We intentionally overflow at 2^16. + sFrameCount++; + } + +#ifdef USE_SKIA + bool drawPaintTimes = StaticPrefs::gfx_content_always_paint(); + if (drawPaintTimes) { + DrawPaintTimes(mCompositor); + } +#endif +} + +void LayerManagerComposite::UpdateDebugOverlayNativeLayers() { + // Remove all debug layers first because PlaceNativeLayers might have changed + // the z-order. By removing and re-adding, we keep the debug overlay layers + // on top. + if (mGPUStatsLayer) { + mNativeLayerRoot->RemoveLayer(mGPUStatsLayer); + } + if (mUnusedTransformWarningLayer) { + mNativeLayerRoot->RemoveLayer(mUnusedTransformWarningLayer); + } + if (mDisabledApzWarningLayer) { + mNativeLayerRoot->RemoveLayer(mDisabledApzWarningLayer); + } + + bool drawFps = StaticPrefs::layers_acceleration_draw_fps(); + + if (drawFps) { + GPUStats stats; + stats.mScreenPixels = mRenderBounds.Area(); + mCompositor->GetFrameStats(&stats); + + std::string text = mDiagnostics->GetFrameOverlayString(stats); + IntSize size = mTextRenderer->ComputeSurfaceSize( + text, 600, TextRenderer::FontType::FixedWidth); + + if (!mGPUStatsLayer || mGPUStatsLayer->GetSize() != size) { + mGPUStatsLayer = + mNativeLayerRoot->CreateLayer(size, false, mSurfacePoolHandle); + } + + mGPUStatsLayer->SetPosition(IntPoint(2, 5)); + IntRect bounds({}, size); + RefPtr<DrawTarget> dt = mGPUStatsLayer->NextSurfaceAsDrawTarget( + bounds, bounds, BackendType::SKIA); + mTextRenderer->RenderTextToDrawTarget(dt, text, 600, + TextRenderer::FontType::FixedWidth); + mGPUStatsLayer->NotifySurfaceReady(); + mNativeLayerRoot->AppendLayer(mGPUStatsLayer); + + IntSize square(20, 20); + // The two warning layers are created on demand and their content is only + // drawn once. After that, they only get moved (if the window size changes) + // and conditionally shown. + // The drawing would be unnecessary if we had native "color layers". + if (mUnusedApzTransformWarning) { + // If we have an unused APZ transform on this composite, draw a 20x20 red + // box in the top-right corner. + if (!mUnusedTransformWarningLayer) { + mUnusedTransformWarningLayer = + mNativeLayerRoot->CreateLayer(square, true, mSurfacePoolHandle); + RefPtr<DrawTarget> dt = + mUnusedTransformWarningLayer->NextSurfaceAsDrawTarget( + IntRect({}, square), IntRect({}, square), BackendType::SKIA); + dt->FillRect(Rect(0, 0, 20, 20), ColorPattern(DeviceColor(1, 0, 0, 1))); + mUnusedTransformWarningLayer->NotifySurfaceReady(); + } + mUnusedTransformWarningLayer->SetPosition( + IntPoint(mRenderBounds.XMost() - 20, mRenderBounds.Y())); + mNativeLayerRoot->AppendLayer(mUnusedTransformWarningLayer); + + mUnusedApzTransformWarning = false; + SetDebugOverlayWantsNextFrame(true); + } + + if (mDisabledApzWarning) { + // If we have a disabled APZ on this composite, draw a 20x20 yellow box + // in the top-right corner, to the left of the unused-apz-transform + // warning box. + if (!mDisabledApzWarningLayer) { + mDisabledApzWarningLayer = + mNativeLayerRoot->CreateLayer(square, true, mSurfacePoolHandle); + RefPtr<DrawTarget> dt = + mDisabledApzWarningLayer->NextSurfaceAsDrawTarget( + IntRect({}, square), IntRect({}, square), BackendType::SKIA); + dt->FillRect(Rect(0, 0, 20, 20), ColorPattern(DeviceColor(1, 1, 0, 1))); + mDisabledApzWarningLayer->NotifySurfaceReady(); + } + mDisabledApzWarningLayer->SetPosition( + IntPoint(mRenderBounds.XMost() - 40, mRenderBounds.Y())); + mNativeLayerRoot->AppendLayer(mDisabledApzWarningLayer); + + mDisabledApzWarning = false; + SetDebugOverlayWantsNextFrame(true); + } + } else { + mGPUStatsLayer = nullptr; + mUnusedTransformWarningLayer = nullptr; + mDisabledApzWarningLayer = nullptr; + } +} + +RefPtr<CompositingRenderTarget> +LayerManagerComposite::PushGroupForLayerEffects() { + // This is currently true, so just making sure that any new use of this + // method is flagged for investigation + MOZ_ASSERT(StaticPrefs::layers_effect_invert() || + StaticPrefs::layers_effect_grayscale() || + StaticPrefs::layers_effect_contrast() != 0.0); + + RefPtr<CompositingRenderTarget> previousTarget = + mCompositor->GetCurrentRenderTarget(); + // make our render target the same size as the destination target + // so that we don't have to change size if the drawing area changes. + IntRect rect(previousTarget->GetOrigin(), previousTarget->GetSize()); + // XXX: I'm not sure if this is true or not... + MOZ_ASSERT(rect.IsEqualXY(0, 0)); + if (!mTwoPassTmpTarget || + mTwoPassTmpTarget->GetSize() != previousTarget->GetSize() || + mTwoPassTmpTarget->GetOrigin() != previousTarget->GetOrigin()) { + mTwoPassTmpTarget = mCompositor->CreateRenderTarget(rect, INIT_MODE_NONE); + } + MOZ_ASSERT(mTwoPassTmpTarget); + mCompositor->SetRenderTarget(mTwoPassTmpTarget); + return previousTarget; +} + +void LayerManagerComposite::PopGroupForLayerEffects( + RefPtr<CompositingRenderTarget> aPreviousTarget, IntRect aClipRect, + bool aGrayscaleEffect, bool aInvertEffect, float aContrastEffect) { + MOZ_ASSERT(mTwoPassTmpTarget); + + // This is currently true, so just making sure that any new use of this + // method is flagged for investigation + MOZ_ASSERT(aInvertEffect || aGrayscaleEffect || aContrastEffect != 0.0); + + mCompositor->SetRenderTarget(aPreviousTarget); + + EffectChain effectChain(RootLayer()); + Matrix5x4 effectMatrix; + if (aGrayscaleEffect) { + // R' = G' = B' = luminance + // R' = 0.2126*R + 0.7152*G + 0.0722*B + // G' = 0.2126*R + 0.7152*G + 0.0722*B + // B' = 0.2126*R + 0.7152*G + 0.0722*B + Matrix5x4 grayscaleMatrix(0.2126f, 0.2126f, 0.2126f, 0, 0.7152f, 0.7152f, + 0.7152f, 0, 0.0722f, 0.0722f, 0.0722f, 0, 0, 0, 0, + 1, 0, 0, 0, 0); + effectMatrix = grayscaleMatrix; + } + + if (aInvertEffect) { + // R' = 1 - R + // G' = 1 - G + // B' = 1 - B + Matrix5x4 colorInvertMatrix(-1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, + 1, 1, 1, 1, 0); + effectMatrix = effectMatrix * colorInvertMatrix; + } + + if (aContrastEffect != 0.0) { + // Multiplying with: + // R' = (1 + c) * (R - 0.5) + 0.5 + // G' = (1 + c) * (G - 0.5) + 0.5 + // B' = (1 + c) * (B - 0.5) + 0.5 + float cP1 = aContrastEffect + 1; + float hc = 0.5 * aContrastEffect; + Matrix5x4 contrastMatrix(cP1, 0, 0, 0, 0, cP1, 0, 0, 0, 0, cP1, 0, 0, 0, 0, + 1, -hc, -hc, -hc, 0); + effectMatrix = effectMatrix * contrastMatrix; + } + + effectChain.mPrimaryEffect = new EffectRenderTarget(mTwoPassTmpTarget); + effectChain.mSecondaryEffects[EffectTypes::COLOR_MATRIX] = + new EffectColorMatrix(effectMatrix); + + mCompositor->DrawQuad(Rect(Point(0, 0), Size(mTwoPassTmpTarget->GetSize())), + aClipRect, effectChain, 1., Matrix4x4()); +} + +void LayerManagerComposite::PlaceNativeLayers( + const IntRegion& aRegion, bool aOpaque, + std::deque<RefPtr<NativeLayer>>* aLayersToRecycle, + IntRegion* aWindowInvalidRegion) { + IntSize tileSize(StaticPrefs::layers_compositing_tiles_width(), + StaticPrefs::layers_compositing_tiles_height()); + IntRect regionBounds = aRegion.GetBounds(); + for (int32_t y = 0; y < regionBounds.YMost(); y += tileSize.height) { + for (int32_t x = 0; x < regionBounds.XMost(); x += tileSize.width) { + IntRegion tileRegion; + tileRegion.And(aRegion, IntRect(IntPoint(x, y), tileSize)); + for (auto iter = tileRegion.RectIter(); !iter.Done(); iter.Next()) { + PlaceNativeLayer(iter.Get(), aOpaque, aLayersToRecycle, + aWindowInvalidRegion); + } + } + } +} + +void LayerManagerComposite::PlaceNativeLayer( + const IntRect& aRect, bool aOpaque, + std::deque<RefPtr<NativeLayer>>* aLayersToRecycle, + IntRegion* aWindowInvalidRegion) { + RefPtr<NativeLayer> layer; + if (aLayersToRecycle->empty() || + aLayersToRecycle->front()->GetSize() != aRect.Size() || + aLayersToRecycle->front()->IsOpaque() != aOpaque) { + layer = mNativeLayerRoot->CreateLayer(aRect.Size(), aOpaque, + mSurfacePoolHandle); + mNativeLayerRoot->AppendLayer(layer); + aWindowInvalidRegion->OrWith(aRect); + } else { + layer = aLayersToRecycle->front(); + aLayersToRecycle->pop_front(); + IntRect oldRect = layer->GetRect(); + if (!aRect.IsEqualInterior(oldRect)) { + aWindowInvalidRegion->OrWith(oldRect); + aWindowInvalidRegion->OrWith(aRect); + } + } + layer->SetPosition(aRect.TopLeft()); + mNativeLayers.push_back(layer); +} + +// Used to clear the 'mLayerComposited' flag at the beginning of each Render(). +static void ClearLayerFlags(Layer* aLayer) { + ForEachNode<ForwardIterator>(aLayer, [](Layer* layer) { + if (layer->AsHostLayer()) { + static_cast<LayerComposite*>(layer->AsHostLayer()) + ->SetLayerComposited(false); + } + }); +} + +bool LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion, + const nsIntRegion& aOpaqueRegion) { + AUTO_PROFILER_LABEL("LayerManagerComposite::Render", GRAPHICS); + + if (mDestroyed || !mCompositor || mCompositor->IsDestroyed()) { + NS_WARNING("Call on destroyed layer manager"); + return false; + } + + mCompositor->RequestAllowFrameRecording(!!mCompositionRecorder); + + ClearLayerFlags(mRoot); + + // At this time, it doesn't really matter if these preferences change + // during the execution of the function; we should be safe in all + // permutations. However, may as well just get the values onces and + // then use them, just in case the consistency becomes important in + // the future. + bool invertVal = StaticPrefs::layers_effect_invert(); + bool grayscaleVal = StaticPrefs::layers_effect_grayscale(); + float contrastVal = StaticPrefs::layers_effect_contrast(); + bool haveLayerEffects = (invertVal || grayscaleVal || contrastVal != 0.0); + + // Set LayerScope begin/end frame + LayerScopeAutoFrame frame(PR_Now()); + + // If you're looking for the code to dump the layer tree, it was moved + // to CompositorBridgeParent::CompositeToTarget(). + + // Dump to LayerScope Viewer + if (LayerScope::CheckSendable()) { + // Create a LayersPacket, dump Layers into it and transfer the + // packet('s ownership) to LayerScope. + auto packet = MakeUnique<layerscope::Packet>(); + layerscope::LayersPacket* layersPacket = packet->mutable_layers(); + this->Dump(layersPacket); + LayerScope::SendLayerDump(std::move(packet)); + } + + mozilla::widget::WidgetRenderingContext widgetContext; +#if defined(XP_MACOSX) + if (CompositorOGL* compositorOGL = mCompositor->AsCompositorOGL()) { + widgetContext.mGL = compositorOGL->gl(); + } +#endif + + { + AUTO_PROFILER_LABEL("LayerManagerComposite::Render:Prerender", GRAPHICS); + + if (!mCompositor->GetWidget()->PreRender(&widgetContext)) { + return false; + } + } + + CompositorBench(mCompositor, mRenderBounds); + + MOZ_ASSERT(mRoot->GetOpacity() == 1); +#if defined(MOZ_WIDGET_ANDROID) + LayerMetricsWrapper wrapper = GetRootContentLayer(); + if (wrapper) { + mCompositor->SetClearColor(wrapper.Metadata().GetBackgroundColor()); + } else { + mCompositor->SetClearColorToDefault(); + } +#endif + + Maybe<IntRect> rootLayerClip = mRoot->GetClipRect().map( + [](const ParentLayerIntRect& r) { return r.ToUnknownRect(); }); + Maybe<IntRect> maybeBounds; + bool usingNativeLayers = false; + if (mTarget) { + maybeBounds = mCompositor->BeginFrameForTarget( + aInvalidRegion, rootLayerClip, mRenderBounds, aOpaqueRegion, mTarget, + mTargetBounds); + } else if (mNativeLayerRoot) { + mSurfacePoolHandle->OnBeginFrame(); + if (aInvalidRegion.Intersects(mRenderBounds)) { + mCompositor->BeginFrameForNativeLayers(); + maybeBounds = Some(mRenderBounds); + usingNativeLayers = true; + } + } else { + maybeBounds = mCompositor->BeginFrameForWindow( + aInvalidRegion, rootLayerClip, mRenderBounds, aOpaqueRegion); + } + + if (!maybeBounds) { + mProfilerScreenshotGrabber.NotifyEmptyFrame(); + mCompositor->GetWidget()->PostRender(&widgetContext); + + // Discard the current payloads. These payloads did not require a composite + // (they caused no changes to anything visible), so we don't want to measure + // their latency. + mPayload.Clear(); + + return true; + } + + IntRect bounds = *maybeBounds; + IntRect clipRect = rootLayerClip.valueOr(bounds); + + // Prepare our layers. + { + Diagnostics::Record record(mRenderStartTime); + RootLayer()->Prepare(RenderTargetIntRect::FromUnknownRect(clipRect)); + if (record.Recording()) { + mDiagnostics->RecordPrepareTime(record.Duration()); + } + } + + auto RenderOnce = [&](const IntRect& aClipRect) { + RefPtr<CompositingRenderTarget> previousTarget; + if (haveLayerEffects) { + previousTarget = PushGroupForLayerEffects(); + } else { + mTwoPassTmpTarget = nullptr; + } + + // Execute draw commands. + RootLayer()->RenderLayer(aClipRect, Nothing()); + + if (mTwoPassTmpTarget) { + MOZ_ASSERT(haveLayerEffects); + PopGroupForLayerEffects(previousTarget, aClipRect, grayscaleVal, + invertVal, contrastVal); + } + if (!mRegionToClear.IsEmpty()) { + for (auto iter = mRegionToClear.RectIter(); !iter.Done(); iter.Next()) { + mCompositor->ClearRect(Rect(iter.Get())); + } + } + mCompositor->NormalDrawingDone(); + }; + + { + Diagnostics::Record record; + + if (usingNativeLayers) { + // Update the placement of our native layers, so that transparent and + // opaque parts of the window are covered by different layers and we can + // update those parts separately. + IntRegion opaqueRegion; + opaqueRegion.And(aOpaqueRegion, mRenderBounds); + + // Limit the complexity of these regions. Usually, opaqueRegion should be + // only one or two rects, so this SimplifyInward call will not change the + // region if everything looks as expected. + opaqueRegion.SimplifyInward(4); + + IntRegion transparentRegion; + transparentRegion.Sub(mRenderBounds, opaqueRegion); + std::deque<RefPtr<NativeLayer>> layersToRecycle = + std::move(mNativeLayers); + IntRegion invalidRegion = aInvalidRegion; + PlaceNativeLayers(opaqueRegion, true, &layersToRecycle, &invalidRegion); + PlaceNativeLayers(transparentRegion, false, &layersToRecycle, + &invalidRegion); + for (const auto& unusedLayer : layersToRecycle) { + mNativeLayerRoot->RemoveLayer(unusedLayer); + } + + for (const auto& nativeLayer : mNativeLayers) { + Maybe<IntRect> maybeLayerRect = + mCompositor->BeginRenderingToNativeLayer( + invalidRegion, rootLayerClip, aOpaqueRegion, nativeLayer); + if (!maybeLayerRect) { + continue; + } + + if (rootLayerClip) { + RenderOnce(rootLayerClip->Intersect(*maybeLayerRect)); + } else { + RenderOnce(*maybeLayerRect); + } + mCompositor->EndRenderingToNativeLayer(); + } + } else { + RenderOnce(clipRect); + } + + if (record.Recording()) { + mDiagnostics->RecordCompositeTime(record.Duration()); + } + } + + RootLayer()->Cleanup(); + + WindowLMC window(mCompositor); + mProfilerScreenshotGrabber.MaybeGrabScreenshot(window, bounds.Size()); + + if (mCompositionRecorder) { + bool hasContentPaint = std::any_of( + mPayload.begin(), mPayload.end(), [](CompositionPayload& payload) { + return payload.mType == CompositionPayloadType::eContentPaint; + }); + + if (hasContentPaint) { + if (RefPtr<RecordedFrame> frame = + mCompositor->RecordFrame(TimeStamp::Now())) { + mCompositionRecorder->RecordFrame(frame); + } + } + } + + if (usingNativeLayers) { + UpdateDebugOverlayNativeLayers(); + } else { +#if defined(MOZ_WIDGET_ANDROID) + HandlePixelsTarget(); +#endif // defined(MOZ_WIDGET_ANDROID) + + // Debugging + RenderDebugOverlay(bounds); + } + + { + AUTO_PROFILER_LABEL("LayerManagerComposite::Render:EndFrame", GRAPHICS); + + mCompositor->EndFrame(); + + if (usingNativeLayers) { + mNativeLayerRoot->CommitToScreen(); + } + } + + mCompositor->GetWidget()->PostRender(&widgetContext); + + mProfilerScreenshotGrabber.MaybeProcessQueue(); + + RecordFrame(); + + PayloadPresented(TimeStamp::Now()); + + // Our payload has now been presented. + mPayload.Clear(); + + if (usingNativeLayers) { + mSurfacePoolHandle->OnEndFrame(); + } + + mCompositor->WaitForGPU(); + + return true; +} + +#if defined(MOZ_WIDGET_ANDROID) +class ScopedCompostitorSurfaceSize { + public: + ScopedCompostitorSurfaceSize(CompositorOGL* aCompositor, + const gfx::IntSize& aSize) + : mCompositor(aCompositor), + mOriginalSize(mCompositor->GetDestinationSurfaceSize()) { + mCompositor->SetDestinationSurfaceSize(aSize); + } + ~ScopedCompostitorSurfaceSize() { + mCompositor->SetDestinationSurfaceSize(mOriginalSize); + } + + private: + CompositorOGL* const mCompositor; + const gfx::IntSize mOriginalSize; +}; + +class ScopedContextSurfaceOverride { + public: + ScopedContextSurfaceOverride(GLContextEGL* aContext, void* aSurface) + : mContext(aContext) { + MOZ_ASSERT(aSurface); + mContext->SetEGLSurfaceOverride(aSurface); + mContext->MakeCurrent(true); + } + ~ScopedContextSurfaceOverride() { + mContext->SetEGLSurfaceOverride(EGL_NO_SURFACE); + mContext->MakeCurrent(true); + } + + private: + GLContextEGL* const mContext; +}; + +void LayerManagerComposite::RenderToPresentationSurface() { + if (!mCompositor) { + return; + } + + widget::CompositorWidget* const widget = mCompositor->GetWidget(); + + if (!widget) { + return; + } + + ANativeWindow* window = widget->AsAndroid()->GetPresentationANativeWindow(); + + if (!window) { + return; + } + + CompositorOGL* compositor = mCompositor->AsCompositorOGL(); + GLContext* gl = compositor->gl(); + GLContextEGL* egl = GLContextEGL::Cast(gl); + + if (!egl) { + return; + } + + EGLSurface surface = widget->AsAndroid()->GetPresentationEGLSurface(); + + if (!surface) { + // create surface; + surface = egl->CreateCompatibleSurface(window); + if (!surface) { + return; + } + + widget->AsAndroid()->SetPresentationEGLSurface(surface); + } + + const IntSize windowSize(ANativeWindow_getWidth(window), + ANativeWindow_getHeight(window)); + + if ((windowSize.width <= 0) || (windowSize.height <= 0)) { + return; + } + + ScreenRotation rotation = compositor->GetScreenRotation(); + + const int actualWidth = windowSize.width; + const int actualHeight = windowSize.height; + + const gfx::IntSize originalSize = compositor->GetDestinationSurfaceSize(); + const nsIntRect originalRect = + nsIntRect(0, 0, originalSize.width, originalSize.height); + + int pageWidth = originalSize.width; + int pageHeight = originalSize.height; + if (rotation == ROTATION_90 || rotation == ROTATION_270) { + pageWidth = originalSize.height; + pageHeight = originalSize.width; + } + + float scale = 1.0; + + if ((pageWidth > actualWidth) || (pageHeight > actualHeight)) { + const float scaleWidth = (float)actualWidth / (float)pageWidth; + const float scaleHeight = (float)actualHeight / (float)pageHeight; + scale = scaleWidth <= scaleHeight ? scaleWidth : scaleHeight; + } + + const gfx::IntSize actualSize(actualWidth, actualHeight); + ScopedCompostitorSurfaceSize overrideSurfaceSize(compositor, actualSize); + + const ScreenPoint offset((actualWidth - (int)(scale * pageWidth)) / 2, 0); + ScopedContextSurfaceOverride overrideSurface(egl, surface); + + Matrix viewMatrix = ComputeTransformForRotation(originalRect, rotation); + viewMatrix.Invert(); // unrotate + viewMatrix.PostScale(scale, scale); + viewMatrix.PostTranslate(offset.x, offset.y); + Matrix4x4 matrix = Matrix4x4::From2D(viewMatrix); + + mRoot->ComputeEffectiveTransforms(matrix); + nsIntRegion opaque; + PostProcessLayers(opaque); + + nsIntRegion invalid; + IntRect bounds = IntRect::Truncate(0, 0, scale * pageWidth, actualHeight); + MOZ_ASSERT(mRoot->GetOpacity() == 1); + Unused << mCompositor->BeginFrameForWindow(invalid, Nothing(), bounds, + nsIntRegion()); + + // The Java side of Fennec sets a scissor rect that accounts for + // chrome such as the URL bar. Override that so that the entire frame buffer + // is cleared. + ScopedScissorRect scissorRect(egl, 0, 0, actualWidth, actualHeight); + egl->fClearColor(0.0, 0.0, 0.0, 0.0); + egl->fClear(LOCAL_GL_COLOR_BUFFER_BIT); + + const IntRect clipRect = IntRect::Truncate(0, 0, actualWidth, actualHeight); + + RootLayer()->Prepare(RenderTargetIntRect::FromUnknownRect(clipRect)); + RootLayer()->RenderLayer(clipRect, Nothing()); + + mCompositor->EndFrame(); +} + +// Used by robocop tests to get a snapshot of the frame buffer. +void LayerManagerComposite::HandlePixelsTarget() { + if (!mScreenPixelsTarget) { + return; + } + + int32_t bufferWidth = mRenderBounds.width; + int32_t bufferHeight = mRenderBounds.height; + ipc::Shmem mem; + if (!mScreenPixelsTarget->AllocPixelBuffer( + bufferWidth * bufferHeight * sizeof(uint32_t), &mem)) { + // Failed to alloc shmem, Just bail out. + return; + } + CompositorOGL* compositor = mCompositor->AsCompositorOGL(); + GLContext* gl = compositor->gl(); + MOZ_ASSERT(gl); + gl->fReadPixels(0, 0, bufferWidth, bufferHeight, LOCAL_GL_RGBA, + LOCAL_GL_UNSIGNED_BYTE, mem.get<uint8_t>()); + Unused << mScreenPixelsTarget->SendScreenPixels( + std::move(mem), ScreenIntSize(bufferWidth, bufferHeight), true); + mScreenPixelsTarget = nullptr; +} +#endif + +already_AddRefed<PaintedLayer> LayerManagerComposite::CreatePaintedLayer() { + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr<PaintedLayer>(new PaintedLayerComposite(this)).forget(); +} + +already_AddRefed<ContainerLayer> LayerManagerComposite::CreateContainerLayer() { + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr<ContainerLayer>(new ContainerLayerComposite(this)).forget(); +} + +already_AddRefed<ImageLayer> LayerManagerComposite::CreateImageLayer() { + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr<ImageLayer>(new ImageLayerComposite(this)).forget(); +} + +already_AddRefed<ColorLayer> LayerManagerComposite::CreateColorLayer() { + if (LayerManagerComposite::mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr<ColorLayer>(new ColorLayerComposite(this)).forget(); +} + +already_AddRefed<CanvasLayer> LayerManagerComposite::CreateCanvasLayer() { + if (LayerManagerComposite::mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr<CanvasLayer>(new CanvasLayerComposite(this)).forget(); +} + +already_AddRefed<RefLayer> LayerManagerComposite::CreateRefLayer() { + if (LayerManagerComposite::mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr<RefLayer>(new RefLayerComposite(this)).forget(); +} + +LayerManagerComposite::AutoAddMaskEffect::AutoAddMaskEffect( + Layer* aMaskLayer, EffectChain& aEffects) + : mCompositable(nullptr), mFailed(false) { + if (!aMaskLayer) { + return; + } + + mCompositable = ToLayerComposite(aMaskLayer)->GetCompositableHost(); + if (!mCompositable) { + NS_WARNING("Mask layer with no compositable host"); + mFailed = true; + return; + } + + if (!mCompositable->AddMaskEffect(aEffects, + aMaskLayer->GetEffectiveTransform())) { + mCompositable = nullptr; + mFailed = true; + } +} + +LayerManagerComposite::AutoAddMaskEffect::~AutoAddMaskEffect() { + if (!mCompositable) { + return; + } + + mCompositable->RemoveMaskEffect(); +} + +bool LayerManagerComposite::IsCompositingToScreen() const { return !mTarget; } + +LayerComposite::LayerComposite(LayerManagerComposite* aManager) + : HostLayer(aManager), + mCompositeManager(aManager), + mCompositor(aManager->GetCompositor()), + mDestroyed(false), + mLayerComposited(false) {} + +LayerComposite::~LayerComposite() = default; + +void LayerComposite::Destroy() { + if (!mDestroyed) { + mDestroyed = true; + CleanupResources(); + } +} + +void LayerComposite::AddBlendModeEffect(EffectChain& aEffectChain) { + gfx::CompositionOp blendMode = GetLayer()->GetEffectiveMixBlendMode(); + if (blendMode == gfx::CompositionOp::OP_OVER) { + return; + } + + aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE] = + new EffectBlendMode(blendMode); +} + +bool LayerManagerComposite::CanUseCanvasLayerForSize(const IntSize& aSize) { + return mCompositor->CanUseCanvasLayerForSize( + gfx::IntSize(aSize.width, aSize.height)); +} + +void LayerManagerComposite::NotifyShadowTreeTransaction() { + if (StaticPrefs::layers_acceleration_draw_fps()) { + mDiagnostics->AddTxnFrame(); + } +} + +void LayerComposite::SetLayerManager(HostLayerManager* aManager) { + HostLayer::SetLayerManager(aManager); + mCompositeManager = static_cast<LayerManagerComposite*>(aManager); + mCompositor = mCompositeManager->GetCompositor(); +} + +bool LayerManagerComposite::AsyncPanZoomEnabled() const { + if (CompositorBridgeParent* bridge = + mCompositor->GetCompositorBridgeParent()) { + return bridge->GetOptions().UseAPZ(); + } + return false; +} + +bool LayerManagerComposite::AlwaysScheduleComposite() const { + return !!(mCompositor->GetDiagnosticTypes() & DiagnosticTypes::FLASH_BORDERS); +} + +nsIntRegion LayerComposite::GetFullyRenderedRegion() { + if (TiledContentHost* tiled = + GetCompositableHost() ? GetCompositableHost()->AsTiledContentHost() + : nullptr) { + nsIntRegion shadowVisibleRegion = + GetShadowVisibleRegion().ToUnknownRegion(); + // Discard the region which hasn't been drawn yet when doing + // progressive drawing. Note that if the shadow visible region + // shrunk the tiled valig region may not have discarded this yet. + shadowVisibleRegion.And(shadowVisibleRegion, tiled->GetValidRegion()); + return shadowVisibleRegion; + } else { + return GetShadowVisibleRegion().ToUnknownRegion(); + } +} + +Matrix4x4 HostLayer::GetShadowTransform() { + Matrix4x4 transform = mShadowTransform; + Layer* layer = GetLayer(); + + transform.PostScale(layer->GetPostXScale(), layer->GetPostYScale(), 1.0f); + if (const ContainerLayer* c = layer->AsContainerLayer()) { + transform.PreScale(c->GetPreXScale(), c->GetPreYScale(), 1.0f); + } + + return transform; +} + +// Async animations can move child layers without updating our visible region. +// PostProcessLayers will recompute visible regions for layers with an +// intermediate surface, but otherwise we need to do it now. +static void ComputeVisibleRegionForChildren(ContainerLayer* aContainer, + LayerIntRegion& aResult) { + for (Layer* l = aContainer->GetFirstChild(); l; l = l->GetNextSibling()) { + if (l->Extend3DContext()) { + MOZ_ASSERT(l->AsContainerLayer()); + ComputeVisibleRegionForChildren(l->AsContainerLayer(), aResult); + } else { + AddTransformedRegion(aResult, l->GetLocalVisibleRegion(), + l->ComputeTransformToPreserve3DRoot()); + } + } +} + +void HostLayer::RecomputeShadowVisibleRegionFromChildren() { + mShadowVisibleRegion.SetEmpty(); + ContainerLayer* container = GetLayer()->AsContainerLayer(); + MOZ_ASSERT(container); + // Layers that extend a 3d context have a local visible region + // that can only be represented correctly in 3d space. Since + // we can't do that, leave it empty instead to stop anyone + // from trying to use it. + NS_ASSERTION( + !GetLayer()->Extend3DContext(), + "Can't compute visible region for layers that extend a 3d context"); + if (container && !GetLayer()->Extend3DContext()) { + ComputeVisibleRegionForChildren(container, mShadowVisibleRegion); + } +} + +bool LayerComposite::HasStaleCompositor() const { + return mCompositeManager->GetCompositor() != mCompositor; +} + +#ifndef MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS + +/*static*/ +bool LayerManagerComposite::SupportsDirectTexturing() { return false; } + +/*static*/ +void LayerManagerComposite::PlatformSyncBeforeReplyUpdate() {} + +#endif // !defined(MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS) + +class RenderSourceLMC : public profiler_screenshots::RenderSource { + public: + explicit RenderSourceLMC(CompositingRenderTarget* aRT) + : RenderSource(aRT->GetSize()), mRT(aRT) {} + + const auto& RenderTarget() { return mRT; } + + protected: + virtual ~RenderSourceLMC() {} + + RefPtr<CompositingRenderTarget> mRT; +}; + +class DownscaleTargetLMC : public profiler_screenshots::DownscaleTarget { + public: + explicit DownscaleTargetLMC(CompositingRenderTarget* aRT, + Compositor* aCompositor) + : profiler_screenshots::DownscaleTarget(aRT->GetSize()), + mRenderSource(new RenderSourceLMC(aRT)), + mCompositor(aCompositor) {} + + already_AddRefed<profiler_screenshots::RenderSource> AsRenderSource() + override { + return do_AddRef(mRenderSource); + } + + bool DownscaleFrom(profiler_screenshots::RenderSource* aSource, + const IntRect& aSourceRect, + const IntRect& aDestRect) override { + MOZ_RELEASE_ASSERT(aSourceRect.TopLeft() == IntPoint()); + MOZ_RELEASE_ASSERT(aDestRect.TopLeft() == IntPoint()); + RefPtr<CompositingRenderTarget> previousTarget = + mCompositor->GetCurrentRenderTarget(); + + mCompositor->SetRenderTarget(mRenderSource->RenderTarget()); + bool result = mCompositor->BlitRenderTarget( + static_cast<RenderSourceLMC*>(aSource)->RenderTarget(), + aSourceRect.Size(), aDestRect.Size()); + + // Restore the old render target. + mCompositor->SetRenderTarget(previousTarget); + + return result; + } + + protected: + virtual ~DownscaleTargetLMC() {} + + RefPtr<RenderSourceLMC> mRenderSource; + Compositor* mCompositor; +}; + +class AsyncReadbackBufferLMC + : public profiler_screenshots::AsyncReadbackBuffer { + public: + AsyncReadbackBufferLMC(mozilla::layers::AsyncReadbackBuffer* aARB, + Compositor* aCompositor) + : profiler_screenshots::AsyncReadbackBuffer(aARB->GetSize()), + mARB(aARB), + mCompositor(aCompositor) {} + void CopyFrom(profiler_screenshots::RenderSource* aSource) override { + mCompositor->ReadbackRenderTarget( + static_cast<RenderSourceLMC*>(aSource)->RenderTarget(), mARB); + } + bool MapAndCopyInto(DataSourceSurface* aSurface, + const IntSize& aReadSize) override { + return mARB->MapAndCopyInto(aSurface, aReadSize); + } + + protected: + virtual ~AsyncReadbackBufferLMC() {} + + RefPtr<mozilla::layers::AsyncReadbackBuffer> mARB; + Compositor* mCompositor; +}; + +already_AddRefed<profiler_screenshots::RenderSource> +WindowLMC::GetWindowContents(const gfx::IntSize& aWindowSize) { + RefPtr<CompositingRenderTarget> rt = mCompositor->GetWindowRenderTarget(); + if (!rt) { + return nullptr; + } + return MakeAndAddRef<RenderSourceLMC>(rt); +} + +already_AddRefed<profiler_screenshots::DownscaleTarget> +WindowLMC::CreateDownscaleTarget(const gfx::IntSize& aSize) { + RefPtr<CompositingRenderTarget> rt = + mCompositor->CreateRenderTarget(IntRect({}, aSize), INIT_MODE_NONE); + return MakeAndAddRef<DownscaleTargetLMC>(rt, mCompositor); +} + +already_AddRefed<profiler_screenshots::AsyncReadbackBuffer> +WindowLMC::CreateAsyncReadbackBuffer(const gfx::IntSize& aSize) { + RefPtr<AsyncReadbackBuffer> carb = + mCompositor->CreateAsyncReadbackBuffer(aSize); + if (!carb) { + return nullptr; + } + return MakeAndAddRef<AsyncReadbackBufferLMC>(carb, mCompositor); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/LayerManagerComposite.h b/gfx/layers/composite/LayerManagerComposite.h new file mode 100644 index 0000000000..18f8f0aea9 --- /dev/null +++ b/gfx/layers/composite/LayerManagerComposite.h @@ -0,0 +1,729 @@ +/* -*- 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/. */ + +#ifndef GFX_LayerManagerComposite_H +#define GFX_LayerManagerComposite_H + +#include <cstdint> // for uint64_t, int32_t +#include <deque> // for deque +#include <new> // for operator new +#include <type_traits> // for remove_reference<>::type +#include <utility> // for move, forward +#include "CompositableHost.h" // for ImageCompositeNotificationInfo +#include "Units.h" // for LayerIntRegion, ParentLayerIntRect, RenderTargetIntRect +#include "mozilla/AlreadyAddRefed.h" // for already_AddRefed +#include "mozilla/Assertions.h" // for MOZ_CRASH, AssertionConditionType, MOZ_ASSERT, MOZ_AS... +#include "mozilla/Maybe.h" // for Maybe +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/TimeStamp.h" // for TimeStamp, BaseTimeDuration +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Polygon.h" // for Polygon +#include "mozilla/gfx/Rect.h" // for IntRect +#include "mozilla/gfx/Types.h" // for DeviceColor (ptr only), SurfaceFormat +#include "mozilla/layers/CompositionRecorder.h" // for CompositionRecorder, CollectedFrames (ptr only) +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorTypes.h" // for DiagnosticTypes, TextureFactoryIdentifier +#include "mozilla/layers/LayerManager.h" // for LayerManager::END_DEFAULT, LayerManager::EndTransacti... +#include "mozilla/layers/LayersTypes.h" // for CompositionOpportunityId, LayersBackend, LayersBacken... +#include "mozilla/layers/ScreenshotGrabber.h" // for ScreenshotGrabber +#include "nsDebug.h" // for NS_WARNING +#include "nsIThread.h" // for TimeDuration +#include "nsRegion.h" // for nsIntRegion +#include "nsRegionFwd.h" // for IntRegion +#include "nsStringFwd.h" // for nsCString, nsAString +#include "nsTArray.h" // for nsTArray + +class gfxContext; + +#ifdef XP_WIN +# include <windows.h> +#endif + +namespace mozilla { +namespace layers { + +class CanvasLayer; +class CanvasLayerComposite; +class ColorLayer; +class ColorLayerComposite; +class ContainerLayer; +class ContainerLayerComposite; +class Diagnostics; +struct EffectChain; +class ImageLayer; +class ImageLayerComposite; +class LayerComposite; +class NativeLayer; +class NativeLayerRoot; +class RefLayerComposite; +class PaintTiming; +class PaintedLayer; +class PaintedLayerComposite; +class RefLayer; +class SurfacePoolHandle; +class TextRenderer; +class TextureSourceProvider; +class CompositingRenderTarget; +struct FPSState; +class PaintCounter; +class LayerMLGPU; +class LayerManagerMLGPU; +class UiCompositorControllerParent; +class Layer; +struct LayerProperties; + +static const int kVisualWarningDuration = 150; // ms + +// An implementation of LayerManager that acts as a pair with ClientLayerManager +// and is mirrored across IPDL. This gets managed/updated by +// LayerTransactionParent. +class HostLayerManager : public LayerManager { + public: + HostLayerManager(); + virtual ~HostLayerManager(); + + bool BeginTransactionWithTarget(gfxContext* aTarget, + const nsCString& aURL) override { + MOZ_CRASH("GFX: Use BeginTransactionWithDrawTarget"); + } + + bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override { + MOZ_CRASH("GFX: Use EndTransaction(aTimeStamp)"); + return false; + } + + void EndTransaction(DrawPaintedLayerCallback aCallback, void* aCallbackData, + EndTransactionFlags aFlags = END_DEFAULT) override { + MOZ_CRASH("GFX: Use EndTransaction(aTimeStamp)"); + } + + int32_t GetMaxTextureSize() const override { + MOZ_CRASH("GFX: Call on compositor, not LayerManagerComposite"); + } + + void GetBackendName(nsAString& name) override { + MOZ_CRASH("GFX: Shouldn't be called for composited layer manager"); + } + + virtual void ForcePresent() = 0; + virtual void AddInvalidRegion(const nsIntRegion& aRegion) = 0; + + virtual void NotifyShadowTreeTransaction() {} + virtual void BeginTransactionWithDrawTarget(gfx::DrawTarget* aTarget, + const gfx::IntRect& aRect) = 0; + virtual Compositor* GetCompositor() const = 0; + virtual TextureSourceProvider* GetTextureSourceProvider() const = 0; + virtual void EndTransaction(const TimeStamp& aTimeStamp, + EndTransactionFlags aFlags = END_DEFAULT) = 0; + virtual void UpdateRenderBounds(const gfx::IntRect& aRect) {} + virtual void SetDiagnosticTypes(DiagnosticTypes aDiagnostics) {} + virtual void InvalidateAll() = 0; + + HostLayerManager* AsHostLayerManager() override { return this; } + virtual LayerManagerMLGPU* AsLayerManagerMLGPU() { return nullptr; } + + void ExtractImageCompositeNotifications( + nsTArray<ImageCompositeNotificationInfo>* aNotifications) { + aNotifications->AppendElements(std::move(mImageCompositeNotifications)); + } + + void AppendImageCompositeNotification( + const ImageCompositeNotificationInfo& aNotification) { + // Only send composite notifications when we're drawing to the screen, + // because that's what they mean. + // Also when we're not drawing to the screen, DidComposite will not be + // called to extract and send these notifications, so they might linger + // and contain stale ImageContainerParent pointers. + if (IsCompositingToScreen()) { + mImageCompositeNotifications.AppendElement(aNotification); + } + } + + /** + * LayerManagerComposite provides sophisticated debug overlays + * that can request a next frame. + */ + bool DebugOverlayWantsNextFrame() { return mDebugOverlayWantsNextFrame; } + void SetDebugOverlayWantsNextFrame(bool aVal) { + mDebugOverlayWantsNextFrame = aVal; + } + + /** + * Add an on frame warning. + * @param severity ranges from 0 to 1. It's used to compute the warning color. + */ + void VisualFrameWarning(float severity) { + mozilla::TimeStamp now = TimeStamp::Now(); + if (mWarnTime.IsNull() || severity > mWarningLevel || + mWarnTime + TimeDuration::FromMilliseconds(kVisualWarningDuration) < + now) { + mWarnTime = now; + mWarningLevel = severity; + } + } + + void SetPaintTime(const TimeDuration& aPaintTime) { + mLastPaintTime = aPaintTime; + } + + virtual bool AlwaysScheduleComposite() const { return false; } + virtual bool IsCompositingToScreen() const { return false; } + + void RecordPaintTimes(const PaintTiming& aTiming); + void RecordUpdateTime(float aValue); + + CompositionOpportunityId GetCompositionOpportunityId() const { + return mCompositionOpportunityId; + } + + TimeStamp GetCompositionTime() const { return mCompositionTime; } + void SetCompositionTime(TimeStamp aTimeStamp) { + mCompositionTime = aTimeStamp; + if (!mCompositionTime.IsNull() && !mCompositeUntilTime.IsNull() && + mCompositionTime >= mCompositeUntilTime) { + mCompositeUntilTime = TimeStamp(); + } + } + + void CompositeUntil(TimeStamp aTimeStamp) { + if (mCompositeUntilTime.IsNull() || mCompositeUntilTime < aTimeStamp) { + mCompositeUntilTime = aTimeStamp; + } + } + TimeStamp GetCompositeUntilTime() const { return mCompositeUntilTime; } + + // We maintaining a global mapping from ID to CompositorBridgeParent for + // async compositables. + uint64_t GetCompositorBridgeID() const { return mCompositorBridgeID; } + void SetCompositorBridgeID(uint64_t aID) { + MOZ_ASSERT(mCompositorBridgeID == 0, + "The compositor ID must be set only once."); + mCompositorBridgeID = aID; + } + + void SetCompositionRecorder(UniquePtr<CompositionRecorder> aRecorder) { + mCompositionRecorder = std::move(aRecorder); + } + + /** + * Write the frames collected by the |CompositionRecorder| to disk. + * + * If there is not currently a |CompositionRecorder|, this is a no-op. + */ + void WriteCollectedFrames(); + + Maybe<CollectedFrames> GetCollectedFrames(); + + protected: + bool mDebugOverlayWantsNextFrame; + nsTArray<ImageCompositeNotificationInfo> mImageCompositeNotifications; + // Testing property. If hardware composer is supported, this will return + // true if the last frame was deemed 'too complicated' to be rendered. + float mWarningLevel; + mozilla::TimeStamp mWarnTime; + UniquePtr<Diagnostics> mDiagnostics; + uint64_t mCompositorBridgeID; + + TimeDuration mLastPaintTime; + TimeStamp mRenderStartTime; + UniquePtr<CompositionRecorder> mCompositionRecorder = nullptr; + + // Render time for the current composition. + TimeStamp mCompositionTime; + + // CompositionOpportunityId of the current composition. + CompositionOpportunityId mCompositionOpportunityId; + + // When nonnull, during rendering, some compositable indicated that it will + // change its rendering at this time. In order not to miss it, we composite + // on every vsync until this time occurs (this is the latest such time). + TimeStamp mCompositeUntilTime; +#if defined(MOZ_WIDGET_ANDROID) + public: + // Used by UiCompositorControllerParent to set itself as the target for the + // contents of the frame buffer after a composite. + // Implemented in LayerManagerComposite + virtual void RequestScreenPixels(UiCompositorControllerParent* aController) {} +#endif // defined(MOZ_WIDGET_ANDROID) +}; + +// A layer manager implementation that uses the Compositor API +// to render layers. +class LayerManagerComposite final : public HostLayerManager { + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::IntSize IntSize; + typedef mozilla::gfx::SurfaceFormat SurfaceFormat; + + public: + explicit LayerManagerComposite(Compositor* aCompositor); + virtual ~LayerManagerComposite(); + + void Destroy() override; + + /** + * Sets the clipping region for this layer manager. This is important on + * windows because using OGL we no longer have GDI's native clipping. Therefor + * widget must tell us what part of the screen is being invalidated, + * and we should clip to this. + * + * \param aClippingRegion Region to clip to. Setting an empty region + * will disable clipping. + */ + void SetClippingRegion(const nsIntRegion& aClippingRegion) { + mClippingRegion = aClippingRegion; + } + + /** + * LayerManager implementation. + */ + LayerManagerComposite* AsLayerManagerComposite() override { return this; } + + void UpdateRenderBounds(const gfx::IntRect& aRect) override; + + bool BeginTransaction(const nsCString& aURL) override; + void BeginTransactionWithDrawTarget(gfx::DrawTarget* aTarget, + const gfx::IntRect& aRect) override; + void EndTransaction(const TimeStamp& aTimeStamp, + EndTransactionFlags aFlags = END_DEFAULT) override; + virtual void EndTransaction( + DrawPaintedLayerCallback aCallback, void* aCallbackData, + EndTransactionFlags aFlags = END_DEFAULT) override { + MOZ_CRASH("GFX: Use EndTransaction(aTimeStamp)"); + } + + void SetRoot(Layer* aLayer) override; + + // XXX[nrc]: never called, we should move this logic to ClientLayerManager + // (bug 946926). + bool CanUseCanvasLayerForSize(const gfx::IntSize& aSize) override; + + void ClearCachedResources(Layer* aSubtree = nullptr) override; + + already_AddRefed<PaintedLayer> CreatePaintedLayer() override; + already_AddRefed<ContainerLayer> CreateContainerLayer() override; + already_AddRefed<ImageLayer> CreateImageLayer() override; + already_AddRefed<ColorLayer> CreateColorLayer() override; + already_AddRefed<CanvasLayer> CreateCanvasLayer() override; + already_AddRefed<RefLayer> CreateRefLayer() override; + + bool AreComponentAlphaLayersEnabled() override; + + already_AddRefed<DrawTarget> CreateOptimalMaskDrawTarget( + const IntSize& aSize) override; + + const char* Name() const override { return ""; } + bool IsCompositingToScreen() const override; + + bool AlwaysScheduleComposite() const override; + + /** + * Post-processes layers before composition. This performs the following: + * + * - Applies occlusion culling. This restricts the shadow visible region of + * layers that are covered with opaque content. |aOpaqueRegion| is the + * region already known to be covered with opaque content, in the + * post-transform coordinate space of aLayer. + * + * - Recomputes visible regions to account for async transforms. + * Each layer accumulates into |aVisibleRegion| its post-transform + * (including async transforms) visible region. + * + * - aRenderTargetClip is the exact clip required for aLayer, in the + * coordinates of the nearest render target (the same as + * GetEffectiveTransform). + * + * - aClipFromAncestors is the approximate combined clip from all + * ancestors, in the coordinate space of our parent, but maybe be an + * overestimate in the presence of complex transforms. + */ + void PostProcessLayers(nsIntRegion& aOpaqueRegion); + void PostProcessLayers(Layer* aLayer, nsIntRegion& aOpaqueRegion, + LayerIntRegion& aVisibleRegion, + const Maybe<RenderTargetIntRect>& aRenderTargetClip, + const Maybe<ParentLayerIntRect>& aClipFromAncestors, + bool aCanContributeOpaque); + + /** + * RAII helper class to add a mask effect with the compositable from + * aMaskLayer to the EffectChain aEffect and notify the compositable when we + * are done. + */ + class AutoAddMaskEffect { + public: + AutoAddMaskEffect(Layer* aMaskLayer, EffectChain& aEffect); + ~AutoAddMaskEffect(); + + bool Failed() const { return mFailed; } + + private: + CompositableHost* mCompositable; + bool mFailed; + }; + + /** + * returns true if PlatformAllocBuffer will return a buffer that supports + * direct texturing + */ + static bool SupportsDirectTexturing(); + + static void PlatformSyncBeforeReplyUpdate(); + + void AddInvalidRegion(const nsIntRegion& aRegion) override { + mInvalidRegion.Or(mInvalidRegion, aRegion); + } + + Compositor* GetCompositor() const override { return mCompositor; } + TextureSourceProvider* GetTextureSourceProvider() const override { + return mCompositor; + } + + void NotifyShadowTreeTransaction() override; + + TextRenderer* GetTextRenderer() { return mTextRenderer; } + + void UnusedApzTransformWarning() { mUnusedApzTransformWarning = true; } + void DisabledApzWarning() { mDisabledApzWarning = true; } + + bool AsyncPanZoomEnabled() const override; + + public: + TextureFactoryIdentifier GetTextureFactoryIdentifier() override { + return mCompositor->GetTextureFactoryIdentifier(); + } + LayersBackend GetBackendType() override { + return mCompositor ? mCompositor->GetBackendType() + : LayersBackend::LAYERS_NONE; + } + void SetDiagnosticTypes(DiagnosticTypes aDiagnostics) override { + mCompositor->SetDiagnosticTypes(aDiagnostics); + } + + void InvalidateAll() override { + AddInvalidRegion(nsIntRegion(mRenderBounds)); + } + + void ForcePresent() override { mCompositor->ForcePresent(); } + + private: + /** Region we're clipping our current drawing to. */ + nsIntRegion mClippingRegion; + gfx::IntRect mRenderBounds; + + /** Current root layer. */ + LayerComposite* RootLayer() const; + + /** + * Update the invalid region and render it. + */ + void UpdateAndRender(); + + /** + * Render the current layer tree to the active target. + * Returns true if the current invalid region can be cleared, false if + * rendering was canceled. + */ + bool Render(const nsIntRegion& aInvalidRegion, + const nsIntRegion& aOpaqueRegion); +#if defined(MOZ_WIDGET_ANDROID) + void RenderToPresentationSurface(); + // Shifts the content down so the toolbar does not cover it. + // Returns the Y shift of the content in screen pixels + ScreenCoord GetContentShiftForToolbar(); + // Renders the static snapshot after the content has been rendered. + void RenderToolbar(); + // Used by robocop tests to get a snapshot of the frame buffer. + void HandlePixelsTarget(); +#endif + + /** + * We need to know our invalid region before we're ready to render. + */ + void InvalidateDebugOverlay(nsIntRegion& aInvalidRegion, + const gfx::IntRect& aBounds); + + /** + * Render debug overlays such as the FPS/FrameCounter above the frame. + */ + void RenderDebugOverlay(const gfx::IntRect& aBounds); + + void DrawBorder(const gfx::IntRect& aOuter, int32_t aBorderWidth, + const gfx::DeviceColor& aColor, + const gfx::Matrix4x4& aTransform); + void DrawTranslationWarningOverlay(const gfx::IntRect& aBounds); + + void UpdateDebugOverlayNativeLayers(); + + RefPtr<CompositingRenderTarget> PushGroupForLayerEffects(); + void PopGroupForLayerEffects(RefPtr<CompositingRenderTarget> aPreviousTarget, + gfx::IntRect aClipRect, bool aGrayscaleEffect, + bool aInvertEffect, float aContrastEffect); + + /** + * Create or recycle native layers to cover aRegion or aRect. + * This method takes existing layers from the front of aLayersToRecycle (or + * creates new layers if no layers are left to recycle) and appends them to + * the end of mNativeLayers. The "take from front, add to back" approach keeps + * the layer to rect assignment stable between frames. + * Updates the rect and opaqueness on the layers. For layers that moved or + * resized, *aWindowInvalidRegion is updated to include the area impacted by + * the move. + * Any layers left in aLayersToRecycle are not needed and can be disposed of. + */ + void PlaceNativeLayers(const gfx::IntRegion& aRegion, bool aOpaque, + std::deque<RefPtr<NativeLayer>>* aLayersToRecycle, + gfx::IntRegion* aWindowInvalidRegion); + void PlaceNativeLayer(const gfx::IntRect& aRect, bool aOpaque, + std::deque<RefPtr<NativeLayer>>* aLayersToRecycle, + gfx::IntRegion* aWindowInvalidRegion); + + bool mUnusedApzTransformWarning; + bool mDisabledApzWarning; + RefPtr<Compositor> mCompositor; + UniquePtr<LayerProperties> mClonedLayerTreeProperties; + + /** + * Context target, nullptr when drawing directly to our swap chain. + */ + RefPtr<gfx::DrawTarget> mTarget; + gfx::IntRect mTargetBounds; + + nsIntRegion mInvalidRegion; + + bool mInTransaction; + bool mIsCompositorReady; + + RefPtr<CompositingRenderTarget> mTwoPassTmpTarget; + ScreenshotGrabber mProfilerScreenshotGrabber; + RefPtr<TextRenderer> mTextRenderer; + RefPtr<NativeLayerRoot> mNativeLayerRoot; + RefPtr<SurfacePoolHandle> mSurfacePoolHandle; + std::deque<RefPtr<NativeLayer>> mNativeLayers; + RefPtr<NativeLayer> mGPUStatsLayer; + RefPtr<NativeLayer> mUnusedTransformWarningLayer; + RefPtr<NativeLayer> mDisabledApzWarningLayer; + +#ifdef USE_SKIA + /** + * Render paint and composite times above the frame. + */ + void DrawPaintTimes(Compositor* aCompositor); + RefPtr<PaintCounter> mPaintCounter; +#endif +#if defined(MOZ_WIDGET_ANDROID) + public: + virtual void RequestScreenPixels( + UiCompositorControllerParent* aController) override { + mScreenPixelsTarget = aController; + } + + private: + UiCompositorControllerParent* mScreenPixelsTarget; +#endif // defined(MOZ_WIDGET_ANDROID) +}; + +/** + * Compositor layers are for use with OMTC on the compositor thread only. There + * must be corresponding Client layers on the content thread. For composite + * layers, the layer manager only maintains the layer tree. + */ +class HostLayer { + public: + explicit HostLayer(HostLayerManager* aManager) + : mCompositorManager(aManager), + mShadowOpacity(1.0), + mShadowTransformSetByAnimation(false), + mShadowOpacitySetByAnimation(false) {} + + virtual void SetLayerManager(HostLayerManager* aManager) { + mCompositorManager = aManager; + } + HostLayerManager* GetLayerManager() const { return mCompositorManager; } + + virtual ~HostLayer() = default; + + virtual LayerComposite* GetFirstChildComposite() { return nullptr; } + + virtual Layer* GetLayer() = 0; + + virtual LayerMLGPU* AsLayerMLGPU() { return nullptr; } + + virtual bool SetCompositableHost(CompositableHost*) { + // We must handle this gracefully, see bug 967824 + NS_WARNING( + "called SetCompositableHost for a layer type not accepting a " + "compositable"); + return false; + } + virtual CompositableHost* GetCompositableHost() = 0; + + /** + * The following methods are + * + * CONSTRUCTION PHASE ONLY + * + * They are analogous to the Layer interface. + */ + void SetShadowVisibleRegion(const LayerIntRegion& aRegion) { + mShadowVisibleRegion = aRegion; + } + void SetShadowVisibleRegion(LayerIntRegion&& aRegion) { + mShadowVisibleRegion = std::move(aRegion); + } + + void SetShadowOpacity(float aOpacity) { mShadowOpacity = aOpacity; } + void SetShadowOpacitySetByAnimation(bool aSetByAnimation) { + mShadowOpacitySetByAnimation = aSetByAnimation; + } + + void SetShadowClipRect(const Maybe<ParentLayerIntRect>& aRect) { + mShadowClipRect = aRect; + } + + void SetShadowBaseTransform(const gfx::Matrix4x4& aMatrix) { + mShadowTransform = aMatrix; + } + void SetShadowTransformSetByAnimation(bool aSetByAnimation) { + mShadowTransformSetByAnimation = aSetByAnimation; + } + + // These getters can be used anytime. + float GetShadowOpacity() { return mShadowOpacity; } + const Maybe<ParentLayerIntRect>& GetShadowClipRect() { + return mShadowClipRect; + } + virtual const LayerIntRegion& GetShadowVisibleRegion() { + return mShadowVisibleRegion; + } + const gfx::Matrix4x4& GetShadowBaseTransform() { return mShadowTransform; } + gfx::Matrix4x4 GetShadowTransform(); + bool GetShadowTransformSetByAnimation() { + return mShadowTransformSetByAnimation; + } + bool GetShadowOpacitySetByAnimation() { return mShadowOpacitySetByAnimation; } + + void RecomputeShadowVisibleRegionFromChildren(); + + protected: + HostLayerManager* mCompositorManager; + + gfx::Matrix4x4 mShadowTransform; + LayerIntRegion mShadowVisibleRegion; + Maybe<ParentLayerIntRect> mShadowClipRect; + float mShadowOpacity; + bool mShadowTransformSetByAnimation; + bool mShadowOpacitySetByAnimation; +}; + +/** + * Composite layers are for use with OMTC on the compositor thread only. There + * must be corresponding Client layers on the content thread. For composite + * layers, the layer manager only maintains the layer tree, all rendering is + * done by a Compositor (see Compositor.h). As such, composite layers are + * platform-independent and can be used on any platform for which there is a + * Compositor implementation. + * + * The composite layer tree reflects exactly the basic layer tree. To + * composite to screen, the layer manager walks the layer tree calling render + * methods which in turn call into their CompositableHosts' Composite methods. + * These call Compositor::DrawQuad to do the rendering. + * + * Mostly, layers are updated during the layers transaction. This is done from + * CompositableClient to CompositableHost without interacting with the layer. + * + * A reference to the Compositor is stored in LayerManagerComposite. + */ +class LayerComposite : public HostLayer { + public: + explicit LayerComposite(LayerManagerComposite* aManager); + + virtual ~LayerComposite(); + + void SetLayerManager(HostLayerManager* aManager) override; + + LayerComposite* GetFirstChildComposite() override { return nullptr; } + + /* Do NOT call this from the generic LayerComposite destructor. Only from the + * concrete class destructor + */ + virtual void Destroy(); + virtual void Cleanup() {} + + /** + * Perform a first pass over the layer tree to render all of the intermediate + * surfaces that we can. This allows us to avoid framebuffer switches in the + * middle of our render which is inefficient especially on mobile GPUs. This + * must be called before RenderLayer. + */ + virtual void Prepare(const RenderTargetIntRect& aClipRect) {} + + // TODO: This should also take RenderTargetIntRect like Prepare. + virtual void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) = 0; + + bool SetCompositableHost(CompositableHost*) override { + // We must handle this gracefully, see bug 967824 + NS_WARNING( + "called SetCompositableHost for a layer type not accepting a " + "compositable"); + return false; + } + + virtual void CleanupResources() = 0; + + virtual void DestroyFrontBuffer() {} + + void AddBlendModeEffect(EffectChain& aEffectChain); + + virtual void GenEffectChain(EffectChain& aEffect) {} + + void SetLayerComposited(bool value) { mLayerComposited = value; } + + void SetClearRect(const gfx::IntRect& aRect) { mClearRect = aRect; } + + bool HasLayerBeenComposited() { return mLayerComposited; } + gfx::IntRect GetClearRect() { return mClearRect; } + + // Returns false if the layer is attached to an older compositor. + bool HasStaleCompositor() const; + + /** + * Return the part of the visible region that has been fully rendered. + * While progressive drawing is in progress this region will be + * a subset of the shadow visible region. + */ + virtual nsIntRegion GetFullyRenderedRegion(); + + protected: + LayerManagerComposite* mCompositeManager; + + RefPtr<Compositor> mCompositor; + bool mDestroyed; + bool mLayerComposited; + gfx::IntRect mClearRect; +}; + +class WindowLMC : public profiler_screenshots::Window { + public: + explicit WindowLMC(Compositor* aCompositor) : mCompositor(aCompositor) {} + + already_AddRefed<profiler_screenshots::RenderSource> GetWindowContents( + const gfx::IntSize& aWindowSize) override; + already_AddRefed<profiler_screenshots::DownscaleTarget> CreateDownscaleTarget( + const gfx::IntSize& aSize) override; + already_AddRefed<profiler_screenshots::AsyncReadbackBuffer> + CreateAsyncReadbackBuffer(const gfx::IntSize& aSize) override; + + protected: + Compositor* mCompositor; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LayerManagerComposite_H */ diff --git a/gfx/layers/composite/LayerManagerCompositeUtils.h b/gfx/layers/composite/LayerManagerCompositeUtils.h new file mode 100644 index 0000000000..d349cf99fc --- /dev/null +++ b/gfx/layers/composite/LayerManagerCompositeUtils.h @@ -0,0 +1,160 @@ +/* -*- 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/. */ + +#ifndef GFX_LayerManagerCompositeUtils_H +#define GFX_LayerManagerCompositeUtils_H + +#include <cstddef> // for size_t +#include "Layers.h" // for Layer +#include "Units.h" // for LayerIntRegion +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/BaseRect.h" // for operator- +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Rect.h" // for IntRect, Rect, RoundedOut, IntRectTyped +#include "mozilla/layers/Compositor.h" // for Compositor, INIT_MODE_CLEAR +#include "mozilla/layers/Effects.h" // for EffectChain, EffectRenderTarget +#include "mozilla/layers/LayerManagerComposite.h" // for LayerManagerComposite::AutoAddMaskEffect, LayerComposite, LayerManagerComposite +#include "mozilla/layers/TextureHost.h" // for CompositingRenderTarget + +namespace mozilla { +namespace layers { + +// Render aLayer using aCompositor and apply all mask layers of aLayer: The +// layer's own mask layer (aLayer->GetMaskLayer()), and any ancestor mask +// layers. +// If more than one mask layer needs to be applied, we use intermediate surfaces +// (CompositingRenderTargets) for rendering, applying one mask layer at a time. +// Callers need to provide a callback function aRenderCallback that does the +// actual rendering of the source. It needs to have the following form: +// void (EffectChain& effectChain, const Rect& clipRect) +// aRenderCallback is called exactly once, inside this function, unless aLayer's +// visible region is completely clipped out (in that case, aRenderCallback won't +// be called at all). +// This function calls aLayer->AsHostLayer()->AddBlendModeEffect for the +// final rendering pass. +// +// (This function should really live in LayerManagerComposite.cpp, but we +// need to use templates for passing lambdas until bug 1164522 is resolved.) +template <typename RenderCallbackType> +void RenderWithAllMasks(Layer* aLayer, Compositor* aCompositor, + const gfx::IntRect& aClipRect, + RenderCallbackType aRenderCallback) { + Layer* firstMask = nullptr; + size_t maskLayerCount = 0; + size_t nextAncestorMaskLayer = 0; + + size_t ancestorMaskLayerCount = aLayer->GetAncestorMaskLayerCount(); + if (Layer* ownMask = aLayer->GetMaskLayer()) { + firstMask = ownMask; + maskLayerCount = ancestorMaskLayerCount + 1; + nextAncestorMaskLayer = 0; + } else if (ancestorMaskLayerCount > 0) { + firstMask = aLayer->GetAncestorMaskLayerAt(0); + maskLayerCount = ancestorMaskLayerCount; + nextAncestorMaskLayer = 1; + } else { + // no mask layers at all + } + + if (maskLayerCount <= 1) { + // This is the common case. Render in one pass and return. + EffectChain effectChain(aLayer); + LayerManagerComposite::AutoAddMaskEffect autoMaskEffect(firstMask, + effectChain); + static_cast<LayerComposite*>(aLayer->AsHostLayer()) + ->AddBlendModeEffect(effectChain); + aRenderCallback(effectChain, aClipRect); + return; + } + + // We have multiple mask layers. + // We split our list of mask layers into three parts: + // (1) The first mask + // (2) The list of intermediate masks (every mask except first and last) + // (3) The final mask. + // Part (2) can be empty. + // For parts (1) and (2) we need to allocate intermediate surfaces to render + // into. The final mask gets rendered into the original render target. + + // Calculate the size of the intermediate surfaces. + gfx::Rect visibleRect( + aLayer->GetLocalVisibleRegion().GetBounds().ToUnknownRect()); + gfx::Matrix4x4 transform = aLayer->GetEffectiveTransform(); + // TODO: Use RenderTargetIntRect and TransformBy here + gfx::IntRect surfaceRect = RoundedOut( + transform.TransformAndClipBounds(visibleRect, gfx::Rect(aClipRect))); + if (surfaceRect.IsEmpty()) { + return; + } + + RefPtr<CompositingRenderTarget> originalTarget = + aCompositor->GetCurrentRenderTarget(); + + RefPtr<CompositingRenderTarget> firstTarget = + aCompositor->CreateRenderTarget(surfaceRect, INIT_MODE_CLEAR); + if (!firstTarget) { + return; + } + + // Render the source while applying the first mask. + aCompositor->SetRenderTarget(firstTarget); + { + EffectChain firstEffectChain(aLayer); + LayerManagerComposite::AutoAddMaskEffect firstMaskEffect(firstMask, + firstEffectChain); + aRenderCallback(firstEffectChain, aClipRect - surfaceRect.TopLeft()); + // firstTarget now contains the transformed source with the first mask and + // opacity already applied. + } + + // Apply the intermediate masks. + gfx::IntRect intermediateClip(surfaceRect - surfaceRect.TopLeft()); + RefPtr<CompositingRenderTarget> previousTarget = firstTarget; + for (size_t i = nextAncestorMaskLayer; i < ancestorMaskLayerCount - 1; i++) { + Layer* intermediateMask = aLayer->GetAncestorMaskLayerAt(i); + RefPtr<CompositingRenderTarget> intermediateTarget = + aCompositor->CreateRenderTarget(surfaceRect, INIT_MODE_CLEAR); + if (!intermediateTarget) { + break; + } + aCompositor->SetRenderTarget(intermediateTarget); + EffectChain intermediateEffectChain(aLayer); + LayerManagerComposite::AutoAddMaskEffect intermediateMaskEffect( + intermediateMask, intermediateEffectChain); + if (intermediateMaskEffect.Failed()) { + continue; + } + intermediateEffectChain.mPrimaryEffect = + new EffectRenderTarget(previousTarget); + aCompositor->DrawQuad(gfx::Rect(surfaceRect), intermediateClip, + intermediateEffectChain, 1.0, gfx::Matrix4x4()); + previousTarget = intermediateTarget; + } + + aCompositor->SetRenderTarget(originalTarget); + + // Apply the final mask, rendering into originalTarget. + EffectChain finalEffectChain(aLayer); + finalEffectChain.mPrimaryEffect = new EffectRenderTarget(previousTarget); + Layer* finalMask = aLayer->GetAncestorMaskLayerAt(ancestorMaskLayerCount - 1); + + // The blend mode needs to be applied in this final step, because this is + // where we're blending with the actual background (which is in + // originalTarget). + static_cast<LayerComposite*>(aLayer->AsHostLayer()) + ->AddBlendModeEffect(finalEffectChain); + LayerManagerComposite::AutoAddMaskEffect autoMaskEffect(finalMask, + finalEffectChain); + if (!autoMaskEffect.Failed()) { + aCompositor->DrawQuad(gfx::Rect(surfaceRect), aClipRect, finalEffectChain, + 1.0, gfx::Matrix4x4()); + } +} + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LayerManagerCompositeUtils_H */ diff --git a/gfx/layers/composite/PaintCounter.cpp b/gfx/layers/composite/PaintCounter.cpp new file mode 100644 index 0000000000..fbf1552116 --- /dev/null +++ b/gfx/layers/composite/PaintCounter.cpp @@ -0,0 +1,79 @@ +/* -*- 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 "mozilla/gfx/Point.h" // for IntSize, Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for Color, SurfaceFormat +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc +#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration +#include "mozilla/Sprintf.h" + +#include "mozilla/gfx/HelpersSkia.h" +#include "skia/include/core/SkFont.h" +#include "PaintCounter.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +// Positioned below the chrome UI +IntRect PaintCounter::mRect = IntRect(0, 175, 300, 60); + +PaintCounter::PaintCounter() { + mFormat = SurfaceFormat::B8G8R8A8; + mSurface = Factory::CreateDataSourceSurface(mRect.Size(), mFormat); + mMap.emplace(mSurface, DataSourceSurface::READ_WRITE); + mStride = mMap->GetStride(); + + mCanvas = SkCanvas::MakeRasterDirect(MakeSkiaImageInfo(mRect.Size(), mFormat), + mMap->GetData(), mStride); + mCanvas->clear(SK_ColorWHITE); +} + +PaintCounter::~PaintCounter() { + mSurface = nullptr; + mTextureSource = nullptr; + mTexturedEffect = nullptr; +} + +void PaintCounter::Draw(Compositor* aCompositor, TimeDuration aPaintTime, + TimeDuration aCompositeTime) { + char buffer[48]; + SprintfLiteral(buffer, "P: %.2f C: %.2f", aPaintTime.ToMilliseconds(), + aCompositeTime.ToMilliseconds()); + + SkPaint paint; + paint.setColor(SkColorSetRGB(0, 255, 0)); + paint.setAntiAlias(true); + + SkFont font(SkTypeface::MakeDefault(), 32); + + mCanvas->clear(SK_ColorTRANSPARENT); + mCanvas->drawString(buffer, 10, 30, font, paint); + mCanvas->flush(); + + if (!mTextureSource) { + mTextureSource = aCompositor->CreateDataTextureSource(); + mTexturedEffect = CreateTexturedEffect(mFormat, mTextureSource, + SamplingFilter::POINT, true); + mTexturedEffect->mTextureCoords = Rect(0, 0, 1.0f, 1.0f); + } + + mTextureSource->Update(mSurface); + + EffectChain effectChain; + effectChain.mPrimaryEffect = mTexturedEffect; + + gfx::Matrix4x4 identity; + Rect rect(mRect.X(), mRect.Y(), mRect.Width(), mRect.Height()); + aCompositor->DrawQuad(rect, mRect, effectChain, 1.0, identity); +} + +} // end namespace layers +} // end namespace mozilla diff --git a/gfx/layers/composite/PaintCounter.h b/gfx/layers/composite/PaintCounter.h new file mode 100644 index 0000000000..084330c998 --- /dev/null +++ b/gfx/layers/composite/PaintCounter.h @@ -0,0 +1,53 @@ +/* -*- 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/. */ + +#ifndef mozilla_layers_PaintCounter_h_ +#define mozilla_layers_PaintCounter_h_ + +#include <map> // for std::map +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" // for already_AddRefed, RefCounted +#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration +#include "skia/include/core/SkCanvas.h" + +namespace mozilla { +namespace layers { + +class Compositor; + +using namespace mozilla::gfx; +using namespace mozilla::gl; + +// Keeps track and paints how long a full invalidation paint takes to rasterize +// and composite. +class PaintCounter { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PaintCounter) + + PaintCounter(); + void Draw(Compositor* aCompositor, TimeDuration aPaintTime, + TimeDuration aCompositeTime); + static IntRect GetPaintRect() { return PaintCounter::mRect; } + + private: + virtual ~PaintCounter(); + + SurfaceFormat mFormat; + std::unique_ptr<SkCanvas> mCanvas; + IntSize mSize; + int mStride; + + RefPtr<DataSourceSurface> mSurface; + RefPtr<DataTextureSource> mTextureSource; + RefPtr<TexturedEffect> mTexturedEffect; + Maybe<DataSourceSurface::ScopedMap> mMap; + static IntRect mRect; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_opengl_PaintCounter_h_ diff --git a/gfx/layers/composite/PaintedLayerComposite.cpp b/gfx/layers/composite/PaintedLayerComposite.cpp new file mode 100644 index 0000000000..fe8028d12f --- /dev/null +++ b/gfx/layers/composite/PaintedLayerComposite.cpp @@ -0,0 +1,169 @@ +/* -*- 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 "PaintedLayerComposite.h" +#include "CompositableHost.h" // for TiledLayerProperties, etc +#include "FrameMetrics.h" // for FrameMetrics +#include "Units.h" // for CSSRect, LayerPixel, etc +#include "gfxEnv.h" // for gfxEnv +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Polygon.h" // for Polygon +#include "mozilla/gfx/Rect.h" // for RoundedToInt, Rect +#include "mozilla/gfx/Types.h" // for SamplingFilter::LINEAR +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/ContentHost.h" // for ContentHost +#include "mozilla/layers/Effects.h" // for EffectChain +#include "mozilla/layers/LayerManagerCompositeUtils.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "nsAString.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsMathUtils.h" // for NS_lround +#include "nsString.h" // for nsAutoCString +#include "TextRenderer.h" +#include "GeckoProfiler.h" + +namespace mozilla { +namespace layers { + +PaintedLayerComposite::PaintedLayerComposite(LayerManagerComposite* aManager) + : PaintedLayer(aManager, nullptr), + LayerComposite(aManager), + mBuffer(nullptr) { + MOZ_COUNT_CTOR(PaintedLayerComposite); + mImplData = static_cast<LayerComposite*>(this); +} + +PaintedLayerComposite::~PaintedLayerComposite() { + MOZ_COUNT_DTOR(PaintedLayerComposite); + CleanupResources(); +} + +bool PaintedLayerComposite::SetCompositableHost(CompositableHost* aHost) { + switch (aHost->GetType()) { + case CompositableType::CONTENT_TILED: + case CompositableType::CONTENT_SINGLE: + case CompositableType::CONTENT_DOUBLE: { + ContentHost* newBuffer = static_cast<ContentHost*>(aHost); + if (mBuffer && newBuffer != mBuffer) { + mBuffer->Detach(this); + } + mBuffer = newBuffer; + return true; + } + default: + return false; + } +} + +void PaintedLayerComposite::Disconnect() { Destroy(); } + +void PaintedLayerComposite::Destroy() { + if (!mDestroyed) { + CleanupResources(); + mDestroyed = true; + } +} + +Layer* PaintedLayerComposite::GetLayer() { return this; } + +void PaintedLayerComposite::SetLayerManager(HostLayerManager* aManager) { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + if (mBuffer && mCompositor) { + mBuffer->SetTextureSourceProvider(mCompositor); + } +} + +void PaintedLayerComposite::RenderLayer(const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) { + if (!mBuffer || !mBuffer->IsAttached()) { + return; + } + AUTO_PROFILER_LABEL("PaintedLayerComposite::RenderLayer", GRAPHICS); + + Compositor* compositor = mCompositeManager->GetCompositor(); + + MOZ_ASSERT(mBuffer->GetTextureSourceProvider() == compositor && + mBuffer->GetLayer() == this, + "buffer is corrupted"); + + const nsIntRegion visibleRegion = GetLocalVisibleRegion().ToUnknownRegion(); + +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpCompositorTextures()) { + RefPtr<gfx::DataSourceSurface> surf = mBuffer->GetAsSurface(); + if (surf) { + WriteSnapshotToDumpFile(this, surf); + } + } +#endif + + RenderWithAllMasks( + this, compositor, aClipRect, + [&](EffectChain& effectChain, const gfx::IntRect& clipRect) { + mBuffer->SetPaintWillResample(MayResample()); + + mBuffer->Composite(compositor, this, effectChain, GetEffectiveOpacity(), + GetEffectiveTransform(), GetSamplingFilter(), + clipRect, &visibleRegion, aGeometry); + }); + + mBuffer->BumpFlashCounter(); + + compositor->MakeCurrent(); +} + +CompositableHost* PaintedLayerComposite::GetCompositableHost() { + if (mBuffer && mBuffer->IsAttached()) { + return mBuffer.get(); + } + + return nullptr; +} + +void PaintedLayerComposite::CleanupResources() { + if (mBuffer) { + mBuffer->Detach(this); + } + mBuffer = nullptr; +} + +bool PaintedLayerComposite::IsOpaque() { + if (!mBuffer || !mBuffer->IsAttached()) { + return false; + } + return PaintedLayer::IsOpaque(); +} + +void PaintedLayerComposite::GenEffectChain(EffectChain& aEffect) { + aEffect.mLayerRef = this; + aEffect.mPrimaryEffect = mBuffer->GenEffect(GetSamplingFilter()); +} + +void PaintedLayerComposite::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + PaintedLayer::PrintInfo(aStream, aPrefix); + if (mBuffer && mBuffer->IsAttached()) { + aStream << "\n"; + nsAutoCString pfx(aPrefix); + pfx += " "; + mBuffer->PrintInfo(aStream, pfx.get()); + } +} + +const gfx::TiledIntRegion& PaintedLayerComposite::GetInvalidRegion() { + if (mBuffer) { + nsIntRegion region = mInvalidRegion.GetRegion(); + mBuffer->AddAnimationInvalidation(region); + } + return mInvalidRegion; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/PaintedLayerComposite.h b/gfx/layers/composite/PaintedLayerComposite.h new file mode 100644 index 0000000000..c2c22eb433 --- /dev/null +++ b/gfx/layers/composite/PaintedLayerComposite.h @@ -0,0 +1,86 @@ +/* -*- 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/. */ + +#ifndef GFX_PaintedLayerComposite_H +#define GFX_PaintedLayerComposite_H + +#include "Layers.h" // for Layer (ptr only), etc +#include "mozilla/gfx/Rect.h" +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/LayerManagerComposite.h" // for LayerComposite, etc +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "nsRegion.h" // for nsIntRegion +#include "nscore.h" // for nsACString + +namespace mozilla { +namespace layers { + +/** + * PaintedLayers use ContentHosts for their compsositable host. + * By using different ContentHosts, PaintedLayerComposite support tiled and + * non-tiled PaintedLayers and single or double buffering. + */ + +class CompositableHost; +class ContentHost; + +class PaintedLayerComposite : public PaintedLayer, public LayerComposite { + public: + explicit PaintedLayerComposite(LayerManagerComposite* aManager); + + protected: + virtual ~PaintedLayerComposite(); + + public: + void Disconnect() override; + + CompositableHost* GetCompositableHost() override; + + void Destroy() override; + + Layer* GetLayer() override; + + void SetLayerManager(HostLayerManager* aManager) override; + + void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe<gfx::Polygon>& aGeometry) override; + + void CleanupResources() override; + + bool IsOpaque() override; + + void GenEffectChain(EffectChain& aEffect) override; + + bool SetCompositableHost(CompositableHost* aHost) override; + + HostLayer* AsHostLayer() override { return this; } + + void InvalidateRegion(const nsIntRegion& aRegion) override { + MOZ_CRASH("PaintedLayerComposites can't fill invalidated regions"); + } + + const gfx::TiledIntRegion& GetInvalidRegion() override; + + MOZ_LAYER_DECL_NAME("PaintedLayerComposite", TYPE_PAINTED) + + protected: + virtual void PrintInfo(std::stringstream& aStream, + const char* aPrefix) override; + + private: + gfx::SamplingFilter GetSamplingFilter() { + return gfx::SamplingFilter::LINEAR; + } + + private: + RefPtr<ContentHost> mBuffer; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_PaintedLayerComposite_H */ diff --git a/gfx/layers/composite/TextRenderer.cpp b/gfx/layers/composite/TextRenderer.cpp new file mode 100644 index 0000000000..3bd1a7d9b3 --- /dev/null +++ b/gfx/layers/composite/TextRenderer.cpp @@ -0,0 +1,245 @@ +/* -*- 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 "TextRenderer.h" +#include "FontData.h" +#include "ConsolasFontData.h" +#include "png.h" +#include "mozilla/Base64.h" +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/layers/Effects.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +const Float sBackgroundOpacity = 0.8f; +const SurfaceFormat sTextureFormat = SurfaceFormat::B8G8R8A8; + +static void PNGAPI info_callback(png_structp png_ptr, png_infop info_ptr) { + png_read_update_info(png_ptr, info_ptr); +} + +static void PNGAPI row_callback(png_structp png_ptr, png_bytep new_row, + png_uint_32 row_num, int pass) { + MOZ_ASSERT(sTextureFormat == SurfaceFormat::B8G8R8A8); + + TextRenderer::FontCache* cache = + static_cast<TextRenderer::FontCache*>(png_get_progressive_ptr(png_ptr)); + + uint32_t* dst = + (uint32_t*)(cache->mMap.mData + cache->mMap.mStride * row_num); + + for (uint32_t x = 0; x < cache->mInfo->mTextureWidth; x++) { + // We blend to a transparent white background, this will make text readable + // even if it's on a dark background. Without hurting our ability to + // interact with the content behind the text. + Float alphaValue = Float(0xFF - new_row[x]) / 255.0f; + Float baseValue = sBackgroundOpacity * (1.0f - alphaValue); + // FIXME(aosmond): PNGs may have color profiles. Should we be checking for + // that and performing color management? + sRGBColor pixelColor(baseValue, baseValue, baseValue, + baseValue + alphaValue); + dst[x] = pixelColor.ToABGR(); + } +} + +TextRenderer::~TextRenderer() = default; + +TextRenderer::FontCache::~FontCache() { mGlyphBitmaps->Unmap(); } + +void TextRenderer::RenderText(Compositor* aCompositor, const std::string& aText, + const IntPoint& aOrigin, + const Matrix4x4& aTransform, uint32_t aTextSize, + uint32_t aTargetPixelWidth, FontType aFontType) { + const FontBitmapInfo* info = GetFontInfo(aFontType); + + // For now we only have a bitmap font with a 24px cell size, so we just + // scale it up if the user wants larger text. + Float scaleFactor = Float(aTextSize) / Float(info->mCellHeight); + aTargetPixelWidth /= scaleFactor; + + RefPtr<TextureSource> src = + RenderText(aCompositor, aText, aTargetPixelWidth, aFontType); + if (!src) { + return; + } + + RefPtr<EffectRGB> effect = new EffectRGB(src, true, SamplingFilter::LINEAR); + EffectChain chain; + chain.mPrimaryEffect = effect; + + Matrix4x4 transform = aTransform; + transform.PreScale(scaleFactor, scaleFactor, 1.0f); + + IntRect drawRect(aOrigin, src->GetSize()); + IntRect clip(-10000, -10000, 20000, 20000); + aCompositor->DrawQuad(Rect(drawRect), clip, chain, 1.0f, transform); +} + +IntSize TextRenderer::ComputeSurfaceSize(const std::string& aText, + uint32_t aTargetPixelWidth, + FontType aFontType) { + if (!EnsureInitialized(aFontType)) { + return IntSize(); + } + + FontCache* cache = mFonts[aFontType].get(); + const FontBitmapInfo* info = cache->mInfo; + + uint32_t numLines = 1; + uint32_t maxWidth = 0; + uint32_t lineWidth = 0; + // Calculate the size of the surface needed to draw all the glyphs. + for (uint32_t i = 0; i < aText.length(); i++) { + // Insert a line break if we go past the TargetPixelWidth. + // XXX - this has the downside of overrunning the intended width, causing + // things at the edge of a window to be cut off. + if (aText[i] == '\n' || + (aText[i] == ' ' && lineWidth > aTargetPixelWidth)) { + numLines++; + lineWidth = 0; + continue; + } + + lineWidth += info->GetGlyphWidth(aText[i]); + maxWidth = std::max(lineWidth, maxWidth); + } + + return IntSize(maxWidth, numLines * info->mCellHeight); +} + +RefPtr<TextureSource> TextRenderer::RenderText(TextureSourceProvider* aProvider, + const std::string& aText, + uint32_t aTargetPixelWidth, + FontType aFontType) { + if (!EnsureInitialized(aFontType)) { + return nullptr; + } + + IntSize size = ComputeSurfaceSize(aText, aTargetPixelWidth, aFontType); + + // Create a DrawTarget to draw our glyphs to. + RefPtr<DrawTarget> dt = + Factory::CreateDrawTarget(BackendType::SKIA, size, sTextureFormat); + + RenderTextToDrawTarget(dt, aText, aTargetPixelWidth, aFontType); + RefPtr<SourceSurface> surf = dt->Snapshot(); + RefPtr<DataSourceSurface> dataSurf = surf->GetDataSurface(); + RefPtr<DataTextureSource> src = aProvider->CreateDataTextureSource(); + + if (!src->Update(dataSurf)) { + // Upload failed. + return nullptr; + } + + return src; +} + +void TextRenderer::RenderTextToDrawTarget(DrawTarget* aDrawTarget, + const std::string& aText, + uint32_t aTargetPixelWidth, + FontType aFontType) { + if (!EnsureInitialized(aFontType)) { + return; + } + + // Initialize the DrawTarget to transparent white. + // FIXME(aosmond): Should the background be color managed if we color manage + // the glyphs or is this part of a mask? + IntSize size = aDrawTarget->GetSize(); + aDrawTarget->FillRect( + Rect(0, 0, size.width, size.height), + ColorPattern(DeviceColor(1.0, 1.0, 1.0, sBackgroundOpacity)), + DrawOptions(1.0, CompositionOp::OP_SOURCE)); + + IntPoint currentPos; + + FontCache* cache = mFonts[aFontType].get(); + const FontBitmapInfo* info = cache->mInfo; + + const unsigned int kGlyphsPerLine = info->mTextureWidth / info->mCellWidth; + + // Copy our glyphs onto the DrawTarget. + for (uint32_t i = 0; i < aText.length(); i++) { + if (aText[i] == '\n' || + (aText[i] == ' ' && currentPos.x > int32_t(aTargetPixelWidth))) { + currentPos.y += info->mCellHeight; + currentPos.x = 0; + continue; + } + + uint32_t index = aText[i] - info->mFirstChar; + uint32_t cellIndexY = index / kGlyphsPerLine; + uint32_t cellIndexX = index - (cellIndexY * kGlyphsPerLine); + uint32_t glyphWidth = info->GetGlyphWidth(aText[i]); + IntRect srcRect(cellIndexX * info->mCellWidth, + cellIndexY * info->mCellHeight, glyphWidth, + info->mCellHeight); + + aDrawTarget->CopySurface(cache->mGlyphBitmaps, srcRect, currentPos); + + currentPos.x += glyphWidth; + } +} + +/* static */ const FontBitmapInfo* TextRenderer::GetFontInfo(FontType aType) { + switch (aType) { + case FontType::Default: + return &sDefaultCompositorFont; + case FontType::FixedWidth: + return &sFixedWidthCompositorFont; + default: + MOZ_ASSERT_UNREACHABLE("unknown font type"); + return nullptr; + } +} + +bool TextRenderer::EnsureInitialized(FontType aType) { + if (mFonts[aType]) { + return true; + } + + const FontBitmapInfo* info = GetFontInfo(aType); + + IntSize size(info->mTextureWidth, info->mTextureHeight); + RefPtr<DataSourceSurface> surface = + Factory::CreateDataSourceSurface(size, sTextureFormat); + if (NS_WARN_IF(!surface)) { + return false; + } + + DataSourceSurface::MappedSurface map; + if (NS_WARN_IF(!surface->Map(DataSourceSurface::MapType::READ_WRITE, &map))) { + return false; + } + + UniquePtr<FontCache> cache = MakeUnique<FontCache>(); + cache->mGlyphBitmaps = surface; + cache->mMap = map; + cache->mInfo = info; + + png_structp png_ptr = NULL; + png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + + png_set_progressive_read_fn(png_ptr, cache.get(), info_callback, row_callback, + nullptr); + png_infop info_ptr = NULL; + info_ptr = png_create_info_struct(png_ptr); + + png_process_data(png_ptr, info_ptr, (uint8_t*)info->mPNG, info->mPNGLength); + + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + + mFonts[aType] = std::move(cache); + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/TextRenderer.h b/gfx/layers/composite/TextRenderer.h new file mode 100644 index 0000000000..e88fc1d8f5 --- /dev/null +++ b/gfx/layers/composite/TextRenderer.h @@ -0,0 +1,95 @@ +/* -*- 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/. */ + +#ifndef GFX_TextRenderer_H +#define GFX_TextRenderer_H + +#include "mozilla/EnumeratedArray.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/UniquePtr.h" +#include "nsISupportsImpl.h" +#include <string> + +namespace mozilla { +namespace layers { + +class Compositor; +class TextureSource; +class TextureSourceProvider; +struct FontBitmapInfo; + +class TextRenderer final { + ~TextRenderer(); + + public: + NS_INLINE_DECL_REFCOUNTING(TextRenderer) + + enum class FontType { Default, FixedWidth, NumTypes }; + + TextRenderer() = default; + + RefPtr<TextureSource> RenderText(TextureSourceProvider* aProvider, + const std::string& aText, + uint32_t aTargetPixelWidth, + FontType aFontType); + + void RenderText(Compositor* aCompositor, const std::string& aText, + const gfx::IntPoint& aOrigin, + const gfx::Matrix4x4& aTransform, uint32_t aTextSize, + uint32_t aTargetPixelWidth, + FontType aFontType = FontType::Default); + + gfx::IntSize ComputeSurfaceSize(const std::string& aText, + uint32_t aTargetPixelWidth, + FontType aFontType = FontType::Default); + + void RenderTextToDrawTarget(gfx::DrawTarget* aDrawTarget, + const std::string& aText, + uint32_t aTargetPixelWidth, + FontType aFontType = FontType::Default); + + struct FontCache { + ~FontCache(); + RefPtr<gfx::DataSourceSurface> mGlyphBitmaps; + gfx::DataSourceSurface::MappedSurface mMap; + const FontBitmapInfo* mInfo; + }; + + protected: + // Note that this may still fail to set mGlyphBitmaps to a valid value + // if the underlying CreateDataSourceSurface fails for some reason. + bool EnsureInitialized(FontType aType); + + static const FontBitmapInfo* GetFontInfo(FontType aType); + + private: + EnumeratedArray<FontType, FontType::NumTypes, UniquePtr<FontCache>> mFonts; +}; + +struct FontBitmapInfo { + Maybe<unsigned int> mGlyphWidth; + Maybe<const unsigned short*> mGlyphWidths; + unsigned int mTextureWidth; + unsigned int mTextureHeight; + unsigned int mCellWidth; + unsigned int mCellHeight; + unsigned int mFirstChar; + const unsigned char* mPNG; + size_t mPNGLength; + + unsigned int GetGlyphWidth(char aGlyph) const { + if (mGlyphWidth) { + return mGlyphWidth.value(); + } + MOZ_ASSERT(unsigned(aGlyph) >= mFirstChar); + return mGlyphWidths.value()[unsigned(aGlyph) - mFirstChar]; + } +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/TextureHost.cpp b/gfx/layers/composite/TextureHost.cpp new file mode 100644 index 0000000000..92ced1ee89 --- /dev/null +++ b/gfx/layers/composite/TextureHost.cpp @@ -0,0 +1,1371 @@ +/* -*- 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 "TextureHost.h" + +#include "CompositableHost.h" // for CompositableHost +#include "LayerScope.h" +#include "mozilla/gfx/2D.h" // for DataSourceSurface, Factory +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ipc/Shmem.h" // for Shmem +#include "mozilla/layers/AsyncImagePipelineManager.h" +#include "mozilla/layers/BufferTexture.h" +#include "mozilla/layers/CompositableTransactionParent.h" // for CompositableParentManager +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/ISurfaceAllocator.h" // for ISurfaceAllocator +#include "mozilla/layers/ImageBridgeParent.h" // for ImageBridgeParent +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor, etc +#include "mozilla/layers/TextureHostBasic.h" +#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/TextureClient.h" +#ifdef XP_DARWIN +# include "mozilla/layers/TextureSync.h" +#endif +#include "mozilla/layers/GPUVideoTextureHost.h" +#include "mozilla/layers/WebRenderTextureHost.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/webrender/RenderBufferTextureHost.h" +#include "mozilla/webrender/RenderBufferTextureHostSWGL.h" +#include "mozilla/webrender/RenderExternalTextureHost.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "nsAString.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsPrintfCString.h" // for nsPrintfCString +#include "mozilla/layers/PTextureParent.h" +#include "mozilla/Unused.h" +#include <limits> +#include "../opengl/CompositorOGL.h" + +#include "gfxUtils.h" +#include "IPDLActor.h" + +#ifdef MOZ_ENABLE_D3D10_LAYER +# include "../d3d11/CompositorD3D11.h" +#endif + +#ifdef MOZ_X11 +# include "mozilla/layers/X11TextureHost.h" +#endif + +#ifdef XP_MACOSX +# include "../opengl/MacIOSurfaceTextureHostOGL.h" +#endif + +#ifdef XP_WIN +# include "mozilla/layers/TextureD3D11.h" +# include "mozilla/layers/TextureDIB.h" +#endif + +#if 0 +# define RECYCLE_LOG(...) printf_stderr(__VA_ARGS__) +#else +# define RECYCLE_LOG(...) \ + do { \ + } while (0) +#endif + +namespace mozilla { +namespace layers { + +/** + * TextureParent is the host-side IPDL glue between TextureClient and + * TextureHost. It is an IPDL actor just like LayerParent, CompositableParent, + * etc. + */ +class TextureParent : public ParentActor<PTextureParent> { + public: + TextureParent(HostIPCAllocator* aAllocator, uint64_t aSerial, + const wr::MaybeExternalImageId& aExternalImageId); + + virtual ~TextureParent(); + + bool Init(const SurfaceDescriptor& aSharedData, + const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, const TextureFlags& aFlags); + + void NotifyNotUsed(uint64_t aTransactionId); + + mozilla::ipc::IPCResult RecvRecycleTexture( + const TextureFlags& aTextureFlags) final; + + TextureHost* GetTextureHost() { return mTextureHost; } + + void Destroy() override; + + uint64_t GetSerial() const { return mSerial; } + + HostIPCAllocator* mSurfaceAllocator; + RefPtr<TextureHost> mTextureHost; + // mSerial is unique in TextureClient's process. + const uint64_t mSerial; + wr::MaybeExternalImageId mExternalImageId; +}; + +static bool WrapWithWebRenderTextureHost(ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, + TextureFlags aFlags) { + if ((aFlags & TextureFlags::SNAPSHOT) || + (aBackend != LayersBackend::LAYERS_WR) || + (!aDeallocator->UsesImageBridge() && + !aDeallocator->AsCompositorBridgeParentBase())) { + return false; + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +PTextureParent* TextureHost::CreateIPDLActor( + HostIPCAllocator* aAllocator, const SurfaceDescriptor& aSharedData, + const ReadLockDescriptor& aReadLock, LayersBackend aLayersBackend, + TextureFlags aFlags, uint64_t aSerial, + const wr::MaybeExternalImageId& aExternalImageId) { + TextureParent* actor = + new TextureParent(aAllocator, aSerial, aExternalImageId); + if (!actor->Init(aSharedData, aReadLock, aLayersBackend, aFlags)) { + actor->ActorDestroy(ipc::IProtocol::ActorDestroyReason::FailedConstructor); + delete actor; + return nullptr; + } + return actor; +} + +// static +bool TextureHost::DestroyIPDLActor(PTextureParent* actor) { + delete actor; + return true; +} + +// static +bool TextureHost::SendDeleteIPDLActor(PTextureParent* actor) { + return PTextureParent::Send__delete__(actor); +} + +// static +TextureHost* TextureHost::AsTextureHost(PTextureParent* actor) { + if (!actor) { + return nullptr; + } + return static_cast<TextureParent*>(actor)->mTextureHost; +} + +// static +uint64_t TextureHost::GetTextureSerial(PTextureParent* actor) { + if (!actor) { + return UINT64_MAX; + } + return static_cast<TextureParent*>(actor)->mSerial; +} + +PTextureParent* TextureHost::GetIPDLActor() { return mActor; } + +void TextureHost::SetLastFwdTransactionId(uint64_t aTransactionId) { + MOZ_ASSERT(mFwdTransactionId <= aTransactionId); + mFwdTransactionId = aTransactionId; +} + +already_AddRefed<TextureHost> CreateDummyBufferTextureHost( + mozilla::layers::LayersBackend aBackend, + mozilla::layers::TextureFlags aFlags) { + // Ensure that the host will delete the memory. + aFlags &= ~TextureFlags::DEALLOCATE_CLIENT; + UniquePtr<TextureData> textureData(BufferTextureData::Create( + gfx::IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8, gfx::BackendType::SKIA, + aBackend, aFlags, TextureAllocationFlags::ALLOC_DEFAULT, nullptr)); + SurfaceDescriptor surfDesc; + textureData->Serialize(surfDesc); + const SurfaceDescriptorBuffer& bufferDesc = + surfDesc.get_SurfaceDescriptorBuffer(); + const MemoryOrShmem& data = bufferDesc.data(); + RefPtr<TextureHost> host = + new MemoryTextureHost(reinterpret_cast<uint8_t*>(data.get_uintptr_t()), + bufferDesc.desc(), aFlags); + return host.forget(); +} + +already_AddRefed<TextureHost> TextureHost::Create( + const SurfaceDescriptor& aDesc, const ReadLockDescriptor& aReadLock, + ISurfaceAllocator* aDeallocator, LayersBackend aBackend, + TextureFlags aFlags, wr::MaybeExternalImageId& aExternalImageId) { + RefPtr<TextureHost> result; + + switch (aDesc.type()) { + case SurfaceDescriptor::TSurfaceDescriptorBuffer: + case SurfaceDescriptor::TSurfaceDescriptorDIB: + case SurfaceDescriptor::TSurfaceDescriptorFileMapping: + case SurfaceDescriptor::TSurfaceDescriptorGPUVideo: + result = CreateBackendIndependentTextureHost(aDesc, aDeallocator, + aBackend, aFlags); + break; + + case SurfaceDescriptor::TEGLImageDescriptor: + case SurfaceDescriptor::TSurfaceTextureDescriptor: + case SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer: + case SurfaceDescriptor::TSurfaceDescriptorSharedGLTexture: + case SurfaceDescriptor::TSurfaceDescriptorDMABuf: + result = CreateTextureHostOGL(aDesc, aDeallocator, aBackend, aFlags); + break; + + case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: + if (aBackend == LayersBackend::LAYERS_OPENGL || + aBackend == LayersBackend::LAYERS_WR) { + result = CreateTextureHostOGL(aDesc, aDeallocator, aBackend, aFlags); + break; + } else { + result = CreateTextureHostBasic(aDesc, aDeallocator, aBackend, aFlags); + break; + } + +#ifdef MOZ_X11 + case SurfaceDescriptor::TSurfaceDescriptorX11: { + if (!aDeallocator->IsSameProcess()) { + NS_ERROR( + "A client process is trying to peek at our address space using a " + "X11Texture!"); + return nullptr; + } + + const SurfaceDescriptorX11& desc = aDesc.get_SurfaceDescriptorX11(); + result = MakeAndAddRef<X11TextureHost>(aFlags, desc); + break; + } +#endif + +#ifdef XP_WIN + case SurfaceDescriptor::TSurfaceDescriptorD3D10: + case SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr: + result = CreateTextureHostD3D11(aDesc, aDeallocator, aBackend, aFlags); + break; +#endif + case SurfaceDescriptor::TSurfaceDescriptorRecorded: { + const SurfaceDescriptorRecorded& desc = + aDesc.get_SurfaceDescriptorRecorded(); + UniquePtr<SurfaceDescriptor> realDesc = + aDeallocator->AsCompositorBridgeParentBase() + ->LookupSurfaceDescriptorForClientTexture(desc.textureId()); + if (!realDesc) { + gfxCriticalNote << "Failed to get descriptor for recorded texture."; + // Create a dummy to prevent any crashes due to missing IPDL actors. + result = CreateDummyBufferTextureHost(aBackend, aFlags); + break; + } + + result = TextureHost::Create(*realDesc, aReadLock, aDeallocator, aBackend, + aFlags, aExternalImageId); + return result.forget(); + } + default: + MOZ_CRASH("GFX: Unsupported Surface type host"); + } + + if (!result) { + gfxCriticalNote << "TextureHost creation failure type=" << aDesc.type(); + } + + if (result && WrapWithWebRenderTextureHost(aDeallocator, aBackend, aFlags)) { + MOZ_ASSERT(aExternalImageId.isSome()); + result = + new WebRenderTextureHost(aDesc, aFlags, result, aExternalImageId.ref()); + } + + if (result) { + result->DeserializeReadLock(aReadLock, aDeallocator); + } + + return result.forget(); +} + +already_AddRefed<TextureHost> CreateBackendIndependentTextureHost( + const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, TextureFlags aFlags) { + RefPtr<TextureHost> result; + switch (aDesc.type()) { + case SurfaceDescriptor::TSurfaceDescriptorBuffer: { + const SurfaceDescriptorBuffer& bufferDesc = + aDesc.get_SurfaceDescriptorBuffer(); + const MemoryOrShmem& data = bufferDesc.data(); + switch (data.type()) { + case MemoryOrShmem::TShmem: { + const ipc::Shmem& shmem = data.get_Shmem(); + const BufferDescriptor& desc = bufferDesc.desc(); + if (!shmem.IsReadable()) { + // We failed to map the shmem so we can't verify its size. This + // should not be a fatal error, so just create the texture with + // nothing backing it. + result = new ShmemTextureHost(shmem, desc, aDeallocator, aFlags); + break; + } + + size_t bufSize = shmem.Size<char>(); + size_t reqSize = SIZE_MAX; + switch (desc.type()) { + case BufferDescriptor::TYCbCrDescriptor: { + const YCbCrDescriptor& ycbcr = desc.get_YCbCrDescriptor(); + reqSize = ImageDataSerializer::ComputeYCbCrBufferSize( + ycbcr.ySize(), ycbcr.yStride(), ycbcr.cbCrSize(), + ycbcr.cbCrStride(), ycbcr.yOffset(), ycbcr.cbOffset(), + ycbcr.crOffset()); + break; + } + case BufferDescriptor::TRGBDescriptor: { + const RGBDescriptor& rgb = desc.get_RGBDescriptor(); + reqSize = ImageDataSerializer::ComputeRGBBufferSize(rgb.size(), + rgb.format()); + break; + } + default: + gfxCriticalError() + << "Bad buffer host descriptor " << (int)desc.type(); + MOZ_CRASH("GFX: Bad descriptor"); + } + + if (reqSize == 0 || bufSize < reqSize) { + NS_ERROR( + "A client process gave a shmem too small to fit for its " + "descriptor!"); + return nullptr; + } + + result = new ShmemTextureHost(shmem, desc, aDeallocator, aFlags); + break; + } + case MemoryOrShmem::Tuintptr_t: { + if (!aDeallocator->IsSameProcess()) { + NS_ERROR( + "A client process is trying to peek at our address space using " + "a MemoryTexture!"); + return nullptr; + } + + result = new MemoryTextureHost( + reinterpret_cast<uint8_t*>(data.get_uintptr_t()), + bufferDesc.desc(), aFlags); + break; + } + default: + gfxCriticalError() + << "Failed texture host for backend " << (int)data.type(); + MOZ_CRASH("GFX: No texture host for backend"); + } + break; + } + case SurfaceDescriptor::TSurfaceDescriptorGPUVideo: { + if (aDesc.get_SurfaceDescriptorGPUVideo().type() == + SurfaceDescriptorGPUVideo::TSurfaceDescriptorPlugin) { + MOZ_ASSERT(aDeallocator && aDeallocator->UsesImageBridge()); + auto ibpBase = static_cast<ImageBridgeParent*>(aDeallocator); + result = + ibpBase->LookupTextureHost(aDesc.get_SurfaceDescriptorGPUVideo()); + if (!result) { + return nullptr; + } + MOZ_ASSERT(aFlags == result->GetFlags()); + break; + } + + MOZ_ASSERT(aDesc.get_SurfaceDescriptorGPUVideo().type() == + SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder); + result = GPUVideoTextureHost::CreateFromDescriptor( + aFlags, aDesc.get_SurfaceDescriptorGPUVideo()); + break; + } +#ifdef XP_WIN + case SurfaceDescriptor::TSurfaceDescriptorDIB: { + if (!aDeallocator->IsSameProcess()) { + NS_ERROR( + "A client process is trying to peek at our address space using a " + "DIBTexture!"); + return nullptr; + } + + result = new DIBTextureHost(aFlags, aDesc); + break; + } + case SurfaceDescriptor::TSurfaceDescriptorFileMapping: { + result = new TextureHostFileMapping(aFlags, aDesc); + break; + } +#endif + default: { + NS_WARNING("No backend independent TextureHost for this descriptor type"); + } + } + return result.forget(); +} + +TextureHost::TextureHost(TextureFlags aFlags) + : AtomicRefCountedWithFinalize("TextureHost"), + mActor(nullptr), + mFlags(aFlags), + mCompositableCount(0), + mFwdTransactionId(0), + mReadLocked(false) {} + +TextureHost::~TextureHost() { + if (mReadLocked) { + // If we still have a ReadLock, unlock it. At this point we don't care about + // the texture client being written into on the other side since it should + // be destroyed by now. But we will hit assertions if we don't ReadUnlock + // before destroying the lock itself. + ReadUnlock(); + MaybeNotifyUnlocked(); + } +} + +void TextureHost::Finalize() { + MaybeDestroyRenderTexture(); + + if (!(GetFlags() & TextureFlags::DEALLOCATE_CLIENT)) { + DeallocateSharedData(); + DeallocateDeviceData(); + } +} + +void TextureHost::UnbindTextureSource() { + if (mReadLocked) { + // This TextureHost is not used anymore. Since most compositor backends are + // working asynchronously under the hood a compositor could still be using + // this texture, so it is generally best to wait until the end of the next + // composition before calling ReadUnlock. We ask the compositor to take care + // of that for us. + if (mProvider) { + mProvider->UnlockAfterComposition(this); + } else { + // GetCompositor returned null which means no compositor can be using this + // texture. We can ReadUnlock right away. + ReadUnlock(); + MaybeNotifyUnlocked(); + } + } +} + +void TextureHost::RecycleTexture(TextureFlags aFlags) { + MOZ_ASSERT(GetFlags() & TextureFlags::RECYCLE); + MOZ_ASSERT(aFlags & TextureFlags::RECYCLE); + mFlags = aFlags; +} + +void TextureHost::NotifyNotUsed() { + if (!mActor) { + return; + } + + // Do not need to call NotifyNotUsed() if TextureHost does not have + // TextureFlags::RECYCLE flag nor TextureFlags::WAIT_HOST_USAGE_END flag. + if (!(GetFlags() & TextureFlags::RECYCLE) && + !(GetFlags() & TextureFlags::WAIT_HOST_USAGE_END)) { + return; + } + + // The following cases do not need to defer NotifyNotUsed until next + // Composite. + // - TextureHost does not have Compositor. + // - Compositor is BasicCompositor. + // - TextureHost has intermediate buffer. + // end of buffer usage. + if (!mProvider || HasIntermediateBuffer() || + !mProvider->NotifyNotUsedAfterComposition(this)) { + static_cast<TextureParent*>(mActor)->NotifyNotUsed(mFwdTransactionId); + return; + } +} + +void TextureHost::CallNotifyNotUsed() { + if (!mActor) { + return; + } + static_cast<TextureParent*>(mActor)->NotifyNotUsed(mFwdTransactionId); +} + +void TextureHost::MaybeDestroyRenderTexture() { + if (mExternalImageId.isNothing()) { + // RenderTextureHost was not created + return; + } + // When TextureHost created RenderTextureHost, delete it here. + TextureHost::DestroyRenderTexture(mExternalImageId.ref()); +} + +void TextureHost::DestroyRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + wr::RenderThread::Get()->UnregisterExternalImage( + wr::AsUint64(aExternalImageId)); +} + +void TextureHost::EnsureRenderTexture( + const wr::MaybeExternalImageId& aExternalImageId) { + if (aExternalImageId.isNothing()) { + // TextureHost is wrapped by GPUVideoTextureHost. + if (mExternalImageId.isSome()) { + // RenderTextureHost was already created. + return; + } + mExternalImageId = + Some(AsyncImagePipelineManager::GetNextExternalImageId()); + } else { + // TextureHost is wrapped by WebRenderTextureHost. + if (aExternalImageId == mExternalImageId) { + // The texture has already been created. + return; + } + MOZ_ASSERT(mExternalImageId.isNothing()); + mExternalImageId = aExternalImageId; + } + CreateRenderTexture(mExternalImageId.ref()); +} + +void TextureHost::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("%s (0x%p)", Name(), this).get(); + // Note: the TextureHost needs to be locked before it is safe to call + // GetSize() and GetFormat() on it. + if (Lock()) { + aStream << " [size=" << GetSize() << "]" + << " [format=" << GetFormat() << "]"; + Unlock(); + } + aStream << " [flags=" << mFlags << "]"; +#ifdef MOZ_DUMP_PAINTING + if (StaticPrefs::layers_dump_texture()) { + nsAutoCString pfx(aPrefix); + pfx += " "; + + aStream << "\n" << pfx.get() << "Surface: "; + RefPtr<gfx::DataSourceSurface> dSurf = GetAsSurface(); + if (dSurf) { + aStream << gfxUtils::GetAsLZ4Base64Str(dSurf).get(); + } + } +#endif +} + +void TextureHost::Updated(const nsIntRegion* aRegion) { + LayerScope::ContentChanged(this); + UpdatedInternal(aRegion); +} + +TextureSource::TextureSource() : mCompositableCount(0) {} + +TextureSource::~TextureSource() = default; +BufferTextureHost::BufferTextureHost(const BufferDescriptor& aDesc, + TextureFlags aFlags) + : TextureHost(aFlags), + mUpdateSerial(1), + mLocked(false), + mNeedsFullUpdate(false) { + mDescriptor = aDesc; + switch (mDescriptor.type()) { + case BufferDescriptor::TYCbCrDescriptor: { + const YCbCrDescriptor& ycbcr = mDescriptor.get_YCbCrDescriptor(); + mSize = ycbcr.display().Size(); + mFormat = gfx::SurfaceFormat::YUV; + mHasIntermediateBuffer = ycbcr.hasIntermediateBuffer(); + break; + } + case BufferDescriptor::TRGBDescriptor: { + const RGBDescriptor& rgb = mDescriptor.get_RGBDescriptor(); + mSize = rgb.size(); + mFormat = rgb.format(); + mHasIntermediateBuffer = rgb.hasIntermediateBuffer(); + break; + } + default: + gfxCriticalError() << "Bad buffer host descriptor " + << (int)mDescriptor.type(); + MOZ_CRASH("GFX: Bad descriptor"); + } + if (aFlags & TextureFlags::COMPONENT_ALPHA) { + // One texture of a component alpha texture pair will start out all white. + // This hack allows us to easily make sure that white will be uploaded. + // See bug 1138934 + mNeedsFullUpdate = true; + } + +#ifdef XP_MACOSX + const int kMinSize = 1024; + const int kMaxSize = 4096; + mUseExternalTextures = + kMaxSize >= mSize.width && mSize.width >= kMinSize && + kMaxSize >= mSize.height && mSize.height >= kMinSize && + StaticPrefs::gfx_webrender_enable_client_storage_AtStartup(); +#else + mUseExternalTextures = false; +#endif +} + +BufferTextureHost::~BufferTextureHost() = default; + +void BufferTextureHost::UpdatedInternal(const nsIntRegion* aRegion) { + ++mUpdateSerial; + // If the last frame wasn't uploaded yet, and we -don't- have a partial + // update, we still need to update the full surface. + if (aRegion && !mNeedsFullUpdate) { + mMaybeUpdatedRegion.OrWith(*aRegion); + } else { + mNeedsFullUpdate = true; + } + if (GetFlags() & TextureFlags::IMMEDIATE_UPLOAD) { + DebugOnly<bool> result = + MaybeUpload(!mNeedsFullUpdate ? &mMaybeUpdatedRegion : nullptr); + NS_WARNING_ASSERTION(result, "Failed to upload a texture"); + } +} + +void BufferTextureHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (mProvider == aProvider) { + return; + } + if (mFirstSource && mFirstSource->IsOwnedBy(this)) { + mFirstSource->SetOwner(nullptr); + } + if (mFirstSource) { + mFirstSource = nullptr; + mNeedsFullUpdate = true; + } + mProvider = aProvider; +} + +void BufferTextureHost::DeallocateDeviceData() { + if (mFirstSource && mFirstSource->NumCompositableRefs() > 0) { + // WrappingTextureSourceYCbCrBasic wraps YUV format BufferTextureHost. + // When BufferTextureHost is destroyed, data of + // WrappingTextureSourceYCbCrBasic becomes invalid. + if (mFirstSource->AsWrappingTextureSourceYCbCrBasic() && + mFirstSource->IsOwnedBy(this)) { + mFirstSource->SetOwner(nullptr); + mFirstSource->DeallocateDeviceData(); + } + return; + } + + if (!mFirstSource || !mFirstSource->IsOwnedBy(this)) { + mFirstSource = nullptr; + return; + } + + mFirstSource->SetOwner(nullptr); + + RefPtr<TextureSource> it = mFirstSource; + while (it) { + it->DeallocateDeviceData(); + it = it->GetNextSibling(); + } +} + +bool BufferTextureHost::Lock() { + MOZ_ASSERT(!mLocked); + if (!UploadIfNeeded()) { + return false; + } + mLocked = !!mFirstSource; + return mLocked; +} + +void BufferTextureHost::Unlock() { + MOZ_ASSERT(mLocked); + mLocked = false; +} + +void BufferTextureHost::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + RefPtr<wr::RenderTextureHost> texture; + + if (gfx::gfxVars::UseSoftwareWebRender()) { + texture = + new wr::RenderBufferTextureHostSWGL(GetBuffer(), GetBufferDescriptor()); + } else if (UseExternalTextures()) { + texture = + new wr::RenderExternalTextureHost(GetBuffer(), GetBufferDescriptor()); + } else { + texture = + new wr::RenderBufferTextureHost(GetBuffer(), GetBufferDescriptor()); + } + + wr::RenderThread::Get()->RegisterExternalImage(wr::AsUint64(aExternalImageId), + texture.forget()); +} + +uint32_t BufferTextureHost::NumSubTextures() { + if (GetFormat() == gfx::SurfaceFormat::YUV) { + return 3; + } + + return 1; +} + +void BufferTextureHost::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, const wr::ExternalImageId& aExtID) { + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + + auto imageType = UseExternalTextures() || gfx::gfxVars::UseSoftwareWebRender() + ? wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureRect) + : wr::ExternalImageType::Buffer(); + + if (GetFormat() != gfx::SurfaceFormat::YUV) { + MOZ_ASSERT(aImageKeys.length() == 1); + + wr::ImageDescriptor descriptor( + GetSize(), + ImageDataSerializer::ComputeRGBStride(GetFormat(), GetSize().width), + GetFormat()); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + } else { + MOZ_ASSERT(aImageKeys.length() == 3); + + const layers::YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + wr::ImageDescriptor yDescriptor( + desc.ySize(), desc.yStride(), + SurfaceFormatForColorDepth(desc.colorDepth())); + wr::ImageDescriptor cbcrDescriptor( + desc.cbCrSize(), desc.cbCrStride(), + SurfaceFormatForColorDepth(desc.colorDepth())); + (aResources.*method)(aImageKeys[0], yDescriptor, aExtID, imageType, 0); + (aResources.*method)(aImageKeys[1], cbcrDescriptor, aExtID, imageType, 1); + (aResources.*method)(aImageKeys[2], cbcrDescriptor, aExtID, imageType, 2); + } +} + +void BufferTextureHost::PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, + wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, + PushDisplayItemFlagSet aFlags) { + // SWGL should always try to bypass shaders and composite directly. + bool preferCompositorSurface = + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE); + bool useExternalSurface = + aFlags.contains(PushDisplayItemFlag::SUPPORTS_EXTERNAL_BUFFER_TEXTURES); + if (GetFormat() != gfx::SurfaceFormat::YUV) { + MOZ_ASSERT(aImageKeys.length() == 1); + aBuilder.PushImage(aBounds, aClip, true, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + preferCompositorSurface, useExternalSurface); + } else { + MOZ_ASSERT(aImageKeys.length() == 3); + const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + aBuilder.PushYCbCrPlanarImage( + aBounds, aClip, true, aImageKeys[0], aImageKeys[1], aImageKeys[2], + wr::ToWrColorDepth(desc.colorDepth()), + wr::ToWrYuvColorSpace(desc.yUVColorSpace()), + wr::ToWrColorRange(desc.colorRange()), aFilter, preferCompositorSurface, + useExternalSurface); + } +} + +void TextureHost::DeserializeReadLock(const ReadLockDescriptor& aDesc, + ISurfaceAllocator* aAllocator) { + if (mReadLock) { + return; + } + + mReadLock = TextureReadLock::Deserialize(aDesc, aAllocator); +} + +void TextureHost::SetReadLocked() { + if (!mReadLock) { + return; + } + // If mReadLocked is true it means we haven't read unlocked yet and the + // content side should not have been able to write into this texture and read + // lock again! + MOZ_ASSERT(!mReadLocked); + mReadLocked = true; + if (mProvider) { + mProvider->MaybeUnlockBeforeNextComposition(this); + } +} + +void TextureHost::ReadUnlock() { + if (mReadLock && mReadLocked) { + mReadLock->ReadUnlock(); + mReadLocked = false; + } +} + +bool TextureHost::NeedsYFlip() const { + return bool(mFlags & TextureFlags::ORIGIN_BOTTOM_LEFT); +} + +bool BufferTextureHost::EnsureWrappingTextureSource() { + MOZ_ASSERT(!mHasIntermediateBuffer); + + if (mFirstSource && mFirstSource->IsOwnedBy(this)) { + return true; + } + // We don't own it, apparently. + if (mFirstSource) { + mNeedsFullUpdate = true; + mFirstSource = nullptr; + } + + if (!mProvider) { + return false; + } + + if (mFormat == gfx::SurfaceFormat::YUV) { + mFirstSource = mProvider->CreateDataTextureSourceAroundYCbCr(this); + } else { + uint8_t* data = GetBuffer(); + if (!data) { + return false; + } + RefPtr<gfx::DataSourceSurface> surf = + gfx::Factory::CreateWrappingDataSourceSurface( + data, ImageDataSerializer::ComputeRGBStride(mFormat, mSize.width), + mSize, mFormat); + if (!surf) { + return false; + } + mFirstSource = mProvider->CreateDataTextureSourceAround(surf); + } + + if (!mFirstSource) { + // BasicCompositor::CreateDataTextureSourceAround never returns null + // and we don't expect to take this branch if we are using another backend. + // Returning false is fine but if we get into this situation it probably + // means something fishy is going on, like a texture being used with + // several compositor backends. + NS_WARNING("Failed to use a BufferTextureHost without intermediate buffer"); + return false; + } + + mFirstSource->SetUpdateSerial(mUpdateSerial); + mFirstSource->SetOwner(this); + + return true; +} + +static bool IsCompatibleTextureSource(TextureSource* aTexture, + const BufferDescriptor& aDescriptor, + TextureSourceProvider* aProvider) { + if (!aProvider) { + return false; + } + + switch (aDescriptor.type()) { + case BufferDescriptor::TYCbCrDescriptor: { + const YCbCrDescriptor& ycbcr = aDescriptor.get_YCbCrDescriptor(); + + if (!aProvider->SupportsEffect(EffectTypes::YCBCR)) { + return aTexture->GetFormat() == gfx::SurfaceFormat::B8G8R8X8 && + aTexture->GetSize() == ycbcr.ySize(); + } + + if (aTexture->GetFormat() != gfx::SurfaceFormat::A8 || + aTexture->GetSize() != ycbcr.ySize()) { + return false; + } + + auto cbTexture = aTexture->GetSubSource(1); + if (!cbTexture || cbTexture->GetFormat() != gfx::SurfaceFormat::A8 || + cbTexture->GetSize() != ycbcr.cbCrSize()) { + return false; + } + + auto crTexture = aTexture->GetSubSource(2); + if (!crTexture || crTexture->GetFormat() != gfx::SurfaceFormat::A8 || + crTexture->GetSize() != ycbcr.cbCrSize()) { + return false; + } + + return true; + } + case BufferDescriptor::TRGBDescriptor: { + const RGBDescriptor& rgb = aDescriptor.get_RGBDescriptor(); + return aTexture->GetFormat() == rgb.format() && + aTexture->GetSize() == rgb.size(); + } + default: { + return false; + } + } +} + +void BufferTextureHost::PrepareTextureSource( + CompositableTextureSourceRef& aTexture) { + // Reuse WrappingTextureSourceYCbCrBasic to reduce memory consumption. + if (mFormat == gfx::SurfaceFormat::YUV && !mHasIntermediateBuffer && + aTexture.get() && aTexture->AsWrappingTextureSourceYCbCrBasic() && + aTexture->NumCompositableRefs() <= 1 && + aTexture->GetSize() == GetSize()) { + aTexture->AsSourceBasic()->SetBufferTextureHost(this); + aTexture->AsDataTextureSource()->SetOwner(this); + mFirstSource = aTexture->AsDataTextureSource(); + mNeedsFullUpdate = true; + } + + if (!mHasIntermediateBuffer) { + EnsureWrappingTextureSource(); + } + + if (mFirstSource && mFirstSource->IsOwnedBy(this)) { + // We are already attached to a TextureSource, nothing to do except tell + // the compositable to use it. + aTexture = mFirstSource.get(); + return; + } + + // We don't own it, apparently. + if (mFirstSource) { + mNeedsFullUpdate = true; + mFirstSource = nullptr; + } + + DataTextureSource* texture = + aTexture.get() ? aTexture->AsDataTextureSource() : nullptr; + + bool compatibleFormats = + texture && IsCompatibleTextureSource(texture, mDescriptor, mProvider); + + bool shouldCreateTexture = !compatibleFormats || + texture->NumCompositableRefs() > 1 || + texture->HasOwner(); + + if (!shouldCreateTexture) { + mFirstSource = texture; + mFirstSource->SetOwner(this); + mNeedsFullUpdate = true; + + // It's possible that texture belonged to a different compositor, + // so make sure we update it (and all of its siblings) to the + // current one. + RefPtr<TextureSource> it = mFirstSource; + while (it) { + it->SetTextureSourceProvider(mProvider); + it = it->GetNextSibling(); + } + } +} + +bool BufferTextureHost::BindTextureSource( + CompositableTextureSourceRef& aTexture) { + MOZ_ASSERT(mLocked); + MOZ_ASSERT(mFirstSource); + aTexture = mFirstSource; + return !!aTexture; +} + +bool BufferTextureHost::AcquireTextureSource( + CompositableTextureSourceRef& aTexture) { + if (!UploadIfNeeded()) { + return false; + } + aTexture = mFirstSource; + return !!mFirstSource; +} + +void BufferTextureHost::ReadUnlock() { + if (mFirstSource) { + mFirstSource->Sync(true); + } + + TextureHost::ReadUnlock(); +} + +void BufferTextureHost::MaybeNotifyUnlocked() { +#ifdef XP_DARWIN + auto actor = GetIPDLActor(); + if (actor) { + AutoTArray<uint64_t, 1> serials; + serials.AppendElement(TextureHost::GetTextureSerial(actor)); + TextureSync::SetTexturesUnlocked(actor->OtherPid(), serials); + } +#endif +} + +void BufferTextureHost::UnbindTextureSource() { + if (mFirstSource && mFirstSource->IsOwnedBy(this)) { + mFirstSource->Unbind(); + } + + // This texture is not used by any layer anymore. + // If the texture doesn't have an intermediate buffer, it means we are + // compositing synchronously on the CPU, so we don't need to wait until + // the end of the next composition to ReadUnlock (which other textures do + // by default). + // If the texture has an intermediate buffer we don't care either because + // texture uploads are also performed synchronously for BufferTextureHost. + ReadUnlock(); + MaybeNotifyUnlocked(); +} + +gfx::SurfaceFormat BufferTextureHost::GetFormat() const { + // mFormat is the format of the data that we share with the content process. + // GetFormat, on the other hand, expects the format that we present to the + // Compositor (it is used to choose the effect type). + // if the compositor does not support YCbCr effects, we give it a RGBX texture + // instead (see BufferTextureHost::Upload) + if (mFormat == gfx::SurfaceFormat::YUV && mProvider && + !mProvider->SupportsEffect(EffectTypes::YCBCR)) { + return gfx::SurfaceFormat::R8G8B8X8; + } + return mFormat; +} + +gfx::YUVColorSpace BufferTextureHost::GetYUVColorSpace() const { + if (mFormat == gfx::SurfaceFormat::YUV) { + const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + return desc.yUVColorSpace(); + } + return gfx::YUVColorSpace::UNKNOWN; +} + +gfx::ColorDepth BufferTextureHost::GetColorDepth() const { + if (mFormat == gfx::SurfaceFormat::YUV) { + const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + return desc.colorDepth(); + } + return gfx::ColorDepth::COLOR_8; +} + +gfx::ColorRange BufferTextureHost::GetColorRange() const { + if (mFormat == gfx::SurfaceFormat::YUV) { + const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + return desc.colorRange(); + } + return TextureHost::GetColorRange(); +} + +bool BufferTextureHost::UploadIfNeeded() { + return MaybeUpload(!mNeedsFullUpdate ? &mMaybeUpdatedRegion : nullptr); +} + +bool BufferTextureHost::MaybeUpload(nsIntRegion* aRegion) { + auto serial = mFirstSource ? mFirstSource->GetUpdateSerial() : 0; + + if (serial == mUpdateSerial) { + return true; + } + + if (serial == 0) { + // 0 means the source has no valid content + aRegion = nullptr; + } + + if (!Upload(aRegion)) { + return false; + } + + if (mHasIntermediateBuffer) { + // We just did the texture upload, the content side can now freely write + // into the shared buffer. + ReadUnlock(); + MaybeNotifyUnlocked(); + } + + // We no longer have an invalid region. + mNeedsFullUpdate = false; + mMaybeUpdatedRegion.SetEmpty(); + + // If upload returns true we know mFirstSource is not null + mFirstSource->SetUpdateSerial(mUpdateSerial); + return true; +} + +bool BufferTextureHost::Upload(nsIntRegion* aRegion) { + uint8_t* buf = GetBuffer(); + if (!buf) { + // We don't have a buffer; a possible cause is that the IPDL actor + // is already dead. This inevitably happens as IPDL actors can die + // at any time, so we want to silently return in this case. + // another possible cause is that IPDL failed to map the shmem when + // deserializing it. + return false; + } + if (!mProvider) { + // This can happen if we send textures to a compositable that isn't yet + // attached to a layer. + return false; + } + if (!mHasIntermediateBuffer && EnsureWrappingTextureSource()) { + if (!mFirstSource || !mFirstSource->IsDirectMap()) { + return true; + } + } + + if (mFormat == gfx::SurfaceFormat::UNKNOWN) { + NS_WARNING("BufferTextureHost: unsupported format!"); + return false; + } else if (mFormat == gfx::SurfaceFormat::YUV) { + const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + + if (!mProvider->SupportsEffect(EffectTypes::YCBCR)) { + RefPtr<gfx::DataSourceSurface> surf = + ImageDataSerializer::DataSourceSurfaceFromYCbCrDescriptor( + buf, mDescriptor.get_YCbCrDescriptor()); + if (NS_WARN_IF(!surf)) { + return false; + } + if (!mFirstSource) { + mFirstSource = mProvider->CreateDataTextureSource( + mFlags | TextureFlags::RGB_FROM_YCBCR); + mFirstSource->SetOwner(this); + } + return mFirstSource->Update(surf, aRegion); + } + + RefPtr<DataTextureSource> srcY; + RefPtr<DataTextureSource> srcU; + RefPtr<DataTextureSource> srcV; + if (!mFirstSource) { + // We don't support BigImages for YCbCr compositing. + srcY = mProvider->CreateDataTextureSource( + mFlags | TextureFlags::DISALLOW_BIGIMAGE); + srcU = mProvider->CreateDataTextureSource( + mFlags | TextureFlags::DISALLOW_BIGIMAGE); + srcV = mProvider->CreateDataTextureSource( + mFlags | TextureFlags::DISALLOW_BIGIMAGE); + mFirstSource = srcY; + mFirstSource->SetOwner(this); + srcY->SetNextSibling(srcU); + srcU->SetNextSibling(srcV); + } else { + // mFormat never changes so if this was created as a YCbCr host and + // already contains a source it should already have 3 sources. + // BufferTextureHost only uses DataTextureSources so it is safe to assume + // all 3 sources are DataTextureSource. + MOZ_ASSERT(mFirstSource->GetNextSibling()); + MOZ_ASSERT(mFirstSource->GetNextSibling()->GetNextSibling()); + srcY = mFirstSource; + srcU = mFirstSource->GetNextSibling()->AsDataTextureSource(); + srcV = mFirstSource->GetNextSibling() + ->GetNextSibling() + ->AsDataTextureSource(); + } + + RefPtr<gfx::DataSourceSurface> tempY = + gfx::Factory::CreateWrappingDataSourceSurface( + ImageDataSerializer::GetYChannel(buf, desc), desc.yStride(), + desc.ySize(), SurfaceFormatForColorDepth(desc.colorDepth())); + RefPtr<gfx::DataSourceSurface> tempCb = + gfx::Factory::CreateWrappingDataSourceSurface( + ImageDataSerializer::GetCbChannel(buf, desc), desc.cbCrStride(), + desc.cbCrSize(), SurfaceFormatForColorDepth(desc.colorDepth())); + RefPtr<gfx::DataSourceSurface> tempCr = + gfx::Factory::CreateWrappingDataSourceSurface( + ImageDataSerializer::GetCrChannel(buf, desc), desc.cbCrStride(), + desc.cbCrSize(), SurfaceFormatForColorDepth(desc.colorDepth())); + // We don't support partial updates for Y U V textures + NS_ASSERTION(!aRegion, "Unsupported partial updates for YCbCr textures"); + if (!tempY || !tempCb || !tempCr || !srcY->Update(tempY) || + !srcU->Update(tempCb) || !srcV->Update(tempCr)) { + NS_WARNING("failed to update the DataTextureSource"); + return false; + } + } else { + // non-YCbCr case + nsIntRegion* regionToUpdate = aRegion; + if (!mFirstSource) { + mFirstSource = mProvider->CreateDataTextureSource(mFlags); + mFirstSource->SetOwner(this); + if (mFlags & TextureFlags::COMPONENT_ALPHA) { + // Update the full region the first time for component alpha textures. + regionToUpdate = nullptr; + } + } + + RefPtr<gfx::DataSourceSurface> surf = + gfx::Factory::CreateWrappingDataSourceSurface( + GetBuffer(), + ImageDataSerializer::ComputeRGBStride(mFormat, mSize.width), mSize, + mFormat); + if (!surf) { + return false; + } + + if (!mFirstSource->Update(surf.get(), regionToUpdate)) { + NS_WARNING("failed to update the DataTextureSource"); + return false; + } + } + MOZ_ASSERT(mFirstSource); + return true; +} + +already_AddRefed<gfx::DataSourceSurface> BufferTextureHost::GetAsSurface() { + RefPtr<gfx::DataSourceSurface> result; + if (mFormat == gfx::SurfaceFormat::UNKNOWN) { + NS_WARNING("BufferTextureHost: unsupported format!"); + return nullptr; + } else if (mFormat == gfx::SurfaceFormat::YUV) { + result = ImageDataSerializer::DataSourceSurfaceFromYCbCrDescriptor( + GetBuffer(), mDescriptor.get_YCbCrDescriptor()); + if (NS_WARN_IF(!result)) { + return nullptr; + } + } else { + result = gfx::Factory::CreateWrappingDataSourceSurface( + GetBuffer(), + ImageDataSerializer::GetRGBStride(mDescriptor.get_RGBDescriptor()), + mSize, mFormat); + } + return result.forget(); +} + +ShmemTextureHost::ShmemTextureHost(const ipc::Shmem& aShmem, + const BufferDescriptor& aDesc, + ISurfaceAllocator* aDeallocator, + TextureFlags aFlags) + : BufferTextureHost(aDesc, aFlags), mDeallocator(aDeallocator) { + if (aShmem.IsReadable()) { + mShmem = MakeUnique<ipc::Shmem>(aShmem); + } else { + // This can happen if we failed to map the shmem on this process, perhaps + // because it was big and we didn't have enough contiguous address space + // available, even though we did on the child process. + // As a result this texture will be in an invalid state and Lock will + // always fail. + + gfxCriticalNote << "Failed to create a valid ShmemTextureHost"; + } + + MOZ_COUNT_CTOR(ShmemTextureHost); +} + +ShmemTextureHost::~ShmemTextureHost() { + MOZ_ASSERT(!mShmem || (mFlags & TextureFlags::DEALLOCATE_CLIENT), + "Leaking our buffer"); + DeallocateDeviceData(); + MOZ_COUNT_DTOR(ShmemTextureHost); +} + +void ShmemTextureHost::DeallocateSharedData() { + if (mShmem) { + MOZ_ASSERT(mDeallocator, + "Shared memory would leak without a ISurfaceAllocator"); + mDeallocator->AsShmemAllocator()->DeallocShmem(*mShmem); + mShmem = nullptr; + } +} + +void ShmemTextureHost::ForgetSharedData() { + if (mShmem) { + mShmem = nullptr; + } +} + +void ShmemTextureHost::OnShutdown() { mShmem = nullptr; } + +uint8_t* ShmemTextureHost::GetBuffer() { + return mShmem ? mShmem->get<uint8_t>() : nullptr; +} + +size_t ShmemTextureHost::GetBufferSize() { + return mShmem ? mShmem->Size<uint8_t>() : 0; +} + +MemoryTextureHost::MemoryTextureHost(uint8_t* aBuffer, + const BufferDescriptor& aDesc, + TextureFlags aFlags) + : BufferTextureHost(aDesc, aFlags), mBuffer(aBuffer) { + MOZ_COUNT_CTOR(MemoryTextureHost); +} + +MemoryTextureHost::~MemoryTextureHost() { + MOZ_ASSERT(!mBuffer || (mFlags & TextureFlags::DEALLOCATE_CLIENT), + "Leaking our buffer"); + DeallocateDeviceData(); + MOZ_COUNT_DTOR(MemoryTextureHost); +} + +void MemoryTextureHost::DeallocateSharedData() { + if (mBuffer) { + GfxMemoryImageReporter::WillFree(mBuffer); + } + delete[] mBuffer; + mBuffer = nullptr; +} + +void MemoryTextureHost::ForgetSharedData() { mBuffer = nullptr; } + +uint8_t* MemoryTextureHost::GetBuffer() { return mBuffer; } + +size_t MemoryTextureHost::GetBufferSize() { + // MemoryTextureHost just trusts that the buffer size is large enough to read + // anything we need to. That's because MemoryTextureHost has to trust the + // buffer pointer anyway, so the security model here is just that + // MemoryTexture's are restricted to same-process clients. + return std::numeric_limits<size_t>::max(); +} + +TextureParent::TextureParent(HostIPCAllocator* aSurfaceAllocator, + uint64_t aSerial, + const wr::MaybeExternalImageId& aExternalImageId) + : mSurfaceAllocator(aSurfaceAllocator), + mSerial(aSerial), + mExternalImageId(aExternalImageId) { + MOZ_COUNT_CTOR(TextureParent); +} + +TextureParent::~TextureParent() { MOZ_COUNT_DTOR(TextureParent); } + +void TextureParent::NotifyNotUsed(uint64_t aTransactionId) { + if (!mTextureHost) { + return; + } + mSurfaceAllocator->NotifyNotUsed(this, aTransactionId); +} + +bool TextureParent::Init(const SurfaceDescriptor& aSharedData, + const ReadLockDescriptor& aReadLock, + const LayersBackend& aBackend, + const TextureFlags& aFlags) { + mTextureHost = TextureHost::Create(aSharedData, aReadLock, mSurfaceAllocator, + aBackend, aFlags, mExternalImageId); + if (mTextureHost) { + mTextureHost->mActor = this; + } + + return !!mTextureHost; +} + +void TextureParent::Destroy() { + if (!mTextureHost) { + return; + } + + if (mTextureHost->mReadLocked) { + // ReadUnlock here to make sure the ReadLock's shmem does not outlive the + // protocol that created it. + mTextureHost->ReadUnlock(); + mTextureHost->MaybeNotifyUnlocked(); + } + + if (mTextureHost->GetFlags() & TextureFlags::DEALLOCATE_CLIENT) { + mTextureHost->ForgetSharedData(); + } + + mTextureHost->mActor = nullptr; + mTextureHost = nullptr; +} + +void TextureHost::ReceivedDestroy(PTextureParent* aActor) { + static_cast<TextureParent*>(aActor)->RecvDestroy(); +} + +mozilla::ipc::IPCResult TextureParent::RecvRecycleTexture( + const TextureFlags& aTextureFlags) { + if (!mTextureHost) { + return IPC_OK(); + } + mTextureHost->RecycleTexture(aTextureFlags); + return IPC_OK(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/TextureHost.h b/gfx/layers/composite/TextureHost.h new file mode 100644 index 0000000000..aafbde1e0d --- /dev/null +++ b/gfx/layers/composite/TextureHost.h @@ -0,0 +1,1082 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_TEXTUREHOST_H +#define MOZILLA_GFX_TEXTUREHOST_H + +#include <stddef.h> // for size_t +#include <stdint.h> // for uint64_t, uint32_t, uint8_t +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed, etc +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/gfx/Point.h" // for IntSize, IntPoint +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/Types.h" // for SurfaceFormat, etc +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/layers/CompositorTypes.h" // for TextureFlags, etc +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "mozilla/layers/LayersSurfaces.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "mozilla/Range.h" +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_WARNING +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsRect.h" +#include "nsRegion.h" // for nsIntRegion +#include "nsTraceRefcnt.h" // for MOZ_COUNT_CTOR, etc +#include "nscore.h" // for nsACString +#include "mozilla/layers/AtomicRefCountedWithFinalize.h" + +class MacIOSurface; +namespace mozilla { +namespace gfx { +class DataSourceSurface; +} + +namespace ipc { +class Shmem; +} // namespace ipc + +namespace wr { +class DisplayListBuilder; +class TransactionBuilder; +} // namespace wr + +namespace layers { + +class AndroidHardwareBuffer; +class AndroidHardwareBufferTextureHost; +class BufferDescriptor; +class BufferTextureHost; +class Compositor; +class CompositableParentManager; +class ReadLockDescriptor; +class CompositorBridgeParent; +class SurfaceDescriptor; +class HostIPCAllocator; +class ISurfaceAllocator; +class MacIOSurfaceTextureHostOGL; +class SurfaceTextureHost; +class TextureHostOGL; +class TextureReadLock; +class TextureSourceOGL; +class TextureSourceD3D11; +class TextureSourceBasic; +class TextureSourceProvider; +class DataTextureSource; +class PTextureParent; +class TextureParent; +class WebRenderTextureHost; +class WrappingTextureSourceYCbCrBasic; + +/** + * A view on a TextureHost where the texture is internally represented as tiles + * (contrast with a tiled buffer, where each texture is a tile). For iteration + * by the texture's buffer host. This is only useful when the underlying surface + * is too big to fit in one device texture, which forces us to split it in + * smaller parts. Tiled Compositable is a different thing. + */ +class BigImageIterator { + public: + virtual void BeginBigImageIteration() = 0; + virtual void EndBigImageIteration(){}; + virtual gfx::IntRect GetTileRect() = 0; + virtual size_t GetTileCount() = 0; + virtual bool NextTile() = 0; +}; + +/** + * TextureSource is the interface for texture objects that can be composited + * by a given compositor backend. Since the drawing APIs are different + * between backends, the TextureSource interface is split into different + * interfaces (TextureSourceOGL, etc.), and TextureSource mostly provide + * access to these interfaces. + * + * This class is used on the compositor side. + */ +class TextureSource : public RefCounted<TextureSource> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(TextureSource) + + TextureSource(); + + virtual ~TextureSource(); + + virtual const char* Name() const = 0; + + /** + * Should be overridden in order to deallocate the data that is associated + * with the rendering backend, such as GL textures. + */ + virtual void DeallocateDeviceData() {} + + /** + * Return the size of the texture in texels. + * If this is a tile iterator, GetSize must return the size of the current + * tile. + */ + virtual gfx::IntSize GetSize() const = 0; + + /** + * Return the pixel format of this texture + */ + virtual gfx::SurfaceFormat GetFormat() const { + return gfx::SurfaceFormat::UNKNOWN; + } + + /** + * Cast to a TextureSource for for each backend.. + */ + virtual TextureSourceOGL* AsSourceOGL() { + gfxCriticalNote << "Failed to cast " << Name() + << " into a TextureSourceOGL"; + return nullptr; + } + virtual TextureSourceD3D11* AsSourceD3D11() { return nullptr; } + virtual TextureSourceBasic* AsSourceBasic() { return nullptr; } + /** + * Cast to a DataTextureSurce. + */ + virtual DataTextureSource* AsDataTextureSource() { return nullptr; } + virtual WrappingTextureSourceYCbCrBasic* AsWrappingTextureSourceYCbCrBasic() { + return nullptr; + } + + /** + * Overload this if the TextureSource supports big textures that don't fit in + * one device texture and must be tiled internally. + */ + virtual BigImageIterator* AsBigImageIterator() { return nullptr; } + + virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider) {} + + virtual void Unbind() {} + + void SetNextSibling(TextureSource* aTexture) { mNextSibling = aTexture; } + + TextureSource* GetNextSibling() const { return mNextSibling; } + + /** + * In some rare cases we currently need to consider a group of textures as one + * TextureSource, that can be split in sub-TextureSources. + */ + TextureSource* GetSubSource(int index) { + switch (index) { + case 0: + return this; + case 1: + return GetNextSibling(); + case 2: + return GetNextSibling() ? GetNextSibling()->GetNextSibling() : nullptr; + } + return nullptr; + } + + void AddCompositableRef() { ++mCompositableCount; } + + void ReleaseCompositableRef() { + --mCompositableCount; + MOZ_ASSERT(mCompositableCount >= 0); + } + + // When iterating as a BigImage, this creates temporary TextureSources + // wrapping individual tiles. + virtual RefPtr<TextureSource> ExtractCurrentTile() { + NS_WARNING("Implementation does not expose tile sources"); + return nullptr; + } + + int NumCompositableRefs() const { return mCompositableCount; } + + // Some texture sources could wrap the cpu buffer to gpu directly. Then, + // we could get better performance of texture uploading. + virtual bool IsDirectMap() { return false; } + // The direct-map cpu buffer should be alive when gpu uses it. And it + // should not be updated while gpu reads it. This Sync() function + // implements this synchronized behavior by allowing us to check if + // the GPU is done with the texture, and block on it if aBlocking is + // true. + virtual bool Sync(bool aBlocking) { return true; } + + protected: + RefPtr<TextureSource> mNextSibling; + int mCompositableCount; +}; + +/// Equivalent of a RefPtr<TextureSource>, that calls AddCompositableRef and +/// ReleaseCompositableRef in addition to the usual AddRef and Release. +/// +/// The semantoics of these CompositableTextureRefs are important because they +/// are used both as a synchronization/safety mechanism, and as an optimization +/// mechanism. They are also tricky and subtle because we use them in a very +/// implicit way (assigning to a CompositableTextureRef is less visible than +/// explicitly calling a method or whatnot). +/// It is Therefore important to be careful about the way we use this tool. +/// +/// CompositableTextureRef is a mechanism that lets us count how many +/// compositables are using a given texture (for TextureSource and TextureHost). +/// We use it to run specific code when a texture is not used anymore, and also +/// we trigger fast paths on some operations when we can see that the texture's +/// CompositableTextureRef counter is equal to 1 (the texture is not shared +/// between compositables). +/// This means that it is important to observe the following rules: +/// * CompositableHosts that receive UseTexture and similar messages *must* +/// store all of the TextureHosts they receive in CompositableTextureRef slots +/// for as long as they may be using them. +/// * CompositableHosts must store each texture in a *single* +/// CompositableTextureRef slot to ensure that the counter properly reflects how +/// many compositables are using the texture. If a compositable needs to hold +/// two references to a given texture (for example to have a pointer to the +/// current texture in a list of textures that may be used), it can hold its +/// extra references with RefPtr or whichever pointer type makes sense. +template <typename T> +class CompositableTextureRef { + public: + CompositableTextureRef() = default; + + explicit CompositableTextureRef(const CompositableTextureRef& aOther) { + *this = aOther; + } + + explicit CompositableTextureRef(T* aOther) { *this = aOther; } + + ~CompositableTextureRef() { + if (mRef) { + mRef->ReleaseCompositableRef(); + } + } + + CompositableTextureRef& operator=(const CompositableTextureRef& aOther) { + if (aOther.get()) { + aOther->AddCompositableRef(); + } + if (mRef) { + mRef->ReleaseCompositableRef(); + } + mRef = aOther.get(); + return *this; + } + + CompositableTextureRef& operator=(T* aOther) { + if (aOther) { + aOther->AddCompositableRef(); + } + if (mRef) { + mRef->ReleaseCompositableRef(); + } + mRef = aOther; + return *this; + } + + T* get() const { return mRef; } + operator T*() const { return mRef; } + T* operator->() const { return mRef; } + T& operator*() const { return *mRef; } + + private: + RefPtr<T> mRef; +}; + +typedef CompositableTextureRef<TextureSource> CompositableTextureSourceRef; +typedef CompositableTextureRef<TextureHost> CompositableTextureHostRef; + +/** + * Interface for TextureSources that can be updated from a DataSourceSurface. + * + * All backend should implement at least one DataTextureSource. + */ +class DataTextureSource : public TextureSource { + public: + DataTextureSource() : mOwner(0), mUpdateSerial(0) {} + + const char* Name() const override { return "DataTextureSource"; } + + DataTextureSource* AsDataTextureSource() override { return this; } + + /** + * Upload a (portion of) surface to the TextureSource. + * + * The DataTextureSource doesn't own aSurface, although it owns and manage + * the device texture it uploads to internally. + */ + virtual bool Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion = nullptr, + gfx::IntPoint* aSrcOffset = nullptr) = 0; + + /** + * A facility to avoid reuploading when it is not necessary. + * The caller of Update can use GetUpdateSerial to see if the number has + * changed since last update, and call SetUpdateSerial after each successful + * update. The caller is responsible for managing the update serial except + * when the texture data is deallocated in which case the TextureSource should + * always reset the update serial to zero. + */ + uint32_t GetUpdateSerial() const { return mUpdateSerial; } + void SetUpdateSerial(uint32_t aValue) { mUpdateSerial = aValue; } + + // By default at least set the update serial to zero. + // overloaded versions should do that too. + void DeallocateDeviceData() override { SetUpdateSerial(0); } + +#ifdef DEBUG + /** + * Provide read access to the data as a DataSourceSurface. + * + * This is expected to be very slow and should be used for mostly debugging. + * XXX - implement everywhere and make it pure virtual. + */ + virtual already_AddRefed<gfx::DataSourceSurface> ReadBack() { + return nullptr; + }; +#endif + + void SetOwner(TextureHost* aOwner) { + auto newOwner = (uintptr_t)aOwner; + if (newOwner != mOwner) { + mOwner = newOwner; + SetUpdateSerial(0); + } + } + + bool IsOwnedBy(TextureHost* aOwner) const { + return mOwner == (uintptr_t)aOwner; + } + + bool HasOwner() const { return !IsOwnedBy(nullptr); } + + private: + // We store mOwner as an integer rather than as a pointer to make it clear + // it is not intended to be dereferenced. + uintptr_t mOwner; + uint32_t mUpdateSerial; +}; + +/** + * TextureHost is a thin abstraction over texture data that need to be shared + * between the content process and the compositor process. It is the + * compositor-side half of a TextureClient/TextureHost pair. A corresponding + * TextureClient lives on the content-side. + * + * TextureHost only knows how to deserialize or synchronize generic image data + * (SurfaceDescriptor) and provide access to one or more TextureSource objects + * (these provide the necessary APIs for compositor backends to composite the + * image). + * + * A TextureHost implementation corresponds to one SurfaceDescriptor type, as + * opposed to TextureSource that corresponds to device textures. + * This means that for YCbCr planes, even though they are represented as + * 3 textures internally (3 TextureSources), we use 1 TextureHost and not 3, + * because the 3 planes are stored in the same buffer of shared memory, before + * they are uploaded separately. + * + * There is always one and only one TextureHost per TextureClient, and the + * TextureClient/Host pair only owns one buffer of image data through its + * lifetime. This means that the lifetime of the underlying shared data + * matches the lifetime of the TextureClient/Host pair. It also means + * TextureClient/Host do not implement double buffering, which is the + * reponsibility of the compositable (which would use two Texture pairs). + * + * The Lock/Unlock mecanism here mirrors Lock/Unlock in TextureClient. + * + */ +class TextureHost : public AtomicRefCountedWithFinalize<TextureHost> { + /** + * Called once, just before the destructor. + * + * Here goes the shut-down code that uses virtual methods. + * Must only be called by Release(). + */ + void Finalize(); + + friend class AtomicRefCountedWithFinalize<TextureHost>; + + public: + explicit TextureHost(TextureFlags aFlags); + + protected: + virtual ~TextureHost(); + + public: + /** + * Factory method. + */ + static already_AddRefed<TextureHost> Create( + const SurfaceDescriptor& aDesc, const ReadLockDescriptor& aReadLock, + ISurfaceAllocator* aDeallocator, LayersBackend aBackend, + TextureFlags aFlags, wr::MaybeExternalImageId& aExternalImageId); + + /** + * Lock the texture host for compositing. + */ + virtual bool Lock() { return true; } + /** + * Unlock the texture host after compositing. Lock() and Unlock() should be + * called in pair. + */ + virtual void Unlock() {} + + /** + * Lock the texture host for compositing without using compositor. + */ + virtual bool LockWithoutCompositor() { return true; } + /** + * Similar to Unlock(), but it should be called with LockWithoutCompositor(). + */ + virtual void UnlockWithoutCompositor() {} + + /** + * Note that the texture host format can be different from its corresponding + * texture source's. For example a ShmemTextureHost can have the ycbcr + * format and produce 3 "alpha" textures sources. + */ + virtual gfx::SurfaceFormat GetFormat() const = 0; + /** + * Return the format used for reading the texture. + * Apple's YCBCR_422 is R8G8B8X8. + */ + virtual gfx::SurfaceFormat GetReadFormat() const { return GetFormat(); } + + virtual gfx::YUVColorSpace GetYUVColorSpace() const { + return gfx::YUVColorSpace::UNKNOWN; + } + + /** + * Return the color depth of the image. Used with YUV textures. + */ + virtual gfx::ColorDepth GetColorDepth() const { + return gfx::ColorDepth::COLOR_8; + } + + /** + * Return true if using full range values (0-255 if 8 bits YUV). Used with YUV + * textures. + */ + virtual gfx::ColorRange GetColorRange() const { + return gfx::ColorRange::LIMITED; + } + + /** + * Called during the transaction. The TextureSource may or may not be + * composited. + * + * Note that this is called outside of lock/unlock. + */ + virtual void PrepareTextureSource(CompositableTextureSourceRef& aTexture) {} + + /** + * Called at composition time, just before compositing the TextureSource + * composited. + * + * Note that this is called only withing lock/unlock. + */ + virtual bool BindTextureSource(CompositableTextureSourceRef& aTexture) = 0; + + /** + * Called when preparing the rendering pipeline for advanced-layers. This is + * a lockless version of BindTextureSource. + */ + virtual bool AcquireTextureSource(CompositableTextureSourceRef& aTexture) { + return false; + } + + /** + * Called when another TextureHost will take over. + */ + virtual void UnbindTextureSource(); + + virtual bool IsValid() { return true; } + + /** + * Is called before compositing if the shared data has changed since last + * composition. + * This method should be overload in cases like when we need to do a texture + * upload for example. + * + * @param aRegion The region that has been changed, if nil, it means that the + * entire surface should be updated. + */ + void Updated(const nsIntRegion* aRegion = nullptr); + + /** + * Sets this TextureHost's compositor. A TextureHost can change compositor + * on certain occasions, in particular if it belongs to an async Compositable. + * aCompositor can be null, in which case the TextureHost must cleanup all + * of its device textures. + * + * Setting mProvider from this callback implicitly causes the texture to + * be locked for an extra frame after being detached from a compositable. + */ + virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider) {} + + /** + * Should be overridden in order to deallocate the data that is associated + * with the rendering backend, such as GL textures. + */ + virtual void DeallocateDeviceData() {} + + /** + * Should be overridden in order to deallocate the data that is shared with + * the content side, such as shared memory. + */ + virtual void DeallocateSharedData() {} + + /** + * Should be overridden in order to force the TextureHost to drop all + * references to it's shared data. + * + * This is important to ensure the correctness of the deallocation protocol. + */ + virtual void ForgetSharedData() {} + + virtual gfx::IntSize GetSize() const = 0; + + /** + * Should be overridden if TextureHost supports crop rect. + */ + virtual void SetCropRect(nsIntRect aCropRect) {} + + /** + * Debug facility. + * XXX - cool kids use Moz2D. See bug 882113. + */ + virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() = 0; + + /** + * XXX - Flags should only be set at creation time, this will be removed. + */ + void SetFlags(TextureFlags aFlags) { mFlags = aFlags; } + + /** + * XXX - Flags should only be set at creation time, this will be removed. + */ + void AddFlag(TextureFlags aFlag) { mFlags |= aFlag; } + + TextureFlags GetFlags() { return mFlags; } + + /** + * Allocate and deallocate a TextureParent actor. + * + * TextureParent< is an implementation detail of TextureHost that is not + * exposed to the rest of the code base. CreateIPDLActor and DestroyIPDLActor + * are for use with the managing IPDL protocols only (so that they can + * implement AllocPTextureParent and DeallocPTextureParent). + */ + static PTextureParent* CreateIPDLActor( + HostIPCAllocator* aAllocator, const SurfaceDescriptor& aSharedData, + const ReadLockDescriptor& aDescriptor, LayersBackend aLayersBackend, + TextureFlags aFlags, uint64_t aSerial, + const wr::MaybeExternalImageId& aExternalImageId); + static bool DestroyIPDLActor(PTextureParent* actor); + + /** + * Destroy the TextureChild/Parent pair. + */ + static bool SendDeleteIPDLActor(PTextureParent* actor); + + static void ReceivedDestroy(PTextureParent* actor); + + /** + * Get the TextureHost corresponding to the actor passed in parameter. + */ + static TextureHost* AsTextureHost(PTextureParent* actor); + + static uint64_t GetTextureSerial(PTextureParent* actor); + + /** + * Return a pointer to the IPDLActor. + * + * This is to be used with IPDL messages only. Do not store the returned + * pointer. + */ + PTextureParent* GetIPDLActor(); + + // If a texture host holds a reference to shmem, it should override this + // method to forget about the shmem _without_ releasing it. + virtual void OnShutdown() {} + + // Forget buffer actor. Used only for hacky fix for bug 966446. + virtual void ForgetBufferActor() {} + + virtual const char* Name() { return "TextureHost"; } + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix); + + /** + * Indicates whether the TextureHost implementation is backed by an + * in-memory buffer. The consequence of this is that locking the + * TextureHost does not contend with locking the texture on the client side. + */ + virtual bool HasIntermediateBuffer() const { return false; } + + /** + * Returns true if the TextureHost can be released before the rendering is + * completed, otherwise returns false. + */ + virtual bool NeedsDeferredDeletion() const { + return !HasIntermediateBuffer(); + } + + void AddCompositableRef() { + ++mCompositableCount; + if (mCompositableCount == 1) { + PrepareForUse(); + } + } + + void ReleaseCompositableRef() { + --mCompositableCount; + MOZ_ASSERT(mCompositableCount >= 0); + if (mCompositableCount == 0) { + UnbindTextureSource(); + // Send mFwdTransactionId to client side if necessary. + NotifyNotUsed(); + } + } + + int NumCompositableRefs() const { return mCompositableCount; } + + void SetLastFwdTransactionId(uint64_t aTransactionId); + + void DeserializeReadLock(const ReadLockDescriptor& aDesc, + ISurfaceAllocator* aAllocator); + void SetReadLocked(); + + TextureReadLock* GetReadLock() { return mReadLock; } + + virtual BufferTextureHost* AsBufferTextureHost() { return nullptr; } + virtual MacIOSurfaceTextureHostOGL* AsMacIOSurfaceTextureHost() { + return nullptr; + } + virtual WebRenderTextureHost* AsWebRenderTextureHost() { return nullptr; } + virtual SurfaceTextureHost* AsSurfaceTextureHost() { return nullptr; } + virtual AndroidHardwareBufferTextureHost* + AsAndroidHardwareBufferTextureHost() { + return nullptr; + } + + // Create the corresponding RenderTextureHost type of this texture, and + // register the RenderTextureHost into render thread. + virtual void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + MOZ_RELEASE_ASSERT( + false, + "No CreateRenderTexture() implementation for this TextureHost type."); + } + + void EnsureRenderTexture(const wr::MaybeExternalImageId& aExternalImageId); + + // Destroy RenderTextureHost when it was created by the TextureHost. + // It is called in TextureHost::Finalize(). + virtual void MaybeDestroyRenderTexture(); + + static void DestroyRenderTexture(const wr::ExternalImageId& aExternalImageId); + + /// Returns the number of actual textures that will be used to render this. + /// For example in a lot of YUV cases it will be 3 + virtual uint32_t NumSubTextures() { return 1; } + + enum ResourceUpdateOp { + ADD_IMAGE, + UPDATE_IMAGE, + }; + + // Add all necessary TextureHost informations to the resource update queue. + virtual void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, + const wr::ExternalImageId& aExtID) { + MOZ_ASSERT_UNREACHABLE("Unimplemented"); + } + + enum class PushDisplayItemFlag { + // Passed if the caller wants these display items to be promoted + // to compositor surfaces if possible. + PREFER_COMPOSITOR_SURFACE, + + // Passed in the RenderCompositor supports BufferTextureHosts + // being used directly as external compositor surfaces. + SUPPORTS_EXTERNAL_BUFFER_TEXTURES, + }; + using PushDisplayItemFlagSet = EnumSet<PushDisplayItemFlag>; + + // Put all necessary WR commands into DisplayListBuilder for this textureHost + // rendering. + virtual void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, + wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aKeys, + PushDisplayItemFlagSet aFlags) { + MOZ_ASSERT_UNREACHABLE( + "No PushDisplayItems() implementation for this TextureHost type."); + } + + /** + * Some API's can use the cross-process IOSurface directly, such as OpenVR + */ + virtual MacIOSurface* GetMacIOSurface() { return nullptr; } + + virtual bool IsDirectMap() { return false; } + + virtual bool NeedsYFlip() const; + + TextureSourceProvider* GetProvider() const { return mProvider; } + + virtual void SetAcquireFence(mozilla::ipc::FileDescriptor&& aFenceFd) {} + + virtual void SetReleaseFence(mozilla::ipc::FileDescriptor&& aFenceFd) {} + + virtual mozilla::ipc::FileDescriptor GetAndResetReleaseFence() { + return mozilla::ipc::FileDescriptor(); + } + + virtual AndroidHardwareBuffer* GetAndroidHardwareBuffer() const { + return nullptr; + } + + virtual bool SupportsExternalCompositing() { return false; } + + protected: + virtual void ReadUnlock(); + + void RecycleTexture(TextureFlags aFlags); + + virtual void MaybeNotifyUnlocked() {} + + virtual void UpdatedInternal(const nsIntRegion* Region) {} + + /** + * Called when mCompositableCount becomes from 0 to 1. + */ + virtual void PrepareForUse() {} + + /** + * Called when mCompositableCount becomes 0. + */ + virtual void NotifyNotUsed(); + + // for Compositor. + void CallNotifyNotUsed(); + + PTextureParent* mActor; + RefPtr<TextureSourceProvider> mProvider; + RefPtr<TextureReadLock> mReadLock; + TextureFlags mFlags; + int mCompositableCount; + uint64_t mFwdTransactionId; + bool mReadLocked; + wr::MaybeExternalImageId mExternalImageId; + + friend class Compositor; + friend class TextureParent; + friend class TiledLayerBufferComposite; + friend class TextureSourceProvider; + friend class GPUVideoTextureHost; + friend class WebRenderTextureHost; +}; + +/** + * TextureHost that wraps a random access buffer such as a Shmem or some raw + * memory. + * + * This TextureHost is backend-independent and the backend-specific bits are + * in the TextureSource. + * This class must be inherited to implement GetBuffer and DeallocSharedData + * (see ShmemTextureHost and MemoryTextureHost) + * + * Uploads happen when Lock is called. + * + * BufferTextureHost supports YCbCr and flavours of RGBA images (RGBX, A, etc.). + */ +class BufferTextureHost : public TextureHost { + public: + BufferTextureHost(const BufferDescriptor& aDescriptor, TextureFlags aFlags); + + virtual ~BufferTextureHost(); + + virtual uint8_t* GetBuffer() = 0; + + virtual size_t GetBufferSize() = 0; + + bool Lock() override; + + void Unlock() override; + + void PrepareTextureSource(CompositableTextureSourceRef& aTexture) override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override; + bool AcquireTextureSource(CompositableTextureSourceRef& aTexture) override; + + void UnbindTextureSource() override; + + void DeallocateDeviceData() override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + /** + * Return the format that is exposed to the compositor when calling + * BindTextureSource. + * + * If the shared format is YCbCr and the compositor does not support it, + * GetFormat will be RGB32 (even though mFormat is SurfaceFormat::YUV). + */ + gfx::SurfaceFormat GetFormat() const override; + + gfx::YUVColorSpace GetYUVColorSpace() const override; + + gfx::ColorDepth GetColorDepth() const override; + + gfx::ColorRange GetColorRange() const override; + + gfx::IntSize GetSize() const override { return mSize; } + + already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override; + + bool HasIntermediateBuffer() const override { return mHasIntermediateBuffer; } + + bool NeedsDeferredDeletion() const override { + return TextureHost::NeedsDeferredDeletion() || UseExternalTextures(); + } + + BufferTextureHost* AsBufferTextureHost() override { return this; } + + const BufferDescriptor& GetBufferDescriptor() const { return mDescriptor; } + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + void ReadUnlock() override; + bool IsDirectMap() override { + return mFirstSource && mFirstSource->IsDirectMap(); + }; + + bool CanUnlock() { return !mFirstSource || mFirstSource->Sync(false); } + void DisableExternalTextures() { mUseExternalTextures = false; } + + protected: + bool UseExternalTextures() const { return mUseExternalTextures; } + bool Upload(nsIntRegion* aRegion = nullptr); + bool UploadIfNeeded(); + bool MaybeUpload(nsIntRegion* aRegion); + bool EnsureWrappingTextureSource(); + + void UpdatedInternal(const nsIntRegion* aRegion = nullptr) override; + void MaybeNotifyUnlocked() override; + + BufferDescriptor mDescriptor; + RefPtr<Compositor> mCompositor; + RefPtr<DataTextureSource> mFirstSource; + nsIntRegion mMaybeUpdatedRegion; + gfx::IntSize mSize; + gfx::SurfaceFormat mFormat; + uint32_t mUpdateSerial; + bool mLocked; + bool mNeedsFullUpdate; + bool mHasIntermediateBuffer; + bool mUseExternalTextures; + + class DataTextureSourceYCbCrBasic; +}; + +/** + * TextureHost that wraps shared memory. + * the corresponding texture on the client side is ShmemTextureClient. + * This TextureHost is backend-independent. + */ +class ShmemTextureHost : public BufferTextureHost { + public: + ShmemTextureHost(const mozilla::ipc::Shmem& aShmem, + const BufferDescriptor& aDesc, + ISurfaceAllocator* aDeallocator, TextureFlags aFlags); + + protected: + ~ShmemTextureHost(); + + public: + void DeallocateSharedData() override; + + void ForgetSharedData() override; + + uint8_t* GetBuffer() override; + + size_t GetBufferSize() override; + + const char* Name() override { return "ShmemTextureHost"; } + + void OnShutdown() override; + + protected: + UniquePtr<mozilla::ipc::Shmem> mShmem; + RefPtr<ISurfaceAllocator> mDeallocator; +}; + +/** + * TextureHost that wraps raw memory. + * The corresponding texture on the client side is MemoryTextureClient. + * Can obviously not be used in a cross process setup. + * This TextureHost is backend-independent. + */ +class MemoryTextureHost : public BufferTextureHost { + public: + MemoryTextureHost(uint8_t* aBuffer, const BufferDescriptor& aDesc, + TextureFlags aFlags); + + protected: + ~MemoryTextureHost(); + + public: + void DeallocateSharedData() override; + + void ForgetSharedData() override; + + uint8_t* GetBuffer() override; + + size_t GetBufferSize() override; + + const char* Name() override { return "MemoryTextureHost"; } + + protected: + uint8_t* mBuffer; +}; + +class MOZ_STACK_CLASS AutoLockTextureHost { + public: + explicit AutoLockTextureHost(TextureHost* aTexture) : mTexture(aTexture) { + mLocked = mTexture ? mTexture->Lock() : false; + } + + ~AutoLockTextureHost() { + if (mTexture && mLocked) { + mTexture->Unlock(); + } + } + + bool Failed() { return mTexture && !mLocked; } + + private: + RefPtr<TextureHost> mTexture; + bool mLocked; +}; + +class MOZ_STACK_CLASS AutoLockTextureHostWithoutCompositor { + public: + explicit AutoLockTextureHostWithoutCompositor(TextureHost* aTexture) + : mTexture(aTexture) { + mLocked = mTexture ? mTexture->LockWithoutCompositor() : false; + } + + ~AutoLockTextureHostWithoutCompositor() { + if (mTexture && mLocked) { + mTexture->UnlockWithoutCompositor(); + } + } + + bool Failed() { return mTexture && !mLocked; } + + private: + RefPtr<TextureHost> mTexture; + bool mLocked; +}; + +/** + * This can be used as an offscreen rendering target by the compositor, and + * subsequently can be used as a source by the compositor. + */ +class CompositingRenderTarget : public TextureSource { + public: + explicit CompositingRenderTarget(const gfx::IntPoint& aOrigin) + : mClearOnBind(false), + mOrigin(aOrigin), + mZNear(0), + mZFar(0), + mHasComplexProjection(false), + mEnableDepthBuffer(false) {} + virtual ~CompositingRenderTarget() = default; + + const char* Name() const override { return "CompositingRenderTarget"; } + +#ifdef MOZ_DUMP_PAINTING + virtual already_AddRefed<gfx::DataSourceSurface> Dump( + Compositor* aCompositor) { + return nullptr; + } +#endif + + /** + * Perform a clear when recycling a non opaque surface. + * The clear is deferred to when the render target is bound. + */ + void ClearOnBind() { mClearOnBind = true; } + + const gfx::IntPoint& GetOrigin() const { return mOrigin; } + gfx::IntRect GetRect() { return gfx::IntRect(GetOrigin(), GetSize()); } + + /** + * If a Projection matrix is set, then it is used for rendering to + * this render target instead of generating one. If no explicit + * projection is set, Compositors are expected to generate an + * orthogonal maaping that maps 0..1 to the full size of the render + * target. + */ + bool HasComplexProjection() const { return mHasComplexProjection; } + void ClearProjection() { mHasComplexProjection = false; } + void SetProjection(const gfx::Matrix4x4& aNewMatrix, bool aEnableDepthBuffer, + float aZNear, float aZFar) { + mProjectionMatrix = aNewMatrix; + mEnableDepthBuffer = aEnableDepthBuffer; + mZNear = aZNear; + mZFar = aZFar; + mHasComplexProjection = true; + } + void GetProjection(gfx::Matrix4x4& aMatrix, bool& aEnableDepth, float& aZNear, + float& aZFar) { + MOZ_ASSERT(mHasComplexProjection); + aMatrix = mProjectionMatrix; + aEnableDepth = mEnableDepthBuffer; + aZNear = mZNear; + aZFar = mZFar; + } + + protected: + bool mClearOnBind; + + private: + gfx::IntPoint mOrigin; + + gfx::Matrix4x4 mProjectionMatrix; + float mZNear, mZFar; + bool mHasComplexProjection; + bool mEnableDepthBuffer; +}; + +/** + * Creates a TextureHost that can be used with any of the existing backends + * Not all SurfaceDescriptor types are supported + */ +already_AddRefed<TextureHost> CreateBackendIndependentTextureHost( + const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, TextureFlags aFlags); + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/TiledContentHost.cpp b/gfx/layers/composite/TiledContentHost.cpp new file mode 100644 index 0000000000..02f7c3b846 --- /dev/null +++ b/gfx/layers/composite/TiledContentHost.cpp @@ -0,0 +1,658 @@ +/* -*- 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 "TiledContentHost.h" +#include "PaintedLayerComposite.h" // for PaintedLayerComposite +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/layers/Compositor.h" // for Compositor +// clang-format off +//#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent +// clang-format on +#include "mozilla/layers/Effects.h" // for TexturedEffect, Effect, etc +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/PTextureParent.h" +#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL +#ifdef XP_DARWIN +# include "mozilla/layers/TextureSync.h" // for TextureSync +#endif +#include "nsAString.h" +#include "nsDebug.h" // for NS_WARNING +#include "nsPoint.h" // for IntPoint +#include "nsPrintfCString.h" // for nsPrintfCString +#include "nsRect.h" // for IntRect +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/layers/TextureClient.h" + +namespace mozilla { +using namespace gfx; +namespace layers { + +class Layer; + +float TileHost::GetFadeInOpacity(float aOpacity) { + TimeStamp now = TimeStamp::Now(); + if (!StaticPrefs::layers_tiles_fade_in_enabled() || mFadeStart.IsNull() || + now < mFadeStart) { + return aOpacity; + } + + float duration = StaticPrefs::layers_tiles_fade_in_duration_ms(); + float elapsed = (now - mFadeStart).ToMilliseconds(); + if (elapsed > duration) { + mFadeStart = TimeStamp(); + return aOpacity; + } + return aOpacity * (elapsed / duration); +} + +RefPtr<TextureSource> TileHost::AcquireTextureSource() const { + if (!mTextureHost || !mTextureHost->AcquireTextureSource(mTextureSource)) { + return nullptr; + } + return mTextureSource.get(); +} + +RefPtr<TextureSource> TileHost::AcquireTextureSourceOnWhite() const { + if (!mTextureHostOnWhite || + !mTextureHostOnWhite->AcquireTextureSource(mTextureSourceOnWhite)) { + return nullptr; + } + return mTextureSourceOnWhite.get(); +} + +TiledLayerBufferComposite::TiledLayerBufferComposite() : mFrameResolution() {} + +TiledLayerBufferComposite::~TiledLayerBufferComposite() { Clear(); } + +void TiledLayerBufferComposite::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + MOZ_ASSERT(aProvider); + for (TileHost& tile : mRetainedTiles) { + if (tile.IsPlaceholderTile()) continue; + tile.mTextureHost->SetTextureSourceProvider(aProvider); + if (tile.mTextureHostOnWhite) { + tile.mTextureHostOnWhite->SetTextureSourceProvider(aProvider); + } + } +} + +void TiledLayerBufferComposite::AddAnimationInvalidation(nsIntRegion& aRegion) { + // We need to invalidate rects where we have a tile that is in the + // process of fading in. + for (size_t i = 0; i < mRetainedTiles.Length(); i++) { + if (!mRetainedTiles[i].mFadeStart.IsNull()) { + TileCoordIntPoint coord = mTiles.TileCoord(i); + IntPoint offset = GetTileOffset(coord); + nsIntRegion tileRegion = IntRect(offset, GetScaledTileSize()); + aRegion.OrWith(tileRegion); + } + } +} + +TiledContentHost::TiledContentHost(const TextureInfo& aTextureInfo) + : ContentHost(aTextureInfo), + mTiledBuffer(TiledLayerBufferComposite()), + mLowPrecisionTiledBuffer(TiledLayerBufferComposite()) { + MOZ_COUNT_CTOR(TiledContentHost); +} + +TiledContentHost::~TiledContentHost() { MOZ_COUNT_DTOR(TiledContentHost); } + +already_AddRefed<TexturedEffect> TiledContentHost::GenEffect( + const gfx::SamplingFilter aSamplingFilter) { + MOZ_ASSERT(mTiledBuffer.GetTileCount() == 1 && + mLowPrecisionTiledBuffer.GetTileCount() == 0); + MOZ_ASSERT(mTiledBuffer.GetTile(0).mTextureHost); + + TileHost& tile = mTiledBuffer.GetTile(0); + if (!tile.mTextureHost->BindTextureSource(tile.mTextureSource)) { + return nullptr; + } + + return CreateTexturedEffect(tile.mTextureSource, nullptr, aSamplingFilter, + true); +} + +void TiledContentHost::Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags /* = NO_FLAGS */) { + CompositableHost::Attach(aLayer, aProvider, aFlags); +} + +void TiledContentHost::Detach(Layer* aLayer, + AttachFlags aFlags /* = NO_FLAGS */) { + if (!mKeepAttached || aLayer == mLayer || aFlags & FORCE_DETACH) { + // Clear the TiledLayerBuffers, which will take care of releasing the + // copy-on-write locks. + mTiledBuffer.Clear(); + mLowPrecisionTiledBuffer.Clear(); + } + CompositableHost::Detach(aLayer, aFlags); +} + +bool TiledContentHost::UseTiledLayerBuffer( + ISurfaceAllocator* aAllocator, + const SurfaceDescriptorTiles& aTiledDescriptor) { + HostLayerManager* lm = GetLayerManager(); + if (!lm) { + return false; + } + + if (aTiledDescriptor.resolution() < 1) { + if (!mLowPrecisionTiledBuffer.UseTiles(aTiledDescriptor, lm, aAllocator)) { + return false; + } + } else { + if (!mTiledBuffer.UseTiles(aTiledDescriptor, lm, aAllocator)) { + return false; + } + } + return true; +} + +static void UseTileTexture(CompositableTextureHostRef& aTexture, + CompositableTextureSourceRef& aTextureSource, + const IntRect& aUpdateRect, + TextureSourceProvider* aProvider) { + MOZ_ASSERT(aTexture); + if (!aTexture) { + return; + } + + if (aProvider) { + aTexture->SetTextureSourceProvider(aProvider); + } + + if (!aUpdateRect.IsEmpty()) { + // For !HasIntermediateBuffer() textures, this is likely a no-op. + nsIntRegion region = aUpdateRect; + aTexture->Updated(®ion); + } + + aTexture->PrepareTextureSource(aTextureSource); +} + +class TextureSourceRecycler { + public: + explicit TextureSourceRecycler(nsTArray<TileHost>&& aTileSet) + : mTiles(std::move(aTileSet)), mFirstPossibility(0) {} + + // Attempts to recycle a texture source that is already bound to the + // texture host for aTile. + void RecycleTextureSourceForTile(TileHost& aTile) { + for (size_t i = mFirstPossibility; i < mTiles.Length(); i++) { + // Skip over existing tiles without a retained texture source + // and make sure we don't iterate them in the future. + if (!mTiles[i].mTextureSource) { + if (i == mFirstPossibility) { + mFirstPossibility++; + } + continue; + } + + // If this tile matches, then copy across the retained texture source (if + // any). + if (aTile.mTextureHost == mTiles[i].mTextureHost) { + aTile.mTextureSource = std::move(mTiles[i].mTextureSource); + if (aTile.mTextureHostOnWhite) { + aTile.mTextureSourceOnWhite = + std::move(mTiles[i].mTextureSourceOnWhite); + } + break; + } + } + } + + // Attempts to recycle any texture source to avoid needing to allocate + // a new one. + void RecycleTextureSource(TileHost& aTile) { + for (size_t i = mFirstPossibility; i < mTiles.Length(); i++) { + if (!mTiles[i].mTextureSource) { + if (i == mFirstPossibility) { + mFirstPossibility++; + } + continue; + } + + if (mTiles[i].mTextureSource && mTiles[i].mTextureHost->GetFormat() == + aTile.mTextureHost->GetFormat()) { + aTile.mTextureSource = std::move(mTiles[i].mTextureSource); + if (aTile.mTextureHostOnWhite) { + aTile.mTextureSourceOnWhite = + std::move(mTiles[i].mTextureSourceOnWhite); + } + break; + } + } + } + + void RecycleTileFading(TileHost& aTile) { + for (size_t i = 0; i < mTiles.Length(); i++) { + if (mTiles[i].mTextureHost == aTile.mTextureHost) { + aTile.mFadeStart = mTiles[i].mFadeStart; + } + } + } + + protected: + nsTArray<TileHost> mTiles; + size_t mFirstPossibility; +}; + +bool TiledLayerBufferComposite::UseTiles(const SurfaceDescriptorTiles& aTiles, + HostLayerManager* aLayerManager, + ISurfaceAllocator* aAllocator) { + if (mResolution != aTiles.resolution() || aTiles.tileSize() != mTileSize) { + Clear(); + } + MOZ_ASSERT(aAllocator); + MOZ_ASSERT(aLayerManager); + if (!aAllocator || !aLayerManager) { + return false; + } + + if (aTiles.resolution() == 0 || IsNaN(aTiles.resolution())) { + // There are divisions by mResolution so this protects the compositor + // process against malicious content processes and fuzzing. + return false; + } + + TilesPlacement newTiles(aTiles.firstTileX(), aTiles.firstTileY(), + aTiles.retainedWidth(), aTiles.retainedHeight()); + + const nsTArray<TileDescriptor>& tileDescriptors = aTiles.tiles(); + + TextureSourceRecycler oldRetainedTiles(std::move(mRetainedTiles)); + mRetainedTiles.SetLength(tileDescriptors.Length()); + + AutoTArray<uint64_t, 10> lockedTextureSerials; + base::ProcessId lockedTexturePid = 0; + + // Step 1, deserialize the incoming set of tiles into mRetainedTiles, and + // attempt to recycle the TextureSource for any repeated tiles. + // + // Since we don't have any retained 'tile' object, we have to search for + // instances of the same TextureHost in the old tile set. The cost of binding + // a TextureHost to a TextureSource for gralloc (binding EGLImage to GL + // texture) can be really high, so we avoid this whenever possible. + for (size_t i = 0; i < tileDescriptors.Length(); i++) { + const TileDescriptor& tileDesc = tileDescriptors[i]; + + TileHost& tile = mRetainedTiles[i]; + + if (tileDesc.type() != TileDescriptor::TTexturedTileDescriptor) { + NS_WARNING_ASSERTION( + tileDesc.type() == TileDescriptor::TPlaceholderTileDescriptor, + "Unrecognised tile descriptor type"); + continue; + } + + const TexturedTileDescriptor& texturedDesc = + tileDesc.get_TexturedTileDescriptor(); + + tile.mTextureHost = + TextureHost::AsTextureHost(texturedDesc.textureParent()); + if (texturedDesc.readLocked()) { + tile.mTextureHost->SetReadLocked(); + auto actor = tile.mTextureHost->GetIPDLActor(); + if (actor && tile.mTextureHost->IsDirectMap()) { + lockedTextureSerials.AppendElement( + TextureHost::GetTextureSerial(actor)); + + if (lockedTexturePid) { + MOZ_ASSERT(lockedTexturePid == actor->OtherPid()); + } + lockedTexturePid = actor->OtherPid(); + } + } + + if (texturedDesc.textureOnWhiteParent().isSome()) { + tile.mTextureHostOnWhite = + TextureHost::AsTextureHost(texturedDesc.textureOnWhiteParent().ref()); + if (texturedDesc.readLockedOnWhite()) { + tile.mTextureHostOnWhite->SetReadLocked(); + auto actor = tile.mTextureHostOnWhite->GetIPDLActor(); + if (actor && tile.mTextureHostOnWhite->IsDirectMap()) { + lockedTextureSerials.AppendElement( + TextureHost::GetTextureSerial(actor)); + } + } + } + + tile.mTileCoord = newTiles.TileCoord(i); + + // If this same tile texture existed in the old tile set then this will move + // the texture source into our new tile. + oldRetainedTiles.RecycleTextureSourceForTile(tile); + + // If this tile is in the process of fading, we need to keep that going + oldRetainedTiles.RecycleTileFading(tile); + + if (aTiles.isProgressive() && texturedDesc.wasPlaceholder()) { + // This is a progressive paint, and the tile used to be a placeholder. + // We need to begin fading it in (if enabled via + // layers.tiles.fade-in.enabled) + tile.mFadeStart = TimeStamp::Now(); + + aLayerManager->CompositeUntil( + tile.mFadeStart + + TimeDuration::FromMilliseconds( + StaticPrefs::layers_tiles_fade_in_duration_ms())); + } + } + +#ifdef XP_DARWIN + if (lockedTextureSerials.Length() > 0) { + TextureSync::SetTexturesLocked(lockedTexturePid, lockedTextureSerials); + } +#endif + + // Step 2, attempt to recycle unused texture sources from the old tile set + // into new tiles. + // + // For gralloc, binding a new TextureHost to the existing TextureSource is the + // fastest way to ensure that any implicit locking on the old gralloc image is + // released. + for (TileHost& tile : mRetainedTiles) { + if (!tile.mTextureHost || tile.mTextureSource) { + continue; + } + oldRetainedTiles.RecycleTextureSource(tile); + } + + // Step 3, handle the texture uploads, texture source binding and release the + // copy-on-write locks for textures with an internal buffer. + for (size_t i = 0; i < mRetainedTiles.Length(); i++) { + TileHost& tile = mRetainedTiles[i]; + if (!tile.mTextureHost) { + continue; + } + + const TileDescriptor& tileDesc = tileDescriptors[i]; + const TexturedTileDescriptor& texturedDesc = + tileDesc.get_TexturedTileDescriptor(); + + UseTileTexture(tile.mTextureHost, tile.mTextureSource, + texturedDesc.updateRect(), + aLayerManager->GetTextureSourceProvider()); + + if (tile.mTextureHostOnWhite) { + UseTileTexture(tile.mTextureHostOnWhite, tile.mTextureSourceOnWhite, + texturedDesc.updateRect(), + aLayerManager->GetTextureSourceProvider()); + } + } + + mTiles = newTiles; + mTileSize = aTiles.tileSize(); + mTileOrigin = aTiles.tileOrigin(); + mValidRegion = aTiles.validRegion(); + mResolution = aTiles.resolution(); + mFrameResolution = CSSToParentLayerScale2D(aTiles.frameXResolution(), + aTiles.frameYResolution()); + + return true; +} + +void TiledLayerBufferComposite::Clear() { + mRetainedTiles.Clear(); + mTiles.mFirst = TileCoordIntPoint(); + mTiles.mSize = TileCoordIntSize(); + mValidRegion = nsIntRegion(); + mResolution = 1.0; +} + +void TiledContentHost::Composite( + Compositor* aCompositor, LayerComposite* aLayer, EffectChain& aEffectChain, + float aOpacity, const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion /* = nullptr */, + const Maybe<gfx::Polygon>& aGeometry) { + // Reduce the opacity of the low-precision buffer to make it a + // little more subtle and less jarring. In particular, text + // rendered at low-resolution and scaled tends to look pretty + // heavy and this helps mitigate that. When we reduce the opacity + // we also make sure to draw the background color behind the + // reduced-opacity tile so that content underneath doesn't show + // through. + // However, in cases where the background is transparent, or the layer + // already has some opacity, we want to skip this behaviour. Otherwise + // we end up changing the expected overall transparency of the content, + // and it just looks wrong. + DeviceColor backgroundColor; + if (aOpacity == 1.0f && StaticPrefs::layers_low_precision_opacity() < 1.0f) { + // Background colors are only stored on scrollable layers. Grab + // the one from the nearest scrollable ancestor layer. + for (LayerMetricsWrapper ancestor(GetLayer(), + LayerMetricsWrapper::StartAt::BOTTOM); + ancestor; ancestor = ancestor.GetParent()) { + if (ancestor.Metrics().IsScrollable()) { + backgroundColor = ancestor.Metadata().GetBackgroundColor(); + break; + } + } + } + float lowPrecisionOpacityReduction = + (aOpacity == 1.0f && backgroundColor.a == 1.0f) + ? StaticPrefs::layers_low_precision_opacity() + : 1.0f; + + nsIntRegion tmpRegion; + const nsIntRegion* renderRegion = aVisibleRegion; +#ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE + if (PaintWillResample()) { + // If we're resampling, then the texture image will contain exactly the + // entire visible region's bounds, and we should draw it all in one quad + // to avoid unexpected aliasing. + tmpRegion = aVisibleRegion->GetBounds(); + renderRegion = &tmpRegion; + } +#endif + + // Render the low and high precision buffers. + RenderLayerBuffer( + mLowPrecisionTiledBuffer, aCompositor, + lowPrecisionOpacityReduction < 1.0f ? &backgroundColor : nullptr, + aEffectChain, lowPrecisionOpacityReduction * aOpacity, aSamplingFilter, + aClipRect, *renderRegion, aTransform, aGeometry); + + RenderLayerBuffer(mTiledBuffer, aCompositor, nullptr, aEffectChain, aOpacity, + aSamplingFilter, aClipRect, *renderRegion, aTransform, + aGeometry); +} + +void TiledContentHost::RenderTile( + TileHost& aTile, Compositor* aCompositor, EffectChain& aEffectChain, + float aOpacity, const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, const gfx::IntRect& aClipRect, + const nsIntRegion& aScreenRegion, const IntPoint& aTextureOffset, + const IntSize& aTextureBounds, const gfx::Rect& aVisibleRect, + const Maybe<gfx::Polygon>& aGeometry) { + MOZ_ASSERT(!aTile.IsPlaceholderTile()); + + AutoLockTextureHost autoLock(aTile.mTextureHost); + AutoLockTextureHost autoLockOnWhite(aTile.mTextureHostOnWhite); + if (autoLock.Failed() || autoLockOnWhite.Failed()) { + NS_WARNING("Failed to lock tile"); + return; + } + + if (!aTile.mTextureHost->BindTextureSource(aTile.mTextureSource)) { + return; + } + + if (aTile.mTextureHostOnWhite && + !aTile.mTextureHostOnWhite->BindTextureSource( + aTile.mTextureSourceOnWhite)) { + return; + } + + RefPtr<TexturedEffect> effect = CreateTexturedEffect( + aTile.mTextureSource, aTile.mTextureSourceOnWhite, aSamplingFilter, true); + if (!effect) { + return; + } + + float opacity = aTile.GetFadeInOpacity(aOpacity); + aEffectChain.mPrimaryEffect = effect; + + for (auto iter = aScreenRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + Rect graphicsRect(rect.X(), rect.Y(), rect.Width(), rect.Height()); + Rect textureRect(rect.X() - aTextureOffset.x, rect.Y() - aTextureOffset.y, + rect.Width(), rect.Height()); + + effect->mTextureCoords.SetRect( + textureRect.X() / aTextureBounds.width, + textureRect.Y() / aTextureBounds.height, + textureRect.Width() / aTextureBounds.width, + textureRect.Height() / aTextureBounds.height); + + aCompositor->DrawGeometry(graphicsRect, aClipRect, aEffectChain, opacity, + aTransform, aVisibleRect, aGeometry); + } + + DiagnosticFlags flags = DiagnosticFlags::CONTENT | DiagnosticFlags::TILE; + if (aTile.mTextureHostOnWhite) { + flags |= DiagnosticFlags::COMPONENT_ALPHA; + } + aCompositor->DrawDiagnostics(flags, aScreenRegion, aClipRect, aTransform, + mFlashCounter); +} + +void TiledContentHost::RenderLayerBuffer( + TiledLayerBufferComposite& aLayerBuffer, Compositor* aCompositor, + const DeviceColor* aBackgroundColor, EffectChain& aEffectChain, + float aOpacity, const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, nsIntRegion aVisibleRegion, + gfx::Matrix4x4 aTransform, const Maybe<Polygon>& aGeometry) { + float resolution = aLayerBuffer.GetResolution(); + gfx::Size layerScale(1, 1); + + // We assume that the current frame resolution is the one used in our high + // precision layer buffer. Compensate for a changing frame resolution when + // rendering the low precision buffer. + if (aLayerBuffer.GetFrameResolution() != mTiledBuffer.GetFrameResolution()) { + const CSSToParentLayerScale2D& layerResolution = + aLayerBuffer.GetFrameResolution(); + const CSSToParentLayerScale2D& localResolution = + mTiledBuffer.GetFrameResolution(); + layerScale.width = layerResolution.xScale / localResolution.xScale; + layerScale.height = layerResolution.yScale / localResolution.yScale; + aVisibleRegion.ScaleRoundOut(layerScale.width, layerScale.height); + } + + // Make sure we don't render at low resolution where we have valid high + // resolution content, to avoid overdraw and artifacts with semi-transparent + // layers. + nsIntRegion maskRegion; + if (resolution != mTiledBuffer.GetResolution()) { + maskRegion = mTiledBuffer.GetValidRegion(); + // XXX This should be ScaleRoundIn, but there is no such function on + // nsIntRegion. + maskRegion.ScaleRoundOut(layerScale.width, layerScale.height); + } + + // Make sure the resolution and difference in frame resolution are accounted + // for in the layer transform. + aTransform.PreScale(1 / (resolution * layerScale.width), + 1 / (resolution * layerScale.height), 1); + + DiagnosticFlags componentAlphaDiagnostic = DiagnosticFlags::NO_DIAGNOSTIC; + + nsIntRegion compositeRegion = aLayerBuffer.GetValidRegion(); + compositeRegion.AndWith(aVisibleRegion); + compositeRegion.SubOut(maskRegion); + + IntRect visibleRect = aVisibleRegion.GetBounds(); + + if (compositeRegion.IsEmpty()) { + return; + } + + if (aBackgroundColor) { + nsIntRegion backgroundRegion = compositeRegion; + backgroundRegion.ScaleRoundOut(resolution, resolution); + EffectChain effect; + effect.mPrimaryEffect = new EffectSolidColor(*aBackgroundColor); + for (auto iter = backgroundRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + Rect graphicsRect(rect.X(), rect.Y(), rect.Width(), rect.Height()); + aCompositor->DrawGeometry(graphicsRect, aClipRect, effect, 1.0, + aTransform, aGeometry); + } + } + + for (size_t i = 0; i < aLayerBuffer.GetTileCount(); ++i) { + TileHost& tile = aLayerBuffer.GetTile(i); + if (tile.IsPlaceholderTile()) { + continue; + } + + TileCoordIntPoint tileCoord = aLayerBuffer.GetPlacement().TileCoord(i); + // A sanity check that catches a lot of mistakes. + MOZ_ASSERT(tileCoord.x == tile.mTileCoord.x && + tileCoord.y == tile.mTileCoord.y); + + IntPoint tileOffset = aLayerBuffer.GetTileOffset(tileCoord); + nsIntRegion tileDrawRegion = + IntRect(tileOffset, aLayerBuffer.GetScaledTileSize()); + tileDrawRegion.AndWith(compositeRegion); + + if (tileDrawRegion.IsEmpty()) { + continue; + } + + tileDrawRegion.ScaleRoundOut(resolution, resolution); + RenderTile(tile, aCompositor, aEffectChain, aOpacity, aTransform, + aSamplingFilter, aClipRect, tileDrawRegion, + tileOffset * resolution, aLayerBuffer.GetTileSize(), + gfx::Rect(visibleRect.X(), visibleRect.Y(), visibleRect.Width(), + visibleRect.Height()), + aGeometry); + + if (tile.mTextureHostOnWhite) { + componentAlphaDiagnostic = DiagnosticFlags::COMPONENT_ALPHA; + } + } + + gfx::Rect rect(visibleRect.X(), visibleRect.Y(), visibleRect.Width(), + visibleRect.Height()); + aCompositor->DrawDiagnostics( + DiagnosticFlags::CONTENT | componentAlphaDiagnostic, rect, aClipRect, + aTransform, mFlashCounter); +} + +void TiledContentHost::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("TiledContentHost (0x%p)", this).get(); + +#if defined(MOZ_DUMP_PAINTING) + if (StaticPrefs::layers_dump_texture()) { + nsAutoCString pfx(aPrefix); + pfx += " "; + + Dump(aStream, pfx.get(), false); + } +#endif +} + +void TiledContentHost::Dump(std::stringstream& aStream, const char* aPrefix, + bool aDumpHtml) { + mTiledBuffer.Dump( + aStream, aPrefix, aDumpHtml, + TextureDumpMode:: + DoNotCompress /* compression not supported on host side */); +} + +void TiledContentHost::AddAnimationInvalidation(nsIntRegion& aRegion) { + return mTiledBuffer.AddAnimationInvalidation(aRegion); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/TiledContentHost.h b/gfx/layers/composite/TiledContentHost.h new file mode 100644 index 0000000000..fa754bff30 --- /dev/null +++ b/gfx/layers/composite/TiledContentHost.h @@ -0,0 +1,263 @@ +/* -*- 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/. */ + +#ifndef GFX_TILEDCONTENTHOST_H +#define GFX_TILEDCONTENTHOST_H + +#include <stdint.h> // for uint16_t +#include <stdio.h> // for FILE +#include <algorithm> // for swap +#include "ContentHost.h" // for ContentHost +#include "TiledLayerBuffer.h" // for TiledLayerBuffer, etc +#include "CompositableHost.h" +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/MatrixFwd.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for SamplingFilter +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "mozilla/layers/TextureHost.h" // for TextureHost +#include "mozilla/layers/TextureClient.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "nsRegion.h" // for nsIntRegion +#include "nscore.h" // for nsACString + +namespace mozilla { + +namespace layers { + +class Compositor; +class ISurfaceAllocator; +class Layer; +class ThebesBufferData; +class TextureReadLock; +struct EffectChain; + +class TileHost { + public: + // Constructs a placeholder TileHost. See the comments above + // TiledLayerBuffer for more information on what this is used for; + // essentially, this is a sentinel used to represent an invalid or blank + // tile. + TileHost() = default; + + // Constructs a TileHost from a TextureReadLock and TextureHost. + TileHost(TextureReadLock* aSharedLock, TextureHost* aTextureHost, + TextureHost* aTextureHostOnWhite, TextureSource* aSource, + TextureSource* aSourceOnWhite) + : mTextureHost(aTextureHost), + mTextureHostOnWhite(aTextureHostOnWhite), + mTextureSource(aSource), + mTextureSourceOnWhite(aSourceOnWhite) {} + + TileHost(const TileHost& o) { + mTextureHost = o.mTextureHost; + mTextureHostOnWhite = o.mTextureHostOnWhite; + mTextureSource = o.mTextureSource; + mTextureSourceOnWhite = o.mTextureSourceOnWhite; + mTileCoord = o.mTileCoord; + } + TileHost& operator=(const TileHost& o) { + if (this == &o) { + return *this; + } + mTextureHost = o.mTextureHost; + mTextureHostOnWhite = o.mTextureHostOnWhite; + mTextureSource = o.mTextureSource; + mTextureSourceOnWhite = o.mTextureSourceOnWhite; + mTileCoord = o.mTileCoord; + return *this; + } + + bool operator==(const TileHost& o) const { + return mTextureHost == o.mTextureHost; + } + bool operator!=(const TileHost& o) const { + return mTextureHost != o.mTextureHost; + } + + bool IsPlaceholderTile() const { return mTextureHost == nullptr; } + + void Dump(std::stringstream& aStream) { + aStream << "TileHost(...)"; // fill in as needed + } + + void DumpTexture(std::stringstream& aStream, + TextureDumpMode /* aCompress, ignored for host tiles */) { + // TODO We should combine the OnWhite/OnBlack here an just output a single + // image. + CompositableHost::DumpTextureHost(aStream, mTextureHost); + } + + RefPtr<TextureSource> AcquireTextureSource() const; + RefPtr<TextureSource> AcquireTextureSourceOnWhite() const; + + /** + * This does a linear tween of the passed opacity (which is assumed + * to be between 0.0 and 1.0). The duration of the fade is controlled + * by the 'layers.tiles.fade-in.duration-ms' preference. It is enabled + * via 'layers.tiles.fade-in.enabled' + */ + float GetFadeInOpacity(float aOpacity); + + CompositableTextureHostRef mTextureHost; + CompositableTextureHostRef mTextureHostOnWhite; + mutable CompositableTextureSourceRef mTextureSource; + mutable CompositableTextureSourceRef mTextureSourceOnWhite; + // This is not strictly necessary but makes debugging whole lot easier. + TileCoordIntPoint mTileCoord; + TimeStamp mFadeStart; +}; + +class TiledLayerBufferComposite + : public TiledLayerBuffer<TiledLayerBufferComposite, TileHost> { + friend class TiledLayerBuffer<TiledLayerBufferComposite, TileHost>; + + public: + TiledLayerBufferComposite(); + ~TiledLayerBufferComposite(); + + bool UseTiles(const SurfaceDescriptorTiles& aTileDescriptors, + HostLayerManager* aLayerManager, ISurfaceAllocator* aAllocator); + + void Clear(); + + TileHost GetPlaceholderTile() const { return TileHost(); } + + // Stores the absolute resolution of the containing frame, calculated + // by the sum of the resolutions of all parent layers' FrameMetrics. + const CSSToParentLayerScale2D& GetFrameResolution() { + return mFrameResolution; + } + + void SetTextureSourceProvider(TextureSourceProvider* aProvider); + + void AddAnimationInvalidation(nsIntRegion& aRegion); + + protected: + CSSToParentLayerScale2D mFrameResolution; +}; + +/** + * ContentHost for tiled PaintedLayers. Since tiled layers are special snow + * flakes, we have a unique update process. All the textures that back the + * tiles are added in the usual way, but Updated is called on the host side + * in response to a message that describes the transaction for every tile. + * Composition happens in the normal way. + * + * TiledContentHost has a TiledLayerBufferComposite which keeps hold of the + * tiles. Each tile has a reference to a texture host. During the layers + * transaction, we receive a list of descriptors for the client-side tile buffer + * tiles (UseTiledLayerBuffer). If we receive two transactions before a + * composition, we immediately unlock and discard the unused buffer. + * + * When the content host is composited, we first validate the TiledLayerBuffer + * (Upload), which calls Updated on each tile's texture host to make sure the + * texture data has been uploaded. For single-buffered tiles, we unlock at this + * point, for double-buffered tiles we unlock and discard the last composited + * buffer after compositing a new one. Rendering takes us to RenderTile which + * is similar to Composite for non-tiled ContentHosts. + */ +class TiledContentHost : public ContentHost { + public: + explicit TiledContentHost(const TextureInfo& aTextureInfo); + + protected: + virtual ~TiledContentHost(); + + public: + // Generate effect for layerscope when using hwc. + virtual already_AddRefed<TexturedEffect> GenEffect( + const gfx::SamplingFilter aSamplingFilter) override; + + virtual bool UpdateThebes(const ThebesBufferData& aData, + const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) override { + NS_ERROR("N/A for tiled layers"); + return false; + } + + const nsIntRegion& GetValidLowPrecisionRegion() const { + return mLowPrecisionTiledBuffer.GetValidRegion(); + } + + const nsIntRegion& GetValidRegion() const { + return mTiledBuffer.GetValidRegion(); + } + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override { + CompositableHost::SetTextureSourceProvider(aProvider); + mTiledBuffer.SetTextureSourceProvider(aProvider); + mLowPrecisionTiledBuffer.SetTextureSourceProvider(aProvider); + } + + bool UseTiledLayerBuffer(ISurfaceAllocator* aAllocator, + const SurfaceDescriptorTiles& aTiledDescriptor); + + void Composite(Compositor* aCompositor, LayerComposite* aLayer, + EffectChain& aEffectChain, float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr, + const Maybe<gfx::Polygon>& aGeometry = Nothing()) override; + + CompositableType GetType() override { + return CompositableType::CONTENT_TILED; + } + + TiledContentHost* AsTiledContentHost() override { return this; } + + void Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags = NO_FLAGS) override; + + void Detach(Layer* aLayer = nullptr, AttachFlags aFlags = NO_FLAGS) override; + + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false) override; + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void AddAnimationInvalidation(nsIntRegion& aRegion) override; + + TiledLayerBufferComposite& GetLowResBuffer() { + return mLowPrecisionTiledBuffer; + } + TiledLayerBufferComposite& GetHighResBuffer() { return mTiledBuffer; } + + private: + void RenderLayerBuffer(TiledLayerBufferComposite& aLayerBuffer, + Compositor* aCompositor, + const gfx::DeviceColor* aBackgroundColor, + EffectChain& aEffectChain, float aOpacity, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, nsIntRegion aMaskRegion, + gfx::Matrix4x4 aTransform, + const Maybe<gfx::Polygon>& aGeometry); + + // Renders a single given tile. + void RenderTile( + TileHost& aTile, Compositor* aCompositor, EffectChain& aEffectChain, + float aOpacity, const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, const gfx::IntRect& aClipRect, + const nsIntRegion& aScreenRegion, const gfx::IntPoint& aTextureOffset, + const gfx::IntSize& aTextureBounds, const gfx::Rect& aVisibleRect, + const Maybe<gfx::Polygon>& aGeometry); + + void EnsureTileStore() {} + + TiledLayerBufferComposite mTiledBuffer; + TiledLayerBufferComposite mLowPrecisionTiledBuffer; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/X11TextureHost.cpp b/gfx/layers/composite/X11TextureHost.cpp new file mode 100644 index 0000000000..5bbb7a82c8 --- /dev/null +++ b/gfx/layers/composite/X11TextureHost.cpp @@ -0,0 +1,103 @@ +/* -*- 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 "X11TextureHost.h" + +#include "mozilla/layers/BasicCompositor.h" +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/X11TextureSourceBasic.h" +#include "mozilla/layers/X11TextureSourceOGL.h" +#include "gfx2DGlue.h" +#include "gfxPlatform.h" +#include "gfxXlibSurface.h" + +namespace mozilla::layers { + +using namespace mozilla::gfx; + +X11TextureHost::X11TextureHost(TextureFlags aFlags, + const SurfaceDescriptorX11& aDescriptor) + : TextureHost(aFlags) { + mSurface = aDescriptor.OpenForeign(); + + if (mSurface && !(aFlags & TextureFlags::DEALLOCATE_CLIENT)) { + mSurface->TakePixmap(); + } +} + +bool X11TextureHost::Lock() { + if (!mCompositor || !mSurface) { + return false; + } + + if (!mTextureSource) { + switch (mCompositor->GetBackendType()) { + case LayersBackend::LAYERS_BASIC: + mTextureSource = new X11TextureSourceBasic( + mCompositor->AsBasicCompositor(), mSurface); + break; + case LayersBackend::LAYERS_OPENGL: + mTextureSource = + new X11TextureSourceOGL(mCompositor->AsCompositorOGL(), mSurface); + break; + default: + return false; + } + } + + return true; +} + +void X11TextureHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + mProvider = aProvider; + if (mProvider) { + mCompositor = mProvider->AsCompositor(); + } else { + mCompositor = nullptr; + } + if (mTextureSource) { + mTextureSource->SetTextureSourceProvider(aProvider); + } +} + +SurfaceFormat X11TextureHost::GetFormat() const { + if (!mSurface) { + return SurfaceFormat::UNKNOWN; + } + gfxContentType type = mSurface->GetContentType(); + if (mCompositor->GetBackendType() == LayersBackend::LAYERS_OPENGL) { + return X11TextureSourceOGL::ContentTypeToSurfaceFormat(type); + } + return X11TextureSourceBasic::ContentTypeToSurfaceFormat(type); +} + +IntSize X11TextureHost::GetSize() const { + if (!mSurface) { + return IntSize(); + } + return mSurface->GetSize(); +} + +already_AddRefed<gfx::DataSourceSurface> X11TextureHost::GetAsSurface() { + if (!mTextureSource || !mTextureSource->AsSourceBasic()) { + return nullptr; + } + RefPtr<DrawTarget> tempDT = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(GetSize(), + GetFormat()); + if (!tempDT) { + return nullptr; + } + RefPtr<SourceSurface> surf = + mTextureSource->AsSourceBasic()->GetSurface(tempDT); + if (!surf) { + return nullptr; + } + return surf->GetDataSurface(); +} + +} // namespace mozilla::layers diff --git a/gfx/layers/composite/X11TextureHost.h b/gfx/layers/composite/X11TextureHost.h new file mode 100644 index 0000000000..568e136ff1 --- /dev/null +++ b/gfx/layers/composite/X11TextureHost.h @@ -0,0 +1,65 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_X11TEXTUREHOST__H +#define MOZILLA_GFX_X11TEXTUREHOST__H + +#include "mozilla/layers/TextureHost.h" +#include "mozilla/layers/LayersSurfaces.h" +#include "mozilla/gfx/Types.h" + +#include "gfxXlibSurface.h" + +namespace mozilla { +namespace layers { + +class X11TextureSource : public TextureSource { + public: + // Called when the underlying X surface has been changed. + // Useful for determining whether to rebind a GLXPixmap to a texture. + virtual void Updated() = 0; + + const char* Name() const override { return "X11TextureSource"; } +}; + +// TextureHost for Xlib-backed TextureSources. +class X11TextureHost : public TextureHost { + public: + X11TextureHost(TextureFlags aFlags, const SurfaceDescriptorX11& aDescriptor); + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + bool Lock() override; + + gfx::SurfaceFormat GetFormat() const override; + + gfx::IntSize GetSize() const override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override { + aTexture = mTextureSource; + return !!aTexture; + } + + already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override; + +#ifdef MOZ_LAYERS_HAVE_LOG + const char* Name() override { return "X11TextureHost"; } +#endif + + protected: + void UpdatedInternal(const nsIntRegion*) override { + if (mTextureSource) mTextureSource->Updated(); + } + + RefPtr<Compositor> mCompositor; + RefPtr<X11TextureSource> mTextureSource; + RefPtr<gfxXlibSurface> mSurface; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_X11TEXTUREHOST__H |