fune/gfx/layers/wr/WebRenderCommandBuilder.cpp
sotaro 954c16acec Bug 1805209 - Use RemoteTexture for WebGPU r=gfx-reviewers,lsalzman
WebGPU uses CompositableInProcessManager to push TextureHost directly from WebGPUParent to WebRender. But CompositableInProcessManager plumbing has a problem and caused Bug 1805209.

gecko already has a similar mechanism, called RemoteTextureMap. It is used in oop WebGL. If WebGPU uses RemoteTextureMap instead of CompositableInProcessManager, both WebGPU and oop WebGL use same mechanism.

WebGPUParent pushes a new texture to RemoteTextureMap. The RemoteTextureMap notifies the pushed texture to WebRenderImageHost.

Before the change, only one TextureHost is used for one swap chain. With the change, multiple TextureHosts are used for one swap chain with recycling.

The changes are followings.

- Use RemoteTextureMap instead of CompositableInProcessManager.
- Use RemoteTextureOwnerId instead of CompositableHandle.
- Use WebRenderCanvasData instead of WebRenderInProcessImageData.
- Add remote texture pushed callback functionality to RemoteTextureMap. With it, RemoteTextureMap notifies a new pushed remote texture to WebRenderImageHost.
- Remove CompositableInProcessManager.

Differential Revision: https://phabricator.services.mozilla.com/D164890
2022-12-23 20:41:02 +00:00

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