diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index 06f5eb933e6f..eee1ecfa7160 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -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; }; /** diff --git a/gfx/layers/basic/BasicLayers.cpp b/gfx/layers/basic/BasicLayers.cpp index 8fbfcca102ec..dfaddf3d7262 100644 --- a/gfx/layers/basic/BasicLayers.cpp +++ b/gfx/layers/basic/BasicLayers.cpp @@ -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); diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index 53ffd42a509e..1df6c8a92362 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -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 @@ -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(); diff --git a/layout/base/FrameLayerBuilder.h b/layout/base/FrameLayerBuilder.h index 55449b4e370b..2bcaa773c46c 100644 --- a/layout/base/FrameLayerBuilder.h +++ b/layout/base/FrameLayerBuilder.h @@ -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 diff --git a/layout/reftests/bugs/637597-1-ref.html b/layout/reftests/bugs/637597-1-ref.html new file mode 100644 index 000000000000..c526f512a200 --- /dev/null +++ b/layout/reftests/bugs/637597-1-ref.html @@ -0,0 +1,37 @@ + + + + + + +
+
+
+
+ + diff --git a/layout/reftests/bugs/637597-1.html b/layout/reftests/bugs/637597-1.html new file mode 100644 index 000000000000..7b0b8eea7f1f --- /dev/null +++ b/layout/reftests/bugs/637597-1.html @@ -0,0 +1,49 @@ + + + + + + +
+
+
+
+ + + diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index 2f8a4310e68f..88fcf45a30d1 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -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