Pourquoi UIBezierPath est-il plus rapide que le chemin Core Graphics?

90

Je jouais avec les chemins de dessin et j'ai remarqué qu'au moins dans certains cas, UIBezierPath surpasse ce que je pensais être un équivalent Core Graphics. La -drawRect:méthode ci-dessous crée deux chemins: un UIBezierPath et un CGPath. Les chemins sont identiques à l'exception de leurs emplacements, mais caresser le CGPath prend environ deux fois plus de temps que caresser le UIBezierPath.

- (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];
}

Les deux chemins utilisent CGContextStrokePath (), j'ai donc créé des méthodes séparées pour tracer chaque chemin afin que je puisse voir l'heure utilisée par chaque chemin dans Instruments. Voici les résultats typiques (arbre d'appel inversé); vous pouvez voir que cela -strokeContext:prend 9,5 secondes, alors que cela -strokeUIBezierPath:ne prend que 5 secondes:

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:]

Il semble qu'UIBezierPath optimise en quelque sorte le chemin qu'il crée, ou que je crée le CGPath de manière naïve. Que puis-je faire pour accélérer mon dessin CGPath?

Caleb
la source
2
+1 qui semble contre-intuitif.
Grady Player
1
J'ai généralement trouvé que CoreGraphics était très lent pour dessiner des lignes, des chemins, etc. Je ne sais pas pourquoi mais je dois surtout passer à OpenGL ou utiliser Cocos2D pour un dessin efficace. Bien sûr, je comprends que c'est plus rapide, mais je ne comprends pas vraiment pourquoi CG est tellement plus lent, étant donné qu'il devrait utiliser OpenGL lui-même.
Accatyyc
4
UIBezierPathest une enveloppe autour CGPathRef. Que faire si vous exécutez les deux, disons dix millions de fois, puis prenez la moyenne, mais n'utilisez pas d'instruments mais deux NSDateobjets avant et après les opérations.
1
@WTP, les résultats sont cohérents sur l'appareil et dans le simulateur et ne changent pas s'il -drawRect:est appelé quelques dizaines de fois ou quelques centaines. Je l'ai essayé avec jusqu'à 80000 segments de courbe sur le simulateur (beaucoup trop pour l'appareil). Les résultats sont toujours à peu près les mêmes: CGPath prend environ deux fois plus de temps que UIBezierPath, même si les deux utilisent CGContextStrokePath () pour dessiner. Il semble clair que le chemin construit par UIBezierPath est en quelque sorte plus efficace que celui que je crée avec CGPathAddCurveToPoint (). J'aimerais savoir comment construire des chemins efficaces comme le fait UIBezierPath.
Caleb

Réponses:

154

Vous avez raison en ce sens qu'il UIBezierPaths'agit simplement d'un wrapper objectif-c pour Core Graphics, et donc de performances comparables. La différence (et la raison de votre delta de performances) est que votre CGContextétat lorsque vous CGPathdessinez directement est assez différent de cette configuration par UIBezierPath. Si vous regardez UIBezierPath, il a des paramètres pour:

  • lineWidth,
  • lineJoinStyle,
  • lineCapStyle,
  • miterLimit et
  • flatness

Lors de l'examen de l'appel (désassemblage) à [path stroke], vous remarquerez qu'il configure le contexte graphique actuel en fonction de ces valeurs précédentes avant d'exécuter l' CGContextStrokePathappel. Si vous faites la même chose avant de dessiner votre CGPath, il effectuera la même chose:

- (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];
}

Instantané des instruments: Instantané d'instruments montrant des performances égales

Stuart Carnie
la source
6
Merci d'avoir pris le temps d'examiner cela et d'écrire une explication aussi claire. C'est vraiment une excellente réponse.
Caleb
14
+1 pour l'icône atari bruce lee ... et éventuellement pour la réponse.
Grady Player
5
Donc ... la différence de performance 2x était un ou plusieurs des paramètres cgcontext - par exemple peut-être quelque chose comme: "lineWidth of 2.0 fonctionne moins bien que lineWidth de 1.0" ...?
Adam
2
FWIW J'ai toujours trouvé que la largeur de ligne de 1,0 était la plus rapide - mon hypothèse est que l'onglet devient un problème pour les largeurs> 1px.
Mark Aufflick