Skip to content

Commit ad2358d

Browse files
authored
Fix resize crash in OpenConsole with AtlasEngine (#13015)
`_api.cellCount` caches the `TextBuffer` size in AtlasEngine. Calculating it based on the `_api.sizeInPixel` is incorrect as the `TextBuffer` size doesn't necessarily have to be the size of the window. This can occur when the window is resized, as the main thread is receiving its `WM_SIZE` message and resizing the `TextBuffer` concurrently with the render thread performing a render pass and AtlasEngine checking the `GetClientRect`. In order to inform `AtlasEngine` about the initial buffer size, `Renderer` was modified to also invoke `UpdateViewport()` on the first render cycle. The only other user of `UpdateViewport()` is `VtEngine` which used to call `InvalidateAll()` in these situations. In order to prevent the `InvalidateAll()` call, `VtEngine::UpdateViewport()` was modified to suppress this. ## Validation Steps Performed * Resizing wide characters doesn't crash the terminal anymore ✅ * The additional call to `UpdateViewport()` doesn't break VtEngine ✅
1 parent c9468aa commit ad2358d

File tree

4 files changed

+35
-31
lines changed

4 files changed

+35
-31
lines changed

src/renderer/atlas/AtlasEngine.api.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& out) noexcept
174174

175175
[[nodiscard]] HRESULT AtlasEngine::UpdateViewport(const SMALL_RECT srNewViewport) noexcept
176176
{
177+
_api.cellCount.x = gsl::narrow_cast<u16>(srNewViewport.Right - srNewViewport.Left + 1);
178+
_api.cellCount.y = gsl::narrow_cast<u16>(srNewViewport.Bottom - srNewViewport.Top + 1);
179+
WI_SetFlag(_api.invalidations, ApiInvalidations::Size);
177180
return S_OK;
178181
}
179182

@@ -396,7 +399,6 @@ void AtlasEngine::SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept
396399
if (_api.sizeInPixel != newSize && newSize != u16x2{})
397400
{
398401
_api.sizeInPixel = newSize;
399-
_api.cellCount = _api.sizeInPixel / _api.fontMetrics.cellSize;
400402
WI_SetFlag(_api.invalidations, ApiInvalidations::Size);
401403
}
402404

@@ -536,7 +538,6 @@ void AtlasEngine::_updateFont(const wchar_t* faceName, const FontInfoDesired& fo
536538

537539
if (previousCellSize != _api.fontMetrics.cellSize)
538540
{
539-
_api.cellCount = _api.sizeInPixel / _api.fontMetrics.cellSize;
540541
WI_SetFlag(_api.invalidations, ApiInvalidations::Size);
541542
}
542543
}

src/renderer/base/renderer.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -390,12 +390,13 @@ bool Renderer::_CheckViewportAndScroll()
390390
const auto srOldViewport = _viewport.ToInclusive();
391391
const auto srNewViewport = _pData->GetViewport().ToInclusive();
392392

393-
if (srOldViewport == srNewViewport)
393+
if (!_forceUpdateViewport && srOldViewport == srNewViewport)
394394
{
395395
return false;
396396
}
397397

398398
_viewport = Viewport::FromInclusive(srNewViewport);
399+
_forceUpdateViewport = false;
399400

400401
COORD coordDelta;
401402
coordDelta.X = srOldViewport.Left - srNewViewport.Left;

src/renderer/base/renderer.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ namespace Microsoft::Console::Render
123123
std::vector<SMALL_RECT> _previousSelection;
124124
std::function<void()> _pfnRendererEnteredErrorState;
125125
bool _destructing = false;
126+
bool _forceUpdateViewport = true;
126127

127128
#ifdef UNIT_TESTING
128129
friend class ConptyOutputTests;

src/renderer/vt/state.cpp

+29-28
Original file line numberDiff line numberDiff line change
@@ -246,18 +246,41 @@ CATCH_RETURN();
246246
[[nodiscard]] HRESULT VtEngine::UpdateViewport(const SMALL_RECT srNewViewport) noexcept
247247
{
248248
auto hr = S_OK;
249-
const auto oldView = _lastViewport;
250249
const auto newView = Viewport::FromInclusive(srNewViewport);
250+
const auto oldSize = _lastViewport.Dimensions();
251+
const auto newSize = newView.Dimensions();
251252

252-
_lastViewport = newView;
253-
254-
if ((oldView.Height() != newView.Height()) || (oldView.Width() != newView.Width()))
253+
if (oldSize != newSize)
255254
{
256255
// Don't emit a resize event if we've requested it be suppressed
257256
if (!_suppressResizeRepaint)
258257
{
259-
hr = _ResizeWindow(newView.Width(), newView.Height());
258+
hr = _ResizeWindow(newSize.X, newSize.Y);
259+
}
260+
261+
if (_resizeQuirk)
262+
{
263+
// GH#3490 - When the viewport width changed, don't do anything extra here.
264+
// If the buffer had areas that were invalid due to the resize, then the
265+
// buffer will have triggered it's own invalidations for what it knows is
266+
// invalid. Previously, we'd invalidate everything if the width changed,
267+
// because we couldn't be sure if lines were reflowed.
268+
_invalidMap.resize(til::size{ newSize });
260269
}
270+
else
271+
{
272+
if (SUCCEEDED(hr))
273+
{
274+
_invalidMap.resize(til::size{ newSize }, true); // resize while filling in new space with repaint requests.
275+
276+
// Viewport is smaller now - just update it all.
277+
if (oldSize.Y > newSize.Y || oldSize.X > newSize.X)
278+
{
279+
hr = InvalidateAll();
280+
}
281+
}
282+
}
283+
261284
_resized = true;
262285
}
263286

@@ -269,29 +292,7 @@ CATCH_RETURN();
269292
// If we only clear the flag when the new viewport is different, this can
270293
// lead to the first _actual_ resize being suppressed.
271294
_suppressResizeRepaint = false;
272-
273-
if (_resizeQuirk)
274-
{
275-
// GH#3490 - When the viewport width changed, don't do anything extra here.
276-
// If the buffer had areas that were invalid due to the resize, then the
277-
// buffer will have triggered it's own invalidations for what it knows is
278-
// invalid. Previously, we'd invalidate everything if the width changed,
279-
// because we couldn't be sure if lines were reflowed.
280-
_invalidMap.resize(til::size{ newView.Dimensions() });
281-
}
282-
else
283-
{
284-
if (SUCCEEDED(hr))
285-
{
286-
_invalidMap.resize(til::size{ newView.Dimensions() }, true); // resize while filling in new space with repaint requests.
287-
288-
// Viewport is smaller now - just update it all.
289-
if (oldView.Height() > newView.Height() || oldView.Width() > newView.Width())
290-
{
291-
hr = InvalidateAll();
292-
}
293-
}
294-
}
295+
_lastViewport = newView;
295296

296297
return hr;
297298
}

0 commit comments

Comments
 (0)