Windows 7中的GDI加速/绘制到内存位图

3
我的GDI程序在Windows XP上运行良好,但在Windows Vista和7上看起来非常糟糕,因为缺乏GDI硬件加速。我记得几年前读过一篇文章,说Windows 7添加了对某些GDI函数的硬件加速,包括BitBlt()函数。据说,如果您绘制到内存位图,然后使用BitBlt()将图像复制到主窗口,它的速度与XP大致相同。这是真的吗?
如果是真的,怎么做?我很菜,有点困难。我创建了下面的类来尝试使其工作:
class CMemBmpTest
{
private: 
    CDC         m_dcDeviceContext;
    CBitmap     m_bmpDrawSurface;

public:
    CMemBmpTest();
    ~CMemBmpTest();
    void Init();
    void Draw();
};

CMemBmpTest::CMemBmpTest()
{
}

CMemBmpTest::~CMemBmpTest()
{
    m_bmpDrawSurface.DeleteObject();
    m_dcDeviceContext.DeleteDC();
}

void CMemBmpTest::Init()
{  
    m_dcDeviceContext.CreateCompatibleDC(NULL);
    m_bmpDrawSurface.CreateCompatibleBitmap(&m_dcDeviceContext, 100, 100);
}

void CMemBmpTest::Draw()
{  
    m_dcDeviceContext.SelectObject(I.m_brshRedBrush);
    m_dcDeviceContext.PatBlt(0, 0, 100, 100, BLACKNESS);
}

在窗口的OnPaint()函数中,我添加了以下行:

pDC->BitBlt(2, 2, 100, 100, &m_MemBmp, 0, 0, SRCCOPY);

我希望在窗口的角落看到一个100x100的黑色方块,但它没有起作用。我可能做错了一切,所以如果有人能告诉我怎样才能正确地做到这一点,我会很感激。
非常感谢您提供的任何建议。

如果您正在使用GDI,则不太可能从硬件加速中受益,至少在任何合理的程度上是如此。更值得做的是检查您的代码并清理其中各种过度处理的部分。您还可以尝试禁用Aero以可能回滚到“经典”的GDI(而不是基于Direct3D的实现)。 - Roman R.
如果你只想做双缓冲(我不太明白你的问题,但我认为这就是你想要的),那么最好使用CMemDC,只需在你的OnPaint()方法中添加2或3行代码,然后你就可以开始了。(当然,如果你对每个像素使用SetPixelV(),渲染仍然会很慢...) - Roel
2个回答

3
据我所知,在所有版本的Windows上,您可以获得GDI函数的硬件加速(如果有人能更详细地解释一下,我很乐意纠正这一点)。但无论如何,您说的双缓冲(也就是您所说的)相对于直接绘制到屏幕上,提供了巨大的性能提升(更重要的是没有闪烁)。
我已经做了很多这方面的工作,并想出了一种方法,使您可以同时在绘图函数中使用GDI和GDI+,但从BitBlt在绘制到屏幕时获得硬件加速。据我所知,GDI+并没有硬件加速,但在许多更复杂的绘图技术中非常有用,因此具有这种选择可能很有用。
因此,我的基本视图类将具有以下成员:
Graphics *m_gr;
CDC *m_pMemDC;
CBitmap *m_pbmpMemBitmap;

然后这个类本身会有如下的代码:
    /*======================================================================================*/
    CBaseControlPanel::CBaseControlPanel()
    /*======================================================================================*/
    { 
        m_pMemDC = NULL;
        m_gr = NULL;
        m_pbmpMemBitmap = NULL;
    }

    /*======================================================================================*/
    CBaseControlPanel::~CBaseControlPanel()
    /*======================================================================================*/
    {
        // Clean up all the GDI and GDI+ objects we've used
        if(m_pMemDC)
        { delete m_pMemDC; m_pMemDC = NULL; }
        if(m_pbmpMemBitmap)
        { delete m_pbmpMemBitmap; m_pbmpMemBitmap = NULL; }
        if(m_gr)
        { delete m_gr; m_gr = NULL; }
    }   

