Чому 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, що звучить протиінтуїтивно.
Граді Граді

1
Я взагалі вважаю, що CoreGraphics дуже повільний при малюванні ліній, контурів тощо. Я не уявляю, чому, але мені в основному доводиться переходити до OpenGL або використовувати Cocos2D для ефективного малювання. Звичайно, я розумію, що це швидше, але я не дуже розумію, чому CG працює набагато повільніше, враховуючи, що він повинен використовувати сам OpenGL.
Accatyyc

4
UIBezierPathце обгортка навколо CGPathRef. Що робити, якщо запустити обидва припустимо, десять мільйонів разів, тоді візьміть середнє значення, але не використовуйте Інструменти, а два NSDateоб’єкти до та після операцій.

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

Відповіді:


154

Ви маєте рацію в тому, що UIBezierPathце просто обгортка-об'єкт-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 ... і, можливо, для відповіді.
Граді Грейді

5
Отже ... різниця в продуктивності в 2 рази була одним або декількома налаштуваннями cgcontext - наприклад, можливо щось на зразок: "lineWidth 2.0 працює гірше, ніж lineWidth 1.0" ...?
Адам

2
FWIW Я завжди вважав, що ширина рядка 1.0 є найшвидшою - моє припущення полягає в тому, що мітринг стає проблемою для ширини> 1 пікс.
Mark Aufflick
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.