diff --git a/include/display.js b/include/display.js index 1fe883cc..8252ef83 100644 --- a/include/display.js +++ b/include/display.js @@ -19,9 +19,12 @@ var that = {}, // Public API methods c_ctx = null, c_forceCanvas = false, + // Queued drawing actions for in-order rendering + renderQ = [], + // Predefine function variables (jslint) imageDataGet, rgbImageData, bgrxImageData, cmapImageData, - setFillColor, rescale, + setFillColor, rescale, scan_renderQ, // The full frame buffer (logical canvas) size fb_width = 0, @@ -412,6 +415,8 @@ that.clear = function() { c_ctx.clearRect(0, 0, viewport.w, viewport.h); } + renderQ = []; + // No benefit over default ("source-over") in Chrome and firefox //c_ctx.globalCompositeOperation = "copy"; }; @@ -582,6 +587,50 @@ that.drawImage = function(img, x, y) { c_ctx.drawImage(img, x - viewport.x, y - viewport.y); }; +that.renderQ_push = function(action) { + renderQ.push(action); + if (renderQ.length === 1) { + // Check if it can be rendered immediately + scan_renderQ(); + } +}; + +scan_renderQ = function() { + var a, ready = true; + while (ready && renderQ.length > 0) { + a = renderQ[0]; + switch (a.type) { + case 'copy': + that.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height); + break; + case 'fill': + that.fillRect(a.x, a.y, a.width, a.height, a.color); + break; + case 'blit': + that.blitImage(a.x, a.y, a.width, a.height, a.data, 0); + break; + case 'blitRgb': + that.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0); + break; + case 'img': + if (a.img.complete) { + that.drawImage(a.img, a.x, a.y); + } else { + // We need to wait for this image to 'load' + // to keep things in-order + ready = false; + } + break; + } + if (ready) { + a = renderQ.shift(); + } + } + if (renderQ.length > 0) { + requestAnimFrame(scan_renderQ); + } +}; + that.changeCursor = function(pixels, mask, hotx, hoty, w, h) { if (conf.cursor_uri === false) { diff --git a/include/rfb.js b/include/rfb.js index fea2fcf0..f80d190e 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -26,7 +26,7 @@ var that = {}, // Public API methods pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests, keyEvent, pointerEvent, clientCutText, - getTightCLength, extract_data_uri, scan_tight_imgQ, + getTightCLength, extract_data_uri, keyPress, mouseButton, mouseMove, checkEvents, // Overridable for testing @@ -93,7 +93,6 @@ var that = {}, // Public API methods encoding : 0, subencoding : -1, background : null, - imgQ : [], // TIGHT_PNG image queue zlibs : [] // TIGHT zlib streams }, @@ -103,7 +102,6 @@ var that = {}, // Public API methods fb_height = 0, fb_name = "", - scan_imgQ_rate = 40, // 25 times per second or so last_req_time = 0, rre_chunk_sz = 100, @@ -314,7 +312,6 @@ init_vars = function() { FBU.subrects = 0; // RRE and HEXTILE FBU.lines = 0; // RAW FBU.tiles = 0; // HEXTILE - FBU.imgQ = []; // TIGHT_PNG image queue FBU.zlibs = []; // TIGHT zlib encoders mouse_buttonMask = 0; mouse_arr = []; @@ -888,7 +885,6 @@ init_msg = function() { /* Start pushing/polling */ setTimeout(checkEvents, conf.check_rate); - setTimeout(scan_tight_imgQ, scan_imgQ_rate); if (conf.encrypt) { updateState('normal', "Connected (encrypted) to: " + fb_name); @@ -1409,9 +1405,9 @@ function display_tight(isTightPNG) { } } - FBU.imgQ.push({ - 'type': 'rgb', - 'img': {'complete': true, 'data': dest}, + display.renderQ_push({ + 'type': 'blitRgb', + 'data': dest, 'x': FBU.x, 'y': FBU.y, 'width': FBU.width, @@ -1440,9 +1436,9 @@ function display_tight(isTightPNG) { data = decompress(ws.rQshiftBytes(clength[1])); } - FBU.imgQ.push({ - 'type': 'rgb', - 'img': {'complete': true, 'data': data}, + display.renderQ_push({ + 'type': 'blitRgb', + 'data': data, 'x': FBU.x, 'y': FBU.y, 'width': FBU.width, @@ -1489,9 +1485,8 @@ function display_tight(isTightPNG) { case "fill": ws.rQshift8(); // shift off ctl color = ws.rQshiftBytes(fb_depth); - FBU.imgQ.push({ + display.renderQ_push({ 'type': 'fill', - 'img': {'complete': true}, 'x': FBU.x, 'y': FBU.y, 'width': FBU.width, @@ -1509,14 +1504,13 @@ function display_tight(isTightPNG) { // clength[0] + ", clength[1]: " + clength[1]); ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length img = new Image(); - //img.onload = scan_tight_imgQ; - FBU.imgQ.push({ + img.src = "data:image/" + cmode + + extract_data_uri(ws.rQshiftBytes(clength[1])); + display.renderQ_push({ 'type': 'img', 'img': img, 'x': FBU.x, 'y': FBU.y}); - img.src = "data:image/" + cmode + - extract_data_uri(ws.rQshiftBytes(clength[1])); img = null; break; case "filter": @@ -1550,25 +1544,6 @@ extract_data_uri = function(arr) { return ";base64," + Base64.encode(arr); }; -scan_tight_imgQ = function() { - var data, imgQ, ctx; - ctx = display.get_context(); - if (rfb_state === 'normal') { - imgQ = FBU.imgQ; - while ((imgQ.length > 0) && (imgQ[0].img.complete)) { - data = imgQ.shift(); - if (data.type === 'fill') { - display.fillRect(data.x, data.y, data.width, data.height, data.color); - } else if (data.type === 'rgb') { - display.blitRgbImage(data.x, data.y, data.width, data.height, data.img.data, 0); - } else { - display.drawImage(data.img, data.x, data.y); - } - } - setTimeout(scan_tight_imgQ, scan_imgQ_rate); - } -}; - encHandlers.TIGHT = function () { return display_tight(false); }; encHandlers.TIGHT_PNG = function () { return display_tight(true); }; diff --git a/include/util.js b/include/util.js index ddc1914c..2c0e4ed5 100644 --- a/include/util.js +++ b/include/util.js @@ -57,6 +57,21 @@ if (!Array.prototype.map) }; } +// +// requestAnimationFrame shim with setTimeout fallback +// + +window.requestAnimFrame = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback){ + window.setTimeout(callback, 1000 / 60); + }; +})(); + /* * ------------------------------------------------------ * Namespaced in Util