Texel
DirectX | Index | Contact

Introduction à Direct Draw
L'initialisation



Sources:     Ouvrir dans une fenêtre    Télécharger

Commençons par le commencement, c'est à dire … l'initialisation. Pour pouvoir suivre ce tutorial, vous devez absolument avoir le code source du programme sous les yeux. Vous pouvez le télécharger ci-dessus. Il a été écrit de telle façon qu'il puisse le plus possible être lu de bas en haut pour suivre, les une après les autres, chaques étapes présentées. Quand vous compilerez le programme, n'oubliez pas de linker la librairie ddraw.lib au projet.

Regardez le début du code source. Avant d'inclure les librairies windows et directdraw il ne faut pas oublier le #define INITGUID pour les interfaces COM. Le fichier "ddutil.h" inclus au début du programme n'est pas l'header d'une librairie mais du fichier cpp du même nom. Nous en parlerons un peu plus tard.

Comme vous le voyez, il y a pas mal de variables globales qui seront présenté lorsqu'elles seront utilisées. Des tableaux stockent des chaînes de caractères qui seront affichés en cas d'erreurs.





I. Préparation de l'affichage

   1) Accès à un objet DirectDraw

Comme vous pouvez le voir sur le code source, après la déclaration des variables nécessaires au compteur et le choix de la position initiale de la balle, l'initialisation se fait par l'appel d'une fonction du même nom dans la fonction WinMain. L'application est en plein écran mains nous sommes tout de même obliger de créer une fenêtre avant d'initialiser DirectDraw.
Passons directement à la fonction DirectDrawCreateEx .

Pour pouvoir utiliser DirectDraw, nous devons avoir accès à un objet DirectDraw qui dans notre cas à été déclaré en variable global sous le nom lpDD avec comme type LPDIRECTDRAW7. Cet accès se fait grâce à la fonction suivante :

HRESULT WINAPI DirectDrawCreateEx(
GUID FAR *lpGUID,
LPVOID *lplpDD,
REFIID iid,
IUnknown FAR *pUnkOuter
);










Le premier argument est un identificateur GUID pour le pilote d'affichage vidéo. Null permet d'avoir le pilote par défaut. Deux autres valeurs sont possibles :
    DDCREATE_EMULATIONONLY pour interdire l'utilisation des fonctions accélératrices des cartes graphiques.
    DDCREATE_HARDWAREONLY pour interdire l'émulation software des fonctions non accélérées par le matériel de l'utilisateur.

Le deuxième argument est l'adresse d'une variable recevant une interface IdirectDraw7 (lpDD pour nous).

Le troisième argument est le type d'interface à retourner. C'est donc IID_IDIRECTDRAW7 pour une interface DirectDraw7.

Le dernier argument doit rester à NULL. Il est réserver à de future versions de DirectDraw.

La fonction retourne une valeur de type HRESULT égale à DD_OK si tous se passent bien. Pour les autres valeurs : cf. doc.



   2) Niveau coopératif

Maintenant, nous devons définir le niveau coopératif de notre objet DirectDraw (lpDD). C'est à dire si notre application s'exécutera oui ou non en plein écran. Nous utilisons pour cela une méthode appelée IDIRECTDRAW7::SetCooperativeLevel :

HRESULT SetCooperativeLevel(
HWND hWnd, // handle de la fenêtre principale
DWORD dwFlags, // drapeau pour niveau coopératif
);







Le premier argument est l'handle de la fenêtre principale de l'application.

Le deuxième paramètre reçoit des drapeaux définissant le niveau coopératif. Plusieurs drapeaux peuvent être utilisés en même temps. Il faut alors les séparer par l'opérateur " | ". Voici les principaux drapeau disponibles (pour les autres, lisez la doc) :

   DDSCL_NORMAL : pour un comportement normal de l'application. Il est utilisé pour le mode pleine écran.
   DDSCL_EXCLUSIVE et DDSCL_FULLSCREEN sont à utiliser en même temps pour donner un accès exclusif à la carte graphique pour le mode pleine écran.
   DDSCL_ALLOWREBOOT permet l'utilisation de la combinaison de touche " Ctrl+ Alt+ Supp " par l'utilisateur pour arrêter l'application.



   Le mode pleine écran :

Pour ce mode, une seule méthode est nécessaire. IdirectDraw7::SetDisplayMode fixe la résolution de l'écran :

HRESULT SetDisplayMode(
DWORD dwWidth, // résolution horizontal en nombre de pixel
DWORD Height, // résolution vertical en nombre de pixel
DWORD bwBPP, // nombre de bits par pixel (couleurs)
DWORD dwRefreshRate, // taux de rafraîchissement
DWORD dwFlags // pour utiliser le mode 13h
);










Pour les deux premiers paramètres, n'oubliez pas d'utiliser des valeurs reconnus par toutes les cartes graphiques. Ca évitera les problèmes.

Le nombres de bits par pixels définissent le nombres de couleurs disponibles pour l'affichage. Si vous ne savez pas comment ça marche, représentez-vous l'écran comme une matrice dont chaques cases correspond à une pixel de l'écran. Chacune de ces cases contiennent un nombre qui définit la couleur de leur pixel. Le nombre de bits par pixels est le nombre de bits sur lequel est codé la couleur. Avec 16 bits par pixels, vous pouvez choisir la couleur de vos pixel parmis une gamme 2 puissance 16 couleurs.

Le quatrième argument est le taux de rafraîchissement de l'écran. Mettez le à 0 pour utiliser le taux par défaut. Sinon utilisez la fonction EnumDisplayModes (cf. doc SDK) pour connaître les taux disponibles sur la machine de l'utilisateur. Attention cette dernière fonction ne fonctionne pas sur toutes les configurations matérielles (dont la mienne J).

