从OpenGL切换到GDI

9
我们有一个应用程序,在其中我们同时使用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的bug?您是否看到解决方案/解决方法?
有可能与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也不是一个选择。
下面是一个示例应用程序,可复现该问题。默认情况下,应用程序显示带有一些文本的蓝色背景。在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的性能,并加以比较。 - Ripi2
我已经知道glReadPixels会导致我们的应用程序明显减速,特别是在4k显示器上。我预计使用GDI缓冲区作为OpenGL中的四边形将具有类似的性能。这将影响应用程序的每一帧。然而,在我们的应用程序中,2D和3D模式之间的切换不是频繁的操作。它将在用户明确请求时执行(例如每分钟一次,每小时一次或从不执行:)) - myavuzselim
4个回答

6
你所遇到的问题是,直到现在你一直依赖于未定义的行为——实际上一开始它就不应该工作,你只是幸运而已。一旦你在没有设置PFD_SUPPORT_GDI标志的窗口上设置了双缓冲像素格式,你就不能再将其用于GDI操作了。 OpenGL渲染上下文并不重要!许多人——我不知道他们为什么会相信这种观点——误解了OpenGL渲染上下文与特定HDC或HWND有某种联系。事实并非如此。只要可绘制的像素格式与给定的OpenGL上下文兼容,那么该上下文就可以绑定到它上面。
由于你的OpenGL渲染上下文与窗口之间没有任何关系,所有销毁和重新创建上下文的操作都没有任何意义。也许,这个操作会在驱动程序中触发一些代码路径,使你的非法操作在某种程度上起作用。但更有可能的是,你只是做了一个完全无意义的操作,期望它能产生一些有用的效果,而事实上它根本就是错误的,你最好一开始就不要这样做,效果是一样的。

我们做错了什么?

是的,你做了一些从来没有被允许或者说本来就不应该工作的事情。你之前没有注意到这一点,是因为到目前为止操作系统/驱动程序并没有利用这种不被允许的余地来对你进行限制。然而,最近GPU/操作系统/驱动程序的发展现在利用了这种余地,并且直接破坏了你的代码。

在OpenGL中绘制2D内容不是一个选择。

为什么?

在OpenGL中绘制2D内容或反之亦然都不是一个选择。此外,该应用程序对性能非常敏感,不能将2D内容绘制到离屏GDI图像上,然后再将其绘制为OpenGL四边形。

你真的试过并测试过吗?我打赌这样做的性能会很好。

谢谢您的回答。我现在相信我们所做的是未定义行为。现在我必须找到一个解决方案。 - myavuzselim
我们的GDI模式是多年开发工作的结果。我们尝试在OpenGL中模仿这种模式。虽然它能够运行,但我们无法获得与GDI模式相同的质量(考虑字体渲染)。性能也可能因内容而变慢(考虑OLE对象)。如果我们足够投入开发时间,所有这些问题都可以得到改善,但那是一个长期的解决方案。 - myavuzselim
让我们来看一个复杂案例的例子:使用GDI可以在120毫秒内呈现,而使用OpenGL则需要400毫秒。请注意,我们的OpenGL案例也经过了多年的优化,但我们仍无法接近GDI的性能。请注意,这不是一般的GDI / OpenGL比较。我们的需求和现有架构使GDI在这里更加优越。无论如何这也不是纯粹的GDI:可以想想类似于AGG运用在GDI之上的情况。 - myavuzselim
1
我们已经有一个选项可以将3D内容绘制到FBO,并将该FBO绘制到GDI。由于客户在4K显示器上的性能投诉,该选项默认情况下被禁用。在我的系统上(窗口大小为2200x1000,没有4K显示器),默认情况下可以在4毫秒内绘制一个简单的案例。如果我启用该选项,则可以在4毫秒内将其绘制到FBO中,并将FBO绘制到GDI缓冲区需要另外22毫秒。随着分辨率的增加,速度会变慢。 - myavuzselim
我在考虑另一种方式:创建一个DIBSECTION上的HDC,使用GDI进行绘制,然后使用PBO流式传输来更新GL纹理,最后将其绘制到四边形上。 - datenwolf
我近期不能将此应用于生产环境。有一些现有机制使它更加困难。但我可以制作一个玩具实现来衡量性能。将来,这种方法也可以帮助我们摆脱这些“现有机制”(这是我们的计划)。谢谢! - myavuzselim

