为什么我的Win32 gdi +游戏在Windows 7上速度异常慢?

M K*_*atz 5 winapi gdi+ game-engine

[重要的新信息,请参阅下面此条目底部的内容]

我有一个使用GDI +的非常标准的游戏循环。在Vista和XP上,它工作得相当好(复杂游戏约25 fps,简单游戏约40 fps)。当我在Windows 7(具有明显更快的CPU和更多内存)上运行它时,它会减慢游戏无法使用的速度(我得到的范围从0 fps到4 fps)。我将下面的代码视为相关部分。正如我所说,我相信这是使用GDI +的最简单的(基于内存位图的)游戏循环。您可以在下面看到我为加快速度所做的两次尝试。首先,我担心如果InvalidateRect()的调用频率比发送WM_PAINT消息的调用频率高得多,则系统会以此为线索来表明我的程序运行不良/运行缓慢,并且隐瞒了我的时间片。所以我添加了paintIsPending标志以确保我没有 每种涂料会使失效不止一次。这没有改善。其次,我在下面的可选部分中添加了代码,认为如果我自己触发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;
    ...
}
Run Code Online (Sandbox Code Playgroud)

啊哈!根据克里斯·贝克(Chris Becke)的线索,我现在有更多且非常相关的信息。我以为肯定是BitBlt()慢,而不是图形-> Clear(),但是当我注释掉图形-> 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 );
Run Code Online (Sandbox Code Playgroud)

瞧,它仍然以40 FPS的速度运行。我不知道为什么FillRectangle()调用比Clear()调用快。

因此,我开始重新添加游戏绘图代码,然后立即发现另一个调用将其杀死:要绘制游戏内容,请使用以下命令将子画面绘制到memoryDC中

graphics->DrawImage( myImageThatCameFromAPngFile, destx, desty, srcx, srcy,
    width, height, UnitPixel );
Run Code Online (Sandbox Code Playgroud)

这些电话也很慢。作为一个实验,我从我的PNG文件中预先提取了另一个与screenDC兼容的memoryDC。然后,为了绘制精灵,我从这个辅助memoryDC绘制到我的主要memoryDC中。因此,我没有上面的DrawImage调用:

BitBlt( memoryDC, destx, desty, width, height, secondaryMemoryDC,
    srcx, srcy, SRCCOPY );
Run Code Online (Sandbox Code Playgroud)

当我这样做时,可以看到整个游戏在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 );
Run Code Online (Sandbox Code Playgroud)

然后使用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 );
Run Code Online (Sandbox Code Playgroud)

因此,现在我的游戏可以在Windows 7上快速运行。

但是我仍然不明白为什么必须在Windows 7上完成所有这些工作。为什么在Windows 7上使用DrawImage()从PNG图像中进行默认绘制的速度如此之慢?

JSB*_*ոգչ 1

我不知道这是否是您当前代码速度慢的原因BitBlt,但更有效的双缓冲解决方案是仅在处理程序中执行操作WM_PAINT,然后DrawGameStuffWM_TIMER处理程序中调用。这样,您在绘制过程中要做的唯一一件事就是将后台缓冲区复制到屏幕,并且实际的绘制逻辑可以在不同的时间发生。