2

これらの考慮事項の何百ものバリエーションがネット全体に投稿されていることを私は知っています。しかし、私の正確な問題に対処するものは何も見つかりませんでしたので、私が光を見るのを手伝ってくれることを願っています。

私は現在、OpenGLを使用したJavaでの2Dゲーム開発をいじっています。使用されている言語とグラフィックライブラリは、より一般的な性質を持っているため、私の質問にはそれほど関係ありません。

適度に重いグラフィック(主にビットマップテクスチャ)と、場合によってはさらに重いゲームロジック(AI、衝突検出など)を持つゲームに、多かれ少なかれそのまま使用できる汎用ゲームループを設計しようとしています。

基本的に、更新(位置、速度、およびその他のゲーム関連の更新)およびレンダリング(更新された位置のテクスチャ/フレーム)できるオブジェクトのリスト/配列/バッファーを可能な限りスムーズかつ効率的に維持することを期待しています。

1)更新+レンダリング用の1つのスレッド

私は1つのスレッド(ユーザー入力を数えるときは2つ)だけを使用してシーケンシャルソリューションを試し、破棄しました。

  • 変更を計算し、バッファオブジェクトを更新します
  • 更新された位置のテクスチャをバックバッファにレンダリングします
  • バックバッファを前面に交換します

明らかに、スワップバッファがハードウェアでブロックされている間、多くの優れたコンピューティング時間が無駄になります。これには、より効率的なソリューションが必要です。

2)更新用に1つのスレッド、レンダリング用に1つのスレッド

プログラムを更新スレッドとレンダリングスレッドに分割し、共有バッファーへのアクセスを同期することで、かなり安定したフレームレートを確保できるはずです。共有バッファへのアクセスの同期はさまざまな方法で実行できますが、すべてに共通する点が1つあります。それらはすべてスレッドの同時実行を禁止しています。これは公正なトレードオフかもしれませんが、何が同期を必要とするのか疑問に思っています。

3)2と同じですが、同期はありません

並行スレッドの不注意な実装によって引き起こされる可能性のある問題の多くを理解しています。潜在的なデッドロックを引き起こすプロデューサー/コンシューマー、リーダー/ライター、および同様の状況。ただし、次の基準が満たされている場合(および満たされている必要がある場合)に共有データの同期を確保する必要がある理由がわかりません。

  • レンダリングスレッドは共有バッファからのみ読み取ることができます
  • 更新スレッドは、共有バッファーからの読み取りと共有バッファーへの書き込みの両方を行うことができます(したがって、これが唯一の「ライター」です)
  • ゲームの実行中、共有バッファが空になったりいっぱいになったりすることはありません
  • スレッドは決してスリープしません
  • レンダリングは100%正確である必要はありません。一部の共有オブジェクトがまだ更新されていないために、他のオブジェクトより1つの更新ステップ(つまり、約10〜20ミリ秒)遅れている場合は、誰も気付かないはずです。

-

だから...私がここで見逃している明らかなことは何ですか?この設定で同期が必要なのはなぜですか?

  • 適切に同期されていない場合、スレッドはデータをキャッシュして問題を引き起こす可能性がありますか?
  • または、不幸な時間に書き込みスレッドが中断された場合、データがどういうわけか文字化けする可能性がありますか?
  • それとも、提案されたステートジーが役に立たなくなるという一般的な問題はありますか?

どんな考え、コメント、または提案も大歓迎です。または、この特定の質問がすでに他の場所で対処されている場合は、参照していただければ幸いです。

4

3 に答える 3

3

私はこれをテストするために少し時間を費やすことに決めました、そして私はこのサイトから非常に多くの良い答えを持っていたので、私はこの質問を完了するためにこれを投稿すると思いました。多分誰か他の人がその情報が役に立つと思うでしょう。

更新とレンダリングが別々のスレッドで実行される単純なスプライトレンダリングアプリケーションの3つの異なる実装を作成しました。

1)同期なし

レンダラーは最大60FPSで動作します。アップデータは可能な限り高速に実行されます。更新およびレンダリングするスプライトは、両方のスレッドで共有されるリストに存在します。同期が存在しないため、スレッドはデータを自由に読み書きできます。

2)共有データの同期

レンダラーは最大60FPSで動作します。アップデータはレンダラーと同じペースで実行されます。更新およびレンダリングするデータは、両方のスレッドで共有されるリストに存在します。リストは完全に同期されています。アップデータは、リスト内のすべてのスプライトを更新します。次に、レンダラーはリストにアクセスし、すべてのスプライトを画面にレンダリングします。

3)同期はダブルレンダリングキューを使用しました

レンダラーは最大60FPSで動作します。アップデータはレンダラーと同じペースで実行されます。アップデータはリストを更新し、スプライトを2つのレンダリングキューのパッシブキューに送信します。一方、レンダラーはアクティブなレンダリングキューでスプライトをレンダリングします。アップデータが最後のオブジェクトをパッシブレンダーキューにコピーすると、アクティブキューとパッシブキューを入れ替えようとします。レンダラーが前のキューのレンダリングを終了していない場合、スワップはブロックされます。これが唯一のブロッキング同期です。レンダラーが現在のフレームを終了するとすぐにスワップが行われ、レンダラーは新しいキューのレンダリングを開始でき、アップデーターは更新を開始して他の(現在はパッシブな)キューに送信できます。

