FAQ DirectX
FAQ DirectXConsultez toutes les FAQ
Nombre d'auteurs : 4, nombre de questions : 63, dernière mise à jour : 14 juin 2021
- J'ai un problème !! Que faire ?
- Comment déboguer le code DirectX de mon application ?
- Comment gérer proprement la perte du focus ?
- Comment détecter et éradiquer les fuites mémoire ?
- Où sont passés les modèles de projets DirectX dans Visual Studio ?
- Ma fonction n'attend pas le même nombre/type de paramètres que ce qu'indique la documentation !
- Pourquoi mes appels à IDirect3DDevice9::Getxxx() échouent-ils ?
- Comment désactiver la synchronisation verticale ?
Avant de venir exposer votre problème sur les forums, il y a plusieurs étapes à suivre qui vous aideront à identifier votre problème.
Testez le retour de vos fonctions. Les composants DirectX ne lèvent pas d'exception, ainsi vous ne détecterez les erreurs qu'au premier plantage. Mais n'oubliez pas que ceux-ci retournent systématiquement un code d'erreur (ou de réussite), ainsi en les testant vous pourrez mettre le doigt plus précisément sur les comportements anormaux.
Utilisez la version debug. Cela vous permettra de déceler les comportements anormaux, les erreurs, ou encore les fuites mémoire de votre application. Voir https://jeux.developpez.com/faq/directx/?page=dgraphics_problemes#DGRAPHICS_PROBLEMES_debug.
Testez le reference rasterizer. Il vous indiquera si votre bogue provient d'une fonctionnalité non supportée. En effet, il s'agit d'un type de device supportant absolument toutes les fonctionnalités de Direct3D. Attention cependant à ne l'utiliser qu'à des fins de débogage : les fonctionnalités non supportées étant émulées en software, les performances seront très médiocres.
Pour utiliser le reference rasterizer dans votre application, il suffit de spécifier le flag D3DDEVTYPE_REF lors de la création du device.
Cherchez sur le net ! En recherchant sur les forums bien connus (Gamedev, Flipcode, Developpez…) ou de manière plus générale sur Google, on retrouve souvent les comportements anormaux que l'on constate, ainsi que la solution qui va avec. Pensez-y !
DirectX fournit depuis sa version 8 une version de débogage, capable de vous indiquer vos éventuelles erreurs et les comportements anormaux de votre application.
Pour l'activer, il faut se rendre dans le panneau de configuration de Windows, puis cliquer sur l'icône « DirectX ». Plusieurs onglets vous sont proposés, celui qui nous intéresse étant « Direct3D ». Puis, cochez le bouton « Use Debug version of Direct3D ». Le reste du panneau de configuration vous permet de paramétrer selon vos envies le niveau de débogage.
Les informations de débogage seront ensuite affichées dans la sortie debug de votre environnement de programmation. Si vous n'en possédez pas, vous pouvez également utiliser le programme DbMon (debugging monitor), qui récupère ces informations et les affiche dans une boîte de texte.
Dernière remarque, la version debug de DirectX est bien entendu beaucoup plus lente que la version retail, ne vous étonnez donc pas d'une perte de performance !
Dans une application plein écran, vous aurez sans doute remarqué qu'une perte du focus (causée la plupart du temps par un Alt+Tab de l'utilisateur, ou l'ouverture d'une fenêtre pop-up) entraîne un crash de celle-ci. Ainsi dans toute bonne application, il faut mettre sur pied un mécanisme pour gérer ces pertes de focus convenablement et éviter les plantages.
La perte du focus se traduit dans DirectGraphics par une perte du device. Une telle perte empêchera toute opération de rendu d'être effectuée, mais sans cependant retourner d'erreur. La seule exception est IDirect3DDevice9::Present, qui renverra D3DERR_DEVICELOST dans ce cas. On pourrait donc détecter la perte du device en testant le retour de Present, mais cela signifierait attendre d'avoir rendu toute la frame courante avant de détecter la perte du device, mais surtout, le retour de Present ne nous fournit pas suffisamment d'information.
La fonction IDirect3DDevice9::TestCooperativeLevel permet de récupérer l'état du device de manière plus précise :
- D3D_OK : le device est valide et l'application peut tourner normalement ;
- D3DERR_DEVICELOST : le device est perdu, mais ne peut pas encore être restauré ; il faut donc attendre ;
- D3DERR_DEVICENOTRESET : le device est perdu, mais peut cette fois être restauré.
Ainsi il est conseillé de l'appeler à chaque frame, avant de débuter le rendu, et d'effectuer les opérations nécessaires selon l'état du device.
Une fois D3DERR_DEVICENOTRESET renvoyé, on peut donc restaurer le device. Cela se fait via la fonction IDirect3DDevice9::Reset, qui va prendre en paramètre un D3DPRESENT_PARAMETERS de la même manière que lorsque vous avez créé votre device. Mais le reset du device nécessite que certaines ressources soient recréées (et donc détruites auparavant). Toute tentative d'appel à Reset sans avoir libéré toutes les ressources échouera.
Cela concerne :
- toutes les ressources (textures, surfaces, buffers…) créées dans le pool D3DPOOL_DEFAULT ;
- les éventuelles swap chains additionnelles ;
- les renderstates, qui auront repris leur valeur par défaut.
Cela concerne également certaines interfaces D3DX utilisant des ressources en interne :
- ID3DXFont ;
- ID3DXLine ;
- ID3DXRenderToEnvMap ;
- ID3DXRenderToSurface ;
- ID3DXSprite ;
- ID3DXEffect.
Cependant inutile de les détruire/recréer : il suffira d'appeler leur fonction membre OnLostDevice avant reset du device, et OnResetDevice après.
Pour avoir une liste à jour de ces interfaces, nous vous invitons à consulter la documentation du SDK.
Cela ne concerne donc pas les ressources créées dans les pools D3DPOOL_SYSTEMMEM et D3DPOOL_MANAGED, ainsi que les vertex/pixel shaders.
Voici un bout de code C++ résumant tout ce qui vient d'être dit :
// La boucle de rendu
void
MainLoop()
{
while
(AppIsRunning) // Tant que l'application tourne
{
// ... Traitement des messages Windows
if
(CheckDevice()) // Teste l'état du device
{
RenderScene(); // Si le device est OK on rend la scène, sinon on attend qu'il soit restauré
}
}
}
// Teste et renvoie l'état du device
bool
CheckDevice()
{
// On récupère l'état du device
HRESULT DeviceState =
Device->
TestCooperativeLevel();
switch
(DeviceState)
{
// Device OK
case
D3D_OK :
return
true
;
// Device perdu et pas encore récupérable : on attend...
case
D3DERR_DEVICELOST :
Sleep(100
);
return
false
;
// Device perdu mais récupérable
case
D3DERR_DEVICENOTRESET :
ReleaseResources(); // Libération des ressources à libérer
ResetDevice(); // Recréation du device
RestoreResources(); // Rechargement des ressources
return
false
;
}
}
Afin de ne pas s'embêter avec ces considérations (et potentiellement mal les gérer), il est cependant recommandé d'utiliser comme base de projet un sample du SDK, par exemple EmptyProject. Celui-ci comporte tout le code nécessaire à la bonne gestion du device, vous n'aurez plus qu'à y insérer votre code de rendu.
On parle de fuite mémoire (memory leak) lorsqu'une ressource n'a pas été libérée proprement à la fin du programme. La mémoire associée étant inaccessible, si de nombreuses fuites interviennent cela peut très vite devenir un problème pour votre application.
DirectX (et de manière générale les objets COM) est particulièrement soumis à ce genre de fuite. Pour rappel, une ressource COM possède un compteur de référence interne qui est incrémenté à chaque fois que l'on obtient un pointeur sur la ressource. Lorsqu'on n'a plus besoin du pointeur, il faut systématiquement appeler la fonction Release, qui effectue une décrémentation du compteur. La ressource est automatiquement libérée lorsque le compteur atteint 0, ainsi si l'on oublie une seule fois un Release(), le compteur n'atteindra jamais 0 et la ressource ne sera jamais libérée.
Le debug de DirectX permet heureusement de détecter ces fuites et nous permet de les éradiquer facilement. Pour cela, il suffit d'activer le debug runtime (voir https://jeux.developpez.com/faq/directx/?page=dgraphics_problemes#DGRAPHICS_PROBLEMES_debug), et de regarder dans la fenêtre de sortie les identifiants des ressources qui n'ont pas été correctement libérées. Il est ensuite possible de cocher dans le panneau de configuration de Direct3D l'option « Break on alloc ID » afin que le débogueur s'arrête automatiquement sur l'allocation correspondant à l'identifiant spécifié.
Les identifiants proches de zéro correspondant généralement au device ou à l'objet d3d, qui ne peuvent être libérés correctement tant que d'autres ressources ne sont pas elles-mêmes libérées. Une bonne pratique est donc de commencer par régler le cas des identifiants les plus élevés.
Un autre procédé beaucoup plus simple et recommandé quoi qu'il arrive est d'utiliser (en C++) des pointeurs intelligents pour encapsuler vos ressources DirectX. Il existe par exemple la classe CComPtr, livrée avec Visual Studio (voir https://msdn.microsoft.com/en-us/library/aa266806%28v=vs.60%29.aspx ou encore http://www.codeproject.com/Articles/1458/A-COM-Smart-Pointer).
Ils n'existent tout simplement plus, depuis la version summer 2004 de DirectX 9. Les concepteurs ont jugé qu'il était trop difficile de maintenir ceux-ci à jour à chaque update, ainsi ils recommandent maintenant de simplement utiliser comme base le sample « Empty Project » fourni avec le SDK.
Il est possible que lors d'une update ou après lecture d'un tutoriel pas tout neuf, vous rencontriez des erreurs de compilation sur certaines fonctions de DirectGraphics, vous indiquant que les paramètres fournis ne sont pas ceux qu'attend la fonction. Ce n'est malheureusement pas un bogue : en effet, certains prototypes de fonctions sont modifiés lors d'une mise à jour du SDK, « cassant » ainsi votre vieux code.
C'est par exemple le cas de la fonction D3DXCreateFont, dont le prototype a été modifié entre DirectX 8 et DirectX 9.
// Version DirectX 8
HRESULT WINAPI D3DXCreateFont
(
LPDIRECT3DDEVICE9 pDevice,
HFONT hFont,
LPD3DXFONT*
ppFont
);
// Version DirectX 9
HRESULT WINAPI D3DXCreateFont
(
LPDIRECT3DDEVICE9 pDevice,
INT Height,
UINT Width,
UINT Weight,
UINT MipLevels,
BOOL Italic,
DWORD CharSet,
DWORD OutputPrecision,
DWORD Quality,
DWORD PitchAndFamily,
LPCTSTR pFacename,
LPD3DXFONT*
ppFont
);
Si une telle erreur se produit, n'hésitez donc pas à consulter la documentation afin d'avoir le prototype correct pour votre version de DirectX.
Si tous vos appels aux fonctions Getxxx() du device échouent, c'est probablement que vous utilisez un pure device (créé avec le flag D3DCREATE_PUREDEVICE). En effet, comme stipulé dans la documentation ce flag permet d'effectuer des optimisations au niveau du stockage et de l'envoi des paramètres, et ceux-ci ne peuvent donc plus être récupérés via les fonctions Getxxx() du device. Cette restriction ne concerne que les états pouvant être stockés dans un bloc d'états (state block).
Pour désactiver la synchronisation verticale dans votre code, il suffit de modifier les paramètres du device de cette manière :
D3DPRESENT_PARAMETERS PresentParameters;
...
PresentParameters.PresentationInterval =
D3DPRESENT_INTERVAL_IMMEDIATE;
Pour plus de détails, vous pouvez consulter la page D3DPRESENT de la documentation du SDK.
À noter que la synchronisation verticale est également réglable dans les options de vos drivers, même si ceux-ci laissent généralement par défaut l'application la contrôler.