OpenGL drawing in another thread

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; } 
+5
source share
2 answers

Two basic rules:

  • Any given OpenGL rendering context can only be active one thread at a time.
  • And any thread can include only one OpenGL context that is currently active.

However, there is no strict connection between specific OpenGL contexts and specific windows ( Win32 test program for using a single OpenGL context with multiple windows ), or a specific thread. You can always transfer the OpenGL context from one thread to another.

Two common approaches:

  • Create a window in stream A, pass the window handle to stream B, and create an OpenGL context then and there.

or

  • Create windows and OpenGL contexts in stream A, make context (s) inactive in A, pass descriptors to stream B, and activate them there.
+6
source

you need an OpenGL context for each target window ... and you also need to exchange contexts before rendering ... see how wglMakeCurrent used, otherwise all the rendering you do is performed for the last selected context.

So, in each render stream, before rendering, set wglMakeCurrent(hdc, hrc) for the window you need ... and then render.

There are also other issues to keep in mind:

0
source

All Articles