Texture and Asset Budgets for Mobile

Updated June 2026
Mobile browser tabs have strict memory limits, typically 150-300 MB on Android and 100-200 MB on iOS. Exceeding the limit causes the browser to kill the tab without warning. Planning and enforcing asset budgets, especially for textures, which consume the most memory, is essential for keeping your mobile web game running reliably.

Memory management in mobile web games is a harder problem than in native games or desktop browser games. Native mobile apps can request more memory from the OS and receive warnings before being killed. Desktop browsers give each tab gigabytes of headroom. Mobile browsers give each tab a modest allocation and terminate it silently when that allocation is exceeded. There is no warning dialog, no error event, and no graceful degradation. The player sees a blank tab or a "page crashed" message. Building a reliable mobile web game means keeping every byte of memory accounted for.

Establish Your Total Memory Budget

Start by determining the maximum memory your game should consume. This depends on the devices you target. On Android, Chrome tabs on mid-range devices typically tolerate 200-300 MB before the OS starts applying pressure. On iOS, Safari is more conservative, with tabs on devices with 3-4 GB of RAM often limited to 100-150 MB. For broad mobile compatibility, design to a ceiling of 150 MB total memory usage.

Divide this budget across your major asset categories. A reasonable starting allocation for a 150 MB budget might be:

  • GPU textures: 70-80 MB (the largest category by far)
  • Audio buffers: 15-20 MB
  • JavaScript heap: 20-25 MB (game logic, framework, entity data)
  • WebAssembly linear memory: 10-20 MB (if using Wasm for physics or core logic)
  • Overhead: 10-15 MB (DOM, browser internals, WebGL context state)

Write these numbers down and treat them as hard constraints. When an artist wants to add a 2048x2048 texture, the question is not "does it look good" but "does it fit within the 80 MB texture budget." If adding the texture pushes the total past the budget, something else must be removed or compressed further.

Audit Current Asset Memory Usage

Before you can manage your budget, you need to know what you are currently spending. Open Chrome DevTools connected to your game running on a mobile device (or in desktop Chrome as a first approximation) and check the Memory panel. Take a heap snapshot to see JavaScript memory usage broken down by object type. The Performance Monitor panel shows real-time JS heap size and DOM node count.

For GPU textures, the browser does not expose GPU memory usage directly, but you can calculate it from your asset manifest. The formula for a single uncompressed RGBA8 texture is: width * height * 4 bytes. With mipmaps, multiply by 1.33. For ASTC 4x4 compressed textures, use width * height * 1 byte (8 bits per pixel). For ASTC 8x8, use width * height * 0.5 bytes (4 bits per pixel). Sum across all loaded textures to get total GPU texture memory.

Build a spreadsheet or script that reads your asset manifest and calculates total memory per category. Run this as part of your build pipeline so that every build produces a memory report. Set up CI warnings that fire when total memory exceeds 80% of your budget, giving you buffer before hitting the hard limit.

Choose Texture Dimensions and Compression

Texture dimensions are the single largest lever for controlling GPU memory. A 2048x2048 RGBA8 texture uses 16 MB. A 1024x1024 version of the same texture uses 4 MB, a 4x reduction. A 512x512 version uses 1 MB. On a 6-inch phone screen held at arm's length, the visual difference between 1024x1024 and 2048x2048 is often imperceptible for environment textures, terrain, and backgrounds.

Apply this sizing rule: use the smallest dimension that looks acceptable on your target screen size. For sprites and UI elements viewed at fixed on-screen size, the texture dimension should match the on-screen pixel count, not exceed it. A button that displays at 120x40 CSS pixels on a 2x DPI screen needs a 240x80 pixel texture at most. Putting that button in a 512x256 atlas slot wastes 97% of the pixels.

After right-sizing, compress everything. ASTC is the preferred format for mobile, supported on all modern GPUs. Use ASTC 4x4 (1 byte per pixel) for textures that need high fidelity, like character art, icons, and detailed UI elements. Use ASTC 6x6 or 8x8 (0.56 or 0.5 bytes per pixel) for textures where softness is acceptable, like terrain, sky gradients, noise patterns, and normal maps. ETC2 is the fallback for devices that lack ASTC support, offering 0.5-1 byte per pixel depending on the format.

A practical example: a game with 40 textures averaging 1024x1024 uses 640 MB uncompressed (40 * 16 MB). With ASTC 4x4, the same set uses 40 MB (40 * 1 MB). With ASTC 6x6, it drops to about 22 MB. The visual difference between uncompressed and ASTC 4x4 is difficult to spot on a phone screen, but the memory difference is the difference between a crashing game and a stable one.

Generate and Use Mipmaps Strategically

Mipmaps are pre-computed lower-resolution versions of a texture (half-size, quarter-size, eighth-size, and so on). When the GPU samples a texture at a distance, it reads from the appropriately sized mip level instead of the full-resolution texture. This improves both visual quality (reducing aliasing) and performance (reducing bandwidth by reading smaller textures).

