class 构造函数中的 OpenGL sigsegv 错误

OpenGL sigsegv error in class constructor

EDIT* 我按照 much_a_chos 的建议重新 运行 初始化列表,以便 Window 对象在游戏对象,确保首先初始化 glew。但是,这没有用:

//Rearranged initialization list 
class TempCore
{
public:
    TempCore(Game* g) :
        win(new Window(800, 800, "EngineTry", false)), gamew(g) {}
    ~TempCore() { if(gamew) delete gamew; } 
...
};

下面是我在 Mesh 构造函数中更改的代码,当上面的代码不起作用时:

Mesh::Mesh( Vertex* vertices, unsigned int numVerts )
{
    m_drawCount = numVerts;

    glewExperimental = GL_TRUE;
    if(glewInit() != GLEW_OK){
        exit(-150);   //application stops and exits here with the code -150
    }

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

   ...
}

当我编译 运行 时发生的事情令人惊讶。 程序在我从 Window 构造函数复制的 if(glewInit() != GLEW_OK) 处退出。 由于某种原因,glew 在 Window 构造函数中正确初始化(之前调用Game 构造函数),但在 Mesh 构造函数中第二次调用时无法初始化。我认为在一个程序中不止一次调用 glewInit() 是不好的做法,但我认为如果我真的这样做了它应该不会失败。有人知道会发生什么吗?我不止一次调用 glewInit() 是不是出错了?

*编辑结束

我一直在关注 3D Game Engine Development tutorial,我在我的代码中遇到了一个奇怪的错误,我将在下面进行演示。我试图纯粹出于教育原因制作自己的游戏引擎。我使用 Code-blocks 13.12 作为我的 IDE 和 mingw-w64 v4.0 作为我的编译器。我还使用 SDL2、glew、Assimp 和 boost 作为我的第三方库。

对于大量的代码摘录,我提前表示歉意,但我输入了我认为理解错误上下文所必需的内容。

我有一个 Core class 用于我的游戏引擎,它包含主循环并相应地更新和渲染,调用 Game class update()render() 方法也在这个过程中。游戏 class 旨在作为游戏中所有资产的持有者,并将成为使用该引擎制作的任何游戏的基础 class,因此,它包含网格、纹理和相机参考。 Game class update()render()input() 方法都是虚拟的,因为 Game class 是派生的。

我的问题是:当我在 Core class 中初始化 Game 成员变量时,我在 Mesh 对象的构造函数中得到一个 SIGSEGV(即分段错误)glGenVertexArrays打电话。 但是,当我将 Game 对象移出 Core class 并直接进入 main 方法时(因此我将其从 class 成员更改为简单的作用域变量在 main 方法中),以及 Core class 中的必要部分,然后它的 运行 完美呈现我的基本三角形示例。这是一个我从未遇到过的错误,我将非常感谢我能得到的任何帮助。

下面是我的变形代码的摘录,运行 完美呈现了三角形:

int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow)
{
    Window win(800, 800, "EngineTry", false); //Creates an SDL implemented window with a GL_context
    Game* gamew = new Game;
    const double frameTime = 1.0 / 500;  //500 = maximum fps

    double lastTime = FTime::getTime();  //gets current time in milliseconds
    double unprocessedTime = 0.0;
    int frames = 0;
    double frameCounter = 0;

    while(win.isRunning()){
        bool _render = false;

        double startTime = FTime::getTime();
        double passedTime = startTime - lastTime;
        lastTime = startTime;

        unprocessedTime += passedTime / (double)FTime::SECOND;
        frameCounter += passedTime;

        while(unprocessedTime > frameTime){
            if(!win.isRunning())
                exit(0);
            _render = true;

            unprocessedTime -= frameTime;
            FTime::delta = frameTime;

            gamew->input();
            Input::update();
            gamew->update();

            if(frameCounter >= FTime::SECOND)
            {
                std::cout << "FPS: " << frames << std::endl;
                frames = 0;
                frameCounter = 0;
            }
        }

        if(_render){
            RenderUtil::clearScreen(); //simple wrapper to the glClear function
            gamew->render();
            win.Update();
            frames++;
        }else{
            Sleep(1);
        }
    }

    delete gamew;
    return 0;
}

