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; }