Hi James,
I don’t think there’s anything wrong with your setup although it does feel like you have a fairly big landscape for a mobile platform. How many landscape components per proxy do you have? For reference, in Fortnite, we are using 64 landscape proxies overall (with 4 components per proxy) and they are always loaded, while in your case, in a scenario where everything would be loaded, that would translate to 1024 landscape proxies (and N*1024 components depending on how many components per landscape proxy you have), which is quite a lot. Then depending on the number of target layers that are actually used on your components, the texture count can increase even more (on Fortnite, we have usually around 3 to 8 different layers on a given component, rarely more than this).
Texture-wise, here’s how things are laid out :
- Weightmaps are shared between components of the same proxy, up to 4 per texture (R, G, B, A) but their resolution is == to the component’s resolution. That means, if you have, say, 4 components per proxy, and 3 layers ABC on component 0 and 1, and 2 layers AB on component 2 and 3, you have a total of 10 layers on your proxy, which requires 10 texture channels, and so you end up with 3 textures (i.e. 3 * 4 = 12 channels , 2 of which are unused)
- Heightmaps can be shared geographically between neighboring components of the same proxy, up to 16 components, depending on the heightmap resolution, if I’m not mistaken. That means that if you have several components per proxy (and haven’t used the Add/remove component tool, which deactivates heightmap sharing), you might end up with a reduced number of heightmaps. TBH it’s a very annoying “feature” for the tooling part and we are planning to eventually get rid of it (we already don’t use it in the World Partition case), as it complicate things quite a lot and prevents some optimizations. I’m not sure if it’s active in the World Composition case, as it’s a code path that we don’t have the resources to exercise, since all developments are using World Partition now. Anyway, TLDR : at worst, you will have 1 heightmap per component, at best, 1 every N components.
It is not clear to me what you are actually loading at any given time. Are your levels loaded / unloaded dynamically? Like I said, for Fortnite, we’ve decided to have all landscape streaming proxies always loaded, as we want the player to always be able to see the entire landscape and we want collisions to always be present, even in the distance (otherwise, we might have considered HLOD, but even then, the gain in terms of memory is not great or even inexistent, as landscape textures are usually lighter than static meshes with unique textures on them). That isn’t to say that all the landscape data is loaded : texture streaming comes into play and mips are dynamically loaded/unloaded depending on the pixel ratio, just like any other streamable texture.
The fact that you seem to have 7 mips being loaded at a time is a bit suspicious, though, because when loading a far away landscape, only the low-resolution mips should be loaded, typically. Do you see those hitches on landscape textures belonging to proxies that are closed to the player only or do you see them also on faraway landscapes? If the latter, then your terrain textures might be mis-configured and the mip tail (i.e. the number of mips that are not streamed, i.e. always loaded) is too high?
Have a look at the texture group settings on your target platform and, in particular, the following 2 : TEXTUREGROUP_Terrain_Heightmap and TEXTUREGROUP_Terrain_Weightmap. We usually tweak LODBias, MaxLODSize, OptionalMaxLODSize, etc. to control the mips that should be cooke,d always loaded, and eventually, bias the LOD level on certain devices.
I suggest you use the command “listtextures -csv” at runtime to have a thorough analysis of what is loaded and evaluate if you don’t have too many mips loaded for the landscape proxies in the distance, for example.
Also, have a look at the number of layers that are currently in use because even if they are shared up to 4 per texture), that can boost the count quite a bit (especially if you use BP brushes to write those weights procedurally, as those will allocate the corresponding layer on all landscape components, regardess if they’re actually used on a given component or not). In order to validate this, you can use the “Layer Usage” landscape visualizer in the viewport options in the editor, it will show you the actual layer count on every landscape component. If it sounds excessive, try to reduce it, as it will lead to a lesser memory consumption but also a higher performance and simpler materials.
And also, if this is a situation you’re in and something that you can afford, you can try to reduce the number of conponents per proxy. As explained earlier, this number influences the sheer amount of texture objects and there decreasing it can reduce the strain on the streaming system and reduce the amount of draw calls too (there’s 1 draw call per component, no matter what). In terms of streaming “unit”, only the streaming proxy matters, since all components are loaded as part of the proxy, so having 16 components per proxy or a single one will lead to the same result.
Lastly, please consider your level loading situation : if you can afford not loading the faraway levels (e.g. by hiding them with fog or replacing them by HLOD), then it would result in a lesser amount of proxies/components/textures to load entirely, and that could yield a net gain.
Hopefully that gives you enough information to start investigating and try to how to improve your setup. Please don’t hesitate if you have more questions,
Cheers,
Jonathan