IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

La programmation avec OpenGL sous Windows

Date de publication : 11 janvier 2010


II. OpenGL sous Windows
II-A. Initialisation de OpenGL
II-B. Code complet
II-C. Le mode plein écran
II-C-1. Généralités
II-C-2. La fonction ChangeDisplaySettings
II-C-3. Code complet
II-D. Le double buffering
II-E. La boucle des messages revisitée


II. OpenGL sous Windows


II-A. Initialisation de OpenGL

Nous avons dit tout à l'heure qu'OpenGL ne gère pas l'affichage graphique. En effet, la manière d'accéder au périphérique d'affichage (bref, l'écran) peut varier d'un système à un autre, or OpenGL veut être totalement portable. Ce qu'il faut savoir, c'est que l'implémentation d'OpenGL sous Windows utilise la GDI pour l'affichage. Cependant, vous ne dessinerez pas directement sur un DC (comment est-ce qu'une API portable connaîtrait ce que c'est qu'un "DC", une notion purement Windows ...), mais sur un RC ou Rendering Context (bien que ce terme RC est spécifique à l'implémentation d'OpenGL sous Windows, cela illustre bien le fonctionnement d'OpenGL). Après que vous ayez fini de dessiner sur votre RC, vous copiez le tout sur votre DC et voilà ! Vous savez maintenant dessiner avec OpenGL.

Mais il y a une autre question : comment faire le "lien" entre un RC et un DC ? Cela se fait en trois étapes ultra simples : configurer le DC de façon à supporter OpenGL, créer un RC compatible avec ce DC et enfin, créer le lien proprement dit, et c'est fini !

Voici un exemple de procédure de fenêtre qui montre comment préparer sa fenêtre (plus précisément le DC de la fenêtre ...) à être utilisée comme sortie OpenGL.
int oglSetupDC(HDC hdc);

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HDC hDC; /* Notre DC */
    static HGLRC hRC; /* Notre RC */
    
    switch(message)
    {
    case WM_CREATE:
        hDC = GetDC(hwnd); /* Récupérons un handle de DC de la fenêtre */
        oglSetupDC(hDC); /* Configurons le DC de façon à supporter OpenGL */
        hRC = wglCreateContext(hDC); /* Créons un RC compatible avec notre DC */
        wglMakeCurrent(hDC, hRC); /* Connectons OpenGL à hRC et ce dernier à hDC */
        break;

    case WM_DESTROY:
        wglMakeCurrent(NULL, NULL); /* Privons OpenGL de périphérique de sortie */
        wglDeleteContext(hRC); /* Détruisons le RC */
        ReleaseDC(hwnd, hDC); /* Détruisons le DC */
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    
    return 0L;
}
Avec la fonction oglSetupDC :
int oglSetupDC(HDC hdc)
{
    PIXELFORMATDESCRIPTOR pfd;
    int iPixelFormat;

    ZeroMemory(&pfd, sizeof(pfd));
    pfd.nSize = sizeof(pfd);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 32;

    iPixelFormat = ChoosePixelFormat(hdc, &pfd);
    SetPixelFormat(hdc, iPixelFormat, &pfd);

    return iPixelFormat;
}
Tout d'abord on compose un idéal en remplissant convenablement une structure de type PIXELFORMATDESCRIPTOR dans lequel on spécifie notamment qu'on veut être en couleurs 32 bits RGBA (expliqué après). Ensuite on demande à Windows quel Pixel Format nous va le mieux et ensuite on applique ce pixel format à notre DC. Rien de vraiment compliqué.

Revenons maintenant aux couleurs RGBA. Si vous n'avez pas encore travaillé avec ce format de couleurs, sachez que la quatrième composante, A, est la composante qu'on appelle alpha. Ne vous souciez pas de cette composante pour l'instant, nous y reviendrons plus tard. Sachez tout simplement que si vous utilisez 8 bits pour chaque composante, alors vous aurez une profondeur de 32 bits/couleur.

Voilà, nous pouvons maintenant dessiner avec OpenGL. Nous allons par exemple peindre la zone cliente toute en noir. Pour cela, traitons le message WM_PAINT comme suit :
case WM_PAINT:
    BeginPaint(hwnd, &ps);
    glClear(GL_COLOR_BUFFER_BIT);
    glFlush();
    EndPaint(hwnd, &ps);
    break;
glClear(GL_COLOR_BUFFER_BIT) réinitialise à 0 (par défaut) tous les bits du color buffer, ce qui nous donnera un rendu tout en noir. glFlush force l'affichage du rendu, c'est-à-dire pour nous qui sommes sous Windows : copie tout ce qu'on a récemment dessiné avec OpenGL vers le DC du RC courant.

Il est également bien de noter que par défaut, il est permis d'utiliser la GDI pour dessiner sur un DC de fenêtre configuré pour OpenGL. Il existe par ailleurs un flag, PFD_SUPPORT_GDI, qui permet d'expliciter ce support de la GDI mais ce flag est en fait toujours inutile. Mixer de l'OpenGL et de la GDI cependant ne se fait pas sans précaution. Dans l'exemple ci-dessus par exemple, après le glFlush(), la zone cliente sera peinte tout en noir. Si vous avez donc utilisé la GDI pour dessiner avant cet appel, vous ne verrez pas votre dessin. En général, vous utiliserez donc la GDI, si vous en avez besoin, après votre dessin OpenGL (c'est-à-dire après glFlush()), rarement avant mais cela n'est pas une règle absolue. Pour résumer, si vous mixez de l'OpenGL et de la GDI, vous êtes donc seul responsable de la bonne organisation de votre code. En tout cas, vous n'utiliseriez la GDI pour dessiner directement sur l'écran que pour afficher du texte ou une image en premier plan. Sachez cependant que la GDI est nettement lente par rapport à OpenGL, mais nous ne sommes pas encore aux questions d'optimisation. Laissons cela pour plus tard.


II-B. Code complet

Voici le code complet d'une application initialisant et arrêtant correctement OpenGL.
#include <windows.h>
#include <gl/gl.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
int oglSetupDC(HDC hdc);
void oglDraw(void);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASS wc;
    HWND hWnd;
    MSG msg;
    RECT rect;
    LONG width = 640, height = 480;

    /* On veut une zone cliente comme ceci : */
    SetRect(&rect, 0, 0, width, height);
    OffsetRect(&rect, (GetSystemMetrics(SM_CXSCREEN) - width) / 2, (GetSystemMetrics(SM_CYSCREEN) - height) / 2);

    AdjustWindowRect(&rect, WS_BORDER | WS_CAPTION | WS_SYSMENU, FALSE);

    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;
    wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
    wc.hInstance      = hInstance;
    wc.lpfnWndProc    = WndProc;
    wc.lpszClassName  = "OpenGL Window Class";
    wc.lpszMenuName   = NULL;
    wc.style          = CS_HREDRAW | CS_VREDRAW;

    RegisterClass(&wc);

    hWnd = CreateWindow(
        "OpenGL Window Class", "OpenGL Window",
        WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
        rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
        NULL, NULL, hInstance, NULL
    );

    ShowWindow(hWnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HDC hDC;
    static HGLRC hRC;
    PAINTSTRUCT ps;

    switch(message)
    {
    case WM_CREATE:
        hDC = GetDC(hwnd);
        oglSetupDC(hDC);
        hRC = wglCreateContext(hDC);
        wglMakeCurrent(hDC, hRC);
        break;

    case WM_PAINT:
        BeginPaint(hwnd, &ps);
        oglDraw();
        glFlush();
        EndPaint(hwnd, &ps);
        break;

    case WM_DESTROY:
        wglMakeCurrent(NULL, NULL);
        wglDeleteContext(hRC);
        ReleaseDC(hwnd, hDC);
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }

    return 0L;
}

int oglSetupDC(HDC hdc)
{
    PIXELFORMATDESCRIPTOR pfd;
    int iPixelFormat;

    ZeroMemory(&pfd, sizeof(pfd));
    pfd.nSize = sizeof(pfd);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 32;

    iPixelFormat = ChoosePixelFormat(hdc, &pfd);
    SetPixelFormat(hdc, iPixelFormat, &pfd);

    return iPixelFormat;
}

void oglDraw(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
}
Il peut vous paraître bizarre qu'on ait créé une fonction - oglDraw - juste pour exécuter une instruction. En fait, c'est pour isoler dès maintenant le code dessin. Par la suite, c'est rare que allons écrire du code complet comme ci-dessus. Ce sera la plupart du temps juste cette fonction que nous modifierons.


II-C. Le mode plein écran


II-C-1. Généralités

Vous avez certainement remarqué que la plupart du temps, les applications multimédia interactives ne sexécutent pas dans une fenêtre avec bordures et barre de titre mais dans une fenêtre en plein écran. Comment créer une telle fenêtre ? Rien de plus simple : la fenêtre doit juste avoir le style WS_POPUP, les mêmes dimensions que l'écran et doit recouvrir tout l'écran. Seulement, lorsque vous développez disons un jeu par exemple, vous l'avez généralement conçu pour fonctionner avec une résolution bien déterminée (mais il ait également possible que vous ayez prévu plusieurs résolutions, mais c'est une autre histoire). De nombreux jeux utilisent la résolution 800 x 600, tout simplement parce que c'est une résolution suportée par la plupart des matériels d'affichage et que ça consomme moins de mémoire que 1024 x 768 par exemple. Ansi, si vous voulez exécuter votre application en mode plein écran, vous devez avant même de créer votre fenêtre, définir la résolution de l'écran à celle requise par votre programme. Si la résolution n'est pas supportée, la meilleure réaction est de demander à l'utilisateur s'il veut continuer l'exécution dans une fenêtre qui n'est pas en plein écran ou s'il veut quitter le programme.

