从OpenGL切换到GDI

mya*_*lim 7 c++ opengl winapi gdi nvidia

我们有一个应用程序,我们使用GDI和OpenGL来绘制相同的HWND,但是只有.

例:

  • 最初我们处于二维模式,所以我们使用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;
}
Run Code Online (Sandbox Code Playgroud)

更新:@Ripi2和@datenwolf建议,一旦我们为没有PFD_SUPPORT_GDI标志的窗口设置像素格式,就不允许切换回GDI .这是SetPixelFormat文档的摘录:

多次设置窗口的像素格式会导致Window Manager和多线程应用程序出现严重的复杂情况,因此不允许这样做.

这是一个强有力的迹象,表明它们是正确的.


更新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;
}
Run Code Online (Sandbox Code Playgroud)

dat*_*olf 5

你发生了什么事,到现在为止你依赖于未定义的行为 - 实际上它本来就不应该起作用,而你很幸运.一旦在未设置PFD_SUPPORT_GDI标志的窗口上设置了双缓冲像素格式,就不再可以将其用于GDI操作.

OpenGL渲染上下文无关紧要!很多人 - 因为我相信它确实相信它的理由而让我理解 - 遭受误解,OpenGL以某种方式渲染上下文与特定的HDC或HWND相关联.没有东西会离事实很远.只要drawables pixelformat 给定的OpenGL上下文兼容,该上下文就可以绑定到它.

而且因为你的OpenGL渲染上下文和窗口之间没有联系,所以破坏和重新创建上下文的所有小舞蹈都没有任何有意义的效果.也许,只是也许,那个小小的舞蹈触发了驱动程序中的一些代码路径,这使得你的非法行为在某种程度上起作用.但更有可能的是,你只是期望它做一些有用的事情,虽然它首先是完全虚假的,你可能只是没有做到这一点,达到同样的效果.

我们做错了吗

是的,是的,你是.你正在做的事情,从来没有被允许或应该首先工作.你只是没有注意到它,因为到目前为止操作系统/驱动程序没有利用提供给他们的摆动空间,这是不允许的.然而,GPU/OS /驱动程序的最新发展现在确实利用了摆动空间,并且只是压缩代码.

在OpenGL中绘制2d内容不是一种选择.

为什么?

在OpenGL中绘制2d内容不是一种选择,反之亦然.此外,该应用程序是非常性能敏感的,因此它不能选择将2d内容绘制到屏幕外的GDI图像,以便将其绘制为OpenGL四元组

你真的试过它吗?10块钱说这会表现得很好.