diff --git a/ui/pages/gpu-diagnostics.html b/ui/pages/gpu-diagnostics.html index 07a3bc4..541bde5 100644 --- a/ui/pages/gpu-diagnostics.html +++ b/ui/pages/gpu-diagnostics.html @@ -3,462 +3,963 @@ - GPU Diagnostics - Nebula Browser + GPU Diagnostics -
-
-
-

GPU Diagnostics

-

- Quick CEF-compatible checks for WebGL, WebGL2, Canvas 2D, GPU renderer strings, - and browser runtime details. Use this page to verify graphics support after GPU - flag or runtime changes. -

+ +
+
+

GPU Diagnostics

+ / + not run +
+
+ + +
+
+ +
+ + +
+ + +
+
+ Overall + WAITING
- -
+
+
+
+ -
-
-
Waiting
-

Summary

-
-
+ +
+
+ WebGL 1 + WAITING +
+
+ +
+
+
-
-
Waiting
-

WebGL

- -

-
+ +
+
+ WebGL 2 + WAITING +
+
+ +
+
+
-
-
Waiting
-

WebGL2

- -

-
+ +
+
+ Canvas 2D + WAITING +
+
+ +
+
+
-
-
Waiting
-

Canvas 2D

- -

-
-
+ +
+
+ WebGPU + WAITING +
+
+
+
+
-
-

Detailed Information

