Подтвердить что ты не робот

Производительность при частом рисовании CGPaths

Я работаю над приложением iOS, которое визуализирует данные как линейный график. Граф рисуется как CGPath в полноэкранном пользовательском UIView и содержит не более 320 точек данных. Данные часто обновляются, и график необходимо перерисовывать соответственно - частота обновления 10/сек будет приятной.

До сих пор так легко. Кажется, однако, что мой подход занимает много процессорного времени. Обновление графика с 320 сегментами в 10 раз в секунду приводит к 45% загрузки процессора для процесса на iPhone 4S.

Возможно, я недооцениваю графическую работу под капотом, но для меня очень много нагрузки на процессор.

Ниже приведена моя функция drawRect(), которая вызывается каждый раз, когда готов к работе новый набор данных. N содержит число точек, а points - вектор CGPoint* с координатами для рисования.

- (void)drawRect:(CGRect)rect {

    CGContextRef context = UIGraphicsGetCurrentContext();

    // set attributes
    CGContextSetStrokeColorWithColor(context, [UIColor lightGrayColor].CGColor);
    CGContextSetLineWidth(context, 1.f);

    // create path
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddLines(path, NULL, points, N+1);

    // stroke path
    CGContextAddPath(context, path);
    CGContextStrokePath(context);

    // clean up
    CGPathRelease(path); 
}

Я попытался передать путь к автономному CGContext прежде, чем добавить его к текущему слою, как предложено здесь, но без какого-либо положительного результата. Я также искал подход к CALayer напрямую, но это тоже не имело никакого значения.

Любые предложения по улучшению производительности для этой задачи? Или рендеринг просто больше для процессора, который я понимаю? Будет ли OpenGL иметь смысл/разницу?

Спасибо/Andi

Обновление: Я также попытался использовать UIBezierPath вместо CGPath. Этот пост здесь дает приятное объяснение, почему это не помогло. Тонкая настройка CGContextSetMiterLimit et al. также не принесло большого облегчения.

Обновление # 2: В итоге я переключился на OpenGL. Это была крутая и разочаровывающая кривая обучения, но повышение производительности просто невероятно. Однако алгоритмы сглаживания CoreGraphics выполняют более приятную работу, чем то, что может быть достигнуто с помощью 4x-мультисэмплинга в OpenGL.

4b9b3361

Ответ 1

Этот пост здесь дает хорошее объяснение, почему это не помогло.

Это также объясняет, почему ваш метод drawRect: работает медленно.

Каждый раз, когда вы рисуете, вы создаете объект CGPath. Вам не нужно это делать; вам нужно только создать новый объект CGPath каждый раз, когда вы изменяете набор точек. Переместите создание CGPath на новый метод, который вы вызываете только при изменении набора точек, и сохраняйте объект CGPath между вызовами этого метода. Имейте drawRect: просто получить его.

Вы уже нашли, что рендеринг - самая дорогая вещь, которую вы делаете, и это хорошо: вы не можете сделать рендеринг быстрее, не так ли? Действительно, drawRect: в идеале не должен делать ничего, кроме рендеринга, поэтому ваша цель должна состоять в том, чтобы максимально сократить время, потраченное на рендеринг, до 100%, что означает перемещение всего остального, насколько это возможно, из кода рисования.

Ответ 2

В зависимости от того, как вы делаете свой путь, может быть, что рисование 300 отдельных путей происходит быстрее, чем один путь с 300 точками. Причина этого заключается в том, что часто алгоритм рисования будет искать, чтобы определить перекрывающиеся линии и как сделать перекрестки "идеальными" - когда, возможно, вы хотите, чтобы линии непрозрачно перекрывали друг друга. Многие алгоритмы перекрытия и пересечения имеют N ** 2 или около того по сложности, поэтому скорость рисования масштабируется с квадратом числа точек в одном пути.

Это зависит от конкретных параметров (некоторые из них по умолчанию), которые вы используете. Вам нужно попробовать.

Ответ 3

Вы пытались использовать UIBezierPath вместо этого? UIBezierPath использует CGPath под капотом, но было бы интересно посмотреть, отличается ли производительность по какой-то тонкой причине. Из Документация для Apple:

Для создания путей в iOS рекомендуется использовать UIBezierPath вместо функций CGPath, если вам не нужны некоторые из возможностей что обеспечивает только Core Graphics, например добавление эллипсов в пути. Дополнительные сведения о создании и визуализации путей в UIKit см. В разделе "Рисование фигур Использование путей Безье."

Я бы также попробовал установить различные свойства в CGContext, в частности, для разных стилей объединения строк, используя CGContextSetLineJoin(), чтобы узнать, не имеет значения.

Профилировали ли вы свой код с помощью инструмента Time Profiler в инструментах? Вероятно, это лучший способ найти, где происходит узкое место производительности, даже когда узкое место находится где-то внутри системных фреймворков.

Ответ 4

Я не эксперт в этом, но сначала я бы сомневался в том, что для обновления "точек" может потребоваться время, а не рендеринг. В этом случае вы можете просто перестать обновлять точки и повторять рендеринг одного и того же пути, и посмотреть, требуется ли ему почти одинаковое время процессора. Если нет, вы можете улучшить производительность, фокусируясь на алгоритме обновления.

Если это действительно проблема рендеринга, я думаю, что OpenGL должен, безусловно, повысить производительность, потому что он будет отображать все 320 строк в то же время в теории.

Ответ 5

Я сделал это, используя Metal в своем проекте. Очевидно, что при использовании металла существует определенная сложность, поэтому, в зависимости от ваших требований к производительности, она может быть или не быть подходящей.

С Metal, все работы выполняются на графическом процессоре, поэтому использование процессора будет около нуля. На большинстве устройств iOS вы можете сделать несколько сотен или несколько тысяч кривых плавно, при 60 FPS.

Вот пример кода, который я написал, это может послужить хорошей отправной точкой для кого-то: https://github.com/eldade/ios_metal_bezier_renderer