Gemeinsame Nutzung von Oberflächen zwischen Windows-Grafik-APIs

Dieses Thema bietet einen technischen Überblick über die Interoperabilität mit der gemeinsamen Nutzung von Oberflächen zwischen Window- Grafik-APIs, einschließlich Direct3D 11, Direct2D, DirectWrite, Direct3D 10 und Direct3D 9Ex. Wenn Sie diese APIs bereits kennen, kann Ihnen dieses Dokument dabei helfen, mehrere APIs zu verwenden, um in einer für die Betriebssysteme Windows 7 oder Windows Vista entwickelten Anwendung auf dieselbe Oberfläche zu rendern. Dieses Thema enthält außerdem Richtlinien für bewährte Verfahren und Verweise auf zusätzliche Ressourcen.

Hinweis

Für Direct2D- und DirectWrite-Interoperabilität in der DirectX 11.1-Runtime können Sie Direct2D-Geräte und Gerätekontexte verwenden, um direkt auf Direct3D 11-Geräte zu rendern.

 

Dieses Thema enthält folgende Abschnitte:

Einführung

In diesem Dokument bezieht sich die Interoperabilität von Windows-Grafik-APIs auf die gemeinsame Nutzung der gleichen Rendering-Oberfläche durch verschiedene APIs. Diese Art der Interoperabilität ermöglicht es Anwendungen, überzeugende Bildschirme zu erstellen, indem sie mehrere Windows-Grafik-APIs nutzen, und die Migration zu neuen Technologien zu erleichtern, indem die Kompatibilität mit bestehenden APIs beibehalten wird.

In Windows 7 (und Windows Vista SP2 mit Windows 7 Interop Pack, Vista 7IP) sind die Grafik-Rendering-APIs Direct3D 11, Direct2D, Direct3D 10.1, Direct3D 10.0, Direct3D 9Ex, Direct3D 9c und frühere Direct3D-APIs sowie GDI und GDI+. Windows Imaging Component (WIC) und DirectWrite sind verwandte Technologien für die Verarbeitung von Bildern, und Direct2D übernimmt das Rendering von Text. DirectX Video Acceleration API (DXVA), basierend auf Direct3D 9c und Direct3D 9Ex, wird für die Videoverarbeitung verwendet.

Da sich die Windows-Grafik-APIs immer mehr zu Direct3D-basierten APIs entwickeln, investiert Microsoft mehr in die Gewährleistung der Interoperabilität zwischen den APIs. Neu entwickelte Direct3D-APIs und APIs auf höherer Ebene, die auf Direct3D-APIs basieren, bieten bei Bedarf auch Unterstützung für die Überbrückung der Kompatibilität mit älteren APIs. So können beispielsweise Direct2D-Anwendungen Direct3D 10.1 verwenden, indem sie ein Direct3D 10.1-Gerät gemeinsam nutzen. Außerdem können Direct3D 11, Direct2D und Direct3D 10.1 APIs alle die Vorteile der DirectX Graphics Infrastructure (DXGI) 1.1 nutzen, die synchronisierte gemeinsame Oberflächen ermöglichen, die die Interoperabilität zwischen diesen APIs vollständig unterstützen. DXGI 1.1-basierte APIs interagieren mit GDI und GDI+, indem sie den GDI-Gerätekontext von einer DXGI 1.1-Oberfläche abrufen.

Die unsynchronisierte gemeinsame Nutzung von Oberflächen wird von der Direct3D 9Ex-Runtime unterstützt. DXVA-basierte Videoanwendungen können die Direct3D 9Ex- und DXGI-Interoperabilitätshilfe für Direct3D 9Ex-basierte DXVA-Interoperabilität mit Direct3D 11 für Compute Shader verwenden oder mit Direct2D für 2D-Steuerelemente oder Textrendering interagieren. WIC und DirectWrite arbeiten auch mit GDI, Direct2D und durch Assoziation mit anderen Direct3D-APIs zusammen.

Direct3D 10.0, Direct3D 9c und ältere Direct3D-Runtimes unterstützen keine gemeinsamen Oberflächen. Kopien aus dem Systemspeicher werden weiterhin für die Interoperabilität mit GDI- oder DXGI-basierten APIs verwendet.

Beachten Sie, dass sich die Interoperabilitätsszenarien in diesem Dokument auf mehrere Grafik-APIs beziehen, die auf einer gemeinsamen Rendering-Oberfläche rendern, und nicht auf dasselbe Anwendungsfenster. Die Synchronisierung für separate APIs, die auf unterschiedliche Oberflächen abzielen und dann als Composite auf dasselbe Fenster angewendet werden, liegt außerhalb des Bereichs dieses Dokuments.

Übersicht über die API-Interoperabilität

Die Interoperabilität von Windows-Grafik-APIs bei der gemeinsamen Nutzung von Oberflächen lässt sich anhand von API-zu-API-Szenarien und den entsprechenden Interoperabilitätsfunktionen beschreiben. Ab Windows 7 und ab Windows Vista SP2 mit 7IP umfassen neue APIs und zugehörige Runtimes Direct2D und verwandte Technologien: Direct3D 11 und DXGI 1.1. Auch die GDI-Leistung wurde in Windows 7 verbessert. Direct3D 10.1 wurde in Windows Vista SP1 eingeführt. Das folgende Diagramm zeigt die Unterstützung der Interoperabilität zwischen den APIs.

Diagramm zur Unterstützung der Interoperabilität zwischen Windows-Grafik-APIs

In diesem Diagramm zeigen die Pfeile Interoperabilitätsszenarien an, in denen die verbundenen APIs auf dieselbe Oberfläche zugreifen können. Blaue Pfeile weisen auf Interoperabilitätsmechanismen hin, die in Windows Vista eingeführt wurden. Grüne Pfeile zeigen die Unterstützung der Interoperabilität für neue APIs oder Verbesserungen an, die älteren APIs helfen, mit neueren APIs zu interagieren. Die grünen Pfeile stehen zum Beispiel für die gemeinsame Nutzung von Geräten, die Unterstützung synchronisierter gemeinsamer Oberflächen, die Direct3D 9Ex/DXGI-Synchronisierungshilfe und das Abrufen eines GDI-Gerätekontexts von einer kompatiblen Oberfläche.

Interoperabilitätsszenarien

Ab Windows 7 und Windows Vista 7IP unterstützen die wichtigsten Grafik-APIs von Windows mehrere APIs, die auf dieselbe DXGI 1.1-Oberfläche rendern.

Direct3D 11, Direct3D 10.1, Direct2D – Interoperabilität miteinander

Direct3D 11, Direct3D 10.1 und Direct2D APIs (und die zugehörigen APIs wie DirectWrite und WIC) können miteinander interagieren, indem sie entweder die gemeinsame Nutzung von Direct3D 10.1-Geräten oder synchronisierte gemeinsame Oberflächen verwenden.

Gemeinsame Nutzung von Direct3D 10.1-Geräten mit Direct2D

Die gemeinsame Nutzung von Geräten zwischen Direct2D und Direct3D 10.1 bietet einer Anwendung die Möglichkeit, beide APIs zu verwenden, um nahtlos und effizient auf derselben DXGI 1.1-Oberfläche zu rendern, wobei dasselbe unterliegende Direct3D-Geräteobjekt verwendet wird. Direct2D bietet die Möglichkeit, Direct2D-APIs über ein vorhandenes Direct3D 10.1-Gerät aufzurufen. Dabei wird die Tatsache genutzt, dass Direct2D über Direct3D 10.1 und DXGI 1.1-Runtimes erstellt wurde. Die folgenden Code-Snippets veranschaulichen, wie Direct2D das Renderziel für Direct3D 10.1-Geräte von einer DXGI 1.1-Oberfläche abruft, die dem Gerät zugeordnet ist. Das Direct3D 10.1 Geräte-Rendering-Ziel kann Direct2D-Drawing-Aufrufe zwischen BeginDraw- und EndDraw-APIs ausführen.

