diff --git a/docker/frontend/src/App.tsx b/docker/frontend/src/App.tsx index 923b16a..3f127d5 100644 --- a/docker/frontend/src/App.tsx +++ b/docker/frontend/src/App.tsx @@ -1050,7 +1050,7 @@ export function Workspace() { if (!engine || !previewBytes || shooting) return; const token = ++renderToken.current; const timer = setTimeout(async () => { - setBusy(t('act.exporting')); + setBusy(t('act.rendering')); try { // The geometry is the frame's own; only the recipe differs between the // render and the compare copy, so the two can never drift apart. diff --git a/docker/frontend/src/engine/inpaint.ts b/docker/frontend/src/engine/inpaint.ts index d76db54..ba30c36 100644 --- a/docker/frontend/src/engine/inpaint.ts +++ b/docker/frontend/src/engine/inpaint.ts @@ -71,23 +71,6 @@ async function warmsUp(ort: typeof Ort, session: Ort.InferenceSession): Promise< } } -// Whether the page has a device of its own to run the graph on. Asked of the -// browser rather than of the runtime: creating a session of any kind — the -// WebGPU one included — builds the wasm backend underneath it, and -// `env.wasm.proxy` is read at that moment. A proxy set after it is a proxy that -// never took, and every run of the fallback then ends the way it did the first -// time this was tried: `no available backend found. ERR: [wasm] Error: worker -// not ready`. So the question is put before anything is created. -async function hasDevice(): Promise { - const gpu = (navigator as Navigator & { gpu?: { requestAdapter(): Promise } }).gpu; - if (!gpu) return false; - try { - return !!(await gpu.requestAdapter()); - } catch { - return false; - } -} - // WebGPU first, the processor as the answer to every way that can fail: no // adapter, no `shader-f16` needed here but an op the provider never // implemented, a driver that opens a session and dies on the first dispatch. @@ -116,16 +99,29 @@ function load(): Promise { ort.env.wasm.numThreads = self.crossOriginIsolated ? Math.min(4, navigator.hardwareConcurrency || 1) : 1; - // The fallback's turn is taken in a worker of the page's own, wherever - // there is no device to take it on: a wasm run is seconds of arithmetic - // that never hands the thread back in the middle of it, and a thread that - // cannot answer a finger for three seconds is a tab a phone takes away - // (measured: 3.5s of frozen page per repair, on a desktop). Proxied, the - // page keeps its frames and its touches while the repair is worked out, - // and the repair lands under the spinner the studio already shows. It is - // left off where a device is there, because the WebGPU path is a device of - // its own and must be built on the page's own thread. - ort.env.wasm.proxy = !(await hasDevice()); + // The fallback's turn is taken in a worker of the page's own, always: a + // wasm run is seconds of arithmetic that never hands the thread back in + // the middle of it, and a thread that cannot answer a finger for three + // seconds is a tab a phone takes away (measured: 3.5s of frozen page per + // repair, on a desktop). Proxied, the page keeps its frames and its + // touches while the repair is worked out, and the repair lands under the + // spinner the studio already shows. + // + // Always, and not "wherever there is no device": `requestAdapter` answers + // for the browser, not for this graph, and a page holding an adapter the + // model cannot run on (measured: chromium's JSEP provider, which fails + // MI-GAN's own GatherND on the first run) took the fallback on its own + // thread — 2.1s frozen per repair against the 0.3s this costs — which is + // the freeze the worker is here to prevent. The WebGPU session is + // unaffected either way: the proxy is the wasm backend's, and the device + // session above is built and run on the page's thread as before. + // + // Set before anything is created: `env.wasm.proxy` is read at the moment + // the wasm backend is built, and a proxy set after one was — the WebGPU + // session builds one underneath it — is a proxy that never took (its runs + // then end in `no available backend found. ERR: [wasm] Error: worker not + // ready`). + ort.env.wasm.proxy = true; ort.env.webgpu.powerPreference = 'high-performance'; const session = await openSession(ort); return { ort, session }; diff --git a/docker/frontend/src/i18n/en.ts b/docker/frontend/src/i18n/en.ts index c527bcc..9194638 100644 --- a/docker/frontend/src/i18n/en.ts +++ b/docker/frontend/src/i18n/en.ts @@ -28,6 +28,9 @@ export const en: Dict = { 'act.reset': 'RESET', 'act.export': 'EXPORT', 'act.exporting': 'Exporting…', + // The stage's own re-render behind a brush stroke or a knob: no file is being + // written anywhere, and "Exporting…" over a HEAL was read as exactly that. + 'act.rendering': 'Rendering…', 'act.upscaling': 'Upscaling the photo {done}/{total}…', 'act.cancel': 'Cancel', diff --git a/docker/frontend/src/i18n/vi.ts b/docker/frontend/src/i18n/vi.ts index 798302c..23529fb 100644 --- a/docker/frontend/src/i18n/vi.ts +++ b/docker/frontend/src/i18n/vi.ts @@ -39,6 +39,7 @@ export const vi = { 'act.reset': 'ĐẶT LẠI', 'act.export': 'XUẤT ẢNH', 'act.exporting': 'Đang xuất…', + 'act.rendering': 'Đang dựng ảnh…', 'act.upscaling': 'Đang phóng to ảnh {done}/{total}…', 'act.cancel': 'Huỷ',