web-dev-qa-db-ja.com

ビットマップパフォーマンス-最適化パターン

WPFでのビットマップ処理を最適化するためのいくつかのパターンを見つけました。ただし、各パターンをいつ使用するかはわかりません。これはよくある問題だと思うので、私が理解していることと推測していることを要約し、あなたの助けを求めます。パターンを追加できる場合、パターンの違いを説明、パターンがあれば説明CPUまたはGPUを使用し、それぞれをいつ使用するかそれらを組み合わせる方法、それは途方もない助けになるでしょう!

コンテキスト–画像の「グリッド」シナリオ:

私のアプリケーションは多くのビットマップ画像を表示する必要があります。画像は、行と列のグリッドのような構成で画面に表示されます(必ずしも、GridクラスまたはUniformGridクラスである必要はありません。WindowMediaPlayerのアルバムビューを考えてください)。画像は異なるグリッドセル間を移動する場合があります。任意のセルの一部の画像が他の画像に置き換えられる場合があります。画像はクリック可能で、コンテキストメニューを提供し、選択可能、ドラッグ可能である必要があります。言い換えると、「小さなバガーを1つの大きなビットマップに結合する」は、少なくとも単純ではありません。

パターン0:ハック

小さなバガーをビットマップに結合し(どのように?コンテキストを描画しますか?)、これを背景として使用します。これを、ヒット、コンテキストメニュー、イベントなどを処理する空のコンテンツを含む画像でオーバーレイします。

利点は、ここでは2つのビットマップについてのみ説明していることです。現在表示されているビットマップとそれを置き換える必要のあるビットマップです。これは本当に速いはずです。しかし、私の長年の経験は危険の危険信号を上げます。あなたのコメント?

パターン1:画像サイズを小さくする

サイズを変更する画像サイズが事前にわかっていて、パフォーマンスのために詳細(色)を失う準備ができている場合、これは簡単です。

  1. BitmapImage.DecodePixelWidthを使用してビットマップサイズを縮小します
  2. FormatConvertedBitmap.DestinationFormatを使用して色情報を減らします
  3. コントロールのスケーリング動作設定Image.StretchをStretch.Noneに設定します
  4. 画像のSetBitmapScalingModeをLowQualityに設定します。
  5. バガーを凍結する

コード ここ を参照してください。

パターン2:バックグラウンドプリフェッチ

このパターンは、ユーザーが画面上の画像を見つめていることを利用して、次の画像を表示する準備をすることができると考える場合に適用できます。プロジェクトの短所は、メモリオーバーヘッドに加えて、クライアントプロファイルだけでなく、.Net Framework 4ターゲットをサポートする必要があるため、クライアントにインストールが発生する可能性があることです。あなた自身が非同期プログラミングの苦痛に苦しむ必要があります。

このパターンでは、必要な数のイメージコントロールを正確に作成します。ビットマップを追加、移動、または削除する必要がある場合は、画像コントロールのBitmapSource(s)のみを変更します。 BackgroundWorkerタスクは、(おそらく上記の「画像サイズの縮小」パターンを使用して)BitmapSourceをプリフェッチし、それらをMemoryCacheに挿入する役割を果たします。

これを機能させるには、BitmapImageのCacheOptionをOnLoadに設定して、作業がバックグラウンドワーカーにオフロードされるようにする必要があります。

パターン3:コンテキストの描画

これは、MSDN WPFフォーラム ここ でMicrosoftSupportのSheldonZiaoによって提案されました。 DrawingContextの説明については、AdamNathanのWPF4Unleashedの494ページの第15章「2Dグラフィックス」を参照してください。理解できたとは言えません。答え here によると、これによりビットマップではなく、ジオメトリ図面の処理が改善されると思います。次に、これが画像のフォーカスとイベントの要件をサポートするとは思わない(フォーラムで要件をより適切に説明しないのは悪い)さらに、本の要約文が心配です。「DrawingContextの使用に注意してください。保持モードシステム内で操作しているという事実は変わりません。指定された描画はすぐには行われません。コマンドは、必要になるまでWPFによって保持されます。」つまり、ハンドラーが1つになると、「バックグラウンドプリフェッチ」のように並列処理を利用できなくなります。

パターン4:書き込み可能なビットマップ

