FullscreenUI: Add background blur to notifications/menus

pull/3704/head
Stenzek 7 months ago
parent c82f4b8bc8
commit 088f05726b
No known key found for this signature in database

@ -149,3 +149,10 @@ namespace ImGui
// Use our texture type
class GPUTexture;
#define ImTextureID GPUTexture*
// Add base vertex/offset to draw callback
struct ImDrawList;
struct ImDrawCmd;
typedef void (*ImDrawWithOffsetCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, unsigned int vertex_offset,
unsigned int index_offset);
#define ImDrawCallback ImDrawWithOffsetCallback

@ -63,7 +63,8 @@ static bool HasBackground();
static bool LoadBackgroundShader(const std::string& path, Error* error);
static bool LoadBackgroundImage(const std::string& path, Error* error);
static void DrawBackground();
static void DrawShaderBackgroundCallback(const ImDrawList* parent_list, const ImDrawCmd* cmd);
static void DrawShaderBackgroundCallback(const ImDrawList* parent_list, const ImDrawCmd* cmd, u32 base_vertex,
u32 base_index);
//////////////////////////////////////////////////////////////////////////
// Resources
@ -1151,7 +1152,8 @@ bool FullscreenUI::LoadBackgroundShader(const std::string& path, Error* error)
return true;
}
void FullscreenUI::DrawShaderBackgroundCallback(const ImDrawList* parent_list, const ImDrawCmd* cmd)
void FullscreenUI::DrawShaderBackgroundCallback(const ImDrawList* parent_list, const ImDrawCmd* cmd, u32 base_vertex,
u32 base_index)
{
if (!g_gpu_device->HasMainSwapChain())
return;
@ -1515,7 +1517,7 @@ void FullscreenUI::DrawPauseMenu()
SmallString buffer;
ImDrawList* dl = ImGui::GetBackgroundDrawList();
ImDrawList* const dl = ImGui::GetBackgroundDrawList();
const ImVec2 display_size(ImGui::GetIO().DisplaySize);
const ImU32 title_text_color = ImGui::GetColorU32(UIStyle.BackgroundTextColor);
const ImU32 text_color = ImGui::GetColorU32(DarkerColor(UIStyle.BackgroundTextColor, 0.85f));
@ -1526,8 +1528,17 @@ void FullscreenUI::DrawPauseMenu()
{
const float scaled_text_spacing = LayoutScale(4.0f);
const float scaled_top_bar_padding = LayoutScale(top_bar_padding);
dl->AddRectFilled(ImVec2(0.0f, 0.0f), ImVec2(display_size.x, scaled_top_bar_height),
ImGui::GetColorU32(ModAlpha(UIStyle.BackgroundColor, 0.95f)), 0.0f);
const ImVec2 top_bar_min = ImVec2(0.0f, 0.0f);
const ImVec2 top_bar_max = ImVec2(display_size.x, scaled_top_bar_height);
if (UIStyle.BlurMenuBackground && BeginBlurBackground(dl, top_bar_min, top_bar_max))
{
dl->AddRectFilled(top_bar_min, top_bar_max, ImGui::GetColorU32(ModAlpha(UIStyle.BackgroundColor, 1.0f)), 0.0f);
EndBlurBackground(dl);
}
else
{
dl->AddRectFilled(top_bar_min, top_bar_max, ImGui::GetColorU32(ModAlpha(UIStyle.BackgroundColor, 0.95f)), 0.0f);
}
const std::string& game_title = VideoThread::GetGameTitle();
const std::string& game_serial = VideoThread::GetGameSerial();
@ -1744,6 +1755,8 @@ void FullscreenUI::DrawPauseMenu()
EndFullscreenWindow();
SetFullscreenFooterBlur(false);
if (IsGamepadInputSource())
{
SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD_UP_DOWN, FSUI_VSTR("Change Selection")),
@ -1949,7 +1962,7 @@ void FullscreenUI::DrawSaveStateSelector()
closed = true;
if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "##save_state_selector_title",
ModAlpha(UIStyle.PrimaryColor, GetBackgroundAlpha())))
ModAlpha(UIStyle.PrimaryColor, GetBackgroundAlpha()), 0.0f, ImVec2(), 0, true))
{
BeginNavBar();
if (NavButton(ICON_PF_NAVIGATION_BACK, true, true))
@ -1968,7 +1981,7 @@ void FullscreenUI::DrawSaveStateSelector()
ImVec2(0.0f, heading_size.y),
ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)),
"##save_state_selector_list", ModAlpha(UIStyle.BackgroundColor, GetBackgroundAlpha()), 0.0f,
ImVec2(LAYOUT_MENU_WINDOW_X_PADDING, LAYOUT_MENU_WINDOW_Y_PADDING)))
ImVec2(LAYOUT_MENU_WINDOW_X_PADDING, LAYOUT_MENU_WINDOW_Y_PADDING), 0, true))
{
ResetFocusHere();
BeginMenuButtons();

@ -347,6 +347,8 @@ void FullscreenUI::DrawNotifications(NotificationLayout& layout)
const float note_icon_size = ImCeil(LAYOUT_LARGE_FONT_SIZE * scale);
const ImVec4 left_background_color = DarkerColor(UIStyle.ToastBackgroundColor, 1.3f);
const ImVec4 right_background_color = DarkerColor(UIStyle.ToastBackgroundColor, 0.8f);
const bool blur_background = g_gpu_settings.display_blur_message_backgrounds && !FullscreenUI::HasActiveWindow() &&
FullscreenUI::CanBlurBackground();
ImDrawList* const dl = ImGui::GetForegroundDrawList();
for (auto iter = s_achievements_locals.notifications.begin(); iter != s_achievements_locals.notifications.end();)
@ -468,15 +470,24 @@ void FullscreenUI::DrawNotifications(NotificationLayout& layout)
const ImVec2 box_min(expected_pos.x, actual_y);
const ImVec2 box_max(box_min.x + box_width, box_min.y + box_height);
const float background_opacity = opacity * 0.95f;
const float min_rounded_width = horizontal_padding * 2.0f;
DrawRoundedGradientRect(
dl, box_min, box_max, ImGui::GetColorU32(ModAlpha(left_background_color, background_opacity)),
ImGui::GetColorU32(ModAlpha(ImLerp(left_background_color, right_background_color,
(box_width - min_rounded_width) / (max_width - min_rounded_width)),
background_opacity)),
horizontal_padding);
if (blur_background && BeginBlurBackground(dl, box_min, box_max))
{
dl->AddRectFilled(box_min, box_max, ImGui::GetColorU32(ModAlpha(left_background_color, opacity)),
horizontal_padding);
EndBlurBackground(dl);
}
else
{
const float background_opacity = opacity * 0.95f;
const float min_rounded_width = horizontal_padding * 2.0f;
DrawRoundedGradientRect(
dl, box_min, box_max, ImGui::GetColorU32(ModAlpha(left_background_color, background_opacity)),
ImGui::GetColorU32(ModAlpha(ImLerp(left_background_color, right_background_color,
(box_width - min_rounded_width) / (max_width - min_rounded_width)),
background_opacity)),
horizontal_padding);
}
const ImVec2 badge_min(box_min.x + horizontal_padding, box_min.y + vertical_padding);
const ImVec2 badge_max(badge_min.x + badge_size, badge_min.y + badge_size);
@ -561,7 +572,9 @@ void FullscreenUI::DrawIndicators(NotificationLayout& layout)
const float image_size = ImCeil(50.0f * scale);
const float font_size = ImCeil(LAYOUT_MEDIUM_FONT_SIZE * scale);
const ImGuiIO& io = ImGui::GetIO();
ImDrawList* dl = ImGui::GetBackgroundDrawList();
const bool blur_background = g_gpu_settings.display_blur_message_backgrounds && !FullscreenUI::HasActiveWindow() &&
FullscreenUI::CanBlurBackground();
ImDrawList* const dl = ImGui::GetBackgroundDrawList();
if (std::vector<Achievements::ActiveChallengeIndicator>& indicators = Achievements::GetActiveChallengeIndicators();
!indicators.empty() &&
@ -628,9 +641,17 @@ void FullscreenUI::DrawIndicators(NotificationLayout& layout)
Achievements::INDICATOR_FADE_OUT_TIME, INDICATOR_WIDTH_COEFF);
const ImVec2 box_max = box_min + ImVec2(box_width, box_height);
DrawRoundedGradientRect(dl, box_min, box_max,
ImGui::GetColorU32(ModAlpha(left_background_color, opacity * bg_opacity)),
ImGui::GetColorU32(ModAlpha(right_background_color, opacity * bg_opacity)), rounding);
if (blur_background && BeginBlurBackground(dl, box_min, box_max))
{
dl->AddRectFilled(box_min, box_max, ImGui::GetColorU32(ModAlpha(left_background_color, opacity)), rounding);
EndBlurBackground(dl);
}
else
{
DrawRoundedGradientRect(dl, box_min, box_max,
ImGui::GetColorU32(ModAlpha(left_background_color, opacity * bg_opacity)),
ImGui::GetColorU32(ModAlpha(right_background_color, opacity * bg_opacity)), rounding);
}
GPUTexture* const badge = FullscreenUI::GetCachedTextureAsync(indicator->badge_path);
if (badge)
@ -676,26 +697,34 @@ void FullscreenUI::DrawIndicators(NotificationLayout& layout)
return UIStyle.Font->CalcTextSizeA(font_size, font_weight, FLT_MAX, 0.0f, IMSTR_START_END(tstr));
};
const auto draw_tracker = [&padding, &rounding, &font_size, &dl, &left_background_color, &right_background_color,
&tstr, &measure_tracker](Achievements::LeaderboardTrackerIndicator& indicator,
const ImVec2& pos, float opacity) {
const ImVec2 size = measure_tracker(indicator);
const float box_width = size.x + padding.x * 2.0f;
const float box_height = size.y + padding.y * 2.0f;
const ImRect box(pos, ImVec2(pos.x + box_width, pos.y + box_height));
const auto draw_tracker =
[&padding, &rounding, &font_size, &blur_background, &dl, &left_background_color, &right_background_color, &tstr,
&measure_tracker](Achievements::LeaderboardTrackerIndicator& indicator, const ImVec2& pos, float opacity) {
const ImVec2 size = measure_tracker(indicator);
const float box_width = size.x + padding.x * 2.0f;
const float box_height = size.y + padding.y * 2.0f;
const ImRect box(pos, ImVec2(pos.x + box_width, pos.y + box_height));
DrawRoundedGradientRect(dl, box.Min, box.Max,
ImGui::GetColorU32(ModAlpha(left_background_color, opacity * bg_opacity)),
ImGui::GetColorU32(ModAlpha(right_background_color, opacity * bg_opacity)), rounding);
if (blur_background && BeginBlurBackground(dl, box.Min, box.Max))
{
dl->AddRectFilled(box.Min, box.Max, ImGui::GetColorU32(ModAlpha(left_background_color, opacity)), rounding);
EndBlurBackground(dl);
}
else
{
DrawRoundedGradientRect(dl, box.Min, box.Max,
ImGui::GetColorU32(ModAlpha(left_background_color, opacity * bg_opacity)),
ImGui::GetColorU32(ModAlpha(right_background_color, opacity * bg_opacity)), rounding);
}
tstr.format(ICON_FA_STOPWATCH " {}", indicator.text);
tstr.format(ICON_FA_STOPWATCH " {}", indicator.text);
const u32 text_col = ImGui::GetColorU32(ModAlpha(UIStyle.ToastTextColor, opacity));
RenderShadowedTextClipped(dl, UIStyle.Font, font_size, font_weight, box.Min + padding, box.Max, text_col, tstr,
nullptr, ImVec2(0.0f, 0.0f), 0.0f, &box);
const u32 text_col = ImGui::GetColorU32(ModAlpha(UIStyle.ToastTextColor, opacity));
RenderShadowedTextClipped(dl, UIStyle.Font, font_size, font_weight, box.Min + padding, box.Max, text_col, tstr,
nullptr, ImVec2(0.0f, 0.0f), 0.0f, &box);
return box_width;
};
return box_width;
};
// animations are not currently handled for more than one tracker... but this should be rare
if (trackers.size() > 1)
@ -938,7 +967,8 @@ void FullscreenUI::DrawAchievementsPauseMenuOverlays(float start_pos_y)
const float box_padding = LayoutScale(15.0f);
const float box_content_width = box_width - box_padding - box_padding;
const float box_rounding = LayoutScale(20.0f);
const u32 box_background_color = ImGui::GetColorU32(ModAlpha(UIStyle.PopupBackgroundColor, 0.8f));
const u32 box_background_color =
ImGui::GetColorU32(ModAlpha(UIStyle.PopupBackgroundColor, UIStyle.BlurMenuBackground ? 1.0f : 0.8f));
const ImU32 box_title_text_color =
ImGui::GetColorU32(DarkerColor(UIStyle.BackgroundTextColor, 0.9f)) | IM_COL32_A_MASK;
const ImU32 title_text_color = ImGui::GetColorU32(UIStyle.BackgroundTextColor) | IM_COL32_A_MASK;
@ -965,7 +995,15 @@ void FullscreenUI::DrawAchievementsPauseMenuOverlays(float start_pos_y)
ImVec2 text_size;
TinyString buffer;
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
if (UIStyle.BlurMenuBackground && BeginBlurBackground(dl, box_min, box_max))
{
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
EndBlurBackground(dl);
}
else
{
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
}
// title
{
@ -1037,7 +1075,15 @@ void FullscreenUI::DrawAchievementsPauseMenuOverlays(float start_pos_y)
box_max = ImVec2(box_min.x + box_width, box_min.y + box_height);
text_pos = ImVec2(box_min.x + box_padding, box_min.y + box_padding);
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
if (UIStyle.BlurMenuBackground && BeginBlurBackground(dl, box_min, box_max))
{
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
EndBlurBackground(dl);
}
else
{
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
}
ImVec4 clip_rect = ImVec4(text_pos.x, text_pos.y, text_pos.x + box_content_width, box_max.y);
dl->AddText(UIStyle.Font, UIStyle.MediumFontSize, UIStyle.BoldFontWeight, text_pos, box_title_text_color,
@ -1227,7 +1273,15 @@ void FullscreenUI::DrawAchievementsPauseMenuOverlays(float start_pos_y)
box_max = ImVec2(box_min.x + box_width, box_min.y + box_height);
text_pos = ImVec2(box_min.x + box_padding, box_min.y + box_padding);
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
if (UIStyle.BlurMenuBackground && BeginBlurBackground(dl, box_min, box_max))
{
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
EndBlurBackground(dl);
}
else
{
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
}
buffer.format(ICON_FA_HAND_FIST " {}",
TRANSLATE_DISAMBIG_SV("Achievements", "Active Challenge Achievements", "Pause Menu"));
@ -1287,7 +1341,15 @@ void FullscreenUI::DrawAchievementsPauseMenuOverlays(float start_pos_y)
box_max = ImVec2(box_min.x + box_width, box_min.y + box_height);
text_pos = ImVec2(box_min.x + box_padding, box_min.y + box_padding);
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
if (UIStyle.BlurMenuBackground && BeginBlurBackground(dl, box_min, box_max))
{
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
EndBlurBackground(dl);
}
else
{
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
}
buffer.format(ICON_FA_STOPWATCH " {}",
TRANSLATE_DISAMBIG_SV("Achievements", "Active Leaderboard Attempts", "Pause Menu"));
@ -1627,15 +1689,15 @@ void FullscreenUI::DrawAchievementsWindow()
((summary.num_unsupported_achievements > 0) ? 20.0f : 0.0f);
const ImVec4 background = ModAlpha(UIStyle.BackgroundColor, WINDOW_ALPHA);
const ImVec4 heading_background = ModAlpha(UIStyle.BackgroundColor, WINDOW_HEADING_ALPHA);
const ImVec4 heading_background = ModAlpha(DarkerColor(UIStyle.BackgroundColor, 1.5f), WINDOW_HEADING_ALPHA);
const ImVec2 display_size = ImGui::GetIO().DisplaySize;
const float heading_height = LayoutScale(heading_height_unscaled);
bool close_window = false;
if (BeginFullscreenWindow(ImVec2(), ImVec2(display_size.x, heading_height), "achievements_heading",
heading_background, 0.0f, ImVec2(10.0f, 10.0f),
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoScrollWithMouse))
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollWithMouse,
true))
{
const ImVec2 pos = ImGui::GetCursorScreenPos() + ImGui::GetStyle().FramePadding;
const float spacing = LayoutScale(LAYOUT_MENU_ITEM_TITLE_SUMMARY_SPACING);
@ -1825,7 +1887,7 @@ void FullscreenUI::DrawAchievementsWindow()
if (BeginFullscreenWindow(ImVec2(0.0f, heading_height),
ImVec2(display_size.x, display_size.y - heading_height - LayoutScale(LAYOUT_FOOTER_HEIGHT)),
"achievements", background, 0.0f,
ImVec2(LAYOUT_MENU_WINDOW_X_PADDING, LAYOUT_MENU_WINDOW_Y_PADDING), 0))
ImVec2(LAYOUT_MENU_WINDOW_X_PADDING, LAYOUT_MENU_WINDOW_Y_PADDING), 0, true))
{
static constexpr std::pair<const char*, const char*> bucket_names[] = {
{ICON_FA_CIRCLE_QUESTION, TRANSLATE_NOOP("Achievements", "Unknown")},
@ -2207,7 +2269,7 @@ void FullscreenUI::DrawLeaderboardsWindow()
SmallString text;
const ImVec4 background = ModAlpha(UIStyle.BackgroundColor, WINDOW_ALPHA);
const ImVec4 heading_background = ModAlpha(UIStyle.BackgroundColor, WINDOW_HEADING_ALPHA);
const ImVec4 heading_background = ModAlpha(DarkerColor(UIStyle.BackgroundColor, 1.5f), WINDOW_HEADING_ALPHA);
const ImVec2 display_size = ImGui::GetIO().DisplaySize;
const u32 text_color = ImGui::GetColorU32(ImGuiCol_Text);
const float spacing = LayoutScale(10.0f);
@ -2216,8 +2278,8 @@ void FullscreenUI::DrawLeaderboardsWindow()
if (BeginFullscreenWindow(ImVec2(), ImVec2(display_size.x, heading_height), "leaderboards_heading",
heading_background, 0.0f, ImVec2(10.0f, 10.0f),
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoScrollWithMouse))
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollWithMouse,
true))
{
const ImVec2 heading_pos = ImGui::GetCursorScreenPos() + ImGui::GetStyle().FramePadding;
const float image_size = LayoutScale(75.0f);
@ -2369,7 +2431,7 @@ void FullscreenUI::DrawLeaderboardsWindow()
if (BeginFullscreenWindow(
ImVec2(0.0f, heading_height),
ImVec2(display_size.x, display_size.y - heading_height - LayoutScale(LAYOUT_FOOTER_HEIGHT)), "leaderboards",
background, 0.0f, ImVec2(LAYOUT_MENU_WINDOW_X_PADDING, LAYOUT_MENU_WINDOW_Y_PADDING), 0))
background, 0.0f, ImVec2(LAYOUT_MENU_WINDOW_X_PADDING, LAYOUT_MENU_WINDOW_Y_PADDING), 0, true))
{
ResetFocusHere();
BeginMenuButtons();
@ -2430,7 +2492,7 @@ void FullscreenUI::DrawLeaderboardsWindow()
if (BeginFullscreenWindow(
ImVec2(0.0f, heading_height),
ImVec2(display_size.x, display_size.y - heading_height - LayoutScale(LAYOUT_FOOTER_HEIGHT)), "leaderboard",
background, 0.0f, ImVec2(LAYOUT_MENU_WINDOW_X_PADDING, LAYOUT_MENU_WINDOW_Y_PADDING), 0))
background, 0.0f, ImVec2(LAYOUT_MENU_WINDOW_X_PADDING, LAYOUT_MENU_WINDOW_Y_PADDING), 0, true))
{
const ImVec2 heading_start_pos = ImGui::GetCursorScreenPos();
ImVec2 column_heading_pos = heading_start_pos;

@ -194,7 +194,6 @@ static constexpr std::string_view COVER_DOWNLOADER_DIALOG_NAME = "##cover_downlo
namespace {
struct SettingsLocals
{
float settings_last_bg_alpha = 1.0f;
SettingsPage settings_page = SettingsPage::Interface;
std::unique_ptr<INISettingsInterface> game_settings_interface;
std::unique_ptr<GameList::Entry> game_settings_entry;
@ -1654,7 +1653,6 @@ void FullscreenUI::SwitchToSettings()
SwitchToMainWindow(MainWindowType::Settings);
s_settings_locals.settings_page = SettingsPage::Interface;
s_settings_locals.settings_last_bg_alpha = GetBackgroundAlpha();
}
bool FullscreenUI::SwitchToGameSettings(SettingsPage page)
@ -1818,12 +1816,8 @@ void FullscreenUI::DrawSettingsWindow()
const ImVec2 heading_size = ImVec2(
io.DisplaySize.x, UIStyle.LargeFontSize + (LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
const float target_bg_alpha = GetBackgroundAlpha();
s_settings_locals.settings_last_bg_alpha =
(target_bg_alpha < s_settings_locals.settings_last_bg_alpha) ?
std::max(s_settings_locals.settings_last_bg_alpha - io.DeltaTime * 2.0f, target_bg_alpha) :
std::min(s_settings_locals.settings_last_bg_alpha + io.DeltaTime * 2.0f, target_bg_alpha);
const float bg_alpha = GetBackgroundAlpha();
const bool blur_background = CanBlurBackground() && s_settings_locals.settings_page != SettingsPage::PostProcessing;
const bool show_localized_titles = GameList::ShouldShowLocalizedTitles();
static constexpr const SettingsPage global_pages[] = {
@ -1867,8 +1861,8 @@ void FullscreenUI::DrawSettingsWindow()
}
if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "settings_category",
ImVec4(UIStyle.PrimaryColor.x, UIStyle.PrimaryColor.y, UIStyle.PrimaryColor.z,
s_settings_locals.settings_last_bg_alpha)))
ModAlpha(UIStyle.PrimaryColor, bg_alpha),
0.0f, ImVec2(), 0, blur_background))
{
BeginNavBar();
@ -1910,9 +1904,8 @@ void FullscreenUI::DrawSettingsWindow()
ImVec2(0.0f, heading_size.y),
ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)),
TinyString::from_format("settings_page_{}", static_cast<u32>(s_settings_locals.settings_page)).c_str(),
ImVec4(UIStyle.BackgroundColor.x, UIStyle.BackgroundColor.y, UIStyle.BackgroundColor.z,
s_settings_locals.settings_last_bg_alpha),
0.0f, ImVec2(LAYOUT_MENU_WINDOW_X_PADDING, 0.0f));
ModAlpha(UIStyle.BackgroundColor, bg_alpha), 0.0f, ImVec2(LAYOUT_MENU_WINDOW_X_PADDING, 0.0f), 0,
blur_background);
if (ImGui::IsWindowFocused() && WantsToCloseMenu())
ReturnToPreviousWindow();
@ -1924,8 +1917,7 @@ void FullscreenUI::DrawSettingsWindow()
ImVec2(0.0f, heading_size.y),
ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)),
TinyString::from_format("settings_page_{}", static_cast<u32>(s_settings_locals.settings_page)).c_str(),
ImVec4(UIStyle.BackgroundColor.x, UIStyle.BackgroundColor.y, UIStyle.BackgroundColor.z,
s_settings_locals.settings_last_bg_alpha));
ModAlpha(UIStyle.BackgroundColor, bg_alpha), 0.0f, ImVec2(), 0, true);
if (SplitWindowIsNavWindow() && WantsToCloseMenu())
ReturnToPreviousWindow();
@ -2283,6 +2275,15 @@ void FullscreenUI::DrawInterfaceSettingsPage()
FSUI_VSTR("Plays sound effects when navigating and activating menus."),
"Main", "FullscreenUISoundEffects", true);
if (DrawToggleSetting(
bsi, FSUI_ICONVSTR(ICON_FA_GLASS_WATER, "Blur Backgrounds"),
FSUI_VSTR("Applies a blur effect to the background when a menu is open to improve readability."), "Main",
"FullscreenUIBlurMenuBackground", true))
{
widgets_settings_changed = true;
BeginTransition({});
}
// have to queue because we're holding the settings lock, and UpdateWidgetsSettings() reads it
if (widgets_settings_changed)
{
@ -2355,6 +2356,13 @@ void FullscreenUI::DrawInterfaceSettingsPage()
FSUI_VSTR("Shows on-screen-display messages when events occur. Errors and warnings are still "
"displayed regardless of this setting."),
"Display", "ShowOSDMessages", true);
DrawToggleSetting(
bsi, FSUI_ICONVSTR(ICON_FA_GLASS_WATER, "Blur Message Backgrounds"),
FSUI_VSTR("Applies a blur effect to the background behind on-screen messages to improve readability."), "Display",
"BlurOSDMessageBackgrounds", true);
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_WINDOW_MAXIMIZE, "Animate Messages"),
FSUI_VSTR("Enables animation for on-screen messages when they appear and disappear."), "Display",
"AnimateOSDMessages", true);
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_PLAY, "Show Status Indicators"),
FSUI_VSTR("Shows persistent icons when turbo is active or when paused."), "Display",
"ShowStatusIndicators", true);

