Draw low quality text for custom UI that is directly part of the game. (#434)

This commit is contained in:
Skyth (Asilkan) 2025-02-20 17:18:27 +03:00 committed by GitHub
parent 0afd01ff7e
commit ba522c0e42
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 144 additions and 64 deletions

View file

@ -11,6 +11,7 @@
#define IMGUI_SHADER_MODIFIER_TITLE_BEVEL 8 #define IMGUI_SHADER_MODIFIER_TITLE_BEVEL 8
#define IMGUI_SHADER_MODIFIER_CATEGORY_BEVEL 9 #define IMGUI_SHADER_MODIFIER_CATEGORY_BEVEL 9
#define IMGUI_SHADER_MODIFIER_RECTANGLE_BEVEL 10 #define IMGUI_SHADER_MODIFIER_RECTANGLE_BEVEL 10
#define IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT 11
#ifdef __cplusplus #ifdef __cplusplus

View file

@ -12,6 +12,7 @@ struct PushConstants
uint GradientBottomLeft; uint GradientBottomLeft;
uint ShaderModifier; uint ShaderModifier;
uint Texture2DDescriptorIndex; uint Texture2DDescriptorIndex;
float2 DisplaySize;
float2 InverseDisplaySize; float2 InverseDisplaySize;
float2 Origin; float2 Origin;
float2 Scale; float2 Scale;

View file

@ -61,10 +61,10 @@ float4 SampleLinear(float2 uvTexspace)
float4 PixelAntialiasing(float2 uvTexspace) float4 PixelAntialiasing(float2 uvTexspace)
{ {
if ((g_PushConstants.InverseDisplaySize.y / g_PushConstants.InverseDisplaySize.x) >= (4.0 / 3.0)) if ((g_PushConstants.DisplaySize.x * g_PushConstants.InverseDisplaySize.y) >= (4.0 / 3.0))
uvTexspace *= g_PushConstants.InverseDisplaySize.y * 720.0f; uvTexspace *= g_PushConstants.InverseDisplaySize.y * 720.0;
else else
uvTexspace *= g_PushConstants.InverseDisplaySize.x * 960.0f; uvTexspace *= g_PushConstants.InverseDisplaySize.x * 960.0;
float2 seam = floor(uvTexspace + 0.5); float2 seam = floor(uvTexspace + 0.5);
uvTexspace = (uvTexspace - seam) / fwidth(uvTexspace) + seam; 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)); return max(min(r, g), min(max(r, g), b));
} }
float4 SampleSdfFont(float4 color, Texture2D<float4> 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 main(in Interpolators interpolators) : SV_Target
{ {
float4 color = interpolators.Color; float4 color = interpolators.Color;
@ -86,52 +131,50 @@ float4 main(in Interpolators interpolators) : SV_Target
if (g_PushConstants.Texture2DDescriptorIndex != 0) if (g_PushConstants.Texture2DDescriptorIndex != 0)
{ {
Texture2D<float4> texture = g_Texture2DDescriptorHeap[g_PushConstants.Texture2DDescriptorIndex & 0x7FFFFFFF]; Texture2D<float4> texture = g_Texture2DDescriptorHeap[g_PushConstants.Texture2DDescriptorIndex & 0x7FFFFFFF];
float4 textureColor = texture.Sample(g_SamplerDescriptorHeap[0], interpolators.UV);
if ((g_PushConstants.Texture2DDescriptorIndex & 0x80000000) != 0) if ((g_PushConstants.Texture2DDescriptorIndex & 0x80000000) != 0)
{ {
uint width, height; if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT)
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)
{ {
float2 normal = normalize(float3(ddx(sd), ddy(sd), 0.01)).xy; float scale;
float3 rimColor = float3(1, 0.8, 0.29); float invScale;
float3 shadowColor = float3(0.84, 0.57, 0);
float cosTheta = dot(normal, normalize(float2(1, 1))); if ((g_PushConstants.DisplaySize.x * g_PushConstants.InverseDisplaySize.y) >= (4.0 / 3.0))
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)); scale = g_PushConstants.InverseDisplaySize.y * 720.0;
} invScale = g_PushConstants.DisplaySize.y / 720.0;
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))); scale = g_PushConstants.InverseDisplaySize.x * 960.0;
float gradient = 1.0 + cosTheta * 0.5; invScale = g_PushConstants.DisplaySize.x / 960.0;
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); float2 lowQualityPosition = (interpolators.Position.xy - 0.5) * scale;
color.a *= textureColor.a; 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
{
color = SampleSdfFont(color, texture, interpolators.UV, 1.0 / fwidth(interpolators.UV));
}
} }
else else
{ {
color *= textureColor; color *= texture.Sample(g_SamplerDescriptorHeap[0], interpolators.UV);
} }
} }

View file

@ -1340,6 +1340,7 @@ struct ImGuiPushConstants
ImU32 gradientBottomLeft{}; ImU32 gradientBottomLeft{};
uint32_t shaderModifier{}; uint32_t shaderModifier{};
uint32_t texture2DDescriptorIndex{}; uint32_t texture2DDescriptorIndex{};
ImVec2 displaySize{};
ImVec2 inverseDisplaySize{}; ImVec2 inverseDisplaySize{};
ImVec2 origin{ 0.0f, 0.0f }; ImVec2 origin{ 0.0f, 0.0f };
ImVec2 scale{ 1.0f, 1.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)); commandList->setViewports(RenderViewport(drawData.DisplayPos.x, drawData.DisplayPos.y, drawData.DisplaySize.x, drawData.DisplaySize.y));
ImGuiPushConstants pushConstants{}; ImGuiPushConstants pushConstants{};
pushConstants.displaySize = drawData.DisplaySize;
pushConstants.inverseDisplaySize = { 1.0f / drawData.DisplaySize.x, 1.0f / drawData.DisplaySize.y }; pushConstants.inverseDisplaySize = { 1.0f / drawData.DisplaySize.x, 1.0f / drawData.DisplaySize.y };
commandList->setGraphicsPushConstants(0, &pushConstants); commandList->setGraphicsPushConstants(0, &pushConstants);

View file

@ -763,7 +763,7 @@ void AchievementMenu::Open()
return std::get<1>(a) > std::get<1>(b); 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(); ResetSelection();
Game_PlaySound("sys_actstg_pausewinopen"); Game_PlaySound("sys_actstg_pausewinopen");

View file

@ -183,6 +183,9 @@ void AchievementOverlay::Draw()
IM_COL32(255, 255, 255, 255) // col IM_COL32(255, 255, 255, 255) // col
); );
// Use low quality text.
SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);
// Draw header text. // Draw header text.
DrawTextWithShadow DrawTextWithShadow
( (
@ -208,6 +211,9 @@ void AchievementOverlay::Draw()
1.0f, // radius 1.0f, // radius
IM_COL32(0, 0, 0, 255) // shadowColour IM_COL32(0, 0, 0, 255) // shadowColour
); );
// Reset low quality text shader modifier.
SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);
} }
else else
{ {

View file

@ -14,7 +14,6 @@
constexpr float DEFAULT_SIDE_MARGINS = 379; constexpr float DEFAULT_SIDE_MARGINS = 379;
ImFont* g_fntNewRodin; ImFont* g_fntNewRodin;
ImFont* g_fntNewRodinLQ;
std::unique_ptr<GuestTexture> g_upControllerIcons; std::unique_ptr<GuestTexture> g_upControllerIcons;
std::unique_ptr<GuestTexture> g_upKBMIcons; std::unique_ptr<GuestTexture> g_upKBMIcons;
@ -144,21 +143,13 @@ std::tuple<std::tuple<ImVec2, ImVec2>, GuestTexture*> GetButtonIcon(EButtonIcon
return std::make_tuple(btn, texture); 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, static void DrawGuide(float* offset, ImVec2 regionMin, ImVec2 regionMax, EButtonIcon icon,
EButtonAlignment alignment, ImVec2 iconMin, ImVec2 iconMax, EFontQuality fontQuality, EButtonAlignment alignment, ImVec2 iconMin, ImVec2 iconMax, EFontQuality fontQuality,
float textWidth, float maxTextWidth, float textScale, float fontSize, const char* text) float textWidth, float maxTextWidth, float textScale, float fontSize, const char* text)
{ {
auto drawList = ImGui::GetBackgroundDrawList(); auto drawList = ImGui::GetBackgroundDrawList();
auto iconWidth = Scale(g_iconWidths[icon]); auto iconWidth = Scale(g_iconWidths[icon]);
auto font = GetFont(fontQuality); auto textMarginY = regionMin.y + Scale(9.0f);
auto textMarginY = regionMin.y + Scale(8.89f);
ImVec2 textPos; ImVec2 textPos;
@ -206,11 +197,17 @@ static void DrawGuide(float* offset, ImVec2 regionMin, ImVec2 regionMax, EButton
SetScale({ textScale, 1.0f }); SetScale({ textScale, 1.0f });
SetOrigin(textPos); 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) 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 }); SetScale({ 1.0f, 1.0f });
SetOrigin({ 0.0f, 0.0f }); SetOrigin({ 0.0f, 0.0f });
@ -221,8 +218,6 @@ void ButtonGuide::Init()
auto& io = ImGui::GetIO(); auto& io = ImGui::GetIO();
g_fntNewRodin = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-M.otf"); g_fntNewRodin = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-M.otf");
g_fntNewRodinLQ = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-M.otf");
g_upControllerIcons = LOAD_ZSTD_TEXTURE(g_controller); g_upControllerIcons = LOAD_ZSTD_TEXTURE(g_controller);
g_upKBMIcons = LOAD_ZSTD_TEXTURE(g_kbm); g_upKBMIcons = LOAD_ZSTD_TEXTURE(g_kbm);
} }

