从 OpenGL 切换到 GDI
Posted
技术标签:
【中文标题】从 OpenGL 切换到 GDI【英文标题】:Switching from OpenGL to GDI 【发布时间】: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 模式之间的切换并不是一个频繁的操作。它会在用户明确请求时完成(比如每分钟一次,每小时一次,或者永远不会:))
【参考方案1】:
发生在你身上的是,直到现在你都依赖于未定义的行为——实际上它从一开始就不应该起作用,而且你很幸运。在未设置 PFD_SUPPORT_GDI 标志的窗口上设置双缓冲像素格式后,您就不能再将其用于 GDI 操作。
OpenGL 渲染上下文无关紧要! 很多人——我理所当然地相信它的人确实相信它——遭受误解,即 OpenGL 渲染上下文在某种程度上是绑定到特定的 HDC 或 HWND。没有东西会离事实很远。 只要可绘制像素格式与给定的 OpenGL 上下文兼容,该上下文就可以绑定到它。
而且因为您的 OpenGL 渲染上下文和窗口之间没有任何联系,所以销毁和重新创建上下文的所有小动作都没有任何有意义的效果。也许,只是也许,那个小小的舞蹈触发了驱动程序中的一些代码路径,这使得你的非法行为以某种方式起作用。但更有可能的是,你只是为了期待它做一些有用的事情而做了雨舞,而一开始它完全是假的,你可能一开始就没有这样做,达到同样的效果。
我们是不是做错了什么,
是的,是的,你是。你正在做的事情,从一开始就不允许或不应该工作。您只是没有注意到它,因为到目前为止,操作系统/驱动程序还没有利用不允许您这样做而为他们提供的回旋余地。然而,GPU/OS/驱动程序的最新发展现在确实利用了回旋余地,并且只是压过你的代码。
在 OpenGL 中绘制 2d 内容不是一个选项。
为什么?
在 OpenGL 中绘制 2d 内容不是一个选项,反之亦然。此外,该应用程序对性能非常敏感,因此无法将 2d 内容绘制到屏幕外 GDI 图像以将其绘制为 OpenGL 四边形
你真的尝试过分析它吗? 10 块钱说这会表现得很好。
【讨论】:
感谢您的回答。我现在确信我们所做的是未定义的行为。现在我必须为此找到解决方案。 我们的 GDI 模式是多年开发工作的结果。我们试图在 OpenGL 中模拟这种模式。虽然它有效,但我们无法获得与 GDI 模式相同的质量(考虑字体渲染)。根据内容(想想 OLE 对象),使用 OpenGL 的性能也会变慢。如果我们能投入足够的开发时间,这些都可以得到改进,但这是一个长期的解决方案。 我们举一个复杂情况的例子:我们可以使用 GDI 在 120 毫秒内渲染它,而使用 OpenGL 则需要 400 毫秒。请注意,我们的 OpenGL 案例几年来也得到了许多优化,但我们仍然无法接近 GDI 性能。请注意,这不是一般的 GDI/OpenGL 比较。我们的需求和现有架构只是让 GDI 在这里更加优化。无论如何,这不是普通的 GDI:想想在 GDI 之上使用的 AGG 之类的东西。 我们已经可以选择将 3d 内容绘制到 FBO 并将该 FBO 绘制到 GDI。由于客户抱怨 4k 显示器的性能,该选项默认禁用。在我没有 4k 显示器(窗口大小 2200x1000)的系统上,默认情况下会在 4 毫秒内绘制一个简单的案例。如果我启用该选项,它会在 4 毫秒内绘制到 FBO,而将 FBO 绘制到 GDI 缓冲区需要另外 22 毫秒。随着分辨率的提高,它会变慢。 我在想相反的方法:在 DIBSECTION 上创建一个 HDC,用 GDI 绘制到它,然后使用 PBO 流更新一个 GL 纹理,然后绘制到一个四边形。【参考方案2】:在OpenGL on Windows - Generic Implementation and Hardware Implementations 进行一些搜索发现:
OpenGL 和 GDI 图形不能在双缓冲窗口中混合。
应用程序可以直接绘制 OpenGL 图形和 GDI 图形 进入单缓冲窗口,但不进入双缓冲窗口。
还有来自PIXELFORMATDESCRIPTOR
structure
PFD_SUPPORT_GDI:缓冲区支持 GDI 绘图。这面旗帜和 PFD_DOUBLEBUFFER 在当前泛型中是互斥的 实施。
由于您使用的是 OpenGL 1.1,只需将 PFD_SUPPORT_GDI
标志添加到您的 PIXELFORMATDESCRIPTOR pfd
【讨论】:
示例代码只是演示问题的示例。我们不能使用 PFD_SUPPORT_GDI,因为我们需要现代 OpenGL(例如,我们需要几何着色器)。 哈!那么我的建议是只使用 OpenGL,完全不使用 GDI。 据说GDI和OpenGL图形不能混用。而我实际上并没有这样做。我一次只使用 OpenGL 或只使用 GDI 绘制。当 OpenGL 上下文被销毁时,就好像新驱动程序忘记激活底层 GDI 缓冲区一样。 但是你想绘制到同一个窗口缓冲区,记住它的最后状态。可以认为是“混合”。 是的,可以认为是混音。从我研究的资源来看,“混合”的含义并不清楚。自从引入 DWM 时的 Vista 以来,就已经不可能进行广义上的混合了。但是在更广泛的意义上混合没有问题,直到这次 NVidia 发生变化。这就是为什么我想知道它是否是故意的。【参考方案3】:我不会破坏 GL 上下文。相反,我会尝试更改
glViewport(x0,y0,xs,ys);
到某个角落的小区域,甚至隐藏起来
glViewport(0,0,1,1);
这样,即使错误仍然存在,用户也只会在角落里看到像素伪影,而很可能甚至没有注意到它(尤其是当使用与背景相同的颜色渲染时)。
【讨论】:
感谢您的想法。不幸的是,行为仍然相同。即使将 OpenGL 视口调整为 1 像素,GDI 输出也是不可见的。如果我将 OpenGL 视口的大小调整为 1 个像素并破坏 OpenGL 上下文,则行为也是相同的。 @myavuzselim 然后我能想到的最后一件事是您的缓冲区没有交换回 GDI 缓冲区。尝试将按钮添加到您的应用程序,当您单击它时调用SwapBuffers(s_hdc);
,如果 ti 有所作为?还尝试检查 gfx 区域的句柄是否没有改变......(这只是疯狂的猜测,因为在 Win10 中可能存在任何错误)。顺便说一句,我有时有 2 个窗口用于这样的事情,并且仅停靠到主应用程序工作区我想看到的一个(另一个设置为不可见)
不幸的是,调用 SwapBuffers(s_hdc);
不会改变输出,这与我是否销毁 GL 上下文或是否调用 glViewport(0,0,1,1)
无关。以上是关于从 OpenGL 切换到 GDI的主要内容,如果未能解决你的问题,请参考以下文章