SpriteKit: снижение производительности при предварительной загрузке SKTextureAtlas

Я испытываю снижение производительности при предварительной загрузке SKTextureAtlas:

let textureAtlas = SKTextureAtlas(named: atlasName)
textureAtlas.preload(completionHandler: {
    ...
})

Под падением производительности я имею в виду падение FPS до ~ 50 на короткое время.

Я протестировал его с Time Profiler в Instruments и убедился, что эта работа действительно выполняется в рабочем потоке, как указано в документация.

На изображении ниже показан Time Profiler снимок всплеска, вызванный предварительной загрузкой атласа. Как видите, большая часть всплеска вызвана двумя рабочими потоками, которые, насколько я понимаю, загружают данные изображения. Однако это не должно привести к снижению производительности основного потока IMHO.

введите описание изображения здесь

Примечание 1. .spriteatlas, которые я предварительно загружаю, не так уж и много: 4 ресурса с прибл. 1000x1000 размер.

Примечание 2. Я тестирую с iPhone 6, iOS 10, Xcode 8.

Примечание 3. В то же время не ведется никакой другой существенной работы; Процессор все время зависает на ~30%. То же самое касается графического процессора.

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

Любая помощь/направление с благодарностью!


ОБНОВЛЕНИЕ

Полный блок кода, где происходит предварительная загрузка:

let updateGroup = DispatchGroup()

for assetDefinition in assetContainmentDefinitions {

    let assetName = assetDefinition.assetName

    // Check if asset is not needed anymore and clear the cache with it
    if progress >= assetDefinition.range.to {
        if cachedAssets[assetName] != nil {
            cachedAssets[assetName] = nil
        }
    }
    // Check if asset is needed and if it's not already loading then preload and cache it
    else if progress >= assetDefinition.range.from {

        if currentlyLoadingAssets.contains(assetName) == false &&
            cachedAssets[assetName] == nil {

            currentlyLoadingAssets.append(assetName)

            // Enter dispatch group
            updateGroup.enter()

            switch assetDefinition.assetType {
            case .textureAtlas:

                let textureAtlas = SKTextureAtlas(named: assetName)
                textureAtlas.preload(completionHandler: {

                    DispatchQueue.main.async { [weak self] in

                        self?.cachedAssets[assetName] = textureAtlas
                        self?.currentlyLoadingAssets.remove(object: assetName)

                        // Leave dispatch group after preload is done
                        updateGroup.leave()
                    }
                })

            case .texture:

                let texture = SKTexture(imageNamed: assetName)
                texture.preload(completionHandler: {

                    DispatchQueue.main.async { [weak self] in

                        self?.cachedAssets[assetName] = texture
                        self?.currentlyLoadingAssets.remove(object: assetName)

                        // Leave dispatch group after preload is done
                        updateGroup.leave()
                    }
                })
            }
        }
    }
}

// Call completion after the dispatch group is fully completed
if let completion = completion {
    updateGroup.notify(queue: DispatchQueue.main, execute: completion)
}

ОБНОВЛЕНИЕ 2

Я создал пустой проект только с блоком предварительной загрузки атласа. Падение производительности все равно происходит. Я пробовал с несколькими атласами, даже с атласом только с одним активом.

Я также пробовал то, что предложил @Sez (см. ниже), в этом пустом новом проекте, но в этом случае блок завершения даже не вызывался, что похоже на еще одну ошибку в классе SKTextureAtlas. (?!)

let atlas = SKTextureAtlas(dictionary: ["texture1": UIImage(named: "texture1")!])

atlas.preload(completionHandler: { [weak self] in
    print("COMPLETION BLOCK NOT CALLED")
    self?.cachedAtlas = atlas
})

ОБНОВЛЕНИЕ 3

Я попытался удалить .spriteatlas и создать .atlas с теми же текстурами, и производительность (почти) не упала. Однако .atlas не поддерживает нарезку, поэтому я хочу использовать .spriteatlas в первую очередь.


person damirstuhec    schedule 27.11.2016    source источник
comment
Я не думаю, что проблема в ваших атласах, у вас что-то еще происходит, код будет нужен   -  person Knight0fDragon    schedule 28.11.2016
comment
@Knight0fDragon, пожалуйста, смотрите обновление выше. Там действительно нет ничего особенного. Я вызываю этот блок из основного потока, жду завершения, и все.   -  person damirstuhec    schedule 28.11.2016
comment
вам нужно диагностировать mutliple вещи. Во-первых, запустите новый проект, в котором больше ничего не происходит... Затем попробуйте запустить свой поток и посмотрите, не упадет ли производительность. Затем отключите вызов многопоточности в своем основном проекте и посмотрите, упадет ли производительность. Затем сделайте многопоточный вызов, но без действий (удалите атласы). (см. второй комментарий из-за длины)   -  person Fluidity    schedule 30.11.2016
comment
Мы не видим ничего особенно вредного в вашей функции выше. Единственная другая идея, которая у меня есть, - перегрузить все встроенные шаги в цикле SK и проверить дельта-время для каждого (чтобы увидеть, какой из них отстает / занимает больше всего времени) developer.apple.com/library/content/documentation/   -  person Fluidity    schedule 30.11.2016
comment
Если вы сможете запустить эти 3 теста или опубликовать больше своего кода, возможно, у нас будет достаточно информации, чтобы дать окончательный ответ. Надеюсь это поможет!   -  person Fluidity    schedule 30.11.2016
comment
@Fluidity Я создал новый пустой проект SpriteKit только с блоком atlas.preload(completionHandler:), и у меня все еще возникает та же проблема. Пробовал с разными атласами.   -  person damirstuhec    schedule 01.12.2016
comment
Ой. Это похоже на ошибку. Возможно, обходным путем было бы просто использовать текстуры eep... или вы могли бы создать функции, которые создают атласы на лету из списка текстур, вместо того, чтобы пытаться загрузить готовые атласы. Я надеюсь, что для этого будет лучший обходной путь. Иногда работает приведение типов или использование специфики. Я пытаюсь подумать, можно ли применить что-либо из этого здесь...   -  person Fluidity    schedule 01.12.2016
comment
spitballing, что, если бы вы обернули все это в функцию, а затем добавили в нее поток с загрузкой атласа в качестве третьего потока? Или вашему основному потоку НУЖНА эта информация прямо сейчас? Кроме того, нет ли других типов/шаблонов потоковой передачи? Возможно, что-то из этого сработает.   -  person Fluidity    schedule 01.12.2016
comment
@Fluidity Смотрите мое обновление 2, где я объясняю, что создание атласа текстур на лету не сработало. Можете ли вы быть более точным с вашим последним комментарием? Я уже пытался поставить все в фоновую очередь, и это не помогает.   -  person damirstuhec    schedule 01.12.2016
comment
@Fluidity Смотрите мое обновление 3.   -  person damirstuhec    schedule 01.12.2016


Ответы (1)


Есть проблемы, связанные с SKTextureAtlas (сообщение на форумах Apple Dev).

Попробуйте создать свой атлас текстур, используя SKTextureAtlas:withDictionary:, поставляющий словарь [String:UIImage], метод, описанный в другом сообщении StackOverflow, и посмотрите, поможет ли это.

ОБНОВЛЕНИЕ: Если вы хотите, чтобы предварительная загрузка SKTexture происходила в фоновом потоке, я написал бы это специально, а не полагался на функцию удобства preload.

let atlas: SKTextureAtlas
let myImages: [String: UIImage] = getImages()
let globalQueue = DispatchQueue.global()
globalQueue.async {
    let atlas = SKTextureAtlas(withDictionary:myImages)
    for (k,v) in myImages {
        let tex = atlas.textureNamed(k)
        print(tex.size()) // force load
    }
    DispatchQueue.main.async {
        completionHandler(atlas)
    }
}

Относительно нарезка приложения, пока вы размещаете активы в каталогах активов, они должны быть нарезаны. После того, как вы загрузите сборку в iTunesConnect, вы увидите отчет о размерах в App Store, отражающий это.

Размеры магазинов приложений iTunesConnect

У меня есть видео, которое я записал в прямом эфире, где я показываю этот процесс импорта. Извините, это немного мучительно длинно (2 часа +), но ссылка здесь ведет прямо к тому моменту, когда происходит импорт в таблицу спрайтов.

https://youtu.be/Ic9Wnux8vd8?t=1h17m

Делая это так, как я показываю здесь, вы получаете атлас спрайтов с нарезанными изображениями. Мои скрипты (которые я демонстрировал ранее в ролике) находятся на моем GitHub, если вы хотите их.

person Sez    schedule 30.11.2016
comment
Серьезно, Apple до сих пор не разобралась с текстурами спрайтов... для чего-то под названием SPRITEkit? - person Confused; 04.12.2016
comment
Вы должны увидеть беспорядок, в котором находятся ресурсы по запросу! По сравнению с ними текстуры спрайтов просто мечта. :-) - person Sez; 06.12.2016