Abfragen (Direct3D 9)

Es gibt mehrere Arten von Abfragen, die zum Abfragen des Status von Ressourcen entwickelt wurden. Der Status einer bestimmten Ressource umfasst den GPU-Status (Graphics Processing Unit), den Treiberstatus oder den Laufzeitstatus. Um den Unterschied zwischen den verschiedenen Abfragetypen zu verstehen, müssen Sie die Abfragezustände verstehen. Im folgenden Zustandsübergangsdiagramm werden die einzelnen Abfragezustände erläutert.

Diagramm mit Übergängen zwischen Abfragezuständen

Das Diagramm zeigt drei Zustände, die jeweils durch Kreise definiert sind. Jede der soliden Linien sind anwendungsgesteuerte Ereignisse, die einen Zustandsübergang verursachen. Die gestrichelte Linie ist ein ressourcengesteuertes Ereignis, bei dem eine Abfrage vom zustandsgesteuerten Zustand in den signalisierten Zustand wechselt. Jeder dieser Zustände hat einen anderen Zweck:

  • Der signalisierte Zustand ist wie ein Leerlaufzustand. Das Abfrageobjekt wurde generiert und wartet darauf, dass die Anwendung die Abfrage aus gibt. Nachdem eine Abfrage abgeschlossen und wieder in den signalisierten Zustand überwechselt wurde, kann die Antwort auf die Abfrage abgerufen werden.
  • Der Gebäudezustand ist wie ein Stagingbereich für eine Abfrage. Vom Gebäudezustand aus wurde eine Abfrage ausgegeben (durch Aufrufen von D3DISSUE _ BEGIN),aber noch nicht in den Zustand ausgestellt. Wenn eine Anwendung ein Abfrageende aus gibt (durch Aufrufen von D3DISSUE _ END),geht die Abfrage in den Zustand ausgestellt über.
  • Der Status Ausgestellt bedeutet, dass die abgefragte Ressource die Kontrolle über die Abfrage hat. Sobald die Ressource ihre Arbeit abgeschlossen hat, geht die Ressource den Zustandscomputer in den signalisierten Zustand über. Während des Status "Ausgestellt" muss die Anwendung eine Abfrage senden, um den Übergang in den signalisierten Zustand zu erkennen. Sobald der Übergang in den signalisierten Zustand erfolgt ist, gibt GetData das Abfrageergebnis (über ein Argument) an die Anwendung zurück.

In der folgenden Tabelle sind die verfügbaren Abfragetypen aufgeführt.

Abfragetyp Problemereignis GetData-Puffer Runtime Impliziter Anfang der Abfrage
BANDWIDTHTIMINGS D3DISSUE _ BEGIN, D3DISSUE _ END D3DDEVINFO _ D3D9BANDWIDTHTIMINGS Einzelhandel/Debuggen Nicht zutreffend
CACHEUTILIZATION D3DISSUE _ BEGIN, D3DISSUE _ END D3DDEVINFO _ D3D9CACHEUTILIZATION Einzelhandel/Debuggen Nicht zutreffend
EREIGNIS D3DISSUE _ END BOOL Einzelhandel/Debuggen CreateDevice
INTERFACETIMINGS D3DISSUE _ BEGIN, D3DISSUE _ END D3DDEVINFO _ D3D9INTERFACETIMINGS Einzelhandel/Debuggen Nicht zutreffend
Okklusion D3DISSUE _ BEGIN, D3DISSUE _ END DWORD Einzelhandel/Debuggen Nicht zutreffend
PIPELINETIMINGS D3DISSUE _ BEGIN, D3DISSUE _ END D3DDEVINFO _ D3D9PIPELINETIMINGS Einzelhandel/Debuggen Nicht zutreffend
Resourcemanager D3DISSUE _ END D3DDEVINFO _ ResourceManager Nur Debuggen Present
timestamp D3DISSUE _ END UINT64 Einzelhandel/Debuggen Nicht zutreffend
TIMESTAMPDISJOINT D3DISSUE _ BEGIN, D3DISSUE _ END BOOL Einzelhandel/Debuggen Nicht zutreffend
TIMESTAMPFREQ D3DISSUE _ END UINT64 Einzelhandel/Debuggen Nicht zutreffend
Vcache D3DISSUE _ END D3DDEVINFO _ VCACHE Einzelhandel/Debuggen CreateDevice
VERTEXSTATS D3DISSUE _ END D3DDEVINFO _ D3DVERTEXSTATS Nur Debuggen Present
VERTEXTIMINGS D3DISSUE _ BEGIN, D3DISSUE _ END D3DDEVINFO _ D3D9STAGETIMINGS Einzelhandel/Debuggen Nicht zutreffend

