diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 10b4b116f..fb88c8256 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -37,14 +37,23 @@ void Font::GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback) { // Try to get the character or cache it if cannot be found const auto key = Pair(_asset->GetOptions().RasterMode == FontRasterMode::MSDF ? _asset->GetOptions().MSDFSize : GetSize(), c); - if (!_asset->_characterCache.TryGet(key, result)) + if (_asset->_characterCache.TryGet(key, result)) + { + // With MSDF font introduced, cached entry may be created by a different font (with same MSDFSize) + // This is to ensure returned entry has a reference to a font whose size matches the font being used to render + result.Font = this; + } + else { // This thread race condition may happen in editor but in game we usually do all stuff with fonts on main thread (chars caching) ScopeLock lock(_asset->Locker); // Handle situation when more than one thread wants to get the same character if (_asset->_characterCache.TryGet(key, result)) + { + result.Font = this; return; + } // Try to use fallback font if character is missing if (enableFallback && !_asset->ContainsChar(c)) @@ -52,13 +61,9 @@ void Font::GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback) for (int32 fallbackIndex = 0; fallbackIndex < FallbackFonts.Count(); fallbackIndex++) { FontAsset* fallbackFont = FallbackFonts.Get()[fallbackIndex].Get(); - if (fallbackFont && _asset->GetOptions().RasterMode == FontRasterMode::MSDF) - { - fallbackFont = fallbackFont->GetMSDF(); - } if (fallbackFont && fallbackFont->ContainsChar(c)) { - fallbackFont->CreateFont(GetSize())->GetCharacter(c, result, enableFallback); + fallbackFont->GetRasterMode(_asset->GetOptions().RasterMode)->CreateFont(GetSize())->GetCharacter(c, result, enableFallback); return; } } @@ -127,6 +132,11 @@ void Font::Invalidate() _kerningTable.Clear(); } +float Font::GetScale(float layoutScale) const +{ + return layoutScale / FontManager::FontScale * (_asset->GetOptions().RasterMode == FontRasterMode::MSDF ? _size / _asset->GetOptions().MSDFSize : 1.0f); +} + void Font::ProcessText(const StringView& text, Array>& outputLines, const TextLayoutOptions& layout) { int32 textLength = text.Length(); @@ -137,7 +147,7 @@ void Font::ProcessText(const StringView& text, ArrayGetOptions().RasterMode == FontRasterMode::MSDF ? _size / _asset->GetOptions().MSDFSize : 1.0f); + const float scale = GetScale(layout.Scale); float boundsWidth = layout.Bounds.GetWidth(); float baseLinesDistance = static_cast(_height) * layout.BaseLinesGapScale * scale; tmpLine.Location = Float2::Zero; @@ -181,6 +191,7 @@ void Font::ProcessText(const StringView& text, ArrayGetScale(layout.Scale); // Get kerning if (!isWhitespace && previous.IsValid) @@ -192,7 +203,7 @@ void Font::ProcessText(const StringView& text, Array> lines; ProcessText(text, lines, layout); ASSERT(lines.HasItems()); - float scale = layout.Scale / FontManager::FontScale * (_asset->GetOptions().RasterMode == FontRasterMode::MSDF ? _size / _asset->GetOptions().MSDFSize : 1.0f); + const float scale = GetScale(layout.Scale); float baseLinesDistance = static_cast(_height) * layout.BaseLinesGapScale * scale; // Offset position to match lines origin space @@ -364,12 +375,13 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te // Cache current character const Char currentChar = text[currentIndex]; GetCharacter(currentChar, entry); + const float entryScale = entry.Font->GetScale(layout.Scale); const bool isWhitespace = StringUtils::IsWhitespace(currentChar); // Apply kerning if (!isWhitespace && previous.IsValid) { - x += entry.Font->GetKerning(previous.Character, entry.Character); + x += entry.Font->GetKerning(previous.Character, entry.Character) * entryScale; } previous = entry; @@ -388,7 +400,7 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te } // Move - x += entry.AdvanceX * scale; + x += entry.AdvanceX * entryScale; } // Test line end edge @@ -431,7 +443,7 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo Array> lines; ProcessText(text, lines, layout); ASSERT(lines.HasItems()); - float scale = layout.Scale / FontManager::FontScale * (_asset->GetOptions().RasterMode == FontRasterMode::MSDF ? _size / _asset->GetOptions().MSDFSize : 1.0f); + const float scale = GetScale(layout.Scale); float baseLinesDistance = static_cast(_height) * layout.BaseLinesGapScale * scale; // Find line with that position @@ -452,17 +464,18 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo // Cache current character const Char currentChar = text[currentIndex]; GetCharacter(currentChar, entry); + const float entryScale = entry.Font->GetScale(layout.Scale); const bool isWhitespace = StringUtils::IsWhitespace(currentChar); // Apply kerning if (!isWhitespace && previous.IsValid) { - charPos.X += entry.Font->GetKerning(previous.Character, entry.Character); + charPos.X += entry.Font->GetKerning(previous.Character, entry.Character) * entryScale; } previous = entry; // Move - charPos.X += entry.AdvanceX * scale; + charPos.X += entry.AdvanceX * entryScale; } // Upper left corner of the character diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index ace21e64a..c8fd960d0 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -234,6 +234,13 @@ public: API_FUNCTION() void Invalidate(); public: + /// + /// Gets the scale factor that maps the rasterized font size to the actual rendered size. + /// + /// The layout scale. + /// The scale factor. + float GetScale(float layoutScale) const; + /// /// Processes text to get cached lines for rendering. /// diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index 39fe89207..adc641f0d 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -69,7 +69,7 @@ void FontAsset::unload(bool isReloading) _fontFile.Release(); _virtualBold = nullptr; _virtualItalic = nullptr; - _virtualMSDF = nullptr; + _virtualRasterMode = nullptr; } AssetChunksFlag FontAsset::getChunksToPreload() const @@ -115,11 +115,11 @@ void FontAsset::SetOptions(const FontOptions& value) options.Flags |= FontFlags::Italic; _virtualItalic->SetOptions(options); } - if (_virtualMSDF) + if (_virtualRasterMode) { auto options = _options; - options.RasterMode = FontRasterMode::MSDF; - _virtualMSDF->SetOptions(options); + options.RasterMode = _options.RasterMode == FontRasterMode::MSDF ? FontRasterMode::Bitmap : FontRasterMode::MSDF; + _virtualRasterMode->SetOptions(options); } } @@ -176,20 +176,20 @@ FontAsset* FontAsset::GetItalic() return _virtualItalic; } -FontAsset* FontAsset::GetMSDF() +FontAsset* FontAsset::GetRasterMode(FontRasterMode rasterMode) { ScopeLock lock(Locker); - if (_options.RasterMode == FontRasterMode::MSDF) + if (_options.RasterMode == rasterMode) return this; - if (!_virtualMSDF) + if (!_virtualRasterMode) { - _virtualMSDF = Content::CreateVirtualAsset(); - _virtualMSDF->Init(_fontFile); + _virtualRasterMode = Content::CreateVirtualAsset(); + _virtualRasterMode->Init(_fontFile); auto options = _options; - options.RasterMode = FontRasterMode::MSDF; - _virtualMSDF->SetOptions(options); + options.RasterMode = rasterMode; + _virtualRasterMode->SetOptions(options); } - return _virtualMSDF; + return _virtualRasterMode; } bool FontAsset::Init(const BytesContainer& fontFile) diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h index fe4458f43..0529469b9 100644 --- a/Source/Engine/Render2D/FontAsset.h +++ b/Source/Engine/Render2D/FontAsset.h @@ -233,7 +233,7 @@ private: Dictionary, FontCharacterEntry> _characterCache; AssetReference _virtualBold; AssetReference _virtualItalic; - AssetReference _virtualMSDF; + AssetReference _virtualRasterMode; public: /// @@ -293,10 +293,10 @@ public: API_FUNCTION() FontAsset* GetItalic(); /// - /// Gets the MSDF version of the font. Returns itself or creates a new virtual font asset using this font but rasterized with Multi-channel Signed Distance Field (MSDF). + /// Gets the different rasterization mode of the font. Returns itself or creates a new virtual font asset using this font but rasterized with the specified mode. /// /// The virtual font or this. - API_FUNCTION() FontAsset* GetMSDF(); + API_FUNCTION() FontAsset* GetRasterMode(FontRasterMode rasterMode); /// /// Initializes the font with a custom font file data. diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index efabc4b58..de6ad9215 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -1204,7 +1204,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, FontCharacterEntry previous; int32 kerning; FontOptions options = font->GetAsset()->GetOptions(); - float scale = 1.0f / FontManager::FontScale * (options.RasterMode == FontRasterMode::MSDF ? font->GetSize() / options.MSDFSize : 1.0f); + const float scale = font->GetScale(1.0f); const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts); // Render all characters @@ -1231,6 +1231,8 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, { // Get character entry font->GetCharacter(currentChar, entry, enableFallbackFonts); + // Fallback fonts may have different MSDFSize, so we need to calculate scale per character + const float entryScale = entry.Font->GetScale(1.0f); // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) @@ -1263,17 +1265,17 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, { kerning = 0; } - pointer.X += kerning * scale; + pointer.X += kerning * entryScale; previous = entry; // Omit whitespace characters if (!isWhitespace) { // Calculate character size and atlas coordinates - const float x = pointer.X + entry.OffsetX * scale; - const float y = pointer.Y + (font->GetHeight() + font->GetDescender() - entry.OffsetY) * scale; + const float x = pointer.X + entry.OffsetX * entryScale; + const float y = pointer.Y - entry.OffsetY * entryScale + (font->GetHeight() + font->GetDescender()) * scale; - Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale); + Rectangle charRect(x, y, entry.UVSize.X * entryScale, entry.UVSize.Y * entryScale); Float2 upperLeftUV = entry.UV * invAtlasSize; Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize; @@ -1286,7 +1288,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, } // Move - pointer.X += entry.AdvanceX * scale; + pointer.X += entry.AdvanceX * entryScale; } else { @@ -1320,7 +1322,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, FontCharacterEntry previous; int32 kerning; FontOptions options = font->GetAsset()->GetOptions(); - float scale = layout.Scale / FontManager::FontScale * (options.RasterMode == FontRasterMode::MSDF ? font->GetSize() / options.MSDFSize : 1.0f); + const float scale = font->GetScale(layout.Scale); const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts); // Process text to get lines @@ -1356,6 +1358,8 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, { // Get character entry font->GetCharacter(currentChar, entry, enableFallbackFonts); + // Fallback fonts may have different MSDFSize, so we need to calculate scale per character + const float entryScale = entry.Font->GetScale(layout.Scale); // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) @@ -1386,17 +1390,17 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, { kerning = 0; } - pointer.X += (float)kerning * scale; + pointer.X += (float)kerning * entryScale; previous = entry; // Omit whitespace characters if (!isWhitespace) { // Calculate character size and atlas coordinates - const float x = pointer.X + entry.OffsetX * scale; - const float y = pointer.Y - entry.OffsetY * scale + Math::Ceil((font->GetHeight() + font->GetDescender()) * scale); + const float x = pointer.X + entry.OffsetX * entryScale; + const float y = pointer.Y - entry.OffsetY * entryScale + Math::Ceil((font->GetHeight() + font->GetDescender()) * scale); - Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale); + Rectangle charRect(x, y, entry.UVSize.X * entryScale, entry.UVSize.Y * entryScale); charRect.Offset(layout.Bounds.Location); Float2 upperLeftUV = entry.UV * invAtlasSize; @@ -1410,7 +1414,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, } // Move - pointer.X += entry.AdvanceX * scale; + pointer.X += entry.AdvanceX * entryScale; } } } diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 88e5f8b2b..ef95d27c4 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -172,7 +172,7 @@ void TextRender::UpdateLayout() // Pick a font (remove DPI text scale as the text is being placed in the world) auto font = Font->CreateFont(_size); - float scale = _layoutOptions.Scale / FontManager::FontScale * (Font->GetOptions().RasterMode == FontRasterMode::MSDF ? _size / Font->GetOptions().MSDFSize : 1.0f); + const float scale = font->GetScale(_layoutOptions.Scale); // Prepare FontTextureAtlas* fontAtlas = nullptr; @@ -217,6 +217,7 @@ void TextRender::UpdateLayout() if (c != '\n') { font->GetCharacter(c, entry); + const float entryScale = entry.Font->GetScale(_layoutOptions.Scale); // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) if (fontAtlas == nullptr || entry.TextureIndex != drawChunk.FontAtlasIndex) @@ -273,17 +274,17 @@ void TextRender::UpdateLayout() { kerning = 0; } - pointer.X += (float)kerning * scale; + pointer.X += (float)kerning * entryScale; previous = entry; // Omit whitespace characters if (!isWhitespace) { // Calculate character size and atlas coordinates - const float x = pointer.X + (float)entry.OffsetX * scale; - const float y = pointer.Y + (float)(font->GetHeight() + font->GetDescender() - entry.OffsetY) * scale; + const float x = pointer.X + (float)entry.OffsetX * entryScale; + const float y = pointer.Y - (float)entry.OffsetY * entryScale + (float)(font->GetHeight() + font->GetDescender()) * scale; - Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale); + Rectangle charRect(x, y, entry.UVSize.X * entryScale, entry.UVSize.Y * entryScale); charRect.Offset(_layoutOptions.Bounds.Location); Float2 upperLeftUV = entry.UV * invAtlasSize; @@ -326,7 +327,7 @@ void TextRender::UpdateLayout() } // Move - pointer.X += (float)entry.AdvanceX * scale; + pointer.X += (float)entry.AdvanceX * entryScale; } } }