这是我修改后的 Core class 的摘录,它不起作用(在 Mesh 构造函数中抛出 sigsegv)

class TempCore
{
public:
    TempCore(Game* g) :
        gamew(g), win(800, 800, "EngineTry", false) {}
    ~TempCore() { if(gamew) delete gamew; }

    void start();

private:
    Window win;
    Game* gamew;
};


int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow)
{
    TempCore m_core(new Game());
    m_core.start();

    return 0;
}


void TempCore::start()
{
    const double frameTime = 1.0 / 500;

    double lastTime = FTime::getTime();
    double unprocessedTime = 0.0;
    int frames = 0;
    double frameCounter = 0;

    while(win.isRunning()){
        bool _render = false;

        double startTime = FTime::getTime();
        double passedTime = startTime - lastTime;
        lastTime = startTime;

        unprocessedTime += passedTime / (double)FTime::SECOND;
        frameCounter += passedTime;

        while(unprocessedTime > frameTime){
            if(!win.isRunning())
                exit(0);
            _render = true;

            unprocessedTime -= frameTime;
            FTime::delta = frameTime;

            gamew->input();
            Input::update();
            gamew->update();

            if(frameCounter >= FTime::SECOND){
                //double totalTime = ((1000.0 * frameCounter)/((double)frames));
                //double totalMeasuredTime = 0.0;

                std::cout << "Frames: " << frames << std::endl;
                //m_frames_per_second = frames;
                frames = 0;
                frameCounter = 0;
            }
        }

        if(_render){
            RenderUtil::clearScreen();
            gamew->render();
            win.Update();
            frames++;
        }else{
            Sleep(1);
        }
    }
}

上述 TestCore 实现中出现 sigsegv 的网格构造函数:

Mesh::Mesh( Vertex* vertices, unsigned int numVerts )
{
    m_drawCount = numVerts;

    glGenVertexArrays(1, &m_vertexArrayObject); //sigsegv occurs here
    glBindVertexArray(m_vertexArrayObject);

        std::vector<glm::vec3> positions;
        std::vector<glm::vec2> texCoords;
        positions.reserve(numVerts);
        texCoords.reserve(numVerts);

        for(unsigned i = 0; i < numVerts; i++){
            positions.push_back(vertices[i].pos);
            texCoords.push_back(vertices[i].texCoord);
        }

        glGenBuffers(NUM_BUFFERS, m_vertexArrayBuffers);
        glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffers[POSITION_VB]); 
        glBufferData(GL_ARRAY_BUFFER, numVerts*sizeof(positions[0]), &positions[0], GL_STATIC_DRAW); 

        glEnableVertexAttribArray(0); 
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);


        glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffers[TEXCOORD_VB]); 
        glBufferData(GL_ARRAY_BUFFER, numVerts*sizeof(texCoords[0]), &texCoords[0], GL_STATIC_DRAW);

        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);

    glBindVertexArray(0);
}

初始化Mesh对象的Game构造函数:

Vertex vertices[] = {   Vertex(-0.5f, -0.5f, 0, 0, 0),
                        Vertex(0, 0.5f, 0, 0.5f, 1.0f),
                        Vertex(0.5f, -0.5f, 0, 1.0f, 0)};
//Vertex is basically a struct with a glm::vec3 for position and a glm::vec2 for texture coordinate

Game::Game() :
    m_mesh(vertices, sizeof(vertices)/sizeof(vertices[0])),
    m_shader("res\shaders\basic_shader"),
    m_texture("res\textures\mist_tree.jpg")
{
}

