CPU-GPUメモリデータフロー[終了]


16

私は初心者のグラフィックプログラマで、最近疑問に思っています-モデルデータ(メッシュとマテリアル)はアプリケーション(CPUメモリ)からグラフィックカード(GPUメモリ)にどのように流れますか?静的モデル(建物など)があり、一度ロードしてセットアップすると、アプリの有効期間中は変更されません。

  • そのデータはGPUメモリに一度だけ送信され、永遠にそこに座っていますか?
  • モデルが各フレームで実際にレンダリングされるとき、GPUプロセッサはGPUメモリから毎回そのデータをフェッチする必要がありますか?つまり、2つのモデルをそれぞれ複数回レンダリングした場合、最初のモデルを複数回レンダリングしてから2番目のモデルを複数回レンダリングするか、最初のモデルを1回だけレンダリングし、2番目のモデルを1回だけレンダリングして、そのようにインターリーブし続けましたか?この意味で、この質問を「内部GPUデータフロー」と呼ぶことができます。
  • 明らかにグラフィックカードのRAMは限られています-1フレームのレンダリングに必要なすべてのモデルデータを保持できない場合、フレームごとにCPU RAMから(一部)を取得し続けると思いますが、それは正しいですか?

インターネットにはこれに関する本や資料がたくさんあることは知っていますが、このデータフローを管理する方法に関する簡単な一般的なガイドラインがあります(何をいつ送信し、いつ、どのようにレンダリングするか)。

編集: 私は1つの区別をするのを忘れ:ありますGPUにデータを送信し、そこの設定/電流としてバッファを結合。後者はデータフローを引き起こしますか?

Edit2: Raxvanの投稿を読んだ後、いくつかのアクションを区別したいと思います。

  • 初期化によるバッファ作成
  • バッファデータの更新(データがCPUラムに保持されている場合は簡単で、GPUラムに保持されている場合はGPUからCPUラムにフェッチする必要があります)
  • バッファをアクティブとしてバインドします(これは、次の描画呼び出しでこのバッファをレンダリングすることをAPIに伝える方法であり、それ自体では何もしませんか?)
  • API draw call(ここで実際に何が起こるかをお聞きしたい)

私は決して専門家ではありませんが、VAOとVBOで最新の(つまり即時ではない)OpenGLを使用している場合、データはglBufferファミリーのいずれかを使用するたびにGPUに送信され、VRAMに保存されます。その後、描画するたびに、関連する頂点がVRAMからフェッチされてレンダリングされます。移動するモデルの場合、静的に保存し、マトリックスを使用してモデル空間からワールド/カメラ空間に移動する傾向があります。最後の点については、RAMが不足するとどうなるかわかりません。私の推測では、VRAMが不足すると、データは送信されず、エラーコードが表示される可能性があります。
ポーラー

@Polar-正確ではありません。GLは実際には、バッファオブジェクトがどのメモリに格納されているかを指定せず、使用パターンに基づいて実行時に自由に移動することさえできます。GL4.4はこれにいくらか対処していますが、最終的に提供できるのは「それらの愚かなヒントの1つ」であることに注意してください。opengl.org/registry/specs/ARB/buffer_storage.txtおよび特に問題2および9を参照してください。
Maximus Minimus

1
@JimmyShelterああ、ありがとう-「馬鹿げたヒント」やより具体的な仕様が少なければ良かったです。
ポーラー

@Polar-厄介なのは、ARB_buffer_storage さらに別のヒントを含めることを避けていたかもしれないことですが、設計者はその機会を逃しました。まあ、4.5でようやく正しくなるでしょう。
マキシマスミニマス

2
回答に「応答」するように質問を編集しないでください。代わりに新しい質問を投稿してください。

回答:


12

そのデータはGPUメモリに一度だけ送信され、永遠にそこに座っていますか?

