Skip to content

Votre première fenêtre Win32 [DirectX 11 - Partie 1]

Posted on:21 octobre 2016 at 02:00

Ceci est le début d’un ensemble d’articles concernant l’utilisation de DirectX 11 pour le rendu de jeux vidéo 3D ou même 2D.

Dans cet article en particulier nous allons voir les fondements de notre programme avec la création d’une fenêtre simple avec Win32. C’est dans celle-ci qu’évoluera pas la suite notre contexte Direct3D (qui correspond au module 3D de DirectX) et donc notre rendu.

Tout d’abord, nous allons voir en introduction à quoi correspond DirectX, ce qu’il permet de faire.

DirectX quésaco ?

Description

Description Wikipédia :

Microsoft DirectX est une collection de bibliothèques destinées à la programmation d’applications multimédia, plus particulièrement de jeux ou de programmes faisant intervenir de la vidéo, sur les plates-formes Microsoft (Xbox, systèmes d’exploitation Windows).

Souvent par abus de langage l’on cite DirectX pour le rendu graphique uniquement et parfois même comparé à OpenGL/Vulkan. Ceci est faux, car DirectX est un framework, c’est-à-dire un cadre de travail composé d’une multitude de module, chacun spécifique à un besoin.

Nous allons voir une liste des modules DirectX que vous pouvez utiliser, je ne citerais ici que les modules non obsolète, c’est-à-dire encore maintenu par Microsoft.

Je ne cite pas dans cette liste Direct Input, car même si très connu avec beaucoup de tutoriel le concernant il n’a pas été mis à jour depuis plusieurs années et Microsoft déconseille son utilisation. Nous verrons que Win32 nous propose déjà tout ce qu’il faut.

Installation

Pour commencer il nous faut un IDE (Environnement de développement) digne de ce nom. Personnellement je vais utiliser Visual Studio Community, qui est gratuit, très complet et propose une très bonne intégration de DirectX ainsi que de Win32.

Téléchargement de Visual Studio Community

Après de longues heures d’attente concernant l’installation de Visual Studio (cela peut dépendre de votre connexion) il vous faut maintenant le SDK DirectX contenant les DLL/Lib et .h de DirectX.

Comme je suis d’humeur sympathique voilà le lien : DirectX Software Development Kit

Hein ? Mais il date de 2010 ton Kit !
Alors oui, il faut savoir que depuis l’arrivée de Windows 8 et les applications de type “metro” et maintenant sur Windows 10 avec les applications Universelles, Microsoft ne met plus à jour le kit DirectX et intègre cela directement dans le SDK Windows. Malheureusement pour nous cela ne fonctionne pas pour les applications Win32, par conséquent nous devons installer l’ancien Kit. Cependant ne vous inquiétez pas, celui-ci ne contient juste pas DirectX 11.1/11.2 et DirectX 12 qui sont dédiés à Windows 8/8.1 et Windows 10. De plus avec le Kit vous retrouverez des tutoriels très spécifiques en anglais ainsi que des outils supplémentaires comme pour le mixage, la compression d’image.

Fondement du projet : système de fenêtrage

Création du projet sur Visual Studio

Nous allons devoir créer un projet du type “Win32 Project”. J’utilise personnellement Visual Studio Community en anglais, par conséquent la démarche que je vais vous décrire le sera de même. Cependant, la manipulation est la même, il suffit en général de traduire bêtement mot par mot.

Sur la page d’accueil de Visual Studio :

Configurer votre projet comme sur l’image ci-dessous : enter image description here

Le code

Maintenant il nous faut définir le point d’entrée de notre programme. Certains d’entre vous serez tenté de faire un fichier main.cpp. Et bien non, Win32 propose/impose une autre approche concernant le point d’entrée. Notre fichier se nommera donc WinMain.cpp. Je vous laisse créer le fichier puis glisser y ce code (je vais vous l’expliquer en détail par la suite).

/////////////////////////////INCLUDE/////////////////////////
#include <Windows.h>
#include <tchar.h>
#include <iostream>
/////////////////////////////////////////////////////////////

HWND hWnd;

LRESULT CALLBACK wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_KEYDOWN:
            if (wParam == VK_ESCAPE)
                PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
            break;
    }
}

