From 46412d23aff1f45dffa83fafb04a683282c8db58 Mon Sep 17 00:00:00 2001 From: Ryan Kuba Date: Wed, 20 Nov 2024 18:43:10 +0000 Subject: [PATCH] update the QOI code to use transferable object with memory leaks plugged (#119) Co-authored-by: ryan.kuba --- app/ui.js | 5 +---- core/decoders/qoi/decoder.js | 22 ++++++++++------------ core/decoders/tight.js | 35 +++++++++-------------------------- 3 files changed, 20 insertions(+), 42 deletions(-) diff --git a/app/ui.js b/app/ui.js index d973ffd42..53e2b4346 100644 --- a/app/ui.js +++ b/app/ui.js @@ -212,15 +212,12 @@ const UI = { // Stream Quality Presets let qualityDropdown = document.getElementById("noVNC_setting_video_quality"); - let supportsSharedArrayBuffers = typeof SharedArrayBuffer !== "undefined"; qualityDropdown.appendChild(Object.assign(document.createElement("option"),{value:0,label:"Static"})) qualityDropdown.appendChild(Object.assign(document.createElement("option"),{value:1,label:"Low"})) qualityDropdown.appendChild(Object.assign(document.createElement("option"),{value:2,label:"Medium"})) qualityDropdown.appendChild(Object.assign(document.createElement("option"),{value:3,label:"High"})) qualityDropdown.appendChild(Object.assign(document.createElement("option"),{value:4,label:"Extreme"})) - if (supportsSharedArrayBuffers) { - qualityDropdown.appendChild(Object.assign(document.createElement("option"),{value:5,label:"Lossless"})) - } + qualityDropdown.appendChild(Object.assign(document.createElement("option"),{value:5,label:"Lossless"})) qualityDropdown.appendChild(Object.assign(document.createElement("option"),{value:10,label:"Custom"})) // if port == 80 (or 443) then it won't be present and should be diff --git a/core/decoders/qoi/decoder.js b/core/decoders/qoi/decoder.js index 45a509b08..bcbe9cf73 100644 --- a/core/decoders/qoi/decoder.js +++ b/core/decoders/qoi/decoder.js @@ -295,9 +295,7 @@ async function init(input) { return wasm; } -var arr; var path; - async function run() { self.addEventListener('message', async function(evt) { if (evt.data.path) { @@ -307,31 +305,31 @@ async function run() { self.postMessage({ result: 1 }) + } else if (evt.data.freemem) { + evt.data.freemem = null; } else { try { - let length = evt.data.length; - let data = new Uint8Array(evt.data.sab.slice(0, length)); + let image = evt.data.image; + let data = new Uint8Array(image); let resultData = decode_qoi(data); - if (!arr) { - arr = new Uint8Array(evt.data.sabR); - } - let lengthR = resultData.data.length; - arr.set(resultData.data); let img = { colorSpace: resultData.colorSpace, width: resultData.width, height: resultData.height }; + var buff = new ArrayBuffer(resultData.data.length); + new Uint8Array(buff).set(new Uint8Array(resultData.data)); self.postMessage({ result: 0, img: img, - length: lengthR, width: evt.data.width, height: evt.data.height, x: evt.data.x, y: evt.data.y, - frame_id: evt.data.frame_id - }); + frame_id: evt.data.frame_id, + data: buff, + freemem: evt.data.image + }, [buff]); } catch (err) { self.postMessage({ result: 2, diff --git a/core/decoders/tight.js b/core/decoders/tight.js index 6fa06a0e4..cad4c6310 100644 --- a/core/decoders/tight.js +++ b/core/decoders/tight.js @@ -149,18 +149,17 @@ export default class TightDecoder { let i = this._availableWorkers.pop(); let worker = this._workers[i]; let rect = this._qoiRects.shift(); - this._arrs[i].set(rect.data); + var image = new ArrayBuffer(rect.data.length); + new Uint8Array(image).set(new Uint8Array(rect.data)); worker.postMessage({ - length: rect.data.length, x: rect.x, y: rect.y, width: rect.width, height: rect.height, depth: rect.depth, - sab: this._sabs[i], - sabR: this._sabsR[i], - frame_id: rect.frame_id - }); + frame_id: rect.frame_id, + image: image + }, [image]); } } @@ -471,10 +470,6 @@ export default class TightDecoder { if (this._workers) { this._enableQOI = false; this._availableWorkers = null; - this._sabs = null; - this._sabsR = null; - this._arrs = null; - this._arrsR = null; this._qoiRects = null; this._rectQlooping = null; for await (let i of Array.from(Array(this._threads).keys())) { @@ -486,12 +481,6 @@ export default class TightDecoder { } _enableQOIWorkers() { - const supportsSharedArrayBuffers = typeof SharedArrayBuffer !== "undefined"; - if (!supportsSharedArrayBuffers) { - Log.Warn("Enabling QOI Failed, client not compatible."); - return false; - } - let fullPath = window.location.pathname; let path = fullPath.substring(0, fullPath.lastIndexOf('/')+1); if ((window.navigator.hardwareConcurrency) && (window.navigator.hardwareConcurrency >= 4)) { @@ -501,24 +490,16 @@ export default class TightDecoder { } this._workers = []; this._availableWorkers = []; - this._sabs = []; - this._sabsR = []; - this._arrs = []; - this._arrsR = []; this._qoiRects = []; this._rectQlooping = false; for (let i = 0; i < this._threads; i++) { this._workers.push(new Worker("core/decoders/qoi/decoder.js")); - this._sabs.push(new SharedArrayBuffer(300000)); - this._sabsR.push(new SharedArrayBuffer(400000)); - this._arrs.push(new Uint8Array(this._sabs[i])); - this._arrsR.push(new Uint8ClampedArray(this._sabsR[i])); this._workers[i].onmessage = (evt) => { this._availableWorkers.push(i); switch(evt.data.result) { case 0: - let data = new Uint8ClampedArray(evt.data.length); - data.set(this._arrsR[i].slice(0, evt.data.length)); + evt.data.freemem = null; + let data = new Uint8ClampedArray(evt.data.data); let img = new ImageData(data, evt.data.img.width, evt.data.img.height, {colorSpace: evt.data.img.colorSpace}); this._displayGlobal.blitQoi( @@ -532,6 +513,8 @@ export default class TightDecoder { false ); this._processRectQ(); + // Send data back for garbage collection + this._workers[i].postMessage({freemem: evt.data.data}); break; case 1: Log.Info("QOI Worker is now available.");