[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?
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. –
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). –
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