尝试在使用 win32 API 创建的子窗口中使用 OpenGL 时调整大小问题

Resizing issues while trying to use OpenGL in a child window made with win32 API

提问人:samas69420 提问时间:10/22/2023 最后编辑:Mark Rotteveelsamas69420 更新时间:10/23/2023 访问量:150

问:

我想在带有自定义标题栏的窗口中使用 OpenGL,只使用 Windows API,但我在调整大小时遇到了奇怪的行为。

这是我现在的情况:我有一个用 win32 API 制作的窗口。在这个窗口中有一个较小的窗口,另一个线程在其中使用 OpenGL 绘制图像。我删除了默认框架(按照微软的本指南),然后绘制了父窗口的可见部分,将其用作自定义标题栏。一切看起来都不错,但是当我尝试调整窗口大小时,它会在一小段时间内开始出现故障,同时它一直在较小的窗口中呈现内容。我该如何解决这个问题?

以下是所发生情况的示例视频

也许知道如果我停止 OpenGL 线程的循环,窗口就会停止给我这个错误,但它也会被完全重绘,子 OpenGL 窗口会被覆盖。

这是我使用的代码的一个小版本,这给了我问题。要重现它,您只需复制粘贴并编译即可。我希望 ~600 行对于这里的问题来说不是太多。

#include<windows.h>
#include<dwmapi.h>
#include<pthread.h>
#include<stdio.h>
#include<GL/gl.h>
#include<GL/glext.h>
#include<GL/wglext.h>

#define TITLEBARHEIGHT 25
#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))

HGLRC glcontext = {0};
HGLRC dummy_glcontext = {0};
void** newglfun = {0};
GLuint buffer = 0;
GLuint ibuffer = 0;
GLuint vao = 0;
unsigned int program = 0;
int u_location = 0;
float r = 0; 
float increment = 0.05;

HANDLE handle_to_this_module = {0};
HWND handle_to_window = {0};
HWND parent = {0};
HWND child = {0};
HDC device_context = {0};

void (*glGenBuffers)(GLsizei, GLuint*)={0};
void (*glBindBuffer)(GLenum, GLuint)={0};
void (*glBufferData)(GLenum, GLsizeiptr, const GLvoid*, GLenum)={0};
void (*glVertexAttribPointer)(GLuint, GLint, GLenum, GLboolean, GLsizei, const GLvoid*)={0};
void (*glEnableVertexAttribArray)(GLuint)={0};
GLuint (*glCreateProgram)(void)={0};
GLuint (*glCreateShader)(GLenum)={0};
void (*glShaderSource)(GLuint, GLsizei,const GLchar**, const GLint*)={0};
void (*glCompileShader)(GLuint)={0};
void (*glAttachShader)(GLuint, GLuint)={0};
void (*glLinkProgram)(GLuint)={0};
void (*glValidateProgram)(GLuint)={0};
void (*glDeleteShader)(GLuint)={0};
void (*glGetShaderiv)(GLuint,GLenum, GLint*)={0};
void (*glUseProgram)(GLuint)={0};
void (*glDeleteProgram)(GLuint)={0};
GLint (*glGetUniformLocation)(GLuint, const GLchar*)={0};
void (*glUniform4f)(GLint,GLfloat,GLfloat,GLfloat,GLfloat)={0};
const GLubyte* (*glGetStringi)(GLenum, GLuint)={0};
void (*glGenVertexArrays)(GLsizei, GLuint*)={0};
void (*glBindVertexArray)(GLuint)={0};
void (*wglSwapIntervalEXT)(int)={0};
const char* (*wglGetExtensionsStringARB)(HDC)={0};
BOOL (*wglChoosePixelFormatARB)(HDC, const int*,const FLOAT*,UINT,int*,UINT*)={0};
HGLRC (*wglCreateContextAttribsARB)(HDC,HGLRC, const int*)={0};

