OpenGL drawing in another thread

匿名 (未验证) 提交于 2019-12-03 03:12:02

问题:

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

回答1:

The two basic rules are:

  • Any given OpenGL rendering context can be active at only one thread at a time.
  • And any thread can have only one OpenGL context been made currently active at a time.

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:

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

or

  • Create windows and OpenGL contexts in thread A, make the context(s) inactive in A, pass the handles to thread B and make them active there.


回答2:

you need OpenGL context per each target window ... and also you need to swap contexts before rendering ... see how wglMakeCurrent is used otherwise all the rendering you are performing is done for lastly selected context.

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

There are also other issues to take care of like:



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!