mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-04 07:40:42 +00:00
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:
parent
a03ff7880b
commit
92273d000e
@ -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
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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()) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user