【问题标题】:OpenGL ES 2.0: How to improve frame rate of drawing sprites?OpenGL ES 2.0:如何提高绘制精灵的帧率?
【发布时间】:2015-08-14 10:51:26
【问题描述】:

我有一个基于 OpenGL ES 2.0 的动画背景层,我使用 GLKView 作为图形容器,使用 GLKViewController 作为控制器。对于绘图,我使用 GLKBaseEffect。

我介绍了一个 sprite 类,它可以将 png 文件作为纹理加载、操作 sprite (SRT) 和一些附加属性,如 alpha 混合等。

我想知道如何优化我的程序,因为当我的 iPhone 4S 显示 50 个大小为 128x128 像素的精灵(都具有相同的纹理/png 文件!)时,帧速率下降到大约 25 FPS .

在以下部分中,我列出了该程序的重​​要部分。目前,我为每个帧的 50 个精灵中的每一个调用 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)(目标帧速率为 60);这相当于每秒 3000 次调用。

这可能是瓶颈吗?我该如何优化呢?

这就是我初始化精灵数组 (GLKViewController.m) 的方式:

- (void)initParticles {
    if(sprites==nil) {
        sprites = [NSMutableArray array];
        for (int i=0; i<50; i++) {
            Sprite* sprite = [[Sprite alloc] initWithFile:@"bubble" extension:@"png" effect:effect];
            // configure some sprite properties [abbreviated]
            [sprites addObject:sprite];
        }
    }
}

这是渲染函数(GLKViewController.m):

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    glClearColor(0.0, 0.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    // render the bubbles
    for (Sprite* sprite in sprites) {
        [sprite render];
    }
}

下面是精灵类(Sprite.m)的一些重要部分:

- (id)initWithFile:(NSString *)filename extension:(NSString*)extension effect:(GLKBaseEffect *)effect {
    if(self = [self init]) {
        self.effect = effect;
        NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], GLKTextureLoaderOriginBottomLeft, nil];
        NSError* error = nil;
        NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:nil];
        self.textureInfo = [GLKTextureLoader textureWithContentsOfFile:path options:options error:&error];
        if (self.textureInfo == nil) {
            NSLog(@"Error loading file: %@", [error localizedDescription]);
            return nil;
        }
        TexturedQuad newQuad;
        newQuad.bl.geometryVertex = GLKVector2Make(0, 0);
        newQuad.br.geometryVertex = GLKVector2Make(self.textureInfo.width, 0);
        newQuad.tl.geometryVertex = GLKVector2Make(0, self.textureInfo.height);
        newQuad.tr.geometryVertex = GLKVector2Make(self.textureInfo.width, self.textureInfo.height);
        newQuad.bl.textureVertex = GLKVector2Make(0, 0);
        newQuad.br.textureVertex = GLKVector2Make(1, 0);
        newQuad.tl.textureVertex = GLKVector2Make(0, 1);
        newQuad.tr.textureVertex = GLKVector2Make(1, 1);
        self.quad = newQuad;
    }
    return self;
}

- (void)render {
    [self applyBaseEffect];
    long offset = (long)&_quad;
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
    glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedVertex), (void*) (offset + offsetof(TexturedVertex, geometryVertex)));
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedVertex), (void*) (offset + offsetof(TexturedVertex, textureVertex)));
    glBlendColor(1.0, 1.0, 1.0, self.alpha);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glDisableVertexAttribArray(GLKVertexAttribPosition);
    glDisableVertexAttribArray(GLKVertexAttribTexCoord0);
}

- (void)applyBaseEffect {
    self.effect.texture2d0.name = self.textureInfo.name;
    self.effect.texture2d0.envMode = GLKTextureEnvModeModulate;
    self.effect.texture2d0.target = GLKTextureTarget2D;
    self.effect.texture2d0.enabled = GL_TRUE;
    self.effect.useConstantColor = GL_TRUE;
    self.effect.constantColor = GLKVector4Make(self.tint.r*self.alpha, self.tint.g*self.alpha, self.tint.b*self.alpha, self.alpha);
    self.effect.transform.modelviewMatrix = GLKMatrix4Multiply(GLKMatrix4Identity, [self modelMatrix]);
    [self.effect prepareToDraw];
}

- (GLKMatrix4)modelMatrix {
    GLKMatrix4 modelMatrix = GLKMatrix4Identity;
    modelMatrix = GLKMatrix4Translate(modelMatrix, self.position.x, self.position.y, 0);
    modelMatrix = GLKMatrix4Rotate(modelMatrix, self.rotation, 0, 0, 1);
    modelMatrix = GLKMatrix4Scale(modelMatrix, self.scale, self.scale, 0);
    modelMatrix = GLKMatrix4Translate(modelMatrix, -self.normalSize.width/2, -self.normalSize.height/2, 0);
    return modelMatrix;
}

EDIT-1:以下是一些性能指标(似乎受 GPU 限制)

EDIT-2:当我添加 view.drawableMultisample 行时,我的 iPhone 4S 上的帧速率从 25 提高到 45,无论我使用哪条行(无 / 4x)。奇怪 - 我的渲染代码似乎不受 MSAA 影响,相反。

GLKView *view = (GLKView*)self.view;
view.context = context;
view.drawableMultisample = GLKViewDrawableMultisampleNone;
//view.drawableMultisample = GLKViewDrawableMultisample4x;

