Avoid Creating Small Objects Frequently
Creating lots of small objects frequently can drastically decrease performance. This commit introduces three fixes which avoid this: - Use a preallocated palette and indexed-to-rgb destination Typed Array (the destination typed array is currently allocated at `4 * width * height`). - Inline `getTightCLength`, which returned a two-item array. - Pass RGBX data directly in a Typed Array to the Display, which avoids an extra loop, and only creates a new Typed Array View, instead of a whole new ArrayBuffer.
This commit is contained in:
parent
38781d931e
commit
d1800d0960
|
@ -15,6 +15,14 @@ var Display;
|
|||
(function () {
|
||||
"use strict";
|
||||
|
||||
var SUPPORTS_IMAGEDATA_CONSTRUCTOR = false;
|
||||
try {
|
||||
new ImageData(new Uint8ClampedArray(1), 1, 1);
|
||||
SUPPORTS_IMAGEDATA_CONSTRUCTOR = true;
|
||||
} catch (ex) {
|
||||
// ignore failure
|
||||
}
|
||||
|
||||
Display = function (defaults) {
|
||||
this._drawCtx = null;
|
||||
this._c_forceCanvas = false;
|
||||
|
@ -435,6 +443,10 @@ var Display;
|
|||
}
|
||||
},
|
||||
|
||||
blitRgbxImage: function (x, y, width, height, arr, offset) {
|
||||
this._rgbxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
|
||||
},
|
||||
|
||||
blitStringImage: function (str, x, y) {
|
||||
var img = new Image();
|
||||
img.onload = function () {
|
||||
|
@ -612,6 +624,19 @@ var Display;
|
|||
this._drawCtx.putImageData(img, x - vx, y - vy);
|
||||
},
|
||||
|
||||
_rgbxImageData: function (x, y, vx, vy, width, height, arr, offset) {
|
||||
// NB(directxman12): arr must be an Type Array view
|
||||
// NB(directxman12): this only works
|
||||
var img;
|
||||
if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
|
||||
img = new ImageData(new Uint8ClampedArray(arr.buffer, 0, width * height * 4), width, height);
|
||||
} else {
|
||||
img = this._drawCtx.createImageData(width, height);
|
||||
img.data.set(new Uint8ClampedArray(arr.buffer, 0, width * height * 4));
|
||||
}
|
||||
this._drawCtx.putImageData(img, x - vx, y - vy);
|
||||
},
|
||||
|
||||
_cmapImageData: function (x, y, vx, vy, width, height, arr, offset) {
|
||||
var img = this._drawCtx.createImageData(width, height);
|
||||
var data = img.data;
|
||||
|
@ -643,6 +668,9 @@ var Display;
|
|||
case 'blitRgb':
|
||||
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0);
|
||||
break;
|
||||
case 'blitRgbx':
|
||||
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0);
|
||||
break;
|
||||
case 'img':
|
||||
if (a.img.complete) {
|
||||
this.drawImage(a.img, a.x, a.y);
|
||||
|
|
|
@ -2386,7 +2386,7 @@ var Inflate = function () {
|
|||
|
||||
Inflate.prototype = {
|
||||
inflate: function (data, flush) {
|
||||
this.strm.input = new Uint8Array(data);
|
||||
this.strm.input = data;
|
||||
this.strm.avail_in = this.strm.input.length;
|
||||
this.strm.next_in = 0;
|
||||
this.strm.next_out = 0;
|
||||
|
|
212
include/rfb.js
212
include/rfb.js
|
@ -91,7 +91,9 @@ var RFB;
|
|||
this._fb_width = 0;
|
||||
this._fb_height = 0;
|
||||
this._fb_name = "";
|
||||
this._dest_buff = null;
|
||||
|
||||
this._destBuff = null;
|
||||
this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
|
||||
|
||||
this._rre_chunk_sz = 100;
|
||||
|
||||
|
@ -1665,43 +1667,77 @@ var RFB;
|
|||
return uncompressed;
|
||||
}.bind(this);
|
||||
|
||||
var indexedToRGB = function (data, numColors, palette, width, height) {
|
||||
var indexedToRGBX2Color = function (data, palette, width, height) {
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
// TODO: reduce number of calculations inside loop
|
||||
var dest = this._dest_buff;
|
||||
var x, y, dp, sp;
|
||||
if (numColors === 2) {
|
||||
var w = Math.floor((width + 7) / 8);
|
||||
var w1 = Math.floor(width / 8);
|
||||
var dest = this._destBuff;
|
||||
var w = Math.floor((width + 7) / 8);
|
||||
var w1 = Math.floor(width / 8);
|
||||
|
||||
for (y = 0; y < height; y++) {
|
||||
var b;
|
||||
for (x = 0; x < w1; x++) {
|
||||
for (b = 7; b >= 0; b--) {
|
||||
dp = (y * width + x * 8 + 7 - b) * 3;
|
||||
sp = (data[y * w + x] >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
}
|
||||
}
|
||||
|
||||
for (b = 7; b >= 8 - width % 8; b--) {
|
||||
dp = (y * width + x * 8 + 7 - b) * 3;
|
||||
sp = (data[y * w + x] >> b & 1) * 3;
|
||||
/*for (var y = 0; y < height; y++) {
|
||||
var b, x, dp, sp;
|
||||
var yoffset = y * width;
|
||||
var ybitoffset = y * w;
|
||||
var xoffset, targetbyte;
|
||||
for (x = 0; x < w1; x++) {
|
||||
xoffset = yoffset + x * 8;
|
||||
targetbyte = data[ybitoffset + x];
|
||||
for (b = 7; b >= 0; b--) {
|
||||
dp = (xoffset + 7 - b) * 3;
|
||||
sp = (targetbyte >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var total = width * height * 3;
|
||||
for (var i = 0, j = 0; i < total; i += 3, j++) {
|
||||
sp = data[j] * 3;
|
||||
dest[i] = palette[sp];
|
||||
dest[i + 1] = palette[sp + 1];
|
||||
dest[i + 2] = palette[sp + 2];
|
||||
|
||||
xoffset = yoffset + x * 8;
|
||||
targetbyte = data[ybitoffset + x];
|
||||
for (b = 7; b >= 8 - width % 8; b--) {
|
||||
dp = (xoffset + 7 - b) * 3;
|
||||
sp = (targetbyte >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
}
|
||||
}*/
|
||||
|
||||
for (var y = 0; y < height; y++) {
|
||||
var b, x, dp, sp;
|
||||
for (x = 0; x < w1; x++) {
|
||||
for (b = 7; b >= 0; b--) {
|
||||
dp = (y * width + x * 8 + 7 - b) * 4;
|
||||
sp = (data[y * w + x] >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
dest[dp + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
for (b = 7; b >= 8 - width % 8; b--) {
|
||||
dp = (y * width + x * 8 + 7 - b) * 4;
|
||||
sp = (data[y * w + x] >> b & 1) * 3;
|
||||
dest[dp] = palette[sp];
|
||||
dest[dp + 1] = palette[sp + 1];
|
||||
dest[dp + 2] = palette[sp + 2];
|
||||
dest[dp + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
return dest;
|
||||
}.bind(this);
|
||||
|
||||
var indexedToRGBX = function (data, palette, width, height) {
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
var dest = this._destBuff;
|
||||
var total = width * height * 4;
|
||||
for (var i = 0, j = 0; i < total; i += 4, j++) {
|
||||
var sp = data[j] * 3;
|
||||
dest[i] = palette[sp];
|
||||
dest[i + 1] = palette[sp + 1];
|
||||
dest[i + 2] = palette[sp + 2];
|
||||
dest[i + 3] = 255;
|
||||
}
|
||||
|
||||
return dest;
|
||||
|
@ -1709,7 +1745,8 @@ var RFB;
|
|||
|
||||
var rQ = this._sock.get_rQ();
|
||||
var rQi = this._sock.get_rQi();
|
||||
var cmode, clength, data;
|
||||
var cmode, data;
|
||||
var cl_header, cl_data;
|
||||
|
||||
var handlePalette = function () {
|
||||
var numColors = rQ[rQi + 2] + 1;
|
||||
|
@ -1722,37 +1759,69 @@ var RFB;
|
|||
var raw = false;
|
||||
if (rowSize * this._FBU.height < 12) {
|
||||
raw = true;
|
||||
clength = [0, rowSize * this._FBU.height];
|
||||
cl_header = 0;
|
||||
cl_data = rowSize * this._FBU.height;
|
||||
//clength = [0, rowSize * this._FBU.height];
|
||||
} else {
|
||||
clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(3 + paletteSize,
|
||||
3 + paletteSize + 3));
|
||||
// begin inline getTightCLength (returning two-item arrays is bad for performance with GC)
|
||||
var cl_offset = rQi + 3 + paletteSize;
|
||||
cl_header = 1;
|
||||
cl_data = 0;
|
||||
cl_data += rQ[cl_offset] & 0x7f;
|
||||
if (rQ[cl_offset] & 0x80) {
|
||||
cl_header++;
|
||||
cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
|
||||
if (rQ[cl_offset + 1] & 0x80) {
|
||||
cl_header++;
|
||||
cl_data += rQ[cl_offset + 2] << 14;
|
||||
}
|
||||
}
|
||||
// end inline getTightCLength
|
||||
}
|
||||
|
||||
this._FBU.bytes += clength[0] + clength[1];
|
||||
this._FBU.bytes += cl_header + cl_data;
|
||||
if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
|
||||
|
||||
// Shift ctl, filter id, num colors, palette entries, and clength off
|
||||
this._sock.rQskipBytes(3);
|
||||
var palette = this._sock.rQshiftBytes(paletteSize);
|
||||
this._sock.rQskipBytes(clength[0]);
|
||||
//var palette = this._sock.rQshiftBytes(paletteSize);
|
||||
this._sock.rQshiftTo(this._paletteBuff, paletteSize);
|
||||
this._sock.rQskipBytes(cl_header);
|
||||
|
||||
if (raw) {
|
||||
data = this._sock.rQshiftBytes(clength[1]);
|
||||
data = this._sock.rQshiftBytes(cl_data);
|
||||
} else {
|
||||
data = decompress(this._sock.rQshiftBytes(clength[1]));
|
||||
data = decompress(this._sock.rQshiftBytes(cl_data));
|
||||
}
|
||||
|
||||
// Convert indexed (palette based) image data to RGB
|
||||
var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height);
|
||||
var rgbx;
|
||||
if (numColors == 2) {
|
||||
rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height);
|
||||
|
||||
/*this._display.renderQ_push({
|
||||
'type': 'blitRgbx',
|
||||
'data': rgbx,
|
||||
'x': this._FBU.x,
|
||||
'y': this._FBU.y,
|
||||
'width': this._FBU.width,
|
||||
'height': this._FBU.height
|
||||
});*/
|
||||
this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0);
|
||||
} else {
|
||||
rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height);
|
||||
|
||||
/*this._display.renderQ_push({
|
||||
'type': 'blitRgbx',
|
||||
'data': rgbx,
|
||||
'x': this._FBU.x,
|
||||
'y': this._FBU.y,
|
||||
'width': this._FBU.width,
|
||||
'height': this._FBU.height
|
||||
});*/
|
||||
this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0);
|
||||
}
|
||||
|
||||
this._display.renderQ_push({
|
||||
'type': 'blitRgb',
|
||||
'data': rgb,
|
||||
'x': this._FBU.x,
|
||||
'y': this._FBU.y,
|
||||
'width': this._FBU.width,
|
||||
'height': this._FBU.height
|
||||
});
|
||||
|
||||
return true;
|
||||
}.bind(this);
|
||||
|
@ -1762,20 +1831,34 @@ var RFB;
|
|||
var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth;
|
||||
if (uncompressedSize < 12) {
|
||||
raw = true;
|
||||
clength = [0, uncompressedSize];
|
||||
cl_header = 0;
|
||||
cl_data = uncompressedSize;
|
||||
} else {
|
||||
clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
|
||||
// begin inline getTightCLength (returning two-item arrays is for peformance with GC)
|
||||
var cl_offset = rQi + 1;
|
||||
cl_header = 1;
|
||||
cl_data = 0;
|
||||
cl_data += rQ[cl_offset] & 0x7f;
|
||||
if (rQ[cl_offset] & 0x80) {
|
||||
cl_header++;
|
||||
cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
|
||||
if (rQ[cl_offset + 1] & 0x80) {
|
||||
cl_header++;
|
||||
cl_data += rQ[cl_offset + 2] << 14;
|
||||
}
|
||||
}
|
||||
// end inline getTightCLength
|
||||
}
|
||||
this._FBU.bytes = 1 + clength[0] + clength[1];
|
||||
this._FBU.bytes = 1 + cl_header + cl_data;
|
||||
if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
|
||||
|
||||
// Shift ctl, clength off
|
||||
this._sock.rQshiftBytes(1 + clength[0]);
|
||||
this._sock.rQshiftBytes(1 + cl_header);
|
||||
|
||||
if (raw) {
|
||||
data = this._sock.rQshiftBytes(clength[1]);
|
||||
data = this._sock.rQshiftBytes(cl_data);
|
||||
} else {
|
||||
data = decompress(this._sock.rQshiftBytes(clength[1]));
|
||||
data = decompress(this._sock.rQshiftBytes(cl_data));
|
||||
}
|
||||
|
||||
this._display.renderQ_push({
|
||||
|
@ -1846,15 +1929,28 @@ var RFB;
|
|||
break;
|
||||
case "png":
|
||||
case "jpeg":
|
||||
clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
|
||||
this._FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
|
||||
// begin inline getTightCLength (returning two-item arrays is for peformance with GC)
|
||||
var cl_offset = rQi + 1;
|
||||
cl_header = 1;
|
||||
cl_data = 0;
|
||||
cl_data += rQ[cl_offset] & 0x7f;
|
||||
if (rQ[cl_offset] & 0x80) {
|
||||
cl_header++;
|
||||
cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
|
||||
if (rQ[cl_offset + 1] & 0x80) {
|
||||
cl_header++;
|
||||
cl_data += rQ[cl_offset + 2] << 14;
|
||||
}
|
||||
}
|
||||
// end inline getTightCLength
|
||||
this._FBU.bytes = 1 + cl_header + cl_data; // ctl + clength size + jpeg-data
|
||||
if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
|
||||
|
||||
// We have everything, render it
|
||||
this._sock.rQskipBytes(1 + clength[0]); // shift off clt + compact length
|
||||
this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length
|
||||
var img = new Image();
|
||||
img.src = "data: image/" + cmode +
|
||||
RFB.extract_data_uri(this._sock.rQshiftBytes(clength[1]));
|
||||
RFB.extract_data_uri(this._sock.rQshiftBytes(cl_data));
|
||||
this._display.renderQ_push({
|
||||
'type': 'img',
|
||||
'img': img,
|
||||
|
@ -1897,7 +1993,7 @@ var RFB;
|
|||
handle_FB_resize: function () {
|
||||
this._fb_width = this._FBU.width;
|
||||
this._fb_height = this._FBU.height;
|
||||
this._dest_buff = new Uint8Array(this._fb_width * this._fb_height * 4);
|
||||
this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
|
||||
this._display.resize(this._fb_width, this._fb_height);
|
||||
this._onFBResize(this, this._fb_width, this._fb_height);
|
||||
this._timing.fbu_rt_start = (new Date()).getTime();
|
||||
|
|
Loading…
Reference in New Issue