karlschiffmann's picture

Speeding Up Multiple GLControls

Are there any tips on speeding up multiple controls? When we have two going simultaneously, we notice a larger than proportionate increase in CPU utilization.


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

The best approach would be to use a profiler like SlimTune to find out the exact bottleneck(s).

There is a good chance that your application is spending a disproportionate amount of time in MakeCurrent() (which is said to be one of the slowest calls in OpenGL). If this is the case, it might help to add a check like:

if (!glControl.Context.IsCurrent)
{
     glControl.MakeCurrent();
}

It is also possible to move all contexts into different threads which can result in better performance. For example, for two GLControls you could use three threads (main GUI, render-1, render-2) and avoid the need for MakeCurrent() completely. In this case, all event processing would occur in the main thread, while the two render threads would execute all OpenGL commands (this means that you cannot use the Paint event for OpenGL rendering. Instead, you would have to notify the render thread to repaint the window).

This can result in better performance but it would be best to profile and find the real bottleneck first.

nythrix's picture

I've always wondered about this. How come the drivers know which context to use when gl commands are issued from a certain thread? I believe some sort of invisible "MakeCurrent" is executed anyway, when you switch threads. In that case how come this internal command is faster?
Or have the drivers gone much further than the public OpenGL API and use an object model in which no context needs to be current (like in OpenCL)?

the Fiddler's picture

Mesa3d has a document describing their GL dispatch mechanism. My guess is that most modern drivers (including Mesa), store the current context in thread-local storage, if possible.

karlschiffmann's picture

Thank you for your rapid response & clear instructions! I will try this out over the next couple of days...

karlschiffmann's picture

Ok, it turns out MakeCurrent() was using a significant amount of time, and checking for it the current context eliminated the CPU problems with mulitple controls we were experiencing. By the way, I found the SlimTune profiler fairly useful and very easy to use. Thanks for these tips!

On to the next issue however...

We are finding that in Windows 7 at higher refresh rates (> 40 Hz), the second control starts to "freeze" and no drawing occurs unless we take the window and move it around the screen. This could be a threading issue necessitating the UI thread to be separate from the two controls... or could something else be involved?

I am not using the latest OpenTK version, could this be related? (I am currently using 0.9.9-3)

Thank you.

the Fiddler's picture

GLControl has remained mostly unchanged between 0.9.9-3 and 1.0.0-rc1 but upgrading wouldn't hurt. It should be a matter of replacing the references and rebuilding the project.

If I understand correctly, you are using continuous rather than event-based rendering. What kind of "game-loop" are you using? (The most common ones are the "Application.Idle" loop and the "Invalidate()" loop). It is possible that the first loop is stealing all CPU time, to the point that the second loop doesn't have a chance to run.

If you are using an Invalidate-loop, adding a few calls to Application.DoEvents() might help (unfortunately, this method allocates memory and may impact performance). For Idle-based loops, you'll have to make sure that no GLControl steals the CPU completely. For example, the following code will cause one of the two GLControls to freeze:

Application.Idle += (sender, e) =>
{
    while (glControl1.IsIdle)
    {
        glControl1.Render();
        glControl1.SwapBuffers();
    }
};
 
Application.Idle += (sender, e) =>
{
    while (glControl2.IsIdle)
    {
        glControl2.Render();
        glControl2.SwapBuffers();
    }
};

The Idle handlers are called sequentially and the first while-loop ensures that the second Idle handler is not called until it's too late (IsIdle typically becomes false for both GLControls at the same time).

Something like this might work better (not tested):

Application.Idle += (sender, e) =>
{
    do
    {
        bool idle1 = glControl1.IsIdle;
        bool idle2 = glControl2.IsIdle;
 
        if (idle1)
        {
            glControl1.Render();
            glControl1.SwapBuffers();
        }
 
        if (idle2)
        {
            glControl2.Render();
            glControl2.SwapBuffers();
        }
    } while (idle1 && idle2);
};

The trick in this case is making this code scale to more GLControls as needed.

Edit: of course, threaded rendering would also solve this issue, at the cost of slightly more complexity (the render threads won't be able to call WinForms methods directly, you'd have to use BeginInvoke() and the like).

karlschiffmann's picture

We are event-driven, upon receiving UDP packets containing sonar data of the ocean floor, we are calling glControl.Invalidate() on the respective control. Each of the two controls has its own packet event stream. We can vary the frequency of the packet generation, thus can see that the higher speeds seems to bother Windows 7. Windows XP does not exhibit this behavior, but depending upon the type of graphics card and the vertical sync settings, other problems can occur (slow response to user input).

Could you please say something more about the Invalidate() strategy using Application.DoEvents() that you mentioned?

As well about BeginInvoke() should we decide to thread the rendering separately?

Thank you!