// Direct3D 10.1 Device and Swapchain creation
HRESULT hr = D3D10CreateDeviceandSwapChain1(
                pAdapter,
                DriverType,
                Software,
                D3D10_CREATE_DEVICE_BGRA_SUPPORT,
                featureLevel,
                D3D10_1_SDK_VERSION,
                pSwapChainDesc,
                &pSwapChain,
                &pDevice
                );

hr = pSwapChain->GetBuffer(
        0,
        __uuidof(IDXGISurface),
        (void **)&pDXGIBackBuffer
        ));

// Direct3D 10.1 API rendering calls
...

hr = D2D1CreateFactory(
        D2D1_FACTORY_TYPE_SINGLE_THREADED,
        &m_spD2DFactory
        ));

pD2DFactory->CreateDxgiSurfaceRenderTarget(
        pDXGIBackBuffer,
        &renderTargetProperties,
        &pD2DBackBufferRenderTarget
        ));
...

pD2DBackBufferRenderTarget->BeginDraw();
//Direct2D API rendering calls
...

pD2DBackBufferRenderTarget->EndDraw();

pSwapChain->Present(0, 0);

Anmerkungen

  • Das zugehörige Direct3D 10.1-Gerät muss das BGRA-Format unterstützen. Dieses Gerät wurde durch den Aufruf von D3D10CreateDevice1 mit dem Parameter D3D10_CREATE_DEVICE_BGRA_SUPPORT erstellt. Das BGRA-Format wird ab der Direct3D 10 Funktion 9.1 unterstützt.
  • Die Anwendung sollte nicht mehrere ID2D1RenderTargets erstellen, die mit demselben Direct3D10.1-Gerät verknüpft sind.
  • Um eine optimale Leistung zu erzielen, sollten Sie immer mindestens eine Ressource behalten, z. B. Texturen oder Oberflächen, die mit dem Gerät verbunden ist.

Die gemeinsame Nutzung von Geräten eignet sich für die prozessinterne Single-Thread-Nutzung eines Geräts, das sowohl von Direct3D 10.1 als auch von Direct2D Rendering-APIs gemeinsam genutzt wird. Synchronisierte gemeinsam genutzte Oberflächen ermöglichen die prozessinterne und prozessübergreifende Nutzung mehrerer Rendering-Geräte, die von Direct3D 10.1-, Direct2D- und Direct3D 11-APIs verwendet werden, mit mehreren Threads.

Eine weitere Methode der Interoperabilität von Direct3D 10.1 und Direct2D ist die Verwendung von ID3D1RenderTarget::CreateSharedBitmap, das ein ID2D1Bitmap-Objekt aus IDXGISurface erstellt. Sie können eine Direct3D10.1-Szene in die Bitmap schreiben und sie mit Direct2D rendern. Weitere Informationen finden Sie unter ID2D1RenderTarget::CreateSharedBitmap-Methode.

Direct2D Software-Rasterung

Die gemeinsame Nutzung von Geräten mit Direct3D 10.1 wird nicht unterstützt, wenn Sie den Direct2D Software-Renderer verwenden, z. B. durch Angabe von D2D1_RENDER_TARGET_USAGE_FORCE_SOFTWARE_RENDERING in D2D1_RENDER_TARGET_USAGE beim Erstellen eines Direct2D-Renderziels.

Direct2D kann den WARP10-Software-Rasterizer verwenden, um das Gerät mit Direct3D 10 oder Direct3D 11 zu teilen, aber die Leistung nimmt deutlich ab.

DXGI 1.1-synchronisierte gemeinsam genutzte Oberflächen

Direct3D 11, Direct3D 10.1 und Direct2D APIs verwenden alle DXGI 1.1, das die Möglichkeit bietet, das Lesen und Schreiben auf derselben Videospeicheroberfläche (DXGISurface1) durch zwei oder mehr Direct3D-Geräte zu synchronisieren. Bei den Rendering-Geräten, die synchronisierte gemeinsame Oberflächen verwenden, kann es sich um Direct3D 10.1- oder Direct3D 11-Geräte handeln, die jeweils im selben Prozess oder prozessübergreifend ausgeführt werden.

Anwendungen können synchronisierte gemeinsame Oberflächen verwenden, um zwischen beliebigen DXGI 1.1-basierten Geräten wie Direct3D 11 und Direct3D 10.1 oder zwischen Direct3D 11 und Direct2D zu interagieren, indem sie das Direct3D 10.1-Gerät vom Direct2D-Rendering-Zielobjekt abrufen.

In Direct3D 10.1 und neueren APIs müssen Sie zur Verwendung von DXGI 1.1 sicherstellen, dass das Direct3D-Gerät mit einem DXGI 1.1-Adapterobjekt erstellt wird, das vom DXGI 1.1-Factory-Objekt aufgelistet wird. Rufen Sie CreateDXGIFactory1 auf, um das Objekt IDXGIFactory1 zu erstellen, und EnumAdapters1, um das Objekt IDXGIAdapter1 aufzulisten. Das Objekt IDXGIAdapter1 muss als Teil des Aufrufs von D3D10CreateDevice oder D3D10CreateDeviceAndSwapChain übergeben werden. Weitere Informationen über DXGI 1.1-APIs finden Sie in der Programmieranleitung für DXGI.

APIs

D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX
Wenn Sie die synchronisierte gemeinsame Ressource erstellen, legen Sie D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX in D3D10_RESOURCE_MISC_FLAG fest.

typedef enum D3D10_RESOURCE_MISC_FLAG {
    D3D10_RESOURCE_MISC_GENERATE_MIPS      = 0x1L,
    D3D10_RESOURCE_MISC_SHARED             = 0x2L,
    D3D10_RESOURCE_MISC_TEXTURECUBE        = 0x4L,
    D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX  = 0x10L,
    D3D10_RESOURCE_MISC_GDI_COMPATIBLE     = 0x20L,
}   D3D10_RESOURCE_MISC_FLAG;

D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX
Ermöglicht die Synchronisierung der erstellten Ressource mit Hilfe der IDXGIKeyedMutex::AcquireSync- und ReleaseSync-APIs. Die folgenden Direct3D 10.1-APIs zur Erstellung von Ressourcen, die alle einen D3D10_RESOURCE_MISC_FLAG-Parameter benötigen, wurden erweitert, um das neue Flag zu unterstützen.

  • ID3D10Device1::CreateTexture1D
  • ID3D10Device1::CreateTexture2D
  • ID3D10Device1::CreateTexture3D
  • ID3D10Device1::CreateBuffer

