ntk 8.12.9 → 8.12.11
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/glswapchain.js +60 -19
- package/lib/renderingcontext_cgl.js +18 -1
- package/lib/renderingcontext_gles.js +40 -13
- package/lib/text/layout.js +5 -2
- package/lib/text/shape.js +6 -8
- package/package.json +1 -1
package/lib/glswapchain.js
CHANGED
|
@@ -12,14 +12,19 @@
|
|
|
12
12
|
// and every later frame in that buffer is a Present of a pixmap the server
|
|
13
13
|
// already has.
|
|
14
14
|
//
|
|
15
|
-
//
|
|
15
|
+
// Three rules shape the code. **A buffer belongs to the server until IdleNotify
|
|
16
16
|
// says otherwise** — drawing into one before then paints whatever is on
|
|
17
|
-
// screen — so a buffer is released back to GBM there and nowhere else.
|
|
18
|
-
//
|
|
17
|
+
// screen — so a buffer is released back to GBM there and nowhere else. **A
|
|
18
|
+
// pixmap must exist before it is presented**: a new buffer's import is
|
|
19
19
|
// confirmed by a round trip before the first Present names it, or the Present
|
|
20
20
|
// would fail with BadPixmap. A new buffer appears a handful of times per
|
|
21
21
|
// generation (and never again in steady state), so that round trip is a
|
|
22
|
-
// startup cost, not a per-frame one.
|
|
22
|
+
// startup cost, not a per-frame one. And **a surface is never destroyed while
|
|
23
|
+
// it is current** (`_destroySurface`): destroying one takes its GBM surface —
|
|
24
|
+
// the native window under the EGL surface — with it, while EGL still holds
|
|
25
|
+
// the EGL surface as current, and on NVIDIA's driver the next surface made
|
|
26
|
+
// current after that swaps nothing but EGL_BAD_SURFACE. A resize retires the
|
|
27
|
+
// generation being drawn into, so every resize of a window killed its GL.
|
|
23
28
|
|
|
24
29
|
import { GLError, glError } from './gl.js';
|
|
25
30
|
|
|
@@ -88,6 +93,9 @@ export class GLSwapchain {
|
|
|
88
93
|
this.onReady = null;
|
|
89
94
|
/** called once the first buffer has been imported, or it failed */
|
|
90
95
|
this.onValidated = null;
|
|
96
|
+
/** called with the error when the chain fails after it validated — the
|
|
97
|
+
* one failure `onValidated` has no way left to report */
|
|
98
|
+
this.onError = null;
|
|
91
99
|
this._validated = false;
|
|
92
100
|
|
|
93
101
|
this._eid = this.X.AllocID();
|
|
@@ -107,10 +115,44 @@ export class GLSwapchain {
|
|
|
107
115
|
const h = Math.max(1, Math.round(height));
|
|
108
116
|
const gen = this.generation;
|
|
109
117
|
if (gen && gen.width === w && gen.height === h) return gen.surface;
|
|
118
|
+
// The new generation first, and only then the old one's retirement: a
|
|
119
|
+
// driver that refuses the new size leaves the chain drawing where it
|
|
120
|
+
// was, and the old surface — current, as a rule, since this runs at the
|
|
121
|
+
// top of a frame — is let go by `_destroySurface`, which unbinds it.
|
|
122
|
+
// A generation that had to go linear keeps that layout: whatever refused
|
|
123
|
+
// the tiled buffer — the server, or EGL itself — has not changed since.
|
|
124
|
+
const surface = this._newGeneration(w, h, gen ? gen.linear : false);
|
|
110
125
|
this._retire(gen);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
return surface;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Destroy a generation's surface, unbinding it first if it is the one
|
|
131
|
+
* current on the connection.
|
|
132
|
+
*
|
|
133
|
+
* The currency is the connection's (`app._glCurrentSurface`, which the
|
|
134
|
+
* context sets whenever it binds): every direct surface on it shares one
|
|
135
|
+
* GPU context, and exactly one surface is current. Unbinding leaves no
|
|
136
|
+
* surface current and says so (`app._glCurrent`), so the next GL call of
|
|
137
|
+
* whichever context draws next binds its own, as it would after another
|
|
138
|
+
* surface stole currency.
|
|
139
|
+
*/
|
|
140
|
+
_destroySurface(surface) {
|
|
141
|
+
const app = this.window.app;
|
|
142
|
+
if (app && app._glCurrentSurface === surface) {
|
|
143
|
+
try {
|
|
144
|
+
this.gpu.makeCurrent(null);
|
|
145
|
+
} catch {
|
|
146
|
+
// nothing is current now either way
|
|
147
|
+
}
|
|
148
|
+
app._glCurrentSurface = null;
|
|
149
|
+
app._glCurrent = null;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
surface.destroy();
|
|
153
|
+
} catch {
|
|
154
|
+
// already gone
|
|
155
|
+
}
|
|
114
156
|
}
|
|
115
157
|
|
|
116
158
|
/** A GBM surface at this size, in the layout asked for. */
|
|
@@ -384,14 +426,20 @@ export class GLSwapchain {
|
|
|
384
426
|
_fail(err) {
|
|
385
427
|
if (this.error) return;
|
|
386
428
|
this.error = err;
|
|
387
|
-
|
|
429
|
+
// Before validation the failure is `ready`'s rejection. After it — a
|
|
430
|
+
// swap refused mid-life — nothing is waiting on `ready` any more, and a
|
|
431
|
+
// chain that only recorded the error left its window showing its last
|
|
432
|
+
// frame, or nothing, with no word to anyone.
|
|
433
|
+
if (this._validated) this.onError?.(err);
|
|
434
|
+
else this._validate(err);
|
|
388
435
|
}
|
|
389
436
|
|
|
390
437
|
_retire(gen) {
|
|
391
438
|
if (!gen || gen.retired) return;
|
|
392
439
|
gen.retired = true;
|
|
393
440
|
this.retired.push(gen);
|
|
394
|
-
|
|
441
|
+
// `surfaceFor` retires the old generation after making the new one
|
|
442
|
+
if (this.generation === gen) this.generation = null;
|
|
395
443
|
this._sweep(gen);
|
|
396
444
|
}
|
|
397
445
|
|
|
@@ -409,11 +457,7 @@ export class GLSwapchain {
|
|
|
409
457
|
if (gen.pixmaps.size || this.importing) return;
|
|
410
458
|
const index = this.retired.indexOf(gen);
|
|
411
459
|
if (index >= 0) this.retired.splice(index, 1);
|
|
412
|
-
|
|
413
|
-
gen.surface.destroy();
|
|
414
|
-
} catch {
|
|
415
|
-
// already gone
|
|
416
|
-
}
|
|
460
|
+
this._destroySurface(gen.surface);
|
|
417
461
|
}
|
|
418
462
|
|
|
419
463
|
_freePixmap(pixmap) {
|
|
@@ -429,14 +473,11 @@ export class GLSwapchain {
|
|
|
429
473
|
this.destroyed = true;
|
|
430
474
|
this.onReady = null;
|
|
431
475
|
this.onValidated = null;
|
|
476
|
+
this.onError = null;
|
|
432
477
|
for (const gen of this._generations()) {
|
|
433
478
|
for (const entry of gen.pixmaps.values()) this._freePixmap(entry.pixmap);
|
|
434
479
|
gen.pixmaps.clear();
|
|
435
|
-
|
|
436
|
-
gen.surface.destroy();
|
|
437
|
-
} catch {
|
|
438
|
-
// already gone
|
|
439
|
-
}
|
|
480
|
+
this._destroySurface(gen.surface);
|
|
440
481
|
}
|
|
441
482
|
this.generation = null;
|
|
442
483
|
this.retired.length = 0;
|
|
@@ -416,7 +416,24 @@ does not make it retroactive. Either:
|
|
|
416
416
|
_fail(err) {
|
|
417
417
|
if (this.error) return;
|
|
418
418
|
this.error = err;
|
|
419
|
-
this
|
|
419
|
+
// before `ready` settled this is its rejection; after, the one word the
|
|
420
|
+
// failure gets is `onError` (the GLES flavor's contract)
|
|
421
|
+
if (this._settleReady) this._settleReady(err);
|
|
422
|
+
else this._errorHandler?.(err);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Called once, with the coded error `error` then holds, when the context
|
|
427
|
+
* fails after `ready` resolved — a flush refused, or a surface that could
|
|
428
|
+
* not be exported again after the window came back. A failure before that
|
|
429
|
+
* is `ready`'s rejection, and only that.
|
|
430
|
+
*/
|
|
431
|
+
set onError(fn) {
|
|
432
|
+
this._errorHandler = fn;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
get onError() {
|
|
436
|
+
return this._errorHandler;
|
|
420
437
|
}
|
|
421
438
|
|
|
422
439
|
destroy() {
|
|
@@ -167,6 +167,12 @@ does not make it retroactive. Either:
|
|
|
167
167
|
this.ready.catch(() => {});
|
|
168
168
|
this.swapchain.onReady = () => this._onFrameAvailable();
|
|
169
169
|
this._frameWanted = null;
|
|
170
|
+
// …and a failure after `ready` resolved, which nothing awaits any more
|
|
171
|
+
this._errorHandler = null;
|
|
172
|
+
this.swapchain.onError = (err) => {
|
|
173
|
+
this.error = err;
|
|
174
|
+
this._errorHandler?.(err);
|
|
175
|
+
};
|
|
170
176
|
|
|
171
177
|
// GL entry points and constants, bound so that whichever surface this
|
|
172
178
|
// context owns is the current one when they run
|
|
@@ -208,24 +214,28 @@ does not make it retroactive. Either:
|
|
|
208
214
|
*/
|
|
209
215
|
makeCurrent() {
|
|
210
216
|
if (this.error || this._destroyed || this.window._destroyed) return this;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
this._width = width;
|
|
215
|
-
this._height = height;
|
|
216
|
-
this._surface = null; // a new size is a new generation
|
|
217
|
-
}
|
|
217
|
+
// a new size is a new generation, which `_bind` asks the chain for
|
|
218
|
+
this._width = this.window.width;
|
|
219
|
+
this._height = this.window.height;
|
|
218
220
|
this._bind();
|
|
219
221
|
return this;
|
|
220
222
|
}
|
|
221
223
|
|
|
222
224
|
_bind() {
|
|
223
225
|
if (this.error || this._destroyed) return;
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
226
|
+
// Asked of the chain every time rather than cached here: the chain also
|
|
227
|
+
// replaces its generation on its own — the linear retry after the server
|
|
228
|
+
// refuses a buffer — and a surface cached across that is one the chain
|
|
229
|
+
// has destroyed. At the same size it is a comparison and a return.
|
|
230
|
+
this._surface = this.swapchain.surfaceFor(
|
|
231
|
+
this._width ?? this.window.width,
|
|
232
|
+
this._height ?? this.window.height
|
|
233
|
+
);
|
|
227
234
|
this.gpu.makeCurrent(this._surface);
|
|
228
235
|
this.app._glCurrent = this;
|
|
236
|
+
// which surface is current, for the chain: it never destroys that one
|
|
237
|
+
// without unbinding it first (GLSwapchain#_destroySurface)
|
|
238
|
+
this.app._glCurrentSurface = this._surface;
|
|
229
239
|
}
|
|
230
240
|
|
|
231
241
|
/**
|
|
@@ -252,6 +262,20 @@ does not make it retroactive. Either:
|
|
|
252
262
|
this._frameWanted?.();
|
|
253
263
|
}
|
|
254
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Called once, with the coded error `error` then holds, when the context
|
|
267
|
+
* fails after `ready` resolved — the driver refusing a swap mid-life. A
|
|
268
|
+
* failure before that is `ready`'s rejection, and only that. Either way
|
|
269
|
+
* the context draws nothing more, and a caller with a fallback shows it.
|
|
270
|
+
*/
|
|
271
|
+
set onError(fn) {
|
|
272
|
+
this._errorHandler = fn;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
get onError() {
|
|
276
|
+
return this._errorHandler;
|
|
277
|
+
}
|
|
278
|
+
|
|
255
279
|
/**
|
|
256
280
|
* Show the frame just drawn.
|
|
257
281
|
*
|
|
@@ -266,9 +290,8 @@ does not make it retroactive. Either:
|
|
|
266
290
|
// A resize seen only now still gets picked up: the next frame binds a
|
|
267
291
|
// generation at the new size. Checked after the swap so the frame that was
|
|
268
292
|
// drawn at the old size is the one that goes out.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
293
|
+
this._width = this.window.width;
|
|
294
|
+
this._height = this.window.height;
|
|
272
295
|
return sent;
|
|
273
296
|
}
|
|
274
297
|
|
|
@@ -288,10 +311,14 @@ does not make it retroactive. Either:
|
|
|
288
311
|
destroy() {
|
|
289
312
|
if (this._destroyed) return;
|
|
290
313
|
this._destroyed = true;
|
|
314
|
+
this._errorHandler = null;
|
|
315
|
+
// unbinds this context's surface first where it is the current one
|
|
316
|
+
// (GLSwapchain#_destroySurface)
|
|
291
317
|
this.swapchain.destroy();
|
|
292
318
|
this.window._setGenericEventSink(0, null);
|
|
293
319
|
if (this.app._glCurrent === this) {
|
|
294
320
|
this.app._glCurrent = null;
|
|
321
|
+
this.app._glCurrentSurface = null;
|
|
295
322
|
try {
|
|
296
323
|
this.gpu.makeCurrent(null);
|
|
297
324
|
} catch {
|
package/lib/text/layout.js
CHANGED
|
@@ -5,9 +5,12 @@ import { drawGlyphRuns } from './glyphs.js';
|
|
|
5
5
|
import { embeddingLevels, normalizedLevels, reorderRuns } from './shape.js';
|
|
6
6
|
import { worthKeeping } from './paragraphs.js';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// The white space a line may end on and hang past its edge: spaces and tabs,
|
|
9
|
+
// as CSS hangs them. A no-break space is not one — CSS measures it, and
|
|
10
|
+
// ` ` at the end of a cell or a span is there to take room.
|
|
11
|
+
const WS = new Set([0x20, 0x09]);
|
|
9
12
|
const HARD_BREAKS = /[\n\r\u2028\u2029]+$/;
|
|
10
|
-
const TRAILING_WS = /[ \t
|
|
13
|
+
const TRAILING_WS = /[ \t]+$/;
|
|
11
14
|
|
|
12
15
|
function isWsGlyph(g) {
|
|
13
16
|
return g.codePoints.length > 0 && g.codePoints.every((cp) => WS.has(cp));
|
package/lib/text/shape.js
CHANGED
|
@@ -104,18 +104,16 @@ function spacedFeatures(features) {
|
|
|
104
104
|
/**
|
|
105
105
|
* Space a shaped run's glyphs apart by `spacing` px: added to the advance of
|
|
106
106
|
* every glyph that has one, so a combining mark keeps sitting on its base.
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
107
|
+
* The gap is on each glyph's right in an rtl run as in an ltr one — where
|
|
108
|
+
* CoreText's kerning attribute and browsers put it — so a line that changes
|
|
109
|
+
* direction keeps one gap between every two neighbours: on the reading side
|
|
110
|
+
* of an rtl run's glyphs it came out twice where the direction changed one
|
|
111
|
+
* way and not at all where it changed back.
|
|
110
112
|
*/
|
|
111
113
|
function spaceGlyphs(shaped, spacing) {
|
|
112
|
-
const rtl = shaped.direction === 'rtl';
|
|
113
114
|
let width = 0;
|
|
114
115
|
for (const g of shaped.glyphs) {
|
|
115
|
-
if (g.ax > 0)
|
|
116
|
-
g.ax += spacing;
|
|
117
|
-
if (rtl) g.dx += spacing;
|
|
118
|
-
}
|
|
116
|
+
if (g.ax > 0) g.ax += spacing;
|
|
119
117
|
width += g.ax;
|
|
120
118
|
}
|
|
121
119
|
shaped.width = width;
|