diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index 8448a723ad..4c58b67c4b 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -609,6 +609,10 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) { } } + if (settings.draw_loader_window) { + m_render_state.loader->draw_debug_window(); + } + m_profiler.finish(); // if (m_profiler.root_time() > 0.018) { // fmt::print("Slow frame: {:.2f} ms\n", m_profiler.root_time() * 1000); diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.h b/game/graphics/opengl_renderer/OpenGLRenderer.h index f336e748ba..7e7f70ad92 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.h +++ b/game/graphics/opengl_renderer/OpenGLRenderer.h @@ -16,6 +16,7 @@ struct RenderOptions { bool draw_render_debug_window = false; bool draw_profiler_window = false; + bool draw_loader_window = false; bool draw_small_profiler_window = false; bool draw_subtitle_editor_window = false; bool draw_filters_window = false; diff --git a/game/graphics/opengl_renderer/debug_gui.cpp b/game/graphics/opengl_renderer/debug_gui.cpp index 05b21376f3..856e3d24a7 100644 --- a/game/graphics/opengl_renderer/debug_gui.cpp +++ b/game/graphics/opengl_renderer/debug_gui.cpp @@ -99,6 +99,7 @@ void OpenGlDebugGui::draw(const DmaStats& dma_stats) { ImGui::MenuItem("Render Debug", nullptr, &m_draw_debug); ImGui::MenuItem("Profiler", nullptr, &m_draw_profiler); ImGui::MenuItem("Small Profiler", nullptr, &small_profiler); + ImGui::MenuItem("Loader", nullptr, &m_draw_loader); ImGui::EndMenu(); } diff --git a/game/graphics/opengl_renderer/debug_gui.h b/game/graphics/opengl_renderer/debug_gui.h index 70761d8693..7191c1274d 100644 --- a/game/graphics/opengl_renderer/debug_gui.h +++ b/game/graphics/opengl_renderer/debug_gui.h @@ -46,6 +46,7 @@ class OpenGlDebugGui { bool should_draw_profiler() const { return master_enable && m_draw_profiler; } bool should_draw_subtitle_editor() const { return master_enable && m_subtitle_editor; } bool should_draw_filters_menu() const { return master_enable && m_filters_menu; } + bool should_draw_loader_menu() const { return master_enable && m_draw_loader; } const char* screenshot_name() const { return m_screenshot_save_name; } bool should_advance_frame() { return m_frame_timer.should_advance_frame(); } @@ -76,6 +77,7 @@ class OpenGlDebugGui { bool m_draw_frame_time = false; bool m_draw_profiler = false; bool m_draw_debug = false; + bool m_draw_loader = false; bool m_subtitle_editor = false; bool m_filters_menu = false; bool m_want_screenshot = false; diff --git a/game/graphics/opengl_renderer/loader/Loader.cpp b/game/graphics/opengl_renderer/loader/Loader.cpp index a2c8a633a2..dff2e68477 100644 --- a/game/graphics/opengl_renderer/loader/Loader.cpp +++ b/game/graphics/opengl_renderer/loader/Loader.cpp @@ -7,6 +7,8 @@ #include "game/graphics/opengl_renderer/loader/LoaderStages.h" +#include "third-party/imgui/imgui.h" + namespace { std::string uppercase_string(const std::string& s) { std::string result; @@ -96,6 +98,62 @@ std::vector Loader::get_in_use_levels() { return result; } +void Loader::draw_debug_window() { + ImGui::Begin("Loader"); + std::unique_lock lk(m_loader_mutex); + ImVec4 blue(0.3, 0.3, 0.8, 1.0); + ImVec4 red(0.8, 0.3, 0.3, 1.0); + ImVec4 green(0.3, 0.8, 0.3, 1.0); + + if (!m_desired_levels.empty()) { + ImGui::Text("desired levels"); + for (auto& lev : m_desired_levels) { + auto lev_color = red; + if (m_initializing_tfrag3_levels.find(lev) != m_initializing_tfrag3_levels.end()) { + lev_color = blue; + } + if (m_loaded_tfrag3_levels.find(lev) != m_loaded_tfrag3_levels.end()) { + lev_color = green; + } + ImGui::TextColored(lev_color, "%s", lev.c_str()); + ImGui::SameLine(); + } + ImGui::NewLine(); + ImGui::Separator(); + } + + if (!m_initializing_tfrag3_levels.empty()) { + ImGui::Text("init levels"); + for (auto& lev : m_initializing_tfrag3_levels) { + ImGui::TextColored(blue, "%s", lev.first.c_str()); + ImGui::SameLine(); + } + ImGui::NewLine(); + ImGui::Separator(); + } + + if (!m_loaded_tfrag3_levels.empty()) { + ImGui::Text("loaded levels"); + for (auto& lev : m_loaded_tfrag3_levels) { + auto lev_color = green; + if (lev.second->frames_since_last_used > 0) { + lev_color = blue; + } + if (lev.second->frames_since_last_used > 180) { + lev_color = red; + } + ImGui::TextColored(lev_color, "%20s : %3d", lev.first.c_str(), + lev.second->frames_since_last_used); + ImGui::Text(" %d textures", (int)lev.second->textures.size()); + ImGui::Text(" %d merc", (int)lev.second->merc_model_lookup.size()); + } + ImGui::NewLine(); + ImGui::Separator(); + } + + ImGui::End(); +} + /*! * Loader function that runs in a completely separate thread. * This is used for file I/O and unpacking. @@ -277,6 +335,23 @@ void Loader::update_blocking(TexturePool& tex_pool) { } } +const std::string* Loader::get_most_unloadable_level() { + for (const auto& [name, lev] : m_loaded_tfrag3_levels) { + if (lev->frames_since_last_used > 180 && + std::find(m_desired_levels.begin(), m_desired_levels.end(), name) == + m_desired_levels.end()) { + return &name; + } + } + + for (const auto& [name, lev] : m_loaded_tfrag3_levels) { + if (lev->frames_since_last_used > 180) { + return &name; + } + } + return nullptr; +} + void Loader::update(TexturePool& texture_pool) { Timer loader_timer; @@ -339,65 +414,62 @@ void Loader::update(TexturePool& texture_pool) { // try to remove levels. Timer unload_timer; if ((int)m_loaded_tfrag3_levels.size() >= m_max_levels) { - for (auto& lev : m_loaded_tfrag3_levels) { - if (lev.second->frames_since_last_used > 180) { - std::unique_lock lk(texture_pool.mutex()); - fmt::print("------------------------- PC unloading {}\n", lev.first); - for (size_t i = 0; i < lev.second->level->textures.size(); i++) { - auto& tex = lev.second->level->textures[i]; - if (tex.load_to_pool) { - texture_pool.unload_texture(PcTextureId::from_combo_id(tex.combo_id), - lev.second->textures.at(i)); - } - } - lk.unlock(); - for (auto tex : lev.second->textures) { - if (EXTRA_TEX_DEBUG) { - for (auto& slot : texture_pool.all_textures()) { - if (slot.source) { - ASSERT(slot.gpu_texture != tex); - } else { - ASSERT(slot.gpu_texture != tex); - } - } - } - - glBindTexture(GL_TEXTURE_2D, tex); - glDeleteTextures(1, &tex); - } - - for (auto& tie_geo : lev.second->tie_data) { - for (auto& tie_tree : tie_geo) { - glDeleteBuffers(1, &tie_tree.vertex_buffer); - if (tie_tree.has_wind) { - glDeleteBuffers(1, &tie_tree.wind_indices); - } - glDeleteBuffers(1, &tie_tree.index_buffer); - } - } - - for (auto& tfrag_geo : lev.second->tfrag_vertex_data) { - for (auto& tfrag_buff : tfrag_geo) { - glDeleteBuffers(1, &tfrag_buff); - } - } - - glDeleteBuffers(1, &lev.second->collide_vertices); - glDeleteBuffers(1, &lev.second->merc_vertices); - glDeleteBuffers(1, &lev.second->merc_indices); - - for (auto& model : lev.second->level->merc_data.models) { - auto& mercs = m_all_merc_models.at(model.name); - MercRef ref{&model, lev.second->load_id}; - auto it = std::find(mercs.begin(), mercs.end(), ref); - ASSERT_MSG(it != mercs.end(), fmt::format("missing merc: {}\n", model.name)); - mercs.erase(it); - } - - m_loaded_tfrag3_levels.erase(lev.first); - break; + auto to_unload = get_most_unloadable_level(); + auto& lev = m_loaded_tfrag3_levels.at(*to_unload); + std::unique_lock lk(texture_pool.mutex()); + fmt::print("------------------------- PC unloading {}\n", *to_unload); + for (size_t i = 0; i < lev->level->textures.size(); i++) { + auto& tex = lev->level->textures[i]; + if (tex.load_to_pool) { + texture_pool.unload_texture(PcTextureId::from_combo_id(tex.combo_id), + lev->textures.at(i)); } } + lk.unlock(); + for (auto tex : lev->textures) { + if (EXTRA_TEX_DEBUG) { + for (auto& slot : texture_pool.all_textures()) { + if (slot.source) { + ASSERT(slot.gpu_texture != tex); + } else { + ASSERT(slot.gpu_texture != tex); + } + } + } + + glBindTexture(GL_TEXTURE_2D, tex); + glDeleteTextures(1, &tex); + } + + for (auto& tie_geo : lev->tie_data) { + for (auto& tie_tree : tie_geo) { + glDeleteBuffers(1, &tie_tree.vertex_buffer); + if (tie_tree.has_wind) { + glDeleteBuffers(1, &tie_tree.wind_indices); + } + glDeleteBuffers(1, &tie_tree.index_buffer); + } + } + + for (auto& tfrag_geo : lev->tfrag_vertex_data) { + for (auto& tfrag_buff : tfrag_geo) { + glDeleteBuffers(1, &tfrag_buff); + } + } + + glDeleteBuffers(1, &lev->collide_vertices); + glDeleteBuffers(1, &lev->merc_vertices); + glDeleteBuffers(1, &lev->merc_indices); + + for (auto& model : lev->level->merc_data.models) { + auto& mercs = m_all_merc_models.at(model.name); + MercRef ref{&model, lev->load_id}; + auto it = std::find(mercs.begin(), mercs.end(), ref); + ASSERT_MSG(it != mercs.end(), fmt::format("missing merc: {}\n", model.name)); + mercs.erase(it); + } + + m_loaded_tfrag3_levels.erase(*to_unload); } if (unload_timer.getMs() > 5.f) { diff --git a/game/graphics/opengl_renderer/loader/Loader.h b/game/graphics/opengl_renderer/loader/Loader.h index fbce81e7ac..689de037a2 100644 --- a/game/graphics/opengl_renderer/loader/Loader.h +++ b/game/graphics/opengl_renderer/loader/Loader.h @@ -25,11 +25,14 @@ class Loader { void load_common(TexturePool& tex_pool, const std::string& name); void set_want_levels(const std::vector& levels); std::vector get_in_use_levels(); + void draw_debug_window(); private: void loader_thread(); bool upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool); + const std::string* get_most_unloadable_level(); + // used by game and loader thread std::unordered_map> m_initializing_tfrag3_levels; diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp index 6a7cb5e2e5..79b4590429 100644 --- a/game/graphics/pipelines/opengl.cpp +++ b/game/graphics/pipelines/opengl.cpp @@ -459,6 +459,7 @@ void render_game_frame(int game_width, options.msaa_samples = msaa_samples; options.draw_render_debug_window = g_gfx_data->debug_gui.should_draw_render_debug(); options.draw_profiler_window = g_gfx_data->debug_gui.should_draw_profiler(); + options.draw_loader_window = g_gfx_data->debug_gui.should_draw_loader_menu(); options.draw_subtitle_editor_window = g_gfx_data->debug_gui.should_draw_subtitle_editor(); options.draw_filters_window = g_gfx_data->debug_gui.should_draw_filters_menu(); options.save_screenshot = false;