-
Waiting for diagnostics...
-
-
+ +
+
+ System +
+
+
+
+
- + /* webgl 2 */ + log('[WebGL2] Initialising context…', 'dim'); + try { + const t1 = performance.now(); + report.webgl2 = drawWebGL('webgl2-canvas', 2); + const elapsed = ((performance.now() - t1) / 1).toFixed(1); + if (report.webgl2.ok) { + log(`[WebGL2] OK — ${report.webgl2.renderer.unmaskedRenderer || report.webgl2.renderer.renderer} (${elapsed} ms)`, 'ok'); + setBadge('badge-webgl2', 'ok', 'OK'); + kvFill('kv-webgl2', [ + ['Status', 'Available', 'ok'], + ['Renderer', report.webgl2.renderer.unmaskedRenderer || report.webgl2.renderer.renderer], + ['Vendor', report.webgl2.renderer.unmaskedVendor || report.webgl2.renderer.vendor], + ['Version', report.webgl2.renderer.version], + ['GLSL', report.webgl2.renderer.glslVersion], + ['Extensions', `${report.webgl2.extensions.length} supported`], + ['Init time', `${elapsed} ms`], + ]); + } else { + log(`[WebGL2] UNAVAIL — ${report.webgl2.error}`, 'warn'); + setBadge('badge-webgl2', 'warn', 'N/A'); + kvFill('kv-webgl2', [['Status', 'Unavailable', 'warn'], ['Reason', report.webgl2.error]]); + } + } catch (e) { + report.webgl2 = { ok: false, error: e.message }; + log(`[WebGL2] ERROR — ${e.message}`, 'err'); + setBadge('badge-webgl2', 'err', 'ERROR'); + kvFill('kv-webgl2', [['Status', 'Error', 'err'], ['Error', e.message]]); + } + + /* canvas 2d */ + log('[Canvas2D] Running draw stress test (900 ops)…', 'dim'); + try { + const t1 = performance.now(); + report.canvas2d = testCanvas2D(); + const elapsed = ((performance.now() - t1) / 1).toFixed(1); + if (report.canvas2d.ok) { + log(`[Canvas2D] OK — ${report.canvas2d.drawTimeMs} ms draw time`, 'ok'); + setBadge('badge-canvas2d', 'ok', 'OK'); + kvFill('kv-canvas2d', [ + ['Status', 'Working', 'ok'], + ['Draw time', `${report.canvas2d.drawTimeMs} ms`], + ['Ops', `${report.canvas2d.ops} arcs`], + ['Compositing', report.canvas2d.compositing], + ['Smoothing', String(report.canvas2d.smoothingEnabled)], + ]); + } else { + log(`[Canvas2D] FAIL — ${report.canvas2d.error}`, 'err'); + setBadge('badge-canvas2d', 'err', 'FAIL'); + kvFill('kv-canvas2d', [['Status', 'Unavailable', 'err'], ['Error', report.canvas2d.error]]); + } + } catch (e) { + report.canvas2d = { ok: false, error: e.message }; + log(`[Canvas2D] ERROR — ${e.message}`, 'err'); + setBadge('badge-canvas2d', 'err', 'ERROR'); + kvFill('kv-canvas2d', [['Status', 'Error', 'err'], ['Error', e.message]]); + } + + /* webgpu */ + log('[WebGPU] Probing adapter…', 'dim'); + report.webgpu = await probeWebGPU(); + if (report.webgpu.ok) { + log(`[WebGPU] Adapter found — ${report.webgpu.vendor} / ${report.webgpu.architecture}`, 'ok'); + setBadge('badge-webgpu', 'ok', 'OK'); + kvFill('kv-webgpu', [ + ['Status', 'Available', 'ok'], + ['Vendor', report.webgpu.vendor], + ['Architecture', report.webgpu.architecture], + ['Device', report.webgpu.device], + ['Description', report.webgpu.description], + ]); + } else { + log(`[WebGPU] UNAVAIL — ${report.webgpu.reason}`, 'warn'); + setBadge('badge-webgpu', 'warn', 'N/A'); + kvFill('kv-webgpu', [['Status', 'Unavailable', 'warn'], ['Reason', report.webgpu.reason]]); + } + + /* capability table — prefer WebGL2, fall back to WebGL1 */ + const glForCaps = report.webgl2.ok ? report.webgl2 : report.webgl.ok ? report.webgl : null; + const extensions = glForCaps ? glForCaps.extensions : []; + if (glForCaps) { + const capRows = [...glForCaps.caps]; + + // Shader precision section + capRows.push({ section: 'Shader Precision' }); + glForCaps.precisions.forEach(([k, v]) => capRows.push({ name: k, val: v })); + + // Context attributes + capRows.push({ section: 'Context Attributes' }); + glForCaps.contextAttrs.forEach(([k, v]) => capRows.push({ name: k, val: v })); + + renderCapTable(capRows, extensions); + } + + /* overall */ + const healthy = report.webgl.ok && report.canvas2d.ok; + const gl2ok = report.webgl2.ok; + const overallLevel = healthy ? 'ok' : 'err'; + const overallText = healthy ? 'PASS' : 'DEGRADED'; + setBadge('badge-overall', overallLevel, overallText); + + kvFill('kv-overall', [ + ['Status', healthy ? 'All critical tests passed' : 'One or more critical tests failed', healthy ? 'ok' : 'err'], + ['WebGL 1', report.webgl.ok ? 'OK' : 'FAIL', report.webgl.ok ? 'ok' : 'err'], + ['WebGL 2', gl2ok ? 'OK' : 'N/A', gl2ok ? 'ok' : 'warn'], + ['Canvas 2D', report.canvas2d.ok ? 'OK' : 'FAIL', report.canvas2d.ok ? 'ok' : 'err'], + ['WebGPU', report.webgpu.ok ? 'OK' : 'N/A', report.webgpu.ok ? 'ok' : 'warn'], + ]); + + const totalMs = (performance.now() - t0.v).toFixed(1); + log(`Run complete in ${totalMs} ms`, healthy ? 'ok' : 'warn'); + + reportData = report; + } + + /* ── copy report ── */ + $('btn-copy').addEventListener('click', () => { + if (!reportData) return; + navigator.clipboard.writeText(JSON.stringify(reportData, null, 2)).then(() => { + const btn = $('btn-copy'); + btn.textContent = 'Copied!'; + btn.classList.add('flash'); + setTimeout(() => { btn.textContent = 'Copy Report'; btn.classList.remove('flash'); }, 1200); + }); + }); + + $('btn-run').addEventListener('click', runDiagnostics); + window.addEventListener('DOMContentLoaded', runDiagnostics); +