Commit d1800d09 authored by Solly Ross's avatar Solly Ross

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.
parent 38781d93
...@@ -15,6 +15,14 @@ var Display; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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 () { ...@@ -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;
......
...@@ -91,7 +91,9 @@ var RFB; ...@@ -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,51 +1667,86 @@ var RFB; ...@@ -1665,51 +1667,86 @@ 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 (var y = 0; y < height; y++) {
var b, x, dp, sp;
for (y = 0; y < height; y++) { var yoffset = y * width;
var b; var ybitoffset = y * w;
for (x = 0; x < w1; x++) { var xoffset, targetbyte;
for (b = 7; b >= 0; b--) { for (x = 0; x < w1; x++) {
dp = (y * width + x * 8 + 7 - b) * 3; xoffset = yoffset + x * 8;
sp = (data[y * w + x] >> b & 1) * 3; targetbyte = data[ybitoffset + x];
dest[dp] = palette[sp]; for (b = 7; b >= 0; b--) {
dest[dp + 1] = palette[sp + 1]; dp = (xoffset + 7 - b) * 3;
dest[dp + 2] = palette[sp + 2]; sp = (targetbyte >> 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--) { xoffset = yoffset + x * 8;
dp = (y * width + x * 8 + 7 - b) * 3; 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; 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];
dest[dp + 3] = 255;
} }
} }
} else {
var total = width * height * 3; for (b = 7; b >= 8 - width % 8; b--) {
for (var i = 0, j = 0; i < total; i += 3, j++) { dp = (y * width + x * 8 + 7 - b) * 4;
sp = data[j] * 3; sp = (data[y * w + x] >> b & 1) * 3;
dest[i] = palette[sp]; dest[dp] = palette[sp];
dest[i + 1] = palette[sp + 1]; dest[dp + 1] = palette[sp + 1];
dest[i + 2] = palette[sp + 2]; dest[dp + 2] = palette[sp + 2];
dest[dp + 3] = 255;
} }
} }
return dest; return dest;
}.bind(this); }.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;
}.bind(this);
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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment