diff --git a/UnleashedRecomp/gpu/imgui_common.h b/UnleashedRecomp/gpu/imgui_common.h index 748d5423..a68bb28e 100644 --- a/UnleashedRecomp/gpu/imgui_common.h +++ b/UnleashedRecomp/gpu/imgui_common.h @@ -17,6 +17,7 @@ enum class ImGuiCallback : int32_t SetOrigin = -3, SetScale = -4, SetMarqueeFade = -5, + SetOutline = -6, // -8 is ImDrawCallback_ResetRenderState, don't use! }; @@ -50,6 +51,11 @@ union ImGuiCallbackData float boundsMin[2]; float boundsMax[2]; } setMarqueeFade; + + struct + { + float outline; + } setOutline; }; extern ImGuiCallbackData* AddImGuiCallback(ImGuiCallback callback); diff --git a/UnleashedRecomp/gpu/imgui_font_builder.cpp b/UnleashedRecomp/gpu/imgui_font_builder.cpp index 334143bc..614e728d 100644 --- a/UnleashedRecomp/gpu/imgui_font_builder.cpp +++ b/UnleashedRecomp/gpu/imgui_font_builder.cpp @@ -205,7 +205,7 @@ static bool FontBuilder_Build(ImFontAtlas* atlas) packer.dimensionsConstraint = msdf_atlas::DimensionsConstraint::POWER_OF_TWO_RECTANGLE; packer.minScale = 24.0; packer.miterLimit = 1.0; - packer.pxRange = 4.0; + packer.pxRange = 8.0; packer.pack(glyphs.data(), glyphs.size(), customRects.data(), customRects.size()); for (size_t i = 0; i < customRects.size(); i++) diff --git a/UnleashedRecomp/gpu/shader/imgui_common.hlsli b/UnleashedRecomp/gpu/shader/imgui_common.hlsli index 6c79a48b..1e8db3d6 100644 --- a/UnleashedRecomp/gpu/shader/imgui_common.hlsli +++ b/UnleashedRecomp/gpu/shader/imgui_common.hlsli @@ -13,6 +13,7 @@ struct PushConstants float2 InverseDisplaySize; float2 Origin; float2 Scale; + float Outline; }; Texture2D g_Texture2DDescriptorHeap[] : register(t0, space0); diff --git a/UnleashedRecomp/gpu/shader/imgui_ps.hlsl b/UnleashedRecomp/gpu/shader/imgui_ps.hlsl index 8bdacb5e..2116a498 100644 --- a/UnleashedRecomp/gpu/shader/imgui_ps.hlsl +++ b/UnleashedRecomp/gpu/shader/imgui_ps.hlsl @@ -73,21 +73,6 @@ float4 PixelAntialiasing(float2 uvTexspace) return SampleLinear(uvTexspace); } -uint GetTexture2DDescriptorIndex() -{ - return g_PushConstants.Texture2DDescriptorIndex & 0x7FFFFFFF; -} - -float ComputeScreenPixelRange(float2 texCoord) -{ - uint width, height; - g_Texture2DDescriptorHeap[GetTexture2DDescriptorIndex()].GetDimensions(width, height); - - float2 unitRange = 4.0 / float2(width, height); - float2 screenTextureSize = 1.0 / fwidth(texCoord); - return max(0.5 * dot(unitRange, screenTextureSize), 1.0); -} - float median(float r, float g, float b) { return max(min(r, g), min(max(r, g), b)); @@ -100,17 +85,28 @@ float4 main(in Interpolators interpolators) : SV_Target if (g_PushConstants.Texture2DDescriptorIndex != 0) { - float4 texture = g_Texture2DDescriptorHeap[GetTexture2DDescriptorIndex()].Sample(g_SamplerDescriptorHeap[0], interpolators.UV); + Texture2D texture = g_Texture2DDescriptorHeap[g_PushConstants.Texture2DDescriptorIndex & 0x7FFFFFFF]; + float4 textureColor = texture.Sample(g_SamplerDescriptorHeap[0], interpolators.UV); + if ((g_PushConstants.Texture2DDescriptorIndex & 0x80000000) != 0) { - float sd = median(texture.r, texture.g, texture.b) - 0.5; - float screenPixelDistance = ComputeScreenPixelRange(interpolators.UV) * sd; - color.a *= saturate(screenPixelDistance + 0.5); - color.a *= texture.a; + 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)); + + color.a *= saturate(screenPxDistance + 0.5); + color.a *= textureColor.a; } else { - color *= texture; + color *= textureColor; } } diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index 4b353f57..374890c1 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -1108,6 +1108,7 @@ struct ImGuiPushConstants ImVec2 inverseDisplaySize{}; ImVec2 origin{ 0.0f, 0.0f }; ImVec2 scale{ 1.0f, 1.0f }; + float outline{}; }; extern ImFontBuilderIO g_fontBuilderIO; @@ -1952,6 +1953,9 @@ static void ProcDrawImGui(const RenderCommand& cmd) case ImGuiCallback::SetMarqueeFade: setPushConstants(&pushConstants.boundsMin, &callbackData->setMarqueeFade, sizeof(callbackData->setMarqueeFade)); break; + case ImGuiCallback::SetOutline: + setPushConstants(&pushConstants.outline, &callbackData->setOutline, sizeof(callbackData->setOutline)); + break; default: assert(false && "Unknown ImGui callback type."); break; diff --git a/UnleashedRecomp/ui/achievement_menu.cpp b/UnleashedRecomp/ui/achievement_menu.cpp index b590875e..98dace2d 100644 --- a/UnleashedRecomp/ui/achievement_menu.cpp +++ b/UnleashedRecomp/ui/achievement_menu.cpp @@ -141,14 +141,14 @@ static void DrawHeaderContainer(const char* text) SetTextSkew((min.y + max.y) / 2.0f, Scale(3.0f)); // TODO: Apply bevel. - DrawTextWithOutline + DrawTextWithOutline ( g_fntNewRodinUB, fontSize, { /* X */ min.x + textMarginX, /* Y */ CENTRE_TEXT_VERT(min, max, textSize) - Scale(5) }, IM_COL32(255, 255, 255, 255 * alpha), text, - 1.65f, + 4, IM_COL32(0, 0, 0, 255 * alpha) ); @@ -349,7 +349,7 @@ static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievemen ); // Draw timestamp text. - DrawTextWithOutline + DrawTextWithOutline ( g_fntNewRodinDB, fontSize, @@ -523,7 +523,7 @@ static void DrawAchievementTotal(ImVec2 min, ImVec2 max) auto fontSize = Scale(20); auto textSize = g_fntNewRodinDB->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str()); - DrawTextWithOutline + DrawTextWithOutline ( g_fntNewRodinDB, fontSize, diff --git a/UnleashedRecomp/ui/button_guide.cpp b/UnleashedRecomp/ui/button_guide.cpp index 275d4acb..88f710e4 100644 --- a/UnleashedRecomp/ui/button_guide.cpp +++ b/UnleashedRecomp/ui/button_guide.cpp @@ -188,7 +188,7 @@ static void DrawGuide(float* offset, ImVec2 regionMin, ImVec2 regionMax, EButton ImVec2 textPosition = { textMarginX, textMarginY }; - DrawTextWithOutline(font, fontSize, textPosition, IM_COL32_WHITE, text, 2, IM_COL32_BLACK); + DrawTextWithOutline(font, fontSize, textPosition, IM_COL32_WHITE, text, 2, IM_COL32_BLACK); // Add extra luminance to low quality text. if (fontQuality == EFontQuality::Low) diff --git a/UnleashedRecomp/ui/imgui_utils.h b/UnleashedRecomp/ui/imgui_utils.h index b23a2c9f..99175a96 100644 --- a/UnleashedRecomp/ui/imgui_utils.h +++ b/UnleashedRecomp/ui/imgui_utils.h @@ -83,6 +83,17 @@ static void ResetMarqueeFade() SetScale({ 1.0f, 1.0f }); } +static void SetOutline(float outline) +{ + auto callbackData = AddImGuiCallback(ImGuiCallback::SetOutline); + callbackData->setOutline.outline = outline; +} + +static void ResetOutline() +{ + SetOutline(0.0f); +} + // Aspect ratio aware. static float Scale(float size) { @@ -179,41 +190,13 @@ static void DrawTextWithMarquee(const ImFont* font, float fontSize, const ImVec2 drawList->PopClipRect(); } -template -static void DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, T outlineSize, ImU32 outlineColor) +static void DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, float outlineSize, ImU32 outlineColor) { auto drawList = ImGui::GetForegroundDrawList(); - outlineSize = Scale(outlineSize); - - if constexpr (std::is_same_v || std::is_same_v) - { - auto step = outlineSize / 2.0f; - - // TODO: This is still very inefficient! - for (float i = -outlineSize; i <= outlineSize; i += step) - { - for (float j = -outlineSize; j <= outlineSize; j += step) - { - if (i == 0.0f && j == 0.0f) - continue; - - drawList->AddText(font, fontSize, { pos.x + i, pos.y + j }, outlineColor, text); - } - } - } - else if constexpr (std::is_integral_v) - { - // TODO: This is very inefficient! - for (int32_t i = -outlineSize + 1; i < outlineSize; i++) - { - for (int32_t j = -outlineSize + 1; j < outlineSize; j++) - { - if (i != 0 || j != 0) - drawList->AddText(font, fontSize, { pos.x + i, pos.y + j }, outlineColor, text); - } - } - } + SetOutline(outlineSize); + drawList->AddText(font, fontSize, pos, outlineColor, text); + ResetOutline(); drawList->AddText(font, fontSize, pos, color, text); } @@ -223,9 +206,8 @@ static void DrawTextWithShadow(const ImFont* font, float fontSize, const ImVec2& auto drawList = ImGui::GetForegroundDrawList(); offset = Scale(offset); - radius = Scale(radius); - DrawTextWithOutline(font, fontSize, { pos.x + offset, pos.y + offset }, shadowColour, text, radius, shadowColour); + DrawTextWithOutline(font, fontSize, { pos.x + offset, pos.y + offset }, shadowColour, text, radius, shadowColour); drawList->AddText(font, fontSize, pos, colour, text, nullptr); } diff --git a/UnleashedRecomp/ui/installer_wizard.cpp b/UnleashedRecomp/ui/installer_wizard.cpp index 3ab17246..4576d809 100644 --- a/UnleashedRecomp/ui/installer_wizard.cpp +++ b/UnleashedRecomp/ui/installer_wizard.cpp @@ -559,7 +559,7 @@ static void DrawScanlineBars() // Installer text const std::string &headerText = Localise(g_currentPage == WizardPage::Installing ? "Installer_Header_Installing" : "Installer_Header_Installer"); int textAlpha = std::lround(255.0f * ComputeMotionInstaller(g_appearTime, g_disappearTime, TITLE_ANIMATION_TIME, TITLE_ANIMATION_DURATION)); - DrawTextWithOutline(g_dfsogeistdFont, Scale(42.0f), { Scale(285.0f), Scale(57.0f) }, IM_COL32(255, 195, 0, textAlpha), headerText.c_str(), 4, IM_COL32(0, 0, 0, textAlpha)); + DrawTextWithOutline(g_dfsogeistdFont, Scale(42.0f), { Scale(285.0f), Scale(57.0f) }, IM_COL32(255, 195, 0, textAlpha), headerText.c_str(), 4, IM_COL32(0, 0, 0, textAlpha)); // Top bar line drawList->AddLine @@ -761,7 +761,7 @@ static void DrawButton(ImVec2 min, ImVec2 max, const char *buttonText, bool sour IM_COL32(baser + 128, baseg + 170, 0, 255) ); - DrawTextWithOutline + DrawTextWithOutline ( font, size, diff --git a/UnleashedRecomp/ui/options_menu.cpp b/UnleashedRecomp/ui/options_menu.cpp index 421e525d..0f2492ea 100644 --- a/UnleashedRecomp/ui/options_menu.cpp +++ b/UnleashedRecomp/ui/options_menu.cpp @@ -138,7 +138,7 @@ static void DrawScanlineBars() // Options text // TODO: localise this. - DrawTextWithOutline(g_dfsogeistdFont, Scale(48.0f), { Scale(122.0f), Scale(56.0f) }, IM_COL32(255, 195, 0, 255), "OPTIONS", 4, IM_COL32_BLACK); + DrawTextWithOutline(g_dfsogeistdFont, Scale(48.0f), { Scale(122.0f), Scale(56.0f) }, IM_COL32(255, 195, 0, 255), "OPTIONS", 4, IM_COL32_BLACK); // Top bar line drawList->AddLine @@ -389,7 +389,7 @@ static bool DrawCategories() IM_COL32(255, 192, 0, alpha) ); - DrawTextWithOutline + DrawTextWithOutline ( g_dfsogeistdFont, size, @@ -755,7 +755,7 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf IM_COL32(128, 170, 0, 255) ); - DrawTextWithOutline + DrawTextWithOutline ( g_newRodinFont, size,