SpriteKit:在预加载SKTextureAtlas时性能受到影响 [英] SpriteKit: performance hit while preloading SKTextureAtlas

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

问题描述

我在预加载时遇到性能损失 SKTextureAtlas

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

受性能影响,我的意思是FPS在短时间内下降到50左右。



我测试了它时间分析器 Instruments 中并验证此工作确实在工作线程上完成,如



注1 .spriteatlas 我正在预加载不是那么大:4个资产大约有。 1000x1000 尺寸。



注意2 :我正在测试iPhone 6,iOS 10,Xcode 8。



注3 :同时没有其他实质性工作; CPU一直徘徊在30%左右。同样适用于GPU。



注4 :在需要来自该地图集的任何纹理之前请求atlas预加载,因此它应该有足够的时间预加载。



非常感谢任何帮助/方向!






更新



完成预加载的代码块:

  let updateGroup = DispatchGroup()

for assetDefinition in assetContainmentDefinitions {

let assetName = assetDefinition.assetName

//检查是否不再需要资产并用它清除缓存
如果进度> = assetDefinition.range.to {
if cachedAssets [assetName]!= nil {
cachedAssets [assetName] = nil
}
}
//检查资产是否需要,如果尚未加载,则预加载并缓存
否则如果进度> = assetDefinition.range .from {

如果currentLoadingAssets.contains(assetName)== false&&
cachedAssets [assetName] == nil {

currentLoadingAssets.append(assetName)

//输入调度组
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)

//完成预加载后离开调度组
updateGroup.leave()
}
})

case .texture:

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

DispatchQueue.main.async {$ weak self in

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

//在预加载完成后离开调度组
updateGroup.leave()
}
})
}
}
}
}

//调度组完全完成后调用完成
if let completion = completion {
updateGroup.notify( queue:DispatchQueue.main,execute:completion)
}






更新2



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



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

  let atlas = SKTextureAtlas(字典:[texture1:UIImage(名称:texture1)!] )

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






更新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天全站免登陆