7

我正在开发一个需要实时绘制贝塞尔曲线以响应用户输入的 iOS 应用程序。起初,我决定尝试使用 CoreGraphics,它有一个很棒的矢量绘图 API。然而,我很快发现性能非常缓慢,极其缓慢,以至于我的视网膜 iPad 上只有一条曲线时帧速率开始严重下降。(诚​​然,这是一个使用低效代码的快速测试。例如,曲线每帧都会重新绘制。但今天的计算机肯定足够快,可以每 1/60 秒绘制一条简单曲线,对吧?!)

在这个实验之后,我切换到了 OpenGL 和MonkVG库,我高兴极了。我现在可以同时渲染数百条曲线而不会出现任何帧率下降,对保真度的影响很小(对于我的用例)。

  1. 是否有可能我以某种方式滥用了 CoreGraphics(以至于它比 OpenGL 解决方案慢了几个数量级),还是性能真的那么糟糕?根据 StackOverflow/论坛关于 CG 性能的问题和答案的数量,我的预感是 CoreGraphics 存在问题。(我看到有几个人说 CG 不应该进入运行循环,它应该只用于不频繁的渲染。)从技术上讲,为什么会出现这种情况?
  2. 如果 CoreGraphics 真的那么慢,那么 Safari 究竟是如何运行得如此流畅的呢?我的印象是 Safari 不是硬件加速的,但它必须同时显示数百个(如果不是数千个)矢量字符而不会丢失任何帧。
  3. 更一般地说,使用大量矢量的应用程序(浏览器、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
4

4 回答 4

4

您真的不应该将 Core Graphics 绘图与 OpenGL 进行比较,您正在比较完全不同的功能以实现非常不同的目的。

在图像质量方面,Core Graphics 和 Quartz 将远远优于 OpenGL,而且花费更少。Core Graphics 框架旨在实现最佳外观、自然抗锯齿的线条和曲线以及与 Apple UI 相关的润色。但这种图像质量是有代价的:渲染速度。

另一方面,OpenGL 的设计以速度为优先。高性能、快速绘图是 OpenGL 难以匹敌的。但是这种速度是有代价的:使用 OpenGL 获得平滑和优美的图形要困难得多。有许多不同的策略可以做一些像 OpenGL 中的抗锯齿一样“简单”的事情,Quartz/Core Graphics 更容易处理这些事情。

于 2013-04-12T12:43:18.410 回答
3

免责声明:我是 MonkVG 的作者。

MonkVG 比 CoreGraphics 快得多的最大原因实际上并不是它使用 OpenGL ES 作为渲染支持来实现,而是因为它通过在任何渲染完成之前将轮廓细分为多边形来“作弊”。轮廓细分实际上非常缓慢,如果您要动态生成轮廓,您会看到速度大大减慢。OpenGL 支持(与使用直接位图渲染的 CoreGraphics 相反)的巨大好处是,任何诸如平移、旋转或缩放的变换都不会强制轮廓的完全重新镶嵌——它本质上是“免费的”。

于 2014-11-07T18:03:53.813 回答
3

首先,看看为什么 UIBezierPath 比 Core Graphics 路径快?并确保您以最佳方式配置路径。默认情况下,CGContext会为路径添加很多“漂亮”选项,这会增加很多开销。如果您关闭这些功能,您可能会发现速度显着提高。

我在 Core Graphics Bézier 曲线中发现的下一个问题是当你在一条曲线中有许多组件时(当我超过大约 3000-5000 个元素时,我看到了问题)。我发现在CGPathAdd.... 减少路径中的元素数量可能是一个重大胜利。从我去年与 Core Graphics 团队的谈话来看,这可能是 Core Graphics 中的一个错误,并且可能已经修复。我没有重新测试。


编辑:通过进行以下更改,我在 iPad 3 上的 Retina 中看到 18-20FPS:

移动CGContextStrokePath()外循环。你不应该抚摸每条路径。你应该在最后抚摸一次。这将我的测试从 ~8FPS 提高到 ~12FPS。

关闭抗锯齿(可能在您的 OpenGL 测试中默认关闭):

CGContextSetShouldAntialias(ctx, false);

这让我达到了 18-20FPS(Retina)和高达 40FPS 左右的非 Retina。

我不知道你在 OpenGL 中看到了什么。请记住,Core Graphics 旨在使事物变得美丽;OpenGL 旨在让事情变得更快。Core Graphics 依赖于 OpenGL;所以我总是希望编写良好的 OpenGL 代码更快。

于 2013-03-12T20:10:28.170 回答
1

您的减速是因为这行代码:

[self setNeedsDisplay];

您需要将其更改为:

[self setNeedsDisplayInRect:changedRect];

由您来计算每帧更改了哪个矩形,但是如果您正确执行此操作,您可能会看到超过一个数量级的性能改进而没有其他更改。

于 2013-11-09T23:10:08.177 回答