简体   繁体   English

在子窗口中多线程OpenGL

[英]Multithreading OpenGL in a child window

I'm trying to build an OpenGL application that is responsive even while the main window is being resized or moved. 我正在尝试构建一个即使在调整主窗口大小或移动时也能响应的OpenGL应用程序。 The most logical solution that I have found is to create a child window and a Message Pump in separate thread which renders the OpenGL. 我找到的最合理的解决方案是在单独的线程中创建一个子窗口和一个消息泵,它呈现OpenGL。 It can resize itself in between frames as necessary. 它可以根据需要在帧之间调整大小。 The primary message pump and window frame runs in the main process. 主消息泵和窗口框架在主过程中运行。

It works great to a point. 它非常有用。 The window can be moved, menus used and resized without affecting the frame rate of the child window. 可以移动窗口,使用菜单和调整大小,而不会影响子窗口的帧速率。 SwapBuffers() is where it all falls apart. SwapBuffers()就是崩溃的地方。

SwapBuffers() seems to be running in software mode when it is run in this manner. 当以这种方式运行时,SwapBuffers()似乎在软件模式下运行。 It no longer holds at 60 FPS to match my monitor's VSync, it jumps into the hundreds when the window is around 100x100 and drops to 20 FPS when maximized to 1920x1080. 它不再保持在60 FPS以匹配我的显示器的VSync,当窗口大约100x100时它会跳到数百个,当最大化到1920x1080时它会降到20 FPS。 When running in a single thread, everything seems normal. 当在单个线程中运行时,一切看起来都很正常。

There were a few issues that I resolved. 我解决了一些问题。 Like when messages need to pass between parent and child it stalls the entire application. 就像消息需要在父节点和子节点之间传递一样,它会停止整个应用程序。 Overriding WM_SETCURSOR and setting WS_EX_NOPARENTNOTIFY resolved those. 覆盖WM_SETCURSOR并设置WS_EX_NOPARENTNOTIFY解决了这些问题。 It still occasionally stutters when I click. 点击时它仍然偶尔会断断续续。

I'm hoping that I'm just not doing something properly and that someone experienced with OpenGL can help me out. 我希望我只是不做正确的事情而且有经验的OpenGL可以帮助我。 Something to do with my initialization or cleanup may be off since this interferes with other OpenGL applications running on my PC even after I close it. 与我的初始化或清理有关的事情可能是关闭的,因为这会干扰我的PC上运行的其他OpenGL应用程序,即使我关闭它。

Here is a simplified test case that exhibits the issues that I'm experiencing. 这是一个简化的测试用例,展示了我遇到的问题。 It should compile in about any modern Visual Studio. 它应该在任何现代Visual Studio中编译。

#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include <wchar.h>

#pragma comment(lib, "opengl32.lib")

typedef signed int s32;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef float f32;
typedef double f64;

bool run = true;

// Window procedure for the main application window
LRESULT CALLBACK AppWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_DESTROY && (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_APPWINDOW))
        PostQuitMessage(0);

    return DefWindowProc(hWnd, msg, wParam, lParam);
}

// Window procedure for the OpenGL rendering window
LRESULT CALLBACK RenderWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_SETCURSOR)
    {
        SetCursor(LoadCursor(NULL, IDC_CROSS));
        return TRUE;
    }
    if (msg == WM_SIZE)
        glViewport(0, 0, LOWORD(lParam)-2, HIWORD(lParam)-2);

    return AppWindowProc(hWnd, msg, wParam, lParam);
}

