Home
Fractals
Tutorials
Books
My blog
My LinkedIn Profile

BOOKS i'm reading

Napoleon Hill Keys to Success: The 17 Principles of Personal Achievement, Napoleon Hill, ISBN: 978-0452272811
The 4-Hour Workweek: Escape 9-5, Live Anywhere, and Join the New Rich (Expanded and Updated), Timothy Ferriss, ISBN: 978-0307465351
The Fountainhead, Ayn Rand, ISBN: 0452273331

Alternative to MFC for GDI programming

Olivier Langlois, IT superstar coach, Dominate LinkedIn Formula author
by Olivier Langlois

Contents





The demo program

Essentially, what the demo program needs to do is draw a bunch of things by either using OLIGDI or MFC, and time the operation and display the difference between the two paint methods. My starting point for the demo program is the cute clover program written by Charles Petzold for his book Programming Windows. His clover program draws a clover with lines and a complex clipping region. From the demo program menu, you can select three display methods: OLIGDI, MFC, and Alternate. The first two can be used so the user can try to observe subjectively the difference between the two painting methods by resizing the window. The third option, Alternate, with the help of the timer option that periodically forces the repainting of the window, allows the demo program to compute the difference between the two painting modes. The timing is performed with the help of this small helper class:

class cHighResolutionTimer
{
public:
    cHighResolutionTimer();

    void start();
    double stop();

private:
    LARGE_INTEGER frequency, startTime;
};

cHighResolutionTimer::cHighResolutionTimer()
{
    startTime.QuadPart = 0;
    LASTERRORDISPLAYD(QueryPerformanceFrequency(&frequency));
}

void cHighResolutionTimer::start()
{
    LASTERRORDISPLAYD(QueryPerformanceCounter(&startTime));
}

double cHighResolutionTimer::stop()
{
    LARGE_INTEGER stopTime;
    LASTERRORDISPLAYD(QueryPerformanceCounter(&stopTime));
    return (double)(stopTime.QuadPart - startTime.QuadPart)/frequency.QuadPart;
}

The most challenging part of programming the demo program has been to output meaningful numbers out of the timing measurements. Something that I have noticed during the development is that, measuring the same drawing method multiple times results in large variations in the timing. This could be caused by multiple factors such as software inconsistencies (task switching) and hardware inconsistencies (GDI device driver having to wait for a particular moment in the video card refresh cycle to perform writes). Since the timing variations are of the same order as the speed differences, I had great difficulties to highlight this difference. After many attempts with different methods, I have devised the following scheme:

  1. NUMSAMP measurements for each method are taken.
  2. Sort the measurements.
  3. Scrap the NUMSAMP/3 lowest and the NUMSAMP/3 highest measurements.
  4. Return the remaining measurements average.
#define NUMSAMP 12

class CTimingStat
{
public:
    CTimingStat()
    { reset(); }

    void reset(void) { m_nSamples = 0; }
    void set(double s) { m_samplArr[m_nSamples++] = s; }
    const UINT getnSamples(void) const { return m_nSamples; }
    double getAverage(void);
private:
    double m_samplArr[NUMSAMP];
    UINT m_nSamples;

    static int __cdecl compare(const void *elem1,
                              const void *elem2);
};

double CTimingStat::getAverage(void)
{
    int a;
    double xa = 0.0;

    qsort(m_samplArr,NUMSAMP,sizeof(double),
                      CTimingStat::compare);

    for( a = NUMSAMP/3; a < (2*NUMSAMP/3); a++ )
    {
        xa += m_samplArr[a];
    }

    xa /= NUMSAMP/3.0;

    return xa;
}

int CTimingStat::compare(const void *elem1,
                         const void *elem2)
{
    return (int)(*(double *)elem1 - *(double *)elem2);
}

To complete the demo program description, there is an interesting bug that slipped away from my attention. When using the memory DC as a double buffer to remove flickers, the painting was fine almost all the time except when only a small portion of the window needed to be repainted. You could resize the window and the repainting was performed flawlessly, but if you opened the About dialog box and dragged it around the client area, the repainting was all screwed up. The problem comes from the fact that the clipping region is computed for the whole client area and the memory DC window origin is set to the invalid rectangle upper left corner. When the window is resized, the whole client area is invalidated and everything fits, but when only a small portion of the client area is invalidated, the memory DC window origin is not (0,0) and the clipping region needs to be moved to consider this difference. To see the problem yourself, just comment out the OffsetClipRgn() calls and select the double buffer option from the menu.

