/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- * 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/. */ #ifndef FRAMELAYERBUILDER_H_ #define FRAMELAYERBUILDER_H_ #include "nsTHashtable.h" #include "nsHashKeys.h" #include "nsTArray.h" #include "nsRegion.h" #include "nsIFrame.h" #include "nsDisplayListInvalidation.h" #include "LayerTreeInvalidation.h" #include "ImageLayers.h" class nsDisplayListBuilder; class nsDisplayList; class nsDisplayItem; class gfxContext; class nsRootPresContext; namespace mozilla { namespace layers { class ContainerLayer; class LayerManager; class ThebesLayer; } class FrameLayerBuilder; class LayerManagerData; enum LayerState { LAYER_NONE, LAYER_INACTIVE, LAYER_ACTIVE, // Force an active layer even if it causes incorrect rendering, e.g. // when the layer has rounded rect clips. LAYER_ACTIVE_FORCE, // Special layer that is metadata only. LAYER_ACTIVE_EMPTY, // Inactive style layer for rendering SVG effects. LAYER_SVG_EFFECTS }; class RefCountedRegion : public RefCounted { public: RefCountedRegion() : mIsInfinite(false) {} nsRegion mRegion; bool mIsInfinite; }; /** * The FrameLayerBuilder is responsible for converting display lists * into layer trees. Every LayerManager needs a unique FrameLayerBuilder * to build layers. * * The most important API in this class is BuildContainerLayerFor. This * method takes a display list as input and constructs a ContainerLayer * with child layers that render the contents of the display list. It * records the relationship between frames and layers. * * That data enables us to retain layer trees. When constructing a * ContainerLayer, we first check to see if there's an existing * ContainerLayer for the same frame that can be recycled. If we recycle * it, we also try to reuse its existing ThebesLayer children to render * the display items without layers of their own. The idea is that by * recycling layers deterministically, we can ensure that when nothing * changes in a display list, we will reuse the existing layers without * changes. * * We expose a GetLeafLayerFor method that can be called by display items * that make their own layers (e.g. canvas and video); this method * locates the last layer used to render the display item, if any, and * return it as a candidate for recycling. * * FrameLayerBuilder sets up ThebesLayers so that 0,0 in the Thebes layer * corresponds to the (pixel-snapped) top-left of the aActiveScrolledRoot. * It sets up ContainerLayers so that 0,0 in the container layer * corresponds to the snapped top-left of the display item reference frame. * * When we construct a container layer, we know the transform that will be * applied to the layer. If the transform scales the content, we can get * better results when intermediate buffers are used by pushing some scale * from the container's transform down to the children. For ThebesLayer * children, the scaling can be achieved by changing the size of the layer * and drawing into it with increased or decreased resolution. By convention, * integer types (nsIntPoint/nsIntSize/nsIntRect/nsIntRegion) are all in layer * coordinates, post-scaling, whereas appunit types are all pre-scaling. */ class FrameLayerBuilder : public layers::LayerUserData { public: typedef layers::ContainerLayer ContainerLayer; typedef layers::Layer Layer; typedef layers::ThebesLayer ThebesLayer; typedef layers::ImageLayer ImageLayer; typedef layers::LayerManager LayerManager; FrameLayerBuilder() : mRetainingManager(nullptr), mDetectedDOMModification(false), mInvalidateAllLayers(false), mContainerLayerGeneration(0), mMaxContainerLayerGeneration(0) { MOZ_COUNT_CTOR(FrameLayerBuilder); mThebesLayerItems.Init(); } ~FrameLayerBuilder() { MOZ_COUNT_DTOR(FrameLayerBuilder); } static void Shutdown(); void Init(nsDisplayListBuilder* aBuilder, LayerManager* aManager); /** * Call this to notify that we have just started a transaction on the * retained layer manager aManager. */ void DidBeginRetainedLayerTransaction(LayerManager* aManager); /** * Call this just before we end a transaction. */ void WillEndTransaction(); /** * Call this after we end a transaction. */ void DidEndTransaction(); struct ContainerParameters { ContainerParameters() : mXScale(1), mYScale(1), mInTransformedSubtree(false), mInActiveTransformedSubtree(false), mDisableSubpixelAntialiasingInDescendants(false) {} ContainerParameters(float aXScale, float aYScale) : mXScale(aXScale), mYScale(aYScale), mInTransformedSubtree(false), mInActiveTransformedSubtree(false), mDisableSubpixelAntialiasingInDescendants(false) {} ContainerParameters(float aXScale, float aYScale, const nsIntPoint& aOffset, const ContainerParameters& aParent) : mXScale(aXScale), mYScale(aYScale), mOffset(aOffset), mInTransformedSubtree(aParent.mInTransformedSubtree), mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree), mDisableSubpixelAntialiasingInDescendants(aParent.mDisableSubpixelAntialiasingInDescendants) {} float mXScale, mYScale; /** * An offset to append to the transform set on all child layers created. */ nsIntPoint mOffset; bool mInTransformedSubtree; bool mInActiveTransformedSubtree; bool mDisableSubpixelAntialiasingInDescendants; /** * When this is false, ThebesLayer coordinates are drawn to with an integer * translation and the scale in mXScale/mYScale. */ bool AllowResidualTranslation() { // If we're in a transformed subtree, but no ancestor transform is actively // changing, we'll use the residual translation when drawing into the // ThebesLayer to ensure that snapping exactly matches the ideal transform. return mInTransformedSubtree && !mInActiveTransformedSubtree; } }; /** * Build a container layer for a display item that contains a child * list, either reusing an existing one or creating a new one. It * sets the container layer children to layers which together render * the contents of the display list. It reuses existing layers from * the retained layer manager if possible. * aContainer may be null, in which case we construct a root layer. * This gets called by display list code. It calls BuildLayer on the * items in the display list, making items with their own layers * children of the new container, and assigning all other items to * ThebesLayer children created and managed by the FrameLayerBuilder. * Returns a layer with clip rect cleared; it is the * caller's responsibility to add any clip rect. The visible region * is set based on what's in the layer. * The container layer is transformed by aTransform (if non-null), and * the result is transformed by the scale factors in aContainerParameters. */ already_AddRefed BuildContainerLayerFor(nsDisplayListBuilder* aBuilder, LayerManager* aManager, nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem, const nsDisplayList& aChildren, const ContainerParameters& aContainerParameters, const gfx3DMatrix* aTransform); /** * Get a retained layer for a display item that needs to create its own * layer for rendering (i.e. under nsDisplayItem::BuildLayer). Returns * null if no retained layer is available, which usually means that this * display item didn't have a layer before so the caller will * need to create one. * Returns a layer with clip rect cleared; it is the * caller's responsibility to add any clip rect and set the visible * region. */ Layer* GetLeafLayerFor(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem); /** * Call this to force all retained layers to be discarded and recreated at * the next paint. */ static void InvalidateAllLayers(LayerManager* aManager); static void InvalidateAllLayersForFrame(nsIFrame *aFrame); /** * Call this to determine if a frame has a dedicated (non-Thebes) layer * for the given display item key. If there isn't one, we return null, * otherwise we return the layer. */ static Layer* GetDedicatedLayer(nsIFrame* aFrame, uint32_t aDisplayItemKey); /** * This callback must be provided to EndTransaction. The callback data * must be the nsDisplayListBuilder containing this FrameLayerBuilder. * This function can be called multiple times in a row to draw * different regions. */ static void DrawThebesLayer(ThebesLayer* aLayer, gfxContext* aContext, const nsIntRegion& aRegionToDraw, const nsIntRegion& aRegionToInvalidate, void* aCallbackData); #ifdef MOZ_DUMP_PAINTING /** * Dumps this FrameLayerBuilder's retained layer manager's retained * layer tree. Defaults to dumping to stdout in non-HTML format. */ static void DumpRetainedLayerTree(LayerManager* aManager, FILE* aFile = stdout, bool aDumpHtml = false); #endif /******* PRIVATE METHODS to FrameLayerBuilder.cpp ********/ /* These are only in the public section because they need * to be called by file-scope helper functions in FrameLayerBuilder.cpp. */ /** * Record aItem as a display item that is rendered by aLayer. * * @param aLayer Layer that the display item will be rendered into * @param aItem Display item to be drawn. * @param aLayerState What LayerState the item is using. * @param aTopLeft offset from active scrolled root to reference frame * @param aManager If the layer is in the LAYER_INACTIVE state, * then this is the temporary layer manager to draw with. */ struct Clip; void AddLayerDisplayItem(Layer* aLayer, nsDisplayItem* aItem, const Clip& aClip, LayerState aLayerState, const nsPoint& aTopLeft, LayerManager* aManager, nsAutoPtr aGeometry); /** * Record aItem as a display item that is rendered by the ThebesLayer * aLayer, with aClipRect, where aContainerLayerFrame is the frame * for the container layer this ThebesItem belongs to. * aItem must have an underlying frame. * @param aTopLeft offset from active scrolled root to reference frame */ void AddThebesDisplayItem(ThebesLayer* aLayer, nsDisplayItem* aItem, const Clip& aClip, nsIFrame* aContainerLayerFrame, LayerState aLayerState, const nsPoint& aTopLeft, nsAutoPtr aGeometry); /** * Gets the frame property descriptor for the given manager, or for the current * widget layer manager if nullptr is passed. */ static const FramePropertyDescriptor* GetDescriptorForManager(LayerManager* aManager); /** * Calls GetOldLayerForFrame on the underlying frame of the display item, * and each subsequent merged frame if no layer is found for the underlying * frame. */ Layer* GetOldLayerFor(nsDisplayItem* aItem, nsDisplayItemGeometry** aOldGeometry = nullptr, Clip** aOldClip = nullptr, nsTArray* aChangedFrames = nullptr, bool *aIsInvalid = nullptr); static Layer* GetDebugOldLayerFor(nsIFrame* aFrame, uint32_t aDisplayItemKey); /** * Destroy any stored LayerManagerDataProperty and the associated data for * aFrame. */ static void DestroyDisplayItemDataFor(nsIFrame* aFrame); LayerManager* GetRetainingLayerManager() { return mRetainingManager; } /** * Returns true if the given display item was rendered during the previous * paint. Returns false otherwise. */ static bool HasRetainedDataFor(nsIFrame* aFrame, uint32_t aDisplayItemKey); class DisplayItemData; typedef void (*DisplayItemDataCallback)(nsIFrame *aFrame, DisplayItemData* aItem); static void IterateRetainedDataFor(nsIFrame* aFrame, DisplayItemDataCallback aCallback); /** * Save transform that was in aLayer when we last painted, and the position * of the active scrolled root frame. It must be an integer * translation. */ void SaveLastPaintOffset(ThebesLayer* aLayer); /** * Get the translation transform that was in aLayer when we last painted. It's either * the transform saved by SaveLastPaintTransform, or else the transform * that's currently in the layer (which must be an integer translation). */ nsIntPoint GetLastPaintOffset(ThebesLayer* aLayer); /** * Return the resolution at which we expect to render aFrame's contents, * assuming they are being painted to retained layers. This takes into account * the resolution the contents of the ContainerLayer containing aFrame are * being rendered at, as well as any currently-inactive transforms between * aFrame and that container layer. */ static gfxSize GetThebesLayerScaleForFrame(nsIFrame* aFrame); /** * Stores a Layer as the dedicated layer in the DisplayItemData for a given frame/key pair. * * Used when we optimize a ThebesLayer into an ImageLayer and want to retroactively update the * DisplayItemData so we can retrieve the layer from within layout. */ void StoreOptimizedLayerForFrame(nsDisplayItem* aItem, Layer* aLayer); /** * Clip represents the intersection of an optional rectangle with a * list of rounded rectangles. */ struct Clip { struct RoundedRect { nsRect mRect; // Indices into mRadii are the NS_CORNER_* constants in nsStyleConsts.h nscoord mRadii[8]; RoundedRect operator+(const nsPoint& aOffset) const { RoundedRect r = *this; r.mRect += aOffset; return r; } bool operator==(const RoundedRect& aOther) const { if (!mRect.IsEqualInterior(aOther.mRect)) { return false; } NS_FOR_CSS_HALF_CORNERS(corner) { if (mRadii[corner] != aOther.mRadii[corner]) { return false; } } return true; } bool operator!=(const RoundedRect& aOther) const { return !(*this == aOther); } }; nsRect mClipRect; nsTArray mRoundedClipRects; bool mHaveClipRect; Clip() : mHaveClipRect(false) {} // Construct as the intersection of aOther and aClipItem. Clip(const Clip& aOther, nsDisplayItem* aClipItem); // Apply this |Clip| to the given gfxContext. Any saving of state // or clearing of other clips must be done by the caller. // See aBegin/aEnd note on ApplyRoundedRectsTo. void ApplyTo(gfxContext* aContext, nsPresContext* aPresContext, uint32_t aBegin = 0, uint32_t aEnd = UINT32_MAX); void ApplyRectTo(gfxContext* aContext, int32_t A2D) const; // Applies the rounded rects in this Clip to aContext // Will only apply rounded rects from aBegin (inclusive) to aEnd // (exclusive) or the number of rounded rects, whichever is smaller. void ApplyRoundedRectsTo(gfxContext* aContext, int32_t A2DPRInt32, uint32_t aBegin, uint32_t aEnd) const; // Draw (fill) the rounded rects in this clip to aContext void DrawRoundedRectsTo(gfxContext* aContext, int32_t A2D, uint32_t aBegin, uint32_t aEnd) const; // 'Draw' (create as a path, does not stroke or fill) aRoundRect to aContext void AddRoundedRectPathTo(gfxContext* aContext, int32_t A2D, const RoundedRect &aRoundRect) const; // Return a rectangle contained in the intersection of aRect with this // clip region. Tries to return the largest possible rectangle, but may // not succeed. nsRect ApproximateIntersect(const nsRect& aRect) const; // Returns false if aRect is definitely not clipped by a rounded corner in // this clip. Returns true if aRect is clipped by a rounded corner in this // clip or it can not be quickly determined that it is not clipped by a // rounded corner in this clip. bool IsRectClippedByRoundedCorner(const nsRect& aRect) const; // Intersection of all rects in this clip ignoring any rounded corners. nsRect NonRoundedIntersection() const; // Intersect the given rects with all rects in this clip, ignoring any // rounded corners. nsRect ApplyNonRoundedIntersection(const nsRect& aRect) const; // Gets rid of any rounded corners in this clip. void RemoveRoundedCorners(); // Adds the difference between Intersect(*this + aPoint, aBounds) and // Intersect(aOther, aOtherBounds) to aDifference. void AddOffsetAndComputeDifference(const nsPoint& aPoint, const nsRect& aBounds, const Clip& aOther, const nsRect& aOtherBounds, nsRegion* aDifference); bool operator==(const Clip& aOther) const { return mHaveClipRect == aOther.mHaveClipRect && (!mHaveClipRect || mClipRect.IsEqualInterior(aOther.mClipRect)) && mRoundedClipRects == aOther.mRoundedClipRects; } bool operator!=(const Clip& aOther) const { return !(*this == aOther); } }; NS_DECLARE_FRAME_PROPERTY_WITH_FRAME_IN_DTOR(LayerManagerDataProperty, RemoveFrameFromLayerManager) /** * Retained data storage: * * Each layer manager (widget, and inactive) stores a LayerManagerData object * that keeps a hash-set of DisplayItemData items that were drawn into it. * Each frame also keeps a list of DisplayItemData pointers that were * created for that frame. DisplayItemData objects manage these lists automatically. * * During layer construction we update the data in the LayerManagerData object, marking * items that are modified. At the end we sweep the LayerManagerData hash-set and remove * all items that haven't been modified. */ /** * Retained data for a display item. */ class DisplayItemData { public: friend class FrameLayerBuilder; uint32_t GetDisplayItemKey() { return mDisplayItemKey; } Layer* GetLayer() { return mLayer; } void Invalidate() { mIsInvalid = true; } protected: DisplayItemData(LayerManagerData* aParent, uint32_t aKey, Layer* aLayer, LayerState aLayerState, uint32_t aGeneration); DisplayItemData(DisplayItemData &toCopy); /** * Removes any references to this object from frames * in mFrameList. */ ~DisplayItemData(); NS_INLINE_DECL_REFCOUNTING(DisplayItemData) /** * Associates this DisplayItemData with a frame, and adds it * to the LayerManagerDataProperty list on the frame. */ void AddFrame(nsIFrame* aFrame); void RemoveFrame(nsIFrame* aFrame); void GetFrameListChanges(nsDisplayItem* aOther, nsTArray& aOut); /** * Updates the contents of this item to a new set of data, instead of allocating a new * object. * Set the passed in parameters, and clears the opt layer, inactive manager, geometry * and clip. * Parent, frame list and display item key are assumed to be the same. */ void UpdateContents(Layer* aLayer, LayerState aState, uint32_t aContainerLayerGeneration, nsDisplayItem* aItem = nullptr); LayerManagerData* mParent; nsRefPtr mLayer; nsRefPtr mOptLayer; nsRefPtr mInactiveManager; nsAutoTArray mFrameList; nsAutoPtr mGeometry; Clip mClip; uint32_t mDisplayItemKey; uint32_t mContainerLayerGeneration; LayerState mLayerState; /** * Used to track if data currently stored in mFramesWithLayers (from an existing * paint) has been updated in the current paint. */ bool mUsed; bool mIsInvalid; }; protected: friend class LayerManagerData; static void RemoveFrameFromLayerManager(nsIFrame* aFrame, void* aPropertyValue); /** * Given a frame and a display item key that uniquely identifies a * display item for the frame, find the layer that was last used to * render that display item. Returns null if there is no such layer. * This could be a dedicated layer for the display item, or a ThebesLayer * that renders many display items. */ DisplayItemData* GetOldLayerForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey); /** * Stores DisplayItemData associated with aFrame, stores the data in * mNewDisplayItemData. */ DisplayItemData* StoreDataForFrame(nsDisplayItem* aItem, Layer* aLayer, LayerState aState); void StoreDataForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey, Layer* aLayer, LayerState aState); // Flash the area within the context clip if paint flashing is enabled. static void FlashPaint(gfxContext *aContext); /* * Get the DisplayItemData array associated with this frame, or null if one * doesn't exist. * * Note that the pointer returned here is only valid so long as you don't * poke the LayerManagerData's mFramesWithLayers hashtable. */ DisplayItemData* GetDisplayItemData(nsIFrame *aFrame, uint32_t aKey); /* * Get the DisplayItemData associated with this frame / display item pair, * using the LayerManager instead of FrameLayerBuilder. */ static DisplayItemData* GetDisplayItemDataForManager(nsIFrame* aFrame, uint32_t aDisplayItemKey, LayerManager* aManager); static DisplayItemData* GetDisplayItemDataForManager(nsIFrame* aFrame, uint32_t aDisplayItemKey); static DisplayItemData* GetDisplayItemDataForManager(nsDisplayItem* aItem, LayerManager* aManager); static DisplayItemData* GetDisplayItemDataForManager(nsIFrame* aFrame, uint32_t aDisplayItemKey, LayerManagerData* aData); static PLDHashOperator DumpDisplayItemDataForFrame(nsRefPtrHashKey* aEntry, void* aClosure); /** * We store one of these for each display item associated with a * ThebesLayer, in a hashtable that maps each ThebesLayer to an array * of ClippedDisplayItems. (ThebesLayerItemsEntry is the hash entry * for that hashtable.) * These are only stored during the paint process, so that the * DrawThebesLayer callback can figure out which items to draw for the * ThebesLayer. * mItem always has an underlying frame. */ struct ClippedDisplayItem { ClippedDisplayItem(nsDisplayItem* aItem, const Clip& aClip, uint32_t aGeneration) : mItem(aItem), mClip(aClip), mContainerLayerGeneration(aGeneration) { } ~ClippedDisplayItem(); nsDisplayItem* mItem; /** * If the display item is being rendered as an inactive * layer, then this stores the layer manager being * used for the inactive transaction. */ nsRefPtr mInactiveLayerManager; Clip mClip; uint32_t mContainerLayerGeneration; }; /** * We accumulate ClippedDisplayItem elements in a hashtable during * the paint process. This is the hashentry for that hashtable. */ public: class ThebesLayerItemsEntry : public nsPtrHashKey { public: ThebesLayerItemsEntry(const ThebesLayer *key) : nsPtrHashKey(key), mContainerLayerFrame(nullptr), mContainerLayerGeneration(0), mHasExplicitLastPaintOffset(false), mCommonClipCount(0) {} ThebesLayerItemsEntry(const ThebesLayerItemsEntry &toCopy) : nsPtrHashKey(toCopy.mKey), mItems(toCopy.mItems) { NS_ERROR("Should never be called, since we ALLOW_MEMMOVE"); } nsTArray mItems; nsIFrame* mContainerLayerFrame; // The translation set on this ThebesLayer before we started updating the // layer tree. nsIntPoint mLastPaintOffset; uint32_t mContainerLayerGeneration; bool mHasExplicitLastPaintOffset; /** * The first mCommonClipCount rounded rectangle clips are identical for * all items in the layer. Computed in ThebesLayerData. */ uint32_t mCommonClipCount; enum { ALLOW_MEMMOVE = true }; }; /** * Get the ThebesLayerItemsEntry object associated with aLayer in this * FrameLayerBuilder */ ThebesLayerItemsEntry* GetThebesLayerItemsEntry(ThebesLayer* aLayer) { return mThebesLayerItems.GetEntry(aLayer); } protected: void RemoveThebesItemsAndOwnerDataForLayerSubtree(Layer* aLayer, bool aRemoveThebesItems, bool aRemoveOwnerData); static PLDHashOperator ProcessRemovedDisplayItems(nsRefPtrHashKey* aEntry, void* aUserArg); static PLDHashOperator RestoreDisplayItemData(nsRefPtrHashKey* aEntry, void *aUserArg); static PLDHashOperator RestoreThebesLayerItemEntries(ThebesLayerItemsEntry* aEntry, void *aUserArg); /** * Returns true if the DOM has been modified since we started painting, * in which case we should bail out and not paint anymore. This should * never happen, but plugins can trigger it in some cases. */ bool CheckDOMModified(); /** * The layer manager belonging to the widget that is being retained * across paints. */ LayerManager* mRetainingManager; /** * The root prescontext for the display list builder reference frame */ nsRefPtr mRootPresContext; /** * The display list builder being used. */ nsDisplayListBuilder* mDisplayListBuilder; /** * A map from ThebesLayers to the list of display items (plus * clipping data) to be rendered in the layer. */ nsTHashtable mThebesLayerItems; /** * Saved generation counter so we can detect DOM changes. */ uint32_t mInitialDOMGeneration; /** * Set to true if we have detected and reported DOM modification during * the current paint. */ bool mDetectedDOMModification; /** * Indicates that the entire layer tree should be rerendered * during this paint. */ bool mInvalidateAllLayers; uint32_t mContainerLayerGeneration; uint32_t mMaxContainerLayerGeneration; }; } #endif /* FRAMELAYERBUILDER_H_ */