カテゴリー別アーカイブ: DirectX

DirectXに関連する話題です

D3D12のRoot Signature 1.1について

前提知識として、D3D12の基本的なResource Bindingが理解できているものとして進めます。
Windows 10 Anniversary Update(build 14393 または Version 1607)より、RootSignature1.1が導入されました。引き続きRootSignature1.0が使用可能ではありますが、RootSignature1.1に変更することによってどんなメリットがあるのか見ていきたいと思います。
続きを読む

GPUViewで確認できるEventを簡単に追加する方法

GPUViewは、GPUとCPUのスレッド実行のタイミングを確認する際に特に有用なのですが、処理が複雑になると、一体どのDMAパケットが、何の処理をしているのかが分かりにくくなります。特に、他人の書いたプログラムをチェックしている際などは顕著です。こんなときに、Custom EventをマーカーとしてGPUViewのLogに埋め込むことが出来れば、CPU側が一体何をしているタイミングなのかを簡単に知ることが出来ます。
続きを読む

A Reconstruction Filter for Plausible Motion Blur について

テストを兼ねて実装してみました。

[参照]
A Reconstruction Filter for Plausible Motion Blur

ScatterとGatherについて

Blurを適用する際に考えられる方法として、自身のPixelのVelocityに従って、自身のPixelの色を他のPixelに拡散させる方法(Scatter)と、周辺のPixelの色とVelocityに従って、自身のPixelに周辺のPixelを重畳させる方法(Gather)の二つが考えられます。
Scatterの演算は、GPUの処理に置き換えれば、多数のPixelのBlending処理に置き換えられます。メモリの書き込み負荷が高いことが容易に想像がつきます。
一方で、Gatherの演算は、Blurの適用半径を大きくすれば、周辺の多数のPixelをSamplingしなければならず、メモリの読み込み負荷が高いです。また、Gather演算の場合は、SampleしたPixelのVelocityが小さかったり、向きが関係ない方向の場合は、対象PixelにBlurが届かず、Sampling処理自体が無駄になります。

本手法は、Gather手法ですが、周辺のPixelに対して一様にSamplingを行う代わりに、周辺に存在する、一番大きいVelocityを代表として、そのVelocityに基くSamplingを行うことで、Gather手法を用いつつも少ないSampling数で、大きなBlurの適用半径を実現するものです。

Tile Max と Neighbor Maxについて

上記で説明した、周辺に存在する、一番大きいVelocityを検出するために、この2つのバッファを作成します。まず、screen spaceにおけるVelocity Mapが存在するか、算出可能なことが前提です。
Tile Maxは、Blurの最大半径に相当する大きさを単位としてTileを作成し、それぞれのTile内で、Velocityが最大のVectorを保存します。従って、Tile Maxバッファの解像度は、(w,h)/size_of_a_tileとなります。
Neighbor Maxは、自身のTileと隣接する周辺8つのTileの中で、Velocityが最大のVectorを保存します。
後に、Gather演算を行う際に、該当するNeighbor Maxに格納されているVectorを参照すると、”周辺に存在する最も大きなVelocity”を取得することが出来ます。
本手法では、Neighbor Maxに格納されるVectorを”周辺で支配的に作用するVelocity”と見做し、BlurのReconstruction処理を、このベクトルに基いて行います。

Reconstruction処理について

Reconstruction処理では、Neighbor Maxに格納されているVectorに沿って、[-1,1]の範囲で直線上をサンプリングします。(ただし、Ghostingを避けるためにJitteringしています)
SamplingしたPixelと、基準となるPixelのZ値を考慮しつつ、互いにBlurで滲出する量を計算して最終結果としています。(詳細は論文参照のこと。pseudo code付きで解説されています。)

Samplingの際のアクセスパターンは、周辺のTileを共有するPixelで一様なので、Samplerのキャッシュヒットは大いに期待できます。
Blurの半径を大きくすれば、キャッシュのヒット率は低下すると思われますが、Sampleの数を増やせばヒット率が上昇すると思われます。各GPUの特性やそれぞれの状況に応じて調整できると思います。

考察