Sous Windows, la fonction souvent utilisée pour modifier les paramètres d'affichage est ChangeDisplaySettings. Vous pouvez énumérer différentes configurations supportées à l'aide de EnumDisplaySettings mais nous n'allons pas parler de cette fonction dans ce document. Ce qu'il faut surtout retenir c'est que quand vous modifier les paramètres d'affichage, vous pouvez indiquer entre autres si ce changement est permanent ou si c'est juste requis par votre application, c'est-à-dire temporaire. Lorsque vous entrez dans un mode temporaire, le mode prend fin aussitôt que votre application s'est terminé. En résumé, le mode temporaire vous permet de ne pas écrire vous même le code permettant de revenir à la configuration originale à la fin de votre application.

Il existe un flag utilisable en argument de ChangeDisplaySettings permettant d'indiquer qu'on veut entrer un mode temporaire, c'est le flag CDS_FULLSCREEN. Comme vous pouvez le constater, le nom du flag est CDS_FULLSCREEN et non CDS_TEMPORARY, même si ce dernier aurait été plus descriptive. La raison est tout simplement que le mode temporaire est quasiment utilisée que par les applications s'exécutant en plein écran.


II-C-2. La fonction ChangeDisplaySettings

La fonction ChangeDisplaySettings permet de modifier les paramètres d'affichage du périphérique d'affichage par défaut. Ces paramètres sont décrits par une structure appelée DEVMODE. Les paramètres ajustables sont nombreux mais ceux qui nous intéresseront ici, ce sont la résolution (largeur x hauteur) de l'écran et la profondeur de couleur (nombre de bits par couleur) à utiliser. Des valeurs typiques sont 800 x 600 ou 1024 x 768, avec indifférement 16 ou 32 bits de profondeur de couleur.

Voici une fonction permettant de modifier simplement la résolution et la profondeur de couleur de l'affichage :
BOOL InitFullScreenMode(int width, int height, int color_bits)
{
    DEVMODE dm;

    dm.dmSize       = sizeof(dm); /* Obligatoire. */
    dm.dmPelsWidth  = width;
    dm.dmPelsHeight = height;
    dm.dmBitsPerPel = color_bits;
    /* Il faut ensuite préciser les champs que la fonction ChangeDisplaySettings doit considérer. */
    dm.dmFields     = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
    
    /* En cas de succès, ChangeDisplaySettings retourne DISP_CHANGE_SUCCESSFUL. */

    return ChangeDisplaySettings(&dm, CDS_FULLSCREEN) == DISP_CHANGE_SUCCESSFUL;
}

II-C-3. Code complet

Voici un exemple d'application capable de s'exécuter en mode plein écran :
#include <windows.h>
#include <gl/gl.h>

