Bug 564991. Part 23: Introduce the concept of 'inactive' layers. r=mats,sr=dbaron

This commit is contained in:
Robert O'Callahan 2010-07-16 09:08:05 +12:00
parent 06e139fe2b
commit 1b38f406bd
11 changed files with 258 additions and 61 deletions

View File

@ -811,13 +811,14 @@ void
BasicLayerManager::EndTransaction(DrawThebesLayerCallback aCallback,
void* aCallbackData)
{
NS_ASSERTION(mRoot, "Root not set");
NS_ASSERTION(InConstruction(), "Should be in construction phase");
#ifdef DEBUG
mPhase = PHASE_DRAWING;
#endif
if (mTarget) {
NS_ASSERTION(mRoot, "Root not set");
nsRefPtr<gfxContext> finalTarget = mTarget;
gfxPoint cachedSurfaceOffset;

View File

@ -41,6 +41,7 @@
#include "nsPresContext.h"
#include "nsLayoutUtils.h"
#include "Layers.h"
#include "BasicLayers.h"
#ifdef DEBUG
#include <stdio.h>
@ -208,6 +209,12 @@ protected:
* available for recycling.
*/
void CollectOldLayers();
/**
* If aItem used to belong to a ThebesLayer, invalidates the area of
* aItem in that layer. If aNewLayer is a ThebesLayer, invalidates the area of
* aItem in that layer.
*/
void InvalidateForLayerChange(nsDisplayItem* aItem, Layer* aNewLayer);
/**
* Indicate that we are done adding items to the ThebesLayer at the top of
* mThebesLayerDataStack. Set the final visible region and opaque-content
@ -825,9 +832,19 @@ ContainerState::ProcessDisplayItems(const nsDisplayList& aList,
PRInt32 appUnitsPerDevPixel = AppUnitsPerDevPixel(item);
nsIntRect itemVisibleRect =
item->GetVisibleRect().ToNearestPixels(appUnitsPerDevPixel);
nsRefPtr<Layer> ownLayer = item->BuildLayer(mBuilder, mManager);
nsDisplayItem::LayerState layerState =
item->GetLayerState(mBuilder, mManager);
// Assign the item to a layer
if (ownLayer) {
if (layerState == LAYER_ACTIVE) {
// Just use its layer.
nsRefPtr<Layer> ownLayer = item->BuildLayer(mBuilder, mManager);
if (!ownLayer) {
InvalidateForLayerChange(item, ownLayer);
continue;
}
// Update that layer's clip and visible rects.
NS_ASSERTION(ownLayer->Manager() == mManager, "Wrong manager");
NS_ASSERTION(ownLayer->GetUserData() != &gThebesDisplayItemLayerUserData,
"We shouldn't have a FrameLayerBuilder-managed layer here!");
@ -847,6 +864,9 @@ ContainerState::ProcessDisplayItems(const nsDisplayList& aList,
}
NS_ASSERTION(!mNewChildLayers.Contains(ownLayer),
"Layer already in list???");
InvalidateForLayerChange(item, ownLayer);
mNewChildLayers.AppendElement(ownLayer);
mBuilder->LayerBuilder()->AddLayerDisplayItem(ownLayer, item);
} else {
@ -864,50 +884,95 @@ ContainerState::ProcessDisplayItems(const nsDisplayList& aList,
FindThebesLayerFor(itemVisibleRect, activeScrolledRoot,
item->IsOpaque(mBuilder),
isUniform ? &uniformColor : nsnull);
NS_ASSERTION(f, "Display items that render using Thebes must have a frame");
PRUint32 key = item->GetPerFrameKey();
NS_ASSERTION(key, "Display items that render using Thebes must have a key");
Layer* oldLayer = mBuilder->LayerBuilder()->GetOldLayerFor(f, key);
if (oldLayer && thebesLayer != oldLayer) {
NS_ASSERTION(oldLayer->AsThebesLayer(),
"The layer for a display item changed type!");
// The item has changed layers.
// Invalidate the bounds in the old layer and new layer.
// The bounds might have changed, but we assume that any difference
// in the bounds will have been invalidated for all Thebes layers
// in the container via regular frame invalidation.
nsRect bounds = item->GetBounds(mBuilder);
nsIntRect r = bounds.ToOutsidePixels(appUnitsPerDevPixel);
// Update the layer contents
InvalidatePostTransformRegion(oldLayer->AsThebesLayer(), r);
InvalidatePostTransformRegion(thebesLayer, r);
// Ensure the relevant area of the window is repainted.
// Note that the area we're currently repainting will not be
// repainted again, thanks to the logic in nsFrame::InvalidateRoot.
mContainerFrame->Invalidate(bounds - mBuilder->ToReferenceFrame(mContainerFrame));
}
InvalidateForLayerChange(item, thebesLayer);
mBuilder->LayerBuilder()->
AddThebesDisplayItem(thebesLayer, item, aClipRect, mContainerFrame);
AddThebesDisplayItem(thebesLayer, mBuilder,
item, aClipRect, mContainerFrame,
layerState);
}
}
}
void
ContainerState::InvalidateForLayerChange(nsDisplayItem* aItem, Layer* aNewLayer)
{
nsIFrame* f = aItem->GetUnderlyingFrame();
NS_ASSERTION(f, "Display items that render using Thebes must have a frame");
PRUint32 key = aItem->GetPerFrameKey();
NS_ASSERTION(key, "Display items that render using Thebes must have a key");
Layer* oldLayer = mBuilder->LayerBuilder()->GetOldLayerFor(f, key);
if (!oldLayer) {
// Nothing to do here, this item didn't have a layer before
return;
}
if (aNewLayer != oldLayer) {
// The item has changed layers.
// Invalidate the bounds in the old layer and new layer.
// The bounds might have changed, but we assume that any difference
// in the bounds will have been invalidated for all Thebes layers
// in the container via regular frame invalidation.
nsRect bounds = aItem->GetBounds(mBuilder);
PRInt32 appUnitsPerDevPixel = AppUnitsPerDevPixel(aItem);
nsIntRect r = bounds.ToOutsidePixels(appUnitsPerDevPixel);
ThebesLayer* t = oldLayer->AsThebesLayer();
if (t) {
InvalidatePostTransformRegion(t, r);
}
if (aNewLayer) {
ThebesLayer* newLayer = aNewLayer->AsThebesLayer();
if (newLayer) {
InvalidatePostTransformRegion(newLayer, r);
}
}
mContainerFrame->Invalidate(bounds - mBuilder->ToReferenceFrame(mContainerFrame));
}
}
void
FrameLayerBuilder::AddThebesDisplayItem(ThebesLayer* aLayer,
nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem,
const nsRect* aClipRect,
nsIFrame* aContainerLayerFrame)
nsIFrame* aContainerLayerFrame,
LayerState aLayerState)
{
nsRefPtr<BasicLayerManager> tempManager;
if (aLayerState == LAYER_INACTIVE) {
// This item has an inactive layer. We will render it to a ThebesLayer
// using a temporary BasicLayerManager. Set up the layer
// manager now so that if we need to modify the retained layer
// tree during this process, those modifications will happen
// during the construction phase for the retained layer tree.
tempManager = new BasicLayerManager(nsnull);
tempManager->BeginTransaction();
nsRefPtr<Layer> layer = aItem->BuildLayer(aBuilder, tempManager);
if (!layer) {
tempManager->EndTransaction(nsnull, nsnull);
return;
}
PRInt32 appUnitsPerDevPixel = AppUnitsPerDevPixel(aItem);
nsIntRect itemVisibleRect =
aItem->GetVisibleRect().ToNearestPixels(appUnitsPerDevPixel);
SetVisibleRectForLayer(layer, itemVisibleRect);
tempManager->SetRoot(layer);
// No painting should occur yet, since there is no target context.
tempManager->EndTransaction(nsnull, nsnull);
}
AddLayerDisplayItem(aLayer, aItem);
ThebesLayerItemsEntry* entry = mThebesLayerItems.PutEntry(aLayer);
if (entry) {
entry->mContainerLayerFrame = aContainerLayerFrame;
NS_ASSERTION(aItem->GetUnderlyingFrame(), "Must have frame");
entry->mItems.AppendElement(ClippedDisplayItem(aItem, aClipRect));
ClippedDisplayItem* cdi =
entry->mItems.AppendElement(ClippedDisplayItem(aItem, aClipRect));
cdi->mTempLayerManager = tempManager.forget();
}
}
@ -1003,11 +1068,17 @@ FrameLayerBuilder::BuildContainerLayerFor(nsDisplayListBuilder* aBuilder,
Layer* oldLayer = GetOldLayerFor(aContainerFrame, containerDisplayItemKey);
if (oldLayer) {
NS_ASSERTION(oldLayer->Manager() == aManager, "Wrong manager");
NS_ASSERTION(oldLayer->GetType() == Layer::TYPE_CONTAINER,
"Wrong layer type");
containerLayer = static_cast<ContainerLayer*>(oldLayer);
// Clear clip rect, the caller will set it.
containerLayer->SetClipRect(nsnull);
if (oldLayer->GetUserData() == &gThebesDisplayItemLayerUserData) {
// The old layer for this item is actually our ThebesLayer
// because we rendered its layer into that ThebesLayer. So we
// don't actually have a retained container layer.
} else {
NS_ASSERTION(oldLayer->GetType() == Layer::TYPE_CONTAINER,
"Wrong layer type");
containerLayer = static_cast<ContainerLayer*>(oldLayer);
// Clear clip rect; the caller will set it if necessary.
containerLayer->SetClipRect(nsnull);
}
}
}
if (!containerLayer) {
@ -1242,17 +1313,24 @@ FrameLayerBuilder::DrawThebesLayer(ThebesLayer* aLayer,
}
}
if (presContext != lastPresContext) {
// Create a new rendering context with the right
// appunits-per-dev-pixel.
nsresult rv =
presContext->DeviceContext()->CreateRenderingContextInstance(*getter_AddRefs(rc));
if (NS_FAILED(rv))
break;
rc->Init(presContext->DeviceContext(), aContext);
lastPresContext = presContext;
if (cdi->mTempLayerManager) {
// This item has an inactive layer. Render it to the ThebesLayer
// using the temporary BasicLayerManager.
cdi->mTempLayerManager->BeginTransactionWithTarget(aContext);
cdi->mTempLayerManager->EndTransaction(DrawThebesLayer, builder);
} else {
if (presContext != lastPresContext) {
// Create a new rendering context with the right
// appunits-per-dev-pixel.
nsresult rv =
presContext->DeviceContext()->CreateRenderingContextInstance(*getter_AddRefs(rc));
if (NS_FAILED(rv))
break;
rc->Init(presContext->DeviceContext(), aContext);
lastPresContext = presContext;
}
cdi->mItem->Paint(builder, rc);
}
cdi->mItem->Paint(builder, rc);
}
if (setClipRect) {

View File

@ -57,6 +57,12 @@ class ThebesLayer;
class LayerManager;
}
enum LayerState {
LAYER_NONE,
LAYER_INACTIVE,
LAYER_ACTIVE
};
/**
* The FrameLayerBuilder belongs to an nsDisplayListBuilder and is
* responsible for converting display lists into layer trees.
@ -216,9 +222,12 @@ public:
* for the container layer this ThebesItem belongs to.
* aItem must have an underlying frame.
*/
void AddThebesDisplayItem(ThebesLayer* aLayer, nsDisplayItem* aItem,
void AddThebesDisplayItem(ThebesLayer* aLayer,
nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem,
const nsRect* aClipRect,
nsIFrame* aContainerLayerFrame);
nsIFrame* aContainerLayerFrame,
LayerState aLayerState);
/**
* Given a frame and a display item key that uniquely identifies a
@ -298,6 +307,7 @@ protected:
}
nsDisplayItem* mItem;
nsRefPtr<LayerManager> mTempLayerManager;
nsRect mClipRect;
PRPackedBool mHasClipRect;
};

View File

@ -372,7 +372,7 @@ void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder,
NS_WARNING("Nowhere to paint into");
return;
}
layerManager = new BasicLayerManager(aCtx->ThebesContext());
layerManager = new BasicLayerManager(nsnull);
if (!layerManager)
return;
}
@ -1008,6 +1008,31 @@ void nsDisplayWrapList::Paint(nsDisplayListBuilder* aBuilder,
NS_ERROR("nsDisplayWrapList should have been flattened away for painting");
}
PRBool nsDisplayWrapList::ChildrenCanBeInactive(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
const nsDisplayList& aList,
nsIFrame* aActiveScrolledRoot) {
for (nsDisplayItem* i = aList.GetBottom(); i; i = i->GetAbove()) {
nsIFrame* f = i->GetUnderlyingFrame();
if (f) {
nsIFrame* activeScrolledRoot =
nsLayoutUtils::GetActiveScrolledRootFor(f, nsnull, nsnull);
if (activeScrolledRoot != aActiveScrolledRoot)
return PR_FALSE;
}
LayerState state = i->GetLayerState(aBuilder, aManager);
if (state == LAYER_ACTIVE)
return PR_FALSE;
if (state == LAYER_NONE) {
nsDisplayList* list = i->GetList();
if (list && !ChildrenCanBeInactive(aBuilder, aManager, *list, aActiveScrolledRoot))
return PR_FALSE;
}
}
return PR_TRUE;
}
static nsresult
WrapDisplayList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayList* aList, nsDisplayWrapper* aWrapper) {
@ -1109,6 +1134,16 @@ nsDisplayOpacity::BuildLayer(nsDisplayListBuilder* aBuilder,
return layer.forget();
}
nsDisplayItem::LayerState
nsDisplayOpacity::GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager) {
// XXX fix this to detect animated opacity
nsIFrame* activeScrolledRoot =
nsLayoutUtils::GetActiveScrolledRootFor(mFrame, nsnull, nsnull);
return !ChildrenCanBeInactive(aBuilder, aManager, mList, activeScrolledRoot)
? LAYER_ACTIVE : LAYER_INACTIVE;
}
PRBool nsDisplayOpacity::ComputeVisibility(nsDisplayListBuilder* aBuilder,
nsRegion* aVisibleRegion,
nsRegion* aVisibleRegionBeforeMove) {

View File

@ -461,6 +461,7 @@ class nsDisplayItem : public nsDisplayItemLink {
public:
typedef mozilla::layers::Layer Layer;
typedef mozilla::layers::LayerManager LayerManager;
typedef mozilla::LayerState LayerState;
// This is never instantiated directly (it has pure virtual methods), so no
// need to count constructors and destructors.
@ -546,6 +547,30 @@ public:
*/
virtual PRBool IsVaryingRelativeToMovingFrame(nsDisplayListBuilder* aBuilder)
{ return PR_FALSE; }
/**
* @return LAYER_NONE if BuildLayer will return null. In this case
* there is no layer for the item, and Paint should be called instead
* to paint the content using Thebes.
* Return LAYER_INACTIVE if there is a layer --- BuildLayer will
* not return null (unless there's an error) --- but the layer contents
* are not changing frequently. In this case it makes sense to composite
* the layer into a ThebesLayer with other content, so we don't have to
* recomposite it every time we paint.
* Note: GetLayerState is only allowed to return LAYER_INACTIVE if all
* descendant display items returned LAYER_INACTIVE or LAYER_NONE. Also,
* all descendant display item frames must have an active scrolled root
* that's either the same as this item's frame's active scrolled root, or
* a descendant of this item's frame. This ensures that the entire
* set of display items can be collapsed onto a single ThebesLayer.
* Return LAYER_ACTIVE if the layer is active, that is, its contents are
* changing frequently. In this case it makes sense to keep the layer
* as a separate buffer in VRAM and composite it into the destination
* every time we paint.
*/
virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager)
{ return mozilla::LAYER_NONE; }
/**
* Actually paint this item to some rendering context.
* Content outside mVisibleRect need not be painted.
@ -553,13 +578,11 @@ public:
*/
virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx) {}
/**
* Get the layer drawn by this display item, if any. If this display
* item doesn't have its own layer, then Paint will be called on it
* later. If it returns a layer here then Paint will not be called.
* Get the layer drawn by this display item. Call this only if
* GetLayerState() returns something other than LAYER_NONE.
* If GetLayerState returned LAYER_NONE then Paint will be called
* instead.
* This is called while aManager is in the construction phase.
* This is where content can decide to be rendered by the layer
* system (with the possibility of accelerated or off-main-thread
* rendering) instead of cairo.
*
* The caller (nsDisplayList) is responsible for setting the visible
* region of the layer.
@ -1427,6 +1450,16 @@ public:
return nsnull;
}
/**
* Returns true if all descendant display items can be placed in the same
* ThebesLayer --- GetLayerState returns LAYER_INACTIVE or LAYER_NONE,
* and they all have the given aActiveScrolledRoot.
*/
static PRBool ChildrenCanBeInactive(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
const nsDisplayList& aList,
nsIFrame* aActiveScrolledRoot);
protected:
nsDisplayWrapList() {}
@ -1473,6 +1506,8 @@ public:
virtual PRBool IsOpaque(nsDisplayListBuilder* aBuilder);
virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
LayerManager* aManager);
virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager);
virtual PRBool ComputeVisibility(nsDisplayListBuilder* aBuilder,
nsRegion* aVisibleRegion,
nsRegion* aVisibleRegionBeforeMove);
@ -1493,6 +1528,11 @@ public:
virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
LayerManager* aManager);
virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager)
{
return mozilla::LAYER_ACTIVE;
}
virtual PRBool TryMerge(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem)
{
// Don't allow merging, each sublist must have its own layer

View File

@ -1594,14 +1594,10 @@ PRBool nsGfxScrollFrameInner::IsAlwaysActive() const
{
// The root scrollframe for a non-chrome document which is the direct
// child of a chrome document is always treated as "active".
if (!mIsRoot)
return PR_FALSE;
nsPresContext* presContext = mOuter->PresContext();
if (presContext->IsChrome())
return PR_FALSE;
nsIFrame* rootFrame = mOuter->PresContext()->PresShell()->GetRootFrame();
nsIFrame* rootParent = nsLayoutUtils::GetCrossDocParentFrame(rootFrame);
return !rootParent || rootParent->PresContext()->IsChrome();
// XXX maybe we should extend this so that IFRAMEs which are fill the
// entire viewport (like GMail!) are always active
return mIsRoot &&
!nsContentUtils::IsChildOfSameType(mOuter->GetContent()->GetCurrentDoc());
}
PRBool nsGfxScrollFrameInner::IsScrollingActive() const

View File

@ -94,6 +94,13 @@ public:
return static_cast<nsHTMLCanvasFrame*>(mFrame)->
BuildLayer(aBuilder, aManager, this);
}
virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager)
{
// XXX we should have some kind of activity timeout here so that
// inactive canvases can be composited into the background
return mozilla::LAYER_ACTIVE;
}
};

View File

@ -375,6 +375,14 @@ public:
{
return static_cast<nsVideoFrame*>(mFrame)->BuildLayer(aBuilder, aManager, this);
}
virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager)
{
// XXX we should have some kind of activity timeout here so that
// inactive videos can be composited into the background
return mozilla::LAYER_ACTIVE;
}
};
NS_IMETHODIMP

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<div style="opacity:0.1">HELLO</div>
</body>
</html>

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html class="reftest-wait">
<body>
<div id="d" style="opacity:0.9">HELLO</div>
<script>
function finishTest() {
var d = document.getElementById("d");
d.style.opacity = 0.1;
document.documentElement.removeAttribute("class");
}
window.addEventListener("MozReftestInvalidate", finishTest, false);
</script>
</body>
</html>

View File

@ -1443,6 +1443,7 @@ random-if(!haveTestPlugin) == 546071-1.html 546071-1-ref.html
== 562835-1.html 562835-ref.html
== 562835-2.html 562835-ref.html
== 564054-1.html 564054-1-ref.html
== 564991-1.html 564991-1-ref.html
== 565819-1.html 565819-ref.html
== 565819-2.html 565819-ref.html
== 569006-1.html 569006-1-ref.html