I have created a simple OpenGL application for Windows. It creates a window, then uses OpenGL commands to draw a triangle to it. This works as expected.
Later on I would like to encapsulate my drawing code into a DLL, so that it can be used in a C# WinForms application to draw to a WinForm. To do so I moved the drawing code to a seperate class and thread. My idea is, that I can just "atttach" my class to any existing window and let my thread draw to it.
Sadly things seem to be not that simple. Once I decouple window creation and drawing stuff into different threads, the screen stays all black. The drawing calls don't seem to work anymore.
Is there a way to make my drawing completely independent from the window creation and the main UI thread?
EDIT: Here's some code :-)
This is my renderer (works when called from UI thread, does not work when called from background thread):
// Constructor
Renderer::Renderer(HWND hwnd, size_t windowWidth, size_t windowHeight)
:
mContext(hwnd)
{
mWindowWidth = windowWidth;
mWindowHeight = windowHeight;
mHdc = GetDC(hwnd);
// From now on everything is similar to initializing a context on any other hdc
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.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 24;
int iFormat = ChoosePixelFormat(mHdc, &pfd);
SetPixelFormat(mHdc, iFormat, &pfd);
mHrc = wglCreateContext(mHdc);
wglMakeCurrent(mHdc, mHrc);
// Set up OpenGL
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glDisable(GL_TEXTURE_2D);
glLoadIdentity();
glViewport(0, 0, windowWidth, windowHeight);
glOrtho(0, windowWidth, windowHeight, 0, -1, 1);
}
// Draws the scene
void Renderer::Draw()
{
wglMakeCurrent(mHdc, mHrc);
glClear(GL_COLOR_BUFFER_BIT);
glColor4f(1, 0, 1, 1);
glBegin(GL_QUADS);
glColor3f(1.0, 1.0, 1.0);
glVertex3f(0.0f, float(mWindowHeight/2), 0.0f);
glVertex3f(float(mWindowWidth/2), float(mWindowHeight/2), 0.0f);
glVertex3f(float(mWindowWidth/2), 0.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 0.0f);
glEnd();
glFlush();
SwapBuffers(mHdc);
}
This is how I call the renderer from the background thread:
// Constructor
BackgroundRenderer::BackgroundRenderer(HWND hwnd, uint32_t windowWidth, uint32_t windowHeight)
:
mCancelThread(false)
{
// Initialize OpenGL
mRenderer = std::make_shared<Renderer>(hwnd, windowWidth, windowHeight);
// Start rendering thread
mRenderingThread = std::thread(&BackgroundRenderer::BackgroundLoop, this);
}
// Destructor
BackgroundRenderer::~BackgroundRenderer()
{
// Stop rendering thread
mCancelThread = true;
mRenderingThread.join();
}
// The background rendering loop
void BackgroundRenderer::BackgroundLoop()
{
while (!mCancelThread)
{
// Draw stuff
mRenderer->Draw();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
And here's my main gluing it all together:
// Message loop
LONG WINAPI WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
// Window creation
HWND CreateApplicationWindow(char* title, int x, int y, int width, int height, int nCmdShow)
{
HWND hWnd;
WNDCLASS wc;
static HINSTANCE hInstance = 0;
if (!hInstance)
{
hInstance = GetModuleHandle(NULL);
wc.style = CS_OWNDC;
wc.lpfnWndProc = (WNDPROC)WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "OpenGL";
RegisterClass(&wc);
}
hWnd = CreateWindowA("OpenGL", title, WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, x, y, width, height, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
return hWnd;
}
// Main entry point of application
int APIENTRY WinMain(HINSTANCE hCurrentInst, HINSTANCE hPreviousInst, LPSTR lpszCmdLine, int nCmdShow)
{
HWND hWnd = CreateApplicationWindow("Test", 0, 0, 640, 480, nCmdShow);
// This renders from another thread (not working)
auto backgroundRenderer = std::make_shared<BackgroundRenderer>(hWnd, 640, 480);
// This would render in the UI thread (works)
//auto renderer = std::make_shared<Renderer>(hWnd, 640, 480);
MSG msg;
while (GetMessage(&msg, hWnd, 0, 0))
{
// This would render in the UI thread (works)
//renderer->Draw();
TranslateMessage(&msg);
DispatchMessage(&msg);
}
DestroyWindow(hWnd);
return msg.wParam;
}
The two basic rules are:
However there is no strict association between particular OpenGL contexts and particular windows (Win32 test program for using a single OpenGL context with multiple windows), or particular thread. You can always migrate a OpenGL context from one thread to another.
Two usual approaches are:
or
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With