/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Corporation code. * * The Initial Developer of the Original Code is Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Robert O'Callahan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "FrameLayerBuilder.h" #include "nsDisplayList.h" #include "nsPresContext.h" #include "nsLayoutUtils.h" using namespace mozilla::layers; namespace mozilla { namespace { /** * This class iterates through a display list tree, descending only into * nsDisplayClip items, and returns each display item encountered during * such iteration. Along with each item we also return the clip rect * accumulated for the item. */ class ClippedItemIterator { public: ClippedItemIterator(const nsDisplayList* aList) { DescendIntoList(aList, nsnull, nsnull); AdvanceToItem(); } PRBool IsDone() { return mStack.IsEmpty(); } void Next() { State* top = StackTop(); top->mItem = top->mItem->GetAbove(); AdvanceToItem(); } // Returns null if there is no clipping affecting the item. The // clip rect is in device pixels const gfxRect* GetEffectiveClipRect() { State* top = StackTop(); return top->mHasClipRect ? &top->mEffectiveClipRect : nsnull; } nsDisplayItem* Item() { return StackTop()->mItem; } private: // We maintain a stack of state objects. Each State object represents // where we're up to in the iteration of a list. struct State { // The current item we're at in the list nsDisplayItem* mItem; // The effective clip rect applying to all the items in this list gfxRect mEffectiveClipRect; PRPackedBool mHasClipRect; }; State* StackTop() { return &mStack[mStack.Length() - 1]; } void DescendIntoList(const nsDisplayList* aList, nsPresContext* aPresContext, const nsRect* aClipRect) { State* state = mStack.AppendElement(); if (!state) return; if (mStack.Length() >= 2) { *state = mStack[mStack.Length() - 2]; } else { state->mHasClipRect = PR_FALSE; } state->mItem = aList->GetBottom(); if (aClipRect) { gfxRect r(aClipRect->x, aClipRect->y, aClipRect->width, aClipRect->height); r.ScaleInverse(aPresContext->AppUnitsPerDevPixel()); if (state->mHasClipRect) { state->mEffectiveClipRect = state->mEffectiveClipRect.Intersect(r); } else { state->mEffectiveClipRect = r; state->mHasClipRect = PR_TRUE; } } } // Advances to an item that the iterator should return. void AdvanceToItem() { while (!mStack.IsEmpty()) { State* top = StackTop(); if (!top->mItem) { mStack.SetLength(mStack.Length() - 1); if (!mStack.IsEmpty()) { top = StackTop(); top->mItem = top->mItem->GetAbove(); } continue; } if (top->mItem->GetType() != nsDisplayItem::TYPE_CLIP) return; nsDisplayClip* clipItem = static_cast(top->mItem); nsRect clip = clipItem->GetClipRect(); DescendIntoList(clipItem->GetList(), clipItem->GetClippingFrame()->PresContext(), &clip); } } nsAutoTArray mStack; }; /** * This class represents a sublist of consecutive items in an nsDisplayList. * The first item in the sublist is mStartItem and the last item * is the item before mEndItem. * * These sublists are themselves organized into a linked list of all * the ItemGroups associated with a given layer, via mNextItemsForLayer. * This list will have more than one element if the display items in a layer * come from different nsDisplayLists, or if they come from the same * nsDisplayList but they aren't consecutive in that list. * * These objects are allocated from the nsDisplayListBuilder arena. */ struct ItemGroup { // If null, then the item group is empty. nsDisplayItem* mStartItem; nsDisplayItem* mEndItem; ItemGroup* mNextItemsForLayer; // The clipping (if any) that needs to be applied to all these items. gfxRect mClipRect; PRPackedBool mHasClipRect; ItemGroup() : mStartItem(nsnull), mEndItem(nsnull), mNextItemsForLayer(nsnull), mHasClipRect(PR_FALSE) {} void* operator new(size_t aSize, nsDisplayListBuilder* aBuilder) CPP_THROW_NEW { return aBuilder->Allocate(aSize); } }; /** * This class represents a layer and the display item(s) it * will render. The items are stored in a linked list of ItemGroups. */ struct LayerItems { nsRefPtr mLayer; // equal to mLayer, or null if mLayer is not a ThebesLayer ThebesLayer* mThebesLayer; ItemGroup* mItems; // The bounds of the visible region for this layer, in device pixels nsIntRect mVisibleRect; LayerItems(ItemGroup* aItems) : mThebesLayer(nsnull), mItems(aItems) { } void* operator new(size_t aSize, nsDisplayListBuilder* aBuilder) CPP_THROW_NEW { return aBuilder->Allocate(aSize); } }; /** * Given a (possibly clipped) display item in aItem, try to append it to * the items in aGroup. If aItem is the next item in the sublist in * aGroup, and the clipping matches, we can just update aGroup in-place, * otherwise we'll allocate a new ItemGroup, add it to the linked list, * and put aItem in the new ItemGroup. We return the ItemGroup into which * aItem was inserted. */ static ItemGroup* AddToItemGroup(nsDisplayListBuilder* aBuilder, ItemGroup* aGroup, nsDisplayItem* aItem, const gfxRect* aClipRect) { NS_ASSERTION(!aGroup->mNextItemsForLayer, "aGroup must be the last group in the chain"); if (!aGroup->mStartItem) { aGroup->mStartItem = aItem; aGroup->mEndItem = aItem->GetAbove(); aGroup->mHasClipRect = aClipRect != nsnull; if (aClipRect) { aGroup->mClipRect = *aClipRect; } return aGroup; } if (aGroup->mEndItem == aItem && (aGroup->mHasClipRect ? (aClipRect && aGroup->mClipRect == *aClipRect) : !aClipRect)) { aGroup->mEndItem = aItem->GetAbove(); return aGroup; } ItemGroup* itemGroup = new (aBuilder) ItemGroup(); if (!itemGroup) return aGroup; aGroup->mNextItemsForLayer = itemGroup; return AddToItemGroup(aBuilder, itemGroup, aItem, aClipRect); } /** * Create an empty Thebes layer, with an empty ItemGroup associated with * it, and append it to aLayers. */ static ItemGroup* CreateEmptyThebesLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager, nsTArray* aLayers) { ItemGroup* itemGroup = new (aBuilder) ItemGroup(); if (!itemGroup) return nsnull; nsRefPtr thebesLayer = aManager->CreateThebesLayer(); if (!thebesLayer) return nsnull; LayerItems* layerItems = new (aBuilder) LayerItems(itemGroup); aLayers->AppendElement(layerItems); thebesLayer->SetUserData(layerItems); layerItems->mThebesLayer = thebesLayer; layerItems->mLayer = thebesLayer.forget(); return itemGroup; } static PRBool IsAllUniform(nsDisplayListBuilder* aBuilder, ItemGroup* aGroup, nscolor* aColor) { nsRect visibleRect = aGroup->mStartItem->GetVisibleRect(); nscolor finalColor = NS_RGBA(0,0,0,0); for (ItemGroup* group = aGroup; group; group = group->mNextItemsForLayer) { for (nsDisplayItem* item = group->mStartItem; item != group->mEndItem; item = item->GetAbove()) { nscolor color; if (visibleRect != item->GetVisibleRect()) return PR_FALSE; if (!item->IsUniform(aBuilder, &color)) return PR_FALSE; finalColor = NS_ComposeColors(finalColor, color); } } *aColor = finalColor; return PR_TRUE; } /** * This is the heart of layout's integration with layers. We * use a ClippedItemIterator to iterate through descendant display * items. Each item either has its own layer or is assigned to a * ThebesLayer. We create ThebesLayers as necessary, although we try * to put items in the bottom-most ThebesLayer because that is most * likely to be able to render with an opaque background, which will often * be required for subpixel text antialiasing to work. */ static void BuildLayers(nsDisplayListBuilder* aBuilder, const nsDisplayList& aList, LayerManager* aManager, nsTArray* aLayers) { NS_ASSERTION(aLayers->IsEmpty(), "aLayers must be initially empty"); // Create "bottom" Thebes layer. We'll try to put as much content // as possible in this layer because if the container is filled with // opaque content, this bottommost layer can also be treated as opaque, // which means content in this layer can have subpixel AA. // firstThebesLayerItems always points to the last ItemGroup for the // first Thebes layer. ItemGroup* firstThebesLayerItems = CreateEmptyThebesLayer(aBuilder, aManager, aLayers); if (!firstThebesLayerItems) return; // lastThebesLayerItems always points to the last ItemGroup for the // topmost layer, if it's a ThebesLayer. If the top layer is not a // Thebes layer, this is null. ItemGroup* lastThebesLayerItems = firstThebesLayerItems; // This region contains the bounds of all the content that is above // the first Thebes layer. nsRegion areaAboveFirstThebesLayer; for (ClippedItemIterator iter(&aList); !iter.IsDone(); iter.Next()) { nsDisplayItem* item = iter.Item(); const gfxRect* clipRect = iter.GetEffectiveClipRect(); // Ask the item if it manages its own layer nsRefPtr layer = item->BuildLayer(aBuilder, aManager); nsRect bounds = item->GetBounds(aBuilder); // We set layerItems to point to the LayerItems object where the // item ends up. LayerItems* layerItems = nsnull; if (layer) { // item has a dedicated layer. Add it to the list, with an ItemGroup // covering this item only. ItemGroup* itemGroup = new (aBuilder) ItemGroup(); if (itemGroup) { AddToItemGroup(aBuilder, itemGroup, item, clipRect); layerItems = new (aBuilder) LayerItems(itemGroup); aLayers->AppendElement(layerItems); if (layerItems) { if (itemGroup->mHasClipRect) { gfxRect r = itemGroup->mClipRect; r.Round(); nsIntRect intRect(r.X(), r.Y(), r.Width(), r.Height()); layer->IntersectClipRect(intRect); } layerItems->mLayer = layer.forget(); } } // This item is above the first Thebes layer. areaAboveFirstThebesLayer.Or(areaAboveFirstThebesLayer, bounds); lastThebesLayerItems = nsnull; } else { // No dedicated layer. Add it to a Thebes layer. First try to add // it to the first Thebes layer, which we can do if there's no // content between the first Thebes layer and our display item that // overlaps our display item. if (!areaAboveFirstThebesLayer.Intersects(bounds)) { firstThebesLayerItems = AddToItemGroup(aBuilder, firstThebesLayerItems, item, clipRect); layerItems = aLayers->ElementAt(0); } else if (lastThebesLayerItems) { // Try to add to the last Thebes layer lastThebesLayerItems = AddToItemGroup(aBuilder, lastThebesLayerItems, item, clipRect); // This item is above the first Thebes layer. areaAboveFirstThebesLayer.Or(areaAboveFirstThebesLayer, bounds); layerItems = aLayers->ElementAt(aLayers->Length() - 1); } else { // Create a new Thebes layer ItemGroup* itemGroup = CreateEmptyThebesLayer(aBuilder, aManager, aLayers); if (itemGroup) { lastThebesLayerItems = AddToItemGroup(aBuilder, itemGroup, item, clipRect); NS_ASSERTION(lastThebesLayerItems == itemGroup, "AddToItemGroup shouldn't create a new group if the " "initial group is empty"); // This item is above the first Thebes layer. areaAboveFirstThebesLayer.Or(areaAboveFirstThebesLayer, bounds); layerItems = aLayers->ElementAt(aLayers->Length() - 1); } } } if (layerItems) { // Update the visible region of the layer to account for the new // item nscoord appUnitsPerDevPixel = item->GetUnderlyingFrame()->PresContext()->AppUnitsPerDevPixel(); layerItems->mVisibleRect.UnionRect(layerItems->mVisibleRect, item->GetVisibleRect().ToNearestPixels(appUnitsPerDevPixel)); } } if (!firstThebesLayerItems->mStartItem) { // The first Thebes layer has nothing in it. Delete the layer. // Ensure layer is released. aLayers->ElementAt(0)->mLayer = nsnull; aLayers->RemoveElementAt(0); } for (PRUint32 i = 0; i < aLayers->Length(); ++i) { LayerItems* layerItems = aLayers->ElementAt(i); nscolor color; // Only convert layers with identity transform to ColorLayers, for now. // This simplifies the code to set the clip region. if (layerItems->mThebesLayer && IsAllUniform(aBuilder, layerItems->mItems, &color) && layerItems->mLayer->GetTransform().IsIdentity()) { nsRefPtr layer = aManager->CreateColorLayer(); layer->SetClipRect(layerItems->mThebesLayer->GetClipRect()); // Clip to mVisibleRect to ensure only the pixels we want are filled. layer->IntersectClipRect(layerItems->mVisibleRect); layer->SetColor(gfxRGBA(color)); layerItems->mLayer = layer.forget(); layerItems->mThebesLayer = nsnull; } gfxMatrix transform; nsIntRect visibleRect = layerItems->mVisibleRect; if (layerItems->mLayer->GetTransform().Is2D(&transform)) { // if 'transform' is not invertible, then nothing will be displayed // for the layer, so it doesn't really matter what we do here transform.Invert(); gfxRect layerVisible = transform.TransformBounds( gfxRect(visibleRect.x, visibleRect.y, visibleRect.width, visibleRect.height)); layerVisible.RoundOut(); if (NS_FAILED(nsLayoutUtils::GfxRectToIntRect(layerVisible, &visibleRect))) { NS_ERROR("Visible rect transformed out of bounds"); } } else { NS_ERROR("Only 2D transformations currently supported"); } layerItems->mLayer->SetVisibleRegion(nsIntRegion(visibleRect)); } } } // anonymous namespace already_AddRefed FrameLayerBuilder::MakeContainerLayerFor(nsDisplayListBuilder* aBuilder, LayerManager* aManager, nsDisplayItem* aContainer, const nsDisplayList& aChildren) { // If there's only one layer, then in principle we can try to flatten // things by returning that layer here. But that adds complexity to // retained layer management so we don't do it. Layer backends can // flatten internally. nsRefPtr container = aManager->CreateContainerLayer(); if (!container) return nsnull; nsAutoTArray layerItems; BuildLayers(aBuilder, aChildren, aManager, &layerItems); Layer* lastChild = nsnull; for (PRUint32 i = 0; i < layerItems.Length(); ++i) { Layer* child = layerItems[i]->mLayer; container->InsertAfter(child, lastChild); lastChild = child; // release the layer now because the ItemGroup destructor doesn't run; // the container is still holding a reference to it layerItems[i]->mLayer = nsnull; } container->SetIsOpaqueContent(aChildren.IsOpaque()); nsRefPtr layer = container.forget(); return layer.forget(); } /* static */ void FrameLayerBuilder::DrawThebesLayer(ThebesLayer* aLayer, gfxContext* aContext, const nsIntRegion& aRegionToDraw, void* aCallbackData) { LayerItems* layerItems = static_cast(aLayer->GetUserData()); nsDisplayListBuilder* builder = static_cast(aCallbackData); // For now, we'll ignore toDraw and just draw the entire visible // area, because the "visible area" is already confined to just the // area that needs to be repainted. Later, when we start reusing layers // from paint to paint, we'll need to pay attention to toDraw and // actually try to avoid drawing stuff that's not in it. // Our list may contain content with different prescontexts at // different zoom levels. 'rc' contains the nsIRenderingContext // used for the previous display item, and lastPresContext is the // prescontext for that item. We also cache the clip state for that // item. nsRefPtr rc; nsPresContext* lastPresContext = nsnull; gfxRect currentClip; PRBool setClipRect = PR_FALSE; NS_ASSERTION(layerItems->mItems, "No empty layers allowed"); for (ItemGroup* group = layerItems->mItems; group; group = group->mNextItemsForLayer) { // If the new desired clip state is different from the current state, // update the clip. if (setClipRect != group->mHasClipRect || (group->mHasClipRect && group->mClipRect != currentClip)) { if (setClipRect) { aContext->Restore(); } setClipRect = group->mHasClipRect; if (setClipRect) { aContext->Save(); aContext->NewPath(); aContext->Rectangle(group->mClipRect, PR_TRUE); aContext->Clip(); currentClip = group->mClipRect; } } NS_ASSERTION(group->mStartItem, "No empty groups allowed"); for (nsDisplayItem* item = group->mStartItem; item != group->mEndItem; item = item->GetAbove()) { nsPresContext* presContext = item->GetUnderlyingFrame()->PresContext(); 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; } item->Paint(builder, rc); } } if (setClipRect) { aContext->Restore(); } } } // namespace mozilla