diff options
author | Solly Ross <sross@redhat.com> | 2015-05-28 15:27:40 -0400 |
---|---|---|
committer | Solly Ross <sross@redhat.com> | 2015-08-06 14:47:03 -0400 |
commit | d1800d0960ad9c3d2ff20c990f5886d2edf7f514 (patch) | |
tree | 3c19e0d01d7b8504c3bb72a0e8f9ac55e75ccaab | |
parent | 38781d931ec18304f51ed3469faff8387e3cbc55 (diff) | |
download | novnc-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.js | 28 | ||||
-rw-r--r-- | include/inflator.js | 2 | ||||
-rw-r--r-- | include/rfb.js | 208 |
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(); |