2010-07-06 2 views
4

[nouvelles informations importantes disponibles ci-dessous au bas de cette entrée]Pourquoi mon jeu Win32 gdi + est-il inutilement lent sur Windows 7?

J'ai ce que je crois être une boucle de jeu très standard utilisant GDI +. Cela fonctionne raisonnablement bien (environ 25 fps pour un jeu complexe, 40 fps pour un jeu simple) sur Vista et XP. Quand je l'exécute sur Windows 7 (avec un processeur beaucoup plus rapide, et plus de mémoire), il ralentit tellement le jeu est inutilisable (je reçois n'importe où de 0 fps à 4 fps). J'ai inclus ci-dessous ce que je pense sont les parties pertinentes du code. Comme je le dis, je crois que ce est le type le plus simple de boucle de jeu (basé sur la mémoire-bitmap) en utilisant GDI +. Vous pouvez voir ci-dessous deux tentatives que j'ai faites pour accélérer les choses . D'abord, j'avais peur que si InvalidateRect() était appelé beaucoup plus fréquemment que les messages WM_PAINT étaient envoyés, le système considérait que mon programme était mauvais/lent et bloquait mes tranches de temps. J'ai donc ajouté le drapeau paintIsPending pour m'assurer que je n'ai pas invalidé plus d'une fois par peinture. Cela n'a donné aucune amélioration. Deuxièmement, j'ai ajouté le code dans la SECTION OPTIONNELLE ci-dessous, pensant que peut-être si je me suis déclenché le message WM_PAINT au lieu de en attendant qu'il soit envoyé les choses seraient mieux. Encore une fois, pas d'amélioration. Il me semble fou qu'une simple boucle de jeu GDI + comme celle-ci mourrait sur Windows 7. Je sais qu'il y a quelques différences dans la façon dont Windows 7 gère l'accélération graphique 2D, mais encore une fois ce code semble si basique, il est difficile de croire qu'il serait être non fonctionnel. Aussi, je sais que je peux passer à DirectX, et je peux le faire, mais actuellement il y a un montant important investi dans la base de code représentée par l'appel DrawGameStuff (graphiques) ci-dessous, et je préfère ne pas le réécrire si possible.

Merci pour toute aide.

#define CLIENT_WIDTH 320 
#define CLIENT_HEIGHT 480 

Graphics *graphics; 
HDC memoryDC; 
HBITMAP memoryBitmap; 
bool paintIsPending = false; 

void InitializeEngine(HDC screenDC) 
{ 
    memoryDC = CreateCompatibleDC(screenDC); 
    memoryBitmap = CreateCompatibleBitmap(screenDC, CLIENT_WIDTH, CLIENT_HEIGHT); 
    SelectObject(memoryDC, memoryBitmap); 
    graphics = new Graphics(memoryDC); 

    ... 
} 

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) 
{ 
    ... 
    InitializeEngine(GetWindowDC(hWnd)); 
    ... 
    myTimer = SetTimer(hWnd, timerID, 1000/60, NULL); 
    ... 
} 

void DrawScreen(HDC hdc) 
{ 
    graphics->Clear(Color(255, 200, 200, 255)); 

    DrawGameStuff(graphics); 

    BitBlt(hdc, 0, 0, CLIENT_WIDTH, CLIENT_HEIGHT, memoryDC, 0, 0, SRCCOPY); 
} 

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 
{ 
    ... 
    case WM_TIMER: 
     if (!paintIsPending) 
     { 
      paintIsPending = true; 
      InvalidateRect(hWnd, NULL, false); 
      /////// START OPTIONAL SECTION 
      UpdateWindow(hWnd); 
      ValidateRect(hWnd, NULL); 
      /////// END OPTIONAL SECTION 
     } 
     break; 
    case WM_PAINT: 
     hdc = BeginPaint(hWnd, &ps); 
     DrawScreen(hdc); 
     EndPaint(hWnd, &ps); 
     paintIsPending = false; 
     break; 
    ... 
} 

Aha! J'ai maintenant plus d'informations très pertinentes, basées sur un indice de Chris Becke. Je pensais avec certitude que c'était le BitBlt() qui était lent et pas les graphiques-> Clear(), mais voilà quand j'ai commenté les graphiques-> Clear() je reçois soudainement 40 FPS sur Windows 7. Alors je changé les graphismes-> Clear() en

// graphics->Clear(Color(255, 200, 200, 255)); 
SolidBrush brush(Color(255, 200, 200, 255)); 
graphics->FillRectangle(&brush, 0, 0, CLIENT_WIDTH, CLIENT_HEIGHT); 

et voilà, il fonctionnait encore à 40 FPS. Je ne sais pas pourquoi l'appel FillRectangle() est plus rapide que l'appel Clear().

Alors je commencé à ajouter mon code de dessin de jeu de retour, et je trouve immédiatement un autre appel qui tue: pour dessiner le contenu du jeu, je dessine les sprites dans le memoryDC en utilisant

graphics->DrawImage(myImageThatCameFromAPngFile, destx, desty, srcx, srcy, 
    width, height, UnitPixel); 

Et ces appels sont très lent aussi. À titre d'expérience, j'ai pré-étiré de mon fichier PNG dans un second memoryDC compatible avec le screenDC. Ensuite, pour dessiner mon sprite, je dessine de cette mémoire secondaireDC dans ma mémoire primaireDC. Ainsi, au lieu de l'appel DrawImage ci-dessus je:

