Implement outlines.

This commit is contained in:
Skyth 2024-12-11 19:17:00 +03:00
parent bbe869c25f
commit 82aee0951a
10 changed files with 55 additions and 66 deletions

View file

@ -17,6 +17,7 @@ enum class ImGuiCallback : int32_t
SetOrigin = -3, SetOrigin = -3,
SetScale = -4, SetScale = -4,
SetMarqueeFade = -5, SetMarqueeFade = -5,
SetOutline = -6,
// -8 is ImDrawCallback_ResetRenderState, don't use! // -8 is ImDrawCallback_ResetRenderState, don't use!
}; };
@ -50,6 +51,11 @@ union ImGuiCallbackData
float boundsMin[2]; float boundsMin[2];
float boundsMax[2]; float boundsMax[2];
} setMarqueeFade; } setMarqueeFade;
struct
{
float outline;
} setOutline;
}; };
extern ImGuiCallbackData* AddImGuiCallback(ImGuiCallback callback); extern ImGuiCallbackData* AddImGuiCallback(ImGuiCallback callback);

View file

@ -205,7 +205,7 @@ static bool FontBuilder_Build(ImFontAtlas* atlas)
packer.dimensionsConstraint = msdf_atlas::DimensionsConstraint::POWER_OF_TWO_RECTANGLE; packer.dimensionsConstraint = msdf_atlas::DimensionsConstraint::POWER_OF_TWO_RECTANGLE;
packer.minScale = 24.0; packer.minScale = 24.0;
packer.miterLimit = 1.0; packer.miterLimit = 1.0;
packer.pxRange = 4.0; packer.pxRange = 8.0;
packer.pack(glyphs.data(), glyphs.size(), customRects.data(), customRects.size()); packer.pack(glyphs.data(), glyphs.size(), customRects.data(), customRects.size());
for (size_t i = 0; i < customRects.size(); i++) for (size_t i = 0; i < customRects.size(); i++)

View file

@ -13,6 +13,7 @@ struct PushConstants
float2 InverseDisplaySize; float2 InverseDisplaySize;
float2 Origin; float2 Origin;
float2 Scale; float2 Scale;
float Outline;
}; };
Texture2D<float4> g_Texture2DDescriptorHeap[] : register(t0, space0); Texture2D<float4> g_Texture2DDescriptorHeap[] : register(t0, space0);

View file

@ -73,21 +73,6 @@ float4 PixelAntialiasing(float2 uvTexspace)
return SampleLinear(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) 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));
@ -100,17 +85,28 @@ float4 main(in Interpolators interpolators) : SV_Target
if (g_PushConstants.Texture2DDescriptorIndex != 0) if (g_PushConstants.Texture2DDescriptorIndex != 0)
{ {
float4 texture = g_Texture2DDescriptorHeap[GetTexture2DDescriptorIndex()].Sample(g_SamplerDescriptorHeap[0], interpolators.UV); 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)
{ {
float sd = median(texture.r, texture.g, texture.b) - 0.5; uint width, height;
float screenPixelDistance = ComputeScreenPixelRange(interpolators.UV) * sd; texture.GetDimensions(width, height);
color.a *= saturate(screenPixelDistance + 0.5);
color.a *= texture.a; 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 else
{ {
color *= texture; color *= textureColor;
} }
} }

View file

@ -1108,6 +1108,7 @@ struct ImGuiPushConstants
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 };
float outline{};
}; };
extern ImFontBuilderIO g_fontBuilderIO; extern ImFontBuilderIO g_fontBuilderIO;
@ -1952,6 +1953,9 @@ static void ProcDrawImGui(const RenderCommand& cmd)
case ImGuiCallback::SetMarqueeFade: case ImGuiCallback::SetMarqueeFade:
setPushConstants(&pushConstants.boundsMin, &callbackData->setMarqueeFade, sizeof(callbackData->setMarqueeFade)); setPushConstants(&pushConstants.boundsMin, &callbackData->setMarqueeFade, sizeof(callbackData->setMarqueeFade));
break; break;
case ImGuiCallback::SetOutline:
setPushConstants(&pushConstants.outline, &callbackData->setOutline, sizeof(callbackData->setOutline));
break;
default: default:
assert(false && "Unknown ImGui callback type."); assert(false && "Unknown ImGui callback type.");
break; break;

View file

