[重要的新信息位于本条目底部]
我有一个我认为是使用 GDI+ 的非常标准的游戏循环。它在 Vista 和 XP 上运行良好(复杂游戏大约 25 fps,简单游戏 40 fps)。当我在 Windows 7 上运行它时(具有明显更快的 CPU 和更多内存),它会减慢很多游戏无法使用(我从 0 fps 到 4 fps)。我在下面包含了我认为是代码的相关部分。正如我所说,我相信这是使用 GDI+ 的最简单的(基于内存位图的)游戏循环。您可以在下面看到我为加快速度所做的两次尝试。首先,我担心如果调用 InvalidateRect() 的频率比发送 WM_PAINT 消息的频率高得多,那么系统会将此作为我的程序坏/慢并且扣留我的时间片的线索。所以我添加了 paintIsPending 标志以确保我没有 t 每次油漆无效一次以上。这没有改善。其次,我在下面的 OPTIONAL SECTION 中添加了代码,认为如果我自己触发 WM_PAINT 消息而不是等待它被发送,事情会更好。再次,没有改善。
对我来说,像这样的简单 GDI+ 游戏循环会在 Windows 7 上死掉,这对我来说似乎很疯狂。我知道 Windows 7 处理 2D 图形加速的方式存在一些差异,但同样,这段代码看起来很基础,很难相信它不会-功能性。另外,我知道我可以切换到 DirectX,而且我可能会这样做,但目前在下面的 DrawGameStuff(graphics) 调用所代表的代码库中投入了大量资金,如果可能的话,我宁愿不重写它。
谢谢你的帮助。
#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;
...
}
啊哈!根据 Chris Becke 的线索,我现在掌握了更多且非常相关的信息。我确信是 BitBlt() 很慢,而不是 graphics->Clear(),但是当我注释掉 graphics->Clear() 时,我突然在 Windows 7 上获得 40 FPS。然后我将 graphics->Clear() 更改为
// graphics->Clear( Color( 255, 200, 200, 255 ) );
SolidBrush brush( Color( 255, 200, 200, 255 ) );
graphics->FillRectangle( &brush, 0, 0, CLIENT_WIDTH, CLIENT_HEIGHT );
瞧,它仍然以 40 FPS 运行。我不知道为什么 FillRectangle() 调用比 Clear() 调用快。
于是我开始重新添加我的游戏绘制代码,我立即找到了另一个杀死它的调用:为了绘制游戏内容,我将精灵绘制到 memoryDC 中
graphics->DrawImage( myImageThatCameFromAPngFile, destx, desty, srcx, srcy,
width, height, UnitPixel );
而且这些电话也很慢。作为一个实验,我从我的 PNG 文件中预先绘制到与 screenDC 兼容的第二个 memoryDC 中。然后,为了绘制我的精灵,我从这个辅助 memoryDC 中绘制到我的主 memoryDC 中。所以我没有上面的 DrawImage 调用:
BitBlt( memoryDC, destx, desty, width, height, secondaryMemoryDC,
srcx, srcy, SRCCOPY );
当我这样做时,你瞧,整个游戏在 Windows 7 上以 40 FPS 运行。
然而,这不是一个真正的解决方案,因为在预渲染到该辅助内存 DC 时,我丢失了 PNG 文件中的透明度信息,所以我的精灵现在都是不透明且丑陋的。
所以看起来我的问题是memoryDC(创建为与screenDC兼容)和PNG源文件之间的不兼容。我不明白为什么这种不兼容性仅在 Windows 7 上存在(或者至少,为什么它会减慢速度)。有没有办法保存 PNG 文件以从一开始就与屏幕兼容?或者一开始就在内部重新渲染以获得与屏幕兼容的新PNG文件?嗯……
好的,所以我能够通过将其渲染为 32bpp HBITMAP 来正确渲染我的 PNG 文件,如下所示:
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 );
然后用 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 );
所以现在我的游戏可以在 Windows 7 上快速运行。
但我仍然不明白为什么我必须在 Windows 7 上完成所有这些操作。为什么在 Windows 7 上使用 DrawImage() 从我的 PNG 图像中绘制的默认绘图如此缓慢?