月別アーカイブ: 2013年10月

ConeTracingについて

Cone Tracingについて少し考えてみたいと思います。VoxelのOctreeの処理については考慮しません。

参照

[Implementing Voxel Cone Tracing. Simon Yeung]
[Non-interleaved Deferred Shading of Interleaved Sample Patterns, Segovia06]
The Technology Behind the 3D Graphics and Games Course “Unreal Engine 4 Elemental demo”

Coneの角度の決め方

参照に挙げた、SimonさんのBlogの解説では、ジオメトリの法線方向を中心に6方向のConeTracingを行う方法を用いています。Coneの角度は60度だそうです。60度のConeは、3つ並べると、丁度180度となるため、法線方向に一つのConeを配置して、それを囲むように5方向のConeを配置することで、半球上の光をTracingするということのようです。

Coneの角度を決める際には、もっと汎用的な考え方もあると思います。
半球の表面積は2πなので、6回のConeTracingでこれを購うとすると、2π/6が一つのConeの受け持つべき面積です。言い換えると、半径の1/6の長さで球を切り取った部分(球冠)が、それに該当すると思います。これより求まるConeの角度は、acos(5/6)*2なので、67.1度となります。
これは、Coneの数で半球上の面積を割り、その面積から求めた角度なので、実際にこの角度のConeを半球状に、重複することなく配置することは不可能です。しかし、ConeTracingのConeは、概念的な存在で、Voxel空間を正確にConeの形状で切り取るわけではありません。したがって、このような考え方も十分通用すると思います。

Voxel内のSampling位置の決め方

次に、ConeTracingする際の、Voxel内のSampling位置の決め方を考えます。

まず、例として、Coneの角度が67.1度の場合を考えます。
Coneの角度が67.1度の場合は、0.5/tan(67.1/2)の位置で、Coneの直径が、ちょうど1になります。値は約0.75です。従って、Coneの直径が2になる場所は、1.5で、直径が4になるのは、3.0です。これ等の位置で、VoxelのそれぞれのLODをサンプリングすれば、Coneの形状に沿ったサンプリングになると思います。
この計算から明らかなように、Voxelの絶対的な大きさは影響せず、Coneの角度によってのみサンプリングの位置が決定します。この例のように、Cornの開度が大きい場合は、サンプリング回数は各LODにつき1回で、連続した空間をサンプリングすることができます。

次に、極端に細いConeの場合を考えます。
例として、角度が10度のConeを考えた場合、0.5/tan(10.0/2)の値は5.7になります。従ってceil(5.7)=6なので、LOD:0で6回サンプリングすれば良いわけです。5.7/6は0.95なので、LOD:0を0.95間隔で6回(0.95, 1.9, 2.85, 3.8, 4.75, 5.7)サンプリングします。
LOD:1は、距離5.7~11.4までをサンプリングします。ceil(5.7/2)=3、つまり3回サンプリングすれば良いわけです。5.7/3は1.9なので、LOD:1は,間隔1.9で3回(7.6, 9.5, 11.4)サンプリングを行います。以降の処理は同様で、目的の距離までサンプリングを行います。

結局のところ、想定したConeに則って、サンプリング位置とLODを決めれば良いだけで、上記も考え方の一つに過ぎません。Quad-Linear filteringを用いれば、各LODの中間の値もサンプリングすることができます。

Coneの角度と、サンプリング回数、サンプル距離、LODの関係

上記の通り、ConeTracingで周辺の情報をVoxelから集める場合、Coneの角度と、サンプリング回数、サンプル距離には密接な関係があります。迅速に遠くまでの情報を集めようとするときは、VoxelのLODを下げて、大きな間隔でサンプリングする必要があります。そのためには、Coneの開度は必然的に大きくなります。一方、精度の高い情報が必要な場合は、角度の小さなConeを用いないと、LODの急激な低下で、情報の精度が落ちてしまいます。
つまり、Coneの角度を決めてしまえば、情報の精度と、同一サンプリング回数で到達する距離は決定します。また、Coneの角度と必要到達距離を決めると、必要とするVoxelのLODのレベルが決定します。

