StoreAppの垂直同期

Store Appを開発する際のSwapChainに関連する制限事項についてです。

Store Appを開発する際のSwapChainに関連する制限事項

Store AppでDirectX11のSwapChainを作成する場合は、SwapEffectにDXGI_SWAP_EFFECT_FLIP_SEQUENTIALを指定する必要があります。Desktop Appで一般的に使用されている、DXGI_SWAP_EFFECT_DISCARDは使用できません。
加えてIDXGISwapChain::Present()を呼び出す際に、第一引数に0を指定してもVSyncを非同期にして描画することができません。なぜこのような制限が課されているか確かなことはわかりませんが、おそらくモバイルデバイスを想定した制限だと思われます。SwapEffectをDXGI_SWAP_EFFECT_FLIP_SEQUENTIALに限定することで、Backbufferの数を2に限定することができ、意図しないメモリの消費を避けることができます。また、VSyncの同期を強制することで、アプリケーションによる意図しない過剰な描画を抑制することができ、電力の不要な消耗を避けることができます。
このため、Store AppではVSyncを切った状態でアプリケーションの動作時間を計測することが出来ないので、手短にDirectXを使用したアプリケーションのパフォーマンスを知ることができません。FPSの計測は、CPU、GPU双方どちらにボトルネックがある場合でも非常に有用な手段なので、何とか使いたいものですが現状は不可能のようです。ちなみにDesktop Appでは、DXGI_SWAP_EFFECT_FLIP_SEQUENTIALを設定しても、Present()の第一引数でVSyncの制御が可能でした。

CPU処理時間の計測に際しての注意点

上記の通りVSyncの同期を切ることができないため、CPU処理がVSyncのタイミングに対して1フレーム以上先行する場合は、IDXGISwapChain::Present()の呼び出しがブロックされる可能性があります。これは、現在のBackBufferに対する描画をすでに完了した上で、次のフレームの描画を行い、Present()を呼び出すまでの間にVSyncによるBackbufferのFlipが行われない場合に発生します。したがって、CPUの処理時間の計測範囲にPresent()の呼び出しを含むと、想定外の処理時間が計測される可能性があります。また、処理時間の計測を行っていない状態でも、CPU処理の意図しないブロッキングが発生する可能性があるので、Present()の呼び出しには注意が必要です。

ID3D11Queryを使用した時間計測

GPU処理時間の計測では、上記の理由でFPSによる計測ができないため、ID3D11Queryオブジェクトを使用した計測が有効なはずです。しかしこの計測方法も、私の手元のWin8マシンで下記のようなコードで試してみたところ、うまく動作しませんでした。

m_query.SetCurrentTimerSet(++cnt%10);
m_query.BeginDisjointQuery(m_context.Get());
m_query.SetTimestamp(m_context.Get(), 0);

ID3D11RenderTargetView *rtv = m_renderTargetView.Get();
m_context->OMSetRenderTargets(1, &rtv, nullptr);

static const float cl[4] = {1.0f, 0.0f, 0.0f, 1.0f};
static const float cl[4] = {1.0f, 0.0f, 0.0f, 1.0f};
for (int i=0; i<50; i++)
  m_context->ClearRenderTargetView(rtv, cl);

m_context->OMSetRenderTargets(0, nullptr, nullptr);

m_query.SetTimestamp(m_context.Get(), 1);
m_query.EndDisjointQuery(m_context.Get());
m_context->Flush();

DXGI_PRESENT_PARAMETERS parameters = {};
parameters.DirtyRectsCount = 0;
parameters.pDirtyRects = nullptr;
parameters.pScrollRect = nullptr;
parameters.pScrollOffset = nullptr;

DX::ThrowIfFailed(m_swapChain->Present1(1, 0, &parameters));

このコード自体はRTを50回クリアするもので、処理自体は1ms以内で終了すると思われるのですが、計測すると16msが計測されました。明らかにVSyncインターバルの時間です。
いろいろ試した結果、Present()呼び出しの直後に、CPUをVSyncインターバル時間以上サスペンドさせる、下記のコードを追加することで正しいと思われるGPU処理時間が計測されました。どうやらPresent()によってCPU処理にブロックが発生するケースでは、うまく計測されないようです。計測の際には注意が必要です。

concurrency::event ev;
ev.wait(17);

この問題は上記のVSyncが切れない問題とは異なり、今後改善される可能性が高いですが、現在のところ、NVIDIA, AMDどちらのGPUを使用したケースでも発生します。おそらくDirectX側のレイヤーの問題だと思われます。

コメントを残す

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

WordPress.com ロゴ

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

Twitter 画像

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

Facebook の写真

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

Google+ フォト

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

%s と連携中