OpenGL Windows Multithreading Issue

Started by
3 comments, last by Erik Rufelt 13 years ago
I'm having trouble getting opengl to work in multiple threads on windows. I followed the sample code from the opengl wiki but Its simply not working for me.

I tried to generate and build a display list on my main thread and within the other thread, both with the same opengl configuration (2 contexts sharing lists). When on the main thread it works perfectly. When on the separate thread, it generates the id but seems to ignore all the commands associated with actually building the list.

I've monitored the processes memory usage during both trials and it is clear that the display list is never generated when called to do so in the other thread.

I've encountered this problem before and gave up on multi-threading opengl, I really want to get it to work this time. Any help is appreciated.

Here is the related code:

DWORD WINAPI Windows::thread_callback(void* arg)
{
Windows* window = static_cast<Windows*> (arg);
wglMakeCurrent(window->hdc, window->thd_hrc);

while (!window->done)
window->ThreadLoop();

if (!wglMakeCurrent(0,0))
error("Could not Thread context", "", "Shutdown Error").print();
if (!wglDeleteContext(window->thd_hrc))
error("Could not delete thread context", "", "Shutdown Error").print();
}
// During Window creation...
if (!(hdc=GetDC(hwd)) ||
!(PixelFormat=ChoosePixelFormat(hdc,&pfd)) ||
!(SetPixelFormat(hdc,PixelFormat,&pfd)) ||
!(hrc=wglCreateContext(hdc)) ||
!(thd_hrc=wglCreateContext(hdc)) )
{
destroy_window();
return false;
}

if (!wglShareLists(hrc, thd_hrc))
{
DWORD errorCode=GetLastError();
LPVOID lpMsgBuf;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),(LPTSTR) &lpMsgBuf, 0, NULL);
MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
LocalFree(lpMsgBuf);
wglDeleteContext(thd_hrc);
destroy_window();
return false;
}

if (!wglMakeCurrent(hdc, hrc))
{
destroy_window();
return false;
}
Advertisement
Only one thread can own the context at any one time. You have to lock out the other thread from taking the context from you mid operation.
"I've encountered this problem before and gave up on multi-threading opengl, I really want to get it to work this time. Any help is appreciated."

Why?
Sig: http://glhlib.sourceforge.net
an open source GLU replacement library. Much more modern than GLU.
float matrix[16], inverse_matrix[16];
glhLoadIdentityf2(matrix);
glhTranslatef2(matrix, 0.0, 0.0, 5.0);
glhRotateAboutXf2(matrix, angleInRadians);
glhScalef2(matrix, 1.0, 1.0, -1.0);
glhQuickInvertMatrixf2(matrix, inverse_matrix);
glUniformMatrix4fv(uniformLocation1, 1, FALSE, matrix);
glUniformMatrix4fv(uniformLocation2, 1, FALSE, inverse_matrix);

Only one thread can own the context at any one time. You have to lock out the other thread from taking the context from you mid operation.


What do you mean specifically by "lock out"? Does this mean that only one thread at a time can make opengl function calls?
You can use OpenGL on different contexts (with shared lists) simultaneously on multiple threads. This works for me:

#include <windows.h>
#include <gl/gl.h>

#pragma comment (lib, "opengl32.lib")

//
DWORD WINAPI GLThreadProc(LPVOID lpParam);

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// Data to send to the other thread on creation
struct ThreadData {
volatile bool exitNow;
HDC hDC;
HGLRC hGLRC;
GLuint listId;
};

// Main
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// Register windowclass
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.lpszClassName = TEXT("MyClass");
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.style = CS_OWNDC;
RegisterClassEx(&wc);

// Create window
HWND hWnd = CreateWindowEx(
WS_EX_OVERLAPPEDWINDOW,
wc.lpszClassName,
TEXT("OpenGL Window"),
WS_OVERLAPPEDWINDOW |
WS_CLIPSIBLINGS |
WS_CLIPCHILDREN,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL
);

