From 70570e62e24fa94dd6d66674fe8ca26b644cf030 Mon Sep 17 00:00:00 2001 From: Dustinspace217 Date: Fri, 16 Oct 2026 00:24:39 -0700 Subject: [PATCH 1/2] Evict unused non-model textures from the GPU between missions bm_page_in_stop() pages out a bitmap the new mission does not preload with bm_unload_fast(), which frees only the system-RAM copy. The OpenGL texture object stayed resident until exit: gr_opengl_bm_free_data() frees it only on release, and the per-mission opengl_tcache_flush() has been commented out since 2007. Only model textures ever left, through bm_release(). A mod with large per-setting effect sets accumulates every explosion, impact and particle animation it has ever shown, exhausts VRAM after a few missions, and the driver spills into system RAM. Track per bitmap whether it was drawn or locked since the last page-in, and how many consecutive page-ins it has gone without being preloaded, drawn or locked. At the tabled count (default 1) the GPU copy is freed, whole animations at a time so a shared texture array is never left half alive. Textures the next mission preloads keep their GPU copy, so shared content is not re-uploaded; anything drawn last mission survives one more; mission restarts keep everything. The marks are cleared in a pass after the page-in loop, because preloading a later frame binds it and would re-mark a first frame the loop had already visited. Eviction runs only on OpenGL: Vulkan already drops every texture at page-in start, and its free deletes the slot's backend object. game_settings.tbl: $Evict Unused GPU Textures After Missions: N (OpenGL only; 0 or less restores the old keep-until-exit behavior; values above 255 clamp). Debug builds log each evicted animation and the per-mission count. The new test suite drives bm_page_in_start/stop with the stub renderer and a recording free hook: the table knob, N=0/1/2 semantics, the used mark, preload, locks held across a page-in, whole-animation frees, user and volume bitmaps, non-array animations, the renderer gate, and the stale-mark case. Fixes #7873 --- code/bmpman/bm_internal.h | 2 + code/bmpman/bmpman.cpp | 70 ++++ code/bmpman/bmpman.h | 6 + code/graphics/opengl/gropengltexture.cpp | 2 + code/mod_table/mod_table.cpp | 18 ++ code/mod_table/mod_table.h | 1 + test/src/bmpman/test_bmpman_evict.cpp | 305 ++++++++++++++++++ test/src/source_groups.cmake | 4 + .../data/tables/test-mod.tbm | 3 + .../data/tables/test-mod.tbm | 3 + 10 files changed, 414 insertions(+) create mode 100644 test/src/bmpman/test_bmpman_evict.cpp create mode 100644 test/test_data/bmpman/knob_above_255_is_clamped/data/tables/test-mod.tbm create mode 100644 test/test_data/bmpman/knob_parsed_in_graphics_settings/data/tables/test-mod.tbm diff --git a/code/bmpman/bm_internal.h b/code/bmpman/bm_internal.h index b5a0aa82605..c12e1ce5089 100644 --- a/code/bmpman/bm_internal.h +++ b/code/bmpman/bm_internal.h @@ -72,6 +72,8 @@ struct bitmap_entry { int preload_count; //!< how many times this gets used in game, for unlocking uint used_flags; //!< What flags it was accessed thru int load_count; + ubyte used_since_page_in; //!< set when the renderer binds the bitmap (or it is locked) after the last page-in + ubyte unused_page_ins; //!< page-ins in a row with neither preload nor use; GPU copy freed at the table limit bitmap bm; //!< Bitmap info diff --git a/code/bmpman/bmpman.cpp b/code/bmpman/bmpman.cpp index 9b9788adb67..c1a80a8be7c 100644 --- a/code/bmpman/bmpman.cpp +++ b/code/bmpman/bmpman.cpp @@ -34,6 +34,7 @@ #include "tracing/Monitor.h" #include "tracing/tracing.h" #include "ktxutils/ktxutils.h" +#include "mod_table/mod_table.h" #include #include @@ -296,6 +297,8 @@ static void allocate_new_block() { entry.used_this_frame = 0; #endif entry.load_count = 0; + entry.used_since_page_in = 0; + entry.unused_page_ins = 0; gr_bm_init(&slot); @@ -2029,6 +2032,7 @@ bitmap * bm_lock(int handle, int bpp, uint flags, bool nodebug) { // Increment ref count for bitmap since lock was made on it. Assert(be->ref_count >= 0); be->ref_count++; // Lock it before we page in data; this prevents a callback from freeing this + bm_mark_used(handle); // as it gets read in // Mark this bitmap as used this frame @@ -2660,6 +2664,61 @@ void bm_page_in_aabitmap(int handle, int nframes) { } } +void bm_mark_used(int handle) { + if (handle < 0) + return; + + // first frame of an animation, whether or not the frames share a texture array: that is where the decision reads + bm_get_entry(handle, false)->used_since_page_in = 1; +} + +/** + * Frees the GPU copy of a bitmap (whole animations only: freeing one frame of a shared texture array would leave the + * others pointing at a dead array) once it has gone the tabled number of page-ins without preload, draw or lock. + * @returns true if the GPU copy was freed + */ +static bool bm_maybe_evict_gpu(bitmap_entry& entry) { + if (Gpu_texture_evict_after_missions <= 0) + return false; + + // Vulkan already drops every texture at page-in start and its free deletes the slot's backend object + if (gr_screen.mode != GraphicsAPI::OpenGL) + return false; + + if (entry.type == BM_TYPE_USER || entry.type == BM_TYPE_3D) + return false; + + if (bm_is_anim(&entry) && entry.info.ani.first_frame != entry.handle) + return false; + + int num_frames = bm_is_anim(&entry) ? entry.info.ani.num_frames : 1; + + bool locked = false; + for (int i = 0; i < num_frames; i++) { + if (bm_get_entry(entry.handle + i)->ref_count != 0) + locked = true; + } + + if (entry.used_since_page_in || locked) { + entry.unused_page_ins = 0; + return false; + } + + if (entry.unused_page_ins < 255) + entry.unused_page_ins++; + + if (entry.unused_page_ins < Gpu_texture_evict_after_missions) + return false; + + nprintf(("BmpMan", "Evicting GPU copy of %s (%d frames)\n", entry.filename, num_frames)); + + for (int i = 0; i < num_frames; i++) + gr_bm_free_data(bm_get_slot(entry.handle + i), true); + + entry.unused_page_ins = 0; + return true; +} + void bm_page_in_start() { Bm_paging = 1; @@ -2691,6 +2750,7 @@ void bm_page_in_stop() { // Load all the ones that are supposed to be loaded for this level. int n = 0; + int n_evicted = 0; int bm_preloading = 1; @@ -2702,6 +2762,7 @@ void bm_page_in_stop() { && (entry.type != BM_TYPE_RENDER_TARGET_STATIC)) { if (entry.preloaded) { TRACE_SCOPE(tracing::PageInSingleBitmap); + entry.unused_page_ins = 0; if (bm_preloading) { if (!gr_preload(entry.handle, (entry.preloaded == 2))) { mprintf(("Out of VRAM. Done preloading.\n")); @@ -2733,12 +2794,21 @@ void bm_page_in_stop() { } } else { bm_unload_fast(entry.handle); + if (bm_maybe_evict_gpu(entry)) + n_evicted++; } } } } + // after the loop: preloading a later frame binds it, which marks a first frame the loop has already cleared + for (auto& block : bm_blocks) { + for (auto& slot : block) + slot.entry.used_since_page_in = 0; + } + nprintf(("BmpInfo", "BMPMAN: Loaded %d bitmaps that are marked as used for this level.\n", n)); + nprintf(("BmpInfo", "BMPMAN: Evicted the GPU copies of %d unused bitmaps or animations.\n", n_evicted)); #ifndef NDEBUG int total_bitmaps = 0; diff --git a/code/bmpman/bmpman.h b/code/bmpman/bmpman.h index d52a01f5125..21fd79f72ba 100644 --- a/code/bmpman/bmpman.h +++ b/code/bmpman/bmpman.h @@ -361,6 +361,12 @@ int bm_load_either(const char *filename, int *nframes = NULL, int *fps = NULL, i */ bitmap* bm_lock(int handle, int bpp, uint flags, bool nodebug = false); +/** + * @brief Records that the bitmap (or any frame of its animation) was drawn or locked since the last page-in, which + * keeps its GPU copy from being evicted at the next page-in (see bm_page_in_stop()) + */ +void bm_mark_used(int handle); + /** * @brief Returns the image type of the given bitmap handle */ diff --git a/code/graphics/opengl/gropengltexture.cpp b/code/graphics/opengl/gropengltexture.cpp index 9375b353468..156dac5970c 100644 --- a/code/graphics/opengl/gropengltexture.cpp +++ b/code/graphics/opengl/gropengltexture.cpp @@ -1164,6 +1164,8 @@ int gr_opengl_tcache_set_internal(int bitmap_handle, int bitmap_type, float *u_s auto t = bm_get_gr_info(bitmap_handle, true); + bm_mark_used(bitmap_handle); + if (!bm_is_render_target(bitmap_handle) && t->bitmap_handle < 0) { GL_state.Texture.SetActiveUnit(tex_unit); diff --git a/code/mod_table/mod_table.cpp b/code/mod_table/mod_table.cpp index 2ca750af08c..d31eb9534ca 100644 --- a/code/mod_table/mod_table.cpp +++ b/code/mod_table/mod_table.cpp @@ -55,6 +55,7 @@ int Default_fiction_viewer_ui; bool Enable_external_shaders; bool Enable_external_default_scripts; DefaultDetailPreset Default_detail_preset; +int Gpu_texture_evict_after_missions; bool Full_color_head_anis; bool Dont_automatically_select_turret_when_targeting_ship; bool Automatically_select_subsystem_under_reticle_when_targeting_same_ship; @@ -571,6 +572,18 @@ void parse_mod_table(const char *filename) } } + if (optional_string("$Evict Unused GPU Textures After Missions:")) { + stuff_int(&Gpu_texture_evict_after_missions); + + if (Gpu_texture_evict_after_missions > 255) { + // clamp before the warning: under unit tests a warning throws and would skip the assignment + int requested = Gpu_texture_evict_after_missions; + Gpu_texture_evict_after_missions = 255; + error_display(0, "$Evict Unused GPU Textures After Missions: %i is above the limit of 255, using 255", + requested); + } + } + if (optional_string("$Briefing Window FOV:")) { stuff_float(&Briefing_window_FOV); } @@ -1646,6 +1659,10 @@ static void mod_table_log_settings() mprintf(("Game Settings Table: External shaders are DISABLED\n")); mprintf(("Game Settings Table: Default detail preset is %i of %i-%i\n", static_cast(Default_detail_preset), 0, static_cast(DefaultDetailPreset::Num_detail_presets) - 1)); + if (Gpu_texture_evict_after_missions > 0) + mprintf(("Game Settings Table: Unused GPU textures are evicted after %i mission(s) (OpenGL only; Vulkan drops them every mission)\n", Gpu_texture_evict_after_missions)); + else + mprintf(("Game Settings Table: Unused GPU textures are kept until exit (OpenGL only; Vulkan drops them every mission)\n")); mprintf(("Game Settings Table: Briefing window FOV is %f\n", Briefing_window_FOV)); mprintf(("Game Settings Table: Generic pain flash factor is %.2f\n", Generic_pain_flash_factor)); mprintf(("Game Settings Table: Shield pain flash factor is %.2f\n", Shield_pain_flash_factor)); @@ -1886,6 +1903,7 @@ void mod_table_reset() Enable_external_shaders = false; Enable_external_default_scripts = false; Default_detail_preset = DefaultDetailPreset::VeryHigh; // "very high" seems a reasonable default in 2012 -zookeeper + Gpu_texture_evict_after_missions = 1; Full_color_head_anis = false; Dont_automatically_select_turret_when_targeting_ship = false; Automatically_select_subsystem_under_reticle_when_targeting_same_ship = false; diff --git a/code/mod_table/mod_table.h b/code/mod_table/mod_table.h index d502f96098b..6c14494677b 100644 --- a/code/mod_table/mod_table.h +++ b/code/mod_table/mod_table.h @@ -75,6 +75,7 @@ extern int Default_fiction_viewer_ui; extern bool Enable_external_shaders; extern bool Enable_external_default_scripts; extern DefaultDetailPreset Default_detail_preset; +extern int Gpu_texture_evict_after_missions; extern bool Full_color_head_anis; extern bool Dont_automatically_select_turret_when_targeting_ship; extern bool Automatically_select_subsystem_under_reticle_when_targeting_same_ship; diff --git a/test/src/bmpman/test_bmpman_evict.cpp b/test/src/bmpman/test_bmpman_evict.cpp new file mode 100644 index 00000000000..2ccbde2f4b2 --- /dev/null +++ b/test/src/bmpman/test_bmpman_evict.cpp @@ -0,0 +1,305 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +struct RecordedFree { + int handle; + bool release; +}; + +class BmpmanEvictTest : public test::FSTestFixture { + public: + BmpmanEvictTest() : test::FSTestFixture(INIT_CFILE | INIT_GRAPHICS | INIT_MOD_TABLE) { + pushModDir("bmpman"); + } + + protected: + SCP_vector frees; + + void SetUp() override { + test::FSTestFixture::SetUp(); + + // the fixture runs as a standalone server, where bm_load substitutes placeholder images + saved_standalone = Is_standalone; + Is_standalone = 0; + + // eviction only runs on OpenGL (Vulkan flushes on its own); the stub renderer's function pointers stay in place + saved_mode = gr_screen.mode; + gr_screen.mode = GraphicsAPI::OpenGL; + + saved_free = gr_screen.gf_bm_free_data; + // a lock's loader also calls the renderer with release=false to drop stale data; only release frees are evictions + gr_screen.gf_bm_free_data = [this](bitmap_slot* slot, bool release) { + if (release) + frees.push_back({slot->entry.handle, release}); + }; + } + + void TearDown() override { + gr_screen.gf_bm_free_data = saved_free; + gr_screen.mode = saved_mode; + Is_standalone = saved_standalone; + + test::FSTestFixture::TearDown(); + } + + static void page_in() { + bm_page_in_start(); + bm_page_in_stop(); + } + + static int load_still() { + int handle = bm_load("attacker"); + EXPECT_GE(handle, 0); + return handle; + } + + static int load_anim(int* nframes) { + int handle = bm_load_animation("CursorWeb", nframes); + EXPECT_GE(handle, 0); + EXPECT_GT(*nframes, 1); + return handle; + } + + private: + int saved_standalone = 0; + GraphicsAPI saved_mode = GraphicsAPI::Stub; + std::function saved_free; +}; + +TEST_F(BmpmanEvictTest, knob_default_is_one) { + EXPECT_EQ(1, Gpu_texture_evict_after_missions); +} + +TEST_F(BmpmanEvictTest, knob_parsed_in_graphics_settings) { + EXPECT_EQ(3, Gpu_texture_evict_after_missions); +} + +// parses the table itself: the clamp warns, and under unit tests a warning throws in debug builds +class BmpmanEvictKnobTest : public test::FSTestFixture { + public: + BmpmanEvictKnobTest() : test::FSTestFixture(INIT_CFILE) { + pushModDir("bmpman"); + } +}; + +TEST_F(BmpmanEvictKnobTest, knob_above_255_is_clamped) { +#ifndef NDEBUG + ASSERT_THROW(mod_table_init(), os::dialogs::WarningException); +#else + mod_table_init(); +#endif + EXPECT_EQ(255, Gpu_texture_evict_after_missions); +} + +TEST_F(BmpmanEvictTest, knob_zero_disables) { + Gpu_texture_evict_after_missions = 0; + int handle = load_still(); + + for (int i = 0; i < 3; i++) + page_in(); + + EXPECT_TRUE(frees.empty()); + EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); +} + +TEST_F(BmpmanEvictTest, never_used_still_freed_at_first_page_in) { + Gpu_texture_evict_after_missions = 1; + int handle = load_still(); + + page_in(); + + ASSERT_EQ(1u, frees.size()); + EXPECT_EQ(handle, frees[0].handle); + EXPECT_TRUE(frees[0].release); + EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); +} + +TEST_F(BmpmanEvictTest, used_mark_keeps_texture_for_one_page_in) { + Gpu_texture_evict_after_missions = 1; + int handle = load_still(); + + bm_mark_used(handle); + page_in(); + + EXPECT_TRUE(frees.empty()); + EXPECT_EQ(0, bm_get_entry(handle)->used_since_page_in); + EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); + + page_in(); + + ASSERT_EQ(1u, frees.size()); + EXPECT_EQ(handle, frees[0].handle); +} + +TEST_F(BmpmanEvictTest, lock_and_unlock_count_as_use) { + Gpu_texture_evict_after_missions = 1; + int handle = load_still(); + + ASSERT_NE(nullptr, bm_lock(handle, 16, BMP_TEX_OTHER)); + bm_unlock(handle); + page_in(); + + EXPECT_TRUE(frees.empty()); + + page_in(); + + ASSERT_EQ(1u, frees.size()); + EXPECT_EQ(handle, frees[0].handle); +} + +TEST_F(BmpmanEvictTest, preload_keeps_and_resets_counter) { + Gpu_texture_evict_after_missions = 2; + int handle = load_still(); + + page_in(); + EXPECT_TRUE(frees.empty()); + EXPECT_EQ(1, bm_get_entry(handle)->unused_page_ins); + + bm_page_in_start(); + bm_page_in_texture(handle); + bm_page_in_stop(); + + EXPECT_TRUE(frees.empty()); + EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); +} + +TEST_F(BmpmanEvictTest, n_two_frees_on_second_unused_page_in) { + Gpu_texture_evict_after_missions = 2; + int handle = load_still(); + + page_in(); + EXPECT_TRUE(frees.empty()); + EXPECT_EQ(1, bm_get_entry(handle)->unused_page_ins); + + page_in(); + ASSERT_EQ(1u, frees.size()); + EXPECT_EQ(handle, frees[0].handle); + EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); +} + +TEST_F(BmpmanEvictTest, animation_freed_whole_with_release) { + Gpu_texture_evict_after_missions = 1; + int nframes = 0; + int handle = load_anim(&nframes); + + page_in(); + + ASSERT_EQ(static_cast(nframes), frees.size()); + for (int i = 0; i < nframes; i++) { + EXPECT_EQ(handle + i, frees[i].handle); + EXPECT_TRUE(frees[i].release); + } +} + +TEST_F(BmpmanEvictTest, locked_frame_counts_as_use_for_whole_animation) { + Gpu_texture_evict_after_missions = 1; + int nframes = 0; + int handle = load_anim(&nframes); + + ASSERT_NE(nullptr, bm_lock(handle + 1, 16, BMP_TEX_OTHER)); + page_in(); + page_in(); + + EXPECT_TRUE(frees.empty()); + EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); + + bm_unlock(handle + 1); + page_in(); + + EXPECT_EQ(static_cast(nframes), frees.size()); +} + +TEST_F(BmpmanEvictTest, user_bitmap_never_freed) { + Gpu_texture_evict_after_missions = 1; + ubyte pixels[4 * 4 * 4] = {0}; + int handle = bm_create(32, 4, 4, pixels); + ASSERT_GE(handle, 0); + + page_in(); + page_in(); + + EXPECT_TRUE(frees.empty()); + bm_release(handle); +} + +TEST_F(BmpmanEvictTest, no_eviction_outside_opengl) { + Gpu_texture_evict_after_missions = 1; + gr_screen.mode = GraphicsAPI::Vulkan; + int handle = load_still(); + + page_in(); + page_in(); + + EXPECT_TRUE(frees.empty()); + EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); +} + +TEST_F(BmpmanEvictTest, volume_bitmap_never_freed) { + Gpu_texture_evict_after_missions = 1; + ubyte pixels[4 * 4 * 4 * 4] = {0}; + int handle = bm_create_3d(32, 4, 4, 4, pixels); + ASSERT_GE(handle, 0); + + page_in(); + page_in(); + + EXPECT_TRUE(frees.empty()); + bm_release(handle); +} + +TEST_F(BmpmanEvictTest, mark_on_frame_lands_on_first_frame) { + int nframes = 0; + int handle = load_anim(&nframes); + + bm_mark_used(handle + 1); + + EXPECT_EQ(1, bm_get_entry(handle)->used_since_page_in); + EXPECT_EQ(0, bm_get_entry(handle + 1)->used_since_page_in); +} + +TEST_F(BmpmanEvictTest, mark_on_frame_of_non_array_animation_lands_on_first_frame) { + int nframes = 0; + int handle = load_anim(&nframes); + + // frames that differ in size never share a texture array; forcing the flag avoids a loader warning, which throws here + bm_get_entry(handle)->info.ani.is_array = false; + + bm_mark_used(handle + 1); + + EXPECT_EQ(1, bm_get_entry(handle)->used_since_page_in); + EXPECT_EQ(0, bm_get_entry(handle + 1)->used_since_page_in); +} + +TEST_F(BmpmanEvictTest, marks_consumed_after_preloading_page_in) { + Gpu_texture_evict_after_missions = 1; + int nframes = 0; + int handle = load_anim(&nframes); + + bm_page_in_start(); + bm_page_in_texture(handle); + bm_page_in_stop(); + + EXPECT_TRUE(frees.empty()); + for (int i = 0; i < nframes; i++) + EXPECT_EQ(0, bm_get_entry(handle + i)->used_since_page_in) << "frame " << i; + + page_in(); + + EXPECT_EQ(static_cast(nframes), frees.size()); +} + +TEST_F(BmpmanEvictTest, mark_invalid_handle_is_safe) { + bm_mark_used(-1); +} + +} // namespace diff --git a/test/src/source_groups.cmake b/test/src/source_groups.cmake index 1c268dda86e..c8ed10a3974 100644 --- a/test/src/source_groups.cmake +++ b/test/src/source_groups.cmake @@ -41,6 +41,10 @@ add_file_folder("menuui" menuui/test_intel_parse.cpp ) +add_file_folder("bmpman" + bmpman/test_bmpman_evict.cpp +) + add_file_folder("mod" mod/test_mod_table.cpp ) diff --git a/test/test_data/bmpman/knob_above_255_is_clamped/data/tables/test-mod.tbm b/test/test_data/bmpman/knob_above_255_is_clamped/data/tables/test-mod.tbm new file mode 100644 index 00000000000..9b6e2c1b7e2 --- /dev/null +++ b/test/test_data/bmpman/knob_above_255_is_clamped/data/tables/test-mod.tbm @@ -0,0 +1,3 @@ +#GRAPHICS SETTINGS +$Evict Unused GPU Textures After Missions: 300 +#END diff --git a/test/test_data/bmpman/knob_parsed_in_graphics_settings/data/tables/test-mod.tbm b/test/test_data/bmpman/knob_parsed_in_graphics_settings/data/tables/test-mod.tbm new file mode 100644 index 00000000000..8672ded2ce9 --- /dev/null +++ b/test/test_data/bmpman/knob_parsed_in_graphics_settings/data/tables/test-mod.tbm @@ -0,0 +1,3 @@ +#GRAPHICS SETTINGS +$Evict Unused GPU Textures After Missions: 3 +#END From 7de5d17b410998e9ec7d8d0c720d9d5e2837611d Mon Sep 17 00:00:00 2001 From: Dustinspace217 Date: Fri, 16 Oct 2026 23:45:13 -0700 Subject: [PATCH 2/2] Free the GPU copy of every non-preloaded bitmap at mission load Replaces the per-bitmap use mark, the unused-mission counter and the $Evict Unused GPU Textures After Missions: table key with the design qazwsxal asked about on #7878: at bm_page_in_stop every bitmap the new mission does not preload loses its GPU copy along with its RAM copy, the way the Vulkan backend already flushes at page-in. Textures a mission draws lazily are re-uploaded on first use; measured on an RTX 5080 that upload stays inside normal frame time (12.6 vs 12.4 ms worst frame in the fight window of a revisited mission). Whole animations are freed or kept together, locked bitmaps and user or volume bitmaps are skipped, and Vulkan is untouched. The irradiance pass and the envmap dump read the environment cubemap's texture id straight from its slot; they now bind it through the texture cache first, which recreates a freed file cubemap (and uploads a fresh one, which also fixes the first mission that uses a file cubemap, where the pass used to bind texture 0). --- code/bmpman/bm_internal.h | 2 - code/bmpman/bmpman.cpp | 55 +--- code/bmpman/bmpman.h | 6 - code/graphics/opengl/gropengl.cpp | 5 + code/graphics/opengl/gropengldraw.cpp | 5 + code/graphics/opengl/gropengltexture.cpp | 2 - code/mod_table/mod_table.cpp | 18 -- code/mod_table/mod_table.h | 1 - test/src/bmpman/test_bmpman_evict.cpp | 244 +++++++----------- .../data/tables/test-mod.tbm | 3 - .../data/tables/test-mod.tbm | 3 - 11 files changed, 109 insertions(+), 235 deletions(-) delete mode 100644 test/test_data/bmpman/knob_above_255_is_clamped/data/tables/test-mod.tbm delete mode 100644 test/test_data/bmpman/knob_parsed_in_graphics_settings/data/tables/test-mod.tbm diff --git a/code/bmpman/bm_internal.h b/code/bmpman/bm_internal.h index c12e1ce5089..b5a0aa82605 100644 --- a/code/bmpman/bm_internal.h +++ b/code/bmpman/bm_internal.h @@ -72,8 +72,6 @@ struct bitmap_entry { int preload_count; //!< how many times this gets used in game, for unlocking uint used_flags; //!< What flags it was accessed thru int load_count; - ubyte used_since_page_in; //!< set when the renderer binds the bitmap (or it is locked) after the last page-in - ubyte unused_page_ins; //!< page-ins in a row with neither preload nor use; GPU copy freed at the table limit bitmap bm; //!< Bitmap info diff --git a/code/bmpman/bmpman.cpp b/code/bmpman/bmpman.cpp index c1a80a8be7c..539a9abe21f 100644 --- a/code/bmpman/bmpman.cpp +++ b/code/bmpman/bmpman.cpp @@ -34,7 +34,6 @@ #include "tracing/Monitor.h" #include "tracing/tracing.h" #include "ktxutils/ktxutils.h" -#include "mod_table/mod_table.h" #include #include @@ -297,8 +296,6 @@ static void allocate_new_block() { entry.used_this_frame = 0; #endif entry.load_count = 0; - entry.used_since_page_in = 0; - entry.unused_page_ins = 0; gr_bm_init(&slot); @@ -2032,7 +2029,6 @@ bitmap * bm_lock(int handle, int bpp, uint flags, bool nodebug) { // Increment ref count for bitmap since lock was made on it. Assert(be->ref_count >= 0); be->ref_count++; // Lock it before we page in data; this prevents a callback from freeing this - bm_mark_used(handle); // as it gets read in // Mark this bitmap as used this frame @@ -2664,24 +2660,13 @@ void bm_page_in_aabitmap(int handle, int nframes) { } } -void bm_mark_used(int handle) { - if (handle < 0) - return; - - // first frame of an animation, whether or not the frames share a texture array: that is where the decision reads - bm_get_entry(handle, false)->used_since_page_in = 1; -} - /** - * Frees the GPU copy of a bitmap (whole animations only: freeing one frame of a shared texture array would leave the - * others pointing at a dead array) once it has gone the tabled number of page-ins without preload, draw or lock. + * Frees the GPU copy of a bitmap the new mission did not preload. Whole animations only: freeing one frame of a shared + * texture array would leave the others pointing at a dead array. * @returns true if the GPU copy was freed */ -static bool bm_maybe_evict_gpu(bitmap_entry& entry) { - if (Gpu_texture_evict_after_missions <= 0) - return false; - - // Vulkan already drops every texture at page-in start and its free deletes the slot's backend object +static bool bm_free_gpu_copy(bitmap_entry& entry) { + // Vulkan drops every texture at page-in start and its free deletes the slot's backend object if (gr_screen.mode != GraphicsAPI::OpenGL) return false; @@ -2693,29 +2678,16 @@ static bool bm_maybe_evict_gpu(bitmap_entry& entry) { int num_frames = bm_is_anim(&entry) ? entry.info.ani.num_frames : 1; - bool locked = false; for (int i = 0; i < num_frames; i++) { if (bm_get_entry(entry.handle + i)->ref_count != 0) - locked = true; - } - - if (entry.used_since_page_in || locked) { - entry.unused_page_ins = 0; - return false; + return false; } - if (entry.unused_page_ins < 255) - entry.unused_page_ins++; - - if (entry.unused_page_ins < Gpu_texture_evict_after_missions) - return false; - - nprintf(("BmpMan", "Evicting GPU copy of %s (%d frames)\n", entry.filename, num_frames)); + nprintf(("BmpMan", "Freeing the GPU copy of %s (%d frames)\n", entry.filename, num_frames)); for (int i = 0; i < num_frames; i++) gr_bm_free_data(bm_get_slot(entry.handle + i), true); - entry.unused_page_ins = 0; return true; } @@ -2750,7 +2722,7 @@ void bm_page_in_stop() { // Load all the ones that are supposed to be loaded for this level. int n = 0; - int n_evicted = 0; + int n_freed = 0; int bm_preloading = 1; @@ -2762,7 +2734,6 @@ void bm_page_in_stop() { && (entry.type != BM_TYPE_RENDER_TARGET_STATIC)) { if (entry.preloaded) { TRACE_SCOPE(tracing::PageInSingleBitmap); - entry.unused_page_ins = 0; if (bm_preloading) { if (!gr_preload(entry.handle, (entry.preloaded == 2))) { mprintf(("Out of VRAM. Done preloading.\n")); @@ -2794,21 +2765,15 @@ void bm_page_in_stop() { } } else { bm_unload_fast(entry.handle); - if (bm_maybe_evict_gpu(entry)) - n_evicted++; + if (bm_free_gpu_copy(entry)) + n_freed++; } } } } - // after the loop: preloading a later frame binds it, which marks a first frame the loop has already cleared - for (auto& block : bm_blocks) { - for (auto& slot : block) - slot.entry.used_since_page_in = 0; - } - nprintf(("BmpInfo", "BMPMAN: Loaded %d bitmaps that are marked as used for this level.\n", n)); - nprintf(("BmpInfo", "BMPMAN: Evicted the GPU copies of %d unused bitmaps or animations.\n", n_evicted)); + nprintf(("BmpInfo", "BMPMAN: Freed the GPU copies of %d bitmaps the level does not preload.\n", n_freed)); #ifndef NDEBUG int total_bitmaps = 0; diff --git a/code/bmpman/bmpman.h b/code/bmpman/bmpman.h index 21fd79f72ba..d52a01f5125 100644 --- a/code/bmpman/bmpman.h +++ b/code/bmpman/bmpman.h @@ -361,12 +361,6 @@ int bm_load_either(const char *filename, int *nframes = NULL, int *fps = NULL, i */ bitmap* bm_lock(int handle, int bpp, uint flags, bool nodebug = false); -/** - * @brief Records that the bitmap (or any frame of its animation) was drawn or locked since the last page-in, which - * keeps its GPU copy from being evicted at the next page-in (see bm_page_in_stop()) - */ -void bm_mark_used(int handle); - /** * @brief Returns the image type of the given bitmap handle */ diff --git a/code/graphics/opengl/gropengl.cpp b/code/graphics/opengl/gropengl.cpp index 73f1e100c88..08021ef2b8c 100644 --- a/code/graphics/opengl/gropengl.cpp +++ b/code/graphics/opengl/gropengl.cpp @@ -472,6 +472,11 @@ void gr_opengl_dump_envmap(const char* filename) auto height = 512; // in future envmap resolution should be dynamic, or at least extracted from envmap_render_target auto sphere_width = 4 * width; auto sphere_height = 2 * height; + float u_scale, v_scale; + uint32_t array_index; + if (!gr_opengl_tcache_set(ENVMAP, TCACHE_TYPE_CUBEMAP, &u_scale, &v_scale, &array_index, 0)) { + return; + } auto env_tex = bm_get_gr_info(ENVMAP); glBindTexture(env_tex->texture_target, env_tex->texture_id); diff --git a/code/graphics/opengl/gropengldraw.cpp b/code/graphics/opengl/gropengldraw.cpp index 774fb956a59..432dd128384 100644 --- a/code/graphics/opengl/gropengldraw.cpp +++ b/code/graphics/opengl/gropengldraw.cpp @@ -1300,6 +1300,11 @@ void gr_opengl_calculate_irrmap() auto previous_target = gr_screen.rendering_to_texture; int irr_shader = gr_opengl_maybe_create_shader(SDR_TYPE_IRRADIANCE_MAP_GEN, 0); opengl_shader_set_current(irr_shader); + float u_scale, v_scale; + uint32_t array_index; + if (!gr_opengl_tcache_set(ENVMAP, TCACHE_TYPE_CUBEMAP, &u_scale, &v_scale, &array_index, 0)) { + return; + } auto env_tex = bm_get_gr_info(ENVMAP); glBindTexture(env_tex->texture_target, env_tex->texture_id); GL_state.Texture.Enable(0, GL_TEXTURE_CUBE_MAP, env_tex->texture_id); diff --git a/code/graphics/opengl/gropengltexture.cpp b/code/graphics/opengl/gropengltexture.cpp index 156dac5970c..9375b353468 100644 --- a/code/graphics/opengl/gropengltexture.cpp +++ b/code/graphics/opengl/gropengltexture.cpp @@ -1164,8 +1164,6 @@ int gr_opengl_tcache_set_internal(int bitmap_handle, int bitmap_type, float *u_s auto t = bm_get_gr_info(bitmap_handle, true); - bm_mark_used(bitmap_handle); - if (!bm_is_render_target(bitmap_handle) && t->bitmap_handle < 0) { GL_state.Texture.SetActiveUnit(tex_unit); diff --git a/code/mod_table/mod_table.cpp b/code/mod_table/mod_table.cpp index d31eb9534ca..2ca750af08c 100644 --- a/code/mod_table/mod_table.cpp +++ b/code/mod_table/mod_table.cpp @@ -55,7 +55,6 @@ int Default_fiction_viewer_ui; bool Enable_external_shaders; bool Enable_external_default_scripts; DefaultDetailPreset Default_detail_preset; -int Gpu_texture_evict_after_missions; bool Full_color_head_anis; bool Dont_automatically_select_turret_when_targeting_ship; bool Automatically_select_subsystem_under_reticle_when_targeting_same_ship; @@ -572,18 +571,6 @@ void parse_mod_table(const char *filename) } } - if (optional_string("$Evict Unused GPU Textures After Missions:")) { - stuff_int(&Gpu_texture_evict_after_missions); - - if (Gpu_texture_evict_after_missions > 255) { - // clamp before the warning: under unit tests a warning throws and would skip the assignment - int requested = Gpu_texture_evict_after_missions; - Gpu_texture_evict_after_missions = 255; - error_display(0, "$Evict Unused GPU Textures After Missions: %i is above the limit of 255, using 255", - requested); - } - } - if (optional_string("$Briefing Window FOV:")) { stuff_float(&Briefing_window_FOV); } @@ -1659,10 +1646,6 @@ static void mod_table_log_settings() mprintf(("Game Settings Table: External shaders are DISABLED\n")); mprintf(("Game Settings Table: Default detail preset is %i of %i-%i\n", static_cast(Default_detail_preset), 0, static_cast(DefaultDetailPreset::Num_detail_presets) - 1)); - if (Gpu_texture_evict_after_missions > 0) - mprintf(("Game Settings Table: Unused GPU textures are evicted after %i mission(s) (OpenGL only; Vulkan drops them every mission)\n", Gpu_texture_evict_after_missions)); - else - mprintf(("Game Settings Table: Unused GPU textures are kept until exit (OpenGL only; Vulkan drops them every mission)\n")); mprintf(("Game Settings Table: Briefing window FOV is %f\n", Briefing_window_FOV)); mprintf(("Game Settings Table: Generic pain flash factor is %.2f\n", Generic_pain_flash_factor)); mprintf(("Game Settings Table: Shield pain flash factor is %.2f\n", Shield_pain_flash_factor)); @@ -1903,7 +1886,6 @@ void mod_table_reset() Enable_external_shaders = false; Enable_external_default_scripts = false; Default_detail_preset = DefaultDetailPreset::VeryHigh; // "very high" seems a reasonable default in 2012 -zookeeper - Gpu_texture_evict_after_missions = 1; Full_color_head_anis = false; Dont_automatically_select_turret_when_targeting_ship = false; Automatically_select_subsystem_under_reticle_when_targeting_same_ship = false; diff --git a/code/mod_table/mod_table.h b/code/mod_table/mod_table.h index 6c14494677b..d502f96098b 100644 --- a/code/mod_table/mod_table.h +++ b/code/mod_table/mod_table.h @@ -75,7 +75,6 @@ extern int Default_fiction_viewer_ui; extern bool Enable_external_shaders; extern bool Enable_external_default_scripts; extern DefaultDetailPreset Default_detail_preset; -extern int Gpu_texture_evict_after_missions; extern bool Full_color_head_anis; extern bool Dont_automatically_select_turret_when_targeting_ship; extern bool Automatically_select_subsystem_under_reticle_when_targeting_same_ship; diff --git a/test/src/bmpman/test_bmpman_evict.cpp b/test/src/bmpman/test_bmpman_evict.cpp index 2ccbde2f4b2..ff24a924023 100644 --- a/test/src/bmpman/test_bmpman_evict.cpp +++ b/test/src/bmpman/test_bmpman_evict.cpp @@ -4,10 +4,6 @@ #include #include #include -#include -#include - -#include namespace { @@ -18,9 +14,7 @@ struct RecordedFree { class BmpmanEvictTest : public test::FSTestFixture { public: - BmpmanEvictTest() : test::FSTestFixture(INIT_CFILE | INIT_GRAPHICS | INIT_MOD_TABLE) { - pushModDir("bmpman"); - } + BmpmanEvictTest() : test::FSTestFixture(INIT_CFILE | INIT_GRAPHICS | INIT_MOD_TABLE) {} protected: SCP_vector frees; @@ -32,12 +26,13 @@ class BmpmanEvictTest : public test::FSTestFixture { saved_standalone = Is_standalone; Is_standalone = 0; - // eviction only runs on OpenGL (Vulkan flushes on its own); the stub renderer's function pointers stay in place + // the free runs only on OpenGL (Vulkan flushes on its own); the stub renderer's hooks stay in place saved_mode = gr_screen.mode; gr_screen.mode = GraphicsAPI::OpenGL; saved_free = gr_screen.gf_bm_free_data; - // a lock's loader also calls the renderer with release=false to drop stale data; only release frees are evictions + // a lock's loader also calls the renderer with release=false to drop stale data; only release frees + // are the frees under test gr_screen.gf_bm_free_data = [this](bitmap_slot* slot, bool release) { if (release) frees.push_back({slot->entry.handle, release}); @@ -52,6 +47,16 @@ class BmpmanEvictTest : public test::FSTestFixture { test::FSTestFixture::TearDown(); } + // counts the recorded frees of the frames [handle, handle + nframes), so other fixture bitmaps cannot skew a test + size_t frees_in(int handle, int nframes = 1) const { + size_t n = 0; + for (const auto& f : frees) { + if (f.handle >= handle && f.handle < handle + nframes) + n++; + } + return n; + } + static void page_in() { bm_page_in_start(); bm_page_in_stop(); @@ -76,230 +81,159 @@ class BmpmanEvictTest : public test::FSTestFixture { std::function saved_free; }; -TEST_F(BmpmanEvictTest, knob_default_is_one) { - EXPECT_EQ(1, Gpu_texture_evict_after_missions); -} - -TEST_F(BmpmanEvictTest, knob_parsed_in_graphics_settings) { - EXPECT_EQ(3, Gpu_texture_evict_after_missions); -} - -// parses the table itself: the clamp warns, and under unit tests a warning throws in debug builds -class BmpmanEvictKnobTest : public test::FSTestFixture { - public: - BmpmanEvictKnobTest() : test::FSTestFixture(INIT_CFILE) { - pushModDir("bmpman"); - } -}; - -TEST_F(BmpmanEvictKnobTest, knob_above_255_is_clamped) { -#ifndef NDEBUG - ASSERT_THROW(mod_table_init(), os::dialogs::WarningException); -#else - mod_table_init(); -#endif - EXPECT_EQ(255, Gpu_texture_evict_after_missions); -} - -TEST_F(BmpmanEvictTest, knob_zero_disables) { - Gpu_texture_evict_after_missions = 0; - int handle = load_still(); - - for (int i = 0; i < 3; i++) - page_in(); - - EXPECT_TRUE(frees.empty()); - EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); -} - -TEST_F(BmpmanEvictTest, never_used_still_freed_at_first_page_in) { - Gpu_texture_evict_after_missions = 1; - int handle = load_still(); - - page_in(); - - ASSERT_EQ(1u, frees.size()); - EXPECT_EQ(handle, frees[0].handle); - EXPECT_TRUE(frees[0].release); - EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); -} - -TEST_F(BmpmanEvictTest, used_mark_keeps_texture_for_one_page_in) { - Gpu_texture_evict_after_missions = 1; +TEST_F(BmpmanEvictTest, not_preloaded_still_freed_at_page_in) { int handle = load_still(); - bm_mark_used(handle); page_in(); - EXPECT_TRUE(frees.empty()); - EXPECT_EQ(0, bm_get_entry(handle)->used_since_page_in); - EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); - - page_in(); + ASSERT_EQ(1u, frees_in(handle)); + for (const auto& f : frees) { + if (f.handle == handle) { + EXPECT_TRUE(f.release); + } + } - ASSERT_EQ(1u, frees.size()); - EXPECT_EQ(handle, frees[0].handle); + // the entry survives the free: the next lock reloads the pixels from the file + ASSERT_NE(nullptr, bm_lock(handle, 16, BMP_TEX_OTHER)); + bm_unlock(handle); } -TEST_F(BmpmanEvictTest, lock_and_unlock_count_as_use) { - Gpu_texture_evict_after_missions = 1; +TEST_F(BmpmanEvictTest, freed_again_at_every_page_in) { int handle = load_still(); - ASSERT_NE(nullptr, bm_lock(handle, 16, BMP_TEX_OTHER)); - bm_unlock(handle); page_in(); - - EXPECT_TRUE(frees.empty()); - page_in(); - ASSERT_EQ(1u, frees.size()); - EXPECT_EQ(handle, frees[0].handle); + EXPECT_EQ(2u, frees_in(handle)); } -TEST_F(BmpmanEvictTest, preload_keeps_and_resets_counter) { - Gpu_texture_evict_after_missions = 2; +TEST_F(BmpmanEvictTest, preloaded_still_kept) { int handle = load_still(); - page_in(); - EXPECT_TRUE(frees.empty()); - EXPECT_EQ(1, bm_get_entry(handle)->unused_page_ins); - + // the stub renderer's preload reports failure; the free lives in the other branch either way bm_page_in_start(); bm_page_in_texture(handle); bm_page_in_stop(); - EXPECT_TRUE(frees.empty()); - EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); + EXPECT_EQ(0u, frees_in(handle)); } -TEST_F(BmpmanEvictTest, n_two_frees_on_second_unused_page_in) { - Gpu_texture_evict_after_missions = 2; +TEST_F(BmpmanEvictTest, lock_then_unlock_does_not_keep) { int handle = load_still(); + ASSERT_NE(nullptr, bm_lock(handle, 16, BMP_TEX_OTHER)); + bm_unlock(handle); page_in(); - EXPECT_TRUE(frees.empty()); - EXPECT_EQ(1, bm_get_entry(handle)->unused_page_ins); - page_in(); - ASSERT_EQ(1u, frees.size()); - EXPECT_EQ(handle, frees[0].handle); - EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); + EXPECT_EQ(1u, frees_in(handle)); } TEST_F(BmpmanEvictTest, animation_freed_whole_with_release) { - Gpu_texture_evict_after_missions = 1; int nframes = 0; int handle = load_anim(&nframes); page_in(); - ASSERT_EQ(static_cast(nframes), frees.size()); + ASSERT_EQ(static_cast(nframes), frees_in(handle, nframes)); + for (int i = 0; i < nframes; i++) { + EXPECT_EQ(1u, frees_in(handle + i)) << "frame " << i; + } + for (const auto& f : frees) { + if (f.handle >= handle && f.handle < handle + nframes) { + EXPECT_TRUE(f.release); + } + } + for (int i = 0; i < nframes; i++) { - EXPECT_EQ(handle + i, frees[i].handle); - EXPECT_TRUE(frees[i].release); + ASSERT_NE(nullptr, bm_lock(handle + i, 16, BMP_TEX_OTHER)) << "frame " << i; + bm_unlock(handle + i); } } -TEST_F(BmpmanEvictTest, locked_frame_counts_as_use_for_whole_animation) { - Gpu_texture_evict_after_missions = 1; +TEST_F(BmpmanEvictTest, locked_frame_keeps_whole_animation) { int nframes = 0; int handle = load_anim(&nframes); ASSERT_NE(nullptr, bm_lock(handle + 1, 16, BMP_TEX_OTHER)); page_in(); - page_in(); - EXPECT_TRUE(frees.empty()); - EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); + EXPECT_EQ(0u, frees_in(handle, nframes)); bm_unlock(handle + 1); page_in(); - EXPECT_EQ(static_cast(nframes), frees.size()); + EXPECT_EQ(static_cast(nframes), frees_in(handle, nframes)); +} + +TEST_F(BmpmanEvictTest, later_frame_preloaded_alone_frees_then_rebuilds) { + int nframes = 0; + int handle = load_anim(&nframes); + + // frames are visited in handle order: the first frame's free runs before the preload of frame 1 rebuilds the array + bm_page_in_start(); + bm_page_in_texture(handle + 1, 1); + bm_page_in_stop(); + + EXPECT_EQ(static_cast(nframes), frees_in(handle, nframes)); +} + +TEST_F(BmpmanEvictTest, first_frame_preloaded_alone_keeps_animation) { + int nframes = 0; + int handle = load_anim(&nframes); + + bm_page_in_start(); + bm_page_in_texture(handle, 1); + bm_page_in_stop(); + + EXPECT_EQ(0u, frees_in(handle, nframes)); } TEST_F(BmpmanEvictTest, user_bitmap_never_freed) { - Gpu_texture_evict_after_missions = 1; ubyte pixels[4 * 4 * 4] = {0}; int handle = bm_create(32, 4, 4, pixels); ASSERT_GE(handle, 0); - page_in(); page_in(); - EXPECT_TRUE(frees.empty()); + EXPECT_EQ(0u, frees_in(handle)); bm_release(handle); } -TEST_F(BmpmanEvictTest, no_eviction_outside_opengl) { - Gpu_texture_evict_after_missions = 1; - gr_screen.mode = GraphicsAPI::Vulkan; - int handle = load_still(); - - page_in(); - page_in(); - - EXPECT_TRUE(frees.empty()); - EXPECT_EQ(0, bm_get_entry(handle)->unused_page_ins); -} - TEST_F(BmpmanEvictTest, volume_bitmap_never_freed) { - Gpu_texture_evict_after_missions = 1; ubyte pixels[4 * 4 * 4 * 4] = {0}; int handle = bm_create_3d(32, 4, 4, 4, pixels); ASSERT_GE(handle, 0); - page_in(); page_in(); - EXPECT_TRUE(frees.empty()); + EXPECT_EQ(0u, frees_in(handle)); bm_release(handle); } -TEST_F(BmpmanEvictTest, mark_on_frame_lands_on_first_frame) { - int nframes = 0; - int handle = load_anim(&nframes); - - bm_mark_used(handle + 1); - - EXPECT_EQ(1, bm_get_entry(handle)->used_since_page_in); - EXPECT_EQ(0, bm_get_entry(handle + 1)->used_since_page_in); -} - -TEST_F(BmpmanEvictTest, mark_on_frame_of_non_array_animation_lands_on_first_frame) { - int nframes = 0; - int handle = load_anim(&nframes); +TEST_F(BmpmanEvictTest, render_target_never_freed) { + ubyte pixels[4 * 4 * 4] = {0}; + int handle = bm_create(32, 4, 4, pixels); + ASSERT_GE(handle, 0); - // frames that differ in size never share a texture array; forcing the flag avoids a loader warning, which throws here - bm_get_entry(handle)->info.ani.is_array = false; + // the stub renderer cannot make a render target, so the slot is retyped by hand for the page-in filter + bm_get_entry(handle)->type = BM_TYPE_RENDER_TARGET_STATIC; + page_in(); + bm_get_entry(handle)->type = BM_TYPE_RENDER_TARGET_DYNAMIC; + page_in(); - bm_mark_used(handle + 1); + EXPECT_EQ(0u, frees_in(handle)); - EXPECT_EQ(1, bm_get_entry(handle)->used_since_page_in); - EXPECT_EQ(0, bm_get_entry(handle + 1)->used_since_page_in); + bm_get_entry(handle)->type = BM_TYPE_USER; + bm_release(handle); } -TEST_F(BmpmanEvictTest, marks_consumed_after_preloading_page_in) { - Gpu_texture_evict_after_missions = 1; - int nframes = 0; - int handle = load_anim(&nframes); - - bm_page_in_start(); - bm_page_in_texture(handle); - bm_page_in_stop(); - - EXPECT_TRUE(frees.empty()); - for (int i = 0; i < nframes; i++) - EXPECT_EQ(0, bm_get_entry(handle + i)->used_since_page_in) << "frame " << i; +TEST_F(BmpmanEvictTest, no_eviction_outside_opengl) { + gr_screen.mode = GraphicsAPI::Vulkan; + int handle = load_still(); page_in(); - EXPECT_EQ(static_cast(nframes), frees.size()); -} - -TEST_F(BmpmanEvictTest, mark_invalid_handle_is_safe) { - bm_mark_used(-1); + EXPECT_EQ(0u, frees_in(handle)); } } // namespace diff --git a/test/test_data/bmpman/knob_above_255_is_clamped/data/tables/test-mod.tbm b/test/test_data/bmpman/knob_above_255_is_clamped/data/tables/test-mod.tbm deleted file mode 100644 index 9b6e2c1b7e2..00000000000 --- a/test/test_data/bmpman/knob_above_255_is_clamped/data/tables/test-mod.tbm +++ /dev/null @@ -1,3 +0,0 @@ -#GRAPHICS SETTINGS -$Evict Unused GPU Textures After Missions: 300 -#END diff --git a/test/test_data/bmpman/knob_parsed_in_graphics_settings/data/tables/test-mod.tbm b/test/test_data/bmpman/knob_parsed_in_graphics_settings/data/tables/test-mod.tbm deleted file mode 100644 index 8672ded2ce9..00000000000 --- a/test/test_data/bmpman/knob_parsed_in_graphics_settings/data/tables/test-mod.tbm +++ /dev/null @@ -1,3 +0,0 @@ -#GRAPHICS SETTINGS -$Evict Unused GPU Textures After Missions: 3 -#END