godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.29k stars 21.05k forks source link

Tilemap - unexpected high memory usage per Tile #40578

Open zzz-assault opened 4 years ago

zzz-assault commented 4 years ago

Godot version:

OS/device including version:

Issue description:

In case of big Tilemaps the memory usage (static) is getting unexpected high (i. e. 2k x 2k = 700 MiB). This is a problem especially for big maps based on procedural generation.

It seems that the memory allocation per Tile is at ~180 Bytes.

Based on the Tilemap code, I'm not sure where this overhead is coming from, as the cell data should only allocate the following structure.

struct {
    int32_t id : 24;
    bool flip_h : 1;
    bool flip_v : 1;
    bool transpose : 1;
    int16_t autotile_coord_x : 16;
    int16_t autotile_coord_y : 16;
};

I assume also the PosKey should be relevant per Tile.

struct {
    int16_t x;
    int16_t y;
};

So basically it should only require uint32_t + uint64_t = 12 Byte ... I totally understand that the Tilemap itself has some overhead and also the Quadrants need some memory allocation, but it seems like there is something wrong.

Steps to reproduce:

Just create a new 2D project and add a Tilemap + Tileset and fill it with a double loop for 2k x 2k or use the attached project.

Minimal reproduction project:

Tilemap_MEM-per-Tile.zip

Zylann commented 4 years ago

The struct you mentionned is only what makes up the cell information of the tiles. There is memory used to also render them (canvas items, geometry), and if you added collision shapes they also use instance memory. And more if you added navigation. Add to this the data structure used to hold the chunks. AFAIK these aren't on-the-fly approaches (in which case it would not have extra retained memory and used the same cell data). On-the-fly is bliss for large grid-based terrains (2D or 3D). Streaming it is also an option rather than having everything loaded at once, though it comes with its caveats too.

zzz-assault commented 4 years ago

@Zylann in my example are no collision shapes or nav shapes used. As said I understand a little bit overhead, but really that much?

I'll test later how the memory usage will change without chunk system (quadrants) and without adding it to scene (should remove the canvas item overhead as no draw is needed).

Geometry in Tilemap is the cell size, which is one value for the whole Tilemap, do you really think this should have an per Tile effect? Even if so, this value is stored in the Tileset, which is only one value per different Tile (in my example 1 Tile with autotile).

zzz-assault commented 4 years ago

Update:

If not added to scene tree (add_child) the memory usage is as follows ...

Total MEM (static) = 325 MiB per Tile MEM (static) = 85 Byte

This should represent basically the memory requirement without the engine overhead (for draw, etc.). Besides that I'm wondering, why the Tree / Draw needs almost 100 Byte memory PER TILE, the remaining overhead must be the chunk system ... not sure if I'm able to test this more deeply without quadrants, as they are integrated very fragmented in the whole Tilemap code.

Zylann commented 4 years ago

Geometry in Tilemap is the cell size

It's beyond Tilemap. Stuff happens in VisualServer. I mean quads. Quads have 4 vertices, each with a 2d position and uv, probably more. If not that I still highly suspect some memory is needed per tile in there due tu using a retained-mode design and a generic system (with some stuff being unused per tile), cuz AFAIK VisualServer has no optimized concept of a "tilemap".

zzz-assault commented 4 years ago

@Zylann Understood, I'm not that deeply into the engine yet, but isn't that the reason for the quadrant system? (instead of one quad / draw per Tile, it's setting one quad / draw per batch of Tiles -> based on Quadrant size)

Zylann commented 4 years ago

Besides if each tile uses a separate texture they can't be batched, but that's only a guess.