2525
2626using namespace Microsoft ::Console::Render;
2727
28- #pragma warning(push)
29- #pragma warning(disable : 26447) // The function is declared 'noexcept' but calls function 'operator()()' which may throw exceptions (f.6).
30- __declspec (noinline) static void showOOMWarning() noexcept
31- {
32- [[maybe_unused]] static const auto once = []() {
33- std::thread t{ []() noexcept {
34- MessageBoxW (nullptr , L" This application is using a highly experimental text rendering engine and has run out of memory. Text rendering will start to behave irrationally and you should restart this process." , L" Out Of Memory" , MB_ICONERROR | MB_OK);
35- } };
36- t.detach ();
37- return false ;
38- }();
39- }
40- #pragma warning(pop)
41-
4228struct TextAnalyzer final : IDWriteTextAnalysisSource, IDWriteTextAnalysisSink
4329{
4430 constexpr TextAnalyzer (const std::vector<wchar_t >& text, std::vector<AtlasEngine::TextAnalyzerResult>& results) noexcept :
@@ -365,12 +351,14 @@ try
365351 }
366352 }
367353
368- _api.dirtyRect = til::rect{
369- 0 ,
370- _api.invalidatedRows .x ,
371- _api.cellCount .x ,
372- _api.invalidatedRows .y ,
373- };
354+ if constexpr (debugGlyphGenerationPerformance)
355+ {
356+ _api.dirtyRect = til::rect{ 0 , 0 , _api.cellCount .x , _api.cellCount .y };
357+ }
358+ else
359+ {
360+ _api.dirtyRect = til::rect{ 0 , _api.invalidatedRows .x , _api.cellCount .x , _api.invalidatedRows .y };
361+ }
374362
375363 return S_OK;
376364}
@@ -394,7 +382,7 @@ CATCH_RETURN()
394382
395383[[nodiscard]] bool AtlasEngine::RequiresContinuousRedraw() noexcept
396384{
397- return continuousRedraw ;
385+ return debugGeneralPerformance ;
398386}
399387
400388void AtlasEngine::WaitUntilCanRender () noexcept
559547 const auto point = options.coordCursor ;
560548 // TODO: options.coordCursor can contain invalid out of bounds coordinates when
561549 // the window is being resized and the cursor is on the last line of the viewport.
562- const auto x = gsl::narrow_cast<uint16_t >(clamp<int >(point.X , 0 , _r.cellCount .x - 1 ));
563- const auto y = gsl::narrow_cast<uint16_t >(clamp<int >(point.Y , 0 , _r.cellCount .y - 1 ));
564- const auto right = gsl::narrow_cast<uint16_t >(x + 1 + (options.fIsDoubleWidth & (options.cursorType != CursorType::VerticalBar)));
550+ const auto x = gsl::narrow_cast<uint16_t >(clamp (point.X , 0 , _r.cellCount .x - 1 ));
551+ const auto y = gsl::narrow_cast<uint16_t >(clamp (point.Y , 0 , _r.cellCount .y - 1 ));
552+ const auto cursorWidth = 1 + (options.fIsDoubleWidth & (options.cursorType != CursorType::VerticalBar));
553+ const auto right = gsl::narrow_cast<uint16_t >(clamp (x + cursorWidth, 0 , _r.cellCount .x - 0 ));
565554 const auto bottom = gsl::narrow_cast<uint16_t >(y + 1 );
566555 _setCellFlags ({ x, y, right, bottom }, CellFlags::Cursor, CellFlags::Cursor);
567556 }
@@ -775,7 +764,7 @@ void AtlasEngine::_createSwapChain()
775764
776765 // D3D swap chain setup (the thing that allows us to present frames on the screen)
777766 {
778- const auto supportsFrameLatencyWaitableObject = IsWindows8Point1OrGreater ();
767+ const auto supportsFrameLatencyWaitableObject = !debugGeneralPerformance && IsWindows8Point1OrGreater ();
779768
780769 // With C++20 we'll finally have designated initializers.
781770 DXGI_SWAP_CHAIN_DESC1 desc{};
@@ -899,6 +888,7 @@ void AtlasEngine::_recreateSizeDependentResources()
899888 // (40x on AMD Zen1-3, which have a rep movsb performance issue. MSFT:33358259.)
900889 _r.cells = Buffer<Cell, 32 >{ totalCellCount };
901890 _r.cellCount = _api.cellCount ;
891+ _r.tileAllocator .setMaxArea (_api.sizeInPixel );
902892
903893 // .clear() doesn't free the memory of these buffers.
904894 // This code allows them to shrink again.
@@ -947,32 +937,14 @@ void AtlasEngine::_recreateFontDependentResources()
947937
948938 // D3D
949939 {
950- // TODO: Consider using IDXGIAdapter3::QueryVideoMemoryInfo() and IDXGIAdapter3::RegisterVideoMemoryBudgetChangeNotificationEvent()
951- // That way we can make better to use of a user's available video memory.
952-
953- static constexpr size_t sizePerPixel = 4 ;
954- static constexpr size_t sizeLimit = D3D10_REQ_RESOURCE_SIZE_IN_MEGABYTES * 1024 * 1024 ;
955- const size_t dimensionLimit = _r.device ->GetFeatureLevel () >= D3D_FEATURE_LEVEL_11_0 ? D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION : D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION;
956- const size_t csx = _api.fontMetrics .cellSize .x ;
957- const size_t csy = _api.fontMetrics .cellSize .y ;
958- const auto xLimit = (dimensionLimit / csx) * csx;
959- const auto pixelsPerCellRow = xLimit * csy;
960- const auto yLimitDueToDimension = (dimensionLimit / csy) * csy;
961- const auto yLimitDueToSize = ((sizeLimit / sizePerPixel) / pixelsPerCellRow) * csy;
962- const auto yLimit = std::min (yLimitDueToDimension, yLimitDueToSize);
963940 const auto scaling = GetScaling ();
964941
965942 _r.cellSizeDIP .x = static_cast <float >(_api.fontMetrics .cellSize .x ) / scaling;
966943 _r.cellSizeDIP .y = static_cast <float >(_api.fontMetrics .cellSize .y ) / scaling;
967944 _r.cellSize = _api.fontMetrics .cellSize ;
968945 _r.cellCount = _api.cellCount ;
969- // x/yLimit are strictly smaller than dimensionLimit, which is smaller than a u16.
970- _r.atlasSizeInPixelLimit = u16x2{ gsl::narrow_cast<u16 >(xLimit), gsl::narrow_cast<u16 >(yLimit) };
971946 _r.atlasSizeInPixel = { 0 , 0 };
972- // The first Cell at {0, 0} is always our cursor texture.
973- // --> The first glyph starts at {1, 0}.
974- _r.atlasPosition .x = _api.fontMetrics .cellSize .x ;
975- _r.atlasPosition .y = 0 ;
947+ _r.tileAllocator = TileAllocator{ _r.cellSize , _api.sizeInPixel };
976948
977949 _r.glyphs = {};
978950 _r.glyphQueue = {};
@@ -1118,26 +1090,6 @@ void AtlasEngine::_setCellFlags(u16r coords, CellFlags mask, CellFlags bits) noe
11181090 }
11191091}
11201092
1121- AtlasEngine::u16x2 AtlasEngine::_allocateAtlasTile () noexcept
1122- {
1123- const auto ret = _r.atlasPosition ;
1124-
1125- _r.atlasPosition .x += _r.cellSize .x ;
1126- if (_r.atlasPosition .x >= _r.atlasSizeInPixelLimit .x )
1127- {
1128- _r.atlasPosition .x = 0 ;
1129- _r.atlasPosition .y += _r.cellSize .y ;
1130- if (_r.atlasPosition .y >= _r.atlasSizeInPixelLimit .y )
1131- {
1132- _r.atlasPosition .x = _r.cellSize .x ;
1133- _r.atlasPosition .y = 0 ;
1134- showOOMWarning ();
1135- }
1136- }
1137-
1138- return ret;
1139- }
1140-
11411093void AtlasEngine::_flushBufferLine ()
11421094{
11431095 if (_api.bufferLine .empty ())
@@ -1449,11 +1401,10 @@ void AtlasEngine::_emplaceGlyph(IDWriteFontFace* fontFace, size_t bufferPos1, si
14491401 auto attributes = _api.attributes ;
14501402 attributes.cellCount = cellCount;
14511403
1452- const auto [it, inserted] = _r.glyphs .emplace (std::piecewise_construct, std::forward_as_tuple (attributes, gsl::narrow<u16 >(charCount), chars), std::forward_as_tuple ());
1453- const auto & key = it->first ;
1454- auto & value = it->second ;
1404+ AtlasKey key{ attributes, gsl::narrow<u16 >(charCount), chars };
1405+ const AtlasValue* valueRef = _r.glyphs .find (key);
14551406
1456- if (inserted )
1407+ if (!valueRef )
14571408 {
14581409 // Do fonts exist *in practice* which contain both colored and uncolored glyphs? I'm pretty sure...
14591410 // However doing it properly means using either of:
@@ -1481,17 +1432,28 @@ void AtlasEngine::_emplaceGlyph(IDWriteFontFace* fontFace, size_t bufferPos1, si
14811432 WI_SetFlagIf (flags, CellFlags::ColoredGlyph, fontFace2 && fontFace2->IsColorFont ());
14821433 }
14831434
1484- const auto coords = value.initialize (flags, cellCount);
1435+ // The AtlasValue constructor fills the `coords` variable with a pointer to an array
1436+ // of at least `cellCount` elements. I did this so that I don't have to type out
1437+ // `value.data()->coords` again, despite the constructor having all the data necessary.
1438+ u16x2* coords;
1439+ AtlasValue value{ flags, cellCount, &coords };
1440+
14851441 for (u16 i = 0 ; i < cellCount; ++i)
14861442 {
1487- coords[i] = _allocateAtlasTile ( );
1443+ coords[i] = _r. tileAllocator . allocate (_r. glyphs );
14881444 }
14891445
1490- _r.glyphQueue .push_back (AtlasQueueItem{ &key, &value });
1446+ const auto it = _r.glyphs .insert (std::move (key), std::move (value));
1447+ valueRef = &it->second ;
1448+ _r.glyphQueue .emplace_back (&it->first , &it->second );
14911449 _r.maxEncounteredCellCount = std::max (_r.maxEncounteredCellCount , cellCount);
14921450 }
14931451
1494- const auto valueData = value.data ();
1452+ // For some reason MSVC doesn't understand that valueRef is overwritten in the branch above, resulting in:
1453+ // C26430: Symbol 'valueRef' is not tested for nullness on all paths (f.23).
1454+ __assume (valueRef != nullptr );
1455+
1456+ const auto valueData = valueRef->data ();
14951457 const auto coords = &valueData->coords [0 ];
14961458 const auto data = _getCell (x1, _api.lastPaintBufferLineCoord .y );
14971459
0 commit comments