Generate mipmaps for 3D scene textures that will be viewed at varying distances: terrain, environment objects, character textures, and anything in the 3D world that the camera can move toward or away from. The mipmap chain adds roughly 33% to the base texture's memory, which is a worthwhile tradeoff for the bandwidth savings and quality improvement.

Skip mipmaps for textures that are always displayed at their native resolution. UI elements, HUD graphics, 2D sprites in a non-zooming camera, and screen-space overlay textures do not benefit from mipmaps because they are always sampled at the same size. Skipping mipmaps for these textures saves the 33% overhead. In WebGL, set gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) instead of gl.LINEAR_MIPMAP_LINEAR for non-mipmapped textures.

Compress Audio Aggressively

Audio is the second largest memory consumer in many games. A 3-minute music track stored as uncompressed PCM at 44.1 kHz stereo uses about 30 MB. The same track encoded with Opus at 96 kbps uses about 2 MB, and the quality difference through phone speakers or typical earbuds is negligible for game music and effects.

Use the Web Audio API's decodeAudioData() to decompress audio on demand. The compressed file is fetched over the network (small download size), then decoded into an AudioBuffer in memory. The decoded buffer is uncompressed in memory, so keep track of total decoded audio. For short sound effects (under 5 seconds), pre-decode them at load time and keep them in memory. For longer music tracks, stream them using an Audio element with MediaElementAudioSourceNode instead of decoding the entire file into memory at once.

Target 15-25 MB total for all decoded audio in memory at any given time. This means keeping about 10-15 short sound effects (~1-3 seconds each, ~0.5-1.5 MB decoded) and streaming music rather than preloading it. When changing game scenes or levels, unload sound effects that are no longer needed and load new ones for the upcoming scene.

Choose Opus for the best compression-to-quality ratio. Opus at 64 kbps sounds better than AAC at 128 kbps for most game audio. If you need iOS compatibility without the Web Audio API (rare but possible in some older WebView contexts), AAC is the fallback.

Implement Asset Loading and Unloading

Loading every game asset at startup is the simplest approach but the most memory-wasteful. If your game has 10 levels and each level uses 40 MB of unique textures, loading all levels at once requires 400 MB, which is impossible on mobile. Instead, load only the assets needed for the current level or scene, and unload assets from previous scenes when they are no longer needed.

Design your asset system around the concept of asset groups or bundles. Each level or scene defines which asset bundles it needs. When transitioning between scenes, the asset loader compares the current set of loaded bundles to the incoming scene's requirements. Bundles shared between scenes stay in memory. Bundles no longer needed are unloaded by deleting their WebGL textures (gl.deleteTexture()), dropping references to decoded audio buffers, and releasing any associated JavaScript objects. Bundles needed by the new scene but not currently loaded are fetched and decoded.

For textures, calling gl.deleteTexture(handle) releases the GPU memory immediately. For audio, setting the AudioBuffer reference to null and allowing garbage collection to reclaim it releases JavaScript heap memory. For WebAssembly modules, you may need to explicitly free allocated memory within the Wasm linear memory if your allocator does not handle it automatically.

Show a loading screen or progress indicator during scene transitions so the player understands that assets are being loaded. Preload the next scene's assets in the background during gameplay if your memory budget allows holding two scenes' worth of assets simultaneously, which eliminates the loading screen at the cost of higher peak memory.

Monitor Memory at Runtime

Budgets only work if you enforce them continuously, not just during development. Add runtime memory monitoring to your game that tracks both JavaScript heap usage and estimated GPU texture memory.

For JavaScript heap, use performance.memory (available in Chrome) to read usedJSHeapSize and jsHeapSizeLimit. Note that this API is not available in all browsers, so wrap it in a feature check. For GPU texture memory, maintain a running total in your asset manager: every time you create a texture, add its calculated size to the total; every time you delete one, subtract it.

Log memory usage to your debug overlay and set up warning thresholds. When total estimated memory exceeds 70% of your budget, log a warning. When it exceeds 85%, consider automatic mitigation: downscale textures to the next smaller mip level, reduce audio quality, or unload non-essential assets. When it exceeds 95%, you are in danger of tab termination and should aggressively shed any disposable assets.

During development, run your game through its full content on the lowest-tier device in your support matrix and log peak memory throughout. If peak memory at any point exceeds your budget, address it before shipping. Memory issues found by players result in silent tab crashes with no error report, making them extremely difficult to diagnose after the fact.

Key Takeaway

Set a hard memory budget based on your lowest-tier target device (150 MB is a safe starting point), compress all textures with ASTC, right-size texture dimensions for phone screens, stream long audio instead of preloading it, and load assets per scene rather than all at startup. Monitor memory at runtime so you catch overruns before players do.