🕳️
[JavaScript] drawImageがどんどん遅くなるときはsaveとrestoreの数を数えてみる
概要
JavaScriptのcanvasを用いてゲームを作るときは次のようにしている人が多いと思います.
- 見えないキャンバス(
visibility:hiddenをつけたものや,動的に生成したものなど)に色々描画 - その結果を見えるキャンバスに
drawImageを用いてコピー - 上の処理を繰り返し
このdrawImageが繰り返す毎に重くなっているときは,saveしている数がrestoreしている数よりも多くなっているかも知れません.
発見
PureScriptでゲームを作成するライブラリを作っていたとき,その動作テスト中にどんどんfpsが落ちる事案が発生.DevToolsで見たところdrawImageの処理がじわじわと遅くなっていっていました.6時間ほどの探索の末,描画処理をしているモジュールの中でsaveがrestoreよりも多かったことが判明しました.
何故見つけるのに時間がかかったか
- 処理を多くの関数に分けていたため全て正確に追えていなかった.
- 描画された見た目は問題なかったため,
save,restoreが原因だと気づかなかった. -
save,restoreの数はデバッグで数えるのが難しい.
再現
<!DOCTYPE html>
<html>
<head>
<script async="" type="text/javascript" src="index.js"></script>
</head>
<body>
<canvas id = "canvas"></canvas>
</body>
</html>
const canvas = document.getElementById('canvas');
canvas.width = 300.0;
canvas.height = 300.0;
const ctx = canvas.getContext('2d');
const off_canvas = document.createElement('canvas');
off_canvas.width = 300.0;
off_canvas.height = 300.0;
const off_ctx = off_canvas.getContext('2d');
const mainLoop = () => {
off_ctx.save();
off_ctx.save(); //2回save
off_ctx.clearRect(0.0, 0.0, 300.0, 300.0);
off_ctx.fillRect (100.0, 100.0, 100.0, 100.0);
off_ctx.restore(); //1回restore
ctx.clearRect(0.0, 0.0, 300.0, 300.0);
ctx.drawImage(off_canvas, 0.0, 0.0);
setTimeout(mainLoop, 1.0);
}
mainLoop();
DevToolsで見ればこのように処理が遅くなっていることが分かります.
-
開始時

-
暫く経った後

再発防止
与えられた処理をsaveとrestoreで囲んでくれるような関数を作って,利用することにします.
例(PureScript)
saveAndRestore ctx f = do
save ctx
f
restore ctx
この関数以外の場所でsave,restoreを書かないようにすればよいです.
restore > saveの場合
特に遅くなるなどの変化はなりませんでした.しかしそもそもsaveとrestoreの数が異なるのは健全なプログラムでは無さそうなので,きちんと揃えた方が良さそうです.
Discussion