Skip to content

Commit

Permalink
Make shaded block glyphs look even betterer (#16760)
Browse files Browse the repository at this point in the history
Shaded glyphs (U+2591..3, etc.) all have one problem in common:
The cell size may not be evenly divisible by the pixel/dot size in
the glyph. This either results in blurring, or in moiré-like patterns
at the edges of the cells with its neighbors, because they happen to
start with a pattern that overlaps with the end of the previous cell.

This PR solves the issue by moving the pixel/dot pattern generation
into the shader. That way the pixel/dot location can be made dependent
on the viewport-position of the actual underlying pixels, which avoids
repeating patterns between cells.

The PR contains some additional modifications, all of which either
extend or improve the existing debug facilities in AtlasEngine.
Suppressing whitespaces changes makes the diff way smaller.
  • Loading branch information
lhecker authored Feb 28, 2024
1 parent badc00e commit 94e74d2
Show file tree
Hide file tree
Showing 11 changed files with 270 additions and 187 deletions.
20 changes: 11 additions & 9 deletions src/renderer/atlas/AtlasEngine.api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -688,29 +688,30 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
const auto underlineWidth = std::max(1.0f, std::roundf(underlineThickness));
const auto strikethroughPos = std::roundf(baseline + strikethroughPosition);
const auto strikethroughWidth = std::max(1.0f, std::roundf(strikethroughThickness));
const auto thinLineWidth = std::max(1.0f, std::roundf(underlineThickness / 2.0f));
const auto doubleUnderlineWidth = std::max(1.0f, std::roundf(underlineThickness / 2.0f));
const auto thinLineWidth = std::max(1.0f, std::roundf(std::max(adjustedWidth / 16.0f, adjustedHeight / 32.0f)));

// For double underlines we loosely follow what Word does:
// 1. The lines are half the width of an underline (= thinLineWidth)
// 1. The lines are half the width of an underline (= doubleUnderlineWidth)
// 2. Ideally the bottom line is aligned with the bottom of the underline
// 3. The top underline is vertically in the middle between baseline and ideal bottom underline
// 4. If the top line gets too close to the baseline the underlines are shifted downwards
// 5. The minimum gap between the two lines appears to be similar to Tex (1.2pt)
// (Additional notes below.)

// 2.
auto doubleUnderlinePosBottom = underlinePos + underlineWidth - thinLineWidth;
auto doubleUnderlinePosBottom = underlinePos + underlineWidth - doubleUnderlineWidth;
// 3. Since we don't align the center of our two lines, but rather the top borders
// we need to subtract half a line width from our center point.
auto doubleUnderlinePosTop = std::roundf((baseline + doubleUnderlinePosBottom - thinLineWidth) / 2.0f);
auto doubleUnderlinePosTop = std::roundf((baseline + doubleUnderlinePosBottom - doubleUnderlineWidth) / 2.0f);
// 4.
doubleUnderlinePosTop = std::max(doubleUnderlinePosTop, baseline + thinLineWidth);
doubleUnderlinePosTop = std::max(doubleUnderlinePosTop, baseline + doubleUnderlineWidth);
// 5. The gap is only the distance _between_ the lines, but we need the distance from the
// top border of the top and bottom lines, which includes an additional line width.
const auto doubleUnderlineGap = std::max(1.0f, std::roundf(1.2f / 72.0f * dpi));
doubleUnderlinePosBottom = std::max(doubleUnderlinePosBottom, doubleUnderlinePosTop + doubleUnderlineGap + thinLineWidth);
doubleUnderlinePosBottom = std::max(doubleUnderlinePosBottom, doubleUnderlinePosTop + doubleUnderlineGap + doubleUnderlineWidth);
// Our cells can't overlap each other so we additionally clamp the bottom line to be inside the cell boundaries.
doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, adjustedHeight - thinLineWidth);
doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, adjustedHeight - doubleUnderlineWidth);

const auto cellWidth = gsl::narrow<u16>(lrintf(adjustedWidth));
const auto cellHeight = gsl::narrow<u16>(lrintf(adjustedHeight));
Expand Down Expand Up @@ -749,6 +750,7 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
const auto strikethroughWidthU16 = gsl::narrow_cast<u16>(lrintf(strikethroughWidth));
const auto doubleUnderlinePosTopU16 = gsl::narrow_cast<u16>(lrintf(doubleUnderlinePosTop));
const auto doubleUnderlinePosBottomU16 = gsl::narrow_cast<u16>(lrintf(doubleUnderlinePosBottom));
const auto doubleUnderlineWidthU16 = gsl::narrow_cast<u16>(lrintf(doubleUnderlineWidth));

// NOTE: From this point onward no early returns or throwing code should exist,
// as we might cause _api to be in an inconsistent state otherwise.
Expand All @@ -771,8 +773,8 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo

fontMetrics->underline = { underlinePosU16, underlineWidthU16 };
fontMetrics->strikethrough = { strikethroughPosU16, strikethroughWidthU16 };
fontMetrics->doubleUnderline[0] = { doubleUnderlinePosTopU16, thinLineWidthU16 };
fontMetrics->doubleUnderline[1] = { doubleUnderlinePosBottomU16, thinLineWidthU16 };
fontMetrics->doubleUnderline[0] = { doubleUnderlinePosTopU16, doubleUnderlineWidthU16 };
fontMetrics->doubleUnderline[1] = { doubleUnderlinePosBottomU16, doubleUnderlineWidthU16 };
fontMetrics->overline = { 0, underlineWidthU16 };

fontMetrics->builtinGlyphs = fontInfoDesired.GetEnableBuiltinGlyphs();
Expand Down
12 changes: 12 additions & 0 deletions src/renderer/atlas/Backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,25 @@

namespace Microsoft::Console::Render::Atlas
{
// Don't use this definition in the code elsewhere.
// It only exists to make the definitions below possible.
#ifdef NDEBUG
#define ATLAS_DEBUG__IS_DEBUG 0
#else
#define ATLAS_DEBUG__IS_DEBUG 1
#endif

// If set to 1, this will cause the entire viewport to be invalidated at all times.
// Helpful for benchmarking our text shaping code based on DirectWrite.
#define ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION 0

// Redraw at display refresh rate at all times. This helps with shader debugging.
#define ATLAS_DEBUG_CONTINUOUS_REDRAW 0

// Hot reload the builtin .hlsl files whenever they change on disk.
// Enabled by default in debug builds.
#define ATLAS_DEBUG_SHADER_HOT_RELOAD ATLAS_DEBUG__IS_DEBUG

// Disables the use of DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT.
// This helps with benchmarking the application as it'll run beyond display refresh rate.
#define ATLAS_DEBUG_DISABLE_FRAME_LATENCY_WAITABLE_OBJECT 0
Expand Down
Loading

0 comments on commit 94e74d2

Please sign in to comment.