int loadModernGLFunctions()
{
    char* newglfuncnames[] = {"glGenBuffers","glBindBuffer","glBufferData",
                                     "glVertexAttribPointer","glEnableVertexAttribArray",
                                     "glCreateProgram", "glCreateShader",
                                     "glShaderSource","glCompileShader",
                                     "glAttachShader","glLinkProgram","glValidateProgram",
                                     "glDeleteShader", "glGetShaderiv","glUseProgram",
                                     "glDeleteProgram","glUniform4f","glGetUniformLocation",
                                     "wglSwapIntervalEXT","glGetStringi","wglGetExtensionsStringARB",
                                     "wglChoosePixelFormatARB","wglCreateContextAttribsARB",
                                     "glGenVertexArrays","glBindVertexArray"};

    int n_functions = sizeof(newglfuncnames)/8;
    newglfun = malloc(sizeof(newglfuncnames));
    for(int i = 0; i<n_functions; i++){
        newglfun[i] = (void*) wglGetProcAddress(newglfuncnames[i]);
    }

    glGenBuffers = newglfun[0];
    glBindBuffer = newglfun[1];
    glBufferData = newglfun[2];
    glVertexAttribPointer = newglfun[3];
    glEnableVertexAttribArray = newglfun[4];
    glCreateProgram = newglfun[5];
    glCreateShader = newglfun[6];
    glShaderSource = newglfun[7];
    glCompileShader = newglfun[8];
    glAttachShader = newglfun[9];
    glLinkProgram = newglfun[10];
    glValidateProgram = newglfun[11];
    glDeleteShader = newglfun[12];
    glGetShaderiv = newglfun[13];
    glUseProgram = newglfun[14];
    glDeleteProgram = newglfun[15];
    glUniform4f = newglfun[16];
    glGetUniformLocation = newglfun[17];
    wglSwapIntervalEXT = newglfun[18];
    glGetStringi = newglfun[19];
    wglGetExtensionsStringARB = newglfun[20];
    wglChoosePixelFormatARB = newglfun[21];
    wglCreateContextAttribsARB = newglfun[22];
    glGenVertexArrays = newglfun[23];
    glBindVertexArray = newglfun[24];
    return TRUE;
}

void end_opengl(void)
{
    glDeleteProgram(program);
}

int make_shaders(void)
{
    int compileresult;
    program = (unsigned int) glCreateProgram();
    unsigned int id_vert = (unsigned int) glCreateShader(GL_VERTEX_SHADER);
    const char* vert_shader_source = "\n\
                     #version 330 core \n\
                     layout(location = 0) in vec4 position;\n\
                     void main()\n\
                     {\n\
                         gl_Position = position;\n\
                     }\n\
                     ";
    glShaderSource(id_vert, 1, &vert_shader_source, NULL);
    glCompileShader(id_vert);
    glGetShaderiv(id_vert, GL_COMPILE_STATUS, &compileresult);
    if(!compileresult){
        printf("error shader vert\n");
        return 0;
    }
    unsigned int vs = id_vert;
    unsigned int id_frag = (unsigned int) glCreateShader(GL_FRAGMENT_SHADER);
    const char* frag_shader_source = "\n\
                     #version 330 core \n\
                     layout(location = 0) out vec4 color;\n\
                     uniform vec4 u_Color;\n\
                     void main()\n\
                     {\n\
                         color = u_Color;\n\
                     }\n\
                     ";
    glShaderSource(id_frag, 1, &frag_shader_source, NULL);
    glCompileShader(id_frag);
    glGetShaderiv(id_frag, GL_COMPILE_STATUS, &compileresult);
    if(!compileresult){
        printf("error shader frag\n");
        return 0;
    }
    unsigned int fs = id_frag;
    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);
    glValidateProgram(program);
    glUseProgram(program);
    glDeleteShader(vs);
    glDeleteShader(fs);

    u_location = glGetUniformLocation(program, "u_Color"); 
    glUniform4f(u_location, 0.3254f, 0.098f, 0.9843f, 1.0f);

    return TRUE;
}