Non-interleaved Deferred Shading of Interleaved Sample Patternsについて

隣接する場所で、同じ向きで、ConeTracingを行った場合、その結果は、おそらく似た値になることが予想されます。この隣接度合いが、Voxelのサイズに対して十分小さい場合は、その結果は、おそらく殆ど同じ値になると思います。これを、DiffuseのConeTracingに大胆に活用した例が、UE4のGIをはじめとした技法になります。
ここで使われる手法の元になるのが、Non-interleaved Deferred Shading of Interleaved Sample Patternsの論文で、簡単に言えば、Shadingの計算をインターリーブで行い、後でそれを合成するという方法です。これを応用し、ConeTracingのConeの向きをインターリーブして、結果を後で合成するようにします。
UE4の例では、G-Bufferを3×3ピクセルブロックに分割し、1-9のインデックスを割りあて、各インデックスで、それぞれ1方向にのみConeTracingを行います。Traceする9方向は、全球上にうまく分布するようにあらかじめ設定しておきます。ConeTracingが終わったら、各自の周辺ピクセルから、ConeTracingの結果を取得します。(UE4の例では周辺5×5ピクセルから情報を集めているそうです。)これによって、各ピクセルで、9方向のConeTracingの結果が手に入るわけです。
当然ながら、周辺のピクセルの深度値を見て、ピクセルが近いかどうかをチェックして使用する必要があります。また、周辺ピクセルの法線が未知なので、ジオメトリの法線を基準としたTracingは行えず、全球上をConeTracingする必要があります。加えて、この手法を用いる場合、Tracingの回数の自由度が少ないです。3×3を用いれば、Tracingの回数は9の倍数となり、4×4を用いれば16の倍数となります。しかし、全体でのConeTracingの回数は1/9もしくは1/16になります。
この手法では、Traceする方向とConeの角度が固定されるので、最適化も行いやすいと思います。たとえば、各ピクセルで、Voxelの各LODに逐次アクセスするのではなく、LOD:0に対して行うTracing処理をまとめて行い、その後、LOD:1以降のTracingへといった処理にすることも可能だと思われます。こうした場合の、Voxel(3Dテクスチャ)へのアクセスは局所化され、キャッシュのヒット率向上が望める場合もあると思われます。
ちなみに、ConeTracingのグリッドは必ずしも正方形である必要は無く、4×3のグリッドが用いられているケースもあるようです。このケースでは、12方向のTracingとなり、正20面体の頂点方向をそのままTracingの方向として使える利点がありそうです。

ところで(実はここからが本題)

ところで、全球上を9分割したConeTracingでは、具体的にどの方向にConeTracingをすればよいのでしょうか。
残念ながら、頂点数が9の正多面体は存在しないので、適当に求めることにします。具体的な求め方は、ベクトルの向きを反復的に修正して、各ベクトルがなるべく離れるように分布させるだけです。これをWebGLで作ってみました。Chromeを使って作りましたので、Chromeならたぶん動作すると思います。
wgtest
使い方は、ベクトルの本数を6~32の範囲で入力し、Magnitudeのスライダを適当に動かして、ベクトルの分布が適切になるのを待ちます。あとはShow Axis Valueのボタンを押せば、ベクトルが表示されます。マウスで適当にドラッグすれば回転させることができるので、好みの向きに合わせることができます。表示される角度の値は、最も隣接するベクトルとの成す角度の平均で、ConeTracingの際の角度の指標になると思います。

OpenGLの機能拡張が分かりにくいワケ

先日、久しぶりにOpenGL Extension Registryを見てたら、glcorearb.hなるファイルがありました。(気づくの遅い)

OpenGLの関数群について

