File size: 2,850 Bytes
4635d45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102a1b6
 
 
4635d45
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// Runtime performance monitor for the live canvas. It measures the time spent
// in our own frame work (not time while a tab is backgrounded) and uses
// hysteresis, so one GC pause never makes quality visibly flicker.
export class RenderPerformance {
  constructor({ frameBudgetMs, degradeAfterMs, recoverAfterMs }, preference = "auto") {
    this.frameBudgetMs = frameBudgetMs;
    this.degradeAfterMs = degradeAfterMs;
    this.recoverAfterMs = recoverAfterMs;
    this.mode = "full";
    this.workMs = 0;
    this.slowSince = null;
    this.nextProbeAt = null;
    this.probing = false;
    this.setPreference(preference);
  }

  setPreference(preference) {
    this.preference = ["auto", "full", "economy"].includes(preference) ? preference : "auto";
    this.slowSince = null;
    this.nextProbeAt = null;
    this.probing = false;
    // Returning to auto starts a fresh high-quality measurement window. Keeping
    // an earlier forced economy mode here would leave it with no scheduled probe.
    this.mode = this.preference === "economy" ? "economy" : "full";
  }

  // In economy mode, periodically allow exactly one full-quality frame. The
  // result tells us whether CPU headroom really returned; simply measuring the
  // cheap sprite path would otherwise make a slow device oscillate forever.
  useEconomy(now) {
    if (this.preference === "economy") return true;
    if (this.preference === "full") return false;
    if (this.mode !== "economy") return false;
    if (this.nextProbeAt !== null && now >= this.nextProbeAt) {
      this.probing = true;
      return false;
    }
    return true;
  }

  record(now, workMs) {
    this.workMs = this.workMs ? this.workMs * 0.85 + workMs * 0.15 : workMs;
    if (this.preference !== "auto") return;
    // A probe must be judged on its own full-quality frame. The smoothed value
    // includes cheap economy frames and would incorrectly promote a slow device.
    const slow = (this.probing ? workMs : this.workMs) > this.frameBudgetMs;
    if (this.mode === "full") {
      this.slowSince = slow ? (this.slowSince ?? now) : null;
      if (this.slowSince !== null && now - this.slowSince >= this.degradeAfterMs) {
        this.mode = "economy";
        this.slowSince = null;
        this.nextProbeAt = now + this.recoverAfterMs;
      }
    } else if (this.probing) {
      this.probing = false;
      if (!slow) {
        this.mode = "full";
        this.nextProbeAt = null;
      } else {
        this.nextProbeAt = now + this.recoverAfterMs;
      }
    }
  }

  get economical() { return this.mode === "economy"; }

  label() {
    const policy = this.preference === "full" ? "always high quality"
      : this.preference === "economy" ? "always power saving"
        : this.economical ? "auto power saving" : "auto";
    return `${this.workMs.toFixed(0)}ms · ${policy}`;
  }
}