Optimize memory usage

Your game needs to load your project's resources into memory in order to render them or work with the data they contain. And every device that can run your game has a hard limit on the amount of memory it makes available. This implies a limit on both the quantity and size of the resources the game can load at any given time. Needless to say, these limits are especially low on mobile devices. However, even on 64-bit PCs and consoles, memory becomes increasingly precious as the scope of your game project increases.

It is critical to ensure that your game never runs out of memory. If your game needs more memory that it can get from the operating system, it is likely to crash immediately and be terminated by the operating system before the engine even has a chance to respond.

Rather than ignore memory usage until your game gets so heavy that it crashes, a more proactive approach establishes budgets and best practices from the beginning of the project, and monitors the actual usage throughout the development cycle. This avoids panic in the final stages before release.

This page presents some tips and techniques for reducing the amount of memory your game needs. These fall into three main categories:

Set budgets or quotas

Before you begin optimizing and minimizing memory usage, it's helpful to have a clear idea of the targets you're trying to reach.

Your overall budget will depend on the specs for the hardware devices that you want to support. From this, you have to subtract the memory used by the operating system. Then, subtract a large percentage to account for memory space used by other apps -- you cannot simply assume that your game is the only app running on the device. Finally, shave off an extra percentage to give yourself the comfort of some extra "headroom". Depending on how safe you want to be from unpredictable crashes, your overall target might be a quarter of the available device memory, or even less.

Once you have an idea of the overall budget for the game as a whole, you may also find it useful to divide the total into smaller budgets for different subsystems or different kinds of resources, such as textures, audio, meshes. The numbers here are likely to vary depending on the kind of game you intend to develop. For example, if the aesthetic of your game involves intentionally low-poly art but complex music and audio cues, you may need to assign a larger budget to your audio resources as a percentage of the overall budget than you do for games that are heavy on visuals. These budgets do not need to be absolute rules and may be subject to negotiation over the course of your project, but if you have multiple team members working on different areas of the project, setting some rough expectations up front can help the teams collaborate more smoothly.

Monitor runtime usage

If you don't have clear data about the real memory usage of your game at runtime, you won't be able to identify where the problems are that you need to solve. Without data, you can easily spend time and effort in the wrong places: for example, aggressively reducing your texture quality even though your problems are really caused by enormous, highly detailed models.

It's also essential to evaluate the memory used in the game at runtime, because the size in memory of a resource is often very different from the size of the resource on disk. For example, image textures in your project folder will often be stored on disk in a compressed .png format that can make the files tiny on disk. However, in runtime memory the texture data are uncompressed and take up much more space than you might expect from their file size.

There are several console commands you can use to get information about the memory usage in your game:

For details on sending commands from the Stingray Editor to a running game, see Send Commands from the Status Bar. For more detail about the commands themselves, see Console commands.

Remove unused resources from the boot package or the project

If your game crashes on startup, particularly when testing on mobile devices with low available memory, the most likely cause is that the boot package is trying to load in too many resources.

If your boot package is using wildcards to decide what resources it should contain, it may be that some of the resources it's loading aren't actually necessary. This is particularly likely if you're using the following line to load all resources of all types that are in the project.

* = ["*"]

If your project has old copies of assets or test levels hanging around that match your wildcards but that aren't needed at runtime, you can save memory by simply deleting these unnecessary resources from the project.

Alternatively, if you want to keep the older copies, you could remove the auto-loading line from the boot package and re-configure the boot package to exclude the unwanted resources.

See also About the boot package and Defining resource packages.

Use multiple resource packages

The most powerful and flexible option that you have for controlling your game's memory usage is to divide your project's resources into multiple separate packages, and to stream these packages in and out of memory dynamically during the game when they are needed.

Any project that needs different sets of resources at different times can usually benefit from this approach. For example, if your game has multiple levels, you could create a separate resource package for the assets used in each level. That way, each level can take up the full memory budget for the game without having to share the available memory with other levels. Even if you only have a single level, you might be able to use resource packages for different isolated parts of the level, or for different characters.

For more detail on how to set up this kind of system, see the topics under Loading and unloading content at runtime.

Note that even if you decide to set up a custom system for streaming resource packages in and out of memory, you can still benefit from applying the other techniques listed below for optimizing the individual resources that you do load.

Optimize textures

Textures are usually by far the largest memory consumers for most games. Of course, they are also a large part of the visual appeal of the game. Therefore, finding a good balance between visual quality and memory size can be tricky. This is especially true if you need to support multiple different platforms with very different screen sizes and available memory. On a device with a large screen, you might need to put more of a priority on image quality. By contrast, on a smaller screen, you can use lower quality or smaller textures without a noticeable difference in the rendering quality.

