Bug 786267: B2G: Lower resolution while doing accelerated panning r=cjones

This commit is contained in:
Doug Sherk 2012-09-29 00:02:45 -04:00
parent 19cc16c9aa
commit 4a17f28103
5 changed files with 185 additions and 29 deletions

View File

@ -459,8 +459,10 @@ TabChild::HandlePossibleMetaViewportChange()
metrics.mZoom.width = metrics.mZoom.height =
metrics.mResolution.width = metrics.mResolution.height = zoom;
metrics.mDisplayPort = AsyncPanZoomController::CalculatePendingDisplayPort(
metrics,
gfx::Point(0.0f, 0.0f));
// The page must have been refreshed in some way such as a new document or
// new CSS viewport, so we know that there's no velocity, acceleration, and
// we have no idea how long painting will take.
metrics, gfx::Point(0.0f, 0.0f), gfx::Point(0.0f, 0.0f), 0.0);
// Force a repaint with these metrics. This, among other things, sets the
// displayport, so we start with async painting.
RecvUpdateFrame(metrics);

View File

@ -44,7 +44,7 @@ static const int32_t FLING_REPAINT_INTERVAL = 75;
* Minimum amount of speed along an axis before we begin painting far ahead by
* adjusting the displayport.
*/
static const float MIN_SKATE_SPEED = 0.5f;
static const float MIN_SKATE_SPEED = 0.7f;
/**
* Angle from axis within which we stay axis-locked.
@ -79,6 +79,12 @@ static const double MIN_ZOOM = 0.125;
*/
static const int TOUCH_LISTENER_TIMEOUT = 300;
/**
* Number of samples to store of how long it took to paint after the previous
* requests.
*/
static const int NUM_PAINT_DURATION_SAMPLES = 3;
AsyncPanZoomController::AsyncPanZoomController(GeckoContentController* aGeckoContentController,
GestureBehavior aGestures)
: mGeckoContentController(aGeckoContentController),
@ -587,6 +593,10 @@ const gfx::Point AsyncPanZoomController::GetVelocityVector() {
return gfx::Point(mX.GetVelocity(), mY.GetVelocity());
}
const gfx::Point AsyncPanZoomController::GetAccelerationVector() {
return gfx::Point(mX.GetAccelerationFactor(), mY.GetAccelerationFactor());
}
void AsyncPanZoomController::StartPanning(const MultiTouchInput& aEvent) {
float dx = mX.PanDistance(),
dy = mY.PanDistance();
@ -661,6 +671,9 @@ bool AsyncPanZoomController::DoFling(const TimeDuration& aDelta) {
shouldContinueFlingY = mY.FlingApplyFrictionOrCancel(aDelta);
// If we shouldn't continue the fling, let's just stop and repaint.
if (!shouldContinueFlingX && !shouldContinueFlingY) {
// Bring the resolution back in sync with the zoom, in case we scaled down
// the zoom while accelerating.
SetZoomAndResolution(mFrameMetrics.mZoom.width);
RequestContentRepaint();
mState = NOTHING;
return false;
@ -712,8 +725,7 @@ void AsyncPanZoomController::SetPageRect(const gfx::Rect& aCSSPageRect) {
}
void AsyncPanZoomController::ScaleWithFocus(float aScale, const nsIntPoint& aFocus) {
float scaleFactor = aScale / mFrameMetrics.mZoom.width,
oldScale = mFrameMetrics.mZoom.width;
float scaleFactor = aScale / mFrameMetrics.mZoom.width;
SetZoomAndResolution(aScale);
@ -721,22 +733,43 @@ void AsyncPanZoomController::ScaleWithFocus(float aScale, const nsIntPoint& aFoc
// current CSS page rect (which is unchanged since it's not affected by zoom).
SetPageRect(mFrameMetrics.mScrollableRect);
mFrameMetrics.mScrollOffset.x += float(aFocus.x) * (scaleFactor - 1.0f) / oldScale;
mFrameMetrics.mScrollOffset.y += float(aFocus.y) * (scaleFactor - 1.0f) / oldScale;
// If the new scale is very small, we risk multiplying in huge rounding
// errors, so don't bother adjusting the scroll offset.
if (aScale >= 0.01f) {
mFrameMetrics.mScrollOffset.x += float(aFocus.x) * (scaleFactor - 1.0f) / aScale;
mFrameMetrics.mScrollOffset.y += float(aFocus.y) * (scaleFactor - 1.0f) / aScale;
}
}
bool AsyncPanZoomController::EnlargeDisplayPortAlongAxis(float aCompositionBounds,
bool AsyncPanZoomController::EnlargeDisplayPortAlongAxis(float aSkateSizeMultiplier,
double aEstimatedPaintDuration,
float aCompositionBounds,
float aVelocity,
float aAcceleration,
float* aDisplayPortOffset,
float* aDisplayPortLength)
{
const float MIN_SKATE_SIZE_MULTIPLIER = 2.0f;
const float MAX_SKATE_SIZE_MULTIPLIER = 4.0f;
if (fabsf(aVelocity) > MIN_SKATE_SPEED) {
*aDisplayPortLength = aCompositionBounds * clamped(fabsf(aVelocity),
MIN_SKATE_SIZE_MULTIPLIER, MAX_SKATE_SIZE_MULTIPLIER);
// Enlarge the area we paint.
*aDisplayPortLength = aCompositionBounds * aSkateSizeMultiplier;
// Position the area we paint such that all of the excess that extends past
// the screen is on the side towards the velocity.
*aDisplayPortOffset = aVelocity > 0 ? 0 : aCompositionBounds - *aDisplayPortLength;
// Only compensate for acceleration when we actually have any. Otherwise
// we'll overcompensate when a user is just panning around without flinging.
if (aAcceleration > 1.01f) {
// Compensate for acceleration and how long we expect a paint to take. We
// try to predict where the viewport will be when painting has finished.
*aDisplayPortOffset +=
fabsf(aAcceleration) * aVelocity * aCompositionBounds * aEstimatedPaintDuration;
// If our velocity is in the negative direction of the axis, we have to
// compensate for the fact that our scroll offset is the top-left position
// of the viewport. In this case, let's make it relative to the
// bottom-right. That way, we'll always be growing the displayport upwards
// and to the left when skating negatively.
*aDisplayPortOffset -= aVelocity < 0 ? aCompositionBounds : 0;
}
return true;
}
return false;
@ -744,36 +777,85 @@ bool AsyncPanZoomController::EnlargeDisplayPortAlongAxis(float aCompositionBound
const gfx::Rect AsyncPanZoomController::CalculatePendingDisplayPort(
const FrameMetrics& aFrameMetrics,
const gfx::Point& aVelocity)
const gfx::Point& aVelocity,
const gfx::Point& aAcceleration,
double aEstimatedPaintDuration)
{
// The multiplier we apply to a dimension's length if it is skating. That is,
// if it's going above MIN_SKATE_SPEED. We prefer to increase the size of the
// Y axis because it is more natural in the case that a user is reading a page
// that scrolls up/down. Note that one, both or neither of these may be used
// at any instant.
const float X_SKATE_SIZE_MULTIPLIER = 3.0f;
const float Y_SKATE_SIZE_MULTIPLIER = 3.5f;
// The multiplier we apply to a dimension's length if it is stationary. We
// prefer to increase the size of the Y axis because it is more natural in the
// case that a user is reading a page that scrolls up/down. Note that one,
// both or neither of these may be used at any instant.
const float X_STATIONARY_SIZE_MULTIPLIER = 1.5f;
const float Y_STATIONARY_SIZE_MULTIPLIER = 2.5f;
// If we don't get an estimated paint duration, we probably don't have any
// data. In this case, we're dealing with either a stationary frame or a first
// paint. In either of these cases, we can just assume it'll take 1 second to
// paint. Getting this correct is not important anyways since it's only really
// useful when accelerating, which can't be happening at this point.
double estimatedPaintDuration =
aEstimatedPaintDuration > EPSILON ? aEstimatedPaintDuration : 1.0;
float scale = aFrameMetrics.mZoom.width;
nsIntRect compositionBounds = aFrameMetrics.mCompositionBounds;
compositionBounds.ScaleInverseRoundIn(scale);
const gfx::Rect& scrollableRect = aFrameMetrics.mScrollableRect;
gfx::Point scrollOffset = aFrameMetrics.mScrollOffset;
const float STATIONARY_SIZE_MULTIPLIER = 2.0f;
gfx::Rect displayPort(0, 0,
compositionBounds.width * STATIONARY_SIZE_MULTIPLIER,
compositionBounds.height * STATIONARY_SIZE_MULTIPLIER);
compositionBounds.width * X_STATIONARY_SIZE_MULTIPLIER,
compositionBounds.height * Y_STATIONARY_SIZE_MULTIPLIER);
// If there's motion along an axis of movement, and it's above a threshold,
// then we want to paint a larger area in the direction of that motion so that
// it's less likely to checkerboard.
bool enlargedX = EnlargeDisplayPortAlongAxis(
compositionBounds.width, aVelocity.x, &displayPort.x, &displayPort.width);
X_SKATE_SIZE_MULTIPLIER, estimatedPaintDuration,
compositionBounds.width, aVelocity.x, aAcceleration.x,
&displayPort.x, &displayPort.width);
bool enlargedY = EnlargeDisplayPortAlongAxis(
compositionBounds.height, aVelocity.y, &displayPort.y, &displayPort.height);
Y_SKATE_SIZE_MULTIPLIER, estimatedPaintDuration,
compositionBounds.height, aVelocity.y, aAcceleration.y,
&displayPort.y, &displayPort.height);
if (!enlargedX && !enlargedY) {
displayPort.x = -displayPort.width / 4;
displayPort.y = -displayPort.height / 4;
// Position the x and y such that the screen falls in the middle of the displayport.
displayPort.x = -(displayPort.width - compositionBounds.width) / 2;
displayPort.y = -(displayPort.height - compositionBounds.height) / 2;
} else if (!enlargedX) {
displayPort.width = compositionBounds.width;
} else if (!enlargedY) {
displayPort.height = compositionBounds.height;
}
// If we go over the bounds when trying to predict where we will be when this
// paint finishes, move it back into the range of the CSS content rect.
// FIXME/bug 780395: Generalize this. This code is pretty hacky as it will
// probably not work at all for RTL content. This is not intended to be
// incredibly accurate; it'll just prevent the entire displayport from being
// outside the content rect (which causes bad things to happen).
if (enlargedX || enlargedY) {
if (scrollOffset.x + compositionBounds.width > scrollableRect.width) {
scrollOffset.x -= compositionBounds.width + scrollOffset.x - scrollableRect.width;
} else if (scrollOffset.x < scrollableRect.x) {
scrollOffset.x = scrollableRect.x;
}
if (scrollOffset.y + compositionBounds.height > scrollableRect.height) {
scrollOffset.y -= compositionBounds.height + scrollOffset.y - scrollableRect.height;
} else if (scrollOffset.y < scrollableRect.y) {
scrollOffset.y = scrollableRect.y;
}
}
gfx::Rect shiftedDisplayPort = displayPort;
shiftedDisplayPort.MoveBy(scrollOffset.x, scrollOffset.y);
displayPort = shiftedDisplayPort.Intersect(aFrameMetrics.mScrollableRect);
@ -797,8 +879,23 @@ void AsyncPanZoomController::ScheduleComposite() {
}
void AsyncPanZoomController::RequestContentRepaint() {
mPreviousPaintStartTime = TimeStamp::Now();
double estimatedPaintSum = 0.0;
for (uint32_t i = 0; i < mPreviousPaintDurations.Length(); i++) {
estimatedPaintSum += mPreviousPaintDurations[i].ToSeconds();
}
double estimatedPaintDuration = 0.0;
if (estimatedPaintSum > EPSILON) {
estimatedPaintDuration = estimatedPaintSum / mPreviousPaintDurations.Length();
}
mFrameMetrics.mDisplayPort =
CalculatePendingDisplayPort(mFrameMetrics, GetVelocityVector());
CalculatePendingDisplayPort(mFrameMetrics,
GetVelocityVector(),
GetAccelerationVector(),
estimatedPaintDuration);
gfx::Point oldScrollOffset = mLastPaintRequestMetrics.mScrollOffset,
newScrollOffset = mFrameMetrics.mScrollOffset;
@ -819,12 +916,27 @@ void AsyncPanZoomController::RequestContentRepaint() {
return;
}
// Cache the resolution since we're temporarily changing it to accomodate
// mixed resolution/zoom (normally we make them the same thing).
float actualResolution = mFrameMetrics.mResolution.width;
// Calculate the factor of acceleration based on the faster of the two axes.
float accelerationFactor =
clamped(NS_MAX(mX.GetAccelerationFactor(), mY.GetAccelerationFactor()),
float(MIN_ZOOM) / 2.0f, float(MAX_ZOOM));
// Scale down the resolution a bit based on acceleration.
mFrameMetrics.mResolution.width = mFrameMetrics.mResolution.height =
actualResolution / accelerationFactor;
// This message is compressed, so fire whether or not we already have a paint
// queued up. We need to know whether or not a paint was requested anyways,
// ofr the purposes of content calling window.scrollTo().
mGeckoContentController->RequestContentRepaint(mFrameMetrics);
mLastPaintRequestMetrics = mFrameMetrics;
mWaitingForContentToPaint = true;
// Set the resolution back to what it was for the purpose of logic control.
mFrameMetrics.mResolution.width = mFrameMetrics.mResolution.height =
actualResolution;
}
bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSampleTime,
@ -931,7 +1043,16 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aViewportFr
mLastContentPaintMetrics = aViewportFrame;
if (!mWaitingForContentToPaint) {
if (mWaitingForContentToPaint) {
// Remove the oldest sample we have if adding a new sample takes us over our
// desired number of samples.
if (mPreviousPaintDurations.Length() >= NUM_PAINT_DURATION_SAMPLES) {
mPreviousPaintDurations.RemoveElementAt(0);
}
mPreviousPaintDurations.AppendElement(
TimeStamp::Now() - mPreviousPaintStartTime);
} else {
// No paint was requested, but we got one anyways. One possible cause of this
// is that content could have fired a scrollTo(). In this case, we should take
// the new scroll offset. Document/viewport changes are handled elsewhere.
@ -954,6 +1075,8 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aViewportFr
mWaitingForContentToPaint = false;
if (aIsFirstPaint || mFrameMetrics.IsDefault()) {
mPreviousPaintDurations.Clear();
mX.CancelTouch();
mY.CancelTouch();

View File

@ -205,7 +205,9 @@ public:
*/
static const gfx::Rect CalculatePendingDisplayPort(
const FrameMetrics& aFrameMetrics,
const gfx::Point& aVelocity);
const gfx::Point& aVelocity,
const gfx::Point& aAcceleration,
double aEstimatedPaintDuration);
protected:
/**
@ -333,6 +335,11 @@ protected:
*/
const gfx::Point GetVelocityVector();
/**
* Gets a vector of the acceleration factors of each axis.
*/
const gfx::Point GetAccelerationVector();
/**
* Gets a reference to the first SingleTouchData from a MultiTouchInput. This
* gets only the first one and assumes the rest are either missing or not
@ -365,8 +372,11 @@ protected:
* |aDisplayPortLength|. If enlarged, these will be updated with the new
* metrics.
*/
static bool EnlargeDisplayPortAlongAxis(float aCompositionBounds,
static bool EnlargeDisplayPortAlongAxis(float aSkateSizeMultiplier,
double aEstimatedPaintDuration,
float aCompositionBounds,
float aVelocity,
float aAcceleration,
float* aDisplayPortOffset,
float* aDisplayPortLength);
@ -496,6 +506,13 @@ private:
// |mMonitor|; that is, it should be held whenever this is updated.
PanZoomState mState;
// How long it took in the past to paint after a series of previous requests.
nsTArray<TimeDuration> mPreviousPaintDurations;
// When the last paint request started. Used to determine the duration of
// previous paints.
TimeStamp mPreviousPaintStartTime;
int mDPI;
// Stores the current paint status of the frame that we're managing. Repaints

View File

@ -18,7 +18,7 @@ static const float EPSILON = 0.0001f;
* or we get a touch point very far away from the previous position for some
* reason.
*/
static const float MAX_EVENT_ACCELERATION = 0.5f;
static const float MAX_EVENT_ACCELERATION = 999.0f;
/**
* Amount of friction applied during flings.
@ -95,15 +95,19 @@ void Axis::StartTouch(int32_t aPos) {
}
float Axis::GetDisplacementForDuration(float aScale, const TimeDuration& aDelta) {
float velocityFactor = powf(ACCELERATION_MULTIPLIER,
NS_MAX(0, (mAcceleration - 4) * 3));
float displacement = mVelocity * aScale * aDelta.ToMilliseconds() * velocityFactor;
if (fabsf(mVelocity) < VELOCITY_THRESHOLD) {
mAcceleration = 0;
}
float accelerationFactor = GetAccelerationFactor();
float displacement = mVelocity * aScale * aDelta.ToMilliseconds() * accelerationFactor;
// If this displacement will cause an overscroll, throttle it. Can potentially
// bring it to 0 even if the velocity is high.
if (DisplacementWillOverscroll(displacement) != OVERSCROLL_NONE) {
// No need to have a velocity along this axis anymore; it won't take us
// anywhere, so we're just spinning needlessly.
mVelocity = 0.0f;
mAcceleration = 0;
displacement -= DisplacementWillOverscrollAmount(displacement);
}
return displacement;
@ -231,6 +235,10 @@ float Axis::GetVelocity() {
return mVelocity;
}
float Axis::GetAccelerationFactor() {
return powf(ACCELERATION_MULTIPLIER, NS_MAX(0, (mAcceleration - 4) * 3));
}
float Axis::GetCompositionEnd() {
return GetOrigin() + GetCompositionLength();
}

View File

@ -116,6 +116,12 @@ public:
*/
float GetExcess();
/**
* Gets the factor of acceleration applied to the velocity, based on the
* amount of flings that have been done successively.
*/
float GetAccelerationFactor();
/**
* Gets the raw velocity of this axis at this moment.
*/