c ++ - Multithreading OpenGL i et barn vindue

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg forsøger at opbygge en OpenGL-applikation, der er lydhør, selvom hovedvinduet bliver ændret eller flyttet. Den mest logiske løsning, jeg har fundet, er at oprette et børnevindue og en meddelelsespumpe i separat tråd, der gør OpenGL. kan ændre størrelsen på hinanden mellem rammer efter behov. Den primære meddelelsespumpe og vinduesramme kører i hovedprocessen.


Det virker godt til et punkt. Vinduet kan flyttes, menuer bruges og ændres uden at påvirke barnets vindues billedfrekvens. SwapBuffers () er hvor det hele falder fra hinanden.


SwapBuffers () ser ud til at køre i softwaremodus, når den køres på denne måde. Det holder ikke længere ved 60 FPS for at matche min skærm VSync, det hopper ind i hundrederne, når vinduet er omkring 100x100 og falder til 20 FPS når det maksimeres til 1920x1080. Når det kører i en enkelt tråd, virker alt normalt.


Der var et par spørgsmål, som jeg besluttede. Ligesom når beskeder skal passere mellem forældre og barn, det forpligter hele ansøgningen. Overordnet WM\_SETCURSOR og indstilling af WS\_EX\_NOPARENTNOTIFY løst dem. Det stiger stadig lejlighedsvis, når jeg klikker.


Jeg håber, at jeg bare ikke gør noget ordentligt, og at nogen, der har erfaring med OpenGL, kan hjælpe mig. Noget at gøre med min initialisering eller oprydning kan være slukket, da dette interfererer med andre OpenGL-applikationer, der kører på min pc, selv efter at jeg har lukket det.


Her er en forenklet test sag, der viser de problemer, jeg oplever. Det burde kompilere om ethvert moderne 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;
}


Jeg har kørt dette på en NVIDIA GeForce 8800 GTX, men jeg håber, at en løsning vil fungere lige på ethvert moderne kort.


Jeg har prøvet andre metoder, som en tråd, der giver et vindue i hovedprocessen, men jeg har ved at ændre størrelsen jeg fået artefakter og endda et par blåskærme. Min ansøgning bliver cross platform, så DirectX er ikke en mulighed.

Bedste reference


Problemet viste sig at være, at langvarig debugging af OpenGL-applikationer i Visual Studio kan få OpenGL til at begynde at undlade at skabe sammenhænge. Da jeg ikke tog fat på fejl, forstod jeg aldrig, at det kørte uden hardware acceleration. Det krævede en fuld genstart for at komme sig og fungerer nu fint.


Udskiftning af pumpen WinMain med dette løser også problemer med resizing:


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