Une fenêtre OpenGL 4

Donald Urquhart

Depuis la version 3, OpenGL a subi beaucoup de modifications. Notamment, OpenGL 3 a introduit une nouvelle manière de programmer qui raidit légèrement la courbe d'apprentissage, mais cette version déprécie également un certain nombre de fonctions en se débarrassant du mode immédiat.

Commentez

- Titre : Une fenêtre OpenGL 4

- Auteur : Donald Urquhart

- Parution : 4 avril 2012

- Mise à jour : 28 octobre 2017

- Licence : Copyright ® 2010 Donald Urquhart. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Introduction

- III. Différences clés d'OpenGL 3 et suivants

- IV. Code

- IV-A. main.h

- IV-B. opengl_3.h

- IV-C. opengl_3.cpp

- IV-C-1. Constructeurs

- IV-C-2. Destructeur

- IV-C-3. Création de contexte

- IV-C-4. Gestion de la scène

- IV-D. main.cpp

- IV-D-1. Gestion des messages

- IV-D-2. Création de la fenêtre

- IV-D-3. Lancement de l'application

- V. Résultat

- VI. Remerciements

I - L'article original

Cet article est une adaptation en langue française de 1. OpenGL 4 Window, de Donald Urquhart.

II - Introduction

Bien que le titre parle d'OpenGL 4, on s'est limité jusqu'à présent à OpenGL 3 : ces deux versions sont très similaires, OpenGL 4 intégrant dans son cœur un certain nombre d'extensions.

Dans ce premier tutoriel, on va simplement créer une fenêtre attachée à un contexte OpenGL 3.2, ni plus, ni moins. Ces tutoriels seront écrits exclusivement pour Windows à l'aide de l'API Win32, le compilateur utilisé étant Visual C++.

III - Différences clés d'OpenGL 3 et suivants

Un contexte OpenGL 3 ou 4 requiert la création d'un contexte OpenGL standard (comme dans les versions 1 et 2), il faut alors spécifier que l'on souhaite un contexte OpenGL 4.

Sans le mode immédiat, OpenGL s'est orienté vers une architecture presque exclusivement tournée vers les shaders, ce qui donne un contrôle plus précis de ce qui se passe sous le capot, tout en améliorant grandement les performances.

En matière de dessin de formes, au lieu de définir l'état puis d'appeler les sommets un à un, OpenGL 3 et plus récents préfèrent utiliser des VBO (Vertex Buffer Objects) et VBA (Vertex Buffer Arrays). Les VBO faisaient partie des extensions d'OpenGL dès la version 1.5, ils en sont maintenant au cœur.

Les matrices sont les plus touchées par OpenGL 3. Simplement, OpenGL n'effectue plus d'opérations comme la définition d'une matrice de projection, de modèle ou de vue. Ainsi, des appels comme glRotate et glTranslate sont dépréciés et non recommandés. OpenGL veut que chacun gère ses propres matrices. Cela fera l'objet d'un prochain tutoriel, mais il existe une bibliothèque appelée GLM, prévue pour OpenGL et GLSL, qui va être d'une grande aide avec ces matrices.

IV - Code

Il est possible, pour ceux qui le souhaitent, de récupérer les fichiers de projet de ce tutoriel.

Rentrons enfin dans le vif du sujet, car ce tutoriel promet d'être long. On va, par la suite, tenter de s'approcher de l'orienté objet. Ce tutoriel va être très semblable aux autres tutoriels Win32/OpenGL, si ce n'est qu'on créera un contexte OpenGL 3.2 plutôt qu'un contexte traditionnel.

Ainsi, on ouvre Visual Studio et on crée une nouvelle application console Windows en C++. On s'assure que ce nouveau projet est bien vide et on y crée quatre fichiers : main.cpp, main.h, opengl_3.cpp et opengl_3.h. Ces quatre fichiers seront passés au crible en commençant par les en-têtes pour éviter de passer de l'un à l'autre sans cesse.

