在另一个线程中绘制 OpenGL

OpenGL drawing in another thread

本文关键字:绘制 OpenGL 线程 另一个      更新时间:2023-10-16

我为Windows创建了一个简单的OpenGL应用程序。它创建一个窗口,然后使用 OpenGL 命令为其绘制一个三角形。这按预期工作。

稍后,我想将我的绘图代码封装到 DLL 中,以便可以在 C# WinForms 应用程序中使用它来绘制到 WinForm。 为此,我将绘图代码移动到单独的类和线程中。我的想法是,我可以将我的类"吸引"到任何现有窗口,并让我的线程绘制到它。

可悲的是,事情似乎并不那么简单。一旦我将窗口创建分离并将内容绘制到不同的线程中,屏幕就会保持全黑状态。绘图调用似乎不再有效。

有没有办法让我的绘图完全独立于窗口创建和主 UI 线程?

编辑:这是一些代码:-)

这是我的渲染器(从 UI 线程调用时有效,从后台线程调用时不起作用):

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

这是我从后台线程调用渲染器的方式:

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

这是我将它们粘合在一起的主要内容:

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

两个基本规则是:

  • 任何给定的 OpenGL 渲染上下文一次只能在一个线程上处于活动状态。
  • 任何线程一次只能有一个 OpenGL 上下文当前处于活动状态。

但是,特定的 OpenGL 上下文和特定窗口(用于将单个 OpenGL 上下文与多个窗口一起使用的 Win32 测试程序)或特定线程之间没有严格的关联。您始终可以将 OpenGL 上下文从一个线程迁移到另一个线程。

两种常用方法是:

  • 在线程 A 中创建一个窗口,将窗口句柄传递给线程 B,然后在那里创建 OpenGL 上下文。

  • 在线程 A 中创建窗口和 OpenGL 上下文,使上下文在 A 中处于非活动状态,将句柄传递给线程 B 并使它们在那里处于活动状态。

您需要每个目标窗口的OpenGL上下文...而且您还需要在渲染之前交换上下文...查看如何使用wglMakeCurrent,否则您正在执行的所有渲染都是针对最后选择的上下文完成的。

因此,在渲染之前的每个渲染线程上,您需要为窗口设置wglMakeCurrent(hdc, hrc)...然后渲染。

还有其他问题需要注意,例如:

  • 什么是英特尔核芯 3000 上正确的 OpenGL 初始化?