Course outline
Texture Memory
In one line
A texture costs width × height × 4 bytes on the GPU, plus a third for mipmaps, whatever its file size. renderer.info.memory.textures counts them, and dispose() frees them.
Think of it like this
A folded map in your pocket is thin. Unfolded on a table it is huge. A JPEG is the folded map; the GPU needs it unfolded, every pixel spelled out, so its size depends on the pixel count and not on how well the file folded.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- GPU memory
- Memory the graphics chip reads from. On phones it is shared with everything else the device runs.
- MiB
- Mebibyte: 1,048,576 bytes. A 1024 × 1024 texture with 4 bytes per texel is exactly 4 MiB without mipmaps.
- compressed texture
- A format the GPU reads directly without unpacking, so it stays small in GPU memory. KTX2 files carry one.
- dispose()
- The method that deletes an object's GPU resources. It does not delete anything in JavaScript.
Step by step
01Compute before you load
For each texture: width × height × 4, times 4/3 with mipmaps. Add them up for the whole scene. For the front wheel: two 1024 textures are 10.7 MiB.
02Read the real numbers
The first example fetches each file's size and the picture's pixel size. The two columns show why the file size misleads: 343 KiB on disk is still 5.3 MiB on the GPU.
03Watch the count
The second example logs renderer.info.memory.textures before the first render (0), after it (6), after disposing three (3). The status bar of the live example shows the same count.
04Dispose what you drop
When a colour change replaces a texture, call old.dispose() after the swap. When a whole object is removed, dispose its geometry, materials and textures (Topic 17.4).
Try it yourself
- 1
Compute a worse case
In the first example add
[8192, 8192]to the list of sizes. The line reads256.00 MiB without mipmaps | 341.33 MiB with. One such texture is more than many phones allow a whole tab. - 2
Leak on purpose
In the second example comment out the
card.material.map.dispose();line in the loop. The count after removing three cards stays at 6: removing a mesh from the scene does not free its textures.
Code & diagrams
Try it: interactive
UV viewer: the memory line
Read the GPU memory line while you switch mipmaps on and off. It is width × height × 4 bytes, times 4/3 with mipmaps, whatever the file size.
Real file sizes and pixel sizes of the course's textures, then the formula for common sizes. The last line is 300 × 200: odd sizes work, and cost what the formula says. A mipmap chain is about one third more (the exact value for odd sizes differs slightly).
Console
Run the scene to see its console.Expected console output
file on disk on the GPU
metal_plate_diff_1k.jpg 676 KiB 5.3 MiB
metal_plate_nor_gl_1k.jpg 752 KiB 5.3 MiB
rubber_tiles_diff_1k.jpg 343 KiB 5.3 MiB
concrete_floor_02_diff_1k.jpg 758 KiB 5.3 MiB
512 x 512 1.00 MiB without mipmaps | 1.33 MiB with
1024 x 1024 4.00 MiB without mipmaps | 5.33 MiB with
2048 x 2048 16.00 MiB without mipmaps | 21.33 MiB with
4096 x 4096 64.00 MiB without mipmaps | 85.33 MiB with
300 x 200 0.23 MiB without mipmaps | 0.31 MiB withMeshBasicMaterial is used so the count starts at 0 (a Standard material adds one lookup table). The last two lines: a texture disposed while still in the scene comes back at the next render.
Console
Run the scene to see its console.Expected console output
created 6 textures, nothing drawn yet: 0
after the first render(): 6
removed 3 cards and disposed their textures: 3
disposed a texture that is still used: 2
after the next render(): 3 (it was uploaded again)Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Swap textures without disposing
In the second example, right after the first renderer.render(scene, camera);, add a loop that runs 20 times: cards[0].material.map = new THREE.CanvasTexture(canvas); (a fresh 1024 × 1024 canvas each time) and renderer.render(scene, camera);, then print mem().
Myth vs fact
Myth
A small JPEG is a cheap texture.
Fact
JPEG is a download format. The GPU needs every texel unpacked: a 340 KB photo of 1024 × 1024 takes 5.3 MiB; of 4096 × 4096 it takes 85 MiB.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
renderer.info.memoryhas onlygeometriesandtexturescounts. There is no byte count: compute it yourself. Chrome's Task Manager (Shift + Esc on Windows and Linux) can show a GPU memory column per tab (right-click its header to add it), which catches leaks. HDR pictures cost more per texel: a half-float RGBA texel is 8 bytes, so the 1024 × 512 studio file is 4 MiB before it is prepared. - ▸
Compressed GPU formats (ASTC on phones, BC7 on desktops) are about 1 byte per texel, a quarter of the 4 bytes; Basis Universal in KTX2 is transcoded at load time to whichever the device supports. They also cut upload time. The cost is some quality and a decoder (Topic 10.5).
Remember this
- 1
The problem this solves: the story's crash. A 4096-pixel photo of the floor was 600 KB as a file and over 85 MiB on the GPU. A page that loads a handful of such pictures can use more memory than the phone allows, and the browser kills the tab. File size tells you the download time, not the memory cost.
- 2
The rule. The GPU stores each texel as 4 bytes (red, green, blue, alpha), even for a JPEG without transparency. So bytes =
width × height × 4. Mipmaps add about a third more (1/4 + 1/16 + ... = 1/3). A 2048 × 2048 texture with mipmaps: 2048 × 2048 × 4 = 16 MiB, × 4/3 = 21.3 MiB. The first example computes real files: each 1024 picture is about 5.3 MiB on the GPU, though the files are only 343 to 752 KiB. - 3
Size grows with the square. Doubling the width and height makes four times the memory: 1024 → 5.3 MiB, 2048 → 21.3 MiB, 4096 → 85.3 MiB. A 300 × 200 picture costs only 0.31 MiB. Forty textures (the scene of a busy configurator) at 1024 cost 40 × 5.33 = 213 MiB; at 2048 they cost 853 MiB, which kills a phone tab. Before shipping, ask how many screen pixels the texture will really cover; a bike part that fills 400 pixels does not need 4096.
- 4
**
renderer.info.memory.texturescounts textures, not bytes.** It goes up when a texture is first used in a render and down when you calldispose(). Compute bytes yourself from the sizes. The second example creates six textures, renders (6), disposes three (3). One of them is still in a card, disposed by mistake: it is uploaded again at the next render. The status bar shows the same count. - 5
**What
dispose()frees.**texture.dispose()deletes the GPU copy. Disposing a material or a geometry does not dispose its textures: textures are shared, so you must do it yourself. The copy in JavaScript memory goes when nothing refers to the texture any more. Remove it from the scene as well, or it is uploaded again. - 6
Ways to spend less. Resize pictures to the size they need; drop mipmaps for pictures always drawn at their own size; pack roughness, metalness and AO into one texture (ORM, Topic 6.4); share one texture between meshes; and use GPU-compressed textures (KTX2, Topic 10.5), which stay compressed on the GPU, roughly 4 to 8 times smaller. A phone gives a tab only part of the device's memory, and nothing in the browser tells you how much, so test on a real cheap phone, like Lena's.
Explain it without notes
A texture is a 300 KB JPEG at 2048 × 2048. How much GPU memory does it take, and how do you cut it?
What does renderer.info.memory.textures tell you, and what does it not?
Practice
Your showroom uses 12 textures of 2048 × 2048 with mipmaps. What is the total, and is it a problem?
Write the code that changes a mesh's colour map and frees the old one.
Done when you can
Compute a texture's GPU memory with and without mipmaps
Explain why file size does not predict memory
Read
renderer.info.memory.texturesand use it to find a leakFree textures with
dispose()and say what it does not free