マルチエンジン同期Multi-engine synchronization

ほとんどの最新の GPU には、専用の機能を備えた独立エンジンが複数搭載されています。Most modern GPUs contain multiple independent engines that provide specialized functionality. 多くに 1 つ以上の専用のコピー エンジンと、3D エンジンとは通常別になっているコンピューティング エンジンがあります。Many have one or more dedicated copy engines, and a compute engine, usually distinct from the 3D engine. これらの各エンジンでは、互いに並列にコマンドを実行できます。Each of these engines can execute commands in parallel with each other. Direct3D 12 では、キューとコマンドリストを使用して、3D、コンピューティング、コピーエンジンへのきめ細かなアクセスを提供します。Direct3D 12 provides fine-grained access to the 3D, compute, and copy engines, using queues and command lists.

GPU のエンジンGPU engines

次の図は、タイトルの CPU スレッドを示しています。各スレッドは、1つまたは複数のコピー、計算、および3D キューを設定しています。The following diagram shows a title's CPU threads, each populating one or more of the copy, compute, and 3D queues. 3D キューは、3つの GPU エンジンをすべて駆動できます。コンピューティングキューは、コンピューティングエンジンとコピーエンジンを駆動できます。コピーキューは単にコピーエンジンです。The 3D queue can drive all three GPU engines; the compute queue can drive the compute and copy engines; and the copy queue simply the copy engine.

さまざまなスレッドによってキューが作成されるため、実行順序を保証することはできません。そのため、タイトルに必要な同期メカニズム—必要があります。As the different threads populate the queues, there can be no simple guarantee of the order of execution, hence the need for synchronization mechanisms—when the title requires them.

4 つのスレッドから 3 つのキューにコマンドを送信

次の画像は、必要に応じたエンジン間の同期を含め、ゲームで複数の GPU エンジン全体にわたり作業のスケジュールが設定される流れを表したものです。ここでは、エンジンごとのワークロードとエンジン間の依存関係を合わせて示しています。The following image illustrate how a title might schedule work across multiple GPU engines, including inter-engine synchronization where necessary: it shows the per-engine workloads with inter-engine dependencies. この例では、まず、コピー エンジンがレンダリングに必要なジオメトリをコピーします。In this example, the copy engine first copies some geometry necessary for rendering. 3D エンジンはこれらのコピーが完了するまで待ってから、ジオメトリの上にプリパスをレンダリングします。The 3D engine waits for these copies to complete, and renders a pre-pass over the geometry. これはその後、コンピューティング エンジンで使用されます。This is then consumed by the compute engine. コンピューティング エンジンの Dispatch の結果は、コピー エンジンにおける複数回のテクスチャのコピー操作とあわせて、3D エンジンの最後の Draw 呼び出しで使用されます。The results of the compute engine Dispatch, along with several texture copy operations on the copy engine, are consumed by the 3D engine for the final Draw call.

コピー エンジン、グラフィックス エンジン、コンピューティング エンジン間の通信

以下の疑似コードは、ゲームでこのようなワークロードを送信する方法を示しています。The following pseudo-code illustrates how a title might submit such a workload.

// Get per-engine contexts. Note that multiple queues may be exposed
// per engine, however that design is not reflected here.
copyEngine = device->GetCopyEngineContext();
renderEngine = device->GetRenderEngineContext();
computeEngine = device->GetComputeEngineContext();
copyEngine->CopyResource(geometry, ...); // copy geometry
copyEngine->Signal(copyFence, 101);
copyEngine->CopyResource(tex1, ...); // copy textures
copyEngine->CopyResource(tex2, ...); // copy more textures
copyEngine->CopyResource(tex3, ...); // copy more textures
copyEngine->CopyResource(tex4, ...); // copy more textures
copyEngine->Signal(copyFence, 102);
renderEngine->Wait(copyFence, 101); // geometry copied
renderEngine->Draw(); // pre-pass using geometry only into rt1
renderEngine->Signal(renderFence, 201);
computeEngine->Wait(renderFence, 201); // prepass completed
computeEngine->Dispatch(); // lighting calculations on pre-pass (using rt1 as SRV)
computeEngine->Signal(computeFence, 301);
renderEngine->Wait(computeFence, 301); // lighting calculated into buf1
renderEngine->Wait(copyFence, 102); // textures copied
renderEngine->Draw(); // final render using buf1 as SRV, and tex[1-4] SRVs

