Skip to content

Commit 4225ee3

Browse files
committed
Isolate macOS GL probe attempts
1 parent 80a18af commit 4225ee3

1 file changed

Lines changed: 233 additions & 111 deletions

File tree

.github/scripts/probe-macos-gl.mjs

Lines changed: 233 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,135 @@
1-
// oxlint-disable no-console
2-
import { GlfwWindow, glfw } from '@node-3d/glfw';
3-
import { BrowserDocument } from '../../ts/core/browser-document.ts';
4-
import { init } from '../../ts/index.ts';
1+
// oxlint-disable no-console node/no-sync node/no-process-env
2+
import { spawnSync } from 'node:child_process';
3+
4+
const childArg = '--child';
5+
6+
const probes = [
7+
{
8+
name: 'GlfwWindow Cocoa hidden default',
9+
kind: 'glfw',
10+
platform: 'cocoa',
11+
window: 'hidden',
12+
},
13+
{
14+
name: 'GlfwWindow Cocoa hidden no depth/stencil',
15+
kind: 'glfw',
16+
platform: 'cocoa',
17+
window: 'loose',
18+
},
19+
{
20+
name: 'GlfwWindow Cocoa hidden dont-care framebuffer',
21+
kind: 'glfw',
22+
platform: 'cocoa',
23+
window: 'dont-care',
24+
},
25+
{
26+
name: 'GlfwWindow Cocoa hidden core profile',
27+
kind: 'glfw',
28+
platform: 'cocoa',
29+
window: 'core-profile',
30+
},
31+
{
32+
name: 'GlfwWindow Null hidden default',
33+
kind: 'glfw',
34+
platform: 'null',
35+
window: 'hidden',
36+
},
37+
{
38+
name: 'GlfwWindow Null OSMesa hidden',
39+
kind: 'glfw',
40+
platform: 'null',
41+
window: 'osmesa',
42+
},
43+
{
44+
name: 'GlfwWindow Null EGL hidden',
45+
kind: 'glfw',
46+
platform: 'null',
47+
window: 'egl',
48+
},
49+
{
50+
name: 'BrowserDocument Cocoa hidden no depth/stencil',
51+
kind: 'document',
52+
platform: 'cocoa',
53+
window: 'loose',
54+
},
55+
{
56+
name: 'BrowserDocument Null OSMesa hidden',
57+
kind: 'document',
58+
platform: 'null',
59+
window: 'osmesa',
60+
},
61+
{
62+
name: 'Core init Cocoa hidden no depth/stencil',
63+
kind: 'core',
64+
platform: 'cocoa',
65+
window: 'loose',
66+
},
67+
{
68+
name: 'Core init Null OSMesa hidden',
69+
kind: 'core',
70+
platform: 'null',
71+
window: 'osmesa',
72+
},
73+
];
574

675
const log = (...args) => {
776
console.log('[macos-gl-probe]', ...args);
877
};
978

