非常缓慢的软件向量,尤其是 CoreGraphics 与 OpenGL

Posted

技术标签:

【中文标题】非常缓慢的软件向量,尤其是 CoreGraphics 与 OpenGL【英文标题】:Painfully slow software vectors, particularly CoreGraphics vs. OpenGL 【发布时间】:2013-03-12 19:25:45 【问题描述】:

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

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

    是否有可能我以某种方式滥用了 CoreGraphics(到了比 OpenGL 解决方案慢几个数量级的程度),或者性能真的那么糟糕?根据 ***/论坛关于 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

【问题讨论】:

很难说你是否在没有看到代码的情况下滥用了 Core Graphics,或者至少没有更详细的描述。您是在每一帧(显式或隐式)上创建一个新的 CGPathRef,还是提前创建一个并重新使用它?我敢打赌这会对性能产生影响。 我可能在每一帧都创建了一个新的 CGPathRef,但我必须仔细检查。 (但即使我这样做了,我也无法想象性能会提高几个数量级,你知道吗?)我知道我试图将我的重绘限制在样条曲线的每个新添加的部分,但即使这样也无济于事非常喜欢。 我构建了一个应用程序,它使用 Core Graphics 在每帧中多次重绘复杂路径。性能很好,甚至比预期的还要好。该路径由大约 100 个元素组成,线宽高达 100 像素,在未连接的部分之间有许多圆帽。在 iPad 2 和 3(视网膜分辨率)上绘制全屏时的表现给我留下了深刻的印象。 CAShapeLayer 可能会给你带来更好的运气 嗯,我刚刚构建了一个带有快速随机样条绘图的测试应用程序,并在 iPhone 5 上运行它。在 60fps 下运行良好。也许我确实做了一些灾难性的错误。回家后我会在 iPad 3 上再试一次。 【参考方案1】:

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

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

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

【讨论】:

我主要感到惊讶的是,CoreGraphics 对于矢量图形的速度有多慢。这似乎不是一项特别困难的任务,但也许我低估了每秒推动近一百万像素的难度。 不,您低估了在计算相邻像素的阴影时创建平滑线条和曲线的所有数学工作。使用 OpenGL,您必须自己执行此操作。 为了扩展这一论点,@Archagon:你的比较点不是很等价。 Safari 并不像您的测试应用程序那么慢,因为它不会尝试以 60fps 的速度全屏栅格化贝塞尔曲线。渲染(然后滚动)类型是一个非常不同的过程,它涉及较小的光栅化作业和大量缓存。【参考方案2】:

首先,请参阅 Why is UIBezierPath faster than Core Graphics path? 并确保您正在以最佳方式配置您的路径。默认情况下,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 代码更快。

【讨论】:

我在上面添加了一个代码示例:SD 模式下为 20fps,Retina 模式下为 6fps,仅 5 条曲线以尝试 60fps 的速度进行动画处理。使用 UIBezierPath 方法每秒只增加几帧。【参考方案3】:

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

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

【讨论】:

【参考方案4】:

你的速度变慢是因为这行代码:

[self setNeedsDisplay];

您需要将其更改为:

[self setNeedsDisplayInRect:changedRect];

您可以自行计算每帧更改了哪个矩形,但如果您正确执行此操作,您可能会看到性能提升一个数量级以上,而没有其他变化。

【讨论】:

以上是关于非常缓慢的软件向量,尤其是 CoreGraphics 与 OpenGL的主要内容,如果未能解决你的问题,请参考以下文章

通过引用向量传递的线程函数启动缓慢

选择选项数量导致 iPad 上的文本输入响应缓慢

360:且用且珍惜!解决虚拟机linux启动缓慢以及ssh端卡顿的问题!

R语言 3.6.3版安装+获取

用先前的非零值替换向量中的所有零

有效地计算 R 中大向量中成对差异的直方图?