Open GL Super Bible

Previous Table of Contents Next


A Makeshift Benchmark

Our final program produces a fairly good representation of the metal bolt we set out to model. Consisting of over 1,700 triangles, this is the most complex example in this book so far. Comparatively speaking, however, this number of triangles isn’t anywhere close to the largest number of polygons you’ll encounter when composing larger scenes and more complex objects. In fact, the latest 3D accelerated graphics cards are rated at hundreds of thousands of triangles per second, and that’s for the cheap ones! One of the goals of this chapter is to introduce you to using display lists to optimize rendering speed. Before we can get into a comparison of rendering speeds, however, we will need a way to measure this—a benchmark.

When we get into the subject of display lists, we want you to be able to see that there is a performance difference rather than just take our word for it. So let’s modify our BOLT program slightly. Rather than spinning the object about its axes when arrow keys are pressed, we’ll have it spin repeatedly around just the y-axis in particular. As you might imagine, this turns the program into a continual triangle-generator that we can use to more easily see differences in performance. Listing 10-8 is the changed RenderScene() function used for SPINBOLT.

Listing 10-8 New RenderScene() function to spin bolt around the y-axis

// Called to draw the entire bolt
void RenderScene(void)
        {
        // Clear the window with current clearing color
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Make sure we have the correct matrix mode
        glMatrixMode(GL_MODELVIEW);

        // Rotate and translate the coordinate system
        glRotatef(5.0f, 0.0f, 1.0f, 0.0f);

        // Translate and render the head
        glTranslatef(0.0f, 0.0f, 55.0f);
        RenderHead();

        // Translate back some and render the shaft and thread
        glTranslatef(0.0f, 0.0f, -15.0f);
        RenderShaft();
        RenderThread();

        // Translate back some again for next pass
        glTranslatef(0.0f, 0.0f, -40.0f);

        // Flush drawing commands
        gl Flush();
        }

This new rendering function does not save or restore the matrix state. We use glTranslate to manually restore the translation state of the matrix before leaving the function, but the effects of glRotate are cumulative. This causes the bolt to be rotated around its y-axis by 5? every time the bolt is rendered.

One simple animation technique would be to create a timer, and when the WM_TIMER message is received, invalidate the window causing a redraw. In this manner we can speed up and slow down the animation as desired. Our goal is not simple animation, however, but to get a feel for the rate of the rotations. A reasonable criterion is the amount of time required to spin the bolt completely around the y-axis (360?).

Using WM_TIMER messages would be a poor choice for benchmarking for two reasons. First, your window is not guaranteed to receive all the WM_TIMER messages (the OS could be too busy). And second, if you specify the time intervals, what good does it do to then measure those intervals with any confidence that they truly indicate performance?

What we really want to do is time the interval between the starting and stopping of rendering. This could provide a value that is too small for practical use, so we can just time the interval between a given number of renderings. By repeatedly rendering the scene a number of times and measuring the time it takes to perform these renderings, we have a fairly good benchmark.


Caution:  This Is Only an Approximation!
This benchmark is very informal and uses a method of timing computer programs that’s not accurate enough for publishing important results. We only use it here to demonstrate an easily detectable performance gain when using display lists. To compare your real programs (as well as the two presented here), you should at least have the rest of your system idle when running the test. Many factors can increase or decrease the values you get, but as long as conditions are more or less equal, you will see a time difference between the two bolt-spinning programs.

You might be tempted to just stack together a bunch of calls to RenderScene and obtain the time before and after to calculate the elapsed time. This would work, but closing the application would be very difficult because it would not have the chance to service any other messages (such as WM_CLOSE). The best way to get a Windows program to repeatedly paint its client area is to omit validation of the client area when the WM_PAINT handler is finished. If the client area is still invalid, Windows will just keep posting WM_PAINT messages to your application forever. In the midst of these WM_PAINT messages, other messages such as WM_CLOSE will still appear and be processed.

Listing 10-9 is the WM_PAINT handler for our new program, SPINBOLT.

Listing 10-9 WM_PAINT message handler for SPINBOLT

        // Storage for timing values
        static unsigned long ulStart = 0L;
        static unsigned long ulFinish = 0L;
        static double dTime = 0.0;

        // Storage for performance statistics
        char cBuffer[80];
        RECT cRect; 
 
        …
        …
        …

                // The painting function.  This message sent by Windows
                // whenever the screen needs updating.
                case WM_PAINT:
                        {
                        // Count how many times rendered
                        static iRenderCount = 0;

                        // Get time at beginning of spin
                        if(iRenderCount == 0)
                                ulStart = ulGetProfileTime();

                        // Call OpenGL drawing code
                        RenderScene();

                        // Bring image to front
                        SwapBuffers(hDC);

                        // Increment count. If 71 or over get the finish
                        time
                        iRenderCount++;

                        if(iRenderCount > 71)
                                {
                                iRenderCount = 0;

                                ulFinish = ulGetProfileTime();

                                // Calculate the time in seconds
                                dTime = ulFinish - ulStart;
                                dTime /= 1000.0;
                                }

                        // Display time (be sure and set background colors)
                        sprintf(cBuffer,"%3.1f Seconds for 360 degrees.",
                        dTime);
                        GetClientRect(hWnd,&cRect);
                        SetBkColor(hDC,RGB(0,0,255));
                        SetTextColor(hDC,RGB(255,255,0));
                       TextOut(hDC,0,cRect.bottom-20,cBuffer,strlen
                        (cBuffer));

                        // Do not validate, forcing a continuous repaint
                        }
                        break;

This message handler gets the current system time and counts the number of times it is called. After 71 times, it gets the new time, subtracts the difference, and displays the lapsed time. Remember that our bolt is rotating 5? each time it is rendered, so this technique effectively measures the amount of time it takes to spin the bolt 360?.

The function ulGetProfileTime simply gets the system time in clock ticks and converts it to thousandths of a second. (You can examine this yourself in the source listing if you want, but its operation is not germane to our discussion here.) SPINBOLT’s output is shown in Figure 10-9. The time to spin the bolt around in this example was just under 15 seconds (on a 90MHz Pentium with no hardware 3D acceleration).


Figure 10-9  Output from the SPINBOLT program


Previous Table of Contents Next