IV-A - main.h

Ce fichier contiendra tous les en-têtes pour le moment. On l'ouvre et colle le code ci-dessous. Il ne sera pas expliqué en détail, vu qu'il est assez simple : on se borne à inclure quelques en-têtes et lier quelques bibliothèques, la seule partie un peu intéressante étant #pragma once, qui n'est pas standard mais largement supporté ; cette commande du précompilateur demande de n'inclure qu'une seule fois le fichier pendant la compilation.

#pragma once

// Inclure les fonctions Windows
#include <Windows.h>

// Inclure les opérations d'entrée/sortie
#include <iostream>

// En-têtes et bibliothèques OpenGL et GLEW
#include <GL/glew.h>
#include <GL/wglew.h>
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "opengl32.lib")

Comme le montre ce code, on utilise encore GLEW, supportant les dernières extensions OpenGL 3 et 4.x. On recommande de l'utiliser, comme il facilite fortement le développement OpenGL sans devoir se soucier de déclarer les extensions soi-même.

IV-B - opengl_3.h

La première chose à faire est d'inclure le fichier d'en-tête principal. On ajoute donc la ligne suivante :

#include "main.h"

Une fois ceci fait, on a accès à GLEW et OpenGL, on peut alors créer une nouvelle classe, OpenGLContext, qui sera responsable de la création du contexte, de la gestion de la scène, de son rendu et, finalement, du nettoyage. On commence avec une classe vide comme celle-ci :

/**
OpenGLContext est une classe prévue pour stocker toutes les fonctions OpenGL et les garder
en dehors de la logique de l'application. On pourra créer un contexte OpenGL sur une fenêtre
donnée et y effectuer le rendu.
*/
class OpenGLContext {
public:

private:

protected:
};

Ensuite, on ajoute quelques variables protégées pour garder une trace de la fenêtre. Quand on travaille avec l'API Win32, chaque fenêtre a son propre HWND, l'identifiant. On doit aussi travailler avec deux contextes : HDC, le contexte matériel ; HGLRC, le contexte de rendu OpenGL. Si on crée à chaque fois une variable, on obtient :

class OpenGLContext {
...

protected:
HGLRC hrc; // Contexte de rendu
HDC hdc;   // Contexte matériel
HWND hwnd; // Identifiant de la fenêtre
};

Une fois les informations requises pour une fenêtre et un contexte récupérées, on doit garder en mémoire les dimensions de la fenêtre. Sinon, quand on va redimensionner la fenêtre, certaines parties ne seront pas affichées, un rendu sera effectué en dehors de la zone affichable : des parties de l'image seront découpées. On ajoute à cette fin deux variables privées.

class OpenGLContext {
...

private:
int windowWidth;  // Largeur de la fenêtre
int windowHeight; // Hauteur de la fenêtre

...
};

Finalement, on ajoute quelques méthodes pour contrôler l'objet OpenGLContext. On va travailler avec deux constructeurs différents et un destructeur, puis quatre autres méthodes pour le travail utile sur le contexte : create30Context(HWND), setupScene(void), reshapeWindow(int, int) et renderScene(void).

class OpenGLContext {
public:
OpenGLContext(void); // Constructeur par défaut
OpenGLContext(HWND hwnd); // Constructeur du contexte à partir d'un identifiant de fenêtre
~OpenGLContext(void); // Destructeur pour le nettoyage de l'application
bool create30Context(HWND hwnd); // Création d'un contexte OpenGL 3.x
void setupScene(void); // Définition de toutes les informations de scène
void reshapeWindow(int w, int h); // Nouvelles dimensions au redimensionnement
void renderScene(void); // Affichage de la scène

...
};

C'est tout ce dont on aura besoin dans cet en-tête pour le moment. On part à présent sur l'implémentation.

IV-C - opengl_3.cpp

Pour accéder aux méthodes de classe, on inclut opengl_3.h, évidemment : ce sera donc la première ligne de ce fichier.

