Merge remote-tracking branch 'origin/main' into dev/migrie/f/non-terminal-content-elevation-warning

This commit is contained in:
Mike Griese 2021-11-15 10:39:36 -06:00
commit fd72b7992e
56 changed files with 3966 additions and 300 deletions

View file

@ -1,13 +1,14 @@
admins
apc
Apc
bsd
calt
ccmp
changelog
cybersecurity
Apc
clickable
clig
copyable
cybersecurity
dalet
dcs
Dcs
@ -35,17 +36,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();