Einige abfragen erfordern ein Begin- und Endereignis, während andere nur ein Endereignis erfordern. Die Abfragen, die nur ein Endereignis erfordern, beginnen, wenn ein anderes implizites Ereignis auftritt (das in der Tabelle aufgeführt ist). Alle Abfragen geben eine Antwort zurück, mit Ausnahme der Ereignisabfrage, deren Antwort immer TRUE ist. Eine Anwendung verwendet entweder den Status der Abfrage oder den Rückgabecode von GetData.

Erstellen einer Abfrage

Bevor Sie eine Abfrage erstellen, können Sie überprüfen, ob die Laufzeit Abfragen unterstützt, indem Sie CreateQuery mit einem NULL-Zeiger wie dem folgenden aufrufen:

IDirect3DQuery9* pEventQuery;

// Create a device pointer m_pd3dDevice

// Create a query object
HRESULT hr = m_pd3dDevice->CreateQuery(D3DQUERYTYPE_EVENT, NULL);

Diese Methode gibt einen Erfolgscode zurück, wenn eine Abfrage erstellt werden kann. Andernfalls wird ein Fehlercode zurückgegeben. Sobald CreateQuery erfolgreich ist, können Sie ein Abfrageobjekt wie folgt erstellen:

IDirect3DQuery9* pEventQuery;
m_pd3dDevice->CreateQuery(D3DQUERYTYPE_EVENT, &pEventQuery);

Wenn dieser Aufruf erfolgreich ist, wird ein Abfrageobjekt erstellt. Die Abfrage befindet sich im Wesentlichen im Leerlauf im signalisierten Zustand (mit einer nicht initialisierten Antwort), die auf die Ausgabe wartet. Wenn Sie die Abfrage abgeschlossen haben, geben Sie sie wie jede andere Schnittstelle frei.

Ausstellen einer Abfrage

Eine Anwendung ändert einen Abfragezustand, indem sie eine Abfrage ausgibt. Hier sehen Sie ein Beispiel für die Ausgabe einer Abfrage:

IDirect3DQuery9* pEventQuery;
m_pD3DDevice->CreateQuery(D3DQUERYTYPE_EVENT, &pEventQuery);

// Issue a Begin event
pEventQuery->Issue(D3DISSUE_BEGIN);

or

// Issue an End event
pEventQuery->Issue(D3DISSUE_END);

Eine Abfrage im signalisierten Zustand geht wie folgt über, wenn sie ausgegeben wird:

Problemtyp Abfrageübergänge zu . . .
D3DISSUE _ BEGIN Erstellungsstatus.
D3DISSUE _ END Ausgestellter Zustand.

Eine Abfrage im Gebäudezustand geht wie folgt über, wenn sie ausgegeben wird:

Problemtyp Abfrageübergänge zu . . .
D3DISSUE _ BEGIN (Kein Übergang, verbleibt im Gebäudezustand. Startet die Abfrageklammer neu.)
D3DISSUE _ END Ausgestellter Zustand.

Eine Abfrage im Zustand "Ausgestellt" geht wie folgt über, wenn sie ausgegeben wird:

Problemtyp Abfrageübergänge zu . . .
D3DISSUE _ BEGIN Erstellen des Zustands und Neustarten der Abfrageklammer.
D3DISSUE _ END Ausgestellter Zustand nach dem Abbrechen der vorhandenen Abfrage.

Überprüfen des Abfragestatus und Abrufen der Antwort auf die Abfrage

GetData führt zwei Dinge aus:

  1. Gibt den Abfragezustand im Rückgabecode zurück.
  2. Gibt die Antwort auf die Abfrage in pData zurück.

Aus jedem der drei Abfragezustände finden Sie hier die GetData-Rückgabecodes:

