Introduce AtlasEngine - A new text rendering prototype (#11623)

This commit introduces "AtlasEngine", a new text renderer based on DxEngine.
But unlike it, DirectWrite and Direct2D are only used to rasterize glyphs.
Blending and placing these glyphs into the target view is being done using
Direct3D and a simple HLSL shader. Since this new renderer more aggressively
assumes that the text is monospace, it simplifies the implementation:
The viewport is divided into cells, and its data is stored as a simple matrix.
Modifications to this matrix involve only simple pointer arithmetic and is easy
to understand. But just like with DxEngine however, DirectWrite
related code remains extremely complex and hard to understand.

Supported features:
* Basic text rendering with grayscale AA
* Foreground and background colors
* Emojis, including zero width joiners
* Underline, dotted underline, strikethrough
* Custom font axes and features
* Selections
* All cursor styles
* Full alpha support for all colors
* _Should_ work with Windows 7

Unsupported features:
* A more conservative GPU memory usage
  The backing texture atlas for glyphs is grow-only and will not shrink.
  After 256MB of memory is used up (~20k glyphs) text output
  will be broken until the renderer is restarted.
* ClearType
* Remaining gridlines (left, right, top, bottom, double underline)
* Hyperlinks don't get full underlines if hovered in WT
* Softfonts
* Non-default line renditions

Performance:
* Runs at up to native display refresh rate
  Unfortunately the frame rate often drops below refresh rate, due us
  fighting over the buffer lock with other parts of the application.
* CPU consumption is up to halved compared to DxEngine
  AtlasEngine is still highly unoptimized. Glyph hashing
  consumes up to a third of the current CPU time.
* No regressions in WT performance
  VT parsing and related buffer management takes up most of the CPU time (~85%),
  due to which the AtlasEngine can't show any further improvements.
* ~2x improvement in raw text throughput in OpenConsole
  compared to DxEngine running at 144 FPS
* ≥10x improvement in colored VT output in WT/OpenConsole
  compared to DxEngine running at 144 FPS
This commit is contained in:
Leonard Hecker 2021-11-13 01:10:06 +01:00 committed by GitHub
parent f6965aeb53
commit 2353349fe5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 3966 additions and 300 deletions

View File

@ -1,12 +1,13 @@
apc
Apc
bsd
calt
ccmp
changelog
cybersecurity
Apc
clickable
clig
copyable
cybersecurity
dalet
dcs
Dcs
@ -34,17 +35,17 @@ It'd
kje
liga
lje
locl
lorem
Llast
Lmid
locl
lorem
Lorigin
maxed
mkmk
mnt
mru
noreply
nje
noreply
ogonek
ok'd
overlined

View File

@ -61,6 +61,7 @@ SUMS$
^src/host/runft\.bat$
^src/host/runut\.bat$
^src/interactivity/onecore/BgfxEngine\.
^src/renderer/atlas/
^src/renderer/wddmcon/WddmConRenderer\.
^src/terminal/adapter/ut_adapter/run\.bat$
^src/terminal/parser/delfuzzpayload\.bat$

View File

@ -400,6 +400,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsTerminal.UIA.Tests",
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "api-ms-win-core-synch-l1-2-0", "src\api-ms-win-core-synch-l1-2-0\api-ms-win-core-synch-l1-2-0.vcxproj", "{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererAtlas", "src\renderer\atlas\atlas.vcxproj", "{8222900C-8B6C-452A-91AC-BE95DB04B95F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
AuditMode|Any CPU = AuditMode|Any CPU
@ -3339,6 +3341,46 @@ Global
{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}.Release|x64.Build.0 = Release|x64
{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}.Release|x86.ActiveCfg = Release|Win32
{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}.Release|x86.Build.0 = Release|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM.ActiveCfg = AuditMode|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x64.ActiveCfg = AuditMode|x64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x64.Build.0 = AuditMode|x64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x86.ActiveCfg = AuditMode|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x86.Build.0 = AuditMode|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|Any CPU.ActiveCfg = Debug|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM.ActiveCfg = Debug|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM64.ActiveCfg = Debug|ARM64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM64.Build.0 = Debug|ARM64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x64.ActiveCfg = Debug|x64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x64.Build.0 = Debug|x64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x86.ActiveCfg = Debug|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x86.Build.0 = Debug|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|DotNet_x64Test.ActiveCfg = Fuzzing|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|DotNet_x86Test.ActiveCfg = Fuzzing|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x64.ActiveCfg = Fuzzing|x64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x64.Build.0 = Fuzzing|x64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x86.Build.0 = Fuzzing|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|Any CPU.ActiveCfg = Release|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM.ActiveCfg = Release|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM64.ActiveCfg = Release|ARM64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM64.Build.0 = Release|ARM64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x64.ActiveCfg = Release|x64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x64.Build.0 = Release|x64
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x86.ActiveCfg = Release|Win32
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -3438,6 +3480,7 @@ Global
{C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
{F19DACD5-0C6E-40DC-B6E4-767A3200542C} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
{8222900C-8B6C-452A-91AC-BE95DB04B95F} = {05500DEF-2294-41E3-AF9A-24E580B82836}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}

View File

@ -29,6 +29,9 @@
<ProjectReference Include="$(SolutionDir)src\types\lib\types.vcxproj">
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)src\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)src\renderer\base\lib\base.vcxproj">
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
</ProjectReference>

View File

@ -3,16 +3,18 @@
#include "pch.h"
#include "ControlCore.h"
#include <argb.h>
#include <DefaultSettings.h>
#include <unicode.hpp>
#include <Utf16Parser.hpp>
#include <Utils.h>
#include <WinUser.h>
#include <LibraryResources.h>
#include "EventArgs.h"
#include "../../types/inc/GlyphWidth.hpp"
#include "../../types/inc/Utils.hpp"
#include "../../buffer/out/search.h"
#include "../../renderer/atlas/AtlasEngine.h"
#include "../../renderer/dx/DxRenderer.hpp"
#include "ControlCore.g.cpp"
@ -202,6 +204,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const double actualHeight,
const double compositionScale)
{
assert(_settings);
_panelWidth = actualWidth;
_panelHeight = actualHeight;
_compositionScale = compositionScale;
@ -222,10 +226,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return false;
}
// Set up the DX Engine
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
_renderer->AddRenderEngine(dxEngine.get());
_renderEngine = std::move(dxEngine);
if (Feature_AtlasEngine::IsEnabled() && _settings.UseAtlasEngine())
{
_renderEngine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>();
}
else
{
_renderEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
}
_renderer->AddRenderEngine(_renderEngine.get());
// Initialize our font with the renderer
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
@ -271,11 +281,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderEngine->SetSoftwareRendering(_settings.SoftwareRendering());
_renderEngine->SetIntenseIsBold(_settings.IntenseIsBold());
_updateAntiAliasingMode(_renderEngine.get());
_updateAntiAliasingMode();
// GH#5098: Inform the engine of the opacity of the default text background.
// GH#11315: Always do this, even if they don't have acrylic on.
_renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.Opacity()));
const auto backgroundIsOpaque = _settings.Opacity() == 1.0 && _settings.BackgroundImage().empty();
_renderEngine->SetDefaultTextBackgroundOpacity(static_cast<float>(backgroundIsOpaque));
THROW_IF_FAILED(_renderEngine->Enable());
@ -616,7 +627,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
_renderEngine->SetSoftwareRendering(_settings.SoftwareRendering());
_updateAntiAliasingMode(_renderEngine.get());
_updateAntiAliasingMode();
// Refresh our font with the renderer
const auto actualFontOldSize = _actualFont.GetSize();
@ -650,22 +661,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
void ControlCore::_updateAntiAliasingMode(::Microsoft::Console::Render::DxEngine* const dxEngine)
void ControlCore::_updateAntiAliasingMode()
{
// Update DxEngine's AntialiasingMode
D2D1_TEXT_ANTIALIAS_MODE mode;
switch (_settings.AntialiasingMode())
{
case TextAntialiasingMode::Cleartype:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
mode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
break;
case TextAntialiasingMode::Aliased:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
mode = D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
break;
case TextAntialiasingMode::Grayscale:
default:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
mode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
break;
}
_renderEngine->SetAntialiasingMode(mode);
}
// Method Description:
@ -1296,7 +1309,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (_renderEngine)
{
auto lock = _terminal->LockForWriting();
_renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(opacity));
const auto backgroundIsOpaque = opacity == 1.0 && _settings.BackgroundImage().empty();
_renderEngine->SetDefaultTextBackgroundOpacity(static_cast<float>(backgroundIsOpaque));
}
}

View File

@ -15,11 +15,8 @@
#pragma once
#include "EventArgs.h"
#include "ControlCore.g.h"
#include "../../renderer/base/Renderer.hpp"
#include "../../renderer/dx/DxRenderer.hpp"
#include "../../renderer/uia/UiaRenderer.hpp"
#include "../../cascadia/TerminalCore/Terminal.hpp"
#include "../buffer/out/search.h"
#include "cppwinrt_utils.h"
@ -188,7 +185,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// As _renderer has a dependency on _renderEngine (through a raw pointer)
// we must ensure the _renderer is deallocated first.
// (C++ class members are destroyed in reverse order.)
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine{ nullptr };
std::unique_ptr<::Microsoft::Console::Render::IRenderEngine> _renderEngine{ nullptr };
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer{ nullptr };
IControlSettings _settings{ nullptr };
@ -248,7 +245,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
#pragma endregion
void _raiseReadOnlyWarning();
void _updateAntiAliasingMode(::Microsoft::Console::Render::DxEngine* const dxEngine);
void _updateAntiAliasingMode();
void _connectionOutputHandler(const hstring& hstr);
void _updateHoveredCell(const std::optional<til::point> terminalPosition);

View File

@ -22,6 +22,7 @@
#include "cppwinrt_utils.h"
#include "ControlCore.h"
#include "../../renderer/uia/UiaRenderer.hpp"
namespace ControlUnitTests
{

View File

@ -31,7 +31,7 @@ namespace Microsoft.Terminal.Control
Boolean UseAcrylic;
ScrollbarState ScrollState;
Boolean UseAtlasEngine;
String FontFace;
Int32 FontSize;
Windows.UI.Text.FontWeight FontWeight;

View File

@ -3,17 +3,16 @@
#include "pch.h"
#include "TermControl.h"
#include <argb.h>
#include <DefaultSettings.h>
#include <unicode.hpp>
#include <Utf16Parser.hpp>
#include <Utils.h>
#include <LibraryResources.h>
#include "TermControlAutomationPeer.h"
#include "../../types/inc/GlyphWidth.hpp"
#include "../../types/inc/Utils.hpp"
#include "../../renderer/atlas/AtlasEngine.h"
#include "TermControl.g.cpp"
#include "TermControlAutomationPeer.h"
using namespace ::Microsoft::Console::Types;
using namespace ::Microsoft::Console::VirtualTerminal;
@ -1828,13 +1827,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const winrt::Windows::Foundation::Size initialSize{ cols, rows };
return GetProposedDimensions(initialSize,
settings.FontSize(),
settings.FontWeight(),
settings.FontFace(),
settings.ScrollState(),
settings.Padding(),
dpi);
return GetProposedDimensions(settings, dpi, initialSize);
}
// Function Description:
@ -1855,16 +1848,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// caller knows what monitor the control is about to appear on.
// Return Value:
// - a size containing the requested dimensions in pixels.
winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars,
const int32_t& fontHeight,
const winrt::Windows::UI::Text::FontWeight& fontWeight,
const winrt::hstring& fontFace,
const ScrollbarState& scrollState,
const winrt::hstring& padding,
const uint32_t dpi)
winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi, const winrt::Windows::Foundation::Size& initialSizeInChars)
{
const auto cols = ::base::saturated_cast<int>(initialSizeInChars.Width);
const auto rows = ::base::saturated_cast<int>(initialSizeInChars.Height);
const auto fontSize = settings.FontSize();
const auto fontWeight = settings.FontWeight();
const auto fontFace = settings.FontFace();
const auto scrollState = settings.ScrollState();
const auto padding = settings.Padding();
// Initialize our font information.
// The font width doesn't terribly matter, we'll only be using the
@ -1873,28 +1865,39 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// The family is only used to determine if the font is truetype or
// not, but DX doesn't use that info at all.
// The Codepage is additionally not actually used by the DX engine at all.
FontInfo actualFont = { fontFace, 0, fontWeight.Weight, { 0, gsl::narrow_cast<short>(fontHeight) }, CP_UTF8, false };
FontInfo actualFont = { fontFace, 0, fontWeight.Weight, { 0, gsl::narrow_cast<short>(fontSize) }, CP_UTF8, false };
FontInfoDesired desiredFont = { actualFont };
// Create a DX engine and initialize it with our font and DPI. We'll
// then use it to measure how much space the requested rows and columns
// will take up.
// TODO: MSFT:21254947 - use a static function to do this instead of
// instantiating a DxEngine
// instantiating a DxEngine/AtlasEngine.
// GH#10211 - UNDER NO CIRCUMSTANCE should this fail. If it does, the
// whole app will crash instantaneously on launch, which is no good.
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
LOG_IF_FAILED(dxEngine->UpdateDpi(dpi));
LOG_IF_FAILED(dxEngine->UpdateFont(desiredFont, actualFont));
double scale;
if (Feature_AtlasEngine::IsEnabled() && settings.UseAtlasEngine())
{
auto engine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>();
LOG_IF_FAILED(engine->UpdateDpi(dpi));
LOG_IF_FAILED(engine->UpdateFont(desiredFont, actualFont));
scale = engine->GetScaling();
}
else
{
auto engine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
LOG_IF_FAILED(engine->UpdateDpi(dpi));
LOG_IF_FAILED(engine->UpdateFont(desiredFont, actualFont));
scale = engine->GetScaling();
}
const auto scale = dxEngine->GetScaling();
const auto fontSize = actualFont.GetSize();
const auto actualFontSize = actualFont.GetSize();
// UWP XAML scrollbars aren't guaranteed to be the same size as the
// ComCtl scrollbars, but it's certainly close enough.
const auto scrollbarSize = GetSystemMetricsForDpi(SM_CXVSCROLL, dpi);
double width = cols * fontSize.X;
double width = cols * actualFontSize.X;
// Reserve additional space if scrollbar is intended to be visible
if (scrollState == ScrollbarState::Visible)
@ -1902,7 +1905,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
width += scrollbarSize;
}
double height = rows * fontSize.Y;
double height = rows * actualFontSize.Y;
const auto thickness = ParseThicknessFromPadding(padding);
// GH#2061 - make sure to account for the size the padding _will be_ scaled to
width += scale * (thickness.Left + thickness.Right);
@ -1962,13 +1965,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const winrt::Windows::Foundation::Size minSize{ 1, 1 };
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
const auto dpi = ::base::saturated_cast<uint32_t>(USER_DEFAULT_SCREEN_DPI * scaleFactor);
return GetProposedDimensions(minSize,
_settings.FontSize(),
_settings.FontWeight(),
_settings.FontFace(),
_settings.ScrollState(),
_settings.Padding(),
dpi);
return GetProposedDimensions(_settings, dpi, minSize);
}
}

View File

@ -92,13 +92,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void Settings(IControlSettings newSettings);
static Windows::Foundation::Size GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi);
static Windows::Foundation::Size GetProposedDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars,
const int32_t& fontSize,
const winrt::Windows::UI::Text::FontWeight& fontWeight,
const winrt::hstring& fontFace,
const ScrollbarState& scrollState,
const winrt::hstring& padding,
const uint32_t dpi);
static Windows::Foundation::Size GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi, const winrt::Windows::Foundation::Size& initialSizeInChars);
void BellLightOn();

View File