現在のOpenGLの関数群は、大きく分けて、3種類に分かれます。
まずは、Vendor拡張とEXT拡張と呼ばれる関数群があります。これ等は、特定のGPUベンダーや、OS/PCメーカーなどが追加した機能拡張です。EXT拡張は、複数の会社が合意して提出された機能拡張です。大抵のOpenGLの最新機能は、これ等の拡張で定義された関数群で実装されます。
次は、ARB/KHR拡張と呼ばれる機能です。ARBは、OpenGL Architecture Review Boardのことで、OpenGLの機能を決める評議会と言えると思います。ここで承認された機能拡張は、ARB拡張となります。大抵は、まずはじめにVendor拡張や、EXT拡張として実装された機能が、一定の有意性が見出された場合、ARBの承認を得ることで、ARB拡張となることが多いです。KHRはKhronos Groupが主導して導入された機能拡張のようですが、ARBと同一の機能拡張番号を使用しているので、実質的にARB拡張とみなして良いと思います。
最後に、Coreと呼ばれる機能です。これは、OpenGLの標準機能に属します。これは、OpenGLのバージョンが上がるたびに、機能が変わりますが、ひとたび特定のバージョンをサポートしたDriver/GPUがあれば、それは、そのバージョンで定義されている機能を全てサポートしている必要があります。サポートする機能は、バージョンが上がるたびに、基本的には追加という形で増えていきます。大抵は、ARB拡張で定義されている機能拡張が、Coreの機能として取り込まれることが多いです。
しかし、OpenGL3.1/3.2では、機能の削減が行われました。(具体的には、一部のCore機能をARB拡張に移しました。)したがって、後方互換性を保障するものではありません。

OpenGLの機能拡張が分かりにくいワケ

上記のように、とあるひとつの機能が、Coreに取り込まれるまでの過程で、Vendor/EXT/ARB/Coreと複数の定義を持つことが多々あります。
そのため同一の機能が、複数の関数やdefineを持つこととなり、OpenGLの機能の分かりにくさを助長していると思います。
不要な機能拡張の使用を避けるため、OpenGLのプログラムを書く際は、OpenGL Extension Registryにアップされている、最新バージョンのCoreProfileSpecificationに沿って、プログラムを書くのが一番だと思います。そして、Coreに無い機能を使う場合には、まずARB拡張を探し、次にEXT拡張、最後にVendor拡張を探すと良いと思います。

ここでは、ひとつの分かりにくい例として、Occlusion Queryの機能について調べてみたいと思います。
Occlusion Query関連は、

  • GL_HP_occlusion_test
  • GL_NV_occlusion_query
  • GL_ARB_occlusion_query
  • GL_ARB_occlusion_query2
  • OpenGL Core 1.5
  • OpenGL Core 3.3
  • OpenGL Core 4.3

で機能が追加されたり、Vendor->ARB->Coreへの変更などが行われています。下記にその内容を示します。

GL_HP_occlusion_test機能拡張

Occlusionの結果をBool値のみで取得可能。QueryがObject化されていないので、複数のOcclusionTestの結果を得るためには、一旦結果をCPU側で取得しなくてはならない。昔に作られた仕様。

GL_HP_occlusion_test

#ifndef GL_HP_occlusion_test
#define GL_HP_occlusion_test 1
#define GL_OCCLUSION_TEST_HP              0x8165
#define GL_OCCLUSION_TEST_RESULT_HP       0x8166
#endif /* GL_HP_occlusion_test */

使い方
glEnable(GL_OCCLUSION_TEST_HP)
//gl rendering calls
glDisable(GL_OCCLUSION_TEST_HP)
 
glGetBooleanv(GL_OCCLUSION_TEST_RESULT_HP, &result)

GL_NV_occlusion_query機能拡張

