Merge pull request #1646 from piepacker/jake/osd_locking_opt

optimize locking mechanism for OSD messages (deadlock-proofing)
This commit is contained in:
Connor McLaughlin 2021-02-22 12:21:12 +10:00 committed by GitHub
commit caae06dbf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 54 deletions

View File

@ -934,30 +934,26 @@ void CommonHostInterface::AddOSDMessage(std::string message, float duration /*=
msg.duration = duration;
std::unique_lock<std::mutex> 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<std::mutex> lock(m_osd_messages_lock);
m_osd_messages.clear();
m_osd_posted_messages.clear();
}
bool CommonHostInterface::EnumerateOSDMessages(std::function<bool(const std::string&, float)> callback)
{
std::unique_lock<std::mutex> 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<float>(msg.duration - time);
if (time_remaining <= 0.0f)
{
iter = m_osd_messages.erase(iter);
iter = m_osd_active_messages.erase(iter);
continue;
}
@ -967,7 +963,7 @@ bool CommonHostInterface::EnumerateOSDMessages(std::function<bool(const std::str
continue;
}
if (!callback(iter->text, time_remaining))
if (callback && !callback(iter->text, time_remaining))
return false;
++iter;
@ -976,17 +972,49 @@ bool CommonHostInterface::EnumerateOSDMessages(std::function<bool(const std::str
return true;
}
void CommonHostInterface::AcquirePendingOSDMessages()
{
// memory_order_consume is roughly equivalent to adding a volatile keyword to the read from the deque.
// we just want to force the compiler to always reload the deque size value from memory.
//
// ARM doesn't have good atomic read guarantees so it _could_ read some non-zero value here spuriously,
// but that's OK because we lock the mutex later and recheck things anyway. This early out will still
// avoid 99.99% of the unnecessary lock attempts when size == 0.
std::atomic_thread_fence(std::memory_order_consume);
if (!m_osd_posted_messages.empty())
{
std::unique_lock<std::mutex> 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<std::mutex> 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;
@ -996,40 +1024,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<float>(msg.duration - time);
if (time_remaining <= 0.0f)
{
iter = m_osd_messages.erase(iter);
continue;
}
if (!g_settings.display_show_osd_messages || position_y >= ImGui::GetIO().DisplaySize.y)
{
++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(msg.text.c_str(), nullptr, false, max_width));
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),
msg.text.c_str(), nullptr, max_width, &text_rect);
message.c_str(), nullptr, max_width, &text_rect);
position_y += size.y + spacing;
++iter;
return true;
}
);
}
void CommonHostInterface::DrawDebugWindows()

View File

@ -184,6 +184,9 @@ public:
bool EnumerateOSDMessages(std::function<bool(const std::string&, float)> 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<HostDisplayTexture> m_logo_texture;
std::deque<OSDMessage> m_osd_messages;
std::deque<OSDMessage> m_osd_active_messages; // accessed only by GUI/OSD thread (no lock reqs)
std::deque<OSDMessage> m_osd_posted_messages; // written to by multiple threads.
std::mutex m_osd_messages_lock;
bool m_fullscreen_ui_enabled = false;

View File

@ -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);