我现在做的一个小游戏的刷新速度很慢。请问如何提高效率1.我的程序是基于win32的。
2.绘图采用gdi双缓冲的方式,
3.OnPaint里用了贴图的方式贴出部分代码void PicDlg::RandDrawPicture(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
WCHAR strFileName[128];
swprintf(strFileName, _T("%sPicture\\pic%d.bmp"), g_strAppPath, m_nRandIndex); HBITMAP hBitmap = (HBITMAP)LoadImage(g_hInstance, strFileName, IMAGE_BITMAP, 
                                                 0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION); BITMAP object;
GetObject(hBitmap, sizeof(BITMAP), &object);
int nPicWidth = object.bmWidth;
int nPicHeitht = object.bmHeight; HDC hBackDC = NULL;
hBackDC = CreateCompatibleDC(NULL);
SelectObject(hBackDC, m_hBitmap);
DeleteObject(hBitmap); RECT rect;
GetClientRect(hWnd, &rect);
StretchBlt(m_hMemDC, // 拷贝到内存DC中
   0, 0, 
   rect.right - rect.left, rect.bottom - rect.top, 
   hBackDC, 
   0, 0, 
   nPicWidth, nPicHeitht, 
   SRCCOPY);
}void PicDlg::OnPaint(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT paint; 
HDC hDC = ::BeginPaint(hWndDlg, &paint);  RECT rcClient;
::GetClientRect(hWndDlg, &rcClient); RandDrawPicture(hWndDlg, uMsg, wParam, lParam);
::BitBlt(hDC,
 0, 0, 
 rcClient.right, rcClient.bottom,  
 m_hMemDC,
 0, 0, 
 SRCCOPY);
::EndPaint(hWndDlg, &paint);
}////////////////////////////////////////////////////////////////////
这是其中一个对话框的刷新
有很多这样的对话框拼出整个界面。现在修改一下数据,我就刷新下界面,要等个几百毫秒,界面才刷新过来,有点迟钝。请问怎么优化,速度慢在哪了?(配置高的机器还好,差一点的就特别慢)谢谢了!!!!