Occlusionの結果を描画されたサンプル数で取得する。QueryがObject化されているので、複数のOcclusionQueryの結果を、非同期的に取得することができる。CPU側で、即時に結果が読めるかどうかのチェックも可能。DX9の機能とほぼ同等。

GL_NV_occlusion_query

#ifndef GL_NV_occlusion_query
#define GL_NV_occlusion_query 1
#define GL_PIXEL_COUNTER_BITS_NV          0x8864
#define GL_CURRENT_OCCLUSION_QUERY_ID_NV  0x8865
#define GL_PIXEL_COUNT_NV                 0x8866
#define GL_PIXEL_COUNT_AVAILABLE_NV       0x8867

#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glGenOcclusionQueriesNV (GLsizei n, GLuint *ids);
GLAPI void APIENTRY glDeleteOcclusionQueriesNV (GLsizei n, const GLuint *ids);
GLAPI GLboolean APIENTRY glIsOcclusionQueryNV (GLuint id);
GLAPI void APIENTRY glBeginOcclusionQueryNV (GLuint id);
GLAPI void APIENTRY glEndOcclusionQueryNV (void);
GLAPI void APIENTRY glGetOcclusionQueryivNV (GLuint id, GLenum pname, GLint *params);
GLAPI void APIENTRY glGetOcclusionQueryuivNV (GLuint id, GLenum pname, GLuint *params);
#endif
#endif /* GL_NV_occlusion_query */

使い方
glBeginOcclusionQueryNV(occlusionQueries[i]);
// render 
glEndOcclusionQueryNV();

// you can check if the result is already available for reading on CPU side.
glGetOcclusionQueryuivNV(occlusionQueries[i], GL_PIXEL_COUNT_AVAILABLE_NV, &isAvailable);
// retrieve the pixel count
glGetOcclusionQueryuivNV(occlusionQueries[i], GL_PIXEL_COUNT_NV, &pixelCount);

GL_ARB_occlusion_query機能拡張

Vendor拡張がARB拡張になった典型的なケース。defineの名称や、関数名がARBに即した形になるが、同じ意味のdefineはNV拡張と同じ値。

GL_ARB_occlusion_query

#ifndef GL_ARB_occlusion_query
#define GL_ARB_occlusion_query 1
#define GL_QUERY_COUNTER_BITS_ARB         0x8864
#define GL_CURRENT_QUERY_ARB              0x8865
#define GL_QUERY_RESULT_ARB               0x8866
#define GL_QUERY_RESULT_AVAILABLE_ARB     0x8867
#define GL_SAMPLES_PASSED_ARB             0x8914

#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glGenQueriesARB (GLsizei n, GLuint *ids);
GLAPI void APIENTRY glDeleteQueriesARB (GLsizei n, const GLuint *ids);
GLAPI GLboolean APIENTRY glIsQueryARB (GLuint id);
GLAPI void APIENTRY glBeginQueryARB (GLenum target, GLuint id);
GLAPI void APIENTRY glEndQueryARB (GLenum target);
GLAPI void APIENTRY glGetQueryivARB (GLenum target, GLenum pname, GLint *params);
GLAPI void APIENTRY glGetQueryObjectivARB (GLuint id, GLenum pname, GLint *params);
GLAPI void APIENTRY glGetQueryObjectuivARB (GLuint id, GLenum pname, GLuint *params);
#endif
#endif /* GL_ARB_occlusion_query */

GL_ARB_occlusion_query2機能拡張

ここで再びBool値でOcclusionの取得が可能になる。同時期にOpenGL Core3.3に取り込まれたため、本来ARBで定義されるべきdefineが無い。このように、Coreに取り込まれることが前提となるARB拡張では、ARBのpostfixがdefineや関数名についていないことがある。

GL_ARB_occlusion_query2

#ifndef GL_ARB_occlusion_query2
#define GL_ARB_occlusion_query2 1
#endif /* GL_ARB_occlusion_query2 */

GL_ANY_SAMPLES_PASSEDというdefineが新設されているが、Coreのdefineとして定義されている。

