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:
Solly Ross 2015-05-28 15:27:40 -04:00
parent 38781d931e
commit d1800d0960
3 changed files with 183 additions and 59 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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();