Bug 737553 - Refactor much of the code in DisplayPortCalculator and tune some of the behaviour. r=Cwiiis
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 30 Mar 2012 09:47:22 -0400
changeset 94041 10cf1144522fe8588bd4a58f17509f53ff24f2e3
parent 94040 ba83e928fec3f78c0c6cb70c11c1104c91c78b2a
child 94042 931fe44e63264f676c1e2811c6c0fd14882866ec
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersCwiiis
bugs737553
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 737553 - Refactor much of the code in DisplayPortCalculator and tune some of the behaviour. r=Cwiiis Commonly reused pieces of code are pulled out into reusable functions in DisplayPortCalculator. Reusing these functions makes the individual strategies' code much clearer and the similarities between them more apparent. One actual bug was fixed, where the display port resolution was incorrectly calculated in the DynamicResolutionStrategy. Prior to this change, it did not apply the reshaping algorithm to the pre-velocity display port size, resulting in a different resolution in some cases which would leave the content blurry. Additionally, the FixedMarginStrategy was modified so that its margins are a multiple of the view size rather than constant pixel values.
mobile/android/base/gfx/DisplayPortCalculator.java
--- a/mobile/android/base/gfx/DisplayPortCalculator.java
+++ b/mobile/android/base/gfx/DisplayPortCalculator.java
@@ -30,16 +30,88 @@ final class DisplayPortCalculator {
     private interface DisplayPortStrategy {
         /** Calculates a displayport given a viewport and panning velocity. */
         public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity);
         /** Returns true if a checkerboard is about to be visible and we should not throttle drawing. */
         public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort);
     }
 
     /**
+     * Return the dimensions for a rect that has area (width*height) that does not exceed the page size in the
+     * given metrics object. The area in the returned FloatSize may be less than width*height if the page is
+     * small, but it will never be larger than width*height.
+     * Note that this process may change the relative aspect ratio of the given dimensions.
+     */
+    private static FloatSize reshapeForPage(float width, float height, ImmutableViewportMetrics metrics) {
+        // figure out how much of the desired buffer amount we can actually use on the horizontal axis
+        float usableWidth = Math.min(width, metrics.pageSizeWidth);
+        // if we reduced the buffer amount on the horizontal axis, we should take that saved memory and
+        // use it on the vertical axis
+        float extraUsableHeight = (float)Math.floor(((width - usableWidth) * height) / usableWidth);
+        float usableHeight = Math.min(height + extraUsableHeight, metrics.pageSizeHeight);
+        if (usableHeight < height && usableWidth == width) {
+            // and the reverse - if we shrunk the buffer on the vertical axis we can add it to the horizontal
+            float extraUsableWidth = (float)Math.floor(((height - usableHeight) * width) / usableHeight);
+            usableWidth = Math.min(width + extraUsableWidth, metrics.pageSizeWidth);
+        }
+        return new FloatSize(usableWidth, usableHeight);
+    }
+
+    /**
+     * Expand the given rect in all directions by a "danger zone". The size of the danger zone on an axis
+     * is the size of the view on that axis multiplied by the given multiplier. The expanded rect is then
+     * clamped to page bounds and returned.
+     */
+    private static RectF expandByDangerZone(RectF rect, float dangerZoneXMultiplier, float dangerZoneYMultiplier, ImmutableViewportMetrics metrics) {
+        // calculate the danger zone amounts in pixels
+        float dangerZoneX = metrics.getWidth() * dangerZoneXMultiplier;
+        float dangerZoneY = metrics.getHeight() * dangerZoneYMultiplier;
+        rect = RectUtils.expand(rect, dangerZoneX, dangerZoneY);
+        // clamp to page bounds
+        if (rect.top < 0) rect.top = 0;
+        if (rect.left < 0) rect.left = 0;
+        if (rect.right > metrics.pageSizeWidth) rect.right = metrics.pageSizeWidth;
+        if (rect.bottom > metrics.pageSizeHeight) rect.bottom = metrics.pageSizeHeight;
+        return rect;
+    }
+
+    /**
+     * Adjust the given margins so if they are applied on the viewport in the metrics, the resulting rect
+     * does not exceed the page bounds. This code will maintain the total margin amount for a given axis;
+     * it assumes that margins.left + metrics.getWidth() + margins.right is less than or equal to
+     * metrics.pageSizeWidth; and the same for the y axis.
+     */
+    private static RectF shiftMarginsForPageBounds(RectF margins, ImmutableViewportMetrics metrics) {
+        // check how much we're overflowing in each direction. note that at most one of leftOverflow
+        // and rightOverflow can be greater than zero, and at most one of topOverflow and bottomOverflow
+        // can be greater than zero, because of the assumption described in the method javadoc.
+        float leftOverflow = margins.left - metrics.viewportRectLeft;
+        float rightOverflow = margins.right - (metrics.pageSizeWidth - metrics.viewportRectRight);
+        float topOverflow = margins.top - metrics.viewportRectTop;
+        float bottomOverflow = margins.bottom - (metrics.pageSizeHeight - metrics.viewportRectBottom);
+
+        // if the margins overflow the page bounds, shift them to other side on the same axis
+        if (leftOverflow > 0) {
+            margins.left -= leftOverflow;
+            margins.right += leftOverflow;
+        } else if (rightOverflow > 0) {
+            margins.right -= rightOverflow;
+            margins.left += rightOverflow;
+        }
+        if (topOverflow > 0) {
+            margins.top -= topOverflow;
+            margins.bottom += topOverflow;
+        } else if (bottomOverflow > 0) {
+            margins.bottom -= bottomOverflow;
+            margins.top += bottomOverflow;
+        }
+        return margins;
+    }
+
+    /**
      * This class implements the variation where we basically don't bother with a a display port.
      */
     private static class NoMarginStrategy implements DisplayPortStrategy {
         public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
             return new DisplayPortMetrics(metrics.viewportRectLeft,
                     metrics.viewportRectTop,
                     metrics.viewportRectRight,
                     metrics.viewportRectBottom,
@@ -55,165 +127,130 @@ final class DisplayPortCalculator {
      * This class implements the variation where we use a fixed-size margin on the display port.
      * The margin is always 300 pixels in all directions, except when we are (a) approaching a page
      * boundary, and/or (b) if we are limited by the page size. In these cases we try to maintain
      * the area of the display port by (a) shifting the buffer to the other side on the same axis,
      * and/or (b) increasing the buffer on the other axis to compensate for the reduced buffer on
      * one axis.
      */
     private static class FixedMarginStrategy implements DisplayPortStrategy {
-        private static final int DEFAULT_DISPLAY_PORT_MARGIN = 300;
+        // The length of each axis of the display port will be the corresponding view length
+        // multiplied by this factor.
+        private static final float SIZE_MULTIPLIER = 1.5f;
 
-        /* If the visible rect is within the danger zone (measured in pixels from each edge of a tile),
-         * we start aggressively redrawing to minimize checkerboarding. */
-        private static final int DANGER_ZONE_X = 75;
-        private static final int DANGER_ZONE_Y = 150;
+        // If the visible rect is within the danger zone (measured as a fraction of the view size
+        // from the edge of the displayport) we start redrawing to minimize checkerboarding.
+        private static final float DANGER_ZONE_X_MULTIPLIER = 0.10f;
+        private static final float DANGER_ZONE_Y_MULTIPLIER = 0.20f;
 
         public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
-            float desiredXMargins = 2 * DEFAULT_DISPLAY_PORT_MARGIN;
-            float desiredYMargins = 2 * DEFAULT_DISPLAY_PORT_MARGIN;
+            float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER;
+            float displayPortHeight = metrics.getHeight() * SIZE_MULTIPLIER;
 
             // we need to avoid having a display port that is larger than the page, or we will end up
             // painting things outside the page bounds (bug 729169). we simultaneously need to make
-            // the display port as large as possible so that we redraw less.
-
-            // figure out how much of the desired buffer amount we can actually use on the horizontal axis
-            float xBufferAmount = Math.min(desiredXMargins, metrics.pageSizeWidth - metrics.getWidth());
-            // if we reduced the buffer amount on the horizontal axis, we should take that saved memory and
-            // use it on the vertical axis
-            float savedPixels = (desiredXMargins - xBufferAmount) * (metrics.getHeight() + desiredYMargins);
-            float extraYAmount = (float)Math.floor(savedPixels / (metrics.getWidth() + xBufferAmount));
-            float yBufferAmount = Math.min(desiredYMargins + extraYAmount, metrics.pageSizeHeight - metrics.getHeight());
-            // and the reverse - if we shrunk the buffer on the vertical axis we can add it to the horizontal
-            if (xBufferAmount == desiredXMargins && yBufferAmount < desiredYMargins) {
-                savedPixels = (desiredYMargins - yBufferAmount) * (metrics.getWidth() + xBufferAmount);
-                float extraXAmount = (float)Math.floor(savedPixels / (metrics.getHeight() + yBufferAmount));
-                xBufferAmount = Math.min(xBufferAmount + extraXAmount, metrics.pageSizeWidth - metrics.getWidth());
-            }
+            // the display port as large as possible so that we redraw less. reshape the display
+            // port dimensions to accomplish this.
+            FloatSize usableSize = reshapeForPage(displayPortWidth, displayPortHeight, metrics);
+            float horizontalBuffer = usableSize.width - metrics.getWidth();
+            float verticalBuffer = usableSize.height - metrics.getHeight();
 
             // and now calculate the display port margins based on how much buffer we've decided to use and
             // the page bounds, ensuring we use all of the available buffer amounts on one side or the other
             // on any given axis. (i.e. if we're scrolled to the top of the page, the vertical buffer is
             // entirely below the visible viewport, but if we're halfway down the page, the vertical buffer
             // is split).
-            float leftMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.viewportRectLeft);
-            float rightMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.pageSizeWidth - (metrics.viewportRectLeft + metrics.getWidth()));
-            if (leftMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
-                rightMargin = xBufferAmount - leftMargin;
-            } else if (rightMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
-                leftMargin = xBufferAmount - rightMargin;
-            } else if (!FloatUtils.fuzzyEquals(leftMargin + rightMargin, xBufferAmount)) {
-                float delta = xBufferAmount - leftMargin - rightMargin;
-                leftMargin += delta / 2;
-                rightMargin += delta / 2;
-            }
-
-            float topMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.viewportRectTop);
-            float bottomMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.pageSizeHeight - (metrics.viewportRectTop + metrics.getHeight()));
-            if (topMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
-                bottomMargin = yBufferAmount - topMargin;
-            } else if (bottomMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
-                topMargin = yBufferAmount - bottomMargin;
-            } else if (!FloatUtils.fuzzyEquals(topMargin + bottomMargin, yBufferAmount)) {
-                float delta = yBufferAmount - topMargin - bottomMargin;
-                topMargin += delta / 2;
-                bottomMargin += delta / 2;
-            }
+            RectF margins = new RectF();
+            margins.left = horizontalBuffer / 2.0f;
+            margins.right = horizontalBuffer - margins.left;
+            margins.top = verticalBuffer / 2.0f;
+            margins.bottom = verticalBuffer - margins.top;
+            margins = shiftMarginsForPageBounds(margins, metrics);
 
             // note that unless the viewport size changes, or the page dimensions change (either because of
             // content changes or zooming), the size of the display port should remain constant. this
             // is intentional to avoid re-creating textures and all sorts of other reallocations in the
             // draw and composition code.