結果をBool値で欲しいとき(Bool値で十分なとき)は、GL_ANY_SAMPLES_PASSEDをQueryのBegin/Endで使用し、結果をBool値で受け取る。

OpenGL Core 1.5

GL_ARB_occlusion_queryに相当する機能がCore1.5で追加された。ARBのpostfixが取れている。

OpenGL Core 1.5
#ifndef GL_VERSION_1_5
#define GL_VERSION_1_5 1
#define GL_QUERY_COUNTER_BITS             0x8864
#define GL_CURRENT_QUERY                  0x8865
#define GL_QUERY_RESULT                   0x8866
#define GL_QUERY_RESULT_AVAILABLE         0x8867
#define GL_SAMPLES_PASSED                 0x8914

#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glGenQueries (GLsizei n, GLuint *ids);
GLAPI void APIENTRY glDeleteQueries (GLsizei n, const GLuint *ids);
GLAPI GLboolean APIENTRY glIsQuery (GLuint id);
GLAPI void APIENTRY glBeginQuery (GLenum target, GLuint id);
GLAPI void APIENTRY glEndQuery (GLenum target);
GLAPI void APIENTRY glGetQueryiv (GLenum target, GLenum pname, GLint *params);
GLAPI void APIENTRY glGetQueryObjectiv (GLuint id, GLenum pname, GLint *params);
GLAPI void APIENTRY glGetQueryObjectuiv (GLuint id, GLenum pname, GLuint *params);
#endif
#endif /* GL_VERSION_1_5 */

OpenGL Core 3.3

GL_ARB_occlusion_query2に相当する機能が追加された。必要なdefineが定義されている。

OpenGL Core 3.3

#ifndef GL_VERSION_3_3
#define GL_VERSION_3_3 1
#define GL_ANY_SAMPLES_PASSED             0x8C2F
#endif /* GL_VERSION_3_3 */

OpenGL Core 4.3

MultiSample使用時のQueryのパフォーマンス向上のための機能追加が行われた。前提となる拡張が無く、いきなりCoreに取り込まれた。

OpenGL Core 4.3

#ifndef GL_VERSION_4_3
#define GL_VERSION_4_3 1
#define GL_ANY_SAMPLES_PASSED_CONSERVATIVE 0x8D6A
#endif /* GL_VERSION_4_3 */

追加されたdefineは、MutisampleのRenderTargetを使用している時の、Ccclusionテストの高速化を実現するためのもの。ただし、保守的な方向で、実際とは異なる結果を返す可能性がある。(全てのsampleがOccludeされているにも関わらず、されていないと返す可能性があるが、はやくなる(かもしれない))

gl_corearb.hについて

上記で示したとおり、OpenGLの機能拡張で定義された関数やdefineを全て含んだ glext.h というヘッダファイルは、いわば二重定義の嵐のような状況になっています。
OpenGLの機能拡張に精通していれば、それほど迷うことは無いのですが、初めてOpenGLを学んだ方には、まさにカオティックな内容になっています。
また、OpenGL3.2で追加されたCoreProfileとCompatibilityProfileでは、使用可能な機能に違いがあるのですが、同一のヘッダファイルとライブラリファイルを使用するため、コンパイルは通るけど、使ってはいけない関数というものが存在していました。
この状況下で、OpenGLのCoreとARB拡張のみを取り出した、gl_corearb.h を新設したのはすばらしい事だと思います。gl_corearb.hに定義されるARB拡張は、OpenGLのCoreProfileと互換性のあるもののみで、CompatibilityProfileでのみ使用可能なARB拡張は定義されないそうです。詳しくは、OpenGL 4.3 Core Profile SpecificationのAppendix G.2に記載されています。
ちなみに、OcclusionQuery関連について、gl_corearb.hをチェックすると、GL_ARB_occlusion_queryはgl_corearb.hに入っていませんでした。しかし、GL_ARB_occlusion_query2は残っていました。なんだか上記の説明と異なる気もしますが、CoreProfileが定義されるOpenGL3.2以前にCoreに統合されたARB拡張も、削除されていると考えると納得できます。

