import { computeDasMap } from "../acoustics-core/das.js"; import { computeGccPhatMap } from "../acoustics-core/gcc-phat.js"; import { isDasAlgorithm, requireLocalizationAlgorithmDefinition, } from "../acoustics-core/localization-algorithms.js"; import { computeDasWebGpu } from "./das.js"; import { isRecoverableWebGpuError } from "./device.js"; function createAbortError(signal) { if (signal?.reason instanceof Error && signal.reason.name === "AbortError") return signal.reason; const error = new Error("DAS 계산이 취소되었습니다."); error.name = "AbortError"; return error; } function throwIfAborted(signal) { if (signal?.aborted) throw createAbortError(signal); } /** * Execute the requested DAS backend. `auto` falls back only for typed WebGPU * operational errors; invalid inputs and AbortError remain visible to callers. */ async function computeDasBackend(options, dependencies = {}) { const computeGpu = dependencies.computeGpu ?? computeDasWebGpu; const computeCpu = dependencies.computeCpu ?? computeDasMap; const requestedBackend = options?.experiment?.beamforming?.backend ?? "auto"; if (requestedBackend === "cpu") return computeCpu(options); if (requestedBackend === "webgpu") return computeGpu(options); if (requestedBackend !== "auto") { throw new TypeError(`지원하지 않는 DAS backend입니다: ${requestedBackend}`); } try { return await computeGpu(options); } catch (gpuError) { if (!isRecoverableWebGpuError(gpuError)) throw gpuError; throwIfAborted(options.signal); options.onFallback?.({ from: "webgpu", to: "cpu", error: gpuError }); throwIfAborted(options.signal); try { const cpuResult = await computeCpu(options); return { ...cpuResult, requestedBackend: "auto", fallback: { from: "webgpu", code: gpuError.code, message: gpuError.message, }, }; } catch (cpuError) { if (cpuError?.name === "AbortError") throw cpuError; throwIfAborted(options.signal); throw new AggregateError( [gpuError, cpuError], `WebGPU 실행 실패 후 CPU fallback도 실패했습니다: ${cpuError.message}`, ); } } } /** * Execute a DAS-family algorithm with the requested CPU/WebGPU policy. * Conventional DAS callers can keep using this backwards-compatible entry. */ export async function computeDasWithBackend(options, dependencies = {}) { const algorithm = requireLocalizationAlgorithmDefinition( options?.experiment?.beamforming?.algorithm, ); if (!isDasAlgorithm(algorithm.id)) { throw new RangeError( `computeDasWithBackend does not implement ${algorithm.id}.`, ); } return computeDasBackend(options, dependencies); } /** * Execute any registered localization algorithm. GCC-PHAT currently uses its * portable CPU implementation for both `auto` and `cpu`; an explicit WebGPU * request is rejected instead of silently changing the requested backend. */ export async function computeLocalizationWithBackend(options, dependencies = {}) { const algorithm = requireLocalizationAlgorithmDefinition( options?.experiment?.beamforming?.algorithm, ); if (isDasAlgorithm(algorithm.id)) { return computeDasBackend(options, dependencies); } const requestedBackend = options?.experiment?.beamforming?.backend ?? "auto"; if (requestedBackend === "webgpu") { throw new TypeError( `${algorithm.label} currently supports only the CPU backend; choose auto or cpu.`, ); } if (requestedBackend !== "auto" && requestedBackend !== "cpu") { throw new TypeError(`unsupported localization backend: ${requestedBackend}`); } const computeCpu = dependencies.computeGccCpu ?? dependencies.computeCpu ?? computeGccPhatMap; return computeCpu(options); }