Πέμπτη 8 Δεκεμβρίου 2011

DirectX3d 10 Part 1 (σετάρισμα)

Για αυτούς που βαριούνται να διαβάσουν, για αυτούς που θέλουν να δουν κώδικα να τρέχει είπα να γράψω έναν "starter" για το directx 10.



DirectX 3d 10, χμμ, παίζει ναι είναι απο τα πιο αγαπημένα μου APIs, για αυτό ελπίζω να τελειώσω το εν λόγο ποστ (:p).


Λοιπόν, για να δουλέψεις με direcrx 10 θέλεις το sdk που το βρίσκεις στο microsoft download center. Πρέπει οπωσδήποτε να ξέρεις πάνω κάτω πως δουλεύει ένα window και ελάχιστα για την COM τεχνολογία ( απλά πράματα, ένα release).

Αρχίζουμε κάπως έτσι.
Φτιάχνουμε ένα empty project (win32 window), σε αυτό θα βάλουμε δυο αρχεία
dxandwindow.h, main.cpp

dxandwindow.h

#pragma once
#include <Windows.h>
#include </Program Files/Microsoft DirectX SDK (June 2008)/Include/D3D10.h>
#include </Program Files/Microsoft DirectX SDK (June 2008)/Include/D3DX10.h>
#pragma comment ( lib,"/Program Files/Microsoft DirectX SDK (June 2008)/Lib/x86/d3d10.lib")
#pragma comment ( lib,"/Program Files/Microsoft DirectX SDK (June 2008)/Lib/x86/d3dx10.lib")

main.cpp
#include "dxandwindow.h"


LPCWSTR lpszWndClsName = L"{BAF17182-20D1-46EB-8CAB-B4AAF5388552}";
LPCWSTR lpszWndTitle = L"Hi!";
HWND     gWnd;
int      Height = 600;
int      Width = 800;


ID3D10Device    *pDevice;
IDXGISwapChain    *pSwapChain;
ID3D10RenderTargetView  *pRenderView;


HRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
int InitDevice(HWND hWnd,int w,int h);
int ReleaseDevice();
void RenderGame();



int WINAPI wWinMain(HINSTANCE hInst,HINSTANCE,LPWSTR,int nShow)
{
 WNDCLASS wc;
 MSG msg;

 ZeroMemory(&wc,sizeof(wc));

 wc.lpfnWndProc  = WndProc;
 wc.lpszClassName = lpszWndClsName;
 wc.hCursor   = LoadCursor(NULL,IDC_ARROW);


 if(!RegisterClass(&wc))
  return 1;
 gWnd = CreateWindow(lpszWndClsName,lpszWndTitle,
  WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,0,Width,Height,
  NULL,NULL,hInst,NULL);
 if(!gWnd)
  return 1;
 ShowWindow(gWnd, nShow);


 while(GetMessage(&msg,0,0,0) > 0)
 {
  TranslateMessage(&msg);
  DispatchMessage(&msg);
 }

 return 0;
}


HRESULT CALLBACK WndProc(HWND hWnd,UINT msg,WPARAM wParam, LPARAM lParam)
{
 switch(msg)
 {
 case WM_TIMER:
  RenderGame();
  break;
 case WM_CREATE:
  if(InitDevice(hWnd,Width,Height))
   return -1;
  SetTimer(hWnd,1,10,NULL);
  break;
 case WM_CLOSE:
  ReleaseDevice();
  PostQuitMessage(0);
  break;

 default:
  return DefWindowProc(hWnd,msg,wParam,lParam);
 }
 return 0;
}
int InitDevice(HWND hWnd,int w,int h)
{

 ID3D10Texture2D    *pBackBuffer;
 DXGI_SWAP_CHAIN_DESC  swapChainDesc;

 
 ZeroMemory(&swapChainDesc,sizeof(swapChainDesc));
 swapChainDesc.BufferCount   = 1;
 swapChainDesc.BufferUsage   = DXGI_USAGE_RENDER_TARGET_OUTPUT;
 swapChainDesc.SampleDesc.Count  = 1;
 swapChainDesc.OutputWindow   = hWnd;
 swapChainDesc.Windowed    = true;

 swapChainDesc.BufferDesc.Height  = h;
 swapChainDesc.BufferDesc.Width  = w;
 swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
 swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
 swapChainDesc.BufferDesc.Format  = DXGI_FORMAT_R8G8B8A8_UNORM;

 if(FAILED(D3D10CreateDeviceAndSwapChain(
  NULL,D3D10_DRIVER_TYPE_HARDWARE,NULL,NULL,
  D3D10_SDK_VERSION,&swapChainDesc,&pSwapChain,&pDevice)))
 {
  return 1;
 }

 if(FAILED(pSwapChain->GetBuffer(0,__uuidof(ID3D10Texture2D),(void**)&pBackBuffer)))
 {
  pSwapChain->Release();
  pDevice->Release();
  return 1;
 }
 
 if(FAILED(pDevice->CreateRenderTargetView(pBackBuffer,0,&pRenderView)))
 {
  pSwapChain->Release();
  pDevice->Release();
  pBackBuffer->Release();
  return 1;
 }
 pBackBuffer->Release();


 pDevice->OMSetRenderTargets(1,&pRenderView,NULL);
 

 D3D10_VIEWPORT vp;
 vp.Height = h;
 vp.Width = w;
 vp.MaxDepth = 1.0f;
 vp.MinDepth = 0.0f;
 vp.TopLeftX = 0;
 vp.TopLeftY = 0;
 pDevice->RSSetViewports(1,&vp);
 
 return 0;
}
int ReleaseDevice()
{
 pRenderView->Release();
 pSwapChain->Release();
 pDevice->Release();
 return 0;
}

