Skip to content

OpenGL never evicts non-model textures between missions #7873

Open

Description

@dustinspace217

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

  1. OpenGL, MediaVPs 4.7.3, a build that counts live GL texture objects after bm_page_in_stop() (patch available on request).
  2. In one process play SM1-01, SM1-02, SM2-07, SM2-08. At SM2-08 the count is 2.24 GB.
  3. In a fresh process play only SM2-07, SM2-08. At SM2-08 the count is 2.11 GB.
  4. 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

  1. 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).
  2. 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.
  3. 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).
  4. 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.

Further reading

  • Commit 7ef78ed (the commented-out flush) and its predecessor 7c2294a (releasing the RAM copy after upload).
  • VulkanTextureAPI.cpp:122-132, the Vulkan flush and its comment.

Activity

  1. dustinspace217 commented on Oct 16, 2026

    @dustinspace217
    ContributorAuthor

    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 made gr_opengl_bm_free_data() free it only on release. So swapping bm_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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions