Переключение с OpenGL на GDI

У нас есть приложение, где мы используем как GDI, так и OpenGL для рисования в том же HWND, но исключительно.

пример:

  • первоначально мы находимся в 2d режиме, поэтому мы рисуем на нем с помощью GDI
  • затем мы переключаемся в 3d-режим и используем его с помощью OpenGL
  • затем мы переключаемся обратно в 2d-режим, и мы рисуем на нем с помощью GDI

при переключении в 3d-режим мы просто создаем контекст OpenGL для этого HWND, и мы можем использовать его использование OpenGL. При переключении обратно в 2d-режим мы просто уничтожаем контекст OpenGL, и мы можем обратиться к HWND с помощью GDI.

это работало очень хорошо до недавнего времени. В Windows 10 для некоторых карт NVidia это больше не работает, если драйвер более поздний, чем 382.05. В этом случае, когда мы удаляем контекст OpenGL и рисуем на HWND с помощью GDI, окно по-прежнему отображает последнее содержимое из OpenGL.

Я проверил все доступные форматы пикселей. У всех одинаковые вопрос.

мы делаем что-то неправильно, или это все ошибка? Ты видишь решение?

существует вероятность того, что это связано с настройками Nvidia + Intel dual 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

Это не вариант для рисования 2D-контента в OpenGL или наоборот. Кроме того, приложение очень чувствительно к производительности, так что это не вариант для рисования 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 предположили, что переключение обратно на GDI не разрешено, как только мы установим формат пикселя для окна, которое не есть PFD_SUPPORT_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;
}

3 ответов


с вами случилось то, что до сих пор вы полагались на неопределенное поведение – на самом деле это никогда не должно было работать в первую очередь, и вам просто повезло. После установки в окне, в котором не установлен флаг PFD_SUPPORT_GDI, двойного буферизованного pixelformat его больше нельзя использовать для операций GDI.

контекст рендеринга OpenGL не имеет значения! многие люди-это меня обязывает, по какой причине те, кто в это верит, верят - страдают от заблуждение, что контексты рендеринга OpenGL каким-то образом были привязаны к определенному HDC или HWND. Ничто не может быть дальше от истины. пока drawables pixelformat является совместимость к заданному контексту OpenGL этот контекст может быть привязан к нему.

и поскольку нет никаких связей между контекстами рендеринга OpenGL и окнами, весь этот маленький танец разрушения и воссоздания контекста не имеет никакого значимого эффекта. Может быть, может быть, этот маленький танец вызвал какой-то кодовый путь в водителе, который каким-то образом заставил ваши незаконные действия работать. Но что более вероятно, так это то, что вы просто сделали raindance в ожидании того, что он сделает что-то полезное, в то время как он был полностью фиктивным в первую очередь, и вы могли бы просто не сделать этого в первую очередь, с тем же эффектом.

мы делаем что-то неправильно,

Да, да ты. Ты делаешь то, что никогда не было позволено или предполагалось. работать в первую очередь. Вы просто не заметили этого, потому что до сих пор OSs/drivers не использовали комнату для маневра, предоставленную им этим не разрешенным для вас. Однако последние события в GPU/OS / drivers сейчас do используйте комнату покачивания, и просто катитесь над своим кодом.

Это не вариант для рисования 2d-контента в OpenGL .

Почему?

Это не вариант для рисования 2d-контента в OpenGL или наоборот. Кроме того, приложение очень чувствительно к производительности, так что это не вариант, чтобы нарисовать 2d-контент на экранное изображение GDI, чтобы нарисовать его как OpenGL quad

вы действительно пытались профилировать его? Ставлю 10 баксов, что это сработает.


немного поиска в OpenGL в Windows-универсальная реализация и аппаратные реализации показывает:

графики OpenGL и GDI нельзя смешивать в окне с двойной буферизацией.

приложение может напрямую рисовать как графику OpenGL, так и графику GDI в окно с одним буфером, но не в окно с двумя буферами.

и с PIXELFORMATDESCRIPTOR структура

PFD_SUPPORT_GDI: буфер поддерживает чертеж GDI. Этот флаг и PFD_DOUBLEBUFFER являются взаимоисключающими в текущем универсальном реализация.

поскольку вы используете OpenGL 1.1, просто добавьте PFD_SUPPORT_GDI флаг в свой PIXELFORMATDESCRIPTOR pfd


Я бы не уничтожил контекст GL. Вместо этого я бы попытался изменить

glViewport(x0,y0,xs,ys);

к небольшой области в некотором угле или даже спрятанный как

glViewport(0,0,1,1);

таким образом, даже если ошибка сохранится, пользователь увидит только пиксельный артефакт в углу где-то, скорее всего, даже не заметит его (особенно если он отображается с тем же цветом, что и фон).