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;
(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;
......
......@@ -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 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 (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 = (y * width + x * 8 + 7 - b) * 3;
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];
}
}
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) * 3;
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;
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': 'blitRgb',
'data': rgb,
/*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);
}
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));
}
this._FBU.bytes = 1 + clength[0] + clength[1];
// 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;
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();
......
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