Spaces:
Running
Running
| // Best-effort device/GPU/RAM probe for the Settings "Your device" panel — handy for | |
| // debugging and benchmarking which quality preset a machine can handle. Everything is | |
| // guarded: unsupported APIs just read "—" rather than throwing. | |
| import { storageEstimate } from '/web/storage.js' | |
| import { fmtBytes } from '/web/modelCatalog.js' | |
| function webglRenderer() { | |
| try { | |
| const c = document.createElement('canvas') | |
| const gl = c.getContext('webgl') || c.getContext('experimental-webgl') | |
| if (!gl) return '—' | |
| const dbg = gl.getExtension('WEBGL_debug_renderer_info') | |
| return dbg ? String(gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL)) : (gl.getParameter(gl.RENDERER) || '—') | |
| } catch { return '—' } | |
| } | |
| async function webgpuInfo() { | |
| try { | |
| if (!navigator.gpu) return 'unavailable' | |
| const ad = await navigator.gpu.requestAdapter() | |
| if (!ad) return 'no adapter' | |
| const info = ad.info || (ad.requestAdapterInfo ? await ad.requestAdapterInfo() : null) | |
| const desc = info ? [info.vendor, info.architecture, info.description].filter(Boolean).join(' ').trim() : '' | |
| return desc || 'available' | |
| } catch { return 'error' } | |
| } | |
| export function isMobile() { | |
| try { if (navigator.userAgentData && typeof navigator.userAgentData.mobile === 'boolean') return navigator.userAgentData.mobile } catch { /* ignore */ } | |
| return /Mobi|Android|iPhone|iPad|iPod|IEMobile|Opera Mini/i.test(navigator.userAgent || '') | |
| } | |
| // A combined, lowercased GPU descriptor (WebGL renderer + WebGPU adapter info) for | |
| // heuristic matching. WebGL's ANGLE string usually carries the real chip name, e.g. | |
| // "…nvidia geforce rtx 3090…" on desktop or "adreno (tm) 730" on Android. | |
| async function gpuDescriptor() { | |
| let s = webglRenderer() | |
| try { | |
| if (navigator.gpu) { | |
| const ad = await navigator.gpu.requestAdapter() | |
| const info = ad && (ad.info || (ad.requestAdapterInfo ? await ad.requestAdapterInfo() : null)) | |
| if (info) s += ' ' + [info.vendor, info.architecture, info.description].filter(Boolean).join(' ') | |
| } | |
| } catch { /* ignore */ } | |
| return s.toLowerCase() | |
| } | |
| // Recommend a quality preset for this device: | |
| // • Mobile → Low, but Medium if a Qualcomm Adreno 700-series (or higher) is detected. | |
| // • Desktop → Medium, but High if an NVIDIA RTX 3090 (or higher) is detected. | |
| // "or higher" for RTX = a newer generation, or the same 30-gen at ≥90 (so 3090/3090 Ti | |
| // and all 40xx/50xx qualify; 3080 and below don't). | |
| export async function recommendPreset() { | |
| const gpu = await gpuDescriptor() | |
| if (isMobile()) { | |
| const a = gpu.match(/adreno[^\d]*(\d{3,4})/) | |
| if (a && parseInt(a[1], 10) >= 700) return { id: 'medium', reason: `mobile · Adreno ${a[1]} (≥700)` } | |
| return { id: 'low', reason: 'mobile device' } | |
| } | |
| const r = gpu.match(/rtx\s*0*(\d{3,4})/) | |
| if (r) { | |
| const n = parseInt(r[1], 10), gen = Math.floor(n / 100), model = n % 100 | |
| if (gen > 30 || (gen === 30 && model >= 90)) return { id: 'high', reason: `desktop · RTX ${r[1]} (≥3090)` } | |
| } | |
| return { id: 'medium', reason: 'desktop' } | |
| } | |
| // Returns an ordered array of [label, value] rows. | |
| export async function gatherDeviceInfo() { | |
| const rows = [] | |
| const add = (k, v) => rows.push([k, v == null || v === '' ? '—' : String(v)]) | |
| add('CPU threads', navigator.hardwareConcurrency) | |
| add('Device memory', navigator.deviceMemory ? `${navigator.deviceMemory} GB (approx)` : '— (not reported)') | |
| add('WebGPU', await webgpuInfo()) | |
| add('GPU (WebGL)', webglRenderer()) | |
| try { | |
| const { usage, quota } = await storageEstimate() | |
| add('Storage cache', quota ? `${fmtBytes(usage || 0)} / ${fmtBytes(quota)}` : '—') | |
| } catch { add('Storage cache', '—') } | |
| try { add('Screen', `${screen.width}×${screen.height} @ ${window.devicePixelRatio || 1}×`) } catch { add('Screen', '—') } | |
| add('Platform', (navigator.userAgentData && navigator.userAgentData.platform) || navigator.platform) | |
| add('Language', navigator.language) | |
| add('User agent', navigator.userAgent) | |
| return rows | |
| } | |