以下の疑似コードは、コピー エンジンと 3D エンジンの間の同期により、リング バッファーを介してヒープに似たメモリ割り当てを実現する方法を示しています。The following pseudo-code illustrates synchronization between the copy and 3D engines to accomplish heap-like memory allocation via a ring buffer. ゲームでは、(大きなバッファーを使用した) 並列処理の最大化と、(小さなバッファーを使用した) メモリ使用量および待ち時間の削減について、適切なバランスを柔軟に選択することができます。Titles have the flexibility to choose the right balance between maximizing parallelism (via a large buffer) and reducing memory consumption and latency (via a small buffer).

device->CreateBuffer(&ringCB);
for(int i=1;i++){
  if(i > length) copyEngine->Wait(fence1, i - length);
  copyEngine->Map(ringCB, value%length, WRITE, pData); // copy new data
  copyEngine->Signal(fence2, i);
  renderEngine->Wait(fence2, i);
  renderEngine->Draw(); // draw using copied data
  renderEngine->Signal(fence1, i);
}

// example for length = 3:
// copyEngine->Map();
// copyEngine->Signal(fence2, 1); // fence2 = 1  
// copyEngine->Map();
// copyEngine->Signal(fence2, 2); // fence2 = 2
// copyEngine->Map();
// copyEngine->Signal(fence2, 3); // fence2 = 3
// copy engine has exhausted the ring buffer, so must wait for render to consume it
// copyEngine->Wait(fence1, 1); // fence1 == 0, wait
// renderEngine->Wait(fence2, 1); // fence2 == 3, pass
// renderEngine->Draw();
// renderEngine->Signal(fence1, 1); // fence1 = 1, copy engine now unblocked
// renderEngine->Wait(fence2, 2); // fence2 == 3, pass
// renderEngine->Draw();
// renderEngine->Signal(fence1, 2); // fence1 = 2
// renderEngine->Wait(fence2, 3); // fence2 == 3, pass
// renderEngine->Draw();
// renderEngine->Signal(fence1, 3); // fence1 = 3
// now render engine is starved, and so must wait for the copy engine
// renderEngine->Wait(fence2, 4); // fence2 == 3, wait

マルチエンジン シナリオMulti-engine scenarios

Direct3D 12 では、予期しない同期の遅延が原因で誤って非効率的に実行されるのを防ぐことができます。Direct3D 12 allows you to avoid accidentally running into inefficiencies caused by unexpected synchronization delays. また、より高いレベルで同期を導入することもできます。この場合、必要な同期をより正確に判断できます。It also allows you to introduce synchronization at a higher level where the required synchronization can be determined with greater certainty. マルチエンジンにより解決できる 2 つ目の問題は、負荷の高い操作をより明示的にすることです。こうした操作には、従来は複数のカーネル コンテキスト間で同期が行われるため高コストであった、3D と動画の間の遷移などがあります。A second issue that multi-engine addresses is to make expensive operations more explicit, which includes transitions between 3D and video that were traditionally costly because of synchronization between multiple kernel contexts.

