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%. То же самое касается GPU.
Примечание 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
на первом месте.
1 ответ
Есть проблемы, изводящие SKTextureAtlas (сообщение на форуме Apple Dev).
Попробуйте создать свой атлас текстуры, используя SKTextureAtlas:withDictionary:
поставляя словарь [String:UIImage]
метод, описанный в другом посте Stackru, и посмотрите, поможет ли это.
ОБНОВЛЕНИЕ: Если вы хотите, чтобы предварительная загрузка 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, отражающий это.
У меня есть видео, которое я записал в прямом эфире, где я показываю этот процесс импорта. Извините, это немного мучительно долго (2 часа +), но ссылка здесь идет прямо в тот момент, когда происходит импорт в лист спрайтов.
https://youtu.be/Ic9Wnux8vd8?t=1h17m
Делая это так, как я показываю здесь, вы получаете атлас спрайтов с нарезанными изображениями. Мои скрипты (которые я демонстрирую ранее в клипе) находятся на моем GitHub, если вы хотите их.