【问题讨论】:

  • 精灵是否具有相同的特征(顶点、纹理等)?
  • 您需要先进行分析才能知道您的应用程序是 CPU 还是 GPU 绑定。请检查this 然后从 XCode 运行您的应用程序并在此处添加结果的屏幕截图。我想查看“利用率”和“帧时间”的值
  • 是的,您受 GPU 限制,但更准确地说,GPU 受片段着色器/光栅化部分的限制。稍后我会尝试写一个详细的答案。但基本上这意味着你的片段着色器太复杂和/或你有太多的屏幕表面被使用 alpha 混合的四边形占用(带有大量过度绘制的 alpha 混合是 iPhone4 / 4S 上的性能杀手,alpha 混合四边形的成本要高得多比不透明的)。您还启用了 MSAA 吗?如果您在低端 IOS 设备上以 60fps 为目标,则不建议使用 MSAA。
  • 我不明白你的 MSAA 结果,你是说 GLKViewDrawableMultisampleNone 给你 45 fps 而 GLKViewDrawableMultisample4x 给你 25 fps 吗?或者仅仅是为 view.drawableMultisample 设置一个值就可以提高你的 fps(这似乎不太可能......)?
  • 另外,我认为问题主要来自您的背景。它的着色器对于低端 IOS 来说太重了(我知道它没有太多,但这已经太多了……)。既然这只是一个“静态背景”,为什么不直接使用预制纹理而不是着色器呢?请注意,通过使用帧缓冲区对象,您可以使用着色器“渲染到纹理”背景,然后将此纹理用作背景(因此纹理将由代码生成......)。最后一件事:请确保在渲染背景时禁用 alpha 混合(我认为不需要它)

标签: ios opengl-es opengl-es-2.0


【解决方案1】:

要提高性能,您必须避免冗余代码。可能你为每个精灵设置了着色器程序。但是你总是使用相同的着色器来绘制精灵,所以你可以避免每次都设置它。您可以对顶点和纹理坐标使用这种优化。对于每个精灵,只需计算矩阵 MVP(模型-视图-投影)。

伪代码中的方法'drawFrame':

...
[set sprite program shader]
[set vertex coordinates]
[set texture coordinates]
[set textures ]
for each sprite
   [calculate matrix MVP]
   [draw sprite]
end for
...

另一个优化是使用顶点缓冲区对象,但在你的情况下,我认为它的优先级低于精灵批量优化。

我在 Android 上工作,所以我无法为您提供示例代码。不过,我建议你看看Libgdx,看看他们是如何实现精灵批处理的。

【讨论】:

  • 非常感谢您的建议。同时我们发现,该应用程序受 GPU 限制。我认为这意味着,VBO/VBA 可能有助于提高性能,而不是改变 CPU 上运行的代码。
  • 当然可以,但是如果减少对 OpenGL 状态机的更改,您将节省大量时间。我在 android opengl 壁纸上遇到了同样的问题,我得到了很好的性能提升。如果可以,请尝试一下。
  • 好的,谢谢您的建议,我会尽快尝试并在这里提供一些反馈。非常感谢!
  • 我认为 VBO 也将是一个巨大的胜利——因为驱动程序没有足够的信息来知道它不需要将您的顶点从 CPU 地址空间和格式传输到 GPU 地址每个render 上的空格和格式。所以这既是一个状态问题(一般来说,这很麻烦,因为它们意味着同步成本)和一个总线问题(这是显式同步)。我可能真的很想先看看那个。
【解决方案2】:

您的性能计数器似乎表明您受 GPU 填充率限制。您可以检查您的 bubble.png 资源是否充分利用了其纹理空间,不必要的过度绘制可能是您的问题的一部分。

您可以在 photoshop/gimp 中打开它并自动裁剪图像,如果它从边缘删除任何纹素,那么您就浪费了一些空间。如果可以消除浪费的空间,就可以减少透支。

您提到气泡是 128x128,但不清楚这是纹理尺寸还是屏幕尺寸。减小屏幕尺寸将提高性能。

除此之外,您可能还需要深入研究 Sprite 类并查看片段着色器实际在做什么。选择更高效的纹理格式并启用 mipmapping 可能也会有所帮助。

【讨论】:

    【解决方案3】:

    经过另外两个小时的深入研究,我终于找到了性能大幅下降的原因。如评论中所述,性能下降的重要性随着视图控制器中包含的按钮的增加而增加。因为我用圆角和阴影装饰我的按钮,所以我照看了一下......这是我在 UIView 类上的原始类别的一部分,用于装饰 UI 元素:

    #import "UIView+Extension.h"
    
    @implementation UIView (Extension)
    
    - (void)styleViewWithRoundedEdges:(BOOL)rounded shadowed:(BOOL)shadowed {
        if (rounded) {
            self.layer.cornerRadius = 3.0;
        }
        if (shadowed) {
            self.layer.shadowColor = [UIColor blackColor].CGColor;
            self.layer.shadowOffset = CGSizeMake(2.0, 2.0);
            self.layer.shadowOpacity = 0.25;
            self.layer.shadowRadius = 1.0;
        }
    }
    
    @end
    

    关闭投影后,帧速率立即变为恒定的 60 FPS。

    快速搜索将我定向到this SO thread

    添加这两行解决了问题:

    self.layer.shouldRasterize = YES;
    self.layer.rasterizationScale = UIScreen.mainScreen.scale;
    

    我现在在 iPhone 4S 上以 60 FPS 轻松渲染 100 个精灵以及线性+径向背景渐变着色器:-D。

    但是非常感谢你们的耐心和帮助!让我知道什么时候可以回报!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多