10

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;
}
Boris
  • 8,551
  • 25
  • 67
  • 120
  • Okay, your posted code has nothing to do with GLFW, so why do you mention it at all? –  May 10 '17 at 07:36
  • 1
    I was using GLFW before. To simplify things I completely removed it from my project (then I added the code above to this topic). But the problem remains the same, with or without GLFW. – Boris May 10 '17 at 07:40

2 Answers2

13

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.
datenwolf
  • 159,371
  • 13
  • 185
  • 298
1

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:

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • I already use wglMakeCurrent before each drawing on the background thread (see code above). – Boris May 10 '17 at 07:35
  • @Boris you never check if the `wglCreateContext,wglMakeCurrent` succeeded In that link In my answer I got problem exactly there so it is worth checking. Also what gfx card and driver you got? Did you try another vendor gfx (to rule out driver bug)? Also did you try `glGetError();` Do your GL calls work (get some string to check it without rendering)? For example like this: [Determine Intel HD Graphics Card Version Via WinAPI](http://stackoverflow.com/a/35826975/2521214) – Spektre May 10 '17 at 08:22
  • Just calling `wglMakeCurrent` on thread A will not do trick if the context is still active on any other thread. It has to be detached beforehand. – datenwolf May 10 '17 at 09:03
  • @Spektre: I don't know if I parse the first sentence in your answer right, but you make it sound like that you needed a separate OpenGL context for each window. If so that would be wrong. A single OpenGL context can be used on an arbitrary number of windows; you can switch the association at any time. – datenwolf May 10 '17 at 09:06
  • @datenwolf it has been some time I did multi GL window app but back in the days using single GL context for more windows did not work (but that could be also a bug on my side) for some cards especially Intel and for some older ATI too. Those apps are still in use and nowadays situation is that nVidia and AMD works old Intel HD does not and newer Intel HD gfx cards/drivers are untested. – Spektre May 11 '17 at 06:35
  • @Spektre: Well, workarounds to issue implementation bugs are a whole different story on their own. – datenwolf May 11 '17 at 07:10
  • @datenwolf yep but OP was not clear enough if the problem is driver bug related or not ... – Spektre May 11 '17 at 07:12
  • 1
    @Spektre: For reference, here's my test program for single-context multiple-window usage: https://github.com/datenwolf/wglarb/blob/master/test/shared.c – datenwolf May 11 '17 at 07:13
  • @datenwolf thanks Will give it a try when I got the time but it will be a while as right now I am hitting a wall with Win7 scheduler and USB transfer threads stalls on some specific machines ... You should also add that link to your answer in case I delete this ... – Spektre May 11 '17 at 07:20