Checkpoint web parity through Chromium input tasks
This commit is contained in:
69
web/protocol/device-budget.ts
Normal file
69
web/protocol/device-budget.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
export const DEVICE_BUDGET_SCHEMA_VERSION = 1 as const;
|
||||
|
||||
export type DeviceBudgetTier = "CONSERVATIVE" | "BALANCED" | "HIGH";
|
||||
|
||||
export interface DeviceBudgetObservation {
|
||||
schemaVersion: typeof DEVICE_BUDGET_SCHEMA_VERSION;
|
||||
identitySha256: string;
|
||||
webgl2: { status: "PASS" | "BLOCKED"; renderer?: string; vendor?: string };
|
||||
webgpu: { status: "PASS" | "BLOCKED"; device?: string; description?: string; isFallbackAdapter?: boolean };
|
||||
hardwareConcurrency: number | null;
|
||||
deviceMemory: number | null;
|
||||
}
|
||||
|
||||
export interface DeviceBudgetLimits {
|
||||
maxTextureGPUBytes: number;
|
||||
maxTexturePayloadBytes: number;
|
||||
maxTextureDimension: number;
|
||||
maxLights: number;
|
||||
maxShadowMaps: number;
|
||||
}
|
||||
|
||||
export interface DeviceBudgetSelection {
|
||||
schemaVersion: typeof DEVICE_BUDGET_SCHEMA_VERSION;
|
||||
identitySha256: string;
|
||||
tier: DeviceBudgetTier;
|
||||
reason: "MISSING_GPU" | "UNTRUSTED_ADAPTER" | "WEBGPU_UNAVAILABLE" | "BALANCED_CAPABILITY" | "HIGH_CAPABILITY";
|
||||
limits: DeviceBudgetLimits;
|
||||
}
|
||||
|
||||
const mib = 1024 * 1024;
|
||||
const LIMITS: Readonly<Record<DeviceBudgetTier, DeviceBudgetLimits>> = Object.freeze({
|
||||
CONSERVATIVE: Object.freeze({ maxTextureGPUBytes: 256 * mib, maxTexturePayloadBytes: 256 * mib, maxTextureDimension: 8192, maxLights: 8, maxShadowMaps: 2 }),
|
||||
BALANCED: Object.freeze({ maxTextureGPUBytes: 512 * mib, maxTexturePayloadBytes: 512 * mib, maxTextureDimension: 16384, maxLights: 16, maxShadowMaps: 4 }),
|
||||
HIGH: Object.freeze({ maxTextureGPUBytes: 1024 * mib, maxTexturePayloadBytes: 512 * mib, maxTextureDimension: 16384, maxLights: 64, maxShadowMaps: 8 }),
|
||||
});
|
||||
|
||||
function validHash(value: unknown): value is string {
|
||||
return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
|
||||
}
|
||||
|
||||
function validPositive(value: number | null): value is number {
|
||||
return value !== null && Number.isSafeInteger(value) && value > 0;
|
||||
}
|
||||
|
||||
export function selectDeviceBudget(observation: DeviceBudgetObservation): DeviceBudgetSelection {
|
||||
if (!observation || observation.schemaVersion !== DEVICE_BUDGET_SCHEMA_VERSION || !validHash(observation.identitySha256)) {
|
||||
throw new Error("DEVICE_BUDGET_IDENTITY_INVALID");
|
||||
}
|
||||
const conservative = (reason: DeviceBudgetSelection["reason"]): DeviceBudgetSelection => ({
|
||||
schemaVersion: DEVICE_BUDGET_SCHEMA_VERSION,
|
||||
identitySha256: observation.identitySha256,
|
||||
tier: "CONSERVATIVE",
|
||||
reason,
|
||||
limits: LIMITS.CONSERVATIVE,
|
||||
});
|
||||
if (observation.webgl2.status !== "PASS") return conservative("MISSING_GPU");
|
||||
const renderer = `${observation.webgl2.renderer ?? ""} ${observation.webgpu.description ?? ""}`.toLowerCase();
|
||||
if (!renderer || renderer.includes("swiftshader") || renderer.includes("unknown") || observation.webgpu.isFallbackAdapter) return conservative("UNTRUSTED_ADAPTER");
|
||||
if (observation.webgpu.status !== "PASS") return conservative("WEBGPU_UNAVAILABLE");
|
||||
if (!validPositive(observation.hardwareConcurrency) || !validPositive(observation.deviceMemory)) return conservative("UNTRUSTED_ADAPTER");
|
||||
const tier: DeviceBudgetTier = observation.hardwareConcurrency >= 8 && observation.deviceMemory >= 8 ? "HIGH" : "BALANCED";
|
||||
return { schemaVersion: DEVICE_BUDGET_SCHEMA_VERSION, identitySha256: observation.identitySha256, tier, reason: tier === "HIGH" ? "HIGH_CAPABILITY" : "BALANCED_CAPABILITY", limits: LIMITS[tier] };
|
||||
}
|
||||
|
||||
export function deviceBudgetLimits(tier: DeviceBudgetTier): DeviceBudgetLimits {
|
||||
const limits = LIMITS[tier];
|
||||
if (!limits) throw new Error("DEVICE_BUDGET_TIER_INVALID");
|
||||
return limits;
|
||||
}
|
||||
Reference in New Issue
Block a user