void RenderGame()
{
 pDevice->ClearRenderTargetView(pRenderView,D3DXCOLOR(0.0f,0.0f,0.30f,1.0f));

 pSwapChain->Present(1,0);
}
.
Το Window κομμάτι
Έχουμε και λέμε:
wWinMain και WndProc είναι το window κομμάτι του προγράμματος. Στην wWinMain φτιάχνουμε το παράθυρο και στην WndProc πιάνουμε τα events. Από τα events πιάνουμε μονό wm_create, wm_close, wm_timer.
wm_create Αυτό το event έρχεται όταν έχουμε καλέσει την createWindow/Ex και πριν η createWindow επιστρέψει το window handler (HWND). Εδώ καλούμε την InitDevice, στη περίπτωση που αποτύχει η initDevice επιστρέφουμε -1 έτσι ωστε η CreateWindow να επιστρέψει null και να τερματίσει το πρόγραμμα (βλ. main.cpp@40-41 line). Αν δεν έχουμε αποτυχία τοτε πάμε στο επόμενο SetTimer, εδώ σετάρουμε ένα χρονόμετρο το οποίο θα μας ενημερώνει κάθε 0,01 δευτερολέπτου. Ενημερώνει... δηλαδή το σύστημα θα καλεί την WndProc κάθε 0,01 δευτερολέπτου με message να είναι το wm_timer.

wm_timer Εδώ καλείται η RenderGame η όποια δημιουργεί τα frames. Αν καλέσουμε την renderGame 10 φορές μεσα σε ένα δευτερόλεπτο τότε έχουμε 10 FPS, αν την καλέσουμε 100 τότε έχουμε 100FPS κλπ. Εδώ την καλούμε 100 φορές αρα έχουμε 100 fps (και καλά)

wm_close αυτή καλείται οταν πατήσουμε το X ή alt+f4 κλπ. Εδώ καλούμε την ReleaseDevice που αποδεσμεύει αυτά που θα δούμε πιο κάτω. Έπειτα καλούμε την PostQuitMessage η οποία σπάει το main loop (βλ. main.cpp@45)

Directx 10 κομμάτι
CreateDevice,RenderGame, ReleaseDevice

CreateDevice Εδώ φτιάχνουμε την βασική μηχανή. Βασικά να πω μερικά πράγματα για την COM τεχνολογία, αυτή δεν είναι τίποτα παραπάνω από ένα abi το οποίο "εξάγει" objects. Προσοχή το api το οποίο είναι σε com αποτελείται μόνο από interfaces. Λογού ότι ενα interface μπορεί να φορτώσει διαφορετικά objects και ένα object μπορεί να κάτσει πάνω σε διαφορετικά interfaces, πρέπει να ξέρουμε δυο πράγματα, το όνομα του object (CLSID), το όνομα του interface (IID). Για να "δημιουργήσουμε" ένα interface μπορούμε να χρησιμοποιήσουμε την CoCreateInstance η οποία παίρνει ένα clsid και ένα iid. Ευτυχώς που το directx έχει παρα πολλές βοηθητικές συναρτήσεις και έτσι δεν θα μπλέξουμε με clsid, iid. Επίσης, επειδή δεν ξέρουμε σε ποιο process έχει φορτώσει το object, δεν χρησιμοποιούμε deallocator, αλλα καλούμε την Release η οποία είναι μια συνάρτηση του IUknown interfface το οποίο είναι η βάση όλων των COM interfaces.
Που μείναμε.. Στη device (ID3D10Device) -να σημειώσω, οπού βλέπουμε το perfix I σημαίνει interface- που είναι ο σκελετός της μηχανές γραφικών μας. Από μονής της δεν είναι τίποτα, για να κάνουμε render πρέπει να έχουμε τουλάχιστο να σετάρουμε έναν buffer και την view port.

Βλέπετε οτι στην initDevice έχουμε μια δομή με κάποιες πληροφορίες για κάποιο interface που λέγεται swapchain. Εδώ δυσκολεύουν τα πράματα διότι εκτός του χωρισμού του προγράμματος μας σε window - directx κομμάτι (το οποίο είναι software) πρέπει να το χωρίσουμε και το hardware.
Το ένα κομμάτι είναι η GPU(οι streamers της κάρτας γραφικών), VRAM (η ram της κάρτας γραφικών).
Δεύτερο είναι η CPU και η RAM.
Πάμε πάλι στο IDXGISwapChain, βλέπετε οτι αυτό το interface διαφέρει στο perfix αυτό γιατί είναι ένα από τα low level interfaces I (interface) DX(directx) G(graphic) I(Infrastructure) που είναι μια γέφυρα προς την κάρτα γραφικών. Η δουλειά του είναι η διαχείριση των buffers, όχι οποιοδήποτε buffer! μονό αυτων που προβάλλονται, ονομαζόμενοι surface. Σίγουρα ο πληθυντικός μπερδεύει. Ας το πάμε λάου-λάου.

