🕳️
[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