So I'm setting up my OpenGL context in my Win32 app. I'm setting up a multithreaded environment, with the window message handling in one thread, and OpenGL rendering calls in another. So far, my workflow is as follows:
Thread A:
- Creates Window
- Gets Display Context
- Chooses a Pixel Format
- Creates a temporary OpenGL context and makes it current
- Grabs extension function pointers
- Sets the current context to NULL and deletes the temporary context
- Creates a new context using wglCreateContextAttribsARB
- Starts Thread B
Then, in Thread B:
- wglMakeCurrent using the hdc and hglrc from thread A
Problem is, the wglMakeCurrent in thread B returns false, with getLastError returning 170 (resource in use). Everything I've read implies that this means that the rendering context is already in use in Thread A, but I've explicitly called "wglMakeCurrent(NULL, NULL)" in Thread A before Thread B is even created.
What might be going wrong?
PS. To make things clear, I am not trying to run OpenGL calls from different concurrent threads. Every single OpenGL call will be made from Thread B, with the exception of creating the rendering context before Thread B even exists.
Edit: Here's some source code. This is the function that initializes the window, in Thread A:
Window* Window::init(void)
{
/* If the singleton exists already, just return a pointer to it */
if (singleton)
return singleton;
/* Allocate the singleton and check for errors */
singleton = new Window();
if (singleton == NULL)
{
fatalerror("In Window::init():\n");
fatalmore("Memory allocation failure.\n");
fatalmore("Could not allocate singleton.\n");
_getch();
exit(1);
}
/* Register window class */
WNDCLASSEX wc;
memset(&wc, 0, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = wndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = APP_NAME;
if (!RegisterClassEx(&wc))
{
fatalerror("In Window::init():\n");
fatalmore("Failed to register window class.\n");
fatalmore("Error code: '%i'\n", GetLastError());
clean();
_getch();
exit(1);
}
/* Read settings */
int width = Settings::get()->screenWidth;
int height = Settings::get()->screenHeight;
DWORD exStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
DWORD style = WS_OVERLAPPEDWINDOW;
/* Are we using fullscreen mode? */
if (Settings::get()->fullscreen)
{
width = Settings::get()->fullscreenWidth;
height = Settings::get()->fullscreenHeight;
DEVMODE dm;
memset(&dm, 0, sizeof(DEVMODE));
dm.dmSize = sizeof(DEVMODE);
dm.dmPelsWidth = width;
dm.dmPelsHeight = height;
dm.dmBitsPerPel = 32;
dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
if (ChangeDisplaySettings(&dm, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
{
warning("in Window::init():\n");
warnmore("The requested fullscreen mode is not supported.\n");
warnmore("Will continue in windowed mode.\n");
width = Settings::get()->screenWidth;
height = Settings::get()->screenHeight;
Settings::get()->fullscreen = false;
}
else
{
exStyle = WS_EX_APPWINDOW;
style = WS_POPUP;
}
}
/* Create the window */
singleton->wnd = CreateWindowEx( exStyle,
APP_NAME, APP_NAME,
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | style,
CW_USEDEFAULT, CW_USEDEFAULT,
width, height,
NULL,
NULL,
GetModuleHandle(NULL),
NULL );
if (!singleton->wnd)
{
fatalerror("In Window::init():\n");
fatalmore("Could not create window.\n");
fatalmore("Error code: '%i'\n", GetLastError());
clean();
_getch();
exit(1);
}
/* Get the DC */
singleton->dc = GetDC(singleton->wnd);
if (!singleton->dc)
{
fatalerror("In Window::init():\n");
fatalmore("Failed to get display context.\n");
fatalmore("Error code: '%i'\n", GetLastError());
clean();
_getch();
exit(1);
}
/* Choose a pixel format */
PIXELFORMATDESCRIPTOR pfd;
memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 24;
pfd.cStencilBits = 8;
pfd.iLayerType = PFD_MAIN_PLANE;
int pf = ChoosePixelFormat(singleton->dc, &pfd);
if (!pf)
{
fatalerror("In Window::init():\n");
fatalmore("Failed to choose a pixel format.\n");
fatalmore("Error code: '%i'\n", GetLastError());
clean();
_getch();
exit(1);
}
/* Set the pixel format */
if (!SetPixelFormat(singleton->dc, pf, &pfd))
{
fatalerror("In Window::init():\n");
fatalmore("Failed to set pixel format '%i'.\n", pf);
fatalmore("Error code: '%i'\n", GetLastError());
clean();
_getch();
exit(1);
}
/* Create our fake, temporary OpenGL context */
HGLRC temprc = wglCreateContext(singleton->dc);
if (!temprc)
{
fatalerror("In Window::init():\n");
fatalmore("Failed to create a temporary context.\n");
fatalmore("Error code: '%i'\n", GetLastError());
clean();
_getch();
exit(1);
}
wglMakeCurrent(singleton->dc, temprc);
/* Initialize the OpenGL extensions we need */
if (!initGLCreationExtensions())
{
fatalerror("In Window::init():\n");
fatalmore("Failed to initialize OpenGL context creation extensions.\n");
clean();
_getch();
exit(1);
}
/* Check for OpenGL version */
int majorver = 0, minorver = 0;
glGetIntegerv(GL_MAJOR_VERSION, &majorver);
glGetIntegerv(GL_MINOR_VERSION, &minorver);
if (majorver < 3 || (majorver == 3 && minorver < 1))
{
fatalerror("In Window::init():\n");
fatalmore("OpenGL version 3.1 or higher is required.\n");
clean();
_getch();
exit(1);
}
/* Define context attributes */
int contextAttribs[] =
{
WGL_CONTEXT_MAJOR_VERSION_ARB, majorver,
WGL_CONTEXT_MINOR_VERSION_ARB, minorver,
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
0
};
/* Create the real OpenGL context */
singleton->rc = wglCreateContextAttribsARB(singleton->dc, NULL, contextAttribs);
if (!singleton->rc)
{
fatalerror("In Window::init():\n");
fatalmore("Failed to create OpenGL context.\n");
fatalmore("Error code: '%i'\n", GetLastError());
clean();
_getch();
exit(1);
}
ShowWindow(singleton->wnd, SW_SHOW);
/* Delete the fake context */
wglMakeCurrent(NULL, NULL);
wglDeleteContext(temprc);
info("Successfully created window.\n");
printf("\n");
return singleton;
}
And here's the startup function that is run in Thread B:
bool Window::renderThreadStartupFunc(void)
{
if (!singleton)
return false;
if (!singleton->dc)
return false;
if (!wglMakeCurrent(singleton->dc, singleton->rc))
{
error("In Window::renderThreadStartupFunc():\n");
errormore("Could not make context current.\n");
errormore("Error code: '%i'\n", GetLastError());
return false;
}
if (!initGLExtensions())
{
error("In Window::renderThreadStartupFunc():\n");
errormore("Failed to initialize OpenGL extensions.\n");
return false;
}
return true;
}
Here's the code that's responsible for starting and shutting down Thread B. It runs in a separate class, and uses c++11's std::thread library.
Renderer* Renderer::init(void)
{
/* If the singleton exists already, just return a pointer to it */
if (singleton)
return singleton;
/* Allocate the singleton and check for errors */
singleton = new Renderer();
if (singleton == NULL)
{
fatalerror("In Renderer::init():\n");
fatalmore("Memory allocation failure.\n");
fatalmore("Could not allocate singleton.\n");
_getch();
exit(1);
}
Window::get();
singleton->running = true;
renderThread = new thread(main);
if (renderThread == NULL)
{
fatalerror("In Renderer::init():\n");
fatalmore("Memory allocation failure.\n");
fatalmore("Could not allocate rendering thread.\n");
_getch();
exit(1);
}
Message startup;
startup.type = MT_STARTUP;
singleton->queue.push(startup);
return singleton;
}
void Renderer::clean(void)
{
if (!singleton)
return;
Message shutdown;
shutdown.type = MT_SHUTDOWN;
singleton->queue.push(shutdown);
if (renderThread)
{
renderThread->join();
delete renderThread;
}
}