Multimodulsynchronisierung
Die meisten modernen GPUs enthalten mehrere unabhängige Engines, die spezielle Funktionen bieten. Viele verfügen über ein oder mehrere dedizierte Kopiermodule und ein Computemodul, das sich normalerweise vom 3D-Modul unterscheidet. Jedes dieser Module kann Befehle parallel zueinander ausführen. Direct3D 12 bietet fein abgestimmten Zugriff auf die 3D-, Compute- und Kopiermodule mithilfe von Warteschlangen und Befehlslisten.
GPU-Engines
Das folgende Diagramm zeigt die CPU-Threads eines Titels, die jeweils eine oder mehrere der Kopier-, Compute- und 3D-Warteschlangen auffüllen. Die 3D-Warteschlange kann alle drei GPU-Engines steuern; die Computewarteschlange kann die Compute- und Kopiermodule steuern; und die Kopierwarteschlange einfach das Kopiermodul.
Da die verschiedenen Threads die Warteschlangen auffüllen, kann es keine einfache Garantie für die Reihenfolge der Ausführung geben, daher ist die Notwendigkeit von Synchronisierungsmechanismen erforderlich, wenn der Titel sie benötigt.
Die folgende Abbildung zeigt, wie ein Titel die Arbeit für mehrere GPU-Engines planen kann, einschließlich der Synchronisierung der Einzelnen Module, falls erforderlich: Es zeigt die Arbeitslasten pro Modul mit Abhängigkeiten zwischen den Motoren. In diesem Beispiel kopiert das Kopiermodul zunächst einige Geometrien, die zum Rendern erforderlich sind. Das 3D-Modul wartet auf den Abschluss dieser Kopien und rendert einen Vorabdurchlauf über die Geometrie. Dies wird dann vom Computemodul verbraucht. Die Ergebnisse des Computemoduls Dispatchwerden zusammen mit mehreren Texturkopievorgängen des Kopiermoduls vom 3D-Modul für den letzten Draw Aufrufs verwendet.
Der folgende Pseudocode veranschaulicht, wie ein Titel eine solche Workload übermitteln kann.
// 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
Der folgende Pseudocode veranschaulicht die Synchronisierung zwischen den Kopier- und 3D-Engines, um die heapähnliche Speicherzuweisung über einen Ringpuffer zu erreichen. Titel haben die Flexibilität, das richtige Gleichgewicht zwischen Maximierung der Parallelität (über einen großen Puffer) und reduzierung der Speicherauslastung und Latenz (über einen kleinen Puffer) zu wählen.
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
Szenarien mit mehreren Motoren
Mit Direct3D 12 können Sie verhindern, dass versehentlich Ineffizienzen auftreten, die durch unerwartete Synchronisierungsverzögerungen verursacht werden. Außerdem können Sie die Synchronisierung auf einer höheren Ebene einführen, wenn die erforderliche Synchronisierung mit größerer Sicherheit bestimmt werden kann. Ein zweites Problem, bei dem Multimoduladressen teure Vorgänge expliziter machen, einschließlich Übergänge zwischen 3D und Video, die traditionell aufgrund der Synchronisierung zwischen mehreren Kernelkontexten kostspielig waren.
Insbesondere können die folgenden Szenarien mit Direct3D 12 behoben werden.
- Die GPU mit asynchroner und niedriger Priorität funktioniert. Dies ermöglicht gleichzeitige Ausführung von GPU-Arbeiten mit niedriger Priorität und atomalen Vorgängen, mit denen ein GPU-Thread die Ergebnisse eines anderen nicht synchronisierten Threads ohne Blockierung nutzen kann.
- Rechenarbeit mit hoher Priorität. Bei der Hintergrundberechnung ist es möglich, das 3D-Rendering zu unterbrechen, um eine kleine Menge an Rechenaufgaben mit hoher Priorität zu erledigen. Die Ergebnisse dieser Arbeit können frühzeitig für die zusätzliche Verarbeitung auf der CPU abgerufen werden.
- Hintergrundberechnungsarbeit. Eine separate Warteschlange mit niedriger Priorität für Computeworkloads ermöglicht es einer Anwendung, ersatzlose GPU-Zyklen zu verwenden, um die Hintergrundberechnung ohne negative Auswirkungen auf die primären Renderingaufgaben (oder andere) Aufgaben auszuführen. Hintergrundaufgaben können die Dekomprimierung von Ressourcen oder das Aktualisieren von Simulationen oder Beschleunigungsstrukturen umfassen. Hintergrundaufgaben sollten selten (etwa einmal pro Frame) auf der CPU synchronisiert werden, um zu vermeiden, dass die Vordergrundarbeit angehalten oder verlangsamt wird.
- Streamen und Hochladen von Daten. Eine separate Kopierwarteschlange ersetzt die D3D11-Konzepte der anfänglichen Daten und das Aktualisieren von Ressourcen. Obwohl die Anwendung für weitere Details im Direct3D 12-Modell verantwortlich ist, liegt diese Verantwortung in der Leistung. Die Anwendung kann steuern, wie viel Systemspeicher zum Puffern von Uploaddaten aufgewendet wird. Die App kann auswählen, wann und wie (CPU vs GPU, Blockierung und nicht blockierend) synchronisiert werden soll, und den Fortschritt nachverfolgen und die Menge der in die Warteschlange eingereihten Arbeit steuern.
- Erhöhte Parallelität. Anwendungen können tiefere Warteschlangen für Hintergrundworkloads (z. B. Videodecodierung) verwenden, wenn sie separate Warteschlangen für Vordergrundarbeit haben.
In Direct3D 12 ist das Konzept einer Befehlswarteschlange die API-Darstellung einer ungefähr seriellen Arbeitssequenz, die von der Anwendung übermittelt wird. Barrieren und andere Techniken ermöglichen die Ausführung dieser Arbeit in einer Pipeline oder außerhalb der Reihenfolge, aber die Anwendung sieht nur eine einzige Abschlusszeitachse. Dies entspricht dem unmittelbaren Kontext in D3D11.
Synchronisierungs-APIs
Geräte und Warteschlangen
Das Direct3D 12-Gerät verfügt über Methoden zum Erstellen und Abrufen von Befehlswarteschlangen unterschiedlicher Typen und Prioritäten. Die meisten Anwendungen sollten die Standardbefehlswarteschlangen verwenden, da diese die gemeinsame Nutzung durch andere Komponenten zulassen. Anwendungen mit zusätzlichen Parallelitätsanforderungen können zusätzliche Warteschlangen erstellen. Warteschlangen werden durch den Befehlslistentyp angegeben, den sie nutzen.
Lesen Sie die folgenden Erstellungsmethoden von ID3D12Device.
- CreateCommandQueue: Erstellt eine Befehlswarteschlange basierend auf Informationen in einer Direct3D-12_COMMAND_QUEUE_DESC-Struktur.
- CreateCommandList- : erstellt eine Befehlsliste vom Typ Direct3D-12_COMMAND_LIST_TYPE.
- CreateFence : erstellt einen Zaun und notiert die Flags in Direct3D-12_FENCE_FLAGS. Zäune werden verwendet, um Warteschlangen zu synchronisieren.
Warteschlangen aller Typen (3D, Berechnen und Kopieren) verwenden dieselbe Schnittstelle und sind alle Befehlslisten basieren.
Weitere Informationen finden Sie unter den folgenden Methoden von ID3D12CommandQueue.
- ExecuteCommandLists : sendet ein Array von Befehlslisten für die Ausführung. Jede Befehlsliste, die von ID3D12CommandListdefiniert wird.
- Signal: Legt einen Zaunwert fest, wenn die Warteschlange (auf der GPU ausgeführt) einen bestimmten Punkt erreicht.
- Warten sie: Die Warteschlange wartet, bis der angegebene Zaun den angegebenen Wert erreicht.
Beachten Sie, dass Bündel nicht von Warteschlangen genutzt werden und daher kann dieser Typ nicht zum Erstellen einer Warteschlange verwendet werden.
Zäune
Die Multimodul-API stellt explizite APIs zum Erstellen und Synchronisieren mithilfe von Zaunen bereit. Ein Zaun ist ein Synchronisierungskonstrukt, das von einem UINT64-Wert gesteuert wird. Zaunwerte werden von der Anwendung festgelegt. Ein Signalvorgang ändert den Zaunwert und ein Wartevorgang blockiert, bis der Zaun den angeforderten Wert erreicht hat. Ein Ereignis kann ausgelöst werden, wenn ein Zaun einen bestimmten Wert erreicht.
Verweisen Sie auf die Methoden der ID3D12Fence Schnittstelle.
- GetCompletedValue : gibt den aktuellen Wert des Zauns zurück.
- SetEventOnCompletion: bewirkt, dass ein Ereignis ausgelöst wird, wenn der Zaun einen bestimmten Wert erreicht.
- Signal: Legt den Zaun auf den angegebenen Wert fest.
Zäune ermöglichen CPU-Zugriff auf den aktuellen Zaunwert, und CPU wartet und Signale.
Die Signal--Methode für die ID3D12Fence Schnittstelle aktualisiert einen Zaun von der CPU-Seite. Dieses Update tritt sofort auf. Die Signal--Methode für ID3D12CommandQueue aktualisiert einen Zaun von der GPU-Seite. Dieses Update tritt auf, nachdem alle anderen Vorgänge in der Befehlswarteschlange abgeschlossen wurden.
Alle Knoten in einer Mehrmoduleinrichtung können jeden Zaun lesen und darauf reagieren, der den richtigen Wert erreicht.
Anwendungen legen ihre eigenen Zaunwerte fest, ein guter Ausgangspunkt könnte einmal pro Frame einen Zaun erhöhen.
Ein Zaun kannumwunden werden. Dies bedeutet, dass der Zaunwert nicht ausschließlich inkrementieren muss. Wenn ein Signal--Vorgang auf zwei verschiedene Befehlswarteschlangen queuiert wird oder zwei CPU-Threads beide Signal auf einem Zaun aufrufen, kann es ein Rennen geben, um zu bestimmen, welches Signal zuletzt abgeschlossen ist und daher der Zaunwert der bleibt. Wenn ein Zaun umwundet wird, werden alle neuen Wartezeiten (einschließlich SetEventOnCompletion- Anforderungen) mit dem neuen niedrigeren Zaunwert verglichen und daher möglicherweise nicht erfüllt, auch wenn der Zaunwert zuvor hoch genug war, um sie zu erfüllen. Wenn ein Rennen stattfindet, wird zwischen einem Wert, der eine ausstehende Wartezeit erfüllt, und einem niedrigeren Wert, der nicht erfüllt wird, die Wartezeit unabhängig davon, welcher Wert danach bleibt, erfüllt.
Die Zaun-APIs bieten leistungsstarke Synchronisierungsfunktionen, können aber potenziell schwierig zum Debuggen von Problemen führen. Es wird empfohlen, dass jeder Zaun nur verwendet wird, um den Fortschritt auf einer Zeitachse anzuzeigen, um Rennen zwischen Signalgebern zu verhindern.
Kopieren und Berechnen von Befehlslisten
Alle drei Arten von Befehlsliste verwenden die ID3D12GraphicsCommandList- Schnittstelle, jedoch werden nur eine Teilmenge der Methoden zum Kopieren und Berechnen unterstützt.
Kopieren und Berechnen von Befehlslisten können die folgenden Methoden verwenden.
- schließen
- CopyBufferRegion-
- CopyResource-
- CopyTextureRegion-
- CopyTiles-
- zurücksetzen
- ResourceBarrier-
Compute-Befehlslisten können auch die folgenden Methoden verwenden.
- ClearState-
- ClearUnorderedAccessViewFloat
- ClearUnorderedAccessViewUint-
- DiscardResource-
- Versand
- ExecuteIndirect-
- SetComputeRoot32BitConstant
- SetComputeRoot32BitConstants
- SetComputeRootConstantBufferView-
- SetComputeRootDescriptorTable-
- SetComputeRootShaderResourceView-
- SetComputeRootSignature-
- SetComputeRootUnorderedAccessView-
- SetDescriptorHeaps-
- SetPipelineState-
- SetPredication-
- EndQuery-
Berechnungsbefehlslisten müssen beim Aufrufen SetPipelineState-eine Compute-PSO festlegen.
Bündel können nicht mit Compute- oder Kopierbefehlslisten oder Warteschlangen verwendet werden.
Beispiel für eine weitergeleitete Compute- und Grafikverarbeitung
In diesem Beispiel wird gezeigt, wie die Zaunsynchronisierung verwendet werden kann, um eine Pipeline der Computearbeit in einer Warteschlange (referenziert durch pComputeQueue
) zu erstellen, die von Grafiken in der Warteschlange pGraphicsQueue
genutzt wird. Die Rechen- und Grafikarbeit wird mit der Grafikwarteschlange weitergeleitet, die das Ergebnis der Computearbeit von mehreren Frames zurück verwendet, und ein CPU-Ereignis wird verwendet, um die Gesamtarbeitswarteschlange zu drosseln.
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);
}
}
}
Um diese Pipelining zu unterstützen, muss ein Puffer ComputeGraphicsLatency+1
verschiedenen Kopien der Daten vorhanden sein, die von der Computewarteschlange an die Grafikwarteschlange übergeben werden. Die Befehlslisten müssen UAVs und Dereferenzierung verwenden, um aus der entsprechenden "Version" der Daten im Puffer zu lesen und zu schreiben. Die Computewarteschlange muss warten, bis die Grafikwarteschlange das Lesen der Daten für Frame N abgeschlossen hat, bevor frame-N+ComputeGraphicsLatency
geschrieben werden kann.
Beachten Sie, dass die Menge der Computewarteschlange relativ zur CPU nicht direkt von der menge der Pufferung abhängig ist, die erforderlich ist, jedoch ist die Warteschlange über die menge des verfügbaren Pufferspeichers hinaus weniger wertvoll.
Ein alternativer Mechanismus zur Vermeidung der Dereferenzierung wäre das Erstellen mehrerer Befehlslisten, die den einzelnen "umbenannten" Versionen der Daten entsprechen. Im nächsten Beispiel wird diese Technik verwendet, während das vorherige Beispiel erweitert wird, damit die Compute- und Grafikwarteschlangen asynchroner ausgeführt werden können.
Beispiel für asynchrone Berechnung und Grafik
In diesem nächsten Beispiel können Grafiken asynchron aus der Computewarteschlange gerendert werden. Es gibt immer noch eine feste Menge an gepufferten Daten zwischen den beiden Phasen. Jetzt funktioniert die Grafik jedoch unabhängig voneinander und verwendet das am meisten up-to-Datumsergebnis der Berechnungsphase, die auf der CPU bekannt ist, wenn die Grafikarbeit in die Warteschlange gestellt wird. Dies wäre nützlich, wenn die Grafikarbeit von einer anderen Quelle aktualisiert wurde, z. B. Benutzereingaben. Es müssen mehrere Befehlslisten vorhanden sein, damit die ComputeGraphicsLatency
Frames von Grafiken gleichzeitig in Test-Flight ausgeführt werden können, und die Funktion UpdateGraphicsCommandList
stellt die Aktualisierung der Befehlsliste dar, um die neuesten Eingabedaten einzuschließen und aus den Computedaten aus dem entsprechenden Puffer zu lesen.
Die Computewarteschlange muss weiterhin warten, bis die Grafikwarteschlange mit den Pipepuffern fertig gestellt wird, aber ein dritter Zaun (pGraphicsComputeFence
) wird eingeführt, damit der Fortschritt der Grafikleseverarbeitung im Vergleich zum Grafikfortschritt im Allgemeinen nachverfolgt werden kann. Dies spiegelt die Tatsache wider, dass jetzt aufeinander folgende Grafikframes aus demselben Berechnungsergebnis gelesen oder ein Berechnungsergebnis übersprungen werden könnten. Ein effizienteres, aber etwas komplizierteres Design würde nur den einzelnen Grafikzaun verwenden und eine Zuordnung zu den Computeframes speichern, die von jedem Grafikframe verwendet werden.
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 + CommandListIndex);
pGraphicsQueue->Signal(pGraphicsFence, FrameNumbers[Stage]);
break;
}
}
}
}
Ressourcenzugriff in mehreren Warteschlangen
Um auf eine Ressource in mehreren Warteschlangen zuzugreifen, muss eine Anwendung die folgenden Regeln einhalten.
Der Ressourcenzugriff (siehe Direct3D-12_RESOURCE_STATES) wird durch die Warteschlangentypklasse bestimmt, die kein Warteschlangenobjekt ist. Es gibt zwei Arten von Warteschlangenklassen: Die Compute-/3D-Warteschlange ist eine Typklasse, "Copy" ist eine zweite Typklasse. Daher kann eine Ressource, die eine Barriere für den NON_PIXEL_SHADER_RESOURCE Zustand in einer 3D-Warteschlange aufweist, in diesem Zustand für jede 3D- oder Computewarteschlange verwendet werden, vorbehaltlich der Synchronisierungsanforderungen, für die die meisten Schreibvorgänge serialisiert werden müssen. Die Ressourcenzustände, die zwischen den beiden Typklassen (COPY_SOURCE und COPY_DEST) gemeinsam verwendet werden, gelten als unterschiedliche Zustände für jede Typklasse. Wenn eine Ressource zu COPY_DEST in einer Kopierwarteschlange wechselt, kann sie nicht als Kopierziel aus 3D- oder Computewarteschlangen zugegriffen werden und umgekehrt.
Zusammenfassen.
- Eine Warteschlange "objekt" ist eine beliebige Warteschlange.
- Ein Warteschlangentyp ist eine der folgenden drei: Compute, 3D und Copy.
- Eine Warteschlange "Typklasse" ist eine der folgenden beiden: Compute/3D und Copy.
Die ALS Anfangszustände verwendeten COPY-Flags (COPY_DEST und COPY_SOURCE) stellen Zustände in der 3D/Compute-Typklasse dar. Um eine Ressource zunächst in einer Kopierwarteschlange zu verwenden, sollte sie im COMMON-Zustand beginnen. Der COMMON-Zustand kann für alle Verwendungen in einer Kopierwarteschlange mithilfe der impliziten Zustandsübergänge verwendet werden.
Obwohl der Ressourcenstatus für alle Compute- und 3D-Warteschlangen freigegeben wird, ist es nicht zulässig, in die Ressource gleichzeitig in verschiedene Warteschlangen zu schreiben. "Gleichzeitig" bedeutet hier nicht synchronisiert, dass die nicht synchronisierte Ausführung auf einer bestimmten Hardware nicht möglich ist. Die folgenden Regeln gelten.
- Es kann jeweils nur eine Warteschlange in eine Ressource geschrieben werden.
- Mehrere Warteschlangen können aus der Ressource lesen, solange sie die vom Writer geänderten Bytes nicht lesen (das gleichzeitig geschriebene Lesen von Bytes führt zu nicht definierten Ergebnissen).
- Ein Zaun muss verwendet werden, um nach dem Schreiben zu synchronisieren, bevor eine andere Warteschlange die geschriebenen Bytes lesen oder schreibzugriff machen kann.
Hintergrundpuffer, die angezeigt werden, müssen sich im Direct3D-12_RESOURCE_STATE_COMMON-Zustand befinden.
Verwandte Themen
Direct3D 12-Programmieranleitung
Verwenden von Ressourcenbarrieren zum Synchronisieren von Ressourcenzuständen in Direct3D 12-