特に、次のシナリオは Direct3D 12 で対処できます。In particular, the following scenarios can be addressed with Direct3D 12.

  • 非同期的な低優先度の GPU 作業。Asynchronous and low priority GPU work. 優先度の低い GPU 作業を同時に実行できるようになり、特定の GPU スレッドで別の非同期スレッドの結果をブロックすることなく使用する不可分操作が可能になります。This enables concurrent execution of low priority GPU work and atomic operations that enable one GPU thread to consume the results of another unsynchronized thread without blocking.
  • 高優先度のコンピューティング作業。High priority compute work. バックグラウンド コンピューティングを使用することで、3D レンダリングに割り込んで、高優先度のコンピューティング作業を少量だけ実行できます。With background compute it is possible to interrupt 3D rendering to do a small amount of high priority compute work. この作業の結果を早期に取得して、CPU で追加の処理を実行できます。The results of this work can be obtained early for additional processing on the CPU.
  • バックグラウンドのコンピューティング作業。Background compute work. コンピューティング ワークロード用に個別の低優先度キューを使用することで、アプリケーションで予備の GPU サイクルを活用して、主要なレンダリング (またはその他の) タスクに悪影響を与えることなく、バックグラウンドで計算を実行できます。A separate low priority queue for compute workloads allows an application to utilize spare GPU cycles to perform background computation without negative impact on the primary rendering (or other) tasks. バックグラウンド タスクには、リソースの展開や、シミュレーションまたはアクセラレーション構造の更新などが含まれます。Background tasks may include decompression of resources or updating simulations or acceleration structures. CPU 上でのバックグラウンド タスクの同期は、フォアグラウンドの作業を停止、失速させないように、低頻度 (1 フレームあたり 1 回程度) で行う必要があります。Background tasks should be synchronized on the CPU infrequently (approximately once per frame) to avoid stalling or slowing foreground work.
  • データのストリーム配信とアップロード。Streaming and uploading data. D3D11 の初期データとリソース更新という概念の代わりとして、個別のコピー キューが導入されました。A separate copy queue replaces the D3D11 concepts of initial data and updating resources. アプリケーションでは、Direct3D 12 モデルの詳細について説明していますが、この責任には力があります。Although the application is responsible for more details in the Direct3D 12 model, this responsibility comes with power. アップロード データのバッファー処理に充てるシステム メモリの量を、アプリケーションで制御できます。The application can control how much system memory is devoted to buffering upload data. アプリで同期のタイミングと方法 (CPU か GPU、ブロックか非ブロック) を選択して、進行状況を追跡しながら、キューに入れる作業の量を管理することができます。The app can choose when and how (CPU vs GPU, blocking vs non-blocking) to synchronize, and can track progress and control the amount of queued work.
  • 並列処理の強化。Increased parallelism. アプリケーションにフォアグラウンド作業用の個別のキューがある場合は、バックグラウンドのワークロード (動画のデコードなど) により深いキューを使用できます。Applications can use deeper queues for background workloads (e.g. video decode) when they have separate queues for foreground work.

Direct3D 12 では、コマンドキューの概念は、アプリケーションによって送信されたほぼシリアルの作業シーケンスの API 表現です。In Direct3D 12 the concept of a command queue is the API representation of a roughly serial sequence of work submitted by the application. こうした作業は、バリアやその他の手法を用いることでパイプライン内で実行することも、順不同で実行することもできますが、アプリケーションには単一の完了タイムラインしか認識されません。Barriers and other techniques allow this work to be executed in a pipeline or out of order, but the application only sees a single completion timeline. これは、D3D11 のインティミデイト コンテキストに相当します。This corresponds to the immediate context in D3D11.

同期 APISynchronization APIs

デバイスとキューDevices and queues

Direct3D 12 デバイスには、さまざまな種類および優先順位のコマンドキューを作成および取得するためのメソッドがあります。The Direct3D 12 device has methods to create and retrieve command queues of different types and priorities. 既定のコマンド キューは別のコンポーネントとの共用に対応しているため、ほとんどのアプリケーションではこちらのキューを使用することをお勧めします。Most applications should use the default command queues because these allow for shared usage by other components. 同時開催について特別な要件があるアプリケーションの場合は、追加のキューを作成してください。Applications with additional concurrency requirements can create additional queues. キューは、そのキューで使用するコマンド リストのタイプで指定します。Queues are specified by the command list type that they consume.

