无闪烁 GDI 绘图






4.50/5 (16投票s)
2003 年 6 月 30 日
2分钟阅读

177783

2102
带有 VSYNC 锁定和低 CPU 使用率的绘图
引言
本文演示了如何使用较低的 CPU 占用率和 GDI 窗口属性进行无撕裂绘图。
背景
撕裂 - 当页面翻转或 blt 发生在错误的时间时,就会发生撕裂。例如,如果页面在监视器扫描线显示表面的中间翻转,如上图中的虚线所示,则会发生撕裂。可以通过将翻转时间设置为仅在整个表面都显示完毕后(如下图中的示例)来避免撕裂。当 blitting 到正在显示的表面时,也可能发生撕裂。有关更多详细信息,请阅读 此 页面。
是否可以在没有撕裂的情况下进行绘制,但具有 GDI 窗口特性,并且 CPU 占用率较低?让我们看看。
主代码
通常,显示刷新率可以从 60 到 120Hz,或 1/60 到 1/120 秒。 DirectX 函数 ::WaitForVerticalBlank
只是为了等待 VSYNC 间隔,但它是通过轮询实现的。这有点荒谬。由于 Windows 系统不是一个实时操作系统,该函数会消耗大量的 CPU 资源,但有时它仍然会错过 VSYNC。
在这里,我同时使用多媒体 Timer
和 DirectX
函数来跟踪垂直同步。主要点是使用 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 日 - 更新的下载文件
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。