通常はそうですが、ドライバーは「最適な」ことを自由に行うことができ、データはVRAMまたはRAMに保存されるか、VBOフローで実際に何が起こるかを説明するためにここにキャッシュすることができます。

たとえば、動的なopenGLバッファー(VBOなど)としてフラグが設定されている場合、RAMに格納される可能性が高くなります。GPUは、CPUの介入なしでRAMに直接アクセスするためにダイレクトメモリアクセス(DMA)を使用します。これは、グラフィックスカードおよびグラフィックスドライバーのDMAコントローラーによって制御され、カーネルモードで実行されます。

モデルが各フレームを実際にレンダリングするとき、モデルが複数の連続した時間をレンダリングしている場合でも、GPUプロセッサはGPUメモリから毎回そのデータをフェッチする必要がありますか?

CPUと同様に、GPUはGPU命令とメモリアクセス操作を並べ替えることができます(読み取り:順不同の実行) )、しかし、時にはそれができません。

明らかにグラフィックカードのRAMは限られています-1フレームのレンダリングに必要なすべてのモデルデータを保持できない場合、フレームごとにCPU RAMから(一部)を取得し続けると思いますが、それは正しいですか?

これが起こるのは望ましくありません。しかし、それが起こっても、GPUはRAMとVRAMの間でメモリの移動を開始し(GPUのコマンドプロセッサがこれを担当します)、レンダリングを非常に遅くします。 V / RAMから/にコピーされます。

データをGPUに送信し、現在のバッファーを設定/バインドします。後者はデータフローを引き起こしますか?

GPUにはコマンドバッファーが含まれており、すべてのAPIコマンドがこのバッファーに送信されます。これは、GPUにコピーされるデータと同時に発生する可能性があることに注意してください。コマンドリングバッファは、CPUとGPUとの間の通信キューであり、それはGPUによってexeculatedできるようにニーズがキューに提出するために実行される必要がある任意のコマンド。新しいバッファをバインドする操作がgpuに送信される必要があるように、メモリにアクセスできるようにします。

これがglBegin / glEndが廃止された理由の1つです。新しいコマンドを送信するにはキューの同期が必要です(メモリフェンス/バリアを使用)。

ここに画像の説明を入力してください

あなたの他のポイントに関して:

初期化によるバッファ作成

初期化せずにバッファを割り当てて、後で使用するために保持できます。または、バッファを割り当てて、同時にデータをコピーすることもできます(APIレベルについて話します)。

バッファデータの更新

glMapBufferを使用して、GPU側のメモリを更新できます。メモリをRAMにコピーするか、RAMにコピーするかは、実際には規格の一部ではなく、ベンダー、GPUタイプ、およびドライバーによって大きく異なります。

APIドローコール(ここで実際に何が起こるかをお聞きします)。

主な質問の私の2番目のポイントはこれをカバーしています。

バッファーをアクティブとしてバインドします(次の描画呼び出しでこのバッファーをレンダリングすることをAPIに伝える方法であり、それ自体では何もしませんか?)

バインドthisは、オブジェクト指向言語でポインターを使用するものと考えてください。厳密には同じではありませんが、その後のAPI呼び出しはすべて、そのバインドバッファーに関連します。


3

一般に、CPUとGPUの境界と関与はプラットフォームによって異なりますが、それらのほとんどはこのモデルに従います:CPUにはRAM、GPUもあり、メモリを移動できます(RAMは共有されますが、簡単にするために、別々のラムに固執しましょう)。

最初のポイント:初期化するデータは、CPUラムまたはGPUラムに保持することを選択でき、両方の利点があります。何かをレンダリングするとき、GPUは重い処理を行う必要があるため、GPUメモリに既にあるデータがより良いパフォーマンスを提供することは明らかです。CPUの場合、最初にデータをGPU(しばらく保持することを選択できます)に送信してから、レンダリングを実行する必要があります。

