Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question How to update a small texture region without uploading the entire data to GPU?

Discussion in 'Scripting' started by NightElfik, Mar 2, 2023.

  1. NightElfik

    NightElfik

    Joined:
    Oct 27, 2014
    Posts:
    27
    I have a large texture, say 8k, and I need to update a small region, say 64x64 px. What is the best way of doing that to avoid CPU => GPU copy of the entire texture?

    The only solution I could think of is to allocate a small texture of the size of the updated region, put new data there, call Apply to copy this small texture to GPU, and then use Graphics.CopyTexture to perform GPU => GPU copy into a region of the big texture. This seems quite complex and I need to store so many 64x64 textures to perform updates of all regions on the 8k texture, wasting a lot of GPU memory.

    Is there a better way?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,514
    The slowest way will be to do GetPixel / SetPixel, that's for sure. :)

    One way might be to make a little 64x64 texture, blit (or copytex) the big texture to it, modify the little texture, then blit it back.

    Alternately you could use RenderTexture and just render one texture onto the other with some geometry.

    I can't say how all of that would perform, but it should always be better than SetPixel!

    I used the latter (RenderTexture) to do my lotto scratcher demo. I set it all up and then just generate geometry and render to it normally. I put a reference to the stage camera in the script but it doesn't interoperate with it, just so you can select it and see what geometry is blasting through it each frame, just a single frame worth of scratching.

    Full source and package linked in comments:

    https://forum.unity.com/threads/how-to-make-scracthcard-2d.1128605/#post-7253498

     
  3. NightElfik

    NightElfik

    Joined:
    Oct 27, 2014
    Posts:
    27
    Thanks for the suggestions! Rendering into the texture is an interesting idea, but it seems more complicated way of doing Graphics.CopyTexture.

    The SetPixels is actually not that bad if it could set the GPU data directly. It has an overload for setting a region, but after setting a region one still have to call Apply that will copy the entire texture data, which is not acceptable.

    Regarding the smaller textures, I am actually using GetRawTextureData() and filling the native array, that should be slightly faster than SetPixels since there is no need for array copy. So the flow is smallTexture.GetRawTextureData() => fill data => smallTexture.Apply() => Graphics.CopyTexture (small texture to a region of the large one). The downside is that I need to keep many small textures with duplicated data (to cover the entire large texture).

    It is possible to avoid keeping many small textures and have just one temp texture that reads data from the big texture before each operation (as you suggested), however I am not sure how to copy the data to CPU buffers if the source texture does not have CPU-side data. Graphics.CopyTexture docs say:

    If both source and destination textures are marked as "readable" (i.e. copy of data exists in system memory for reading/writing on the CPU), these functions copy it as well.

    This implies that if source texture does not have CPU data (in my case the big texture), I won't be able to access them after the copy into smaller texture is done. Enabling CPU data on the big texture would introduce a CPU copy of the entire data, completely defeating the purpose of saving memory of the smaller textures.