The following sections provide some tips to help you achieve the best balance of memory and quality for your project.

Reduce texture sizes

The first step in optimizing your textures is to start with an appropriate image size. If you make every texture in your game 4096x4096 pixels, you will quickly run out of memory on any system.

There is no hard and fast rule about what sizes your textures should be. Ultimately you make this decision depending on:

You can also experiment with using different sizes for the different texture maps that you assign to each material. For example, you can often use smaller images for roughness maps and metallic maps than for color maps and normal maps without losing much apparent quality in the way a material appears when rendered.

Note that for streaming compressed textures, the pixel sizes need to be multiples of 4.

Use per-platform compilation settings

You can use the Stingray Texture Manager to control the way Stingray compiles your textures for each different target platform.

For details on using the Texture Manager, see also the topics under Working with Textures.

Take advantage of texture streaming

By default, Stingray treats textures like any other resource. It loads them into memory along with all the other resources in a resource package, keeps them in memory persistently, and unloads them only when you unload the resource package. This means that all the mip levels for each texture are taking up memory in your game even when those mip levels are not actually being used due to the texture being off-screen or very distant.

Instead, you can set up Stingray to stream texture data into memory only when that data is actually needed for rendering. With texture streaming, you can increase the number of textures you use in your game without increasing memory usage, and without decreasing the maximum resolution of those textures.

For details on how this system works, see Texture streaming.

Or, reduce texture streaming memory

The engine reserves 16 MB of memory at startup for use by the texture streaming system. If you choose not to use texture streaming in your game, you can avoid allocating this memory by setting the value of the streaming_buffer_size key to 0 in your project's settings.ini file for each of your target platforms. See Stingray engine settings.ini file reference.

Reduce lightmap texture size

When you bake the lighting for a level, you generate additional textures that your game needs to load. Although baking typically achieves higher quality rendering with lower CPU and GPU usage, this always comes at the cost of higher runtime memory requirements.

If your lightmaps are taking up too much memory, you can try the following ideas:

Optimize models

The more vertices and data channels in each model asset that you add to your project, the more memory it will take to load that mesh into the game. You may be able to save some memory by optimizing your meshes in your modeling tool (i.e. 3ds Max or Maya) to reduce their vertex count and to remove any channels that aren't needed in the game (for example, extra UV sets).

There are no strict rules about what vertex counts you should aim for. Like with texture size, you have to balance memory requirements against the visual quality of the model when rendering in game, which depends on the size of the model, its distance from the camera, and its importance to the gameplay or the scene. Remember that you may be able to treat some models like movie set dressing: if the player will never see the back of a particular building or object, it can be a flat plane with minimal vertices.

Unless your mesh sizes are really excessive to start with, you probably will not see as dramatic a difference in memory consumption as you do when optimizing textures. However, the more different meshes you have in the game at the same time, the more your small optimizations will add up.

Optimize audio

After textures and meshes, the type of resource that typically uses the most memory in your game is audio files.

As with textures and meshes described above, you can reduce memory requirements by trading off sound quality. However, there are many other complementary techniques that you can use in addition, such as streaming audio direct from disk, and tuning the size of the memory pools allocated internally by the Wwise engine.

For a complete discussion, see Optimize audio memory usage.

Optimize physics

The main memory optimization you can make in the physics subsystem is to avoid creating actors and movers for any units that don't require them. Whenever you spawn a unit, some additional space is reserved in memory for its actors and movers.

Compress animation clips

When you select an animation clip in the Asset Browser, the Property Editor offers a set of compression settings that you can apply to that clip when it is compiled. Enabling these compression settings can save you some memory at runtime, at the cost of slight inaccuracies introduced into the animation curves.

You can fine-tune the compression settings for each clip by enabling and disabling compression on the position, rotation and/or scale curves individually, and by adjusting a tolerance value that controls the balance between how aggressively the data is compressed and how large the allowable errors are.

For more details, see also Animation clip properties, Animation compression and Animation optimization.

Optimize Lua environment and scripts

To keep the memory used by the Lua environment down:

See also this blog post on Lua memory issues.

Check for leaks in level design and gameplay

If a level is still taking up too much memory after you've gone through the options above for reducing the memory consumed by the various resource types and sub-systems in your game, check thoroughly to make sure that your level isn't consuming memory unnecessarily or without your knowledge.