diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index 54b3b252f..85ef6fb9c 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -930,30 +930,26 @@ void CommonHostInterface::AddOSDMessage(std::string message, float duration /*= msg.duration = duration; std::unique_lock lock(m_osd_messages_lock); - m_osd_messages.push_back(std::move(msg)); + m_osd_posted_messages.push_back(std::move(msg)); } void CommonHostInterface::ClearOSDMessages() { std::unique_lock lock(m_osd_messages_lock); - m_osd_messages.clear(); + m_osd_posted_messages.clear(); } bool CommonHostInterface::EnumerateOSDMessages(std::function callback) { - std::unique_lock lock(m_osd_messages_lock); - if (m_osd_messages.empty()) - return true; - - auto iter = m_osd_messages.begin(); - while (iter != m_osd_messages.end()) + auto iter = m_osd_active_messages.begin(); + while (iter != m_osd_active_messages.end()) { const OSDMessage& msg = *iter; const double time = msg.time.GetTimeSeconds(); const float time_remaining = static_cast(msg.duration - time); if (time_remaining <= 0.0f) { - iter = m_osd_messages.erase(iter); + iter = m_osd_active_messages.erase(iter); continue; } @@ -963,7 +959,7 @@ bool CommonHostInterface::EnumerateOSDMessages(std::functiontext, time_remaining)) + if (callback && !callback(iter->text, time_remaining)) return false; ++iter; @@ -972,17 +968,49 @@ bool CommonHostInterface::EnumerateOSDMessages(std::function lock(m_osd_messages_lock); + for(;;) + { + // lock-and-copy mechanism. + // this allows us to unlock the deque and minimize time that the mutex is held. + // it is almost always the best model to follow for multithread deque. + + if (m_osd_posted_messages.empty()) + break; + + m_osd_active_messages.push_back(std::move(m_osd_posted_messages.front())); + m_osd_posted_messages.pop_front(); + + // somewhat arbitrary hard cap on # of messages. This might be unnecessarily paranoid. If something is + // spamming the osd message log this badly, then probably this isn't going to really help things much. + static constexpr size_t MAX_ACTIVE_OSD_MESSAGES = 512; + if (m_osd_active_messages.size() > MAX_ACTIVE_OSD_MESSAGES) + m_osd_active_messages.pop_front(); + } + } +} + void CommonHostInterface::DrawOSDMessages() { + AcquirePendingOSDMessages(); + constexpr ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing; - std::unique_lock lock(m_osd_messages_lock); - if (m_osd_messages.empty()) - return; - const float scale = ImGui::GetIO().DisplayFramebufferScale.x; const float spacing = 5.0f * scale; const float margin = 10.0f * scale; @@ -992,40 +1020,30 @@ void CommonHostInterface::DrawOSDMessages() float position_x = margin; float position_y = margin; - ImDrawList* dl = ImGui::GetBackgroundDrawList(); - ImFont* font = ImGui::GetFont(); - auto iter = m_osd_messages.begin(); - while (iter != m_osd_messages.end()) - { - const OSDMessage& msg = *iter; - const double time = msg.time.GetTimeSeconds(); - const float time_remaining = static_cast(msg.duration - time); - if (time_remaining <= 0.0f) - { - iter = m_osd_messages.erase(iter); - continue; + EnumerateOSDMessages( + [max_width, spacing, padding, rounding, &position_x, &position_y](const std::string& message, float time_remaining) -> bool { + const float opacity = std::min(time_remaining, 1.0f); + + if (position_y >= ImGui::GetIO().DisplaySize.y) + return false; + + const ImVec2 pos(position_x, position_y); + const ImVec2 text_size(ImGui::CalcTextSize(message.c_str(), nullptr, false, max_width)); + const ImVec2 size(text_size.x + padding * 2.0f, text_size.y + padding * 2.0f); + const ImVec4 text_rect(pos.x + padding, pos.y + padding, pos.x + size.x - padding, pos.y + size.y - padding); + + ImDrawList* dl = ImGui::GetBackgroundDrawList(); + ImFont* font = ImGui::GetFont(); + dl->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), ImGui::GetColorU32(ImGuiCol_WindowBg, opacity), + rounding); + dl->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), ImGui::GetColorU32(ImGuiCol_Border), rounding); + dl->AddText(font, font->FontSize, ImVec2(text_rect.x, text_rect.y), ImGui::GetColorU32(ImGuiCol_Text, opacity), + message.c_str(), nullptr, max_width, &text_rect); + position_y += size.y + spacing; + + return true; } - - if (!g_settings.display_show_osd_messages || position_y >= ImGui::GetIO().DisplaySize.y) - { - ++iter; - continue; - } - - const float opacity = std::min(time_remaining, 1.0f); - - const ImVec2 pos(position_x, position_y); - const ImVec2 text_size(ImGui::CalcTextSize(msg.text.c_str(), nullptr, false, max_width)); - const ImVec2 size(text_size.x + padding * 2.0f, text_size.y + padding * 2.0f); - const ImVec4 text_rect(pos.x + padding, pos.y + padding, pos.x + size.x - padding, pos.y + size.y - padding); - dl->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), ImGui::GetColorU32(ImGuiCol_WindowBg, opacity), - rounding); - dl->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), ImGui::GetColorU32(ImGuiCol_Border), rounding); - dl->AddText(font, font->FontSize, ImVec2(text_rect.x, text_rect.y), ImGui::GetColorU32(ImGuiCol_Text, opacity), - msg.text.c_str(), nullptr, max_width, &text_rect); - position_y += size.y + spacing; - ++iter; - } + ); } void CommonHostInterface::DrawDebugWindows() diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index e60293c61..a8e28a3db 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -184,6 +184,9 @@ public: bool EnumerateOSDMessages(std::function callback); void ClearOSDMessages(); + /// async message queue bookeeping for. Should be called on UI thread. + void AcquirePendingOSDMessages(); + /// Displays a loading screen with the logo, rendered with ImGui. Use when executing possibly-time-consuming tasks /// such as compiling shaders when starting up. void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, @@ -394,7 +397,8 @@ protected: std::unique_ptr m_logo_texture; - std::deque m_osd_messages; + std::deque m_osd_active_messages; // accessed only by GUI/OSD thread (no lock reqs) + std::deque m_osd_posted_messages; // written to by multiple threads. std::mutex m_osd_messages_lock; bool m_fullscreen_ui_enabled = false; diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index 3e128137e..c3a23d3dc 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -2712,12 +2712,7 @@ void DrawStatsOverlay() void DrawOSDMessages() { - if (!g_settings.display_show_osd_messages) - { - // we still need to remove them from the queue - s_host_interface->EnumerateOSDMessages([](const std::string& message, float time_remaining) { return true; }); - return; - } + s_host_interface->AcquirePendingOSDMessages(); ImGui::PushFont(g_large_font);