从OpenGL切换到GDI

cl25kdpy  于 2023-10-18  发布在  其他
关注(0)|答案(4)|浏览(172)

我们有一个应用程序,我们使用GDI和OpenGL绘制到同一个HWND,但只使用。
范例:

  • 最初我们是在2d模式下,所以我们使用GDI绘制它
  • 然后我们切换到3D模式,
  • 然后我们切换回2D模式,

当切换到3D模式时,我们只需为该HWND创建一个OpenGL上下文,然后就可以使用OpenGL在其上进行绘制。当切换回2D模式时,我们只需销毁OpenGL上下文,然后使用GDI绘制到HWND。
直到最近,这一直运作得很好。在Windows 10上,对于某些NVidia卡,如果驱动程序的更新时间超过382.05,则此操作不再起作用。在这种情况下,当我们删除OpenGL上下文并使用GDI在HWND上绘制时,窗口仍然显示来自OpenGL的最后内容。
我已经检查了所有可用的像素格式。都有同样的问题。
是我们做错了什么,还是NVIDIA的bug?您是否看到了解决方案/变通办法?
有可能与NVIDIA + Intel双GPU设置有关,但至少有一个反例。我们有反馈的卡片:
未复制:

  • GTX 980 M,单GPU
  • GTX 1060,单GPU

复制:

  • GTX 1060(Forceware 397.31)+ Intel HD Graphics 630
  • Quadro M3000 M(Forceware 387.95)+英特尔核芯显卡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;
}
nkkqxpd9

nkkqxpd91#

发生在你身上的是,到目前为止,你依赖于未定义的行为-实际上,它从来没有被认为是工作摆在首位,你只是幸运。一旦你在一个没有设置PFD_SUPPORT_GDI标志的窗口上设置了一个双缓冲像素格式,你就不能再用它来进行GDI操作了。

OpenGL渲染上下文无关紧要!*很多人-我认为那些相信它的人确实相信它的原因是什么-遭受误解,OpenGL渲染上下文以某种方式与特定的HDC或HWND相关联。没有什么比这更不符合事实了。 只要可绘制像素格式与给定的OpenGL上下文兼容**,该上下文就可以绑定到它。*

而且因为在你的OpenGL渲染上下文和窗口之间没有联系,所有那些破坏和重新创建上下文的小动作都没有任何意义。也许,只是也许,那一小段舞蹈触发了驱动程序中的一些代码路径,这使得你的非法行为不知何故起作用。但更有可能的是,你只是在期望它做一些有用的事情,而它是完全虚假的摆在首位,你可以只是没有这样做摆在首位,以同样的效果。
我们是不是做错了什么,
是的,是的。你正在做的事情,从来没有被允许或应该工作摆在首位。你只是没有注意到它,因为到目前为止,操作系统/驱动程序没有利用这不允许你提供给他们的回旋余地。然而,GPU/操作系统/驱动程序的最新发展现在 * 确实 * 利用了回旋余地,并且只是压倒了你的代码。
在OpenGL中绘制2D内容不是一个选项。
为什么?为什么?
在OpenGL中绘制2D内容不是一个选项,反之亦然。此外,该应用程序对性能非常敏感,因此不能选择将2d内容绘制到屏幕外的GDI图像,以便将其绘制为OpenGL四边形
你真的试过侧写吗?我赌10巴克斯钱这东西会表现得很好

41zrol4v

41zrol4v2#

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即可

bprjcwpo

bprjcwpo3#

我不会破坏GL上下文。相反,我会尝试改变

glViewport(x0,y0,xs,ys);

到某个角落的小区域,甚至是隐藏的,

glViewport(0,0,1,1);

这样,即使bug持续存在,用户也只会在角落里看到像素伪影,甚至很可能没有注意到它(特别是如果渲染与背景颜色相同)。

jhdbpxl9

jhdbpxl94#

我不知道。也许是因为我来自Android编程时代。解决问题的方法很简单。假设你有一个主窗口。然后,您就有了一个内容窗口,它覆盖了您实际绘制的主窗口客户区。然后,您可以销毁此内容窗口,并将其替换为另一个内容窗口来绘制2d图形。并不断地这样做。创建和销毁窗口非常快。
如果你在一台超级慢的机器上;由水提供动力,并且不想连续地创建和销毁窗口。你可以让你的主窗口绘制3d图形。和内容窗口绘制2d图形。然后你可以隐藏内容窗口来显示3d窗口。显示内容窗口,隐藏2d窗口。

相关问题