Erzeugung und Verwendung von Vertex Buffern mit DirectX 11
| Tutorial | |
|---|---|
| Vertex Buffer Einführung | |
| Autor | Roland "Glatzemann" Rosenkranz |
| Programmiersprache | C++ |
| Kategorie | DirectX 11 |
| Diskussion | Thread im Forum |
| Lizenz | indiedev article license |
Dieser Artikel soll einen Überblick zur Verwendung von Vertex Buffern mit DirectX 11 bieten. Dabei möchte ich nicht auf alle Aspekte und Details eingehen und den Artikel nicht in die Länge ziehen. Viel mehr möchte ich dem Leser die Grundlagen vermitteln, die es ermöglichen ein tieferes Verständnis aufzubauen.
Zunächst sollte ich vielleicht erklären, was ein Vertex Buffer ist und wozu dieser verwendet wird. Ein Vertex Buffer (der im verlinkten Glossar etwas ausführlicher definiert wird) ist ein Bereich im Speicher der Grafikkarte der Vertex-Daten und Zusatzdaten enthält, die zur Darstellung von Polygonen verwendet werden. Dabei bestehen sowohl 2D- als auch 3D-Objekte aus Polygonen.
Inhaltsverzeichnis |
Vorbereitungen
Zunächst sollten wir uns ein grundlegendes Programm schaffen, dass ein Fenster öffnet und DirectX initialisiert. Auf dieses Beispielprogramm können wir dann im weiteren Verlauf aufbauen um einen Vertex-Buffer anzulegen, diesen mit Daten zu befüllen und anschliessend zu rendern. Wie dies genau geht würde den Umfang dieses Grundlagen-Tutorials deutlich sprengen. Ich möchte daher an dieser Stelle auf ein anderes Tutorial von dieser Website verweisen, in dem dies sehr ausführlich erklärt wird. Es handelt sich dabei um die Tutorial-Reihe DirectX 11 Jumpstart. Dort sollten die ersten vier Teile durchgearbeitet werden:
- Die Konfiguration der IDE
- Windows-Programme für Einsteiger
- Ein Fenster für DirectX
- DirectX initialisieren
Sobald dies geschehen ist, haben wir bei Programmstart ein Fenster, ein initialisiertes DirectX, sowie ein leeres, blaues Bild.
Dieser Aufbau ermöglicht es uns alle Änderungen in den abgeleiteten View-Klassen vorzunehmen. In meinem Beispielprojekt habe ich diese beiden Dateien wie folgt genannt:
- VertexBufferTutorial01View.h
- VertexBufferTutorial01View.cpp
Ich werde mich im weiteren Verlauf dieses Tutorials auf diese Namen beziehen. Solltet ihr andere verwenden wollen, dann muss das natürlich entsprechend berücksichtigt werden.
Übersicht
Grundsätzlich sollte folgender Aufbau zur Verwendung eines Vertex Buffer eingehalten werden:
- Erzeugung eines Datentyp der den Inhalt des Vertex Buffer beschreibt
- Erzeugung einer Input Element Description
- Erzeugung des Vertex Buffer
- Füllen des Vertex Buffer mit Daten
Diese Schritte sollten einmalig beim Programm- oder Levelstart ausgeführt werden. Diese Aktionen kosten selbstverständlich Zeit und wenn sich die Daten nicht ständig ändern, sollte der Buffer auch nicht ständig neu aufgebaut werden. Solche "statischen Geometrien" sind aus Performancesicht verhältnismäßig günstig.
Die weiteren Schritte sind folgende:
- Auswahl eines Vertex Shader
- Auswahl eines oder mehrerer Vertex Buffer
- Befehl zur Darstellung des Vertex Buffer
Der Vertex Shader ist dabei eine kleine Besonderheit. Er stellt ein Programm dar, dass auf jeden einzelnen Vertex im Vertex Buffer ausgeführt wird. Man kann sich das so vorstellen, dass der Vertex aus dem Buffer als Parameter an eine Funktion (der eigentliche Vertex Shader) übergeben wird. Der Rückgabewert ist wieder ein Vertex. Innerhalb dieses Programm kann der Vertex nun verändert werden. So wird es möglich, dass mit einem statischen Buffer dynamische Inhalte angezeigt werden. Bei Animationen wird ein derartiges Verfahren übrigens angewendet.
Deklaration
Wie im allgemeinen Grundlagen-Artikel über Vertex Buffer bereits erwähnt handelt es sich bei einem Vertex Buffer um eine einfache Byte-Folge im Grafikkartenspeicher. Diese Daten sind nicht weiter strukturiert und daher müssen wir der Grafikkarte mitteilen, wie diese Daten zu interpretieren sind. Dies erfolgt in zwei Schritten.
Zunächst erzeugen wir einen eigenen Datentyp, der es uns bequem ermöglicht auf die Daten unserer Vertices zuzugreifen. Dieser Datentyp dient später auch als Basis zum kopieren der Vertex-Daten aus dem Artbeitsspeicher in den Grafikkartenspeicher. Wir erzeugen also eine Struktur (im Header VertexBufferTutorial01View.h).
struct VERTEX
{
FLOAT X, Y, Z; // position
D3DXCOLOR Color; // color
};
Wie die beiden Kommentare zeigen, handelt es sich dabei um eine Positionsangabe, die aus drei float besteht, jeweils für die X-, Y- und Z-Achse. Das zweite Element ist eine Farbangabe, die es uns ermöglicht einzelne Vertices einzufärben.
Der zweite Schritt ist die Erzeugung einer Input Element Description. Dies erfolgt in der Initialize-Methode der Source-Datei VertexBufferTutorial01View.cpp.
D3D11_INPUT_ELEMENT_DESC ied[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
Wir haben damit zwei Elemente erzeugt. Auf die einzelnen Parameter möchte ich nun genauer eingehen.
Der erste Parameter ist die sogenannte Input Element Semantic. Dieser Parameter beschreibt gemeinsam mit dem zweiten Parameter, dem Usage Index, wie der jeweilige Bestandteil des Vertex interpretiert werden soll und um das wievielte Element dieses Typs es sich dabei handelt. Die genaue Auflistung aller Semantics findet ihr hier. Es ist darauf zu achten, dass ihr die dort beschriebenen [n]-Werte nicht explizit im Namen angeben müsst, sondern diese über den Usage Index festgelegt werden.
Die wichtigsten Semantics sind dabei sicherlich:
- Position
- Color
- TexCoord
Der Sinn dabei ist größtenteils selbsterklärend. Position dient der Beschreibung der Position eines Vertex, Color ist dessen Farbe und TexCoord beschreibt, wie eine Textur zu diesem Vertex zugeordnet werden soll.
Der nächste Parameter ist das sogenannte Format. Dieser Parameter beschreibt den Datentyp des Vertex-Buffer-Elements. Nähere Informationen zu möglichen Werten findet ihr hier.
Der Parameter InputSlot, welcher an vierter Stelle steht, dient der Zuordnung des Elements zu einem Input Assembler. Dieser Parameter hat einen Wertebereich von 0 bis 15.
Der nächste, wichtige Parameter ist der AlignedByteOffset. Dieser Parameter gibt an, ab welcher Byte-Position das eigentliche Vertex-Element im Vertex-Buffer beginnt. Dieser Parameter ist dabei relativ zum Beginn des Eintrages im Vertex-Buffer. Das erste Element beginnt also immer bei 0 und das darauf folgende Element beginnt bei 0 plus Größe des ersten Elements, dass dritte Element beginnt bei 0 plus Größe des ersten Elements plus Größe des zweiten Elements usw. Wichtig ist, dass wir dabei mit Byte-Größen arbeiten müssen. Im Grunde genommen handelt es sich bei diesem Parameter um die Summe aller Größen der vorhergehenden Formate. Zur Vereinfachung können wir für diesen Parameter auch einfach D3D11_APPEND_ALIGNED_ELEMENT angeben. Dies führt dazu, dass der Offset automatisch berechnet wird.
Der Parameter InputSlotClass ist unter D3D11_INPUT_CLASSIFICATION genauer beschrieben. Dieser wird gemeinsam mit dem letzten Parameter, der InstanceDataStepRate zum Beispiel für Instancing verwendet.
Erzeugung
Die eigentliche Erzeugung der Vertex-Buffer Instanz ist kein großes Problem. Dazu deklarieren wir im Header-File erstmal eine private Member-Variable die den Zeiger auf unseren Vertex-Buffer speichert.
ID3D11Buffer *pVBuffer;
Danach legen wir in der Initialize-Methode den Vertex-Buffer an.
D3D11_BUFFER_DESC bd; ZeroMemory(&bd, sizeof(bd)); bd.Usage = D3D11_USAGE_DYNAMIC; bd.ByteWidth = sizeof(VERTEX) * 3; bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; device->CreateBuffer(&bd, NULL, &pVBuffer);
Wie in DirectX üblich, müssen wir zunächst eine Description-Struktur aufbauen, die unseren Vertex-Buffer beschreibt. Dazu verwenden wir den Typ D3D11_BUFFER_DESC, der erstmal leer initialisiert wird (zur Erinnerung: Speicherbereiche für Strukturen werden von C++ nicht automatisch gelöscht bei der Speicheranforderung).
In Zeile 4 legen wir fest, dass unser Vertex-Buffer von der GPU gelesen werden soll und von der CPU beschrieben werden soll. Die möglichen Werte sind hier beschrieben. Wir verwenden DYNAMIC obwohl dies nicht die günstigste Wahl ist. Dynamic wird verwendet, wenn der Vertex-Buffer häufiger von der CPU mit Daten befüllt werden soll, zum Beispiel in jedem Frame. Standard zur einmaligen Befüllung wäre DEFAULT. Das Füllen eines solchen Vertex-Buffer funktioniert jedoch ein wenig komplizierter, daher begnügen wir uns zunächst mit dem dynamischen Buffer.
In Zeile 5 legen wir die Größe des Vertex-Buffer in Byte fest. Dazu müssen wir die Größe eines unserer Vertices in Byte mit deren Anzahl multiplizieren.
Die BindFlags in Zeile 6 legen fest, dass wir einen Vertex-Buffer erzeugen wollen. Mögliche andere Werte finden sich hier.
Der letzte Parameter in Zeile 7 gibt an, dass wir mit der CPU schreibend auf den Buffer zugreifen wollen.
Die eigentliche Erzeung des Buffers erfolgt dann in Zeile 9. Dort wird eine Referenz auf unsere Buffer-Beschreibung übergeben und als letzter Parameter wird festgelegt, wo der Zeiger auf den gerade erzeugten Buffer abgelegt werden soll. Den zweiten Parameter verwenden wir an dieser Stelle nicht. Wir könnten hiermit direkt Daten in den Buffer kopieren lassen. Dazu müssten wir aber einen Staging-Buffer anlegen. Dies wollen wir aber für den Moment noch nicht.
Füllen
Nachdem wir nun schon einiges an Vorarbeit geleistet haben möchten wir endlich auch mal Daten in den Vertex Buffer packen, denn das ist ja einer der Hauptgründe, warum wir diesen Aufwand betreiben. Glücklicherweise ist auch dies sehr einfach, da wir ja bereits hervorragende Vorarbeiten geleistet haben.
Erstmal benötigen wir Daten. Dazu erzeugen wir mit unserem eigenen Datentyp, den wir ja definiert haben, ein Array und setzen die Daten so wie wir sie haben wollen. Für dieses Tutorial machen wir das wie folgt:
VERTEX OurVertices[] =
{
{ 0.0f, -0.5f, 0.0f, D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f)},
{-0.5f, 0.5f, 0.0f, D3DXCOLOR(0.0f, 1.0f, 0.0f, 1.0f)},
{ 0.5f, 0.5f, 0.0f, D3DXCOLOR(0.0f, 0.0f, 1.0f, 1.0f)},
};
Wichtig ist, dass wir zunächst möglichst die gesamte Liste aller Vertices aufbauen. Dies benötigt zwar Arbeitsspeicher, führt aber dazu, dass wir diese im nächsten Schritt in einem Rutsch an die Grafikkarte übertragen können. Ausserdem haben wir damit einen weiteren Vorteil: Wir müssen, wenn wir diese Daten abändern wollen, nicht auf den Grafikkartenspeicher zugreifen. Dazu später aber noch mehr im Abschnitt über das Lesen von Daten aus dem Vertex-Buffer.
Das schreiben der Daten müssen wir selbst ausführen. Dazu mappen wir zunächst den Speicherbereich auf der Grafikkarte, schieben die Daten mit memcpy dorthing und geben das Mapping danach wieder frei:
D3D11_MAPPED_SUBRESOURCE ms; deviceContext->Map(pVBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms); memcpy(ms.pData, OurVertices, sizeof(OurVertices)); deviceContext->Unmap(pVBuffer, NULL);
Verwendung
Wie das eigentliche Rendern funktioniert möchte ich an dieser Stelle nicht erklären, da dies den Rahmen dieses Tutorials deutlich sprengen würde. Ich möchte natürlich trotzdem ein sichtbares Ergebnis vorweisen. Wie sowas genau funktioniert wird unter anderem im Tutorial DirectX 11 Jumpstart erklärt. Wir kopieren dazu den folgenden Code in die Initialize-Methode unseres Views.
ID3D10Blob *VS, *PS; ID3D10Blob* l_pBlob_Errors = NULL; LPVOID l_pError = NULL; D3DX11CompileFromFile(L"shaders.hlsl", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, &l_pBlob_Errors, 0); D3DX11CompileFromFile(L"shaders.hlsl", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, &l_pBlob_Errors, 0); device->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS); device->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS); deviceContext->VSSetShader(pVS, 0, 0); deviceContext->PSSetShader(pPS, 0, 0); device->CreateInputLayout(ied, 2, VS->GetBufferPointer(), VS->GetBufferSize(), &pLayout); deviceContext->IASetInputLayout(pLayout);
Wir laden und kompilieren mit diesen Zeilen einen Shader und binden unsere InputElementDescription an diesen Shader (Zeile 14). Diese Bindung ist wichtig, da die Grafikkarte damit erfährt, wie die Daten aus unserem Vertex-Buffer zu interpretieren sind.
Damit dieser Code funktioniert müssen wir im Header noch ein paar private Member-Variablen anlegen.
ID3D11VertexShader *pVS; ID3D11PixelShader *pPS; ID3D11InputLayout *pLayout;
Den eigentlichen Shader legen wir als Datei in das gleiche Verzeichnis ab, in dem sich auch unsere Quellcode-Dateien befinden. Der Shader sieht dabei wie folgt aus:
struct VOut
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
VOut output;
output.position = position;
output.color = color;
return output;
}
float4 PShader(float4 position : SV_POSITION, float4 color : COLOR) : SV_TARGET
{
return color;
}
Auch auf eine genaue Beschreibung der Funktionsweise dieses Shaders möchte ich an dieser Stelle verzichten und verweise dafür zum Beispiel auf das Shader 101.
Soweit zur Initialisierung, wir kommen nun zum eigentlichen Rendern. Dies erfolgt in der RenderFrame-Methode zwischen dem Aufruf zum löschen des Hintergrundes und dem Present-Aufruf zum umschalten des BackBuffers. Dazu benötigen wir den folgenden Code.
UINT stride = sizeof(VERTEX); UINT offset = 0; deviceContext->IASetVertexBuffers(0, 1, &pVBuffer, &stride, &offset); deviceContext->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->Draw(3, 0);
Wir teilen der Grafikkarte zunächst mit, welchen Vertex-Buffer wir für die folgenden Render-Aufrufe verwenden wollen. Die Beschreibung der einzelnen Parameter entnehmt ihr bitte der Dokumentation. Der nächste Befehl legt fest, wie die Daten im Vertex-Buffer ausgelesen werden. TOPOLOGY_TRIANGLELIST bedeutet dabei, dass immer drei aufeinanderfolgende Vertices gemeinsam als ein Dreieck interpretiert und gerendert werden sollen. Ich wurde im Forum bei der Rezension des XNA-Artikels zu Vertex Buffern gebeten, dass ich dazu noch ein paar Zeilen schreibe, da es auch noch andere Werte gibt. Welche das im einzelnen sind, findet ihr hier. Das sind eine ganze Menge und einige davon werden zum Beispiel für Tessellation verwendet. Es ist wichtig, dass ihr dies im Hinterkopf behaltet, damit ihr die Möglichkeiten kennt.
Der letzte Befehl in Zeile 16 gibt nun einfach an, dass drei Vertices ab Position 0 gerendert werden sollen. Dies sind schlicht und einfach die ersten drei Vertices unseres Vertex-Buffer und diese formen ein einzelnes Dreieck, wie wir gleich sehen werden.
Wir haben es auch schon fast geschafft. Wir müssen nur noch ein wenig aufräumen, indem wir die Shutdown-Methode der Basis-Klasse unseres Views überschreiben. Dies erfolgt mit dem folgenden Code:
void VertexBufferTutorial01View::Shutdown()
{
pVBuffer->Release();
pLayout->Release();
pVS->Release();
pPS->Release();
D3DRenderView::Shutdown();
}
Selbstverständlich müssen wir die Methode im Header-File vorher deklarieren.
void Shutdown();
Der Code sollte selbsterklärend sein. Wir geben den reservierten Vertex-Buffer, das erzeugte Layout, sowie den Pixel- und Vertex-Shader frei. Danach rufen wir die Shutdown-Methode der Basisklasse auf, in der DirectX deinitialisiert wird.
Wenn wir das Programm nun starten, dann sollte der folgende Bildschirminhalt erscheinen.
Daten auslesen
Auslesen von Daten aus einem Vertex-Buffer ist ebenfalls möglich. Dies funktioniert genau so, wie auch das senden von Daten geschieht, allerdings müssen Quell- und Ziel-Zeiger des memcpy Aufrufs vertauscht werden.
| |
Merke Lesender Zugriff auf den VertexBuffer im Grafikkartenspeicher ist langsam. |
Derartige Lesevorgänge sollten nur selten gemacht werden, denn diese belasten die Grafikkarte sehr und zwar aus mehreren Gründen:
- Lesevorgänge belasten den Bus zur Grafikkarte. Diese Bandbreite steht für Schreibvorgänge (Texturen hochladen, Vertex Buffer hochladen, etc.) nicht mehr zur Verfügung.
- Lesevorgänge können erst durchgeführt werden, wenn alle vorhergehenden Schreibvorgänge vollständig abgeschlossen sind.
- Bis ein Lesevorgang abgeschlossen wurde, kann kein weiterer Schreibvorgang durchgeführt werden.
- Vertex-Daten die gelesen werden können in der Regel nicht gleichzeitig gerendert werden.
- Die (begrenzten) Caches zum Zugriff auf den Grafikkartenspeicher werden "zugemüllt"
Wie im Grundlagenartikel Grafikpipeline beschrieben, arbeitet die Grafikkarte Aufträge parallelisiert ab. Diese ist soweit optimiert, dass zum Beispiel ein Render-Vorgang schon beginnen kann, während ein Vertex-Buffer noch befüllt wird. Ein Lesezugriff in der Zwischenzeit bringt dieses Konzept aber durcheinander und führt zu sogenannten Stalls der Grafikpipeline.
Wie ich zuvor schon angedeutet hatte wird ein solcher Lesevorgang aber auch sehr selten wirklich benötigt. Da wir in der Regel den Vertex-Buffer ja selbst befüllt haben, steht uns ja ein Array mit den Daten die wir hochgeladen haben im Arbeitsspeicher zur Verfügung. Mit diesen Daten können wir nun nach Herzenslust unabhängig von der Grafikkarte arbeiten. Wenn wir die Daten verändert haben und diese zur Grafikkarte hochladen müssen, dann können wir dies mit einem erneuten SetData-Aufruf durchführen. So kommt die Grafikkarte nicht durcheinander und der Zugriff auf die im Arbeitsspeicher befindlichen Daten durch die CPU erfolgt schnell und problemlos ohne zu stören.
Zusammenfassung
In diesem Artikel haben wir die Grundlagen im Umgang mit Vertex Buffern in DirectX 11 erlernt. Ich habe erklärt, was ein Vertex Buffer ist, wie dieser deklariert und erzeugt wird und wie lesende und schreibende Zugriffe darauf erfolgen. Ich habe kurz gezeigt wie ein Vertex Buffer zum Rendern von Dreiecken verwendet werden kann und möchte damit dieses Tutorial abschliessen. Ich möchte an dieser Stelle nochmal den deutlichen Hinweis geben, dass dieses Tutorial kein umfassendes Werk für Vertex Buffer in DirectX ist, sondern lediglich den Einstieg erleichtern soll und die Anfangsschwierigkeiten etwas schlichten soll. Mit dem hier erarbeiteten Code hat man jedoch einen guten Startpunkt für weitere Experimente.
Um den Überblick über den hier entwickelten Code zu verbessern werde ich diesen abschliessend nocheinmal zusammenhängend präsentieren. Hier also die Source- und die zugehörige Header-Datei dieses Tutorials.
VertexBufferTutorial01View.h
#ifndef _VERTEXBUFFER_TUTORIAL_01_VIEW_H_
#define _VERTEXBUFFER_TUTORIAL_01_VIEW_H_
#include "D3DRenderView.h"
struct VERTEX
{
FLOAT X, Y, Z; // position
D3DXCOLOR Color; // color
};
class VertexBufferTutorial01View : public D3DRenderView
{
public:
VertexBufferTutorial01View() {};
void Initialize(HWND hWnd);
void Shutdown();
void RenderFrame();
private:
ID3D11VertexShader *pVS;
ID3D11PixelShader *pPS;
ID3D11InputLayout *pLayout;
ID3D11Buffer *pVBuffer;
};
#endif
VertexBufferTutorial01View.cpp
#include "VertexBufferTutorial01View.h"
void VertexBufferTutorial01View::Initialize(HWND hWnd)
{
D3DRenderView::Initialize(hWnd);
D3D11_INPUT_ELEMENT_DESC ied[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
D3D11_BUFFER_DESC bd;
ZeroMemory(&bd, sizeof(bd));
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(VERTEX) * 3;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
device->CreateBuffer(&bd, NULL, &pVBuffer);
VERTEX OurVertices[] =
{
{ 0.0f, -0.5f, 0.0f, D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f)},
{-0.5f, 0.5f, 0.0f, D3DXCOLOR(0.0f, 1.0f, 0.0f, 1.0f)},
{ 0.5f, 0.5f, 0.0f, D3DXCOLOR(0.0f, 0.0f, 1.0f, 1.0f)},
};
D3D11_MAPPED_SUBRESOURCE ms;
deviceContext->Map(pVBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms);
memcpy(ms.pData, OurVertices, sizeof(OurVertices));
deviceContext->Unmap(pVBuffer, NULL);
ID3D10Blob *VS, *PS;
ID3D10Blob* l_pBlob_Errors = NULL;
LPVOID l_pError = NULL;
D3DX11CompileFromFile(L"shaders.hlsl", 0, 0, "VShader", "vs_4_0", 0, 0, 0, &VS, &l_pBlob_Errors, 0);
D3DX11CompileFromFile(L"shaders.hlsl", 0, 0, "PShader", "ps_4_0", 0, 0, 0, &PS, &l_pBlob_Errors, 0);
device->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS);
device->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS);
deviceContext->VSSetShader(pVS, 0, 0);
deviceContext->PSSetShader(pPS, 0, 0);
device->CreateInputLayout(ied, 2, VS->GetBufferPointer(), VS->GetBufferSize(), &pLayout);
deviceContext->IASetInputLayout(pLayout);
}
void VertexBufferTutorial01View::Shutdown()
{
pVBuffer->Release();
pLayout->Release();
pVS->Release();
pPS->Release();
D3DRenderView::Shutdown();
}
void VertexBufferTutorial01View::RenderFrame()
{
D3DRenderView::deviceContext->ClearRenderTargetView(D3DRenderView::backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));
UINT stride = sizeof(VERTEX);
UINT offset = 0;
deviceContext->IASetVertexBuffers(0, 1, &pVBuffer, &stride, &offset);
deviceContext->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
deviceContext->Draw(3, 0);
swapchain->Present(0, 0);
}
