mirror of
https://github.com/BinomialLLC/basis_universal.git
synced 2026-07-30 01:49:02 +00:00
191 lines
7.4 KiB
JavaScript
191 lines
7.4 KiB
JavaScript
// encode-worker.js
|
|
//
|
|
// Step 2 of moving encoding off the UI thread: load the (threaded) BasisEncoder
|
|
// module in a Web Worker (proven working), then on demand apply a DTO and run a
|
|
// real encode -- reporting the resulting size/PSNR/time back to the page. Uses its
|
|
// OWN independent module instance; the page keeps its own instance for transcode.
|
|
//
|
|
// Protocol (page -> worker):
|
|
// { type: 'init', scriptSrc } load + initialize the module ONCE
|
|
// { type: 'encode', dto } applyEncodeDTO(dto) + encode(); report result (.basis/.KTX2)
|
|
// { type: 'encodeDDS', dto } applyEncodeDTO(dto) + applyDDSEncodeDTO(dto) + encodeToDDS(); report .DDS
|
|
// Protocol (worker -> page):
|
|
// { type: 'log', text } informational
|
|
// { type: 'loaded', text } module loaded + initializeBasis() succeeded
|
|
// { type: 'encoded', text } encode finished (size / PSNR / time)
|
|
// { type: 'encodedDDS', text } DDS encode finished (size / time)
|
|
// { type: 'error', text } something failed
|
|
|
|
importScripts('encode_dto_apply.js'); // defines applyEncodeDTO()/applyDDSEncodeDTO() -- shared with the page
|
|
|
|
let g_module = null;
|
|
|
|
function post(type, text) { self.postMessage({ type: type, text: text }); }
|
|
|
|
self.onmessage = function (e)
|
|
{
|
|
const msg = e.data || {};
|
|
|
|
if (msg.type === 'init')
|
|
{
|
|
if (g_module)
|
|
{
|
|
post('loaded', 'worker: module already loaded (reusing instance)');
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
post('log', 'worker: importScripts(' + msg.scriptSrc + ')');
|
|
importScripts(msg.scriptSrc); // defines the global BASIS() factory (EXPORT_NAME=BASIS)
|
|
|
|
if (typeof BASIS !== 'function')
|
|
{
|
|
post('error', 'worker: BASIS factory not defined after importScripts');
|
|
return;
|
|
}
|
|
|
|
// Absolute URL to the encoder .js (relative to this worker's location).
|
|
const absMainUrl = new URL(msg.scriptSrc, self.location.href).href;
|
|
post('log', 'worker: mainScriptUrlOrBlob=' + absMainUrl);
|
|
|
|
BASIS({
|
|
// REQUIRED when loaded via importScripts inside a worker: emscripten
|
|
// spawns its pthread sub-workers from this URL. Without it the thread
|
|
// pool never comes up and BASIS() hangs silently.
|
|
mainScriptUrlOrBlob: absMainUrl,
|
|
locateFile: function (path) { return new URL('../encoder/build/' + path, self.location.href).href; },
|
|
// Forward the encoder's stdout/stderr to the page line-by-line. Because
|
|
// encode() blocks the WORKER (not the main thread), these stream LIVE to
|
|
// the page during the encode -- the modal log updates in real time.
|
|
print: function (t) { self.postMessage({ type: 'print', text: t }); },
|
|
printErr: function (t) { self.postMessage({ type: 'print', text: t }); },
|
|
onRuntimeInitialized: function () { post('log', 'worker: onRuntimeInitialized'); }
|
|
})
|
|
.then(function (module)
|
|
{
|
|
g_module = module;
|
|
|
|
if (module.initializeBasis)
|
|
{
|
|
module.initializeBasis();
|
|
post('loaded', 'worker: module loaded + initializeBasis() OK (threaded module live in worker)');
|
|
}
|
|
else
|
|
{
|
|
post('error', 'worker: module loaded but initializeBasis() is missing');
|
|
}
|
|
})
|
|
.catch(function (err)
|
|
{
|
|
post('error', 'worker: BASIS() instantiation failed: ' + err);
|
|
});
|
|
}
|
|
catch (err)
|
|
{
|
|
post('error', 'worker: init exception: ' + err);
|
|
}
|
|
}
|
|
else if (msg.type === 'encode')
|
|
{
|
|
if (!g_module)
|
|
{
|
|
post('error', 'worker: encode requested before module is loaded');
|
|
return;
|
|
}
|
|
|
|
let enc = null;
|
|
try
|
|
{
|
|
enc = new g_module.BasisEncoder(); // embind class lives on the module instance
|
|
applyEncodeDTO(enc, msg.dto); // shared with the page; pure (encoder, dto)
|
|
|
|
// destination buffer -- matches the legacy page (new Uint8Array(1024*1024*24))
|
|
const out = new Uint8Array(1024 * 1024 * 24);
|
|
|
|
// time JUST the encode() call, like the main-thread path (startTime/elapsed)
|
|
const encT0 = performance.now();
|
|
const len = enc.encode(out);
|
|
const encodeMs = performance.now() - encT0;
|
|
|
|
const psnr = (typeof enc.getLastEncodeMip0RGBAPSNR === 'function') ? enc.getLastEncodeMip0RGBAPSNR() : -1;
|
|
|
|
if (len > 0)
|
|
{
|
|
// exact-size copy (its own ArrayBuffer) so we can transfer it back zero-copy
|
|
const result = out.slice(0, len);
|
|
self.postMessage({
|
|
type: 'encoded',
|
|
text: 'worker: ENCODE OK -> ' + len + ' bytes, mip0 RGBA PSNR ' + psnr.toFixed(3) + ', ' + encodeMs.toFixed(2) + ' ms',
|
|
bytes: result.buffer,
|
|
len: len,
|
|
psnr: psnr, // number: mip0 RGBA PSNR (-> g_lastEncodeMip0RGBAPSNR)
|
|
encodeMs: encodeMs // number: encode() wall time (-> g_lastEncodeTime)
|
|
}, [result.buffer]); // transfer the buffer (no copy)
|
|
}
|
|
else
|
|
post('error', 'worker: encode returned 0 bytes (failed)');
|
|
}
|
|
catch (err)
|
|
{
|
|
post('error', 'worker: encode exception: ' + err);
|
|
}
|
|
finally
|
|
{
|
|
// Free the WASM encoder even on exception -- the worker is never terminated, so a leak here
|
|
// would permanently consume WASM heap.
|
|
if (enc) enc.delete();
|
|
}
|
|
}
|
|
else if (msg.type === 'encodeDDS')
|
|
{
|
|
if (!g_module)
|
|
{
|
|
post('error', 'worker: DDS encode requested before module is loaded');
|
|
return;
|
|
}
|
|
|
|
let enc = null;
|
|
try
|
|
{
|
|
enc = new g_module.BasisEncoder(); // embind class lives on the module instance
|
|
applyEncodeDTO(enc, msg.dto); // shared source/sRGB/mip/weights setup (same as KTX2)
|
|
applyDDSEncodeDTO(enc, msg.dto); // DDS-specific output format + BC7 options
|
|
|
|
// Destination buffer: sized by the page (worst case 32bpp + full mip chain, bounded by the
|
|
// encoder's source-pixel cap). If somehow too small, encodeToDDS() returns 0 (clean failure).
|
|
const out = new Uint8Array(msg.dto.ddsBufferSize);
|
|
|
|
// Time JUST the encode, like the .basis/.KTX2 path.
|
|
const encT0 = performance.now();
|
|
const len = enc.encodeToDDS(out);
|
|
const encodeMs = performance.now() - encT0;
|
|
|
|
if (len > 0)
|
|
{
|
|
// exact-size copy (its own ArrayBuffer) so we can transfer it back zero-copy
|
|
const result = out.slice(0, len);
|
|
self.postMessage({
|
|
type: 'encodedDDS',
|
|
text: 'worker: DDS ENCODE OK -> ' + len + ' bytes, ' + encodeMs.toFixed(2) + ' ms',
|
|
bytes: result.buffer,
|
|
len: len,
|
|
encodeMs: encodeMs // number: encodeToDDS() wall time (-> g_lastEncodeTime)
|
|
}, [result.buffer]); // transfer the buffer (no copy)
|
|
}
|
|
else
|
|
post('error', 'worker: encodeToDDS returned 0 bytes (failed)');
|
|
}
|
|
catch (err)
|
|
{
|
|
post('error', 'worker: DDS encode exception: ' + err);
|
|
}
|
|
finally
|
|
{
|
|
// Free the WASM encoder even on exception -- the worker is never terminated, so a leak here
|
|
// would permanently consume WASM heap.
|
|
if (enc) enc.delete();
|
|
}
|
|
}
|
|
};
|