// src/engine/adapter.ts
import type { PerfBudget, PerfTier } from './types';
export function isWebGPUAvailable(nav: Navigator = navigator): boolean {
return typeof (nav as Navigator & { gpu?: GPU }).gpu !== 'undefined';
}
export function isMobileUserAgent(ua: string = navigator.userAgent): boolean {
return /iPhone|iPad|Android|Mobile/i.test(ua);
}
export function detectTier(adapter: GPUAdapter, isMobile: boolean): PerfTier {
// @webgpu/types ^0.1.50: isFallbackAdapter lives on adapter.info
if (adapter.info?.isFallbackAdapter) return 'minimum';
if (isMobile) return 'mobile';
// mid-tier heuristic: integrated GPUs often have architecture strings containing "intel" or "vega"
const arch = (adapter.info?.architecture ?? '').toLowerCase();
if (arch.includes('intel') || arch.includes('vega') || arch.includes('llvmpipe')) return 'mid';
return 'desktop';
}
export function budgetForTier(tier: PerfTier): PerfBudget {
switch (tier) {
case 'desktop':
return { agentCount: 500_000, fieldScale: 1.0 };
case 'mid':
return { agentCount: 100_000, fieldScale: 0.75 };
case 'mobile':
return { agentCount: 50_000, fieldScale: 0.6 };
case 'minimum':
return { agentCount: 10_000, fieldScale: 0.4 };
}
}
export interface AdapterResult {
device: GPUDevice;
adapter: GPUAdapter;
tier: PerfTier;
budget: PerfBudget;
}
export async function requestAdapter(): Promise<AdapterResult | null> {
if (!isWebGPUAvailable()) return null;
const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' });
if (!adapter) return null;
const tier = detectTier(adapter, isMobileUserAgent());
const budget = budgetForTier(tier);
const device = await adapter.requestDevice();
return { device, adapter, tier, budget };
}