Почему UIBezierPath быстрее, чем путь Core Graphics?


90

Я играл с контурами рисования и заметил, что по крайней мере в некоторых случаях UIBezierPath превосходит то, что я думал, будет эквивалентом Core Graphics. Приведенный -drawRect:ниже метод создает два пути: один UIBezierPath и один CGPath. Пути идентичны, за исключением их расположения, но перемещение по CGPath занимает примерно вдвое больше времени, чем перемещение по 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];
}

Оба пути используют CGContextStrokePath (), поэтому я создал отдельные методы для обводки каждого пути, чтобы я мог видеть время, используемое каждым путем в инструментах. Ниже приведены типичные результаты (дерево вызовов перевернуто); Вы можете видеть, что это -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 каким-то образом оптимизирует создаваемый им путь, или я создаю CGPath наивным способом. Что я могу сделать, чтобы ускорить рисование CGPath?


2
+1, что звучит противоречиво.
Grady Player

1
Я обычно обнаружил, что CoreGraphics очень медленно рисует линии, пути и т. Д. Понятия не имею, почему, но в основном мне приходится переходить на OpenGL или использовать Cocos2D для эффективного рисования. Конечно, я понимаю, что это быстрее, но я не совсем понимаю, почему CG намного медленнее, учитывая, что он должен использовать сам OpenGL.
Accatyyc

4
UIBezierPathэто обертка вокруг CGPathRef. Что, если вы запустите оба, скажем, десять миллионов раз, затем возьмете среднее значение, но не используете инструменты, а два NSDateобъекта до и после операций.

1
@WTP, результаты согласованы на устройстве и в симуляторе и не меняются при -drawRect:вызове несколько десятков раз или нескольких сотен. Я пробовал это с целых 80000 сегментов кривой на симуляторе (слишком много для устройства). Результаты всегда примерно одинаковы: CGPath занимает примерно в два раза больше времени, чем UIBezierPath, хотя оба используют CGContextStrokePath () для рисования. Кажется очевидным, что путь, построенный UIBezierPath, в какой-то мере более эффективен, чем тот, который я создаю с помощью CGPathAddCurveToPoint (). Я хотел бы знать, как строить эффективные пути, как это делает UIBezierPath.
Калеб

Ответы:


156

Вы правы в том, что UIBezierPathэто просто оболочка objective-c для Core Graphics и, следовательно, будет работать сравнимо. Разница (и причина для вашей дельты производительности) в том, что ваше CGContextсостояние при CGPathпрямом рисовании сильно отличается от той настройки 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
+1 за иконку atari bruce lee ... и, возможно, за ответ.
Grady Player

5
Итак ... двукратная разница в производительности связана с одной или несколькими настройками cgcontext - например, что-то вроде: «lineWidth of 2.0 работает хуже, чем lineWidth of 1.0» ...?
Adam

2
FWIW Я всегда считал, что ширина линии 1.0 является самой быстрой - мое предположение состоит в том, что смягчение становится проблемой для ширины> 1 пикселя.
Марк Оффлик
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.