Έστω οτι θέλουμε να προβάλουμε ένα κύβο, και έστω οτι έχουμε έτοιμους τους buffers στην κάρτα γραφικών. Η VRAM θα έχει δυο buffers
1 τον surface ο καμβάς
2 τον mesh αυτός που κρατάει τα δεδομένα για τον κύβο (points,textures etc..)
Η διαδικασία για να βγει το frame θα είναι mesh buffer -> dx pipeline -> surface buffer -> screen

Αυτός είναι ο surface buffer. Στο dx10 όμως έχουμε δυο+ buffers για surface ο front και ο back. Λόγος; Για αποφυγή του flickering.


Το swap γίνετε στους δείκτες των buffers.
Αυτός είναι ο surface buffer, και τώρα πάμε στο swapchaindesc. Τα πιο σημαντικά
1) Το width, height που πρακτικά είναι το μέγεθος της εικόνας σε pixel αυτό που βλέπουμε και στα παιχνίδια πχ να τρέχει σε ανάλυσή 1280 x 720 (στο παράδειγμα μου είναι 800 x 600)
2) Το format τα χρώματα (στο παράδειγμα εχω 32bit)
3) Τον αριθμό των back buffers (έχω έναν)
4) Το RefreshRate (denominator/numerator = Hz)
5) To output window , windowed στο πρώτο βάζουμε το window που θα φιλοξενήσει το directx μας το δεύτερο true θα είναι σε window false θα είναι fullscreen


Έπειτα καλούμε την βοηθητική συνάρτηση CreateDeviceAndSwapChain η οποία μας ξεμπλέκει από τα παραπάνω που είπα για τα COM interfaces. Να σημειώσω οτι όλες (εκτός από τα state) οι συναρτήσεις του directx επιστρέφουν HRESULT (βλ. wiki)  καλό είναι να χρησιμοποιούμε το μάκρο FAILED για προφανούς λόγους.

Τώρα πρέπει να κουμπώσουμε τον backbuffer στο output marger το οποίο ειναι το τελευταίο στάδιο του directx pipeline (μετά απο αυτό.. η οθόνη) (για τα στάδια θα γράψω στο επόμενο).
Για να το κάνουμε αυτο πρέπει να φτιάξουμε ένα RenderTargetView το οποίο θα κρατάει τον backbuffer. Παίρνουμε το πρώτο buffer (τον backbuffer) από το swapchain και με αυτόν φτιάχνουμε το rendertargetview -επειδή περνάμε τον buffer στο rendertargetview ο buffer αυξάνει τα references για αυτό μετά πρέπει να καλέσουμε την release ώστε να ρίξει τα refs για να μην έχουμε memory leaks-. Αυτό που μένει να κάνουμε με το rendertargetview είναι να το κουμπώσουμε στην μηχανή. Για να το κάνουμε αυτό καλούμε την Device::OMSetRenderTargets.

Για να είναι έτοιμη η μηχανή να πυροβολάει pixel, θέλουμε μια viewport. Η Viewport δεν θέλει πολύ εξιγηση, απλά λεμέ σε πιο μέρος του backbuffer να "εκτυπωθεί" το σχήμα (mesh,texture etc..) μας. Στο παράδειγμα χρησιμοποιώ όλο τον buffer. Βασικά τώρα που το σκέφτομαι.. δεν υπάρχει λόγος να βάλουμε viewport διότι δεν ζωγραφίζω κάποιο σχήμα.. Χε χε τεσπα, βαριέμαι να αλλάζω το κώδικα που έκανα ποστ για αυτό ας υπάρχει. Και για να ολοκληρώσω, κουμπώνουμε την viewport στο rasterizer stage (Device::RSSetViewPort)

Τέλος ήρθε η ώρα να "πυροβολήσουμε" την οθόνη με pixel. Αυτό γίνεται στο RenderGame.
Πρώτη γραμμή device::ClearTargetView. Όπως ειπα και πιο πάνω, το renderTargetView κρατάει τον backbuffer και όπως λέει και η συνάρτηση (clear) αυτό που κάνει ειναι να καθαρίζει τον backbuffer με κάποιο χρώμα. Στο παράδειγμα μου είναι ενα σκούρο μπλε -να σημειώσω οτι τα χρώματα του dx είναι από 0 έως 1-.
Δεύτερη γραμμή swapchain::Present Το περιβόητο swap ο front buffer γίνεται backbuffer και ο backbuffer γίνεται front και viola -στο παράδειγμα εχω έναν άσσο σε αυτή τη συνάρτηση, αυτο ειναι για vsync-.

Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου