memorandums

日々の作業ログです。

p5.jsでFPSを60以上にあげてみる

厨ニ的な発想ですが...

p5.jsではframeRateで画面のリフレッシュレートを設定できまして,最大が60に設定されています.

ただ,ブラウザのcanvasのリフレッシュレートはもっと上げられるんじゃないか...と想像しまして,p5.jsのソースを読んだりしていました.

p5.jsでは描画関数であるdraw関数を定期的に呼び出すために,window.requestAnimationFrame というものを使用しているらしいことがわかりました.

ぐぐると,Mozillaのサイトに解説がありました.

developer.mozilla.org

1/60秒のタイマーをsetTimeoutで作成するのではなく,あくまでブラウザのタイミングで更新しようという関数のようです.確かにその方が,ブラウザごとの実装に合わせた表示ができそうです.スマートな実装です.

最初,p5.jsのコードに,frameRateに60以上を指定したらif文か何かで制限するコードがあるんじゃないかと思ったのですが...見つからず.このwindow.requestAnimationFrame がそもそも制約を作っていたってわけなんですね.なるほどです.

計測してみたところ,確かに約1/60秒ごとに呼び出されていました(Firefox).

このwindow.requestAnimationFrameとsetTimeoutで動作を比較してみました.

以下,実験コードです.

ブラウザで実行すると2つの□が右に移動していきます.上はwindow.requestAnimationFrameで1/60秒周期で実行したもの.下はsetTimeoutで1/200秒周期で実行したものです.

y座標の変動はそれぞれ指定したFPS(上段は60で下段は200です)の実測値が変動している様子を表しています.細かく波打っていますが,だいたい指定した値で動作するようです.

とりあえず,setTimeoutで指定すればリフレッシュレートを60以上にあげられそうなことがわかりました.

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <canvas id="canvas" width="400" height="400"></canvas>
    <script>
      var canvas = document.getElementById("canvas");
      var context = canvas.getContext('2d');
      let x_a=0;
      let x_b=0;

      function a() {
        let et = new Date();
        let dy = (et-st_a-1000/60);
        if (x_a++>400) x_a=0;
        context.strokeRect(x_a, 100+dy, 10, 10);
        window.requestAnimationFrame(a);
        st_a = et;
      }

      function b() {
        let et = new Date();
        let dy = (et-st_b-1000/200);
        if (x_b++>400) x_b=0;
        context.clearRect(0,0,400,400);
        context.strokeRect(x_b, 300+dy, 10, 10);
        setTimeout(b,1000/200);
        st_b = et;
      }

      let st_a = new Date();
      let st_b = st_a;
      a();
      b();
    </script>
  </body>
</html>

さて,お次はp5.jsのコードをちょっと変更してみたいと思います.p5.min.jsだと見にくいので,本家サイトにより圧縮前のソースをダウンロードします.

p5.js ダウンロード

で,p5.jsを取り出して,下図のように55655行から55659行までコメントアウトし,55662行目の1000 / 60の60を変えます.以下は120に変えたところです.最大120FPSで動作するようにできます.

f:id:ke_takahashi:20200802100356p:plain

まぁ,こんなことをやる人はいないでしょうけどね...とりあえず.

せっかくだから比較してみたかったので,p5.jsをインスタンスモードで利用することで,frameRateを60と120で指定して違いをみてみました.

ツイートにも書いているけど,決して滑らかではないんですね...やる前からわかっていたことですが...

ただ,速く回っているだけ...リフレッシュレートの高いディスプレイで表示してみてみたいものです...ヌルヌル動くっていうやつを😁

メモまで,インスタンスモードで比較した○ソコードだけど晒しておきます.

<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script language="javascript" type="text/javascript" src="p5.js"></script>
  <style> body { padding: 0; margin: 0; } </style>
</head>
<body>
  <table>
    <tr>
      <td>
        <h2>60FPS</h2>
        <div id='fps60'></div>
        実測値(FPS):<div id='fps60_count'></div>
      </td>
      <td>
        <h2>120FPS</h2>
        <div id='fps120'></div>
        実測値(FPS):<div id='fps120_count'></div>
      </td>
    </tr>
  </table>

<script>
 let c60 = [];
 let c120 = [];
 let sketch60 = function(p) {
   p.setup = function() {
     p.createCanvas(300, 300, p.WEBGL);
     p.frameRate(60);
   };
   p.draw = function() {
     p.background(200);
     p.rotateY(p.frameCount * 0.01);
     p.box(150);
     let tag = window.document.getElementById('fps60_count');
     c60.push(p.frameRate()); if (c60.length>10) c60.shift();
     tag.innerHTML = p.int(c60.reduce((accumulator, currentValue) => accumulator + currentValue)/10);
   };
 };
 let st120;
 let sketch120 = function(p) {
   p.setup = function() {
     p.createCanvas(300, 300, p.WEBGL);
     p.frameRate(120);
   };
   p.draw = function() {
     p.background(200);
     p.rotateY(p.frameCount * 0.01);
     p.box(150);
     let tag = window.document.getElementById('fps120_count');
     c120.push(p.frameRate()); if (c120.length>10) c120.shift();
     tag.innerHTML = p.int(c120.reduce((accumulator, currentValue) => accumulator + currentValue)/10);
   };
 };
 new p5(sketch60, window.document.getElementById('fps60'));
 new p5(sketch120, window.document.getElementById('fps120'));
 </script>

</body>
</html>

うーん,何の役に立つんでしょうね...まぁ,いいか.やりたいからやっただけです.