Wenn eine der aufgeführten Funktionen mit dem festgelegten Flag D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX aufgerufen wird, kann die zurückgegebene Schnittstelle auf eine IDXGIKeyedMutex-Schnittstelle abgefragt werden, die die APIs AcquireSync und ReleaseSync implementiert, um den Zugriff auf die Oberfläche zu synchronisieren. Das Gerät, das die Oberfläche erstellt, und jedes andere Gerät, das die Oberfläche öffnet (mit OpenSharedResource), muss IDXGIKeyedMutex::AcquireSync aufrufen, bevor es Rendering-Befehle an die Oberfläche sendet, und IDXGIKeyedMutex::ReleaseSync, wenn es mit dem Rendering fertig ist.
WARP- und REF-Geräte unterstützen keine gemeinsam genutzten Ressourcen. Der Versuch, eine Ressource mit diesem Flag auf einem WARP- oder REF-Gerät zu erstellen, führt dazu, dass die Methode create einen E_OUTOFMEMORY-Fehlercode zurückgibt.
IDXGIKEYEDMUTEX INTERFACE
Eine neue Schnittstelle in DXGI 1.1, IDXGIKeyedMutex, repräsentiert einen Keyed Mutex, der exklusiven Zugriff auf eine gemeinsam genutzte Ressource bietet, die von mehreren Geräten verwendet wird. Eine Referenzdokumentation zu dieser Schnittstelle und ihren beiden Methoden AcquireSync und ReleaseSync finden Sie unter IDXGIKeyedMutex.

Beispiel: Synchronisierte gemeinsame Nutzung einer Oberfläche zwischen zwei Direct3D 10.1-Geräten

Das folgende Beispiel veranschaulicht die gemeinsame Nutzung einer Oberfläche durch zwei Direct3D 10.1-Geräte. Die synchronisierte gemeinsame Oberfläche wird von einem Direct3D10.1-Gerät erstellt.

// Create Sync Shared Surface using Direct3D10.1 Device 1.
D3D10_TEXTURE2D_DESC desc;
ZeroMemory( &desc, sizeof(desc) );
desc.Width = width;
desc.Height = height;
desc.MipLevels = 1;
desc.ArraySize = 1;
// must match swapchain format in order to CopySubresourceRegion.
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.Usage = D3D10_USAGE_DEFAULT;
// creates 2D texture as a Synchronized Shared Surface.
desc.MiscFlags = D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX;
desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE;
ID3D10Texture2D* g_pShared = NULL;
g_pd3dDevice1->CreateTexture2D( &desc, NULL, &g_pShared );

// QI IDXGIResource interface to synchronized shared surface.
IDXGIResource* pDXGIResource = NULL;
g_pShared->QueryInterface(__uuidof(IDXGIResource), (LPVOID*) &pDXGIResource);

// obtain handle to IDXGIResource object.
pDXGIResource->GetSharedHandle(&g_hsharedHandle);
pDXGIResource->Release();
if ( !g_hsharedHandle )
    return E_FAIL;

// QI IDXGIKeyedMutex interface of synchronized shared surface's resource handle.
hr = g_pShared->QueryInterface( __uuidof(IDXGIKeyedMutex),
    (LPVOID*)&g_pDXGIKeyedMutex_dev1 );
If ( FAILED( hr ) || ( g_pDXGIKeyedMutex_dev1 == NULL ) )
    return E_FAIL;

Dasselbe Direct3D10.1-Gerät kann die synchronisierte gemeinsame Oberfläche für das Rendering abrufen, indem es AcquireSync aufruft und dann die Oberfläche für das Rendering des anderen Geräts durch den Aufruf von ReleaseSync freigibt. Wenn die synchronisierte gemeinsame Oberfläche nicht mit einem anderen Direct3D-Gerät geteilt wird, kann der Ersteller die synchronisierte gemeinsame Oberfläche (zum Starten und Beenden des Renderings) durch AcquireSync und ReleaseSync mit demselben Schlüsselwert abrufen und freigeben.

// Obtain handle to Sync Shared Surface created by Direct3D10.1 Device 1.
hr = g_pd3dDevice2->OpenSharedResource( g_hsharedHandle,__uuidof(ID3D10Texture2D),
                                        (LPVOID*) &g_pdev2Shared);
if (FAILED (hr))
    return hr;
hr = g_pdev2Shared->QueryInterface( __uuidof(IDXGIKeyedMutex),
                                    (LPVOID*) &g_pDXGIKeyedMutex_dev2);
if( FAILED( hr ) || ( g_pDXGIKeyedMutex_dev2 == NULL ) )
    return E_FAIL;

// Rendering onto Sync Shared Surface from D3D10.1 Device 1 using D3D10.1 Device 2.
UINT acqKey = 1;
UINT relKey = 0;
DWORD timeOut = 5;
DWORD result = g_pDXGIKeyedMutex_dev2->AcquireSync(acqKey, timeOut);
if ( result == WAIT_OBJECT_0 )
    // Rendering calls using Device 2.
else
    // Handle unable to acquire shared surface error.
result = g_pDXGIKeyedMutex_dev2->ReleaseSync(relKey));
if (result == WAIT_OBJECT_0)
    return S_OK;

Das zweite Direct3D10.1-Gerät kann die synchronisierte gemeinsame Oberfläche für das Rendering abrufen, indem es AcquireSync aufruft und dann die Oberfläche für das Rendering des ersten Geräts durch den Aufruf von ReleaseSync freigibt. Beachten Sie, dass Gerät 2 in der Lage ist, die synchronisierte gemeinsame Oberfläche mit demselben Schlüsselwert zu übernehmen, der in dem ReleaseSync-Aufruf von Gerät 1 angegeben wurde.

// Rendering onto Sync Shared Surface from D3D10.1 Device 1 using D3D10.1 Device 1.
UINT acqKey = 0;
UINT relKey = 1;
DWORD timeOut = 5;
DWORD result = g_pDXGIKeyedMutex_dev1->AcquireSync(acqKey, timeOut);
if (result == WAIT_OBJECT_0)
    // Rendering calls using Device 1.
else
    // Handle unable to acquire shared surface error.
result = g_pDXGIKeyedMutex_dev1->ReleaseSync(relKey));
if ( result == WAIT_OBJECT_0 )
    return S_OK;

Weitere Geräte, die sich dieselbe Oberfläche teilen, können abwechselnd die Oberfläche übernehmen und freigeben, indem sie zusätzliche Schlüssel verwenden, wie in den folgenden Aufrufen gezeigt.

// Within Device 1's process/thread:
// Rendering onto Sync Shared Surface from D3D10.1 Device 1 using D3D10.1 Device 1
result = g_pDXGIKeyedMutex_dev1->AcquireSync(0, timeOut);
// Rendering calls using Device 1
...
result = g_pDXGIKeyedMutex_dev1->ReleaseSync(1);
...
////////////////////////////////////////////////////////////////////////////
// Within Device 2's process/thread:
// Rendering onto Sync Shared Surface from D3D10.1 Device 1 using D3D10.1 Device 2
result = g_pDXGIKeyedMutex_dev2->AcquireSync(1, timeOut);
// Rendering calls using Device 2
...
result = g_pDXGIKeyedMutex_dev1->ReleaseSync(2);

////////////////////////////////////////////////////////////////////////////
// Within Device 3's process/thread:
// Rendering onto Sync Shared Surface from D3D10.1 Device 1 using D3D10.1 Device 3
result = g_pDXGIKeyedMutex_dev1->AcquireSync(2, timeOut);
// Rendering calls using Device 3
...
result = g_pDXGIKeyedMutex_dev1->ReleaseSync(0);
...

Beachten Sie, dass eine reale Anwendung möglicherweise immer auf einer Zwischenoberfläche gerendert wird, die dann in die gemeinsame Oberfläche kopiert wird, um zu verhindern, dass ein Gerät auf ein anderes Gerät wartet, das die Oberfläche gemeinsam nutzt.

Verwendung synchronisierter gemeinsamer Oberflächen mit Direct2D und Direct3D 11