Abfragestatus GetData-Rückgabecode
Signalisiert S _ OK
Erstellen Fehlercode
Ausgestellt S _ FALSE

Wenn sich beispielsweise eine Abfrage im Zustand "Ausgestellt" befindet und die Antwort auf die Abfrage nicht verfügbar ist, gibt GetData S _ FALSE zurück. Wenn die Ressource ihre Arbeit beendet und die Anwendung ein Abfrageende ausgegeben hat, geht die Ressource in den signalisierten Zustand über. Vom signalisierten Zustand gibt GetData S _ OK zurück, was bedeutet, dass die Antwort auf die Abfrage auch in pData zurückgegeben wird. Hier ist beispielsweise die Sequenz von Ereignissen angegeben, um die Anzahl der in einer Rendersequenz gezeichneten Pixel zurückzugeben:

  • Erstellen der Abfrage
  • Ein Startereignis ausstellen.
  • Zeichnen Sie etwas.
  • Ein Endereignis ausstellen.

Im Folgenden ist die entsprechende Codesequenz angegeben:

IDirect3DQuery9* pOcclusionQuery;
DWORD numberOfPixelsDrawn;

m_pD3DDevice->CreateQuery(D3DQUERYTYPE_OCCLUSION, &pOcclusionQuery);

// Add an end marker to the command buffer queue.
pOcclusionQuery->Issue(D3DISSUE_BEGIN);

// API render loop
...
Draw(...)
...

// Add an end marker to the command buffer queue.
pOcclusionQuery->Issue(D3DISSUE_END);

// Force the driver to execute the commands from the command buffer.
// Empty the command buffer and wait until the GPU is idle.
while(S_FALSE == pOcclusionQuery->GetData( &numberOfPixelsDrawn, 
                                  sizeof(DWORD), D3DGETDATA_FLUSH ))
    ;

Diese Codezeilen erledigen mehrere Dinge:

  • Rufen Sie GetData auf, um die Anzahl der gezeichneten Pixel zurückzugeben.
  • Geben Sie D3DGETDATA _ FLUSH an, damit die Ressource die Abfrage in den signalisierten Zustand übergehen kann.
  • Fragen Sie die Abfrageressource ab, indem Sie GetData aus einer Schleife aufrufen. Solange GetData S _ FALSE zurückgibt, bedeutet dies, dass die Ressource die Antwort noch nicht zurückgegeben hat.

Der Rückgabewert von GetData teilt Ihnen im Wesentlichen mit, in welchem Zustand sich die Abfrage befindet. Mögliche Werte sind S _ OK, S _ FALSE und ein Fehler. Rufen Sie GetData nicht für eine Abfrage auf, die sich im Erstellungszustand befindet.

  • S _ OK bedeutet, dass die Ressource (GPU, Treiber oder Runtime) abgeschlossen ist. Die Abfrage kehrt in den signalisierten Zustand zurück. Die Antwort (sofern vorhanden) wird von GetDatazurückgegeben.
  • S _ FALSE bedeutet, dass die Ressource (GPU, Treiber oder Laufzeit) noch keine Antwort zurückgeben kann. Dies kann daran liegt, dass die GPU nicht abgeschlossen ist oder die Arbeit noch nicht angezeigt wurde.
  • Ein Fehler bedeutet, dass die Abfrage einen Fehler generiert hat, von dem sie nicht wiederhergestellt werden kann. Dies kann der Fall sein, wenn das Gerät während einer Abfrage verloren geht. Sobald eine Abfrage einen Fehler generiert hat (mit Ausnahme von S _ FALSE), muss die Abfrage neu erstellt werden, wodurch die Abfragesequenz aus dem signalisierten Zustand neu gestartet wird.

Anstatt D3DGETDATA _ FLUSHanzugeben, die aktuellere Informationen bereitstellt, können Sie 0 (null) angeben. Dies ist eine einfachere Überprüfung, wenn sich die Abfrage im ausgegebenen Zustand befindet. Die Bereitstellung von 0 führt dazu, dass GetData den Befehlspuffer nicht leert. Aus diesem Grund muss darauf geachtet werden, dass keine schleifendefiniert sind (weitere Informationen finden Sie unter GetData). Da die Laufzeit Arbeit im Befehlspuffer in die Warteschlange einreiht, ist D3DGETDATA _ FLUSH ein Mechanismus zum Leeren des Befehlspuffers an den Treiber (und somit die GPU; siehe Genaue Profilerstellung für Direct3D-API-Aufrufe (Direct3D 9)). Während der Befehlspufferlöschung kann eine Abfrage in den signalisierten Zustand übergehen.

