/* -*- 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 namespace mozilla { namespace layers { using namespace gfx; using namespace image; static int sIndent; #include #include 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(std::numeric_limits::min()); const float max = static_cast(std::numeric_limits::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* aArray); NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(BlobGroupDataProperty, nsTArray, 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* 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 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> 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* array = aFrame->GetProperty(BlobGroupDataProperty()); if (!array) { array = new nsTArray(); 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* 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* 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>& 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 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 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 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 mKey; std::vector> 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 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 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 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 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 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 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( mHitTestBounds, PixelCastJustification::LayerIsImage)); if (!hitTestRect.IsEmpty()) { auto deviceHitTestRect = (LayerRect(hitTestRect) - mResidualOffset) / scale; PushHitTest(aBuilder, deviceHitTestRect); } mVisibleRect = mVisibleRect.Intersect(ViewAs( 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(mVisibleRect, PixelCastJustification::LayerIsImage)); mLastVisibleRect = mVisibleRect; PushImage(aBuilder, itemBounds); } return; } std::vector> fonts; bool validFonts = true; RefPtr recorder = MakeAndAddRef( [&](MemStream& aStream, std::vector>& aScaledFonts) { size_t count = aScaledFonts.size(); aStream.write((const char*)&count, sizeof(count)); for (auto& scaled : aScaledFonts) { Maybe 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 dummyDt = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); RefPtr 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 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(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(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(mVisibleRect, PixelCastJustification::LayerIsImage), dirtyRect)) { return; } } mFonts = std::move(fonts); aResources.SetBlobImageVisibleArea( *mKey, ViewAs(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(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(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(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(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(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(aItem->Frame()->PresContext()->AppUnitsPerDevPixel()); float width = static_cast(bounds.width) * aSc.GetInheritedScale().xScale; float height = static_cast(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(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(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(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(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(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 groupData = aCommandBuilder->CreateOrRecycleWebRenderUserData( 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(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 groupData = CreateOrRecycleWebRenderUserData(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(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 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 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 cacheSuppressor; if (itemType == DisplayItemType::TYPE_OPACITY) { nsDisplayOpacity* opacity = static_cast(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; 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 restoreDoGrouping(mDoGrouping); if (itemType == DisplayItemType::TYPE_SVG_WRAPPER) { // Inside an , 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(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 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& aAsyncImageBounds) { RefPtr imageData = CreateOrRecycleWebRenderUserData(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 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 WebRenderCommandBuilder::CreateImageProviderKey( nsDisplayItem* aItem, image::WebRenderImageProvider* aProvider, image::ImgDrawResult aDrawResult, mozilla::wr::IpcResourceUpdateQueue& aResources) { RefPtr imageData = CreateOrRecycleWebRenderUserData(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 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& 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 fallbackData = CreateOrRecycleWebRenderUserData(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 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 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 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 fallbackData = CreateOrRecycleWebRenderUserData(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(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> fonts; bool validFonts = true; RefPtr recorder = MakeAndAddRef( [&](MemStream& aStream, std::vector>& aScaledFonts) { size_t count = aScaledFonts.size(); aStream.write((const char*)&count, sizeof(count)); for (auto& scaled : aScaledFonts) { Maybe 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 dummyDt = gfx::Factory::CreateDrawTarget( gfx::BackendType::SKIA, gfx::IntSize(1, 1), format); RefPtr 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 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(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 = imageData->GetImageClient(); RefPtr imageContainer = MakeAndAddRef(); { UpdateImageHelper helper(imageContainer, imageClient, dtRect.Size().ToUnknownSize(), format); { RefPtr 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(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 WebRenderCommandBuilder::BuildWrMaskImage( nsDisplayMasksAndClipPaths* aMaskItem, wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, nsDisplayListBuilder* aDisplayListBuilder, const LayoutDeviceRect& aBounds) { RefPtr maskData = CreateOrRecycleWebRenderUserData(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> fonts; bool validFonts = true; RefPtr recorder = MakeAndAddRef( [&](MemStream& aStream, std::vector>& aScaledFonts) { size_t count = aScaledFonts.size(); aStream.write((const char*)&count, sizeof(count)); for (auto& scaled : aScaledFonts) { Maybe 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 dummyDt = Factory::CreateDrawTarget( BackendType::SKIA, IntSize(1, 1), SurfaceFormat::A8); RefPtr 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 or 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 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(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 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