web-dev-qa-db-fra.com

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

Je jouais avec des chemins de dessin et j'ai remarqué que dans au moins certains cas, UIBezierPath surpasse ce que je pensais être un équivalent Core Graphics. Le -drawRect: la 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 distinctes pour caresser chaque chemin afin que je puisse voir le temps utilisé par chaque chemin dans Instruments. Voici les résultats typiques (arborescence d'appels inversée); tu peux voir ça -strokeContext: prend 9,5 secondes, tandis que -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 que UIBezierPath optimise en quelque sorte le chemin qu'il crée, ou je crée le CGPath de manière naïve. Que puis-je faire pour accélérer mon dessin CGPath?

86
Caleb

Vous avez raison en ce que UIBezierPath est simplement un wrapper objective-c pour Core Graphics, et par conséquent fonctionnera de manière comparable. La différence (et la raison de votre delta de performance) est que votre état CGContext lorsque vous dessinez directement votre CGPath est assez différent de celui configuré 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émontage) à [path stroke], vous remarquerez qu'il configure le contexte graphique actuel en fonction de ces valeurs précédentes avant d'effectuer l'appel CGContextStrokePath. Si vous faites de même 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: Instruments snapshot showing equal performance

149
Stuart Carnie