react-x11 2.16.0 → 2.17.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/README.md +38 -23
- package/package.json +3 -1
- package/src/Reconciler.js +82 -23
- package/src/a11y.js +18 -1
- package/src/acceleratorhooks.js +40 -6
- package/src/anchor.js +20 -2
- package/src/appcontext.js +8 -0
- package/src/appearance.js +36 -0
- package/src/{cocoa → backend}/context2d.js +27 -7
- package/src/capabilities.js +99 -1
- package/src/cocoa/app.js +204 -6
- package/src/cocoa/fonts.js +1 -1
- package/src/cocoa/overlay.js +2 -2
- package/src/cocoa/panewindow.js +2 -2
- package/src/cocoa/presenter.js +2 -2
- package/src/cocoa/surface.js +3 -3
- package/src/cocoa/window.js +2 -2
- package/src/events.js +21 -0
- package/src/foreignnodes.js +8 -3
- package/src/frame/index.js +30 -4
- package/src/glnodes.js +12 -1
- package/src/idle.js +59 -1
- package/src/index.d.ts +51 -1
- package/src/index.js +30 -3
- package/src/keysymchars.js +47 -0
- package/src/keysyms.d.ts +19 -1
- package/src/keysyms.js +107 -8
- package/src/launcher.js +17 -8
- package/src/launcherhooks.js +24 -10
- package/src/node.d.ts +1 -1
- package/src/nodes/cascade.js +9 -0
- package/src/nodes/node.js +6 -1
- package/src/nodes/window/hints.js +21 -2
- package/src/nodes/window/window.js +2 -2
- package/src/notifications.js +39 -14
- package/src/screens.js +159 -24
- package/src/taskbarhooks.js +164 -0
- package/src/transfer.js +20 -1
- package/src/trayhooks.js +1 -1
- package/src/types/capabilities.d.ts +32 -3
- package/src/types/elements.d.ts +23 -1
- package/src/types/events.d.ts +21 -0
- package/src/types/filedialog.d.ts +3 -1
- package/src/types/launcher.d.ts +20 -6
- package/src/types/taskbar.d.ts +79 -0
- package/src/wayland/context2d.js +1 -1
- package/src/wayland/xkb.js +170 -59
- package/src/win32/a11y.js +604 -0
- package/src/win32/app.js +768 -0
- package/src/win32/bezels.js +158 -0
- package/src/win32/dnd.js +283 -0
- package/src/win32/fonts.js +497 -0
- package/src/win32/glarea.js +548 -0
- package/src/win32/ime.js +267 -0
- package/src/win32/keymap.js +116 -0
- package/src/win32/native.js +54 -0
- package/src/win32/panehost.js +106 -0
- package/src/win32/panewindow.js +343 -0
- package/src/win32/shell.js +426 -0
- package/src/win32/surface.js +192 -0
- package/src/win32/window.js +659 -0
- package/src/windowid.js +128 -20
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
// `<glarea>` on Windows: a WGL core context, composited as a visual.
|
|
2
|
+
//
|
|
3
|
+
// docs/windows-gl.md is the report behind this, and the route is the X11
|
|
4
|
+
// model rather than ANGLE — a GL surface covers its rect and the tree draws
|
|
5
|
+
// under it, positioned from the parent's yoga rect. src/glnodes.js already
|
|
6
|
+
// expects exactly that, including the rule that the surface selects no
|
|
7
|
+
// pointer input so a press over it reaches the tree by propagation.
|
|
8
|
+
//
|
|
9
|
+
// Where it parts from X11 is how the surface reaches the screen. It is not a
|
|
10
|
+
// child window: a window presenting through DirectComposition is shown from
|
|
11
|
+
// its visual tree, and a child HWND's pixels go to a redirection bitmap that
|
|
12
|
+
// is then no part of what is composited. That was built and measured before
|
|
13
|
+
// it was believed — see the commit and the report. So the bridge gives each
|
|
14
|
+
// surface a composition swap chain under the window's own visual, and GL
|
|
15
|
+
// draws into a Direct3D texture the frame copies into it. Which means a
|
|
16
|
+
// `<glarea>` here *does* composite with the window's alpha, where on X11 it
|
|
17
|
+
// does not.
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The window object a `<glarea>` gets. It is *not* a Win32Window: it has no
|
|
21
|
+
* 2d context and no frame clock of the window's — GL draws into its own
|
|
22
|
+
* composition surface and presents it, which is the whole difference.
|
|
23
|
+
*/
|
|
24
|
+
export class Win32GlWindow {
|
|
25
|
+
constructor(app, attributes = {}) {
|
|
26
|
+
this.app = app;
|
|
27
|
+
this._native = app._native;
|
|
28
|
+
this.attributes = attributes;
|
|
29
|
+
this.x = Math.round(attributes.x ?? 0);
|
|
30
|
+
this.y = Math.round(attributes.y ?? 0);
|
|
31
|
+
this.width = Math.max(1, Math.round(attributes.width ?? 1));
|
|
32
|
+
this.height = Math.max(1, Math.round(attributes.height ?? 1));
|
|
33
|
+
this.mapped = false;
|
|
34
|
+
this.destroyed = false;
|
|
35
|
+
this._handlers = {};
|
|
36
|
+
this._gl = null;
|
|
37
|
+
/** The surface exists on the UI thread; until it does there is nothing to
|
|
38
|
+
* draw into, and a frame asked for meanwhile is refused rather than
|
|
39
|
+
* dropped — `glnodes.js` re-requests it from `onFrameAvailable`. */
|
|
40
|
+
this._ready = false;
|
|
41
|
+
|
|
42
|
+
// The parent window's id, not its handle: the surface is composited as a
|
|
43
|
+
// visual inside that window's tree rather than parented as a child HWND.
|
|
44
|
+
const parentId = attributes.parent ? attributes.parent.id : 0;
|
|
45
|
+
this.id = this._native.glCreateSurface(
|
|
46
|
+
parentId,
|
|
47
|
+
this.x,
|
|
48
|
+
this.y,
|
|
49
|
+
this.width,
|
|
50
|
+
this.height,
|
|
51
|
+
);
|
|
52
|
+
app._glWindows.set(this.id, this);
|
|
53
|
+
this.parent = attributes.parent ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The bridge reports the surface. `ok` is false where this machine has no
|
|
58
|
+
* vendor driver: opengl32.dll is then the 1.1 software rasterizer, with no
|
|
59
|
+
* shaders and no framebuffer objects — which is not a degraded direct
|
|
60
|
+
* backend but the indirect one's feature set without its portability, and
|
|
61
|
+
* is docs/windows-gl.md's rung 2 rather than this one.
|
|
62
|
+
*/
|
|
63
|
+
_onReady(ok, why = 0) {
|
|
64
|
+
if (!ok) {
|
|
65
|
+
// The bridge says which step refused, because "no GL surface" covers
|
|
66
|
+
// four quite different machines and only one of them is the one this
|
|
67
|
+
// error used to describe.
|
|
68
|
+
const REASONS = {
|
|
69
|
+
1: 'the surface window could not be created',
|
|
70
|
+
2:
|
|
71
|
+
'no pixel format on this device supports OpenGL — a remote ' +
|
|
72
|
+
'session or a display driver without an OpenGL ICD',
|
|
73
|
+
3: 'a pixel format was set but no OpenGL context could be made on it',
|
|
74
|
+
4:
|
|
75
|
+
'this machine has no vendor OpenGL driver, so only the 1.1 ' +
|
|
76
|
+
'software rasterizer is available, which has no shaders',
|
|
77
|
+
};
|
|
78
|
+
this.emit(
|
|
79
|
+
'error',
|
|
80
|
+
new Error(
|
|
81
|
+
`react-x11: no OpenGL surface — ${REASONS[why] ?? 'the bridge refused it'}. ` +
|
|
82
|
+
'docs/windows-gl.md rung 2 (ANGLE) covers this case and is not built yet.',
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this._ready = true;
|
|
88
|
+
// The frame this surface refused while it was being made.
|
|
89
|
+
this._gl?.onFrameAvailable?.();
|
|
90
|
+
this.emit('expose', {});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The WebGL-shaped table, plus the two calls `glnodes.js` makes around a
|
|
95
|
+
* frame. `SwapBuffers` keeps its PascalCase because that is the one name
|
|
96
|
+
* the element spells the same way on both backends.
|
|
97
|
+
*/
|
|
98
|
+
getContext(kind) {
|
|
99
|
+
if (kind !== 'opengl') return null;
|
|
100
|
+
if (this._gl) return this._gl;
|
|
101
|
+
const gl = this._native.glTable();
|
|
102
|
+
// What `glnodes.js` branches on to choose the camelCase spelling over the
|
|
103
|
+
// indirect backend's PascalCase one — `gl.backend === 'direct'`. This is a
|
|
104
|
+
// real direct context (shaders, framebuffer objects, vertex buffers), and
|
|
105
|
+
// saying otherwise sends every call to a name this table does not have.
|
|
106
|
+
gl.backend = 'direct';
|
|
107
|
+
gl.makeCurrent = () => this._native.glMakeCurrent(this.id);
|
|
108
|
+
gl.SwapBuffers = () => this._native.glSwapBuffers(this.id);
|
|
109
|
+
// A WGL swap does not hold a frame back the way a DRI3 Present or a CGL
|
|
110
|
+
// flush does, so the only reason to refuse one is that the surface does
|
|
111
|
+
// not exist yet. `onFrameAvailable` is the channel glnodes.js installs to
|
|
112
|
+
// be told when it does — declared here so it finds the property.
|
|
113
|
+
gl.canRender = () => this._ready;
|
|
114
|
+
gl.onFrameAvailable = null;
|
|
115
|
+
Object.assign(gl, CONSTANTS);
|
|
116
|
+
this._gl = GL_DEBUG ? instrument(gl) : gl;
|
|
117
|
+
return this._gl;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The frame clock, which is the parent window's — the same delegation the
|
|
122
|
+
* Cocoa surface makes (src/cocoa/glarea.js). A `<glarea>` has no clock of
|
|
123
|
+
* its own here: it is a visual inside its parent's composition tree, so
|
|
124
|
+
* the parent's tick is the one its frames belong to. Without this
|
|
125
|
+
* `glnodes.js` falls back to `setImmediate`, which is not a frame clock at
|
|
126
|
+
* all — it is "as fast as the event loop will go", which paces a still
|
|
127
|
+
* scene against nothing.
|
|
128
|
+
*/
|
|
129
|
+
requestAnimationFrame(cb) {
|
|
130
|
+
return this.parent?.requestAnimationFrame?.(cb) ?? setImmediate(cb);
|
|
131
|
+
}
|
|
132
|
+
resize(width, height) {
|
|
133
|
+
this.width = Math.max(1, Math.round(width));
|
|
134
|
+
this.height = Math.max(1, Math.round(height));
|
|
135
|
+
this._native.glResizeSurface(
|
|
136
|
+
this.id,
|
|
137
|
+
this.x,
|
|
138
|
+
this.y,
|
|
139
|
+
this.width,
|
|
140
|
+
this.height,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
move(x, y) {
|
|
145
|
+
this.x = Math.round(x);
|
|
146
|
+
this.y = Math.round(y);
|
|
147
|
+
this._native.glResizeSurface(
|
|
148
|
+
this.id,
|
|
149
|
+
this.x,
|
|
150
|
+
this.y,
|
|
151
|
+
this.width,
|
|
152
|
+
this.height,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
map() {
|
|
157
|
+
this.mapped = true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
unmap() {
|
|
161
|
+
this.mapped = false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
destroy() {
|
|
165
|
+
if (this.destroyed) return;
|
|
166
|
+
this.destroyed = true;
|
|
167
|
+
this.app._glWindows.delete(this.id);
|
|
168
|
+
this._native.glDestroySurface(this.id);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
on(name, fn) {
|
|
172
|
+
(this._handlers[name] ??= []).push(fn);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
emit(name, ev) {
|
|
176
|
+
for (const fn of this._handlers[name] ?? []) fn(ev);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// A GL surface has no 2d context and no backing store; these exist so the
|
|
180
|
+
// node layer's feature tests answer no rather than throwing.
|
|
181
|
+
getContext2D() {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
setCursor() {}
|
|
185
|
+
raise() {}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* `REACT_X11_GL_DEBUG=1` — every call checked against glGetError, and every
|
|
190
|
+
* name the caller reaches for that this table does not have.
|
|
191
|
+
*
|
|
192
|
+
* A missing verb is the failure this exists for: the table is written by hand
|
|
193
|
+
* against the WebGL names, and a renderer that asks for one that is not here
|
|
194
|
+
* gets `undefined is not a function` somewhere deep inside its own frame,
|
|
195
|
+
* where an example's error handling may swallow it. Asking the object what it
|
|
196
|
+
* was asked for turns that into a list.
|
|
197
|
+
*/
|
|
198
|
+
const GL_DEBUG = process.env.REACT_X11_GL_DEBUG === '1';
|
|
199
|
+
|
|
200
|
+
function instrument(gl) {
|
|
201
|
+
// REACT_X11_GL_FORCE_CLEAR=1 paints the GL surface a colour nothing else in
|
|
202
|
+
// the window uses. It answers the one question a screenshot of a map cannot:
|
|
203
|
+
// whether what is on screen is the GL child or the 2D layer behind it, which
|
|
204
|
+
// in this app are both the style's background colour.
|
|
205
|
+
if (process.env.REACT_X11_GL_FORCE_CLEAR === '1') {
|
|
206
|
+
const realClearColor = gl.clearColor;
|
|
207
|
+
gl.clearColor = () => realClearColor(1, 0, 1, 1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const counts = new Map();
|
|
211
|
+
const missing = new Set();
|
|
212
|
+
let swaps = 0;
|
|
213
|
+
// Frame timing, split at the only place it can be split from here: the work
|
|
214
|
+
// between making the context current and presenting is the GL frame; the
|
|
215
|
+
// rest of the interval is everything else — the tile and label work, the
|
|
216
|
+
// React render, the 2D layer, and whatever the pacer decides to wait.
|
|
217
|
+
let frameStart = 0;
|
|
218
|
+
let drawTotal = 0;
|
|
219
|
+
let gapTotal = 0;
|
|
220
|
+
let lastSwap = 0;
|
|
221
|
+
let presentTotal = 0;
|
|
222
|
+
let errors = 0;
|
|
223
|
+
const raw = gl.getError;
|
|
224
|
+
const rawRead = gl.readPixels;
|
|
225
|
+
const raw_getShaderInfoLog = gl.getShaderInfoLog;
|
|
226
|
+
const raw_getProgramInfoLog = gl.getProgramInfoLog;
|
|
227
|
+
// REACT_X11_GL_PEEK=<n> reads the stencil and the colour at the middle of
|
|
228
|
+
// the surface after each draw. A fill pass that lays down no winding and a
|
|
229
|
+
// cover pass that paints nothing look identical from outside; the stencil
|
|
230
|
+
// buffer is the only place that tells them apart.
|
|
231
|
+
const peek = Number(process.env.REACT_X11_GL_PEEK ?? 0);
|
|
232
|
+
let peeked = 0;
|
|
233
|
+
|
|
234
|
+
const describe = (v) =>
|
|
235
|
+
ArrayBuffer.isView(v)
|
|
236
|
+
? `${v.constructor.name}(${v.length})`
|
|
237
|
+
: typeof v === 'string'
|
|
238
|
+
? JSON.stringify(v.length > 30 ? `${v.slice(0, 30)}…` : v)
|
|
239
|
+
: String(v);
|
|
240
|
+
|
|
241
|
+
// REACT_X11_GL_TRACE=<n> also prints the first n calls in order, with their
|
|
242
|
+
// arguments: the only way to see a frame that draws everything correctly
|
|
243
|
+
// into a place nothing is looking at.
|
|
244
|
+
const trace = Number(process.env.REACT_X11_GL_TRACE ?? 0);
|
|
245
|
+
// Startup frames draw nothing — the tiles are still arriving — so the
|
|
246
|
+
// interesting frame is never the first one.
|
|
247
|
+
const traceAfter = Number(process.env.REACT_X11_GL_TRACE_AFTER ?? 0);
|
|
248
|
+
let traced = 0;
|
|
249
|
+
|
|
250
|
+
for (const name of Object.keys(gl)) {
|
|
251
|
+
const fn = gl[name];
|
|
252
|
+
if (typeof fn !== 'function' || name === 'getError') continue;
|
|
253
|
+
gl[name] = (...args) => {
|
|
254
|
+
if (name === 'makeCurrent') frameStart = performance.now();
|
|
255
|
+
const out = fn(...args);
|
|
256
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
257
|
+
if (traced < trace && swaps >= traceAfter) {
|
|
258
|
+
traced += 1;
|
|
259
|
+
process.stderr.write(
|
|
260
|
+
`[gl] ${String(traced).padStart(3)} ${name}(${args.map(describe).join(', ')})` +
|
|
261
|
+
`${out === undefined ? '' : ` = ${describe(out)}`}
|
|
262
|
+
`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
// readPixels answers in an out-parameter, so the trace has to look at it
|
|
266
|
+
// after the call or the most interesting result in the API is invisible.
|
|
267
|
+
if (
|
|
268
|
+
trace > 0 &&
|
|
269
|
+
(name === 'bufferData' || name === 'bufferSubData') &&
|
|
270
|
+
traced <= trace
|
|
271
|
+
) {
|
|
272
|
+
const buf = args.find((a) => ArrayBuffer.isView(a));
|
|
273
|
+
process.stderr.write(
|
|
274
|
+
`[gl] upload: ${buf ? `${buf.constructor.name} ${buf.byteLength}B` : 'no view argument'}
|
|
275
|
+
`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
if (name === 'readPixels' && trace > 0 && ArrayBuffer.isView(args[6])) {
|
|
279
|
+
process.stderr
|
|
280
|
+
.write(`[gl] read back: [${[...args[6].slice(0, 16)].join(', ')}]
|
|
281
|
+
`);
|
|
282
|
+
}
|
|
283
|
+
if (peek > 0 && peeked < peek && name.startsWith('draw')) {
|
|
284
|
+
peeked += 1;
|
|
285
|
+
// A whole scanline rather than one pixel: geometry that misses the
|
|
286
|
+
// point sampled and geometry that was never rasterised look alike.
|
|
287
|
+
const st = new Uint8Array(1600);
|
|
288
|
+
rawRead(100, 600, 1600, 1, 0x1901 /* STENCIL_INDEX */, 0x1401, st);
|
|
289
|
+
const rgba = new Uint8Array(1600 * 4);
|
|
290
|
+
rawRead(100, 600, 1600, 1, 0x1908, 0x1401, rgba);
|
|
291
|
+
let set = 0;
|
|
292
|
+
let peak = 0;
|
|
293
|
+
for (const v of st) {
|
|
294
|
+
if (v !== 0) set += 1;
|
|
295
|
+
if (v > peak) peak = v;
|
|
296
|
+
}
|
|
297
|
+
const colours = new Set();
|
|
298
|
+
for (let i = 0; i < rgba.length; i += 4) {
|
|
299
|
+
colours.add(`${rgba[i]},${rgba[i + 1]},${rgba[i + 2]}`);
|
|
300
|
+
}
|
|
301
|
+
process.stderr.write(
|
|
302
|
+
`[gl] after ${name}: stencil set on ${set}/1600 (max ${peak}), ` +
|
|
303
|
+
`${colours.size} colours ${[...colours].slice(0, 3).join(' | ')}\n`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
// A shader that will not compile, or a program that will not link, is
|
|
307
|
+
// not a GL *error* — glGetError stays clean and the caller is expected
|
|
308
|
+
// to ask. An app that asks and then throws its own message leaves
|
|
309
|
+
// nothing behind, so the log is printed here where it still exists.
|
|
310
|
+
if (
|
|
311
|
+
(name === 'getShaderParameter' || name === 'getProgramParameter') &&
|
|
312
|
+
out === false
|
|
313
|
+
) {
|
|
314
|
+
const log =
|
|
315
|
+
name === 'getShaderParameter'
|
|
316
|
+
? raw_getShaderInfoLog(args[0])
|
|
317
|
+
: raw_getProgramInfoLog(args[0]);
|
|
318
|
+
process.stderr.write(`[gl] ${name} false — ${log || '(no log)'}\n`);
|
|
319
|
+
}
|
|
320
|
+
const err = raw();
|
|
321
|
+
if (err !== 0 && errors < 40) {
|
|
322
|
+
errors += 1;
|
|
323
|
+
process.stderr.write(
|
|
324
|
+
`[gl] ${name}(${args.map(describe).join(', ')}) -> 0x${err.toString(16)}\n`,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
return out;
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const swap = gl.SwapBuffers;
|
|
332
|
+
gl.SwapBuffers = (...args) => {
|
|
333
|
+
swaps += 1;
|
|
334
|
+
const now = performance.now();
|
|
335
|
+
if (frameStart) drawTotal += now - frameStart;
|
|
336
|
+
if (lastSwap) gapTotal += now - lastSwap;
|
|
337
|
+
lastSwap = now;
|
|
338
|
+
if (swaps % 30 === 0) {
|
|
339
|
+
const top = [...counts.entries()]
|
|
340
|
+
.sort((a, b) => b[1] - a[1])
|
|
341
|
+
.slice(0, Number(process.env.REACT_X11_GL_TOP ?? 12))
|
|
342
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
343
|
+
.join(' ');
|
|
344
|
+
process.stderr.write(
|
|
345
|
+
`[gl] after ${swaps} swaps: GL frame ${(drawTotal / 30).toFixed(1)} ms, ` +
|
|
346
|
+
`present ${(presentTotal / 30).toFixed(1)} ms, interval ${(gapTotal / 30).toFixed(1)} ms — ${top}
|
|
347
|
+
`,
|
|
348
|
+
);
|
|
349
|
+
drawTotal = 0;
|
|
350
|
+
gapTotal = 0;
|
|
351
|
+
presentTotal = 0;
|
|
352
|
+
if (missing.size > 0) {
|
|
353
|
+
process.stderr.write(`[gl] NOT IN TABLE: ${[...missing].join(', ')}\n`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const before = performance.now();
|
|
357
|
+
const result = swap(...args);
|
|
358
|
+
presentTotal += performance.now() - before;
|
|
359
|
+
return result;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
return new Proxy(gl, {
|
|
363
|
+
get(target, prop) {
|
|
364
|
+
if (
|
|
365
|
+
typeof prop === 'string' &&
|
|
366
|
+
!(prop in target) &&
|
|
367
|
+
// a constant would be SHOUTED; anything else read off this object is
|
|
368
|
+
// being read to be called
|
|
369
|
+
prop !== 'then' &&
|
|
370
|
+
!missing.has(prop)
|
|
371
|
+
) {
|
|
372
|
+
missing.add(prop);
|
|
373
|
+
process.stderr.write(`[gl] NOT IN TABLE: ${prop}\n`);
|
|
374
|
+
}
|
|
375
|
+
return target[prop];
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* The GL enumerants, as numbers. They are constants of the API rather than of
|
|
382
|
+
* a context, so they live here rather than being asked of the driver — which
|
|
383
|
+
* is also what every WebGL implementation does.
|
|
384
|
+
*/
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* The texture units, as a range rather than one entry.
|
|
388
|
+
*
|
|
389
|
+
* `TEXTURE0` alone was here, and a scene that binds a second texture —
|
|
390
|
+
* anything with a base map and an overlay, which is most of them — asked for
|
|
391
|
+
* `gl.TEXTURE1`, got `undefined`, and passed it to `activeTexture` as an
|
|
392
|
+
* invalid enum. The bind then landed on unit 0 and the sampler read black.
|
|
393
|
+
*
|
|
394
|
+
* GL guarantees the units are consecutive from TEXTURE0, so this is the
|
|
395
|
+
* definition rather than a table of 32 lines.
|
|
396
|
+
*/
|
|
397
|
+
const TEXTURE_UNITS = Object.fromEntries(
|
|
398
|
+
Array.from({ length: 32 }, (_, i) => [`TEXTURE${i}`, 0x84c0 + i]),
|
|
399
|
+
);
|
|
400
|
+
const CONSTANTS = Object.freeze({
|
|
401
|
+
DEPTH_BUFFER_BIT: 0x0100,
|
|
402
|
+
STENCIL_BUFFER_BIT: 0x0400,
|
|
403
|
+
COLOR_BUFFER_BIT: 0x4000,
|
|
404
|
+
POINTS: 0,
|
|
405
|
+
LINES: 1,
|
|
406
|
+
LINE_LOOP: 2,
|
|
407
|
+
LINE_STRIP: 3,
|
|
408
|
+
TRIANGLES: 4,
|
|
409
|
+
TRIANGLE_STRIP: 5,
|
|
410
|
+
TRIANGLE_FAN: 6,
|
|
411
|
+
ZERO: 0,
|
|
412
|
+
ONE: 1,
|
|
413
|
+
SRC_COLOR: 0x0300,
|
|
414
|
+
ONE_MINUS_SRC_COLOR: 0x0301,
|
|
415
|
+
SRC_ALPHA: 0x0302,
|
|
416
|
+
ONE_MINUS_SRC_ALPHA: 0x0303,
|
|
417
|
+
DST_ALPHA: 0x0304,
|
|
418
|
+
ONE_MINUS_DST_ALPHA: 0x0305,
|
|
419
|
+
DST_COLOR: 0x0306,
|
|
420
|
+
ONE_MINUS_DST_COLOR: 0x0307,
|
|
421
|
+
FRONT: 0x0404,
|
|
422
|
+
BACK: 0x0405,
|
|
423
|
+
FRONT_AND_BACK: 0x0408,
|
|
424
|
+
CULL_FACE: 0x0b44,
|
|
425
|
+
DEPTH_TEST: 0x0b71,
|
|
426
|
+
STENCIL_TEST: 0x0b90,
|
|
427
|
+
BLEND: 0x0be2,
|
|
428
|
+
SCISSOR_TEST: 0x0c11,
|
|
429
|
+
TEXTURE_2D: 0x0de1,
|
|
430
|
+
NEVER: 0x0200,
|
|
431
|
+
LESS: 0x0201,
|
|
432
|
+
EQUAL: 0x0202,
|
|
433
|
+
LEQUAL: 0x0203,
|
|
434
|
+
GREATER: 0x0204,
|
|
435
|
+
NOTEQUAL: 0x0205,
|
|
436
|
+
GEQUAL: 0x0206,
|
|
437
|
+
ALWAYS: 0x0207,
|
|
438
|
+
KEEP: 0x1e00,
|
|
439
|
+
REPLACE: 0x1e01,
|
|
440
|
+
INCR: 0x1e02,
|
|
441
|
+
DECR: 0x1e03,
|
|
442
|
+
INVERT: 0x150a,
|
|
443
|
+
INCR_WRAP: 0x8507,
|
|
444
|
+
DECR_WRAP: 0x8508,
|
|
445
|
+
BYTE: 0x1400,
|
|
446
|
+
UNSIGNED_BYTE: 0x1401,
|
|
447
|
+
SHORT: 0x1402,
|
|
448
|
+
UNSIGNED_SHORT: 0x1403,
|
|
449
|
+
INT: 0x1404,
|
|
450
|
+
UNSIGNED_INT: 0x1405,
|
|
451
|
+
UNPACK_ALIGNMENT: 0x0cf5,
|
|
452
|
+
FLOAT: 0x1406,
|
|
453
|
+
RGB: 0x1907,
|
|
454
|
+
RGBA: 0x1908,
|
|
455
|
+
LUMINANCE: 0x1909,
|
|
456
|
+
LUMINANCE_ALPHA: 0x190a,
|
|
457
|
+
NEAREST: 0x2600,
|
|
458
|
+
LINEAR: 0x2601,
|
|
459
|
+
NEAREST_MIPMAP_NEAREST: 0x2700,
|
|
460
|
+
LINEAR_MIPMAP_NEAREST: 0x2701,
|
|
461
|
+
NEAREST_MIPMAP_LINEAR: 0x2702,
|
|
462
|
+
LINEAR_MIPMAP_LINEAR: 0x2703,
|
|
463
|
+
TEXTURE_MAG_FILTER: 0x2800,
|
|
464
|
+
TEXTURE_MIN_FILTER: 0x2801,
|
|
465
|
+
TEXTURE_WRAP_S: 0x2802,
|
|
466
|
+
TEXTURE_WRAP_T: 0x2803,
|
|
467
|
+
REPEAT: 0x2901,
|
|
468
|
+
CLAMP_TO_EDGE: 0x812f,
|
|
469
|
+
MIRRORED_REPEAT: 0x8370,
|
|
470
|
+
...TEXTURE_UNITS,
|
|
471
|
+
ARRAY_BUFFER: 0x8892,
|
|
472
|
+
ELEMENT_ARRAY_BUFFER: 0x8893,
|
|
473
|
+
STREAM_DRAW: 0x88e0,
|
|
474
|
+
STATIC_DRAW: 0x88e4,
|
|
475
|
+
DYNAMIC_DRAW: 0x88e8,
|
|
476
|
+
FRAGMENT_SHADER: 0x8b30,
|
|
477
|
+
VERTEX_SHADER: 0x8b31,
|
|
478
|
+
COMPILE_STATUS: 0x8b81,
|
|
479
|
+
LINK_STATUS: 0x8b82,
|
|
480
|
+
VALIDATE_STATUS: 0x8b83,
|
|
481
|
+
INFO_LOG_LENGTH: 0x8b84,
|
|
482
|
+
DELETE_STATUS: 0x8b80,
|
|
483
|
+
FRAMEBUFFER: 0x8d40,
|
|
484
|
+
RENDERBUFFER: 0x8d41,
|
|
485
|
+
RGBA4: 0x8056,
|
|
486
|
+
DEPTH_COMPONENT16: 0x81a5,
|
|
487
|
+
DEPTH24_STENCIL8: 0x88f0,
|
|
488
|
+
STENCIL_INDEX8: 0x8d48,
|
|
489
|
+
COLOR_ATTACHMENT0: 0x8ce0,
|
|
490
|
+
DEPTH_ATTACHMENT: 0x8d00,
|
|
491
|
+
STENCIL_ATTACHMENT: 0x8d20,
|
|
492
|
+
DEPTH_STENCIL_ATTACHMENT: 0x821a,
|
|
493
|
+
FRAMEBUFFER_COMPLETE: 0x8cd5,
|
|
494
|
+
FRAMEBUFFER_BINDING: 0x8ca6,
|
|
495
|
+
RENDERBUFFER_BINDING: 0x8ca7,
|
|
496
|
+
VERTEX_ARRAY_BINDING: 0x85b5,
|
|
497
|
+
ARRAY_BUFFER_BINDING: 0x8894,
|
|
498
|
+
CURRENT_PROGRAM: 0x8b8d,
|
|
499
|
+
MAX_TEXTURE_SIZE: 0x0d33,
|
|
500
|
+
VIEWPORT: 0x0ba2,
|
|
501
|
+
NO_ERROR: 0,
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* The GL seams on the app — asked, not answered.
|
|
506
|
+
*
|
|
507
|
+
* Probing for a context is not free: making one, asking it its version and
|
|
508
|
+
* throwing it away costs about 120ms on a vendor driver, and it used to be
|
|
509
|
+
* spent by every app during `createRoot`, whether or not it would ever hold
|
|
510
|
+
* a `<glarea>`. Most never do. So this installs the seams and nothing else,
|
|
511
|
+
* and the probe happens the first time something actually asks.
|
|
512
|
+
*
|
|
513
|
+
* `glCapabilities()` answering a promise is what makes that safe:
|
|
514
|
+
* `watchDirectGL` (src/glbackend.js) already calls it and re-reads when it
|
|
515
|
+
* settles, which is the machinery for an answer that is not ready at the
|
|
516
|
+
* first render. `useSupports('shaders')` is false for a tick and then true,
|
|
517
|
+
* rather than costing every app a fifth of a second at startup.
|
|
518
|
+
*/
|
|
519
|
+
export function installGl(app) {
|
|
520
|
+
let settled = null;
|
|
521
|
+
|
|
522
|
+
const probe = () => {
|
|
523
|
+
if (settled) return settled;
|
|
524
|
+
const result = app._native.glProbe?.();
|
|
525
|
+
// A core context is the line. Below it there is no vendor driver, and
|
|
526
|
+
// the software rasterizer's GL 1.1 is not a degraded direct backend.
|
|
527
|
+
const direct = Boolean(result && result.core);
|
|
528
|
+
app._glCapsResolved = { direct, indirect: false, probe: result ?? null };
|
|
529
|
+
settled = Promise.resolve(app._glCapsResolved);
|
|
530
|
+
return settled;
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// A statement of intent, not of capability — `hasDirectGL` reads the
|
|
534
|
+
// policy *and* the capabilities, and only the second needs the probe.
|
|
535
|
+
app.glPolicy = { mode: 'direct' };
|
|
536
|
+
app.glCapabilities = () => probe();
|
|
537
|
+
app.chooseGLConfig = () =>
|
|
538
|
+
probe().then((caps) => {
|
|
539
|
+
if (caps.direct) return { backend: 'direct', visual: 0, depth: 32 };
|
|
540
|
+
throw new Error(
|
|
541
|
+
'react-x11: <glarea> has no GL surface — this machine has no vendor ' +
|
|
542
|
+
'OpenGL driver, so only the 1.1 software rasterizer is available, ' +
|
|
543
|
+
'which has no shaders. docs/windows-gl.md rung 2 (ANGLE) covers ' +
|
|
544
|
+
'this case and is not built yet.',
|
|
545
|
+
);
|
|
546
|
+
});
|
|
547
|
+
return true;
|
|
548
|
+
}
|