各メソッドで3つのテストを実行し、1秒間に更新とレンダリングが実行された回数を計測しました。

テスト1:
スプライトの数が十分に少ないため、レンダラーはフルスピード(60 FPS)で実行できます
。各スプライトの更新ロジックが重すぎて、アップデーターがペースを維持できません。

テスト2:
スプライトの数が多すぎるため、レンダラーをフルスピードで実行できません。
各スプライトの更新ロジックは非常に単純なので、追いつく以上のことができます。

テスト3:
スプライトの数は、レンダラーを最大速度より少し低く実行し続けるのに十分な数です。
各スプライトの更新ロジックは、アップデーターをレンダラーの最大速度より少し低く実行し続けるのに十分なほど重いです。

結果

同期なし-テスト1:
レンダラーは1秒間に60回実行されます(最大速度)。
アップデータは1秒間に45回実行されます。

同期なし-テスト2:
レンダラーは1秒間に24回実行されます。
アップデータは1秒間に1150回実行されます。

同期なし-テスト3:
レンダラーは1秒間に58回実行されます。
アップデータは1秒間に51回実行されます。

共有データの同期-テスト1:
レンダラーは1秒間に23回実行されます(最大速度)。
アップデータは1秒間に24回実行されます。

共有データの同期-テスト2:
レンダラーは1秒間に23回実行されます。
アップデータは1秒間に23回実行されます。

共有データの同期-テスト3:
レンダラーは1秒間に17回実行されます。
アップデータは1秒間に17回実行されます。

ダブルキューの同期-テスト1:
レンダラーは1秒間に43回実行されます(最大速度)。
アップデータは1秒間に43回実行されます。

二重キューの同期-テスト2:
レンダラーは1秒間に24回実行されます。
アップデータは1秒間に24回実行されます。

二重キューの同期-テスト3:
レンダラーは1秒間に54回実行されます。
アップデータは1秒間に54回実行されます。

結論

ご指摘のとおり、Jirkaは、ライターが1つしかない場合に同期を行わない方法は無害に見えても、望ましくない副作用が発生する可能性があり、レンダリングされたフレームの一貫性が保たれません。

デュアルキューを使用したレンダリングが、1つの大きな共有スプライトリストを使用したレンダリングよりも高速であることは、驚くことではありません。ただし、驚くべきことに、更新せずに複数のフレームをレンダリングしたり、レンダリングせずに複数回更新したりしても何も得られないという事実を考慮すると、デュアルキュー方式の最終結果は実際には非同期方式と同じくらい高速です。

言うことも試すこともできることは他にもあるでしょうが、私はすでに十分に見ました。更新/レンダリングシステムに非同期アクセスを使用することは二度と考えません。

于 2012-07-17T22:01:49.800 に答える
1

(多くの)同期なしで、レンダリングスレッドと更新スレッドを分離することができます。チェックアウト

http://blog.slapware.eu/game-engine/programming/multithreaded-renderloop-part1/

http://blog.slapware.eu/game-engine/programming/multithreaded-renderloop-part2/

説明と実装(ソース+バイナリ)。それは簡単ではありませんが、まさにあなたが望むものです。

于 2012-10-11T03:12:52.710 に答える
0

非同期アプローチは問題なく機能します。これがストックのIntelハードウェアである場合は2倍になります1。私はまだそれを使用しません。

非同期の同時実行性が確実に機能することはほとんどない理由は、プロセッサがメインRAMとキャッシュの間でストアとロードを実行するタイミングを自由に使えるためです。これにより、ほとんどすべての非同期プロトコルが破壊される可能性があります。ただし、あなたが言うように、アプリケーションでシーンが突然変更されないかどうかに気付く人はいないでしょう。すべてのデータはRAMに送られ、遅かれ早かれ他のスレッドに表示されるようになります。

ただし、それがいつ、どのシーケンスになるかは保証されません。そのため、2つの後続のフレーム(シーンまたはその照明の突然の変更の前後)を奇妙な方法で混合する理論的な可能性が残ります。

プログラミング言語とそのメモリモデル(C ++11より古いC++だと思いますか?)によっては、パフォーマンスへの影響が無視できる適切なメモリバリアが保証された副作用を持つ軽量の同期プリミティブが見つかる可能性があります。これが私が出発点としてお勧めするものです。極端なパフォーマンスの最適化(安全であると証明できるものを超えて)は、エンジンを最適化する最後の段階である必要があります。


1)i86がストアを再注文することはありません。私はこれがどこにも文書化されていないと思います、そして私はそれに頼りたくありません。読み取りを並べ替えることができるので、とにかくシナリオでは役に立ちません。

于 2012-07-09T21:59:11.317 に答える