gecko/layout/base/FrameLayerBuilder.h
Chris Lord cfe2b98a1f Bug 775431 - Make MOZ_DUMP_PAINT_LIST output terminal-friendly. r=mattwoodrow
Only output in HTML if we're dumping to a file, otherwise output as it did
before the HTML output change was made.
2012-08-07 18:57:26 +01:00

674 lines
25 KiB
C++

/* -*- 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 "Layers.h"
class nsDisplayListBuilder;
class nsDisplayList;
class nsDisplayItem;
class gfxContext;
class nsRootPresContext;
namespace mozilla {
namespace layers {
class ContainerLayer;
class ThebesLayer;
}
class FrameLayerBuilder;
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 LayerManagerLayerBuilder : public layers::LayerUserData {
public:
LayerManagerLayerBuilder(FrameLayerBuilder* aBuilder, bool aDelete = true)
: mLayerBuilder(aBuilder)
, mDelete(aDelete)
{
MOZ_COUNT_CTOR(LayerManagerLayerBuilder);
}
~LayerManagerLayerBuilder();
FrameLayerBuilder* mLayerBuilder;
bool mDelete;
};
extern PRUint8 gLayerManagerLayerBuilder;
class ContainerLayerPresContext : public layers::LayerUserData {
public:
nsPresContext* mPresContext;
};
extern PRUint8 gContainerLayerPresContext;
static inline FrameLayerBuilder *GetLayerBuilderForManager(layers::LayerManager* aManager)
{
LayerManagerLayerBuilder *data = static_cast<LayerManagerLayerBuilder*>(aManager->GetUserData(&gLayerManagerLayerBuilder));
return data ? data->mLayerBuilder : nullptr;
}
class RefCountedRegion : public RefCounted<RefCountedRegion> {
public:
nsRegion mRegion;
};
/**
* The FrameLayerBuilder belongs to an nsDisplayListBuilder and is
* responsible for converting display lists into layer trees.
*
* 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
* also updates userdata for the retained layer manager, and
* DisplayItemDataProperty data for frames, to record 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 list 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:
typedef layers::ContainerLayer ContainerLayer;
typedef layers::Layer Layer;
typedef layers::ThebesLayer ThebesLayer;
typedef layers::LayerManager LayerManager;
FrameLayerBuilder() :
mRetainingManager(nullptr),
mDetectedDOMModification(false),
mInvalidateAllLayers(false),
mContainerLayerGeneration(0),
mMaxContainerLayerGeneration(0)
{
MOZ_COUNT_CTOR(FrameLayerBuilder);
mNewDisplayItemData.Init();
mThebesLayerItems.Init();
}
~FrameLayerBuilder()
{
MOZ_COUNT_DTOR(FrameLayerBuilder);
}
static void Shutdown();
void Init(nsDisplayListBuilder* aBuilder);
/**
* 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 on aManager. If aManager
* is not the retained layer manager then it must be a temporary layer
* manager that will not be used again.
*/
void WillEndTransaction(LayerManager* aManager);
/**
* Call this after we end a transaction on aManager. If aManager
* is not the retained layer manager then it must be a temporary layer
* manager that will not be used again.
*/
void DidEndTransaction(LayerManager* aManager);
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 ContainerParameters& aParent) :
mXScale(aXScale), mYScale(aYScale),
mInTransformedSubtree(aParent.mInTransformedSubtree),
mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree),
mDisableSubpixelAntialiasingInDescendants(aParent.mDisableSubpixelAntialiasingInDescendants)
{}
float mXScale, mYScale;
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<ContainerLayer>
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,
LayerManager* aManager,
nsDisplayItem* aItem);
/**
* Call this during invalidation if aFrame has
* the NS_FRAME_HAS_CONTAINER_LAYER state bit. Only the nearest
* ancestor frame of the damaged frame that has
* NS_FRAME_HAS_CONTAINER_LAYER needs to be invalidated this way.
*/
static void InvalidateThebesLayerContents(nsIFrame* aFrame,
const nsRect& aRect);
/**
* For any descendant frame of aFrame (including across documents) that
* has an associated container layer, invalidate all the contents of
* all ThebesLayer children of the container. Useful when aFrame is
* being moved and we need to invalidate everything in aFrame's subtree.
*/
static void InvalidateThebesLayersInSubtree(nsIFrame* aFrame);
/**
* As InvalidateThebesLayersInSubtree, but don't trust frame geometry
* (e.g. because appunits-per-dev-pixel changed).
*/
static void InvalidateThebesLayersInSubtreeWithUntrustedFrameGeometry(nsIFrame* aFrame);
/**
* Call this to force all retained layers to be discarded and recreated at
* the next paint.
*/
static void InvalidateAllLayers(LayerManager* aManager);
/**
* 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, PRUint32 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.
*/
void AddLayerDisplayItem(Layer* aLayer,
nsDisplayItem* aItem,
LayerState aLayerState);
/**
* 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.
*/
struct Clip;
void AddThebesDisplayItem(ThebesLayer* aLayer,
nsDisplayItem* aItem,
const Clip& aClip,
nsIFrame* aContainerLayerFrame,
LayerState aLayerState);
/**
* 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.
*/
Layer* GetOldLayerFor(nsIFrame* aFrame, PRUint32 aDisplayItemKey);
static Layer* GetDebugOldLayerFor(nsIFrame* aFrame, PRUint32 aDisplayItemKey);
/**
* Try to determine whether the ThebesLayer aLayer paints an opaque
* single color everywhere it's visible in aRect.
* If successful, return that color, otherwise return NS_RGBA(0,0,0,0).
*/
nscolor FindOpaqueColorCovering(nsDisplayListBuilder* aBuilder,
ThebesLayer* aLayer, const nsRect& aRect);
/**
* Destroy any stored LayerManagerDataProperty and the associated data for
* aFrame.
*/
static void DestroyDisplayItemDataFor(nsIFrame* aFrame)
{
aFrame->Properties().Delete(LayerManagerDataProperty());
}
LayerManager* GetRetainingLayerManager() { return mRetainingManager; }
/**
* Returns true if the given item (which we assume here is
* background-attachment:fixed) needs to be repainted as we scroll in its
* document.
* Returns false if it doesn't need to be repainted because the layer system
* is ensuring its fixed-ness for us.
*/
static bool NeedToInvalidateFixedDisplayItem(nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem);
/**
* Returns true if the given display item was rendered directly
* into a retained layer.
* Returns false if it was rendered into a temporary layer manager and then
* into a retained layer.
*/
static bool HasRetainedLayerFor(nsIFrame* aFrame, PRUint32 aDisplayItemKey);
/**
* Save transform that was in aLayer when we last painted. 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);
/**
* 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];
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<RoundedRect> 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,
PRUint32 aBegin = 0, PRUint32 aEnd = PR_UINT32_MAX);
void ApplyRectTo(gfxContext* aContext, PRInt32 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, PRInt32 A2DPRInt32,
PRUint32 aBegin, PRUint32 aEnd) const;
// Draw (fill) the rounded rects in this clip to aContext
void DrawRoundedRectsTo(gfxContext* aContext, PRInt32 A2D,
PRUint32 aBegin, PRUint32 aEnd) const;
// 'Draw' (create as a path, does not stroke or fill) aRoundRect to aContext
void AddRoundedRectPathTo(gfxContext* aContext, PRInt32 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;
// Gets rid of any rounded corners in this clip.
void RemoveRoundedCorners();
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);
}
};
protected:
/**
* We store an array of these for each frame that is associated with
* one or more retained layers. Each DisplayItemData records the layer
* used to render one of the frame's display items.
*/
class DisplayItemData {
public:
DisplayItemData(Layer* aLayer, PRUint32 aKey, LayerState aLayerState, PRUint32 aGeneration)
: mLayer(aLayer)
, mDisplayItemKey(aKey)
, mContainerLayerGeneration(aGeneration)
, mLayerState(aLayerState)
{}
nsRefPtr<Layer> mLayer;
PRUint32 mDisplayItemKey;
PRUint32 mContainerLayerGeneration;
LayerState mLayerState;
};
static void RemoveFrameFromLayerManager(nsIFrame* aFrame, void* aPropertyValue);
NS_DECLARE_FRAME_PROPERTY_WITH_FRAME_IN_DTOR(LayerManagerDataProperty,
RemoveFrameFromLayerManager)
/**
* We accumulate DisplayItemData elements in a hashtable during
* the paint process, and store them in the frame property only when
* paint is complete. This is the hashentry for that hashtable.
*/
class DisplayItemDataEntry : public nsPtrHashKey<nsIFrame> {
public:
DisplayItemDataEntry(const nsIFrame *key) : nsPtrHashKey<nsIFrame>(key), mIsSharingContainerLayer(false) {}
DisplayItemDataEntry(DisplayItemDataEntry &toCopy) :
nsPtrHashKey<nsIFrame>(toCopy.mKey), mIsSharingContainerLayer(toCopy.mIsSharingContainerLayer)
{
// This isn't actually a copy-constructor; notice that it steals toCopy's
// array and invalid region. Be careful.
mData.SwapElements(toCopy.mData);
mInvalidRegion.swap(toCopy.mInvalidRegion);
mContainerLayerGeneration = toCopy.mContainerLayerGeneration;
}
bool HasNonEmptyContainerLayer();
nsAutoTArray<DisplayItemData, 1> mData;
nsRefPtr<RefCountedRegion> mInvalidRegion;
PRUint32 mContainerLayerGeneration;
bool mIsSharingContainerLayer;
enum { ALLOW_MEMMOVE = false };
};
// LayerManagerData needs to see DisplayItemDataEntry.
friend class LayerManagerData;
// 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.
*/
static nsTArray<DisplayItemData>* GetDisplayItemDataArrayForFrame(nsIFrame *aFrame);
/**
* A useful hashtable iteration function that removes the
* DisplayItemData property for the frame, clears its
* NS_FRAME_HAS_CONTAINER_LAYER bit and returns PL_DHASH_REMOVE.
* aClosure is ignored.
*/
static PLDHashOperator RemoveDisplayItemDataForFrame(DisplayItemDataEntry* aEntry,
void* aClosure)
{
return UpdateDisplayItemDataForFrame(aEntry, nullptr);
}
/**
* 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, PRUint32 aGeneration)
: mItem(aItem), mClip(aClip), mContainerLayerGeneration(aGeneration)
{
}
nsDisplayItem* mItem;
Clip mClip;
PRUint32 mContainerLayerGeneration;
bool mInactiveLayer;
};
/**
* We accumulate ClippedDisplayItem elements in a hashtable during
* the paint process. This is the hashentry for that hashtable.
*/
public:
class ThebesLayerItemsEntry : public nsPtrHashKey<ThebesLayer> {
public:
ThebesLayerItemsEntry(const ThebesLayer *key) :
nsPtrHashKey<ThebesLayer>(key), mContainerLayerFrame(nullptr),
mHasExplicitLastPaintOffset(false), mCommonClipCount(0) {}
ThebesLayerItemsEntry(const ThebesLayerItemsEntry &toCopy) :
nsPtrHashKey<ThebesLayer>(toCopy.mKey), mItems(toCopy.mItems)
{
NS_ERROR("Should never be called, since we ALLOW_MEMMOVE");
}
nsTArray<ClippedDisplayItem> mItems;
nsIFrame* mContainerLayerFrame;
// The translation set on this ThebesLayer before we started updating the
// layer tree.
nsIntPoint mLastPaintOffset;
PRUint32 mContainerLayerGeneration;
bool mHasExplicitLastPaintOffset;
/**
* The first mCommonClipCount rounded rectangle clips are identical for
* all items in the layer. Computed in ThebesLayerData.
*/
PRUint32 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 RemoveThebesItemsForLayerSubtree(Layer* aLayer);
static void SetAndClearInvalidRegion(DisplayItemDataEntry* aEntry);
static PLDHashOperator UpdateDisplayItemDataForFrame(DisplayItemDataEntry* aEntry,
void* aUserArg);
static PLDHashOperator StoreNewDisplayItemData(DisplayItemDataEntry* aEntry,
void* aUserArg);
static PLDHashOperator RestoreDisplayItemData(DisplayItemDataEntry* 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
*/
nsRootPresContext* mRootPresContext;
/**
* A map from frames to a list of (display item key, layer) pairs that
* describes what layers various parts of the frame are assigned to.
*/
nsTHashtable<DisplayItemDataEntry> mNewDisplayItemData;
/**
* A map from ThebesLayers to the list of display items (plus
* clipping data) to be rendered in the layer.
*/
nsTHashtable<ThebesLayerItemsEntry> mThebesLayerItems;
/**
* Saved generation counter so we can detect DOM changes.
*/
PRUint32 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;
PRUint32 mContainerLayerGeneration;
PRUint32 mMaxContainerLayerGeneration;
};
}
#endif /* FRAMELAYERBUILDER_H_ */