karlschiffmann's picture

Multithreading with glControl exceptions

Hello - I am attempting to adapt my working OpenTK-based C# Windows application (.NET 2.0) which uses two glControls by placing the rendering for each control into separate threads.

I am simply creating two BackgroundWorker threads and in the DoWork() loops am calling the rendering routines for the respective glControls, instead of Invalidating and using the Paint event.

However, immediately when I am setting up the viewport, etc, I am getting exceptions whenever I try and execute OpenGL commands with the glControl.

The glControls are still members of a Windows.Form. Is there something else I need to do get the glControls to work in their separate threads?

Thank you!

Inline Images

Comments

Comment viewing options

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

An OpenGL context can be current on a single thread at a time. To use it from a different thread, you need to make it non-current on the first thread and make it current on the new thread:

  • Use glControl.Context.MakeCurrent(null) to make the context non-current on the calling thread.
  • Use glControl.MakeCurrent() to make the context current on the calling thread. If a different context is already current on this thread, it will be replaced.

GraphicsContext documentation (check the section on MakeCurrent).

karlschiffmann's picture

Ok thanks, I read the section on MakeCurrent - it actually warns that multi-threading is difficult to pull off:
===========================================
If you wish to use OpenGL on multiple threads, you can either:

* create one OpenGL context for each thread, or
* use MakeCurrent() to make move a single context between threads.

Both alternatives can be quite complicated to implement correctly. For this reason, it is usually advisable to restrict all OpenGL commands to a single thread, typically your main application thread, and use asynchronous method calls to invoke OpenGL from different threads.
===========================================

May I describe more of what I am trying to do, and then you can advise me whether or not to abandon that tack?

This is what I am trying to do:
- two separate GLControls in one Windows.Form
- each GLControl has a separate BackgroundWorker thread whose DoWork() loop contains all the OpenGL calls
- the rendering trigger for each GLControl arrives as events on separate threads (via Ethernet data packet arrivals)
- each DoWork() loop waits on an AutoResetEvent which is set by data packet arrival events

Previously, I had both GLControls rendering via Paint() done in the main thread triggered by Invalidate() calls made upon data packet arrivals. This seemed to work well for most platforms, however some versions of Windows with certain graphic cards caused "stuttering" and poor responses in the second GLControl display.

Is the solution to my immediate problem (that you have just answered) simply to call glControl.Context.MakeCurrent(null) from the main thread for each of the two glControls that will have BackgroundWorkers doing their rendering, and then calling glControl.MakeCurrent() in each of the BackgroundWorker threads before starting the DoWork() rendering loops?

Thank you!

the Fiddler's picture
Quote:

Is the solution to my immediate problem (that you have just answered) simply to call glControl.Context.MakeCurrent(null) from the main thread for each of the two glControls that will have BackgroundWorkers doing their rendering, and then calling glControl.MakeCurrent() in each of the BackgroundWorker threads before starting the DoWork() rendering loops?

For plain background threads the answer would be yes. However, I think BackgroundWorker utilizes an internal thread pool which complicates things a little, since DoWork events might be handled by different threads every time (keyword: "I think". I don't know if this is true for certain).

In that case, you might wish to play it safe and release each context at the end of DoWork:

glControl1.Context.MakeCurrent(null);
backgroundWorker1.DoWork += (sender, e) =>
{
    glControl1.MakeCurrent();
    ...
    glControl1.SwapBuffers();
    glControl1.Context.MakeCurrent(null);
};
 
glControl2.Context.MakeCurrent(null);
backgroundWorker2.DoWork += (sender, e) =>
{
    glControl2.MakeCurrent();
    ...
    glControl2.SwapBuffers();
    glControl2.Context.MakeCurrent(null);
};
 
backgroundWorker1.RunWorkerAsync();
backgroundWorker2.RunWorkerAsync();

Edit: fixed DoWork signatures.
Edit2: fixed typos.

karlschiffmann's picture

Yes, I read that the BackgroundWorker utilizes a thread pool. It is not clear to me how the threads are used/allocated during the lifetime of the DoWork() loop however.

I tried your suggestion above of clearing the context at the end of each iteration of the DoWork() loop and there is some improvement: the first glControl now works continuously. But, the second glControl gets a context error on the very first MakeCurrent(null). Now... why would this be? (It is the same code/class for each of the glControl/DoWork/threads.)

From the GraphicsContext documentation (the section on MakeCurrent): "A single GraphicsContext may be current on a single thread at a time."

Do the two controls indeed have separate contexts? If so, any ideas on why the second control can't access itscontext? Otherwise... might they be sharing something?

Next... I will try using normal background threads (not BackgroundWorker), and if that fails, will put everything back into the main thread and use timers for each control to trigger checking for time-to-render events, and rendering in the main thread also. Will keep you posted.

Thank you!

the Fiddler's picture

Every GLControl gets its own context.

The second GLControl should already be non-current by the time MakeCurrent(null) is called, but this shouldn't result in an error condition (at least, this isn't mentioned as an error in MSDN). That said, the GraphicsContextException should contain a message in the essense of "Failed to make context {0} current. Error: {1}". Please post the exception text / stacktrace to identify why the call fails.

