レンダリング速度向上のためにやったこと

uupaa.js / uuCanvas.js / uuAltCSS.js コードリード用のエントリです。

uuCanvas.js のコードを眺めてて、

// CanvasRenderingContext2D.prototype.fill
function fill(wire, path) {
  var fg = ""; // fragment
    :
    :
  this._elm.insertAdjacentHTML("BeforeEnd", fg);
}

とかやってることに気が付きました。
# insertAdjacentHTML("BeforeEnd", ...) は 文字列から要素を作成して appendChild する JScript 独自メソッド

fill() するたびに、DOMツリーにちょこちょこ要素を追加しているし、そのつど再描画が発生するので2重に遅くなるはず。

DOM アクセスや再描画は一つの実行単位内に纏めるべき

ctx.lock() と ctx.unlock() のようにスクリーンへの描画をロックするメソッドを追加し、どうなるか試してみました。

// こちらは prototype にメソッドを追加
uu.mix(uu.canvas.VML2D.prototype, {
  lock:             lock,       // ctx.lock(clearScreen = 0)
  unlock:           unlock,     // ctx.unlock()
});

// こちらは ctx の初期化処理に追加
  ctx._lockstock = []; // lock stock
  ctx._lockstate = 0;  // lock state, 0: unlock, 1: lock, 2: lock + clear
// fill() で DOM に書き込まないようにちょっと変更
// CanvasRenderingContext2D.prototype.fill
function fill(wire, path) {
  var fg = ""; // fragment
    :
//this._elm.insertAdjacentHTML("BeforeEnd", fg);
  this._lockstate ? this._lockstock.push(fg)
                  : this._elm.insertAdjacentHTML("BeforeEnd", fg);
}

// lock() と unlock() を実装
// CanvasRenderingContext2D.prototype.lock
function lock(clearScreen) { // @param Boolean(= false):
  if (this._lockstate) {
    throw "duplicate lock";
  }
  this._lockstate = clearScreen ? 2 : 1;
}

// CanvasRenderingContext2D.prototype.unlock
function unlock() {
  if (this._lockstate) {
    (this._lockstate === 2) && _clear(this);
    if (this._lockstock.length) {
      this._elm.insertAdjacentHTML("BeforeEnd", this._lockstock.join(""));
    }
  }
  this._lockstock = [];
  this._lockstate = 0;
}

使い方(How to use)

2D Canvas に書き込んでいる処理の前後に、ctx.lock(), ctx.unlock() を追加し、clearRect() を削ります。

// 変更前
function drawLoop() {
  ctx.clearRect(0, 0, canvasWidth, canvasHeight);

  scene.camera.x = 70*Math.sin(count);
  scene.camera.y = 70;
  scene.camera.z = 70*Math.cos(count);
  scene.cameraRotation = count / 10;

  count += 0.01;
  scene.draw(); // この中で描画
}
// 変更後
function drawLoop() {
//  ctx.clearRect(0, 0, canvasWidth, canvasHeight);

  ctx.lock(1);

  scene.camera.x = 70*Math.sin(count);
  scene.camera.y = 70;
  scene.camera.z = 70*Math.cos(count);
  scene.cameraRotation = count / 10;

  count += 0.01;
  scene.draw(); // この中で描画

  ctx.unlock();
}

この修正により、excanvas.js と比べ、描画速度が120〜140%に向上しました(in IE6 VML MODE)。

説明

  • ctx.lock() は、スクリーンへの書き込みをロックし、ctx.unlock() で纏めて描画します。
  • ctx.lock(1) とすると ctx.unlock() が呼ばれたタイミングで、描画結果をクリアしてからスクリーンに書き込みます。

ctx.lock(), ctx.unlock() は uuCanvas.js がサポートするブラウザ(IE6+, Opera9.5+, Safari3.1+, Google Chrome2+, Firefox2+)で利用可能ですが、描画速度が向上するのは、IE だけです。

uuAltCSS.js も uuCanvas.js の機能を利用しています

uuAltCSS.js で、 -uu-box-shadow: blue 0px 0px 5px; 等とした場合の擬似的な影のレンダリングがとても重いため、uuAltCSS.js 専用の I/F を uuCanvas.js に追加しました。

// CanvasRenderingContext2D.prototype.qstroke - quick stroke
function qstroke(hexcolor, alpha, lineWidth) {
  var fg = '<v:shape style="position:absolute;width:10px;height:10px' +
            '" filled="f" stroked="t" coordsize="100,100" path="' +
            this._path.join("") + '"><v:stroke color="' + hexcolor +
            '" opacity="' + alpha.toFixed(2) +
            '" weight="' + lineWidth + 'px" /></v:shape>';

  this._lockstate ? this._lockstock.push(fg)
                  : this._elm.insertAdjacentHTML("BeforeEnd", fg);
}

// CanvasRenderingContext2D.prototype.qstrokeRect
function qstrokeRect(x, y, w, h, hexcolor, alpha, lineWidth) {
  var hm = _HALF_ZOOM,
      ix = x * _ZOOM,
      iy = y * _ZOOM,
      iw = (x + w) * _ZOOM,
      ih = (y + h) * _ZOOM;

  this._path = ["m " + (ix - hm) + " " + (iy - hm) +
                "l " + (ix - hm) + " " + (ih - hm) +
                "l " + (iw - hm) + " " + (ih - hm) +
                "l " + (iw - hm) + " " + (iy - hm) +
                "l " + (ix - hm) + " " + (iy - hm) + "x"];
  this.qstroke(hexcolor, alpha, lineWidth);
}

uuAltCSS.js 側のコードにも最適化を加えました。

function drawFakeShadowIE(ctx, x, y, width, height,
                          rgba, blur, radius) {
  var i = 0, j = 0, k, step = 1, line = 5, r = radius,
      hexcolor = uu.color.hex(rgba);

  if (uu.ie6 && uu.light) {
    step *= 3, line *= 2.5;
  }

  ctx.lock();
  if (r[0] === 0 && (r[0] === r[1] && r[0] === r[2] && r[0] === r[3])) {
    for (; i < blur; i += step) {
      k = i / blur;
      ctx.qstrokeRect(x + i, y + i, width - (i * 2), height - (i * 2),
                      hexcolor, k * k * k, line);
    }
  } else {
    for (; i < blur; i += step) {
      k = i / blur;
      j += 0.5;
      boxpath(ctx, x + i, y + i, width - (i * 2), height - (i * 2),
              [r[0] - j, r[1] - j, r[2] - j, r[3] - j]);
      ctx.qstroke(hexcolor, k * k * k, line);
    }
  }
  ctx.unlock();
}

これらの最適化を加えたことにより、ページ読み込み時のレンダリングのもたつきが見えなくなり、画面リサイズ時にもキビキビと動作するようになりました。

これらの変更は

uupaa.js version 0.7 用のリポジトリに対して行っています。

spin-off プロジェクトの uuCanvas.js や uuAltCSS.js には、まだ反映していません。