解决方案 »

  1.   

    眼睛反应能力大概是0.1秒,几百毫秒是看得出的
    使用DC进行绘制是不可能很高效的吧,我觉得你需要学习DirectX或者OpenGL
      

  2.   

    单纯的用GDI画图是存在性能瓶颈的 除非结合图形加速卡
      

  3.   

    GDI本身还是不错的,只要你用好了!在CAD/M应用中可以画很多高效的东西!你的问题在于你的东西本身上就是有问题的,你每次都会去更新整个一张位图!最主要的是每次只刷新改变的地方(配合InvalidateRect)
      

  4.   

    别每次都LoadImage,初始化时load进来,保存在成员变量里面,之后如果需要更换再重新load。OnPaint中只处理贴图。
      

  5.   

    另外
    我做了个动画效果,移动对话框,其实我是想做个图片移动的效果,就把图片贴在窗口上。移动窗口,到达效果。但是每20毫秒刷新显示下,但是看到的是白色的窗口移动,没有图片出来。配置高的机器可以看到效果,我是在Arm9的机器运行的,我的代码有效率的问题吗?void PicDlg::MoveUpThread(LPVOID pPrarm)
    {
    PicDlg *This = (PicDlg *)pPrarm;
    int nHeight = This->m_rectCur.bottom - This->m_rectCur.top;

    for (int i = 0; i < nHeight; i += 3)
    {
    This->m_rectCur.top -= 3;
    This->m_rectCur.bottom -= 3;
    MoveWindow(g_hPicWnd,
       This->m_rectCur.left, This->m_rectCur.top, 
       This->m_rectCur.right - This->m_rectCur.left, 
                               This->m_rectCur.bottom - This->m_rectCur.top, 
       true);
    Sleep(50);
    }}
      

  6.   

    1、你不应该每次画图时都在RandDrawPicture函数里加载位图做为画板,而应该在程序启动时加载一次就好了
    2、你在进行画图时,用了一次hbackDC画到内存memdc上,又从memdc拷贝到实际的窗口dc,这样做没必要,只要先把图片画到memdc上就好了,处理好画图的顺序,比如在底层的先画,再画上面的。
    3、做精灵动画不能采用移动窗口的方式,而应该是快速擦除重画。这样效率是最高的,移动窗口,要经过MFC的消息循环,不能保证实时性,特别是WM_PAINT消息是非同步消息,系统有时间才会去处理的,不是实时处理的。
      

  7.   

    进程里不要使用Sleep 它会阻塞消息循环void _Sleep_(UINT Delay_ms)
    {
      DWORD dwTick = GetTickCount() + Delay_ms;
      while(GetTickCount() < dwTick)
      {
        MSG msg;
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
          if (msg.message == WM_QUIT) break;
          TranslateMessage(&msg);
          DispatchMessage(&msg);
        }
        Sleep(0);
      }
    }本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zgl7903/archive/2010/03/15/5382869.aspx
      

  8.   

    怎么会没有呢?无论你怎么刷新,你动画对象还是存在,有状态的,你还是要根据他当前状态去画啊。
    比如:在屏幕画一个移动的圆,你所要做的就是1、擦除上次的圆。2、画新位置的圆。如果窗口收到了WM_PAINT之类的消息,你也是擦掉当前的圆,再画一个圆,相当于画了两次圆(你也可以新旧位置判断,只画一次),但还是可以看到圆啊。擦是擦掉旧的,重画是画新的。。
      

  9.   

    我是在一个线程里面sleep也有问题吗?说下现在的情况,我把LoadImage 放到OnPaint外面来了,将位图作为成员变量了void PicDlg::OnPaint(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
    PAINTSTRUCT paint; 
    HDC hDC = ::BeginPaint(hWndDlg, &paint);  RECT rcClient;
    ::GetClientRect(hWndDlg, &rcClient);
     
    //RandDrawPicture(hWndDlg, uMsg, wParam, lParam); HDC hBackDC = NULL;
    hBackDC = CreateCompatibleDC(NULL);
    SelectObject(hBackDC, m_hBitmap);
    ::BitBlt(hDC,
     0, 0, 
     rcClient.right, rcClient.bottom,  
     hBackDC,
     0, 0, 
     SRCCOPY);

    DeleteDC(hBackDC); 
    ::EndPaint(hWndDlg, &paint);
    }跳到OnPaint里面后m_hBitmap突然又为0了(之前已经加载好的),怎么搞的?
      

  10.   

    做任何优化都要先找出瓶颈在哪儿然后再动手。
    这个是一个原则。你的LoadImage是放在哪儿的?怎么写的。
      

  11.   

    void PicDlg::SetRandIndex(void)
    {
    int  x = GetLastError();
    srand((unsigned)GetRandSeed());
    m_nRandIndex = rand() % PIC_COUNT + 1; // 加载位图
    WCHAR strFileName[128];
    swprintf(strFileName, _T("%sPicture\\pic%d.bmp"), g_strAppPath, m_nRandIndex);
    #ifdef _WINXP
    HBITMAP m_hBitmap = (HBITMAP)LoadImage(g_hInstance, _T("C:\\1.bmp"), IMAGE_BITMAP, 0, 0,  LR_LOADFROMFILE);
    #else
    HBITMAP m_hBitmap = (HBITMAP)SHLoadDIBitmap(strFileName);
    #endif 

    x = GetLastError(); BITMAP object;
    GetObject(m_hBitmap, sizeof(BITMAP), &object);
    m_nPicWidth = object.bmWidth;
    m_nPicHeitht = object.bmHeight;
    }
    这个函数在OnPaint之前调用的
    m_hBitmap已经有值了,GetLastError也等于 0
    为什么跳到OnPaint里就没了呢?
      

  12.   

    再次谢谢楼上所有的朋友们现在可以显示图片效果了,但是会有闪烁的现象,我的代码是void PicDlg::OnPaint(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
    PAINTSTRUCT paint; 
    HDC hDC = ::BeginPaint(hWndDlg, &paint);  RECT rcClient;
    ::GetClientRect(hWndDlg, &rcClient); HDC hBackDC = NULL;
    hBackDC = CreateCompatibleDC(NULL);
    SelectObject(hBackDC, m_hBitmap); ::BitBlt(hDC,
     0, 0, 
     rcClient.right, rcClient.bottom,  
     hBackDC,
     0, 0, 
     SRCCOPY);
    DeleteDC(hBackDC); 
    ::EndPaint(hWndDlg, &paint);
    }void PicDlg::MoveDown(void)
    {
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MoveDownThread, this, 0, NULL);
    }void PicDlg::MoveDownThread(LPVOID pPrarm)
    {
    PicDlg *This = (PicDlg *)pPrarm; g_GameManage.GetBigVideoRect(This->m_rectCur);
    int nHeight = This->m_rectCur.bottom - This->m_rectCur.top;
    This->m_rectCur.bottom = 0;
    This->m_rectCur.top = This->m_rectCur.bottom - PIC_HEIGHT; MoveWindow(g_hPicWnd, 
       This->m_rectCur.left, This->m_rectCur.top, 
       This->m_rectCur.right - This->m_rectCur.left, 
                               This->m_rectCur.bottom - This->m_rectCur.top, 
       false);
    for (int i = 0; i < PIC_HEIGHT; i += 5)
    {
    This->m_rectCur.top += 5;
    This->m_rectCur.bottom += 5;
    MoveWindow(g_hPicWnd, 
       This->m_rectCur.left, This->m_rectCur.top, 
       This->m_rectCur.right - This->m_rectCur.left, 
                                       This->m_rectCur.bottom - This->m_rectCur.top, 
       false);
    InvalidateRect(g_hPicWnd, NULL, false);
    _Sleep_(20);
    }
    }INT_PTR PicDlg::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
    switch (uMsg)
    {
    case WM_PAINT:
    {
    OnPaint(hWndDlg, uMsg, wParam, lParam);
    }
    break;
    case WM_ERASEBKGND:
    return 0;
    break;
    } return 0;
    }
    怎么解决闪烁的问题
      

  13.   

    WM_ERASEBKGND
    查查这个东西的用法。
      

  14.   

    我已经屏蔽了这个消息了啊WM_ERASEBKGND
    还是闪得厉害
      

  15.   

    谢谢chen_wenyue,
    请教下,不用对话框,我应该在哪个窗口绘制呢?
    我现在的贴图的窗口的下面还有一个窗口的。是播放视频的。应该不能在那上面绘吧。
      

  16.   

    case WM_ERASEBKGND:
            return 1;仔细看MSDN:
    An application should return nonzero in response to WM_ERASEBKGND  if it processes the message and erases the background; this indicates that no further erasing is required. If the application returns zero, the window will remain ed for erasing. (Typically, this indicates that the fErase member of the PAINTSTRUCT  structure will be TRUE.) 
      

  17.   

    case WM_ERASEBKGND:
      return 1;
    仍然闪我在WM_ERASEBKGND里把onpaint中的内容再绘一遍就不闪了。
    能解释下吗?
      

  18.   

    怀疑是由于背景干扰引起的,当返回1时,对话框不重绘背景,但你背景在播放视频,这样,在你两次重绘中间,背景会被视频图象擦除,这样,由于两次切换的时间不够短,人眼能分辨出变化,就会产生闪烁现象。
    但你如果在WM_ERASEBKGND中画了背景,视频播放不引起背景变化,所以感觉不到闪烁,但还是建议你不要这样来实现精灵动画,呵呵。。感觉很别扭。
      

  19.   

    你用移动窗口来做动画,这倒是头一次听说。也许是因为Windows本身的重画机制导致的吧。
      

  20.   

    速度慢在贴背景图片上,把绘制背景去掉就不慢了//////////////////////////////////////////////////////////////////////////
    // 绘制背景
    void GameView::DrawBackgroud(void)
    {

    // 绘制到内存DC上
    ::BitBlt(g_hMemDC,
    0, 0,  
    SCREEN_WIDTH, SCREEN_HEIGHT,  
    m_hBkgrdDC,
    0, 0,
    SRCCOPY);   
    }
    这样还能怎么优化啊
      

  21.   

    //////////////////////////////////////////////////////////////////////////
    // 绘制背景
    void GameView::DrawBackgroud(void)
    {
     ///空
    }
    这样就快多了。
      

  22.   

    你在BitBlt前后加个计时,看到底用了多少时间。
      

  23.   

    一般来说,BitBlt是相当短的时间,如果你的BitBlt时间是瓶颈,基本上任何优化都没用了。
      

  24.   

    我是arm9的cpu,主频400多M
    应该怎么处理呢?
      

  25.   

    从应用层面有没有办法处理,
    每次bitblt的范围小点行吗?
    只bitblt需要改变背景的区域,其它背景都没变的。但是又不知道怎么取改变了背景的区域,
      

  26.   

    InvalidateRect刷新的时候,传一个矩形区域进去,这个区域就是要刷新的部分。
    PAINTSTRUCT里就有要刷新的矩形,仔细看MSDN,很重要的。要做的深入一点不仔细看文档是不行的。
      

  27.   

    多谢gamedragon
    请问如何得到这个结构呢,我看了下MSDN,好像没提到。
      

  28.   

    typedef struct tagPAINTSTRUCT { 
      HDC hdc; 
      BOOL fErase; 
      RECT rcPaint; 
      BOOL fRestore; 
      BOOL fIncUpdate; 
      BYTE rgbReserved[32]; 
    } PAINTSTRUCT;
    RECT rcPaint;
    你真的看了吗?
      

  29.   

    看了
    rcPaint,Specifies the upper-left and lower-right corners of the rectangle in which the painting is requested. rcPaint,指定需要绘制矩形区域的左上和右下角的坐标。这是设置需要绘制的区域吧,我想问的是,怎么取得哪写区域发生的变化,需要重绘,返回的应该是多个区域的链表类似的结构才对吧,我的理解哈。
      

  30.   

    Windows在PAINTSTRUCT里会把无效矩形合并成一个大矩形,不是链表。
    如果要精确的无效区域,用GetUpdateRgn,得到的是个区域。
    不过用区域的效率大部分时间里不如直接用RECT。
      

  31.   

    GDI绘图是比较慢,如果底层支持directdraw的话可以考虑使用directdraw绘图。
    而且我看你onpaint函数里面好像对整个DC重绘了,其实有时候不用全部重绘,只需要重绘修相应修改的区域就行。