@ -147,6 +147,7 @@
<ProjectReference Include="..\..\types\lib\types.vcxproj" />
<ProjectReference Include="..\..\buffer\out\lib\bufferout.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\renderer\base\lib\base.vcxproj" />
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj" />
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj" />
<ProjectReference Include="..\..\renderer\uia\lib\uia.vcxproj" />
<ProjectReference Include="..\..\terminal\parser\lib\parser.vcxproj" />

View File

@ -37,6 +37,9 @@
<ProjectReference Include="$(OpenConsoleDir)src\terminal\parser\lib\parser.vcxproj">
<Project>{3ae13314-1939-4dfa-9c14-38ca0834050c}</Project>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>

View File

@ -246,13 +246,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return _profile.HasUnfocusedAppearance();
}
bool ProfileViewModel::EditableUnfocusedAppearance()
bool ProfileViewModel::EditableUnfocusedAppearance() const noexcept
{
if constexpr (Feature_EditableUnfocusedAppearance::IsEnabled())
{
return true;
}
return false;
return Feature_EditableUnfocusedAppearance::IsEnabled();
}
bool ProfileViewModel::ShowUnfocusedAppearance()
@ -286,6 +282,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return _unfocusedAppearanceViewModel;
}
bool ProfileViewModel::AtlasEngineAvailable() const noexcept
{
return Feature_AtlasEngine::IsEnabled();
}
bool ProfileViewModel::UseParentProcessDirectory()
{
return StartingDirectory().empty();

View File

@ -61,12 +61,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Editor::AppearanceViewModel DefaultAppearance();
Editor::AppearanceViewModel UnfocusedAppearance();
bool HasUnfocusedAppearance();
bool EditableUnfocusedAppearance();
bool EditableUnfocusedAppearance() const noexcept;
bool ShowUnfocusedAppearance();
void CreateUnfocusedAppearance(const Windows::Foundation::Collections::IMapView<hstring, Model::ColorScheme>& schemes,
const IHostedInWindow& windowRoot);
void DeleteUnfocusedAppearance();
bool AtlasEngineAvailable() const noexcept;
WINRT_PROPERTY(bool, IsBaseLayer, false);
@ -95,6 +95,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
OBSERVABLE_PROJECTED_SETTING(_profile, SnapOnInput);
OBSERVABLE_PROJECTED_SETTING(_profile, AltGrAliasing);
OBSERVABLE_PROJECTED_SETTING(_profile, BellStyle);
OBSERVABLE_PROJECTED_SETTING(_profile, UseAtlasEngine);
private:
Model::Profile _profile;

View File

@ -32,6 +32,7 @@ namespace Microsoft.Terminal.Settings.Editor
Boolean EditableUnfocusedAppearance { get; };
Boolean ShowUnfocusedAppearance { get; };
AppearanceViewModel UnfocusedAppearance { get; };
Boolean AtlasEngineAvailable { get; };
void CreateUnfocusedAppearance(Windows.Foundation.Collections.IMapView<String, Microsoft.Terminal.Settings.Model.ColorScheme> Schemes, IHostedInWindow WindowRoot);
void DeleteUnfocusedAppearance();
@ -61,6 +62,7 @@ namespace Microsoft.Terminal.Settings.Editor
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, SnapOnInput);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AltGrAliasing);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Settings.Model.BellStyle, BellStyle);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, UseAtlasEngine);
}
runtimeclass DeleteProfileEventArgs

View File

@ -319,7 +319,7 @@
</StackPanel>
<StackPanel>
<StackPanel Orientation="Horizontal"
Visibility="{x:Bind State.Profile.EditableUnfocusedAppearance, Mode=OneWay}">
Visibility="{x:Bind State.Profile.EditableUnfocusedAppearance}">
<TextBlock x:Uid="Profile_UnfocusedAppearanceTextBlock"
Style="{StaticResource TitleTextBlockStyle}" />
<Button x:Uid="Profile_CreateUnfocusedAppearanceButton"
@ -477,6 +477,15 @@
IsChecked="{x:Bind IsBellStyleFlagSet(4), BindBack=SetBellStyleTaskbar, Mode=TwoWay}" />
</StackPanel>
</local:SettingContainer>
<!-- AtlasEngine -->
<local:SettingContainer x:Uid="Profile_UseAtlasEngine"
ClearSettingValue="{x:Bind State.Profile.ClearUseAtlasEngine}"
HasSettingValue="{x:Bind State.Profile.HasUseAtlasEngine, Mode=OneWay}"
SettingOverrideSource="{x:Bind State.Profile.UseAtlasEngineOverrideSource, Mode=OneWay}"
Visibility="{x:Bind State.Profile.AtlasEngineAvailable}">
<ToggleSwitch IsOn="{x:Bind State.Profile.UseAtlasEngine, Mode=TwoWay}" />
</local:SettingContainer>
</StackPanel>
</ScrollViewer>
</PivotItem>

View File

@ -927,6 +927,10 @@
<value>Controls what happens when the application emits a BEL character.</value>
<comment>A description for what the "bell style" setting does. Presented near "Profile_BellStyle".{Locked="BEL"}</comment>
</data>
<data name="Profile_UseAtlasEngine.Header" xml:space="preserve">
<value>Enable experimental text rendering engine</value>
<comment>An option to enable an experimental text rendering engine</comment>
</data>
<data name="Profile_BellStyleAudible.Content" xml:space="preserve">
<value>Audible</value>
<comment>An option to choose from for the "bell style" setting. When selected, an audible cue is used to notify the user.</comment>

View File

@ -73,7 +73,8 @@ Author(s):
X(hstring, Icon, "icon", L"\uE756") \
X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Graceful) \
X(hstring, TabTitle, "tabTitle") \
X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible)
X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \
X(bool, UseAtlasEngine, "experimental.useAtlasEngine", false)
#define MTSM_FONT_SETTINGS(X) \
X(hstring, FontFace, "face", DEFAULT_FONT_FACE) \

View File

@ -79,5 +79,6 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_PROFILE_SETTING(Boolean, SnapOnInput);
INHERITABLE_PROFILE_SETTING(Boolean, AltGrAliasing);
INHERITABLE_PROFILE_SETTING(BellStyle, BellStyle);
INHERITABLE_PROFILE_SETTING(Boolean, UseAtlasEngine);
}
}

View File

@ -298,6 +298,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
_SuppressApplicationTitle = profile.SuppressApplicationTitle();
}
_UseAtlasEngine = profile.UseAtlasEngine();
_ScrollState = profile.ScrollState();
_AntialiasingMode = profile.AntialiasingMode();

View File

@ -148,6 +148,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
INHERITABLE_SETTING(Model::TerminalSettings, hstring, EnvironmentVariables);
INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::ScrollbarState, ScrollState, Microsoft::Terminal::Control::ScrollbarState::Visible);
INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAtlasEngine, false);
INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale);

View File

@ -76,6 +76,7 @@ namespace ControlUnitTests
WINRT_PROPERTY(winrt::hstring, EnvironmentVariables);
WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::ScrollbarState, ScrollState, winrt::Microsoft::Terminal::Control::ScrollbarState::Visible);
WINRT_PROPERTY(bool, UseAtlasEngine, false);
WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale);

View File

@ -79,4 +79,14 @@
<!-- This feature will not ship to Stable until it is complete. -->
<alwaysDisabledReleaseTokens />
</feature>
<feature>
<name>Feature_AtlasEngine</name>
<description>If enabled, AtlasEngine and the experimental.useAtlasEngine setting are compiled into the project</description>
<stage>AlwaysEnabled</stage>
<alwaysDisabledBrandingTokens>
<brandingToken>Release</brandingToken>
<brandingToken>WindowsInbox</brandingToken>
</alwaysDisabledBrandingTokens>
</feature>
</featureStaging>

View File

@ -43,6 +43,9 @@
<ProjectReference Include="..\..\renderer\base\lib\base.vcxproj">
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>

View File

@ -38,6 +38,9 @@
<ProjectReference Include="..\..\renderer\base\lib\base.vcxproj">
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>
@ -71,6 +74,7 @@
<AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>winmm.lib;%(AdditionalDependencies)</AdditionalDependencies>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>

View File

@ -2320,9 +2320,6 @@ void ReadStringWithReadConsoleInputAHelper(HANDLE hIn, PCSTR pszExpectedText, si
while (cchRead < cchExpectedText)
{
// expected read is either the size of the buffer or the number of characters remaining, whichever is smaller.
DWORD const dwReadExpected = (DWORD)std::min(cbBuffer, cchExpectedText - cchRead);
DWORD dwRead;
if (!VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInputA(hIn, irRead, (DWORD)cbBuffer, &dwRead), L"Attempt to read input into buffer."))
{

View File

@ -22,10 +22,7 @@ Revision History:
#include "ConsoleArguments.hpp"
#include "ApiRoutines.h"
#include "../renderer/inc/IRenderData.hpp"
#include "../renderer/inc/IRenderEngine.hpp"
#include "../renderer/inc/IRenderer.hpp"
#include "../renderer/inc/IFontDefaultList.hpp"
#include "../renderer/base/Renderer.hpp"
#include "../server/DeviceComm.h"
#include "../server/ConDrvDeviceComm.h"
@ -62,7 +59,7 @@ public:
std::vector<wchar_t> WordDelimiters;
Microsoft::Console::Render::IRenderer* pRender;
Microsoft::Console::Render::Renderer* pRender;
Microsoft::Console::Render::IFontDefaultList* pFontDefaultList;

View File

@ -58,7 +58,7 @@ Settings::Settings() :
_fInterceptCopyPaste(0),
_DefaultForeground(INVALID_COLOR),
_DefaultBackground(INVALID_COLOR),
_fUseDx(false),
_fUseDx(UseDx::Disabled),
_fCopyColor(false)
{
_dwScreenBufferSize.X = 80;
@ -820,12 +820,9 @@ void Settings::SetTerminalScrolling(const bool terminalScrollingEnabled) noexcep
_TerminalScrolling = terminalScrollingEnabled;
}
// Routine Description:
// - Determines whether our primary renderer should be DirectX or GDI.
// - This is based on user preference and velocity hold back state.
// Return Value:
// - True means use DirectX renderer. False means use GDI renderer.
bool Settings::GetUseDx() const noexcept
// Determines whether our primary renderer should be DirectX or GDI.
// This is based on user preference and velocity hold back state.
UseDx Settings::GetUseDx() const noexcept
{
return _fUseDx;
}

View File

@ -26,6 +26,13 @@ constexpr unsigned short MIN_WINDOW_OPACITY = 0x4D; // 0x4D is approximately 30%
#include "ConsoleArguments.hpp"
#include "../inc/conattrs.hpp"
enum class UseDx : DWORD
{
Disabled = 0,
DxEngine,
AtlasEngine,
};
class Settings
{
public:
@ -188,7 +195,7 @@ public:
bool IsTerminalScrolling() const noexcept;
void SetTerminalScrolling(const bool terminalScrollingEnabled) noexcept;
bool GetUseDx() const noexcept;
UseDx GetUseDx() const noexcept;
bool GetCopyColor() const noexcept;
private:
@ -232,7 +239,7 @@ private:
bool _fAutoReturnOnNewline;
bool _fRenderGridWorldwide;
bool _fScreenReversed;
bool _fUseDx;
UseDx _fUseDx;
bool _fCopyColor;
std::array<COLORREF, XTERM_COLOR_TABLE_SIZE> _colorTable;

View File

@ -52,6 +52,9 @@
<ProjectReference Include="..\..\internal\internal.vcxproj">
<Project>{ef3e32a7-5ff6-42b4-b6e2-96cd7d033f00}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>

View File

@ -6,7 +6,7 @@
<RootNamespace>win32</RootNamespace>
<ProjectName>InteractivityWin32</ProjectName>
<TargetName>ConInteractivityWin32Lib</TargetName>
<ConfigurationType>StaticLibrary</ConfigurationType>
<ConfigurationType>StaticLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemDefinitionGroup>
@ -61,6 +61,9 @@
<ClInclude Include="..\windowUiaProvider.hpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>

View File

@ -22,6 +22,9 @@
<ProjectReference Include="..\..\..\internal\internal.vcxproj">
<Project>{ef3e32a7-5ff6-42b4-b6e2-96cd7d033f00}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>
@ -76,4 +79,4 @@
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
<Import Project="$(SolutionDir)src\common.build.tests.props" />
</Project>
</Project>

View File

@ -735,7 +735,6 @@ class UiaTextRangeTests
TEST_METHOD(CanMoveByCharacter)
{
const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().RightInclusive();
const SHORT bottomRow = gsl::narrow<SHORT>(_pTextBuffer->TotalRowCount() - 1);
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
// instead of parsing through thousands of empty lines of text.
@ -824,7 +823,6 @@ class UiaTextRangeTests
TEST_METHOD(CanMoveByLine)
{
const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1;
const SHORT bottomRow = gsl::narrow<SHORT>(_pTextBuffer->TotalRowCount() - 1);
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
// instead of parsing through thousands of empty lines of text.
@ -913,7 +911,6 @@ class UiaTextRangeTests
TEST_METHOD(CanMoveEndpointByUnitCharacter)
{
const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1;
const SHORT bottomRow = static_cast<SHORT>(_pTextBuffer->TotalRowCount() - 1);
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
// instead of parsing through thousands of empty lines of text.
@ -1197,7 +1194,6 @@ class UiaTextRangeTests
TEST_METHOD(CanMoveEndpointByUnitDocument)
{
const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1;
const SHORT bottomRow = gsl::narrow<SHORT>(_pTextBuffer->TotalRowCount() - 1);
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster

View File

@ -9,17 +9,14 @@
#include "window.hpp"
#include "windowio.hpp"
#include "windowdpiapi.hpp"
#include "windowmetrics.hpp"
#include "WindowMetrics.hpp"
#include "../../inc/conint.h"
#include "../../host/globals.h"
#include "../../host/dbcs.h"
#include "../../host/getset.h"
#include "../../host/misc.h"
#include "../../host/_output.h"
#include "../../host/output.h"
#include "../../host/renderData.hpp"
#include "../../host/scrolling.hpp"
#include "../../host/srvinit.h"
#include "../../host/stream.h"
@ -29,6 +26,9 @@
#include "../../renderer/base/renderer.hpp"
#include "../../renderer/gdi/gdirenderer.hpp"
#if TIL_FEATURE_ATLASENGINE_ENABLED
#include "../../renderer/atlas/AtlasEngine.h"
#endif
#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED
#include "../../renderer/dx/DxRenderer.hpp"
#endif
@ -209,16 +209,20 @@ void Window::_UpdateSystemMetrics() const
// Ensure we have appropriate system metrics before we start constructing the window.
_UpdateSystemMetrics();
const bool useDx = pSettings->GetUseDx();
const auto useDx = pSettings->GetUseDx();
GdiEngine* pGdiEngine = nullptr;
#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED
[[maybe_unused]] DxEngine* pDxEngine = nullptr;
DxEngine* pDxEngine = nullptr;
#endif
#if TIL_FEATURE_ATLASENGINE_ENABLED
AtlasEngine* pAtlasEngine = nullptr;
#endif
try
{
#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED
if (useDx)
switch (useDx)
{
#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED
case UseDx::DxEngine:
pDxEngine = new DxEngine();
// TODO: MSFT:21255595 make this less gross
// Manually set the Dx Engine to Hwnd mode. When we're trying to
@ -227,12 +231,18 @@ void Window::_UpdateSystemMetrics() const
// math in the hwnd mode, not the Composition mode.
THROW_IF_FAILED(pDxEngine->SetHwnd(nullptr));
g.pRender->AddRenderEngine(pDxEngine);
}
else
break;
#endif
{
#if TIL_FEATURE_ATLASENGINE_ENABLED
case UseDx::AtlasEngine:
pAtlasEngine = new AtlasEngine();
g.pRender->AddRenderEngine(pAtlasEngine);
break;
#endif
default:
pGdiEngine = new GdiEngine();
g.pRender->AddRenderEngine(pGdiEngine);
break;
}
}
catch (...)
@ -324,7 +334,7 @@ void Window::_UpdateSystemMetrics() const
_hWnd = hWnd;
#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED
if (useDx)
if (pDxEngine)
{
status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pDxEngine->SetHwnd(hWnd))));
@ -334,6 +344,13 @@ void Window::_UpdateSystemMetrics() const
}
}
else
#endif
#if TIL_FEATURE_ATLASENGINE_ENABLED
if (pAtlasEngine)
{
status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pAtlasEngine->SetHwnd(hWnd))));
}
else
#endif
{
status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pGdiEngine->SetHwnd(hWnd))));