Auch für die gemeinsame Nutzung von Direct3D 11- und Direct3D 10.1-APIs kann eine synchronisierte gemeinsame Oberfläche von einem der beiden API-Geräte erstellt und mit dem/den anderen API-Gerät(en) gemeinsam genutzt werden, sowohl während als auch außerhalb des Prozesses.

Anwendungen, die Direct2D verwenden, können ein Direct3D 10.1-Gerät gemeinsam nutzen und eine synchronisierte gemeinsame Oberfläche verwenden, um mit Direct3D 11- oder anderen Direct3D 10.1-Geräten zu interagieren, unabhängig davon, ob sie zum selben Prozess oder zu verschiedenen Prozessen gehören. Für Single-Prozess- und Single-Thread-Anwendungen ist die gemeinsame Nutzung von Geräten jedoch die leistungsfähigste und effizienteste Methode der Interoperabilität zwischen Direct2D und Direct3D 10 oder Direct3D 11.

Software-Rasterizer

Synchronisierte gemeinsame Oberflächen werden nicht unterstützt, wenn Anwendungen die Direct3D- oder Direct2D-Software-Rasterizer, einschließlich des Referenz-Rasterizers und WARP, verwenden, anstatt die Grafikhardwarebeschleunigung zu nutzen.

Interoperabilität zwischen Direct3D 9Ex und DXGI-basierten APIs

Die Direct3D 9Ex-APIs umfassen das Konzept der gemeinsamen Nutzung von Oberflächen, um anderen APIs die Möglichkeit zu bieten, von der gemeinsamen Oberfläche zu lesen. Um das Lesen und Schreiben auf einer gemeinsam genutzten Direct3D 9Ex-Oberfläche zu nutzen, müssen Sie der Anwendung selbst eine manuelle Synchronisierung hinzufügen.

Gemeinsame Direct3D 9Ex-Oberflächen plus Helper für die manuelle Synchronisierung

Die grundlegendste Aufgabe bei der Interoperabilität von Direct3D 9Ex und Direct3D 10 oder 11 besteht darin, eine einzelne Oberfläche vom ersten Gerät (Gerät A) an das zweite (Gerät B) weiterzugeben, sodass das Rendering von Gerät A garantiert abgeschlossen ist, wenn Gerät B einen Zugriff auf die Oberfläche erhält. Daher kann Gerät B diese Oberfläche unbesorgt nutzen. Dies ist dem klassischen Producer-Consumer-Problem sehr ähnlich, und in dieser Diskussion wird das Problem auf diese Weise modelliert. Das erste Gerät, das die Oberfläche nutzt und sie dann wieder abgibt, ist der Producer (Gerät A), und das Gerät, das zunächst wartet, ist der Consumer (Gerät B). Eine reale Anwendung ist jedoch komplexer und wird mehrere Producer-Consumer-Bausteine miteinander verknüpfen, um die gewünschte Funktionalität zu erzeugen.

Die Producer-Consumer-Bausteine werden im Helper durch eine Warteschlange von Oberflächen implementiert. Die Oberflächen werden vom Producer in die Warteschlange gestellt und vom Consumer wieder entfernt. Der Helper führt drei COM-Schnittstellen ein: ISurfaceQueue, ISurfaceProducer, und ISurfaceConsumer.

High-Level Übersicht des Helpers

Das ISurfaceQueue-Objekt ist der Baustein für die Verwendung der gemeinsamen Oberflächen. Es wird mit einem initialisierten Direct3D-Gerät und einer Beschreibung erstellt, um eine feste Anzahl von gemeinsam genutzten Oberflächen zu erstellen. Das Warteschlangen-Objekt verwaltet die Erstellung der Ressourcen und das Öffnen des Codes. Die Anzahl und der Typ der Oberflächen sind festgelegt. Sobald die Oberflächen erstellt sind, kann die Anwendung sie nicht mehr hinzufügen oder entfernen.

Jede Instanz des ISurfaceQueue-Objekts stellt eine Art Einbahnstraße dar, mit der Oberflächen vom Producer-Gerät an das Consumer-Gerät gesendet werden können. Mehrere solcher Einbahnstraßen können verwendet werden, um Szenarien der gemeinsamen Nutzung von Oberflächen zwischen Geräten bestimmter Anwendungen zu ermöglichen.

Erstellung/Objekt-Lebensdauer
Es gibt zwei Möglichkeiten, die Warteschlange zu erstellen: über CreateSurfaceQueue oder über die Methode Clone von ISurfaceQueue. Da es sich bei den Schnittstellen um COM-Objekte handelt, gilt die standardmäßige COM-Lebensdauerverwaltung.
Producer/Consumer Modell
Enqueue (): Der Produzent ruft diese Funktion auf, um zu signalisieren, dass er mit der Oberfläche fertig ist und diese nun einem anderen Gerät zur Verfügung steht. Nach der Rückkehr von dieser Funktion hat das Producer-Gerät keine Rechte mehr an der Oberfläche, und es ist unsicher, sie weiter zu verwenden.
Dequeue (): Das Consumer-Gerät ruft diese Funktion auf, um eine gemeinsam genutzte Oberfläche zu erhalten. Die API garantiert, dass alle Oberflächen, die aus der Warteschlange genommen wurden, zur Verwendung bereit sind.
Metadaten
Die API unterstützt die Verknüpfung von Metadaten mit den gemeinsam genutzten Oberflächen.
Enqueue() hat die Möglichkeit, zusätzliche Metadaten anzugeben, die an das Consumer-Gerät weitergegeben werden. Die Metadaten müssen kleiner sein als ein zum Zeitpunkt der Erstellung bekanntes Maximum.
Dequeue() kann optional einen Puffer und einen Zeiger auf die Größe des Puffers übergeben. Die Warteschlange füllt den Puffer mit den Metadaten aus dem entsprechenden Enqueue-Aufruf auf.
Klonen?
Jedes ISurfaceQueue-Objekt erfüllt eine Einweg-Synchronisierung. Wir gehen davon aus, dass die überwiegende Mehrheit der Anwendungen, die diese API verwenden, ein geschlossenes System nutzen werden. Das einfachste geschlossene System mit zwei Geräten, die Oberflächen hin- und herschicken, erfordert zwei Warteschlangen. Das ISurfaceQueue-Objekt verfügt über eine Clone()-Methode, die es ermöglicht, mehrere Warteschlangen zu erstellen, die alle Teil derselben größeren Pipeline sind.
Clone erstellt ein neues ISurfaceQueue-Objekt aus einer bestehenden Warteschlange und teilt alle geöffneten Ressourcen mit ihr. Das resultierende Objekt hat genau dieselben Oberflächen wie die Ausgangswarteschlange. Geklonte Warteschlangen können unterschiedliche Metadatengrößen haben.
flächen
Die ISurfaceQueue übernimmt die Verantwortung für das Erstellen und Verwalten ihrer Oberflächen. Es ist nicht zulässig, beliebige Oberflächen in die Warteschlange aufzunehmen. Außerdem sollte eine Oberfläche nur einen aktiven „Besitzer“ haben. Sie sollte sich entweder in einer bestimmten Warteschlange befinden oder von einem bestimmten Gerät verwendet werden. Es ist nicht zulässig, dass sie in mehreren Warteschlangen steht oder dass Geräte die Oberfläche weiter benutzen, nachdem sie in die Warteschlange gestellt wurde.

API Details

IsurfaceQueue

