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

OpenGL application works differently on different computers

本文关键字:工作 方式不 计算机 应用程序 OpenGL      更新时间:2023-10-16

我将测试应用程序发送给了几个人。第一个测试者的结果和我的一样,但其他两个测试者有一些奇怪的地方。出于某种原因,左下角的图像应该是原始大小,他们认为它被拉伸到了全屏。此外,他们看不到GUI元素(但是,如果鼠标找到按钮,按钮就会工作)。我会保留,这不是拉伸的图像重叠的按钮,我给他们发送了透明图像的版本,按钮仍然没有绘制。对于GUI绘图,我使用Nuklear库。我将给出负责定位问题图像的屏幕截图和代码。是什么原因造成的?

[良好行为/不良行为]

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]

我在发布的代码中发现了一些错误。我将戏剧性地揭露罪魁祸首的最后一面。

NULL用作整数

例如,

glBindTexture(GL_TEXTURE_2D, NULL); // Incorrect

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

glBindTexture(GL_TEXTURE_2D, 0); // Correct

这也适用于glBindBufferglBindVertexArray

null构造函数定义为显式

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注释不正确。请参阅C++"new"运算符在现实生活中会抛出异常吗?

更实用的一点是,构造函数从磁盘中读取图像。这很可能会失败,抛出异常是一种合理的处理方式。

noexcept关键字在这里不是特别有用。也许编译器可以在调用站点生成稍微少一点的代码,但这可能最多会产生无穷小的差异,因为构造函数是冷代码(cold=不经常调用)。noexcept限定符主要用于在不同的通用算法之间进行选择,请参阅noexcept对什么有用?

无错误处理

请记住,如果发生错误,stdbi_load将返回NULL。这个案子没有得到处理。

违反了三条规则

Background类不定义复制构造函数或复制赋值运算符,即使它定义了析构函数。虽然这并不能保证你的程序会出错,但这有点像把一把上膛的枪放在厨房柜台上,希望没有人碰它。这被称为"三条规则",修复起来很简单。添加已删除的复制构造函数和复制赋值运算符。

// 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();

看什么是三条规则?

缓冲区被错误删除

这是一条线:

glDeleteBuffers(1, &VBO);

简短的版本。。。您应该将其移动到Background::~Background()中。

长版本。。。删除缓冲区时,它不会从VAO中删除,但名称可以立即重新使用。根据OpenGL 4.6规范5.1.2:

删除缓冲区、纹理或renderbuffer对象时,它是。。。与绑定到当前上下文的容器对象的任何附件分离。。。

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

当缓冲区、纹理、采样器、renderbuffer、查询或同步对象被删除时,其名称立即变为无效(例如,标记为未使用),但基础对象在不再使用之前不会被删除。

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

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