Reputation: 8951
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;
}
Upvotes: 10
Views: 8733
Reputation: 162317
The two basic rules are:
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:
or
Upvotes: 14
Reputation: 51893
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:
Upvotes: 1