BitBlt(memoryDC, destx, desty, width, height, secondaryMemoryDC, 
    srcx, srcy, SRCCOPY); 

et voilà tout le jeu tourne à 40 FPS sur Windows 7 quand je fais ça.

Cependant, ce n'est pas une vraie solution car dans le pré-rendu à cette mémoire secondaire DC je perds les informations de transparence du fichier PNG, donc mes sprites sont tous opaques et moche maintenant.

Il semble donc que mon problème est une incompatibilité entre le memoryDC (créé pour être compatible avec le screenDC) et le fichier source PNG. Je ne comprends pas pourquoi cette incompatibilité existe (ou du moins, pourquoi il ralentit tellement les choses) que sur Windows 7. Y a-t-il un moyen d'enregistrer le fichier PNG pour être compatible avec l'écran dès le départ? Ou de le re-rendre en interne au départ pour obtenir un nouveau fichier PNG compatible avec l'écran? Hmmm ....

Ok donc j'ai pu obtenir mon fichier PNG rendu correctement en le rendant à un HBITMAP 32bpp comme suit:

HDC hdc = CreateCompatibleDC(GetWindowDC(hWnd)); 
    Bitmap *bitmap = new Bitmap(image->GetWidth(), 
     image->GetHeight(), PixelFormat32bppARGB); 
    HBITMAP hbitmap; 
    bitmap->GetHBITMAP(Color(0, 0, 0, 0), &hbitmap); 
    SelectObject(hdc, hbitmap); 

    Graphics *g = new Graphics(hdc); 
    g->DrawImage(pngImage, 0, 0, 0, 0, 
     pngImage->GetWidth(), pngImage->GetHeight(), UnitPixel); 

puis rendu avec AlphaBlend():

_BLENDFUNCTION bf; 
    bf.BlendOp = AC_SRC_OVER; 
    bf.BlendFlags = 0; 
    bf.SourceConstantAlpha = 255; 
    bf.AlphaFormat = AC_SRC_ALPHA; 
    AlphaBlend(memoryDC, destx, desty, width, height, hdc, 
      destx, desty, width, height, bf); 

Alors maintenant, j'ai mon jeu rapidement opérationnel sur Windows 7.

Mais je ne comprends toujours pas pourquoi je devais passer par tous ceci pour Windows 7. Pourquoi le dessin par défaut de mon image PNG utilisant DrawImage() est-il si lent sur Windows 7?

+1

Avez-vous effectué un profilage simple pour voir quelle partie de la boucle est lente? Mon instinct me dit que «BitBlt» est probablement le temps, ce qui pourrait être dû à votre carte vidéo et pas Win7 en soi. –

+0

Oui, cela peut être un problème de carte vidéo, mais cela se produit sur trois machines Windows 7 différentes et aucune autre machine (que j'ai testée). –

+0

Désactivez Aero pour voir si cela fait une différence. C'est-à-dire, faites un clic droit sur le bureau et sélectionnez le thème "Windows Basic" ou "Windows Classic". Redémarrez et voyez si cela fait une différence. J'ai remarqué que d'autres applications DX étaient améliorées quand Aero est éteint. – selbie

Répondre

1

Je ne sais pas si cela est pourquoi votre code actuel est lent, mais une solution à double tampon plus efficace serait de seulement les BitBlt dans le gestionnaire WM_PAINT, et appelez DrawGameStuff dans le gestionnaire WM_TIMER. De cette façon, le seulement chose que vous faites pendant une peinture est de copier le tampon arrière à l'écran, et la logique de dessin réelle peut arriver à un moment différent.

+0

Merci, mais la vitesse est la même, même si je commente l'appel DrawGameStuff() tout à fait. Votre point est bon en général, mais ne semble pas être lié au problème que j'ai. –

+1

graphiques-> Effacer est lent alors? Hormis le fait que j'utilise str8 GDI, pas GDI + je n'ai remarqué AUCUN problème avec un code très similaire sur Windows 7. Si vous utilisez VS non-express, il y a un profileur intégré qui devrait pouvoir vous dire où se trouve le temps est dépensé - une fois que vous avez travaillé la magie mojo pour le faire fonctionner. –

0

Le mode d'interpolation par défaut peut-il être différent entre 7 et Vista/XP? Cela pourrait expliquer vos problèmes de vitesse DrawImage, au moins. Essayez de définir cela spécifiquement sur une valeur de qualité inférieure pour voir si cela affecte la vitesse de DrawImage. AlphaBlend et BitBlt sont presque toujours plus rapides - beaucoup plus rapides - que DrawImage de GDI +. Je soupçonne qu'une grande partie de cela est parce qu'ils ne font pas l'interpolation (c.-à-d., La qualité étirement/rétrécissement d'une image) que fait GDI +.

+0

Go, merci pour la suggestion, mais j'ai déjà converti mon code en DirectX, et maintenant il court vite partout. Mais, à propos du mode d'interpolation, savez-vous si cela s'applique même si l'image n'est pas redimensionnée du tout (ce qui est le cas pour moi)? –

+0

GDI + est plus lent même dans ce cas, mais pas aussi lent. Je n'ai jamais entièrement compris pourquoi. Je suppose que cela a quelque chose à voir avec le système de coordonnées - que DrawImage() n'est pas seulement un wrapper autour de BitBlt, mais est sa propre routine de dessin indépendante des périphériques (pas basée sur les pixels). DirectX était probablement un bon choix pour un jeu. Maintenant, vous ne serez pas limité du tout. – GrandmasterB