diff --git a/src/chat_gui4.cpp b/src/chat_gui4.cpp index 7e00d4cc..fab54bca 100644 --- a/src/chat_gui4.cpp +++ b/src/chat_gui4.cpp @@ -37,7 +37,7 @@ namespace Components { } // Components -static float lerp(float a, float b, float t) { +static constexpr float lerp(float a, float b, float t) { return a + t * (b - a); } @@ -121,8 +121,10 @@ ChatGui4::ChatGui4( ConfigModelI& conf, RegistryMessageModel& rmm, Contact3Registry& cr, - TextureUploaderI& tu -) : _conf(conf), _rmm(rmm), _cr(cr), _tal(_cr), _contact_tc(_tal, tu), _msg_tc(_mil, tu), _sip(tu) { + TextureUploaderI& tu, + ContactTextureCache& contact_tc, + MessageTextureCache& msg_tc +) : _conf(conf), _rmm(rmm), _cr(cr), _contact_tc(contact_tc), _msg_tc(msg_tc), _sip(tu) { } ChatGui4::~ChatGui4(void) { @@ -137,19 +139,6 @@ ChatGui4::~ChatGui4(void) { } float ChatGui4::render(float time_delta) { - if (!_cr.storage().empty()) { // handle force-reloads for avatars - std::vector to_purge; - _cr.view().each([&to_purge](const Contact3 c) { - to_purge.push_back(c); - }); - _cr.remove(to_purge.cbegin(), to_purge.cend()); - _contact_tc.invalidate(to_purge); - } - // ACTUALLY NOT IF RENDERED, MOVED LOGIC TO ABOVE - // it might unload textures, so it needs to be done before rendering - _contact_tc.update(); - _msg_tc.update(); - _fss.render(); _sip.render(time_delta); @@ -627,14 +616,7 @@ float ChatGui4::render(float time_delta) { } ImGui::End(); - bool unfinished_work_queue = _contact_tc.workLoadQueue(); - unfinished_work_queue = unfinished_work_queue || _msg_tc.workLoadQueue(); - - if (unfinished_work_queue) { - return 0.1f; // so we can get images loaded faster - } else { - return 1.f; // TODO: higher min fps? - } + return 1000.f; // TODO: higher min fps? } void ChatGui4::sendFilePath(const char* file_path) { diff --git a/src/chat_gui4.hpp b/src/chat_gui4.hpp index d7410a46..837c8bd5 100644 --- a/src/chat_gui4.hpp +++ b/src/chat_gui4.hpp @@ -9,7 +9,8 @@ #include "./message_image_loader.hpp" #include "./file_selector.hpp" #include "./send_image_popup.hpp" -#include "entt/container/dense_map.hpp" + +#include #include #include @@ -17,15 +18,16 @@ #include #include +using ContactTextureCache = TextureCache; +using MessageTextureCache = TextureCache; + class ChatGui4 { ConfigModelI& _conf; RegistryMessageModel& _rmm; Contact3Registry& _cr; - ToxAvatarLoader _tal; - TextureCache _contact_tc; - MessageImageLoader _mil; - TextureCache _msg_tc; + ContactTextureCache& _contact_tc; + MessageTextureCache& _msg_tc; FileSelector _fss; SendImagePopup _sip; @@ -52,7 +54,9 @@ class ChatGui4 { ConfigModelI& conf, RegistryMessageModel& rmm, Contact3Registry& cr, - TextureUploaderI& tu + TextureUploaderI& tu, + ContactTextureCache& contact_tc, + MessageTextureCache& msg_tc ); ~ChatGui4(void); diff --git a/src/main_screen.cpp b/src/main_screen.cpp index 7821cd8f..0aeb813d 100644 --- a/src/main_screen.cpp +++ b/src/main_screen.cpp @@ -1,5 +1,7 @@ #include "./main_screen.hpp" +#include + #include #include @@ -21,7 +23,11 @@ MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path, std::stri mmil(rmm), tam(rmm, cr, conf), sdlrtu(renderer_), - cg(conf, rmm, cr, sdlrtu), + tal(cr), + contact_tc(tal, sdlrtu), + mil(), + msg_tc(mil, sdlrtu), + cg(conf, rmm, cr, sdlrtu, contact_tc, msg_tc), sw(conf), tuiu(tc, conf), tdch(tpi) @@ -168,6 +174,21 @@ Screen* MainScreen::render(float time_delta, bool&) { const float pm_interval = pm.render(time_delta); // render + // TODO: move this somewhere else!!! + // needs both tal and tc <.< + if (!cr.storage().empty()) { // handle force-reloads for avatars + std::vector to_purge; + cr.view().each([&to_purge](const Contact3 c) { + to_purge.push_back(c); + }); + cr.remove(to_purge.cbegin(), to_purge.cend()); + contact_tc.invalidate(to_purge); + } + // ACTUALLY NOT IF RENDERED, MOVED LOGIC TO ABOVE + // it might unload textures, so it needs to be done before rendering + const float ctc_interval = contact_tc.update(); + const float msgtc_interval = msg_tc.update(); + const float cg_interval = cg.render(time_delta); // render sw.render(); // render tuiu.render(); // render @@ -217,22 +238,136 @@ Screen* MainScreen::render(float time_delta, bool&) { ImGui::ShowDemoWindow(); } - _render_interval = std::min(pm_interval, cg_interval); + float tc_unfinished_queue_interval; + { // load rendered but not loaded textures + bool unfinished_work_queue = contact_tc.workLoadQueue(); + unfinished_work_queue = unfinished_work_queue || msg_tc.workLoadQueue(); - if ( - _fps_perf_mode > 1 // TODO: magic - ) { - // powersave forces 250ms - _render_interval = 1.f/4.f; - } else if ( - _time_since_event > 1.f && ( // 1sec cool down - _fps_perf_mode == 1 || // TODO: magic - _window_hidden + if (unfinished_work_queue) { + tc_unfinished_queue_interval = 0.1f; // so we can get images loaded faster + } else { + tc_unfinished_queue_interval = 1.f; // TODO: higher min fps? + } + } + + // calculate interval for next frame + // normal: + // - if < 1.5sec since last event + // - min all and clamp(1/60, 1/1) + // - if < 30sec since last event + // - min all (anim + everything else) clamp(1/60, 1/1) (maybe less?) + // - else + // - min without anim and clamp(1/60, 1/1) (maybe more?) + // reduced: + // - if < 1sec since last event + // - min all and clamp(1/60, 1/1) + // - if < 10sec since last event + // - min all (anim + everything else) clamp(1/10, 1/1) + // - else + // - min without anim and max clamp(1/10, 1/1) + // powersave: + // - if < 0sec since last event + // - (ignored) + // - if < 1sec since last event + // - min all (anim + everything else) clamp(1/8, 1/1) + // - else + // - min without anim and clamp(1/1, 1/1) + struct PerfProfileRender { + float low_delay_window {1.5f}; + float low_delay_min {1.f/60.f}; + float low_delay_max {1.f/30.f}; + + float mid_delay_window {30.f}; + float mid_delay_min {1.f/60.f}; + float mid_delay_max {1.f/2.f}; + + // also when main window hidden + float else_delay_min {1.f/60.f}; + float else_delay_max {1.f/2.f}; + }; + + const static PerfProfileRender normalPerfProfile{ + //1.5f, // low_delay_window + //1.f/60.f, // low_delay_min + //1.f/30.f, // low_delay_max + + //30.f, // mid_delay_window + //1.f/60.f, // mid_delay_min + //1.f/2.f, // mid_delay_max + + //1.f/60.f, // else_delay_min + //1.f/2.f, // else_delay_max + }; + const static PerfProfileRender reducedPerfProfile{ + 1.f, // low_delay_window + 1.f/60.f, // low_delay_min + 1.f/30.f, // low_delay_max + + 10.f, // mid_delay_window + 1.f/10.f, // mid_delay_min + 1.f/4.f, // mid_delay_max + + 1.f/10.f, // else_delay_min + 1.f, // else_delay_max + }; + // TODO: fix powersave by adjusting it in the events handler (make ppr member) + const static PerfProfileRender powersavePerfProfile{ + // no window -> ignore first case + 0.f, // low_delay_window + 1.f, // low_delay_min + 1.f, // low_delay_max + + 1.f, // mid_delay_window + 1.f/8.f, // mid_delay_min + 1.f/4.f, // mid_delay_max + + 1.f, // else_delay_min + 1.f, // else_delay_max + }; + + const PerfProfileRender& curr_profile = + // TODO: magic + _fps_perf_mode > 1 + ? powersavePerfProfile + : ( + _fps_perf_mode == 1 + ? reducedPerfProfile + : normalPerfProfile ) - ) { - _render_interval = std::min(1.f/1.f, _render_interval); + ; + + // min over non animations in all cases + _render_interval = std::min(pm_interval, cg_interval); + _render_interval = std::min(_render_interval, tc_unfinished_queue_interval); + + // low delay time window + if (!_window_hidden && _time_since_event < curr_profile.low_delay_window) { + _render_interval = std::min(_render_interval, ctc_interval); + _render_interval = std::min(_render_interval, msgtc_interval); + + _render_interval = std::clamp( + _render_interval, + curr_profile.low_delay_min, + curr_profile.low_delay_max + ); + // mid delay time window + } else if (!_window_hidden && _time_since_event < curr_profile.mid_delay_window) { + _render_interval = std::min(_render_interval, ctc_interval); + _render_interval = std::min(_render_interval, msgtc_interval); + + _render_interval = std::clamp( + _render_interval, + curr_profile.mid_delay_min, + curr_profile.mid_delay_max + ); + // timed out or window hidden } else { - _render_interval = std::min(1.f/60.f, _render_interval); + // no animation timing here + _render_interval = std::clamp( + _render_interval, + curr_profile.else_delay_min, + curr_profile.else_delay_max + ); } _time_since_event += time_delta; diff --git a/src/main_screen.hpp b/src/main_screen.hpp index 3c73deeb..95c2478c 100644 --- a/src/main_screen.hpp +++ b/src/main_screen.hpp @@ -21,6 +21,10 @@ #include "./tox_avatar_manager.hpp" #include "./sdlrenderer_texture_uploader.hpp" +#include "./texture_cache.hpp" +#include "./tox_avatar_loader.hpp" +#include "./message_image_loader.hpp" + #include "./chat_gui4.hpp" #include "./settings_window.hpp" #include "./tox_ui_utils.hpp" @@ -61,6 +65,11 @@ struct MainScreen final : public Screen { SDLRendererTextureUploader sdlrtu; //OpenGLTextureUploader ogltu; + ToxAvatarLoader tal; + TextureCache contact_tc; + MessageImageLoader mil; + TextureCache msg_tc; + ChatGui4 cg; SettingsWindow sw; ToxUIUtils tuiu; diff --git a/src/texture_cache.cpp b/src/texture_cache.cpp index 382af96e..b7657cb6 100644 --- a/src/texture_cache.cpp +++ b/src/texture_cache.cpp @@ -2,8 +2,9 @@ #include #include +#include -void TextureEntry::doAnimation(const int64_t ts_now) { +int64_t TextureEntry::doAnimation(const int64_t ts_now) { if (frame_duration.size() > 1) { // is animation do { // why is this loop so ugly const int64_t duration = getDuration(); @@ -11,11 +12,13 @@ void TextureEntry::doAnimation(const int64_t ts_now) { timestamp_last_rendered += duration; next(); } else { - break; + // return ts for next frame + return timestamp_last_rendered + duration; } - } while(true); + } while (true); } else { timestamp_last_rendered = ts_now; + return std::numeric_limits::max(); // static image } } diff --git a/src/texture_cache.hpp b/src/texture_cache.hpp index 0c35f3c9..f505936e 100644 --- a/src/texture_cache.hpp +++ b/src/texture_cache.hpp @@ -50,7 +50,8 @@ struct TextureEntry { current_texture = (current_texture + 1) % frame_duration.size(); } - void doAnimation(const int64_t ts_now); + // returns ts for next frame + int64_t doAnimation(const int64_t ts_now); template TextureType getID(void) { @@ -133,14 +134,16 @@ struct TextureCache { } } - void update(void) { + float update(void) { const uint64_t ts_now = Message::getTimeMS(); + uint64_t ts_min_next = ts_now + ms_before_purge; std::vector to_purge; for (auto&& [key, te] : _cache) { if (te.rendered_this_frame) { - te.doAnimation(ts_now); + const uint64_t ts_next = te.doAnimation(ts_now); te.rendered_this_frame = false; + ts_min_next = std::min(ts_min_next, ts_next); } else if (_cache.size() > min_count_before_purge && ts_now - te.timestamp_last_rendered >= ms_before_purge) { to_purge.push_back(key); } @@ -148,7 +151,10 @@ struct TextureCache { invalidate(to_purge); + // we ignore the default texture ts :) _default_texture.doAnimation(ts_now); + + return (ts_min_next - ts_now) / 1000.f; } void invalidate(const std::vector& to_purge) { @@ -169,14 +175,14 @@ struct TextureCache { auto new_entry_opt = _l.load(_tu, *it); if (new_entry_opt.has_value()) { _cache.emplace(*it, new_entry_opt.value()); - _to_load.erase(it); - // TODO: not a good idea + it = _to_load.erase(it); + + // TODO: not a good idea? break; // end load from queue/onlyload 1 per update } } - it++; - + // peak return it != _to_load.end(); } };