cvmts: replace jpeg-turbo native module with new rust module
This module also does threadpooling internally, so we don't need Piscina anymore (which I'm pretty sure was actually bottlenecking.)
This commit is contained in:
@@ -11,12 +11,11 @@
|
||||
"author": "Elijah R, modeco80",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@computernewb/jpeg-turbo": "*",
|
||||
"@cvmts/guac-rs": "*",
|
||||
"@cvmts/jpegturbo-rs": "*",
|
||||
"@cvmts/qemu": "*",
|
||||
"execa": "^8.0.1",
|
||||
"mnemonist": "^0.39.5",
|
||||
"piscina": "^4.4.0",
|
||||
"sharp": "^0.33.3",
|
||||
"toml": "^3.0.0",
|
||||
"ws": "^8.14.1"
|
||||
|
||||
@@ -810,7 +810,7 @@ export default class CollabVMServer {
|
||||
let display = this.VM.GetDisplay();
|
||||
let displaySize = display.Size();
|
||||
|
||||
let encoded = await JPEGEncoder.EncodeJpeg(display.Buffer(), displaySize, rect);
|
||||
let encoded = await JPEGEncoder.Encode(display.Buffer(), displaySize, rect);
|
||||
|
||||
return encoded.toString('base64');
|
||||
}
|
||||
|
||||
@@ -1,59 +1,52 @@
|
||||
import path from 'node:path';
|
||||
import Piscina from 'piscina';
|
||||
|
||||
import { Size, Rect } from '@cvmts/shared';
|
||||
|
||||
const kMaxJpegThreads = 4;
|
||||
const kIdleTimeout = 25000;
|
||||
|
||||
// Thread pool for doing JPEG encoding for rects.
|
||||
const TheJpegEncoderPool = new Piscina({
|
||||
filename: path.join(import.meta.dirname + '/JPEGEncoderWorker.js'),
|
||||
idleTimeout: kIdleTimeout,
|
||||
maxThreads: kMaxJpegThreads
|
||||
});
|
||||
|
||||
const TheThumbnailEncoderPool = new Piscina({
|
||||
filename: path.join(import.meta.dirname + '/ThumbnailJPEGEncoderWorker.js'),
|
||||
idleTimeout: kIdleTimeout,
|
||||
maxThreads: kMaxJpegThreads
|
||||
});
|
||||
import sharp from 'sharp';
|
||||
import * as jpeg from '@cvmts/jpegturbo-rs';
|
||||
|
||||
// A good balance. TODO: Configurable?
|
||||
let gJpegQuality = 35;
|
||||
|
||||
export class JPEGEncoder {
|
||||
const kThumbnailSize: Size = {
|
||||
width: 400,
|
||||
height: 300
|
||||
};
|
||||
|
||||
// this returns appropiate Sharp options to deal with CVMTS raw framebuffers
|
||||
// (which are RGBA bitmaps, essentially. We probably should abstract that out but
|
||||
// that'd mean having to introduce that to rfb and oihwekjtgferklds;./tghnredsltg;erhds)
|
||||
function GetRawSharpOptions(size: Size): sharp.CreateRaw {
|
||||
return {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
channels: 4
|
||||
};
|
||||
}
|
||||
|
||||
export class JPEGEncoder {
|
||||
static SetQuality(quality: number) {
|
||||
gJpegQuality = quality;
|
||||
}
|
||||
|
||||
static async EncodeJpeg(canvas: Buffer, displaySize: Size, rect: Rect): Promise<Buffer> {
|
||||
static async Encode(canvas: Buffer, displaySize: Size, rect: Rect): Promise<Buffer> {
|
||||
let offset = (rect.y * displaySize.width + rect.x) * 4;
|
||||
|
||||
let res = await TheJpegEncoderPool.run({
|
||||
buffer: canvas.subarray(offset),
|
||||
return jpeg.jpegEncode({
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
stride: displaySize.width,
|
||||
quality: gJpegQuality
|
||||
buffer: canvas.subarray(offset)
|
||||
});
|
||||
|
||||
// TODO: There's probably (definitely) a better way to fix this
|
||||
if (res == undefined) return Buffer.from([]);
|
||||
|
||||
// have to manually turn it back into a buffer because
|
||||
// Piscina for some reason turns it into a Uint8Array
|
||||
return Buffer.from(res);
|
||||
}
|
||||
|
||||
static async EncodeThumbnail(buffer: Buffer, size: Size) : Promise<Buffer> {
|
||||
let res = await TheThumbnailEncoderPool.run({
|
||||
buffer: buffer,
|
||||
size: size,
|
||||
quality: gJpegQuality
|
||||
});
|
||||
static async EncodeThumbnail(buffer: Buffer, size: Size): Promise<Buffer> {
|
||||
let { data, info } = await sharp(buffer, { raw: GetRawSharpOptions(size) })
|
||||
.resize(kThumbnailSize.width, kThumbnailSize.height, { fit: 'fill' })
|
||||
.raw()
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
|
||||
return Buffer.from(res)
|
||||
return jpeg.jpegEncode({
|
||||
width: kThumbnailSize.width,
|
||||
height: kThumbnailSize.height,
|
||||
stride: kThumbnailSize.width,
|
||||
buffer: data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import jpegTurbo from '@computernewb/jpeg-turbo';
|
||||
import Piscina from 'piscina';
|
||||
|
||||
export default async (opts: any) => {
|
||||
try {
|
||||
let res = await jpegTurbo.compress(opts.buffer, {
|
||||
format: jpegTurbo.FORMAT_RGBA,
|
||||
width: opts.width,
|
||||
height: opts.height,
|
||||
subsampling: jpegTurbo.SAMP_422,
|
||||
stride: opts.stride,
|
||||
quality: opts.quality || 75
|
||||
});
|
||||
|
||||
return Piscina.move(res);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
import { Size } from '@cvmts/shared';
|
||||
import Piscina from 'piscina';
|
||||
import sharp from 'sharp';
|
||||
|
||||
const kThumbnailSize: Size = {
|
||||
width: 400,
|
||||
height: 300
|
||||
};
|
||||
|
||||
// this returns appropiate Sharp options to deal with CVMTS raw framebuffers
|
||||
// (which are RGBA bitmaps, essentially. We probably should abstract that out but
|
||||
// that'd mean having to introduce that to rfb and oihwekjtgferklds;./tghnredsltg;erhds)
|
||||
function GetRawSharpOptions(size: Size): sharp.CreateRaw {
|
||||
return {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
channels: 4
|
||||
};
|
||||
}
|
||||
|
||||
export default async (opts: any) => {
|
||||
try {
|
||||
let out = await sharp(opts.buffer, { raw: GetRawSharpOptions(opts.size) })
|
||||
.resize(kThumbnailSize.width, kThumbnailSize.height, { fit: 'fill' })
|
||||
.jpeg({
|
||||
quality: opts.quality || 75
|
||||
})
|
||||
.toFormat('jpeg')
|
||||
.toBuffer();
|
||||
|
||||
return out;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user