Sol's dirty DX7 tutorial
Vertex Buffer
Obwohl ihr vielleicht versucht seid gleich zum nächsten Kapitel zu springen (welches Texture Mapping behandelt),
solltet ihr euch überlegen, ob ihr nicht doch zuerst dieses lest - besonders wenn ihr in Zukunft komplexere Dinge
mit D3D machen wollt.
Ich hatte ursprünglich vor dieses Kapitel "Ein bischen Optimierung" zu nennen, da das einbinden der Vertex Arrays
in einen Vertex Buffer mir nette 50% Geschwindigkeitszuwachs auf meinen alten TNT Treibern gebracht hat; mit
den Detonator Treibern sind Vertex Buffer allerdings etwas langsamer. Ich nehme an, das die originalen TNT Treiber
eine lokale Kopie der Vertex Arrays behielten um sicher zu gehen, das die Daten auch nach dem DrawPrimitive
Aufruf noch erhalten bleiben. Ich weiß nicht, wie die Detonator Treiber ohne dies arbeiten können, aber so
ist es eben. Auf jeden Fall nutzen wir aufgrund der zusätzlichen Funktionen und dem normalerweise
vorhandenen Geschwindigkeitszuwachs Vertex Buffer.
#define WIN32_LEAN_AND_MEAN
#include <windows.h> // windows stuff
#include <stdio.h> // standard IO
#include <stdlib.h> // standard C lib
#include <ddraw.h> // DirectDraw
#include <d3d.h> // Direct3D
#include <d3dx.h> // Direct3DX
/*
* Griddy2
* d3dx7 single threaded app
* sol/trauma 1999
*/
char progname[]="Griddy2 - Sol";
HWND mainhWnd;
HINSTANCE mainhInst;
LPD3DXCONTEXT dxctx;
LPDIRECT3DDEVICE7 d3dd;
LPDIRECT3D7 d3d;
LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam) {
if (uMsg==WM_DESTROY) {
d3d->Release();
d3dd->Release();
D3DXUninitialize();
exit(wParam);
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
#define GRID_X 64
#define GRID_Y 48
#define XMUL (640.0f/(GRID_X))
#define YMUL (480.0f/(GRID_Y))
WORD idx[2*(GRID_X+1)*(GRID_Y+1)];
LPDIRECT3DVERTEXBUFFER7 vbuf;
void init(void)
{
D3DTLVERTEX *buf;
D3DVERTEXBUFFERDESC vbd;
vbd.dwSize=sizeof(vbd);
vbd.dwCaps=D3DVBCAPS_WRITEONLY;
vbd.dwFVF=D3DFVF_TLVERTEX;
vbd.dwNumVertices=(GRID_X+1)*(GRID_Y+1);
d3d->CreateVertexBuffer(&vbd, &vbuf,0);
vbuf->Lock(DDLOCK_WAIT|DDLOCK_WRITEONLY|DDLOCK_NOOVERWRITE,(void**)&buf,NULL);
for (int y=0;y<(GRID_Y+1);y++)
for (int x=0;x<GRID_X+1;x++) {
idx[y*(GRID_X+1)*2+x*2+1]=(WORD)((y)*(GRID_X+1)+x);
idx[y*(GRID_X+1)*2+x*2]=(WORD)((y+1)*(GRID_X+1)+x);
buf[y*(GRID_X+1)+x].sx=x*XMUL;
buf[y*(GRID_X+1)+x].sy=y*YMUL;
buf[y*(GRID_X+1)+x].sz=0;
buf[y*(GRID_X+1)+x].rhw=1;
buf[y*(GRID_X+1)+x].color=0;
}
vbuf->Unlock();
}
void setup(void)
{
D3DTLVERTEX *buf;
vbuf->Lock(DDLOCK_WAIT|DDLOCK_WRITEONLY|DDLOCK_NOOVERWRITE,(void**)&buf,NULL);
float i=GetTickCount()*0.02f;
for (int y=0,c=0;y<GRID_Y+1;y++)
for (int x=0;x<GRID_X+1;x++,c++) {
buf[c].color=RGB((int)(
sin((y+i)*0.2251474)*44+
cos((y-i)*0.2354128)*
cos((x+i)*0.3434913)*84
)+128,
(int)(
cos((y-i)*0.2252474)*44+
cos((x-i)*0.3334923)*84
)+128,
(int)(
sin((y-i)*0.1453474)*44+
sin((y+i)*0.2354328)*
cos((x-i)*0.3234933)*84
)+128);
}
vbuf->Unlock();
}
void reindeer(void)
{
static int inited=0;
if (!inited) {
init();
inited++;
}
setup();
//dxctx->Clear(D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER);
d3dd->BeginScene();
for (int y=0;y<GRID_Y;y++)
d3dd->DrawIndexedPrimitiveVB(D3DPT_TRIANGLESTRIP, vbuf, 0, (GRID_X+1)*(GRID_Y+1),
(idx+y*(GRID_X+1)*2), (GRID_X+1)*2,0);
d3dd->EndScene();
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASSEX winclass;
HWND hWnd;
MSG msg;
int fs=0;
if (MessageBox(NULL,"Shall we do it in fullscreen mode?",
"Silly question",MB_YESNO)==IDYES) fs=1;
lpCmdLine=lpCmdLine;
hPrevInstance=hPrevInstance;
if (FAILED(D3DXInitialize())) return 0;
mainhInst=hInstance;
winclass.cbSize=sizeof(WNDCLASSEX);
winclass.style=CS_DBLCLKS;
winclass.lpfnWndProc=&WindowProc;
winclass.cbClsExtra=0;
winclass.cbWndExtra=0;
winclass.hInstance=hInstance;
winclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
winclass.hCursor=LoadCursor(NULL,IDC_ARROW);
winclass.hbrBackground=GetSysColorBrush(COLOR_APPWORKSPACE);
winclass.lpszMenuName=NULL;
winclass.lpszClassName=progname;
winclass.hIconSm=NULL;
if (!RegisterClassEx(&winclass))
return 0;
hWnd=CreateWindow(
progname,
progname,
WS_SYSMENU|WS_CAPTION|WS_BORDER|WS_OVERLAPPED|WS_VISIBLE|WS_MINIMIZEBOX,
CW_USEDEFAULT,
0,
640,
480,
NULL,
NULL,
hInstance,
NULL);
mainhWnd=hWnd;
if (FAILED(D3DXCreateContext(
D3DX_DEFAULT,
D3DX_CONTEXT_FULLSCREEN*fs, //windowed = 0
hWnd,
D3DX_DEFAULT,
D3DX_DEFAULT,
&dxctx))) return 0;
d3dd=dxctx->GetD3DDevice();
d3d=dxctx->GetD3D();
d3dd->SetRenderState(D3DRENDERSTATE_CULLMODE,D3DCULL_NONE ); // no cull
d3dd->SetRenderState(D3DRENDERSTATE_DITHERENABLE,TRUE); // dither on
d3dd->SetRenderState(D3DRENDERSTATE_ZENABLE,D3DZB_FALSE); // no zbuf
d3dd->SetRenderState(D3DRENDERSTATE_CLIPPING,FALSE);
d3dd->SetRenderState(D3DRENDERSTATE_LIGHTING,FALSE);
ShowWindow(hWnd,nCmdShow);
int frame=0;
int starttime;
starttime=GetTickCount();;
char str[200];
while (1) {
reindeer();
frame++;
int sec=GetTickCount()-starttime;
if (sec>0) sprintf(str,"frame:%05d sec:%05d.%03d fps:%3.3f (alt-F4 quits)",
frame,sec/1000,sec%1000,(frame*1000.0)/sec);
dxctx->DrawDebugText((float)(2/640.0),(float)(2/480.0),0xffffff,str);
dxctx->UpdateFrame(0);
if(PeekMessage(&msg,hWnd,0,0,PM_REMOVE)) {
switch(msg.message) {
case WM_QUIT:
case WM_DESTROY:
d3d->Release();
d3dd->Release();
D3DXUninitialize();
return (msg.wParam);
default:
DefWindowProc(hWnd,msg.message,msg.wParam,msg.lParam);
}
}
}
}
Lasst uns die Dinge mal zur Abwechslung in logischer Reihenfolge durchgehen.
LPDIRECT3DVERTEXBUFFER7 vbuf;
Hier setzen wir die Vertex Buffer Variable.
D3DVERTEXBUFFERDESC vbd;
vbd.dwSize=sizeof(vbd);
vbd.dwCaps=D3DVBCAPS_WRITEONLY;
vbd.dwFVF=D3DFVF_TLVERTEX;
vbd.dwNumVertices=(GRID_X+1)*(GRID_Y+1);
d3d->CreateVertexBuffer(&vbd, &vbuf,0);
Und hier erzeugen wir den Vertex Buffer. Wie bei so vielen Windows-Funktionen bedeutet das, das
wir die Spezifikation was wir alles brauchen in einen Array schreiben und dann eine Funktion
zum erzeugen aufrufen. Als erstes setzen wir die Größe des Arrays (wie bei allen anderen Windows-Funktionen
auch), dann definieren wir die Fähigkeiten dieses Vertex Buffers. Wir werden nur in diesen Buffer schreiben,
also können die Grafikkarten ihre Aufrufe optimieren, wenn sie das wissen. Wir könnten den Vertex Buffer
zwingen den Hauptspeicher zu nutzen (anstatt z.B. den T&L Speicher der Grafikkarte), indem wir die
_SYSTEMMEMORY Flag setzen. Es gibt auch eine _DONOTCLIP Flag, aber die läßt aus unterschiedlichen
Gründen das System abstürzen, aber wir brauchen keine Clipping Informationen an dieser Stelle.
FVF ist das selbe Vertex Format, welches wir vorher im DrawPrimitive Aufruf genutzt haben.
vbuf->Lock(DDLOCK_WAIT|DDLOCK_WRITEONLY|DDLOCK_NOOVERWRITE,(void**)&buf,NULL);
vbuf->Unlock();
Bevor ihr mit dem Inhalt des Buffers herumspielt müßt ihr ihn sperren und anschließend wieder entsperren,
so das die Hardware (oder DirectX selbst) wieder an ihn heran kann. Die ersten Parameter sind wieder
Flags, in denen wir dem System mitteilen können das es warten soll, bis wir den Buffer gesperrt
haben, falls er gerade genutzt wird und das wir nur darin schreiben, aber in diesem Frame nichts
überschreiben. Die letzten beiden sind dazu da, das die Treiber ihr Verhalten optimieren können.
Der nächste Parameter ist der Pointer zu unserem Pointer auf den Buffer, welchen wir genauso
nutzen können, wie vorher unseren Vertex Array. Der letzte Parameter wäre ein Pointer zu einem
dword, welcher die Größe des Vertex Buffers beinhaltet. Da wir sie aber schon kennen setzen wir ihn auf NULL.
Der letzte geänderte Teil ist der Rendering Aufruf:
d3dd->DrawIndexedPrimitiveVB(D3DPT_TRIANGLESTRIP, vbuf, 0, (GRID_X+1)*(GRID_Y+1),
(idx+y*(GRID_X+1)*2), (GRID_X+1)*2,0);
Wie ihr sehen könnt fehlt im Vergleich zur non-Vertex Buffer Variante die Vertex Type Information.
Die extra 0 nach vbuf zeigt an das der erste Vertex im Vertex Buffer genutzt wird. Ansonsten ist alles
identisch.
Also wieso nutzen wir Vertex Buffer? Als erstes ist es ein handlicher Wrapper. Zweitens können sich
die Treiber darauf verlassen das der Speicher, den sie nutzen auch nach dem Aufruf noch vorhanden ist.
Drittens können die Daten auch in Nicht-Hauptspeicher abgelegt werden und die neuen T&L Karten
können genutzt werden, um die Transformationen und anderen Kram mit Hilfe des ProcessVertices
Aufrufes im Vertex Buffer Objekt zu berechnen.
Im (bis jetzt) letzten Kapitel fügen wir ein paar Texturen hinzu