#include "opengl_3.h"

IV-C-1 - Constructeurs

On implémente alors quelques méthodes de la classe, en commençant par les deux constructeurs et le destructeur. Pour le moment, le premier constructeur ne fait rien, on crée donc juste un constructeur vide. Le deuxième fera appel à la méthode de création de contexte.

/**
Constructeur par défaut de la classe OpenGLContext. À présent, il ne fait rien,
mais on peut y ajouter ce que l'on veut. */
OpenGLContext::OpenGLContext(void) {
}

/**
Constructeur de la classe OpenGLContext qui crée un contexte avec un HWND.
*/
OpenGLContext::OpenGLContext(HWND hwnd) {
create30Context(hwnd);
}

IV-C-2 - Destructeur

Pour le destructeur, on gérera la suppression du contexte de rendu et du contexte matériel. À cette fin, on utilisera wglMakeCurrent, wglDeleteContext et ReleaseDC.

/**
Destructeur de la classe OpenGLContext, qui va nettoyer le contexte de rendu et
libérer le contexte matériel de la fenêtre courante.
*/
OpenGLContext::~OpenGLContext(void) {
wglMakeCurrent(hdc, 0); // Enlever le contexte de rendu du contexte matériel
wglDeleteContext(hrc); // Supprimer le contexte de rendu

ReleaseDC(hwnd, hdc); // Libérer le contexte matériel de la fenêtre
}

IV-C-3 - Création de contexte

On poursuit avec la méthode create30Context. Cette méthode prend un identifiant de fenêtre et y crée des contextes valides de rendu et matériel. On crée une méthode vide à cette fin, définit le HWND pour l'objet et récupère un HDC valide. Cette méthode peut renvoyer un booléen, qui sera faux s'il n'est pas possible de créer les deux contextes. Par défaut, cependant, on retourne à un contexte OpenGL 2.1 s'il n'est pas possible de créer un contexte OpenGL 3.0.

/**
create30Context crée un contexte OpenGL et l'attache à la fenêtre pointée par le HWND.
Cette méthode crée, pour l'instant, un contexte OpenGL 3.2 par défaut, mais tentera
de créer un contexte OpenGL 2.1 si cela n'est pas possible.
*/
bool OpenGLContext::create30Context(HWND hwnd) {
this->hwnd = hwnd; // On définit le HWND de la fenêtre
hdc = GetDC(hwnd); // On récupère le contexte matériel pour la fenêtre
return true; // On a bien créé un contexte, on retourne vrai
}

La prochaine chose à faire est de créer une variable PIXELFORMATDESCRIPTOR ; elle aura la lourde tâche de déterminer le nombre de bits de profondeur et de couleur à utiliser, en même temps que fournir le double buffering et le support d'OpenGL. À cette fin, on crée d'abord une variable vide, pfd, pour laquelle on alloue un peu de mémoire. Ici, on donne trente-deux bits de couleur, pour les canaux rouge, vert, bleu et alpha ; vingt-quatre sans transparence ; chaque canal prend huit bits. Après avoir alloué cette variable, on en définit la taille (sizeof(PIXELFORMATDESCRIPTOR)). Ensuite, on définit les drapeaux (flags) nécessaires pour ce que l'on veut faire : double buffering, support d'OpenGL et dessin dans une fenêtre. Puis on définit le iPixelType pour des pixels RVBA, avant de définir le nombre de bits à accorder pour la couleur et les tampons de profondeur. Finalement, on définit le iLayerType à Main_Plane pour la fenêtre.