BOOL InitFullScreenMode(int width, int height, int color_bits);
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
int oglSetupDC(HDC hdc);
void oglDraw(void);
void gdiDraw(HWND hwnd, HDC hdc);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASS wc;
    HWND hWnd;
    MSG msg;
    RECT rect;
    LONG width = 800, height = 600;
    DWORD style = WS_POPUP;
    BOOL FullScreen = TRUE, ScreenOk = FALSE;
    int ret = 0;

    SetRect(&rect, 0, 0, width, height);

    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;
    wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
    wc.hInstance      = hInstance;
    wc.lpfnWndProc    = WndProc;
    wc.lpszClassName  = "OpenGL Window Class";
    wc.lpszMenuName   = NULL;
    wc.style          = CS_HREDRAW | CS_VREDRAW;

    RegisterClass(&wc);

    if (FullScreen)
    {
        ScreenOk = InitFullScreenMode(width, height, 32);
        if (!ScreenOk)
        {
            int reponse = MessageBox(
                NULL,
                "Impossible d'initialiser le mode plein écran.\n"
                "Voulez-vous exécuter le programme dans une fenêtre normale ?",
                "OpenGL",
                MB_YESNO | MB_ICONQUESTION
            );

            FullScreen = (reponse == IDNO);
        }
    }

    if (!FullScreen)
    {
        OffsetRect(&rect, (GetSystemMetrics(SM_CXSCREEN) - width) / 2, (GetSystemMetrics(SM_CYSCREEN) - height) / 2);
        style = WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
    }

    if (!FullScreen || ScreenOk)
    {
        if (!FullScreen)
            AdjustWindowRect(&rect, style, FALSE);

        hWnd = CreateWindow(
            "OpenGL Window Class", "OpenGL Window",
            style,
            rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
            NULL, NULL, hInstance, NULL
        );

        ShowWindow(hWnd, nCmdShow);

        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        ret = (int)msg.wParam;
    }

    return ret;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HDC hDC;
    static HGLRC hRC;
    PAINTSTRUCT ps;

    switch(message)
    {
    case WM_CREATE:
        hDC = GetDC(hwnd);
        oglSetupDC(hDC);
        hRC = wglCreateContext(hDC);
        wglMakeCurrent(hDC, hRC);
        break;

    case WM_PAINT:
        BeginPaint(hwnd, &ps);
        oglDraw();
        glFlush();
        gdiDraw(hwnd, hDC);
        EndPaint(hwnd, &ps);
        break;

    case WM_DESTROY:
        wglMakeCurrent(NULL, NULL);
        wglDeleteContext(hRC);
        ReleaseDC(hwnd, hDC);
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }

    return 0L;
}

int oglSetupDC(HDC hdc)
{
    PIXELFORMATDESCRIPTOR pfd;
    int iPixelFormat;

    ZeroMemory(&pfd, sizeof(pfd));
    pfd.nSize = sizeof(pfd);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 32;

    iPixelFormat = ChoosePixelFormat(hdc, &pfd);
    SetPixelFormat(hdc, iPixelFormat, &pfd);

    return iPixelFormat;
}

void oglDraw(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
}