@ -141,14 +141,14 @@ static void DrawHeaderContainer(const char* text)
SetTextSkew((min.y + max.y) / 2.0f, Scale(3.0f)); SetTextSkew((min.y + max.y) / 2.0f, Scale(3.0f));
// TODO: Apply bevel. // TODO: Apply bevel.
DrawTextWithOutline<float> DrawTextWithOutline
( (
g_fntNewRodinUB, g_fntNewRodinUB,
fontSize, fontSize,
{ /* X */ min.x + textMarginX, /* Y */ CENTRE_TEXT_VERT(min, max, textSize) - Scale(5) }, { /* X */ min.x + textMarginX, /* Y */ CENTRE_TEXT_VERT(min, max, textSize) - Scale(5) },
IM_COL32(255, 255, 255, 255 * alpha), IM_COL32(255, 255, 255, 255 * alpha),
text, text,
1.65f, 4,
IM_COL32(0, 0, 0, 255 * alpha) IM_COL32(0, 0, 0, 255 * alpha)
); );
@ -349,7 +349,7 @@ static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievemen
); );
// Draw timestamp text. // Draw timestamp text.
DrawTextWithOutline<int> DrawTextWithOutline
( (
g_fntNewRodinDB, g_fntNewRodinDB,
fontSize, fontSize,
@ -523,7 +523,7 @@ static void DrawAchievementTotal(ImVec2 min, ImVec2 max)
auto fontSize = Scale(20); auto fontSize = Scale(20);
auto textSize = g_fntNewRodinDB->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str()); auto textSize = g_fntNewRodinDB->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str());
DrawTextWithOutline<int> DrawTextWithOutline
( (
g_fntNewRodinDB, g_fntNewRodinDB,
fontSize, fontSize,

View file

@ -188,7 +188,7 @@ static void DrawGuide(float* offset, ImVec2 regionMin, ImVec2 regionMax, EButton
ImVec2 textPosition = { textMarginX, textMarginY }; ImVec2 textPosition = { textMarginX, textMarginY };
DrawTextWithOutline<int>(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. // Add extra luminance to low quality text.
if (fontQuality == EFontQuality::Low) if (fontQuality == EFontQuality::Low)

View file

@ -83,6 +83,17 @@ static void ResetMarqueeFade()
SetScale({ 1.0f, 1.0f }); 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. // Aspect ratio aware.
static float Scale(float size) static float Scale(float size)
{ {
@ -179,41 +190,13 @@ static void DrawTextWithMarquee(const ImFont* font, float fontSize, const ImVec2
drawList->PopClipRect(); drawList->PopClipRect();
} }
template<typename T> static void DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, float outlineSize, ImU32 outlineColor)
static void DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, T outlineSize, ImU32 outlineColor)
{ {
auto drawList = ImGui::GetForegroundDrawList(); auto drawList = ImGui::GetForegroundDrawList();
outlineSize = Scale(outlineSize); SetOutline(outlineSize);
drawList->AddText(font, fontSize, pos, outlineColor, text);
if constexpr (std::is_same_v<float, T> || std::is_same_v<double, T>) ResetOutline();
{
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<T>)
{
// 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);
}
}
}
drawList->AddText(font, fontSize, pos, color, text); 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(); auto drawList = ImGui::GetForegroundDrawList();
offset = Scale(offset); offset = Scale(offset);
radius = Scale(radius);
DrawTextWithOutline<float>(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); drawList->AddText(font, fontSize, pos, colour, text, nullptr);
} }

View file

@ -559,7 +559,7 @@ static void DrawScanlineBars()
// Installer text // Installer text
const std::string &headerText = Localise(g_currentPage == WizardPage::Installing ? "Installer_Header_Installing" : "Installer_Header_Installer"); 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)); int textAlpha = std::lround(255.0f * ComputeMotionInstaller(g_appearTime, g_disappearTime, TITLE_ANIMATION_TIME, TITLE_ANIMATION_DURATION));
DrawTextWithOutline<int>(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 // Top bar line
drawList->AddLine 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) IM_COL32(baser + 128, baseg + 170, 0, 255)
); );
DrawTextWithOutline<int> DrawTextWithOutline
( (
font, font,
size, size,

View file

@ -138,7 +138,7 @@ static void DrawScanlineBars()
// Options text // Options text
// TODO: localise this. // TODO: localise this.
DrawTextWithOutline<int>(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 // Top bar line
drawList->AddLine drawList->AddLine
@ -389,7 +389,7 @@ static bool DrawCategories()
IM_COL32(255, 192, 0, alpha) IM_COL32(255, 192, 0, alpha)
); );
DrawTextWithOutline<int> DrawTextWithOutline
( (
g_dfsogeistdFont, g_dfsogeistdFont,
size, size,
@ -755,7 +755,7 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef<T>* conf
IM_COL32(128, 170, 0, 255) IM_COL32(128, 170, 0, 255)
); );
DrawTextWithOutline<int> DrawTextWithOutline
( (
g_newRodinFont, g_newRodinFont,
size, size,