bool InitWindow(HINSTANCE hInstance, int nCmdShow)
{
    //Window Size
    int windowHeight = 600;
    int windowWidth = 600;

    //Init Window
    WNDCLASSEX wndStruct;
    ZeroMemory(&wndStruct, sizeof(WNDCLASSEX));
    wndStruct.cbSize = sizeof(WNDCLASSEX);
    wndStruct.style = CS_HREDRAW | CS_VREDRAW;
    wndStruct.lpfnWndProc = (WNDPROC)wndProc;
    wndStruct.hInstance = hInstance;
    wndStruct.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndStruct.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wndStruct.lpszClassName = TEXT("Window");
    RegisterClassEx(&wndStruct);

    hWnd = CreateWindow(_T("Window"),
        _T("Nom de votre fenêtre"),
        WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        windowWidth,
        windowHeight,
        NULL,
        NULL,
        hInstance,
        NULL);

    if (!hWnd)
        return false;

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    return true;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    //Init Win32 Window
    if (!InitWindow(hInstance, nCmdShow))
        return 0;

    bool run = true;
    MSG msg = { 0 };

    while (WM_QUIT != msg.message && run)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            //DirectX Render
        }
    }

    return (int)msg.wParam;
}

Si vous compilez vous verrez apparaître une fenêtre Windows simple et redimensionnement. Bravo ! Voilà votre première fenêtre Win32 ! Maintenant je vais vous expliquer le code en détail, pour cela je vais le faire de façon analytique, c’est-à-dire de haut en bas.

Je ne vais pas détailler plus que cela les include. Je vais juste faire un petit point sur HWND hWnd;. Il correspond, en quelque sorte, à un lien avec notre fenêtre. Il est donc important d’y avoir accès simplement dans notre WinMain pour cela que je déclare donc ici.

wndProc

Cette fonction est très simple. Elle est appelée automatiquement lorsqu’un événement a lieu. C’est-à-dire lorsque Windows détecte la pression d’une touche du clavier par exemple. Je ne la détaille pas plus car nous nous en servirons plus tard dans notre programme. Lors de la gestion des entrées utilisateurs.

InitWindow

Comme son nom l’indique c’est ici que nous allons initialiser notre fenêtre et lui donner des paramètres qui vont régir son comportement par la suite. La fonction retourne true si tout s’est bien passé et false dans le cas contraire, jusqu’ici rien d’extraordinaire.

//Init Window
WNDCLASSEX wndStruct;
ZeroMemory(&wndStruct, sizeof(WNDCLASSEX));
wndStruct.cbSize = sizeof(WNDCLASSEX);
wndStruct.style = CS_HREDRAW | CS_VREDRAW;
wndStruct.lpfnWndProc = (WNDPROC)wndProc;
wndStruct.hInstance = hInstance;
wndStruct.hCursor = LoadCursor(NULL, IDC_ARROW);
wndStruct.hbrBackground = (HBRUSH)COLOR_WINDOW;
wndStruct.lpszClassName = TEXT("Window");
RegisterClassEx(&wndStruct);

WNDCLASSEX wndStruct;
Ceci est une structure qui contient les différents paramètres de notre fenêtre, ce qui régira son comportement par la suite. Le EX indique ici que nous utilisons la version étendue, de l’anglais “extended”. Cela permet de simplifier la structure et donc la création de notre fenêtre.

ZeroMemory(&wndStruct, sizeof(WNDCLASSEX));
Vous retrouverez régulièrement cette ligne de code. Elle permet simplement de mettre à NULL un espace de mémoire d’une certaine taille. Le premier paramètre est un pointeur vers l’objet et le second correspond à sa taille dans la mémoire.

wndStruct.cbSize = sizeof(WNDCLASSEX);
Rien d’extraordinaire, l’on donne juste la taille de notre structure WNDCLASSEX via un sizeof.

wndStruct.style = CS_HREDRAW | CS_VREDRAW;
Ici nous allons stocker le style de notre fenêtre. Vous retrouvez une liste complète ici. CS_HREDRAW permet d’indiquer à Windows qu’il doit réafficher le contenu de la fenêtre après un mouvement ou changement de taille de la fenêtre en largeur. De même pour CS_VREDRAW mais pour la hauteur cette fois-ci.

wndStruct.lpfnWndProc = (WNDPROC)wndProc;
Cette ligne de code indique que les événements Windows seront envoyés à notre fonction wndProc pour être ensuite traité par notre programme.

