Bug 684759 - Part 3a - Sort preserve-3d layers using their z depth at points where they intersect in 2d space. r=roc

This commit is contained in:
Matt Woodrow 2011-10-07 10:23:18 +13:00
Родитель c45a16d1df
Коммит 224fe2d2f1
7 изменённых файлов: 354 добавлений и 29 удалений

258
gfx/layers/LayerSorter.cpp Normal file
Просмотреть файл

@ -0,0 +1,258 @@
/* -*- 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) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Matt Woodrow <mwoodrow@mozilla.com>
*
* 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 "LayerSorter.h"
#include "DirectedGraph.h"
#include "limits.h"
namespace mozilla {
namespace layers {
enum LayerSortOrder {
Undefined,
ABeforeB,
BBeforeA,
};
/**
* Recover the z component from a 2d transformed point by finding the intersection
* of a line through the point in the z direction and the transformed plane.
*
* We want to solve:
*
* point = normal . (p0 - l0) / normal . l
*/
static gfxFloat RecoverZDepth(const gfx3DMatrix& aTransform, const gfxPoint& aPoint)
{
const gfxPoint3D l(0, 0, 1);
gfxPoint3D l0 = gfxPoint3D(aPoint.x, aPoint.y, 0);
gfxPoint3D p0 = aTransform.Transform3D(gfxPoint3D(0, 0, 0));
gfxPoint3D normal = aTransform.GetNormalVector();
gfxFloat n = normal.DotProduct(p0 - l0);
gfxFloat d = normal.DotProduct(l);
if (!d) {
return 0;
}
return n/d;
}
/**
* Determine if this transform layer should be drawn before another when they
* are both preserve-3d children.
*
* We want to find the relative z depths of the 2 layers at points where they
* intersect when projected onto the 2d screen plane.
*
* If the ordering is consistent at all intersection points, then we have
* a definitive order, otherwise the 2 layers must actually intersect in 3d
* space, and we just order these arbitrarily.
*/
static LayerSortOrder CompareDepth(Layer* aOne, Layer* aTwo) {
gfxRect ourRect = aOne->GetEffectiveVisibleRegion().GetBounds();
gfxRect otherRect = aTwo->GetEffectiveVisibleRegion().GetBounds();
gfx3DMatrix ourTransform = aOne->GetTransform();
gfx3DMatrix otherTransform = aTwo->GetTransform();
// Transform both rectangles and project into 2d space.
gfxQuad ourTransformedRect = ourTransform.TransformRect(ourRect);
gfxQuad otherTransformedRect = otherTransform.TransformRect(otherRect);
// Make a list of all points that are within the other rect.
nsTArray<gfxPoint> points;
for (PRUint32 i=0; i<4; i++) {
if (ourTransformedRect.Contains(otherTransformedRect.mPoints[i])) {
points.AppendElement(otherTransformedRect.mPoints[i]);
}
if (otherTransformedRect.Contains(ourTransformedRect.mPoints[i])) {
points.AppendElement(ourTransformedRect.mPoints[i]);
}
}
// No intersections, no defined order between these layers.
if (points.IsEmpty()) {
return Undefined;
}
// Find the relative Z depths of each intersection point and check that the layers are in the same order.
bool drawBefore = false;
for (PRUint32 i = 0; i < points.Length(); i++) {
bool temp = RecoverZDepth(ourTransform, points.ElementAt(i)) <= RecoverZDepth(otherTransform, points.ElementAt(i));
if (i == 0) {
drawBefore = temp;
} else if (drawBefore != temp) {
// Mixed ordering means an intersection in 3d space that we can't resolve without plane splitting
// or depth buffering. Store this as having no defined order for now.
return Undefined;
}
}
if (drawBefore) {
return ABeforeB;
}
return BBeforeA;
}
#ifdef DEBUG
static bool gDumpLayerSortList = getenv("MOZ_DUMP_LAYER_SORT_LIST") != 0;
static void DumpLayerList(nsTArray<Layer*>& aLayers)
{
for (PRUint32 i = 0; i < aLayers.Length(); i++) {
fprintf(stderr, "%p, ", aLayers.ElementAt(i));
}
fprintf(stderr, "\n");
}
static void DumpEdgeList(DirectedGraph<Layer*>& aGraph)
{
nsTArray<DirectedGraph<Layer*>::Edge> edges = aGraph.GetEdgeList();
for (PRUint32 i = 0; i < edges.Length(); i++) {
fprintf(stderr, "From: %p, To: %p\n", edges.ElementAt(i).mFrom, edges.ElementAt(i).mTo);
}
}
#endif
// The maximum number of layers that we will attempt to sort. Anything
// greater than this will be left unsorted. We should consider enabling
// depth buffering for the scene in this case.
#define MAX_SORTABLE_LAYERS 100
void SortLayersBy3DZOrder(nsTArray<Layer*>& aLayers)
{
PRUint32 nodeCount = aLayers.Length();
if (nodeCount > MAX_SORTABLE_LAYERS) {
return;
}
DirectedGraph<Layer*> graph;
#ifdef DEBUG
if (gDumpLayerSortList) {
fprintf(stderr, " --- Layers before sorting: --- \n");
DumpLayerList(aLayers);
}
#endif
// Iterate layers and determine edges.
for (PRUint32 i = 0; i < nodeCount; i++) {
for (PRUint32 j = i + 1; j < nodeCount; j++) {
Layer* a = aLayers.ElementAt(i);
Layer* b = aLayers.ElementAt(j);
LayerSortOrder order = CompareDepth(a, b);
if (order == ABeforeB) {
graph.AddEdge(a, b);
} else if (order == BBeforeA) {
graph.AddEdge(b, a);
}
}
}
#ifdef DEBUG
if (gDumpLayerSortList) {
fprintf(stderr, " --- Edge List: --- \n");
DumpEdgeList(graph);
}
#endif
// Build a new array using the graph.
nsTArray<Layer*> noIncoming;
nsTArray<Layer*> sortedList;
// Make a list of all layers with no incoming edges.
noIncoming.AppendElements(aLayers);
const nsTArray<DirectedGraph<Layer*>::Edge>& edges = graph.GetEdgeList();
for (PRUint32 i = 0; i < edges.Length(); i++) {
noIncoming.RemoveElement(edges.ElementAt(i).mTo);
}
// Move each item without incoming edges into the sorted list,
// and remove edges from it.
while (!noIncoming.IsEmpty()) {
PRUint32 last = noIncoming.Length() - 1;
Layer* layer = noIncoming.ElementAt(last);
noIncoming.RemoveElementAt(last);
sortedList.AppendElement(layer);
nsTArray<DirectedGraph<Layer*>::Edge> outgoing;
graph.GetEdgesFrom(layer, outgoing);
for (PRUint32 i = 0; i < outgoing.Length(); i++) {
DirectedGraph<Layer*>::Edge edge = outgoing.ElementAt(i);
graph.RemoveEdge(edge);
if (!graph.NumEdgesTo(edge.mTo)) {
// If this node also has no edges now, add it to the list
noIncoming.AppendElement(edge.mTo);
}
}
// If there are no nodes without incoming edges, but there
// are still edges, then we have a cycle.
if (noIncoming.IsEmpty() && graph.GetEdgeCount()) {
// Find the node with the least incoming edges.
PRUint32 minEdges = UINT_MAX;
Layer* minNode = nsnull;
for (PRUint32 i = 0; i < aLayers.Length(); i++) {
PRUint32 edgeCount = graph.NumEdgesTo(aLayers.ElementAt(i));
if (edgeCount && edgeCount < minEdges) {
minEdges = edgeCount;
minNode = aLayers.ElementAt(i);
}
}
// Remove all of them!
graph.RemoveEdgesTo(minNode);
noIncoming.AppendElement(minNode);
}
}
NS_ASSERTION(!graph.GetEdgeCount(), "Cycles detected!");
#ifdef DEBUG
if (gDumpLayerSortList) {
fprintf(stderr, " --- Layers after sorting: --- \n");
DumpLayerList(sortedList);
}
#endif
aLayers.Clear();
aLayers.AppendElements(sortedList);
}
}
}

