为啥这个 OpenGL ES 代码在 iPhone 上运行缓慢?

Posted

技术标签:

【中文标题】为啥这个 OpenGL ES 代码在 iPhone 上运行缓慢?【英文标题】:Why is this OpenGL ES code slow on iPhone?为什么这个 OpenGL ES 代码在 iPhone 上运行缓慢? 【发布时间】:2009-01-16 10:46:03 【问题描述】:

我在学习 OpenGL ES 时稍微修改了 iPhone SDK 的 GLSprite 示例,结果发现速度很慢。即使在模拟器中(硬件最差)所以我一定做错了,因为它只有 400 个带纹理的三角形。

const GLfloat spriteVertices[] = 
  0.0f, 0.0f, 
  100.0f, 0.0f,  
  0.0f, 100.0f,
  100.0f, 100.0f
;

const GLshort spriteTexcoords[] = 
  0,0,
  1,0,
  0,1,
  1,1
;

- (void)setupView 
    glViewport(0, 0, backingWidth, backingHeight);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrthof(0.0f, backingWidth, backingHeight,0.0f, -10.0f, 10.0f);
    glMatrixMode(GL_MODELVIEW);

    glClearColor(0.3f, 0.0f, 0.0f, 1.0f);

    glVertexPointer(2, GL_FLOAT, 0, spriteVertices);
    glEnableClientState(GL_VERTEX_ARRAY);
    glTexCoordPointer(2, GL_SHORT, 0, spriteTexcoords);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    // sprite data is preloaded. 512x512 rgba8888   
    glGenTextures(1, &spriteTexture);
    glBindTexture(GL_TEXTURE_2D, spriteTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    free(spriteData);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glEnable(GL_TEXTURE_2D);
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
 

- (void)drawView 
  ..
    glClear(GL_COLOR_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(tx-100, ty-100,10);
    for (int i=0; i<200; i++)  
        glTranslatef(1, 1, 0);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    
  ..

每次触摸屏幕或移动屏幕上的手指时都会调用 drawView,并将 tx,ty 设置为发生触摸的 x,y 坐标。

我也尝试过使用 GLBuffer,当翻译是预先生成的并且只有一个 DrawArray 但提供相同的性能 (~4 FPS)。

===编辑===

同时,我已对其进行了修改,以便使用更小的四边形(尺寸:34x20)并且重叠更少。大约 400 个四边形 -> 800 个三角形分布在整个屏幕上。纹理大小为 512x512 atlas 和 RGBA_8888,而纹理坐标为浮点数。 该代码在 API 效率方面非常难看:有两个 MatrixMode 更改以及两个加载和两个平移,然后是三角形带(四边形)的绘制数组。 现在这会产生 ~45 FPS。

【问题讨论】:

【参考方案1】:

(我知道这很晚,但我无法抗拒。无论如何我都会发帖,以防其他人来这里寻求建议。)

这与纹理大小无关。我不知道为什么人们评价尼尔斯。他似乎对 OpenGL 管道有一个根本性的误解。他似乎认为对于给定的三角形,整个纹理被加载并映射到该三角形上。反之亦然。

一旦三角形被映射到视口中,它就会被光栅化。对于三角形覆盖的每个屏幕像素,都会调用片段着色器。默认片段着色器(您正在使用的 OpenGL ES 1.1)将查找最接近映射 (GL_NEAREST) 到您正在绘制的像素的纹素。它可能会查找 4 个纹素,因为您使用更高质量的 GL_LINEAR 方法来平均最佳纹素。尽管如此,如果您的三角形中的像素数是 100,那么您必须读取的纹理字节数最多为 4(查找)* 100(像素)* 4(每种颜色的字节数。远远少于 Nils 所说的。令人惊讶的是,他可以让自己听起来好像他真的知道自己在说什么。

WRT 平铺架构,这在嵌入式 OpenGL 设备中很常见,以保留参考的局部性。我相信每个图块都会暴露在每个绘图操作中,很快就会剔除其中的大部分。然后图块决定在其自身上绘制什么。当你打开混合时,这会慢得多,就像你做的那样。因为您使用的大三角形可能会与其他图块重叠和混合,所以 GPU 必须做很多额外的工作。如果不是渲染带有 alpha 边缘的示例正方形,而是渲染实际形状(而不是形状的正方形图片),那么您可以关闭这部分场景的混合,我敢打赌这会加快速度非常好。

如果您想尝试一下,只需关闭混合并查看速度加快了多少,即使看起来不正确。 glDisable(GL_BLEND);

【讨论】:

感谢您的回答。实际上,也应该删除 200x2 ogl 调用。相反,整个场景应该被一个 glDraw 调用淹没。我会试一试,然后告诉你结果。 禁用混合将 fps 提高 10 倍。混合后的瓷砖利用率约为 5%。约 45% 没有。 gldraw 仍然被调用了 200 次。哇。 据我了解,每个图块都必须保留与其相交的每个三角形的队列。如果不进行混合,覆盖瓷砖的三角形可以刷新在其之前绘制的所有内容的瓷砖队列。通过混合,瓷砖必须保持一个完整的队列,直到最后。我曾经为一家在其视频芯片上使用定制 GPU ASIC 的公司开发 GPU。而且我仍然不完全理解平铺。祝你好运。 如果我们假设 tiler 利用率实际上是恒定的,如果我们将总速度提高 10 倍,则 tiler 时间的相对百分比也应该增长(几乎)10 倍。像素填充率在没有 alphablending 的情况下变得更快,原因有两个。第一个原因是 alphablending 本身是一项昂贵的操作,第二个原因是 tiling 引擎只有在 alphablending/alphatest 关闭时才能完成它的工作。 忘了补充说,可能每个纹理硬件都包含片上纹理缓存。因此,在使用 GL_LINEAR 时,您通常不会将内存获取量乘以 4。硬件不会从缓存中提取一次,而是并行执行 4 次。如果缓存未命中,将从系统内存中读取整个缓存行,其中包含几个附近像素的数据。【参考方案2】:

您的纹理是每像素 512*512*4 字节。那是一兆字节的数据。如果每帧渲染 200 次,则会产生每帧 200 兆字节的带宽负载。

在大约 4 fps 的情况下,仅纹理读取就消耗 800mb/秒。帧和 Zbuffer 写入也需要带宽。然后是 CPU,不要小看显示器的带宽要求。

嵌入式系统(例如您的 iphone)上的 RAM 不如台式 PC 上的快。您在这里看到的是带宽匮乏效应。 RAM 根本无法更快地处理数据。

如何解决这个问题:

选择一个合理的纹理大小。平均而言,每个像素应该有 1 个纹素。这给出了清晰的纹理。我知道 - 这并不总是可能的。使用常识

使用 mipmap。这占用了 33% 的额外空间,但允许图形芯片在可能的情况下选择使用较低分辨率的 mipmap。

尝试更小的纹理格式。也许您可以使用 ARGB4444 格式。这将使渲染速度加倍。还要看看压缩的纹理格式。解压缩不会像在硬件中那样导致性能下降。事实恰恰相反:由于内存更小,图形芯片可以更快地读取纹理数据。

【讨论】:

为什么要为每个三角形加载纹理?那不是已经在显存中了吗? 我不了解 iPhone,但在手持和嵌入式设备上,视频和系统内存之间没有物理差异是很常见的。这称为统一内存。 此外,从带宽使用的角度来看,纹理是在视频内存还是系统内存中也没有什么区别。仅仅因为纹理在显存中并不意味着显存具有无限带宽。 vid-mem 也不是更快或更聪明左右.. 使用较小的纹理格式并没有加快速度。虽然,使用较小的顶点(显然?)。当正方形的大小为 20x20 时,渲染速度足够快(~50fsp)。即使有大纹理。我认为性能缓慢是因为设置:200 个重叠正方形。 真的,很难理解为什么这个明显不正确的答案会得到这么多票。是的,大纹理会破坏纹理缓存,但内存获取量仅与渲染像素量成正比。关于减少内存带宽使用的建议实际上是正确的,但在这种情况下无关紧要。顺便说一句,在 MBX/SGX 上,Z-buffer/framebuffer 访问只使用少量的内存带宽,除非你编程不正确。【参考方案3】:

我想我的第一次尝试只是一个糟糕(或非常好)的测试。 iPhone 有一个 PowerVR MBX Lite,它有一个基于 tile 的图形处理器。它将屏幕细分为更小的图块并平行渲染。现在在上面的第一种情况下,由于非常高的重叠,细分可能会有点用尽。此外,由于距离相同,它们无法被裁剪,因此必须计算所有纹理坐标(这可以通过更改循环中的平移来轻松测试)。 同样由于重叠,并行性无法被利用,一些瓦片什么也不做,其余的(1/3)工作很多。

所以我认为,虽然内存带宽可能是一个瓶颈,但在本例中并非如此。问题更多在于图形硬件的工作方式和测试设置。

【讨论】:

如果启用了 alpha 混合或 alpha 测试,则不适用过度绘制优化,因为每个像素都可以是透明的,因此不会拒绝任何内容。瓦片不是并行渲染的,顺便说一句,它们是按顺序渲染的,但是 z-buffer 受限于瓦片大小并使用片上内存实现。【参考方案4】:

我不熟悉 iPhone,但如果它没有专门的硬件来处理浮点数(我怀疑它没有),那么尽可能使用整数会更快。 我目前正在为android(也使用OpenGL ES)开发,例如我的顶点数组是int而不是float。我不能说它有多大的不同,但我想它值得一试。

【讨论】:

低端和高端Android硬件之间的巨大差异是Android是劣质游戏平台的原因之一。 iPhone 从一开始就有 VFP 和非常不错的硬件规格。没有理由费时的整数数学技巧。 PowerVR 硬件本机接受和使用浮点数。事实上,32 位定点数学会降低性能。【参考方案5】:

Apple 对 iPhone 的具体硬件规格守口如瓶,这对于我们这些来自控制台背景的人来说似乎很奇怪。但是人们已经能够确定 CPU 是 32 位 RISC ARM1176JZF。好消息是它有一个完整的浮点单元,所以我们可以像在大多数平台上一样继续编写数学和物理代码。

http://gamesfromwithin.com/?p=239

【讨论】:

以上是关于为啥这个 OpenGL ES 代码在 iPhone 上运行缓慢?的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL ES/iPhone 的透明度/混合问题

Iphone 上的多个 OpenGL ES 视图是不是有高效且标准化的代码?

iPhone 成本与收益 - OpenGL ES 1.x 与 2.0

iPhone Cheetah 3D OpenGL ES 顶点缓冲对象 (VBO) 示例

iPhone 应用程序中奇怪的 OpenGL ES 行为

将 DirectX 移植到 OpenGL ES (iPhone)