Browse Source

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.
Solly Ross 10 years ago
parent
commit
d1800d0960
3 changed files with 181 additions and 57 deletions
  1. 28 0
      include/display.js
  2. 1 1
      include/inflator.js
  3. 152 56
      include/rfb.js

+ 28 - 0
include/display.js

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

+ 1 - 1
include/inflator.js

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

+ 152 - 56
include/rfb.js

@@ -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;
                 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 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;
                             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 (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;
                 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;
                 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,
-                                                                                      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; }
                 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);
-                this._sock.rQskipBytes(clength[0]);
+                //var palette = this._sock.rQshiftBytes(paletteSize);
+                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));
-                    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; }
                     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();