We have an application where we use both GDI and OpenGL to draw to the same HWND, but exclusively.
Example:
When switching to 3d mode, we just create an OpenGL context for that HWND and we can draw on it using OpenGL. When switching back to 2d mode, we just destroy the OpenGL context and we can draw to the HWND using GDI.
This worked very well until recently. On Windows 10, for some NVidia cards, this does not work any more if the driver is more recent than 382.05. In this case, when we delete the OpenGL context and draw on the HWND using GDI, the window still displays the last content from OpenGL.
I have checked all available pixel formats. All have the same issue.
Are we doing something wrong, or is it an NVidia bug? Do you see solutions / workarounds?
There is possibility that it is related to NVidia + Intel dual GPU setups, but there is at least one counterexample. Cards on for which we have feedback:
NOT REPRODUCED:
REPRODUCED:
It is not an option to draw 2d content in OpenGL or vice versa. Also, the application is very performance sensitive, such that it is not an option to draw 2d content to an offscreen GDI image in order to draw it as an OpenGL quad. It is also not an option to destroy and recreate the HWND.
Below is a sample application to reproduce the issue. By default, the application shows a blue background with some text in GDI mode. In OpenGL mode a rotating triangle is shown. Space key is used to switch between modes.
#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;
}
Update: @Ripi2 and @datenwolf suggested that switching back to GDI is not allowed once we set a pixel format for the window that does not have PFD_SUPPORT_GDI
flag. This is an excerpt from SetPixelFormat documentation:
Setting the pixel format of a window more than once can lead to significant complications for the Window Manager and for multithread applications, so it is not allowed.
That is a strong sign that they are correct.
Update 2: I had stated that it is not an option to recreate the HWND. But after rethinking, that seems to be the easiest solution to me.
The code:
#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;
}
What happened to you is, that up until now you relied on undefined behaviour – actually it never was supposed to work in the first place, and you were just lucky. Once you've set a doublebuffered pixelformat on a window that does not set the PFD_SUPPORT_GDI flag, you no longer can use it for GDI operations.
The OpenGL rendering context does not matter! A lot of people – it behooves me for what reason those who believe it do believe it – suffer from the misconception, that OpenGL render contexts in some way were tied to a particular HDC or HWND. Nothing could be further from the truth. As long as a drawables pixelformat is compatible to a given OpenGL context, that context can be bound to it.
And because there're no ties between your OpenGL render contexts and the windows, all that little dance of destroying and re-creating the context has no meaningful effect whatsoever. Maybe, just maybe, that little dance triggered some codepath in the driver, which made your illegal actions somehow work. But what's more likely is, that you just did the raindance in expectation for it to do something useful, while it was completely bogus in the first place, and you could just have not done it in the first place, to the same effect.
Are we doing something wrong,
Yes, yes you are. You are doing something, that never was allowed or supposed to work in the first place. You just didn't notice it, because so far OSs/drivers didn't make use of the wiggle room afforded to them by this not being allowed for you. However recent developments in GPU/OS/drivers now do make use of the wiggle room, and just steamroll over your code.
It is not an option to draw 2d content in OpenGL .
Why?
It is not an option to draw 2d content in OpenGL or vice versa. Also, the application is very performance sensitive, such that it is not an option to draw 2d content to an offscreen GDI image in order to draw it as an OpenGL quad
Did you actually try and profile it? 10 bucks says this would perform just fine.
A bit of search at OpenGL on Windows - Generic Implementation and Hardware Implementations reveals:
OpenGL and GDI graphics cannot be mixed in a double-buffered window.
An application can directly draw both OpenGL graphics and GDI graphics into a single-buffered window, but not into a double-buffered window.
And also from PIXELFORMATDESCRIPTOR
structure
PFD_SUPPORT_GDI: The buffer supports GDI drawing. This flag and PFD_DOUBLEBUFFER are mutually exclusive in the current generic implementation.
Since you are using OpenGL 1.1, just add PFD_SUPPORT_GDI
flag to your PIXELFORMATDESCRIPTOR pfd
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With