Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 4 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | package org.mozilla.gecko.gfx; |
michael@0 | 7 | |
michael@0 | 8 | import org.mozilla.gecko.GeckoAppShell; |
michael@0 | 9 | import org.mozilla.gecko.PrefsHelper; |
michael@0 | 10 | import org.mozilla.gecko.util.FloatUtils; |
michael@0 | 11 | |
michael@0 | 12 | import org.json.JSONArray; |
michael@0 | 13 | |
michael@0 | 14 | import android.graphics.PointF; |
michael@0 | 15 | import android.graphics.RectF; |
michael@0 | 16 | import android.util.FloatMath; |
michael@0 | 17 | import android.util.Log; |
michael@0 | 18 | |
michael@0 | 19 | import java.util.HashMap; |
michael@0 | 20 | import java.util.Map; |
michael@0 | 21 | |
michael@0 | 22 | final class DisplayPortCalculator { |
michael@0 | 23 | private static final String LOGTAG = "GeckoDisplayPort"; |
michael@0 | 24 | private static final PointF ZERO_VELOCITY = new PointF(0, 0); |
michael@0 | 25 | |
michael@0 | 26 | // Keep this in sync with the TILEDLAYERBUFFER_TILE_SIZE defined in gfx/layers/TiledLayerBuffer.h |
michael@0 | 27 | private static final int TILE_SIZE = 256; |
michael@0 | 28 | |
michael@0 | 29 | private static final String PREF_DISPLAYPORT_STRATEGY = "gfx.displayport.strategy"; |
michael@0 | 30 | private static final String PREF_DISPLAYPORT_FM_MULTIPLIER = "gfx.displayport.strategy_fm.multiplier"; |
michael@0 | 31 | private static final String PREF_DISPLAYPORT_FM_DANGER_X = "gfx.displayport.strategy_fm.danger_x"; |
michael@0 | 32 | private static final String PREF_DISPLAYPORT_FM_DANGER_Y = "gfx.displayport.strategy_fm.danger_y"; |
michael@0 | 33 | private static final String PREF_DISPLAYPORT_VB_MULTIPLIER = "gfx.displayport.strategy_vb.multiplier"; |
michael@0 | 34 | private static final String PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD = "gfx.displayport.strategy_vb.threshold"; |
michael@0 | 35 | private static final String PREF_DISPLAYPORT_VB_REVERSE_BUFFER = "gfx.displayport.strategy_vb.reverse_buffer"; |
michael@0 | 36 | private static final String PREF_DISPLAYPORT_VB_DANGER_X_BASE = "gfx.displayport.strategy_vb.danger_x_base"; |
michael@0 | 37 | private static final String PREF_DISPLAYPORT_VB_DANGER_Y_BASE = "gfx.displayport.strategy_vb.danger_y_base"; |
michael@0 | 38 | private static final String PREF_DISPLAYPORT_VB_DANGER_X_INCR = "gfx.displayport.strategy_vb.danger_x_incr"; |
michael@0 | 39 | private static final String PREF_DISPLAYPORT_VB_DANGER_Y_INCR = "gfx.displayport.strategy_vb.danger_y_incr"; |
michael@0 | 40 | private static final String PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD = "gfx.displayport.strategy_pb.threshold"; |
michael@0 | 41 | |
michael@0 | 42 | private static DisplayPortStrategy sStrategy = new VelocityBiasStrategy(null); |
michael@0 | 43 | |
michael@0 | 44 | static DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) { |
michael@0 | 45 | return sStrategy.calculate(metrics, (velocity == null ? ZERO_VELOCITY : velocity)); |
michael@0 | 46 | } |
michael@0 | 47 | |
michael@0 | 48 | static boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) { |
michael@0 | 49 | if (displayPort == null) { |
michael@0 | 50 | return true; |
michael@0 | 51 | } |
michael@0 | 52 | return sStrategy.aboutToCheckerboard(metrics, (velocity == null ? ZERO_VELOCITY : velocity), displayPort); |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | static boolean drawTimeUpdate(long millis, int pixels) { |
michael@0 | 56 | return sStrategy.drawTimeUpdate(millis, pixels); |
michael@0 | 57 | } |
michael@0 | 58 | |
michael@0 | 59 | static void resetPageState() { |
michael@0 | 60 | sStrategy.resetPageState(); |
michael@0 | 61 | } |
michael@0 | 62 | |
michael@0 | 63 | static void initPrefs() { |
michael@0 | 64 | final String[] prefs = { PREF_DISPLAYPORT_STRATEGY, |
michael@0 | 65 | PREF_DISPLAYPORT_FM_MULTIPLIER, |
michael@0 | 66 | PREF_DISPLAYPORT_FM_DANGER_X, |
michael@0 | 67 | PREF_DISPLAYPORT_FM_DANGER_Y, |
michael@0 | 68 | PREF_DISPLAYPORT_VB_MULTIPLIER, |
michael@0 | 69 | PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD, |
michael@0 | 70 | PREF_DISPLAYPORT_VB_REVERSE_BUFFER, |
michael@0 | 71 | PREF_DISPLAYPORT_VB_DANGER_X_BASE, |
michael@0 | 72 | PREF_DISPLAYPORT_VB_DANGER_Y_BASE, |
michael@0 | 73 | PREF_DISPLAYPORT_VB_DANGER_X_INCR, |
michael@0 | 74 | PREF_DISPLAYPORT_VB_DANGER_Y_INCR, |
michael@0 | 75 | PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD }; |
michael@0 | 76 | |
michael@0 | 77 | PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() { |
michael@0 | 78 | private Map<String, Integer> mValues = new HashMap<String, Integer>(); |
michael@0 | 79 | |
michael@0 | 80 | @Override public void prefValue(String pref, int value) { |
michael@0 | 81 | mValues.put(pref, value); |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | @Override public void finish() { |
michael@0 | 85 | setStrategy(mValues); |
michael@0 | 86 | } |
michael@0 | 87 | }); |
michael@0 | 88 | } |
michael@0 | 89 | |
michael@0 | 90 | /** |
michael@0 | 91 | * Set the active strategy to use. |
michael@0 | 92 | * See the gfx.displayport.strategy pref in mobile/android/app/mobile.js to see the |
michael@0 | 93 | * mapping between ints and strategies. |
michael@0 | 94 | */ |
michael@0 | 95 | static boolean setStrategy(Map<String, Integer> prefs) { |
michael@0 | 96 | Integer strategy = prefs.get(PREF_DISPLAYPORT_STRATEGY); |
michael@0 | 97 | if (strategy == null) { |
michael@0 | 98 | return false; |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | switch (strategy) { |
michael@0 | 102 | case 0: |
michael@0 | 103 | sStrategy = new FixedMarginStrategy(prefs); |
michael@0 | 104 | break; |
michael@0 | 105 | case 1: |
michael@0 | 106 | sStrategy = new VelocityBiasStrategy(prefs); |
michael@0 | 107 | break; |
michael@0 | 108 | case 2: |
michael@0 | 109 | sStrategy = new DynamicResolutionStrategy(prefs); |
michael@0 | 110 | break; |
michael@0 | 111 | case 3: |
michael@0 | 112 | sStrategy = new NoMarginStrategy(prefs); |
michael@0 | 113 | break; |
michael@0 | 114 | case 4: |
michael@0 | 115 | sStrategy = new PredictionBiasStrategy(prefs); |
michael@0 | 116 | break; |
michael@0 | 117 | default: |
michael@0 | 118 | Log.e(LOGTAG, "Invalid strategy index specified"); |
michael@0 | 119 | return false; |
michael@0 | 120 | } |
michael@0 | 121 | Log.i(LOGTAG, "Set strategy " + sStrategy.toString()); |
michael@0 | 122 | return true; |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | private static float getFloatPref(Map<String, Integer> prefs, String prefName, int defaultValue) { |
michael@0 | 126 | Integer value = (prefs == null ? null : prefs.get(prefName)); |
michael@0 | 127 | return (float)(value == null || value < 0 ? defaultValue : value) / 1000f; |
michael@0 | 128 | } |
michael@0 | 129 | |
michael@0 | 130 | private static abstract class DisplayPortStrategy { |
michael@0 | 131 | /** Calculates a displayport given a viewport and panning velocity. */ |
michael@0 | 132 | public abstract DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity); |
michael@0 | 133 | /** Returns true if a checkerboard is about to be visible and we should not throttle drawing. */ |
michael@0 | 134 | public abstract boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort); |
michael@0 | 135 | /** Notify the strategy of a new recorded draw time. Return false to turn off draw time recording. */ |
michael@0 | 136 | public boolean drawTimeUpdate(long millis, int pixels) { return false; } |
michael@0 | 137 | /** Reset any page-specific state stored, as the page being displayed has changed. */ |
michael@0 | 138 | public void resetPageState() {} |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | /** |
michael@0 | 142 | * Return the dimensions for a rect that has area (width*height) that does not exceed the page size in the |
michael@0 | 143 | * given metrics object. The area in the returned FloatSize may be less than width*height if the page is |
michael@0 | 144 | * small, but it will never be larger than width*height. |
michael@0 | 145 | * Note that this process may change the relative aspect ratio of the given dimensions. |
michael@0 | 146 | */ |
michael@0 | 147 | private static FloatSize reshapeForPage(float width, float height, ImmutableViewportMetrics metrics) { |
michael@0 | 148 | // figure out how much of the desired buffer amount we can actually use on the horizontal axis |
michael@0 | 149 | float usableWidth = Math.min(width, metrics.getPageWidth()); |
michael@0 | 150 | // if we reduced the buffer amount on the horizontal axis, we should take that saved memory and |
michael@0 | 151 | // use it on the vertical axis |
michael@0 | 152 | float extraUsableHeight = (float)Math.floor(((width - usableWidth) * height) / usableWidth); |
michael@0 | 153 | float usableHeight = Math.min(height + extraUsableHeight, metrics.getPageHeight()); |
michael@0 | 154 | if (usableHeight < height && usableWidth == width) { |
michael@0 | 155 | // and the reverse - if we shrunk the buffer on the vertical axis we can add it to the horizontal |
michael@0 | 156 | float extraUsableWidth = (float)Math.floor(((height - usableHeight) * width) / usableHeight); |
michael@0 | 157 | usableWidth = Math.min(width + extraUsableWidth, metrics.getPageWidth()); |
michael@0 | 158 | } |
michael@0 | 159 | return new FloatSize(usableWidth, usableHeight); |
michael@0 | 160 | } |
michael@0 | 161 | |
michael@0 | 162 | /** |
michael@0 | 163 | * Expand the given rect in all directions by a "danger zone". The size of the danger zone on an axis |
michael@0 | 164 | * is the size of the view on that axis multiplied by the given multiplier. The expanded rect is then |
michael@0 | 165 | * clamped to page bounds and returned. |
michael@0 | 166 | */ |
michael@0 | 167 | private static RectF expandByDangerZone(RectF rect, float dangerZoneXMultiplier, float dangerZoneYMultiplier, ImmutableViewportMetrics metrics) { |
michael@0 | 168 | // calculate the danger zone amounts in pixels |
michael@0 | 169 | float dangerZoneX = metrics.getWidth() * dangerZoneXMultiplier; |
michael@0 | 170 | float dangerZoneY = metrics.getHeight() * dangerZoneYMultiplier; |
michael@0 | 171 | rect = RectUtils.expand(rect, dangerZoneX, dangerZoneY); |
michael@0 | 172 | // clamp to page bounds |
michael@0 | 173 | return clampToPageBounds(rect, metrics); |
michael@0 | 174 | } |
michael@0 | 175 | |
michael@0 | 176 | /** |
michael@0 | 177 | * Expand the given margins such that when they are applied on the viewport, the resulting rect |
michael@0 | 178 | * does not have any partial tiles, except when it is clipped by the page bounds. This assumes |
michael@0 | 179 | * the tiles are TILE_SIZE by TILE_SIZE and start at the origin, such that there will always be |
michael@0 | 180 | * a tile at (0,0)-(TILE_SIZE,TILE_SIZE)). |
michael@0 | 181 | */ |
michael@0 | 182 | private static DisplayPortMetrics getTileAlignedDisplayPortMetrics(RectF margins, float zoom, ImmutableViewportMetrics metrics) { |
michael@0 | 183 | float left = metrics.viewportRectLeft - margins.left; |
michael@0 | 184 | float top = metrics.viewportRectTop - margins.top; |
michael@0 | 185 | float right = metrics.viewportRectRight + margins.right; |
michael@0 | 186 | float bottom = metrics.viewportRectBottom + margins.bottom; |
michael@0 | 187 | left = Math.max(metrics.pageRectLeft, TILE_SIZE * FloatMath.floor(left / TILE_SIZE)); |
michael@0 | 188 | top = Math.max(metrics.pageRectTop, TILE_SIZE * FloatMath.floor(top / TILE_SIZE)); |
michael@0 | 189 | right = Math.min(metrics.pageRectRight, TILE_SIZE * FloatMath.ceil(right / TILE_SIZE)); |
michael@0 | 190 | bottom = Math.min(metrics.pageRectBottom, TILE_SIZE * FloatMath.ceil(bottom / TILE_SIZE)); |
michael@0 | 191 | return new DisplayPortMetrics(left, top, right, bottom, zoom); |
michael@0 | 192 | } |
michael@0 | 193 | |
michael@0 | 194 | /** |
michael@0 | 195 | * Adjust the given margins so if they are applied on the viewport in the metrics, the resulting rect |
michael@0 | 196 | * does not exceed the page bounds. This code will maintain the total margin amount for a given axis; |
michael@0 | 197 | * it assumes that margins.left + metrics.getWidth() + margins.right is less than or equal to |
michael@0 | 198 | * metrics.getPageWidth(); and the same for the y axis. |
michael@0 | 199 | */ |
michael@0 | 200 | private static RectF shiftMarginsForPageBounds(RectF margins, ImmutableViewportMetrics metrics) { |
michael@0 | 201 | // check how much we're overflowing in each direction. note that at most one of leftOverflow |
michael@0 | 202 | // and rightOverflow can be greater than zero, and at most one of topOverflow and bottomOverflow |
michael@0 | 203 | // can be greater than zero, because of the assumption described in the method javadoc. |
michael@0 | 204 | float leftOverflow = metrics.pageRectLeft - (metrics.viewportRectLeft - margins.left); |
michael@0 | 205 | float rightOverflow = (metrics.viewportRectRight + margins.right) - metrics.pageRectRight; |
michael@0 | 206 | float topOverflow = metrics.pageRectTop - (metrics.viewportRectTop - margins.top); |
michael@0 | 207 | float bottomOverflow = (metrics.viewportRectBottom + margins.bottom) - metrics.pageRectBottom; |
michael@0 | 208 | |
michael@0 | 209 | // if the margins overflow the page bounds, shift them to other side on the same axis |
michael@0 | 210 | if (leftOverflow > 0) { |
michael@0 | 211 | margins.left -= leftOverflow; |
michael@0 | 212 | margins.right += leftOverflow; |
michael@0 | 213 | } else if (rightOverflow > 0) { |
michael@0 | 214 | margins.right -= rightOverflow; |
michael@0 | 215 | margins.left += rightOverflow; |
michael@0 | 216 | } |
michael@0 | 217 | if (topOverflow > 0) { |
michael@0 | 218 | margins.top -= topOverflow; |
michael@0 | 219 | margins.bottom += topOverflow; |
michael@0 | 220 | } else if (bottomOverflow > 0) { |
michael@0 | 221 | margins.bottom -= bottomOverflow; |
michael@0 | 222 | margins.top += bottomOverflow; |
michael@0 | 223 | } |
michael@0 | 224 | return margins; |
michael@0 | 225 | } |
michael@0 | 226 | |
michael@0 | 227 | /** |
michael@0 | 228 | * Clamp the given rect to the page bounds and return it. |
michael@0 | 229 | */ |
michael@0 | 230 | private static RectF clampToPageBounds(RectF rect, ImmutableViewportMetrics metrics) { |
michael@0 | 231 | if (rect.top < metrics.pageRectTop) rect.top = metrics.pageRectTop; |
michael@0 | 232 | if (rect.left < metrics.pageRectLeft) rect.left = metrics.pageRectLeft; |
michael@0 | 233 | if (rect.right > metrics.pageRectRight) rect.right = metrics.pageRectRight; |
michael@0 | 234 | if (rect.bottom > metrics.pageRectBottom) rect.bottom = metrics.pageRectBottom; |
michael@0 | 235 | return rect; |
michael@0 | 236 | } |
michael@0 | 237 | |
michael@0 | 238 | /** |
michael@0 | 239 | * This class implements the variation where we basically don't bother with a a display port. |
michael@0 | 240 | */ |
michael@0 | 241 | private static class NoMarginStrategy extends DisplayPortStrategy { |
michael@0 | 242 | NoMarginStrategy(Map<String, Integer> prefs) { |
michael@0 | 243 | // no prefs in this strategy |
michael@0 | 244 | } |
michael@0 | 245 | |
michael@0 | 246 | @Override |
michael@0 | 247 | public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) { |
michael@0 | 248 | return new DisplayPortMetrics(metrics.viewportRectLeft, |
michael@0 | 249 | metrics.viewportRectTop, |
michael@0 | 250 | metrics.viewportRectRight, |
michael@0 | 251 | metrics.viewportRectBottom, |
michael@0 | 252 | metrics.zoomFactor); |
michael@0 | 253 | } |
michael@0 | 254 | |
michael@0 | 255 | @Override |
michael@0 | 256 | public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) { |
michael@0 | 257 | return true; |
michael@0 | 258 | } |
michael@0 | 259 | |
michael@0 | 260 | @Override |
michael@0 | 261 | public String toString() { |
michael@0 | 262 | return "NoMarginStrategy"; |
michael@0 | 263 | } |
michael@0 | 264 | } |
michael@0 | 265 | |
michael@0 | 266 | /** |
michael@0 | 267 | * This class implements the variation where we use a fixed-size margin on the display port. |
michael@0 | 268 | * The margin is always 300 pixels in all directions, except when we are (a) approaching a page |
michael@0 | 269 | * boundary, and/or (b) if we are limited by the page size. In these cases we try to maintain |
michael@0 | 270 | * the area of the display port by (a) shifting the buffer to the other side on the same axis, |
michael@0 | 271 | * and/or (b) increasing the buffer on the other axis to compensate for the reduced buffer on |
michael@0 | 272 | * one axis. |
michael@0 | 273 | */ |
michael@0 | 274 | private static class FixedMarginStrategy extends DisplayPortStrategy { |
michael@0 | 275 | // The length of each axis of the display port will be the corresponding view length |
michael@0 | 276 | // multiplied by this factor. |
michael@0 | 277 | private final float SIZE_MULTIPLIER; |
michael@0 | 278 | |
michael@0 | 279 | // If the visible rect is within the danger zone (measured as a fraction of the view size |
michael@0 | 280 | // from the edge of the displayport) we start redrawing to minimize checkerboarding. |
michael@0 | 281 | private final float DANGER_ZONE_X_MULTIPLIER; |
michael@0 | 282 | private final float DANGER_ZONE_Y_MULTIPLIER; |
michael@0 | 283 | |
michael@0 | 284 | FixedMarginStrategy(Map<String, Integer> prefs) { |
michael@0 | 285 | SIZE_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_FM_MULTIPLIER, 2000); |
michael@0 | 286 | DANGER_ZONE_X_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_FM_DANGER_X, 100); |
michael@0 | 287 | DANGER_ZONE_Y_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_FM_DANGER_Y, 200); |
michael@0 | 288 | } |
michael@0 | 289 | |
michael@0 | 290 | @Override |
michael@0 | 291 | public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) { |
michael@0 | 292 | float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER; |
michael@0 | 293 | float displayPortHeight = metrics.getHeight() * SIZE_MULTIPLIER; |
michael@0 | 294 | |
michael@0 | 295 | // we need to avoid having a display port that is larger than the page, or we will end up |
michael@0 | 296 | // painting things outside the page bounds (bug 729169). we simultaneously need to make |
michael@0 | 297 | // the display port as large as possible so that we redraw less. reshape the display |
michael@0 | 298 | // port dimensions to accomplish this. |
michael@0 | 299 | FloatSize usableSize = reshapeForPage(displayPortWidth, displayPortHeight, metrics); |
michael@0 | 300 | float horizontalBuffer = usableSize.width - metrics.getWidth(); |
michael@0 | 301 | float verticalBuffer = usableSize.height - metrics.getHeight(); |
michael@0 | 302 | |
michael@0 | 303 | // and now calculate the display port margins based on how much buffer we've decided to use and |
michael@0 | 304 | // the page bounds, ensuring we use all of the available buffer amounts on one side or the other |
michael@0 | 305 | // on any given axis. (i.e. if we're scrolled to the top of the page, the vertical buffer is |
michael@0 | 306 | // entirely below the visible viewport, but if we're halfway down the page, the vertical buffer |
michael@0 | 307 | // is split). |
michael@0 | 308 | RectF margins = new RectF(); |
michael@0 | 309 | margins.left = horizontalBuffer / 2.0f; |
michael@0 | 310 | margins.right = horizontalBuffer - margins.left; |
michael@0 | 311 | margins.top = verticalBuffer / 2.0f; |
michael@0 | 312 | margins.bottom = verticalBuffer - margins.top; |
michael@0 | 313 | margins = shiftMarginsForPageBounds(margins, metrics); |
michael@0 | 314 | |
michael@0 | 315 | return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics); |
michael@0 | 316 | } |
michael@0 | 317 | |
michael@0 | 318 | @Override |
michael@0 | 319 | public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) { |
michael@0 | 320 | // Increase the size of the viewport based on the danger zone multiplier (and clamp to page |
michael@0 | 321 | // boundaries), and intersect it with the current displayport to determine whether we're |
michael@0 | 322 | // close to checkerboarding. |
michael@0 | 323 | RectF adjustedViewport = expandByDangerZone(metrics.getViewport(), DANGER_ZONE_X_MULTIPLIER, DANGER_ZONE_Y_MULTIPLIER, metrics); |
michael@0 | 324 | return !displayPort.contains(adjustedViewport); |
michael@0 | 325 | } |
michael@0 | 326 | |
michael@0 | 327 | @Override |
michael@0 | 328 | public String toString() { |
michael@0 | 329 | return "FixedMarginStrategy mult=" + SIZE_MULTIPLIER + ", dangerX=" + DANGER_ZONE_X_MULTIPLIER + ", dangerY=" + DANGER_ZONE_Y_MULTIPLIER; |
michael@0 | 330 | } |
michael@0 | 331 | } |
michael@0 | 332 | |
michael@0 | 333 | /** |
michael@0 | 334 | * This class implements the variation with a small fixed-size margin with velocity bias. |
michael@0 | 335 | * In this variation, the default margins are pretty small relative to the view size, but |
michael@0 | 336 | * they are affected by the panning velocity. Specifically, if we are panning on one axis, |
michael@0 | 337 | * we remove the margins on the other axis because we are likely axis-locked. Also once |
michael@0 | 338 | * we are panning in one direction above a certain threshold velocity, we shift the buffer |
michael@0 | 339 | * so that it is almost entirely in the direction of the pan, with a little bit in the |
michael@0 | 340 | * reverse direction. |
michael@0 | 341 | */ |
michael@0 | 342 | private static class VelocityBiasStrategy extends DisplayPortStrategy { |
michael@0 | 343 | // The length of each axis of the display port will be the corresponding view length |
michael@0 | 344 | // multiplied by this factor. |
michael@0 | 345 | private final float SIZE_MULTIPLIER; |
michael@0 | 346 | // The velocity above which we apply the velocity bias |
michael@0 | 347 | private final float VELOCITY_THRESHOLD; |
michael@0 | 348 | // How much of the buffer to keep in the reverse direction of the velocity |
michael@0 | 349 | private final float REVERSE_BUFFER; |
michael@0 | 350 | // If the visible rect is within the danger zone we start redrawing to minimize |
michael@0 | 351 | // checkerboarding. the danger zone amount is a linear function of the form: |
michael@0 | 352 | // viewportsize * (base + velocity * incr) |
michael@0 | 353 | // where base and incr are configurable values. |
michael@0 | 354 | private final float DANGER_ZONE_BASE_X_MULTIPLIER; |
michael@0 | 355 | private final float DANGER_ZONE_BASE_Y_MULTIPLIER; |
michael@0 | 356 | private final float DANGER_ZONE_INCR_X_MULTIPLIER; |
michael@0 | 357 | private final float DANGER_ZONE_INCR_Y_MULTIPLIER; |
michael@0 | 358 | |
michael@0 | 359 | VelocityBiasStrategy(Map<String, Integer> prefs) { |
michael@0 | 360 | SIZE_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_MULTIPLIER, 2000); |
michael@0 | 361 | VELOCITY_THRESHOLD = GeckoAppShell.getDpi() * getFloatPref(prefs, PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD, 32); |
michael@0 | 362 | REVERSE_BUFFER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_REVERSE_BUFFER, 200); |
michael@0 | 363 | DANGER_ZONE_BASE_X_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_X_BASE, 1000); |
michael@0 | 364 | DANGER_ZONE_BASE_Y_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_Y_BASE, 1000); |
michael@0 | 365 | DANGER_ZONE_INCR_X_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_X_INCR, 0); |
michael@0 | 366 | DANGER_ZONE_INCR_Y_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_Y_INCR, 0); |
michael@0 | 367 | } |
michael@0 | 368 | |
michael@0 | 369 | /** |
michael@0 | 370 | * Split the given amounts into margins based on the VELOCITY_THRESHOLD and REVERSE_BUFFER values. |
michael@0 | 371 | * If the velocity is above the VELOCITY_THRESHOLD on an axis, split the amount into REVERSE_BUFFER |
michael@0 | 372 | * and 1.0 - REVERSE_BUFFER fractions. The REVERSE_BUFFER fraction is set as the margin in the |
michael@0 | 373 | * direction opposite to the velocity, and the remaining fraction is set as the margin in the direction |
michael@0 | 374 | * of the velocity. If the velocity is lower than VELOCITY_THRESHOLD, split the amount evenly into the |
michael@0 | 375 | * two margins on that axis. |
michael@0 | 376 | */ |
michael@0 | 377 | private RectF velocityBiasedMargins(float xAmount, float yAmount, PointF velocity) { |
michael@0 | 378 | RectF margins = new RectF(); |
michael@0 | 379 | |
michael@0 | 380 | if (velocity.x > VELOCITY_THRESHOLD) { |
michael@0 | 381 | margins.left = xAmount * REVERSE_BUFFER; |
michael@0 | 382 | } else if (velocity.x < -VELOCITY_THRESHOLD) { |
michael@0 | 383 | margins.left = xAmount * (1.0f - REVERSE_BUFFER); |
michael@0 | 384 | } else { |
michael@0 | 385 | margins.left = xAmount / 2.0f; |
michael@0 | 386 | } |
michael@0 | 387 | margins.right = xAmount - margins.left; |
michael@0 | 388 | |
michael@0 | 389 | if (velocity.y > VELOCITY_THRESHOLD) { |
michael@0 | 390 | margins.top = yAmount * REVERSE_BUFFER; |
michael@0 | 391 | } else if (velocity.y < -VELOCITY_THRESHOLD) { |
michael@0 | 392 | margins.top = yAmount * (1.0f - REVERSE_BUFFER); |
michael@0 | 393 | } else { |
michael@0 | 394 | margins.top = yAmount / 2.0f; |
michael@0 | 395 | } |
michael@0 | 396 | margins.bottom = yAmount - margins.top; |
michael@0 | 397 | |
michael@0 | 398 | return margins; |
michael@0 | 399 | } |
michael@0 | 400 | |
michael@0 | 401 | @Override |
michael@0 | 402 | public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) { |
michael@0 | 403 | float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER; |
michael@0 | 404 | float displayPortHeight = metrics.getHeight() * SIZE_MULTIPLIER; |
michael@0 | 405 | |
michael@0 | 406 | // but if we're panning on one axis, set the margins for the other axis to zero since we are likely |
michael@0 | 407 | // axis locked and won't be displaying that extra area. |
michael@0 | 408 | if (Math.abs(velocity.x) > VELOCITY_THRESHOLD && FloatUtils.fuzzyEquals(velocity.y, 0)) { |
michael@0 | 409 | displayPortHeight = metrics.getHeight(); |
michael@0 | 410 | } else if (Math.abs(velocity.y) > VELOCITY_THRESHOLD && FloatUtils.fuzzyEquals(velocity.x, 0)) { |
michael@0 | 411 | displayPortWidth = metrics.getWidth(); |
michael@0 | 412 | } |
michael@0 | 413 | |
michael@0 | 414 | // we need to avoid having a display port that is larger than the page, or we will end up |
michael@0 | 415 | // painting things outside the page bounds (bug 729169). |
michael@0 | 416 | displayPortWidth = Math.min(displayPortWidth, metrics.getPageWidth()); |
michael@0 | 417 | displayPortHeight = Math.min(displayPortHeight, metrics.getPageHeight()); |
michael@0 | 418 | float horizontalBuffer = displayPortWidth - metrics.getWidth(); |
michael@0 | 419 | float verticalBuffer = displayPortHeight - metrics.getHeight(); |
michael@0 | 420 | |
michael@0 | 421 | // split the buffer amounts into margins based on velocity, and shift it to |
michael@0 | 422 | // take into account the page bounds |
michael@0 | 423 | RectF margins = velocityBiasedMargins(horizontalBuffer, verticalBuffer, velocity); |
michael@0 | 424 | margins = shiftMarginsForPageBounds(margins, metrics); |
michael@0 | 425 | |
michael@0 | 426 | return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics); |
michael@0 | 427 | } |
michael@0 | 428 | |
michael@0 | 429 | @Override |
michael@0 | 430 | public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) { |
michael@0 | 431 | // calculate the danger zone amounts based on the prefs |
michael@0 | 432 | float dangerZoneX = metrics.getWidth() * (DANGER_ZONE_BASE_X_MULTIPLIER + (velocity.x * DANGER_ZONE_INCR_X_MULTIPLIER)); |
michael@0 | 433 | float dangerZoneY = metrics.getHeight() * (DANGER_ZONE_BASE_Y_MULTIPLIER + (velocity.y * DANGER_ZONE_INCR_Y_MULTIPLIER)); |
michael@0 | 434 | // clamp it such that when added to the viewport, they don't exceed page size. |
michael@0 | 435 | // this is a prerequisite to calling shiftMarginsForPageBounds as we do below. |
michael@0 | 436 | dangerZoneX = Math.min(dangerZoneX, metrics.getPageWidth() - metrics.getWidth()); |
michael@0 | 437 | dangerZoneY = Math.min(dangerZoneY, metrics.getPageHeight() - metrics.getHeight()); |
michael@0 | 438 | |
michael@0 | 439 | // split the danger zone into margins based on velocity, and ensure it doesn't exceed |
michael@0 | 440 | // page bounds. |
michael@0 | 441 | RectF dangerMargins = velocityBiasedMargins(dangerZoneX, dangerZoneY, velocity); |
michael@0 | 442 | dangerMargins = shiftMarginsForPageBounds(dangerMargins, metrics); |
michael@0 | 443 | |
michael@0 | 444 | // we're about to checkerboard if the current viewport area + the danger zone margins |
michael@0 | 445 | // fall out of the current displayport anywhere. |
michael@0 | 446 | RectF adjustedViewport = new RectF( |
michael@0 | 447 | metrics.viewportRectLeft - dangerMargins.left, |
michael@0 | 448 | metrics.viewportRectTop - dangerMargins.top, |
michael@0 | 449 | metrics.viewportRectRight + dangerMargins.right, |
michael@0 | 450 | metrics.viewportRectBottom + dangerMargins.bottom); |
michael@0 | 451 | return !displayPort.contains(adjustedViewport); |
michael@0 | 452 | } |
michael@0 | 453 | |
michael@0 | 454 | @Override |
michael@0 | 455 | public String toString() { |
michael@0 | 456 | return "VelocityBiasStrategy mult=" + SIZE_MULTIPLIER + ", threshold=" + VELOCITY_THRESHOLD + ", reverse=" + REVERSE_BUFFER |
michael@0 | 457 | + ", dangerBaseX=" + DANGER_ZONE_BASE_X_MULTIPLIER + ", dangerBaseY=" + DANGER_ZONE_BASE_Y_MULTIPLIER |
michael@0 | 458 | + ", dangerIncrX=" + DANGER_ZONE_INCR_Y_MULTIPLIER + ", dangerIncrY=" + DANGER_ZONE_INCR_Y_MULTIPLIER; |
michael@0 | 459 | } |
michael@0 | 460 | } |
michael@0 | 461 | |
michael@0 | 462 | /** |
michael@0 | 463 | * This class implements the variation where we draw more of the page at low resolution while panning. |
michael@0 | 464 | * In this variation, as we pan faster, we increase the page area we are drawing, but reduce the draw |
michael@0 | 465 | * resolution to compensate. This results in the same device-pixel area drawn; the compositor then |
michael@0 | 466 | * scales this up to the viewport zoom level. This results in a large area of the page drawn but it |
michael@0 | 467 | * looks blurry. The assumption is that drawing extra that we never display is better than checkerboarding, |
michael@0 | 468 | * where we draw less but never even show it on the screen. |
michael@0 | 469 | */ |
michael@0 | 470 | private static class DynamicResolutionStrategy extends DisplayPortStrategy { |
michael@0 | 471 | // The length of each axis of the display port will be the corresponding view length |
michael@0 | 472 | // multiplied by this factor. |
michael@0 | 473 | private static final float SIZE_MULTIPLIER = 1.5f; |
michael@0 | 474 | |
michael@0 | 475 | // The velocity above which we start zooming out the display port to keep up |
michael@0 | 476 | // with the panning. |
michael@0 | 477 | private static final float VELOCITY_EXPANSION_THRESHOLD = GeckoAppShell.getDpi() / 16f; |
michael@0 | 478 | |
michael@0 | 479 | // How much we increase the display port based on velocity. Assuming no friction and |
michael@0 | 480 | // splitting (see below), this should be be the number of frames (@60fps) between us |
michael@0 | 481 | // calculating the display port and the draw of the *next* display port getting composited |
michael@0 | 482 | // and displayed on the screen. This is because the timeline looks like this: |
michael@0 | 483 | // Java: pan pan pan pan pan pan ! pan pan pan pan pan pan ! |
michael@0 | 484 | // Gecko: \-> draw -> composite / \-> draw -> composite / |
michael@0 | 485 | // The display port calculated on the first "pan" gets composited to the screen at the |
michael@0 | 486 | // first exclamation mark, and remains on the screen until the second exclamation mark. |
michael@0 | 487 | // In order to avoid checkerboarding, that display port must be able to contain all of |
michael@0 | 488 | // the panning until the second exclamation mark, which encompasses two entire draw/composite |
michael@0 | 489 | // cycles. |
michael@0 | 490 | // If we take into account friction, our velocity multiplier should be reduced as the |
michael@0 | 491 | // amount of pan will decrease each time. If we take into account display port splitting, |
michael@0 | 492 | // it should be increased as the splitting means some of the display port will be used to |
michael@0 | 493 | // draw in the opposite direction of the velocity. For now I'm assuming these two cancel |
michael@0 | 494 | // each other out. |
michael@0 | 495 | private static final float VELOCITY_MULTIPLIER = 60.0f; |
michael@0 | 496 | |
michael@0 | 497 | // The following constants adjust how biased the display port is in the direction of panning. |
michael@0 | 498 | // When panning fast (above the FAST_THRESHOLD) we use the fast split factor to split the |
michael@0 | 499 | // display port "buffer" area, otherwise we use the slow split factor. This is based on the |
michael@0 | 500 | // assumption that if the user is panning fast, they are less likely to reverse directions |
michael@0 | 501 | // and go backwards, so we should spend more of our display port buffer in the direction of |
michael@0 | 502 | // panning. |
michael@0 | 503 | private static final float VELOCITY_FAST_THRESHOLD = VELOCITY_EXPANSION_THRESHOLD * 2.0f; |
michael@0 | 504 | private static final float FAST_SPLIT_FACTOR = 0.95f; |
michael@0 | 505 | private static final float SLOW_SPLIT_FACTOR = 0.8f; |
michael@0 | 506 | |
michael@0 | 507 | // The following constants are used for viewport prediction; we use them to estimate where |
michael@0 | 508 | // the viewport will be soon and whether or not we should trigger a draw right now. "soon" |
michael@0 | 509 | // in the previous sentence really refers to the amount of time it would take to draw and |
michael@0 | 510 | // composite from the point at which we do the calculation, and that is not really a known |
michael@0 | 511 | // quantity. The velocity multiplier is how much we multiply the velocity by; it has the |
michael@0 | 512 | // same caveats as the VELOCITY_MULTIPLIER above except that it only needs to take into account |
michael@0 | 513 | // one draw/composite cycle instead of two. The danger zone multiplier is a multiplier of the |
michael@0 | 514 | // viewport size that we use as an extra "danger zone" around the viewport; if this danger |
michael@0 | 515 | // zone falls outside the display port then we are approaching the point at which we will |
michael@0 | 516 | // checkerboard, and hence should start drawing. Note that if DANGER_ZONE_MULTIPLIER is |
michael@0 | 517 | // greater than (SIZE_MULTIPLIER - 1.0f), then at zero velocity we will always be in the |
michael@0 | 518 | // danger zone, and thus will be constantly drawing. |
michael@0 | 519 | private static final float PREDICTION_VELOCITY_MULTIPLIER = 30.0f; |
michael@0 | 520 | private static final float DANGER_ZONE_MULTIPLIER = 0.20f; // must be less than (SIZE_MULTIPLIER - 1.0f) |
michael@0 | 521 | |
michael@0 | 522 | DynamicResolutionStrategy(Map<String, Integer> prefs) { |
michael@0 | 523 | // ignore prefs for now |
michael@0 | 524 | } |
michael@0 | 525 | |
michael@0 | 526 | @Override |
michael@0 | 527 | public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) { |
michael@0 | 528 | float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER; |
michael@0 | 529 | float displayPortHeight = metrics.getHeight() * SIZE_MULTIPLIER; |
michael@0 | 530 | |
michael@0 | 531 | // for resolution calculation purposes, we need to know what the adjusted display port dimensions |
michael@0 | 532 | // would be if we had zero velocity, so calculate that here before we increase the display port |
michael@0 | 533 | // based on velocity. |
michael@0 | 534 | FloatSize reshapedSize = reshapeForPage(displayPortWidth, displayPortHeight, metrics); |
michael@0 | 535 | |
michael@0 | 536 | // increase displayPortWidth and displayPortHeight based on the velocity, but maintaining their |
michael@0 | 537 | // relative aspect ratio. |
michael@0 | 538 | if (velocity.length() > VELOCITY_EXPANSION_THRESHOLD) { |
michael@0 | 539 | float velocityFactor = Math.max(Math.abs(velocity.x) / displayPortWidth, |
michael@0 | 540 | Math.abs(velocity.y) / displayPortHeight); |
michael@0 | 541 | velocityFactor *= VELOCITY_MULTIPLIER; |
michael@0 | 542 | |
michael@0 | 543 | displayPortWidth += (displayPortWidth * velocityFactor); |
michael@0 | 544 | displayPortHeight += (displayPortHeight * velocityFactor); |
michael@0 | 545 | } |
michael@0 | 546 | |
michael@0 | 547 | // at this point, displayPortWidth and displayPortHeight are how much of the page (in device pixels) |
michael@0 | 548 | // we want to be rendered by Gecko. Note here "device pixels" is equivalent to CSS pixels multiplied |
michael@0 | 549 | // by metrics.zoomFactor |
michael@0 | 550 | |
michael@0 | 551 | // we need to avoid having a display port that is larger than the page, or we will end up |
michael@0 | 552 | // painting things outside the page bounds (bug 729169). we simultaneously need to make |
michael@0 | 553 | // the display port as large as possible so that we redraw less. reshape the display |
michael@0 | 554 | // port dimensions to accomplish this. this may change the aspect ratio of the display port, |
michael@0 | 555 | // but we are assuming that this is desirable because the advantages from pre-drawing will |
michael@0 | 556 | // outweigh the disadvantages from any buffer reallocations that might occur. |
michael@0 | 557 | FloatSize usableSize = reshapeForPage(displayPortWidth, displayPortHeight, metrics); |
michael@0 | 558 | float horizontalBuffer = usableSize.width - metrics.getWidth(); |
michael@0 | 559 | float verticalBuffer = usableSize.height - metrics.getHeight(); |
michael@0 | 560 | |
michael@0 | 561 | // at this point, horizontalBuffer and verticalBuffer are the dimensions of the buffer area we have. |
michael@0 | 562 | // the buffer area is the off-screen area that is part of the display port and will be pre-drawn in case |
michael@0 | 563 | // the user scrolls there. we now need to split the buffer area on each axis so that we know |
michael@0 | 564 | // what the exact margins on each side will be. first we split the buffer amount based on the direction |
michael@0 | 565 | // we're moving, so that we have a larger buffer in the direction of travel. |
michael@0 | 566 | RectF margins = new RectF(); |
michael@0 | 567 | margins.left = splitBufferByVelocity(horizontalBuffer, velocity.x); |
michael@0 | 568 | margins.right = horizontalBuffer - margins.left; |
michael@0 | 569 | margins.top = splitBufferByVelocity(verticalBuffer, velocity.y); |
michael@0 | 570 | margins.bottom = verticalBuffer - margins.top; |
michael@0 | 571 | |
michael@0 | 572 | // then, we account for running into the page bounds - so that if we hit the top of the page, we need |
michael@0 | 573 | // to drop the top margin and move that amount to the bottom margin. |
michael@0 | 574 | margins = shiftMarginsForPageBounds(margins, metrics); |
michael@0 | 575 | |
michael@0 | 576 | // finally, we calculate the resolution we want to render the display port area at. We do this |
michael@0 | 577 | // so that as we expand the display port area (because of velocity), we reduce the resolution of |
michael@0 | 578 | // the painted area so as to maintain the size of the buffer Gecko is painting into. we calculate |
michael@0 | 579 | // the reduction in resolution by comparing the display port size with and without the velocity |
michael@0 | 580 | // changes applied. |
michael@0 | 581 | // this effectively means that as we pan faster and faster, the display port grows, but we paint |
michael@0 | 582 | // at lower resolutions. this paints more area to reduce checkerboard at the cost of increasing |
michael@0 | 583 | // compositor-scaling and blurriness. Once we stop panning, the blurriness must be entirely gone. |
michael@0 | 584 | // Note that usable* could be less than base* if we are pinch-zoomed out into overscroll, so we |
michael@0 | 585 | // clamp it to make sure this doesn't increase our display resolution past metrics.zoomFactor. |
michael@0 | 586 | float scaleFactor = Math.min(reshapedSize.width / usableSize.width, reshapedSize.height / usableSize.height); |
michael@0 | 587 | float displayResolution = metrics.zoomFactor * Math.min(1.0f, scaleFactor); |
michael@0 | 588 | |
michael@0 | 589 | DisplayPortMetrics dpMetrics = new DisplayPortMetrics( |
michael@0 | 590 | metrics.viewportRectLeft - margins.left, |
michael@0 | 591 | metrics.viewportRectTop - margins.top, |
michael@0 | 592 | metrics.viewportRectRight + margins.right, |
michael@0 | 593 | metrics.viewportRectBottom + margins.bottom, |
michael@0 | 594 | displayResolution); |
michael@0 | 595 | return dpMetrics; |
michael@0 | 596 | } |
michael@0 | 597 | |
michael@0 | 598 | /** |
michael@0 | 599 | * Split the given buffer amount into two based on the velocity. |
michael@0 | 600 | * Given an amount of total usable buffer on an axis, this will |
michael@0 | 601 | * return the amount that should be used on the left/top side of |
michael@0 | 602 | * the axis (the side which a negative velocity vector corresponds |
michael@0 | 603 | * to). |
michael@0 | 604 | */ |
michael@0 | 605 | private float splitBufferByVelocity(float amount, float velocity) { |
michael@0 | 606 | // if no velocity, so split evenly |
michael@0 | 607 | if (FloatUtils.fuzzyEquals(velocity, 0)) { |
michael@0 | 608 | return amount / 2.0f; |
michael@0 | 609 | } |
michael@0 | 610 | // if we're moving quickly, assign more of the amount in that direction |
michael@0 | 611 | // since is is less likely that we will reverse direction immediately |
michael@0 | 612 | if (velocity < -VELOCITY_FAST_THRESHOLD) { |
michael@0 | 613 | return amount * FAST_SPLIT_FACTOR; |
michael@0 | 614 | } |
michael@0 | 615 | if (velocity > VELOCITY_FAST_THRESHOLD) { |
michael@0 | 616 | return amount * (1.0f - FAST_SPLIT_FACTOR); |
michael@0 | 617 | } |
michael@0 | 618 | // if we're moving slowly, then assign less of the amount in that direction |
michael@0 | 619 | if (velocity < 0) { |
michael@0 | 620 | return amount * SLOW_SPLIT_FACTOR; |
michael@0 | 621 | } else { |
michael@0 | 622 | return amount * (1.0f - SLOW_SPLIT_FACTOR); |
michael@0 | 623 | } |
michael@0 | 624 | } |
michael@0 | 625 | |
michael@0 | 626 | @Override |
michael@0 | 627 | public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) { |
michael@0 | 628 | // Expand the viewport based on our velocity (and clamp it to page boundaries). |
michael@0 | 629 | // Then intersect it with the last-requested displayport to determine whether we're |
michael@0 | 630 | // close to checkerboarding. |
michael@0 | 631 | |
michael@0 | 632 | RectF predictedViewport = metrics.getViewport(); |
michael@0 | 633 | |
michael@0 | 634 | // first we expand the viewport in the direction we're moving based on some |
michael@0 | 635 | // multiple of the current velocity. |
michael@0 | 636 | if (velocity.length() > 0) { |
michael@0 | 637 | if (velocity.x < 0) { |
michael@0 | 638 | predictedViewport.left += velocity.x * PREDICTION_VELOCITY_MULTIPLIER; |
michael@0 | 639 | } else if (velocity.x > 0) { |
michael@0 | 640 | predictedViewport.right += velocity.x * PREDICTION_VELOCITY_MULTIPLIER; |
michael@0 | 641 | } |
michael@0 | 642 | |
michael@0 | 643 | if (velocity.y < 0) { |
michael@0 | 644 | predictedViewport.top += velocity.y * PREDICTION_VELOCITY_MULTIPLIER; |
michael@0 | 645 | } else if (velocity.y > 0) { |
michael@0 | 646 | predictedViewport.bottom += velocity.y * PREDICTION_VELOCITY_MULTIPLIER; |
michael@0 | 647 | } |
michael@0 | 648 | } |
michael@0 | 649 | |
michael@0 | 650 | // then we expand the viewport evenly in all directions just to have an extra |
michael@0 | 651 | // safety zone. this also clamps it to page bounds. |
michael@0 | 652 | predictedViewport = expandByDangerZone(predictedViewport, DANGER_ZONE_MULTIPLIER, DANGER_ZONE_MULTIPLIER, metrics); |
michael@0 | 653 | return !displayPort.contains(predictedViewport); |
michael@0 | 654 | } |
michael@0 | 655 | |
michael@0 | 656 | @Override |
michael@0 | 657 | public String toString() { |
michael@0 | 658 | return "DynamicResolutionStrategy"; |
michael@0 | 659 | } |
michael@0 | 660 | } |
michael@0 | 661 | |
michael@0 | 662 | /** |
michael@0 | 663 | * This class implements the variation where we use the draw time to predict where we will be when |
michael@0 | 664 | * a draw completes, and draw that instead of where we are now. In this variation, when our panning |
michael@0 | 665 | * speed drops below a certain threshold, we draw 9 viewports' worth of content so that the user can |
michael@0 | 666 | * pan in any direction without encountering checkerboarding. |
michael@0 | 667 | * Once the user is panning, we modify the displayport to encompass an area range of where we think |
michael@0 | 668 | * the user will be when the draw completes. This heuristic relies on both the estimated draw time |
michael@0 | 669 | * the panning velocity; unexpected changes in either of these values will cause the heuristic to |
michael@0 | 670 | * fail and show checkerboard. |
michael@0 | 671 | */ |
michael@0 | 672 | private static class PredictionBiasStrategy extends DisplayPortStrategy { |
michael@0 | 673 | private static float VELOCITY_THRESHOLD; |
michael@0 | 674 | |
michael@0 | 675 | private int mPixelArea; // area of the viewport, used in draw time calculations |
michael@0 | 676 | private int mMinFramesToDraw; // minimum number of frames we take to draw |
michael@0 | 677 | private int mMaxFramesToDraw; // maximum number of frames we take to draw |
michael@0 | 678 | |
michael@0 | 679 | PredictionBiasStrategy(Map<String, Integer> prefs) { |
michael@0 | 680 | VELOCITY_THRESHOLD = GeckoAppShell.getDpi() * getFloatPref(prefs, PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD, 16); |
michael@0 | 681 | resetPageState(); |
michael@0 | 682 | } |
michael@0 | 683 | |
michael@0 | 684 | @Override |
michael@0 | 685 | public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) { |
michael@0 | 686 | float width = metrics.getWidth(); |
michael@0 | 687 | float height = metrics.getHeight(); |
michael@0 | 688 | mPixelArea = (int)(width * height); |
michael@0 | 689 | |
michael@0 | 690 | if (velocity.length() < VELOCITY_THRESHOLD) { |
michael@0 | 691 | // if we're going slow, expand the displayport to 9x viewport size |
michael@0 | 692 | RectF margins = new RectF(width, height, width, height); |
michael@0 | 693 | return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics); |
michael@0 | 694 | } |
michael@0 | 695 | |
michael@0 | 696 | // figure out how far we expect to be |
michael@0 | 697 | float minDx = velocity.x * mMinFramesToDraw; |
michael@0 | 698 | float minDy = velocity.y * mMinFramesToDraw; |
michael@0 | 699 | float maxDx = velocity.x * mMaxFramesToDraw; |
michael@0 | 700 | float maxDy = velocity.y * mMaxFramesToDraw; |
michael@0 | 701 | |
michael@0 | 702 | // figure out how many pixels we will be drawing when we draw the above-calculated range. |
michael@0 | 703 | // this will be larger than the viewport area. |
michael@0 | 704 | float pixelsToDraw = (width + Math.abs(maxDx - minDx)) * (height + Math.abs(maxDy - minDy)); |
michael@0 | 705 | // adjust how far we will get because of the time spent drawing all these extra pixels. this |
michael@0 | 706 | // will again increase the number of pixels drawn so really we could keep iterating this over |
michael@0 | 707 | // and over, but once seems enough for now. |
michael@0 | 708 | maxDx = maxDx * pixelsToDraw / mPixelArea; |
michael@0 | 709 | maxDy = maxDy * pixelsToDraw / mPixelArea; |
michael@0 | 710 | |
michael@0 | 711 | // and finally generate the displayport. the min/max stuff takes care of |
michael@0 | 712 | // negative velocities as well as positive. |
michael@0 | 713 | RectF margins = new RectF( |
michael@0 | 714 | -Math.min(minDx, maxDx), |
michael@0 | 715 | -Math.min(minDy, maxDy), |
michael@0 | 716 | Math.max(minDx, maxDx), |
michael@0 | 717 | Math.max(minDy, maxDy)); |
michael@0 | 718 | return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics); |
michael@0 | 719 | } |
michael@0 | 720 | |
michael@0 | 721 | @Override |
michael@0 | 722 | public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) { |
michael@0 | 723 | // the code below is the same as in calculate() but is awkward to refactor since it has multiple outputs. |
michael@0 | 724 | // refer to the comments in calculate() to understand what this is doing. |
michael@0 | 725 | float minDx = velocity.x * mMinFramesToDraw; |
michael@0 | 726 | float minDy = velocity.y * mMinFramesToDraw; |
michael@0 | 727 | float maxDx = velocity.x * mMaxFramesToDraw; |
michael@0 | 728 | float maxDy = velocity.y * mMaxFramesToDraw; |
michael@0 | 729 | float pixelsToDraw = (metrics.getWidth() + Math.abs(maxDx - minDx)) * (metrics.getHeight() + Math.abs(maxDy - minDy)); |
michael@0 | 730 | maxDx = maxDx * pixelsToDraw / mPixelArea; |
michael@0 | 731 | maxDy = maxDy * pixelsToDraw / mPixelArea; |
michael@0 | 732 | |
michael@0 | 733 | // now that we have an idea of how far we will be when the draw completes, take the farthest |
michael@0 | 734 | // end of that range and see if it falls outside the displayport bounds. if it does, allow |
michael@0 | 735 | // the draw to go through |
michael@0 | 736 | RectF predictedViewport = metrics.getViewport(); |
michael@0 | 737 | predictedViewport.left += maxDx; |
michael@0 | 738 | predictedViewport.top += maxDy; |
michael@0 | 739 | predictedViewport.right += maxDx; |
michael@0 | 740 | predictedViewport.bottom += maxDy; |
michael@0 | 741 | |
michael@0 | 742 | predictedViewport = clampToPageBounds(predictedViewport, metrics); |
michael@0 | 743 | return !displayPort.contains(predictedViewport); |
michael@0 | 744 | } |
michael@0 | 745 | |
michael@0 | 746 | @Override |
michael@0 | 747 | public boolean drawTimeUpdate(long millis, int pixels) { |
michael@0 | 748 | // calculate the number of frames it took to draw a viewport-sized area |
michael@0 | 749 | float normalizedTime = (float)mPixelArea * (float)millis / (float)pixels; |
michael@0 | 750 | int normalizedFrames = (int)FloatMath.ceil(normalizedTime * 60f / 1000f); |
michael@0 | 751 | // broaden our range on how long it takes to draw if the draw falls outside |
michael@0 | 752 | // the range. this allows it to grow gradually. this heuristic may need to |
michael@0 | 753 | // be tweaked into more of a floating window average or something. |
michael@0 | 754 | if (normalizedFrames <= mMinFramesToDraw) { |
michael@0 | 755 | mMinFramesToDraw--; |
michael@0 | 756 | } else if (normalizedFrames > mMaxFramesToDraw) { |
michael@0 | 757 | mMaxFramesToDraw++; |
michael@0 | 758 | } else { |
michael@0 | 759 | return true; |
michael@0 | 760 | } |
michael@0 | 761 | Log.d(LOGTAG, "Widened draw range to [" + mMinFramesToDraw + ", " + mMaxFramesToDraw + "]"); |
michael@0 | 762 | return true; |
michael@0 | 763 | } |
michael@0 | 764 | |
michael@0 | 765 | @Override |
michael@0 | 766 | public void resetPageState() { |
michael@0 | 767 | mMinFramesToDraw = 0; |
michael@0 | 768 | mMaxFramesToDraw = 2; |
michael@0 | 769 | } |
michael@0 | 770 | |
michael@0 | 771 | @Override |
michael@0 | 772 | public String toString() { |
michael@0 | 773 | return "PredictionBiasStrategy threshold=" + VELOCITY_THRESHOLD; |
michael@0 | 774 | } |
michael@0 | 775 | } |
michael@0 | 776 | } |