Le dernier paramètre reçoit un drapeau pour définir des options (pas indispensables) qui permettent par exemple d'utiliser le mode 13h. Mettez le à 0 sinon lisez la doc du SDK.


   Note sur le mode fenêtré:

Si vous ne trouvez pas de tutoriaux sur le mode fenêtré, renseignez-vous dans la documentation du SDK sur ces méthodes :

IDIRECTDRAW7 : :CreateClipper
IDIRECTDRAW7 : :SetHWND
IDIRECTDRAWSURFACE7 : :SetClipper

En gros, il faut pouvoir gérer les déplacements de notre fenêtre ainsi que celle des autres applications lancées à côté. Les deux premières méthodes remplacent SetDisplayMode et la dernière est appelée après la création de la surface primaire.



II. Création de la surface primaire et du BackBuffer

   1) Quelque explication avant de commencer

Avec DirectDraw, vous dessinez sur des surfaces. La surface primaire est la surface de l'écran visible. L'espace mémoire qui correspond à cette surface est situé dans la mémoire vive de la carte graphique.
Les surfaces secondaires sont des surfaces stockées dans la mémoire vive du PC. Elles ne sont pas visibles à l'écran.

Les surfaces secondaires sont très importantes. En effet, les disques durs sont lents par rapport à la mémoire vive. Charger une image depuis le disque dur pour l'afficher sur la surface primaire prend du temps et on ne peut pas ce permettre de le faire plusieurs dizaines de fois par secondes. Il est plus intelligent de charger les images dont on aura besoin dans une ou plusieurs surfaces secondaires, au début du programme. Lorsqu'il faut les utiliser, on les transfert très rapidement des surfaces secondaires à la surface primaire.

Malheureusement, un autre problème entre en jeu. Le programme peut mettre à jour la surface primaire pendant que le canon à électron en est au milieu de son balayage de l'écran. La partie déjà balayée ne sera pas remise à jours avant le prochain balayage et cela crée des scintillements désagréables à l'écran.

Pour résoudre le problème, il faut utiliser un troisième type de surface appelée back buffer. Ces surfaces sont des surfaces secondaires stockées dans la mémoire vive des cartes graphiques (comme les surfaces primaires mais invisibles).

Lorsque l'on veut remettre à jours l'affichage à l'écran, on transfère sur le back buffer de la carte graphique toutes les images à afficher depuis les différentes surfaces secondaires de la RAM du PC. Puis entre deux balayages de l'écran (lorsque le faisceau remonte pour recommencer) on inverse les pointeurs qui pointent vers la surface primaire et le back buffer. Ainsi, il n'y a pas de transfert d'une partie de l'image pendant qu'elle est dessinée à l écran. Cette méthode est appelée double buffering (avec deux back buffer on parle de triple buffering). Nous verrons un peu plus tard comment effectuer cette opération. Commençons d'abord par voir comment créer la surface primaire et le back buffer.


   2) Création des surfaces

Pour créer une surface, vous devez d'abord créer un objet de type LPDIRECTDRAWSURFACE7 qui recevra un pointeur vers une surface. Dans notre pong, quatre surfaces sont déclarées en variable global : la surface primaire (lpDDSPrimary), le back buffer (lpDDSBack), et deux surfaces secondaires dans lesquelles nous chargeons l'image de fond ( background_surf) et les images des éléments actifs (objet_surf pour le stick et la balle).

C'est la méthode IDIRECTDRAW7::CreateSurface qui affecte les pointeurs des surfaces :

HRESULT CreateSurface(
LPDDSURFACEDESC2 lpDDSurfaceDesc2,
LPDIRECTDRAWSURFACE7 FAR *lplpDDSurface,
IUnknown FAR *pUnkOuter
);










Le premier paramètre de cette méthode est l'adresse d'une structure de type DDSURFACEDESC2. Cette structure que vous devez remplir, contient les caractéristiques d'une surface à créer. Avant de remplir cette structure, vous devez initialiser à zéro tous ses champs pour évité certains bugs. Dans notre VRpong, quatre champs d'une telle structure sont remplis pour la surface primaire:
_ dwSize : Doit contenir la taille de la structure.
_ dwFlags : Indique quels champs de structure sont valides et doivent être pris en compte pour la création de la surface. Dans notre cas, nous avons utiliser deux drapeaux correspondant aux deux autres champs de la structure que nous avons remplis (mise à part dwSize).
_ ddsCaps.dwCaps : Dans ce champ, nous indiquons par des drapeaux que notre surface est primaire, quelle est complexe (car nous utilisons un back buffer) et que l'on peut basculer la surface primaire avec le back buffer.
_ dwBackBufferCount : Ce champ reçoit le nombre de back buffer que l'on veut créer.

Pour créer le back buffer on utilise une autre méthode qui s'applique à la surface primaire :

HRESULT GetAttachedSurface(
LPDDSCAPS2 lpDDSCaps,
LPDIRECTDRAWSURFACE7 FAR *lplpDDAttachedSurface
);







Son deuxième paramètre est l'adresse d'une surface (le back buffer pour nous).
Le premier argument est l'adresse d'une structure de type DDSCAPS2. Comme vous le voyez, seul le champs dwCaps de cette structure est remplie dans notre pong par un drapeau qui indique que la surface est un back buffer.



Conclusion:

L'initialisation de DirectDraw et la création de la surfaces primaire et du back buffer sont terminées. Les surfaces secondaires n'ont pas besoin d'être créer dans notre cas, car vous allez voir que nous utilisons une fonction qui le fait à notre place dans la fonction load_image.

Aller au deuxième chapitre: Du chargement des images aux rendus




Version originale: Mars 2001
Dernière mise à jour: Septembre 2002
Par Grégory Smialek

www.texel.fr.fm