email sol follow sol rss feed of the blog wishlist Sol::Tutorials

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

Site design & Copyright © 2021 Jari Komppa
Possibly modified around: June 01 2010