【发布时间】:2018-11-08 06:46:42
【问题描述】:
我们有一个应用程序,我们同时使用 GDI 和 OpenGL 来绘制相同的 HWND,但只使用它。
例子:
- 最初我们处于 2d 模式,因此我们使用 GDI 对其进行绘制
- 然后我们切换到 3d 模式并使用 OpenGL 进行绘制
- 然后我们切换回 2d 模式并使用 GDI 对其进行绘图
当切换到 3d 模式时,我们只需为该 HWND 创建一个 OpenGL 上下文,然后我们可以使用 OpenGL 在其上绘制。当切换回 2d 模式时,我们只需销毁 OpenGL 上下文,然后我们可以使用 GDI 绘制到 HWND。
直到最近,这一直运作良好。在 Windows 10 上,对于某些 NVidia 卡,如果驱动程序比 382.05 更新,这将不再起作用。在这种情况下,当我们删除 OpenGL 上下文并使用 GDI 在 HWND 上绘制时,窗口仍然显示来自 OpenGL 的最后内容。
我检查了所有可用的像素格式。都有同样的问题。
是我们做错了什么,还是 NVidia 的错误?您看到解决方案/解决方法了吗?
有可能与 NVidia + Intel 双 GPU 设置有关,但至少有一个反例。我们有反馈的卡片:
未复制:
- GTX 980M,单 GPU
- GTX 1060,单 GPU
转载:
- GTX 1060 (Forceware 397.31) + Intel HD Graphics 630
- Quadro M3000M (Forceware 387.95) + Intel HD Graphics P530
- Qudrao K110M + Intel HD 4600
- Quadro P3000 + Intel HD 630
- Quadro M4000(Forceware 385.90),单 GPU
在 OpenGL 中绘制 2d 内容不是一个选项,反之亦然。此外,该应用程序对性能非常敏感,因此无法将 2d 内容绘制到屏幕外 GDI 图像以将其绘制为 OpenGL 四边形。它也不是销毁和重新创建 HWND 的选项。
以下是重现该问题的示例应用程序。默认情况下,应用程序在 GDI 模式下显示带有一些文本的蓝色背景。在 OpenGL 模式下,会显示一个旋转三角形。空格键用于切换模式。
#include <windows.h>
#include <GL/gl.h>
#include <iostream>
#pragma comment( lib, "OpenGL32.lib" )
HWND s_hwnd = 0;
HDC s_hdc = 0;
HGLRC s_hglrc = 0;
bool s_quit = false;
static HGLRC createContext(HWND hwnd, HDC hdc)
{
PIXELFORMATDESCRIPTOR pfd;
memset(&pfd, 0, sizeof(pfd));
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |
PFD_GENERIC_ACCELERATED /*| PFD_DOUBLEBUFFER*/;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
int pf = ChoosePixelFormat(hdc, &pfd);
SetPixelFormat(hdc, pf, &pfd);
return wglCreateContext(hdc);
}
static void display()
{
if (s_hglrc)
{
/* rotate a triangle around */
glClear(GL_COLOR_BUFFER_BIT);
glRotatef(1.0f, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glIndexi(1);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(0.0f, 0.8f);
glIndexi(2);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex2f(-0.8f, -0.8f);
glIndexi(3);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex2f(0.8f, -0.8f);
glEnd();
glFlush();
SwapBuffers(s_hdc);
}
else
{
HBRUSH brush = CreateSolidBrush(RGB(0, 0, 255));
RECT rect;
GetClientRect(s_hwnd, &rect);
FillRect(s_hdc, &rect, brush);
DeleteObject(brush);
DrawText(s_hdc, L"This is GDI", -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
GdiFlush();
}
}
static void toggle_between_GDI_and_OpenGL()
{
if (!s_hglrc)
{
s_hglrc = createContext(s_hwnd, s_hdc);
wglMakeCurrent(s_hdc, s_hglrc);
std::cout << "Renderer: " << glGetString(GL_RENDERER) << std::endl;
}
else
{
wglMakeCurrent(NULL, NULL);
wglDeleteContext(s_hglrc);
s_hglrc = 0;
}
}
LONG WINAPI WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_ERASEBKGND:
return 0;
case WM_PAINT:
display();
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
return 0;
case WM_TIMER:
display();
return 0;
case WM_SIZE:
glViewport(0, 0, LOWORD(lParam), HIWORD(lParam));
PostMessage(hWnd, WM_PAINT, 0, 0);
return 0;
case WM_CHAR:
switch (wParam) {
case 27: /* ESC key */
s_quit = true;
break;
case ' ':
toggle_between_GDI_and_OpenGL();
PostMessage(hWnd, WM_PAINT, 0, 0);
break;
}
return 0;
case WM_CLOSE:
s_quit = true;
return 0;
case WM_QUIT:
s_quit = true;
return 0;
}
return (LONG)DefWindowProc(hWnd, uMsg, wParam, lParam);
}
static HWND CreateOpenGLWindow()
{
HWND hWnd;
WNDCLASS wc;
static HINSTANCE hInstance = 0;
/* only register the window class once - use hInstance as a flag. */
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 = L"OpenGL";
if (!RegisterClass(&wc)) {
MessageBox(NULL, L"RegisterClass() failed: Cannot register window class.", L"Error", MB_OK);
return NULL;
}
}
hWnd = CreateWindow(L"OpenGL", L"GDI / OpenGL switching", WS_OVERLAPPEDWINDOW |
WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
0, 0, 256, 256, NULL, NULL, hInstance, NULL);
if (hWnd == NULL) {
MessageBox(NULL, L"CreateWindow() failed: Cannot create a window.",
L"Error", MB_OK);
return NULL;
}
return hWnd;
}
void executeApplication()
{
s_hwnd = CreateOpenGLWindow();
if (s_hwnd == NULL)
exit(1);
s_hdc = GetDC(s_hwnd);
//toggle_between_GDI_and_OpenGL(); // initialize OpenGL
ShowWindow(s_hwnd, SW_SHOW);
UpdateWindow(s_hwnd);
SetTimer(s_hwnd, 1, 50, NULL);
while (1) {
MSG msg;
while (PeekMessage(&msg, s_hwnd, 0, 0, PM_NOREMOVE)) {
if (!s_quit && GetMessage(&msg, s_hwnd, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
goto quit;
}
}
if (s_quit)
goto quit;
}
quit:
wglMakeCurrent(NULL, NULL);
if (s_hglrc)
toggle_between_GDI_and_OpenGL(); // uninitialize OpenGL
DestroyWindow(s_hwnd);
DeleteDC(s_hdc);
}
int APIENTRY WinMain(HINSTANCE hCurrentInst, HINSTANCE hPreviousInst, LPSTR lpszCmdLine, int nCmdShow)
{
executeApplication();
return 0;
}
int main()
{
executeApplication();
return 0;
}
更新:@Ripi2 和@datenwolf 建议一旦我们为没有PFD_SUPPORT_GDI 标志的窗口设置像素格式,就不允许切换回GDI。这是SetPixelFormat文档的摘录:
多次设置窗口的像素格式可能会导致窗口管理器和多线程应用程序变得非常复杂,因此不允许这样做。
这是一个强烈的信号,表明他们是正确的。
更新 2:我已经说过重新创建 HWND 不是一个选项。但经过重新考虑,这似乎是我最简单的解决方案。
代码:
#include <windows.h>
#include <GL/gl.h>
#include <iostream>
#pragma comment( lib, "OpenGL32.lib" )
HWND s_mainWnd = 0;
HWND s_childWnd = 0;
HGLRC s_hglrc = 0;
bool s_isOpenGLMode = false;
bool s_quit = false;
static HWND CreateChildWindow(HWND hWndParent);
static HGLRC createContext(HWND hwnd, HDC hdc)
{
PIXELFORMATDESCRIPTOR pfd;
memset(&pfd, 0, sizeof(pfd));
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |
PFD_GENERIC_ACCELERATED /*| PFD_DOUBLEBUFFER*/;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
int pf = ChoosePixelFormat(hdc, &pfd);
SetPixelFormat(hdc, pf, &pfd);
return wglCreateContext(hdc);
}
static void display()
{
HDC hdc = GetDC(s_childWnd);
if (s_isOpenGLMode)
{
/* rotate a triangle around */
glClear(GL_COLOR_BUFFER_BIT);
glRotatef(1.0f, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glIndexi(1);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(0.0f, 0.8f);
glIndexi(2);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex2f(-0.8f, -0.8f);
glIndexi(3);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex2f(0.8f, -0.8f);
glEnd();
glFlush();
SwapBuffers(hdc);
}
else
{
HBRUSH brush = CreateSolidBrush(RGB(0, 0, 255));
RECT rect;
GetClientRect(s_childWnd, &rect);
FillRect(hdc, &rect, brush);
DeleteObject(brush);
DrawText(hdc, L"This is GDI", -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
GdiFlush();
}
DeleteDC(hdc);
}
static void toggle_between_GDI_and_OpenGL()
{
if (!s_isOpenGLMode)
{
DestroyWindow(s_childWnd);
s_childWnd = CreateChildWindow(s_mainWnd);
ShowWindow(s_childWnd, SW_SHOW);
HDC hdc = GetDC(s_childWnd);
s_hglrc = createContext(s_childWnd, hdc);
wglMakeCurrent(hdc, s_hglrc);
DeleteDC(hdc);
std::cout << "Renderer: " << glGetString(GL_RENDERER) << std::endl;
RECT rect;
GetClientRect(s_childWnd, &rect);
glViewport(0, 0, max(rect.left, rect.right), max(rect.top, rect.bottom));
}
else
{
if (s_hglrc)
{
wglMakeCurrent(NULL, NULL);
wglDeleteContext(s_hglrc);
s_hglrc = 0;
}
DestroyWindow(s_childWnd);
s_childWnd = CreateChildWindow(s_mainWnd);
ShowWindow(s_childWnd, SW_SHOW);
}
s_isOpenGLMode = !s_isOpenGLMode;
}
LONG WINAPI WindowProc_MainWnd(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_TIMER:
display();
return 0;
case WM_CHAR:
switch (wParam) {
case 27: /* ESC key */
s_quit = true;
break;
case ' ':
toggle_between_GDI_and_OpenGL();
PostMessage(hWnd, WM_PAINT, 0, 0);
break;
}
return 0;
case WM_CLOSE:
case WM_QUIT:
s_quit = true;
return 0;
case WM_SIZE:
if (s_childWnd)
MoveWindow(s_childWnd, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
break;
}
return (LONG)DefWindowProc(hWnd, uMsg, wParam, lParam);
}
LONG WINAPI WindowProc_ChildWnd(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_ERASEBKGND:
return 0;
case WM_PAINT:
display();
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
return 0;
case WM_SIZE:
if (s_hglrc && s_isOpenGLMode)
glViewport(0, 0, LOWORD(lParam), HIWORD(lParam));
PostMessage(hWnd, WM_PAINT, 0, 0);
return 0;
}
return (LONG)DefWindowProc(hWnd, uMsg, wParam, lParam);
}
static HWND CreateMainWindow()
{
static HINSTANCE hInstance = 0;
if (!hInstance)
{
hInstance = GetModuleHandle(NULL);
WNDCLASS wc;
wc.style = CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = (WNDPROC)WindowProc_MainWnd;
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 = L"MainWindow";
if (!RegisterClass(&wc)) {
MessageBox(NULL, L"RegisterClass() failed: Cannot register window class.", L"Error", MB_OK);
return NULL;
}
}
HWND hWnd = CreateWindow(L"MainWindow", L"GDI / OpenGL switching", WS_OVERLAPPEDWINDOW |
WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
300, 300, 256, 256, NULL, NULL, hInstance, NULL);
if (hWnd == NULL) {
MessageBox(NULL, L"CreateWindow() failed: Cannot create a window.",
L"Error", MB_OK);
return NULL;
}
return hWnd;
}
static HWND CreateChildWindow(HWND hWndParent)
{
static HINSTANCE hInstance = 0;
/* only register the window class once - use hInstance as a flag. */
if (!hInstance)
{
hInstance = GetModuleHandle(NULL);
WNDCLASS wc;
wc.style = CS_OWNDC;
wc.lpfnWndProc = (WNDPROC)WindowProc_ChildWnd;
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 = L"ChildWindow";
if (!RegisterClass(&wc)) {
MessageBox(NULL, L"RegisterClass() failed: Cannot register window class.", L"Error", MB_OK);
return NULL;
}
}
RECT rect;
GetClientRect(hWndParent, &rect);
HWND hWnd = CreateWindow(L"ChildWindow", L"ChildWindow", WS_CHILD,
0, 0, max(rect.left, rect.right), max(rect.top, rect.bottom), hWndParent, NULL, hInstance, NULL);
if (hWnd == NULL) {
MessageBox(NULL, L"CreateWindow() failed: Cannot create a window.",
L"Error", MB_OK);
return NULL;
}
return hWnd;
}
void executeApplication()
{
s_mainWnd = CreateMainWindow();
if (s_mainWnd == NULL)
exit(1);
s_childWnd = CreateChildWindow(s_mainWnd);
//toggle_between_GDI_and_OpenGL(); // initialize OpenGL
ShowWindow(s_mainWnd, SW_SHOW);
ShowWindow(s_childWnd, SW_SHOW);
UpdateWindow(s_mainWnd);
UpdateWindow(s_childWnd);
SetTimer(s_mainWnd, 1, 50, NULL);
while (1) {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
if (!s_quit && GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
goto quit;
}
}
if (s_quit)
goto quit;
}
quit:
if (s_hglrc)
{
wglMakeCurrent(NULL, NULL);
wglDeleteContext(s_hglrc);
}
DestroyWindow(s_childWnd);
DestroyWindow(s_mainWnd);
}
int APIENTRY WinMain(HINSTANCE hCurrentInst, HINSTANCE hPreviousInst, LPSTR lpszCmdLine, int nCmdShow)
{
executeApplication();
return 0;
}
int main()
{
executeApplication();
return 0;
}
【问题讨论】:
-
您应该测量创建/删除上下文与使用
glReadPixels并将其用作 GDI 中的位图与将 GDI 作为四边形传递给 GPU 的性能。 -
我已经知道 glReadPixels 会导致我们的应用程序大幅减速,尤其是在 4k 显示器上。我希望在 OpenGL 中使用 GDI 缓冲区作为四边形具有类似的性能。这将影响应用程序的每一帧。然而,在我们的应用程序中,2d 和 3d 模式之间的切换并不是一个频繁的操作。它会在用户明确请求时完成(比如每分钟一次,每小时一次,或者永远不会:))
标签: c++ winapi opengl nvidia gdi