I created a simple OpenGL application for Windows. He creates a window, then uses OpenGL commands to draw a triangle. This works as expected.
Later, I would like to encapsulate the drawing code in a DLL so that it can be used in a C # WinForms application for drawing in WinForm. To do this, I moved the drawing code to a separate class and stream. My idea is that I can just βhookβ my class into any existing window and let my stream draw it.
Unfortunately, it does not look so simple. As soon as I separate the creation and drawing of windows into different streams, the screen remains black. Sample drawings no longer work.
Is there a way to make my drawing completely independent of window creation and main user interface thread?
EDIT: Here's the code :-)
This is my renderer (works when called from a UI thread, doesn't work when called from a 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 is my main sticking 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; }