Accueil ⇒ Informatique ⇒ Bibliothèques ⇒ DirectX ⇒ Leçon 2 : DirectDraw

Leçon 2 : DirectDraw

Présentation

Nous allons tout d'abord passer en mode plein écran avec un fond noir, ce mode d'affichage correspondant plus à un jeu qu'une fenêtre windows. Nous allons donc remanier certains points de la fonction principale.

Plein écran

Pour passer en mode plein écran nous allons modifier certains points de la fonction principale. Tout d'abord, ajoutez '#include "ddraw.h"' en haut de votre fichier. Et oui, nous allons utiliser Direct Draw, le module de graphisme 2D de DirectX. Et vu que nous allons faire de la 2D il va falloir créer une fonction d'affichage. Nous la nommerons "InitDDObject" (DD comme Direct Draw) et nous la déclarerons ainsi :

InitDDObject(HWND hwnd);

Enfin, après avoir la fonction d'affichage, nous aurons besoin de l'objet Direct Draw, que nous nommerons lpDD et que nous déclarerons en variable globale ainsi :

LPDIRECTDRAWOBJECT lpDD;

Ce nom est de loin le plus simple à utiliser, mais nous aurions pu en trouver un autre. Désormais, il va falloir appeller la fonction d'affichage depuis la fonction principale. Pour une plus grande lisibilité de votre code source je vous conseille vivement de placer les fonctions de Direct Draw dans un fichier d'en-tête. Dans la fonction principale, après l'affichage de la fenêtre et sa remise à jour (ShowWindow() et UpdateWindow()), ajoutez la ligne suivante :

InitDDObject(hwnd);

Bon, tout est prêt, il ne reste plus qu'à écrire ladite fonction InitDDObject, ce que nous allons faire dans la partie suivante.

Affichage

Voici le code source, que je vous expliquerai par la suite.

bool InitDDObject(HWND hwnd)
{
    HRESULT ddrval = DirectDrawCreate(NULL, &lpDD, NULL); 
 
    if (ddrval != DD_OK)
        return false;
 
    ddrval = lpDD->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); 
 
    if (ddrval != DD_OK) {
        lpDD->Release();
        return false;
    }
 
    ddrval = lpDD->SetDisplayMode(640, 480, 8); 
 
    if (ddrval != DD_OK) {
        lpDD->Release();
        return false;
    }
 
    return true;
}

Tout d'abord, on initialise l'objet ddrval qui est utilisé pour contrôler le résultat des opérations. On vérifiera à chaque fois qu'il est bien égal à OK, et s'il ne l'est pas on quittera la fonction en détruisant lpDD à l'aidede la fonction Release(), que nous verrons plus tard. On suite on créé (et non pas initialise) l'objet Direct Draw lpDD. On vérifie ensuite le succès de l'opération (je ne le redis pas à chaque fois mais nous le ferons assez régulièrement). Puis on passe en mode exclusif, ce qui permet de passer en mode plein écran, avant de définir une résolution de 640*480 en couleurs 8 bits. Cette fonction nous permet donc d'avoir un écran noir, pour l'instant.

Surfaces

Avant d'aller plus loin il va nous falloir comprendre Direct Draw et sa manière de faire. Direct Draw n'est pas vraiment une SDK de graphismes mais plutôt un gestionnaire de la mémoire vidéo. Il permet la création ,depuis des octets, de surfaces. Une surface est un tableau 2D : une image, qui a des dimensions précises. Pourtant, Direct Draw n'agit pas que sur un type de surfaces, en réalité il en existe trois : les surfaces primaires, affichées à l'écran, les surfaces offscreen, invisibles et stockées en mémoire pour le traitement des images, et les surfaces complexes, un ensemble de surfaces traité comme une surface unique. Nous n'utiliserons pas les surfaces complexes durant ce cours. Mais un peu de code : pour créer une surface Direct Draw il faut la déclarer ainsi :

LPDIRECTDRAWSURFACE Surface;

Pour vous y retrouver vous pouvez appeller une surface lpDDSsurface par exemple, afin de savoir que c'est une surface de type DIRECT DRAW SURFACE. La fonction de création des surfaces est IDirectDraw::CreateSurface, que nous avons déjà utilisé lors de la création de l'objet lpDD->CreateSurface(...);.

