update the QOI code to use transferable object with memory leaks plugged (#119)

Co-authored-by: ryan.kuba <ryan.kuba@kasmweb.com>
This commit is contained in:
Ryan Kuba 2024-11-20 18:43:10 +00:00 committed by GitHub
parent db7bca45c9
commit 46412d23af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 20 additions and 42 deletions

View File

@ -212,15 +212,12 @@ const UI = {
// Stream Quality Presets // Stream Quality Presets
let qualityDropdown = document.getElementById("noVNC_setting_video_quality"); 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:0,label:"Static"}))
qualityDropdown.appendChild(Object.assign(document.createElement("option"),{value:1,label:"Low"})) 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:2,label:"Medium"}))
qualityDropdown.appendChild(Object.assign(document.createElement("option"),{value:3,label:"High"})) qualityDropdown.appendChild(Object.assign(document.createElement("option"),{value:3,label:"High"}))
qualityDropdown.appendChild(Object.assign(document.createElement("option"),{value:4,label:"Extreme"})) 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"})) 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 // if port == 80 (or 443) then it won't be present and should be

View File

@ -295,9 +295,7 @@ async function init(input) {
return wasm; return wasm;
} }
var arr;
var path; var path;
async function run() { async function run() {
self.addEventListener('message', async function(evt) { self.addEventListener('message', async function(evt) {
if (evt.data.path) { if (evt.data.path) {
@ -307,31 +305,31 @@ async function run() {
self.postMessage({ self.postMessage({
result: 1 result: 1
}) })
} else if (evt.data.freemem) {
evt.data.freemem = null;
} else { } else {
try { try {
let length = evt.data.length; let image = evt.data.image;
let data = new Uint8Array(evt.data.sab.slice(0, length)); let data = new Uint8Array(image);
let resultData = decode_qoi(data); let resultData = decode_qoi(data);
if (!arr) {
arr = new Uint8Array(evt.data.sabR);
}
let lengthR = resultData.data.length;
arr.set(resultData.data);
let img = { let img = {
colorSpace: resultData.colorSpace, colorSpace: resultData.colorSpace,
width: resultData.width, width: resultData.width,
height: resultData.height height: resultData.height
}; };
var buff = new ArrayBuffer(resultData.data.length);
new Uint8Array(buff).set(new Uint8Array(resultData.data));
self.postMessage({ self.postMessage({
result: 0, result: 0,
img: img, img: img,
length: lengthR,
width: evt.data.width, width: evt.data.width,
height: evt.data.height, height: evt.data.height,
x: evt.data.x, x: evt.data.x,
y: evt.data.y, 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) { } catch (err) {
self.postMessage({ self.postMessage({
result: 2, result: 2,

View File

@ -149,18 +149,17 @@ export default class TightDecoder {
let i = this._availableWorkers.pop(); let i = this._availableWorkers.pop();
let worker = this._workers[i]; let worker = this._workers[i];
let rect = this._qoiRects.shift(); 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({ worker.postMessage({
length: rect.data.length,
x: rect.x, x: rect.x,
y: rect.y, y: rect.y,
width: rect.width, width: rect.width,
height: rect.height, height: rect.height,
depth: rect.depth, depth: rect.depth,
sab: this._sabs[i], frame_id: rect.frame_id,
sabR: this._sabsR[i], image: image
frame_id: rect.frame_id }, [image]);
});
} }
} }
@ -471,10 +470,6 @@ export default class TightDecoder {
if (this._workers) { if (this._workers) {
this._enableQOI = false; this._enableQOI = false;
this._availableWorkers = null; this._availableWorkers = null;
this._sabs = null;
this._sabsR = null;
this._arrs = null;
this._arrsR = null;
this._qoiRects = null; this._qoiRects = null;
this._rectQlooping = null; this._rectQlooping = null;
for await (let i of Array.from(Array(this._threads).keys())) { for await (let i of Array.from(Array(this._threads).keys())) {
@ -486,12 +481,6 @@ export default class TightDecoder {
} }
_enableQOIWorkers() { _enableQOIWorkers() {
const supportsSharedArrayBuffers = typeof SharedArrayBuffer !== "undefined";
if (!supportsSharedArrayBuffers) {
Log.Warn("Enabling QOI Failed, client not compatible.");
return false;
}
let fullPath = window.location.pathname; let fullPath = window.location.pathname;
let path = fullPath.substring(0, fullPath.lastIndexOf('/')+1); let path = fullPath.substring(0, fullPath.lastIndexOf('/')+1);
if ((window.navigator.hardwareConcurrency) && (window.navigator.hardwareConcurrency >= 4)) { if ((window.navigator.hardwareConcurrency) && (window.navigator.hardwareConcurrency >= 4)) {
@ -501,24 +490,16 @@ export default class TightDecoder {
} }
this._workers = []; this._workers = [];
this._availableWorkers = []; this._availableWorkers = [];
this._sabs = [];
this._sabsR = [];
this._arrs = [];
this._arrsR = [];
this._qoiRects = []; this._qoiRects = [];
this._rectQlooping = false; this._rectQlooping = false;
for (let i = 0; i < this._threads; i++) { for (let i = 0; i < this._threads; i++) {
this._workers.push(new Worker("core/decoders/qoi/decoder.js")); 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._workers[i].onmessage = (evt) => {
this._availableWorkers.push(i); this._availableWorkers.push(i);
switch(evt.data.result) { switch(evt.data.result) {
case 0: case 0:
let data = new Uint8ClampedArray(evt.data.length); evt.data.freemem = null;
data.set(this._arrsR[i].slice(0, evt.data.length)); 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}); let img = new ImageData(data, evt.data.img.width, evt.data.img.height, {colorSpace: evt.data.img.colorSpace});
this._displayGlobal.blitQoi( this._displayGlobal.blitQoi(
@ -532,6 +513,8 @@ export default class TightDecoder {
false false
); );
this._processRectQ(); this._processRectQ();
// Send data back for garbage collection
this._workers[i].postMessage({freemem: evt.data.data});
break; break;
case 1: case 1:
Log.Info("QOI Worker is now available."); Log.Info("QOI Worker is now available.");