Étude sur les performances d'affichage de la SDL 1.2

Détermination de la meilleure méthode pour afficher des sprites avec la SDL 1.2

Souvent, la SDL est décriée. Elle est lente, principalement, car le rendu est effectué en n'utilisant que le CPU et absolument pas le GPU. Cet article tente donc de faire le point sur les performances de la bibliothèque.

5 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

La bibliothèque Simple DirectMedia Layer est une bibliothèque incontournable pour les développeurs souhaitant démarrer dans la programmation d'un jeu vidéo. C'est une bibliothèque offrant tous les outils pour réaliser rapidement une application dynamique 2D et cela pour un grand nombre de plateformes (Windows, Mac OS, Linux, GP2X, DreamCast, Amiga…). Elle est simple à utiliser et est accessible à travers de nombreux de langages (C, C++, C#, Python…).

Toutefois, la version 1.2 vieillit et la version 2.0 vient d'arriver. Toutefois, revenons un peu sur l'ancienne version de la bibliothèque. Le défaut le plus cité est sa lenteur et l'impossibilité de gérer de grandes résolutions. En effet, la version 1.2 n'utilise pas le GPU, contrairement à la SFML, ou à Allegro 5. Mais cela n'est plus le cas grâce à la version 2.0 de la SDL.

Ayant moi-même utilisé la SDL pour de nombreux projets, je souhaitais revenir sur les performances de celle-ci, qui ne me semblaient pas si catastrophiques que cela. Voici donc, un article proposant une série de tests pour mesurer les performances de la bibliothèque et apprendre à mieux l'utiliser.

II. Conditions de test

II-A. Machine

Les tests sont effectués sur un PC portable équipé d'un processeur Intel i7 Q720, d'une NVIDIA GeForce GTS 360M et de huit gigaoctets de mémoire vive.

II-B. Logiciels

La machine fonctionne sous GNU/Linux, avec un noyau Linux 3.5.0. Les tests ont été compilés avec la version 1.2.15 de la SDL, la version 1.2.12 de SDL_image à l'aide de GCC 4.6.4 (avec les paramétrages d'optimisation -O3).

Les tests sont exécutés les un après les autres à l'aide d'un script python pour les résolutions suivantes :

  • 640x480 ;
  • 800x600 ;
  • 1024x768 ;
  • 1366x768 ;
  • 1900x1200.

III. Tests

Tous les tests sont effectués en suivant le même algorithme :

 
Sélectionnez
Initialisation de la SDL (et de SDL_image si besoin)
Création de la fenêtre (et chargement du sprite si besoin)
tant que le test n'est pas fini
    récupération des événements
    dessin
    copie du dessin à l'écran
    si une seconde s'est écoulée depuis la dernière mise à jour
        sauvegarde des compteurs d'images et de sprites
        réinitialisation des compteurs
fin
fermeture de la SDL
sauvegarde des résultats dans un fichier

Chaque test ne diffère que très peu. En effet, seuls la méthode de chargement du sprite (qui n'est pas prise en compte par le test) et les éléments affichés changent.

Les tests sont exécutés pendant 50 secondes. À chaque seconde, le nombre d'images affichées en une seconde et le nombre de sprites (pour les tests se basant sur les sprites) sont sauvegardés dans un tableau (afin de ne par perdre de temps avec les entrées/sorties d'un fichier). Lors de la sauvegarde, une moyenne des résultats récupérés dans le test est effectuée afin d'avoir un résultat facilement exploitable.

Ci-dessous, un explicatif détaillé de chaque test.

III-A. Test 1 - Événements

Ce test ne fait pratiquement rien. Il exécute la boucle principale à vide, sans même alterner les tampons d'affichage. Finalement, ce test n'exécute que le traitement des événements de la SDL.

