Introduction
This article demonstrates Tearing free drawing with low CPU usage and GDI window properties.
Background
Tearing - A tear occurs when a page flip or blt happens at the wrong time. For example, if a page flips while the monitor scan line is in the middle of displaying a surface, as represented by the dashed line in the preceding figure, a tear occurs. The tear can be avoided by timing the flip to occur only after the entire surface has been displayed (as in the lower example of the figure). A tear can also occur when blitting to a surface that is in the process of being displayed. For more details, please read this page.
Is it possible to draw without tearing, but with GDI window characteristic, and low CPU usage? Let's see.
The Main Code
Generally, the display refresh rate could be from 60 to 120Hz. or 1/60 down to 1/120 second. DirectX function ::WaitForVerticalBlank
is just to wait for the VSYNC interval, but it is by Polling. That is a little ridiculous. Since the windows system is not a real time operating system, that function consumes a lot of CPU resource but sometimes, it still misses VSYNC.
Here, I use both multimedia Timer
and DirectX
function to track the vertical sync. The main point is starting a multimedia Timer
with 2ms interval. In the multimedia Timer
callback handler, use function ::GetScanLine
to get the current scan line number, then judge whether it is the time to draw. I use cdx
library to simplify using DirectX
function ::GetScanLine
, so that please link your code with cdxlib.
When Initializing
...
Screen = new CDXScreen();
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);
...fall into messages dispatcher
StopTimer();
Function to Start Multimedia Timer
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)
);
if (NULL != result)
{
m_timerId = (UINT)result;
res = true;
}
return res;
}
Function to Stop Multimedia Timer
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;
}
Multimedia Timer Callback Handler
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;
}
}
And do not handle the WM_PAINT
message, real drawing codes go like the following:
public:
void ForceFrame()
{
PaintWithMemDC(m_hWnd);
}
public:
inline
void PaintWithMemDC( HWND hWnd )
{
if (m_MemDC == NULL) return;
HDC hDC = GetDC(hWnd);
if (! BitBlt(
hDC,
0,
0,
WIN_WIDTH,
WIN_HEIGHT,
m_MemDC,
m_ScrollCnt,
0,
SRCCOPY
))
{
PostMessage(m_hWnd, WM_CLOSE, 0, 0);
}
m_ScrollCnt += m_ScrollSpeed;
if (m_ScrollCnt > m_StrLen + WIN_WIDTH)
{
m_ScrollCnt = 0;
StopTimer();
CreateMemDC(hWnd);
SafeStartTimer();
}
ReleaseDC(hWnd, hDC);
}
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;
}
}
About the Demo
To run the demo, you may need Japanese Fonts. The demo effect is very good on Dell 8200 (P4 1.8G, JW2000, Ti200), flicker-free, tearing-free, low CPU usage and with GDI windows properties. Notice that the opaque mode has the lowest CPU usage because the copy bitblt
function is the basic function of display card. This VSYNC detection method could be used in DirectX windows program, but it is only an idea by now.
History
- 4th July, 2003 - Updated download file
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.