Bug 772679. RestrictToLayerPixels needs to accurately convert between appunits scroll offsets and ThebesLayer pixel coordinates. r=tnikkel

Change GetThebesLayerResolutionForFrame to GetThebesLayerScaleForFrame,
which just returns a scale. Ensure that the scale is as accurate as possible
even if dedicated layers for scrolled content (or any layers at all) have not
been created yet, by taking into account transforms that have not yet
generated layers. This makes the decisions made by
nsGfxScrollFrameInner::ScrollToImpl independent of whether there is
currently an active layer for the scrolled content (or much more nearly so).
In nsGfxScrollFrameInner::ScrollToImpl, do not use the current internal
fractional offset of the ThebesLayer, which is in a mostly unrelated
coordinate space to our scroll positions. Instead, just try to make sure
that the previous and next scroll position differ by a whole number of
layer pixels.
This commit is contained in:
Robert O'Callahan 2012-08-05 00:26:38 +12:00
parent 20266d6b15
commit 078dd6ac15
4 changed files with 97 additions and 87 deletions

View File

@ -2505,44 +2505,50 @@ FrameLayerBuilder::GetDedicatedLayer(nsIFrame* aFrame, PRUint32 aDisplayItemKey)
return nullptr;
}
bool
FrameLayerBuilder::GetThebesLayerResolutionForFrame(nsIFrame* aFrame,
double* aXres, double* aYres,
gfxPoint* aPoint)
static gfxSize
PredictScaleForContent(nsIFrame* aFrame, nsIFrame* aAncestorWithScale,
const gfxSize& aScale)
{
nsTArray<DisplayItemData> *array = GetDisplayItemDataArrayForFrame(aFrame);
if (array) {
for (PRUint32 i = 0; i < array->Length(); ++i) {
Layer* layer = array->ElementAt(i).mLayer;
if (layer->HasUserData(&gThebesDisplayItemLayerUserData)) {
ThebesDisplayItemLayerUserData* data =
static_cast<ThebesDisplayItemLayerUserData*>
(layer->GetUserData(&gThebesDisplayItemLayerUserData));
*aXres = data->mXScale;
*aYres = data->mYScale;
*aPoint = data->mActiveScrolledRootPosition;
return true;
gfx3DMatrix transform =
gfx3DMatrix::ScalingMatrix(aScale.width, aScale.height, 1.0);
// aTransform is applied first, then the scale is applied to the result
transform = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestorWithScale)*transform;
gfxMatrix transform2d;
if (transform.CanDraw2D(&transform2d)) {
return transform2d.ScaleFactors(true);
}
return gfxSize(1.0, 1.0);
}
gfxSize
FrameLayerBuilder::GetThebesLayerScaleForFrame(nsIFrame* aFrame)
{
nsIFrame* last;
for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
last = f;
if (f->GetStateBits() & NS_FRAME_HAS_CONTAINER_LAYER) {
nsTArray<DisplayItemData>* array = GetDisplayItemDataArrayForFrame(f);
NS_ASSERTION(array, "Must have display item data for container");
for (PRUint32 i = 0; i < array->Length(); ++i) {
Layer* layer = array->ElementAt(i).mLayer;
ContainerLayer* container = layer->AsContainerLayer();
if (!container) {
continue;
}
for (Layer* l = container->GetFirstChild(); l; l = l->GetNextSibling()) {
ThebesDisplayItemLayerUserData* data =
static_cast<ThebesDisplayItemLayerUserData*>
(l->GetUserData(&gThebesDisplayItemLayerUserData));
if (data) {
return PredictScaleForContent(aFrame, f, gfxSize(data->mXScale, data->mYScale));
}
}
}
}
}
nsIFrame::ChildListIterator lists(aFrame);
for (; !lists.IsDone(); lists.Next()) {
if (lists.CurrentID() == nsIFrame::kPopupList ||
lists.CurrentID() == nsIFrame::kSelectPopupList) {
continue;
}
nsFrameList::Enumerator childFrames(lists.CurrentList());
for (; !childFrames.AtEnd(); childFrames.Next()) {
if (GetThebesLayerResolutionForFrame(childFrames.get(),
aXres, aYres, aPoint)) {
return true;
}
}
}
return false;
return PredictScaleForContent(aFrame, last,
last->PresContext()->PresShell()->GetResolution());
}
#ifdef MOZ_DUMP_PAINTING

View File

@ -369,15 +369,13 @@ public:
nsIntPoint GetLastPaintOffset(ThebesLayer* aLayer);
/**
* Return resolution and scroll offset of ThebesLayer content associated
* with aFrame's subtree.
* Returns true if some ThebesLayer was found.
* This just looks for the first ThebesLayer and returns its data. There
* could be other ThebesLayers with different resolution and offsets.
* Return the resolution at which we expect to render aFrame's contents,
* assuming they are being painted to retained layers. This takes into account
* the resolution the contents of the ContainerLayer containing aFrame are
* being rendered at, as well as any currently-inactive transforms between
* aFrame and that container layer.
*/
static bool GetThebesLayerResolutionForFrame(nsIFrame* aFrame,
double* aXRes, double* aYRes,
gfxPoint* aPoint);
static gfxSize GetThebesLayerScaleForFrame(nsIFrame* aFrame);
/**
* Clip represents the intersection of an optional rectangle with a

View File

@ -1196,6 +1196,7 @@ public:
* The resolution defaults to 1.0.
*/
virtual nsresult SetResolution(float aXResolution, float aYResolution) = 0;
gfxSize GetResolution() { return gfxSize(mXResolution, mYResolution); }
float GetXResolution() { return mXResolution; }
float GetYResolution() { return mYResolution; }

View File

@ -1967,38 +1967,34 @@ void nsGfxScrollFrameInner::ScrollVisual(nsPoint aOldScrolledFramePos)
}
/**
* Adjust the desired scroll value in given range
* in order to get resulting scroll by whole amount of layer pixels.
* Current implementation is not checking that result value is the best.
* Ideally it's would be possible to find best value by implementing
* test function which is repeating last part of CreateOrRecycleThebesLayer,
* and checking other points in allowed range, but that may cause another perf hit.
* Let's keep it as TODO.
* Return an appunit value close to aDesired and between aLower and aUpper
* such that (aDesired - aCurrent)*aRes/aAppUnitsPerPixel is an integer (or
* as close as we can get modulo rounding to appunits). If that
* can't be done, just returns aDesired.
*/
static nscoord
RestrictToLayerPixels(nscoord aDesired, nscoord aLower,
nscoord aUpper, nscoord aAppUnitsPerPixel,
double aRes, double aCurrentLayerOffset)
AlignWithLayerPixels(nscoord aDesired, nscoord aLower,
nscoord aUpper, nscoord aAppUnitsPerPixel,
double aRes, nscoord aCurrent)
{
// convert the result to layer pixels
double layerVal = aRes * double(aDesired) / aAppUnitsPerPixel;
double currentLayerVal = (aRes*aCurrent)/aAppUnitsPerPixel;
double desiredLayerVal = (aRes*aDesired)/aAppUnitsPerPixel;
double delta = desiredLayerVal - currentLayerVal;
double nearestVal = NS_round(delta) + currentLayerVal;
// Correct value using current layer offset
layerVal -= aCurrentLayerOffset;
// Try nearest pixel bound first
double nearestVal = NS_round(layerVal);
// Convert back from ThebesLayer space to appunits relative to the top-left
// of the scrolled frame.
nscoord nearestAppUnitVal =
NSToCoordRoundWithClamp(nearestVal * aAppUnitsPerPixel / aRes);
NSToCoordRoundWithClamp(nearestVal*aAppUnitsPerPixel/aRes);
// Check if nearest layer pixel result fit into allowed and scroll range
if (nearestAppUnitVal >= aLower && nearestAppUnitVal <= aUpper) {
return nearestAppUnitVal;
} else if (nearestVal != layerVal) {
} else if (nearestVal != desiredLayerVal) {
// Check if opposite pixel boundary fit into scroll range
double oppositeVal = nearestVal + ((nearestVal < layerVal) ? 1 : -1);
double oppositeVal = nearestVal + ((nearestVal < desiredLayerVal) ? 1 : -1);
nscoord oppositeAppUnitVal =
NSToCoordRoundWithClamp(oppositeVal * aAppUnitsPerPixel / aRes);
NSToCoordRoundWithClamp(oppositeVal*aAppUnitsPerPixel/aRes);
if (oppositeAppUnitVal >= aLower && oppositeAppUnitVal <= aUpper) {
return oppositeAppUnitVal;
}
@ -2007,17 +2003,17 @@ RestrictToLayerPixels(nscoord aDesired, nscoord aLower,
}
/**
* Clamp desired scroll position aPt to aBounds (if aBounds is non-null) and then snap
* it to the nearest layer pixel edges, keeping it within aRange during snapping
* (if aRange is non-null). aCurrScroll is the current scroll position.
* Clamp desired scroll position aPt to aBounds and then snap
* it to the same layer pixel edges as aCurrent, keeping it within aRange
* during snapping. aCurrent is the current scroll position.
*/
static nsPoint
ClampAndRestrictToLayerPixels(const nsPoint& aPt,
const nsRect& aBounds,
nscoord aAppUnitsPerPixel,
const nsRect& aRange,
double aXRes, double aYRes,
const gfxPoint& aCurrScroll)
ClampAndAlignWithLayerPixels(const nsPoint& aPt,
const nsRect& aBounds,
const nsRect& aRange,
const nsPoint& aCurrent,
nscoord aAppUnitsPerPixel,
const gfxSize& aScale)
{
nsPoint pt = aBounds.ClampPoint(aPt);
// Intersect scroll range with allowed range, by clamping the corners
@ -2025,10 +2021,10 @@ ClampAndRestrictToLayerPixels(const nsPoint& aPt,
nsPoint rangeTopLeft = aBounds.ClampPoint(aRange.TopLeft());
nsPoint rangeBottomRight = aBounds.ClampPoint(aRange.BottomRight());
return nsPoint(RestrictToLayerPixels(pt.x, rangeTopLeft.x, rangeBottomRight.x,
aAppUnitsPerPixel, aXRes, aCurrScroll.x),
RestrictToLayerPixels(pt.y, rangeTopLeft.y, rangeBottomRight.y,
aAppUnitsPerPixel, aYRes, aCurrScroll.y));
return nsPoint(AlignWithLayerPixels(pt.x, rangeTopLeft.x, rangeBottomRight.x,
aAppUnitsPerPixel, aScale.width, aCurrent.x),
AlignWithLayerPixels(pt.y, rangeTopLeft.y, rangeBottomRight.y,
aAppUnitsPerPixel, aScale.height, aCurrent.y));
}
/* static */ void
@ -2061,19 +2057,28 @@ nsGfxScrollFrameInner::ScrollToImpl(nsPoint aPt, const nsRect& aRange)
{
nsPresContext* presContext = mOuter->PresContext();
nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
double xres = 1.0, yres = 1.0;
gfxPoint activeScrolledRootPosition;
FrameLayerBuilder::GetThebesLayerResolutionForFrame(mScrolledFrame, &xres, &yres,
&activeScrolledRootPosition);
nsPoint pt =
ClampAndRestrictToLayerPixels(aPt,
GetScrollRangeForClamping(),
appUnitsPerDevPixel,
aRange, xres, yres,
activeScrolledRootPosition);
// 'scale' is our estimate of the scale factor that will be applied
// when rendering the scrolled content to its own ThebesLayer.
gfxSize scale = FrameLayerBuilder::GetThebesLayerScaleForFrame(mScrolledFrame);
nsPoint curPos = GetScrollPosition();
// 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.
// Note that when we actually draw into a ThebesLayer, the coordinates
// that get mapped onto the layer buffer pixels are from the display list,
// which are relative to the display root frame's top-left increasing down,
// whereas here our coordinates are scroll positions which increase upward
// and are relative to the scrollport top-left. This difference doesn't actually
// matter since all we are about is that there be an integer number of
// layer pixels between pt and curPos.
nsPoint pt =
ClampAndAlignWithLayerPixels(aPt,
GetScrollRangeForClamping(),
aRange,
curPos,
appUnitsPerDevPixel,
scale);
if (pt == curPos) {
return;
}