III-A-1. Code

 
Sélectionnez
// Boucle principale 
while ( mustContinue && resultsCounter < NB_ITERATION ) 
{ 
    SDL_Event event; 
    SDL_PollEvent(&event); 

    // Détecte si on clique sur la croix 
    if( event.type == SDL_QUIT ) 
    { 
        // On quitte le programme 
        mustContinue = 0; 
    } 
    // Détecte si une touche est appuyée 
    else if( event.type == SDL_KEYDOWN ) 
    { 
        if ( event.key.keysym.sym == SDLK_ESCAPE ) 
        { 
            mustContinue = 0; 
        } 
    } 
    
    // Compteur de FPS
    nbFrames++; 
    if ( SDL_GetTicks()-lastUpdate > 1000 ) 
    { 
        results[resultsCounter].nbFrames = nbFrames; 
        nbFrames = 0; 
        lastUpdate+=1000; 
        
        resultsCounter++; 
    } 
} 

III-A-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 1 344 840 1 334 179 1 310 083 1 336 968 1 324 169

III-A-3. Réflexion

Il n'y a rien à conclure de ce test. Aucun affichage n'est effectué et ce test ne mesure que le temps pris par la SDL pour traiter les événements, ce qui est fait très rapidement vu qu'aucun événement ne se produit.

III-B. Test 2 - Échange des tampons

Ce test rajoute l'échange des tampons d'affichage au premier test.

III-B-1. Code

 
Sélectionnez
// Boucle principale 
while ( mustContinue && resultsCounter < NB_ITERATION ) 
{ 
    SDL_Event event; 
    SDL_PollEvent(&event); 

    // Détecte si on clique sur la croix 
    if( event.type == SDL_QUIT ) 
    { 
        //On quitte le programme 
        mustContinue = 0; 
    } 
    // Détecte si une touche est appuyée 
    else if( event.type == SDL_KEYDOWN ) 
    { 
        if ( event.key.keysym.sym == SDLK_ESCAPE ) 
        { 
            mustContinue = 0; 
        } 
    } 
    
    SDL_Flip(pWindowSurface); // Nouvelle ligne
    
    nbFrames++; 
    if ( SDL_GetTicks()-lastUpdate > 1000 ) 
    { 
        results[resultsCounter].nbFrames = nbFrames; 
        nbFrames = 0; 
        lastUpdate+=1000; 
        
        resultsCounter++; 
    } 
} 

III-B-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 1136 812 543 431 219

III-B-3. Réflexion

Il est remarquable que les performances pour un programme ne faisant qu'un échange des tampons d'affichage soient autant impactées. Sachant que la SDL travaille uniquement du côté CPU, cette perte de temps est due au transfert des tampons du CPU vers l'écran (GPU).

III-C. Test 3 - Nettoyage de l'écran

Ce test ajoute le nettoyage de l'écran. Généralement, une boucle de rendu commence par le nettoyage de l'écran.

III-C-1. Code

 
Sélectionnez
// Boucle principale 
while ( mustContinue && resultsCounter < NB_ITERATION ) 
{ 
    SDL_Event event; 
    SDL_PollEvent(&event); 
    Uint32 color = SDL_MapRGB(pWindowSurface->format,100,32,32); 

    // Détecte si on clique sur la croix 
    if( event.type == SDL_QUIT ) 
    { 
        //On quitte le programme 
        mustContinue = 0; 
    } 
    // Détecte si une touche est appuyée 
    else if( event.type == SDL_KEYDOWN ) 
    { 
        if ( event.key.keysym.sym == SDLK_ESCAPE ) 
        { 
            mustContinue = 0; 
        } 
    } 
    
    SDL_FillRect(pWindowSurface,NULL, color); // Nouvelle ligne
    SDL_Flip(pWindowSurface); 
    
    nbFrames++; 
    if ( SDL_GetTicks()-lastUpdate > 1000 ) 
    { 
        results[resultsCounter].nbFrames = nbFrames; 
        nbFrames = 0; 
        lastUpdate+=1000; 
        
        resultsCounter++; 
    } 
} 

III-C-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 770 552 351 271 128

III-C-3. Réflexion

Le nettoyage de la surface de l'écran est lui aussi couteux, pénalisant une nouvelle fois, l'application.

III-D. Test 4 - Nettoyage coloré de l'écran

