UIBezierPathがCore Graphicsパスより速いのはなぜですか?


90

私は描画パスをいじってみましたが、少なくともいくつかのケースでは、UIBezierPathがCore Graphicsの同等のものだと思っていたものよりも優れていることに気付きました。-drawRect:以下のメソッドは2つのパスを作成します。1つはUIBezierPath、もう1つはCGPathです。パスは場所を除いて同一ですが、CGPathのストロークには、UIBezierPathのストロークの約2倍の時間がかかります。

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

    // Create the two paths, cgpath and uipath.
    CGMutablePathRef cgpath = CGPathCreateMutable();
    CGPathMoveToPoint(cgpath, NULL, 0, 100);

    UIBezierPath *uipath = [[UIBezierPath alloc] init];
    [uipath moveToPoint:CGPointMake(0, 200)];

    // Add 200 curve segments to each path.
    int iterations = 200;
    CGFloat cgBaseline = 100;
    CGFloat uiBaseline = 200;
    CGFloat xincrement = self.bounds.size.width / iterations;
    for (CGFloat x1 = 0, x2 = xincrement;
         x2 < self.bounds.size.width;
         x1 = x2, x2 += xincrement)
    {
        CGPathAddCurveToPoint(cgpath, NULL, x1, cgBaseline-50, x2, cgBaseline+50, x2, cgBaseline);
        [uipath addCurveToPoint:CGPointMake(x2, uiBaseline)
                  controlPoint1:CGPointMake(x1, uiBaseline-50)
                  controlPoint2:CGPointMake(x2, uiBaseline+50)];
    }
    [[UIColor blackColor] setStroke];
    CGContextAddPath(ctx, cgpath);

    // Stroke each path.
    [self strokeContext:ctx];
    [self strokeUIBezierPath:uipath];

    [uipath release];
    CGPathRelease(cgpath);
}

- (void)strokeContext:(CGContextRef)context
{
    CGContextStrokePath(context);
}

- (void)strokeUIBezierPath:(UIBezierPath*)path
{
    [path stroke];
}

どちらのパスもCGContextStrokePath()を使用するため、Instrumentsで各パスが使用する時間を確認できるように、各パスをストロークする個別のメソッドを作成しました。以下は典型的な結果です(呼び出しツリーを反転)。あなたはそれ-strokeContext:が9.5秒-strokeUIBezierPath:かかるのに対し、わずか5秒かかるのがわかります:

Running (Self)      Symbol Name
14638.0ms   88.2%               CGContextStrokePath
9587.0ms   57.8%                 -[QuartzTestView strokeContext:]
5051.0ms   30.4%                 -[UIBezierPath stroke]
5051.0ms   30.4%                  -[QuartzTestView strokeUIBezierPath:]

UIBezierPathが何らかの方法でUIBezierPathが作成するパスを最適化しているようであるか、または単純な方法でCGPathを作成しています。CGPath描画を高速化するにはどうすればよいですか?


2
+1は直感にそぐわないように聞こえます。
グレイディプレーヤー

1
私は一般的に、CoreGraphicsが線やパスなどを描くときに非常に遅いことを発見しました。なぜなのかはわかりませんが、効率的に描画するには、ほとんどの場合、OpenGLを使用するか、Cocos2Dを使用する必要があります。確かに私はそれがより高速であることを理解していますが、CGがOpenGL自体を使用しているはずであることを考えると、CGが非常に遅い理由を本当に理解していません。
Accatyyc

4
UIBezierPathはラッパーCGPathRefです。たとえば、両方を実行した場合、たとえば1,000万回実行して平均を計算しますがNSDate、操作の前後にInstrumentsを使用せずに2つのオブジェクトを使用します。

1
@WTP、結果はデバイスとシミュレータで一貫しており-drawRect:、数十回呼び出されても数百回呼び出されても変わりません。シミュレータで80000ものカーブセグメントを使用して試してみました(デバイスには多すぎます)。結果は常にほぼ同じです。CGContextStrokePath()を使用して描画しても、CGPathはUIBezierPathの約2倍かかります。UIBezierPathが構築するパスは、CGPathAddCurveToPoint()で作成したパスよりも効率的であることは明らかです。UIBezierPathのように効率的なパスを構築する方法を教えてください。
カレブ

回答:


154

あなたは正しいでありUIBezierPath、単純コアグラフィックスのためのObjective-Cのラッパーであるため、比較的実行します。違い(およびパフォーマンスデルタの理由)はCGContextCGPath直接描画したときの状態であり、による設定とはかなり異なりますUIBezierPath。を見るとUIBezierPath、次の設定があります。

  • lineWidth
  • lineJoinStyle
  • lineCapStyle
  • miterLimit そして
  • flatness

への呼び出し(逆アセンブリ)を調べると、呼び出しを[path stroke]実行する前に、以前の値に基づいて現在のグラフィックコンテキストが構成されていることがわかりCGContextStrokePathます。CGPathを描画する前に同じことを行うと、同じように実行されます。

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

    // Create the two paths, cgpath and uipath.
    CGMutablePathRef cgpath = CGPathCreateMutable();
    CGPathMoveToPoint(cgpath, NULL, 0, 100);

    UIBezierPath *uipath = [[UIBezierPath alloc] init];
    [uipath moveToPoint:CGPointMake(0, 200)];

    // Add 200 curve segments to each path.
    int iterations = 80000;
    CGFloat cgBaseline = 100;
    CGFloat uiBaseline = 200;
    CGFloat xincrement = self.bounds.size.width / iterations;
    for (CGFloat x1 = 0, x2 = xincrement;
         x2 < self.bounds.size.width;
         x1 = x2, x2 += xincrement)
    {
        CGPathAddCurveToPoint(cgpath, NULL, x1, cgBaseline-50, x2, cgBaseline+50, x2, cgBaseline);
        [uipath addCurveToPoint:CGPointMake(x2, uiBaseline)
                  controlPoint1:CGPointMake(x1, uiBaseline-50)
                  controlPoint2:CGPointMake(x2, uiBaseline+50)];
    }
    [[UIColor blackColor] setStroke];
    CGContextAddPath(ctx, cgpath);

    // Stroke each path
    CGContextSaveGState(ctx); {
        // configure context the same as uipath
        CGContextSetLineWidth(ctx, uipath.lineWidth);
        CGContextSetLineJoin(ctx, uipath.lineJoinStyle);
        CGContextSetLineCap(ctx, uipath.lineCapStyle);
        CGContextSetMiterLimit(ctx, uipath.miterLimit);
        CGContextSetFlatness(ctx, uipath.flatness);
        [self strokeContext:ctx];
        CGContextRestoreGState(ctx);
    }
    [self strokeUIBezierPath:uipath];

    [uipath release];
    CGPathRelease(cgpath);
}

- (void)strokeContext:(CGContextRef)context
{
    CGContextStrokePath(context);
}

- (void)strokeUIBezierPath:(UIBezierPath*)path
{
    [path stroke];
}

機器からのスナップショット: 同等のパフォーマンスを示す機器のスナップショット


6
これを検討し、そのような明確な説明を書いてくれてありがとう。これは本当に素晴らしい答えです。
カレブ

14
atari bruce leeアイコンの+1と、おそらく答え。
Gradyプレーヤー、

5
つまり、2倍のパフォーマンスの違いは、cgcontext設定の1つ以上でした。たとえば、「lineWidthの2.0は、lineWidthの1.0よりもパフォーマンスが悪い」などです。
アダム

2
FWIW私は常に1.0の線幅が最速であると思っていました。私の仮定は、幅が1pxを超えるとマイタリングが問題になるためです。
Mark Aufflick 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.