View file

@ -55,8 +55,8 @@ public:
Button(std::string name, float maxWidth, EButtonIcon icon, bool* visibility) Button(std::string name, float maxWidth, EButtonIcon icon, bool* visibility)
: Name(name), MaxWidth(maxWidth), Icon(icon), Visibility(visibility) {} : Name(name), MaxWidth(maxWidth), Icon(icon), Visibility(visibility) {}
Button(std::string name, float maxWidth, EButtonIcon icon) Button(std::string name, float maxWidth, EButtonIcon icon, EFontQuality fontQuality = EFontQuality::High)
: Name(name), MaxWidth(maxWidth), Icon(icon) {} : Name(name), MaxWidth(maxWidth), Icon(icon), FontQuality(fontQuality) {}
}; };
class ButtonGuide class ButtonGuide

View file

@ -45,6 +45,9 @@ void ResetGradient()
void SetShaderModifier(uint32_t shaderModifier) 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); auto callbackData = AddImGuiCallback(ImGuiCallback::SetShaderModifier);
callbackData->setShaderModifier.shaderModifier = shaderModifier; callbackData->setShaderModifier.shaderModifier = shaderModifier;
} }

View file

@ -223,6 +223,10 @@ void DrawButton(int rowIndex, float yOffset, float width, float height, std::str
auto fontSize = Scale(28); auto fontSize = Scale(28);
auto textSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, text.c_str()); 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 DrawTextWithShadow
( (
g_fntSeurat, 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), isSelected ? IM_COL32(255, 128, 0, 255) : IM_COL32(255, 255, 255, 255),
text.c_str() text.c_str()
); );
// Reset the shader modifier.
if (App::s_isInit)
SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);
} }
void DrawNextButtonGuide(bool isController, bool isKeyboard) void DrawNextButtonGuide(bool isController, bool isKeyboard)
@ -241,11 +249,16 @@ void DrawNextButtonGuide(bool isController, bool isKeyboard)
? EButtonIcon::Enter ? EButtonIcon::Enter
: EButtonIcon::LMB; : EButtonIcon::LMB;
// Always show controller prompt in-game. auto fontQuality = EFontQuality::High;
if (App::s_isInit)
icon = EButtonIcon::A;
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() 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)) 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 DrawRubyAnnotatedText
( (
g_fntSeurat, g_fntSeurat,
@ -359,6 +376,10 @@ void MessageWindow::Draw()
true true
); );
// Reset the shader modifier.
if (App::s_isInit)
SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);
drawList->PopClipRect(); drawList->PopClipRect();
if (g_buttons.size()) if (g_buttons.size())
@ -423,10 +444,17 @@ void MessageWindow::Draw()
backIcon = EButtonIcon::Escape; backIcon = EButtonIcon::Escape;
} }
auto fontQuality = EFontQuality::High;
if (App::s_isInit)
{
// Show low quality text in-game.
fontQuality = EFontQuality::Low;
}
std::array<Button, 2> buttons = std::array<Button, 2> buttons =
{ {
Button("Common_Select", 115.0f, selectIcon), Button("Common_Select", 115.0f, selectIcon, fontQuality),
Button("Common_Back", FLT_MAX, backIcon), Button("Common_Back", FLT_MAX, backIcon, fontQuality),
}; };
ButtonGuide::Open(buttons); ButtonGuide::Open(buttons);

View file

@ -91,5 +91,6 @@ CONFIG_DEFINE_HIDDEN("Codes", bool, SaveScoreAtCheckpoints, false);
CONFIG_DEFINE_HIDDEN("Codes", bool, SkipIntroLogos, false); CONFIG_DEFINE_HIDDEN("Codes", bool, SkipIntroLogos, false);
CONFIG_DEFINE_HIDDEN("Codes", bool, UseArrowsForTimeOfDayTransition, false); CONFIG_DEFINE_HIDDEN("Codes", bool, UseArrowsForTimeOfDayTransition, false);
CONFIG_DEFINE_HIDDEN("Codes", bool, UseOfficialTitleOnTitleBar, false); CONFIG_DEFINE_HIDDEN("Codes", bool, UseOfficialTitleOnTitleBar, false);
CONFIG_DEFINE_HIDDEN("Codes", bool, DisableLowResolutionFontOnCustomUI, false);
CONFIG_DEFINE("Update", time_t, LastChecked, 0); CONFIG_DEFINE("Update", time_t, LastChecked, 0);