Bug 777194. Part 5: When choosing a subpixel position to scroll to, align the new position with the position that was most recently used to rerender the scrolled layer(s). r=tnikkel

If we always align the new scroll position with the previous scroll position, we can accumulate error and
gradually drift out of alignment with the layer pixels until we exceed the tolerance and are forced to
rerender everything.
This commit is contained in:
Robert O'Callahan 2012-08-17 11:40:10 +12:00
parent a03ff7880b
commit 92273d000e
6 changed files with 61 additions and 18 deletions

View File

@ -17,6 +17,7 @@
#include "nsImageFrame.h"
#include "nsRenderingContext.h"
#include "MaskLayerImageCache.h"
#include "nsIScrollableFrame.h"
#include "mozilla/Preferences.h"
#include "sampler.h"
@ -1097,13 +1098,6 @@ GetTranslationForThebesLayer(ThebesLayer* aLayer)
static const double SUBPIXEL_OFFSET_EPSILON = 0.02;
static bool
SubpixelOffsetFuzzyEqual(gfxPoint aV1, gfxPoint aV2)
{
return fabs(aV2.x - aV1.x) < SUBPIXEL_OFFSET_EPSILON &&
fabs(aV2.y - aV1.y) < SUBPIXEL_OFFSET_EPSILON;
}
/**
* This normally computes NSToIntRoundUp(aValue). However, if that would
* give a residual near 0.5 while aOldResidual is near -0.5, or
@ -1130,12 +1124,30 @@ RoundToMatchResidual(double aValue, double aOldResidual)
return v;
}
static void
ResetScrollPositionForLayerPixelAlignment(const nsIFrame* aActiveScrolledRoot)
{
nsIScrollableFrame* sf = nsLayoutUtils::GetScrollableFrameFor(aActiveScrolledRoot);
if (sf) {
sf->ResetScrollPositionForLayerPixelAlignment();
}
}
static void
InvalidateEntireThebesLayer(ThebesLayer* aLayer, const nsIFrame* aActiveScrolledRoot)
{
nsIntRect invalidate = aLayer->GetValidRegion().GetBounds();
aLayer->InvalidateRegion(invalidate);
ResetScrollPositionForLayerPixelAlignment(aActiveScrolledRoot);
}
already_AddRefed<ThebesLayer>
ContainerState::CreateOrRecycleThebesLayer(const nsIFrame* aActiveScrolledRoot, const nsIFrame* aReferenceFrame)
{
// We need a new thebes layer
nsRefPtr<ThebesLayer> layer;
ThebesDisplayItemLayerUserData* data;
bool didResetScrollPositionForLayerPixelAlignment = false;
if (mNextFreeRecycledThebesLayer < mRecycledThebesLayers.Length()) {
// Recycle a layer
layer = mRecycledThebesLayers[mNextFreeRecycledThebesLayer];
@ -1160,11 +1172,14 @@ ContainerState::CreateOrRecycleThebesLayer(const nsIFrame* aActiveScrolledRoot,
if (mInvalidateAllThebesContent ||
data->mXScale != mParameters.mXScale ||
data->mYScale != mParameters.mYScale) {
nsIntRect invalidate = layer->GetValidRegion().GetBounds();
layer->InvalidateRegion(invalidate);
InvalidateEntireThebesLayer(layer, aActiveScrolledRoot);
didResetScrollPositionForLayerPixelAlignment = true;
} else {
InvalidatePostTransformRegion(layer, mInvalidThebesContent,
GetTranslationForThebesLayer(layer));
nsIntRect bounds = mInvalidThebesContent.GetBounds();
if (!bounds.IsEmpty()) {
InvalidatePostTransformRegion(layer, mInvalidThebesContent,
GetTranslationForThebesLayer(layer));
}
}
// We do not need to Invalidate these areas in the widget because we
// assume the caller of InvalidateThebesLayerContents has ensured
@ -1177,6 +1192,8 @@ ContainerState::CreateOrRecycleThebesLayer(const nsIFrame* aActiveScrolledRoot,
// Mark this layer as being used for Thebes-painting display items
data = new ThebesDisplayItemLayerUserData();
layer->SetUserData(&gThebesDisplayItemLayerUserData, data);
ResetScrollPositionForLayerPixelAlignment(aActiveScrolledRoot);
didResetScrollPositionForLayerPixelAlignment = true;
}
data->mXScale = mParameters.mXScale;
data->mYScale = mParameters.mYScale;
@ -1209,10 +1226,11 @@ ContainerState::CreateOrRecycleThebesLayer(const nsIFrame* aActiveScrolledRoot,
// If it has changed, then we need to invalidate the entire layer since the
// pixels in the layer buffer have the content at a (subpixel) offset
// from what we need.
if (!SubpixelOffsetFuzzyEqual(activeScrolledRootTopLeft, data->mActiveScrolledRootPosition)) {
if (!activeScrolledRootTopLeft.WithinEpsilonOf(data->mActiveScrolledRootPosition, SUBPIXEL_OFFSET_EPSILON)) {
data->mActiveScrolledRootPosition = activeScrolledRootTopLeft;
InvalidateEntireThebesLayer(layer, aActiveScrolledRoot);
} else if (didResetScrollPositionForLayerPixelAlignment) {
data->mActiveScrolledRootPosition = activeScrolledRootTopLeft;
nsIntRect invalidate = layer->GetValidRegion().GetBounds();
layer->InvalidateRegion(invalidate);
}
#endif

View File

@ -860,7 +860,7 @@ nsLayoutUtils::FindSiblingViewFor(nsIView* aParentView, nsIFrame* aFrame) {
//static
nsIScrollableFrame*
nsLayoutUtils::GetScrollableFrameFor(nsIFrame *aScrolledFrame)
nsLayoutUtils::GetScrollableFrameFor(const nsIFrame *aScrolledFrame)
{
nsIFrame *frame = aScrolledFrame->GetParent();
if (!frame) {

View File

@ -323,7 +323,7 @@ public:
/**
* GetScrollableFrameFor returns the scrollable frame for a scrolled frame
*/
static nsIScrollableFrame* GetScrollableFrameFor(nsIFrame *aScrolledFrame);
static nsIScrollableFrame* GetScrollableFrameFor(const nsIFrame *aScrolledFrame);
/**
* GetNearestScrollableFrameForDirection locates the first ancestor of

View File

@ -1593,6 +1593,7 @@ nsGfxScrollFrameInner::nsGfxScrollFrameInner(nsContainerFrame* aOuter,
, mScrollPosAtLastPaint(0, 0)
, mRestorePos(-1, -1)
, mLastPos(-1, -1)
, mScrollPosForLayerPixelAlignment(-1, -1)
, mNeverHasVerticalScrollbar(false)
, mNeverHasHorizontalScrollbar(false)
, mHasVerticalScrollbar(false)
@ -2091,6 +2092,8 @@ nsGfxScrollFrameInner::ScrollToImpl(nsPoint aPt, const nsRect& aRange)
// when rendering the scrolled content to its own ThebesLayer.
gfxSize scale = FrameLayerBuilder::GetThebesLayerScaleForFrame(mScrolledFrame);
nsPoint curPos = GetScrollPosition();
nsPoint alignWithPos = mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)
? curPos : mScrollPosForLayerPixelAlignment;
// Try to align aPt with curPos so they have an integer number of layer
// pixels between them. This gives us the best chance of scrolling without
// having to invalidate due to changes in subpixel rendering.
@ -2105,10 +2108,9 @@ nsGfxScrollFrameInner::ScrollToImpl(nsPoint aPt, const nsRect& aRange)
ClampAndAlignWithLayerPixels(aPt,
GetScrollRangeForClamping(),
aRange,
curPos,
alignWithPos,
appUnitsPerDevPixel,
scale);
if (pt == curPos) {
return;
}
@ -2236,6 +2238,13 @@ nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
if (IsScrollingActive() && !CanScrollWithBlitting(mOuter)) {
MarkInactive();
}
if (IsScrollingActive()) {
if (mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)) {
mScrollPosForLayerPixelAlignment = mScrollPosAtLastPaint;
}
} else {
mScrollPosForLayerPixelAlignment = nsPoint(-1,-1);
}
}
if (aBuilder->GetIgnoreScrollFrame() == mOuter || IsIgnoringViewportClipping()) {

View File

@ -232,6 +232,10 @@ public:
bool IsLTR() const;
bool IsScrollbarOnRight() const;
bool IsScrollingActive() const { return mScrollingActive || ShouldBuildLayer(); }
void ResetScrollPositionForLayerPixelAlignment()
{
mScrollPosForLayerPixelAlignment = GetScrollPosition();
}
bool UpdateOverflow();
@ -290,6 +294,7 @@ public:
nsExpirationState mActivityExpirationState;
nsCOMPtr<nsITimer> mScrollActivityTimer;
nsPoint mScrollPosForLayerPixelAlignment;
bool mNeverHasVerticalScrollbar:1;
bool mNeverHasHorizontalScrollbar:1;
@ -511,6 +516,9 @@ public:
virtual bool IsScrollingActive() MOZ_OVERRIDE {
return mInner.IsScrollingActive();
}
virtual void ResetScrollPositionForLayerPixelAlignment() {
mInner.ResetScrollPositionForLayerPixelAlignment();
}
virtual bool UpdateOverflow() {
return mInner.UpdateOverflow();
}
@ -759,6 +767,9 @@ public:
virtual bool IsScrollingActive() MOZ_OVERRIDE {
return mInner.IsScrollingActive();
}
virtual void ResetScrollPositionForLayerPixelAlignment() {
mInner.ResetScrollPositionForLayerPixelAlignment();
}
virtual bool UpdateOverflow() {
return mInner.UpdateOverflow();
}

View File

@ -204,6 +204,11 @@ public:
* expectation that scrolling is going to happen.
*/
virtual bool IsScrollingActive() = 0;
/**
* Call this when the layer(s) induced by active scrolling are being
* completely redrawn.
*/
virtual void ResetScrollPositionForLayerPixelAlignment() = 0;
};
#endif