dc.SelectClipRgn((HRGN)RgnClip.GetSafeHandle());
/*
 * Since Clip region is in device point, it is important to offset
 * it because the double buffering DC window origin is set at the top
 * corner of the invalidated rect.
 */
dc.OffsetClipRgn(p.x,p.y);

Another interesting point of the demo program is that I have derived a specialized StatusBar class CCloverStatusBar from CStatusBar. It shows to readers unfamiliar with the tooltip control how to create, use and show tooltips from a rectangular region in a window. You first need to call AddRectTool() to create a tooltip. Then you can change the text of your tooltip with UpdateTipText() and finally, it is very important to adjust the tooltip region with SetToolRect() when its host window is resized.

int CCloverStatusBar::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    RECT toolRect;

    if (CStrechyStatusBar::OnCreate(lpCreateStruct) == -1)
        return -1;

    // TODO: Add your specialized creation code here
    if (!SetIndicators(indicators,
        sizeof(indicators)/sizeof(UINT)))
    {
        return -1;      // fail to create
    }

    /*
     * Set the first strechy indicator width to its minimum to
     * make sure that the right side indicators do not disapear when
     * the status bar width is reduced.
     */
    MakeStrechy();

    m_ttctl.Create(this);
    GetItemRect(PAINTTIMEPANE,&toolRect);
    m_ttctl.AddRectTool(m_hWnd,MAKEINTRESOURCE(IDS_ONOLIGDI),&toolRect,PAINTTIMEPANE);

    return 0;
}

/*
 * Function CCloverStatusBar::SetPaintMethod
 */
void CCloverStatusBar::SetPaintMethod(UINT nID)
{
    static LPCTSTR MethodNameTb[3] =
    {
        __TEXT("\tOLIGDI"),
        __TEXT("\tMFC"),
        __TEXT("\tAlternate")
    };

    m_paintMethod = nID;
    SetPaneText(METHODNAMEPANE,
                MethodNameTb[m_paintMethod]);
    m_ttctl.UpdateTipText(nID+IDS_ONOLIGDI,this,PAINTTIMEPANE);

    if( m_paintMethod == USEALTERNATE )
    {
        initStats();
    }
}

/*
 * Function CCloverStatusBar::OnSize
 */
void CCloverStatusBar::OnSize(UINT nType, int cx, int cy)
{
    RECT toolRect;

    CStrechyStatusBar::OnSize(nType,cx,cy);

    GetItemRect(PAINTTIMEPANE,&toolRect);
    m_ttctl.SetToolRect(this,PAINTTIMEPANE,&toolRect);
}

Please note that m_ttctl is of type CSubclassToolTipCtrl, a class derived from the MFC class CToolTipCtrl. I describe in detail CSubclassToolTipCtrl in my article on my hyperlink control class if you are interested to know more about CSubclassToolTipCtrl.

Conclusion

The results are very disappointing. On my machine, I got a shy improvement varying from 1% to 3%. It seems that the result depends largely on the hardware on which the demo program is run; as I tested it on different machines, with few exceptions where I witnessed 10%-15% improvement, the improvement is generally below 5%. Without measurements, the difference is not visually perceptible. The conclusion that can be drawn from this experiment is that despite MFC's overhead, it is negligible compare to the time spent inside the GDI functions themselves.

That is it! I hope you enjoyed this C++ Windows programming tutorial. In the next section, you will find the books that I have consulted to build this C++ Windows programming tutorial. Those books are great and filled with Windows programming gems that you should know. It is strongly recommended that you get yourself a copy of these books especially since from time to time, you can find these books at a bargain price. Take the time to check the prices. This is maybe your lucky day today! Also, if you get amazing results with the demo program on your machine, or if you found an application for this code, I would love to hear from you!

Bibliography

History

  • 01-09-2006
    • Original article.


Page 1 2 3

Back to the Tutorials list

Home :: Fractals :: Tutorials :: Books :: My blog :: My LinkedIn Profile :: Contact