特定の状態変化の実際のコストは非常に多くの要因によって異なるため、一般的な答えはほぼ不可能です。
第1に、すべての状態の変更には、CPU側のコストとGPU側のコストの両方が潜在的に含まれる可能性があります。CPUコストは、ドライバーとグラフィックスAPIに応じて、メインスレッド全体または一部バックグラウンドスレッドで支払われる場合があります。
第二に、GPUコストは飛行中の作業量に依存する場合があります。最新のGPUは非常にパイプライン化されており、一度に多くの作業を実行するのが大好きです。あなたが得ることができる最大の減速は、現在飛行中のすべてのものが状態が変化する前に廃止する必要があるようにパイプラインを停止することです。パイプラインが停止する原因は何ですか?まあ、それはあなたのGPUに依存します!
ここでパフォーマンスを理解するために実際に知る必要があるのは、状態変更を処理するためにドライバーとGPUが何をする必要があるかということです。もちろんこれはGPUに依存し、ISVが一般に公開していないことが多い詳細にも依存します。ただし、いくつかの一般的な原則があります。
GPUは通常、フロントエンドとバックエンドに分割されます。フロントエンドはドライバーによって生成されたコマンドのストリームを処理し、バックエンドは実際の作業をすべて行います。前にも言ったように、バックエンドは多くの作業を実行するのが大好きですが、その作業に関する情報を保存するためにいくつかの情報が必要です(おそらくフロントエンドによって入力されます)。十分な数の小さなバッチをキックし、作業を追跡するシリコンをすべて使い切ると、たくさんの未使用の馬力が座っている場合でも、フロントエンドが停止する必要があります。そのため、ここでの原則:状態の変更(および小さな描画)が多いほど、GPUバックエンドが枯渇する可能性が高くなります。
描画が実際に処理されている間、基本的にシェーダープログラムを実行しているだけです。シェーダープログラムは、ユニフォーム、頂点バッファーデータ、テクスチャをフェッチするためのメモリアクセスを実行していますが、シェーダーユニットに頂点バッファーとあなたのテクスチャがあります。また、GPUには、これらのメモリアクセスの前にキャッシュがあります。したがって、GPUで新しいユニフォームまたは新しいテクスチャ/バッファバインディングをスローすると、最初にそれらを読み取る必要があるときにキャッシュミスが発生する可能性があります。別の原則:ほとんどの状態変更はGPUキャッシュミスを引き起こします。(これは、自分で定数バッファーを管理している場合に最も意味があります。描画間で定数バッファーを同じに保つと、GPUのキャッシュに残る可能性が高くなります。)
シェーダーリソースの状態変更のコストの大部分はCPU側です。新しい定数バッファーを設定するたびに、ドライバーはおそらくその定数バッファーの内容をGPUのコマンドストリームにコピーします。単一のユニフォームを設定すると、ドライバーはそれを背中の後ろの大きな定数バッファに変える可能性が高いため、定数バッファでそのユニフォームのオフセットを調べ、値をコピーしてから、定数バッファをマークする必要があります次の描画呼び出しの前にコマンドストリームにコピーできるようにダーティです。新しいテクスチャまたは頂点バッファーをバインドする場合、ドライバーはおそらくそのリソースの制御構造をコピーしています。また、マルチタスクOSでディスクリートGPUを使用している場合、ドライバーは使用するすべてのリソースを追跡する必要があります。■GPUメモリマネージャは、描画が発生したときに、そのリソースのメモリがGPUのVRAMに常駐することを保証できます。原理:状態が変化すると、ドライバーはメモリをシャッフルして、GPUの最小限のコマンドストリームを生成します。
現在のシェーダーを変更すると、GPUキャッシュミスが発生している可能性があります(命令キャッシュもあります!)。原則として、CPUの作業は、コマンドストリームに「シェーダーを使用する」という新しいコマンドを入力することに限定する必要があります。ただし、実際には、対処するシェーダーコンパイルが非常に複雑です。GPUドライバーは、シェーダーを事前に作成した場合でも、非常に頻繁にシェーダーを遅延コンパイルします。ただし、このトピックに関連性が高いのは、GPUハードウェアでネイティブにサポートされていない状態もあり、代わりにシェーダープログラムにコンパイルされる状態です。人気のある例の1つは頂点形式です。これらは、チップ上の個別の状態ではなく、頂点シェーダーにコンパイルできます。そのため、以前に特定の頂点シェーダーで使用したことがない頂点フォーマットを使用する場合、シェーダーにパッチを適用し、シェーダープログラムをGPUにコピーするために大量のCPUコストを支払うことになります。さらに、ドライバーおよびシェーダーコンパイラーは、シェーダープログラムの実行を最適化するためにあらゆる種類のことを行うように共謀する場合があります。これは、ユニフォームとリソース制御構造のメモリレイアウトを最適化して、隣接するメモリまたはシェーダーレジスタに適切にパックすることを意味する場合があります。したがって、シェーダーを変更すると、ドライバーは、パイプラインに既にバインドされているすべてのものを見て、新しいシェーダー用にまったく異なる形式に再パックし、それをコマンドストリームにコピーします。原理:これは、ユニフォームとリソース制御構造のメモリレイアウトを最適化して、隣接するメモリまたはシェーダーレジスタに適切にパックすることを意味する場合があります。したがって、シェーダーを変更すると、ドライバーは、パイプラインに既にバインドされているすべてのものを見て、新しいシェーダー用にまったく異なる形式に再パックし、それをコマンドストリームにコピーします。原理:これは、ユニフォームとリソース制御構造のメモリレイアウトを最適化して、隣接するメモリまたはシェーダーレジスタに適切にパックすることを意味する場合があります。したがって、シェーダーを変更すると、ドライバーは、パイプラインに既にバインドされているすべてのものを見て、新しいシェーダー用にまったく異なる形式に再パックし、それをコマンドストリームにコピーします。原理:シェーダーを変更すると、大量のCPUメモリシャッフルが発生する可能性があります。
フレームバッファの変更はおそらく最も実装に依存しますが、GPUでは一般にかなり高価です。GPUは、異なるレンダーターゲットへの複数の描画呼び出しを同時に処理できない場合があるため、これら2つの描画呼び出しの間でパイプラインを停止する必要がある場合があります。レンダーターゲットを後で読み取ることができるように、キャッシュをフラッシュする必要がある場合があります。描画中に延期した作業を解決する必要がある場合があります。(デプスバッファ、MSAAレンダーターゲットなどとともに、個別のデータ構造を蓄積することは非常に一般的です。そのレンダーターゲットから切り替える場合、これをファイナライズする必要があります。タイルベースのGPUを使用している場合、多くのモバイルGPUのように、フレームバッファーから切り替えるときに、かなり大量の実際のシェーディング作業をフラッシュする必要がある場合があります。)原理:レンダーターゲットの変更は、GPUで高価です。
私はそれがすべて非常に混乱していると確信しており、残念ながら詳細は公開されていないことが多いため、具体的にするのは難しいですが、州を呼ぶときに実際に起こっていることの半分のまとまりになることを望んでいますお気に入りのグラフィックAPIの機能を変更します。