bool OpenGLContext::create30Context(HWND hwnd) {
this->hwnd = hwnd; // On définit le HWND de la fenêtre

hdc = GetDC(hwnd); // On récupère le contexte matériel pour la fenêtre

PIXELFORMATDESCRIPTOR pfd; // On crée un nouveau PIXELFORMATDESCRIPTOR (PFD)
memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); // que l'on efface
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); // On en définit la taille à la taille de la structure
pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW; // On active le double buffering, le support d'OpenGL et le dessin sur une fenêtre
pfd.iPixelType = PFD_TYPE_RGBA; // On utilise des pixels RVBA
pfd.cColorBits = 32; // Trente-deux bits d'informations de couleur (plus cette valeur est élevée, plus on a de couleurs)
pfd.cDepthBits = 32; // Trente-deux bits d'information de profondeur (plus cette valeur est élevée, plus on a de niveaux de détails)
pfd.iLayerType = PFD_MAIN_PLANE; // On définit le calque du PFD

return true; // On a bien créé un contexte, on retourne vrai
}

Subséquemment, on doit choisir un format de pixels pour le contexte matériel, en se basant sur le descripteur de formats de pixels. On appelle donc ChoosePixelFormat à laquelle on passe les valeurs hdc et pfd ; en retour, on récupère la valeur entière qui représente le format de pixels utilisé, que l'on peut alors utiliser pour définir le format de pixels du contexte matériel en utilisant SetPixelFormat.

int nPixelFormat = ChoosePixelFormat(hdc, &pfd); // Vérifie si le PFD est valide et renvoie un format de pixels
if (nPixelFormat == 0) // Si elle échoue
return false;

bool bResult = SetPixelFormat(hdc, nPixelFormat, &pfd); // On essaie de définir le format de pixels
if (!bResult) // Si elle échoue
return false;

return true; // On a bien créé un contexte, on retourne vrai

Maintenant, le format de pixels est défini. On continue et crée le contexte OpenGL. On crée donc un contexte compatible avec OpenGL 2.1 sur lequel on appelle une extension OpenGL 3.x, wglCreateContextAttribsARB, pour créer un contexte compatible avec OpenGL 3.x.

On crée un contexte OpenGL 2.1 pour une application Win32 OpenGL standard.

HGLRC tempOpenGLContext = wglCreateContext(hdc); // On crée un contexte OpenGL 2.1 pour le contexte matériel
wglMakeCurrent(hdc, tempOpenGLContext); // On rend ce contexte courant et actif

On va plus loin et on active GLEW pour utiliser l'extension de création de contextes OpenGL 3.x.

GLenum error = glewInit(); // On active GLEW
if (error != GLEW_OK) // Si cela échoue
return false;

Pour créer le contexte correctement, on doit définir une série d'attributs, dont les versions majeure et mineure d'OpenGL que l'on veut utiliser, puis un drapeau pour qu'il soit compatible avec les versions plus récentes.

int attributes[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, 3, // Version majeure d'OpenGL : 3
WGL_CONTEXT_MINOR_VERSION_ARB, 2, // Version mineure d'OpenGL : 2
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, // Compatibilité ascendante
0
};

Une fois la question des attributs réglée, on vérifie si l'extension de création de contexte OpenGL 3.x est disponible. Si oui, on crée un contexte OpenGL 3.2 ; si non, on reste sur le contexte OpenGL 2.1 créé plus haut. Pour passer au nouveau contexte après sa création, on doit s'assurer qu'aucun contexte n'est actif sur la fenêtre, supprimer l'ancien contexte et rendre courant le nouveau contexte OpenGL 3.2.

if (wglewIsSupported("WGL_ARB_create_context") == 1) { // Si l'extension de création de contexte OpenGL 3.x est disponible
hrc = wglCreateContextAttribsARB(hdc, NULL, attributes); // On crée un contexte OpenGL 3 grâce aux attributs donnés
wglMakeCurrent(NULL, NULL); // On supprime le contexte temporaire
wglDeleteContext(tempOpenGLContext); // On supprime le contexte OpenGL 2.1 temporaire
wglMakeCurrent(hdc, hrc); // On passe au contexte OpenGL 3.0
}
else {
hrc = tempOpenGLContext; // S'il n'y a pas de support d'OpenGL 3.x ou supérieur, on utilise le contexte OpenGL 2.1.
}

