OpenGL应用程序在不同计算机上的工作方式不同

时间:2018-07-02 14:56:41

标签: c++ user-interface opengl

我将测试申请发送给了几个人。第一个测试人员的结果与我的相同,但是其他两个结果却有些奇怪。由于某种原因,图像应该以原始尺寸显示在左下角,因此它们被拉伸为全屏显示。此外,他们也看不到GUI元素(但是,如果通过鼠标找到按钮,它们将起作用)。我将保留一点,即该图像不是与按钮重叠的拉伸图像,我给它们发送了带有透明图像的版本,并且按钮仍未绘制。对于GUI绘图,我使用Nuklear库。我将提供负责定位问题图像的屏幕截图和代码。是什么原因造成的?

[Good behavior / Bad behavior]

int width, height;
{
    fs::path const path = fs::current_path() / "gamedata" / "images" / "logo.png";
    unsigned char *const texture = stbi_load(path.u8string().c_str(), &width, &height, nullptr, STBI_rgb_alpha);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture);
    stbi_image_free(texture);
}

...

{
    float const x = -1.0f + width * 2.0f / xResolution;
    float const y = -1.0f + height * 2.0f / yResolution;

    float const vertices[30] = {
        /* Position */        /* UV */
        -1.0f, -1.0f, 0.0f,   0.0f, 0.0f,
        -1.0f,  y,    0.0f,   0.0f, 1.0f,
        x,      y,    0.0f,   1.0f, 1.0f,
        -1.0f, -1.0f, 0.0f,   0.0f, 0.0f,
        x,     -1.0f, 0.0f,   1.0f, 0.0f,
        x,      y,    0.0f,   1.0f, 1.0f
    };

    glBufferData(GL_ARRAY_BUFFER, 30 * sizeof(float), vertices, GL_STATIC_DRAW);
}

[更新]

通过反复试验,我意识到问题是由负责渲染背景和徽标的类引起的,并且两者都是错误的。它们可以分别正常工作,但是一旦在游戏循环中添加了其他内容,一切就会崩溃。

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
background.render();
glEnable(GL_BLEND);
glDepthMask(GL_FALSE);
logo.render();
nk_glfw3_render();
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);

我自己写了这些课,所以很可能我错过了一些东西。发现一个错误之后,还有另一个错误,因为它们几乎相同。到目前为止,我还无法确定,这些类中到底有什么错误……

[Background.hpp / Background.cpp]

1 个答案:

答案 0 :(得分:1)

我在发布的代码中发现了一些错误。最后,我将以戏剧性的方式揭示罪魁祸首。

NULL用作整数

例如,

glBindTexture(GL_TEXTURE_2D, NULL); // Incorrect

glBindTexture函数接受整数参数,而不是指针。这是正确的版本:

glBindTexture(GL_TEXTURE_2D, 0); // Correct

这也适用于glBindBufferglBindVertexArray

空构造函数是显式定义的

explicit关键字仅影响一元构造函数(采用一个参数的构造函数)。它不会影响具有任何其他数量参数的构造函数。

explicit Background() noexcept; // "explicit" does not do anything.

Background() noexcept; // Exact same declaration as above.

构造函数错误地定义为noexcept

noexcept关键字的意思是“此函数永远不会抛出异常”。但是,它包含以下可能抛出的代码:

new Shader("image")

根据标准,这可能引发std::bad_alloc。因此,noexcept注释不正确。参见Can the C++ `new` operator ever throw an exception in real life?

更实际地讲,构造函数从磁盘读取图像。这很可能会失败,并且抛出异常是处理此错误的合理方法。

noexcept关键字在这里不是特别有用。也许编译器可以在调用位置生成较少的代码,但这可能最多产生无穷小的差别,因为构造函数是冷代码(冷=不经常调用)。 noexcept限定符主要用于在不同的通用算​​法之间进行选择,请参见What is noexcept useful for?

没有错误处理

请记住,如果发生错误,stdbi_load将返回NULL。这种情况无法处理。

违反了三项规则

Background类没有定义副本构造函数或副本赋值运算符,即使它定义了析构函数。尽管不能保证这会使您的程序不正确,但这就像在厨房柜台上放了一把翘起的枪,希望没有人碰它。这称为Rule of Three,而且很容易修复。添加一个已删除的副本构造函数和副本分配运算符。

// These three go together, either define all of them or none.
// Hence, "rule of three".
Background(const Background &) = delete;
Background &operator=(const Background &) = delete;
~Background();

请参见What is The Rule of Three?

缓冲区被错误地删除

以下是一行:

glDeleteBuffers(1, &VBO);

简短版本...您应该将其移至Background::~Background()

长版本...删除缓冲区时,不会将其从VAO中删除,但是名称可以立即重用。根据{{​​3}} 5.1.2:

  

删除缓冲区,纹理或渲染缓冲区对象后,它会与绑定到当前上下文的容器对象的任何附件分离……。

因此,由于VAO当前未绑定,因此删除VBO不会将VBO从VAO中删除(如果绑定了VAO,则情况会有所不同)。但是,第5.1.3节:

  

当删除缓冲区,纹理,采样器,渲染缓冲区,查询或同步对象时,其名称立即变为无效(例如,标记为未使用),但是直到不再使用基础对象时,才会删除该对象。 / p>

因此,VBO将保留,但是 name 可以重用。这意味着以后调用glGenBuffers可能会给您相同的名称。然后,当您调用glBufferData时,它将覆盖背景和徽标中的数据。或者,glGenBuffers可能会给您一个完全不同的缓冲区名称。这完全取决于实现,这解释了为什么您在不同的计算机上看到不同的行为。

通常,在使用完缓冲区之前,我将避免调用glDeleteBuffers。从技术上讲,您可以更早地调用glDeleteBuffers,但这意味着您可以从glGenBuffers返回相同的缓冲区。