Die Warteschlange ist für das Erstellen und Verwalten der gemeinsamen Ressourcen verantwortlich. Sie bietet auch die Möglichkeit, mehrere Warteschlangen mit Clone zu verketten. Die Warteschlange verfügt über Methoden, die das Producer-Gerät und ein Consumer-Gerät öffnen. Es kann immer nur eines von beiden geöffnet werden.

Die Warteschlange stellt die folgenden APIs zur Verfügung:

API Beschreibung
CreateSurfaceQueue Erzeugt ein ISurfaceQueue-Objekt (die „Root“-Warteschlange).
ISurfaceQueue::OpenConsumer Gibt eine Schnittstelle für das Consumer-Gerät zurück, um die Warteschlange zu dequeuen.
ISurfaceQueue::OpenProducer Gibt eine Schnittstelle für das Producer-Gerät zum Einreihen in die Warteschlange zurück.
ISurfaceQueue::Clone Erstellt ein ISurfaceQueue-Objekt, das Oberflächen mit dem Objekt der Root-Warteschlange teilt.

 

CreateSurfaceQueue

typedef struct SURFACE_QUEUE_DESC {
  UINT            Width;
  UINT            Height;
  DXGI_FORMAT     Format;
  UINT            NumSurfaces;
  UINT            MetaDataSize;
  DWORD           Flags;
} SURFACE_QUEUE_DESC;

Mitglieder

Width, Height Die Dimensionen der gemeinsam genutzten Oberflächen. Alle gemeinsam genutzten Oberflächen müssen die gleichen Dimensionen haben.
Format Das Format der gemeinsam genutzten Oberflächen. Alle gemeinsam genutzten Oberflächen müssen das gleiche Format haben. Die gültigen Formate hängen von den verwendeten Geräten ab, da verschiedene gekoppelte Geräte unterschiedliche Formattypen haben können.
NumSurfaces Die Anzahl der Oberflächen, die Teil der Warteschlange sind. Dies ist eine feste Zahl.
MetaDataSize Die maximale Größe des Metadatenpuffers.
Flags Flags, um das Verhalten der Warteschlange zu steuern. Siehe Hinweise.

HRESULT CreateSurfaceQueue(
  [in]   SURFACE_QUEUE_DESC *pDesc,
  [in]   IUnknown *pDevice,
  [out]  IDXGIXSurfaceQueue **ppQueue
);

Parameter

pDesc [in] Die Beschreibung der zu erstellenden Warteschlange für gemeinsam genutzte Oberflächen.

pDevice [in] Das Gerät, das zum Erstellen der gemeinsam genutzten Oberflächen verwendet werden soll. Dies ist ein expliziter Parameter aufgrund einer Funktion in Windows Vista. Für Oberflächen, die von Direct3D 9 und Direct3D 10 gemeinsam genutzt werden, müssen die Oberflächen mit Direct3D 9 erstellt werden.

ppQueue [out] Enthält bei Rückgabe einen Zeiger auf das Objekt ISurfaceQueue.

Rückgabewerte

Wenn pDevice nicht in der Lage ist, Ressourcen gemeinsam zu nutzen, gibt diese Funktion DXGI_ERROR_INVALID_CALL zurück. Diese Funktion erstellt die Ressourcen. Wenn sie fehlschlägt, gibt sie einen Fehler zurück. Wenn sie erfolgreich ist, gibt sie S_OK zurück.

Anmerkungen

Durch das Erstellen des Warteschlangen-Objekts werden auch alle Oberflächen erstellt. Es wird davon ausgegangen, dass es sich bei allen Oberflächen um 2D-Renderziele handelt. Sie werden mit den gesetzten Flags D3D10_BIND_RENDER_TARGET und D3D10_BIND_SHADER_RESOURCE erstellt (oder den entsprechenden Flags für die verschiedenen Runtimes).

Die Entwickler*innen können ein Flag angeben, das angibt, ob der Zugriff auf die Warteschlange durch mehrere Threads erfolgt. Wenn keine Flags festgelegt sind (Flags == 0), wird die Warteschlange von mehreren Threads verwendet. Die Entwickler*innen können den Zugriff auf die Warteschlange durch einen einzigen Thread festlegen, wodurch der Code für die Synchronisierung ausgeschaltet wird und die Leistung in diesen Fällen verbessert wird. Jede geklonte Warteschlange hat ihr eigenes Flag, sodass es möglich ist, dass verschiedene Warteschlangen im System unterschiedliche Synchronisationselemente haben.

Öffnen eines Producers

HRESULT OpenProducer(
  [in]   IUnknown *pDevice,
  [out]  IDXGIXSurfaceProducer **ppProducer
);

Parameter

pDevice [in]

Das Producer-Gerät, das Oberflächen in die Warteschlange für Oberflächen einreiht.

ppProducer [out] Gibt ein Objekt an die Producer-Schnittstelle zurück.

Rückgabewerte

Wenn das Gerät nicht in der Lage ist, Oberflächen zu teilen, wird DXGI_ERROR_INVALID_CALL zurückgegeben.

Öffnen eines Consumers

HRESULT OpenConsumer(
  [in]   IUnknown *pDevice,
  [out]  IDXGIXSurfaceConsumer **ppConsumer
);

Parameter
pDevice [in]
Das Consumer-Gerät, das Oberflächen aus der Warteschlange für Oberflächen abruft. ppConsumer [out] Gibt ein Objekt an die Consumer-Schnittstelle zurück.

Rückgabewerte

Wenn das Gerät nicht in der Lage ist, Oberflächen zu teilen, wird DXGI_ERROR_INVALID_CALL zurückgegeben.

Anmerkungen

Diese Funktion öffnet alle Oberflächen in der Warteschlange des Geräts und speichert sie zwischen. Nachfolgende Aufrufe von Dequeue gehen einfach in den Cache und müssen die Oberflächen nicht jedes Mal neu öffnen.

Klonen einer IDXGIXSurfaceQueue

typedef struct SHARED_SURFACE_QUEUE_CLONE_DESC {
  UINT         MetaDataSize;
  DWORD        Flags;
} SHARED_SURFACE_QUEUE_CLONE_DESC;

Members MetaDataSize und Flags haben das gleiche Verhalten wie bei CreateSurfaceQueue.

HRESULT Clone(
  [in]   SHARED_SURFACE_QUEUE_CLONE_DESC *pDesc,
  [out]  IDXGIXSurfaceQueue **ppQueue
);

Parameter

pDesc [in] Eine Struktur, die eine Beschreibung des zu erstellenden Clone-Objekts liefert. Dieser Parameter sollte initialisiert werden.
ppQueue [out] Gibt das initialisierte Objekt zurück.

Anmerkungen

Sie können von jedem bestehenden Warteschlangen-Objekt klonen, auch wenn es nicht die Root ist.

IDXGIXSurfaceConsumer

HRESULT Dequeue(
  [in]      REFIID    id,
  [out]     void      **ppSurface,
  [in,out]  void      *pBuffer,
  [in,out]  UINT      *pBufferSize,
  [in]      DWORD     dwTimeout
);

Parameter
id [in]
Die REFIID einer 2D-Oberfläche des Consumer-Gerätes.

  • Für ein IDirect3DDevice9 sollte die REFIID __uuidof(IDirect3DTexture9) sein.
  • Für ein ID3D10Device sollte die REFIID __uuidof(ID3D10Texture2D) sein.
  • Für ein ID3D11Device sollte die REFIID __uuidof(ID3D11Texture2D) sein.