int init_opengl(void)
{
    PIXELFORMATDESCRIPTOR pfd =
    {
        sizeof(PIXELFORMATDESCRIPTOR),
        1,
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
        PFD_TYPE_RGBA,        
        32,                   
        0, 0, 0, 0, 0, 0,
        0,
        0,
        0,
        0, 0, 0, 0,
        24,                   
        8,                    
        0,                    
        PFD_MAIN_PLANE,
        0,
        0, 0, 0
    };
    int dummyiPixelFormat = ChoosePixelFormat(device_context, &pfd); 
    SetPixelFormat(device_context, dummyiPixelFormat, &pfd);
    if(!(dummy_glcontext = wglCreateContext(device_context)))
        printf("errcode %d\n", GetLastError()); 
    
    if(!wglMakeCurrent(device_context, dummy_glcontext))
        return 1;

    if(!loadModernGLFunctions())
        return 0;

    int iPixelFormat;
    UINT numFormats;
    const int PixAttribList[] =
    {
        WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
        WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
        WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
        WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
        WGL_COLOR_BITS_ARB, 32,
        WGL_DEPTH_BITS_ARB, 24,
        WGL_STENCIL_BITS_ARB, 8,
        0,
    };
    if(!wglChoosePixelFormatARB(device_context, PixAttribList, NULL, 1, &iPixelFormat, &numFormats))
    {
        return 0; 
    }

    const int CtxAttribList[] =
    {
        WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 
        WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
        WGL_CONTEXT_MINOR_VERSION_ARB, 3,
        0,
    };
    if(!(glcontext = wglCreateContextAttribsARB(device_context, NULL, CtxAttribList)))
        return 0;
    
    if(!wglMakeCurrent(device_context, glcontext))
        return 0;
    
    glClearColor(0.1f,0.1f,0.1f,0.1f);

    glGenVertexArrays(1,&vao);
    glBindVertexArray(vao);

    wglSwapIntervalEXT(0);

    float positions[8] = {
        -0.5, -0.5, 
        -0.5f, 0.5f,
        0.5f, 0.5f, 
        0.5f, -0.5f 
    };
    unsigned int indices[] = {
        0, 1, 2,
        2, 3, 0
    };
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, 8*sizeof(float),positions,GL_STATIC_DRAW);
    glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(float)*2, 0);
    glEnableVertexAttribArray(0);

    glGenBuffers(1, &ibuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*sizeof(unsigned int),indices,GL_STATIC_DRAW);

    if(!make_shaders())
        return 0;

    glUseProgram(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    if(!wglMakeCurrent(device_context, NULL)) 
        return 0;
    
    return TRUE;
}

void opengl_draw(void)
{
    if (r>1.0f)
        increment = -0.05f;
    else if (r< 0.0f)
        increment = 0.05f;
    r+=increment;

    RECT rcCli;          
    GetClientRect(WindowFromDC(device_context), &rcCli);
    int nWidth = rcCli.right-rcCli.left; 
    int nHeight  = rcCli.bottom-rcCli.top;
    glViewport(rcCli.left,rcCli.top,nWidth,nHeight);

    glUseProgram(program);
    glBindVertexArray(vao);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer);

    glClear(GL_COLOR_BUFFER_BIT);
    glUniform4f(u_location, r, 0.098f, 0.9843f, 1.0f);
    glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,NULL);
    SwapBuffers(device_context);
}

void OnSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    int parentWidth = LOWORD(lParam);
    int parentHeight = HIWORD(lParam);

    RECT parentRect;
    GetClientRect(hwnd, &parentRect);

    RECT childRect;
    GetWindowRect(child, &childRect); 

    int childWidth = parentRect.right - parentRect.left;
    int childHeight = parentRect.bottom - parentRect.top - 25;

    int childX = parentRect.left;
    int childY = parentRect.top + 25;

    SetWindowPos(child, 
                 NULL, 
                 childX, childY, 
                 childWidth, childHeight, 
                 SWP_NOZORDER | SWP_NOACTIVATE);
}

