• Bugs
  • [Unity] GetRepackedSkin() with reduced texture sizes

  • Изменено
Related Discussions
...

Hi - long story short - GetRepackedSkin() doesn't appear to work correctly when the source textures it's packing have had their MaxSize reduced in the Texture Import Settings. (This is in 2019.4, and I've been through all the recommended notes in the documentation on GetRepackedSkin(), regarding FullRect, Read/Write enabled, etc.)

We recently started porting our game to Switch and needed to reduce the resolution of our character textures due to the memory overhead when packing them, which we do extensively. Unfortunately we ran into issues with Graphics.CopyTexture() reporting "region not fitting in source element" after overriding the platform settings for Switch.

After a lot of digging, I believe this is because Spine does not account for a reduced MaxSize in the texture import settings when repacking. More specifically, this is caused by the use of GetUnityRect() within ToTexture(), before the returned Rect is passed to CopyTexture(). The Rect returned by GetUnityRect() uses region.page.height, which will be larger than the Texture2D you are packing if its MaxSize setting has been reduced, resulting in a Rect whose dimensions will be incorrect, and likely lie outside the Texture's bounds, producing the error above.

There is a note in the XML above GetUnityRect that mentions the method "relies on region.page.height being correctly set", so this may be a known issue that I'm not dealing with correctly (if so, please let me know), but unfortunately I'm seriously short on time right now, so I'm just reporting what we're using as a workaround.

I fixed this for us by modifying ToTexture() to scale the result of GetUnityRect() by the ratio between the source texture and the page's dimensions:

public static Texture2D ToTexture (this AtlasRegion ar, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps) {
         Texture2D output;

     CachedRegionTextures.TryGetValue(ar, out output);
     if (output == null) {
        Texture2D sourceTexture = ar.GetMainTexture();
        Rect r = ar.GetUnityRect();
        
        //GetUnityRect() doesn't work if the source texture's max size in its importer settings has been reduced (e.g. per platform).
        //Scale the rect to account for this.
        float scaleX = sourceTexture.width * 1f / ar.page.width;
        float scaleY = sourceTexture.height * 1f / ar.page.height;
        var scale = new Vector2(scaleX, scaleY); 
        r = new Rect(r.position * scale, r.size * scale);
        
        int width = (int)r.width;
        int height = (int)r.height;
        output = new Texture2D(width, height, textureFormat, mipmaps) { name = ar.name };
        output.CopyTextureAttributesFrom(sourceTexture);
        AtlasUtilities.CopyTexture(sourceTexture, r, output);
        CachedRegionTextures.Add(ar, output);
        CachedRegionTexturesList.Add(output);
     }

     return output;
  }

I would like to provide a small repro project, but unfortunately due to my current time restraints, and only having our game's source assets to work with, I am unable to. However, I imagine it would be possible to reproduce this by exporting a Spine skeleton with more than one skin, reducing each of its textures atlases' MaxSize (say from 2048 to 512) in the import settings, and calling GetRepackedSkin() on a composite Skin.

I'll also throw it out there that this might be related to your GitHub issue #1609, which I came across when trying to diagnose our problem. I haven't tested whether it would work, but given my fix adjusts the rect by reading the Texture's dimensons, I would imagine it would correctly scale any textures that had been reduced in size via Quality Settings.

I'm using Spine 2.8-2019-09-27. I did try using the latest version of AssetUtilities from github but it the results were the same.


As an aside, I would personally very much appreciate it if the GC footprint of GetRepackedSkin() could be reduced. It allocates a Dictionary and five lists on every call (at least; there may be more in any secondary methods). Considering this method won't be thread safe because of Unity anyway, I would think these collections could probably be allocated once and reused?

Thanks very much for reporting and providing the code up front! Strangely I remember trying to query sourceTexture.width to create a similar workaround in the past, but back then it returned the un-reduced original texture size, maybe they have fixed this issue in the meantime.

I have created an issue ticket here:
https://github.com/EsotericSoftware/spine-runtimes/issues/1871
We will let you know once the issue has been resolved. Thanks again!


A new 3.8 spine-unity unitypackage has just been released which fixes the issue with Max Size smaller than the original size and repacking.


Another commit has just been pushed to the 3.8 branch, dealing with the unnecessary new List<> calls in every GetRepackedSkin() method call. A new 3.8 unitypackage has just been released. This improvement will be merged to the 4.0-beta branch in the next few days.

Issue ticket for reference:
https://github.com/EsotericSoftware/spine-runtimes/issues/1872

Awesome, thanks for the speedy response / fixes Harald! Glad to see I wasn't misunderstanding the method.

Also cheers for cleaning up the allocations, it's nice going forward, much appreciated. 🙂

No problem, thanks for reporting! A good report allows us to fix things quickly. 🙂