Repository navigation
OpenGL never evicts non-model textures between missions #7873
Description
Problem
Textures a mission pages in but the next mission does not use stay on the GPU for the rest of the session. bm_page_in_stop() pages them out with bm_unload_fast(), which frees only the system-RAM copy (bmpman.cpp:2735, :739). gr_opengl_bm_free_data() deletes a GL texture only on release (gropenglbmpman.cpp:48), and the per-mission flush in opengl_preload_init() has been commented out since 7ef78ed in 2007 (gropengltexture.cpp:1240), the same commit that started dropping the RAM copy after upload. Only model textures leave, through bm_release(). Resident GPU memory becomes the union of every effect, impact animation, particle, HUD, interface and Lua bitmap used so far. Mods with large per-setting effect sets (Star Fox Event Horizon is about 4 GB of GPU textures per mission) exhaust VRAM after a few missions and the driver spills to system RAM, which players report as a memory leak. The Vulkan backend already flushes at page-in (VulkanTextureAPI.cpp:122) and its comment names this exact accumulation.
Steps to reproduce
- OpenGL, MediaVPs 4.7.3, a build that counts live GL texture objects after bm_page_in_stop() (patch available on request).
- In one process play SM1-01, SM1-02, SM2-07, SM2-08. At SM2-08 the count is 2.24 GB.
- In a fresh process play only SM2-07, SM2-08. At SM2-08 the count is 2.11 GB.
- The 124 MB difference is missions 1-2's residue; repeating one mission six times shows no growth. On Event Horizon (LW_Mission_01, LW_Mission_02, SC_Mission_01, SC_Mission_02) the stock chain was OOM-killed loading mission 3 inside a 9.5 GB process budget; with eviction at page-out the same budget ran all four.
Proposed solutions
- Evict at page-out (tested). In the page-out branch of bm_page_in_stop(), also free the GPU copy for ordinary unlocked bitmaps; textures the new mission preloads keep theirs. About five lines.
Downside: bitmaps the page-in system never marks (interface, loading screens, Lua-loaded textures, message portraits) are re-read and re-uploaded at every mission boundary, and content used in missions N and N+2 but not N+1 uploads twice. Animations with only some frames marked would need a test (shared texture arrays). - Evict after N missions unused (middle ground). Count missions since each bitmap was last preloaded or bound; free the GPU copy when the count reaches N (default 1 or 2, table or option value). Alternating content, replays and interface art survive; growth is bounded to about N+1 missions of unique content. Keeps Optimization Opportunity: Mission restarts #3515's restart path cheap.
Downside: one int per bitmap entry and one store in the texture-set hot path; same animation-array corner as 1. - Budget-driven LRU. Track resident bytes and at page-in evict least-recently-used non-preloaded textures until under a budget taken from GL_NVX_gpu_memory_info / GL_ATI_meminfo or a table value. Keeps the whole cache when VRAM allows.
Downside: the largest change, hardware-dependent and harder to test, and tcache_slot_opengl::size must be fixed first (gropengltexture.cpp:777 counts the last mip level twice). - Restore the 2007 flush with Vulkan's exclusions (skip render targets, handle shared animation arrays).
Downside: every texture re-uploads every mission, including shared ones, so load times grow; opengl_tcache_flush() as written would delete render-target textures out from under their FBOs.
Activity
dustinspace217 commentedon Oct 16, 2026
Fix is up as #7878.
One correction to the issue text: "nothing was added to evict it" is not quite right. A plain
bm_unload()did free the OpenGL texture until f1bf0a4 (2017, texture arrays), which madegr_opengl_bm_free_data()free it only on release. So swappingbm_unload()in for the fast unload at page-out, which came up as a possible fix, does not free GPU memory on current master; I tested it and the numbers matched stock.