Производительность при частом рисовании 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 строк одновременно.

Другие вопросы по тегам