Zum Inhalt

src/engine/adapter.ts

// 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 };
}