Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AtlasEngine: Improve dotted, dashed and curly underlines #16719

Merged
merged 2 commits into from
Feb 22, 2024
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
38 changes: 13 additions & 25 deletions src/renderer/atlas/BackendD3D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,29 +310,18 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p)
// baseline of curlyline is at the middle of singly underline. When there's
// limited space to draw a curlyline, we apply a limit on the peak height.
{
// initialize curlyline peak height to a desired value. Clamp it to at
// least 1.
constexpr auto curlyLinePeakHeightEm = 0.075f;
_curlyLinePeakHeight = std::max(1.0f, std::roundf(curlyLinePeakHeightEm * font.fontSize));

// calc the limit we need to apply
const auto strokeHalfWidth = std::floor(font.underline.height / 2.0f);
const auto underlineMidY = font.underline.position + strokeHalfWidth;
const auto maxDrawableCurlyLinePeakHeight = font.cellSize.y - underlineMidY - font.underline.height;

// if the limit is <= 0 (no height at all), stick with the desired height.
// This is how we force a curlyline even when there's no space, though it
// might be clipped at the bottom.
if (maxDrawableCurlyLinePeakHeight > 0.0f)
{
_curlyLinePeakHeight = std::min(_curlyLinePeakHeight, maxDrawableCurlyLinePeakHeight);
}
const auto cellHeight = static_cast<f32>(font.cellSize.y);
const auto strokeWidth = static_cast<f32>(font.thinLineWidth);

// This gives it the same position and height as our double-underline. There's no particular reason for that, apart from
// it being simple to implement and robust against more peculiar fonts with unusually large/small descenders, etc.
// We still need to ensure though that it doesn't clip out of the cellHeight at the bottom.
const auto height = std::max(3.0f, static_cast<f32>(font.doubleUnderline[1].position + font.doubleUnderline[1].height - font.doubleUnderline[0].position));
const auto top = std::min(static_cast<f32>(font.doubleUnderline[0].position), floorf(cellHeight - height - strokeWidth));

const auto curlyUnderlinePos = underlineMidY - _curlyLinePeakHeight - font.underline.height;
const auto curlyUnderlineWidth = 2.0f * (_curlyLinePeakHeight + font.underline.height);
const auto curlyUnderlinePosU16 = gsl::narrow_cast<u16>(lrintf(curlyUnderlinePos));
const auto curlyUnderlineWidthU16 = gsl::narrow_cast<u16>(lrintf(curlyUnderlineWidth));
_curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 };
_curlyLineHalfHeight = height * 0.5f;
_curlyUnderline.position = gsl::narrow_cast<u16>(lrintf(top));
_curlyUnderline.height = gsl::narrow_cast<u16>(lrintf(height));
}

DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put());
Expand Down Expand Up @@ -573,9 +562,8 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const
DWrite_GetGammaRatios(_gamma, data.gammaRatios);
data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast;
data.underlineWidth = p.s->font->underline.height;
data.curlyLineWaveFreq = 2.0f * 3.14f / p.s->font->cellSize.x;
data.curlyLinePeakHeight = _curlyLinePeakHeight;
data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f;
data.thinLineWidth = p.s->font->thinLineWidth;
data.curlyLineHalfHeight = _curlyLineHalfHeight;
p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0);
}
}
Expand Down
7 changes: 3 additions & 4 deletions src/renderer/atlas/BackendD3D.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ namespace Microsoft::Console::Render::Atlas
alignas(sizeof(f32x4)) f32 gammaRatios[4]{};
alignas(sizeof(f32)) f32 enhancedContrast = 0;
alignas(sizeof(f32)) f32 underlineWidth = 0;
alignas(sizeof(f32)) f32 curlyLinePeakHeight = 0;
alignas(sizeof(f32)) f32 curlyLineWaveFreq = 0;
alignas(sizeof(f32)) f32 curlyLineCellOffset = 0;
alignas(sizeof(f32)) f32 thinLineWidth = 0;
alignas(sizeof(f32)) f32 curlyLineHalfHeight = 0;
#pragma warning(suppress : 4324) // 'PSConstBuffer': structure was padded due to alignment specifier
};

Expand Down Expand Up @@ -291,7 +290,7 @@ namespace Microsoft::Console::Render::Atlas
// The bounding rect of _cursorRects in pixels.
til::rect _cursorPosition;

f32 _curlyLinePeakHeight = 0.0f;
f32 _curlyLineHalfHeight = 0.0f;
FontDecorationPosition _curlyUnderline;

bool _requiresContinuousRedraw = false;
Expand Down
25 changes: 9 additions & 16 deletions src/renderer/atlas/shader_ps.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ cbuffer ConstBuffer : register(b0)
float4 gammaRatios;
float enhancedContrast;
float underlineWidth;
float curlyLinePeakHeight;
float curlyLineWaveFreq;
float curlyLineCellOffset;
float thinLineWidth;
float curlyLineHalfHeight;
}

Texture2D<float4> background : register(t0);
Expand Down Expand Up @@ -76,31 +75,25 @@ Output main(PSData data) : SV_Target
}
case SHADING_TYPE_DOTTED_LINE:
{
const bool on = frac(data.position.x / (2.0f * underlineWidth * data.renditionScale.x)) < 0.5f;
const bool on = frac(data.position.x / (3.0f * underlineWidth * data.renditionScale.x)) < (1.0f / 3.0f);
color = on * premultiplyColor(data.color);
weights = color.aaaa;
break;
}
case SHADING_TYPE_DASHED_LINE:
{
const bool on = frac(data.position.x / (backgroundCellSize.x * data.renditionScale.x)) < 0.5f;
const bool on = frac(data.position.x / (6.0f * underlineWidth * data.renditionScale.x)) < (4.0f / 6.0f);
color = on * premultiplyColor(data.color);
weights = color.aaaa;
break;
}
case SHADING_TYPE_CURLY_LINE:
{
uint cellRow = floor(data.position.y / backgroundCellSize.y);
// Use the previous cell when drawing 'Double Height' curly line.
cellRow -= data.renditionScale.y - 1;
const float cellTop = cellRow * backgroundCellSize.y;
const float centerY = cellTop + curlyLineCellOffset * data.renditionScale.y;
const float strokeWidthHalf = underlineWidth * data.renditionScale.y / 2.0f;
const float amp = curlyLinePeakHeight * data.renditionScale.y;
const float freq = curlyLineWaveFreq / data.renditionScale.x;

const float s = sin(data.position.x * freq);
const float d = abs(centerY - (s * amp) - data.position.y);
const float strokeWidthHalf = thinLineWidth * data.renditionScale.y * 0.5f;
const float amp = (curlyLineHalfHeight - strokeWidthHalf) * data.renditionScale.y;
const float freq = data.renditionScale.x / curlyLineHalfHeight * 1.57079632679489661923f;
const float s = sin(data.position.x * freq) * amp;
const float d = abs(curlyLineHalfHeight - data.texcoord.y - s);
const float a = 1 - saturate(d - strokeWidthHalf);
color = a * premultiplyColor(data.color);
weights = color.aaaa;
Expand Down
Loading