Bug 637852. Part 18: Support computing the "residual transform" for a ThebesLayer --- the difference between its snapped transform and the ideal transform --- and use it to align ThebesLayer drawing for transforms that aren't changing. r=tnikkel

This fixes bug 637597 and probably other bugs.
This commit is contained in:
Robert O'Callahan 2011-06-23 00:11:28 +12:00
parent 50a8bdee8e
commit 9f092ece3e
7 changed files with 198 additions and 5 deletions

View File

@ -943,6 +943,19 @@ public:
* region.
*/
virtual void InvalidateRegion(const nsIntRegion& aRegion) = 0;
/**
* CONSTRUCTION PHASE ONLY
* Set whether ComputeEffectiveTransforms should compute the
* "residual translation" --- the translation that should be applied *before*
* mEffectiveTransform to get the ideal transform for this ThebesLayer.
* When this is true, ComputeEffectiveTransforms will compute the residual
* and ensure that the layer is invalidated whenever the residual changes.
* When it's false, a change in the residual will not trigger invalidation
* and GetResidualTranslation will return 0,0.
* So when the residual is to be ignored, set this to false for better
* performance.
*/
void SetAllowResidualTranslation(bool aAllow) { mAllowResidualTranslation = aAllow; }
/**
* Can be used anytime
@ -957,29 +970,63 @@ public:
{
// The default implementation just snaps 0,0 to pixels.
gfx3DMatrix idealTransform = GetLocalTransform()*aTransformToSurface;
mEffectiveTransform = SnapTransform(idealTransform, gfxRect(0, 0, 0, 0), nsnull);
gfxMatrix residual;
mEffectiveTransform = SnapTransform(idealTransform, gfxRect(0, 0, 0, 0),
mAllowResidualTranslation ? &residual : nsnull);
// The residual can only be a translation because ThebesLayer snapping
// only aligns a single point with the pixel grid; scale factors are always
// preserved exactly
NS_ASSERTION(!residual.HasNonTranslation(),
"Residual transform can only be a translation");
if (residual.GetTranslation() != mResidualTranslation) {
mResidualTranslation = residual.GetTranslation();
NS_ASSERTION(-0.5 <= mResidualTranslation.x && mResidualTranslation.x < 0.5 &&
-0.5 <= mResidualTranslation.y && mResidualTranslation.y < 0.5,
"Residual translation out of range");
mValidRegion.SetEmpty();
}
}
bool UsedForReadback() { return mUsedForReadback; }
void SetUsedForReadback(bool aUsed) { mUsedForReadback = aUsed; }
/**
* Returns the residual translation. Apply this translation when drawing
* into the ThebesLayer so that when mEffectiveTransform is applied afterwards
* by layer compositing, the results exactly match the "ideal transform"
* (the product of the transform of this layer and its ancestors).
* Returns 0,0 unless SetAllowResidualTranslation(true) has been called.
* The residual translation components are always in the range [-0.5, 0.5).
*/
gfxPoint GetResidualTranslation() const { return mResidualTranslation; }
protected:
ThebesLayer(LayerManager* aManager, void* aImplData)
: Layer(aManager, aImplData)
, mValidRegion()
, mUsedForReadback(false)
, mAllowResidualTranslation(false)
{
mContentFlags = 0; // Clear NO_TEXT, NO_TEXT_OVER_TRANSPARENT
}
virtual nsACString& PrintInfo(nsACString& aTo, const char* aPrefix);
/**
* ComputeEffectiveTransforms snaps the ideal transform to get mEffectiveTransform.
* mResidualTranslation is the translation that should be applied *before*
* mEffectiveTransform to get the ideal transform.
*/
gfxPoint mResidualTranslation;
nsIntRegion mValidRegion;
/**
* Set when this ThebesLayer is participating in readback, i.e. some
* ReadbackLayer (may) be getting its background from this layer.
*/
bool mUsedForReadback;
/**
* True when
*/
bool mAllowResidualTranslation;
};
/**

View File

@ -504,6 +504,10 @@ public:
// Don't do any snapping of our transform, since we're just going to
// draw straight through without intermediate buffers.
mEffectiveTransform = GetLocalTransform()*aTransformToSurface;
if (gfxPoint(0,0) != mResidualTranslation) {
mResidualTranslation = gfxPoint(0,0);
mValidRegion.SetEmpty();
}
return;
}
ThebesLayer::ComputeEffectiveTransforms(aTransformToSurface);

View File

@ -844,6 +844,11 @@ ContainerState::CreateOrRecycleThebesLayer(nsIFrame* aActiveScrolledRoot)
}
data->mXScale = mParameters.mXScale;
data->mYScale = mParameters.mYScale;
// 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.
layer->SetAllowResidualTranslation(
mParameters.mInTransformedSubtree && !mParameters.mInActiveTransformedSubtree);
mBuilder->LayerBuilder()->SaveLastPaintOffset(layer);
@ -1693,7 +1698,16 @@ ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder,
// Apply the inverse of our resolution-scale before the rest of our transform
transform = gfx3DMatrix::Scale(1.0/scale.width, 1.0/scale.height, 1.0)*transform;
aLayer->SetTransform(transform);
return FrameLayerBuilder::ContainerParameters(scale.width, scale.height);
FrameLayerBuilder::ContainerParameters
result(scale.width, scale.height, aIncomingScale);
if (aTransform) {
result.mInTransformedSubtree = true;
if (aContainerFrame->AreLayersMarkedActive(nsChangeHint_UpdateTransformLayer)) {
result.mInActiveTransformedSubtree = true;
}
}
return result;
}
already_AddRefed<ContainerLayer>
@ -1933,6 +1947,34 @@ FrameLayerBuilder::GetDedicatedLayer(nsIFrame* aFrame, PRUint32 aDisplayItemKey)
return nsnull;
}
/*
* A note on residual transforms:
*
* In a transformed subtree we sometimes apply the ThebesLayer's
* "residual transform" when drawing content into the ThebesLayer.
* This is a translation by components in the range [-0.5,0.5) provided
* by the layer system; applying the residual transform followed by the
* transforms used by layer compositing ensures that the subpixel alignment
* of the content of the ThebesLayer exactly matches what it would be if
* we used cairo/Thebes to draw directly to the screen without going through
* retained layer buffers.
*
* The visible and valid regions of the ThebesLayer are computed without
* knowing the residual transform (because we don't know what the residual
* transform is going to be until we've built the layer tree!). So we have to
* consider whether content painted in the range [x, xmost) might be painted
* outside the visible region we computed for that content. The visible region
* would be [floor(x), ceil(xmost)). The content would be rendered at
* [x + r, xmost + r), where -0.5 <= r < 0.5. So some half-rendered pixels could
* indeed fall outside the computed visible region, which is not a big deal;
* similar issues already arise when we snap cliprects to nearest pixels.
* Note that if the rendering of the content is snapped to nearest pixels ---
* which it often is --- then the content is actually rendered at
* [snap(x + r), snap(xmost + r)). It turns out that floor(x) <= snap(x + r)
* and ceil(xmost) >= snap(xmost + r), so the rendering of snapped content
* always falls within the visible region we computed.
*/
/* static */ void
FrameLayerBuilder::DrawThebesLayer(ThebesLayer* aLayer,
gfxContext* aContext,
@ -1976,7 +2018,10 @@ FrameLayerBuilder::DrawThebesLayer(ThebesLayer* aLayer,
gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
nsIntPoint offset = GetTranslationForThebesLayer(aLayer);
aContext->Scale(userData->mXScale, userData->mYScale);
aContext->Translate(-gfxPoint(offset.x, offset.y));
// Apply the residual transform if it has been enabled, to ensure that
// snapping when we draw into aContext exactly matches the ideal transform.
// See above for why this is OK.
aContext->Translate(aLayer->GetResidualTranslation() - gfxPoint(offset.x, offset.y));
nsPresContext* presContext = containerLayerFrame->PresContext();
PRInt32 appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();

View File

@ -142,10 +142,20 @@ public:
void DidEndTransaction(LayerManager* aManager);
struct ContainerParameters {
ContainerParameters() : mXScale(1), mYScale(1) {}
ContainerParameters() :
mXScale(1), mYScale(1),
mInTransformedSubtree(false), mInActiveTransformedSubtree(false) {}
ContainerParameters(float aXScale, float aYScale) :
mXScale(aXScale), mYScale(aYScale) {}
mXScale(aXScale), mYScale(aYScale),
mInTransformedSubtree(false), mInActiveTransformedSubtree(false) {}
ContainerParameters(float aXScale, float aYScale,
const ContainerParameters& aParent) :
mXScale(aXScale), mYScale(aYScale),
mInTransformedSubtree(aParent.mInTransformedSubtree),
mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree) {}
float mXScale, mYScale;
bool mInTransformedSubtree;
bool mInActiveTransformedSubtree;
};
/**
* Build a container layer for a display item that contains a child

View File

@ -0,0 +1,37 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
#parent {
position: absolute;
top: 100.8px;
-moz-transform: scale(1.1232,1.1232);
transform: scale(1.1232,1.1232);
}
#b1 {
position: absolute;
width: 100px;
height: 109px;
background: #c00;
-moz-transform: translate(100px,100px);
transform: translate(100px,100px);
}
#b2 {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background: white;
-moz-transform: translate(400px,0);
transform: translate(400px,0);
}
</style>
</head>
<body>
<div id="parent">
<div id="b1"></div>
<div id="b2"></div>
</div>
</body>
</html>

View File

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<html class="reftest-wait">
<head>
<style>
html { background:white; }
#parent {
position: absolute;
top: 100.8px;
-moz-transform: scale(1.1232,1.1232);
transform: scale(1.1232,1.1232);
}
#b1 {
position: absolute;
width: 100px;
height: 109px;
background: #c00;
-moz-transform: translate(100px,100px);
transform: translate(100px,100px);
}
#b2 {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background: white;
-moz-transform: translate(400px,200px);
transform: translate(400px,200px);
}
#b2.done {
-moz-transform: translate(400px,0);
transform: translate(400px,0);
}
</style>
</head>
<body>
<div id="parent">
<div id="b1"></div>
<div id="b2"></div>
</div>
<script>
function doTest() {
document.getElementById('b2').setAttribute("class", "done");
document.documentElement.removeAttribute("class");
}
window.addEventListener("MozReftestInvalidate", doTest, false);
</script>
</body>
</html>

View File

@ -1629,6 +1629,7 @@ fails-if(Android) == 635302-1.html 635302-1-ref.html
fails-if(http.platform=="X11"&&!layersGPUAccelerated) == 635373-3.html 635373-3-ref.html
HTTP(..) == 635639-1.html 635639-1-ref.html
HTTP(..) == 635639-2.html 635639-2-ref.html
== 637597-1.html 637597-1-ref.html
== 637852-1.html 637852-1-ref.html
== 641770-1.html 641770-1-ref.html
== 641856-1.html 641856-1-ref.html