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