ppSurface [out] Gibt einen Zeiger auf die Oberfläche zurück.
pBuffer [in, out] Ein optionaler Parameter, der, wenn er nicht NULL ist, bei der Rückgabe die Metadaten enthält, die bei dem entsprechenden Enqueue-Aufruf übergeben wurden.
pBufferSize [in, out] Die Größe von pBuffer, in Bytes. Gibt die Anzahl der in pBuffer zurückgegebenen Bytes zurück. Wenn der Enqueue-Aufruf keine Metadaten geliefert hat, wird pBuffer auf 0 festgelegt.
dwTimeout [in] Gibt einen Timeout-Wert an. Weitere Einzelheiten finden Sie in den Bemerkungen.

Rückgabewerte

Diese Funktion kann WAIT_TIMEOUT zurückgeben, wenn ein Timeout-Wert angegeben ist und die Funktion nicht vor dem Timeout-Wert zurückgibt. Siehe Hinweise. Wenn keine Oberflächen verfügbar sind, gibt die Funktion ppSurface auf NULL festgelegt, pBufferSize auf 0 festgelegt zurück, und der Rückgabewert ist 0x80070120 (WIN32_TO_HRESULT(WAIT_TIMEOUT)).

Anmerkungen

Diese API kann blockieren, wenn die Warteschlange leer ist. Der Parameter dwTimeout funktioniert identisch mit den Windows-Synchronisierungs-APIs, wie z. B. WaitForSingleObject. Für ein nicht-blockierendes Verhalten verwenden Sie einen Timeout von 0.

ISurfaceProducer

Diese Schnittstelle bietet zwei Methoden, die der App die Möglichkeit bieten, Oberflächen in die Warteschlange zu stellen. Nachdem eine Oberfläche in die Warteschlange gestellt wurde, ist der Oberflächenzeiger nicht mehr gültig und kann nicht mehr sicher verwendet werden. Die einzige Aktion, die die Anwendung mit dem Zeiger durchführen sollte, ist die Freigabe des Zeigers.

Methode Beschreibung
ISurfaceProducer::Enqueue Stellt eine Oberfläche in die Warteschlange des Objekts. Nach Beendigung dieses Aufrufs ist der Producer mit der Oberfläche fertig, und die Oberfläche ist bereit für ein anderes Gerät.
ISurfaceProducer::Flush Wird verwendet, wenn sich die Anwendungen nicht blockierend verhalten sollen. Weitere Informationen finden Sie im Abschnitt Hinweise.

 

Einreihen in die Warteschlange

HRESULT Enqueue(
  [in]  IUnknown *pSurface,
  [in]  void *pBuffer,
  [in]  UINT BufferSize,
  [in]  DWORD Flags
);

Parameter
pSurface [in]
Die Oberfläche des Producer-Gerätes, das in die Warteschlange gestellt werden muss. Diese Oberfläche muss eine Oberfläche aus der Warteschlange des gleichen Warteschlangen-Netzwerks sein. pBuffer [in] Ein optionaler Parameter, der für die Übergabe von Metadaten verwendet wird. Er sollte auf die Daten verweisen, die an den Dequeue-Aufruf weitergegeben werden.
BufferSize [in] Die Größe von pBuffer, in Bytes.
Flags [in] Ein optionaler Parameter, der das Verhalten dieser Funktion steuert. Das einzige Flag ist SURFACE_QUEUE_FLAG_ DO_NOT_WAIT. Siehe die Bemerkungen zu Flush. Wenn kein Flag übergeben wird (Flags == 0), wird das Standard-Blockierverhalten verwendet.

Rückgabewerte

Diese Funktion kann DXGI_ERROR_WAS_STILL_DRAWING zurückgeben, wenn ein SURFACE_QUEUE_FLAG_DO_NOT_WAIT-Flag verwendet wird.

Anmerkungen

  • Diese Funktion stellt die Oberfläche in die Warteschlange. Wenn die Anwendung nicht SURFACE_QUEUE_FLAG_DO_NOT_WAIT angibt, ist diese Funktion blockierend und führt eine GPU-CPU-Synchronisierung durch, um sicherzustellen, dass das Rendering auf der Oberfläche in der Warteschlange abgeschlossen ist. Wenn diese Funktion erfolgreich ist, steht eine Oberfläche für die Dequeue-Funktion zur Verfügung. Wenn Sie ein nicht-blockierendes Verhalten wünschen, verwenden Sie den Flag DO_NOT_WAIT. Siehe Flush() für weitere Einzelheiten.
  • Gemäß den COM-Referenzierungsregeln wird die von Dequeue zurückgegebene Oberfläche vom Typ AddRef() sein, sodass die Anwendung nichts zu tun braucht. Nach dem Aufruf von Enqueue muss die Anwendung die Oberfläche freigeben, da sie sie nicht mehr verwendet.

Leerung

HRESULT Flush(
  [in]  DWORD Flags,
  [out] UINT *nSurfaces
);

Parameter
Flaggen [in]
Das einzige Flag ist SURFACE_QUEUE_FLAG_ DO_NOT_WAIT. Siehe Hinweise. nSurfaces [out] Gibt die Anzahl der Oberflächen zurück, die noch ausstehen und nicht geflusht wurden.

Rückgabewerte

Diese Funktion kann DXGI_ERROR_WAS_STILL_DRAWING zurückgeben, wenn das Flag SURFACE_QUEUE_FLAG_DO_NOT_WAIT verwendet wird. Diese Funktion gibt S_OK zurück, wenn alle Oberflächen erfolgreich geflusht wurden. Diese Funktion gibt DXGI_ERROR_WAS_STILL_DRAWING nur zurück, wenn keine Oberflächen geflusht wurden. Zusammen zeigen der Rückgabewert und nSurfaces der Anwendung an, welche Arbeit bereits erledigt wurde und ob noch etwas zu tun ist.

Anmerkungen

Flush ist nur sinnvoll, wenn der vorherige Aufruf von Enqueue das Flag DO_NOT_WAIT verwendet hat; andernfalls ist es ein No-op. Wenn der Aufruf von Enqueue das Flag DO_NOT_WAIT verwendet hat, kehrt Enqueue sofort zurück und die GPU-CPU-Synchronisierung ist nicht gewährleistet. Die Oberfläche wird weiterhin als in der Warteschlange befindlich betrachtet, das Producer-Gerät kann sie nicht weiter verwenden, aber sie steht nicht für Dequeue zur Verfügung. Um zu versuchen, die Oberfläche für die Warteschlange zu übergeben, muss Flush aufgerufen werden. Flush versucht, alle Oberflächen, die derzeit in der Warteschlange stehen, zu übergeben. Wenn Flush kein Flag übergeben wird, blockiert es und leert die gesamte Warteschlange, sodass alle Oberflächen in der Warteschlange für die Auslagerung bereit sind. Wenn das Flag DO_NOT_WAIT verwendet wird, prüft die Warteschlange die Oberflächen, um zu sehen, ob eine von ihnen bereit ist; dieser Schritt ist nicht blockierend. Oberflächen, die die GPU-CPU-Synchronisierung abgeschlossen haben, sind bereit für das Consumer-Gerät. Oberflächen, die noch ausstehen, sind davon nicht betroffen. Die Funktion gibt die Anzahl der Oberflächen zurück, die noch geflusht werden müssen.

Hinweis

Flush unterbricht nicht die Warteschlangen-Semantik. Die API garantiert, dass Oberflächen, die sich zuerst in der Warteschlange befinden, vor Oberflächen, die sich später in der Warteschlange befinden, übergeben werden, unabhängig davon, wann die GPU-CPU-Synchronisierung stattfindet.

 