初始化glewWindowclass构造函数:

Window::Window(int width, int height, const std::string& title, bool full_screen) :
    m_fullscreen(full_screen)
{
    SDL_Init(SDL_INIT_EVERYTHING);

        SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32);
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    //SDL_Window* in private of class declaration 
    m_window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    //SDL_GLContext in private of class declaration 
    m_glContext = SDL_GL_CreateContext(m_window);

    std::cout << "GL Version: " << glGetString(GL_VERSION) << std::endl;
    glewExperimental = GL_TRUE;
    if(glewInit() != GLEW_OK || !glVersionAbove(3.0)){
        std::cerr << "Glew failed to initialize...\n";
        exit(-150);
    }
}

这里不太可能,因为给定的信息量非常大。我搜索了类似的问题,例如 this one and this one,但每个问题都已通过您在 Window class 构造函数 中所做的技巧得到了回答,这些技巧必须在之前调用你的游戏构造函数。正如我在你的 TempCore 构造函数中看到的那样,你构建你的游戏对象(并调用 glGenVertexArrays) 你的 Window 对象被构建之前

...

TempCore(Game* g) :
    gamew(g), win(800, 800, "EngineTry", false) {}

...

因此,在使用 SDL_GL_CreateContext(m_window)glewExperimental = GL_TRUE; glewInit(); 调用创建 OpenGL 上下文之前。然后你说按这个顺序放在main里就解决了问题...

...
Window win(800, 800, "EngineTry", false); //Creates an SDL implemented window with a GL_context
Game* gamew = new Game;
...

也许像这样在构造函数中重新排序初始化列表可以解决您的问题?

class TempCore
{
public:
    TempCore(Game* g) :
        win(800, 800, "EngineTry", false), gamew(g) {}
    ~TempCore() { if(gamew) delete gamew; }

...
};

更新

我错了,如评论中所述,初始化列表顺序无关紧要。重要的是定义顺序,这里是正确的...

解决您的编辑问题:您不应多次调用 glewInit()。在这方面我不熟悉 glew,但一般来说,任何东西都应该只 "initialized" 一次。 glew 可能假设它是未初始化的,并且当一些初始化已经存在时出错。

我建议在程序的最开始而不是在对象构造函数中调用 glewInit()。 (除非你有那个对象"own" glew)

编辑: 看来我对 glewInit() 的假设有点错误。 glewInit() 的行为因构建而异,但无论如何都应仅在切换上下文时调用。但是,因为您没有更改上下文(据我所知),所以您不应多次调用它。

感谢@much_a_chos 和@vu1p3n0x 的帮助。事实证明 much_a_chos 对 Game 对象在 Window 对象之前初始化的想法是正确的,因此完全错过了 glewInit() 调用,导致 sigsegv 错误。然而,问题不在初始化列表中,而是在 main.cpp 文件中。我正在 创建一个游戏 class 对象,然后通过指向核心 class 的指针将该游戏对象传递给 class,所以无论我如何安排核心 class,Game 对象总是在 Window class 之前初始化,因此总是在调用 glewInit() 之前执行它的 glGenVertexArrays 调用。这对我来说是一个严重的逻辑错误,对于浪费您的时间,我深表歉意。

下面是固定的 main.cpp 文件和固定的 TempCore class 的摘录(请记住,这些是临时修复,以说明我将如何解决我的错误):

class TempCore
{
public:
    TempCore(Window* w, Game* g) :  //take in a Window class pointer to ensure its created before the Game class constructor
        win(w), gamew(g) {}
    ~TempCore() { if(gamew) delete gamew; }

    void start();

private:
    Window* win;
    Game* gamew;
};

int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow)
{
    Window* win = new Window(800, 800, "EngineTry", false); //this way the Window constructor with the glewinit() call is called before the Game contructor
    TempCore m_core(win, new Game());
    m_core.start();

    return 0;
}