Bug 675837 - Add 3D transforms support to BasicLayers. r=roc

This commit is contained in:
Matt Woodrow 2011-08-27 12:04:06 +12:00
Родитель 6c63c596b7
Коммит 7e10034edd
4 изменённых файлов: 238 добавлений и 16 удалений

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

@ -63,6 +63,7 @@
#endif
#include "GLContext.h"
#include "pixman.h"
namespace mozilla {
namespace layers {
@ -237,8 +238,17 @@ public:
// We push groups for container layers if we need to, which always
// are aligned in device space, so it doesn't really matter how we snap
// containers.
gfxMatrix residual;
gfx3DMatrix idealTransform = GetLocalTransform()*aTransformToSurface;
mEffectiveTransform = SnapTransform(idealTransform, gfxRect(0, 0, 0, 0), nsnull);
if (!idealTransform.CanDraw2D()) {
mEffectiveTransform = idealTransform;
ComputeEffectiveTransformsForChildren(gfx3DMatrix());
mUseIntermediateSurface = PR_TRUE;
return;
}
mEffectiveTransform = SnapTransform(idealTransform, gfxRect(0, 0, 0, 0), &residual);
// We always pass the ideal matrix down to our children, so there is no
// need to apply any compensation using the residual from SnapTransform.
ComputeEffectiveTransformsForChildren(idealTransform);
@ -288,7 +298,7 @@ PRBool
BasicContainerLayer::ChildrenPartitionVisibleRegion(const nsIntRect& aInRect)
{
gfxMatrix transform;
if (!GetEffectiveTransform().Is2D(&transform) ||
if (!GetEffectiveTransform().CanDraw2D(&transform) ||
transform.HasNonIntegerTranslation())
return PR_FALSE;
@ -301,7 +311,7 @@ BasicContainerLayer::ChildrenPartitionVisibleRegion(const nsIntRect& aInRect)
continue;
gfxMatrix childTransform;
if (!l->GetEffectiveTransform().Is2D(&childTransform) ||
if (!l->GetEffectiveTransform().CanDraw2D(&childTransform) ||
childTransform.HasNonIntegerTranslation() ||
l->GetEffectiveOpacity() != 1.0)
return PR_FALSE;
@ -704,7 +714,7 @@ BasicThebesLayer::PaintThebes(gfxContext* aContext,
{
PRUint32 flags = 0;
gfxMatrix transform;
if (!GetEffectiveTransform().Is2D(&transform) ||
if (!GetEffectiveTransform().CanDraw2D(&transform) ||
transform.HasNonIntegerTranslation() ||
MustRetainContent() /*<=> has shadow layer*/) {
flags |= ThebesLayerBuffer::PAINT_WILL_RESAMPLE;
@ -1355,6 +1365,18 @@ BasicLayerManager::PopGroupToSourceWithCachedSurface(gfxContext *aTarget, gfxCon
}
}
already_AddRefed<gfxASurface>
BasicLayerManager::PopGroupToSurface(gfxContext *aTarget, gfxContext *aPushed)
{
if (!aTarget)
return nsnull;
nsRefPtr<gfxASurface> current = aPushed->CurrentSurface();
NS_ASSERTION(!mCachedSurface.IsSurface(current), "Should never be popping cached surface here!");
nsRefPtr<gfxPattern> pat = aTarget->PopGroup();
current = pat->GetSurface();
return current.forget();
}
void
BasicLayerManager::BeginTransactionWithTarget(gfxContext* aTarget)
{
@ -1420,7 +1442,7 @@ MarkLayersHidden(Layer* aLayer, const nsIntRect& aClipRect,
// global coordinate system.
if (aLayer->GetParent()) {
gfxMatrix tr;
if (aLayer->GetParent()->GetEffectiveTransform().Is2D(&tr)) {
if (aLayer->GetParent()->GetEffectiveTransform().CanDraw2D(&tr)) {
// Clip rect is applied after aLayer's transform, i.e., in the coordinate
// system of aLayer's parent.
TransformIntRect(cr, tr, ToInsideIntRect);
@ -1438,7 +1460,7 @@ MarkLayersHidden(Layer* aLayer, const nsIntRect& aClipRect,
if (!aLayer->AsContainerLayer()) {
gfxMatrix transform;
if (!aLayer->GetEffectiveTransform().Is2D(&transform)) {
if (!aLayer->GetEffectiveTransform().CanDraw2D(&transform)) {
data->SetHidden(PR_FALSE);
return;
}
@ -1499,7 +1521,7 @@ ApplyDoubleBuffering(Layer* aLayer, const nsIntRect& aVisibleRect)
// global coordinate system.
if (aLayer->GetParent()) {
gfxMatrix tr;
if (aLayer->GetParent()->GetEffectiveTransform().Is2D(&tr)) {
if (aLayer->GetParent()->GetEffectiveTransform().CanDraw2D(&tr)) {
NS_ASSERTION(!tr.HasNonIntegerTranslation(),
"Parent can only have an integer translation");
cr += nsIntPoint(PRInt32(tr.x0), PRInt32(tr.y0));
@ -1640,6 +1662,143 @@ BasicLayerManager::SetRoot(Layer* aLayer)
mRoot = aLayer;
}
static pixman_transform
Matrix3DToPixman(const gfx3DMatrix& aMatrix)
{
pixman_f_transform transform;
transform.m[0][0] = aMatrix._11;
transform.m[0][1] = aMatrix._21;
transform.m[0][2] = aMatrix._41;
transform.m[1][0] = aMatrix._12;
transform.m[1][1] = aMatrix._22;
transform.m[1][2] = aMatrix._42;
transform.m[2][0] = aMatrix._14;
transform.m[2][1] = aMatrix._24;
transform.m[2][2] = aMatrix._44;
pixman_transform result;
pixman_transform_from_pixman_f_transform(&result, &transform);
return result;
}
static void
PixmanTransform(const gfxImageSurface *aDest,
const gfxImageSurface *aSrc,
const gfx3DMatrix& aTransform,
gfxPoint aDestOffset)
{
gfxIntSize destSize = aDest->GetSize();
pixman_image_t* dest = pixman_image_create_bits(aDest->Format() == gfxASurface::ImageFormatARGB32 ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
destSize.width,
destSize.height,
(uint32_t*)aDest->Data(),
aDest->Stride());
gfxIntSize srcSize = aSrc->GetSize();
pixman_image_t* src = pixman_image_create_bits(aSrc->Format() == gfxASurface::ImageFormatARGB32 ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
srcSize.width,
srcSize.height,
(uint32_t*)aSrc->Data(),
aSrc->Stride());
NS_ABORT_IF_FALSE(src && dest, "Failed to create pixman images?");
pixman_transform pixTransform = Matrix3DToPixman(aTransform);
pixman_transform pixTransformInverted;
// If the transform is singular then nothing would be drawn anyway, return here
if (!pixman_transform_invert(&pixTransformInverted, &pixTransform)) {
return;
}
pixman_image_set_transform(src, &pixTransformInverted);
pixman_image_composite32(PIXMAN_OP_SRC,
src,
nsnull,
dest,
aDestOffset.x,
aDestOffset.y,
0,
0,
0,
0,
destSize.width,
destSize.height);
pixman_image_unref(dest);
pixman_image_unref(src);
}
/**
* Transform a surface using a gfx3DMatrix and blit to the destination if
* it is efficient to do so.
*
* @param aSource Source surface.
* @param aDest Desintation context.
* @param aBounds Area represented by aSource.
* @param aTransform Transformation matrix.
* @param aDrawOffset Location to draw returned surface on aDest.
* @param aDontBlit Never draw to aDest if this is true.
* @return Transformed surface, or nsnull if it has been drawn to aDest.
*/
static already_AddRefed<gfxASurface>
Transform3D(gfxASurface* aSource, gfxContext* aDest,
const gfxRect& aBounds, const gfx3DMatrix& aTransform,
gfxPoint& aDrawOffset, PRBool aDontBlit)
{
nsRefPtr<gfxImageSurface> sourceImage = aSource->GetAsImageSurface();
if (!sourceImage) {
sourceImage = new gfxImageSurface(gfxIntSize(aBounds.width, aBounds.height), gfxASurface::FormatFromContent(aSource->GetContentType()));
nsRefPtr<gfxContext> ctx = new gfxContext(sourceImage);
aSource->SetDeviceOffset(gfxPoint(0, 0));
ctx->SetSource(aSource);
ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
ctx->Paint();
}
// Find the transformed rectangle of our layer.
gfxRect offsetRect = aTransform.TransformBounds(aBounds);
// Intersect the transformed layer with the destination rectangle.
// This is in device space since we have an identity transform set on aTarget.
gfxRect destRect = aDest->GetClipExtents();
destRect.IntersectRect(destRect, offsetRect);
// Create a surface the size of the transformed object.
nsRefPtr<gfxASurface> dest = aDest->CurrentSurface();
nsRefPtr<gfxImageSurface> destImage = dest->GetAsImageSurface();
destImage = nsnull;
gfxPoint offset;
PRBool blitComplete;
if (!destImage || aDontBlit || !aDest->ClipContainsRect(destRect)) {
destImage = new gfxImageSurface(gfxIntSize(destRect.width, destRect.height),
gfxASurface::ImageFormatARGB32);
offset = destRect.TopLeft();
blitComplete = PR_FALSE;
} else {
offset = -dest->GetDeviceOffset();
blitComplete = PR_TRUE;
}
// Include a translation to the correct origin.
gfx3DMatrix translation = gfx3DMatrix::Translation(aBounds.x, aBounds.y, 0);
// Transform the content and offset it such that the content begins at the origin.
PixmanTransform(destImage, sourceImage, translation * aTransform, offset);
if (blitComplete) {
return nsnull;
}
// If we haven't actually drawn to aDest then return our temporary image so that
// the caller can do this.
aDrawOffset = destRect.TopLeft();
return destImage.forget();
}
void
BasicLayerManager::PaintLayer(gfxContext* aTarget,
Layer* aLayer,
@ -1676,12 +1835,16 @@ BasicLayerManager::PaintLayer(gfxContext* aTarget,
}
gfxMatrix transform;
// XXX we need to add some kind of 3D transform support, possibly
// using pixman?
NS_ASSERTION(effectiveTransform.Is2D(),
"Only 2D transforms supported currently");
effectiveTransform.Is2D(&transform);
aTarget->SetMatrix(transform);
// Will return an identity matrix for 3d transforms, and is handled separately below.
PRBool is2D = effectiveTransform.CanDraw2D(&transform);
NS_ABORT_IF_FALSE(is2D || needsGroup || !aLayer->GetFirstChild(), "Must PushGroup for 3d transforms!");
if (is2D) {
aTarget->SetMatrix(transform);
} else {
aTarget->SetMatrix(gfxMatrix());
// Save so we can restore clipping after PushGroupForLayer changes it.
aTarget->Save();
}
const nsIntRegion& visibleRegion = aLayer->GetEffectiveVisibleRegion();
// If needsGroup is true, we'll clip to the visible region after we've popped the group
@ -1741,7 +1904,27 @@ BasicLayerManager::PaintLayer(gfxContext* aTarget,
}
if (needsGroup) {
PopGroupToSourceWithCachedSurface(aTarget, groupTarget);
PRBool blitComplete = PR_FALSE;
if (is2D) {
PopGroupToSourceWithCachedSurface(aTarget, groupTarget);
} else {
nsRefPtr<gfxASurface> sourceSurface = PopGroupToSurface(aTarget, groupTarget);
aTarget->Restore();
NS_ABORT_IF_FALSE(sourceSurface, "PopGroup should always return a surface pattern");
gfxRect bounds = visibleRegion.GetBounds();
gfxPoint offset;
PRBool dontBlit = needsClipToVisibleRegion || mTransactionIncomplete ||
aLayer->GetEffectiveOpacity() != 1.0f;
nsRefPtr<gfxASurface> result =
Transform3D(sourceSurface, aTarget, bounds,
effectiveTransform, offset, dontBlit);
blitComplete = !result;
if (result) {
aTarget->SetSource(result, offset);
}
}
// If we're doing our own double-buffering, we need to avoid drawing
// the results of an incomplete transaction to the destination surface ---
// that could cause flicker. Double-buffering is implemented using a
@ -1751,7 +1934,7 @@ BasicLayerManager::PaintLayer(gfxContext* aTarget,
// intersect any other leaf layers, so if the transaction is not yet marked
// incomplete, the contents of this container layer are the final contents
// for the window.
if (!mTransactionIncomplete) {
if (!mTransactionIncomplete && !blitComplete) {
if (needsClipToVisibleRegion) {
gfxUtils::ClipToRegion(aTarget, aLayer->GetEffectiveVisibleRegion());
}
@ -2677,8 +2860,17 @@ public:
// We push groups for container layers if we need to, which always
// are aligned in device space, so it doesn't really matter how we snap
// containers.
gfxMatrix residual;
gfx3DMatrix idealTransform = GetLocalTransform()*aTransformToSurface;
mEffectiveTransform = SnapTransform(idealTransform, gfxRect(0, 0, 0, 0), nsnull);
if (!idealTransform.CanDraw2D()) {
mEffectiveTransform = idealTransform;
ComputeEffectiveTransformsForChildren(gfx3DMatrix());
mUseIntermediateSurface = PR_TRUE;
return;
}
mEffectiveTransform = SnapTransform(idealTransform, gfxRect(0, 0, 0, 0), &residual);
// We always pass the ideal matrix down to our children, so there is no
// need to apply any compensation using the residual from SnapTransform.
ComputeEffectiveTransformsForChildren(idealTransform);

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

@ -171,6 +171,7 @@ public:
already_AddRefed<gfxContext> PushGroupWithCachedSurface(gfxContext *aTarget,
gfxASurface::gfxContentType aContent);
void PopGroupToSourceWithCachedSurface(gfxContext *aTarget, gfxContext *aPushed);
already_AddRefed<gfxASurface> PopGroupToSurface(gfxContext *aTarget, gfxContext *aPushed);
virtual PRBool IsCompositingCheap() { return PR_FALSE; }
virtual bool HasShadowManagerInternal() const { return false; }

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

@ -347,6 +347,24 @@ gfx3DMatrix::Is2D(gfxMatrix* aMatrix) const
return PR_TRUE;
}
PRBool
gfx3DMatrix::CanDraw2D(gfxMatrix* aMatrix) const
{
if (_14 != 0.0f || _24 != 0.0f ||
_34 != 0.0f || _44 != 1.0f) {
return PR_FALSE;
}
if (aMatrix) {
aMatrix->xx = _11;
aMatrix->yx = _12;
aMatrix->xy = _21;
aMatrix->yy = _22;
aMatrix->x0 = _41;
aMatrix->y0 = _42;
}
return PR_TRUE;
}
gfxPoint gfx3DMatrix::ProjectPoint(const gfxPoint& aPoint) const
{
// Define a ray of the form P + Ut where t is a real number

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

@ -96,6 +96,17 @@ public:
*/
PRBool Is2D(gfxMatrix* aMatrix = nsnull) const;
/**
* Returns true if the matrix can be reduced to a 2D affine transformation
* (i.e. as obtained by From2D). If it is, optionally returns the 2D
* matrix in aMatrix. This should only be used on matrices required for
* rendering, not for intermediate calculations.
*
* Since drawing is to a 2d plane, any 3d transform without perspective
* can be reduced by dropping the z row and column.
*/
PRBool CanDraw2D(gfxMatrix* aMatrix = nsnull) const;
/**
* Returns true if the matrix is the identity matrix. The most important
* property we require is that gfx3DMatrix().IsIdentity() returns true.