@@ -59,17 +59,14 @@ TextBuffer::TextBuffer(til::size screenBufferSize,
5959 _cursor{ cursorSize, *this },
6060 _isActiveBuffer{ isActiveBuffer }
6161{
62- // Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
63- screenBufferSize.width = std::max (screenBufferSize.width , 1 );
64- screenBufferSize.height = std::max (screenBufferSize.height , 1 );
6562 _reserve (screenBufferSize, defaultAttributes);
6663}
6764
6865TextBuffer::~TextBuffer ()
6966{
7067 if (_buffer)
7168 {
72- _destroy ();
69+ _destroy (_buffer. get () );
7370 }
7471}
7572
@@ -88,10 +85,11 @@ TextBuffer::~TextBuffer()
8885// with our huge allocation, as well as to be able to reduce the private working set of
8986// the application by only committing what we actually need. This reduces conhost's
9087// memory usage from ~7MB down to just ~2MB at startup in the general case.
91- void TextBuffer::_reserve (til::size screenBufferSize, const TextAttribute& defaultAttributes)
88+ __declspec (noinline) void TextBuffer::_reserve(til::size screenBufferSize, const TextAttribute& defaultAttributes)
9289{
93- const auto w = gsl::narrow<uint16_t >(screenBufferSize.width );
94- const auto h = gsl::narrow<uint16_t >(screenBufferSize.height );
90+ // Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
91+ const auto w = std::clamp (screenBufferSize.width , 1 , 0xffff );
92+ const auto h = std::clamp (screenBufferSize.height , 1 , til::CoordTypeMax / 2 + UINT16_MAX);
9593
9694 constexpr auto rowSize = ROW::CalculateRowSize ();
9795 const auto charsBufferSize = ROW::CalculateCharsBufferSize (w);
@@ -102,13 +100,13 @@ void TextBuffer::_reserve(til::size screenBufferSize, const TextAttribute& defau
102100 // 65535*65535 cells would result in a allocSize of 8GiB.
103101 // --> Use uint64_t so that we can safely do our calculations even on x86.
104102 // We allocate 1 additional row, which will be used for GetScratchpadRow().
105- const auto rowCount = ::base::strict_cast <uint64_t >(h) + 1 ;
103+ const auto rowCount = gsl::narrow_cast <uint64_t >(h) + 1 ;
106104 const auto allocSize = gsl::narrow<size_t >(rowCount * rowStride);
107105
108106 // NOTE: Modifications to this block of code might have to be mirrored over to ResizeTraditional().
109107 // It constructs a temporary TextBuffer and then extracts the members below, overwriting itself.
110- _buffer = wil::unique_virtualalloc_ptr<std::byte >{
111- static_cast <std::byte *>(THROW_LAST_ERROR_IF_NULL (VirtualAlloc (nullptr , allocSize, MEM_RESERVE, PAGE_READWRITE)))
108+ _buffer = wil::unique_virtualalloc_ptr<uint8_t >{
109+ static_cast <uint8_t *>(THROW_LAST_ERROR_IF_NULL (VirtualAlloc (nullptr , allocSize, MEM_RESERVE, PAGE_READWRITE)))
112110 };
113111 _bufferEnd = _buffer.get () + allocSize;
114112 _commitWatermark = _buffer.get ();
@@ -126,7 +124,7 @@ void TextBuffer::_reserve(til::size screenBufferSize, const TextAttribute& defau
126124// Declaring this function as noinline allows _getRowByOffsetDirect() to be inlined,
127125// which improves overall TextBuffer performance by ~6%. And all it cost is this annotation.
128126// The compiler doesn't understand the likelihood of our branches. (PGO does, but that's imperfect.)
129- __declspec (noinline) void TextBuffer::_commit(const std::byte * row)
127+ __declspec (noinline) void TextBuffer::_commit(const uint8_t * row)
130128{
131129 assert (row >= _commitWatermark);
132130
@@ -143,29 +141,62 @@ __declspec(noinline) void TextBuffer::_commit(const std::byte* row)
143141
144142// Destructs and MEM_DECOMMITs all previously constructed ROWs.
145143// You can use this (or rather the Reset() method) to fully clear the TextBuffer.
146- void TextBuffer::_decommit () noexcept
144+ void TextBuffer::_decommit (til::CoordType keep ) noexcept
147145{
148- _destroy ();
149- VirtualFree (_buffer.get (), 0 , MEM_DECOMMIT);
150- _commitWatermark = _buffer.get ();
146+ SYSTEM_INFO si;
147+ GetSystemInfo (&si);
148+
149+ keep = std::clamp (keep, 0 , _height);
150+ keep += _commitReadAheadRowCount;
151+ keep = std::min (keep, _height);
152+
153+ // Amount of bytes that have been allocated with MEM_COMMIT so far.
154+ const auto commitBytes = gsl::narrow_cast<size_t >(_commitWatermark - _buffer.get ());
155+ // Offset in bytes to the first row that we were asked to destroy.
156+ // We must ensure that the offset is not past the end of the current _commitWatermark,
157+ // since we don't want to finish with a watermark that's somehow larger than what we started with.
158+ const auto byteOffset = std::min (commitBytes, keep * _bufferRowStride);
159+ const auto newWatermark = _buffer.get () + byteOffset;
160+ // Since the last row we were asked to keep may reside in the middle
161+ // of a page, we must round the offset up to the next page boundary.
162+ // That offset will tell us the offset at which we will MEM_DECOMMIT memory.
163+ const auto pageMask = gsl::narrow_cast<size_t >(si.dwPageSize ) - 1 ;
164+ const auto pageOffset = (byteOffset + pageMask) & ~pageMask;
165+
166+ // _destroy() takes care to check that the given pointer is valid.
167+ _destroy (newWatermark);
168+
169+ // MEM_DECOMMIT the memory that we don't need anymore.
170+ if (pageOffset < commitBytes)
171+ {
172+ VirtualFree (_buffer.get () + pageOffset, commitBytes - pageOffset, MEM_DECOMMIT);
173+ }
174+
175+ _commitWatermark = newWatermark;
151176}
152177
153178// Constructs ROWs between [_commitWatermark,until).
154- void TextBuffer::_construct (const std::byte * until) noexcept
179+ void TextBuffer::_construct (const uint8_t * until) noexcept
155180{
156- for (; _commitWatermark < until; _commitWatermark += _bufferRowStride)
181+ // _width has been validated to fit into uint16_t during reserve().
182+ const auto width = gsl::narrow_cast<uint16_t >(_width);
183+ auto wm = _commitWatermark;
184+
185+ for (; wm < until; wm += _bufferRowStride)
157186 {
158- const auto row = reinterpret_cast <ROW*>(_commitWatermark );
159- const auto chars = reinterpret_cast <wchar_t *>(_commitWatermark + _bufferOffsetChars);
160- const auto indices = reinterpret_cast <uint16_t *>(_commitWatermark + _bufferOffsetCharOffsets);
161- std::construct_at (row, chars, indices, _width , _initialAttributes);
187+ const auto row = reinterpret_cast <ROW*>(wm );
188+ const auto chars = reinterpret_cast <wchar_t *>(wm + _bufferOffsetChars);
189+ const auto indices = reinterpret_cast <uint16_t *>(wm + _bufferOffsetCharOffsets);
190+ std::construct_at (row, chars, indices, width , _initialAttributes);
162191 }
192+
193+ _commitWatermark = wm;
163194}
164195
165- // Destructs ROWs between [_buffer ,_commitWatermark).
166- void TextBuffer::_destroy () const noexcept
196+ // Destructs ROWs between [it ,_commitWatermark).
197+ void TextBuffer::_destroy (uint8_t * it ) const noexcept
167198{
168- for (auto it = _buffer. get () ; it < _commitWatermark; it += _bufferRowStride)
199+ for (; it < _commitWatermark; it += _bufferRowStride)
169200 {
170201 std::destroy_at (reinterpret_cast <ROW*>(it));
171202 }
@@ -973,7 +1004,7 @@ til::point TextBuffer::BufferToScreenPosition(const til::point position) const
9731004// and the default current color attributes
9741005void TextBuffer::Reset () noexcept
9751006{
976- _decommit ();
1007+ _decommit (0 );
9771008 _initialAttributes = _currentAttributes;
9781009}
9791010
@@ -988,29 +1019,20 @@ void TextBuffer::ClearScrollback(const til::CoordType newFirstRow, const til::Co
9881019 return ;
9891020 }
9901021 // The new viewport should keep 0 rows? Then just reset everything.
991- if (rowsToKeep <= 0 )
1022+ if (rowsToKeep > 0 )
9921023 {
993- _decommit ();
994- return ;
1024+ // Our goal is to move the viewport to the absolute start of the underlying memory buffer so that we can
1025+ // MEM_DECOMMIT the remaining memory. _firstRow is used to make the TextBuffer behave like a circular buffer.
1026+ // The newFirstRow parameter is relative to the _firstRow. The trick to get the content to the absolute start
1027+ // is to simply add _firstRow ourselves and then reset it to 0. This causes ScrollRows() to write into
1028+ // the absolute start while reading from relative coordinates. This works because GetRowByOffset()
1029+ // operates modulo the buffer height and so the possibly-too-large startAbsolute won't be an issue.
1030+ const auto startAbsolute = _firstRow + newFirstRow;
1031+ _firstRow = 0 ;
1032+ ScrollRows (startAbsolute, rowsToKeep, -startAbsolute);
9951033 }
9961034
997- ClearMarksInRange (til::point{ 0 , 0 }, til::point{ _width, std::max (0 , newFirstRow - 1 ) });
998-
999- // Our goal is to move the viewport to the absolute start of the underlying memory buffer so that we can
1000- // MEM_DECOMMIT the remaining memory. _firstRow is used to make the TextBuffer behave like a circular buffer.
1001- // The newFirstRow parameter is relative to the _firstRow. The trick to get the content to the absolute start
1002- // is to simply add _firstRow ourselves and then reset it to 0. This causes ScrollRows() to write into
1003- // the absolute start while reading from relative coordinates. This works because GetRowByOffset()
1004- // operates modulo the buffer height and so the possibly-too-large startAbsolute won't be an issue.
1005- const auto startAbsolute = _firstRow + newFirstRow;
1006- _firstRow = 0 ;
1007- ScrollRows (startAbsolute, rowsToKeep, -startAbsolute);
1008-
1009- const auto end = _estimateOffsetOfLastCommittedRow ();
1010- for (auto y = rowsToKeep; y <= end; ++y)
1011- {
1012- GetMutableRowByOffset (y).Reset (_initialAttributes);
1013- }
1035+ _decommit (rowsToKeep);
10141036}
10151037
10161038// Routine Description:
@@ -2015,7 +2037,7 @@ std::string TextBuffer::GenHTML(const CopyRequest& req,
20152037 const auto [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper (req, iRow, row);
20162038 const auto rowBegU16 = gsl::narrow_cast<uint16_t >(rowBeg);
20172039 const auto rowEndU16 = gsl::narrow_cast<uint16_t >(rowEnd);
2018- const auto runs = row.Attributes ().slice (rowBegU16, rowEndU16).runs ();
2040+ const auto & runs = row.Attributes ().slice (rowBegU16, rowEndU16).runs ();
20192041
20202042 auto x = rowBegU16;
20212043 for (const auto & [attr, length] : runs)
@@ -2265,7 +2287,7 @@ std::string TextBuffer::GenRTF(const CopyRequest& req,
22652287 const auto [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper (req, iRow, row);
22662288 const auto rowBegU16 = gsl::narrow_cast<uint16_t >(rowBeg);
22672289 const auto rowEndU16 = gsl::narrow_cast<uint16_t >(rowEnd);
2268- const auto runs = row.Attributes ().slice (rowBegU16, rowEndU16).runs ();
2290+ const auto & runs = row.Attributes ().slice (rowBegU16, rowEndU16).runs ();
22692291
22702292 auto x = rowBegU16;
22712293 for (auto & [attr, length] : runs)
@@ -2457,7 +2479,7 @@ void TextBuffer::_SerializeRow(const ROW& row, const til::CoordType startX, cons
24572479
24582480 const auto startXU16 = gsl::narrow_cast<uint16_t >(startX);
24592481 const auto endXU16 = gsl::narrow_cast<uint16_t >(endX);
2460- const auto runs = row.Attributes ().slice (startXU16, endXU16).runs ();
2482+ const auto & runs = row.Attributes ().slice (startXU16, endXU16).runs ();
24612483
24622484 const auto beg = runs.begin ();
24632485 const auto end = runs.end ();
@@ -3246,7 +3268,6 @@ MarkExtents TextBuffer::_scrollMarkExtentForRow(const til::CoordType rowOffset,
32463268 bool startedPrompt = false ;
32473269 bool startedCommand = false ;
32483270 bool startedOutput = false ;
3249- MarkKind lastMarkKind = MarkKind::Output;
32503271
32513272 const auto endThisMark = [&](auto x, auto y) {
32523273 if (startedOutput)
@@ -3273,7 +3294,7 @@ MarkExtents TextBuffer::_scrollMarkExtentForRow(const til::CoordType rowOffset,
32733294 // Output attribute.
32743295
32753296 const auto & row = GetRowByOffset (y);
3276- const auto runs = row.Attributes ().runs ();
3297+ const auto & runs = row.Attributes ().runs ();
32773298 x = 0 ;
32783299 for (const auto & [attr, length] : runs)
32793300 {
@@ -3316,8 +3337,6 @@ MarkExtents TextBuffer::_scrollMarkExtentForRow(const til::CoordType rowOffset,
33163337
33173338 endThisMark (lastMarkedText.x , lastMarkedText.y );
33183339 }
3319- // Otherwise, we've changed from any state -> any state, and it doesn't really matter.
3320- lastMarkKind = markKind;
33213340 }
33223341 // advance to next run of text
33233342 x = nextX;
@@ -3350,7 +3369,7 @@ std::wstring TextBuffer::_commandForRow(const til::CoordType rowOffset,
33503369 // Command attributes. Collect up all of those, till we get to the next
33513370 // Output attribute.
33523371 const auto & row = GetRowByOffset (y);
3353- const auto runs = row.Attributes ().runs ();
3372+ const auto & runs = row.Attributes ().runs ();
33543373 auto x = 0 ;
33553374 for (const auto & [attr, length] : runs)
33563375 {
0 commit comments