Programming

HTMLのCanvas要素の描画性能を調べてみる

HTMLのCanvas要素にビットマップを描画する場合、どの程度の性能がでるのか、試してみました。

<html>
    <body>
        <script
            src="https://code.jquery.com/jquery-3.2.1.js"
            integrity="sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE="
            crossorigin="anonymous"></script>
        <p><div id="time">Time[ms]</div></p>
        <canvas id="canvas" width="1920" height="1080"></canvas>
        <script type="text/javascript">
            $(window).on('load', function(){
                var tm = 0;
                var canvas = $('#canvas').get(0);
                var width = canvas.width;
                var height = canvas.height;
                var ctx = canvas.getContext('2d');
                var imageData = ctx.getImageData(0, 0, width, height);

                var buf = new ArrayBuffer(imageData.data.length);
                var buf8 = new Uint8ClampedArray(buf);
                var data = new Uint32Array(buf);
                var start_ms = performance.now();
                var elapsed_ms;

                var draw = function(){
                    for (var j = 0; j < height; j++){
                        for (var i = 0; i < width; i++){
                            var val = (i+j+tm) & 0xFF;
                            data[j*width+i] =   (255 << 24) |
                                                (val << 16) |
                                                (val << 8) |
                                                val;
                        }
                    }
                    imageData.data.set(buf8);
                    ctx.putImageData(imageData, 0, 0);
                    tm++;
                    if (tm % 60 == 0){
                        var current = performance.now();
                        elapsed_ms = current - start_ms;
                        $("#time").text(elapsed_ms/60.0 + "[ms]");
                        start_ms = current;
                    }
                }
                setInterval(draw, 5);
            });
        </script>
    </body>
</html>

setIntervalに小さめの値を設定することで、できるだけ頻繁にdraw関数での描画が実行されるようにしました。

試してみると、ジッタがありますが、12-15[ms]程度に1回の描画が行われているようです。 試した環境は、Core i7-6500U, Windows 10, Firefox 57.0.2 (64bit)です。 動作中は、Firefoxのプロセスが30%程度のCPU時間を消費し、GPUは55%程度を示しています(タスクマネージャで計測)。

WinUSBドライバの自動インストール

WindowsにはWinUSBというドライバフレームワーク(?)があります。これを使えば、 自作USBデバイスと通信するドライバを自作する必要なく、 アプリケーションレベルでバルク転送などの通信を行うことができます。

というのは組み込み業界では良く知られていると思います。でも、 比較的最近可能になった裏技(?)についてはあまり知られていないのではないかと思います。

これまでは、WinUSBのドライバをインストールするためにinfファイルを作ったりする必要がありました。 でも実はWindows8以降からは、infファイルさえ必要なく、デバイスを接続するだけでドライバをインストールすることが可能になっています。

つまり、ドライバ署名なども一切気にする必要はないのです。ドライバ署名のチェックを無効にして再起動するという前時代的な作業は不要です。

これを実現するには、USBデバイス側にも少し変更が必要です。具体的には、 Microsoft OSディスクリプタというものを作成し、Windowsからのリクエストに応じて返すようにします。

Microsoft OS descriptorには2種類存在します。

  • 標準USBストリングディスクリプタ。OS string descriptorと呼ばれます。 このディスクリプタにより、デバイスから(次に述べる)OS feature descriptorを取得可能であるとOSが判断します。
  • OS feature descriptor: デバイスは1つまたは複数のOS feature descriptorを持つことができます。

OS feature decriptorを取得するための手順は次のようになります。

  1. まず、WindowsがOS string descriptorを取得するためのコントロール要求を送る
  2. Windowsは有効なOS string descriptorであることを検証する
  3. WindowsはOS string descriptorのbMS_VendorCodeフィールドの情報を用いて、OS feature descriptorを取得する

OS feature descriptorには次の種類があります。

  • Extended Compat ID この情報に基づき、Windowsはどのドライバをロードするかを決定します。
  • Extended Properties
  • Genre これはHIDデバイスによって使用される(予定)。

とりあえず、上記二つのExtended Compat ID, Extended Propertiesのディスクリプタを準備すれば、 ドライバの自動インストールと、GUIDによるアプリケーションからの通信が可能になります。

OS string descriptor

OS string descriptorは標準ストリングディスクリプタの、string index 0xEEに格納されます。 OS string descriptorはデバイスにつき1つのみ持つことができます。

OS string descriptorを取得するために、GET_DESCRIPTORコントロール要求がデバイスに送られます。

Windows環境でFormsアプリを作るなら、C++が最強ですよ

Windows環境でアプリケーション(Webアプリではなく、Formsアプリ)を作る場合、 最近ではC#を使うのが典型的となっているのではないかと思います。 必要に応じてC++やCで開発したDLLを呼び出したり、あるいはC#から直接DLLを呼び出すかもしれません。 マニアックな方であれば、C#ではなくてF#を使ったりするかもしれません。

業務上作成するアプリケーションはできる限りのパフォーマンスが欲しい、という要求が生じることがあります。 そうなると、現状ではC++/CLIでアプリケーションを作るのが最も良いのではないかと思います。

C++/CLIで実装すれば、WindowsのFormもグラフィカルデザイナーで作成できますし、 必要であれば.NETコードではなくてネイティブコードを生成するようコンパイラオプションで設定することもできます。 しかも.NETとネイティブコードとのデータのやり取りは、関数呼び出しで行う限りほぼ暗黙的に実行できます。

C++であればOpenMPもC++ AMPも自由自在です。そのため、GPGPUやマルチスレッドプログラムの作成も (APIを呼び出すことなく)比較的簡便に行うことができます。

個人的にはC++なんて醜悪言語は使いたくないですが、パフォーマンスのためには背に腹は代えられません。