WPFアプリケーションでSharpDX経由でDirectX11を使用して、ウィンドウ全体でいくつかの画像をスムーズにスクロールしようとしています。
背景のビット:
画像は、ファイルから読み込まれ、テクスチャ配列として D3D に読み込まれた、時間によって変化するシグナル リターンであり、画像自体は一連のテクスチャ化された四角形 (非常に長い水平線内の一連の隣接する四角形) としてレンダリングされます。 )。ビューポートは、左端と右端が時間オフセットを表すように設定され、信号はこれらの時間の間にある期間表示されます。DirectX の実装は非常に単純です。クアッド頂点は一度生成され、フレームごとの非常に小さなバッファーには、現在の可視範囲/ズームなどに従ってスケールと変換を更新する単純な 2D ワールドビュー変換が含まれています。私が知る限り、D3D 実装はそうではありません。私が抱えている問題の一部 - それは実際には非常に単純なセットアップであり、非常に速くレンダリングされているようです (そうあるべきです)。
問題:
可視時間範囲がアニメーション化されている場合、画像のスクロールがスムーズではありません。画像は多くの場合「ジッター」し、率直に言って見栄えが悪い (ジッターがない場合は見栄えが良い)。
セットアップ:
DirectX は D3DImage にレンダリングされ、DX11 を機能させるために舞台裏で少し作業が行われます - このコードはhttps://sharpdxwpf.codeplex.com/から取得されます
グリッドに配置された複数の D3DImages (合計 4 つまで) があります (私は 2 つをテストしており、両方とも同じ期間の信号を含み、一緒にアニメーション化されています)。
これらの D3DImages は、ImageBrush のソースとして使用される DrawingVisual (カスタム FrameworkElement によってホストされる) によって描画されます。FrameworkElement オブジェクトは必要に応じてレンダリングをトリガーし、描画ビジュアルは D3D レンダリング呼び出しを処理し、D3DImage ブラシを使用してコントロールを埋める四角形を描画します。
時間範囲の値は、WPF の DoubleAnimation を使用してアニメーション化されます。現在表示されているビジュアルは、INotifyPropertyChanged を介してこの値にバインドされ、変更のたびに (InvalidateVisual を介して) レンダリングをトリガーします。
"Position" 値の変更 (可視時間範囲の開始) によってトリガーされる DrawingVisual レンダリング コード:
// update scene per-frame buffer first, with world-view transform
using (DrawingContext dc = RenderOpen()
{
_scene.Renderer.Render(_draw_args);
_image.Invalidate();
dc.DrawRectangle(_brush, null, new Rect(viewer.RenderSize));
}
私が試したこと:
問題が不安定なレンダリング リクエストのタイミングによるものなのか、それとも問題がレンダリング プロセスのさらに先にあるのかを判断するために、さまざまなことを試しました (そして多くのことを検索しました)。
- CompositionTarget.Rendering へのフック まず、CompositionTarget.Rendering を介して位置値の更新を実行し、残りのプロセスはそのままにします (つまり、要素はこの値の変更に反応します)。
(言うまでもなく、これは非常に「テスト」コードです):
Stopwatch rsw;
long last_time = 0;
void play()
{
rsw = new Stopwatch();
last_time = 0;
rsw.Start();
CompositionTarget.Rendering += rendering;
}
void rendering(object sender, EventArgs e)
{
double update_distance = rsw.ElapsedMilliseconds - last_time;
Position += 10 * update_distance;
last_time = rsw.ElapsedMilliseconds;
}
結果 - 単純な二重アニメーションより悪い。
- DispatcherTimer の使用。上記と同様に、タイマーとストップウォッチを組み合わせて使用すると、経過時間をもう少し正確に判断できます。興味深いことに、CPU 使用率が約 7% (コアの半分) から 0.7% に低下しました。
元の試みは、バリアント 1 と 2 とともに、すべて単一の値を更新しようとし、それによって関係者のレンダリングがトリガーされます。興味深いことに、レンダリング リクエストが Drawing ビジュアルに到達するまでの時間差をログに記録しました。実際には DisptacherTimer が最も一貫した結果をもたらしましたが (WPF アニメーションと CompositionTarget の両方で奇数フレームがドロップされました)、DisptacherTimer もアニメーションが最も不安定になりました。
画像を更新しますが、ビジュアルを無効にしません。ImageBrush のソースを更新すると、DrawingVisual を再レンダリングする必要なく、表示されるイメージが更新されます。この方法では、非常にぎくしゃくした結果が得られました。
フレームワーク要素自体の CompositionTarget.Rendering。これにより、ロットの中で最高の結果が得られましたが、まだ完全ではありません。ジッターが発生し、その後消散し、再び戻るだけです。このアプローチでは、DV を保持するフレームワーク要素が CompositionTarget.Rendering に接続し、ビジュアルが個別にアニメーション化されている現在の位置を照会します。私はこのアプローチでほとんど生きることができました。
画像を無効にする前に、D3D シーンがレンダリングされるまで待機を試みます (目に見える改善はありません)。
_scene.Renderer.Render(_draw_args);
_scene.Renderer.Device.ImmediateContext.End(q);while (!(_scene.Renderer.Device.ImmediateContext.IsDataAvailable(q))) Thread.Yield();
_image.Invalidate();
所見:
- これがパフォーマンスの問題だとは本当に思いません。私の開発マシンには優れたグラフィックス カード、8 i7 コアなどが搭載されており、これは単純なレンダリング操作です。
- これは、D3D と WPF レンダリングの間のある種の同期の問題のようですが、これを調査する方法がわかりません。
- 2 つの画像を並行してアニメーション化すると、(通常は) 2 つのうちの最初の画像でジッターがはるかに顕著になります。
- アニメーション中にウィンドウのサイズを積極的に変更すると、アニメーションは完全にスムーズになります (D3D コンテキストが常にサイズ変更されているため、多くの余分な作業が行われているにもかかわらず)。
編集
問題を切り分けるために、可能な限り元に戻しましたが、この問題は、D3DImage が WPF によって更新およびレンダリングされる方法の基本的なものであるようです。
表示される単純な三角形が画面全体にアニメーション化されるように、SharpDX サンプル ソリューションの WPFHost の例を変更しました。この例は、CompositionTarget.Rendering の DPFCanvas によってレンダリングされる DX10ImageSource でホストされます。
この例はこれ以上単純ではなく、WPF で D3DImage をレンダリングするときに得られる「金属に近い」ものです。レンダリング間の時間差から計算された値によって画面全体に変換された単一の三角形。なんらかの同期の問題であるかのように、スタッターが行き来します。それは私を困惑させますが、本質的にSharpDXをWPF内であらゆる種類のスムーズなアニメーションで使用できなくします。これは非常に残念です.
この問題の再現に興味のある方は、SharpDX サンプルをこちらから入手できます: https://github.com/sharpdx/SharpDX-Samples
WPFHost の例に次の簡単な変更を加えました。
void IScene.Render()
{
...
EffectMatrixVariable wv = this.SimpleEffect.GetVariableBySemantic("WorldView").AsMatrix();
wv.SetMatrix(this.WorldViewMatrix);
...
}
void IScene.Update(TimeSpan sceneTime)
{
float x = (float)sceneTime.Milliseconds * 0.001f - 0.5f;
WorldViewMatrix = Matrix.Translation(x, 0, 0);
}
およびシェーダー Simple.fx で:
float4x4 WorldViewTransform : WorldView;
PS_IN VS( VS_IN input )
{
PS_IN output = (PS_IN)0;
output.pos = mul(input.pos, WorldViewTransform);
output.col = input.col * Overlay;
return output;
}