mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 11:25:00 +00:00
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:
parent
50a8bdee8e
commit
9f092ece3e
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
37
layout/reftests/bugs/637597-1-ref.html
Normal file
37
layout/reftests/bugs/637597-1-ref.html
Normal 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>
|
49
layout/reftests/bugs/637597-1.html
Normal file
49
layout/reftests/bugs/637597-1.html
Normal 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>
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user