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.
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
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.
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.
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?
Sure, here's what I'm doing: Code (CSharp): //each sheet for this character: for (int i = 0; i < sheets.Length; i++) StartCoroutine(LoadTexture(sheets[i].name)); Code (CSharp): protected IEnumerator LoadTexture(string textureName) { string filePath = "file://" + rootFolderPath + gameFolderNamePlusSkinFolderName + "/" + textureName + ".png"; using (UnityWebRequest webRequest = UnityWebRequestTexture.GetTexture(filePath)) { yield return webRequest.SendWebRequest(); if (webRequest.isNetworkError || webRequest.isHttpError) Debug.LogError(gameObject.name + " " + webRequest.error + " " + textureName + ".png"); else { Texture2D texture = DownloadHandlerTexture.GetContent(webRequest); texture.filterMode = FilterMode.Point; texture.name = textureName; FillModDictionary(texture); } } } Code (CSharp): protected virtual void FillModDictionary(Texture2D texture) { var spritesOnThisSheet = new List<Sprite>(); for (int i = 0; i < resourceSprites.Count; i++) { if (resourceSprites[i].texture.name == texture.name) spritesOnThisSheet.Add(resourceSprites[i]); } for (int i = 0; i < spritesOnThisSheet.Count; i++) { Sprite spriteToCopy = dictionary[texture.name + spritesOnThisSheet[i].name]; Vector2 pivot = GetActualPivot(spriteToCopy.pivot, spriteToCopy.rect.size); Sprite newSprite = Sprite.Create(texture, spriteToCopy.rect, pivot, 16f); newSprite.name = spriteToCopy.name; modDictionary.Add(texture.name + newSprite.name, newSprite); } } protected static Vector2 GetActualPivot(Vector2 pivot, Vector2 rectSize) { return new Vector2(pivot.x / rectSize.x, pivot.y / rectSize.y); }
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.
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): Sprite fullSprite = Sprite.Create(texture, rect, centerPivot, 16f, 0, SpriteMeshType.FullRect); print("full: " + fullSprite.rect); Sprite tightSprite = Sprite.Create(texture, rect, centerPivot, 16f, 0, SpriteMeshType.Tight); print("tight: " + tightSprite.rect); Am I missing something?
You will need to check sprite.vertices to compare the difference between SpriteMeshType.Tight and FullRect.
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.
Man, this was a great reply. Speeded up my road sprites generation from 5.6 seconds down to 0.2 seconds. Thank you!
Could someone elaborate on this? does this code have better performance on execution?: Code (CSharp): 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?
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.
@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.
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!
Hey, @ChuanXin thank you so much for you / the team getting around to this! Your solution is simpler and better than my suggestion https://unity3d.com/unity/alpha/2022.2.0a9
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): 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): 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!!