Beispiel: Eine Ereignisabfrage

Eine Ereignisabfrage unterstützt kein Startereignis.

  • Erstellen der Abfrage
  • Ein Endereignis ausstellen.
  • Fragt ab, bis sich die GPU im Leerlauf befindet.
  • Ein Endereignis ausstellen.
IDirect3DQuery9* pEventQuery = NULL;
m_pD3DDevice->CreateQuery(D3DQUERYTYPE_EVENT, &pEventQuery);

// Add an end marker to the command buffer queue.
pEventQuery->Issue(D3DISSUE_END);

// Empty the command buffer and wait until the GPU is idle.
while(S_FALSE == pEventQuery->GetData( NULL, 0, D3DGETDATA_FLUSH ))
    ;

... // API calls

// Add an end marker to the command buffer queue.
pEventQuery->Issue(D3DISSUE_END);

// Force the driver to execute the commands from the command buffer.
// Empty the command buffer and wait until the GPU is idle.
while(S_FALSE == pEventQuery->GetData( NULL, 0, D3DGETDATA_FLUSH ))
    ;

Dies ist die Sequenz von Befehlen, die von einer Ereignisabfrage zum Profilieren von API-Aufrufen (Application Programming Interface) verwendet werden (siehe Genaue Profilerstellung für Direct3D-API-Aufrufe (Direct3D 9)). Diese Sequenz verwendet Marker, um die Arbeitsmenge im Befehlspuffer zu steuern.

Beachten Sie, dass Anwendungen besonders auf die hohen Kosten achten sollten, die mit dem Leeren des Befehlspuffers verbunden sind, da dies dazu führt, dass das Betriebssystem in den Kernelmodus wechselt, was zu erheblichen Leistungseinbußen führt. Anwendungen sollten auch die Auslastung von CPU-Zyklen beachten, indem sie auf den Abschluss von Abfragen warten.

Abfragen sind eine Optimierung, die während des Renderings verwendet werden soll, um die Leistung zu erhöhen. Daher ist es nicht vorteilhaft, auf den Abschluss einer Abfrage zu warten. Wenn eine Abfrage ausgegeben wird und die Ergebnisse zum Zeitpunkt der Anwendungsüberprüfung noch nicht bereit sind, war der Optimierungsversuch nicht erfolgreich, und das Rendering sollte normal fortgesetzt werden.

Das klassische Beispiel hierfür ist occlusion Culling. Anstelle der obigen while-Schleife kann eine Anwendung, die Abfragen verwendet, Okklusions-Culling implementieren, um zu überprüfen, ob eine Abfrage abgeschlossen war, bis sie das Ergebnis benötigt. Wenn die Abfrage nicht abgeschlossen ist, fahren Sie (im schlimmsten Fall) so fort, als ob das objekt, für das getestet wird, nicht verdeckt (d. h. es ist sichtbar) ist, und rendern Sie es. Der Code sieht in etwa wie folgt aus.

IDirect3DQuery9* pOcclusionQuery = NULL;
m_pD3DDevice->CreateQuery( D3DQUERYTYPE_OCCLUSION, &pOcclusionQuery );

// Add a begin marker to the command buffer queue.
pOcclusionQuery->Issue( D3DISSUE_BEGIN );

... // API calls

// Add an end marker to the command buffer queue.
pOcclusionQuery->Issue( D3DISSUE_END );

// Avoid flushing and letting the CPU go idle by not using a while loop.
// Check if queries are finished:
DWORD dwOccluded = 0;
if( S_FALSE == pOcclusionQuery->GetData( &dwOccluded, sizeof(DWORD), 0 ) )
{
    // Query is not done yet or object not occluded; avoid flushing/wait by continuing with worst-case scenario
    pSomeComplexMesh->Render();
}
else if( dwOccluded != 0 )
{
    // Query is done and object is not occluded.
    pSomeComplexMesh->Render();
}

Weiterführende Themen