Il est une seconde notion de Direct Draw qu'il vous faut connaître : le blitting. Le blitting est en quelque sorte la copie d'une surface par-dessus une autre surface (avec gestion de la transparence si nécessaire). Pour ce faire, il faudra utiliser des surfaces offscreen, auxquelles on affectera nos images, puis de les blitter, soit de les afficher, sur la surface primaire (l'écran). Le blitting évite certains effets de clignotement mais aussi de redimensionner des surfaces ou de faire des rotations etc.

Bon, nous allons pouvoir travailler sur du concret (virtuel) si vous avez compris les notions de surfaces et de blitting. Nous allons commencer par initialiser une surface DirectDraw ainsi :

LPDIRECTDRAWSURFACE Surface;

Ceci fait, nous allons utiliser une fonction chargée de créer la surface :

bool InitSurf()
{
    DDSURFACEDESC desc;
    HRESULT ddrval;
 
    ZeroMemory(&desc, sizeof(desc));
    desc.dwSize = sizeof(desc);
    desc.dwFlags = DDSD_CAPS;
    desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
 
    ddrval = lpDD->CreateSurface(&desc, &lpDDSPrim, NULL);
 
    if (ddrval != DD_OK)
        exit(0);
 
    return true;
}

Tout d'abord, on créé un descripteur de surface sur la surface que nous allons créer. Comme d'habitude, on créé ddrval pour contrôler le succès des opérations. Ensuite, ça se corse : on donne différentes propriétés au descripteur de la surface, soit des propriétés de la surface. Pour l'instant, nous n'allons pas nous encombrer de détails inutiles, contentez-vous de comprendre que l'on définit en mémoire les dimensions de la structure surface. Ensuite, on créé la surface à partir du descripteur desc que nous avons complété. Si les opérations se sont déroulées normalement, nous avons créé une surface. Reste à la remplir et à l'afficher.

Nous allons utiliser une fonction Affichage() chargée du raffraîchissement de la scène 2D. Pour l'instant, nous nosu contenterons d'afficher un écran rouge en remplissant la surface de rouge :

bool Affichage()
{
    DDBLTFX ddbltfx;
    ZeroMemory(&ddbltfx, sizeof(ddbltfx));
    ddbltfx.dwSize = sizeof(ddbltfx);
    ddbltfx.dwFillColor = RGB(255, 0, 0);
    Surface->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx);
    return true;
}

Cette fonction évoluera considérablement lors des prochains cours. Pour l'instant, on adapte la taille de la surface à la totalité de l'écran et en la remplissant de rouge. Ce point peut être intéressant : on affecte la couleur rouge à dwFillColor par une définition qui peut être utile : RGB(R,G,B) ou R représente la valeur de rouge de 0 à 255, G pour le Vert et B pour le Bleur. Ici donc, on met la totalité de rouge et rien de bleu ou de vert. Ensuite, on blitte la surface Surface (si vous l'avez appelée ainsi) sur l'écran. Mais la fonction Affichage() n'est pas encore intégré au programme. Pour cela, nous allons l'appeler dans la boucle d'écoute des messages lorsque le processeur est inactif (aucune commande). Donc, dans la fonction principale, modifiez la boucle d'écoute des messages ainsi :

while (true) {
    if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
        if (!GetMessage(&msg, NULL, 0, 0))
            break;
        DispatchMessage(&msg);
    } else
        Update();
}

Maintenant que nous sommes lancés dans les surfaces, vous devez savoir qu'après les avoir initialisées il faut absolument libérer la mémoire après utilisation. Donc, nous devons créer une fonction appelée à la sortie du programme qui se chargera de vérifier que les surfaces sont bien libérées et, si elles ne le sont pas, de les effacer. Voici comment nous pourrions écrire cette Sortie() :

void Sortie()
{
    if(lpDD != NULL) {
        if(lpDDSPrim != NULL) {
            lpDDSPrim->Release();
            lpDDSPrim = NULL;
        }
    }
 
    lpDD->Release();
    lpDD = NULL;
}

Mais nous devons appeler cette fonction à la sortie du programme, et le seul endroit où l'on peut y accèder est la boucle d'écoute des messages. Modifions là de cette manière :

long FAR PASCAL WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    switch(message) {
        case WM_DESTROY:
            PostQuitMessage(0);
            ExitDD();
            break;
        case WM_KEYDOWN:
            switch(wParam) {
                case VK_ESCAPE:
                    ExitDD();
                    PostQuitMessage(0);
                    break;
            }
            break;
        case WM_CLOSE:
            ExitDD();
            DestroyWindow(hwnd);
            return 0;
    }
 
    return DefWindowProc(hwnd,message,wParam,lParam);
}

Ainsi, à la sortie du programme on lance la fonction Sortie() afin de libérer les surfaces activées. Vous pouvez compiler votre programme. Désormais, nous avons un écran rouge, mais en surface, et il nous reste peu de choses à modifier avant d'afficher une image.

Vous pouvez consulter les sources du cours.