import { computeDasMap } from "../acoustics-core/das.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. */ export async function computeDasWithBackend(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}`, ); } } }