void gdiDraw(HWND hwnd, HDC hdc)
{
    RECT r;
    COLORREF OldTextColor = SetTextColor(hdc, RGB(255, 255, 255));
    int OldBkMode = SetBkMode(hdc, TRANSPARENT);
    const char * lpszText = "Appuyez sur Alt + F4 pour fermer cette fenêtre.";
    int TextLength = strlen(lpszText);

    GetClientRect(hwnd, &r);
    DrawText(hdc, lpszText, TextLength, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

    SetTextColor(hdc, OldTextColor);
    SetBkMode(hdc, OldBkMode);
}

BOOL InitFullScreenMode(int width, int height, int color_bits)
{
    DEVMODE dm;

    dm.dmSize       = sizeof(dm);
    dm.dmPelsWidth  = width;
    dm.dmPelsHeight = height;
    dm.dmBitsPerPel = color_bits;
    dm.dmFields     = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;

    return ChangeDisplaySettings(&dm, CDS_FULLSCREEN) == DISP_CHANGE_SUCCESSFUL;
}

II-D. Le double buffering

Lorsque vous applez glFlush, le processus de transformation de la scène 3D que vous avez dessiné en image 2D sur l'écran va se lancer. Si votre scène est complexe, vous pourrez donc apprécier à l'écran les étapes de sa construction, ce qui est le plus souvent indésirable. On préfèrera donc la plupart du temps, au lieu d'appeler bêtement glFlush, demander à OpenGL de calculer la sortie en "background" et de laisser l'image à remplacer à l'écran tant que la nouvelle image n'est pas prête, et remplacer l'ancienne image par la nouvelle une fois cette dernière prête. Cela donne de très bons résultats.

Implémenter cette technique, qui s'appelle le double buffering, en OpenGL "pur" est cependant long et fastidieux. Comme nous sommes sous Windows, nous allons tout simplement utiliser SwapBuffers à la place de glFlush et le tour est joué. Cette fonction requiert en paramètre un handle de DC.

Enfin, sachez également que le double buffering n'est activé que si vous avez spécifié le flag PFD_DOUBLEBUFFER dans les flags de votre pfd et que ce flag est incompatible avec PFD_SUPPORT_GDI. Si vous voulez utiliser le double buffering tout en gardant la possibilité de dessiner directement à l'écran à l'aide de la GDI, vous n'avez qu'à créer deux DC : un DC OpenGL et un DC GDI uniquement.


II-E. La boucle des messages revisitée

Pour créer une scène animée, il suffit comme vous devez déjà le savoir d'actualiser plus ou moins régulièrement le dessin. Actualiser le dessin est différent de modifier la scène. Supposez que vous actualisiez votre dessin toutes les 40 millisecondes par exemple, c'est-à-dire que vous rendez 25 images (frames) par seconde, ce qui constitue déjà une animation fluide. Cela signifie que, que le dessin ait besoin d'être actualisé ou non, vous l'actualiserez quand même toutes les 40 ms. La modification de la scène, c'est la modification d'une ou plusieurs variables qui décrivent un objet de la scène par exemple. Cela est généralement une réponse à une entrée de lutilisateur (appui sur une touche du clavier pour déplacer un objet par exemple).

Dans une application critique tel qu'un jeu vidéo par exemple, il est hors de question de s'appuyer sur le message WM_TIMER pour créer l'animation. En effet, ce message est non seulement de faible priorité, mais de plus la précision du timer n'est pas très bonne (erreurs absolues possibles de l'ordre d'une dizaine de millisecondes). On ne devrait pas non plus utiliser GetMessage mais PeekMessage dans la boucle des messages, car GetMessage est blocante or on veut avoir la possibilité de dessiner à n'importe quel moment, toutes les 40 ms par exemple. Voici un exemple de boucle des messages adapté à une application critique, en utilisant quelques types, macros et fonctions à définir :
BOOL done = FALSE;
TIMEVAL t1 = get_time();
while (!done)
{
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        done = (msg.message == WM_QUIT);
        if (!done)
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    else
    {
        TIMEVAL t2 = get_time();
        if (t2 - t1 >= 40 ms)
        {
            rafraichissement_du_dessin();
        }
    }
}
Typiquement, vous implémentez la fonction get_time à l'aide de GetTickCount (faible précision), QueryPerformanceCounter (haute précision) ou directement une instruction de type RDSTC par exemple. Parfois, on veut ne pas limiter le FPS (le "frame per second"), c'est-à-dire qu'on veut actualiser le dessin chaque fois qu'on a l'occasion. On utilise ainsi un FPS maximum. C'est la technique que nous allons adopter dans ce tutoriel. Notre boucle des messages sera donc :
BOOL done = FALSE;
while (!done)
{
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        done = (msg.message == WM_QUIT);
        if (!done)
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    else
    {
        rafraichissement_du_dessin();
    }
}
Dans le cas d'une animation autonôme, c'est-à-dire une animation qui ne nécessite aucune intervention de l'utilisateur, ne rendez jamais la cadence de l'animation au FPS. En effet, si vous utilisez par exemple la technique du FPS maximum, votre animation tournera plus vite sur une machine musclée et tournera plus lentement sur une machine moins puissante. Séparez dans ce cas les codes de rafraîchissement de la scène et de rafraîchissement de la sortie. Par exemple :
BOOL done = FALSE;
TIMEVAL t1 = get_time();
while (!done)
{
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        done = (msg.message == WM_QUIT);
        if (!done)
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    else
    {
        TIMEVAL t2 = get_time();
        if (t2 - t1 >= 40 ms)
        {
            rafraichissement_de_la_scene();
        }
        rafraichissement_du_dessin();
    }
}
Si vraiment seule la fonction rafraichissement_de_la_scene peut modifier la scène, c'est-à-dire qu'il n'y a vraiment aucune intervention humaine possible, alors vous pouvez déplacer l'appel à rafraichissement_du_dessin immédiatement après l'appel à rafraichissement_de_la_scene.

 

Valid XHTML 1.0 TransitionalValid CSS!

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2010 Melem. Aucune reproduction, même partielle, ne peut être faite de ce site ni 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.