4
Windows上的OpenGL - 通用实现和硬件实现 进行一些搜索后得到以下结果:

OpenGL 和 GDI 图形无法在双缓冲窗口中混合绘制。

应用程序可以直接将 OpenGL 图形和 GDI 图形绘制到单缓冲窗口中,但不能在双缓冲窗口中绘制。

还从 PIXELFORMATDESCRIPTOR 结构 中找到了以下内容:

PFD_SUPPORT_GDI: 缓冲区支持 GDI 绘图。在当前的通用实现中,此标志和 PFD_DOUBLEBUFFER 是互斥的。

由于您正在使用的是OpenGL 1.1,所以只需将 PFD_SUPPORT_GDI 标志添加到您的 PIXELFORMATDESCRIPTOR pfd 中即可。

示例代码只是为了演示问题而提供的示例。我们不能使用PFD_SUPPORT_GDI,因为我们需要现代OpenGL(例如,我们需要几何着色器)。 - myavuzselim
哈!那么我的建议是只使用OpenGL,不要用GDI。 - Ripi2
据说GDI和OpenGL图形不能混合使用。实际上我也不这样做。我一次只使用OpenGL或者只使用GDI进行绘制。似乎新的驱动程序在销毁OpenGL上下文时忘记了激活底层的GDI缓冲区。 - myavuzselim
但是您想要绘制到相同的窗口缓冲区,记住其上一状态。这可以被视为“混合”。 - Ripi2
这不是你我的观点。这是微软和显卡供应商的观点。Windows 10改变了一些东西... - Ripi2
显示剩余3条评论

2
我不会销毁GL上下文,相反我会尝试更改它。
glViewport(x0,y0,xs,ys);

在某些角落或者甚至是隐藏的小区域,比如说:
glViewport(0,0,1,1);

这样即使有bug存在,用户也只会在某个角落看到像素伪影,很可能甚至都不会注意到(尤其是如果与背景颜色相同渲染)。


谢谢您的建议。不幸的是,行为仍然相同。即使将OpenGL视口调整为1像素,GDI输出也是不可见的。如果将OpenGL视口调整为1像素并销毁OpenGL上下文,行为也是相同的。 - myavuzselim
@myavuzselim,我能想到的最后一件事是您的缓冲区没有切换回GDI。尝试向您的应用程序添加按钮,并在单击它时调用SwapBuffers(s_hdc);,看看是否有所不同?此外,请检查图形区域的句柄是否发生了变化...(这只是一个猜测,在Win10中可能存在任何错误)。顺便说一句,有时我会为这样的事情打开2个窗口,并将我想要查看的窗口停靠在主应用程序工作区中(另一个窗口设置为不可见)。 - Spektre
不幸的是,调用 SwapBuffers(s_hdc); 不会改变输出结果,无论我是否销毁 GL 上下文,或者是否调用 glViewport(0,0,1,1) - myavuzselim

0
我不知道。可能是因为我来自Android编程时代。解决问题的方法很简单。假设你有一个主窗口。然后你有一个覆盖主窗口客户区域的内容窗口,你实际上在这个窗口上绘制图形。然后你可以销毁这个内容窗口,并用另一个内容窗口替换它来绘制2D图形。并且不断重复这个过程。就像创建和销毁窗口非常快一样。
如果你使用的是一个超级慢得离谱、由水驱动的机器,并且不想不断地创建和销毁窗口。你可以让主窗口绘制3D图形,而内容窗口绘制2D图形。然后你可以隐藏内容窗口以显示3D窗口,并显示内容窗口以隐藏2D窗口。

确实,那就是我最终做的事情。这在我的问题中的“更新2”部分有提到。 - myavuzselim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接