Sol's dirty DX7 tutorial

VertexBuffer

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