LRESULT HitTestNCA(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    POINT ptMouse = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
    RECT rcWindow;
    GetWindowRect(hWnd, &rcWindow);
    RECT rcFrame = { 0 };
    rcFrame.top = rcWindow.top + 5;
    rcFrame.bottom = rcWindow.bottom - 5;
    rcFrame.left = rcWindow.left + 5;
    rcFrame.right = rcWindow.right - 5;
    USHORT uRow = 1;
    USHORT uCol = 1;
    BOOL fOnResizeBorder = FALSE;

    if (ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + 25){
        fOnResizeBorder = (ptMouse.y < rcFrame.top);
        uRow = 0;
    }
    else if (ptMouse.y < rcWindow.bottom && ptMouse.y >= rcWindow.bottom - 20){
        uRow = 2;
    }

    if (ptMouse.x >= rcWindow.left && ptMouse.x < rcWindow.left + 8){
        uCol = 0;
    }
    else if (ptMouse.x < rcWindow.right && ptMouse.x >= rcWindow.right - 8){
        uCol = 2;
    }

    BOOL titlebar = (ptMouse.x > rcWindow.left && ptMouse.x < rcWindow.right-25) &&
                    (ptMouse.y > rcWindow.top && ptMouse.y < rcWindow.top+25) ? 1 : 0;
    if(titlebar) 
        return HTCAPTION;

    BOOL client = (ptMouse.x > rcWindow.left+5 && ptMouse.x < rcWindow.right-5) &&
                  (ptMouse.y > rcWindow.top+5 && ptMouse.y < rcWindow.bottom-5) ? 1 : 0;
    if(client){
        return HTCLIENT;
    }

    LRESULT hitTests[3][3] =
    {
        { HTTOPLEFT,    fOnResizeBorder ? HTTOP : HTCAPTION,    HTTOPRIGHT },
        { HTLEFT,       HTNOWHERE,     HTRIGHT },
        { HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT },
    };
    return hitTests[uRow][uCol];
}


