Texture caching / Texture wrapper

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Hurricane

I’m using Godot 4.0 rc 1 for a small project. (With C#)

I’m drawing a lot of UI components. Essentially a few custom lists.
The lists are huge, thousands of items each (for a total of tens of thousands).
Each item has a texture of fixed dimension (128x96 at the moment)

I’ve quickly opted to draw them myself which solves most issues but one problem remains.
I can’t reasonably keep a hundred thousand of textures in memory, obviously.

My first idea was to create a cache as a huge texture (ImageTexture) and overwrite parts of it as needed. Drawing, I would use parts of the texture.
That would have been very easy.
Alas, as far as I’ve seen, there is no way to tell to only overwrite a part of the texture (if I’m wrong, please tell me.)

My second idea was to create a cache with a maximum amount of textures (Dictionary, MRU, all that jazz). But of course, I need to keep track of what textures are being used.
If I restrict myself to one use case, it’s fine. But I’d rather have a generic case.
For that purpose I was thinking making a wrapper inheriting, say, from Texture2D and either hook calls to a contained texture, either hook calls to a placeholder. Textures being released from the cache would thus be easily cleaned by the cache.
But here I’ve hit another snag : I don’t think I can do that in C# as it’s only a wrapper over the real classes.
I feel like such a tool/feature should already exist and that I’ve just not found it (yet).

Is there such a wrapper?
Is there a better mechanism for this?

Thanks in advance.

:bust_in_silhouette: Reply From: Wakatta

If you would allow me to bore you with some math.

ImageTexture of 2048 width / 128 = 16
ImageTexture of 1920 height / 96 = 20
20 * 16 = 320
10,000 / 320 = 31.25 Images

a class to as you put it

create huge texture (ImageTexture) and overwrite parts of it as needed

using Godot;

public class Example : Node
{
    private ImageTexture _texture;

    public override void _Ready()
    {
        // Load the one of the 31 textures of 2048x1920
        _texture = Load("path/to/image");

        // Instead of color here you can create 3 for loops
        // two for the x and y dimentions of the image to create the rect2
        // and one to loop the pixel data of another image or something
        // since you have not specified how you intend to "draw those images"
        var color = new Color(1, 0, 0);
        UpdateTexture(Rect2(0, 0, 128, 96), color);
    }

    private void UpdateTexture(Rect2 rect, Color color)
    {
        // Lock the image for writing
        var image = _texture.GetData();
        image.Lock();

        // Loop through the pixels in the given rect and set them to the specified color
        for (int y = (int)rect.Position.y; y < rect.Position.y + rect.Size.y; y++)
        {
            for (int x = (int)rect.Position.x; x < rect.Position.x + rect.Size.x; x++)
            {
                image.SetPixel(x, y, color);
            }
        }

        // Unlock the image to apply the changes
        image.Unlock();
        _texture.SetData(image);
    }
}

It is highly unlikely you will need all those images loaded at all times so you can always nullify the visually unused ones and re load when needed

The for loop above can look like this

for (int x = 0; x < 16 - 1; x++)  # _texture.GetWidth() / 128 - 1
{
    for (int y = 0; y < 20 - 1; y++) # _texture.GetHeight() / 96 - 1
    {
        var rect = new Rect2(x * 128, y * 96, 128, 96)
     }
 }

Wakatta | 2023-03-04 14:15

Math never bore me :wink:

As I said in my question, using an atlas was my first idea.
However, with the tools at my disposition, it’s not very efficient as there is no way to only update a part of a texture.

As demonstrated in your example :

  • you load the image from the texture,
  • you update a part of the image
    (I hope there is a way to directly write an image into an image instead of doing it pixel by pixel)
  • you then reload the whole image into the texture

In the end I’ve opted for a set of textures overwritten with an update. The scene will have to make more bindings per frame but on the memory bandwidth front there should be no overhead. (Thankfully in Godot 4 we have _texture.Update(image) to avoid reallocating memory.)

Hurricane | 2023-03-06 12:04

hope there is a way to directly write an image into an image instead of doing it pixel by pixel

There is actually it is called BlitRect in the image class

Wakatta | 2023-03-06 12:42