mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-09 07:41:06 +00:00
Add support for custom box drawing and powerline glyphs (#16729)
This adds support for drawing our own box drawing, block element, and basic Powerline (U+E0Bx) glyphs in AtlasEngine. This PR consists of 4 parts: * AtlasEngine was refactored to simplify `_drawGlyph` because I've wanted to do that for ~1 year now and never got a chance. Well, now I'm doing it and you all will review it muahahaha. The good news is that it removes a goto usage that even for my standards was rather dangerous. Now it's gone and the risk with it. * AtlasEngine was further refactored to properly parse out text that we want to handle different from regular text. Previously, we only did that for soft fonts, but now we want to do that for a lot more, so a refactor was in order. The new code is still extremely disgusting, because I now stuff `wchar_t`s into an array that's intended for glyph indices, but that's the best way to make it fast and not blow up the complexity of the code even further. * Finally this adds a huge LUT for all the aforementioned glyphs. The LUT has 4 "drawing instruction" entries per glyph which describe the shape (rectangle, circle, lines, etc.) and the start/end coord. With a lot of bit packing each entry is only 4 bytes large. * Finally-finally a `builtinGlyphs` setting was added to the font object and it defaults to `true`. Closes #5897 ## Validation Steps Performed * RenderingTests with soft fonts ✅ * All the aforementioned glyphs ✅ * ...with color ✅ * `customGlyphs` setting can be toggled on and off ✅
This commit is contained in:
@@ -860,6 +860,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
const auto lock = _terminal->LockForWriting();
|
||||
|
||||
_builtinGlyphs = _settings->EnableBuiltinGlyphs();
|
||||
_cellWidth = CSSLengthPercentage::FromString(_settings->CellWidth().c_str());
|
||||
_cellHeight = CSSLengthPercentage::FromString(_settings->CellHeight().c_str());
|
||||
_runtimeOpacity = std::nullopt;
|
||||
@@ -1038,6 +1039,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_actualFont = { fontFace, 0, fontWeight.Weight, _desiredFont.GetEngineSize(), CP_UTF8, false };
|
||||
_actualFontFaceName = { fontFace };
|
||||
|
||||
_desiredFont.SetEnableBuiltinGlyphs(_builtinGlyphs);
|
||||
_desiredFont.SetCellSize(_cellWidth, _cellHeight);
|
||||
|
||||
const auto before = _actualFont.GetSize();
|
||||
|
||||
@@ -316,6 +316,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
FontInfoDesired _desiredFont;
|
||||
FontInfo _actualFont;
|
||||
winrt::hstring _actualFontFaceName;
|
||||
bool _builtinGlyphs = true;
|
||||
CSSLengthPercentage _cellWidth;
|
||||
CSSLengthPercentage _cellHeight;
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace Microsoft.Terminal.Control
|
||||
String Padding { get; };
|
||||
Windows.Foundation.Collections.IMap<String, UInt32> FontFeatures { get; };
|
||||
Windows.Foundation.Collections.IMap<String, Single> FontAxes { get; };
|
||||
Boolean EnableBuiltinGlyphs { get; };
|
||||
String CellWidth { get; };
|
||||
String CellHeight { get; };
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontFace);
|
||||
OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontSize);
|
||||
OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontWeight);
|
||||
OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), EnableBuiltinGlyphs);
|
||||
|
||||
OBSERVABLE_PROJECTED_SETTING(_appearance, RetroTerminalEffect);
|
||||
OBSERVABLE_PROJECTED_SETTING(_appearance, CursorShape);
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Single, FontSize);
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Double, LineHeight);
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.UI.Text.FontWeight, FontWeight);
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Boolean, EnableBuiltinGlyphs);
|
||||
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, DarkColorSchemeName);
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, LightColorSchemeName);
|
||||
|
||||
@@ -287,6 +287,15 @@
|
||||
</StackPanel>
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Builtin Glyphs -->
|
||||
<local:SettingContainer x:Uid="Profile_EnableBuiltinGlyphs"
|
||||
ClearSettingValue="{x:Bind Appearance.ClearEnableBuiltinGlyphs}"
|
||||
HasSettingValue="{x:Bind Appearance.HasEnableBuiltinGlyphs, Mode=OneWay}"
|
||||
SettingOverrideSource="{x:Bind Appearance.EnableBuiltinGlyphsOverrideSource, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind Appearance.EnableBuiltinGlyphs, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Retro Terminal Effect -->
|
||||
<local:SettingContainer x:Uid="Profile_RetroTerminalEffect"
|
||||
ClearSettingValue="{x:Bind Appearance.ClearRetroTerminalEffect}"
|
||||
|
||||
@@ -886,6 +886,14 @@
|
||||
<value>1.2</value>
|
||||
<comment>"1.2" is a decimal number.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.Header" xml:space="preserve">
|
||||
<value>Builtin Glyphs</value>
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>When enabled, the terminal draws custom glyphs for block element and box drawing characters instead of using the font. This feature only works when GPU Acceleration is available.</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_FontWeightComboBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Font weight</value>
|
||||
<comment>Name for a control to select the weight (i.e. bold, thin, etc.) of the text in the app.</comment>
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
INHERITABLE_FONT_SETTING(Windows.UI.Text.FontWeight, FontWeight);
|
||||
INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap<String COMMA UInt32>, FontFeatures);
|
||||
INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap<String COMMA Single>, FontAxes);
|
||||
INHERITABLE_FONT_SETTING(Boolean, EnableBuiltinGlyphs);
|
||||
INHERITABLE_FONT_SETTING(String, CellWidth);
|
||||
INHERITABLE_FONT_SETTING(String, CellHeight);
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@ Author(s):
|
||||
X(winrt::Windows::UI::Text::FontWeight, FontWeight, "weight", DEFAULT_FONT_WEIGHT) \
|
||||
X(IFontAxesMap, FontAxes, "axes") \
|
||||
X(IFontFeatureMap, FontFeatures, "features") \
|
||||
X(bool, EnableBuiltinGlyphs, "builtinGlyphs", true) \
|
||||
X(winrt::hstring, CellWidth, "cellWidth") \
|
||||
X(winrt::hstring, CellHeight, "cellHeight")
|
||||
|
||||
|
||||
@@ -289,6 +289,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
_FontWeight = fontInfo.FontWeight();
|
||||
_FontFeatures = fontInfo.FontFeatures();
|
||||
_FontAxes = fontInfo.FontAxes();
|
||||
_EnableBuiltinGlyphs = fontInfo.EnableBuiltinGlyphs();
|
||||
_CellWidth = fontInfo.CellWidth();
|
||||
_CellHeight = fontInfo.CellHeight();
|
||||
_Padding = profile.Padding();
|
||||
|
||||
@@ -129,6 +129,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, winrt::Windows::UI::Text::FontWeight, FontWeight);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, IFontAxesMap, FontAxes);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, IFontFeatureMap, FontFeatures);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, bool, EnableBuiltinGlyphs, true);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, hstring, CellWidth);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, hstring, CellHeight);
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
X(winrt::Windows::UI::Text::FontWeight, FontWeight) \
|
||||
X(IFontFeatureMap, FontFeatures) \
|
||||
X(IFontAxesMap, FontAxes) \
|
||||
X(bool, EnableBuiltinGlyphs, true) \
|
||||
X(winrt::hstring, CellWidth) \
|
||||
X(winrt::hstring, CellHeight) \
|
||||
X(winrt::Microsoft::Terminal::Control::IKeyBindings, KeyBindings, nullptr) \
|
||||
|
||||
@@ -64,6 +64,7 @@ SCREEN_INFORMATION::SCREEN_INFORMATION(
|
||||
{
|
||||
OutputMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
}
|
||||
_desiredFont.SetEnableBuiltinGlyphs(gci.GetEnableBuiltinGlyphs());
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -539,7 +540,10 @@ void SCREEN_INFORMATION::RefreshFontWithRenderer()
|
||||
|
||||
void SCREEN_INFORMATION::UpdateFont(const FontInfo* const pfiNewFont)
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
|
||||
FontInfoDesired fiDesiredFont(*pfiNewFont);
|
||||
fiDesiredFont.SetEnableBuiltinGlyphs(gci.GetEnableBuiltinGlyphs());
|
||||
|
||||
GetDesiredFont() = fiDesiredFont;
|
||||
|
||||
|
||||
@@ -776,3 +776,8 @@ bool Settings::GetCopyColor() const noexcept
|
||||
{
|
||||
return _fCopyColor;
|
||||
}
|
||||
|
||||
bool Settings::GetEnableBuiltinGlyphs() const noexcept
|
||||
{
|
||||
return _fEnableBuiltinGlyphs;
|
||||
}
|
||||
|
||||
@@ -171,6 +171,7 @@ public:
|
||||
|
||||
bool GetUseDx() const noexcept;
|
||||
bool GetCopyColor() const noexcept;
|
||||
bool GetEnableBuiltinGlyphs() const noexcept;
|
||||
|
||||
private:
|
||||
RenderSettings _renderSettings;
|
||||
@@ -214,6 +215,7 @@ private:
|
||||
DWORD _dwVirtTermLevel;
|
||||
bool _fUseDx;
|
||||
bool _fCopyColor;
|
||||
bool _fEnableBuiltinGlyphs = true;
|
||||
|
||||
// this is used for the special STARTF_USESIZE mode.
|
||||
bool _fUseWindowSizePixels;
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
#pragma once
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
|
||||
#pragma warning(disable : 26409) // Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11).
|
||||
#pragma warning(disable : 26432) // If you define or delete any default operation in the type '...', define or delete them all (c.21).
|
||||
#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
|
||||
|
||||
namespace til
|
||||
{
|
||||
@@ -36,7 +37,7 @@ namespace til
|
||||
// * small and cheap T
|
||||
// * >= 50% successful lookups
|
||||
// * <= 50% load factor (LoadFactor >= 2, which is the minimum anyways)
|
||||
template<typename T, size_t LoadFactor = 2, size_t GrowthExponent = 1>
|
||||
template<typename T, typename Traits, size_t LoadFactor = 2, size_t GrowthExponent = 1>
|
||||
struct linear_flat_set
|
||||
{
|
||||
static_assert(LoadFactor >= 2);
|
||||
@@ -98,27 +99,28 @@ namespace til
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto hash = ::std::hash<T>{}(key) >> _shift;
|
||||
const auto hash = Traits::hash(key) >> _shift;
|
||||
|
||||
for (auto i = hash;; ++i)
|
||||
{
|
||||
auto& slot = _map[i & _mask];
|
||||
if (!slot)
|
||||
if (!Traits::occupied(slot))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (slot == key) [[likely]]
|
||||
if (Traits::equals(slot, key)) [[likely]]
|
||||
{
|
||||
return &slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: It also does not initialize the returned slot.
|
||||
// You must do that yourself in way that ensures that Traits::occupied(slot) now returns true.
|
||||
// Use lookup() to check if the item already exists.
|
||||
template<typename U>
|
||||
std::pair<T&, bool> insert(U&& key)
|
||||
std::pair<T*, bool> insert(U&& key)
|
||||
{
|
||||
// Putting this into the lookup path is a little pessimistic, but it
|
||||
// allows us to default-construct this hashmap with a size of 0.
|
||||
if (_load >= _capacity) [[unlikely]]
|
||||
{
|
||||
_bumpSize();
|
||||
@@ -129,20 +131,20 @@ namespace til
|
||||
// many times in literature that such a scheme performs the best on average.
|
||||
// As such, we perform the divide here to get the topmost bits down.
|
||||
// See flat_set_hash_integer.
|
||||
const auto hash = ::std::hash<T>{}(key) >> _shift;
|
||||
const auto hash = Traits::hash(key) >> _shift;
|
||||
|
||||
for (auto i = hash;; ++i)
|
||||
{
|
||||
auto& slot = _map[i & _mask];
|
||||
if (!slot)
|
||||
if (!Traits::occupied(slot))
|
||||
{
|
||||
slot = std::forward<U>(key);
|
||||
_load += LoadFactor;
|
||||
return { slot, true };
|
||||
Traits::assign(slot, key);
|
||||
return { &slot, true };
|
||||
}
|
||||
if (slot == key) [[likely]]
|
||||
if (Traits::equals(slot, key)) [[likely]]
|
||||
{
|
||||
return { slot, false };
|
||||
return { &slot, false };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,17 +168,17 @@ namespace til
|
||||
// This mirrors the insert() function, but without the lookup part.
|
||||
for (auto& oldSlot : container())
|
||||
{
|
||||
if (!oldSlot)
|
||||
if (!Traits::occupied(oldSlot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto hash = ::std::hash<T>{}(oldSlot) >> newShift;
|
||||
const auto hash = Traits::hash(oldSlot) >> newShift;
|
||||
|
||||
for (auto i = hash;; ++i)
|
||||
{
|
||||
auto& slot = newMap[i & newMask];
|
||||
if (!slot)
|
||||
if (!Traits::occupied(slot))
|
||||
{
|
||||
slot = std::move_if_noexcept(oldSlot);
|
||||
break;
|
||||
|
||||
@@ -10,21 +10,28 @@ namespace til
|
||||
inline constexpr wchar_t UNICODE_REPLACEMENT = 0xFFFD;
|
||||
}
|
||||
|
||||
static constexpr bool is_surrogate(const wchar_t wch) noexcept
|
||||
constexpr bool is_surrogate(const auto wch) noexcept
|
||||
{
|
||||
return (wch & 0xF800) == 0xD800;
|
||||
}
|
||||
|
||||
static constexpr bool is_leading_surrogate(const wchar_t wch) noexcept
|
||||
constexpr bool is_leading_surrogate(const auto wch) noexcept
|
||||
{
|
||||
return (wch & 0xFC00) == 0xD800;
|
||||
}
|
||||
|
||||
static constexpr bool is_trailing_surrogate(const wchar_t wch) noexcept
|
||||
constexpr bool is_trailing_surrogate(const auto wch) noexcept
|
||||
{
|
||||
return (wch & 0xFC00) == 0xDC00;
|
||||
}
|
||||
|
||||
constexpr char32_t combine_surrogates(const auto lead, const auto trail)
|
||||
{
|
||||
// Ah, I love these bracketed C-style casts. I use them in C all the time. Yep.
|
||||
#pragma warning(suppress : 26493) // Don't use C-style casts (type.4).
|
||||
return (char32_t{ lead } << 10) - 0x35FDC00 + char32_t{ trail };
|
||||
}
|
||||
|
||||
// Verifies the beginning of the given UTF16 string and returns the first UTF16 sequence
|
||||
// or U+FFFD otherwise. It's not really useful and at the time of writing only a
|
||||
// single caller uses this. It's best to delete this if you read this comment.
|
||||
|
||||
@@ -61,7 +61,10 @@ const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMa
|
||||
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_INTERCEPTCOPYPASTE, SET_FIELD_AND_SIZE(_fInterceptCopyPaste) },
|
||||
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_TERMINALSCROLLING, SET_FIELD_AND_SIZE(_TerminalScrolling) },
|
||||
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) },
|
||||
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) }
|
||||
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) },
|
||||
#if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED
|
||||
{ _RegPropertyType::Boolean, L"EnableBuiltinGlyphs", SET_FIELD_AND_SIZE(_fEnableBuiltinGlyphs) },
|
||||
#endif
|
||||
|
||||
// Special cases that are handled manually in Registry::LoadFromRegistry:
|
||||
// - CONSOLE_REGISTRY_WINDOWPOS
|
||||
|
||||
@@ -295,8 +295,8 @@ CATCH_RETURN()
|
||||
/* fontStyle */ DWRITE_FONT_STYLE_NORMAL,
|
||||
/* fontStretch */ DWRITE_FONT_STRETCH_NORMAL,
|
||||
/* fontSize */ _api.s->font->fontSize,
|
||||
/* localeName */ L"",
|
||||
/* textFormat */ textFormat.put()));
|
||||
/* localeName */ _p.userLocaleName.c_str(),
|
||||
/* textFormat */ textFormat.addressof()));
|
||||
|
||||
wil::com_ptr<IDWriteTextLayout> textLayout;
|
||||
RETURN_IF_FAILED(_p.dwriteFactory->CreateTextLayout(glyph.data(), gsl::narrow_cast<uint32_t>(glyph.size()), textFormat.get(), FLT_MAX, FLT_MAX, textLayout.addressof()));
|
||||
@@ -774,5 +774,7 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
|
||||
fontMetrics->doubleUnderline[0] = { doubleUnderlinePosTopU16, thinLineWidthU16 };
|
||||
fontMetrics->doubleUnderline[1] = { doubleUnderlinePosBottomU16, thinLineWidthU16 };
|
||||
fontMetrics->overline = { 0, underlineWidthU16 };
|
||||
|
||||
fontMetrics->builtinGlyphs = fontInfoDesired.GetEnableBuiltinGlyphs();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
#include "pch.h"
|
||||
#include "AtlasEngine.h"
|
||||
|
||||
#include <til/unicode.h>
|
||||
|
||||
#include "Backend.h"
|
||||
#include "BuiltinGlyphs.h"
|
||||
#include "DWriteTextAnalysis.h"
|
||||
#include "../../interactivity/win32/CustomWindowMessages.h"
|
||||
|
||||
@@ -20,6 +23,7 @@
|
||||
#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
|
||||
#pragma warning(disable : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe [...].
|
||||
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
|
||||
#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
|
||||
|
||||
using namespace Microsoft::Console::Render::Atlas;
|
||||
@@ -70,8 +74,9 @@ try
|
||||
_handleSettingsUpdate();
|
||||
}
|
||||
|
||||
if constexpr (ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION)
|
||||
if (ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION || _hackTriggerRedrawAll)
|
||||
{
|
||||
_hackTriggerRedrawAll = false;
|
||||
_api.invalidatedRows = invalidatedRowsAll;
|
||||
_api.scrollOffset = 0;
|
||||
}
|
||||
@@ -575,7 +580,7 @@ void AtlasEngine::_recreateFontDependentResources()
|
||||
memcpy(&localeName[0], L"en-US", 12);
|
||||
}
|
||||
|
||||
_api.userLocaleName = std::wstring{ &localeName[0] };
|
||||
_p.userLocaleName = std::wstring{ &localeName[0] };
|
||||
}
|
||||
|
||||
if (_p.s->font->fontAxisValues.empty())
|
||||
@@ -606,6 +611,8 @@ void AtlasEngine::_recreateFontDependentResources()
|
||||
_api.textFormatAxes[i] = { fontAxisValues.data(), fontAxisValues.size() };
|
||||
}
|
||||
}
|
||||
|
||||
_hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !_hackIsBackendD2D;
|
||||
}
|
||||
|
||||
void AtlasEngine::_recreateCellCountDependentResources()
|
||||
@@ -666,14 +673,65 @@ void AtlasEngine::_flushBufferLine()
|
||||
// This would seriously blow us up otherwise.
|
||||
Expects(_api.bufferLineColumn.size() == _api.bufferLine.size() + 1);
|
||||
|
||||
const auto beg = _api.bufferLine.data();
|
||||
const auto len = _api.bufferLine.size();
|
||||
size_t segmentBeg = 0;
|
||||
size_t segmentEnd = 0;
|
||||
bool custom = false;
|
||||
|
||||
if (!_hackWantsBuiltinGlyphs)
|
||||
{
|
||||
_mapRegularText(0, len);
|
||||
return;
|
||||
}
|
||||
|
||||
while (segmentBeg < len)
|
||||
{
|
||||
segmentEnd = segmentBeg;
|
||||
do
|
||||
{
|
||||
auto i = segmentEnd;
|
||||
char32_t codepoint = beg[i++];
|
||||
if (til::is_leading_surrogate(codepoint) && i < len)
|
||||
{
|
||||
codepoint = til::combine_surrogates(codepoint, beg[i++]);
|
||||
}
|
||||
|
||||
const auto c = BuiltinGlyphs::IsBuiltinGlyph(codepoint) || BuiltinGlyphs::IsSoftFontChar(codepoint);
|
||||
if (custom != c)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
segmentEnd = i;
|
||||
} while (segmentEnd < len);
|
||||
|
||||
if (segmentBeg != segmentEnd)
|
||||
{
|
||||
if (custom)
|
||||
{
|
||||
_mapBuiltinGlyphs(segmentBeg, segmentEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mapRegularText(segmentBeg, segmentEnd);
|
||||
}
|
||||
}
|
||||
|
||||
segmentBeg = segmentEnd;
|
||||
custom = !custom;
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasEngine::_mapRegularText(size_t offBeg, size_t offEnd)
|
||||
{
|
||||
auto& row = *_p.rows[_api.lastPaintBufferLineCoord.y];
|
||||
|
||||
#pragma warning(suppress : 26494) // Variable 'mappedEnd' is uninitialized. Always initialize an object (type.5).
|
||||
for (u32 idx = 0, mappedEnd; idx < _api.bufferLine.size(); idx = mappedEnd)
|
||||
for (u32 idx = gsl::narrow_cast<u32>(offBeg), mappedEnd = 0; idx < offEnd; idx = mappedEnd)
|
||||
{
|
||||
u32 mappedLength = 0;
|
||||
wil::com_ptr<IDWriteFontFace2> mappedFontFace;
|
||||
_mapCharacters(_api.bufferLine.data() + idx, gsl::narrow_cast<u32>(_api.bufferLine.size()) - idx, &mappedLength, mappedFontFace.addressof());
|
||||
_mapCharacters(_api.bufferLine.data() + idx, gsl::narrow_cast<u32>(offEnd - idx), &mappedLength, mappedFontFace.addressof());
|
||||
mappedEnd = idx + mappedLength;
|
||||
|
||||
if (!mappedFontFace)
|
||||
@@ -697,7 +755,7 @@ void AtlasEngine::_flushBufferLine()
|
||||
_api.glyphProps = Buffer<DWRITE_SHAPING_GLYPH_PROPERTIES>{ size };
|
||||
}
|
||||
|
||||
if (_api.s->font->fontFeatures.empty())
|
||||
if (_p.s->font->fontFeatures.empty())
|
||||
{
|
||||
// We can reuse idx here, as it'll be reset to "idx = mappedEnd" in the outer loop anyways.
|
||||
for (u32 complexityLength = 0; idx < mappedEnd; idx += complexityLength)
|
||||
@@ -712,10 +770,10 @@ void AtlasEngine::_flushBufferLine()
|
||||
|
||||
for (size_t i = 0; i < complexityLength; ++i)
|
||||
{
|
||||
const size_t col1 = _api.bufferLineColumn[idx + i + 0];
|
||||
const size_t col2 = _api.bufferLineColumn[idx + i + 1];
|
||||
const auto col1 = _api.bufferLineColumn[idx + i + 0];
|
||||
const auto col2 = _api.bufferLineColumn[idx + i + 1];
|
||||
const auto glyphAdvance = (col2 - col1) * _p.s->font->cellSize.x;
|
||||
const auto fg = colors[col1 << shift];
|
||||
const auto fg = colors[static_cast<size_t>(col1) << shift];
|
||||
row.glyphIndices.emplace_back(_api.glyphIndices[i]);
|
||||
row.glyphAdvances.emplace_back(static_cast<f32>(glyphAdvance));
|
||||
row.glyphOffsets.emplace_back();
|
||||
@@ -750,9 +808,31 @@ void AtlasEngine::_flushBufferLine()
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasEngine::_mapBuiltinGlyphs(size_t offBeg, size_t offEnd)
|
||||
{
|
||||
auto& row = *_p.rows[_api.lastPaintBufferLineCoord.y];
|
||||
auto initialIndicesCount = row.glyphIndices.size();
|
||||
const auto shift = gsl::narrow_cast<u8>(row.lineRendition != LineRendition::SingleWidth);
|
||||
const auto colors = _p.foregroundBitmap.begin() + _p.colorBitmapRowStride * _api.lastPaintBufferLineCoord.y;
|
||||
const auto base = reinterpret_cast<const u16*>(_api.bufferLine.data());
|
||||
const auto len = offEnd - offBeg;
|
||||
|
||||
row.glyphIndices.insert(row.glyphIndices.end(), base + offBeg, base + offEnd);
|
||||
row.glyphAdvances.insert(row.glyphAdvances.end(), len, static_cast<f32>(_p.s->font->cellSize.x));
|
||||
row.glyphOffsets.insert(row.glyphOffsets.end(), len, {});
|
||||
|
||||
for (size_t i = offBeg; i < offEnd; ++i)
|
||||
{
|
||||
const auto col = _api.bufferLineColumn[i];
|
||||
row.colors.emplace_back(colors[static_cast<size_t>(col) << shift]);
|
||||
}
|
||||
|
||||
row.mappings.emplace_back(nullptr, gsl::narrow_cast<u32>(initialIndicesCount), gsl::narrow_cast<u32>(row.glyphIndices.size()));
|
||||
}
|
||||
|
||||
void AtlasEngine::_mapCharacters(const wchar_t* text, const u32 textLength, u32* mappedLength, IDWriteFontFace2** mappedFontFace) const
|
||||
{
|
||||
TextAnalysisSource analysisSource{ _api.userLocaleName.c_str(), text, textLength };
|
||||
TextAnalysisSource analysisSource{ _p.userLocaleName.c_str(), text, textLength };
|
||||
const auto& textFormatAxis = _api.textFormatAxes[static_cast<size_t>(_api.attributes)];
|
||||
|
||||
// We don't read from scale anyways.
|
||||
@@ -807,7 +887,7 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len
|
||||
{
|
||||
_api.analysisResults.clear();
|
||||
|
||||
TextAnalysisSource analysisSource{ _api.userLocaleName.c_str(), _api.bufferLine.data(), gsl::narrow<UINT32>(_api.bufferLine.size()) };
|
||||
TextAnalysisSource analysisSource{ _p.userLocaleName.c_str(), _api.bufferLine.data(), gsl::narrow<UINT32>(_api.bufferLine.size()) };
|
||||
TextAnalysisSink analysisSink{ _api.analysisResults };
|
||||
THROW_IF_FAILED(_p.textAnalyzer->AnalyzeScript(&analysisSource, idx, length, &analysisSink));
|
||||
|
||||
@@ -852,7 +932,7 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len
|
||||
/* isSideways */ false,
|
||||
/* isRightToLeft */ 0,
|
||||
/* scriptAnalysis */ &a.analysis,
|
||||
/* localeName */ _api.userLocaleName.c_str(),
|
||||
/* localeName */ _p.userLocaleName.c_str(),
|
||||
/* numberSubstitution */ nullptr,
|
||||
/* features */ &features,
|
||||
/* featureRangeLengths */ &featureRangeLengths,
|
||||
@@ -904,7 +984,7 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len
|
||||
/* isSideways */ false,
|
||||
/* isRightToLeft */ 0,
|
||||
/* scriptAnalysis */ &a.analysis,
|
||||
/* localeName */ _api.userLocaleName.c_str(),
|
||||
/* localeName */ _p.userLocaleName.c_str(),
|
||||
/* features */ &features,
|
||||
/* featureRangeLengths */ &featureRangeLengths,
|
||||
/* featureRanges */ featureRanges,
|
||||
@@ -979,53 +1059,31 @@ void AtlasEngine::_mapReplacementCharacter(u32 from, u32 to, ShapedRow& row)
|
||||
return;
|
||||
}
|
||||
|
||||
auto pos1 = from;
|
||||
auto pos2 = pos1;
|
||||
size_t col1 = _api.bufferLineColumn[from];
|
||||
size_t col2 = col1;
|
||||
auto pos = from;
|
||||
auto col1 = _api.bufferLineColumn[from];
|
||||
auto initialIndicesCount = row.glyphIndices.size();
|
||||
const auto softFontAvailable = !_p.s->font->softFontPattern.empty();
|
||||
auto currentlyMappingSoftFont = isSoftFontChar(_api.bufferLine[pos1]);
|
||||
const auto shift = gsl::narrow_cast<u8>(row.lineRendition != LineRendition::SingleWidth);
|
||||
const auto colors = _p.foregroundBitmap.begin() + _p.colorBitmapRowStride * _api.lastPaintBufferLineCoord.y;
|
||||
|
||||
while (pos2 < to)
|
||||
while (pos < to)
|
||||
{
|
||||
col2 = _api.bufferLineColumn[++pos2];
|
||||
const auto col2 = _api.bufferLineColumn[++pos];
|
||||
if (col1 == col2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto cols = col2 - col1;
|
||||
const auto ch = static_cast<u16>(_api.bufferLine[pos1]);
|
||||
const auto nowMappingSoftFont = isSoftFontChar(ch);
|
||||
|
||||
row.glyphIndices.emplace_back(nowMappingSoftFont ? ch : _api.replacementCharacterGlyphIndex);
|
||||
row.glyphAdvances.emplace_back(static_cast<f32>(cols * _p.s->font->cellSize.x));
|
||||
row.glyphIndices.emplace_back(_api.replacementCharacterGlyphIndex);
|
||||
row.glyphAdvances.emplace_back(static_cast<f32>((col2 - col1) * _p.s->font->cellSize.x));
|
||||
row.glyphOffsets.emplace_back();
|
||||
row.colors.emplace_back(colors[col1 << shift]);
|
||||
row.colors.emplace_back(colors[static_cast<size_t>(col1) << shift]);
|
||||
|
||||
if (currentlyMappingSoftFont != nowMappingSoftFont)
|
||||
{
|
||||
const auto indicesCount = row.glyphIndices.size();
|
||||
const auto fontFace = currentlyMappingSoftFont && softFontAvailable ? nullptr : _api.replacementCharacterFontFace.get();
|
||||
|
||||
if (indicesCount > initialIndicesCount)
|
||||
{
|
||||
row.mappings.emplace_back(fontFace, gsl::narrow_cast<u32>(initialIndicesCount), gsl::narrow_cast<u32>(indicesCount));
|
||||
initialIndicesCount = indicesCount;
|
||||
}
|
||||
}
|
||||
|
||||
pos1 = pos2;
|
||||
col1 = col2;
|
||||
currentlyMappingSoftFont = nowMappingSoftFont;
|
||||
}
|
||||
|
||||
{
|
||||
const auto indicesCount = row.glyphIndices.size();
|
||||
const auto fontFace = currentlyMappingSoftFont && softFontAvailable ? nullptr : _api.replacementCharacterFontFace.get();
|
||||
const auto fontFace = _api.replacementCharacterFontFace.get();
|
||||
|
||||
if (indicesCount > initialIndicesCount)
|
||||
{
|
||||
|
||||
@@ -86,6 +86,8 @@ namespace Microsoft::Console::Render::Atlas
|
||||
void _recreateFontDependentResources();
|
||||
void _recreateCellCountDependentResources();
|
||||
void _flushBufferLine();
|
||||
void _mapRegularText(size_t offBeg, size_t offEnd);
|
||||
void _mapBuiltinGlyphs(size_t offBeg, size_t offEnd);
|
||||
void _mapCharacters(const wchar_t* text, u32 textLength, u32* mappedLength, IDWriteFontFace2** mappedFontFace) const;
|
||||
void _mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 length, ShapedRow& row);
|
||||
ATLAS_ATTR_COLD void _mapReplacementCharacter(u32 from, u32 to, ShapedRow& row);
|
||||
@@ -117,6 +119,18 @@ namespace Microsoft::Console::Render::Atlas
|
||||
std::unique_ptr<IBackend> _b;
|
||||
RenderingPayload _p;
|
||||
|
||||
// _p.s->font->builtinGlyphs is the setting which decides whether we should map box drawing glyphs to
|
||||
// our own builtin versions. There's just one problem: BackendD2D doesn't have this functionality.
|
||||
// But since AtlasEngine shapes the text before it's handed to the backends, it would need to know
|
||||
// whether BackendD2D is in use, before BackendD2D even exists. These two flags solve the issue
|
||||
// by triggering a complete, immediate redraw whenever the backend type changes.
|
||||
//
|
||||
// The proper solution is to move text shaping into the backends.
|
||||
// Someone just needs to write a generic "TextBuffer to DWRITE_GLYPH_RUN" function.
|
||||
bool _hackIsBackendD2D = false;
|
||||
bool _hackWantsBuiltinGlyphs = true;
|
||||
bool _hackTriggerRedrawAll = false;
|
||||
|
||||
struct ApiState
|
||||
{
|
||||
GenerationalSettings s = DirtyGenerationalSettings();
|
||||
@@ -133,8 +147,6 @@ namespace Microsoft::Console::Render::Atlas
|
||||
std::vector<wchar_t> bufferLine;
|
||||
std::vector<u16> bufferLineColumn;
|
||||
|
||||
std::wstring userLocaleName;
|
||||
|
||||
std::array<Buffer<DWRITE_FONT_AXIS_VALUE>, 4> textFormatAxes;
|
||||
std::vector<TextAnalysisSinkResult> analysisResults;
|
||||
Buffer<u16> clusterMap;
|
||||
|
||||
@@ -32,7 +32,7 @@ using namespace Microsoft::Console::Render::Atlas;
|
||||
[[nodiscard]] HRESULT AtlasEngine::Present() noexcept
|
||||
try
|
||||
{
|
||||
if (!_p.dxgi.adapter || !_p.dxgi.factory->IsCurrent())
|
||||
if (!_p.dxgi.adapter)
|
||||
{
|
||||
_recreateAdapter();
|
||||
}
|
||||
@@ -77,7 +77,7 @@ CATCH_RETURN()
|
||||
|
||||
[[nodiscard]] bool AtlasEngine::RequiresContinuousRedraw() noexcept
|
||||
{
|
||||
return ATLAS_DEBUG_CONTINUOUS_REDRAW || (_b && _b->RequiresContinuousRedraw());
|
||||
return ATLAS_DEBUG_CONTINUOUS_REDRAW || (_b && _b->RequiresContinuousRedraw()) || _hackTriggerRedrawAll;
|
||||
}
|
||||
|
||||
void AtlasEngine::WaitUntilCanRender() noexcept
|
||||
@@ -276,12 +276,14 @@ void AtlasEngine::_recreateBackend()
|
||||
_b = std::make_unique<BackendD3D>(_p);
|
||||
}
|
||||
|
||||
// !!! NOTE !!!
|
||||
// Normally the viewport is indirectly marked as dirty by `AtlasEngine::_handleSettingsUpdate()` whenever
|
||||
// the settings change, but the `!_p.dxgi.factory->IsCurrent()` check is not part of the settings change
|
||||
// flow and so we have to manually recreate how AtlasEngine.cpp marks viewports as dirty here.
|
||||
// This ensures that the backends redraw their entire viewports whenever a new swap chain is created.
|
||||
// This ensures that the backends redraw their entire viewports whenever a new swap chain is created,
|
||||
// EVEN IF we got called when no actual settings changed (i.e. rendering failure, etc.).
|
||||
_p.MarkAllAsDirty();
|
||||
|
||||
const auto hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !d2dMode;
|
||||
_hackTriggerRedrawAll = _hackWantsBuiltinGlyphs != hackWantsBuiltinGlyphs;
|
||||
_hackIsBackendD2D = d2dMode;
|
||||
_hackWantsBuiltinGlyphs = hackWantsBuiltinGlyphs;
|
||||
}
|
||||
|
||||
void AtlasEngine::_handleSwapChainUpdate()
|
||||
@@ -319,13 +321,15 @@ void AtlasEngine::_createSwapChain()
|
||||
// 3 buffers seems to guarantee a stable framerate at display frequency at all times.
|
||||
.BufferCount = 3,
|
||||
.Scaling = DXGI_SCALING_NONE,
|
||||
// DXGI_SWAP_EFFECT_FLIP_DISCARD is a mode that was created at a time were display drivers
|
||||
// lacked support for Multiplane Overlays (MPO) and were copying buffers was expensive.
|
||||
// DXGI_SWAP_EFFECT_FLIP_DISCARD is the easiest to use, because it's fast and uses little memory.
|
||||
// But it's a mode that was created at a time were display drivers lacked support
|
||||
// for Multiplane Overlays (MPO) and were copying buffers was expensive.
|
||||
// This allowed DWM to quickly draw overlays (like gamebars) on top of rendered content.
|
||||
// With faster GPU memory in general and with support for MPO in particular this isn't
|
||||
// really an advantage anymore. Instead DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL allows for a
|
||||
// more "intelligent" composition and display updates to occur like Panel Self Refresh
|
||||
// (PSR) which requires dirty rectangles (Present1 API) to work correctly.
|
||||
// We were asked by DWM folks to use DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL for this reason (PSR).
|
||||
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
|
||||
// If our background is opaque we can enable "independent" flips by setting DXGI_ALPHA_MODE_IGNORE.
|
||||
// As our swap chain won't have to compose with DWM anymore it reduces the display latency dramatically.
|
||||
|
||||
@@ -74,11 +74,6 @@ namespace Microsoft::Console::Render::Atlas
|
||||
return val < min ? min : (max < val ? max : val);
|
||||
}
|
||||
|
||||
constexpr bool isSoftFontChar(wchar_t ch) noexcept
|
||||
{
|
||||
return ch >= 0xEF20 && ch < 0xEF80;
|
||||
}
|
||||
|
||||
inline constexpr D2D1_RECT_F GlyphRunEmptyBounds{ 1e38f, 1e38f, -1e38f, -1e38f };
|
||||
void GlyphRunAccumulateBounds(const ID2D1DeviceContext* d2dRenderTarget, D2D1_POINT_2F baselineOrigin, const DWRITE_GLYPH_RUN* glyphRun, D2D1_RECT_F& bounds);
|
||||
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
#include "pch.h"
|
||||
#include "BackendD3D.h"
|
||||
|
||||
#include <til/unicode.h>
|
||||
|
||||
#include <custom_shader_ps.h>
|
||||
#include <custom_shader_vs.h>
|
||||
#include <shader_ps.h>
|
||||
#include <shader_vs.h>
|
||||
|
||||
#include "BuiltinGlyphs.h"
|
||||
#include "dwrite.h"
|
||||
#include "../../types/inc/ColorFix.hpp"
|
||||
|
||||
@@ -42,45 +45,8 @@ TIL_FAST_MATH_BEGIN
|
||||
|
||||
using namespace Microsoft::Console::Render::Atlas;
|
||||
|
||||
template<>
|
||||
struct std::hash<u16>
|
||||
{
|
||||
constexpr size_t operator()(u16 key) const noexcept
|
||||
{
|
||||
return til::flat_set_hash_integer(key);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct std::hash<BackendD3D::AtlasGlyphEntry>
|
||||
{
|
||||
constexpr size_t operator()(u16 key) const noexcept
|
||||
{
|
||||
return til::flat_set_hash_integer(key);
|
||||
}
|
||||
|
||||
constexpr size_t operator()(const BackendD3D::AtlasGlyphEntry& slot) const noexcept
|
||||
{
|
||||
return til::flat_set_hash_integer(slot.glyphIndex);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct std::hash<BackendD3D::AtlasFontFaceEntry>
|
||||
{
|
||||
using T = BackendD3D::AtlasFontFaceEntry;
|
||||
|
||||
size_t operator()(const BackendD3D::AtlasFontFaceKey& key) const noexcept
|
||||
{
|
||||
return til::flat_set_hash_integer(std::bit_cast<uintptr_t>(key.fontFace) | static_cast<u8>(key.lineRendition));
|
||||
}
|
||||
|
||||
size_t operator()(const BackendD3D::AtlasFontFaceEntry& slot) const noexcept
|
||||
{
|
||||
const auto& inner = *slot.inner;
|
||||
return til::flat_set_hash_integer(std::bit_cast<uintptr_t>(inner.fontFace.get()) | static_cast<u8>(inner.lineRendition));
|
||||
}
|
||||
};
|
||||
static constexpr D2D1_MATRIX_3X2_F identityTransform{ .m11 = 1, .m22 = 1 };
|
||||
static constexpr D2D1_COLOR_F whiteColor{ 1, 1, 1, 1 };
|
||||
|
||||
BackendD3D::BackendD3D(const RenderingPayload& p)
|
||||
{
|
||||
@@ -329,7 +295,11 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p)
|
||||
_fontChangedResetGlyphAtlas = true;
|
||||
_textShadingType = font.antialiasingMode == AntialiasingMode::ClearType ? ShadingType::TextClearType : ShadingType::TextGrayscale;
|
||||
|
||||
// _ligatureOverhangTriggerLeft/Right are essentially thresholds for a glyph's width at
|
||||
// which point we consider it wider than allowed and "this looks like a coding ligature".
|
||||
// See _drawTextOverlapSplit for more information about what this does.
|
||||
{
|
||||
// No ligatures -> No thresholds.
|
||||
auto ligaturesDisabled = false;
|
||||
for (const auto& feature : font.fontFeatures)
|
||||
{
|
||||
@@ -752,11 +722,15 @@ void BackendD3D::_resetGlyphAtlas(const RenderingPayload& p)
|
||||
// It's not great, but it's not terrible.
|
||||
for (auto& slot : _glyphAtlasMap.container())
|
||||
{
|
||||
if (slot.inner)
|
||||
for (auto& glyphs : slot.glyphs)
|
||||
{
|
||||
slot.inner->glyphs.clear();
|
||||
glyphs.clear();
|
||||
}
|
||||
}
|
||||
for (auto& glyphs : _builtinGlyphs.glyphs)
|
||||
{
|
||||
glyphs.clear();
|
||||
}
|
||||
|
||||
_d2dBeginDrawing();
|
||||
_d2dRenderTarget->Clear();
|
||||
@@ -797,9 +771,6 @@ void BackendD3D::_resizeGlyphAtlas(const RenderingPayload& p, const u16 u, const
|
||||
_d2dRenderTarget.try_query_to(_d2dRenderTarget4.addressof());
|
||||
|
||||
_d2dRenderTarget->SetUnitMode(D2D1_UNIT_MODE_PIXELS);
|
||||
// We don't really use D2D for anything except DWrite, but it
|
||||
// can't hurt to ensure that everything it does is pixel aligned.
|
||||
_d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
// Ensure that D2D uses the exact same gamma as our shader uses.
|
||||
_d2dRenderTarget->SetTextRenderingParams(_textRenderingParams.get());
|
||||
|
||||
@@ -821,9 +792,8 @@ void BackendD3D::_resizeGlyphAtlas(const RenderingPayload& p, const u16 u, const
|
||||
}
|
||||
|
||||
{
|
||||
static constexpr D2D1_COLOR_F color{ 1, 1, 1, 1 };
|
||||
THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, _emojiBrush.put()));
|
||||
THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, _brush.put()));
|
||||
THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&whiteColor, nullptr, _emojiBrush.put()));
|
||||
THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&whiteColor, nullptr, _brush.put()));
|
||||
}
|
||||
|
||||
ID3D11ShaderResourceView* resources[]{ _backgroundBitmapView.get(), _glyphAtlasView.get() };
|
||||
@@ -955,7 +925,7 @@ void BackendD3D::_drawBackground(const RenderingPayload& p)
|
||||
}
|
||||
|
||||
_appendQuad() = {
|
||||
.shadingType = ShadingType::Background,
|
||||
.shadingType = static_cast<u16>(ShadingType::Background),
|
||||
.size = p.s->targetSize,
|
||||
};
|
||||
}
|
||||
@@ -1013,65 +983,65 @@ void BackendD3D::_drawText(RenderingPayload& p)
|
||||
for (const auto& m : row->mappings)
|
||||
{
|
||||
auto x = m.glyphsFrom;
|
||||
const AtlasFontFaceKey fontFaceKey{
|
||||
.fontFace = m.fontFace.get(),
|
||||
.lineRendition = row->lineRendition,
|
||||
};
|
||||
const auto glyphsTo = m.glyphsTo;
|
||||
const auto fontFace = m.fontFace.get();
|
||||
|
||||
// This goto label exists to allow us to retry rendering a glyph if the glyph atlas was full.
|
||||
// We need to goto here, because a retry will cause the atlas texture as well as the
|
||||
// _glyphCache hashmap to be cleared, and so we'll have to call insert() again.
|
||||
drawGlyphRetry:
|
||||
const auto [fontFaceEntryOuter, fontFaceInserted] = _glyphAtlasMap.insert(fontFaceKey);
|
||||
auto& fontFaceEntry = *fontFaceEntryOuter.inner;
|
||||
|
||||
if (fontFaceInserted)
|
||||
// The lack of a fontFace indicates a soft font.
|
||||
AtlasFontFaceEntry* fontFaceEntry = &_builtinGlyphs;
|
||||
if (fontFace) [[likely]]
|
||||
{
|
||||
_initializeFontFaceEntry(fontFaceEntry);
|
||||
fontFaceEntry = _glyphAtlasMap.insert(fontFace).first;
|
||||
}
|
||||
|
||||
while (x < m.glyphsTo)
|
||||
{
|
||||
const auto [glyphEntry, inserted] = fontFaceEntry.glyphs.insert(row->glyphIndices[x]);
|
||||
const auto& glyphs = fontFaceEntry->glyphs[WI_EnumValue(row->lineRendition)];
|
||||
|
||||
if (inserted && !_drawGlyph(p, fontFaceEntry, glyphEntry))
|
||||
while (x < glyphsTo)
|
||||
{
|
||||
size_t dx = 1;
|
||||
u32 glyphIndex = row->glyphIndices[x];
|
||||
|
||||
// Note: !fontFace is only nullptr for builtin glyphs which then use glyphIndices for UTF16 code points.
|
||||
// In other words, this doesn't accidentally corrupt any actual glyph indices.
|
||||
if (!fontFace && til::is_leading_surrogate(glyphIndex))
|
||||
{
|
||||
// A deadlock in this retry loop is detected in _drawGlyphPrepareRetry.
|
||||
//
|
||||
// Yes, I agree, avoid goto. Sometimes. It's not my fault that C++ still doesn't
|
||||
// have a `continue outerloop;` like other languages had it for decades. :(
|
||||
#pragma warning(suppress : 26438) // Avoid 'goto' (es.76).
|
||||
#pragma warning(suppress : 26448) // Consider using gsl::finally if final action is intended (gsl.util).
|
||||
goto drawGlyphRetry;
|
||||
glyphIndex = til::combine_surrogates(glyphIndex, row->glyphIndices[x + 1]);
|
||||
dx = 2;
|
||||
}
|
||||
|
||||
if (glyphEntry.data.shadingType != ShadingType::Default)
|
||||
auto glyphEntry = glyphs.lookup(glyphIndex);
|
||||
if (!glyphEntry)
|
||||
{
|
||||
glyphEntry = _drawGlyph(p, *row, *fontFaceEntry, glyphIndex);
|
||||
}
|
||||
|
||||
// A shadingType of 0 (ShadingType::Default) indicates a glyph that is whitespace.
|
||||
if (glyphEntry->shadingType != ShadingType::Default)
|
||||
{
|
||||
auto l = static_cast<til::CoordType>(lrintf((baselineX + row->glyphOffsets[x].advanceOffset) * scaleX));
|
||||
auto t = static_cast<til::CoordType>(lrintf((baselineY - row->glyphOffsets[x].ascenderOffset) * scaleY));
|
||||
|
||||
l += glyphEntry.data.offset.x;
|
||||
t += glyphEntry.data.offset.y;
|
||||
l += glyphEntry->offset.x;
|
||||
t += glyphEntry->offset.y;
|
||||
|
||||
row->dirtyTop = std::min(row->dirtyTop, t);
|
||||
row->dirtyBottom = std::max(row->dirtyBottom, t + glyphEntry.data.size.y);
|
||||
row->dirtyBottom = std::max(row->dirtyBottom, t + glyphEntry->size.y);
|
||||
|
||||
_appendQuad() = {
|
||||
.shadingType = glyphEntry.data.shadingType,
|
||||
.shadingType = static_cast<u16>(glyphEntry->shadingType),
|
||||
.position = { static_cast<i16>(l), static_cast<i16>(t) },
|
||||
.size = glyphEntry.data.size,
|
||||
.texcoord = glyphEntry.data.texcoord,
|
||||
.size = glyphEntry->size,
|
||||
.texcoord = glyphEntry->texcoord,
|
||||
.color = row->colors[x],
|
||||
};
|
||||
|
||||
if (glyphEntry.data.overlapSplit)
|
||||
if (glyphEntry->overlapSplit)
|
||||
{
|
||||
_drawTextOverlapSplit(p, y);
|
||||
}
|
||||
}
|
||||
|
||||
baselineX += row->glyphAdvances[x];
|
||||
++x;
|
||||
x += dx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1189,71 +1159,22 @@ void BackendD3D::_drawTextOverlapSplit(const RenderingPayload& p, u16 y)
|
||||
}
|
||||
}
|
||||
|
||||
void BackendD3D::_initializeFontFaceEntry(AtlasFontFaceEntryInner& fontFaceEntry)
|
||||
BackendD3D::AtlasGlyphEntry* BackendD3D::_drawGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex)
|
||||
{
|
||||
// The lack of a fontFace indicates a soft font.
|
||||
if (!fontFaceEntry.fontFace)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ALLOW_UNINITIALIZED_BEGIN
|
||||
std::array<u32, 0x100> codepoints;
|
||||
std::array<u16, 0x100> indices;
|
||||
ALLOW_UNINITIALIZED_END
|
||||
|
||||
for (u32 i = 0; i < codepoints.size(); ++i)
|
||||
{
|
||||
codepoints[i] = 0x2500 + i;
|
||||
}
|
||||
|
||||
THROW_IF_FAILED(fontFaceEntry.fontFace->GetGlyphIndicesW(codepoints.data(), codepoints.size(), indices.data()));
|
||||
|
||||
for (u32 i = 0; i < indices.size(); ++i)
|
||||
{
|
||||
if (const auto idx = indices[i])
|
||||
{
|
||||
fontFaceEntry.boxGlyphs.insert(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry)
|
||||
{
|
||||
if (!fontFaceEntry.fontFace)
|
||||
{
|
||||
return _drawSoftFontGlyph(p, fontFaceEntry, glyphEntry);
|
||||
return _drawBuiltinGlyph(p, row, fontFaceEntry, glyphIndex);
|
||||
}
|
||||
|
||||
const auto glyphIndexU16 = static_cast<u16>(glyphIndex);
|
||||
const DWRITE_GLYPH_RUN glyphRun{
|
||||
.fontFace = fontFaceEntry.fontFace.get(),
|
||||
.fontEmSize = p.s->font->fontSize,
|
||||
.glyphCount = 1,
|
||||
.glyphIndices = &glyphEntry.glyphIndex,
|
||||
.glyphIndices = &glyphIndexU16,
|
||||
};
|
||||
|
||||
// To debug issues with this function it may be helpful to know which file
|
||||
// a given font face corresponds to. This code works for most cases.
|
||||
#if 0
|
||||
wchar_t filePath[MAX_PATH]{};
|
||||
{
|
||||
UINT32 fileCount = 1;
|
||||
wil::com_ptr<IDWriteFontFile> file;
|
||||
if (SUCCEEDED(fontFaceEntry.fontFace->GetFiles(&fileCount, file.addressof())))
|
||||
{
|
||||
wil::com_ptr<IDWriteFontFileLoader> loader;
|
||||
THROW_IF_FAILED(file->GetLoader(loader.addressof()));
|
||||
|
||||
if (const auto localLoader = loader.try_query<IDWriteLocalFontFileLoader>())
|
||||
{
|
||||
void const* fontFileReferenceKey;
|
||||
UINT32 fontFileReferenceKeySize;
|
||||
THROW_IF_FAILED(file->GetReferenceKey(&fontFileReferenceKey, &fontFileReferenceKeySize));
|
||||
THROW_IF_FAILED(localLoader->GetFilePathFromKey(fontFileReferenceKey, fontFileReferenceKeySize, &filePath[0], MAX_PATH));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// It took me a while to figure out how to rasterize glyphs manually with DirectWrite without depending on Direct2D.
|
||||
// The benefits are a reduction in memory usage, an increase in performance under certain circumstances and most
|
||||
// importantly, the ability to debug the renderer more easily, because many graphics debuggers don't support Direct2D.
|
||||
@@ -1322,16 +1243,13 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI
|
||||
// The buffer now contains a grayscale alpha mask.
|
||||
#endif
|
||||
|
||||
const auto lineRendition = static_cast<LineRendition>(fontFaceEntry.lineRendition);
|
||||
const auto needsTransform = lineRendition != LineRendition::SingleWidth;
|
||||
|
||||
static constexpr D2D1_MATRIX_3X2_F identityTransform{ .m11 = 1, .m22 = 1 };
|
||||
const int scale = row.lineRendition != LineRendition::SingleWidth;
|
||||
D2D1_MATRIX_3X2_F transform = identityTransform;
|
||||
|
||||
if (needsTransform)
|
||||
if (scale)
|
||||
{
|
||||
transform.m11 = 2.0f;
|
||||
transform.m22 = lineRendition >= LineRendition::DoubleHeightTop ? 2.0f : 1.0f;
|
||||
transform.m22 = row.lineRendition >= LineRendition::DoubleHeightTop ? 2.0f : 1.0f;
|
||||
_d2dRenderTarget->SetTransform(&transform);
|
||||
}
|
||||
|
||||
@@ -1387,26 +1305,10 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI
|
||||
}
|
||||
}
|
||||
|
||||
// Overhangs for box glyphs can produce unsightly effects, where the antialiased edges of horizontal
|
||||
// and vertical lines overlap between neighboring glyphs and produce "boldened" intersections.
|
||||
// It looks a little something like this:
|
||||
// ---+---+---
|
||||
// This avoids the issue in most cases by simply clipping the glyph to the size of a single cell.
|
||||
// The downside is that it fails to work well for custom line heights, etc.
|
||||
const auto isBoxGlyph = fontFaceEntry.boxGlyphs.lookup(glyphEntry.glyphIndex) != nullptr;
|
||||
if (isBoxGlyph)
|
||||
{
|
||||
// NOTE: As mentioned above, the "origin" of a glyph's coordinate system is its baseline.
|
||||
bounds.left = std::max(bounds.left, 0.0f);
|
||||
bounds.top = std::max(bounds.top, static_cast<f32>(-p.s->font->baseline) * transform.m22);
|
||||
bounds.right = std::min(bounds.right, static_cast<f32>(p.s->font->cellSize.x) * transform.m11);
|
||||
bounds.bottom = std::min(bounds.bottom, static_cast<f32>(p.s->font->descender) * transform.m22);
|
||||
}
|
||||
|
||||
// The bounds may be empty if the glyph is whitespace.
|
||||
if (bounds.left >= bounds.right || bounds.top >= bounds.bottom)
|
||||
{
|
||||
return true;
|
||||
return _drawGlyphAllocateEntry(row, fontFaceEntry, glyphIndex);
|
||||
}
|
||||
|
||||
const auto bl = lrintf(bounds.left);
|
||||
@@ -1418,37 +1320,15 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI
|
||||
.w = br - bl,
|
||||
.h = bb - bt,
|
||||
};
|
||||
if (!stbrp_pack_rects(&_rectPacker, &rect, 1))
|
||||
{
|
||||
_drawGlyphPrepareRetry(p);
|
||||
return false;
|
||||
}
|
||||
_drawGlyphAtlasAllocate(p, rect);
|
||||
_d2dBeginDrawing();
|
||||
|
||||
const D2D1_POINT_2F baselineOrigin{
|
||||
static_cast<f32>(rect.x - bl),
|
||||
static_cast<f32>(rect.y - bt),
|
||||
};
|
||||
|
||||
_d2dBeginDrawing();
|
||||
|
||||
if (isBoxGlyph)
|
||||
{
|
||||
const D2D1_RECT_F clipRect{
|
||||
static_cast<f32>(rect.x) / transform.m11,
|
||||
static_cast<f32>(rect.y) / transform.m22,
|
||||
static_cast<f32>(rect.x + rect.w) / transform.m11,
|
||||
static_cast<f32>(rect.y + rect.h) / transform.m22,
|
||||
};
|
||||
_d2dRenderTarget->PushAxisAlignedClip(&clipRect, D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
}
|
||||
const auto boxGlyphCleanup = wil::scope_exit([&]() {
|
||||
if (isBoxGlyph)
|
||||
{
|
||||
_d2dRenderTarget->PopAxisAlignedClip();
|
||||
}
|
||||
});
|
||||
|
||||
if (needsTransform)
|
||||
if (scale)
|
||||
{
|
||||
transform.dx = (1.0f - transform.m11) * baselineOrigin.x;
|
||||
transform.dy = (1.0f - transform.m22) * baselineOrigin.y;
|
||||
@@ -1474,56 +1354,85 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI
|
||||
//
|
||||
// The former condition makes sure to exclude diacritics and such from being considered a ligature,
|
||||
// while the latter condition-pair makes sure to exclude regular BMP wide glyphs that overlap a little.
|
||||
const auto horizontalScale = lineRendition != LineRendition::SingleWidth ? 2 : 1;
|
||||
const auto triggerLeft = _ligatureOverhangTriggerLeft * horizontalScale;
|
||||
const auto triggerRight = _ligatureOverhangTriggerRight * horizontalScale;
|
||||
const auto triggerLeft = _ligatureOverhangTriggerLeft << scale;
|
||||
const auto triggerRight = _ligatureOverhangTriggerRight << scale;
|
||||
const auto overlapSplit = rect.w >= p.s->font->cellSize.x && (bl <= triggerLeft || br >= triggerRight);
|
||||
|
||||
glyphEntry.data.shadingType = isColorGlyph ? ShadingType::TextPassthrough : _textShadingType;
|
||||
glyphEntry.data.overlapSplit = overlapSplit;
|
||||
glyphEntry.data.offset.x = bl;
|
||||
glyphEntry.data.offset.y = bt;
|
||||
glyphEntry.data.size.x = rect.w;
|
||||
glyphEntry.data.size.y = rect.h;
|
||||
glyphEntry.data.texcoord.x = rect.x;
|
||||
glyphEntry.data.texcoord.y = rect.y;
|
||||
const auto glyphEntry = _drawGlyphAllocateEntry(row, fontFaceEntry, glyphIndex);
|
||||
glyphEntry->shadingType = isColorGlyph ? ShadingType::TextPassthrough : _textShadingType;
|
||||
glyphEntry->overlapSplit = overlapSplit;
|
||||
glyphEntry->offset.x = bl;
|
||||
glyphEntry->offset.y = bt;
|
||||
glyphEntry->size.x = rect.w;
|
||||
glyphEntry->size.y = rect.h;
|
||||
glyphEntry->texcoord.x = rect.x;
|
||||
glyphEntry->texcoord.y = rect.y;
|
||||
|
||||
if (lineRendition >= LineRendition::DoubleHeightTop)
|
||||
if (row.lineRendition >= LineRendition::DoubleHeightTop)
|
||||
{
|
||||
_splitDoubleHeightGlyph(p, fontFaceEntry, glyphEntry);
|
||||
_splitDoubleHeightGlyph(p, row, fontFaceEntry, glyphEntry);
|
||||
}
|
||||
|
||||
return true;
|
||||
return glyphEntry;
|
||||
}
|
||||
|
||||
bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry)
|
||||
BackendD3D::AtlasGlyphEntry* BackendD3D::_drawBuiltinGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex)
|
||||
{
|
||||
auto baseline = p.s->font->baseline;
|
||||
stbrp_rect rect{
|
||||
.w = p.s->font->cellSize.x,
|
||||
.h = p.s->font->cellSize.y,
|
||||
};
|
||||
|
||||
const auto lineRendition = static_cast<LineRendition>(fontFaceEntry.lineRendition);
|
||||
auto baseline = p.s->font->baseline;
|
||||
|
||||
if (lineRendition != LineRendition::SingleWidth)
|
||||
if (row.lineRendition != LineRendition::SingleWidth)
|
||||
{
|
||||
const auto heightShift = static_cast<u8>(lineRendition >= LineRendition::DoubleHeightTop);
|
||||
const auto heightShift = static_cast<u8>(row.lineRendition >= LineRendition::DoubleHeightTop);
|
||||
rect.w <<= 1;
|
||||
rect.h <<= heightShift;
|
||||
baseline <<= heightShift;
|
||||
}
|
||||
|
||||
if (!stbrp_pack_rects(&_rectPacker, &rect, 1))
|
||||
_drawGlyphAtlasAllocate(p, rect);
|
||||
_d2dBeginDrawing();
|
||||
|
||||
if (BuiltinGlyphs::IsSoftFontChar(glyphIndex))
|
||||
{
|
||||
_drawGlyphPrepareRetry(p);
|
||||
return false;
|
||||
_drawSoftFontGlyph(p, rect, glyphIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
const D2D1_RECT_F r{
|
||||
static_cast<f32>(rect.x),
|
||||
static_cast<f32>(rect.y),
|
||||
static_cast<f32>(rect.x + rect.w),
|
||||
static_cast<f32>(rect.y + rect.h),
|
||||
};
|
||||
BuiltinGlyphs::DrawBuiltinGlyph(p.d2dFactory.get(), _d2dRenderTarget.get(), _brush.get(), r, glyphIndex);
|
||||
}
|
||||
|
||||
const auto glyphEntry = _drawGlyphAllocateEntry(row, fontFaceEntry, glyphIndex);
|
||||
glyphEntry->shadingType = ShadingType::TextGrayscale;
|
||||
glyphEntry->overlapSplit = 0;
|
||||
glyphEntry->offset.x = 0;
|
||||
glyphEntry->offset.y = -baseline;
|
||||
glyphEntry->size.x = rect.w;
|
||||
glyphEntry->size.y = rect.h;
|
||||
glyphEntry->texcoord.x = rect.x;
|
||||
glyphEntry->texcoord.y = rect.y;
|
||||
|
||||
if (row.lineRendition >= LineRendition::DoubleHeightTop)
|
||||
{
|
||||
_splitDoubleHeightGlyph(p, row, fontFaceEntry, glyphEntry);
|
||||
}
|
||||
|
||||
return glyphEntry;
|
||||
}
|
||||
|
||||
void BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const stbrp_rect& rect, u32 glyphIndex)
|
||||
{
|
||||
if (!_softFontBitmap)
|
||||
{
|
||||
// Allocating such a tiny texture is very wasteful (min. texture size on GPUs
|
||||
// right now is 64kB), but this is a seldom used feature so it's fine...
|
||||
// right now is 64kB), but this is a seldomly used feature so it's fine...
|
||||
const D2D1_SIZE_U size{
|
||||
static_cast<UINT32>(p.s->font->softFontCellSize.width),
|
||||
static_cast<UINT32>(p.s->font->softFontCellSize.height),
|
||||
@@ -1536,6 +1445,30 @@ bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFa
|
||||
THROW_IF_FAILED(_d2dRenderTarget->CreateBitmap(size, nullptr, 0, &bitmapProperties, _softFontBitmap.addressof()));
|
||||
}
|
||||
|
||||
{
|
||||
const auto width = static_cast<size_t>(p.s->font->softFontCellSize.width);
|
||||
const auto height = static_cast<size_t>(p.s->font->softFontCellSize.height);
|
||||
|
||||
auto bitmapData = Buffer<u32>{ width * height };
|
||||
const auto softFontIndex = glyphIndex - 0xEF20u;
|
||||
auto src = p.s->font->softFontPattern.begin() + height * softFontIndex;
|
||||
auto dst = bitmapData.begin();
|
||||
|
||||
for (size_t y = 0; y < height; y++)
|
||||
{
|
||||
auto srcBits = *src++;
|
||||
for (size_t x = 0; x < width; x++)
|
||||
{
|
||||
const auto srcBitIsSet = (srcBits & 0x8000) != 0;
|
||||
*dst++ = srcBitIsSet ? 0xffffffff : 0x00000000;
|
||||
srcBits <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
const auto pitch = static_cast<UINT32>(width * sizeof(u32));
|
||||
THROW_IF_FAILED(_softFontBitmap->CopyFromMemory(nullptr, bitmapData.data(), pitch));
|
||||
}
|
||||
|
||||
const auto interpolation = p.s->font->antialiasingMode == AntialiasingMode::Aliased ? D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR : D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC;
|
||||
const D2D1_RECT_F dest{
|
||||
static_cast<f32>(rect.x),
|
||||
@@ -1545,118 +1478,67 @@ bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFa
|
||||
};
|
||||
|
||||
_d2dBeginDrawing();
|
||||
_drawSoftFontGlyphInBitmap(p, glyphEntry);
|
||||
_d2dRenderTarget->DrawBitmap(_softFontBitmap.get(), &dest, 1, interpolation, nullptr, nullptr);
|
||||
|
||||
glyphEntry.data.shadingType = ShadingType::TextGrayscale;
|
||||
glyphEntry.data.overlapSplit = 0;
|
||||
glyphEntry.data.offset.x = 0;
|
||||
glyphEntry.data.offset.y = -baseline;
|
||||
glyphEntry.data.size.x = rect.w;
|
||||
glyphEntry.data.size.y = rect.h;
|
||||
glyphEntry.data.texcoord.x = rect.x;
|
||||
glyphEntry.data.texcoord.y = rect.y;
|
||||
|
||||
if (lineRendition >= LineRendition::DoubleHeightTop)
|
||||
{
|
||||
_splitDoubleHeightGlyph(p, fontFaceEntry, glyphEntry);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BackendD3D::_drawSoftFontGlyphInBitmap(const RenderingPayload& p, const AtlasGlyphEntry& glyphEntry) const
|
||||
void BackendD3D::_drawGlyphAtlasAllocate(const RenderingPayload& p, stbrp_rect& rect)
|
||||
{
|
||||
if (!isSoftFontChar(glyphEntry.glyphIndex))
|
||||
if (stbrp_pack_rects(&_rectPacker, &rect, 1))
|
||||
{
|
||||
// AtlasEngine::_mapReplacementCharacter should have filtered inputs with isSoftFontChar already.
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto width = static_cast<size_t>(p.s->font->softFontCellSize.width);
|
||||
const auto height = static_cast<size_t>(p.s->font->softFontCellSize.height);
|
||||
const auto& softFontPattern = p.s->font->softFontPattern;
|
||||
|
||||
// The isSoftFontChar() range is [0xEF20,0xEF80).
|
||||
const auto offset = glyphEntry.glyphIndex - 0xEF20u;
|
||||
const auto offsetBeg = offset * height;
|
||||
const auto offsetEnd = offsetBeg + height;
|
||||
|
||||
if (offsetEnd > softFontPattern.size())
|
||||
{
|
||||
// Out of range values should not occur, but they do and it's unknown why: GH#15553
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Buffer<u32> bitmapData{ width * height };
|
||||
auto dst = bitmapData.begin();
|
||||
auto it = softFontPattern.begin() + offsetBeg;
|
||||
const auto end = softFontPattern.begin() + offsetEnd;
|
||||
|
||||
while (it != end)
|
||||
{
|
||||
auto srcBits = *it++;
|
||||
for (size_t x = 0; x < width; x++)
|
||||
{
|
||||
const auto srcBitIsSet = (srcBits & 0x8000) != 0;
|
||||
*dst++ = srcBitIsSet ? 0xffffffff : 0x00000000;
|
||||
srcBits <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
const auto pitch = static_cast<UINT32>(width * sizeof(u32));
|
||||
THROW_IF_FAILED(_softFontBitmap->CopyFromMemory(nullptr, bitmapData.data(), pitch));
|
||||
}
|
||||
|
||||
void BackendD3D::_drawGlyphPrepareRetry(const RenderingPayload& p)
|
||||
{
|
||||
THROW_HR_IF_MSG(E_UNEXPECTED, _glyphAtlasMap.empty(), "BackendD3D::_drawGlyph deadlock");
|
||||
_d2dEndDrawing();
|
||||
_flushQuads(p);
|
||||
_resetGlyphAtlas(p);
|
||||
|
||||
if (!stbrp_pack_rects(&_rectPacker, &rect, 1))
|
||||
{
|
||||
THROW_HR(HRESULT_FROM_WIN32(ERROR_POSSIBLE_DEADLOCK));
|
||||
}
|
||||
}
|
||||
|
||||
BackendD3D::AtlasGlyphEntry* BackendD3D::_drawGlyphAllocateEntry(const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex)
|
||||
{
|
||||
const auto glyphEntry = fontFaceEntry.glyphs[WI_EnumValue(row.lineRendition)].insert(glyphIndex).first;
|
||||
glyphEntry->shadingType = ShadingType::Default;
|
||||
return glyphEntry;
|
||||
}
|
||||
|
||||
// If this is a double-height glyph (DECDHL), we need to split it into 2 glyph entries:
|
||||
// One for the top/bottom half each, because that's how DECDHL works. This will clip the
|
||||
// `glyphEntry` to only contain the one specified by `fontFaceEntry.lineRendition`
|
||||
// and create a second entry in our glyph cache hashmap that contains the other half.
|
||||
void BackendD3D::_splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry)
|
||||
void BackendD3D::_splitDoubleHeightGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, AtlasGlyphEntry* glyphEntry)
|
||||
{
|
||||
// Twice the line height, twice the descender gap. For both.
|
||||
glyphEntry.data.offset.y -= p.s->font->descender;
|
||||
glyphEntry->offset.y -= p.s->font->descender;
|
||||
|
||||
const auto isTop = fontFaceEntry.lineRendition == LineRendition::DoubleHeightTop;
|
||||
const auto isTop = row.lineRendition == LineRendition::DoubleHeightTop;
|
||||
const auto otherLineRendition = isTop ? LineRendition::DoubleHeightBottom : LineRendition::DoubleHeightTop;
|
||||
const auto entry2 = fontFaceEntry.glyphs[WI_EnumValue(otherLineRendition)].insert(glyphEntry->glyphIndex).first;
|
||||
|
||||
const AtlasFontFaceKey key2{
|
||||
.fontFace = fontFaceEntry.fontFace.get(),
|
||||
.lineRendition = isTop ? LineRendition::DoubleHeightBottom : LineRendition::DoubleHeightTop,
|
||||
};
|
||||
*entry2 = *glyphEntry;
|
||||
|
||||
auto& glyphCache = _glyphAtlasMap.insert(key2).first.inner->glyphs;
|
||||
auto& entry2 = glyphCache.insert(glyphEntry.glyphIndex).first;
|
||||
entry2.data = glyphEntry.data;
|
||||
const auto top = isTop ? glyphEntry : entry2;
|
||||
const auto bottom = isTop ? entry2 : glyphEntry;
|
||||
const auto topSize = clamp(-glyphEntry->offset.y - p.s->font->baseline, 0, static_cast<int>(glyphEntry->size.y));
|
||||
|
||||
auto& top = isTop ? glyphEntry : entry2;
|
||||
auto& bottom = isTop ? entry2 : glyphEntry;
|
||||
|
||||
const auto topSize = clamp(-glyphEntry.data.offset.y - p.s->font->baseline, 0, static_cast<int>(glyphEntry.data.size.y));
|
||||
top.data.offset.y += p.s->font->cellSize.y;
|
||||
top.data.size.y = topSize;
|
||||
bottom.data.offset.y += topSize;
|
||||
bottom.data.size.y = std::max(0, bottom.data.size.y - topSize);
|
||||
bottom.data.texcoord.y += topSize;
|
||||
top->offset.y += p.s->font->cellSize.y;
|
||||
top->size.y = topSize;
|
||||
bottom->offset.y += topSize;
|
||||
bottom->size.y = std::max(0, bottom->size.y - topSize);
|
||||
bottom->texcoord.y += topSize;
|
||||
|
||||
// Things like diacritics might be so small that they only exist on either half of the
|
||||
// double-height row. This effectively turns the other (unneeded) side into whitespace.
|
||||
if (!top.data.size.y)
|
||||
if (!top->size.y)
|
||||
{
|
||||
top.data.shadingType = ShadingType::Default;
|
||||
top->shadingType = ShadingType::Default;
|
||||
}
|
||||
if (!bottom.data.size.y)
|
||||
if (!bottom->size.y)
|
||||
{
|
||||
bottom.data.shadingType = ShadingType::Default;
|
||||
bottom->shadingType = ShadingType::Default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1691,7 +1573,7 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y)
|
||||
for (; posX < end; posX += textCellWidth)
|
||||
{
|
||||
_appendQuad() = {
|
||||
.shadingType = ShadingType::SolidLine,
|
||||
.shadingType = static_cast<u16>(ShadingType::SolidLine),
|
||||
.position = { static_cast<i16>(posX), rowTop },
|
||||
.size = { width, p.s->font->cellSize.y },
|
||||
.color = r.gridlineColor,
|
||||
@@ -1713,7 +1595,7 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y)
|
||||
if (rt < rb)
|
||||
{
|
||||
_appendQuad() = {
|
||||
.shadingType = shadingType,
|
||||
.shadingType = static_cast<u16>(shadingType),
|
||||
.renditionScale = { static_cast<u8>(1 << horizontalShift), static_cast<u8>(1 << verticalShift) },
|
||||
.position = { left, static_cast<i16>(rt) },
|
||||
.size = { width, static_cast<u16>(rb - rt) },
|
||||
@@ -1803,8 +1685,8 @@ void BackendD3D::_drawCursorBackground(const RenderingPayload& p)
|
||||
}
|
||||
|
||||
const i16x2 position{
|
||||
p.s->font->cellSize.x * x0,
|
||||
p.s->font->cellSize.y * p.cursorRect.top,
|
||||
static_cast<i16>(p.s->font->cellSize.x * x0),
|
||||
static_cast<i16>(p.s->font->cellSize.y * p.cursorRect.top),
|
||||
};
|
||||
const u16x2 size{
|
||||
static_cast<u16>(p.s->font->cellSize.x * (x1 - x0)),
|
||||
@@ -1887,7 +1769,7 @@ void BackendD3D::_drawCursorBackground(const RenderingPayload& p)
|
||||
for (const auto& c : _cursorRects)
|
||||
{
|
||||
_appendQuad() = {
|
||||
.shadingType = ShadingType::Cursor,
|
||||
.shadingType = static_cast<u16>(ShadingType::Cursor),
|
||||
.position = c.position,
|
||||
.size = c.size,
|
||||
.color = c.background,
|
||||
@@ -1911,7 +1793,7 @@ void BackendD3D::_drawCursorForeground()
|
||||
// start and end of this "block" here in advance.
|
||||
for (; instancesOffset < instancesCount; ++instancesOffset)
|
||||
{
|
||||
const auto shadingType = _instances[instancesOffset].shadingType;
|
||||
const auto shadingType = static_cast<ShadingType>(_instances[instancesOffset].shadingType);
|
||||
if (shadingType >= ShadingType::TextDrawingFirst && shadingType <= ShadingType::TextDrawingLast)
|
||||
{
|
||||
break;
|
||||
@@ -1931,7 +1813,7 @@ void BackendD3D::_drawCursorForeground()
|
||||
// Now do the same thing as above, but backwards from the end.
|
||||
for (; instancesCount > instancesOffset; --instancesCount)
|
||||
{
|
||||
const auto shadingType = _instances[instancesCount - 1].shadingType;
|
||||
const auto shadingType = static_cast<ShadingType>(_instances[instancesCount - 1].shadingType);
|
||||
if (shadingType >= ShadingType::TextDrawingFirst && shadingType <= ShadingType::TextDrawingLast)
|
||||
{
|
||||
break;
|
||||
@@ -1990,7 +1872,7 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off
|
||||
// There's one special exception to the rule: Emojis. We currently don't really support inverting
|
||||
// (or reversing) colored glyphs like that, so we can return early here and avoid cutting them up.
|
||||
// It'd be too expensive to check for these rare glyph types inside the _drawCursorForeground() loop.
|
||||
if (it.shadingType == ShadingType::TextPassthrough)
|
||||
if (static_cast<ShadingType>(it.shadingType) == ShadingType::TextPassthrough)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -2121,14 +2003,14 @@ void BackendD3D::_drawSelection(const RenderingPayload& p)
|
||||
else
|
||||
{
|
||||
_appendQuad() = {
|
||||
.shadingType = ShadingType::Selection,
|
||||
.shadingType = static_cast<u16>(ShadingType::Selection),
|
||||
.position = {
|
||||
p.s->font->cellSize.x * row->selectionFrom,
|
||||
p.s->font->cellSize.y * y,
|
||||
static_cast<i16>(p.s->font->cellSize.x * row->selectionFrom),
|
||||
static_cast<i16>(p.s->font->cellSize.y * y),
|
||||
},
|
||||
.size = {
|
||||
static_cast<u16>(p.s->font->cellSize.x * (row->selectionTo - row->selectionFrom)),
|
||||
p.s->font->cellSize.y,
|
||||
static_cast<u16>(p.s->font->cellSize.y),
|
||||
},
|
||||
.color = p.s->misc->selectionColor,
|
||||
};
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
#pragma warning(suppress : 4324) // 'CustomConstBuffer': structure was padded due to alignment specifier
|
||||
};
|
||||
|
||||
enum class ShadingType : u16
|
||||
enum class ShadingType : u8 // u8 so that it packs perfectly into AtlasGlyphEntry
|
||||
{
|
||||
Default = 0,
|
||||
Background = 0,
|
||||
@@ -89,7 +89,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
// impact on performance and power draw. If (when?) displays with >32k resolution make their
|
||||
// appearance in the future, this should be changed to f32x2. But if you do so, please change
|
||||
// all other occurrences of i16x2 positions/offsets throughout the class to keep it consistent.
|
||||
alignas(u16) ShadingType shadingType;
|
||||
alignas(u16) u16 shadingType; // not using ShadingType here to avoid struct padding which causes optimization failures with MSVC
|
||||
alignas(u16) u8x2 renditionScale;
|
||||
alignas(u32) i16x2 position;
|
||||
alignas(u32) u16x2 size;
|
||||
@@ -97,8 +97,12 @@ namespace Microsoft::Console::Render::Atlas
|
||||
alignas(u32) u32 color;
|
||||
};
|
||||
|
||||
struct alignas(u32) AtlasGlyphEntryData
|
||||
// NOTE: Don't initialize any members in this struct. This ensures that no
|
||||
// zero-initialization needs to occur when we allocate large buffers of this object.
|
||||
struct AtlasGlyphEntry
|
||||
{
|
||||
u32 glyphIndex;
|
||||
u8 occupied;
|
||||
ShadingType shadingType;
|
||||
u16 overlapSplit;
|
||||
i16x2 offset;
|
||||
@@ -106,80 +110,71 @@ namespace Microsoft::Console::Render::Atlas
|
||||
u16x2 texcoord;
|
||||
};
|
||||
|
||||
// NOTE: Don't initialize any members in this struct. This ensures that no
|
||||
// zero-initialization needs to occur when we allocate large buffers of this object.
|
||||
struct AtlasGlyphEntry
|
||||
struct AtlasGlyphEntryHashTrait
|
||||
{
|
||||
u16 glyphIndex;
|
||||
// All data in QuadInstance is u32-aligned anyways, so this simultaneously serves as padding.
|
||||
u16 _occupied;
|
||||
|
||||
AtlasGlyphEntryData data;
|
||||
|
||||
constexpr bool operator==(u16 key) const noexcept
|
||||
static constexpr bool occupied(const AtlasGlyphEntry& entry) noexcept
|
||||
{
|
||||
return glyphIndex == key;
|
||||
return entry.occupied != 0;
|
||||
}
|
||||
|
||||
constexpr operator bool() const noexcept
|
||||
static constexpr size_t hash(const u16 glyphIndex) noexcept
|
||||
{
|
||||
return _occupied != 0;
|
||||
return til::flat_set_hash_integer(glyphIndex);
|
||||
}
|
||||
|
||||
constexpr AtlasGlyphEntry& operator=(u16 key) noexcept
|
||||
static constexpr size_t hash(const AtlasGlyphEntry& entry) noexcept
|
||||
{
|
||||
glyphIndex = key;
|
||||
_occupied = 1;
|
||||
return *this;
|
||||
return til::flat_set_hash_integer(entry.glyphIndex);
|
||||
}
|
||||
|
||||
static constexpr bool equals(const AtlasGlyphEntry& entry, u16 glyphIndex) noexcept
|
||||
{
|
||||
return entry.glyphIndex == glyphIndex;
|
||||
}
|
||||
|
||||
static constexpr void assign(AtlasGlyphEntry& entry, u16 glyphIndex) noexcept
|
||||
{
|
||||
entry.glyphIndex = glyphIndex;
|
||||
entry.occupied = 1;
|
||||
}
|
||||
};
|
||||
|
||||
// This exists so that we can look up a AtlasFontFaceEntry without AddRef()/Release()ing fontFace first.
|
||||
struct AtlasFontFaceKey
|
||||
{
|
||||
IDWriteFontFace2* fontFace;
|
||||
LineRendition lineRendition;
|
||||
};
|
||||
|
||||
struct AtlasFontFaceEntryInner
|
||||
struct AtlasFontFaceEntry
|
||||
{
|
||||
// BODGY: At the time of writing IDWriteFontFallback::MapCharacters returns the same IDWriteFontFace instance
|
||||
// for the same font face variant as long as someone is holding a reference to the instance (see ActiveFaceCache).
|
||||
// This allows us to hash the value of the pointer as if it was uniquely identifying the font face variant.
|
||||
wil::com_ptr<IDWriteFontFace2> fontFace;
|
||||
LineRendition lineRendition = LineRendition::SingleWidth;
|
||||
|
||||
til::linear_flat_set<AtlasGlyphEntry> glyphs;
|
||||
// boxGlyphs gets an increased growth rate of 2^2 = 4x, because presumably fonts either contain very
|
||||
// few or almost all of the box glyphs. This reduces the cost of _initializeFontFaceEntry quite a bit.
|
||||
til::linear_flat_set<u16, 2, 2> boxGlyphs;
|
||||
// The 4 entries map to the 4 corresponding LineRendition enum values.
|
||||
til::linear_flat_set<AtlasGlyphEntry, AtlasGlyphEntryHashTrait> glyphs[4];
|
||||
};
|
||||
|
||||
struct AtlasFontFaceEntry
|
||||
struct AtlasFontFaceEntryHashTrait
|
||||
{
|
||||
// This being a heap allocated allows us to insert into `glyphs` in `_splitDoubleHeightGlyph`
|
||||
// (which might resize the hashmap!), while the caller `_drawText` is holding onto `glyphs`.
|
||||
// If it wasn't heap allocated, all pointers into `linear_flat_set` would be invalidated.
|
||||
std::unique_ptr<AtlasFontFaceEntryInner> inner;
|
||||
|
||||
bool operator==(const AtlasFontFaceKey& key) const noexcept
|
||||
static bool occupied(const AtlasFontFaceEntry& entry) noexcept
|
||||
{
|
||||
const auto& i = *inner;
|
||||
return i.fontFace.get() == key.fontFace && i.lineRendition == key.lineRendition;
|
||||
return static_cast<bool>(entry.fontFace);
|
||||
}
|
||||
|
||||
operator bool() const noexcept
|
||||
static constexpr size_t hash(const IDWriteFontFace2* fontFace) noexcept
|
||||
{
|
||||
return static_cast<bool>(inner);
|
||||
return til::flat_set_hash_integer(std::bit_cast<uintptr_t>(fontFace));
|
||||
}
|
||||
|
||||
AtlasFontFaceEntry& operator=(const AtlasFontFaceKey& key)
|
||||
static size_t hash(const AtlasFontFaceEntry& entry) noexcept
|
||||
{
|
||||
inner = std::make_unique<AtlasFontFaceEntryInner>();
|
||||
auto& i = *inner;
|
||||
i.fontFace = key.fontFace;
|
||||
i.lineRendition = key.lineRendition;
|
||||
return *this;
|
||||
return hash(entry.fontFace.get());
|
||||
}
|
||||
|
||||
static bool equals(const AtlasFontFaceEntry& entry, const IDWriteFontFace2* fontFace) noexcept
|
||||
{
|
||||
return entry.fontFace.get() == fontFace;
|
||||
}
|
||||
|
||||
static void assign(AtlasFontFaceEntry& entry, IDWriteFontFace2* fontFace) noexcept
|
||||
{
|
||||
entry.fontFace = fontFace;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -216,12 +211,12 @@ namespace Microsoft::Console::Render::Atlas
|
||||
void _uploadBackgroundBitmap(const RenderingPayload& p);
|
||||
void _drawText(RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD void _drawTextOverlapSplit(const RenderingPayload& p, u16 y);
|
||||
ATLAS_ATTR_COLD static void _initializeFontFaceEntry(AtlasFontFaceEntryInner& fontFaceEntry);
|
||||
[[nodiscard]] ATLAS_ATTR_COLD bool _drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry);
|
||||
bool _drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry);
|
||||
void _drawSoftFontGlyphInBitmap(const RenderingPayload& p, const AtlasGlyphEntry& glyphEntry) const;
|
||||
void _drawGlyphPrepareRetry(const RenderingPayload& p);
|
||||
void _splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry);
|
||||
[[nodiscard]] ATLAS_ATTR_COLD AtlasGlyphEntry* _drawGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex);
|
||||
AtlasGlyphEntry* _drawBuiltinGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex);
|
||||
void _drawSoftFontGlyph(const RenderingPayload& p, const stbrp_rect& rect, u32 glyphIndex);
|
||||
void _drawGlyphAtlasAllocate(const RenderingPayload& p, stbrp_rect& rect);
|
||||
static AtlasGlyphEntry* _drawGlyphAllocateEntry(const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex);
|
||||
static void _splitDoubleHeightGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, AtlasGlyphEntry* glyphEntry);
|
||||
void _drawGridlines(const RenderingPayload& p, u16 y);
|
||||
void _drawCursorBackground(const RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD void _drawCursorForeground();
|
||||
@@ -258,7 +253,8 @@ namespace Microsoft::Console::Render::Atlas
|
||||
|
||||
wil::com_ptr<ID3D11Texture2D> _glyphAtlas;
|
||||
wil::com_ptr<ID3D11ShaderResourceView> _glyphAtlasView;
|
||||
til::linear_flat_set<AtlasFontFaceEntry> _glyphAtlasMap;
|
||||
til::linear_flat_set<AtlasFontFaceEntry, AtlasFontFaceEntryHashTrait> _glyphAtlasMap;
|
||||
AtlasFontFaceEntry _builtinGlyphs;
|
||||
Buffer<stbrp_node> _rectPackerData;
|
||||
stbrp_context _rectPacker{};
|
||||
til::CoordType _ligatureOverhangTriggerLeft = 0;
|
||||
|
||||
1276
src/renderer/atlas/BuiltinGlyphs.cpp
Normal file
1276
src/renderer/atlas/BuiltinGlyphs.cpp
Normal file
File diff suppressed because it is too large
Load Diff
18
src/renderer/atlas/BuiltinGlyphs.h
Normal file
18
src/renderer/atlas/BuiltinGlyphs.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
|
||||
namespace Microsoft::Console::Render::Atlas::BuiltinGlyphs
|
||||
{
|
||||
bool IsBuiltinGlyph(char32_t codepoint) noexcept;
|
||||
void DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_RECT_F& rect, char32_t codepoint);
|
||||
|
||||
// This is just an extra. It's not actually implemented as part of BuiltinGlyphs.cpp.
|
||||
constexpr bool IsSoftFontChar(char32_t ch) noexcept
|
||||
{
|
||||
return ch >= 0xEF20 && ch < 0xEF80;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
<ClCompile Include="Backend.cpp" />
|
||||
<ClCompile Include="BackendD2D.cpp" />
|
||||
<ClCompile Include="BackendD3D.cpp" />
|
||||
<ClCompile Include="BuiltinGlyphs.cpp" />
|
||||
<ClCompile Include="dwrite.cpp" />
|
||||
<ClCompile Include="DWriteTextAnalysis.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
@@ -28,6 +29,7 @@
|
||||
<ClInclude Include="Backend.h" />
|
||||
<ClInclude Include="BackendD2D.h" />
|
||||
<ClInclude Include="BackendD3D.h" />
|
||||
<ClInclude Include="BuiltinGlyphs.h" />
|
||||
<ClInclude Include="colorbrewer.h" />
|
||||
<ClInclude Include="common.h" />
|
||||
<ClInclude Include="dwrite.h" />
|
||||
|
||||
@@ -360,6 +360,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
|
||||
u16 dpi = 96;
|
||||
AntialiasingMode antialiasingMode = DefaultAntialiasingMode;
|
||||
bool builtinGlyphs = false;
|
||||
|
||||
std::vector<uint16_t> softFontPattern;
|
||||
til::size softFontCellSize;
|
||||
@@ -420,8 +421,8 @@ namespace Microsoft::Console::Render::Atlas
|
||||
struct FontMapping
|
||||
{
|
||||
wil::com_ptr<IDWriteFontFace2> fontFace;
|
||||
u32 glyphsFrom = 0;
|
||||
u32 glyphsTo = 0;
|
||||
size_t glyphsFrom = 0;
|
||||
size_t glyphsTo = 0;
|
||||
};
|
||||
|
||||
struct GridLineRange
|
||||
@@ -450,11 +451,18 @@ namespace Microsoft::Console::Render::Atlas
|
||||
dirtyBottom = dirtyTop + cellHeight;
|
||||
}
|
||||
|
||||
// Each mappings from/to range indicates the range of indices/advances/offsets/colors this fontFace is to be used for.
|
||||
std::vector<FontMapping> mappings;
|
||||
// Stores glyph indices of the corresponding mappings.fontFace, unless fontFace is nullptr,
|
||||
// in which case this stores UTF16 because we're dealing with a custom glyph (box glyph, etc.).
|
||||
std::vector<u16> glyphIndices;
|
||||
std::vector<f32> glyphAdvances; // same size as glyphIndices
|
||||
std::vector<DWRITE_GLYPH_OFFSET> glyphOffsets; // same size as glyphIndices
|
||||
std::vector<u32> colors; // same size as glyphIndices
|
||||
// Same size as glyphIndices.
|
||||
std::vector<f32> glyphAdvances;
|
||||
// Same size as glyphIndices.
|
||||
std::vector<DWRITE_GLYPH_OFFSET> glyphOffsets;
|
||||
// Same size as glyphIndices.
|
||||
std::vector<u32> colors;
|
||||
|
||||
std::vector<GridLineRange> gridLineRanges;
|
||||
LineRendition lineRendition = LineRendition::SingleWidth;
|
||||
u16 selectionFrom = 0;
|
||||
@@ -499,6 +507,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
|
||||
//// Parameters which change seldom.
|
||||
GenerationalSettings s;
|
||||
std::wstring userLocaleName;
|
||||
|
||||
//// Parameters which change every frame.
|
||||
// This is the backing buffer for `rows`.
|
||||
|
||||
@@ -29,6 +29,11 @@ void FontInfoDesired::SetCellSize(const CSSLengthPercentage& cellWidth, const CS
|
||||
_cellHeight = cellHeight;
|
||||
}
|
||||
|
||||
void FontInfoDesired::SetEnableBuiltinGlyphs(bool builtinGlyphs) noexcept
|
||||
{
|
||||
_builtinGlyphs = builtinGlyphs;
|
||||
}
|
||||
|
||||
const CSSLengthPercentage& FontInfoDesired::GetCellWidth() const noexcept
|
||||
{
|
||||
return _cellWidth;
|
||||
@@ -39,6 +44,11 @@ const CSSLengthPercentage& FontInfoDesired::GetCellHeight() const noexcept
|
||||
return _cellHeight;
|
||||
}
|
||||
|
||||
bool FontInfoDesired::GetEnableBuiltinGlyphs() const noexcept
|
||||
{
|
||||
return _builtinGlyphs;
|
||||
}
|
||||
|
||||
float FontInfoDesired::GetFontSize() const noexcept
|
||||
{
|
||||
return _fontSize;
|
||||
|
||||
@@ -35,9 +35,11 @@ public:
|
||||
bool operator==(const FontInfoDesired& other) = delete;
|
||||
|
||||
void SetCellSize(const CSSLengthPercentage& cellWidth, const CSSLengthPercentage& cellHeight) noexcept;
|
||||
void SetEnableBuiltinGlyphs(bool builtinGlyphs) noexcept;
|
||||
|
||||
const CSSLengthPercentage& GetCellWidth() const noexcept;
|
||||
const CSSLengthPercentage& GetCellHeight() const noexcept;
|
||||
bool GetEnableBuiltinGlyphs() const noexcept;
|
||||
float GetFontSize() const noexcept;
|
||||
til::size GetEngineSize() const noexcept;
|
||||
bool IsDefaultRasterFont() const noexcept;
|
||||
@@ -47,4 +49,5 @@ private:
|
||||
float _fontSize;
|
||||
CSSLengthPercentage _cellWidth;
|
||||
CSSLengthPercentage _cellHeight;
|
||||
bool _builtinGlyphs = false;
|
||||
};
|
||||
|
||||
@@ -12,38 +12,35 @@ using namespace WEX::TestExecution;
|
||||
struct Data
|
||||
{
|
||||
static constexpr auto emptyMarker = std::numeric_limits<size_t>::max();
|
||||
|
||||
constexpr operator bool() const noexcept
|
||||
{
|
||||
return value != emptyMarker;
|
||||
}
|
||||
|
||||
constexpr bool operator==(int key) const noexcept
|
||||
{
|
||||
return value == static_cast<size_t>(key);
|
||||
}
|
||||
|
||||
constexpr Data& operator=(int key) noexcept
|
||||
{
|
||||
value = static_cast<size_t>(key);
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t value = emptyMarker;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ::std::hash<Data>
|
||||
struct DataHashTrait
|
||||
{
|
||||
constexpr size_t operator()(int key) const noexcept
|
||||
static constexpr bool occupied(const Data& d) noexcept
|
||||
{
|
||||
return til::flat_set_hash_integer(static_cast<size_t>(key));
|
||||
return d.value != Data::emptyMarker;
|
||||
}
|
||||
|
||||
constexpr size_t operator()(Data d) const noexcept
|
||||
static constexpr size_t hash(const size_t key) noexcept
|
||||
{
|
||||
return til::flat_set_hash_integer(key);
|
||||
}
|
||||
|
||||
static constexpr size_t hash(const Data& d) noexcept
|
||||
{
|
||||
return til::flat_set_hash_integer(d.value);
|
||||
}
|
||||
|
||||
static constexpr bool equals(const Data& d, size_t key) noexcept
|
||||
{
|
||||
return d.value == key;
|
||||
}
|
||||
|
||||
static constexpr void assign(Data& d, size_t key) noexcept
|
||||
{
|
||||
d.value = key;
|
||||
}
|
||||
};
|
||||
|
||||
class FlatSetTests
|
||||
@@ -52,7 +49,7 @@ class FlatSetTests
|
||||
|
||||
TEST_METHOD(Basic)
|
||||
{
|
||||
til::linear_flat_set<Data> set;
|
||||
til::linear_flat_set<Data, DataHashTrait> set;
|
||||
|
||||
// This simultaneously demonstrates how the class can't just do "heterogeneous lookups"
|
||||
// like STL does, but also insert items with a different type.
|
||||
@@ -62,7 +59,7 @@ class FlatSetTests
|
||||
const auto [entry2, inserted2] = set.insert(123);
|
||||
VERIFY_IS_FALSE(inserted2);
|
||||
|
||||
VERIFY_ARE_EQUAL(&entry1, &entry2);
|
||||
VERIFY_ARE_EQUAL(123u, entry2.value);
|
||||
VERIFY_ARE_EQUAL(entry1, entry2);
|
||||
VERIFY_ARE_EQUAL(123u, entry2->value);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user