File size: 3,609 Bytes
c971a45 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | // `immediate_address_space` is a WGSL language extension rather than a
// requestDevice feature. Browsers exposing it also expose
// GPUComputePassEncoder.setImmediates(); record() still checks the method at
// use time so an inconsistent implementation fails loudly.
export function supportsImmediates(gpu = globalThis.navigator?.gpu) {
return !!gpu?.wgslLanguageFeatures?.has?.('immediate_address_space');
}
const immediateDevices = new WeakSet();
export function deviceSupportsImmediates(device) {
return !!device && immediateDevices.has(device);
}
export async function initDevice({ requireF16 = false } = {}) {
if (!navigator.gpu) throw new Error('WebGPU unavailable');
const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' });
if (!adapter) throw new Error('no adapter');
const want = ['shader-f16', 'timestamp-query', 'subgroups'];
const features = want.filter((f) => adapter.features.has(f));
if (requireF16 && !features.includes('shader-f16')) throw new Error('shader-f16 unsupported');
const device = await adapter.requestDevice({
requiredFeatures: features,
requiredLimits: {
// Ask for generous limits (clamped to what the adapter offers) so large
// decode batches fit in single bindings; granted values are surfaced in
// ctx.limits for the maxBatchForLimits guard.
maxStorageBufferBindingSize: Math.min(536870912, adapter.limits.maxStorageBufferBindingSize),
maxBufferSize: Math.min(1073741824, adapter.limits.maxBufferSize),
},
});
const hasImmediates = supportsImmediates(navigator.gpu);
if (hasImmediates) immediateDevices.add(device);
return {
device,
adapterInfo: { vendor: adapter.info?.vendor ?? '', architecture: adapter.info?.architecture ?? '', device: adapter.info?.device ?? '', description: adapter.info?.description ?? '' },
hasF16: features.includes('shader-f16'),
hasTimestamps: features.includes('timestamp-query'),
hasSubgroups: features.includes('subgroups'),
hasImmediates,
// Kernel SG variants assume a TK-slice never straddles a subgroup:
// routing must require TK ≤ subgroupMinSize (power-of-two sizes ⇒ that
// also gives sgSize % TK == 0). 0 when the adapter doesn't report it.
subgroupMinSize: adapter.info?.subgroupMinSize ?? 0,
subgroupMaxSize: adapter.info?.subgroupMaxSize ?? 0,
limits: {
maxStorageBufferBindingSize: device.limits.maxStorageBufferBindingSize,
maxBufferSize: device.limits.maxBufferSize,
// Read-only extras for the benchmark payload: workgroup-storage size is
// exactly the ceiling suspected in the Adreno mega+sg miscompile (#3).
maxComputeWorkgroupStorageSize: device.limits.maxComputeWorkgroupStorageSize,
maxComputeInvocationsPerWorkgroup: device.limits.maxComputeInvocationsPerWorkgroup,
},
// Raw adapter capability (NOT the granted device limits above): limits
// we never request come back as spec defaults on the device, which is
// useless for driver-quirk analysis. Enumerate the prototype getters —
// the key set follows the browser's spec version automatically.
adapterLimits: (() => {
const out = {};
try {
for (const k of Object.getOwnPropertyNames(Object.getPrototypeOf(adapter.limits))) {
const v = adapter.limits[k];
if (typeof v === 'number' && Number.isFinite(v)) out[k] = v;
}
} catch { /* keep whatever was collected */ }
return out;
})(),
features: (() => { try { return [...adapter.features]; } catch { return []; } })(),
};
}
|