From 326b9315356b0cf57609d9c17cf3d2cee1ab6f74 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 30 Jan 2024 18:13:15 +0100 Subject: [PATCH 1/2] Remove DxEngine --- OpenConsole.sln | 36 - doc/cascadia/profiles.schema.json | 5 - src/cascadia/TerminalControl/ControlCore.cpp | 19 +- src/cascadia/TerminalControl/ControlCore.h | 2 +- src/cascadia/TerminalControl/HwndTerminal.cpp | 49 +- src/cascadia/TerminalControl/HwndTerminal.hpp | 31 +- .../TerminalControl/IControlSettings.idl | 2 - src/cascadia/TerminalControl/TermControl.cpp | 21 +- src/cascadia/TerminalControl/TermControl.h | 1 - .../TerminalControlLib.vcxproj | 3 +- .../TerminalCore/lib/terminalcore-lib.vcxproj | 3 - .../TerminalSettingsEditor/ProfileViewModel.h | 1 - .../ProfileViewModel.idl | 1 - .../Profiles_Advanced.xaml | 9 - .../TerminalSettingsEditor/Rendering.xaml | 6 - .../RenderingViewModel.h | 1 - .../RenderingViewModel.idl | 1 - .../Resources/en-US/Resources.resw | 4 - .../TerminalSettingsModel/MTSMSettings.h | 1 - .../TerminalSettingsModel/Profile.idl | 1 - .../TerminalSettings.cpp | 1 - .../TerminalSettingsModel/TerminalSettings.h | 1 - .../Control.UnitTests.vcxproj | 1 - .../UnitTests_TerminalCore/ScrollTest.cpp | 12 +- .../TilWinRtHelpersTests.cpp | 1 - src/cascadia/inc/ControlProperties.h | 1 - src/features.xml | 18 - src/host/exe/Host.EXE.vcxproj | 3 - src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj | 3 - src/host/settings.cpp | 4 +- src/host/settings.hpp | 11 +- src/host/ut_host/Host.UnitTests.vcxproj | 3 - src/host/ut_host/VtIoTests.cpp | 39 - src/interactivity/win32/lib/win32.LIB.vcxproj | 3 - .../Interactivity.Win32.UnitTests.vcxproj | 3 - src/interactivity/win32/window.cpp | 44 +- src/interactivity/win32/window.hpp | 4 - src/propslib/RegistrySerialization.cpp | 2 +- src/renderer/atlas/README.md | 2 - src/renderer/dx/BoxDrawingEffect.cpp | 30 - src/renderer/dx/BoxDrawingEffect.h | 30 - src/renderer/dx/CustomTextLayout.cpp | 1757 ------------ src/renderer/dx/CustomTextLayout.h | 216 -- src/renderer/dx/CustomTextRenderer.cpp | 979 ------- src/renderer/dx/CustomTextRenderer.h | 153 -- src/renderer/dx/DxFontInfo.cpp | 313 --- src/renderer/dx/DxFontInfo.h | 72 - src/renderer/dx/DxFontRenderData.cpp | 923 ------- src/renderer/dx/DxFontRenderData.h | 142 - src/renderer/dx/DxRenderer.cpp | 2396 ----------------- src/renderer/dx/DxRenderer.hpp | 327 --- src/renderer/dx/DxSoftFont.cpp | 245 -- src/renderer/dx/DxSoftFont.h | 56 - src/renderer/dx/IBoxDrawingEffect.idl | 20 - src/renderer/dx/ScreenPixelShader.h | 91 - src/renderer/dx/ScreenVertexShader.h | 20 - src/renderer/dx/dirs | 2 - src/renderer/dx/lib/dx.vcxproj | 48 - src/renderer/dx/lib/dx.vcxproj.filters | 31 - src/renderer/dx/lib/sources | 8 - src/renderer/dx/precomp.cpp | 4 - src/renderer/dx/precomp.h | 41 - src/renderer/dx/sources.inc | 42 - .../dx/ut_dx/CustomTextLayoutTests.cpp | 100 - src/renderer/dx/ut_dx/DefaultResource.rc | 12 - src/renderer/dx/ut_dx/Dx.Unit.Tests.vcxproj | 42 - src/renderer/dx/ut_dx/product.pbxproj | 4 - src/renderer/dx/ut_dx/sources | 36 - 68 files changed, 76 insertions(+), 8417 deletions(-) delete mode 100644 src/renderer/dx/BoxDrawingEffect.cpp delete mode 100644 src/renderer/dx/BoxDrawingEffect.h delete mode 100644 src/renderer/dx/CustomTextLayout.cpp delete mode 100644 src/renderer/dx/CustomTextLayout.h delete mode 100644 src/renderer/dx/CustomTextRenderer.cpp delete mode 100644 src/renderer/dx/CustomTextRenderer.h delete mode 100644 src/renderer/dx/DxFontInfo.cpp delete mode 100644 src/renderer/dx/DxFontInfo.h delete mode 100644 src/renderer/dx/DxFontRenderData.cpp delete mode 100644 src/renderer/dx/DxFontRenderData.h delete mode 100644 src/renderer/dx/DxRenderer.cpp delete mode 100644 src/renderer/dx/DxRenderer.hpp delete mode 100644 src/renderer/dx/DxSoftFont.cpp delete mode 100644 src/renderer/dx/DxSoftFont.h delete mode 100644 src/renderer/dx/IBoxDrawingEffect.idl delete mode 100644 src/renderer/dx/ScreenPixelShader.h delete mode 100644 src/renderer/dx/ScreenVertexShader.h delete mode 100644 src/renderer/dx/dirs delete mode 100644 src/renderer/dx/lib/dx.vcxproj delete mode 100644 src/renderer/dx/lib/dx.vcxproj.filters delete mode 100644 src/renderer/dx/lib/sources delete mode 100644 src/renderer/dx/precomp.cpp delete mode 100644 src/renderer/dx/precomp.h delete mode 100644 src/renderer/dx/sources.inc delete mode 100644 src/renderer/dx/ut_dx/CustomTextLayoutTests.cpp delete mode 100644 src/renderer/dx/ut_dx/DefaultResource.rc delete mode 100644 src/renderer/dx/ut_dx/Dx.Unit.Tests.vcxproj delete mode 100644 src/renderer/dx/ut_dx/product.pbxproj delete mode 100644 src/renderer/dx/ut_dx/sources diff --git a/OpenConsole.sln b/OpenConsole.sln index 5c2b55ccd4b..61799479fb0 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -161,8 +161,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererVt.unittest", "src\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BufferOut", "src\buffer\out\lib\bufferout.vcxproj", "{0CF235BD-2DA0-407E-90EE-C467E8BBC714}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererDx", "src\renderer\dx\lib\dx.vcxproj", "{48D21369-3D7B-4431-9967-24E81292CF62}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalConnection", "src\cascadia\TerminalConnection\TerminalConnection.vcxproj", "{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}" ProjectSection(ProjectDependencies) = postProject {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF} = {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF} @@ -173,7 +171,6 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Control.Lib", "src\cascadia\TerminalControl\TerminalControlLib.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}" ProjectSection(ProjectDependencies) = postProject {1CF55140-EF6A-4736-A403-957E4F7430BB} = {1CF55140-EF6A-4736-A403-957E4F7430BB} - {48D21369-3D7B-4431-9967-24E81292CF62} = {48D21369-3D7B-4431-9967-24E81292CF62} {48D21369-3D7B-4431-9967-24E81292CF63} = {48D21369-3D7B-4431-9967-24E81292CF63} {8222900C-8B6C-452A-91AC-BE95DB04B95F} = {8222900C-8B6C-452A-91AC-BE95DB04B95F} {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F} = {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F} @@ -319,8 +316,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{D3EF build\scripts\Test-WindowsTerminalPackage.ps1 = build\scripts\Test-WindowsTerminalPackage.ps1 EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dx.Unit.Tests", "src\renderer\dx\ut_dx\Dx.Unit.Tests.vcxproj", "{95B136F9-B238-490C-A7C5-5843C1FECAC4}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winconpty.Tests.Feature", "src\winconpty\ft_pty\winconpty.FeatureTests.vcxproj", "{024052DE-83FB-4653-AEA4-90790D29D5BD}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAzBridge", "src\cascadia\TerminalAzBridge\TerminalAzBridge.vcxproj", "{067F0A06-FCB7-472C-96E9-B03B54E8E18D}" @@ -1412,36 +1407,6 @@ Global {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|x64.Build.0 = Release|x64 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|x86.ActiveCfg = Release|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|x86.Build.0 = Release|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|x64.ActiveCfg = AuditMode|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|x64.Build.0 = AuditMode|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|x86.ActiveCfg = AuditMode|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|x86.Build.0 = AuditMode|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|ARM.ActiveCfg = Debug|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|ARM64.Build.0 = Debug|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|x64.ActiveCfg = Debug|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|x64.Build.0 = Debug|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|x86.ActiveCfg = Debug|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|x86.Build.0 = Debug|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|x64.Build.0 = Fuzzing|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|Any CPU.ActiveCfg = Release|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|ARM.ActiveCfg = Release|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|ARM64.ActiveCfg = Release|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|ARM64.Build.0 = Release|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x64.ActiveCfg = Release|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x64.Build.0 = Release|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x86.ActiveCfg = Release|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|Any CPU.ActiveCfg = Debug|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|ARM64.ActiveCfg = Release|ARM64 @@ -2849,7 +2814,6 @@ Global {18D09A24-8240-42D6-8CB6-236EEE820263} = {89CDCC5C-9F53-4054-97A4-639D99F169CD} {990F2657-8580-4828-943F-5DD657D11843} = {05500DEF-2294-41E3-AF9A-24E580B82836} {0CF235BD-2DA0-407E-90EE-C467E8BBC714} = {1E4A062E-293B-4817-B20D-BF16B979E350} - {48D21369-3D7B-4431-9967-24E81292CF62} = {05500DEF-2294-41E3-AF9A-24E580B82836} {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {59840756-302F-44DF-AA47-441A9D673202} {CA5CAD1A-ABCD-429C-B551-8562EC954746} = {9921CA0A-320C-4460-8623-3A3196E7F4CB} {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {9921CA0A-320C-4460-8623-3A3196E7F4CB} diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 0bc403498cf..cf81c65f081 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2786,11 +2786,6 @@ "description": "Use to set a path to a pixel shader to use with the Terminal. Overrides `experimental.retroTerminalEffect`. This is an experimental feature, and its continued existence is not guaranteed.", "type": "string" }, - "useAtlasEngine": { - "description": "Windows Terminal 1.16 and later ship with a new, performant text renderer. Set this to false to revert back to the old text renderer.", - "type": "boolean", - "default": true - }, "fontFace": { "default": "Cascadia Mono", "description": "[deprecated] Define 'face' within the 'font' object instead.", diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 68702b27500..0dd8df67a83 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -17,7 +17,6 @@ #include "EventArgs.h" #include "../../buffer/out/search.h" #include "../../renderer/atlas/AtlasEngine.h" -#include "../../renderer/dx/DxRenderer.hpp" #include "ControlCore.g.cpp" #include "SelectionColor.g.cpp" @@ -335,15 +334,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation return false; } - if (_settings->UseAtlasEngine()) - { - _renderEngine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>(); - } - else - { - _renderEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>(); - } - + _renderEngine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>(); _renderer->AddRenderEngine(_renderEngine.get()); // Initialize our font with the renderer @@ -359,7 +350,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize); LOG_IF_FAILED(_renderEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() })); - // Update DxEngine's SelectionBackground + // Update AtlasEngine's SelectionBackground _renderEngine->SetSelectionBackground(til::color{ _settings->SelectionBackground() }); const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels); @@ -915,10 +906,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Update the terminal core with its new Core settings _terminal->UpdateAppearance(*newAppearance); - // Update DxEngine settings under the lock + // Update AtlasEngine settings under the lock if (_renderEngine) { - // Update DxEngine settings under the lock + // Update AtlasEngine settings under the lock _renderEngine->SetSelectionBackground(til::color{ newAppearance->SelectionBackground() }); _renderEngine->SetRetroTerminalEffect(newAppearance->RetroTerminalEffect()); _renderEngine->SetPixelShaderPath(newAppearance->PixelShaderPath()); @@ -954,7 +945,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::_updateAntiAliasingMode() { D2D1_TEXT_ANTIALIAS_MODE mode; - // Update DxEngine's AntialiasingMode + // Update AtlasEngine's AntialiasingMode switch (_settings->AntialiasingMode()) { case TextAntialiasingMode::Cleartype: diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 6b513f2bf86..dff8ba52bf3 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -5,7 +5,7 @@ // - ControlCore.h // // Abstract: -// - This encapsulates a `Terminal` instance, a `DxEngine` and `Renderer`, and +// - This encapsulates a `Terminal` instance, a `AtlasEngine` and `Renderer`, and // an `ITerminalConnection`. This is intended to be everything that someone // might need to stand up a terminal instance in a control, but without any // regard for how the UX works. diff --git a/src/cascadia/TerminalControl/HwndTerminal.cpp b/src/cascadia/TerminalControl/HwndTerminal.cpp index b5a930f701a..8ac7708ce0f 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.cpp +++ b/src/cascadia/TerminalControl/HwndTerminal.cpp @@ -3,11 +3,18 @@ #include "pch.h" #include "HwndTerminal.hpp" -#include + #include +#include + +#include "HwndTerminalAutomationPeer.hpp" +#include "../../cascadia/TerminalCore/Terminal.hpp" +#include "../../renderer/atlas/AtlasEngine.h" +#include "../../renderer/base/renderer.hpp" +#include "../../renderer/uia/UiaRenderer.hpp" #include "../../types/viewport.cpp" -#include "../../types/inc/GlyphWidth.hpp" +using namespace ::Microsoft::Console::VirtualTerminal; using namespace ::Microsoft::Terminal::Core; static LPCWSTR term_window_class = L"HwndTerminalClass"; @@ -102,25 +109,23 @@ try } break; case WM_RBUTTONDOWN: - if (publicTerminal->_terminal && publicTerminal->_terminal->IsSelectionActive()) + try { - try + if (publicTerminal->_terminal) { - Terminal::TextCopyData bufferData; + const auto lock = publicTerminal->_terminal->LockForWriting(); + if (publicTerminal->_terminal->IsSelectionActive()) { - const auto lock = publicTerminal->_terminal->LockForWriting(); - bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false, true, true); + const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false, true, true); + LOG_IF_FAILED(publicTerminal->_CopyTextToSystemClipboard(bufferData.plainText, bufferData.html, bufferData.rtf)); + publicTerminal->_ClearSelection(); + return 0; } - LOG_IF_FAILED(publicTerminal->_CopyTextToSystemClipboard(bufferData.plainText, bufferData.html, bufferData.rtf)); - publicTerminal->_ClearSelection(); } - CATCH_LOG(); - } - else - { publicTerminal->_PasteTextFromClipboard(); + return 0; } - return 0; + CATCH_LOG(); case WM_DESTROY: // Release Terminal's hwnd so Teardown doesn't try to destroy it again publicTerminal->_hwnd.release(); @@ -210,10 +215,10 @@ HRESULT HwndTerminal::Initialize() RETURN_HR_IF_NULL(E_POINTER, localPointerToThread); RETURN_IF_FAILED(localPointerToThread->Initialize(_renderer.get())); - auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>(); - RETURN_IF_FAILED(dxEngine->SetHwnd(_hwnd.get())); - RETURN_IF_FAILED(dxEngine->Enable()); - _renderer->AddRenderEngine(dxEngine.get()); + auto engine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>(); + RETURN_IF_FAILED(engine->SetHwnd(_hwnd.get())); + RETURN_IF_FAILED(engine->Enable()); + _renderer->AddRenderEngine(engine.get()); _UpdateFont(USER_DEFAULT_SCREEN_DPI); RECT windowRect; @@ -224,9 +229,9 @@ HRESULT HwndTerminal::Initialize() // Fist set up the dx engine with the window size in pixels. // Then, using the font, get the number of characters that can fit. const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize); - RETURN_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() })); + RETURN_IF_FAILED(engine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() })); - _renderEngine = std::move(dxEngine); + _renderEngine = std::move(engine); _terminal->Create({ 80, 25 }, 9001, *_renderer); _terminal->SetWriteInputCallback([=](std::wstring_view input) noexcept { _WriteTextToConnection(input); }); @@ -749,7 +754,7 @@ try ScreenToClient(_hwnd.get(), cursorPosition.as_win32_point()); } - const TerminalInput::MouseButtonState state{ + const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state{ WI_IsFlagSet(GetKeyState(VK_LBUTTON), KeyPressed), WI_IsFlagSet(GetKeyState(VK_MBUTTON), KeyPressed), WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed) @@ -894,7 +899,7 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font [[gsl::suppress(bounds .3)]] renderSettings.SetColorTableEntry(tableIndex, gsl::at(theme.ColorTable, tableIndex)); } - publicTerminal->_terminal->SetCursorStyle(static_cast(theme.CursorStyle)); + publicTerminal->_terminal->SetCursorStyle(static_cast(theme.CursorStyle)); publicTerminal->_desiredFont = { fontFamily, 0, DEFAULT_FONT_WEIGHT, static_cast(fontSize), CP_UTF8 }; publicTerminal->_UpdateFont(newDpi); diff --git a/src/cascadia/TerminalControl/HwndTerminal.hpp b/src/cascadia/TerminalControl/HwndTerminal.hpp index 6646fd56506..45780a1d50e 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.hpp +++ b/src/cascadia/TerminalControl/HwndTerminal.hpp @@ -3,14 +3,31 @@ #pragma once -#include "../../renderer/base/Renderer.hpp" -#include "../../renderer/dx/DxRenderer.hpp" -#include "../../renderer/uia/UiaRenderer.hpp" -#include "../../cascadia/TerminalCore/Terminal.hpp" +#include "../../buffer/out/textBuffer.hpp" +#include "../../renderer/inc/FontInfoDesired.hpp" #include "../../types/IControlAccessibilityInfo.h" -#include "HwndTerminalAutomationPeer.hpp" -using namespace Microsoft::Console::VirtualTerminal; +namespace Microsoft::Console::Render::Atlas +{ + class AtlasEngine; +} + +namespace Microsoft::Console::Render +{ + using AtlasEngine = Atlas::AtlasEngine; + class IRenderData; + class Renderer; + class UiaEngine; +} + +namespace Microsoft::Terminal::Core +{ + class Terminal; +} + +class FontInfo; +class FontInfoDesired; +class HwndTerminalAutomationPeer; // Keep in sync with TerminalTheme.cs typedef struct _TerminalTheme @@ -79,7 +96,7 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal; std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer; - std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine; + std::unique_ptr<::Microsoft::Console::Render::AtlasEngine> _renderEngine; std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine; bool _focused{ false }; diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 2ccfbd5b90d..e89b58eeb8c 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -35,8 +35,6 @@ namespace Microsoft.Terminal.Control Boolean EnableUnfocusedAcrylic; ScrollbarState ScrollState { get; }; - Boolean UseAtlasEngine { get; }; - String FontFace { get; }; Single FontSize { get; }; Windows.UI.Text.FontWeight FontWeight { get; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 5978149cd36..b9c81abe67a 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -2404,25 +2404,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation // 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/AtlasEngine. + // instantiating a AtlasEngine. // GH#10211 - UNDER NO CIRCUMSTANCE should this fail. If it does, the // whole app will crash instantaneously on launch, which is no good. - float scale; - if (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 engine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>(); + LOG_IF_FAILED(engine->UpdateDpi(dpi)); + LOG_IF_FAILED(engine->UpdateFont(desiredFont, actualFont)); + const auto scale = engine->GetScaling(); const auto actualFontSize = actualFont.GetSize(); // UWP XAML scrollbars aren't guaranteed to be the same size as the diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 7dc74d7136b..f2cc715b6eb 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -7,7 +7,6 @@ #include "XamlLights.h" #include "EventArgs.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" diff --git a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj index 151fe35932d..d4bd7e4a004 100644 --- a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj @@ -31,7 +31,7 @@ - + @@ -166,7 +166,6 @@ - diff --git a/src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj b/src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj index a6a9f5f1612..f1bc1acacdf 100644 --- a/src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj +++ b/src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj @@ -43,9 +43,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {48d21369-3d7b-4431-9967-24e81292cf62} - {3c67784e-1453-49c2-9660-483e2cc7f7ad} diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h index 751645b79ba..f33757a0c35 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -102,7 +102,6 @@ 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); OBSERVABLE_PROJECTED_SETTING(_profile, Elevate); OBSERVABLE_PROJECTED_SETTING(_profile, VtPassthrough); OBSERVABLE_PROJECTED_SETTING(_profile, ReloadEnvironmentVariables); diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl index e8517be3995..5020ecb7945 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl @@ -103,7 +103,6 @@ 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); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, Elevate); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, VtPassthrough); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, ReloadEnvironmentVariables); diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml index 815d6859d70..c9403bac0af 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml @@ -117,15 +117,6 @@ - - - - - - - - - - Controls what happens when the application emits a BEL character. A description for what the "bell style" setting does. Presented near "Profile_BellStyle".{Locked="BEL"} - - Use the new text renderer ("AtlasEngine") - {Locked="AtlasEngine"} - Launch this application with a new environment block "environment variables" are user-definable values that can affect the way running processes will behave on a computer diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index d0dc6bfcca5..f9626e6b6c8 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -90,7 +90,6 @@ Author(s): X(hstring, TabTitle, "tabTitle") \ X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ - X(bool, UseAtlasEngine, "useAtlasEngine", true) \ X(bool, RightClickContextMenu, "experimental.rightClickContextMenu", false) \ X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ X(bool, Elevate, "elevate", false) \ diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index 4aa9af3422a..16a17b15ee0 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -85,7 +85,6 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IMap, EnvironmentVariables); - INHERITABLE_PROFILE_SETTING(Boolean, UseAtlasEngine); INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IVector, BellSound); INHERITABLE_PROFILE_SETTING(Boolean, Elevate); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 580b06089fd..21d76fd8ed8 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -307,7 +307,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _SuppressApplicationTitle = profile.SuppressApplicationTitle(); } - _UseAtlasEngine = profile.UseAtlasEngine(); _ScrollState = profile.ScrollState(); _AntialiasingMode = profile.AntialiasingMode(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 009e6503da0..b065ee3c9f6 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -149,7 +149,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, IEnvironmentVariableMap, EnvironmentVariables); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::ScrollbarState, ScrollState, Microsoft::Terminal::Control::ScrollbarState::Visible); - INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAtlasEngine, true); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale); diff --git a/src/cascadia/UnitTests_Control/Control.UnitTests.vcxproj b/src/cascadia/UnitTests_Control/Control.UnitTests.vcxproj index 76858394072..5406e1d7bb2 100644 --- a/src/cascadia/UnitTests_Control/Control.UnitTests.vcxproj +++ b/src/cascadia/UnitTests_Control/Control.UnitTests.vcxproj @@ -40,7 +40,6 @@ - diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index 21022f30333..92770d79727 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -2,18 +2,10 @@ // Licensed under the MIT license. #include "pch.h" -#include - -#include - -#include "../renderer/inc/DummyRenderer.hpp" -#include "../renderer/base/Renderer.hpp" -#include "../renderer/dx/DxRenderer.hpp" #include "../cascadia/TerminalCore/Terminal.hpp" -#include "MockTermSettings.h" -#include "consoletaeftemplates.hpp" -#include "../../inc/TestUtils.h" +#include "../renderer/inc/DummyRenderer.hpp" +#include "../renderer/inc/RenderEngineBase.hpp" using namespace winrt::Microsoft::Terminal::Core; using namespace Microsoft::Terminal::Core; diff --git a/src/cascadia/UnitTests_TerminalCore/TilWinRtHelpersTests.cpp b/src/cascadia/UnitTests_TerminalCore/TilWinRtHelpersTests.cpp index 89c2856b8e0..6e6eee5f355 100644 --- a/src/cascadia/UnitTests_TerminalCore/TilWinRtHelpersTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TilWinRtHelpersTests.cpp @@ -8,7 +8,6 @@ #include "../renderer/inc/DummyRenderer.hpp" #include "../renderer/base/Renderer.hpp" -#include "../renderer/dx/DxRenderer.hpp" #include "../cascadia/TerminalCore/Terminal.hpp" #include "MockTermSettings.h" diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index 1b61775becc..02c4ad2427a 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -72,7 +72,6 @@ X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ X(bool, ForceFullRepaintRendering, false) \ X(bool, SoftwareRendering, false) \ - X(bool, UseAtlasEngine, true) \ X(bool, UseBackgroundImageForWindow, false) \ X(bool, ShowMarks, false) \ X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0) \ diff --git a/src/features.xml b/src/features.xml index be08ced6932..c9c6ceae7cb 100644 --- a/src/features.xml +++ b/src/features.xml @@ -26,15 +26,6 @@ - - Feature_ConhostDxEngine - Controls whether conhost supports the DX engine and the UseDx registry key - AlwaysEnabled - - WindowsInbox - - - Feature_ConhostAtlasEngine Controls whether conhost supports the Atlas engine @@ -44,15 +35,6 @@ - - Feature_DxEngineShaderSupport - Controls whether the DX engine is built with shader support. - AlwaysEnabled - - WindowsInbox - - - Feature_UseNumpadEventsForClipboardInput Controls whether the clipboard converter (and ConPTY InputStateMachine) uses Numpad events instead of UChar diff --git a/src/host/exe/Host.EXE.vcxproj b/src/host/exe/Host.EXE.vcxproj index 13084434d7e..6de848979aa 100644 --- a/src/host/exe/Host.EXE.vcxproj +++ b/src/host/exe/Host.EXE.vcxproj @@ -47,9 +47,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {48d21369-3d7b-4431-9967-24e81292cf62} - {1c959542-bac2-4e55-9a6d-13251914cbb9} diff --git a/src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj b/src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj index 7525726d8ff..6e5fb985efe 100644 --- a/src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj +++ b/src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj @@ -42,9 +42,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {48d21369-3d7b-4431-9967-24e81292cf62} - {1c959542-bac2-4e55-9a6d-13251914cbb9} diff --git a/src/host/settings.cpp b/src/host/settings.cpp index a9673658bb6..274fadec58f 100644 --- a/src/host/settings.cpp +++ b/src/host/settings.cpp @@ -53,7 +53,7 @@ Settings::Settings() : _fUseWindowSizePixels(false), // window size pixels initialized below _fInterceptCopyPaste(0), - _fUseDx(UseDx::Disabled), + _fUseDx(false), _fCopyColor(false) { _dwScreenBufferSize.X = 80; @@ -767,7 +767,7 @@ void Settings::SetTerminalScrolling(const bool terminalScrollingEnabled) noexcep // 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 +bool Settings::GetUseDx() const noexcept { return _fUseDx; } diff --git a/src/host/settings.hpp b/src/host/settings.hpp index 03eff335367..4edeb3ae927 100644 --- a/src/host/settings.hpp +++ b/src/host/settings.hpp @@ -24,13 +24,6 @@ constexpr unsigned short MIN_WINDOW_OPACITY = 0x4D; // 0x4D is approximately 30% #include "ConsoleArguments.hpp" #include "../renderer/inc/RenderSettings.hpp" -enum class UseDx : DWORD -{ - Disabled = 0, - DxEngine, - AtlasEngine, -}; - class Settings { using RenderSettings = Microsoft::Console::Render::RenderSettings; @@ -176,7 +169,7 @@ class Settings bool IsTerminalScrolling() const noexcept; void SetTerminalScrolling(const bool terminalScrollingEnabled) noexcept; - UseDx GetUseDx() const noexcept; + bool GetUseDx() const noexcept; bool GetCopyColor() const noexcept; private: @@ -219,7 +212,7 @@ class Settings std::wstring _LaunchFaceName; bool _fAllowAltF4Close; DWORD _dwVirtTermLevel; - UseDx _fUseDx; + bool _fUseDx; bool _fCopyColor; // this is used for the special STARTF_USESIZE mode. diff --git a/src/host/ut_host/Host.UnitTests.vcxproj b/src/host/ut_host/Host.UnitTests.vcxproj index 3592ec1489f..d464570c788 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj +++ b/src/host/ut_host/Host.UnitTests.vcxproj @@ -47,9 +47,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {48d21369-3d7b-4431-9967-24e81292cf62} - {990F2657-8580-4828-943F-5DD657D11843} diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 7f4796df60d..23d2d3a9c7e 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -12,10 +12,6 @@ #include "../../renderer/vt/Xterm256Engine.hpp" #include "../../renderer/vt/XtermEngine.hpp" -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED -#include "../../renderer/dx/DxRenderer.hpp" -#endif - using namespace WEX::Common; using namespace WEX::Logging; using namespace WEX::TestExecution; @@ -38,10 +34,6 @@ class Microsoft::Console::VirtualTerminal::VtIoTests TEST_METHOD(RendererDtorAndThread); -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED - TEST_METHOD(RendererDtorAndThreadAndDx); -#endif - TEST_METHOD(BasicAnonymousPipeOpeningWithSignalChannelTest); }; @@ -428,37 +420,6 @@ void VtIoTests::RendererDtorAndThread() } } -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED -void VtIoTests::RendererDtorAndThreadAndDx() -{ - Log::Comment(NoThrowString().Format( - L"Test deleting a Renderer a bunch of times")); - - for (auto i = 0; i < 16; ++i) - { - auto data = std::make_unique(); - auto thread = std::make_unique(); - auto* pThread = thread.get(); - auto pRenderer = std::make_unique(RenderSettings{}, data.get(), nullptr, 0, std::move(thread)); - VERIFY_SUCCEEDED(pThread->Initialize(pRenderer.get())); - - auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>(); - pRenderer->AddRenderEngine(dxEngine.get()); - // Sleep for a hot sec to make sure the thread starts before we enable painting - // If you don't, the thread might wait on the paint enabled event AFTER - // EnablePainting gets called, and if that happens, then the thread will - // never get destructed. This will only ever happen in the vstest test runner, - // which is what CI uses. - /*Sleep(500);*/ - - (void)dxEngine->Enable(); - pThread->EnablePainting(); - pRenderer->TriggerTeardown(); - pRenderer.reset(); - } -} -#endif - void VtIoTests::BasicAnonymousPipeOpeningWithSignalChannelTest() { Log::Comment(L"Test using anonymous pipes for the input and adding a signal channel."); diff --git a/src/interactivity/win32/lib/win32.LIB.vcxproj b/src/interactivity/win32/lib/win32.LIB.vcxproj index 8ffd169d7a7..1988841434b 100644 --- a/src/interactivity/win32/lib/win32.LIB.vcxproj +++ b/src/interactivity/win32/lib/win32.LIB.vcxproj @@ -63,9 +63,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {48d21369-3d7b-4431-9967-24e81292cf62} - diff --git a/src/interactivity/win32/ut_interactivity_win32/Interactivity.Win32.UnitTests.vcxproj b/src/interactivity/win32/ut_interactivity_win32/Interactivity.Win32.UnitTests.vcxproj index 919e3fb8365..9f354048194 100644 --- a/src/interactivity/win32/ut_interactivity_win32/Interactivity.Win32.UnitTests.vcxproj +++ b/src/interactivity/win32/ut_interactivity_win32/Interactivity.Win32.UnitTests.vcxproj @@ -26,9 +26,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {48d21369-3d7b-4431-9967-24e81292cf62} - {990f2657-8580-4828-943f-5dd657d11842} diff --git a/src/interactivity/win32/window.cpp b/src/interactivity/win32/window.cpp index 03eed072c13..3ef07add747 100644 --- a/src/interactivity/win32/window.cpp +++ b/src/interactivity/win32/window.cpp @@ -28,9 +28,6 @@ #if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED #include "../../renderer/atlas/AtlasEngine.h" #endif -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED -#include "../../renderer/dx/DxRenderer.hpp" -#endif #include "../inc/ServiceLocator.hpp" #include "../../types/inc/Viewport.hpp" @@ -70,9 +67,6 @@ Window::~Window() // reducing the change for existing race conditions to turn into deadlocks. #ifndef NDEBUG delete pGdiEngine; -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED - delete pDxEngine; -#endif #if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED delete pAtlasEngine; #endif @@ -217,31 +211,17 @@ void Window::_UpdateSystemMetrics() const const auto useDx = pSettings->GetUseDx(); try { - 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 - // determine the initial window size, which happens BEFORE the - // window is created, we'll want to make sure the DX engine does - // math in the hwnd mode, not the Composition mode. - THROW_IF_FAILED(pDxEngine->SetHwnd(nullptr)); - g.pRender->AddRenderEngine(pDxEngine); - break; -#endif #if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED - case UseDx::AtlasEngine: + if (useDx) + { pAtlasEngine = new AtlasEngine(); g.pRender->AddRenderEngine(pAtlasEngine); - break; + } + else #endif - default: + { pGdiEngine = new GdiEngine(); g.pRender->AddRenderEngine(pGdiEngine); - break; -#pragma warning(suppress : 4065) } } catch (...) @@ -332,20 +312,8 @@ void Window::_UpdateSystemMetrics() const { _hWnd = hWnd; -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED - if (pDxEngine) - { - HRESULT hr = S_OK; - if (SUCCEEDED(hr = pDxEngine->SetHwnd(hWnd))) - { - hr = pDxEngine->Enable(); - } - status = NTSTATUS_FROM_HRESULT(hr); - } - else -#endif #if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED - if (pAtlasEngine) + if (pAtlasEngine) { const auto hr = pAtlasEngine->SetHwnd(hWnd); status = NTSTATUS_FROM_HRESULT(hr); diff --git a/src/interactivity/win32/window.hpp b/src/interactivity/win32/window.hpp index 7550b18c9d8..ea678628e1e 100644 --- a/src/interactivity/win32/window.hpp +++ b/src/interactivity/win32/window.hpp @@ -24,7 +24,6 @@ namespace Microsoft::Console::Render::Atlas namespace Microsoft::Console::Render { using AtlasEngine = Atlas::AtlasEngine; - class DxEngine; class GdiEngine; } @@ -113,9 +112,6 @@ namespace Microsoft::Console::Interactivity::Win32 HWND _hWnd; Render::GdiEngine* pGdiEngine = nullptr; -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED - Render::DxEngine* pDxEngine = nullptr; -#endif #if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED Render::AtlasEngine* pAtlasEngine = nullptr; #endif diff --git a/src/propslib/RegistrySerialization.cpp b/src/propslib/RegistrySerialization.cpp index 2b9de45223e..a4441d25a5a 100644 --- a/src/propslib/RegistrySerialization.cpp +++ b/src/propslib/RegistrySerialization.cpp @@ -60,7 +60,7 @@ const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMa { _RegPropertyType::Dword, CONSOLE_REGISTRY_CURSORTYPE, SET_FIELD_AND_SIZE(_CursorType) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_INTERCEPTCOPYPASTE, SET_FIELD_AND_SIZE(_fInterceptCopyPaste) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_TERMINALSCROLLING, SET_FIELD_AND_SIZE(_TerminalScrolling) }, - { _RegPropertyType::Dword, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) }, + { _RegPropertyType::Boolean, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) } // Special cases that are handled manually in Registry::LoadFromRegistry: diff --git a/src/renderer/atlas/README.md b/src/renderer/atlas/README.md index e5e87f2ca0f..c72e77f51e6 100644 --- a/src/renderer/atlas/README.md +++ b/src/renderer/atlas/README.md @@ -8,7 +8,6 @@ graph TD Renderer["Renderer (base/renderer.cpp)\nbreaks the text buffer down into GDI-oriented graphics\nprimitives (#quot;change brush to color X#quot;, #quot;draw string Y#quot;, ...)"] RenderEngineBase[/"RenderEngineBase\n(base/RenderEngineBase.cpp)\nabstracts 24 LOC 👻"\] GdiEngine["GdiEngine (gdi/...)"] - DxEngine["DxEngine (dx/...)"] subgraph AtlasEngine["AtlasEngine (atlas/...)"] AtlasEngine.cpp["AtlasEngine.cpp\nImplements IRenderEngine text rendering API\nbreaks GDI graphics primitives down into DWRITE_GLYPH_RUNs"] @@ -24,7 +23,6 @@ graph TD Renderer -.-> RenderEngineBase %% Mermaid.js has no support for backwards arrow at the moment RenderEngineBase <-.->|extends| GdiEngine - RenderEngineBase <-.->|extends| DxEngine Renderer ----> AtlasEngine AtlasEngine.cpp <--> AtlasEngine.api.cpp AtlasEngine.cpp <--> AtlasEngine.r.cpp diff --git a/src/renderer/dx/BoxDrawingEffect.cpp b/src/renderer/dx/BoxDrawingEffect.cpp deleted file mode 100644 index d5af5a8451f..00000000000 --- a/src/renderer/dx/BoxDrawingEffect.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "BoxDrawingEffect.h" - -using namespace Microsoft::Console::Render; - -BoxDrawingEffect::BoxDrawingEffect() noexcept : - _scale{ 1.0f, 0.0f, 1.0f, 0.0f } -{ -} - -#pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize -HRESULT BoxDrawingEffect::RuntimeClassInitialize(float verticalScale, float verticalTranslate, float horizontalScale, float horizontalTranslate) noexcept -{ - _scale.VerticalScale = verticalScale; - _scale.VerticalTranslation = verticalTranslate; - _scale.HorizontalScale = horizontalScale; - _scale.HorizontalTranslation = horizontalTranslate; - return S_OK; -} - -[[nodiscard]] HRESULT STDMETHODCALLTYPE BoxDrawingEffect::GetScale(BoxScale* scale) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, scale); - *scale = _scale; - return S_OK; -} diff --git a/src/renderer/dx/BoxDrawingEffect.h b/src/renderer/dx/BoxDrawingEffect.h deleted file mode 100644 index 1bd0439cffb..00000000000 --- a/src/renderer/dx/BoxDrawingEffect.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include -#include -#include - -#include "IBoxDrawingEffect_h.h" - -namespace Microsoft::Console::Render -{ - class BoxDrawingEffect : public ::Microsoft::WRL::RuntimeClass<::Microsoft::WRL::RuntimeClassFlags<::Microsoft::WRL::ClassicCom | ::Microsoft::WRL::InhibitFtmBase>, IBoxDrawingEffect> - { - public: - BoxDrawingEffect() noexcept; - HRESULT RuntimeClassInitialize(float verticalScale, float verticalTranslate, float horizontalScale, float horizontalTranslate) noexcept; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetScale(BoxScale* scale) noexcept override; - - protected: - private: - BoxScale _scale; -#ifdef UNIT_TESTING - public: - friend class BoxDrawingEffectTests; -#endif - }; -} diff --git a/src/renderer/dx/CustomTextLayout.cpp b/src/renderer/dx/CustomTextLayout.cpp deleted file mode 100644 index 41c64a5a5cd..00000000000 --- a/src/renderer/dx/CustomTextLayout.cpp +++ /dev/null @@ -1,1757 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "CustomTextLayout.h" -#include "CustomTextRenderer.h" - -#include -#include -#include - -#include "BoxDrawingEffect.h" - -using namespace Microsoft::Console::Render; - -// Routine Description: -// - Creates a CustomTextLayout object for calculating which glyphs should be placed and where -// Arguments: -// - dxFontRenderData - The DirectWrite font render data for our layout -CustomTextLayout::CustomTextLayout(const gsl::not_null fontRenderData) : - _fontRenderData{ fontRenderData }, - _formatInUse{ fontRenderData->DefaultTextFormat().Get() }, - _fontInUse{ fontRenderData->DefaultFontFace().Get() }, - _numberSubstitution{}, - _readingDirection{ DWRITE_READING_DIRECTION_LEFT_TO_RIGHT }, - _runs{}, - _breakpoints{}, - _runIndex{ 0 }, - _width{ gsl::narrow_cast(fontRenderData->GlyphCell().width) }, - _isEntireTextSimple{ false } -{ - _localeName.resize(gsl::narrow_cast(fontRenderData->DefaultTextFormat()->GetLocaleNameLength()) + 1); // +1 for null - THROW_IF_FAILED(fontRenderData->DefaultTextFormat()->GetLocaleName(_localeName.data(), gsl::narrow(_localeName.size()))); -} - -//Routine Description: -// - Resets this custom text layout to the freshly allocated state in terms of text analysis. -// Arguments: -// - , modifies internal state -// Return Value: -// - S_OK or suitable memory management issue -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::Reset() noexcept -try -{ - _runs.clear(); - _breakpoints.clear(); - _runIndex = 0; - _isEntireTextSimple = false; - _textClusterColumns.clear(); - _text.clear(); - _glyphScaleCorrections.clear(); - _glyphClusters.clear(); - _glyphIndices.clear(); - _glyphDesignUnitAdvances.clear(); - _glyphAdvances.clear(); - _glyphOffsets.clear(); - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Appends text to this layout for analysis/processing. -// Arguments: -// - clusters - From the backing buffer, the text to be displayed clustered by the columns it should consume. -// Return Value: -// - S_OK or suitable memory management issue. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::AppendClusters(const std::span clusters) -try -{ - _textClusterColumns.reserve(_textClusterColumns.size() + clusters.size()); - - for (const auto& cluster : clusters) - { - const auto cols = gsl::narrow(cluster.GetColumns()); - const auto text = cluster.GetText(); - - // Push back the number of columns for this bit of text. - _textClusterColumns.push_back(cols); - - // If there is more than one text character here, push 0s for the rest of the columns - // of the text run. - _textClusterColumns.resize(_textClusterColumns.size() + base::ClampSub(text.size(), 1u), gsl::narrow_cast(0u)); - - _text += text; - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Figures out how many columns this layout should take. This will use the analyze step only. -// Arguments: -// - columns - The number of columns the layout should consume when done. -// Return Value: -// - S_OK or suitable DirectX/DirectWrite/Direct2D result code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetColumns(_Out_ UINT32* columns) -{ - RETURN_HR_IF_NULL(E_INVALIDARG, columns); - *columns = 0; - - _formatInUse = _fontRenderData->DefaultTextFormat().Get(); - _fontInUse = _fontRenderData->DefaultFontFace().Get(); - - RETURN_IF_FAILED(_AnalyzeTextComplexity()); - RETURN_IF_FAILED(_AnalyzeRuns()); - RETURN_IF_FAILED(_ShapeGlyphRuns()); - - const auto totalAdvance = std::accumulate(_glyphAdvances.cbegin(), _glyphAdvances.cend(), 0.0f); - - *columns = static_cast(ceil(totalAdvance / _width)); - - return S_OK; -} - -// Routine Description: -// - Implements a drawing interface similarly to the default IDWriteTextLayout which will -// take the string from construction, analyze it for complexity, shape up the glyphs, -// and then draw the final product to the given renderer at the point and pass along -// the context information. -// - This specific class does the layout calculations and complexity analysis, not the -// final drawing. That's the renderer's job (passed in.) -// Arguments: -// - clientDrawingContext - Optional pointer to information that the renderer might need -// while attempting to graphically place the text onto the screen -// - renderer - The interface to be used for actually putting text onto the screen -// - originX - X pixel point of top left corner on final surface for drawing -// - originY - Y pixel point of top left corner on final surface for drawing -// Return Value: -// - S_OK or suitable DirectX/DirectWrite/Direct2D result code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::Draw(_In_opt_ void* clientDrawingContext, - _In_ IDWriteTextRenderer* renderer, - FLOAT originX, - FLOAT originY) noexcept -try -{ - const auto drawingContext = static_cast(clientDrawingContext); - - auto weight = _fontRenderData->DefaultFontWeight(); - auto style = _fontRenderData->DefaultFontStyle(); - const auto stretch = _fontRenderData->DefaultFontStretch(); - - if (drawingContext->useBoldFont) - { - // TODO: "relative" bold? - weight = DWRITE_FONT_WEIGHT_BOLD; - // Since we are setting the font weight according to the text attribute, - // make sure to tell the text format to ignore the user set font weight - _fontRenderData->InhibitUserWeight(true); - } - else - { - _fontRenderData->InhibitUserWeight(false); - } - - if (drawingContext->useItalicFont || _fontRenderData->DidUserSetItalic()) - { - style = DWRITE_FONT_STYLE_ITALIC; - } - - _formatInUse = _fontRenderData->TextFormatWithAttribute(weight, style, stretch).Get(); - _fontInUse = _fontRenderData->FontFaceWithAttribute(weight, style, stretch).Get(); - - RETURN_IF_FAILED(_AnalyzeTextComplexity()); - RETURN_IF_FAILED(_AnalyzeRuns()); - RETURN_IF_FAILED(_ShapeGlyphRuns()); - RETURN_IF_FAILED(_CorrectGlyphRuns()); - // Correcting box drawing has to come after both font fallback and - // the glyph run advance correction (which will apply a font size scaling factor). - // We need to know all the proposed X and Y dimension metrics to get this right. - RETURN_IF_FAILED(_CorrectBoxDrawing()); - - RETURN_IF_FAILED(_DrawGlyphRuns(clientDrawingContext, renderer, { originX, originY })); - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Uses the internal text information and the analyzers/font information from construction -// to determine the complexity of the text. If the text is determined to be entirely simple, -// we'll have more chances to optimize the layout process. -// Arguments: -// - - Uses internal state -// Return Value: -// - S_OK or suitable DirectWrite or STL error code -[[nodiscard]] HRESULT CustomTextLayout::_AnalyzeTextComplexity() noexcept -{ - try - { - const auto textLength = gsl::narrow(_text.size()); - - auto isTextSimple = FALSE; - UINT32 uiLengthRead = 0; - - // Start from the beginning. - const UINT32 glyphStart = 0; - - _glyphIndices.resize(textLength); - - const auto hr = _fontRenderData->Analyzer()->GetTextComplexity( - _text.c_str(), - textLength, - _fontInUse, - &isTextSimple, - &uiLengthRead, - &_glyphIndices.at(glyphStart)); - - RETURN_IF_FAILED(hr); - - _isEntireTextSimple = isTextSimple && uiLengthRead == textLength; - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Uses the internal text information and the analyzers/font information from construction -// to determine the complexity of the text inside this layout, compute the subsections (or runs) -// that contain similar property information, and stores that information internally. -// - We determine line breakpoints, bidirectional information, the script properties, -// number substitution, and font fallback properties in this function. -// Arguments: -// - - Uses internal state -// Return Value: -// - S_OK or suitable DirectWrite or STL error code -[[nodiscard]] HRESULT CustomTextLayout::_AnalyzeRuns() noexcept -{ - try - { - // We're going to need the text length in UINT32 format for the DWrite calls. - // Convert it once up front. - const auto textLength = gsl::narrow(_text.size()); - - // Initially start out with one result that covers the entire range. - // This result will be subdivided by the analysis processes. - _runs.resize(1); - auto& initialRun = _runs.front(); - initialRun.textLength = textLength; - initialRun.bidiLevel = (_readingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT); - - // Allocate enough room to have one breakpoint per code unit. - _breakpoints.resize(_text.size()); - - if (!_isEntireTextSimple || _fontRenderData->DidUserSetAxes()) - { - // Call each of the analyzers in sequence, recording their results. - RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeLineBreakpoints(this, 0, textLength, this)); - RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeBidi(this, 0, textLength, this)); - RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeScript(this, 0, textLength, this)); - RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeNumberSubstitution(this, 0, textLength, this)); - // Perform our custom font fallback analyzer that mimics the pattern of the real analyzers. - RETURN_IF_FAILED(_AnalyzeFontFallback(this, 0, textLength)); - } - - // Ensure that a font face is attached to every run - for (auto& run : _runs) - { - if (!run.fontFace) - { - run.fontFace = _fontInUse; - } - } - - // Resequence the resulting runs in order before returning to caller. - _OrderRuns(); - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Uses the internal run analysis information (from the analyze step) to map and shape out -// the glyphs from the fonts. This is effectively a loop of _ShapeGlyphRun. See it for details. -// Arguments: -// - - Uses internal state -// Return Value: -// - S_OK or suitable DirectWrite or STL error code -[[nodiscard]] HRESULT CustomTextLayout::_ShapeGlyphRuns() noexcept -{ - try - { - // Shapes all the glyph runs in the layout. - const auto textLength = gsl::narrow(_text.size()); - - // Estimate the maximum number of glyph indices needed to hold a string. - const auto estimatedGlyphCount = _EstimateGlyphCount(textLength); - - _glyphIndices.resize(estimatedGlyphCount); - _glyphOffsets.resize(estimatedGlyphCount); - _glyphAdvances.resize(estimatedGlyphCount); - _glyphClusters.resize(textLength); - - UINT32 glyphStart = 0; - - // Shape each run separately. This is needed whenever script, locale, - // or reading direction changes. - for (UINT32 runIndex = 0; runIndex < _runs.size(); ++runIndex) - { - LOG_IF_FAILED(_ShapeGlyphRun(runIndex, glyphStart)); - } - - _glyphIndices.resize(glyphStart); - _glyphOffsets.resize(glyphStart); - _glyphAdvances.resize(glyphStart); - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Calculates the following information for any one particular run of text: -// 1. Indices (finding the ID number in each font for each glyph) -// 2. Offsets (the left/right or top/bottom spacing from the baseline origin for each glyph) -// 3. Advances (the width allowed for every glyph) -// 4. Clusters (the bunches of glyphs that represent a particular combined character) -// - A run is defined by the analysis step as a substring of the original text that has similar properties -// such that it can be processed together as a unit. -// Arguments: -// - runIndex - The ID number of the internal runs array to use while shaping -// - glyphStart - On input, which portion of the internal indices/offsets/etc. arrays to use -// to write the shaping information. -// - On output, the position that should be used by the next call as its start position -// Return Value: -// - S_OK or suitable DirectWrite or STL error code -[[nodiscard]] HRESULT CustomTextLayout::_ShapeGlyphRun(const UINT32 runIndex, UINT32& glyphStart) noexcept -{ - try - { - // Shapes a single run of text into glyphs. - // Alternately, you could iteratively interleave shaping and line - // breaking to reduce the number glyphs held onto at once. It's simpler - // for this demonstration to just do shaping and line breaking as two - // separate processes, but realize that this does have the consequence that - // certain advanced fonts containing line specific features (like Gabriola) - // will shape as if the line is not broken. - - Run& run = _runs.at(runIndex); - const auto textStart = run.textStart; - const auto textLength = run.textLength; - auto maxGlyphCount = gsl::narrow(_glyphIndices.size() - glyphStart); - UINT32 actualGlyphCount = 0; - - run.glyphStart = glyphStart; - run.glyphCount = 0; - - if (textLength == 0) - { - return S_FALSE; // Nothing to do.. - } - - // Allocate space for shaping to fill with glyphs and other information, - // with about as many glyphs as there are text characters. We'll actually - // need more glyphs than codepoints if they are decomposed into separate - // glyphs, or fewer glyphs than codepoints if multiple are substituted - // into a single glyph. In any case, the shaping process will need some - // room to apply those rules to even make that determination. - - if (textLength > maxGlyphCount) - { - maxGlyphCount = _EstimateGlyphCount(textLength); - const auto totalGlyphsArrayCount = glyphStart + maxGlyphCount; - _glyphIndices.resize(totalGlyphsArrayCount); - } - - if (_isEntireTextSimple && !_fontRenderData->DidUserSetFeatures()) - { - // When the entire text is simple, we can skip GetGlyphs and directly retrieve glyph indices and - // advances(in font design unit). With the help of font metrics, we can calculate the actual glyph - // advances without the need of GetGlyphPlacements. This shortcut will significantly reduce the time - // needed for text analysis. - DWRITE_FONT_METRICS1 metrics; - run.fontFace->GetMetrics(&metrics); - - // With simple text, there's only one run. The actual glyph count is the same as textLength. - _glyphDesignUnitAdvances.resize(textLength); - _glyphAdvances.resize(textLength); - - auto designUnitsPerEm = metrics.designUnitsPerEm; - - RETURN_IF_FAILED(_fontInUse->GetDesignGlyphAdvances( - textLength, - &_glyphIndices.at(glyphStart), - &_glyphDesignUnitAdvances.at(glyphStart), - run.isSideways)); - - for (size_t i = glyphStart; i < _glyphAdvances.size(); i++) - { - _glyphAdvances.at(i) = (float)_glyphDesignUnitAdvances.at(i) / designUnitsPerEm * _formatInUse->GetFontSize() * run.fontScale; - } - - // Set all the clusters as sequential. In a simple run, we're going 1 to 1. - // Fill the clusters sequentially from 0 to N-1. - std::iota(_glyphClusters.begin(), _glyphClusters.end(), gsl::narrow_cast(0)); - - run.glyphCount = textLength; - glyphStart += textLength; - - return S_OK; - } - - std::vector textProps(textLength); - std::vector glyphProps(maxGlyphCount); - - // Get the features to apply to the font - const auto& features = _fontRenderData->DefaultFontFeatures(); -#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3). - const DWRITE_TYPOGRAPHIC_FEATURES typographicFeatures = { const_cast(features.data()), gsl::narrow(features.size()) }; - DWRITE_TYPOGRAPHIC_FEATURES const* typographicFeaturesPointer = &typographicFeatures; - const uint32_t fontFeatureLengths[] = { textLength }; - - // Get the glyphs from the text, retrying if needed. - - auto tries = 0; - -#pragma warning(suppress : 26485) // so we can pass in the fontFeatureLengths to GetGlyphs without the analyzer complaining - auto hr = S_OK; - do - { - hr = _fontRenderData->Analyzer()->GetGlyphs( - &_text.at(textStart), - textLength, - run.fontFace.Get(), - run.isSideways, // isSideways, - WI_IsFlagSet(run.bidiLevel, 1), // isRightToLeft - &run.script, - _localeName.data(), - (run.isNumberSubstituted) ? _numberSubstitution.Get() : nullptr, - &typographicFeaturesPointer, // features - &fontFeatureLengths[0], // featureLengths - 1, // featureCount - maxGlyphCount, // maxGlyphCount - &_glyphClusters.at(textStart), - &textProps.at(0), - &_glyphIndices.at(glyphStart), - &glyphProps.at(0), - &actualGlyphCount); - tries++; - - if (hr == E_NOT_SUFFICIENT_BUFFER) - { - // Try again using a larger buffer. - maxGlyphCount = _EstimateGlyphCount(maxGlyphCount); - const auto totalGlyphsArrayCount = glyphStart + maxGlyphCount; - - glyphProps.resize(maxGlyphCount); - _glyphIndices.resize(totalGlyphsArrayCount); - } - else - { - break; - } - } while (tries < 2); // We'll give it two chances. - - RETURN_IF_FAILED(hr); - - // Get the placement of the all the glyphs. - - _glyphAdvances.resize(std::max(gsl::narrow_cast(glyphStart) + gsl::narrow_cast(actualGlyphCount), _glyphAdvances.size())); - _glyphOffsets.resize(std::max(gsl::narrow_cast(glyphStart) + gsl::narrow_cast(actualGlyphCount), _glyphOffsets.size())); - - const auto fontSizeFormat = _formatInUse->GetFontSize(); - const auto fontSize = fontSizeFormat * run.fontScale; - - hr = _fontRenderData->Analyzer()->GetGlyphPlacements( - &_text.at(textStart), - &_glyphClusters.at(textStart), - &textProps.at(0), - textLength, - &_glyphIndices.at(glyphStart), - &glyphProps.at(0), - actualGlyphCount, - run.fontFace.Get(), - fontSize, - run.isSideways, - (run.bidiLevel & 1), // isRightToLeft - &run.script, - _localeName.data(), - &typographicFeaturesPointer, // features - &fontFeatureLengths[0], // featureLengths - 1, // featureCount - &_glyphAdvances.at(glyphStart), - &_glyphOffsets.at(glyphStart)); - - RETURN_IF_FAILED(hr); - - // Set the final glyph count of this run and advance the starting glyph. - run.glyphCount = actualGlyphCount; - glyphStart += actualGlyphCount; - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Adjusts the glyph information from shaping to fit the layout pattern required -// for our renderer. -// This is effectively a loop of _CorrectGlyphRun. See it for details. -// Arguments: -// - - Uses internal state -// Return Value: -// - S_OK or suitable DirectWrite or STL error code -[[nodiscard]] HRESULT CustomTextLayout::_CorrectGlyphRuns() noexcept -{ - try - { - // For simple text, there is no need to correct runs. - if (_isEntireTextSimple) - { - return S_OK; - } - - // Correct each run separately. This is needed whenever script, locale, - // or reading direction changes. - for (UINT32 runIndex = 0; runIndex < _runs.size(); ++runIndex) - { - LOG_IF_FAILED(_CorrectGlyphRun(runIndex)); - } - - // If scale corrections were needed, we need to split the run. - for (auto& c : _glyphScaleCorrections) - { - // Split after the adjustment first so it - // takes a copy of all the run properties before we modify them. - // GH 4665: This is the other half of the potential future perf item. - // If glyphs needing the same scale are coalesced, we could - // break fewer times and have fewer runs. - - // Example - // Text: - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // LEN = 26 - // Runs: - // ^0----^1---------^2------- - // Scale Factors: - // 1.0 1.0 1.0 - // (arrows are run begin) - // 0: IDX = 0, LEN = 6 - // 1: IDX = 6, LEN = 11 - // 2: IDX = 17, LEN = 9 - - // From the scale correction... we get - // IDX = where the scale starts - // LEN = how long the scale adjustment runs - // SCALE = the scale factor. - - // We need to split the run so the SCALE factor - // only applies from IDX to LEN. - - // This is the index after the segment we're splitting. - const auto afterIndex = c.textIndex + c.textLength; - - // If the after index is still within the text, split the back - // half off first so we don't apply the scale factor to anything - // after this glyph/run segment. - // Example relative to above sample state: - // Correction says: IDX = 12, LEN = 2, FACTOR = 0.8 - // We must split off first at 14 to leave the existing factor from 14-16. - // (because the act of splitting copies all properties, we don't want to - // adjust the scale factor BEFORE splitting off the existing one.) - // Text: - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // LEN = 26 - // Runs: - // ^0----^1----xx^2-^3------- - // (xx is where we're going to put the correction when all is said and done. - // We're adjusting the scale of the "MN" text only.) - // Scale Factors: - // 1 1 1 1 - // (arrows are run begin) - // 0: IDX = 0, LEN = 6 - // 1: IDX = 6, LEN = 8 - // 2: IDX = 14, LEN = 3 - // 3: IDX = 17, LEN = 9 - if (afterIndex < _text.size()) - { - _SetCurrentRun(afterIndex); - _SplitCurrentRun(afterIndex); - } - // If it's after the text, don't bother. The correction will just apply - // from the begin point to the end of the text. - // Example relative to above sample state: - // Correction says: IDX = 24, LEN = 2 - // Text: - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // LEN = 26 - // Runs: - // ^0----^1---------^2-----xx - // xx is where we're going to put the correction when all is said and done. - // We don't need to split off the back portion because there's nothing after the xx. - - // Now split just this glyph off. - // Example versus the one above where we did already split the back half off.. - // Correction says: IDX = 12, LEN = 2, FACTOR = 0.8 - // Text: - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // LEN = 26 - // Runs: - // ^0----^1----^2^3-^4------- - // (The MN text has been broken into its own run, 2.) - // Scale Factors: - // 1 1 1 1 1 - // (arrows are run begin) - // 0: IDX = 0, LEN = 6 - // 1: IDX = 6, LEN = 6 - // 2: IDX = 12, LEN = 2 - // 2: IDX = 14, LEN = 3 - // 3: IDX = 17, LEN = 9 - _SetCurrentRun(c.textIndex); - _SplitCurrentRun(c.textIndex); - - // Get the run with the one glyph and adjust the scale. - auto& run = _GetCurrentRun(); - run.fontScale = c.scale; - // Correction says: IDX = 12, LEN = 2, FACTOR = 0.8 - // Text: - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // LEN = 26 - // Runs: - // ^0----^1----^2^3-^4------- - // (We've now only corrected run 2, selecting only the MN to 0.8) - // Scale Factors: - // 1 1 .8 1 1 - } - - // Dump the glyph scale corrections now that we're done with them. - _glyphScaleCorrections.clear(); - - // Order the runs. - _OrderRuns(); - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Adjusts the advances for each glyph in the run so it fits within a fixed-column count of cells. -// Arguments: -// - runIndex - The ID number of the internal runs array to use while shaping. -// Return Value: -// - S_OK or suitable DirectWrite or STL error code -[[nodiscard]] HRESULT CustomTextLayout::_CorrectGlyphRun(const UINT32 runIndex) noexcept -try -{ - const Run& run = _runs.at(runIndex); - - if (run.textLength == 0) - { - return S_FALSE; // Nothing to do.. - } - - // We're going to walk through and check for advances that don't match the space that we expect to give out. - - // Glyph Indices represents the number inside the selected font where the glyph image/paths are found. - // Text represents the original text we gave in. - // Glyph Clusters represents the map between Text and Glyph Indices. - // - There is one Glyph Clusters map column per character of text. - // - The value of the cluster at any given position is relative to the 0 index of this run. - // (a.k.a. it resets to 0 for every run) - // - If multiple Glyph Cluster map values point to the same index, then multiple text chars were used - // to create the same glyph cluster. - // - The delta between the value from one Glyph Cluster's value and the next one is how many - // Glyph Indices are consumed to make that cluster. - - // We're going to walk the map to find what spans of text and glyph indices make one cluster. - const auto clusterMapBegin = _glyphClusters.cbegin() + run.textStart; - const auto clusterMapEnd = clusterMapBegin + run.textLength; - - // Walk through every glyph in the run, collect them into clusters, then adjust them to fit in - // however many columns are expected for display by the text buffer. -#pragma warning(suppress : 26496) // clusterBegin is updated at the bottom of the loop but analysis isn't seeing it. - for (auto clusterBegin = clusterMapBegin; clusterBegin < clusterMapEnd; /* we will increment this inside the loop*/) - { - // One or more glyphs might belong to a single cluster. - // Consider the following examples: - - // 1. - // U+00C1 is Á. - // That is text of length one. - // A given font might have a complete single glyph for this - // which will be mapped into the _glyphIndices array. - // _text[0] = Á - // _glyphIndices[0] = 153 - // _glyphClusters[0] = 0 - // _glyphClusters[1] = 1 - // The delta between the value of Clusters 0 and 1 is 1. - // The number of times "0" is specified is once. - // This means that we've represented one text with one glyph. - - // 2. - // U+0041 is A and U+0301 is a combining acute accent ´. - // That is a text length of two. - // A given font might have two glyphs for this - // which will be mapped into the _glyphIndices array. - // _text[0] = A - // _text[1] = ´ (U+0301, combining acute) - // _glyphIndices[0] = 153 - // _glyphIndices[1] = 421 - // _glyphClusters[0] = 0 - // _glyphClusters[1] = 0 - // _glyphClusters[2] = 2 - // The delta between the value of Clusters 0/1 and 2 is 2. - // The number of times "0" is specified is twice. - // This means that we've represented two text with two glyphs. - - // There are two more scenarios that can occur that get us into - // NxM territory (N text by M glyphs) - - // 3. - // U+00C1 is Á. - // That is text of length one. - // A given font might represent this as two glyphs - // which will be mapped into the _glyphIndices array. - // _text[0] = Á - // _glyphIndices[0] = 153 - // _glyphIndices[1] = 421 - // _glyphClusters[0] = 0 - // _glyphClusters[1] = 2 - // The delta between the value of Clusters 0 and 1 is 2. - // The number of times "0" is specified is once. - // This means that we've represented one text with two glyphs. - - // 4. - // U+0041 is A and U+0301 is a combining acute accent ´. - // That is a text length of two. - // A given font might represent this as one glyph - // which will be mapped into the _glyphIndices array. - // _text[0] = A - // _text[1] = ´ (U+0301, combining acute) - // _glyphIndices[0] = 984 - // _glyphClusters[0] = 0 - // _glyphClusters[1] = 0 - // _glyphClusters[2] = 1 - // The delta between the value of Clusters 0/1 and 2 is 1. - // The number of times "0" is specified is twice. - // This means that we've represented two text with one glyph. - - // Finally, there's one more dimension. - // Due to supporting a specific coordinate system, the text buffer - // has told us how many columns it expects the text it gave us to take - // when displayed. - // That is stored in _textClusterColumns with one value for each - // character in the _text array. - // It isn't aware of how glyphs actually get mapped. - // So it's giving us a column count in terms of text characters - // but expecting it to be applied to all the glyphs in the cluster - // required to represent that text. - // We'll collect that up and use it at the end to adjust our drawing. - - // Our goal below is to walk through and figure out... - // A. How many glyphs belong to this cluster? - // B. Which text characters belong with those glyphs? - // C. How many columns, in total, were we told we could use - // to draw the glyphs? - - // This is the value under the beginning position in the map. - const auto clusterValue = *clusterBegin; - - // Find the cluster end point inside the map. - // We want to walk forward in the map until it changes (or we reach the end). - const auto clusterEnd = std::find_if(clusterBegin, clusterMapEnd, [clusterValue](auto compareVal) -> bool { return clusterValue != compareVal; }); - - // The beginning of the text span is just how far the beginning of the cluster is into the map. - const auto clusterTextBegin = std::distance(_glyphClusters.cbegin(), clusterBegin); - - // The distance from beginning to end is the cluster text length. - const auto clusterTextLength = std::distance(clusterBegin, clusterEnd); - - // The beginning of the glyph span is just the original cluster value. - const auto clusterGlyphBegin = clusterValue + run.glyphStart; - - // The difference between the value inside the end iterator and the original value is the glyph length. - // If the end iterator was off the end of the map, then it's the total run glyph count minus wherever we started. - const auto clusterGlyphLength = (clusterEnd != clusterMapEnd ? *clusterEnd : run.glyphCount) - clusterValue; - - // Now we can specify the spans within the text-index and glyph-index based vectors - // that store our drawing metadata. - // All the text ones run [clusterTextBegin, clusterTextBegin + clusterTextLength) - // All the cluster ones run [clusterGlyphBegin, clusterGlyphBegin + clusterGlyphLength) - - // Get how many columns we expected the glyph to have. - const auto columns = base::saturated_cast(std::accumulate(_textClusterColumns.cbegin() + clusterTextBegin, - _textClusterColumns.cbegin() + clusterTextBegin + clusterTextLength, - 0u)); - - // Multiply into pixels to get the "advance" we expect this text/glyphs to take when drawn. - const auto advanceExpected = static_cast(columns * _width); - - // Sum up the advances across the entire cluster to find what the actual value is that we've been told. - const auto advanceActual = std::accumulate(_glyphAdvances.cbegin() + clusterGlyphBegin, - _glyphAdvances.cbegin() + clusterGlyphBegin + clusterGlyphLength, - 0.0f); - - // With certain font faces at certain sizes, the advances seem to be slightly more than - // the pixel grid; Cascadia Code at 13pt (though, 200% scale) had an advance of 10.000001. - // We don't want anything sub one hundredth of a cell to make us break up runs, because - // doing so results in suboptimal rendering. - // If what we expect is bigger than what we have... pad it out. - if ((advanceExpected - advanceActual) > 0.001f) - { - // Get the amount of space we have leftover. - const auto diff = advanceExpected - advanceActual; - - // Move the X offset (pixels to the right from the left edge) by half the excess space - // so half of it will be left of the glyph and the other half on the right. - // Here we need to move every glyph in the cluster. - std::for_each(_glyphOffsets.begin() + clusterGlyphBegin, - _glyphOffsets.begin() + clusterGlyphBegin + clusterGlyphLength, - [halfDiff = diff / 2](DWRITE_GLYPH_OFFSET& offset) -> void { offset.advanceOffset += halfDiff; }); - - // Set the advance of the final glyph in the set to all excess space not consumed by the first few so - // we get the perfect width we want. - _glyphAdvances.at(static_cast(clusterGlyphBegin) + clusterGlyphLength - 1) += diff; - } - // If what we expect is smaller than what we have... rescale the font size to get a smaller glyph to fit. - else if ((advanceExpected - advanceActual) < -0.001f) - { - const auto scaleProposed = advanceExpected / advanceActual; - - // Store the glyph scale correction for future run breaking - // GH 4665: In theory, we could also store the length of the new run and coalesce - // in case two adjacent glyphs need the same scale factor. - _glyphScaleCorrections.push_back(ScaleCorrection{ - gsl::narrow(clusterTextBegin), - gsl::narrow(clusterTextLength), - scaleProposed }); - - // Adjust all relevant advances by the scale factor. - std::for_each(_glyphAdvances.begin() + clusterGlyphBegin, - _glyphAdvances.begin() + clusterGlyphBegin + clusterGlyphLength, - [scaleProposed](float& advance) -> void { advance *= scaleProposed; }); - } - - clusterBegin = clusterEnd; - } - - // Certain fonts, like Batang, contain glyphs for hidden control - // and formatting characters. So we'll want to explicitly force their - // advance to zero. - // I'm leaving this here for future reference, but I don't think we want invisible glyphs for this renderer. - //if (run.script.shapes & DWRITE_SCRIPT_SHAPES_NO_VISUAL) - //{ - // std::fill(_glyphAdvances.begin() + glyphStart, - // _glyphAdvances.begin() + glyphStart + actualGlyphCount, - // 0.0f - // ); - //} - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Takes the analyzed and shaped textual information from the layout process and -// forwards it into the given renderer in a run-by-run fashion. -// Arguments: -// - clientDrawingContext - Optional pointer to information that the renderer might need -// while attempting to graphically place the text onto the screen -// - renderer - The interface to be used for actually putting text onto the screen -// - origin - pixel point of top left corner on final surface for drawing -// Return Value: -// - S_OK or suitable DirectX/DirectWrite/Direct2D result code. -[[nodiscard]] HRESULT CustomTextLayout::_DrawGlyphRuns(_In_opt_ void* clientDrawingContext, - IDWriteTextRenderer* renderer, - const D2D_POINT_2F origin) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, renderer); - - try - { - // We're going to start from the origin given and walk to the right for each - // sub-run that was calculated by the layout analysis. - auto mutableOrigin = origin; - - // Draw each run separately. - for (auto runIndex = 0; runIndex < gsl::narrow(_runs.size()); ++runIndex) - { - // Get the run - const Run& run = _runs.at(runIndex); - - if (!WI_IsFlagSet(run.bidiLevel, 1)) - { - RETURN_IF_FAILED(_DrawGlyphRun(clientDrawingContext, renderer, mutableOrigin, run)); - } - // This is the RTL behavior. We will advance to the last contiguous RTL run, draw that, - // and then keep on going backwards from there, and then move runIndex beyond. - // Let's say we have runs abcdEFGh, where runs EFG are RTL. - // Then we will draw them in the order abcdGFEh - else - { - const auto originalRunIndex = runIndex; - auto lastIndexRTL = runIndex; - - // Step 1: Get to the last contiguous RTL run from here - while (lastIndexRTL < gsl::narrow(_runs.size()) - 1) // only could ever advance if there's something left - { - const Run& nextRun = _runs.at(gsl::narrow_cast(lastIndexRTL + 1)); - if (WI_IsFlagSet(nextRun.bidiLevel, 1)) - { - lastIndexRTL++; - } - else - { - break; - } - } - - // Go from the last to the first and draw - for (runIndex = lastIndexRTL; runIndex >= originalRunIndex; runIndex--) - { - const Run& currentRun = _runs.at(runIndex); - RETURN_IF_FAILED(_DrawGlyphRun(clientDrawingContext, renderer, mutableOrigin, currentRun)); - } - runIndex = lastIndexRTL; // and the for loop will take the increment to the last one - } - } - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Draw the given run -// - The origin is updated to be after the run. -// Arguments: -// - clientDrawingContext - Optional pointer to information that the renderer might need -// while attempting to graphically place the text onto the screen -// - renderer - The interface to be used for actually putting text onto the screen -// - origin - pixel point of top left corner on final surface for drawing -// - run - the run to be drawn -[[nodiscard]] HRESULT CustomTextLayout::_DrawGlyphRun(_In_opt_ void* clientDrawingContext, - gsl::not_null renderer, - D2D_POINT_2F& mutableOrigin, - const Run& run) noexcept -{ - try - { - // Prepare the glyph run and description objects by converting our - // internal storage representation into something that matches DWrite's structures. - DWRITE_GLYPH_RUN glyphRun; - glyphRun.bidiLevel = run.bidiLevel; - glyphRun.fontEmSize = _formatInUse->GetFontSize() * run.fontScale; - glyphRun.fontFace = run.fontFace.Get(); - glyphRun.glyphAdvances = &_glyphAdvances.at(run.glyphStart); - glyphRun.glyphCount = run.glyphCount; - glyphRun.glyphIndices = &_glyphIndices.at(run.glyphStart); - glyphRun.glyphOffsets = &_glyphOffsets.at(run.glyphStart); - glyphRun.isSideways = false; - - DWRITE_GLYPH_RUN_DESCRIPTION glyphRunDescription; - glyphRunDescription.clusterMap = _glyphClusters.data(); - glyphRunDescription.localeName = _localeName.data(); - glyphRunDescription.string = _text.data(); - glyphRunDescription.stringLength = run.textLength; - glyphRunDescription.textPosition = run.textStart; - - // Calculate the origin for the next run based on the amount of space - // that would be consumed. We are doing this calculation now, not after, - // because if the text is RTL then we need to advance immediately, before the - // write call since DirectX expects the origin to the RIGHT of the text for RTL. - const auto postOriginX = std::accumulate(_glyphAdvances.begin() + run.glyphStart, - _glyphAdvances.begin() + run.glyphStart + run.glyphCount, - mutableOrigin.x); - - // Check for RTL, if it is, apply space adjustment. - if (WI_IsFlagSet(glyphRun.bidiLevel, 1)) - { - mutableOrigin.x = postOriginX; - } - - // Try to draw it - RETURN_IF_FAILED(renderer->DrawGlyphRun(clientDrawingContext, - mutableOrigin.x, - mutableOrigin.y, - DWRITE_MEASURING_MODE_NATURAL, - &glyphRun, - &glyphRunDescription, - run.drawingEffect.Get())); - - // Either way, we should be at this point by the end of writing this sequence, - // whether it was LTR or RTL. - mutableOrigin.x = postOriginX; - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Estimates the maximum number of glyph indices needed to hold a string of -// a given length. This is the formula given in the Uniscribe SDK and should -// cover most cases. Degenerate cases will require a reallocation. -// Arguments: -// - textLength - the number of wchar_ts in the original string -// Return Value: -// - An estimate of how many glyph spaces may be required in the shaping arrays -// to hold the data from a string of the given length. -[[nodiscard]] constexpr UINT32 CustomTextLayout::_EstimateGlyphCount(const UINT32 textLength) noexcept -{ - // This formula is from https://docs.microsoft.com/en-us/windows/desktop/api/dwrite/nf-dwrite-idwritetextanalyzer-getglyphs - // and is the recommended formula for estimating buffer size for glyph count. - return 3 * textLength / 2 + 16; -} - -#pragma region IDWriteTextAnalysisSource methods -// Routine Description: -// - Implementation of IDWriteTextAnalysisSource::GetTextAtPosition -// - This method will retrieve a substring of the text in this layout -// to be used in an analysis step. -// Arguments: -// - textPosition - The index of the first character of the text to retrieve. -// - textString - The pointer to the first character of text at the index requested. -// - textLength - The characters available at/after the textString pointer (string length). -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetTextAtPosition(UINT32 textPosition, - _Outptr_result_buffer_(*textLength) WCHAR const** textString, - _Out_ UINT32* textLength) -{ - RETURN_HR_IF_NULL(E_INVALIDARG, textString); - RETURN_HR_IF_NULL(E_INVALIDARG, textLength); - - *textString = nullptr; - *textLength = 0; - - if (textPosition < _text.size()) - { - *textString = &_text.at(textPosition); - *textLength = gsl::narrow(_text.size()) - textPosition; - } - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSource::GetTextBeforePosition -// - This method will retrieve a substring of the text in this layout -// to be used in an analysis step. -// Arguments: -// - textPosition - The index one after the last character of the text to retrieve. -// - textString - The pointer to the first character of text at the index requested. -// - textLength - The characters available at/after the textString pointer (string length). -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetTextBeforePosition(UINT32 textPosition, - _Outptr_result_buffer_(*textLength) WCHAR const** textString, - _Out_ UINT32* textLength) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, textString); - RETURN_HR_IF_NULL(E_INVALIDARG, textLength); - - *textString = nullptr; - *textLength = 0; - - if (textPosition > 0 && textPosition <= _text.size()) - { - *textString = _text.data(); - *textLength = textPosition; - } - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSource::GetParagraphReadingDirection -// - This returns the implied reading direction for this block of text (LTR/RTL/etc.) -// Arguments: -// - -// Return Value: -// - The reading direction held for this layout from construction -[[nodiscard]] DWRITE_READING_DIRECTION STDMETHODCALLTYPE CustomTextLayout::GetParagraphReadingDirection() noexcept -{ - return _readingDirection; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSource::GetLocaleName -// - Retrieves the locale name to apply to this text. Sometimes analysis and chosen glyphs vary on locale. -// Arguments: -// - textPosition - The index of the first character in the held string for which layout information is needed -// - textLength - How many characters of the string from the index that the returned locale applies to -// - localeName - Zero terminated string of the locale name. -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetLocaleName(UINT32 textPosition, - _Out_ UINT32* textLength, - _Outptr_result_z_ const WCHAR** localeName) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, textLength); - RETURN_HR_IF_NULL(E_INVALIDARG, localeName); - - *localeName = _localeName.data(); - *textLength = gsl::narrow(_text.size()) - textPosition; - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSource::GetNumberSubstitution -// - Retrieves the number substitution object name to apply to this text. -// Arguments: -// - textPosition - The index of the first character in the held string for which layout information is needed -// - textLength - How many characters of the string from the index that the returned locale applies to -// - numberSubstitution - Object to use for substituting numbers inside the determined range -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetNumberSubstitution(UINT32 textPosition, - _Out_ UINT32* textLength, - _COM_Outptr_ IDWriteNumberSubstitution** numberSubstitution) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, textLength); - RETURN_HR_IF_NULL(E_INVALIDARG, numberSubstitution); - - *numberSubstitution = nullptr; - *textLength = gsl::narrow(_text.size()) - textPosition; - - return S_OK; -} -#pragma endregion - -#pragma region IDWriteTextAnalysisSink methods -// Routine Description: -// - Implementation of IDWriteTextAnalysisSink::SetScriptAnalysis -// - Accepts the result of the script analysis computation performed by an IDWriteTextAnalyzer and -// stores it internally for later shaping and drawing purposes. -// Arguments: -// - textPosition - The index of the first character in the string that the result applies to -// - textLength - How many characters of the string from the index that the result applies to -// - scriptAnalysis - The analysis information for all glyphs starting at position for length. -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::SetScriptAnalysis(UINT32 textPosition, - UINT32 textLength, - _In_ const DWRITE_SCRIPT_ANALYSIS* scriptAnalysis) -{ - try - { - _SetCurrentRun(textPosition); - _SplitCurrentRun(textPosition); - while (textLength > 0) - { - auto& run = _FetchNextRun(textLength); - run.script = *scriptAnalysis; - } - } - CATCH_RETURN(); - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSink::SetLineBreakpoints -// - Accepts the result of the line breakpoint computation performed by an IDWriteTextAnalyzer and -// stores it internally for later shaping and drawing purposes. -// Arguments: -// - textPosition - The index of the first character in the string that the result applies to -// - textLength - How many characters of the string from the index that the result applies to -// - scriptAnalysis - The analysis information for all glyphs starting at position for length. -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::SetLineBreakpoints(UINT32 textPosition, - UINT32 textLength, - _In_reads_(textLength) DWRITE_LINE_BREAKPOINT const* lineBreakpoints) -{ - try - { - if (textLength > 0) - { - RETURN_HR_IF_NULL(E_INVALIDARG, lineBreakpoints); - std::copy_n(lineBreakpoints, textLength, _breakpoints.begin() + textPosition); - } - } - CATCH_RETURN(); - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSink::SetBidiLevel -// - Accepts the result of the bidirectional analysis computation performed by an IDWriteTextAnalyzer and -// stores it internally for later shaping and drawing purposes. -// Arguments: -// - textPosition - The index of the first character in the string that the result applies to -// - textLength - How many characters of the string from the index that the result applies to -// - explicitLevel - The analysis information for all glyphs starting at position for length. -// - resolvedLevel - The analysis information for all glyphs starting at position for length. -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::SetBidiLevel(UINT32 textPosition, - UINT32 textLength, - UINT8 /*explicitLevel*/, - UINT8 resolvedLevel) -{ - try - { - _SetCurrentRun(textPosition); - _SplitCurrentRun(textPosition); - while (textLength > 0) - { - auto& run = _FetchNextRun(textLength); - run.bidiLevel = resolvedLevel; - } - } - CATCH_RETURN(); - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSink::SetNumberSubstitution -// - Accepts the result of the number substitution analysis computation performed by an IDWriteTextAnalyzer and -// stores it internally for later shaping and drawing purposes. -// Arguments: -// - textPosition - The index of the first character in the string that the result applies to -// - textLength - How many characters of the string from the index that the result applies to -// - numberSubstitution - The analysis information for all glyphs starting at position for length. -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::SetNumberSubstitution(UINT32 textPosition, - UINT32 textLength, - _In_ IDWriteNumberSubstitution* numberSubstitution) -{ - try - { - _SetCurrentRun(textPosition); - _SplitCurrentRun(textPosition); - while (textLength > 0) - { - auto& run = _FetchNextRun(textLength); - run.isNumberSubstituted = (numberSubstitution != nullptr); - } - } - CATCH_RETURN(); - - return S_OK; -} -#pragma endregion - -#pragma region internal methods for mimicking text analyzer pattern but for font fallback -// Routine Description: -// - Mimics an IDWriteTextAnalyser but for font fallback calculations. -// Arguments: -// - source - a text analysis source to retrieve substrings of the text to be analyzed -// - textPosition - the index to start the substring operation -// - textLength - the length of the substring operation -// Result: -// - S_OK, STL/GSL errors, or a suitable DirectWrite failure code on font fallback analysis. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::_AnalyzeFontFallback(IDWriteTextAnalysisSource* const source, - UINT32 textPosition, - UINT32 textLength) -{ - try - { - // Get the font fallback first - ::Microsoft::WRL::ComPtr format1; - if (FAILED(_formatInUse->QueryInterface(IID_PPV_ARGS(&format1)))) - { - // If IDWriteTextFormat1 does not exist, return directly as this OS version doesn't have font fallback. - return S_FALSE; - } - RETURN_HR_IF_NULL(E_NOINTERFACE, format1); - - ::Microsoft::WRL::ComPtr fallback; - RETURN_IF_FAILED(format1->GetFontFallback(&fallback)); - - ::Microsoft::WRL::ComPtr collection; - RETURN_IF_FAILED(format1->GetFontCollection(&collection)); - - std::wstring familyName; - familyName.resize(gsl::narrow_cast(format1->GetFontFamilyNameLength()) + 1); - RETURN_IF_FAILED(format1->GetFontFamilyName(familyName.data(), gsl::narrow(familyName.size()))); - - const auto weight = format1->GetFontWeight(); - const auto style = format1->GetFontStyle(); - const auto stretch = format1->GetFontStretch(); - - if (!fallback) - { - fallback = _fontRenderData->SystemFontFallback(); - } - - ::Microsoft::WRL::ComPtr fallback1; - ::Microsoft::WRL::ComPtr format3; - - // If the OS supports IDWriteFontFallback1 and IDWriteTextFormat3, we can use the - // newer MapCharacters to apply axes of variation to the font - if (!FAILED(_formatInUse->QueryInterface(IID_PPV_ARGS(&format3))) && !FAILED(fallback->QueryInterface(IID_PPV_ARGS(&fallback1)))) - { - const auto axesVector = _fontRenderData->GetAxisVector(weight, stretch, style, format3.Get()); - // Walk through and analyze the entire string - while (textLength > 0) - { - UINT32 mappedLength = 0; - ::Microsoft::WRL::ComPtr mappedFont; - auto scale = 0.0f; - - fallback1->MapCharacters(source, - textPosition, - textLength, - collection.Get(), - familyName.data(), - axesVector.data(), - gsl::narrow(axesVector.size()), - &mappedLength, - &scale, - &mappedFont); - - RETURN_IF_FAILED(_SetMappedFontFace(textPosition, mappedLength, mappedFont, scale)); - - textPosition += mappedLength; - textLength -= mappedLength; - } - } - else - { - // The chunk of code below is very similar to the one above, unfortunately this needs - // to stay for Win7 compatibility reasons. It is also not possible to combine the two - // because they call different versions of MapCharacters - - // Walk through and analyze the entire string - while (textLength > 0) - { - UINT32 mappedLength = 0; - ::Microsoft::WRL::ComPtr mappedFont; - auto scale = 0.0f; - - fallback->MapCharacters(source, - textPosition, - textLength, - collection.Get(), - familyName.data(), - weight, - style, - stretch, - &mappedLength, - &mappedFont, - &scale); - - RETURN_LAST_ERROR_IF(!mappedFont); - ::Microsoft::WRL::ComPtr face; - RETURN_IF_FAILED(mappedFont->CreateFontFace(&face)); - RETURN_IF_FAILED(_SetMappedFontFace(textPosition, mappedLength, face, scale)); - - textPosition += mappedLength; - textLength -= mappedLength; - } - } - } - CATCH_RETURN(); - - return S_OK; -} - -// Routine Description: -// - Mimics an IDWriteTextAnalysisSink but for font fallback calculations with our -// Analyzer mimic method above. -// Arguments: -// - textPosition - the index to start the substring operation -// - textLength - the length of the substring operation -// - fontFace - the fontFace that applies to the substring range -// - scale - the scale of the font to apply -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::_SetMappedFontFace(UINT32 textPosition, - UINT32 textLength, - const ::Microsoft::WRL::ComPtr& fontFace, - FLOAT const scale) -{ - try - { - _SetCurrentRun(textPosition); - _SplitCurrentRun(textPosition); - while (textLength > 0) - { - auto& run = _FetchNextRun(textLength); - - if (fontFace != nullptr) - { - RETURN_IF_FAILED(fontFace.As(&run.fontFace)); - } - else - { - run.fontFace = _fontInUse; - } - - // Store the font scale as well. - run.fontScale = scale; - } - } - CATCH_RETURN(); - - return S_OK; -} -#pragma endregion - -#pragma region internal methods for mimicking text analyzer to identify and split box drawing regions - -// Routine Description: -// - Helper method to detect if something is a box drawing character. -// Arguments: -// - wch - Specific character. -// Return Value: -// - True if box drawing. False otherwise. -static constexpr bool _IsBoxDrawingCharacter(const wchar_t wch) -{ - if (wch >= 0x2500 && wch <= 0x259F) - { - return true; - } - - return false; -} - -// Routine Description: -// - Corrects all runs for box drawing characteristics. Splits as it walks, if it must. -// If there are fallback fonts, this must happen after that's analyzed and after the -// advances are corrected so we can use the font size scaling factors to determine -// the appropriate layout heights for the correction scale/translate matrix. -// Arguments: -// - - Operates on all runs then orders them back up. -// Return Value: -// - S_OK, STL/GSL errors, or an E_ABORT from mathematical failures. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::_CorrectBoxDrawing() noexcept -try -{ - RETURN_IF_FAILED(_AnalyzeBoxDrawing(this, 0, gsl::narrow(_text.size()))); - _OrderRuns(); - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - An analyzer to walk through the source text and search for runs of box drawing characters. -// It will segment the text into runs of those characters and mark them for special drawing, if necessary. -// Arguments: -// - source - a text analysis source to retrieve substrings of the text to be analyzed -// - textPosition - the index to start the substring operation -// - textLength - the length of the substring operation -// Result: -// - S_OK, STL/GSL errors, or an E_ABORT from mathematical failures. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::_AnalyzeBoxDrawing(const gsl::not_null source, - UINT32 textPosition, - UINT32 textLength) -try -{ - // Walk through and analyze the entire string - while (textLength > 0) - { - // Get the substring of text remaining to analyze. - const WCHAR* text; - UINT32 length; - RETURN_IF_FAILED(source->GetTextAtPosition(textPosition, &text, &length)); - - // Put it into a view for iterator convenience. - const std::wstring_view str(text, length); - - // Find the first box drawing character in the string from the front. - const auto firstBox = std::find_if(str.cbegin(), str.cend(), _IsBoxDrawingCharacter); - - // If we found no box drawing characters, move on with life. - if (firstBox == str.cend()) - { - return S_OK; - } - // If we found one, keep looking forward until we find NOT a box drawing character. - else - { - // Find the last box drawing character. - const auto lastBox = std::find_if(firstBox, str.cend(), [](wchar_t wch) { return !_IsBoxDrawingCharacter(wch); }); - - // Skip distance is how far we had to move forward to find a box. - const auto firstBoxDistance = std::distance(str.cbegin(), firstBox); - UINT32 skipDistance; - RETURN_HR_IF(E_ABORT, !base::MakeCheckedNum(firstBoxDistance).AssignIfValid(&skipDistance)); - - // Move the position/length of the outside counters up to the part where boxes start. - textPosition += skipDistance; - textLength -= skipDistance; - - // Run distance is how many box characters in a row there are. - const auto runDistance = std::distance(firstBox, lastBox); - UINT32 mappedLength; - RETURN_HR_IF(E_ABORT, !base::MakeCheckedNum(runDistance).AssignIfValid(&mappedLength)); - - // Split the run and set the box effect on this segment of the run - RETURN_IF_FAILED(_SetBoxEffect(textPosition, mappedLength)); - - // Move us forward for the outer loop to continue scanning after this point. - textPosition += mappedLength; - textLength -= mappedLength; - } - } - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - A callback to split a run and apply box drawing characteristics to just that sub-run. -// Arguments: -// - textPosition - the index to start the substring operation -// - textLength - the length of the substring operation -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::_SetBoxEffect(UINT32 textPosition, - UINT32 textLength) -try -{ - _SetCurrentRun(textPosition); - _SplitCurrentRun(textPosition); - - while (textLength > 0) - { - auto& run = _FetchNextRun(textLength); - - if (run.fontFace == _fontRenderData->DefaultFontFace()) - { - run.drawingEffect = _fontRenderData->DefaultBoxDrawingEffect(); - } - else - { - ::Microsoft::WRL::ComPtr eff; - RETURN_IF_FAILED(DxFontRenderData::s_CalculateBoxEffect(_formatInUse, _width, run.fontFace.Get(), run.fontScale, &eff)); - - // store data in the run - run.drawingEffect = std::move(eff); - } - } - - return S_OK; -} -CATCH_RETURN(); - -#pragma endregion - -#pragma region internal Run manipulation functions for storing information from sink callbacks -// Routine Description: -// - Used by the sink setters, this returns a reference to the next run. -// Position and length are adjusted to now point after the current run -// being returned. -// Arguments: -// - textLength - The amount of characters for which the next analysis result will apply. -// - The starting index is implicit based on the currently chosen run. -// Return Value: -// - reference to the run needed to store analysis data -[[nodiscard]] CustomTextLayout::LinkedRun& CustomTextLayout::_FetchNextRun(UINT32& textLength) -{ - const auto originalRunIndex = _runIndex; - - auto& run = _runs.at(originalRunIndex); - auto runTextLength = run.textLength; - - // Split the tail if needed (the length remaining is less than the - // current run's size). - if (textLength < runTextLength) - { - runTextLength = textLength; // Limit to what's actually left. - const auto runTextStart = run.textStart; - - _SplitCurrentRun(runTextStart + runTextLength); - } - else - { - // Just advance the current run. - _runIndex = run.nextRunIndex; - } - - textLength -= runTextLength; - - // Return a reference to the run that was just current. - // Careful, we have to look it up again as _SplitCurrentRun can resize the array and reshuffle all the reference locations - return _runs.at(originalRunIndex); -} - -// Routine Description: -// - Retrieves the current run according to the internal -// positioning set by Set/Split Current Run methods. -// Arguments: -// - -// Return Value: -// - Mutable reference of the current run. -[[nodiscard]] CustomTextLayout::LinkedRun& CustomTextLayout::_GetCurrentRun() -{ - return _runs.at(_runIndex); -} - -// Routine Description: -// - Move the current run to the given position. -// Since the analyzers generally return results in a forward manner, -// this will usually just return early. If not, find the -// corresponding run for the text position. -// Arguments: -// - textPosition - The index into the original string for which we want to select the corresponding run -// Return Value: -// - - Updates internal state -void CustomTextLayout::_SetCurrentRun(const UINT32 textPosition) -{ - if (_runIndex < _runs.size() && _runs.at(_runIndex).ContainsTextPosition(textPosition)) - { - return; - } - - _runIndex = gsl::narrow( - std::find(_runs.begin(), _runs.end(), textPosition) - _runs.begin()); -} - -// Routine Description: -// - Splits the current run and adjusts the run values accordingly. -// Arguments: -// - splitPosition - The index into the run where we want to split it into two -// Return Value: -// - - Updates internal state, the back half will be selected after running -void CustomTextLayout::_SplitCurrentRun(const UINT32 splitPosition) -{ - const auto runTextStart = _runs.at(_runIndex).textStart; - - if (splitPosition <= runTextStart) - return; // no change - - // Grow runs by one. - const auto totalRuns = _runs.size(); - try - { - _runs.resize(totalRuns + 1); - } - catch (...) - { - return; // Can't increase size. Return same run. - } - - // Copy the old run to the end. - auto& frontHalf = _runs.at(_runIndex); - auto& backHalf = _runs.back(); - backHalf = frontHalf; - - // Adjust runs' text positions and lengths. - const auto splitPoint = splitPosition - runTextStart; - backHalf.textStart += splitPoint; - backHalf.textLength -= splitPoint; - frontHalf.textLength = splitPoint; - frontHalf.nextRunIndex = gsl::narrow(totalRuns); - _runIndex = gsl::narrow(totalRuns); - - // If there is already a glyph mapping in these runs, - // we need to correct it for the split as well. - // See also (for NxM): - // https://social.msdn.microsoft.com/Forums/en-US/993365bc-8689-45ff-a675-c5ed0c011788/dwriteglyphrundescriptionclustermap-explained - - if (frontHalf.glyphCount > 0) - { - // Starting from this: - // TEXT (_text) - // f i ñ e - // CLUSTERMAP (_glyphClusters) - // 0 0 1 3 - // GLYPH INDICES (_glyphIndices) - // 19 81 23 72 - // With _runs length = 1 - // _runs(0): - // - Text Index: 0 - // - Text Length: 4 - // - Glyph Index: 0 - // - Glyph Length: 4 - // - // If we split at text index = 2 (between i and ñ)... - // ... then this will be the state after the text splitting above: - // - // TEXT (_text) - // f i ñ e - // CLUSTERMAP (_glyphClusters) - // 0 0 1 3 - // GLYPH INDICES (_glyphIndices) - // 19 81 23 72 - // With _runs length = 2 - // _runs(0): - // - Text Index: 0 - // - Text Length: 2 - // - Glyph Index: 0 - // - Glyph Length: 4 - // _runs(1): - // - Text Index: 2 - // - Text Length: 2 - // - Glyph Index: 0 - // - Glyph Length: 4 - // - // Notice that the text index/length values are correct, - // but we haven't fixed up the glyph index/lengths to match. - // We need it to say: - // With _runs length = 2 - // _runs(0): - // - Text Index: 0 - // - Text Length: 2 - // - Glyph Index: 0 - // - Glyph Length: 1 - // _runs(1): - // - Text Index: 2 - // - Text Length: 2 - // - Glyph Index: 1 - // - Glyph Length: 3 - // - // Which means that the cluster map value under the beginning - // of the right-hand text range is our offset to fix all the values. - // In this case, that's 1 corresponding with the ñ. - const auto mapOffset = _glyphClusters.at(backHalf.textStart); - - // The front half's glyph start index (position in _glyphIndices) - // stays the same. - - // The front half's glyph count (items in _glyphIndices to consume) - // is the offset value as that's now one past the end of the front half. - // (and count is end index + 1) - frontHalf.glyphCount = mapOffset; - - // The back half starts at the index that's one past the end of the front - backHalf.glyphStart += mapOffset; - - // And the back half count (since it was copied from the front half above) - // now just needs to be subtracted by how many we gave the front half. - backHalf.glyphCount -= mapOffset; - - // The CLUSTERMAP is also wrong given that it is relative - // to each run. And now there are two runs so the map - // value under the ñ and e need to updated to be relative - // to the text index "2" now instead of the original. - // - // For the entire range of the back half, we need to walk through and - // slide all the glyph mapping values to be relative to the new - // backHalf.glyphStart, or adjust it by the offset we just set it to. - const auto updateBegin = _glyphClusters.begin() + backHalf.textStart; - std::for_each(updateBegin, updateBegin + backHalf.textLength, [mapOffset](UINT16& n) { - n -= mapOffset; - }); - } -} - -// Routine Description: -// - Takes the linked runs stored in the state variable _runs -// and ensures that their vector/array indexes are in order in which they're drawn. -// - This is to be used after splitting and reordering them with the split/select functions -// as those manipulate the runs like a linked list (instead of an ordered array) -// while splitting to reduce copy overhead and just reorder them when complete with this func. -// Arguments: -// - - Manipulates _runs variable. -// Return Value: -// - -void CustomTextLayout::_OrderRuns() -{ - std::sort(_runs.begin(), _runs.end(), [](auto& a, auto& b) { return a.textStart < b.textStart; }); - for (UINT32 i = 0; i < _runs.size() - 1; ++i) - { - til::at(_runs, i).nextRunIndex = i + 1; - } - - _runs.back().nextRunIndex = 0; -} - -#pragma endregion diff --git a/src/renderer/dx/CustomTextLayout.h b/src/renderer/dx/CustomTextLayout.h deleted file mode 100644 index 4ea4da0f4d9..00000000000 --- a/src/renderer/dx/CustomTextLayout.h +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include -#include - -#include -#include -#include - -#include "BoxDrawingEffect.h" -#include "DxFontRenderData.h" -#include "../inc/Cluster.hpp" - -namespace Microsoft::Console::Render -{ - class CustomTextLayout : public ::Microsoft::WRL::RuntimeClass<::Microsoft::WRL::RuntimeClassFlags<::Microsoft::WRL::ClassicCom | ::Microsoft::WRL::InhibitFtmBase>, IDWriteTextAnalysisSource, IDWriteTextAnalysisSink> - { - public: - // Based on the Windows 7 SDK sample at https://github.com/pauldotknopf/WindowsSDK7-Samples/tree/master/multimedia/DirectWrite/CustomLayout - - CustomTextLayout(const gsl::not_null fontRenderData); - - [[nodiscard]] HRESULT STDMETHODCALLTYPE AppendClusters(const std::span clusters); - - [[nodiscard]] HRESULT STDMETHODCALLTYPE Reset() noexcept; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetColumns(_Out_ UINT32* columns); - - // IDWriteTextLayout methods (but we don't actually want to implement them all, so just this one matching the existing interface) - [[nodiscard]] HRESULT STDMETHODCALLTYPE Draw(_In_opt_ void* clientDrawingContext, - _In_ IDWriteTextRenderer* renderer, - FLOAT originX, - FLOAT originY) noexcept; - - // IDWriteTextAnalysisSource methods - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetTextAtPosition(UINT32 textPosition, - _Outptr_result_buffer_(*textLength) WCHAR const** textString, - _Out_ UINT32* textLength) override; - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetTextBeforePosition(UINT32 textPosition, - _Outptr_result_buffer_(*textLength) WCHAR const** textString, - _Out_ UINT32* textLength) noexcept override; - [[nodiscard]] DWRITE_READING_DIRECTION STDMETHODCALLTYPE GetParagraphReadingDirection() noexcept override; - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetLocaleName(UINT32 textPosition, - _Out_ UINT32* textLength, - _Outptr_result_z_ const WCHAR** localeName) noexcept override; - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetNumberSubstitution(UINT32 textPosition, - _Out_ UINT32* textLength, - _COM_Outptr_ IDWriteNumberSubstitution** numberSubstitution) noexcept override; - - // IDWriteTextAnalysisSink methods - [[nodiscard]] HRESULT STDMETHODCALLTYPE SetScriptAnalysis(UINT32 textPosition, - UINT32 textLength, - _In_ const DWRITE_SCRIPT_ANALYSIS* scriptAnalysis) override; - [[nodiscard]] HRESULT STDMETHODCALLTYPE SetLineBreakpoints(UINT32 textPosition, - UINT32 textLength, - _In_reads_(textLength) DWRITE_LINE_BREAKPOINT const* lineBreakpoints) override; - [[nodiscard]] HRESULT STDMETHODCALLTYPE SetBidiLevel(UINT32 textPosition, - UINT32 textLength, - UINT8 explicitLevel, - UINT8 resolvedLevel) override; - [[nodiscard]] HRESULT STDMETHODCALLTYPE SetNumberSubstitution(UINT32 textPosition, - UINT32 textLength, - _In_ IDWriteNumberSubstitution* numberSubstitution) override; - - protected: - // A single contiguous run of characters containing the same analysis results. - struct Run - { - Run() noexcept : - textStart(), - textLength(), - glyphStart(), - glyphCount(), - bidiLevel(), - script(), - isNumberSubstituted(), - isSideways(), - fontFace{ nullptr }, - fontScale{ 1.0 }, - drawingEffect{ nullptr } - { - } - - UINT32 textStart; // starting text position of this run - UINT32 textLength; // number of contiguous code units covered - UINT32 glyphStart; // starting glyph in the glyphs array - UINT32 glyphCount; // number of glyphs associated with this run of text - DWRITE_SCRIPT_ANALYSIS script; - UINT8 bidiLevel; - bool isNumberSubstituted; - bool isSideways; - ::Microsoft::WRL::ComPtr fontFace; - FLOAT fontScale; - ::Microsoft::WRL::ComPtr drawingEffect; - - inline bool ContainsTextPosition(UINT32 desiredTextPosition) const noexcept - { - return desiredTextPosition >= textStart && desiredTextPosition < textStart + textLength; - } - - inline bool operator==(UINT32 desiredTextPosition) const noexcept - { - // Search by text position using std::find - return ContainsTextPosition(desiredTextPosition); - } - }; - - // Single text analysis run, which points to the next run. - struct LinkedRun : Run - { - LinkedRun() noexcept : - nextRunIndex(0) - { - } - - UINT32 nextRunIndex; // index of next run - }; - - [[nodiscard]] LinkedRun& _FetchNextRun(UINT32& textLength); - [[nodiscard]] LinkedRun& _GetCurrentRun(); - void _SetCurrentRun(const UINT32 textPosition); - void _SplitCurrentRun(const UINT32 splitPosition); - void _OrderRuns(); - - [[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeFontFallback(IDWriteTextAnalysisSource* const source, UINT32 textPosition, UINT32 textLength); - [[nodiscard]] HRESULT STDMETHODCALLTYPE _SetMappedFontFace(UINT32 textPosition, UINT32 textLength, const ::Microsoft::WRL::ComPtr& fontFace, FLOAT const scale); - - [[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeBoxDrawing(const gsl::not_null source, UINT32 textPosition, UINT32 textLength); - [[nodiscard]] HRESULT STDMETHODCALLTYPE _SetBoxEffect(UINT32 textPosition, UINT32 textLength); - - [[nodiscard]] HRESULT _AnalyzeTextComplexity() noexcept; - [[nodiscard]] HRESULT _AnalyzeRuns() noexcept; - [[nodiscard]] HRESULT _ShapeGlyphRuns() noexcept; - [[nodiscard]] HRESULT _ShapeGlyphRun(const UINT32 runIndex, UINT32& glyphStart) noexcept; - [[nodiscard]] HRESULT _CorrectGlyphRuns() noexcept; - [[nodiscard]] HRESULT _CorrectGlyphRun(const UINT32 runIndex) noexcept; - [[nodiscard]] HRESULT STDMETHODCALLTYPE _CorrectBoxDrawing() noexcept; - [[nodiscard]] HRESULT _DrawGlyphRuns(_In_opt_ void* clientDrawingContext, - IDWriteTextRenderer* renderer, - const D2D_POINT_2F origin) noexcept; - [[nodiscard]] HRESULT _DrawGlyphRun(_In_opt_ void* clientDrawingContext, - gsl::not_null renderer, - D2D_POINT_2F& mutableOrigin, - const Run& run) noexcept; - - [[nodiscard]] static constexpr UINT32 _EstimateGlyphCount(const UINT32 textLength) noexcept; - - private: - // DirectWrite font render data - DxFontRenderData* _fontRenderData; - - // DirectWrite text formats - IDWriteTextFormat* _formatInUse; - - // DirectWrite font faces - IDWriteFontFace1* _fontInUse; - - // The text we're analyzing and processing into a layout - std::wstring _text; - std::vector _textClusterColumns; - size_t _width; - - // Properties of the text that might be relevant. - std::wstring _localeName; - ::Microsoft::WRL::ComPtr _numberSubstitution; - DWRITE_READING_DIRECTION _readingDirection; - - // Text analysis results - std::vector _runs; - std::vector _breakpoints; - - // Text analysis interim status variable (to assist the Analyzer Sink in operations involving _runs) - UINT32 _runIndex; - - // Glyph shaping results - - // Whether the entire text is determined to be simple and does not require full script shaping. - bool _isEntireTextSimple; - - std::vector _glyphOffsets; - - // Clusters are complicated. They're in respect to each individual run. - // The offsets listed here are in respect to the _text string, but from the beginning index of - // each run. - // That means if we have two runs, we will see 0 1 2 3 4 0 1 2 3 4 5 6 7... in this clusters count. - std::vector _glyphClusters; - - // This appears to be the index of the glyph inside each font. - std::vector _glyphIndices; - - // This is for calculating glyph advances when the entire text is simple. - std::vector _glyphDesignUnitAdvances; - - std::vector _glyphAdvances; - - struct ScaleCorrection - { - UINT32 textIndex; - UINT32 textLength; - float scale; - }; - - // These are used to further break the runs apart and adjust the font size so glyphs fit inside the cells. - std::vector _glyphScaleCorrections; - -#ifdef UNIT_TESTING - public: - CustomTextLayout() = default; - - friend class CustomTextLayoutTests; -#endif - }; -} diff --git a/src/renderer/dx/CustomTextRenderer.cpp b/src/renderer/dx/CustomTextRenderer.cpp deleted file mode 100644 index c217cbfc353..00000000000 --- a/src/renderer/dx/CustomTextRenderer.cpp +++ /dev/null @@ -1,979 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "CustomTextRenderer.h" - -#include "../../inc/DefaultSettings.h" - -#include -#include -#include - -using namespace Microsoft::Console::Render; - -#pragma region IDWritePixelSnapping methods -// Routine Description: -// - Implementation of IDWritePixelSnapping::IsPixelSnappingDisabled -// - Determines if we're allowed to snap text to pixels for this particular drawing context -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - isDisabled - TRUE if we do not snap to nearest pixels. FALSE otherwise. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT CustomTextRenderer::IsPixelSnappingDisabled(void* /*clientDrawingContext*/, - _Out_ BOOL* isDisabled) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, isDisabled); - - *isDisabled = false; - return S_OK; -} - -// Routine Description: -// - Implementation of IDWritePixelSnapping::GetPixelsPerDip -// - Retrieves the number of real monitor pixels to use per device-independent-pixel (DIP) -// - DIPs are used by DirectX all the way until the final drawing surface so things are only -// scaled at the very end and the complexity can be abstracted. -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - pixelsPerDip - The number of pixels per DIP. 96 is standard DPI. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT CustomTextRenderer::GetPixelsPerDip(void* clientDrawingContext, - _Out_ FLOAT* pixelsPerDip) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, pixelsPerDip); - - const DrawingContext* drawingContext = static_cast(clientDrawingContext); - RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext); - - float dpiX, dpiY; - drawingContext->renderTarget->GetDpi(&dpiX, &dpiY); - *pixelsPerDip = dpiX / USER_DEFAULT_SCREEN_DPI; - return S_OK; -} - -// Routine Description: -// - Implementation of IDWritePixelSnapping::GetCurrentTransform -// - Retrieves the matrix transform to be used while laying pixels onto the -// drawing context -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - transform - The matrix transform to use to adapt DIP representations into real monitor coordinates. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT CustomTextRenderer::GetCurrentTransform(void* clientDrawingContext, - DWRITE_MATRIX* transform) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, transform); - - const DrawingContext* drawingContext = static_cast(clientDrawingContext); - RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext); - - // Retrieve as D2D1 matrix then copy into DWRITE matrix. - D2D1_MATRIX_3X2_F d2d1Matrix{ 0 }; - drawingContext->renderTarget->GetTransform(&d2d1Matrix); - - transform->dx = d2d1Matrix.dx; - transform->dy = d2d1Matrix.dy; - transform->m11 = d2d1Matrix.m11; - transform->m12 = d2d1Matrix.m12; - transform->m21 = d2d1Matrix.m21; - transform->m22 = d2d1Matrix.m22; - - return S_OK; -} -#pragma endregion - -#pragma region IDWriteTextRenderer methods -// Routine Description: -// - Implementation of IDWriteTextRenderer::DrawUnderline -// - Directs us to draw an underline on the given context at the given position. -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - baselineOriginX - The text baseline position's X coordinate -// - baselineOriginY - The text baseline position's Y coordinate -// - The baseline is generally not the top nor the bottom of the "cell" that -// text is drawn into. It's usually somewhere "in the middle" and depends on the -// font and the glyphs. It can be calculated during layout and analysis in respect -// to the given font and glyphs. -// - underline - The properties of the underline that we should use for drawing -// - clientDrawingEffect - any special effect to pass along for rendering -// Return Value: -// - S_OK -[[nodiscard]] HRESULT CustomTextRenderer::DrawUnderline(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - _In_ const DWRITE_UNDERLINE* underline, - IUnknown* clientDrawingEffect) noexcept -{ - return _FillRectangle(clientDrawingContext, - clientDrawingEffect, - baselineOriginX, - baselineOriginY + underline->offset, - underline->width, - underline->thickness, - underline->readingDirection, - underline->flowDirection); -} - -// Routine Description: -// - Implementation of IDWriteTextRenderer::DrawStrikethrough -// - Directs us to draw a strikethrough on the given context at the given position. -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - baselineOriginX - The text baseline position's X coordinate -// - baselineOriginY - The text baseline position's Y coordinate -// - The baseline is generally not the top nor the bottom of the "cell" that -// text is drawn into. It's usually somewhere "in the middle" and depends on the -// font and the glyphs. It can be calculated during layout and analysis in respect -// to the given font and glyphs. -// - strikethrough - The properties of the strikethrough that we should use for drawing -// - clientDrawingEffect - any special effect to pass along for rendering -// Return Value: -// - S_OK -[[nodiscard]] HRESULT CustomTextRenderer::DrawStrikethrough(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - _In_ const DWRITE_STRIKETHROUGH* strikethrough, - IUnknown* clientDrawingEffect) noexcept -{ - return _FillRectangle(clientDrawingContext, - clientDrawingEffect, - baselineOriginX, - baselineOriginY + strikethrough->offset, - strikethrough->width, - strikethrough->thickness, - strikethrough->readingDirection, - strikethrough->flowDirection); -} - -// Routine Description: -// - Helper method to draw a line through our text. -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - clientDrawingEffect - any special effect passed along for rendering -// - x - The left coordinate of the rectangle -// - y - The top coordinate of the rectangle -// - width - The width of the rectangle (from X to the right) -// - height - The height of the rectangle (from Y down) -// - readingDirection - textual reading information that could affect the rectangle -// - flowDirection - textual flow information that could affect the rectangle -// Return Value: -// - S_OK -[[nodiscard]] HRESULT CustomTextRenderer::_FillRectangle(void* clientDrawingContext, - IUnknown* clientDrawingEffect, - float x, - float y, - float width, - float thickness, - DWRITE_READING_DIRECTION /*readingDirection*/, - DWRITE_FLOW_DIRECTION /*flowDirection*/) noexcept -{ - auto drawingContext = static_cast(clientDrawingContext); - RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext); - - // Get brush - ID2D1Brush* brush = drawingContext->foregroundBrush; - - if (clientDrawingEffect != nullptr) - { - brush = static_cast(clientDrawingEffect); - } - - const auto rect = D2D1::RectF(x, y, x + width, y + thickness); - drawingContext->renderTarget->FillRectangle(&rect, brush); - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextRenderer::DrawInlineObject -// - Passes drawing control from the outer layout down into the context of an embedded object -// which can have its own drawing layout and renderer properties at a given position -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - originX - The left coordinate of the draw position -// - originY - The top coordinate of the draw position -// - inlineObject - The object to draw at the position -// - isSideways - Should be drawn vertically instead of horizontally -// - isRightToLeft - Should be drawn RTL (or bottom to top) instead of the default way -// - clientDrawingEffect - any special effect passed along for rendering -// Return Value: -// - S_OK or appropriate error from the delegated inline object's draw call -[[nodiscard]] HRESULT CustomTextRenderer::DrawInlineObject(void* clientDrawingContext, - FLOAT originX, - FLOAT originY, - IDWriteInlineObject* inlineObject, - BOOL isSideways, - BOOL isRightToLeft, - IUnknown* clientDrawingEffect) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, inlineObject); - - return inlineObject->Draw(clientDrawingContext, - this, - originX, - originY, - isSideways, - isRightToLeft, - clientDrawingEffect); -} - -// Function Description: -// - Attempt to draw the cursor. If the cursor isn't visible or on, this -// function will do nothing. If the cursor isn't within the bounds of the -// current run of text, then this function will do nothing. -// - This function will get called twice during a run, once before the text is -// drawn (underneath the text), and again after the text is drawn (above the -// text). Depending on if the cursor wants to be drawn above or below the -// text, this function will do nothing for the first/second pass -// (respectively). -// Arguments: -// - d2dContext - Pointer to the current D2D drawing context -// - textRunBounds - The bounds of the current run of text. -// - drawingContext - Pointer to structure of information required to draw -// - firstPass - true if we're being called before the text is drawn, false afterwards. -// Return Value: -// - S_FALSE if we did nothing, S_OK if we successfully painted, otherwise an appropriate HRESULT -[[nodiscard]] HRESULT CustomTextRenderer::DrawCursor(gsl::not_null d2dContext, - D2D1_RECT_F textRunBounds, - const DrawingContext& drawingContext, - const bool firstPass) -try -{ - if (!drawingContext.cursorInfo.has_value()) - { - return S_FALSE; - } - - const auto& options = drawingContext.cursorInfo.value(); - - // if the cursor is off, do nothing - it should not be visible. - if (!options.isOn) - { - return S_FALSE; - } - - const auto fInvert = !options.fUseColor; - // The normal, colored FullBox and legacy cursors are drawn in the first pass - // so they go behind the text. - // Inverted cursors are drawn in two passes. - // All other cursors are drawn in the second pass only. - if (!fInvert) - { - if (firstPass != (options.cursorType == CursorType::FullBox)) - { - return S_FALSE; - } - } - - // TODO GH#6338: Add support for `"cursorTextColor": null` for letting the - // cursor draw on top again. - - // Create rectangular block representing where the cursor can fill. - D2D1_RECT_F rect; - rect.left = options.coordCursor.x * drawingContext.cellSize.width; - rect.top = options.coordCursor.y * drawingContext.cellSize.height; - rect.right = rect.left + drawingContext.cellSize.width; - rect.bottom = rect.top + drawingContext.cellSize.height; - - // If we're double-width, make it one extra glyph wider - if (options.fIsDoubleWidth) - { - rect.right += drawingContext.cellSize.width; - } - - // If the cursor isn't within the bounds of this current run of text, do nothing. - if (rect.top > textRunBounds.bottom || - rect.bottom <= textRunBounds.top || - rect.left > textRunBounds.right || - rect.right <= textRunBounds.left) - { - return S_FALSE; - } - - auto paintType = CursorPaintType::Fill; - switch (options.cursorType) - { - case CursorType::Legacy: - { - // Enforce min/max cursor height - auto ulHeight = std::clamp(options.ulCursorHeightPercent, MinCursorHeightPercent, MaxCursorHeightPercent); - ulHeight = gsl::narrow_cast(drawingContext.cellSize.height * ulHeight) / 100; - ulHeight = std::max(ulHeight, MinCursorHeightPixels); // No smaller than 1px - - rect.top = rect.bottom - ulHeight; - break; - } - case CursorType::VerticalBar: - { - // It can't be wider than one cell or we'll have problems in invalidation, so restrict here. - // It's either the left + the proposed width from the ease of access setting, or - // it's the right edge of the block cursor as a maximum. - rect.right = std::min(rect.right, rect.left + options.cursorPixelWidth); - break; - } - case CursorType::Underscore: - { - rect.top = rect.bottom - 1; - break; - } - case CursorType::DoubleUnderscore: - { - // Use rect for lower line. - rect.top = rect.bottom - 1; - break; - } - case CursorType::EmptyBox: - { - paintType = CursorPaintType::Outline; - break; - } - case CursorType::FullBox: - { - break; - } - default: - return E_NOTIMPL; - } - - // **DRAW** PHASE - Microsoft::WRL::ComPtr brush; - Microsoft::WRL::ComPtr originalTarget; - Microsoft::WRL::ComPtr commandList; - D2D1::Matrix3x2F originalTransform; - if (!fInvert) - { - // Make sure to make the cursor opaque - RETURN_IF_FAILED(d2dContext->CreateSolidColorBrush(til::color{ options.cursorColor }, &brush)); - } - else - { - // CURSOR INVERSION - // We're trying to invert the cursor and the character underneath it without redrawing the text (as - // doing so would break up the run if it were part of a ligature). To do that, we're going to try - // to invert the content of the screen where the cursor would have been. - // - // This renderer, however, supports transparency. In fact, in its default configuration it will not - // have a background at all (it delegates background handling to somebody else.) You can't invert what - // isn't there. - // - // To properly invert the cursor in such a configuration, then, we have to play some tricks. Examples - // are given below for two cursor types, but this applies to all of them. - // - // First, we'll draw a "backplate" in the user's requested background color (with the alpha channel - // set to 0xFF). (firstPass == true) - // - // EMPTY BOX FILLED BOX - // ===== ===== - // = = ===== - // = = ===== - // = = ===== - // ===== ===== - // - // Then, outside of DrawCursor, the glyph is drawn: - // - // EMPTY BOX FILLED BOX - // ==A== ==A== - // =A A= =A=A= - // AAAAA AAAAA - // A A A===A - // A===A A===A - // - // Last, we'll draw the cursor again in all white and use that as the *mask* for inverting the already- - // drawn pixels. (firstPass == false) (# = mask, a = inverted A) - // - // EMPTY BOX FILLED BOX - // ##a## ##a## - // #A A# #a#a# - // aAAAa aaaaa - // a a a###a - // a###a a###a - if (firstPass) - { - // Draw a backplate behind the cursor in the *background* color so that we can invert it later. - // Make sure the cursor is always readable (see gh-3647) - const til::color color{ til::color{ drawingContext.backgroundBrush->GetColor() } ^ RGB(63, 63, 63) }; - RETURN_IF_FAILED(d2dContext->CreateSolidColorBrush(color.with_alpha(255), - &brush)); - } - else - { - // When we're drawing an inverted cursor on the second pass (foreground), we want to draw it into a - // command list, which we will then draw down with MASK_INVERT. We'll draw it in white, - // which will ensure that every component is masked. - RETURN_IF_FAILED(d2dContext->CreateCommandList(&commandList)); - d2dContext->GetTarget(&originalTarget); - d2dContext->SetTarget(commandList.Get()); - // We use an identity transform here to avoid the active transform being applied twice. - d2dContext->GetTransform(&originalTransform); - d2dContext->SetTransform(D2D1::Matrix3x2F::Identity()); - RETURN_IF_FAILED(d2dContext->CreateSolidColorBrush(COLOR_WHITE, &brush)); - } - } - - switch (paintType) - { - case CursorPaintType::Fill: - { - d2dContext->FillRectangle(rect, brush.Get()); - break; - } - case CursorPaintType::Outline: - { - // DrawRectangle in straddles physical pixels in an attempt to draw a line - // between them. To avoid this, bump the rectangle around by half the stroke width. - rect.top += 0.5f; - rect.left += 0.5f; - rect.bottom -= 0.5f; - rect.right -= 0.5f; - - d2dContext->DrawRectangle(rect, brush.Get()); - break; - } - default: - return E_NOTIMPL; - } - - if (options.cursorType == CursorType::DoubleUnderscore) - { - // Draw upper line directly. - auto upperLine = rect; - upperLine.top -= 2; - upperLine.bottom -= 2; - d2dContext->FillRectangle(upperLine, brush.Get()); - } - - if (commandList) - { - // We drew the entire cursor in a command list - // so now we draw that command list using MASK_INVERT over the existing image - RETURN_IF_FAILED(commandList->Close()); - d2dContext->SetTarget(originalTarget.Get()); - d2dContext->SetTransform(originalTransform); - d2dContext->DrawImage(commandList.Get(), D2D1_INTERPOLATION_MODE_LINEAR, D2D1_COMPOSITE_MODE_MASK_INVERT); - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Implementation of IDWriteTextRenderer::DrawInlineObject -// - Passes drawing control from the outer layout down into the context of an embedded object -// which can have its own drawing layout and renderer properties at a given position -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - baselineOriginX - The text baseline position's X coordinate -// - baselineOriginY - The text baseline position's Y coordinate -// - The baseline is generally not the top nor the bottom of the "cell" that -// text is drawn into. It's usually somewhere "in the middle" and depends on the -// font and the glyphs. It can be calculated during layout and analysis in respect -// to the given font and glyphs. -// - measuringMode - The mode to measure glyphs in the DirectWrite context -// - glyphRun - Information on the glyphs -// - glyphRunDescription - Further metadata about the glyphs used while drawing -// - clientDrawingEffect - any special effect passed along for rendering -// Return Value: -// - S_OK, GSL/WIL/STL error, or appropriate DirectX/Direct2D/DirectWrite based error while drawing. -[[nodiscard]] HRESULT CustomTextRenderer::DrawGlyphRun( - void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - DWRITE_MEASURING_MODE measuringMode, - const DWRITE_GLYPH_RUN* glyphRun, - const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, - IUnknown* clientDrawingEffect) -{ - // 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). - auto drawingContext = static_cast(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. - // It's the top left corner. Save that off first. - const auto origin = D2D1::Point2F(baselineOriginX, baselineOriginY); - - // Then make a copy for the baseline origin (which is part way down the left side of the text, not the top or bottom). - // We'll use this baseline Origin for drawing the actual text. - const D2D1_POINT_2F baselineOrigin{ origin.x, origin.y + drawingContext->spacing.baseline }; - - ::Microsoft::WRL::ComPtr d2dContext; - RETURN_IF_FAILED(drawingContext->renderTarget->QueryInterface(d2dContext.GetAddressOf())); - - // Determine clip rectangle - D2D1_RECT_F clipRect; - clipRect.top = origin.y + drawingContext->topClipOffset; - clipRect.bottom = origin.y + drawingContext->cellSize.height - drawingContext->bottomClipOffset; - clipRect.left = 0; - clipRect.right = FLT_MAX; - - // If we already have a clip rectangle, check if it different than the previous one. - if (_clipRect.has_value()) - { - const auto storedVal = _clipRect.value(); - // If it is different, pop off the old one and push the new one on. - if (storedVal.top != clipRect.top || storedVal.bottom != clipRect.bottom || - storedVal.left != clipRect.left || storedVal.right != clipRect.right) - { - d2dContext->PopAxisAlignedClip(); - - // Clip all drawing in this glyph run to where we expect. - // We need the AntialiasMode here to be Aliased to ensure - // that background boxes line up with each other and don't leave behind - // stray colors. - // See GH#3626 for more details. - d2dContext->PushAxisAlignedClip(clipRect, D2D1_ANTIALIAS_MODE_ALIASED); - _clipRect = clipRect; - } - } - // If we have no clip rectangle, it's easy. Push it on and go. - else - { - // See above for aliased flag explanation. - d2dContext->PushAxisAlignedClip(clipRect, D2D1_ANTIALIAS_MODE_ALIASED); - _clipRect = clipRect; - } - - // Draw the background - // The rectangle needs to be deduced based on the origin and the BidiDirection - const auto advancesSpan = std::span{ glyphRun->glyphAdvances, glyphRun->glyphCount }; - const auto totalSpan = std::accumulate(advancesSpan.begin(), advancesSpan.end(), 0.0f); - - D2D1_RECT_F rect; - rect.top = origin.y; - rect.bottom = rect.top + drawingContext->cellSize.height; - rect.left = origin.x; - // Check for RTL, if it is, move rect.left to the left from the baseline - if (WI_IsFlagSet(glyphRun->bidiLevel, 1)) - { - rect.left -= totalSpan; - } - rect.right = rect.left + totalSpan; - - d2dContext->FillRectangle(rect, drawingContext->backgroundBrush); - - RETURN_IF_FAILED(DrawCursor(d2dContext.Get(), rect, *drawingContext, true)); - - // GH#5098: If we're rendering with cleartype text, we need to always render - // onto an opaque background. If our background _isn't_ opaque, then we need - // to use grayscale AA for this run of text. - // - // We can force grayscale AA for just this run of text by pushing a new - // layer onto the d2d context. We'll only need to do this for cleartype - // text, when our eventual background isn't actually opaque. See - // DxEngine::PaintBufferLine and DxEngine::UpdateDrawingBrushes for more - // details. - // - // DANGER: Layers slow us down. Only do this in the specific case where - // someone has chosen the slower ClearType antialiasing (versus the faster - // grayscale antialiasing). - - // First, create the scope_exit to pop the layer. If we don't need the - // layer, we'll just gracefully release it. - auto popLayer = wil::scope_exit([&d2dContext]() noexcept { - d2dContext->PopLayer(); - }); - - if (drawingContext->forceGrayscaleAA) - { - // Mysteriously, D2D1_LAYER_OPTIONS_INITIALIZE_FOR_CLEARTYPE actually - // gets us the behavior we want, which is grayscale. - d2dContext->PushLayer(D2D1::LayerParameters(rect, - nullptr, - D2D1_ANTIALIAS_MODE_ALIASED, - D2D1::IdentityMatrix(), - 1.0, - nullptr, - D2D1_LAYER_OPTIONS_INITIALIZE_FOR_CLEARTYPE), - nullptr); - } - else - { - popLayer.release(); - } - // Now go onto drawing the text. - - // First check if we want a color font and try to extract color emoji first. - // Color emoji are only available on Windows 10+ - static const auto s_isWindows10OrGreater = IsWindows10OrGreater(); - if (WI_IsFlagSet(drawingContext->options, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT) && s_isWindows10OrGreater) - { - ::Microsoft::WRL::ComPtr d2dContext4; - RETURN_IF_FAILED(d2dContext.As(&d2dContext4)); - - ::Microsoft::WRL::ComPtr dwriteFactory4; - RETURN_IF_FAILED(drawingContext->dwriteFactory->QueryInterface(dwriteFactory4.GetAddressOf())); - - // The list of glyph image formats this renderer is prepared to support. - const auto supportedFormats = - DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE | - DWRITE_GLYPH_IMAGE_FORMATS_CFF | - DWRITE_GLYPH_IMAGE_FORMATS_COLR | - DWRITE_GLYPH_IMAGE_FORMATS_SVG | - DWRITE_GLYPH_IMAGE_FORMATS_PNG | - DWRITE_GLYPH_IMAGE_FORMATS_JPEG | - DWRITE_GLYPH_IMAGE_FORMATS_TIFF | - DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8; - - // Determine whether there are any color glyph runs within glyphRun. If - // there are, glyphRunEnumerator can be used to iterate through them. - ::Microsoft::WRL::ComPtr glyphRunEnumerator; - const auto hr = dwriteFactory4->TranslateColorGlyphRun(baselineOrigin, - glyphRun, - glyphRunDescription, - supportedFormats, - measuringMode, - nullptr, - 0, - &glyphRunEnumerator); - - // If the analysis found no color glyphs in the run, just draw normally. - if (hr == DWRITE_E_NOCOLOR) - { - RETURN_IF_FAILED(_DrawBasicGlyphRun(drawingContext, - baselineOrigin, - measuringMode, - glyphRun, - glyphRunDescription, - drawingContext->foregroundBrush, - clientDrawingEffect)); - } - else - { - RETURN_IF_FAILED(hr); - - ::Microsoft::WRL::ComPtr tempBrush; - - // Complex case: the run has one or more color runs within it. Iterate - // over the sub-runs and draw them, depending on their format. - for (;;) - { - BOOL haveRun; - RETURN_IF_FAILED(glyphRunEnumerator->MoveNext(&haveRun)); - if (!haveRun) - break; - - DWRITE_COLOR_GLYPH_RUN1 const* colorRun; - RETURN_IF_FAILED(glyphRunEnumerator->GetCurrentRun(&colorRun)); - - const auto currentBaselineOrigin = D2D1::Point2F(colorRun->baselineOriginX, colorRun->baselineOriginY); - - switch (colorRun->glyphImageFormat) - { - case DWRITE_GLYPH_IMAGE_FORMATS_PNG: - case DWRITE_GLYPH_IMAGE_FORMATS_JPEG: - case DWRITE_GLYPH_IMAGE_FORMATS_TIFF: - case DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8: - { - // This run is bitmap glyphs. Use Direct2D to draw them. - d2dContext4->DrawColorBitmapGlyphRun(colorRun->glyphImageFormat, - currentBaselineOrigin, - &colorRun->glyphRun, - measuringMode); - } - break; - - case DWRITE_GLYPH_IMAGE_FORMATS_SVG: - { - // This run is SVG glyphs. Use Direct2D to draw them. - d2dContext4->DrawSvgGlyphRun(currentBaselineOrigin, - &colorRun->glyphRun, - drawingContext->foregroundBrush, - nullptr, // svgGlyphStyle - 0, // colorPaletteIndex - measuringMode); - } - break; - - case DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE: - case DWRITE_GLYPH_IMAGE_FORMATS_CFF: - case DWRITE_GLYPH_IMAGE_FORMATS_COLR: - default: - { - // This run is solid-color outlines, either from non-color - // glyphs or from COLR glyph layers. Use Direct2D to draw them. - - ID2D1Brush* layerBrush{ nullptr }; - // The rule is "if 0xffff, use current brush." See: - // https://docs.microsoft.com/en-us/windows/desktop/api/dwrite_2/ns-dwrite_2-dwrite_color_glyph_run - if (colorRun->paletteIndex == 0xFFFF) - { - // This run uses the current text color. - layerBrush = drawingContext->foregroundBrush; - } - else - { - if (!tempBrush) - { - RETURN_IF_FAILED(d2dContext4->CreateSolidColorBrush(colorRun->runColor, &tempBrush)); - } - else - { - // This run specifies its own color. - tempBrush->SetColor(colorRun->runColor); - } - layerBrush = tempBrush.Get(); - } - - // Draw the run with the selected color. - RETURN_IF_FAILED(_DrawBasicGlyphRun(drawingContext, - currentBaselineOrigin, - measuringMode, - &colorRun->glyphRun, - colorRun->glyphRunDescription, - layerBrush, - clientDrawingEffect)); - } - break; - } - } - } - } - else - { - // Simple case: the run has no color glyphs. Draw the main glyph run - // using the current text color. - RETURN_IF_FAILED(_DrawBasicGlyphRun(drawingContext, - baselineOrigin, - measuringMode, - glyphRun, - glyphRunDescription, - drawingContext->foregroundBrush, - clientDrawingEffect)); - } - - RETURN_IF_FAILED(DrawCursor(d2dContext.Get(), rect, *drawingContext, false)); - - return S_OK; -} -#pragma endregion - -[[nodiscard]] HRESULT CustomTextRenderer::EndClip(void* clientDrawingContext) noexcept -try -{ - auto drawingContext = static_cast(clientDrawingContext); - RETURN_HR_IF(E_INVALIDARG, !drawingContext); - - if (_clipRect.has_value()) - { - drawingContext->renderTarget->PopAxisAlignedClip(); - _clipRect = std::nullopt; - } - - return S_OK; -} -CATCH_RETURN() - -[[nodiscard]] HRESULT CustomTextRenderer::_DrawBasicGlyphRun(DrawingContext* clientDrawingContext, - D2D1_POINT_2F baselineOrigin, - DWRITE_MEASURING_MODE measuringMode, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, - ID2D1Brush* brush, - _In_opt_ IUnknown* clientDrawingEffect) -{ - RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingContext); - RETURN_HR_IF_NULL(E_INVALIDARG, glyphRun); - RETURN_HR_IF_NULL(E_INVALIDARG, brush); - - ::Microsoft::WRL::ComPtr d2dContext; - RETURN_IF_FAILED(clientDrawingContext->renderTarget->QueryInterface(d2dContext.GetAddressOf())); - - // If a special drawing effect was specified, see if we know how to deal with it. - if (clientDrawingEffect) - { - ::Microsoft::WRL::ComPtr boxEffect; - if (SUCCEEDED(clientDrawingEffect->QueryInterface(&boxEffect))) - { - return _DrawBoxRunManually(clientDrawingContext, baselineOrigin, measuringMode, glyphRun, glyphRunDescription, boxEffect.Get()); - } - - //_DrawBasicGlyphRunManually(clientDrawingContext, baselineOrigin, measuringMode, glyphRun, glyphRunDescription); - //_DrawGlowGlyphRun(clientDrawingContext, baselineOrigin, measuringMode, glyphRun, glyphRunDescription); - } - - // If we get down here, there either was no special effect or we don't know what to do with it. Use the standard GlyphRun drawing. - - // Using the context is the easiest/default way of drawing. - d2dContext->DrawGlyphRun(baselineOrigin, glyphRun, glyphRunDescription, brush, measuringMode); - - return S_OK; -} - -[[nodiscard]] HRESULT CustomTextRenderer::_DrawBoxRunManually(DrawingContext* clientDrawingContext, - D2D1_POINT_2F baselineOrigin, - DWRITE_MEASURING_MODE /*measuringMode*/, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* /*glyphRunDescription*/, - _In_ IBoxDrawingEffect* clientDrawingEffect) noexcept -try -{ - RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingContext); - RETURN_HR_IF_NULL(E_INVALIDARG, glyphRun); - RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingEffect); - - ::Microsoft::WRL::ComPtr d2dFactory; - clientDrawingContext->renderTarget->GetFactory(d2dFactory.GetAddressOf()); - - ::Microsoft::WRL::ComPtr pathGeometry; - d2dFactory->CreatePathGeometry(pathGeometry.GetAddressOf()); - - ::Microsoft::WRL::ComPtr geometrySink; - pathGeometry->Open(geometrySink.GetAddressOf()); - - glyphRun->fontFace->GetGlyphRunOutline( - glyphRun->fontEmSize, - glyphRun->glyphIndices, - glyphRun->glyphAdvances, - glyphRun->glyphOffsets, - glyphRun->glyphCount, - glyphRun->isSideways, - glyphRun->bidiLevel % 2, - geometrySink.Get()); - - geometrySink->Close(); - - // Can be used to see the dimensions of what is written. - /*D2D1_RECT_F bounds; - pathGeometry->GetBounds(D2D1::IdentityMatrix(), &bounds);*/ - // The bounds here are going to be centered around the baseline of the font. - // That is, the DWRITE_GLYPH_METRICS property for this glyph's baseline is going - // to be at the 0 point in the Y direction when we receive the geometry. - // The ascent will go up negative from Y=0 and the descent will go down positive from Y=0. - // As for the horizontal direction, I didn't study this in depth, but it appears to always be - // positive X with both the left and right edges being positive and away from X=0. - // For one particular instance, we might ask for the geometry for a U+2588 box and see the bounds as: - // - // Top= - // -20.315 - // ----------- - // | | - // | | - // Left= | | Right= - // 13.859 | | 26.135 - // | | - // Origin --> X | | - // (0,0) | | - // ----------- - // Bottom= - // 5.955 - - // Dig out the box drawing effect parameters. - BoxScale scale; - RETURN_IF_FAILED(clientDrawingEffect->GetScale(&scale)); - - // The scale transform will inflate the entire geometry first. - // We want to do this before it moves out of its original location as generally our - // algorithms for fitting cells will blow up the glyph to the size it needs to be first and then - // nudge it into place with the translations. - const auto scaleTransform = D2D1::Matrix3x2F::Scale(scale.HorizontalScale, scale.VerticalScale); - - // Now shift it all the way to where the baseline says it should be. - const auto baselineTransform = D2D1::Matrix3x2F::Translation(baselineOrigin.x, baselineOrigin.y); - - // Finally apply the little "nudge" that we may have been directed to align it better with the cell. - const auto offsetTransform = D2D1::Matrix3x2F::Translation(scale.HorizontalTranslation, scale.VerticalTranslation); - - // The order is important here. Scale it first, then slide it into place. - const auto matrixTransformation = scaleTransform * baselineTransform * offsetTransform; - - ::Microsoft::WRL::ComPtr transformedGeometry; - d2dFactory->CreateTransformedGeometry(pathGeometry.Get(), - &matrixTransformation, - transformedGeometry.GetAddressOf()); - - // Can be used to see the dimensions after translation. - /*D2D1_RECT_F boundsAfter; - transformedGeometry->GetBounds(D2D1::IdentityMatrix(), &boundsAfter);*/ - // Compare this to the original bounds above to see what the matrix did. - // To make it useful, first visualize for yourself the pixel dimensions of the cell - // based on the baselineOrigin and the exact integer cell width and heights that we're storing. - // You'll also probably need the full-pixel ascent and descent because the point we're given - // is the baseline, not the top left corner of the cell as we're used to. - // Most of these metrics can be found in the initial font creation routines or in - // the line spacing applied to the text format (member variables on the renderer). - // baselineOrigin = (0, 567) - // fullPixelAscent = 39 - // fullPixelDescent = 9 - // cell dimensions = 26 x 48 (notice 48 height is 39 + 9 or ascent + descent) - // This means that our cell should be the rectangle - // - // T=528 - // |-------| - // L=0 | | - // | | - // Baseline->x | - // Origin | | R=26 - // |-------| - // B=576 - // - // And we'll want to check that the bounds after transform will fit the glyph nicely inside - // this box. - // If not? We didn't do the scaling or translation correctly. Oops. - - // Fill in the geometry. Don't outline, it can leave stuff outside the area we expect. - clientDrawingContext->renderTarget->FillGeometry(transformedGeometry.Get(), clientDrawingContext->foregroundBrush); - - return S_OK; -} -CATCH_RETURN(); - -[[nodiscard]] HRESULT CustomTextRenderer::_DrawGlowGlyphRun(DrawingContext* clientDrawingContext, - D2D1_POINT_2F baselineOrigin, - DWRITE_MEASURING_MODE /*measuringMode*/, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* /*glyphRunDescription*/) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingContext); - RETURN_HR_IF_NULL(E_INVALIDARG, glyphRun); - - // This is glow text manually - ::Microsoft::WRL::ComPtr d2dFactory; - clientDrawingContext->renderTarget->GetFactory(d2dFactory.GetAddressOf()); - - ::Microsoft::WRL::ComPtr pathGeometry; - d2dFactory->CreatePathGeometry(pathGeometry.GetAddressOf()); - - ::Microsoft::WRL::ComPtr geometrySink; - pathGeometry->Open(geometrySink.GetAddressOf()); - - glyphRun->fontFace->GetGlyphRunOutline( - glyphRun->fontEmSize, - glyphRun->glyphIndices, - glyphRun->glyphAdvances, - glyphRun->glyphOffsets, - glyphRun->glyphCount, - glyphRun->isSideways, - glyphRun->bidiLevel % 2, - geometrySink.Get()); - - geometrySink->Close(); - - const auto matrixAlign = D2D1::Matrix3x2F::Translation(baselineOrigin.x, baselineOrigin.y); - - ::Microsoft::WRL::ComPtr transformedGeometry; - d2dFactory->CreateTransformedGeometry(pathGeometry.Get(), - &matrixAlign, - transformedGeometry.GetAddressOf()); - - ::Microsoft::WRL::ComPtr alignedGeometry; - d2dFactory->CreateTransformedGeometry(pathGeometry.Get(), - &matrixAlign, - alignedGeometry.GetAddressOf()); - - ::Microsoft::WRL::ComPtr brush; - ::Microsoft::WRL::ComPtr outlineBrush; - - clientDrawingContext->renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White, 1.0f), brush.GetAddressOf()); - clientDrawingContext->renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red, 1.0f), outlineBrush.GetAddressOf()); - - clientDrawingContext->renderTarget->DrawGeometry(transformedGeometry.Get(), outlineBrush.Get(), 2.0f); - - clientDrawingContext->renderTarget->FillGeometry(alignedGeometry.Get(), brush.Get()); - - return S_OK; -} diff --git a/src/renderer/dx/CustomTextRenderer.h b/src/renderer/dx/CustomTextRenderer.h deleted file mode 100644 index 188b843d950..00000000000 --- a/src/renderer/dx/CustomTextRenderer.h +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include -#include "BoxDrawingEffect.h" -#include "../../renderer/inc/CursorOptions.h" - -namespace Microsoft::Console::Render -{ - struct DrawingContext - { - DrawingContext(ID2D1RenderTarget* renderTarget, - ID2D1SolidColorBrush* foregroundBrush, - ID2D1SolidColorBrush* backgroundBrush, - bool forceGrayscaleAA, - IDWriteFactory* dwriteFactory, - const DWRITE_LINE_SPACING spacing, - const D2D_SIZE_F cellSize, - const D2D_SIZE_F targetSize, - const std::optional& cursorInfo, - const D2D1_DRAW_TEXT_OPTIONS options = D2D1_DRAW_TEXT_OPTIONS_NONE) noexcept : - renderTarget(renderTarget), - foregroundBrush(foregroundBrush), - backgroundBrush(backgroundBrush), - useBoldFont(false), - useItalicFont(false), - forceGrayscaleAA(forceGrayscaleAA), - dwriteFactory(dwriteFactory), - spacing(spacing), - cellSize(cellSize), - targetSize(targetSize), - cursorInfo(cursorInfo), - options(options), - topClipOffset(0), - bottomClipOffset(0) - { - } - - ID2D1RenderTarget* renderTarget; - ID2D1SolidColorBrush* foregroundBrush; - ID2D1SolidColorBrush* backgroundBrush; - bool useBoldFont; - bool useItalicFont; - bool forceGrayscaleAA; - IDWriteFactory* dwriteFactory; - DWRITE_LINE_SPACING spacing; - D2D_SIZE_F cellSize; - D2D_SIZE_F targetSize; - std::optional cursorInfo; - D2D1_DRAW_TEXT_OPTIONS options; - FLOAT topClipOffset; - FLOAT bottomClipOffset; - }; - - // Helper to choose which Direct2D method to use when drawing the cursor rectangle - enum class CursorPaintType - { - Fill, - Outline - }; - - constexpr const ULONG MinCursorHeightPixels = 1; - constexpr const ULONG MinCursorHeightPercent = 1; - constexpr const ULONG MaxCursorHeightPercent = 100; - - class CustomTextRenderer : public ::Microsoft::WRL::RuntimeClass<::Microsoft::WRL::RuntimeClassFlags<::Microsoft::WRL::ClassicCom | ::Microsoft::WRL::InhibitFtmBase>, IDWriteTextRenderer> - { - public: - // http://www.charlespetzold.com/blog/2014/01/Character-Formatting-Extensions-with-DirectWrite.html - // https://docs.microsoft.com/en-us/windows/desktop/DirectWrite/how-to-implement-a-custom-text-renderer - - // IDWritePixelSnapping methods - [[nodiscard]] HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(void* clientDrawingContext, - _Out_ BOOL* isDisabled) noexcept override; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetPixelsPerDip(void* clientDrawingContext, - _Out_ FLOAT* pixelsPerDip) noexcept override; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetCurrentTransform(void* clientDrawingContext, - _Out_ DWRITE_MATRIX* transform) noexcept override; - - // IDWriteTextRenderer methods - [[nodiscard]] HRESULT STDMETHODCALLTYPE DrawGlyphRun(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - DWRITE_MEASURING_MODE measuringMode, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, - IUnknown* clientDrawingEffect) override; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE DrawUnderline(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - _In_ const DWRITE_UNDERLINE* underline, - IUnknown* clientDrawingEffect) noexcept override; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE DrawStrikethrough(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - _In_ const DWRITE_STRIKETHROUGH* strikethrough, - IUnknown* clientDrawingEffect) noexcept override; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE DrawInlineObject(void* clientDrawingContext, - FLOAT originX, - FLOAT originY, - IDWriteInlineObject* inlineObject, - BOOL isSideways, - BOOL isRightToLeft, - IUnknown* clientDrawingEffect) noexcept override; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE EndClip(void* clientDrawingContext) noexcept; - - [[nodiscard]] static HRESULT DrawCursor(gsl::not_null d2dContext, - D2D1_RECT_F textRunBounds, - const DrawingContext& drawingContext, - const bool firstPass); - - private: - [[nodiscard]] HRESULT _FillRectangle(void* clientDrawingContext, - IUnknown* clientDrawingEffect, - float x, - float y, - float width, - float thickness, - DWRITE_READING_DIRECTION readingDirection, - DWRITE_FLOW_DIRECTION flowDirection) noexcept; - - [[nodiscard]] HRESULT _DrawBasicGlyphRun(DrawingContext* clientDrawingContext, - D2D1_POINT_2F baselineOrigin, - DWRITE_MEASURING_MODE measuringMode, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, - ID2D1Brush* brush, - _In_opt_ IUnknown* clientDrawingEffect); - - [[nodiscard]] HRESULT _DrawBoxRunManually(DrawingContext* clientDrawingContext, - D2D1_POINT_2F baselineOrigin, - DWRITE_MEASURING_MODE measuringMode, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, - _In_ IBoxDrawingEffect* clientDrawingEffect) noexcept; - - [[nodiscard]] HRESULT _DrawGlowGlyphRun(DrawingContext* clientDrawingContext, - D2D1_POINT_2F baselineOrigin, - DWRITE_MEASURING_MODE measuringMode, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription) noexcept; - - std::optional _clipRect; - }; -} diff --git a/src/renderer/dx/DxFontInfo.cpp b/src/renderer/dx/DxFontInfo.cpp deleted file mode 100644 index b0f79cde39a..00000000000 --- a/src/renderer/dx/DxFontInfo.cpp +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "DxFontInfo.h" - -#include -#include - -#include "../base/FontCache.h" - -static constexpr std::wstring_view FALLBACK_FONT_FACES[] = { L"Consolas", L"Lucida Console", L"Courier New" }; - -using namespace Microsoft::Console::Render; - -DxFontInfo::DxFontInfo(IDWriteFactory1* dwriteFactory) : - DxFontInfo{ dwriteFactory, {}, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL } -{ -} - -DxFontInfo::DxFontInfo( - IDWriteFactory1* dwriteFactory, - std::wstring_view familyName, - DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch) : - _familyName(familyName), - _weight(weight), - _style(style), - _stretch(stretch), - _didFallback(false) -{ - __assume(dwriteFactory != nullptr); - THROW_IF_FAILED(dwriteFactory->GetSystemFontCollection(_fontCollection.addressof(), FALSE)); -} - -bool DxFontInfo::operator==(const DxFontInfo& other) const noexcept -{ - return (_familyName == other._familyName && - _weight == other._weight && - _style == other._style && - _stretch == other._stretch && - _didFallback == other._didFallback); -} - -std::wstring_view DxFontInfo::GetFamilyName() const noexcept -{ - return _familyName; -} - -void DxFontInfo::SetFamilyName(const std::wstring_view familyName) -{ - _familyName = familyName; -} - -DWRITE_FONT_WEIGHT DxFontInfo::GetWeight() const noexcept -{ - return _weight; -} - -void DxFontInfo::SetWeight(const DWRITE_FONT_WEIGHT weight) noexcept -{ - _weight = weight; -} - -DWRITE_FONT_STYLE DxFontInfo::GetStyle() const noexcept -{ - return _style; -} - -void DxFontInfo::SetStyle(const DWRITE_FONT_STYLE style) noexcept -{ - _style = style; -} - -DWRITE_FONT_STRETCH DxFontInfo::GetStretch() const noexcept -{ - return _stretch; -} - -void DxFontInfo::SetStretch(const DWRITE_FONT_STRETCH stretch) noexcept -{ - _stretch = stretch; -} - -bool DxFontInfo::GetFallback() const noexcept -{ - return _didFallback; -} - -IDWriteFontCollection* DxFontInfo::GetFontCollection() const noexcept -{ - return _fontCollection.get(); -} - -void DxFontInfo::SetFromEngine(const std::wstring_view familyName, - const DWRITE_FONT_WEIGHT weight, - const DWRITE_FONT_STYLE style, - const DWRITE_FONT_STRETCH stretch) -{ - _familyName = familyName; - _weight = weight; - _style = style; - _stretch = stretch; -} - -// Routine Description: -// - Attempts to locate the font given, but then begins falling back if we cannot find it. -// - We'll try to fall back to Consolas with the given weight/stretch/style first, -// then try Consolas again with normal weight/stretch/style, -// and if nothing works, then we'll throw an error. -// Arguments: -// - dwriteFactory - The DWrite factory to use -// - localeName - Locale to search for appropriate fonts -// Return Value: -// - Smart pointer holding interface reference for queryable font data. -[[nodiscard]] Microsoft::WRL::ComPtr DxFontInfo::ResolveFontFaceWithFallback(std::wstring& localeName) -{ - // First attempt to find exactly what the user asked for. - _didFallback = false; - Microsoft::WRL::ComPtr face{ nullptr }; - - // GH#10211 - wrap this all up in a try/catch. If the nearby fonts are - // corrupted, then we don't want to throw out of this top half of this - // method. We still want to fall back to a font that's reasonable, below. - try - { - face = _FindFontFace(localeName); - } - CATCH_LOG(); - - if constexpr (Feature_NearbyFontLoading::IsEnabled()) - { - try - { - if (!face) - { - _fontCollection = FontCache::GetCached(); - face = _FindFontFace(localeName); - } - } - CATCH_LOG(); - } - - if (!face) - { - // If we missed, try looking a little more by trimming the last word off the requested family name a few times. - // Quite often, folks are specifying weights or something in the familyName and it causes failed resolution and - // an unexpected error dialog. We theoretically could detect the weight words and convert them, but this - // is the quick fix for the majority scenario. - // The long/full fix is backlogged to GH#9744 - // Also this doesn't count as a fallback because we don't want to annoy folks with the warning dialog over - // this resolution. - while (!face && !_familyName.empty()) - { - const auto lastSpace = _familyName.find_last_of(UNICODE_SPACE); - - // value is unsigned and npos will be greater than size. - // if we didn't find anything to trim, leave. - if (lastSpace >= _familyName.size()) - { - break; - } - - // trim string down to just before the found space - // (space found at 6... trim from 0 for 6 length will give us 0-5 as the new string) - _familyName = _familyName.substr(0, lastSpace); - - try - { - // Try to find it with the shortened family name - face = _FindFontFace(localeName); - } - CATCH_LOG(); - } - } - - // Alright, if our quick shot at trimming didn't work either... - // move onto looking up a font from our hard-coded list of fonts - // that should really always be available. - if (!face) - { - for (const auto fallbackFace : FALLBACK_FONT_FACES) - { - _familyName = fallbackFace; - - try - { - face = _FindFontFace(localeName); - } - CATCH_LOG(); - - if (face) - { - _didFallback = true; - break; - } - } - } - - THROW_HR_IF_NULL(E_FAIL, face); - - return face; -} - -// Routine Description: -// - Locates a suitable font face from the given information -// Arguments: -// - dwriteFactory - The DWrite factory to use -// - localeName - Locale to search for appropriate fonts -// Return Value: -// - Smart pointer holding interface reference for queryable font data. -#pragma warning(suppress : 26429) // C26429: Symbol 'fontCollection' is never tested for nullness, it can be marked as not_null (f.23). -[[nodiscard]] Microsoft::WRL::ComPtr DxFontInfo::_FindFontFace(std::wstring& localeName) -{ - Microsoft::WRL::ComPtr fontFace; - - UINT32 familyIndex; - BOOL familyExists; - - THROW_IF_FAILED(_fontCollection->FindFamilyName(_familyName.data(), &familyIndex, &familyExists)); - - if (familyExists) - { - Microsoft::WRL::ComPtr fontFamily; - THROW_IF_FAILED(_fontCollection->GetFontFamily(familyIndex, &fontFamily)); - - Microsoft::WRL::ComPtr font; - THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(GetWeight(), GetStretch(), GetStyle(), &font)); - - Microsoft::WRL::ComPtr fontFace0; - THROW_IF_FAILED(font->CreateFontFace(&fontFace0)); - - THROW_IF_FAILED(fontFace0.As(&fontFace)); - - // Retrieve metrics in case the font we created was different than what was requested. - _weight = font->GetWeight(); - _stretch = font->GetStretch(); - _style = font->GetStyle(); - - // Dig the family name out at the end to return it. - _familyName = _GetFontFamilyName(fontFamily.Get(), localeName); - } - - return fontFace; -} - -// Routine Description: -// - Retrieves the font family name out of the given object in the given locale. -// - If we can't find a valid name for the given locale, we'll fallback and report it back. -// Arguments: -// - fontFamily - DirectWrite font family object -// - localeName - The locale in which the name should be retrieved. -// - If fallback occurred, this is updated to what we retrieved instead. -// Return Value: -// - Localized string name of the font family -[[nodiscard]] std::wstring DxFontInfo::_GetFontFamilyName(const gsl::not_null fontFamily, - std::wstring& localeName) -{ - // See: https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontcollection - Microsoft::WRL::ComPtr familyNames; - THROW_IF_FAILED(fontFamily->GetFamilyNames(&familyNames)); - - // First we have to find the right family name for the locale. We're going to bias toward what the caller - // requested, but fallback if we need to and reply with the locale we ended up choosing. - UINT32 index = 0; - BOOL exists = false; - - // This returns S_OK whether or not it finds a locale name. Check exists field instead. - // If it returns an error, it's a real problem, not an absence of this locale name. - // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-findlocalename - THROW_IF_FAILED(familyNames->FindLocaleName(localeName.data(), &index, &exists)); - - // If we tried and it still doesn't exist, try with the fallback locale. - if (!exists) - { - localeName = L"en-us"; - THROW_IF_FAILED(familyNames->FindLocaleName(localeName.data(), &index, &exists)); - } - - // If it still doesn't exist, we're going to try index 0. - if (!exists) - { - index = 0; - - // Get the locale name out so at least the caller knows what locale this name goes with. - UINT32 length = 0; - THROW_IF_FAILED(familyNames->GetLocaleNameLength(index, &length)); - localeName.resize(length); - - // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getlocalenamelength - // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getlocalename - // GetLocaleNameLength does not include space for null terminator, but GetLocaleName needs it so add one. - THROW_IF_FAILED(familyNames->GetLocaleName(index, localeName.data(), length + 1)); - } - - // OK, now that we've decided which family name and the locale that it's in... let's go get it. - UINT32 length = 0; - THROW_IF_FAILED(familyNames->GetStringLength(index, &length)); - - // Make our output buffer and resize it so it is allocated. - std::wstring retVal; - retVal.resize(length); - - // FINALLY, go fetch the string name. - // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getstringlength - // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getstring - // Once again, GetStringLength is without the null, but GetString needs the null. So add one. - THROW_IF_FAILED(familyNames->GetString(index, retVal.data(), length + 1)); - - // and return it. - return retVal; -} diff --git a/src/renderer/dx/DxFontInfo.h b/src/renderer/dx/DxFontInfo.h deleted file mode 100644 index c9ed0a41e2c..00000000000 --- a/src/renderer/dx/DxFontInfo.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include -#include -#include -#include - -namespace Microsoft::Console::Render -{ - class DxFontInfo - { - public: - DxFontInfo(IDWriteFactory1* dwriteFactory); - - DxFontInfo( - IDWriteFactory1* dwriteFactory, - std::wstring_view familyName, - DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch); - - bool operator==(const DxFontInfo& other) const noexcept; - - std::wstring_view GetFamilyName() const noexcept; - void SetFamilyName(const std::wstring_view familyName); - - DWRITE_FONT_WEIGHT GetWeight() const noexcept; - void SetWeight(const DWRITE_FONT_WEIGHT weight) noexcept; - - DWRITE_FONT_STYLE GetStyle() const noexcept; - void SetStyle(const DWRITE_FONT_STYLE style) noexcept; - - DWRITE_FONT_STRETCH GetStretch() const noexcept; - void SetStretch(const DWRITE_FONT_STRETCH stretch) noexcept; - - bool GetFallback() const noexcept; - IDWriteFontCollection* GetFontCollection() const noexcept; - - void SetFromEngine(const std::wstring_view familyName, - const DWRITE_FONT_WEIGHT weight, - const DWRITE_FONT_STYLE style, - const DWRITE_FONT_STRETCH stretch); - - [[nodiscard]] ::Microsoft::WRL::ComPtr ResolveFontFaceWithFallback(std::wstring& localeName); - - private: - [[nodiscard]] ::Microsoft::WRL::ComPtr _FindFontFace(std::wstring& localeName); - - [[nodiscard]] std::wstring _GetFontFamilyName(const gsl::not_null fontFamily, - std::wstring& localeName); - - // The font name we should be looking for - std::wstring _familyName; - - // The weight (bold, light, etc.) - DWRITE_FONT_WEIGHT _weight; - - // Normal, italic, etc. - DWRITE_FONT_STYLE _style; - - // The stretch of the font is the spacing between each letter - DWRITE_FONT_STRETCH _stretch; - - wil::com_ptr _fontCollection; - - // Indicates whether we couldn't match the user request and had to choose from a hard-coded default list. - bool _didFallback; - }; -} diff --git a/src/renderer/dx/DxFontRenderData.cpp b/src/renderer/dx/DxFontRenderData.cpp deleted file mode 100644 index 7765067d0b7..00000000000 --- a/src/renderer/dx/DxFontRenderData.cpp +++ /dev/null @@ -1,923 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "DxFontRenderData.h" - -#include - -static constexpr float POINTS_PER_INCH = 72.0f; -static constexpr std::wstring_view FALLBACK_LOCALE = L"en-us"; -static constexpr size_t TAG_LENGTH = 4; - -using namespace Microsoft::Console::Render; - -DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr dwriteFactory) : - _dwriteFactory(std::move(dwriteFactory)), - _defaultFontInfo{ _dwriteFactory.Get() }, - _lineSpacing{}, - _lineMetrics{}, - _fontSize{} -{ -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::Analyzer() -{ - if (!_dwriteTextAnalyzer) - { - Microsoft::WRL::ComPtr analyzer; - THROW_IF_FAILED(_dwriteFactory->CreateTextAnalyzer(&analyzer)); - THROW_IF_FAILED(analyzer.As(&_dwriteTextAnalyzer)); - } - - return _dwriteTextAnalyzer; -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::SystemFontFallback() -{ - if (!_systemFontFallback) - { - ::Microsoft::WRL::ComPtr factory2; - THROW_IF_FAILED(_dwriteFactory.As(&factory2)); - factory2->GetSystemFontFallback(&_systemFontFallback); - } - - return _systemFontFallback; -} - -[[nodiscard]] std::wstring DxFontRenderData::UserLocaleName() -{ - if (_userLocaleName.empty()) - { - std::array localeName; - - const auto returnCode = GetUserDefaultLocaleName(localeName.data(), gsl::narrow(localeName.size())); - if (returnCode) - { - _userLocaleName = { localeName.data() }; - } - else - { - _userLocaleName = { FALLBACK_LOCALE.data(), FALLBACK_LOCALE.size() }; - } - } - - return _userLocaleName; -} - -[[nodiscard]] til::size DxFontRenderData::GlyphCell() noexcept -{ - return _glyphCell; -} - -[[nodiscard]] DxFontRenderData::LineMetrics DxFontRenderData::GetLineMetrics() noexcept -{ - return _lineMetrics; -} - -[[nodiscard]] DWRITE_FONT_WEIGHT DxFontRenderData::DefaultFontWeight() noexcept -{ - return _defaultFontInfo.GetWeight(); -} - -[[nodiscard]] DWRITE_FONT_STYLE DxFontRenderData::DefaultFontStyle() noexcept -{ - return _defaultFontInfo.GetStyle(); -} - -[[nodiscard]] DWRITE_FONT_STRETCH DxFontRenderData::DefaultFontStretch() noexcept -{ - return _defaultFontInfo.GetStretch(); -} - -[[nodiscard]] const std::vector& DxFontRenderData::DefaultFontFeatures() const noexcept -{ - return _featureVector; -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::DefaultTextFormat() -{ - return TextFormatWithAttribute(_defaultFontInfo.GetWeight(), _defaultFontInfo.GetStyle(), _defaultFontInfo.GetStretch()); -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::DefaultFontFace() -{ - return FontFaceWithAttribute(_defaultFontInfo.GetWeight(), _defaultFontInfo.GetStyle(), _defaultFontInfo.GetStretch()); -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::DefaultBoxDrawingEffect() -{ - if (!_boxDrawingEffect) - { - // Calculate and cache the box effect for the base font. Scale is 1.0f because the base font is exactly the scale we want already. - THROW_IF_FAILED(s_CalculateBoxEffect(DefaultTextFormat().Get(), _glyphCell.width, DefaultFontFace().Get(), 1.0f, &_boxDrawingEffect)); - } - - return _boxDrawingEffect; -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::TextFormatWithAttribute(DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch) -{ - const auto textFormatIt = _textFormatMap.find(_ToMapKey(weight, style, stretch)); - if (textFormatIt == _textFormatMap.end()) - { - auto fontInfo = _defaultFontInfo; - fontInfo.SetWeight(weight); - fontInfo.SetStyle(style); - fontInfo.SetStretch(stretch); - - // Create the font with the fractional pixel height size. - // It should have an integer pixel width by our math. - // Then below, apply the line spacing to the format to position the floating point pixel height characters - // into a cell that has an integer pixel height leaving some padding above/below as necessary to round them out. - auto localeName = UserLocaleName(); - Microsoft::WRL::ComPtr textFormat; - THROW_IF_FAILED(_BuildTextFormat(fontInfo, localeName).As(&textFormat)); - THROW_IF_FAILED(textFormat->SetLineSpacing(_lineSpacing.method, _lineSpacing.height, _lineSpacing.baseline)); - THROW_IF_FAILED(textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR)); - THROW_IF_FAILED(textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP)); - - _textFormatMap.emplace(_ToMapKey(weight, style, stretch), textFormat); - return textFormat; - } - else - { - return textFormatIt->second; - } -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::FontFaceWithAttribute(DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch) -{ - const auto fontFaceIt = _fontFaceMap.find(_ToMapKey(weight, style, stretch)); - if (fontFaceIt == _fontFaceMap.end()) - { - auto fontInfo = _defaultFontInfo; - fontInfo.SetWeight(weight); - fontInfo.SetStyle(style); - fontInfo.SetStretch(stretch); - - auto fontLocaleName = UserLocaleName(); - auto fontFace = fontInfo.ResolveFontFaceWithFallback(fontLocaleName); - - _fontFaceMap.emplace(_ToMapKey(weight, style, stretch), fontFace); - return fontFace; - } - else - { - return fontFaceIt->second; - } -} - -// Routine Description: -// - Updates the font used for drawing -// Arguments: -// - desired - Information specifying the font that is requested -// - actual - Filled with the nearest font actually chosen for drawing -// - dpi - The DPI of the screen -// Return Value: -// - S_OK or relevant DirectX error -[[nodiscard]] HRESULT DxFontRenderData::UpdateFont(const FontInfoDesired& desired, FontInfo& actual, const int dpi, const std::unordered_map& features, const std::unordered_map& axes) noexcept -{ - try - { - _userLocaleName.clear(); - _textFormatMap.clear(); - _fontFaceMap.clear(); - _boxDrawingEffect.Reset(); - - // Initialize the default font info and build everything from here. - _defaultFontInfo = DxFontInfo( - _dwriteFactory.Get(), - desired.GetFaceName(), - static_cast(desired.GetWeight()), - DWRITE_FONT_STYLE_NORMAL, - DWRITE_FONT_STRETCH_NORMAL); - - _SetFeatures(features); - _SetAxes(axes); - - _BuildFontRenderData(desired, actual, dpi); - } - CATCH_RETURN(); - - return S_OK; -} - -// Routine Description: -// - Calculates the box drawing scale/translate matrix values to fit a box glyph into the cell as perfectly as possible. -// Arguments: -// - format - Text format used to determine line spacing (height including ascent & descent) as calculated from the base font. -// - widthPixels - The pixel width of the available cell. -// - face - The font face that is currently being used, may differ from the base font from the layout. -// - fontScale - if the given font face is going to be scaled versus the format, we need to know so we can compensate for that. pass 1.0f for no scaling. -// - effect - Receives the effect to apply to box drawing characters. If no effect is received, special treatment isn't required. -// Return Value: -// - S_OK, GSL/WIL errors, DirectWrite errors, or math errors. -[[nodiscard]] HRESULT STDMETHODCALLTYPE DxFontRenderData::s_CalculateBoxEffect(IDWriteTextFormat* format, size_t widthPixels, IDWriteFontFace1* face, float fontScale, IBoxDrawingEffect** effect) noexcept -try -{ - // Check for bad in parameters. - RETURN_HR_IF(E_INVALIDARG, !format); - RETURN_HR_IF(E_INVALIDARG, !face); - - // Check the out parameter and fill it up with null. - RETURN_HR_IF(E_INVALIDARG, !effect); - *effect = nullptr; - - // The format is based around the main font that was specified by the user. - // We need to know its size as well as the final spacing that was calculated around - // it when it was first selected to get an idea of how large the bounding box is. - const auto fontSize = format->GetFontSize(); - - DWRITE_LINE_SPACING_METHOD spacingMethod; - float lineSpacing; // total height of the cells - float baseline; // vertical position counted down from the top where the characters "sit" - RETURN_IF_FAILED(format->GetLineSpacing(&spacingMethod, &lineSpacing, &baseline)); - - const auto ascentPixels = baseline; - const auto descentPixels = lineSpacing - baseline; - - // We need this for the designUnitsPerEm which will be required to move back and forth between - // Design Units and Pixels. I'll elaborate below. - DWRITE_FONT_METRICS1 fontMetrics; - face->GetMetrics(&fontMetrics); - - // If we had font fallback occur, the size of the font given to us (IDWriteFontFace1) can be different - // than the font size used for the original format (IDWriteTextFormat). - const auto scaledFontSize = fontScale * fontSize; - - // This is Unicode FULL BLOCK U+2588. - // We presume that FULL BLOCK should be filling its entire cell in all directions so it should provide a good basis - // in knowing exactly where to touch every single edge. - // We're also presuming that the other box/line drawing glyphs were authored in this font to perfectly inscribe - // inside of FULL BLOCK, with the same left/top/right/bottom bearings so they would look great when drawn adjacent. - const UINT32 blockCodepoint = L'\x2588'; - - // Get the index of the block out of the font. - UINT16 glyphIndex; - RETURN_IF_FAILED(face->GetGlyphIndicesW(&blockCodepoint, 1, &glyphIndex)); - - // If it was 0, it wasn't found in the font. We're going to try again with - // Unicode BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL U+253C which should be touching - // all the edges of the possible rectangle, much like a full block should. - if (glyphIndex == 0) - { - const UINT32 alternateCp = L'\x253C'; - RETURN_IF_FAILED(face->GetGlyphIndicesW(&alternateCp, 1, &glyphIndex)); - } - - // If we still didn't find the glyph index, we haven't implemented any further logic to figure out the box dimensions. - // So we're just going to leave successfully as is and apply no scaling factor. It might look not-right, but it won't - // stop the rendering pipeline. - RETURN_HR_IF(S_FALSE, glyphIndex == 0); - - // Get the metrics of the given glyph, which we're going to treat as the outline box in which all line/block drawing - // glyphs will be inscribed within, perfectly touching each edge as to align when two cells meet. - DWRITE_GLYPH_METRICS boxMetrics = { 0 }; - RETURN_IF_FAILED(face->GetDesignGlyphMetrics(&glyphIndex, 1, &boxMetrics)); - - // NOTE: All metrics we receive from DWRITE are going to be in "design units" which are a somewhat agnostic - // way of describing proportions. - // Converting back and forth between real pixels and design units is possible using - // any font's specific fontSize and the designUnitsPerEm FONT_METRIC value. - // - // Here's what to know about the boxMetrics: - // - // - // - // topLeft --> +--------------------------------+ --- - // | ^ | | - // | | topSide | | - // | | Bearing | | - // | v | | - // | +-----------------+ | | - // | | | | | - // | | | | | a - // | | | | | d - // | | | | | v - // +<---->+ | | | a - // | | | | | n - // | left | | | | c - // | Side | | | | e - // | Bea- | | | | H - // | ring | | right | | e - // vertical | | | Side | | i - // OriginY --> x | | Bea- | | g - // | | | ring | | h - // | | | | | t - // | | +<----->+ | - // | +-----------------+ | | - // | ^ | | - // | bottomSide | | | - // | Bearing | | | - // | v | | - // +--------------------------------+ --- - // - // - // | | - // +--------------------------------+ - // | advanceWidth | - // - // - // NOTE: The bearings can be negative, in which case it is specifying that the glyphs overhang the box - // as defined by the advanceHeight/width. - // See also: https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ns-dwrite-dwrite_glyph_metrics - - // The scale is a multiplier and the translation is addition. So *1 and +0 will mean nothing happens. - const auto defaultBoxVerticalScaleFactor = 1.0f; - auto boxVerticalScaleFactor = defaultBoxVerticalScaleFactor; - const auto defaultBoxVerticalTranslation = 0.0f; - auto boxVerticalTranslation = defaultBoxVerticalTranslation; - { - // First, find the dimensions of the glyph representing our fully filled box. - - // Ascent is how far up from the baseline we'll draw. - // verticalOriginY is the measure from the topLeft corner of the bounding box down to where - // the glyph's version of the baseline is. - // topSideBearing is how much "gap space" is left between that topLeft and where the glyph - // starts drawing. Subtract the gap space to find how far is drawn upward from baseline. - const auto boxAscentDesignUnits = boxMetrics.verticalOriginY - boxMetrics.topSideBearing; - - // Descent is how far down from the baseline we'll draw. - // advanceHeight is the total height of the drawn bounding box. - // verticalOriginY is how much was given to the ascent, so subtract that out. - // What remains is then the descent value. Remove the - // bottomSideBearing as the "gap space" on the bottom to find how far is drawn downward from baseline. - const auto boxDescentDesignUnits = boxMetrics.advanceHeight - boxMetrics.verticalOriginY - boxMetrics.bottomSideBearing; - - // The height, then, of the entire box is just the sum of the ascent above the baseline and the descent below. - const auto boxHeightDesignUnits = boxAscentDesignUnits + boxDescentDesignUnits; - - // Second, find the dimensions of the cell we're going to attempt to fit within. - // We know about the exact ascent/descent units in pixels as calculated when we chose a font and - // adjusted the ascent/descent for a nice perfect baseline and integer total height. - // All we need to do is adapt it into Design Units so it meshes nicely with the Design Units above. - // Use the formula: Pixels * Design Units Per Em / Font Size = Design Units - const auto cellAscentDesignUnits = ascentPixels * fontMetrics.designUnitsPerEm / scaledFontSize; - const auto cellDescentDesignUnits = descentPixels * fontMetrics.designUnitsPerEm / scaledFontSize; - const auto cellHeightDesignUnits = cellAscentDesignUnits + cellDescentDesignUnits; - - // OK, now do a few checks. If the drawn box touches the top and bottom of the cell - // and the box is overall tall enough, then we'll not bother adjusting. - // We will presume the font author has set things as they wish them to be. - const auto boxTouchesCellTop = boxAscentDesignUnits >= cellAscentDesignUnits; - const auto boxTouchesCellBottom = boxDescentDesignUnits >= cellDescentDesignUnits; - const auto boxIsTallEnoughForCell = boxHeightDesignUnits >= cellHeightDesignUnits; - - // If not... - if (!(boxTouchesCellTop && boxTouchesCellBottom && boxIsTallEnoughForCell)) - { - // Find a scaling factor that will make the total height drawn of this box - // perfectly fit the same number of design units as the cell. - // Since scale factor is a multiplier, it doesn't matter that this is design units. - // The fraction between the two heights in pixels should be exactly the same - // (which is what will matter when we go to actually render it... the pixels that is.) - // Don't scale below 1.0. If it'd shrink, just center it at the prescribed scale. - boxVerticalScaleFactor = std::max(cellHeightDesignUnits / boxHeightDesignUnits, 1.0f); - - // The box as scaled might be hanging over the top or bottom of the cell (or both). - // We find out the amount of overhang/underhang on both the top and the bottom. - const auto extraAscent = boxAscentDesignUnits * boxVerticalScaleFactor - cellAscentDesignUnits; - const auto extraDescent = boxDescentDesignUnits * boxVerticalScaleFactor - cellDescentDesignUnits; - - // This took a bit of time and effort and it's difficult to put into words, but here goes. - // We want the average of the two magnitudes to find out how much to "take" from one and "give" - // to the other such that both are equal. We presume the glyphs are designed to be drawn - // centered in their box vertically to look good. - // The ordering around subtraction is required to ensure that the direction is correct with a negative - // translation moving up (taking excess descent and adding to ascent) and positive is the opposite. - const auto boxVerticalTranslationDesignUnits = (extraAscent - extraDescent) / 2; - - // The translation is just a raw movement of pixels up or down. Since we were working in Design Units, - // we need to run the opposite algorithm shown above to go from Design Units to Pixels. - boxVerticalTranslation = boxVerticalTranslationDesignUnits * scaledFontSize / fontMetrics.designUnitsPerEm; - } - } - - // The horizontal adjustments follow the exact same logic as the vertical ones. - const auto defaultBoxHorizontalScaleFactor = 1.0f; - auto boxHorizontalScaleFactor = defaultBoxHorizontalScaleFactor; - const auto defaultBoxHorizontalTranslation = 0.0f; - auto boxHorizontalTranslation = defaultBoxHorizontalTranslation; - { - // This is the only difference. We don't have a horizontalOriginX from the metrics. - // However, https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ns-dwrite-dwrite_glyph_metrics says - // the X coordinate is specified by half the advanceWidth to the right of the horizontalOrigin. - // So we'll use that as the "center" and apply it the role that verticalOriginY had above. - - const auto boxCenterDesignUnits = boxMetrics.advanceWidth / 2; - const auto boxLeftDesignUnits = boxCenterDesignUnits - boxMetrics.leftSideBearing; - const auto boxRightDesignUnits = boxMetrics.advanceWidth - boxMetrics.rightSideBearing - boxCenterDesignUnits; - const auto boxWidthDesignUnits = boxLeftDesignUnits + boxRightDesignUnits; - - const auto cellWidthDesignUnits = widthPixels * fontMetrics.designUnitsPerEm / scaledFontSize; - const auto cellLeftDesignUnits = cellWidthDesignUnits / 2; - const auto cellRightDesignUnits = cellLeftDesignUnits; - - const auto boxTouchesCellLeft = boxLeftDesignUnits >= cellLeftDesignUnits; - const auto boxTouchesCellRight = boxRightDesignUnits >= cellRightDesignUnits; - const auto boxIsWideEnoughForCell = boxWidthDesignUnits >= cellWidthDesignUnits; - - if (!(boxTouchesCellLeft && boxTouchesCellRight && boxIsWideEnoughForCell)) - { - boxHorizontalScaleFactor = std::max(cellWidthDesignUnits / boxWidthDesignUnits, 1.0f); - const auto extraLeft = boxLeftDesignUnits * boxHorizontalScaleFactor - cellLeftDesignUnits; - const auto extraRight = boxRightDesignUnits * boxHorizontalScaleFactor - cellRightDesignUnits; - - const auto boxHorizontalTranslationDesignUnits = (extraLeft - extraRight) / 2; - - boxHorizontalTranslation = boxHorizontalTranslationDesignUnits * scaledFontSize / fontMetrics.designUnitsPerEm; - } - } - - // If we set anything, make a drawing effect. Otherwise, there isn't one. - if (defaultBoxVerticalScaleFactor != boxVerticalScaleFactor || - defaultBoxVerticalTranslation != boxVerticalTranslation || - defaultBoxHorizontalScaleFactor != boxHorizontalScaleFactor || - defaultBoxHorizontalTranslation != boxHorizontalTranslation) - { - // OK, make the object that will represent our effect, stuff the metrics into it, and return it. - RETURN_IF_FAILED(WRL::MakeAndInitialize(effect, boxVerticalScaleFactor, boxVerticalTranslation, boxHorizontalScaleFactor, boxHorizontalTranslation)); - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Returns whether the user set or updated any of the font features to be applied -bool DxFontRenderData::DidUserSetFeatures() const noexcept -{ - return _didUserSetFeatures; -} - -// Routine Description: -// - Returns whether the user set or updated any of the font axes to be applied -bool DxFontRenderData::DidUserSetAxes() const noexcept -{ - return _didUserSetAxes; -} - -// Routine Description: -// - Function called to inform us whether to use the user set weight -// in the font axes -// - Called by CustomTextLayout, when the text attribute is intense we should -// ignore the user set weight, otherwise setting the bold font axis -// breaks the bold font attribute -// Arguments: -// - inhibitUserWeight: boolean that tells us if we should use the user set weight -// in the font axes -void DxFontRenderData::InhibitUserWeight(bool inhibitUserWeight) noexcept -{ - _inhibitUserWeight = inhibitUserWeight; -} - -// Routine Description: -// - Returns whether the set italic in the font axes -// Return Value: -// - True if the user set the italic axis to 1, -// false if the italic axis is not present or the italic axis is set to 0 -bool DxFontRenderData::DidUserSetItalic() const noexcept -{ - return _didUserSetItalic; -} - -// Routine Description: -// - Updates our internal map of font features with the given features -// - NOTE TO CALLER: Make sure to call _BuildFontRenderData after calling this for the feature changes -// to take place -// Arguments: -// - features - the features to update our map with -void DxFontRenderData::_SetFeatures(const std::unordered_map& features) -{ - // Populate the feature map with the standard list first - std::unordered_map featureMap{ - { DWRITE_MAKE_FONT_FEATURE_TAG('c', 'a', 'l', 't'), 1 }, // Contextual Alternates - { DWRITE_MAKE_FONT_FEATURE_TAG('l', 'i', 'g', 'a'), 1 }, // Standard Ligatures - { DWRITE_MAKE_FONT_FEATURE_TAG('c', 'l', 'i', 'g'), 1 }, // Contextual Ligatures - { DWRITE_MAKE_FONT_FEATURE_TAG('k', 'e', 'r', 'n'), 1 } // Kerning - }; - - // Update our feature map with the provided features - if (!features.empty()) - { - for (const auto& [tag, param] : features) - { - if (tag.length() == TAG_LENGTH) - { - featureMap.insert_or_assign(DWRITE_MAKE_FONT_FEATURE_TAG(til::at(tag, 0), til::at(tag, 1), til::at(tag, 2), til::at(tag, 3)), param); - } - } - _didUserSetFeatures = true; - } - else - { - _didUserSetFeatures = false; - } - - // Convert the data to DWRITE_FONT_FEATURE and store it in a vector for CustomTextLayout - _featureVector.clear(); - for (const auto [tag, param] : featureMap) - { - _featureVector.push_back(DWRITE_FONT_FEATURE{ tag, param }); - } -} - -// Routine Description: -// - Updates our internal map of font axes with the given axes -// - NOTE TO CALLER: Make sure to call _BuildFontRenderData after calling this for the axes changes -// to take place -// Arguments: -// - axes - the axes to update our map with -void DxFontRenderData::_SetAxes(const std::unordered_map& axes) -{ - // Clear out the old vector and booleans in case this is a hot reload - _axesVector = std::vector{}; - _didUserSetAxes = false; - _didUserSetItalic = false; - - // Update our axis map with the provided axes - if (!axes.empty()) - { - // Store the weight aside: we will be creating a span of all the axes in the vector except the weight, - // and then we will add the weight to the vector - // We are doing this so that when the text attribute is intense, we can apply all the axes except the weight - std::optional weightAxis; - - // Since we are calling an 'emplace_back' after creating the span, - // there is a chance a reallocation happens (if the vector needs to grow), which would make the span point to - // deallocated memory. To avoid this, make sure to reserve enough memory in the vector. - _axesVector.reserve(axes.size()); - -#pragma warning(suppress : 26445) // the analyzer doesn't like reference to string_view - for (const auto& [axis, value] : axes) - { - if (axis.length() == TAG_LENGTH) - { - const auto dwriteFontAxis = DWRITE_FONT_AXIS_VALUE{ DWRITE_MAKE_FONT_AXIS_TAG(til::at(axis, 0), til::at(axis, 1), til::at(axis, 2), til::at(axis, 3)), value }; - if (dwriteFontAxis.axisTag != DWRITE_FONT_AXIS_TAG_WEIGHT) - { - _axesVector.emplace_back(dwriteFontAxis); - } - else - { - weightAxis = dwriteFontAxis; - } - _didUserSetItalic |= dwriteFontAxis.axisTag == DWRITE_FONT_AXIS_TAG_ITALIC && value == 1; - } - } - - // Make the span, which has all the axes except the weight - _axesVectorWithoutWeight = std::span{ _axesVector }; - - // Add the weight axis to the vector if needed - if (weightAxis) - { - _axesVector.emplace_back(weightAxis.value()); - } - _didUserSetAxes = true; - } -} - -// Method Description: -// - Converts a DWRITE_FONT_STRETCH enum into the corresponding float value to -// create a DWRITE_FONT_AXIS_VALUE with -// Arguments: -// - fontStretch: the old DWRITE_FONT_STRETCH enum to be converted into an axis value -// Return value: -// - The float value corresponding to the passed in fontStretch -float DxFontRenderData::_FontStretchToWidthAxisValue(DWRITE_FONT_STRETCH fontStretch) noexcept -{ - // 10 elements from DWRITE_FONT_STRETCH_UNDEFINED (0) to DWRITE_FONT_STRETCH_ULTRA_EXPANDED (9) - static constexpr auto fontStretchEnumToVal = std::array{ 100.0f, 50.0f, 62.5f, 75.0f, 87.5f, 100.0f, 112.5f, 125.0f, 150.0f, 200.0f }; - - if (gsl::narrow_cast(fontStretch) > fontStretchEnumToVal.size()) - { - fontStretch = DWRITE_FONT_STRETCH_NORMAL; - } - - return til::at(fontStretchEnumToVal, fontStretch); -} - -// Method Description: -// - Converts a DWRITE_FONT_STYLE enum into the corresponding float value to -// create a DWRITE_FONT_AXIS_VALUE with -// Arguments: -// - fontStyle: the old DWRITE_FONT_STYLE enum to be converted into an axis value -// Return value: -// - The float value corresponding to the passed in fontStyle -float DxFontRenderData::_FontStyleToSlantFixedAxisValue(DWRITE_FONT_STYLE fontStyle) noexcept -{ - // DWRITE_FONT_STYLE_NORMAL (0), DWRITE_FONT_STYLE_OBLIQUE (1), DWRITE_FONT_STYLE_ITALIC (2) - static constexpr auto fontStyleEnumToVal = std::array{ 0.0f, -20.0f, -12.0f }; - - // Both DWRITE_FONT_STYLE_OBLIQUE and DWRITE_FONT_STYLE_ITALIC default to having slant. - // Though an italic font technically need not have slant (there exist upright ones), the - // vast majority of italic fonts are also slanted. Ideally the slant comes from the - // 'slnt' value in the STAT or fvar table, or the post table italic angle. - - if (gsl::narrow_cast(fontStyle) > fontStyleEnumToVal.size()) - { - fontStyle = DWRITE_FONT_STYLE_NORMAL; - } - - return til::at(fontStyleEnumToVal, fontStyle); -} - -// Method Description: -// - Fill any missing axis values that might be known but were unspecified, such as omitting -// the 'wght' axis tag but specifying the old DWRITE_FONT_WEIGHT enum -// - This function will only be called with a valid IDWriteTextFormat3 -// (on platforms where IDWriteTextFormat3 is supported) -// Arguments: -// - fontWeight: the old DWRITE_FONT_WEIGHT enum to be converted into an axis value -// - fontStretch: the old DWRITE_FONT_STRETCH enum to be converted into an axis value -// - fontStyle: the old DWRITE_FONT_STYLE enum to be converted into an axis value -// - fontSize: the number to convert into an axis value -// - format: the IDWriteTextFormat3 to get the defined axes from -// Return value: -// - The fully formed axes vector -#pragma warning(suppress : 26429) // the analyzer doesn't detect that our FAIL_FAST_IF_NULL macro \ - // checks format for nullness -std::vector DxFontRenderData::GetAxisVector(const DWRITE_FONT_WEIGHT fontWeight, - const DWRITE_FONT_STRETCH fontStretch, - const DWRITE_FONT_STYLE fontStyle, - IDWriteTextFormat3* format) -{ - FAIL_FAST_IF_NULL(format); - - const auto axesCount = format->GetFontAxisValueCount(); - std::vector axesVector; - axesVector.resize(axesCount); - format->GetFontAxisValues(axesVector.data(), axesCount); - - auto axisTagPresence = AxisTagPresence::None; - for (const auto& fontAxisValue : axesVector) - { - switch (fontAxisValue.axisTag) - { - case DWRITE_FONT_AXIS_TAG_WEIGHT: - WI_SetFlag(axisTagPresence, AxisTagPresence::Weight); - break; - case DWRITE_FONT_AXIS_TAG_WIDTH: - WI_SetFlag(axisTagPresence, AxisTagPresence::Width); - break; - case DWRITE_FONT_AXIS_TAG_ITALIC: - WI_SetFlag(axisTagPresence, AxisTagPresence::Italic); - break; - case DWRITE_FONT_AXIS_TAG_SLANT: - WI_SetFlag(axisTagPresence, AxisTagPresence::Slant); - break; - } - } - - if (WI_IsFlagClear(axisTagPresence, AxisTagPresence::Weight)) - { - axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_WEIGHT, gsl::narrow(fontWeight) }); - } - if (WI_IsFlagClear(axisTagPresence, AxisTagPresence::Width)) - { - axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_WIDTH, _FontStretchToWidthAxisValue(fontStretch) }); - } - if (WI_IsFlagClear(axisTagPresence, AxisTagPresence::Italic)) - { - axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_ITALIC, (fontStyle == DWRITE_FONT_STYLE_ITALIC ? 1.0f : 0.0f) }); - } - if (WI_IsFlagClear(axisTagPresence, AxisTagPresence::Slant)) - { - axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_SLANT, _FontStyleToSlantFixedAxisValue(fontStyle) }); - } - - return axesVector; -} - -// Routine Description: -// - Build the needed data for rendering according to the font used -// Arguments: -// - desired - Information specifying the font that is requested -// - actual - Filled with the nearest font actually chosen for drawing -// - dpi - The DPI of the screen -// Return Value: -// - None -void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, FontInfo& actual, const int dpi) -{ - const auto dpiF = static_cast(dpi); - auto fontLocaleName = UserLocaleName(); - // This is the first attempt to resolve font face after `UpdateFont`. - // Note that the following line may cause property changes _inside_ `_defaultFontInfo` because the desired font may not exist. - // See the implementation of `ResolveFontFaceWithFallback` for details. - const auto face = _defaultFontInfo.ResolveFontFaceWithFallback(fontLocaleName); - - DWRITE_FONT_METRICS1 fontMetrics; - face->GetMetrics(&fontMetrics); - - const UINT32 spaceCodePoint = L'M'; - UINT16 spaceGlyphIndex; - THROW_IF_FAILED(face->GetGlyphIndicesW(&spaceCodePoint, 1, &spaceGlyphIndex)); - - INT32 advanceInDesignUnits; - THROW_IF_FAILED(face->GetDesignGlyphAdvances(1, &spaceGlyphIndex, &advanceInDesignUnits)); - - DWRITE_GLYPH_METRICS spaceMetrics = { 0 }; - THROW_IF_FAILED(face->GetDesignGlyphMetrics(&spaceGlyphIndex, 1, &spaceMetrics)); - - // The math here is actually: - // Requested Size in Points * DPI scaling factor * Points to Pixels scaling factor. - // - DPI = dots per inch - // - PPI = points per inch or "points" as usually seen when choosing a font size - // - The DPI scaling factor is the current monitor DPI divided by 96, the default DPI. - // - The Points to Pixels factor is based on the typography definition of 72 points per inch. - // As such, converting requires taking the 96 pixel per inch default and dividing by the 72 points per inch - // to get a factor of 1 and 1/3. - // This turns into something like: - // - 12 ppi font * (96 dpi / 96 dpi) * (96 dpi / 72 points per inch) = 16 pixels tall font for 100% display (96 dpi is 100%) - // - 12 ppi font * (144 dpi / 96 dpi) * (96 dpi / 72 points per inch) = 24 pixels tall font for 150% display (144 dpi is 150%) - // - 12 ppi font * (192 dpi / 96 dpi) * (96 dpi / 72 points per inch) = 32 pixels tall font for 200% display (192 dpi is 200%) - const auto heightDesired = desired.GetEngineSize().height / POINTS_PER_INCH * dpiF; - - // The advance is the number of pixels left-to-right (X dimension) for the given font. - // We're finding a proportional factor here with the design units in "ems", not an actual pixel measurement. - const auto widthAdvance = static_cast(advanceInDesignUnits) / fontMetrics.designUnitsPerEm; - - // Use the real pixel height desired by the "em" factor for the width to get the number of pixels - // we will need per character in width. This will almost certainly result in fractional X-dimension pixels. - const auto widthAdvanceInPx = heightDesired * widthAdvance; - - // Now reverse the "em" factor from above to turn the exact pixel width into a (probably) fractional - // height in pixels of each character. It's easier for us to pad out height and align vertically - // than it is horizontally. - const auto fontSize = roundf(widthAdvanceInPx) / widthAdvance; - _fontSize = fontSize; - - // Now figure out the basic properties of the character height which include ascent and descent - // for this specific font size. - const auto ascent = (fontSize * fontMetrics.ascent) / fontMetrics.designUnitsPerEm; - const auto descent = (fontSize * fontMetrics.descent) / fontMetrics.designUnitsPerEm; - - // Get the gap. - const auto gap = (fontSize * fontMetrics.lineGap) / fontMetrics.designUnitsPerEm; - const auto halfGap = gap / 2; - - // We're going to build a line spacing object here to track all of this data in our format. - DWRITE_LINE_SPACING lineSpacing = {}; - lineSpacing.method = DWRITE_LINE_SPACING_METHOD_UNIFORM; - - // We need to make sure the baseline falls on a round pixel (not a fractional pixel). - // If the baseline is fractional, the text appears blurry, especially at small scales. - // Since we also need to make sure the bounding box as a whole is round pixels - // (because the entire console system maths in full cell units), - // we're just going to ceiling up the ascent and descent to make a full pixel amount - // and set the baseline to the full round pixel ascent value. - // - // For reference, for the letters "ag": - // ... - // gggggg bottom of previous line - // - // ----------------- <===========================================| - // | topSideBearing | 1/2 lineGap | - // aaaaaa ggggggg <-------------------------|-------------| | - // a g g | | | - // aaaaa ggggg |<-ascent | | - // a a g | | |---- lineHeight - // aaaaa a gggggg <----baseline, verticalOriginY----------|---| - // g g |<-descent | | - // gggggg <-------------------------|-------------| | - // | bottomSideBearing | 1/2 lineGap | - // ----------------- <===========================================| - // - // aaaaaa ggggggg top of next line - // ... - // - // Also note... - // We're going to add half the line gap to the ascent and half the line gap to the descent - // to ensure that the spacing is balanced vertically. - // Generally speaking, the line gap is added to the ascent by DirectWrite itself for - // horizontally drawn text which can place the baseline and glyphs "lower" in the drawing - // box than would be desired for proper alignment of things like line and box characters - // which will try to sit centered in the area and touch perfectly with their neighbors. - - const auto fullPixelAscent = ceil(ascent + halfGap); - const auto fullPixelDescent = ceil(descent + halfGap); - const auto defaultHeight = fullPixelAscent + fullPixelDescent; - const auto lineHeight = desired.GetCellHeight().Resolve(defaultHeight, dpiF, heightDesired, widthAdvanceInPx); - const auto baseline = fullPixelAscent + (lineHeight - defaultHeight) / 2.0f; - - lineSpacing.height = roundf(lineHeight); - lineSpacing.baseline = roundf(baseline); - - // According to MSDN (https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/ne-dwrite_3-dwrite_font_line_gap_usage) - // Setting "ENABLED" means we've included the line gapping in the spacing numbers given. - lineSpacing.fontLineGapUsage = DWRITE_FONT_LINE_GAP_USAGE_ENABLED; - - _lineSpacing = lineSpacing; - - const auto widthApprox = desired.GetCellWidth().Resolve(widthAdvanceInPx, dpiF, heightDesired, widthAdvanceInPx); - const auto widthExact = roundf(widthApprox); - - // The scaled size needs to represent the pixel box that each character will fit within for the purposes - // of hit testing math and other such multiplication/division. - til::size coordSize; - coordSize.width = static_cast(widthExact); - coordSize.height = static_cast(lineSpacing.height); - - // Unscaled is for the purposes of re-communicating this font back to the renderer again later. - // As such, we need to give the same original size parameter back here without padding - // or rounding or scaling manipulation. - const auto unscaled = desired.GetEngineSize(); - - const auto scaled = coordSize; - - actual.SetFromEngine(_defaultFontInfo.GetFamilyName(), - desired.GetFamily(), - DefaultTextFormat()->GetFontWeight(), - false, - scaled, - unscaled); - - actual.SetFallback(_defaultFontInfo.GetFallback()); - - LineMetrics lineMetrics; - // There is no font metric for the grid line width, so we use a small - // multiple of the font size, which typically rounds to a pixel. - lineMetrics.gridlineWidth = std::round(fontSize * 0.025f); - - // All other line metrics are in design units, so to get a pixel value, - // we scale by the font size divided by the design-units-per-em. - const auto scale = fontSize / fontMetrics.designUnitsPerEm; - lineMetrics.underlineOffset = std::round(fontMetrics.underlinePosition * scale); - lineMetrics.underlineWidth = std::round(fontMetrics.underlineThickness * scale); - lineMetrics.strikethroughOffset = std::round(fontMetrics.strikethroughPosition * scale); - lineMetrics.strikethroughWidth = std::round(fontMetrics.strikethroughThickness * scale); - - // We always want the lines to be visible, so if a stroke width ends up - // at zero after rounding, we need to make it at least 1 pixel. - lineMetrics.gridlineWidth = std::max(lineMetrics.gridlineWidth, 1.0f); - lineMetrics.underlineWidth = std::max(lineMetrics.underlineWidth, 1.0f); - lineMetrics.strikethroughWidth = std::max(lineMetrics.strikethroughWidth, 1.0f); - - // Offsets are relative to the base line of the font, so we subtract - // from the ascent to get an offset relative to the top of the cell. - lineMetrics.underlineOffset = lineSpacing.baseline - lineMetrics.underlineOffset; - lineMetrics.strikethroughOffset = lineSpacing.baseline - lineMetrics.strikethroughOffset; - - // For double underlines we need a second offset, just below the first, - // but with a bit of a gap (about double the grid line width). - lineMetrics.underlineOffset2 = lineMetrics.underlineOffset + - lineMetrics.underlineWidth + - std::round(fontSize * 0.05f); - - // However, we don't want the underline to extend past the bottom of the - // cell, so we clamp the offset to fit just inside. - const auto maxUnderlineOffset = lineSpacing.height - lineMetrics.underlineWidth; - lineMetrics.underlineOffset2 = std::min(lineMetrics.underlineOffset2, maxUnderlineOffset); - - // But if the resulting gap isn't big enough even to register as a thicker - // line, it's better to place the second line slightly above the first. - if (lineMetrics.underlineOffset2 < lineMetrics.underlineOffset + lineMetrics.gridlineWidth) - { - lineMetrics.underlineOffset2 = lineMetrics.underlineOffset - lineMetrics.gridlineWidth; - } - - // We also add half the stroke width to the offsets, since the line - // coordinates designate the center of the line. - lineMetrics.underlineOffset += lineMetrics.underlineWidth / 2.0f; - lineMetrics.underlineOffset2 += lineMetrics.underlineWidth / 2.0f; - lineMetrics.strikethroughOffset += lineMetrics.strikethroughWidth / 2.0f; - - _lineMetrics = lineMetrics; - - _glyphCell = actual.GetSize(); -} - -Microsoft::WRL::ComPtr DxFontRenderData::_BuildTextFormat(const DxFontInfo& fontInfo, const std::wstring_view localeName) -{ - Microsoft::WRL::ComPtr format; - THROW_IF_FAILED(_dwriteFactory->CreateTextFormat(fontInfo.GetFamilyName().data(), - fontInfo.GetFontCollection(), - fontInfo.GetWeight(), - fontInfo.GetStyle(), - fontInfo.GetStretch(), - _fontSize, - localeName.data(), - &format)); - - // If the OS supports IDWriteTextFormat3, set the font axes - ::Microsoft::WRL::ComPtr format3; - if (!FAILED(format->QueryInterface(IID_PPV_ARGS(&format3)))) - { - if (_inhibitUserWeight && !_axesVectorWithoutWeight.empty()) - { - format3->SetFontAxisValues(_axesVectorWithoutWeight.data(), gsl::narrow(_axesVectorWithoutWeight.size())); - } - else if (!_inhibitUserWeight && !_axesVector.empty()) - { - format3->SetFontAxisValues(_axesVector.data(), gsl::narrow(_axesVector.size())); - } - } - - return format; -} diff --git a/src/renderer/dx/DxFontRenderData.h b/src/renderer/dx/DxFontRenderData.h deleted file mode 100644 index e12c6da91bb..00000000000 --- a/src/renderer/dx/DxFontRenderData.h +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "../../renderer/inc/FontInfoDesired.hpp" -#include "DxFontInfo.h" -#include "BoxDrawingEffect.h" - -#include -#include -#include -#include - -#include - -namespace Microsoft::Console::Render -{ - enum class AxisTagPresence : BYTE - { - None = 0x00, - Weight = 0x01, - Width = 0x02, - Italic = 0x04, - Slant = 0x08, - }; - DEFINE_ENUM_FLAG_OPERATORS(AxisTagPresence); - - class DxFontRenderData - { - public: - struct LineMetrics - { - float gridlineWidth; - float underlineOffset; - float underlineOffset2; - float underlineWidth; - float strikethroughOffset; - float strikethroughWidth; - }; - - DxFontRenderData(::Microsoft::WRL::ComPtr dwriteFactory); - - // DirectWrite text analyzer from the factory - [[nodiscard]] Microsoft::WRL::ComPtr Analyzer(); - - [[nodiscard]] Microsoft::WRL::ComPtr SystemFontFallback(); - - // A locale that can be used on construction of assorted DX objects that want to know one. - [[nodiscard]] std::wstring UserLocaleName(); - - [[nodiscard]] til::size GlyphCell() noexcept; - [[nodiscard]] LineMetrics GetLineMetrics() noexcept; - - // The weight of default font - [[nodiscard]] DWRITE_FONT_WEIGHT DefaultFontWeight() noexcept; - - // The style of default font - [[nodiscard]] DWRITE_FONT_STYLE DefaultFontStyle() noexcept; - - // The stretch of default font - [[nodiscard]] DWRITE_FONT_STRETCH DefaultFontStretch() noexcept; - - // The font features of the default font - [[nodiscard]] const std::vector& DefaultFontFeatures() const noexcept; - - // The DirectWrite format object representing the size and other text properties to be applied (by default) - [[nodiscard]] Microsoft::WRL::ComPtr DefaultTextFormat(); - - // The DirectWrite font face to use while calculating layout (by default) - [[nodiscard]] Microsoft::WRL::ComPtr DefaultFontFace(); - - // Box drawing scaling effects that are cached for the base font across layouts - [[nodiscard]] Microsoft::WRL::ComPtr DefaultBoxDrawingEffect(); - - // The attributed variants of the format object representing the size and other text properties - [[nodiscard]] Microsoft::WRL::ComPtr TextFormatWithAttribute(DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch); - - // The attributed variants of the font face to use while calculating layout - [[nodiscard]] Microsoft::WRL::ComPtr FontFaceWithAttribute(DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch); - - [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& desired, FontInfo& fiFontInfo, const int dpi, const std::unordered_map& features = {}, const std::unordered_map& axes = {}) noexcept; - - [[nodiscard]] static HRESULT STDMETHODCALLTYPE s_CalculateBoxEffect(IDWriteTextFormat* format, size_t widthPixels, IDWriteFontFace1* face, float fontScale, IBoxDrawingEffect** effect) noexcept; - - bool DidUserSetFeatures() const noexcept; - bool DidUserSetAxes() const noexcept; - void InhibitUserWeight(bool inhibitUserWeight) noexcept; - bool DidUserSetItalic() const noexcept; - - std::vector GetAxisVector(const DWRITE_FONT_WEIGHT fontWeight, - const DWRITE_FONT_STRETCH fontStretch, - const DWRITE_FONT_STYLE fontStyle, - IDWriteTextFormat3* format); - - private: - using FontAttributeMapKey = uint32_t; - - bool _inhibitUserWeight{ false }; - bool _didUserSetItalic{ false }; - bool _didUserSetFeatures{ false }; - bool _didUserSetAxes{ false }; - // The font features to apply to the text - std::vector _featureVector; - - // The font axes to apply to the text - std::vector _axesVector; - std::span _axesVectorWithoutWeight; - - // We use this to identify font variants with different attributes. - static FontAttributeMapKey _ToMapKey(DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style, DWRITE_FONT_STRETCH stretch) noexcept - { - return (weight << 16) | (style << 8) | stretch; - }; - - void _SetFeatures(const std::unordered_map& features); - void _SetAxes(const std::unordered_map& axes); - float _FontStretchToWidthAxisValue(DWRITE_FONT_STRETCH fontStretch) noexcept; - float _FontStyleToSlantFixedAxisValue(DWRITE_FONT_STYLE fontStyle) noexcept; - void _BuildFontRenderData(const FontInfoDesired& desired, FontInfo& actual, const int dpi); - Microsoft::WRL::ComPtr _BuildTextFormat(const DxFontInfo& fontInfo, const std::wstring_view localeName); - - std::unordered_map> _textFormatMap; - std::unordered_map> _fontFaceMap; - - ::Microsoft::WRL::ComPtr _boxDrawingEffect; - ::Microsoft::WRL::ComPtr _systemFontFallback; - ::Microsoft::WRL::ComPtr _dwriteFactory; - ::Microsoft::WRL::ComPtr _dwriteTextAnalyzer; - - std::wstring _userLocaleName; - DxFontInfo _defaultFontInfo; - til::size _glyphCell; - DWRITE_LINE_SPACING _lineSpacing; - LineMetrics _lineMetrics; - float _fontSize; - }; -} diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp deleted file mode 100644 index e03cfb20081..00000000000 --- a/src/renderer/dx/DxRenderer.cpp +++ /dev/null @@ -1,2396 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "DxRenderer.hpp" -#include "CustomTextLayout.h" - -#include "../../interactivity/win32/CustomWindowMessages.h" -#include "../../types/inc/Viewport.hpp" -#include "../../inc/unicode.hpp" -#include "../../inc/DefaultSettings.h" -#include - -#include "ScreenPixelShader.h" -#include "ScreenVertexShader.h" -#include -#include -#include - -using namespace DirectX; - -std::atomic Microsoft::Console::Render::DxEngine::_tracelogCount{ 0 }; -#pragma warning(suppress : 26477) // We don't control tracelogging macros -TRACELOGGING_DEFINE_PROVIDER(g_hDxRenderProvider, - "Microsoft.Windows.Terminal.Renderer.DirectX", - // {c93e739e-ae50-5a14-78e7-f171e947535d} - (0xc93e739e, 0xae50, 0x5a14, 0x78, 0xe7, 0xf1, 0x71, 0xe9, 0x47, 0x53, 0x5d), ); - -// Quad where we draw the terminal. -// pos is world space coordinates where origin is at the center of screen. -// tex is texel coordinates where origin is top left. -// Layout the quad as a triangle strip where the _screenQuadVertices are place like so. -// 2 0 -// 3 1 -struct ShaderInput -{ - XMFLOAT3 pos; - XMFLOAT2 tex; -} const _screenQuadVertices[] = { - { XMFLOAT3(1.f, 1.f, 0.f), XMFLOAT2(1.f, 0.f) }, - { XMFLOAT3(1.f, -1.f, 0.f), XMFLOAT2(1.f, 1.f) }, - { XMFLOAT3(-1.f, 1.f, 0.f), XMFLOAT2(0.f, 0.f) }, - { XMFLOAT3(-1.f, -1.f, 0.f), XMFLOAT2(0.f, 1.f) }, -}; - -D3D11_INPUT_ELEMENT_DESC _shaderInputLayout[] = { - { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 } -}; - -namespace -{ - bool operator==(const D2D1::Matrix3x2F& lhs, const D2D1::Matrix3x2F& rhs) noexcept - { - return ::memcmp(&lhs.m[0][0], &rhs.m[0][0], sizeof(lhs.m)) == 0; - }; -} - -#pragma hdrstop - -using namespace Microsoft::Console::Render; -using namespace Microsoft::Console::Types; - -// Routine Description: -// - Constructs a DirectX-based renderer for console text -// which primarily uses DirectWrite on a Direct2D surface -#pragma warning(suppress : 26455) -// TODO GH 2683: The default constructor should not throw. -DxEngine::DxEngine() : - RenderEngineBase(), - _pool{ til::pmr::get_default_resource() }, - _invalidMap{ &_pool }, - _invalidScroll{}, - _allInvalid{ false }, - _firstFrame{ true }, - _presentParams{ 0 }, - _presentReady{ false }, - _presentScroll{ 0 }, - _presentDirty{ 0 }, - _presentOffset{ 0 }, - _isEnabled{ false }, - _isPainting{ false }, - _displaySizePixels{}, - _foregroundColor{ 0 }, - _backgroundColor{ 0 }, - _selectionBackground{}, - _currentLineRendition{ LineRendition::SingleWidth }, - _currentLineTransform{ D2D1::Matrix3x2F::Identity() }, - _haveDeviceResources{ false }, - _swapChainHandle{ INVALID_HANDLE_VALUE }, - _swapChainDesc{ 0 }, - _swapChainFrameLatencyWaitableObject{ INVALID_HANDLE_VALUE }, - _recreateDeviceRequested{ false }, - _terminalEffectsEnabled{ false }, - _retroTerminalEffect{ false }, - _pixelShaderPath{}, - _forceFullRepaintRendering{ false }, - _softwareRendering{ false }, - _antialiasingMode{ D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE }, - _defaultBackgroundIsTransparent{ true }, - _hwndTarget{ static_cast(INVALID_HANDLE_VALUE) }, - _sizeTarget{}, - _dpi{ USER_DEFAULT_SCREEN_DPI }, - _scale{ 1.0f }, - _prevScale{ 1.0f }, - _chainMode{ SwapChainMode::ForComposition }, - _customLayout{}, - _customRenderer{ ::Microsoft::WRL::Make() }, - _drawingContext{} -{ - const auto was = _tracelogCount.fetch_add(1); - if (0 == was) - { - TraceLoggingRegister(g_hDxRenderProvider); - } - - THROW_IF_FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_PPV_ARGS(&_d2dFactory))); - - THROW_IF_FAILED(DWriteCreateFactory( - DWRITE_FACTORY_TYPE_SHARED, - __uuidof(_dwriteFactory), - reinterpret_cast(_dwriteFactory.GetAddressOf()))); - - // Initialize our default selection color to DEFAULT_FOREGROUND, but make - // sure to set to to a D2D1::ColorF - SetSelectionBackground(DEFAULT_FOREGROUND); - - _fontRenderData = std::make_unique(_dwriteFactory); -} - -// Routine Description: -// - Destroys an instance of the DirectX rendering engine -DxEngine::~DxEngine() -{ - _ReleaseDeviceResources(); - - const auto was = _tracelogCount.fetch_sub(1); - if (1 == was) - { - TraceLoggingUnregister(g_hDxRenderProvider); - } -} - -// Routine Description: -// - Sets this engine to enabled allowing painting and presentation to occur -// Arguments: -// - -// Return Value: -// - Generally S_OK, but might return a DirectX or memory error if -// resources need to be created or adjusted when enabling to prepare for draw -// Can give invalid state if you enable an enabled class. -[[nodiscard]] HRESULT DxEngine::Enable() noexcept -{ - return _EnableDisplayAccess(true); -} - -// Routine Description: -// - Sets this engine to disabled to prevent painting and presentation from occurring -// Arguments: -// - -// Return Value: -// - Should be OK. We might close/free resources, but that shouldn't error. -// Can give invalid state if you disable a disabled class. -[[nodiscard]] HRESULT DxEngine::Disable() noexcept -{ - return _EnableDisplayAccess(false); -} - -// Routine Description: -// - Helper to enable/disable painting/display access/presentation in a unified -// manner between enable/disable functions. -// Arguments: -// - outputEnabled - true to enable, false to disable -// Return Value: -// - Generally OK. Can return invalid state if you set to the state that is already -// active (enabling enabled, disabling disabled). -[[nodiscard]] HRESULT DxEngine::_EnableDisplayAccess(const bool outputEnabled) noexcept -{ - // Invalid state if we're setting it to the same as what we already have. - RETURN_HR_IF(E_NOT_VALID_STATE, outputEnabled == _isEnabled); - - _isEnabled = outputEnabled; - if (!_isEnabled) - { - _ReleaseDeviceResources(); - } - - return S_OK; -} - -// Routine Description: -// - Compiles a shader source into binary blob. -// Arguments: -// - source - Shader source -// - target - What kind of shader this is -// - entry - Entry function of shader -// Return Value: -// - Compiled binary. Errors are thrown and logged. -static Microsoft::WRL::ComPtr _CompileShader(const std::string_view& source, const char* target) -{ -#if !TIL_FEATURE_DXENGINESHADERSUPPORT_ENABLED - THROW_HR(E_UNEXPECTED); - return 0; -#else - Microsoft::WRL::ComPtr code{}; - Microsoft::WRL::ComPtr error{}; - - const auto hr = D3DCompile( - source.data(), - source.size(), - nullptr, - nullptr, - nullptr, - "main", - target, - D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR | D3DCOMPILE_OPTIMIZATION_LEVEL3, - 0, - &code, - &error); - - if (FAILED(hr)) - { - LOG_HR_MSG(hr, "D3DCompile failed with %08x", hr); - if (error) - { - LOG_HR_MSG(hr, "D3DCompile error\n%S", static_cast(error->GetBufferPointer())); - } - - THROW_HR(hr); - } - - return code; -#endif -} - -// Routine Description: -// - Checks if terminal effects are enabled. -// Arguments: -// Return Value: -// - True if terminal effects are enabled -bool DxEngine::_HasTerminalEffects() const noexcept -{ - return _terminalEffectsEnabled && (_retroTerminalEffect || !_pixelShaderPath.empty()); -} - -// Routine Description: -// - Loads pixel shader source depending on _retroTerminalEffect and _pixelShaderPath -// Arguments: -// Return Value: -// - Pixel shader source code -std::string DxEngine::_LoadPixelShaderFile() const -{ - // If the user specified the new pixel shader, it has precedence - if (!_pixelShaderPath.empty()) - { - try - { - wil::unique_hfile hFile{ CreateFileW(_pixelShaderPath.c_str(), - GENERIC_READ, - FILE_SHARE_READ, - nullptr, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - nullptr) }; - - THROW_LAST_ERROR_IF(!hFile); // This will be caught below. - - // fileSize is in bytes - const auto fileSize = GetFileSize(hFile.get(), nullptr); - THROW_LAST_ERROR_IF(fileSize == INVALID_FILE_SIZE); - - std::vector utf8buffer; - utf8buffer.reserve(fileSize); - - DWORD bytesRead = 0; - THROW_LAST_ERROR_IF(!ReadFile(hFile.get(), utf8buffer.data(), fileSize, &bytesRead, nullptr)); - - // convert buffer to UTF-8 string - std::string utf8string(utf8buffer.data(), fileSize); - - return utf8string; - } - catch (...) - { - // If we ran into any problems during loading pixel shader, call to - // the warning callback to surface the file not found error - const auto exceptionHr = LOG_CAUGHT_EXCEPTION(); - if (_pfnWarningCallback) - { - _pfnWarningCallback(exceptionHr); - } - - return std::string{}; - } - } - else if (_retroTerminalEffect) - { - return std::string{ retroPixelShaderString }; - } - - return std::string{}; -} - -// Routine Description: -// - Setup D3D objects for doing shader things for terminal effects. -// Arguments: -// Return Value: -// - HRESULT status. -HRESULT DxEngine::_SetupTerminalEffects() -{ - _pixelShaderLoaded = false; - - const auto pixelShaderSource = _LoadPixelShaderFile(); - if (pixelShaderSource.empty()) - { - // There's no shader to compile. This might be due to failing to load, - // or because there's just no shader enabled at all. - // Turn the effects off for now. - _terminalEffectsEnabled = false; - - return S_FALSE; - } - - ::Microsoft::WRL::ComPtr swapBuffer; - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&swapBuffer)); - - // Setup render target. - RETURN_IF_FAILED(_d3dDevice->CreateRenderTargetView(swapBuffer.Get(), nullptr, &_renderTargetView)); - - // Setup _framebufferCapture, to where we'll copy current frame when rendering effects. - D3D11_TEXTURE2D_DESC framebufferCaptureDesc{}; - swapBuffer->GetDesc(&framebufferCaptureDesc); - WI_SetFlag(framebufferCaptureDesc.BindFlags, D3D11_BIND_SHADER_RESOURCE); - RETURN_IF_FAILED(_d3dDevice->CreateTexture2D(&framebufferCaptureDesc, nullptr, &_framebufferCapture)); - - // Setup the viewport. - D3D11_VIEWPORT vp; - vp.Width = static_cast(_displaySizePixels.width); - vp.Height = static_cast(_displaySizePixels.height); - vp.MinDepth = 0.0f; - vp.MaxDepth = 1.0f; - vp.TopLeftX = 0; - vp.TopLeftY = 0; - _d3dDeviceContext->RSSetViewports(1, &vp); - - const char* shaderTargetVS = nullptr; - const char* shaderTargetPS = nullptr; - switch (_d3dDevice->GetFeatureLevel()) - { - case D3D_FEATURE_LEVEL_10_0: - shaderTargetVS = "vs_4_0"; - shaderTargetPS = "ps_4_0"; - break; - case D3D_FEATURE_LEVEL_10_1: - shaderTargetVS = "vs_4_1"; - shaderTargetPS = "ps_4_1"; - break; - default: - shaderTargetVS = "vs_5_0"; - shaderTargetPS = "ps_5_0"; - break; - } - - // Prepare shaders. - auto vertexBlob = _CompileShader(&screenVertexShaderString[0], shaderTargetVS); - Microsoft::WRL::ComPtr pixelBlob; - // As the pixel shader source is user provided it's possible there's a problem with it - // so load it inside a try catch, on any error log and fallback on the error pixel shader - // If even the error pixel shader fails to load rely on standard exception handling - try - { - pixelBlob = _CompileShader(pixelShaderSource, shaderTargetPS); - } - catch (...) - { - // Call to the warning callback to surface the shader compile error - const auto exceptionHr = LOG_CAUGHT_EXCEPTION(); - if (_pfnWarningCallback) - { - // If this fails, it'll return E_FAIL, which is terribly - // uninformative. Instead, raise something more useful. - _pfnWarningCallback(D2DERR_SHADER_COMPILE_FAILED); - } - return exceptionHr; - } - - RETURN_IF_FAILED(_d3dDevice->CreateVertexShader( - vertexBlob->GetBufferPointer(), - vertexBlob->GetBufferSize(), - nullptr, - &_vertexShader)); - - RETURN_IF_FAILED(_d3dDevice->CreatePixelShader( - pixelBlob->GetBufferPointer(), - pixelBlob->GetBufferSize(), - nullptr, - &_pixelShader)); - - RETURN_IF_FAILED(_d3dDevice->CreateInputLayout( - static_cast(_shaderInputLayout), - ARRAYSIZE(_shaderInputLayout), - vertexBlob->GetBufferPointer(), - vertexBlob->GetBufferSize(), - &_vertexLayout)); - - // Create vertex buffer for screen quad. - D3D11_BUFFER_DESC bd{}; - bd.Usage = D3D11_USAGE_DEFAULT; - bd.ByteWidth = sizeof(ShaderInput) * ARRAYSIZE(_screenQuadVertices); - bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; - bd.CPUAccessFlags = 0; - - D3D11_SUBRESOURCE_DATA InitData{}; - InitData.pSysMem = static_cast(_screenQuadVertices); - - RETURN_IF_FAILED(_d3dDevice->CreateBuffer(&bd, &InitData, &_screenQuadVertexBuffer)); - - D3D11_BUFFER_DESC pixelShaderSettingsBufferDesc{}; - pixelShaderSettingsBufferDesc.Usage = D3D11_USAGE_DEFAULT; - pixelShaderSettingsBufferDesc.ByteWidth = sizeof(_pixelShaderSettings); - pixelShaderSettingsBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; - - _shaderStartTime = std::chrono::steady_clock::now(); - - _ComputePixelShaderSettings(); - - D3D11_SUBRESOURCE_DATA pixelShaderSettingsInitData{}; - pixelShaderSettingsInitData.pSysMem = &_pixelShaderSettings; - - RETURN_IF_FAILED(_d3dDevice->CreateBuffer(&pixelShaderSettingsBufferDesc, &pixelShaderSettingsInitData, &_pixelShaderSettingsBuffer)); - - // Sampler state is needed to use texture as input to shader. - D3D11_SAMPLER_DESC samplerDesc{}; - samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; - samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER; - samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER; - samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER; - samplerDesc.MipLODBias = 0.0f; - samplerDesc.MaxAnisotropy = 1; - samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; - samplerDesc.BorderColor[0] = 0; - samplerDesc.BorderColor[1] = 0; - samplerDesc.BorderColor[2] = 0; - samplerDesc.BorderColor[3] = 0; - samplerDesc.MinLOD = 0; - samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; - - // Create the texture sampler state. - RETURN_IF_FAILED(_d3dDevice->CreateSamplerState(&samplerDesc, &_samplerState)); - - _pixelShaderLoaded = true; - return S_OK; -} - -// Routine Description: -// - Puts the correct values in _pixelShaderSettings, so the struct can be -// passed the GPU and updates the GPU resource. -// Arguments: -// - -// Return Value: -// - -void DxEngine::_ComputePixelShaderSettings() noexcept -{ - if (_HasTerminalEffects() && _d3dDeviceContext && _pixelShaderSettingsBuffer) - { - try - { - // Set the time (seconds since the shader was loaded) - _pixelShaderSettings.Time = std::chrono::duration_cast>(std::chrono::steady_clock::now() - _shaderStartTime).count(); - - // Set the UI Scale - _pixelShaderSettings.Scale = _scale; - - // Set the display resolution - const auto w = static_cast(_displaySizePixels.width); - const auto h = static_cast(_displaySizePixels.height); - _pixelShaderSettings.Resolution = XMFLOAT2{ w, h }; - - // Set the background - DirectX::XMFLOAT4 background{}; - background.x = _backgroundColor.r; - background.y = _backgroundColor.g; - background.z = _backgroundColor.b; - background.w = _backgroundColor.a; - _pixelShaderSettings.Background = background; - - _d3dDeviceContext->UpdateSubresource(_pixelShaderSettingsBuffer.Get(), 0, nullptr, &_pixelShaderSettings, 0, 0); - } - CATCH_LOG(); - } -} - -// Method Description: -// - Use DCompositionCreateSurfaceHandle to create a swapchain handle. This API -// is only present in Windows 8.1+, so we need to delay-load it to make sure -// we can still load on Windows 7. -// - We can't actually hit this on Windows 7, because only the WPF control uses -// us on Windows 7, and they're using the ForHwnd path, which doesn't hit this -// at all. -// Arguments: -// - -// Return Value: -// - An HRESULT for failing to load dcomp.dll, or failing to find the API, or an -// actual failure from the API itself. -[[nodiscard]] HRESULT DxEngine::_CreateSurfaceHandle() noexcept -{ -#pragma warning(suppress : 26447) - wil::unique_hmodule hDComp{ LoadLibraryEx(L"Dcomp.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) }; - RETURN_LAST_ERROR_IF(hDComp.get() == nullptr); - - auto fn = GetProcAddressByFunctionDeclaration(hDComp.get(), DCompositionCreateSurfaceHandle); - RETURN_LAST_ERROR_IF(fn == nullptr); - - return fn(GENERIC_ALL, nullptr, &_swapChainHandle); -} - -// Routine Description; -// - Creates device-specific resources required for drawing -// which generally means those that are represented on the GPU and can -// vary based on the monitor, display adapter, etc. -// - These may need to be recreated during the course of painting a frame -// should something about that hardware pipeline change. -// - Will free device resources that already existed as first operation. -// Arguments: -// - createSwapChain - If true, we create the entire rendering pipeline -// - If false, we just set up the adapter. -// Return Value: -// - Could be any DirectX/D3D/D2D/DXGI/DWrite error or memory issue. -[[nodiscard]] HRESULT DxEngine::_CreateDeviceResources(const bool createSwapChain) noexcept -try -{ - if (_haveDeviceResources) - { - _ReleaseDeviceResources(); - } - - auto freeOnFail = wil::scope_exit([&]() noexcept { _ReleaseDeviceResources(); }); - - RETURN_IF_FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&_dxgiFactory2))); - - const DWORD DeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | - // clang-format off -// This causes problems for folks who do not have the whole DirectX SDK installed -// when they try to run the rest of the project in debug mode. -// As such, I'm leaving this flag here for people doing DX-specific work to toggle it -// only when they need it and shutting it off otherwise. -// Find out more about the debug layer here: -// https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-devices-layers -// You can find out how to install it here: -// https://docs.microsoft.com/en-us/windows/uwp/gaming/use-the-directx-runtime-and-visual-studio-graphics-diagnostic-features - // clang-format on - // D3D11_CREATE_DEVICE_DEBUG | - D3D11_CREATE_DEVICE_SINGLETHREADED; - - static constexpr std::array FeatureLevels{ - D3D_FEATURE_LEVEL_11_1, - D3D_FEATURE_LEVEL_11_0, - D3D_FEATURE_LEVEL_10_1, - D3D_FEATURE_LEVEL_10_0, - }; - - // Trying hardware first for maximum performance, then trying WARP (software) renderer second - // in case we're running inside a downlevel VM where hardware passthrough isn't enabled like - // for Windows 7 in a VM. - auto hardwareResult = E_NOT_SET; - - // If we're not forcing software rendering, try hardware first. - // Otherwise, let the error state fall down and create with the software renderer directly. - if (!_softwareRendering) - { - hardwareResult = D3D11CreateDevice(nullptr, - D3D_DRIVER_TYPE_HARDWARE, - nullptr, - DeviceFlags, - FeatureLevels.data(), - gsl::narrow_cast(FeatureLevels.size()), - D3D11_SDK_VERSION, - &_d3dDevice, - nullptr, - &_d3dDeviceContext); - } - - if (FAILED(hardwareResult)) - { - RETURN_IF_FAILED(D3D11CreateDevice(nullptr, - D3D_DRIVER_TYPE_WARP, - nullptr, - DeviceFlags, - FeatureLevels.data(), - gsl::narrow_cast(FeatureLevels.size()), - D3D11_SDK_VERSION, - &_d3dDevice, - nullptr, - &_d3dDeviceContext)); - } - - _displaySizePixels = _GetClientSize(); - - // Get the other device types so we have deeper access to more functionality - // in our pipeline than by just walking straight from the D3D device. - - RETURN_IF_FAILED(_d3dDevice.As(&_dxgiDevice)); - RETURN_IF_FAILED(_d2dFactory->CreateDevice(_dxgiDevice.Get(), _d2dDevice.ReleaseAndGetAddressOf())); - - // Create a device context out of it (supersedes render targets) - RETURN_IF_FAILED(_d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &_d2dDeviceContext)); - - if (createSwapChain) - { - _swapChainDesc = { 0 }; - _swapChainDesc.Flags = 0; - - // requires DXGI 1.3 which was introduced in Windows 8.1 - WI_SetFlagIf(_swapChainDesc.Flags, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT, IsWindows8Point1OrGreater()); - - _swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - _swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - _swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; - _swapChainDesc.BufferCount = 2; - _swapChainDesc.SampleDesc.Count = 1; - _swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; - _swapChainDesc.Scaling = DXGI_SCALING_NONE; - - switch (_chainMode) - { - case SwapChainMode::ForHwnd: - { - // use the HWND's dimensions for the swap chain dimensions. - RECT rect{}; - RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect)); - - _swapChainDesc.Width = rect.right - rect.left; - _swapChainDesc.Height = rect.bottom - rect.top; - - // We can't do alpha for HWNDs. Set to ignore. It will fail otherwise. - _swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; - const auto createSwapChainResult = _dxgiFactory2->CreateSwapChainForHwnd(_d3dDevice.Get(), - _hwndTarget, - &_swapChainDesc, - nullptr, - nullptr, - &_dxgiSwapChain); - if (FAILED(createSwapChainResult)) - { - _swapChainDesc.Scaling = DXGI_SCALING_STRETCH; - RETURN_IF_FAILED(_dxgiFactory2->CreateSwapChainForHwnd(_d3dDevice.Get(), - _hwndTarget, - &_swapChainDesc, - nullptr, - nullptr, - &_dxgiSwapChain)); - } - - break; - } - case SwapChainMode::ForComposition: - { - if (!_swapChainHandle) - { - RETURN_IF_FAILED(_CreateSurfaceHandle()); - } - - RETURN_IF_FAILED(_dxgiFactory2.As(&_dxgiFactoryMedia)); - - // Use the given target size for compositions. - _swapChainDesc.Width = _displaySizePixels.narrow_width(); - _swapChainDesc.Height = _displaySizePixels.narrow_height(); - - // We're doing advanced composition pretty much for the purpose of pretty alpha, so turn it on. - _swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; - // It's 100% required to use scaling mode stretch for composition. There is no other choice. - _swapChainDesc.Scaling = DXGI_SCALING_STRETCH; - - RETURN_IF_FAILED(_dxgiFactoryMedia->CreateSwapChainForCompositionSurfaceHandle(_d3dDevice.Get(), - _swapChainHandle.get(), - &_swapChainDesc, - nullptr, - &_dxgiSwapChain)); - break; - } - default: - THROW_HR(E_NOTIMPL); - } - - if (IsWindows8Point1OrGreater()) - { - ::Microsoft::WRL::ComPtr swapChain2; - const auto asResult = _dxgiSwapChain.As(&swapChain2); - if (SUCCEEDED(asResult)) - { - _swapChainFrameLatencyWaitableObject = wil::unique_handle{ swapChain2->GetFrameLatencyWaitableObject() }; - } - else - { - LOG_HR_MSG(asResult, "Failed to obtain IDXGISwapChain2 from swap chain"); - } - } - - if (_HasTerminalEffects()) - { - const auto hr = _SetupTerminalEffects(); - if (FAILED(hr)) - { - LOG_HR_MSG(hr, "Failed to setup terminal effects. Disabling."); - _terminalEffectsEnabled = false; - } - } - - // With a new swap chain, mark the entire thing as invalid. - RETURN_IF_FAILED(InvalidateAll()); - - // This is our first frame on this new target. - _firstFrame = true; - - RETURN_IF_FAILED(_PrepareRenderTarget()); - } - - _haveDeviceResources = true; - if (_isPainting) - { - // TODO: MSFT: 21169176 - remove this or restore the "try a few times to render" code... I think - _d2dDeviceContext->BeginDraw(); - } - - freeOnFail.release(); // don't need to release if we made it to the bottom and everything was good. - - // Notify that swap chain changed. - - if (_pfn) - { - try - { - _pfn(_swapChainHandle.get()); - } - CATCH_LOG(); // A failure in the notification function isn't a failure to prepare, so just log it and go on. - } - - _recreateDeviceRequested = false; - - return S_OK; -} -CATCH_RETURN(); - -static constexpr D2D1_ALPHA_MODE _dxgiAlphaToD2d1Alpha(DXGI_ALPHA_MODE mode) noexcept -{ - switch (mode) - { - case DXGI_ALPHA_MODE_PREMULTIPLIED: - return D2D1_ALPHA_MODE_PREMULTIPLIED; - case DXGI_ALPHA_MODE_STRAIGHT: - return D2D1_ALPHA_MODE_STRAIGHT; - case DXGI_ALPHA_MODE_IGNORE: - return D2D1_ALPHA_MODE_IGNORE; - case DXGI_ALPHA_MODE_FORCE_DWORD: - return D2D1_ALPHA_MODE_FORCE_DWORD; - default: - case DXGI_ALPHA_MODE_UNSPECIFIED: - return D2D1_ALPHA_MODE_UNKNOWN; - } -} - -[[nodiscard]] HRESULT DxEngine::_PrepareRenderTarget() noexcept -{ - try - { - // Pull surface out of swap chain. - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&_dxgiSurface))); - - // Make a bitmap and bind it to the swap chain surface - const auto bitmapProperties = D2D1::BitmapProperties1( - D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, - D2D1::PixelFormat(_swapChainDesc.Format, _dxgiAlphaToD2d1Alpha(_swapChainDesc.AlphaMode))); - - RETURN_IF_FAILED(_d2dDeviceContext->CreateBitmapFromDxgiSurface(_dxgiSurface.Get(), bitmapProperties, &_d2dBitmap)); - - // Assign that bitmap as the target of the D2D device context. Draw commands hit the context - // and are backed by the bitmap which is bound to the swap chain which goes on to be presented. - // (The foot bone connected to the leg bone, - // The leg bone connected to the knee bone, - // The knee bone connected to the thigh bone - // ... and so on) - - _d2dDeviceContext->SetTarget(_d2dBitmap.Get()); - - // We need the AntialiasMode for non-text object to be Aliased to ensure - // that background boxes line up with each other and don't leave behind - // stray colors. - // See GH#3626 for more details. - _d2dDeviceContext->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); - _d2dDeviceContext->SetTextAntialiasMode(_antialiasingMode); - - RETURN_IF_FAILED(_d2dDeviceContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::DarkRed), - &_d2dBrushBackground)); - - RETURN_IF_FAILED(_d2dDeviceContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), - &_d2dBrushForeground)); - - _strokeStyleProperties = D2D1_STROKE_STYLE_PROPERTIES{ - D2D1_CAP_STYLE_SQUARE, // startCap - D2D1_CAP_STYLE_SQUARE, // endCap - D2D1_CAP_STYLE_SQUARE, // dashCap - D2D1_LINE_JOIN_MITER, // lineJoin - 0.f, // miterLimit - D2D1_DASH_STYLE_SOLID, // dashStyle - 0.f, // dashOffset - }; - RETURN_IF_FAILED(_d2dFactory->CreateStrokeStyle(&_strokeStyleProperties, nullptr, 0, &_strokeStyle)); - - _dashStrokeStyleProperties = D2D1_STROKE_STYLE_PROPERTIES{ - D2D1_CAP_STYLE_SQUARE, // startCap - D2D1_CAP_STYLE_SQUARE, // endCap - D2D1_CAP_STYLE_FLAT, // dashCap - D2D1_LINE_JOIN_MITER, // lineJoin - 0.f, // miterLimit - D2D1_DASH_STYLE_CUSTOM, // dashStyle - 0.f, // dashOffset - }; - // Custom dashes: - // # # # # - // 1234123412341234 - static constexpr std::array hyperlinkDashes{ 1.f, 3.f }; - RETURN_IF_FAILED(_d2dFactory->CreateStrokeStyle(&_dashStrokeStyleProperties, hyperlinkDashes.data(), gsl::narrow_cast(hyperlinkDashes.size()), &_dashStrokeStyle)); - - // If in composition mode, apply scaling factor matrix - if (_chainMode == SwapChainMode::ForComposition) - { - DXGI_MATRIX_3X2_F inverseScale = { 0 }; - inverseScale._11 = 1.0f / _scale; - inverseScale._22 = inverseScale._11; - - ::Microsoft::WRL::ComPtr sc2; - RETURN_IF_FAILED(_dxgiSwapChain.As(&sc2)); - RETURN_IF_FAILED(sc2->SetMatrixTransform(&inverseScale)); - } - - _prevScale = _scale; - return S_OK; - } - CATCH_RETURN(); -} - -// Routine Description: -// - Releases device-specific resources (typically held on the GPU) -// Arguments: -// - -// Return Value: -// - -void DxEngine::_ReleaseDeviceResources() noexcept -{ - try - { - _haveDeviceResources = false; - - // Destroy Terminal Effect resources - _renderTargetView.Reset(); - _vertexShader.Reset(); - _pixelShader.Reset(); - _vertexLayout.Reset(); - _screenQuadVertexBuffer.Reset(); - _pixelShaderSettingsBuffer.Reset(); - _samplerState.Reset(); - _framebufferCapture.Reset(); - - _d2dBrushForeground.Reset(); - _d2dBrushBackground.Reset(); - - _d2dBitmap.Reset(); - - _softFont.Reset(); - - if (nullptr != _d2dDeviceContext.Get() && _isPainting) - { - _d2dDeviceContext->EndDraw(); - } - - _d2dDeviceContext.Reset(); - - _dxgiSurface.Reset(); - _dxgiSwapChain.Reset(); - _swapChainFrameLatencyWaitableObject.reset(); - - _d2dDevice.Reset(); - _dxgiDevice.Reset(); - - if (nullptr != _d3dDeviceContext.Get()) - { - // To ensure the swap chain goes away we must unbind any views from the - // D3D pipeline - _d3dDeviceContext->OMSetRenderTargets(0, nullptr, nullptr); - } - _d3dDeviceContext.Reset(); - - _d3dDevice.Reset(); - - _dxgiFactory2.Reset(); - } - CATCH_LOG(); -} - -// Routine Description: -// - Calculates whether or not we should force grayscale AA based on the -// current renderer state. -// Arguments: -// - - Uses internal state of _antialiasingMode, _defaultTextBackgroundOpacity, -// _backgroundColor, and _defaultBackgroundColor. -// Return Value: -// - True if we must render this text in grayscale AA as cleartype simply won't work. False otherwise. -[[nodiscard]] bool DxEngine::_ShouldForceGrayscaleAA() noexcept -{ - // GH#5098: If we're rendering with cleartype text, we need to always - // render onto an opaque background. If our background's opacity is - // 1.0f, that's great, we can use that. Otherwise, we need to force the - // text renderer to render this text in grayscale. In - // UpdateDrawingBrushes, we'll set the backgroundColor's a channel to - // 1.0 if we're in cleartype mode and the background's opacity is 1.0. - // Otherwise, at this point, the _backgroundColor's alpha is <1.0. - // - // Currently, only text with the default background color uses an alpha - // of 0, every other background uses 1.0 - // - // DANGER: Layers slow us down. Only do this in the specific case where - // someone has chosen the slower ClearType antialiasing (versus the faster - // grayscale antialiasing) - const auto usingCleartype = _antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; - const auto usingTransparency = _defaultBackgroundIsTransparent; - // Another way of naming "bgIsDefault" is "bgHasTransparency" - const auto bgIsDefault = (_backgroundColor.a == _defaultBackgroundColor.a) && - (_backgroundColor.r == _defaultBackgroundColor.r) && - (_backgroundColor.g == _defaultBackgroundColor.g) && - (_backgroundColor.b == _defaultBackgroundColor.b); - const auto forceGrayscaleAA = usingCleartype && - usingTransparency && - bgIsDefault; - - return forceGrayscaleAA; -} - -// Routine Description: -// - Helper to create a DirectWrite text layout object -// out of a string. -// Arguments: -// - string - The text to attempt to layout -// - stringLength - Length of string above in characters -// - ppTextLayout - Location to receive new layout object -// Return Value: -// - S_OK if layout created successfully, otherwise a DirectWrite error -[[nodiscard]] HRESULT DxEngine::_CreateTextLayout( - _In_reads_(stringLength) PCWCHAR string, - _In_ size_t stringLength, - _Out_ IDWriteTextLayout** ppTextLayout) noexcept -try -{ - return _dwriteFactory->CreateTextLayout(string, - gsl::narrow(stringLength), - _fontRenderData->DefaultTextFormat().Get(), - static_cast(_displaySizePixels.width), - _fontRenderData->GlyphCell().height != 0 ? _fontRenderData->GlyphCell().narrow_height() : _displaySizePixels.narrow_height(), - ppTextLayout); -} -CATCH_RETURN() - -// Routine Description: -// - Sets the target window handle for our display pipeline -// - We will take over the surface of this window for drawing -// Arguments: -// - hwnd - Window handle -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::SetHwnd(const HWND hwnd) noexcept -{ - _hwndTarget = hwnd; - _chainMode = SwapChainMode::ForHwnd; - return S_OK; -} - -[[nodiscard]] HRESULT DxEngine::SetWindowSize(const til::size Pixels) noexcept -try -{ - _sizeTarget = Pixels; - return S_OK; -} -CATCH_RETURN(); - -void DxEngine::SetCallback(std::function pfn) noexcept -{ - _pfn = std::move(pfn); -} - -void DxEngine::SetWarningCallback(std::function pfn) noexcept -{ - _pfnWarningCallback = std::move(pfn); -} - -bool DxEngine::GetRetroTerminalEffect() const noexcept -{ - return _retroTerminalEffect; -} - -void DxEngine::SetRetroTerminalEffect(bool enable) noexcept -try -{ - if (_retroTerminalEffect != enable) - { - // Enable shader effects if the path isn't empty. Otherwise leave it untouched. - _terminalEffectsEnabled = enable ? true : _terminalEffectsEnabled; - _retroTerminalEffect = enable; - _recreateDeviceRequested = true; - LOG_IF_FAILED(InvalidateAll()); - } -} -CATCH_LOG() - -std::wstring_view DxEngine::GetPixelShaderPath() noexcept -{ - return _pixelShaderPath; -} - -void DxEngine::SetPixelShaderPath(std::wstring_view value) noexcept -try -{ - if (_pixelShaderPath != value) - { - // Enable shader effects if the path isn't empty. Otherwise leave it untouched. - _terminalEffectsEnabled = value.empty() ? _terminalEffectsEnabled : true; - _pixelShaderPath = std::wstring{ value }; - _recreateDeviceRequested = true; - LOG_IF_FAILED(InvalidateAll()); - } -} -CATCH_LOG() - -void DxEngine::SetForceFullRepaintRendering(bool enable) noexcept -try -{ - if (_forceFullRepaintRendering != enable) - { - _forceFullRepaintRendering = enable; - LOG_IF_FAILED(InvalidateAll()); - } -} -CATCH_LOG() - -void DxEngine::SetSoftwareRendering(bool enable) noexcept -try -{ - if (_softwareRendering != enable) - { - _softwareRendering = enable; - _recreateDeviceRequested = true; - LOG_IF_FAILED(InvalidateAll()); - } -} -CATCH_LOG() - -void DxEngine::_InvalidateRectangle(const til::rect& rc) -{ - const auto size = _invalidMap.size(); - const auto topLeft = til::point{ 0, std::clamp(rc.top, 0, size.height) }; - const auto bottomRight = til::point{ size.width, std::clamp(rc.bottom, 0, size.height) }; - _invalidMap.set({ topLeft, bottomRight }); -} - -bool DxEngine::_IsAllInvalid() const noexcept -{ - return std::abs(_invalidScroll.y) >= _invalidMap.size().height; -} - -// Routine Description: -// - Invalidates a rectangle described in characters -// Arguments: -// - psrRegion - Character rectangle -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::Invalidate(const til::rect* const psrRegion) noexcept -try -{ - RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion); - - if (!_allInvalid) - { - _InvalidateRectangle(*psrRegion); - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Invalidates the cells of the cursor -// Arguments: -// - psrRegion - the region covered by the cursor -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::InvalidateCursor(const til::rect* const psrRegion) noexcept -{ - return Invalidate(psrRegion); -} - -// Routine Description: -// - Invalidates a rectangle describing a pixel area on the display -// Arguments: -// - prcDirtyClient - pixel rectangle -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::InvalidateSystem(const til::rect* const prcDirtyClient) noexcept -try -{ - RETURN_HR_IF_NULL(E_INVALIDARG, prcDirtyClient); - - if (!_allInvalid) - { - // Dirty client is in pixels. Use divide specialization against glyph factor to make conversion - // to cells. - _InvalidateRectangle(prcDirtyClient->scale_down(_fontRenderData->GlyphCell())); - } - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Invalidates a series of character rectangles -// Arguments: -// - rectangles - One or more rectangles describing character positions on the grid -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::InvalidateSelection(const std::vector& rectangles) noexcept -{ - if (!_allInvalid) - { - for (const auto& rect : rectangles) - { - RETURN_IF_FAILED(Invalidate(&rect)); - } - } - return S_OK; -} - -// Routine Description: -// - Scrolls the existing dirty region (if it exists) and -// invalidates the area that is uncovered in the window. -// Arguments: -// - pcoordDelta - The number of characters to move and uncover. -// - -Y is up, Y is down, -X is left, X is right. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::InvalidateScroll(const til::point* const pcoordDelta) noexcept -try -{ - RETURN_HR_IF(E_INVALIDARG, !pcoordDelta); - - const auto deltaCells{ *pcoordDelta }; - - if (!_allInvalid) - { - if (deltaCells != til::point{}) - { - // Shift the contents of the map and fill in revealed area. - _invalidMap.translate(deltaCells, true); - _invalidScroll += deltaCells; - _allInvalid = _IsAllInvalid(); - } - } - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Invalidates the entire window area -// Arguments: -// - -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::InvalidateAll() noexcept -try -{ - _invalidMap.set_all(); - _allInvalid = true; - - // Since everything is invalidated here, mark this as a "first frame", so - // that we won't use incremental drawing on it. The caller of this intended - // for _everything_ to get redrawn, so setting _firstFrame will force us to - // redraw the entire frame. This will make sure that things like the gutters - // get cleared correctly. - // - // Invalidating everything is supposed to happen with resizes of the - // entire canvas, changes of the font, and other such adjustments. - _firstFrame = true; - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Gets the area in pixels of the surface we are targeting -// Arguments: -// - -// Return Value: -// - X by Y area in pixels of the surface -[[nodiscard]] til::size DxEngine::_GetClientSize() const -{ - switch (_chainMode) - { - case SwapChainMode::ForHwnd: - { - RECT clientRect{}; - LOG_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &clientRect)); - - return til::rect{ clientRect }.size(); - } - case SwapChainMode::ForComposition: - { - return _sizeTarget; - } - default: - FAIL_FAST_HR(E_NOTIMPL); - } -} - -// Routine Description: -// - Helper to multiply all parameters of a rectangle by the font size -// to convert from characters to pixels. -// Arguments: -// - cellsToPixels - rectangle to update -// - fontSize - scaling factors -// Return Value: -// - - Updates reference -void _ScaleByFont(til::rect& cellsToPixels, til::size fontSize) noexcept -{ - cellsToPixels.left *= fontSize.width; - cellsToPixels.right *= fontSize.width; - cellsToPixels.top *= fontSize.height; - cellsToPixels.bottom *= fontSize.height; -} - -// Routine Description: -// - This is unused by this renderer. -// Arguments: -// - pForcePaint - always filled with false. -// Return Value: -// - S_FALSE because this is unused. -[[nodiscard]] HRESULT DxEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); - - *pForcePaint = false; - return S_FALSE; -} - -// Routine description: -// - Prepares the surfaces for painting and begins a drawing batch -// Arguments: -// - -// Return Value: -// - Any DirectX error, a memory error, etc. -[[nodiscard]] HRESULT DxEngine::StartPaint() noexcept -try -{ - RETURN_HR_IF(E_NOT_VALID_STATE, _isPainting); // invalid to start a paint while painting. - - // If full repaints are needed then we need to invalidate everything - // so the entire frame is repainted. - if (_FullRepaintNeeded()) - { - RETURN_IF_FAILED(InvalidateAll()); - } - - if (TraceLoggingProviderEnabled(g_hDxRenderProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto invalidatedStr = _invalidMap.to_string(); - const auto invalidated = invalidatedStr.c_str(); - -#pragma warning(suppress : 26477 26485 26494 26482 26446 26447) // We don't control TraceLoggingWrite - TraceLoggingWrite(g_hDxRenderProvider, - "Invalid", - TraceLoggingWideString(invalidated), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } - - if (_isEnabled) - { - const auto clientSize = _GetClientSize(); - const auto glyphCellSize = _fontRenderData->GlyphCell(); - - // If we don't have device resources or if someone has requested that we - // recreate the device... then make new resources. (Create will dump the old ones.) - if (!_haveDeviceResources || _recreateDeviceRequested) - { - RETURN_IF_FAILED(_CreateDeviceResources(true)); - } - else if (_displaySizePixels != clientSize || _prevScale != _scale) - { - // OK, we're going to play a dangerous game here for the sake of optimizing resize - // First, set up a complete clear of all device resources if something goes terribly wrong. - auto resetDeviceResourcesOnFailure = wil::scope_exit([&]() noexcept { - _ReleaseDeviceResources(); - }); - - // Now let go of a few of the device resources that get in the way of resizing buffers in the swap chain - _dxgiSurface.Reset(); - _d2dDeviceContext->SetTarget(nullptr); - _d2dBitmap.Reset(); - - // Change the buffer size and recreate the render target (and surface) - RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.narrow_width(), clientSize.narrow_height(), _swapChainDesc.Format, _swapChainDesc.Flags)); - RETURN_IF_FAILED(_PrepareRenderTarget()); - - // OK we made it past the parts that can cause errors. We can release our failure handler. - resetDeviceResourcesOnFailure.release(); - - // And persist the new size. - _displaySizePixels = clientSize; - } - - if (const auto size = clientSize / glyphCellSize; size != _invalidMap.size()) - { - _invalidMap.resize(size); - RETURN_IF_FAILED(InvalidateAll()); - } - - _d2dDeviceContext->BeginDraw(); - _isPainting = true; - - { - // Get the baseline for this font as that's where we draw from - DWRITE_LINE_SPACING spacing; - RETURN_IF_FAILED(_fontRenderData->DefaultTextFormat()->GetLineSpacing(&spacing.method, &spacing.height, &spacing.baseline)); - - // Assemble the drawing context information - _drawingContext = std::make_unique(_d2dDeviceContext.Get(), - _d2dBrushForeground.Get(), - _d2dBrushBackground.Get(), - _ShouldForceGrayscaleAA(), - _dwriteFactory.Get(), - spacing, - glyphCellSize.to_d2d_size(), - _d2dDeviceContext->GetSize(), - std::nullopt, - D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT); - } - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Ends batch drawing and captures any state necessary for presentation -// Arguments: -// - -// Return Value: -// - Any DirectX error, a memory error, etc. -[[nodiscard]] HRESULT DxEngine::EndPaint() noexcept -try -{ - RETURN_HR_IF(E_INVALIDARG, !_isPainting); // invalid to end paint when we're not painting - - auto hr = S_OK; - - if (_haveDeviceResources) - { - _isPainting = false; - - // If there's still a clip hanging around, remove it. We're all done. - LOG_IF_FAILED(_customRenderer->EndClip(_drawingContext.get())); - - hr = _d2dDeviceContext->EndDraw(); - - if (SUCCEEDED(hr)) - { - if (_invalidScroll != til::point{ 0, 0 }) - { - // Copy `til::rects` into RECT map. - _presentDirty.assign(_invalidMap.begin(), _invalidMap.end()); - - // Scale all dirty rectangles into pixels - std::transform(_presentDirty.begin(), _presentDirty.end(), _presentDirty.begin(), [&](const til::rect& rc) { - return rc.scale_up(_fontRenderData->GlyphCell()); - }); - - // Invalid scroll is in characters, convert it to pixels. - const auto scrollPixels = (_invalidScroll * _fontRenderData->GlyphCell()); - - // The scroll rect is the entire field of cells, but in pixels. - til::rect scrollArea{ _invalidMap.size() * _fontRenderData->GlyphCell() }; - - // Reduce the size of the rectangle by the scroll. - scrollArea.left = std::clamp(scrollArea.left + scrollPixels.x, scrollArea.left, scrollArea.right); - scrollArea.top = std::clamp(scrollArea.top + scrollPixels.y, scrollArea.top, scrollArea.bottom); - scrollArea.right = std::clamp(scrollArea.right + scrollPixels.x, scrollArea.left, scrollArea.right); - scrollArea.bottom = std::clamp(scrollArea.bottom + scrollPixels.y, scrollArea.top, scrollArea.bottom); - - // Assign the area to the present storage - _presentScroll = scrollArea.to_win32_rect(); - - // Pass the offset. - _presentOffset = scrollPixels.to_win32_point(); - - // Now fill up the parameters structure from the member variables. - _presentParams.DirtyRectsCount = gsl::narrow(_presentDirty.size()); - - // It's not nice to use reinterpret_cast between til::rect and RECT, - // but to be honest... it does save a ton of type juggling. - static_assert(sizeof(decltype(_presentDirty)::value_type) == sizeof(RECT)); -#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). - _presentParams.pDirtyRects = reinterpret_cast(_presentDirty.data()); - - _presentParams.pScrollOffset = &_presentOffset; - _presentParams.pScrollRect = &_presentScroll; - - // The scroll rect will be empty if we scrolled >= 1 full screen size. - // Present1 doesn't like that. So clear it out. Everything will be dirty anyway. - if (IsRectEmpty(&_presentScroll)) - { - _presentParams.pScrollRect = nullptr; - _presentParams.pScrollOffset = nullptr; - } - } - - _presentReady = true; - } - else - { - _presentReady = false; - _ReleaseDeviceResources(); - } - } - - _invalidMap.reset_all(); - _allInvalid = false; - - _invalidScroll = {}; - - return hr; -} -CATCH_RETURN() - -// Routine Description: -// - Copies the front surface of the swap chain (the one being displayed) -// to the back surface of the swap chain (the one we draw on next) -// so we can draw on top of what's already there. -// Arguments: -// - -// Return Value: -// - Any DirectX error, a memory error, etc. -[[nodiscard]] HRESULT DxEngine::_CopyFrontToBack() noexcept -{ - try - { - Microsoft::WRL::ComPtr backBuffer; - Microsoft::WRL::ComPtr frontBuffer; - - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))); - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(1, IID_PPV_ARGS(&frontBuffer))); - - _d3dDeviceContext->CopyResource(backBuffer.Get(), frontBuffer.Get()); - } - CATCH_RETURN(); - - return S_OK; -} - -// Method Description: -// - When the shaders are on, say that we need to keep redrawing every -// possible frame in case they have some smooth action on every frame tick. -// It is presumed that if you're using shaders, you're not about performance... -// You're instead about OOH SHINY. And that's OK. But returning true here is 100% -// a perf detriment. -[[nodiscard]] bool DxEngine::RequiresContinuousRedraw() noexcept -{ - // We're only going to request continuous redraw if someone is using - // a pixel shader from a path because we cannot tell if those are using the - // time parameter or not. - // And if they are using time, they probably need it to tick continuously. - // - // By contrast, the in-built retro effect does NOT need it, - // so let's not tick for it and save some amount of performance. - // - // Finally... if we're not using effects at all... let the render thread - // go to sleep. It deserves it. That thread works hard. Also it sleeping - // saves battery power and all sorts of related perf things. - return _terminalEffectsEnabled && !_pixelShaderPath.empty(); -} - -// Method Description: -// - Blocks until the engine is able to render without blocking. -// - See https://docs.microsoft.com/en-us/windows/uwp/gaming/reduce-latency-with-dxgi-1-3-swap-chains. -void DxEngine::WaitUntilCanRender() noexcept -{ - // Throttle the DxEngine a bit down to ~60 FPS. - // This improves throughput for rendering complex or colored text. - Sleep(8); - - if (_swapChainFrameLatencyWaitableObject) - { - WaitForSingleObjectEx(_swapChainFrameLatencyWaitableObject.get(), 100, true); - } -} - -// Routine Description: -// - Takes queued drawing information and presents it to the screen. -// - This is separated out so it can be done outside the lock as it's expensive. -// Arguments: -// - -// Return Value: -// - S_OK on success, E_PENDING to indicate a retry or a relevant DirectX error -[[nodiscard]] HRESULT DxEngine::Present() noexcept -{ - if (_presentReady) - { - if (_HasTerminalEffects() && _pixelShaderLoaded) - { - const auto hr2 = _PaintTerminalEffects(); - if (FAILED(hr2)) - { - _pixelShaderLoaded = false; - LOG_HR_MSG(hr2, "Failed to paint terminal effects. Disabling."); - } - } - - try - { - auto hr = S_OK; - - auto recreate = false; - - // On anything but the first frame, try partial presentation. - // We'll do it first because if it fails, we'll try again with full presentation. - if (!_firstFrame) - { - hr = _dxgiSwapChain->Present1(1, 0, &_presentParams); - - // These two error codes are indicated for destroy-and-recreate - // If we were told to destroy-and-recreate, we're going to skip straight into doing that - // and not try again with full presentation. - recreate = hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET; - - // Log this as we actually don't expect it to happen, we just will try again - // below for robustness of our drawing. - if (FAILED(hr) && !recreate) - { - LOG_HR(hr); - } - } - - // If it's the first frame through, we cannot do partial presentation. - // Also if partial presentation failed above and we weren't told to skip straight to - // device recreation. - // In both of these circumstances, do a full presentation. - if (_firstFrame || (FAILED(hr) && !recreate)) - { - hr = _dxgiSwapChain->Present(1, 0); - _firstFrame = false; - - // These two error codes are indicated for destroy-and-recreate - recreate = hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET; - } - - // Now check for failure cases from either presentation mode. - if (FAILED(hr)) - { - // If we were told to recreate the device surface, do that. - if (recreate) - { - // We don't need to end painting here, as the renderer has done it for us. - _ReleaseDeviceResources(); - FAIL_FAST_IF_FAILED(InvalidateAll()); - return E_PENDING; // Indicate a retry to the renderer. - } - // Otherwise, we don't know what to do with this error. Report it. - else - { - FAIL_FAST_HR(hr); - } - } - - // If we are doing full repaints we don't need to copy front buffer to back buffer - if (!_FullRepaintNeeded()) - { - // Finally copy the front image (being presented now) onto the backing buffer - // (where we are about to draw the next frame) so we can draw only the differences - // next frame. - RETURN_IF_FAILED(_CopyFrontToBack()); - } - - _presentReady = false; - - _presentDirty.clear(); - _presentOffset = { 0 }; - _presentScroll = { 0 }; - _presentParams = { 0 }; - } - CATCH_RETURN(); - } - - return S_OK; -} - -// Routine Description: -// - This is currently unused. -// Arguments: -// - -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::ScrollFrame() noexcept -{ - return S_OK; -} - -// Routine Description: -// - This paints in the back most layer of the frame with the background color. -// Arguments: -// - -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::PaintBackground() noexcept -try -{ - D2D1_COLOR_F nothing{ 0 }; - if (_chainMode == SwapChainMode::ForHwnd) - { - // When we're drawing over an HWND target, we need to fully paint the background color. - nothing = _backgroundColor; - } - - // If the entire thing is invalid, just use one big clear operation. - if (_invalidMap.all()) - { - _d2dDeviceContext->Clear(nothing); - } - else - { - // Runs are counts of cells. - // Use a transform by the size of one cell to convert cells-to-pixels - // as we clear. - _d2dDeviceContext->SetTransform(D2D1::Matrix3x2F::Scale(_fontRenderData->GlyphCell().to_d2d_size())); - for (const auto& rect : _invalidMap.runs()) - { - // Use aliased. - // For graphics reasons, it'll look better because it will ensure that - // the edges are cut nice and sharp (not blended by anti-aliasing). - // For performance reasons, it takes a lot less work to not - // do anti-alias blending. - _d2dDeviceContext->PushAxisAlignedClip(rect.to_d2d_rect(), D2D1_ANTIALIAS_MODE_ALIASED); - _d2dDeviceContext->Clear(nothing); - _d2dDeviceContext->PopAxisAlignedClip(); - } - _d2dDeviceContext->SetTransform(D2D1::Matrix3x2F::Identity()); - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Places one line of text onto the screen at the given position -// Arguments: -// - clusters - Iterable collection of cluster information (text and columns it should consume) -// - coord - Character coordinate position in the cell grid -// - fTrimLeft - Whether or not to trim off the left half of a double wide character -// Return Value: -// - S_OK or relevant DirectX error -[[nodiscard]] HRESULT DxEngine::PaintBufferLine(const std::span clusters, - const til::point coord, - const bool /*trimLeft*/, - const bool /*lineWrapped*/) noexcept -try -{ - // Calculate positioning of our origin. - const auto origin = (coord * _fontRenderData->GlyphCell()).to_d2d_point(); - - if (_usingSoftFont) - { - // We need to reset the clipping rect applied by the CustomTextRenderer, - // since the soft font will want to set its own clipping rect. - RETURN_IF_FAILED(_customRenderer->EndClip(_drawingContext.get())); - RETURN_IF_FAILED(_softFont.Draw(*_drawingContext, clusters, origin.x, origin.y)); - } - else - { - // Create the text layout - RETURN_IF_FAILED(_customLayout->Reset()); - RETURN_IF_FAILED(_customLayout->AppendClusters(clusters)); - - // Layout then render the text - RETURN_IF_FAILED(_customLayout->Draw(_drawingContext.get(), _customRenderer.Get(), origin.x, origin.y)); - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Paints lines around cells (draws in pieces of the grid) -// Arguments: -// - lines - Which grid lines (top, left, bottom, right) to draw -// - gridlineColor - The color to use for drawing the gridlines -// - underlineColor - The color to use for drawing the underlines -// - cchLine - Length of the line to draw in character cells -// - coordTarget - The X,Y character position in the grid where we should start drawing -// - We will draw rightward (+X) from here -// Return Value: -// - S_OK or relevant DirectX error -[[nodiscard]] HRESULT DxEngine::PaintBufferGridLines(const GridLineSet lines, - const COLORREF gridlineColor, - const COLORREF underlineColor, - const size_t cchLine, - const til::point coordTarget) noexcept -try -{ - const auto existingColor = _d2dBrushForeground->GetColor(); - const auto restoreBrushOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); }); - - const auto font = _fontRenderData->GlyphCell().to_d2d_size(); - const D2D_POINT_2F target = { coordTarget.x * font.width, coordTarget.y * font.height }; - const auto fullRunWidth = font.width * gsl::narrow_cast(cchLine); - - const auto DrawLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept { - _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _strokeStyle.Get()); - }; - - const auto DrawDottedLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept { - _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _dashStrokeStyle.Get()); - }; - - _d2dBrushForeground->SetColor(_ColorFFromColorRef(gridlineColor | 0xff000000)); - - // NOTE: Line coordinates are centered within the line, so they need to be - // offset by half the stroke width. For the start coordinate we add half - // the stroke width, and for the end coordinate we subtract half the width. - const auto lineMetrics = _fontRenderData->GetLineMetrics(); - if (lines.any(GridLines::Left, GridLines::Right)) - { - const auto halfGridlineWidth = lineMetrics.gridlineWidth / 2.0f; - const auto startY = target.y + halfGridlineWidth; - const auto endY = target.y + font.height - halfGridlineWidth; - - if (lines.test(GridLines::Left)) - { - auto x = target.x + halfGridlineWidth; - for (size_t i = 0; i < cchLine; i++, x += font.width) - { - DrawLine(x, startY, x, endY, lineMetrics.gridlineWidth); - } - } - - if (lines.test(GridLines::Right)) - { - auto x = target.x + font.width - halfGridlineWidth; - for (size_t i = 0; i < cchLine; i++, x += font.width) - { - DrawLine(x, startY, x, endY, lineMetrics.gridlineWidth); - } - } - } - - if (lines.any(GridLines::Top, GridLines::Bottom)) - { - const auto halfGridlineWidth = lineMetrics.gridlineWidth / 2.0f; - const auto startX = target.x + halfGridlineWidth; - const auto endX = target.x + fullRunWidth - halfGridlineWidth; - - if (lines.test(GridLines::Top)) - { - const auto y = target.y + halfGridlineWidth; - DrawLine(startX, y, endX, y, lineMetrics.gridlineWidth); - } - - if (lines.test(GridLines::Bottom)) - { - const auto y = target.y + font.height - halfGridlineWidth; - DrawLine(startX, y, endX, y, lineMetrics.gridlineWidth); - } - } - - if (lines.test(GridLines::Strikethrough)) - { - const auto halfStrikethroughWidth = lineMetrics.strikethroughWidth / 2.0f; - const auto startX = target.x + halfStrikethroughWidth; - const auto endX = target.x + fullRunWidth - halfStrikethroughWidth; - const auto y = target.y + lineMetrics.strikethroughOffset; - - DrawLine(startX, y, endX, y, lineMetrics.strikethroughWidth); - } - - _d2dBrushForeground->SetColor(_ColorFFromColorRef(underlineColor | 0xff000000)); - - // In the case of the underline and strikethrough offsets, the stroke width - // is already accounted for, so they don't require further adjustments. - - if (lines.any(GridLines::Underline, GridLines::DoubleUnderline, GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) - { - const auto halfUnderlineWidth = lineMetrics.underlineWidth / 2.0f; - const auto startX = target.x + halfUnderlineWidth; - const auto endX = target.x + fullRunWidth - halfUnderlineWidth; - const auto y = target.y + lineMetrics.underlineOffset; - - if (lines.test(GridLines::Underline)) - { - DrawLine(startX, y, endX, y, lineMetrics.underlineWidth); - } - - if (lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) - { - DrawDottedLine(startX, y, endX, y, lineMetrics.underlineWidth); - } - - if (lines.test(GridLines::DoubleUnderline)) - { - DrawLine(startX, y, endX, y, lineMetrics.underlineWidth); - const auto y2 = target.y + lineMetrics.underlineOffset2; - DrawLine(startX, y2, endX, y2, lineMetrics.underlineWidth); - } - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Paints an overlay highlight on a portion of the frame to represent selected text -// Arguments: -// - rect - Rectangle to invert or highlight to make the selection area -// Return Value: -// - S_OK or relevant DirectX error. -[[nodiscard]] HRESULT DxEngine::PaintSelection(const til::rect& rect) noexcept -try -{ - // If a clip rectangle is in place from drawing the text layer, remove it here. - LOG_IF_FAILED(_customRenderer->EndClip(_drawingContext.get())); - - const auto existingColor = _d2dBrushForeground->GetColor(); - - _d2dBrushForeground->SetColor(_selectionBackground); - const auto resetColorOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); }); - - const D2D1_RECT_F draw = rect.scale_up(_fontRenderData->GlyphCell()).to_d2d_rect(); - - _d2dDeviceContext->FillRectangle(draw, _d2dBrushForeground.Get()); - - return S_OK; -} -CATCH_RETURN() - -[[nodiscard]] HRESULT DxEngine::PaintSelections(const std::vector& rects) noexcept -try -{ - UNREFERENCED_PARAMETER(rects); - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Does nothing. Our cursor is drawn in CustomTextRenderer::DrawGlyphRun, -// either above or below the text. -// Arguments: -// - options - unused -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::PaintCursor(const CursorOptions& /*options*/) noexcept -{ - return S_OK; -} - -// Routine Description: -// - Paint terminal effects. -// Arguments: -// Return Value: -// - S_OK or relevant DirectX error. -[[nodiscard]] HRESULT DxEngine::_PaintTerminalEffects() noexcept -try -{ - // Should have been initialized. - RETURN_HR_IF(E_NOT_VALID_STATE, !_framebufferCapture); - - // Capture current frame in swap chain to a texture. - ::Microsoft::WRL::ComPtr swapBuffer; - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&swapBuffer))); - _d3dDeviceContext->CopyResource(_framebufferCapture.Get(), swapBuffer.Get()); - - // Prepare captured texture as input resource to shader program. - D3D11_TEXTURE2D_DESC desc; - _framebufferCapture->GetDesc(&desc); - - D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; - srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MostDetailedMip = 0; - srvDesc.Texture2D.MipLevels = desc.MipLevels; - srvDesc.Format = desc.Format; - - ::Microsoft::WRL::ComPtr shaderResource; - RETURN_IF_FAILED(_d3dDevice->CreateShaderResourceView(_framebufferCapture.Get(), &srvDesc, &shaderResource)); - - // Render the screen quad with shader effects. - const UINT stride = sizeof(ShaderInput); - const UINT offset = 0; - - _d3dDeviceContext->OMSetRenderTargets(1, _renderTargetView.GetAddressOf(), nullptr); - _d3dDeviceContext->IASetVertexBuffers(0, 1, _screenQuadVertexBuffer.GetAddressOf(), &stride, &offset); - _d3dDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); - _d3dDeviceContext->IASetInputLayout(_vertexLayout.Get()); - _d3dDeviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0); - _d3dDeviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0); - _d3dDeviceContext->PSSetShaderResources(0, 1, shaderResource.GetAddressOf()); - _d3dDeviceContext->PSSetSamplers(0, 1, _samplerState.GetAddressOf()); - _d3dDeviceContext->PSSetConstantBuffers(0, 1, _pixelShaderSettingsBuffer.GetAddressOf()); - _d3dDeviceContext->Draw(ARRAYSIZE(_screenQuadVertices), 0); - - return S_OK; -} -CATCH_RETURN() - -[[nodiscard]] bool DxEngine::_FullRepaintNeeded() const noexcept -{ - // If someone explicitly requested differential rendering off, then we need to invalidate everything - // so the entire frame is repainted. - // - // If terminal effects are on, we must invalidate everything for them to draw correctly. - // Yes, this will further impact the performance of terminal effects. - // But we're talking about running the entire display pipeline through a shader for - // cosmetic effect, so performance isn't likely the top concern with this feature. - return _forceFullRepaintRendering || _HasTerminalEffects(); -} - -// Routine Description: -// - Updates the default brush colors used for drawing -// Arguments: -// - textAttributes - Text attributes to use for the brush color -// - renderSettings - The color table and modes required for rendering -// - pData - The interface to console data structures required for rendering -// - usingSoftFont - Whether we're rendering characters from a soft font -// - isSettingDefaultBrushes - Lets us know that these are the default brushes to paint the swapchain background or selection -// Return Value: -// - S_OK or relevant DirectX error. -[[nodiscard]] HRESULT DxEngine::UpdateDrawingBrushes(const TextAttribute& textAttributes, - const RenderSettings& renderSettings, - const gsl::not_null /*pData*/, - const bool usingSoftFont, - const bool isSettingDefaultBrushes) noexcept -try -{ - const auto [colorForeground, colorBackground] = renderSettings.GetAttributeColorsWithAlpha(textAttributes); - - const auto usingCleartype = _antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; - const auto usingTransparency = _defaultBackgroundIsTransparent; - const auto forceOpaqueBG = usingCleartype && !usingTransparency; - - _foregroundColor = _ColorFFromColorRef(OPACITY_OPAQUE | colorForeground); - // October 2021: small changes were made to the way BG color interacts with - // grayscale AA, esp. with regards to acrylic and GH#5098. See comment in - // _ShouldForceGrayscaleAA for more details. - _backgroundColor = _ColorFFromColorRef((forceOpaqueBG ? OPACITY_OPAQUE : 0) | colorBackground); - - _d2dBrushForeground->SetColor(_foregroundColor); - _d2dBrushBackground->SetColor(_backgroundColor); - - _usingSoftFont = usingSoftFont; - if (_usingSoftFont) - { - _softFont.SetColor(_foregroundColor); - } - - // If this flag is set, then we need to update the default brushes too and the swap chain background. - if (isSettingDefaultBrushes) - { - _defaultForegroundColor = _foregroundColor; - _defaultBackgroundColor = _backgroundColor; - - // If we have a swap chain, set the background color there too so the area - // outside the chain on a resize can be filled in with an appropriate color value. - /*if (_dxgiSwapChain) - { - const auto dxgiColor = s_RgbaFromColorF(_defaultBackgroundColor); - RETURN_IF_FAILED(_dxgiSwapChain->SetBackgroundColor(&dxgiColor)); - }*/ - } - - // If we have a drawing context, it may be choosing its antialiasing based - // on the colors. Update it if it exists. - // Also record whether we need to render the text with an italic font. - // We only need to do this here because this is called all the time on painting frames - // and will update it in a timely fashion. Changing the AA mode or opacity do affect - // it, but we will always hit updating the drawing brushes so we don't - // need to update this in those locations. - if (_drawingContext) - { - _drawingContext->forceGrayscaleAA = _ShouldForceGrayscaleAA(); - _drawingContext->useBoldFont = textAttributes.IsIntense() && renderSettings.GetRenderMode(RenderSettings::Mode::IntenseIsBold); - _drawingContext->useItalicFont = textAttributes.IsItalic(); - } - - // Update pixel shader settings as background color might have changed - _ComputePixelShaderSettings(); - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Updates the font used for drawing -// - This is the version that complies with the IRenderEngine interface -// Arguments: -// - pfiFontInfoDesired - Information specifying the font that is requested -// - fiFontInfo - Filled with the nearest font actually chosen for drawing -// Return Value: -// - S_OK or relevant DirectX error -[[nodiscard]] HRESULT DxEngine::UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo) noexcept -{ - return UpdateFont(pfiFontInfoDesired, fiFontInfo, {}, {}); -} - -// Routine Description: -// - Updates the font used for drawing -// Arguments: -// - pfiFontInfoDesired - Information specifying the font that is requested -// - fiFontInfo - Filled with the nearest font actually chosen for drawing -// - features - The map of font features to use -// - axes - The map of font axes to use -// Return Value: -// - S_OK or relevant DirectX error -[[nodiscard]] HRESULT DxEngine::UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept -try -{ - RETURN_IF_FAILED(_fontRenderData->UpdateFont(pfiFontInfoDesired, fiFontInfo, _dpi, features, axes)); - - // Prepare the text layout. - _customLayout = WRL::Make(_fontRenderData.get()); - - // Inform the soft font of the new cell size so it can scale appropriately. - return _softFont.SetTargetSize(_fontRenderData->GlyphCell()); -} -CATCH_RETURN(); - -[[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) const noexcept -{ - const auto widthInChars = viewInPixels.Width() / _fontRenderData->GlyphCell().width; - const auto heightInChars = viewInPixels.Height() / _fontRenderData->GlyphCell().height; - - return Viewport::FromDimensions(viewInPixels.Origin(), { widthInChars, heightInChars }); -} - -[[nodiscard]] Viewport DxEngine::GetViewportInPixels(const Viewport& viewInCharacters) const noexcept -{ - const auto widthInPixels = viewInCharacters.Width() * _fontRenderData->GlyphCell().width; - const auto heightInPixels = viewInCharacters.Height() * _fontRenderData->GlyphCell().height; - - return Viewport::FromDimensions(viewInCharacters.Origin(), { widthInPixels, heightInPixels }); -} - -// Routine Description: -// - Sets the DPI in this renderer -// Arguments: -// - iDpi - DPI -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::UpdateDpi(const int iDpi) noexcept -{ - _dpi = iDpi; - - // The scale factor may be necessary for composition contexts, so save it once here. - _scale = _dpi / static_cast(USER_DEFAULT_SCREEN_DPI); - - RETURN_IF_FAILED(InvalidateAll()); - - // Update pixel shader settings as scale might have changed - _ComputePixelShaderSettings(); - - return S_OK; -} - -// Method Description: -// - Get the current scale factor of this renderer. The actual DPI the renderer -// is USER_DEFAULT_SCREEN_DPI * GetScaling() -// Arguments: -// - -// Return Value: -// - the scaling multiplier of this render engine -float DxEngine::GetScaling() const noexcept -{ - return _scale; -} - -// Method Description: -// - This method will update our internal reference for how big the viewport is. -// Does nothing for DX. -// Arguments: -// - srNewViewport - The bounds of the new viewport. -// Return Value: -// - HRESULT S_OK -[[nodiscard]] HRESULT DxEngine::UpdateViewport(const til::inclusive_rect& /*srNewViewport*/) noexcept -{ - return S_OK; -} - -// Routine Description: -// - Currently unused by this renderer -// Arguments: -// - pfiFontInfoDesired - -// - pfiFontInfo - -// - iDpi - -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::GetProposedFont(const FontInfoDesired& pfiFontInfoDesired, - FontInfo& pfiFontInfo, - const int iDpi) noexcept -try -{ - DxFontRenderData fontRenderData(_dwriteFactory); - return fontRenderData.UpdateFont(pfiFontInfoDesired, pfiFontInfo, iDpi); -} -CATCH_RETURN(); - -// Routine Description: -// - Gets the area that we currently believe is dirty within the character cell grid -// Arguments: -// - area - Rectangle describing dirty area in characters. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::GetDirtyArea(std::span& area) noexcept -try -{ - area = _invalidMap.runs(); - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Gets the current font size -// Arguments: -// - pFontSize - Filled with the font size. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::GetFontSize(_Out_ til::size* const pFontSize) noexcept -try -{ - *pFontSize = _fontRenderData->GlyphCell(); - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Currently unused by this renderer. -// Arguments: -// - glyph - The glyph run to process for column width. -// - pResult - True if it should take two columns. False if it should take one. -// Return Value: -// - S_OK or relevant DirectWrite error. -[[nodiscard]] HRESULT DxEngine::IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept -try -{ - RETURN_HR_IF_NULL(E_INVALIDARG, pResult); - - const Cluster cluster(glyph, 0); // columns don't matter, we're doing analysis not layout. - - RETURN_IF_FAILED(_customLayout->Reset()); - RETURN_IF_FAILED(_customLayout->AppendClusters({ &cluster, 1 })); - - UINT32 columns = 0; - RETURN_IF_FAILED(_customLayout->GetColumns(&columns)); - - *pResult = columns != 1; - - return S_OK; -} -CATCH_RETURN(); - -// Method Description: -// - Updates the window's title string. -// Arguments: -// - newTitle: the new string to use for the title of the window -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::_DoUpdateTitle(_In_ const std::wstring_view /*newTitle*/) noexcept -{ - if (_hwndTarget != INVALID_HANDLE_VALUE) - { - return PostMessageW(_hwndTarget, CM_UPDATE_TITLE, 0, 0) ? S_OK : E_FAIL; - } - return S_FALSE; -} - -// Routine Description: -// - Helps convert a GDI COLORREF into a Direct2D ColorF -// Arguments: -// - color - GDI color -// Return Value: -// - D2D color -[[nodiscard]] D2D1_COLOR_F DxEngine::_ColorFFromColorRef(const COLORREF color) noexcept -{ - // Converts BGR color order to RGB. - const UINT32 rgb = ((color & 0x0000FF) << 16) | (color & 0x00FF00) | ((color & 0xFF0000) >> 16); - - switch (_chainMode) - { - case SwapChainMode::ForHwnd: - { - return D2D1::ColorF(rgb); - } - case SwapChainMode::ForComposition: - { - // Get the A value we've snuck into the highest byte - const BYTE a = ((color >> 24) & 0xFF); - const auto aFloat = a / 255.0f; - - return D2D1::ColorF(rgb, aFloat); - } - default: - FAIL_FAST_HR(E_NOTIMPL); - } -} - -// Routine Description: -// - Updates the selection background color of the DxEngine -// Arguments: -// - color - GDI Color -// Return Value: -// - N/A -void DxEngine::SetSelectionBackground(const COLORREF color, const float alpha) noexcept -{ - _selectionBackground = D2D1::ColorF(GetRValue(color) / 255.0f, - GetGValue(color) / 255.0f, - GetBValue(color) / 255.0f, - alpha); -} - -// Routine Description: -// - Changes the antialiasing mode of the renderer. This must be called before -// _PrepareRenderTarget, otherwise the renderer will default to -// D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE. -// Arguments: -// - antialiasingMode: a value from the D2D1_TEXT_ANTIALIAS_MODE enum. See: -// https://docs.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_text_antialias_mode -// Return Value: -// - N/A -void DxEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept -try -{ - if (_antialiasingMode != antialiasingMode) - { - _antialiasingMode = antialiasingMode; - _recreateDeviceRequested = true; - LOG_IF_FAILED(_softFont.SetAntialiasing(antialiasingMode != D2D1_TEXT_ANTIALIAS_MODE_ALIASED)); - LOG_IF_FAILED(InvalidateAll()); - } -} -CATCH_LOG() - -// Method Description: -// - Update our tracker of the opacity of our background. We can only -// effectively render cleartype text onto fully-opaque backgrounds. If we're -// rendering onto a transparent surface (like acrylic), then cleartype won't -// work correctly, and will actually just additively blend with the -// background. This is here to support GH#5098. -// - We'll use this, along with whether cleartype was requested, to manually set -// the alpha channel of the background brush to 1.0. We need to do that to -// make cleartype work without blending. However, we don't want to do that too -// often - if we do that on top of a transparent BG, then the entire swap -// chain will be fully opaque. -// Arguments: -// - isTransparent: true if our BG is transparent (acrylic, or anything that's not fully opaque) -// Return Value: -// - -void DxEngine::EnableTransparentBackground(const bool isTransparent) noexcept -try -{ - _defaultBackgroundIsTransparent = isTransparent; - - // Make sure we redraw all the cells, to update whether they're actually - // drawn with cleartype or not. - // We don't terribly care if this fails. - LOG_IF_FAILED(InvalidateAll()); -} -CATCH_LOG() - -// Method Description: -// - Updates our internal tracker for which hyperlink ID we are hovering over -// This is needed for UpdateDrawingBrushes to know where we need to set a different style -// Arguments: -// - The new link ID we are hovering over -void DxEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept -{ - _hyperlinkHoveredId = hoveredId; -} - -// Routine Description: -// - This method will replace the active soft font with the given bit pattern. -// Arguments: -// - bitPattern - An array of scanlines representing all the glyphs in the font. -// - cellSize - The cell size for an individual glyph. -// - centeringHint - The horizontal extent that glyphs are offset from center. -// Return Value: -// - S_OK if successful. E_FAIL if there was an error. -HRESULT DxEngine::UpdateSoftFont(const std::span bitPattern, - const til::size cellSize, - const size_t centeringHint) noexcept -try -{ - _softFont.SetFont(bitPattern, cellSize, _fontRenderData->GlyphCell(), centeringHint); - return S_OK; -} -CATCH_RETURN(); - -// Method Description: -// - Informs this render engine about certain state for this frame at the -// beginning of this frame. We'll use it to get information about the cursor -// before PaintCursor is called. This enables the DX renderer to draw the -// cursor underneath the text. -// - This is called every frame. When the cursor is Off or out of frame, the -// info's cursorInfo will be set to std::nullopt; -// Arguments: -// - info - a RenderFrameInfo with information about the state of the cursor in this frame. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::PrepareRenderInfo(const RenderFrameInfo& info) noexcept -{ - _drawingContext->cursorInfo = info.cursorInfo; - return S_OK; -} - -// Routine Description -// - Resets the world transform to the identity matrix. -// Arguments: -// - -// Return Value: -// - S_OK if successful. S_FALSE if already reset. E_FAIL if there was an error. -[[nodiscard]] HRESULT DxEngine::ResetLineTransform() noexcept -{ - // Return early if the current transform is already the identity matrix. - RETURN_HR_IF(S_FALSE, _currentLineTransform.IsIdentity()); - // Reset the active transform to the identity matrix. - _drawingContext->renderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); - // Reset the clipping offsets. - _drawingContext->topClipOffset = 0; - _drawingContext->bottomClipOffset = 0; - // Reset the current state. - _currentLineTransform = D2D1::Matrix3x2F::Identity(); - _currentLineRendition = LineRendition::SingleWidth; - return S_OK; -} - -// Routine Description -// - Applies an appropriate transform for the given line rendition and viewport offset. -// Arguments: -// - lineRendition - The line rendition specifying the scaling of the line. -// - targetRow - The row on which the line is expected to be rendered. -// - viewportLeft - The left offset of the current viewport. -// Return Value: -// - S_OK if successful. S_FALSE if already set. E_FAIL if there was an error. -[[nodiscard]] HRESULT DxEngine::PrepareLineTransform(const LineRendition lineRendition, - const til::CoordType targetRow, - const til::CoordType viewportLeft) noexcept -{ - auto lineTransform = D2D1::Matrix3x2F{ 0, 0, 0, 0, 0, 0 }; - const auto fontSize = _fontRenderData->GlyphCell(); - // The X delta is to account for the horizontal viewport offset. - lineTransform.dx = viewportLeft ? -1.0f * viewportLeft * fontSize.width : 0.0f; - switch (lineRendition) - { - case LineRendition::SingleWidth: - lineTransform.m11 = 1; // single width - lineTransform.m22 = 1; // single height - break; - case LineRendition::DoubleWidth: - lineTransform.m11 = 2; // double width - lineTransform.m22 = 1; // single height - break; - case LineRendition::DoubleHeightTop: - lineTransform.m11 = 2; // double width - lineTransform.m22 = 2; // double height - // The Y delta is to negate the offset caused by the scaled height. - lineTransform.dy = -1.0f * targetRow * fontSize.height; - break; - case LineRendition::DoubleHeightBottom: - lineTransform.m11 = 2; // double width - lineTransform.m22 = 2; // double height - // The Y delta is to negate the offset caused by the scaled height. - // An extra row is added because we need the bottom half of the line. - lineTransform.dy = -1.0f * (targetRow + 1) * fontSize.height; - break; - } - // Return early if the new matrix is the same as the current transform. - RETURN_HR_IF(S_FALSE, _currentLineRendition == lineRendition && _currentLineTransform == lineTransform); - // Set the active transform with the new matrix. - _drawingContext->renderTarget->SetTransform(lineTransform); - // If the line rendition is double height, we need to adjust the top or bottom - // of the clipping rect to clip half the height of the rendered characters. - const auto halfHeight = _drawingContext->cellSize.height / 2; - _drawingContext->topClipOffset = lineRendition == LineRendition::DoubleHeightBottom ? halfHeight : 0; - _drawingContext->bottomClipOffset = lineRendition == LineRendition::DoubleHeightTop ? halfHeight : 0; - // Save the current state. - _currentLineTransform = lineTransform; - _currentLineRendition = lineRendition; - return S_OK; -} diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp deleted file mode 100644 index 990c77e18e5..00000000000 --- a/src/renderer/dx/DxRenderer.hpp +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "../../renderer/inc/RenderEngineBase.hpp" - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "CustomTextLayout.h" -#include "CustomTextRenderer.h" -#include "DxFontRenderData.h" -#include "DxSoftFont.h" - -#include "../../types/inc/Viewport.hpp" - -#include - -TRACELOGGING_DECLARE_PROVIDER(g_hDxRenderProvider); - -namespace Microsoft::Console::Render -{ - class DxEngine final : public RenderEngineBase - { - public: - DxEngine(); - ~DxEngine(); - DxEngine(const DxEngine&) = default; - DxEngine(DxEngine&&) = default; - DxEngine& operator=(const DxEngine&) = default; - DxEngine& operator=(DxEngine&&) = default; - - // 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 override; - [[nodiscard]] HRESULT Disable() noexcept; - - [[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept override; - - [[nodiscard]] HRESULT SetWindowSize(const til::size pixels) noexcept override; - - void SetCallback(std::function pfn) noexcept override; - void SetWarningCallback(std::function pfn) noexcept override; - - bool GetRetroTerminalEffect() const noexcept override; - void SetRetroTerminalEffect(bool enable) noexcept override; - - std::wstring_view GetPixelShaderPath() noexcept override; - void SetPixelShaderPath(std::wstring_view value) noexcept override; - - void SetForceFullRepaintRendering(bool enable) noexcept override; - - void SetSoftwareRendering(bool enable) noexcept override; - - // IRenderEngine Members - [[nodiscard]] HRESULT Invalidate(const til::rect* const psrRegion) noexcept override; - [[nodiscard]] HRESULT InvalidateCursor(const til::rect* const psrRegion) noexcept override; - [[nodiscard]] HRESULT InvalidateSystem(const til::rect* const prcDirtyClient) noexcept override; - [[nodiscard]] HRESULT InvalidateSelection(const std::vector& rectangles) noexcept override; - [[nodiscard]] HRESULT InvalidateScroll(const til::point* const pcoordDelta) noexcept override; - [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override; - - [[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 ScrollFrame() noexcept override; - - [[nodiscard]] HRESULT UpdateSoftFont(const std::span bitPattern, - const til::size cellSize, - const size_t centeringHint) noexcept override; - - [[nodiscard]] HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept override; - - [[nodiscard]] HRESULT ResetLineTransform() noexcept override; - [[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, - const til::CoordType targetRow, - const til::CoordType viewportLeft) noexcept override; - - [[nodiscard]] HRESULT PaintBackground() noexcept override; - [[nodiscard]] HRESULT PaintBufferLine(const std::span clusters, - const til::point coord, - const bool fTrimLeft, - const bool lineWrapped) noexcept override; - - [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; - [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; - [[nodiscard]] HRESULT PaintSelections(const std::vector& rect) noexcept override; - - [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; - - [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, - const RenderSettings& renderSettings, - const gsl::not_null pData, - 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& features, const std::unordered_map& axes) noexcept override; - [[nodiscard]] HRESULT UpdateDpi(const int iDpi) noexcept override; - [[nodiscard]] HRESULT UpdateViewport(const til::inclusive_rect& srNewViewport) noexcept override; - - [[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, const int iDpi) noexcept override; - - [[nodiscard]] HRESULT GetDirtyArea(std::span& area) noexcept override; - - [[nodiscard]] HRESULT GetFontSize(_Out_ til::size* 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) const noexcept override; - [[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInPixels(const ::Microsoft::Console::Types::Viewport& viewInCharacters) const noexcept override; - - float GetScaling() const noexcept override; - - void SetSelectionBackground(const COLORREF color, const float alpha = 0.5f) noexcept override; - void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept override; - void EnableTransparentBackground(const bool isTransparent) noexcept override; - - void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept override; - - protected: - [[nodiscard]] HRESULT _DoUpdateTitle(_In_ const std::wstring_view newTitle) noexcept override; - [[nodiscard]] HRESULT _PaintTerminalEffects() noexcept; - [[nodiscard]] bool _FullRepaintNeeded() const noexcept; - - private: - enum class SwapChainMode - { - ForHwnd, - ForComposition - }; - - SwapChainMode _chainMode; - - HWND _hwndTarget; - til::size _sizeTarget; - int _dpi; - float _scale; - float _prevScale; - - std::function _pfn; - std::function _pfnWarningCallback; - - bool _isEnabled; - bool _isPainting; - - til::size _displaySizePixels; - - D2D1_COLOR_F _defaultForegroundColor; - D2D1_COLOR_F _defaultBackgroundColor; - - D2D1_COLOR_F _foregroundColor; - D2D1_COLOR_F _backgroundColor; - D2D1_COLOR_F _selectionBackground; - - LineRendition _currentLineRendition; - D2D1::Matrix3x2F _currentLineTransform; - - uint16_t _hyperlinkHoveredId; - - bool _firstFrame; - std::pmr::unsynchronized_pool_resource _pool; - til::pmr::bitmap _invalidMap; - til::point _invalidScroll; - bool _allInvalid; - - bool _presentReady; - std::vector _presentDirty; - RECT _presentScroll; - POINT _presentOffset; - DXGI_PRESENT_PARAMETERS _presentParams; - - static std::atomic _tracelogCount; - - wil::unique_handle _swapChainHandle; - - // Device-Independent Resources - ::Microsoft::WRL::ComPtr _d2dFactory; - - ::Microsoft::WRL::ComPtr _dwriteFactory; - ::Microsoft::WRL::ComPtr _customLayout; - ::Microsoft::WRL::ComPtr _customRenderer; - ::Microsoft::WRL::ComPtr _strokeStyle; - ::Microsoft::WRL::ComPtr _dashStrokeStyle; - - std::unique_ptr _fontRenderData; - DxSoftFont _softFont; - bool _usingSoftFont; - - D2D1_STROKE_STYLE_PROPERTIES _strokeStyleProperties; - D2D1_STROKE_STYLE_PROPERTIES _dashStrokeStyleProperties; - - // Device-Dependent Resources - bool _recreateDeviceRequested; - bool _haveDeviceResources; - ::Microsoft::WRL::ComPtr _d3dDevice; - ::Microsoft::WRL::ComPtr _d3dDeviceContext; - - ::Microsoft::WRL::ComPtr _d2dDevice; - ::Microsoft::WRL::ComPtr _d2dDeviceContext; - ::Microsoft::WRL::ComPtr _d2dBitmap; - ::Microsoft::WRL::ComPtr _d2dBrushForeground; - ::Microsoft::WRL::ComPtr _d2dBrushBackground; - - ::Microsoft::WRL::ComPtr _dxgiFactory2; - ::Microsoft::WRL::ComPtr _dxgiFactoryMedia; - ::Microsoft::WRL::ComPtr _dxgiDevice; - ::Microsoft::WRL::ComPtr _dxgiSurface; - - DXGI_SWAP_CHAIN_DESC1 _swapChainDesc; - ::Microsoft::WRL::ComPtr _dxgiSwapChain; - wil::unique_handle _swapChainFrameLatencyWaitableObject; - std::unique_ptr _drawingContext; - - // Terminal effects resources. - - // Controls if configured terminal effects are enabled - bool _terminalEffectsEnabled; - - // Experimental and deprecated retro terminal effect - // Preserved for backwards compatibility - // Implemented in terms of the more generic pixel shader effect - // Has precedence over pixel shader effect - bool _retroTerminalEffect; - - // Experimental and pixel shader effect - // Allows user to load a pixel shader from a few presets or from a file path - std::wstring _pixelShaderPath; - bool _pixelShaderLoaded{ false }; - - std::chrono::steady_clock::time_point _shaderStartTime; - - // DX resources needed for terminal effects - ::Microsoft::WRL::ComPtr _renderTargetView; - ::Microsoft::WRL::ComPtr _vertexShader; - ::Microsoft::WRL::ComPtr _pixelShader; - ::Microsoft::WRL::ComPtr _vertexLayout; - ::Microsoft::WRL::ComPtr _screenQuadVertexBuffer; - ::Microsoft::WRL::ComPtr _pixelShaderSettingsBuffer; - ::Microsoft::WRL::ComPtr _samplerState; - ::Microsoft::WRL::ComPtr _framebufferCapture; - - // Preferences and overrides - bool _softwareRendering; - bool _forceFullRepaintRendering; - - D2D1_TEXT_ANTIALIAS_MODE _antialiasingMode; - - bool _defaultBackgroundIsTransparent; - - // DirectX constant buffers need to be a multiple of 16; align to pad the size. - __declspec(align(16)) struct - { - // Note: This can be seen as API endpoint towards user provided pixel shaders. - // Changes here can break existing pixel shaders so be careful with changing datatypes - // and order of parameters - float Time; - float Scale; - DirectX::XMFLOAT2 Resolution; - DirectX::XMFLOAT4 Background; -#pragma warning(suppress : 4324) // structure was padded due to __declspec(align()) - } _pixelShaderSettings; - - [[nodiscard]] HRESULT _CreateDeviceResources(const bool createSwapChain) noexcept; - [[nodiscard]] HRESULT _CreateSurfaceHandle() noexcept; - - bool _HasTerminalEffects() const noexcept; - std::string _LoadPixelShaderFile() const; - HRESULT _SetupTerminalEffects(); - void _ComputePixelShaderSettings() noexcept; - - [[nodiscard]] HRESULT _PrepareRenderTarget() noexcept; - - void _ReleaseDeviceResources() noexcept; - - bool _ShouldForceGrayscaleAA() noexcept; - - [[nodiscard]] HRESULT _CreateTextLayout( - _In_reads_(StringLength) PCWCHAR String, - _In_ size_t StringLength, - _Out_ IDWriteTextLayout** ppTextLayout) noexcept; - - [[nodiscard]] HRESULT _CopyFrontToBack() noexcept; - - [[nodiscard]] HRESULT _EnableDisplayAccess(const bool outputEnabled) noexcept; - - [[nodiscard]] til::size _GetClientSize() const; - - void _InvalidateRectangle(const til::rect& rc); - bool _IsAllInvalid() const noexcept; - - [[nodiscard]] D2D1_COLOR_F _ColorFFromColorRef(const COLORREF color) noexcept; - - // Routine Description: - // - Helps convert a Direct2D ColorF into a DXGI RGBA - // Arguments: - // - color - Direct2D Color F - // Return Value: - // - DXGI RGBA - [[nodiscard]] constexpr DXGI_RGBA s_RgbaFromColorF(const D2D1_COLOR_F color) noexcept - { - return { color.r, color.g, color.b, color.a }; - } - }; -} diff --git a/src/renderer/dx/DxSoftFont.cpp b/src/renderer/dx/DxSoftFont.cpp deleted file mode 100644 index 2bfa932d878..00000000000 --- a/src/renderer/dx/DxSoftFont.cpp +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "DxSoftFont.h" -#include "CustomTextRenderer.h" - -#include - -#pragma comment(lib, "dxguid.lib") - -using namespace Microsoft::Console::Render; -using Microsoft::WRL::ComPtr; - -// The soft font is rendered into a bitmap laid out in a 12x8 grid, which is -// enough space for the 96 characters expected in the font, and which minimizes -// the dimensions for a typical 2:1 cell size. Each position in the grid is -// surrounded by a 2 pixel border which helps avoid bleed across the character -// boundaries when the output is scaled. - -constexpr size_t BITMAP_GRID_WIDTH = 12; -constexpr size_t BITMAP_GRID_HEIGHT = 8; -constexpr size_t PADDING = 2; - -void DxSoftFont::SetFont(const std::span bitPattern, - const til::size sourceSize, - const til::size targetSize, - const size_t centeringHint) -{ - Reset(); - - // If the font is being reset, just free up the memory and return. - if (bitPattern.empty()) - { - _bitmapBits.clear(); - return; - } - - const auto maxGlyphCount = BITMAP_GRID_WIDTH * BITMAP_GRID_HEIGHT; - _glyphCount = std::min(bitPattern.size() / sourceSize.height, maxGlyphCount); - _sourceSize = sourceSize; - _targetSize = targetSize; - _centeringHint = centeringHint; - - const auto bitmapWidth = BITMAP_GRID_WIDTH * (_sourceSize.width + PADDING * 2); - const auto bitmapHeight = BITMAP_GRID_HEIGHT * (_sourceSize.height + PADDING * 2); - _bitmapBits = std::vector(bitmapWidth * bitmapHeight); - _bitmapSize = { gsl::narrow_cast(bitmapWidth), gsl::narrow_cast(bitmapHeight) }; - - const auto bitmapScanline = [=](const auto lineNumber) noexcept { - return _bitmapBits.begin() + lineNumber * bitmapWidth; - }; - - // The source bitPattern is just a list of the scanlines making up the - // glyphs one after the other, but we want to lay them out in a grid, so - // we need to process each glyph individually. - auto srcPointer = bitPattern.begin(); - for (auto glyphNumber = 0u; glyphNumber < _glyphCount; glyphNumber++) - { - // We start by calculating the position in the bitmap where the glyph - // needs to be stored. - const auto xOffset = _xOffsetForGlyph(glyphNumber); - const auto yOffset = _yOffsetForGlyph(glyphNumber); - auto dstPointer = bitmapScanline(yOffset) + xOffset; - for (auto y = 0; y < sourceSize.height; y++) - { - // Then for each scanline in the source, we need to expand the bits - // into 8-bit values. For every bit that is set we write out an FF - // value, and if not set, we write out 00. In the end, all we care - // about is a single red component for the R8_UNORM bitmap format, - // since we'll later remap that to RGBA with a color matrix. - auto srcBits = *(srcPointer++); - for (auto x = 0; x < sourceSize.width; x++) - { - const auto srcBitIsSet = (srcBits & 0x8000) != 0; - *(dstPointer++) = srcBitIsSet ? 0xFF : 0x00; - srcBits <<= 1; - } - // When glyphs in this bitmap are output, they will typically need - // to scaled, and this can result in some bleed from the surrounding - // pixels. So to keep the borders clean, we pad the areas to the left - // and right by repeating the first and last pixels of each scanline. - std::fill_n(dstPointer, PADDING, til::at(dstPointer, -1)); - dstPointer -= sourceSize.width; - std::fill_n(dstPointer - PADDING, PADDING, til::at(dstPointer, 0)); - dstPointer += bitmapWidth; - } - } - - // In the same way that we padded the left and right of each glyph in the - // code above, we also need to pad the top and bottom. But in this case we - // can simply do a whole row of glyphs from the grid at the same time. - for (auto gridRow = 0u; gridRow < BITMAP_GRID_HEIGHT; gridRow++) - { - const auto rowOffset = _yOffsetForGlyph(gridRow); - const auto rowTop = bitmapScanline(rowOffset); - const auto rowBottom = bitmapScanline(rowOffset + _sourceSize.height - 1); - for (auto i = 1; i <= PADDING; i++) - { - std::copy_n(rowTop, bitmapWidth, rowTop - i * bitmapWidth); - std::copy_n(rowBottom, bitmapWidth, rowBottom + i * bitmapWidth); - } - } -} - -HRESULT DxSoftFont::SetTargetSize(const til::size targetSize) -{ - _targetSize = targetSize; - return _scaleEffect ? _scaleEffect->SetValue(D2D1_SCALE_PROP_SCALE, _scaleForTargetSize()) : S_OK; -} - -HRESULT DxSoftFont::SetAntialiasing(const bool antialiased) -{ - _interpolation = (antialiased ? ANTIALIASED_INTERPOLATION : ALIASED_INTERPOLATION); - return _scaleEffect ? _scaleEffect->SetValue(D2D1_SCALE_PROP_INTERPOLATION_MODE, _interpolation) : S_OK; -} - -HRESULT DxSoftFont::SetColor(const D2D1_COLOR_F& color) -{ - // Since our source image is monochrome, we don't care about the - // individual color components. We just multiply the red component - // by the active color value to get the output color. And note that - // the alpha matrix entry is already set to 1 in the constructor, - // so we don't need to keep updating it here. - _colorMatrix.m[0][0] = color.r; - _colorMatrix.m[0][1] = color.g; - _colorMatrix.m[0][2] = color.b; - return _colorEffect ? _colorEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, _colorMatrix) : S_OK; -} - -HRESULT DxSoftFont::Draw(const DrawingContext& drawingContext, - const std::span clusters, - const float originX, - const float originY) -{ - ComPtr d2dContext; - RETURN_IF_FAILED(drawingContext.renderTarget->QueryInterface(d2dContext.GetAddressOf())); - - // We start by creating a clipping rectangle for the region we're going to - // draw, and this is initially filled with the active background color. - D2D1_RECT_F rect; - rect.top = originY + drawingContext.topClipOffset; - rect.bottom = originY + _targetSize.height - drawingContext.bottomClipOffset; - rect.left = originX; - rect.right = originX + _targetSize.width * clusters.size(); - d2dContext->FillRectangle(rect, drawingContext.backgroundBrush); - d2dContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED); - auto resetClippingRect = wil::scope_exit([&]() noexcept { d2dContext->PopAxisAlignedClip(); }); - - // The bitmap and associated scaling/coloring effects are created on demand - // so we need make sure they're generated now. - RETURN_IF_FAILED(_createResources(d2dContext.Get())); - - // We use the CustomTextRenderer to draw the first pass of the cursor. - RETURN_IF_FAILED(CustomTextRenderer::DrawCursor(d2dContext.Get(), rect, drawingContext, true)); - - // Then we draw the associated glyph for each entry in the cluster list. - auto targetPoint = D2D1_POINT_2F{ originX, originY }; - for (auto& cluster : clusters) - { - // For DRCS, we only care about the character's lower 7 bits, then - // codepoint 0x20 will be the first glyph in the set. - const auto glyphNumber = (cluster.GetTextAsSingle() & 0x7f) - 0x20; - const auto x = _xOffsetForGlyph(glyphNumber); - const auto y = _yOffsetForGlyph(glyphNumber); - const auto sourceRect = D2D1_RECT_F{ x, y, x + _targetSize.width, y + _targetSize.height }; - LOG_IF_FAILED(_scaleEffect->SetValue(D2D1_SCALE_PROP_CENTER_POINT, D2D1::Point2F(x, y))); - d2dContext->DrawImage(_colorEffect.Get(), targetPoint, sourceRect); - targetPoint.x += _targetSize.width; - } - - // We finish by the drawing the second pass of the cursor. - return CustomTextRenderer::DrawCursor(d2dContext.Get(), rect, drawingContext, false); -} - -void DxSoftFont::Reset() -{ - _colorEffect.Reset(); - _scaleEffect.Reset(); - _bitmap.Reset(); -} - -HRESULT DxSoftFont::_createResources(gsl::not_null d2dContext) -{ - if (!_bitmap) - { - D2D1_BITMAP_PROPERTIES bitmapProperties{}; - bitmapProperties.pixelFormat.format = DXGI_FORMAT_R8_UNORM; - bitmapProperties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE; - const auto bitmapPitch = gsl::narrow_cast(_bitmapSize.width); - RETURN_IF_FAILED(d2dContext->CreateBitmap(_bitmapSize, _bitmapBits.data(), bitmapPitch, bitmapProperties, _bitmap.GetAddressOf())); - } - - if (!_scaleEffect) - { - RETURN_IF_FAILED(d2dContext->CreateEffect(CLSID_D2D1Scale, _scaleEffect.GetAddressOf())); - RETURN_IF_FAILED(_scaleEffect->SetValue(D2D1_SCALE_PROP_INTERPOLATION_MODE, _interpolation)); - RETURN_IF_FAILED(_scaleEffect->SetValue(D2D1_SCALE_PROP_SCALE, _scaleForTargetSize())); - _scaleEffect->SetInput(0, _bitmap.Get()); - - if (_colorEffect) - { - _colorEffect->SetInputEffect(0, _scaleEffect.Get()); - } - } - - if (!_colorEffect) - { - RETURN_IF_FAILED(d2dContext->CreateEffect(CLSID_D2D1ColorMatrix, _colorEffect.GetAddressOf())); - RETURN_IF_FAILED(_colorEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, _colorMatrix)); - _colorEffect->SetInputEffect(0, _scaleEffect.Get()); - } - - return S_OK; -} - -D2D1_VECTOR_2F DxSoftFont::_scaleForTargetSize() const noexcept -{ - // If the text in the font is not perfectly centered, the _centeringHint - // gives us the offset needed to correct that misalignment. So to ensure - // the scaling is evenly balanced around the center point of the glyphs, - // we can use that hint to adjust the dimensions of our source and target - // widths when calculating the horizontal scale. - const auto targetCenteringHint = std::lround((float)_centeringHint * _targetSize.width / _sourceSize.width); - const auto xScale = gsl::narrow_cast(_targetSize.width - targetCenteringHint) / (_sourceSize.width - _centeringHint); - const auto yScale = gsl::narrow_cast(_targetSize.height) / _sourceSize.height; - return D2D1::Vector2F(xScale, yScale); -} - -template -T DxSoftFont::_xOffsetForGlyph(const size_t glyphNumber) const noexcept -{ - const auto xOffsetInGrid = glyphNumber / BITMAP_GRID_HEIGHT; - const auto paddedGlyphWidth = _sourceSize.width + PADDING * 2; - return gsl::narrow_cast(xOffsetInGrid * paddedGlyphWidth + PADDING); -} - -template -T DxSoftFont::_yOffsetForGlyph(const size_t glyphNumber) const noexcept -{ - const auto yOffsetInGrid = glyphNumber % BITMAP_GRID_HEIGHT; - const auto paddedGlyphHeight = _sourceSize.height + PADDING * 2; - return gsl::narrow_cast(yOffsetInGrid * paddedGlyphHeight + PADDING); -} diff --git a/src/renderer/dx/DxSoftFont.h b/src/renderer/dx/DxSoftFont.h deleted file mode 100644 index 5dd83b3f4d2..00000000000 --- a/src/renderer/dx/DxSoftFont.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "../inc/Cluster.hpp" - -#include -#include -#include -#include - -namespace Microsoft::Console::Render -{ - struct DrawingContext; - - class DxSoftFont - { - public: - void SetFont(const std::span bitPattern, - const til::size sourceSize, - const til::size targetSize, - const size_t centeringHint); - HRESULT SetTargetSize(const til::size targetSize); - HRESULT SetAntialiasing(const bool antialiased); - HRESULT SetColor(const D2D1_COLOR_F& color); - HRESULT Draw(const DrawingContext& drawingContext, - const std::span clusters, - const float originX, - const float originY); - void Reset(); - - private: - static constexpr auto ANTIALIASED_INTERPOLATION = D2D1_SCALE_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC; - static constexpr auto ALIASED_INTERPOLATION = D2D1_SCALE_INTERPOLATION_MODE_NEAREST_NEIGHBOR; - - HRESULT _createResources(gsl::not_null d2dContext); - D2D1_VECTOR_2F _scaleForTargetSize() const noexcept; - template - T _xOffsetForGlyph(const size_t glyphNumber) const noexcept; - template - T _yOffsetForGlyph(const size_t glyphNumber) const noexcept; - - size_t _glyphCount = 0; - til::size _sourceSize; - til::size _targetSize; - size_t _centeringHint = 0; - D2D1_SCALE_INTERPOLATION_MODE _interpolation = ALIASED_INTERPOLATION; - D2D1_MATRIX_5X4_F _colorMatrix{ ._14 = 1 }; - D2D1_SIZE_U _bitmapSize{}; - std::vector _bitmapBits; - ::Microsoft::WRL::ComPtr _bitmap; - ::Microsoft::WRL::ComPtr _scaleEffect; - ::Microsoft::WRL::ComPtr _colorEffect; - }; -} diff --git a/src/renderer/dx/IBoxDrawingEffect.idl b/src/renderer/dx/IBoxDrawingEffect.idl deleted file mode 100644 index 14443a05442..00000000000 --- a/src/renderer/dx/IBoxDrawingEffect.idl +++ /dev/null @@ -1,20 +0,0 @@ -import "oaidl.idl"; -import "ocidl.idl"; - -typedef struct BoxScale -{ - float VerticalScale; - float VerticalTranslation; - float HorizontalScale; - float HorizontalTranslation; -} BoxScale; - -[ - uuid("C164926F-1A4D-470D-BB8A-3D2CC4B035E4"), - object, - local -] -interface IBoxDrawingEffect : IUnknown -{ - HRESULT GetScale([out] BoxScale* scale); -}; diff --git a/src/renderer/dx/ScreenPixelShader.h b/src/renderer/dx/ScreenPixelShader.h deleted file mode 100644 index fa532205740..00000000000 --- a/src/renderer/dx/ScreenPixelShader.h +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -#if !TIL_FEATURE_DXENGINESHADERSUPPORT_ENABLED -constexpr std::string_view retroPixelShaderString{ "" }; -#else -constexpr std::string_view retroPixelShaderString{ R"( -// The original retro pixel shader -Texture2D shaderTexture; -SamplerState samplerState; - -cbuffer PixelShaderSettings { - float Time; - float Scale; - float2 Resolution; - float4 Background; -}; - -#define SCANLINE_FACTOR 0.5 -#define SCALED_SCANLINE_PERIOD Scale -#define SCALED_GAUSSIAN_SIGMA (2.0*Scale) - -static const float M_PI = 3.14159265f; - -float Gaussian2D(float x, float y, float sigma) -{ - return 1/(sigma*sqrt(2*M_PI)) * exp(-0.5*(x*x + y*y)/sigma/sigma); -} - -float4 Blur(Texture2D input, float2 tex_coord, float sigma) -{ - uint width, height; - shaderTexture.GetDimensions(width, height); - - float texelWidth = 1.0f/width; - float texelHeight = 1.0f/height; - - float4 color = { 0, 0, 0, 0 }; - - int sampleCount = 13; - - for (int x = 0; x < sampleCount; x++) - { - float2 samplePos = { 0, 0 }; - - samplePos.x = tex_coord.x + (x - sampleCount/2) * texelWidth; - for (int y = 0; y < sampleCount; y++) - { - samplePos.y = tex_coord.y + (y - sampleCount/2) * texelHeight; - if (samplePos.x <= 0 || samplePos.y <= 0 || samplePos.x >= width || samplePos.y >= height) continue; - - color += input.Sample(samplerState, samplePos) * Gaussian2D((x - sampleCount/2), (y - sampleCount/2), sigma); - } - } - - return color; -} - -float SquareWave(float y) -{ - return 1 - (floor(y / SCALED_SCANLINE_PERIOD) % 2) * SCANLINE_FACTOR; -} - -float4 Scanline(float4 color, float4 pos) -{ - float wave = SquareWave(pos.y); - - // TODO:GH#3929 make this configurable. - // Remove the && false to draw scanlines everywhere. - if (length(color.rgb) < 0.2 && false) - { - return color + wave*0.1; - } - else - { - return color * wave; - } -} - -float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET -{ - Texture2D input = shaderTexture; - - // TODO:GH#3930 Make these configurable in some way. - float4 color = input.Sample(samplerState, tex); - color += Blur(input, tex, SCALED_GAUSSIAN_SIGMA)*0.3; - color = Scanline(color, pos); - - return color; -} -)" }; -#endif diff --git a/src/renderer/dx/ScreenVertexShader.h b/src/renderer/dx/ScreenVertexShader.h deleted file mode 100644 index c0a4c8eaf6a..00000000000 --- a/src/renderer/dx/ScreenVertexShader.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#if !TIL_FEATURE_DXENGINESHADERSUPPORT_ENABLED -const char screenVertexShaderString[] = ""; -#else -const char screenVertexShaderString[] = R"( -struct VS_OUTPUT -{ - float4 pos : SV_POSITION; - float2 tex : TEXCOORD; -}; -VS_OUTPUT main(float4 pos : POSITION, float2 tex : TEXCOORD) -{ - VS_OUTPUT output; - output.pos = pos; - output.tex = tex; - return output; -} -)"; -#endif diff --git a/src/renderer/dx/dirs b/src/renderer/dx/dirs deleted file mode 100644 index 3cff781c8ae..00000000000 --- a/src/renderer/dx/dirs +++ /dev/null @@ -1,2 +0,0 @@ -DIRS= \ - lib \ diff --git a/src/renderer/dx/lib/dx.vcxproj b/src/renderer/dx/lib/dx.vcxproj deleted file mode 100644 index facf5a3cb5c..00000000000 --- a/src/renderer/dx/lib/dx.vcxproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - {48D21369-3D7B-4431-9967-24E81292CF62} - Win32Proj - dx - RendererDx - ConRenderDx - StaticLibrary - - - - $(SolutionDir)src\renderer\dx\ - - - - - - - - - - - - - - Create - - - - - - - - - - - - - - - - - - - - - diff --git a/src/renderer/dx/lib/dx.vcxproj.filters b/src/renderer/dx/lib/dx.vcxproj.filters deleted file mode 100644 index db30c6d1ae2..00000000000 --- a/src/renderer/dx/lib/dx.vcxproj.filters +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/renderer/dx/lib/sources b/src/renderer/dx/lib/sources deleted file mode 100644 index 7b2df6658ed..00000000000 --- a/src/renderer/dx/lib/sources +++ /dev/null @@ -1,8 +0,0 @@ -!include ..\sources.inc - -# ------------------------------------- -# Program Information -# ------------------------------------- - -TARGETNAME = ConRenderDx -TARGETTYPE = LIBRARY diff --git a/src/renderer/dx/precomp.cpp b/src/renderer/dx/precomp.cpp deleted file mode 100644 index c51e9b31b2f..00000000000 --- a/src/renderer/dx/precomp.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" diff --git a/src/renderer/dx/precomp.h b/src/renderer/dx/precomp.h deleted file mode 100644 index 9a09c25bb2c..00000000000 --- a/src/renderer/dx/precomp.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -// This includes support libraries from the CRT, STL, WIL, and GSL -#define BLOCK_TIL // We want to include it later, after DX. -#include "LibraryIncludes.h" - -#include -#include - -#include "../host/conddkrefs.h" -#include - -#include - -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Re-include TIL at the bottom to gain DX superpowers. -#include "til.h" - -#pragma hdrstop diff --git a/src/renderer/dx/sources.inc b/src/renderer/dx/sources.inc deleted file mode 100644 index 35b2fbc7035..00000000000 --- a/src/renderer/dx/sources.inc +++ /dev/null @@ -1,42 +0,0 @@ -!include ..\..\..\project.inc - -# ------------------------------------- -# Windows Console -# - Console Renderer for DirectX -# ------------------------------------- - -# This module provides a rendering engine implementation that -# draws to a DirectX surface. - -# ------------------------------------- -# CRT Configuration -# ------------------------------------- - -BUILD_FOR_CORESYSTEM = 1 - -# ------------------------------------- -# Sources, Headers, and Libraries -# ------------------------------------- - -PRECOMPILED_CXX = 1 -PRECOMPILED_INCLUDE = ..\precomp.h - -INCLUDES = \ - $(INCLUDES); \ - ..; \ - ..\..\inc; \ - ..\..\..\inc; \ - ..\..\..\host; \ - $(MINWIN_INTERNAL_PRIV_SDK_INC_PATH_L); \ - $(MINWIN_RESTRICTED_PRIV_SDK_INC_PATH_L); \ - -SOURCES = \ - $(SOURCES) \ - ..\DxRenderer.cpp \ - ..\DxFontInfo.cpp \ - ..\DxFontRenderData.cpp \ - ..\DxSoftFont.cpp \ - ..\CustomTextRenderer.cpp \ - ..\CustomTextLayout.cpp \ - -C_DEFINES=$(C_DEFINES) -D__INSIDE_WINDOWS diff --git a/src/renderer/dx/ut_dx/CustomTextLayoutTests.cpp b/src/renderer/dx/ut_dx/CustomTextLayoutTests.cpp deleted file mode 100644 index 782e105f7ed..00000000000 --- a/src/renderer/dx/ut_dx/CustomTextLayoutTests.cpp +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" - -#include "../CustomTextLayout.h" - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; - -using namespace Microsoft::Console::Render; - -class Microsoft::Console::Render::CustomTextLayoutTests -{ - TEST_CLASS(CustomTextLayoutTests); - - TEST_METHOD(OrderRuns) - { - CustomTextLayout layout; - - // Create linked list runs where a --> c --> b - CustomTextLayout::LinkedRun a; - a.nextRunIndex = 2; - a.textStart = 0; - CustomTextLayout::LinkedRun b; - b.nextRunIndex = 0; - b.textStart = 20; - CustomTextLayout::LinkedRun c; - c.nextRunIndex = 1; - c.textStart = 10; - - // but insert them into the runs as a, b, c - layout._runs.push_back(a); - layout._runs.push_back(b); - layout._runs.push_back(c); - - // Now order them. - layout._OrderRuns(); - - // Validate that they've been reordered to a, c, b by index so they can be iterated to go in order. - - // The text starts should be in order 0, 10, 20. - // The next run indexes should point at each other. - VERIFY_ARE_EQUAL(a.textStart, layout._runs.at(0).textStart); - VERIFY_ARE_EQUAL(1u, layout._runs.at(0).nextRunIndex); - VERIFY_ARE_EQUAL(c.textStart, layout._runs.at(1).textStart); - VERIFY_ARE_EQUAL(2u, layout._runs.at(1).nextRunIndex); - VERIFY_ARE_EQUAL(b.textStart, layout._runs.at(2).textStart); - VERIFY_ARE_EQUAL(0u, layout._runs.at(2).nextRunIndex); - } - - TEST_METHOD(SplitCurrentRunIncludingGlyphs) - { - CustomTextLayout layout; - - // Put glyph data into the layout as if we've already gone through analysis. - // This data matches the verbose comment from the CustomTextLayout.cpp file - // and is derived from - // https://social.msdn.microsoft.com/Forums/en-US/993365bc-8689-45ff-a675-c5ed0c011788/dwriteglyphrundescriptionclustermap-explained - - layout._text = L"fiñe"; - - layout._glyphIndices.push_back(19); - layout._glyphIndices.push_back(81); - layout._glyphIndices.push_back(23); - layout._glyphIndices.push_back(72); - - layout._glyphClusters.push_back(0); - layout._glyphClusters.push_back(0); - layout._glyphClusters.push_back(1); - layout._glyphClusters.push_back(3); - - // Set up the layout to have a run that already has glyph data inside of it. - CustomTextLayout::LinkedRun run; - run.textStart = 0; - run.textLength = 4; - run.glyphStart = 0; - run.glyphCount = 4; - - layout._runs.push_back(run); - - // Now split it in the middle per the comment example - layout._SetCurrentRun(2); - layout._SplitCurrentRun(2); - - // And validate that the split state matches what we expected. - VERIFY_ARE_EQUAL(0u, layout._runs.at(0).textStart); - VERIFY_ARE_EQUAL(2u, layout._runs.at(0).textLength); - VERIFY_ARE_EQUAL(0u, layout._runs.at(0).glyphStart); - VERIFY_ARE_EQUAL(1u, layout._runs.at(0).glyphCount); - - VERIFY_ARE_EQUAL(2u, layout._runs.at(1).textStart); - VERIFY_ARE_EQUAL(2u, layout._runs.at(1).textLength); - VERIFY_ARE_EQUAL(1u, layout._runs.at(1).glyphStart); - VERIFY_ARE_EQUAL(3u, layout._runs.at(1).glyphCount); - } -}; diff --git a/src/renderer/dx/ut_dx/DefaultResource.rc b/src/renderer/dx/ut_dx/DefaultResource.rc deleted file mode 100644 index 85ec2648d12..00000000000 --- a/src/renderer/dx/ut_dx/DefaultResource.rc +++ /dev/null @@ -1,12 +0,0 @@ -//Autogenerated file name + version resource file for Device Guard whitelisting effort - -#include -#include - -#define VER_FILETYPE VFT_UNKNOWN -#define VER_FILESUBTYPE VFT2_UNKNOWN -#define VER_FILEDESCRIPTION_STR ___TARGETNAME -#define VER_INTERNALNAME_STR ___TARGETNAME -#define VER_ORIGINALFILENAME_STR ___TARGETNAME - -#include "common.ver" diff --git a/src/renderer/dx/ut_dx/Dx.Unit.Tests.vcxproj b/src/renderer/dx/ut_dx/Dx.Unit.Tests.vcxproj deleted file mode 100644 index 5a46c150a7a..00000000000 --- a/src/renderer/dx/ut_dx/Dx.Unit.Tests.vcxproj +++ /dev/null @@ -1,42 +0,0 @@ - - - - {95B136F9-B238-490C-A7C5-5843C1FECAC4} - Win32Proj - DxUnitTests - Dx.Unit.Tests - Dx.Unit.Tests - DynamicLibrary - - - - - - - Create - - - - - {18d09a24-8240-42d6-8cb6-236eee820263} - - - {af0a096a-8b3a-4949-81ef-7df8f0fee91f} - - - {48D21369-3D7B-4431-9967-24E81292CF62} - - - - - - - - ..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;%(AdditionalIncludeDirectories) - - - - - - - diff --git a/src/renderer/dx/ut_dx/product.pbxproj b/src/renderer/dx/ut_dx/product.pbxproj deleted file mode 100644 index 9e5ef983069..00000000000 --- a/src/renderer/dx/ut_dx/product.pbxproj +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/renderer/dx/ut_dx/sources b/src/renderer/dx/ut_dx/sources deleted file mode 100644 index acea323c97c..00000000000 --- a/src/renderer/dx/ut_dx/sources +++ /dev/null @@ -1,36 +0,0 @@ -!include ..\..\..\project.unittest.inc - -# ------------------------------------- -# Program Information -# ------------------------------------- - -TARGETNAME = Microsoft.Console.Renderer.Dx.UnitTests -TARGETTYPE = DYNLINK -DLLDEF = - -# ------------------------------------- -# Sources, Headers, and Libraries -# ------------------------------------- - -SOURCES = \ - $(SOURCES) \ - CustomTextLayoutTests.cpp \ - DefaultResource.rc \ - -INCLUDES = \ - .. \ - $(INCLUDES) \ - -TARGETLIBS = \ - $(WINCORE_OBJ_PATH)\console\open\src\renderer\dx\lib\$(O)\ConRenderDx.lib \ - $(WINCORE_OBJ_PATH)\console\open\src\renderer\base\lib\$(O)\ConRenderBase.lib \ - $(WINCORE_OBJ_PATH)\console\open\src\types\lib\$(O)\ConTypes.lib \ - $(TARGETLIBS) \ - -# ------------------------------------- -# Localization -# ------------------------------------- - -# Autogenerated. Sets file name for Device Guard whitelisting effort, used in RC.exe. -C_DEFINES = $(C_DEFINES) -D___TARGETNAME="""$(TARGETNAME).$(TARGETTYPE)""" -MUI_VERIFY_NO_LOC_RESOURCE = 1 From 0189f84796ca977d1adb5d6f391589e8c3437e4e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 30 Jan 2024 19:24:17 +0100 Subject: [PATCH 2/2] Another one --- OpenConsole.sln | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/OpenConsole.sln b/OpenConsole.sln index 61799479fb0..3d0a5c3dfb8 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -1987,34 +1987,6 @@ Global {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x64.Build.0 = Release|x64 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.ActiveCfg = Release|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.Build.0 = Release|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x64.ActiveCfg = Release|x64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x86.ActiveCfg = AuditMode|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x86.Build.0 = AuditMode|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|ARM.ActiveCfg = Debug|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|ARM64.Build.0 = Debug|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x64.ActiveCfg = Debug|x64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x64.Build.0 = Debug|x64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x86.ActiveCfg = Debug|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x86.Build.0 = Debug|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|Any CPU.ActiveCfg = Release|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|ARM.ActiveCfg = Release|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|ARM64.ActiveCfg = Release|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|ARM64.Build.0 = Release|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x64.ActiveCfg = Release|x64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x64.Build.0 = Release|x64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x86.ActiveCfg = Release|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x86.Build.0 = Release|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 @@ -2843,7 +2815,6 @@ Global {53DD5520-E64C-4C06-B472-7CE62CA539C9} = {04170EEF-983A-4195-BFEF-2321E5E38A1E} {6B5A44ED-918D-4747-BFB1-2472A1FCA173} = {04170EEF-983A-4195-BFEF-2321E5E38A1E} {D3EF7B96-CD5E-47C9-B9A9-136259563033} = {04170EEF-983A-4195-BFEF-2321E5E38A1E} - {95B136F9-B238-490C-A7C5-5843C1FECAC4} = {05500DEF-2294-41E3-AF9A-24E580B82836} {024052DE-83FB-4653-AEA4-90790D29D5BD} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} {067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {61901E80-E97D-4D61-A9BB-E8F2FDA8B40C} {6BAE5851-50D5-4934-8D5E-30361A8A40F3} = {81C352DB-1818-45B7-A284-18E259F1CC87}