Finalement, pour vérifier qu'on a bien créé un contexte correctement, on affiche le numéro de version d'OpenGL sur la console.

int glVersion[2] = {-1, -1}; // Des valeurs par défaut pour la version
glGetIntegerv(GL_MAJOR_VERSION, &glVersion[0]); // On récupère la version majeure d'OpenGL utilisée
glGetIntegerv(GL_MINOR_VERSION, &glVersion[1]); // On récupère la version mineure d'OpenGL utilisée

std::cout << "Using OpenGL: " << glVersion[0] << "." << glVersion[1] << std::endl;
// On affiche la version d'OpenGL utilisée.

Sur Windows, il n'y aura pas de console pour une application Win32 ; par contre, on peut en afficher la sortie en ouvrant l'invite de commandes et en utilisant une commande comme program.exe > temp.txt. Les linuxiens seront probablement familiers de ce genre de commandes.

IV-C-4 - Gestion de la scène

Il reste maintenant trois méthodes supplémentaires à implémenter avant d'en avoir fini ; heureusement, elles sont très courtes.

La première est setupScene, qui ne prend pas de paramètres et ne fera que définir la valeur de couleur de fond d'OpenGL. On va utiliser un bleu de bleuet des champs, comme celui utilisé par XNA.

/**
setupScene contient tout ce qui doit être initialisé avant rendu.
*/
void OpenGLContext::setupScene(void) {
glClearColor(0.4f, 0.6f, 0.9f, 0.0f); // On définit la couleur à la valeur par défaut de XNA
}

Ensuite, la méthode reshapeWindow : elle prend deux entiers, un pour la hauteur, un pour la largeur de la fenêtre. Elle passe ensuite ces valeurs au contexte.

/**
reshapeWindow est appelée à chaque fois que la fenêtre est redimensionnée,
elle définit windowWidth et windowHeight, taille du viewport.
*/
void OpenGLContext::reshapeWindow(int w, int h) {
windowWidth = w; // On définit la largeur
windowHeight = h; // On définit la hauteur
}

Dernière méthode pour ce fichier, renderScene, qui ne prend pas d'argument. La première chose à faire est de définir la taille de la zone de dessin à celle de la fenêtre, ce que l'on fait avec glViewport. On vide les tampons de couleur, profondeur et pochoir, avant d'échanger les tampons avant et arrière pour dessiner.

/**
renderScene contient tout le code dessin.

La première chose à faire est de définir une zone d'affichage pour remplir
toute la fenêtre.

Ensuite, on vide les tampons COLOR, DEPTH et STENCIL pour éviter que les
affichages se superposent.

Tout le reste du code de rendu ira ici.

Finalement, on échange les tampons.
*/
void OpenGLContext::renderScene(void) {
glViewport(0, 0, windowWidth, windowHeight); // On définit la zone de dessin à la fenêtre
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // On vide les tampons.
SwapBuffers(hdc); // On échange les tampons pour afficher quelque chose.
}

Maintenant, on a tout en place pour un contexte simple OpenGL 3.2 ; on achève ainsi ce tutoriel avec le fichier main.cpp, qui va s'occuper de la création de la fenêtre.

IV-D - main.cpp

Pour ces tutoriels, on supposera travailler avec une seule fenêtre par application, on n'ira pas plus loin dans l'approche orientée objet pour la création des fenêtre. On crée donc la première fenêtre sans plus tarder.

La première chose à faire est d'inclure les fichiers d'en-têtes communs, main.h et opengl_3.h, on déclare alors quelques variables. On aura besoin d'une instance de l'objet OpenGLContext, d'un état booléen pour savoir si l'application est toujours lancée ou pas, ainsi que d'une HINSTANCE standard pour Win32. On déclare également une méthode WndProc, requise pour une application Windows.

#include "main.h"
#include "opengl_3.h"

