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

IOS/Core-Animation: настройка производительности

У меня мое приложение работает на моем iPad. но он работает очень плохо - я получаю ниже 15 кадров в секунду. может ли кто-нибудь помочь мне оптимизировать?

Это в основном колесо (полученное из UIView), содержащее 12 кнопок (из UIControl). image of control

По мере того как пользователь вращает его, кнопки динамически расширяются и сжимаются (например, тот, который находится в положении "12 часов", всегда должен быть самым большим)

Итак, мое колесо содержит:

- (void) displayLinkIsCallingBack: (CADisplayLink *) dispLink 
{
    :
    // using CATransaction like this goes from 14fps to 19fps
    [CATransaction begin];
    [CATransaction setDisableActions: YES];

    // NEG, as coord system is flipped/fucked
    self.transform = CGAffineTransformMakeRotation(-thetaWheel);

    [CATransaction commit];

    if (BLA)
        [self rotateNotch: direction];  
}

... который вычисляет с недавнего сенсорного ввода новое вращение колеса. Здесь уже есть одна проблема с производительностью, которую я преследую в отдельном потоке: iOS Core-Animation: проблемы производительности с матрицами преобразования CATransaction/Interpolating

Эта процедура также проверяет, завершило ли колесо еще одно вращение на 1/12, и если это так, то все 12 кнопок будут изменять размер:

// Wheel.m
- (void) rotateNotch: (int) direction
{
    for (int i=0; i < [self buttonCount] ; i++)
    {
            CustomButton * b = (CustomButton *) [self.buttons objectAtIndex: i];

    // Note that b.btnSize is a dynamic property which will calculate
    // the desired button size based on the button index and the wheels rotation.
            [b resize: b.btnSize];
    }
}

Теперь для фактического кода изменения размера в кнопке .m:

// Button.m

- (void) scaleFrom: (float) s_old
                to: (float) s_new
              time: (float) t
{
    CABasicAnimation * scaleAnimation = [CABasicAnimation animationWithKeyPath: @"transform.scale"];

    [scaleAnimation setDuration:  t ];
    [scaleAnimation setFromValue:  (id) [NSNumber numberWithDouble: s_old] ];
    [scaleAnimation setToValue:  (id) [NSNumber numberWithDouble: s_new] ];
    [scaleAnimation setTimingFunction:  [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut] ];
    [scaleAnimation setFillMode: kCAFillModeForwards];
    scaleAnimation.removedOnCompletion = NO;

    [self.contentsLayer addAnimation: scaleAnimation 
                              forKey: @"transform.scale"];

    if (self.displayShadow && self.shadowLayer)
        [self.shadowLayer addAnimation: scaleAnimation 
                                forKey: @"transform.scale"];    

    size = s_new;
}

// - - - 

- (void) resize: (float) newSize
{
    [self scaleFrom: size
                 to: newSize
               time: 1.];
}

Интересно, связана ли проблема с накладными расходами нескольких операций transform.scale, стоящими перед очередью - каждая модификация кнопки занимает полную секунду, и если я быстро вращаю колесо, я могу закрутить пару оборотов в секунду; это означает, что каждая кнопка получает размер 24 раза в секунду.

** создание слоя кнопки **

Последний фрагмент головоломки, на мой взгляд, - это посмотреть на кнопку contentsLayer. но я пробовал

contentsLayer.setRasterize = YES;

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

Я не могу поверить, что это налогообложение устройства за его пределами. однако основной инструмент анимации говорит мне иначе; в то время как я вращаю колесо (волоча пальцем по кругу), он сообщает ~ 15 кадров в секунду.

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

Должно быть, я что-то делаю неправильно! но что?

EDIT: вот код, ответственный за создание слоя содержимого кнопки (т.е. формы с тенью):

- (CALayer *) makeContentsLayer
{
    CAShapeLayer * shapeOutline = [CAShapeLayer layer];
    shapeOutline.path = self.pOutline;

    CALayer * contents = [CALayer layer];

    // get the smallest rectangle centred on (0,0) that completely contains the button
    CGRect R = CGRectIntegral(CGPathGetPathBoundingBox(self.pOutline)); 
    float xMax = MAX(abs(R.origin.x), abs(R.origin.x+R.size.width));
    float yMax = MAX(abs(R.origin.y), abs(R.origin.y+R.size.height));
    CGRect S = CGRectMake(-xMax, -yMax, 2*xMax, 2*yMax);
    contents.bounds = S; 

    contents.shouldRasterize = YES; // try NO also

    switch (technique)
    {
        case kMethodMask:
            // clip contents layer by outline (outline centered on (0, 0))
            contents.backgroundColor = self.clr; 
            contents.mask = shapeOutline;
            break;

        case kMethodComposite:
            shapeOutline.fillColor = self.clr;
            [contents addSublayer: shapeOutline];
            self.shapeLayer = shapeOutline;
            break;

        default:
            break;
    }

    if (NO)
        [self markPosition: CGPointZero
                   onLayer: contents ];

    //[self refreshTextLayer];

    //[contents addSublayer: self.shapeLayerForText];

    return contents;
}

как вы можете видеть, я стараюсь всякий возможный подход, я пытаюсь использовать два метода для создания фигуры, и отдельно я переключаюсь .shouldRasterize

** компрометация дизайна пользовательского интерфейса для получения допустимой частоты кадров **

EDIT: теперь я попытался отключить динамическое изменение размера, пока колесо не опустится в новое положение и не установит wheel.setRasterize = YES. поэтому он эффективно вращает одиночный preberendered UIView (который, по общему признанию, занимает большую часть экрана) под пальцем (который он с радостью делает @~ 60 кадров в секунду), пока колесо не успокоится, и в этот момент он выполняет эту небольшую анимацию изменения размера ( @< 20fps).

в то время как это дает терпимый результат, кажется, что мне кажется, что я должен пожертвовать своим дизайном интерфейса таким образом. Я уверен, что я должен что-то делать неправильно.

EDIT: я просто попробовал в качестве эксперимента изменить размеры кнопок вручную; т.е. поставить обратный вызов на экранной линии в каждой кнопке и динамически рассчитать ожидаемый размер этого фрейма, явно отключить анимацию с помощью CATransaction так же, как и с колесом, установить новую матрицу преобразования (масштабное преобразование, генерируемое из ожидаемого размера). добавлено к этому, я установил слой содержимого кнопок shouldRasterize = YES. поэтому он должен просто масштабировать 12 растровых изображений каждого кадра на один UIView, который сам вращается. удивительно, что это медленно, это даже приводит к остановке симулятора. Это определенно в 10 раз медленнее, чем автоматическое использование функции анимации ядра.

4b9b3361

Ответ 1

У меня нет опыта разработки iPad-приложений, но у меня есть некоторые возможности для оптимизации видеоигр. Итак, я не могу дать точный ответ, но я хочу дать несколько советов по оптимизации.

Не догадывайтесь. Профилируйте его.

Кажется, вы пытаетесь внести изменения без профилирования кода. Изменение некоторого подозрительного кода и скрещивание пальцев на самом деле не работает. Вы должны прокомментировать свой код, изучив, как long каждая задача берется и как often им нужно запустить. Попробуйте разбить свои задачи и поместите код профилирования для измерения time и frequency. Это даже лучше, если вы можете измерить, сколько memory используется для каждой задачи или сколько других системных ресурсов. Найдите свое узкое место, основанное на доказательствах, а не на ваших ощущениях.

Для вашей конкретной проблемы вы думаете, что программа становится очень медленной, когда вы выполняете работу по изменению размера. Но, вы уверены? Это может быть что-то другое. Мы не знаем наверняка, пока у нас нет фактических данных профилирования.

Сведите к минимуму проблемную область и измерьте реальную производительность перед внесением изменений.

После профилирования у вас есть кандидат на свое узкое место. Если вы все еще можете разделить причину на несколько небольших задач, сделайте это и перейдите к профилю, пока вы больше не сможете его разбить. Затем попробуйте измерить их точную производительность, запустив их несколько раз, как несколько тысяч раз. Это важно, потому что вам нужно знать производительность (скорость и пространство) перед внесением любых изменений, чтобы вы могли сравнить их с будущими изменениями.

Для вашей конкретной проблемы, если изменение размера действительно является проблемой, попробуйте изучить, как она выполняется. Сколько времени требуется для выполнения одного изменения размера? Как часто вам нужно выполнять работу по изменению размера, чтобы завершить свою работу?

Улучшить его. Как? Это зависит.

Теперь у вас есть проблемный блок кода и его текущая производительность. Теперь вам нужно его улучшить. Как? хорошо, это действительно зависит от проблемы. вы можете выполнить поиск better algorithms, вы можете сделать lazy fetching, если вы можете отложить вычисления до тех пор, пока вам действительно не понадобится выполнить, или вы можете сделать over-eager evaluation путем кеширования некоторых данных, если вы делаете такую ​​же операцию слишком часто. Чем лучше вы знаете причину, тем легче ее улучшить.

Для вашей конкретной проблемы это может быть только ограничение функции OS ui. Обычно кнопка изменения размера - это не только сама кнопка изменения размера, но также invalidates целая область или ее родительский виджет, чтобы каждый материал ui мог отображаться правильно. Кнопка изменения размера может быть дорогой, и в этом случае вы можете решить проблему, просто используя подход на основе изображений вместо системы на основе ОС ui. Вы можете попробовать использовать OpenGL, если операций с изображениями из OS API недостаточно. Но, опять же, мы не знаем, пока мы их не попробуем, и profile эти альтернативы. Удачи!:)

Ответ 2

Попробуйте без тени и посмотрите, улучшит ли производительность. Я думаю, это сильно улучшит его. Затем я рассмотрю использование теневого пути CALayer для рендеринга теней. Это значительно улучшит производительность рендеринга теней.

Видеоролики Apple Core Animation от прошлого года WWDC имеют много отличной информации об увеличении производительности в основной анимации.

Кстати, я анимационирую нечто более сложное, чем сейчас, и прекрасно работает даже на более старом iPhone 3G. Аппаратное/программное обеспечение вполне способно.

Ответ 3

Вероятно, вы просто должны сделать это в OpenGL

Ответ 4

Используете ли вы тени на своем слое для меня, это было причиной проблемы с производительностью, тогда у вас есть 2 варианта AFAIK:  - установка shadowPath таким образом, CA не нужно вычислять его каждый раз  - удаление теней и использование изображений для замены

Ответ 5

Я знаю, что этот вопрос старый, но все еще актуальный. CoreAnimation (CA) - это просто обертка вокруг OpenGL - или, между тем, возможно, вокруг Metal. Слои на самом деле представляют собой текстуры, нарисованные на прямоугольниках, а анимации выражаются с помощью 3D-преобразований. Поскольку все это обрабатывается GPU, оно должно быть очень быстрым... но это не так. Вся подсистема CA кажется довольно сложной, и перевод между AppKit/UIKit и 3D-миром сложнее, чем кажется (если вы когда-либо пытались написать такую ​​обертку самостоятельно, вы знаете, как сильно это может быть). Для программиста CA предлагает супер простой в использовании интерфейс, но эта простота идет с ценой. Все мои попытки оптимизировать очень медленный СА были бесполезны до сих пор; вы можете немного ускорить его, но в какой-то момент вам придется пересмотреть свой подход: либо CA достаточно быстр, чтобы выполнять эту работу, либо вам нужно прекратить использование CA и либо реализовать всю анимацию самостоятельно, используя классический чертеж (если процессор может справиться с этим) или реализовать сами анимации с помощью 3D-API (тогда это сделает GPU), и в этом случае вы можете решить, как 3D-мир взаимодействует с остальной частью вашего приложения; цена гораздо больше кода для написания или гораздо более сложный API для использования, но результаты сами будут говорить сами по себе.

Тем не менее, я хотел бы дать несколько общих советов о ускорении CA:

  • Каждый раз, когда вы "рисуете" слой или загружаете контент в слой (новое изображение), данные текстуры, поддерживающие этот слой, должны обновляться. Каждый 3D-программист знает: обновление текстур очень дорогое. Избегайте этого любой ценой.
  • Не используйте огромные слои, как если бы слои были слишком большими, чтобы обрабатывать их непосредственно с помощью графического процессора как единой текстуры, они разбиты на несколько текстур, и это само по себе ухудшает производительность.
  • Не используйте слишком много уровней, поскольку количество графических процессоров памяти, которое может тратить на текстуры, часто ограничено. Если вам нужно больше памяти, чем этот предел, текстуры обмениваются (удаляются из памяти графического процессора, чтобы освободить место для других текстур, а затем добавляются обратно, когда они нужны для рисования). См. Первый совет выше, этот тип обмена очень дорогой.
  • Не перерисовывайте объекты, которые не требуют перерисовки, вместо этого кэшируйте изображения. Например. рисование теней и градиентов чертежа являются utlra дорогим и обычно редко меняются. Поэтому вместо того, чтобы делать CA, нарисуйте их каждый раз, нарисуйте их один раз на слой или нарисуйте их на изображение и загрузите это изображение в CALayer, а затем расположите слой там, где он вам нужен. Контролируйте, когда вам нужно их обновить (например, если размер объекта был изменен), затем повторно нарисуйте их раз и снова, кешируйте результат. Сама CA также пытается кэшировать результаты, но вы получаете лучшие результаты, если вы сами контролируете это кэширование.
  • Осторожно с прозрачностью. Нанесение непрозрачного слоя всегда быстрее, чем рисование, которое не является. Поэтому избегайте использования прозрачности там, где это не требуется, поскольку система не будет сканировать весь контент для прозрачности (или его отсутствия). Если a NSView не содержит области, где его родительский элемент близок, создайте собственный подкласс и переопределите isOpaque, чтобы вернуть YES. То же самое верно для UIView и слоев, где ни один родитель, ни их братья и сестры никогда не будут сиять, но здесь достаточно просто установить для свойства opaque значение YES.

Если это не помогает, вы нажимаете CA на него, и вам, вероятно, нужно заменить его чем-то другим.