Search Unity

Instanced rendering with texture arrays causing lag at certain zoom levels

Discussion in 'Shaders' started by cpancake, Jul 2, 2019.

  1. cpancake

    cpancake

    Joined:
    Sep 13, 2014
    Posts:
    4
    I've got a problem I just can't work out here. In our game, we're rendering an isometric tile map, generally 50x50 tiles, with multiple possible layers - so around 2500-5000 quads to render the entire map. This would obviously run terribly without instancing, so I set it up to use a single texture array to render all the sprites (118 sprites currently), with each quad receiving an instanced _SpriteIndex property to point to an entry in this texture array.

    This works perfectly fine zoomed out - the entire board can render at a smooth 60FPS. It also works fine close up - though it does drop below sixty. In the middle of those two zoom levels, however, it can slow down to even single digit FPS numbers. The GPU time per frame spikes, GPU usage hits 100%, and the game slows to a crawl, even with no change in number of draw calls or triangles drawn. I thought this problem could be mipmapping, but disabling mipmapping seems to have no effect.

    Here is a version of the shader we are using, with only the necessary portions:
    Code (CSharp):
    1. Shader "Sprites/MinimalExample"
    2. {
    3.     Properties
    4.     {
    5.     }
    6.  
    7.     SubShader
    8.     {
    9.         Tags
    10.         {
    11.             "Queue" = "Transparent"
    12.             "IgnoreProjector" = "True"
    13.             "RenderType" = "Transparent"
    14.             "PreviewType" = "Plane"
    15.         }
    16.  
    17.         Cull Off
    18.         Lighting Off
    19.         ZWrite Off
    20.         Blend One OneMinusSrcAlpha
    21.  
    22.         Pass
    23.         {
    24.             CGPROGRAM
    25.  
    26.             #pragma vertex SpriteVert
    27.             #pragma fragment SpriteFrag
    28.             #pragma target 2.0
    29.             #pragma multi_compile_instancing
    30.             #pragma require 2darray
    31.  
    32.             #include "UnityCG.cginc"
    33.  
    34.             UNITY_INSTANCING_BUFFER_START(PerDrawSprite)
    35.                 UNITY_DEFINE_INSTANCED_PROP(float, _SpriteIndex_Instanced)
    36.             UNITY_INSTANCING_BUFFER_END(PerDrawSprite)
    37.  
    38.             #define _SpriteIndex    UNITY_ACCESS_INSTANCED_PROP(PerDrawSprite, _SpriteIndex_Instanced)
    39.  
    40.             UNITY_DECLARE_TEX2DARRAY(_MainTexArray);
    41.  
    42.             struct appdata_t
    43.             {
    44.                 float4 vertex   : POSITION;
    45.                 float2 texcoord : TEXCOORD0;
    46.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    47.             };
    48.  
    49.             struct v2f
    50.             {
    51.                 float4 vertex   : SV_POSITION;
    52.                 float2 texcoord : TEXCOORD0;
    53.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    54.             };
    55.  
    56.             v2f SpriteVert(appdata_t IN)
    57.             {
    58.                 v2f OUT;
    59.  
    60.                 UNITY_SETUP_INSTANCE_ID(IN);
    61.  
    62.                 OUT.vertex = UnityObjectToClipPos(IN.vertex);
    63.                 OUT.texcoord = IN.texcoord;
    64.  
    65.                 UNITY_TRANSFER_INSTANCE_ID(IN, OUT);  
    66.  
    67.                 return OUT;
    68.             }
    69.  
    70.             fixed4 SpriteFrag(v2f IN) : SV_Target
    71.             {
    72.                 UNITY_SETUP_INSTANCE_ID(IN);
    73.                 float4 c = UNITY_SAMPLE_TEX2DARRAY(_MainTexArray, float3(IN.texcoord, _SpriteIndex));
    74.                 c.rgb *= c.a;
    75.                 return c;
    76.             }
    77.  
    78.             ENDCG
    79.         }
    80.     }
    81. }
    82.  
    Replacing _SpriteIndex with a constant makes the issue go away, so it seems that sampling from the texture array is the root of this issue. Does anyone know what the problem is, or have any leads I can follow?

    This is Unity 2018.4.1f1.
     
  2. aleksandrk

    aleksandrk

    Unity Technologies

    Joined:
    Jul 3, 2017
    Posts:
    3,014
    What platform does this happen on?
     
  3. cpancake

    cpancake

    Joined:
    Sep 13, 2014
    Posts:
    4
    I've only tested it on Windows, and it seems to happen across rendering APIs - D3D11, Vulkan, and OpenGL tested with the same issue.


    I've been slowly narrowing down the problem and I've found that, at the exact zoom level that the issue starts occurring, the textures get substantially sharper. That is, at a size of 8.6615 the textures are blurry and the game runs well, and at 8.6614 the textures are sharp and run terribly. So it's something to do with mipmapping after all...?
     
  4. aleksandrk

    aleksandrk

    Unity Technologies

    Joined:
    Jul 3, 2017
    Posts:
    3,014
    Try making your texture array way smaller, like 4x4x<num_layers>. This will make sure it hits the cache at least most of the time.
     
  5. cpancake

    cpancake

    Joined:
    Sep 13, 2014
    Posts:
    4
    Using half-sized textures for the texture array seemed to work (128x512 instead of 256x1024). Is there a way to solve this that doesn't reduce texture quality or should we only use the full-sized textures for higher-end GPUs?
     
  6. aleksandrk

    aleksandrk

    Unity Technologies

    Joined:
    Jul 3, 2017
    Posts:
    3,014
    How do you setup the texture array?
     
  7. cpancake

    cpancake

    Joined:
    Sep 13, 2014
    Posts:
    4
    Code (CSharp):
    1. public static Texture2DArray GetTextureArray()
    2. {
    3.     if(_texArrayDirty || _texArray == null)
    4.     {
    5.         // _nextId starts at zero and is incremented every time we load a tile sprite so it is equivalent to the number of tiles
    6.         _texArray = new Texture2DArray(TA_WIDTH, TA_HEIGHT, _nextId, TextureFormat.ARGB32, true);
    7.  
    8.         // for each tile id we've loaded from disk
    9.         foreach(var id in _tileIds.Values)
    10.         {
    11.             if(_loadedTiles[id].width != TA_WIDTH || _loadedTiles[id].height != TA_HEIGHT)
    12.             {
    13.                 Debug.Log($"invalid sprite size: {_loadedTiles[id].name}");
    14.             }
    15.  
    16.             // add its texture to that layer in the texture array
    17.             var pixels = ((Texture2D)_loadedTiles[id]).GetPixels32();
    18.             _texArray.SetPixels32(pixels, id);
    19.         }
    20.  
    21.         _texArray.Apply();
    22.         _texArrayDirty = false;
    23.     }
    24.  
    25.     return _texArray;
    26. }
     
    Last edited: Jul 5, 2019
  8. aleksandrk

    aleksandrk

    Unity Technologies

    Joined:
    Jul 3, 2017
    Posts:
    3,014
    It looks fine...
    Can you please submit a bugreport?
     
  9. abeacco_unity

    abeacco_unity

    Joined:
    Sep 21, 2018
    Posts:
    4
    Hi, did you ever solve this?
    I am encountering a similar issue.
    I want to draw impostors using instancing: one quad per impostor, accessing a texture inside a texture array.
    When zooming out everything is smooth, but when getting really closer, the sampling of the texture2DArray seems to kill the performance in the fragment shader.
    I understand that zooming in means more fragments executing the fragment shader, but I thought there should be a cache or something that improves performance.
    I am running this on an Oculus Quest by the way, on PC it seems to work fine.
     
  10. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,549
    Zooming in also means higher resolution mip-levels of your texture get streamed in, taking up memory. There could be a bottleneck happening.
    The other thing to keep in mind is if you're rendering with semi-transparency and thus experiencing a lot of over-draw when you get zoomed in compared to zoomed out where each impostor is mostly covering its own unique screen-space instead of overlapping with dozens or hundreds of others. There is an "Overdraw" mode you can set the Unity viewport mode as to visual this.
     
  11. abeacco_unity

    abeacco_unity

    Joined:
    Sep 21, 2018
    Posts:
    4
    Hi,
    Thanks for your answer.
    I don't completely understand the memory bottleneck you are mentiong.
    Could you point me to any reference to better understand this problem? Is it related to texture magnification and how the texture is filtered when doing so?
    In any case, we are not using transparency, but alpha-cut (discarding fragments based on alpha).