Object Paging Polishing
Object Paging is brilliant, but it could be better. Some of the limitations haven't ever made it to our tracker, and that's meant a lot of partial solutions have been proposed, with no one person really being aware of the full picture. This Epic will let us discuss what the limitations are perceived to be, whether they're actually real, and then work on designing solutions, ideally, but not necessarily, without any compromises whatsoever.
## Selection of objects
Currently, this is done based on a semi-good visibility heuristic which requires whole Nifs to be loaded, and sometimes wildly underestimates silhouette impact, choosing to skip wall planes that make up key buildings. Obviously, we'd like to be able to skip loading Nifs that are too small.
### How can we avoid loading whole Nifs?
Proposals so far have included:
* Assuming it won't be a problem. This has turned out to be insufficient.
* Modifying the Nif loader to skip over things we don't want. This is believed to likely be impossible to do in a meaningful way, but maybe if it could be done, it would be enough.
* Caching the visibility heuristic so once the engine's calculated it for a Nif once, it never needs to do it again.
* A flag in the content files like `VWD` that labels things as visually important.
* Checking for the existence of LOD (as no one would bother making LOD for things that wouldn't be visible from a long way away).
* Letting it be precomputed, e.g. via a button in the launcher a user can do for their mod setup, or a button in the CS a mod author can do for their mod, and then only computing it at runtime when no precomputed copy exists.
### How can we detect how visible things are?
Clearly, this can be computed, as humans are easily able to do it.
The only reliable heuristic that's been suggested so far is to do something equivalent to rendering the mesh from a bunch of angles and counting the number of pixels it touched. This is roughly related to octahedral impostors. We might be able to get away with a very low-resolution framebuffer, e.g. 4x4 or 8x8, and only a few angles, so it would be relatively cheap, but we'd still need to load the whole model and render it, so it's unlikely to be suitable for runtime. In the extreme case of really high resolution and lots of angles, this is just looking at the mesh from every angle, which would obviously give the right answer.
## Too much geometry
When object paging was going through its first burst of work, people kind of assumed that all Morrowind assets were so low-poly as to not need LOD. In its second burst, proof was obtained of modded assets benefitting from LOD. I'm under the impression we've also got some evidence suggesting that even vanilla stuff benefits from LOD, although evidence is muddied a bit by other performance limitations.
When Nifs provide NiLODNodes, then we load those, and things work out okay (the other issues here notwithstanding). They haven't been widely adopted by mod authors, and none exist in the base game data. Needing to load the whole Nif to get the LOD makes this slower than it inherently needs to be.
It's also been proposed that we load `<thing>_dist.nif` as LOD for `<thing>.nif`, which would be backwards-compatible with existing content for MGE XE. At one point, the MGE XE compatibility wasn't seen as an advantage at all. There're also the disadvantages that it's easy to accidentally override part of a mod and end up with LOD that doesn't match the base mesh (unless we tell the VFS to handle things it really shouldn't know about), that the data Object Paging renders isn't a necessarily a tweaked version of the canonical scene graph, and that LOD intended for MGE XE without any particular guidelines *may* not be appropriate for a machine in <current year>.
Another proposal was autogenerated LOD, either at load time or precomputed, with the former being slow (although a cache could be introduced), and the latter meaning bad UX unless we could shift the work to modders.
## Loading of relevant LOD levels
We believe a lot of time is wasted loading things from Nifs other than the LOD levels we're interested in.
It was initially proposed that the Nif loader could be made aware of what we wanted from it, and skip everything else, but the structure of Nifs is likely not amenable to this as (AFAIK) we can't do things like seek to a specific node. Maybe this, too, is something resolvable by keeping a cache with offsets into Nifs
An alternative proposal is a tweaked file format based on Nif, that's potentially also an archive format, which would add the data we needed to grab specific LOD levels (and other useful data like visibility) to the header. If it's an archive, this is similar to how Fallout 4 texture BA2s work, and how the archives for modern Serious Game Engines work.
## Moving distant things
Unless a script makes a significant change that knocks something out of Object Paging, or you use fancy UV tricks, nothing in the distance can move at all.
## Depth-only versions of chunks, e.g. for shadow maps
There's a lot of state that's unnecessary if you only want a correct depth buffer value. Eliminating it presents more opportunities for batching. This could be particularly helpful in the active grid.
## Potential optimisations we're not applying
There are things Object Paging's optimiser doesn't do which it potentially could do, and might improve performance:
### Multi-draw indirect
If we've got a lot of dissimilar geometry with compatible state, it may be beneficial to use multi-draw indirect to have a compute shader do frustum (and maybe occlusion) culling, so CPU-side, we submit massive (indirect) draw calls of merged geometry, then the GPU decides which segments of the buffers need to actually be drawn. This also could let us get away with putting a single copy of geometry used several times, but not enough for instancing, into the buffer as the compute shader could submit it multiple times, whereas with static batching, we'd need a copy for each occurrence.
### Instancing
Currently, we only use instancing for groundcover. If there are other objects that get placed a bunch of times in close proximity, they could benefit more from instancing than static batching, too.
## Just load things faster
If we just loaded things faster, then loading lots of files would be less of a problem. It would be good to look into optimisations here. Some things to think about would be:
* Just generally making the Nif loader faster.
* Just generally making the other parts of loading faster.
* Reuse file handles for different files in the same archive. Especially on Windows, creating a new file handle is slow. For things in BSAs, we're likely to load several files from them, so it's wasteful to create a fresh handle instead of seeking the existing one to the place we're interested in. This is actually something Morrowind and all the later BGS games do, and the people who measure performance with loading times for those games (e.g. MO2 and Kortex teams who need to assess the impact of their VFS implementations) claim it has a measurable impact. This might mean having a handle per BSA per thread, or having a pool of handles per BSA that multiple threads share, maybe with eviction if they've not been used for a second.
* BA2-style texture streaming would mean we could load the small mipmaps on their own really quickly, and then maybe also load the bigger mipmaps over several frames to reduce perceived latency. Dedicated issue https://gitlab.com/OpenMW/openmw/-/issues/7384
epic