diff --git a/src/inc/til.h b/src/inc/til.h index bbafefe0cd7..b1773062cbc 100644 --- a/src/inc/til.h +++ b/src/inc/til.h @@ -18,6 +18,26 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { + template + void manage_vector(std::vector& vector, typename std::vector::size_type requestedSize, float shrinkThreshold) + { + const auto existingCapacity = vector.capacity(); + const auto requiredCapacity = requestedSize; + + // Check by integer first as float math is way more expensive. + if (requiredCapacity < existingCapacity) + { + // Now check if it's even worth shrinking. We don't want to shrink by 1 at a time, so meet a threshold first. + if (requiredCapacity <= gsl::narrow_cast((static_cast(existingCapacity) * shrinkThreshold))) + { + // There's no real way to force a shrink, so make a new one. + vector = std::vector{}; + } + } + + // Reserve won't shrink on its own and won't grow if we have enough space. + vector.reserve(requiredCapacity); + } } // These sit outside the namespace because they sit outside for WIL too. diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 3caaf4b836b..6cae08d9ac2 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -28,7 +28,8 @@ Renderer::Renderer(IRenderData* pData, std::unique_ptr thread) : _pData(pData), _pThread{ std::move(thread) }, - _destructing{ false } + _destructing{ false }, + _clusterBuffer{} { _srViewportPrevious = { 0 }; @@ -370,6 +371,12 @@ bool Renderer::_CheckViewportAndScroll() _srViewportPrevious = srNewViewport; + // If we're keeping some buffers between calls, let them know about the viewport size + // so they can prepare the buffers for changes to either preallocate memory at once + // (instead of growing naturally) or shrink down to reduce usage as appropriate. + const size_t lineLength = gsl::narrow_cast(til::rectangle{ srNewViewport }.width()); + til::manage_vector(_clusterBuffer, lineLength, _shrinkThreshold); + if (coordDelta.X != 0 || coordDelta.Y != 0) { for (auto engine : _rgpEngines) @@ -683,7 +690,6 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, // we should have an iterator/view adapter for the rendering. // That would probably also eliminate the RenderData needing to give us the entire TextBuffer as well... // Retrieve the iterator for one line of information. - std::vector clusters; size_t cols = 0; // Retrieve the first color. @@ -714,7 +720,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, const auto currentRunTargetStart = screenPoint; // Ensure that our cluster vector is clear. - clusters.clear(); + _clusterBuffer.clear(); // Reset our flag to know when we're in the special circumstance // of attempting to draw only the right-half of a two-column character @@ -746,7 +752,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, // If we're on the first cluster to be added and it's marked as "trailing" // (a.k.a. the right half of a two column character), then we need some special handling. - if (clusters.empty() && it->DbcsAttr().IsTrailing()) + if (_clusterBuffer.empty() && it->DbcsAttr().IsTrailing()) { // If we have room to move to the left to start drawing... if (screenPoint.X > 0) @@ -757,7 +763,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, trimLeft = true; // And add one to the number of columns we expect it to take as we insert it. columnCount = it->Columns() + 1; - clusters.emplace_back(it->Chars(), columnCount); + _clusterBuffer.emplace_back(it->Chars(), columnCount); } else { @@ -770,7 +776,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, else { columnCount = it->Columns(); - clusters.emplace_back(it->Chars(), columnCount); + _clusterBuffer.emplace_back(it->Chars(), columnCount); } if (columnCount > 1) @@ -785,7 +791,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, } while (it); // Do the painting. - THROW_IF_FAILED(pEngine->PaintBufferLine({ clusters.data(), clusters.size() }, screenPoint, trimLeft, lineWrapped)); + THROW_IF_FAILED(pEngine->PaintBufferLine({ _clusterBuffer.data(), _clusterBuffer.size() }, screenPoint, trimLeft, lineWrapped)); // If we're allowed to do grid drawing, draw that now too (since it will be coupled with the color data) // We're only allowed to draw the grid lines under certain circumstances. diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index 37a72c56925..04cd76d0bc1 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -121,6 +121,9 @@ namespace Microsoft::Console::Render SMALL_RECT _srViewportPrevious; + static constexpr float _shrinkThreshold = 0.8f; + std::vector _clusterBuffer; + std::vector _GetSelectionRects() const; void _ScrollPreviousSelection(const til::point delta); std::vector _previousSelection; diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 3f8eae05d9c..060bbd119ce 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -463,7 +463,9 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, size_t clearType = 0; unsigned int function = 0; DispatchTypes::EraseType eraseType = DispatchTypes::EraseType::ToEnd; - std::vector graphicsOptions; + // We hold the vector in the class because client applications that do a lot of color work + // would spend a lot of time reallocating/resizing the vector. + _graphicsOptions.clear(); DispatchTypes::AnsiStatusType deviceStatusType = static_cast(0); // there is no default status type. size_t repeatCount = 0; // This is all the args after the first arg, and the count of args not including the first one. @@ -502,7 +504,7 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, success = _GetEraseOperation(parameters, eraseType); break; case VTActionCodes::SGR_SetGraphicsRendition: - success = _GetGraphicsOptions(parameters, graphicsOptions); + success = _GetGraphicsOptions(parameters, _graphicsOptions); break; case VTActionCodes::DSR_DeviceStatusReport: success = _GetDeviceStatusOperation(parameters, deviceStatusType); @@ -613,7 +615,7 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, TermTelemetry::Instance().Log(TermTelemetry::Codes::EL); break; case VTActionCodes::SGR_SetGraphicsRendition: - success = _dispatch->SetGraphicsRendition({ graphicsOptions.data(), graphicsOptions.size() }); + success = _dispatch->SetGraphicsRendition({ _graphicsOptions.data(), _graphicsOptions.size() }); TermTelemetry::Instance().Log(TermTelemetry::Codes::SGR); break; case VTActionCodes::DSR_DeviceStatusReport: diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index 1a0b97e16e4..bbe0fc5e42d 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -71,6 +71,7 @@ namespace Microsoft::Console::VirtualTerminal Microsoft::Console::ITerminalOutputConnection* _pTtyConnection; std::function _pfnFlushToTerminal; wchar_t _lastPrintedChar; + std::vector _graphicsOptions; bool _IntermediateScsDispatch(const wchar_t wch, const std::basic_string_view intermediates); diff --git a/src/til/ut_til/BaseTests.cpp b/src/til/ut_til/BaseTests.cpp new file mode 100644 index 00000000000..bdae62ed71e --- /dev/null +++ b/src/til/ut_til/BaseTests.cpp @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "til.h" + +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +// Tests for things in the TIL base class. +class BaseTests +{ + TEST_CLASS(BaseTests); + + TEST_METHOD(ManageVector) + { + constexpr float shrinkThreshold = 0.5f; + + std::vector foo; + foo.reserve(20); + + Log::Comment(L"Expand vector."); + til::manage_vector(foo, 30, shrinkThreshold); + VERIFY_ARE_EQUAL(30, foo.capacity()); + + Log::Comment(L"Try shrink but by not enough for threshold."); + til::manage_vector(foo, 18, shrinkThreshold); + VERIFY_ARE_EQUAL(30, foo.capacity()); + + Log::Comment(L"Shrink because it is meeting threshold."); + til::manage_vector(foo, 15, shrinkThreshold); + VERIFY_ARE_EQUAL(15, foo.capacity()); + } +}; diff --git a/src/til/ut_til/sources b/src/til/ut_til/sources index 0fd583bafb4..4cd37543c7f 100644 --- a/src/til/ut_til/sources +++ b/src/til/ut_til/sources @@ -14,6 +14,7 @@ DLLDEF = SOURCES = \ $(SOURCES) \ + BaseTests.cpp \ BitmapTests.cpp \ ColorTests.cpp \ OperatorTests.cpp \ diff --git a/src/til/ut_til/til.unit.tests.vcxproj b/src/til/ut_til/til.unit.tests.vcxproj index 011a39a7682..84ee72de877 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj +++ b/src/til/ut_til/til.unit.tests.vcxproj @@ -10,6 +10,7 @@ + diff --git a/src/til/ut_til/til.unit.tests.vcxproj.filters b/src/til/ut_til/til.unit.tests.vcxproj.filters index 21bf4896239..20492e2c64c 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj.filters +++ b/src/til/ut_til/til.unit.tests.vcxproj.filters @@ -13,6 +13,8 @@ + +