ID3D12Deviceの次の作成方法を参照してください。Refer to the following creation methods of ID3D12Device.

すべてのタイプ (3D、コンピューティング、およびコピー) のキューは同じインターフェイスを共有しており、コマンドリストに基づいています。Queues of all types (3D, compute and copy) share the same interface and are all command-list based.

ID3D12CommandQueueの次のメソッドを参照してください。Refer to the following methods of ID3D12CommandQueue.

  • ExecuteCommandLists: コマンド リストの配列を実行用に送信します。ExecuteCommandLists : submits an array of command lists for execution. 各コマンド リストは、ID3D12CommandList で定義します。Each command list being defined by ID3D12CommandList.
  • Signal: (GPU 上で実行されている) キューが特定のポイントに到達したときにフェンスの値を設定します。Signal : sets a fence value when the queue (running on the GPU) reaches a certain point.
  • Wait: 指定したフェンスが指定値に到達するまで、キューを待機させます。Wait : the queue waits until the specified fence reaches the specified value.

バンドルはいずれのキューでも使用されないため、このタイプを使用してキューを作成することはできません。Note that bundles are not consumed by any queues and therefore this type cannot be used to create a queue.

フェンスFences

マルチエンジン API には、フェンスを使用した作成と同期のための明示的な API が備わっています。The multi-engine API provides explicit APIs to create and synchronize using fences. フェンスは、UINT64 値によって制御される同期コンストラクトです。A fence is a synchronization construct controlled by a UINT64 value. フェンスの値は、アプリケーションによって設定されます。Fence values are set by the application. シグナル操作はフェンスの値を変更し、待機操作はフェンスが要求された値以上になるまでブロックします。A signal operation modifies the fence value and a wait operation blocks until the fence has reached the requested value or greater. フェンスが特定の値に達したときに、イベントを発生させることができます。An event can be fired when a fence reaches a certain value.

ID3D12Fenceインターフェイスのメソッドを参照してください。Refer to the methods of the ID3D12Fence interface.

フェンスにより CPU は現在のフェンス値にアクセスでき、待機操作とシグナル操作を行います。Fences allow CPU access to the current fence value, and CPU waits and signals.

ID3D12Fence インターフェイスの Signal メソッドは、CPU の側からフェンスを更新します。The Signal method on the ID3D12Fence interface updates a fence from the CPU side. この更新プログラムは直ちに実行します。This update occurs immediately. ID3D12CommandQueueSignal メソッドは、GPU の側からフェンスを更新します。The Signal method on ID3D12CommandQueue updates a fence from the GPU side. この更新は、コマンドキューに対する他のすべての操作が完了した後に行われます。This update occurs after all other operations on the command queue have been completed.

マルチエンジン環境のすべてのノードが、適切な値に達したあらゆるフェンスを読み取り、対応できます。All nodes in a multi-engine setup can read and react to any fence reaching the right value.

アプリケーションで独自のフェンス値を設定することになりますが、初めは 1 フレームごとにフェンス値を 1 増やすことをお勧めします。Applications set their own fence values, a good starting point might be increasing a fence once per frame.

フェンスは巻き戻しいる可能性があります。A fence may be rewound. つまり、フェンス値は単にインクリメントする必要がありません。This means that the fence value does not need to solely increment. 2つの異なるコマンドキューでシグナル操作がエンキューされている場合、または2つの CPU スレッドがフェンスでシグナルを呼び出している場合は、最後に完了した信号を判別する競合が発生する可能性があります。そのため、どのようなフェンス値が残ります。If a Signal operation is enqueued on two different command queues, or if two CPU threads are both calling Signal on a fence, there may be a race to determine which Signal completes last, and therefore which fence value is the one which will remain. フェンスが巻き戻されている場合、新しい待機 ( Seteventoncompletion要求を含む) は、新しいフェンスの値と比較されます。そのため、フェンスの値が以前に満たされていた場合でも、これは満たされない可能性があります。If a fence is rewound, any new waits (including SetEventOnCompletion requests) will be compared against the new lower fence value, and therefore may not be satisfied, even if the fence value had previously been high enough to satisfy them. 競合が発生した場合、未処理の待機に適合する値の間に、待機時間が小さい場合は、その後の値に関係なく待機が満たされますIf a race does occur, between a value which will satisfy an outstanding wait, and a lower value which will not, the wait will be satisfied regardless of which value remains afterwards.