-            return new DisplayPortMetrics(metrics.viewportRectLeft - leftMargin,
-                    metrics.viewportRectTop - topMargin,
-                    metrics.viewportRectRight + rightMargin,
-                    metrics.viewportRectBottom + bottomMargin,
+            return new DisplayPortMetrics(metrics.viewportRectLeft - margins.left,
+                    metrics.viewportRectTop - margins.top,
+                    metrics.viewportRectRight + margins.right,
+                    metrics.viewportRectBottom + margins.bottom,
                     metrics.zoomFactor);
         }
 
         public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
-            // Increase the size of the viewport (and clamp to page boundaries), and
-            // intersect it with the tile's displayport to determine whether we're
+            // Increase the size of the viewport based on the danger zone multiplier (and clamp to page
+            // boundaries), and intersect it with the current displayport to determine whether we're
             // close to checkerboarding.
-            FloatSize pageSize = metrics.getPageSize();
-            RectF adjustedViewport = RectUtils.expand(metrics.getViewport(), DANGER_ZONE_X, DANGER_ZONE_Y);
-            if (adjustedViewport.top < 0) adjustedViewport.top = 0;
-            if (adjustedViewport.left < 0) adjustedViewport.left = 0;
-            if (adjustedViewport.right > pageSize.width) adjustedViewport.right = pageSize.width;
-            if (adjustedViewport.bottom > pageSize.height) adjustedViewport.bottom = pageSize.height;
-
+            RectF adjustedViewport = expandByDangerZone(metrics.getViewport(), DANGER_ZONE_X_MULTIPLIER, DANGER_ZONE_Y_MULTIPLIER, metrics);
             return !displayPort.contains(adjustedViewport);
         }
     }
 
     /**
      * This class implements the variation with a small fixed-size margin with velocity bias.
      * In this variation, the default margins are pretty small relative to the view size, but
      * they are affected by the panning velocity. Specifically, if we are panning on one axis,
      * we remove the margins on the other axis because we are likely axis-locked. Also once
      * we are panning in one direction above a certain threshold velocity, we shift the buffer
      * so that it is entirely in the direction of the pan.
      */
     private static class VelocityBiasStrategy implements DisplayPortStrategy {
-        /* The size of the margin as a fraction of view size */
-        private static final float SIZE_MULTIPLIER = 0.2f;
-        /* The velocity above which we apply the velocity bias */
+        // The length of each axis of the display port will be the corresponding view length
+        // multiplied by this factor.
+        private static final float SIZE_MULTIPLIER = 1.2f;
+        // The velocity above which we apply the velocity bias
         private static final float VELOCITY_THRESHOLD = GeckoAppShell.getDpi() / 32f;
 
         public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
-            // by default we apply margins that are a fraction of the view size
-            float desiredXMargins = metrics.getWidth() * SIZE_MULTIPLIER;
-            float desiredYMargins = metrics.getHeight() * SIZE_MULTIPLIER;
+            float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER;
+            float displayPortHeight = metrics.getHeight() * SIZE_MULTIPLIER;
 
             // but if we're panning on one axis, set the margins for the other axis to zero since we are likely
             // axis locked and won't be displaying that extra area.
             if (Math.abs(velocity.x) > VELOCITY_THRESHOLD && FloatUtils.fuzzyEquals(velocity.y, 0)) {
-                desiredYMargins = 0;
+                displayPortHeight = metrics.getHeight();
             } else if (Math.abs(velocity.y) > VELOCITY_THRESHOLD && FloatUtils.fuzzyEquals(velocity.x, 0)) {
-                desiredXMargins = 0;
+                displayPortWidth = metrics.getWidth();
             }
 
             // we need to avoid having a display port that is larger than the page, or we will end up
             // painting things outside the page bounds (bug 729169).
+            displayPortWidth = Math.min(displayPortWidth, metrics.pageSizeWidth);
+            displayPortHeight = Math.min(displayPortHeight, metrics.pageSizeHeight);
+            float horizontalBuffer = displayPortWidth - metrics.getWidth();
+            float verticalBuffer = displayPortHeight - metrics.getHeight();
 
-            // figure out how much of the desired buffer amount we can actually use on the two axes
-            float xBufferAmount = Math.min(desiredXMargins, metrics.pageSizeWidth - metrics.getWidth());
-            float yBufferAmount = Math.min(desiredYMargins, metrics.pageSizeHeight - metrics.getHeight());
-
-            // if we're panning above the VELOCITY_THRESHOLD on an axis, shift the margin so that it
+            // if we're panning above the VELOCITY_THRESHOLD on an axis, apply the margin so that it
             // is entirely in the direction of panning. Otherwise, split the margin evenly on both sides of
             // the display port.
-            float leftMargin, rightMargin;
+            RectF margins = new RectF();
             if (velocity.x > VELOCITY_THRESHOLD) {
-                rightMargin = Math.min(xBufferAmount, metrics.pageSizeWidth - (metrics.viewportRectLeft + metrics.getWidth()));
-                leftMargin = xBufferAmount - rightMargin;
+                margins.right = horizontalBuffer;
             } else if (velocity.x < -VELOCITY_THRESHOLD) {
-                leftMargin = Math.min(xBufferAmount, metrics.viewportRectLeft);
-                rightMargin = xBufferAmount - leftMargin;
+                margins.left = horizontalBuffer;
             } else {
-                leftMargin = Math.min(xBufferAmount / 2.0f, metrics.viewportRectLeft);
-                rightMargin = xBufferAmount - leftMargin;
+                margins.left = horizontalBuffer / 2.0f;
+                margins.right = horizontalBuffer - margins.left;
             }
-
-            float topMargin, bottomMargin;
             if (velocity.y > VELOCITY_THRESHOLD) {
-                bottomMargin = Math.min(yBufferAmount, metrics.pageSizeHeight - (metrics.viewportRectTop + metrics.getHeight()));
-                topMargin = yBufferAmount - bottomMargin;
+                margins.bottom = verticalBuffer;
             } else if (velocity.y < -VELOCITY_THRESHOLD) {
-                topMargin = Math.min(yBufferAmount, metrics.viewportRectTop);
-                bottomMargin = yBufferAmount - topMargin;
+                margins.top = verticalBuffer;
             } else {
-                topMargin = Math.min(yBufferAmount / 2.0f, metrics.viewportRectTop);
-                bottomMargin = yBufferAmount - topMargin;
+                margins.top = verticalBuffer / 2.0f;
+                margins.bottom = verticalBuffer - margins.top;
             }
+            // and finally shift the margins to account for page bounds
+            margins = shiftMarginsForPageBounds(margins, metrics);
 
-            return new DisplayPortMetrics(metrics.viewportRectLeft - leftMargin,
-                    metrics.viewportRectTop - topMargin,
-                    metrics.viewportRectRight + rightMargin,
-                    metrics.viewportRectBottom + bottomMargin,
+            return new DisplayPortMetrics(metrics.viewportRectLeft - margins.left,
+                    metrics.viewportRectTop - margins.top,
+                    metrics.viewportRectRight + margins.right,
+                    metrics.viewportRectBottom + margins.bottom,
                     metrics.zoomFactor);
         }
 
         public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
             // Since we have such a small margin, we want to be drawing more aggressively. At the start of a
             // pan the velocity is going to be large so we're almost certainly going to go into checkerboard
             // on every frame, so drawing all the time seems like the right thing. At the end of the pan we
             // want to re-center the displayport and draw stuff on all sides, so again we don't want to throttle
@@ -272,108 +309,88 @@ final class DisplayPortCalculator {
         // in the previous sentence really refers to the amount of time it would take to draw and
         // composite from the point at which we do the calculation, and that is not really a known
         // quantity. The velocity multiplier is how much we multiply the velocity by; it has the
         // same caveats as the VELOCITY_MULTIPLIER above except that it only needs to take into account
         // one draw/composite cycle instead of two. The danger zone multiplier is a multiplier of the
         // viewport size that we use as an extra "danger zone" around the viewport; if this danger
         // zone falls outside the display port then we are approaching the point at which we will
         // checkerboard, and hence should start drawing. Note that if DANGER_ZONE_MULTIPLIER is
-        // greater than (SIZE_MULTIPLIER - 1.0f) / 2, then at zero velocity we will always be in the
+        // greater than (SIZE_MULTIPLIER - 1.0f), then at zero velocity we will always be in the
         // danger zone, and thus will be constantly drawing.
         private static final float PREDICTION_VELOCITY_MULTIPLIER = 30.0f;
-        private static final float DANGER_ZONE_MULTIPLIER = 0.10f; // must be less than (SIZE_MULTIPLIER - 1.0f) / 2
+        private static final float DANGER_ZONE_MULTIPLIER = 0.20f; // must be less than (SIZE_MULTIPLIER - 1.0f)
 
         public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
-            float baseWidth = metrics.getWidth() * SIZE_MULTIPLIER;
-            float baseHeight = metrics.getHeight() * SIZE_MULTIPLIER;
+            float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER;
+            float displayPortHeight = metrics.getHeight() * SIZE_MULTIPLIER;
 
-            float width = baseWidth;
-            float height = baseHeight;
+            // for resolution calculation purposes, we need to know what the adjusted display port dimensions
+            // would be if we had zero velocity, so calculate that here before we increase the display port
+            // based on velocity.
+            FloatSize reshapedSize = reshapeForPage(displayPortWidth, displayPortHeight, metrics);
+
+            // increase displayPortWidth and displayPortHeight based on the velocity, but maintaining their
+            // relative aspect ratio.
             if (velocity.length() > VELOCITY_EXPANSION_THRESHOLD) {
-                // increase width and height based on the velocity, but maintaining aspect ratio.
-                float velocityFactor = Math.max(Math.abs(velocity.x) / width,
-                                                Math.abs(velocity.y) / height);
+                float velocityFactor = Math.max(Math.abs(velocity.x) / displayPortWidth,
+                                                Math.abs(velocity.y) / displayPortHeight);
                 velocityFactor *= VELOCITY_MULTIPLIER;
 
-                width += (width * velocityFactor);
-                height += (height * velocityFactor);
+                displayPortWidth += (displayPortWidth * velocityFactor);
+                displayPortHeight += (displayPortHeight * velocityFactor);
             }
 
-            // at this point, width and height are how much of the page (in device pixels) we want to
-            // be rendered by Gecko. Note here "device pixels" is equivalent to CSS pixels multiplied
+            // at this point, displayPortWidth and displayPortHeight are how much of the page (in device pixels)
+            // we want to be rendered by Gecko. Note here "device pixels" is equivalent to CSS pixels multiplied
             // by metrics.zoomFactor
 
             // we need to avoid having a display port that is larger than the page, or we will end up
             // painting things outside the page bounds (bug 729169). we simultaneously need to make
-            // the display port as large as possible so that we redraw less.
-            //
-            // so, first we figure out how much of the desired amount we can actually use on the horizontal axis,
-            // and transfer any unused amount to the vertical axis. Then we see if we have unused amounts on
-            // the vertical axis and transfer them back (if possible) to the horizontal axis.
-            //
-            // Note that this process may throw the aspect ratio out of whack, possibly requiring various buffers
-            // in Gecko and/or the compositor to be reallocated. Therefore it is not strictly desirable, but for
-            // now we assume that the benefits outweigh the disadvantages.
-            float usableWidth = Math.min(width, metrics.pageSizeWidth);
-            float extraUsableHeight = ((width - usableWidth) * height) / usableWidth;
-            float usableHeight = Math.min(height + extraUsableHeight, metrics.pageSizeHeight);
-            if (usableHeight < height && usableWidth == width) {
-                float extraUsableWidth = ((height - usableHeight) * width) / usableHeight;
-                usableWidth = Math.min(width + extraUsableWidth, metrics.pageSizeWidth);
-            }
+            // the display port as large as possible so that we redraw less. reshape the display
+            // port dimensions to accomplish this. this may change the aspect ratio of the display port,
+            // but we are assuming that this is desirable because the advantages from pre-drawing will
+            // outweigh the disadvantages from any buffer reallocations that might occur.
+            FloatSize usableSize = reshapeForPage(displayPortWidth, displayPortHeight, metrics);
+            float horizontalBuffer = usableSize.width - metrics.getWidth();
+            float verticalBuffer = usableSize.height - metrics.getHeight();
 
-            // now that we know how large the display port is, we allocate the area to the four sides as margins.
-            // this ensures we use all of the available display port area on one side or the other of any given axis
-            // in a desirable manner.
-
-            float horizontalBuffer = usableWidth - metrics.getWidth();
-            float verticalBuffer = usableHeight - metrics.getHeight();
-            // first we split the buffer amount based on the direction we're moving, so that we have a larger buffer
-            // in the direction of travel.
-            float leftMargin = splitBufferByVelocity(horizontalBuffer, velocity.x);
-            float rightMargin = horizontalBuffer - leftMargin;
-            float topMargin = splitBufferByVelocity(verticalBuffer, velocity.y);
-            float bottomMargin = verticalBuffer - topMargin;
+            // at this point, horizontalBuffer and verticalBuffer are the dimensions of the buffer area we have.
+            // the buffer area is the off-screen area that is part of the display port and will be pre-drawn in case
+            // the user scrolls there. we now need to split the buffer area on each axis so that we know
+            // what the exact margins on each side will be. first we split the buffer amount based on the direction
+            // we're moving, so that we have a larger buffer in the direction of travel.
+            RectF margins = new RectF();
+            margins.left = splitBufferByVelocity(horizontalBuffer, velocity.x);
+            margins.right = horizontalBuffer - margins.left;
+            margins.top = splitBufferByVelocity(verticalBuffer, velocity.y);
+            margins.bottom = verticalBuffer - margins.top;
 
             // then, we account for running into the page bounds - so that if we hit the top of the page, we need
             // to drop the top margin and move that amount to the bottom margin.
-            if (metrics.viewportRectLeft - leftMargin < 0) {
-                leftMargin = metrics.viewportRectLeft;
-                rightMargin = horizontalBuffer - leftMargin;
-            } else if (metrics.viewportRectRight + rightMargin > metrics.pageSizeWidth) {
-                rightMargin = metrics.pageSizeWidth - metrics.viewportRectRight;
-                leftMargin = horizontalBuffer - rightMargin;
-            }
-            if (metrics.viewportRectTop - topMargin < 0) {
-                topMargin = metrics.viewportRectTop;
-                bottomMargin = verticalBuffer - topMargin;
-            } else if (metrics.viewportRectBottom + bottomMargin > metrics.pageSizeHeight) {
-                bottomMargin = metrics.pageSizeHeight - metrics.viewportRectBottom;
-                topMargin = verticalBuffer - bottomMargin;
-            }
+            margins = shiftMarginsForPageBounds(margins, metrics);
 
             // finally, we calculate the resolution we want to render the display port area at. We do this
             // so that as we expand the display port area (because of velocity), we reduce the resolution of
-            // the painted area so as to maintain the size of the buffer Gecko is painting into (assuming no
-            // aspect ratio changes as described above). if the velocity is zero, then the displayResolution
-            // must be equal to metrics.zoomFactor.
+            // the painted area so as to maintain the size of the buffer Gecko is painting into. we calculate
+            // the reduction in resolution by comparing the display port size with and without the velocity
+            // changes applied.
             // this effectively means that as we pan faster and faster, the display port grows, but we paint
             // at lower resolutions. this paints more area to reduce checkerboard at the cost of increasing
             // compositor-scaling and blurriness. Once we stop panning, the blurriness must be entirely gone.
             // Note that usable* could be less than base* if we are pinch-zoomed out into overscroll, so we
             // clamp it to make sure this doesn't increase our display resolution past metrics.zoomFactor.
-            float scaleFactor = Math.min(baseWidth / usableWidth, baseHeight / usableHeight);
+            float scaleFactor = Math.min(reshapedSize.width / usableSize.width, reshapedSize.height / usableSize.height);
             float displayResolution = metrics.zoomFactor * Math.min(1.0f, scaleFactor);
 
             DisplayPortMetrics dpMetrics = new DisplayPortMetrics(
-                metrics.viewportRectLeft - leftMargin,
-                metrics.viewportRectTop - topMargin,
-                metrics.viewportRectRight + rightMargin,
-                metrics.viewportRectBottom + bottomMargin,
+                metrics.viewportRectLeft - margins.left,
+                metrics.viewportRectTop - margins.top,
+                metrics.viewportRectRight + margins.right,
+                metrics.viewportRectBottom + margins.bottom,
                 displayResolution);
             return dpMetrics;
         }
 
         /**
          * Split the given buffer amount into two based on the velocity.
          * Given an amount of total usable buffer on an axis, this will
          * return the amount that should be used on the left/top side of
@@ -401,50 +418,33 @@ final class DisplayPortCalculator {
             }
         }
 
         public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
             // Expand the viewport based on our velocity (and clamp it to page boundaries).
             // Then intersect it with the last-requested displayport to determine whether we're
             // close to checkerboarding.
 
-            float left = metrics.viewportRectLeft;
-            float right = metrics.viewportRectRight;
-            float top = metrics.viewportRectTop;
-            float bottom = metrics.viewportRectBottom;
+            RectF predictedViewport = metrics.getViewport();
 
             // first we expand the viewport in the direction we're moving based on some
             // multiple of the current velocity.
             if (velocity.length() > 0) {
                 if (velocity.x < 0) {
-                    left += velocity.x * PREDICTION_VELOCITY_MULTIPLIER;
+                    predictedViewport.left += velocity.x * PREDICTION_VELOCITY_MULTIPLIER;
                 } else if (velocity.x > 0) {
-                    right += velocity.x * PREDICTION_VELOCITY_MULTIPLIER;
+                    predictedViewport.right += velocity.x * PREDICTION_VELOCITY_MULTIPLIER;
                 }
 
                 if (velocity.y < 0) {
-                    top += velocity.y * PREDICTION_VELOCITY_MULTIPLIER;
+                    predictedViewport.top += velocity.y * PREDICTION_VELOCITY_MULTIPLIER;
                 } else if (velocity.y > 0) {
-                    bottom += velocity.y * PREDICTION_VELOCITY_MULTIPLIER;
+                    predictedViewport.bottom += velocity.y * PREDICTION_VELOCITY_MULTIPLIER;
                 }
             }
 
             // then we expand the viewport evenly in all directions just to have an extra
-            // safety zone.
-            float dangerZoneX = metrics.getWidth() * DANGER_ZONE_MULTIPLIER;
-            float dangerZoneY = metrics.getHeight() * DANGER_ZONE_MULTIPLIER;
-            left -= dangerZoneX;
-            top -= dangerZoneY;
-            right += dangerZoneX;
-            bottom += dangerZoneY;
-
-            // finally, we clamp the calculated viewport to the page bounds, since we will
-            // never checkerboard outside of the page bounds.
-            if (left < 0) left = 0;
-            if (top < 0) top = 0;
-            if (right > metrics.pageSizeWidth) right = metrics.pageSizeWidth;
-            if (bottom > metrics.pageSizeHeight) bottom = metrics.pageSizeHeight;
-
-            RectF predictedViewport = new RectF(left, top, right, bottom);
+            // safety zone. this also clamps it to page bounds.
+            predictedViewport = expandByDangerZone(predictedViewport, DANGER_ZONE_MULTIPLIER, DANGER_ZONE_MULTIPLIER, metrics);
             return !displayPort.contains(predictedViewport);
         }
     }
 }