我正在开发一个需要实时绘制贝塞尔曲线以响应用户输入的 iOS 应用程序。起初,我决定尝试使用 CoreGraphics,它有一个很棒的矢量绘图 API。然而,我很快发现性能非常缓慢,极其缓慢,以至于我的视网膜 iPad 上只有一条曲线时帧速率开始严重下降。(诚然,这是一个使用低效代码的快速测试。例如,曲线每帧都会重新绘制。但今天的计算机肯定足够快,可以每 1/60 秒绘制一条简单曲线,对吧?!)
在这个实验之后,我切换到了 OpenGL 和MonkVG库,我高兴极了。我现在可以同时渲染数百条曲线而不会出现任何帧率下降,对保真度的影响很小(对于我的用例)。
- 是否有可能我以某种方式滥用了 CoreGraphics(以至于它比 OpenGL 解决方案慢了几个数量级),还是性能真的那么糟糕?根据 StackOverflow/论坛关于 CG 性能的问题和答案的数量,我的预感是 CoreGraphics 存在问题。(我看到有几个人说 CG 不应该进入运行循环,它应该只用于不频繁的渲染。)从技术上讲,为什么会出现这种情况?
- 如果 CoreGraphics 真的那么慢,那么 Safari 究竟是如何运行得如此流畅的呢?我的印象是 Safari 不是硬件加速的,但它必须同时显示数百个(如果不是数千个)矢量字符而不会丢失任何帧。
- 更一般地说,使用大量矢量的应用程序(浏览器、Illustrator 等)如何在没有硬件加速的情况下保持如此快速?(据我了解,现在许多浏览器和图形套件都带有硬件加速选项,但默认情况下通常不会打开。)
更新:
我编写了一个快速测试应用程序来更准确地测量性能。下面是我的自定义 CALayer 子类的代码。
NUM_PATHS 设置为 5,NUM_POINTS 设置为 15(每条路径 5 个曲线段),代码在我的 iPad 3 上以非视网膜模式下 20fps 和视网膜模式下 6fps 运行。分析器将 CGContextDrawPath 列为具有 96% 的 CPU 时间. 是的——显然,我可以通过限制我的重绘矩形来进行优化,但是如果我真的真的需要 60fps 的全屏矢量动画怎么办?
OpenGL 把这个测试当做早餐吃。矢量图怎么可能这么慢?
#import "CGTLayer.h"
@implementation CGTLayer
- (id) init
{
self = [super init];
if (self)
{
self.backgroundColor = [[UIColor grayColor] CGColor];
displayLink = [[CADisplayLink displayLinkWithTarget:self selector:@selector(updatePoints:)] retain];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
initialized = false;
previousTime = 0;
frameTimer = 0;
}
return self;
}
- (void) updatePoints:(CADisplayLink*)displayLink
{
for (int i = 0; i < NUM_PATHS; i++)
{
for (int j = 0; j < NUM_POINTS; j++)
{
points[i][j] = CGPointMake(arc4random()%768, arc4random()%1024);
}
}
for (int i = 0; i < NUM_PATHS; i++)
{
if (initialized)
{
CGPathRelease(paths[i]);
}
paths[i] = CGPathCreateMutable();
CGPathMoveToPoint(paths[i], &CGAffineTransformIdentity, points[i][0].x, points[i][0].y);
for (int j = 0; j < NUM_POINTS; j += 3)
{
CGPathAddCurveToPoint(paths[i], &CGAffineTransformIdentity, points[i][j].x, points[i][j].y, points[i][j+1].x, points[i][j+1].y, points[i][j+2].x, points[i][j+2].y);
}
}
[self setNeedsDisplay];
initialized = YES;
double time = CACurrentMediaTime();
if (frameTimer % 30 == 0)
{
NSLog(@"FPS: %f\n", 1.0f/(time-previousTime));
}
previousTime = time;
frameTimer += 1;
}
- (void)drawInContext:(CGContextRef)ctx
{
// self.contentsScale = [[UIScreen mainScreen] scale];
if (initialized)
{
CGContextSetLineWidth(ctx, 10);
for (int i = 0; i < NUM_PATHS; i++)
{
UIColor* randomColor = [UIColor colorWithRed:(arc4random()%RAND_MAX/((float)RAND_MAX)) green:(arc4random()%RAND_MAX/((float)RAND_MAX)) blue:(arc4random()%RAND_MAX/((float)RAND_MAX)) alpha:1];
CGContextSetStrokeColorWithColor(ctx, randomColor.CGColor);
CGContextAddPath(ctx, paths[i]);
CGContextStrokePath(ctx);
}
}
}
@end