Less strict webgpu reqs

This commit is contained in:
Andras Schmelczer 2026-05-24 15:24:20 +01:00
parent a73914d0ef
commit 39e19a3c64
2 changed files with 104 additions and 32 deletions

View file

@ -121,12 +121,15 @@ html > body {
position: absolute;
top: 0;
left: 0;
max-width: calc(100% - var(--normal-margin) * 2);
margin: var(--normal-margin);
z-index: 5;
pre {
font-size: 20px;
color: red;
white-space: pre-wrap;
overflow-wrap: anywhere;
}
}
}

View file

@ -15,7 +15,30 @@ const REQUESTED_LIMIT_NAMES = [
'maxComputeWorkgroupsPerDimension',
] as const satisfies ReadonlyArray<keyof GPUSupportedLimits>;
const getRequiredLimits = (
interface AdapterRequestAttempt {
label: string;
options?: GPURequestAdapterOptions;
}
const ADAPTER_REQUEST_ATTEMPTS: ReadonlyArray<AdapterRequestAttempt> = [
{
label: 'compatibility-default',
options: { featureLevel: 'compatibility' },
},
{
label: 'core-default',
},
{
label: 'compatibility-high-performance',
options: { featureLevel: 'compatibility', powerPreference: 'high-performance' },
},
{
label: 'core-high-performance',
options: { powerPreference: 'high-performance' },
},
] as const;
const getRelevantLimits = (
limits: GPUSupportedLimits
): Record<(typeof REQUESTED_LIMIT_NAMES)[number], number> =>
Object.fromEntries(REQUESTED_LIMIT_NAMES.map((name) => [name, limits[name]])) as Record<
@ -42,25 +65,64 @@ const getAdapterInfo = (adapter: GPUAdapter): Record<string, unknown> => {
}
};
const requestAdapter = async (
gpu: GPU,
options?: GPURequestAdapterOptions
): Promise<GPUAdapter | null> => {
try {
return await gpu.requestAdapter(options);
} catch (error) {
throw new RuntimeError(
ErrorCode.WEBGPU_ADAPTER_UNAVAILABLE,
'Could not request a WebGPU adapter.',
{
cause: error,
details: {
causeMessage: getErrorMessage(error),
powerPreference: options?.powerPreference ?? 'default',
},
}
);
const getRequiredFeatures = (adapter: GPUAdapter): Array<GPUFeatureName> => {
const requiredFeatures: Array<GPUFeatureName> = [];
if (adapter.features.has('core-features-and-limits')) {
requiredFeatures.push('core-features-and-limits');
}
return requiredFeatures;
};
type AdapterRequestOutcome = 'adapter' | 'unavailable' | 'error';
const describeAdapterRequest = (
attempt: AdapterRequestAttempt,
outcome: AdapterRequestOutcome,
causeMessage?: string
): Record<string, unknown> => ({
label: attempt.label,
featureLevel: attempt.options?.featureLevel ?? 'core',
powerPreference: attempt.options?.powerPreference ?? 'default',
outcome,
...(causeMessage === undefined ? {} : { causeMessage }),
});
const requestAdapter = async (
gpu: GPU
): Promise<{
adapter: GPUAdapter | null;
attempts: Array<Record<string, unknown>>;
}> => {
const attempts: Array<Record<string, unknown>> = [];
for (const attempt of ADAPTER_REQUEST_ATTEMPTS) {
try {
const adapter = await gpu.requestAdapter(attempt.options);
attempts.push(describeAdapterRequest(attempt, adapter ? 'adapter' : 'unavailable'));
if (adapter) {
return { adapter, attempts };
}
} catch (error) {
attempts.push(describeAdapterRequest(attempt, 'error', getErrorMessage(error)));
}
}
return { adapter: null, attempts };
};
const formatAdapterAttemptSummary = (
attempts: Array<Record<string, unknown>>
): string => {
if (attempts.length === 0) {
return 'No adapter requests were attempted.';
}
return `Adapter attempts: ${attempts
.map((attempt) => `${attempt.label}: ${attempt.outcome}`)
.join('; ')}`;
};
export const initializeGpu = async (): Promise<GPUDevice> => {
@ -81,35 +143,43 @@ export const initializeGpu = async (): Promise<GPUDevice> => {
});
}
const adapter =
(await requestAdapter(gpu, {
powerPreference: 'high-performance',
})) ?? (await requestAdapter(gpu));
const { adapter, attempts } = await requestAdapter(gpu);
ErrorHandler.addMetadata('webgpuAdapterRequest', {
attempts,
selectedAttempt: attempts[attempts.length - 1]?.label ?? null,
});
if (!adapter) {
throw new RuntimeError(
ErrorCode.WEBGPU_ADAPTER_UNAVAILABLE,
'WebGPU is available, but this browser could not provide a compatible GPU adapter.'
[
'WebGPU is available, but this browser could not provide a compatible GPU adapter.',
formatAdapterAttemptSummary(attempts),
].join('\n'),
{
details: {
attempts,
hasNavigatorGpu: true,
isSecureContext: window.isSecureContext,
platform: navigator.platform,
userAgent: navigator.userAgent,
},
}
);
}
const requiredLimits = getRequiredLimits(adapter.limits);
const requiredFeatures: Array<GPUFeatureName> = [];
if (adapter.features.has('timestamp-query')) {
requiredFeatures.push('timestamp-query');
}
const requiredFeatures = getRequiredFeatures(adapter);
ErrorHandler.addMetadata('webgpuAdapter', {
features: Array.from(adapter.features).sort(),
info: getAdapterInfo(adapter),
requiredFeatures,
requiredLimits,
relevantLimits: getRelevantLimits(adapter.limits),
});
let gpuDevice: GPUDevice;
try {
gpuDevice = await adapter.requestDevice({
requiredFeatures,
requiredLimits,
});
} catch (error) {
throw new RuntimeError(
@ -120,7 +190,6 @@ export const initializeGpu = async (): Promise<GPUDevice> => {
details: {
causeMessage: getErrorMessage(error),
requiredFeatures,
requiredLimits,
},
}
);