2番目のポイント:レンダリングには多くのトリックがありますが、主な方法はポリゴンを使用することです。フレーム上で、GPUはポリゴンで作成されたオブジェクトを1つずつレンダリングし、終了後にGPUがディスプレイに画像を送信します。オブジェクトのような概念はありません。ポリゴンだけがあり、それらを組み合わせて画像を作成します。GPUの仕事は、これらのポリゴンを3Dから2Dに投影し、効果を適用することです(必要な場合)。ポリゴンは、CPU-> GPU-> SCREENまたはGPU-> SCREENのように直接進みます(ポリゴンがすでにGPU RAMにある場合)

3番目のポイント:たとえば、アニメーションをレンダリングする場合、データをCPUの近くに保持する方が良いでしょう。重荷を抱えているため、GPUにデータを保持し、CPUに移動してすべてのフレームに戻すことは最適ではありません。このような他の例も多数ありますが、一般的に、すべてのデータは計算を実行している人の近くに留まります。通常、できるだけ多くのデータをGPU RAMに移動してパフォーマンスを向上させます。

gpuへの実際のデータ送信は、使用するAPI(directx / openglまたはその他)によって行われます。バインディングの概念やこのようなものは単なる抽象化であるため、APIは目的を理解します。

編集のための編集:

  • buffer creation with initialisation:これは、int a = new int[10]a[0] = 0,a[1] = 1.... etc 、バッファーを作成するときのデータのスペースを確保するときと、データを初期化するときに必要なものをそこに置くときとの違いのようなものです。

  • buffer data updateCPUラム上にある場合はvertex * vertices、それを使用して再生できます。それがない場合vertex * vertices = map(buffer_id);は、GPUから移動する必要があります(マップはGPUからCPUラムにデータを移動する神話関数であり、逆もあります)buffer_id = create_buffer(vertices);

  • binding the buffer as active彼らがbindingレンダリングを呼び出すのは単なる概念であり、複雑なプロセスであり、10000個のパラメーターを持つ関数を呼び出すようなものです。バインディングとは、どのバッファがどこに行くかを伝えるために使用する用語です。この用語の背後には本当の魔法はありません。バッファーの変換、移動、再割り当ては行わず、次の描画呼び出しでこのバッファーを使用することをドライバーに伝えます。

  • API draw callすべての結合と設定のバッファリングの後、これはゴムが道路に出会う場所です。draw呼び出しは、指定されたすべてのデータ(またはデータを指すID)を取得し、GPUに送信し(必要な場合)、GPUに数値の計算を開始するように指示します。これはすべてのプラットフォームで完全に当てはまるわけではありませんが、多くの違いがありますが、物事を単純にするために、描画はGPUに....描画するように指示します。


2

最も正しい答えは、それをどのようにプログラムするかに依存しますが、これは心配するのが良いことです。GPUは非常に高速になりましたが、GPU RAMとの間の帯域幅はそうではなく、最もイライラするボトルネックになります。

そのデータはGPUメモリに一度だけ送信され、永遠にそこに座っていますか?

できれば、はい。レンダリング速度を上げるには、フレームごとに再送信するのではなく、できるだけ多くのデータをGPUに配置する必要があります。VBOは、まさにこの目的を果たします。静的と動的の両方のVBOがあり、前者は静的モデルに最適であり、後者は頂点がすべてのフレームを変更するモデル(パーティクルシステムなど)に最適です。ただし、ダイナミックVBOの場合でも、フレームごとにすべての頂点を再送信する必要はありません。変化しているものだけです。

建物の場合、頂点データはそこにあり、変更されるのはマトリックス(モデル/ワールド、投影、ビュー)だけです。

パーティクルシステムの場合、そのシステムに存在するパーティクルの最大数を格納するのに十分な大きさの動的VBOを作成しました。各フレームは、そのフレームから放出されたパーティクルのデータと、いくつかのユニフォームを送信します。それがすべてです。描画するとき、そのVBOで開始点と終了点を指定できるため、パーティクルデータを削除する必要はありません。それらを描かないでください。

