@@ -59,17 +59,14 @@ TextBuffer::TextBuffer(til::size screenBufferSize,
59
59
_cursor{ cursorSize, *this },
60
60
_isActiveBuffer{ isActiveBuffer }
61
61
{
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 );
65
62
_reserve (screenBufferSize, defaultAttributes);
66
63
}
67
64
68
65
TextBuffer::~TextBuffer ()
69
66
{
70
67
if (_buffer)
71
68
{
72
- _destroy ();
69
+ _destroy (_buffer. get () );
73
70
}
74
71
}
75
72
@@ -88,10 +85,11 @@ TextBuffer::~TextBuffer()
88
85
// with our huge allocation, as well as to be able to reduce the private working set of
89
86
// the application by only committing what we actually need. This reduces conhost's
90
87
// 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)
92
89
{
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);
95
93
96
94
constexpr auto rowSize = ROW::CalculateRowSize ();
97
95
const auto charsBufferSize = ROW::CalculateCharsBufferSize (w);
@@ -102,13 +100,13 @@ void TextBuffer::_reserve(til::size screenBufferSize, const TextAttribute& defau
102
100
// 65535*65535 cells would result in a allocSize of 8GiB.
103
101
// --> Use uint64_t so that we can safely do our calculations even on x86.
104
102
// 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 ;
106
104
const auto allocSize = gsl::narrow<size_t >(rowCount * rowStride);
107
105
108
106
// NOTE: Modifications to this block of code might have to be mirrored over to ResizeTraditional().
109
107
// 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)))
112
110
};
113
111
_bufferEnd = _buffer.get () + allocSize;
114
112
_commitWatermark = _buffer.get ();
@@ -126,7 +124,7 @@ void TextBuffer::_reserve(til::size screenBufferSize, const TextAttribute& defau
126
124
// Declaring this function as noinline allows _getRowByOffsetDirect() to be inlined,
127
125
// which improves overall TextBuffer performance by ~6%. And all it cost is this annotation.
128
126
// 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)
130
128
{
131
129
assert (row >= _commitWatermark);
132
130
@@ -143,29 +141,62 @@ __declspec(noinline) void TextBuffer::_commit(const std::byte* row)
143
141
144
142
// Destructs and MEM_DECOMMITs all previously constructed ROWs.
145
143
// 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
147
145
{
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;
151
176
}
152
177
153
178
// Constructs ROWs between [_commitWatermark,until).
154
- void TextBuffer::_construct (const std::byte * until) noexcept
179
+ void TextBuffer::_construct (const uint8_t * until) noexcept
155
180
{
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)
157
186
{
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);
162
191
}
192
+
193
+ _commitWatermark = wm;
163
194
}
164
195
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
167
198
{
168
- for (auto it = _buffer. get () ; it < _commitWatermark; it += _bufferRowStride)
199
+ for (; it < _commitWatermark; it += _bufferRowStride)
169
200
{
170
201
std::destroy_at (reinterpret_cast <ROW*>(it));
171
202
}
@@ -973,7 +1004,7 @@ til::point TextBuffer::BufferToScreenPosition(const til::point position) const
973
1004
// and the default current color attributes
974
1005
void TextBuffer::Reset () noexcept
975
1006
{
976
- _decommit ();
1007
+ _decommit (0 );
977
1008
_initialAttributes = _currentAttributes;
978
1009
}
979
1010
@@ -988,29 +1019,20 @@ void TextBuffer::ClearScrollback(const til::CoordType newFirstRow, const til::Co
988
1019
return ;
989
1020
}
990
1021
// The new viewport should keep 0 rows? Then just reset everything.
991
- if (rowsToKeep <= 0 )
1022
+ if (rowsToKeep > 0 )
992
1023
{
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);
995
1033
}
996
1034
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);
1014
1036
}
1015
1037
1016
1038
// Routine Description:
@@ -2015,7 +2037,7 @@ std::string TextBuffer::GenHTML(const CopyRequest& req,
2015
2037
const auto [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper (req, iRow, row);
2016
2038
const auto rowBegU16 = gsl::narrow_cast<uint16_t >(rowBeg);
2017
2039
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 ();
2019
2041
2020
2042
auto x = rowBegU16;
2021
2043
for (const auto & [attr, length] : runs)
@@ -2265,7 +2287,7 @@ std::string TextBuffer::GenRTF(const CopyRequest& req,
2265
2287
const auto [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper (req, iRow, row);
2266
2288
const auto rowBegU16 = gsl::narrow_cast<uint16_t >(rowBeg);
2267
2289
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 ();
2269
2291
2270
2292
auto x = rowBegU16;
2271
2293
for (auto & [attr, length] : runs)
@@ -2457,7 +2479,7 @@ void TextBuffer::_SerializeRow(const ROW& row, const til::CoordType startX, cons
2457
2479
2458
2480
const auto startXU16 = gsl::narrow_cast<uint16_t >(startX);
2459
2481
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 ();
2461
2483
2462
2484
const auto beg = runs.begin ();
2463
2485
const auto end = runs.end ();
@@ -3246,7 +3268,6 @@ MarkExtents TextBuffer::_scrollMarkExtentForRow(const til::CoordType rowOffset,
3246
3268
bool startedPrompt = false ;
3247
3269
bool startedCommand = false ;
3248
3270
bool startedOutput = false ;
3249
- MarkKind lastMarkKind = MarkKind::Output;
3250
3271
3251
3272
const auto endThisMark = [&](auto x, auto y) {
3252
3273
if (startedOutput)
@@ -3273,7 +3294,7 @@ MarkExtents TextBuffer::_scrollMarkExtentForRow(const til::CoordType rowOffset,
3273
3294
// Output attribute.
3274
3295
3275
3296
const auto & row = GetRowByOffset (y);
3276
- const auto runs = row.Attributes ().runs ();
3297
+ const auto & runs = row.Attributes ().runs ();
3277
3298
x = 0 ;
3278
3299
for (const auto & [attr, length] : runs)
3279
3300
{
@@ -3316,8 +3337,6 @@ MarkExtents TextBuffer::_scrollMarkExtentForRow(const til::CoordType rowOffset,
3316
3337
3317
3338
endThisMark (lastMarkedText.x , lastMarkedText.y );
3318
3339
}
3319
- // Otherwise, we've changed from any state -> any state, and it doesn't really matter.
3320
- lastMarkKind = markKind;
3321
3340
}
3322
3341
// advance to next run of text
3323
3342
x = nextX;
@@ -3350,7 +3369,7 @@ std::wstring TextBuffer::_commandForRow(const til::CoordType rowOffset,
3350
3369
// Command attributes. Collect up all of those, till we get to the next
3351
3370
// Output attribute.
3352
3371
const auto & row = GetRowByOffset (y);
3353
- const auto runs = row.Attributes ().runs ();
3372
+ const auto & runs = row.Attributes ().runs ();
3354
3373
auto x = 0 ;
3355
3374
for (const auto & [attr, length] : runs)
3356
3375
{
0 commit comments