ios - 优化 openGL ES 2.0 2D 纹理输出和帧率

标签 ios optimization opengl-es-2.0 textures vbo

我希望有人能帮助我在我在 OpenGL ES 2.0 和 iPhone 4 上做的一些纹理基准测试中取得一些进展。

我有一个包含 Sprite 对象的数组。渲染循环循环遍历每个纹理的所有 Sprite ,并检索它们的所有纹理坐标和顶点坐标。它使用退化的顶点和索引将它们添加到一个巨大的交错数组中,并将它们发送到 GPU(我嵌入的代码在底部)。这一切都是按纹理完成的,所以我绑定(bind)一次纹理,然后创建我的交错数组,然后绘制它。一切都很好,屏幕上的结果也正是它们应有的样子。

所以我的基准测试是通过在不同的不透明度下每次触摸添加 25 个新 Sprite 并在更新时更改它们的顶点,以便它们在旋转时在屏幕上弹跳并在应用程序上运行 OpenGL ES Analyzer。

在这里我希望得到一些帮助...... 我可以得到大约 275 个 32x32 的 Sprite ,它们具有不同的不透明度,以 60 fps 的速度在屏幕上弹跳。到 400 帧时,我的帧率下降到 40 fps。当我运行 OpenGL ES Performance Detective 时,它​​告诉我...

The app rendering is limited by triangle rasterization - the process of converting triangles into pixels. The total area in pixels of all of the triangles you are rendering is too large. To draw at a faster frame rate, simplify your scene by reducing either the number of triangles, their size, or both.

事情是我刚刚在 cocos2D 中使用 CCSpriteBatchNode 使用相同的纹理进行了测试,并创建了 800 个透明 Sprite ,帧速率很容易达到 60fps。

下面是一些可能相关的代码...

Shader.vsh(一开始就设置一次矩阵)

void main()
{
    gl_Position = projectionMatrix * modelViewMatrix * position;
    texCoordOut = texCoordIn;
    colorOut = colorIn;
}

Shader.fsh(colorOut用于计算不透明度)

void main()
{
    lowp vec4 fColor = texture2D(texture, texCoordOut);
    gl_FragColor = vec4(fColor.xyz, fColor.w * colorOut.a);
}

VBO 设置

    glGenBuffers(1, &_vertexBuf);
    glGenBuffers(1, &_indiciesBuf);
    glGenVertexArraysOES(1, &_vertexArray);

    glBindVertexArrayOES(_vertexArray);

    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuf);
    glBufferData(GL_ARRAY_BUFFER, sizeof(TDSEVertex)*12000, &vertices[0].x, GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(TDSEVertex), BUFFER_OFFSET(0));

    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(TDSEVertex), BUFFER_OFFSET(8));

    glEnableVertexAttribArray(GLKVertexAttribColor);
    glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_FALSE, sizeof(TDSEVertex), BUFFER_OFFSET(16));

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indiciesBuf);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(ushort)*12000, indicies, GL_STATIC_DRAW);

    glBindVertexArrayOES(0);

更新代码

    /*

        Here it cycles through all the sprites, gets their vert info (includes coords, texture coords, and color) and adds them to this giant array
        The array is of...
        typedef struct{
             float x, y;
             float tx, ty;
             float r, g, b, a;
        }TDSEVertex;
    */

     glBindBuffer(GL_ARRAY_BUFFER, _vertexBuf);
    //glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices[0])*(start), sizeof(TDSEVertex)*(indicesCount), &vertices[start]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(TDSEVertex)*indicesCount, &vertices[start].x, GL_DYNAMIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

渲染代码

    GLKTextureInfo* textureInfo = [[TDSETextureManager sharedTextureManager].textures objectForKey:textureName];
    glBindTexture(GL_TEXTURE_2D, textureInfo.name);

    glBindVertexArrayOES(_vertexArray);
    glDrawElements(GL_TRIANGLE_STRIP, indicesCount, GL_UNSIGNED_SHORT, BUFFER_OFFSET(start));
    glBindVertexArrayOES(0);

这是 400 个 Sprite (800 个三角形 + 800 个退化三角形)的屏幕截图,可以了解纹理移动时的不透明度分层...... 我应该再次注意到,正在为每个纹理创建和发送一个 VBO,因此我绑定(bind)然后每帧只绘制两次(因为只有两个纹理)。