karlschiffmann's picture

here is the trace

AttachmentSize
thread_ex_2010may21.JPG249.39 KB
karlschiffmann's picture

Here is the code involved with the above exception for each of the two GLControls:

private void InitRenderThread()
{
if (bwRenderThread != null)
{
//TODO: can't wait for this to end here because we are in UI thread... is this enough?
bwRenderThread.CancelAsync();
}
bwRenderThread = new BackgroundWorker();
bwRenderThread.WorkerReportsProgress = true;
bwRenderThread.WorkerSupportsCancellation = true;
bwRenderThread.DoWork += bw_DoWork;
bwRenderThread.ProgressChanged += bw_ProgressChanged;
bwRenderThread.RunWorkerCompleted += bw_RunWorkerCompleted;

bwAutoResetEvent = new AutoResetEvent(false);

glControl.Context.MakeCurrent(null); // release main threads ownership of this GL control

}
public void Start()
{
bwRenderThread.RunWorkerAsync();
}

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
// update glControl only in this procedure, not any UI items (due to thread ownership conflicts)
while (true)
{
// wait for redraw signal with a 200ms redraw timeout
bwAutoResetEvent.WaitOne(200);
if (bwRenderThread.CancellationPending)
{
e.Cancel = true;
return;
}

glControl.MakeCurrent(); // need to direct all GL commands to this GL control

// do glControl updates (SwapBuffers() is called at the end of the Paint routine)
glControl_Paint(null, null);

// problem: second sonar display is crashing due to "context" loading problems - why?
// we try releasing it here on each loop with
glControl.Context.MakeCurrent(null);
// actually, the second control crashes on the first MakeCurrent(null) in InitRenderThread()

// update any UI thread controls (in FrmSonarDisplay) allowed here
bwRenderThread.ReportProgress(0);
}
}

the Fiddler's picture

(Hint: you can select a section of your post and click on the "C#" button above the text area to highlight it as code.)

Error: 6 translates into "invalid handle" (win32 error code list). Which doesn't really make sense, since MakeCurrent(null) calls wglMakeCurrent(NULL, NULL) internally, which is supposed to release the current context (regardless of handle).There's no mention of how this call works when no context is current, unfortunately.

In any case, it is safe to swallow this exception and continue. According to MSDN,

Quote:

If an error occurs, the wglMakeCurrent function makes the thread's current rendering context not current before returning.

which is what we want (albeit in a non-straightforward way).

I'll have to check what happens when calling MakeCurrent(null) without a current context and maybe add a check to prevent the error in that case. As a workaround (and if indeed this is the cause of the exception), you can use this code:

if (glControl.Context.IsCurrent)
{
    glControl.Context.MakeCurrent(null);
}
karlschiffmann's picture

Ok, swallowing the exception seems to be a workable work-around... so I have the two BackgroundWorker threads rendering the two glControls now!!! Thanks for your help!

A fly in the ointment... using the two BackgroundWorker threads for rendering appears to be very expensive, at least at this point, but not the most expensive alternative. Here are some rough statistics using the Task Manager CPU usage:

18% :: two BackgroundWorker threads doing the rendering if "time-to-paint" flags set
8% :: only one thread, and using timers for each control that check "time-to-paint" flags before rendering, each timer set to twice the maximum desired render rate
58% :: only one thread, and using the Application.Idle event to check "time-to-paint" flags before rendering for each control

There are three applications running: the C# app with the two glControls, and two simulator apps driving the rendering in each control by sending data packets.

karlschiffmann's picture

Hello...

Shall I start another thread for the following questions? My original aim in multi-threading the two glControls was to deal with an undesireable effect seen on some computers and/or graphic cards: the second display window would "stutter" and be less responsive to input and not re-render at the desired rates.

Here is the feedback I have gotten from some of my colleagues: "Looking at the Windows Performance Monitor, the number of system calls/sec is lower on the test version. About 35k call/sec on the previous version, about 20k calls/sec on the test version running at 60 pings/sec. That's still a lot of system calls, probably around 5x too many. Perhaps the OpenGL libraries you're using are inefficient."

The "versions" they are referring to above are both single-threaded, the newer/faster one using Windows.Form Timers to check for time-to-render flags set by data packet arrivals, while the older/slower version simply called glControl.Invalidate() on the data packet arrivals.

When instantiating the glControl, I am using the default constructor... not supplying GraphicsMode or GraphicsContextFlags parameters. Could these possibly have an effect upon those systems exhibiting performance problems for the second glControl?

Thanks in advance for your help.