View File

@ -63,7 +63,7 @@ const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMa
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_DEFAULTFOREGROUND, SET_FIELD_AND_SIZE(_DefaultForeground) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_DEFAULTBACKGROUND, SET_FIELD_AND_SIZE(_DefaultBackground) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_TERMINALSCROLLING, SET_FIELD_AND_SIZE(_TerminalScrolling) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) }
};
@ -251,7 +251,8 @@ NTSTATUS RegistrySerialization::s_OpenKey(_In_opt_ HKEY const hKey, _In_ PCWSTR
[[nodiscard]]
NTSTATUS RegistrySerialization::s_DeleteValue(const HKEY hKey, _In_ PCWSTR const pwszValueName)
{
return NTSTATUS_FROM_WIN32(RegDeleteKeyValueW(hKey, nullptr, pwszValueName));
const auto result = RegDeleteKeyValueW(hKey, nullptr, pwszValueName);
return result == ERROR_FILE_NOT_FOUND ? S_OK : NTSTATUS_FROM_WIN32(result);
}
// Routine Description:

View File

@ -0,0 +1,605 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "AtlasEngine.h"
// #### NOTE ####
// If you see any code in here that contains "_r." you might be seeing a race condition.
// The AtlasEngine::Present() method is called on a background thread without any locks,
// while any of the API methods (like AtlasEngine::Invalidate) might be called concurrently.
// The usage of _r fields is unsafe as those are accessed and written to by the Present() method.
#pragma warning(disable : 4100) // '...': unreferenced formal parameter
// Disable a bunch of warnings which get in the way of writing performant code.
#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 : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
using namespace Microsoft::Console::Render;
// Like gsl::narrow but returns a HRESULT.
#pragma warning(push)
#pragma warning(disable : 26472) // Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1).
template<typename T, typename U>
constexpr HRESULT api_narrow(U val, T& out) noexcept
{
out = static_cast<T>(val);
return static_cast<U>(out) != val || (std::is_signed_v<T> != std::is_signed_v<U> && out < T{} != val < U{}) ? HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW) : S_OK;
}
#pragma warning(pop)
template<typename T, typename U>
constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& out) noexcept
{
return api_narrow(x, out.x) | api_narrow(y, out.y);
}
#pragma region IRenderEngine
[[nodiscard]] HRESULT AtlasEngine::Invalidate(const SMALL_RECT* const psrRegion) noexcept
{
//assert(psrRegion->Top < psrRegion->Bottom && psrRegion->Top >= 0 && psrRegion->Bottom <= _api.cellCount.y);
// BeginPaint() protects against invalid out of bounds numbers.
_api.invalidatedRows.x = std::min(_api.invalidatedRows.x, gsl::narrow_cast<u16>(psrRegion->Top));
_api.invalidatedRows.y = std::max(_api.invalidatedRows.y, gsl::narrow_cast<u16>(psrRegion->Bottom));
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept
{
//assert(psrRegion->Left <= psrRegion->Right && psrRegion->Left >= 0 && psrRegion->Right <= _api.cellCount.x);
//assert(psrRegion->Top <= psrRegion->Bottom && psrRegion->Top >= 0 && psrRegion->Bottom <= _api.cellCount.y);
const auto left = gsl::narrow_cast<u16>(psrRegion->Left);
const auto top = gsl::narrow_cast<u16>(psrRegion->Top);
const auto right = gsl::narrow_cast<u16>(psrRegion->Right);
const auto bottom = gsl::narrow_cast<u16>(psrRegion->Bottom);
// BeginPaint() protects against invalid out of bounds numbers.
_api.invalidatedCursorArea.left = std::min(_api.invalidatedCursorArea.left, left);
_api.invalidatedCursorArea.top = std::min(_api.invalidatedCursorArea.top, top);
_api.invalidatedCursorArea.right = std::max(_api.invalidatedCursorArea.right, right);
_api.invalidatedCursorArea.bottom = std::max(_api.invalidatedCursorArea.bottom, bottom);
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::InvalidateSystem(const RECT* const prcDirtyClient) noexcept
{
const auto top = prcDirtyClient->top / _api.fontMetrics.cellSize.y;
const auto bottom = prcDirtyClient->bottom / _api.fontMetrics.cellSize.y;
// BeginPaint() protects against invalid out of bounds numbers.
SMALL_RECT rect;
rect.Top = gsl::narrow_cast<SHORT>(top);
rect.Bottom = gsl::narrow_cast<SHORT>(bottom);
return Invalidate(&rect);
}
[[nodiscard]] HRESULT AtlasEngine::InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept
{
for (const auto& rect : rectangles)
{
// BeginPaint() protects against invalid out of bounds numbers.
// TODO: rect can contain invalid out of bounds coordinates when the selection is being
// dragged outside of the viewport (and the window begins scrolling automatically).
_api.invalidatedRows.x = gsl::narrow_cast<u16>(std::min<int>(_api.invalidatedRows.x, std::max<int>(0, rect.Top)));
_api.invalidatedRows.y = gsl::narrow_cast<u16>(std::max<int>(_api.invalidatedRows.y, std::max<int>(0, rect.Bottom)));
}
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::InvalidateScroll(const COORD* const pcoordDelta) noexcept
{
const auto delta = pcoordDelta->Y;
if (delta == 0)
{
return S_OK;
}
_api.scrollOffset = gsl::narrow_cast<i16>(clamp<int>(_api.scrollOffset + delta, i16min, i16max));
// InvalidateScroll() is a "synchronous" API. Any Invalidate()s after
// a InvalidateScroll() refer to the new viewport after the scroll.
// --> We need to shift the current invalidation rectangles as well.
_api.invalidatedCursorArea.top = gsl::narrow_cast<u16>(clamp<int>(_api.invalidatedCursorArea.top + delta, u16min, u16max));
_api.invalidatedCursorArea.bottom = gsl::narrow_cast<u16>(clamp<int>(_api.invalidatedCursorArea.bottom + delta, u16min, u16max));
if (delta < 0)
{
_api.invalidatedRows.x = gsl::narrow_cast<u16>(clamp<int>(_api.invalidatedRows.x + delta, u16min, u16max));
_api.invalidatedRows.y = _api.cellCount.y;
}
else
{
_api.invalidatedRows.x = 0;
_api.invalidatedRows.y = gsl::narrow_cast<u16>(clamp<int>(_api.invalidatedRows.y + delta, u16min, u16max));
}
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::InvalidateAll() noexcept
{
_api.invalidatedRows = invalidatedRowsAll;
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::InvalidateCircling(_Out_ bool* const pForcePaint) noexcept
{
RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint);
*pForcePaint = false;
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::InvalidateTitle(const std::wstring_view proposedTitle) noexcept
{
WI_SetFlag(_api.invalidations, ApiInvalidations::Title);
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, _Out_ FontInfo& fontInfo) noexcept
{
return UpdateFont(fontInfoDesired, fontInfo, {}, {});
}
[[nodiscard]] HRESULT AtlasEngine::UpdateSoftFont(const gsl::span<const uint16_t> bitPattern, const SIZE cellSize, const size_t centeringHint) noexcept
{
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::UpdateDpi(const int dpi) noexcept
{
u16 newDPI;
RETURN_IF_FAILED(api_narrow(dpi, newDPI));
if (_api.dpi != newDPI)
{
_api.dpi = newDPI;
WI_SetFlag(_api.invalidations, ApiInvalidations::Font);
}
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::UpdateViewport(const SMALL_RECT srNewViewport) noexcept
{
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::GetProposedFont(const FontInfoDesired& fontInfoDesired, _Out_ FontInfo& fontInfo, const int dpi) noexcept
try
{
// One day I'm going to implement GDI for AtlasEngine...
// Until then this code is work in progress.
#if 0
wil::unique_hfont hfont;
// This block of code (for GDI fonts) is unfinished.
if (fontInfoDesired.IsDefaultRasterFont())
{
hfont.reset(static_cast<HFONT>(GetStockObject(OEM_FIXED_FONT)));
RETURN_HR_IF(E_FAIL, !hfont);
}
else if (requestedFaceName == DEFAULT_RASTER_FONT_FACENAME)
{
// GDI Windows Font Mapping reference:
// https://msdn.microsoft.com/en-us/library/ms969909.aspx
LOGFONTW lf;
lf.lfHeight = -MulDiv(requestedSize.Y, dpi, 72);
lf.lfWidth = 0;
lf.lfEscapement = 0;
lf.lfOrientation = 0;
lf.lfWeight = requestedWeight;
lf.lfItalic = FALSE;
lf.lfUnderline = FALSE;
lf.lfStrikeOut = FALSE;
lf.lfCharSet = OEM_CHARSET;
lf.lfOutPrecision = OUT_RASTER_PRECIS;
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
lf.lfQuality = PROOF_QUALITY; // disables scaling for rasterized fonts
lf.lfPitchAndFamily = FIXED_PITCH | FF_MODERN;
// .size() only includes regular characters, but we also want to copy the trailing \0, so +1 it is.
memcpy(&lf.lfFaceName[0], &DEFAULT_RASTER_FONT_FACENAME[0], sizeof(DEFAULT_RASTER_FONT_FACENAME));
hfont.reset(CreateFontIndirectW(&lf));
RETURN_HR_IF(E_FAIL, !hfont);
}
if (hfont)
{
// wil::unique_any_t's constructor says: "should not be WI_NOEXCEPT (may forward to a throwing constructor)".
// The constructor we use by default doesn't throw.
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6).
wil::unique_hdc hdc{ CreateCompatibleDC(nullptr) };
RETURN_HR_IF(E_FAIL, !hdc);
DeleteObject(SelectObject(hdc.get(), hfont.get()));
SIZE sz;
RETURN_HR_IF(E_FAIL, !GetTextExtentPoint32W(hdc.get(), L"M", 1, &sz));
resultingCellSize.X = gsl::narrow<SHORT>(sz.cx);
resultingCellSize.Y = gsl::narrow<SHORT>(sz.cy);
}
#endif
_resolveFontMetrics(fontInfoDesired, fontInfo);
return S_OK;
}
CATCH_RETURN()
[[nodiscard]] HRESULT AtlasEngine::GetDirtyArea(gsl::span<const til::rectangle>& area) noexcept
{
area = gsl::span{ &_api.dirtyRect, 1 };
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::GetFontSize(_Out_ COORD* const pFontSize) noexcept
{
RETURN_HR_IF_NULL(E_INVALIDARG, pFontSize);
pFontSize->X = gsl::narrow_cast<SHORT>(_api.fontMetrics.cellSize.x);
pFontSize->Y = gsl::narrow_cast<SHORT>(_api.fontMetrics.cellSize.y);
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept
{
RETURN_HR_IF_NULL(E_INVALIDARG, pResult);
wil::com_ptr<IDWriteTextLayout> textLayout;
RETURN_IF_FAILED(_sr.dwriteFactory->CreateTextLayout(glyph.data(), gsl::narrow_cast<uint32_t>(glyph.size()), _getTextFormat(false, false), FLT_MAX, FLT_MAX, textLayout.addressof()));
DWRITE_TEXT_METRICS metrics;
RETURN_IF_FAILED(textLayout->GetMetrics(&metrics));
*pResult = static_cast<unsigned int>(std::ceil(metrics.width)) > _api.fontMetrics.cellSize.x;
return S_OK;
}
[[nodiscard]] HRESULT AtlasEngine::UpdateTitle(const std::wstring_view newTitle) noexcept
{
return S_OK;
}
#pragma endregion
#pragma region DxRenderer
HRESULT AtlasEngine::Enable() noexcept
{
return S_OK;
}
[[nodiscard]] bool AtlasEngine::GetRetroTerminalEffect() const noexcept
{
return false;
}
[[nodiscard]] float AtlasEngine::GetScaling() const noexcept
{
return static_cast<float>(_api.dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
}
[[nodiscard]] HANDLE AtlasEngine::GetSwapChainHandle()
{
if (WI_IsFlagSet(_api.invalidations, ApiInvalidations::Device))
{
_createResources();
WI_ClearFlag(_api.invalidations, ApiInvalidations::Device);
}
return _api.swapChainHandle.get();
}
[[nodiscard]] Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept
{
assert(_api.fontMetrics.cellSize.x != 0);
assert(_api.fontMetrics.cellSize.y != 0);
return Types::Viewport::FromDimensions(viewInPixels.Origin(), COORD{ gsl::narrow_cast<short>(viewInPixels.Width() / _api.fontMetrics.cellSize.x), gsl::narrow_cast<short>(viewInPixels.Height() / _api.fontMetrics.cellSize.y) });
}
[[nodiscard]] Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept
{
assert(_api.fontMetrics.cellSize.x != 0);
assert(_api.fontMetrics.cellSize.y != 0);
return Types::Viewport::FromDimensions(viewInCharacters.Origin(), COORD{ gsl::narrow_cast<short>(viewInCharacters.Width() * _api.fontMetrics.cellSize.x), gsl::narrow_cast<short>(viewInCharacters.Height() * _api.fontMetrics.cellSize.y) });
}
void AtlasEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept
{
const auto mode = gsl::narrow_cast<u16>(antialiasingMode);
if (_api.antialiasingMode != mode)
{
_api.antialiasingMode = mode;
WI_SetFlag(_api.invalidations, ApiInvalidations::Font);
}
}
void AtlasEngine::SetCallback(std::function<void()> pfn) noexcept
{
_api.swapChainChangedCallback = std::move(pfn);
}
void AtlasEngine::SetDefaultTextBackgroundOpacity(const float opacity) noexcept
{
const auto mixin = opacity == 1.0f ? 0xff000000 : 0x00000000;
if (_api.backgroundOpaqueMixin != mixin)
{
_api.backgroundOpaqueMixin = mixin;
WI_SetFlag(_api.invalidations, ApiInvalidations::SwapChain);
}
}
void AtlasEngine::SetForceFullRepaintRendering(bool enable) noexcept
{
}
[[nodiscard]] HRESULT AtlasEngine::SetHwnd(const HWND hwnd) noexcept
{
if (_api.hwnd != hwnd)
{
_api.hwnd = hwnd;
WI_SetFlag(_api.invalidations, ApiInvalidations::SwapChain);
}
return S_OK;
}
void AtlasEngine::SetPixelShaderPath(std::wstring_view value) noexcept
{
}
void AtlasEngine::SetRetroTerminalEffect(bool enable) noexcept
{
}
void AtlasEngine::SetSelectionBackground(const COLORREF color, const float alpha) noexcept
{
const u32 selectionColor = (color & 0xffffff) | gsl::narrow_cast<u32>(std::lroundf(alpha * 255.0f)) << 24;
if (_api.selectionColor != selectionColor)
{
_api.selectionColor = selectionColor;
WI_SetFlag(_api.invalidations, ApiInvalidations::Settings);
}
}
void AtlasEngine::SetSoftwareRendering(bool enable) noexcept
{
}
void AtlasEngine::SetIntenseIsBold(bool enable) noexcept
{
}
void AtlasEngine::SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept
{
_api.warningCallback = std::move(pfn);
}
[[nodiscard]] HRESULT AtlasEngine::SetWindowSize(const SIZE pixels) noexcept
{
u16x2 newSize;
RETURN_IF_FAILED(vec2_narrow(pixels.cx, pixels.cy, newSize));
// At the time of writing:
// When Win+D is pressed, `TriggerRedrawCursor` is called and a render pass is initiated.
// As conhost is in the background, GetClientRect will return {0,0} and we'll get called with {0,0}.
// This isn't a valid value for _api.sizeInPixel and would crash _recreateSizeDependentResources().
if (_api.sizeInPixel != newSize && newSize != u16x2{})
{
_api.sizeInPixel = newSize;
_api.cellCount = _api.sizeInPixel / _api.fontMetrics.cellSize;
WI_SetFlag(_api.invalidations, ApiInvalidations::Size);
}
return S_OK;
}
void AtlasEngine::ToggleShaderEffects() noexcept
{
}
[[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept
try
{
std::vector<DWRITE_FONT_FEATURE> fontFeatures;
if (!features.empty())
{
fontFeatures.reserve(features.size() + 3);
// All of these features are enabled by default by DirectWrite.
// If you want to (and can) peek into the source of DirectWrite
// you can look for the "GenericDefaultGsubFeatures" and "GenericDefaultGposFeatures" arrays.
// Gsub is for GetGlyphs() and Gpos for GetGlyphPlacements().
//
// GH#10774: Apparently specifying all of the features is just redundant.
fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES, 1 });
fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_LIGATURES, 1 });
fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES, 1 });
for (const auto& p : features)
{
if (p.first.size() == 4)
{
const auto s = p.first.data();
switch (const auto tag = DWRITE_MAKE_FONT_FEATURE_TAG(s[0], s[1], s[2], s[3]))
{
case DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES:
fontFeatures[0].parameter = p.second;
break;
case DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_LIGATURES:
fontFeatures[1].parameter = p.second;
break;
case DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES:
fontFeatures[2].parameter = p.second;
break;
default:
fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ tag, p.second });
break;
}
}
}
}
std::vector<DWRITE_FONT_AXIS_VALUE> fontAxisValues;
if (!axes.empty())
{
fontAxisValues.reserve(axes.size() + 3);
// AtlasEngine::_recreateFontDependentResources() relies on these fields to
// exist in this particular order in order to create appropriate default axes.
fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_WEIGHT, -1.0f });
fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_ITALIC, -1.0f });
fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_SLANT, -1.0f });
for (const auto& p : axes)
{
if (p.first.size() == 4)
{
const auto s = p.first.data();
switch (const auto tag = DWRITE_MAKE_FONT_AXIS_TAG(s[0], s[1], s[2], s[3]))
{
case DWRITE_FONT_AXIS_TAG_WEIGHT:
fontAxisValues[0].value = p.second;
break;
case DWRITE_FONT_AXIS_TAG_ITALIC:
fontAxisValues[1].value = p.second;
break;
case DWRITE_FONT_AXIS_TAG_SLANT:
fontAxisValues[2].value = p.second;
break;
default:
fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ tag, p.second });
break;
}
}
}
}
const auto previousCellSize = _api.fontMetrics.cellSize;
_resolveFontMetrics(fontInfoDesired, fontInfo, &_api.fontMetrics);
_api.fontFeatures = std::move(fontFeatures);
_api.fontAxisValues = std::move(fontAxisValues);
WI_SetFlag(_api.invalidations, ApiInvalidations::Font);
if (previousCellSize != _api.fontMetrics.cellSize)
{
_api.cellCount = _api.sizeInPixel / _api.fontMetrics.cellSize;
WI_SetFlag(_api.invalidations, ApiInvalidations::Size);
}
return S_OK;
}
CATCH_RETURN()
void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept
{
}
#pragma endregion
void AtlasEngine::_resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics) const
{
auto requestedFaceName = fontInfoDesired.GetFaceName().c_str();
const auto requestedFamily = fontInfoDesired.GetFamily();
auto requestedWeight = fontInfoDesired.GetWeight();
auto requestedSize = fontInfoDesired.GetEngineSize();
if (!requestedFaceName)
{
requestedFaceName = L"Consolas";
}
if (!requestedSize.Y)
{
requestedSize = { 0, 12 };
}
if (!requestedWeight)
{
requestedWeight = DWRITE_FONT_WEIGHT_NORMAL;
}
wil::com_ptr<IDWriteFontCollection> systemFontCollection;
THROW_IF_FAILED(_sr.dwriteFactory->GetSystemFontCollection(systemFontCollection.addressof(), false));
u32 index = 0;
BOOL exists = false;
THROW_IF_FAILED(systemFontCollection->FindFamilyName(requestedFaceName, &index, &exists));
THROW_HR_IF(DWRITE_E_NOFONT, !exists);
wil::com_ptr<IDWriteFontFamily> fontFamily;
THROW_IF_FAILED(systemFontCollection->GetFontFamily(index, fontFamily.addressof()));
wil::com_ptr<IDWriteFont> font;
THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(static_cast<DWRITE_FONT_WEIGHT>(requestedWeight), DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.addressof()));
wil::com_ptr<IDWriteFontFace> fontFace;
THROW_IF_FAILED(font->CreateFontFace(fontFace.addressof()));
DWRITE_FONT_METRICS metrics;
fontFace->GetMetrics(&metrics);
// According to Wikipedia:
// > One em was traditionally defined as the width of the capital 'M' in the current typeface and point size,
// > because the 'M' was commonly cast the full-width of the square blocks [...] which are used in printing presses.
// Even today M is often the widest character in a font that supports ASCII.
// In the future a more robust solution could be written, until then this simple solution works for most cases.
static constexpr u32 codePoint = L'M';
u16 glyphIndex;
THROW_IF_FAILED(fontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex));
DWRITE_GLYPH_METRICS glyphMetrics;
THROW_IF_FAILED(fontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics));
// Point sizes are commonly treated at a 72 DPI scale
// (including by OpenType), whereas DirectWrite uses 96 DPI.
// Since we want the height in px we multiply by the display's DPI.
const auto fontSizeInPx = std::ceil(requestedSize.Y / 72.0 * _api.dpi);
const auto designUnitsPerPx = fontSizeInPx / static_cast<double>(metrics.designUnitsPerEm);
const auto ascentInPx = static_cast<double>(metrics.ascent) * designUnitsPerPx;
const auto descentInPx = static_cast<double>(metrics.descent) * designUnitsPerPx;
const auto lineGapInPx = static_cast<double>(metrics.lineGap) * designUnitsPerPx;
const auto advanceWidthInPx = static_cast<double>(glyphMetrics.advanceWidth) * designUnitsPerPx;
const auto halfGapInPx = lineGapInPx / 2.0;
const auto baseline = std::ceil(ascentInPx + halfGapInPx);
const auto cellWidth = gsl::narrow<u16>(std::ceil(advanceWidthInPx));
const auto cellHeight = gsl::narrow<u16>(std::ceil(baseline + descentInPx + halfGapInPx));
{
COORD resultingCellSize;
resultingCellSize.X = gsl::narrow<SHORT>(cellWidth);
resultingCellSize.Y = gsl::narrow<SHORT>(cellHeight);
fontInfo.SetFromEngine(requestedFaceName, requestedFamily, requestedWeight, false, resultingCellSize, requestedSize);
}
if (fontMetrics)
{
const auto underlineOffsetInPx = static_cast<double>(-metrics.underlinePosition) * designUnitsPerPx;
const auto underlineThicknessInPx = static_cast<double>(metrics.underlineThickness) * designUnitsPerPx;
const auto strikethroughOffsetInPx = static_cast<double>(-metrics.strikethroughPosition) * designUnitsPerPx;
const auto strikethroughThicknessInPx = static_cast<double>(metrics.strikethroughThickness) * designUnitsPerPx;
const auto lineThickness = gsl::narrow<u16>(std::round(std::min(underlineThicknessInPx, strikethroughThicknessInPx)));
const auto underlinePos = gsl::narrow<u16>(std::round(baseline + underlineOffsetInPx - lineThickness / 2.0));
const auto strikethroughPos = gsl::narrow<u16>(std::round(baseline + strikethroughOffsetInPx - lineThickness / 2.0));
auto fontName = wil::make_process_heap_string(requestedFaceName);
const auto fontWeight = gsl::narrow<u16>(requestedWeight);
// 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.
fontMetrics->fontName = std::move(fontName);
fontMetrics->fontSizeInDIP = static_cast<float>(fontSizeInPx / static_cast<double>(_api.dpi) * 96.0);
fontMetrics->baselineInDIP = static_cast<float>(baseline / static_cast<double>(_api.dpi) * 96.0);
fontMetrics->cellSize = { cellWidth, cellHeight };
fontMetrics->fontWeight = fontWeight;
fontMetrics->underlinePos = underlinePos;
fontMetrics->strikethroughPos = strikethroughPos;
fontMetrics->lineThickness = lineThickness;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,761 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <d2d1.h>
#include <d3d11_1.h>
#include <dwrite_3.h>
#include "../../renderer/inc/IRenderEngine.hpp"
namespace Microsoft::Console::Render
{
class AtlasEngine final : public IRenderEngine
{
public:
explicit AtlasEngine();
AtlasEngine(const AtlasEngine&) = delete;
AtlasEngine& operator=(const AtlasEngine&) = delete;
// IRenderEngine
[[nodiscard]] HRESULT StartPaint() noexcept override;
[[nodiscard]] HRESULT EndPaint() noexcept override;
[[nodiscard]] bool RequiresContinuousRedraw() noexcept override;
void WaitUntilCanRender() noexcept override;
[[nodiscard]] HRESULT Present() noexcept override;
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* pForcePaint) noexcept override;
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateSystem(const RECT* prcDirtyClient) noexcept override;
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
[[nodiscard]] HRESULT InvalidateScroll(const COORD* pcoordDelta) noexcept override;
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* pForcePaint) noexcept override;
[[nodiscard]] HRESULT InvalidateTitle(std::wstring_view proposedTitle) noexcept override;
[[nodiscard]] HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept override;
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
[[nodiscard]] HRESULT PrepareLineTransform(LineRendition lineRendition, size_t targetRow, size_t viewportLeft) noexcept override;
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> clusters, COORD coord, bool fTrimLeft, bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, COORD coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintSelection(SMALL_RECT rect) noexcept override;
[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;
[[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, gsl::not_null<IRenderData*> pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept override;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override;
[[nodiscard]] HRESULT UpdateSoftFont(gsl::span<const uint16_t> bitPattern, SIZE cellSize, size_t centeringHint) noexcept override;
[[nodiscard]] HRESULT UpdateDpi(int iDpi) noexcept override;
[[nodiscard]] HRESULT UpdateViewport(SMALL_RECT srNewViewport) noexcept override;
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, int iDpi) noexcept override;
[[nodiscard]] HRESULT GetDirtyArea(gsl::span<const til::rectangle>& area) noexcept override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(std::wstring_view glyph, _Out_ bool* pResult) noexcept override;
[[nodiscard]] HRESULT UpdateTitle(std::wstring_view newTitle) noexcept override;
// DxRenderer - getter
HRESULT Enable() noexcept override;
[[nodiscard]] bool GetRetroTerminalEffect() const noexcept override;
[[nodiscard]] float GetScaling() const noexcept override;
[[nodiscard]] HANDLE GetSwapChainHandle() override;
[[nodiscard]] Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept override;
[[nodiscard]] Types::Viewport GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept override;
// DxRenderer - setter
void SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept override;
void SetCallback(std::function<void()> pfn) noexcept override;
void SetDefaultTextBackgroundOpacity(float opacity) noexcept override;
void SetForceFullRepaintRendering(bool enable) noexcept override;
[[nodiscard]] HRESULT SetHwnd(HWND hwnd) noexcept override;
void SetPixelShaderPath(std::wstring_view value) noexcept override;
void SetRetroTerminalEffect(bool enable) noexcept override;
void SetSelectionBackground(COLORREF color, float alpha = 0.5f) noexcept override;
void SetSoftwareRendering(bool enable) noexcept override;
void SetIntenseIsBold(bool enable) noexcept override;
void SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept override;
[[nodiscard]] HRESULT SetWindowSize(SIZE pixels) noexcept override;
void ToggleShaderEffects() noexcept override;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept override;
void UpdateHyperlinkHoveredId(uint16_t hoveredId) noexcept override;
// Some helper classes for the implementation.
// public because I don't want to sprinkle the code with friends.
public:
#define ATLAS_POD_OPS(type) \
constexpr bool operator==(const type& rhs) const noexcept \
{ \
return __builtin_memcmp(this, &rhs, sizeof(rhs)) == 0; \
} \
\
constexpr bool operator!=(const type& rhs) const noexcept \
{ \
return __builtin_memcmp(this, &rhs, sizeof(rhs)) != 0; \
}
#define ATLAS_FLAG_OPS(type, underlying) \
friend constexpr type operator~(type v) noexcept { return static_cast<type>(~static_cast<underlying>(v)); } \
friend constexpr type operator|(type lhs, type rhs) noexcept { return static_cast<type>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs)); } \
friend constexpr type operator&(type lhs, type rhs) noexcept { return static_cast<type>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs)); } \
friend constexpr void operator|=(type& lhs, type rhs) noexcept { lhs = lhs | rhs; } \
friend constexpr void operator&=(type& lhs, type rhs) noexcept { lhs = lhs & rhs; }
template<typename T>
struct vec2
{
T x{};
T y{};
ATLAS_POD_OPS(vec2)
constexpr vec2 operator/(const vec2& rhs) noexcept
{
assert(rhs.x != 0 && rhs.y != 0);
return { gsl::narrow_cast<T>(x / rhs.x), gsl::narrow_cast<T>(y / rhs.y) };
}
};
template<typename T>
struct vec4
{
T x{};
T y{};
T z{};
T w{};
ATLAS_POD_OPS(vec4)
};
template<typename T>
struct rect
{
T left{};
T top{};
T right{};
T bottom{};
ATLAS_POD_OPS(rect)
constexpr bool non_empty() noexcept
{
return (left < right) & (top < bottom);
}
};
using u8 = uint8_t;
using u16 = uint16_t;
using u16x2 = vec2<u16>;
using u16r = rect<u16>;
using i16 = int16_t;
using u32 = uint32_t;
using u32x2 = vec2<u32>;
using i32 = int32_t;
using f32 = float;
using f32x2 = vec2<f32>;
using f32x4 = vec4<f32>;
struct TextAnalyzerResult
{
u32 textPosition = 0;
u32 textLength = 0;
// These 2 fields represent DWRITE_SCRIPT_ANALYSIS.
// Not using DWRITE_SCRIPT_ANALYSIS drops the struct size from 20 down to 12 bytes.
u16 script = 0;
u8 shapes = 0;
u8 bidiLevel = 0;
};
private:
template<typename T, size_t Alignment = alignof(T)>
struct Buffer
{
constexpr Buffer() noexcept = default;
explicit Buffer(size_t size) :
_data{ allocate(size) },
_size{ size }
{
}
Buffer(const T* data, size_t size) :
_data{ allocate(size) },
_size{ size }
{
static_assert(std::is_trivially_copyable_v<T>);
memcpy(_data, data, size * sizeof(T));
}
~Buffer()
{
deallocate(_data);
}
Buffer(Buffer&& other) noexcept :
_data{ std::exchange(other._data, nullptr) },
_size{ std::exchange(other._size, 0) }
{
}
#pragma warning(suppress : 26432) // If you define or delete any default operation in the type '...', define or delete them all (c.21).
Buffer& operator=(Buffer&& other) noexcept
{
deallocate(_data);
_data = std::exchange(other._data, nullptr);
_size = std::exchange(other._size, 0);
return *this;
}
explicit operator bool() const noexcept
{
return _data != nullptr;
}
T& operator[](size_t index) noexcept
{
assert(index < _size);
return _data[index];
}
const T& operator[](size_t index) const noexcept
{
assert(index < _size);
return _data[index];
}
T* data() noexcept
{
return _data;
}
const T* data() const noexcept
{
return _data;
}
size_t size() const noexcept
{
return _size;
}
private:
// These two functions don't need to use scoped objects or standard allocators,
// since this class is in fact an scoped allocator object itself.
#pragma warning(push)
#pragma warning(disable : 26402) // Return a scoped object instead of a heap-allocated if it has a move constructor (r.3).
#pragma warning(disable : 26409) // Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11).
static T* allocate(size_t size)
{
if constexpr (Alignment <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
{
return static_cast<T*>(::operator new(size * sizeof(T)));
}
else
{
return static_cast<T*>(::operator new(size * sizeof(T), static_cast<std::align_val_t>(Alignment)));
}
}
static void deallocate(T* data) noexcept
{
if constexpr (Alignment <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
{
::operator delete(data);
}
else
{
::operator delete(data, static_cast<std::align_val_t>(Alignment));
}
}
#pragma warning(pop)
T* _data = nullptr;
size_t _size = 0;
};
// This structure works similar to how std::string works:
// You can think of a std::string as a structure consisting of:
// char* data;
// size_t size;
// size_t capacity;
// where data is some backing memory allocated on the heap.
//
// But std::string employs an optimization called "small string optimization" (SSO).
// To simplify things it could be explained as:
// If the string capacity is small, then the characters are stored inside the "data"
// pointer and you make sure to set the lowest bit in the pointer one way or another.
// Heap allocations are always aligned by at least 4-8 bytes on any platform.
// If the address of the "data" pointer is not even you know data is stored inline.
template<typename T>
union SmallObjectOptimizer
{
static_assert(std::is_trivially_copyable_v<T>);
static_assert(std::has_unique_object_representations_v<T>);
T* allocated = nullptr;
T inlined;
constexpr SmallObjectOptimizer() = default;
SmallObjectOptimizer(const SmallObjectOptimizer& other)
{
const auto otherData = other.data();
const auto otherSize = other.size();
const auto data = initialize(otherSize);
memcpy(data, otherData, otherSize);
}
SmallObjectOptimizer& operator=(const SmallObjectOptimizer& other)
{
if (this != &other)
{
delete this;
new (this) SmallObjectOptimizer(other);
}
return &this;
}
SmallObjectOptimizer(SmallObjectOptimizer&& other) noexcept
{
memcpy(this, &other, std::max(sizeof(allocated), sizeof(inlined)));
other.allocated = nullptr;
}
SmallObjectOptimizer& operator=(SmallObjectOptimizer&& other) noexcept
{
return *new (this) SmallObjectOptimizer(other);
}
~SmallObjectOptimizer()
{
if (!is_inline())
{
#pragma warning(suppress : 26408) // Avoid malloc() and free(), prefer the nothrow version of new with delete (r.10).
free(allocated);
}
}
T* initialize(size_t byteSize)
{
if (would_inline(byteSize))
{
return &inlined;
}
#pragma warning(suppress : 26408) // Avoid malloc() and free(), prefer the nothrow version of new with delete (r.10).
allocated = THROW_IF_NULL_ALLOC(static_cast<T*>(malloc(byteSize)));
return allocated;
}
constexpr bool would_inline(size_t byteSize) const noexcept
{
return byteSize <= sizeof(T);
}
bool is_inline() const noexcept
{
return (__builtin_bit_cast(uintptr_t, allocated) & 1) != 0;
}
const T* data() const noexcept
{
return is_inline() ? &inlined : allocated;
}
size_t size() const noexcept
{
return is_inline() ? sizeof(inlined) : _msize(allocated);
}
};
struct FontMetrics
{
wil::unique_process_heap_string fontName;
float baselineInDIP = 0.0f;
float fontSizeInDIP = 0.0f;
u16x2 cellSize;
u16 fontWeight = 0;
u16 underlinePos = 0;
u16 strikethroughPos = 0;
u16 lineThickness = 0;
};
// These flags are shared with shader_ps.hlsl.
// If you change this be sure to copy it over to shader_ps.hlsl.
//
// clang-format off
enum class CellFlags : u32
{
None = 0x00000000,
Inlined = 0x00000001,
ColoredGlyph = 0x00000002,
ThinFont = 0x00000004,
Cursor = 0x00000008,
Selected = 0x00000010,
BorderLeft = 0x00000020,
BorderTop = 0x00000040,
BorderRight = 0x00000080,
BorderBottom = 0x00000100,
Underline = 0x00000200,
UnderlineDotted = 0x00000400,
UnderlineDouble = 0x00000800,
Strikethrough = 0x00001000,
};
// clang-format on
ATLAS_FLAG_OPS(CellFlags, u32)
// This structure is shared with the GPU shader and needs to follow certain alignment rules.
// You can generally assume that only u32 or types of that alignment are allowed.
struct Cell
{
alignas(u32) u16x2 tileIndex;
alignas(u32) CellFlags flags = CellFlags::None;
u32x2 color;
};
struct AtlasKeyAttributes
{
u16 inlined : 1;
u16 bold : 1;
u16 italic : 1;
u16 cellCount : 13;
ATLAS_POD_OPS(AtlasKeyAttributes)
};
struct AtlasKeyData
{
AtlasKeyAttributes attributes;
u16 charCount;
wchar_t chars[14];
};
struct AtlasKey
{
AtlasKey(AtlasKeyAttributes attributes, u16 charCount, const wchar_t* chars)
{
const auto size = dataSize(charCount);
const auto data = _data.initialize(size);
attributes.inlined = _data.would_inline(size);
data->attributes = attributes;
data->charCount = charCount;
memcpy(&data->chars[0], chars, static_cast<size_t>(charCount) * sizeof(AtlasKeyData::chars[0]));
}
const AtlasKeyData* data() const noexcept
{
return _data.data();
}
size_t hash() const noexcept
{
const auto d = data();
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
return std::_Fnv1a_append_bytes(std::_FNV_offset_basis, reinterpret_cast<const u8*>(d), dataSize(d->charCount));
}
bool operator==(const AtlasKey& rhs) const noexcept
{
const auto a = data();
const auto b = rhs.data();
return a->charCount == b->charCount && memcmp(a, b, dataSize(a->charCount)) == 0;
}
private:
SmallObjectOptimizer<AtlasKeyData> _data;
static constexpr size_t dataSize(u16 charCount) noexcept
{
// This returns the actual byte size of a AtlasKeyData struct for the given charCount.
// The `wchar_t chars[2]` is only a buffer for the inlined variant after
// all and the actual charCount can be smaller or larger. Due to this we
// remove the size of the `chars` array and add it's true length on top.
return sizeof(AtlasKeyData) - sizeof(AtlasKeyData::chars) + static_cast<size_t>(charCount) * sizeof(AtlasKeyData::chars[0]);
}
};
struct AtlasKeyHasher
{
size_t operator()(const AtlasKey& key) const noexcept
{
return key.hash();
}
};
struct AtlasValueData
{
CellFlags flags = CellFlags::None;
u16x2 coords[7];
};
struct AtlasValue
{
constexpr AtlasValue() = default;
u16x2* initialize(CellFlags flags, u16 cellCount)
{
const auto size = dataSize(cellCount);
const auto data = _data.initialize(size);
WI_SetFlagIf(flags, CellFlags::Inlined, _data.would_inline(size));
data->flags = flags;
return &data->coords[0];
}
const AtlasValueData* data() const noexcept
{
return _data.data();
}
private:
SmallObjectOptimizer<AtlasValueData> _data;
static constexpr size_t dataSize(u16 coordCount) noexcept
{
return sizeof(AtlasValueData) - sizeof(AtlasValueData::coords) + static_cast<size_t>(coordCount) * sizeof(AtlasValueData::coords[0]);
}
};
struct AtlasQueueItem
{
const AtlasKey* key;
const AtlasValue* value;
float scale;
};
struct CachedCursorOptions
{
u32 cursorColor = INVALID_COLOR;
u16 cursorType = gsl::narrow_cast<u16>(CursorType::Legacy);
u8 heightPercentage = 20;
ATLAS_POD_OPS(CachedCursorOptions)
};
struct BufferLineMetadata
{
u32x2 colors;
CellFlags flags = CellFlags::None;
};
// NOTE: D3D constant buffers sizes must be a multiple of 16 bytes.
struct alignas(16) ConstBuffer
{
// WARNING: Modify this carefully after understanding how HLSL struct packing works.
// The gist is:
// * Minimum alignment is 4 bytes (like `#pragma pack 4`)
// * Members cannot straddle 16 byte boundaries
// 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(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;
#pragma warning(suppress : 4324) // 'ConstBuffer': structure was padded due to alignment specifier
};
// Handled in BeginPaint()
enum class ApiInvalidations : u8
{
None = 0,
Title = 1 << 0,
Device = 1 << 1,
SwapChain = 1 << 2,
Size = 1 << 3,
Font = 1 << 4,
Settings = 1 << 5,
};
ATLAS_FLAG_OPS(ApiInvalidations, u8)
// Handled in Present()
enum class RenderInvalidations : u8
{
None = 0,
Cursor = 1 << 0,
ConstBuffer = 1 << 1,
};
ATLAS_FLAG_OPS(RenderInvalidations, u8)
// MSVC STL (version 22000) implements std::clamp<T>(T, T, T) in terms of the generic
// std::clamp<T, Predicate>(T, T, T, Predicate) with std::less{} as the argument,
// which introduces branching. While not perfect, this is still better than std::clamp.
template<typename T>
static constexpr T clamp(T val, T min, T max)
{
return std::max(min, std::min(max, val));
}
// AtlasEngine.cpp
[[nodiscard]] HRESULT _handleException(const wil::ResultException& exception) noexcept;
__declspec(noinline) void _createResources();
void _releaseSwapChain();
__declspec(noinline) void _createSwapChain();
__declspec(noinline) void _recreateSizeDependentResources();
__declspec(noinline) void _recreateFontDependentResources();
IDWriteTextFormat* _getTextFormat(bool bold, bool italic) const noexcept;
const Buffer<DWRITE_FONT_AXIS_VALUE>& _getTextFormatAxis(bool bold, bool italic) const noexcept;
Cell* _getCell(u16 x, u16 y) noexcept;
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);
// 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);
void _processGlyphQueue();
void _drawGlyph(const AtlasQueueItem& item) const;
void _drawCursor();
void _copyScratchpadTile(uint32_t scratchpadIndex, u16x2 target, uint32_t copyFlags = 0) const noexcept;
static constexpr bool debugGlyphGenerationPerformance = false;
static constexpr bool debugGeneralPerformance = false || debugGlyphGenerationPerformance;
static constexpr bool continuousRedraw = false || debugGeneralPerformance;
static constexpr u16 u16min = 0x0000;
static constexpr u16 u16max = 0xffff;
static constexpr i16 i16min = -0x8000;
static constexpr i16 i16max = 0x7fff;
static constexpr u16r invalidatedAreaNone = { u16max, u16max, u16min, u16min };
static constexpr u16x2 invalidatedRowsNone{ u16max, u16min };
static constexpr u16x2 invalidatedRowsAll{ u16min, u16max };
struct StaticResources
{
wil::com_ptr<ID2D1Factory> d2dFactory;
wil::com_ptr<IDWriteFactory1> dwriteFactory;
wil::com_ptr<IDWriteFontFallback> systemFontFallback;
wil::com_ptr<IDWriteTextAnalyzer1> textAnalyzer;
bool isWindows10OrGreater = true;
#ifndef NDEBUG
wil::unique_folder_change_reader_nothrow sourceCodeWatcher;
std::atomic<int64_t> sourceCodeInvalidationTime{ INT64_MAX };
#endif
} _sr;
struct Resources
{
// D3D resources
wil::com_ptr<ID3D11Device> device;
wil::com_ptr<ID3D11DeviceContext1> deviceContext;
wil::com_ptr<IDXGISwapChain1> swapChain;
wil::unique_handle frameLatencyWaitableObject;
wil::com_ptr<ID3D11RenderTargetView> renderTargetView;
wil::com_ptr<ID3D11VertexShader> vertexShader;
wil::com_ptr<ID3D11PixelShader> pixelShader;
wil::com_ptr<ID3D11Buffer> constantBuffer;
wil::com_ptr<ID3D11Buffer> cellBuffer;
wil::com_ptr<ID3D11ShaderResourceView> cellView;
// D2D resources
wil::com_ptr<ID3D11Texture2D> atlasBuffer;
wil::com_ptr<ID3D11ShaderResourceView> atlasView;
wil::com_ptr<ID3D11Texture2D> atlasScratchpad;
wil::com_ptr<ID2D1RenderTarget> d2dRenderTarget;
wil::com_ptr<ID2D1Brush> brush;
wil::com_ptr<IDWriteTextFormat> textFormats[2][2];
Buffer<DWRITE_FONT_AXIS_VALUE> textFormatAxes[2][2];
wil::com_ptr<IDWriteTypography> typography;
Buffer<Cell, 32> cells; // invalidated by ApiInvalidations::Size
f32x2 cellSizeDIP; // invalidated by ApiInvalidations::Font, caches _api.cellSize but in DIP
u16x2 cellSize; // invalidated by ApiInvalidations::Font, caches _api.cellSize
u16x2 cellCount; // invalidated by ApiInvalidations::Font|Size, caches _api.cellCount
u16 underlinePos = 0;
u16 strikethroughPos = 0;
u16 lineThickness = 0;
u16 dpi = USER_DEFAULT_SCREEN_DPI; // invalidated by ApiInvalidations::Font, caches _api.dpi
u16 maxEncounteredCellCount = 0;
u16 scratchpadCellWidth = 0;
u16x2 atlasSizeInPixelLimit; // invalidated by ApiInvalidations::Font
u16x2 atlasSizeInPixel; // invalidated by ApiInvalidations::Font
u16x2 atlasPosition;
std::unordered_map<AtlasKey, AtlasValue, AtlasKeyHasher> glyphs;
std::vector<AtlasQueueItem> glyphQueue;
f32 gamma = 0;
f32 grayscaleEnhancedContrast = 0;
u32 backgroundColor = 0xff000000;
u32 selectionColor = 0x7fffffff;
CachedCursorOptions cursorOptions;
RenderInvalidations invalidations = RenderInvalidations::None;
#ifndef NDEBUG
// See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
// > For every frame it renders, the app should wait on this handle before starting any rendering operations.
// > Note that this requirement includes the first frame the app renders with the swap chain.
bool frameLatencyWaitableObjectUsed = false;
#endif
} _r;
struct ApiState
{
// This structure is loosely sorted in chunks from "very often accessed together"
// to seldom accessed and/or usually not together.
std::vector<wchar_t> bufferLine;
std::vector<u16> bufferLineColumn;
Buffer<BufferLineMetadata> bufferLineMetadata;
std::vector<TextAnalyzerResult> analysisResults;
Buffer<u16> clusterMap;
Buffer<DWRITE_SHAPING_TEXT_PROPERTIES> textProps;
Buffer<u16> glyphIndices;
Buffer<DWRITE_SHAPING_GLYPH_PROPERTIES> glyphProps;
std::vector<DWRITE_FONT_FEATURE> fontFeatures; // changes are flagged as ApiInvalidations::Font|Size
std::vector<DWRITE_FONT_AXIS_VALUE> fontAxisValues; // changes are flagged as ApiInvalidations::Font|Size
FontMetrics fontMetrics; // changes are flagged as ApiInvalidations::Font|Size
u16x2 cellCount; // caches `sizeInPixel / cellSize`
u16x2 sizeInPixel; // changes are flagged as ApiInvalidations::Size
// UpdateDrawingBrushes()
u32 backgroundOpaqueMixin = 0xff000000; // changes are flagged as ApiInvalidations::Device
u32x2 currentColor;
AtlasKeyAttributes attributes{};
u16 currentRow = 0;
CellFlags flags = CellFlags::None;
// SetSelectionBackground()
u32 selectionColor = 0x7fffffff;
// dirtyRect is a computed value based on invalidatedRows.
til::rectangle dirtyRect;
// These "invalidation" fields are reset in EndPaint()
u16r invalidatedCursorArea = invalidatedAreaNone;
u16x2 invalidatedRows = invalidatedRowsNone; // x is treated as "top" and y as "bottom"
i16 scrollOffset = 0;
std::function<void(HRESULT)> warningCallback;
std::function<void()> swapChainChangedCallback;
wil::unique_handle swapChainHandle;
HWND hwnd = nullptr;
u16 dpi = USER_DEFAULT_SCREEN_DPI; // changes are flagged as ApiInvalidations::Font|Size
u16 antialiasingMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; // changes are flagged as ApiInvalidations::Font
ApiInvalidations invalidations = ApiInvalidations::Device;
} _api;
#undef ATLAS_POD_OPS
#undef ATLAS_FLAG_OPS
};
}