MSDNのドキュメント ここ は、デュアルバッファシステムとして説明しています。UIスレッドがバッファを更新します。 WPFのレンダリングスレッドはこれをビデオメモリに移動します。

使用目的( ここ を参照)は、ディスプレイなどのビデオムービーで大きく変化するビットマップ用です。よくわかりませんが、これがハッキングされてバックグラウンドプリフェッチパターンと組み合わされ、グリッドシナリオで使用される可能性があります。

パターン5:キャッシュされたビットマップ

MSDNに関する情報はあまりありません( ここ )。 WPFフォーラムアーカイブ( ここ )で、「BitmapCache APIは、コンテンツを(ハードウェアでレンダリングするときに)ビデオメモリにキャッシュするように設計されているため、GPUに常駐し続けると説明されています。 。これにより、コンテンツを画面に描画するときにそのコンテンツを再レンダリングするコストを節約できます。」これは素晴らしいアイデアのようです。ただし、落とし穴とその使用方法はわかりません。

パターン6:RenderTargetBitmap

RenderTargetBitmapは、ビジュアルをビットマップに変換します。ここに関連があるかどうかはわかりません。 ここ を参照してください。

編集:Paul Hoeneckeの質問について:「私のアプリケーションは多くのビットマップ画像を表示する必要があります」と書いています。約800枚の画像を同時に表示する必要があることについては言及しませんでした。

私のSO質問 WPFビットマップパフォーマンス および 画像の表示を作成するにはどうすればよいですか?)に関連するパフォーマンスの問題について読むことができますWPFでもっと「きびきび」?

パターン1の説明を変更して、画像コントロールが作成または削除されないという概念を強調しました(より大きなグリッドまたはより小さなグリッドを表示する場合を除く)。それらのソースのみが、異なる、新しい、またはnullのBitmapSourceに設定されます。

編集この質問はWPFサポートフォーラム に投稿されており、MS担当者からの回答がいくつかあります。

36
Avi

以下のアプローチについてコメントを求める以外に、あなたの投稿で特定の質問を見つけることができません。上記のすべてを知っているとは言いませんが、WPFとSilverlightを使用して高性能UIを開発している間、私が知っていることを説明します。

パターン0:ハック。すべてを1つの画像に結合

できればこれは避けたいと思います。何千もの小さな画像の大きなラップパネルを表示したいようです。したがって、各画像はサムネイルになります(一度に数千の大きな画像を表示することはできないため)。結果として、私は組み合わせよりもキャッシュ/サイズ変更を提唱したいと思います。

パターン1:画像サイズを小さくする

一度に1,000枚の画像を画面に表示する場合は、利用可能な画面領域を検討してください。平均的なモニターは1280x1024ピクセル、つまり1.3Mピクセル強です。 1000の画像は、画像あたり1300ピクセル、つまり36 * 36の最大サイズを取得することを示しています。画像のサイズが32 * 32だとします。画面にレンダリングする画像サイズのサムネイルを作成してから、クリック(またはその他のアクション)でフルサイズの画像を表示する必要があります。

また、大きな画像のサイズを変更することによるレンダリングオーバーヘッドだけでなく、大きな画像をGPUに送信してサイズを変更することも考慮してください。そのデータを送信するには帯域幅が必要です。大きな画像は数メガバイトになる可能性がありますが、サイズ32 * 32のサムネイルは数キロバイトになる可能性があります。

動的なサイズ設定が必要な場合は問題ありませんが、複数のサムネイルを作成するか、その場で生成することを試す必要があります。

パターン2:バックグラウンドプリフェッチ

これは私が聞いたことがないテクニックですが、もっともらしいです。アプリケーションのオーバーヘッドはどれくらいですか? Image.Sourceプロパティを更新しますか、それとも新しい画像を作成し、テッセレーションし、レイアウトを実行し、情報を送信してGPUにレンダリングしますか?

上記はすべて、最終レンダリングを除いてCPUで発生します。 CPU側のオーバーヘッドを減らし、ソースを更新することで、何かに取り組むことができます。これをソースとしてWriteableBitmapと組み合わせると、パフォーマンスがさらに向上する可能性があります(以下を参照)。

パターン3:コンテキストの描画

