ntk 8.11.0 → 8.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/window.js +114 -8
- package/package.json +1 -1
package/lib/window.js
CHANGED
|
@@ -109,6 +109,32 @@ const REFRESH_QUANTILE = 0.25;
|
|
|
109
109
|
const STALL_TIMEOUT = 2000;
|
|
110
110
|
const STALL_TIMEOUT_FIRST = 250;
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* The frame period Present makes up for a screen with no vertical blank to
|
|
114
|
+
* offer it, in µs: 60Hz, which xorg-server 21.1 and later compute as
|
|
115
|
+
* `1000000 / 60` and 1.20 and earlier wrote down as 16667.
|
|
116
|
+
*
|
|
117
|
+
* A server started with `-fakescreenfps` at another rate is not recognised.
|
|
118
|
+
* It was asked for that clock, and keeps it.
|
|
119
|
+
*/
|
|
120
|
+
const FAKE_MSC_INTERVALS = [16666, 16667];
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* How much of the counter a made-up msc has to account for before a window
|
|
124
|
+
* believes it, in vblanks: about a quarter of a second.
|
|
125
|
+
*
|
|
126
|
+
* One completion that fits proves little. A display's counter starts after
|
|
127
|
+
* the server's clock does, so its `ust / msc` falls toward its own period as
|
|
128
|
+
* it runs, and a display a little faster than 60Hz passes through the
|
|
129
|
+
* made-up line on the way down. It stays within half a tick of the line for
|
|
130
|
+
* `interval / (interval - period)` vblanks, so a fit across this span rules
|
|
131
|
+
* out every display more than `interval / span` µs faster than the tick, which
|
|
132
|
+
* is anything past 64Hz. A display closer than that fits only while its
|
|
133
|
+
* counter crosses the line, and only a window whose first frames land in that
|
|
134
|
+
* stretch is fooled.
|
|
135
|
+
*/
|
|
136
|
+
const SYNTHETIC_MSC_SPAN = 16;
|
|
137
|
+
|
|
112
138
|
/**
|
|
113
139
|
* MotionNotify detail saying "this is the only motion you get until you ask".
|
|
114
140
|
*
|
|
@@ -476,7 +502,9 @@ export default class Window extends Drawable {
|
|
|
476
502
|
this._frameSyncEnabled = args.frameSync !== false;
|
|
477
503
|
// which signal ends a frame (see _clockOnPresent): 'auto' takes the
|
|
478
504
|
// display's own vblank where the Present path can supply it and the fence
|
|
479
|
-
// everywhere else,
|
|
505
|
+
// everywhere else, including a server whose Present has no display behind
|
|
506
|
+
// it (see _probeMsc); 'present' takes Present's clock even there; 'fence'
|
|
507
|
+
// pins the round-trip clock
|
|
480
508
|
this._frameClockPref = args.frameClock ?? 'auto';
|
|
481
509
|
// _NET_WM_SYNC_REQUEST (see enableSyncRequest): the counter the window
|
|
482
510
|
// manager watches, the value it last asked for, and the watchdog that
|
|
@@ -520,6 +548,12 @@ export default class Window extends Drawable {
|
|
|
520
548
|
this._refreshSamples = [];
|
|
521
549
|
this._lastComplete = null;
|
|
522
550
|
this._clockStalled = false;
|
|
551
|
+
// Whether the msc on those completions is a display's count or a number
|
|
552
|
+
// the server works out from its clock (see _probeMsc): while undecided,
|
|
553
|
+
// the fake intervals every completion so far fits and the msc of the
|
|
554
|
+
// first of them; then the verdict.
|
|
555
|
+
this._mscProbe = { intervals: FAKE_MSC_INTERVALS, since: null };
|
|
556
|
+
this._mscSynthetic = false;
|
|
523
557
|
// `frameInterval` is a plain number, but whether the caller set it is what
|
|
524
558
|
// decides its meaning under the vblank clock — an explicit value caps the
|
|
525
559
|
// frame rate, an unasked-for default must not (see _armTimer). Assigning
|
|
@@ -1311,6 +1345,8 @@ export default class Window extends Drawable {
|
|
|
1311
1345
|
* - a fence, everywhere else: a cheap request with a reply
|
|
1312
1346
|
* (GetInputFocus) sent after each frame; X processes requests in order,
|
|
1313
1347
|
* so its reply confirms the server consumed everything the frame drew.
|
|
1348
|
+
* That includes a server whose Present has no display behind it and
|
|
1349
|
+
* makes its vertical blanks up from a timer (see _probeMsc).
|
|
1314
1350
|
*
|
|
1315
1351
|
* Either way at most one is outstanding, which is what bounds the work in
|
|
1316
1352
|
* flight: on a slow connection frames degrade to one per round-trip instead
|
|
@@ -1379,14 +1415,15 @@ export default class Window extends Drawable {
|
|
|
1379
1415
|
/**
|
|
1380
1416
|
* Which clock is ending this window's frames right now: 'present' when the
|
|
1381
1417
|
* display is, 'fence' when the server round-trip is. Assignable with
|
|
1382
|
-
* 'auto' (prefer the display)
|
|
1418
|
+
* 'auto' (prefer the display), 'present' (prefer Present's clock even
|
|
1419
|
+
* where no display is behind it) or 'fence' (never use it).
|
|
1383
1420
|
*/
|
|
1384
1421
|
get frameClock() {
|
|
1385
1422
|
return this._clockOnPresent() ? 'present' : 'fence';
|
|
1386
1423
|
}
|
|
1387
1424
|
|
|
1388
1425
|
set frameClock(mode) {
|
|
1389
|
-
this._frameClockPref = mode
|
|
1426
|
+
this._frameClockPref = mode;
|
|
1390
1427
|
if (this._frameClockPref !== 'fence') this._selectPresentInput();
|
|
1391
1428
|
}
|
|
1392
1429
|
|
|
@@ -1400,21 +1437,35 @@ export default class Window extends Drawable {
|
|
|
1400
1437
|
* completion that never came drops the window back to the fence until one
|
|
1401
1438
|
* does, so a clock that stops cannot stop the window with it.
|
|
1402
1439
|
*
|
|
1403
|
-
* `
|
|
1404
|
-
*
|
|
1405
|
-
* completions the clock is made of.
|
|
1440
|
+
* `_presentAbandoned` is the one-way door — a window whose server turned
|
|
1441
|
+
* out unable to do what the path is for is off it from then on, and its
|
|
1442
|
+
* blits no longer produce the completions the clock is made of.
|
|
1406
1443
|
*/
|
|
1407
1444
|
_clockOnPresent() {
|
|
1408
1445
|
return (
|
|
1409
1446
|
this._frameClockPref !== 'fence' &&
|
|
1410
1447
|
this._frameSyncEnabled &&
|
|
1411
1448
|
!this._clockStalled &&
|
|
1412
|
-
!this.
|
|
1449
|
+
!this._presentAbandoned() &&
|
|
1413
1450
|
!!this._presentExt &&
|
|
1414
1451
|
!!this._presentEid
|
|
1415
1452
|
);
|
|
1416
1453
|
}
|
|
1417
1454
|
|
|
1455
|
+
/**
|
|
1456
|
+
* Has this window stopped presenting through the extension, the blit along
|
|
1457
|
+
* with the clock, because of what its server turned out to be?
|
|
1458
|
+
*
|
|
1459
|
+
* Two things close the door. A server that flipped a present sent with
|
|
1460
|
+
* Option.Copy owns the pixmap it was handed (see _handlePresentEvent). A
|
|
1461
|
+
* server whose msc is made up has no display behind the extension, and
|
|
1462
|
+
* only holds each copy back for a timer (see _probeMsc) — that one stays
|
|
1463
|
+
* open to a caller who asked for Present's clock by name.
|
|
1464
|
+
*/
|
|
1465
|
+
_presentAbandoned() {
|
|
1466
|
+
return !!this._presentFlipped || (this._mscSynthetic && this._frameClockPref !== 'present');
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1418
1469
|
_runFrame() {
|
|
1419
1470
|
const f = this._frame;
|
|
1420
1471
|
if (
|
|
@@ -1823,6 +1874,7 @@ export default class Window extends Drawable {
|
|
|
1823
1874
|
f.presentInFlight = false;
|
|
1824
1875
|
this.frameLatency = now - f.presentAt;
|
|
1825
1876
|
this._sampleRefresh(ev, now);
|
|
1877
|
+
this._probeMsc(ev);
|
|
1826
1878
|
this._frameEnded();
|
|
1827
1879
|
}
|
|
1828
1880
|
|
|
@@ -1890,6 +1942,60 @@ export default class Window extends Drawable {
|
|
|
1890
1942
|
this.refreshInterval = sorted[Math.floor(REFRESH_QUANTILE * (sorted.length - 1))];
|
|
1891
1943
|
}
|
|
1892
1944
|
|
|
1945
|
+
/**
|
|
1946
|
+
* Is a display counting these frames, or is the server making the count up?
|
|
1947
|
+
*
|
|
1948
|
+
* Present waits for vertical blanks, and a screen whose driver has none to
|
|
1949
|
+
* offer gets fake ones: xserver's present_fake.c computes the msc from its
|
|
1950
|
+
* clock, `(ust + interval / 2) / interval`, and completes a present when a
|
|
1951
|
+
* timer set for the next tick fires. That is XQuartz, Xvfb, Xephyr, Xnest,
|
|
1952
|
+
* the VNC servers and Xorg on a driver without Present support. The
|
|
1953
|
+
* vblank clock buys nothing there and costs throughput. It paces to a 60Hz
|
|
1954
|
+
* timer rather than to the panel, which on XQuartz is the macOS
|
|
1955
|
+
* compositor's business. And a present arriving past the middle of a
|
|
1956
|
+
* period already counts as the next tick, and is aimed at the one after
|
|
1957
|
+
* that, so a frame that takes more than half a period to turn round waits
|
|
1958
|
+
* two (issue #366).
|
|
1959
|
+
*
|
|
1960
|
+
* The blit goes with the clock, as it does for a server that flips: the
|
|
1961
|
+
* copy waits for the same timer, so a window on the fence would draw
|
|
1962
|
+
* faster yet still update at most 60 times a second, and a copy could
|
|
1963
|
+
* read a frame half drawn, because the fence reply only says the server
|
|
1964
|
+
* has read the present, not run it. CopyArea runs in request order.
|
|
1965
|
+
*
|
|
1966
|
+
* So the question is whether `msc` is `ust` rounded to the nearest tick.
|
|
1967
|
+
* A display's counter cannot keep to that for long (SYNTHETIC_MSC_SPAN),
|
|
1968
|
+
* and a single completion off the line settles it the other way. Either
|
|
1969
|
+
* verdict is final: whether Present has a driver behind it is settled per
|
|
1970
|
+
* screen, when the server starts.
|
|
1971
|
+
*/
|
|
1972
|
+
_probeMsc(ev) {
|
|
1973
|
+
const probe = this._mscProbe;
|
|
1974
|
+
if (!probe) return;
|
|
1975
|
+
const intervals = probe.intervals.filter((interval) => {
|
|
1976
|
+
// msc === floor((ust + half) / interval), in integers
|
|
1977
|
+
const half = Math.floor(interval / 2);
|
|
1978
|
+
const offset = ev.ust - ev.msc * interval;
|
|
1979
|
+
return offset >= -half && offset < interval - half;
|
|
1980
|
+
});
|
|
1981
|
+
if (!intervals.length) {
|
|
1982
|
+
this._mscProbe = null; // a count, so a display
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
probe.intervals = intervals;
|
|
1986
|
+
probe.since ??= ev.msc;
|
|
1987
|
+
if (ev.msc - probe.since < SYNTHETIC_MSC_SPAN) return;
|
|
1988
|
+
this._mscProbe = null;
|
|
1989
|
+
this._mscSynthetic = true;
|
|
1990
|
+
// What was measured was a timer: no display period to report, and no
|
|
1991
|
+
// display to have dropped frames on. A window kept on this clock by
|
|
1992
|
+
// `frameClock: 'present'` learns the timer's period again from here.
|
|
1993
|
+
this.refreshInterval = null;
|
|
1994
|
+
this._refreshSamples = [];
|
|
1995
|
+
this._lastComplete = null;
|
|
1996
|
+
this.droppedFrames = 0;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1893
1999
|
/**
|
|
1894
2000
|
* Guarantee that a frame ends even if its completion does not arrive.
|
|
1895
2001
|
*
|
|
@@ -2235,7 +2341,7 @@ export default class Window extends Drawable {
|
|
|
2235
2341
|
_presentWithExtension(rects) {
|
|
2236
2342
|
const P = this._presentExt;
|
|
2237
2343
|
if (!P || !this._fixesExt || !this._updateRegion) return false;
|
|
2238
|
-
if (this.
|
|
2344
|
+
if (this._presentAbandoned()) return false;
|
|
2239
2345
|
safeRelease(this.X, () => {
|
|
2240
2346
|
this._fixesExt.SetRegion(
|
|
2241
2347
|
this._updateRegion,
|