summaryrefslogtreecommitdiffstats
path: root/gfx/layers/wr/WebRenderCommandBuilder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/wr/WebRenderCommandBuilder.cpp')
-rw-r--r--gfx/layers/wr/WebRenderCommandBuilder.cpp2959
1 files changed, 2959 insertions, 0 deletions
diff --git a/gfx/layers/wr/WebRenderCommandBuilder.cpp b/gfx/layers/wr/WebRenderCommandBuilder.cpp
new file mode 100644
index 0000000000..f280bb8a0c
--- /dev/null
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -0,0 +1,2959 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebRenderCommandBuilder.h"
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/SVGGeometryFrame.h"
+#include "mozilla/SVGImageFrame.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/image/WebRenderImageProvider.h"
+#include "mozilla/layers/AnimationHelper.h"
+#include "mozilla/layers/ClipManager.h"
+#include "mozilla/layers/ImageClient.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/layers/IpcResourceUpdateQueue.h"
+#include "mozilla/layers/SharedSurfacesChild.h"
+#include "mozilla/layers/SourceSurfaceSharedData.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/UpdateImageHelper.h"
+#include "mozilla/layers/WebRenderDrawEventRecorder.h"
+#include "UnitTransforms.h"
+#include "gfxEnv.h"
+#include "nsDisplayListInvalidation.h"
+#include "nsLayoutUtils.h"
+#include "nsTHashSet.h"
+#include "WebRenderCanvasRenderer.h"
+
+#include <cstdint>
+
+namespace mozilla {
+namespace layers {
+
+using namespace gfx;
+using namespace image;
+static int sIndent;
+#include <stdarg.h>
+#include <stdio.h>
+
+static void GP(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+#if 0
+ for (int i = 0; i < sIndent; i++) { printf(" "); }
+ vprintf(fmt, args);
+#endif
+ va_end(args);
+}
+
+bool FitsInt32(const float aVal) {
+ // Although int32_t min and max can't be represented exactly with floats, the
+ // cast truncates towards zero which is what we want here.
+ const float min = static_cast<float>(std::numeric_limits<int32_t>::min());
+ const float max = static_cast<float>(std::numeric_limits<int32_t>::max());
+ return aVal > min && aVal < max;
+}
+
+// XXX: problems:
+// - How do we deal with scrolling while having only a single invalidation rect?
+// We can have a valid rect and an invalid rect. As we scroll the valid rect
+// will move and the invalid rect will be the new area
+
+struct BlobItemData;
+static void DestroyBlobGroupDataProperty(nsTArray<BlobItemData*>* aArray);
+NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(BlobGroupDataProperty,
+ nsTArray<BlobItemData*>,
+ DestroyBlobGroupDataProperty);
+
+// These are currently manually allocated and ownership is help by the
+// mDisplayItems hash table in DIGroup
+struct BlobItemData {
+ // a weak pointer to the frame for this item.
+ // DisplayItemData has a mFrameList to deal with merged frames. Hopefully we
+ // don't need to worry about that.
+ nsIFrame* mFrame;
+
+ uint32_t mDisplayItemKey;
+ nsTArray<BlobItemData*>*
+ mArray; // a weak pointer to the array that's owned by the frame property
+
+ LayerIntRect mRect;
+ // It would be nice to not need this. We need to be able to call
+ // ComputeInvalidationRegion. ComputeInvalidationRegion will sometimes reach
+ // into parent style structs to get information that can change the
+ // invalidation region
+ UniquePtr<nsDisplayItemGeometry> mGeometry;
+ DisplayItemClip mClip;
+ bool mInvisible;
+ bool mUsed; // initialized near construction
+ // XXX: only used for debugging
+ bool mInvalid;
+
+ // a weak pointer to the group that owns this item
+ // we use this to track whether group for a particular item has changed
+ struct DIGroup* mGroup;
+
+ // We need to keep a list of all the external surfaces used by the blob image.
+ // We do this on a per-display item basis so that the lists remains correct
+ // during invalidations.
+ std::vector<RefPtr<SourceSurface>> mExternalSurfaces;
+
+ BlobItemData(DIGroup* aGroup, nsDisplayItem* aItem)
+ : mInvisible(false), mUsed(false), mGroup(aGroup) {
+ mInvalid = false;
+ mDisplayItemKey = aItem->GetPerFrameKey();
+ AddFrame(aItem->Frame());
+ }
+
+ private:
+ void AddFrame(nsIFrame* aFrame) {
+ mFrame = aFrame;
+
+ nsTArray<BlobItemData*>* array =
+ aFrame->GetProperty(BlobGroupDataProperty());
+ if (!array) {
+ array = new nsTArray<BlobItemData*>();
+ aFrame->SetProperty(BlobGroupDataProperty(), array);
+ }
+ array->AppendElement(this);
+ mArray = array;
+ }
+
+ public:
+ void ClearFrame() {
+ // Delete the weak pointer to this BlobItemData on the frame
+ MOZ_RELEASE_ASSERT(mFrame);
+ // the property may already be removed if WebRenderUserData got deleted
+ // first so we use our own mArray pointer.
+ mArray->RemoveElement(this);
+
+ // drop the entire property if nothing's left in the array
+ if (mArray->IsEmpty()) {
+ // If the frame is in the process of being destroyed this will fail
+ // but that's ok, because the the property will be removed then anyways
+ mFrame->RemoveProperty(BlobGroupDataProperty());
+ }
+ mFrame = nullptr;
+ }
+
+ ~BlobItemData() {
+ if (mFrame) {
+ ClearFrame();
+ }
+ }
+};
+
+static BlobItemData* GetBlobItemData(nsDisplayItem* aItem) {
+ nsIFrame* frame = aItem->Frame();
+ uint32_t key = aItem->GetPerFrameKey();
+ const nsTArray<BlobItemData*>* array =
+ frame->GetProperty(BlobGroupDataProperty());
+ if (array) {
+ for (BlobItemData* item : *array) {
+ if (item->mDisplayItemKey == key) {
+ return item;
+ }
+ }
+ }
+ return nullptr;
+}
+
+// We keep around the BlobItemData so that when we invalidate it get properly
+// included in the rect
+static void DestroyBlobGroupDataProperty(nsTArray<BlobItemData*>* aArray) {
+ for (BlobItemData* item : *aArray) {
+ GP("DestroyBlobGroupDataProperty: %p-%d\n", item->mFrame,
+ item->mDisplayItemKey);
+ item->mFrame = nullptr;
+ }
+ delete aArray;
+}
+
+static void TakeExternalSurfaces(
+ WebRenderDrawEventRecorder* aRecorder,
+ std::vector<RefPtr<SourceSurface>>& aExternalSurfaces,
+ RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources) {
+ aRecorder->TakeExternalSurfaces(aExternalSurfaces);
+
+ for (auto& surface : aExternalSurfaces) {
+ // While we don't use the image key with the surface, because the blob image
+ // renderer doesn't have easy access to the resource set, we still want to
+ // ensure one is generated. That will ensure the surface remains alive until
+ // at least the last epoch which the blob image could be used in.
+ wr::ImageKey key;
+ DebugOnly<nsresult> rv =
+ SharedSurfacesChild::Share(surface, aManager, aResources, key);
+ MOZ_ASSERT(rv.value != NS_ERROR_NOT_IMPLEMENTED);
+ }
+}
+
+struct DIGroup;
+struct Grouper {
+ explicit Grouper(ClipManager& aClipManager)
+ : mAppUnitsPerDevPixel(0),
+ mDisplayListBuilder(nullptr),
+ mClipManager(aClipManager) {}
+
+ int32_t mAppUnitsPerDevPixel;
+ nsDisplayListBuilder* mDisplayListBuilder;
+ ClipManager& mClipManager;
+ HitTestInfoManager mHitTestInfoManager;
+ Matrix mTransform;
+
+ // Paint the list of aChildren display items.
+ void PaintContainerItem(DIGroup* aGroup, nsDisplayItem* aItem,
+ BlobItemData* aData, const IntRect& aItemBounds,
+ bool aDirty, nsDisplayList* aChildren,
+ gfxContext* aContext,
+ WebRenderDrawEventRecorder* aRecorder,
+ RenderRootStateManager* aRootManager,
+ wr::IpcResourceUpdateQueue& aResources);
+
+ // Builds groups of display items split based on 'layer activity'
+ void ConstructGroups(nsDisplayListBuilder* aDisplayListBuilder,
+ WebRenderCommandBuilder* aCommandBuilder,
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, DIGroup* aGroup,
+ nsDisplayList* aList, nsDisplayItem* aWrappingItem,
+ const StackingContextHelper& aSc);
+ // Builds a group of display items without promoting anything to active.
+ bool ConstructGroupInsideInactive(WebRenderCommandBuilder* aCommandBuilder,
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ DIGroup* aGroup, nsDisplayList* aList,
+ const StackingContextHelper& aSc);
+ // Helper method for processing a single inactive item
+ bool ConstructItemInsideInactive(WebRenderCommandBuilder* aCommandBuilder,
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ DIGroup* aGroup, nsDisplayItem* aItem,
+ const StackingContextHelper& aSc,
+ bool* aOutIsInvisible);
+ ~Grouper() = default;
+};
+
+// Returns whether this is an item for which complete invalidation was
+// reliant on LayerTreeInvalidation in the pre-webrender world.
+static bool IsContainerLayerItem(nsDisplayItem* aItem) {
+ switch (aItem->GetType()) {
+ case DisplayItemType::TYPE_WRAP_LIST:
+ case DisplayItemType::TYPE_CONTAINER:
+ case DisplayItemType::TYPE_TRANSFORM:
+ case DisplayItemType::TYPE_OPACITY:
+ case DisplayItemType::TYPE_FILTER:
+ case DisplayItemType::TYPE_BLEND_CONTAINER:
+ case DisplayItemType::TYPE_BLEND_MODE:
+ case DisplayItemType::TYPE_MASK:
+ case DisplayItemType::TYPE_PERSPECTIVE: {
+ return true;
+ }
+ default: {
+ return false;
+ }
+ }
+}
+
+#include <sstream>
+
+static bool DetectContainerLayerPropertiesBoundsChange(
+ nsDisplayItem* aItem, BlobItemData* aData,
+ nsDisplayItemGeometry& aGeometry) {
+ if (aItem->GetType() == DisplayItemType::TYPE_FILTER) {
+ // Filters get clipped to the BuildingRect since they can
+ // have huge bounds outside of the visible area.
+ aGeometry.mBounds = aGeometry.mBounds.Intersect(aItem->GetBuildingRect());
+ }
+
+ return !aGeometry.mBounds.IsEqualEdges(aData->mGeometry->mBounds);
+}
+
+/* A Display Item Group. This represents a set of diplay items that
+ * have been grouped together for rasterization and can be partially
+ * invalidated. It also tracks a number of properties from the environment
+ * that when changed would cause us to repaint like mScale. */
+struct DIGroup {
+ // XXX: Storing owning pointers to the BlobItemData in a hash table is not
+ // a good choice. There are two better options:
+ //
+ // 1. We should just be using a linked list for this stuff.
+ // That we can iterate over only the used items.
+ // We remove from the unused list and add to the used list
+ // when we see an item.
+ //
+ // we allocate using a free list.
+ //
+ // 2. We can use a Vec and use SwapRemove().
+ // We'll just need to be careful when iterating.
+ // The advantage of a Vec is that everything stays compact
+ // and we don't need to heap allocate the BlobItemData's
+ nsTHashSet<BlobItemData*> mDisplayItems;
+
+ LayerIntRect mInvalidRect;
+ LayerIntRect mVisibleRect;
+ // This is the last visible rect sent to WebRender. It's used
+ // to compute the invalid rect and ensure that we send
+ // the appropriate data to WebRender for merging.
+ LayerIntRect mLastVisibleRect;
+
+ // This is the intersection of mVisibleRect and mLastVisibleRect
+ // we ensure that mInvalidRect is contained in mPreservedRect
+ LayerIntRect mPreservedRect;
+ // mHitTestBounds is the same as mActualBounds except for the bounds
+ // of invisible items which are accounted for in the former but not
+ // in the latter.
+ LayerIntRect mHitTestBounds;
+ LayerIntRect mActualBounds;
+ int32_t mAppUnitsPerDevPixel;
+ gfx::MatrixScales mScale;
+ ScrollableLayerGuid::ViewID mScrollId;
+ CompositorHitTestInfo mHitInfo;
+ LayerPoint mResidualOffset;
+ LayerIntRect mLayerBounds; // mGroupBounds converted to Layer space
+ // mLayerBounds clipped to the container/parent of the
+ // current item being processed.
+ LayerIntRect mClippedImageBounds; // mLayerBounds with the clipping of any
+ // containers applied
+ Maybe<wr::BlobImageKey> mKey;
+ std::vector<RefPtr<ScaledFont>> mFonts;
+
+ DIGroup()
+ : mAppUnitsPerDevPixel(0),
+ mScrollId(ScrollableLayerGuid::NULL_SCROLL_ID),
+ mHitInfo(CompositorHitTestInvisibleToHit) {}
+
+ void InvalidateRect(const LayerIntRect& aRect) {
+ auto r = aRect.Intersect(mPreservedRect);
+ // Empty rects get dropped
+ if (!r.IsEmpty()) {
+ mInvalidRect = mInvalidRect.Union(r);
+ }
+ }
+
+ LayerIntRect ItemBounds(nsDisplayItem* aItem) {
+ BlobItemData* data = GetBlobItemData(aItem);
+ return data->mRect;
+ }
+
+ void ClearItems() {
+ GP("items: %d\n", mDisplayItems.Count());
+ for (BlobItemData* data : mDisplayItems) {
+ GP("Deleting %p-%d\n", data->mFrame, data->mDisplayItemKey);
+ delete data;
+ }
+ mDisplayItems.Clear();
+ }
+
+ void ClearImageKey(RenderRootStateManager* aManager, bool aForce = false) {
+ if (mKey) {
+ MOZ_RELEASE_ASSERT(aForce || mInvalidRect.IsEmpty());
+ aManager->AddBlobImageKeyForDiscard(*mKey);
+ mKey = Nothing();
+ }
+ mFonts.clear();
+ }
+
+ static LayerIntRect ToDeviceSpace(nsRect aBounds, Matrix& aMatrix,
+ int32_t aAppUnitsPerDevPixel) {
+ // RoundedOut can convert empty rectangles to non-empty ones
+ // so special case them here
+ if (aBounds.IsEmpty()) {
+ return LayerIntRect();
+ }
+ return LayerIntRect::FromUnknownRect(RoundedOut(aMatrix.TransformBounds(
+ ToRect(nsLayoutUtils::RectToGfxRect(aBounds, aAppUnitsPerDevPixel)))));
+ }
+
+ bool ComputeGeometryChange(nsDisplayItem* aItem, BlobItemData* aData,
+ Matrix& aMatrix, nsDisplayListBuilder* aBuilder) {
+ // If the frame is marked as invalidated, and didn't specify a rect to
+ // invalidate then we want to invalidate both the old and new bounds,
+ // otherwise we only want to invalidate the changed areas. If we do get an
+ // invalid rect, then we want to add this on top of the change areas.
+ nsRect invalid;
+ bool invalidated = false;
+ const DisplayItemClip& clip = aItem->GetClip();
+
+ int32_t appUnitsPerDevPixel =
+ aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+ MOZ_RELEASE_ASSERT(mAppUnitsPerDevPixel == appUnitsPerDevPixel);
+ GP("\n");
+ GP("clippedImageRect %d %d %d %d\n", mClippedImageBounds.x,
+ mClippedImageBounds.y, mClippedImageBounds.width,
+ mClippedImageBounds.height);
+ LayerIntSize size = mVisibleRect.Size();
+ GP("imageSize: %d %d\n", size.width, size.height);
+ /*if (aItem->IsReused() && aData->mGeometry) {
+ return;
+ }*/
+
+ GP("pre mInvalidRect: %s %p-%d - inv: %d %d %d %d\n", aItem->Name(),
+ aItem->Frame(), aItem->GetPerFrameKey(), mInvalidRect.x, mInvalidRect.y,
+ mInvalidRect.width, mInvalidRect.height);
+ if (!aData->mGeometry) {
+ // This item is being added for the first time, invalidate its entire
+ // area.
+ UniquePtr<nsDisplayItemGeometry> geometry(
+ aItem->AllocateGeometry(aBuilder));
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ geometry->ComputeInvalidationRegion());
+ aData->mGeometry = std::move(geometry);
+
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ aData->mRect = transformedRect.Intersect(mClippedImageBounds);
+ GP("CGC %s %d %d %d %d\n", aItem->Name(), clippedBounds.x,
+ clippedBounds.y, clippedBounds.width, clippedBounds.height);
+ GP("%d %d, %f %f\n", mVisibleRect.TopLeft().x.value,
+ mVisibleRect.TopLeft().y.value, aMatrix._11, aMatrix._22);
+ GP("mRect %d %d %d %d\n", aData->mRect.x, aData->mRect.y,
+ aData->mRect.width, aData->mRect.height);
+ InvalidateRect(aData->mRect);
+ aData->mInvalid = true;
+ invalidated = true;
+ } else if (aItem->IsInvalid(invalid) && invalid.IsEmpty()) {
+ UniquePtr<nsDisplayItemGeometry> geometry(
+ aItem->AllocateGeometry(aBuilder));
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ geometry->ComputeInvalidationRegion());
+ aData->mGeometry = std::move(geometry);
+
+ GP("matrix: %f %f\n", aMatrix._31, aMatrix._32);
+ GP("frame invalid invalidate: %s\n", aItem->Name());
+ GP("old rect: %d %d %d %d\n", aData->mRect.x, aData->mRect.y,
+ aData->mRect.width, aData->mRect.height);
+ InvalidateRect(aData->mRect);
+ // We want to snap to outside pixels. When should we multiply by the
+ // matrix?
+ // XXX: TransformBounds is expensive. We should avoid doing it if we have
+ // no transform
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ aData->mRect = transformedRect.Intersect(mClippedImageBounds);
+ InvalidateRect(aData->mRect);
+ GP("new rect: %d %d %d %d\n", aData->mRect.x, aData->mRect.y,
+ aData->mRect.width, aData->mRect.height);
+ aData->mInvalid = true;
+ invalidated = true;
+ } else {
+ GP("else invalidate: %s\n", aItem->Name());
+ nsRegion combined;
+ // this includes situations like reflow changing the position
+ aItem->ComputeInvalidationRegion(aBuilder, aData->mGeometry.get(),
+ &combined);
+ if (!combined.IsEmpty()) {
+ // There might be no point in doing this elaborate tracking here to get
+ // smaller areas
+ InvalidateRect(aData->mRect); // invalidate the old area -- in theory
+ // combined should take care of this
+ UniquePtr<nsDisplayItemGeometry> geometry(
+ aItem->AllocateGeometry(aBuilder));
+ // invalidate the invalidated area.
+
+ aData->mGeometry = std::move(geometry);
+
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ aData->mGeometry->ComputeInvalidationRegion());
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ aData->mRect = transformedRect.Intersect(mClippedImageBounds);
+ InvalidateRect(aData->mRect);
+
+ aData->mInvalid = true;
+ invalidated = true;
+ } else {
+ if (aData->mClip != clip) {
+ UniquePtr<nsDisplayItemGeometry> geometry(
+ aItem->AllocateGeometry(aBuilder));
+ if (!IsContainerLayerItem(aItem)) {
+ // the bounds of layer items can change on us without
+ // ComputeInvalidationRegion returning any change. Other items
+ // shouldn't have any hidden geometry change.
+ MOZ_RELEASE_ASSERT(
+ geometry->mBounds.IsEqualEdges(aData->mGeometry->mBounds));
+ } else {
+ aData->mGeometry = std::move(geometry);
+ }
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ aData->mGeometry->ComputeInvalidationRegion());
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ InvalidateRect(aData->mRect);
+ aData->mRect = transformedRect.Intersect(mClippedImageBounds);
+ InvalidateRect(aData->mRect);
+ invalidated = true;
+
+ GP("ClipChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x,
+ aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost());
+
+ } else if (IsContainerLayerItem(aItem)) {
+ UniquePtr<nsDisplayItemGeometry> geometry(
+ aItem->AllocateGeometry(aBuilder));
+ // we need to catch bounds changes of containers so that we continue
+ // to have the correct bounds rects in the recording
+ if (DetectContainerLayerPropertiesBoundsChange(aItem, aData,
+ *geometry)) {
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ geometry->ComputeInvalidationRegion());
+ aData->mGeometry = std::move(geometry);
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ InvalidateRect(aData->mRect);
+ aData->mRect = transformedRect.Intersect(mClippedImageBounds);
+ InvalidateRect(aData->mRect);
+ invalidated = true;
+ GP("DetectContainerLayerPropertiesBoundsChange change\n");
+ } else {
+ // Handle changes in mClippedImageBounds
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ geometry->ComputeInvalidationRegion());
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ auto rect = transformedRect.Intersect(mClippedImageBounds);
+ if (!rect.IsEqualEdges(aData->mRect)) {
+ GP("ContainerLayer image rect bounds change\n");
+ InvalidateRect(aData->mRect);
+ aData->mRect = rect;
+ InvalidateRect(aData->mRect);
+ invalidated = true;
+ } else {
+ GP("Layer NoChange: %s %d %d %d %d\n", aItem->Name(),
+ aData->mRect.x, aData->mRect.y, aData->mRect.XMost(),
+ aData->mRect.YMost());
+ }
+ }
+ } else {
+ UniquePtr<nsDisplayItemGeometry> geometry(
+ aItem->AllocateGeometry(aBuilder));
+ nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
+ geometry->ComputeInvalidationRegion());
+ LayerIntRect transformedRect =
+ ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel);
+ auto rect = transformedRect.Intersect(mClippedImageBounds);
+ // Make sure we update mRect for mClippedImageBounds changes
+ if (!rect.IsEqualEdges(aData->mRect)) {
+ GP("ContainerLayer image rect bounds change\n");
+ InvalidateRect(aData->mRect);
+ aData->mRect = rect;
+ InvalidateRect(aData->mRect);
+ invalidated = true;
+ } else {
+ GP("NoChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x,
+ aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost());
+ }
+ }
+ }
+ }
+
+ mHitTestBounds.OrWith(aData->mRect);
+ if (!aData->mInvisible) {
+ mActualBounds.OrWith(aData->mRect);
+ }
+ aData->mClip = clip;
+ GP("post mInvalidRect: %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y,
+ mInvalidRect.width, mInvalidRect.height);
+ return invalidated;
+ }
+
+ void EndGroup(WebRenderLayerManager* aWrManager,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, Grouper* aGrouper,
+ nsDisplayList::iterator aStartItem,
+ nsDisplayList::iterator aEndItem) {
+ GP("\n\n");
+ GP("Begin EndGroup\n");
+
+ auto scale = LayoutDeviceToLayerScale2D::FromUnknownScale(mScale);
+
+ auto hitTestRect = mVisibleRect.Intersect(ViewAs<LayerPixel>(
+ mHitTestBounds, PixelCastJustification::LayerIsImage));
+ if (!hitTestRect.IsEmpty()) {
+ auto deviceHitTestRect =
+ (LayerRect(hitTestRect) - mResidualOffset) / scale;
+ PushHitTest(aBuilder, deviceHitTestRect);
+ }
+
+ mVisibleRect = mVisibleRect.Intersect(ViewAs<LayerPixel>(
+ mActualBounds, PixelCastJustification::LayerIsImage));
+
+ if (mVisibleRect.IsEmpty()) {
+ return;
+ }
+
+ // Invalidate any unused items
+ GP("mDisplayItems\n");
+ mDisplayItems.RemoveIf([&](BlobItemData* data) {
+ GP(" : %p-%d\n", data->mFrame, data->mDisplayItemKey);
+ if (!data->mUsed) {
+ GP("Invalidate unused: %p-%d\n", data->mFrame, data->mDisplayItemKey);
+ InvalidateRect(data->mRect);
+ delete data;
+ return true;
+ }
+
+ data->mUsed = false;
+ return false;
+ });
+
+ IntSize dtSize = mVisibleRect.Size().ToUnknownSize();
+ // The actual display item's size shouldn't have the scale factored in
+ // Round the bounds out to leave space for unsnapped content
+ LayoutDeviceRect itemBounds =
+ (LayerRect(mVisibleRect) - mResidualOffset) / scale;
+
+ if (mInvalidRect.IsEmpty() && mVisibleRect.IsEqualEdges(mLastVisibleRect)) {
+ GP("Not repainting group because it's empty\n");
+ GP("End EndGroup\n");
+ if (mKey) {
+ // Although the contents haven't changed, the visible area *may* have,
+ // so request it be updated unconditionally (wr should be able to easily
+ // detect if this is a no-op on its side, if that matters)
+ aResources.SetBlobImageVisibleArea(
+ *mKey, ViewAs<ImagePixel>(mVisibleRect,
+ PixelCastJustification::LayerIsImage));
+ mLastVisibleRect = mVisibleRect;
+ PushImage(aBuilder, itemBounds);
+ }
+ return;
+ }
+
+ std::vector<RefPtr<ScaledFont>> fonts;
+ bool validFonts = true;
+ RefPtr<WebRenderDrawEventRecorder> recorder =
+ MakeAndAddRef<WebRenderDrawEventRecorder>(
+ [&](MemStream& aStream,
+ std::vector<RefPtr<ScaledFont>>& aScaledFonts) {
+ size_t count = aScaledFonts.size();
+ aStream.write((const char*)&count, sizeof(count));
+ for (auto& scaled : aScaledFonts) {
+ Maybe<wr::FontInstanceKey> key =
+ aWrManager->WrBridge()->GetFontKeyForScaledFont(scaled,
+ aResources);
+ if (key.isNothing()) {
+ validFonts = false;
+ break;
+ }
+ BlobFont font = {key.value(), scaled};
+ aStream.write((const char*)&font, sizeof(font));
+ }
+ fonts = std::move(aScaledFonts);
+ });
+
+ RefPtr<gfx::DrawTarget> dummyDt =
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+
+ RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(
+ recorder, dummyDt, mLayerBounds.ToUnknownRect());
+ if (!dt || !dt->IsValid()) {
+ gfxCriticalNote << "Failed to create drawTarget for blob image";
+ return;
+ }
+
+ gfxContext context(dt);
+ context.SetMatrix(Matrix::Scaling(mScale).PostTranslate(mResidualOffset.x,
+ mResidualOffset.y));
+
+ GP("mInvalidRect: %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y,
+ mInvalidRect.width, mInvalidRect.height);
+
+ RenderRootStateManager* rootManager =
+ aWrManager->GetRenderRootStateManager();
+
+ bool empty = aStartItem == aEndItem;
+ if (empty) {
+ ClearImageKey(rootManager, true);
+ return;
+ }
+
+ PaintItemRange(aGrouper, aStartItem, aEndItem, &context, recorder,
+ rootManager, aResources);
+
+ // XXX: set this correctly perhaps using
+ // aItem->GetOpaqueRegion(aDisplayListBuilder, &snapped).
+ // Contains(paintBounds);?
+ wr::OpacityType opacity = wr::OpacityType::HasAlphaChannel;
+
+ bool hasItems = recorder->Finish();
+ GP("%d Finish\n", hasItems);
+ if (!validFonts) {
+ gfxCriticalNote << "Failed serializing fonts for blob image";
+ return;
+ }
+ Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData,
+ recorder->mOutputStream.mLength);
+ if (!mKey) {
+ // we don't want to send a new image that doesn't have any
+ // items in it
+ if (!hasItems || mVisibleRect.IsEmpty()) {
+ GP("Skipped group with no items\n");
+ return;
+ }
+
+ wr::BlobImageKey key =
+ wr::BlobImageKey{aWrManager->WrBridge()->GetNextImageKey()};
+ GP("No previous key making new one %d\n", key._0.mHandle);
+ wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity);
+ MOZ_RELEASE_ASSERT(bytes.length() > sizeof(size_t));
+ if (!aResources.AddBlobImage(
+ key, descriptor, bytes,
+ ViewAs<ImagePixel>(mVisibleRect,
+ PixelCastJustification::LayerIsImage))) {
+ return;
+ }
+ mKey = Some(key);
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(
+ aWrManager->WrBridge()->MatchesNamespace(mKey.ref()),
+ "Stale blob key for group!");
+
+ wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity);
+
+ // Convert mInvalidRect to image space by subtracting the corner of the
+ // image bounds
+ auto dirtyRect = ViewAs<ImagePixel>(mInvalidRect,
+ PixelCastJustification::LayerIsImage);
+
+ auto bottomRight = dirtyRect.BottomRight();
+ GP("check invalid %d %d - %d %d\n", bottomRight.x.value,
+ bottomRight.y.value, dtSize.width, dtSize.height);
+ GP("Update Blob %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y,
+ mInvalidRect.width, mInvalidRect.height);
+ if (!aResources.UpdateBlobImage(
+ *mKey, descriptor, bytes,
+ ViewAs<ImagePixel>(mVisibleRect,
+ PixelCastJustification::LayerIsImage),
+ dirtyRect)) {
+ return;
+ }
+ }
+ mFonts = std::move(fonts);
+ aResources.SetBlobImageVisibleArea(
+ *mKey,
+ ViewAs<ImagePixel>(mVisibleRect, PixelCastJustification::LayerIsImage));
+ mLastVisibleRect = mVisibleRect;
+ PushImage(aBuilder, itemBounds);
+ GP("End EndGroup\n\n");
+ }
+
+ void PushImage(wr::DisplayListBuilder& aBuilder,
+ const LayoutDeviceRect& bounds) {
+ wr::LayoutRect dest = wr::ToLayoutRect(bounds);
+ GP("PushImage: %f %f %f %f\n", dest.min.x, dest.min.y, dest.max.x,
+ dest.max.y);
+ // wr::ToImageRendering(aItem->Frame()->UsedImageRendering());
+ auto rendering = wr::ImageRendering::Auto;
+ bool backfaceHidden = false;
+
+ // XXX - clipping the item against the paint rect breaks some content.
+ // cf. Bug 1455422.
+ // wr::LayoutRect clip = wr::ToLayoutRect(bounds.Intersect(mVisibleRect));
+
+ aBuilder.PushImage(dest, dest, !backfaceHidden, false, rendering,
+ wr::AsImageKey(*mKey));
+ }
+
+ void PushHitTest(wr::DisplayListBuilder& aBuilder,
+ const LayoutDeviceRect& bounds) {
+ wr::LayoutRect dest = wr::ToLayoutRect(bounds);
+ GP("PushHitTest: %f %f %f %f\n", dest.min.x, dest.min.y, dest.max.x,
+ dest.max.y);
+
+ // We don't really know the exact shape of this blob because it may contain
+ // SVG shapes. Also mHitInfo may be a combination of hit info flags from
+ // different shapes so generate an irregular-area hit-test region for it.
+ CompositorHitTestInfo hitInfo = mHitInfo;
+ if (hitInfo.contains(CompositorHitTestFlags::eVisibleToHitTest)) {
+ hitInfo += CompositorHitTestFlags::eIrregularArea;
+ }
+
+ bool backfaceHidden = false;
+ aBuilder.PushHitTest(dest, dest, !backfaceHidden, mScrollId, hitInfo,
+ SideBits::eNone);
+ }
+
+ void PaintItemRange(Grouper* aGrouper, nsDisplayList::iterator aStartItem,
+ nsDisplayList::iterator aEndItem, gfxContext* aContext,
+ WebRenderDrawEventRecorder* aRecorder,
+ RenderRootStateManager* aRootManager,
+ wr::IpcResourceUpdateQueue& aResources) {
+ LayerIntSize size = mVisibleRect.Size();
+ for (auto it = aStartItem; it != aEndItem; ++it) {
+ nsDisplayItem* item = *it;
+ MOZ_ASSERT(item);
+
+ BlobItemData* data = GetBlobItemData(item);
+ if (data->mInvisible) {
+ continue;
+ }
+
+ LayerIntRect bounds = data->mRect;
+ auto bottomRight = bounds.BottomRight();
+
+ GP("Trying %s %p-%d %d %d %d %d\n", item->Name(), item->Frame(),
+ item->GetPerFrameKey(), bounds.x, bounds.y, bounds.XMost(),
+ bounds.YMost());
+
+ if (item->GetType() == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
+ continue;
+ }
+
+ GP("paint check invalid %d %d - %d %d\n", bottomRight.x.value,
+ bottomRight.y.value, size.width, size.height);
+ // skip empty items
+ if (bounds.IsEmpty()) {
+ continue;
+ }
+
+ bool dirty = true;
+ auto preservedBounds = bounds.Intersect(mPreservedRect);
+ if (!mInvalidRect.Contains(preservedBounds)) {
+ GP("Passing\n");
+ dirty = false;
+ BlobItemData* data = GetBlobItemData(item);
+ if (data->mInvalid) {
+ gfxCriticalError()
+ << "DisplayItem" << item->Name() << "-should be invalid";
+ }
+ // if the item is invalid it needs to be fully contained
+ MOZ_RELEASE_ASSERT(!data->mInvalid);
+ }
+
+ nsDisplayList* children = item->GetChildren();
+ if (children) {
+ // If we aren't dirty, we still need to iterate over the children to
+ // ensure the blob index data is recorded the same as before to allow
+ // the merging of the parts inside in the invalid rect. Any items that
+ // are painted as a single item need to avoid repainting in that case.
+ GP("doing children in EndGroup\n");
+ aGrouper->PaintContainerItem(this, item, data, bounds.ToUnknownRect(),
+ dirty, children, aContext, aRecorder,
+ aRootManager, aResources);
+ continue;
+ }
+ nsPaintedDisplayItem* paintedItem = item->AsPaintedDisplayItem();
+ if (!paintedItem) {
+ continue;
+ }
+ if (dirty) {
+ // What should the clip settting strategy be? We can set the full
+ // clip everytime. this is probably easiest for now. An alternative
+ // would be to put the push and the pop into separate items and let
+ // invalidation handle it that way.
+ DisplayItemClip currentClip = paintedItem->GetClip();
+
+ if (currentClip.HasClip()) {
+ aContext->Save();
+ currentClip.ApplyTo(aContext, aGrouper->mAppUnitsPerDevPixel);
+ }
+ aContext->NewPath();
+ GP("painting %s %p-%d\n", paintedItem->Name(), paintedItem->Frame(),
+ paintedItem->GetPerFrameKey());
+ if (aGrouper->mDisplayListBuilder->IsPaintingToWindow()) {
+ paintedItem->Frame()->AddStateBits(NS_FRAME_PAINTED_THEBES);
+ }
+
+ paintedItem->Paint(aGrouper->mDisplayListBuilder, aContext);
+ TakeExternalSurfaces(aRecorder, data->mExternalSurfaces, aRootManager,
+ aResources);
+
+ if (currentClip.HasClip()) {
+ aContext->Restore();
+ }
+ }
+ aContext->GetDrawTarget()->FlushItem(bounds.ToUnknownRect());
+ }
+ }
+
+ ~DIGroup() {
+ GP("Group destruct\n");
+ for (BlobItemData* data : mDisplayItems) {
+ GP("Deleting %p-%d\n", data->mFrame, data->mDisplayItemKey);
+ delete data;
+ }
+ }
+};
+
+// If we have an item we need to make sure it matches the current group
+// otherwise it means the item switched groups and we need to invalidate
+// it and recreate the data.
+static BlobItemData* GetBlobItemDataForGroup(nsDisplayItem* aItem,
+ DIGroup* aGroup) {
+ BlobItemData* data = GetBlobItemData(aItem);
+ if (data) {
+ MOZ_RELEASE_ASSERT(data->mGroup->mDisplayItems.Contains(data));
+ if (data->mGroup != aGroup) {
+ GP("group don't match %p %p\n", data->mGroup, aGroup);
+ data->ClearFrame();
+ // the item is for another group
+ // it should be cleared out as being unused at the end of this paint
+ data = nullptr;
+ }
+ }
+ if (!data) {
+ GP("Allocating blob data\n");
+ data = new BlobItemData(aGroup, aItem);
+ aGroup->mDisplayItems.Insert(data);
+ }
+ data->mUsed = true;
+ return data;
+}
+
+void Grouper::PaintContainerItem(DIGroup* aGroup, nsDisplayItem* aItem,
+ BlobItemData* aData,
+ const IntRect& aItemBounds, bool aDirty,
+ nsDisplayList* aChildren, gfxContext* aContext,
+ WebRenderDrawEventRecorder* aRecorder,
+ RenderRootStateManager* aRootManager,
+ wr::IpcResourceUpdateQueue& aResources) {
+ switch (aItem->GetType()) {
+ case DisplayItemType::TYPE_TRANSFORM: {
+ DisplayItemClip currentClip = aItem->GetClip();
+
+ gfxContextMatrixAutoSaveRestore saveMatrix;
+ if (currentClip.HasClip()) {
+ aContext->Save();
+ currentClip.ApplyTo(aContext, this->mAppUnitsPerDevPixel);
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ } else {
+ saveMatrix.SetContext(aContext);
+ }
+
+ auto transformItem = static_cast<nsDisplayTransform*>(aItem);
+ Matrix4x4Flagged trans = transformItem->GetTransform();
+ Matrix trans2d;
+ if (!trans.Is2D(&trans2d)) {
+ // Painting will cause us to include the item's recording in the blob.
+ // We only want to do that if it is dirty, because otherwise the
+ // recording might change (e.g. due to factor of 2 scaling of images
+ // giving different results) and the merging will discard it because it
+ // is outside the invalid rect.
+ if (aDirty) {
+ // We don't currently support doing invalidation inside 3d transforms.
+ // For now just paint it as a single item.
+ aItem->AsPaintedDisplayItem()->Paint(mDisplayListBuilder, aContext);
+ TakeExternalSurfaces(aRecorder, aData->mExternalSurfaces,
+ aRootManager, aResources);
+ }
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ } else if (!trans2d.IsSingular()) {
+ aContext->Multiply(ThebesMatrix(trans2d));
+ aGroup->PaintItemRange(this, aChildren->begin(), aChildren->end(),
+ aContext, aRecorder, aRootManager, aResources);
+ }
+
+ if (currentClip.HasClip()) {
+ aContext->Restore();
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ }
+ break;
+ }
+ case DisplayItemType::TYPE_OPACITY: {
+ auto opacityItem = static_cast<nsDisplayOpacity*>(aItem);
+ float opacity = opacityItem->GetOpacity();
+ if (opacity == 0.0f) {
+ return;
+ }
+
+ aContext->GetDrawTarget()->PushLayer(false, opacityItem->GetOpacity(),
+ nullptr, mozilla::gfx::Matrix(),
+ aItemBounds);
+ GP("beginGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ aGroup->PaintItemRange(this, aChildren->begin(), aChildren->end(),
+ aContext, aRecorder, aRootManager, aResources);
+ aContext->GetDrawTarget()->PopLayer();
+ GP("endGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ break;
+ }
+ case DisplayItemType::TYPE_BLEND_MODE: {
+ auto blendItem = static_cast<nsDisplayBlendMode*>(aItem);
+ auto blendMode = blendItem->BlendMode();
+ aContext->GetDrawTarget()->PushLayerWithBlend(
+ false, 1.0, nullptr, mozilla::gfx::Matrix(), aItemBounds, false,
+ blendMode);
+ GP("beginGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ aGroup->PaintItemRange(this, aChildren->begin(), aChildren->end(),
+ aContext, aRecorder, aRootManager, aResources);
+ aContext->GetDrawTarget()->PopLayer();
+ GP("endGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ break;
+ }
+ case DisplayItemType::TYPE_BLEND_CONTAINER: {
+ aContext->GetDrawTarget()->PushLayer(false, 1.0, nullptr,
+ mozilla::gfx::Matrix(), aItemBounds);
+ GP("beginGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ aGroup->PaintItemRange(this, aChildren->begin(), aChildren->end(),
+ aContext, aRecorder, aRootManager, aResources);
+ aContext->GetDrawTarget()->PopLayer();
+ GP("endGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ break;
+ }
+ case DisplayItemType::TYPE_MASK: {
+ GP("Paint Mask\n");
+ auto maskItem = static_cast<nsDisplayMasksAndClipPaths*>(aItem);
+ if (maskItem->IsValidMask()) {
+ maskItem->PaintWithContentsPaintCallback(
+ mDisplayListBuilder, aContext, [&] {
+ GP("beginGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ aGroup->PaintItemRange(this, aChildren->begin(), aChildren->end(),
+ aContext, aRecorder, aRootManager,
+ aResources);
+ GP("endGroup %s %p-%d\n", aItem->Name(), aItem->Frame(),
+ aItem->GetPerFrameKey());
+ });
+ TakeExternalSurfaces(aRecorder, aData->mExternalSurfaces, aRootManager,
+ aResources);
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ }
+ break;
+ }
+ case DisplayItemType::TYPE_FILTER: {
+ GP("Paint Filter\n");
+ // Painting will cause us to include the item's recording in the blob. We
+ // only want to do that if it is dirty, because otherwise the recording
+ // might change (e.g. due to factor of 2 scaling of images giving
+ // different results) and the merging will discard it because it is
+ // outside the invalid rect.
+ if (aDirty) {
+ auto filterItem = static_cast<nsDisplayFilters*>(aItem);
+
+ nsRegion visible(aItem->GetClippedBounds(mDisplayListBuilder));
+ nsRect buildingRect = aItem->GetBuildingRect();
+ visible.And(visible, buildingRect);
+
+ filterItem->Paint(mDisplayListBuilder, aContext);
+ TakeExternalSurfaces(aRecorder, aData->mExternalSurfaces, aRootManager,
+ aResources);
+ }
+ aContext->GetDrawTarget()->FlushItem(aItemBounds);
+ break;
+ }
+
+ default:
+ aGroup->PaintItemRange(this, aChildren->begin(), aChildren->end(),
+ aContext, aRecorder, aRootManager, aResources);
+ break;
+ }
+}
+
+class WebRenderGroupData : public WebRenderUserData {
+ public:
+ WebRenderGroupData(RenderRootStateManager* aWRManager, nsDisplayItem* aItem);
+ virtual ~WebRenderGroupData();
+
+ WebRenderGroupData* AsGroupData() override { return this; }
+ UserDataType GetType() override { return UserDataType::eGroup; }
+ static UserDataType Type() { return UserDataType::eGroup; }
+
+ DIGroup mSubGroup;
+ DIGroup mFollowingGroup;
+};
+
+enum class ItemActivity : uint8_t {
+ /// Item must not be active.
+ No = 0,
+ /// Could be active if it has no layerization cost.
+ /// Typically active if first of an item group.
+ Could = 1,
+ /// Should be active unless something external makes that less useful.
+ /// For example if the item is affected by a complex mask, it remains
+ /// inactive.
+ Should = 2,
+ /// Must be active regardless of external factors.
+ Must = 3,
+};
+
+ItemActivity CombineActivity(ItemActivity a, ItemActivity b) {
+ return a > b ? a : b;
+}
+
+bool ActivityAtLeast(ItemActivity rhs, ItemActivity atLeast) {
+ return rhs >= atLeast;
+}
+
+static ItemActivity IsItemProbablyActive(
+ nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder, bool aSiblingActive,
+ bool aUniformlyScaled);
+
+static ItemActivity HasActiveChildren(
+ const nsDisplayList& aList, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder, bool aUniformlyScaled) {
+ ItemActivity activity = ItemActivity::No;
+ for (nsDisplayItem* item : aList) {
+ // Here we only want to know if a child must be active, so we don't specify
+ // when the item is first or last, which can cause an item that could be
+ // either decide to be active. This is a bit conservative and avoids some
+ // extra layers. It's a good tradeoff until we get to the point where most
+ // items could have been active but none *had* to. Right now this is
+ // unlikely but as more svg items get webrenderized it will be better to
+ // make them active more aggressively.
+ auto childActivity =
+ IsItemProbablyActive(item, aBuilder, aResources, aSc, aManager,
+ aDisplayListBuilder, false, aUniformlyScaled);
+ activity = CombineActivity(activity, childActivity);
+ if (activity == ItemActivity::Must) {
+ return activity;
+ }
+ }
+ return activity;
+}
+
+static ItemActivity AssessBounds(const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ nsDisplayItem* aItem,
+ bool aHasActivePrecedingSibling) {
+ // Arbitrary threshold up for adjustments. What we want to avoid here
+ // is alternating between active and non active items and create a lot
+ // of overlapping blobs, so we only make images active if they are
+ // costly enough that it's worth the risk of having more layers. As we
+ // move more blob items into wr display items it will become less of a
+ // concern.
+ constexpr float largeish = 512;
+
+ bool snap = false;
+ nsRect bounds = aItem->GetBounds(aDisplayListBuilder, &snap);
+
+ float appUnitsPerDevPixel =
+ static_cast<float>(aItem->Frame()->PresContext()->AppUnitsPerDevPixel());
+
+ float width =
+ static_cast<float>(bounds.width) * aSc.GetInheritedScale().xScale;
+ float height =
+ static_cast<float>(bounds.height) * aSc.GetInheritedScale().yScale;
+
+ // Webrender doesn't handle primitives smaller than a pixel well, so
+ // avoid making them active.
+ if (width >= appUnitsPerDevPixel && height >= appUnitsPerDevPixel) {
+ if (aHasActivePrecedingSibling || width > largeish || height > largeish) {
+ return ItemActivity::Should;
+ }
+
+ return ItemActivity::Could;
+ }
+
+ return ItemActivity::No;
+}
+
+// This function decides whether we want to treat this item as "active", which
+// means that it's a container item which we will turn into a WebRender
+// StackingContext, or whether we treat it as "inactive" and include it inside
+// the parent blob image.
+//
+// We can't easily use GetLayerState because it wants a bunch of layers related
+// information.
+static ItemActivity IsItemProbablyActive(
+ nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder, bool aHasActivePrecedingSibling,
+ bool aUniformlyScaled) {
+ switch (aItem->GetType()) {
+ case DisplayItemType::TYPE_TRANSFORM: {
+ nsDisplayTransform* transformItem =
+ static_cast<nsDisplayTransform*>(aItem);
+ const Matrix4x4Flagged& t = transformItem->GetTransform();
+ Matrix t2d;
+ bool is2D = t.Is2D(&t2d);
+ if (!is2D) {
+ return ItemActivity::Must;
+ }
+
+ auto activity = HasActiveChildren(*transformItem->GetChildren(), aBuilder,
+ aResources, aSc, aManager,
+ aDisplayListBuilder, aUniformlyScaled);
+
+ if (transformItem->MayBeAnimated(aDisplayListBuilder)) {
+ activity = CombineActivity(activity, ItemActivity::Should);
+ }
+
+ return activity;
+ }
+ case DisplayItemType::TYPE_OPACITY: {
+ nsDisplayOpacity* opacityItem = static_cast<nsDisplayOpacity*>(aItem);
+ if (opacityItem->NeedsActiveLayer(aDisplayListBuilder,
+ opacityItem->Frame())) {
+ return ItemActivity::Must;
+ }
+ return HasActiveChildren(*opacityItem->GetChildren(), aBuilder,
+ aResources, aSc, aManager, aDisplayListBuilder,
+ aUniformlyScaled);
+ }
+ case DisplayItemType::TYPE_FOREIGN_OBJECT: {
+ return ItemActivity::Must;
+ }
+ case DisplayItemType::TYPE_SVG_GEOMETRY: {
+ auto* svgItem = static_cast<DisplaySVGGeometry*>(aItem);
+ if (StaticPrefs::gfx_webrender_svg_shapes() && aUniformlyScaled &&
+ svgItem->ShouldBeActive(aBuilder, aResources, aSc, aManager,
+ aDisplayListBuilder)) {
+ return AssessBounds(aSc, aDisplayListBuilder, aItem,
+ aHasActivePrecedingSibling);
+ }
+
+ return ItemActivity::No;
+ }
+ case DisplayItemType::TYPE_SVG_IMAGE: {
+ auto* svgItem = static_cast<DisplaySVGImage*>(aItem);
+ if (StaticPrefs::gfx_webrender_svg_images() && aUniformlyScaled &&
+ svgItem->ShouldBeActive(aBuilder, aResources, aSc, aManager,
+ aDisplayListBuilder)) {
+ return AssessBounds(aSc, aDisplayListBuilder, aItem,
+ aHasActivePrecedingSibling);
+ }
+
+ return ItemActivity::No;
+ }
+ case DisplayItemType::TYPE_BLEND_MODE: {
+ /* BLEND_MODE needs to be active if it might have a previous sibling
+ * that is active so that it's able to blend with that content. */
+ if (aHasActivePrecedingSibling) {
+ return ItemActivity::Must;
+ }
+
+ return HasActiveChildren(*aItem->GetChildren(), aBuilder, aResources, aSc,
+ aManager, aDisplayListBuilder, aUniformlyScaled);
+ }
+ case DisplayItemType::TYPE_MASK: {
+ if (aItem->GetChildren()) {
+ auto activity =
+ HasActiveChildren(*aItem->GetChildren(), aBuilder, aResources, aSc,
+ aManager, aDisplayListBuilder, aUniformlyScaled);
+ // For masked items, don't bother with making children active since we
+ // are going to have to need to paint and upload a large mask anyway.
+ if (activity < ItemActivity::Must) {
+ return ItemActivity::No;
+ }
+ return activity;
+ }
+ return ItemActivity::No;
+ }
+ case DisplayItemType::TYPE_WRAP_LIST:
+ case DisplayItemType::TYPE_CONTAINER:
+ case DisplayItemType::TYPE_PERSPECTIVE: {
+ if (aItem->GetChildren()) {
+ return HasActiveChildren(*aItem->GetChildren(), aBuilder, aResources,
+ aSc, aManager, aDisplayListBuilder,
+ aUniformlyScaled);
+ }
+ return ItemActivity::No;
+ }
+ case DisplayItemType::TYPE_FILTER: {
+ nsDisplayFilters* filters = static_cast<nsDisplayFilters*>(aItem);
+ if (filters->CanCreateWebRenderCommands()) {
+ // Items are usually expensive enough on the CPU that we want to
+ // make them active whenever we can.
+ return ItemActivity::Must;
+ }
+ return ItemActivity::No;
+ }
+ default:
+ // TODO: handle other items?
+ return ItemActivity::No;
+ }
+}
+
+// This does a pass over the display lists and will join the display items
+// into groups as well as paint them
+void Grouper::ConstructGroups(nsDisplayListBuilder* aDisplayListBuilder,
+ WebRenderCommandBuilder* aCommandBuilder,
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ DIGroup* aGroup, nsDisplayList* aList,
+ nsDisplayItem* aWrappingItem,
+ const StackingContextHelper& aSc) {
+ RenderRootStateManager* manager =
+ aCommandBuilder->mManager->GetRenderRootStateManager();
+
+ nsDisplayList::iterator startOfCurrentGroup = aList->end();
+ DIGroup* currentGroup = aGroup;
+
+ // We need to track whether we have active siblings for mixed blend mode.
+ bool encounteredActiveItem = false;
+ bool isFirstGroup = true;
+ // Track whether the item is the first (visible) of its group in which case
+ // making it active won't add extra layers.
+ bool isFirst = true;
+
+ for (auto it = aList->begin(); it != aList->end(); ++it) {
+ nsDisplayItem* item = *it;
+ MOZ_ASSERT(item);
+
+ if (item->HasHitTestInfo()) {
+ // Accumulate the hit-test info flags. In cases where there are multiple
+ // hittest-info display items with different flags, mHitInfo will have
+ // the union of all those flags. If that is the case, we will
+ // additionally set eIrregularArea (at the site that we use mHitInfo)
+ // so that downstream consumers of this (primarily APZ) will know that
+ // the exact shape of what gets hit with what is unknown.
+ currentGroup->mHitInfo += item->GetHitTestInfo().Info();
+ }
+
+ if (startOfCurrentGroup == aList->end()) {
+ startOfCurrentGroup = it;
+ if (!isFirstGroup) {
+ mClipManager.SwitchItem(aDisplayListBuilder, aWrappingItem);
+ }
+ }
+
+ bool isLast = it.HasNext();
+
+ // WebRender's anti-aliasing approximation is not very good under
+ // non-uniform scales.
+ bool uniformlyScaled =
+ fabs(aGroup->mScale.xScale - aGroup->mScale.yScale) < 0.1;
+
+ auto activity = IsItemProbablyActive(
+ item, aBuilder, aResources, aSc, manager, mDisplayListBuilder,
+ encounteredActiveItem, uniformlyScaled);
+ auto threshold =
+ isFirst || isLast ? ItemActivity::Could : ItemActivity::Should;
+
+ if (activity >= threshold) {
+ encounteredActiveItem = true;
+ // We're going to be starting a new group.
+ RefPtr<WebRenderGroupData> groupData =
+ aCommandBuilder->CreateOrRecycleWebRenderUserData<WebRenderGroupData>(
+ item);
+
+ groupData->mFollowingGroup.mInvalidRect.SetEmpty();
+
+ // Initialize groupData->mFollowingGroup with data from currentGroup.
+ // We want to copy out this information before calling EndGroup because
+ // EndGroup will set mLastVisibleRect depending on whether
+ // we send something to WebRender.
+
+ // TODO: compute the group bounds post-grouping, so that they can be
+ // tighter for just the sublist that made it into this group.
+ // We want to ensure the tight bounds are still clipped by area
+ // that we're building the display list for.
+ if (groupData->mFollowingGroup.mScale != currentGroup->mScale ||
+ groupData->mFollowingGroup.mAppUnitsPerDevPixel !=
+ currentGroup->mAppUnitsPerDevPixel ||
+ groupData->mFollowingGroup.mResidualOffset !=
+ currentGroup->mResidualOffset) {
+ if (groupData->mFollowingGroup.mAppUnitsPerDevPixel !=
+ currentGroup->mAppUnitsPerDevPixel) {
+ GP("app unit change following: %d %d\n",
+ groupData->mFollowingGroup.mAppUnitsPerDevPixel,
+ currentGroup->mAppUnitsPerDevPixel);
+ }
+ // The group changed size
+ GP("Inner group size change\n");
+ groupData->mFollowingGroup.ClearItems();
+ groupData->mFollowingGroup.ClearImageKey(
+ aCommandBuilder->mManager->GetRenderRootStateManager());
+ }
+ groupData->mFollowingGroup.mAppUnitsPerDevPixel =
+ currentGroup->mAppUnitsPerDevPixel;
+ groupData->mFollowingGroup.mLayerBounds = currentGroup->mLayerBounds;
+ groupData->mFollowingGroup.mClippedImageBounds =
+ currentGroup->mClippedImageBounds;
+ groupData->mFollowingGroup.mScale = currentGroup->mScale;
+ groupData->mFollowingGroup.mResidualOffset =
+ currentGroup->mResidualOffset;
+ groupData->mFollowingGroup.mVisibleRect = currentGroup->mVisibleRect;
+ groupData->mFollowingGroup.mPreservedRect =
+ groupData->mFollowingGroup.mVisibleRect.Intersect(
+ groupData->mFollowingGroup.mLastVisibleRect);
+ groupData->mFollowingGroup.mActualBounds = LayerIntRect();
+ groupData->mFollowingGroup.mHitTestBounds = LayerIntRect();
+ groupData->mFollowingGroup.mHitInfo = currentGroup->mHitInfo;
+
+ currentGroup->EndGroup(aCommandBuilder->mManager, aDisplayListBuilder,
+ aBuilder, aResources, this, startOfCurrentGroup,
+ it);
+
+ {
+ auto spaceAndClipChain =
+ mClipManager.SwitchItem(aDisplayListBuilder, item);
+ wr::SpaceAndClipChainHelper saccHelper(aBuilder, spaceAndClipChain);
+ bool hasHitTest = mHitTestInfoManager.ProcessItem(item, aBuilder,
+ aDisplayListBuilder);
+ // XXX - This is hacky. Some items have hit testing info on them but we
+ // also have dedicated hit testing items, the flags of which apply to
+ // the the group that contains them. We don't want layerization to
+ // affect that so if the item didn't emit any hit testing then we still
+ // push a hit test item if the previous group had some hit test flags
+ // set. This is obviously not great. Hit testing should be independent
+ // from how we layerize.
+ if (!hasHitTest &&
+ currentGroup->mHitInfo != gfx::CompositorHitTestInvisibleToHit) {
+ auto hitTestRect = item->GetBuildingRect();
+ if (!hitTestRect.IsEmpty()) {
+ currentGroup->PushHitTest(
+ aBuilder, LayoutDeviceRect::FromAppUnits(
+ hitTestRect, currentGroup->mAppUnitsPerDevPixel));
+ }
+ }
+
+ sIndent++;
+ // Note: this call to CreateWebRenderCommands can recurse back into
+ // this function.
+ bool createdWRCommands = item->CreateWebRenderCommands(
+ aBuilder, aResources, aSc, manager, mDisplayListBuilder);
+ MOZ_RELEASE_ASSERT(
+ createdWRCommands,
+ "active transforms should always succeed at creating "
+ "WebRender commands");
+ sIndent--;
+ }
+
+ isFirstGroup = false;
+ startOfCurrentGroup = aList->end();
+ currentGroup = &groupData->mFollowingGroup;
+ isFirst = true;
+ } else { // inactive item
+ bool isInvisible = false;
+ ConstructItemInsideInactive(aCommandBuilder, aBuilder, aResources,
+ currentGroup, item, aSc, &isInvisible);
+ if (!isInvisible) {
+ // Invisible items don't count.
+ isFirst = false;
+ }
+ }
+ }
+
+ currentGroup->EndGroup(aCommandBuilder->mManager, aDisplayListBuilder,
+ aBuilder, aResources, this, startOfCurrentGroup,
+ aList->end());
+}
+
+// This does a pass over the display lists and will join the display items
+// into a single group.
+bool Grouper::ConstructGroupInsideInactive(
+ WebRenderCommandBuilder* aCommandBuilder, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, DIGroup* aGroup,
+ nsDisplayList* aList, const StackingContextHelper& aSc) {
+ bool invalidated = false;
+ for (nsDisplayItem* item : *aList) {
+ if (item->HasHitTestInfo()) {
+ // Accumulate the hit-test info flags. In cases where there are multiple
+ // hittest-info display items with different flags, mHitInfo will have
+ // the union of all those flags. If that is the case, we will
+ // additionally set eIrregularArea (at the site that we use mHitInfo)
+ // so that downstream consumers of this (primarily APZ) will know that
+ // the exact shape of what gets hit with what is unknown.
+ aGroup->mHitInfo += item->GetHitTestInfo().Info();
+ }
+
+ bool invisible = false;
+ invalidated |= ConstructItemInsideInactive(
+ aCommandBuilder, aBuilder, aResources, aGroup, item, aSc, &invisible);
+ }
+ return invalidated;
+}
+
+bool Grouper::ConstructItemInsideInactive(
+ WebRenderCommandBuilder* aCommandBuilder, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, DIGroup* aGroup,
+ nsDisplayItem* aItem, const StackingContextHelper& aSc,
+ bool* aOutIsInvisible) {
+ nsDisplayList* children = aItem->GetChildren();
+ BlobItemData* data = GetBlobItemDataForGroup(aItem, aGroup);
+
+ /* mInvalid unfortunately persists across paints. Clear it so that if we don't
+ * set it to 'true' we ensure that we're not using the value from the last
+ * time that we painted */
+ data->mInvalid = false;
+ data->mInvisible = aItem->IsInvisible();
+ *aOutIsInvisible = data->mInvisible;
+
+ // we compute the geometry change here because we have the transform around
+ // still
+ bool invalidated = aGroup->ComputeGeometryChange(aItem, data, mTransform,
+ mDisplayListBuilder);
+
+ // Temporarily restrict the image bounds to the bounds of the container so
+ // that clipped children within the container know about the clip. This
+ // ensures that the bounds passed to FlushItem are contained in the bounds of
+ // the clip so that we don't include items in the recording without including
+ // their corresponding clipping items.
+ auto oldClippedImageBounds = aGroup->mClippedImageBounds;
+ aGroup->mClippedImageBounds =
+ aGroup->mClippedImageBounds.Intersect(data->mRect);
+
+ if (aItem->GetType() == DisplayItemType::TYPE_FILTER) {
+ // If ConstructGroupInsideInactive finds any change, we invalidate the
+ // entire container item. This is needed because blob merging requires the
+ // entire item to be within the invalid region.
+ Matrix m = mTransform;
+ mTransform = Matrix();
+ sIndent++;
+ if (ConstructGroupInsideInactive(aCommandBuilder, aBuilder, aResources,
+ aGroup, children, aSc)) {
+ data->mInvalid = true;
+ aGroup->InvalidateRect(data->mRect);
+ invalidated = true;
+ }
+ sIndent--;
+ mTransform = m;
+ } else if (aItem->GetType() == DisplayItemType::TYPE_TRANSFORM) {
+ Matrix m = mTransform;
+ nsDisplayTransform* transformItem = static_cast<nsDisplayTransform*>(aItem);
+ const Matrix4x4Flagged& t = transformItem->GetTransform();
+ Matrix t2d;
+ bool is2D = t.CanDraw2D(&t2d);
+ if (!is2D) {
+ // If ConstructGroupInsideInactive finds any change, we invalidate the
+ // entire container item. This is needed because blob merging requires the
+ // entire item to be within the invalid region.
+ mTransform = Matrix();
+ sIndent++;
+ if (ConstructGroupInsideInactive(aCommandBuilder, aBuilder, aResources,
+ aGroup, children, aSc)) {
+ data->mInvalid = true;
+ aGroup->InvalidateRect(data->mRect);
+ invalidated = true;
+ }
+ sIndent--;
+ } else {
+ GP("t2d: %f %f\n", t2d._31, t2d._32);
+ mTransform.PreMultiply(t2d);
+ GP("mTransform: %f %f\n", mTransform._31, mTransform._32);
+ sIndent++;
+ invalidated |= ConstructGroupInsideInactive(
+ aCommandBuilder, aBuilder, aResources, aGroup, children, aSc);
+ sIndent--;
+ }
+ mTransform = m;
+ } else if (children) {
+ sIndent++;
+ invalidated |= ConstructGroupInsideInactive(
+ aCommandBuilder, aBuilder, aResources, aGroup, children, aSc);
+ sIndent--;
+ }
+
+ GP("Including %s of %d\n", aItem->Name(), aGroup->mDisplayItems.Count());
+ aGroup->mClippedImageBounds = oldClippedImageBounds;
+ return invalidated;
+}
+
+/* This is just a copy of nsRect::ScaleToOutsidePixels with an offset added in.
+ * The offset is applied just before the rounding. It's in the scaled space. */
+static mozilla::LayerIntRect ScaleToOutsidePixelsOffset(
+ nsRect aRect, float aXScale, float aYScale, nscoord aAppUnitsPerPixel,
+ LayerPoint aOffset) {
+ mozilla::LayerIntRect rect;
+ rect.SetNonEmptyBox(
+ NSToIntFloor(NSAppUnitsToFloatPixels(aRect.x, float(aAppUnitsPerPixel)) *
+ aXScale +
+ aOffset.x),
+ NSToIntFloor(NSAppUnitsToFloatPixels(aRect.y, float(aAppUnitsPerPixel)) *
+ aYScale +
+ aOffset.y),
+ NSToIntCeil(
+ NSAppUnitsToFloatPixels(aRect.XMost(), float(aAppUnitsPerPixel)) *
+ aXScale +
+ aOffset.x),
+ NSToIntCeil(
+ NSAppUnitsToFloatPixels(aRect.YMost(), float(aAppUnitsPerPixel)) *
+ aYScale +
+ aOffset.y));
+ return rect;
+}
+
+/* This function is the same as the above except that it rounds to the
+ * nearest instead of rounding out. We use it for attempting to compute the
+ * actual pixel bounds of opaque items */
+static mozilla::gfx::IntRect ScaleToNearestPixelsOffset(
+ nsRect aRect, float aXScale, float aYScale, nscoord aAppUnitsPerPixel,
+ LayerPoint aOffset) {
+ mozilla::gfx::IntRect rect;
+ rect.SetNonEmptyBox(
+ NSToIntFloor(NSAppUnitsToFloatPixels(aRect.x, float(aAppUnitsPerPixel)) *
+ aXScale +
+ aOffset.x + 0.5),
+ NSToIntFloor(NSAppUnitsToFloatPixels(aRect.y, float(aAppUnitsPerPixel)) *
+ aYScale +
+ aOffset.y + 0.5),
+ NSToIntFloor(
+ NSAppUnitsToFloatPixels(aRect.XMost(), float(aAppUnitsPerPixel)) *
+ aXScale +
+ aOffset.x + 0.5),
+ NSToIntFloor(
+ NSAppUnitsToFloatPixels(aRect.YMost(), float(aAppUnitsPerPixel)) *
+ aYScale +
+ aOffset.y + 0.5));
+ return rect;
+}
+
+RenderRootStateManager* WebRenderCommandBuilder::GetRenderRootStateManager() {
+ return mManager->GetRenderRootStateManager();
+}
+
+void WebRenderCommandBuilder::DoGroupingForDisplayList(
+ nsDisplayList* aList, nsDisplayItem* aWrappingItem,
+ nsDisplayListBuilder* aDisplayListBuilder, const StackingContextHelper& aSc,
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources) {
+ if (!aList->GetBottom()) {
+ return;
+ }
+
+ GP("DoGroupingForDisplayList\n");
+
+ mClipManager.BeginList(aSc);
+ mHitTestInfoManager.Reset();
+ Grouper g(mClipManager);
+
+ int32_t appUnitsPerDevPixel =
+ aWrappingItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+
+ g.mDisplayListBuilder = aDisplayListBuilder;
+ RefPtr<WebRenderGroupData> groupData =
+ CreateOrRecycleWebRenderUserData<WebRenderGroupData>(aWrappingItem);
+
+ bool snapped;
+ nsRect groupBounds =
+ aWrappingItem->GetUntransformedBounds(aDisplayListBuilder, &snapped);
+ DIGroup& group = groupData->mSubGroup;
+
+ auto scale = aSc.GetInheritedScale();
+ GP("Inherited scale %f %f\n", scale.xScale, scale.yScale);
+
+ auto trans =
+ ViewAs<LayerPixel>(aSc.GetSnappingSurfaceTransform().GetTranslation());
+ auto snappedTrans = LayerIntPoint::Floor(trans);
+ LayerPoint residualOffset = trans - snappedTrans;
+
+ auto layerBounds =
+ ScaleToOutsidePixelsOffset(groupBounds, scale.xScale, scale.yScale,
+ appUnitsPerDevPixel, residualOffset);
+
+ const nsRect& untransformedPaintRect =
+ aWrappingItem->GetUntransformedPaintRect();
+
+ auto visibleRect = ScaleToOutsidePixelsOffset(
+ untransformedPaintRect, scale.xScale, scale.yScale,
+ appUnitsPerDevPixel, residualOffset)
+ .Intersect(layerBounds);
+
+ GP("LayerBounds: %d %d %d %d\n", layerBounds.x, layerBounds.y,
+ layerBounds.width, layerBounds.height);
+ GP("VisibleRect: %d %d %d %d\n", visibleRect.x, visibleRect.y,
+ visibleRect.width, visibleRect.height);
+
+ GP("Inherited scale %f %f\n", scale.xScale, scale.yScale);
+
+ group.mInvalidRect.SetEmpty();
+ if (group.mAppUnitsPerDevPixel != appUnitsPerDevPixel ||
+ group.mScale != scale || group.mResidualOffset != residualOffset) {
+ GP("Property change. Deleting blob\n");
+
+ if (group.mAppUnitsPerDevPixel != appUnitsPerDevPixel) {
+ GP(" App unit change %d -> %d\n", group.mAppUnitsPerDevPixel,
+ appUnitsPerDevPixel);
+ }
+
+ if (group.mScale != scale) {
+ GP(" Scale %f %f -> %f %f\n", group.mScale.xScale, group.mScale.yScale,
+ scale.xScale, scale.yScale);
+ }
+
+ if (group.mResidualOffset != residualOffset) {
+ GP(" Residual Offset %f %f -> %f %f\n", group.mResidualOffset.x.value,
+ group.mResidualOffset.y.value, residualOffset.x.value,
+ residualOffset.y.value);
+ }
+
+ group.ClearItems();
+ group.ClearImageKey(mManager->GetRenderRootStateManager());
+ }
+
+ ScrollableLayerGuid::ViewID scrollId = ScrollableLayerGuid::NULL_SCROLL_ID;
+ if (const ActiveScrolledRoot* asr = aWrappingItem->GetActiveScrolledRoot()) {
+ scrollId = asr->GetViewId();
+ }
+
+ g.mAppUnitsPerDevPixel = appUnitsPerDevPixel;
+ group.mResidualOffset = residualOffset;
+ group.mLayerBounds = layerBounds;
+ group.mVisibleRect = visibleRect;
+ group.mActualBounds = LayerIntRect();
+ group.mHitTestBounds = LayerIntRect();
+ group.mPreservedRect = group.mVisibleRect.Intersect(group.mLastVisibleRect);
+ group.mAppUnitsPerDevPixel = appUnitsPerDevPixel;
+ group.mClippedImageBounds = layerBounds;
+
+ g.mTransform =
+ Matrix::Scaling(scale).PostTranslate(residualOffset.x, residualOffset.y);
+ group.mScale = scale;
+ group.mScrollId = scrollId;
+ g.ConstructGroups(aDisplayListBuilder, this, aBuilder, aResources, &group,
+ aList, aWrappingItem, aSc);
+ mClipManager.EndList(aSc);
+}
+
+WebRenderCommandBuilder::WebRenderCommandBuilder(
+ WebRenderLayerManager* aManager)
+ : mManager(aManager),
+ mLastAsr(nullptr),
+ mBuilderDumpIndex(0),
+ mDumpIndent(0),
+ mDoGrouping(false),
+ mContainsSVGGroup(false) {}
+
+void WebRenderCommandBuilder::Destroy() {
+ mLastCanvasDatas.Clear();
+ ClearCachedResources();
+}
+
+void WebRenderCommandBuilder::EmptyTransaction() {
+ // We need to update canvases that might have changed.
+ for (RefPtr<WebRenderCanvasData> canvasData : mLastCanvasDatas) {
+ WebRenderCanvasRendererAsync* canvas = canvasData->GetCanvasRenderer();
+ if (canvas) {
+ canvas->UpdateCompositableClientForEmptyTransaction();
+ }
+ }
+}
+
+bool WebRenderCommandBuilder::NeedsEmptyTransaction() {
+ return !mLastCanvasDatas.IsEmpty();
+}
+
+void WebRenderCommandBuilder::BuildWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResourceUpdates, nsDisplayList* aDisplayList,
+ nsDisplayListBuilder* aDisplayListBuilder, WebRenderScrollData& aScrollData,
+ WrFiltersHolder&& aFilters) {
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_WRDisplayList);
+
+ StackingContextHelper sc;
+ aScrollData = WebRenderScrollData(mManager, aDisplayListBuilder);
+ MOZ_ASSERT(mLayerScrollData.empty());
+ mClipManager.BeginBuild(mManager, aBuilder);
+ mHitTestInfoManager.Reset();
+
+ mBuilderDumpIndex = 0;
+ mLastCanvasDatas.Clear();
+ mLastAsr = nullptr;
+ mContainsSVGGroup = false;
+ MOZ_ASSERT(mDumpIndent == 0);
+
+ {
+ wr::StackingContextParams params;
+ params.mRootReferenceFrame = aDisplayListBuilder->RootReferenceFrame();
+ params.mFilters = std::move(aFilters.filters);
+ params.mFilterDatas = std::move(aFilters.filter_datas);
+ params.clip =
+ wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
+
+ StackingContextHelper pageRootSc(sc, nullptr, nullptr, nullptr, aBuilder,
+ params);
+ if (ShouldDumpDisplayList(aDisplayListBuilder)) {
+ mBuilderDumpIndex =
+ aBuilder.Dump(mDumpIndent + 1, Some(mBuilderDumpIndex), Nothing());
+ }
+ CreateWebRenderCommandsFromDisplayList(aDisplayList, nullptr,
+ aDisplayListBuilder, pageRootSc,
+ aBuilder, aResourceUpdates);
+ }
+
+ // Make a "root" layer data that has everything else as descendants
+ mLayerScrollData.emplace_back();
+ mLayerScrollData.back().InitializeRoot(mLayerScrollData.size() - 1);
+ auto callback =
+ [&aScrollData](ScrollableLayerGuid::ViewID aScrollId) -> bool {
+ return aScrollData.HasMetadataFor(aScrollId).isSome();
+ };
+ Maybe<ScrollMetadata> rootMetadata =
+ nsLayoutUtils::GetRootMetadata(aDisplayListBuilder, mManager, callback);
+ if (rootMetadata) {
+ // Put the fallback root metadata on the rootmost layer that is
+ // a matching async zoom container, or the root layer that we just
+ // created above.
+ size_t rootMetadataTarget = mLayerScrollData.size() - 1;
+ for (size_t i = rootMetadataTarget; i > 0; i--) {
+ if (auto zoomContainerId =
+ mLayerScrollData[i - 1].GetAsyncZoomContainerId()) {
+ if (*zoomContainerId == rootMetadata->GetMetrics().GetScrollId()) {
+ rootMetadataTarget = i - 1;
+ break;
+ }
+ }
+ }
+ mLayerScrollData[rootMetadataTarget].AppendScrollMetadata(
+ aScrollData, rootMetadata.ref());
+ }
+
+ // Append the WebRenderLayerScrollData items into WebRenderScrollData
+ // in reverse order, from topmost to bottommost. This is in keeping with
+ // the semantics of WebRenderScrollData.
+ for (auto it = mLayerScrollData.rbegin(); it != mLayerScrollData.rend();
+ it++) {
+ aScrollData.AddLayerData(std::move(*it));
+ }
+ mLayerScrollData.clear();
+ mClipManager.EndBuild();
+
+ // Remove the user data those are not displayed on the screen and
+ // also reset the data to unused for next transaction.
+ RemoveUnusedAndResetWebRenderUserData();
+}
+
+bool WebRenderCommandBuilder::ShouldDumpDisplayList(
+ nsDisplayListBuilder* aBuilder) {
+ return aBuilder && aBuilder->IsInActiveDocShell() &&
+ ((XRE_IsParentProcess() &&
+ StaticPrefs::gfx_webrender_debug_dl_dump_parent()) ||
+ (XRE_IsContentProcess() &&
+ StaticPrefs::gfx_webrender_debug_dl_dump_content()));
+}
+
+void WebRenderCommandBuilder::CreateWebRenderCommands(
+ nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ mHitTestInfoManager.ProcessItem(aItem, aBuilder, aDisplayListBuilder);
+ if (aItem->GetType() == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
+ // The hit test information was processed above.
+ return;
+ }
+
+ auto* item = aItem->AsPaintedDisplayItem();
+ MOZ_RELEASE_ASSERT(item, "Tried to paint item that cannot be painted");
+
+ if (aBuilder.ReuseItem(item)) {
+ // No further processing should be needed, since the item was reused.
+ return;
+ }
+
+ RenderRootStateManager* manager = mManager->GetRenderRootStateManager();
+
+ // Note: this call to CreateWebRenderCommands can recurse back into
+ // this function if the |item| is a wrapper for a sublist.
+ const bool createdWRCommands = aItem->CreateWebRenderCommands(
+ aBuilder, aResources, aSc, manager, aDisplayListBuilder);
+
+ if (!createdWRCommands) {
+ PushItemAsImage(aItem, aBuilder, aResources, aSc, aDisplayListBuilder);
+ }
+}
+
+// A helper struct to store information needed when creating a new
+// WebRenderLayerScrollData in CreateWebRenderCommandsFromDisplayList().
+// This information is gathered before the recursion, and then used to
+// emit the new layer after the recursion.
+struct NewLayerData {
+ size_t mLayerCountBeforeRecursing = 0;
+ const ActiveScrolledRoot* mStopAtAsr = nullptr;
+
+ // Information pertaining to the deferred transform.
+ nsDisplayTransform* mDeferredItem = nullptr;
+ ScrollableLayerGuid::ViewID mDeferredId = ScrollableLayerGuid::NULL_SCROLL_ID;
+ bool mTransformShouldGetOwnLayer = false;
+
+ void ComputeDeferredTransformInfo(
+ const StackingContextHelper& aSc, nsDisplayItem* aItem,
+ nsDisplayTransform* aLastDeferredTransform) {
+ // See the comments on StackingContextHelper::mDeferredTransformItem
+ // for an overview of what deferred transforms are.
+ // In the case where we deferred a transform, but have a child display
+ // item with a different ASR than the deferred transform item, we cannot
+ // put the transform on the WebRenderLayerScrollData item for the child.
+ // We cannot do this because it will not conform to APZ's expectations
+ // with respect to how the APZ tree ends up structured. In particular,
+ // the GetTransformToThis() for the child APZ (which is created for the
+ // child item's ASR) will not include the transform when we actually do
+ // want it to.
+ // When we run into this scenario, we solve it by creating two
+ // WebRenderLayerScrollData items; one that just holds the transform,
+ // that we deferred, and a child WebRenderLayerScrollData item that
+ // holds the scroll metadata for the child's ASR.
+ mDeferredItem = aSc.GetDeferredTransformItem();
+ // If this deferred transform is already slated to be emitted onto an
+ // ancestor layer, do not emit it on this layer as well. Note that it's
+ // sufficient to check the most recently deferred item here, because
+ // there's only one per stacking context, and we emit it when changing
+ // stacking contexts.
+ if (mDeferredItem == aLastDeferredTransform) {
+ mDeferredItem = nullptr;
+ }
+ if (mDeferredItem) {
+ // It's possible the transform's ASR is not only an ancestor of
+ // the item's ASR, but an ancestor of stopAtAsr. In such cases,
+ // don't use the transform at all at this level (it would be
+ // scrolled by stopAtAsr which is incorrect). The transform will
+ // instead be emitted as part of the ancestor WebRenderLayerScrollData
+ // node (the one with stopAtAsr as its item ASR), or one of its
+ // ancetors in turn.
+ if (ActiveScrolledRoot::IsProperAncestor(
+ mDeferredItem->GetActiveScrolledRoot(), mStopAtAsr)) {
+ mDeferredItem = nullptr;
+ }
+ }
+ if (mDeferredItem) {
+ if (const auto* asr = mDeferredItem->GetActiveScrolledRoot()) {
+ mDeferredId = asr->GetViewId();
+ }
+ if (mDeferredItem->GetActiveScrolledRoot() !=
+ aItem->GetActiveScrolledRoot()) {
+ mTransformShouldGetOwnLayer = true;
+ } else if (aItem->GetType() == DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
+ // A scroll info layer has its own scroll id that's not reflected
+ // in item->GetActiveScrolledRoot(), but will be added to the
+ // WebRenderLayerScrollData node, so it needs to be treated as
+ // having a distinct ASR from the deferred transform item.
+ mTransformShouldGetOwnLayer = true;
+ }
+ }
+ }
+};
+
+void WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList(
+ nsDisplayList* aDisplayList, nsDisplayItem* aWrappingItem,
+ nsDisplayListBuilder* aDisplayListBuilder, const StackingContextHelper& aSc,
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ bool aNewClipList) {
+ if (mDoGrouping) {
+ MOZ_RELEASE_ASSERT(
+ aWrappingItem,
+ "Only the root list should have a null wrapping item, and mDoGrouping "
+ "should never be true for the root list.");
+ GP("actually entering the grouping code\n");
+ DoGroupingForDisplayList(aDisplayList, aWrappingItem, aDisplayListBuilder,
+ aSc, aBuilder, aResources);
+ return;
+ }
+
+ bool dumpEnabled = ShouldDumpDisplayList(aDisplayListBuilder);
+ if (dumpEnabled) {
+ // If we're inside a nested display list, print the WR DL items from the
+ // wrapper item before we start processing the nested items.
+ mBuilderDumpIndex =
+ aBuilder.Dump(mDumpIndent + 1, Some(mBuilderDumpIndex), Nothing());
+ }
+
+ FlattenedDisplayListIterator iter(aDisplayListBuilder, aDisplayList);
+ if (!iter.HasNext()) {
+ return;
+ }
+
+ mDumpIndent++;
+ if (aNewClipList) {
+ mClipManager.BeginList(aSc);
+ }
+
+ const bool apzEnabled = mManager->AsyncPanZoomEnabled();
+ do {
+ nsDisplayItem* item = iter.GetNextItem();
+
+ DisplayItemType itemType = item->GetType();
+
+ // If this is a new (not retained/reused) item, then we need to disable
+ // the display item cache for descendants, since it's possible that some of
+ // them got cached with a flattened opacity values., which may no longer be
+ // applied.
+ Maybe<AutoDisplayItemCacheSuppressor> cacheSuppressor;
+
+ if (itemType == DisplayItemType::TYPE_OPACITY) {
+ nsDisplayOpacity* opacity = static_cast<nsDisplayOpacity*>(item);
+
+ if (!opacity->IsReused()) {
+ cacheSuppressor.emplace(aBuilder.GetDisplayItemCache());
+ }
+
+ if (opacity->CanApplyOpacityToChildren(
+ mManager->GetRenderRootStateManager()->LayerManager(),
+ aDisplayListBuilder, aBuilder.GetInheritedOpacity())) {
+ // If all our children support handling the opacity directly, then push
+ // the opacity and clip onto the builder and skip creating a stacking
+ // context.
+ float oldOpacity = aBuilder.GetInheritedOpacity();
+ const DisplayItemClipChain* oldClip = aBuilder.GetInheritedClipChain();
+ aBuilder.SetInheritedOpacity(oldOpacity * opacity->GetOpacity());
+ aBuilder.PushInheritedClipChain(aDisplayListBuilder,
+ opacity->GetClipChain());
+
+ CreateWebRenderCommandsFromDisplayList(opacity->GetChildren(), item,
+ aDisplayListBuilder, aSc,
+ aBuilder, aResources, false);
+
+ aBuilder.SetInheritedOpacity(oldOpacity);
+ aBuilder.SetInheritedClipChain(oldClip);
+ continue;
+ }
+ }
+
+ // If this is an unscrolled background color item, in the root display list
+ // for the parent process, consider doing opaque checks.
+ if (XRE_IsParentProcess() && !aWrappingItem &&
+ itemType == DisplayItemType::TYPE_BACKGROUND_COLOR &&
+ !item->GetActiveScrolledRoot() &&
+ item->GetClip().GetRoundedRectCount() == 0) {
+ bool snap;
+ nsRegion opaque = item->GetOpaqueRegion(aDisplayListBuilder, &snap);
+ if (opaque.GetNumRects() == 1) {
+ nsRect clippedOpaque =
+ item->GetClip().ApplyNonRoundedIntersection(opaque.GetBounds());
+ if (!clippedOpaque.IsEmpty()) {
+ aDisplayListBuilder->AddWindowOpaqueRegion(item->Frame(),
+ clippedOpaque);
+ }
+ }
+ }
+
+ Maybe<NewLayerData> newLayerData;
+ if (apzEnabled) {
+ // For some types of display items we want to force a new
+ // WebRenderLayerScrollData object, to ensure we preserve the APZ-relevant
+ // data that is in the display item.
+ if (item->UpdateScrollData(nullptr, nullptr)) {
+ newLayerData = Some(NewLayerData());
+ }
+
+ // Anytime the ASR changes we also want to force a new layer data because
+ // the stack of scroll metadata is going to be different for this
+ // display item than previously, so we can't squash the display items
+ // into the same "layer".
+ const ActiveScrolledRoot* asr = item->GetActiveScrolledRoot();
+ if (asr != mLastAsr) {
+ mLastAsr = asr;
+ newLayerData = Some(NewLayerData());
+ }
+
+ // Refer to the comment on StackingContextHelper::mDeferredTransformItem
+ // for an overview of what this is about. This bit of code applies to the
+ // case where we are deferring a transform item, and we then need to defer
+ // another transform with a different ASR. In such a case we cannot just
+ // merge the deferred transforms, but need to force a new
+ // WebRenderLayerScrollData item to flush the old deferred transform, so
+ // that we can then start deferring the new one.
+ if (!newLayerData && item->CreatesStackingContextHelper() &&
+ aSc.GetDeferredTransformItem() &&
+ aSc.GetDeferredTransformItem()->GetActiveScrolledRoot() != asr) {
+ newLayerData = Some(NewLayerData());
+ }
+
+ // If we're going to create a new layer data for this item, stash the
+ // ASR so that if we recurse into a sublist they will know where to stop
+ // walking up their ASR chain when building scroll metadata.
+ if (newLayerData) {
+ newLayerData->mLayerCountBeforeRecursing = mLayerScrollData.size();
+ newLayerData->mStopAtAsr =
+ mAsrStack.empty() ? nullptr : mAsrStack.back();
+ newLayerData->ComputeDeferredTransformInfo(
+ aSc, item,
+ mDeferredTransformStack.empty() ? nullptr
+ : mDeferredTransformStack.back());
+
+ // Ensure our children's |stopAtAsr| is not be an ancestor of our
+ // |stopAtAsr|, otherwise we could get cyclic scroll metadata
+ // annotations.
+ const ActiveScrolledRoot* stopAtAsrForChildren =
+ ActiveScrolledRoot::PickDescendant(asr, newLayerData->mStopAtAsr);
+ // Additionally, while unusual and probably indicative of a poorly
+ // behaved display list, it's possible to have a deferred transform item
+ // which we will emit as its own layer on the way out of the recursion,
+ // whose ASR (let's call it T) is a *descendant* of the current item's
+ // ASR. In such cases, make sure our children have stopAtAsr=T,
+ // otherwise ASRs in the range [T, asr) may be emitted in duplicate,
+ // leading again to cylic scroll metadata annotations.
+ if (newLayerData->mTransformShouldGetOwnLayer) {
+ stopAtAsrForChildren = ActiveScrolledRoot::PickDescendant(
+ stopAtAsrForChildren,
+ newLayerData->mDeferredItem->GetActiveScrolledRoot());
+ }
+ mAsrStack.push_back(stopAtAsrForChildren);
+
+ // If we're going to emit a deferred transform onto this layer,
+ // keep track of that so descendant layers know not to emit the
+ // same deferred transform.
+ if (newLayerData->mDeferredItem) {
+ mDeferredTransformStack.push_back(newLayerData->mDeferredItem);
+ }
+ }
+ }
+
+ // This is where we emulate the clip/scroll stack that was previously
+ // implemented on the WR display list side.
+ auto spaceAndClipChain = mClipManager.SwitchItem(aDisplayListBuilder, item);
+ wr::SpaceAndClipChainHelper saccHelper(aBuilder, spaceAndClipChain);
+
+ { // scope restoreDoGrouping
+ AutoRestore<bool> restoreDoGrouping(mDoGrouping);
+ if (itemType == DisplayItemType::TYPE_SVG_WRAPPER) {
+ // Inside an <svg>, all display items that are not LAYER_ACTIVE wrapper
+ // display items (like animated transforms / opacity) share the same
+ // animated geometry root, so we can combine subsequent items of that
+ // type into the same image.
+ mContainsSVGGroup = mDoGrouping = true;
+ GP("attempting to enter the grouping code\n");
+ }
+
+ if (dumpEnabled) {
+ std::stringstream ss;
+ nsIFrame::PrintDisplayItem(aDisplayListBuilder, item, ss,
+ static_cast<uint32_t>(mDumpIndent));
+ printf_stderr("%s", ss.str().c_str());
+ }
+
+ CreateWebRenderCommands(item, aBuilder, aResources, aSc,
+ aDisplayListBuilder);
+
+ if (dumpEnabled) {
+ mBuilderDumpIndex =
+ aBuilder.Dump(mDumpIndent + 1, Some(mBuilderDumpIndex), Nothing());
+ }
+ }
+
+ if (apzEnabled) {
+ if (newLayerData) {
+ // Pop the thing we pushed before the recursion, so the topmost item on
+ // the stack is enclosing display item's ASR (or the stack is empty)
+ mAsrStack.pop_back();
+
+ if (newLayerData->mDeferredItem) {
+ MOZ_ASSERT(!mDeferredTransformStack.empty());
+ mDeferredTransformStack.pop_back();
+ }
+
+ const ActiveScrolledRoot* stopAtAsr = newLayerData->mStopAtAsr;
+
+ int32_t descendants =
+ mLayerScrollData.size() - newLayerData->mLayerCountBeforeRecursing;
+
+ nsDisplayTransform* deferred = newLayerData->mDeferredItem;
+ ScrollableLayerGuid::ViewID deferredId = newLayerData->mDeferredId;
+
+ if (newLayerData->mTransformShouldGetOwnLayer) {
+ // This creates the child WebRenderLayerScrollData for |item|, but
+ // omits the transform (hence the Nothing() as the last argument to
+ // Initialize(...)). We also need to make sure that the ASR from
+ // the deferred transform item is not on this node, so we use that
+ // ASR as the "stop at" ASR for this WebRenderLayerScrollData.
+ mLayerScrollData.emplace_back();
+ mLayerScrollData.back().Initialize(
+ mManager->GetScrollData(), item, descendants,
+ deferred->GetActiveScrolledRoot(), Nothing(),
+ ScrollableLayerGuid::NULL_SCROLL_ID);
+
+ // The above WebRenderLayerScrollData will also be a descendant of
+ // the transform-holding WebRenderLayerScrollData we create below.
+ descendants++;
+
+ // This creates the WebRenderLayerScrollData for the deferred
+ // transform item. This holds the transform matrix and the remaining
+ // ASRs needed to complete the ASR chain (i.e. the ones from the
+ // stopAtAsr down to the deferred transform item's ASR, which must be
+ // "between" stopAtAsr and |item|'s ASR in the ASR tree).
+ mLayerScrollData.emplace_back();
+ mLayerScrollData.back().Initialize(
+ mManager->GetScrollData(), deferred, descendants, stopAtAsr,
+ aSc.GetDeferredTransformMatrix(), deferredId);
+ } else {
+ // This is the "simple" case where we don't need to create two
+ // WebRenderLayerScrollData items; we can just create one that also
+ // holds the deferred transform matrix, if any.
+ mLayerScrollData.emplace_back();
+ mLayerScrollData.back().Initialize(
+ mManager->GetScrollData(), item, descendants, stopAtAsr,
+ deferred ? aSc.GetDeferredTransformMatrix() : Nothing(),
+ deferredId);
+ }
+ }
+ }
+ } while (iter.HasNext());
+
+ mDumpIndent--;
+ if (aNewClipList) {
+ mClipManager.EndList(aSc);
+ }
+}
+
+void WebRenderCommandBuilder::PushOverrideForASR(
+ const ActiveScrolledRoot* aASR, const wr::WrSpatialId& aSpatialId) {
+ mClipManager.PushOverrideForASR(aASR, aSpatialId);
+}
+
+void WebRenderCommandBuilder::PopOverrideForASR(
+ const ActiveScrolledRoot* aASR) {
+ mClipManager.PopOverrideForASR(aASR);
+}
+
+Maybe<wr::ImageKey> WebRenderCommandBuilder::CreateImageKey(
+ nsDisplayItem* aItem, ImageContainer* aContainer,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ mozilla::wr::ImageRendering aRendering, const StackingContextHelper& aSc,
+ gfx::IntSize& aSize, const Maybe<LayoutDeviceRect>& aAsyncImageBounds) {
+ RefPtr<WebRenderImageData> imageData =
+ CreateOrRecycleWebRenderUserData<WebRenderImageData>(aItem);
+ MOZ_ASSERT(imageData);
+
+ if (aContainer->IsAsync()) {
+ MOZ_ASSERT(aAsyncImageBounds);
+
+ LayoutDeviceRect rect = aAsyncImageBounds.value();
+ LayoutDeviceRect scBounds(LayoutDevicePoint(0, 0), rect.Size());
+ // TODO!
+ // We appear to be using the image bridge for a lot (most/all?) of
+ // layers-free image handling and that breaks frame consistency.
+ imageData->CreateAsyncImageWebRenderCommands(
+ aBuilder, aContainer, aSc, rect, scBounds, aContainer->GetRotation(),
+ aRendering, wr::MixBlendMode::Normal, !aItem->BackfaceIsHidden());
+ return Nothing();
+ }
+
+ AutoLockImage autoLock(aContainer);
+ if (!autoLock.HasImage()) {
+ return Nothing();
+ }
+ mozilla::layers::Image* image = autoLock.GetImage();
+ aSize = image->GetSize();
+
+ return imageData->UpdateImageKey(aContainer, aResources);
+}
+
+bool WebRenderCommandBuilder::PushImage(
+ nsDisplayItem* aItem, ImageContainer* aContainer,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, const LayoutDeviceRect& aRect,
+ const LayoutDeviceRect& aClip) {
+ auto rendering = wr::ToImageRendering(aItem->Frame()->UsedImageRendering());
+ gfx::IntSize size;
+ Maybe<wr::ImageKey> key =
+ CreateImageKey(aItem, aContainer, aBuilder, aResources, rendering, aSc,
+ size, Some(aRect));
+ if (aContainer->IsAsync()) {
+ // Async ImageContainer does not create ImageKey, instead it uses Pipeline.
+ MOZ_ASSERT(key.isNothing());
+ return true;
+ }
+ if (!key) {
+ return false;
+ }
+
+ auto r = wr::ToLayoutRect(aRect);
+ auto c = wr::ToLayoutRect(aClip);
+ aBuilder.PushImage(r, c, !aItem->BackfaceIsHidden(), false, rendering,
+ key.value());
+
+ return true;
+}
+
+Maybe<wr::ImageKey> WebRenderCommandBuilder::CreateImageProviderKey(
+ nsDisplayItem* aItem, image::WebRenderImageProvider* aProvider,
+ image::ImgDrawResult aDrawResult,
+ mozilla::wr::IpcResourceUpdateQueue& aResources) {
+ RefPtr<WebRenderImageProviderData> imageData =
+ CreateOrRecycleWebRenderUserData<WebRenderImageProviderData>(aItem);
+ MOZ_ASSERT(imageData);
+ return imageData->UpdateImageKey(aProvider, aDrawResult, aResources);
+}
+
+bool WebRenderCommandBuilder::PushImageProvider(
+ nsDisplayItem* aItem, image::WebRenderImageProvider* aProvider,
+ image::ImgDrawResult aDrawResult, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const LayoutDeviceRect& aRect, const LayoutDeviceRect& aClip) {
+ Maybe<wr::ImageKey> key =
+ CreateImageProviderKey(aItem, aProvider, aDrawResult, aResources);
+ if (!key) {
+ return false;
+ }
+
+ bool antialiased = aItem->GetType() == DisplayItemType::TYPE_SVG_GEOMETRY;
+
+ auto rendering = wr::ToImageRendering(aItem->Frame()->UsedImageRendering());
+ auto r = wr::ToLayoutRect(aRect);
+ auto c = wr::ToLayoutRect(aClip);
+ aBuilder.PushImage(r, c, !aItem->BackfaceIsHidden(), antialiased, rendering,
+ key.value());
+
+ return true;
+}
+
+static void PaintItemByDrawTarget(nsDisplayItem* aItem, gfx::DrawTarget* aDT,
+ const LayoutDevicePoint& aOffset,
+ const IntRect& visibleRect,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ const gfx::MatrixScales& aScale,
+ Maybe<gfx::DeviceColor>& aHighlight) {
+ MOZ_ASSERT(aDT && aDT->IsValid());
+
+ // XXX Why is this ClearRect() needed?
+ aDT->ClearRect(Rect(visibleRect));
+ gfxContext context(aDT);
+
+ switch (aItem->GetType()) {
+ case DisplayItemType::TYPE_SVG_WRAPPER:
+ case DisplayItemType::TYPE_MASK: {
+ // These items should be handled by other code paths
+ MOZ_RELEASE_ASSERT(0);
+ break;
+ }
+ default:
+ if (!aItem->AsPaintedDisplayItem()) {
+ break;
+ }
+
+ context.SetMatrix(context.CurrentMatrix().PreScale(aScale).PreTranslate(
+ -aOffset.x, -aOffset.y));
+ if (aDisplayListBuilder->IsPaintingToWindow()) {
+ aItem->Frame()->AddStateBits(NS_FRAME_PAINTED_THEBES);
+ }
+ aItem->AsPaintedDisplayItem()->Paint(aDisplayListBuilder, &context);
+ break;
+ }
+
+ if (aHighlight && aItem->GetType() != DisplayItemType::TYPE_MASK) {
+ // Apply highlight fills, if the appropriate prefs are set.
+ // We don't do this for masks because we'd be filling the A8 mask surface,
+ // which isn't very useful.
+ aDT->SetTransform(gfx::Matrix());
+ aDT->FillRect(Rect(visibleRect), gfx::ColorPattern(aHighlight.value()));
+ }
+}
+
+bool WebRenderCommandBuilder::ComputeInvalidationForDisplayItem(
+ nsDisplayListBuilder* aBuilder, const nsPoint& aShift,
+ nsDisplayItem* aItem) {
+ RefPtr<WebRenderFallbackData> fallbackData =
+ CreateOrRecycleWebRenderUserData<WebRenderFallbackData>(aItem);
+
+ nsRect invalid;
+ if (!fallbackData->mGeometry || aItem->IsInvalid(invalid)) {
+ fallbackData->mGeometry = WrapUnique(aItem->AllocateGeometry(aBuilder));
+ return true;
+ }
+
+ fallbackData->mGeometry->MoveBy(aShift);
+ nsRegion combined;
+ aItem->ComputeInvalidationRegion(aBuilder, fallbackData->mGeometry.get(),
+ &combined);
+
+ UniquePtr<nsDisplayItemGeometry> geometry;
+ if (!combined.IsEmpty() || aItem->NeedsGeometryUpdates()) {
+ geometry = WrapUnique(aItem->AllocateGeometry(aBuilder));
+ }
+
+ fallbackData->mClip.AddOffsetAndComputeDifference(
+ aShift, fallbackData->mGeometry->ComputeInvalidationRegion(),
+ aItem->GetClip(),
+ geometry ? geometry->ComputeInvalidationRegion()
+ : fallbackData->mGeometry->ComputeInvalidationRegion(),
+ &combined);
+
+ if (geometry) {
+ fallbackData->mGeometry = std::move(geometry);
+ }
+ fallbackData->mClip = aItem->GetClip();
+
+ if (!combined.IsEmpty()) {
+ return true;
+ } else if (aItem->GetChildren()) {
+ return ComputeInvalidationForDisplayList(aBuilder, aShift,
+ aItem->GetChildren());
+ }
+ return false;
+}
+
+bool WebRenderCommandBuilder::ComputeInvalidationForDisplayList(
+ nsDisplayListBuilder* aBuilder, const nsPoint& aShift,
+ nsDisplayList* aList) {
+ FlattenedDisplayListIterator iter(aBuilder, aList);
+ while (iter.HasNext()) {
+ if (ComputeInvalidationForDisplayItem(aBuilder, aShift,
+ iter.GetNextItem())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// When drawing fallback images we create either
+// a real image or a blob image that will contain the display item.
+// In the case of a blob image we paint the item at 0,0 instead
+// of trying to keep at aItem->GetBounds().TopLeft() like we do
+// with SVG. We do this because there's not necessarily a reference frame
+// between us and the rest of the world so the the coordinates
+// that we get for the bounds are not necessarily stable across scrolling
+// or other movement.
+already_AddRefed<WebRenderFallbackData>
+WebRenderCommandBuilder::GenerateFallbackData(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder, LayoutDeviceRect& aImageRect) {
+ const bool paintOnContentSide = aItem->MustPaintOnContentSide();
+ bool useBlobImage =
+ StaticPrefs::gfx_webrender_blob_images() && !paintOnContentSide;
+ Maybe<gfx::DeviceColor> highlight = Nothing();
+ if (StaticPrefs::gfx_webrender_debug_highlight_painted_layers()) {
+ highlight = Some(useBlobImage ? gfx::DeviceColor(1.0, 0.0, 0.0, 0.5)
+ : gfx::DeviceColor(1.0, 1.0, 0.0, 0.5));
+ }
+
+ RefPtr<WebRenderFallbackData> fallbackData =
+ CreateOrRecycleWebRenderUserData<WebRenderFallbackData>(aItem);
+
+ bool snap;
+ nsRect itemBounds = aItem->GetBounds(aDisplayListBuilder, &snap);
+
+ // Blob images will only draw the visible area of the blob so we don't need to
+ // clip them here and can just rely on the webrender clipping.
+ // TODO We also don't clip native themed widget to avoid over-invalidation
+ // during scrolling. It would be better to support a sort of streaming/tiling
+ // scheme for large ones but the hope is that we should not have large native
+ // themed items.
+ nsRect paintBounds = (useBlobImage || paintOnContentSide)
+ ? itemBounds
+ : aItem->GetClippedBounds(aDisplayListBuilder);
+
+ nsRect buildingRect = aItem->GetBuildingRect();
+
+ const int32_t appUnitsPerDevPixel =
+ aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+ auto bounds =
+ LayoutDeviceRect::FromAppUnits(paintBounds, appUnitsPerDevPixel);
+ if (bounds.IsEmpty()) {
+ return nullptr;
+ }
+
+ MatrixScales scale = aSc.GetInheritedScale();
+ MatrixScales oldScale = fallbackData->mScale;
+ // We tolerate slight changes in scale so that we don't, for example,
+ // rerasterize on MotionMark
+ bool differentScale = gfx::FuzzyEqual(scale.xScale, oldScale.xScale, 1e-6f) &&
+ gfx::FuzzyEqual(scale.yScale, oldScale.yScale, 1e-6f);
+
+ auto layerScale = LayoutDeviceToLayerScale2D::FromUnknownScale(scale);
+
+ auto trans =
+ ViewAs<LayerPixel>(aSc.GetSnappingSurfaceTransform().GetTranslation());
+
+ if (!FitsInt32(trans.X()) || !FitsInt32(trans.Y())) {
+ // The translation overflowed int32_t.
+ return nullptr;
+ }
+
+ auto snappedTrans = LayerIntPoint::Floor(trans);
+ LayerPoint residualOffset = trans - snappedTrans;
+
+ nsRegion opaqueRegion = aItem->GetOpaqueRegion(aDisplayListBuilder, &snap);
+ wr::OpacityType opacity = opaqueRegion.Contains(paintBounds)
+ ? wr::OpacityType::Opaque
+ : wr::OpacityType::HasAlphaChannel;
+
+ LayerIntRect dtRect, visibleRect;
+ // If we think the item is opaque we round the bounds
+ // to the nearest pixel instead of rounding them out. If we rounded
+ // out we'd potentially introduce transparent pixels.
+ //
+ // Ideally we'd be able to ask an item its bounds in pixels and whether
+ // they're all opaque. Unfortunately no such API exists so we currently
+ // just hope that we get it right.
+ if (aBuilder.GetInheritedOpacity() == 1.0f &&
+ opacity == wr::OpacityType::Opaque && snap) {
+ dtRect = LayerIntRect::FromUnknownRect(
+ ScaleToNearestPixelsOffset(paintBounds, scale.xScale, scale.yScale,
+ appUnitsPerDevPixel, residualOffset));
+
+ visibleRect =
+ LayerIntRect::FromUnknownRect(
+ ScaleToNearestPixelsOffset(buildingRect, scale.xScale, scale.yScale,
+ appUnitsPerDevPixel, residualOffset))
+ .Intersect(dtRect);
+ } else {
+ dtRect = ScaleToOutsidePixelsOffset(paintBounds, scale.xScale, scale.yScale,
+ appUnitsPerDevPixel, residualOffset);
+
+ visibleRect =
+ ScaleToOutsidePixelsOffset(buildingRect, scale.xScale, scale.yScale,
+ appUnitsPerDevPixel, residualOffset)
+ .Intersect(dtRect);
+ }
+
+ auto visibleSize = visibleRect.Size();
+ // these rectangles can overflow from scaling so try to
+ // catch that with IsEmpty() checks. See bug 1622126.
+ if (visibleSize.IsEmpty() || dtRect.IsEmpty()) {
+ return nullptr;
+ }
+
+ if (useBlobImage) {
+ // Display item bounds should be unscaled
+ aImageRect = visibleRect / layerScale;
+ } else {
+ // Display item bounds should be unscaled
+ aImageRect = dtRect / layerScale;
+ }
+
+ // We always paint items at 0,0 so the visibleRect that we use inside the blob
+ // is needs to be adjusted by the display item bounds top left.
+ visibleRect -= dtRect.TopLeft();
+
+ nsDisplayItemGeometry* geometry = fallbackData->mGeometry.get();
+
+ bool needPaint = true;
+
+ MOZ_RELEASE_ASSERT(aItem->GetType() != DisplayItemType::TYPE_SVG_WRAPPER);
+ if (geometry && !fallbackData->IsInvalid() &&
+ aItem->GetType() != DisplayItemType::TYPE_SVG_WRAPPER && differentScale) {
+ nsRect invalid;
+ if (!aItem->IsInvalid(invalid)) {
+ nsPoint shift = itemBounds.TopLeft() - geometry->mBounds.TopLeft();
+ geometry->MoveBy(shift);
+
+ nsRegion invalidRegion;
+ aItem->ComputeInvalidationRegion(aDisplayListBuilder, geometry,
+ &invalidRegion);
+
+ nsRect lastBounds = fallbackData->mBounds;
+ lastBounds.MoveBy(shift);
+
+ if (lastBounds.IsEqualInterior(paintBounds) && invalidRegion.IsEmpty() &&
+ aBuilder.GetInheritedOpacity() == fallbackData->mOpacity) {
+ if (aItem->GetType() == DisplayItemType::TYPE_FILTER) {
+ needPaint = ComputeInvalidationForDisplayList(
+ aDisplayListBuilder, shift, aItem->GetChildren());
+ if (!buildingRect.IsEqualInterior(fallbackData->mBuildingRect)) {
+ needPaint = true;
+ }
+ } else {
+ needPaint = false;
+ }
+ }
+ }
+ }
+
+ if (needPaint || !fallbackData->GetImageKey()) {
+ fallbackData->mGeometry =
+ WrapUnique(aItem->AllocateGeometry(aDisplayListBuilder));
+
+ gfx::SurfaceFormat format = aItem->GetType() == DisplayItemType::TYPE_MASK
+ ? gfx::SurfaceFormat::A8
+ : (opacity == wr::OpacityType::Opaque
+ ? gfx::SurfaceFormat::B8G8R8X8
+ : gfx::SurfaceFormat::B8G8R8A8);
+ if (useBlobImage) {
+ MOZ_ASSERT(!opaqueRegion.IsComplex());
+
+ std::vector<RefPtr<ScaledFont>> fonts;
+ bool validFonts = true;
+ RefPtr<WebRenderDrawEventRecorder> recorder =
+ MakeAndAddRef<WebRenderDrawEventRecorder>(
+ [&](MemStream& aStream,
+ std::vector<RefPtr<ScaledFont>>& aScaledFonts) {
+ size_t count = aScaledFonts.size();
+ aStream.write((const char*)&count, sizeof(count));
+ for (auto& scaled : aScaledFonts) {
+ Maybe<wr::FontInstanceKey> key =
+ mManager->WrBridge()->GetFontKeyForScaledFont(scaled,
+ aResources);
+ if (key.isNothing()) {
+ validFonts = false;
+ break;
+ }
+ BlobFont font = {key.value(), scaled};
+ aStream.write((const char*)&font, sizeof(font));
+ }
+ fonts = std::move(aScaledFonts);
+ });
+ RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(
+ gfx::BackendType::SKIA, gfx::IntSize(1, 1), format);
+ RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(
+ recorder, dummyDt, (dtRect - dtRect.TopLeft()).ToUnknownRect());
+ if (aBuilder.GetInheritedOpacity() != 1.0f) {
+ dt->PushLayer(false, aBuilder.GetInheritedOpacity(), nullptr,
+ gfx::Matrix());
+ }
+ PaintItemByDrawTarget(aItem, dt, (dtRect / layerScale).TopLeft(),
+ /*aVisibleRect: */ dt->GetRect(),
+ aDisplayListBuilder, scale, highlight);
+ if (aBuilder.GetInheritedOpacity() != 1.0f) {
+ dt->PopLayer();
+ }
+
+ // the item bounds are relative to the blob origin which is
+ // dtRect.TopLeft()
+ recorder->FlushItem((dtRect - dtRect.TopLeft()).ToUnknownRect());
+ recorder->Finish();
+
+ if (!validFonts) {
+ gfxCriticalNote << "Failed serializing fonts for blob image";
+ return nullptr;
+ }
+
+ Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData,
+ recorder->mOutputStream.mLength);
+ wr::BlobImageKey key =
+ wr::BlobImageKey{mManager->WrBridge()->GetNextImageKey()};
+ wr::ImageDescriptor descriptor(visibleSize.ToUnknownSize(), 0,
+ dt->GetFormat(), opacity);
+ if (!aResources.AddBlobImage(
+ key, descriptor, bytes,
+ ViewAs<ImagePixel>(visibleRect,
+ PixelCastJustification::LayerIsImage))) {
+ return nullptr;
+ }
+ TakeExternalSurfaces(recorder, fallbackData->mExternalSurfaces,
+ mManager->GetRenderRootStateManager(), aResources);
+ fallbackData->SetBlobImageKey(key);
+ fallbackData->SetFonts(fonts);
+ } else {
+ WebRenderImageData* imageData = fallbackData->PaintIntoImage();
+
+ imageData->CreateImageClientIfNeeded();
+ RefPtr<ImageClient> imageClient = imageData->GetImageClient();
+ RefPtr<ImageContainer> imageContainer = MakeAndAddRef<ImageContainer>();
+
+ {
+ UpdateImageHelper helper(imageContainer, imageClient,
+ dtRect.Size().ToUnknownSize(), format);
+ {
+ RefPtr<gfx::DrawTarget> dt = helper.GetDrawTarget();
+ if (!dt) {
+ return nullptr;
+ }
+ if (aBuilder.GetInheritedOpacity() != 1.0f) {
+ dt->PushLayer(false, aBuilder.GetInheritedOpacity(), nullptr,
+ gfx::Matrix());
+ }
+ PaintItemByDrawTarget(aItem, dt,
+ /*aOffset: */ aImageRect.TopLeft(),
+ /*aVisibleRect: */ dt->GetRect(),
+ aDisplayListBuilder, scale, highlight);
+ if (aBuilder.GetInheritedOpacity() != 1.0f) {
+ dt->PopLayer();
+ }
+ }
+
+ // Update image if there it's invalidated.
+ if (!helper.UpdateImage()) {
+ return nullptr;
+ }
+ }
+
+ // Force update the key in fallback data since we repaint the image in
+ // this path. If not force update, fallbackData may reuse the original key
+ // because it doesn't know UpdateImageHelper already updated the image
+ // container.
+ if (!imageData->UpdateImageKey(imageContainer, aResources, true)) {
+ return nullptr;
+ }
+ }
+
+ fallbackData->mScale = scale;
+ fallbackData->mOpacity = aBuilder.GetInheritedOpacity();
+ fallbackData->SetInvalid(false);
+ }
+
+ if (useBlobImage) {
+ MOZ_DIAGNOSTIC_ASSERT(mManager->WrBridge()->MatchesNamespace(
+ fallbackData->GetBlobImageKey().ref()),
+ "Stale blob key for fallback!");
+
+ aResources.SetBlobImageVisibleArea(
+ fallbackData->GetBlobImageKey().value(),
+ ViewAs<ImagePixel>(visibleRect, PixelCastJustification::LayerIsImage));
+ }
+
+ // Update current bounds to fallback data
+ fallbackData->mBounds = paintBounds;
+ fallbackData->mBuildingRect = buildingRect;
+
+ MOZ_ASSERT(fallbackData->GetImageKey());
+
+ return fallbackData.forget();
+}
+
+void WebRenderMaskData::ClearImageKey() {
+ if (mBlobKey) {
+ mManager->AddBlobImageKeyForDiscard(mBlobKey.value());
+ }
+ mBlobKey.reset();
+}
+
+void WebRenderMaskData::Invalidate() {
+ mMaskStyle = nsStyleImageLayers(nsStyleImageLayers::LayerType::Mask);
+}
+
+Maybe<wr::ImageMask> WebRenderCommandBuilder::BuildWrMaskImage(
+ nsDisplayMasksAndClipPaths* aMaskItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ const LayoutDeviceRect& aBounds) {
+ RefPtr<WebRenderMaskData> maskData =
+ CreateOrRecycleWebRenderUserData<WebRenderMaskData>(aMaskItem);
+
+ if (!maskData) {
+ return Nothing();
+ }
+
+ bool snap;
+ nsRect bounds = aMaskItem->GetBounds(aDisplayListBuilder, &snap);
+
+ const int32_t appUnitsPerDevPixel =
+ aMaskItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+
+ MatrixScales scale = aSc.GetInheritedScale();
+ MatrixScales oldScale = maskData->mScale;
+ // This scale determination should probably be done using
+ // ChooseScaleAndSetTransform but for now we just fake it.
+ // We tolerate slight changes in scale so that we don't, for example,
+ // rerasterize on MotionMark
+ bool sameScale = FuzzyEqual(scale.xScale, oldScale.xScale, 1e-6f) &&
+ FuzzyEqual(scale.yScale, oldScale.yScale, 1e-6f);
+
+ LayerIntRect itemRect =
+ LayerIntRect::FromUnknownRect(bounds.ScaleToOutsidePixels(
+ scale.xScale, scale.yScale, appUnitsPerDevPixel));
+
+ LayerIntRect visibleRect =
+ LayerIntRect::FromUnknownRect(
+ aMaskItem->GetBuildingRect().ScaleToOutsidePixels(
+ scale.xScale, scale.yScale, appUnitsPerDevPixel))
+ .SafeIntersect(itemRect);
+
+ if (visibleRect.IsEmpty()) {
+ return Nothing();
+ }
+
+ LayoutDeviceToLayerScale2D layerScale(scale.xScale, scale.yScale);
+ LayoutDeviceRect imageRect = LayerRect(visibleRect) / layerScale;
+
+ nsPoint maskOffset = aMaskItem->ToReferenceFrame() - bounds.TopLeft();
+
+ bool shouldHandleOpacity = aBuilder.GetInheritedOpacity() != 1.0f;
+
+ nsRect dirtyRect;
+ // If this mask item is being painted for the first time, some members of
+ // WebRenderMaskData are still default initialized. This is intentional.
+ if (aMaskItem->IsInvalid(dirtyRect) ||
+ !itemRect.IsEqualInterior(maskData->mItemRect) ||
+ !(aMaskItem->Frame()->StyleSVGReset()->mMask == maskData->mMaskStyle) ||
+ maskOffset != maskData->mMaskOffset || !sameScale ||
+ shouldHandleOpacity != maskData->mShouldHandleOpacity) {
+ IntSize size = itemRect.Size().ToUnknownSize();
+
+ if (!Factory::AllowedSurfaceSize(size)) {
+ return Nothing();
+ }
+
+ std::vector<RefPtr<ScaledFont>> fonts;
+ bool validFonts = true;
+ RefPtr<WebRenderDrawEventRecorder> recorder =
+ MakeAndAddRef<WebRenderDrawEventRecorder>(
+ [&](MemStream& aStream,
+ std::vector<RefPtr<ScaledFont>>& aScaledFonts) {
+ size_t count = aScaledFonts.size();
+ aStream.write((const char*)&count, sizeof(count));
+
+ for (auto& scaled : aScaledFonts) {
+ Maybe<wr::FontInstanceKey> key =
+ mManager->WrBridge()->GetFontKeyForScaledFont(scaled,
+ aResources);
+ if (key.isNothing()) {
+ validFonts = false;
+ break;
+ }
+ BlobFont font = {key.value(), scaled};
+ aStream.write((const char*)&font, sizeof(font));
+ }
+
+ fonts = std::move(aScaledFonts);
+ });
+
+ RefPtr<DrawTarget> dummyDt = Factory::CreateDrawTarget(
+ BackendType::SKIA, IntSize(1, 1), SurfaceFormat::A8);
+ RefPtr<DrawTarget> dt = Factory::CreateRecordingDrawTarget(
+ recorder, dummyDt, IntRect(IntPoint(0, 0), size));
+ if (!dt || !dt->IsValid()) {
+ gfxCriticalNote << "Failed to create drawTarget for blob mask image";
+ return Nothing();
+ }
+
+ gfxContext context(dt);
+ context.SetMatrix(context.CurrentMatrix()
+ .PreTranslate(-itemRect.x, -itemRect.y)
+ .PreScale(scale));
+
+ bool maskPainted = false;
+ bool maskIsComplete = aMaskItem->PaintMask(
+ aDisplayListBuilder, &context, shouldHandleOpacity, &maskPainted);
+ if (!maskPainted) {
+ return Nothing();
+ }
+
+ // If a mask is incomplete or missing (e.g. it's display: none) the proper
+ // behaviour depends on the masked frame being html or svg.
+ //
+ // For an HTML frame:
+ // According to css-masking spec, always create a mask surface when
+ // we have any item in maskFrame even if all of those items are
+ // non-resolvable <mask-sources> or <images> so continue with the
+ // painting code. Note that in a common case of no layer of the mask being
+ // complete or even partially complete then the mask surface will be
+ // transparent black so this results in hiding the frame.
+ // For an SVG frame:
+ // SVG 1.1 say that if we fail to resolve a mask, we should draw the
+ // object unmasked so return Nothing().
+ if (!maskIsComplete &&
+ aMaskItem->Frame()->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ return Nothing();
+ }
+
+ recorder->FlushItem(IntRect(0, 0, size.width, size.height));
+ recorder->Finish();
+
+ if (!validFonts) {
+ gfxCriticalNote << "Failed serializing fonts for blob mask image";
+ return Nothing();
+ }
+
+ Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData,
+ recorder->mOutputStream.mLength);
+ wr::BlobImageKey key =
+ wr::BlobImageKey{mManager->WrBridge()->GetNextImageKey()};
+ wr::ImageDescriptor descriptor(size, 0, dt->GetFormat(),
+ wr::OpacityType::HasAlphaChannel);
+ if (!aResources.AddBlobImage(key, descriptor, bytes,
+ ImageIntRect(0, 0, size.width, size.height))) {
+ return Nothing();
+ }
+ maskData->ClearImageKey();
+ maskData->mBlobKey = Some(key);
+ maskData->mFonts = fonts;
+ TakeExternalSurfaces(recorder, maskData->mExternalSurfaces,
+ mManager->GetRenderRootStateManager(), aResources);
+ if (maskIsComplete) {
+ maskData->mItemRect = itemRect;
+ maskData->mMaskOffset = maskOffset;
+ maskData->mScale = scale;
+ maskData->mMaskStyle = aMaskItem->Frame()->StyleSVGReset()->mMask;
+ maskData->mShouldHandleOpacity = shouldHandleOpacity;
+ }
+ }
+
+ aResources.SetBlobImageVisibleArea(
+ maskData->mBlobKey.value(),
+ ViewAs<ImagePixel>(visibleRect - itemRect.TopLeft(),
+ PixelCastJustification::LayerIsImage));
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ mManager->WrBridge()->MatchesNamespace(maskData->mBlobKey.ref()),
+ "Stale blob key for mask!");
+
+ wr::ImageMask imageMask;
+ imageMask.image = wr::AsImageKey(maskData->mBlobKey.value());
+ imageMask.rect = wr::ToLayoutRect(imageRect);
+ return Some(imageMask);
+}
+
+bool WebRenderCommandBuilder::PushItemAsImage(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ LayoutDeviceRect imageRect;
+ RefPtr<WebRenderFallbackData> fallbackData = GenerateFallbackData(
+ aItem, aBuilder, aResources, aSc, aDisplayListBuilder, imageRect);
+ if (!fallbackData) {
+ return false;
+ }
+
+ wr::LayoutRect dest = wr::ToLayoutRect(imageRect);
+ auto rendering = wr::ToImageRendering(aItem->Frame()->UsedImageRendering());
+ aBuilder.PushImage(dest, dest, !aItem->BackfaceIsHidden(), false, rendering,
+ fallbackData->GetImageKey().value());
+ return true;
+}
+
+void WebRenderCommandBuilder::RemoveUnusedAndResetWebRenderUserData() {
+ mWebRenderUserDatas.RemoveIf([&](WebRenderUserData* data) {
+ if (!data->IsUsed()) {
+ nsIFrame* frame = data->GetFrame();
+
+ MOZ_ASSERT(frame->HasProperty(WebRenderUserDataProperty::Key()));
+
+ WebRenderUserDataTable* userDataTable =
+ frame->GetProperty(WebRenderUserDataProperty::Key());
+
+ MOZ_ASSERT(userDataTable->Count());
+
+ userDataTable->Remove(
+ WebRenderUserDataKey(data->GetDisplayItemKey(), data->GetType()));
+
+ if (!userDataTable->Count()) {
+ frame->RemoveProperty(WebRenderUserDataProperty::Key());
+ userDataTable = nullptr;
+ }
+
+ switch (data->GetType()) {
+ case WebRenderUserData::UserDataType::eCanvas:
+ mLastCanvasDatas.Remove(data->AsCanvasData());
+ break;
+ case WebRenderUserData::UserDataType::eAnimation:
+ EffectCompositor::ClearIsRunningOnCompositor(
+ frame, GetDisplayItemTypeFromKey(data->GetDisplayItemKey()));
+ break;
+ default:
+ break;
+ }
+
+ return true;
+ }
+
+ data->SetUsed(false);
+ return false;
+ });
+}
+
+void WebRenderCommandBuilder::ClearCachedResources() {
+ RemoveUnusedAndResetWebRenderUserData();
+ // UserDatas should only be in the used state during a call to
+ // WebRenderCommandBuilder::BuildWebRenderCommands The should always be false
+ // upon return from BuildWebRenderCommands().
+ MOZ_RELEASE_ASSERT(mWebRenderUserDatas.Count() == 0);
+}
+
+WebRenderGroupData::WebRenderGroupData(
+ RenderRootStateManager* aRenderRootStateManager, nsDisplayItem* aItem)
+ : WebRenderUserData(aRenderRootStateManager, aItem) {
+ MOZ_COUNT_CTOR(WebRenderGroupData);
+}
+
+WebRenderGroupData::~WebRenderGroupData() {
+ MOZ_COUNT_DTOR(WebRenderGroupData);
+ GP("Group data destruct\n");
+ mSubGroup.ClearImageKey(mManager, true);
+ mFollowingGroup.ClearImageKey(mManager, true);
+}
+
+} // namespace layers
+} // namespace mozilla