まず、Tile Max の算出には、Compute Shaderを用いるのが最も適切と思われます。Vectorの長さは常に正なので、uintキャストしてAtomicMaxのパターンです。
一方で、CSの使えない環境下では、PSでMipMapへの畳み込みを行う必要があると思います。
Neighbor Maxも同様の方法で求めることが出来ますが、周辺3×3のSamplingなので、1Threadで3×3ブロックをサンプリングして、比較したほうが速いです。
ここでは、PSを使うかCSを使うか迷うところなので、確かめるために実装してみました。しかし、Neighbor Maxを求めるための同機能のシェーダーコードをPS,CSに実装し、実行時間を比較をGTX680で行ってみましたが、有意な差は見出せませんでした。
したがって、どちらを使っても問題なさそうです。(使用したCSのnumthreadは[4x8x1]です。)

Blur

ちなみにこの論文の手法を元に改良した手法が既に発表されています。
[参照]
A Fast and Stable Feature-Aware Motion Blur Filter

フルスクリーン描画時のリフレッシュレート

DirectXで、フルスクリーン描画をする際のディスプレイのリフレッシュレートについてです。

最近のPC向けディスプレイ

近年のPC向けディスプレイは、さまざまな垂直同期周波数を持っているケースがあります。具体的には、60Hzのほかに、120Hzや、144Hzなどです。
これ等のリフレッシュレートは、垂直同期を有効にした場合でも、60Hzのディスプレイに比べ、高頻度に画面を書き換える機会を有しています。また、垂直同期を無効にした場合でも、同様に、高頻度に画面を書き換える機会を有しているため、ティアリングによる不快感を軽減(分断された絵の差分を最小限に)できる可能性があります。

ゲーム製作者側の留意点

ゲーム製作者側が留意しなくてはならないのは、当然ながら、垂直同期:有効=60Hzという前提でゲームを製作しないということです。これに関しては、殆どのPC向けのゲームでは、フレーム間の時間間隔は可変となっているので、問題になることは無いと思います。また、垂直同期を無効にしたケースではプログラム側は特に留意することは無いように思えます。
ただし一点だけ気をつけたほうが良いことがあります。それは、フルスクリーン描画を行う際の、RefreshRateの設定値です。デバイス作成時に設定するRefreshRateは、そのままディスプレイの垂直同期周波数として設定され、D3DPRESENT_INTERVALがどのように設定されても、その値が使用されます。
つまり、120Hzの垂直同期周波数をサポートするディスプレイでは、60Hz/120Hzいずれにも設定して、垂直同期を無効にしてフルスクリーン描画を行うことができます。その際に観測されるFPSは、基本的には全く差が無い状態となりますが、ディスプレイの駆動周波数は異なるため、プレイヤーの体験としては違うものになってしまいます。
では、どうすれば良いかですが、現在のディスプレイのRefreshRateを取得し、同一のRefreshRateで使用可能なスクリーン解像度を使用すると良いと思います。また、ゲーム内のメニューから明示的に設定できるようにしても良いと思います。

初期化の例

以下の例では、現在設定されているRefreshRateを尊重しつつ、19×10解像度のフルスクリーン描画の初期化を行うコードです。解像度が存在しない場合の処理などは省略されています。

if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
  return E_FAIL;

D3DDISPLAYMODE curDM;
g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &curDM);

D3DDISPLAYMODE targetDM;
UINT targetRes[2] = {1920, 1080};

ZeroMemory(&targetDM, sizeof(targetDM));

// check if there is 19x10 resolution with current refresh rate and color depth.
UINT cnt = g_pD3D->GetAdapterModeCount(D3DADAPTER_DEFAULT,
      curDM.Format);
for (UINT i=0; iEnumAdapterModes(D3DADAPTER_DEFAULT,
  curDM.Format, i, &dm);

  if (curDM.Format == dm.Format &&
      curDM.RefreshRate == dm.RefreshRate &&
      targetRes[0] == dm.Width &&
      targetRes[1] == dm.Height) {
    targetDM = dm;
    break;
  }
}

// you'll need proper handler...
if (targetDM.Width != targetRes[0])
  return E_FAIL;

static D3DPRESENT_PARAMETERS d3dpp = {
  targetDM.Width, 
  targetDM.Height,
  targetDM.Format,
  2,
  D3DMULTISAMPLE_NONE,
  0,
  D3DSWAPEFFECT_DISCARD,
  hWnd,
  0, //Full screen
  0,
  D3DFMT_UNKNOWN,
  0,
  targetDM.RefreshRate, // set the refresh rate that have been set.
   //D3DPRESENT_INTERVAL_ONE // enable v-sync
  D3DPRESENT_INTERVAL_IMMEDIATE // disable v-sync
};

// create a device.
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
 D3DCREATE_SOFTWARE_VERTEXPROCESSING,
 &d3dpp, &g_pd3dDevice ) ) )