// Get DC
HDC hDC = GetDC(hWnd);

// Pixelformat
PIXELFORMATDESCRIPTOR pfd;
ZeroMemory(&pfd, sizeof(pfd));
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags =
PFD_DRAW_TO_WINDOW |
PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER |
PFD_SUPPORT_COMPOSITION;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;

int pf = ChoosePixelFormat(hDC, &pfd);

SetPixelFormat(hDC, pf, &pfd);

// OpenGL contexts
HGLRC hGLRC = wglCreateContext(hDC);
HGLRC hGLRCThread = wglCreateContext(hDC);

// Enable sharing
wglShareLists(hGLRC, hGLRCThread);

// This thread uses the first context
wglMakeCurrent(hDC, hGLRC);

// List id
GLuint listId = glGenLists(1);

// Create the thread that will use the second context to update the display-list
ThreadData data;
data.exitNow = false;
data.hDC = hDC;
data.hGLRC = hGLRCThread;
data.listId = listId;

HANDLE hThread = CreateThread(NULL, 0, GLThreadProc, &data, 0, NULL);

// Main loop
ShowWindow(hWnd, nCmdShow);

while(true) {
MSG msg;
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) != 0) {
if(msg.message == WM_QUIT)
break;
else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else {
// Draw
glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

glRotatef(1.0f, 0.0f, 0.0f, 1.0f);

// Draw the list being generated on the other thread
glCallList(listId);

SwapBuffers(hDC);

Sleep(15);
}
}

// Wait for the thread to exit
data.exitNow = true;
WaitForSingleObject(hThread, INFINITE);

// Clean up
CloseHandle(hThread);

glDeleteLists(listId, 1);

wglMakeCurrent(NULL, NULL);
wglDeleteContext(hGLRC);

UnregisterClass(wc.lpszClassName, hInstance);

return 0;
}

// The thread that updates the list
DWORD WINAPI GLThreadProc(LPVOID lpParam) {
ThreadData *data = (ThreadData*)lpParam;

// Make the provided context current on this thread
wglMakeCurrent(data->hDC, data->hGLRC);

while(!data->exitNow) {
float red = (rand()%1001) / 1000.0f;
float green = (rand()%1001) / 1000.0f;
float blue = (rand()%1001) / 1000.0f;
bool quad = ((rand()%2) == 1);

// Compile display list
glNewList(data->listId, GL_COMPILE);

glColor3f(red, green, blue);
if(quad) {
glBegin(GL_QUADS);
glVertex2f(-1.0f, -1.0f);
glVertex2f(1.0f, -1.0f);
glVertex2f(1.0f, 1.0f);
glVertex2f(-1.0f, 1.0f);
glEnd();
}
else {
glBegin(GL_TRIANGLES);
glVertex2f(-1.0f, -1.0f);
glVertex2f(1.0f, -1.0f);
glVertex2f(0.0f, 1.0f);
glEnd();
}

glEndList();

Sleep(250);
}

wglMakeCurrent(NULL, NULL);

return 0;
}

// Window procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch(msg) {
case WM_DESTROY:
PostQuitMessage(0);
break;

case WM_SIZE:
glViewport(0, 0, LOWORD(lParam), HIWORD(lParam));
break;
}

return DefWindowProc(hWnd, msg, wParam, lParam);
}


Note however that the actual synchronization is up to the driver, and if your thread tasks aren't CPU heavy it might not help a lot. For example, when I try updating a very large texture on a shared context in a separate thread, my main thread still gets stalled. This is probably because data upload to the graphics card is not possible during operation, either in the hardware or in the driver. Updating the large texture in 256x256 tiles one at a time however yields very good results, but these could just as well have been executed on the main thread after a separate thread had done all CPU calculations preparing the texture.
However, if multiple shared contexts fits better, you should definitely be able to use that as long as you properly set up the sharing.

This topic is closed to new replies.

Advertisement