mobile/android/base/gfx/DisplayPortCalculator.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/gfx/DisplayPortCalculator.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,776 @@
     1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.7 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +package org.mozilla.gecko.gfx;
    1.10 +
    1.11 +import org.mozilla.gecko.GeckoAppShell;
    1.12 +import org.mozilla.gecko.PrefsHelper;
    1.13 +import org.mozilla.gecko.util.FloatUtils;
    1.14 +
    1.15 +import org.json.JSONArray;
    1.16 +
    1.17 +import android.graphics.PointF;
    1.18 +import android.graphics.RectF;
    1.19 +import android.util.FloatMath;
    1.20 +import android.util.Log;
    1.21 +
    1.22 +import java.util.HashMap;
    1.23 +import java.util.Map;
    1.24 +
    1.25 +final class DisplayPortCalculator {
    1.26 +    private static final String LOGTAG = "GeckoDisplayPort";
    1.27 +    private static final PointF ZERO_VELOCITY = new PointF(0, 0);
    1.28 +
    1.29 +    // Keep this in sync with the TILEDLAYERBUFFER_TILE_SIZE defined in gfx/layers/TiledLayerBuffer.h
    1.30 +    private static final int TILE_SIZE = 256;
    1.31 +
    1.32 +    private static final String PREF_DISPLAYPORT_STRATEGY = "gfx.displayport.strategy";
    1.33 +    private static final String PREF_DISPLAYPORT_FM_MULTIPLIER = "gfx.displayport.strategy_fm.multiplier";
    1.34 +    private static final String PREF_DISPLAYPORT_FM_DANGER_X = "gfx.displayport.strategy_fm.danger_x";
    1.35 +    private static final String PREF_DISPLAYPORT_FM_DANGER_Y = "gfx.displayport.strategy_fm.danger_y";
    1.36 +    private static final String PREF_DISPLAYPORT_VB_MULTIPLIER = "gfx.displayport.strategy_vb.multiplier";
    1.37 +    private static final String PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD = "gfx.displayport.strategy_vb.threshold";
    1.38 +    private static final String PREF_DISPLAYPORT_VB_REVERSE_BUFFER = "gfx.displayport.strategy_vb.reverse_buffer";
    1.39 +    private static final String PREF_DISPLAYPORT_VB_DANGER_X_BASE = "gfx.displayport.strategy_vb.danger_x_base";
    1.40 +    private static final String PREF_DISPLAYPORT_VB_DANGER_Y_BASE = "gfx.displayport.strategy_vb.danger_y_base";
    1.41 +    private static final String PREF_DISPLAYPORT_VB_DANGER_X_INCR = "gfx.displayport.strategy_vb.danger_x_incr";
    1.42 +    private static final String PREF_DISPLAYPORT_VB_DANGER_Y_INCR = "gfx.displayport.strategy_vb.danger_y_incr";
    1.43 +    private static final String PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD = "gfx.displayport.strategy_pb.threshold";
    1.44 +
    1.45 +    private static DisplayPortStrategy sStrategy = new VelocityBiasStrategy(null);
    1.46 +
    1.47 +    static DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
    1.48 +        return sStrategy.calculate(metrics, (velocity == null ? ZERO_VELOCITY : velocity));
    1.49 +    }
    1.50 +
    1.51 +    static boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
    1.52 +        if (displayPort == null) {
    1.53 +            return true;
    1.54 +        }
    1.55 +        return sStrategy.aboutToCheckerboard(metrics, (velocity == null ? ZERO_VELOCITY : velocity), displayPort);
    1.56 +    }
    1.57 +
    1.58 +    static boolean drawTimeUpdate(long millis, int pixels) {
    1.59 +        return sStrategy.drawTimeUpdate(millis, pixels);
    1.60 +    }
    1.61 +
    1.62 +    static void resetPageState() {
    1.63 +        sStrategy.resetPageState();
    1.64 +    }
    1.65 +
    1.66 +    static void initPrefs() {
    1.67 +        final String[] prefs = { PREF_DISPLAYPORT_STRATEGY,
    1.68 +                                 PREF_DISPLAYPORT_FM_MULTIPLIER,
    1.69 +                                 PREF_DISPLAYPORT_FM_DANGER_X,
    1.70 +                                 PREF_DISPLAYPORT_FM_DANGER_Y,
    1.71 +                                 PREF_DISPLAYPORT_VB_MULTIPLIER,
    1.72 +                                 PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD,
    1.73 +                                 PREF_DISPLAYPORT_VB_REVERSE_BUFFER,
    1.74 +                                 PREF_DISPLAYPORT_VB_DANGER_X_BASE,
    1.75 +                                 PREF_DISPLAYPORT_VB_DANGER_Y_BASE,
    1.76 +                                 PREF_DISPLAYPORT_VB_DANGER_X_INCR,
    1.77 +                                 PREF_DISPLAYPORT_VB_DANGER_Y_INCR,
    1.78 +                                 PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD };
    1.79 +
    1.80 +        PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
    1.81 +            private Map<String, Integer> mValues = new HashMap<String, Integer>();
    1.82 +
    1.83 +            @Override public void prefValue(String pref, int value) {
    1.84 +                mValues.put(pref, value);
    1.85 +            }
    1.86 +
    1.87 +            @Override public void finish() {
    1.88 +                setStrategy(mValues);
    1.89 +            }
    1.90 +        });
    1.91 +    }
    1.92 +
    1.93 +    /**
    1.94 +     * Set the active strategy to use.
    1.95 +     * See the gfx.displayport.strategy pref in mobile/android/app/mobile.js to see the
    1.96 +     * mapping between ints and strategies.
    1.97 +     */
    1.98 +    static boolean setStrategy(Map<String, Integer> prefs) {
    1.99 +        Integer strategy = prefs.get(PREF_DISPLAYPORT_STRATEGY);
   1.100 +        if (strategy == null) {
   1.101 +            return false;
   1.102 +        }
   1.103 +
   1.104 +        switch (strategy) {
   1.105 +            case 0:
   1.106 +                sStrategy = new FixedMarginStrategy(prefs);
   1.107 +                break;
   1.108 +            case 1:
   1.109 +                sStrategy = new VelocityBiasStrategy(prefs);
   1.110 +                break;
   1.111 +            case 2:
   1.112 +                sStrategy = new DynamicResolutionStrategy(prefs);
   1.113 +                break;
   1.114 +            case 3:
   1.115 +                sStrategy = new NoMarginStrategy(prefs);
   1.116 +                break;
   1.117 +            case 4:
   1.118 +                sStrategy = new PredictionBiasStrategy(prefs);
   1.119 +                break;
   1.120 +            default:
   1.121 +                Log.e(LOGTAG, "Invalid strategy index specified");
   1.122 +                return false;
   1.123 +        }
   1.124 +        Log.i(LOGTAG, "Set strategy " + sStrategy.toString());
   1.125 +        return true;
   1.126 +    }
   1.127 +
   1.128 +    private static float getFloatPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
   1.129 +        Integer value = (prefs == null ? null : prefs.get(prefName));
   1.130 +        return (float)(value == null || value < 0 ? defaultValue : value) / 1000f;
   1.131 +    }
   1.132 +
   1.133 +    private static abstract class DisplayPortStrategy {
   1.134 +        /** Calculates a displayport given a viewport and panning velocity. */
   1.135 +        public abstract DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity);
   1.136 +        /** Returns true if a checkerboard is about to be visible and we should not throttle drawing. */
   1.137 +        public abstract boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort);
   1.138 +        /** Notify the strategy of a new recorded draw time. Return false to turn off draw time recording. */
   1.139 +        public boolean drawTimeUpdate(long millis, int pixels) { return false; }
   1.140 +        /** Reset any page-specific state stored, as the page being displayed has changed. */
   1.141 +        public void resetPageState() {}
   1.142 +    }
   1.143 +
   1.144 +    /**
   1.145 +     * Return the dimensions for a rect that has area (width*height) that does not exceed the page size in the
   1.146 +     * given metrics object. The area in the returned FloatSize may be less than width*height if the page is
   1.147 +     * small, but it will never be larger than width*height.
   1.148 +     * Note that this process may change the relative aspect ratio of the given dimensions.
   1.149 +     */
   1.150 +    private static FloatSize reshapeForPage(float width, float height, ImmutableViewportMetrics metrics) {
   1.151 +        // figure out how much of the desired buffer amount we can actually use on the horizontal axis
   1.152 +        float usableWidth = Math.min(width, metrics.getPageWidth());
   1.153 +        // if we reduced the buffer amount on the horizontal axis, we should take that saved memory and
   1.154 +        // use it on the vertical axis
   1.155 +        float extraUsableHeight = (float)Math.floor(((width - usableWidth) * height) / usableWidth);
   1.156 +        float usableHeight = Math.min(height + extraUsableHeight, metrics.getPageHeight());
   1.157 +        if (usableHeight < height && usableWidth == width) {
   1.158 +            // and the reverse - if we shrunk the buffer on the vertical axis we can add it to the horizontal
   1.159 +            float extraUsableWidth = (float)Math.floor(((height - usableHeight) * width) / usableHeight);
   1.160 +            usableWidth = Math.min(width + extraUsableWidth, metrics.getPageWidth());
   1.161 +        }
   1.162 +        return new FloatSize(usableWidth, usableHeight);
   1.163 +    }
   1.164 +
   1.165 +    /**
   1.166 +     * Expand the given rect in all directions by a "danger zone". The size of the danger zone on an axis
   1.167 +     * is the size of the view on that axis multiplied by the given multiplier. The expanded rect is then
   1.168 +     * clamped to page bounds and returned.
   1.169 +     */
   1.170 +    private static RectF expandByDangerZone(RectF rect, float dangerZoneXMultiplier, float dangerZoneYMultiplier, ImmutableViewportMetrics metrics) {
   1.171 +        // calculate the danger zone amounts in pixels
   1.172 +        float dangerZoneX = metrics.getWidth() * dangerZoneXMultiplier;
   1.173 +        float dangerZoneY = metrics.getHeight() * dangerZoneYMultiplier;
   1.174 +        rect = RectUtils.expand(rect, dangerZoneX, dangerZoneY);
   1.175 +        // clamp to page bounds
   1.176 +        return clampToPageBounds(rect, metrics);
   1.177 +    }
   1.178 +
   1.179 +    /**
   1.180 +     * Expand the given margins such that when they are applied on the viewport, the resulting rect
   1.181 +     * does not have any partial tiles, except when it is clipped by the page bounds. This assumes
   1.182 +     * the tiles are TILE_SIZE by TILE_SIZE and start at the origin, such that there will always be
   1.183 +     * a tile at (0,0)-(TILE_SIZE,TILE_SIZE)).
   1.184 +     */
   1.185 +    private static DisplayPortMetrics getTileAlignedDisplayPortMetrics(RectF margins, float zoom, ImmutableViewportMetrics metrics) {
   1.186 +        float left = metrics.viewportRectLeft - margins.left;
   1.187 +        float top = metrics.viewportRectTop - margins.top;
   1.188 +        float right = metrics.viewportRectRight + margins.right;
   1.189 +        float bottom = metrics.viewportRectBottom + margins.bottom;
   1.190 +        left = Math.max(metrics.pageRectLeft, TILE_SIZE * FloatMath.floor(left / TILE_SIZE));
   1.191 +        top = Math.max(metrics.pageRectTop, TILE_SIZE * FloatMath.floor(top / TILE_SIZE));
   1.192 +        right = Math.min(metrics.pageRectRight, TILE_SIZE * FloatMath.ceil(right / TILE_SIZE));
   1.193 +        bottom = Math.min(metrics.pageRectBottom, TILE_SIZE * FloatMath.ceil(bottom / TILE_SIZE));
   1.194 +        return new DisplayPortMetrics(left, top, right, bottom, zoom);
   1.195 +    }
   1.196 +
   1.197 +    /**
   1.198 +     * Adjust the given margins so if they are applied on the viewport in the metrics, the resulting rect
   1.199 +     * does not exceed the page bounds. This code will maintain the total margin amount for a given axis;
   1.200 +     * it assumes that margins.left + metrics.getWidth() + margins.right is less than or equal to
   1.201 +     * metrics.getPageWidth(); and the same for the y axis.
   1.202 +     */
   1.203 +    private static RectF shiftMarginsForPageBounds(RectF margins, ImmutableViewportMetrics metrics) {
   1.204 +        // check how much we're overflowing in each direction. note that at most one of leftOverflow
   1.205 +        // and rightOverflow can be greater than zero, and at most one of topOverflow and bottomOverflow
   1.206 +        // can be greater than zero, because of the assumption described in the method javadoc.
   1.207 +        float leftOverflow = metrics.pageRectLeft - (metrics.viewportRectLeft - margins.left);
   1.208 +        float rightOverflow = (metrics.viewportRectRight + margins.right) - metrics.pageRectRight;
   1.209 +        float topOverflow = metrics.pageRectTop - (metrics.viewportRectTop - margins.top);
   1.210 +        float bottomOverflow = (metrics.viewportRectBottom + margins.bottom) - metrics.pageRectBottom;
   1.211 +
   1.212 +        // if the margins overflow the page bounds, shift them to other side on the same axis
   1.213 +        if (leftOverflow > 0) {
   1.214 +            margins.left -= leftOverflow;
   1.215 +            margins.right += leftOverflow;
   1.216 +        } else if (rightOverflow > 0) {
   1.217 +            margins.right -= rightOverflow;
   1.218 +            margins.left += rightOverflow;
   1.219 +        }
   1.220 +        if (topOverflow > 0) {
   1.221 +            margins.top -= topOverflow;
   1.222 +            margins.bottom += topOverflow;
   1.223 +        } else if (bottomOverflow > 0) {
   1.224 +            margins.bottom -= bottomOverflow;
   1.225 +            margins.top += bottomOverflow;
   1.226 +        }
   1.227 +        return margins;
   1.228 +    }
   1.229 +
   1.230 +    /**
   1.231 +     * Clamp the given rect to the page bounds and return it.
   1.232 +     */
   1.233 +    private static RectF clampToPageBounds(RectF rect, ImmutableViewportMetrics metrics) {
   1.234 +        if (rect.top < metrics.pageRectTop) rect.top = metrics.pageRectTop;
   1.235 +        if (rect.left < metrics.pageRectLeft) rect.left = metrics.pageRectLeft;
   1.236 +        if (rect.right > metrics.pageRectRight) rect.right = metrics.pageRectRight;
   1.237 +        if (rect.bottom > metrics.pageRectBottom) rect.bottom = metrics.pageRectBottom;
   1.238 +        return rect;
   1.239 +    }
   1.240 +
   1.241 +    /**
   1.242 +     * This class implements the variation where we basically don't bother with a a display port.
   1.243 +     */
   1.244 +    private static class NoMarginStrategy extends DisplayPortStrategy {
   1.245 +        NoMarginStrategy(Map<String, Integer> prefs) {
   1.246 +            // no prefs in this strategy
   1.247 +        }
   1.248 +
   1.249 +        @Override
   1.250 +        public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
   1.251 +            return new DisplayPortMetrics(metrics.viewportRectLeft,
   1.252 +                    metrics.viewportRectTop,
   1.253 +                    metrics.viewportRectRight,
   1.254 +                    metrics.viewportRectBottom,
   1.255 +                    metrics.zoomFactor);
   1.256 +        }
   1.257 +
   1.258 +        @Override
   1.259 +        public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
   1.260 +            return true;
   1.261 +        }
   1.262 +
   1.263 +        @Override
   1.264 +        public String toString() {
   1.265 +            return "NoMarginStrategy";
   1.266 +        }
   1.267 +    }
   1.268 +
   1.269 +    /**
   1.270 +     * This class implements the variation where we use a fixed-size margin on the display port.
   1.271 +     * The margin is always 300 pixels in all directions, except when we are (a) approaching a page
   1.272 +     * boundary, and/or (b) if we are limited by the page size. In these cases we try to maintain
   1.273 +     * the area of the display port by (a) shifting the buffer to the other side on the same axis,
   1.274 +     * and/or (b) increasing the buffer on the other axis to compensate for the reduced buffer on
   1.275 +     * one axis.
   1.276 +     */
   1.277 +    private static class FixedMarginStrategy extends DisplayPortStrategy {
   1.278 +        // The length of each axis of the display port will be the corresponding view length
   1.279 +        // multiplied by this factor.
   1.280 +        private final float SIZE_MULTIPLIER;
   1.281 +
   1.282 +        // If the visible rect is within the danger zone (measured as a fraction of the view size
   1.283 +        // from the edge of the displayport) we start redrawing to minimize checkerboarding.
   1.284 +        private final float DANGER_ZONE_X_MULTIPLIER;
   1.285 +        private final float DANGER_ZONE_Y_MULTIPLIER;
   1.286 +
   1.287 +        FixedMarginStrategy(Map<String, Integer> prefs) {
   1.288 +            SIZE_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_FM_MULTIPLIER, 2000);
   1.289 +            DANGER_ZONE_X_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_FM_DANGER_X, 100);
   1.290 +            DANGER_ZONE_Y_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_FM_DANGER_Y, 200);
   1.291 +        }
   1.292 +
   1.293 +        @Override
   1.294 +        public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
   1.295 +            float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER;
   1.296 +            float displayPortHeight = metrics.getHeight() * SIZE_MULTIPLIER;
   1.297 +
   1.298 +            // we need to avoid having a display port that is larger than the page, or we will end up
   1.299 +            // painting things outside the page bounds (bug 729169). we simultaneously need to make
   1.300 +            // the display port as large as possible so that we redraw less. reshape the display
   1.301 +            // port dimensions to accomplish this.
   1.302 +            FloatSize usableSize = reshapeForPage(displayPortWidth, displayPortHeight, metrics);
   1.303 +            float horizontalBuffer = usableSize.width - metrics.getWidth();
   1.304 +            float verticalBuffer = usableSize.height - metrics.getHeight();
   1.305 +
   1.306 +            // and now calculate the display port margins based on how much buffer we've decided to use and
   1.307 +            // the page bounds, ensuring we use all of the available buffer amounts on one side or the other
   1.308 +            // on any given axis. (i.e. if we're scrolled to the top of the page, the vertical buffer is
   1.309 +            // entirely below the visible viewport, but if we're halfway down the page, the vertical buffer
   1.310 +            // is split).
   1.311 +            RectF margins = new RectF();
   1.312 +            margins.left = horizontalBuffer / 2.0f;
   1.313 +            margins.right = horizontalBuffer - margins.left;
   1.314 +            margins.top = verticalBuffer / 2.0f;
   1.315 +            margins.bottom = verticalBuffer - margins.top;
   1.316 +            margins = shiftMarginsForPageBounds(margins, metrics);
   1.317 +
   1.318 +            return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
   1.319 +        }
   1.320 +
   1.321 +        @Override
   1.322 +        public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
   1.323 +            // Increase the size of the viewport based on the danger zone multiplier (and clamp to page
   1.324 +            // boundaries), and intersect it with the current displayport to determine whether we're
   1.325 +            // close to checkerboarding.
   1.326 +            RectF adjustedViewport = expandByDangerZone(metrics.getViewport(), DANGER_ZONE_X_MULTIPLIER, DANGER_ZONE_Y_MULTIPLIER, metrics);
   1.327 +            return !displayPort.contains(adjustedViewport);
   1.328 +        }
   1.329 +
   1.330 +        @Override
   1.331 +        public String toString() {
   1.332 +            return "FixedMarginStrategy mult=" + SIZE_MULTIPLIER + ", dangerX=" + DANGER_ZONE_X_MULTIPLIER + ", dangerY=" + DANGER_ZONE_Y_MULTIPLIER;
   1.333 +        }
   1.334 +    }
   1.335 +
   1.336 +    /**
   1.337 +     * This class implements the variation with a small fixed-size margin with velocity bias.
   1.338 +     * In this variation, the default margins are pretty small relative to the view size, but
   1.339 +     * they are affected by the panning velocity. Specifically, if we are panning on one axis,
   1.340 +     * we remove the margins on the other axis because we are likely axis-locked. Also once
   1.341 +     * we are panning in one direction above a certain threshold velocity, we shift the buffer
   1.342 +     * so that it is almost entirely in the direction of the pan, with a little bit in the
   1.343 +     * reverse direction.
   1.344 +     */
   1.345 +    private static class VelocityBiasStrategy extends DisplayPortStrategy {
   1.346 +        // The length of each axis of the display port will be the corresponding view length
   1.347 +        // multiplied by this factor.
   1.348 +        private final float SIZE_MULTIPLIER;
   1.349 +        // The velocity above which we apply the velocity bias
   1.350 +        private final float VELOCITY_THRESHOLD;
   1.351 +        // How much of the buffer to keep in the reverse direction of the velocity
   1.352 +        private final float REVERSE_BUFFER;
   1.353 +        // If the visible rect is within the danger zone we start redrawing to minimize
   1.354 +        // checkerboarding. the danger zone amount is a linear function of the form:
   1.355 +        //    viewportsize * (base + velocity * incr)
   1.356 +        // where base and incr are configurable values.
   1.357 +        private final float DANGER_ZONE_BASE_X_MULTIPLIER;
   1.358 +        private final float DANGER_ZONE_BASE_Y_MULTIPLIER;
   1.359 +        private final float DANGER_ZONE_INCR_X_MULTIPLIER;
   1.360 +        private final float DANGER_ZONE_INCR_Y_MULTIPLIER;
   1.361 +
   1.362 +        VelocityBiasStrategy(Map<String, Integer> prefs) {
   1.363 +            SIZE_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_MULTIPLIER, 2000);
   1.364 +            VELOCITY_THRESHOLD = GeckoAppShell.getDpi() * getFloatPref(prefs, PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD, 32);
   1.365 +            REVERSE_BUFFER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_REVERSE_BUFFER, 200);
   1.366 +            DANGER_ZONE_BASE_X_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_X_BASE, 1000);
   1.367 +            DANGER_ZONE_BASE_Y_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_Y_BASE, 1000);
   1.368 +            DANGER_ZONE_INCR_X_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_X_INCR, 0);
   1.369 +            DANGER_ZONE_INCR_Y_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_Y_INCR, 0);
   1.370 +        }
   1.371 +
   1.372 +        /**
   1.373 +         * Split the given amounts into margins based on the VELOCITY_THRESHOLD and REVERSE_BUFFER values.
   1.374 +         * If the velocity is above the VELOCITY_THRESHOLD on an axis, split the amount into REVERSE_BUFFER
   1.375 +         * and 1.0 - REVERSE_BUFFER fractions. The REVERSE_BUFFER fraction is set as the margin in the
   1.376 +         * direction opposite to the velocity, and the remaining fraction is set as the margin in the direction
   1.377 +         * of the velocity. If the velocity is lower than VELOCITY_THRESHOLD, split the amount evenly into the
   1.378 +         * two margins on that axis.
   1.379 +         */
   1.380 +        private RectF velocityBiasedMargins(float xAmount, float yAmount, PointF velocity) {
   1.381 +            RectF margins = new RectF();
   1.382 +
   1.383 +            if (velocity.x > VELOCITY_THRESHOLD) {
   1.384 +                margins.left = xAmount * REVERSE_BUFFER;
   1.385 +            } else if (velocity.x < -VELOCITY_THRESHOLD) {
   1.386 +                margins.left = xAmount * (1.0f - REVERSE_BUFFER);
   1.387 +            } else {
   1.388 +                margins.left = xAmount / 2.0f;
   1.389 +            }
   1.390 +            margins.right = xAmount - margins.left;
   1.391 +    
   1.392 +            if (velocity.y > VELOCITY_THRESHOLD) {
   1.393 +                margins.top = yAmount * REVERSE_BUFFER;
   1.394 +            } else if (velocity.y < -VELOCITY_THRESHOLD) {
   1.395 +                margins.top = yAmount * (1.0f - REVERSE_BUFFER);
   1.396 +            } else {
   1.397 +                margins.top = yAmount / 2.0f;
   1.398 +            }
   1.399 +            margins.bottom = yAmount - margins.top;
   1.400 +
   1.401 +            return margins;
   1.402 +        }
   1.403 +
   1.404 +        @Override
   1.405 +        public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
   1.406 +            float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER;
   1.407 +            float displayPortHeight = metrics.getHeight() * SIZE_MULTIPLIER;
   1.408 +
   1.409 +            // but if we're panning on one axis, set the margins for the other axis to zero since we are likely
   1.410 +            // axis locked and won't be displaying that extra area.
   1.411 +            if (Math.abs(velocity.x) > VELOCITY_THRESHOLD && FloatUtils.fuzzyEquals(velocity.y, 0)) {
   1.412 +                displayPortHeight = metrics.getHeight();
   1.413 +            } else if (Math.abs(velocity.y) > VELOCITY_THRESHOLD && FloatUtils.fuzzyEquals(velocity.x, 0)) {
   1.414 +                displayPortWidth = metrics.getWidth();
   1.415 +            }
   1.416 +
   1.417 +            // we need to avoid having a display port that is larger than the page, or we will end up
   1.418 +            // painting things outside the page bounds (bug 729169).
   1.419 +            displayPortWidth = Math.min(displayPortWidth, metrics.getPageWidth());
   1.420 +            displayPortHeight = Math.min(displayPortHeight, metrics.getPageHeight());
   1.421 +            float horizontalBuffer = displayPortWidth - metrics.getWidth();
   1.422 +            float verticalBuffer = displayPortHeight - metrics.getHeight();
   1.423 +
   1.424 +            // split the buffer amounts into margins based on velocity, and shift it to
   1.425 +            // take into account the page bounds
   1.426 +            RectF margins = velocityBiasedMargins(horizontalBuffer, verticalBuffer, velocity);
   1.427 +            margins = shiftMarginsForPageBounds(margins, metrics);
   1.428 +
   1.429 +            return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
   1.430 +        }
   1.431 +
   1.432 +        @Override
   1.433 +        public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
   1.434 +            // calculate the danger zone amounts based on the prefs
   1.435 +            float dangerZoneX = metrics.getWidth() * (DANGER_ZONE_BASE_X_MULTIPLIER + (velocity.x * DANGER_ZONE_INCR_X_MULTIPLIER));
   1.436 +            float dangerZoneY = metrics.getHeight() * (DANGER_ZONE_BASE_Y_MULTIPLIER + (velocity.y * DANGER_ZONE_INCR_Y_MULTIPLIER));
   1.437 +            // clamp it such that when added to the viewport, they don't exceed page size.
   1.438 +            // this is a prerequisite to calling shiftMarginsForPageBounds as we do below.
   1.439 +            dangerZoneX = Math.min(dangerZoneX, metrics.getPageWidth() - metrics.getWidth());
   1.440 +            dangerZoneY = Math.min(dangerZoneY, metrics.getPageHeight() - metrics.getHeight());
   1.441 +
   1.442 +            // split the danger zone into margins based on velocity, and ensure it doesn't exceed
   1.443 +            // page bounds.
   1.444 +            RectF dangerMargins = velocityBiasedMargins(dangerZoneX, dangerZoneY, velocity);
   1.445 +            dangerMargins = shiftMarginsForPageBounds(dangerMargins, metrics);
   1.446 +
   1.447 +            // we're about to checkerboard if the current viewport area + the danger zone margins
   1.448 +            // fall out of the current displayport anywhere.
   1.449 +            RectF adjustedViewport = new RectF(
   1.450 +                    metrics.viewportRectLeft - dangerMargins.left,
   1.451 +                    metrics.viewportRectTop - dangerMargins.top,
   1.452 +                    metrics.viewportRectRight + dangerMargins.right,
   1.453 +                    metrics.viewportRectBottom + dangerMargins.bottom);
   1.454 +            return !displayPort.contains(adjustedViewport);
   1.455 +        }
   1.456 +
   1.457 +        @Override
   1.458 +        public String toString() {
   1.459 +            return "VelocityBiasStrategy mult=" + SIZE_MULTIPLIER + ", threshold=" + VELOCITY_THRESHOLD + ", reverse=" + REVERSE_BUFFER
   1.460 +                + ", dangerBaseX=" + DANGER_ZONE_BASE_X_MULTIPLIER + ", dangerBaseY=" + DANGER_ZONE_BASE_Y_MULTIPLIER
   1.461 +                + ", dangerIncrX=" + DANGER_ZONE_INCR_Y_MULTIPLIER + ", dangerIncrY=" + DANGER_ZONE_INCR_Y_MULTIPLIER;
   1.462 +        }
   1.463 +    }
   1.464 +
   1.465 +    /**
   1.466 +     * This class implements the variation where we draw more of the page at low resolution while panning.
   1.467 +     * In this variation, as we pan faster, we increase the page area we are drawing, but reduce the draw
   1.468 +     * resolution to compensate. This results in the same device-pixel area drawn; the compositor then
   1.469 +     * scales this up to the viewport zoom level. This results in a large area of the page drawn but it
   1.470 +     * looks blurry. The assumption is that drawing extra that we never display is better than checkerboarding,
   1.471 +     * where we draw less but never even show it on the screen.
   1.472 +     */
   1.473 +    private static class DynamicResolutionStrategy extends DisplayPortStrategy {
   1.474 +        // The length of each axis of the display port will be the corresponding view length
   1.475 +        // multiplied by this factor.
   1.476 +        private static final float SIZE_MULTIPLIER = 1.5f;
   1.477 +
   1.478 +        // The velocity above which we start zooming out the display port to keep up
   1.479 +        // with the panning.
   1.480 +        private static final float VELOCITY_EXPANSION_THRESHOLD = GeckoAppShell.getDpi() / 16f;
   1.481 +
   1.482 +        // How much we increase the display port based on velocity. Assuming no friction and
   1.483 +        // splitting (see below), this should be be the number of frames (@60fps) between us
   1.484 +        // calculating the display port and the draw of the *next* display port getting composited
   1.485 +        // and displayed on the screen. This is because the timeline looks like this:
   1.486 +        //      Java: pan pan pan pan pan pan ! pan pan pan pan pan pan !
   1.487 +        //     Gecko:   \-> draw -> composite /   \-> draw -> composite /
   1.488 +        // The display port calculated on the first "pan" gets composited to the screen at the
   1.489 +        // first exclamation mark, and remains on the screen until the second exclamation mark.
   1.490 +        // In order to avoid checkerboarding, that display port must be able to contain all of
   1.491 +        // the panning until the second exclamation mark, which encompasses two entire draw/composite
   1.492 +        // cycles.
   1.493 +        // If we take into account friction, our velocity multiplier should be reduced as the
   1.494 +        // amount of pan will decrease each time. If we take into account display port splitting,
   1.495 +        // it should be increased as the splitting means some of the display port will be used to
   1.496 +        // draw in the opposite direction of the velocity. For now I'm assuming these two cancel
   1.497 +        // each other out.
   1.498 +        private static final float VELOCITY_MULTIPLIER = 60.0f;
   1.499 +
   1.500 +        // The following constants adjust how biased the display port is in the direction of panning.
   1.501 +        // When panning fast (above the FAST_THRESHOLD) we use the fast split factor to split the
   1.502 +        // display port "buffer" area, otherwise we use the slow split factor. This is based on the
   1.503 +        // assumption that if the user is panning fast, they are less likely to reverse directions
   1.504 +        // and go backwards, so we should spend more of our display port buffer in the direction of
   1.505 +        // panning.
   1.506 +        private static final float VELOCITY_FAST_THRESHOLD = VELOCITY_EXPANSION_THRESHOLD * 2.0f;
   1.507 +        private static final float FAST_SPLIT_FACTOR = 0.95f;
   1.508 +        private static final float SLOW_SPLIT_FACTOR = 0.8f;
   1.509 +
   1.510 +        // The following constants are used for viewport prediction; we use them to estimate where
   1.511 +        // the viewport will be soon and whether or not we should trigger a draw right now. "soon"
   1.512 +        // in the previous sentence really refers to the amount of time it would take to draw and
   1.513 +        // composite from the point at which we do the calculation, and that is not really a known
   1.514 +        // quantity. The velocity multiplier is how much we multiply the velocity by; it has the
   1.515 +        // same caveats as the VELOCITY_MULTIPLIER above except that it only needs to take into account
   1.516 +        // one draw/composite cycle instead of two. The danger zone multiplier is a multiplier of the
   1.517 +        // viewport size that we use as an extra "danger zone" around the viewport; if this danger
   1.518 +        // zone falls outside the display port then we are approaching the point at which we will
   1.519 +        // checkerboard, and hence should start drawing. Note that if DANGER_ZONE_MULTIPLIER is
   1.520 +        // greater than (SIZE_MULTIPLIER - 1.0f), then at zero velocity we will always be in the
   1.521 +        // danger zone, and thus will be constantly drawing.
   1.522 +        private static final float PREDICTION_VELOCITY_MULTIPLIER = 30.0f;
   1.523 +        private static final float DANGER_ZONE_MULTIPLIER = 0.20f; // must be less than (SIZE_MULTIPLIER - 1.0f)
   1.524 +
   1.525 +        DynamicResolutionStrategy(Map<String, Integer> prefs) {
   1.526 +            // ignore prefs for now
   1.527 +        }
   1.528 +
   1.529 +        @Override
   1.530 +        public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
   1.531 +            float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER;
   1.532 +            float displayPortHeight = metrics.getHeight() * SIZE_MULTIPLIER;
   1.533 +
   1.534 +            // for resolution calculation purposes, we need to know what the adjusted display port dimensions
   1.535 +            // would be if we had zero velocity, so calculate that here before we increase the display port
   1.536 +            // based on velocity.
   1.537 +            FloatSize reshapedSize = reshapeForPage(displayPortWidth, displayPortHeight, metrics);
   1.538 +
   1.539 +            // increase displayPortWidth and displayPortHeight based on the velocity, but maintaining their
   1.540 +            // relative aspect ratio.
   1.541 +            if (velocity.length() > VELOCITY_EXPANSION_THRESHOLD) {
   1.542 +                float velocityFactor = Math.max(Math.abs(velocity.x) / displayPortWidth,
   1.543 +                                                Math.abs(velocity.y) / displayPortHeight);
   1.544 +                velocityFactor *= VELOCITY_MULTIPLIER;
   1.545 +
   1.546 +                displayPortWidth += (displayPortWidth * velocityFactor);
   1.547 +                displayPortHeight += (displayPortHeight * velocityFactor);
   1.548 +            }
   1.549 +
   1.550 +            // at this point, displayPortWidth and displayPortHeight are how much of the page (in device pixels)
   1.551 +            // we want to be rendered by Gecko. Note here "device pixels" is equivalent to CSS pixels multiplied
   1.552 +            // by metrics.zoomFactor
   1.553 +
   1.554 +            // we need to avoid having a display port that is larger than the page, or we will end up
   1.555 +            // painting things outside the page bounds (bug 729169). we simultaneously need to make
   1.556 +            // the display port as large as possible so that we redraw less. reshape the display
   1.557 +            // port dimensions to accomplish this. this may change the aspect ratio of the display port,
   1.558 +            // but we are assuming that this is desirable because the advantages from pre-drawing will
   1.559 +            // outweigh the disadvantages from any buffer reallocations that might occur.
   1.560 +            FloatSize usableSize = reshapeForPage(displayPortWidth, displayPortHeight, metrics);
   1.561 +            float horizontalBuffer = usableSize.width - metrics.getWidth();
   1.562 +            float verticalBuffer = usableSize.height - metrics.getHeight();
   1.563 +
   1.564 +            // at this point, horizontalBuffer and verticalBuffer are the dimensions of the buffer area we have.
   1.565 +            // the buffer area is the off-screen area that is part of the display port and will be pre-drawn in case
   1.566 +            // the user scrolls there. we now need to split the buffer area on each axis so that we know
   1.567 +            // what the exact margins on each side will be. first we split the buffer amount based on the direction
   1.568 +            // we're moving, so that we have a larger buffer in the direction of travel.
   1.569 +            RectF margins = new RectF();
   1.570 +            margins.left = splitBufferByVelocity(horizontalBuffer, velocity.x);
   1.571 +            margins.right = horizontalBuffer - margins.left;
   1.572 +            margins.top = splitBufferByVelocity(verticalBuffer, velocity.y);
   1.573 +            margins.bottom = verticalBuffer - margins.top;
   1.574 +
   1.575 +            // then, we account for running into the page bounds - so that if we hit the top of the page, we need
   1.576 +            // to drop the top margin and move that amount to the bottom margin.
   1.577 +            margins = shiftMarginsForPageBounds(margins, metrics);
   1.578 +
   1.579 +            // finally, we calculate the resolution we want to render the display port area at. We do this
   1.580 +            // so that as we expand the display port area (because of velocity), we reduce the resolution of
   1.581 +            // the painted area so as to maintain the size of the buffer Gecko is painting into. we calculate
   1.582 +            // the reduction in resolution by comparing the display port size with and without the velocity
   1.583 +            // changes applied.
   1.584 +            // this effectively means that as we pan faster and faster, the display port grows, but we paint
   1.585 +            // at lower resolutions. this paints more area to reduce checkerboard at the cost of increasing
   1.586 +            // compositor-scaling and blurriness. Once we stop panning, the blurriness must be entirely gone.
   1.587 +            // Note that usable* could be less than base* if we are pinch-zoomed out into overscroll, so we
   1.588 +            // clamp it to make sure this doesn't increase our display resolution past metrics.zoomFactor.
   1.589 +            float scaleFactor = Math.min(reshapedSize.width / usableSize.width, reshapedSize.height / usableSize.height);
   1.590 +            float displayResolution = metrics.zoomFactor * Math.min(1.0f, scaleFactor);
   1.591 +
   1.592 +            DisplayPortMetrics dpMetrics = new DisplayPortMetrics(
   1.593 +                metrics.viewportRectLeft - margins.left,
   1.594 +                metrics.viewportRectTop - margins.top,
   1.595 +                metrics.viewportRectRight + margins.right,
   1.596 +                metrics.viewportRectBottom + margins.bottom,
   1.597 +                displayResolution);
   1.598 +            return dpMetrics;
   1.599 +        }
   1.600 +
   1.601 +        /**
   1.602 +         * Split the given buffer amount into two based on the velocity.
   1.603 +         * Given an amount of total usable buffer on an axis, this will
   1.604 +         * return the amount that should be used on the left/top side of
   1.605 +         * the axis (the side which a negative velocity vector corresponds
   1.606 +         * to).
   1.607 +         */
   1.608 +        private float splitBufferByVelocity(float amount, float velocity) {
   1.609 +            // if no velocity, so split evenly
   1.610 +            if (FloatUtils.fuzzyEquals(velocity, 0)) {
   1.611 +                return amount / 2.0f;
   1.612 +            }
   1.613 +            // if we're moving quickly, assign more of the amount in that direction
   1.614 +            // since is is less likely that we will reverse direction immediately
   1.615 +            if (velocity < -VELOCITY_FAST_THRESHOLD) {
   1.616 +                return amount * FAST_SPLIT_FACTOR;
   1.617 +            }
   1.618 +            if (velocity > VELOCITY_FAST_THRESHOLD) {
   1.619 +                return amount * (1.0f - FAST_SPLIT_FACTOR);
   1.620 +            }
   1.621 +            // if we're moving slowly, then assign less of the amount in that direction
   1.622 +            if (velocity < 0) {
   1.623 +                return amount * SLOW_SPLIT_FACTOR;
   1.624 +            } else {
   1.625 +                return amount * (1.0f - SLOW_SPLIT_FACTOR);
   1.626 +            }
   1.627 +        }
   1.628 +
   1.629 +        @Override
   1.630 +        public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
   1.631 +            // Expand the viewport based on our velocity (and clamp it to page boundaries).
   1.632 +            // Then intersect it with the last-requested displayport to determine whether we're
   1.633 +            // close to checkerboarding.
   1.634 +
   1.635 +            RectF predictedViewport = metrics.getViewport();
   1.636 +
   1.637 +            // first we expand the viewport in the direction we're moving based on some
   1.638 +            // multiple of the current velocity.
   1.639 +            if (velocity.length() > 0) {
   1.640 +                if (velocity.x < 0) {
   1.641 +                    predictedViewport.left += velocity.x * PREDICTION_VELOCITY_MULTIPLIER;
   1.642 +                } else if (velocity.x > 0) {
   1.643 +                    predictedViewport.right += velocity.x * PREDICTION_VELOCITY_MULTIPLIER;
   1.644 +                }
   1.645 +
   1.646 +                if (velocity.y < 0) {
   1.647 +                    predictedViewport.top += velocity.y * PREDICTION_VELOCITY_MULTIPLIER;
   1.648 +                } else if (velocity.y > 0) {
   1.649 +                    predictedViewport.bottom += velocity.y * PREDICTION_VELOCITY_MULTIPLIER;
   1.650 +                }
   1.651 +            }
   1.652 +
   1.653 +            // then we expand the viewport evenly in all directions just to have an extra
   1.654 +            // safety zone. this also clamps it to page bounds.
   1.655 +            predictedViewport = expandByDangerZone(predictedViewport, DANGER_ZONE_MULTIPLIER, DANGER_ZONE_MULTIPLIER, metrics);
   1.656 +            return !displayPort.contains(predictedViewport);
   1.657 +        }
   1.658 +
   1.659 +        @Override
   1.660 +        public String toString() {
   1.661 +            return "DynamicResolutionStrategy";
   1.662 +        }
   1.663 +    }
   1.664 +
   1.665 +    /**
   1.666 +     * This class implements the variation where we use the draw time to predict where we will be when
   1.667 +     * a draw completes, and draw that instead of where we are now. In this variation, when our panning
   1.668 +     * speed drops below a certain threshold, we draw 9 viewports' worth of content so that the user can
   1.669 +     * pan in any direction without encountering checkerboarding.
   1.670 +     * Once the user is panning, we modify the displayport to encompass an area range of where we think
   1.671 +     * the user will be when the draw completes. This heuristic relies on both the estimated draw time
   1.672 +     * the panning velocity; unexpected changes in either of these values will cause the heuristic to
   1.673 +     * fail and show checkerboard.
   1.674 +     */
   1.675 +    private static class PredictionBiasStrategy extends DisplayPortStrategy {
   1.676 +        private static float VELOCITY_THRESHOLD;
   1.677 +
   1.678 +        private int mPixelArea;         // area of the viewport, used in draw time calculations
   1.679 +        private int mMinFramesToDraw;   // minimum number of frames we take to draw
   1.680 +        private int mMaxFramesToDraw;   // maximum number of frames we take to draw
   1.681 +
   1.682 +        PredictionBiasStrategy(Map<String, Integer> prefs) {
   1.683 +            VELOCITY_THRESHOLD = GeckoAppShell.getDpi() * getFloatPref(prefs, PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD, 16);
   1.684 +            resetPageState();
   1.685 +        }
   1.686 +
   1.687 +        @Override
   1.688 +        public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
   1.689 +            float width = metrics.getWidth();
   1.690 +            float height = metrics.getHeight();
   1.691 +            mPixelArea = (int)(width * height);
   1.692 +
   1.693 +            if (velocity.length() < VELOCITY_THRESHOLD) {
   1.694 +                // if we're going slow, expand the displayport to 9x viewport size
   1.695 +                RectF margins = new RectF(width, height, width, height);
   1.696 +                return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
   1.697 +            }
   1.698 +
   1.699 +            // figure out how far we expect to be
   1.700 +            float minDx = velocity.x * mMinFramesToDraw;
   1.701 +            float minDy = velocity.y * mMinFramesToDraw;
   1.702 +            float maxDx = velocity.x * mMaxFramesToDraw;
   1.703 +            float maxDy = velocity.y * mMaxFramesToDraw;
   1.704 +
   1.705 +            // figure out how many pixels we will be drawing when we draw the above-calculated range.
   1.706 +            // this will be larger than the viewport area.
   1.707 +            float pixelsToDraw = (width + Math.abs(maxDx - minDx)) * (height + Math.abs(maxDy - minDy));
   1.708 +            // adjust how far we will get because of the time spent drawing all these extra pixels. this
   1.709 +            // will again increase the number of pixels drawn so really we could keep iterating this over
   1.710 +            // and over, but once seems enough for now.
   1.711 +            maxDx = maxDx * pixelsToDraw / mPixelArea;
   1.712 +            maxDy = maxDy * pixelsToDraw / mPixelArea;
   1.713 +
   1.714 +            // and finally generate the displayport. the min/max stuff takes care of
   1.715 +            // negative velocities as well as positive.
   1.716 +            RectF margins = new RectF(
   1.717 +                -Math.min(minDx, maxDx),
   1.718 +                -Math.min(minDy, maxDy),
   1.719 +                Math.max(minDx, maxDx),
   1.720 +                Math.max(minDy, maxDy));
   1.721 +            return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
   1.722 +        }
   1.723 +
   1.724 +        @Override
   1.725 +        public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
   1.726 +            // the code below is the same as in calculate() but is awkward to refactor since it has multiple outputs.
   1.727 +            // refer to the comments in calculate() to understand what this is doing.
   1.728 +            float minDx = velocity.x * mMinFramesToDraw;
   1.729 +            float minDy = velocity.y * mMinFramesToDraw;
   1.730 +            float maxDx = velocity.x * mMaxFramesToDraw;
   1.731 +            float maxDy = velocity.y * mMaxFramesToDraw;
   1.732 +            float pixelsToDraw = (metrics.getWidth() + Math.abs(maxDx - minDx)) * (metrics.getHeight() + Math.abs(maxDy - minDy));
   1.733 +            maxDx = maxDx * pixelsToDraw / mPixelArea;
   1.734 +            maxDy = maxDy * pixelsToDraw / mPixelArea;
   1.735 +
   1.736 +            // now that we have an idea of how far we will be when the draw completes, take the farthest
   1.737 +            // end of that range and see if it falls outside the displayport bounds. if it does, allow
   1.738 +            // the draw to go through
   1.739 +            RectF predictedViewport = metrics.getViewport();
   1.740 +            predictedViewport.left += maxDx;
   1.741 +            predictedViewport.top += maxDy;
   1.742 +            predictedViewport.right += maxDx;
   1.743 +            predictedViewport.bottom += maxDy;
   1.744 +
   1.745 +            predictedViewport = clampToPageBounds(predictedViewport, metrics);
   1.746 +            return !displayPort.contains(predictedViewport);
   1.747 +        }
   1.748 +
   1.749 +        @Override
   1.750 +        public boolean drawTimeUpdate(long millis, int pixels) {
   1.751 +            // calculate the number of frames it took to draw a viewport-sized area
   1.752 +            float normalizedTime = (float)mPixelArea * (float)millis / (float)pixels;
   1.753 +            int normalizedFrames = (int)FloatMath.ceil(normalizedTime * 60f / 1000f);
   1.754 +            // broaden our range on how long it takes to draw if the draw falls outside
   1.755 +            // the range. this allows it to grow gradually. this heuristic may need to
   1.756 +            // be tweaked into more of a floating window average or something.
   1.757 +            if (normalizedFrames <= mMinFramesToDraw) {
   1.758 +                mMinFramesToDraw--;
   1.759 +            } else if (normalizedFrames > mMaxFramesToDraw) {
   1.760 +                mMaxFramesToDraw++;
   1.761 +            } else {
   1.762 +                return true;
   1.763 +            }
   1.764 +            Log.d(LOGTAG, "Widened draw range to [" + mMinFramesToDraw + ", " + mMaxFramesToDraw + "]");
   1.765 +            return true;
   1.766 +        }
   1.767 +
   1.768 +        @Override
   1.769 +        public void resetPageState() {
   1.770 +            mMinFramesToDraw = 0;
   1.771 +            mMaxFramesToDraw = 2;
   1.772 +        }
   1.773 +
   1.774 +        @Override
   1.775 +        public String toString() {
   1.776 +            return "PredictionBiasStrategy threshold=" + VELOCITY_THRESHOLD;
   1.777 +        }
   1.778 +    }
   1.779 +}

mercurial