diff --git a/UnleashedRecomp/gpu/imgui/imgui_common.h b/UnleashedRecomp/gpu/imgui/imgui_common.h index db2427a..10c5ebb 100644 --- a/UnleashedRecomp/gpu/imgui/imgui_common.h +++ b/UnleashedRecomp/gpu/imgui/imgui_common.h @@ -11,6 +11,7 @@ #define IMGUI_SHADER_MODIFIER_TITLE_BEVEL 8 #define IMGUI_SHADER_MODIFIER_CATEGORY_BEVEL 9 #define IMGUI_SHADER_MODIFIER_RECTANGLE_BEVEL 10 +#define IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT 11 #ifdef __cplusplus diff --git a/UnleashedRecomp/gpu/shader/imgui_common.hlsli b/UnleashedRecomp/gpu/shader/imgui_common.hlsli index fe7a02e..f4cbe3c 100644 --- a/UnleashedRecomp/gpu/shader/imgui_common.hlsli +++ b/UnleashedRecomp/gpu/shader/imgui_common.hlsli @@ -12,6 +12,7 @@ struct PushConstants uint GradientBottomLeft; uint ShaderModifier; uint Texture2DDescriptorIndex; + float2 DisplaySize; float2 InverseDisplaySize; float2 Origin; float2 Scale; diff --git a/UnleashedRecomp/gpu/shader/imgui_ps.hlsl b/UnleashedRecomp/gpu/shader/imgui_ps.hlsl index a61c4d1..add17ba 100644 --- a/UnleashedRecomp/gpu/shader/imgui_ps.hlsl +++ b/UnleashedRecomp/gpu/shader/imgui_ps.hlsl @@ -61,10 +61,10 @@ float4 SampleLinear(float2 uvTexspace) float4 PixelAntialiasing(float2 uvTexspace) { - if ((g_PushConstants.InverseDisplaySize.y / g_PushConstants.InverseDisplaySize.x) >= (4.0 / 3.0)) - uvTexspace *= g_PushConstants.InverseDisplaySize.y * 720.0f; + if ((g_PushConstants.DisplaySize.x * g_PushConstants.InverseDisplaySize.y) >= (4.0 / 3.0)) + uvTexspace *= g_PushConstants.InverseDisplaySize.y * 720.0; else - uvTexspace *= g_PushConstants.InverseDisplaySize.x * 960.0f; + uvTexspace *= g_PushConstants.InverseDisplaySize.x * 960.0; float2 seam = floor(uvTexspace + 0.5); uvTexspace = (uvTexspace - seam) / fwidth(uvTexspace) + seam; @@ -78,6 +78,51 @@ float median(float r, float g, float b) return max(min(r, g), min(max(r, g), b)); } +float4 SampleSdfFont(float4 color, Texture2D texture, float2 uv, float2 screenTexSize) +{ + float4 textureColor = texture.Sample(g_SamplerDescriptorHeap[0], uv); + + uint width, height; + texture.GetDimensions(width, height); + + float pxRange = 8.0; + float2 unitRange = pxRange / float2(width, height); + float screenPxRange = max(0.5 * dot(unitRange, screenTexSize), 1.0); + + float sd = median(textureColor.r, textureColor.g, textureColor.b) - 0.5; + float screenPxDistance = screenPxRange * (sd + g_PushConstants.Outline / (pxRange * 2.0)); + + if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TITLE_BEVEL) + { + float2 normal = normalize(float3(ddx(sd), ddy(sd), 0.01)).xy; + float3 rimColor = float3(1, 0.8, 0.29); + float3 shadowColor = float3(0.84, 0.57, 0); + + float cosTheta = dot(normal, normalize(float2(1, 1))); + float3 gradient = lerp(color.rgb, cosTheta >= 0.0 ? rimColor : shadowColor, abs(cosTheta)); + color.rgb = lerp(gradient, color.rgb, pow(saturate(sd + 0.8), 32.0)); + } + else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_CATEGORY_BEVEL) + { + float2 normal = normalize(float3(ddx(sd), ddy(sd), 0.25)).xy; + float cosTheta = dot(normal, normalize(float2(1, 1))); + float gradient = 1.0 + cosTheta * 0.5; + color.rgb = saturate(color.rgb * gradient); + } + else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TEXT_SKEW) + { + float2 normal = normalize(float3(ddx(sd), ddy(sd), 0.5)).xy; + float cosTheta = dot(normal, normalize(float2(1, 1))); + float gradient = saturate(1.0 + cosTheta); + color.rgb = lerp(color.rgb * gradient, color.rgb, pow(saturate(sd + 0.77), 32.0)); + } + + color.a *= saturate(screenPxDistance + 0.5); + color.a *= textureColor.a; + + return color; +} + float4 main(in Interpolators interpolators) : SV_Target { float4 color = interpolators.Color; @@ -86,52 +131,50 @@ float4 main(in Interpolators interpolators) : SV_Target if (g_PushConstants.Texture2DDescriptorIndex != 0) { Texture2D texture = g_Texture2DDescriptorHeap[g_PushConstants.Texture2DDescriptorIndex & 0x7FFFFFFF]; - float4 textureColor = texture.Sample(g_SamplerDescriptorHeap[0], interpolators.UV); if ((g_PushConstants.Texture2DDescriptorIndex & 0x80000000) != 0) { - uint width, height; - texture.GetDimensions(width, height); - - float pxRange = 8.0; - float2 unitRange = pxRange / float2(width, height); - float2 screenTexSize = 1.0 / fwidth(interpolators.UV); - float screenPxRange = max(0.5 * dot(unitRange, screenTexSize), 1.0); - - float sd = median(textureColor.r, textureColor.g, textureColor.b) - 0.5; - float screenPxDistance = screenPxRange * (sd + g_PushConstants.Outline / (pxRange * 2.0)); - - if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TITLE_BEVEL) + if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT) { - float2 normal = normalize(float3(ddx(sd), ddy(sd), 0.01)).xy; - float3 rimColor = float3(1, 0.8, 0.29); - float3 shadowColor = float3(0.84, 0.57, 0); - - float cosTheta = dot(normal, normalize(float2(1, 1))); - float3 gradient = lerp(color.rgb, cosTheta >= 0.0 ? rimColor : shadowColor, abs(cosTheta)); - color.rgb = lerp(gradient, color.rgb, pow(saturate(sd + 0.8), 32.0)); + float scale; + float invScale; + + if ((g_PushConstants.DisplaySize.x * g_PushConstants.InverseDisplaySize.y) >= (4.0 / 3.0)) + { + scale = g_PushConstants.InverseDisplaySize.y * 720.0; + invScale = g_PushConstants.DisplaySize.y / 720.0; + } + else + { + scale = g_PushConstants.InverseDisplaySize.x * 960.0; + invScale = g_PushConstants.DisplaySize.x / 960.0; + } + + float2 lowQualityPosition = (interpolators.Position.xy - 0.5) * scale; + float2 fracPart = frac(lowQualityPosition); + + float2 uvStep = fwidth(interpolators.UV) * invScale; + float2 lowQualityUV = interpolators.UV - fracPart * uvStep; + float2 screenTexSize = 1.0 / uvStep; + + float4 topLeft = SampleSdfFont(color, texture, lowQualityUV + float2(0, 0), screenTexSize); + float4 topRight = SampleSdfFont(color, texture, lowQualityUV + float2(uvStep.x, 0), screenTexSize); + float4 bottomLeft = SampleSdfFont(color, texture, lowQualityUV + float2(0, uvStep.y), screenTexSize); + float4 bottomRight = SampleSdfFont(color, texture, lowQualityUV + uvStep.xy, screenTexSize); + + float4 top = lerp(topLeft, topRight, fracPart.x); + float4 bottom = lerp(bottomLeft, bottomRight, fracPart.x); + + color = lerp(top, bottom, fracPart.y); } - else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_CATEGORY_BEVEL) + else { - float2 normal = normalize(float3(ddx(sd), ddy(sd), 0.25)).xy; - float cosTheta = dot(normal, normalize(float2(1, 1))); - float gradient = 1.0 + cosTheta * 0.5; - color.rgb = saturate(color.rgb * gradient); + color = SampleSdfFont(color, texture, interpolators.UV, 1.0 / fwidth(interpolators.UV)); } - else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TEXT_SKEW) - { - float2 normal = normalize(float3(ddx(sd), ddy(sd), 0.5)).xy; - float cosTheta = dot(normal, normalize(float2(1, 1))); - float gradient = saturate(1.0 + cosTheta); - color.rgb = lerp(color.rgb * gradient, color.rgb, pow(saturate(sd + 0.77), 32.0)); - } - - color.a *= saturate(screenPxDistance + 0.5); - color.a *= textureColor.a; } else { - color *= textureColor; + color *= texture.Sample(g_SamplerDescriptorHeap[0], interpolators.UV); } } diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index 3dce587..f21f2ce 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -1340,6 +1340,7 @@ struct ImGuiPushConstants ImU32 gradientBottomLeft{}; uint32_t shaderModifier{}; uint32_t texture2DDescriptorIndex{}; + ImVec2 displaySize{}; ImVec2 inverseDisplaySize{}; ImVec2 origin{ 0.0f, 0.0f }; ImVec2 scale{ 1.0f, 1.0f }; @@ -2496,6 +2497,7 @@ static void ProcDrawImGui(const RenderCommand& cmd) commandList->setViewports(RenderViewport(drawData.DisplayPos.x, drawData.DisplayPos.y, drawData.DisplaySize.x, drawData.DisplaySize.y)); ImGuiPushConstants pushConstants{}; + pushConstants.displaySize = drawData.DisplaySize; pushConstants.inverseDisplaySize = { 1.0f / drawData.DisplaySize.x, 1.0f / drawData.DisplaySize.y }; commandList->setGraphicsPushConstants(0, &pushConstants); diff --git a/UnleashedRecomp/ui/achievement_menu.cpp b/UnleashedRecomp/ui/achievement_menu.cpp index 9a7bd3a..5cc3272 100644 --- a/UnleashedRecomp/ui/achievement_menu.cpp +++ b/UnleashedRecomp/ui/achievement_menu.cpp @@ -763,7 +763,7 @@ void AchievementMenu::Open() return std::get<1>(a) > std::get<1>(b); }); - ButtonGuide::Open(Button("Common_Back", FLT_MAX, EButtonIcon::B)); + ButtonGuide::Open(Button("Common_Back", FLT_MAX, EButtonIcon::B, EFontQuality::Low)); ResetSelection(); Game_PlaySound("sys_actstg_pausewinopen"); diff --git a/UnleashedRecomp/ui/achievement_overlay.cpp b/UnleashedRecomp/ui/achievement_overlay.cpp index 18b255a..3eda011 100644 --- a/UnleashedRecomp/ui/achievement_overlay.cpp +++ b/UnleashedRecomp/ui/achievement_overlay.cpp @@ -183,6 +183,9 @@ void AchievementOverlay::Draw() IM_COL32(255, 255, 255, 255) // col ); + // Use low quality text. + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + // Draw header text. DrawTextWithShadow ( @@ -208,6 +211,9 @@ void AchievementOverlay::Draw() 1.0f, // radius IM_COL32(0, 0, 0, 255) // shadowColour ); + + // Reset low quality text shader modifier. + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } else { diff --git a/UnleashedRecomp/ui/button_guide.cpp b/UnleashedRecomp/ui/button_guide.cpp index 605b249..89a1a6b 100644 --- a/UnleashedRecomp/ui/button_guide.cpp +++ b/UnleashedRecomp/ui/button_guide.cpp @@ -14,7 +14,6 @@ constexpr float DEFAULT_SIDE_MARGINS = 379; ImFont* g_fntNewRodin; -ImFont* g_fntNewRodinLQ; std::unique_ptr g_upControllerIcons; std::unique_ptr g_upKBMIcons; @@ -144,21 +143,13 @@ std::tuple, GuestTexture*> GetButtonIcon(EButtonIcon return std::make_tuple(btn, texture); } -ImFont* GetFont(EFontQuality fontQuality) -{ - return fontQuality == EFontQuality::Low - ? g_fntNewRodinLQ - : g_fntNewRodin; -} - static void DrawGuide(float* offset, ImVec2 regionMin, ImVec2 regionMax, EButtonIcon icon, EButtonAlignment alignment, ImVec2 iconMin, ImVec2 iconMax, EFontQuality fontQuality, float textWidth, float maxTextWidth, float textScale, float fontSize, const char* text) { auto drawList = ImGui::GetBackgroundDrawList(); auto iconWidth = Scale(g_iconWidths[icon]); - auto font = GetFont(fontQuality); - auto textMarginY = regionMin.y + Scale(8.89f); + auto textMarginY = regionMin.y + Scale(9.0f); ImVec2 textPos; @@ -206,11 +197,17 @@ static void DrawGuide(float* offset, ImVec2 regionMin, ImVec2 regionMax, EButton SetScale({ textScale, 1.0f }); SetOrigin(textPos); - DrawTextWithOutline(font, fontSize, textPos, IM_COL32_WHITE, text, 4, IM_COL32_BLACK); - - // Add extra luminance to low quality text. if (fontQuality == EFontQuality::Low) - drawList->AddText(font, fontSize, textPos, IM_COL32(255, 255, 255, 127), text); + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + + DrawTextWithOutline(g_fntNewRodin, fontSize, textPos, IM_COL32_WHITE, text, 4, IM_COL32_BLACK); + + if (fontQuality == EFontQuality::Low) + { + // Add extra luminance to low quality text. + drawList->AddText(g_fntNewRodin, fontSize, textPos, IM_COL32(255, 255, 255, 127), text); + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); + } SetScale({ 1.0f, 1.0f }); SetOrigin({ 0.0f, 0.0f }); @@ -221,8 +218,6 @@ void ButtonGuide::Init() auto& io = ImGui::GetIO(); g_fntNewRodin = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-M.otf"); - g_fntNewRodinLQ = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-M.otf"); - g_upControllerIcons = LOAD_ZSTD_TEXTURE(g_controller); g_upKBMIcons = LOAD_ZSTD_TEXTURE(g_kbm); } diff --git a/UnleashedRecomp/ui/button_guide.h b/UnleashedRecomp/ui/button_guide.h index 81e6dcd..94e3ae2 100644 --- a/UnleashedRecomp/ui/button_guide.h +++ b/UnleashedRecomp/ui/button_guide.h @@ -55,8 +55,8 @@ public: Button(std::string name, float maxWidth, EButtonIcon icon, bool* visibility) : Name(name), MaxWidth(maxWidth), Icon(icon), Visibility(visibility) {} - Button(std::string name, float maxWidth, EButtonIcon icon) - : Name(name), MaxWidth(maxWidth), Icon(icon) {} + Button(std::string name, float maxWidth, EButtonIcon icon, EFontQuality fontQuality = EFontQuality::High) + : Name(name), MaxWidth(maxWidth), Icon(icon), FontQuality(fontQuality) {} }; class ButtonGuide diff --git a/UnleashedRecomp/ui/imgui_utils.cpp b/UnleashedRecomp/ui/imgui_utils.cpp index 7b8cf2e..fcb4a8d 100644 --- a/UnleashedRecomp/ui/imgui_utils.cpp +++ b/UnleashedRecomp/ui/imgui_utils.cpp @@ -45,6 +45,9 @@ void ResetGradient() void SetShaderModifier(uint32_t shaderModifier) { + if (shaderModifier == IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT && Config::DisableLowResolutionFontOnCustomUI) + shaderModifier = IMGUI_SHADER_MODIFIER_NONE; + auto callbackData = AddImGuiCallback(ImGuiCallback::SetShaderModifier); callbackData->setShaderModifier.shaderModifier = shaderModifier; } diff --git a/UnleashedRecomp/ui/message_window.cpp b/UnleashedRecomp/ui/message_window.cpp index 5502eb8..f5c8436 100644 --- a/UnleashedRecomp/ui/message_window.cpp +++ b/UnleashedRecomp/ui/message_window.cpp @@ -223,6 +223,10 @@ void DrawButton(int rowIndex, float yOffset, float width, float height, std::str auto fontSize = Scale(28); auto textSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, text.c_str()); + // Show low quality text in-game. + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + DrawTextWithShadow ( g_fntSeurat, @@ -231,6 +235,10 @@ void DrawButton(int rowIndex, float yOffset, float width, float height, std::str isSelected ? IM_COL32(255, 128, 0, 255) : IM_COL32(255, 255, 255, 255), text.c_str() ); + + // Reset the shader modifier. + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); } void DrawNextButtonGuide(bool isController, bool isKeyboard) @@ -241,11 +249,16 @@ void DrawNextButtonGuide(bool isController, bool isKeyboard) ? EButtonIcon::Enter : EButtonIcon::LMB; - // Always show controller prompt in-game. - if (App::s_isInit) - icon = EButtonIcon::A; + auto fontQuality = EFontQuality::High; - ButtonGuide::Open(Button("Common_Next", FLT_MAX, icon)); + // Always show controller prompt and low quality text in-game. + if (App::s_isInit) + { + icon = EButtonIcon::A; + fontQuality = EFontQuality::Low; + } + + ButtonGuide::Open(Button("Common_Next", FLT_MAX, icon, fontQuality)); } static void ResetSelection() @@ -338,6 +351,10 @@ void MessageWindow::Draw() if (DrawContainer(g_appearTime, centre, { textSize.x / 2 + textMarginX, textSize.y / 2 + textMarginY }, !g_isControlsVisible)) { + // Use low quality text when the game is booted to not clash with existing UI. + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + DrawRubyAnnotatedText ( g_fntSeurat, @@ -359,6 +376,10 @@ void MessageWindow::Draw() true ); + // Reset the shader modifier. + if (App::s_isInit) + SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT); + drawList->PopClipRect(); if (g_buttons.size()) @@ -423,10 +444,17 @@ void MessageWindow::Draw() backIcon = EButtonIcon::Escape; } + auto fontQuality = EFontQuality::High; + if (App::s_isInit) + { + // Show low quality text in-game. + fontQuality = EFontQuality::Low; + } + std::array buttons = { - Button("Common_Select", 115.0f, selectIcon), - Button("Common_Back", FLT_MAX, backIcon), + Button("Common_Select", 115.0f, selectIcon, fontQuality), + Button("Common_Back", FLT_MAX, backIcon, fontQuality), }; ButtonGuide::Open(buttons); diff --git a/UnleashedRecomp/user/config_def.h b/UnleashedRecomp/user/config_def.h index c9069f9..cb282c1 100644 --- a/UnleashedRecomp/user/config_def.h +++ b/UnleashedRecomp/user/config_def.h @@ -91,5 +91,6 @@ CONFIG_DEFINE_HIDDEN("Codes", bool, SaveScoreAtCheckpoints, false); CONFIG_DEFINE_HIDDEN("Codes", bool, SkipIntroLogos, false); CONFIG_DEFINE_HIDDEN("Codes", bool, UseArrowsForTimeOfDayTransition, false); CONFIG_DEFINE_HIDDEN("Codes", bool, UseOfficialTitleOnTitleBar, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, DisableLowResolutionFontOnCustomUI, false); CONFIG_DEFINE("Update", time_t, LastChecked, 0);