MegaTextures

Dev Blog #8 by George Baron

19 November 2019

I’ve lost a bit of momentum on development recently due to lost weekends and my sleep pattern shifting. It has caused me to suffer slightly from burn out and I have not yet got back into the swing of things. However I am still pushing ahead and this week I will talk about a technique I am currently implementing to help improve performance.

What is a MegaTexture?

The information on this technique online is a bit scattered. The name “MegaTexture” seems to have been coined by John Carmack at id software, but the actual technique has apparently been in use for quite some time. I am personally using this as my guide on the implementation in my own engine.

The main idea of using a MegaTexture is, instead of having lots of separate textures in GPU memory for each of the objects in a scene, you have one BIG texture that is a texture atlas of those smaller textures. It differs slightly from a normal texture atlas in the following ways:

  • A MegaTexture is dynamic. Textures can be loaded in (or “streamed”) while gameplay is happening and inserted into the MegaTexture. Games like Rage that have a large amount of textures and a large open world stream textures into the MegaTexture depending on what is currently visible to the player.
  • A MegaTexture has multiple levels of detail. These are called mipmaps in computer graphics and mean that if a texture is far away from the camera, a smaller version of the texture can be substituted in. This both saves the number of pixels that have to be rendered in the texture which improves performance, but also prevents aliasing.
Left shows a scene without mipmapping and right shows a scene with mipmapping. Notice the aliasing and moire pattern in the left image. Credit to Wikipedia.

Why use a MegaTexture?

This may now raise the question as to why you would want to use a MegaTexture over using separate textures in memory. I mentioned in the introduction that I am doing this to improve performance, but that doesn’t particularly explain why. To do so, I will explain how textures are currently loaded in and rendered in my engine.

The engine currently works by using separate textures. When I call a scene to load, textures are read into a bitmap on the CPU and passed through to texture memory on the GPU. To use this texture, I need to bind it to a texture unit that objects can then reference. Objects that use the same texture are all drawn at the same time, which means I only have to bind the texture once for all of the objects to use it.

The performance issue to take note of here is that binding a texture is expensive. I already made the performance improvement to draw similar objects together, which means I only have to bind a texture for every group of similar objects rather than every single object. If I instead use a MegaTexture for a scene, I can instead bind a texture once for the entire scene. This means I can have lots of unique textures for objects and it won’t make any difference to performance.

My Implementation

For my implementation I am going to be ditching the “streaming” aspect of MegaTextures. This is because the game is, on the whole, made up of different rooms separated by loading screens. A MegaTexture is typically 32768x32768px in size, which I am going to predict is more than enough room for my needs.

I am going to investigate dynamically resizing the MegaTexture depending on the scene however. If I can use a 16384x16384px texture or 8192x8192px texture, then I will. This means I will have to recreate the MegaTexture on scene load (unlike something like Rage that will just stream new textures in) but I’m not so fussed about performance hits during a loading screen. Using a smaller texture means I will be reserving less memory on the GPU which will drastically improve performance on GPUs with less texture memory available to them.

Let’s not forget my game looks like a PS1 game. If I required a higher-end GPU to play it I feel like it would not go down too well.

Next Week

That’s it for this week! I am part way through actually implementing this and it will be done in time for the next blog post. I should be able to show some nice images of it working!