ntk 6.3.0 → 6.4.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/glx.js +121 -6
- package/lib/index.js +4 -1
- package/lib/renderingcontext_opengl.js +20 -8
- package/package.json +2 -2
package/lib/glx.js
CHANGED
|
@@ -1,10 +1,115 @@
|
|
|
1
|
-
// GLX visual/fbconfig discovery.
|
|
1
|
+
// GLX visual/fbconfig discovery, and the diagnosis of a failed setup.
|
|
2
2
|
//
|
|
3
3
|
// A GLX drawable has to use a visual the GL context was created for, so the
|
|
4
4
|
// visual must be known *before* CreateWindow. This module asks the server
|
|
5
5
|
// (GetFBConfigs, falling back to the GLX 1.2 GetVisualConfigs) instead of
|
|
6
6
|
// shelling out to `glxinfo`, so it works headlessly and in CI.
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* `err.code` on every error this module and the OpenGL context throw, so
|
|
10
|
+
* callers can branch on the cause instead of matching on message text.
|
|
11
|
+
*
|
|
12
|
+
* - `GLX_NO_EXTENSION` — the server does not have the GLX extension.
|
|
13
|
+
* - `GLX_INDIRECT_DISABLED` — it has GLX, but refuses indirect contexts,
|
|
14
|
+
* which is the only kind that works over the wire. The default on Xorg
|
|
15
|
+
* >= 1.17 and Xwayland, and by far the most common failure.
|
|
16
|
+
* - `GLX_NO_CONFIG` — no visual/fbconfig matches the requested attributes.
|
|
17
|
+
* - `GLX_CONTEXT_FAILED` — anything else that went wrong in setup.
|
|
18
|
+
*/
|
|
19
|
+
export const GLXError = {
|
|
20
|
+
NO_EXTENSION: 'GLX_NO_EXTENSION',
|
|
21
|
+
INDIRECT_DISABLED: 'GLX_INDIRECT_DISABLED',
|
|
22
|
+
NO_CONFIG: 'GLX_NO_CONFIG',
|
|
23
|
+
CONTEXT_FAILED: 'GLX_CONTEXT_FAILED'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// `message` says what is wrong in one line; `hint` is the multi-line
|
|
27
|
+
// remedy, printed by the default warning and available to anyone rendering
|
|
28
|
+
// their own fallback UI.
|
|
29
|
+
function glxError(code, message, hint, cause) {
|
|
30
|
+
const err = new Error(message);
|
|
31
|
+
err.code = code;
|
|
32
|
+
if (hint) err.hint = hint;
|
|
33
|
+
if (cause) err.cause = cause;
|
|
34
|
+
return err;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const displayName = () => process.env.DISPLAY || 'the X server';
|
|
38
|
+
|
|
39
|
+
const IGLX_HINT = `Indirect GLX is what ntk draws with: GL commands travel over the X
|
|
40
|
+
connection, since there is no client-side GL library involved. Xorg >= 1.17
|
|
41
|
+
and Xwayland ship with it disabled, and answer GLX CreateContext with
|
|
42
|
+
BadValue. The extension is still present and still advertises GLX 1.4 with a
|
|
43
|
+
full set of fbconfigs, which is why this looks like it should have worked.
|
|
44
|
+
|
|
45
|
+
To get a server that allows it:
|
|
46
|
+
under Wayland: Xwayland :5 +iglx & then DISPLAY=:5
|
|
47
|
+
under X: Xephyr :5 +iglx & then DISPLAY=:5
|
|
48
|
+
Xorg: +iglx on the command line, or
|
|
49
|
+
Option "AllowIndirectGLX" "on" in xorg.conf
|
|
50
|
+
XQuartz: defaults write org.xquartz.X11 enable_iglx -bool true
|
|
51
|
+
|
|
52
|
+
Note that on current Linux distros the indirect GL *engine* behind those
|
|
53
|
+
contexts is often gone even with +iglx on — contexts are created but nothing
|
|
54
|
+
rasterizes. See docs/context-opengl.md.`;
|
|
55
|
+
|
|
56
|
+
/** Turn a failed GLX CreateContext into an error that explains itself. */
|
|
57
|
+
export function diagnoseCreateContext(err, cfg) {
|
|
58
|
+
// BadValue with value 0 from CreateContext (minor opcode 3) is how a
|
|
59
|
+
// server without +iglx says "no indirect contexts here" — every other
|
|
60
|
+
// rejection names the value it disliked
|
|
61
|
+
if (err?.error === 2 && err.badParam === 0) {
|
|
62
|
+
return glxError(
|
|
63
|
+
GLXError.INDIRECT_DISABLED,
|
|
64
|
+
`${displayName()} has GLX but refuses to create indirect GLX contexts, so there is no OpenGL context to draw with`,
|
|
65
|
+
IGLX_HINT,
|
|
66
|
+
err
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const visual = cfg?.visual ? ` for visual 0x${cfg.visual.toString(16)}` : '';
|
|
70
|
+
return glxError(
|
|
71
|
+
GLXError.CONTEXT_FAILED,
|
|
72
|
+
`GLX CreateContext${visual} failed: ${err?.message ?? err}`,
|
|
73
|
+
null,
|
|
74
|
+
err
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The console form of a failed GL setup: what went wrong, then how to fix
|
|
80
|
+
* it. Indented so the remedy reads as one block and not as unrelated log
|
|
81
|
+
* lines, and ending with the way to take over the handling.
|
|
82
|
+
*/
|
|
83
|
+
export function glxErrorReport(err) {
|
|
84
|
+
const lines = [`ntk: getContext('opengl') failed — ${err?.message ?? err}`];
|
|
85
|
+
if (err?.hint) lines.push('', ...err.hint.split('\n').map((l) => (l ? ` ${l}` : l)));
|
|
86
|
+
lines.push('', ` (await gl.ready to handle this yourself; err.code is ${err?.code ?? 'unset'})`);
|
|
87
|
+
return lines.join('\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create a GLX context and find out whether it worked.
|
|
92
|
+
*
|
|
93
|
+
* CreateContext has no reply, so its failure would otherwise surface as an
|
|
94
|
+
* unattributed X error plus a misleading GLXBadContext from the MakeCurrent
|
|
95
|
+
* that follows. node-x11 >= 3.8 takes a callback here; older versions
|
|
96
|
+
* cannot report it at all, and this resolves optimistically rather than
|
|
97
|
+
* hanging.
|
|
98
|
+
*/
|
|
99
|
+
export function createContext(GLX, ctx, cfg) {
|
|
100
|
+
if (GLX.CreateContext.length < 6) {
|
|
101
|
+
GLX.CreateContext(ctx, cfg.visual, cfg.screen ?? 0, 0, 0);
|
|
102
|
+
return Promise.resolve();
|
|
103
|
+
}
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
GLX.CreateContext(ctx, cfg.visual, cfg.screen ?? 0, 0, 0, (err) => {
|
|
106
|
+
if (err) reject(diagnoseCreateContext(err, cfg));
|
|
107
|
+
else resolve();
|
|
108
|
+
return true; // handled here; keep it away from the app's onXError
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
8
113
|
// defaults applied on top of the caller's spec; `null` means "don't care"
|
|
9
114
|
const DEFAULT_SPEC = {
|
|
10
115
|
DOUBLEBUFFER: true,
|
|
@@ -120,8 +225,12 @@ function matchLegacy(configs, spec) {
|
|
|
120
225
|
export async function chooseGLXConfig(app, spec = {}) {
|
|
121
226
|
const GLX = app.display.GLX;
|
|
122
227
|
if (!GLX) {
|
|
123
|
-
throw
|
|
124
|
-
|
|
228
|
+
throw glxError(
|
|
229
|
+
GLXError.NO_EXTENSION,
|
|
230
|
+
`${displayName()} does not have the GLX extension, so it cannot do OpenGL at all`,
|
|
231
|
+
'A server built without GLX, or started with -extension GLX. This is a\n' +
|
|
232
|
+
'different problem from indirect GLX being disabled, which is the usual\n' +
|
|
233
|
+
'one — there the extension is present and CreateContext is what fails.'
|
|
125
234
|
);
|
|
126
235
|
}
|
|
127
236
|
|
|
@@ -135,7 +244,11 @@ export async function chooseGLXConfig(app, spec = {}) {
|
|
|
135
244
|
|
|
136
245
|
if (want.visual) {
|
|
137
246
|
const info = visualInfo(screen, want.visual);
|
|
138
|
-
if (!info)
|
|
247
|
+
if (!info)
|
|
248
|
+
throw glxError(
|
|
249
|
+
GLXError.NO_CONFIG,
|
|
250
|
+
`GLX: visual 0x${want.visual.toString(16)} is not on screen ${screenNum}`
|
|
251
|
+
);
|
|
139
252
|
return {
|
|
140
253
|
visual: want.visual,
|
|
141
254
|
depth: info.depth,
|
|
@@ -187,8 +300,10 @@ export async function chooseGLXConfig(app, spec = {}) {
|
|
|
187
300
|
const legacyBest = pick(fromVisuals);
|
|
188
301
|
if (legacyBest) return legacyBest;
|
|
189
302
|
|
|
190
|
-
throw
|
|
191
|
-
|
|
303
|
+
throw glxError(
|
|
304
|
+
GLXError.NO_CONFIG,
|
|
305
|
+
`GLX: no visual on screen ${screenNum} matches ${describe(want)} (server offered ${legacy.length} GLX visuals)`,
|
|
306
|
+
'Relax the spec — DEPTH_SIZE and DOUBLEBUFFER are the usual culprits.'
|
|
192
307
|
);
|
|
193
308
|
}
|
|
194
309
|
|
package/lib/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import x11 from 'x11';
|
|
|
3
3
|
import App from './app.js';
|
|
4
4
|
import Clipboard from './clipboard.js';
|
|
5
5
|
import { BLANK_CURSOR, CursorCache, cursorNames, cursorShapes, resolveCursorShape } from './cursor.js';
|
|
6
|
+
import { GLXError } from './glx.js';
|
|
6
7
|
import Window from './window.js';
|
|
7
8
|
import { loadLayout } from './yoga.js';
|
|
8
9
|
import { decodeKey, groupForState } from './keyboard.js';
|
|
@@ -227,7 +228,9 @@ export {
|
|
|
227
228
|
premultiply,
|
|
228
229
|
cssLength,
|
|
229
230
|
decodeKey,
|
|
230
|
-
groupForState
|
|
231
|
+
groupForState,
|
|
232
|
+
// the `code` on a failed GL setup: branch on it rather than on the message
|
|
233
|
+
GLXError
|
|
231
234
|
};
|
|
232
235
|
// The layout engine ntk lays HtmlView out with — downstream layout consumers
|
|
233
236
|
// (e.g. the react-x11 renderer) must import it from here rather than from
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Drawable from './drawable.js';
|
|
2
|
-
import { chooseGLXConfig } from './glx.js';
|
|
2
|
+
import { GLXError, chooseGLXConfig, createContext, glxErrorReport } from './glx.js';
|
|
3
3
|
|
|
4
4
|
// Indirect GLX rendering context exposing an OpenGL 1.4-style api.
|
|
5
5
|
// Note: many modern X servers ship with indirect GLX disabled — see README.
|
|
@@ -14,9 +14,11 @@ class RenderingContextOpenGL {
|
|
|
14
14
|
const X = window.X;
|
|
15
15
|
const GLX = window.display.GLX;
|
|
16
16
|
if (!GLX) {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const err = new Error(
|
|
18
|
+
`getContext('opengl'): ${process.env.DISPLAY || 'the X server'} does not have the GLX extension, so it cannot do OpenGL at all`
|
|
19
19
|
);
|
|
20
|
+
err.code = GLXError.NO_EXTENSION;
|
|
21
|
+
throw err;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
this.window = window;
|
|
@@ -82,9 +84,21 @@ class RenderingContextOpenGL {
|
|
|
82
84
|
|
|
83
85
|
const ctx = X.AllocID();
|
|
84
86
|
this.contextId = ctx;
|
|
85
|
-
|
|
87
|
+
// CreateContext first, and on its own: it is the request a server with
|
|
88
|
+
// indirect GLX disabled rejects, and going on to MakeCurrent would bury
|
|
89
|
+
// that under a GLXBadContext about the context that was never created
|
|
90
|
+
await createContext(GLX, ctx, cfg).catch((err) => {
|
|
91
|
+
this.contextId = 0; // nothing to destroy
|
|
92
|
+
throw err;
|
|
93
|
+
});
|
|
86
94
|
const tag = await new Promise((resolve, reject) => {
|
|
87
|
-
GLX.MakeCurrent(window.id, ctx, 0, (err, contextTag) =>
|
|
95
|
+
GLX.MakeCurrent(window.id, ctx, 0, (err, contextTag) => {
|
|
96
|
+
if (!err) return resolve(contextTag);
|
|
97
|
+
const failed = new Error(`GLX MakeCurrent failed: ${err.message}`);
|
|
98
|
+
failed.code = GLXError.CONTEXT_FAILED;
|
|
99
|
+
failed.cause = err;
|
|
100
|
+
reject(failed);
|
|
101
|
+
});
|
|
88
102
|
});
|
|
89
103
|
|
|
90
104
|
this.contextTag = tag;
|
|
@@ -97,9 +111,7 @@ class RenderingContextOpenGL {
|
|
|
97
111
|
this.ready.catch((err) => {
|
|
98
112
|
this.error = err;
|
|
99
113
|
queue.length = 0;
|
|
100
|
-
console.warn(
|
|
101
|
-
`ntk: getContext('opengl') failed: ${err.message} (await gl.ready to handle this yourself)`
|
|
102
|
-
);
|
|
114
|
+
console.warn(glxErrorReport(err));
|
|
103
115
|
});
|
|
104
116
|
}
|
|
105
117
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ntk",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
4
4
|
"description": "Desktop UI toolkit for X11 with canvas-like 2d and OpenGL rendering",
|
|
5
5
|
"author": "Andrey Sidorov <sidorares@yandex.ru>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"parse-color": "^1.0.0",
|
|
49
49
|
"pngjs": "^7.0.0",
|
|
50
50
|
"postcss": "^8.5.23",
|
|
51
|
-
"x11": "^3.
|
|
51
|
+
"x11": "^3.8.0",
|
|
52
52
|
"yoga-layout": "^3.2.1"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|