wndStruct.hInstance = hInstance;
Ici nous copions l’en-tête de notre application, notre instance.

wndStruct.hCursor = LoadCursor(NULL, IDC_ARROW);
Nous définissions un type pour représenter la souris de l’utilisateur. Dans le cas actuel nous donnons simplement la forme par défaut. Il faut savoir que cela s’applique uniquement lorsque la souris de l’utilisateur est dans la fenêtre. En dehors c’est le système qui s’en occupe pour nous.

wndStruct.hbrBackground = (HBRUSH)COLOR_WINDOW;
Nous définissons un background, une couleur de fond pour notre fenêtre, ici par défaut se sera blanc.

wndStruct.lpszClassName = TEXT(“Window”);
Ici nous donnons un ID à notre fenêtre, nous l’utiliserons plus tard dans notre programme. La ligne que nous allons voir après générera donc en quelque sorte une classe qui possédera le nom que nous avons indiqué.

RegisterClassEx(&wndStruct);
Comme le nom l’indique nous enregistrons donc une classe avec nos paramètres définis ultérieurement.

Maintenant nous allons devoir créer notre fenêtre pour qu’elle s’affiche, voilà le code correspondant.

hWnd = CreateWindow(_T("Window"), //Nom de la classe de la fenêtre, nous l'avons défini précédemment
    _T("Nom de votre fenêtre"), //Nom visible de la fenêtre
    WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_OVERLAPPEDWINDOW, //Style de la fenêtre
    CW_USEDEFAULT, //Position x de la fenêtre sur l'écran, ici je laisse Windows s'en charger
    CW_USEDEFAULT, //Position y de la fenêtre sur l'écran
    windowWidth, //Largeur de la fenêtre
    windowHeight, //Hauteur de la fenêtre
    NULL, //Fenêtre parent, ici il n'y en a pas
    NULL, //Ajout d'un menu, ici nous ne l'utilisons pas
    hInstance, //En-tête de l'instance de l’application
    NULL); //Si utilisation de plusieurs fenêtres

if (!hWnd)
    return false;

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

J’ai fait une description des paramètres dans le code, cela est plus simple et plus agréable pour vous. Je vais juste revenir sur le style de la fenêtre, cela permet de définir son comportement (redimensionnement ou non, les boutons accessibles comme la croix, plein écran, etc…), son style (bordure, etc…). Vous retrouverez une liste ici.

Par la suite nous indiquons simplement que nous souhaitons que la fenêtre s’affiche ShowWindow(hWnd, nCmdShow);, pour cela nous donnons simplement en paramètre les informations nécessaires.

####WinMain Maintenant nous allons voir le WinMain, c’est par ici que démarrera notre programme lorsqu’il sera exécuté. Les paramètres d’appel de la fonction sont automatiquement donnés par Windows, comme c’est le cas avec une main traditionnel.

//Init Win32 Window
if (!InitWindow(hInstance, nCmdShow))
    return 0;

bool run = true;
MSG msg = { 0 };

Pour commencer nous faisons appel à la fonction InitWindow que nous avons vue précédemment. Celle-ci va initialiser les paramètres de notre fenêtre et l’afficher. Si tous se passent bien nous déclarons un booléen qui pourra être modifiable dans le futur pour annoncer la fin du programme. De plus nous créons une variable du type MSG qui se chargera de récupérer les messages provenant de Windows.

while (WM_QUIT != msg.message && run)
{
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else
    {
        //DirectX Render
    }
}

return (int)msg.wParam;

Nous retrouvons ici une boucle qui s’exécutera indéfiniment durant l’exécution de notre jeu et de notre fenêtre. Nous indiquons aussi que l’action sur la croix de fermeture de la fenêtre ferme bien le programme. Dans cette boucle nous récupérons le message s’il y en existe un, le translate le traduit dans le bon format et le dispatch s’occupe de remonter l’événement à notre fonction wndProc.

Et bien voilà pour cette première partie ! Vous savez maintenant comment créer une fenêtre Win32 pour votre futur contexte Direct3D. Si vous compilez vous devez normalement avoir cela.

enter image description here

Merci d’avoir suivi cet article ! Je rappelle que le code source ainsi que l’article en lui-même est libre de droits, selon les indications ci-dessous.

Téléchargement du projet Visual Studio Community
Téléchargement du code source (sans projet)