/*======================================================================================*/
void CBaseControlPanel::OnPaint()
/*======================================================================================*/
{
    pDC->BitBlt(rcUpdate.left, rcUpdate.top, rcUpdate.Width(), rcUpdate.Height(),
                        m_pMemDC, rcUpdate.left, rcUpdate.top, SRCCOPY);
}

/*======================================================================================*/
void CBaseControlPanel::vCreateScreenBuffer(const CSize szPanel, CDC *pDesktopDC)
// In : 
//      szPanel = The size that we want the double buffer bitmap to be
// Out : None
/*======================================================================================*/
{
    // Delete anything we're already using first
    if(m_pMemDC)
    {
        delete m_gr;
        m_gr = NULL;
        delete m_pMemDC;
        m_pMemDC = NULL;
        delete m_pbmpMemBitmap;
        m_pbmpMemBitmap = NULL;
    }
    // Make a compatible DC
    m_pMemDC = new CDC;
    m_pMemDC->CreateCompatibleDC(pDesktopDC);           
    // Create a new bitmap
    m_pbmpMemBitmap = new CBitmap;
    // Create the new bitmap
    m_pbmpMemBitmap->CreateCompatibleBitmap(pDesktopDC, szPanel.cx, szPanel.cy);
    m_pbmpMemBitmap->SetBitmapDimension(szPanel.cx, szPanel.cy);
    // Select the new bitmap into the memory DC
    m_pMemDC->SelectObject(m_pbmpMemBitmap);
    // Then create a GDI+ Graphics object
    m_gr = Graphics::FromHDC(m_pMemDC->m_hDC);
    // And update the bitmap
    rcUpdateBitmap(rcNewSize, true);
}

/*======================================================================================*/
CRect CBaseControlPanel::rcUpdateBitmap(const CRect &rcInvalid, const bool bInvalidate, const bool bDrawBackground /*=true*/)
// Redraws an area of the double buffered bitmap
// In : 
//      rcInvalid - The rect to redraw
//      bInvalidate - Whether to refresh to the screen when we're done
//      bDrawBackground - Whether to draw the background first (can give speed benefits if we don't need to)
// Out : None
/*======================================================================================*/
{
   // The memory bitmap is actually updated here

   // Then make the screen update
   if(bInvalidate)
   { InvalidateRect(rcInvalid); }
}

那么,你可以直接绘制到内存DC中并调用InvalidateRect(),或者将所有绘图代码放在rcUpdateBitmap()中,这对我使用的方式更加方便。您需要在OnSize()中调用vCreateScreenBuffer()。

希望这能给您一些想法。双缓冲肯定是提高速度和防止UI闪烁的方法。它可能需要花费一点精力才能启动,但绝对值得。


非常感谢您详细的回答。我会尝试在周末让它工作起来。至于GDI加速,根据维基百科中关于Windows Vista下图形设备接口的条目,“GDI不再由视频卡驱动程序进行硬件加速”,而在Windows 7中,“Windows 7包括用于blitting操作的GDI硬件加速”。在Vista和7下与XP相比,性能差异明显。希望一旦我将其更改为双缓冲,它就能更快地工作,我最终能够切换到Windows 7。再次感谢您的回答。 - user1429288
2
GDI在Vista上没有加速,但在带有WDDM 1.1的Windows 7中又被重新添加了(并非所有功能都被加速,只有一小部分函数,请参见http://msdn.microsoft.com/en-us/library/windows/hardware/ff569719%28v=vs.85%29.aspx)。 - Anders
好的,谢谢你提供有关硬件加速的信息,我之前不知道。关于绘图速度的额外建议是 - 对于许多事情,特别是位图复制,GDI+比GDI慢得多。这就是为什么我保留了指向GDI和GDI+设备上下文的指针,所以只有在找不到在GDI中实现我想要的功能时才使用GDI+。 - Redeye


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接