iOS上的核心图形性能

smi*_*org 31 iphone performance core-graphics quartz-2d ios

摘要

我正在为iOS制作一款相当简单的2D塔防游戏.

到目前为止,我一直在使用Core Graphics专门处理渲染.应用程序中根本没有图像文件(尚未).我一直在经历一些相对简单的绘图时出现的重大性能问题,我正在寻找关于如何解决这个问题的想法,而不是转向OpenGL.

游戏设置

在高层次上,我有一个Board类,它是UIView代表游戏板的子类.游戏中的所有其他对象(塔,小兵,武器,爆炸等)也是子类UIView,并在创建时作为子视图添加到棋盘中.

我将游戏状态与对象中的视图属性完全分开,并且每个对象的状态在主游戏循环中更新(由NSTimer60-240 Hz 激发,具体取决于游戏速度设置).游戏完全可玩,无需绘制,更新或动画视图.

我使用CADisplayLink本机刷新率(60 Hz)的计时器处理视图更新,该计时器调用setNeedsDisplay需要根据游戏状态的变化更新其视图属性的板对象.电路板上的所有对象都覆盖drawRect:在其框架内绘制一些非常简单的2D形状.因此,例如,当一个武器被动画化时,它将根据武器的新状态重绘自己.

性能问题

在iPhone 5上测试,在电路板上总共有大约24个游戏对象,帧速率显着下降到60 FPS(目标帧速率),通常在10-20 FPS范围内.随着屏幕上的更多动作,它从这里走下坡路.在iPhone 4上,情况更糟.

使用Instruments我已经确定只有大约5%的CPU时间用于实际更新游戏状态 - 其中绝大部分是用于渲染.具体来说,CGContextDrawPath函数(根据我的理解是矢量路径的光栅化完成的地方)占用了大量的CPU时间.有关详细信息,请参阅底部的"仪器"屏幕截图.

从对StackOverflow和其他网站的一些研究来看,似乎Core Graphics不能满足我的需求.显然,抚摸矢量路径非常昂贵(尤其是在绘制不透明且具有某些alpha值<1.0的东西时).我几乎可以肯定OpenGL可以解决我的问题,但它的水平相当低,而且我不是很高兴不得不使用它 - 看起来它不应该是我在这里所做的事情.

问题

是否有任何优化我应该考虑尝试从Core Graphics获得平滑的60 FPS?

一些想法......

有人建议我考虑将所有对象都绘制成单个CALayer而不是单独使用每个对象CALayer,但我不相信这会有助于根据Instruments展示的内容.

就个人而言,我有一个理论,即使用CGAffineTransforms我的动画(即,drawRect:一次绘制对象的形状,然后转换以在后续帧中移动/旋转/调整其图层大小)将解决我的问题,因为这些直接基于OpenGL的.但我不认为这样做比直接使用OpenGL更容易.

示例代码

为了让你感受到我正在做的绘画水平,这里是我的一个drawRect:武器对象(从塔发射的"光束")的实现示例.

注意:此光束可以"重新定位"并穿过整个电路板,因此为简单起见,其框架尺寸与电路板尺寸相同.但是,电路板上的大多数其他对象的框架设置为最小的外接矩形.

- (void)drawRect:(CGRect)rect
{
    CGContextRef c = UIGraphicsGetCurrentContext();

    // Draw beam
    CGContextSetStrokeColorWithColor(c, [UIColor greenColor].CGColor);
    CGContextSetLineWidth(c, self.width);
    CGContextMoveToPoint(c, self.origin.x, self.origin.y);
    CGPoint vector = [TDBoard vectorFromPoint:self.origin toPoint:self.destination];
    double magnitude = sqrt(pow(self.board.frame.size.width, 2) + pow(self.board.frame.size.height, 2));
    CGContextAddLineToPoint(c, self.origin.x+magnitude*vector.x, self.origin.y+magnitude*vector.y);
    CGContextStrokePath(c);

}
Run Code Online (Sandbox Code Playgroud)

仪器运行

让游戏运行一段时间后,看看乐器:

TDGreenBeam类具有完全相同drawRect:的示例代码部分如上图所示执行.

全尺寸截图 仪器运行游戏,最重的堆栈跟踪扩展.

Tom*_*mmy 17

核心图形工作由CPU执行.然后将结果推送到GPU.当你打电话给setNeedsDisplay你时表明需要重新进行绘图工作.

假设您的许多对象保持一致的形状并且仅仅移动或旋转,您应该只需调用setNeedsLayout父视图,然后在该视图中推送最新的对象位置layoutSubviews,可能直接推送到center属性.仅仅调整位置并不会导致需要重新绘制; 合成器将简单地要求GPU重现已经在不同位置的图形.

更常见的游戏解决方案可能是忽略center,boundsframe不是初始设置.只需按下您想要的仿射变换transform,可能是使用这些助手的某些组合创建的.这将允许您无需CPU干预即可重新定位,旋转和缩放对象 - 这些都是GPU工作.

如果你想要更多控制,那么每个视图都有CALayer自己的视图,affineTransform但它们也有一个sublayerTransform与子层变换相结合的视图.因此,如果您对3d如此感兴趣,那么最简单的方法是在超层上加载合适的透视矩阵sublayerTransform,然后将合适的3d变换推送到子图层或子视图.

这种方法有一个明显的缺点,即如果你绘制一次然后放大,你将能够看到像素.您可以contentsScale提前调整图层以尝试改善图层,否则您只会看到允许GPU继续合成的自然结果.magnificationFilter如果要在线性和最近的过滤之间切换,则图层上有一个属性; linear是默认值.


jus*_*tin 7

机会是,你正在透支.也就是说,绘制冗余信息.

因此,您需要将视图层次结构分解为多个层(如您所述).仅更新/绘制所需内容.这些图层可以缓存合成的中间体,然后GPU可以快速合成所有这些图层.但是你需要小心地只绘制你需要绘制的东西,并且只使实际改变的层的区域无效.

调试它:打开"Quartz Debug"并启用"Flash相同屏幕更新",然后在模拟器中运行您的应用程序.你想最小化那些彩色闪光.

一旦过度绘制得到修复,请考虑您可以在辅助线程上渲染的内容(例如CALayer.drawsAsynchronously),或者如何处理合成中间表示(例如缓存)或栅格化不变层/面.执行这些更改时,请小心测量成本(例如内存和CPU).