Afin de mieux connaître l'impact du nettoyage de l'écran et de chercher à savoir si des optimisations sont effectuées, ce quatrième test change de couleur de fond pour chaque image.

III-D-1. Code

 
Sélectionnez
// Boucle principale 
while ( mustContinue && resultsCounter < NB_ITERATION ) 
{ 
    SDL_Event event; 
    SDL_PollEvent(&event); 
    // Détermination d'une couleur aléatoire
    Uint32 color = SDL_MapRGB(pWindowSurface->format,rand()%255,rand()%255,rand()%255); 

    // Détecte si on clique sur la croix 
    if( event.type == SDL_QUIT ) 
    { 
        //On quitte le programme 
        mustContinue = 0; 
    } 
    // Détecte si une touche est appuyée 
    else if( event.type == SDL_KEYDOWN ) 
    { 
        if ( event.key.keysym.sym == SDLK_ESCAPE ) 
        { 
            mustContinue = 0; 
        } 
    } 
    
    SDL_FillRect(pWindowSurface,NULL, color); 
    SDL_Flip(pWindowSurface); 
    
    nbFrames++; 
    if ( SDL_GetTicks()-lastUpdate > 1000 ) 
    { 
        results[resultsCounter].nbFrames = nbFrames; 
        nbFrames = 0; 
        lastUpdate+=1000; 
        
        resultsCounter++; 
    } 
} 

III-D-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 768 531 350 270 128

III-D-3. Réflexion

Les performances mesurées entre le test 3 et le test 4 sont proches, indiquant qu'il n'y a pas d'optimisation suivant la couleur de nettoyage de l'écran (nous pouvions imaginer des optimisations pour le cas du noir).

III-E. Test 5 - Rectangles

Ce cinquième test se rapproche de ce que l'on trouve dans les jeux vidéo, mais à la place d'avoir des sprites, seuls des carrés colorés sont dessinés. La dimension des blocs est de 20x20 pixels.

III-E-1. Code

Voici le code d'affichage des rectangles :

 
Sélectionnez
SDL_FillRect(pWindowSurface,NULL, color); 

nbSprites=0; 
for ( y = 0 ; y < HEIGHT ; y+=20 ) 
{ 
    for ( x = 0 ; x < WIDTH ; x+=20 ) 
    { 
        SDL_Rect spriteDest = { x,y,20,20 }; 
        SDL_FillRect(pWindowSurface,&spriteDest,color2); 
        nbSprites++; 
    } 
} 
SDL_Flip(pWindowSurface); 

III-E-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 519 371 225 188 83
Nombre de sprites par seconde 768 1200 2028 2691 5700

III-E-3. Réflexion

La SDL commence à montrer ses faiblesses avec la résolution 1900x1200. Alors que notre application est très simple et ne fait que dessiner des sprites uniformes, l'application est descendue en dessous de 100 FPS.

Dans un tel cas, afin de s'assurer de pouvoir afficher une application fluide avec une résolution 1900x1200, il sera conseillé de paralléliser l'application afin d'avoir un unique thread pour gérer l'affichage.

III-F. Test 6 - Sprite BMP

Enfin, nous affichons des sprites. Le sprite est chargé à partir d'un fichier BMP où chaque couleur est constituée de 24 bits, 8 pour chaque canal. Tout le sprite est affiché (49x98 pixels) mais seuls 20x20 seront visibles à chaque fois, car le reste est recouvert par les sprites suivants.

Pour les tests, il n'est pas très important de voir le sprite en entier sur l'image finale. De plus, cela permet de stresser la machine plus qu'il ne le faut, en affichant plus de sprites qu'il ne serait possible d'aligner sans superposer l'image d'origine.

III-F-1. Code

Fonction de chargement du sprite :

 
Sélectionnez
SDL_Surface* loadBMPSprite(const char* spritePath)
{
    SDL_Surface* pSurface = SDL_LoadBMP(spritePath);
    if ( pSurface == NULL )
    {
        fprintf(stderr,"Fail to load sprite '%s'\n",spritePath);
    }
    
    return pSurface;
}

