I have few thousands of buildings (imported through Cesium Ion from a CityGML file) that are using the same texture.
When I fly over my level, it get very slow and I am getting warning messages like
Texture Streaming Pool Over or
Video memory has been exhausted ; Expect extremely poor performances.
Even if all buildings are using the same texture.
Is it possible that this texture is duplicated in memory for every single 3dtile or building, or maybe every single building face?
I have no problem flying over hundreds of thousands buildings with no face texture.
We currently do not have a system to re-use textures that are being used by multiple tiles at once. We have an issue for adding this feature but we are not positive when we will be able to get around to it: Cache and reuse render resources for external images · Issue #497 · CesiumGS/cesium-native · GitHub.
I believe CesiumJS has this feature already though, in case it would work better for your use case in the meanwhile.
Thank you for your answer. May I ask what is the technical problem behind?
That’s sad because I made a try with a few thousands of buildings, they are using only a dozen of textures but this make things extremely slow. And I would like to have hundreds of thousands of buildings, which is working perfectly fine without any texture.
It might be worth considering that for properly created tilesets, textures should not be making performance super slow - even with redundant textures. 3D Tiles is meant to take advantage of hierarchical levels-of-detail to only keep a reasonable amount of textures / geometry in GPU memory at any given time. So thousands of buildings with redundant textures should work fine so long as there are lower detail levels for those buildings when viewed from far away. Only the nearby buildings need to be high-detail. My suspicion is that your tileset is built without proper levels of detail, so it is trying to load all the buildings at their full resolution.
Regarding the actual texture re-use feature that you asked about:
There is no outright blocker to implementing such a system, but we don’t know exactly when we’ll get around to it. In the meantime we are always open to community contributions implementing such features. On a technical level, it will be a bit tricky to refactor the loading pipeline in Cesium Native to incorporate a client (Cesium for Unreal) resource table. Different tiles don’t “know” about each other, so it’s hard for them to determine whether another tile has already loaded the same external image. We can tackle this problem by introducing a new caching layer (sort of).
Technical detail you may or may not be interested in:
Currently, resource request urls (e.g., external image urls) are looked-up in the cache before making a network request - this means when there are multiple tiles using the same image, they should currently only be making a single network request and later reusing the response data from the cache. We could go further by adding a “resource table lookup” before checking the cache, which checks if the image is actually already loaded and resident in GPU memory. If so, we can give the new tile a shared reference to the already loaded image resource. When there are no tiles referencing the shared image, the reference count should go to 0 and it should be removed from the resource table and evicted from GPU memory.
There are a lot of implementation details to sort out with this though. Resource ownership has to be decoupled from the lifetime of individual tiles. We need to restructure the image representations in Cesium Native to accommodate the fact that they can be external and “shared” (currently we assume they are embedded in the gltf after loading). There might be tricky corner cases like tiles referencing the same image but disagreeing on sampler filter modes, wrapping, etc, it might be unclear what the shared image should look like in such a situation. We are currently experimenting with some cache-related refactoring which might prove useful for this sort of feature.
(Forgive me for the technical dump here, mostly just wanted to write down my thoughts so I could reference it in the github issue, since this discussion got me thinking about the details!)
Wow thank you @Nithin_Pranesh for sharing these very interesting implementation details. Here are more details on my experimentations.
- I use Cesium Ion to generate 3dtiles, so LOD should be properly generated.
- With no buildings, all is running fine at max FPS.
- With 500k untextured buildings, all is running fine at max FPS.
- Here is a video capture of an experimentation with terrain + ocean + 58k buildings (3060 TI, 64 GB RAM). With textured buildings (which are just basic cubes using a pool of ~10 different low quality facade textures), as soon as the game is launched (in editor), I got the “texture streaming pool over budget” warning. The ocean disappear, and each camera move show a temporary drop in FPS from 60-80 to 5-10 as 3dtiles load/unload.
- 58k buildings (Ion ID #1382946 if you want to take a look) is just a sample as ideally I would like to render 500k buildings for my entire terrain (+ vegetation, roads, gaming logic, …).
- I use a
Maximum Screen Space Error of 32-48px.
- I use
r.Streaming.PoolSize=2000 (same with 4000 MB)
So from what I can see, I have two issues with buildings: 1. GPU memory usage and 2. drastic FPS drop on tiles load/unload
I really hope that I did not yet reach the limits of Cesium for Unreal but as your ambition is to simulate the whole planet, I am pretty confident that there is some room for improvements for my small terrain of 60x60km.
I appreciate your help very much!
I just start diving into the Cesium C++ code to see if I can prototype such a cache because I really need this. I think I can do something quick&dirty for my usecase.
As textures are embedded in glTF, optional
ImageSpec.uri is not provided. What do you have in mind to use as the unique cache key?
There might be tricky corner cases like tiles referencing the same image but disagreeing on sampler filter modes, wrapping, etc, it might be unclear what the shared image should look like in such a situation.
Maybe this can be encoded into the cache key also.