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