Переключение с OpenGL на GDI - PullRequest
0 голосов
/ 29 мая 2018

У нас есть приложение, в котором мы используем 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?Видите ли вы решения / обходные пути?

Возможно, это связано с настройками NVIDIA + Intel с двумя графическими процессорами, но существует хотя бы один контрпример.Карты, по которым у нас есть обратная связь:

НЕ ВОСПРОИЗВЕДЕНО:

  • 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 + IntelHD 4600
  • Quadro P3000 + Intel HD 630
  • Quadro M4000 (Forceware 385.90), один графический процессор

Невозможно рисовать 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 ]

0 голосов
/ 30 мая 2018

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

glViewport(x0,y0,xs,ys);

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

glViewport(0,0,1,1);

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

0 голосов
/ 30 мая 2018

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

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

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

Мы что-то делаем не так,

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

Невозможно нарисовать 2d контент вOpenGL.

Почему?

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

Вы действительно пытались его профилировать??10 баксов говорят, что это будет хорошо.

0 голосов
/ 29 мая 2018

Немного поиска в OpenGL в Windows - общие реализации и аппаратные реализации показывает:

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

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

А также из структуры PIXELFORMATDESCRIPTOR

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

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

...