33

私は現在ゲームに取り組んでおり、背景画像付きのメイン メニューが必要です。

ただし、この方法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()ということは、それは大きな画像を描くのには使えないということですか? その場合、ゲームのパフォーマンスを向上させるにはどうすればよいですか?

4

4 に答える 4

135

はい、遅すぎます。

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

GDI+ の代わりに GDI を使用するように WinForms を強制することが可能であり、Graphicsその上に別のレンダリング ツールキット (Direct2D など) を重ねることができます。しかし、それは単純ではありません。私はこれを Paint.NET で行いますGdiPaintControl。SystemLayer DLL で呼び出されるクラスで Reflector などを使用することで何が必要かを確認できますが、あなたが行っていることについては、最後の手段と考えています。

ただし、使用しているビットマップ サイズ (800x1200) は、高度な相互運用機能に頼らなくても GDI+ で十分に機能するはずです。役立つヒントを次に示します。

  • への呼び出しで不透明なビットマップ (アルファ/透明度なし) を使用しているGraphics.DrawImage()場合、特にそれがアルファ チャネル付きの 32 ビット ビットマップである場合 (ただし、不透明であることはわかっているか、気にしない場合)、before に設定Graphics.CompositingModeします。CompositingMode.SourceCopyを呼び出しDrawImage()ます (後で必ず元の値に戻してください。そうしないと、通常の描画プリミティブが非常に見苦しくなります)。これにより、ピクセルごとの余分なブレンド計算が大幅にスキップされます。
  • Graphics.InterpolationModeのような値に設定されていないことを確認してくださいInterpolationMode.HighQualityBicubic。を使用NearestNeighborするのが最も高速ですが、ストレッチがある場合は (正確に 2 倍、3 倍、4 倍などでストレッチしない限り) 見栄えがよくない場合がありますBilinearが、通常は適切な妥協点です。NearestNeighborビットマップのサイズが描画先の領域 (ピクセル単位) と一致する場合以外は、何も使用しないでください。
  • Graphicsで与えられたオブジェクトに常に描画しますOnPaint()
  • 常に で描画を行ってくださいOnPaint。領域を再描画する必要がある場合は、 を呼び出しますInvalidate()。すぐに描画する必要がある場合は、Update()afterを呼び出します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()」および「常にGraphics指定されたものを常に使用するOnPaint()」の結果)
  • Paint イベント ハンドラは使用しないことをお勧めします。代わりに、OnPaint()から派生する必要がある作成中のクラスに実装しますControlPictureBoxまたは などの別のクラスから派生さUserControlせると、値が追加されないか、追加のオーバーヘッドが追加されます。(ところでPictureBox、誤解されることがよくあります。おそらく、使用することはほとんどないでしょう。)

それが役立つことを願っています。

于 2012-06-14T01:01:39.680 に答える
7

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

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

したがって、ビットマップをホストする Control のコンストラクターで 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 が使用されています。どちらの場合もほぼ同じ速度でマウスを動かしました。

ここに画像の説明を入力

于 2018-12-08T16:57:57.547 に答える
3

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

于 2012-06-13T18:08:41.140 に答える
3

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

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

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

于 2012-06-13T18:09:19.363 に答える