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, отражающий это.

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

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

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

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

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