ntk 7.2.0 → 7.3.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 +95 -0
- package/lib/gl.js +321 -0
- package/lib/glswapchain.js +408 -0
- package/lib/index.js +34 -4
- package/lib/path.js +28 -0
- package/lib/renderingcontext_2d.js +128 -6
- package/lib/renderingcontext_gles.js +312 -0
- package/lib/window.js +29 -1
- package/package.json +5 -2
package/lib/app.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Clipboard from './clipboard.js';
|
|
2
2
|
import { CursorCache } from './cursor.js';
|
|
3
|
+
import { GLError, backendFor, glCapabilities, glError, resolveGLPolicy } from './gl.js';
|
|
3
4
|
import { chooseGLXConfig } from './glx.js';
|
|
4
5
|
import Picture from './picture.js';
|
|
5
6
|
import Pixmap from './pixmap.js';
|
|
@@ -265,6 +266,39 @@ export default class App {
|
|
|
265
266
|
: DEFAULT_RASTER_POLICY;
|
|
266
267
|
}
|
|
267
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Which OpenGL backend windows on this connection draw through, merged over
|
|
271
|
+
* `DEFAULT_GL_POLICY` and overridden by `NTK_GL_POLICY` — see
|
|
272
|
+
* docs/context-gles.md. The default is `'indirect'`.
|
|
273
|
+
*
|
|
274
|
+
* Recomputed rather than cached, so raising it at run time works the way
|
|
275
|
+
* `rasterPolicy` does. A policy raised to `auto`/`direct` after connecting
|
|
276
|
+
* has missed the probe `createClient` would have run for it, so one
|
|
277
|
+
* `await app.glCapabilities()` is needed before a context is created.
|
|
278
|
+
*/
|
|
279
|
+
get glPolicy() {
|
|
280
|
+
return resolveGLPolicy(this.options);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* What GL this connection can really do: `{ direct, indirect, device,
|
|
285
|
+
* reason }`, where `reason` is the coded error explaining a false `direct`
|
|
286
|
+
* (see `GLError`). Asked once and cached.
|
|
287
|
+
*
|
|
288
|
+
* `createClient` warms this during the handshake whenever the policy could
|
|
289
|
+
* choose the direct backend — that is what lets `getContext` pick one
|
|
290
|
+
* synchronously afterwards.
|
|
291
|
+
*
|
|
292
|
+
* @returns {Promise<{direct: boolean, indirect: boolean,
|
|
293
|
+
* device: string|null, reason: Error|null}>}
|
|
294
|
+
*/
|
|
295
|
+
glCapabilities() {
|
|
296
|
+
return glCapabilities(this).then((caps) => {
|
|
297
|
+
this._glCapsResolved = caps;
|
|
298
|
+
return caps;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
268
302
|
/** selection/clipboard transfer: write()/read() text (docs/clipboard.md) */
|
|
269
303
|
get clipboard() {
|
|
270
304
|
if (!this._clipboard) this._clipboard = new Clipboard(this);
|
|
@@ -305,6 +339,53 @@ export default class App {
|
|
|
305
339
|
return chooseGLXConfig(this, spec);
|
|
306
340
|
}
|
|
307
341
|
|
|
342
|
+
/**
|
|
343
|
+
* The same question, asked of whichever backend `glPolicy` selects: what to
|
|
344
|
+
* create a GL window with. Resolves with a `chooseGLXConfig` object plus
|
|
345
|
+
* `backend` (`'direct'` or `'indirect'`) — pass `visual`/`depth` to
|
|
346
|
+
* `createWindow` and the whole object to `wnd.getContext('opengl', config)`.
|
|
347
|
+
*
|
|
348
|
+
* The spec is GLX's attribute vocabulary either way, because a caller
|
|
349
|
+
* should not have to write the request twice: `DEPTH_SIZE` becomes the EGL
|
|
350
|
+
* depth-buffer size on the direct backend, and `ALPHA_SIZE` picks an ARGB
|
|
351
|
+
* visual there rather than an fbconfig with an alpha channel. Direct needs
|
|
352
|
+
* no round trip to answer — there are no fbconfigs in it, only a window the
|
|
353
|
+
* GPU's buffers can be copied or flipped into.
|
|
354
|
+
*
|
|
355
|
+
* @param {object} [spec] GLX attributes, e.g. `{ DEPTH_SIZE: 24 }`
|
|
356
|
+
*/
|
|
357
|
+
async chooseGLConfig(spec = {}) {
|
|
358
|
+
const caps = this._glCapsResolved ?? (await this.glCapabilities());
|
|
359
|
+
const backend = backendFor(this);
|
|
360
|
+
if (backend === 'off') throw caps.reason ?? new Error('ntk: no GL backend is available');
|
|
361
|
+
if (backend !== 'direct') return { ...(await chooseGLXConfig(this, spec)), backend: 'indirect' };
|
|
362
|
+
|
|
363
|
+
const screenNum = spec.screen ?? 0;
|
|
364
|
+
const screen = this.display.screen[screenNum];
|
|
365
|
+
const wantAlpha = (spec.ALPHA_SIZE ?? 0) > 0;
|
|
366
|
+
const argb = wantAlpha ? this.findArgbVisual(screenNum) : null;
|
|
367
|
+
if (wantAlpha && !argb) {
|
|
368
|
+
throw glError(
|
|
369
|
+
GLError.CONTEXT_FAILED,
|
|
370
|
+
`direct rendering: screen ${screenNum} has no 32-bit visual, so a GL window cannot have an alpha channel`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
backend: 'direct',
|
|
375
|
+
// depth 24 on the root visual needs no colormap of its own, which is
|
|
376
|
+
// why an opaque GL window here is an ordinary window
|
|
377
|
+
visual: argb?.visual ?? screen.root_visual,
|
|
378
|
+
depth: argb?.depth ?? screen.root_depth,
|
|
379
|
+
class: 4, // TrueColor; the only class these buffers can be read as
|
|
380
|
+
doubleBuffer: true, // a swap chain, always
|
|
381
|
+
depthSize: spec.DEPTH_SIZE ?? 16,
|
|
382
|
+
screen: screenNum,
|
|
383
|
+
fbconfig: null,
|
|
384
|
+
device: caps.device,
|
|
385
|
+
config: {}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
308
389
|
/**
|
|
309
390
|
* Find a 32-bit TrueColor visual with an alpha channel, for per-pixel
|
|
310
391
|
* transparent windows (ARGB). Returns `{ visual, depth: 32 }` — pass
|
|
@@ -420,6 +501,20 @@ export default class App {
|
|
|
420
501
|
picture._sourcePixmap?.destroy();
|
|
421
502
|
}
|
|
422
503
|
this._solidPictures.clear();
|
|
504
|
+
// GPU contexts are shared by every direct GL surface on this connection
|
|
505
|
+
// (see renderingcontext_gles.js), so they outlive individual contexts and
|
|
506
|
+
// are the connection's to release. Each holds an EGL display, a GBM device
|
|
507
|
+
// and an open render node.
|
|
508
|
+
if (this._glGpus) {
|
|
509
|
+
for (const gpu of this._glGpus.values()) {
|
|
510
|
+
try {
|
|
511
|
+
gpu.destroy();
|
|
512
|
+
} catch {
|
|
513
|
+
// already torn down, or the driver is gone
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
this._glGpus.clear();
|
|
517
|
+
}
|
|
423
518
|
return new Promise((resolve) => this.X.close(resolve));
|
|
424
519
|
}
|
|
425
520
|
|
package/lib/gl.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// Which OpenGL backend a window draws through, and whether it can.
|
|
2
|
+
//
|
|
3
|
+
// ntk has two, and they are different pipelines rather than two spellings of
|
|
4
|
+
// one:
|
|
5
|
+
//
|
|
6
|
+
// - **indirect GLX** (lib/renderingcontext_opengl.js) — GL commands encoded
|
|
7
|
+
// into the X connection. Reaches any server that allows indirect contexts,
|
|
8
|
+
// including over a network, and is a fixed-function OpenGL 1.x pipeline
|
|
9
|
+
// with no shaders, because that is what the GLX protocol encodes.
|
|
10
|
+
// - **direct** (lib/renderingcontext_gles.js) — a GPU render node draws the
|
|
11
|
+
// frame, and the finished buffer reaches the server as a dma-buf
|
|
12
|
+
// descriptor over DRI3 + Present. OpenGL ES 2 with real shaders, and no
|
|
13
|
+
// pixels on the socket; local connections only, and it needs the optional
|
|
14
|
+
// `x11-dri` addon.
|
|
15
|
+
//
|
|
16
|
+
// Which one runs is `glPolicy`, and the default is `indirect` — the backend
|
|
17
|
+
// that has always run. See docs/context-gles.md.
|
|
18
|
+
//
|
|
19
|
+
// This module is the decision and nothing else: what is available, what the
|
|
20
|
+
// caller asked for, and what that resolves to. Neither context imports the
|
|
21
|
+
// other, and the direct one is only ever loaded when the answer is `direct`.
|
|
22
|
+
|
|
23
|
+
import { nodeRequire } from './builtin.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `err.code` on failures of the direct path, alongside `GLXError` for the
|
|
27
|
+
* indirect one. Every code names a distinct remedy:
|
|
28
|
+
*
|
|
29
|
+
* - `GL_DISABLED` — `glPolicy: 'off'`; nothing tried.
|
|
30
|
+
* - `GL_NO_ADDON` — the optional `x11-dri` addon is not installed or would
|
|
31
|
+
* not load. `npm install x11-dri`.
|
|
32
|
+
* - `GL_NO_DRIVER` — the addon loaded but the GPU libraries it needs are
|
|
33
|
+
* missing (`libgbm`, `libEGL`, `libGLESv2`), or this is not Linux.
|
|
34
|
+
* - `GL_NO_DEVICE` — no readable DRM render node (`/dev/dri/renderD*`).
|
|
35
|
+
* - `GL_REMOTE_DISPLAY` — the connection cannot carry descriptors: a TCP or
|
|
36
|
+
* forwarded display, so there is no way to hand the server a buffer.
|
|
37
|
+
* - `GL_NO_DRI3` — the server has no DRI3/Present (Xvfb, Xephyr, XQuartz).
|
|
38
|
+
* - `GL_IMPORT_FAILED` — the server refused the buffer; usually client and
|
|
39
|
+
* server on different DRM devices.
|
|
40
|
+
* - `GL_CONTEXT_FAILED` — GBM/EGL setup failed for some other reason.
|
|
41
|
+
*/
|
|
42
|
+
export const GLError = {
|
|
43
|
+
DISABLED: 'GL_DISABLED',
|
|
44
|
+
NO_ADDON: 'GL_NO_ADDON',
|
|
45
|
+
NO_DRIVER: 'GL_NO_DRIVER',
|
|
46
|
+
NO_DEVICE: 'GL_NO_DEVICE',
|
|
47
|
+
REMOTE_DISPLAY: 'GL_REMOTE_DISPLAY',
|
|
48
|
+
NO_DRI3: 'GL_NO_DRI3',
|
|
49
|
+
IMPORT_FAILED: 'GL_IMPORT_FAILED',
|
|
50
|
+
CONTEXT_FAILED: 'GL_CONTEXT_FAILED'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function glError(code, message, hint, cause) {
|
|
54
|
+
const err = new Error(message);
|
|
55
|
+
err.code = code;
|
|
56
|
+
if (hint) err.hint = hint;
|
|
57
|
+
if (cause) err.cause = cause;
|
|
58
|
+
return err;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The backend choice, and the knobs the direct one has.
|
|
63
|
+
*
|
|
64
|
+
* `mode` is the whole decision:
|
|
65
|
+
* - `'indirect'` (default) — indirect GLX, the only backend before 7.x.
|
|
66
|
+
* - `'auto'` — direct where everything for it is present, indirect otherwise.
|
|
67
|
+
* - `'direct'` — direct or nothing; `getContext` fails with a coded error
|
|
68
|
+
* rather than quietly running a fixed-function pipeline instead.
|
|
69
|
+
* - `'off'` — no GL at all.
|
|
70
|
+
*
|
|
71
|
+
* The default is deliberately not `'auto'`: the two backends expose different
|
|
72
|
+
* GL APIs (see docs/context-gles.md), so switching under an app that never
|
|
73
|
+
* asked would break its draw code. Opt in per app, or per run with
|
|
74
|
+
* `NTK_GL_POLICY`.
|
|
75
|
+
*/
|
|
76
|
+
export const DEFAULT_GL_POLICY = {
|
|
77
|
+
mode: 'indirect',
|
|
78
|
+
// which render node to draw on; null picks the first usable one
|
|
79
|
+
devicePath: null,
|
|
80
|
+
// presents allowed in flight before a frame waits for a buffer to come back
|
|
81
|
+
maxInFlight: 2,
|
|
82
|
+
// retry a refused buffer import once with a linear layout, which is what
|
|
83
|
+
// makes cross-device (render on one GPU, display on another) work
|
|
84
|
+
linearFallback: true
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const GL_MODES = ['auto', 'direct', 'indirect', 'off'];
|
|
88
|
+
|
|
89
|
+
function badMode(mode, source) {
|
|
90
|
+
return new Error(
|
|
91
|
+
`ntk: ${source} is ${JSON.stringify(mode)}, which is not a GL policy mode — use one of ${GL_MODES.join(', ')} (see docs/context-gles.md)`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The effective policy for an app: defaults, then `options.glPolicy` (a mode
|
|
97
|
+
* string is sugar for `{ mode }`), then `NTK_GL_POLICY`.
|
|
98
|
+
*
|
|
99
|
+
* The environment wins on purpose. Its job is running one build both ways —
|
|
100
|
+
* `NTK_GL_POLICY=direct npm start` against the same app that ships
|
|
101
|
+
* `'indirect'` — which it cannot do if the code overrides it.
|
|
102
|
+
*/
|
|
103
|
+
export function resolveGLPolicy(options = {}) {
|
|
104
|
+
const given = options.glPolicy;
|
|
105
|
+
const asObject = typeof given === 'string' ? { mode: given } : given;
|
|
106
|
+
if (asObject && typeof asObject !== 'object') throw badMode(given, 'glPolicy');
|
|
107
|
+
const policy = { ...DEFAULT_GL_POLICY, ...asObject };
|
|
108
|
+
|
|
109
|
+
const fromEnv = globalThis.process?.env?.NTK_GL_POLICY;
|
|
110
|
+
if (fromEnv) policy.mode = fromEnv;
|
|
111
|
+
|
|
112
|
+
if (!GL_MODES.includes(policy.mode)) {
|
|
113
|
+
throw badMode(policy.mode, fromEnv ? 'NTK_GL_POLICY' : 'glPolicy.mode');
|
|
114
|
+
}
|
|
115
|
+
return policy;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Does this policy ever want the direct backend? */
|
|
119
|
+
export const wantsDirect = (policy) => policy.mode === 'auto' || policy.mode === 'direct';
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// the addon
|
|
123
|
+
|
|
124
|
+
let addon; // undefined = not tried, null = unavailable
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The optional `x11-dri` addon, or null.
|
|
128
|
+
*
|
|
129
|
+
* Loaded through `nodeRequire` rather than imported: it is a native module and
|
|
130
|
+
* an optional dependency, so it is absent on plenty of machines that run ntk
|
|
131
|
+
* perfectly well, and a static import would make it a hard requirement of the
|
|
132
|
+
* package (and of any bundle built from it). Nothing here throws.
|
|
133
|
+
*/
|
|
134
|
+
export function loadDriAddon() {
|
|
135
|
+
if (addon !== undefined) return addon;
|
|
136
|
+
addon = null;
|
|
137
|
+
try {
|
|
138
|
+
const require = nodeRequire();
|
|
139
|
+
if (require) addon = require('x11-dri');
|
|
140
|
+
} catch {
|
|
141
|
+
// not installed, wrong platform, or no loadable binary — all "no direct"
|
|
142
|
+
}
|
|
143
|
+
return addon;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Test seam: swap the addon (or clear the cache with `undefined`). */
|
|
147
|
+
export function setDriAddon(module) {
|
|
148
|
+
addon = module;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const INSTALL_HINT = `Direct rendering needs the optional native addon:
|
|
152
|
+
|
|
153
|
+
npm install x11-dri
|
|
154
|
+
|
|
155
|
+
It ships prebuilt binaries for linux x64/arm64 and macOS arm64, so no build
|
|
156
|
+
tools are needed; anything else compiles with node-gyp and a C toolchain. ntk
|
|
157
|
+
does not depend on it — without it, GL runs through indirect GLX.`;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* What the *client side* can do, before any server is asked: the addon, the
|
|
161
|
+
* GPU libraries under it, and a render node to draw on. Never throws.
|
|
162
|
+
*
|
|
163
|
+
* @returns {{ok: boolean, code?: string, message?: string, hint?: string,
|
|
164
|
+
* device?: string, devices?: string[], probe?: object}}
|
|
165
|
+
*/
|
|
166
|
+
export function probeDirect(policy = DEFAULT_GL_POLICY) {
|
|
167
|
+
const dri = loadDriAddon();
|
|
168
|
+
if (!dri) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
code: GLError.NO_ADDON,
|
|
172
|
+
message: 'the x11-dri addon is not installed, so there is no way to produce GPU buffers',
|
|
173
|
+
hint: INSTALL_HINT
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let probe;
|
|
178
|
+
try {
|
|
179
|
+
probe = dri.probe();
|
|
180
|
+
} catch (err) {
|
|
181
|
+
return { ok: false, code: GLError.NO_DRIVER, message: `x11-dri probe() failed: ${err.message}` };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// probe() reports each capability as `true` or as the string saying why not
|
|
185
|
+
const missing = ['gbm', 'egl', 'gles'].filter((key) => probe[key] !== true);
|
|
186
|
+
if (missing.length) {
|
|
187
|
+
const detail = missing.map((key) => `${key}: ${probe[key]}`).join('; ');
|
|
188
|
+
return {
|
|
189
|
+
ok: false,
|
|
190
|
+
code: GLError.NO_DRIVER,
|
|
191
|
+
message: `the GPU libraries direct rendering needs are unavailable (${detail})`,
|
|
192
|
+
hint:
|
|
193
|
+
probe.platform && probe.platform !== 'linux'
|
|
194
|
+
? `Direct rendering is Linux-only — dma-buf, GBM and DRI3 have no equivalent on ${probe.platform}.`
|
|
195
|
+
: 'Install Mesa (libgbm1, libegl1, libgles2 on Debian/Ubuntu). They are dlopen()ed at\nrun time, so no rebuild of x11-dri is needed once they are there.',
|
|
196
|
+
probe
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let devices = [];
|
|
201
|
+
try {
|
|
202
|
+
devices = dri.listRenderNodes();
|
|
203
|
+
} catch {
|
|
204
|
+
devices = [];
|
|
205
|
+
}
|
|
206
|
+
const device = policy.devicePath ?? devices[0] ?? null;
|
|
207
|
+
if (!device) {
|
|
208
|
+
return {
|
|
209
|
+
ok: false,
|
|
210
|
+
code: GLError.NO_DEVICE,
|
|
211
|
+
message: 'no readable DRM render node (/dev/dri/renderD*) — there is no GPU to render on',
|
|
212
|
+
hint:
|
|
213
|
+
'A container usually needs the device mapped in (--device /dev/dri), and a bare\nmachine needs the user in the "render" (or "video") group.',
|
|
214
|
+
probe,
|
|
215
|
+
devices
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return { ok: true, device, devices, probe };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// the connection and the server
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Can this connection carry a descriptor to the server at all?
|
|
226
|
+
*
|
|
227
|
+
* DRI3 hands the server a dma-buf over the socket with SCM_RIGHTS, which only
|
|
228
|
+
* a local unix socket can do. Everything remote — TCP, `ssh -X` — is out
|
|
229
|
+
* before any extension is queried, and this costs nothing to find out.
|
|
230
|
+
*/
|
|
231
|
+
export function canPassDescriptors(display) {
|
|
232
|
+
const stream = display?.client?.stream;
|
|
233
|
+
return !!(display?.isLocalSocket && stream?._fdCapable && typeof stream.sendFds === 'function');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const displayName = () => globalThis.process?.env?.DISPLAY || 'the X server';
|
|
237
|
+
|
|
238
|
+
const requireExt = (X, name) =>
|
|
239
|
+
new Promise((resolve) => X.require(name, (err, ext) => resolve(err ? null : ext)));
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Everything about `app` that decides the backend, answered once and cached.
|
|
243
|
+
*
|
|
244
|
+
* The two extension queries are the only round trips, and they only happen
|
|
245
|
+
* under a policy that could use direct — `createClient` warms this during the
|
|
246
|
+
* connect handshake for exactly that reason, so `getContext` can decide
|
|
247
|
+
* synchronously afterwards.
|
|
248
|
+
*
|
|
249
|
+
* @returns {Promise<{direct: boolean, indirect: boolean, device: string|null,
|
|
250
|
+
* reason: Error|null, DRI3: object|null, Present: object|null}>}
|
|
251
|
+
*/
|
|
252
|
+
export function glCapabilities(app) {
|
|
253
|
+
if (app._glCaps) return app._glCaps;
|
|
254
|
+
app._glCaps = (async () => {
|
|
255
|
+
const policy = app.glPolicy;
|
|
256
|
+
const indirect = !!app.display.GLX;
|
|
257
|
+
const fail = (code, message, hint) => ({
|
|
258
|
+
direct: false,
|
|
259
|
+
indirect,
|
|
260
|
+
device: null,
|
|
261
|
+
reason: glError(code, message, hint),
|
|
262
|
+
DRI3: null,
|
|
263
|
+
Present: null
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
if (policy.mode === 'off') {
|
|
267
|
+
return fail(GLError.DISABLED, "glPolicy is 'off', so no GL context will be created");
|
|
268
|
+
}
|
|
269
|
+
if (!canPassDescriptors(app.display)) {
|
|
270
|
+
return fail(
|
|
271
|
+
GLError.REMOTE_DISPLAY,
|
|
272
|
+
'this X connection is not a local socket that can pass descriptors, and DRI3 works by passing one',
|
|
273
|
+
'Direct rendering is local-only by construction. Over a network, indirect GLX is\n' +
|
|
274
|
+
'the backend that can work at all — leave glPolicy at its default.'
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const client = probeDirect(policy);
|
|
279
|
+
if (!client.ok) return fail(client.code, client.message, client.hint);
|
|
280
|
+
|
|
281
|
+
const X = app.X;
|
|
282
|
+
const [DRI3, Present] = await Promise.all([requireExt(X, 'dri3'), requireExt(X, 'present')]);
|
|
283
|
+
if (!DRI3 || !Present) {
|
|
284
|
+
const absent = [!DRI3 && 'DRI3', !Present && 'Present'].filter(Boolean).join(' and ');
|
|
285
|
+
return fail(
|
|
286
|
+
GLError.NO_DRI3,
|
|
287
|
+
`${displayName()} does not have ${absent}, so a GPU buffer cannot be turned into a pixmap or shown`,
|
|
288
|
+
'Xorg with glamor and Xwayland both have them; Xvfb, Xephyr and XQuartz do not.\n' +
|
|
289
|
+
'Indirect GLX is the backend that can work on such a server.'
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
if (!DRI3.fdCapable) {
|
|
293
|
+
return fail(
|
|
294
|
+
GLError.REMOTE_DISPLAY,
|
|
295
|
+
'the x11 client cannot send descriptors on this connection (DRI3.fdCapable is false)',
|
|
296
|
+
'x11 builds an fd-capable socket for local displays unless `shm: false` was passed\nto createClient.'
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return { direct: true, indirect, device: client.device, reason: null, DRI3, Present };
|
|
301
|
+
})();
|
|
302
|
+
return app._glCaps;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* The backend `getContext('opengl')` should use right now, synchronously.
|
|
307
|
+
*
|
|
308
|
+
* Synchronous because `getContext` is, and the async part (two extension
|
|
309
|
+
* queries) is warmed at connect time under any policy that could say
|
|
310
|
+
* `direct`. `null` means "not known yet" — a policy raised to `auto` after
|
|
311
|
+
* connect, before `await app.glCapabilities()` has answered.
|
|
312
|
+
*/
|
|
313
|
+
export function backendFor(app) {
|
|
314
|
+
const mode = app.glPolicy.mode;
|
|
315
|
+
if (mode === 'off') return 'off';
|
|
316
|
+
if (mode === 'indirect') return 'indirect';
|
|
317
|
+
const settled = app._glCapsResolved;
|
|
318
|
+
if (!settled) return null;
|
|
319
|
+
if (settled.direct) return 'direct';
|
|
320
|
+
return mode === 'direct' ? 'off' : 'indirect';
|
|
321
|
+
}
|