VNC-132 Fix image rendering logic to decrease memory usage (#141)

* VNC-132 Fix image rendering logic to decrease memory usage

* Updating the recalculate primary display container size logic to ensure an even resolution is set

* VNC-132 Free memory after rendering bitmap image

* VNC-132 Update screen channel logic
This commit is contained in:
quickiwiki 2025-07-09 17:42:43 +05:00 committed by GitHub
parent 4d5609e9c4
commit b2a6b2a2e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 137 additions and 96 deletions

View File

@ -41,6 +41,7 @@ import Keyboard from "../core/input/keyboard.js";
import RFB from "../core/rfb.js"; import RFB from "../core/rfb.js";
import { MouseButtonMapper, XVNC_BUTTONS } from "../core/mousebuttonmapper.js"; import { MouseButtonMapper, XVNC_BUTTONS } from "../core/mousebuttonmapper.js";
import * as WebUtil from "./webutil.js"; import * as WebUtil from "./webutil.js";
import { uuidv4 } from '../core/util/strings.js';
const PAGE_TITLE = "KasmVNC"; const PAGE_TITLE = "KasmVNC";
@ -70,7 +71,8 @@ const UI = {
selectedMonitor: null, selectedMonitor: null,
refreshRotation: 0, refreshRotation: 0,
currentDisplay: null, currentDisplay: null,
displayWindows: ['primary'], displayWindows: new Map([['primary', 'primary']]),
registeredWindows: new Map([['primary', 'primary']]),
supportsBroadcastChannel: (typeof BroadcastChannel !== "undefined"), supportsBroadcastChannel: (typeof BroadcastChannel !== "undefined"),
@ -2009,8 +2011,9 @@ const UI = {
}, },
async addSecondaryMonitor() { async addSecondaryMonitor() {
let new_display_path = window.location.pathname.replace(/[^/]*$/, '') let new_display_path = window.location.pathname.replace(/[^/]*$/, '');
let new_display_url = `${window.location.protocol}//${window.location.host}${new_display_path}screen.html`; const windowId = uuidv4();
let new_display_url = `${window.location.protocol}//${window.location.host}${new_display_path}screen.html?windowId=${windowId}`;
const auto_placement = document.getElementById('noVNC_auto_placement').checked const auto_placement = document.getElementById('noVNC_auto_placement').checked
if (auto_placement && 'getScreenDetails' in window) { if (auto_placement && 'getScreenDetails' in window) {
@ -2024,7 +2027,7 @@ const UI = {
let screen = details.screens[current] let screen = details.screens[current]
const options = 'left='+screen.availLeft+',top='+screen.availTop+',width='+screen.availWidth+',height='+screen.availHeight+',fullscreen' const options = 'left='+screen.availLeft+',top='+screen.availTop+',width='+screen.availWidth+',height='+screen.availHeight+',fullscreen'
let newdisplay = window.open(new_display_url, '_blank', options); let newdisplay = window.open(new_display_url, '_blank', options);
UI.displayWindows.push(newdisplay); UI.displayWindows.set(windowId, newdisplay);
return; return;
} }
} catch (e) { } catch (e) {
@ -2035,14 +2038,16 @@ const UI = {
Log.Debug(`Opening a secondary display ${new_display_url}`) Log.Debug(`Opening a secondary display ${new_display_url}`)
let newdisplay = window.open(new_display_url, '_blank', 'toolbar=0,location=0,menubar=0'); let newdisplay = window.open(new_display_url, '_blank', 'toolbar=0,location=0,menubar=0');
UI.displayWindows.push(newdisplay); if (newdisplay) {
UI.displayWindows.set(windowId, newdisplay);
}
}, },
initMonitors(screenPlan) { initMonitors(screenPlan) {
const { scale } = UI.multiMonitorSettings() const { scale } = UI.multiMonitorSettings()
let monitors = [] let monitors = []
let showNativeResolution = false let showNativeResolution = false
let num = 1 let num = 1;
screenPlan.screens.forEach(screen => { screenPlan.screens.forEach(screen => {
if (parseFloat(screen.pixelRatio) != 1) { if (parseFloat(screen.pixelRatio) != 1) {
showNativeResolution = true showNativeResolution = true
@ -2223,7 +2228,9 @@ const UI = {
serverWidth: Math.round(width * scale), serverWidth: Math.round(width * scale),
screens screens
} }
if (UI.rfb) {
UI.rfb.applyScreenPlan(screenPlan); UI.rfb.applyScreenPlan(screenPlan);
}
}, },

View File

@ -376,7 +376,7 @@ export default class Display {
return changes; return changes;
} }
addScreen(screenID, width, height, pixelRatio, containerHeight, containerWidth, scale, serverWidth, serverHeight, x, y) { addScreen(screenID, width, height, pixelRatio, containerHeight, containerWidth, scale, serverWidth, serverHeight, x, y, windowId) {
if (!this._isPrimaryDisplay) { if (!this._isPrimaryDisplay) {
throw new Error("Cannot add a screen to a secondary display."); throw new Error("Cannot add a screen to a secondary display.");
} }
@ -432,14 +432,18 @@ export default class Display {
pixelRatio: pixelRatio, pixelRatio: pixelRatio,
containerHeight: containerHeight, containerHeight: containerHeight,
containerWidth: containerWidth, containerWidth: containerWidth,
channel: UI.displayWindows[this.screens.length], channel: UI.displayWindows.get(windowId),
scale: scale, scale: scale,
x2: x + serverWidth, x2: x + serverWidth,
y2: serverHeight y2: serverHeight
} }
this._screens.push(new_screen); this._screens.push(new_screen);
if (new_screen.channel) {
UI.registeredWindows.set(screenID, windowId);
new_screen.channel.postMessage({eventType: "registered", screenIndex: new_screen.screenIndex}); new_screen.channel.postMessage({eventType: "registered", screenIndex: new_screen.screenIndex});
} else
Log.Debug(`Channel not found for screenId ${screenID}`);
return new_screen.screenIndex; return new_screen.screenIndex;
} }
@ -454,7 +458,11 @@ export default class Display {
if (this._screens[i].screenID == screenID) { if (this._screens[i].screenID == screenID) {
//flush all rects on target screen //flush all rects on target screen
this._flushRectsScreen(i); this._flushRectsScreen(i);
UI.displayWindows.splice(i, 1); const windowId = UI.registeredWindows.get(screenID);
if (windowId) {
UI.registeredWindows.delete(screenID);
UI.displayWindows.delete(windowId);
}
this._screens.splice(i, 1); this._screens.splice(i, 1);
removed = true; removed = true;
break; break;
@ -757,30 +765,22 @@ export default class Display {
return; return;
} }
let rect = { const blob = new Blob([arr], { type: mime });
'type': 'img', const rect = {
'type': 'bitmap',
'img': null, 'img': null,
'x': x, 'x': x,
'y': y, 'y': y,
'width': width, 'width': width,
'height': height, 'height': height,
'frame_id': frame_id 'frame_id': frame_id,
} 'mime': mime
};
this._processRectScreens(rect); this._processRectScreens(rect);
createImageBitmap(blob).then((bitmapImg) => {
if (rect.inPrimary) { rect.img = bitmapImg;
const img = new Image();
img.src = "data: " + mime + ";base64," + Base64.encode(arr);
rect.img = img;
} else {
rect.type = "_img";
}
if (rect.inSecondary) {
rect.mime = mime;
rect.src = "data: " + mime + ";base64," + Base64.encode(arr);
}
this._asyncRenderQPush(rect); this._asyncRenderQPush(rect);
});
} }
transparentRect(x, y, width, height, img, frame_id, hashId) { transparentRect(x, y, width, height, img, frame_id, hashId) {
@ -1027,6 +1027,10 @@ export default class Display {
this.drawImage(a.img, pos.x, pos.y, a.width, a.height); this.drawImage(a.img, pos.x, pos.y, a.width, a.height);
a.img.close(); a.img.close();
break; break;
case 'bitmap':
this.drawImage(a.img, pos.x, pos.y, a.width, a.height);
a.img.close();
break;
default: default:
this._syncFrameQueue.shift(); this._syncFrameQueue.shift();
continue; continue;
@ -1238,11 +1242,18 @@ export default class Display {
case 'vid': case 'vid':
this.drawImage(a.img, screenLocation.x, screenLocation.y, a.width, a.height); this.drawImage(a.img, screenLocation.x, screenLocation.y, a.width, a.height);
break; break;
case 'bitmap':
this.drawImage(a.img, screenLocation.x, screenLocation.y, a.width, a.height);
break;
default: default:
continue; continue;
} }
primaryScreenRects++; primaryScreenRects++;
} else { } else {
if (!this._screens[screenLocation.screenIndex]) {
continue;
}
switch (a.type) { switch (a.type) {
case 'dummy': case 'dummy':
case 'transparent': case 'transparent':
@ -1250,7 +1261,7 @@ export default class Display {
break; break;
case 'vid': case 'vid':
secondaryScreenRects++; secondaryScreenRects++;
if (this._screens[screenLocation.screenIndex].channel) { if (this._screens[screenLocation.screenIndex]?.channel) {
this._screens[screenLocation.screenIndex].channel.postMessage({ this._screens[screenLocation.screenIndex].channel.postMessage({
eventType: 'rect', eventType: 'rect',
rect: { rect: {
@ -1267,6 +1278,25 @@ export default class Display {
}, [a.img]); }, [a.img]);
} }
break; break;
case 'bitmap':
secondaryScreenRects++;
if (this._screens[screenLocation.screenIndex].channel) {
this._screens[screenLocation.screenIndex].channel.postMessage({
eventType: 'rect',
rect: {
'type': 'bitmap',
'img': a.img,
'x': a.x,
'y': a.y,
'width': a.width,
'height': a.height,
'frame_id': a.frame_id,
'screenLocations': a.screenLocations
},
screenLocationIndex: sI
}, [a.img]);
}
break;
case 'blit': case 'blit':
secondaryScreenRects++; secondaryScreenRects++;
let buf = a.data.buffer; let buf = a.data.buffer;
@ -1434,6 +1464,8 @@ export default class Display {
this._target.style.imageRendering = 'auto'; //auto is really smooth (blurry) using trilinear of linear this._target.style.imageRendering = 'auto'; //auto is really smooth (blurry) using trilinear of linear
Log.Debug('Smoothing enabled'); Log.Debug('Smoothing enabled');
} }
requestAnimationFrame( () => { this._pushAsyncFrame(); });
} }
_setFillColor(color) { _setFillColor(color) {

View File

@ -1817,7 +1817,7 @@ export default class RFB extends EventTargetMixin {
...event.data.details, ...event.data.details,
screenID: event.data.screenID screenID: event.data.screenID
} }
let screenIndex = this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth, event.data.scale, event.data.serverWidth, event.data.serverHeight, event.data.x, event.data.y); let screenIndex = this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth, event.data.scale, event.data.serverWidth, event.data.serverHeight, event.data.x, event.data.y, event.data.windowId);
this._proxyRFBMessage('screenRegistrationConfirmed', [ this._display.screens[screenIndex].screenID, screenIndex ]); this._proxyRFBMessage('screenRegistrationConfirmed', [ this._display.screens[screenIndex].screenID, screenIndex ]);
this._sendEncodings(); this._sendEncodings();
clearTimeout(this._resizeTimeout); clearTimeout(this._resizeTimeout);
@ -1826,7 +1826,7 @@ export default class RFB extends EventTargetMixin {
Log.Info(`Secondary monitor (${event.data.screenID}) has been registered.`); Log.Info(`Secondary monitor (${event.data.screenID}) has been registered.`);
break; break;
case 'reattach': case 'reattach':
let changes = this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth, event.data.scale, event.data.serverWidth, event.data.serverHeight, event.data.x, event.data.y); let changes = this._display.addScreen(event.data.screenID, event.data.width, event.data.height, event.data.pixelRatio, event.data.containerHeight, event.data.containerWidth, event.data.scale, event.data.serverWidth, event.data.serverHeight, event.data.x, event.data.y, event.data.windowId);
clearTimeout(this._resizeTimeout); clearTimeout(this._resizeTimeout);
this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500); this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
@ -1971,10 +1971,12 @@ export default class RFB extends EventTargetMixin {
this._display.autoscale(size.screens[0].serverWidth, size.screens[0].serverHeight, size.screens[0].scale); this._display.autoscale(size.screens[0].serverWidth, size.screens[0].serverHeight, size.screens[0].scale);
let screen = size.screens[0]; let screen = size.screens[0];
const windowId = new URLSearchParams(document.location.search).get('windowId');
let message = { let message = {
eventType: registerType, eventType: registerType,
screenID: screen.screenID, screenID: screen.screenID,
windowId,
width: screen.width, width: screen.width,
height: screen.height, height: screen.height,
x: currentScreen.x || 0, x: currentScreen.x || 0,