Производительность при частом рисовании CGPaths
Я работаю над приложением для iOS, которое визуализирует данные в виде линейного графика. График нарисован как CGPath
в полноэкранном режиме UIView
и содержит не более 320 точек данных. Данные часто обновляются, и график должен быть соответствующим образом перерисован - частота обновления 10/ сек была бы хорошей.
Пока все просто. Кажется, однако, что мой подход занимает много процессорного времени. Обновление графика с 320 сегментами со скоростью 10 раз в секунду приводит к 45% загрузке ЦП для процесса на iPhone 4S.
Может быть, я недооцениваю графическую работу под капотом, но мне кажется, что загрузка CPU для этой задачи очень важна.
Ниже мой 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 иметь смысл / разницу?
Спасибо / Энди
Обновление: я также пытался использовать UIBezierPath
вместо CGPath
, Этот пост дает хорошее объяснение, почему это не помогло. доводка CGContextSetMiterLimit
и другие. также не принесло большого облегчения.
Обновление № 2: я в конечном итоге перешел на OpenGL. Это была крутая и разочаровывающая кривая обучения, но повышение производительности просто невероятно. Однако алгоритмы сглаживания в CoreGraphics работают лучше, чем то, чего можно достичь с помощью 4x-мультисэмплинга в OpenGL.
5 ответов
Этот пост дает хорошее объяснение, почему это не помогло.
Это также объясняет, почему ваш drawRect:
метод медленный
Вы создаете объект CGPath каждый раз, когда рисуете. Вам не нужно делать это; вам нужно только создавать новый объект CGPath каждый раз, когда вы изменяете набор точек. Переместите создание CGPath в новый метод, который вызывается только при изменении набора точек, и сохраняйте объект CGPath между вызовами этого метода. Есть drawRect:
просто получить это.
Вы уже обнаружили, что рендеринг - это самое дорогое, что вы делаете, и это хорошо: вы не можете сделать рендеринг быстрее, не так ли? В самом деле, drawRect:
в идеале должен делать только рендеринг, поэтому ваша цель должна заключаться в том, чтобы максимально сократить время, затрачиваемое на рендеринг, до 100%, что означает удаление всего остального, насколько это возможно, из кода рисования.
В зависимости от того, как вы делаете свой путь, может получиться, что 300 отдельных путей быстрее, чем один путь с 300 точками. Причина этого заключается в том, что часто алгоритм рисования будет пытаться выяснить перекрывающиеся линии и как сделать пересечения "идеальными" - когда, возможно, вы хотите, чтобы линии только непрозрачно перекрывали друг друга. Многие алгоритмы перекрытия и пересечения имеют сложность N**2 или около того, поэтому скорость рисования масштабируется с помощью квадрата числа точек на одном пути.
Это зависит от конкретных параметров (некоторые из них по умолчанию), которые вы используете. Вы должны попробовать это.
Я сделал это, используя металл в моем проекте. Очевидно, что использование Metal сопряжено с дополнительными сложностями, поэтому, в зависимости от ваших требований к производительности, он может подходить или не подходить.
С Metal вся работа выполняется в графическом процессоре, поэтому загрузка процессора будет близка к нулю. На большинстве iOS-устройств вы можете плавно воспроизводить несколько сотен или несколько тысяч кривых со скоростью 60 кадров в секунду.
Вот пример кода, который я написал, он может послужить хорошей отправной точкой для кого-то: https://github.com/eldade/ios_metal_bezier_renderer
Вы пытались использовать UIBezierPath вместо этого? UIBezierPath использует CGPath скрытно, но было бы интересно узнать, отличается ли производительность по какой-то тонкой причине. Из документации Apple:
Для создания путей в iOS рекомендуется использовать UIBezierPath вместо функций CGPath, если вам не нужны некоторые возможности, которые предоставляет только Core Graphics, например добавление эллипсов к путям. Дополнительные сведения о создании и рендеринге путей в UIKit см. В разделе "Рисование фигур с использованием контуров Безье".
Я также попытался бы установить различные свойства в CGContext, в частности разные стили соединения строк, используя CGContextSetLineJoin()
, чтобы увидеть, если это имеет какое-либо значение.
Вы профилировали свой код, используя инструмент Time Profiler в Инструментах? Это, вероятно, лучший способ определить, где на самом деле возникает узкое место в производительности, даже когда узкое место находится где-то внутри системных платформ.
Я не эксперт в этом, но я бы сначала усомнился в том, что для обновления "точек" может потребоваться время, а не для рендеринга. В этом случае вы можете просто прекратить обновление точек и повторить рендеринг по одному и тому же пути и посмотреть, занимает ли это примерно одинаковое время ЦП. Если нет, вы можете улучшить производительность, сосредоточившись на алгоритме обновления.
Если это действительно проблема рендеринга, я думаю, OpenGL, безусловно, должен улучшить производительность, потому что теоретически он будет рендерить все 320 строк одновременно.