初めまして、ドリコムクライアントエンジニアの増野です。
ドリコムでは、スマートフォンを中心として“ゲーム制作”に関する技術のキャッチアップを行っています。

その中で今回は描画に関する部分、特に GPU と DrawCall (ドローコール) の話について触れてみたいと思います。

画面の描画はどのように行われているか?

ゲームにおける「描画」とは極端に言ってしまえば、表示用のメモリ領域をピクセル情報で埋める作業のことを指します。

例えば、下記のようなドリコムのロゴがあったとします。

ドリコムのロゴ画像

これを拡大していくと画像のようなピクセル情報の集まりだという事が見えてきます。

ドリコムのロゴを部分拡大した画像

ピクセル一点一点につき、メモリにはそのピクセルが赤なのか青なのかと言った色情報が保存されており、その色情報のメモリをディスプレイに転送することで初めて目に見える形で表示が行われます。なお、画面全体の色情報が記録されたメモリ領域のことをフレームバッファと呼んだりします。

ピクセル一点一点のイメージ

極端な例になりますが 2×2 px のフレームバッファがあった場合(画像は3600%拡大しています)下記のような値が書き込まれます。

値が書き込まれた例

少し分かりづらいですが、ABGR の順で16進数での色情報が入っています。

  • 00 00 FF 00 → 緑
  • 00 FF 00 00 → 青
  • 00 FF FF FF → 白
  • 00 00 00 FF → 赤

を指しています。

※ なぜ一般的な RGBA ではなく ABGR の順で記述されているのかは別の話になるためここでは割愛させて頂きます。興味がある方は「リトルエンディアン」で調べて見て下さい。

描画とは、システムから見れば上記のような 00 FF 00 FF と言った色情報(ピクセル)をメモリに書き込む工程を指します。

CPU と GPU の違い

簡単にですが描画とは何かを説明してきました。

さて、最近ではフルHD(1920x1080px)の画面解像度を持ったスマートフォン端末が増えてきています。1920x1080px だと、ピクセル数で言えば縦横合計でおよそ 207万px あります。

またリアルタイム性が求められるゲームでは 60FPS(1秒間に60回画面が更新される)での実行速度が求められることが多く、その場合 207万ピクセル x 60回 = 約1億2441万ピクセルを1秒間に更新を行う必要があります。

たった1秒ゲームが実行されるだけで、さきほど説明した 00 FF 00 FF のような色情報が1億回以上メモリに書き込まれています。私たちが普段何気なく遊んでいるゲームも、膨大な処理によって成り立っていることが分かります。

この1億回のメモリの書き換え(ピクセルの更新)ですが、当然 CPU を使用して行うことも出来ますが、幾ら CPU が早くなったとは言え、毎回毎回この膨大なピクセル情報をメモリに書き込み続けるのはあまり効率的ではありません。

そのため、フレームバッファのメモリを書き換える専用のプロセッサが必要とされ、それが GPU として搭載されています。

最近では計算目的に活用されることが多くなった GPU ですが、今でもフレームバッファのメモリを埋める(厳密に言えば、ピクセル更新に必要な一連の演算処理を行う)のが主な役割です。

グラフィックス API

GPU はフレームバッファの内容を書き換え、描画結果の生成を行います。
そこで、GPU を使用したプログラミングを考えてみたいと思います。

GPU は複数のベンダーからデスクトップ用途で使われるものや、モバイル向けに低消費電力化された物など幾つかの製品が存在します。こう言った外部のハードウェア(デバイス)には必ず、そのハードウェアを制御するための専用のソフトウェア「ドライバ」が存在します。アプリケーションはドライバを通してハードウェアにアクセスすることが出来ます。

しかし大きな問題があります。
ドライバの実装内容は製品によって異なるため、アプリケーションから GPU を使おうとした場合、A社の GPU と B社の GPU ではプログラマは別のコードを書かなくてはなりません。対応する GPU が増えるごとに動作させるコードを追加していくのはめっちゃ大変です。

そこで共通の API を策定して統一的に GPU を利用できるようにした方が効率的だと考え、登場したのが OpenGL / DirectX の グラフィックスAPI です。アプリケーションから見れば、OpenGL / DirectX の関数を呼び出せば、後はドライバが良しなに GPU を駆動させて描画してくれます。

