Search Unity

Fastest way to display a 200^200 grid of colors

Discussion in 'General Graphics' started by stumpytheangry, Aug 26, 2021.

  1. stumpytheangry

    stumpytheangry

    Joined:
    Jan 13, 2021
    Posts:
    8
    I have a grid of 200 by 200 squares (so 40,000 objects) to display. Each one has one of 9 colours which change frequently.

    grid.png

    My current approach is instantiating all 40,000 objects from a prefab. This prefab contains 9 materials which I can easily switch between.

    This works but is 'barely adequate' and the render times are approaching unacceptable on lower end machines. I'm using URP unlit shaders as I feel this should be the fastest. GPU instancing is enabled and all objects are tagged as static. All other shadows and reflections are disabled.

    Note: the issue is purely with rendering - the game logic and material switching is not issue.

    Alternatives I've tried include creating a pixel-by-pixel texture for a dynamic material that I could apply to a single plane (this was both blurry and slow) and using the UI to draw rects directly on screen (painfully slow).

    I feel there should be a way to utilise a custom shader to make this faster to render but I'm not sure how to approach it. Suggestions welcome.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    Rendering 40,000 objects that are that small is going to be expensive; GPUs are bad at drawing lots of small things.

    You can have objects that are static, or you can have objects that are GPU instanced, you can't have both. I forget which one takes priority if you try to enable both at the same time, but I think static batching overrides instancing. It might be faster if you disable the static setting. But it doesn't get around the first issue.

    This is just as bad as the first option, except Unity is going to be generating a new mesh every frame, so it's way worse, and still expensive because it's drawing lots of small things.

    This is probably the correct solution. It was blurry because textures default to using bilinear filtering. You'll want to set your texture to use point filtering. It was slow because you're using more data than you need. I'm assuming you used an RGBA32, or maybe RGB24 format texture.

    I would use an R8 texture, and then use the value as an index in the shader to choose from an array of colors, or maybe another texture that has the 9 colors as a swatch (a 9x1 texture with each pixel being the color options).


    The other question I would have is ... why are you doing this? Does the CPU need to actually control the color values or is it just random? A compute shader might also be a good option, where you use it to write to a RGBA32 render texture, but only send a list of pixels that need to change each frame (assuming it's considerably fewer than the entire 200x200 grid at once, apart from the initial start). The main goal is to try to limit the amount of data being changed each frame.

    And also avoid rendering lots of 1 pixel triangles.
     
    SugoiDev likes this.
  3. stumpytheangry

    stumpytheangry

    Joined:
    Jan 13, 2021
    Posts:
    8
    Thanks for your reply Bgolus.

    I'll look into the static vs instancing side. I don't feel this is the issue though.

    The game logic is deeply involved in the color - it's not random (otherwise I could probably just have used particles or a random shader).

    The 'changing' side isn't actually an issue at all either - just having them existing on-screen is what's causing the bottlenecks - although I can see why a compute shader would need to address this.

    I do like the idea of a downscaling from RGBA32 (you were right that's what I was using) and maybe using that as a key for a custom shader - that feels very doable and might well be faster.

    I appreciate your thoughts and will check back in in a couple of days to let you know how it went - the only thing I didn't follow from your reply I was the 1pixel triangles thing. Could you elaborate here please?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    Micro triangles are very bad for performance. The smallest number of pixels a GPU can render in a 2x2 group of pixels called a "pixel quad". If a triangle is visible in a single pixel within that pixel quad, you pay the cost of rendering all 4.

    In your example, if you have a grid of 200x200 objects, and you're rendering to a 200x200 render target, it's 16x more expensive to render the pixel color vs a single triangle covering the whole view. That might not sound like a lot if you think about this comparison as being between 40000 quad (2 triangle, 4 vertex) meshes vs a single quad mesh, as that alone is an increase in work of 40000x more vertices, but even lower end GPUs can eat through a massive number of vertices without an issue. The major hardware limits that still exist for GPUs tend to be in memory bandwidth usage, and parallelization / ROP usage. A ROP is an acronym for a bit of hardware with a lot of different names, but the short version is it's the hardware on the GPU that takes the color output from a fragment shader and merges it into the render target. There's a limited number of them, and they can only do so much work, so they're often the bottleneck when it comes to rendering very dense geometry, or very high resolutions.

    Search on google for micro triangles and/or pixel quads and you should be able to find more.
     
    SugoiDev likes this.