code wiki / (root) / nx_glprobe_lib.nx

nx_glprobe_lib.nx source

↩ module page · 122 lines · 7255 B

1// nx_glprobe_lib.nx -- THE GL-CONTEXT PROBE RULER: one emitted probe, one native classifier, N page emitters. 2// 3// WHY (2026-09-02, operator: "it says no webgl2"): /world/rigview refused with the bare string "NXA REFUSED: 4// no webgl2" and /world/beach fell to its sovereign tier with a bare "no-webgl2-context" -- neither said WHOSE 5// refusal it was. Measured on the operator's laptop the same hour: every process started AFTER a display-driver 6// swap (Intel UMD 32.0.101.6629 -> .8991) created WebGL2 (a fresh headless Edge AND a fresh headless Waterfox, 7// ANGLE on the new UMD; the served rigview rendered 14164 verts / 104 joints in that Edge), while the Waterfox 8// that had stayed open since before the swap still MAPPED THE DELETED 6629 UMD in its GPU process (the module 9// that faulted Windows Terminal five times that week) and refused. The pages were innocent and the refusal 10// text could not say so. A refusal that does not name its cause sends the reader at the wrong subject 11// (nx_refusal_shape_lib asks exactly this: does the refusal name its subject and its remedy?). 12// 13// ONE TABLE, TWO SURFACES. The browser must answer the question itself (no page can call the estate for a 14// remedy while its GPU is refusing), so the emitted JS carries the state and remedy table -- and the SAME 15// table is the native classifier below, so an MCP caller (nx_glprobe classify <gl2> <gl1> <gpu>) and the 16// served page cannot disagree. nx_glprobe_gate proves the two surfaces carry identical strings and that no 17// page emitter still ships a bare refusal, with a runtime-assembled bare refusal as the firing neg-control. 18// 19// WHAT THIS IS NOT: not a fallback renderer. It names a refusal; the sovereign tier that would make the 20// refusal irrelevant is the gameengine contract (wc_mob_tex, wc_render_tiles), not this lib. 21// license_tier: ORIGINAL No hw writes (Rule 26). 22import "nx_syscalls.nx" 23 24// the four named states. Raw facts: gl2 (0/1 granted on the target canvas), gl1 (0/1 granted on a FRESH 25// canvas), gpu (0/1 navigator.gpu advertised). NOT probed on purpose: a 2d context on the TARGET canvas -- 26// a canvas holds ONE context type forever, and a probe that claimed it would poison the very fallback it 27// reports on (the beach page's own 2026-08-23 finding). 28const GLP_OK: i64 = 0 29const GLP_GL1_ONLY: i64 = 1 30const GLP_GPU_NO_GL: i64 = 2 31const GLP_NO_CONTEXT: i64 = 3 32const GLP_STATES: i64 = 4 33 34const GLP_S_OK: *u8 = "webgl2-granted" 35const GLP_S_GL1: *u8 = "webgl2-refused-webgl1-granted" 36const GLP_S_GPU: *u8 = "no-gl-context-webgpu-adapter-advertised" 37const GLP_S_NONE: *u8 = "no-gpu-context-of-any-kind" 38const GLP_R_OK: *u8 = "none" 39const GLP_R_GL1: *u8 = "this browser build stops at WebGL1: open the page in a WebGL2 browser" 40const GLP_R_NOGL: *u8 = "the browser is not creating GPU contexts. Measured cause 2026-09-02: a browser left open across a display-driver update keeps the deleted driver mapped while every fresh process gets the new one -- restart the browser (finish any pending reboot), then reload" 41 42func glp_classify(gl2: i64, gl1: i64, gpu: i64) -> i64 { 43 if gl2 == 1 { return GLP_OK } 44 if gl1 == 1 { return GLP_GL1_ONLY } 45 if gpu == 1 { return GLP_GPU_NO_GL } 46 return GLP_NO_CONTEXT 47} 48func glp_state_name(st: i64) -> *u8 { 49 if st == GLP_OK { return GLP_S_OK } 50 if st == GLP_GL1_ONLY { return GLP_S_GL1 } 51 if st == GLP_GPU_NO_GL { return GLP_S_GPU } 52 return GLP_S_NONE 53} 54func glp_remedy(st: i64) -> *u8 { 55 if st == GLP_OK { return GLP_R_OK } 56 if st == GLP_GL1_ONLY { return GLP_R_GL1 } 57 return GLP_R_NOGL 58} 59 60// THE JS TWIN. Consumers append this ONCE before their first getContext and call nxGlProbe(canvas, opts): 61// it returns the WebGL2 context or null, and ALWAYS leaves window.__nx_gl = {gl2,gl1,gpu,state,remedy} so a 62// refusal message, a tier badge or an instrument can read the diagnosis instead of a bare word. The state and 63// remedy strings below MUST equal the consts above; nx_glprobe_gate holds that equality as teeth. 64func glp_js() -> *u8 { 65 return "function nxGlProbe(cv,opts){\n// nx_glprobe_lib: the GL-context probe ruler -- one table, emitted here and held natively by nx_glprobe classify.\nvar g=null;try{g=cv.getContext(\"webgl2\",opts);}catch(e0){g=null;}\nif(g){window.__nx_gl={gl2:1,gl1:1,gpu:(navigator.gpu?1:0),state:\"webgl2-granted\",remedy:\"none\"};return g;}\nvar g1=0,gpu=(navigator.gpu?1:0);\ntry{g1=document.createElement(\"canvas\").getContext(\"webgl\")?1:0;}catch(e1){g1=0;}\nvar st=g1?\"webgl2-refused-webgl1-granted\":(gpu?\"no-gl-context-webgpu-adapter-advertised\":\"no-gpu-context-of-any-kind\");\nvar rem=g1?\"this browser build stops at WebGL1: open the page in a WebGL2 browser\":\"the browser is not creating GPU contexts. Measured cause 2026-09-02: a browser left open across a display-driver update keeps the deleted driver mapped while every fresh process gets the new one -- restart the browser (finish any pending reboot), then reload\";\nwindow.__nx_gl={gl2:0,gl1:g1,gpu:gpu,state:st,remedy:rem};\nreturn null;}\n" as *u8 66} 67 68// ---- scanning helpers (shared by the gate and the CLI so there is ONE detector) ---- 69func glp_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 70func glp_find(buf: *u8, off: i64, n: i64, needle: *u8) -> i64 { 71 let m: i64 = glp_slen(needle) 72 if m == 0 { return 0 - 1 } 73 var i: i64 = off 74 while i + m <= n { 75 var k: i64 = 0 76 var hit: i64 = 1 77 while k < m { 78 if buf[i + k] != needle[k] { hit = 0; k = m } else { k = k + 1 } 79 } 80 if hit == 1 { return i } 81 i = i + 1 82 } 83 return 0 - 1 84} 85func glp_count(buf: *u8, n: i64, needle: *u8) -> i64 { 86 var c: i64 = 0 87 var at: i64 = 0 88 var run: i64 = 1 89 while run == 1 { 90 let h: i64 = glp_find(buf, at, n, needle) 91 if h < 0 { run = 0 } else { c = c + 1; at = h + 1 } 92 } 93 return c 94} 95// A BARE REFUSAL is the string no webgl2 followed IMMEDIATELY by a quote -- the shape `throw"no webgl2"` in 96// emitted HTML, `throw\"no webgl2\"` in an emitter's .nx source. The diagnosed form the probe emits reads 97// `no webgl2 (<state>)`, so a paren after the 2 is NOT bare. The needle is ASSEMBLED at runtime from two 98// literals so this lib's own source never contains it contiguously (a detector that scans source finds its 99// own fixture -- prose is source bytes too). 100func glp_bare_needle(esc: i64) -> *u8 { 101 let nd: *u8 = sys_mmap(32) 102 var p: i64 = 0 103 let a: *u8 = "no webgl" as *u8 104 var k: i64 = 0 105 while a[k] != (0 as u8) { nd[p] = a[k]; p = p + 1; k = k + 1 } 106 let b: *u8 = "2" as *u8 107 nd[p] = b[0]; p = p + 1 108 if esc == 1 { let bs: *u8 = "\\" as *u8; nd[p] = bs[0]; p = p + 1 } 109 let q: *u8 = "\"" as *u8 110 nd[p] = q[0]; p = p + 1 111 nd[p] = 0 as u8 112 return nd 113} 114// count bare refusals in a buffer: both the HTML shape and the .nx-source shape 115func glp_bare_count(buf: *u8, n: i64) -> i64 { 116 return glp_count(buf, n, glp_bare_needle(0)) + glp_count(buf, n, glp_bare_needle(1)) 117} 118// does an emitter source COMPOSE the probe? (the call site the consumer must carry) 119func glp_composes(buf: *u8, n: i64) -> i64 { 120 if glp_find(buf, 0, n, "nxGlProbe(" as *u8) < 0 { return 0 } 121 return 1 122}