Multi-threading with Detonator3 Drivers

Apologies for this being a long post but I wanted to include some code to make it clear what I am trying.

I wish to have my OpenGL rendering in a seperate thread to my main app. Now I know that a single OpenGL context can only be accessed by a single thread but that isn’t my problem. The code below creates a new child thread which in turn creates a window, sets up the context and repeatedly clears the background. It then deletes the context and the window ready for the next time the thread is started. This code works repeatedly on all OpenGL hardware I’ve tried except nVIDIA GeForce with Detonator3 drivers. With that particular setup the app is fine the first time the thread is created but crashes the second time I try and create the thread. The call stack shows that there is a null function pointer in the nVIDIA ICD. This happens with NT4(SP6a) and Win2K.

Has anyone else seen this with the Detonator3 drivers or can you see a problem with my code? I can fix the crash by installing Hercules drivers for the same GeForce card but that doesn’t help my customers.

Many thanks
Rob

Code snippet from basic MFC AppWizard app follows (note I’ve simplified the code by removing checks on return values - I have checked all function returns and they are valid)…

volatile BOOL thread_running = FALSE;
volatile BOOL stop_thread = TRUE;

static const char szViewClassName [] = {“ThreadTestWindowClass”};
static const char szViewWindowName [] = {“ThreadTest”};

BOOL RegisterWindowClass (void);
HWND CreateViewWindow ( int width, int height, BOOL bShow);
UINT RenderThread(LPVOID param);

// Two message handlers to control creation and deletion of the thread
void CMainFrame::OnGo()
{
// Don’t start if the thread is running
if (!thread_running)
{
stop_thread = FALSE;

  // The thread is not running so start it
  AfxBeginThread(RenderThread, NULL);

  // Wait until the thread has actually started
  while (!thread_running)
  {
     _sleep(0);
  }

}
}

void CMainFrame::OnStop()
{
// Don’t stop unless the thread is running
if (thread_running)
{
// Flag the thread to stop
stop_thread = TRUE;

  // Wait for the thread to stop
  while (thread_running)
  {
     _sleep(0);
  }

}
}

// This thread is controlled by the parent process - created by AfxBeginThread
UINT RenderThread(LPVOID param)
{
GLclampf red = 0.0F, green = 0.0F, blue = 0.0F;

HWND hWnd = NULL;
HDC hdc = NULL;
HGLRC hrc = NULL;

// Flag thread as running
thread_running = TRUE;

// Register our window class
RegisterWindowClass ();

// Create the window
hWnd = CreateViewWindow (200, 200, TRUE);

// Get device context
hdc = GetDC(hWnd);

// Define required pixel format
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd
1, // version number
PFD_DRAW_TO_WINDOW | // support window
PFD_SUPPORT_OPENGL | // support OpenGL
PFD_DOUBLEBUFFER, // double buffered
PFD_TYPE_RGBA, // RGBA type
24, // 24-bit color depth
0, 0, 0, 0, 0, 0, // color bits ignored
0, // no alpha buffer
0, // shift bit ignored
0, // no accumulation buffer
0, 0, 0, 0, // accum bits ignored
32, // 32-bit z-buffer
0, // no stencil buffer
0, // no auxiliary buffer
PFD_MAIN_PLANE, // main layer
0, // reserved
0, 0, 0 // layer masks ignored
};
int iPixelFormat;

// get the best available match of pixel format for the device context
iPixelFormat = ChoosePixelFormat(hdc, &pfd);

// make that the pixel format of the device context
SetPixelFormat(hdc, iPixelFormat, &pfd);

// Create the OpenGL context
hrc = wglCreateContext(hdc);

// Make OpenGL context current
wglMakeCurrent(hdc, hrc);

// while the thread is active do something simple
while ( !stop_thread )
{
// Clear background and swap - simple OpenGL rendering
glClearColor(red, green, blue, 0.0F);
glClear(GL_COLOR_BUFFER_BIT);
red += 0.005F;
green += 0.0005F;
blue += 0.000005F;
if (red > 1.0F) red = 0.0F;
if (green > 1.0F) green = 0.0F;
if (blue > 1.0F) blue = 0.0F;
glFlush();
SwapBuffers(hdc);
}
glFinish();

// Delete the OpenGL context
wglDeleteContext(hrc);

// Release the device context
ReleaseDC(hWnd, hdc);

// Destroy the window
DestroyWindow(hWnd);

thread_running = FALSE;

return 0;
}

// Register a window class for channels which require one
BOOL RegisterWindowClass (void)
{
WNDCLASS wc;

/* === Register a class for the top level window === */
wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = DefWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = GetModuleHandle(NULL);
wc.hIcon = NULL;
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = szViewClassName;

return (RegisterClass(&wc) == 0);
}

// Create and show a window for OpenGL rendering
HWND CreateViewWindow ( int width, int height, BOOL bShow)
{
DWORD dwStyle = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE | WS_POPUP;

HWND hWnd = CreateWindowEx(WS_EX_TOPMOST,
szViewClassName,
szViewWindowName,
dwStyle,
0,
0,
width,
height,
(HWND)NULL,
(HMENU)NULL,
GetModuleHandle(NULL),
NULL);

if ((hWnd != NULL) && (bShow))
{
ShowWindow (hWnd, SW_SHOW);
}

return hWnd;
}

Hi,

Just a thought, I don’t know for sure.

I took a look at a GL init/end source I have here ; and I saw it has something you don’t have in your end sequence ; perhaps, this causes the bug : it seems you don’t tell GL to release the rendering context before you delete it… so it may mess up the internal state !..

You should try with :

// Release the OpenGL context
wglMakeCurrent(NULL,NULL);

// Delete the OpenGL context
wglDeleteContext(hrc);

Hope this helps.

Nico

Thanks Nico, that is a line I’d missed but unfortunately even with that change the Detonator3 drivers still crash on the second time round.

Many Thanks
Rob

If it is just nVidia drivers that are causing problems you should contact them. Have you been able to get two applications to run at the same time using the GL hardware?

/skw|d

Have you tried to execute many times exactly the same code in the ‘main’ thread of your app ?

I was just thinking that as you don’t have 2 threads using OpenGL in distinct contexts, the behaviour of your code should be the same in whatever thread it is…

So, I believe it should crash as well in the main thread !?! If so, that could help debugging… Maybe

Thanks again Nico.

It turns out that if I perform the follwoing in the parent process…

HWND hWnd = ::GetDesktopWindow();
HDC hdc = ::GetDC(hWnd);
SetPixelFormat(hdc, 1, NULL);
::ReleaseDC(hWnd, hdc);

Then all is well. Looks like the ICD has to be first called from the parent process and then it can be called from child threads with no problems. When nVIDIA get back to me I’ll let them know

Thanks
Rob

I have observed this bug as well. My solution was to create an OpenGL context in the main thread first, make it current, delete it, and then start the new thread which creates its own context.