LRESULT CALLBACK childProc(HWND chwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{
    switch(uMsg)
    {
        case WM_NCHITTEST:
            return HTTRANSPARENT;
        default:
            return DefWindowProc(chwnd, uMsg, wParam, lParam);
    }
}


LRESULT CALLBACK myProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SETCURSOR:
        {
           int ht_result = LOWORD(lParam);
           if(ht_result == HTCLIENT){
               SetCursor(LoadCursor(NULL, IDC_ARROW));
               return TRUE;
           }
           else 
               return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
        case WM_DESTROY:
        {
           PostQuitMessage(0);
           return 0;
        }
        case WM_SIZING:
        {
           RECT rect;
           GetClientRect(hwnd, &rect);
           InvalidateRect(hwnd, &rect, TRUE); 
           return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
        case WM_SIZE:
        {
           OnSize(hwnd, wParam, lParam);
           return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
        case WM_LBUTTONDOWN:
        {
           POINT ptMouse = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
           RECT rc;
           GetClientRect(hwnd, &rc);
           BOOL close = (ptMouse.x >= rc.right-25 && ptMouse.x < rc.right) &&
                        (ptMouse.y > rc.top && ptMouse.y < rc.top+25) ? 1 : 0;
           if(close) 
               DestroyWindow(hwnd);
           InvalidateRect(hwnd, NULL, TRUE);
           return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
        case WM_PAINT:
        {
           RECT rect;
           GetClientRect(hwnd, &rect);
           RECT titlebar;
           RECT closebutton;
           PAINTSTRUCT ps;
           HDC hdc = BeginPaint(hwnd, &ps);
           
           titlebar.top = 0;
           titlebar.bottom = 25;
           titlebar.left = 0;
           titlebar.right = rect.right - rect.left;

           closebutton.top = 0;
           closebutton.bottom = 25;
           closebutton.left = rect.right - 25;
           closebutton.right = rect.right;

           HBRUSH hBrush = CreateSolidBrush(RGB(255, 0, 0));
           FillRect(hdc, &titlebar, hBrush);
           DeleteObject(hBrush);

           HBRUSH hBrush3 = CreateSolidBrush(RGB(10, 10, 10));
           FillRect(hdc, &closebutton, hBrush3);
           DeleteObject(hBrush3);

           HBRUSH hBrush2 = CreateSolidBrush(RGB(100, 100, 100));
           RECT main;
           main.top = 25;
           main.bottom = rect.bottom;
           main.left = 0;
           main.right = titlebar.right;
           FillRect(hdc, &main, hBrush2);
           DeleteObject(hBrush2);

           EndPaint(hwnd, &ps);
           return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
        case WM_NCHITTEST:
        {
           return HitTestNCA(hwnd, wParam, lParam);
        }
        case WM_ACTIVATE:
        {
           MARGINS margins;
           margins.cxLeftWidth = 0;
           margins.cxRightWidth = 0;
           margins.cyBottomHeight = 0;
           margins.cyTopHeight = 0;
           HRESULT hr = DwmExtendFrameIntoClientArea(hwnd, &margins);
           return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
        case WM_CREATE:
        {
           SetWindowPos(hwnd,
                        NULL, 
                        0, 0, 
                        0, 0, 
                        SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
           return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
        case WM_NCCALCSIZE:
        {
           if(wParam == TRUE) return 0;
           else return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
        default: 
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}


int init_win32(void)
{
    handle_to_this_module = GetModuleHandle(NULL);
    if(!handle_to_this_module)
        return 0;

    WNDCLASS class = {0};
    class.lpfnWndProc = myProc;                                      
    class.hInstance = handle_to_this_module;                         
    class.lpszClassName = (LPCSTR) L"mainclass";                     
    class.hbrBackground = NULL;
    RegisterClass(&class);

    handle_to_window = CreateWindowExA(
                           0,                      
                           (LPCSTR) L"mainclass",    
                           "mainwin",             
                           WS_OVERLAPPEDWINDOW |   
                           WS_VISIBLE,
                           CW_USEDEFAULT,          
                           CW_USEDEFAULT,          
                           420,                    
                           420,                    
                           (HWND) NULL,            
                           (HMENU) NULL,           
                           handle_to_this_module,  
                           NULL                    
                           );

    if(!handle_to_window)
        return 0;

    parent = handle_to_window;

    WNDCLASS childclass = {0};
    childclass.lpfnWndProc = childProc;    
    childclass.hInstance = handle_to_this_module; 
    childclass.lpszClassName = (LPCSTR) L"childclass";
    childclass.hCursor = class.hCursor;      
    childclass.hbrBackground = NULL;
    RegisterClass(&childclass);
    child = CreateWindowExA(
                0,
                (LPCSTR) L"childclass",
                "childwin", 
                WS_CHILD | WS_VISIBLE,
                0, TITLEBARHEIGHT, 
                420, 420-TITLEBARHEIGHT, 
                parent, 
                0, 
                handle_to_this_module, 
                NULL);

    UpdateWindow(handle_to_window);
    device_context = GetDC(child);
    return TRUE;
}

void init(void)
{
    if(!init_win32())
        exit(1);
    if(!init_opengl())
        exit(2);
}

void run(void)
{
    MSG msg = {0};
    while(msg.message != WM_QUIT){
        if(PeekMessageW(&msg, NULL, 0x0,0x0, PM_REMOVE))
            DispatchMessage(&msg);
    }

}

void* drawing_loop(void* arg)
{
    if(!wglMakeCurrent(device_context, glcontext))
        return 0;
    
    while(1){
        opengl_draw();
    }
} 

void start_drawing_thread(void)
{
    pthread_t rendering_thread;
    pthread_create(&rendering_thread, NULL, drawing_loop, NULL);
}

void quit(void)
{
    end_opengl();
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    init(); 
    start_drawing_thread();
    run();
    quit();
}

// > gcc .\file.c -lgdi32 -ldwmapi -lopengl32 -o file
c winapi opengl

评论

0赞 samas69420 10/24/2023
更新:我刚刚尝试注释掉“WM_NCCALCSIZE”情况的代码,现在即使我有两个标题栏(默认标题栏和自定义标题栏),令人惊讶的是,我不再看到闪烁问题,这有点奇怪,因为我评论的部分是直接从 MSDN 文章中复制的,我应该认为指南有错误还是我遗漏了什么?

答: 暂无答案