diff --git a/UnleashedRecomp/gpu/imgui_font_builder.cpp b/UnleashedRecomp/gpu/imgui_font_builder.cpp index bec0ba13..7ab1e9d8 100644 --- a/UnleashedRecomp/gpu/imgui_font_builder.cpp +++ b/UnleashedRecomp/gpu/imgui_font_builder.cpp @@ -2,12 +2,165 @@ #include +// Taken directly from msdf-atlas-gen, modified to support custom rectangles. +struct TightAtlasPacker { + + TightAtlasPacker() : + width(-1), height(-1), + spacing(0), + dimensionsConstraint(msdf_atlas::DimensionsConstraint::POWER_OF_TWO_SQUARE), + scale(-1), + minScale(1), + unitRange(0), + pxRange(0), + miterLimit(0), + pxAlignOriginX(false), pxAlignOriginY(false), + scaleMaximizationTolerance(.001) + { + } + + int pack(msdf_atlas::GlyphGeometry* glyphs, int count, msdf_atlas::Rectangle* customRects, int customRectCount) { + double initialScale = scale > 0 ? scale : minScale; + if (initialScale > 0) { + if (int remaining = tryPack(glyphs, count, customRects, customRectCount, dimensionsConstraint, width, height, initialScale)) + return remaining; + } + else if (width < 0 || height < 0) + return -1; + if (scale <= 0) + scale = packAndScale(glyphs, count, customRects, customRectCount); + if (scale <= 0) + return -1; + return 0; + } + + int width, height; + int spacing; + msdf_atlas::DimensionsConstraint dimensionsConstraint; + double scale; + double minScale; + msdfgen::Range unitRange; + msdfgen::Range pxRange; + double miterLimit; + bool pxAlignOriginX, pxAlignOriginY; + msdf_atlas::Padding innerUnitPadding, outerUnitPadding; + msdf_atlas::Padding innerPxPadding, outerPxPadding; + double scaleMaximizationTolerance; + + int tryPack(msdf_atlas::GlyphGeometry* glyphs, int count, msdf_atlas::Rectangle* customRects, int customRectCount, msdf_atlas::DimensionsConstraint dimensionsConstraint, int& width, int& height, double scale) const { + // Wrap glyphs into boxes + std::vector rectangles; + std::vector rectangleGlyphs; + rectangles.reserve(count + customRectCount); + rectangleGlyphs.reserve(count); + msdf_atlas::GlyphGeometry::GlyphAttributes attribs = { }; + attribs.scale = scale; + attribs.range = { unitRange.lower + pxRange.lower / scale, unitRange.upper + pxRange.upper / scale }; + attribs.innerPadding = innerUnitPadding + innerPxPadding / scale; + attribs.outerPadding = outerUnitPadding + outerPxPadding / scale; + attribs.miterLimit = miterLimit; + attribs.pxAlignOriginX = pxAlignOriginX; + attribs.pxAlignOriginY = pxAlignOriginY; + for (msdf_atlas::GlyphGeometry* glyph = glyphs, *end = glyphs + count; glyph < end; ++glyph) { + if (!glyph->isWhitespace()) { + msdf_atlas::Rectangle rect = { }; + glyph->wrapBox(attribs); + glyph->getBoxSize(rect.w, rect.h); + if (rect.w > 0 && rect.h > 0) { + rectangles.push_back(rect); + rectangleGlyphs.push_back(glyph); + } + } + } + rectangles.insert(rectangles.end(), customRects, customRects + customRectCount); + // No non-zero size boxes? + if (rectangles.empty()) { + if (width < 0 || height < 0) + width = 0, height = 0; + return 0; + } + // Box rectangle packing + if (width < 0 || height < 0) { + std::pair dimensions = std::make_pair(width, height); + switch (dimensionsConstraint) { + case msdf_atlas::DimensionsConstraint::POWER_OF_TWO_SQUARE: + dimensions = msdf_atlas::packRectangles(rectangles.data(), rectangles.size(), spacing); + break; + case msdf_atlas::DimensionsConstraint::POWER_OF_TWO_RECTANGLE: + dimensions = msdf_atlas::packRectangles(rectangles.data(), rectangles.size(), spacing); + break; + case msdf_atlas::DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE: + dimensions = msdf_atlas::packRectangles >(rectangles.data(), rectangles.size(), spacing); + break; + case msdf_atlas::DimensionsConstraint::EVEN_SQUARE: + dimensions = msdf_atlas::packRectangles >(rectangles.data(), rectangles.size(), spacing); + break; + case msdf_atlas::DimensionsConstraint::SQUARE: + default: + dimensions = msdf_atlas::packRectangles >(rectangles.data(), rectangles.size(), spacing); + break; + } + if (!(dimensions.first > 0 && dimensions.second > 0)) + return -1; + width = dimensions.first, height = dimensions.second; + } + else { + if (int result = packRectangles(rectangles.data(), rectangles.size(), width, height, spacing)) + return result; + } + + // Set glyph box placement + for (size_t i = 0; i < rectangleGlyphs.size(); ++i) + rectangleGlyphs[i]->placeBox(rectangles[i].x, height - (rectangles[i].y + rectangles[i].h)); + + for (int i = 0; i < customRectCount; ++i) { + customRects[i].x = rectangles[rectangleGlyphs.size() + i].x; + customRects[i].y = height - (rectangles[rectangleGlyphs.size() + i].y + rectangles[rectangleGlyphs.size() + i].h); + } + + return 0; + } + + double packAndScale(msdf_atlas::GlyphGeometry* glyphs, int count, msdf_atlas::Rectangle* customRects, int customRectCount) const { + bool lastResult = false; + int w = width, h = height; +#define TRY_PACK(scale) (lastResult = !tryPack(glyphs, count, customRects, customRectCount, msdf_atlas::DimensionsConstraint(), w, h, (scale))) + double minScale = 1, maxScale = 1; + if (TRY_PACK(1)) { + while (maxScale < 1e+32 && ((maxScale = 2 * minScale), TRY_PACK(maxScale))) + minScale = maxScale; + } + else { + while (minScale > 1e-32 && ((minScale = .5 * maxScale), !TRY_PACK(minScale))) + maxScale = minScale; + } + if (minScale == maxScale) + return 0; + while (minScale / maxScale < 1 - scaleMaximizationTolerance) { + double midScale = .5 * (minScale + maxScale); + if (TRY_PACK(midScale)) + minScale = midScale; + else + maxScale = midScale; + } + if (!lastResult) + TRY_PACK(minScale); + return minScale; + } +}; + +extern void ImFontAtlasBuildInit(ImFontAtlas* atlas); +extern void ImFontAtlasBuildFinish(ImFontAtlas* atlas); + static bool FontBuilder_Build(ImFontAtlas* atlas) { + ImFontAtlasBuildInit(atlas); + auto freeType = msdfgen::initializeFreetype(); std::vector glyphs; std::vector> ranges; + std::vector customRects; for (auto& config : atlas->ConfigData) { @@ -44,16 +197,25 @@ static bool FontBuilder_Build(ImFontAtlas* atlas) for (auto& glyph : glyphs) glyph.edgeColoring(&msdfgen::edgeColoringInkTrap, 3.0, 0); - msdf_atlas::TightAtlasPacker packer; - packer.setMinimumScale(32.0); - packer.setMiterLimit(1.0); - packer.setPixelRange(2.0); - packer.pack(glyphs.data(), glyphs.size()); + for (auto& customRect : atlas->CustomRects) + customRects.emplace_back(0, 0, int(customRect.Width), int(customRect.Height)); - int width = 0, height = 0; - packer.getDimensions(width, height); + TightAtlasPacker packer; + packer.spacing = 1; + packer.minScale = 16.0; + packer.miterLimit = 1.0; + packer.pxRange = 4.0; + packer.pack(glyphs.data(), glyphs.size(), customRects.data(), customRects.size()); - msdf_atlas::ImmediateAtlasGenerator> generator(width, height); + for (size_t i = 0; i < customRects.size(); i++) + { + auto& srcRect = customRects[i]; + auto& dstRect = atlas->CustomRects[i]; + dstRect.X = srcRect.x; + dstRect.Y = srcRect.y; + } + + msdf_atlas::ImmediateAtlasGenerator> generator(packer.width, packer.height); generator.generate(glyphs.data(), glyphs.size()); for (size_t i = 0; i < atlas->ConfigData.size(); i++) @@ -73,28 +235,39 @@ static bool FontBuilder_Build(ImFontAtlas* atlas) -y1 + config.DstFont->Ascent, x1, -y0 + config.DstFont->Ascent, - u0 / width, - v1 / height, - u1 / width, - v0 / height, + u0 / packer.width, + v1 / packer.height, + u1 / packer.width, + v0 / packer.height, glyph.getAdvance()); } config.DstFont->BuildLookupTable(); } - atlas->TexReady = true; - atlas->TexPixelsUseColors = true; - atlas->TexPixelsRGBA32 = (unsigned int*)IM_ALLOC(width * height * 4); - atlas->TexWidth = width; - atlas->TexHeight = height; - atlas->TexUvScale = { 1.0f / width, 1.0f / height }; + atlas->TexPixelsRGBA32 = (unsigned int*)IM_ALLOC(packer.width * packer.height * 4); + atlas->TexWidth = packer.width; + atlas->TexHeight = packer.height; + atlas->TexUvScale = { 1.0f / packer.width, 1.0f / packer.height }; - auto bitmapRef = (msdfgen::BitmapConstRef)generator.atlasStorage(); - memcpy(atlas->TexPixelsRGBA32, bitmapRef.pixels, width * height * 4); + auto bitmapRef = (msdfgen::BitmapConstRef)generator.atlasStorage(); + for (int y = 0; y < packer.height; y++) + { + for (int x = 0; x < packer.width; x++) + { + auto* srcPixels = bitmapRef(x, y); + auto* dstPixels = (uint8_t*)&atlas->TexPixelsRGBA32[y * packer.width + x]; + dstPixels[0] = srcPixels[0]; + dstPixels[1] = srcPixels[1]; + dstPixels[2] = srcPixels[2]; + dstPixels[3] = 0xFF; + } + } msdfgen::deinitializeFreetype(freeType); + ImFontAtlasBuildFinish(atlas); + return true; } diff --git a/UnleashedRecomp/gpu/imgui_snapshot.cpp b/UnleashedRecomp/gpu/imgui_snapshot.cpp index 67149061..85a5304a 100644 --- a/UnleashedRecomp/gpu/imgui_snapshot.cpp +++ b/UnleashedRecomp/gpu/imgui_snapshot.cpp @@ -252,12 +252,12 @@ void ImFontAtlasSnapshot::GenerateGlyphRanges() g_glyphRanges.push_back(0); } -ImFont* ImFontAtlasSnapshot::GetFont(const char* name, float size) +ImFont* ImFontAtlasSnapshot::GetFont(const char* name) { auto fontAtlas = ImGui::GetIO().Fonts; for (auto& configData : fontAtlas->ConfigData) { - if (strstr(configData.Name, name) != nullptr && abs(configData.SizePixels - size) < 0.001f) + if (strstr(configData.Name, name) != nullptr) { assert(configData.DstFont != nullptr); return configData.DstFont; @@ -268,5 +268,5 @@ ImFont* ImFontAtlasSnapshot::GetFont(const char* name, float size) assert(false && "Unable to locate equivalent font in the atlas file."); #endif - return fontAtlas->AddFontFromFileTTF(name, size, nullptr, g_glyphRanges.data()); + return fontAtlas->AddFontFromFileTTF(name, 1.0f, nullptr, g_glyphRanges.data()); } diff --git a/UnleashedRecomp/gpu/imgui_snapshot.h b/UnleashedRecomp/gpu/imgui_snapshot.h index 8613637e..70b539b2 100644 --- a/UnleashedRecomp/gpu/imgui_snapshot.h +++ b/UnleashedRecomp/gpu/imgui_snapshot.h @@ -58,5 +58,5 @@ struct ImFontAtlasSnapshot static void GenerateGlyphRanges(); // When ENABLE_IM_FONT_ATLAS_SNAPSHOT is undefined, this creates the font runtime instead. - static ImFont* GetFont(const char* name, float size); + static ImFont* GetFont(const char* name); }; diff --git a/UnleashedRecomp/gpu/shader/imgui_ps.hlsl b/UnleashedRecomp/gpu/shader/imgui_ps.hlsl index 6888b895..1db4f777 100644 --- a/UnleashedRecomp/gpu/shader/imgui_ps.hlsl +++ b/UnleashedRecomp/gpu/shader/imgui_ps.hlsl @@ -73,13 +73,34 @@ float4 PixelAntialiasing(float2 uvTexspace) return SampleLinear(uvTexspace); } +float ComputeScreenPixelRange(float2 texCoord) +{ + uint width, height; + g_Texture2DDescriptorHeap[g_PushConstants.Texture2DDescriptorIndex].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)); +} + float4 main(in Interpolators interpolators) : SV_Target { float4 color = interpolators.Color; color *= PixelAntialiasing(interpolators.Position.xy - 0.5); if (g_PushConstants.Texture2DDescriptorIndex != 0) - color *= g_Texture2DDescriptorHeap[g_PushConstants.Texture2DDescriptorIndex].Sample(g_SamplerDescriptorHeap[0], interpolators.UV); + { + float4 msd = g_Texture2DDescriptorHeap[g_PushConstants.Texture2DDescriptorIndex].Sample(g_SamplerDescriptorHeap[0], interpolators.UV); + float sd = median(msd.r, msd.g, msd.b) - 0.5; + float screenPixelDistance = ComputeScreenPixelRange(interpolators.UV) * sd; + color.a *= saturate(screenPixelDistance + 0.5); + color.a *= msd.a; + } if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_MARQUEE_FADE) { diff --git a/UnleashedRecomp/ui/achievement_menu.cpp b/UnleashedRecomp/ui/achievement_menu.cpp index b0928046..b590875e 100644 --- a/UnleashedRecomp/ui/achievement_menu.cpp +++ b/UnleashedRecomp/ui/achievement_menu.cpp @@ -765,11 +765,9 @@ void AchievementMenu::Init() { auto& io = ImGui::GetIO(); - constexpr float FONT_SCALE = 2.0f; - - g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE); - g_fntNewRodinDB = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-DB.otf", 20.0f * FONT_SCALE); - g_fntNewRodinUB = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf", 20.0f * FONT_SCALE); + g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf"); + g_fntNewRodinDB = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-DB.otf"); + g_fntNewRodinUB = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf"); g_upTrophyIcon = LOAD_ZSTD_TEXTURE(g_trophy); g_upSelectionCursor = LOAD_ZSTD_TEXTURE(g_select_fill); diff --git a/UnleashedRecomp/ui/achievement_overlay.cpp b/UnleashedRecomp/ui/achievement_overlay.cpp index 8bd398dc..efa78207 100644 --- a/UnleashedRecomp/ui/achievement_overlay.cpp +++ b/UnleashedRecomp/ui/achievement_overlay.cpp @@ -76,9 +76,7 @@ void AchievementOverlay::Init() { auto& io = ImGui::GetIO(); - constexpr float FONT_SCALE = 2.0f; - - g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE); + g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf"); g_upWindow = LOAD_ZSTD_TEXTURE(g_general_window); } diff --git a/UnleashedRecomp/ui/button_guide.cpp b/UnleashedRecomp/ui/button_guide.cpp index f415f9cf..275d4acb 100644 --- a/UnleashedRecomp/ui/button_guide.cpp +++ b/UnleashedRecomp/ui/button_guide.cpp @@ -214,10 +214,8 @@ void ButtonGuide::Init() { auto& io = ImGui::GetIO(); - constexpr float FONT_SCALE = 2.0f; - - g_fntNewRodin = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-M.otf", 24.0f * FONT_SCALE); - g_fntNewRodinLQ = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-M.otf", 19.0f); + 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/installer_wizard.cpp b/UnleashedRecomp/ui/installer_wizard.cpp index e090e6bb..3ab17246 100644 --- a/UnleashedRecomp/ui/installer_wizard.cpp +++ b/UnleashedRecomp/ui/installer_wizard.cpp @@ -1334,10 +1334,9 @@ static void DrawMessagePrompt() void InstallerWizard::Init() { auto &io = ImGui::GetIO(); - constexpr float FONT_SCALE = 2.0f; - g_seuratFont = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE); - g_dfsogeistdFont = ImFontAtlasSnapshot::GetFont("DFSoGeiStd-W7.otf", 48.0f * FONT_SCALE); - g_newRodinFont = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-DB.otf", 20.0f * FONT_SCALE); + g_seuratFont = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf"); + g_dfsogeistdFont = ImFontAtlasSnapshot::GetFont("DFSoGeiStd-W7.otf"); + g_newRodinFont = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-DB.otf"); g_installTextures[0] = LOAD_ZSTD_TEXTURE(g_install_001); g_installTextures[1] = LOAD_ZSTD_TEXTURE(g_install_002); g_installTextures[2] = LOAD_ZSTD_TEXTURE(g_install_003); diff --git a/UnleashedRecomp/ui/message_window.cpp b/UnleashedRecomp/ui/message_window.cpp index d75787cf..d00f6106 100644 --- a/UnleashedRecomp/ui/message_window.cpp +++ b/UnleashedRecomp/ui/message_window.cpp @@ -256,9 +256,7 @@ void MessageWindow::Init() { auto& io = ImGui::GetIO(); - constexpr float FONT_SCALE = 2.0f; - - g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE); + g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf"); g_upSelectionCursor = LOAD_ZSTD_TEXTURE(g_select_fade); g_upWindow = LOAD_ZSTD_TEXTURE(g_general_window); diff --git a/UnleashedRecomp/ui/options_menu.cpp b/UnleashedRecomp/ui/options_menu.cpp index ea63d655..421e525d 100644 --- a/UnleashedRecomp/ui/options_menu.cpp +++ b/UnleashedRecomp/ui/options_menu.cpp @@ -990,11 +990,9 @@ void OptionsMenu::Init() { auto& io = ImGui::GetIO(); - constexpr float FONT_SCALE = 2.0f; - - g_seuratFont = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE); - g_dfsogeistdFont = ImFontAtlasSnapshot::GetFont("DFSoGeiStd-W7.otf", 48.0f * FONT_SCALE); - g_newRodinFont = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-DB.otf", 20.0f * FONT_SCALE); + g_seuratFont = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf"); + g_dfsogeistdFont = ImFontAtlasSnapshot::GetFont("DFSoGeiStd-W7.otf"); + g_newRodinFont = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-DB.otf"); LoadThumbnails(); }