View File

@ -0,0 +1,460 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "AtlasEngine.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,
// while any of the API methods (like AtlasEngine::Invalidate) might be called concurrently.
// The usage of the _r field is safe as its members are in practice
// only ever written to by the caller of Present() (the "Renderer" class).
// The _api fields on the other hand are concurrently written to by others.
#pragma warning(disable : 4100) // '...': unreferenced formal parameter
// Disable a bunch of warnings which get in the way of writing performant code.
#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 : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
using namespace Microsoft::Console::Render;
#pragma region IRenderEngine
// Present() is called without the console buffer lock being held.
// --> Put as much in here as possible.
[[nodiscard]] HRESULT AtlasEngine::Present() noexcept
try
{
_adjustAtlasSize();
_reserveScratchpadSize(_r.maxEncounteredCellCount);
_processGlyphQueue();
if (WI_IsFlagSet(_r.invalidations, RenderInvalidations::Cursor))
{
_drawCursor();
WI_ClearFlag(_r.invalidations, RenderInvalidations::Cursor);
}
// The values the constant buffer depends on are potentially updated after BeginPaint().
if (WI_IsFlagSet(_r.invalidations, RenderInvalidations::ConstBuffer))
{
_updateConstantBuffer();
WI_ClearFlag(_r.invalidations, RenderInvalidations::ConstBuffer);
}
{
#pragma warning(suppress : 26494) // Variable 'mapped' is uninitialized. Always initialize an object (type.5).
D3D11_MAPPED_SUBRESOURCE mapped;
THROW_IF_FAILED(_r.deviceContext->Map(_r.cellBuffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped));
assert(mapped.RowPitch >= _r.cells.size() * sizeof(Cell));
memcpy(mapped.pData, _r.cells.data(), _r.cells.size() * sizeof(Cell));
_r.deviceContext->Unmap(_r.cellBuffer.get(), 0);
}
// After Present calls, the back buffer needs to explicitly be
// re-bound to the D3D11 immediate context before it can be used again.
_r.deviceContext->OMSetRenderTargets(1, _r.renderTargetView.addressof(), nullptr);
_r.deviceContext->Draw(3, 0);
// See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
// > For every frame it renders, the app should wait on this handle before starting any rendering operations.
// > Note that this requirement includes the first frame the app renders with the swap chain.
assert(_r.frameLatencyWaitableObjectUsed);
// > IDXGISwapChain::Present: Partial Presentation (using a dirty rects or scroll) is not supported
// > for SwapChains created with DXGI_SWAP_EFFECT_DISCARD or DXGI_SWAP_EFFECT_FLIP_DISCARD.
// ---> No need to call IDXGISwapChain1::Present1.
// TODO: Would IDXGISwapChain1::Present1 and its dirty rects have benefits for remote desktop?
THROW_IF_FAILED(_r.swapChain->Present(1, 0));
// On some GPUs with tile based deferred rendering (TBDR) architectures, binding
// RenderTargets that already have contents in them (from previous rendering) incurs a
// cost for having to copy the RenderTarget contents back into tile memory for rendering.
//
// On Windows 10 with DXGI_SWAP_EFFECT_FLIP_DISCARD we get this for free.
if (!_sr.isWindows10OrGreater)
{
_r.deviceContext->DiscardView(_r.renderTargetView.get());
}
return S_OK;
}
catch (const wil::ResultException& exception)
{
return _handleException(exception);
}
CATCH_RETURN()
#pragma endregion
void AtlasEngine::_setShaderResources() const
{
_r.deviceContext->VSSetShader(_r.vertexShader.get(), nullptr, 0);
_r.deviceContext->PSSetShader(_r.pixelShader.get(), nullptr, 0);
// Our vertex shader uses a trick from Bill Bilodeau published in
// "Vertex Shader Tricks" at GDC14 to draw a fullscreen triangle
// without vertex/index buffers. This prepares our context for this.
_r.deviceContext->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr);
_r.deviceContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_UNKNOWN, 0);
_r.deviceContext->IASetInputLayout(nullptr);
_r.deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
_r.deviceContext->PSSetConstantBuffers(0, 1, _r.constantBuffer.addressof());
const std::array resources{ _r.cellView.get(), _r.atlasView.get() };
_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
{
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;
data.cellCountX = _r.cellCount.x;
data.cellSize.x = _r.cellSize.x;
data.cellSize.y = _r.cellSize.y;
data.underlinePos.x = _r.underlinePos;
data.underlinePos.y = _r.underlinePos + _r.lineThickness;
data.strikethroughPos.x = _r.strikethroughPos;
data.strikethroughPos.y = _r.strikethroughPos + _r.lineThickness;
data.backgroundColor = _r.backgroundColor;
data.cursorColor = _r.cursorOptions.cursorColor;
data.selectionColor = _r.selectionColor;
#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);
}
void AtlasEngine::_adjustAtlasSize()
{
if (_r.atlasPosition.y < _r.atlasSizeInPixel.y && _r.atlasPosition.x < _r.atlasSizeInPixel.x)
{
return;
}
const u32 limitX = _r.atlasSizeInPixelLimit.x;
const u32 limitY = _r.atlasSizeInPixelLimit.y;
const u32 posX = _r.atlasPosition.x;
const u32 posY = _r.atlasPosition.y;
const u32 cellX = _r.cellSize.x;
const u32 cellY = _r.cellSize.y;
const auto perCellArea = cellX * cellY;
// The texture atlas is filled like this:
// x →
// y +--------------+
// ↓ |XXXXXXXXXXXXXX|
// |XXXXXXXXXXXXXX|
// |XXXXX↖ |
// | | |
// +------|-------+
// This is where _r.atlasPosition points at.
//
// Each X is a glyph texture tile that's occupied.
// We can compute the area of pixels consumed by adding the first
// two lines of X (rectangular) together with the last line of X.
const auto currentArea = posY * limitX + posX * cellY;
// minArea reserves enough room for 64 cells in all cases (mainly during startup).
const auto minArea = 64 * perCellArea;
auto newArea = std::max(minArea, currentArea);
// I want the texture to grow exponentially similar to std::vector, as this
// ensures we don't need to resize the texture again right after having done.
// This rounds newArea up to the next power of 2.
unsigned long int index;
_BitScanReverse(&index, newArea); // newArea can't be 0
newArea = u32{ 1 } << (index + 1);
const auto pixelPerRow = limitX * cellY;
// newArea might be just large enough that it spans N full rows of cells and one additional row
// just barely. This algorithm rounds up newArea to the _next_ multiple of cellY.
const auto wantedHeight = (newArea + pixelPerRow - 1) / pixelPerRow * cellY;
// The atlas might either be a N rows of full width (xLimit) or just one
// row (where wantedHeight == cellY) that doesn't quite fill it's maximum width yet.
const auto wantedWidth = wantedHeight != cellY ? limitX : newArea / perCellArea * cellX;
// We know that limitX/limitY were u16 originally, and thus it's safe to narrow_cast it back.
const auto height = gsl::narrow_cast<u16>(std::min(limitY, wantedHeight));
const auto width = gsl::narrow_cast<u16>(std::min(limitX, wantedWidth));
assert(width != 0);
assert(height != 0);
wil::com_ptr<ID3D11Texture2D> atlasBuffer;
wil::com_ptr<ID3D11ShaderResourceView> atlasView;
{
D3D11_TEXTURE2D_DESC desc{};
desc.Width = width;
desc.Height = height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc = { 1, 0 };
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
THROW_IF_FAILED(_r.device->CreateTexture2D(&desc, nullptr, atlasBuffer.addressof()));
THROW_IF_FAILED(_r.device->CreateShaderResourceView(atlasBuffer.get(), nullptr, atlasView.addressof()));
}
// If a _r.atlasBuffer already existed, we can copy its glyphs
// over to the new texture without re-rendering everything.
const auto copyFromExisting = _r.atlasSizeInPixel != u16x2{};
if (copyFromExisting)
{
D3D11_BOX box;
box.left = 0;
box.top = 0;
box.front = 0;
box.right = _r.atlasSizeInPixel.x;
box.bottom = _r.atlasSizeInPixel.y;
box.back = 1;
_r.deviceContext->CopySubresourceRegion1(atlasBuffer.get(), 0, 0, 0, 0, _r.atlasBuffer.get(), 0, &box, D3D11_COPY_NO_OVERWRITE);
}
_r.atlasSizeInPixel = u16x2{ width, height };
_r.atlasBuffer = std::move(atlasBuffer);
_r.atlasView = std::move(atlasView);
_setShaderResources();
WI_SetFlagIf(_r.invalidations, RenderInvalidations::Cursor, !copyFromExisting);
}
void AtlasEngine::_reserveScratchpadSize(u16 minWidth)
{
if (minWidth <= _r.scratchpadCellWidth)
{
return;
}
// The new size is the greater of ... cells wide:
// * 2
// * minWidth
// * current size * 1.5
const auto newWidth = std::max<UINT>(std::max<UINT>(2, minWidth), _r.scratchpadCellWidth + (_r.scratchpadCellWidth >> 1));
_r.d2dRenderTarget.reset();
_r.atlasScratchpad.reset();
{
D3D11_TEXTURE2D_DESC desc{};
desc.Width = _r.cellSize.x * newWidth;
desc.Height = _r.cellSize.y;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc = { 1, 0 };
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
THROW_IF_FAILED(_r.device->CreateTexture2D(&desc, nullptr, _r.atlasScratchpad.put()));
}
{
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();
D2D1_RENDER_TARGET_PROPERTIES props{};
props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
props.pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED };
props.dpiX = static_cast<float>(_r.dpi);
props.dpiY = static_cast<float>(_r.dpi);
THROW_IF_FAILED(_sr.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, _r.d2dRenderTarget.put()));
// 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);
// 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 };
wil::com_ptr<ID2D1SolidColorBrush> brush;
THROW_IF_FAILED(_r.d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, brush.addressof()));
_r.brush = brush.query<ID2D1Brush>();
}
_r.scratchpadCellWidth = _r.maxEncounteredCellCount;
WI_SetAllFlags(_r.invalidations, RenderInvalidations::ConstBuffer);
}
void AtlasEngine::_processGlyphQueue()
{
if (_r.glyphQueue.empty())
{
return;
}
for (const auto& pair : _r.glyphQueue)
{
_drawGlyph(pair);
}
_r.glyphQueue.clear();
}
void AtlasEngine::_drawGlyph(const AtlasQueueItem& item) const
{
const auto key = item.key->data();
const auto value = item.value->data();
const auto coords = &value->coords[0];
const auto charsLength = key->charCount;
const auto cells = static_cast<u32>(key->attributes.cellCount);
const auto textFormat = _getTextFormat(key->attributes.bold, key->attributes.italic);
// 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 });
}
auto options = D2D1_DRAW_TEXT_OPTIONS_NONE;
// D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT enables a bunch of internal machinery
// which doesn't have to run if we know we can't use it anyways in the shader.
WI_SetFlagIf(options, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT, WI_IsFlagSet(value->flags, CellFlags::ColoredGlyph));
_r.d2dRenderTarget->BeginDraw();
// We could call
// _r.d2dRenderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED);
// now to reduce the surface that needs to be cleared, but this decreases
// performance by 10% (tested using debugGlyphGenerationPerformance).
_r.d2dRenderTarget->Clear();
_r.d2dRenderTarget->DrawTextLayout({}, textLayout.get(), _r.brush.get(), options);
THROW_IF_FAILED(_r.d2dRenderTarget->EndDraw());
for (uint32_t i = 0; i < cells; ++i)
{
// Specifying NO_OVERWRITE means that the system can assume that existing references to the surface that
// may be in flight on the GPU will not be affected by the update, so the copy can proceed immediately
// (avoiding either a batch flush or the system maintaining multiple copies of the resource behind the scenes).
//
// Since our shader only draws whatever is in the atlas, and since we don't replace glyph tiles that are in use,
// we can safely (?) tell the GPU that we don't overwrite parts of our atlas that are in use.
_copyScratchpadTile(i, coords[i], D3D11_COPY_NO_OVERWRITE);
}
}
void AtlasEngine::_drawCursor()
{
_reserveScratchpadSize(1);
// lineWidth is in D2D's DIPs. For instance if we have a 150-200% zoom scale we want to draw a 2px wide line.
// At 150% scale lineWidth thus needs to be 1.33333... because at a zoom scale of 1.5 this results in a 2px wide line.
const auto lineWidth = std::max(1.0f, static_cast<float>((_r.dpi + USER_DEFAULT_SCREEN_DPI / 2) / USER_DEFAULT_SCREEN_DPI * USER_DEFAULT_SCREEN_DPI) / static_cast<float>(_r.dpi));
const auto cursorType = static_cast<CursorType>(_r.cursorOptions.cursorType);
D2D1_RECT_F rect;
rect.left = 0.0f;
rect.top = 0.0f;
rect.right = _r.cellSizeDIP.x;
rect.bottom = _r.cellSizeDIP.y;
switch (cursorType)
{
case CursorType::Legacy:
rect.top = _r.cellSizeDIP.y * static_cast<float>(100 - _r.cursorOptions.heightPercentage) / 100.0f;
break;
case CursorType::VerticalBar:
rect.right = lineWidth;
break;
case CursorType::EmptyBox:
{
// EmptyBox is drawn as a line and unlike filled rectangles those are drawn centered on their
// coordinates in such a way that the line border extends half the width to each side.
// --> Our coordinates have to be 0.5 DIP off in order to draw a 2px line on a 200% scaling.
const auto halfWidth = lineWidth / 2.0f;
rect.left = halfWidth;
rect.top = halfWidth;
rect.right -= halfWidth;
rect.bottom -= halfWidth;
break;
}
case CursorType::Underscore:
case CursorType::DoubleUnderscore:
rect.top = _r.cellSizeDIP.y - lineWidth;
break;
default:
break;
}
_r.d2dRenderTarget->BeginDraw();
_r.d2dRenderTarget->Clear();
if (cursorType == CursorType::EmptyBox)
{
_r.d2dRenderTarget->DrawRectangle(&rect, _r.brush.get(), lineWidth);
}
else
{
_r.d2dRenderTarget->FillRectangle(&rect, _r.brush.get());
}
if (cursorType == CursorType::DoubleUnderscore)
{
rect.top -= 2.0f;
rect.bottom -= 2.0f;
_r.d2dRenderTarget->FillRectangle(&rect, _r.brush.get());
}
THROW_IF_FAILED(_r.d2dRenderTarget->EndDraw());
_copyScratchpadTile(0, {});
}
void AtlasEngine::_copyScratchpadTile(uint32_t scratchpadIndex, u16x2 target, uint32_t copyFlags) const noexcept
{
D3D11_BOX box;
box.left = scratchpadIndex * _r.cellSize.x;
box.top = 0;
box.front = 0;
box.right = box.left + _r.cellSize.x;
box.bottom = _r.cellSize.y;
box.back = 1;
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6).
_r.deviceContext->CopySubresourceRegion1(_r.atlasBuffer.get(), 0, target.x, target.y, 0, _r.atlasScratchpad.get(), 0, &box, copyFlags);
}

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>atlas</RootNamespace>
<ProjectName>RendererAtlas</ProjectName>
<TargetName>ConRenderAtlas</TargetName>
<ConfigurationType>StaticLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemGroup>
<ClCompile Include="AtlasEngine.api.cpp" />
<ClCompile Include="AtlasEngine.r.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="AtlasEngine.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="AtlasEngine.h" />
</ItemGroup>
<ItemGroup>
<FxCompile Include="shader_ps.hlsl">
<ShaderType>Pixel</ShaderType>
<ShaderModel>4.1</ShaderModel>
<AllResourcesBound>true</AllResourcesBound>
<VariableName>shader_ps</VariableName>
<ObjectFileOutput />
<HeaderFileOutput>$(OutDir)$(ProjectName)\%(Filename).h</HeaderFileOutput>
<TreatWarningAsError>true</TreatWarningAsError>
<AdditionalOptions>/Zpc %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="'$(Configuration)'=='Release'">/O3 /Qstrip_debug /Qstrip_reflect %(AdditionalOptions)</AdditionalOptions>
</FxCompile>
<FxCompile Include="shader_vs.hlsl">
<ShaderType>Vertex</ShaderType>
<ShaderModel>4.1</ShaderModel>
<AllResourcesBound>true</AllResourcesBound>
<VariableName>shader_vs</VariableName>
<ObjectFileOutput />
<HeaderFileOutput>$(OutDir)$(ProjectName)\%(Filename).h</HeaderFileOutput>
<TreatWarningAsError>true</TreatWarningAsError>
<AdditionalOptions>/Zpc %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="'$(Configuration)'=='Release'">/O3 /Qstrip_debug /Qstrip_reflect %(AdditionalOptions)</AdditionalOptions>
</FxCompile>
</ItemGroup>
<Import Project="$(SolutionDir)src\common.build.post.props" />
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(OutDir)$(ProjectName)\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
</Project>