さて、これはすべて、古いGDI OnPaintのようなものではない「OnPaint」スタイルの構文を使用して保持モードの描画呼び出しをキューに入れることを可能にします。私の経験では、OnRenderはパフォーマンスを改善しません。ただし、描画対象と描画時期を細かく柔軟に設定できます。OnRenderは、DrawImage関数を備えたコンテキストを提供し、Imageコントロールを必要とせずにBitmapSourceをレンダリングパイプラインに描画できるようにします。オーバーヘッドはある程度削除されますが、Pattern0で見られるのと同様の問題が発生します(レイアウトが失われ、すべての画像の位置を計算する必要があります)。これを行う場合は、パターン0を呼び出すことをお勧めします。

パターン4:書き込み可能なビットマップ

WriteableBitmapsは、WPF内で少し使用され、非常に強力なサブシステムです。私はそれらを非常に効果的に使用して、大量のデータをリアルタイムでレンダリングできるグラフ作成コンポーネントを作成します。 WriteableBitmapEx codeplexプロジェクトをチェックすることをお勧めします開示、私はこれに一度貢献しましたそしてそれを他のパターンと組み合わせることができるかどうかを確認します。具体的には、キャッシュされたビットマップを画像のビットマップソースに書き込むことができるBlit関数です。

たとえば、良いテクニックはパターン1 + 2 +4かもしれません。

画面上のグリッドコントロールの設定された場所に、N個の画像コントロールのグリッドを配置できます。これらはそれぞれ静的であり、スクロールして表示されないため、作成/削除は行われません。次に、これに加えて、画像のサイズを変更し、各画像のSourceプロパティとして設定されているWriteableBitmapに書き込みます。スクロールしながら、次/前のサムネイルを取得し、WriteableBitmapEx.Blitを使用してソースを更新します。捕虜!仮想化され、キャッシュされた、マルチスレッドのイメージングの良さ。

パターン5:キャッシュされたビットマップ

これは、上で説明したように、Microsoftが1 + 2 +4を実行しようとする試みです。レイアウト(CPU側)、テッセレーション(CPU側)、保持モードのレンダリング命令をGPU(CPU側)に送信し、レンダリング(GPU側)した後、レンダリングされた要素のラスターイメージをキャッシュします。次のレンダリングパスで使用されます。はい、WPFについてほとんど知られていない事実は、素晴らしいGPU搭載エンジンは、CPUでほとんどの作業を行うため、非常に遅いということです:P

BitmapCacheを試して、そのパフォーマンスを確認します。注意点があります。UIElementを更新するときにキャッシュを再作成する必要があるため、静的要素のパフォーマンスは動的要素よりもはるかに優れています。また、これを使用してもパフォーマンスが大幅に向上することはありませんが、WriteableBitmapスタイルの手法では桁違いに向上する可能性があります。

パターン6:RenderTargetBitmap

この最後のテクニックを使用すると、UIElementをビットマップにレンダリングできます(ご存知のとおり)が、興味深いのは、これが貧弱なサムネイルジェネレーター(またはサイズ変更)を実行できることです。たとえば、フルサイズの画像のBitmapSourceを使用して画像を設定します。次に、Imageコントロールのサイズを32 * 32に設定し、ビットマップにレンダリングします。出来上がり!いくつかのスワッピング(パターン2)および/または書き込み可能なビットマップと組み合わせて使用​​するBitmapSourceサムネイルがあります。

最後に、あなたが持っている要件がWPFを限界まで押し上げると言いたかったのですが、それを実行する方法はいくつかあります。私が言ったように、私はWriteableBitmapである素晴らしい回避策を使用して、画面上に数千または数百万の要素を一度にレンダリングするシステムを構築しています。標準のWPFルートを使用すると、パフォーマンスが低下するため、これを解決するにはエキゾチックな処理を行う必要があります。

私が言ったように、私の推奨は1 + 2 +4です。サムネイルのサイズを変更する必要がありますが、間違いありません。画像コントロールの静的グリッドを作成し、ソースを更新するというアイデアは非常に優れています。 WriteableBitmap(具体的にはWriteableBitmapEx blit関数)を使用してソースを更新するというアイデアも、検討する価値のあるものです。

幸運を!

19
Dr. ABT