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 () {
|
(function () {
|
||||||
"use strict";
|
"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) {
|
Display = function (defaults) {
|
||||||
this._drawCtx = null;
|
this._drawCtx = null;
|
||||||
this._c_forceCanvas = false;
|
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) {
|
blitStringImage: function (str, x, y) {
|
||||||
var img = new Image();
|
var img = new Image();
|
||||||
img.onload = function () {
|
img.onload = function () {
|
||||||
|
@ -612,6 +624,19 @@ var Display;
|
||||||
this._drawCtx.putImageData(img, x - vx, y - vy);
|
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) {
|
_cmapImageData: function (x, y, vx, vy, width, height, arr, offset) {
|
||||||
var img = this._drawCtx.createImageData(width, height);
|
var img = this._drawCtx.createImageData(width, height);
|
||||||
var data = img.data;
|
var data = img.data;
|
||||||
|
@ -643,6 +668,9 @@ var Display;
|
||||||
case 'blitRgb':
|
case 'blitRgb':
|
||||||
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0);
|
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0);
|
||||||
break;
|
break;
|
||||||
|
case 'blitRgbx':
|
||||||
|
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0);
|
||||||
|
break;
|
||||||
case 'img':
|
case 'img':
|
||||||
if (a.img.complete) {
|
if (a.img.complete) {
|
||||||
this.drawImage(a.img, a.x, a.y);
|
this.drawImage(a.img, a.x, a.y);
|
||||||
|
|
|
@ -2386,7 +2386,7 @@ var Inflate = function () {
|
||||||
|
|
||||||
Inflate.prototype = {
|
Inflate.prototype = {
|
||||||
inflate: function (data, flush) {
|
inflate: function (data, flush) {
|
||||||
this.strm.input = new Uint8Array(data);
|
this.strm.input = data;
|
||||||
this.strm.avail_in = this.strm.input.length;
|
this.strm.avail_in = this.strm.input.length;
|
||||||
this.strm.next_in = 0;
|
this.strm.next_in = 0;
|
||||||
this.strm.next_out = 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_width = 0;
|
||||||
this._fb_height = 0;
|
this._fb_height = 0;
|
||||||
this._fb_name = "";
|
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;
|
this._rre_chunk_sz = 100;
|
||||||
|
|
||||||
|
@ -1665,43 +1667,77 @@ var RFB;
|
||||||
return uncompressed;
|
return uncompressed;
|
||||||
}.bind(this);
|
}.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
|
// Convert indexed (palette based) image data to RGB
|
||||||
// TODO: reduce number of calculations inside loop
|
// TODO: reduce number of calculations inside loop
|
||||||
var dest = this._dest_buff;
|
var dest = this._destBuff;
|
||||||
var x, y, dp, sp;
|
var w = Math.floor((width + 7) / 8);
|
||||||
if (numColors === 2) {
|
var w1 = Math.floor(width / 8);
|
||||||
var w = Math.floor((width + 7) / 8);
|
|
||||||
var w1 = Math.floor(width / 8);
|
|
||||||
|
|
||||||
for (y = 0; y < height; y++) {
|
/*for (var y = 0; y < height; y++) {
|
||||||
var b;
|
var b, x, dp, sp;
|
||||||
for (x = 0; x < w1; x++) {
|
var yoffset = y * width;
|
||||||
for (b = 7; b >= 0; b--) {
|
var ybitoffset = y * w;
|
||||||
dp = (y * width + x * 8 + 7 - b) * 3;
|
var xoffset, targetbyte;
|
||||||
sp = (data[y * w + x] >> b & 1) * 3;
|
for (x = 0; x < w1; x++) {
|
||||||
dest[dp] = palette[sp];
|
xoffset = yoffset + x * 8;
|
||||||
dest[dp + 1] = palette[sp + 1];
|
targetbyte = data[ybitoffset + x];
|
||||||
dest[dp + 2] = palette[sp + 2];
|
for (b = 7; b >= 0; b--) {
|
||||||
}
|
dp = (xoffset + 7 - b) * 3;
|
||||||
}
|
sp = (targetbyte >> b & 1) * 3;
|
||||||
|
|
||||||
for (b = 7; b >= 8 - width % 8; b--) {
|
|
||||||
dp = (y * width + x * 8 + 7 - b) * 3;
|
|
||||||
sp = (data[y * w + x] >> b & 1) * 3;
|
|
||||||
dest[dp] = palette[sp];
|
dest[dp] = palette[sp];
|
||||||
dest[dp + 1] = palette[sp + 1];
|
dest[dp + 1] = palette[sp + 1];
|
||||||
dest[dp + 2] = palette[sp + 2];
|
dest[dp + 2] = palette[sp + 2];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
var total = width * height * 3;
|
xoffset = yoffset + x * 8;
|
||||||
for (var i = 0, j = 0; i < total; i += 3, j++) {
|
targetbyte = data[ybitoffset + x];
|
||||||
sp = data[j] * 3;
|
for (b = 7; b >= 8 - width % 8; b--) {
|
||||||
dest[i] = palette[sp];
|
dp = (xoffset + 7 - b) * 3;
|
||||||
dest[i + 1] = palette[sp + 1];
|
sp = (targetbyte >> b & 1) * 3;
|
||||||
dest[i + 2] = palette[sp + 2];
|
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;
|
return dest;
|
||||||
|
@ -1709,7 +1745,8 @@ var RFB;
|
||||||
|
|
||||||
var rQ = this._sock.get_rQ();
|
var rQ = this._sock.get_rQ();
|
||||||
var rQi = this._sock.get_rQi();
|
var rQi = this._sock.get_rQi();
|
||||||
var cmode, clength, data;
|
var cmode, data;
|
||||||
|
var cl_header, cl_data;
|
||||||
|
|
||||||
var handlePalette = function () {
|
var handlePalette = function () {
|
||||||
var numColors = rQ[rQi + 2] + 1;
|
var numColors = rQ[rQi + 2] + 1;
|
||||||
|
@ -1722,37 +1759,69 @@ var RFB;
|
||||||
var raw = false;
|
var raw = false;
|
||||||
if (rowSize * this._FBU.height < 12) {
|
if (rowSize * this._FBU.height < 12) {
|
||||||
raw = true;
|
raw = true;
|
||||||
clength = [0, rowSize * this._FBU.height];
|
cl_header = 0;
|
||||||
|
cl_data = rowSize * this._FBU.height;
|
||||||
|
//clength = [0, rowSize * this._FBU.height];
|
||||||
} else {
|
} else {
|
||||||
clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(3 + paletteSize,
|
// begin inline getTightCLength (returning two-item arrays is bad for performance with GC)
|
||||||
3 + paletteSize + 3));
|
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; }
|
if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
|
||||||
|
|
||||||
// Shift ctl, filter id, num colors, palette entries, and clength off
|
// Shift ctl, filter id, num colors, palette entries, and clength off
|
||||||
this._sock.rQskipBytes(3);
|
this._sock.rQskipBytes(3);
|
||||||
var palette = this._sock.rQshiftBytes(paletteSize);
|
//var palette = this._sock.rQshiftBytes(paletteSize);
|
||||||
this._sock.rQskipBytes(clength[0]);
|
this._sock.rQshiftTo(this._paletteBuff, paletteSize);
|
||||||
|
this._sock.rQskipBytes(cl_header);
|
||||||
|
|
||||||
if (raw) {
|
if (raw) {
|
||||||
data = this._sock.rQshiftBytes(clength[1]);
|
data = this._sock.rQshiftBytes(cl_data);
|
||||||
} else {
|
} else {
|
||||||
data = decompress(this._sock.rQshiftBytes(clength[1]));
|
data = decompress(this._sock.rQshiftBytes(cl_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert indexed (palette based) image data to RGB
|
// 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;
|
return true;
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
@ -1762,20 +1831,34 @@ var RFB;
|
||||||
var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth;
|
var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth;
|
||||||
if (uncompressedSize < 12) {
|
if (uncompressedSize < 12) {
|
||||||
raw = true;
|
raw = true;
|
||||||
clength = [0, uncompressedSize];
|
cl_header = 0;
|
||||||
|
cl_data = uncompressedSize;
|
||||||
} else {
|
} 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; }
|
if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
|
||||||
|
|
||||||
// Shift ctl, clength off
|
// Shift ctl, clength off
|
||||||
this._sock.rQshiftBytes(1 + clength[0]);
|
this._sock.rQshiftBytes(1 + cl_header);
|
||||||
|
|
||||||
if (raw) {
|
if (raw) {
|
||||||
data = this._sock.rQshiftBytes(clength[1]);
|
data = this._sock.rQshiftBytes(cl_data);
|
||||||
} else {
|
} else {
|
||||||
data = decompress(this._sock.rQshiftBytes(clength[1]));
|
data = decompress(this._sock.rQshiftBytes(cl_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
this._display.renderQ_push({
|
this._display.renderQ_push({
|
||||||
|
@ -1846,15 +1929,28 @@ var RFB;
|
||||||
break;
|
break;
|
||||||
case "png":
|
case "png":
|
||||||
case "jpeg":
|
case "jpeg":
|
||||||
clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
|
// begin inline getTightCLength (returning two-item arrays is for peformance with GC)
|
||||||
this._FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
|
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; }
|
if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
|
||||||
|
|
||||||
// We have everything, render it
|
// 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();
|
var img = new Image();
|
||||||
img.src = "data: image/" + cmode +
|
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({
|
this._display.renderQ_push({
|
||||||
'type': 'img',
|
'type': 'img',
|
||||||
'img': img,
|
'img': img,
|
||||||
|
@ -1897,7 +1993,7 @@ var RFB;
|
||||||
handle_FB_resize: function () {
|
handle_FB_resize: function () {
|
||||||
this._fb_width = this._FBU.width;
|
this._fb_width = this._FBU.width;
|
||||||
this._fb_height = this._FBU.height;
|
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._display.resize(this._fb_width, this._fb_height);
|
||||||
this._onFBResize(this, this._fb_width, this._fb_height);
|
this._onFBResize(this, this._fb_width, this._fb_height);
|
||||||
this._timing.fbu_rt_start = (new Date()).getTime();
|
this._timing.fbu_rt_start = (new Date()).getTime();
|
||||||
|
|
Loading…
Reference in New Issue