DrawCallが9倍早くなるワケ

地表で生きるものとして、Windows上でのDrawCallの仕組みを見てみます。

DrawCallがCPU処理時間を消費する理由

DrawCallがプロセス側から発行されると、その時点で、描画に使用するリソース(VB,IB,Tex,CBなど)が、存在するかどうか、大きさは適切であるかなどの、妥当性のチェックが行われると同時に、リソースがGPU側のデバイスメモリに転送されているか、転送されていればそのアドレスはどこなのか、などの問題が解決され、GPU用の描画命令コマンドとして、専用のバッファに蓄積されます。
ここまでが、大まかですが、DrawCallの発行によって、CPU側で処理される内容です。
リソースの生成を伴わない、通常のDrawCallであれば、専用のバッファへの蓄積までは、UMD(User Mode Driver)で処理されます。UMDは、皆さんのプロセスと同じアドレス空間を用いて動作するもので、要はDLLのライブラリ等と同じです。KMDはこのケースでは動作することは殆どありません。(KMDについては後述)
では、何がCPU処理時間を消費しているのでしょうか。
上記で述べた、使用リソースの妥当性チェックと、リソースのオブジェクト名からアドレスへの変換と、API呼び出しのオーバーヘッドです。

ちなみに、DXでは、DXのAPIが呼び出されると、DXのレイヤーの処理に加え、必要に応じてそれに対応するUMDの関数が呼び出されますが、OGLの場合は、その処理のほとんどが、UMDに相当するレイヤーで行われます。従って、OGLの方が、全般的にAPIレイヤーのオーバーヘッドが少ないと思われます。
下記のリンクは、この違いが、実際の性能に現れたケースだと思われます。
[EXTREME TECH: Valve: OpenGL is faster than DirectX — even on Windows]
ただし、全てにおいてDXよりOGLの方が速いとは限りません。DXは多数のゲームタイトルで使用され、最適化も進んでいます。加えてOGLのように太古の昔からの互換性もありません。

KMDについて

私はWindowsのドライバの中を見たことも書いたことが無いので、以下は正確性を欠いているかもしれません。
KMDはKernel Mode Driverで、その名の通り、KernelModeで動作しているものです。KMDのインスタンスはシステム上で1つのみロードされており、GPUとの直接の通信を担当します。
対してUMDはプロセスごとにロードされるため、使用しているプロセスごとに、複数個存在する前提となっています。
KMDはGPUとの唯一の窓口であるため、GPU側のメモリの管理や、Apertureと呼ばれる、GPU側から見えるHOST側のメモリの管理を行っています。
KMDはUMDの要求に応じて、GPUのメモリやApertureの領域を割り当てます。UMDはそれを、自分のリソースとして管理しているため、GPU側の、自分の保持しているリソースのアドレスを知ることが出来ます。GPU側のメモリやAperture領域の確保は、UMD-KMD間のやり取りを必要とするため、非常にコストが高いです。
従って、小さなサイズのリソースに関しては、UMDがある程度の大きさでKMDよりメモリ空間を取得し、それをUMD側で分割して割り当てていることがあります。
つまり、グラフィックスAPI上で新規にリソースを作成したからといって、直ちにKMDでGPU側のメモリ確保を行っているとは限りません。
もうひとつKMDの大きな役割として、GPU処理命令のブロック(DMAパケット)のGPU側への送出があります。DMAパケットの生成の殆どはUMD側で行いますが、実際にGPUに送る作業はKMDが行います。KMDはGPUを使用するプロセスが複数存在する場合は、そのスケジューリングも行います。スケジューリングの粒度は、このDMAパケットになります。
ひとつのDMAパケットがどの程度のGPU処理時間を使用するかは分からないので、プロセス間でGPUの競合が起きた場合の切り替わりの速度は、このDMAパケットの処理時間に依存します。
このあたりの事情はWDDMが2.xになると大きく変わると思われますが、Windows8.1でもWDDMは1.3なので、今でも大枠でこの仕組みは変わっていないと思われます。
また、ディスプレイの解像度変更など、システム全体に影響のある処理はKMDが担当しています。

話を元に戻します

OGLでDrawCallのCPU処理時間について詳細に扱ったセッションが、GTC2013でありました。
[GTC2013 Advanced Scenegraph Rendering Pipeline]

このスライドの30ページの所に注目です。2400回のDrawCallのCPU側の処理時間を比較しています。
VBOと示されるのは、通常の頂点バッファを用いたDrawCallです。これを基準として考えます。
VABは頂点フォーマットの設定をオブジェクト化してBindできるようにしたものです。OGLでは通常は、頂点フォーマットの設定は多数のGL関数の呼び出しを伴いますが、これを用いると、頂点フォーマットの設定をオブジェクト化でき、一度のAPI呼び出しで設定できるようになります。DXで言うところの、FVFからCreateVertexDeclaration()への変更といったところでしょうか。これによって処理時間は、10%ほど短縮されています。
VBUMは、頂点バッファのアドレスを、プロセス側で先に取得しておき、DrawCallの発行時には、GPU上のアドレスを直接指定する方法です。これにより、DrawCallごとに発生していた、頂点バッファのオブジェクト名からGPU上のアドレスへの変換作業が省略することができ、50%以上CPU処理時間が短縮されています。この短縮された時間には、アドレス変換だけではなく、リソースの妥当性検証部分も含まれていると思われます。なぜならば、GPU側のアドレスを直接指定するため、UMDレベルでは、リソースの妥当性検証は不可能に近いので、行われていないと考えるのが自然でだからです。
BINDLESS INDIRECT HOSTは、CPU側で、頂点バッファと頂点フォーマットに相当するアドレス情報と、インデックスバッファのアドレスを指定した、DrawCallに相当する構造体の配列を作成し、これにより複数のDrawCallを一回のAPI呼び出しで処理するものです。これを用いることで90%近い処理時間の短縮が達成されています。
つまり、通常の手順でDrawCallを呼び出す代わりに、DrawCallの使用するリソースの指定にGPU側のアドレスを直接指定し、一時的なバッファに描画命令を蓄積して、DrawCallをある程度まとめて発行すれば、DrawCallの処理が9倍早くなるのも妥当だと思われます。
BINDLESS INDIRECT GPUは、DrawCallの内容を指定した構造体配列が、GPU側のメモリに格納されているケースです。こちらは、もはやCPUの処理時間を殆ど消費しません。
このように既存のAPIである、OpenGLでも、GPUベンダーによる拡張を使用することを前提にすると、Textureなどの他のリソースも殆どがGPU側のアドレスを取得でき、描画リソースを指定する際に使用できるようになっています。これ等を積極的に用いると、DrawCallのCPU側の処理時間を数倍オーダーで高速化することは十分可能です。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中