Search Unity

Any way to speed up Sprite.Create?

Discussion in '2D' started by icefallgames, May 2, 2018.

  1. icefallgames

    icefallgames

    Joined:
    Dec 6, 2014
    Posts:
    75
    I need to generate texture atlases and sprites at runtime, and I'm finding the bottleneck is Sprite.Create.

    So, for instance, in one case I create 10 texture atlases, send the bits to them, and create 80 sprites (in total) from them. My timing info:

    • 0.20ms for creating the 10 textures (in total)
    • 0.08ms for LoadRawTextureData/Apply for those 10 textures (in total), 173KB total.
    • 14.3ms for SpriteCreate for 80 sprites (in total).
    That last time is really surprising to me. I'm passing SpriteMeshType.FullRect to Sprite.Create, so it shouldn't need to inspect the texture bits at all, right?

    Is there any way to speed this up? What is Sprite.Create doing under the covers to take so much time? It seems like it should just be storing a rect and some other metadata in some internal structure.
     
  2. icefallgames

    icefallgames

    Joined:
    Dec 6, 2014
    Posts:
    75
    Ok, to answer my own question, it looks like Unity is retrieving the texture pixels to calculate the sprite outline. Callstack for my call to Sprite.Create with SpriteMeshType.FullRect.

    It doesn't seem like it should be doing this. Maybe this is fixed in Unity 2018? (I'm using Unity 2017.4.1f1).

    00efe338 0fbfd7cf UnityPlayer!prcore::RemapGeneric<TexFormatA8,TexFormatARGB8888>+0x13
    00efe36c 0fbfe3fc UnityPlayer!prcore::BlitterRemapAny::Blit+0x7f
    00efe3d4 0fbfdcea UnityPlayer!prcore::BlitImageRemapNoScale+0xcc
    00efe494 0fcf1cdd UnityPlayer!prcore::BlitImage+0x10a
    00efe4a8 0fcf1bbd UnityPlayer!BlitProphecy+0x6d
    00efe4c0 0fd0f06c UnityPlayer!ImageReference::BlitImage+0x3d
    00efe584 0fd0e56a UnityPlayer!Texture2D::GetPixels32+0x11c
    00efe674 0fd06189 UnityPlayer!GenerateSpriteOutline+0x2da
    00efe6c0 0fd074e1 UnityPlayer!Sprite::GenerateOutline+0x109
    00efe6fc 0fed7d7c UnityPlayer!Sprite::Initialize+0x331
    00efe748 0724e988 UnityPlayer!Sprite_CUSTOM_INTERNAL_CALL_Create+0xec
     
    Bersaelor likes this.
  3. eighto

    eighto

    Joined:
    Apr 27, 2013
    Posts:
    32
    Bump. I'd like to see this fixed too if possible. Sprite.Create causes my game to hang for a second if I call it dozens of times in a loop (Unity 2019.2.9). This happens when each player spawns their character in the pre-game lobby, I create their sprites at this point for mod support.
     
    CloudyVR likes this.
  4. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    Not sure if there's a bug or not but I'd recommend pooling them early to avoid the hitch.
     
  5. icefallgames

    icefallgames

    Joined:
    Dec 6, 2014
    Posts:
    75
    fwiw, I just ended up writing my own sprite implementation completely separate from Unity's.
     
  6. eighto

    eighto

    Joined:
    Apr 27, 2013
    Posts:
    32
    Mind sharing?
     
  7. icefallgames

    icefallgames

    Joined:
    Dec 6, 2014
    Posts:
    75
    It's kind of tangled up with my project and customized for it (so there's extra stuff in here that you don't need), but these are the two core files I'm using. The MeshRenderer/mesh used is just a default unity quad.

    You need to construct FakeSprite by providing an atlas Texture, and a rect/pivot for the sprite in that atlas.
     

    Attached Files:

    Saeed-Barari likes this.
  8. eighto

    eighto

    Joined:
    Apr 27, 2013
    Posts:
    32
    Sweet thank you!
     
  9. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    Would it be possible to share the parameters used to call Sprite.Create and possibly give details about the Texture used to generate the Sprites from?
     
  10. eighto

    eighto

    Joined:
    Apr 27, 2013
    Posts:
    32
    Sure, here's what I'm doing:

    Code (CSharp):
    1. //each sheet for this character:
    2.         for (int i = 0; i < sheets.Length; i++) StartCoroutine(LoadTexture(sheets[i].name));
    Code (CSharp):
    1.  protected IEnumerator LoadTexture(string textureName)
    2.     {
    3.         string filePath = "file://" + rootFolderPath + gameFolderNamePlusSkinFolderName + "/" + textureName + ".png";
    4.         using (UnityWebRequest webRequest = UnityWebRequestTexture.GetTexture(filePath))
    5.         {
    6.             yield return webRequest.SendWebRequest();
    7.             if (webRequest.isNetworkError || webRequest.isHttpError) Debug.LogError(gameObject.name + " " + webRequest.error + " " + textureName + ".png");
    8.             else
    9.             {
    10.                 Texture2D texture = DownloadHandlerTexture.GetContent(webRequest);
    11.                 texture.filterMode = FilterMode.Point;
    12.                 texture.name = textureName;
    13.                 FillModDictionary(texture);
    14.             }
    15.         }
    16.     }

    Code (CSharp):
    1.  protected virtual void FillModDictionary(Texture2D texture)
    2.     {
    3.         var spritesOnThisSheet = new List<Sprite>();
    4.         for (int i = 0; i < resourceSprites.Count; i++)
    5.         {
    6.             if (resourceSprites[i].texture.name == texture.name) spritesOnThisSheet.Add(resourceSprites[i]);
    7.         }
    8.         for (int i = 0; i < spritesOnThisSheet.Count; i++)
    9.         {
    10.             Sprite spriteToCopy = dictionary[texture.name + spritesOnThisSheet[i].name];
    11.             Vector2 pivot = GetActualPivot(spriteToCopy.pivot, spriteToCopy.rect.size);
    12.             Sprite newSprite = Sprite.Create(texture, spriteToCopy.rect, pivot, 16f);
    13.             newSprite.name = spriteToCopy.name;
    14.             modDictionary.Add(texture.name + newSprite.name, newSprite);
    15.         }
    16.     }
    17.     protected static Vector2 GetActualPivot(Vector2 pivot, Vector2 rectSize)
    18.     {
    19.         return new Vector2(pivot.x / rectSize.x, pivot.y / rectSize.y);
    20.     }
     
  11. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    It seems that you are generating a Tight mesh for the Sprite? When the Sprite is created, this will attempt to generate a mesh based on the outline of the Sprite which involves reading the pixels of the texture. If the SpriteMeshType.FullRect is used instead, it will not do so and save time in generating the Sprite. However, this will increase the fill rate during rendering.
     
  12. eighto

    eighto

    Joined:
    Apr 27, 2013
    Posts:
    32
    Oh thanks, didn't know that. But I couldn't verify a change in rect. I tried comparing fullRect vs tight but they're printing out the same rect (the sprite slices that I feed in have plenty of transparency so I was expecting tight to produce a tighter rect):

    Code (CSharp):
    1. Sprite fullSprite = Sprite.Create(texture, rect, centerPivot, 16f, 0, SpriteMeshType.FullRect);
    2. print("full: " + fullSprite.rect);
    3. Sprite tightSprite = Sprite.Create(texture, rect, centerPivot, 16f, 0, SpriteMeshType.Tight);
    4. print("tight: " + tightSprite.rect);
    upload_2020-1-31_8-26-45.png

    Am I missing something?
     
  13. eighto

    eighto

    Joined:
    Apr 27, 2013
    Posts:
    32
    Oh wait duh, I realize what I'm missing. Mesh != rect
     
  14. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    You will need to check
    sprite.vertices
    to compare the difference between SpriteMeshType.Tight and FullRect.
     
  15. icefallgames

    icefallgames

    Joined:
    Dec 6, 2014
    Posts:
    75
    If you read my initial post, even when FullRect is specified, Unity reads the sprite pixels.
     
  16. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    Sorry for not replying to this issue! This has been fixed in 2017.4.2f2, where the fallback physics shape was always generated, resulting in the pixel reading (first improvement line). If FullRect is specified without further parameters (the generatePhysicsShape parameter will be False by default but you can specify it to be False if you want), there will not be any pixel reading.
     
    LiterallyJeff likes this.
  17. icefallgames

    icefallgames

    Joined:
    Dec 6, 2014
    Posts:
    75
    Great news, thanks!
     
  18. Zexx500

    Zexx500

    Joined:
    Mar 6, 2014
    Posts:
    2
    Man, this was a great reply. Speeded up my road sprites generation from 5.6 seconds down to 0.2 seconds. Thank you!
     
  19. rarac

    rarac

    Joined:
    Feb 14, 2021
    Posts:
    570
    Could someone elaborate on this?

    does this code have better performance on execution?:
    Code (CSharp):
    1. Sprite fullSprite = Sprite.Create(texture, rect, centerPivot, 16f, 0, SpriteMeshType.FullRect);
    even though it will have lesser performance while rendering in the scene? What if the sprite Im creating has the exact pixel bounds of the rect? Is it strictly superior in that case?
     
  20. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    Yes, Sprite creation will be faster as using FullRect will use directly use the rect of the Sprite as the mesh for rendering, instead of reading the outline of the Sprite from its texture and tessellating that to generate the mesh for rendering for Tight.

    This generally has better performance for Sprites whose rect includes a lot of empty space, as this will reduce the amount of overdraw because Sprites are considered as transparent geometry.

    If the Sprite you are creating has the same outline as the rect, then the generation from using Tight will produce the same results as if you were using FullRect. If you know this beforehand, using FullRect will be better, since it will produce the same result without needing to read the outline of the Sprite and tessellate that. Since the created mesh will be the same, the rendering performance will be equal in both cases.

    Do take note that the outline detection is based on the alpha values of the pixels in the texture, so there may be cases where your Sprites may appear to take up the entire rect visually, but may not be the case for outline generation.
     
    rarac likes this.
  21. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,511
    @ChuanXin There's a difference between using Tight in builds and Editor, which I find a bit annoying.

    Here's the situation: I want to generate some sprites and use OverrideGeometry. If I use Tight in Sprite.Create in builds, it behaves like FullRect then I use OverrideGeometry and it's all good. No problem there (aside from this being undocumented behavior).

    The problem is in Editor. I can't use the same pathway for Builds as in Editor. This is because the same usage of Tight, in Editor, will take 30 seconds to complete because my many sprites do their arduous geometry generation. This means that testing my sprite generation in Editor has very poor iteration time.

    This is less annoying now (since the system works and I don't need to test it), but it was extremely annoying to discover and work around at the time. If I recall correctly, I can't just use SpriteMeshType.FullRect because if using that option it won't accept OverrideGeometry, or there was some other problem with it that I'm forgetting now.

    What I want: a Sprite.Create method overload that includes the parameters from OverrideGeometry so a later call to OverrideGeometry isn't even necessary: there's a Sprite.Create method to do it all in 1 call. It behaves the same in both Editor and Build, there's no funny business, it's the 1-stop shop to create a sprite and set custom geometry.
     
    Last edited: Jun 22, 2021
  22. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    In Player Builds, the Sprite's texture needs to be readable for Tight to be utilised, otherwise it will fallback to FullRect mode (Unity is unable to generate an outline from the texture's pixel data if the texture cannot be read from the CPU). Textures by default are not readable, which is likely why you see this behaviour in Player Builds.

    This is a fair request. We will check this out and see if we can add this to our backlog. Thanks!
     
  23. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,511
    Hey, @ChuanXin thank you so much for you / the team getting around to this! Your solution is simpler and better than my suggestion :D

    https://unity3d.com/unity/alpha/2022.2.0a9
     
  24. astracat111

    astracat111

    Joined:
    Sep 21, 2016
    Posts:
    725
    I just wanted to bump this up and say,
    this optimization after like 6 years of making my games/tools in Unity has finally increased the speed of my system 50 fold!! Thank you so much! I was generating like 100 sprites with Sprite.Create and thinking because we're always told 'don't use Resources.Load' it was the Resources system.

    Nope, code before I had:

    Code (CSharp):
    1. Sprite newSprite = Sprite.Create(texture2D,new Rect(0, 0, texture2D.width, texture2D.height),new Vector2(0.5f, 0.0f), 48f);
    This had a 3-5ms delay per Sprite that was created.

    Now with just adding 0 and SpriteMeshType.FullRect to the end:

    Code (CSharp):
    1. Sprite newSprite = Sprite.Create(texture2D,new Rect(0, 0, texture2D.width, texture2D.height),new Vector2(0.5f, 0.0f), 48f, 0, SpriteMeshType.FullRect);
    It's now loading at 0ms! 50 times the speed, great optimization thank you guys so much!!