Nota: acest articol se adreseaza numai Direct3D 9, deoarece Direct3D 10 a rezolvat aceasta problema... la 10 ani dupa OpenGL.

Exista o problema destul de enervanta la Direct3D care iti poate da bataie de cap daca nu stii ce se intampla. Atunci cand se intampla ceva cu fereastra ta in care rendezi, de exemplu daca utilizatorul ia schimbat dimensiunea (de la bordura), sau atunci cand dai ALT-TAB din modul fullscreen si te intorci la joc, iti "pierzi" device-ul tau Direct3D care il foloseai sa rendezi. Este de tipul LPDIRECT3DDEVICE9, mai exact IDirect3DDevice9 (primul e pointer la acesta).
Se poate observa destul de usor ce se intampla: ecranul/fereastra ta ramane cam goala. Perversa. Nu se mai rendeaza nimic, si totul este "din senin", deoarece nu o sa-ti apara nici o eroare sau asa ceva.
Problema este putin cam smechera cand vine vorba de rezolvat, dar iata cum se face:
Sa zicem ca ai o functie in codul tau care tot ce face este sa rendeze. Probabil ca o sa apelezi aceasta functie in fiecare frame. Ei bine, la inceputul acestei functii, adica deasupra:
Cod sursă:
void rendeaza()
{
..
device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
..
device->BeginScene();
...
device->EndScene();
..
}
unde desigur "device" este de tipul LPDIRECT3DDEVICE9, adica device-ul tau Direct3D, trebuie sa adaugi cateva linii care verifica daca ai pierdut device-ul.
In mare, faci verifici acest lucru cu ajutorul functiei "Present" al device-ului tau. Este singura functie care o poti apela, si face ceva folositor, cand device-ul tau este pierdut (lost).
Cod sursă:
void render()
{
// verifica daca device-ul e pierdut
if (device->Present(NULL, NULL, NULL, NULL) == D3DERR_DEVICELOST)
{
HRESULT hr = device->TestCooperativeLevel();
// device-ul este pierdut, si nu se poate recupera momentan,
// termina functia fara sa mai incerci sa rendezi nimic
if (hr == D3DERR_DEVICELOST)
return;
// de data asta, device-ul poate fi recuperat!
if (hr == D3DERR_DEVICENOTRESET)
{
invalideaza_grafica();
if (FAILED(device->Reset(&d3dpp)))
return; // ghinion, mai incearca frame-ul urmator!
initializeaza_grafica(); // am terminat!
}
}
..
device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
..
device->BeginScene();
...
device->EndScene();
..
}
Si acum, explicatiile:
1)
Cod sursă:
if (device->Present(NULL, NULL, NULL, NULL) == D3DERR_DEVICELOST)
Evident, linia asta verifica daca device-ul nostru e intreg. Daca e, se continua cu rendarea, ca in mod normal.
2)
Cod sursă:
HRESULT hr = device->TestCooperativeLevel();
Aceasta functie ne spune mai exact ce e cu device-ul nostru, daca il putem reseta (si deci sa functioneze normal), sau daca mai trebuie sa asteptam, si nu putem renda inca.
Dupa 2), daca 'hr' este 'D3DERR_DEVICENOTRESET', inseamna ca putem reseta, si sa ne continuam viata de joc obisnuit ce eram.
Cum se reseteaza:
Eu am prezentat destul de abstract, dar defapt, atunci cand trebuie sa-ti resetezi device-ul, trebuie sa distrugi tot ce ai creat, si sa creezi din nou.
De mentionat ca prin "distrugi" inseamna sa chemi functia "Release()" a respectivului obiect Direct3D, de exemplu o textura, sau o suprafata etc.
Probabil o sa scrii ceva de genul acesta:
Cod sursă:
LPDIRECT3DTEXTURE9 textura_mea;
...
if (textura_mea != 0)
{
textura_mea->Release();
textura_mea = 0;
}
Insa, ai si ceva noroc azi! Daca respetivul tau obiect Direct3D (ex: textura) este creat folosind parametrul "D3DPOOL_MANAGED", nu mai trebuie sa faci distrugi acel obiect tu, se ocupa Direct3D de asta automat. Sunt cazuri in care unele obiecte nu pot fi create cu D3DPOOL_MANAGED, ci doar in D3DPOOL_DEFAULT, sau poate asa ai vrut tu intentionat sa-ti creezi acel obiect. (deoarece obiectele din D3DPOOL_MANAGED au copia principala in memoria placii tale video, insa o copie in plus in RAM)
Daca nu iti distrugi obiectele, resetarea nu va reusi.
3)
Cod sursă:
if (FAILED(device->Reset(&d3dpp)))
return;
Aici incerci sa resetezi. Daca nu reusesti, functia se termina, si se incearca automat data viitoare (frame-ul urmator)
Ai vazut de ce am pus mai sus "if (textura_mea != 0)" ? Daca prima resetare nu a reusit, a doua oara cand incerci (frameu urm.), daca nu verifici ca e diferit de 0 (null), o sa apelezi "Release()" din nou pe acel obiect. Eu unul habar n-am ce se intampla in acest caz, fiindca nu incerc lucruri evident tampite (cum ar fi sa sterg un obiect de 2 ori)
4) Ultimul pas, dupa ce esti sigur ca device-ul tau este din nou functional, este sa recreezi resursele si setarile. Adica sa creezi texturile, vertex buffer-ii, fonturile etc.etc. asa cum i-ai crea de obicei, nimic deosebit. Insa in afara de resurse, trebuie sa setezi din nou tot ce ai setat inainte in render state-ul device-ului tau. Cum ar fi, daca vei folosi iluminare sau nu; mai trebuie sa setezi din nou si proiectia, si cam tot ce ai setat inainte sa se petreaca tragedia de a pierde device-ul
Ei bine, in mare - asta e. Poate ca unii o sa spuna "ei si ce, Direct3D 10 rezolva problema". Mda, dar multi dintre voi o sa foloseasca tot Direct3D 9. Starcraft 2 va fi lansat in 2008 (yay! ^_^). Si o sa foloseasca tot D3D 9.
O sa mai treaca ceva pana D3D 10 o sa fie acceptat complet.
Intre timp, iata inca un sfat pentru a simplifica problema de mai sus. Foloseste o singura functie pentru a initializa resursele grafice din jocul tau, astfel, codul tau ar trebui sa arate cam asa: (abstract)
Cod sursă:
void initializeaza_fereastra(); // creaza fereastra
void initializeaza_resursele_grafice(); // incarca texturi, modele etc.
void distruge_resursele_grafice(); // distruge texturile, modelele etc.
// la fel ca in prima parte a articolului
void rendeaza()
{
// verifica daca device-ul e pierdut
if (device->Present(NULL, NULL, NULL, NULL) == D3DERR_DEVICELOST)
{
HRESULT hr = device->TestCooperativeLevel();
// device-ul este pierdut, si nu se poate recupera momentan,
// termina functia fara sa mai incerci sa rendezi nimic
if (hr == D3DERR_DEVICELOST)
return;
// de data asta, device-ul poate fi recuperat!
if (hr == D3DERR_DEVICENOTRESET)
{
distruge_resursele_grafice();
if (FAILED(device->Reset(&d3dpp)))
return; // ghinion, mai incearca frame-ul urmator!
initializeaza_resursele_grafice(); // am terminat!
}
}
..
device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
..
device->BeginScene();
...
device->EndScene();
..
}
int main(int argc, char **argv)
{
initializeaza_fereastra();
initializeaza_resursele_grafice();
while (TRUE) // la infinit
{
rendeaza();
}
return 0;
}
Desigur, acesta este un exemplu foarte abstract, dar din care reiese ca folosesti aceeasi functie atat cand incarci jocul/nivelul, cat si cand trebuie sa-ti recreezi device-ul.
Cam asta e tot. Acesta a fost primul tutorial/articol care l-am scris in viata mea, deci probabil ca e destul de imperfect (ca sa nu spun chiar idiot scris

)
Comentarii/intrebari/sugestii?