フェンス API には強力な同期機能が備わっていますが、これを使用すると問題のデバッグが困難になる可能性があります。The fence APIs provide powerful synchronization functionality but can create potentially difficult to debug issues. 各フェンスは、1つのタイムラインでの進行状況を示すためにのみ使用することをお勧めします。これにより、signalers 間の競合を防ぐことができます。It is recommended that each fence only be used to indicate progress on one timeline to prevent races between signalers.

コマンド一覧のコピーと計算Copy and compute command lists

3 タイプのコマンド リストすべてで ID3D12GraphicsCommandList インターフェイスを使用しますが、コピーおよびコンピューティングではこれらのメソッドの一部のみサポートされます。All three types of command list use the ID3D12GraphicsCommandList interface, however only a subset of the methods are supported for copy and compute.

コピーと計算のコマンドリストでは、次の方法を使用できます。Copy and compute command lists can use the following methods.

Compute コマンドリストでは、次のメソッドを使用することもできます。Compute command lists can also use the following methods.

コンピューティング コマンド リストで SetPipelineState を呼び出す場合、コンピューティング PSO を設定する必要があります。Compute command lists must set a compute PSO when calling SetPipelineState.

コンピューティングまたはコピーのコマンド リストおよびキューと、バンドルを併用することはできません。Bundles cannot be used with compute or copy command lists or queues.

コンピューティングとグラフィックスのパイプライン化の例Pipelined compute and graphics example

この例では、フェンス同期を使用して、キュー pGraphicsQueueのグラフィック作業によって使用されるキュー (pComputeQueueによって参照される) でのコンピューティング作業のパイプラインを作成する方法を示します。This example shows how fence synchronization can be used to create a pipeline of compute work on a queue (referenced by pComputeQueue) that's consumed by graphics work on queue pGraphicsQueue. コンピューティングとグラフィックスの作業は、複数のフレームからのコンピューティング作業の結果を処理するグラフィックスキューによって処理されます。また、CPU イベントを使用して、キューに置かれている総作業量を調整します。The compute and graphics work is pipelined with the graphics queue consuming the result of compute work from several frames back, and a CPU event is used to throttle the total work queued overall.

void PipelinedComputeGraphics()
{
    const UINT CpuLatency = 3;
    const UINT ComputeGraphicsLatency = 2;

    HANDLE handle = CreateEvent(nullptr, FALSE, FALSE, nullptr);

    UINT64 FrameNumber = 0;

    while (1)
    {
        if (FrameNumber > ComputeGraphicsLatency)
        {
            pComputeQueue->Wait(pGraphicsFence,
                FrameNumber - ComputeGraphicsLatency);
        }

        if (FrameNumber > CpuLatency)
        {
            pComputeFence->SetEventOnFenceCompletion(
                FrameNumber - CpuLatency,
                handle);
            WaitForSingleObject(handle, INFINITE);
        }

        ++FrameNumber;

        pComputeQueue->ExecuteCommandLists(1, &pComputeCommandList);
        pComputeQueue->Signal(pComputeFence, FrameNumber);
        if (FrameNumber > ComputeGraphicsLatency)
        {
            UINT GraphicsFrameNumber = FrameNumber - ComputeGraphicsLatency;
            pGraphicsQueue->Wait(pComputeFence, GraphicsFrameNumber);
            pGraphicsQueue->ExecuteCommandLists(1, &pGraphicsCommandList);
            pGraphicsQueue->Signal(pGraphicsFence, GraphicsFrameNumber);
        }
    }
}

このパイプライン処理をサポートするには、コンピューティングキューからグラフィックスキューに渡すデータの異なるコピーを ComputeGraphicsLatency+1 バッファーが必要です。To support this pipelining, there must be a buffer of ComputeGraphicsLatency+1 different copies of the data passing from the compute queue to the graphics queue. コマンド リストでは、UAV と間接参照を使用して、バッファー内の適切な "バージョン" のデータを対象に読み取りと書き込みを行う必要があります。The command lists must use UAVs and indirection to read and write from the appropriate “version” of the data in the buffer. コンピューティング キューは、フレーム N+ComputeGraphicsLatency に書き込む前に、グラフィックス キューでフレーム N のデータの読み取りが完了するまで待機しなければなりません。The compute queue must wait until the graphics queue has finished reading from the data for frame N before it can write frame N+ComputeGraphicsLatency.

CPU に対して実行されたコンピューティングキューの量は、必要なバッファーの量に直接依存していないことに注意してください。ただし、使用可能なバッファー領域の大きさを超えるキューの GPU 作業は、あまり重要ではありません。Note that the amount of compute queue worked relative to the CPU does not depend directly on the amount of buffering required, however, queuing GPU work beyond the amount of buffer space available is less valuable.

間接参照を使用しない別のメカニズムとしては、データの各 "名前変更後" バージョンに相当するコマンド リストを複数作成することが考えられます。An alternative mechanism to avoid indirection would be to create multiple command lists corresponding to each of the “renamed” versions of the data. 次の例では、上記の例を拡張してこの手法を用いることで、コンピューティング キューとグラフィックス キューをより非同期的に実行します。The next example uses this technique while extending the previous example to allow the compute and graphics queues to run more asynchronously.

非同期コンピューティングおよびグラフィックスの例Asynchronous compute and graphics example

次の例では、コンピューティング キューとは非同期的にグラフィックスでレンダリングを行えます。This next example allows graphics to render asynchronously from the compute queue. 依然として 2 つのステージ間にはバッファー処理対象のデータが一定量存在しますが、今回のグラフィックス作業は独立的に進行し、キューへのグラフィックス作業の登録時点で CPU において最も新しいコンピューティング ステージの結果が使用されます。There is still a fixed amount of buffered data between the two stages, however now graphics work proceeds independently and uses the most up-to-date result of the compute stage as known on the CPU when the graphics work is queued. これは、グラフィックス作業が別のソース (ユーザーによる入力など) によって更新される場合に役立ちます。This would be useful if the graphics work was being updated by another source, for example user input. グラフィックス作業の ComputeGraphicsLatency 個のフレームを一度に転送できるように、コマンド リストを複数使用する必要があります。また、UpdateGraphicsCommandList 関数がコマンド リストの更新に相当しており、最新の入力データを格納して適切なバッファーからコンピューティング データを読み取っています。There must be multiple command lists to allow the ComputeGraphicsLatency frames of graphics work to be in flight at a time, and the function UpdateGraphicsCommandList represents updating the command list to include the most recent input data and read from the compute data from the appropriate buffer.

ここでも、コンピューティング キューはグラフィックス キューによるパイプ バッファーの使用が終わるまで待機する必要がありますが、3 番目のフェンス (pGraphicsComputeFence) が追加されているので、グラフィックスによるコンピューティング作業の読み取りの進行状況と、一般的なグラフィックスの進行状況を比較して追跡できます。The compute queue must still wait for the graphics queue to finish with the pipe buffers, but a third fence (pGraphicsComputeFence) is introduced so that the progress of graphics reading compute work versus graphics progress in general can be tracked. これは、ここでは連続したグラフィックス フレームで同じコンピューティング結果が読み取られるか、コンピューティング結果をスキップされる可能性があることを考慮したものです。This reflects the fact that now consecutive graphics frames could read from the same compute result or could skip a compute result. 若干複雑ではあるものの効率を高める設計としては、単一のグラフィックス フェンスのみを使用し、各グラフィックス フレームで使用するコンピューティング フレームへのマッピングを格納する方法も考えられます。A more efficient but slightly more complicated design would use just the single graphics fence and store a mapping to the compute frames used by each graphics frame.

void AsyncPipelinedComputeGraphics()
{
    const UINT CpuLatency{ 3 };
    const UINT ComputeGraphicsLatency{ 2 };

    // The compute fence is at index 0; the graphics fence is at index 1.
    ID3D12Fence* rgpFences[]{ pComputeFence, pGraphicsFence };
    HANDLE handles[2];
    handles[0] = CreateEvent(nullptr, FALSE, TRUE, nullptr);
    handles[1] = CreateEvent(nullptr, FALSE, TRUE, nullptr);
    UINT FrameNumbers[]{ 0, 0 };

    ID3D12GraphicsCommandList* rgpGraphicsCommandLists[CpuLatency];
    CreateGraphicsCommandLists(ARRAYSIZE(rgpGraphicsCommandLists),
        rgpGraphicsCommandLists);

    // Graphics needs to wait for the first compute frame to complete; this is the
    // only wait that the graphics queue will perform.
    pGraphicsQueue->Wait(pComputeFence, 1);

    while (true)
    {
        for (auto i = 0; i < 2; ++i)
        {
            if (FrameNumbers[i] > CpuLatency)
            {
                rgpFences[i]->SetEventOnCompletion(
                    FrameNumbers[i] - CpuLatency,
                    handles[i]);
            }
            else
            {
                ::SetEvent(handles[i]);
            }
        }


        auto WaitResult = ::WaitForMultipleObjects(2, handles, FALSE, INFINITE);
        if (WaitResult > WAIT_OBJECT_0 + 1) continue;
        auto Stage = WaitResult - WAIT_OBJECT_0;
        ++FrameNumbers[Stage];

        switch (Stage)
        {
        case 0:
        {
            if (FrameNumbers[Stage] > ComputeGraphicsLatency)
            {
                pComputeQueue->Wait(pGraphicsComputeFence,
                    FrameNumbers[Stage] - ComputeGraphicsLatency);
            }
            pComputeQueue->ExecuteCommandLists(1, &pComputeCommandList);
            pComputeQueue->Signal(pComputeFence, FrameNumbers[Stage]);
            break;
        }
        case 1:
        {
            // Recall that the GPU queue started with a wait for pComputeFence, 1
            UINT64 CompletedComputeFrames = min(1,
                pComputeFence->GetCompletedValue());
            UINT64 PipeBufferIndex =
                (CompletedComputeFrames - 1) % ComputeGraphicsLatency;
            UINT64 CommandListIndex = (FrameNumbers[Stage] - 1) % CpuLatency;
            // Update graphics command list based on CPU input and using the appropriate
            // buffer index for data produced by compute.
            UpdateGraphicsCommandList(PipeBufferIndex,
                rgpGraphicsCommandLists[CommandListIndex]);

            // Signal *before* new rendering to indicate what compute work
            // the graphics queue is DONE with
            pGraphicsQueue->Signal(pGraphicsComputeFence, CompletedComputeFrames - 1);
            pGraphicsQueue->ExecuteCommandLists(1,
                rgpGraphicsCommandLists + PipeBufferIndex);
            pGraphicsQueue->Signal(pGraphicsFence, FrameNumbers[Stage]);
            break;
        }
        }
    }
}

複数のキュー上にあるリソースへのアクセスMulti-queue resource access