10-
const describeGlfw = () => {
79+
const asMessage = (error) => {
80+
if (error instanceof Error) {
81+
return `${error.name}: ${error.message}`;
82+
}
83+
return String(error);
84+
};
85+
86+
const boolFromExitCode = (code) => code === 0;
87+
88+
const runParent = () => {
89+
log('parent node', process.version, process.platform, process.arch);
90+
91+
const results = probes.map((probe) => {
92+
log(`child start: ${probe.name}`);
93+
const child = spawnSync(process.execPath, [import.meta.filename, childArg], {
94+
env: {
95+
...process.env,
96+
NODE_3D_MACOS_GL_PROBE: JSON.stringify(probe),
97+
},
98+
stdio: 'inherit',
99+
});
100+
101+
const isOk = boolFromExitCode(child.status);
102+
log(`child ${isOk ? 'ok' : 'failed'}: ${probe.name}`);
103+
return isOk;
104+
});
105+
106+
if (!results.some(Boolean)) {
107+
throw new Error('No macOS GLFW/OpenGL probe could create a context.');
108+
}
109+
};
110+
111+
const readProbe = () => {
112+
const value = process.env['NODE_3D_MACOS_GL_PROBE'];
113+
if (!value) {
114+
throw new Error('Missing NODE_3D_MACOS_GL_PROBE.');
115+
}
116+
117+
return JSON.parse(value);
118+
};
119+
120+
const describeGlfw = (glfw) => {
11121
log('node', process.version, process.platform, process.arch);
12122
log('glfw version', glfw.getVersionString?.());
13123
log('glfw windowHint', typeof glfw.windowHint);
124+
log('glfw initHint', typeof glfw.initHint);
14125
log('glfw createWindow', typeof glfw.createWindow);
126+
log('glfw PLATFORM', glfw.PLATFORM);
127+
log('glfw PLATFORM_COCOA', glfw.PLATFORM_COCOA);
128+
log('glfw PLATFORM_NULL', glfw.PLATFORM_NULL);
129+
log('glfw CONTEXT_CREATION_API', glfw.CONTEXT_CREATION_API);
130+
log('glfw NATIVE_CONTEXT_API', glfw.NATIVE_CONTEXT_API);
131+
log('glfw EGL_CONTEXT_API', glfw.EGL_CONTEXT_API);
132+
log('glfw OSMESA_CONTEXT_API', glfw.OSMESA_CONTEXT_API);
15133
log('glfw STENCIL_BITS', glfw.STENCIL_BITS);
16134
log('glfw DEPTH_BITS', glfw.DEPTH_BITS);
17135
log('glfw SAMPLES', glfw.SAMPLES);
@@ -20,135 +138,139 @@ const describeGlfw = () => {
20138
log('glfw enumerable keys', Object.keys(glfw).length);
21139
};
22140

23-
const asMessage = (error) => {
24-
if (error instanceof Error) {
25-
return `${error.name}: ${error.message}`;
141+
const setInitHints = (glfw, probe) => {
142+
if (probe.platform === 'null') {
143+
glfw.initHint(glfw.PLATFORM, glfw.PLATFORM_NULL);
144+
} else if (probe.platform === 'cocoa') {
145+
glfw.initHint(glfw.PLATFORM, glfw.PLATFORM_COCOA);
26146
}
27-
return String(error);
28147
};
29148

30-
const destroy = (window) => {
31-
try {
32-
window?.destroy();
33-
} catch (error) {
34-
log('destroy failed', asMessage(error));
149+
const initGlfw = (glfw, probe) => {
150+
setInitHints(glfw, probe);
151+
152+
if (!glfw.init()) {
153+
throw new Error('Failed to initialize GLFW.');
35154
}
36-
};
37155

38-
const setHidden = (_window, currentGlfw) => {
39-
currentGlfw.windowHint(currentGlfw.VISIBLE, currentGlfw.FALSE);
156+
glfw.defaultWindowHints();
40157
};
41158

42-
const setLooseFramebuffer = (_window, currentGlfw) => {
159+
const setWindowHints = (currentGlfw, windowKind) => {
43160
currentGlfw.windowHint(currentGlfw.VISIBLE, currentGlfw.FALSE);
44-
currentGlfw.windowHint(currentGlfw.STENCIL_BITS, 0);
45-
currentGlfw.windowHint(currentGlfw.DEPTH_BITS, 0);
46-
currentGlfw.windowHint(currentGlfw.SAMPLES, 0);
47-
};
48161

49-
const setDontCareFramebuffer = (_window, currentGlfw) => {
50-
currentGlfw.windowHint(currentGlfw.VISIBLE, currentGlfw.FALSE);
51-
currentGlfw.windowHint(currentGlfw.RED_BITS, currentGlfw.DONT_CARE);
52-
currentGlfw.windowHint(currentGlfw.GREEN_BITS, currentGlfw.DONT_CARE);
53-
currentGlfw.windowHint(currentGlfw.BLUE_BITS, currentGlfw.DONT_CARE);
54-
currentGlfw.windowHint(currentGlfw.ALPHA_BITS, currentGlfw.DONT_CARE);
55-
currentGlfw.windowHint(currentGlfw.DEPTH_BITS, currentGlfw.DONT_CARE);
56-
currentGlfw.windowHint(currentGlfw.STENCIL_BITS, currentGlfw.DONT_CARE);
57-
currentGlfw.windowHint(currentGlfw.SAMPLES, 0);
58-
};
162+
if (windowKind === 'loose' || windowKind === 'osmesa' || windowKind === 'egl') {
163+
currentGlfw.windowHint(currentGlfw.STENCIL_BITS, 0);
164+
currentGlfw.windowHint(currentGlfw.DEPTH_BITS, 0);
165+
currentGlfw.windowHint(currentGlfw.SAMPLES, 0);
166+
}
59167

60-
const setCoreProfile = (_window, currentGlfw) => {
61-
currentGlfw.windowHint(currentGlfw.VISIBLE, currentGlfw.FALSE);
62-
currentGlfw.windowHint(currentGlfw.CONTEXT_VERSION_MAJOR, 3);
63-
currentGlfw.windowHint(currentGlfw.CONTEXT_VERSION_MINOR, 2);
64-
currentGlfw.windowHint(currentGlfw.OPENGL_FORWARD_COMPAT, currentGlfw.TRUE);
65-
currentGlfw.windowHint(currentGlfw.OPENGL_PROFILE, currentGlfw.OPENGL_CORE_PROFILE);
66-
currentGlfw.windowHint(currentGlfw.STENCIL_BITS, 0);
67-
currentGlfw.windowHint(currentGlfw.DEPTH_BITS, 0);
68-
currentGlfw.windowHint(currentGlfw.SAMPLES, 0);
168+
if (windowKind === 'dont-care') {
169+
currentGlfw.windowHint(currentGlfw.RED_BITS, currentGlfw.DONT_CARE);
170+
currentGlfw.windowHint(currentGlfw.GREEN_BITS, currentGlfw.DONT_CARE);
171+
currentGlfw.windowHint(currentGlfw.BLUE_BITS, currentGlfw.DONT_CARE);
172+
currentGlfw.windowHint(currentGlfw.ALPHA_BITS, currentGlfw.DONT_CARE);
173+
currentGlfw.windowHint(currentGlfw.DEPTH_BITS, currentGlfw.DONT_CARE);
174+
currentGlfw.windowHint(currentGlfw.STENCIL_BITS, currentGlfw.DONT_CARE);
175+
currentGlfw.windowHint(currentGlfw.SAMPLES, 0);
176+
}
177+
178+
if (windowKind === 'core-profile') {
179+
currentGlfw.windowHint(currentGlfw.CONTEXT_VERSION_MAJOR, 3);
180+
currentGlfw.windowHint(currentGlfw.CONTEXT_VERSION_MINOR, 2);
181+
currentGlfw.windowHint(currentGlfw.OPENGL_FORWARD_COMPAT, currentGlfw.TRUE);
182+
currentGlfw.windowHint(currentGlfw.OPENGL_PROFILE, currentGlfw.OPENGL_CORE_PROFILE);
183+
currentGlfw.windowHint(currentGlfw.STENCIL_BITS, 0);
184+
currentGlfw.windowHint(currentGlfw.DEPTH_BITS, 0);
185+
currentGlfw.windowHint(currentGlfw.SAMPLES, 0);
186+
}
187+
188+
if (windowKind === 'osmesa') {
189+
currentGlfw.windowHint(currentGlfw.CONTEXT_CREATION_API, currentGlfw.OSMESA_CONTEXT_API);
190+
}
191+
192+
if (windowKind === 'egl') {
193+
currentGlfw.windowHint(currentGlfw.CONTEXT_CREATION_API, currentGlfw.EGL_CONTEXT_API);
194+
}
69195
};
70196

71-
const probe = (name, create) => {
72-
log(`probe start: ${name}`);
73-
let window = null;
197+
const makeBeforeWindow = (windowKind) => (_window, currentGlfw) => {
198+
setWindowHints(currentGlfw, windowKind);
199+
};
74200

201+
const destroy = (window) => {
75202
try {
76-
window = create();
77-
log(`probe ok: ${name}`);
78-
log('window version', window.version);
79-
log('window size', JSON.stringify(window.size));
80-
log('framebuffer size', JSON.stringify(window.framebufferSize));
81-
log('platform context', window.platformContext);
82-
return true;
203+
window?.destroy();
83204
} catch (error) {
84-
log(`probe failed: ${name}`);
85-
log(asMessage(error));
86-
return false;
87-
} finally {
88-
destroy(window);
205+
log('destroy failed', asMessage(error));
89206
}
90207
};
91208

92-
describeGlfw();
93-
94-
const results = [
95-
probe(
96-
'GlfwWindow hidden default',
97-
() => new GlfwWindow({ width: 64, height: 64, title: 'glfw-hidden-default', onBeforeWindow: setHidden }),
98-
),
99-
probe(
100-
'GlfwWindow hidden no depth/stencil',
101-
() =>
102-
new GlfwWindow({
103-
width: 64,
104-
height: 64,
105-
title: 'glfw-hidden-loose',
106-
onBeforeWindow: setLooseFramebuffer,
107-
}),
108-
),
109-
probe(
110-
'GlfwWindow hidden dont-care framebuffer',
111-
() =>
112-
new GlfwWindow({
209+
const printWindow = (window) => {
210+
log('window version', window.version);
211+
log('window size', JSON.stringify(window.size));
212+
log('framebuffer size', JSON.stringify(window.framebufferSize));
213+
log('platform context', window.platformContext);
214+
};
215+
216+
const runChild = async () => {
217+
const probe = readProbe();
218+
219+
// @node-3d/glfw checks this flag during module evaluation. Setting it before
220+
// dynamic imports lets this probe own glfwInitHint/glfwInit ordering.
221+
globalThis['__isGlfwInited'] = true;
222+
223+
const { GlfwWindow, glfw } = await import('@node-3d/glfw');
224+
describeGlfw(glfw);
225+
log(`probe start: ${probe.name}`);
226+
initGlfw(glfw, probe);
227+
228+
let window = null;
229+
try {
230+
if (probe.kind === 'glfw') {
231+
window = new GlfwWindow({
113232
width: 64,
114233
height: 64,
115-
title: 'glfw-hidden-dont-care',
116-
onBeforeWindow: setDontCareFramebuffer,
117-
}),
118-
),
119-
probe(
120-
'GlfwWindow hidden core profile',
121-
() =>
122-
new GlfwWindow({
234+
major: probe.window === 'core-profile' ? 3 : 2,
235+
minor: probe.window === 'core-profile' ? 2 : 1,
236+
title: probe.name,
237+
onBeforeWindow: makeBeforeWindow(probe.window),
238+
});
239+
} else if (probe.kind === 'document') {
240+
const { BrowserDocument } = await import('../../ts/core/browser-document.ts');
241+
window = new BrowserDocument({
123242
width: 64,
124243
height: 64,
125-
major: 3,
126-
minor: 2,
127-
title: 'glfw-hidden-core-profile',
128-
onBeforeWindow: setCoreProfile,
129-
}),
130-
),
131-
probe(
132-
'BrowserDocument hidden no depth/stencil',
133-
() =>
134-
new BrowserDocument({
244+
title: probe.name,
245+
onBeforeWindow: makeBeforeWindow(probe.window),
246+
});
247+
} else if (probe.kind === 'core') {
248+
const { init } = await import('../../ts/index.ts');
249+
window = init({
135250
width: 64,
136251
height: 64,
137-
title: 'browser-document-hidden-loose',
138-
onBeforeWindow: setLooseFramebuffer,
139-
}),
140-
),
141-
probe('core init hidden no depth/stencil', () =>
142-
init({
143-
width: 64,
144-
height: 64,
145-
isVisible: false,
146-
title: 'core-init-hidden-loose',
147-
onBeforeWindow: setLooseFramebuffer,
148-
}).doc,
149-
),
150-
];
252+
isVisible: false,
253+
title: probe.name,
254+
onBeforeWindow: makeBeforeWindow(probe.window),
255+
}).doc;
256+
} else {
257+
throw new Error(`Unknown probe kind: ${probe.kind}`);
258+
}
259+
260+
log(`probe ok: ${probe.name}`);
261+
printWindow(window);
262+
} catch (error) {
263+
log(`probe failed: ${probe.name}`);
264+
log(asMessage(error));
265+
process.exitCode = 1;
266+
} finally {
267+
destroy(window);
268+
glfw.terminate();
269+
}
270+
};
151271

152-
if (!results.some(Boolean)) {
153-
throw new Error('No macOS GLFW/OpenGL probe could create a context.');
272+
if (process.argv.includes(childArg)) {
273+
await runChild();
274+
} else {
275+
runParent();
154276
}

0 commit comments

Comments
 (0)