Direct3D 9Ex- und DXGI-Interop-Helper: Verwendung

Wir gehen davon aus, dass die meisten Anwendungsfälle zwei Geräte betreffen, die sich eine Reihe von Oberflächen teilen. Da dies auch das einfachste Szenario ist, wird in diesem Dokument detailliert beschrieben, wie Sie die APIs verwenden, um dieses Ziel zu erreichen. Außerdem wird eine nicht-blockierende Variante erörtert, und am Ende wird kurz auf die Initialisierung für drei Geräte eingegangen.

Zwei Geräte

Die Beispielanwendung, die diesen Helper verwendet, kann Direct3D 9Ex und Direct3D 11 zusammen verwenden. Die Anwendung kann Inhalte mit beiden Geräten verarbeiten und Inhalte mit Direct3D 9 darstellen. Verarbeiten könnte bedeuten, Inhalte zu rendern, Videos zu dekodieren, Compute Shader auszuführen und so weiter. Für jeden Frame verarbeitet die Anwendung zunächst mit Direct3D 11, dann mit Direct3D 9 und schließlich mit Direct3D 9. Außerdem werden bei der Verarbeitung mit Direct3D 11 einige Metadaten erzeugt, die die Direct3D 9-Präsentation verarbeiten muss. Dieser Abschnitt behandelt die Verwendung von Helpern in drei Teilen, die dieser Sequenz entsprechen: Initialisierung, Hauptschleife und Bereinigung.

Initialisierung
Die Initialisierung umfasst die folgenden Schritte:

  1. Initialisieren beider Geräte.
  2. Erstellen der Root-Warteschlange: m_11to9Queue.
  3. Klonen der Root-Warteschlange: m_9to11Queue.
  4. Aufrufen von OpenProducer/OpenConsumer in beiden Warteschlangen.

Die Namen der Warteschlangen verwenden die Zahlen 9 und 11, um anzuzeigen, welche API der Producer und welche der Consumer ist: m_ProducerbisConsumerQueue. Dementsprechend zeigt m_11to9Queue eine Warteschlange an, für die das Direct3D 11-Gerät Oberflächen produziert, die das Direct3D 9-Gerät nutzt. Ebenso zeigt m_9to11Queue eine Warteschlange an, für die Direct3D 9 Oberflächen produziert, die Direct3D 11 nutzt.
Die Root-Warteschlange ist anfangs voll, und alle geklonten Warteschlangen sind anfangs leer. Dies sollte für die Anwendung kein Problem darstellen, mit Ausnahme des ersten Zyklus der Enqueues und Dequeues und der Verfügbarkeit von Metadaten. Wenn eine Dequeue nach Metadaten fragt, aber keine festgelegt wurden (entweder weil anfangs nichts vorhanden ist oder die Enqueue nichts festgelegt hat), sieht die Dequeue, dass keine Metadaten empfangen wurden.

  1. Initialisieren beider Geräte.

    m_pD3D9Device = InitializeD3D9ExDevice();
    m_pD3D11Device = InitializeD3D11Device();
    
  2. Erstellen der Root-Warteschlange.
    In diesem Schritt werden auch die Oberflächen erstellt. Die Größen- und Formatbeschränkungen sind identisch mit dem Erstellen jeder gemeinsam genutzten Ressource. Die Größe des Metadatenpuffers wird zum Zeitpunkt des Erstellens festgelegt, und in diesem Fall übergeben wir einfach einen UINT.
    Die Warteschlange muss mit einer festen Anzahl von Oberflächen erstellt werden. Die Leistung hängt vom jeweiligen Szenario ab. Wenn Sie mehrere Oberflächen haben, steigt die Wahrscheinlichkeit, dass die Geräte ausgelastet sind. Wenn es zum Beispiel nur eine Oberfläche gibt, dann findet keine Parallelisierung zwischen den beiden Geräten statt. Andererseits erhöht sich mit der Anzahl der Oberflächen auch der Speicherbedarf, was die Leistung beeinträchtigen kann. In diesem Beispiel werden zwei Oberflächen verwendet.

    SURFACE_QUEUE_DESC Desc;
    Desc.Width        = 640;
    Desc.Height       = 480;
    Desc.Format       = DXGI_FORMAT_R16G16B16A16_FLOAT;
    Desc.NumSurfaces  = 2;
    Desc.MetaDataSize = sizeof(UINT);
    Desc.Flags        = 0;
    
    CreateSurfaceQueue(&Desc, m_pD3D9Device, &m_11to9Queue);
    
  3. Klonen der Root-Warteschlange.
    Jede geklonte Warteschlange muss dieselben Oberflächen verwenden, kann aber unterschiedliche Größen der Metadatenpuffer und unterschiedliche Flags haben. In diesem Fall gibt es keine Metadaten von Direct3D 9 zu Direct3D 11.

    SURFACE_QUEUE_CLONE_DESC Desc;
    Desc.MetaDataSize = 0;
    Desc.Flags        = 0;
    
    m_11to9Queue->Clone(&Desc, &m_9to11Queue);
    
  4. Öffnen der Producer- und Consumer-Geräte.
    Die Anwendung muss diesen Schritt vor dem Aufruf von Enqueue und Dequeue ausführen. Das Öffnen eines Producers und Consumers liefert Schnittstellen, die die Enqueue/Dequeue-APIs enthalten.

    // Open for m_p9to11Queue.
    m_p9to11Queue->OpenProducer(m_pD3D9Device, &m_pD3D9Producer);
    m_p9to11Queue->OpenConsumer(m_pD3D11Device, &m_pD3D11Consumer);
    
    // Open for m_p11to9Queue.
    m_p11to9Queue->OpenProducer(m_pD3D11Device, &m_pD3D11Producer);
    m_p11to9Queue->OpenConsumer(m_pD3D9Device, &m_pD3D9Consumer);
    

Hauptschleife
Die Verwendung der Warteschlange ist dem klassischen Producer/Consumer-Problem nachempfunden. Stellen Sie sich das Ganze aus der Perspektive eines einzelnen Geräts vor. Jedes Gerät muss diese Schritte ausführen: dequeue, um eine Oberfläche aus seiner Consumer-Warteschlange zu erhalten, Verarbeitung mit der Oberfläche und dann enqueue in seiner Producer-Warteschlange. Für das Direct3D 11-Gerät ist die Verwendung von Direct3D 9 fast identisch.

// Direct3D 9 Device.
IDirect3DTexture9* pTexture9 = NULL;
REFIID             surfaceID9 = _uuidof(IDirect3DTexture9);
UINT               metaData;
UINT               metaDataSize;
while (!done)
{
    // Dequeue surface.
    m_pD3D9Consumer->Dequeue(surfaceID9, (void**)&pSurface9,
                             &metaData, &metaDataSize, INFINITE);

    // Process the surface.
    ProcessD3D9(pSurface9);

    // Present the surface using the meta data.
    PresentD3D9(pSurface9, metaData, metaDataSize);

    // Enqueue surface.
    m_pD3D9Producer->Enqueue(pSurface9, NULL, 0, 0);
}

Bereinigung
Dieser Schritt ist sehr einfach. Zusätzlich zu den normalen Schritten zum Bereinigen von Direct3D-APIs muss die Anwendung die zurückgegebenen COM-Schnittstellen freigeben.

m_pD3D9Producer->Release();
m_pD3D9Consumer->Release();
m_pD3D11Producer->Release();
m_pD3D11Consumer->Release();
m_p9to11Queue->Release();
m_p11to9Queue->Release();

Nicht-blockierende Verwendung

