web-dev-qa-db-ja.com

大きな画像に対してGraphics.DrawImageは遅すぎるのですか?

私は現在ゲームに取り組んでおり、背景画像のあるメインメニューが欲しいです。

ただし、メソッドGraphics.DrawImage()は非常に遅いことがわかりました。私はいくつかの測定を行いました。 MenuBackgroundが解像度800 x 1200ピクセルのリソースイメージであるとします。それを別の800 x 1200ビットマップに描画します(すべてを最初にバッファービットマップにレンダリングし、次にそれをスケーリングして最後に画面に描画します。これが、複数のプレーヤーの解像度の可能性に対処する方法です。ただし、影響はありません。何らかの方法で、次の段落を参照してください)。

だから私は次のコードを測定しました:

_Stopwatch SW = new Stopwatch();
SW.Start();

// First let's render background image into original-sized bitmap:

OriginalRenderGraphics.DrawImage(Properties.Resources.MenuBackground,
   new Rectangle(0, 0, Globals.OriginalScreenWidth, Globals.OriginalScreenHeight));

SW.Stop();
System.Windows.Forms.MessageBox.Show(SW.ElapsedMilliseconds + " milliseconds");
_

結果は私には驚くほど静かです-Stopwatchは_40 - 50 milliseconds_の間の何かを測定します。また、描画されるのは背景画像だけではないため、メニュー全体が表示されるまでに約100ミリ秒以上かかり、観察可能な遅延が生じます。

Paintイベントによって指定されたGraphicsオブジェクトに描画しようとしましたが、結果は_30 - 40 milliseconds_でした-あまり変更されていません。

それでは、Graphics.DrawImage()は大きな画像の描画には使用できないということですか?その場合、ゲームのパフォーマンスを向上させるにはどうすればよいですか?

30
Miroslav Mares

はい、遅すぎます。

Paint.NETを開発しているときに、数年前にこの問題に遭遇しました(最初から、実際には、かなりイライラしていました!)。再描画するように指示された領域のサイズではなく、常にビットマップのサイズに比例していたため、レンダリングのパフォーマンスはひどいものでした。つまり、ビットマップのサイズが上がるとフレームレートが下がり、OnPaint()を実装してGraphics.DrawImage()を呼び出すと、無効/再描画領域のサイズが下がるので、フレームレートは上がりません。 800x600などの小さなビットマップは常に問題なく機能しましたが、大きな画像(2400x1800など)は非常に低速でした。 (とにかく、前の段落では、パフォーマンスに悪影響を与える可能性のあるいくつかの高価なバイキュービックフィルターを使用したスケーリングなど、余分なことは何も行われていなかったと想定できます。)

WinFormsにGDI +の代わりにGDIを使用するように強制し、Graphicsオブジェクトの作成をシーンの背後でさえ回避することができます。その時点で、その上に別のレンダリングツールキットをレイヤー化できます(Direct2Dなど) )ただし、それは単純ではありません。Paint.NETでこれを行い、SystemLayer DLLのGdiPaintControlというクラスでReflectorのようなものを使用することで何が必要かを確認できますが、あなたがしていることについては、最後の手段。

ただし、使用しているビットマップサイズ(800x1200)は、300 MHzのPentium II程度の低さを対象としない限り、高度な相互運用に頼らなくてもGDI +で十分に機能します。ここに役立つかもしれないいくつかのヒントがあります:

  • Graphics.DrawImage()の呼び出しで不透明なビットマップ(アルファ/透明度なし)を使用している場合、特にそれがアルファチャネル付きの32ビットビットマップである場合(ただし知っている不透明、または気にしない)、次にDrawImage()を呼び出す前に_Graphics.CompositingMode_を_CompositingMode.SourceCopy_に設定します(後で元の値に戻してください。そうしないと、通常の描画プリミティブで非常に醜く見えます)。これにより、ピクセルごとの多くの余分なブレンド演算がスキップされます。
  • _Graphics.InterpolationMode_が_InterpolationMode.HighQualityBicubic_のように設定されていないことを確認してください。 NearestNeighborを使用するのが最も高速ですが、ストレッチがある場合は(2倍、3倍、4倍などでストレッチしない限り)見栄えがよくない場合があります。通常、Bilinearは適切な妥協案です。ピクセル単位でビットマップサイズが描画している領域と一致する場合は、NearestNeighbor以外のものを使用しないでください。
  • 常にOnPaint()で指定されたGraphicsオブジェクトに描画します。
  • 常にOnPaintで描画してください。領域を再描画する必要がある場合は、Invalidate()を呼び出します。すぐに描画する必要がある場合は、Update()の後にInvalidate()を呼び出します。 WM_Paintメッセージ(これによりOnPaint()が呼び出される)は「優先度の低い」メッセージであるため、これは合理的なアプローチです。ウィンドウマネージャーによるその他の処理は最初に行われるため、そうしないとフレームスキップやヒッチングが大量に発生する可能性があります。
  • _System.Windows.Forms.Timer_をフレームレート/ティックタイマーとして使用すると、うまく機能しません。これらはWin32のSetTimerを使用して実装され、結果としてWM_TIMERメッセージが発生し、その結果_Timer.Tick_イベントが発生します。WM_TIMERは、メッセージキューが空の場合にのみ送信される別の優先度の低いメッセージです。 _System.Threading.Timer_を使用し、次にControl.Invoke()を使用して(正しいスレッドにいることを確認するため)、Control.Update()を呼び出す方がよいでしょう。
  • 一般に、Control.CreateGraphics()は使用しないでください。 (「常にOnPaint()で描画」および「OnPaint()によって指定されたGraphicsを常に使用」の当然の結果)
  • Paintイベントハンドラーは使用しないことをお勧めします。代わりに、作成しているクラスにOnPaint()を実装します。これはControlから派生する必要があります。他のクラス、例えばPictureBoxまたはUserControlは、値を追加しないか、オーバーヘッドを追加します。 (ところで、PictureBoxは誤解されがちです。おそらくほとんど使用したくありません。)

