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

View File

@ -376,7 +376,7 @@ export default class Display {
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) {
throw new Error("Cannot add a screen to a secondary display.");
}
@ -432,14 +432,18 @@ export default class Display {
pixelRatio: pixelRatio,
containerHeight: containerHeight,
containerWidth: containerWidth,
channel: UI.displayWindows[this.screens.length],
channel: UI.displayWindows.get(windowId),
scale: scale,
x2: x + serverWidth,
y2: serverHeight
}
this._screens.push(new_screen);
if (new_screen.channel) {
UI.registeredWindows.set(screenID, windowId);
new_screen.channel.postMessage({eventType: "registered", screenIndex: new_screen.screenIndex});
} else
Log.Debug(`Channel not found for screenId ${screenID}`);
return new_screen.screenIndex;
}
@ -454,7 +458,11 @@ export default class Display {
if (this._screens[i].screenID == screenID) {
//flush all rects on target screen
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);
removed = true;
break;
@ -757,30 +765,22 @@ export default class Display {
return;
}
let rect = {
'type': 'img',
const blob = new Blob([arr], { type: mime });
const rect = {
'type': 'bitmap',
'img': null,
'x': x,
'y': y,
'width': width,
'height': height,
'frame_id': frame_id
}
'frame_id': frame_id,
'mime': mime
};
this._processRectScreens(rect);
if (rect.inPrimary) {
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);
}
createImageBitmap(blob).then((bitmapImg) => {
rect.img = bitmapImg;
this._asyncRenderQPush(rect);
});
}
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);
a.img.close();
break;
case 'bitmap':
this.drawImage(a.img, pos.x, pos.y, a.width, a.height);
a.img.close();
break;
default:
this._syncFrameQueue.shift();
continue;
@ -1238,11 +1242,18 @@ export default class Display {
case 'vid':
this.drawImage(a.img, screenLocation.x, screenLocation.y, a.width, a.height);
break;
case 'bitmap':
this.drawImage(a.img, screenLocation.x, screenLocation.y, a.width, a.height);
break;
default:
continue;
}
primaryScreenRects++;
} else {
if (!this._screens[screenLocation.screenIndex]) {
continue;
}
switch (a.type) {
case 'dummy':
case 'transparent':
@ -1250,7 +1261,7 @@ export default class Display {
break;
case 'vid':
secondaryScreenRects++;
if (this._screens[screenLocation.screenIndex].channel) {
if (this._screens[screenLocation.screenIndex]?.channel) {
this._screens[screenLocation.screenIndex].channel.postMessage({
eventType: 'rect',
rect: {
@ -1267,6 +1278,25 @@ export default class Display {
}, [a.img]);
}
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':
secondaryScreenRects++;
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
Log.Debug('Smoothing enabled');
}
requestAnimationFrame( () => { this._pushAsyncFrame(); });
}
_setFillColor(color) {

View File

@ -1817,7 +1817,7 @@ export default class RFB extends EventTargetMixin {
...event.data.details,
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._sendEncodings();
clearTimeout(this._resizeTimeout);
@ -1826,7 +1826,7 @@ export default class RFB extends EventTargetMixin {
Log.Info(`Secondary monitor (${event.data.screenID}) has been registered.`);
break;
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);
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);
let screen = size.screens[0];
const windowId = new URLSearchParams(document.location.search).get('windowId');
let message = {
eventType: registerType,
screenID: screen.screenID,
windowId,
width: screen.width,
height: screen.height,
x: currentScreen.x || 0,