65.9K
CodeProject 正在变化。 阅读更多。
Home

无闪烁 GDI 绘图

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (16投票s)

2003 年 6 月 30 日

2分钟阅读

viewsIcon

177783

downloadIcon

2102

带有 VSYNC 锁定和低 CPU 使用率的绘图

引言

本文演示了如何使用较低的 CPU 占用率和 GDI 窗口属性进行无撕裂绘图。

背景

撕裂 - 当页面翻转或 blt 发生在错误的时间时,就会发生撕裂。例如,如果页面在监视器扫描线显示表面的中间翻转,如上图中的虚线所示,则会发生撕裂。可以通过将翻转时间设置为仅在整个表面都显示完毕后(如下图中的示例)来避免撕裂。当 blitting 到正在显示的表面时,也可能发生撕裂。有关更多详细信息,请阅读 页面。

是否可以在没有撕裂的情况下进行绘制,但具有 GDI 窗口特性,并且 CPU 占用率较低?让我们看看。

主代码

通常,显示刷新率可以从 60 到 120Hz,或 1/60 到 1/120 秒。 DirectX 函数 ::WaitForVerticalBlank 只是为了等待 VSYNC 间隔,但它是通过轮询实现的。这有点荒谬。由于 Windows 系统不是一个实时操作系统,该函数会消耗大量的 CPU 资源,但有时它仍然会错过 VSYNC。

在这里,我同时使用多媒体 TimerDirectX 函数来跟踪垂直同步。主要点是使用 2 毫秒的间隔启动多媒体 Timer。在多媒体 Timer 回调处理程序中,使用函数 ::GetScanLine 来获取当前的扫描线号,然后判断是否是绘制的时间。我使用 cdx 库来简化使用 DirectX 函数 ::GetScanLine 的过程,所以请将您的代码与 cdxlib 链接起来。

初始化时

...
    Screen = new CDXScreen();           // Start the instance
    if (Screen==NULL)
    {
        return -2;
    }
    cdx_DD = Screen->GetDD();

    HWND hOWin = GetForegroundWindow();

    SetPriorityClass(
       GetCurrentProcess(),
       HIGH_PRIORITY_CLASS );

    pTheWnd = new COsdWnd;
    if(!pTheWnd->Initialize(hInstance))
        return -3;

    SetForegroundWindow(hOWin);
... 
...
...
StartTimer(2, FALSE, 0);                // Start mmTimer when entry
...fall into messages dispatcher
StopTimer();                            // Stop mmTimmer when exit

启动多媒体计时器的函数

///////////////////////////////////////////////////////////////////
// start mmTimer
///////////////////////////////////////////////////////////////////
public:
bool StartTimer(UINT period, bool oneShot, UINT resolution)
{
    bool        res = false;
    MMRESULT    result;

    TIMECAPS    tc;

    if (TIMERR_NOERROR == timeGetDevCaps(&tc,sizeof(TIMECAPS)))
    {
        m_timerRes = min(max(tc.wPeriodMin,resolution),tc.wPeriodMax);
        timeBeginPeriod(m_timerRes);
    }
    else return false;

    result = timeSetEvent(
        period,
        m_timerRes,                                                 
        mmTimerProc,
        0,                                                          
        (oneShot ? TIME_ONESHOT : TIME_PERIODIC)
        ); // Kill with Sync?

    if (NULL != result)
    {
        m_timerId = (UINT)result;
        res = true;
    }

    return res;
}

停止多媒体计时器的函数

//////////////////////////////////////////////////////////////////////
// stop mmTimer
//////////////////////////////////////////////////////////////////////
public:
bool StopTimer()
{
    MMRESULT    result;

    result = timeKillEvent(m_timerId);
    if (TIMERR_NOERROR == result)
        m_timerId = 0;

    if (0 != m_timerRes)
    {
        timeEndPeriod(m_timerRes);
        m_timerRes = 0;
    }

    return TIMERR_NOERROR == result;
}