@ -115,8 +115,11 @@ TRANSLATE_NOOP("FullscreenUI", "Alpha Blending");
TRANSLATE_NOOP("FullscreenUI", "Always Track Uploads");
TRANSLATE_NOOP("FullscreenUI", "An error occurred while deleting empty game settings:\n{}");
TRANSLATE_NOOP("FullscreenUI", "An error occurred while saving game settings:\n{}");
TRANSLATE_NOOP("FullscreenUI", "Animate Messages");
TRANSLATE_NOOP("FullscreenUI", "Animates windows opening/closing and changes between views in the Big Picture UI.");
TRANSLATE_NOOP("FullscreenUI", "Appearance");
TRANSLATE_NOOP("FullscreenUI", "Applies a blur effect to the background behind on-screen messages to improve readability.");
TRANSLATE_NOOP("FullscreenUI", "Applies a blur effect to the background when a menu is open to improve readability.");
TRANSLATE_NOOP("FullscreenUI", "Apply Image Patches");
TRANSLATE_NOOP("FullscreenUI", "Are you sure you want to clear all mappings for this controller?\n\nYou cannot undo this action.");
TRANSLATE_NOOP("FullscreenUI", "Are you sure you want to clear the current post-processing chain? All configuration will be lost.");
@ -152,6 +155,8 @@ TRANSLATE_NOOP("FullscreenUI", "Back To Pause Menu");
TRANSLATE_NOOP("FullscreenUI", "Backend Settings");
TRANSLATE_NOOP("FullscreenUI", "Behavior");
TRANSLATE_NOOP("FullscreenUI", "Bindings");
TRANSLATE_NOOP("FullscreenUI", "Blur Backgrounds");
TRANSLATE_NOOP("FullscreenUI", "Blur Message Backgrounds");
TRANSLATE_NOOP("FullscreenUI", "Border Overlay");
TRANSLATE_NOOP("FullscreenUI", "Borderless Fullscreen");
TRANSLATE_NOOP("FullscreenUI", "Bottom: ");
@ -331,6 +336,7 @@ TRANSLATE_NOOP("FullscreenUI", "Enable/Disable the Player LED on DualSense contr
TRANSLATE_NOOP("FullscreenUI", "Enables alignment and bus exceptions. Not needed for any known games.");
TRANSLATE_NOOP("FullscreenUI", "Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles.");
TRANSLATE_NOOP("FullscreenUI", "Enables an additional three controller slots on each port. Not supported in all games.");
TRANSLATE_NOOP("FullscreenUI", "Enables animation for on-screen messages when they appear and disappear.");
TRANSLATE_NOOP("FullscreenUI", "Enables caching of guest textures, required for texture replacement.");
TRANSLATE_NOOP("FullscreenUI", "Enables depth testing for semi-transparent polygons. Usually these include shadows, and tend to clip through the ground when depth testing is enabled.");
TRANSLATE_NOOP("FullscreenUI", "Enables dumping of textures to image files, which can be replaced. Not compatible with all games.");

@ -21,6 +21,7 @@
#include "common/assert.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/gsvector_formatter.h"
#include "common/log.h"
#include "common/lru_cache.h"
#include "common/path.h"
@ -59,6 +60,10 @@ static constexpr int MENU_BUTTON_SPLIT_LAYER_HIGHLIGHT = 1;
static constexpr int MENU_BUTTON_SPLIT_LAYER_FOREGROUND = 2;
static constexpr int NUM_MENU_BUTTON_SPLIT_LAYERS = 3;
// Render to a 720p-sized texture for consistent blur across all resolutions.
static constexpr u32 BLUR_TARGET_WIDTH = 1280;
static constexpr u32 BLUR_TARGET_HEIGHT = 720;
enum class SplitWindowFocusChange : u8
{
None,
@ -69,7 +74,9 @@ enum class SplitWindowFocusChange : u8
static std::optional<Image> LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height);
static std::shared_ptr<GPUTexture> UploadTexture(std::string_view path, const Image& image);
static bool CompileTransitionPipelines(Error* error);
static bool CompilePipelines(Error* error);
static void DrawWithBlurTexture(const ImDrawList* parent_list, const ImDrawCmd* cmd, u32 base_vertex, u32 base_index);
static void CreateFooterTextString(SmallStringBase& dest,
std::span<const std::pair<const char*, std::string_view>> items);
@ -338,6 +345,8 @@ struct WidgetsState
FocusResetType focus_reset_queued = FocusResetType::None;
TransitionState transition_state = TransitionState::Inactive;
s8 has_pending_nav_move = static_cast<s8>(ImGuiDir_None);
bool blur_active = false;
bool blur_valid = false;
ImVec2 horizontal_menu_button_size = {};
@ -353,6 +362,16 @@ struct WidgetsState
float transition_total_time = 0.0f;
float transition_remaining_time = 0.0f;
// Blur resources
GSVector2 blur_texture_scale = GSVector2::cxpr(0);
GSVector4i blur_rect = GSVector4i::cxpr(0);
std::unique_ptr<GPUTexture> blur_source_texture;
std::unique_ptr<GPUTexture> blur_intermediate_texture;
std::unique_ptr<GPUTexture> blur_output_texture;
std::unique_ptr<GPUPipeline> blur_render_pipeline;
std::unique_ptr<GPUPipeline> blur_apply_pipeline;
std::unique_ptr<GPUPipeline> present_copy_pipeline;
SmallString fullscreen_footer_text;
SmallString last_fullscreen_footer_text;
SmallString left_fullscreen_footer_text;
@ -369,8 +388,8 @@ struct WidgetsState
bool has_hovered_menu_item = false;
bool rendered_menu_item_border = false;
bool had_focus_reset = false;
bool sound_effects_enabled = false;
bool had_sound_effect = false;
bool fullscreen_footer_blur_allowed = false;
ImAnimatedVec2 menu_button_frame_min_animated;
ImAnimatedVec2 menu_button_frame_max_animated;
@ -414,13 +433,23 @@ ALIGN_TO_CACHE_LINE static WidgetsState s_state;
} // namespace FullscreenUI
#if defined(_DEBUG) || defined(_DEVEL)
FullscreenUI::WidgetsState::~WidgetsState()
{
DebugAssert(!transition_prev_texture);
DebugAssert(!transition_current_texture);
DebugAssert(!transition_blend_pipeline);
DebugAssert(!blur_source_texture);
DebugAssert(!blur_intermediate_texture);
DebugAssert(!blur_output_texture);
DebugAssert(!blur_render_pipeline);
DebugAssert(!blur_apply_pipeline);
DebugAssert(!present_copy_pipeline);
}
#endif
void FullscreenUI::SetFont(ImFont* ui_font)
{
UIStyle.Font = ui_font;
@ -476,7 +505,8 @@ void FullscreenUI::UpdateWidgetsSettings()
UIStyle.Animations = Core::GetBaseBoolSettingValue("Main", "FullscreenUIAnimations", true);
UIStyle.SmoothScrolling = Core::GetBaseBoolSettingValue("Main", "FullscreenUISmoothScrolling", true);
UIStyle.MenuBorders = Core::GetBaseBoolSettingValue("Main", "FullscreenUIMenuBorders", false);
s_state.sound_effects_enabled = Core::GetBaseBoolSettingValue("Main", "FullscreenUISoundEffects", true);
UIStyle.BlurMenuBackground = Core::GetBaseBoolSettingValue("Main", "FullscreenUIBlurMenuBackground", true);
UIStyle.SoundEffects = Core::GetBaseBoolSettingValue("Main", "FullscreenUISoundEffects", true);
const bool display_ps_icons = Core::GetBaseBoolSettingValue("Main", "FullscreenUIDisplayPSIcons", false);
const bool swap_face_buttons = Core::GetBaseBoolSettingValue("Main", "FullscreenUISwapGamepadFaceButtons", false);
@ -503,7 +533,7 @@ bool FullscreenUI::CreateWidgetsGPUResources(Error* error)
return false;
}
if (!CompileTransitionPipelines(error))
if (!CompilePipelines(error))
return false;
return true;
@ -511,6 +541,15 @@ bool FullscreenUI::CreateWidgetsGPUResources(Error* error)
void FullscreenUI::DestroyWidgetsGPUResources()
{
g_gpu_device->RecycleTexture(std::move(s_state.blur_source_texture));
g_gpu_device->RecycleTexture(std::move(s_state.blur_intermediate_texture));
g_gpu_device->RecycleTexture(std::move(s_state.blur_output_texture));
s_state.blur_render_pipeline.reset();
s_state.blur_apply_pipeline.reset();
s_state.present_copy_pipeline.reset();
s_state.blur_active = false;
s_state.blur_valid = false;
s_state.transition_blend_pipeline.reset();
g_gpu_device->RecycleTexture(std::move(s_state.transition_prev_texture));
g_gpu_device->RecycleTexture(std::move(s_state.transition_current_texture));
@ -520,6 +559,11 @@ void FullscreenUI::DestroyWidgetsGPUResources()
s_state.texture_cache.Clear();
}
GPUPipeline* FullscreenUI::GetPresentCopyPipeline()
{
return s_state.present_copy_pipeline.get();
}
const std::shared_ptr<GPUTexture>& FullscreenUI::GetPlaceholderTexture()
{
return s_state.placeholder_texture;
@ -823,7 +867,7 @@ GPUTexture* FullscreenUI::GetTransitionRenderTexture(GPUSwapChain* swap_chain)
return s_state.transition_current_texture.get();
}
bool FullscreenUI::CompileTransitionPipelines(Error* error)
bool FullscreenUI::CompilePipelines(Error* error)
{
const RenderAPI render_api = g_gpu_device->GetRenderAPI();
const ShaderGen shadergen(render_api, ShaderGen::GetShaderLanguageForAPI(render_api), false, false);
@ -860,6 +904,60 @@ bool FullscreenUI::CompileTransitionPipelines(Error* error)
return false;
}
fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
shadergen.GenerateCopyFragmentShader(false), error);
if (!fs)
return false;
GL_OBJECT_NAME(fs, "Copy Fragment Shader");
plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants;
plconfig.fragment_shader = fs.get();
if (!(s_state.present_copy_pipeline = g_gpu_device->CreatePipeline(plconfig, error)))
return false;
GL_OBJECT_NAME(s_state.present_copy_pipeline, "Present Copy Pipeline");
fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
shadergen.GenerateGaussianBlurFragmentShader(), error);
if (!fs)
return false;
GL_OBJECT_NAME(fs, "Blur Fragment Shader");
plconfig.fragment_shader = fs.get();
if (!(s_state.blur_render_pipeline = g_gpu_device->CreatePipeline(plconfig, error)))
return false;
GL_OBJECT_NAME(s_state.blur_render_pipeline, "Blur Render Pipeline");
vs = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(),
shadergen.GenerateImGuiBlurVertexShader(), error);
if (!vs)
return false;
GL_OBJECT_NAME(vs, "Blur Apply Vertex Shader");
fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
shadergen.GenerateImGuiBlurFragmentShader(), error);
if (!fs)
return false;
GL_OBJECT_NAME(fs, "Blur Apply Fragment Shader");
// Don't need texture coordinates, only pos+color.
static constexpr GPUPipeline::VertexAttribute imgui_attributes[] = {
GPUPipeline::VertexAttribute::Make(0, GPUPipeline::VertexAttribute::Semantic::Position, 0,
GPUPipeline::VertexAttribute::Type::Float, 2, OFFSETOF(ImDrawVert, pos)),
GPUPipeline::VertexAttribute::Make(1, GPUPipeline::VertexAttribute::Semantic::Color, 0,
GPUPipeline::VertexAttribute::Type::UNorm8, 4, OFFSETOF(ImDrawVert, col)),
};
plconfig.layout = GPUPipeline::Layout::MultiTextureAndUBOAndPushConstants; // SingleTextureAndUBOAndPushConstants
plconfig.input_layout.vertex_attributes = imgui_attributes;
plconfig.input_layout.vertex_stride = sizeof(ImDrawVert);
plconfig.blend = GPUPipeline::BlendState::GetAlphaBlendingState();
plconfig.blend.write_mask = 0x7;
plconfig.vertex_shader = vs.get();
plconfig.fragment_shader = fs.get();
s_state.blur_apply_pipeline = g_gpu_device->CreatePipeline(plconfig, error);
if (!s_state.blur_apply_pipeline)
return false;
GL_OBJECT_NAME(s_state.blur_apply_pipeline, "Blur Apply Pipeline");
return true;
}
@ -923,6 +1021,237 @@ void FullscreenUI::UpdateTransitionState()
}
}
bool FullscreenUI::CanBlurBackground()
{
// If there's no video presenter, we have no way to get the current backbuffer for blurring, so don't even try.
return VideoPresenter::HasDisplayTexture();
}
void FullscreenUI::InvalidateBlurBackground()
{
s_state.blur_valid = false;
}
GPUTexture* FullscreenUI::GetBlurRenderTexture()
{
if (!s_state.blur_active)
return nullptr;
// clear for next frame
s_state.blur_active = false;
const GPUSwapChain* const swap_chain = g_gpu_device->GetMainSwapChain();
if (!swap_chain)
return nullptr;
const u32 swap_chain_width = swap_chain->GetPostRotatedWidth();
const u32 swap_chain_height = swap_chain->GetPostRotatedHeight();
u32 blur_width, blur_height;
if ((static_cast<float>(swap_chain_width) / static_cast<float>(swap_chain_height)) >
(static_cast<float>(BLUR_TARGET_WIDTH) / static_cast<float>(BLUR_TARGET_HEIGHT)))
{
blur_width = BLUR_TARGET_WIDTH;
blur_height = static_cast<u32>(BLUR_TARGET_WIDTH * swap_chain_height / swap_chain_width);
}
else
{
blur_height = BLUR_TARGET_HEIGHT;
blur_width = static_cast<u32>(BLUR_TARGET_HEIGHT * swap_chain_width / swap_chain_height);
}
if (!s_state.blur_source_texture || s_state.blur_source_texture->GetWidth() != swap_chain_width ||
s_state.blur_source_texture->GetHeight() != swap_chain_height)
{
if (!g_gpu_device->ResizeTexture(&s_state.blur_source_texture, swap_chain_width, swap_chain_height,
GPUTexture::Type::RenderTarget, swap_chain->GetFormat(), GPUTexture::Flags::None,
false) ||
!g_gpu_device->ResizeTexture(&s_state.blur_intermediate_texture, blur_width, blur_height,
GPUTexture::Type::RenderTarget, swap_chain->GetFormat(), GPUTexture::Flags::None,
false) ||
!g_gpu_device->ResizeTexture(&s_state.blur_output_texture, blur_width, blur_height,
GPUTexture::Type::RenderTarget, swap_chain->GetFormat(), GPUTexture::Flags::None,
false))
{
ERROR_LOG("Failed to allocate {}x{}/{}x{} blur source texture.", swap_chain_width, swap_chain_height, blur_width,
blur_height);
g_gpu_device->RecycleTexture(std::move(s_state.blur_source_texture));
g_gpu_device->RecycleTexture(std::move(s_state.blur_intermediate_texture));
g_gpu_device->RecycleTexture(std::move(s_state.blur_output_texture));
return nullptr;
}
s_state.blur_texture_scale =
GSVector2(s_state.blur_output_texture->GetSizeVec()) / GSVector2(swap_chain->GetSizeVec());
s_state.blur_valid = false;
}
// skip rendering blur if it's unchanged
GL_INS_FMT("Blur active, needs render: {}, scale: {}", !s_state.blur_valid, s_state.blur_texture_scale);
return s_state.blur_valid ? nullptr : s_state.blur_source_texture.get();
}
void FullscreenUI::RenderBlur(GPUTexture* const blur_render_texture)
{
DebugAssert(s_state.blur_source_texture && s_state.blur_source_texture.get() == blur_render_texture);
DebugAssert(s_state.blur_intermediate_texture);
DebugAssert(s_state.blur_output_texture);
DebugAssert(s_state.blur_render_pipeline);
// We only blur the area of the screen where we need it for a background to save GPU cycles.
// But because the blur is over a wide range of pixels, we need to expand the are on the first pass.
static constexpr s32 BLUR_RADIUS = 30 + 1;
// Scale the active area to the downsampled texture.
const GSVector2i source_size_vec = s_state.blur_source_texture->GetSizeVec();
const GSVector2i size_vec = s_state.blur_output_texture->GetSizeVec();
const GSVector4i viewport = GSVector4i::loadh(size_vec);
const GSVector4 scaled_blur_rectf = GSVector4(s_state.blur_rect) * GSVector4::xyxy(s_state.blur_texture_scale);
const GSVector4i scaled_blur_rect = GSVector4i(scaled_blur_rectf.floor().blend32<12>(scaled_blur_rectf.ceil()));
const GSVector2 texel_size = GSVector2::cxpr(1.0f) / GSVector2(size_vec);
GL_SCOPE_FMT("RenderDisplayBlur() Rect={}, ScaledRect={}", s_state.blur_rect, scaled_blur_rect);
blur_render_texture->MakeReadyForSampling();
g_gpu_device->SetViewport(viewport);
GPUTexture* source_texture = s_state.blur_source_texture.get();
if (!source_size_vec.eq(size_vec))
{
// Downsample is pulling more pixels than the first blur.
const GSVector4i draw_rect =
scaled_blur_rect.add32(GSVector4i::cxpr(-BLUR_RADIUS * 2, -BLUR_RADIUS * 2, BLUR_RADIUS * 2, BLUR_RADIUS * 2))
.rintersect(viewport);
GSVector4 uv_rect = GSVector4(draw_rect) * GSVector4::xyxy(texel_size);
if (g_gpu_device->UsesLowerLeftOrigin())
uv_rect = uv_rect.blend32<10>(GSVector4::cxpr(1.0f) - uv_rect);
GL_SCOPE_FMT("Downsample Framebuffer: Rect={}", draw_rect);
g_gpu_device->InvalidateRenderTarget(s_state.blur_output_texture.get());
g_gpu_device->SetRenderTarget(s_state.blur_output_texture.get());
g_gpu_device->SetPipeline(s_state.present_copy_pipeline.get());
g_gpu_device->SetTextureSampler(0, blur_render_texture, g_gpu_device->GetLinearSampler());
VideoPresenter::DrawScreenQuad(draw_rect, uv_rect, size_vec, size_vec, DisplayRotation::Normal,
WindowInfoPrerotation::Identity, nullptr, 0);
s_state.blur_output_texture->MakeReadyForSampling();
source_texture = s_state.blur_output_texture.get();
}
g_gpu_device->SetPipeline(s_state.blur_render_pipeline.get());
// We run the blur at a reduced number of taps but multiple passes for a stronger effect.
// Two full passes (H+V, H+V) gives a visually pleasing result.
{
// First pass needs to sample a wider range, since we'll be pulling from this for the vertical pass.
const GSVector4i draw_rect =
scaled_blur_rect.add32(GSVector4i::cxpr(-BLUR_RADIUS, -BLUR_RADIUS, BLUR_RADIUS, BLUR_RADIUS))
.rintersect(viewport);
GSVector4 uv_rect = GSVector4(draw_rect) * GSVector4::xyxy(texel_size);
if (g_gpu_device->UsesLowerLeftOrigin())
uv_rect = uv_rect.blend32<10>(GSVector4::cxpr(1.0f) - uv_rect);
GL_SCOPE_FMT("Horizontal Blur: Rect={}", draw_rect);
s_state.blur_source_texture->MakeReadyForSampling();
g_gpu_device->InvalidateRenderTarget(s_state.blur_intermediate_texture.get());
g_gpu_device->SetRenderTarget(s_state.blur_intermediate_texture.get());
g_gpu_device->SetTextureSampler(0, source_texture, g_gpu_device->GetLinearSampler());
const GSVector2 uniforms = texel_size.insert32<1, 1>(GSVector2::zero());
VideoPresenter::DrawScreenQuad(draw_rect, uv_rect, size_vec, size_vec, DisplayRotation::Normal,
WindowInfoPrerotation::Identity, &uniforms, sizeof(uniforms));
}
{
// Second pass can be clamped to the actual sampled area. Add one for bilinear filtering.
const GSVector4i draw_rect = scaled_blur_rect.add32(GSVector4i::cxpr(-1, -1, 1, 1)).rintersect(viewport);
GSVector4 uv_rect = GSVector4(draw_rect) * GSVector4::xyxy(texel_size);
if (g_gpu_device->UsesLowerLeftOrigin())
uv_rect = uv_rect.blend32<10>(GSVector4::cxpr(1.0f) - uv_rect);
GL_SCOPE_FMT("Vertical Blur: Rect={}", draw_rect);
s_state.blur_intermediate_texture->MakeReadyForSampling();
g_gpu_device->InvalidateRenderTarget(s_state.blur_output_texture.get());
g_gpu_device->SetRenderTarget(s_state.blur_output_texture.get());
g_gpu_device->SetTextureSampler(0, s_state.blur_intermediate_texture.get(), g_gpu_device->GetLinearSampler());
const GSVector2 uniforms = texel_size.insert32<0, 0>(GSVector2::zero());
VideoPresenter::DrawScreenQuad(draw_rect, uv_rect, size_vec, size_vec, DisplayRotation::Normal,
WindowInfoPrerotation::Identity, &uniforms, sizeof(uniforms));
}
s_state.blur_output_texture->MakeReadyForSampling();
s_state.blur_valid = true;
}
bool FullscreenUI::BeginBlurBackground(ImDrawList* const dl, const ImVec2& bb_min, const ImVec2& bb_max)
{
if (!CanBlurBackground())
return false;
const GSVector4i bounds =
GSVector4i(GSVector4::xyxy(GSVector2::load<false>(&bb_min.x).floor(), GSVector2::load<false>(&bb_max.x).ceil()));
if (s_state.blur_valid)
{
// If the rectangle gets larger, invalidate it.
const GSVector4i prev_rect = s_state.blur_rect;
s_state.blur_rect = s_state.blur_rect.runion(bounds);
s_state.blur_valid &= s_state.blur_rect.eq(prev_rect);
}
s_state.blur_rect = (s_state.blur_active || s_state.blur_valid) ? s_state.blur_rect.runion(bounds) : bounds;
s_state.blur_active = true;
// should flush the current draw, if any
ImDrawCmd* curr_cmd = &dl->CmdBuffer.Data[dl->CmdBuffer.Size - 1];
if (curr_cmd->ElemCount > 0)
{
dl->AddDrawCmd();
curr_cmd = &dl->CmdBuffer.Data[dl->CmdBuffer.Size - 1];
}
// mark it so we can check in EndPopBackgroundTexture
DebugAssert(curr_cmd->ElemCount == 0);
curr_cmd->UserCallback = &DrawWithBlurTexture;
return true;
}
void FullscreenUI::EndBlurBackground(ImDrawList* const dl)
{
ImDrawCmd* const curr_cmd = &dl->CmdBuffer.Data[dl->CmdBuffer.Size - 1];
DebugAssert(curr_cmd->UserCallback == &DrawWithBlurTexture);
// allow imgui to clear empty draw commands
if (curr_cmd->ElemCount == 0)
{
GL_INS("No vertices drawn with blur background, skipping.");
curr_cmd->UserCallback = nullptr;
}
else
{
// flush the blurred draw
dl->AddDrawCmd();
}
}
void FullscreenUI::DrawWithBlurTexture(const ImDrawList* parent_list, const ImDrawCmd* cmd, u32 base_vertex,
u32 base_index)
{
struct Uniforms
{
float blur_texture_scale[2];
float blur_background_weight;
float inv_blur_background_weight;
} uniforms;
uniforms.blur_background_weight = FullscreenUI::UIStyle.BlurBackgroundWeight;
uniforms.inv_blur_background_weight = 1.0f - FullscreenUI::UIStyle.BlurBackgroundWeight;
GSVector2::store<true>(uniforms.blur_texture_scale, s_state.blur_texture_scale);
g_gpu_device->SetPipeline(s_state.blur_apply_pipeline.get());
g_gpu_device->SetTextureSampler(0, s_state.blur_output_texture.get(), g_gpu_device->GetLinearSampler());
g_gpu_device->DrawIndexedWithPushConstants(cmd->ElemCount, base_index + cmd->IdxOffset, base_vertex + cmd->VtxOffset,
&uniforms, sizeof(uniforms));
}
bool FullscreenUI::UpdateLayoutScale()
{
#ifndef __ANDROID__
@ -1067,6 +1396,7 @@ void FullscreenUI::BeginLayout()
// we evict from the texture cache at the start of the frame, in case we go over mid-frame,
// we need to keep all those textures alive until the end of the frame
s_state.texture_cache.ManualEvict();
s_state.fullscreen_footer_blur_allowed = true;
PushResetLayout();
}
@ -1113,7 +1443,7 @@ void FullscreenUI::EndLayout()
void FullscreenUI::EnqueueSoundEffect(std::string_view sound_effect)
{
if (s_state.had_sound_effect || !s_state.sound_effects_enabled)
if (s_state.had_sound_effect || !UIStyle.SoundEffects)
return;
SoundEffectManager::EnqueueSoundEffect(sound_effect);
@ -1499,7 +1829,7 @@ bool FullscreenUI::BeginFullscreenWindow(float left, float top, float width, flo
bool FullscreenUI::BeginFullscreenWindow(const ImVec2& position, const ImVec2& size, const char* name,
const ImVec4& background /* = HEX_TO_IMVEC4(0x212121, 0xFF) */,
float rounding /*= 0.0f*/, const ImVec2& padding /*= 0.0f*/,
ImGuiWindowFlags flags /*= 0*/)
ImGuiWindowFlags flags /*= 0*/, bool blur /*= false*/)
{
ImGui::SetNextWindowPos(position);
ImGui::SetNextWindowSize(size);
@ -1509,10 +1839,33 @@ bool FullscreenUI::BeginFullscreenWindow(const ImVec2& position, const ImVec2& s
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
return ImGui::Begin(name, nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoBringToFrontOnFocus |
((background.w == 0.0f) ? ImGuiWindowFlags_NoBackground : 0) | flags);
const bool actually_blur = (blur && UIStyle.BlurMenuBackground && CanBlurBackground());
const bool has_background = (background.w != 0.0f);
const bool res = ImGui::Begin(name, nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoBringToFrontOnFocus |
((!has_background || actually_blur) ? ImGuiWindowFlags_NoBackground : 0) | flags);
if (res && actually_blur)
{
ImDrawList* const dl = ImGui::GetWindowDrawList();
const ImVec2 bg_min = position;
const ImVec2 bg_max = position + size;
if (BeginBlurBackground(dl, bg_min, bg_max))
{
if (has_background)
dl->AddRectFilled(bg_min, bg_max,
ImGui::GetColorU32(ImVec4(background.x * background.w, background.y * background.w,
background.z * background.w, 1.0f)));
EndBlurBackground(dl);
}
else if (has_background)
{
dl->AddRectFilled(bg_min, bg_max, ImGui::GetColorU32(background));
}
}
return res;
}
void FullscreenUI::EndFullscreenWindow(bool allow_wrap_x, bool allow_wrap_y)
@ -1603,6 +1956,11 @@ void FullscreenUI::SetStandardSelectionFooterText(bool back_instead_of_cancel)
}
}
void FullscreenUI::SetFullscreenFooterBlur(bool allowed)
{
s_state.fullscreen_footer_blur_allowed = allowed;
}
void FullscreenUI::DrawFullscreenFooter()
{
const ImGuiIO& io = ImGui::GetIO();
@ -1621,9 +1979,20 @@ void FullscreenUI::DrawFullscreenFooter()
const u32 text_color = ImGui::GetColorU32(UIStyle.PrimaryTextColor);
const float bg_alpha = GetBackgroundAlpha();
ImDrawList* dl = ImGui::GetForegroundDrawList();
dl->AddRectFilled(ImVec2(0.0f, io.DisplaySize.y - height), io.DisplaySize,
ImGui::GetColorU32(ModAlpha(UIStyle.PrimaryColor, bg_alpha)), 0.0f);
ImDrawList* const dl = ImGui::GetForegroundDrawList();
const ImVec2 bb_min = ImVec2(0.0f, io.DisplaySize.y - height);
const ImVec2& bb_max = io.DisplaySize;
if (UIStyle.BlurMenuBackground && s_state.fullscreen_footer_blur_allowed && BeginBlurBackground(dl, bb_min, bb_max))
{
dl->AddRectFilled(ImVec2(0.0f, io.DisplaySize.y - height), io.DisplaySize,
ImGui::GetColorU32(ModAlpha(UIStyle.PrimaryColor, 1.0f)), 0.0f);
EndBlurBackground(dl);
}
else
{
dl->AddRectFilled(ImVec2(0.0f, io.DisplaySize.y - height), io.DisplaySize,
ImGui::GetColorU32(ModAlpha(UIStyle.PrimaryColor, bg_alpha)), 0.0f);
}
ImFont* const font = UIStyle.Font;
const float font_size = UIStyle.MediumFontSize;
@ -5152,6 +5521,17 @@ void FullscreenUI::DrawLoadingScreen(std::string_view image, std::string_view ti
const ImVec2 image_pos =
ImVec2(ImCeil((io.DisplaySize.x - image_width) * 0.5f), ImCeil(((io.DisplaySize.y - total_height) * 0.5f)));
ImDrawList* const dl = ImGui::GetBackgroundDrawList();
if (UIStyle.BlurMenuBackground && BeginBlurBackground(dl, ImVec2(), io.DisplaySize))
{
dl->AddRectFilled(ImVec2(), io.DisplaySize, ImGui::GetColorU32(ModAlpha(UIStyle.BackgroundColor, 0.9f)));
EndBlurBackground(dl);
}
else if (VideoPresenter::HasDisplayTexture())
{
dl->AddRectFilled(ImVec2(), io.DisplaySize, ImGui::GetColorU32(ModAlpha(UIStyle.BackgroundColor, 0.9f)));
}
GPUTexture* tex = GetCachedTexture(image);
if (tex)
{
@ -5502,7 +5882,7 @@ std::pair<ImVec2, float> FullscreenUI::NotificationLayout::GetNextPosition(float
ImVec2 pos;
if (m_location == NotificationLocation::TopLeft || m_location == NotificationLocation::BottomLeft)
{
if (anim_coeff != 1.0f)
if (anim_coeff != 1.0f && g_gpu_settings.display_animate_messages)
{
if (active)
{
@ -5526,7 +5906,7 @@ std::pair<ImVec2, float> FullscreenUI::NotificationLayout::GetNextPosition(float
else
{
pos.x = m_current_position.x - width;
if (anim_coeff != 1.0f)
if (anim_coeff != 1.0f && g_gpu_settings.display_animate_messages)
{
if (active)
{
@ -5568,7 +5948,7 @@ std::pair<ImVec2, float> FullscreenUI::NotificationLayout::GetNextPosition(float
pos.x = ImFloor((ImGui::GetIO().DisplaySize.x - width) * 0.5f);
pos.y = m_current_position.y;
if (anim_coeff != 1.0f)
if (anim_coeff != 1.0f && g_gpu_settings.display_animate_messages)
{
if (active)
{
@ -5602,7 +5982,7 @@ std::pair<ImVec2, float> FullscreenUI::NotificationLayout::GetNextPosition(float
pos.x = ImFloor((ImGui::GetIO().DisplaySize.x - width) * 0.5f);
pos.y = m_current_position.y - height;
if (anim_coeff != 1.0f)
if (anim_coeff != 1.0f && g_gpu_settings.display_animate_messages)
{
if (active)
{
@ -5786,6 +6166,7 @@ void FullscreenUI::UpdateTheme()
UIStyle.ToastBackgroundColor = HEX_TO_IMVEC4(0x282828, 0xff);
UIStyle.ToastTextColor = HEX_TO_IMVEC4(0xffffff, 0xff);
UIStyle.ShadowColor = IM_COL32(0, 0, 0, 100);
UIStyle.BlurBackgroundWeight = 0.25f;
UIStyle.IsDarkTheme = true;
}
else if (theme == "CobaltSky")
@ -5810,6 +6191,7 @@ void FullscreenUI::UpdateTheme()
UIStyle.ToastBackgroundColor = HEX_TO_IMVEC4(0x2d4183, 0xff);
UIStyle.ToastTextColor = HEX_TO_IMVEC4(0xffffff, 0xff);
UIStyle.ShadowColor = IM_COL32(0, 0, 0, 100);
UIStyle.BlurBackgroundWeight = 0.25f;
UIStyle.IsDarkTheme = true;
}
else if (theme == "GreyMatter")
@ -5834,6 +6216,7 @@ void FullscreenUI::UpdateTheme()
UIStyle.ToastBackgroundColor = HEX_TO_IMVEC4(0x282828, 0xff);
UIStyle.ToastTextColor = HEX_TO_IMVEC4(0xffffff, 0xff);
UIStyle.ShadowColor = IM_COL32(0, 0, 0, 100);
UIStyle.BlurBackgroundWeight = 0.25f;
UIStyle.IsDarkTheme = true;
}
else if (theme == "PinkyPals")
@ -5858,6 +6241,7 @@ void FullscreenUI::UpdateTheme()
UIStyle.ToastBackgroundColor = HEX_TO_IMVEC4(0xd86a66, 0xff);
UIStyle.ToastTextColor = HEX_TO_IMVEC4(0xffffff, 0xff);
UIStyle.ShadowColor = IM_COL32(100, 100, 100, 50);
UIStyle.BlurBackgroundWeight = 0.5f;
UIStyle.IsDarkTheme = false;
}
else if (theme == "GreenGiant")
@ -5881,6 +6265,7 @@ void FullscreenUI::UpdateTheme()
UIStyle.ToastBackgroundColor = HEX_TO_IMVEC4(0xD5DE2E, 0xff);
UIStyle.ToastTextColor = HEX_TO_IMVEC4(0x000000, 0xff);
UIStyle.ShadowColor = IM_COL32(100, 100, 100, 50);
UIStyle.BlurBackgroundWeight = 0.25f;
UIStyle.IsDarkTheme = false;
}
else if (theme == "DarkRuby")
@ -5905,6 +6290,7 @@ void FullscreenUI::UpdateTheme()
UIStyle.ToastBackgroundColor = HEX_TO_IMVEC4(0x282828, 0xff);
UIStyle.ToastTextColor = HEX_TO_IMVEC4(0xffffff, 0xff);
UIStyle.ShadowColor = IM_COL32(0, 0, 0, 100);
UIStyle.BlurBackgroundWeight = 0.25f;
UIStyle.IsDarkTheme = true;
}
else if (theme == "PurpleRain")
@ -5929,6 +6315,7 @@ void FullscreenUI::UpdateTheme()
UIStyle.ToastBackgroundColor = HEX_TO_IMVEC4(0x8e65cb, 0xff);
UIStyle.ToastTextColor = HEX_TO_IMVEC4(0xffffff, 0xff);
UIStyle.ShadowColor = IM_COL32(100, 100, 100, 50);
UIStyle.BlurBackgroundWeight = 0.25f;
UIStyle.IsDarkTheme = true;
}
else if (theme == "Light")
@ -5954,6 +6341,7 @@ void FullscreenUI::UpdateTheme()
UIStyle.ToastBackgroundColor = HEX_TO_IMVEC4(0xf1f1f1, 0xff);
UIStyle.ToastTextColor = HEX_TO_IMVEC4(0x000000, 0xff);
UIStyle.ShadowColor = IM_COL32(100, 100, 100, 50);
UIStyle.BlurBackgroundWeight = 0.5f;
UIStyle.IsDarkTheme = false;
}
else
@ -5979,6 +6367,7 @@ void FullscreenUI::UpdateTheme()
UIStyle.ToastBackgroundColor = HEX_TO_IMVEC4(0x282828, 0xff);
UIStyle.ToastTextColor = HEX_TO_IMVEC4(0xffffff, 0xff);
UIStyle.ShadowColor = IM_COL32(0, 0, 0, 100);
UIStyle.BlurBackgroundWeight = 0.25f;
UIStyle.IsDarkTheme = true;
}
}

@ -28,9 +28,12 @@
class Error;
class Image;
class ProgressCallbackWithPrompt;
enum class GPUPresentResult : u8;
class GPUPipeline;
class GPUTexture;
class GPUSwapChain;
class ProgressCallbackWithPrompt;
enum class OSDMessageType : u8;
@ -108,14 +111,17 @@ struct ALIGN_TO_CACHE_LINE UIStyles
float MediumFontSize;
float MediumLargeFontSize;
float MediumSmallFontSize;
float BlurBackgroundWeight;
static constexpr float NormalFontWeight = 0.0f;
static constexpr float BoldFontWeight = 500.0f;
bool Animations;
bool SmoothScrolling;
bool MenuBorders;
bool IsDarkTheme;
bool Animations : 1;
bool SmoothScrolling : 1;
bool MenuBorders : 1;
bool BlurMenuBackground : 1;
bool SoundEffects : 1;
bool IsDarkTheme : 1;
};
extern UIStyles UIStyle;
@ -229,6 +235,7 @@ void UpdateWidgetsSettings();
bool CreateWidgetsGPUResources(Error* error);
void DestroyWidgetsGPUResources();
GPUPipeline* GetPresentCopyPipeline();
std::span<const char* const> GetThemeNames();
std::span<const char* const> GetThemeDisplayNames();
@ -273,6 +280,14 @@ GPUTexture* GetTransitionRenderTexture(GPUSwapChain* swap_chain);
void RenderTransitionBlend(GPUSwapChain* swap_chain);
void UpdateTransitionState();
/// Screen blurring.
bool CanBlurBackground();
void InvalidateBlurBackground();
GPUTexture* GetBlurRenderTexture();
void RenderBlur(GPUTexture* const blur_render_texture);
bool BeginBlurBackground(ImDrawList* const dl, const ImVec2& bb_min, const ImVec2& bb_max);
void EndBlurBackground(ImDrawList* const dl);
/// Layout helpers.
void BeginLayout();
void EndLayout();
@ -341,7 +356,7 @@ bool BeginFullscreenWindow(float left, float top, float width, float height, con
const ImVec2& padding = ImVec2(), ImGuiWindowFlags flags = 0);
bool BeginFullscreenWindow(const ImVec2& position, const ImVec2& size, const char* name,
const ImVec4& background = HEX_TO_IMVEC4(0x212121, 0xFF), float rounding = 0.0f,
const ImVec2& padding = ImVec2(), ImGuiWindowFlags flags = 0);
const ImVec2& padding = ImVec2(), ImGuiWindowFlags flags = 0, bool blur = false);
void EndFullscreenWindow(bool allow_wrap_x = false, bool allow_wrap_y = true);
void SetWindowNavWrapping(bool allow_wrap_x = false, bool allow_wrap_y = true);
@ -350,6 +365,7 @@ std::string_view GetControllerIconMapping(std::string_view icon);
void SetFullscreenFooterText(std::string_view text);
void SetFullscreenFooterText(std::span<const std::pair<const char*, std::string_view>> items);
void SetStandardSelectionFooterText(bool back_instead_of_cancel);
void SetFullscreenFooterBlur(bool allowed);
void SetFullscreenStatusText(std::string_view text);
void SetFullscreenStatusText(std::span<const std::pair<const char*, std::string_view>> items);
void DrawFullscreenFooter();

@ -374,6 +374,8 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
display_line_start_offset = static_cast<s8>(si.GetIntValue("Display", "LineStartOffset", 0));
display_line_end_offset = static_cast<s8>(si.GetIntValue("Display", "LineEndOffset", 0));
display_show_messages = si.GetBoolValue("Display", "ShowOSDMessages", true);
display_animate_messages = si.GetBoolValue("Display", "AnimateOSDMessages", true);
display_blur_message_backgrounds = si.GetBoolValue("Display", "BlurOSDMessageBackgrounds", true);
display_show_fps = si.GetBoolValue("Display", "ShowFPS", false);
display_show_speed = si.GetBoolValue("Display", "ShowSpeed", false);
display_show_gpu_stats = si.GetBoolValue("Display", "ShowGPUStatistics", false);
@ -732,6 +734,8 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
if (!ignore_base)
{
si.SetBoolValue("Display", "ShowOSDMessages", display_show_messages);
si.SetBoolValue("Display", "AnimateOSDMessages", display_animate_messages);
si.SetBoolValue("Display", "BlurOSDMessageBackgrounds", display_blur_message_backgrounds);
si.SetBoolValue("Display", "ShowFPS", display_show_fps);
si.SetBoolValue("Display", "ShowSpeed", display_show_speed);
si.SetBoolValue("Display", "ShowResolution", display_show_resolution);

@ -106,6 +106,8 @@ struct GPUSettings
bool display_force_4_3_for_24bit : 1 = false;
bool display_24bit_chroma_smoothing : 1 = false;
bool display_show_messages : 1 = true;
bool display_animate_messages : 1 = true;
bool display_blur_message_backgrounds : 1 = true;
bool display_show_fps : 1 = false;
bool display_show_speed : 1 = false;
bool display_show_gpu_stats : 1 = false;

@ -4816,6 +4816,8 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
}
else if (const bool device_settings_changed = g_settings.AreGPUDeviceSettingsChanged(old_settings);
device_settings_changed || g_settings.display_show_messages != old_settings.display_show_messages ||
g_settings.display_animate_messages != old_settings.display_animate_messages ||
g_settings.display_blur_message_backgrounds != old_settings.display_blur_message_backgrounds ||
g_settings.display_show_fps != old_settings.display_show_fps ||
g_settings.display_show_speed != old_settings.display_show_speed ||
g_settings.display_show_gpu_stats != old_settings.display_show_gpu_stats ||

@ -67,6 +67,8 @@ static GPUTexture* GetDisplayPostProcessInputTexture(const GSVector4i source_rec
DisplayRotation rotation);
static GPUPresentResult ApplyDisplayPostProcess(GPUTexture* target, GPUTexture* input, const GSVector4i display_rect,
const GSVector2i postfx_size);
static GPUPresentResult DrawDisplayCopy(GPUTexture* const source, GPUTexture* const target,
GPUSwapChain* const swap_chain);
static bool DeinterlaceSetTargetSize(u32 width, u32 height, bool preserve);
static void DestroyDeinterlaceTextures();
@ -111,8 +113,6 @@ struct Locals
bool border_overlay_alpha_blend = false;
bool border_overlay_destination_alpha_blend = false;
std::unique_ptr<GPUPipeline> present_copy_pipeline;
std::unique_ptr<PostProcessing::Chain> display_postfx;
std::unique_ptr<GPUTexture> border_overlay_texture;
@ -151,7 +151,6 @@ VideoPresenter::Locals::~Locals()
DebugAssert(!display_pipeline);
DebugAssert(!display_24bit_pipeline);
DebugAssert(!display_texture);
DebugAssert(!present_copy_blend_pipeline);
DebugAssert(!display_postfx);
DebugAssert(!border_overlay_texture);
DebugAssert(!border_overlay_pipeline);
@ -167,14 +166,17 @@ const GSVector2i& VideoPresenter::GetVideoSize()
{
return s_locals.video_size;
}
GPUTexture* VideoPresenter::GetDisplayTexture()
{
return s_locals.display_texture;
}
const GSVector4i& VideoPresenter::GetDisplayTextureRect()
{
return s_locals.display_texture_rect;
}
bool VideoPresenter::HasDisplayTexture()
{
return s_locals.display_texture;
@ -214,6 +216,8 @@ bool VideoPresenter::Initialize(Error* error)
void VideoPresenter::Shutdown()
{
FullscreenUI::InvalidateBlurBackground();
DestroyDeinterlaceTextures();
g_gpu_device->RecycleTexture(std::move(s_locals.chroma_smoothing_texture));
g_gpu_device->RecycleTexture(std::move(s_locals.border_overlay_texture));
@ -236,8 +240,6 @@ void VideoPresenter::Shutdown()
s_locals.border_overlay_alpha_blend = false;
s_locals.border_overlay_destination_alpha_blend = false;
s_locals.present_copy_pipeline.reset();
s_locals.display_postfx.reset();
s_locals.border_overlay_pipeline.reset();
@ -367,27 +369,21 @@ bool VideoPresenter::CompileDisplayPipelines(bool display, bool deinterlace, boo
s_locals.display_24bit_pipeline.reset();
}
std::unique_ptr<GPUShader> copy_fso = g_gpu_device->CreateShader(
GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GenerateCopyFragmentShader(false), error);
if (!copy_fso)
return false;
GL_OBJECT_NAME(copy_fso, "Display Copy Fragment Shader");
plconfig.fragment_shader = copy_fso.get();
if (!(s_locals.present_copy_pipeline = g_gpu_device->CreatePipeline(plconfig, error)))
return false;
GL_OBJECT_NAME(s_locals.present_copy_pipeline, "Display Copy Pipeline");
// blended variants
if (s_locals.border_overlay_texture)
{
std::unique_ptr<GPUShader> clear_fso = g_gpu_device->CreateShader(
const std::unique_ptr<GPUShader> clear_fso = g_gpu_device->CreateShader(
GPUShaderStage::Fragment, shadergen.GetLanguage(),
shadergen.GenerateFillFragmentShader(GSVector4::cxpr(0.0f, 0.0f, 0.0f, 1.0f)), error);
if (!clear_fso)
return false;
GL_OBJECT_NAME(clear_fso, "Display Clear Fragment Shader");
const std::unique_ptr<GPUShader> copy_fso = g_gpu_device->CreateShader(
GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GenerateCopyFragmentShader(false), error);
if (!copy_fso)
return false;
GL_OBJECT_NAME(copy_fso, "Display Copy Fragment Shader");
plconfig.fragment_shader = copy_fso.get();
plconfig.blend = s_locals.border_overlay_alpha_blend ? GPUPipeline::BlendState::GetAlphaBlendingState() :
GPUPipeline::BlendState::GetNoBlendingState();
@ -572,6 +568,7 @@ void VideoPresenter::ClearDisplayTexture()
{
s_locals.display_texture = nullptr;
s_locals.display_texture_rect = GSVector4i::zero();
FullscreenUI::InvalidateBlurBackground();
}
void VideoPresenter::SetDisplayParameters(const GSVector2i& video_size, const GSVector4i& video_active_rect,
@ -581,6 +578,7 @@ void VideoPresenter::SetDisplayParameters(const GSVector2i& video_size, const GS
s_locals.video_active_rect = video_active_rect;
s_locals.display_pixel_aspect_ratio = display_pixel_aspect_ratio;
s_locals.display_texture_24bit = display_24bit;
FullscreenUI::InvalidateBlurBackground();
}
void VideoPresenter::SetDisplayTexture(GPUTexture* texture, const GSVector4i& source_rect)
@ -592,6 +590,7 @@ void VideoPresenter::SetDisplayTexture(GPUTexture* texture, const GSVector4i& so
s_locals.display_texture = texture;
s_locals.display_texture_rect = source_rect;
FullscreenUI::InvalidateBlurBackground();
}
GPUPresentResult VideoPresenter::RenderDisplay(GPUTexture* target, const GSVector2i target_size, bool postfx,
@ -777,7 +776,7 @@ GPUPresentResult VideoPresenter::RenderDisplay(GPUTexture* target, const GSVecto
{
// Otherwise, just copy the framebuffer.
GL_SCOPE_FMT("Copy framebuffer for prerotation");
g_gpu_device->SetPipeline(s_locals.present_copy_pipeline.get());
g_gpu_device->SetPipeline(FullscreenUI::GetPresentCopyPipeline());
g_gpu_device->SetTextureSampler(0, postfx_output, g_gpu_device->GetNearestSampler());
DrawScreenQuad(GSVector4i::loadh(target_size), src_uv_rect, target_size, final_target_size, present_rotation,
prerotation, nullptr, 0);
@ -1104,7 +1103,7 @@ GPUTexture* VideoPresenter::GetDisplayPostProcessInputTexture(const GSVector4i s
g_gpu_device->ClearRenderTarget(postfx_input, GPUDevice::DEFAULT_CLEAR_COLOR);
g_gpu_device->SetRenderTarget(postfx_input);
g_gpu_device->SetViewportAndScissor(GSVector4i::loadh(postfx_input_size));
g_gpu_device->SetPipeline(s_locals.present_copy_pipeline.get());
g_gpu_device->SetPipeline(FullscreenUI::GetPresentCopyPipeline());
g_gpu_device->SetTextureSampler(0, s_locals.display_texture, g_gpu_device->GetNearestSampler());
DrawScreenQuad(input_draw_rect, src_uv_rect, postfx_input_size, postfx_input_size, rotation,
WindowInfoPrerotation::Identity, nullptr, 0);
@ -1145,6 +1144,35 @@ GPUPresentResult VideoPresenter::ApplyDisplayPostProcess(GPUTexture* target, GPU
s_locals.video_size.y);
}
GPUPresentResult VideoPresenter::DrawDisplayCopy(GPUTexture* const source, GPUTexture* const target,
GPUSwapChain* const swap_chain)
{
// write direct to final target
if (target)
{
g_gpu_device->InvalidateRenderTarget(target);
g_gpu_device->SetRenderTarget(target);
}
else
{
if (const GPUPresentResult pres = g_gpu_device->BeginPresent(swap_chain); pres != GPUPresentResult::OK)
return pres;
}
const WindowInfoPrerotation prerotation = target ? WindowInfoPrerotation::Identity : swap_chain->GetPreRotation();
const GSVector2i final_target_size = target ? target->GetSizeVec() : swap_chain->GetPostRotatedSizeVec();
const GSVector2i target_size = target ? target->GetSizeVec() : swap_chain->GetSizeVec();
const GSVector4i rect = GSVector4i::loadh(target_size);
const GSVector4 uv_rect = g_gpu_device->UsesLowerLeftOrigin() ? GSVector4::cxpr(0.0f, 1.0f, 1.0f, 0.0f) :
GSVector4::cxpr(0.0f, 0.0f, 1.0f, 1.0f);
g_gpu_device->SetViewportAndScissor(rect);
g_gpu_device->SetTextureSampler(0, source, g_gpu_device->GetNearestSampler());
g_gpu_device->SetPipeline(FullscreenUI::GetPresentCopyPipeline());
DrawScreenQuad(rect, uv_rect, target_size, final_target_size, DisplayRotation::Normal, prerotation, nullptr, 0);
return GPUPresentResult::OK;
}
void VideoPresenter::SendDisplayToMediaCapture(MediaCapture* cap)
{
GPUTexture* target = cap->GetRenderTexture();
@ -1479,35 +1507,46 @@ bool VideoPresenter::PresentFrame(GPUBackend* backend, u64 present_time)
ImGuiManager::CreateDrawLists();
// render offscreen for transitions
if (FullscreenUI::IsTransitionActive())
{
GPUTexture* const rtex = FullscreenUI::GetTransitionRenderTexture(g_gpu_device->GetMainSwapChain());
if (rtex)
{
if (backend)
RenderDisplay(rtex, rtex->GetSizeVec(), true, true);
else
g_gpu_device->ClearRenderTarget(rtex, GPUDevice::DEFAULT_CLEAR_COLOR);
g_gpu_device->SetRenderTarget(rtex);
ImGuiManager::RenderDrawLists(rtex);
}
}
GPUSwapChain* const swap_chain = g_gpu_device->GetMainSwapChain();
DebugAssert(swap_chain);
const GPUPresentResult pres =
((backend && !FullscreenUI::IsTransitionActive()) ? RenderDisplay(nullptr, swap_chain->GetSizeVec(), true, true) :
g_gpu_device->BeginPresent(swap_chain));
if (pres == GPUPresentResult::OK)
GPUTexture* const blur_target = FullscreenUI::GetBlurRenderTexture();
if (blur_target)
{
if (FullscreenUI::IsTransitionActive())
FullscreenUI::RenderTransitionBlend(swap_chain);
RenderDisplay(blur_target, blur_target->GetSizeVec(), true, true);
FullscreenUI::RenderBlur(blur_target);
}
GPUTexture* const transition_target = FullscreenUI::IsTransitionActive() ?
FullscreenUI::GetTransitionRenderTexture(g_gpu_device->GetMainSwapChain()) :
nullptr;
GPUPresentResult pres;
if (transition_target)
{
if (blur_target)
DrawDisplayCopy(blur_target, transition_target, swap_chain);
else if (backend)
RenderDisplay(transition_target, transition_target->GetSizeVec(), true, true);
else
g_gpu_device->ClearRenderTarget(transition_target, GPUDevice::DEFAULT_CLEAR_COLOR);
g_gpu_device->SetRenderTarget(transition_target);
ImGuiManager::RenderDrawLists(transition_target);
if ((pres = g_gpu_device->BeginPresent(swap_chain)) == GPUPresentResult::OK)
FullscreenUI::RenderTransitionBlend(swap_chain);
}
else
{
if ((pres = blur_target ? DrawDisplayCopy(blur_target, nullptr, swap_chain) :
RenderDisplay(nullptr, swap_chain->GetSizeVec(), true, true)) == GPUPresentResult::OK)
{
ImGuiManager::RenderDrawLists(swap_chain);
}
}
if (pres == GPUPresentResult::OK)
{
const GPUDevice::Features features = g_gpu_device->GetFeatures();
const bool scheduled_present = (present_time != 0);
const bool explicit_present = (scheduled_present && (features.explicit_present && !features.timed_present));

@ -30,6 +30,9 @@ OSDSettingsWidget::OSDSettingsWidget(SettingsWindow* dialog, QWidget* parent) :
&Settings::GetNotificationLocationName, &Settings::GetNotificationLocationDisplayName,
Settings::DEFAULT_OSD_MESSAGE_LOCATION, NotificationLocation::MaxCount);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showMessages, "Display", "ShowOSDMessages", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.animateMessages, "Display", "AnimateOSDMessages", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.blurMessageBackgrounds, "Display", "BlurOSDMessageBackgrounds",
true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showFPS, "Display", "ShowFPS", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showSpeed, "Display", "ShowSpeed", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showResolution, "Display", "ShowResolution", false);
@ -77,6 +80,11 @@ OSDSettingsWidget::OSDSettingsWidget(SettingsWindow* dialog, QWidget* parent) :
m_ui.showMessages, tr("Show Messages"), tr("Checked"),
tr("Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being "
"taken, etc. Errors and warnings are still displayed regardless of this setting."));
dialog->registerWidgetHelp(m_ui.animateMessages, tr("Animate Messages"), tr("Checked"),
tr("Enables animation for on-screen messages when they appear and disappear."));
dialog->registerWidgetHelp(
m_ui.blurMessageBackgrounds, tr("Blur Message Backgrounds"), tr("Checked"),
tr("Enables a blur effect on the background behind on-screen messages to improve readability."));
dialog->registerWidgetHelp(m_ui.showResolution, tr("Show Resolution"), tr("Unchecked"),
tr("Shows the resolution of the game in the top-right corner of the display."));
dialog->registerWidgetHelp(

@ -102,14 +102,28 @@
</property>
</widget>
</item>
<item row="1" column="0">
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="animateMessages">
<property name="text">
<string>Animate Messages</string>
</property>
</widget>
</item>
<item row="1" column="2" colspan="2">
<widget class="QCheckBox" name="blurMessageBackgrounds">
<property name="text">
<string>Blur Message Backgrounds</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="osdErrorDurationLabel">
<property name="text">
<string>Error Duration:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="osdErrorDuration">
<property name="suffix">
<string> seconds</string>
@ -128,14 +142,14 @@
</property>
</widget>
</item>
<item row="1" column="2">
<item row="2" column="2">
<widget class="QLabel" name="osdWarningDurationLabel">
<property name="text">
<string>Warning Duration:</string>
</property>
</widget>
</item>
<item row="1" column="3">
<item row="2" column="3">
<widget class="QDoubleSpinBox" name="osdWarningDuration">
<property name="suffix">
<string> seconds</string>
@ -154,14 +168,14 @@
</property>
</widget>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="osdInformationDurationLabel">
<property name="text">
<string>Information Duration:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="osdInformationDuration">
<property name="suffix">
<string> seconds</string>
@ -180,14 +194,14 @@
</property>
</widget>
</item>
<item row="2" column="2">
<item row="3" column="2">
<widget class="QLabel" name="osdQuickDurationLabel">
<property name="text">
<string>Action Duration:</string>
</property>
</widget>
</item>
<item row="2" column="3">
<item row="3" column="3">
<widget class="QDoubleSpinBox" name="osdQuickDuration">
<property name="suffix">
<string> seconds</string>
@ -206,14 +220,14 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Display Location:</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="3">
<item row="4" column="1" colspan="3">
<widget class="QComboBox" name="osdMessageLocation"/>
</item>
</layout>

@ -532,8 +532,7 @@ void ImGuiManager::RenderDrawLists(u32 window_width, u32 window_height, WindowIn
if (pcmd->UserCallback) [[unlikely]]
{
pcmd->UserCallback(cmd_list, pcmd);
g_gpu_device->UploadUniformBuffer(&mproj, sizeof(mproj));
pcmd->UserCallback(cmd_list, pcmd, base_vertex, base_index);
g_gpu_device->SetPipeline(s_state.imgui_pipeline.get());
}
else
@ -1073,6 +1072,8 @@ void ImGuiManager::DrawOSDMessages(Timer::Value current_time)
const float max_width_for_color = std::ceil(400.0f * scale);
const float min_rounded_width = rounding * 2.0f;
const bool show_messages = g_gpu_settings.display_show_messages;
const bool blur_background = g_gpu_settings.display_blur_message_backgrounds && !FullscreenUI::HasActiveWindow() &&
FullscreenUI::CanBlurBackground();
const ImVec4 left_background_color = DarkerColor(UIStyle.ToastBackgroundColor, 1.3f);
const ImVec4 right_background_color = DarkerColor(UIStyle.ToastBackgroundColor, 0.8f);
@ -1176,14 +1177,22 @@ void ImGuiManager::DrawOSDMessages(Timer::Value current_time)
ImDrawList* const dl = ImGui::GetForegroundDrawList();
const float background_opacity = opacity * 0.95f;
DrawRoundedGradientRect(
dl, pos, pos_max, ImGui::GetColorU32(ModAlpha(left_background_color, background_opacity)),
ImGui::GetColorU32(
ModAlpha(ImLerp(left_background_color, right_background_color,
std::min((box_width - min_rounded_width) / (max_width_for_color - min_rounded_width), 1.0f)),
background_opacity)),
rounding);
if (blur_background && FullscreenUI::BeginBlurBackground(dl, pos, pos_max))
{
dl->AddRectFilled(pos, pos_max, ImGui::GetColorU32(ModAlpha(left_background_color, opacity)), rounding);
FullscreenUI::EndBlurBackground(dl);
}
else
{
const float background_opacity = opacity * 0.95f;
DrawRoundedGradientRect(
dl, pos, pos_max, ImGui::GetColorU32(ModAlpha(left_background_color, background_opacity)),
ImGui::GetColorU32(
ModAlpha(ImLerp(left_background_color, right_background_color,
std::min((box_width - min_rounded_width) / (max_width_for_color - min_rounded_width), 1.0f)),
background_opacity)),
rounding);
}
const ImVec2 base_pos = ImVec2(pos.x + padding, pos.y + padding);
const ImU32 color = ImGui::GetColorU32(ModAlpha(text_color, opacity));

@ -977,6 +977,47 @@ std::string ShaderGen::GenerateImGuiFragmentShader() const
ss << R"(
{
o_col0 = v_col0 * SAMPLE_TEXTURE(samp0, v_tex0);
}
)";
return std::move(ss).str();
}
std::string ShaderGen::GenerateImGuiBlurVertexShader() const
{
std::stringstream ss;
WriteHeader(ss);
DeclareUniformBuffer(ss, {"float4x4 ProjectionMatrix"}, false);
DeclareUniformBuffer(ss, {"float2 BlurTextureScale", "float BlurBackgroundWeight", "float InvBlurBackgroundWeight"},
true);
DeclareVertexEntryPoint(ss, {"float2 a_pos", "float4 a_col0"}, 1, 0, {}, false);
ss << R"(
{
v_pos = mul(ProjectionMatrix, float4(a_pos, 0.f, 1.f));
v_col0 = a_col0;
#if API_VULKAN
v_pos.y = -v_pos.y;
#endif
}
)";
return std::move(ss).str();
}
std::string ShaderGen::GenerateImGuiBlurFragmentShader() const
{
std::stringstream ss;
WriteHeader(ss);
DeclareUniformBuffer(ss, {"float4x4 ProjectionMatrix"}, false); // needs the descriptor set defined
DeclareUniformBuffer(ss, {"float2 BlurTextureScale", "float BlurBackgroundWeight", "float InvBlurBackgroundWeight"},
true);
DeclareTexture(ss, "samp0", 0);
DeclareFragmentEntryPoint(ss, 1, 0, {}, true);
ss << R"(
{
int2 blur_pos = int2(floor(v_pos.xy) * BlurTextureScale);
o_col0 = float4(LOAD_TEXTURE(samp0, blur_pos, 0).rgb * BlurBackgroundWeight + v_col0.rgb * InvBlurBackgroundWeight, v_col0.a);
}
)";
@ -1002,3 +1043,103 @@ std::string ShaderGen::GenerateFadeFragmentShader() const
return std::move(ss).str();
}
std::string ShaderGen::GenerateGaussianBlurFragmentShader() const
{
std::stringstream ss;
WriteHeader(ss);
// Push constants: blur direction scaled by texel size (x_dir/width, y_dir/height)
DeclareUniformBuffer(ss, {"float2 u_blur_direction"}, true);
DeclareTexture(ss, "samp0", 0);
// https://lisyarus.github.io/blog/posts/blur-coefficients-generator.html
// Radius: 30, sigma: 20
ss << R"(
#define SAMPLE_COUNT 31
CONSTANT float OFFSETS[SAMPLE_COUNT] = BEGIN_ARRAY(float, SAMPLE_COUNT)
-29.481574788498758,
-27.482822966811984,
-25.48407133476425,
-23.48531987689748,
-21.4865685814993,
-19.487817441187733,
-17.4890664530808,
-15.49031561813869,
-13.491564939014534,
-11.492814415362423,
-9.49406403495946,
-7.495313758095638,
-5.496563491310285,
-3.4978130444607514,
-1.4990620619239194,
0.4996866407382734,
2.498437651462263,
4.497188307705211,
6.4959386324091755,
8.494688887763292,
10.493439208848661,
12.492189658097768,
14.490940258972138,
16.48969101629867,
18.48844192812288,
20.487192992240907,
22.485944209457497,
24.484695584886197,
26.48344712812872,
28.482198852858183,
30
END_ARRAY;
CONSTANT float WEIGHTS[SAMPLE_COUNT] = BEGIN_ARRAY(float, SAMPLE_COUNT)
0.01541016258836947,
0.017768151535171497,
0.020283267106540014,
0.022924205290094233,
0.025651423266747478,
0.02841773778937317,
0.03116939833169751,
0.03384762398431486,
0.03639055836034262,
0.03873556231203028,
0.040821733313367456,
0.042592516137640944,
0.043998254731464806,
0.0449985319608136,
0.04556415312900511,
0.04567825324468065,
0.04533701597951506,
0.04455118394212641,
0.043343783883772295,
0.04174984227341022,
0.03981466909233367,
0.03759167925377385,
0.03513992790902628,
0.032521509921346885,
0.029798976743363043,
0.027032914767164073,
0.024279809433072556,
0.02159029141425853,
0.019007828077837678,
0.016567888438120362,
0.007421145789225499
END_ARRAY;
)";
DeclareFragmentEntryPoint(ss, 0, 1);
ss << R"(
{
float3 result = float3(0.0f, 0.0f, 0.0f);
for (int i = 0; i < SAMPLE_COUNT; i++)
{
float2 offset = u_blur_direction * OFFSETS[i];
float3 color = SAMPLE_TEXTURE(samp0, v_tex0 + offset).rgb;
result += color * WEIGHTS[i];
}
o_col0 = float4(result, 1.0);
})";
return std::move(ss).str();
}

@ -36,7 +36,10 @@ public:
std::string GenerateImGuiVertexShader() const;
std::string GenerateImGuiFragmentShader() const;
std::string GenerateImGuiBlurVertexShader() const;
std::string GenerateImGuiBlurFragmentShader() const;
std::string GenerateFadeFragmentShader() const;
std::string GenerateGaussianBlurFragmentShader() const;
const char* GetInterpolationQualifier(bool interface_block, bool centroid_interpolation, bool sample_interpolation,
bool is_out) const;

Loading…
Cancel
Save