concept-collection / dulcimer
dulcimer / src / device.ts
45 lines · 2.0 KBBlameHistoryRaw
1/**
2 * The GPU device, requested the same way everywhere (app, tests, scripts).
3 *
4 * One limit matters here. A fused kernel binds one storage buffer per distinct
5 * field its line reads, plus its output and the parameter block, and the air
6 * update reads a lot of fields at once — the two pressure histories, the
7 * medium, the wall mask, the Laplacian, the coupling source. WebGPU only
8 * guarantees 8 storage buffers per compute stage, so we ask for whatever the
9 * adapter will give up to 16. When that is not enough the planner splits the
10 * kernel instead of failing (see `fitToBudget` in plan.ts), so this is a
11 * performance request rather than a requirement.
12 */
13export const MAX_STORAGE_BUFFERS = 16;
15/** The adapter the device came from, kept so its limits and info stay
16 * available for as long as the device is in use. */
17let heldAdapter: GPUAdapter | null = null;
19export async function requestAcousticDevice(): Promise<GPUDevice> {
20 if (!navigator.gpu) {
21 throw new Error(
22 'this browser has no WebGPU. Chrome and Edge 113+, Safari 26+, and ' +
23 'Firefox 141+ on Windows have it; on Linux Firefox and Chrome may need ' +
24 'it enabled explicitly.',
25 );
26 }
27 const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' });
28 if (!adapter) throw new Error('WebGPU found no adapter on this machine.');
29 heldAdapter = adapter;
30 const want = Math.min(
31 MAX_STORAGE_BUFFERS,
32 adapter.limits.maxStorageBuffersPerShaderStage ?? 8,
33 );
34 return adapter.requestDevice({
35 requiredLimits: { maxStorageBuffersPerShaderStage: want },
36 });
39/** The adapter the current device came from, if there is one. */
40export const currentAdapter = (): GPUAdapter | null => heldAdapter;
42/** Grid fields one kernel may read, given the device's binding limit: every
43 * binding but the output and the parameter block. */
44export const kernelOperandBudget = (device: GPUDevice): number =>
45 Math.max(2, (device.limits.maxStorageBuffersPerShaderStage ?? 8) - 2);