Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/gfx/layers/wr/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 116 kB image not shown  

Quelle  WebRenderCommandBuilder.cpp   Sprache: C

 
/* -*- 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 "MediaInfo.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.
  DrawEventRecorderPrivate::ExternalSurfacesHolder 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,
    DrawEventRecorderPrivate::ExternalSurfacesHolder& aExternalSurfaces,
    RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources) {
  aRecorder->TakeExternalSurfaces(aExternalSurfaces);

  for (auto& entry : 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(entry.mSurface, 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.
    // This function and similar code in ComputeGeometryChange should be kept in
    // sync.
    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
  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) {
    mInvalidRect = mInvalidRect.Union(aRect);
  }

  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());
          }
        }
      }
    }

    if (aData->mGeometry && aItem->GetType() == DisplayItemType::TYPE_FILTER) {
      // This hunk DetectContainerLayerPropertiesBoundsChange should be kept in
      // sync.
      aData->mGeometry->mBounds =
          aData->mGeometry->mBounds.Intersect(aItem->GetBuildingRect());
    }

    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);

      if (item->GetType() == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
        continue;
      }

      BlobItemData* data = GetBlobItemData(item);
      if (data->mInvisible) {
        continue;
      }

      LayerIntRect bounds = data->mRect;

      // skip empty items
      if (bounds.IsEmpty()) {
        continue;
      }

      GP("Trying %s %p-%d %d %d %d %d\n", item->Name(), item->Frame(),
         item->GetPerFrameKey(), bounds.x, bounds.y, bounds.XMost(),
         bounds.YMost());

      auto bottomRight = bounds.BottomRight();

      GP("paint check invalid %d %d - %d %d\n", bottomRight.x.value,
         bottomRight.y.value, size.width, size.height);

      bool dirty = true;
      auto preservedBounds = bounds.Intersect(mPreservedRect);
      if (!mInvalidRect.Contains(preservedBounds)) {
        GP("Passing\n");
        dirty = false;
        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_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);
        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)) *
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=89 H=95 G=91

¤ Dauer der Verarbeitung: 0.17 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.