Utilisation de DirectDraw
DirectDraw (DD), avec son cousin Direct3D (D3D), est la composante visible de DirectX. C'est généralement la première composante que les débutants apprennent à utiliser dans DirectX. DD a une fonction principale : vous permettre de contrôler votre matériel vidéo, ce qui est impossible sous Windows. Ou, du moins, vous ne pourriez pas le contrôler efficacement ni avec de bonnes performances.
Ce page vous permettra de vous familiariser avec la composante de DirectDraw contrôlant les ressources d'affichage : l'interface IDirectDraw7.
Création de l'objet IDirectDraw7
Toutes les interfaces DirectX sont utilisées via des pointeurs, et chaque objet possède un pointeur de type spécifique permettant de communiquer avec son interface. Dans le cas d'IDirectDraw7, ce type de pointeur est LPDIRECT-DRAW7. Dans un jeu ou une application, un seul de ces types est nécessaire (sauf si vous disposez d'un système multi-écrans, auquel cas vous pouvez en utiliser deux ou plus, mais les systèmes multi-écrans dépassent le cadre de cette page).
Par conséquent, lorsque vous utilisez DD, déclarez toujours une variable globale pointant vers une interface IDirectDraw7 :
- /* Pointeur IDirectDraw7 */
- LPDIRECTDRAW7 lpdd=NULL;
Et quelque part au début de votre initialisation (Prog_Init), créez votre objet en utilisant DirectDrawCreateEx :
- HRESULT WINAPI DirectDrawCreateEx(
- GUID FAR *lpGUID,
- LPVOID *lplpDD,
- REFIID iid,
- IUnknown FAR *pUnkOuter
- );
Cette fonction renvoie un HRESULT, étant une constante DD_OK ou DDERR_*. Les paramètres sont expliqués dans le tableau suivant :
| Paramètre DirectDrawCreateEx | Objectif |
|---|---|
| lpGUID | Un pointeur vers un GUID (identifiant unique global) qui identifie les pilotes d'affichage à utiliser avec l'objet IDirectDraw7. |
| lplpDD | Un pointeur vers votre pointeur vers une interface IDirectDraw7. Doit être typé en void**. |
| iid | Identifiant de type d'objet. Doit être défini sur IID_IDirectDraw7. |
| pUnkOuter | Agrégation COM. Utiliser NULL. |
Ces paramètres sont assez complexes, il est donc nécessaire de vous donner quelques explications. Un GUID (identifiant unique global) permet à Windows d'identifier tout. Votre carte vidéo en possède un, tout comme la plupart des autres composants matériels de votre machine. Un GUID permet d'identifier n'importe quel composant matériel grâce à un mécanisme de numérotation simple (enfin, pas vraiment simple). Vous n'aurez pas grand-chose à faire avec les GUID, et vous passerez NULL lorsqu'ils vous seront demandés.
Le paramètre iid a une fonction similaire à un GUID : c'est un identifiant de classe. Chaque objet COM (IDirectDraw7 inclus) possède un identifiant de classe ; pour les utiliser, vous devez avoir lié dxguid.lib à votre projet sous l'onglet Projet, Paramètres.
Laissez nous vous montrer le code de création de votre objet IDirectDraw7 :
- /* créer l'interface de dessin direct */
- HRESULT hr=DirectDrawCreateEx(NULL,(void**)&lpdd,IID_IDirectDraw7,NULL);
Habituellement, la règle est la suivante : si vous ne savez pas à quoi sert le paramètre ou quelle devrait être sa valeur, passez un NULL.
À propos de HRESULT
Généralement, lorsqu'un appel de fonction aboutit et réussit, la valeur DD_OK est obtenue. En cas d'échec, l'une des nombreuses constantes DDERR_* est obtenue, indiquant l'échec et la raison de l'échec. Pour tester cette condition, Microsoft a fourni une macro appelée FAILED. Pour vérifier les erreurs, procédez comme suit :
- /* Erreur de vérification */
- if(FAILED(hr)) {
- /* il y a eu une erreur */
- }
Définition du niveau coopératif
Après avoir créé votre objet IDirectDraw7, vous devez spécifier son mode d'utilisation. Deux options s'offrent à vous : fenêtré et plein écran.
Sélectionnez le mode d'utilisation d'IdirectDraw7 grâce à la fonction membre SetCooperativeLevel :
- HRESULT SetCooperativeLevel(
- HWND hWnd,
- DWORD dwFlags
- );
Comme toutes les autres fonctions DirectX, cette fonction renvoie une erreur ou une réussite dans un HRESULT. Les paramètres sont expliqués dans le tableau suivant :
| Paramètre SetCooperativeLevel | Objectif |
|---|---|
| hWnd | La fenêtre de niveau supérieur que DirectDraw doit utiliser. |
| dwFlags | Drapeaux de coopération |
Il n'existe qu'une poignée d'options que vous utiliserez fréquemment. Elles sont répertoriées dans le tableau suivant (La plupart des autres options concernent les systèmes multi-écrans et Direct3D) :
| Indicateur SetCooperativeLevel | Signification |
|---|---|
| DDSCL_ALLOWREBOOT | Permet à un utilisateur final d'utiliser Ctrl+Alt+Delete pendant une application plein écran. C'est indispensable si vous avez l'intention de créer des jeux compatibles Windows. |
| DDSCL_EXCLUSIVE | Spécifie que vous souhaitez un contrôle exclusif sur le matériel d'affichage. Vous devez également utiliser DDSCL_FULLSCREEN. |
| DDSCL_FULLSCREEN | Indique que vous souhaitez une application plein écran. Doit être utilisé avec DDSCL_EXCLUSIVE. |
| DDSCL_NORMAL | Indique que vous créez une application fenêtrée avec DirectDraw. Utile pour le débogage. |
La plupart du temps, vous souhaiterez créer des applications exclusives en plein écran, permettant d'utiliser la combinaison de touches Ctrl+Alt+Delete. Le code ressemblera donc à ceci :
- /* définir le niveau coopératif-plein écran-exclusif */
- hr=lpdd->SetCooperativeLevel(hWndMain,DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWREBOOT);
Maintenant que vous avez accès au plein écran, vous souhaiterez peut-être modifier le mode d'affichage. Deux options s'offrent à vous : choisir parmi les modes d'affichage courants jusqu'à ce que l'un d'eux fonctionne, ou jusqu'à ce qu'aucun ne fonctionne, auquel cas vous vous retrouverez dans une situation délicate. Ou encore, lister les modes d'affichage disponibles et choisir dans cette liste. Je préfère la deuxième méthode. Les essais et erreurs ne sont pas mon fort.
Énumération des modes d'affichage
L'énumération, quel que soit le type, est un peu déroutante au début. Nous allons simplement regrouper les modes d'affichage dans une liste claire que vous pourrez examiner plus tard dans le code. Commençons par examiner la fonction que vous allez appeler pour effectuer l'énumération, EnumDisplayModes :
- HRESULT EnumDisplayModes(
- DWORD dwFlags,
- LPDDSURFACEDESC2 lpDDSurfaceDesc2,
- LPVOID lpContext,
- LPDDENUMMODESCALLBACK2 lpEnumModesCallback
- );
Renvoie un HRESULT contenant une indication de réussite ou d'échec. Le tableau suivant détaille la liste des paramètres :
| Paramètre EnumDisplayModes | Objectif |
|---|---|
| dwFlags | Drapeaux spéciaux indiquant à DD quel type d'énumération vous souhaitez |
| lpDDSurfaceDesc2 | Une description du type de mode d'affichage que vous recherchez |
| lpContext | Une variable de contexte définie par l'utilisateur qui est transmise à la fonction de rappel |
| lpEnumModesCallback | Un pointeur vers la fonction de rappel |
Le paramètre dwFlags possède deux valeurs spéciales : DDEDM_REFRESHRATES, qui prend en compte la fréquence de rafraîchissement du mode d'affichage, et DDEDM_STANDARDVGAMODES, énumérant l'ancien mode d'affichage VGA 320x200x8. Vous n'utiliserez aucun de ces indicateurs; la valeur 0 sera toujours passée.
lpDDSurfaceDesc2 est un pointeur vers une structure DDSURFACEDESC2. En définissant certains membres d'une structure DDSURFACEDESC2, puis en appelant l'énumération, vous pourriez filtrer votre recherche. Pour l'instant, vous allez simplement lister tous les modes d'affichage.
Le dernier paramètre, lpEnumModesCallback, est une fonction de rappel définie par l'utilisateur, semblable à celle-ci :
- HRESULT WINAPI EnumModesCallback2(
- LPDDSURFACEDESC2 lpDDSurfaceDesc,
- LPVOID lpContext
- );
Cette fonction renvoie l'une des deux valeurs suivantes : DDENUMRET_OK pour poursuivre l'énumération, et DDENUMRET_CANCEL pour l'arrêter.
lpDDSurfaceDesc est un autre pointeur vers DDSURFACEDESC2, contenant des informations sur le mode d'affichage énuméré. lpContext contient la valeur initialement transmise lors de l'appel à EnumDisplayModes.
Une structure DDSURFACEDESC2 contient de nombreuses informations. Voici sa définition :
- typedef struct _DDSURFACEDESC2 {
- DWORD dwSize;
- DWORD dwFlags;
- DWORD dwHeight;
- DWORD dwWidth;
- union {
- LONG lPitch;
- DWORD dwLinearSize;
- } DUMMYUNIONNAMEN(1);
- DWORD dwBackBufferCount;
- union {
- DWORD dwMipMapCount;
- DWORD dwRefreshRate;
- } DUMMYUNIONNAMEN(2);
- DWORD dwAlphaBitDepth;
- DWORD dwReserved;
- LPVOID lpSurface;
- union {
- DDCOLORKEY ddckCKDestOverlay;
- DWORD dwEmptyFaceColor;
- } DUMMYUNIONNAMEN(3);
- DDCOLORKEY ddckCKDestBlt;
- DDCOLORKEY ddckCKSrcOverlay;
- DDCOLORKEY ddckCKSrcBlt;
- DDPIXELFORMAT ddpfPixelFormat;
- DDSCAPS2 ddsCaps;
- DWORD dwTextureStage;
- } DDSURFACEDESC2, FAR* LPDDSURFACEDESC2;
Comme vous pouvez le constater, les informations ici sont nombreuses, et vous n'en utiliserez qu'une fraction.
Les informations importantes pour un mode d'affichage sont trois éléments : la largeur, la hauteur et la profondeur de couleur. En bref, la profondeur de couleur indique le nombre de bits que contient chaque pixel. Elle a généralement une valeur de 8, 16, 24 ou 32. Au-delà de 8, les bits correspondent à une valeur RVB (rouge, vert, bleu) décrivant une couleur.
Dans DDSURFACEDESC2, vous pouvez voir les membres dwWidth et dwHeight. Ils correspondent à la taille du mode d'affichage (la résolution). Les valeurs courantes sont 640x480, 800x600 et 1024x768. Certaines cartes vidéo peuvent aller encore plus loin, et beaucoup proposent des modes d'affichage plus complexes, comme 400x300, 512x384,...
L'emplacement des bits par pixel n'est pas aussi évident dans la structure DDSURFACEDESC2, car elle fait partie du membre ddpfPixelFormat, lui-même une structure DDPIXELFORMAT.
- typedef struct _DDPIXELFORMAT{
- DWORD dwSize;
- DWORD dwFlags;
- DWORD dwFourCC;
- union {
- DWORD dwRGBBitCount;
- DWORD dwYUVBitCount;
- DWORD dwZBufferBitDepth;
- DWORD dwAlphaBitDepth;
- DWORD dwLuminanceBitCount;
- DWORD dwBumpBitCount;
- } DUMMYUNIONNAMEN(1);
- union {
- DWORD dwRBitMask;
- DWORD dwYBitMask;
- DWORD dwStencilBitDepth;
- DWORD dwLuminanceBitMask;
- DWORD dwBumpDuBitMask;
- } DUMMYUNIONNAMEN(2);
- union {
- DWORD dwGBitMask;
- DWORD dwUBitMask;
- DWORD dwZBitMask;
- DWORD dwBumpDvBitMask;
- } DUMMYUNIONNAMEN(3);
- union {
- DWORD dwBBitMask;
- DWORD dwVBitMask;
- DWORD dwStencilBitMask;
- DWORD dwBumpLuminanceBitMask;
- } DUMMYUNIONNAMEN(4);
- union {
- DWORD dwRGBAlphaBitMask;
- DWORD dwYUVAlphaBitMask;
- DWORD dwLuminanceAlphaBitMask;
- DWORD dwRGBZBitMask;
- DWORD dwYUVZBitMask;
- } DUMMYUNIONNAMEN(5);
- } DDPIXELFORMAT, FAR* LPDDPIXELFORMAT;
Il s'agit d'une autre structure volumineuse contenant beaucoup d'informations (la plupart sous forme d'unions). Voici votre premier aperçu de DDPIXELFORMAT. Le membre de DDPIXELFORMAT vous intéressant est dwRGBBitCount, contenant la résolution en bits du mode d'affichage.
Commençons donc l'énumération. Deux énumérations : la première compte les modes d'affichage, et la seconde les place dans une liste.
Définissez d'abord une structure contenant toutes les informations applicables à un mode d'affichage (du moins, en ce qui vous concerne) :
- struct DisplayMode {
- DWORD dwWidth;
- DWORD dwHeight;
- DWORD dwBPP;
- };
Bref et concis, comme il se doit. Ensuite, ajoutez deux variables globales :
- /* Le nombre de modes d'affichage sera conservé ici. */
- DWORD dwDisplayModeCount=0;
- /* Ceci pointera vers la liste des modes d'affichage */
- DisplayMode* DisplayModeList=NULL;
La première fonction d'énumération est assez simple, puisqu'elle compte simplement les modes d'affichage :
- HRESULT WINAPI EnumModesCallbackCount(
- LPDDSURFACEDESC2 lpDDSurfaceDesc,
- LPVOID lpContext
- )
- {
- /* incrémenter la variable de comptage */
- dwDisplayModeCount++;
- /* continuer l'énumération */
- return(DDENUMRET_OK);
- }
La deuxième énumération n'est pas beaucoup plus difficile :
- HRESULT WINAPI EnumModesCallbackList(
- LPDDSURFACEDESC2 lpDDSurfaceDesc,
- LPVOID lpContext
- )
- {
- /* copier les informations applicables dans la liste */
- DisplayModeList[dwDisplayModeCount].dwWidth=lpDDSurfaceDesc->dwWidth;
- DisplayModeList[dwDisplayModeCount].dwHeight=lpDDSurfaceDesc->dwHeight;
- DisplayModeList[dwDisplayModeCount].dwBPP=lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount;
- /* incrémenter la variable de comptage */
- dwDisplayModeCount++;
- /* continuer l'énumération */
- return(DDENUMRET_OK);
- }
Enfin, rassemblez le tout pour que le dénombrement ait lieu :
- // effacer le nombre de modes d'affichage
- dwDisplayModeCount=0;
- // modes d'affichage du nombre
- lpdd->EnumDisplayModes(0,NULL,NULL,EnumModesCallbackCount);
- // allouer de l'espace à la liste
- DisplayModeList=new DisplayMode[dwCount];
- // réinitialiser le compte
- dwDisplayModeCount=0;
- // lister les modes d'affichage
- lpdd->EnumDisplayModes(0,NULL, NULL,EnumModesCallbackList);
L'opérateur new remplit à peu près la même fonction que malloc, mais avec une sécurité de type plus élevée. L'équivalent de malloc serait :
Lorsque vous avez terminé avec la liste, utilisez le code suivant pour la désallouer :
- /* supprimer la liste des modes d'affichage */
- delete [] DisplayModeList;
- DisplayModeList=NULL;
Cela équivaut à utiliser la fonction free, normalement utilisée avec malloc. Vous disposez désormais de tous les modes d'affichage possibles dans une liste, que vous pouvez parcourir et tester pour trouver le mode souhaité. Vous pouvez également vérifier si un mode donné est pris en charge. Dans le cas contraire, vous pouvez opter pour un mode moins adapté.
Écrivons un code qui vérifie la présence d'un mode 800?600?16 (quasiment disponible).
- // Configuration du mode de test
- DisplayMode TestMode;
- TestMode.dwWidth=800;
- TestMode.dwHeight=600;
- TestMode.dwBPP=16;
- // notre variable de test booléenne
- bool found=false;
- // notre itérateur
- DWORD index;
- // où nous l'avons trouvé (tous les bits définis signifient qu'il n'a pas été trouvé)
- DWORD foundindex=0xFFFFFFFF;
- for(index=0;(index<dwDisplayModeCount) && (!found);index++) {
- if((DisplayModeList[index].dwWidth==TestMode.dwWidth) && (DisplayModeList[index].dwHeight==TestMode.dwHeight) && (DisplayModeList[index].dwBPP==TestMode.dwBPP)) {
- foundindex=index;
- found=true;
- }
- }
Vous pourriez effectuer une grande variété de tests sur la liste des modes d'affichage, allant de la recherche du mode le plus grand avec un certain BPP à la recherche du BPP le plus élevé pour un mode donné. Vous pourriez également laisser l'utilisateur final sélectionner le mode d'affichage qu'il souhaite utiliser et enregistrer cette valeur dans un fichier de configuration. Maintenant que vous connaissez les modes disponibles et celui que vous souhaitez, vous pouvez utiliser ces informations pour définir le mode d'affichage. (Difficile de croire que ce sujet ait nécessité plusieurs pages : le code s'exécute en une fraction de seconde.)
Définition du mode d'affichage
Après avoir énuméré les modes d'affichage, le paramétrage est simple. Il se fait avec la fonction membre SetDisplayMode de IDirectDraw7 :
- HRESULT SetDisplayMode(
- DWORD dwWidth,
- DWORD dwHeight,
- DWORD dwBPP,
- DWORD dwRefreshRate,
- DWORD dwFlags
- );
Cela renvoie à nouveau un HRESULT (vous devriez maintenant repérer un modèle), et les paramètres ressemblent étrangement aux membres de DisplayMode, à l'exception de dwRefreshRate (indifférent, passez donc 0) et dwFlags (indifférent également, passez donc 0). L'appel de cette fonction ressemble généralement à ceci :
- // Définir le mode d'affichage
- hr=lpdd->SetDisplayMode(800,600,16,0,0);
Bien sûr, les 800, 600 et 16 correspondent au mode d'affichage que vous souhaitez, ou aux variables contenant les valeurs que vous souhaitez.
Récupérer le mode d'affichage actuel
Il peut arriver que vous souhaitiez récupérer le mode d'affichage actuel. Pour cela, utilisez GetDisplayMode.
- HRESULT GetDisplayMode(
- LPDDSURFACEDESC2 lpDDSurfaceDesc2
- );
Regardez ! Elle renvoie un HRESULT ! (Ce sont toutes des HRESULT et elles sont toutes traitées exactement de la même manière.)
Le seul paramètre de cette fonction est lpDDSurfaceDesc2, étant un pointeur vers une DDSURFACEDESC2. Déclarez une variable de DDSURFACEDESC2, nettoyez-la et appelez la fonction. À son retour, votre DDSURFACEDESC2 contient les informations décrivant le mode d'affichage actuel (de la même manière que lorsque vous avez énuméré les modes d'affichage). Mais qu'entend-on par «nettoyer» une DDSURFACEDESC2 ? Eh bien, dans la plupart des cas, lorsque vous travaillez avec des DDSURFACEDESC2, ou toute autre structure DirectX, vous devez d'abord l'initialiser (mettre tous ses membres à 0) et définir le membre dwSize. Voici comment procéder :
Une fois nettoyé, il est prêt à être utilisé :
- //récupérer le mode d'affichage
- hr=lpdd->GetDisplayMode(&ddsd);
Tout comme dans la fonction d'énumération, la largeur et la hauteur du mode d'affichage sont entreposées dans ddsd.dwWidth et ddsd.dwHeight, et les bits par pixel sont stockés dans ddsd.ddpfPixelFormat.dwRGBBitCount.
Dernière chose : Libérer les objets
Il existe une méthode particulière pour supprimer presque tous les objets DirectX une fois que vous en avez terminé avec eux. Pour votre lpdd, voici à quoi cela ressemble :
- if(lpdd) {
- lpdd->Release();
- lpdd=NULL;
- }
Ce même extrait, avec une variable différente, sera utilisé pour la majeure partie du nettoyage de DirectX. Tout comme il était important, sous GDI, de supprimer votre objet et vos contrôleurs de domaine, il est également important de supprimer votre objet DirectX.