50
gfx/layers/LayerSorter.h Normal file
Просмотреть файл

@ -0,0 +1,50 @@
/* -*- 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) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Matt Woodrow <mwoodrow@mozilla.com>
*
* 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 ***** */
#ifndef GFX_LAYERSORTER_H
#define GFX_LAYERSORTER_H
#include "Layers.h"
namespace mozilla {
namespace layers {
void SortLayersBy3DZOrder(nsTArray<Layer*>& aLayers);
}
}
#endif /* GFX_LAYERSORTER_H */

Просмотреть файл

@ -48,6 +48,7 @@
#include "gfxUtils.h"
#include "nsPrintfCString.h"
#include "mozilla/Util.h"
#include "LayerSorter.h"
using namespace mozilla::layers;
using namespace mozilla::gfx;
@ -415,6 +416,29 @@ ContainerLayer::HasMultipleChildren()
return PR_FALSE;
}
void
ContainerLayer::SortChildrenBy3DZOrder(nsTArray<Layer*>& aArray)
{
nsAutoTArray<Layer*, 10> toSort;
for (Layer* l = GetFirstChild(); l; l = l->GetNextSibling()) {
ContainerLayer* container = l->AsContainerLayer();
if (container && container->GetContentFlags() & CONTENT_PRESERVE_3D) {
toSort.AppendElement(l);
} else {
if (toSort.Length() > 0) {
SortLayersBy3DZOrder(toSort);
aArray.MoveElementsFrom(toSort);
}
aArray.AppendElement(l);
}
}
if (toSort.Length() > 0) {
SortLayersBy3DZOrder(toSort);
aArray.MoveElementsFrom(toSort);
}
}
void
ContainerLayer::DefaultComputeEffectiveTransforms(const gfx3DMatrix& aTransformToSurface)
{

Просмотреть файл

@ -48,6 +48,7 @@
#include "gfx3DMatrix.h"
#include "gfxColor.h"
#include "gfxPattern.h"
#include "nsTArray.h"
#include "mozilla/gfx/2D.h"
@ -577,7 +578,13 @@ public:
* paint time.
* This should never be set at the same time as CONTENT_OPAQUE.
*/
CONTENT_COMPONENT_ALPHA = 0x02
CONTENT_COMPONENT_ALPHA = 0x02,
/**
* If this is set then this layer is part of a preserve-3d group, and should
* be sorted with sibling layers that are also part of the same group.
*/
CONTENT_PRESERVE_3D = 0x04
};
/**
* CONSTRUCTION PHASE ONLY
@ -1095,6 +1102,8 @@ public:
virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs);
void SortChildrenBy3DZOrder(nsTArray<Layer*>& aArray);
// These getters can be used anytime.
virtual ContainerLayer* AsContainerLayer() { return this; }

Просмотреть файл

@ -66,6 +66,7 @@ EXPORTS = \
LayerManagerOGL.h \
LayerManagerOGLProgram.h \
ReadbackLayer.h \
LayerSorter.h \
$(NULL)
CPPSRCS = \
@ -80,6 +81,7 @@ CPPSRCS = \
ImageLayerOGL.cpp \
LayerManagerOGL.cpp \
ThebesLayerOGL.cpp \
LayerSorter.cpp \
$(NULL)
ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)

Просмотреть файл

@ -762,24 +762,6 @@ static bool IsContentLEQ(nsDisplayItem* aItem1, nsDisplayItem* aItem2,
static_cast<nsIContent*>(aClosure)) <= 0;
}
static bool IsZPositionLEQ(nsDisplayItem* aItem1, nsDisplayItem* aItem2,
void* aClosure) {
if (!aItem1->GetUnderlyingFrame()->Preserves3D() ||
!aItem1->GetUnderlyingFrame()->Preserves3D()) {
return IsContentLEQ(aItem1, aItem2, aClosure);
}
nsIFrame* ancestor;
gfx3DMatrix matrix1 = aItem1->GetUnderlyingFrame()->GetTransformMatrix(&ancestor);
gfx3DMatrix matrix2 = aItem2->GetUnderlyingFrame()->GetTransformMatrix(&ancestor);
if (matrix1._43 == matrix2._43) {
return IsContentLEQ(aItem1, aItem2, aClosure);
}
return matrix1._43 < matrix2._43;
}
static bool IsZOrderLEQ(nsDisplayItem* aItem1, nsDisplayItem* aItem2,
void* aClosure) {
// These GetUnderlyingFrame calls return non-null because we're only used
@ -788,7 +770,7 @@ static bool IsZOrderLEQ(nsDisplayItem* aItem1, nsDisplayItem* aItem2,
PRInt32 index1 = nsLayoutUtils::GetZIndex(aItem1->GetUnderlyingFrame());
PRInt32 index2 = nsLayoutUtils::GetZIndex(aItem2->GetUnderlyingFrame());
if (index1 == index2)
return IsZPositionLEQ(aItem1, aItem2, aClosure);
return IsContentLEQ(aItem1, aItem2, aClosure);
return index1 < index2;
}
@ -835,11 +817,6 @@ void nsDisplayList::SortByContentOrder(nsDisplayListBuilder* aBuilder,
Sort(aBuilder, IsContentLEQ, aCommonAncestor);
}
void nsDisplayList::SortByZPosition(nsDisplayListBuilder* aBuilder,
nsIContent* aCommonAncestor) {
Sort(aBuilder, IsZPositionLEQ, aCommonAncestor);
}
void nsDisplayList::Sort(nsDisplayListBuilder* aBuilder,
SortLEQ aCmp, void* aClosure) {
ExplodeAnonymousChildLists(aBuilder);
@ -2536,9 +2513,16 @@ already_AddRefed<Layer> nsDisplayTransform::BuildLayer(nsDisplayListBuilder *aBu
return nsnull;
}
return aBuilder->LayerBuilder()->
nsRefPtr<ContainerLayer> container = aBuilder->LayerBuilder()->
BuildContainerLayerFor(aBuilder, aManager, mFrame, this, *mStoredList.GetList(),
aContainerParameters, &newTransformMatrix);
// Add the preserve-3d flag for this layer, BuildContainerLayerFor clears all flags,
// so we never need to explicitely unset this flag.
if (mFrame->Preserves3D()) {
container->SetContentFlags(container->GetContentFlags() | Layer::CONTENT_PRESERVE_3D);
}
return container.forget();
}
nsDisplayItem::LayerState
@ -2546,7 +2530,7 @@ nsDisplayTransform::GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager) {
if (mFrame->AreLayersMarkedActive(nsChangeHint_UpdateTransformLayer))
return LAYER_ACTIVE;
if (!GetTransform(mFrame->PresContext()->AppUnitsPerDevPixel()).Is2D())
if (!GetTransform(mFrame->PresContext()->AppUnitsPerDevPixel()).Is2D() || mFrame->Preserves3D())
return LAYER_ACTIVE;
nsIFrame* activeScrolledRoot =
nsLayoutUtils::GetActiveScrolledRootFor(mFrame, nsnull);

Просмотреть файл

@ -1012,8 +1012,6 @@ public:
*/
void SortByContentOrder(nsDisplayListBuilder* aBuilder, nsIContent* aCommonAncestor);
void SortByZPosition(nsDisplayListBuilder* aBuilder, nsIContent* aCommonAncestor);
/**
* Generic stable sort. Take care, because some of the items might be nsDisplayLists
* themselves.