diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /gfx/layers/composite | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'gfx/layers/composite')
38 files changed, 12747 insertions, 0 deletions
diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp new file mode 100644 index 0000000000..69eafcecae --- /dev/null +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -0,0 +1,1495 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 et 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 "apz/src/AsyncPanZoomController.h" +#include "FrameMetrics.h" // for FrameMetrics +#include "LayerManagerComposite.h" // for LayerManagerComposite, etc +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "gfxPoint.h" // for gfxPoint, gfxSize +#include "gfxPrefs.h" // for gfxPrefs +#include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc +#include "mozilla/WidgetUtils.h" // for ComputeTransformForRotation +#include "mozilla/dom/KeyframeEffectReadOnly.h" +#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for dom::FillMode +#include "mozilla/dom/KeyframeEffectBinding.h" // for dom::IterationComposite +#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/APZUtils.h" // for CompleteAsyncTransform +#include "mozilla/layers/Compositor.h" // for Compositor +#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 "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 InfallibleTArray +#include "UnitTransforms.h" // for TransformTo +#include "gfxPrefs.h" +#if defined(MOZ_WIDGET_ANDROID) +# include <android/log.h> +# include "mozilla/widget/AndroidCompositorWidget.h" +#endif +#include "GeckoProfiler.h" +#include "FrameUniformityData.h" +#include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch +#include "VsyncSource.h" + +struct nsCSSValueSharedList; + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +static bool +IsSameDimension(dom::ScreenOrientationInternal o1, dom::ScreenOrientationInternal o2) +{ + bool isO1portrait = (o1 == dom::eScreenOrientation_PortraitPrimary || o1 == dom::eScreenOrientation_PortraitSecondary); + bool isO2portrait = (o2 == dom::eScreenOrientation_PortraitPrimary || o2 == dom::eScreenOrientation_PortraitSecondary); + return !(isO1portrait ^ isO2portrait); +} + +static bool +ContentMightReflowOnOrientationChange(const IntRect& rect) +{ + return rect.width != rect.height; +} + +AsyncCompositionManager::AsyncCompositionManager(LayerManagerComposite* aManager) + : mLayerManager(aManager) + , mIsFirstPaint(true) + , mLayersUpdated(false) + , mPaintSyncId(0) + , mReadyForCompose(true) +{ +} + +AsyncCompositionManager::~AsyncCompositionManager() +{ +} + +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()) { + dom::ScreenOrientationInternal chromeOrientation = + mTargetConfig.orientation(); + dom::ScreenOrientationInternal 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()); + } +} + +#ifdef DEBUG +static void +GetBaseTransform(Layer* aLayer, Matrix4x4* aTransform) +{ + // Start with the animated transform if there is one + *aTransform = + (aLayer->AsLayerComposite()->GetShadowTransformSetByAnimation() + ? aLayer->GetLocalTransform() + : aLayer->GetTransform()); +} +#endif + +static void +TransformClipRect(Layer* aLayer, + const ParentLayerToParentLayerMatrix4x4& aTransform) +{ + MOZ_ASSERT(aTransform.Is2D()); + const Maybe<ParentLayerIntRect>& clipRect = aLayer->AsLayerComposite()->GetShadowClipRect(); + if (clipRect) { + ParentLayerIntRect transformed = TransformBy(aTransform, *clipRect); + aLayer->AsLayerComposite()->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->AsLayerComposite()->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->AsLayerComposite()->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->AsLayerComposite()->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); + } + } +} + +#ifdef DEBUG +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; + } +} +#endif + +static LayerPoint +GetLayerFixedMarginsOffset(Layer* aLayer, + const ScreenMargin& aFixedLayerMargins) +{ + // Work out the necessary translation, in root scrollable layer space. + // Because fixed layer margins are stored relative to the root scrollable + // layer, we can just take the difference between these values. + LayerPoint translation; + int32_t sides = aLayer->GetFixedPositionSides(); + + if ((sides & eSideBitsLeftRight) == eSideBitsLeftRight) { + translation.x += (aFixedLayerMargins.left - aFixedLayerMargins.right) / 2; + } else if (sides & eSideBitsRight) { + translation.x -= aFixedLayerMargins.right; + } else if (sides & eSideBitsLeft) { + translation.x += aFixedLayerMargins.left; + } + + if ((sides & eSideBitsTopBottom) == eSideBitsTopBottom) { + translation.y += (aFixedLayerMargins.top - aFixedLayerMargins.bottom) / 2; + } else if (sides & eSideBitsBottom) { + translation.y -= aFixedLayerMargins.bottom; + } else if (sides & eSideBitsTop) { + translation.y += aFixedLayerMargins.top; + } + + return translation; +} + +static gfxFloat +IntervalOverlap(gfxFloat aTranslation, gfxFloat aMin, gfxFloat aMax) +{ + // Determine the amount of overlap between the 1D vector |aTranslation| + // and the interval [aMin, aMax]. + if (aTranslation > 0) { + return std::max(0.0, std::min(aMax, aTranslation) - std::max(aMin, 0.0)); + } else { + return std::min(0.0, std::max(aMin, aTranslation) - std::min(aMax, 0.0)); + } +} + +/** + * 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, FrameMetrics::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, + FrameMetrics::ViewID aFixedWithRespectTo, + Layer* aTransformedLayer, + FrameMetrics::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<FrameMetrics::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, + FrameMetrics::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aFixedLayerMargins, + ClipPartsCache* aClipPartsCache) +{ + // We're going to be inverting |aCurrentTransformForRoot|. + // If it's singular, there's nothing we can do. + if (aCurrentTransformForRoot.IsSingular()) { + return; + } + + Layer* layer = aStartTraversalAt; + bool needsAsyncTransformUnapplied = false; + if (Maybe<FrameMetrics::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, + aTransformScrollId, aPreviousTransformForRoot, + aCurrentTransformForRoot, aFixedLayerMargins, aClipPartsCache); + } + return; + } + + // Insert a translation so that the position of the anchor point is the same + // before and after the change to the transform of aTransformedSubtreeRoot. + + // 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. +#ifdef DEBUG + Matrix4x4 ancestorTransform; + if (layer != aTransformedSubtreeRoot) { + AccumulateLayerTransforms(layer->GetParent(), aTransformedSubtreeRoot, + ancestorTransform); + } + ancestorTransform.NudgeToIntegersFixedEpsilon(); + MOZ_ASSERT(ancestorTransform.IsIdentity()); +#endif + + // 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(); + + // Offset the layer's anchor point to make sure fixed position content + // respects content document fixed position margins. + LayerPoint offsetAnchor = anchor + GetLayerFixedMarginsOffset(layer, aFixedLayerMargins); + + // 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 = + aPreviousTransformForRoot.TransformPoint(offsetAnchor); + LayerPoint transformedAnchor = aCurrentTransformForRoot.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 LayerRect& stickyOuter = layer->GetStickyScrollRangeOuter(); + const LayerRect& 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 = + aPreviousTransformForRoot.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, aTransformScrollId, + aPreviousTransformForRoot, newTransform, aFixedLayerMargins, aClipPartsCache); + } + } + + return; +} + +static void +SampleValue(float aPortion, Animation& aAnimation, + const StyleAnimationValue& aStart, const StyleAnimationValue& aEnd, + const StyleAnimationValue& aLastValue, uint64_t aCurrentIteration, + Animatable* aValue, Layer* aLayer) +{ + NS_ASSERTION(aStart.GetUnit() == aEnd.GetUnit() || + aStart.GetUnit() == StyleAnimationValue::eUnit_None || + aEnd.GetUnit() == StyleAnimationValue::eUnit_None, + "Must have same unit"); + + StyleAnimationValue startValue = aStart; + StyleAnimationValue endValue = aEnd; + // Iteration composition for accumulate + if (static_cast<dom::IterationCompositeOperation> + (aAnimation.iterationComposite()) == + dom::IterationCompositeOperation::Accumulate && + aCurrentIteration > 0) { + // FIXME: Bug 1293492: Add a utility function to calculate both of + // below StyleAnimationValues. + DebugOnly<bool> accumulateResult = + StyleAnimationValue::Accumulate(aAnimation.property(), + startValue, + aLastValue, + aCurrentIteration); + MOZ_ASSERT(accumulateResult, "could not accumulate value"); + accumulateResult = + StyleAnimationValue::Accumulate(aAnimation.property(), + endValue, + aLastValue, + aCurrentIteration); + MOZ_ASSERT(accumulateResult, "could not accumulate value"); + } + + StyleAnimationValue interpolatedValue; + // This should never fail because we only pass transform and opacity values + // to the compositor and they should never fail to interpolate. + DebugOnly<bool> uncomputeResult = + StyleAnimationValue::Interpolate(aAnimation.property(), + startValue, endValue, + aPortion, interpolatedValue); + MOZ_ASSERT(uncomputeResult, "could not uncompute value"); + + if (aAnimation.property() == eCSSProperty_opacity) { + *aValue = interpolatedValue.GetFloatValue(); + return; + } + + nsCSSValueSharedList* interpolatedList = + interpolatedValue.GetCSSValueSharedListValue(); + + TransformData& data = aAnimation.data().get_TransformData(); + nsPoint origin = data.origin(); + // we expect all our transform data to arrive in device pixels + Point3D transformOrigin = data.transformOrigin(); + nsDisplayTransform::FrameTransformProperties props(interpolatedList, + transformOrigin); + + // If our parent layer is a perspective layer, then the offset into reference + // frame coordinates is already on that layer. If not, then we need to ask + // for it to be added here. + uint32_t flags = 0; + if (!aLayer->GetParent() || !aLayer->GetParent()->GetTransformIsPerspective()) { + flags = nsDisplayTransform::OFFSET_BY_ORIGIN; + } + + Matrix4x4 transform = + nsDisplayTransform::GetResultingTransformMatrix(props, origin, + data.appUnitsPerDevPixel(), + flags, &data.bounds()); + + InfallibleTArray<TransformFunction> functions; + functions.AppendElement(TransformMatrix(transform)); + *aValue = functions; +} + +static bool +SampleAnimations(Layer* aLayer, TimeStamp aPoint) +{ + bool activeAnimations = false; + + ForEachNode<ForwardIterator>( + aLayer, + [&activeAnimations, &aPoint] (Layer* layer) + { + AnimationArray& animations = layer->GetAnimations(); + InfallibleTArray<AnimData>& animationData = layer->GetAnimationData(); + + // Process in order, since later animations override earlier ones. + for (size_t i = 0, iEnd = animations.Length(); i < iEnd; ++i) { + Animation& animation = animations[i]; + AnimData& animData = animationData[i]; + + activeAnimations = true; + + MOZ_ASSERT(!animation.startTime().IsNull(), + "Failed to resolve start time of pending animations"); + TimeDuration elapsedDuration = + (aPoint - animation.startTime()).MultDouble(animation.playbackRate()); + TimingParams timing; + timing.mDuration.emplace(animation.duration()); + timing.mDelay = animation.delay(); + timing.mIterations = animation.iterations(); + timing.mIterationStart = animation.iterationStart(); + timing.mDirection = + static_cast<dom::PlaybackDirection>(animation.direction()); + timing.mFill = static_cast<dom::FillMode>(animation.fillMode()); + timing.mFunction = + AnimationUtils::TimingFunctionToComputedTimingFunction( + animation.easingFunction()); + + ComputedTiming computedTiming = + dom::AnimationEffectReadOnly::GetComputedTimingAt( + Nullable<TimeDuration>(elapsedDuration), timing, + animation.playbackRate()); + + if (computedTiming.mProgress.IsNull()) { + continue; + } + + uint32_t segmentIndex = 0; + size_t segmentSize = animation.segments().Length(); + AnimationSegment* segment = animation.segments().Elements(); + while (segment->endPortion() < computedTiming.mProgress.Value() && + segmentIndex < segmentSize - 1) { + ++segment; + ++segmentIndex; + } + + double positionInSegment = + (computedTiming.mProgress.Value() - segment->startPortion()) / + (segment->endPortion() - segment->startPortion()); + + double portion = + ComputedTimingFunction::GetPortion(animData.mFunctions[segmentIndex], + positionInSegment, + computedTiming.mBeforeFlag); + + // interpolate the property + Animatable interpolatedValue; + SampleValue(portion, animation, + animData.mStartValues[segmentIndex], + animData.mEndValues[segmentIndex], + animData.mEndValues.LastElement(), + computedTiming.mCurrentIteration, + &interpolatedValue, layer); + LayerComposite* layerComposite = layer->AsLayerComposite(); + switch (animation.property()) { + case eCSSProperty_opacity: + { + layerComposite->SetShadowOpacity(interpolatedValue.get_float()); + layerComposite->SetShadowOpacitySetByAnimation(true); + break; + } + case eCSSProperty_transform: + { + Matrix4x4 matrix = interpolatedValue.get_ArrayOfTransformFunction()[0].get_TransformMatrix().value(); + if (ContainerLayer* c = layer->AsContainerLayer()) { + matrix.PostScale(c->GetInheritedXScale(), c->GetInheritedYScale(), 1); + } + layerComposite->SetShadowBaseTransform(matrix); + layerComposite->SetShadowTransformSetByAnimation(true); + break; + } + default: + NS_WARNING("Unhandled animated property"); + } + } + }); + return activeAnimations; +} + +static bool +SampleAPZAnimations(const LayerMetricsWrapper& aLayer, TimeStamp aSampleTime) +{ + bool activeAnimations = false; + + ForEachNodePostOrder<ForwardIterator>(aLayer, + [&activeAnimations, &aSampleTime](LayerMetricsWrapper aLayerMetrics) + { + if (AsyncPanZoomController* apzc = aLayerMetrics.GetApzc()) { + apzc->ReportCheckerboard(aSampleTime); + activeAnimations |= apzc->AdvanceAnimations(aSampleTime); + } + } + ); + + return activeAnimations; +} + +void +AsyncCompositionManager::RecordShadowTransforms(Layer* aLayer) +{ + MOZ_ASSERT(gfxPrefs::CollectScrollTransforms()); + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + ForEachNodePostOrder<ForwardIterator>( + aLayer, + [this] (Layer* layer) + { + for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) { + AsyncPanZoomController* apzc = layer->GetAsyncPanZoomController(i); + if (!apzc) { + continue; + } + gfx::Matrix4x4 shadowTransform = layer->AsLayerComposite()->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->AsLayerComposite()->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->AsLayerComposite()->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->AsLayerComposite()->SetShadowClipRect(Some(RoundedOut(rect))); + } +} + +#ifdef MOZ_WIDGET_ANDROID +static void +MoveScrollbarForLayerMargin(Layer* aRoot, FrameMetrics::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->GetScrollbarDirection() == Layer::HORIZONTAL && + aNode->GetScrollbarTargetContainerId() == 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; + + // 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; + + ForEachNode<ForwardIterator>( + aLayer, + [&stackDeferredClips] (Layer* layer) + { + stackDeferredClips.push(Maybe<ParentLayerIntRect>()); + }, + [this, &aOutFoundRoot, &stackDeferredClips, &appliedTransform, &clipPartsCache] (Layer* layer) + { + Maybe<ParentLayerIntRect> clipDeferredFromChildren = stackDeferredClips.top(); + stackDeferredClips.pop(); + LayerToParentLayerMatrix4x4 oldTransform = layer->GetTransformTyped() * + AsyncTransformMatrix(); + + AsyncTransformComponentMatrix combinedAsyncTransform; + bool hasAsyncTransform = false; + 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())); + } + } + + for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) { + AsyncPanZoomController* controller = layer->GetAsyncPanZoomController(i); + if (!controller) { + continue; + } + + hasAsyncTransform = true; + + AsyncTransform asyncTransformWithoutOverscroll = + controller->GetCurrentAsyncTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE); + AsyncTransformComponentMatrix overscrollTransform = + controller->GetOverscrollTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE); + AsyncTransformComponentMatrix asyncTransform = + AsyncTransformComponentMatrix(asyncTransformWithoutOverscroll) + * overscrollTransform; + + if (!layer->IsScrollInfoLayer()) { + controller->MarkAsyncTransformAppliedToContent(); + } + + const ScrollMetadata& scrollMetadata = layer->GetScrollMetadata(i); + const FrameMetrics& metrics = scrollMetadata.GetMetrics(); + +#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(); + CSSToLayerScale geckoZoom = metrics.LayersPixelsPerCSSPixel().ToScaleFactor(); + if (mIsFirstPaint) { + LayerIntPoint scrollOffsetLayerPixels = RoundedToInt(metrics.GetScrollOffset() * geckoZoom); + mContentRect = metrics.GetScrollableRect(); + SetFirstPaintViewport(scrollOffsetLayerPixels, + geckoZoom, + mContentRect); + } else { + ParentLayerPoint scrollOffset = controller->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::RESPECT_FORCE_DISABLE); + // Compute the painted displayport in document-relative CSS pixels. + CSSRect displayPort(metrics.GetCriticalDisplayPort().IsEmpty() ? + metrics.GetDisplayPort() : + metrics.GetCriticalDisplayPort()); + displayPort += metrics.GetScrollOffset(); + SyncFrameMetrics(scrollOffset, + geckoZoom * asyncTransformWithoutOverscroll.mScale, + metrics.GetScrollableRect(), displayPort, geckoZoom, mLayersUpdated, + mPaintSyncId, fixedLayerMargins); + mFixedLayerMargins = fixedLayerMargins; + mLayersUpdated = false; + mPaintSyncId = 0; + } + mIsFirstPaint = false; + } + } +#else + // 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. If we're + // using containerful scrolling, then the clip is not part of the scrolled + // frame and should not be transformed. + if (!scrollMetadata.UsesContainerScrolling()) { + 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(asyncTransformWithoutOverscroll, layer)); + + AlignFixedAndStickyLayers(layer, layer, metrics.GetScrollId(), oldTransform, + transformWithoutOverscrollOrOmta, fixedLayerMargins, + &clipPartsCache); + + // 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); + } + } + } + + 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->AsLayerComposite()->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->GetScrollbarDirection() != Layer::NONE) { + ApplyAsyncTransformToScrollbar(layer); + } + }); + + return appliedTransform; +} + +static bool +LayerIsScrollbarTarget(const LayerMetricsWrapper& aTarget, Layer* aScrollbar) +{ + AsyncPanZoomController* apzc = aTarget.GetApzc(); + if (!apzc) { + return false; + } + const FrameMetrics& metrics = aTarget.Metrics(); + if (metrics.GetScrollId() != aScrollbar->GetScrollbarTargetContainerId()) { + return false; + } + return !aTarget.IsScrollInfoLayer(); +} + +static void +ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar, + const LayerMetricsWrapper& aContent, + bool aScrollbarIsDescendant) +{ + // We only apply the transform if the scroll-target layer has non-container + // children (i.e. when it has some possibly-visible content). This is to + // avoid moving scroll-bars in the situation that only a scroll information + // layer has been built for a scroll frame, as this would result in a + // disparity between scrollbars and visible content. + if (aContent.IsScrollInfoLayer()) { + return; + } + + const FrameMetrics& metrics = aContent.Metrics(); + AsyncPanZoomController* apzc = aContent.GetApzc(); + MOZ_RELEASE_ASSERT(apzc); + + AsyncTransformComponentMatrix asyncTransform = + apzc->GetCurrentAsyncTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE); + + // |asyncTransform| represents the amount by which we have scrolled and + // zoomed since the last paint. Because the scrollbar was sized and positioned based + // on the painted content, we need to adjust it based on asyncTransform so that + // it reflects what the user is actually seeing now. + AsyncTransformComponentMatrix scrollbarTransform; + if (aScrollbar->GetScrollbarDirection() == Layer::VERTICAL) { + const ParentLayerCoord asyncScrollY = asyncTransform._42; + const float asyncZoomY = asyncTransform._22; + + // The scroll thumb needs to be scaled in the direction of scrolling by the + // inverse of the async zoom. This is because zooming in decreases the + // fraction of the whole srollable rect that is in view. + const float yScale = 1.f / asyncZoomY; + + // Note: |metrics.GetZoom()| doesn't yet include the async zoom. + const CSSToParentLayerScale effectiveZoom(metrics.GetZoom().yScale * asyncZoomY); + + // Here we convert the scrollbar thumb ratio into a true unitless ratio by + // dividing out the conversion factor from the scrollframe's parent's space + // to the scrollframe's space. + const float ratio = aScrollbar->GetScrollbarThumbRatio() / + (metrics.GetPresShellResolution() * asyncZoomY); + // The scroll thumb needs to be translated in opposite direction of the + // async scroll. This is because scrolling down, which translates the layer + // content up, should result in moving the scroll thumb down. + ParentLayerCoord yTranslation = -asyncScrollY * ratio; + + // The scroll thumb additionally needs to be translated to compensate for + // the scale applied above. The origin with respect to which the scale is + // applied is the origin of the entire scrollbar, rather than the origin of + // the scroll thumb (meaning, for a vertical scrollbar it's at the top of + // the composition bounds). This means that empty space above the thumb + // is scaled too, effectively translating the thumb. We undo that + // translation here. + // (One can think of the adjustment being done to the translation here as + // a change of basis. We have a method to help with that, + // Matrix4x4::ChangeBasis(), but it wouldn't necessarily make the code + // cleaner in this case). + const CSSCoord thumbOrigin = (metrics.GetScrollOffset().y * ratio); + const CSSCoord thumbOriginScaled = thumbOrigin * yScale; + const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin; + const ParentLayerCoord thumbOriginDeltaPL = thumbOriginDelta * effectiveZoom; + yTranslation -= thumbOriginDeltaPL; + + if (metrics.IsRootContent()) { + // Scrollbar for the root are painted at the same resolution as the + // content. Since the coordinate space we apply this transform in includes + // the resolution, we need to adjust for it as well here. Note that in + // another metrics.IsRootContent() hunk below we apply a + // resolution-cancelling transform which ensures the scroll thumb isn't + // actually rendered at a larger scale. + yTranslation *= metrics.GetPresShellResolution(); + } + + scrollbarTransform.PostScale(1.f, yScale, 1.f); + scrollbarTransform.PostTranslate(0, yTranslation, 0); + } + if (aScrollbar->GetScrollbarDirection() == Layer::HORIZONTAL) { + // See detailed comments under the VERTICAL case. + + const ParentLayerCoord asyncScrollX = asyncTransform._41; + const float asyncZoomX = asyncTransform._11; + + const float xScale = 1.f / asyncZoomX; + + const CSSToParentLayerScale effectiveZoom(metrics.GetZoom().xScale * asyncZoomX); + + const float ratio = aScrollbar->GetScrollbarThumbRatio() / + (metrics.GetPresShellResolution() * asyncZoomX); + ParentLayerCoord xTranslation = -asyncScrollX * ratio; + + const CSSCoord thumbOrigin = (metrics.GetScrollOffset().x * ratio); + const CSSCoord thumbOriginScaled = thumbOrigin * xScale; + const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin; + const ParentLayerCoord thumbOriginDeltaPL = thumbOriginDelta * effectiveZoom; + xTranslation -= thumbOriginDeltaPL; + + if (metrics.IsRootContent()) { + xTranslation *= metrics.GetPresShellResolution(); + } + + scrollbarTransform.PostScale(xScale, 1.f, 1.f); + scrollbarTransform.PostTranslate(xTranslation, 0, 0); + } + + LayerToParentLayerMatrix4x4 transform = + aScrollbar->GetLocalTransformTyped() * scrollbarTransform; + + AsyncTransformComponentMatrix compensation; + // If the scrollbar layer is for the root then the content's resolution + // applies to the scrollbar as well. Since we don't actually want the scroll + // thumb's size to vary with the zoom (other than its length reflecting the + // fraction of the scrollable length that's in view, which is taken care of + // above), we apply a transform to cancel out this resolution. + if (metrics.IsRootContent()) { + compensation = + AsyncTransformComponentMatrix::Scaling( + metrics.GetPresShellResolution(), + metrics.GetPresShellResolution(), + 1.0f).Inverse(); + } + // If the scrollbar layer is a child of the content it is a scrollbar for, + // then we need to adjust for any async transform (including an overscroll + // transform) on the content. This needs to be cancelled out because layout + // positions and sizes the scrollbar on the assumption that there is no async + // transform, and without this adjustment the scrollbar will end up in the + // wrong place. + // + // Note that since the async transform is applied on top of the content's + // regular transform, we need to make sure to unapply the async transform in + // the same coordinate space. This requires applying the content transform + // and then unapplying it after unapplying the async transform. + if (aScrollbarIsDescendant) { + AsyncTransformComponentMatrix overscroll = + apzc->GetOverscrollTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE); + Matrix4x4 asyncUntransform = (asyncTransform * overscroll).Inverse().ToUnknownMatrix(); + Matrix4x4 contentTransform = aContent.GetTransform(); + Matrix4x4 contentUntransform = contentTransform.Inverse(); + + AsyncTransformComponentMatrix asyncCompensation = + ViewAs<AsyncTransformComponentMatrix>( + contentTransform + * asyncUntransform + * contentUntransform); + + compensation = compensation * asyncCompensation; + + // 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, asyncCompensation); + } + } + transform = transform * compensation; + + 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(aLayer, scrollTarget, isAncestor); + } +} + +void +AsyncCompositionManager::GetFrameUniformity(FrameUniformityData* aOutData) +{ + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + mLayerTransformRecorder.EndTest(aOutData); +} + +bool +AsyncCompositionManager::TransformShadowTree(TimeStamp aCurrentFrame, + TimeDuration aVsyncRate, + TransformsToSkip aSkip) +{ + PROFILER_LABEL("AsyncCompositionManager", "TransformShadowTree", + js::ProfileEntry::Category::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. + // Use a previous vsync time to make main thread animations and compositor + // more in sync with each other. + // On the initial frame we use aVsyncTimestamp here so the timestamp on the + // second frame are the same as the initial frame, but it does not matter. + bool wantNextFrame = SampleAnimations(root, + !mPreviousFrameTimeStamp.IsNull() ? + mPreviousFrameTimeStamp : aCurrentFrame); + + // 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 : TimeStamp(); + + if (!(aSkip & TransformsToSkip::APZ)) { + // FIXME/bug 775437: unify this interface with the ~native-fennec + // derived code + // + // Attempt to apply an async content transform to any layer that has + // an async pan zoom controller (which means that it is rendered + // async using Gecko). If this fails, fall back to transforming the + // primary scrollable layer. "Failing" here means that we don't + // find a frame that is async scrollable. Note that the fallback + // code also includes Fennec which is rendered async. Fennec uses + // its own platform-specific async rendering that is done partially + // in Gecko and partially in Java. + bool foundRoot = false; + if (ApplyAsyncContentTransformToTree(root, &foundRoot)) { +#if defined(MOZ_WIDGET_ANDROID) + MOZ_ASSERT(foundRoot); + if (foundRoot && mFixedLayerMargins != ScreenMargin()) { + MoveScrollbarForLayerMargin(root, mRootScrollableId, mFixedLayerMargins); + } +#endif + } + + // Advance APZ animations to the next expected vsync timestamp, if we can + // get it. + TimeStamp nextFrame = aCurrentFrame; + + MOZ_ASSERT(aVsyncRate != TimeDuration::Forever()); + if (aVsyncRate != TimeDuration::Forever()) { + nextFrame += aVsyncRate; + } + + wantNextFrame |= SampleAPZAnimations(LayerMetricsWrapper(root), nextFrame); + } + + LayerComposite* rootComposite = root->AsLayerComposite(); + + gfx::Matrix4x4 trans = rootComposite->GetShadowBaseTransform(); + trans *= gfx::Matrix4x4::From2D(mWorldTransform); + rootComposite->SetShadowBaseTransform(trans); + + if (gfxPrefs::CollectScrollTransforms()) { + RecordShadowTransforms(root); + } + + return wantNextFrame; +} + +void +AsyncCompositionManager::SetFirstPaintViewport(const LayerIntPoint& aOffset, + const CSSToLayerScale& aZoom, + const CSSRect& aCssPageRect) +{ +#ifdef MOZ_WIDGET_ANDROID + widget::AndroidCompositorWidget* widget = + mLayerManager->GetCompositor()->GetWidget()->AsAndroid(); + if (!widget) { + return; + } + widget->SetFirstPaintViewport(aOffset, aZoom, aCssPageRect); +#endif +} + +void +AsyncCompositionManager::SyncFrameMetrics(const ParentLayerPoint& aScrollOffset, + const CSSToParentLayerScale& aZoom, + const CSSRect& aCssPageRect, + const CSSRect& aDisplayPort, + const CSSToLayerScale& aPaintedResolution, + bool aLayersUpdated, + int32_t aPaintSyncId, + ScreenMargin& aFixedLayerMargins) +{ +#ifdef MOZ_WIDGET_ANDROID + widget::AndroidCompositorWidget* widget = + mLayerManager->GetCompositor()->GetWidget()->AsAndroid(); + if (!widget) { + return; + } + widget->SyncFrameMetrics( + aScrollOffset, aZoom, aCssPageRect, aDisplayPort, aPaintedResolution, + aLayersUpdated, aPaintSyncId, aFixedLayerMargins); +#endif +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/AsyncCompositionManager.h b/gfx/layers/composite/AsyncCompositionManager.h new file mode 100644 index 0000000000..4ec49b1a93 --- /dev/null +++ b/gfx/layers/composite/AsyncCompositionManager.h @@ -0,0 +1,283 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_ASYNCCOMPOSITIONMANAGER_H +#define GFX_ASYNCCOMPOSITIONMANAGER_H + +#include "Units.h" // for ScreenPoint, etc +#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/dom/ScreenOrientation.h" // for ScreenOrientation +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#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 AsyncPanZoomController; +class Layer; +class LayerManagerComposite; +class AutoResolveRefLayers; +class CompositorBridgeParent; + +// Represents async transforms consisting of a scale and a translation. +struct AsyncTransform { + explicit AsyncTransform(LayerToParentLayerScale aScale = LayerToParentLayerScale(), + ParentLayerPoint aTranslation = ParentLayerPoint()) + : mScale(aScale) + , mTranslation(aTranslation) + {} + + operator AsyncTransformComponentMatrix() const + { + return AsyncTransformComponentMatrix::Scaling(mScale.scale, mScale.scale, 1) + .PostTranslate(mTranslation.x, mTranslation.y, 0); + } + + bool operator==(const AsyncTransform& rhs) const { + return mTranslation == rhs.mTranslation && mScale == rhs.mScale; + } + + bool operator!=(const AsyncTransform& rhs) const { + return !(*this == rhs); + } + + LayerToParentLayerScale mScale; + ParentLayerPoint mTranslation; +}; + +/** + * 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) + + explicit AsyncCompositionManager(LayerManagerComposite* 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 + * SetFirstPaintViewport on the next frame of composition. + */ + void ForceIsFirstPaint() { mIsFirstPaint = true; } + + // Sample transforms for layer trees. Return true to request + // another animation frame. + enum class TransformsToSkip : uint8_t { NoneOfThem = 0, APZ = 1 }; + bool TransformShadowTree(TimeStamp aCurrentFrame, + TimeDuration aVsyncRate, + TransformsToSkip aSkip = 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, + int32_t aPaintSyncId) + { + mIsFirstPaint |= isFirstPaint; + mLayersUpdated = true; + mTargetConfig = aTargetConfig; + if (aPaintSyncId) { + mPaintSyncId = aPaintSyncId; + } + } + + bool RequiresReorientation(mozilla::dom::ScreenOrientationInternal 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); + + void SetFirstPaintViewport(const LayerIntPoint& aOffset, + const CSSToLayerScale& aZoom, + const CSSRect& aCssPageRect); + void SyncFrameMetrics(const ParentLayerPoint& aScrollOffset, + const CSSToParentLayerScale& aZoom, + const CSSRect& aCssPageRect, + const CSSRect& aDisplayPort, + const CSSToLayerScale& aPaintedResolution, + bool aLayersUpdated, + int32_t aPaintSyncId, + ScreenMargin& aFixedLayerMargins); + + /** + * 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| optionally 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, + FrameMetrics::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aFixedLayerMargins, + ClipPartsCache* aClipPartsCache); + + /** + * 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); + + TargetConfig mTargetConfig; + CSSRect mContentRect; + + RefPtr<LayerManagerComposite> 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; + + int32_t mPaintSyncId; + + bool mReadyForCompose; + + gfx::Matrix mWorldTransform; + LayerTransformRecorder mLayerTransformRecorder; + + TimeStamp mPreviousFrameTimeStamp; + +#ifdef MOZ_WIDGET_ANDROID + // 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. + FrameMetrics::ViewID mRootScrollableId; + ScreenMargin mFixedLayerMargins; +#endif +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(AsyncCompositionManager::TransformsToSkip) + +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..3c8299e05f --- /dev/null +++ b/gfx/layers/composite/CanvasLayerComposite.cpp @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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/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: + mCompositableHost = aHost; + return true; + default: + return false; + } + +} + +Layer* +CanvasLayerComposite::GetLayer() +{ + return this; +} + +void +CanvasLayerComposite::SetLayerManager(LayerManagerComposite* aManager) +{ + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + if (mCompositableHost && mCompositor) { + mCompositableHost->SetCompositor(mCompositor); + } +} + +LayerRenderState +CanvasLayerComposite::GetRenderState() +{ + if (mDestroyed || !mCompositableHost || !mCompositableHost->IsAttached()) { + return LayerRenderState(); + } + return mCompositableHost->GetRenderState(); +} + +void +CanvasLayerComposite::RenderLayer(const IntRect& aClipRect) +{ + 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(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..0042d9027e --- /dev/null +++ b/gfx/layers/composite/CanvasLayerComposite.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 "nsDebug.h" // for NS_RUNTIMEABORT +#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: + // CanvasLayer impl + virtual void Initialize(const Data& aData) override + { + NS_RUNTIMEABORT("Incompatibe surface type"); + } + + virtual LayerRenderState GetRenderState() override; + + virtual bool SetCompositableHost(CompositableHost* aHost) override; + + virtual void Disconnect() override + { + Destroy(); + } + + virtual void SetLayerManager(LayerManagerComposite* aManager) override; + + virtual Layer* GetLayer() override; + virtual void RenderLayer(const gfx::IntRect& aClipRect) override; + + virtual void CleanupResources() override; + + virtual void GenEffectChain(EffectChain& aEffect) override; + + CompositableHost* GetCompositableHost() override; + + virtual LayerComposite* AsLayerComposite() override { return this; } + + void SetBounds(gfx::IntRect aBounds) { mBounds = aBounds; } + + virtual const char* Name() const override { return "CanvasLayerComposite"; } + +protected: + virtual 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..4277a8f70a --- /dev/null +++ b/gfx/layers/composite/ColorLayerComposite.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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/mozalloc.h" // for operator delete, etc + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +void +ColorLayerComposite::RenderLayer(const IntRect& aClipRect) +{ + Rect rect(GetBounds()); + const Matrix4x4& transform = GetEffectiveTransform(); + + RenderWithAllMasks(this, mCompositor, aClipRect, + [&](EffectChain& effectChain, const IntRect& clipRect) { + GenEffectChain(effectChain); + mCompositor->DrawQuad(rect, clipRect, effectChain, GetEffectiveOpacity(), + transform); + }); + + 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..fb019f74f3 --- /dev/null +++ b/gfx/layers/composite/ColorLayerComposite.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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: + ~ColorLayerComposite() + { + MOZ_COUNT_DTOR(ColorLayerComposite); + Destroy(); + } + +public: + // LayerComposite Implementation + virtual Layer* GetLayer() override { return this; } + + virtual void SetLayerManager(LayerManagerComposite* aManager) override + { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + } + + virtual void Destroy() override { mDestroyed = true; } + + virtual void RenderLayer(const gfx::IntRect& aClipRect) override; + virtual void CleanupResources() override {}; + + virtual void GenEffectChain(EffectChain& aEffect) override; + + CompositableHost* GetCompositableHost() override { return nullptr; } + + virtual LayerComposite* AsLayerComposite() override { return this; } + + virtual 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..5ed3d3fe9d --- /dev/null +++ b/gfx/layers/composite/CompositableHost.cpp @@ -0,0 +1,292 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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 "TiledContentHost.h" // for TiledContentHost +#include "mozilla/layers/ImageContainerParent.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/TextureHost.h" // for TextureHost, etc +#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 "mozilla/layers/PCompositableParent.h" +#include "IPDLActor.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +class Compositor; + +/** + * IPDL actor used by CompositableHost to match with its corresponding + * CompositableClient on the content side. + * + * CompositableParent is owned by the IPDL system. It's deletion is triggered + * by either the CompositableChild's deletion, or by the IPDL communication + * going down. + */ +class CompositableParent : public ParentActor<PCompositableParent> +{ +public: + CompositableParent(CompositableParentManager* aMgr, + const TextureInfo& aTextureInfo, + uint64_t aID = 0, + PImageContainerParent* aImageContainer = nullptr) + { + MOZ_COUNT_CTOR(CompositableParent); + mHost = CompositableHost::Create(aTextureInfo); + mHost->SetAsyncID(aID); + if (aID) { + CompositableMap::Set(aID, this); + } + if (aImageContainer) { + mHost->SetImageContainer( + static_cast<ImageContainerParent*>(aImageContainer)); + } + } + + ~CompositableParent() + { + MOZ_COUNT_DTOR(CompositableParent); + CompositableMap::Erase(mHost->GetAsyncID()); + } + + virtual void Destroy() override + { + if (mHost) { + mHost->Detach(nullptr, CompositableHost::FORCE_DETACH); + } + } + + RefPtr<CompositableHost> mHost; +}; + +CompositableHost::CompositableHost(const TextureInfo& aTextureInfo) + : mTextureInfo(aTextureInfo) + , mAsyncID(0) + , mCompositorID(0) + , mCompositor(nullptr) + , mLayer(nullptr) + , mFlashCounter(0) + , mAttached(false) + , mKeepAttached(false) +{ + MOZ_COUNT_CTOR(CompositableHost); +} + +CompositableHost::~CompositableHost() +{ + MOZ_COUNT_DTOR(CompositableHost); +} + +PCompositableParent* +CompositableHost::CreateIPDLActor(CompositableParentManager* aMgr, + const TextureInfo& aTextureInfo, + uint64_t aID, + PImageContainerParent* aImageContainer) +{ + return new CompositableParent(aMgr, aTextureInfo, aID, aImageContainer); +} + +bool +CompositableHost::DestroyIPDLActor(PCompositableParent* aActor) +{ + delete aActor; + return true; +} + +CompositableHost* +CompositableHost::FromIPDLActor(PCompositableParent* aActor) +{ + MOZ_ASSERT(aActor); + return static_cast<CompositableParent*>(aActor)->mHost; +} + +void +CompositableHost::UseTextureHost(const nsTArray<TimedTexture>& aTextures) +{ + if (GetCompositor()) { + for (auto& texture : aTextures) { + texture.mTexture->SetCompositor(GetCompositor()); + } + } +} + +void +CompositableHost::UseComponentAlphaTextures(TextureHost* aTextureOnBlack, + TextureHost* aTextureOnWhite) +{ + MOZ_ASSERT(aTextureOnBlack && aTextureOnWhite); + if (GetCompositor()) { + aTextureOnBlack->SetCompositor(GetCompositor()); + aTextureOnWhite->SetCompositor(GetCompositor()); + } +} + +void +CompositableHost::RemoveTextureHost(TextureHost* aTexture) +{} + +void +CompositableHost::SetCompositor(Compositor* aCompositor) +{ + MOZ_ASSERT(aCompositor); + mCompositor = aCompositor; +} + +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) +{ + 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: + result = new ImageHost(aTextureInfo); + break; + case CompositableType::CONTENT_SINGLE: + result = new ContentHostSingleBuffered(aTextureInfo); + break; + case CompositableType::CONTENT_DOUBLE: + 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(); +} + +void +CompositableHost::ReceivedDestroy(PCompositableParent* aActor) +{ + static_cast<CompositableParent*>(aActor)->RecvDestroy(); +} + +namespace CompositableMap { + +typedef std::map<uint64_t, PCompositableParent*> CompositableMap_t; +static CompositableMap_t* sCompositableMap = nullptr; +bool IsCreated() { + return sCompositableMap != nullptr; +} +PCompositableParent* Get(uint64_t aID) +{ + if (!IsCreated() || aID == 0) { + return nullptr; + } + CompositableMap_t::iterator it = sCompositableMap->find(aID); + if (it == sCompositableMap->end()) { + return nullptr; + } + return it->second; +} +void Set(uint64_t aID, PCompositableParent* aParent) +{ + if (!IsCreated() || aID == 0) { + return; + } + (*sCompositableMap)[aID] = aParent; +} +void Erase(uint64_t aID) +{ + if (!IsCreated() || aID == 0) { + return; + } + CompositableMap_t::iterator it = sCompositableMap->find(aID); + if (it != sCompositableMap->end()) { + sCompositableMap->erase(it); + } +} +void Clear() +{ + if (!IsCreated()) { + return; + } + sCompositableMap->clear(); +} +void Create() +{ + if (sCompositableMap == nullptr) { + sCompositableMap = new CompositableMap_t; + } +} +void Destroy() +{ + Clear(); + delete sCompositableMap; + sCompositableMap = nullptr; +} + +} // namespace CompositableMap + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/CompositableHost.h b/gfx/layers/composite/CompositableHost.h new file mode 100644 index 0000000000..d8a9677321 --- /dev/null +++ b/gfx/layers/composite/CompositableHost.h @@ -0,0 +1,316 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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/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 Compositor; +class ImageContainerParent; +class ThebesBufferData; +class TiledContentHost; +class CompositableParentManager; +class PCompositableParent; +struct EffectChain; + +/** + * 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_REFCOUNTING(CompositableHost) + explicit CompositableHost(const TextureInfo& aTextureInfo); + + static already_AddRefed<CompositableHost> Create(const TextureInfo& aTextureInfo); + + virtual CompositableType GetType() = 0; + + // If base class overrides, it should still call the parent implementation + virtual void SetCompositor(Compositor* aCompositor); + + // composite the contents of this buffer host to the compositor's surface + virtual void Composite(LayerComposite* aLayer, + EffectChain& aEffectChain, + float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr) = 0; + + /** + * Update the content host. + * aUpdated is the region which should be updated + * aUpdatedRegionBack is the region in aNewBackResult which has been updated + */ + virtual bool UpdateThebes(const ThebesBufferData& aData, + const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack, + nsIntRegion* aUpdatedRegionBack) + { + 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 LayerRenderState GetRenderState() = 0; + + virtual gfx::IntSize GetImageSize() const + { + MOZ_ASSERT(false, "Should have been overridden"); + return gfx::IntSize(); + } + + /** + * 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(); + + Compositor* GetCompositor() const + { + return mCompositor; + } + + Layer* GetLayer() const { return mLayer; } + void SetLayer(Layer* aLayer) { mLayer = aLayer; } + + virtual void SetImageContainer(ImageContainerParent* aImageContainer) {} + + virtual TiledContentHost* AsTiledContentHost() { 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, + Compositor* aCompositor, + AttachFlags aFlags = NO_FLAGS) + { + MOZ_ASSERT(aCompositor, "Compositor is required"); + NS_ASSERTION(aFlags & ALLOW_REATTACH || !mAttached, + "Re-attaching compositables must be explicitly authorised"); + SetCompositor(aCompositor); + 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; } + + static void + ReceivedDestroy(PCompositableParent* aActor); + + 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 UseOverlaySource(OverlaySource aOverlay, + const gfx::IntRect& aPictureRect) { } + + 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; + } + + static PCompositableParent* + CreateIPDLActor(CompositableParentManager* mgr, + const TextureInfo& textureInfo, + uint64_t asyncID, + PImageContainerParent* aImageContainer = nullptr); + + static bool DestroyIPDLActor(PCompositableParent* actor); + + static CompositableHost* FromIPDLActor(PCompositableParent* actor); + + uint64_t GetCompositorID() const { return mCompositorID; } + + uint64_t GetAsyncID() const { return mAsyncID; } + + void SetCompositorID(uint64_t aID) { mCompositorID = aID; } + + void SetAsyncID(uint64_t aID) { mAsyncID = 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() {} + +protected: + TextureInfo mTextureInfo; + uint64_t mAsyncID; + uint64_t mCompositorID; + RefPtr<Compositor> mCompositor; + 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; +}; + +/** + * Global CompositableMap, to use in the compositor thread only. + * + * PCompositable and PLayer can, in the case of async textures, be managed by + * different top level protocols. In this case they don't share the same + * communication channel and we can't send an OpAttachCompositable (PCompositable, + * PLayer) message. + * + * In order to attach a layer and the right compositable if the the compositable + * is async, we store references to the async compositables in a CompositableMap + * that is accessed only on the compositor thread. During a layer transaction we + * send the message OpAttachAsyncCompositable(ID, PLayer), and on the compositor + * side we lookup the ID in the map and attach the corresponding compositable to + * the layer. + * + * CompositableMap must be global because the image bridge doesn't have any + * reference to whatever we have created with PLayerTransaction. So, the only way to + * actually connect these two worlds is to have something global that they can + * both query (in the same thread). The map is not allocated the map on the + * stack to avoid the badness of static initialization. + * + * Also, we have a compositor/PLayerTransaction protocol/etc. per layer manager, and the + * ImageBridge is used by all the existing compositors that have a video, so + * there isn't an instance or "something" that lives outside the boudaries of a + * given layer manager on the compositor thread except the image bridge and the + * thread itself. + */ +namespace CompositableMap { + void Create(); + void Destroy(); + PCompositableParent* Get(uint64_t aID); + void Set(uint64_t aID, PCompositableParent* aParent); + void Erase(uint64_t aID); + void Clear(); +} // namespace CompositableMap + + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/ContainerLayerComposite.cpp b/gfx/layers/composite/ContainerLayerComposite.cpp new file mode 100755 index 0000000000..35070cad63 --- /dev/null +++ b/gfx/layers/composite/ContainerLayerComposite.cpp @@ -0,0 +1,698 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ContainerLayerComposite.h" +#include <algorithm> // for min +#include "apz/src/AsyncPanZoomController.h" // for AsyncPanZoomController +#include "FrameMetrics.h" // for FrameMetrics +#include "Units.h" // for LayerRect, LayerPixel, etc +#include "CompositableHost.h" // for CompositableHost +#include "gfxEnv.h" // for gfxEnv +#include "gfxPrefs.h" // for gfxPrefs +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr +#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/Compositor.h" // for Compositor, etc +#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/AsyncCompositionManager.h" // for ViewTransform +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#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 +#ifdef MOZ_ENABLE_PROFILER_SPS +#include "ProfilerMarkers.h" // for ProfilerMarkers +#endif + +#define CULLING_LOG(...) +// #define CULLING_LOG(...) printf_stderr("CULLING: " __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.ToUnknownRegion().GetBounds().TopLeft(); + aManager->GetTextRenderer()->RenderText(ss.str().c_str(), topLeft, + aLayer->GetEffectiveTransform(), 16, + maxWidth); +} + +template<class ContainerT> +static gfx::IntRect ContainerVisibleRect(ContainerT* aContainer) +{ + gfx::IntRect surfaceRect = aContainer->GetLocalVisibleRegion().ToUnknownRegion().GetBounds(); + return surfaceRect; +} + +static void PrintUniformityInfo(Layer* aLayer) +{ +#ifdef MOZ_ENABLE_PROFILER_SPS + if (!profiler_is_active()) { + 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->AsLayerComposite()->GetShadowBaseTransform(); + if (!transform.Is2D()) { + return; + } + + Point translation = transform.As2D().GetTranslation(); + LayerTranslationPayload* payload = new LayerTranslationPayload(aLayer, translation); + PROFILER_MARKER_PAYLOAD("LayerTranslation", payload); +#endif +} + +/* all of the per-layer prepared data we need to maintain */ +struct PreparedLayer +{ + PreparedLayer(Layer *aLayer, RenderTargetIntRect aClipRect) : + mLayer(aLayer), mClipRect(aClipRect) {} + RefPtr<Layer> mLayer; + RenderTargetIntRect mClipRect; +}; + +/* 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) +{ + aContainer->mPrepared = MakeUnique<PreparedData>(); + aContainer->mPrepared->mNeedsSurfaceCopy = false; + + /** + * Determine which layers to draw. + */ + AutoTArray<Layer*, 12> children; + aContainer->SortChildrenBy3DZOrder(children); + + for (uint32_t i = 0; i < children.Length(); i++) { + LayerComposite* layerToRender = static_cast<LayerComposite*>(children.ElementAt(i)->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() && + !layerToRender->NeedToDrawCheckerboarding(nullptr)) { + 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)); + } + + 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, LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect, Layer* aLayer) +{ + Compositor* compositor = aManager->GetCompositor(); + + if (aLayer->GetScrollMetadataCount() < 1) { + return; + } + + AsyncPanZoomController* controller = aLayer->GetAsyncPanZoomController(0); + if (!controller) { + return; + } + + ParentLayerPoint scrollOffset = controller->GetCurrentAsyncScrollOffset(AsyncPanZoomController::RESPECT_FORCE_DISABLE); + + // Options + const int verticalPadding = 10; + const int horizontalPadding = 5; + gfx::Color backgroundColor(0.3f, 0.3f, 0.3f, 0.3f); + gfx::Color tileActiveColor(1, 1, 1, 0.4f); + gfx::Color tileBorderColor(0, 0, 0, 0.1f); + gfx::Color pageBorderColor(0, 0, 0); + gfx::Color criticalDisplayPortColor(1.f, 1.f, 0); + gfx::Color displayPortColor(0, 1.f, 0); + gfx::Color viewPortColor(0, 0, 1.f, 0.3f); + gfx::Color visibilityColor(1.f, 0, 0); + + // Rects + const FrameMetrics& fm = aLayer->GetFrameMetrics(0); + ParentLayerRect compositionBounds = fm.GetCompositionBounds(); + LayerRect scrollRect = fm.GetScrollableRect() * fm.LayersPixelsPerCSSPixel(); + LayerRect viewRect = ParentLayerRect(scrollOffset, compositionBounds.Size()) / LayerToParentLayerScale(1); + LayerRect dp = (fm.GetDisplayPort() + fm.GetScrollOffset()) * fm.LayersPixelsPerCSSPixel(); + Maybe<LayerRect> cdp; + if (!fm.GetCriticalDisplayPort().IsEmpty()) { + cdp = Some((fm.GetCriticalDisplayPort() + fm.GetScrollOffset()) * fm.LayersPixelsPerCSSPixel()); + } + + // Don't render trivial minimap. They can show up from textboxes and other tiny frames. + if (viewRect.width < 64 && viewRect.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()); + + // If enabled, render information about visibility. + if (gfxPrefs::APZMinimapVisibilityEnabled()) { + // Retrieve the APZC scrollable layer guid, which we'll use to get the + // appropriate visibility information from the layer manager. + AsyncPanZoomController* controller = aLayer->GetAsyncPanZoomController(0); + MOZ_ASSERT(controller); + + ScrollableLayerGuid guid = controller->GetGuid(); + + // Get the approximately visible region. + static CSSIntRegion emptyRegion; + CSSIntRegion* visibleRegion = aManager->GetApproximatelyVisibleRegion(guid); + if (!visibleRegion) { + visibleRegion = &emptyRegion; + } + + // Iterate through and draw the rects in the region. + for (CSSIntRegion::RectIterator iterator = visibleRegion->RectIter(); + !iterator.Done(); + iterator.Next()) + { + CSSIntRect rect = iterator.Get(); + LayerRect scaledRect = rect * fm.LayersPixelsPerCSSPixel(); + Rect r = transform.TransformBounds(scaledRect.ToUnknownRect()); + compositor->FillRect(r, visibilityColor, 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 viewport. + r = transform.TransformBounds(viewRect.ToUnknownRect()); + compositor->SlowDrawRect(r, viewPortColor, clipRect, aContainer->GetEffectiveTransform(), 2); +} + + +template<class ContainerT> void +RenderLayers(ContainerT* aContainer, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect) +{ + Compositor* compositor = aManager->GetCompositor(); + + for (size_t i = 0u; i < aContainer->mPrepared->mLayers.Length(); i++) { + PreparedLayer& preparedData = aContainer->mPrepared->mLayers[i]; + LayerComposite* layerToRender = static_cast<LayerComposite*>(preparedData.mLayer->ImplData()); + const RenderTargetIntRect& clipRect = preparedData.mClipRect; + Layer* layer = layerToRender->GetLayer(); + + if (layerToRender->HasStaleCompositor()) { + continue; + } + + if (gfxPrefs::LayersDrawFPS()) { + for (const auto& metadata : layer->GetAllScrollMetadata()) { + if (metadata.IsApzForceDisabled()) { + aManager->DisabledApzWarning(); + break; + } + } + } + + Color color; + if (layerToRender->NeedToDrawCheckerboarding(&color)) { + if (gfxPrefs::APZHighlightCheckerboardedAreas()) { + color = Color(255 / 255.f, 188 / 255.f, 217 / 255.f, 1.f); // "Cotton Candy" + } + // Ideally we would want to intersect the checkerboard region from the APZ with the layer bounds + // and only fill in that area. However the layer bounds takes into account the base translation + // for the painted layer whereas the checkerboard region does not. One does not simply + // intersect areas in different coordinate spaces. So we do this a little more permissively + // and only fill in the background when we know there is checkerboard, which in theory + // should only occur transiently. + gfx::IntRect layerBounds = layer->GetLayerBounds(); + EffectChain effectChain(layer); + effectChain.mPrimaryEffect = new EffectSolidColor(color); + aManager->GetCompositor()->DrawQuad(gfx::Rect(layerBounds.x, layerBounds.y, layerBounds.width, layerBounds.height), + clipRect.ToUnknownRect(), + effectChain, layer->GetEffectiveOpacity(), + layer->GetEffectiveTransform()); + } + + 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 { + layerToRender->RenderLayer(clipRect.ToUnknownRect()); + } + + if (gfxPrefs::UniformityInfo()) { + PrintUniformityInfo(layer); + } + + if (gfxPrefs::DrawLayerInfo()) { + DrawLayerInfo(clipRect, 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; + for (uint32_t i = layer->GetScrollMetadataCount(); i > 0; --i) { + if (layer->GetFrameMetrics(i - 1).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 = layer->GetFrameMetrics(i - 1).GetCompositionBounds(); + aManager->GetCompositor()->DrawDiagnostics(DiagnosticFlags::CONTAINER, + compositionBounds.ToUnknownRect(), + aClipRect.ToUnknownRect(), + asyncTransform * aContainer->GetEffectiveTransform()); + if (AsyncPanZoomController* apzc = layer->GetAsyncPanZoomController(i - 1)) { + asyncTransform = + apzc->GetCurrentAsyncTransformWithOverscroll(AsyncPanZoomController::RESPECT_FORCE_DISABLE).ToUnknownMatrix() + * asyncTransform; + } + } + } + + if (gfxPrefs::APZMinimap()) { + RenderMinimap(aContainer, 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().ToUnknownRegion().GetBounds(); + 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 -= compositor->GetCurrentRenderTarget()->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)); + // 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) +{ + 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) { + aContainer->mPrepared = nullptr; + return; + } + + gfx::Rect visibleRect(aContainer->GetLocalVisibleRegion().ToUnknownRegion().GetBounds()); + 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->DrawQuad(visibleRect, clipRect, effectChain, + container->GetEffectiveOpacity(), + container->GetEffectiveTransform()); + }); + } else { + RenderLayers(aContainer, aManager, RenderTargetIntRect::FromUnknownRect(aClipRect)); + } + aContainer->mPrepared = nullptr; + + // 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 (gfxPrefs::LayersDrawFPS() && aContainer->IsScrollInfoLayer()) { + // 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 (AsyncPanZoomController* apzc = i.GetApzc()) { + if (!apzc->GetAsyncTransformAppliedToContent() + && !AsyncTransformComponentMatrix(apzc->GetCurrentAsyncTransform(AsyncPanZoomController::NORMAL)).IsIdentity()) { + 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. + while (mFirstChild) { + RemoveChild(mFirstChild); + } +} + +void +ContainerLayerComposite::Destroy() +{ + if (!mDestroyed) { + while (mFirstChild) { + static_cast<LayerComposite*>(GetFirstChild()->ImplData())->Destroy(); + RemoveChild(mFirstChild); + } + mDestroyed = true; + } +} + +LayerComposite* +ContainerLayerComposite::GetFirstChildComposite() +{ + if (!mFirstChild) { + return nullptr; + } + return static_cast<LayerComposite*>(mFirstChild->ImplData()); +} + +void +ContainerLayerComposite::RenderLayer(const gfx::IntRect& aClipRect) +{ + ContainerRender(this, mCompositeManager, aClipRect); +} + +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()) { + LayerComposite* layerToCleanup = static_cast<LayerComposite*>(l->ImplData()); + layerToCleanup->CleanupResources(); + } +} + +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->ImplData()); +} + +void +RefLayerComposite::RenderLayer(const gfx::IntRect& aClipRect) +{ + ContainerRender(this, mCompositeManager, aClipRect); +} + +void +RefLayerComposite::Prepare(const RenderTargetIntRect& aClipRect) +{ + ContainerPrepare(this, mCompositeManager, aClipRect); +} + +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..5128b9d80b --- /dev/null +++ b/gfx/layers/composite/ContainerLayerComposite.h @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 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); + template<class ContainerT> + friend void RenderLayers(ContainerT* aContainer, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect); + 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, LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect, Layer* aLayer); +public: + explicit ContainerLayerComposite(LayerManagerComposite *aManager); + +protected: + ~ContainerLayerComposite(); + +public: + // LayerComposite Implementation + virtual Layer* GetLayer() override { return this; } + + virtual void SetLayerManager(LayerManagerComposite* aManager) override + { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + mLastIntermediateSurface = nullptr; + } + + virtual void Destroy() override; + + LayerComposite* GetFirstChildComposite() override; + + virtual void RenderLayer(const gfx::IntRect& aClipRect) override; + virtual void Prepare(const RenderTargetIntRect& aClipRect) override; + + virtual void ComputeEffectiveTransforms(const gfx::Matrix4x4& aTransformToSurface) override + { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } + + virtual void CleanupResources() override; + + virtual LayerComposite* AsLayerComposite() override { return this; } + + // container layers don't use a compositable + CompositableHost* GetCompositableHost() override { return nullptr; } + + // If the layer is marked as scale-to-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. + virtual float GetPostXScale() const override { + if (mScaleToResolution) { + return mPostXScale * mPresShellResolution; + } + return mPostXScale; + } + virtual float GetPostYScale() const override { + if (mScaleToResolution) { + return mPostYScale * mPresShellResolution; + } + return mPostYScale; + } + + virtual 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); + template<class ContainerT> + friend void RenderLayers(ContainerT* aContainer, + LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect); + 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: + ~RefLayerComposite(); + +public: + /** LayerOGL implementation */ + Layer* GetLayer() override { return this; } + + virtual void SetLayerManager(LayerManagerComposite* aManager) override + { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + mLastIntermediateSurface = nullptr; + } + + void Destroy() override; + + LayerComposite* GetFirstChildComposite() override; + + virtual void RenderLayer(const gfx::IntRect& aClipRect) override; + virtual void Prepare(const RenderTargetIntRect& aClipRect) override; + + virtual void ComputeEffectiveTransforms(const gfx::Matrix4x4& aTransformToSurface) override + { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } + + virtual void CleanupResources() override; + + virtual LayerComposite* AsLayerComposite() override { return this; } + + // ref layers don't use a compositable + CompositableHost* GetCompositableHost() override { return nullptr; } + + virtual 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..b1d92a6c9e --- /dev/null +++ b/gfx/layers/composite/ContentHost.cpp @@ -0,0 +1,491 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/ContentHost.h" +#include "LayersLogging.h" // for AppendToString +#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() +{ +} + +void +ContentHostTexture::Composite(LayerComposite* aLayer, + EffectChain& aEffectChain, + float aOpacity, + const gfx::Matrix4x4& aTransform, + const SamplingFilter aSamplingFilter, + const IntRect& aClipRect, + const nsIntRegion* aVisibleRegion) +{ + 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, + GetRenderState()); + 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); + GetCompositor()->DrawQuad(rect, aClipRect, aEffectChain, aOpacity, aTransform); + if (usingTiles) { + DiagnosticFlags diagnostics = DiagnosticFlags::CONTENT | DiagnosticFlags::BIGIMAGE; + if (iterOnWhite) { + diagnostics |= DiagnosticFlags::COMPONENT_ALPHA; + } + GetCompositor()->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; + } + GetCompositor()->DrawDiagnostics(diagnostics, nsIntRegion(mBufferRect), aClipRect, + aTransform, mFlashCounter); +} + +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::SetCompositor(Compositor* aCompositor) +{ + ContentHostBase::SetCompositor(aCompositor); + if (mTextureHost) { + mTextureHost->SetCompositor(aCompositor); + } + if (mTextureHostOnWhite) { + mTextureHostOnWhite->SetCompositor(aCompositor); + } +} + +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, + nsIntRegion* aUpdatedRegionBack) +{ + aUpdatedRegionBack->SetEmpty(); + + 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, + nsIntRegion* aUpdatedRegionBack) +{ + if (!mTextureHost) { + mInitialised = false; + + *aUpdatedRegionBack = aUpdated; + 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(); + + *aUpdatedRegionBack = aUpdated; + + // 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(); + + AppendToString(aStream, mBufferRect, " [buffer-rect=", "]"); + AppendToString(aStream, mBufferRotation, " [buffer-rotation=", "]"); + if (PaintWillResample()) { + aStream << " [paint-will-resample]"; + } + + if (mTextureHost) { + nsAutoCString pfx(aPrefix); + pfx += " "; + + aStream << "\n"; + mTextureHost->PrintInfo(aStream, pfx.get()); + } +} + + +LayerRenderState +ContentHostTexture::GetRenderState() +{ + if (!mTextureHost) { + return LayerRenderState(); + } + + LayerRenderState result = mTextureHost->GetRenderState(); + + if (mBufferRotation != nsIntPoint()) { + result.mFlags |= LayerRenderStateFlags::BUFFER_ROTATION; + } + result.SetOffset(GetOriginOffset()); + return result; +} + +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, + GetRenderState()); +} + +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..9b74984154 --- /dev/null +++ b/gfx/layers/composite/ContentHost.h @@ -0,0 +1,228 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 RotatedContentBuffer, 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/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for SamplingFilter +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#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 "nsDebug.h" // for NS_RUNTIMEABORT +#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, + nsIntRegion* aUpdatedRegionBack) = 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) { } + +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 RotatedContentBuffer::ContentType ContentType; + typedef RotatedContentBuffer::PaintState PaintState; + + explicit ContentHostBase(const TextureInfo& aTextureInfo); + virtual ~ContentHostBase(); + +protected: + virtual nsIntPoint GetOriginOffset() + { + return mBufferRect.TopLeft() - mBufferRotation; + } + + + 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) + { } + + virtual void Composite(LayerComposite* aLayer, + EffectChain& aEffectChain, + float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr) override; + + virtual void SetCompositor(Compositor* aCompositor) override; + + virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override; + + virtual void Dump(std::stringstream& aStream, + const char* aPrefix="", + bool aDumpHtml=false) override; + + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + virtual void UseTextureHost(const nsTArray<TimedTexture>& aTextures) override; + virtual void UseComponentAlphaTextures(TextureHost* aTextureOnBlack, + TextureHost* aTextureOnWhite) override; + + virtual 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; + } + virtual void Unlock() override { + MOZ_ASSERT(mLocked); + mTextureHost->Unlock(); + if (mTextureHostOnWhite) { + mTextureHostOnWhite->Unlock(); + } + mLocked = false; + } + + LayerRenderState GetRenderState() override; + + virtual 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() {} + + virtual CompositableType GetType() { return CompositableType::CONTENT_DOUBLE; } + + virtual bool UpdateThebes(const ThebesBufferData& aData, + const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack, + nsIntRegion* aUpdatedRegionBack); + +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() {} + + virtual CompositableType GetType() { return CompositableType::CONTENT_SINGLE; } + + virtual bool UpdateThebes(const ThebesBufferData& aData, + const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack, + nsIntRegion* aUpdatedRegionBack); +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/FPSCounter.cpp b/gfx/layers/composite/FPSCounter.cpp new file mode 100644 index 0000000000..02ffc4b2c1 --- /dev/null +++ b/gfx/layers/composite/FPSCounter.cpp @@ -0,0 +1,450 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stddef.h> // for size_t +#include "Units.h" // for ScreenIntRect +#include "gfxRect.h" // for gfxRect +#include "gfxPrefs.h" // for gfxPrefs +#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 "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() { } + +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 >= 0); + 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 >= 0); + 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 (!gfxPrefs::FPSPrintHistogram()) { + 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) +{ + int length = 0; + const int kBufferLength = 512; + char buffer[kBufferLength]; + + for (std::map<int, int>::iterator iter = aHistogram.begin(); + iter != aHistogram.end(); iter++) + { + int fps = iter->first; + int count = iter->second; + + length += snprintf(buffer + length, kBufferLength - length, + "FPS: %d = %d. ", fps, count); + NS_ASSERTION(length >= kBufferLength, "Buffer overrun while printing FPS histogram."); + } + + 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 (!gfxPrefs::WriteFPSToFile()) { + 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(NS_LITERAL_STRING("fps.txt")); + } else { + resultFile->Append(NS_LITERAL_STRING("txn.txt")); + } + + 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); + + nsAutoCString path; + rv = resultFile->GetNativePath(path); + NS_ENSURE_SUCCESS(rv, rv); + + printf_stderr("Wrote FPS data to file: %s\n", path.get()); + return NS_OK; +} + +FPSState::FPSState() + : mCompositionFps("Compositor") + , mTransactionFps("LayerTransactions") +{ +} + +// Size of the builtin font. +static const float FontHeight = 7.f; +static const float FontWidth = 4.f; + +// Scale the font when drawing it to the viewport for better readability. +static const float FontScaleX = 2.f; +static const float FontScaleY = 3.f; + +static void DrawDigits(unsigned int aValue, + int aOffsetX, int aOffsetY, + Compositor* aCompositor, + EffectChain& aEffectChain) +{ + if (aValue > 999) { + aValue = 999; + } + + unsigned int divisor = 100; + float textureWidth = FontWidth * 10; + gfx::Float opacity = 1; + gfx::Matrix4x4 transform; + transform.PreScale(FontScaleX, FontScaleY, 1); + + for (size_t n = 0; n < 3; ++n) { + unsigned int digit = aValue % (divisor * 10) / divisor; + divisor /= 10; + + RefPtr<TexturedEffect> texturedEffect = static_cast<TexturedEffect*>(aEffectChain.mPrimaryEffect.get()); + texturedEffect->mTextureCoords = Rect(float(digit * FontWidth) / textureWidth, 0, FontWidth / textureWidth, 1.0f); + + Rect drawRect = Rect(aOffsetX + n * FontWidth, aOffsetY, FontWidth, FontHeight); + IntRect clipRect = IntRect(0, 0, 300, 100); + aCompositor->DrawQuad(drawRect, clipRect, aEffectChain, opacity, transform); + } +} + +void FPSState::DrawFPS(TimeStamp aNow, + int aOffsetX, int aOffsetY, + unsigned int aFillRatio, + Compositor* aCompositor) +{ + if (!mFPSTextureSource) { + const char *text = + " " + " XXX XX XXX XXX X X XXX XXX XXX XXX XXX" + " X X X X X X X X X X X X X X" + " X X X XXX XXX XXX XXX XXX X XXX XXX" + " X X X X X X X X X X X X X" + " XXX XXX XXX XXX X XXX XXX X XXX X" + " "; + + // Convert the text encoding above to RGBA. + int w = FontWidth * 10; + int h = FontHeight; + uint32_t* buf = (uint32_t *) malloc(w * h * sizeof(uint32_t)); + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + uint32_t purple = 0xfff000ff; + uint32_t white = 0xffffffff; + buf[i * w + j] = (text[i * w + j] == ' ') ? purple : white; + } + } + + int bytesPerPixel = 4; + RefPtr<DataSourceSurface> fpsSurface = Factory::CreateWrappingDataSourceSurface( + reinterpret_cast<uint8_t*>(buf), w * bytesPerPixel, IntSize(w, h), SurfaceFormat::B8G8R8A8); + mFPSTextureSource = aCompositor->CreateDataTextureSource(); + mFPSTextureSource->Update(fpsSurface); + } + + EffectChain effectChain; + effectChain.mPrimaryEffect = CreateTexturedEffect(SurfaceFormat::B8G8R8A8, + mFPSTextureSource, + SamplingFilter::POINT, + true); + + unsigned int fps = unsigned(mCompositionFps.AddFrameAndGetFps(aNow)); + unsigned int txnFps = unsigned(mTransactionFps.GetFPS(aNow)); + + DrawDigits(fps, aOffsetX + 0, aOffsetY, aCompositor, effectChain); + DrawDigits(txnFps, aOffsetX + FontWidth * 4, aOffsetY, aCompositor, effectChain); + DrawDigits(aFillRatio, aOffsetX + FontWidth * 8, aOffsetY, aCompositor, effectChain); +} + +} // 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..7a59267fce --- /dev/null +++ b/gfx/layers/composite/FPSCounter.h @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 { +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; +}; + +struct FPSState { + FPSState(); + void DrawFPS(TimeStamp, int offsetX, int offsetY, unsigned, Compositor* aCompositor); + void NotifyShadowTreeTransaction() { + mTransactionFps.AddFrame(TimeStamp::Now()); + } + + FPSCounter mCompositionFps; + FPSCounter mTransactionFps; + +private: + RefPtr<DataTextureSource> mFPSTextureSource; +}; + +} // 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..f8c73cc990 --- /dev/null +++ b/gfx/layers/composite/FontData.h @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// 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. +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 }; +const unsigned int sTextureWidth = 256; +const unsigned int sTextureHeight = 256; +const unsigned int sCellWidth = 16; +const unsigned int sCellHeight = 16; diff --git a/gfx/layers/composite/FrameUniformityData.cpp b/gfx/layers/composite/FrameUniformityData.cpp new file mode 100644 index 0000000000..e8bab6adbc --- /dev/null +++ b/gfx/layers/composite/FrameUniformityData.cpp @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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; +} + +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 (auto iter = mFrameTransforms.begin(); iter != mFrameTransforms.end(); ++iter) { + uintptr_t layer = iter->first; + float uniformity = CalculateFrameUniformity(layer); + + std::pair<uintptr_t,float> result(layer, uniformity); + aOutData->mUniformities.insert(result); + } + + Reset(); +} + +LayerTransforms* +LayerTransformRecorder::GetLayerTransforms(uintptr_t aLayer) +{ + if (!mFrameTransforms.count(aLayer)) { + LayerTransforms* newTransform = new LayerTransforms(); + std::pair<uintptr_t, LayerTransforms*> newLayer(aLayer, newTransform); + mFrameTransforms.insert(newLayer); + } + + return mFrameTransforms.find(aLayer)->second; +} + +void +LayerTransformRecorder::Reset() +{ + for (auto iter = mFrameTransforms.begin(); iter != mFrameTransforms.end(); ++iter) { + LayerTransforms* layerTransforms = iter->second; + delete layerTransforms; + } + + mFrameTransforms.clear(); +} + +float +LayerTransformRecorder::CalculateFrameUniformity(uintptr_t aLayer) +{ + LayerTransforms* layerTransform = GetLayerTransforms(aLayer); + float yUniformity = -1; + if (!layerTransform->mTransforms.IsEmpty()) { + 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 (auto iter = mUniformities.begin(); iter != mUniformities.end(); ++iter) { + uintptr_t layerAddr = iter->first; + float uniformity = iter->second; + + // 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..3ff1bdc4a8 --- /dev/null +++ b/gfx/layers/composite/FrameUniformityData.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_FrameUniformityData_h_ +#define mozilla_layers_FrameUniformityData_h_ + +#include "ipc/IPCMessageUtils.h" +#include "js/TypeDecls.h" +#include "mozilla/RefPtr.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() {} + + gfx::Point GetAverage(); + gfx::Point GetStdDev(); + + // 60 fps * 5 seconds worth of data + AutoTArray<gfx::Point, 300> mTransforms; +}; + +class LayerTransformRecorder { +public: + LayerTransformRecorder() {} + ~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); + std::map<uintptr_t,LayerTransforms*> 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..1e539d8ac9 --- /dev/null +++ b/gfx/layers/composite/GPUVideoTextureHost.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GPUVideoTextureHost.h" +#include "mozilla/dom/VideoDecoderManagerParent.h" +#include "ImageContainer.h" +#include "mozilla/layers/VideoBridgeParent.h" + +namespace mozilla { +namespace layers { + +GPUVideoTextureHost::GPUVideoTextureHost(TextureFlags aFlags, + const SurfaceDescriptorGPUVideo& aDescriptor) + : TextureHost(aFlags) +{ + MOZ_COUNT_CTOR(GPUVideoTextureHost); + mWrappedTextureHost = VideoBridgeParent::GetSingleton()->LookupTexture(aDescriptor.handle()); +} + +GPUVideoTextureHost::~GPUVideoTextureHost() +{ + MOZ_COUNT_DTOR(GPUVideoTextureHost); +} + +bool +GPUVideoTextureHost::Lock() +{ + if (!mWrappedTextureHost) { + return false; + } + return mWrappedTextureHost->Lock(); +} + +bool +GPUVideoTextureHost::BindTextureSource(CompositableTextureSourceRef& aTexture) +{ + if (!mWrappedTextureHost) { + return false; + } + return mWrappedTextureHost->BindTextureSource(aTexture); +} + +Compositor* +GPUVideoTextureHost::GetCompositor() +{ + if (!mWrappedTextureHost) { + return nullptr; + } + return mWrappedTextureHost->GetCompositor(); +} + +void +GPUVideoTextureHost::SetCompositor(Compositor* aCompositor) +{ + if (mWrappedTextureHost) { + mWrappedTextureHost->SetCompositor(aCompositor); + } +} + +YUVColorSpace +GPUVideoTextureHost::GetYUVColorSpace() const +{ + if (mWrappedTextureHost) { + return mWrappedTextureHost->GetYUVColorSpace(); + } + return YUVColorSpace::UNKNOWN; +} + +gfx::IntSize +GPUVideoTextureHost::GetSize() const +{ + if (!mWrappedTextureHost) { + return gfx::IntSize(); + } + return mWrappedTextureHost->GetSize(); +} + +gfx::SurfaceFormat +GPUVideoTextureHost::GetFormat() const +{ + if (!mWrappedTextureHost) { + return gfx::SurfaceFormat::UNKNOWN; + } + return mWrappedTextureHost->GetFormat(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/GPUVideoTextureHost.h b/gfx/layers/composite/GPUVideoTextureHost.h new file mode 100644 index 0000000000..fd6bdc3fb2 --- /dev/null +++ b/gfx/layers/composite/GPUVideoTextureHost.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_GPUVIDEOTEXTUREHOST_H +#define MOZILLA_GFX_GPUVIDEOTEXTUREHOST_H + +#include "mozilla/layers/TextureHost.h" + +namespace mozilla { +namespace layers { + +class GPUVideoTextureHost : public TextureHost +{ +public: + GPUVideoTextureHost(TextureFlags aFlags, + const SurfaceDescriptorGPUVideo& aDescriptor); + virtual ~GPUVideoTextureHost(); + + virtual void DeallocateDeviceData() override {} + + virtual void SetCompositor(Compositor* aCompositor) override; + + virtual Compositor* GetCompositor() override; + + virtual bool Lock() override; + + virtual gfx::SurfaceFormat GetFormat() const override; + + virtual bool BindTextureSource(CompositableTextureSourceRef& aTexture) override; + + virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override + { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + virtual YUVColorSpace GetYUVColorSpace() const override; + + virtual gfx::IntSize GetSize() const override; + +#ifdef MOZ_LAYERS_HAVE_LOG + virtual const char* Name() override { return "GPUVideoTextureHost"; } +#endif + +protected: + RefPtr<TextureHost> mWrappedTextureHost; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_GPUVIDEOTEXTUREHOST_H diff --git a/gfx/layers/composite/ImageHost.cpp b/gfx/layers/composite/ImageHost.cpp new file mode 100644 index 0000000000..b1d77924b7 --- /dev/null +++ b/gfx/layers/composite/ImageHost.cpp @@ -0,0 +1,739 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageHost.h" + +#include "LayersLogging.h" // for AppendToString +#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/ImageContainerParent.h" +#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 + +#define BIAS_TIME_MS 1.0 + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +class ISurfaceAllocator; + +ImageHost::ImageHost(const TextureInfo& aTextureInfo) + : CompositableHost(aTextureInfo) + , mImageContainer(nullptr) + , mLastFrameID(-1) + , mLastProducerID(-1) + , mBias(BIAS_NONE) + , mLocked(false) +{} + +ImageHost::~ImageHost() +{ + SetImageContainer(nullptr); +} + +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(); + } + + mImages.SwapElements(newImages); + newImages.Clear(); + + // 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 (mImages.Length() == 1) { + SetCurrentTextureHost(mImages[0].mTextureHost); + } + + // 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 (GetCompositor() && mLastFrameID >= 0) { + for (size_t i = 0; i < mImages.Length(); ++i) { + bool frameComesAfter = mImages[i].mFrameID > mLastFrameID || + mImages[i].mProducerID != mLastProducerID; + if (frameComesAfter && !mImages[i].mTimeStamp.IsNull()) { + GetCompositor()->CompositeUntil(mImages[i].mTimeStamp + + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); + break; + } + } + } +} + +void +ImageHost::SetCurrentTextureHost(TextureHost* aTexture) +{ + if (aTexture == mCurrentTextureHost.get()) { + return; + } + + bool swapTextureSources = !!mCurrentTextureHost && !!mCurrentTextureSource + && 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); + + for (int32_t i = mImages.Length() - 1; i >= 0; --i) { + if (mImages[i].mTextureHost == aTexture) { + aTexture->UnbindTextureSource(); + mImages.RemoveElementAt(i); + } + } +} + +void +ImageHost::UseOverlaySource(OverlaySource aOverlay, + const gfx::IntRect& aPictureRect) +{ + if (ImageHostOverlay::IsValid(aOverlay)) { + if (!mImageHostOverlay) { + mImageHostOverlay = new ImageHostOverlay(); + } + mImageHostOverlay->UseOverlaySource(aOverlay, aPictureRect); + } else { + mImageHostOverlay = nullptr; + } +} + +static TimeStamp +GetBiasedTime(const TimeStamp& aInput, ImageHost::Bias aBias) +{ + switch (aBias) { + case ImageHost::BIAS_NEGATIVE: + return aInput - TimeDuration::FromMilliseconds(BIAS_TIME_MS); + case ImageHost::BIAS_POSITIVE: + return aInput + TimeDuration::FromMilliseconds(BIAS_TIME_MS); + default: + return aInput; + } +} + +static ImageHost::Bias +UpdateBias(const TimeStamp& aCompositionTime, + const TimeStamp& aCompositedImageTime, + const TimeStamp& aNextImageTime, // may be null + ImageHost::Bias aBias) +{ + if (aCompositedImageTime.IsNull()) { + return ImageHost::BIAS_NONE; + } + TimeDuration threshold = TimeDuration::FromMilliseconds(1.0); + if (aCompositionTime - aCompositedImageTime < threshold && + aCompositionTime - aCompositedImageTime > -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. + return ImageHost::BIAS_NEGATIVE; + } + if (!aNextImageTime.IsNull() && + aNextImageTime - aCompositionTime < threshold && + aNextImageTime - aCompositionTime > -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. + // 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. + return ImageHost::BIAS_POSITIVE; + } + return ImageHost::BIAS_NONE; +} + +int ImageHost::ChooseImageIndex() const +{ + if (!GetCompositor() || mImages.IsEmpty()) { + return -1; + } + TimeStamp now = GetCompositor()->GetCompositionTime(); + + if (now.IsNull()) { + // Not in a composition, so just return the last image we composited + // (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 -1; + } + + uint32_t result = 0; + while (result + 1 < mImages.Length() && + GetBiasedTime(mImages[result + 1].mTimeStamp, mBias) <= now) { + ++result; + } + return result; +} + +const ImageHost::TimedImage* ImageHost::ChooseImage() const +{ + int index = ChooseImageIndex(); + return index >= 0 ? &mImages[index] : nullptr; +} + +ImageHost::TimedImage* ImageHost::ChooseImage() +{ + int index = ChooseImageIndex(); + return index >= 0 ? &mImages[index] : nullptr; +} + +TextureHost* +ImageHost::GetAsTextureHost(IntRect* aPictureRect) +{ + TimedImage* img = ChooseImage(); + if (img) { + SetCurrentTextureHost(img->mTextureHost); + } + if (aPictureRect && img) { + *aPictureRect = img->mPictureRect; + } + return img ? img->mTextureHost.get() : nullptr; +} + +void ImageHost::Attach(Layer* aLayer, + Compositor* aCompositor, + AttachFlags aFlags) +{ + CompositableHost::Attach(aLayer, aCompositor, aFlags); + for (auto& img : mImages) { + if (GetCompositor()) { + img.mTextureHost->SetCompositor(GetCompositor()); + } + img.mTextureHost->Updated(); + } +} + +void +ImageHost::Composite(LayerComposite* aLayer, + EffectChain& aEffectChain, + float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion) +{ + if (!GetCompositor()) { + // should only happen when a tab is dragged to another window and + // async-video is still sending frames but we haven't attached the + // set the new compositor yet. + return; + } + + if (mImageHostOverlay) { + mImageHostOverlay->Composite(GetCompositor(), + mFlashCounter, + aLayer, + aEffectChain, + aOpacity, + aTransform, + aSamplingFilter, + aClipRect, + aVisibleRegion); + mBias = BIAS_NONE; + return; + } + + int imageIndex = ChooseImageIndex(); + if (imageIndex < 0) { + return; + } + + if (uint32_t(imageIndex) + 1 < mImages.Length()) { + GetCompositor()->CompositeUntil(mImages[imageIndex + 1].mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); + } + + TimedImage* img = &mImages[imageIndex]; + img->mTextureHost->SetCompositor(GetCompositor()); + SetCurrentTextureHost(img->mTextureHost); + + { + 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, + GetRenderState()); + if (!effect) { + return; + } + + if (!GetCompositor()->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; + } + + if (mLastFrameID != img->mFrameID || mLastProducerID != img->mProducerID) { + if (mImageContainer) { + aLayer->GetLayerManager()-> + AppendImageCompositeNotification(ImageCompositeNotification( + mImageContainer, nullptr, + img->mTimeStamp, GetCompositor()->GetCompositionTime(), + img->mFrameID, img->mProducerID)); + } + mLastFrameID = img->mFrameID; + mLastProducerID = img->mProducerID; + } + 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.y = effect->mTextureCoords.YMost(); + effect->mTextureCoords.height = -effect->mTextureCoords.height; + } + GetCompositor()->DrawQuad(rect, aClipRect, aEffectChain, + aOpacity, aTransform); + GetCompositor()->DrawDiagnostics(diagnosticFlags | DiagnosticFlags::BIGIMAGE, + rect, aClipRect, aTransform, mFlashCounter); + } while (it->NextTile()); + it->EndBigImageIteration(); + // layer border + GetCompositor()->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.y = effect->mTextureCoords.YMost(); + effect->mTextureCoords.height = -effect->mTextureCoords.height; + } + + GetCompositor()->DrawQuad(pictureRect, aClipRect, aEffectChain, + aOpacity, aTransform); + GetCompositor()->DrawDiagnostics(diagnosticFlags, + pictureRect, aClipRect, + aTransform, mFlashCounter); + } + } + + // Update mBias last. This can change which frame ChooseImage(Index) would + // return, and we don't want to do that until we've finished compositing + // since callers of ChooseImage(Index) assume the same image will be chosen + // during a given composition. This must happen after autoLock's + // destructor! + mBias = UpdateBias( + GetCompositor()->GetCompositionTime(), mImages[imageIndex].mTimeStamp, + uint32_t(imageIndex + 1) < mImages.Length() ? + mImages[imageIndex + 1].mTimeStamp : TimeStamp(), + mBias); +} + +void +ImageHost::SetCompositor(Compositor* aCompositor) +{ + if (mCompositor != aCompositor) { + for (auto& img : mImages) { + img.mTextureHost->SetCompositor(aCompositor); + } + } + if (mImageHostOverlay) { + mImageHostOverlay->SetCompositor(aCompositor); + } + CompositableHost::SetCompositor(aCompositor); +} + +void +ImageHost::PrintInfo(std::stringstream& aStream, const char* aPrefix) +{ + aStream << aPrefix; + aStream << nsPrintfCString("ImageHost (0x%p)", this).get(); + + nsAutoCString pfx(aPrefix); + pfx += " "; + for (auto& img : mImages) { + aStream << "\n"; + img.mTextureHost->PrintInfo(aStream, pfx.get()); + AppendToString(aStream, img.mPictureRect, " [picture-rect=", "]"); + } + + if (mImageHostOverlay) { + mImageHostOverlay->PrintInfo(aStream, aPrefix); + } +} + +void +ImageHost::Dump(std::stringstream& aStream, + const char* aPrefix, + bool aDumpHtml) +{ + for (auto& img : mImages) { + aStream << aPrefix; + aStream << (aDumpHtml ? "<ul><li>TextureHost: " + : "TextureHost: "); + DumpTextureHost(aStream, img.mTextureHost); + aStream << (aDumpHtml ? " </li></ul> " : " "); + } +} + +LayerRenderState +ImageHost::GetRenderState() +{ + if (mImageHostOverlay) { + return mImageHostOverlay->GetRenderState(); + } + + TimedImage* img = ChooseImage(); + if (img) { + SetCurrentTextureHost(img->mTextureHost); + return img->mTextureHost->GetRenderState(); + } + return LayerRenderState(); +} + +already_AddRefed<gfx::DataSourceSurface> +ImageHost::GetAsSurface() +{ + if (mImageHostOverlay) { + return nullptr; + } + + TimedImage* img = ChooseImage(); + if (img) { + return img->mTextureHost->GetAsSurface(); + } + return nullptr; +} + +bool +ImageHost::Lock() +{ + MOZ_ASSERT(!mLocked); + 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 +{ + if (mImageHostOverlay) { + return mImageHostOverlay->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) +{ + 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, + GetRenderState()); +} + +void +ImageHost::SetImageContainer(ImageContainerParent* aImageContainer) +{ + if (mImageContainer) { + mImageContainer->mImageHosts.RemoveElement(this); + } + mImageContainer = aImageContainer; + if (mImageContainer) { + mImageContainer->mImageHosts.AppendElement(this); + } +} + +ImageHostOverlay::ImageHostOverlay() +{ + MOZ_COUNT_CTOR(ImageHostOverlay); +} + +ImageHostOverlay::~ImageHostOverlay() +{ + if (mCompositor) { + mCompositor->RemoveImageHostOverlay(this); + } + MOZ_COUNT_DTOR(ImageHostOverlay); +} + +/* static */ bool +ImageHostOverlay::IsValid(OverlaySource aOverlay) +{ + if ((aOverlay.handle().type() == OverlayHandle::Tint32_t) && + aOverlay.handle().get_int32_t() != INVALID_OVERLAY) { + return true; + } else if (aOverlay.handle().type() == OverlayHandle::TGonkNativeHandle) { + return true; + } + return false; +} + +void +ImageHostOverlay::SetCompositor(Compositor* aCompositor) +{ + if (mCompositor && (mCompositor != aCompositor)) { + mCompositor->RemoveImageHostOverlay(this); + } + if (aCompositor) { + aCompositor->AddImageHostOverlay(this); + } + mCompositor = aCompositor; +} + +void +ImageHostOverlay::Composite(Compositor* aCompositor, + uint32_t aFlashCounter, + LayerComposite* aLayer, + EffectChain& aEffectChain, + float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion) +{ + MOZ_ASSERT(mCompositor == aCompositor); + + if (mOverlay.handle().type() == OverlayHandle::Tnull_t) { + return; + } + + Color hollow(0.0f, 0.0f, 0.0f, 0.0f); + aEffectChain.mPrimaryEffect = new EffectSolidColor(hollow); + aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE] = new EffectBlendMode(CompositionOp::OP_SOURCE); + + gfx::Rect rect; + gfx::Rect clipRect(aClipRect.x, aClipRect.y, + aClipRect.width, aClipRect.height); + rect.SetRect(mPictureRect.x, mPictureRect.y, + mPictureRect.width, mPictureRect.height); + + aCompositor->DrawQuad(rect, aClipRect, aEffectChain, aOpacity, aTransform); + aCompositor->DrawDiagnostics(DiagnosticFlags::IMAGE | DiagnosticFlags::BIGIMAGE, + rect, aClipRect, aTransform, aFlashCounter); +} + +LayerRenderState +ImageHostOverlay::GetRenderState() +{ + LayerRenderState state; + return state; +} + +void +ImageHostOverlay::UseOverlaySource(OverlaySource aOverlay, + const nsIntRect& aPictureRect) +{ + mOverlay = aOverlay; + mPictureRect = aPictureRect; +} + +IntSize +ImageHostOverlay::GetImageSize() const +{ + return IntSize(mPictureRect.width, mPictureRect.height); +} + +void +ImageHostOverlay::PrintInfo(std::stringstream& aStream, const char* aPrefix) +{ + aStream << aPrefix; + aStream << nsPrintfCString("ImageHostOverlay (0x%p)", this).get(); + + AppendToString(aStream, mPictureRect, " [picture-rect=", "]"); + + if (mOverlay.handle().type() == OverlayHandle::Tint32_t) { + nsAutoCString pfx(aPrefix); + pfx += " "; + aStream << nsPrintfCString("Overlay: %d", mOverlay.handle().get_int32_t()).get(); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ImageHost.h b/gfx/layers/composite/ImageHost.h new file mode 100644 index 0000000000..b8d23afee9 --- /dev/null +++ b/gfx/layers/composite/ImageHost.h @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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/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, 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 ImageContainerParent; +class ImageHostOverlay; + +/** + * ImageHost. Works with ImageClientSingle and ImageClientBuffered + */ +class ImageHost : public CompositableHost +{ +public: + explicit ImageHost(const TextureInfo& aTextureInfo); + ~ImageHost(); + + virtual CompositableType GetType() override { return mTextureInfo.mCompositableType; } + + virtual void Composite(LayerComposite* aLayer, + EffectChain& aEffectChain, + float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr) override; + + virtual void UseTextureHost(const nsTArray<TimedTexture>& aTextures) override; + + virtual void RemoveTextureHost(TextureHost* aTexture) override; + + virtual void UseOverlaySource(OverlaySource aOverlay, + const gfx::IntRect& aPictureRect) override; + + virtual TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) override; + + virtual void Attach(Layer* aLayer, + Compositor* aCompositor, + AttachFlags aFlags = NO_FLAGS) override; + + virtual void SetCompositor(Compositor* aCompositor) override; + + virtual void SetImageContainer(ImageContainerParent* aImageContainer) override; + + gfx::IntSize GetImageSize() const override; + + virtual LayerRenderState GetRenderState() override; + + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + virtual void Dump(std::stringstream& aStream, + const char* aPrefix = "", + bool aDumpHtml = false) override; + + virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override; + + virtual bool Lock() override; + + virtual void Unlock() override; + + virtual already_AddRefed<TexturedEffect> GenEffect(const gfx::SamplingFilter aSamplingFilter) override; + + void SetCurrentTextureHost(TextureHost* aTexture); + + virtual void CleanupResources() override; + + 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; } + + 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, + }; + + bool IsOpaque(); + +protected: + struct TimedImage { + CompositableTextureHostRef mTextureHost; + TimeStamp mTimeStamp; + gfx::IntRect mPictureRect; + int32_t mFrameID; + int32_t mProducerID; + }; + + // 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; + + /** + * 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() const; + TimedImage* ChooseImage(); + int ChooseImageIndex() const; + + nsTArray<TimedImage> mImages; + // Weak reference, will be null if mImageContainer has been destroyed. + ImageContainerParent* mImageContainer; + int32_t mLastFrameID; + int32_t mLastProducerID; + /** + * Bias to apply to the next frame. + */ + Bias mBias; + + bool mLocked; + + RefPtr<ImageHostOverlay> mImageHostOverlay; +}; + +/** + * ImageHostOverlay handles OverlaySource compositing + */ +class ImageHostOverlay { +protected: + virtual ~ImageHostOverlay(); + +public: + NS_INLINE_DECL_REFCOUNTING(ImageHostOverlay) + ImageHostOverlay(); + + static bool IsValid(OverlaySource aOverlay); + + void SetCompositor(Compositor* aCompositor); + + virtual void Composite(Compositor* aCompositor, + uint32_t aFlashCounter, + LayerComposite* aLayer, + EffectChain& aEffectChain, + float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion); + virtual LayerRenderState GetRenderState(); + virtual void UseOverlaySource(OverlaySource aOverlay, + const gfx::IntRect& aPictureRect); + virtual gfx::IntSize GetImageSize() const; + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix); +protected: + RefPtr<Compositor> mCompositor; + gfx::IntRect mPictureRect; + OverlaySource mOverlay; +}; + +} // 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..bac9f37909 --- /dev/null +++ b/gfx/layers/composite/ImageLayerComposite.cpp @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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/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: + mImageHost = static_cast<ImageHost*>(aHost); + return true; + default: + return false; + } +} + +void +ImageLayerComposite::Disconnect() +{ + Destroy(); +} + +LayerRenderState +ImageLayerComposite::GetRenderState() +{ + if (mImageHost && mImageHost->IsAttached()) { + return mImageHost->GetRenderState(); + } + return LayerRenderState(); +} + +Layer* +ImageLayerComposite::GetLayer() +{ + return this; +} + +void +ImageLayerComposite::SetLayerManager(LayerManagerComposite* aManager) +{ + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + if (mImageHost) { + mImageHost->SetCompositor(mCompositor); + } +} + +void +ImageLayerComposite::RenderLayer(const IntRect& aClipRect) +{ + 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->SetCompositor(mCompositor); + mImageHost->Composite(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.width != 0.0 && sourceRect.height != 0.0) { + 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; + } + + if (mScaleMode == ScaleMode::STRETCH) { + 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..445917a756 --- /dev/null +++ b/gfx/layers/composite/ImageLayerComposite.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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: + virtual LayerRenderState GetRenderState() override; + + virtual void Disconnect() override; + + virtual bool SetCompositableHost(CompositableHost* aHost) override; + + virtual Layer* GetLayer() override; + + virtual void SetLayerManager(LayerManagerComposite* aManager) override; + + virtual void RenderLayer(const gfx::IntRect& aClipRect) override; + + virtual void ComputeEffectiveTransforms(const mozilla::gfx::Matrix4x4& aTransformToSurface) override; + + virtual void CleanupResources() override; + + CompositableHost* GetCompositableHost() override; + + virtual void GenEffectChain(EffectChain& aEffect) override; + + virtual LayerComposite* AsLayerComposite() override { return this; } + + virtual const char* Name() const override { return "ImageLayerComposite"; } + + virtual bool IsOpaque() override; + + virtual nsIntRegion GetFullyRenderedRegion() override; + +protected: + virtual 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..800478d559 --- /dev/null +++ b/gfx/layers/composite/LayerManagerComposite.cpp @@ -0,0 +1,1451 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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 "Composer2D.h" // for Composer2D +#include "CompositableHost.h" // for CompositableHost +#include "ContainerLayerComposite.h" // for ContainerLayerComposite, etc +#include "FPSCounter.h" // for FPSState, FPSCounter +#include "FrameMetrics.h" // for FrameMetrics +#include "GeckoProfiler.h" // for profiler_set_frame_number, etc +#include "ImageLayerComposite.h" // for ImageLayerComposite +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "LayerScope.h" // for LayerScope Tool +#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 "gfxPrefs.h" // for gfxPrefs +#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/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/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/widget/CompositorWidget.h" // for WidgetRenderingContext +#include "ipc/CompositorBench.h" // for CompositorBench +#include "ipc/ShadowLayerUtils.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, NS_RUNTIMEABORT, etc +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsIWidget.h" // for nsIWidget +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion, etc +#ifdef MOZ_WIDGET_ANDROID +#include <android/log.h> +#include <android/native_window.h> +#endif +#if defined(MOZ_WIDGET_ANDROID) +#include "opengl/CompositorOGL.h" +#include "GLContextEGL.h" +#include "GLContextProvider.h" +#include "ScopedGLHelpers.h" +#endif +#include "GeckoProfiler.h" +#include "TextRenderer.h" // for TextRenderer +#include "mozilla/layers/CompositorBridgeParent.h" +#include "TreeTraversal.h" // for ForEachNode + +#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? +} + +/** + * LayerManagerComposite + */ +LayerManagerComposite::LayerManagerComposite(Compositor* aCompositor) +: mWarningLevel(0.0f) +, mUnusedApzTransformWarning(false) +, mDisabledApzWarning(false) +, mCompositor(aCompositor) +, mInTransaction(false) +, mIsCompositorReady(false) +, mDebugOverlayWantsNextFrame(false) +, mGeometryChanged(true) +, mLastFrameMissedHWC(false) +, mWindowOverlayChanged(false) +, mLastPaintTime(TimeDuration::Forever()) +, mRenderStartTime(TimeStamp::Now()) +{ + mTextRenderer = new TextRenderer(aCompositor); + MOZ_ASSERT(aCompositor); + +#ifdef USE_SKIA + mPaintCounter = nullptr; +#endif +} + +LayerManagerComposite::~LayerManagerComposite() +{ + Destroy(); +} + + +void +LayerManagerComposite::Destroy() +{ + if (!mDestroyed) { + mCompositor->GetWidget()->CleanupWindowEffects(); + if (mRoot) { + RootLayer()->Destroy(); + } + mRoot = nullptr; + mClonedLayerTreeProperties = 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() +{ + 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; + mCompositor->SetTargetContext(aTarget, aRect); + mTarget = aTarget; + mTargetBounds = aRect; +} + +/** + * Get accumulated transform of from the context creating layer to the + * given layer. + */ +static Matrix4x4 +GetAccTransformIn3DContext(Layer* aLayer) { + Matrix4x4 transform = aLayer->GetLocalTransform(); + for (Layer* layer = aLayer->GetParent(); + layer && layer->Extend3DContext(); + layer = layer->GetParent()) { + transform = transform * layer->GetLocalTransform(); + } + return transform; +} + +void +LayerManagerComposite::PostProcessLayers(Layer* aLayer, + nsIntRegion& aOpaqueRegion, + LayerIntRegion& aVisibleRegion, + const Maybe<ParentLayerIntRect>& aClipFromAncestors) +{ + if (aLayer->Extend3DContext()) { + // For layers participating 3D rendering context, their visible + // region should be empty (invisible), so we pass through them + // without doing anything. + + // Direct children of the establisher may have a clip, becaue the + // item containing it; ex. of nsHTMLScrollFrame, may give it one. + Maybe<ParentLayerIntRect> layerClip = + aLayer->AsLayerComposite()->GetShadowClipRect(); + Maybe<ParentLayerIntRect> ancestorClipForChildren = + IntersectMaybeRects(layerClip, aClipFromAncestors); + MOZ_ASSERT(!layerClip || !aLayer->Combines3DTransformWithAncestors(), + "Only direct children of the establisher could have a clip"); + + for (Layer* child = aLayer->GetLastChild(); + child; + child = child->GetPrevSibling()) { + PostProcessLayers(child, aOpaqueRegion, aVisibleRegion, + ancestorClipForChildren); + } + 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 = GetAccTransformIn3DContext(aLayer); + Matrix transform2d; + Maybe<IntPoint> integerTranslation; + // 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 (transform.Is2D(&transform2d)) { + if (transform2d.IsIntegerTranslation()) { + integerTranslation = Some(IntPoint::Truncate(transform2d.GetTranslation())); + localOpaque = aOpaqueRegion; + localOpaque.MoveBy(-*integerTranslation); + } + } + + // Compute a clip that's the combination of our layer clip with the clip + // from our ancestors. + LayerComposite* composite = aLayer->AsLayerComposite(); + 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); + + // 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). + Maybe<LayerIntRect> insideClip; + if (outsideClip && !transform.HasPerspectiveComponent()) { + Matrix4x4 inverse = transform; + if (inverse.Invert()) { + Maybe<LayerRect> insideClipFloat = + UntransformBy(ViewAs<ParentLayerToLayerMatrix4x4>(inverse), + ParentLayerRect(*outsideClip), + LayerRect::MaxIntRect()); + if (insideClipFloat) { + insideClipFloat->RoundOut(); + LayerIntRect insideClipInt; + if (insideClipFloat->ToIntRect(&insideClipInt)) { + insideClip = Some(insideClipInt); + } + } + } + } + + Maybe<ParentLayerIntRect> ancestorClipForChildren; + if (insideClip) { + ancestorClipForChildren = + Some(ViewAs<ParentLayerPixel>(*insideClip, PixelCastJustification::MovingDownToChildren)); + } + + // 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()) { + PostProcessLayers(child, localOpaque, descendantsVisibleRegion, ancestorClipForChildren); + 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); + if (const Maybe<ParentLayerIntRect>& clipRect = composite->GetShadowClipRect()) { + visibleParentSpace.AndWith(*clipRect); + } + aVisibleRegion.OrWith(ViewAs<LayerPixel>(visibleParentSpace, + PixelCastJustification::MovingDownToChildren)); + + // If we have a simple transform, then we can add our opaque area into + // aOpaqueRegion. + if (integerTranslation && + !aLayer->HasMaskLayers() && + aLayer->IsOpaqueForVisibility()) { + if (aLayer->IsOpaque()) { + localOpaque.OrWith(composite->GetFullyRenderedRegion()); + } + localOpaque.MoveBy(*integerTranslation); + if (layerClip) { + localOpaque.AndWith(layerClip->ToUnknownRect()); + } + aOpaqueRegion.OrWith(localOpaque); + } +} + +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(); + + 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. + mCompositor->SetCompositionTime(aTimeStamp); + + if (mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW)) { + MOZ_ASSERT(!aTimeStamp.IsNull()); + UpdateAndRender(); + mCompositor->FlushPendingNotifyNotUsed(); + } else { + // Modified the layer tree. + mGeometryChanged = true; + } + + mCompositor->ClearTargetContext(); + mTarget = nullptr; + +#ifdef MOZ_LAYERS_HAVE_LOG + Log(); + MOZ_LAYERS_LOG(("]----- EndTransaction")); +#endif +} + +void +LayerManagerComposite::UpdateAndRender() +{ + nsIntRegion invalid; + bool didEffectiveTransforms = false; + + nsIntRegion opaque; + LayerIntRegion visible; + PostProcessLayers(mRoot, opaque, visible, Nothing()); + + if (mClonedLayerTreeProperties) { + // Effective transforms are needed by ComputeDifferences(). + mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4()); + didEffectiveTransforms = true; + + // 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 = mClonedLayerTreeProperties->ComputeDifferences(mRoot, nullptr, &mGeometryChanged); + + 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. Instead we accumulate the layers + // damage region for the next window composite. + mInvalidRegion.Or(mInvalidRegion, changed); + } else { + invalid = Move(changed); + } + } + + if (mTarget) { + invalid.Or(invalid, mTargetBounds); + } else { + // If we didn't have a previous layer tree, invalidate the entire render + // area. + if (!mClonedLayerTreeProperties) { + invalid.Or(invalid, mRenderBounds); + } + + // Add any additional invalid rects from the window manager or previous + // damage computed during ComposeToTarget(). + invalid.Or(invalid, mInvalidRegion); + mInvalidRegion.SetEmpty(); + } + + if (invalid.IsEmpty() && !mWindowOverlayChanged) { + // Composition requested, but nothing has changed. Don't do any work. + mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot()); + 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. + InvalidateDebugOverlay(invalid, mRenderBounds); + + if (!didEffectiveTransforms) { + // 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()); + } + + Render(invalid, opaque); +#if defined(MOZ_WIDGET_ANDROID) + RenderToPresentationSurface(); +#endif + mGeometryChanged = false; + mWindowOverlayChanged = false; + + // Update cached layer tree information. + mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot()); +} + +already_AddRefed<DrawTarget> +LayerManagerComposite::CreateOptimalMaskDrawTarget(const IntSize &aSize) +{ + NS_RUNTIMEABORT("Should only be called on the drawing side"); + return nullptr; +} + +already_AddRefed<PaintedLayer> +LayerManagerComposite::CreatePaintedLayer() +{ + MOZ_ASSERT(gIsGtest, "Unless you're testing the compositor using GTest," + "this should only be called on the drawing side"); + RefPtr<PaintedLayer> layer = new PaintedLayerComposite(this); + return layer.forget(); +} + +already_AddRefed<ContainerLayer> +LayerManagerComposite::CreateContainerLayer() +{ + MOZ_ASSERT(gIsGtest, "Unless you're testing the compositor using GTest," + "this should only be called on the drawing side"); + RefPtr<ContainerLayer> layer = new ContainerLayerComposite(this); + return layer.forget(); +} + +already_AddRefed<ImageLayer> +LayerManagerComposite::CreateImageLayer() +{ + NS_RUNTIMEABORT("Should only be called on the drawing side"); + return nullptr; +} + +already_AddRefed<ColorLayer> +LayerManagerComposite::CreateColorLayer() +{ + MOZ_ASSERT(gIsGtest, "Unless you're testing the compositor using GTest," + "this should only be called on the drawing side"); + RefPtr<ColorLayer> layer = new ColorLayerComposite(this); + return layer.forget(); +} + +already_AddRefed<CanvasLayer> +LayerManagerComposite::CreateCanvasLayer() +{ + NS_RUNTIMEABORT("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); +} + +#ifdef MOZ_PROFILING +// Only build the QR feature when profiling to avoid bloating +// our data section. +// This table was generated using qrencode and is a binary +// encoding of the qrcodes 0-255. +#include "qrcode_table.h" +#endif + +void +LayerManagerComposite::InvalidateDebugOverlay(nsIntRegion& aInvalidRegion, const IntRect& aBounds) +{ + bool drawFps = gfxPrefs::LayersDrawFPS(); + bool drawFrameCounter = gfxPrefs::DrawFrameCounter(); + bool drawFrameColorBars = gfxPrefs::CompositorDrawColorBars(); + + if (drawFps || drawFrameCounter) { + aInvalidRegion.Or(aInvalidRegion, nsIntRect(0, 0, 256, 256)); + } + if (drawFrameColorBars) { + aInvalidRegion.Or(aInvalidRegion, nsIntRect(0, 0, 10, aBounds.height)); + } + +#ifdef USE_SKIA + bool drawPaintTimes = gfxPrefs::AlwaysPaint(); + 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 uint16_t sFrameCount = 0; +void +LayerManagerComposite::RenderDebugOverlay(const IntRect& aBounds) +{ + bool drawFps = gfxPrefs::LayersDrawFPS(); + bool drawFrameCounter = gfxPrefs::DrawFrameCounter(); + bool drawFrameColorBars = gfxPrefs::CompositorDrawColorBars(); + + TimeStamp now = TimeStamp::Now(); + + if (drawFps) { + if (!mFPS) { + mFPS = MakeUnique<FPSState>(); + } + + float alpha = 1; +#ifdef ANDROID + // Draw a translation delay warning overlay + int width; + int border; + if (!mWarnTime.IsNull() && (now - mWarnTime).ToMilliseconds() < kVisualWarningDuration) { + EffectChain effects; + + // Black blorder + border = 4; + width = 6; + effects.mPrimaryEffect = new EffectSolidColor(gfx::Color(0, 0, 0, 1)); + mCompositor->DrawQuad(gfx::Rect(border, border, aBounds.width - 2 * border, width), + aBounds, effects, alpha, gfx::Matrix4x4()); + mCompositor->DrawQuad(gfx::Rect(border, aBounds.height - border - width, aBounds.width - 2 * border, width), + aBounds, effects, alpha, gfx::Matrix4x4()); + mCompositor->DrawQuad(gfx::Rect(border, border + width, width, aBounds.height - 2 * border - width * 2), + aBounds, effects, alpha, gfx::Matrix4x4()); + mCompositor->DrawQuad(gfx::Rect(aBounds.width - border - width, border + width, width, aBounds.height - 2 * border - 2 * width), + aBounds, effects, alpha, gfx::Matrix4x4()); + + // Content + border = 5; + width = 4; + effects.mPrimaryEffect = new EffectSolidColor(gfx::Color(1, 1.f - mWarningLevel, 0, 1)); + mCompositor->DrawQuad(gfx::Rect(border, border, aBounds.width - 2 * border, width), + aBounds, effects, alpha, gfx::Matrix4x4()); + mCompositor->DrawQuad(gfx::Rect(border, aBounds.height - border - width, aBounds.width - 2 * border, width), + aBounds, effects, alpha, gfx::Matrix4x4()); + mCompositor->DrawQuad(gfx::Rect(border, border + width, width, aBounds.height - 2 * border - width * 2), + aBounds, effects, alpha, gfx::Matrix4x4()); + mCompositor->DrawQuad(gfx::Rect(aBounds.width - border - width, border + width, width, aBounds.height - 2 * border - 2 * width), + aBounds, effects, alpha, gfx::Matrix4x4()); + SetDebugOverlayWantsNextFrame(true); + } +#endif + + float fillRatio = mCompositor->GetFillRatio(); + mFPS->DrawFPS(now, drawFrameColorBars ? 10 : 1, 2, unsigned(fillRatio), mCompositor); + + 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::Color(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::Color(1, 1, 0, 1)); + mCompositor->DrawQuad(gfx::Rect(aBounds.width - 40, 0, 20, 20), + aBounds, effects, alpha, gfx::Matrix4x4()); + + mDisabledApzWarning = false; + SetDebugOverlayWantsNextFrame(true); + } + + + // Each frame is invalidate by the previous frame for simplicity + } else { + mFPS = nullptr; + } + + 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()); + + } + +#ifdef MOZ_PROFILING + if (drawFrameCounter) { + profiler_set_frame_number(sFrameCount); + const char* qr = sQRCodeTable[sFrameCount%256]; + + int size = 21; + int padding = 2; + float opacity = 1.0; + const uint16_t bitWidth = 5; + gfx::IntRect clip(0,0, bitWidth*640, bitWidth*640); + + // Draw the white squares at once + gfx::Color bitColor(1.0, 1.0, 1.0, 1.0); + EffectChain effects; + effects.mPrimaryEffect = new EffectSolidColor(bitColor); + int totalSize = (size + padding * 2) * bitWidth; + mCompositor->DrawQuad(gfx::Rect(0, 0, totalSize, totalSize), + clip, + effects, + opacity, + gfx::Matrix4x4()); + + // Draw a black square for every bit set in qr[index] + effects.mPrimaryEffect = new EffectSolidColor(gfx::Color(0, 0, 0, 1.0)); + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + // Select the right bit from the binary encoding + int currBit = 128 >> ((x + y * 21) % 8); + int i = (x + y * 21) / 8; + if (qr[i] & currBit) { + mCompositor->DrawQuad(gfx::Rect(bitWidth * (x + padding), + bitWidth * (y + padding), + bitWidth, bitWidth), + clip, + effects, + opacity, + gfx::Matrix4x4()); + } + } + } + + } +#endif + + if (drawFrameColorBars || drawFrameCounter) { + // We intentionally overflow at 2^16. + sFrameCount++; + } + +#ifdef USE_SKIA + bool drawPaintTimes = gfxPrefs::AlwaysPaint(); + if (drawPaintTimes) { + DrawPaintTimes(mCompositor); + } +#endif +} + +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(gfxPrefs::LayersEffectInvert() || + gfxPrefs::LayersEffectGrayscale() || + gfxPrefs::LayersEffectContrast() != 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.x == 0 && rect.y == 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()); +} + +// Used to clear the 'mLayerComposited' flag at the beginning of each Render(). +static void +ClearLayerFlags(Layer* aLayer) { + ForEachNode<ForwardIterator>( + aLayer, + [] (Layer* layer) + { + if (layer->AsLayerComposite()) { + layer->AsLayerComposite()->SetLayerComposited(false); + } + }); +} + +void +LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion, const nsIntRegion& aOpaqueRegion) +{ + PROFILER_LABEL("LayerManagerComposite", "Render", + js::ProfileEntry::Category::GRAPHICS); + + if (mDestroyed || !mCompositor || mCompositor->IsDestroyed()) { + NS_WARNING("Call on destroyed layer manager"); + return; + } + + 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 = gfxPrefs::LayersEffectInvert(); + bool grayscaleVal = gfxPrefs::LayersEffectGrayscale(); + float contrastVal = gfxPrefs::LayersEffectContrast(); + bool haveLayerEffects = (invertVal || grayscaleVal || contrastVal != 0.0); + + // Set LayerScope begin/end frame + LayerScopeAutoFrame frame(PR_Now()); + + // Dump to console + if (gfxPrefs::LayersDump()) { + this->Dump(/* aSorted= */true); + } else if (profiler_feature_active("layersdump")) { + std::stringstream ss; + Dump(ss); + profiler_log(ss.str().c_str()); + } + + // 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(Move(packet)); + } + + /** Our more efficient but less powerful alter ego, if one is available. */ + RefPtr<Composer2D> composer2D; + composer2D = mCompositor->GetWidget()->GetComposer2D(); + + // We can't use composert2D if we have layer effects + if (!mTarget && !haveLayerEffects && + gfxPrefs::Composer2DCompositionEnabled() && + composer2D && composer2D->HasHwc() && composer2D->TryRenderWithHwc(mRoot, + mCompositor->GetWidget()->RealWidget(), + mGeometryChanged, + mCompositor->HasImageHostOverlays())) + { + LayerScope::SetHWComposed(); + if (mFPS) { + double fps = mFPS->mCompositionFps.AddFrameAndGetFps(TimeStamp::Now()); + if (gfxPrefs::LayersDrawFPS()) { + printf_stderr("HWComposer: FPS is %g\n", fps); + } + } + mCompositor->EndFrameForExternalComposition(Matrix()); + mLastFrameMissedHWC = false; + return; + } else if (!mTarget && !haveLayerEffects) { + mLastFrameMissedHWC = !!composer2D; + } + + mozilla::widget::WidgetRenderingContext widgetContext; +#if defined(XP_MACOSX) + widgetContext.mLayerManager = this; +#elif defined(MOZ_WIDGET_ANDROID) + widgetContext.mCompositor = GetCompositor(); +#endif + + { + PROFILER_LABEL("LayerManagerComposite", "PreRender", + js::ProfileEntry::Category::GRAPHICS); + + if (!mCompositor->GetWidget()->PreRender(&widgetContext)) { + return; + } + } + + ParentLayerIntRect clipRect; + IntRect bounds(mRenderBounds.x, mRenderBounds.y, mRenderBounds.width, mRenderBounds.height); + IntRect actualBounds; + + CompositorBench(mCompositor, bounds); + + MOZ_ASSERT(mRoot->GetOpacity() == 1); +#if defined(MOZ_WIDGET_ANDROID) + LayerMetricsWrapper wrapper = GetRootContentLayer(); + if (wrapper) { + mCompositor->SetClearColor(wrapper.Metadata().GetBackgroundColor()); + } else { + mCompositor->SetClearColorToDefault(); + } +#endif + if (mRoot->GetClipRect()) { + clipRect = *mRoot->GetClipRect(); + IntRect rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height); + mCompositor->BeginFrame(aInvalidRegion, &rect, bounds, aOpaqueRegion, nullptr, &actualBounds); + } else { + gfx::IntRect rect; + mCompositor->BeginFrame(aInvalidRegion, nullptr, bounds, aOpaqueRegion, &rect, &actualBounds); + clipRect = ParentLayerIntRect(rect.x, rect.y, rect.width, rect.height); + } + + if (actualBounds.IsEmpty()) { + mCompositor->GetWidget()->PostRender(&widgetContext); + return; + } + + // Allow widget to render a custom background. + mCompositor->GetWidget()->DrawWindowUnderlay( + &widgetContext, LayoutDeviceIntRect::FromUnknownRect(actualBounds)); + + RefPtr<CompositingRenderTarget> previousTarget; + if (haveLayerEffects) { + previousTarget = PushGroupForLayerEffects(); + } else { + mTwoPassTmpTarget = nullptr; + } + + // Render our layers. + RootLayer()->Prepare(ViewAs<RenderTargetPixel>(clipRect, PixelCastJustification::RenderTargetIsParentLayerForRoot)); + RootLayer()->RenderLayer(clipRect.ToUnknownRect()); + + if (!mRegionToClear.IsEmpty()) { + for (auto iter = mRegionToClear.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& r = iter.Get(); + mCompositor->ClearRect(Rect(r.x, r.y, r.width, r.height)); + } + } + + if (mTwoPassTmpTarget) { + MOZ_ASSERT(haveLayerEffects); + PopGroupForLayerEffects(previousTarget, clipRect.ToUnknownRect(), + grayscaleVal, invertVal, contrastVal); + } + + // Allow widget to render a custom foreground. + mCompositor->GetWidget()->DrawWindowOverlay( + &widgetContext, LayoutDeviceIntRect::FromUnknownRect(actualBounds)); + + // Debugging + RenderDebugOverlay(actualBounds); + + { + PROFILER_LABEL("LayerManagerComposite", "EndFrame", + js::ProfileEntry::Category::GRAPHICS); + + mCompositor->EndFrame(); + + // Call after EndFrame() + mCompositor->SetDispAcquireFence(mRoot); + } + + if (composer2D) { + composer2D->Render(mCompositor->GetWidget()->RealWidget()); + } + + mCompositor->GetWidget()->PostRender(&widgetContext); + + RecordFrame(); +} + +#if defined(MOZ_WIDGET_ANDROID) +class ScopedCompositorProjMatrix { +public: + ScopedCompositorProjMatrix(CompositorOGL* aCompositor, const Matrix4x4& aProjMatrix): + mCompositor(aCompositor), + mOriginalProjMatrix(mCompositor->GetProjMatrix()) + { + mCompositor->SetProjMatrix(aProjMatrix); + } + + ~ScopedCompositorProjMatrix() + { + mCompositor->SetProjMatrix(mOriginalProjMatrix); + } +private: + CompositorOGL* const mCompositor; + const Matrix4x4 mOriginalProjMatrix; +}; + +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 ScopedCompositorRenderOffset { +public: + ScopedCompositorRenderOffset(CompositorOGL* aCompositor, const ScreenPoint& aOffset) : + mCompositor(aCompositor), + mOriginalOffset(mCompositor->GetScreenRenderOffset()) + { + mCompositor->SetScreenRenderOffset(aOffset); + } + ~ScopedCompositorRenderOffset() + { + mCompositor->SetScreenRenderOffset(mOriginalOffset); + } +private: + CompositorOGL* const mCompositor; + const ScreenPoint mOriginalOffset; +}; + +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() +{ +#ifdef MOZ_WIDGET_ANDROID + nsIWidget* const widget = mCompositor->GetWidget()->RealWidget(); + auto window = static_cast<ANativeWindow*>( + widget->GetNativeData(NS_PRESENTATION_WINDOW)); + + if (!window) { + return; + } + + EGLSurface surface = widget->GetNativeData(NS_PRESENTATION_SURFACE); + + if (!surface) { + //create surface; + surface = GLContextProviderEGL::CreateEGLSurface(window); + if (!surface) { + return; + } + + widget->SetNativeData(NS_PRESENTATION_SURFACE, + reinterpret_cast<uintptr_t>(surface)); + } + + CompositorOGL* compositor = mCompositor->AsCompositorOGL(); + GLContext* gl = compositor->gl(); + GLContextEGL* egl = GLContextEGL::Cast(gl); + + if (!egl) { + return; + } + + const IntSize windowSize(ANativeWindow_getWidth(window), + ANativeWindow_getHeight(window)); + +#endif + + 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; + LayerIntRegion visible; + PostProcessLayers(mRoot, opaque, visible, Nothing()); + + nsIntRegion invalid; + IntRect bounds = IntRect::Truncate(0, 0, scale * pageWidth, actualHeight); + IntRect rect, actualBounds; + MOZ_ASSERT(mRoot->GetOpacity() == 1); + mCompositor->BeginFrame(invalid, nullptr, bounds, nsIntRegion(), &rect, &actualBounds); + + // 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); + + mCompositor->EndFrame(); +} +#endif + +already_AddRefed<PaintedLayerComposite> +LayerManagerComposite::CreatePaintedLayerComposite() +{ + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr<PaintedLayerComposite>(new PaintedLayerComposite(this)).forget(); +} + +already_AddRefed<ContainerLayerComposite> +LayerManagerComposite::CreateContainerLayerComposite() +{ + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr<ContainerLayerComposite>(new ContainerLayerComposite(this)).forget(); +} + +already_AddRefed<ImageLayerComposite> +LayerManagerComposite::CreateImageLayerComposite() +{ + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr<ImageLayerComposite>(new ImageLayerComposite(this)).forget(); +} + +already_AddRefed<ColorLayerComposite> +LayerManagerComposite::CreateColorLayerComposite() +{ + if (LayerManagerComposite::mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr<ColorLayerComposite>(new ColorLayerComposite(this)).forget(); +} + +already_AddRefed<CanvasLayerComposite> +LayerManagerComposite::CreateCanvasLayerComposite() +{ + if (LayerManagerComposite::mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr<CanvasLayerComposite>(new CanvasLayerComposite(this)).forget(); +} + +already_AddRefed<RefLayerComposite> +LayerManagerComposite::CreateRefLayerComposite() +{ + if (LayerManagerComposite::mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr<RefLayerComposite>(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(); +} + +void +LayerManagerComposite::ChangeCompositor(Compositor* aNewCompositor) +{ + mCompositor = aNewCompositor; + mTextRenderer = new TextRenderer(aNewCompositor); + mTwoPassTmpTarget = nullptr; +} + +LayerComposite::LayerComposite(LayerManagerComposite *aManager) + : mCompositeManager(aManager) + , mCompositor(aManager->GetCompositor()) + , mShadowOpacity(1.0) + , mShadowTransformSetByAnimation(false) + , mShadowOpacitySetByAnimation(false) + , mDestroyed(false) + , mLayerComposited(false) +{ } + +LayerComposite::~LayerComposite() +{ +} + +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); + return; +} + +bool +LayerManagerComposite::CanUseCanvasLayerForSize(const IntSize &aSize) +{ + return mCompositor->CanUseCanvasLayerForSize(gfx::IntSize(aSize.width, + aSize.height)); +} + +void +LayerManagerComposite::NotifyShadowTreeTransaction() +{ + if (mFPS) { + mFPS->NotifyShadowTreeTransaction(); + } +} + +void +LayerComposite::SetLayerManager(LayerManagerComposite* aManager) +{ + mCompositeManager = aManager; + mCompositor = aManager->GetCompositor(); +} + +bool +LayerManagerComposite::AsyncPanZoomEnabled() const +{ + if (CompositorBridgeParent* bridge = mCompositor->GetCompositorBridgeParent()) { + return bridge->AsyncPanZoomEnabled(); + } + return false; +} + +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 +LayerComposite::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; +} + +bool +LayerComposite::HasStaleCompositor() const +{ + return mCompositeManager->GetCompositor() != mCompositor; +} + +static bool +LayerHasCheckerboardingAPZC(Layer* aLayer, Color* aOutColor) +{ + bool answer = false; + for (LayerMetricsWrapper i(aLayer, LayerMetricsWrapper::StartAt::BOTTOM); i; i = i.GetParent()) { + if (!i.Metrics().IsScrollable()) { + continue; + } + if (i.GetApzc() && i.GetApzc()->IsCurrentlyCheckerboarding()) { + if (aOutColor) { + *aOutColor = i.Metadata().GetBackgroundColor(); + } + answer = true; + break; + } + break; + } + return answer; +} + +bool +LayerComposite::NeedToDrawCheckerboarding(gfx::Color* aOutCheckerboardingColor) +{ + return GetLayer()->Manager()->AsyncPanZoomEnabled() && + (GetLayer()->GetContentFlags() & Layer::CONTENT_OPAQUE) && + GetLayer()->IsOpaqueForVisibility() && + LayerHasCheckerboardingAPZC(GetLayer(), aOutCheckerboardingColor); +} + +#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) + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/LayerManagerComposite.h b/gfx/layers/composite/LayerManagerComposite.h new file mode 100644 index 0000000000..8fe7a6b34a --- /dev/null +++ b/gfx/layers/composite/LayerManagerComposite.h @@ -0,0 +1,687 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LayerManagerComposite_H +#define GFX_LayerManagerComposite_H + +#include <stdint.h> // for int32_t, uint32_t +#include "GLDefs.h" // for GLenum +#include "Layers.h" +#include "Units.h" // for ParentLayerIntRect +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/Effects.h" // for EffectChain +#include "mozilla/layers/LayersMessages.h" +#include "mozilla/layers/LayersTypes.h" // for LayersBackend, etc +#include "mozilla/Maybe.h" // for Maybe +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsAString.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion +#include "nscore.h" // for nsAString, etc +#include "LayerTreeInvalidation.h" + +class gfxContext; + +#ifdef XP_WIN +#include <windows.h> +#endif + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx + +namespace layers { + +class CanvasLayerComposite; +class ColorLayerComposite; +class CompositableHost; +class Compositor; +class ContainerLayerComposite; +struct EffectChain; +class ImageLayer; +class ImageLayerComposite; +class LayerComposite; +class RefLayerComposite; +class PaintedLayerComposite; +class TextRenderer; +class CompositingRenderTarget; +struct FPSState; +class PaintCounter; + +static const int kVisualWarningDuration = 150; // ms + +class LayerManagerComposite final : public LayerManager +{ + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::IntSize IntSize; + typedef mozilla::gfx::SurfaceFormat SurfaceFormat; + +public: + explicit LayerManagerComposite(Compositor* aCompositor); + ~LayerManagerComposite(); + + virtual 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. + */ + virtual LayerManagerComposite* AsLayerManagerComposite() override + { + return this; + } + + void UpdateRenderBounds(const gfx::IntRect& aRect); + + virtual bool BeginTransaction() override; + virtual bool BeginTransactionWithTarget(gfxContext* aTarget) override + { + MOZ_CRASH("GFX: Use BeginTransactionWithDrawTarget"); + return false; + } + void BeginTransactionWithDrawTarget(gfx::DrawTarget* aTarget, + const gfx::IntRect& aRect); + + virtual bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override + { + MOZ_CRASH("GFX: Use EndTransaction(aTimeStamp)"); + return false; + } + virtual void EndTransaction(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags aFlags = END_DEFAULT) override + { + MOZ_CRASH("GFX: Use EndTransaction(aTimeStamp)"); + } + void EndTransaction(const TimeStamp& aTimeStamp, + EndTransactionFlags aFlags = END_DEFAULT); + + virtual void SetRoot(Layer* aLayer) override { mRoot = aLayer; } + + // XXX[nrc]: never called, we should move this logic to ClientLayerManager + // (bug 946926). + virtual bool CanUseCanvasLayerForSize(const gfx::IntSize &aSize) override; + + virtual int32_t GetMaxTextureSize() const override + { + MOZ_CRASH("GFX: Call on compositor, not LayerManagerComposite"); + } + + virtual void ClearCachedResources(Layer* aSubtree = nullptr) override; + + virtual already_AddRefed<PaintedLayer> CreatePaintedLayer() override; + virtual already_AddRefed<ContainerLayer> CreateContainerLayer() override; + virtual already_AddRefed<ImageLayer> CreateImageLayer() override; + virtual already_AddRefed<ColorLayer> CreateColorLayer() override; + virtual already_AddRefed<CanvasLayer> CreateCanvasLayer() override; + already_AddRefed<PaintedLayerComposite> CreatePaintedLayerComposite(); + already_AddRefed<ContainerLayerComposite> CreateContainerLayerComposite(); + already_AddRefed<ImageLayerComposite> CreateImageLayerComposite(); + already_AddRefed<ColorLayerComposite> CreateColorLayerComposite(); + already_AddRefed<CanvasLayerComposite> CreateCanvasLayerComposite(); + already_AddRefed<RefLayerComposite> CreateRefLayerComposite(); + + virtual LayersBackend GetBackendType() override + { + MOZ_CRASH("GFX: Shouldn't be called for composited layer manager"); + } + virtual void GetBackendName(nsAString& name) override + { + MOZ_CRASH("GFX: Shouldn't be called for composited layer manager"); + } + + virtual bool AreComponentAlphaLayersEnabled() override; + + virtual already_AddRefed<DrawTarget> + CreateOptimalMaskDrawTarget(const IntSize &aSize) override; + + virtual const char* Name() const override { return ""; } + + /** + * 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. + */ + void PostProcessLayers(Layer* aLayer, + nsIntRegion& aOpaqueRegion, + LayerIntRegion& aVisibleRegion, + const Maybe<ParentLayerIntRect>& aClipFromAncestors); + + /** + * 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) + { + mInvalidRegion.Or(mInvalidRegion, aRegion); + } + + void ClearApproximatelyVisibleRegions(uint64_t aLayersId, + const Maybe<uint32_t>& aPresShellId) + { + for (auto iter = mVisibleRegions.Iter(); !iter.Done(); iter.Next()) { + if (iter.Key().mLayersId == aLayersId && + (!aPresShellId || iter.Key().mPresShellId == *aPresShellId)) { + iter.Remove(); + } + } + } + + void UpdateApproximatelyVisibleRegion(const ScrollableLayerGuid& aGuid, + const CSSIntRegion& aRegion) + { + CSSIntRegion* regionForScrollFrame = mVisibleRegions.LookupOrAdd(aGuid); + MOZ_ASSERT(regionForScrollFrame); + + *regionForScrollFrame = aRegion; + } + + CSSIntRegion* GetApproximatelyVisibleRegion(const ScrollableLayerGuid& aGuid) + { + return mVisibleRegions.Get(aGuid); + } + + Compositor* GetCompositor() const + { + return mCompositor; + } + + // Called by CompositorBridgeParent when a new compositor has been created due + // to a device reset. The layer manager must clear any cached resources + // attached to the old compositor, and make a best effort at ignoring + // layer or texture updates against the old compositor. + void ChangeCompositor(Compositor* aNewCompositor); + + /** + * LayerManagerComposite provides sophisticated debug overlays + * that can request a next frame. + */ + bool DebugOverlayWantsNextFrame() { return mDebugOverlayWantsNextFrame; } + void SetDebugOverlayWantsNextFrame(bool aVal) + { mDebugOverlayWantsNextFrame = aVal; } + + void NotifyShadowTreeTransaction(); + + TextRenderer* GetTextRenderer() { return mTextRenderer; } + + /** + * 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 UnusedApzTransformWarning() { + mUnusedApzTransformWarning = true; + } + void DisabledApzWarning() { + mDisabledApzWarning = true; + } + + bool LastFrameMissedHWC() { return mLastFrameMissedHWC; } + + bool AsyncPanZoomEnabled() const override; + + void AppendImageCompositeNotification(const ImageCompositeNotification& 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 (!mCompositor->GetTargetContext()) { + mImageCompositeNotifications.AppendElement(aNotification); + } + } + void ExtractImageCompositeNotifications(nsTArray<ImageCompositeNotification>* aNotifications) + { + aNotifications->AppendElements(Move(mImageCompositeNotifications)); + } + + // Indicate that we need to composite even if nothing in our layers has + // changed, so that the widget can draw something different in its window + // overlay. + void SetWindowOverlayChanged() { mWindowOverlayChanged = true; } + + void ForcePresent() { mCompositor->ForcePresent(); } + + void SetPaintTime(const TimeDuration& aPaintTime) { mLastPaintTime = aPaintTime; } + +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. + */ + void Render(const nsIntRegion& aInvalidRegion, const nsIntRegion& aOpaqueRegion); +#if defined(MOZ_WIDGET_ANDROID) + void RenderToPresentationSurface(); +#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); + + + RefPtr<CompositingRenderTarget> PushGroupForLayerEffects(); + void PopGroupForLayerEffects(RefPtr<CompositingRenderTarget> aPreviousTarget, + gfx::IntRect aClipRect, + bool aGrayscaleEffect, + bool aInvertEffect, + float aContrastEffect); + + void ChangeCompositorInternal(Compositor* aNewCompositor); + + float mWarningLevel; + mozilla::TimeStamp mWarnTime; + bool mUnusedApzTransformWarning; + bool mDisabledApzWarning; + RefPtr<Compositor> mCompositor; + UniquePtr<LayerProperties> mClonedLayerTreeProperties; + + nsTArray<ImageCompositeNotification> mImageCompositeNotifications; + + /** + * Context target, nullptr when drawing directly to our swap chain. + */ + RefPtr<gfx::DrawTarget> mTarget; + gfx::IntRect mTargetBounds; + + nsIntRegion mInvalidRegion; + + typedef nsClassHashtable<nsGenericHashKey<ScrollableLayerGuid>, + CSSIntRegion> VisibleRegions; + VisibleRegions mVisibleRegions; + + UniquePtr<FPSState> mFPS; + + bool mInTransaction; + bool mIsCompositorReady; + bool mDebugOverlayWantsNextFrame; + + RefPtr<CompositingRenderTarget> mTwoPassTmpTarget; + RefPtr<TextRenderer> mTextRenderer; + bool mGeometryChanged; + + // Testing property. If hardware composer is supported, this will return + // true if the last frame was deemed 'too complicated' to be rendered. + bool mLastFrameMissedHWC; + + bool mWindowOverlayChanged; + TimeDuration mLastPaintTime; + TimeStamp mRenderStartTime; + +#ifdef USE_SKIA + /** + * Render paint and composite times above the frame. + */ + void DrawPaintTimes(Compositor* aCompositor); + RefPtr<PaintCounter> mPaintCounter; +#endif +}; + +/** + * Composite layers are for use with OMTC on the compositor thread only. There + * must be corresponding Basic 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: + explicit LayerComposite(LayerManagerComposite* aManager); + + virtual ~LayerComposite(); + + virtual LayerComposite* GetFirstChildComposite() + { + return nullptr; + } + + /* Do NOT call this from the generic LayerComposite destructor. Only from the + * concrete class destructor + */ + virtual void Destroy(); + + virtual Layer* GetLayer() = 0; + + virtual void SetLayerManager(LayerManagerComposite* aManager); + + LayerManagerComposite* GetLayerManager() const { return mCompositeManager; } + + /** + * 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) = 0; + + 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; + + virtual void CleanupResources() = 0; + + virtual void DestroyFrontBuffer() { } + + void AddBlendModeEffect(EffectChain& aEffectChain); + + virtual void GenEffectChain(EffectChain& aEffect) { } + + /** + * The following methods are + * + * CONSTRUCTION PHASE ONLY + * + * They are analogous to the Layer interface. + */ + void SetShadowVisibleRegion(const LayerIntRegion& aRegion) + { + mShadowVisibleRegion = 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; + } + + void SetLayerComposited(bool value) + { + mLayerComposited = value; + } + + void SetClearRect(const gfx::IntRect& aRect) + { + mClearRect = aRect; + } + + // These getters can be used anytime. + float GetShadowOpacity() { return mShadowOpacity; } + const Maybe<ParentLayerIntRect>& GetShadowClipRect() { return mShadowClipRect; } + const LayerIntRegion& GetShadowVisibleRegion() { return mShadowVisibleRegion; } + const gfx::Matrix4x4& GetShadowBaseTransform() { return mShadowTransform; } + gfx::Matrix4x4 GetShadowTransform(); + bool GetShadowTransformSetByAnimation() { return mShadowTransformSetByAnimation; } + bool GetShadowOpacitySetByAnimation() { return mShadowOpacitySetByAnimation; } + 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(); + + /** + * Return true if a checkerboarding background color needs to be drawn + * for this layer. + */ + bool NeedToDrawCheckerboarding(gfx::Color* aOutCheckerboardingColor = nullptr); + +protected: + gfx::Matrix4x4 mShadowTransform; + LayerIntRegion mShadowVisibleRegion; + Maybe<ParentLayerIntRect> mShadowClipRect; + LayerManagerComposite* mCompositeManager; + RefPtr<Compositor> mCompositor; + float mShadowOpacity; + bool mShadowTransformSetByAnimation; + bool mShadowOpacitySetByAnimation; + bool mDestroyed; + bool mLayerComposited; + gfx::IntRect mClearRect; +}; + +// 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->AsLayerComposite()->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); + aLayer->AsLayerComposite()->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().ToUnknownRegion().GetBounds()); + 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). + aLayer->AsLayerComposite()->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_LayerManagerComposite_H */ diff --git a/gfx/layers/composite/PaintCounter.cpp b/gfx/layers/composite/PaintCounter.cpp new file mode 100644 index 0000000000..56e57aab45 --- /dev/null +++ b/gfx/layers/composite/PaintCounter.cpp @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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 "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); + mStride = mSurface->Stride(); + + mCanvas.reset( + SkCanvas::NewRasterDirect(MakeSkiaImageInfo(mRect.Size(), mFormat), + mSurface->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.setTextSize(32); + paint.setColor(SkColorSetRGB(0, 255, 0)); + paint.setAntiAlias(true); + + mCanvas->clear(SK_ColorTRANSPARENT); + mCanvas->drawText(buffer, strlen(buffer), 10, 30, 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..b5296939fc --- /dev/null +++ b/gfx/layers/composite/PaintCounter.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_PaintCounter_h_ +#define mozilla_layers_PaintCounter_h_ + +#include <map> // for std::map +#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; + sk_sp<SkCanvas> mCanvas; + IntSize mSize; + int mStride; + + RefPtr<DataSourceSurface> mSurface; + RefPtr<DataTextureSource> mTextureSource; + RefPtr<TexturedEffect> mTexturedEffect; + 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..b58f5d690d --- /dev/null +++ b/gfx/layers/composite/PaintedLayerComposite.cpp @@ -0,0 +1,194 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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/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/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: + mBuffer = static_cast<ContentHost*>(aHost); + 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(LayerManagerComposite* aManager) +{ + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + if (mBuffer && mCompositor) { + mBuffer->SetCompositor(mCompositor); + } +} + +LayerRenderState +PaintedLayerComposite::GetRenderState() +{ + if (!mBuffer || !mBuffer->IsAttached() || mDestroyed) { + return LayerRenderState(); + } + return mBuffer->GetRenderState(); +} + +void +PaintedLayerComposite::RenderLayer(const gfx::IntRect& aClipRect) +{ + if (!mBuffer || !mBuffer->IsAttached()) { + return; + } + PROFILER_LABEL("PaintedLayerComposite", "RenderLayer", + js::ProfileEntry::Category::GRAPHICS); + + Compositor* compositor = mCompositeManager->GetCompositor(); + + MOZ_ASSERT(mBuffer->GetCompositor() == 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(this, effectChain, + GetEffectiveOpacity(), + GetEffectiveTransform(), + GetSamplingFilter(), + clipRect, + &visibleRegion); + }); + + 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; +} + +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..45a89eccfb --- /dev/null +++ b/gfx/layers/composite/PaintedLayerComposite.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 "nsDebug.h" // for NS_RUNTIMEABORT +#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: + virtual void Disconnect() override; + + virtual LayerRenderState GetRenderState() override; + + CompositableHost* GetCompositableHost() override; + + virtual void Destroy() override; + + virtual Layer* GetLayer() override; + + virtual void SetLayerManager(LayerManagerComposite* aManager) override; + + virtual void RenderLayer(const gfx::IntRect& aClipRect) override; + + virtual void CleanupResources() override; + + virtual void GenEffectChain(EffectChain& aEffect) override; + + virtual bool SetCompositableHost(CompositableHost* aHost) override; + + virtual LayerComposite* AsLayerComposite() override { return this; } + + virtual void InvalidateRegion(const nsIntRegion& aRegion) override + { + NS_RUNTIMEABORT("PaintedLayerComposites can't fill invalidated regions"); + } + + void SetValidRegion(const nsIntRegion& aRegion) + { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) ValidRegion", this)); + mValidRegion = aRegion; + Mutated(); + } + + const virtual 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..be59cb2461 --- /dev/null +++ b/gfx/layers/composite/TextRenderer.cpp @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextRenderer.h" +#include "FontData.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; +using namespace std; + +const Float sBackgroundOpacity = 0.6f; +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); + + DataSourceSurface::MappedSurface map = static_cast<TextRenderer*>(png_get_progressive_ptr(png_ptr))->GetSurfaceMap(); + + uint32_t* dst = (uint32_t*)(map.mData + map.mStride * row_num); + + for (uint32_t x = 0; x < sTextureWidth; 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); + Color pixelColor(baseValue, baseValue, baseValue, baseValue + alphaValue); + dst[x] = pixelColor.ToABGR(); + } +} + +TextRenderer::~TextRenderer() +{ + if (mGlyphBitmaps) { + mGlyphBitmaps->Unmap(); + } +} + +void +TextRenderer::RenderText(const string& aText, const IntPoint& aOrigin, + const Matrix4x4& aTransform, uint32_t aTextSize, + uint32_t aTargetPixelWidth) +{ + EnsureInitialized(); + + // For now we only have a bitmap font with a 16px cell size, so we just + // scale it up if the user wants larger text. + Float scaleFactor = Float(aTextSize) / Float(sCellHeight); + + aTargetPixelWidth /= scaleFactor; + + 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 += sGlyphWidths[uint32_t(aText[i])]; + maxWidth = std::max(lineWidth, maxWidth); + } + + // Create a surface to draw our glyphs to. + RefPtr<DataSourceSurface> textSurf = + Factory::CreateDataSourceSurface(IntSize(maxWidth, numLines * sCellHeight), sTextureFormat); + if (NS_WARN_IF(!textSurf)) { + return; + } + + DataSourceSurface::MappedSurface map; + if (NS_WARN_IF(!textSurf->Map(DataSourceSurface::MapType::READ_WRITE, &map))) { + return; + } + + // Initialize the surface to transparent white. + memset(map.mData, uint8_t(sBackgroundOpacity * 255.0f), + numLines * sCellHeight * map.mStride); + + uint32_t currentXPos = 0; + uint32_t currentYPos = 0; + + // Copy our glyphs onto the surface. + for (uint32_t i = 0; i < aText.length(); i++) { + if (aText[i] == '\n' || (aText[i] == ' ' && currentXPos > aTargetPixelWidth)) { + currentYPos += sCellHeight; + currentXPos = 0; + continue; + } + + uint32_t glyphXOffset = aText[i] % (sTextureWidth / sCellWidth) * sCellWidth * BytesPerPixel(sTextureFormat); + uint32_t truncatedLine = aText[i] / (sTextureWidth / sCellWidth); + uint32_t glyphYOffset = truncatedLine * sCellHeight * mMap.mStride; + + for (int y = 0; y < 16; y++) { + memcpy(map.mData + (y + currentYPos) * map.mStride + currentXPos * BytesPerPixel(sTextureFormat), + mMap.mData + glyphYOffset + y * mMap.mStride + glyphXOffset, + sGlyphWidths[uint32_t(aText[i])] * BytesPerPixel(sTextureFormat)); + } + + currentXPos += sGlyphWidths[uint32_t(aText[i])]; + } + + textSurf->Unmap(); + + RefPtr<DataTextureSource> src = mCompositor->CreateDataTextureSource(); + + if (!src->Update(textSurf)) { + // Upload failed. + return; + } + + RefPtr<EffectRGB> effect = new EffectRGB(src, true, SamplingFilter::LINEAR); + EffectChain chain; + chain.mPrimaryEffect = effect; + + Matrix4x4 transform = aTransform; + transform.PreScale(scaleFactor, scaleFactor, 1.0f); + mCompositor->DrawQuad(Rect(aOrigin.x, aOrigin.y, maxWidth, numLines * 16), + IntRect(-10000, -10000, 20000, 20000), chain, 1.0f, transform); +} + +void +TextRenderer::EnsureInitialized() +{ + if (mGlyphBitmaps) { + return; + } + + mGlyphBitmaps = Factory::CreateDataSourceSurface(IntSize(sTextureWidth, sTextureHeight), sTextureFormat); + if (NS_WARN_IF(!mGlyphBitmaps)) { + return; + } + + if (NS_WARN_IF(!mGlyphBitmaps->Map(DataSourceSurface::MapType::READ_WRITE, &mMap))) { + return; + } + + 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, this, 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*)sFontPNG, sizeof(sFontPNG)); + + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/TextRenderer.h b/gfx/layers/composite/TextRenderer.h new file mode 100644 index 0000000000..7665558eb3 --- /dev/null +++ b/gfx/layers/composite/TextRenderer.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_TextRenderer_H +#define GFX_TextRenderer_H + +#include "mozilla/gfx/2D.h" +#include "nsISupportsImpl.h" +#include <string> + +namespace mozilla { +namespace layers { + +class Compositor; + +class TextRenderer +{ + ~TextRenderer(); + +public: + NS_INLINE_DECL_REFCOUNTING(TextRenderer) + + explicit TextRenderer(Compositor *aCompositor) + : mCompositor(aCompositor), mMap({nullptr, 0}) + { + } + + void RenderText(const std::string& aText, const gfx::IntPoint& aOrigin, + const gfx::Matrix4x4& aTransform, uint32_t aTextSize, + uint32_t aTargetPixelWidth); + + gfx::DataSourceSurface::MappedSurface& GetSurfaceMap() { return mMap; } + +private: + + // Note that this may still fail to set mGlyphBitmaps to a valid value + // if the underlying CreateDataSourceSurface fails for some reason. + void EnsureInitialized(); + + RefPtr<Compositor> mCompositor; + RefPtr<gfx::DataSourceSurface> mGlyphBitmaps; + gfx::DataSourceSurface::MappedSurface mMap; +}; + +} // 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..8c5b8c7b77 --- /dev/null +++ b/gfx/layers/composite/TextureHost.cpp @@ -0,0 +1,1142 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextureHost.h" + +#include "CompositableHost.h" // for CompositableHost +#include "LayerScope.h" +#include "LayersLogging.h" // for AppendToString +#include "mozilla/gfx/2D.h" // for DataSourceSurface, Factory +#include "mozilla/ipc/Shmem.h" // for Shmem +#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/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" +#include "mozilla/layers/GPUVideoTextureHost.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 "gfxPrefs.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/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: + explicit TextureParent(HostIPCAllocator* aAllocator, uint64_t aSerial); + + ~TextureParent(); + + bool Init(const SurfaceDescriptor& aSharedData, + const LayersBackend& aLayersBackend, + const TextureFlags& aFlags); + + void NotifyNotUsed(uint64_t aTransactionId); + + virtual bool RecvRecycleTexture(const TextureFlags& aTextureFlags) override; + + TextureHost* GetTextureHost() { return mTextureHost; } + + virtual void Destroy() override; + + uint64_t GetSerial() const { return mSerial; } + + virtual bool RecvDestroySync() override { + DestroyIfNeeded(); + return true; + } + + HostIPCAllocator* mSurfaceAllocator; + RefPtr<TextureHost> mTextureHost; + // mSerial is unique in TextureClient's process. + const uint64_t mSerial; +}; + +//////////////////////////////////////////////////////////////////////////////// +PTextureParent* +TextureHost::CreateIPDLActor(HostIPCAllocator* aAllocator, + const SurfaceDescriptor& aSharedData, + LayersBackend aLayersBackend, + TextureFlags aFlags, + uint64_t aSerial) +{ + if (aSharedData.type() == SurfaceDescriptor::TSurfaceDescriptorBuffer && + aSharedData.get_SurfaceDescriptorBuffer().data().type() == MemoryOrShmem::Tuintptr_t && + !aAllocator->IsSameProcess()) + { + NS_ERROR("A client process is trying to peek at our address space using a MemoryTexture!"); + return nullptr; + } + TextureParent* actor = new TextureParent(aAllocator, aSerial); + if (!actor->Init(aSharedData, aLayersBackend, aFlags)) { + 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; +} + +// implemented in TextureHostOGL.cpp +already_AddRefed<TextureHost> CreateTextureHostOGL(const SurfaceDescriptor& aDesc, + ISurfaceAllocator* aDeallocator, + TextureFlags aFlags); + +// implemented in TextureHostBasic.cpp +already_AddRefed<TextureHost> CreateTextureHostBasic(const SurfaceDescriptor& aDesc, + ISurfaceAllocator* aDeallocator, + TextureFlags aFlags); + +// implemented in TextureD3D11.cpp +already_AddRefed<TextureHost> CreateTextureHostD3D11(const SurfaceDescriptor& aDesc, + ISurfaceAllocator* aDeallocator, + TextureFlags aFlags); + +// implemented in TextureD3D9.cpp +already_AddRefed<TextureHost> CreateTextureHostD3D9(const SurfaceDescriptor& aDesc, + ISurfaceAllocator* aDeallocator, + TextureFlags aFlags); + +already_AddRefed<TextureHost> +TextureHost::Create(const SurfaceDescriptor& aDesc, + ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, + TextureFlags aFlags) +{ + switch (aDesc.type()) { + case SurfaceDescriptor::TSurfaceDescriptorBuffer: + case SurfaceDescriptor::TSurfaceDescriptorDIB: + case SurfaceDescriptor::TSurfaceDescriptorFileMapping: + case SurfaceDescriptor::TSurfaceDescriptorGPUVideo: + return CreateBackendIndependentTextureHost(aDesc, aDeallocator, aFlags); + + case SurfaceDescriptor::TEGLImageDescriptor: + case SurfaceDescriptor::TSurfaceTextureDescriptor: + case SurfaceDescriptor::TSurfaceDescriptorSharedGLTexture: + return CreateTextureHostOGL(aDesc, aDeallocator, aFlags); + + case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: + if (aBackend == LayersBackend::LAYERS_OPENGL) { + return CreateTextureHostOGL(aDesc, aDeallocator, aFlags); + } else { + return CreateTextureHostBasic(aDesc, aDeallocator, aFlags); + } + +#ifdef MOZ_X11 + case SurfaceDescriptor::TSurfaceDescriptorX11: { + const SurfaceDescriptorX11& desc = aDesc.get_SurfaceDescriptorX11(); + return MakeAndAddRef<X11TextureHost>(aFlags, desc); + } +#endif + +#ifdef XP_WIN + case SurfaceDescriptor::TSurfaceDescriptorD3D9: + return CreateTextureHostD3D9(aDesc, aDeallocator, aFlags); + + case SurfaceDescriptor::TSurfaceDescriptorD3D10: + case SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr: + if (aBackend == LayersBackend::LAYERS_D3D9) { + return CreateTextureHostD3D9(aDesc, aDeallocator, aFlags); + } else { + return CreateTextureHostD3D11(aDesc, aDeallocator, aFlags); + } +#endif + default: + MOZ_CRASH("GFX: Unsupported Surface type host"); + } +} + +already_AddRefed<TextureHost> +CreateBackendIndependentTextureHost(const SurfaceDescriptor& aDesc, + ISurfaceAllocator* aDeallocator, + 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: { + result = new ShmemTextureHost(data.get_Shmem(), + bufferDesc.desc(), + aDeallocator, + aFlags); + break; + } + case MemoryOrShmem::Tuintptr_t: { + 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: { + result = new GPUVideoTextureHost(aFlags, aDesc.get_SurfaceDescriptorGPUVideo()); + break; + } +#ifdef XP_WIN + case SurfaceDescriptor::TSurfaceDescriptorDIB: { + 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) +{ +} + +TextureHost::~TextureHost() +{ + // 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(); +} + +void TextureHost::Finalize() +{ + if (!(GetFlags() & TextureFlags::DEALLOCATE_CLIENT)) { + DeallocateSharedData(); + DeallocateDeviceData(); + } +} + +void +TextureHost::UnbindTextureSource() +{ + if (mReadLock) { + auto compositor = GetCompositor(); + // 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 (compositor) { + compositor->UnlockAfterComposition(this); + } else { + // GetCompositor returned null which means no compositor can be using this + // texture. We can ReadUnlock right away. + ReadUnlock(); + } + } +} + +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. + if (!(GetFlags() & TextureFlags::RECYCLE)) { + return; + } + + auto compositor = GetCompositor(); + // 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 (!compositor || + compositor->IsDestroyed() || + compositor->AsBasicCompositor() || + HasIntermediateBuffer()) { + static_cast<TextureParent*>(mActor)->NotifyNotUsed(mFwdTransactionId); + return; + } + + compositor->NotifyNotUsedAfterComposition(this); +} + +void +TextureHost::CallNotifyNotUsed() +{ + if (!mActor) { + return; + } + static_cast<TextureParent*>(mActor)->NotifyNotUsed(mFwdTransactionId); +} + +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()) { + AppendToString(aStream, GetSize(), " [size=", "]"); + AppendToString(aStream, GetFormat(), " [format=", "]"); + Unlock(); + } + AppendToString(aStream, mFlags, " [flags=", "]"); +#ifdef MOZ_DUMP_PAINTING + if (gfxPrefs::LayersDumpTexture() || profiler_feature_active("layersdump")) { + 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) +{ + MOZ_COUNT_CTOR(TextureSource); +} + +TextureSource::~TextureSource() +{ + MOZ_COUNT_DTOR(TextureSource); +} + +const char* +TextureSource::Name() const +{ + MOZ_CRASH("GFX: TextureSource without class name"); + return "TextureSource"; +} + +BufferTextureHost::BufferTextureHost(const BufferDescriptor& aDesc, + TextureFlags aFlags) +: TextureHost(aFlags) +, mCompositor(nullptr) +, mUpdateSerial(1) +, mLocked(false) +, mNeedsFullUpdate(false) +{ + mDescriptor = aDesc; + switch (mDescriptor.type()) { + case BufferDescriptor::TYCbCrDescriptor: { + const YCbCrDescriptor& ycbcr = mDescriptor.get_YCbCrDescriptor(); + mSize = ycbcr.ySize(); + 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; + } +} + +BufferTextureHost::~BufferTextureHost() +{} + +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::SetCompositor(Compositor* aCompositor) +{ + MOZ_ASSERT(aCompositor); + if (mCompositor == aCompositor) { + return; + } + if (aCompositor && mCompositor && + aCompositor->GetBackendType() == mCompositor->GetBackendType()) { + RefPtr<TextureSource> it = mFirstSource; + while (it) { + it->SetCompositor(aCompositor); + it = it->GetNextSibling(); + } + } + if (mFirstSource && mFirstSource->IsOwnedBy(this)) { + mFirstSource->SetOwner(nullptr); + } + if (mFirstSource) { + mFirstSource = nullptr; + mNeedsFullUpdate = true; + } + mCompositor = aCompositor; +} + +void +BufferTextureHost::DeallocateDeviceData() +{ + if (mFirstSource && mFirstSource->NumCompositableRefs() > 0) { + 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 (!MaybeUpload(!mNeedsFullUpdate ? &mMaybeUpdatedRegion : nullptr)) { + return false; + } + mLocked = !!mFirstSource; + return mLocked; +} + +void +BufferTextureHost::Unlock() +{ + MOZ_ASSERT(mLocked); + mLocked = false; +} + +void +TextureHost::DeserializeReadLock(const ReadLockDescriptor& aDesc, + ISurfaceAllocator* aAllocator) +{ + RefPtr<TextureReadLock> lock = TextureReadLock::Deserialize(aDesc, aAllocator); + if (!lock) { + return; + } + + // If mReadLock is not null it means we haven't unlocked it yet and the content + // side should not have been able to write into this texture and send a new lock! + MOZ_ASSERT(!mReadLock); + mReadLock = lock.forget(); +} + +void +TextureHost::ReadUnlock() +{ + if (mReadLock) { + mReadLock->ReadUnlock(); + mReadLock = nullptr; + } +} + +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 (!mCompositor) { + return false; + } + + if (mFormat == gfx::SurfaceFormat::YUV) { + mFirstSource = mCompositor->CreateDataTextureSourceAroundYCbCr(this); + } else { + RefPtr<gfx::DataSourceSurface> surf = + gfx::Factory::CreateWrappingDataSourceSurface(GetBuffer(), + ImageDataSerializer::ComputeRGBStride(mFormat, mSize.width), mSize, mFormat); + if (!surf) { + return false; + } + mFirstSource = mCompositor->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, + Compositor* aCompositor) +{ + if (!aCompositor) { + return false; + } + + switch (aDescriptor.type()) { + case BufferDescriptor::TYCbCrDescriptor: { + const YCbCrDescriptor& ycbcr = aDescriptor.get_YCbCrDescriptor(); + + if (!aCompositor->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, + mCompositor); + + 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->SetCompositor(mCompositor); + it = it->GetNextSibling(); + } + } +} + +bool +BufferTextureHost::BindTextureSource(CompositableTextureSourceRef& aTexture) +{ + MOZ_ASSERT(mLocked); + MOZ_ASSERT(mFirstSource); + aTexture = mFirstSource; + return !!aTexture; +} + +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(); +} + +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 && + mCompositor && + !mCompositor->SupportsEffect(EffectTypes::YCBCR)) { + return gfx::SurfaceFormat::R8G8B8X8; + } + return mFormat; +} + +YUVColorSpace +BufferTextureHost::GetYUVColorSpace() const +{ + if (mFormat == gfx::SurfaceFormat::YUV) { + const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + return desc.yUVColorSpace(); + } + return YUVColorSpace::UNKNOWN; +} + +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(); + } + + // 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 (!mCompositor) { + // This can happen if we send textures to a compositable that isn't yet + // attached to a layer. + return false; + } + if (!mHasIntermediateBuffer && EnsureWrappingTextureSource()) { + 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 (!mCompositor->SupportsEffect(EffectTypes::YCBCR)) { + RefPtr<gfx::DataSourceSurface> surf = + ImageDataSerializer::DataSourceSurfaceFromYCbCrDescriptor(buf, mDescriptor.get_YCbCrDescriptor()); + if (NS_WARN_IF(!surf)) { + return false; + } + if (!mFirstSource) { + mFirstSource = mCompositor->CreateDataTextureSource(mFlags|TextureFlags::RGB_FROM_YCBCR); + mFirstSource->SetOwner(this); + } + mFirstSource->Update(surf, aRegion); + return true; + } + + RefPtr<DataTextureSource> srcY; + RefPtr<DataTextureSource> srcU; + RefPtr<DataTextureSource> srcV; + if (!mFirstSource) { + // We don't support BigImages for YCbCr compositing. + srcY = mCompositor->CreateDataTextureSource(mFlags|TextureFlags::DISALLOW_BIGIMAGE); + srcU = mCompositor->CreateDataTextureSource(mFlags|TextureFlags::DISALLOW_BIGIMAGE); + srcV = mCompositor->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.ySize().width, + desc.ySize(), + gfx::SurfaceFormat::A8); + RefPtr<gfx::DataSourceSurface> tempCb = + gfx::Factory::CreateWrappingDataSourceSurface(ImageDataSerializer::GetCbChannel(buf, desc), + desc.cbCrSize().width, + desc.cbCrSize(), + gfx::SurfaceFormat::A8); + RefPtr<gfx::DataSourceSurface> tempCr = + gfx::Factory::CreateWrappingDataSourceSurface(ImageDataSerializer::GetCrChannel(buf, desc), + desc.cbCrSize().width, + desc.cbCrSize(), + gfx::SurfaceFormat::A8); + // 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 = mCompositor->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) +: mSurfaceAllocator(aSurfaceAllocator) +, mSerial(aSerial) +{ + 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 LayersBackend& aBackend, + const TextureFlags& aFlags) +{ + mTextureHost = TextureHost::Create(aSharedData, + mSurfaceAllocator, + aBackend, + aFlags); + if (mTextureHost) { + mTextureHost->mActor = this; + } + + return !!mTextureHost; +} + +void +TextureParent::Destroy() +{ + if (!mTextureHost) { + return; + } + + // ReadUnlock here to make sure the ReadLock's shmem does not outlive the + // protocol that created it. + mTextureHost->ReadUnlock(); + + if (mTextureHost->GetFlags() & TextureFlags::DEALLOCATE_CLIENT) { + mTextureHost->ForgetSharedData(); + } + + mTextureHost->mActor = nullptr; + mTextureHost = nullptr; +} + +void +TextureHost::ReceivedDestroy(PTextureParent* aActor) +{ + static_cast<TextureParent*>(aActor)->RecvDestroy(); +} + +bool +TextureParent::RecvRecycleTexture(const TextureFlags& aTextureFlags) +{ + if (!mTextureHost) { + return true; + } + mTextureHost->RecycleTexture(aTextureFlags); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/TextureHost.h b/gfx/layers/composite/TextureHost.h new file mode 100644 index 0000000000..c224d87772 --- /dev/null +++ b/gfx/layers/composite/TextureHost.h @@ -0,0 +1,900 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 "gfxTypes.h" +#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/2D.h" // for DataSourceSurface +#include "mozilla/gfx/Point.h" // for IntSize, IntPoint +#include "mozilla/gfx/Types.h" // for SurfaceFormat, etc +#include "mozilla/layers/Compositor.h" // for Compositor +#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/UniquePtr.h" // for UniquePtr +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_RUNTIMEABORT +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsRegion.h" // for nsIntRegion +#include "nsTraceRefcnt.h" // for MOZ_COUNT_CTOR, etc +#include "nscore.h" // for nsACString +#include "mozilla/layers/AtomicRefCountedWithFinalize.h" +#include "mozilla/gfx/Rect.h" + +namespace mozilla { +namespace ipc { +class Shmem; +} // namespace ipc + +namespace layers { + +class BufferDescriptor; +class BufferTextureHost; +class Compositor; +class CompositableParentManager; +class ReadLockDescriptor; +class CompositorBridgeParent; +class SurfaceDescriptor; +class HostIPCAllocator; +class ISurfaceAllocator; +class TextureHostOGL; +class TextureReadLock; +class TextureSourceOGL; +class TextureSourceD3D9; +class TextureSourceD3D11; +class TextureSourceBasic; +class DataTextureSource; +class PTextureParent; +class TextureParent; +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 TextureSourceD3D9* AsSourceD3D9() { 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 SetCompositor(Compositor* aCompositor) {} + + 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); + } + + int NumCompositableRefs() const { return mCompositableCount; } + +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() {} + + 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) + {} + + virtual const char* Name() const override { return "DataTextureSource"; } + + virtual 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. + virtual 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, + ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, + TextureFlags aFlags); + + /** + * 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 YUVColorSpace GetYUVColorSpace() const { return YUVColorSpace::UNKNOWN; } + + /** + * 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 another TextureHost will take over. + */ + virtual void UnbindTextureSource(); + + /** + * 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 it's device textures. + */ + virtual void SetCompositor(Compositor* aCompositor) {} + + /** + * 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, + LayersBackend aLayersBackend, + TextureFlags aFlags, + uint64_t aSerial); + 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(); + + /** + * Specific to B2G's Composer2D + * XXX - more doc here + */ + virtual LayerRenderState GetRenderState() + { + // By default we return an empty render state, this should be overridden + // by the TextureHost implementations that are used on B2G with Composer2D + return LayerRenderState(); + } + + // 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; } + + void AddCompositableRef() { ++mCompositableCount; } + + 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); + + TextureReadLock* GetReadLock() { return mReadLock; } + + virtual Compositor* GetCompositor() = 0; + + virtual BufferTextureHost* AsBufferTextureHost() { return nullptr; } + +protected: + void ReadUnlock(); + + void RecycleTexture(TextureFlags aFlags); + + virtual void UpdatedInternal(const nsIntRegion *Region) {} + + /** + * Called when mCompositableCount becomes 0. + */ + void NotifyNotUsed(); + + // for Compositor. + void CallNotifyNotUsed(); + + PTextureParent* mActor; + RefPtr<TextureReadLock> mReadLock; + TextureFlags mFlags; + int mCompositableCount; + uint64_t mFwdTransactionId; + + friend class Compositor; + friend class TextureParent; + friend class TiledLayerBufferComposite; +}; + +/** + * 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); + + ~BufferTextureHost(); + + virtual uint8_t* GetBuffer() = 0; + + virtual size_t GetBufferSize() = 0; + + virtual bool Lock() override; + + virtual void Unlock() override; + + virtual void PrepareTextureSource(CompositableTextureSourceRef& aTexture) override; + + virtual bool BindTextureSource(CompositableTextureSourceRef& aTexture) override; + + virtual void UnbindTextureSource() override; + + virtual void DeallocateDeviceData() override; + + virtual void SetCompositor(Compositor* aCompositor) override; + + virtual Compositor* GetCompositor() override { return mCompositor; } + + /** + * 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). + */ + virtual gfx::SurfaceFormat GetFormat() const override; + + virtual YUVColorSpace GetYUVColorSpace() const override; + + virtual gfx::IntSize GetSize() const override { return mSize; } + + virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override; + + virtual bool HasIntermediateBuffer() const override { return mHasIntermediateBuffer; } + + virtual BufferTextureHost* AsBufferTextureHost() override { return this; } + + const BufferDescriptor& GetBufferDescriptor() const { return mDescriptor; } + +protected: + bool Upload(nsIntRegion *aRegion = nullptr); + bool MaybeUpload(nsIntRegion *aRegion = nullptr); + bool EnsureWrappingTextureSource(); + + virtual void UpdatedInternal(const nsIntRegion* aRegion = nullptr) 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; + + 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: + virtual void DeallocateSharedData() override; + + virtual void ForgetSharedData() override; + + virtual uint8_t* GetBuffer() override; + + virtual size_t GetBufferSize() override; + + virtual const char *Name() override { return "ShmemTextureHost"; } + + virtual 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: + virtual void DeallocateSharedData() override; + + virtual void ForgetSharedData() override; + + virtual uint8_t* GetBuffer() override; + + virtual size_t GetBufferSize() override; + + virtual 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) + , mHasComplexProjection(false) + {} + virtual ~CompositingRenderTarget() {} + + virtual 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, + 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..7458c74971 --- /dev/null +++ b/gfx/layers/composite/TiledContentHost.cpp @@ -0,0 +1,645 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TiledContentHost.h" +#include "gfxPrefs.h" // for gfxPrefs +#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 +//#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent +#include "mozilla/layers/Effects.h" // for TexturedEffect, Effect, etc +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL +#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/layers/TextureClient.h" + +namespace mozilla { +using namespace gfx; +namespace layers { + +class Layer; + +float +TileHost::GetFadeInOpacity(float aOpacity) +{ + TimeStamp now = TimeStamp::Now(); + if (!gfxPrefs::LayerTileFadeInEnabled() || + mFadeStart.IsNull() || + now < mFadeStart) + { + return aOpacity; + } + + float duration = gfxPrefs::LayerTileFadeInDuration(); + float elapsed = (now - mFadeStart).ToMilliseconds(); + if (elapsed > duration) { + mFadeStart = TimeStamp(); + return aOpacity; + } + return aOpacity * (elapsed / duration); +} + +TiledLayerBufferComposite::TiledLayerBufferComposite() + : mFrameResolution() +{} + +TiledLayerBufferComposite::~TiledLayerBufferComposite() +{ + Clear(); +} + +void +TiledLayerBufferComposite::SetCompositor(Compositor* aCompositor) +{ + MOZ_ASSERT(aCompositor); + for (TileHost& tile : mRetainedTiles) { + if (tile.IsPlaceholderTile()) continue; + tile.mTextureHost->SetCompositor(aCompositor); + if (tile.mTextureHostOnWhite) { + tile.mTextureHostOnWhite->SetCompositor(aCompositor); + } + } +} + +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()) { + TileIntPoint position = mTiles.TilePosition(i); + IntPoint offset = GetTileOffset(position); + 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) +{ + // If we can use hwc for this TiledContentHost, it implies that we have exactly + // one high precision tile. Please check TiledContentHost::GetRenderState() for + // all condition. + 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, + tile.mTextureHost->GetRenderState()); +} + +void +TiledContentHost::Attach(Layer* aLayer, + Compositor* aCompositor, + AttachFlags aFlags /* = NO_FLAGS */) +{ + CompositableHost::Attach(aLayer, aCompositor, 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) +{ + if (aTiledDescriptor.resolution() < 1) { + if (!mLowPrecisionTiledBuffer.UseTiles(aTiledDescriptor, mCompositor, aAllocator)) { + return false; + } + } else { + if (!mTiledBuffer.UseTiles(aTiledDescriptor, mCompositor, aAllocator)) { + return false; + } + } + return true; +} + +void +UseTileTexture(CompositableTextureHostRef& aTexture, + CompositableTextureSourceRef& aTextureSource, + const IntRect& aUpdateRect, + Compositor* aCompositor) +{ + MOZ_ASSERT(aTexture); + if (!aTexture) { + return; + } + + if (aCompositor) { + aTexture->SetCompositor(aCompositor); + } + + 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(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 = Move(mTiles[i].mTextureSource); + if (aTile.mTextureHostOnWhite) { + aTile.mTextureSourceOnWhite = 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 = Move(mTiles[i].mTextureSource); + if (aTile.mTextureHostOnWhite) { + aTile.mTextureSourceOnWhite = 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, + Compositor* aCompositor, + ISurfaceAllocator* aAllocator) +{ + if (mResolution != aTiles.resolution() || + aTiles.tileSize() != mTileSize) { + Clear(); + } + MOZ_ASSERT(aAllocator); + MOZ_ASSERT(aCompositor); + if (!aAllocator || !aCompositor) { + 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 InfallibleTArray<TileDescriptor>& tileDescriptors = aTiles.tiles(); + + TextureSourceRecycler oldRetainedTiles(Move(mRetainedTiles)); + mRetainedTiles.SetLength(tileDescriptors.Length()); + + // 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()); + tile.mTextureHost->SetCompositor(aCompositor); + tile.mTextureHost->DeserializeReadLock(texturedDesc.sharedLock(), aAllocator); + + if (texturedDesc.textureOnWhite().type() == MaybeTexture::TPTextureParent) { + tile.mTextureHostOnWhite = TextureHost::AsTextureHost( + texturedDesc.textureOnWhite().get_PTextureParent() + ); + tile.mTextureHostOnWhite->DeserializeReadLock( + texturedDesc.sharedLockOnWhite(), aAllocator + ); + } + + tile.mTilePosition = newTiles.TilePosition(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(); + + aCompositor->CompositeUntil(tile.mFadeStart + + TimeDuration::FromMilliseconds(gfxPrefs::LayerTileFadeInDuration())); + } + } + + // 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(), + aCompositor); + + if (tile.mTextureHostOnWhite) { + UseTileTexture(tile.mTextureHostOnWhite, + tile.mTextureSourceOnWhite, + texturedDesc.updateRect(), + aCompositor); + } + } + + 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 = TileIntPoint(); + mTiles.mSize = TileIntSize(); + mValidRegion = nsIntRegion(); + mResolution = 1.0; +} + +void +TiledContentHost::Composite(LayerComposite* aLayer, + EffectChain& aEffectChain, + float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion /* = nullptr */) +{ + MOZ_ASSERT(mCompositor); + // 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. + Color backgroundColor; + if (aOpacity == 1.0f && gfxPrefs::LowPrecisionOpacity() < 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) + ? gfxPrefs::LowPrecisionOpacity() : 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, + lowPrecisionOpacityReduction < 1.0f ? &backgroundColor : nullptr, + aEffectChain, lowPrecisionOpacityReduction * aOpacity, + aSamplingFilter, aClipRect, *renderRegion, aTransform); + RenderLayerBuffer(mTiledBuffer, nullptr, aEffectChain, aOpacity, aSamplingFilter, + aClipRect, *renderRegion, aTransform); +} + + +void +TiledContentHost::RenderTile(TileHost& aTile, + 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) +{ + 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, + aTile.mTextureHost->GetRenderState()); + 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 = Rect(textureRect.x / aTextureBounds.width, + textureRect.y / aTextureBounds.height, + textureRect.width / aTextureBounds.width, + textureRect.height / aTextureBounds.height); + mCompositor->DrawQuad(graphicsRect, aClipRect, aEffectChain, opacity, aTransform, aVisibleRect); + } + DiagnosticFlags flags = DiagnosticFlags::CONTENT | DiagnosticFlags::TILE; + if (aTile.mTextureHostOnWhite) { + flags |= DiagnosticFlags::COMPONENT_ALPHA; + } + mCompositor->DrawDiagnostics(flags, + aScreenRegion, aClipRect, aTransform, mFlashCounter); +} + +void +TiledContentHost::RenderLayerBuffer(TiledLayerBufferComposite& aLayerBuffer, + const Color* aBackgroundColor, + EffectChain& aEffectChain, + float aOpacity, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + nsIntRegion aVisibleRegion, + gfx::Matrix4x4 aTransform) +{ + if (!mCompositor) { + NS_WARNING("Can't render tiled content host - no compositor"); + return; + } + 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); + mCompositor->DrawQuad(graphicsRect, aClipRect, effect, 1.0, aTransform); + } + } + + for (size_t i = 0; i < aLayerBuffer.GetTileCount(); ++i) { + TileHost& tile = aLayerBuffer.GetTile(i); + if (tile.IsPlaceholderTile()) { + continue; + } + + TileIntPoint tilePosition = aLayerBuffer.GetPlacement().TilePosition(i); + // A sanity check that catches a lot of mistakes. + MOZ_ASSERT(tilePosition.x == tile.mTilePosition.x && tilePosition.y == tile.mTilePosition.y); + + IntPoint tileOffset = aLayerBuffer.GetTileOffset(tilePosition); + nsIntRegion tileDrawRegion = IntRect(tileOffset, aLayerBuffer.GetScaledTileSize()); + tileDrawRegion.AndWith(compositeRegion); + + if (tileDrawRegion.IsEmpty()) { + continue; + } + + tileDrawRegion.ScaleRoundOut(resolution, resolution); + RenderTile(tile, aEffectChain, aOpacity, + aTransform, aSamplingFilter, aClipRect, tileDrawRegion, + tileOffset * resolution, aLayerBuffer.GetTileSize(), + gfx::Rect(visibleRect.x, visibleRect.y, + visibleRect.width, visibleRect.height)); + if (tile.mTextureHostOnWhite) { + componentAlphaDiagnostic = DiagnosticFlags::COMPONENT_ALPHA; + } + } + + gfx::Rect rect(visibleRect.x, visibleRect.y, + visibleRect.width, visibleRect.height); + GetCompositor()->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 (gfxPrefs::LayersDumpTexture() || profiler_feature_active("layersdump")) { + nsAutoCString pfx(aPrefix); + pfx += " "; + + Dump(aStream, pfx.get(), false); + } +} + +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..4b52394def --- /dev/null +++ b/gfx/layers/composite/TiledContentHost.h @@ -0,0 +1,292 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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() + {} + + // 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; + mTilePosition = o.mTilePosition; + } + TileHost& operator=(const TileHost& o) { + if (this == &o) { + return *this; + } + mTextureHost = o.mTextureHost; + mTextureHostOnWhite = o.mTextureHostOnWhite; + mTextureSource = o.mTextureSource; + mTextureSourceOnWhite = o.mTextureSourceOnWhite; + mTilePosition = o.mTilePosition; + 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); + } + + /** + * 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. + TileIntPoint mTilePosition; + TimeStamp mFadeStart; +}; + +class TiledLayerBufferComposite + : public TiledLayerBuffer<TiledLayerBufferComposite, TileHost> +{ + friend class TiledLayerBuffer<TiledLayerBufferComposite, TileHost>; + +public: + TiledLayerBufferComposite(); + ~TiledLayerBufferComposite(); + + bool UseTiles(const SurfaceDescriptorTiles& aTileDescriptors, + Compositor* aCompositor, + 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 SetCompositor(Compositor* aCompositor); + + 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: + ~TiledContentHost(); + +public: + virtual LayerRenderState GetRenderState() override + { + // If we have exactly one high precision tile, then we can support hwc. + if (mTiledBuffer.GetTileCount() == 1 && + mLowPrecisionTiledBuffer.GetTileCount() == 0) { + TextureHost* host = mTiledBuffer.GetTile(0).mTextureHost; + if (host) { + MOZ_ASSERT(!mTiledBuffer.GetTile(0).mTextureHostOnWhite, "Component alpha not supported!"); + + gfx::IntPoint offset = mTiledBuffer.GetTileOffset(mTiledBuffer.GetPlacement().TilePosition(0)); + + // Don't try to use HWC if the content doesn't start at the top-left of the tile. + if (offset != GetValidRegion().GetBounds().TopLeft()) { + return LayerRenderState(); + } + + LayerRenderState state = host->GetRenderState(); + state.SetOffset(offset); + return state; + } + } + return LayerRenderState(); + } + + // 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, + nsIntRegion* aUpdatedRegionBack) override + { + NS_ERROR("N/A for tiled layers"); + return false; + } + + const nsIntRegion& GetValidLowPrecisionRegion() const + { + return mLowPrecisionTiledBuffer.GetValidRegion(); + } + + const nsIntRegion& GetValidRegion() const + { + return mTiledBuffer.GetValidRegion(); + } + + virtual void SetCompositor(Compositor* aCompositor) override + { + MOZ_ASSERT(aCompositor); + CompositableHost::SetCompositor(aCompositor); + mTiledBuffer.SetCompositor(aCompositor); + mLowPrecisionTiledBuffer.SetCompositor(aCompositor); + } + + bool UseTiledLayerBuffer(ISurfaceAllocator* aAllocator, + const SurfaceDescriptorTiles& aTiledDescriptor); + + virtual void Composite(LayerComposite* aLayer, + EffectChain& aEffectChain, + float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr) override; + + virtual CompositableType GetType() override { return CompositableType::CONTENT_TILED; } + + virtual TiledContentHost* AsTiledContentHost() override { return this; } + + virtual void Attach(Layer* aLayer, + Compositor* aCompositor, + AttachFlags aFlags = NO_FLAGS) override; + + virtual void Detach(Layer* aLayer = nullptr, + AttachFlags aFlags = NO_FLAGS) override; + + virtual void Dump(std::stringstream& aStream, + const char* aPrefix="", + bool aDumpHtml=false) override; + + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + virtual void AddAnimationInvalidation(nsIntRegion& aRegion) override; + +private: + + void RenderLayerBuffer(TiledLayerBufferComposite& aLayerBuffer, + const gfx::Color* aBackgroundColor, + EffectChain& aEffectChain, + float aOpacity, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + nsIntRegion aMaskRegion, + gfx::Matrix4x4 aTransform); + + // Renders a single given tile. + void RenderTile(TileHost& aTile, + 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); + + 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..7ca42426dd --- /dev/null +++ b/gfx/layers/composite/X11TextureHost.cpp @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "X11TextureHost.h" +#include "mozilla/layers/BasicCompositor.h" +#include "mozilla/layers/X11TextureSourceBasic.h" +#ifdef GL_PROVIDER_GLX +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/X11TextureSourceOGL.h" +#endif +#include "gfxXlibSurface.h" +#include "gfx2DGlue.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +X11TextureHost::X11TextureHost(TextureFlags aFlags, + const SurfaceDescriptorX11& aDescriptor) + : TextureHost(aFlags) +{ + RefPtr<gfxXlibSurface> surface = aDescriptor.OpenForeign(); + mSurface = surface.get(); + + if (!(aFlags & TextureFlags::DEALLOCATE_CLIENT)) { + mSurface->TakePixmap(); + } +} + +bool +X11TextureHost::Lock() +{ + if (!mCompositor) { + return false; + } + + if (!mTextureSource) { + switch (mCompositor->GetBackendType()) { + case LayersBackend::LAYERS_BASIC: + mTextureSource = + new X11TextureSourceBasic(mCompositor->AsBasicCompositor(), mSurface); + break; +#ifdef GL_PROVIDER_GLX + case LayersBackend::LAYERS_OPENGL: + mTextureSource = + new X11TextureSourceOGL(mCompositor->AsCompositorOGL(), mSurface); + break; +#endif + default: + return false; + } + } + + return true; +} + +void +X11TextureHost::SetCompositor(Compositor* aCompositor) +{ + mCompositor = aCompositor; + if (mTextureSource) { + mTextureSource->SetCompositor(aCompositor); + } +} + +SurfaceFormat +X11TextureHost::GetFormat() const +{ + gfxContentType type = mSurface->GetContentType(); +#ifdef GL_PROVIDER_GLX + if (mCompositor->GetBackendType() == LayersBackend::LAYERS_OPENGL) { + return X11TextureSourceOGL::ContentTypeToSurfaceFormat(type); + } +#endif + return X11TextureSourceBasic::ContentTypeToSurfaceFormat(type); +} + +IntSize +X11TextureHost::GetSize() const +{ + 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(); +} + +} +} diff --git a/gfx/layers/composite/X11TextureHost.h b/gfx/layers/composite/X11TextureHost.h new file mode 100644 index 0000000000..1f1d34409c --- /dev/null +++ b/gfx/layers/composite/X11TextureHost.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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; + + virtual const char* Name() const override { return "X11TextureSource"; } +}; + +// TextureHost for Xlib-backed TextureSources. +class X11TextureHost : public TextureHost +{ +public: + X11TextureHost(TextureFlags aFlags, + const SurfaceDescriptorX11& aDescriptor); + + virtual void SetCompositor(Compositor* aCompositor) override; + + virtual Compositor* GetCompositor() override { return mCompositor; } + + virtual bool Lock() override; + + virtual gfx::SurfaceFormat GetFormat() const override; + + virtual gfx::IntSize GetSize() const override; + + virtual bool BindTextureSource(CompositableTextureSourceRef& aTexture) override + { + aTexture = mTextureSource; + return !!aTexture; + } + + virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override; + +#ifdef MOZ_LAYERS_HAVE_LOG + virtual const char* Name() override { return "X11TextureHost"; } +#endif + +protected: + virtual 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 diff --git a/gfx/layers/composite/qrcode_table.h b/gfx/layers/composite/qrcode_table.h new file mode 100644 index 0000000000..553be28c10 --- /dev/null +++ b/gfx/layers/composite/qrcode_table.h @@ -0,0 +1,259 @@ +const char * const sQRCodeTable[] = { + "\xFE\x5B\xFC\x16\x90\x6E\xB2\xBB\x74\xA5\xDB\xA8\xAE\xC1\x4D\x7\xFA\xAF\xE0\x1F\x0\xD3\x63\xB2\xC7\x11\x94\xE3\x6\x63\xA0\xF1\x74\xAA\x80\x78\xE3\xFA\x94\xD0\x43\xBA\xBA\x73\xFD\xD4\x83\xEE\x8A\x23\x5\x4D\x6F\xEF\x8E\x0", + "\xFE\x5B\xFC\x13\x90\x6E\xB6\xBB\x74\xA5\xDB\xA2\xAE\xC1\x5\x7\xFA\xAF\xE0\x1B\x0\xEF\xF6\x20\xA5\x11\xB4\xF2\x22\x6A\x84\x62\xF0\xAB\x80\x5A\xAB\xFA\x5D\xF0\x53\xBA\xBA\x97\x6D\xD2\x11\xAE\xA2\x23\x5\x84\x4F\xEA\xAA\x1", + "\xFE\x5B\xFC\x16\x90\x6E\xB2\xBB\x74\xA5\xDB\xA8\xAE\xC1\x4D\x7\xFA\xAF\xE0\x1F\x0\xD3\x63\xB7\xE4\x11\xAB\xCB\x7\x9B\xA0\xF8\xB6\xAA\x0\x48\xE3\xFB\x94\xD0\x4F\xBA\xBA\x13\xFD\xD7\x83\xEE\x8A\x23\x5\xD\x6F\xED\x8E\x0", + "\xFE\x5B\xFC\x13\x90\x6E\xB6\xBB\x74\xA5\xDB\xA2\xAE\xC1\x5\x7\xFA\xAF\xE0\x1B\x0\xEF\xF6\x25\x86\x11\x8B\xDA\x23\x92\x84\x6B\x32\xAB\x0\x6A\xAB\xFB\x5D\xF0\x5F\xBA\xBA\xF7\x6D\xD1\x11\xAE\xA2\x23\x5\xC4\x4F\xE8\xAA\x1", + "\xFE\x5B\xFC\x16\x90\x6E\xB2\xBB\x74\xA5\xDB\xA8\xAE\xC1\x4D\x7\xFA\xAF\xE0\x1F\x0\xD3\x63\xB1\xAD\x11\xAF\xC3\x7\xC8\x20\xFB\x6C\xAA\x80\x68\xEB\xFB\x14\xD0\x4F\xBA\xBA\x13\xFD\xD4\x83\xEE\x82\x23\x5\x4D\x6F\xE9\x8E\x0", + "\xFE\xFB\xFC\x15\x50\x6E\xA0\xBB\x75\xE5\xDB\xA0\xAE\xC1\x69\x7\xFA\xAF\xE0\xF\x0\xCE\x51\x7E\x69\xCA\xE2\xE4\xF9\x53\x4D\x4C\x7A\xE2\x80\x67\x17\xF8\xB0\x50\x5B\x28\xBA\xD3\xFD\xD0\xCA\xEE\x9C\xF9\x5\xCD\x6F\xEE\xE3\x1", + "\xFE\x5B\xFC\x13\x90\x6E\xB6\xBB\x74\xA5\xDB\xA2\xAE\xC1\x5\x7\xFA\xAF\xE0\x1B\x0\xEF\xF6\x24\x8E\x11\x82\xA2\x22\x79\x4\x62\xAE\xAA\x0\x4A\xA3\xFA\x5D\xF0\x53\xBA\xBA\xD7\x6D\xD3\x11\xAE\xA2\x23\x5\x44\x4F\xEA\xAA\x1", + "\xFE\x5B\xFC\x13\x90\x6E\xB6\xBB\x74\xA5\xDB\xA2\xAE\xC1\x5\x7\xFA\xAF\xE0\x1B\x0\xEF\xF6\x26\xEC\x11\xB0\xFA\x22\x39\x4\x61\x2A\xAB\x0\x7A\xA3\xFA\xDD\xF0\x53\xBA\xBA\x97\x6D\xD1\x11\xAE\xAA\x23\x5\xC4\x4F\xEE\xAA\x1", + "\xFE\x5B\xFC\x16\x90\x6E\xB2\xBB\x74\xA5\xDB\xA8\xAE\xC1\x4D\x7\xFA\xAF\xE0\x1F\x0\xD3\x63\xB6\x2A\x11\x81\x83\x7\x7A\xE0\xFE\xEC\xAA\x80\x58\xE7\xFA\x14\xD0\x43\xBA\xBA\x13\xFD\xD6\x83\xEE\x8A\x23\x5\xCD\x6F\xED\x8E\x0", + "\xFE\x5B\xFC\x13\x90\x6E\xB6\xBB\x74\xA5\xDB\xA2\xAE\xC1\x5\x7\xFA\xAF\xE0\x1B\x0\xEF\xF6\x24\x48\x11\xA1\x92\x23\x73\xC4\x6D\x68\xAB\x80\x7A\xAF\xFA\xDD\xF0\x53\xBA\xBA\xF7\x6D\xD0\x11\xAE\xA2\x23\x5\x4\x4F\xE8\xAA\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\xB\xAA\x82\xDD\xCA\xAB\x3B\x86\xED\x73\x80\x71\x1B\xFA\xA2\x10\x58\x46\xBA\x8A\xAD\xD3\xAA\xAE\xB5\xDB\x5\x3B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\xAE\xAA\xA5\xB5\xC9\xFB\x3B\x82\x23\x73\x80\x61\x1B\xFB\x22\x10\x58\x46\xBA\xCA\xAD\xD3\xAA\xAE\xBD\xDB\x5\xFB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x24\xAA\xBC\xD5\xCB\xFB\xFB\xB4\x69\x73\x80\x61\x1B\xFA\xA2\x10\x58\x46\xBA\xCA\xAD\xD0\xAA\xAE\xBD\xDB\x5\x3B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB3\x81\xAA\x89\xF4\xEC\xE2\xDF\x20\xA7\x73\x80\x63\x53\xFB\x6B\x30\x48\x46\xBA\x2E\x3D\xD4\x38\xEE\x95\xDB\x5\xB2\xAF\xEC\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x6E\xAA\xA7\xC5\xCA\x1B\xBB\x94\xAF\x73\x80\x41\x1B\xFB\x22\x10\x54\x46\xBA\xAA\xAD\xD2\xAA\xAE\xAD\xDB\x5\xFB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\xCB\xAA\x80\xAD\xC9\x4B\xBB\x90\x61\x73\x80\x51\x1B\xFA\xA2\x10\x54\x46\xBA\xEA\xAD\xD2\xAA\xAE\xA5\xDB\x5\x3B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\x0\xAA\x94\xBD\xCA\xF3\x7B\xAC\x6D\x72\x0\x51\x1B\xFA\xA2\x10\x58\x46\xBA\xCA\xAD\xD0\xAA\xAE\xAD\xDB\x5\x3B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\xA5\xAA\xB3\xD5\xC9\xA3\x7B\xA8\xA3\x72\x0\x41\x1B\xFB\x22\x10\x58\x46\xBA\x8A\xAD\xD0\xAA\xAE\xA5\xDB\x5\xFB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\x4A\xAA\x8F\xAD\xCB\x13\x3B\x8C\xAB\x72\x0\x71\x1B\xFB\x22\x10\x54\x46\xBA\xAA\xAD\xD2\xAA\xAE\xBD\xDB\x5\xFB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\xEF\xAA\xA8\xC5\xC8\x43\x3B\x88\x65\x72\x0\x61\x1B\xFA\xA2\x10\x54\x46\xBA\xEA\xAD\xD2\xAA\xAE\xB5\xDB\x5\x3B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x65\xAA\xB1\xA5\xCA\x43\xFB\xBE\x2F\x72\x0\x61\x1B\xFB\x22\x10\x54\x46\xBA\xEA\xAD\xD1\xAA\xAE\xB5\xDB\x5\xFB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\xC0\xAA\x96\xCD\xC9\x13\xFB\xBA\xE1\x72\x0\x71\x1B\xFA\xA2\x10\x54\x46\xBA\xAA\xAD\xD1\xAA\xAE\xBD\xDB\x5\x3B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x2F\xAA\xAA\xB5\xCB\xA3\xBB\x9E\xE9\x72\x0\x41\x1B\xFA\xA2\x10\x58\x46\xBA\x8A\xAD\xD3\xAA\xAE\xA5\xDB\x5\x3B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x8A\xAA\x8D\xDD\xC8\xF3\xBB\x9A\x27\x72\x0\x51\x1B\xFB\x22\x10\x58\x46\xBA\xCA\xAD\xD3\xAA\xAE\xAD\xDB\x5\xFB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x62\xAA\xA6\xE5\xCA\xB3\x7B\xAF\xE9\x73\x0\x61\x1B\xFA\x22\x10\x58\x46\xBA\x8A\xAD\xD2\xAA\xAE\xA5\xDB\x5\xBB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB2\xC7\xAA\x93\xC4\xED\xAA\x5F\x3B\x27\x73\x0\x63\x53\xFB\xEB\x30\x48\x46\xBA\x6E\x3D\xD6\x38\xEE\x8D\xDB\x5\x32\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x28\xAA\xBD\xF5\xCB\x53\x3B\x8F\x2F\x73\x0\x41\x1B\xFB\xA2\x10\x54\x46\xBA\xEA\xAD\xD0\xAA\xAE\xB5\xDB\x5\x7B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x8D\xAA\x9A\x9D\xC8\x3\x3B\x8B\xE1\x73\x0\x51\x1B\xFA\x22\x10\x54\x46\xBA\xAA\xAD\xD0\xAA\xAE\xBD\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x7\xAA\x83\xFD\xCA\x3\xFB\xBD\xAB\x73\x0\x51\x1B\xFB\xA2\x10\x54\x46\xBA\xAA\xAD\xD3\xAA\xAE\xBD\xDB\x5\x7B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xA2\xAA\xA4\x95\xC9\x53\xFB\xB9\x65\x73\x0\x41\x1B\xFA\x22\x10\x54\x46\xBA\xEA\xAD\xD3\xAA\xAE\xB5\xDB\x5\xBB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x4D\xAA\x98\xED\xCB\xE3\xBB\x9D\x6D\x73\x0\x71\x1B\xFA\x22\x10\x58\x46\xBA\xCA\xAD\xD1\xAA\xAE\xAD\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xE8\xAA\xBF\x85\xC8\xB3\xBB\x99\xA3\x73\x0\x61\x1B\xFB\xA2\x10\x58\x46\xBA\x8A\xAD\xD1\xAA\xAE\xA5\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x49\xAA\x90\xB5\xCA\xA0\xFB\xAF\xB7\x72\x80\x71\x13\xFA\x22\x10\x58\x46\xBA\xCA\xAD\xD3\xAA\xAE\xA5\xDB\x5\x7B\x8F\xE9\x76\x1", + "\xFE\x4B\xFC\x14\x90\x6E\x90\xBB\x75\x25\xDB\xA3\xAE\xC1\x75\x7\xFA\xAF\xE0\x7\x0\xFB\xCD\x50\xC4\x49\x39\xE5\x2A\x7A\xC3\x48\xF7\x4A\x0\x6F\x2B\xFA\x2C\x30\x4B\xC8\xBA\xE9\x25\xD7\x49\x2E\xB5\x39\x5\x83\x6F\xE9\x4E\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x3\xAA\x8B\xA5\xCB\x40\xBB\x8F\x71\x72\x80\x51\x13\xFB\xA2\x10\x54\x46\xBA\xAA\xAD\xD1\xAA\xAE\xB5\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xA6\xAA\xAC\xCD\xC8\x10\xBB\x8B\xBF\x72\x80\x41\x13\xFA\x22\x10\x54\x46\xBA\xEA\xAD\xD1\xAA\xAE\xBD\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x2C\xAA\xB5\xAD\xCA\x10\x7B\xBD\xF5\x72\x80\x41\x13\xFB\xA2\x10\x54\x46\xBA\xEA\xAD\xD2\xAA\xAE\xBD\xDB\x5\xBB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x89\xAA\x92\xC5\xC9\x40\x7B\xB9\x3B\x72\x80\x51\x13\xFA\x22\x10\x54\x46\xBA\xAA\xAD\xD2\xAA\xAE\xB5\xDB\x5\x7B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB2\x66\xAA\xBC\xF4\xEF\xB9\x1F\xD\x33\x72\x80\x73\x5B\xFA\x6B\x30\x48\x46\xBA\x2E\x3D\xD4\x38\xEE\x8D\xDB\x5\x32\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\xC3\xAA\x89\xD5\xC8\xA0\x3B\x99\xFD\x72\x80\x71\x13\xFB\xA2\x10\x58\x46\xBA\xCA\xAD\xD0\xAA\xAE\xA5\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x2B\xAA\xA2\xED\xCA\xE0\xFB\xAC\x33\x73\x80\x41\x13\xFA\xA2\x10\x58\x46\xBA\x8A\xAD\xD1\xAA\xAE\xAD\xDB\x5\xFB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x8E\xAA\x85\x85\xC9\xB0\xFB\xA8\xFD\x73\x80\x51\x13\xFB\x22\x10\x58\x46\xBA\xCA\xAD\xD1\xAA\xAE\xA5\xDB\x5\x3B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB4\x61\xAA\xAB\xB4\xEF\x49\x9F\x1C\xF5\x73\x80\x73\x5B\xFB\x6B\x30\x44\x46\xBA\x4E\x3D\xD7\x38\xEE\x9D\xDB\x5\x72\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\xC4\xAA\x9E\x95\xC8\x50\xBB\x88\x3B\x73\x80\x71\x13\xFA\xA2\x10\x54\x46\xBA\xAA\xAD\xD3\xAA\xAE\xB5\xDB\x5\xFB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\x4E\xAA\x87\xF5\xCA\x50\x7B\xBE\x71\x73\x80\x71\x13\xFB\x22\x10\x54\x46\xBA\xAA\xAD\xD0\xAA\xAE\xB5\xDB\x5\x3B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB0\xEB\xAA\xB2\xD4\xED\x49\x5F\x2A\xBF\x73\x80\x73\x5B\xFA\xEB\x30\x44\x46\xBA\x4E\x3D\xD4\x38\xEE\x9D\xDB\x5\xB2\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB0\x4\xAA\x8E\xAC\xEF\xF9\x1F\xE\xB7\x73\x80\x43\x5B\xFA\xEB\x30\x48\x46\xBA\x6E\x3D\xD6\x38\xEE\x85\xDB\x5\xB2\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB0\xA1\xAA\xA9\xC4\xEC\xA9\x1F\xA\x79\x73\x80\x53\x5B\xFB\x6B\x30\x48\x46\xBA\x2E\x3D\xD6\x38\xEE\x8D\xDB\x5\x72\xAF\xE8\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x6A\xAA\xAF\x9D\xCB\x58\xFB\xA6\x75\x72\x0\x41\x13\xFB\x22\x10\x54\x46\xBA\xAA\xAD\xD0\xAA\xAE\xA5\xDB\x5\x3B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\xCF\xAA\x88\xF5\xC8\x8\xFB\xA2\xBB\x72\x0\x51\x13\xFA\xA2\x10\x54\x46\xBA\xEA\xAD\xD0\xAA\xAE\xAD\xDB\x5\xFB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x20\xAA\xB4\x8D\xCA\xB8\xBB\x86\xB3\x72\x0\x61\x13\xFA\xA2\x10\x58\x46\xBA\xCA\xAD\xD2\xAA\xAE\xB5\xDB\x5\xFB\x8F\xE9\x76\x1", + "\xFE\xFB\xFC\x12\x90\x6E\x98\xBB\x74\xE5\xDB\xA2\xAE\xC1\xD\x7\xFA\xAF\xE0\x1F\x0\xDA\x6A\xC\x6A\x55\x54\xF9\xB8\x5A\x60\xE7\x28\x27\x0\x5C\xA7\xF8\x3E\x70\x47\xB9\xBA\xFB\x6D\xD5\xC7\x2E\x88\x8F\x5\x8D\x5F\xEB\x7\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\xF\xAA\x8A\x85\xCB\xE8\x7B\xB4\x37\x72\x0\x71\x13\xFA\xA2\x10\x58\x46\xBA\x8A\xAD\xD1\xAA\xAE\xBD\xDB\x5\xFB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\xAA\xAA\xAD\xED\xC8\xB8\x7B\xB0\xF9\x72\x0\x61\x13\xFB\x22\x10\x58\x46\xBA\xCA\xAD\xD1\xAA\xAE\xB5\xDB\x5\x3B\x8F\xED\x76\x1", + "\xFE\x7B\xFC\x12\x90\x6E\x90\xBB\x75\xE5\xDB\xAB\xAE\xC1\x15\x7\xFA\xAF\xE0\xF\x0\xC7\x48\xC0\xAA\x55\x5F\xAD\x29\xA2\x81\x71\xA4\x27\x0\x7F\xAB\xFA\xAC\x30\x5B\xB9\xBA\x9\x25\xD1\x41\x2E\x98\x8F\x5\x81\x6F\xEF\x4E\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\xE0\xAA\xB6\xFD\xC9\x58\x3B\x90\x3F\x72\x0\x41\x13\xFA\xA2\x10\x54\x46\xBA\xAA\xAD\xD3\xAA\xAE\xA5\xDB\x5\xFB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x8\xAA\x9D\xC5\xCB\x18\xFB\xA5\xF1\x73\x0\x71\x13\xFB\xA2\x10\x54\x46\xBA\xEA\xAD\xD2\xAA\xAE\xAD\xDB\x5\xBB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\xAD\xAA\xBA\xAD\xC8\x48\xFB\xA1\x3F\x73\x0\x61\x13\xFA\x22\x10\x54\x46\xBA\xAA\xAD\xD2\xAA\xAE\xA5\xDB\x5\x7B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x42\xAA\x86\xD5\xCA\xF8\xBB\x85\x37\x73\x0\x51\x13\xFA\x22\x10\x58\x46\xBA\x8A\xAD\xD0\xAA\xAE\xBD\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\xE7\xAA\xA1\xBD\xC9\xA8\xBB\x81\xF9\x73\x0\x41\x13\xFB\xA2\x10\x58\x46\xBA\xCA\xAD\xD0\xAA\xAE\xB5\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB5\x6D\xAA\xAA\x94\xEF\xE1\x5F\x27\xB3\x73\x0\x53\x5B\xFA\x6B\x30\x48\x46\xBA\x6E\x3D\xD7\x38\xEE\x95\xDB\x5\x32\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB5\xC8\xAA\x8D\xFC\xEC\xB1\x5F\x23\x7D\x73\x0\x43\x5B\xFB\xEB\x30\x48\x46\xBA\x2E\x3D\xD7\x38\xEE\x9D\xDB\x5\xF2\xAF\xE8\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\x27\xAA\xA3\xCD\xCA\x48\x3B\x97\x75\x73\x0\x61\x13\xFB\xA2\x10\x54\x46\xBA\xAA\xAD\xD1\xAA\xAE\xA5\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\x82\xAA\x84\xA5\xC9\x18\x3B\x93\xBB\x73\x0\x71\x13\xFA\x22\x10\x54\x46\xBA\xEA\xAD\xD1\xAA\xAE\xAD\xDB\x5\x7B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\xCE\xAA\xBE\xF5\xCA\x12\x3B\xAA\x37\x72\x80\x41\x1F\xFB\x22\x10\x54\x46\xBA\xCA\xAD\xD1\xAA\xAE\xAD\xDB\x5\xFB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB1\x6B\xAA\x8B\xD4\xED\xB\x1F\x3E\xF9\x72\x80\x43\x57\xFA\xEB\x30\x44\x46\xBA\x2E\x3D\xD5\x38\xEE\x85\xDB\x5\x72\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x84\xAA\xA5\xE5\xCB\xF2\x7B\x8A\xF1\x72\x80\x61\x1F\xFA\xA2\x10\x58\x46\xBA\xAA\xAD\xD3\xAA\xAE\xBD\xDB\x5\x3B\x8F\xEF\x76\x1", + "\xFE\x4B\xFC\x14\x90\x6E\x90\xBB\x75\x25\xDB\xA3\xAE\xC1\x75\x7\xFA\xAF\xE0\x7\x0\xFB\xCD\x57\x9\x49\xC\xB5\x2B\x28\x43\x6D\xB1\x4A\x0\x7F\x27\xFA\xAC\x30\x4B\xC8\xBA\x89\x25\xD7\x49\x2E\xAD\x39\x5\xC3\x6F\xEF\x4E\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB5\xAB\xAA\x89\xA4\xEE\xEB\x9F\x28\x75\x72\x80\x63\x57\xFA\xEB\x30\x48\x46\xBA\x4E\x3D\xD4\x38\xEE\x95\xDB\x5\x72\xAF\xEC\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB5\xE\xAA\xAE\xCC\xED\xBB\x9F\x2C\xBB\x72\x80\x73\x57\xFB\x6B\x30\x48\x46\xBA\xE\x3D\xD4\x38\xEE\x9D\xDB\x5\xB2\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\xE1\xAA\x80\xFD\xCB\x42\xFB\x98\xB3\x72\x80\x51\x1F\xFB\x22\x10\x54\x46\xBA\x8A\xAD\xD2\xAA\xAE\xA5\xDB\x5\xFB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\x44\xAA\xA7\x95\xC8\x12\xFB\x9C\x7D\x72\x80\x41\x1F\xFA\xA2\x10\x54\x46\xBA\xCA\xAD\xD2\xAA\xAE\xAD\xDB\x5\x3B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\xAC\xAA\x8C\xAD\xCA\x52\x3B\xA9\xB3\x73\x80\x71\x1F\xFB\xA2\x10\x54\x46\xBA\x8A\xAD\xD3\xAA\xAE\xA5\xDB\x5\x7B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x9\xAA\xAB\xC5\xC9\x2\x3B\xAD\x7D\x73\x80\x61\x1F\xFA\x22\x10\x54\x46\xBA\xCA\xAD\xD3\xAA\xAE\xAD\xDB\x5\xBB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\xE6\xAA\x97\xBD\xCB\xB2\x7B\x89\x75\x73\x80\x51\x1F\xFA\x22\x10\x58\x46\xBA\xEA\xAD\xD1\xAA\xAE\xB5\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x43\xAA\xB0\xD5\xC8\xE2\x7B\x8D\xBB\x73\x80\x41\x1F\xFB\xA2\x10\x58\x46\xBA\xAA\xAD\xD1\xAA\xAE\xBD\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\xC9\xAA\xA9\xB5\xCA\xE2\xBB\xBB\xF1\x73\x80\x41\x1F\xFA\x22\x10\x58\x46\xBA\xAA\xAD\xD2\xAA\xAE\xBD\xDB\x5\xBB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\x6C\xAA\x8E\xDD\xC9\xB2\xBB\xBF\x3F\x73\x80\x51\x1F\xFB\xA2\x10\x58\x46\xBA\xEA\xAD\xD2\xAA\xAE\xB5\xDB\x5\x7B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\x83\xAA\xB2\xA5\xCB\x2\xFB\x9B\x37\x73\x80\x61\x1F\xFB\xA2\x10\x54\x46\xBA\xCA\xAD\xD0\xAA\xAE\xAD\xDB\x5\x7B\x8F\xEB\x76\x1", + "\xFE\xFB\xFC\x12\x90\x6E\x98\xBB\x74\xE5\xDB\xA2\xAE\xC1\xD\x7\xFA\xAF\xE0\x1F\x0\xDA\x6A\x8\xC9\x55\x52\xD1\xB9\xE0\x20\xFA\xAC\x26\x80\x5C\xAB\xF9\x3E\x70\x4B\xB9\xBA\xFB\x6D\xD7\xC7\x2E\x90\x8F\x5\xD\x5F\xE9\x7\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\xED\xAA\x81\xDD\xCB\xEA\x3B\xA3\xF5\x72\x0\x71\x1F\xFA\x22\x10\x58\x46\xBA\xAA\xAD\xD2\xAA\xAE\xAD\xDB\x5\xBB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x48\xAA\xA6\xB5\xC8\xBA\x3B\xA7\x3B\x72\x0\x61\x1F\xFB\xA2\x10\x58\x46\xBA\xEA\xAD\xD2\xAA\xAE\xA5\xDB\x5\x7B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\xA7\xAA\x9A\xCD\xCA\xA\x7B\x83\x33\x72\x0\x51\x1F\xFB\xA2\x10\x54\x46\xBA\xCA\xAD\xD0\xAA\xAE\xBD\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB4\x2\xAA\xAF\xEC\xED\x13\x5F\x17\xFD\x72\x0\x53\x57\xFA\x6B\x30\x44\x46\xBA\x2E\x3D\xD4\x38\xEE\x95\xDB\x5\xF2\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\x88\xAA\xA4\xC5\xCB\x5A\xBB\xB1\xB7\x72\x0\x41\x1F\xFB\xA2\x10\x54\x46\xBA\x8A\xAD\xD3\xAA\xAE\xB5\xDB\x5\x7B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\x2D\xAA\x83\xAD\xC8\xA\xBB\xB5\x79\x72\x0\x51\x1F\xFA\x22\x10\x54\x46\xBA\xCA\xAD\xD3\xAA\xAE\xBD\xDB\x5\xBB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB0\xC2\xAA\xAD\x9C\xEE\xF3\xDF\x1\x71\x72\x0\x73\x57\xFA\x6B\x30\x48\x46\xBA\x4E\x3D\xD5\x38\xEE\x85\xDB\x5\xF2\xAF\xEC\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB0\x67\xAA\x8A\xF4\xED\xA3\xDF\x5\xBF\x72\x0\x63\x57\xFB\xEB\x30\x48\x46\xBA\xE\x3D\xD5\x38\xEE\x8D\xDB\x5\x32\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB6\x8F\xAA\xA1\xCC\xEF\xE3\x1F\x30\x71\x73\x0\x53\x57\xFA\xEB\x30\x48\x46\xBA\x4E\x3D\xD4\x38\xEE\x85\xDB\x5\x72\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x2A\xAA\x94\xED\xC8\xFA\x3B\xA4\xBF\x73\x0\x51\x1F\xFB\x22\x10\x58\x46\xBA\xAA\xAD\xD0\xAA\xAE\xAD\xDB\x5\xFB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xC5\xAA\xA8\x95\xCA\x4A\x7B\x80\xB7\x73\x0\x61\x1F\xFB\x22\x10\x54\x46\xBA\x8A\xAD\xD2\xAA\xAE\xB5\xDB\x5\xFB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x60\xAA\x8F\xFD\xC9\x1A\x7B\x84\x79\x73\x0\x71\x1F\xFA\xA2\x10\x54\x46\xBA\xCA\xAD\xD2\xAA\xAE\xBD\xDB\x5\x3B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\xEA\xAA\x96\x9D\xCB\x1A\xBB\xB2\x33\x73\x0\x71\x1F\xFB\x22\x10\x54\x46\xBA\xCA\xAD\xD1\xAA\xAE\xBD\xDB\x5\xFB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB2\x4F\xAA\xA3\xBC\xEC\x3\x9F\x26\xFD\x73\x0\x73\x57\xFA\xEB\x30\x44\x46\xBA\x2E\x3D\xD5\x38\xEE\x95\xDB\x5\x72\xAF\xEC\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\xA0\xAA\x8D\x8D\xCA\xFA\xFB\x92\xF5\x73\x0\x51\x1F\xFA\xA2\x10\x58\x46\xBA\xAA\xAD\xD3\xAA\xAE\xAD\xDB\x5\x3B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x5\xAA\xAA\xE5\xC9\xAA\xFB\x96\x3B\x73\x0\x41\x1F\xFB\x22\x10\x58\x46\xBA\xEA\xAD\xD3\xAA\xAE\xA5\xDB\x5\xFB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\xA4\xAA\x85\xD5\xCB\xB9\xBB\xA0\x2F\x72\x80\x51\x17\xFA\xA2\x10\x58\x46\xBA\xAA\xAD\xD1\xAA\xAE\xA5\xDB\x5\xFB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x1\xAA\xA2\xBD\xC8\xE9\xBB\xA4\xE1\x72\x80\x41\x17\xFB\x22\x10\x58\x46\xBA\xEA\xAD\xD1\xAA\xAE\xAD\xDB\x5\x3B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\xEE\xAA\x9E\xC5\xCA\x59\xFB\x80\xE9\x72\x80\x71\x17\xFB\x22\x10\x54\x46\xBA\xCA\xAD\xD3\xAA\xAE\xB5\xDB\x5\x3B\x8F\xE9\x76\x1", + "\xFE\x8B\xFC\x16\x90\x6E\xBE\xBB\x75\x55\xDB\xA7\xAE\xC1\x55\x7\xFA\xAF\xE0\x0\x0\xCE\x9\x7F\xED\x71\xD4\x9B\x13\x9B\xB2\xA0\xB5\x3B\x80\x4C\xA3\xF9\xCF\xB0\x50\xD4\xBA\xAE\x3D\xD1\x71\xEE\x8B\x1\x5\xB2\xAF\xED\x3F\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB5\x27\xAA\xA4\xCC\xEA\xB2\xDF\x2D\x23\x73\x80\x73\x53\xFB\xEB\x70\x48\x46\xBA\x6E\x3D\xD4\x38\xEE\x8D\xDB\x5\xF2\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x64\xAA\x3D\xBD\xCE\x79\x3B\xB3\x79\x73\x80\x71\x1B\xFA\x22\x50\x5C\x46\xBA\xAA\xAD\xD2\xAA\xAE\xA5\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\x4C\xAA\xF3\xED\xCE\x6A\xFB\xB0\x2F\x73\x80\x41\x1B\xFB\x22\x50\x5C\x46\xBA\xAA\xAD\xD3\xAA\xAE\xAD\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB1\xF\xAA\x6A\x9C\xEA\xA1\x1F\x2E\x75\x73\x80\x43\x53\xFA\xEB\x70\x48\x46\xBA\x6E\x3D\xD5\x38\xEE\x85\xDB\x5\xF2\xAF\xE8\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\xED\xAA\x9F\xBD\xC9\x3B\x3B\xB5\x7B\x73\x80\x41\x1B\xFB\x22\x50\x50\x46\xBA\xAA\xAD\xD1\xAA\xAE\xB5\xDB\x5\x3B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\xAE\xAA\x14\x85\xC9\xB9\xFB\xBB\x21\x73\x80\x51\x1B\xFA\xA2\x50\x54\x46\xBA\xCA\xAD\xD3\xAA\xAE\xBD\xDB\x5\x3B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x86\xAA\xDA\xD5\xC9\xAA\x3B\xB8\x77\x73\x80\x61\x1B\xFB\xA2\x50\x54\x46\xBA\xCA\xAD\xD2\xAA\xAE\xB5\xDB\x5\x3B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\xC5\xAA\x51\xED\xC9\x28\xFB\xB6\x2D\x73\x80\x71\x1B\xFA\x22\x50\x50\x46\xBA\xAA\xAD\xD0\xAA\xAE\xBD\xDB\x5\x3B\x8F\xEB\x76\x1", + "\xFE\x4B\xFC\x14\x90\x6E\x90\xBB\x75\x25\xDB\xA3\xAE\xC1\x75\x7\xFA\xAF\xE0\x7\x0\xFB\xCD\x53\xAA\x49\x1F\xD5\x2E\x21\xC3\x5A\x63\x4B\x0\x7F\x23\xFB\xAC\x70\x4B\xC8\xBA\xE9\x25\xD4\x49\x2E\xBD\x39\x5\x43\x6F\xEF\x4E\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\xC1\xAA\x1A\xD5\xCD\x29\x3B\xB7\xB7\x73\x80\x61\x1B\xFB\xA2\x50\x5C\x46\xBA\xEA\xAD\xD2\xAA\xAE\xAD\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB7\xE9\xAA\xC6\xCC\xE9\x73\xDF\x24\xE1\x73\x80\x43\x53\xFA\xEB\x70\x4C\x46\xBA\x4E\x3D\xD7\x38\xEE\x85\xDB\x5\x32\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\xAA\xAA\x5F\xBD\xCD\xB8\x3B\xBA\xBB\x73\x80\x41\x1B\xFB\x22\x50\x58\x46\xBA\x8A\xAD\xD1\xAA\xAE\xAD\xDB\x5\x7B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x2\xAA\xA3\xC5\xCB\x8B\x7B\x91\x73\x73\x80\x71\x1B\xFB\x22\x50\x5C\x46\xBA\x8A\xAD\xD3\xAA\xAE\xAD\xDB\x5\x3B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x41\xAA\x28\xFD\xCB\x9\xBB\x9F\x29\x73\x80\x61\x1B\xFA\xA2\x50\x58\x46\xBA\xEA\xAD\xD1\xAA\xAE\xA5\xDB\x5\x3B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x69\xAA\xE6\xAD\xCB\x1A\x7B\x9C\x7F\x73\x80\x51\x1B\xFB\xA2\x50\x58\x46\xBA\xEA\xAD\xD0\xAA\xAE\xAD\xDB\x5\x3B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\x2A\xAA\x6D\x95\xCB\x98\xBB\x92\x25\x73\x80\x41\x1B\xFA\x22\x50\x5C\x46\xBA\x8A\xAD\xD2\xAA\xAE\xA5\xDB\x5\x3B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\x6D\xAA\xAD\x95\xCF\x1B\xBB\x9D\xE5\x73\x80\x41\x1B\xFA\x22\x50\x54\x46\xBA\xAA\xAD\xD2\xAA\xAE\xBD\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x2E\xAA\x26\xAD\xCF\x99\x7B\x93\xBF\x73\x80\x51\x1B\xFB\xA2\x50\x50\x46\xBA\xCA\xAD\xD0\xAA\xAE\xB5\xDB\x5\x7B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\x6\xAA\xE8\xFD\xCF\x8A\xBB\x90\xE9\x73\x80\x61\x1B\xFA\xA2\x50\x50\x46\xBA\xCA\xAD\xD1\xAA\xAE\xBD\xDB\x5\x7B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x45\xAA\x63\xC5\xCF\x8\x7B\x9E\xB3\x73\x80\x71\x1B\xFB\x22\x50\x54\x46\xBA\xAA\xAD\xD3\xAA\xAE\xB5\xDB\x5\x7B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\xA7\xAA\x84\xAD\xC8\xDB\x7B\x95\xBD\x73\x80\x61\x1B\xFA\xA2\x50\x5C\x46\xBA\xCA\xAD\xD3\xAA\xAE\xA5\xDB\x5\xFB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB2\xE4\xAA\x1D\xDC\xEC\x10\x9F\xB\xE7\x73\x80\x63\x53\xFB\x6B\x70\x48\x46\xBA\xE\x3D\xD5\x38\xEE\x8D\xDB\x5\xB2\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xCC\xAA\xC1\xC5\xC8\x4A\x7B\x98\xB1\x73\x80\x41\x1B\xFA\x22\x50\x58\x46\xBA\xAA\xAD\xD0\xAA\xAE\xA5\xDB\x5\xFB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\x8F\xAA\x4A\xFD\xC8\xC8\xBB\x96\xEB\x73\x80\x51\x1B\xFB\xA2\x50\x5C\x46\xBA\xCA\xAD\xD2\xAA\xAE\xAD\xDB\x5\xFB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\xC8\xAA\x8A\xFD\xCC\x4B\xBB\x99\x2B\x73\x80\x51\x1B\xFB\xA2\x50\x54\x46\xBA\xEA\xAD\xD2\xAA\xAE\xB5\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x8B\xFC\x16\x90\x6E\xBE\xBB\x75\x55\xDB\xA7\xAE\xC1\x55\x7\xFA\xAF\xE0\x0\x0\xCE\x9\x7E\x2D\x71\x6C\xF3\x16\x5B\x32\xB3\xE3\x3A\x80\x6C\xAF\xF9\x4F\xF0\x54\xD4\xBA\xAE\x3D\xD2\x71\xEE\x8B\x1\x5\xF2\xAF\xED\x3F\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\xA3\xAA\xCF\x95\xCC\xDA\xBB\x94\x27\x73\x80\x71\x1B\xFB\x22\x50\x50\x46\xBA\x8A\xAD\xD1\xAA\xAE\xB5\xDB\x5\xBB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\xE0\xAA\x44\xAD\xCC\x58\x7B\x9A\x7D\x73\x80\x61\x1B\xFA\xA2\x50\x54\x46\xBA\xEA\xAD\xD3\xAA\xAE\xBD\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB7\x6C\xAA\x82\xF4\xEF\x2A\x9F\x39\xB1\x72\x0\x73\x53\xFA\xEB\x70\x40\x46\xBA\x4E\x3D\xD5\x38\xEE\x8D\xDB\x5\xB2\xAF\xEC\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB1\x2F\xAA\x9\xCC\xEF\xA8\x5F\x37\xEB\x72\x0\x63\x53\xFB\x6B\x70\x44\x46\xBA\x2E\x3D\xD7\x38\xEE\x85\xDB\x5\xB2\xAF\xE8\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB5\x7\xAA\xC7\x9C\xEF\xBB\x9F\x34\xBD\x72\x0\x53\x53\xFA\x6B\x70\x44\x46\xBA\x2E\x3D\xD6\x38\xEE\x8D\xDB\x5\xB2\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB3\x44\xAA\x4C\xA4\xEF\x39\x5F\x3A\xE7\x72\x0\x43\x53\xFB\xEB\x70\x40\x46\xBA\x4E\x3D\xD4\x38\xEE\x85\xDB\x5\xB2\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x3\xAA\x9E\xED\xCF\xF3\x7B\xA5\x27\x72\x0\x51\x1B\xFB\xA2\x50\x58\x46\xBA\xCA\xAD\xD0\xAA\xAE\xBD\xDB\x5\xBB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\x40\xAA\x15\xD5\xCF\x71\xBB\xAB\x7D\x72\x0\x41\x1B\xFA\x22\x50\x5C\x46\xBA\xAA\xAD\xD2\xAA\xAE\xB5\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x68\xAA\xDB\x85\xCF\x62\x7B\xA8\x2B\x72\x0\x71\x1B\xFB\x22\x50\x5C\x46\xBA\xAA\xAD\xD3\xAA\xAE\xBD\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x2B\xAA\x50\xBD\xCF\xE0\xBB\xA6\x71\x72\x0\x61\x1B\xFA\xA2\x50\x58\x46\xBA\xCA\xAD\xD1\xAA\xAE\xB5\xDB\x5\xBB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB7\xC9\xAA\xA5\x9C\xEC\x7A\x9F\x3D\x7F\x72\x0\x63\x53\xFB\x6B\x70\x40\x46\xBA\xE\x3D\xD5\x38\xEE\x85\xDB\x5\x72\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x8A\xAA\x3C\xED\xC8\xB1\x7B\xA3\x25\x72\x0\x61\x1B\xFA\xA2\x50\x54\x46\xBA\xCA\xAD\xD3\xAA\xAE\xAD\xDB\x5\x3B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\xA2\xAA\xF2\xBD\xC8\xA2\xBB\xA0\x73\x72\x0\x51\x1B\xFB\xA2\x50\x54\x46\xBA\xCA\xAD\xD2\xAA\xAE\xA5\xDB\x5\x3B\x8F\xE9\x76\x1", + "\xFE\x8B\xFC\x16\x90\x6E\xBE\xBB\x75\x55\xDB\xA7\xAE\xC1\x55\x7\xFA\xAF\xE0\x0\x0\xCE\x9\x7E\x47\x71\x14\xB3\x12\xB2\x32\x8A\xBB\x3B\x0\x6C\xAF\xF9\x4F\xF0\x54\xD4\xBA\x8E\x3D\xD2\x71\xEE\x9B\x1\x5\x72\xAF\xEF\x3F\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB6\xA6\xAA\xAB\xCC\xE8\xEA\x5F\x31\xE9\x72\x0\x53\x53\xFA\x6B\x70\x48\x46\xBA\x2E\x3D\xD4\x38\xEE\x95\xDB\x5\x32\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\xE5\xAA\x32\xBD\xCC\x21\xBB\xAF\xB3\x72\x0\x51\x1B\xFB\xA2\x50\x5C\x46\xBA\xEA\xAD\xD2\xAA\xAE\xBD\xDB\x5\x7B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\xCD\xAA\xFC\xED\xCC\x32\x7B\xAC\xE5\x72\x0\x61\x1B\xFA\xA2\x50\x5C\x46\xBA\xEA\xAD\xD3\xAA\xAE\xB5\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x8E\xAA\x77\xD5\xCC\xB0\xBB\xA2\xBF\x72\x0\x71\x1B\xFB\x22\x50\x58\x46\xBA\x8A\xAD\xD1\xAA\xAE\xBD\xDB\x5\x7B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB7\x26\xAA\x99\xE4\xEE\xCA\xDF\x19\x77\x72\x0\x53\x53\xFB\x6B\x70\x4C\x46\xBA\x2E\x3D\xD7\x38\xEE\x9D\xDB\x5\x72\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB1\x65\xAA\x12\xDC\xEE\x48\x1F\x17\x2D\x72\x0\x43\x53\xFA\xEB\x70\x48\x46\xBA\x4E\x3D\xD5\x38\xEE\x95\xDB\x5\x72\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\x4D\xAA\xCE\xC5\xCA\x12\xFB\x84\x7B\x72\x0\x61\x1B\xFB\xA2\x50\x58\x46\xBA\xEA\xAD\xD0\xAA\xAE\xBD\xDB\x5\x3B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\xE\xAA\x45\xFD\xCA\x90\x3B\x8A\x21\x72\x0\x71\x1B\xFA\x22\x50\x5C\x46\xBA\x8A\xAD\xD2\xAA\xAE\xB5\xDB\x5\x3B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x49\xAA\x85\xFD\xCE\x13\x3B\x85\xE1\x72\x0\x71\x1B\xFA\x22\x50\x54\x46\xBA\xAA\xAD\xD2\xAA\xAE\xAD\xDB\x5\x7B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\xA\xAA\xE\xC5\xCE\x91\xFB\x8B\xBB\x72\x0\x61\x1B\xFB\xA2\x50\x50\x46\xBA\xCA\xAD\xD0\xAA\xAE\xA5\xDB\x5\x7B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB4\x22\xAA\xD2\xDC\xEA\xCB\x1F\x18\xED\x72\x0\x43\x53\xFA\xEB\x70\x40\x46\xBA\x6E\x3D\xD5\x38\xEE\x8D\xDB\x5\x32\xAF\xE8\x52\x0", + "\xFE\x4B\xFC\x14\x90\x6E\x90\xBB\x75\x25\xDB\xA3\xAE\xC1\x75\x7\xFA\xAF\xE0\x7\x0\xFB\xCD\x54\x49\x49\xC5\x95\x2D\x8A\xC3\x65\x39\x4A\x80\x4F\x23\xFA\xAC\x70\x47\xC8\xBA\xC9\x25\xD7\x49\x2E\xBD\x39\x5\x43\x6F\xEB\x4E\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\x83\xAA\xAC\xC5\xC9\xD3\xFB\x8D\xB9\x72\x0\x51\x1B\xFA\xA2\x50\x5C\x46\xBA\xCA\xAD\xD3\xAA\xAE\xB5\xDB\x5\xFB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB1\xC0\xAA\x35\xB4\xED\x18\x1F\x13\xE3\x72\x0\x53\x53\xFB\x6B\x70\x48\x46\xBA\xE\x3D\xD5\x38\xEE\x9D\xDB\x5\xB2\xAF\xEC\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\xE8\xAA\xE9\xAD\xC9\x42\xFB\x80\xB5\x72\x0\x71\x1B\xFA\x22\x50\x58\x46\xBA\xAA\xAD\xD0\xAA\xAE\xB5\xDB\x5\xFB\x8F\xEB\x76\x1", + "\xFE\x4B\xFC\x14\x90\x6E\x90\xBB\x75\x25\xDB\xA3\xAE\xC1\x75\x7\xFA\xAF\xE0\x7\x0\xFB\xCD\x55\x83\x49\xEC\xAD\x2A\x4A\x3\x6D\x61\x4A\x80\x6F\x23\xFA\x2C\x70\x4F\xC8\xBA\xA9\x25\xD6\x49\x2E\xA5\x39\x5\xC3\x6F\xE9\x4E\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xEC\xAA\xA2\x95\xCD\x43\x3B\x81\x2F\x72\x0\x61\x1B\xFB\xA2\x50\x54\x46\xBA\xEA\xAD\xD2\xAA\xAE\xA5\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x4B\xFC\x14\x90\x6E\x90\xBB\x75\x25\xDB\xA3\xAE\xC1\x75\x7\xFA\xAF\xE0\x7\x0\xFB\xCD\x56\x87\x49\xA7\x95\x2E\x4B\xC3\x6C\xFB\x4A\x80\x7F\x23\xFB\xAC\x70\x43\xC8\xBA\xE9\x25\xD4\x49\x2E\xB5\x39\x5\x83\x6F\xEF\x4E\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x87\xAA\xE7\xFD\xCD\xD2\x3B\x8C\x23\x72\x0\x41\x1B\xFB\x22\x50\x50\x46\xBA\x8A\xAD\xD1\xAA\xAE\xA5\xDB\x5\xBB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\xC4\xAA\x6C\xC5\xCD\x50\xFB\x82\x79\x72\x0\x51\x1B\xFA\xA2\x50\x54\x46\xBA\xEA\xAD\xD3\xAA\xAE\xAD\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x9\xAA\xB5\xA5\xCB\xD3\x3B\xBB\xF3\x72\x0\x51\x1B\xFB\x22\x50\x5C\x46\xBA\xCA\xAD\xD0\xAA\xAE\xB5\xDB\x5\x3B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB5\x4A\xAA\x2C\xD4\xEF\x18\xDF\x25\xA9\x72\x0\x53\x53\xFA\xEB\x70\x48\x46\xBA\xE\x3D\xD6\x38\xEE\x9D\xDB\x5\x72\xAF\xE8\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x62\xAA\xF0\xCD\xCB\x42\x3B\xB6\xFF\x72\x0\x71\x1B\xFB\xA2\x50\x58\x46\xBA\xAA\xAD\xD3\xAA\xAE\xB5\xDB\x5\x3B\x8F\xEF\x76\x1", + "\xFE\x4B\xFC\x14\x90\x6E\x90\xBB\x75\x25\xDB\xA3\xAE\xC1\x75\x7\xFA\xAF\xE0\x7\x0\xFB\xCD\x51\x9\x49\xF5\xCD\x28\x4A\xC3\x5B\x2B\x4A\x80\x6F\x23\xFB\xAC\x70\x4F\xC8\xBA\xA9\x25\xD5\x49\x2E\xA5\x39\x5\x3\x6F\xED\x4E\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB2\x66\xAA\xA9\xBC\xEB\xA\xDF\x27\x65\x72\x0\x73\x53\xFA\x6B\x70\x44\x46\xBA\x4E\x3D\xD5\x38\xEE\x85\xDB\x5\x32\xAF\xE8\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x25\xAA\x30\xCD\xCF\xC1\x3B\xB9\x3F\x72\x0\x71\x1B\xFB\xA2\x50\x50\x46\xBA\x8A\xAD\xD3\xAA\xAE\xAD\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\xD\xAA\xFE\x9D\xCF\xD2\xFB\xBA\x69\x72\x0\x41\x1B\xFA\xA2\x50\x50\x46\xBA\x8A\xAD\xD2\xAA\xAE\xA5\xDB\x5\x7B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB6\x4E\xAA\x67\xEC\xEB\x19\x1F\x24\x33\x72\x0\x43\x53\xFB\x6B\x70\x44\x46\xBA\x4E\x3D\xD4\x38\xEE\x8D\xDB\x5\x32\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB3\xAC\xAA\x80\x84\xEC\xCA\x1F\x2F\x3D\x72\x0\x53\x53\xFA\xEB\x70\x4C\x46\xBA\x2E\x3D\xD4\x38\xEE\x9D\xDB\x5\xB2\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB5\xEF\xAA\xB\xBC\xEC\x48\xDF\x21\x67\x72\x0\x43\x53\xFB\x6B\x70\x48\x46\xBA\x4E\x3D\xD6\x38\xEE\x95\xDB\x5\xB2\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB1\xC7\xAA\xC5\xEC\xEC\x5B\x1F\x22\x31\x72\x0\x73\x53\xFA\x6B\x70\x48\x46\xBA\x4E\x3D\xD7\x38\xEE\x9D\xDB\x5\xB2\xAF\xE8\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB7\x84\xAA\x4E\xD4\xEC\xD9\xDF\x2C\x6B\x72\x0\x63\x53\xFB\xEB\x70\x4C\x46\xBA\x2E\x3D\xD5\x38\xEE\x95\xDB\x5\xB2\xAF\xEC\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\xC3\xAA\x9C\x9D\xCC\x13\xFB\xB3\xAB\x72\x0\x71\x1B\xFB\xA2\x50\x54\x46\xBA\xAA\xAD\xD1\xAA\xAE\xAD\xDB\x5\xBB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x80\xAA\x17\xA5\xCC\x91\x3B\xBD\xF1\x72\x0\x61\x1B\xFA\x22\x50\x50\x46\xBA\xCA\xAD\xD3\xAA\xAE\xA5\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\xA8\xAA\xD9\xF5\xCC\x82\xFB\xBE\xA7\x72\x0\x51\x1B\xFB\x22\x50\x50\x46\xBA\xCA\xAD\xD2\xAA\xAE\xAD\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xEB\xAA\x52\xCD\xCC\x0\x3B\xB0\xFD\x72\x0\x41\x1B\xFA\xA2\x50\x54\x46\xBA\xAA\xAD\xD0\xAA\xAE\xA5\xDB\x5\xBB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x43\xAA\xAE\xB5\xCA\x33\x7B\x9B\x35\x72\x0\x71\x1B\xFA\xA2\x50\x50\x46\xBA\xAA\xAD\xD2\xAA\xAE\xA5\xDB\x5\xFB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\x0\xAA\x25\x8D\xCA\xB1\xBB\x95\x6F\x72\x0\x61\x1B\xFB\x22\x50\x54\x46\xBA\xCA\xAD\xD0\xAA\xAE\xAD\xDB\x5\xFB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x28\xAA\xEB\xDD\xCA\xA2\x7B\x96\x39\x72\x0\x51\x1B\xFA\x22\x50\x54\x46\xBA\xCA\xAD\xD1\xAA\xAE\xA5\xDB\x5\xFB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\x6B\xAA\x60\xE5\xCA\x20\xBB\x98\x63\x72\x0\x41\x1B\xFB\xA2\x50\x50\x46\xBA\xAA\xAD\xD3\xAA\xAE\xAD\xDB\x5\xFB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x2C\xAA\xA0\xE5\xCE\xA3\xBB\x97\xA3\x72\x0\x41\x1B\xFB\xA2\x50\x58\x46\xBA\x8A\xAD\xD3\xAA\xAE\xB5\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB4\x6F\xAA\x39\x94\xEA\x68\x5F\x9\xF9\x72\x0\x43\x53\xFA\x6B\x70\x4C\x46\xBA\x4E\x3D\xD5\x38\xEE\x9D\xDB\x5\xF2\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\x47\xAA\xE5\x8D\xCE\x32\xBB\x9A\xAF\x72\x0\x61\x1B\xFB\x22\x50\x5C\x46\xBA\xEA\xAD\xD0\xAA\xAE\xB5\xDB\x5\xBB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x4\xAA\x6E\xB5\xCE\xB0\x7B\x94\xF5\x72\x0\x71\x1B\xFA\xA2\x50\x58\x46\xBA\x8A\xAD\xD2\xAA\xAE\xBD\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\xE6\xAA\x89\xDD\xC9\x63\x7B\x9F\xFB\x72\x0\x61\x1B\xFB\x22\x50\x50\x46\xBA\xEA\xAD\xD2\xAA\xAE\xAD\xDB\x5\x3B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\xA5\xAA\x2\xE5\xC9\xE1\xBB\x91\xA1\x72\x0\x71\x1B\xFA\xA2\x50\x54\x46\xBA\x8A\xAD\xD0\xAA\xAE\xA5\xDB\x5\x3B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x8D\xAA\xCC\xB5\xC9\xF2\x7B\x92\xF7\x72\x0\x41\x1B\xFB\xA2\x50\x54\x46\xBA\x8A\xAD\xD1\xAA\xAE\xAD\xDB\x5\x3B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\xCE\xAA\x47\x8D\xC9\x70\xBB\x9C\xAD\x72\x0\x51\x1B\xFA\x22\x50\x50\x46\xBA\xEA\xAD\xD3\xAA\xAE\xA5\xDB\x5\x3B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x89\xAA\x87\x8D\xCD\xF3\xBB\x93\x6D\x72\x0\x51\x1B\xFA\x22\x50\x58\x46\xBA\xCA\xAD\xD3\xAA\xAE\xBD\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\xCA\xAA\xC\xB5\xCD\x71\x7B\x9D\x37\x72\x0\x41\x1B\xFB\xA2\x50\x5C\x46\xBA\xAA\xAD\xD1\xAA\xAE\xB5\xDB\x5\x7B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\xE2\xAA\xC2\xE5\xCD\x62\xBB\x9E\x61\x72\x0\x71\x1B\xFA\xA2\x50\x5C\x46\xBA\xAA\xAD\xD0\xAA\xAE\xBD\xDB\x5\x7B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xA1\xAA\x49\xDD\xCD\xE0\x7B\x90\x3B\x72\x0\x61\x1B\xFB\x22\x50\x58\x46\xBA\xCA\xAD\xD2\xAA\xAE\xB5\xDB\x5\x7B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\xE\xAA\xA2\xE5\xCB\x23\xBB\xAA\x35\x73\x0\x51\x1B\xFA\x22\x50\x50\x46\xBA\xAA\xAD\xD3\xAA\xAE\xA5\xDB\x5\x7B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x4D\xAA\x29\xDD\xCB\xA1\x7B\xA4\x6F\x73\x0\x41\x1B\xFB\xA2\x50\x54\x46\xBA\xCA\xAD\xD1\xAA\xAE\xAD\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\x65\xAA\xE7\x8D\xCB\xB2\xBB\xA7\x39\x73\x0\x71\x1B\xFA\xA2\x50\x54\x46\xBA\xCA\xAD\xD0\xAA\xAE\xA5\xDB\x5\x7B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x26\xAA\x6C\xB5\xCB\x30\x7B\xA9\x63\x73\x0\x61\x1B\xFB\x22\x50\x50\x46\xBA\xAA\xAD\xD2\xAA\xAE\xAD\xDB\x5\x7B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x61\xAA\xAC\xB5\xCF\xB3\x7B\xA6\xA3\x73\x0\x61\x1B\xFB\x22\x50\x58\x46\xBA\x8A\xAD\xD2\xAA\xAE\xB5\xDB\x5\x3B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB2\x22\xAA\x35\xC4\xEB\x78\x9F\x38\xF9\x73\x0\x63\x53\xFA\xEB\x70\x4C\x46\xBA\x4E\x3D\xD4\x38\xEE\x9D\xDB\x5\x72\xAF\xE8\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xA\xAA\xE9\xDD\xCF\x22\x7B\xAB\xAF\x73\x0\x41\x1B\xFB\xA2\x50\x5C\x46\xBA\xEA\xAD\xD1\xAA\xAE\xB5\xDB\x5\x3B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\x49\xAA\x62\xE5\xCF\xA0\xBB\xA5\xF5\x73\x0\x51\x1B\xFA\x22\x50\x58\x46\xBA\x8A\xAD\xD3\xAA\xAE\xBD\xDB\x5\x3B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB5\xAB\xAA\x97\xC4\xEC\x3A\x9F\x3E\xFB\x73\x0\x53\x53\xFB\xEB\x70\x40\x46\xBA\x4E\x3D\xD7\x38\xEE\x8D\xDB\x5\xF2\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\xE8\xAA\xE\xB5\xC8\xF1\x7B\xA0\xA1\x73\x0\x51\x1B\xFA\x22\x50\x54\x46\xBA\x8A\xAD\xD1\xAA\xAE\xA5\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\xC0\xAA\xC0\xE5\xC8\xE2\xBB\xA3\xF7\x73\x0\x61\x1B\xFB\x22\x50\x54\x46\xBA\x8A\xAD\xD0\xAA\xAE\xAD\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x83\xAA\x4B\xDD\xC8\x60\x7B\xAD\xAD\x73\x0\x71\x1B\xFA\xA2\x50\x50\x46\xBA\xEA\xAD\xD2\xAA\xAE\xA5\xDB\x5\xBB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB4\xC4\xAA\x99\x94\xE8\xAA\x5F\x32\x6D\x73\x0\x63\x53\xFA\xEB\x70\x48\x46\xBA\x6E\x3D\xD6\x38\xEE\x9D\xDB\x5\xB2\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x87\xAA\x0\xE5\xCC\x61\xBB\xAC\x37\x73\x0\x61\x1B\xFB\x22\x50\x5C\x46\xBA\xAA\xAD\xD0\xAA\xAE\xB5\xDB\x5\xFB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xAF\xAA\xCE\xB5\xCC\x72\x7B\xAF\x61\x73\x0\x51\x1B\xFA\x22\x50\x5C\x46\xBA\xAA\xAD\xD1\xAA\xAE\xBD\xDB\x5\xFB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\xEC\xAA\x45\x8D\xCC\xF0\xBB\xA1\x3B\x73\x0\x41\x1B\xFB\xA2\x50\x58\x46\xBA\xCA\xAD\xD3\xAA\xAE\xB5\xDB\x5\xFB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\x44\xAA\xB9\xF5\xCA\xC3\xFB\x8A\xF3\x73\x0\x71\x1B\xFB\xA2\x50\x5C\x46\xBA\xCA\xAD\xD1\xAA\xAE\xB5\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\xFB\xFC\x16\xD0\x6E\x9C\xBB\x74\xB5\xDB\xA8\xAE\xC1\x51\x7\xFA\xAF\xE0\x1E\x0\xE6\xFF\x99\xAD\x0\xA7\x98\x9E\xEB\x91\x21\xFC\x26\x0\x4B\xB3\xF9\x77\x10\x52\xEC\xBA\x7F\xFD\xD1\x0\x2E\xA8\x8F\x5\x11\x2F\xEA\x23\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\x2F\xAA\xFC\x9D\xCA\x52\xFB\x87\xFF\x73\x0\x51\x1B\xFB\x22\x50\x58\x46\xBA\xAA\xAD\xD2\xAA\xAE\xB5\xDB\x5\xBB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x6C\xAA\x77\xA5\xCA\xD0\x3B\x89\xA5\x73\x0\x41\x1B\xFA\xA2\x50\x5C\x46\xBA\xCA\xAD\xD0\xAA\xAE\xBD\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB4\x2B\xAA\xA5\xEC\xEA\x1A\x1F\x16\x65\x73\x0\x53\x53\xFA\xEB\x70\x44\x46\xBA\x4E\x3D\xD4\x38\xEE\x85\xDB\x5\xB2\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB2\x68\xAA\x2E\xD4\xEA\x98\xDF\x18\x3F\x73\x0\x43\x53\xFB\x6B\x70\x40\x46\xBA\x2E\x3D\xD6\x38\xEE\x8D\xDB\x5\xB2\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x40\xAA\xF2\xCD\xCE\xC2\x3B\x8B\x69\x73\x0\x61\x1B\xFA\x22\x50\x50\x46\xBA\x8A\xAD\xD3\xAA\xAE\xA5\xDB\x5\xFB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\x3\xAA\x79\xF5\xCE\x40\xFB\x85\x33\x73\x0\x71\x1B\xFB\xA2\x50\x54\x46\xBA\xEA\xAD\xD1\xAA\xAE\xAD\xDB\x5\xFB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB5\xE1\xAA\x8C\xD4\xED\xDA\xDF\x1E\x3D\x73\x0\x73\x53\xFA\x6B\x70\x4C\x46\xBA\x2E\x3D\xD5\x38\xEE\x9D\xDB\x5\x32\xAF\xEC\x52\x0", + "\xFE\x4B\xFC\x14\x90\x6E\x90\xBB\x75\x25\xDB\xA3\xAE\xC1\x75\x7\xFA\xAF\xE0\x7\x0\xFB\xCD\x55\x8A\x49\x9B\x9D\x2A\x9B\x3\x63\xE9\x4B\x80\x7F\x23\xFA\x2C\x70\x4B\xC8\xBA\x89\x25\xD7\x49\x2E\xAD\x39\x5\x43\x6F\xEF\x4E\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\x8A\xAA\xDB\xF5\xC9\x2\xFB\x83\x31\x73\x0\x41\x1B\xFA\xA2\x50\x58\x46\xBA\xEA\xAD\xD2\xAA\xAE\xBD\xDB\x5\x7B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB1\xC9\xAA\x42\x84\xED\xC9\x1F\x1D\x6B\x73\x0\x43\x53\xFB\x6B\x70\x4C\x46\xBA\x2E\x3D\xD4\x38\xEE\x95\xDB\x5\x32\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x8E\xAA\x90\xCD\xCD\x3\x3B\x82\xAB\x73\x0\x51\x1B\xFB\x22\x50\x54\x46\xBA\xAA\xAD\xD0\xAA\xAE\xAD\xDB\x5\x3B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB2\xCD\xAA\x9\xBC\xE9\xC8\xDF\x1C\xF1\x73\x0\x53\x53\xFA\xEB\x70\x40\x46\xBA\x6E\x3D\xD6\x38\xEE\x85\xDB\x5\x72\xAF\xEC\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xE5\xAA\xD5\xA5\xCD\x92\x3B\x8F\xA7\x73\x0\x71\x1B\xFB\xA2\x50\x50\x46\xBA\xCA\xAD\xD3\xAA\xAE\xAD\xDB\x5\x3B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\xA6\xAA\x5E\x9D\xCD\x10\xFB\x81\xFD\x73\x0\x61\x1B\xFA\x22\x50\x54\x46\xBA\xAA\xAD\xD1\xAA\xAE\xA5\xDB\x5\x3B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB1\x6B\xAA\x95\xB4\xEF\xDA\x1F\x28\x77\x73\x0\x73\x53\xFB\xEB\x70\x4C\x46\xBA\x2E\x3D\xD6\x38\xEE\x9D\xDB\x5\xF2\xAF\xE8\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\x28\xAA\xC\xC5\xCB\x11\xFB\xB6\x2D\x73\x0\x71\x1B\xFA\x22\x50\x58\x46\xBA\xEA\xAD\xD0\xAA\xAE\xB5\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x0\xAA\xC2\x95\xCB\x2\x3B\xB5\x7B\x73\x0\x41\x1B\xFB\x22\x50\x58\x46\xBA\xEA\xAD\xD1\xAA\xAE\xBD\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\x43\xAA\x49\xAD\xCB\x80\xFB\xBB\x21\x73\x0\x51\x1B\xFA\xA2\x50\x5C\x46\xBA\x8A\xAD\xD3\xAA\xAE\xB5\xDB\x5\xBB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\x4\xAA\x89\xAD\xCF\x3\xFB\xB4\xE1\x73\x0\x51\x1B\xFA\xA2\x50\x54\x46\xBA\xAA\xAD\xD3\xAA\xAE\xAD\xDB\x5\xFB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\x47\xAA\x2\x95\xCF\x81\x3B\xBA\xBB\x73\x0\x41\x1B\xFB\x22\x50\x50\x46\xBA\xCA\xAD\xD1\xAA\xAE\xA5\xDB\x5\xFB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x6F\xAA\xCC\xC5\xCF\x92\xFB\xB9\xED\x73\x0\x71\x1B\xFA\x22\x50\x50\x46\xBA\xCA\xAD\xD0\xAA\xAE\xAD\xDB\x5\xFB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x2C\xAA\x47\xFD\xCF\x10\x3B\xB7\xB7\x73\x0\x61\x1B\xFB\xA2\x50\x54\x46\xBA\xAA\xAD\xD2\xAA\xAE\xA5\xDB\x5\xFB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\xCE\xAA\xA0\x95\xC8\xC3\x3B\xBC\xB9\x73\x0\x71\x1B\xFA\x22\x50\x5C\x46\xBA\xCA\xAD\xD2\xAA\xAE\xB5\xDB\x5\x7B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x27\x8D\xAA\x2B\xAD\xC8\x41\xFB\xB2\xE3\x73\x0\x61\x1B\xFB\xA2\x50\x58\x46\xBA\xAA\xAD\xD0\xAA\xAE\xBD\xDB\x5\x7B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\xA5\xAA\xE5\xFD\xC8\x52\x3B\xB1\xB5\x73\x0\x51\x1B\xFA\xA2\x50\x58\x46\xBA\xAA\xAD\xD1\xAA\xAE\xB5\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\xE6\xAA\x6E\xC5\xC8\xD0\xFB\xBF\xEF\x73\x0\x41\x1B\xFB\x22\x50\x5C\x46\xBA\xCA\xAD\xD3\xAA\xAE\xBD\xDB\x5\x7B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\xA1\xAA\xAE\xC5\xCC\x53\xFB\xB0\x2F\x73\x0\x41\x1B\xFB\x22\x50\x54\x46\xBA\xEA\xAD\xD3\xAA\xAE\xA5\xDB\x5\x3B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xE2\xAA\x25\xFD\xCC\xD1\x3B\xBE\x75\x73\x0\x51\x1B\xFA\xA2\x50\x50\x46\xBA\x8A\xAD\xD1\xAA\xAE\xAD\xDB\x5\x3B\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\xCA\xAA\xEB\xAD\xCC\xC2\xFB\xBD\x23\x73\x0\x61\x1B\xFB\xA2\x50\x50\x46\xBA\x8A\xAD\xD0\xAA\xAE\xA5\xDB\x5\x3B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\x89\xAA\x60\x95\xCC\x40\x3B\xB3\x79\x73\x0\x71\x1B\xFA\x22\x50\x54\x46\xBA\xEA\xAD\xD2\xAA\xAE\xAD\xDB\x5\x3B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x21\xAA\x9C\xED\xCA\x73\x7B\x98\xB1\x73\x0\x41\x1B\xFA\x22\x50\x50\x46\xBA\xEA\xAD\xD0\xAA\xAE\xAD\xDB\x5\x7B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB7\x62\xAA\x5\x9C\xEE\xB8\x9F\x6\xEB\x73\x0\x43\x53\xFB\xEB\x70\x44\x46\xBA\x2E\x3D\xD6\x38\xEE\x85\xDB\x5\x32\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\x4A\xAA\xD9\x85\xCA\xE2\x7B\x95\xBD\x73\x0\x61\x1B\xFA\xA2\x50\x54\x46\xBA\x8A\xAD\xD3\xAA\xAE\xAD\xDB\x5\x7B\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\x9\xAA\x52\xBD\xCA\x60\xBB\x9B\xE7\x73\x0\x71\x1B\xFB\x22\x50\x50\x46\xBA\xEA\xAD\xD1\xAA\xAE\xA5\xDB\x5\x7B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB0\x4E\xAA\x80\xF4\xEA\xAA\x9F\x4\x27\x73\x0\x63\x53\xFB\x6B\x70\x48\x46\xBA\x6E\x3D\xD5\x38\xEE\x9D\xDB\x5\x72\xAF\xEE\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x26\xD\xAA\x19\x85\xCE\x61\x7B\x9A\x7D\x73\x0\x61\x1B\xFA\xA2\x50\x5C\x46\xBA\xAA\xAD\xD3\xAA\xAE\xB5\xDB\x5\x3B\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x22\x25\xAA\xD7\xD5\xCE\x72\xBB\x99\x2B\x73\x0\x51\x1B\xFB\xA2\x50\x5C\x46\xBA\xAA\xAD\xD2\xAA\xAE\xBD\xDB\x5\x3B\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB4\x66\xAA\x4E\xA4\xEA\xB9\x5F\x7\x71\x73\x0\x53\x53\xFA\x6B\x70\x48\x46\xBA\x6E\x3D\xD4\x38\xEE\x95\xDB\x5\x72\xAF\xE8\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x84\xAA\xBB\x85\xC9\x23\x7B\x9C\x7F\x73\x0\x51\x1B\xFB\xA2\x50\x50\x46\xBA\xAA\xAD\xD0\xAA\xAE\xA5\xDB\x5\xBB\x8F\xED\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB7\xC7\xAA\x22\xF4\xED\xE8\x9F\x2\x25\x73\x0\x53\x53\xFA\x6B\x70\x44\x46\xBA\x6E\x3D\xD6\x38\xEE\x8D\xDB\x5\xF2\xAF\xE8\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x23\xEF\xAA\xFE\xED\xC9\xB2\x7B\x91\x73\x73\x0\x71\x1B\xFB\x22\x50\x54\x46\xBA\xCA\xAD\xD3\xAA\xAE\xA5\xDB\x5\xBB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x25\xAC\xAA\x75\xD5\xC9\x30\xBB\x9F\x29\x73\x0\x61\x1B\xFA\xA2\x50\x50\x46\xBA\xAA\xAD\xD1\xAA\xAE\xAD\xDB\x5\xBB\x8F\xEB\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x20\xEB\xAA\xB5\xD5\xCD\xB3\xBB\x90\xE9\x73\x0\x61\x1B\xFA\xA2\x50\x58\x46\xBA\x8A\xAD\xD1\xAA\xAE\xB5\xDB\x5\xFB\x8F\xE9\x76\x1", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB6\xA8\xAA\x2C\xA4\xE9\x78\x5F\xE\xB3\x73\x0\x63\x53\xFB\x6B\x70\x4C\x46\xBA\x4E\x3D\xD7\x38\xEE\x9D\xDB\x5\xB2\xAF\xEC\x52\x0", + "\xFE\x2B\xFC\x15\x50\x6E\xAC\xBB\x74\x15\xDB\xAF\xAE\xC1\x71\x7\xFA\xAF\xE0\x10\x0\xD3\x3B\xB2\x80\xAA\xE2\xF4\xE9\x6B\x9F\xD\xE5\x73\x0\x53\x53\xFA\x6B\x70\x4C\x46\xBA\x4E\x3D\xD6\x38\xEE\x95\xDB\x5\xB2\xAF\xEA\x52\x0", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x24\xC3\xAA\x7B\x85\xCD\xA0\x7B\x93\xBF\x73\x0\x51\x1B\xFB\xA2\x50\x58\x46\xBA\x8A\xAD\xD0\xAA\xAE\xBD\xDB\x5\xFB\x8F\xEF\x76\x1", + "\xFE\x2B\xFC\x10\x50\x6E\xA8\xBB\x74\x15\xDB\xA5\xAE\xC1\x39\x7\xFA\xAF\xE0\x14\x0\xEF\xAE\x21\x25\xAA\x94\xB5\xCB\x30\x3B\xAA\x6B\x72\x80\x41\x13\xFA\x22\x50\x50\x46\xBA\xEA\xAD\xD2\xAA\xAE\xA5\xDB\x5\xBB\x8F\xE9\x76\x1", +}; |