AtlasEngine: Fix Present() of out of bounds glyphs (#15403)

`til::rect`'s truthiness check (= rect is valid) returns `false` for
any rects that have negative coordinates. This makes sense for buffer
handling, but breaks AtlasEngine, where glyph coordinates can go out
of bounds and it's entirely valid for that to happen.

Closes #15416

## Validation Steps Performed
* Use MesloLGM NF and print NF glyphs in the first row
* Text rendering, selection, etc., still works 

---------

Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
This commit is contained in:
Leonard Hecker 2023-05-25 19:39:44 +02:00 committed by GitHub
parent c589784b54
commit a9f34e3095
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 28 additions and 21 deletions

View File

@ -415,31 +415,32 @@ void AtlasEngine::_waitUntilCanRender() noexcept
void AtlasEngine::_present()
{
// Present1() dislikes being called with an empty dirty rect.
if (!_p.dirtyRectInPx)
{
return;
}
const til::rect fullRect{ 0, 0, _p.swapChain.targetSize.x, _p.swapChain.targetSize.y };
const RECT fullRect{ 0, 0, _p.swapChain.targetSize.x, _p.swapChain.targetSize.y };
DXGI_PRESENT_PARAMETERS params{};
RECT scrollRect{};
POINT scrollOffset{};
// Since rows might be taller than their cells, they might have drawn outside of the viewport.
auto dirtyRect = _p.dirtyRectInPx;
dirtyRect.left = std::max(dirtyRect.left, 0);
dirtyRect.top = std::max(dirtyRect.top, 0);
dirtyRect.right = std::min(dirtyRect.right, fullRect.right);
dirtyRect.bottom = std::min(dirtyRect.bottom, fullRect.bottom);
RECT dirtyRect{
.left = std::max(_p.dirtyRectInPx.left, 0),
.top = std::max(_p.dirtyRectInPx.top, 0),
.right = std::min<LONG>(_p.dirtyRectInPx.right, fullRect.right),
.bottom = std::min<LONG>(_p.dirtyRectInPx.bottom, fullRect.bottom),
};
// Present1() dislikes being called with an empty dirty rect.
if (dirtyRect.left >= dirtyRect.right || dirtyRect.top >= dirtyRect.bottom)
{
return;
}
if constexpr (!ATLAS_DEBUG_SHOW_DIRTY)
{
if (dirtyRect != fullRect)
if (memcmp(&dirtyRect, &fullRect, sizeof(RECT)) != 0)
{
params.DirtyRectsCount = 1;
params.pDirtyRects = dirtyRect.as_win32_rect();
params.pDirtyRects = &dirtyRect;
if (_p.scrollOffset)
{

View File

@ -627,7 +627,8 @@ void BackendD2D::_debugShowDirty(const RenderingPayload& p)
for (size_t i = 0; i < std::size(_presentRects); ++i)
{
if (const auto& rect = _presentRects[i])
const auto& rect = _presentRects[(_presentRectsPos + i) % std::size(_presentRects)];
if (rect.non_empty())
{
const D2D1_RECT_F rectF{
static_cast<f32>(rect.left),

View File

@ -56,7 +56,7 @@ namespace Microsoft::Console::Render::Atlas
u16x2 _cellCount{};
#if ATLAS_DEBUG_SHOW_DIRTY
til::rect _presentRects[9]{};
i32r _presentRects[9]{};
size_t _presentRectsPos = 0;
#endif

View File

@ -2042,10 +2042,11 @@ void BackendD3D::_debugShowDirty(const RenderingPayload& p)
for (size_t i = 0; i < std::size(_presentRects); ++i)
{
if (const auto& rect = _presentRects[i])
const auto& rect = _presentRects[(_presentRectsPos + i) % std::size(_presentRects)];
if (rect.non_empty())
{
_appendQuad() = {
.shadingType = ShadingType::SolidFill,
.shadingType = ShadingType::Selection,
.position = {
static_cast<i16>(rect.left),
static_cast<i16>(rect.top),

View File

@ -293,7 +293,7 @@ namespace Microsoft::Console::Render::Atlas
bool _requiresContinuousRedraw = false;
#if ATLAS_DEBUG_SHOW_DIRTY
til::rect _presentRects[9]{};
i32r _presentRects[9]{};
size_t _presentRectsPos = 0;
#endif

View File

@ -531,8 +531,12 @@ namespace Microsoft::Console::Render::Atlas
std::array<til::generation_t, 2> colorBitmapGenerations{ 1, 1 };
// In columns/rows.
til::rect cursorRect;
// In pixel.
til::rect dirtyRectInPx;
// The viewport/SwapChain area to be presented. In pixel.
// NOTE:
// This cannot use til::rect, because til::rect generally expects positive coordinates only
// (`operator!()` checks for negative values), whereas this one can go out of bounds,
// whenever glyphs go out of bounds. `AtlasEngine::_present()` will clamp it.
i32r dirtyRectInPx{};
// In rows.
range<u16> invalidatedRows{};
// In pixel.