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
675const 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