WindowsでGLを御使用の方へ

Windows上でのOpenGL Extensionを使用する場合は、Perl等を使って、gl_ext.hを書き換えれば簡単に使用できる旨を以前の[記事]で書きましたが、gl_corearb.hも同様に、Perl等を使って加工することで簡単に使えると思います。
ただし、gl_corearb.hには、OpenGL1.0 Coreについて定義があり、ここで定義されている関数は既に存在します。一方その他の関数はwglGetProcAddress()で取得する必要があるので、OpenGL1.0 Coreの定義の部分だけ、気をつけて処理する必要があります。
一応2013/10現在のgl_ext.h, wgl_ext.h, gl_corearb.hをPerlで加工したものをUploadしておきます。
glcorearb_win-zip.jpg(保存して拡張子変えてください)

使い方は、プログラム中で一箇所のみ、関数とポインタの実体を作る必要があるので、以下のように記述します。

#define WINDOWS_GL_EXT_DEFINE_FUNCTIONPTR 1
#define WINDOWS_GL_EXT_CREATE_FUNCTIONPTR 1
#include "glcorearb_win.h"
#include "wglext_win.h"
#undef WINDOWS_GL_EXT_CREATE_FUNCTIONPTR
#undef WINDOWS_GL_EXT_DEFINE_FUNCTIONPTR

それ以外の箇所で外部参照する際は、下記の様に記述します。

#define WINDOWS_GL_EXT_DEFINE_FUNCTIONPTR 1
#include "glcorearb_win.h"
#include "wglext_win.h"
#undef WINDOWS_GL_EXT_DEFINE_FUNCTIONPTR

注:上記ファイルは、余り(殆ど)チェックをしていないので、なにか不具合があるかもしれません。

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側の処理時間を数倍オーダーで高速化することは十分可能です。

Direct10/11のDebugRuntimeのトラブル

参照 [MSDN Blogs – DirectX 11.1 and Windows 7 Update]
http://blogs.msdn.com/b/chuckw/archive/2013/02/26/directx-11-1-and-windows-7-update.aspx

Direct10/11のDebugRuntimeをお使いの方で、2013年の2月あたりからVisualStudioでメッセージが見れなくなる現象が発生していたら、IE10の導入と共に導入される可能性の高い、KB 2670838の適用が原因かもしれません。
このパッチの適用はPIXにも影響があるそうで、結果的に、DirectX SDK(June2010)のデバッグランタイムと、KB 2670838で導入されるランタイムとで互換性が無くなるそうです。
解決方法としては、VisualStudio2012の導入もしくは、Windows8.0SDKの使用が挙げられています。

DirectX Control Panelのを使用する際にも注意が必要です

参照 [#AltDevBlog – Fixing the DirectX D3D debug layer]
http://www.altdevblogaday.com/2013/09/30/fixing-the-directx-d3d-debug-layer/

DirectX ControlPanelの互換性も無くなっており、新いDebugRuntimeを使用する際に、古いダイアログで設定しても正しく設定されないそうです。加えて、新しいDebugRuntimeのためのdxcpl.exeは、 C:\Windows\SysWOW64 もしくは、C:\Windows\System32 に入っており、ダイアログで設定できる項目も異なります。(AltDebBlogを参照のこと)
古いDXSDKを導入している方は、DirectX Control Panelはスタートメニューのショートカットに入っていますが、そっちじゃないそうです。
ちなみに、古いダイアログと新しいダイアログを見比べてみると、いくらか設定項目が増えているようです。一番大きな違いはFuture Level Limitの項目でしょうか。
Future Levelに応じた対応をするアプリケーションを開発する際には有用と思われます。