ntk 8.18.1 → 8.19.0
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/app.js +214 -35
- package/lib/renderingcontext_2d.js +1036 -397
- package/lib/text/layout.js +12 -6
- package/package.json +1 -1
package/lib/app.js
CHANGED
|
@@ -52,6 +52,39 @@ const DEFAULT_FRAME_INTERVAL = 16;
|
|
|
52
52
|
const MIN_REFRESH_RATE = 20;
|
|
53
53
|
const MAX_REFRESH_RATE = 1000;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* How many of the solids drawings make for themselves a connection keeps
|
|
57
|
+
* (`App#transientSolid`). A fade folds each new alpha into every colour it
|
|
58
|
+
* draws, so it makes a few a frame; a frame faded at one alpha draws with
|
|
59
|
+
* fewer than this, and finds them all again on the next.
|
|
60
|
+
*/
|
|
61
|
+
const TRANSIENT_SOLIDS = 256;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* How many of the solids contexts paint their colour styles with a
|
|
65
|
+
* connection keeps (`App#styleSolid`): `fillStyle`, `strokeStyle`, a
|
|
66
|
+
* `TextLayout` span's colour. An app's palette is far fewer; a colour
|
|
67
|
+
* animated through a transition or a tween is a new one every frame.
|
|
68
|
+
*/
|
|
69
|
+
const STYLE_SOLIDS = 1024;
|
|
70
|
+
|
|
71
|
+
/** How many CSS colour strings it finds them by before starting over. */
|
|
72
|
+
const STYLE_NAMES = 1024;
|
|
73
|
+
|
|
74
|
+
// a solid, and on a server too old for CreateSolidFill the 1x1 pixmap it
|
|
75
|
+
// repeats
|
|
76
|
+
function freeSolid(picture) {
|
|
77
|
+
picture.destroy();
|
|
78
|
+
picture._sourcePixmap?.destroy();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// the value `key` names, taken out of `map`
|
|
82
|
+
function take(map, key) {
|
|
83
|
+
const value = map.get(key);
|
|
84
|
+
if (value !== undefined) map.delete(key);
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
55
88
|
/**
|
|
56
89
|
* A RandR mode's vertical refresh rate in Hz, or 0 when it does not describe
|
|
57
90
|
* one.
|
|
@@ -147,9 +180,21 @@ export default class App {
|
|
|
147
180
|
// client costs no I/O — discovery waits for the first page to bind.
|
|
148
181
|
sharedGlyphsFor(this);
|
|
149
182
|
this._cursors = null;
|
|
183
|
+
// the solids a caller holds for good (solidPicture)
|
|
150
184
|
this._solidPictures = new Map();
|
|
151
|
-
// the
|
|
185
|
+
// the solids contexts paint their colour styles with, least recently
|
|
186
|
+
// used first; a clock that ticks as they are made and moved up; and the
|
|
187
|
+
// same pictures by the CSS colour that asked for them (styleSolid)
|
|
188
|
+
this._styleSolids = new Map();
|
|
189
|
+
this._styleSolidLimit = STYLE_SOLIDS;
|
|
190
|
+
this._styleClock = 0;
|
|
152
191
|
this._solidByName = new Map();
|
|
192
|
+
// style solids let go of in this job, freed once it has run
|
|
193
|
+
this._evictedSolids = [];
|
|
194
|
+
// the solids one drawing makes for itself, least recently used first
|
|
195
|
+
// (transientSolid)
|
|
196
|
+
this._transientSolids = new Map();
|
|
197
|
+
this._transientSolidLimit = TRANSIENT_SOLIDS;
|
|
153
198
|
this._rasterizer = undefined;
|
|
154
199
|
this._shm = undefined;
|
|
155
200
|
this._extensionPromises = new Map();
|
|
@@ -959,14 +1004,18 @@ export default class App {
|
|
|
959
1004
|
}
|
|
960
1005
|
|
|
961
1006
|
/**
|
|
962
|
-
* A repeating source Picture of one colour, for compositing
|
|
963
|
-
* are 0..1 floats,
|
|
1007
|
+
* A repeating source Picture of one colour, for compositing, held for as
|
|
1008
|
+
* long as the connection is open. Components are 0..1 floats,
|
|
1009
|
+
* premultiplied by alpha.
|
|
964
1010
|
*
|
|
965
|
-
* Cached per connection, not per context:
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
*
|
|
969
|
-
*
|
|
1011
|
+
* Cached per connection, not per context: `Surface.render` builds a
|
|
1012
|
+
* context per call, and a per-context cache made every render recreate
|
|
1013
|
+
* its colours server-side. Nothing evicts: whoever asked may hold the
|
|
1014
|
+
* picture as long as it likes — a caller what `createSolidPicture` handed
|
|
1015
|
+
* back, a context its glyph source — so this is for the few colours an
|
|
1016
|
+
* app holds. Freed in `close()`. A context's colour styles are
|
|
1017
|
+
* `styleSolid`'s, and the colours a drawing makes for itself
|
|
1018
|
+
* `transientSolid`'s: once a colour animates, neither is few.
|
|
970
1019
|
*
|
|
971
1020
|
* On RENDER >= 0.10 a solid is one CreateSolidFill request; an older
|
|
972
1021
|
* server (or a hand-built App that skipped the version handshake in
|
|
@@ -976,7 +1025,151 @@ export default class App {
|
|
|
976
1025
|
const key = `${r}|${g}|${b}|${a}`;
|
|
977
1026
|
let p = this._solidPictures.get(key);
|
|
978
1027
|
if (p) return p;
|
|
1028
|
+
// one a style paints with, or a drawing made for itself, is held from
|
|
1029
|
+
// here on: out of its LRU, rather than made a second time
|
|
1030
|
+
p =
|
|
1031
|
+
take(this._styleSolids, key) ??
|
|
1032
|
+
take(this._transientSolids, key) ??
|
|
1033
|
+
this._createSolid(key, r, g, b, a);
|
|
1034
|
+
// and no LRU's clock ages it now (_usedStyle)
|
|
1035
|
+
p._used = Infinity;
|
|
1036
|
+
this._solidPictures.set(key, p);
|
|
1037
|
+
return p;
|
|
1038
|
+
}
|
|
979
1039
|
|
|
1040
|
+
/**
|
|
1041
|
+
* The solid a context paints a colour style with — `fillStyle`,
|
|
1042
|
+
* `strokeStyle`, a `TextLayout` span's colour: a CSS colour string, or a
|
|
1043
|
+
* premultiplied `[r, g, b, a]`. A string that is not a colour throws, as
|
|
1044
|
+
* `fillStyle` always has.
|
|
1045
|
+
*
|
|
1046
|
+
* Found by the string, where there is one: a UI sets the same few strings
|
|
1047
|
+
* again on every paint — every box's background, every border — and the
|
|
1048
|
+
* way to a solid is a parse, a premultiply and a key made of four numbers
|
|
1049
|
+
* turned into text, where by the string it is one lookup.
|
|
1050
|
+
*
|
|
1051
|
+
* Not held for good, as `solidPicture`'s are: a colour animated through a
|
|
1052
|
+
* transition or a tween is a new solid every frame, so the `STYLE_SOLIDS`
|
|
1053
|
+
* most recently used are kept and the rest let go. One let go is marked
|
|
1054
|
+
* `_evicted` at once, so that nothing hands it out again, and freed when
|
|
1055
|
+
* the job that let it go has run. X runs a client's requests in order, so
|
|
1056
|
+
* every request sent with it in that job reaches the server first: within
|
|
1057
|
+
* a job, holding one is safe however many colours are asked for meanwhile
|
|
1058
|
+
* — and a drawing does ask, taking its style before the shadow it paints
|
|
1059
|
+
* first makes colours of its own. Past the job, whoever holds one keeps
|
|
1060
|
+
* the style too, and asks again on finding the mark, as a context does
|
|
1061
|
+
* for `fillStyle` and `strokeStyle` (its `_backgroundPicture`).
|
|
1062
|
+
*/
|
|
1063
|
+
styleSolid(value) {
|
|
1064
|
+
if (typeof value !== 'string') return this._styleSolid(value);
|
|
1065
|
+
const p = this._solidByName.get(value);
|
|
1066
|
+
if (p !== undefined && !p._evicted) return this._usedStyle(p);
|
|
1067
|
+
const c = cssColor(value);
|
|
1068
|
+
if (!c) throw new Error(`Not a color: ${JSON.stringify(value)}`);
|
|
1069
|
+
const solid = this._styleSolid(c);
|
|
1070
|
+
// spellings are unbounded too: an app animating a colour writes a new
|
|
1071
|
+
// string every frame
|
|
1072
|
+
if (this._solidByName.size >= STYLE_NAMES) this._solidByName.clear();
|
|
1073
|
+
this._solidByName.set(value, solid);
|
|
1074
|
+
return solid;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// styleSolid by the numbers: a premultiplied [r, g, b, a]
|
|
1078
|
+
_styleSolid([r, g, b, a]) {
|
|
1079
|
+
const key = `${r}|${g}|${b}|${a}`;
|
|
1080
|
+
const held = this._solidPictures.get(key);
|
|
1081
|
+
if (held) return held;
|
|
1082
|
+
const lru = this._styleSolids;
|
|
1083
|
+
let p = lru.get(key);
|
|
1084
|
+
if (p) return this._usedStyle(p);
|
|
1085
|
+
p = this._createSolid(key, r, g, b, a);
|
|
1086
|
+
p._used = ++this._styleClock;
|
|
1087
|
+
lru.set(key, p);
|
|
1088
|
+
for (const [oldest, picture] of lru) {
|
|
1089
|
+
if (lru.size <= this._styleSolidLimit || picture === p) break;
|
|
1090
|
+
lru.delete(oldest);
|
|
1091
|
+
this._evictStyle(picture);
|
|
1092
|
+
}
|
|
1093
|
+
return p;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* A style solid asked for again: moved to the recent end of the LRU, but
|
|
1098
|
+
* only once it has aged into the older half. Moved on every ask, a
|
|
1099
|
+
* `fillStyle` set to a string it knows cost six times what the lookup
|
|
1100
|
+
* does; this way it stays a lookup and a comparison, and a colour asked
|
|
1101
|
+
* for every frame is still not the one let go.
|
|
1102
|
+
*/
|
|
1103
|
+
_usedStyle(p) {
|
|
1104
|
+
if (this._styleClock - p._used > this._styleSolidLimit / 2) {
|
|
1105
|
+
const lru = this._styleSolids;
|
|
1106
|
+
lru.delete(p._key);
|
|
1107
|
+
lru.set(p._key, p);
|
|
1108
|
+
p._used = ++this._styleClock;
|
|
1109
|
+
}
|
|
1110
|
+
return p;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// a style solid let go: marked, and freed once the job has run (styleSolid)
|
|
1114
|
+
_evictStyle(picture) {
|
|
1115
|
+
picture._evicted = true;
|
|
1116
|
+
const evicted = this._evictedSolids;
|
|
1117
|
+
evicted.push(picture);
|
|
1118
|
+
if (evicted.length === 1) queueMicrotask(() => this._freeEvictedSolids());
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
_freeEvictedSolids() {
|
|
1122
|
+
const evicted = this._evictedSolids;
|
|
1123
|
+
this._evictedSolids = [];
|
|
1124
|
+
for (const picture of evicted) freeSolid(picture);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* A solid for the requests of one drawing call, and nothing after them:
|
|
1129
|
+
* the colours a drawing makes for itself — a style with `globalAlpha`
|
|
1130
|
+
* folded into it, or the alpha alone in a mask slot. A fade makes new ones
|
|
1131
|
+
* nearly every frame; kept the way `solidPicture` keeps its own, every
|
|
1132
|
+
* fade left frames × colours of them on the server until the connection
|
|
1133
|
+
* closed. So these are kept apart: the `TRANSIENT_SOLIDS` most recently
|
|
1134
|
+
* used, the least recent freed as the next is made. A drawing faded at
|
|
1135
|
+
* one alpha frame after frame finds its few again each time.
|
|
1136
|
+
*
|
|
1137
|
+
* Freeing one is safe because X runs a client's requests in the order
|
|
1138
|
+
* they were sent, so the FreePicture reaches the server after every
|
|
1139
|
+
* request sent with the picture before it. What that cannot cover is a
|
|
1140
|
+
* request sent after — hence the contract: use the solid in the requests
|
|
1141
|
+
* of the call that asked for it, and drop it. Never keep one or hand it
|
|
1142
|
+
* out. Once evicted its id goes back to the pool, and the next solid made
|
|
1143
|
+
* can take it, in another colour.
|
|
1144
|
+
*
|
|
1145
|
+
* A colour the app keeps anyway, for a caller (`solidPicture`) or a style
|
|
1146
|
+
* (`styleSolid`), is answered from there: at full opacity, a rounded
|
|
1147
|
+
* box's corners paint with the style's own.
|
|
1148
|
+
*/
|
|
1149
|
+
transientSolid(r, g, b, a) {
|
|
1150
|
+
const key = `${r}|${g}|${b}|${a}`;
|
|
1151
|
+
let p = this._solidPictures.get(key) ?? this._styleSolids.get(key);
|
|
1152
|
+
if (p) return p;
|
|
1153
|
+
const lru = this._transientSolids;
|
|
1154
|
+
p = lru.get(key);
|
|
1155
|
+
if (p) {
|
|
1156
|
+
// Map iteration order is insertion order — re-insert to mark recent
|
|
1157
|
+
lru.delete(key);
|
|
1158
|
+
lru.set(key, p);
|
|
1159
|
+
return p;
|
|
1160
|
+
}
|
|
1161
|
+
p = this._createSolid(key, r, g, b, a);
|
|
1162
|
+
lru.set(key, p);
|
|
1163
|
+
for (const [oldest, picture] of lru) {
|
|
1164
|
+
if (lru.size <= this._transientSolidLimit || picture === p) break;
|
|
1165
|
+
lru.delete(oldest);
|
|
1166
|
+
freeSolid(picture);
|
|
1167
|
+
}
|
|
1168
|
+
return p;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
_createSolid(key, r, g, b, a) {
|
|
1172
|
+
let p;
|
|
980
1173
|
const Render = this.display.Render;
|
|
981
1174
|
const [major, minor] = Render.version || [0, 0];
|
|
982
1175
|
if (major > 0 || minor >= 10) {
|
|
@@ -994,27 +1187,10 @@ export default class App {
|
|
|
994
1187
|
// what it paints with, so a drawing can fold `globalAlpha` into it: glyphs
|
|
995
1188
|
// composite straight from their source, with no mask slot to carry one
|
|
996
1189
|
p._rgba = Object.freeze([r, g, b, a]);
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
/**
|
|
1002
|
-
* The solid picture a CSS colour string paints with. A UI sets the same few
|
|
1003
|
-
* strings again on every paint — every box's background, every border —
|
|
1004
|
-
* and the way to `solidPicture` is a parse, a premultiply and a key made of
|
|
1005
|
-
* four numbers turned into text; by the string it is one lookup. Throws, as
|
|
1006
|
-
* `fillStyle` always has, for a string that is not a colour.
|
|
1007
|
-
*/
|
|
1008
|
-
solidPictureOf(value) {
|
|
1009
|
-
let p = this._solidByName.get(value);
|
|
1010
|
-
if (p !== undefined) return p;
|
|
1011
|
-
const c = cssColor(value);
|
|
1012
|
-
if (!c) throw new Error(`Not a color: ${JSON.stringify(value)}`);
|
|
1013
|
-
p = this.solidPicture(c[0], c[1], c[2], c[3]);
|
|
1014
|
-
// spellings are unbounded where solids are not: an app animating a
|
|
1015
|
-
// colour writes a new string every frame
|
|
1016
|
-
if (this._solidByName.size >= 1024) this._solidByName.clear();
|
|
1017
|
-
this._solidByName.set(value, p);
|
|
1190
|
+
// and what the style LRU needs of it (styleSolid)
|
|
1191
|
+
p._key = key;
|
|
1192
|
+
p._evicted = false;
|
|
1193
|
+
p._used = 0;
|
|
1018
1194
|
return p;
|
|
1019
1195
|
}
|
|
1020
1196
|
|
|
@@ -1031,13 +1207,16 @@ export default class App {
|
|
|
1031
1207
|
// shadow coverage is cached per connection for the same reason solids
|
|
1032
1208
|
// are: the contexts that drew it are long gone (see lib/shadow.js)
|
|
1033
1209
|
dropShadowSurfaces(this);
|
|
1034
|
-
// shared by every context that ever asked (see solidPicture), so
|
|
1035
|
-
//
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1210
|
+
// shared by every context that ever asked (see solidPicture), so they are
|
|
1211
|
+
// the app's to free, never a context's teardown; whatever is left goes
|
|
1212
|
+
// with the connection, the style solids this job let go of included
|
|
1213
|
+
for (const picture of this._solidPictures.values()) freeSolid(picture);
|
|
1214
|
+
for (const picture of this._styleSolids.values()) freeSolid(picture);
|
|
1215
|
+
for (const picture of this._transientSolids.values()) freeSolid(picture);
|
|
1216
|
+
this._freeEvictedSolids();
|
|
1040
1217
|
this._solidPictures.clear();
|
|
1218
|
+
this._styleSolids.clear();
|
|
1219
|
+
this._transientSolids.clear();
|
|
1041
1220
|
this._solidByName.clear();
|
|
1042
1221
|
// GPU contexts are shared by every direct GL surface on this connection
|
|
1043
1222
|
// (see renderingcontext_gles.js), so they outlive individual contexts and
|