screenshot showing layering of sprites

抱歉,如果这是压倒性的,但这是我在这里发表的第一篇文章,希望内容详尽。 任何帮助将不胜感激。

PS,我知道我可以只使用 Cocos2D 而不是从头开始编写所有内容,但是其中的乐趣(和学习)在哪里?!

更新#1 当我将片段着色器切换为仅是

    gl_FragColor = texture2D(texture, texCoordOut);

它以 50fps 的速度达到 802 个 Sprite (4804 个三角形,包括退化三角形),尽管设置 Sprite 不透明度丢失了。关于我如何在不以 1/4 速度运行的情况下仍然处理我的着色器中的不透明度有什么建议吗?

更新#2 所以我放弃了 GLKit 的 View 和 View Controller ,并编写了一个从 AppDelegate 加载的自定义 View 。 902 个 Sprite ,不透明度和透明度为 60fps。

最佳答案

主要是杂念……

如果您的三角形有限,请尝试从 GL_TRIANGLE_STRIP 切换到 GL_TRIANGLES。您仍然需要指定完全相同数量的索引——每个四边形六个——但 GPU 永远不必发现四边形之间的连接三角形退化(即,它永远不必将它们转换为零像素)。您需要进行概要分析,看看您最终是否会因为不再隐式共享边而付出代价。

您还应该缩小顶点的足迹。我敢想象您可以将 x、y、tx 和 ty 指定为 16 位整数,并将您的颜色指定为 8 位整数,而不会在渲染中产生任何明显的变化。这会将每个顶点的占用空间从 32 字节(八个组件,每个组件的大小为四个字节)减少到 12 字节(四个两字节值加上四个一字节值,不需要填充,因为所有内容都已经对齐)——削减几乎63% 的内存带宽成本都在那里。

由于您实际上似乎受到填充率的限制,因此您也应该考虑您的源纹理。您可以采取任何措施来减少其字节大小,这将直接有助于提取纹素,从而提高填充率。

看起来您正在使用有意识地关注像素的艺术,因此切换到 PVR 可能不是一种选择。也就是说,人们有时没有意识到 PVR 纹理的全部好处;例如,如果你切换到每像素 4 位模式,那么你可以将图像放大两倍宽和两倍高,以减少压缩伪影,并且仍然只在每个源像素上支付 16 位,但可能会得到比 16 bpp RGB 纹理更好的亮度范围。

假设您当前正在使用 32 bpp 纹理,您至少应该看看普通的 16 bpp RGB 纹理是否足以使用任何提供的硬件模式(尤其是如果 1 位 alpha 加上每个颜色 channel 5 位是适合您的艺术作品,因为与原始作品相比,它仅损失了 9 位颜色信息,同时将带宽成本降低了 50%)。

看起来您也在每一帧上传索引。仅当您将额外的对象添加到场景或上次上传的缓冲区比需要的大得多时才上传。您可以只限制传递给 glDrawElements 的计数以减少对象而无需重新上传。您还应该通过将顶点上传到 VBO 来检查您是否真的获得了任何东西,然后如果它们只是改变每一帧则重新使用它们。直接从客户端内存中提供它们可能会更快。

关于ios - 优化 openGL ES 2.0 2D 纹理输出和帧率,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9780579/

相关文章:

c++ - 复制省略和返回值优化与复制构造函数

swift - Swift 是否优化结构的链式创建和复制?

opengl-es - 如果我的场景包含少于 20 万个顶点,八叉树会提高性能吗?

android - 使用其他方法创建等效的 GL_MAX 混合方程

ios - 钥匙串(keychain)有时会返回空值

当 HTTP 请求命中并且应用程序突然进入后台时,iOS 12 URLSession 中断

ios - 按顺序将图像附加到数组中

iphone - ABPeoplePickerNavigationController 仅在模拟器中被解雇时崩溃

performance - 编译器完成的配置文件引导的优化是否会严重损害性能分析数据集未涵盖的情况?

android - 使用 CMake 在 Android Studio 中向 NDK 应用程序添加静态预构建库和 GLESv2 支持