Skip to content

Commit

Permalink
AtlasEngine: Implement ClearType blending (#12242)
Browse files Browse the repository at this point in the history
This commit extracts DirectWrite related shader code into dwrite.hlsl
and adds support for ClearType blending.

Additionally the following changes are piggybacked into this commit:
* Some incorrect code around fallback glyph sizing was removed as
  this is already accomplished by `CreateTextLayout` internally
* Hot-reload failed to work with dwrite.hlsl as the `pFileName`
  parameter was missing
* Legibility of the dotted underline was improved by increasing
  the line gap from 1:1 to 3:1

Part of #9999.

## PR Checklist
* [x] I work here
* [x] Tests added/passed

## Validation Steps Performed
* Types are clear ✅
  • Loading branch information
lhecker authored Jan 27, 2022
1 parent bcc38d0 commit 4ccfe0b
Show file tree
Hide file tree
Showing 8 changed files with 364 additions and 112 deletions.
29 changes: 8 additions & 21 deletions src/renderer/atlas/AtlasEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,22 +265,10 @@ try
try
{
static const auto compile = [](const std::filesystem::path& path, const char* target) {
const wil::unique_hfile fileHandle{ CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr) };
THROW_LAST_ERROR_IF(!fileHandle);

const auto fileSize = GetFileSize(fileHandle.get(), nullptr);
const wil::unique_handle mappingHandle{ CreateFileMappingW(fileHandle.get(), nullptr, PAGE_READONLY, 0, fileSize, nullptr) };
THROW_LAST_ERROR_IF(!mappingHandle);

const wil::unique_mapview_ptr<void> dataBeg{ MapViewOfFile(mappingHandle.get(), FILE_MAP_READ, 0, 0, 0) };
THROW_LAST_ERROR_IF(!dataBeg);

wil::com_ptr<ID3DBlob> error;
wil::com_ptr<ID3DBlob> blob;
const auto hr = D3DCompile(
/* pSrcData */ dataBeg.get(),
/* SrcDataSize */ fileSize,
/* pFileName */ nullptr,
const auto hr = D3DCompileFromFile(
/* pFileName */ path.c_str(),
/* pDefines */ nullptr,
/* pInclude */ D3D_COMPILE_STANDARD_FILE_INCLUDE,
/* pEntrypoint */ "main",
Expand Down Expand Up @@ -1201,10 +1189,9 @@ void AtlasEngine::_flushBufferLine()
#pragma warning(suppress : 26494) // Variable 'mappedEnd' is uninitialized. Always initialize an object (type.5).
for (u32 idx = 0, mappedEnd; idx < _api.bufferLine.size(); idx = mappedEnd)
{
float scale = 1.0f;

if (_sr.systemFontFallback)
{
float scale = 1.0f;
u32 mappedLength = 0;

if (textFormatAxis)
Expand Down Expand Up @@ -1268,7 +1255,7 @@ void AtlasEngine::_flushBufferLine()
{
if (const auto col2 = _api.bufferLineColumn[pos2]; col1 != col2)
{
_emplaceGlyph(nullptr, scale, pos1, pos2);
_emplaceGlyph(nullptr, pos1, pos2);
pos1 = pos2;
col1 = col2;
}
Expand Down Expand Up @@ -1306,7 +1293,7 @@ void AtlasEngine::_flushBufferLine()
{
for (size_t i = 0; i < complexityLength; ++i)
{
_emplaceGlyph(mappedFontFace.get(), scale, idx + i, idx + i + 1u);
_emplaceGlyph(mappedFontFace.get(), idx + i, idx + i + 1u);
}
}
else
Expand Down Expand Up @@ -1390,7 +1377,7 @@ void AtlasEngine::_flushBufferLine()
{
if (_api.textProps[i].canBreakShapingAfter)
{
_emplaceGlyph(mappedFontFace.get(), scale, a.textPosition + beg, a.textPosition + i + 1);
_emplaceGlyph(mappedFontFace.get(), a.textPosition + beg, a.textPosition + i + 1);
beg = i + 1;
}
}
Expand All @@ -1400,7 +1387,7 @@ void AtlasEngine::_flushBufferLine()
}
}

void AtlasEngine::_emplaceGlyph(IDWriteFontFace* fontFace, float scale, size_t bufferPos1, size_t bufferPos2)
void AtlasEngine::_emplaceGlyph(IDWriteFontFace* fontFace, size_t bufferPos1, size_t bufferPos2)
{
static constexpr auto replacement = L'\uFFFD';

Expand Down Expand Up @@ -1459,7 +1446,7 @@ void AtlasEngine::_emplaceGlyph(IDWriteFontFace* fontFace, float scale, size_t b
coords[i] = _allocateAtlasTile();
}

_r.glyphQueue.push_back(AtlasQueueItem{ &key, &value, scale });
_r.glyphQueue.push_back(AtlasQueueItem{ &key, &value });
_r.maxEncounteredCellCount = std::max(_r.maxEncounteredCellCount, cellCount);
}

Expand Down
11 changes: 5 additions & 6 deletions src/renderer/atlas/AtlasEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,6 @@ namespace Microsoft::Console::Render
Inlined = 0x00000001,

ColoredGlyph = 0x00000002,
ThinFont = 0x00000004,

Cursor = 0x00000008,
Selected = 0x00000010,
Expand Down Expand Up @@ -528,7 +527,6 @@ namespace Microsoft::Console::Render
{
const AtlasKey* key;
const AtlasValue* value;
float scale;
};

struct CachedCursorOptions
Expand Down Expand Up @@ -556,15 +554,16 @@ namespace Microsoft::Console::Render
// This means a structure like {u32; u32; u32; u32x2} would require
// padding so that it is {u32; u32; u32; <4 byte padding>; u32x2}.
alignas(sizeof(f32x4)) f32x4 viewport;
alignas(sizeof(f32x4)) f32x4 gammaRatios;
alignas(sizeof(f32)) f32 grayscaleEnhancedContrast = 0;
alignas(sizeof(f32x4)) f32 gammaRatios[4]{};
alignas(sizeof(f32)) f32 enhancedContrast = 0;
alignas(sizeof(u32)) u32 cellCountX = 0;
alignas(sizeof(u32x2)) u32x2 cellSize;
alignas(sizeof(u32x2)) u32x2 underlinePos;
alignas(sizeof(u32x2)) u32x2 strikethroughPos;
alignas(sizeof(u32)) u32 backgroundColor = 0;
alignas(sizeof(u32)) u32 cursorColor = 0;
alignas(sizeof(u32)) u32 selectionColor = 0;
alignas(sizeof(u32)) u32 useClearType = 0;
#pragma warning(suppress : 4324) // 'ConstBuffer': structure was padded due to alignment specifier
};

Expand Down Expand Up @@ -612,14 +611,13 @@ namespace Microsoft::Console::Render
void _setCellFlags(SMALL_RECT coords, CellFlags mask, CellFlags bits) noexcept;
u16x2 _allocateAtlasTile() noexcept;
void _flushBufferLine();
void _emplaceGlyph(IDWriteFontFace* fontFace, float scale, size_t bufferPos1, size_t bufferPos2);
void _emplaceGlyph(IDWriteFontFace* fontFace, size_t bufferPos1, size_t bufferPos2);

// AtlasEngine.api.cpp
void _resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics = nullptr) const;

// AtlasEngine.r.cpp
void _setShaderResources() const;
static f32x4 _getGammaRatios(float gamma) noexcept;
void _updateConstantBuffer() const noexcept;
void _adjustAtlasSize();
void _reserveScratchpadSize(u16 minWidth);
Expand Down Expand Up @@ -696,6 +694,7 @@ namespace Microsoft::Console::Render
std::vector<AtlasQueueItem> glyphQueue;

f32 gamma = 0;
f32 cleartypeEnhancedContrast = 0;
f32 grayscaleEnhancedContrast = 0;
u32 backgroundColor = 0xff000000;
u32 selectionColor = 0x7fffffff;
Expand Down
52 changes: 9 additions & 43 deletions src/renderer/atlas/AtlasEngine.r.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include "pch.h"
#include "AtlasEngine.h"

#include "dwrite.h"

// #### NOTE ####
// If you see any code in here that contains "_api." you might be seeing a race condition.
// The AtlasEngine::Present() method is called on a background thread without any locks,
Expand Down Expand Up @@ -109,42 +111,17 @@ void AtlasEngine::_setShaderResources() const
_r.deviceContext->PSSetShaderResources(0, gsl::narrow_cast<UINT>(resources.size()), resources.data());
}

AtlasEngine::f32x4 AtlasEngine::_getGammaRatios(float gamma) noexcept
{
static constexpr f32x4 gammaIncorrectTargetRatios[13]{
{ 0.0000f / 4.f, 0.0000f / 4.f, 0.0000f / 4.f, 0.0000f / 4.f }, // gamma = 1.0
{ 0.0166f / 4.f, -0.0807f / 4.f, 0.2227f / 4.f, -0.0751f / 4.f }, // gamma = 1.1
{ 0.0350f / 4.f, -0.1760f / 4.f, 0.4325f / 4.f, -0.1370f / 4.f }, // gamma = 1.2
{ 0.0543f / 4.f, -0.2821f / 4.f, 0.6302f / 4.f, -0.1876f / 4.f }, // gamma = 1.3
{ 0.0739f / 4.f, -0.3963f / 4.f, 0.8167f / 4.f, -0.2287f / 4.f }, // gamma = 1.4
{ 0.0933f / 4.f, -0.5161f / 4.f, 0.9926f / 4.f, -0.2616f / 4.f }, // gamma = 1.5
{ 0.1121f / 4.f, -0.6395f / 4.f, 1.1588f / 4.f, -0.2877f / 4.f }, // gamma = 1.6
{ 0.1300f / 4.f, -0.7649f / 4.f, 1.3159f / 4.f, -0.3080f / 4.f }, // gamma = 1.7
{ 0.1469f / 4.f, -0.8911f / 4.f, 1.4644f / 4.f, -0.3234f / 4.f }, // gamma = 1.8
{ 0.1627f / 4.f, -1.0170f / 4.f, 1.6051f / 4.f, -0.3347f / 4.f }, // gamma = 1.9
{ 0.1773f / 4.f, -1.1420f / 4.f, 1.7385f / 4.f, -0.3426f / 4.f }, // gamma = 2.0
{ 0.1908f / 4.f, -1.2652f / 4.f, 1.8650f / 4.f, -0.3476f / 4.f }, // gamma = 2.1
{ 0.2031f / 4.f, -1.3864f / 4.f, 1.9851f / 4.f, -0.3501f / 4.f }, // gamma = 2.2
};
static constexpr auto norm13 = static_cast<float>(static_cast<double>(0x10000) / (255 * 255) * 4);
static constexpr auto norm24 = static_cast<float>(static_cast<double>(0x100) / (255) * 4);

gamma = clamp(gamma, 1.0f, 2.2f);

const size_t index = gsl::narrow_cast<size_t>(std::round((gamma - 1.0f) / 1.2f * 12.0f));
const auto& ratios = gammaIncorrectTargetRatios[index];
return { norm13 * ratios.x, norm24 * ratios.y, norm13 * ratios.z, norm24 * ratios.w };
}

void AtlasEngine::_updateConstantBuffer() const noexcept
{
const auto useClearType = _api.antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;

ConstBuffer data;
data.viewport.x = 0;
data.viewport.y = 0;
data.viewport.z = static_cast<float>(_r.cellCount.x * _r.cellSize.x);
data.viewport.w = static_cast<float>(_r.cellCount.y * _r.cellSize.y);
data.gammaRatios = _getGammaRatios(_r.gamma);
data.grayscaleEnhancedContrast = _r.grayscaleEnhancedContrast;
DWrite_GetGammaRatios(_r.gamma, data.gammaRatios);
data.enhancedContrast = useClearType ? _r.cleartypeEnhancedContrast : _r.grayscaleEnhancedContrast;
data.cellCountX = _r.cellCount.x;
data.cellSize.x = _r.cellSize.x;
data.cellSize.y = _r.cellSize.y;
Expand All @@ -155,6 +132,7 @@ void AtlasEngine::_updateConstantBuffer() const noexcept
data.backgroundColor = _r.backgroundColor;
data.cursorColor = _r.cursorOptions.cursorColor;
data.selectionColor = _r.selectionColor;
data.useClearType = useClearType;
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6).
_r.deviceContext->UpdateSubresource(_r.constantBuffer.get(), 0, nullptr, &data, 0, 0);
}
Expand Down Expand Up @@ -282,13 +260,8 @@ void AtlasEngine::_reserveScratchpadSize(u16 minWidth)
{
const auto surface = _r.atlasScratchpad.query<IDXGISurface>();

wil::com_ptr<IDWriteRenderingParams1> defaultParams;
THROW_IF_FAILED(_sr.dwriteFactory->CreateRenderingParams(reinterpret_cast<IDWriteRenderingParams**>(defaultParams.addressof())));
wil::com_ptr<IDWriteRenderingParams1> renderingParams;
THROW_IF_FAILED(_sr.dwriteFactory->CreateCustomRenderingParams(1.0f, 0.0f, 0.0f, defaultParams->GetClearTypeLevel(), defaultParams->GetPixelGeometry(), defaultParams->GetRenderingMode(), renderingParams.addressof()));

_r.gamma = defaultParams->GetGamma();
_r.grayscaleEnhancedContrast = defaultParams->GetGrayscaleEnhancedContrast();
DWrite_GetRenderParams(_sr.dwriteFactory.get(), &_r.gamma, &_r.cleartypeEnhancedContrast, &_r.grayscaleEnhancedContrast, renderingParams.addressof());

D2D1_RENDER_TARGET_PROPERTIES props{};
props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
Expand All @@ -300,11 +273,9 @@ void AtlasEngine::_reserveScratchpadSize(u16 minWidth)
// We don't really use D2D for anything except DWrite, but it
// can't hurt to ensure that everything it does is pixel aligned.
_r.d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
_r.d2dRenderTarget->SetTextAntialiasMode(static_cast<D2D1_TEXT_ANTIALIAS_MODE>(_api.antialiasingMode));
// Ensure that D2D uses the exact same gamma as our shader uses.
_r.d2dRenderTarget->SetTextRenderingParams(renderingParams.get());
// We can't set the antialiasingMode here, as D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE
// will force the alpha channel to be 0 for _all_ text.
//_r.d2dRenderTarget->SetTextAntialiasMode(static_cast<D2D1_TEXT_ANTIALIAS_MODE>(_api.antialiasingMode));
}
{
static constexpr D2D1_COLOR_F color{ 1, 1, 1, 1 };
Expand Down Expand Up @@ -344,11 +315,6 @@ void AtlasEngine::_drawGlyph(const AtlasQueueItem& item) const
// See D2DFactory::DrawText
wil::com_ptr<IDWriteTextLayout> textLayout;
THROW_IF_FAILED(_sr.dwriteFactory->CreateTextLayout(&key->chars[0], charsLength, textFormat, cells * _r.cellSizeDIP.x, _r.cellSizeDIP.y, textLayout.addressof()));
if (item.scale != 1.0f)
{
const auto f = textFormat->GetFontSize();
textLayout->SetFontSize(f * item.scale, { 0, charsLength });
}
if (_r.typography)
{
textLayout->SetTypography(_r.typography.get(), { 0, charsLength });
Expand Down
7 changes: 6 additions & 1 deletion src/renderer/atlas/atlas.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@
<ItemGroup>
<ClCompile Include="AtlasEngine.api.cpp" />
<ClCompile Include="AtlasEngine.r.cpp" />
<ClCompile Include="dwrite.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="AtlasEngine.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="dwrite.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="AtlasEngine.h" />
</ItemGroup>
<ItemGroup>
<FxCompile Include="dwrite.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="shader_ps.hlsl">
<ShaderType>Pixel</ShaderType>
<ShaderModel>4.1</ShaderModel>
Expand Down Expand Up @@ -52,4 +57,4 @@
<AdditionalIncludeDirectories>$(OutDir)$(ProjectName)\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
</Project>
</Project>
Loading

0 comments on commit 4ccfe0b

Please sign in to comment.