summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSolly Ross <sross@redhat.com>2015-05-28 15:27:40 -0400
committerSolly Ross <sross@redhat.com>2015-08-06 14:47:03 -0400
commitd1800d0960ad9c3d2ff20c990f5886d2edf7f514 (patch)
tree3c19e0d01d7b8504c3bb72a0e8f9ac55e75ccaab
parent38781d931ec18304f51ed3469faff8387e3cbc55 (diff)
downloadnovnc-d1800d0960ad9c3d2ff20c990f5886d2edf7f514.tar.gz
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.
-rw-r--r--include/display.js28
-rw-r--r--include/inflator.js2
-rw-r--r--include/rfb.js208
3 files changed, 181 insertions, 57 deletions
diff --git a/include/display.js b/include/display.js
index 8994856..418b431 100644
--- a/include/display.js
+++ b/include/display.js
@@ -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);
diff --git a/include/inflator.js b/include/inflator.js
index 68f85cb..a9c75a6 100644
--- a/include/inflator.js
+++ b/include/inflator.js
@@ -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;
diff --git a/include/rfb.js b/include/rfb.js
index 113d941..e2dd42f 100644
--- a/include/rfb.js
+++ b/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,51 +1667,86 @@ 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);
-
- 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];
- }
+ var dest = this._destBuff;
+ 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;
+ 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];
}
+ }
- for (b = 7; b >= 8 - width % 8; b--) {
- dp = (y * width + x * 8 + 7 - b) * 3;
+ 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;
}
}
- } 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];
+
+ 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;
+ }.bind(this);
+
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();