Click here to Skip to main content
15,879,535 members
Articles / Multimedia / GDI

Tearing-free Drawing with GDI

Rate me:
Please Sign up or sign in to vote.
4.50/5 (22 votes)
3 Jul 20032 min read 172.6K   2.1K   37   30
Drawing with VSYNC locked and low CPU usage

Image 1

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

C++
...
    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

Function to Start Multimedia Timer

C++
///////////////////////////////////////////////////////////////////
// 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;
}

Function to Stop Multimedia Timer

C++
//////////////////////////////////////////////////////////////////////
// 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;
}

Multimedia Timer Callback Handler

C++
///////////////////////////////////////////////////////////////////////
// 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;
    }
}

And do not handle the WM_PAINT message, real drawing codes go like the following:

C++
///////////////////////////////////////////////////////////////////
// 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;
    }
}

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.


Written By
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHi again Pin
fenotipo19-Feb-12 3:36
fenotipo19-Feb-12 3:36 
Questionvsync in gdi Pin
flowwolf3-Aug-11 21:55
flowwolf3-Aug-11 21:55 
GeneralScreen rotate Pin
calv00823-May-07 17:54
calv00823-May-07 17:54 
GeneralGetScanLine for Secondary Display Help Pin
soorya prakash19-Jul-06 13:50
soorya prakash19-Jul-06 13:50 
GeneralRe: GetScanLine for Secondary Display Help Pin
Jonnie White17-Nov-09 3:43
Jonnie White17-Nov-09 3:43 
GeneralDeleting the Bitmap Object Pin
Alex Evans6-Feb-05 15:00
Alex Evans6-Feb-05 15:00 
GeneralDoesnt work on some hardware... Pin
Raul Sobon19-May-04 15:40
Raul Sobon19-May-04 15:40 
GeneralRe: Doesnt work on some hardware... Pin
wheregone21-Jun-04 5:27
wheregone21-Jun-04 5:27 
QuestionVertical SYNC interrupt in DirectX9??? Pin
wheregone29-Feb-04 23:54
wheregone29-Feb-04 23:54 
AnswerRe: Vertical SYNC interrupt in DirectX9??? Pin
wheregone1-Mar-04 0:49
wheregone1-Mar-04 0:49 
And the Win DDK function:

DdVideoPortGetLine
The DdVideoPortGetLine callback function returns the current line number of the hardware video port.

DX7,8,9,Managed,C++, confusing me. lol

Hardware-OS-Software
===== Bridge =======
AnswerRe: Vertical SYNC interrupt in DirectX9??? Pin
fenotipo24-Apr-05 7:28
fenotipo24-Apr-05 7:28 
GeneralExplanation of mmTimerProc Pin
zizka2-Jan-04 23:43
zizka2-Jan-04 23:43 
GeneralRe: Explanation of mmTimerProc Pin
wheregone13-Jan-04 4:40
wheregone13-Jan-04 4:40 
GeneralRe: Explanation of mmTimerProc Pin
Anonymous13-Jan-04 8:08
Anonymous13-Jan-04 8:08 
GeneralRe: Explanation of mmTimerProc Pin
wheregone29-Feb-04 23:57
wheregone29-Feb-04 23:57 
General::WaitForVerticalBlank( ) Pin
drc_slime7-Nov-03 8:05
drc_slime7-Nov-03 8:05 
GeneralRe: ::WaitForVerticalBlank( ) Pin
wheregone16-Nov-03 21:28
wheregone16-Nov-03 21:28 
GeneralNo project to compile Pin
z_j23-Aug-03 16:44
z_j23-Aug-03 16:44 
GeneralRe: No project to compile Pin
wheregone5-Oct-03 23:35
wheregone5-Oct-03 23:35 
QuestionProblem ?? Pin
Jim Crafton30-Jun-03 3:14
Jim Crafton30-Jun-03 3:14 
AnswerRe: Problem ?? Pin
John M. Drescher30-Jun-03 3:21
John M. Drescher30-Jun-03 3:21 
GeneralRe: Problem ?? Pin
wheregone30-Jun-03 5:04
wheregone30-Jun-03 5:04 
GeneralRe: Problem ?? Pin
John M. Drescher30-Jun-03 5:13
John M. Drescher30-Jun-03 5:13 
GeneralRe: Problem ?? Pin
wheregone30-Jun-03 5:25
wheregone30-Jun-03 5:25 
GeneralRe: Problem ?? Pin
John M. Drescher30-Jun-03 5:44
John M. Drescher30-Jun-03 5:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.