Search Unity

can not do late atlas binding after reload spriteAtlas

Discussion in '2D' started by ranye, Aug 30, 2019.

  1. ranye

    ranye

    Joined:
    Sep 10, 2012
    Posts:
    18
    i want to cache lots of game objects and sprites,and load/unload spriteAtlas dynamically to save memory, atlas texture is huge.

    the first late binding is successful, the experiment is following:
    1, i keep gameObjs and sprite ,
    2, unload spriteAtlas and atlas texture using Resources.UnloadAsset(),
    3, load spriteAtlas and atlas texture again from assetBundle
    4, call previous saved System.Action<SpriteAtlas> callback in SpriteAtlasManager.atlasRequested

    this time late binding doesn't work for the sprites already existed.
    only new sprite cloned from spriteAtlas has atlas texture.
    unity version is 2018.4.4f1

    i hope old sprite can be bind again.
    the experiment is done using assetBundle, sprite and spriteAtlas asset is one bundle, gameObjs is in another,
    bundle isn't unloaded during experiment.
    in memory profiler spriteAtlas and atlas texture is unloaded/loaded correctly.

    btw, now spriteAtlas can only be power of 2 , multiple of 4 will comsume less memory , and is supported by dxt/etc/astc, mipmap is not used in this case
     
    Last edited: Aug 31, 2019
  2. MikePage_Artrix

    MikePage_Artrix

    Joined:
    Mar 18, 2016
    Posts:
    15
    Have you possibly managed to find a solution to this?

    I have almost an identical issue on 2019.2.0

    1. handle sprite atlas requested by saving the callback
    2. load sprite atlas from asset bundle
    3. callback action with sprite atlas - everything is fine here
    4. unload scene and call assetbundle.unload(true) on the sprite atlas bundle
    5. reload scene

    once I reload the scene and we repeat the cycle, not late binding will take place. I get the request in 1. and return the newly loaded atlas in 3. but the sprites simply don't show.
     
  3. ranye

    ranye

    Joined:
    Sep 10, 2012
    Posts:
    18
    i found a workaround, that is to use cloned sprites from spriteAtals to replace old sprites.
    but that is not that good for performance, especially for ugui, lots of ui components and sprite need to be handled.
     
  4. Venkify

    Venkify

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    644
    @MikePage_Artrix @ranye Please submit a bug report with a simple repro project please. We will take a look.
     
  5. MikePage_Artrix

    MikePage_Artrix

    Joined:
    Mar 18, 2016
    Posts:
    15
    Last edited: Sep 6, 2019
  6. MikePage_Artrix

    MikePage_Artrix

    Joined:
    Mar 18, 2016
    Posts:
    15
    @Venkify To add this, I think I've found the issue. This snipping shows the difference between my test scene after I've unloaded the sprite atlas (and unload the scene with the sprites that referenced it) and reloaded it.

    You can see that two sprites are left over from the unloading of the sprite atlas, even though I also unloaded an additive scene which contained the sprites. These two sprites have a ref count of 0.

    upload_2019-9-9_10-9-0.png

    If I forcibly call Resources.UnloadUnsedAssets() before reload it actually solves the problem by cleaning up these two orphaned sprites.

    However, this solution only works in my most basic test bed and does not work in our currently released Android application.
     
  7. MikePage_Artrix

    MikePage_Artrix

    Joined:
    Mar 18, 2016
    Posts:
    15
    @Venkify further to this, after some testing I replaced the UI images with this component,

    Code (CSharp):
    1. public class ManagedImage : Image
    2. {    protected override void OnDestroy()
    3.     {
    4.         Resources.UnloadAsset(sprite);
    5.         base.OnDestroy();
    6.     }
    7. }
    This then works as expected when the additive scene is reloaded.

    Although it also throws up a few:

    UnloadAsset can only be used on assets;
    UnityEngine.Resources:UnloadAsset(Object)

    Errors during runtime...

    OK, these errors are caused by trying to unload a sprite returned by SpriteAtlas.GetSprite() which are clones, once I wrapped these so the don't try to unload everything is fine.
     
    Last edited: Sep 9, 2019
  8. Venkify

    Venkify

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    644
  9. ranye

    ranye

    Joined:
    Sep 10, 2012
    Posts:
    18
  10. MikePage_Artrix

    MikePage_Artrix

    Joined:
    Mar 18, 2016
    Posts:
    15
    @Venkify Just a little more of an update. Unfortunately though everything seemed to be working, eventually the game/editor will crash with the following stack trace:

    Code (CSharp):
    1. ========== OUTPUTTING STACK TRACE ==================
    2.  
    3. 0x00007FF7B41730DC (Unity) SpriteAtlas::GetRuntimeRenderData
    4. 0x00007FF7B4171729 (Unity) SpriteAtlas::CanBindTo
    5. 0x00007FF7B50F3C61 (Unity) SpriteAtlas_CUSTOM_CanBindTo
    6. 0x0000017DA1AB78E9 (Mono JIT Code) (wrapper managed-to-native) UnityEngine.U2D.SpriteAtlas:CanBindTo (UnityEngine.U2D.SpriteAtlas,UnityEngine.Sprite)
    7. 0x0000017DA1AB75CB (Mono JIT Code) [C:\Program Files\Unity\Hub\Editor\2019.2.0f1\Editor\Data\Resources\PackageManager\BuiltInPackages\com.unity.ugui\Runtime\UI\Core\Image.cs:1864] UnityEngine.UI.Image:RebuildImage (UnityEngine.U2D.SpriteAtlas)
    8. 0x0000017DA1AB73C4 (Mono JIT Code) [C:\buildslave\unity\build\Runtime\2D\SpriteAtlas\ScriptBindings\SpriteAtlas.bindings.cs:31] UnityEngine.U2D.SpriteAtlasManager:PostRegisteredAtlas (UnityEngine.U2D.SpriteAtlas)
    9. 0x0000017DB5EAA59B (Mono JIT Code) (wrapper runtime-invoke) <Module>:runtime_invoke_void_object (object,intptr,intptr,intptr)
    10. 0x00007FF8B01BBC80 (mono-2.0-bdwgc) [c:\users\bokken\builds\vm\mono\mono\mini\mini-runtime.c:2809] mono_jit_runtime_invoke
    11. 0x00007FF8B0141D92 (mono-2.0-bdwgc) [c:\users\bokken\builds\vm\mono\mono\metadata\object.c:2921] do_runtime_invoke
    12. 0x00007FF8B014AD8F (mono-2.0-bdwgc) [c:\users\bokken\builds\vm\mono\mono\metadata\object.c:2968] mono_runtime_invoke
    13. 0x00007FF7B4FB9AF2 (Unity) scripting_method_invoke
    14. 0x00007FF7B4FB37B1 (Unity) ScriptingInvocation::Invoke
    15. 0x00007FF7B4173E41 (Unity) SpriteAtlasManager::Register
    16. 0x00007FF7B50F3AC5 (Unity) SpriteAtlasManager_CUSTOM_Register
    17. 0x0000017DA1AB71CE (Mono JIT Code) (wrapper managed-to-native) UnityEngine.U2D.SpriteAtlasManager:Register (UnityEngine.U2D.SpriteAtlas)
    18. 0x0000017D9E25845B (Mono JIT Code) [C:\Development\Depots\ManaMonsters\up_6\mc_client\Assets\Scripts\System\SpriteAtlasLateBinder.cs:97] SpriteAtlasLateBinder:SpriteAtlasRequested (string,System.Action`1<UnityEngine.U2D.SpriteAtlas>)
    19. 0x0000017D9E257D75 (Mono JIT Code) [C:\buildslave\unity\build\Runtime\2D\SpriteAtlas\ScriptBindings\SpriteAtlas.bindings.cs:20] UnityEngine.U2D.SpriteAtlasManager:RequestAtlas (string)
    20. 0x0000017D9E257F8B (Mono JIT Code) (wrapper runtime-invoke) <Module>:runtime_invoke_bool_object (object,intptr,intptr,intptr)
    21. 0x00007FF8B01BBC80 (mono-2.0-bdwgc) [c:\users\bokken\builds\vm\mono\mono\mini\mini-runtime.c:2809] mono_jit_runtime_invoke
    22. 0x00007FF8B0141D92 (mono-2.0-bdwgc) [c:\users\bokken\builds\vm\mono\mono\metadata\object.c:2921] do_runtime_invoke
    23. 0x00007FF8B014AD8F (mono-2.0-bdwgc) [c:\users\bokken\builds\vm\mono\mono\metadata\object.c:2968] mono_runtime_invoke
    24. 0x00007FF7B4FB9AF2 (Unity) scripting_method_invoke
    25. 0x00007FF7B4FB37B1 (Unity) ScriptingInvocation::Invoke
    26. 0x00007FF7B4FADA0A (Unity) ScriptingInvocation::Invoke<bool>
    27. 0x00007FF7B4173F6A (Unity) SpriteAtlasManager::RequestAtlasViaScript
    28. 0x00007FF7B417272F (Unity) `SpriteAtlasManager::SpriteAtlasManager'::`2'::EarlyUpdateSpriteAtlasManagerUpdateRegistrator::Forward
    29. 0x00007FF7B4A7D2A8 (Unity) ExecutePlayerLoop
    30. 0x00007FF7B4A7D389 (Unity) ExecutePlayerLoop
    31. 0x00007FF7B4A821FB (Unity) PlayerLoop
    32. 0x00007FF7B304EA9B (Unity) PlayerLoopController::UpdateScene
    33. 0x00007FF7B304C59C (Unity) Application::TickTimer
    34. 0x00007FF7B38DBE50 (Unity) MainMessageLoop
    35. 0x00007FF7B38DE86F (Unity) WinMain
    36. 0x00007FF7B6575792 (Unity) __scrt_common_main_seh
    37. 0x00007FF8F90F7BD4 (KERNEL32) BaseThreadInitThunk
    38. 0x00007FF8F9CECE71 (ntdll) RtlUserThreadStart
    39.  
    40. ========== END OF STACKTRACE ===========
     
  11. MikePage_Artrix

    MikePage_Artrix

    Joined:
    Mar 18, 2016
    Posts:
    15
    @Venkify

    So I've tracked down the problem causing the crash.

    This occurs when you have a coroutine / async function running waiting to Invoke the action from the sprite atlas late binding.

    Basically if an Image has been loaded which causes the late binder to start one if these async functions and then the Image is removed, the callback action is still valid.

    So when the sprite atlas is loaded, it calls the action, the sprite atlas manager gets the callback and promptly tries to see if a now destroy sprite can bind to the sprite atlas. Causing the crash.
     
  12. joe_nk

    joe_nk

    Joined:
    Jan 6, 2017
    Posts:
    67
    I've just run into this issue.

    In my case (I know this is not best practice) I'm instantiating a prefab that contains an Image with a sprite set as the source. That sprite triggers a late bind request and I load that atlas from from an asset bundle and trigger the given request callback. While this load is on-going, I swap the source sprite for a null sprite - expecting a white square. However, when the atlas request callback is invoked, I get a crash. I figured this is the problem, in com.unity.ugui/Runtime/UI/Core/Image.cs:

    Code (CSharp):
    1. static void RebuildImage(SpriteAtlas spriteAtlas)
    2. {
    3.     for (var i = m_TrackedTexturelessImages.Count - 1; i >= 0; i--)
    4.     {
    5.         var g = m_TrackedTexturelessImages[i];
    6.         if (spriteAtlas.CanBindTo(g.activeSprite))
    7.         {
    8.             g.SetAllDirty();
    9.             m_TrackedTexturelessImages.RemoveAt(i);
    10.         }
    11.     }
    12. }
    g.activeSprite is null, and passing null to spriteAtlas.CanBindTo yields a callstack idential to that posted by @MikePage_Artrix.

    I believe the solution to be a simple null check:

    Code (CSharp):
    1. static void RebuildImage(SpriteAtlas spriteAtlas)
    2. {
    3.     for (var i = m_TrackedTexturelessImages.Count - 1; i >= 0; i--)
    4.     {
    5.         var g = m_TrackedTexturelessImages[i];
    6.         if (g.activeSprite != null)
    7.         {
    8.             if (spriteAtlas.CanBindTo(g.activeSprite))
    9.             {
    10.                 g.SetAllDirty();
    11.                 m_TrackedTexturelessImages.RemoveAt(i);
    12.             }
    13.         }
    14.         else
    15.         {
    16.             m_TrackedTexturelessImages.RemoveAt(i);
    17.         }
    18.     }
    19. }
     
  13. AnthonyReddan

    AnthonyReddan

    Joined:
    Feb 27, 2018
    Posts:
    39
    This is my exact use case. Doesn't this make Late Binding somewhat unusable? We want to late bind Atlas loaded from AssetBundle.

    From the documentation:

    Seems like a bug to me. We're getting this on Motorola Moto G(7) Supra and Moto G6+
     
  14. as5405as

    as5405as

    Joined:
    Aug 22, 2017
    Posts:
    5
    Is anyone has solution ? I upload unity from 2018.3 to 2019.4 this bug happen.Is it fix?
     
  15. Venkify

    Venkify

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    644
    We are looking into the issue, will post an update soon. Thanks for posting.
     
    AnthonyReddan likes this.
  16. joe_nk

    joe_nk

    Joined:
    Jan 6, 2017
    Posts:
    67
    Any update here?
     
  17. tealm

    tealm

    Joined:
    Feb 4, 2014
    Posts:
    108
    Still an issue in 2019.4.9f1! I have Sprite Atlas variants pre loaded through Addressables in a Preload scene, and atlasRequested does not get called on my UI scene. What is even wierder is that I actually get one atlasRequested warning immediatly the next time I hit Play in the editor.

    Neither the variants or master atlases have "Included in Build" checked (since they are addressables).
     
    santo821 likes this.
  18. tealm

    tealm

    Joined:
    Feb 4, 2014
    Posts:
    108
    I created a empty project today to debug this further. At first it works fine, but after testing out various code the
    SpriteAtlasManager.atlasRequested
    events stopped being invoked again. I am using
     Addressables.LoadAssetAsync()
    to load the sprite atlas.

    When the bug appeared and the atlasRequested event was not being invoked I had to close Unity, and delete the Library folder in order for it to show up again in playmode.

    @Venkify Not sure if that helps, sounds like a very wierd bug. I assume it would not happen in a build, only editor - but ill make sure to test that if the bug appears again.
     
  19. JulyYin

    JulyYin

    Joined:
    Oct 11, 2019
    Posts:
    9
    The same issue,load atlas form assetbundle, first time late binding is working, when we release the bundle by unload(true), next time back to the scene,late binding does not work
     
  20. BeardlyDan

    BeardlyDan

    Joined:
    Oct 18, 2013
    Posts:
    27
    Still an issue with 2022.3.10f1. Unity atlases are pretty useless because of this issue. Without working late binding, it is impossible to manage the memory.