SKTextureAtlas no longer sharing textures in iOS 1

2019-01-24 14:17发布

It seems that when pulling textures from a texture atlas, I am now generating new textures instead of using the same texture across different sprites with iOS 10. On iOS 9, this works as expected. Is anyone else experiencing this issue? Perhaps there is a step I missed that is now a part of iOS 10.

Notes: I created a sample project and created a new atlas, then just dragged spaceship in @1x, I have also tried preloading, and that did nothing as well.

Code:

  let atlas = SKTexturAtlas(named:"Sprites")
  var texture = atlas.textureNamed("Spaceship")
  print("\(Unmanaged.passUnretained(texture)),\(Unmanaged.passUnretained(texture).toOpaque())")

  texture = atlas.textureNamed("Spaceship")
  print("\(Unmanaged.passUnretained(texture)),\(Unmanaged.passUnretained(texture).toOpaque())")

Edit: To get around issues of comparison, I use the description property to compare if 2 textures are equal. For this to work though, you can't be using 2 atlases that each contain a texture with an exact name and size. I will never hit this situation, but for anybody out there looking for help, keep this in mind.

3条回答
劳资没心,怎么记你
2楼-- · 2019-01-24 15:09

Is having a different physical address for a texture referencing the "same texture" really a problem?

I've run the default sample game project, but setup for Obj-C. I have a texture atlas that would be something like the image below. However, note that I ran that through TexturePacker. So the actual generated atlas by Xcode is different.

enter image description here

I do as you said and created 2 textures with the same name.

self.myTextureAtlas = [SKTextureAtlas atlasNamed:@"MyTexture"];
self.tex0 = [self.myTextureAtlas textureNamed:@"tex0"];
self.tex1 = [self.myTextureAtlas textureNamed:@"tex0"];

As you said, the pointers for tex0 and tex1 are different. So at least there is consistency between Swift and Obj-C.

However, I don't think this is a problem/bug. What I suspect is that that they changed implementation so the returned SKTexture is a new "instance", however the underlying texture is still the same.

I'll talk OpenGL, since that is what I write my engines in. Metal will still have similarities. A basic sub-texture really has only 2 important properties: a texture name (this is the OpenGL texture name) and UVs. If you were thinking about what would be considered the "equality" for conforming to Equatable, it would most likely be testing for equality against those 2 items. The texture name is the atlas texture name, and the UVs are the UVs within the atlas which represent the area of the particular sub-texture.

To test this hypothesis, I ran a GPU frame capture on this. With Xcode 8 this seems pretty buggy. Using Metal, it crashed 100% of the time. I forced it to use OpenGL and managed to get a frame capture. As expected when I looked at all texture resources, I see only one texture for my atlas.

enter image description here

Texture #3 is MyTexture.

If I dump the textureRect, which appear to be the UVs, I can see they are the same:

Tex0 Rect 0.001618 0.793765 0.139159 0.203837

Tex1 Rect 0.001618 0.793765 0.139159 0.203837

Based on this, it would seem that both self.tex0 and self.tex1, although having different physical addresses, still both point to the same sub-texture.

Note that I no longer use SpriteKit. My current renderer uses handles for textures, however, when retrieved, you can get handle objects with different physical addresses. They still all dereference to the true texture since they still reference the same underlying texture instance.

I guess, I don't really see getting diff pointers a problem provided they still reference the same underlying texture (ie. no more texture memory is allocated).

查看更多
做个烂人
3楼-- · 2019-01-24 15:15

I've make the same test and get your same results.

I'm not sure 100% but seems that during the development of Swift 3 there was a proposal here to change Unmanaged to use UnsafePointer.

But if you try to make:

func address<T: AnyObject>(o: T) -> String{
     let addr = unsafeBitCast(o, to: Int.self)
     return NSString(format: "%p", addr) as String
}

Usage:

print(address(o: texture))

in iOS9 you have correct values, in iOS10 wrong results.

enter image description here

I think you're right, we are facing a bug (another..)

查看更多
smile是对你的礼貌
4楼-- · 2019-01-24 15:15

To get around this issue, I had to come up with a way to cache the textures so that it doesn't duplicate:

private var textureCache = [String: SKTexture]()
extension SKTextureAtlas
{
    func texturesWithNames(_ names:[String]) -> [SKTexture]
    {
        var textures = [SKTexture]()
        names.forEach({textures.append(textureNamed($0))})
        return textures
    }
    func cachedTextureWithName(_ name:String) -> SKTexture
    {
        if  textureCache[name] == nil
        {

            textureCache[name] = textureNamed(name)
        }

        return textureCache[name]!
    }
    func cachedTexturesWithNames(_ names:[String]) -> [SKTexture]
    {
        var textures = [SKTexture]()
        names.forEach({textures.append(cachedTextureWithName($0))})
        return textures
    }
    func clearCache()
    {
        textureCache = [String: SKTexture]()
    }
}


extension SKTexture
{

    var name : String
    {
        return self.description.slice(start: "'",to: "'")!
    }


}
查看更多
登录 后发表回答