Introduce AtlasEngine - A new text rendering prototype (#11623)
This commit introduces "AtlasEngine", a new text renderer based on DxEngine. But unlike it, DirectWrite and Direct2D are only used to rasterize glyphs. Blending and placing these glyphs into the target view is being done using Direct3D and a simple HLSL shader. Since this new renderer more aggressively assumes that the text is monospace, it simplifies the implementation: The viewport is divided into cells, and its data is stored as a simple matrix. Modifications to this matrix involve only simple pointer arithmetic and is easy to understand. But just like with DxEngine however, DirectWrite related code remains extremely complex and hard to understand. Supported features: * Basic text rendering with grayscale AA * Foreground and background colors * Emojis, including zero width joiners * Underline, dotted underline, strikethrough * Custom font axes and features * Selections * All cursor styles * Full alpha support for all colors * _Should_ work with Windows 7 Unsupported features: * A more conservative GPU memory usage The backing texture atlas for glyphs is grow-only and will not shrink. After 256MB of memory is used up (~20k glyphs) text output will be broken until the renderer is restarted. * ClearType * Remaining gridlines (left, right, top, bottom, double underline) * Hyperlinks don't get full underlines if hovered in WT * Softfonts * Non-default line renditions Performance: * Runs at up to native display refresh rate Unfortunately the frame rate often drops below refresh rate, due us fighting over the buffer lock with other parts of the application. * CPU consumption is up to halved compared to DxEngine AtlasEngine is still highly unoptimized. Glyph hashing consumes up to a third of the current CPU time. * No regressions in WT performance VT parsing and related buffer management takes up most of the CPU time (~85%), due to which the AtlasEngine can't show any further improvements. * ~2x improvement in raw text throughput in OpenConsole compared to DxEngine running at 144 FPS * ≥10x improvement in colored VT output in WT/OpenConsole compared to DxEngine running at 144 FPS
This commit is contained in:
parent
f6965aeb53
commit
2353349fe5
|
@ -1,12 +1,13 @@
|
|||
apc
|
||||
Apc
|
||||
bsd
|
||||
calt
|
||||
ccmp
|
||||
changelog
|
||||
cybersecurity
|
||||
Apc
|
||||
clickable
|
||||
clig
|
||||
copyable
|
||||
cybersecurity
|
||||
dalet
|
||||
dcs
|
||||
Dcs
|
||||
|
@ -34,17 +35,17 @@ It'd
|
|||
kje
|
||||
liga
|
||||
lje
|
||||
locl
|
||||
lorem
|
||||
Llast
|
||||
Lmid
|
||||
locl
|
||||
lorem
|
||||
Lorigin
|
||||
maxed
|
||||
mkmk
|
||||
mnt
|
||||
mru
|
||||
noreply
|
||||
nje
|
||||
noreply
|
||||
ogonek
|
||||
ok'd
|
||||
overlined
|
||||
|
|
|
@ -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$
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "cppwinrt_utils.h"
|
||||
|
||||
#include "ControlCore.h"
|
||||
#include "../../renderer/uia/UiaRenderer.hpp"
|
||||
|
||||
namespace ControlUnitTests
|
||||
{
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace Microsoft.Terminal.Control
|
|||
|
||||
Boolean UseAcrylic;
|
||||
ScrollbarState ScrollState;
|
||||
|
||||
Boolean UseAtlasEngine;
|
||||
String FontFace;
|
||||
Int32 FontSize;
|
||||
Windows.UI.Text.FontWeight FontWeight;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -298,6 +298,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
_SuppressApplicationTitle = profile.SuppressApplicationTitle();
|
||||
}
|
||||
|
||||
_UseAtlasEngine = profile.UseAtlasEngine();
|
||||
_ScrollState = profile.ScrollState();
|
||||
|
||||
_AntialiasingMode = profile.AntialiasingMode();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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."))
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))));
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
@ -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
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue