SpriteKit:预加载 SKTextureAtlas 时性能下降 [英] SpriteKit: performance hit while preloading SKTextureAtlas

查看:19
本文介绍了SpriteKit:预加载 SKTextureAtlas 时性能下降的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在预加载 SKTextureAtlas 时遇到性能问题:

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

所谓性能损失,是指 FPS 在短时间内下降到约 50.

我在 Instruments 中使用 Time Profiler 对其进行了测试,并验证了这项工作确实是在工作线程上完成的,如

注意 1:我预加载的 .spriteatlas 并不大:4 个资产,大约 4 个.1000x1000 大小.

注意 2:我正在使用 iPhone 6、iOS 10、Xcode 8 进行测试.

注3:没有其他实质性工作同时进行;CPU 一直徘徊在 30% 左右.GPU 也是如此.

注意 4:在需要该图集中的任何纹理之前请求图集预加载,因此它应该有足够的时间进行预加载.

非常感谢任何帮助/指导!

<小时>

更新

发生预加载的完整代码块:

让 updateGroup = DispatchGroup()对于assetContainmentDefinitions中的assetDefinition {让assetName =assetDefinition.assetName//检查是否不再需要资产并用它清除缓存如果进度>=assetDefinition.range.to {如果 cachedAssets[assetName] != nil {cachedAssets[assetName] = nil}}//检查是否需要资产,如果尚未加载,则预加载并缓存它else if progress >=assetDefinition.range.from {如果 currentLoadingAssets.contains(assetName) == false &&cachedAssets[assetName] == nil {currentLoadingAssets.append(assetName)//进入调度组updateGroup.enter()切换assetDefinition.assetType {案例.textureAtlas:让 textureAtlas = SKTextureAtlas(命名:assetName)textureAtlas.preload(completionHandler: {DispatchQueue.main.async { [weak self] inself?.cachedAssets[assetName] = textureAtlasself?.currentlyLoadingAssets.remove(object:assetName)//预加载完成后离开调度组updateGroup.leave()}})案例.纹理:让纹理 = SKTexture(imageNamed:assetName)纹理.preload(completionHandler:{DispatchQueue.main.async { [weak self] inself?.cachedAssets[assetName] = 纹理self?.currentlyLoadingAssets.remove(object:assetName)//预加载完成后离开调度组updateGroup.leave()}})}}}}//dispatch group 完全完成后调用完成如果让完成=完成{updateGroup.notify(队列:DispatchQueue.main,执行:完成)}

<小时>

更新 2

我创建了一个空项目,只有 atlas 预加载块.性能下降仍然存在.我尝试了多个图集,即使图集只有一个资产.

在这个空的新项目中,我也尝试了@Sez 的建议(见下文),但在这种情况下,甚至没有调用完成块,这似乎是 SKTextureAtlas 类.(?!)

let atlas = SKTextureAtlas(dictionary: ["texture1": UIImage(named: "texture1")!])atlas.preload(completionHandler: { [weak self] inprint("未调用完成块")self?.cachedAtlas = 图集})

<小时>

更新 3

我尝试删除 .spriteatlas 并创建具有相同纹理的 .atlas 并且(几乎)没有性能损失.但是,.atlas 不支持切片,这就是我想首先使用 .spriteatlas 的原因.

解决方案

我录制了一段直播视频,其中展示了这个导入过程.抱歉,它的时间有点长(2 小时以上),但此处的链接直接指向精灵表导入发生的那一刻.

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

按照我在这里展示的方式进行操作,您将获得一个带有切片图像的精灵图集.我的脚本(我在剪辑的前面演示过)是 在我的 GitHub 上 如果你想要他们.

I'm experiencing a performance hit when preloading SKTextureAtlas:

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

By performance hit, I mean FPS dropping to ~50 for a short amounts of time.

I tested it with Time Profiler in Instruments and verified that this work is indeed being done on a worker thread, like stated in documentation.

The image bellow shows a Time Profiler capture of the spike, caused by preloading atlas. As you can see, most of the spike is caused by 2 worker threads, which all seem to be loading image data, as far as I can understand. However, this should not cause a performance hit on the main thread IMHO.

Note 1: The .spriteatlas I'm preloading is not that big: 4 assets with approx. 1000x1000 size.

Note 2: I'm testing with iPhone 6, iOS 10, Xcode 8.

Note 3: There is no other substantial work being done at the same time; CPU is hovering at ~30% all the time. Same goes for GPU.

Note 4: The atlas preload is requested way before any of the textures from that atlas is needed, so it should have more than enough time to preload.

Any help/direction greatly appreciated!


UPDATE

Complete code block where the preload happens:

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)
}


UPDATE 2

I've created an empty project with nothing but atlas preload block. The performance drop still occurs. I've tried with multiple atlases, even with atlas with only one asset.

I've also tried what @Sez suggested (see bellow), in this empty new project, but in that case the completion block didn't even get called, which seems like another bug in SKTextureAtlas class. (?!)

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

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


UPDATE 3

I tried deleting the .spriteatlas and creating the .atlas with the same textures and there is (almost) no performance hit. However, .atlas doesn't support slicing which is why I want to use .spriteatlas in the first place.

解决方案

There are issues plaguing SKTextureAtlas (Apple Dev forums post).

Try creating your texture atlas using the SKTextureAtlas:withDictionary: supplying a dictionary of [String:UIImage], a method described on another StackOverflow post and see if that helps.

UPDATE: If you want that preload of the SKTexture to happen in a background thread, I'd write that specifically rather than relying on the preload convenience function.

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)
    }
}

Regarding app slicing, as long as you place assets in Asset catalogs they should be sliced. After you upload a build to iTunesConnect you can see a report of the App Store sizes reflecting this.

I have a video I recorded of a live stream where I show this import process. Sorry its a bit agonisingly long (2hrs+), but the link here goes right to the moment where the import into the sprite sheet happens.

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

Doing it the way I show here you get a sprite atlas, with sliced images. My scripts (which I demonstrate earlier in the clip) are on my GitHub if you want them.

这篇关于SpriteKit:预加载 SKTextureAtlas 时性能下降的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