View File

@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"

52
src/renderer/atlas/pch.h Normal file
View File

@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <array>
#include <iomanip>
#include <optional>
#include <sstream>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <d2d1.h>
#include <d3d11_1.h>
#include <d3dcompiler.h>
#include <dwrite_3.h>
#include <dcomp.h>
#include <dxgi1_3.h>
#include <dxgidebug.h>
#include <VersionHelpers.h>
#include <gsl/gsl_util>
#include <gsl/pointers>
#include <gsl/span>
#include <wil/com.h>
#include <wil/filesystem.h>
#include <wil/result_macros.h>
#include <wil/stl.h>
#include <wil/win32_helpers.h>
// Dynamic Bitset (optional dependency on LibPopCnt for perf at bit counting)
// Variable-size compressed-storage header-only bit flag storage library.
#pragma warning(push)
#pragma warning(disable : 4702) // unreachable code
#include <dynamic_bitset.hpp>
#pragma warning(pop)
// Chromium Numerics (safe math)
#pragma warning(push)
#pragma warning(disable : 4100) // '...': unreferenced formal parameter
#pragma warning(disable : 26812) // The enum type '...' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
#include <base/numerics/safe_math.h>
#pragma warning(pop)
#include <til.h>
#include <til/bit.h>

View File

@ -0,0 +1,182 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#define INVALID_COLOR 0xffffffff
// These flags are shared with AtlasEngine::CellFlags.
//
// clang-format off
#define CellFlags_None 0x00000000
#define CellFlags_Inlined 0x00000001
#define CellFlags_ColoredGlyph 0x00000002
#define CellFlags_ThinFont 0x00000004
#define CellFlags_Cursor 0x00000008
#define CellFlags_Selected 0x00000010
#define CellFlags_BorderLeft 0x00000020
#define CellFlags_BorderTop 0x00000040
#define CellFlags_BorderRight 0x00000080
#define CellFlags_BorderBottom 0x00000100
#define CellFlags_Underline 0x00000200
#define CellFlags_UnderlineDotted 0x00000400
#define CellFlags_UnderlineDouble 0x00000800
#define CellFlags_Strikethrough 0x00001000
// clang-format on
// According to Nvidia's "Understanding Structured Buffer Performance" guide
// one should aim for structures with sizes divisible by 128 bits (16 bytes).
// This prevents elements from spanning cache lines.
struct Cell
{
uint glyphPos;
uint flags;
uint2 color; // x: foreground, y: background
};
cbuffer ConstBuffer : register(b0)
{
float4 viewport;
float4 gammaRatios;
float grayscaleEnhancedContrast;
uint cellCountX;
uint2 cellSize;
uint2 underlinePos;
uint2 strikethroughPos;
uint backgroundColor;
uint cursorColor;
uint selectionColor;
};
StructuredBuffer<Cell> cells : register(t0);
Texture2D<float4> glyphs : register(t1);
float4 decodeRGBA(uint i)
{
uint r = i & 0xff;
uint g = (i >> 8) & 0xff;
uint b = (i >> 16) & 0xff;
uint a = i >> 24;
float4 c = float4(r, g, b, a) / 255.0f;
// Convert to premultiplied alpha for simpler alpha blending.
c.rgb *= c.a;
return c;
}
uint2 decodeU16x2(uint i)
{
return uint2(i & 0xffff, i >> 16);
}
float4 alphaBlendPremultiplied(float4 bottom, float4 top)
{
float ia = 1 - top.a;
return float4(bottom.rgb * ia + top.rgb, bottom.a * ia + top.a);
}
float applyLightOnDarkContrastAdjustment(float3 color)
{
float lightness = 0.30f * color.r + 0.59f * color.g + 0.11f * color.b;
float multiplier = saturate(4.0f * (0.75f - lightness));
return grayscaleEnhancedContrast * multiplier;
}
float calcColorIntensity(float3 color)
{
return (color.r + color.g + color.g + color.b) / 4.0f;
}
float enhanceContrast(float alpha, float k)
{
return alpha * (k + 1.0f) / (alpha * k + 1.0f);
}
float applyAlphaCorrection(float a, float f, float4 g)
{
return a + a * (1 - a) * ((g.x * f + g.y) * a + (g.z * f + g.w));
}
// clang-format off
float4 main(float4 pos: SV_Position): SV_Target
// clang-format on
{
if (any(pos.xy < viewport.xy) || any(pos.xy >= viewport.zw))
{
return decodeRGBA(backgroundColor);
}
// If you want to write test a before/after change simultaneously
// you can turn the image into a checkerboard by writing:
// if ((uint(pos.x) ^ uint(pos.y)) / 4 & 1) { return float4(1, 0, 0, 1); }
// This will generate a checkerboard of 4*4px red squares.
// Of course you wouldn't just return a red color there, but instead
// for instance run your new code and compare it with the old.
uint2 viewportPos = pos.xy - viewport.xy;
uint2 cellIndex = viewportPos / cellSize;
uint2 cellPos = viewportPos % cellSize;
Cell cell = cells[cellIndex.y * cellCountX + cellIndex.x];
// Layer 0:
// The cell's background color
float4 color = decodeRGBA(cell.color.y);
float4 fg = decodeRGBA(cell.color.x);
// Layer 1 (optional):
// Colored cursors are drawn "in between" the background color and the text of a cell.
if ((cell.flags & CellFlags_Cursor) && cursorColor != INVALID_COLOR)
{
// The cursor texture is stored at the top-left-most glyph cell.
// Cursor pixels are either entirely transparent or opaque.
// --> We can just use .a as a mask to flip cursor pixels on or off.
color = alphaBlendPremultiplied(color, decodeRGBA(cursorColor) * glyphs[cellPos].a);
}
// Layer 2:
// Step 1: Underlines
if ((cell.flags & CellFlags_Underline) && cellPos.y >= underlinePos.x && cellPos.y < underlinePos.y)
{
color = alphaBlendPremultiplied(color, fg);
}
if ((cell.flags & CellFlags_UnderlineDotted) && cellPos.y >= underlinePos.x && cellPos.y < underlinePos.y && (viewportPos.x / (underlinePos.y - underlinePos.x) & 1))
{
color = alphaBlendPremultiplied(color, fg);
}
// Step 2: The cell's glyph, potentially drawn in the foreground color
{
float4 glyph = glyphs[decodeU16x2(cell.glyphPos) + cellPos];
if (!(cell.flags & CellFlags_ColoredGlyph))
{
float contrastBoost = (cell.flags & CellFlags_ThinFont) == 0 ? 0.0f : 0.5f;
float enhancedContrast = contrastBoost + applyLightOnDarkContrastAdjustment(fg.rgb);
float intensity = calcColorIntensity(fg.rgb);
float contrasted = enhanceContrast(glyph.a, enhancedContrast);
float correctedAlpha = applyAlphaCorrection(contrasted, intensity, gammaRatios);
glyph = fg * correctedAlpha;
}
color = alphaBlendPremultiplied(color, glyph);
}
// Step 3: Lines, but not "under"lines
if ((cell.flags & CellFlags_Strikethrough) && cellPos.y >= strikethroughPos.x && cellPos.y < strikethroughPos.y)
{
color = alphaBlendPremultiplied(color, fg);
}
// Layer 3 (optional):
// Uncolored cursors invert the cells color.
if ((cell.flags & CellFlags_Cursor) && cursorColor == INVALID_COLOR)
{
color.rgb = abs(glyphs[cellPos].rgb - color.rgb);
}
// Layer 4:
// The current selection is drawn semi-transparent on top.
if (cell.flags & CellFlags_Selected)
{
color = alphaBlendPremultiplied(color, decodeRGBA(selectionColor));
}
return color;
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// clang-format off
float4 main(uint id: SV_VERTEXID): SV_POSITION
// clang-format on
{
// The algorithm below is a fast way to generate a full screen triangle,
// published by Bill Bilodeau "Vertex Shader Tricks" at GDC14.
// It covers the entire viewport and is faster for the GPU than a quad/rectangle.
return float4(
float(id / 2) * 4.0 - 1.0,
float(id % 2) * 4.0 - 1.0,
0.0,
1.0
);
}

View File

@ -74,5 +74,6 @@ HRESULT RenderEngineBase::PrepareLineTransform(const LineRendition /*lineRenditi
// - Blocks until the engine is able to render without blocking.
void RenderEngineBase::WaitUntilCanRender() noexcept
{
// do nothing by default
// Throttle the render loop a bit by default (~60 FPS), improving throughput.
Sleep(8);
}

View File

@ -213,12 +213,6 @@ DWORD WINAPI RenderThread::_ThreadProc()
LOG_IF_FAILED(_pRenderer->PaintFrame());
SetEvent(_hPaintCompletedEvent);
// extra check before we sleep since it's a "long" activity, relatively speaking.
if (_fKeepRunning)
{
Sleep(s_FrameLimitMilliseconds);
}
}
return S_OK;

View File

@ -37,8 +37,6 @@ namespace Microsoft::Console::Render
static DWORD WINAPI s_ThreadProc(_In_ LPVOID lpParameter);
DWORD WINAPI _ThreadProc();
static DWORD const s_FrameLimitMilliseconds = 8;
HANDLE _hThread;
HANDLE _hEvent;

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
@ -404,9 +404,9 @@ CATCH_RETURN()
std::vector<DWRITE_SHAPING_GLYPH_PROPERTIES> glyphProps(maxGlyphCount);
// Get the features to apply to the font
auto features = _fontRenderData->DefaultFontFeatures();
DWRITE_FONT_FEATURE* featureList = features.data();
DWRITE_TYPOGRAPHIC_FEATURES typographicFeatures = { &featureList[0], gsl::narrow<uint32_t>(features.size()) };
const auto& features = _fontRenderData->DefaultFontFeatures();
#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3).
DWRITE_TYPOGRAPHIC_FEATURES typographicFeatures = { const_cast<DWRITE_FONT_FEATURE*>(features.data()), gsl::narrow<uint32_t>(features.size()) };
DWRITE_TYPOGRAPHIC_FEATURES const* typographicFeaturesPointer = &typographicFeatures;
const uint32_t fontFeatureLengths[] = { textLength };

View File

@ -487,6 +487,7 @@ CATCH_RETURN()
{
// Color glyph rendering sourced from https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/DWriteColorGlyph
#pragma warning(suppress : 26429) // Symbol 'drawingContext' is never tested for nullness, it can be marked as not_null (f.23).
DrawingContext* drawingContext = static_cast<DrawingContext*>(clientDrawingContext);
// Since we've delegated the drawing of the background of the text into this function, the origin passed in isn't actually the baseline.

View File

@ -244,10 +244,11 @@ bool DxEngine::_HasTerminalEffects() const noexcept
// Arguments:
// Return Value:
// - Void
void DxEngine::ToggleShaderEffects()
void DxEngine::ToggleShaderEffects() noexcept
{
_terminalEffectsEnabled = !_terminalEffectsEnabled;
_recreateDeviceRequested = true;
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function 'Log_IfFailed()' which may throw exceptions (f.6).
LOG_IF_FAILED(InvalidateAll());
}
@ -969,14 +970,14 @@ try
}
CATCH_RETURN();
void DxEngine::SetCallback(std::function<void()> pfn)
void DxEngine::SetCallback(std::function<void()> pfn) noexcept
{
_pfn = pfn;
_pfn = std::move(pfn);
}
void DxEngine::SetWarningCallback(std::function<void(const HRESULT)> pfn)
void DxEngine::SetWarningCallback(std::function<void(const HRESULT)> pfn) noexcept
{
_pfnWarningCallback = pfn;
_pfnWarningCallback = std::move(pfn);
}
bool DxEngine::GetRetroTerminalEffect() const noexcept
@ -1046,11 +1047,12 @@ try
}
CATCH_LOG()
HANDLE DxEngine::GetSwapChainHandle()
HANDLE DxEngine::GetSwapChainHandle() noexcept
{
if (!_swapChainHandle)
{
THROW_IF_FAILED(_CreateDeviceResources(true));
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function 'Log_IfFailed()' which may throw exceptions (f.6).
LOG_IF_FAILED(_CreateDeviceResources(true));
}
return _swapChainHandle.get();
@ -1498,18 +1500,13 @@ CATCH_RETURN()
// - See https://docs.microsoft.com/en-us/windows/uwp/gaming/reduce-latency-with-dxgi-1-3-swap-chains.
void DxEngine::WaitUntilCanRender() noexcept
{
if (!_swapChainFrameLatencyWaitableObject)
{
return;
}
// Throttle the DxEngine a bit down to ~60 FPS.
// This improves throughput for rendering complex or colored text.
Sleep(8);
const auto ret = WaitForSingleObjectEx(
_swapChainFrameLatencyWaitableObject.get(),
1000, // 1 second timeout (shouldn't ever occur)
true);
if (ret != WAIT_OBJECT_0)
if (_swapChainFrameLatencyWaitableObject)
{
LOG_WIN32_MSG(ret, "Waiting for swap chain frame latency waitable object returned error or timeout.");
WaitForSingleObjectEx(_swapChainFrameLatencyWaitableObject.get(), 100, true);
}
}
@ -2023,7 +2020,7 @@ try
}
CATCH_RETURN();
[[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) noexcept
[[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) const noexcept
{
const short widthInChars = base::saturated_cast<short>(viewInPixels.Width() / _fontRenderData->GlyphCell().width());
const short heightInChars = base::saturated_cast<short>(viewInPixels.Height() / _fontRenderData->GlyphCell().height());
@ -2031,7 +2028,7 @@ CATCH_RETURN();
return Viewport::FromDimensions(viewInPixels.Origin(), { widthInChars, heightInChars });
}
[[nodiscard]] Viewport DxEngine::GetViewportInPixels(const Viewport& viewInCharacters) noexcept
[[nodiscard]] Viewport DxEngine::GetViewportInPixels(const Viewport& viewInCharacters) const noexcept
{
const short widthInPixels = base::saturated_cast<short>(viewInCharacters.Width() * _fontRenderData->GlyphCell().width());
const short heightInPixels = base::saturated_cast<short>(viewInCharacters.Height() * _fontRenderData->GlyphCell().height());

View File

@ -49,28 +49,28 @@ namespace Microsoft::Console::Render
// Used to release device resources so that another instance of
// conhost can render to the screen (i.e. only one DirectX
// application may control the screen at a time.)
[[nodiscard]] HRESULT Enable() noexcept;
[[nodiscard]] HRESULT Enable() noexcept override;
[[nodiscard]] HRESULT Disable() noexcept;
[[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept;
[[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept override;
[[nodiscard]] HRESULT SetWindowSize(const SIZE pixels) noexcept;
[[nodiscard]] HRESULT SetWindowSize(const SIZE pixels) noexcept override;
void SetCallback(std::function<void()> pfn);
void SetWarningCallback(std::function<void(const HRESULT)> pfn);
void SetCallback(std::function<void()> pfn) noexcept override;
void SetWarningCallback(std::function<void(const HRESULT)> pfn) noexcept override;
void ToggleShaderEffects();
void ToggleShaderEffects() noexcept override;
bool GetRetroTerminalEffect() const noexcept;
void SetRetroTerminalEffect(bool enable) noexcept;
bool GetRetroTerminalEffect() const noexcept override;
void SetRetroTerminalEffect(bool enable) noexcept override;
void SetPixelShaderPath(std::wstring_view value) noexcept;
void SetPixelShaderPath(std::wstring_view value) noexcept override;
void SetForceFullRepaintRendering(bool enable) noexcept;
void SetForceFullRepaintRendering(bool enable) noexcept override;
void SetSoftwareRendering(bool enable) noexcept;
void SetSoftwareRendering(bool enable) noexcept override;
HANDLE GetSwapChainHandle();
HANDLE GetSwapChainHandle() noexcept override;
// IRenderEngine Members
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
@ -110,7 +110,7 @@ namespace Microsoft::Console::Render
const bool usingSoftFont,
const bool isSettingDefaultBrushes) noexcept override;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo) noexcept override;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept override;
[[nodiscard]] HRESULT UpdateDpi(int const iDpi) noexcept override;
[[nodiscard]] HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept override;
@ -121,17 +121,17 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
[[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInCharacters(const ::Microsoft::Console::Types::Viewport& viewInPixels) noexcept;
[[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInPixels(const ::Microsoft::Console::Types::Viewport& viewInCharacters) noexcept;
[[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInCharacters(const ::Microsoft::Console::Types::Viewport& viewInPixels) const noexcept override;
[[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInPixels(const ::Microsoft::Console::Types::Viewport& viewInCharacters) const noexcept override;
float GetScaling() const noexcept;
float GetScaling() const noexcept override;
void SetSelectionBackground(const COLORREF color, const float alpha = 0.5f) noexcept;
void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept;
void SetDefaultTextBackgroundOpacity(const float opacity) noexcept;
void SetIntenseIsBold(const bool opacity) noexcept;
void SetSelectionBackground(const COLORREF color, const float alpha = 0.5f) noexcept override;
void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept override;
void SetDefaultTextBackgroundOpacity(const float opacity) noexcept override;
void SetIntenseIsBold(const bool opacity) noexcept override;
void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept;
void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept override;
protected:
[[nodiscard]] HRESULT _DoUpdateTitle(_In_ const std::wstring_view newTitle) noexcept override;

View File

@ -14,12 +14,16 @@ Author(s):
#pragma once
#include <d2d1.h>
#include "CursorOptions.h"
#include "Cluster.hpp"
#include "FontInfoDesired.hpp"
#include "IRenderData.hpp"
#include "../../buffer/out/LineRendition.hpp"
#pragma warning(push)
#pragma warning(disable : 4100) // '...': unreferenced formal parameter
namespace Microsoft::Console::Render
{
struct RenderFrameInfo
@ -44,78 +48,75 @@ namespace Microsoft::Console::Render
};
using GridLineSet = til::enumset<GridLines>;
virtual ~IRenderEngine() = 0;
#pragma warning(suppress : 26432) // If you define or delete any default operation in the type '...', define or delete them all (c.21).
virtual ~IRenderEngine()
{
}
protected:
IRenderEngine() = default;
IRenderEngine(const IRenderEngine&) = default;
IRenderEngine(IRenderEngine&&) = default;
IRenderEngine& operator=(const IRenderEngine&) = default;
IRenderEngine& operator=(IRenderEngine&&) = default;
public:
[[nodiscard]] virtual HRESULT StartPaint() noexcept = 0;
[[nodiscard]] virtual HRESULT EndPaint() noexcept = 0;
[[nodiscard]] virtual bool RequiresContinuousRedraw() noexcept = 0;
virtual void WaitUntilCanRender() noexcept = 0;
[[nodiscard]] virtual HRESULT Present() noexcept = 0;
[[nodiscard]] virtual HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept = 0;
[[nodiscard]] virtual HRESULT PrepareForTeardown(_Out_ bool* pForcePaint) noexcept = 0;
[[nodiscard]] virtual HRESULT ScrollFrame() noexcept = 0;
[[nodiscard]] virtual HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept = 0;
[[nodiscard]] virtual HRESULT Invalidate(const SMALL_RECT* psrRegion) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateCursor(const SMALL_RECT* psrRegion) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateSystem(const RECT* prcDirtyClient) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* pcoordDelta) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateAll() noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateTitle(const std::wstring_view proposedTitle) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateCircling(_Out_ bool* pForcePaint) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateTitle(std::wstring_view proposedTitle) noexcept = 0;
[[nodiscard]] virtual HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept = 0;
[[nodiscard]] virtual HRESULT ResetLineTransform() noexcept = 0;
[[nodiscard]] virtual HRESULT PrepareLineTransform(const LineRendition lineRendition,
const size_t targetRow,
const size_t viewportLeft) noexcept = 0;
[[nodiscard]] virtual HRESULT PrepareLineTransform(LineRendition lineRendition, size_t targetRow, size_t viewportLeft) noexcept = 0;
[[nodiscard]] virtual HRESULT PaintBackground() noexcept = 0;
[[nodiscard]] virtual HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters,
const COORD coord,
const bool fTrimLeft,
const bool lineWrapped) noexcept = 0;
[[nodiscard]] virtual HRESULT PaintBufferGridLines(const GridLineSet lines,
const COLORREF color,
const size_t cchLine,
const COORD coordTarget) noexcept = 0;
[[nodiscard]] virtual HRESULT PaintSelection(const SMALL_RECT rect) noexcept = 0;
[[nodiscard]] virtual HRESULT PaintBufferLine(gsl::span<const Cluster> clusters, COORD coord, bool fTrimLeft, bool lineWrapped) noexcept = 0;
[[nodiscard]] virtual HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, COORD coordTarget) noexcept = 0;
[[nodiscard]] virtual HRESULT PaintSelection(SMALL_RECT rect) noexcept = 0;
[[nodiscard]] virtual HRESULT PaintCursor(const CursorOptions& options) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes,
const gsl::not_null<IRenderData*> pData,
const bool usingSoftFont,
const bool isSettingDefaultBrushes) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired,
_Out_ FontInfo& FontInfo) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateSoftFont(const gsl::span<const uint16_t> bitPattern,
const SIZE cellSize,
const size_t centeringHint) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateDpi(const int iDpi) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept = 0;
[[nodiscard]] virtual HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired,
_Out_ FontInfo& FontInfo,
const int iDpi) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, gsl::not_null<IRenderData*> pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateSoftFont(gsl::span<const uint16_t> bitPattern, SIZE cellSize, size_t centeringHint) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateDpi(int iDpi) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateViewport(SMALL_RECT srNewViewport) noexcept = 0;
[[nodiscard]] virtual HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, int iDpi) noexcept = 0;
[[nodiscard]] virtual HRESULT GetDirtyArea(gsl::span<const til::rectangle>& area) noexcept = 0;
[[nodiscard]] virtual HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept = 0;
[[nodiscard]] virtual HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateTitle(const std::wstring_view newTitle) noexcept = 0;
};
[[nodiscard]] virtual HRESULT GetFontSize(_Out_ COORD* pFontSize) noexcept = 0;
[[nodiscard]] virtual HRESULT IsGlyphWideByFont(std::wstring_view glyph, _Out_ bool* pResult) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateTitle(std::wstring_view newTitle) noexcept = 0;
inline Microsoft::Console::Render::IRenderEngine::~IRenderEngine() {}
// The following functions used to be specific to the DxRenderer and they should
// be abstracted away and integrated into the above or simply get removed.
// DxRenderer - getter
virtual HRESULT Enable() noexcept { return S_OK; }
virtual [[nodiscard]] bool GetRetroTerminalEffect() const noexcept { return false; }
virtual [[nodiscard]] float GetScaling() const noexcept { return 1; }
#pragma warning(suppress : 26440) // Function '...' can be declared 'noexcept' (f.6).
virtual [[nodiscard]] HANDLE GetSwapChainHandle()
{
return nullptr;
}
virtual [[nodiscard]] Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept { return Types::Viewport::Empty(); }
virtual [[nodiscard]] Types::Viewport GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept { return Types::Viewport::Empty(); }
// DxRenderer - setter
virtual void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept {}
virtual void SetCallback(std::function<void()> pfn) noexcept {}
virtual void SetDefaultTextBackgroundOpacity(const float opacity) noexcept {}
virtual void SetForceFullRepaintRendering(bool enable) noexcept {}
virtual [[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept { return E_NOTIMPL; }
virtual void SetPixelShaderPath(std::wstring_view value) noexcept {}
virtual void SetRetroTerminalEffect(bool enable) noexcept {}
virtual void SetSelectionBackground(const COLORREF color, const float alpha = 0.5f) noexcept {}
virtual void SetSoftwareRendering(bool enable) noexcept {}
virtual void SetIntenseIsBold(bool enable) noexcept {}
virtual void SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept {}
virtual [[nodiscard]] HRESULT SetWindowSize(const SIZE pixels) noexcept { return E_NOTIMPL; }
virtual void ToggleShaderEffects() noexcept {}
virtual [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept { return E_NOTIMPL; }
virtual void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept {}
};
}
#pragma warning(pop)

View File

@ -261,6 +261,13 @@ CATCH_RETURN();
return S_OK;
}
// RenderEngineBase defines a WaitUntilCanRender() that sleeps for 8ms to throttle rendering.
// But UiaEngine is never the only engine running. Overriding this function prevents
// us from sleeping 16ms per frame, when the other engine also sleeps for 8ms.
void UiaEngine::WaitUntilCanRender() noexcept
{
}
// Routine Description:
// - Used to perform longer running presentation steps outside the lock so the
// other threads can continue.

View File

@ -30,18 +30,16 @@ namespace Microsoft::Console::Render
// Only one UiaEngine may present information at a time.
// This ensures that an automation client isn't overwhelmed
// by events when there are multiple TermControls
[[nodiscard]] HRESULT Enable() noexcept;
[[nodiscard]] HRESULT Enable() noexcept override;
[[nodiscard]] HRESULT Disable() noexcept;
// IRenderEngine Members
[[nodiscard]] HRESULT StartPaint() noexcept override;
[[nodiscard]] HRESULT EndPaint() noexcept override;
void WaitUntilCanRender() noexcept override;
[[nodiscard]] HRESULT Present() noexcept override;
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
@ -49,27 +47,16 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override;
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters,
COORD const coord,
bool const fTrimLeft,
const bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters, const COORD coord, const bool fTrimLeft, const bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const COORD coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override;
[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;
[[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes,
const gsl::not_null<IRenderData*> pData,
const bool usingSoftFont,
const bool isSettingDefaultBrushes) noexcept override;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo) noexcept override;
[[nodiscard]] HRESULT UpdateDpi(int const iDpi) noexcept override;
[[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const gsl::not_null<IRenderData*> pData, const bool usingSoftFont, const bool isSettingDefaultBrushes) noexcept override;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override;
[[nodiscard]] HRESULT UpdateDpi(const int iDpi) noexcept override;
[[nodiscard]] HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept override;
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, const int iDpi) noexcept override;
[[nodiscard]] HRESULT GetDirtyArea(gsl::span<const til::rectangle>& area) noexcept override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;

View File

@ -43,70 +43,41 @@ namespace Microsoft::Console::Render
VtEngine(_In_ wil::unique_hfile hPipe,
const Microsoft::Console::Types::Viewport initialViewport);
virtual ~VtEngine() override = default;
// IRenderEngine
[[nodiscard]] HRESULT StartPaint() noexcept override;
[[nodiscard]] HRESULT EndPaint() noexcept override;
[[nodiscard]] HRESULT Present() noexcept override;
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* pForcePaint) noexcept override;
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateSystem(const RECT* prcDirtyClient) noexcept override;
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
[[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept = 0;
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override;
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;
[[nodiscard]] virtual HRESULT StartPaint() noexcept override;
[[nodiscard]] virtual HRESULT EndPaint() noexcept override;
[[nodiscard]] virtual HRESULT Present() noexcept override;
[[nodiscard]] virtual HRESULT ScrollFrame() noexcept = 0;
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* pForcePaint) noexcept override;
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] virtual HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters,
const COORD coord,
const bool trimLeft,
const bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines,
const COLORREF color,
const size_t cchLine,
const COORD coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override;
[[nodiscard]] virtual HRESULT PaintCursor(const CursorOptions& options) noexcept override;
[[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes,
const gsl::not_null<IRenderData*> pData,
const bool usingSoftFont,
const bool isSettingDefaultBrushes) noexcept = 0;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired,
_Out_ FontInfo& pfiFontInfo) noexcept override;
[[nodiscard]] HRESULT UpdateDpi(const int iDpi) noexcept override;
[[nodiscard]] HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept override;
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontDesired,
_Out_ FontInfo& Font,
const int iDpi) noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> clusters, COORD coord, bool fTrimLeft, bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, COORD coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintSelection(SMALL_RECT rect) noexcept override;
[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override;
[[nodiscard]] HRESULT UpdateDpi(int iDpi) noexcept override;
[[nodiscard]] HRESULT UpdateViewport(SMALL_RECT srNewViewport) noexcept override;
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, int iDpi) noexcept override;
[[nodiscard]] HRESULT GetDirtyArea(gsl::span<const til::rectangle>& area) noexcept override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(std::wstring_view glyph, _Out_ bool* pResult) noexcept override;
// VtEngine
[[nodiscard]] HRESULT SuppressResizeRepaint() noexcept;
[[nodiscard]] HRESULT RequestCursor() noexcept;
[[nodiscard]] HRESULT InheritCursor(const COORD coordCursor) noexcept;
[[nodiscard]] HRESULT WriteTerminalUtf8(const std::string_view str) noexcept;
[[nodiscard]] virtual HRESULT WriteTerminalW(const std::wstring_view str) noexcept = 0;
void SetTerminalOwner(Microsoft::Console::ITerminalOwner* const terminalOwner);
void BeginResizeRequest();
void EndResizeRequest();
void SetResizeQuirk(const bool resizeQuirk);
[[nodiscard]] virtual HRESULT ManuallyClearScrollback() noexcept;
[[nodiscard]] HRESULT RequestWin32Input() noexcept;
protected:

View File

@ -19,7 +19,7 @@ namespace Microsoft::Console::Render
// Used to release device resources so that another instance of
// conhost can render to the screen (i.e. only one DirectX
// application may control the screen at a time.)
[[nodiscard]] HRESULT Enable() noexcept;
[[nodiscard]] HRESULT Enable() noexcept override;
[[nodiscard]] HRESULT Disable() noexcept;
RECT GetDisplaySize();