OpenGL 版本在使用 ifstream 时太低

OpenGL version too low when using ifstream

本文关键字:ifstream 版本 OpenGL      更新时间:2023-10-16

我正在为2D Roguelike编写引擎的一些元素。我正处于我希望能够打开 .pngs 的部分。(我手动做事,因为我喜欢了解这些事情。所以我创建了一个PngLoader类,并开始用它做一些基本的事情,比如......打开文件。出于某种原因,这会破坏 OpenGL GLFunctionFinder 类,该类执行类似于 GLEW 的操作,但手动操作除外。

当OpenGL版本太低时,GLFF基本上会使程序崩溃;这是预期的行为。(可能是未设置函数指针上的段错误。我可以通过让它更优雅地崩溃来"修复"这个问题,但谁在乎呢?GLFF通常运行良好,因为我的显卡运行OpenGL 4.3左右,但是几天前当驱动程序切换到集成图形驱动程序(仅执行OpenGL版本1.1)时,我确实中断了它。这是通过更改图形仪表板中的某些设置来修复的。

因此,当我写这样的东西时,我今天出现的问题出现了:

class ifcontainerclass {
    std::ifstream fs;
};
/* other code */
int WINAPI WinMain(/* ... */) {
    GLFunctionFinder ff;
    ff.interrogateWindows();
    ifcontainerclass ifcc;
    /* GL code and main loop */
    return 0;
}

。OpenGL 上下文卡在 1.1 版。如果我ifstream更改为fstream,我会得到我期望的更高版本的上下文,问题就会消失。

我还在测试中发现,如果我注释掉GL code and main loop区域,问题会再次消失。"版本太低"检查是在 GLFunctionFinder::interrogateWindows() 中完成的,而不是在后来的 GL 代码中完成的,因此仍在检查条件。(经过一些测试,我发现注释掉MSG结构是使问题消失的原因。

我目前的信念是编译器正在做一些魔术,导致Windows/Intel/NVidia仅在...我真的不知道什么时候。这个问题看起来真的很武断。

可能会考虑摆脱我出于懒惰而使用的全局HDC和全局HGLRC,因为我认为问题与事物的初始化方式/编译器如何安排初始化这些东西有关,将它们拉出全局范围将让我更有效地检查和控制该过程。我在GLFunctionFinder中通过使用一个static void * GlobalAddr = this文件范围的指针来做到这一点,将其转换为虚拟窗口WndProc中的GLFunctionFinder,并让HDCHGLRC成为GLFunctionFinder的成员变量,可通过指针访问。我可能会在我的主窗口中尝试类似的东西;无论如何,我一直需要清理全局范围的东西。我可以做的另一件事是在调试器中运行每个版本并查看它的差异,尽管我不愿意这样做,因为调试在我的 IDE 中并没有真正正确设置,而且我不期待修复它。

我想我可以通过使用 fstream 而不是 ifstream 来度过这一关,但我对不理解如此奇怪的问题感到不舒服,因为它表明在我有 10k 行代码之前,我应该意识到某种不稳定性任意停止运行,只能通过更改其他地方看似完全不相关的东西来修复。

问题:

  • 到底发生了什么?这里的核心问题是什么?
  • 为什么将ifstream更改为fstream解决问题?
  • 为什么注释掉MSG结构可以解决问题?

PS:NvOptimusEnablement = 0x00000001没有解决问题。

PPS:Qt中的MinGW 4.9.2(作为IDE,没有Qt库)和CMake

编辑:在确定Qt的调试器在-ggdb传递给g++时工作后,我逐步浏览了代码,发现GLFunctionFinder中的PIXELFORMATDESCRIPTOR没有被分配;我将属性分配给某个随机临时变量而不是成员变量,而ChoosePixelFormat正在使用成员变量。由于您获得的上下文取决于您指定的像素类型,因此我实际上是从 Windows 请求不确定的设备上下文。编译的细节决定了在PIXELFORMATDESCRIPTOR中放入了哪些随机垃圾,而声明一个ifstream而不是一个fstream将错误的随机垃圾放在那个区域。

这个问题是通过在定义临时pfd后向GLFunctionFinder的构造函数添加一些具有this->pfd_ = pfd;效果的东西来解决的。

编辑2:为了满足我对"题外话"标志含义的理解,我将提供核心问题的最小示例:

主.cpp:

#include <windows.h>
#include <sstream>
#include <GL/gl.h>
HDC   h_dc;
HGLRC h_context;
LRESULT CALLBACK MainWndProc(_In_ HWND   h_wnd,
                             _In_ UINT   u_msg,
                             _In_ WPARAM w_param,
                             _In_ LPARAM l_param) {
    switch(u_msg) {
    case WM_CREATE: {
        PIXELFORMATDESCRIPTOR pfd; // <-- This was the error source, (pfd not set to an
                                   //     accelerated format, but only sometimes) 
                                   //     except in my code it was harder to see than
                                   //     this.
        h_dc = GetDC(h_wnd);
        int pfint = ChoosePixelFormat(h_dc, &pfd);
        SetPixelFormat(h_dc, pfint, &pfd);
        h_context = wglCreateContext(h_dc);
        wglMakeCurrent(h_dc, h_context);
        const unsigned char * version_string =
                static_cast<const unsigned char *>(glGetString(GL_VERSION));
        if(version_string[0] == '1' || version_string[0] == '2') {
            std::stringstream ss;
            ss << "OpenGL version (" << version_string << ") is too low";
            MessageBox(NULL, ss.str().c_str(), "Error", MB_OK | MB_ICONERROR);
        }
        break;
    }
    case WM_DESTROY:
        PostQuitMessage(EXIT_SUCCESS);
        break;
    default:
        return DefWindowProc(h_wnd, u_msg, w_param, l_param);
    }
    return 1;
}
int WINAPI WinMain(  HINSTANCE h_inst,
                     HINSTANCE h_previnst,
                     LPSTR cmd_str_in,
                     int cmd_show_opt) {
    WNDCLASSEX wc;
    wc.cbSize         = sizeof(WNDCLASSEX);
    wc.style          = CS_OWNDC;
    wc.lpfnWndProc    = MainWndProc;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;
    wc.hInstance      = h_inst;
    wc.hIcon          = NULL;
    wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground  = (HBRUSH)(COLOR_BACKGROUND + 1);
    wc.lpszMenuName   = NULL;
    wc.lpszClassName  = "MAINWIN";
    wc.hIconSm        = NULL;
    RegisterClassEx(&wc);
    HWND h_wnd = CreateWindowEx(0,
                                "MAINWIN",
                                "MCVE Program",
                                WS_OVERLAPPEDWINDOW,
                                CW_USEDEFAULT,
                                CW_USEDEFAULT,
                                640,
                                480,
                                NULL,
                                NULL,
                                h_inst,
                                NULL);
    return EXIT_SUCCESS;
}

CMakeLists.txt:

project(mcve_pfd_problem)
cmake_minimum_required(VERSION 2.8)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} WIN32 ${SRC_LIST})
target_link_libraries(${PROJECT_NAME} opengl32)

万一有人跳到最后,问题就解决了,但我不知道我应该如何表明这一点。

因此,我实际看到的是由于默认初始化结构而导致的未定义行为的影响,其中包含的值未初始化:

class GLFunctionFinder {
    PIXELFORMATDESCRIPTOR pfdarr_;
    /* other code */
    GLFunctionFinder();
    setupContext();
    /* other code */
}
GLFunctionFinder::GLFunctionFinder() {
    /* other code */
    PIXELFORMATDESCRIPTOR pfd = { /* things */ };
    // Missing: pfdarr_ = pfd;
    // pfdarr_ never gets set
}
GLFunctionFinder::setupContext() {
    // Undefined behavior:
    int px_format_default = ChoosePixelFormat(this->h_cd, &(this->pfdarr_));
    /* other code */
}

这给了ChoosePixelFormat pfdarr_中的任何垃圾.当我最初写这篇文章时,它表现得好像没有问题,因为显然垃圾数据"看起来像"加速像素格式类型,ChoosePixelFormat会给我一个产生我所追求的OpenGL上下文的int format。它就这样呆了一段时间,因为它一直在工作。

fstream切换到ifstream改变了编译器布局/优化程序方式的一些细节,pfdarr_中的垃圾数据更改为"看起来像"未加速的格式。这导致获得错误的上下文,从而导致OpenGL版本检查失败。注释掉MSG结构和部分事件循环的故事基本相同:碰编译器发出的东西会产生我想要的 OpenGL 上下文。

我昨晚正在编译我在 Edit 2 中给出的代码,它给了我一个 1.1 的上下文。今天早上,完全相同的代码,没有错误;移动了MessageBox,发现我得到了 4.3 上下文。有趣的错误。