Das vorige Beispiel ergibt Sinn für einen Multithreading-Nutzungsfall, bei dem jedes Gerät seinen eigenen Thread hat. Das Beispiel verwendet die blockierenden Versionen der APIs: INFINITE für Timeout und kein Flag für Enqueue. Wenn Sie den Helper nicht blockierend verwenden möchten, müssen Sie nur wenige Änderungen vornehmen. Dieser Abschnitt zeigt die nicht-blockierende Verwendung mit beiden Geräten in einem Thread.

Initialisierung
Die Initialisierung ist bis auf die Flags identisch. Da es sich um eine Single-Thread-Anwendung handelt, verwenden Sie dieses Flag für die Erstellung. Dadurch wird ein Teil des Codes für die Synchronisierung ausgeschaltet, was die Leistung verbessern kann.

SURFACE_QUEUE_DESC Desc;
Desc.Width        = 640;
Desc.Height       = 480;
Desc.Format       = DXGI_FORMAT_R16G16B16A16_FLOAT;
Desc.NumSurfaces  = 2;
Desc.MetaDataSize = sizeof(UINT);
Desc.Flags        = SURFACE_QUEUE_FLAG_SINGLE_THREADED;

CreateSurfaceQueue(&Desc, m_pD3D9Device, &m_11to9Queue);
SURFACE_QUEUE_CLONE_DESC Desc;
Desc.MetaDataSize = 0;
Desc.Flags        = SURFACE_QUEUE_FLAG_SINGLE_THREADED;

m_11to9Queue->Clone(&Desc, &m_9to11Queue);

Das Öffnen der Producer- und Consumer-Geräte erfolgt auf die gleiche Weise wie im Blocking-Beispiel.
Verwendung der Warteschlange
Es gibt viele Möglichkeiten, die Warteschlange auf nicht-blockierende Weise mit unterschiedlichen Leistungsmerkmalen zu verwenden. Das folgende Beispiel ist einfach, hat aber aufgrund von übermäßigem Spinning und Polling eine schlechte Leistung. Trotz dieser Probleme zeigt das Beispiel, wie Sie den Helper verwenden können. Der Ansatz beruht auf einer ständigen Schleife, in der dequeue, process, enqueue und flush ausgeführt werden. Wenn einer der Schritte fehlschlägt, weil die Ressource nicht verfügbar ist, versucht die Anwendung einfach den nächsten Schleifendurchlauf.

// Direct3D 11 Device.
ID3D11Texture2D* pSurface11 = NULL;
REFIID           surfaceID11 = __uuidof(ID3D11Texture2D);
UINT             metaData;
while (!done)
{
    //
    // D3D11 Portion.
    //

    // Dequeue surface.
    hr = m_pD3D11Consumer->Dequeue(surfaceID11,
                                   (void**)&pSurface11,
                                   NULL, 0, 0);
    // Only continue if we got a surface.
    if (SUCCEEDED(hr))
    {
        // Process the surface and return some meta data.
        ProcessD3D11(pSurface11, &metaData);

        // Enqueue surface.
        m_pD3D11Producer->Enqueue(pSurface11, &metaData,
                                  sizeof(UINT),
                                  SURFACE_QUEUE_FLAG_DO_NOT_WAIT);
    }
    // Flush the queue to check if any surfaces completed.
    m_pD3D11Producer->Flush(NULL,SURFACE_QUEUE_FLAG_DO_NOT_WAIT);

    //
    // Do the same with the Direct3D 9 Device.
    //

    // Dequeue surface.
    hr = m_pD3D9Consumer->Dequeue(surfaceID9,
                                  (void**)&pSurface9,
                                  &metaData,
                                  &metaDataSize, 0);
    // Only continue if we got a surface.
    if (SUCCEEDED(hr)))
    {
        // Process the surface.
        ProcessD3D9(pSurface9);

        // Present the surface using the meta data.
        PresentD3D9(pSurface9, metaData, metaDataSize);

        // Enqueue surface.
        m_pD3D9Producer->Enqueue(pSurface9, NULL, 0,
                                 SURFACE_QUEUE_FLAG_DO_NOT_WAIT);
    }
    // Flush the queue to check if any surfaces completed.
    m_pD3D9Producer->Flush(NULL,SURFACE_QUEUE_FLAG_DO_NOT_WAIT);
}

Eine komplexere Lösung könnte den Rückgabewert von enqueue und flush überprüfen, um festzustellen, ob ein Flushing erforderlich ist.

Drei Geräte

Die Erweiterung der vorherigen Beispiele auf mehrere Geräte ist ganz einfach. Der folgende Code führt die Initialisierung durch. Nachdem die Producer/Consumer-Objekte erstellt wurden, ist der Code für ihre Verwendung derselbe. Dieses Beispiel hat drei Geräte und damit drei Warteschlangen. Die Oberflächen fließen von Direct3D 9 zu Direct3D 10 zu Direct3D 11.

SURFACE_QUEUE_DESC Desc;
Desc.Width        = 640;
Desc.Height       = 480;
Desc.Format       = DXGI_FORMAT_R16G16B16A16_FLOAT;
Desc.NumSurfaces  = 2;
Desc.MetaDataSize = sizeof(UINT);
Desc.Flags        = 0;

SURFACE_QUEUE_CLONE_DESC Desc;
Desc.MetaDataSize = 0;
Desc.Flags        = 0;

CreateSurfaceQueue(&Desc, m_pD3D9Device, &m_11to9Queue);
m_11to9Queue->Clone(&Desc, &m_9to10Queue);
m_11to9Queue->Clone(&Desc, &m_10to11Queue);

Wie bereits erwähnt, funktioniert das Klonen auf die gleiche Weise, unabhängig davon, welche Warteschlange geklont wird. Der zweite Clone-Aufruf könnte zum Beispiel von dem Objekt m_9to10Queue stammen.

// Open for m_p9to10Queue.
m_p9to10Queue->OpenProducer(m_pD3D9Device, &m_pD3D9Producer);
m_p9to10Queue->OpenConsumer(m_pD3D10Device, &m_pD3D10Consumer);

// Open for m_p10to11Queue.
m_p10to11Queue->OpenProducer(m_pD3D10Device, &m_pD3D10Producer);
m_p10to11Queue->OpenConsumer(m_pD3D11Device, &m_pD3D11Consumer);

// Open for m_p11to9Queue.
m_p11to9Queue->OpenProducer(m_pD3D11Device, &m_pD3D11Producer);
m_p11to9Queue->OpenConsumer(m_pD3D9Device, &m_pD3D9Consumer);

Zusammenfassung

Sie können Lösungen erstellen, die die Interoperabilität nutzen, um die Leistungsfähigkeit mehrerer DirectX-APIs einzusetzen. Die Windows-Grafik-API-Interoperabilität bietet jetzt eine allgemeine Runtime für die Oberflächenverwaltung DXGI 1.1. Diese Runtime ermöglicht die Unterstützung der synchronisierten gemeinsamen Nutzung von Oberflächen innerhalb neu entwickelter APIs, wie Direct3D 11, Direct3D 10.1 und Direct2D. Verbesserungen der Interoperabilität zwischen neuen APIs und bestehenden APIs erleichtern die Migration von Anwendungen und die Abwärtskompatibilität. Direct3D 9Ex- und DXGI 1.1-Consumer-APIs können interoperieren, wie mit dem Synchronisierungsmechanismus in älteren Win32-Beispiel-Apps dargestellt, die im archivierten MSDN Code Gallery Microsoft Samples-Repository enthalten sind.