OpenGLContext openglContext; // La classe de contexte

bool running = true; // L'application est-elle lancée ?

HINSTANCE hInstance; // La HINSTANCE de cette application
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Méthode de rappel standard

IV-D-1 - Gestion des messages

Pour compléter ce tutoriel, on aura besoin de trois méthodes. La première est requise pour toute application Windows et est responsable des méthodes de rappel de la fenêtre. Lors d'un redimensionnement, on appellera la méthode openglContext.reshapeWindow(). En voici la définition :

/**
WndProc est une méthode standard utilisée en programmation Win32 pour la gestion des messages à la fenêtre.
Ici, on gère le redimensionnement de la fenêtre en donnant au contexte la nouvelle taille.
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
return DefWindowProc(hWnd, message, wParam, lParam);
}

Pour gérer plusieurs appels à la fenêtre, on se tourne vers le paramètre de message dans la définition de la méthode. Il s'agit d'une variable de type UINT que l'on peut tester avec n'importe quelle variable WM_*. On s'occupera de WM_SIZE pour le redimensionnement et de WM_DESTROY pour la fermeture de la fenêtre. On utilise un simple bloc switch :

switch (message) {
case WM_SIZE: // En cas de redimensionnement
case WM_DESTROY: // En cas de fermeture
}

En ce qui concerne le redimensionnement on devra mettre à jour le contexte OpenGL pour qu'il connaisse la nouvelle taille de fenêtre. À cette fin, on récupère les nouvelles hauteur et largeur par LOWORD(lParam) et HIWORD(lParam), ce qui facilite grandement la tâche. La méthode devient donc :

RESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_SIZE: // En cas de redimensionnement
openglContext.reshapeWindow(LOWORD(lParam), HIWORD(lParam)); // Envoyer la nouvelle taille au contexte
break;
case WM_DESTROY: // En cas de fermeture
}

return DefWindowProc(hWnd, message, wParam, lParam);
}

Il ne reste plus qu'à gérer la fermeture. On appelle PostQuitMesssage pour sortir de l'application.

RESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_SIZE: // En cas de redimensionnement
openglContext.reshapeWindow(LOWORD(lParam), HIWORD(lParam)); // Envoyer la nouvelle taille au contexte
break;
case WM_DESTROY: // En cas de fermeture
PostQuitMessage(0);
break;
}

return DefWindowProc(hWnd, message, wParam, lParam);
}

IV-D-2 - Création de la fenêtre

Bien qu'on dispose à présent d'une fonction pour gérer tous les rappels, on doit avancer et créer une fenêtre. On crée donc une nouvelle méthode, createWindow, qui retournera un booléen déterminant si la fenêtre a bien été créée. Ses paramètres sont un LPCWSTR pour le titre et deux entiers (la largeur et la hauteur de la fenêtre).

bool createWindow(LPCWSTR title, int width, int height) {
return true;
}

Avec ce squelette, on doit définir quelques variables pour la fenêtre. On aura besoin d'une WNDCLASS, une HWND et une DWORD. windowClass, de type WNDCLASS, définira toutes les informations de base, telles que l'icône, le curseur, le type de dessin, l'instance du propriétaire de l'application, etc. La variable de type HWND, que l'on nommera hwnd, contiendra l'identifiant unique de la fenêtre créée. Finalement, dwExStyle, de type DWORD, définit le type de fenêtre, elle indiquera également qu'elle a une bordure.

On ne va pas entrer dans les détails des valeurs, vu que cela concerne Win32, pas OpenGL, voici donc simplement le code. On a ajouté une petite vérification rapide pour s'assurer que la variable windowClass est bien enregistrée :

bool createWindow(LPCWSTR title, int width, int height) {
WNDCLASS windowClass;
HWND hWnd;
DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;

hInstance = GetModuleHandle(NULL);

windowClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
windowClass.lpfnWndProc = (WNDPROC) WndProc;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hInstance = hInstance;
windowClass.hIcon = LoadIcon(NULL, IDI_WINLOGO);
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
windowClass.hbrBackground = NULL;
windowClass.lpszMenuName = NULL;
windowClass.lpszClassName = title;

if (!RegisterClass(&windowClass)) {
return false;
}

return true;
}

Ensuite, on crée la fenêtre avec la classe ci-dessus, puis on y crée le contexte OpenGL avant d'afficher la fenêtre et d'effectuer un rapide appel pour la mettre à jour.

bool createWindow(LPCWSTR title, int width, int height) {
...

hWnd = CreateWindowEx(dwExStyle, title, title, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, width, height, NULL, NULL, hInstance, NULL);

openglContext.create30Context(hWnd); // On crée le contexte OpenGL dans la fenêtre juste créée

ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);

return true;
}

Ceci devrait donner une fenêtre avec un contexte OpenGL 3 qui devrait être visible.

IV-D-3 - Lancement de l'application

Finalement, on crée une méthode WinMain qui sera utilisée pour lancer l'application. On crée une méthode WinMain vide que l'on remplira. On a laissé la variable MSG nommée msg juste parce qu'elle sera utilisée pour la valeur de retour.

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR    lpCmdLine,
int       nCmdShow) {
MSG msg;

return (int) msg.wParam;
}

Avant de remplir, voici un petit aperçu de ce qu'il va y avoir. Les premières lignes serviront à convertir une chaîne de caractères en un LPCWSTR, ce qui est malheureusement requis parce que la méthode CreateWindowEx ne supporte pas les tableaux de caractères. Les deux lignes d'après créeront la fenêtre et s'occuperont du contexte OpenGL. On terminera par une boucle qui continuera aussi longtemps que l'application vivra et sera responsable du rendu et de la vérification des messages envoyés à la fenêtre.

Tout d'abord, les premières lignes, qui convertissent le tableau.

/**
Les six lignes suivantes effectuent la conversion entre les tableaux de caractères
et les variables LPCWSTR utilisées par l'API Windows.
*/
char *orig = "OpenGL 3 Project"; // Le titre de la fenêtre
size_t origsize = strlen(orig) + 1;
const size_t newsize = 100;
size_t convertedChars = 0;
wchar_t wcstring[newsize];
mbstowcs_s(&convertedChars, wcstring, origsize, orig, _TRUNCATE);

