上のように、スペクトラムのウォーターフォール表示ではよく下に1行追記して全体を上にスクロールさせていくみたいな見せかたをするが、全体の再描画が必須なため、かなり描画負荷が高い処理となる。
いくつか実装を書いてどのぐらい差がでるかを検証してみた。
毎回 canvas の内容を全部描く
http://cho45.stfuawsc.com/spectrum-performance/canvas-redraw-whole/
ctx.putImageData( ctx.getImageData(0, 1, canvas.width, canvas.height - 1), 0, 0 );
上のようなコードで canvas 全体を 1px ずらして、最後の一行に新しいデータによる putImageData() をさらに行う。
ピクセルデータを直接シフトさせるやりかただと、一番ナイーブで実装が簡単だが実際のところかなり遅い。
35ms/render ぐらい。
2枚の canvas とっかえひっかえ追記で塗りつつ、DOM 的にエレメントを移動する
http://cho45.stfuawsc.com/spectrum-performance/canvas-double-dom/
canvas の 描画自体は1px * width だけを常に上書きする形で行い、全体のスクロールは DOM でずらす (ブラウザに再描画をやらせる)
わかりにくいので動画にするとこんな感じで、2枚の canvas 要素を margin で使って動かして、親要素で表示領域を制限 (overflow: hidden) してる。
4ms/render
WebGL でテクスチャ2枚をとっかえひっかえし、シェーダーでスクロールする
http://cho45.stfuawsc.com/spectrum-performance/webgl-double-textures/
2枚 canvas とやっていることはほぼ同じだが、canvas の代わりに WebGL のテクスチャを2枚定義し、DOM の代わりにシェーダーを使う。
0.6ms/render
毎回 canvas の内容を全部描く (drawImage)
@cho45 デバイスがなくてコードを試せてないのですが、putImageData+getImageDataではなくてdrawImageにcanvas自身を渡してずらして一行描くのだと速くなりそうですがどうでしょう。
— Mayuki Sawatari (@mayuki) October 5, 2015
と言われて、drawImage! そういえばそんなのも! という感じだったので試したらこれは十分高速だった。
0.6ms/render で WebGL でやるのと変わらないぐらい。
肌感覚
canvas へ広範囲に putImageData の繰替えしをするより DOM で移動させたほうが圧倒的に早いのがおもしろかった。
WebGL は面倒くさい分たしかに高速で、2D でも可能な限り使ったほうがよさそうだけど、直接文字のレンダリングができないとか (canvas に書いてからテクスチャとして転送する必要がある)、API 的に面倒とか、GLSL というシェーダー言語を覚える必要があるとか、難点もある。
とはいえ、面倒なぶん自力でチューニング可能なポイントが多いのでおもしろい。
備考
もっと早くなる方法がありそうなら是非お知らせください。