int WINAPI ThreadMain(HWND parent)
{
    HINSTANCE hInstance = GetModuleHandle(0);

    // Depending on if this is running as a child or a overlap window, set up the window styles
    UINT ClassStyle, Style, ExStyle;
    if (parent)
    {
        ClassStyle = 0;
        Style = WS_CHILD;
        ExStyle = WS_EX_NOPARENTNOTIFY;
    } else {
        ClassStyle = 0 | CS_VREDRAW | CS_HREDRAW;
        Style = WS_OVERLAPPEDWINDOW;
        ExStyle = WS_EX_APPWINDOW;
    }

    // Create the child window class
    WNDCLASSEX wc = {0};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = ClassStyle;
    wc.hInstance = hInstance;
    wc.lpfnWndProc = RenderWindowProc;
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.hIcon = LoadIcon(0, IDI_APPLICATION);
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    wc.lpszClassName = L"OGLChild";
    ATOM ClassAtom = RegisterClassEx(&wc);

    // Create the child window
    RECT r = {0, 0, 640, 480};
    if (parent)
        GetClientRect(parent, &r);
    HWND WindowHandle = CreateWindowExW(ExStyle, (LPCTSTR)MAKELONG(ClassAtom, 0), 0, Style, 
                                        0, 0, r.right, r.bottom, parent, 0, hInstance, 0);

    // Initialize OpenGL render context
    PIXELFORMATDESCRIPTOR pfd = {0};
    pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 32;
    pfd.cDepthBits = 16;
    pfd.iLayerType = PFD_MAIN_PLANE;
    HDC DeviceContext = GetDC(WindowHandle);
    int format = ChoosePixelFormat(DeviceContext, &pfd);
    SetPixelFormat(DeviceContext, format, &pfd);
    HGLRC RenderContext = wglCreateContext(DeviceContext);
    wglMakeCurrent(DeviceContext, RenderContext);

    ShowWindow(WindowHandle, SW_SHOW);

    GetClientRect(WindowHandle, &r);
    glViewport(0, 0, r.right, r.bottom);

    // Set up an accurate clock
    u64 start, now, last, frequency;
    QueryPerformanceFrequency((LARGE_INTEGER*)&frequency);
    QueryPerformanceCounter((LARGE_INTEGER*)&now);
    start = last = now;

    u32 frames = 0; // total frames this second
    f64 nextFrameCount = 0; // next FPS update
    f32 left = 0; // line position

    MSG msg;
    while (run)
    {
        while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            if (msg.message == WM_QUIT || msg.message == WM_DESTROY)
                run = false;
        }

        // Update the clock
        QueryPerformanceCounter((LARGE_INTEGER*)&now);
        f64 clock = (f64)(now - start) / frequency;
        f64 delta = (f64)(now - last) / frequency;
        last = now;

        // Render a line moving
        glOrtho(0, 640, 480, 0, -1, 1);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();   
        glColor3f(1.0f, 1.0f, 0.0f);
        left += (f32)(delta * 320.0f);
        if (left > 640.0f)
            left = 0;
        glBegin(GL_LINES);
            glVertex2f(0.0f, 0.0f);
            glVertex2f(left, 480.0f);
        glEnd();
        SwapBuffers(DeviceContext);

        // Resize as necessary
        if (parent)
        {
            RECT pr, cr;
            GetClientRect(parent, &pr);
            GetClientRect(WindowHandle, &cr);
            if (pr.right != cr.right || pr.bottom != cr.bottom)
                MoveWindow(WindowHandle, 0, 0, pr.right, pr.bottom, FALSE);
        }

        // Update FPS counter
        frames++;
        if (clock > nextFrameCount)
        {
            WCHAR title[16] = {0};
            _snwprintf_s(title, 16, 16, L"FPS: %u", frames);
            SetWindowText(parent ? parent : WindowHandle, title);
            nextFrameCount = clock + 1;
            frames = 0;
        }

        Sleep(1);

    }

    // Cleanup OpenGL context
    wglDeleteContext(RenderContext);
    wglMakeCurrent(0,0);
    ReleaseDC(WindowHandle, DeviceContext);

    DestroyWindow(WindowHandle);

    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    int result = MessageBox(0, L"Would you like to run in threaded child mode?", 
                            L"Threaded OpenGL Demo", MB_YESNOCANCEL | MB_ICONQUESTION);
    if (result == IDNO)
        return ThreadMain(0);
    else if (result != IDYES)
        return 0;

    // Create the parent window class
    WNDCLASSEX wc = {0};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.hInstance = hInstance;
    wc.lpfnWndProc = (WNDPROC)AppWindowProc;
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.hIcon = LoadIcon(0, IDI_APPLICATION);
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    wc.lpszClassName = L"OGLFrame";
    ATOM ClassAtom = RegisterClassEx(&wc);

    // Create the parent window
    HWND WindowHandle = CreateWindowExW(WS_EX_APPWINDOW, (LPCTSTR)MAKELONG(ClassAtom, 0), 
                                        0, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, 
                                        0, 0, 640, 480, 0, 0, hInstance, 0);

    ShowWindow(WindowHandle, SW_SHOW);

    // Start the child thread
    HANDLE thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&ThreadMain, (LPVOID)WindowHandle, 0, 0);

    MSG msg;
    while (run)
    {
        while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            if (msg.message == WM_QUIT)
                run = false;
        }

        Sleep(100);
    }

    DestroyWindow(WindowHandle);

    // Wait for the child thread to finish
    WaitForSingleObject(thread, INFINITE);

    ExitProcess(0);
    return 0;
}

I've been running this on a NVIDIA GeForce 8800 GTX, but I'd hope that any solution will work equally on any modern card. 我一直在NVIDIA GeForce 8800 GTX上运行它,但我希望任何解决方案都适用于任何现代卡。

I have tried other methods, such as a thread rendering into a window in the main process but during resizing I've gotten artifacting and even a couple blue screens. 我已经尝试过其他方法,例如在主进程中渲染到窗口中的线程,但在调整大小期间我得到了伪像,甚至还有几个蓝屏。 My application will be cross platform so DirectX isn't an option. 我的应用程序将是跨平台的,因此DirectX不是一个选项。

The issue turned out to be that prolonged debugging of OpenGL applications in Visual Studio can cause OpenGL to start failing to create contexts. 问题是,在Visual Studio中对OpenGL应用程序进行长时间调试会导致OpenGL无法创建上下文。 Since I didn't trap for any errors, I never realized that it was running without hardware acceleration. 由于我没有捕获任何错误,我从未意识到它在没有硬件加速的情况下运行。 It required a full reboot to recover and now works fine. 它需要完全重启才能恢复,现在工作正常。

Also, replacing the pump WinMain with this fixes any issues with resizing: 此外,用这个替换泵WinMain修复了调整大小的任何问题:

MSG msg;
while (GetMessage(&msg, 0, 0, 0) != 0)
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
run = false;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM