Уж больно медленные программные векторы, в частности CoreGraphics против OpenGL

Я работаю над приложением для iOS, которое требует рисования кривых Безье в реальном времени в ответ на вводимые пользователем данные. Сначала я решил попробовать использовать CoreGraphics, который имеет фантастический API для векторного рисования. Тем не менее, я быстро обнаружил, что производительность была мучительно, мучительно медленной, до такой степени, что частота кадров начала сильно падать только с одной кривой на моем сетчатке iPad. (Следует признать, что это был быстрый тест с неэффективным кодом. Например, кривая перерисовывалась каждый кадр. Но, конечно, современные компьютеры достаточно быстры, чтобы справиться с рисованием простой кривой каждые 1/60 секунды, верно?!)

После этого эксперимента я переключился на OpenGL и библиотеку MonkVG, и я не мог быть счастливее. Теперь я могу отображать сотни кривых одновременно, без снижения частоты кадров, с минимальным влиянием на точность (для моего случая использования).

  1. Возможно ли, что я как-то неправильно использовал CoreGraphics (до такой степени, что он был на несколько порядков медленнее, чем решение OpenGL), или производительность действительно так ужасна? Я догадываюсь, что проблема заключается в CoreGraphics, основанном на количестве вопросов и ответов Stackru/forum, касающихся производительности компьютерной графики. (Я видел, как несколько человек утверждали, что CG не предназначен для выполнения цикла выполнения и что его следует использовать только для нечастого рендеринга.) С технической точки зрения, почему это так?
  2. Если CoreGraphics действительно такой медленный, то как же Safari работает так гладко? У меня сложилось впечатление, что Safari не поддерживает аппаратное ускорение, и все же он должен отображать сотни (если не тысячи) векторных символов одновременно, не пропуская ни одного кадра.
  3. В целом, как приложения с интенсивным векторным использованием (браузеры, Illustrator и т. Д.) Остаются такими быстрыми без аппаратного ускорения? (Насколько я понимаю, многие браузеры и графические пакеты теперь оснащены опцией аппаратного ускорения, но по умолчанию она часто не включена.)

ОБНОВИТЬ:

Я написал быстрое тестовое приложение для более точного измерения производительности. Ниже приведен код моего пользовательского подкласса CALayer.

Если для NUM_PATHS установлено значение 5 и для NUM_POINTS установлено значение 15 (5 сегментов кривой на путь), код выполняется со скоростью 20 кадров в секунду в режиме без сетчатки и 6 кадров в режиме сетчатки на моем iPad 3. Профилировщик отображает CGContextDrawPath как имеющий 96% времени ЦП., Да - очевидно, я могу оптимизировать, ограничив мой прямоугольник перерисовки, но что, если мне действительно, действительно нужна полноэкранная векторная анимация на скорости 60 кадров в секунду?

OpenGL ест этот тест на завтрак. Как может векторное рисование быть таким невероятно медленным?

#import "CGTLayer.h"

@implementation CGTLayer

- (id) init
{
    self = [super init];
    if (self)
    {
        self.backgroundColor = [[UIColor grayColor] CGColor];
        displayLink = [[CADisplayLink displayLinkWithTarget:self selector:@selector(updatePoints:)] retain];
        [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
        initialized = false;

        previousTime = 0;
        frameTimer = 0;
    }
    return self;
}

- (void) updatePoints:(CADisplayLink*)displayLink
{
    for (int i = 0; i < NUM_PATHS; i++)
    {
        for (int j = 0; j < NUM_POINTS; j++)
        {
            points[i][j] = CGPointMake(arc4random()%768, arc4random()%1024);
        }
    }

    for (int i = 0; i < NUM_PATHS; i++)
    {
        if (initialized)
        {
            CGPathRelease(paths[i]);
        }

        paths[i] = CGPathCreateMutable();

        CGPathMoveToPoint(paths[i], &CGAffineTransformIdentity, points[i][0].x, points[i][0].y);

        for (int j = 0; j < NUM_POINTS; j += 3)
        {
            CGPathAddCurveToPoint(paths[i], &CGAffineTransformIdentity, points[i][j].x, points[i][j].y, points[i][j+1].x, points[i][j+1].y, points[i][j+2].x, points[i][j+2].y);
        }
    }

    [self setNeedsDisplay];

    initialized = YES;

    double time = CACurrentMediaTime();

    if (frameTimer % 30 == 0)
    {
        NSLog(@"FPS: %f\n", 1.0f/(time-previousTime));
    }

    previousTime = time;
    frameTimer += 1;
}

- (void)drawInContext:(CGContextRef)ctx
{
//    self.contentsScale = [[UIScreen mainScreen] scale];

    if (initialized)
    {
        CGContextSetLineWidth(ctx, 10);

        for (int i = 0; i < NUM_PATHS; i++)
        {
            UIColor* randomColor = [UIColor colorWithRed:(arc4random()%RAND_MAX/((float)RAND_MAX)) green:(arc4random()%RAND_MAX/((float)RAND_MAX)) blue:(arc4random()%RAND_MAX/((float)RAND_MAX)) alpha:1];
            CGContextSetStrokeColorWithColor(ctx, randomColor.CGColor);

            CGContextAddPath(ctx, paths[i]);
            CGContextStrokePath(ctx);
        }
    }
}

@end

4 ответа

Вы действительно не должны сравнивать рисунок Core Craphics с OpenGL, вы сравниваете совершенно разные функции для самых разных целей.

С точки зрения качества изображения Core Graphics и Quartz будут намного превосходить OpenGL при меньших усилиях. Инфраструктура Core Graphics разработана для оптимального внешнего вида, естественно сглаженных линий и кривых и полировки, связанной с пользовательским интерфейсом Apple. Но это качество изображения имеет цену: скорость рендеринга.

OpenGL, с другой стороны, спроектирован со скоростью как приоритет. Высокая производительность, быстрое рисование трудно победить с OpenGL. Но эта скорость обходится дорого: гораздо сложнее получить гладкую и отточенную графику с OpenGL. Существует много разных стратегий, позволяющих сделать что-то столь же "простое", как сглаживание в OpenGL, что легче обрабатывать в Quartz/Core Graphics.

Отказ от ответственности: я автор MonkVG.

Основная причина того, что MonkVG намного быстрее, чем CoreGraphics, на самом деле не столько в том, что он реализован с OpenGL ES в качестве поддержки рендеринга, а в том, что он "обманывает" путем тесселяции контуров в многоугольники перед выполнением любого рендеринга. Тесселяция контуров на самом деле мучительно медленная, и если бы вы динамически генерировали контуры, вы бы увидели большое замедление. Большим преимуществом поддержки OpenGL (стих CoreGraphics, использующей прямой рендеринг растровых изображений) является то, что любое преобразование, такое как перевод, вращение или масштабирование, не требует полного повторного пересчета контуров - это по существу "бесплатно".

Во-первых, посмотрите, почему UIBezierPath быстрее, чем путь Core Graphics? и убедитесь, что вы настраиваете свой путь оптимально. По умолчанию, CGContext добавляет много "симпатичных" опций к путям, которые могут добавить много накладных расходов. Если вы отключите их, вы, вероятно, найдете значительное улучшение скорости.

Следующая проблема, которую я обнаружил с кривыми Core Graphics Bézier, - это когда у вас много компонентов в одной кривой (я столкнулся с проблемами, когда перебрал около 3000-5000 элементов). Я нашел очень удивительное количество времени, проведенного в CGPathAdd..., Сокращение количества элементов на вашем пути может быть большой победой. Судя по моим беседам с командой Core Graphics в прошлом году, это могло быть ошибкой в ​​Core Graphics и могло быть исправлено. Я не перепроверил.


РЕДАКТИРОВАТЬ: я вижу 18-20FPS в Retina на iPad 3, внося следующие изменения:

Переместить CGContextStrokePath() вне петли. Вы не должны проходить каждый путь. Вы должны погладить один раз в конце. Это берет мой тест от ~8FPS до ~12FPS.

Отключите сглаживание (которое, вероятно, отключено по умолчанию в ваших тестах OpenGL):

CGContextSetShouldAntialias(ctx, false);

Это дает мне 18-20FPS (Retina) и до 40FPS без Retina.

Я не знаю, что вы видите в OpenGL. Помните, что Core Graphics разработана, чтобы делать вещи красивыми; OpenGL предназначен для быстрой работы. Core Graphics опирается на OpenGL; поэтому я всегда ожидал бы, что хорошо написанный код OpenGL будет быстрее.

Ваше замедление из-за этой строки кода:

[self setNeedsDisplay];

Вам нужно изменить это на:

[self setNeedsDisplayInRect:changedRect];

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

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