Ensuite, on crée la fenêtre et prépare la scène OpenGL.

createWindow(wcstring, 500, 500); // On crée la fenêtre OpenGL
openglContext.setupScene(); // On prépare la scène OpenGL

Finalement, on peut lancer la boucle infinie. On utilisera l'appel à la fonction PeekMessage de l'API Win32 pour vérifier les appels à la fenêtre. Si on reçoit un message WM_QUIT, on déclare la variable booléenne running à la valeur faux. Sinon, on continue à traduire les messages et les expédier, ce qui est effectué dans la méthode de rappel. S'il n'y a pas de message à gérer, on appelle la méthode openglContext.renderScene avant de retourner.

while (running)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // S'il y a un message à gérer, on s'en occupe
if (msg.message == WM_QUIT) {
running = false; // Si on doit quitter, on met running à la valeur faux
}
else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else { // S'il n'y a pas de message
openglContext.renderScene(); // On affiche la scène (qui s'occupe aussi d'échanger les tampons)
}
}

V - Résultat

Tout ceci achevé, il est grand temps de prendre un peu de repos. Quelques jours ont été requis pour rédiger ce tutoriel, la base sur laquelle les suivants vont reposer. Les fichiers de projet Visual Studio 2010 de ce tutoriel sont disponibles au téléchargement.

Animation en format "youtube" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://www.youtube.com/v/Itgqb_GrRns?version=3&feature=player_detailpage

VI - Remerciements

Merci à Alexandre Laurent pour son aide à la traduction et à Maxime Gault pour sa relecture orthographique !