Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions src/renderer/atlas/AtlasEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ try
auto dst = _p.colorBitmap.data() + dstOffset;
const auto bytes = count * sizeof(u32);

for (size_t i = 0; i < 2; ++i)
for (size_t i = 0; i < 3; ++i)
{
// Avoid bumping the colorBitmapGeneration unless necessary. This approx. further halves
// the (already small) GPU load. This could easily be replaced with some custom SIMD
Expand Down Expand Up @@ -353,7 +353,7 @@ CATCH_RETURN()
return S_OK;
}

void AtlasEngine::_fillColorBitmap(const size_t y, const size_t x1, const size_t x2, const u32 fgColor, const u32 bgColor) noexcept
void AtlasEngine::_fillColorBitmap(const size_t y, const size_t x1, const size_t x2, const u32 fgColor, const u32 bgColor, const u32 ulColor) noexcept
{
const auto bitmap = _p.colorBitmap.begin() + _p.colorBitmapRowStride * y;
const auto shift = gsl::narrow_cast<u8>(_p.rows[y]->lineRendition != LineRendition::SingleWidth);
Expand All @@ -363,10 +363,11 @@ void AtlasEngine::_fillColorBitmap(const size_t y, const size_t x1, const size_t
const u32 colors[] = {
u32ColorPremultiply(bgColor),
fgColor,
ulColor,
};

// This fills the color in the background bitmap, and then in the foreground bitmap.
for (size_t i = 0; i < 2; ++i)
for (size_t i = 0; i < 3; ++i)
{
const auto color = colors[i];

Expand Down Expand Up @@ -428,7 +429,7 @@ try
{
const auto isFinalRow = y == hiEnd.y;
const auto end = isFinalRow ? std::min(hiEnd.x, x2) : x2;
_fillColorBitmap(row, x1, end, fgColor, bgColor);
_fillColorBitmap(row, x1, end, fgColor, bgColor, fgColor);

// Return early if we couldn't paint the whole region (either this was not the last row, or
// it was the last row but the highlight ends outside of our x range.)
Expand All @@ -451,7 +452,7 @@ try
const auto isEndInside = y == hiEnd.y && hiEnd.x <= x2;
if (isStartInside && isEndInside)
{
_fillColorBitmap(row, hiStart.x, static_cast<size_t>(hiEnd.x), fgColor, bgColor);
_fillColorBitmap(row, hiStart.x, static_cast<size_t>(hiEnd.x), fgColor, bgColor, fgColor);
++it;
}
else
Expand All @@ -460,7 +461,7 @@ try
if (isStartInside)
{
const auto start = std::max(x1, hiStart.x);
_fillColorBitmap(y, start, x2, fgColor, bgColor);
_fillColorBitmap(y, start, x2, fgColor, bgColor, fgColor);
}

break;
Expand Down Expand Up @@ -517,7 +518,7 @@ try
}

// Apply the current foreground and background colors to the cells
_fillColorBitmap(y, x, columnEnd, _api.currentForeground, _api.currentBackground);
_fillColorBitmap(y, x, columnEnd, _api.currentForeground, _api.currentBackground, _api.currentUnderlineColor);

// Apply the highlighting colors to the highlighted cells
RETURN_IF_FAILED(_drawHighlighted(_api.searchHighlights, y, x, columnEnd, highlightFg, highlightBg));
Expand All @@ -532,14 +533,14 @@ CATCH_RETURN()
[[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept
try
{
UNREFERENCED_PARAMETER(gridlineColor);
UNREFERENCED_PARAMETER(underlineColor);
const auto shift = gsl::narrow_cast<u8>(_api.lineRendition != LineRendition::SingleWidth);
const auto x = std::max(0, coordTarget.x - (_api.viewportOffset.x >> shift));
const auto y = gsl::narrow_cast<u16>(clamp<til::CoordType>(coordTarget.y, 0, _p.s->viewportCellCount.y - 1));
const auto from = gsl::narrow_cast<u16>(clamp<til::CoordType>(x << shift, 0, _p.s->viewportCellCount.x - 1));
const auto to = gsl::narrow_cast<u16>(clamp<size_t>((x + cchLine) << shift, from, _p.s->viewportCellCount.x));
const auto glColor = gsl::narrow_cast<u32>(gridlineColor) | 0xff000000;
const auto ulColor = gsl::narrow_cast<u32>(underlineColor) | 0xff000000;
_p.rows[y]->gridLineRanges.emplace_back(lines, glColor, ulColor, from, to);
_p.rows[y]->gridLineRanges.emplace_back(lines, from, to);
return S_OK;
}
CATCH_RETURN()
Expand Down Expand Up @@ -650,7 +651,9 @@ CATCH_RETURN()
try
{
auto [fg, bg] = renderSettings.GetAttributeColorsWithAlpha(textAttributes);
auto ul = renderSettings.GetAttributeUnderlineColor(textAttributes);
fg |= 0xff000000;
ul |= 0xff000000;
bg |= _api.backgroundOpaqueMixin;

if (!isSettingDefaultBrushes)
Expand All @@ -666,6 +669,7 @@ try

_api.currentBackground = gsl::narrow_cast<u32>(bg);
_api.currentForeground = gsl::narrow_cast<u32>(fg);
_api.currentUnderlineColor = gsl::narrow_cast<u32>(ul);
_api.attributes = attributes;
}
else
Expand Down Expand Up @@ -791,9 +795,10 @@ void AtlasEngine::_recreateCellCountDependentResources()
// so we round up to multiple of 8 because 8 * sizeof(u32) == 32.
_p.colorBitmapRowStride = alignForward<size_t>(_p.s->viewportCellCount.x, 8);
_p.colorBitmapDepthStride = _p.colorBitmapRowStride * _p.s->viewportCellCount.y;
_p.colorBitmap = Buffer<u32, 32>(_p.colorBitmapDepthStride * 2);
_p.colorBitmap = Buffer<u32, 32>(_p.colorBitmapDepthStride * 3);
_p.backgroundBitmap = { _p.colorBitmap.data(), _p.colorBitmapDepthStride };
_p.foregroundBitmap = { _p.colorBitmap.data() + _p.colorBitmapDepthStride, _p.colorBitmapDepthStride };
_p.underlineBitmap = { _p.colorBitmap.data() + (_p.colorBitmapDepthStride << 1), _p.colorBitmapDepthStride };

memset(_p.colorBitmap.data(), 0, _p.colorBitmap.size() * sizeof(u32));

Expand Down
3 changes: 2 additions & 1 deletion src/renderer/atlas/AtlasEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ namespace Microsoft::Console::Render::Atlas
void _mapCharacters(const wchar_t* text, u32 textLength, u32* mappedLength, IDWriteFontFace2** mappedFontFace) const;
void _mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 length, ShapedRow& row);
ATLAS_ATTR_COLD void _mapReplacementCharacter(u32 from, u32 to, ShapedRow& row);
void _fillColorBitmap(const size_t y, const size_t x1, const size_t x2, const u32 fgColor, const u32 bgColor) noexcept;
void _fillColorBitmap(const size_t y, const size_t x1, const size_t x2, const u32 fgColor, const u32 bgColor, const u32 ulColor) noexcept;
[[nodiscard]] HRESULT _drawHighlighted(std::span<const til::point_span>& highlights, const u16 row, const u16 begX, const u16 endX, const u32 fgColor, const u32 bgColor) noexcept;

// AtlasEngine.api.cpp
Expand Down Expand Up @@ -162,6 +162,7 @@ namespace Microsoft::Console::Render::Atlas
u32 backgroundOpaqueMixin = 0xff000000;
u32 currentBackground = 0;
u32 currentForeground = 0;
u32 currentUnderlineColor = 0;
FontRelevantAttributes attributes = FontRelevantAttributes::None;
u16x2 lastPaintBufferLineCoord{};
// UpdateHyperlinkHoveredId()
Expand Down
83 changes: 56 additions & 27 deletions src/renderer/atlas/BackendD2D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ TIL_FAST_MATH_BEGIN
#pragma warning(disable : 26429) // Symbol 'data' is never tested for nullness, it can be marked as not_null (f.23).
#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
#pragma warning(disable : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe [...].
#pragma warning(disable : 26472) // Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1).
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).

Expand Down Expand Up @@ -619,38 +620,53 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro
const auto cellCenter = row->lineRendition == LineRendition::DoubleHeightTop ? rowBottom : rowTop;
const auto scaleHorizontal = row->lineRendition != LineRendition::SingleWidth ? 0.5f : 1.0f;
const auto scaledCellWidth = cellWidth * scaleHorizontal;
const auto horizontalShift = static_cast<u8>(row->lineRendition != LineRendition::SingleWidth);

const auto appendVerticalLines = [&](const GridLineRange& r, FontDecorationPosition pos) {
// Vertical lines are always gridlines, and gridlines are always rendered in the foreground color
const auto colors = &p.foregroundBitmap[p.colorBitmapRowStride * y];
const auto from = r.from * scaledCellWidth;
const auto to = r.to * scaledCellWidth;
auto x = from + pos.position;
auto c = r.from;

D2D1_POINT_2F point0{ 0, cellCenter };
D2D1_POINT_2F point1{ 0, cellCenter + cellHeight };
const auto brush = _brushWithColor(r.gridlineColor);
const f32 w = pos.height;
const f32 hw = w * 0.5f;

for (; x < to; x += cellWidth)
for (; x < to; x += cellWidth, c += 1 << horizontalShift)
{
const auto brush = _brushWithColor(colors[c]);
const auto centerX = x + hw;
point0.x = centerX;
point1.x = centerX;
_renderTarget->DrawLine(point0, point1, brush, w, nullptr);
}
};
const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle, const u32 color) {
const auto from = r.from * scaledCellWidth;
const auto to = r.to * scaledCellWidth;
const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle, const std::span<const u32>& colorBitmap) {
const auto colors = &colorBitmap[p.colorBitmapRowStride * y];

const auto brush = _brushWithColor(color);
const f32 w = pos.height;
const f32 centerY = cellCenter + pos.position + w * 0.5f;
const D2D1_POINT_2F point0{ from, centerY };
const D2D1_POINT_2F point1{ to, centerY };
_renderTarget->DrawLine(point0, point1, brush, w, strokeStyle);

for (auto from = r.from; from < r.to;)
{
const auto start = colors[from];
u16 run = 1u;
for (; colors[from + run] == start && run < (r.to - from); ++run)
;

const auto brush = _brushWithColor(start);
const D2D1_POINT_2F point0{ from * scaledCellWidth, centerY };
const D2D1_POINT_2F point1{ (from + run) * scaledCellWidth, centerY };
_renderTarget->DrawLine(point0, point1, brush, w, strokeStyle);

from += run;
}
};
const auto appendCurlyLine = [&](const GridLineRange& r) {
const auto appendCurlyLine = [&](const GridLineRange& r, const std::span<const u32>& colorBitmap) {
const auto colors = &colorBitmap[p.colorBitmapRowStride * y];
const auto& font = *p.s->font;

const auto duTop = static_cast<f32>(font.doubleUnderline[0].position);
Expand All @@ -672,11 +688,14 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro
const auto step = roundf(0.5f * height);
const auto period = 4.0f * step;

const auto from = r.from * scaledCellWidth;
const auto to = r.to * scaledCellWidth;
// Calculate the wave over the entire region to be underlined, even if
// it has multiple colors in it. That way, when we clip it to render each
// color, it is seamless.
const auto fullSpanFrom = r.from * scaledCellWidth;
const auto fullSpanTo = r.to * scaledCellWidth;
// Align the start of the wave to the nearest preceding period boundary.
// This ensures that the wave is continuous across color and cell changes.
auto x = floorf(from / period) * period;
auto x = floorf(fullSpanFrom / period) * period;

wil::com_ptr<ID2D1PathGeometry> geometry;
THROW_IF_FAILED(p.d2dFactory->CreatePathGeometry(geometry.addressof()));
Expand All @@ -686,7 +705,7 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro

// This adds complete periods of the wave until we reach the end of the range.
sink->BeginFigure({ x, center }, D2D1_FIGURE_BEGIN_HOLLOW);
for (D2D1_QUADRATIC_BEZIER_SEGMENT segment; x < to;)
for (D2D1_QUADRATIC_BEZIER_SEGMENT segment; x < fullSpanTo;)
{
x += step;
segment.point1.x = x;
Expand All @@ -708,11 +727,21 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro

THROW_IF_FAILED(sink->Close());

const auto brush = _brushWithColor(r.underlineColor);
const D2D1_RECT_F clipRect{ from, rowTop, to, rowBottom };
_renderTarget->PushAxisAlignedClip(&clipRect, D2D1_ANTIALIAS_MODE_ALIASED);
_renderTarget->DrawGeometry(geometry.get(), brush, duHeight, nullptr);
_renderTarget->PopAxisAlignedClip();
for (auto from = r.from; from < r.to;)
{
const auto start = colors[from];
u16 run = 1u;
for (; colors[from + run] == start && run < (r.to - from); ++run)
;

const auto brush = _brushWithColor(start);
const D2D1_RECT_F clipRect{ (from * scaledCellWidth), rowTop, (from + run) * scaledCellWidth, rowBottom };
_renderTarget->PushAxisAlignedClip(&clipRect, D2D1_ANTIALIAS_MODE_ALIASED);
_renderTarget->DrawGeometry(geometry.get(), brush, duHeight, nullptr);
_renderTarget->PopAxisAlignedClip();

from += run;
}
};

for (const auto& r : row->gridLineRanges)
Expand All @@ -730,20 +759,20 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro
}
if (r.lines.test(GridLines::Top))
{
appendHorizontalLine(r, p.s->font->gridTop, nullptr, r.gridlineColor);
appendHorizontalLine(r, p.s->font->gridTop, nullptr, p.foregroundBitmap);
}
if (r.lines.test(GridLines::Bottom))
{
appendHorizontalLine(r, p.s->font->gridBottom, nullptr, r.gridlineColor);
appendHorizontalLine(r, p.s->font->gridBottom, nullptr, p.foregroundBitmap);
}
if (r.lines.test(GridLines::Strikethrough))
{
appendHorizontalLine(r, p.s->font->strikethrough, nullptr, r.gridlineColor);
appendHorizontalLine(r, p.s->font->strikethrough, nullptr, p.foregroundBitmap);
}

if (r.lines.test(GridLines::Underline))
{
appendHorizontalLine(r, p.s->font->underline, nullptr, r.underlineColor);
appendHorizontalLine(r, p.s->font->underline, nullptr, p.underlineBitmap);
}
else if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline))
{
Expand All @@ -753,7 +782,7 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro
static constexpr FLOAT dashes[2]{ 1, 1 };
THROW_IF_FAILED(p.d2dFactory->CreateStrokeStyle(&props, &dashes[0], 2, _dottedStrokeStyle.addressof()));
}
appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get(), r.underlineColor);
appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get(), p.underlineBitmap);
}
else if (r.lines.test(GridLines::DashedUnderline))
{
Expand All @@ -763,17 +792,17 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro
static constexpr FLOAT dashes[2]{ 2, 2 };
THROW_IF_FAILED(p.d2dFactory->CreateStrokeStyle(&props, &dashes[0], 2, _dashedStrokeStyle.addressof()));
}
appendHorizontalLine(r, p.s->font->underline, _dashedStrokeStyle.get(), r.underlineColor);
appendHorizontalLine(r, p.s->font->underline, _dashedStrokeStyle.get(), p.underlineBitmap);
}
else if (r.lines.test(GridLines::CurlyUnderline))
{
appendCurlyLine(r);
appendCurlyLine(r, p.underlineBitmap);
}
else if (r.lines.test(GridLines::DoubleUnderline))
{
for (const auto pos : p.s->font->doubleUnderline)
{
appendHorizontalLine(r, pos, nullptr, r.underlineColor);
appendHorizontalLine(r, pos, nullptr, p.underlineBitmap);
}
}
}
Expand Down
Loading
Loading