Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "Layers.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/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, mVisibleRect.TopLeft().y,
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());
// Setup the gfxContext
RefPtr<gfxContext> context = gfxContext::CreateOrNull(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, bottomRight.y,
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, bottomRight.y,
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 {
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;
}
// 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_images() && aUniformlyScaled &&
svgItem->ShouldBeActive(aBuilder, aResources, aSc, aManager,
aDisplayListBuilder)) {
bool snap = false;
auto bounds = aItem->GetBounds(aDisplayListBuilder, &snap);
// 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 dislplay items it will become less of a
// concern.
int32_t largeish = 512;
if (aHasActivePrecedingSibling || bounds.width > largeish ||
bounds.height > largeish) {
return ItemActivity::Should;
}
return ItemActivity::Could;
}
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 */