モデルが各フレームで実際にレンダリングされるとき、GPUプロセッサはGPUメモリから毎回そのデータをフェッチする必要がありますか?つまり、2つのモデルをそれぞれ複数回レンダリングした場合、最初のモデルを複数回レンダリングしてから2番目のモデルを複数回レンダリングするか、最初のモデルを1回だけレンダリングし、2番目のモデルを1回だけレンダリングして、そのようにインターリーブし続けましたか?

1つではなく複数の描画呼び出しを送信するという行為は、はるかに大きな制限です。インスタンス化されたレンダリングを確認してください。それはあなたを大いに助け、この質問に対する答えを役に立たなくするかもしれません。まだ解決していないドライバの問題がいくつかありましたが、機能するようになれば問題は解決しました。

明らかにグラフィックカードのRAMは限られています-1フレームのレンダリングに必要なすべてのモデルデータを保持できない場合、フレームごとにCPU RAMから(一部)を取得し続けると思いますが、それは正しいですか?

GPU RAMを使い果たしたくありません。その場合は、変更しないように変更してください。あなたが使い果たした非常に仮説的なシナリオでは、おそらく何らかの形でクラッシュするでしょうが、私はそれが起こるのを見たことがないので、正直に知りません。

私は1つの区別を忘れていました:データをGPUに送信し、現在のバッファを設定/バインドします。後者はデータフローを引き起こしますか?

重要なデータフローはありません。それにはいくらかコストがかかりますが、それはあなたが書くコードのすべての行に当てはまります。費用がいくらになるか、また、プロファイリングの目的を確認してください。

初期化によるバッファ作成

Raxvanの答えは良さそうですが、正確ではありません。OpenGLでは、バッファーを作成してもスペース予約されません。データを渡さずにスペースを確保したい場合は、glBufferDataを呼び出して、nullを渡すことができます。(ここの注意事項セクションを参照してください。)

バッファデータの更新

glBufferData、またはそのような他の関数を意味していると思いますか?これが実際のデータ転送が発生する場所です。(最後の段落で言ったように、nullを渡さない限り。)

バッファーをアクティブとしてバインドします(次の描画呼び出しでこのバッファーをレンダリングすることをAPIに伝える方法であり、それ自体では何もしませんか?)

はい。ただし、それ以上のことができます。たとえば、VAO(頂点配列オブジェクト)をバインドしてからVBOをバインドすると、そのVBOはVAOにバインドされます。後で、そのVAOを再度バインドし、glDrawArraysを呼び出すと、描画するVBOがわかります。

多くのチュートリアルでは、すべてのVBOに対してVAOを作成する必要がありますが、これはそれらの使用目的ではないと言われていることに注意してください。おそらく、1つのVAOを作成し、同じ属性を持つすべてのVBOで使用する必要があります。私はまだこれを試していませんので、それが良いか悪いかを確実に言うことはできません。

API描画呼び出し

ここで何が起こるかは非常に簡単です(私たちの観点から)。VAOをバインドした後、glDrawArraysを呼び出します。開始点とカウントを指定すると、その範囲内のすべての頂点に対して頂点シェーダーが実行され、その出力がラインに渡されます。ただし、そのプロセス全体は別のエッセイです。


「問題は解決しました」はい、インスタンス化は大いに役立ちますが、それなしでは各オブジェクトに対して描画呼び出しを行う必要があります。両方のケースで同じ量。だから、順序が重要なのだろうか。
NPS

@NPS- いくつか重要です。それらが順序付けられているので、バインディングを切り替え続ける必要がない場合、ええ、それはおそらくごくわずかな量の高速です。しかし、それらをソートするために邪魔をしなければならない場合、それはおそらくはるかに、はるかに高価になります。実装に依存する変数が多すぎるため、それ以上のことは言えません。
氷の反抗
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.