多媒体计时器回调处理程序

///////////////////////////////////////////////////////////////////////
// mmTimer CALLBACK handler
// From the trace data, I think mmTimer Callback is driven 
// by Semaphore with counting.
// And it won't overrun. In other words, the Callback 
// function won't be re-entried.
///////////////////////////////////////////////////////////////////////
void CALLBACK mmTimerProc(UINT id,UINT msg,DWORD dwUser,DWORD dw1,DWORD dw2)
{
    cdx_DD->GetScanLine( &gdwScanLine );

    XYTrace(TraceError, _T("ScanLine = %d "), gdwScanLine );

    if (pTheWnd->m_bOnScreenUp)
    {
        if (!gbUsed && (gdwScanLine > pTheWnd->m_ScreenHalfHeight) )
        {
            pTheWnd->ForceFrame();
            gbUsed = TRUE;
        }

        if (gdwScanLine < gdwScanLineLastTime)
        {
            gbUsed = FALSE;
        }
        gdwScanLineLastTime = gdwScanLine;
    }
    else
    {
        if ( gdwScanLine < gdwScanLineLastTime )
        {
            pTheWnd->ForceFrame();
        }
        gdwScanLineLastTime = gdwScanLine;
    }
}

并且不要处理 WM_PAINT 消息,真正的绘图代码如下所示

///////////////////////////////////////////////////////////////////
// Force draw a new frame
///////////////////////////////////////////////////////////////////
public:
void ForceFrame()
{    
    PaintWithMemDC(m_hWnd);
}

////////////////////////////////////////////////////////////////////
// Paint with MemDC
////////////////////////////////////////////////////////////////////
public:
inline
void PaintWithMemDC( HWND hWnd )
{
    if (m_MemDC == NULL) return;

    HDC    hDC = GetDC(hWnd);

    if (! BitBlt(
        hDC,         // handle to destination DC
        0,           // x-coord of destination upper-left corner
        0,           // y-coord of destination upper-left corner
        WIN_WIDTH,   // width of destination rectangle
        WIN_HEIGHT,  // height of destination rectangle
        m_MemDC,     // handle to source DC
        m_ScrollCnt, // x-coordinate of source upper-left corner
        0,           // y-coordinate of source upper-left corner
        SRCCOPY      // raster operation code
    ))
    {
        PostMessage(m_hWnd, WM_CLOSE, 0, 0);
    }

    m_ScrollCnt += m_ScrollSpeed;

    if (m_ScrollCnt > m_StrLen + WIN_WIDTH)
    {
        m_ScrollCnt = 0;
        
        StopTimer();        
        
        CreateMemDC(hWnd); // re create the new memDC
        
        SafeStartTimer();
    }

    ReleaseDC(hWnd, hDC);
}

//////////////////////////////////////////////////////////////////////
// On Left Button Up , at the same time when windows position moved.
//////////////////////////////////////////////////////////////////////
private:
void OnLBtnUp( WINDOWPOS* lpwp)
{
    int cy = GetSystemMetrics(SM_CYSCREEN);

    m_ScreenHalfHeight = cy/2;

    if ( (UINT)(lpwp->y + WIN_HEIGHT) < (m_ScreenHalfHeight) )
    {
        m_bOnScreenUp = TRUE;
        gbUsed        = FALSE;
    }
    else
    {
        m_bOnScreenUp = FALSE;
    }
}

关于演示

要运行演示,您可能需要日语字体。演示效果在 Dell 8200 (P4 1.8G, JW2000, Ti200) 上非常好,无闪烁、无撕裂、低 CPU 占用率并具有 GDI 窗口属性。请注意,不透明模式具有最低的 CPU 占用率,因为复制 bitblt 函数是显示卡的基本功能。这种 VSYNC 检测方法可以用于 DirectX 窗口程序,但现在这只是一个想法。

历史

  • 2003 年 7 月 4 日 - 更新的下载文件

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.