複数のキュー上にあるリソースにアクセスする場合、アプリケーションでは以下の規則を守る必要があります。To access a resource on more than one queue an application must adhere to the following rules.

  • リソースへのアクセス (「 Direct3D 12_リソース_状態」を参照) は、queue 型クラスではなく queue オブジェクトによって決定されます。Resource access (refer to Direct3D 12_RESOURCE_STATES) is determined by queue type class not queue object. Queue の2つの種類のクラスがあります。 Compute/3D キューは1つの型クラス、Copy は2番目の型クラスです。There are two type classes of queue: Compute/3D queue is one type class, Copy is a second type class. したがって、同期の要件において書き込みの大部分をシリアル化するように求められている場合は、ある 3D キュー上で NON_PIXEL_SHADER_RESOURCE 状態に対するバリアを持つリソースは、その状態のまま任意の 3D キューまたはコンピューティング キュー上で使用できます。So a resource that has a barrier to the NON_PIXEL_SHADER_RESOURCE state on one 3D queue can be used in that state on any 3D or Compute queue, subject to synchronization requirements which require most writes to be serialized. 2 つのタイプ クラス (COPY_SOURCE と COPY_DEST) 間で共有されているリソース状態は、タイプ クラスごとに異なる状態とみなされます。The resource states that are shared between the two type classes (COPY_SOURCE and COPY_DEST) are considered different states for each type class. このため、コピー キュー上でリソースが COPY_DEST に遷移する場合、3D キューまたはコンピューティング キューからコピー先としてアクセスすることはできません。その逆も同様です。So that if a resource transitions to COPY_DEST on a Copy queue it is not accessible as a copy destination from 3D or Compute queues and vice versa.

    まとめる。To summarize.

    • キュー "オブジェクト" は単一のキューである。A queue "object" is any single queue.
    • キュー "type" は、Compute、3D、Copy という3つのうちのいずれかです。A queue "type" is any one of these three: Compute, 3D, and Copy.
    • キュー "type class" は、Compute/3D と Copy の2つのうちのいずれかです。A queue "type class" is any one of these two: Compute/3D and Copy.
  • 初期状態として使用される COPY フラグ (COPY_DEST と COPY_SOURCE) は、3D/コンピューティング タイプ クラスの状態を表します。The COPY flags (COPY_DEST and COPY_SOURCE) used as initial states represent states in the 3D/Compute type class. 初めからコピー キューでリソースを使用するには、COMMON 状態で開始する必要があります。To use a resource initially on a Copy queue it should start in the COMMON state. 暗黙的な状態遷移を使用することで、COMMON 状態はコピー キュー上のあらゆる用途に利用できます。The COMMON state can be used for all usages on a Copy queue using the implicit state transitions. 

  • リソースの状態はすべてのコンピューティング キューと 3D キューで共有されますが、異なるキュー上にあるリソースに同時に書き込みを行うことはできません。Although resource state is shared across all Compute and 3D queues, it is not permitted to write to the resource simultaneously on different queues. ここで言う "同時に" とは非同期のことを指しており、一部のハードウェアでは非同期実行は許可されません。"Simultaneously" here means unsynchronized, noting unsynchronized execution is not possible on some hardware. 次の規則が適用されます。The following rules apply.

    • リソースへの書き込みを行えるキューは一度に 1 つのみです。Only one queue can write to a resource at a time.
    • ライターにより変更中のバイトを読み取らない限り、複数のキューで 1 つのリソースから読み取ることができます (同時書き込み中のバイトを読み取ると、未定義の結果が生じます)。Multiple queues can read from the resource as long as they don’t read the bytes being modified by the writer (reading bytes being simultaneously written produces undefined results).
    • 書き込み完了後、別のキューで書き込み済みのバイトを読み取るか書き込みアクセスを行う前に、フェンスを使用して同期を実行する必要があります。A fence must be used to synchronize after writing before another queue can read the written bytes or make any write access.
  • 表示されるバックバッファーは、Direct3D 12_リソース_状態_共通状態にある必要があります。Back buffers being presented must be in the Direct3D 12_RESOURCE_STATE_COMMON state. 

Direct3D 12 プログラミング ガイドDirect3D 12 programming guide

リソースの障壁を使用して、Direct3D 12 のリソースの状態を同期するUsing resource barriers to synchronize resource states in Direct3D 12

Direct3D 12 でのメモリ管理Memory management in Direct3D 12