SpriteKit:在预加载SKTextureAtlas时性能受到影响 [英] SpriteKit: performance hit while preloading 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屋!