お役に立てば幸いです。

109
Rick Brewster

GDI +は決して速度の悪魔ではありません。深刻な画像操作は通常、ネイティブサイドに入る必要があります(pInvoke呼び出し、およびLockBitsを呼び出して取得したポインターを介した操作)。

XNA/DirectX/OpenGLを調べましたか?これらはゲーム開発用に設計されたフレームワークであり、WinFormsやWPFなどのUIフレームワークを使用するよりもはるかに効率的で柔軟です。ライブラリはすべてC#バインディングを提供します。

BitBlt などの関数を使用してネイティブコードにpInvokeすることもできますが、マネージコードの境界を越えることに関連するオーバーヘッドもあります。

3
Ed S.

GDI +はおそらくゲームに最適ではありません。 DirectX/XNAまたはOpenGLは、可能なグラフィックアクセラレーションを利用し、非常に高速であるため、推奨されます。

3
Ani

これは古代の質問であり、WinFormsは古代のフレームワークですが、偶然発見したことを共有したいと思います。ビットマップをBufferedGraphicsに描画し、後でOnPaintが提供するグラフィックスコンテキストにレンダリングしますwayビットマップをOnPaintのグラフィックスコンテキストに直接描画するよりも高速です-少なくとも私のWindows 10マシンでは。

データを2回コピーすると少し遅いと直感的に思っていたので、これは驚くべきことです(したがって、これは通常、手動でダブルバッファリングを実行する場合にのみ正当化されると思いました)。しかし、明らかに、BufferedGraphicsオブジェクトには、より高度な処理が行われています。

したがって、ビットマップをホストするコントロールのコンストラクターにBufferedGraphicsを作成します(私の場合、フルスクリーンビットマップ1920x1080を描画したいと思います)。

        using (Graphics graphics = CreateGraphics())
        {
            graphicsBuffer = BufferedGraphicsManager.Current.Allocate(graphics, new Rectangle(0,0,Screen.PrimaryScreen.Bounds.Width,Screen.PrimaryScreen.Bounds.Height));
        }

onPaintで使用します(OnPaintBackgroundを無効にします)

    protected override void OnPaintBackground(PaintEventArgs e) {/* just rely on the bitmap to fill the screen */}

    protected override void OnPaint(PaintEventArgs e)
    {   
        Graphics g = graphicsBuffer.Graphics;

        g.DrawImage(someBitmap,0,0,bitmap.Width, bitmap.Height);

        graphicsBuffer.Render(e.Graphics);
    }

単純に定義する代わりに

    protected override void OnPaintBackground(PaintEventArgs e) {/* just rely on the bitmap to fill the screen */}

    protected override void OnPaint(PaintEventArgs e)
    {   
        e.Graphics.DrawImage(someBitmap,0,0,bitmap.Width, bitmap.Height);
    }

結果として生じるMouseMoveイベントの頻度の比較については、次のスクリーンショットを参照してください(非常に単純なビットマップスケッチコントロールを実装しています)。上部はビットマップが直接描画されるバージョンで、下部はBufferedGraphicsが使用されます。どちらの場合も、マウスをほぼ同じ速度で動かしました。

enter image description here

2
oliver