アプリケーションはこのグラフィックスAPIを呼び出して、初めて GPU を用いた描画が行えるのです。

DrawCall (ドローコール) とは何か?

ここまで長くなりましたが本題です。

グラフィックスAPI を使用して、画面に描画を行う際に呼び出す命令の事を DrawCall (ドローコール) と呼びます。

具体的には下記のような関数が該当します。(ここで並べているのは一部です)

  • ID3D11DeviceContext::DrawIndexed()
  • glDrawElements()

これらの関数を呼び出すことで初めて GPU は駆動し、フレームバッファのメモリ情報が書き換わることになります。

当然、Unity なども内部で glDrawElements()DrawIndexed() を呼び出して描画を行っており、この Draw関数の一度の呼び出しが、1 DrawCall になります。

CPU から GPUへ 描画時には何が行われるのか?

グラフィックスAPI の呼び出しによって、モデルやステートの情報は描画コマンドとしてメインメモリ上のコマンドバッファに蓄積されます。そのコマンドはグラフィクスAPI の Present や Flush の呼び出しなどによってGPUに転送され処理されます。

Direct3D のステート設定、プレゼント、描画のコマンドがアプリケーションによって呼び出されると、これらのコマンドは内部コマンド バッファーのキューに格納されます。Flush はこれらのコマンドを GPU に送信して処理されるようにします。通常これらのコマンドは、コマンド バッファーが満杯の場合や、リソースのマッピング時など、Direct3D によって必要だと判定された場合に自動的に GPU に送信されます。Flush は、コマンドを手動で送信します。

ID3D11DeviceContext::Flush

簡単にまとめると、以下のような形になると思います。

  • アプリケーションからグラフィックスAPI の呼び出しを行うと、ユーザーモードで動作しているドライバがメインメモリ上のコマンドバッファに内容を蓄積する
  • DrawCall の呼び出しで コマンドバッファが一杯になる、あるいは Flush, Present が呼び出されると、ドライバはカーネルモードに切り替わる
  • ドライバが描画コマンドをGPUが解釈できる命令に変換してデバイスに転送する。同時にリソースを DMA 経由で VRAM に転送を行う
  • GPU が起動して、描画が行われる

実際には GPU に対してコマンドが転送される前に、描画コマンドの正当性チェック(バリデーション)なども行われているはずです。

コンシューマ機であればグラフィックスAPI が直接ネイティブなGPUコマンドを発行しますが、DirectX ではグラフィックスAPIが蓄積したコマンドをドライバがGPUコマンドへと変換する処理が入るため、これが余計なオーバーヘッドにもなっています。

まとめ

描画コマンドの処理は、DrawCall を一つの基準としてドライバが処理するため、DrawCall の増加が CPU の負荷となります。

  • コマンドバッファの蓄積
  • コマンドバッファ(レンダーステート)の正当性のチェック
  • ネイティブな描画命令への変換
  • GPU への転送

これが DrawCall による CPU の オーバーヘッドになり、これを減らす事がアプリケーションの最適化へと繋がります。

なお、よく誤解されるのですが、上述の通り DrawCall は CPU 側の負荷です。アプリケーションにおいて GPU がボトルネックになっている場合は、DrawCall を減らしても最適化には繋がりません。

DrawCall のオーバーヘッドの削減へ

近年、Low-Overhead, Low-Level グラフィックスAPI が登場しています。
2013年の AMD Mantle 以降、その思想は DirectX 12 へと受け継がれ、Apple Metal や Khronos Vulkan など新しいグラフィックスAPIが生み出されてきました。

従来、開発者からは不透明であったリソースのメモリ管理であったり、コマンドバッファの構築など、全てアプリケーション側で責任を持つ必要があります。今までブラックボックスだった多くの部分が開発者に公開されるようになりました。その反面、グラフィックスAPIは更に高度化、複雑化の道を辿りつつあります。

最近のグラフィックスAPIを考える上で大切なことは、また別の機会に解説出来たらと思っています。

About the Author

増野 健人

増野 健人

クライアントエンジニア

コンシューマゲーム会社にて PS4 / XboxOne / PC 向けのグラフィックスシステムやシェーダー開発に携わる。2015年にドリコム入社後は、アプリ開発の傍ら社内の3Dゲーム制作やシェーダー開発のスキルボトムアップの活動を行う。