L'affichage du sprite ne change pas du test précédent.

III-F-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 22 13 7 5 2
Nombre de sprites par seconde 768 1200 2028 2691 5700

III-F-3. Réflexion

Pour toutes les résolutions testées, notre application n'est pas fluide. Toutefois, l'application peut être améliorée.

III-G. Test 7 - Sprite BMP transparent

Comme le sprite BMP n'embarque pas de composante transparente, la couleur rose (RGB : 255, 0, 255) utilisée pour le fond, peut être indiquée à la SDL afin qu'elle ne soit finalement pas utilisée comme telle, mais comme une absence de couleur (donc vous ne verrez pas du rose à l'écran, mais la couleur en dessous de votre sprite pour tous les pixels roses).

III-G-1. Code

 
Sélectionnez
SDL_Surface* loadBMPSpriteColorKey(const char* spritePath) 
{ 
    SDL_Color transparencyColour = {255, 0, 255, 255}; 
    
    SDL_Surface* pSurface = SDL_LoadBMP(spritePath); 
    if ( pSurface != NULL ) 
    { 
        // Nous définissons une couleur de transparence
        Uint32 colorkey = SDL_MapRGB(pSurface->format, transparencyColour.r, transparencyColour.g, transparencyColour.b); 
        if ( SDL_SetColorKey(pSurface, SDL_SRCCOLORKEY, colorkey ) == -1 ) 
        { 
            fprintf(stderr,"Fail to set transparency in '%s'\n",spritePath); 
        } 
        else 
        { 
            fprintf(stderr,"Fail to optimise sprite '%s'\n",spritePath); 
        } 
    } 
    
    return pSurface; 
}

III-G-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 41 25 14 11 5
Nombre de sprites par seconde 768 1200 2028 2691 5700

III-G-3. Réflexion

Indirectement, les performances sont meilleures. Il est probable qu'en rajoutant la transparence et sa gestion, le format des couleurs du sprite se rapproche de la surface de l'écran. Ce n'est toutefois pas suffisant pour une application fluide.

III-H. Test 8 - Conversion de sprite

La SDL propose de convertir et d'optimiser les sprites avec la fonction SDL_DisplayFormat(). Le sprite est modifié afin d'être au même format que la surface destination (l'écran), évitant ainsi les conversions de format de couleur pendant le blit.

III-H-1. Code

 
Sélectionnez
SDL_Surface* loadBMPSpriteOptimised(const char* spritePath) 
{ 
    SDL_Color transparencyColour = {0, 0, 0, 0}; 
     
    SDL_Surface* pSurface = SDL_LoadBMP(spritePath); 
    if ( pSurface != NULL ) 
    { 
        // Nous optimisons la texture pour correspondre avec le format de l'écran 
        SDL_Surface* pOptimisedSurface = SDL_DisplayFormat(pSurface); 
        if ( pOptimisedSurface != NULL ) 
        { 
            // Nous libérons la mémoire associée à l'ancienne surface 
            SDL_FreeSurface(pSurface); 

            // Nous définissons une couleur de transparence 
            Uint32 colorkey = SDL_MapRGB(pOptimisedSurface->format, transparencyColour.r, transparencyColour.g, transparencyColour.b); 
            if ( SDL_SetColorKey(pOptimisedSurface, SDL_RLEACCEL, colorkey ) == -1 ) 
            { 
                fprintf(stderr,"Fail to set transparency in '%s'\n",spritePath); 
            } 

            // Nous remplaçons notre pointeur final par le pointeur sur la surface optimisée 
            pSurface = pOptimisedSurface; 
        } 
        else 
        { 
            fprintf(stderr,"Fail to optimise sprite '%s'\n",spritePath); 
        } 
    } 
    else 
    { 
        fprintf(stderr,"Fail to load sprite '%s'\n",spritePath); 
    } 
    
    return pSurface; 
}

III-H-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 98 61 39 29 14
Nombre de sprites par seconde 768 1200 2028 2691 5700

III-H-3. Réflexion

Le gain de performance n'est pas négligeable et cette amélioration permet d'avoir une application fluide même lors de l'affichage d'un millier de sprites.

III-I. Test 9 - Conversion de sprites transparents

Les deux méthodes utilisées dans le test 7 et le test 8 sont maintenant utilisées conjointement afin d'obtenir un sprite donnant un résultat correct (grâce à la couleur de transparence) tout en étant optimisé avec la fonction SDL_DisplayFormat.

III-I-1. Code

 
Sélectionnez
SDL_Surface* loadBMPSpriteColorKeyOptimised(const char* spritePath)
{
    SDL_Color transparencyColour = {255, 0, 255, 255};
    
    SDL_Surface* pSurface = SDL_LoadBMP(spritePath);
    if ( pSurface != NULL )
    {
        // Nous optimisons la texture pour correspondre avec le format de l'écran
        SDL_Surface* pOptimisedSurface = SDL_DisplayFormat(pSurface);
        if ( pOptimisedSurface != NULL )
        {
            // Nous libérons la mémoire associée à l'ancienne surface
            SDL_FreeSurface(pSurface);

            // Nous définissons une couleur de transparence
            Uint32 colorkey = SDL_MapRGB(pOptimisedSurface->format, transparencyColour.r, transparencyColour.g, transparencyColour.b);
            if ( SDL_SetColorKey(pOptimisedSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY, colorkey ) == -1 )
            {
                fprintf(stderr,"Fail to set transparency in '%s'\n",spritePath);
            }

            // Nous remplaçons notre pointeur final par le pointeur sur la surface optimisée
            pSurface = pOptimisedSurface;
        }
        else
        {
            fprintf(stderr,"Fail to optimise sprite '%s'\n",spritePath);
        }
    }
    else
    {
        fprintf(stderr,"Fail to load sprite '%s'\n",spritePath);
    }
    
    return pSurface;
}

III-I-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 116 93 50 41 22
Nombre de sprites par seconde 768 1200 2028 2691 5700

III-I-3. Réflexion

Finalement, pour la plupart des applications, nous retrouvons des performances raisonnables. Seule la résolution 1900x1200 résulte en une application peu fluide.

III-J. Test 10 - PNG transparent

La bibliothèque SDL_image est très pratique pour charger des fichiers images autres que des BMP. Dans ce test, nous chargeons un PNG possédant un canal transparent.

III-J-1. Code

Fonction de chargement basique d'un fichier PNG en utilisant SDL_image :

 
Sélectionnez
SDL_Surface* loadSDLImageSprite(const char* spritePath)
{
    SDL_Surface* pSprite = IMG_Load(spritePath);
    if ( !pSprite )
    {
        fprintf(stderr,"Fail to load sprite '%s' -> '%s'\n",spritePath,IMG_GetError());
    }
    
    return pSprite;
}

III-J-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 12 7 4 3 1
Nombre de sprites par seconde 768 1200 2028 2691 5700

III-J-3. Réflexion

L'utilisation d'un sprite PNG donne des résultats épouvantables. Même si la bibliothèque SDL_image est simple à utiliser et facilite le chargement de l'image, elle sera à éviter pour un jeu nécessitant de nombreux éléments actifs sur l'écran.

III-K. Test 11 - PNG transparent optimisé

Une question à poser et de savoir s'il est possible d'améliorer les performances de l'affichage d'une image chargée à partir d'un fichier PNG. Ce test essaie d'optimiser le sprite avec la fonction SDL_DisplayFormat().

III-K-1. Code

Fonction de chargement d'un fichier PNG avec optimisation du sprite :

 
Sélectionnez
SDL_Surface* loadSDLImageSpriteOptimised(const char* spritePath)
{
    SDL_Color transparencyColour = {0, 0, 0, 0};
    
    SDL_Surface* pSurface = IMG_Load(spritePath);
    if ( pSurface != NULL )
    {
        // Nous optimisons la texture pour correspondre avec le format de l'écran
        SDL_Surface* pOptimisedSurface = SDL_DisplayFormat(pSurface);
        if ( pOptimisedSurface != NULL )
        {
            // Nous libérons la mémoire associée à l'ancienne surface
            SDL_FreeSurface(pSurface);

            // Nous définissons une couleur de transparence
            Uint32 colorkey = SDL_MapRGB(pOptimisedSurface->format, transparencyColour.r, transparencyColour.g, transparencyColour.b);
            if ( SDL_SetColorKey(pOptimisedSurface, SDL_RLEACCEL, colorkey ) == -1 )
            {
                fprintf(stderr,"Fail to set transparency in '%s'\n",spritePath);
            }

            // Nous remplaçons notre pointeur final par le pointeur sur la surface optimisée
            pSurface = pOptimisedSurface;
        }
        else
        {
            fprintf(stderr,"Fail to optimise sprite '%s'\n",spritePath);
        }
    }
    else
    {
        fprintf(stderr,"Fail to load sprite '%s'\n",spritePath);
    }
    
    return pSurface;
}

III-K-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 99 58 38 29 14
Nombre de sprites par seconde 768 1200 2028 2691 5700

III-K-3. Réflexion

Même si l'utilisation du SDL_DisplayFormat() pour optimiser la surface apporte un plus, les performances restent basses.

III-L. Test 12 - JPG

Ce test est équivalent au dixième testTest 10, mais à la place de charger une image PNG, une image JPG est chargée.

III-L-1. Code

Le code de chargement et d'affichage est identique au dixième testTest 10. Seul le fichier d'images source change.

III-L-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 21 13 7 6 2
Nombre de sprites par seconde 768 1200 2028 2691 5700

III-L-3. Réflexion

Tout comme pour le PNG, l'utilisation des JPG, sans optimisation est un gouffre de performances ne permettant pas de réaliser une application fluide.

III-M. Test 13 - JPG optimisé

Ce test est équivalent au onzième testTest 11, mais à la place de charger une image PNG, une image JPG est chargée.

III-M-1. Code

Le code de chargement et d'affichage est identique au onzième testTest 11. Seul le fichier d'images source change.

III-M-2. Résultats

Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Nombre d'images par seconde 99 59 39 29 14
Nombre de sprites par seconde 768 1200 2028 2691 5700

III-M-3. Réflexion

On retrouve ici les mêmes performances qu'avec l'utilisation d'image PNG.

IV. Optimisations

Quelques optimisations sont possibles et il est donc nécessaire de voir leur impact sur les performances.

IV-A. Test 2 optimisé

La fonction SDL_SetVideoMode() possède un indicateur SDL_HWSURFACE permettant de créer les surfaces directement sur la mémoire vidéo.

IV-A-1. Code

 
Sélectionnez
// Créer la fenêtre 
pWindowSurface = SDL_SetVideoMode(WIDTH,HEIGHT,32,SDL_HWSURFACE | SDL_DOUBLEBUF); 
if ( pWindowSurface == NULL ) 
{ 
    fprintf(stderr,"Erreur lors de l'ouverture de la fenêtre\n"); 
    SDL_Quit(); 
    return -2; 
} 

IV-A-2. Résultats

  Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Test 2 Nombre d'images par seconde 1136 812 543 431 219
Test 2 optimisé 1135 811 552 431 219

IV-A-3. Impact

Sous Linux, le changement entre SDL_SWSURFACE et SDL_HWSURFACE est négligeable.

IV-A-4. Réflexion

Malheureusement, la création de surface dans la mémoire vidéo est rarement implémentée (comme ici, sous Linux où cela ne l'est pas) et l'est uniquement pour quelques plateformes. La GP2X, par exemple, bénéficie d'une accélération.

IV-B. Test 6 optimisé

Sachant que le test recouvre toute la surface et que l'on ne voit donc pas le fond, il est possible d'enlever la ligne de nettoyage de l'écran.

IV-B-1. Code

 
Sélectionnez
// Boucle principale 
while ( mustContinue && resultsCounter < NB_ITERATION ) 
{ 
    SDL_Event event; 
    SDL_PollEvent(&event); 
    
    unsigned int x = 0; 
    unsigned int y = 0; 

    // Détecte si on clique sur la croix 
    if( event.type == SDL_QUIT ) 
    { 
        //On quitte le programme 
        mustContinue = 0; 
    } 
    // Détecte si une touche est appuyée 
    else if( event.type == SDL_KEYDOWN ) 
    { 
        if ( event.key.keysym.sym == SDLK_ESCAPE ) 
        { 
            mustContinue = 0; 
        } 
    } 
    
    nbSprites=0; 
    for ( y = 0 ; y < HEIGHT ; y+=20 ) 
    { 
        for ( x = 0 ; x < WIDTH ; x+=20 ) 
        { 
            SDL_Rect spriteDest = { x,y,pSprite->w,pSprite->h }; 
            SDL_BlitSurface(pSprite,NULL,pWindowSurface,&spriteDest); 
            nbSprites++; 
        } 
    } 
    SDL_Flip(pWindowSurface); 
    
    nbFrames++; 
    if ( SDL_GetTicks()-lastUpdate > 1000 ) 
    { 
        results[resultsCounter].nbFrames = nbFrames; 
        results[resultsCounter].nbSprites = nbSprites; 
        nbFrames = 0; 
        nbSprites = 0; 
        lastUpdate+=1000; 
        
        resultsCounter++; 
    } 
} 

IV-B-2. Résultats

  Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Test 6 optimisé Nombre d'images par seconde 22 13 7 5 2
Nombre de sprites par seconde 768 1200 2028 2691 5700
Test 6 optimisé Nombre d'images par seconde 21 13 7 5 2
Nombre de sprites par seconde 768 1200 2028 2691 5700

IV-B-3. Impact

La suppression du nettoyage de l'écran n'affecte en rien les performances.

IV-C. Test 8 optimisé

Ce test reprend l'optimisation du test 6 et ne nettoie pas l'écran du fond.

IV-C-1. Code

 
Sélectionnez
// Boucle principale 
while ( mustContinue && resultsCounter < NB_ITERATION ) 
{ 
    SDL_Event event; 
    SDL_PollEvent(&event); 
    
    unsigned int x = 0; 
    unsigned int y = 0; 

    // Détecte si on clique sur la croix 
    if( event.type == SDL_QUIT ) 
    { 
        //On quitte le programme 
        mustContinue = 0; 
    } 
    // Détecte si une touche est appuyée 
    else if( event.type == SDL_KEYDOWN ) 
    { 
        if ( event.key.keysym.sym == SDLK_ESCAPE ) 
        { 
            mustContinue = 0; 
        } 
    } 
    
    nbSprites=0; 
    for ( y = 0 ; y < HEIGHT ; y+=20 ) 
    { 
        for ( x = 0 ; x < WIDTH ; x+=20 ) 
        { 
            SDL_Rect spriteDest = { x,y,pSprite->w,pSprite->h }; 
            SDL_BlitSurface(pSprite,NULL,pWindowSurface,&spriteDest); 
            nbSprites++; 
        } 
    } 
    SDL_Flip(pWindowSurface); 
    
    nbFrames++; 
    if ( SDL_GetTicks()-lastUpdate > 1000 ) 
    { 
        results[resultsCounter].nbFrames = nbFrames; 
        results[resultsCounter].nbSprites = nbSprites; 
        nbFrames = 0; 
        nbSprites = 0; 
        lastUpdate+=1000; 
        
        resultsCounter++; 
    } 
} 

IV-C-2. Résultats

  Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Test 8 Nombre d'images par seconde 98 61 39 29 14
Nombre de sprites par seconde 768 1200 2028 2691 5700
Test 8 optimisé Nombre d'images par seconde 99 63 39 30 15
Nombre de sprites par seconde 768 1200 2028 2691 5700

IV-C-3. Impact

La suppression du nettoyage de l'écran n'affecte en rien les performances.

V. Récapitulatif

  Résolution 640x480 800x600 1024x768 1366x768 1900x1200
Test 1 - ÉvénementsTest 1 Nombre d'images par seconde 1 344 840 1 334 179 1 310 083 1 336 968 1 324 169
Test 2 - Échange des tamponsTest 2 Nombre d'images par seconde 1136 812 543 431 219
Test 2 optimiséTest 2 optimisé Nombre d'images par seconde 1135 811 552 431 219
Test 3 - Nettoyage de l'écranTest 3 Nombre d'images par seconde 770 552 351 271 128
Test 4 - Nettoyage coloré de l'écranTest 4 Nombre d'images par seconde 768 531 350 270 128
Test 5 - RectanglesTest 5 Nombre d'images par seconde 519 371 225 188 83
Nombre de sprites par seconde 768 1200 2028 2691 5700
Test 6 - Sprite BMPTest 6 Nombre d'images par seconde 22 13 7 5 2
Nombre de sprites par seconde 768 1200 2028 2691 5700
Test 6 - optimiséTest 6 optimisé Nombre d'images par seconde 21 13 7 5 2
Nombre de sprites par seconde 768 1200 2028 2691 5700
Test 7 - Sprite BMP transparentTest 7 Nombre d'images par seconde 41 25 14 11 5
Nombre de sprites par seconde 768 1200 2028 2691 5700
Test 8 - Conversion de spritesTest 8 Nombre d'images par seconde 98 61 39 29 14
Nombre de sprites par seconde 768 1200 2028 2691 5700
Test 8 optimiséTest 8 optimisé Nombre d'images par seconde 99 63 39 30 15
Nombre de sprites par seconde 768 1200 2028 2691 5700
Test 9 - Conversion de sprites transparentsTest 9 Nombre d'images par seconde 116 93 50 41 22
Nombre de sprites par seconde 768 1200 2028 2691 5700
Test 10 - PNG transparentTest 10 Nombre d'images par seconde 12 7 4 3 1
Nombre de sprites par seconde 768 1200 2028 2691 5700
Test 11 - PNG transparent optimiséTest 11 Nombre d'images par seconde 99 58 38 29 14
Nombre de sprites par seconde 768 1200 2028 2691 5700
Test 12 - JPGTest 12 Nombre d'images par seconde 21 13 7 6 2
Nombre de sprites par seconde 768 1200 2028 2691 5700
Test 13 - JPG optimiséTest 13 Nombre d'images par seconde 99 59 39 29 14
Nombre de sprites par seconde 768 1200 2028 2691 5700

VI. Ce qu'il faut en retenir

À l'aide de ce test, il a été possible de déterminer l'impact de l'affichage des sprites et la nécessité de les optimiser à l'aide de la fonction SDL_DisplayFormat(). De plus, l'utilisation de SDL_image, pour charger des fichiers images autres que des BMP réduit considérablement les performances de l'application. Lorsque l'on utilise la SDL, il est clairement avantageux de garder les ressources au format BMP.

Afin que le programme final ne prenne pas une place extraordinaire sur le disque, il peut être utile de compresser les BMP et de les décompresser au lancement du jeu.

VII. Conclusion

La SDL n'est pas une si mauvaise bibliothèque que ce que l'on dit. Il est possible de faire un jeu avec, même dans des résolutions comme 1024x768. Les tests ont été effectués de façon à afficher plus de sprites qu'il n'en faut pour remplir l'écran, ce qui n'arrive que très rarement dans les jeux (même les shoot em up ne verront pas 5000 sprites affichés à l'écran, comme cela a été fait pour la résolution 1900x1200).

VIII. Que faut-il donc faire ?

La bibliothèque pousse à réfléchir aux solutions pouvant être mises en place pour garder un jeu 2D fluide. Par exemple, lorsque cela est possible, il pourrait être intéressant d'éviter d'afficher un sprite déjà à l'écran. Une autre idée sera de paralléliser le jeu afin de garder un thread uniquement pour l'affichage du jeu et le reste pour la gestion des éléments de ce jeu.

IX. Remerciements

Je tiens à remercier Kannagi pour m'avoir soufflé l'envie de faire ces expérimentations. De plus, je remercie Winjerome pour ses retours et ClaudeLELOUP pour ses corrections orthographiques.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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 © 2013 LittleWhite. 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.