diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 19f5fb487..c36d03acd 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -9,6 +9,7 @@ BUILDBRANCH BUILDMSG BUILDNUMBER BYPOSITION +calloc charconv CLASSNOTAVAILABLE cmdletbinding @@ -25,6 +26,8 @@ DERR dlldata DONTADDTORECENT DWORDLONG +dxgidebug +dxguid enumset environstrings EXPCMDFLAGS @@ -66,8 +69,8 @@ IObject iosfwd IPackage IPeasant -isspace ISetup +isspace IStorage istream IStringable @@ -82,12 +85,12 @@ llu localtime lround LSHIFT +memicmp MENUCOMMAND MENUDATA MENUINFO -memicmp -mptt mov +mptt msappx MULTIPLEUSE NCHITTEST diff --git a/.github/actions/spelling/allow/names.txt b/.github/actions/spelling/allow/names.txt index 3635d3723..31422bbb6 100644 --- a/.github/actions/spelling/allow/names.txt +++ b/.github/actions/spelling/allow/names.txt @@ -3,6 +3,7 @@ austdi Ballmer bhoj Bhojwani +Bilodeau carlos dhowett Diviness @@ -25,8 +26,8 @@ jerrysh Kaiyu kimwalisch KMehrain -KODELIFE Kodelife +KODELIFE Kourosh kowalczyk leonmsft @@ -73,8 +74,8 @@ Wirt Wojciech zadjii Zamor -Zamora zamora +Zamora Zoey zorio Zverovich diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index dccd6d740..0866a6739 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -312,6 +312,7 @@ commdlg COMMITID compat componentization +COMPOSITIONSURFACE conapi conareainfo conattrs @@ -426,6 +427,8 @@ cstring cstyle csv CSwitch +csx +csy CTerminal CText ctime @@ -847,6 +850,7 @@ GAUSSIAN gci gcx gcy +GDC gdi gdip gdirenderer @@ -1344,6 +1348,7 @@ LPWINDOWPOS lpwpos lpwstr LRESULT +lroundf lru lsb lsconfig @@ -1445,6 +1450,7 @@ MOUSEMOVE mousewheel movemask MOVESTART +movsb msb msbuild mscorlib @@ -1623,6 +1629,7 @@ NVIDIA NVR OACR oauth +obin objbase ocf ocolor @@ -1746,6 +1753,7 @@ pdx peb PEMAGIC PENDTASKMSG +Pentium pfa PFACENODE pfed @@ -1910,6 +1918,7 @@ pythonw qos QRSTU qsort +Qstrip queryable QUESTIONMARK quickedit @@ -1917,6 +1926,7 @@ QWER qzmp RAII RALT +rapi rasterbar rasterfont rasterization @@ -2199,7 +2209,6 @@ somefile SOURCEBRANCH sourced SOURCESDIRECTORY -SPACEBAR spammy spand sprintf @@ -2309,6 +2318,7 @@ taskbar tbar TBase tbc +TBDR tbi Tbl TBM @@ -2421,6 +2431,7 @@ Trd TREX triaged triaging +TRIANGLELIST TRIANGLESTRIP TRIMZEROHEADINGS truetype @@ -2498,12 +2509,13 @@ unittesting universaltest unk unknwn +Unmap unmark UNORM unparseable unpause -Unregister unregistering +unscoped untests untextured untimes @@ -2568,6 +2580,7 @@ vectorized VERCTRL versioning VERTBAR +VERTEXID VFT vga vgaoem @@ -2575,6 +2588,7 @@ viewkind viewports Virt VIRTTERM +virtualalloc Virtualizing vkey VKKEYSCAN @@ -2851,6 +2865,7 @@ YDPI yIcon yml YOffset +yolo YPosition YSize YSubstantial diff --git a/OpenConsole.sln b/OpenConsole.sln index 493cba041..4552ad51b 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29001.49 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31410.414 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Terminal", "Terminal", "{59840756-302F-44DF-AA47-441A9D673202}" EndProject @@ -131,6 +131,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Scratch", "src\tools\scratc EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InteractivityWin32", "src\interactivity\win32\lib\win32.LIB.vcxproj", "{06EC74CB-9A12-429C-B551-8532EC964726}" ProjectSection(ProjectDependencies) = postProject + {8222900C-8B6C-452A-91AC-BE95DB04B95F} = {8222900C-8B6C-452A-91AC-BE95DB04B95F} {1C959542-BAC2-4E55-9A6D-13251914CBB9} = {1C959542-BAC2-4E55-9A6D-13251914CBB9} {990F2657-8580-4828-943F-5DD657D11842} = {990F2657-8580-4828-943F-5DD657D11842} {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F} = {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F} @@ -400,6 +401,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsTerminal.UIA.Tests", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "api-ms-win-core-synch-l1-2-0", "src\api-ms-win-core-synch-l1-2-0\api-ms-win-core-synch-l1-2-0.vcxproj", "{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererAtlas", "src\renderer\atlas\atlas.vcxproj", "{8222900C-8B6C-452A-91AC-BE95DB04B95F}" +EndProject +Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "HostPackage", "src\host\HostPackage\HostPackage.wapproj", "{44D35904-4DC6-4EEC-86F2-6E3E341C95BE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OpenConsoleShellExt", "src\host\ShellExtension\OpenConsoleShellExt.vcxproj", "{B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AuditMode|Any CPU = AuditMode|Any CPU @@ -3339,6 +3346,170 @@ Global {9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}.Release|x64.Build.0 = Release|x64 {9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}.Release|x86.ActiveCfg = Release|Win32 {9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}.Release|x86.Build.0 = Release|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x64.ActiveCfg = AuditMode|x64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x64.Build.0 = AuditMode|x64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x86.ActiveCfg = AuditMode|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x86.Build.0 = AuditMode|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM.ActiveCfg = Debug|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM64.Build.0 = Debug|ARM64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x64.ActiveCfg = Debug|x64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x64.Build.0 = Debug|x64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x86.ActiveCfg = Debug|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x86.Build.0 = Debug|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|DotNet_x64Test.ActiveCfg = Fuzzing|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|DotNet_x86Test.ActiveCfg = Fuzzing|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x64.Build.0 = Fuzzing|x64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x86.Build.0 = Fuzzing|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|Any CPU.ActiveCfg = Release|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM.ActiveCfg = Release|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM64.ActiveCfg = Release|ARM64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM64.Build.0 = Release|ARM64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|DotNet_x64Test.ActiveCfg = Release|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|DotNet_x86Test.ActiveCfg = Release|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x64.ActiveCfg = Release|x64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x64.Build.0 = Release|x64 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x86.ActiveCfg = Release|Win32 + {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x86.Build.0 = Release|Win32 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|Any CPU.ActiveCfg = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|Any CPU.Build.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|Any CPU.Deploy.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|ARM.ActiveCfg = Debug|ARM + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|ARM.Build.0 = Debug|ARM + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|ARM.Deploy.0 = Debug|ARM + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|ARM64.ActiveCfg = Debug|ARM64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|ARM64.Build.0 = Debug|ARM64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|ARM64.Deploy.0 = Debug|ARM64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|DotNet_x64Test.ActiveCfg = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|DotNet_x64Test.Build.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|DotNet_x64Test.Deploy.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|DotNet_x86Test.ActiveCfg = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|DotNet_x86Test.Build.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|DotNet_x86Test.Deploy.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|x64.ActiveCfg = Debug|x64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|x64.Build.0 = Debug|x64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|x64.Deploy.0 = Debug|x64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|x86.ActiveCfg = Debug|x86 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|x86.Build.0 = Debug|x86 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.AuditMode|x86.Deploy.0 = Debug|x86 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|ARM.ActiveCfg = Debug|ARM + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|ARM.Build.0 = Debug|ARM + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|ARM.Deploy.0 = Debug|ARM + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|ARM64.Build.0 = Debug|ARM64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|DotNet_x64Test.ActiveCfg = Debug|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|DotNet_x64Test.Build.0 = Debug|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|DotNet_x64Test.Deploy.0 = Debug|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|DotNet_x86Test.ActiveCfg = Debug|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|DotNet_x86Test.Build.0 = Debug|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|DotNet_x86Test.Deploy.0 = Debug|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|x64.ActiveCfg = Debug|x64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|x64.Build.0 = Debug|x64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|x64.Deploy.0 = Debug|x64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|x86.ActiveCfg = Debug|x86 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|x86.Build.0 = Debug|x86 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Debug|x86.Deploy.0 = Debug|x86 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|Any CPU.ActiveCfg = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|Any CPU.Build.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|Any CPU.Deploy.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|ARM.ActiveCfg = Debug|ARM + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|ARM.Build.0 = Debug|ARM + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|ARM.Deploy.0 = Debug|ARM + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|ARM64.ActiveCfg = Debug|ARM64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|ARM64.Build.0 = Debug|ARM64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|ARM64.Deploy.0 = Debug|ARM64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|DotNet_x64Test.ActiveCfg = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|DotNet_x64Test.Build.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|DotNet_x64Test.Deploy.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|DotNet_x86Test.ActiveCfg = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|DotNet_x86Test.Build.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|DotNet_x86Test.Deploy.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|x64.ActiveCfg = Debug|x64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|x64.Build.0 = Debug|x64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|x64.Deploy.0 = Debug|x64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|x86.ActiveCfg = Debug|x86 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|x86.Build.0 = Debug|x86 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Fuzzing|x86.Deploy.0 = Debug|x86 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|Any CPU.Build.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|Any CPU.Deploy.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|ARM.ActiveCfg = Release|ARM + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|ARM.Build.0 = Release|ARM + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|ARM.Deploy.0 = Release|ARM + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|ARM64.ActiveCfg = Release|ARM64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|ARM64.Build.0 = Release|ARM64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|ARM64.Deploy.0 = Release|ARM64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|DotNet_x64Test.ActiveCfg = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|DotNet_x64Test.Build.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|DotNet_x64Test.Deploy.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|DotNet_x86Test.ActiveCfg = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|DotNet_x86Test.Build.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|DotNet_x86Test.Deploy.0 = Release|Any CPU + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|x64.ActiveCfg = Release|x64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|x64.Build.0 = Release|x64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|x64.Deploy.0 = Release|x64 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|x86.ActiveCfg = Release|x86 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|x86.Build.0 = Release|x86 + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE}.Release|x86.Deploy.0 = Release|x86 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.AuditMode|x64.ActiveCfg = AuditMode|x64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.AuditMode|x64.Build.0 = AuditMode|x64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.AuditMode|x86.ActiveCfg = AuditMode|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.AuditMode|x86.Build.0 = AuditMode|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Debug|ARM.ActiveCfg = Debug|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Debug|ARM64.Build.0 = Debug|ARM64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Debug|x64.ActiveCfg = Debug|x64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Debug|x64.Build.0 = Debug|x64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Debug|x86.ActiveCfg = Debug|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Debug|x86.Build.0 = Debug|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Fuzzing|DotNet_x64Test.ActiveCfg = Fuzzing|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Fuzzing|DotNet_x86Test.ActiveCfg = Fuzzing|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Fuzzing|x64.Build.0 = Fuzzing|x64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Fuzzing|x86.Build.0 = Fuzzing|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Release|Any CPU.ActiveCfg = Release|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Release|ARM.ActiveCfg = Release|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Release|ARM64.ActiveCfg = Release|ARM64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Release|ARM64.Build.0 = Release|ARM64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Release|DotNet_x64Test.ActiveCfg = Release|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Release|DotNet_x86Test.ActiveCfg = Release|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Release|x64.ActiveCfg = Release|x64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Release|x64.Build.0 = Release|x64 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Release|x86.ActiveCfg = Release|Win32 + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -3438,6 +3609,9 @@ Global {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E} {F19DACD5-0C6E-40DC-B6E4-767A3200542C} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E} {9CF74355-F018-4C19-81AD-9DC6B7F2C6F5} = {89CDCC5C-9F53-4054-97A4-639D99F169CD} + {8222900C-8B6C-452A-91AC-BE95DB04B95F} = {05500DEF-2294-41E3-AF9A-24E580B82836} + {44D35904-4DC6-4EEC-86F2-6E3E341C95BE} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271} diff --git a/oss/robin-hood-hashing/LICENSE b/oss/robin-hood-hashing/LICENSE new file mode 100644 index 000000000..2065a8e52 --- /dev/null +++ b/oss/robin-hood-hashing/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2021 Martin Ankerl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/oss/robin-hood-hashing/MAINTAINER_README.md b/oss/robin-hood-hashing/MAINTAINER_README.md new file mode 100644 index 000000000..a35914ff5 --- /dev/null +++ b/oss/robin-hood-hashing/MAINTAINER_README.md @@ -0,0 +1,9 @@ +### Notes for Future Maintainers + +The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme. +Please update the provenance information in that file when ingesting an updated version of the dependent library. +That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards. + +## Updates + +Get updates from here: https://github.com/martinus/robin-hood-hashing diff --git a/oss/robin-hood-hashing/cgmanifest.json b/oss/robin-hood-hashing/cgmanifest.json new file mode 100644 index 000000000..12acc2bf4 --- /dev/null +++ b/oss/robin-hood-hashing/cgmanifest.json @@ -0,0 +1,14 @@ +{ + "Registrations": [ + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/martinus/robin-hood-hashing", + "commitHash": "24b3f50f9532153edc23b29ae277dcccfd75a462" + } + } + } + ], + "Version": 1 +} \ No newline at end of file diff --git a/oss/robin-hood-hashing/robin_hood.h b/oss/robin-hood-hashing/robin_hood.h new file mode 100644 index 000000000..511a308d3 --- /dev/null +++ b/oss/robin-hood-hashing/robin_hood.h @@ -0,0 +1,2529 @@ +// ______ _____ ______ _________ +// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ / +// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ / +// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ / +// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/ +// _/_____/ +// +// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20 +// https://github.com/martinus/robin-hood-hashing +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2018-2021 Martin Ankerl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef ROBIN_HOOD_H_INCLUDED +#define ROBIN_HOOD_H_INCLUDED + +// see https://semver.org/ +#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes +#define ROBIN_HOOD_VERSION_MINOR 11 // for adding functionality in a backwards-compatible manner +#define ROBIN_HOOD_VERSION_PATCH 3 // for backwards-compatible bug fixes + +#include +#include +#include +#include +#include +#include // only to support hash of smart pointers +#include +#include +#include +#include +#if __cplusplus >= 201703L +# include +#endif + +// #define ROBIN_HOOD_LOG_ENABLED +#ifdef ROBIN_HOOD_LOG_ENABLED +# include +# define ROBIN_HOOD_LOG(...) \ + std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; +#else +# define ROBIN_HOOD_LOG(x) +#endif + +// #define ROBIN_HOOD_TRACE_ENABLED +#ifdef ROBIN_HOOD_TRACE_ENABLED +# include +# define ROBIN_HOOD_TRACE(...) \ + std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; +#else +# define ROBIN_HOOD_TRACE(x) +#endif + +// #define ROBIN_HOOD_COUNT_ENABLED +#ifdef ROBIN_HOOD_COUNT_ENABLED +# include +# define ROBIN_HOOD_COUNT(x) ++counts().x; +namespace robin_hood { +struct Counts { + uint64_t shiftUp{}; + uint64_t shiftDown{}; +}; +inline std::ostream& operator<<(std::ostream& os, Counts const& c) { + return os << c.shiftUp << " shiftUp" << std::endl << c.shiftDown << " shiftDown" << std::endl; +} + +static Counts& counts() { + static Counts counts{}; + return counts; +} +} // namespace robin_hood +#else +# define ROBIN_HOOD_COUNT(x) +#endif + +// all non-argument macros should use this facility. See +// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x() + +// mark unused members with this macro +#define ROBIN_HOOD_UNUSED(identifier) + +// bitness +#if SIZE_MAX == UINT32_MAX +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32 +#elif SIZE_MAX == UINT64_MAX +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64 +#else +# error Unsupported bitness +#endif + +// endianess +#ifdef _MSC_VER +# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0 +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \ + (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#endif + +// inline +#ifdef _MSC_VER +# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline) +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline)) +#endif + +// exceptions +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0 +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1 +#endif + +// count leading/trailing bits +#if !defined(ROBIN_HOOD_DISABLE_INTRINSICS) +# ifdef _MSC_VER +# if ROBIN_HOOD(BITNESS) == 32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64 +# endif +# include +# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD)) +# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \ + [](size_t mask) noexcept -> int { \ + unsigned long index; \ + return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast(index) \ + : ROBIN_HOOD(BITNESS); \ + }(x) +# else +# if ROBIN_HOOD(BITNESS) == 32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl +# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll +# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll +# endif +# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS)) +# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS)) +# endif +#endif + +// fallthrough +#ifndef __has_cpp_attribute // For backwards compatibility +# define __has_cpp_attribute(x) 0 +#endif +#if __has_cpp_attribute(clang::fallthrough) +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]] +#elif __has_cpp_attribute(gnu::fallthrough) +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]] +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() +#endif + +// likely/unlikely +#ifdef _MSC_VER +# define ROBIN_HOOD_LIKELY(condition) condition +# define ROBIN_HOOD_UNLIKELY(condition) condition +#else +# define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1) +# define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0) +#endif + +// detect if native wchar_t type is availiable in MSVC +#ifdef _MSC_VER +# ifdef _NATIVE_WCHAR_T_DEFINED +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0 +# endif +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 +#endif + +// detect if MSVC supports the pair(std::piecewise_construct_t,...) consructor being constexpr +#ifdef _MSC_VER +# if _MSC_VER <= 1900 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 1 +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 +# endif +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 +#endif + +// workaround missing "is_trivially_copyable" in g++ < 5.0 +// See https://stackoverflow.com/a/31798726/48181 +#if defined(__GNUC__) && __GNUC__ < 5 +# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) +#else +# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value +#endif + +// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) +# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]] +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() +#endif + +namespace robin_hood { + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) +# define ROBIN_HOOD_STD std +#else + +// c++11 compatibility layer +namespace ROBIN_HOOD_STD { +template +struct alignment_of + : std::integral_constant::type)> {}; + +template +class integer_sequence { +public: + using value_type = T; + static_assert(std::is_integral::value, "not integral type"); + static constexpr std::size_t size() noexcept { + return sizeof...(Ints); + } +}; +template +using index_sequence = integer_sequence; + +namespace detail_ { +template +struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)"); + + template + struct IntSeqCombiner; + + template + struct IntSeqCombiner, integer_sequence> { + using TResult = integer_sequence; + }; + + using TResult = + typename IntSeqCombiner::TResult, + typename IntSeqImpl::TResult>::TResult; +}; + +template +struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0, "unexpected argument (Begin<0)"); + using TResult = integer_sequence; +}; + +template +struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0, "unexpected argument (Begin<0)"); + using TResult = integer_sequence; +}; +} // namespace detail_ + +template +using make_integer_sequence = typename detail_::IntSeqImpl::TResult; + +template +using make_index_sequence = make_integer_sequence; + +template +using index_sequence_for = make_index_sequence; + +} // namespace ROBIN_HOOD_STD + +#endif + +namespace detail { + +// make sure we static_cast to the correct type for hash_int +#if ROBIN_HOOD(BITNESS) == 64 +using SizeT = uint64_t; +#else +using SizeT = uint32_t; +#endif + +template +T rotr(T x, unsigned k) { + return (x >> k) | (x << (8U * sizeof(T) - k)); +} + +// This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to +// 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with +// care! +template +inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept { + return reinterpret_cast(ptr); +} + +template +inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept { + return reinterpret_cast(ptr); +} + +// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other +// inlinings more difficult. Throws are also generally the slow path. +template +[[noreturn]] ROBIN_HOOD(NOINLINE) +#if ROBIN_HOOD(HAS_EXCEPTIONS) + void doThrow(Args&&... args) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + throw E(std::forward(args)...); +} +#else + void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) { + abort(); +} +#endif + +template +T* assertNotNull(T* t, Args&&... args) { + if (ROBIN_HOOD_UNLIKELY(nullptr == t)) { + doThrow(std::forward(args)...); + } + return t; +} + +template +inline T unaligned_load(void const* ptr) noexcept { + // using memcpy so we don't get into unaligned load problems. + // compiler should optimize this very well anyways. + T t; + std::memcpy(&t, ptr, sizeof(T)); + return t; +} + +// Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor, +// and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a +// pointer. +template +class BulkPoolAllocator { +public: + BulkPoolAllocator() noexcept = default; + + // does not copy anything, just creates a new allocator. + BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept + : mHead(nullptr) + , mListForFree(nullptr) {} + + BulkPoolAllocator(BulkPoolAllocator&& o) noexcept + : mHead(o.mHead) + , mListForFree(o.mListForFree) { + o.mListForFree = nullptr; + o.mHead = nullptr; + } + + BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept { + reset(); + mHead = o.mHead; + mListForFree = o.mListForFree; + o.mListForFree = nullptr; + o.mHead = nullptr; + return *this; + } + + BulkPoolAllocator& + // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) + operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept { + // does not do anything + return *this; + } + + ~BulkPoolAllocator() noexcept { + reset(); + } + + // Deallocates all allocated memory. + void reset() noexcept { + while (mListForFree) { + T* tmp = *mListForFree; + ROBIN_HOOD_LOG("std::free") + std::free(mListForFree); + mListForFree = reinterpret_cast_no_cast_align_warning(tmp); + } + mHead = nullptr; + } + + // allocates, but does NOT initialize. Use in-place new constructor, e.g. + // T* obj = pool.allocate(); + // ::new (static_cast(obj)) T(); + T* allocate() { + T* tmp = mHead; + if (!tmp) { + tmp = performAllocation(); + } + + mHead = *reinterpret_cast_no_cast_align_warning(tmp); + return tmp; + } + + // does not actually deallocate but puts it in store. + // make sure you have already called the destructor! e.g. with + // obj->~T(); + // pool.deallocate(obj); + void deallocate(T* obj) noexcept { + *reinterpret_cast_no_cast_align_warning(obj) = mHead; + mHead = obj; + } + + // Adds an already allocated block of memory to the allocator. This allocator is from now on + // responsible for freeing the data (with free()). If the provided data is not large enough to + // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor. + void addOrFree(void* ptr, const size_t numBytes) noexcept { + // calculate number of available elements in ptr + if (numBytes < ALIGNMENT + ALIGNED_SIZE) { + // not enough data for at least one element. Free and return. + ROBIN_HOOD_LOG("std::free") + std::free(ptr); + } else { + ROBIN_HOOD_LOG("add to buffer") + add(ptr, numBytes); + } + } + + void swap(BulkPoolAllocator& other) noexcept { + using std::swap; + swap(mHead, other.mHead); + swap(mListForFree, other.mListForFree); + } + +private: + // iterates the list of allocated memory to calculate how many to alloc next. + // Recalculating this each time saves us a size_t member. + // This ignores the fact that memory blocks might have been added manually with addOrFree. In + // practice, this should not matter much. + ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept { + auto tmp = mListForFree; + size_t numAllocs = MinNumAllocs; + + while (numAllocs * 2 <= MaxNumAllocs && tmp) { + auto x = reinterpret_cast(tmp); + tmp = *x; + numAllocs *= 2; + } + + return numAllocs; + } + + // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree(). + void add(void* ptr, const size_t numBytes) noexcept { + const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE; + + auto data = reinterpret_cast(ptr); + + // link free list + auto x = reinterpret_cast(data); + *x = mListForFree; + mListForFree = data; + + // create linked list for newly allocated data + auto* const headT = + reinterpret_cast_no_cast_align_warning(reinterpret_cast(ptr) + ALIGNMENT); + + auto* const head = reinterpret_cast(headT); + + // Visual Studio compiler automatically unrolls this loop, which is pretty cool + for (size_t i = 0; i < numElements; ++i) { + *reinterpret_cast_no_cast_align_warning(head + i * ALIGNED_SIZE) = + head + (i + 1) * ALIGNED_SIZE; + } + + // last one points to 0 + *reinterpret_cast_no_cast_align_warning(head + (numElements - 1) * ALIGNED_SIZE) = + mHead; + mHead = headT; + } + + // Called when no memory is available (mHead == 0). + // Don't inline this slow path. + ROBIN_HOOD(NOINLINE) T* performAllocation() { + size_t const numElementsToAlloc = calcNumElementsToAlloc(); + + // alloc new memory: [prev |T, T, ... T] + size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc; + ROBIN_HOOD_LOG("std::malloc " << bytes << " = " << ALIGNMENT << " + " << ALIGNED_SIZE + << " * " << numElementsToAlloc) + add(assertNotNull(std::malloc(bytes)), bytes); + return mHead; + } + + // enforce byte alignment of the T's +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) + static constexpr size_t ALIGNMENT = + (std::max)(std::alignment_of::value, std::alignment_of::value); +#else + static const size_t ALIGNMENT = + (ROBIN_HOOD_STD::alignment_of::value > ROBIN_HOOD_STD::alignment_of::value) + ? ROBIN_HOOD_STD::alignment_of::value + : +ROBIN_HOOD_STD::alignment_of::value; // the + is for walkarround +#endif + + static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT; + + static_assert(MinNumAllocs >= 1, "MinNumAllocs"); + static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs"); + static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE"); + static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod"); + static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT"); + + T* mHead{nullptr}; + T** mListForFree{nullptr}; +}; + +template +struct NodeAllocator; + +// dummy allocator that does nothing +template +struct NodeAllocator { + + // we are not using the data, so just free it. + void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept { + ROBIN_HOOD_LOG("std::free") + std::free(ptr); + } +}; + +template +struct NodeAllocator : public BulkPoolAllocator {}; + +// c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making +// my own here. +namespace swappable { +#if ROBIN_HOOD(CXX) < ROBIN_HOOD(CXX17) +using std::swap; +template +struct nothrow { + static const bool value = noexcept(swap(std::declval(), std::declval())); +}; +#else +template +struct nothrow { + static const bool value = std::is_nothrow_swappable::value; +}; +#endif +} // namespace swappable + +} // namespace detail + +struct is_transparent_tag {}; + +// A custom pair implementation is used in the map because std::pair is not is_trivially_copyable, +// which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is +// also tested. +template +struct pair { + using first_type = T1; + using second_type = T2; + + template ::value && + std::is_default_constructible::value>::type> + constexpr pair() noexcept(noexcept(U1()) && noexcept(U2())) + : first() + , second() {} + + // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. + explicit constexpr pair(std::pair const& o) noexcept( + noexcept(T1(std::declval())) && noexcept(T2(std::declval()))) + : first(o.first) + , second(o.second) {} + + // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. + explicit constexpr pair(std::pair&& o) noexcept(noexcept( + T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) + : first(std::move(o.first)) + , second(std::move(o.second)) {} + + constexpr pair(T1&& a, T2&& b) noexcept(noexcept( + T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) + : first(std::move(a)) + , second(std::move(b)) {} + + template + constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward( + std::declval()))) && noexcept(T2(std::forward(std::declval())))) + : first(std::forward(a)) + , second(std::forward(b)) {} + + template + // MSVC 2015 produces error "C2476: ‘constexpr’ constructor does not initialize all members" + // if this constructor is constexpr +#if !ROBIN_HOOD(BROKEN_CONSTEXPR) + constexpr +#endif + pair(std::piecewise_construct_t /*unused*/, std::tuple a, + std::tuple + b) noexcept(noexcept(pair(std::declval&>(), + std::declval&>(), + ROBIN_HOOD_STD::index_sequence_for(), + ROBIN_HOOD_STD::index_sequence_for()))) + : pair(a, b, ROBIN_HOOD_STD::index_sequence_for(), + ROBIN_HOOD_STD::index_sequence_for()) { + } + + // constructor called from the std::piecewise_construct_t ctor + template + pair(std::tuple& a, std::tuple& b, ROBIN_HOOD_STD::index_sequence /*unused*/, ROBIN_HOOD_STD::index_sequence /*unused*/) noexcept( + noexcept(T1(std::forward(std::get( + std::declval&>()))...)) && noexcept(T2(std:: + forward(std::get( + std::declval&>()))...))) + : first(std::forward(std::get(a))...) + , second(std::forward(std::get(b))...) { + // make visual studio compiler happy about warning about unused a & b. + // Visual studio's pair implementation disables warning 4100. + (void)a; + (void)b; + } + + void swap(pair& o) noexcept((detail::swappable::nothrow::value) && + (detail::swappable::nothrow::value)) { + using std::swap; + swap(first, o.first); + swap(second, o.second); + } + + T1 first; // NOLINT(misc-non-private-member-variables-in-classes) + T2 second; // NOLINT(misc-non-private-member-variables-in-classes) +}; + +template +inline void swap(pair& a, pair& b) noexcept( + noexcept(std::declval&>().swap(std::declval&>()))) { + a.swap(b); +} + +template +inline constexpr bool operator==(pair const& x, pair const& y) { + return (x.first == y.first) && (x.second == y.second); +} +template +inline constexpr bool operator!=(pair const& x, pair const& y) { + return !(x == y); +} +template +inline constexpr bool operator<(pair const& x, pair const& y) noexcept(noexcept( + std::declval() < std::declval()) && noexcept(std::declval() < + std::declval())) { + return x.first < y.first || (!(y.first < x.first) && x.second < y.second); +} +template +inline constexpr bool operator>(pair const& x, pair const& y) { + return y < x; +} +template +inline constexpr bool operator<=(pair const& x, pair const& y) { + return !(x > y); +} +template +inline constexpr bool operator>=(pair const& x, pair const& y) { + return !(x < y); +} + +inline size_t hash_bytes(void const* ptr, size_t len) noexcept { + static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995); + static constexpr uint64_t seed = UINT64_C(0xe17a1465); + static constexpr unsigned int r = 47; + + auto const* const data64 = static_cast(ptr); + uint64_t h = seed ^ (len * m); + + size_t const n_blocks = len / 8; + for (size_t i = 0; i < n_blocks; ++i) { + auto k = detail::unaligned_load(data64 + i); + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + + auto const* const data8 = reinterpret_cast(data64 + n_blocks); + switch (len & 7U) { + case 7: + h ^= static_cast(data8[6]) << 48U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 6: + h ^= static_cast(data8[5]) << 40U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 5: + h ^= static_cast(data8[4]) << 32U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 4: + h ^= static_cast(data8[3]) << 24U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 3: + h ^= static_cast(data8[2]) << 16U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 2: + h ^= static_cast(data8[1]) << 8U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 1: + h ^= static_cast(data8[0]); + h *= m; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + default: + break; + } + + h ^= h >> r; + + // not doing the final step here, because this will be done by keyToIdx anyways + // h *= m; + // h ^= h >> r; + return static_cast(h); +} + +inline size_t hash_int(uint64_t x) noexcept { + // tried lots of different hashes, let's stick with murmurhash3. It's simple, fast, well tested, + // and doesn't need any special 128bit operations. + x ^= x >> 33U; + x *= UINT64_C(0xff51afd7ed558ccd); + x ^= x >> 33U; + + // not doing the final step here, because this will be done by keyToIdx anyways + // x *= UINT64_C(0xc4ceb9fe1a85ec53); + // x ^= x >> 33U; + return static_cast(x); +} + +// A thin wrapper around std::hash, performing an additional simple mixing step of the result. +template +struct hash : public std::hash { + size_t operator()(T const& obj) const + noexcept(noexcept(std::declval>().operator()(std::declval()))) { + // call base hash + auto result = std::hash::operator()(obj); + // return mixed of that, to be save against identity has + return hash_int(static_cast(result)); + } +}; + +template +struct hash> { + size_t operator()(std::basic_string const& str) const noexcept { + return hash_bytes(str.data(), sizeof(CharT) * str.size()); + } +}; + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) +template +struct hash> { + size_t operator()(std::basic_string_view const& sv) const noexcept { + return hash_bytes(sv.data(), sizeof(CharT) * sv.size()); + } +}; +#endif + +template +struct hash { + size_t operator()(T* ptr) const noexcept { + return hash_int(reinterpret_cast(ptr)); + } +}; + +template +struct hash> { + size_t operator()(std::unique_ptr const& ptr) const noexcept { + return hash_int(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash> { + size_t operator()(std::shared_ptr const& ptr) const noexcept { + return hash_int(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash::value>::type> { + size_t operator()(Enum e) const noexcept { + using Underlying = typename std::underlying_type::type; + return hash{}(static_cast(e)); + } +}; + +#define ROBIN_HOOD_HASH_INT(T) \ + template <> \ + struct hash { \ + size_t operator()(T const& obj) const noexcept { \ + return hash_int(static_cast(obj)); \ + } \ + } + +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wuseless-cast" +#endif +// see https://en.cppreference.com/w/cpp/utility/hash +ROBIN_HOOD_HASH_INT(bool); +ROBIN_HOOD_HASH_INT(char); +ROBIN_HOOD_HASH_INT(signed char); +ROBIN_HOOD_HASH_INT(unsigned char); +ROBIN_HOOD_HASH_INT(char16_t); +ROBIN_HOOD_HASH_INT(char32_t); +#if ROBIN_HOOD(HAS_NATIVE_WCHART) +ROBIN_HOOD_HASH_INT(wchar_t); +#endif +ROBIN_HOOD_HASH_INT(short); +ROBIN_HOOD_HASH_INT(unsigned short); +ROBIN_HOOD_HASH_INT(int); +ROBIN_HOOD_HASH_INT(unsigned int); +ROBIN_HOOD_HASH_INT(long); +ROBIN_HOOD_HASH_INT(long long); +ROBIN_HOOD_HASH_INT(unsigned long); +ROBIN_HOOD_HASH_INT(unsigned long long); +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +#endif +namespace detail { + +template +struct void_type { + using type = void; +}; + +template +struct has_is_transparent : public std::false_type {}; + +template +struct has_is_transparent::type> + : public std::true_type {}; + +// using wrapper classes for hash and key_equal prevents the diamond problem when the same type +// is used. see https://stackoverflow.com/a/28771920/48181 +template +struct WrapHash : public T { + WrapHash() = default; + explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval()))) + : T(o) {} +}; + +template +struct WrapKeyEqual : public T { + WrapKeyEqual() = default; + explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval()))) + : T(o) {} +}; + +// A highly optimized hashmap implementation, using the Robin Hood algorithm. +// +// In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but +// be about 2x faster in most cases and require much less allocations. +// +// This implementation uses the following memory layout: +// +// [Node, Node, ... Node | info, info, ... infoSentinel ] +// +// * Node: either a DataNode that directly has the std::pair as member, +// or a DataNode with a pointer to std::pair. Which DataNode representation to use +// depends on how fast the swap() operation is. Heuristically, this is automatically choosen +// based on sizeof(). there are always 2^n Nodes. +// +// * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes. +// Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the +// corresponding node contains data. Set to 2 means the corresponding Node is filled, but it +// actually belongs to the previous position and was pushed out because that place is already +// taken. +// +// * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the +// need for a idx variable. +// +// According to STL, order of templates has effect on throughput. That's why I've moved the +// boolean to the front. +// https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/ +template +class Table + : public WrapHash, + public WrapKeyEqual, + detail::NodeAllocator< + typename std::conditional< + std::is_void::value, Key, + robin_hood::pair::type, T>>::type, + 4, 16384, IsFlat> { +public: + static constexpr bool is_flat = IsFlat; + static constexpr bool is_map = !std::is_void::value; + static constexpr bool is_set = !is_map; + static constexpr bool is_transparent = + has_is_transparent::value && has_is_transparent::value; + + using key_type = Key; + using mapped_type = T; + using value_type = typename std::conditional< + is_set, Key, + robin_hood::pair::type, T>>::type; + using size_type = size_t; + using hasher = Hash; + using key_equal = KeyEqual; + using Self = Table; + +private: + static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100, + "MaxLoadFactor100 needs to be >10 && < 100"); + + using WHash = WrapHash; + using WKeyEqual = WrapKeyEqual; + + // configuration defaults + + // make sure we have 8 elements, needed to quickly rehash mInfo + static constexpr size_t InitialNumElements = sizeof(uint64_t); + static constexpr uint32_t InitialInfoNumBits = 5; + static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits; + static constexpr size_t InfoMask = InitialInfoInc - 1U; + static constexpr uint8_t InitialInfoHashShift = 0; + using DataPool = detail::NodeAllocator; + + // type needs to be wider than uint8_t. + using InfoType = uint32_t; + + // DataNode //////////////////////////////////////////////////////// + + // Primary template for the data node. We have special implementations for small and big + // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these + // on the heap so swap merely swaps a pointer. + template + class DataNode {}; + + // Small: just allocate on the stack. + template + class DataNode final { + public: + template + explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept( + noexcept(value_type(std::forward(args)...))) + : mData(std::forward(args)...) {} + + DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept( + std::is_nothrow_move_constructible::value) + : mData(std::move(n.mData)) {} + + // doesn't do anything + void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {} + void destroyDoNotDeallocate() noexcept {} + + value_type const* operator->() const noexcept { + return &mData; + } + value_type* operator->() noexcept { + return &mData; + } + + const value_type& operator*() const noexcept { + return mData; + } + + value_type& operator*() noexcept { + return mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData.first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type + getFirst() const noexcept { + return mData.first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() const noexcept { + return mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() noexcept { + return mData.second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() const noexcept { + return mData.second; + } + + void swap(DataNode& o) noexcept( + noexcept(std::declval().swap(std::declval()))) { + mData.swap(o.mData); + } + + private: + value_type mData; + }; + + // big object: allocate on heap. + template + class DataNode { + public: + template + explicit DataNode(M& map, Args&&... args) + : mData(map.allocate()) { + ::new (static_cast(mData)) value_type(std::forward(args)...); + } + + DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept + : mData(std::move(n.mData)) {} + + void destroy(M& map) noexcept { + // don't deallocate, just put it into list of datapool. + mData->~value_type(); + map.deallocate(mData); + } + + void destroyDoNotDeallocate() noexcept { + mData->~value_type(); + } + + value_type const* operator->() const noexcept { + return mData; + } + + value_type* operator->() noexcept { + return mData; + } + + const value_type& operator*() const { + return *mData; + } + + value_type& operator*() { + return *mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData->first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return *mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type + getFirst() const noexcept { + return mData->first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() const noexcept { + return *mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() noexcept { + return mData->second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() const noexcept { + return mData->second; + } + + void swap(DataNode& o) noexcept { + using std::swap; + swap(mData, o.mData); + } + + private: + value_type* mData; + }; + + using Node = DataNode; + + // helpers for insertKeyPrepareEmptySpot: extract first entry (only const required) + ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(Node const& n) const noexcept { + return n.getFirst(); + } + + // in case we have void mapped_type, we are not using a pair, thus we just route k through. + // No need to disable this because it's just not used if not applicable. + ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(key_type const& k) const noexcept { + return k; + } + + // in case we have non-void mapped_type, we have a standard robin_hood::pair + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, key_type const&>::type + getFirstConst(value_type const& vt) const noexcept { + return vt.first; + } + + // Cloner ////////////////////////////////////////////////////////// + + template + struct Cloner; + + // fast path: Just copy data, without allocating anything. + template + struct Cloner { + void operator()(M const& source, M& target) const { + auto const* const src = reinterpret_cast(source.mKeyVals); + auto* tgt = reinterpret_cast(target.mKeyVals); + auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1); + std::copy(src, src + target.calcNumBytesTotal(numElementsWithBuffer), tgt); + } + }; + + template + struct Cloner { + void operator()(M const& s, M& t) const { + auto const numElementsWithBuffer = t.calcNumElementsWithBuffer(t.mMask + 1); + std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(numElementsWithBuffer), t.mInfo); + + for (size_t i = 0; i < numElementsWithBuffer; ++i) { + if (t.mInfo[i]) { + ::new (static_cast(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]); + } + } + } + }; + + // Destroyer /////////////////////////////////////////////////////// + + template + struct Destroyer {}; + + template + struct Destroyer { + void nodes(M& m) const noexcept { + m.mNumElements = 0; + } + + void nodesDoNotDeallocate(M& m) const noexcept { + m.mNumElements = 0; + } + }; + + template + struct Destroyer { + void nodes(M& m) const noexcept { + m.mNumElements = 0; + // clear also resets mInfo to 0, that's sometimes not necessary. + auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); + + for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { + if (0 != m.mInfo[idx]) { + Node& n = m.mKeyVals[idx]; + n.destroy(m); + n.~Node(); + } + } + } + + void nodesDoNotDeallocate(M& m) const noexcept { + m.mNumElements = 0; + // clear also resets mInfo to 0, that's sometimes not necessary. + auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); + for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { + if (0 != m.mInfo[idx]) { + Node& n = m.mKeyVals[idx]; + n.destroyDoNotDeallocate(); + n.~Node(); + } + } + } + }; + + // Iter //////////////////////////////////////////////////////////// + + struct fast_forward_tag {}; + + // generic iterator for both const_iterator and iterator. + template + // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions) + class Iter { + private: + using NodePtr = typename std::conditional::type; + + public: + using difference_type = std::ptrdiff_t; + using value_type = typename Self::value_type; + using reference = typename std::conditional::type; + using pointer = typename std::conditional::type; + using iterator_category = std::forward_iterator_tag; + + // default constructed iterator can be compared to itself, but WON'T return true when + // compared to end(). + Iter() = default; + + // Rule of zero: nothing specified. The conversion constructor is only enabled for + // iterator to const_iterator, so it doesn't accidentally work as a copy ctor. + + // Conversion constructor from iterator to const_iterator. + template ::type> + // NOLINTNEXTLINE(hicpp-explicit-conversions) + Iter(Iter const& other) noexcept + : mKeyVals(other.mKeyVals) + , mInfo(other.mInfo) {} + + Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept + : mKeyVals(valPtr) + , mInfo(infoPtr) {} + + Iter(NodePtr valPtr, uint8_t const* infoPtr, + fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept + : mKeyVals(valPtr) + , mInfo(infoPtr) { + fastForward(); + } + + template ::type> + Iter& operator=(Iter const& other) noexcept { + mKeyVals = other.mKeyVals; + mInfo = other.mInfo; + return *this; + } + + // prefix increment. Undefined behavior if we are at end()! + Iter& operator++() noexcept { + mInfo++; + mKeyVals++; + fastForward(); + return *this; + } + + Iter operator++(int) noexcept { + Iter tmp = *this; + ++(*this); + return tmp; + } + + reference operator*() const { + return **mKeyVals; + } + + pointer operator->() const { + return &**mKeyVals; + } + + template + bool operator==(Iter const& o) const noexcept { + return mKeyVals == o.mKeyVals; + } + + template + bool operator!=(Iter const& o) const noexcept { + return mKeyVals != o.mKeyVals; + } + + private: + // fast forward to the next non-free info byte + // I've tried a few variants that don't depend on intrinsics, but unfortunately they are + // quite a bit slower than this one. So I've reverted that change again. See map_benchmark. + void fastForward() noexcept { + size_t n = 0; + while (0U == (n = detail::unaligned_load(mInfo))) { + mInfo += sizeof(size_t); + mKeyVals += sizeof(size_t); + } +#if defined(ROBIN_HOOD_DISABLE_INTRINSICS) + // we know for certain that within the next 8 bytes we'll find a non-zero one. + if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { + mInfo += 4; + mKeyVals += 4; + } + if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { + mInfo += 2; + mKeyVals += 2; + } + if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) { + mInfo += 1; + mKeyVals += 1; + } +#else +# if ROBIN_HOOD(LITTLE_ENDIAN) + auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8; +# else + auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8; +# endif + mInfo += inc; + mKeyVals += inc; +#endif + } + + friend class Table; + NodePtr mKeyVals{nullptr}; + uint8_t const* mInfo{nullptr}; + }; + + //////////////////////////////////////////////////////////////////// + + // highly performance relevant code. + // Lower bits are used for indexing into the array (2^n size) + // The upper 1-5 bits need to be a reasonable good hash, to save comparisons. + template + void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const { + // In addition to whatever hash is used, add another mul & shift so we get better hashing. + // This serves as a bad hash prevention, if the given data is + // badly mixed. + auto h = static_cast(WHash::operator()(key)); + + h *= mHashMultiplier; + h ^= h >> 33U; + + // the lower InitialInfoNumBits are reserved for info. + *info = mInfoInc + static_cast((h & InfoMask) >> mInfoHashShift); + *idx = (static_cast(h) >> InitialInfoNumBits) & mMask; + } + + // forwards the index by one, wrapping around at the end + void next(InfoType* info, size_t* idx) const noexcept { + *idx = *idx + 1; + *info += mInfoInc; + } + + void nextWhileLess(InfoType* info, size_t* idx) const noexcept { + // unrolling this by hand did not bring any speedups. + while (*info < mInfo[*idx]) { + next(info, idx); + } + } + + // Shift everything up by one element. Tries to move stuff around. + void + shiftUp(size_t startIdx, + size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable::value) { + auto idx = startIdx; + ::new (static_cast(mKeyVals + idx)) Node(std::move(mKeyVals[idx - 1])); + while (--idx != insertion_idx) { + mKeyVals[idx] = std::move(mKeyVals[idx - 1]); + } + + idx = startIdx; + while (idx != insertion_idx) { + ROBIN_HOOD_COUNT(shiftUp) + mInfo[idx] = static_cast(mInfo[idx - 1] + mInfoInc); + if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + --idx; + } + } + + void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable::value) { + // until we find one that is either empty or has zero offset. + // TODO(martinus) we don't need to move everything, just the last one for the same + // bucket. + mKeyVals[idx].destroy(*this); + + // until we find one that is either empty or has zero offset. + while (mInfo[idx + 1] >= 2 * mInfoInc) { + ROBIN_HOOD_COUNT(shiftDown) + mInfo[idx] = static_cast(mInfo[idx + 1] - mInfoInc); + mKeyVals[idx] = std::move(mKeyVals[idx + 1]); + ++idx; + } + + mInfo[idx] = 0; + // don't destroy, we've moved it + // mKeyVals[idx].destroy(*this); + mKeyVals[idx].~Node(); + } + + // copy of find(), except that it returns iterator instead of const_iterator. + template + ROBIN_HOOD(NODISCARD) + size_t findIdx(Other const& key) const { + size_t idx{}; + InfoType info{}; + keyToIdx(key, &idx, &info); + + do { + // unrolling this twice gives a bit of a speedup. More unrolling did not help. + if (info == mInfo[idx] && + ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { + return idx; + } + next(&info, &idx); + if (info == mInfo[idx] && + ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { + return idx; + } + next(&info, &idx); + } while (info <= mInfo[idx]); + + // nothing found! + return mMask == 0 ? 0 + : static_cast(std::distance( + mKeyVals, reinterpret_cast_no_cast_align_warning(mInfo))); + } + + void cloneData(const Table& o) { + Cloner()(o, *this); + } + + // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized. + // @return True on success, false if something went wrong + void insert_move(Node&& keyval) { + // we don't retry, fail if overflowing + // don't need to check max num elements + if (0 == mMaxNumElementsAllowed && !try_increase_info()) { + throwOverflowError(); + } + + size_t idx{}; + InfoType info{}; + keyToIdx(keyval.getFirst(), &idx, &info); + + // skip forward. Use <= because we are certain that the element is not there. + while (info <= mInfo[idx]) { + idx = idx + 1; + info += mInfoInc; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = static_cast(info); + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + auto& l = mKeyVals[insertion_idx]; + if (idx == insertion_idx) { + ::new (static_cast(&l)) Node(std::move(keyval)); + } else { + shiftUp(idx, insertion_idx); + l = std::move(keyval); + } + + // put at empty spot + mInfo[insertion_idx] = insertion_info; + + ++mNumElements; + } + +public: + using iterator = Iter; + using const_iterator = Iter; + + Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual())) + : WHash() + , WKeyEqual() { + ROBIN_HOOD_TRACE(this) + } + + // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. + // This tremendously speeds up ctor & dtor of a map that never receives an element. The + // penalty is payed at the first insert, and not before. Lookup of this empty map works + // because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the + // standard, but we can ignore it. + explicit Table( + size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{}, + const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal))) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) + } + + template + Table(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, + const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) + insert(first, last); + } + + Table(std::initializer_list initlist, + size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, + const KeyEqual& equal = KeyEqual{}) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) + insert(initlist.begin(), initlist.end()); + } + + Table(Table&& o) noexcept + : WHash(std::move(static_cast(o))) + , WKeyEqual(std::move(static_cast(o))) + , DataPool(std::move(static_cast(o))) { + ROBIN_HOOD_TRACE(this) + if (o.mMask) { + mHashMultiplier = std::move(o.mHashMultiplier); + mKeyVals = std::move(o.mKeyVals); + mInfo = std::move(o.mInfo); + mNumElements = std::move(o.mNumElements); + mMask = std::move(o.mMask); + mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); + mInfoInc = std::move(o.mInfoInc); + mInfoHashShift = std::move(o.mInfoHashShift); + // set other's mask to 0 so its destructor won't do anything + o.init(); + } + } + + Table& operator=(Table&& o) noexcept { + ROBIN_HOOD_TRACE(this) + if (&o != this) { + if (o.mMask) { + // only move stuff if the other map actually has some data + destroy(); + mHashMultiplier = std::move(o.mHashMultiplier); + mKeyVals = std::move(o.mKeyVals); + mInfo = std::move(o.mInfo); + mNumElements = std::move(o.mNumElements); + mMask = std::move(o.mMask); + mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); + mInfoInc = std::move(o.mInfoInc); + mInfoHashShift = std::move(o.mInfoHashShift); + WHash::operator=(std::move(static_cast(o))); + WKeyEqual::operator=(std::move(static_cast(o))); + DataPool::operator=(std::move(static_cast(o))); + + o.init(); + + } else { + // nothing in the other map => just clear us. + clear(); + } + } + return *this; + } + + Table(const Table& o) + : WHash(static_cast(o)) + , WKeyEqual(static_cast(o)) + , DataPool(static_cast(o)) { + ROBIN_HOOD_TRACE(this) + if (!o.empty()) { + // not empty: create an exact copy. it is also possible to just iterate through all + // elements and insert them, but copying is probably faster. + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); + auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); + + ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" + << numElementsWithBuffer << ")") + mHashMultiplier = o.mHashMultiplier; + mKeyVals = static_cast( + detail::assertNotNull(std::malloc(numBytesTotal))); + // no need for calloc because clonData does memcpy + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + mNumElements = o.mNumElements; + mMask = o.mMask; + mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; + mInfoInc = o.mInfoInc; + mInfoHashShift = o.mInfoHashShift; + cloneData(o); + } + } + + // Creates a copy of the given map. Copy constructor of each entry is used. + // Not sure why clang-tidy thinks this doesn't handle self assignment, it does + // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) + Table& operator=(Table const& o) { + ROBIN_HOOD_TRACE(this) + if (&o == this) { + // prevent assigning of itself + return *this; + } + + // we keep using the old allocator and not assign the new one, because we want to keep + // the memory available. when it is the same size. + if (o.empty()) { + if (0 == mMask) { + // nothing to do, we are empty too + return *this; + } + + // not empty: destroy what we have there + // clear also resets mInfo to 0, that's sometimes not necessary. + destroy(); + init(); + WHash::operator=(static_cast(o)); + WKeyEqual::operator=(static_cast(o)); + DataPool::operator=(static_cast(o)); + + return *this; + } + + // clean up old stuff + Destroyer::value>{}.nodes(*this); + + if (mMask != o.mMask) { + // no luck: we don't have the same array size allocated, so we need to realloc. + if (0 != mMask) { + // only deallocate if we actually have data! + ROBIN_HOOD_LOG("std::free") + std::free(mKeyVals); + } + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); + auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); + ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" + << numElementsWithBuffer << ")") + mKeyVals = static_cast( + detail::assertNotNull(std::malloc(numBytesTotal))); + + // no need for calloc here because cloneData performs a memcpy. + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + // sentinel is set in cloneData + } + WHash::operator=(static_cast(o)); + WKeyEqual::operator=(static_cast(o)); + DataPool::operator=(static_cast(o)); + mHashMultiplier = o.mHashMultiplier; + mNumElements = o.mNumElements; + mMask = o.mMask; + mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; + mInfoInc = o.mInfoInc; + mInfoHashShift = o.mInfoHashShift; + cloneData(o); + + return *this; + } + + // Swaps everything between the two maps. + void swap(Table& o) { + ROBIN_HOOD_TRACE(this) + using std::swap; + swap(o, *this); + } + + // Clears all data, without resizing. + void clear() { + ROBIN_HOOD_TRACE(this) + if (empty()) { + // don't do anything! also important because we don't want to write to + // DummyInfoByte::b, even though we would just write 0 to it. + return; + } + + Destroyer::value>{}.nodes(*this); + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + // clear everything, then set the sentinel again + uint8_t const z = 0; + std::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z); + mInfo[numElementsWithBuffer] = 1; + + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + // Destroys the map and all it's contents. + ~Table() { + ROBIN_HOOD_TRACE(this) + destroy(); + } + + // Checks if both tables contain the same entries. Order is irrelevant. + bool operator==(const Table& other) const { + ROBIN_HOOD_TRACE(this) + if (other.size() != size()) { + return false; + } + for (auto const& otherEntry : other) { + if (!has(otherEntry)) { + return false; + } + } + + return true; + } + + bool operator!=(const Table& other) const { + ROBIN_HOOD_TRACE(this) + return !operator==(other); + } + + template + typename std::enable_if::value, Q&>::type operator[](const key_type& key) { + ROBIN_HOOD_TRACE(this) + auto idxAndState = insertKeyPrepareEmptySpot(key); + switch (idxAndState.second) { + case InsertionState::key_found: + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) + Node(*this, std::piecewise_construct, std::forward_as_tuple(key), + std::forward_as_tuple()); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, + std::forward_as_tuple(key), std::forward_as_tuple()); + break; + + case InsertionState::overflow_error: + throwOverflowError(); + } + + return mKeyVals[idxAndState.first].getSecond(); + } + + template + typename std::enable_if::value, Q&>::type operator[](key_type&& key) { + ROBIN_HOOD_TRACE(this) + auto idxAndState = insertKeyPrepareEmptySpot(key); + switch (idxAndState.second) { + case InsertionState::key_found: + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) + Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), + std::forward_as_tuple()); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = + Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), + std::forward_as_tuple()); + break; + + case InsertionState::overflow_error: + throwOverflowError(); + } + + return mKeyVals[idxAndState.first].getSecond(); + } + + template + void insert(Iter first, Iter last) { + for (; first != last; ++first) { + // value_type ctor needed because this might be called with std::pair's + insert(value_type(*first)); + } + } + + void insert(std::initializer_list ilist) { + for (auto&& vt : ilist) { + insert(std::move(vt)); + } + } + + template + std::pair emplace(Args&&... args) { + ROBIN_HOOD_TRACE(this) + Node n{*this, std::forward(args)...}; + auto idxAndState = insertKeyPrepareEmptySpot(getFirstConst(n)); + switch (idxAndState.second) { + case InsertionState::key_found: + n.destroy(*this); + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) Node(*this, std::move(n)); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = std::move(n); + break; + + case InsertionState::overflow_error: + n.destroy(*this); + throwOverflowError(); + break; + } + + return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), + InsertionState::key_found != idxAndState.second); + } + + template + std::pair try_emplace(const key_type& key, Args&&... args) { + return try_emplace_impl(key, std::forward(args)...); + } + + template + std::pair try_emplace(key_type&& key, Args&&... args) { + return try_emplace_impl(std::move(key), std::forward(args)...); + } + + template + std::pair try_emplace(const_iterator hint, const key_type& key, + Args&&... args) { + (void)hint; + return try_emplace_impl(key, std::forward(args)...); + } + + template + std::pair try_emplace(const_iterator hint, key_type&& key, Args&&... args) { + (void)hint; + return try_emplace_impl(std::move(key), std::forward(args)...); + } + + template + std::pair insert_or_assign(const key_type& key, Mapped&& obj) { + return insertOrAssignImpl(key, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type&& key, Mapped&& obj) { + return insertOrAssignImpl(std::move(key), std::forward(obj)); + } + + template + std::pair insert_or_assign(const_iterator hint, const key_type& key, + Mapped&& obj) { + (void)hint; + return insertOrAssignImpl(key, std::forward(obj)); + } + + template + std::pair insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) { + (void)hint; + return insertOrAssignImpl(std::move(key), std::forward(obj)); + } + + std::pair insert(const value_type& keyval) { + ROBIN_HOOD_TRACE(this) + return emplace(keyval); + } + + std::pair insert(value_type&& keyval) { + return emplace(std::move(keyval)); + } + + // Returns 1 if key is found, 0 otherwise. + size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { + return 1; + } + return 0; + } + + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::type count(const OtherKey& key) const { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { + return 1; + } + return 0; + } + + bool contains(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + return 1U == count(key); + } + + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::type contains(const OtherKey& key) const { + return 1U == count(key); + } + + // Returns a reference to the value found for key. + // Throws std::out_of_range if element cannot be found + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::value, Q&>::type at(key_type const& key) { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { + doThrow("key not found"); + } + return kv->getSecond(); + } + + // Returns a reference to the value found for key. + // Throws std::out_of_range if element cannot be found + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::value, Q const&>::type at(key_type const& key) const { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { + doThrow("key not found"); + } + return kv->getSecond(); + } + + const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{mKeyVals + idx, mInfo + idx}; + } + + template + const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{mKeyVals + idx, mInfo + idx}; + } + + template + typename std::enable_if::type // NOLINT(modernize-use-nodiscard) + find(const OtherKey& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{mKeyVals + idx, mInfo + idx}; + } + + iterator find(const key_type& key) { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{mKeyVals + idx, mInfo + idx}; + } + + template + iterator find(const OtherKey& key, is_transparent_tag /*unused*/) { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{mKeyVals + idx, mInfo + idx}; + } + + template + typename std::enable_if::type find(const OtherKey& key) { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{mKeyVals + idx, mInfo + idx}; + } + + iterator begin() { + ROBIN_HOOD_TRACE(this) + if (empty()) { + return end(); + } + return iterator(mKeyVals, mInfo, fast_forward_tag{}); + } + const_iterator begin() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return cbegin(); + } + const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + if (empty()) { + return cend(); + } + return const_iterator(mKeyVals, mInfo, fast_forward_tag{}); + } + + iterator end() { + ROBIN_HOOD_TRACE(this) + // no need to supply valid info pointer: end() must not be dereferenced, and only node + // pointer is compared. + return iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; + } + const_iterator end() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return cend(); + } + const_iterator cend() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return const_iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; + } + + iterator erase(const_iterator pos) { + ROBIN_HOOD_TRACE(this) + // its safe to perform const cast here + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + return erase(iterator{const_cast(pos.mKeyVals), const_cast(pos.mInfo)}); + } + + // Erases element at pos, returns iterator to the next element. + iterator erase(iterator pos) { + ROBIN_HOOD_TRACE(this) + // we assume that pos always points to a valid entry, and not end(). + auto const idx = static_cast(pos.mKeyVals - mKeyVals); + + shiftDown(idx); + --mNumElements; + + if (*pos.mInfo) { + // we've backward shifted, return this again + return pos; + } + + // no backward shift, return next element + return ++pos; + } + + size_t erase(const key_type& key) { + ROBIN_HOOD_TRACE(this) + size_t idx{}; + InfoType info{}; + keyToIdx(key, &idx, &info); + + // check while info matches with the source idx + do { + if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + shiftDown(idx); + --mNumElements; + return 1; + } + next(&info, &idx); + } while (info <= mInfo[idx]); + + // nothing found to delete + return 0; + } + + // reserves space for the specified number of elements. Makes sure the old data fits. + // exactly the same as reserve(c). + void rehash(size_t c) { + // forces a reserve + reserve(c, true); + } + + // reserves space for the specified number of elements. Makes sure the old data fits. + // Exactly the same as rehash(c). Use rehash(0) to shrink to fit. + void reserve(size_t c) { + // reserve, but don't force rehash + reserve(c, false); + } + + // If possible reallocates the map to a smaller one. This frees the underlying table. + // Does not do anything if load_factor is too large for decreasing the table's size. + void compact() { + ROBIN_HOOD_TRACE(this) + auto newSize = InitialNumElements; + while (calcMaxNumElementsAllowed(newSize) < mNumElements && newSize != 0) { + newSize *= 2; + } + if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { + throwOverflowError(); + } + + ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") + + // only actually do anything when the new size is bigger than the old one. This prevents to + // continuously allocate for each reserve() call. + if (newSize < mMask + 1) { + rehashPowerOfTwo(newSize, true); + } + } + + size_type size() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return mNumElements; + } + + size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return static_cast(-1); + } + + ROBIN_HOOD(NODISCARD) bool empty() const noexcept { + ROBIN_HOOD_TRACE(this) + return 0 == mNumElements; + } + + float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return MaxLoadFactor100 / 100.0F; + } + + // Average number of elements per bucket. Since we allow only 1 per bucket + float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return static_cast(size()) / static_cast(mMask + 1); + } + + ROBIN_HOOD(NODISCARD) size_t mask() const noexcept { + ROBIN_HOOD_TRACE(this) + return mMask; + } + + ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept { + if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits::max)() / 100)) { + return maxElements * MaxLoadFactor100 / 100; + } + + // we might be a bit inprecise, but since maxElements is quite large that doesn't matter + return (maxElements / 100) * MaxLoadFactor100; + } + + ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const noexcept { + // we add a uint64_t, which houses the sentinel (first byte) and padding so we can load + // 64bit types. + return numElements + sizeof(uint64_t); + } + + ROBIN_HOOD(NODISCARD) + size_t calcNumElementsWithBuffer(size_t numElements) const noexcept { + auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements); + return numElements + (std::min)(maxNumElementsAllowed, (static_cast(0xFF))); + } + + // calculation only allowed for 2^n values + ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const { +#if ROBIN_HOOD(BITNESS) == 64 + return numElements * sizeof(Node) + calcNumBytesInfo(numElements); +#else + // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows. + auto const ne = static_cast(numElements); + auto const s = static_cast(sizeof(Node)); + auto const infos = static_cast(calcNumBytesInfo(numElements)); + + auto const total64 = ne * s + infos; + auto const total = static_cast(total64); + + if (ROBIN_HOOD_UNLIKELY(static_cast(total) != total64)) { + throwOverflowError(); + } + return total; +#endif + } + +private: + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, bool>::type has(const value_type& e) const { + ROBIN_HOOD_TRACE(this) + auto it = find(e.first); + return it != end() && it->second == e.second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, bool>::type has(const value_type& e) const { + ROBIN_HOOD_TRACE(this) + return find(e) != end(); + } + + void reserve(size_t c, bool forceRehash) { + ROBIN_HOOD_TRACE(this) + auto const minElementsAllowed = (std::max)(c, mNumElements); + auto newSize = InitialNumElements; + while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) { + newSize *= 2; + } + if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { + throwOverflowError(); + } + + ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") + + // only actually do anything when the new size is bigger than the old one. This prevents to + // continuously allocate for each reserve() call. + if (forceRehash || newSize > mMask + 1) { + rehashPowerOfTwo(newSize, false); + } + } + + // reserves space for at least the specified number of elements. + // only works if numBuckets if power of two + // True on success, false otherwise + void rehashPowerOfTwo(size_t numBuckets, bool forceFree) { + ROBIN_HOOD_TRACE(this) + + Node* const oldKeyVals = mKeyVals; + uint8_t const* const oldInfo = mInfo; + + const size_t oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + + // resize operation: move stuff + initData(numBuckets); + if (oldMaxElementsWithBuffer > 1) { + for (size_t i = 0; i < oldMaxElementsWithBuffer; ++i) { + if (oldInfo[i] != 0) { + // might throw an exception, which is really bad since we are in the middle of + // moving stuff. + insert_move(std::move(oldKeyVals[i])); + // destroy the node but DON'T destroy the data. + oldKeyVals[i].~Node(); + } + } + + // this check is not necessary as it's guarded by the previous if, but it helps + // silence g++'s overeager "attempt to free a non-heap object 'map' + // [-Werror=free-nonheap-object]" warning. + if (oldKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { + // don't destroy old data: put it into the pool instead + if (forceFree) { + std::free(oldKeyVals); + } else { + DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer)); + } + } + } + } + + ROBIN_HOOD(NOINLINE) void throwOverflowError() const { +#if ROBIN_HOOD(HAS_EXCEPTIONS) + throw std::overflow_error("robin_hood::map overflow"); +#else + abort(); +#endif + } + + template + std::pair try_emplace_impl(OtherKey&& key, Args&&... args) { + ROBIN_HOOD_TRACE(this) + auto idxAndState = insertKeyPrepareEmptySpot(key); + switch (idxAndState.second) { + case InsertionState::key_found: + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) Node( + *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + break; + + case InsertionState::overflow_error: + throwOverflowError(); + break; + } + + return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), + InsertionState::key_found != idxAndState.second); + } + + template + std::pair insertOrAssignImpl(OtherKey&& key, Mapped&& obj) { + ROBIN_HOOD_TRACE(this) + auto idxAndState = insertKeyPrepareEmptySpot(key); + switch (idxAndState.second) { + case InsertionState::key_found: + mKeyVals[idxAndState.first].getSecond() = std::forward(obj); + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) Node( + *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(obj))); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(obj))); + break; + + case InsertionState::overflow_error: + throwOverflowError(); + break; + } + + return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), + InsertionState::key_found != idxAndState.second); + } + + void initData(size_t max_elements) { + mNumElements = 0; + mMask = max_elements - 1; + mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements); + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements); + + // calloc also zeroes everything + auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); + ROBIN_HOOD_LOG("std::calloc " << numBytesTotal << " = calcNumBytesTotal(" + << numElementsWithBuffer << ")") + mKeyVals = reinterpret_cast( + detail::assertNotNull(std::calloc(1, numBytesTotal))); + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + + // set sentinel + mInfo[numElementsWithBuffer] = 1; + + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + enum class InsertionState { overflow_error, key_found, new_node, overwrite_node }; + + // Finds key, and if not already present prepares a spot where to pot the key & value. + // This potentially shifts nodes out of the way, updates mInfo and number of inserted + // elements, so the only operation left to do is create/assign a new node at that spot. + template + std::pair insertKeyPrepareEmptySpot(OtherKey&& key) { + for (int i = 0; i < 256; ++i) { + size_t idx{}; + InfoType info{}; + keyToIdx(key, &idx, &info); + nextWhileLess(&info, &idx); + + // while we potentially have a match + while (info == mInfo[idx]) { + if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + // key already exists, do NOT insert. + // see http://en.cppreference.com/w/cpp/container/unordered_map/insert + return std::make_pair(idx, InsertionState::key_found); + } + next(&info, &idx); + } + + // unlikely that this evaluates to true + if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { + if (!increase_size()) { + return std::make_pair(size_t(0), InsertionState::overflow_error); + } + continue; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = info; + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + if (idx != insertion_idx) { + shiftUp(idx, insertion_idx); + } + // put at empty spot + mInfo[insertion_idx] = static_cast(insertion_info); + ++mNumElements; + return std::make_pair(insertion_idx, idx == insertion_idx + ? InsertionState::new_node + : InsertionState::overwrite_node); + } + + // enough attempts failed, so finally give up. + return std::make_pair(size_t(0), InsertionState::overflow_error); + } + + bool try_increase_info() { + ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements + << ", maxNumElementsAllowed=" + << calcMaxNumElementsAllowed(mMask + 1)) + if (mInfoInc <= 2) { + // need to be > 2 so that shift works (otherwise undefined behavior!) + return false; + } + // we got space left, try to make info smaller + mInfoInc = static_cast(mInfoInc >> 1U); + + // remove one bit of the hash, leaving more space for the distance info. + // This is extremely fast because we can operate on 8 bytes at once. + ++mInfoHashShift; + auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + + for (size_t i = 0; i < numElementsWithBuffer; i += 8) { + auto val = unaligned_load(mInfo + i); + val = (val >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f); + std::memcpy(mInfo + i, &val, sizeof(val)); + } + // update sentinel, which might have been cleared out! + mInfo[numElementsWithBuffer] = 1; + + mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); + return true; + } + + // True if resize was possible, false otherwise + bool increase_size() { + // nothing allocated yet? just allocate InitialNumElements + if (0 == mMask) { + initData(InitialNumElements); + return true; + } + + auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); + if (mNumElements < maxNumElementsAllowed && try_increase_info()) { + return true; + } + + ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed=" + << maxNumElementsAllowed << ", load=" + << (static_cast(mNumElements) * 100.0 / + (static_cast(mMask) + 1))) + + if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) { + // we have to resize, even though there would still be plenty of space left! + // Try to rehash instead. Delete freed memory so we don't steadyily increase mem in case + // we have to rehash a few times + nextHashMultiplier(); + rehashPowerOfTwo(mMask + 1, true); + } else { + // we've reached the capacity of the map, so the hash seems to work nice. Keep using it. + rehashPowerOfTwo((mMask + 1) * 2, false); + } + return true; + } + + void nextHashMultiplier() { + // adding an *even* number, so that the multiplier will always stay odd. This is necessary + // so that the hash stays a mixing function (and thus doesn't have any information loss). + mHashMultiplier += UINT64_C(0xc4ceb9fe1a85ec54); + } + + void destroy() { + if (0 == mMask) { + // don't deallocate! + return; + } + + Destroyer::value>{} + .nodesDoNotDeallocate(*this); + + // This protection against not deleting mMask shouldn't be needed as it's sufficiently + // protected with the 0==mMask check, but I have this anyways because g++ 7 otherwise + // reports a compile error: attempt to free a non-heap object 'fm' + // [-Werror=free-nonheap-object] + if (mKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { + ROBIN_HOOD_LOG("std::free") + std::free(mKeyVals); + } + } + + void init() noexcept { + mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); + mInfo = reinterpret_cast(&mMask); + mNumElements = 0; + mMask = 0; + mMaxNumElementsAllowed = 0; + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + // members are sorted so no padding occurs + uint64_t mHashMultiplier = UINT64_C(0xc4ceb9fe1a85ec53); // 8 byte 8 + Node* mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); // 8 byte 16 + uint8_t* mInfo = reinterpret_cast(&mMask); // 8 byte 24 + size_t mNumElements = 0; // 8 byte 32 + size_t mMask = 0; // 8 byte 40 + size_t mMaxNumElementsAllowed = 0; // 8 byte 48 + InfoType mInfoInc = InitialInfoInc; // 4 byte 52 + InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 56 + // 16 byte 56 if NodeAllocator +}; + +} // namespace detail + +// map + +template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> +using unordered_flat_map = detail::Table; + +template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> +using unordered_node_map = detail::Table; + +template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> +using unordered_map = + detail::Table) <= sizeof(size_t) * 6 && + std::is_nothrow_move_constructible>::value && + std::is_nothrow_move_assignable>::value, + MaxLoadFactor100, Key, T, Hash, KeyEqual>; + +// set + +template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> +using unordered_flat_set = detail::Table; + +template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> +using unordered_node_set = detail::Table; + +template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> +using unordered_set = detail::Table::value && + std::is_nothrow_move_assignable::value, + MaxLoadFactor100, Key, void, Hash, KeyEqual>; + +} // namespace robin_hood + +#endif diff --git a/src/buffer/out/CharRow.cpp b/src/buffer/out/CharRow.cpp index 40a945c50..b3194d124 100644 --- a/src/buffer/out/CharRow.cpp +++ b/src/buffer/out/CharRow.cpp @@ -17,8 +17,8 @@ // Note: will through if unable to allocate char/attribute buffers #pragma warning(push) #pragma warning(disable : 26447) // small_vector's constructor says it can throw but it should not given how we use it. This suppresses this error for the AuditMode build. -CharRow::CharRow(size_t rowWidth, ROW* const pParent) noexcept : - _data(rowWidth, value_type()), +CharRow::CharRow(CharRowCell* buffer, size_t rowWidth, ROW* const pParent) noexcept : + _data(buffer, rowWidth), _pParent{ FAIL_FAST_IF_NULL(pParent) } { } @@ -53,38 +53,9 @@ void CharRow::Reset() noexcept // - resizes the width of the CharRowBase // Arguments: // - newSize - the new width of the character and attributes rows -// Return Value: -// - S_OK on success, otherwise relevant error code -[[nodiscard]] HRESULT CharRow::Resize(const size_t newSize) noexcept +void CharRow::Resize(CharRowCell* buffer, const size_t newSize) noexcept { - try - { - const value_type insertVals; - _data.resize(newSize, insertVals); - } - CATCH_RETURN(); - - return S_OK; -} - -typename CharRow::iterator CharRow::begin() noexcept -{ - return _data.begin(); -} - -typename CharRow::const_iterator CharRow::cbegin() const noexcept -{ - return _data.cbegin(); -} - -typename CharRow::iterator CharRow::end() noexcept -{ - return _data.end(); -} - -typename CharRow::const_iterator CharRow::cend() const noexcept -{ - return _data.cend(); + _data = { buffer, newSize }; } // Routine Description: @@ -95,12 +66,16 @@ typename CharRow::const_iterator CharRow::cend() const noexcept // - The calculated left boundary of the internal string. size_t CharRow::MeasureLeft() const noexcept { - const_iterator it = _data.cbegin(); - while (it != _data.cend() && it->IsSpace()) + const auto beg = _data.begin(); + const auto end = _data.end(); + + auto it = beg; + while (it != end && it->IsSpace()) { ++it; } - return it - _data.cbegin(); + + return it - beg; } // Routine Description: @@ -111,17 +86,21 @@ size_t CharRow::MeasureLeft() const noexcept // - The calculated right boundary of the internal string. size_t CharRow::MeasureRight() const { - const_reverse_iterator it = _data.crbegin(); - while (it != _data.crend() && it->IsSpace()) + const auto beg = _data.rbegin(); + const auto end = _data.rend(); + + auto it = beg; + while (it != end && it->IsSpace()) { ++it; } - return _data.crend() - it; + + return end - it; } void CharRow::ClearCell(const size_t column) { - _data.at(column).Reset(); + _data[column].Reset(); } // Routine Description: @@ -132,7 +111,7 @@ void CharRow::ClearCell(const size_t column) // - True if there is valid text in this row. False otherwise. bool CharRow::ContainsText() const noexcept { - for (const value_type& cell : _data) + for (const auto& cell : _data) { if (!cell.IsSpace()) { @@ -151,7 +130,7 @@ bool CharRow::ContainsText() const noexcept // Note: will throw exception if column is out of bounds const DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) const { - return _data.at(column).DbcsAttr(); + return _data[column].DbcsAttr(); } // Routine Description: @@ -163,7 +142,7 @@ const DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) const // Note: will throw exception if column is out of bounds DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) { - return _data.at(column).DbcsAttr(); + return _data[column].DbcsAttr(); } // Routine Description: @@ -175,7 +154,7 @@ DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) // Note: will throw exception if column is out of bounds void CharRow::ClearGlyph(const size_t column) { - _data.at(column).EraseChars(); + _data[column].EraseChars(); } // Routine Description: diff --git a/src/buffer/out/CharRow.hpp b/src/buffer/out/CharRow.hpp index 55d747e87..2c6442b23 100644 --- a/src/buffer/out/CharRow.hpp +++ b/src/buffer/out/CharRow.hpp @@ -49,15 +49,12 @@ class CharRow final public: using glyph_type = typename wchar_t; using value_type = typename CharRowCell; - using iterator = typename boost::container::small_vector_base::iterator; - using const_iterator = typename boost::container::small_vector_base::const_iterator; - using const_reverse_iterator = typename boost::container::small_vector_base::const_reverse_iterator; using reference = typename CharRowCellReference; - CharRow(size_t rowWidth, ROW* const pParent) noexcept; + CharRow(CharRowCell* buffer, size_t rowWidth, ROW* const pParent) noexcept; size_t size() const noexcept; - [[nodiscard]] HRESULT Resize(const size_t newSize) noexcept; + void Resize(CharRowCell* buffer, const size_t newSize) noexcept; size_t MeasureLeft() const noexcept; size_t MeasureRight() const; bool ContainsText() const noexcept; @@ -71,14 +68,46 @@ public: const reference GlyphAt(const size_t column) const; reference GlyphAt(const size_t column); - // iterators - iterator begin() noexcept; - const_iterator cbegin() const noexcept; - const_iterator begin() const noexcept { return cbegin(); } + auto begin() noexcept + { + // gsl::span uses strict bounds checking even in Release mode. + // While this can be useful, not even our STL is that strict. + // --> Reduce iteration overhead in Release by returning pointers. +#ifdef NDEBUG + return _data.data(); +#else + return _data.begin(); +#endif + } - iterator end() noexcept; - const_iterator cend() const noexcept; - const_iterator end() const noexcept { return cend(); } + auto begin() const noexcept + { +#ifdef NDEBUG + return _data.data(); +#else + return _data.begin(); +#endif + } + + auto end() noexcept + { +#ifdef NDEBUG +#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). + return _data.data() + _data.size(); +#else + return _data.end(); +#endif + } + + auto end() const noexcept + { +#ifdef NDEBUG +#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). + return _data.data() + _data.size(); +#else + return _data.end(); +#endif + } UnicodeStorage& GetUnicodeStorage() noexcept; const UnicodeStorage& GetUnicodeStorage() const noexcept; @@ -96,20 +125,21 @@ private: protected: // storage for glyph data and dbcs attributes - boost::container::small_vector _data; + gsl::span _data; // ROW that this CharRow belongs to ROW* _pParent; }; -template -void OverwriteColumns(InputIt1 startChars, InputIt1 endChars, InputIt2 startAttrs, CharRow::iterator outIt) +template +void OverwriteColumns(InputIt1 startChars, InputIt1 endChars, InputIt2 startAttrs, OutputIt outIt) { - std::transform(startChars, - endChars, - startAttrs, - outIt, - [](const wchar_t wch, const DbcsAttribute attr) { - return CharRow::value_type{ wch, attr }; - }); + std::transform( + startChars, + endChars, + startAttrs, + outIt, + [](const wchar_t wch, const DbcsAttribute attr) { + return CharRow::value_type{ wch, attr }; + }); } diff --git a/src/buffer/out/CharRowCell.cpp b/src/buffer/out/CharRowCell.cpp index 2ada05d3c..e102ff18e 100644 --- a/src/buffer/out/CharRowCell.cpp +++ b/src/buffer/out/CharRowCell.cpp @@ -33,7 +33,7 @@ void CharRowCell::Reset() noexcept // - true if cell contains a space glyph, false otherwise bool CharRowCell::IsSpace() const noexcept { - return !_attr.IsGlyphStored() && _wch == UNICODE_SPACE; + return !_attr.IsGlyphStored() && (_wch == 0 || _wch == UNICODE_SPACE); } // Routine Description: diff --git a/src/buffer/out/CharRowCell.hpp b/src/buffer/out/CharRowCell.hpp index 2e68a51ef..1d0db91f3 100644 --- a/src/buffer/out/CharRowCell.hpp +++ b/src/buffer/out/CharRowCell.hpp @@ -50,7 +50,7 @@ public: friend constexpr bool operator==(const CharRowCell& a, const CharRowCell& b) noexcept; private: - wchar_t _wch{ UNICODE_SPACE }; + wchar_t _wch{}; DbcsAttribute _attr{}; }; diff --git a/src/buffer/out/CharRowCellReference.cpp b/src/buffer/out/CharRowCellReference.cpp index 0f8a2e1a5..51dae5e39 100644 --- a/src/buffer/out/CharRowCellReference.cpp +++ b/src/buffer/out/CharRowCellReference.cpp @@ -5,6 +5,13 @@ #include "UnicodeStorage.hpp" #include "CharRow.hpp" +CharRowCellReference::CharRowCellReference(CharRow& parent, const size_t index) : + _parent{ parent }, + _index{ index } +{ + Expects(index < parent.size()); +} + // Routine Description: // - assignment operator. will store extended glyph data in a separate storage location // Arguments: @@ -41,7 +48,7 @@ CharRowCellReference::operator std::wstring_view() const // - ref to the CharRowCell CharRowCell& CharRowCellReference::_cellData() { - return _parent._data.at(_index); + return til::at(_parent._data, _index); } // Routine Description: @@ -50,7 +57,7 @@ CharRowCell& CharRowCellReference::_cellData() // - ref to the CharRowCell const CharRowCell& CharRowCellReference::_cellData() const { - return _parent._data.at(_index); + return til::at(_parent._data, _index); } // Routine Description: diff --git a/src/buffer/out/CharRowCellReference.hpp b/src/buffer/out/CharRowCellReference.hpp index 1d1d59668..ab72c0fe9 100644 --- a/src/buffer/out/CharRowCellReference.hpp +++ b/src/buffer/out/CharRowCellReference.hpp @@ -25,11 +25,7 @@ class CharRowCellReference final public: using const_iterator = const wchar_t*; - CharRowCellReference(CharRow& parent, const size_t index) noexcept : - _parent{ parent }, - _index{ index } - { - } + CharRowCellReference(CharRow& parent, const size_t index); ~CharRowCellReference() = default; CharRowCellReference(const CharRowCellReference&) noexcept = default; diff --git a/src/buffer/out/OutputCellIterator.cpp b/src/buffer/out/OutputCellIterator.cpp index d32087fe9..42ed770fb 100644 --- a/src/buffer/out/OutputCellIterator.cpp +++ b/src/buffer/out/OutputCellIterator.cpp @@ -81,7 +81,7 @@ OutputCellIterator::OutputCellIterator(const CHAR_INFO& charInfo, const size_t f // - This is an iterator over a range of text only. No color data will be modified as the text is inserted. // Arguments: // - utf16Text - UTF-16 text range -OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) : +OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) noexcept : _mode(Mode::LooseTextOnly), _currentView(s_GenerateView(utf16Text)), _run(utf16Text), @@ -97,7 +97,7 @@ OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) : // Arguments: // - utf16Text - UTF-16 text range // - attribute - Color to apply over the entire range -OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute) : +OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute) noexcept : _mode(Mode::Loose), _currentView(s_GenerateView(utf16Text, attribute)), _run(utf16Text), @@ -362,7 +362,7 @@ bool OutputCellIterator::_TryMoveTrailing() noexcept // - view - View representing characters corresponding to a single glyph // Return Value: // - Object representing the view into this cell -OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view) +OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view) noexcept { return s_GenerateView(view, InvalidTextAttribute, TextAttributeBehavior::Current); } @@ -377,8 +377,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view) // - attr - Color attributes to apply to the text // Return Value: // - Object representing the view into this cell -OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, - const TextAttribute attr) +OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, const TextAttribute attr) noexcept { return s_GenerateView(view, attr, TextAttributeBehavior::Stored); } @@ -394,9 +393,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, // - behavior - Behavior of the given text attribute (used when writing) // Return Value: // - Object representing the view into this cell -OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, - const TextAttribute attr, - const TextAttributeBehavior behavior) +OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, const TextAttribute attr, const TextAttributeBehavior behavior) noexcept { const auto glyph = Utf16Parser::ParseNext(view); DbcsAttribute dbcsAttr; diff --git a/src/buffer/out/OutputCellIterator.hpp b/src/buffer/out/OutputCellIterator.hpp index 59c6007aa..3291ae9ab 100644 --- a/src/buffer/out/OutputCellIterator.hpp +++ b/src/buffer/out/OutputCellIterator.hpp @@ -37,8 +37,8 @@ public: OutputCellIterator(const TextAttribute& attr, const size_t fillLimit = 0) noexcept; OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit = 0) noexcept; OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit = 0) noexcept; - OutputCellIterator(const std::wstring_view utf16Text); - OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute); + OutputCellIterator(const std::wstring_view utf16Text) noexcept; + OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute) noexcept; OutputCellIterator(const gsl::span legacyAttributes) noexcept; OutputCellIterator(const gsl::span charInfos) noexcept; OutputCellIterator(const gsl::span cells); @@ -100,14 +100,9 @@ private: bool _TryMoveTrailing() noexcept; - static OutputCellView s_GenerateView(const std::wstring_view view); - - static OutputCellView s_GenerateView(const std::wstring_view view, - const TextAttribute attr); - - static OutputCellView s_GenerateView(const std::wstring_view view, - const TextAttribute attr, - const TextAttributeBehavior behavior); + static OutputCellView s_GenerateView(const std::wstring_view view) noexcept; + static OutputCellView s_GenerateView(const std::wstring_view view, const TextAttribute attr) noexcept; + static OutputCellView s_GenerateView(const std::wstring_view view, const TextAttribute attr, const TextAttributeBehavior behavior) noexcept; static OutputCellView s_GenerateView(const wchar_t& wch) noexcept; static OutputCellView s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept; diff --git a/src/buffer/out/OutputCellView.cpp b/src/buffer/out/OutputCellView.cpp index cd1f16a76..14980d23e 100644 --- a/src/buffer/out/OutputCellView.cpp +++ b/src/buffer/out/OutputCellView.cpp @@ -31,7 +31,17 @@ OutputCellView::OutputCellView(const std::wstring_view view, // TODO: GH 2681 - remove this suppression by reconciling the probably bad design of the iterators that leads to this being required. [[gsl::suppress(26445)]] const std::wstring_view& OutputCellView::Chars() const noexcept { - return _view; + static constexpr std::wstring_view emptyBufferCell{ L"\0", 1 }; + static constexpr std::wstring_view spaceBufferCell{ L" ", 1 }; + + // The buffer uses virtual memory and rows might not be initialized yet. + // To us they'll appear as if they contain \0, but we don't want to pass that to the renderer. + if (_view != emptyBufferCell) + { + return _view; + } + + return spaceBufferCell; } // Routine Description: diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 0f4cdc842..e52c3ede7 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -16,10 +16,9 @@ // - pParent - the text buffer that this row belongs to // Return Value: // - constructed object -ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) : +ROW::ROW(const SHORT rowId, CharRowCell* buffer, const unsigned short rowWidth, const TextAttribute& fillAttribute, TextBuffer* const pParent) : _id{ rowId }, - _rowWidth{ rowWidth }, - _charRow{ rowWidth, this }, + _charRow{ buffer, rowWidth, this }, _attrRow{ rowWidth, fillAttribute }, _lineRendition{ LineRendition::SingleWidth }, _wrapForced{ false }, @@ -34,7 +33,7 @@ ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute f // - Attr - The default attribute (color) to fill // Return Value: // - -bool ROW::Reset(const TextAttribute Attr) +bool ROW::Reset(const TextAttribute& Attr) { _lineRendition = LineRendition::SingleWidth; _wrapForced = false; @@ -52,26 +51,6 @@ bool ROW::Reset(const TextAttribute Attr) return true; } -// Routine Description: -// - resizes ROW to new width -// Arguments: -// - width - the new width, in cells -// Return Value: -// - S_OK if successful, otherwise relevant error -[[nodiscard]] HRESULT ROW::Resize(const unsigned short width) -{ - RETURN_IF_FAILED(_charRow.Resize(width)); - try - { - _attrRow.Resize(width); - } - CATCH_RETURN(); - - _rowWidth = width; - - return S_OK; -} - // Routine Description: // - clears char data in column in row // Arguments: diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index cee3b53bd..526be7beb 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -32,9 +32,9 @@ class TextBuffer; class ROW final { public: - ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent); + ROW(const SHORT rowId, CharRowCell* buffer, const unsigned short rowWidth, const TextAttribute& fillAttribute, TextBuffer* const pParent); - size_t size() const noexcept { return _rowWidth; } + size_t size() const noexcept { return _charRow.size(); } void SetWrapForced(const bool wrap) noexcept { _wrapForced = wrap; } bool WasWrapForced() const noexcept { return _wrapForced; } @@ -54,8 +54,7 @@ public: SHORT GetId() const noexcept { return _id; } void SetId(const SHORT id) noexcept { _id = id; } - bool Reset(const TextAttribute Attr); - [[nodiscard]] HRESULT Resize(const unsigned short width); + bool Reset(const TextAttribute& Attr); void ClearColumn(const size_t column); std::wstring GetText() const { return _charRow.GetText(); } @@ -74,13 +73,12 @@ private: CharRow _charRow; ATTR_ROW _attrRow; LineRendition _lineRendition; + TextBuffer* _pParent; // non ownership pointer SHORT _id; - unsigned short _rowWidth; // Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line bool _wrapForced; // Occurs when the user runs out of text to support a double byte character and we're forced to the next line bool _doubleBytePadded; - TextBuffer* _pParent; // non ownership pointer }; #ifdef UNIT_TESTING diff --git a/src/buffer/out/TextAttribute.hpp b/src/buffer/out/TextAttribute.hpp index 00f8a2266..a624ab36f 100644 --- a/src/buffer/out/TextAttribute.hpp +++ b/src/buffer/out/TextAttribute.hpp @@ -85,10 +85,6 @@ public: friend constexpr bool operator==(const TextAttribute& a, const TextAttribute& b) noexcept; friend constexpr bool operator!=(const TextAttribute& a, const TextAttribute& b) noexcept; - friend constexpr bool operator==(const TextAttribute& attr, const WORD& legacyAttr) noexcept; - friend constexpr bool operator!=(const TextAttribute& attr, const WORD& legacyAttr) noexcept; - friend constexpr bool operator==(const WORD& legacyAttr, const TextAttribute& attr) noexcept; - friend constexpr bool operator!=(const WORD& legacyAttr, const TextAttribute& attr) noexcept; bool IsLegacy() const noexcept; bool IsBold() const noexcept; diff --git a/src/buffer/out/UnicodeStorage.cpp b/src/buffer/out/UnicodeStorage.cpp index 7de3c1102..63b0f6ae0 100644 --- a/src/buffer/out/UnicodeStorage.cpp +++ b/src/buffer/out/UnicodeStorage.cpp @@ -47,7 +47,7 @@ void UnicodeStorage::Erase(const key_type key) noexcept // - rowMap - A map of the old row IDs to the new row IDs. // - width - The width of the new row. Remove any items that are beyond the row width. // - Use nullopt if we're not resizing the width of the row, just renumbering the rows. -void UnicodeStorage::Remap(const std::unordered_map& rowMap, const std::optional width) +void UnicodeStorage::Remap(const std::unordered_map& rowMap, SHORT width) { // Make a temporary map to hold all the new row positioning std::unordered_map newMap; @@ -58,18 +58,10 @@ void UnicodeStorage::Remap(const std::unordered_map& rowMap, const // Extract the old coordinate position const auto oldCoord = pair.first; - // Only try to short-circuit based on width if we were told it changed - // by being given a new width value. - if (width.has_value()) + // If the column index is at/beyond the row width, don't bother copying it to the new map. + if (oldCoord.X >= width) { - // Get the column ID - const auto oldColId = oldCoord.X; - - // If the column index is at/beyond the row width, don't bother copying it to the new map. - if (oldColId >= width.value()) - { - continue; - } + continue; } // Get the row ID from the position as that's what we need to remap diff --git a/src/buffer/out/UnicodeStorage.hpp b/src/buffer/out/UnicodeStorage.hpp index e1bd2ff36..7af651cd0 100644 --- a/src/buffer/out/UnicodeStorage.hpp +++ b/src/buffer/out/UnicodeStorage.hpp @@ -55,7 +55,7 @@ public: void Erase(const key_type key) noexcept; - void Remap(const std::unordered_map& rowMap, const std::optional width); + void Remap(const std::unordered_map& rowMap, SHORT width); private: std::unordered_map _map; diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 3fba7b082..3979eb68e 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -31,21 +31,19 @@ TextBuffer::TextBuffer(const COORD screenBufferSize, const TextAttribute defaultAttributes, const UINT cursorSize, Microsoft::Console::Render::IRenderTarget& renderTarget) : - _firstRow{ 0 }, _currentAttributes{ defaultAttributes }, _cursor{ cursorSize, *this }, - _storage{}, - _unicodeStorage{}, - _renderTarget{ renderTarget }, - _size{}, - _currentHyperlinkId{ 1 }, - _currentPatternId{ 0 } + _renderTarget{ renderTarget } { - // initialize ROWs + _charBuffer = _AllocateCharBuffer(screenBufferSize); _storage.reserve(static_cast(screenBufferSize.Y)); - for (size_t i = 0; i < static_cast(screenBufferSize.Y); ++i) + + auto buffer = _charBuffer.get(); + + for (SHORT i = 0; i < screenBufferSize.Y; ++i) { - _storage.emplace_back(static_cast(i), screenBufferSize.X, _currentAttributes, this); + _storage.emplace_back(i, buffer, screenBufferSize.X, _currentAttributes, this); + buffer += screenBufferSize.X; } _UpdateSize(); @@ -83,11 +81,9 @@ UINT TextBuffer::TotalRowCount() const noexcept // - const reference to the requested row. Asserts if out of bounds. const ROW& TextBuffer::GetRowByOffset(const size_t index) const { - const size_t totalRows = TotalRowCount(); - // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. - const size_t offsetIndex = (_firstRow + index) % totalRows; - return _storage.at(offsetIndex); + const size_t offsetIndex = (_firstRow + index) % _storage.size(); + return _storage[offsetIndex]; } // Routine Description: @@ -99,11 +95,9 @@ const ROW& TextBuffer::GetRowByOffset(const size_t index) const // - reference to the requested row. Asserts if out of bounds. ROW& TextBuffer::GetRowByOffset(const size_t index) { - const size_t totalRows = TotalRowCount(); - // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. - const size_t offsetIndex = (_firstRow + index) % totalRows; - return _storage.at(offsetIndex); + const size_t offsetIndex = (_firstRow + index) % _storage.size(); + return _storage[offsetIndex]; } // Routine Description: @@ -400,7 +394,7 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt, //Return Value: // - true if we successfully inserted the character // - false otherwise (out of memory) -bool TextBuffer::InsertCharacter(const std::wstring_view chars, +bool TextBuffer::InsertCharacter(const std::wstring_view& chars, const DbcsAttribute dbcsAttribute, const TextAttribute attr) { @@ -670,6 +664,15 @@ const Viewport TextBuffer::GetSize() const noexcept return _size; } +wil::unique_virtualalloc_ptr TextBuffer::_AllocateCharBuffer(const COORD size) +{ + const auto dx = static_cast(size.X); + const auto dy = static_cast(size.Y); + const auto buffer = static_cast(VirtualAlloc(nullptr, dx * dy * sizeof(CharRowCell), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); + THROW_IF_NULL_ALLOC(buffer); + return wil::unique_virtualalloc_ptr{ buffer }; +} + void TextBuffer::_UpdateSize() { _size = Viewport::FromDimensions({ 0, 0 }, { gsl::narrow(_storage.at(0).size()), gsl::narrow(_storage.size()) }); @@ -778,7 +781,7 @@ void TextBuffer::ScrollRows(const SHORT firstRow, const SHORT size, const SHORT // Renumber the IDs now that we've rearranged where the rows sit within the buffer. // Refreshing should also delegate to the UnicodeStorage to re-key all the stored unicode sequences (where applicable). - _RefreshRowIDs(std::nullopt); + _RefreshRowIDs(_size.Width()); } Cursor& TextBuffer::GetCursor() noexcept @@ -863,14 +866,14 @@ COORD TextBuffer::ScreenToBufferPosition(const COORD position) const { // Use shift right to quickly divide the X pos by 2 for double width lines. const SHORT scale = IsDoubleWidthLine(position.Y) ? 1 : 0; - return { position.X >> scale, position.Y }; + return { gsl::narrow_cast(position.X >> scale), position.Y }; } COORD TextBuffer::BufferToScreenPosition(const COORD position) const { // Use shift left to quickly multiply the X pos by 2 for double width lines. const SHORT scale = IsDoubleWidthLine(position.Y) ? 1 : 0; - return { position.X << scale, position.Y }; + return { gsl::narrow_cast(position.X << scale), position.Y }; } // Routine Description: @@ -896,49 +899,43 @@ void TextBuffer::Reset() { RETURN_HR_IF(E_INVALIDARG, newSize.X < 0 || newSize.Y < 0); - try + auto charBuffer = _AllocateCharBuffer(newSize); + const auto currentSize = GetSize().Dimensions(); + const auto attributes = GetCurrentAttributes(); + + SHORT TopRow = 0; // new top row of the screen buffer + if (newSize.Y <= GetCursor().GetPosition().Y) { - const auto currentSize = GetSize().Dimensions(); - const auto attributes = GetCurrentAttributes(); - - SHORT TopRow = 0; // new top row of the screen buffer - if (newSize.Y <= GetCursor().GetPosition().Y) - { - TopRow = GetCursor().GetPosition().Y - newSize.Y + 1; - } - const SHORT TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.Y; - - // rotate rows until the top row is at index 0 - for (int i = 0; i < TopRowIndex; i++) - { - _storage.emplace_back(std::move(_storage.front())); - _storage.erase(_storage.begin()); - } - - _SetFirstRowIndex(0); - - // realloc in the Y direction - // remove rows if we're shrinking - while (_storage.size() > static_cast(newSize.Y)) - { - _storage.pop_back(); - } - // add rows if we're growing - while (_storage.size() < static_cast(newSize.Y)) - { - _storage.emplace_back(static_cast(_storage.size()), newSize.X, attributes, this); - } - - // Now that we've tampered with the row placement, refresh all the row IDs. - // Also take advantage of the row ID refresh loop to resize the rows in the X dimension - // and cleanup the UnicodeStorage characters that might fall outside the resized buffer. - _RefreshRowIDs(newSize.X); - - // Update the cached size value - _UpdateSize(); + TopRow = GetCursor().GetPosition().Y - newSize.Y + 1; } - CATCH_RETURN(); + const SHORT TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.Y; + // rotate rows until the top row is at index 0 + std::rotate(_storage.begin(), _storage.begin() + TopRowIndex, _storage.end()); + _SetFirstRowIndex(0); + + // realloc in the Y direction + // remove rows if we're shrinking + while (_storage.size() > static_cast(newSize.Y)) + { + _storage.pop_back(); + } + // add rows if we're growing + while (_storage.size() < static_cast(newSize.Y)) + { + _storage.emplace_back(static_cast(_storage.size()), nullptr, newSize.X, attributes, this); + } + + // Now that we've tampered with the row placement, refresh all the row IDs. + // Also take advantage of the row ID refresh loop to resize the rows in the X dimension + // and cleanup the UnicodeStorage characters that might fall outside the resized buffer. + _RefreshRowIDs(newSize.X); + _RefreshRowWidth(charBuffer.get(), newSize.X); + + // Update the cached size value + _UpdateSize(); + + _charBuffer = std::move(charBuffer); return S_OK; } @@ -961,7 +958,7 @@ UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept // any high unicode (UnicodeStorage) runs while we're already looping through the rows. // Arguments: // - newRowWidth - Optional new value for the row width. -void TextBuffer::_RefreshRowIDs(std::optional newRowWidth) +void TextBuffer::_RefreshRowIDs(SHORT width) { std::unordered_map rowMap; SHORT i = 0; @@ -975,17 +972,19 @@ void TextBuffer::_RefreshRowIDs(std::optional newRowWidth) // Also update the char row parent pointers as they can get shuffled up in the rotates. it.GetCharRow().UpdateParent(&it); - - // Resize the rows in the X dimension if we have a new width - if (newRowWidth.has_value()) - { - // Realloc in the X direction - THROW_IF_FAILED(it.Resize(newRowWidth.value())); - } } // Give the new mapping to Unicode Storage - _unicodeStorage.Remap(rowMap, newRowWidth); + _unicodeStorage.Remap(rowMap, width); +} + +void TextBuffer::_RefreshRowWidth(CharRowCell* data, size_t width) noexcept +{ + for (auto& it : _storage) + { + it.GetCharRow().Resize(data, width); + data += width; + } } void TextBuffer::_NotifyPaint(const Viewport& viewport) const @@ -1044,7 +1043,7 @@ Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcep // - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar // Return Value: // - the delimiter class for the given char -const DelimiterClass TextBuffer::_GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const +const DelimiterClass TextBuffer::_GetDelimiterClassAt(const COORD pos, const std::wstring_view& wordDelimiters) const { return GetRowByOffset(pos.Y).GetCharRow().DelimiterClassAt(pos.X, wordDelimiters); } @@ -1060,7 +1059,7 @@ const DelimiterClass TextBuffer::_GetDelimiterClassAt(const COORD pos, const std // - limitOptional - (optional) the last possible position in the buffer that can be explored. This can be used to improve performance. // Return Value: // - The COORD for the first character on the "word" (inclusive) -const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode, std::optional limitOptional) const +const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view& wordDelimiters, bool accessibilityMode, std::optional limitOptional) const { // Consider a buffer with this text in it: // " word other " @@ -1110,7 +1109,7 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view // - wordDelimiters - what characters are we considering for the separation of words // Return Value: // - The COORD for the first character on the current/previous READABLE "word" (inclusive) -const COORD TextBuffer::_GetWordStartForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const +const COORD TextBuffer::_GetWordStartForAccessibility(const COORD target, const std::wstring_view& wordDelimiters) const { COORD result = target; const auto bufferSize = GetSize(); @@ -1155,7 +1154,7 @@ const COORD TextBuffer::_GetWordStartForAccessibility(const COORD target, const // - wordDelimiters - what characters are we considering for the separation of words // Return Value: // - The COORD for the first character on the current word or delimiter run (stopped by the left margin) -const COORD TextBuffer::_GetWordStartForSelection(const COORD target, const std::wstring_view wordDelimiters) const +const COORD TextBuffer::_GetWordStartForSelection(const COORD target, const std::wstring_view& wordDelimiters) const { COORD result = target; const auto bufferSize = GetSize(); @@ -1188,7 +1187,7 @@ const COORD TextBuffer::_GetWordStartForSelection(const COORD target, const std: // - limitOptional - (optional) the last possible position in the buffer that can be explored. This can be used to improve performance. // Return Value: // - The COORD for the last character on the "word" (inclusive) -const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode, std::optional limitOptional) const +const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view& wordDelimiters, bool accessibilityMode, std::optional limitOptional) const { // Consider a buffer with this text in it: // " word other " @@ -1226,7 +1225,7 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w // - limit - the last "valid" position in the text buffer (to improve performance) // Return Value: // - The COORD for the first character of the next readable "word". If no next word, return one past the end of the buffer -const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD limit) const +const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view& wordDelimiters, const COORD limit) const { const auto bufferSize{ GetSize() }; COORD result{ target }; @@ -1276,7 +1275,7 @@ const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const st // - wordDelimiters - what characters are we considering for the separation of words // Return Value: // - The COORD for the last character of the current word or delimiter run (stopped by right margin) -const COORD TextBuffer::_GetWordEndForSelection(const COORD target, const std::wstring_view wordDelimiters) const +const COORD TextBuffer::_GetWordEndForSelection(const COORD target, const std::wstring_view& wordDelimiters) const { const auto bufferSize = GetSize(); @@ -1359,7 +1358,7 @@ void TextBuffer::_PruneHyperlinks() // Return Value: // - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary) // - pos - The COORD for the first character on the "word" (inclusive) -bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, std::optional limitOptional) const +bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view& wordDelimiters, std::optional limitOptional) const { // move to the beginning of the next word // NOTE: _GetWordEnd...() returns the exclusive position of the "end of the word" @@ -1385,7 +1384,7 @@ bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimite // Return Value: // - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary) // - pos - The COORD for the first character on the "word" (inclusive) -bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters) const +bool TextBuffer::MoveToPreviousWord(COORD& pos, const std::wstring_view& wordDelimiters) const { // move to the beginning of the current word auto copy{ GetWordStart(pos, wordDelimiters, true) }; @@ -1782,7 +1781,7 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF, // - string containing the generated HTML std::string TextBuffer::GenHTML(const TextAndColor& rows, const int fontHeightPoints, - const std::wstring_view fontFaceName, + const std::wstring_view& fontFaceName, const COLORREF backgroundColor) { try @@ -1845,7 +1844,7 @@ std::string TextBuffer::GenHTML(const TextAndColor& rows, const auto writeAccumulatedChars = [&](bool includeCurrent) { if (col >= startOffset) { - const auto unescapedText = ConvertToA(CP_UTF8, std::wstring_view(rows.text.at(row)).substr(startOffset, col - startOffset + includeCurrent)); + const auto unescapedText = ConvertToA(CP_UTF8, rows.text.at(row).substr(startOffset, col - startOffset + includeCurrent)); for (const auto c : unescapedText) { switch (c) @@ -1971,7 +1970,7 @@ std::string TextBuffer::GenHTML(const TextAndColor& rows, // - htmlTitle - value used in title tag of html header. Used to name the application // Return Value: // - string containing the generated RTF -std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoints, const std::wstring_view fontFaceName, const COLORREF backgroundColor) +std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoints, const std::wstring_view& fontFaceName, const COLORREF backgroundColor) { try { @@ -2033,7 +2032,7 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi const auto writeAccumulatedChars = [&](bool includeCurrent) { if (col >= startOffset) { - const auto unescapedText = ConvertToA(CP_UTF8, std::wstring_view(rows.text.at(row)).substr(startOffset, col - startOffset + includeCurrent)); + const auto unescapedText = ConvertToA(CP_UTF8, rows.text.at(row).substr(startOffset, col - startOffset + includeCurrent)); for (const auto c : unescapedText) { switch (c) @@ -2411,9 +2410,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, // - Adds or updates a hyperlink in our hyperlink table // Arguments: // - The hyperlink URI, the hyperlink id (could be new or old) -void TextBuffer::AddHyperlinkToMap(std::wstring_view uri, uint16_t id) +void TextBuffer::AddHyperlinkToMap(const std::wstring_view& uri, uint16_t id) { - _hyperlinkMap[id] = uri; + _hyperlinkMap.emplace(id, uri); } // Method Description: @@ -2433,28 +2432,31 @@ std::wstring TextBuffer::GetHyperlinkUriFromId(uint16_t id) const // - The user-defined id // Return value: // - The internal hyperlink ID -uint16_t TextBuffer::GetHyperlinkId(std::wstring_view uri, std::wstring_view id) +uint16_t TextBuffer::GetHyperlinkId(const std::wstring_view& uri, const std::wstring_view& id) { uint16_t numericId = 0; if (id.empty()) { // no custom id specified, return our internal count - numericId = _currentHyperlinkId; - ++_currentHyperlinkId; + numericId = _currentHyperlinkId++; } else { - // assign _currentHyperlinkId if the custom id does not already exist - std::wstring newId{ id }; - // hash the URL and add it to the custom ID - GH#7698 - newId += L"%" + std::to_wstring(std::hash{}(uri)); - const auto result = _hyperlinkCustomIdMap.emplace(newId, _currentHyperlinkId); + // We need to use both uri and id for hashing. See GH#7698 + std::wstring key; + key.reserve(uri.size() + id.size()); + key.append(uri); + key.append(id); + + const auto result = _hyperlinkCustomIdMap.emplace(key, _currentHyperlinkId); + numericId = result.first->second; + if (result.second) { // the custom id did not already exist + _hyperlinkCustomIdMapReverse.insert_or_assign(_currentHyperlinkId, key); ++_currentHyperlinkId; } - numericId = (*(result.first)).second; } // _currentHyperlinkId could overflow, make sure its not 0 if (_currentHyperlinkId == 0) @@ -2472,13 +2474,11 @@ uint16_t TextBuffer::GetHyperlinkId(std::wstring_view uri, std::wstring_view id) void TextBuffer::RemoveHyperlinkFromMap(uint16_t id) noexcept { _hyperlinkMap.erase(id); - for (const auto& customIdPair : _hyperlinkCustomIdMap) + + if (auto it = _hyperlinkCustomIdMapReverse.find(id); it != _hyperlinkCustomIdMapReverse.end()) { - if (customIdPair.second == id) - { - _hyperlinkCustomIdMap.erase(customIdPair.first); - break; - } + _hyperlinkCustomIdMap.erase(it->second); + _hyperlinkCustomIdMapReverse.erase(it); } } @@ -2491,12 +2491,9 @@ void TextBuffer::RemoveHyperlinkFromMap(uint16_t id) noexcept // - The custom ID if there was one, empty string otherwise std::wstring TextBuffer::GetCustomIdFromId(uint16_t id) const { - for (auto customIdPair : _hyperlinkCustomIdMap) + if (auto it = _hyperlinkCustomIdMapReverse.find(id); it != _hyperlinkCustomIdMapReverse.end()) { - if (customIdPair.second == id) - { - return customIdPair.first; - } + return it->second; } return {}; } @@ -2510,6 +2507,7 @@ void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other) { _hyperlinkMap = other._hyperlinkMap; _hyperlinkCustomIdMap = other._hyperlinkCustomIdMap; + _hyperlinkCustomIdMapReverse = other._hyperlinkCustomIdMapReverse; _currentHyperlinkId = other._currentHyperlinkId; } @@ -2520,7 +2518,7 @@ void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other) // - The regex pattern // Return value: // - An ID that the caller should associate with the given pattern -const size_t TextBuffer::AddPatternRecognizer(const std::wstring_view regexString) +const size_t TextBuffer::AddPatternRecognizer(const std::wstring_view& regexString) { ++_currentPatternId; _idsAndPatterns.emplace(std::make_pair(_currentPatternId, regexString)); diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index a2b34ed5b..f871e1e2c 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -98,7 +98,7 @@ public: const std::optional limitRight = std::nullopt); bool InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr); - bool InsertCharacter(const std::wstring_view chars, const DbcsAttribute dbcsAttribute, const TextAttribute attr); + bool InsertCharacter(const std::wstring_view& chars, const DbcsAttribute dbcsAttribute, const TextAttribute attr); bool IncrementCursor(); bool NewlineCursor(); @@ -141,10 +141,10 @@ public: Microsoft::Console::Render::IRenderTarget& GetRenderTarget() noexcept; - const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false, std::optional limitOptional = std::nullopt) const; - const COORD GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false, std::optional limitOptional = std::nullopt) const; - bool MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, std::optional limitOptional = std::nullopt) const; - bool MoveToPreviousWord(COORD& pos, const std::wstring_view wordDelimiters) const; + const COORD GetWordStart(const COORD target, const std::wstring_view& wordDelimiters, bool accessibilityMode = false, std::optional limitOptional = std::nullopt) const; + const COORD GetWordEnd(const COORD target, const std::wstring_view& wordDelimiters, bool accessibilityMode = false, std::optional limitOptional = std::nullopt) const; + bool MoveToNextWord(COORD& pos, const std::wstring_view& wordDelimiters, std::optional limitOptional = std::nullopt) const; + bool MoveToPreviousWord(COORD& pos, const std::wstring_view& wordDelimiters) const; const til::point GetGlyphStart(const til::point pos, std::optional limitOptional = std::nullopt) const; const til::point GetGlyphEnd(const til::point pos, bool accessibilityMode = false, std::optional limitOptional = std::nullopt) const; @@ -153,9 +153,9 @@ public: const std::vector GetTextRects(COORD start, COORD end, bool blockSelection, bool bufferCoordinates) const; - void AddHyperlinkToMap(std::wstring_view uri, uint16_t id); + void AddHyperlinkToMap(const std::wstring_view& uri, uint16_t id); std::wstring GetHyperlinkUriFromId(uint16_t id) const; - uint16_t GetHyperlinkId(std::wstring_view uri, std::wstring_view id); + uint16_t GetHyperlinkId(const std::wstring_view& uri, const std::wstring_view& id); void RemoveHyperlinkFromMap(uint16_t id) noexcept; std::wstring GetCustomIdFromId(uint16_t id) const; void CopyHyperlinkMaps(const TextBuffer& OtherBuffer); @@ -176,12 +176,12 @@ public: static std::string GenHTML(const TextAndColor& rows, const int fontHeightPoints, - const std::wstring_view fontFaceName, + const std::wstring_view& fontFaceName, const COLORREF backgroundColor); static std::string GenRTF(const TextAndColor& rows, const int fontHeightPoints, - const std::wstring_view fontFaceName, + const std::wstring_view& fontFaceName, const COLORREF backgroundColor); struct PositionInformation @@ -195,60 +195,49 @@ public: const std::optional lastCharacterViewport, std::optional> positionInfo); - const size_t AddPatternRecognizer(const std::wstring_view regexString); + const size_t AddPatternRecognizer(const std::wstring_view& regexString); void ClearPatternRecognizers() noexcept; void CopyPatterns(const TextBuffer& OtherBuffer); interval_tree::IntervalTree GetPatterns(const size_t firstRow, const size_t lastRow) const; private: + static wil::unique_virtualalloc_ptr _AllocateCharBuffer(const COORD size); + void _UpdateSize(); - Microsoft::Console::Types::Viewport _size; - std::vector _storage; - Cursor _cursor; - - SHORT _firstRow; // indexes top row (not necessarily 0) - - TextAttribute _currentAttributes; - - // storage location for glyphs that can't fit into the buffer normally - UnicodeStorage _unicodeStorage; - - std::unordered_map _hyperlinkMap; - std::unordered_map _hyperlinkCustomIdMap; - uint16_t _currentHyperlinkId; - - void _RefreshRowIDs(std::optional newRowWidth); - - Microsoft::Console::Render::IRenderTarget& _renderTarget; - + void _RefreshRowIDs(SHORT width); + void _RefreshRowWidth(CharRowCell* data, size_t width) noexcept; void _SetFirstRowIndex(const SHORT FirstRowIndex) noexcept; - COORD _GetPreviousFromCursor() const; - void _SetWrapOnCurrentRow(); void _AdjustWrapOnCurrentRow(const bool fSet); - void _NotifyPaint(const Microsoft::Console::Types::Viewport& viewport) const; - // Assist with maintaining proper buffer state for Double Byte character sequences bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute); bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute); - ROW& _GetFirstRow(); ROW& _GetPrevRowNoWrap(const ROW& row); - void _ExpandTextRow(SMALL_RECT& selectionRow) const; - - const DelimiterClass _GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const; - const COORD _GetWordStartForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const; - const COORD _GetWordStartForSelection(const COORD target, const std::wstring_view wordDelimiters) const; - const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD limit) const; - const COORD _GetWordEndForSelection(const COORD target, const std::wstring_view wordDelimiters) const; - + const DelimiterClass _GetDelimiterClassAt(const COORD pos, const std::wstring_view& wordDelimiters) const; + const COORD _GetWordStartForAccessibility(const COORD target, const std::wstring_view& wordDelimiters) const; + const COORD _GetWordStartForSelection(const COORD target, const std::wstring_view& wordDelimiters) const; + const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view& wordDelimiters, const COORD limit) const; + const COORD _GetWordEndForSelection(const COORD target, const std::wstring_view& wordDelimiters) const; void _PruneHyperlinks(); + Microsoft::Console::Render::IRenderTarget& _renderTarget; + wil::unique_virtualalloc_ptr _charBuffer; + std::vector _storage; + std::unordered_map _hyperlinkMap; + std::unordered_map _hyperlinkCustomIdMap; + std::unordered_map _hyperlinkCustomIdMapReverse; std::unordered_map _idsAndPatterns; - size_t _currentPatternId; + TextAttribute _currentAttributes; + UnicodeStorage _unicodeStorage; + Cursor _cursor; + Microsoft::Console::Types::Viewport _size; + uint16_t _currentHyperlinkId{ 1 }; + size_t _currentPatternId{ 0 }; + SHORT _firstRow{ 0 }; // indexes top row (not necessarily 0) #ifdef UNIT_TESTING friend class TextBufferTests; diff --git a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp index 02025be81..0a757cc45 100644 --- a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp +++ b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp @@ -28,25 +28,6 @@ static constexpr bool _IsMouseMessage(UINT uMsg) uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL; } -// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow. -// See microsoft/terminal#2066 for more info. -static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */) noexcept -{ - return false; // glyph is not wide. -} - -static bool _EnsureStaticInitialization() -{ - // use C++11 magic statics to make sure we only do this once. - static bool initialized = []() { - // *** THIS IS A SINGLETON *** - SetGlyphWidthFallback(_IsGlyphWideForceNarrowFallback); - - return true; - }(); - return initialized; -} - LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc( HWND hwnd, UINT uMsg, @@ -176,8 +157,6 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) : _pfnWriteCallback{ nullptr }, _multiClickTime{ 500 } // this will be overwritten by the windows system double-click time { - _EnsureStaticInitialization(); - HINSTANCE hInstance = wil::GetModuleInstanceHandle(); if (RegisterTermClass(hInstance)) @@ -220,7 +199,6 @@ HRESULT HwndTerminal::Initialize() 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()); _UpdateFont(USER_DEFAULT_SCREEN_DPI); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index c287cdbd7..14260e1d6 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -35,25 +35,6 @@ constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds( namespace winrt::Microsoft::Terminal::Control::implementation { - // Helper static function to ensure that all ambiguous-width glyphs are reported as narrow. - // See microsoft/terminal#2066 for more info. - static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */) - { - return false; // glyph is not wide. - } - - static bool _EnsureStaticInitialization() - { - // use C++11 magic statics to make sure we only do this once. - static bool initialized = []() { - // *** THIS IS A SINGLETON *** - SetGlyphWidthFallback(_IsGlyphWideForceNarrowFallback); - - return true; - }(); - return initialized; - } - ControlCore::ControlCore(IControlSettings settings, TerminalConnection::ITerminalConnection connection) : _connection{ connection }, @@ -61,8 +42,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation _desiredFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8 }, _actualFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false } { - _EnsureStaticInitialization(); - _terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>(); // Subscribe to the connection's disconnected event and call our connection closed handlers. diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 9b96f6c7e..e267478b8 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -19,6 +19,7 @@ #include "ControlCore.g.h" #include "../../renderer/base/Renderer.hpp" #include "../../renderer/dx/DxRenderer.hpp" +#include "../../renderer/atlas/AtlasEngine.h" #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 3ce1d8a52..625ab868f 100644 --- a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj @@ -147,6 +147,7 @@ + diff --git a/src/cascadia/TerminalControl/pch.h b/src/cascadia/TerminalControl/pch.h index bac6c29ca..b287bef2c 100644 --- a/src/cascadia/TerminalControl/pch.h +++ b/src/cascadia/TerminalControl/pch.h @@ -51,6 +51,10 @@ #include #include +#include +#include +#include + #include #include diff --git a/src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj b/src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj index 9ced8ea9f..552bd1433 100644 --- a/src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj +++ b/src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj @@ -37,6 +37,9 @@ {3ae13314-1939-4dfa-9c14-38ca0834050c} + + {21d07cec-24b8-458f-bc34-0866833c60b6} + {48d21369-3d7b-4431-9967-24e81292cf62} diff --git a/src/common.build.pre.props b/src/common.build.pre.props index 24e8903d5..5f4f53275 100644 --- a/src/common.build.pre.props +++ b/src/common.build.pre.props @@ -112,7 +112,7 @@ true precomp.h ProgramDatabase - $(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;$(SolutionDir)\dep\Win32K;$(SolutionDir)\oss\boost\boost_1_73_0;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\pcg\include;%(AdditionalIncludeDirectories); + $(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;$(SolutionDir)\dep\Win32K;$(SolutionDir)\oss\boost\boost_1_73_0;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\pcg\include;$(SolutionDir)\oss\robin-hood-hashing;%(AdditionalIncludeDirectories); true false false @@ -174,6 +174,11 @@ + + StreamingSIMDExtensions2 WIN32;%(PreprocessorDefinitions) diff --git a/src/host/HostPackage/HostPackage.wapproj b/src/host/HostPackage/HostPackage.wapproj new file mode 100644 index 000000000..7daa0c469 --- /dev/null +++ b/src/host/HostPackage/HostPackage.wapproj @@ -0,0 +1,115 @@ + + + + 15.0 + + + + Debug + x86 + + + Release + x86 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + Debug + AnyCPU + + + Release + AnyCPU + + + + $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ + + + + 44d35904-4dc6-4eec-86f2-6e3e341c95be + 10.0.20348.0 + 10.0.20348.0 + en-US + True + ..\exe\Host.EXE.vcxproj + False + False + False + True + x64 + 0 + HostPackage_TemporaryKey.pfx + SHA256 + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + Designer + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/host/HostPackage/Images/Square150x150Logo.scale-200.png b/src/host/HostPackage/Images/Square150x150Logo.scale-200.png new file mode 100644 index 000000000..af49fec1a Binary files /dev/null and b/src/host/HostPackage/Images/Square150x150Logo.scale-200.png differ diff --git a/src/host/HostPackage/Images/Square44x44Logo.scale-200.png b/src/host/HostPackage/Images/Square44x44Logo.scale-200.png new file mode 100644 index 000000000..ce342a2ec Binary files /dev/null and b/src/host/HostPackage/Images/Square44x44Logo.scale-200.png differ diff --git a/src/host/HostPackage/Package.appxmanifest b/src/host/HostPackage/Package.appxmanifest new file mode 100644 index 000000000..da97ac924 --- /dev/null +++ b/src/host/HostPackage/Package.appxmanifest @@ -0,0 +1,68 @@ + + + + + + + + OpenConsole + lhecker + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/host/ShellExtension/OpenConsoleShellExt.def b/src/host/ShellExtension/OpenConsoleShellExt.def new file mode 100644 index 000000000..4ef0135f2 --- /dev/null +++ b/src/host/ShellExtension/OpenConsoleShellExt.def @@ -0,0 +1,4 @@ +EXPORTS + DllCanUnloadNow PRIVATE + DllGetActivationFactory PRIVATE + DllGetClassObject PRIVATE diff --git a/src/host/ShellExtension/OpenConsoleShellExt.vcxproj b/src/host/ShellExtension/OpenConsoleShellExt.vcxproj new file mode 100644 index 000000000..d029bdcee --- /dev/null +++ b/src/host/ShellExtension/OpenConsoleShellExt.vcxproj @@ -0,0 +1,69 @@ + + + + {B321ECD6-18E2-4F07-BFB0-B63750CE0CBD} + OpenConsoleShellExt + OpenConsole.ShellExtension + + + DynamicLibrary + Console + + false + + + + + + + + + + + + + + + + + + user32.lib;%(AdditionalDependencies) + + + NotUsing + + + + + <_ContinueOnError Condition="'$(BuildingProject)' == 'true'">true + <_ContinueOnError Condition="'$(BuildingProject)' != 'true'">false + + + + + + + <_PackagingOutputsUnexpanded Include="%(_BuiltProjectOutputGroupOutput.FinalOutputPath)"> + %(_BuiltProjectOutputGroupOutput.TargetPath) + BuiltProjectOutputGroup + $(ProjectName) + + + + + + + <_PackagingOutputsUnexpanded Include="%(_DebugSymbolsProjectOutputGroupOutput.FinalOutputPath)"> + DebugSymbolsProjectOutputGroup + $(ProjectName) + + + + + %(Filename)%(Extension) + + + + \ No newline at end of file diff --git a/src/host/ShellExtension/OpenTerminalHere.cpp b/src/host/ShellExtension/OpenTerminalHere.cpp new file mode 100644 index 000000000..de9d7c02f --- /dev/null +++ b/src/host/ShellExtension/OpenTerminalHere.cpp @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "OpenTerminalHere.h" + +#include + +#include +#include +#include + +#include +#include + +#include + +HRESULT OpenTerminalHere::Invoke(IShellItemArray* psiItemArray, IBindCtx* /*pBindContext*/) +try +{ + std::wstring path; + + if (psiItemArray == nullptr) + { + path = _GetPathFromExplorer(); + if (path.empty()) + { + return S_FALSE; + } + } + else + { + DWORD count; + psiItemArray->GetCount(&count); + + winrt::com_ptr psi; + wil::unique_cotaskmem_string pszName; + RETURN_IF_FAILED(psiItemArray->GetItemAt(0, psi.put())); + RETURN_IF_FAILED(psi->GetDisplayName(SIGDN_FILESYSPATH, &pszName)); + + path = std::wstring{ pszName.get() }; + } + + // Append a "\." to the given path, so that this will work in "C:\" + path.append(L"\\."); + + std::filesystem::path applicationName{ wil::GetModuleFileNameW(wil::GetModuleInstanceHandle()) }; + applicationName.replace_filename(L"..\\Host.EXE\\OpenConsole.exe"); + + std::wstring commandLine; + commandLine.push_back(L'"'); + commandLine.append(applicationName); + commandLine.append(L"\" pwsh.exe"); + + wil::unique_process_information _piClient; + STARTUPINFOEX siEx{ 0 }; + siEx.StartupInfo.cb = sizeof(STARTUPINFOEX); + + RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW( + applicationName.c_str(), // lpApplicationName + commandLine.data(), + nullptr, // lpProcessAttributes + nullptr, // lpThreadAttributes + false, // bInheritHandles + EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags + nullptr, // lpEnvironment + path.data(), + &siEx.StartupInfo, // lpStartupInfo + &_piClient // lpProcessInformation + )); + + return S_OK; +} +CATCH_RETURN() + +HRESULT OpenTerminalHere::GetToolTip(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszInfoTip) +{ + *ppszInfoTip = nullptr; + return E_NOTIMPL; +} + +HRESULT OpenTerminalHere::GetTitle(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszName) +{ + return SHStrDupW(L"Open in OpenConsole", ppszName); +} + +HRESULT OpenTerminalHere::GetState(IShellItemArray* /*psiItemArray*/, BOOL /*fOkToBeSlow*/, EXPCMDSTATE* pCmdState) +{ + *pCmdState = ECS_ENABLED; + return S_OK; +} + +HRESULT OpenTerminalHere::GetIcon(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszIcon) +try +{ + std::filesystem::path path{ wil::GetModuleFileNameW(wil::GetModuleInstanceHandle()) }; + path.replace_filename(L"..\\Host.EXE\\OpenConsole.exe,0"); + return SHStrDupW(path.c_str(), ppszIcon); +} +CATCH_RETURN(); + +HRESULT OpenTerminalHere::GetFlags(EXPCMDFLAGS* pFlags) +{ + *pFlags = ECF_DEFAULT; + return S_OK; +} + +HRESULT OpenTerminalHere::GetCanonicalName(GUID* pguidCommandName) +{ + *pguidCommandName = __uuidof(this); + return S_OK; +} + +HRESULT OpenTerminalHere::EnumSubCommands(IEnumExplorerCommand** ppEnum) +{ + *ppEnum = nullptr; + return E_NOTIMPL; +} + +std::wstring OpenTerminalHere::_GetPathFromExplorer() const +{ + using namespace std; + using namespace winrt; + + wstring path; + HRESULT hr = NOERROR; + + auto hwnd = ::GetForegroundWindow(); + if (hwnd == nullptr) + { + return path; + } + + TCHAR szName[MAX_PATH] = { 0 }; + ::GetClassName(hwnd, szName, MAX_PATH); + if (0 == StrCmp(szName, L"WorkerW") || + 0 == StrCmp(szName, L"Progman")) + { + //special folder: desktop + hr = ::SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, szName); + if (FAILED(hr)) + { + return path; + } + + path = szName; + return path; + } + + if (0 != StrCmp(szName, L"CabinetWClass")) + { + return path; + } + + com_ptr shell; + try + { + shell = create_instance(CLSID_ShellWindows, CLSCTX_ALL); + } + catch (...) + { + //look like try_create_instance is not available no more + } + + if (shell == nullptr) + { + return path; + } + + com_ptr disp; + wil::unique_variant variant; + variant.vt = VT_I4; + + com_ptr browser; + // look for correct explorer window + for (variant.intVal = 0; + shell->Item(variant, disp.put()) == S_OK; + variant.intVal++) + { + com_ptr tmp; + if (FAILED(disp->QueryInterface(tmp.put()))) + { + disp = nullptr; // get rid of DEBUG non-nullptr warning + continue; + } + + HWND tmpHWND = NULL; + hr = tmp->get_HWND(reinterpret_cast(&tmpHWND)); + if (hwnd == tmpHWND) + { + browser = tmp; + disp = nullptr; // get rid of DEBUG non-nullptr warning + break; //found + } + + disp = nullptr; // get rid of DEBUG non-nullptr warning + } + + if (browser) + { + wil::unique_bstr url; + hr = browser->get_LocationURL(&url); + if (FAILED(hr)) + { + return path; + } + + wstring sUrl(url.get(), SysStringLen(url.get())); + DWORD size = MAX_PATH; + hr = ::PathCreateFromUrl(sUrl.c_str(), szName, &size, NULL); + if (SUCCEEDED(hr)) + { + path = szName; + } + } + + return path; +} diff --git a/src/host/ShellExtension/OpenTerminalHere.h b/src/host/ShellExtension/OpenTerminalHere.h new file mode 100644 index 000000000..42631453f --- /dev/null +++ b/src/host/ShellExtension/OpenTerminalHere.h @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +#pragma once + +#include +#include +#include + +using namespace Microsoft::WRL; + +struct __declspec(uuid("74cd627e-aad1-4ee4-9427-7c4db186ee59")) OpenTerminalHere : public RuntimeClass, IExplorerCommand> +{ +#pragma region IExplorerCommand + STDMETHODIMP Invoke(IShellItemArray* psiItemArray, IBindCtx* pBindContext); + STDMETHODIMP GetToolTip(IShellItemArray* psiItemArray, LPWSTR* ppszInfoTip); + STDMETHODIMP GetTitle(IShellItemArray* psiItemArray, LPWSTR* ppszName); + STDMETHODIMP GetState(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState); + STDMETHODIMP GetIcon(IShellItemArray* psiItemArray, LPWSTR* ppszIcon); + STDMETHODIMP GetFlags(EXPCMDFLAGS* pFlags); + STDMETHODIMP GetCanonicalName(GUID* pguidCommandName); + STDMETHODIMP EnumSubCommands(IEnumExplorerCommand** ppEnum); +#pragma endregion + +private: + std::wstring _GetPathFromExplorer() const; +}; + +CoCreatableClass(OpenTerminalHere); diff --git a/src/host/ShellExtension/dllmain.cpp b/src/host/ShellExtension/dllmain.cpp new file mode 100644 index 000000000..1dc6a711c --- /dev/null +++ b/src/host/ShellExtension/dllmain.cpp @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include + +using namespace Microsoft::WRL; + +__control_entrypoint(DllExport) STDAPI DllCanUnloadNow() +{ + return Module::GetModule().Terminate() ? S_OK : S_FALSE; +} + +STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IActivationFactory** factory) +{ + return Module::GetModule().GetActivationFactory(activatableClassId, factory); +} + +_Check_return_ STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv) +{ + return Module::GetModule().GetClassObject(rclsid, riid, ppv); +} + +STDAPI_(BOOL) +DllMain(_In_opt_ HINSTANCE hinst, DWORD reason, _In_opt_ void*) +{ + if (reason == DLL_PROCESS_ATTACH) + { + DisableThreadLibraryCalls(hinst); + } + return TRUE; +} diff --git a/src/host/ShellExtension/packages.config b/src/host/ShellExtension/packages.config new file mode 100644 index 000000000..13c85cc69 --- /dev/null +++ b/src/host/ShellExtension/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/src/host/exe/Host.EXE.vcxproj b/src/host/exe/Host.EXE.vcxproj index 3967d752f..60b662819 100644 --- a/src/host/exe/Host.EXE.vcxproj +++ b/src/host/exe/Host.EXE.vcxproj @@ -46,6 +46,9 @@ {48d21369-3d7b-4431-9967-24e81292cf62} + + {8222900C-8B6C-452A-91AC-BE95DB04B95F} + {1c959542-bac2-4e55-9a6d-13251914cbb9} diff --git a/src/host/getset.cpp b/src/host/getset.cpp index 36f2ebbd8..e1b126184 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -274,8 +274,7 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept const FontInfo& fontInfo = activeScreenInfo.GetCurrentFont(); consoleFontInfoEx.FontFamily = fontInfo.GetFamily(); consoleFontInfoEx.FontWeight = fontInfo.GetWeight(); - - RETURN_IF_FAILED(fontInfo.FillLegacyNameBuffer(gsl::make_span(consoleFontInfoEx.FaceName))); + fontInfo.FillLegacyNameBuffer(consoleFontInfoEx.FaceName); return S_OK; } diff --git a/src/host/globals.h b/src/host/globals.h index a955e991f..3b116ea8e 100644 --- a/src/host/globals.h +++ b/src/host/globals.h @@ -22,10 +22,7 @@ Revision History: #include "ConsoleArguments.hpp" #include "ApiRoutines.h" -#include "../renderer/inc/IRenderData.hpp" -#include "../renderer/inc/IRenderEngine.hpp" -#include "../renderer/inc/IRenderer.hpp" -#include "../renderer/inc/IFontDefaultList.hpp" +#include "../renderer/base/Renderer.hpp" #include "../server/DeviceComm.h" #include "../server/ConDrvDeviceComm.h" @@ -62,7 +59,7 @@ public: std::vector WordDelimiters; - Microsoft::Console::Render::IRenderer* pRender; + Microsoft::Console::Render::Renderer* pRender; Microsoft::Console::Render::IFontDefaultList* pFontDefaultList; diff --git a/src/host/settings.cpp b/src/host/settings.cpp index a9fb5d093..75b34a235 100644 --- a/src/host/settings.cpp +++ b/src/host/settings.cpp @@ -58,7 +58,7 @@ Settings::Settings() : _fInterceptCopyPaste(0), _DefaultForeground(INVALID_COLOR), _DefaultBackground(INVALID_COLOR), - _fUseDx(false), + _fUseDx(0), _fCopyColor(false) { _dwScreenBufferSize.X = 80; @@ -816,7 +816,7 @@ void Settings::SetTerminalScrolling(const bool terminalScrollingEnabled) noexcep // - This is based on user preference and velocity hold back state. // Return Value: // - True means use DirectX renderer. False means use GDI renderer. -bool Settings::GetUseDx() const noexcept +DWORD Settings::GetUseDx() const noexcept { return _fUseDx; } diff --git a/src/host/settings.hpp b/src/host/settings.hpp index e8468da6b..469d82986 100644 --- a/src/host/settings.hpp +++ b/src/host/settings.hpp @@ -186,7 +186,7 @@ public: bool IsTerminalScrolling() const noexcept; void SetTerminalScrolling(const bool terminalScrollingEnabled) noexcept; - bool GetUseDx() const noexcept; + DWORD GetUseDx() const noexcept; bool GetCopyColor() const noexcept; private: @@ -230,7 +230,7 @@ private: bool _fAutoReturnOnNewline; bool _fRenderGridWorldwide; bool _fScreenReversed; - bool _fUseDx; + DWORD _fUseDx; bool _fCopyColor; std::array _colorTable; diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index b91a12873..561422f46 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -771,8 +771,9 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand, // Set up the renderer to be used to calculate the width of a glyph, // should we be unable to figure out its width another way. - auto pfn = std::bind(&Renderer::IsGlyphWideByFont, static_cast(g.pRender), std::placeholders::_1); - SetGlyphWidthFallback(pfn); + SetGlyphWidthFallback([renderer = g.pRender](const std::wstring_view& glyph) -> bool { + return renderer->IsGlyphWideByFont(glyph); + }); } catch (...) { diff --git a/src/host/ut_host/CodepointWidthDetectorTests.cpp b/src/host/ut_host/CodepointWidthDetectorTests.cpp index fdbc775e1..893189504 100644 --- a/src/host/ut_host/CodepointWidthDetectorTests.cpp +++ b/src/host/ut_host/CodepointWidthDetectorTests.cpp @@ -62,7 +62,7 @@ class CodepointWidthDetectorTests } } - static bool FallbackMethod(const std::wstring_view glyph) + static bool FallbackMethod(const std::wstring_view& glyph) { if (glyph.size() < 1) { diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h index b19b42f16..9bfb1f8f6 100644 --- a/src/inc/LibraryIncludes.h +++ b/src/inc/LibraryIncludes.h @@ -73,7 +73,8 @@ // Chromium Numerics (safe math) #pragma warning(push) -#pragma warning(disable:4100) // unreferenced parameter +#pragma warning(disable : 4100) // '...': unreferenced formal parameter +#pragma warning(disable : 26812) // The enum type '...' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). #include #pragma warning(pop) @@ -84,16 +85,15 @@ #define ENABLE_INTSAFE_SIGNED_FUNCTIONS #include -// LibPopCnt - Fast C/C++ bit population count library (on bits in an array) -#include - // Dynamic Bitset (optional dependency on LibPopCnt for perf at bit counting) // Variable-size compressed-storage header-only bit flag storage library. #pragma warning(push) -#pragma warning(disable:4702) // unreachable code +#pragma warning(disable : 4702) // unreachable code #include #pragma warning(pop) +#include + // {fmt}, a C++20-compatible formatting library #include #include diff --git a/src/inc/til/bitmap.h b/src/inc/til/bitmap.h index f4bef68b6..01644f73f 100644 --- a/src/inc/til/bitmap.h +++ b/src/inc/til/bitmap.h @@ -15,11 +15,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" class _bitmap_const_iterator { public: - using iterator_category = typename std::input_iterator_tag; - using value_type = typename const til::rectangle; - using difference_type = typename ptrdiff_t; - using pointer = typename const til::rectangle*; - using reference = typename const til::rectangle&; + using iterator_category = std::input_iterator_tag; + using value_type = const til::rectangle; + using difference_type = ptrdiff_t; + using pointer = const til::rectangle*; + using reference = const til::rectangle&; _bitmap_const_iterator(const dynamic_bitset& values, til::rectangle rc, ptrdiff_t pos) : _values(values), diff --git a/src/inc/til/pair.h b/src/inc/til/pair.h new file mode 100644 index 000000000..37dee0c8e --- /dev/null +++ b/src/inc/til/pair.h @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +namespace til // Terminal Implementation Library. Also: "Today I Learned" +{ + // pair is a simple clone of std::pair, with one difference: + // copy and move constructors and operators are explicitly defaulted. + // This allows pair to be std::is_trivially_copyable, if both T and S are. + // --> pair can be used with memcpy(), unlike std::pair. + template + struct pair + { + using first_type = T; + using second_type = S; + + pair() = default; + + pair(const pair&) = default; + pair& operator=(const pair&) = default; + + pair(pair&&) = default; + pair& operator=(pair&&) = default; + + constexpr pair(const T& first, const S& second) noexcept(std::is_nothrow_copy_constructible_v&& std::is_nothrow_copy_constructible_v) : + first(first), second(second) + { + } + + constexpr pair(T&& first, S&& second) noexcept(std::is_nothrow_constructible_v&& std::is_nothrow_constructible_v) : + first(std::forward(first)), second(std::forward(second)) + { + } + + constexpr void swap(pair& other) noexcept(std::is_nothrow_swappable_v&& std::is_nothrow_swappable_v) + { + if (this != std::addressof(other)) + { + std::swap(first, other.first); + std::swap(second, other.second); + } + } + + first_type first{}; + second_type second{}; + }; + + template + [[nodiscard]] constexpr bool operator==(const pair& lhs, const pair& rhs) + { + return lhs.first == rhs.first && lhs.second == rhs.second; + } + + template + [[nodiscard]] constexpr bool operator!=(const pair& lhs, const pair& rhs) + { + return !(lhs == rhs); + } +}; diff --git a/src/interactivity/onecore/BgfxEngine.cpp b/src/interactivity/onecore/BgfxEngine.cpp index ef9e95112..86146a9fb 100644 --- a/src/interactivity/onecore/BgfxEngine.cpp +++ b/src/interactivity/onecore/BgfxEngine.cpp @@ -253,12 +253,6 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid return S_OK; } -[[nodiscard]] HRESULT BgfxEngine::IsGlyphWideByFont(const std::wstring_view /*glyph*/, _Out_ bool* const pResult) noexcept -{ - *pResult = false; - return S_OK; -} - // Method Description: // - Updates the window's title string. // Does nothing for BGFX. diff --git a/src/interactivity/onecore/BgfxEngine.hpp b/src/interactivity/onecore/BgfxEngine.hpp index 4a01837d9..7bdb12cfa 100644 --- a/src/interactivity/onecore/BgfxEngine.hpp +++ b/src/interactivity/onecore/BgfxEngine.hpp @@ -70,7 +70,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT GetDirtyArea(gsl::span& area) noexcept override; [[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override; - [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override; protected: [[nodiscard]] HRESULT _DoUpdateTitle(_In_ const std::wstring_view newTitle) noexcept override; diff --git a/src/interactivity/win32/window.cpp b/src/interactivity/win32/window.cpp index e5ea81735..9128d511e 100644 --- a/src/interactivity/win32/window.cpp +++ b/src/interactivity/win32/window.cpp @@ -30,6 +30,7 @@ #include "../../renderer/gdi/gdirenderer.hpp" #if TIL_FEATURE_CONHOSTDXENGINE_ENABLED +#include "../../renderer/atlas/AtlasEngine.h" #include "../../renderer/dx/DxRenderer.hpp" #endif @@ -209,16 +210,18 @@ void Window::_UpdateSystemMetrics() const // Ensure we have appropriate system metrics before we start constructing the window. _UpdateSystemMetrics(); - const bool useDx = pSettings->GetUseDx(); + const auto useDx = pSettings->GetUseDx(); GdiEngine* pGdiEngine = nullptr; #if TIL_FEATURE_CONHOSTDXENGINE_ENABLED - [[maybe_unused]] DxEngine* pDxEngine = nullptr; + DxEngine* pDxEngine = nullptr; + AtlasEngine* pAtlasEngine = nullptr; #endif try { -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED - if (useDx) + switch (useDx) { +#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED + case 1: pDxEngine = new DxEngine(); // TODO: MSFT:21255595 make this less gross // Manually set the Dx Engine to Hwnd mode. When we're trying to @@ -227,12 +230,16 @@ void Window::_UpdateSystemMetrics() const // math in the hwnd mode, not the Composition mode. THROW_IF_FAILED(pDxEngine->SetHwnd(nullptr)); g.pRender->AddRenderEngine(pDxEngine); - } - else + break; + case 2: + pAtlasEngine = new AtlasEngine(); + g.pRender->AddRenderEngine(pAtlasEngine); + break; #endif - { + default: pGdiEngine = new GdiEngine(); g.pRender->AddRenderEngine(pGdiEngine); + break; } } catch (...) @@ -242,10 +249,6 @@ void Window::_UpdateSystemMetrics() const if (NT_SUCCESS(status)) { - SCREEN_INFORMATION& siAttached = GetScreenInfo(); - - siAttached.RefreshFontWithRenderer(); - // Save reference to settings _pSettings = pSettings; @@ -324,7 +327,7 @@ void Window::_UpdateSystemMetrics() const _hWnd = hWnd; #if TIL_FEATURE_CONHOSTDXENGINE_ENABLED - if (useDx) + if (pDxEngine) { status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pDxEngine->SetHwnd(hWnd)))); @@ -333,6 +336,10 @@ void Window::_UpdateSystemMetrics() const status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pDxEngine->Enable()))); } } + else if (pAtlasEngine) + { + status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pAtlasEngine->SetHwnd(hWnd)))); + } else #endif { @@ -362,6 +369,7 @@ void Window::_UpdateSystemMetrics() const // Post a window size update so that the new console window will size itself correctly once it's up and // running. This works around chicken & egg cases involving window size calculations having to do with font // sizes, DPI, and non-primary monitors (see MSFT #2367234). + SCREEN_INFORMATION& siAttached = GetScreenInfo(); siAttached.PostUpdateWindowSize(); // Locate window theming modules and try to set the dark mode. diff --git a/src/project.inc b/src/project.inc index 8c3f92206..c14e63c04 100644 --- a/src/project.inc +++ b/src/project.inc @@ -28,6 +28,7 @@ USE_NATIVE_EH = 1 # Compiler Settings # ------------------------------------- +MSC_OPTIMIZATION = /O2 MSC_WARNING_LEVEL = /W4 /WX USER_C_FLAGS = $(USER_C_FLAGS) /utf-8 @@ -43,12 +44,13 @@ INCLUDES= \ $(INCLUDES); \ $(CONSOLE_SRC_PATH)\inc; \ $(CONSOLE_SRC_PATH)\..\..\inc; \ - $(CONSOLE_SRC_PATH)\..\oss\dynamic_bitset; \ - $(CONSOLE_SRC_PATH)\..\oss\libpopcnt; \ + $(CONSOLE_SRC_PATH)\..\oss\boost\boost_1_73_0; \ $(CONSOLE_SRC_PATH)\..\oss\chromium; \ + $(CONSOLE_SRC_PATH)\..\oss\dynamic_bitset; \ $(CONSOLE_SRC_PATH)\..\oss\fmt\include; \ $(CONSOLE_SRC_PATH)\..\oss\interval_tree; \ - $(CONSOLE_SRC_PATH)\..\oss\boost\boost_1_73_0; \ + $(CONSOLE_SRC_PATH)\..\oss\libpopcnt; \ + $(CONSOLE_SRC_PATH)\..\oss\robin-hood-hashing; \ $(MINWIN_INTERNAL_PRIV_SDK_INC_PATH_L); \ $(MINWIN_RESTRICTED_PRIV_SDK_INC_PATH_L); \ $(MINCORE_INTERNAL_PRIV_SDK_INC_PATH_L); \ diff --git a/src/propsheet/registry.cpp b/src/propsheet/registry.cpp index 7c3b7364c..19eb05b53 100644 --- a/src/propsheet/registry.cpp +++ b/src/propsheet/registry.cpp @@ -188,12 +188,13 @@ DWORD GetRegistryValues( if (pStateInfo == nullptr) { - Status = RegistrySerialization::s_QueryValue(hConsoleKey, - CONSOLE_REGISTRY_CURRENTPAGE, - sizeof(dwValue), - REG_DWORD, - (PBYTE)&dwValue, - nullptr); + Status = RegistrySerialization::s_QueryValue( + hConsoleKey, + CONSOLE_REGISTRY_CURRENTPAGE, + sizeof(dwValue), + REG_DWORD, + (PBYTE)&dwValue, + nullptr); if (NT_SUCCESS(Status)) { dwRet = dwValue; @@ -211,9 +212,10 @@ DWORD GetRegistryValues( } else { - Status = RegistrySerialization::s_OpenKey(hConsoleKey, - pStateInfo->OriginalTitle, - &hTitleKey); + Status = RegistrySerialization::s_OpenKey( + hConsoleKey, + pStateInfo->OriginalTitle, + &hTitleKey); if (!NT_SUCCESS(Status)) { RegCloseKey(hConsoleKey); @@ -701,11 +703,12 @@ VOID SetRegistryValues( // // Save the current page. // - LOG_IF_FAILED(RegistrySerialization::s_SetValue(hConsoleKey, - CONSOLE_REGISTRY_CURRENTPAGE, - REG_DWORD, - (BYTE*)&dwPage, - sizeof(dwPage))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_SetValue( + hConsoleKey, + CONSOLE_REGISTRY_CURRENTPAGE, + REG_DWORD, + (BYTE*)&dwPage, + sizeof(dwPage))); // // Open the console title subkey unless we're changing the defaults. @@ -716,9 +719,10 @@ VOID SetRegistryValues( } else { - Status = RegistrySerialization::s_CreateKey(hConsoleKey, - pStateInfo->OriginalTitle, - &hTitleKey); + Status = RegistrySerialization::s_CreateKey( + hConsoleKey, + pStateInfo->OriginalTitle, + &hTitleKey); if (!NT_SUCCESS(Status)) { RegCloseKey(hConsoleKey); @@ -732,30 +736,33 @@ VOID SetRegistryValues( // dwValue = pStateInfo->ScreenAttributes; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_FILLATTR, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_FILLATTR, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->PopupAttributes; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_POPUPATTR, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_POPUPATTR, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); for (i = 0; i < 16; i++) { dwValue = pStateInfo->ColorTable[i]; if (SUCCEEDED(StringCchPrintf(awchBuffer, ARRAYSIZE(awchBuffer), CONSOLE_REGISTRY_COLORTABLE, i))) { - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - awchBuffer, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + awchBuffer, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); } } @@ -764,30 +771,33 @@ VOID SetRegistryValues( // dwValue = pStateInfo->InsertMode; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_INSERTMODE, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_INSERTMODE, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->QuickEdit; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_QUICKEDIT, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_QUICKEDIT, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); FAIL_FAST_IF(!(OEMCP != 0)); if (g_fEastAsianSystem) { dwValue = (DWORD)pStateInfo->CodePage; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_CODEPAGE, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_CODEPAGE, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); } // @@ -796,12 +806,13 @@ VOID SetRegistryValues( dwValue = MAKELONG(pStateInfo->ScreenBufferSize.X, pStateInfo->ScreenBufferSize.Y); - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_BUFFERSIZE, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_BUFFERSIZE, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); // // Save window size @@ -809,12 +820,13 @@ VOID SetRegistryValues( dwValue = MAKELONG(pStateInfo->WindowSize.X, pStateInfo->WindowSize.Y); - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_WINDOWSIZE, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_WINDOWSIZE, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); // // Save window position @@ -822,18 +834,19 @@ VOID SetRegistryValues( if (pStateInfo->AutoPosition) { - LOG_IF_FAILED(RegistrySerialization::s_DeleteValue(hTitleKey, CONSOLE_REGISTRY_WINDOWPOS)); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_DeleteValue(hTitleKey, CONSOLE_REGISTRY_WINDOWPOS)); } else { dwValue = MAKELONG(pStateInfo->WindowPosX, pStateInfo->WindowPosY); - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_WINDOWPOS, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_WINDOWPOS, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); } // @@ -842,107 +855,120 @@ VOID SetRegistryValues( dwValue = MAKELONG(pStateInfo->FontSize.X, pStateInfo->FontSize.Y); - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_FONTSIZE, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_FONTSIZE, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->FontFamily; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_FONTFAMILY, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_FONTFAMILY, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->FontWeight; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_FONTWEIGHT, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_FACENAME, - REG_SZ, - (BYTE*)(pStateInfo->FaceName), - (DWORD)(wcslen(pStateInfo->FaceName) + 1) * sizeof(TCHAR))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_FONTWEIGHT, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_FACENAME, + REG_SZ, + (BYTE*)(pStateInfo->FaceName), + (DWORD)(wcslen(pStateInfo->FaceName) + 1) * sizeof(TCHAR))); // // Save cursor size // dwValue = pStateInfo->CursorSize; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_CURSORSIZE, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_CURSORSIZE, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); // // Save history buffer size and number // dwValue = pStateInfo->HistoryBufferSize; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_HISTORYSIZE, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_HISTORYSIZE, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->NumberOfHistoryBuffers; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_HISTORYBUFS, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_HISTORYBUFS, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->HistoryNoDup; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_HISTORYNODUP, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_HISTORYNODUP, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); // Save per-title V2 console state dwValue = pStateInfo->fWrapText; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_LINEWRAP, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_LINEWRAP, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->fFilterOnPaste; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_FILTERONPASTE, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_FILTERONPASTE, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->fCtrlKeyShortcutsDisabled; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_CTRLKEYSHORTCUTS_DISABLED, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_CTRLKEYSHORTCUTS_DISABLED, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->fLineSelection; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_LINESELECTION, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_LINESELECTION, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->bWindowTransparency; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_WINDOWALPHA, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_WINDOWALPHA, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); SetGlobalRegistryValues(); @@ -954,50 +980,56 @@ VOID SetRegistryValues( { // Save cursor type and color dwValue = pStateInfo->CursorType; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_CURSORTYPE, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_CURSORTYPE, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->CursorColor; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_CURSORCOLOR, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_CURSORCOLOR, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->InterceptCopyPaste; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_INTERCEPTCOPYPASTE, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_INTERCEPTCOPYPASTE, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->TerminalScrolling; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_TERMINALSCROLLING, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_TERMINALSCROLLING, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->DefaultForeground; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_DEFAULTFOREGROUND, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_DEFAULTFOREGROUND, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); dwValue = pStateInfo->DefaultBackground; - LOG_IF_FAILED(RegistrySerialization::s_UpdateValue(hConsoleKey, - hTitleKey, - CONSOLE_REGISTRY_DEFAULTBACKGROUND, - REG_DWORD, - (BYTE*)&dwValue, - sizeof(dwValue))); + LOG_IF_NTSTATUS_FAILED(RegistrySerialization::s_UpdateValue( + hConsoleKey, + hTitleKey, + CONSOLE_REGISTRY_DEFAULTBACKGROUND, + REG_DWORD, + (BYTE*)&dwValue, + sizeof(dwValue))); } // diff --git a/src/propslib/RegistrySerialization.cpp b/src/propslib/RegistrySerialization.cpp index 09995d680..d9e9bcfbe 100644 --- a/src/propslib/RegistrySerialization.cpp +++ b/src/propslib/RegistrySerialization.cpp @@ -63,7 +63,7 @@ const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMa { _RegPropertyType::Dword, CONSOLE_REGISTRY_DEFAULTFOREGROUND, SET_FIELD_AND_SIZE(_DefaultForeground) }, { _RegPropertyType::Dword, CONSOLE_REGISTRY_DEFAULTBACKGROUND, SET_FIELD_AND_SIZE(_DefaultBackground) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_TERMINALSCROLLING, SET_FIELD_AND_SIZE(_TerminalScrolling) }, - { _RegPropertyType::Boolean, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) }, + { _RegPropertyType::Dword, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) } }; @@ -251,7 +251,8 @@ NTSTATUS RegistrySerialization::s_OpenKey(_In_opt_ HKEY const hKey, _In_ PCWSTR [[nodiscard]] NTSTATUS RegistrySerialization::s_DeleteValue(const HKEY hKey, _In_ PCWSTR const pwszValueName) { - return NTSTATUS_FROM_WIN32(RegDeleteKeyValueW(hKey, nullptr, pwszValueName)); + const auto result = RegDeleteKeyValueW(hKey, nullptr, pwszValueName); + return result == ERROR_FILE_NOT_FOUND ? S_OK : NTSTATUS_FROM_WIN32(result); } // Routine Description: diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp new file mode 100644 index 000000000..b54556652 --- /dev/null +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -0,0 +1,1128 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "AtlasEngine.h" + +#include "../../interactivity/win32/CustomWindowMessages.h" + +#include "shader_vs.h" +#include "shader_ps.h" + +#pragma warning(disable : 4100) + +using namespace Microsoft::Console::Render; + +// Like gsl::narrow but living fast and dying young. +// I don't want to handle users passing fonts larger than 65535pt. +template +constexpr T yolo_narrow(U u) noexcept +{ + const auto t = static_cast(u); + if (static_cast(t) != u || std::is_signed_v != std::is_signed_v && t < T{} != u < U{}) + { + FAIL_FAST(); + } + return t; +} + +template +constexpr AtlasEngine::vec2 yolo_vec2(COORD val) noexcept +{ + return { yolo_narrow(val.X), yolo_narrow(val.Y) }; +} + +template +constexpr AtlasEngine::vec2 yolo_vec2(SIZE val) noexcept +{ + return { yolo_narrow(val.cx), yolo_narrow(val.cy) }; +} + +#define getLocaleName(varName) \ + wchar_t varName[LOCALE_NAME_MAX_LENGTH]; \ + getLocaleNameImpl(varName); + +static void getLocaleNameImpl(wchar_t (&localeName)[LOCALE_NAME_MAX_LENGTH]) +{ + if (!GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH)) + { + static constexpr wchar_t fallback[] = L"en-US"; + memcpy(localeName, fallback, sizeof(fallback)); + } + // GetUserDefaultLocaleName can return bullshit locales with trailing underscores. Strip it off. + // See: https://docs.microsoft.com/en-us/windows/win32/intl/locale-names + else if (auto p = wcschr(localeName, L'_')) + { + *p = L'\0'; + } +} + +static uint32_t utf16utf32(const std::wstring_view& str) +{ + uint32_t codepoint; +#pragma warning(suppress : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). + switch (str.size()) + { + case 1: + codepoint = str[0]; + break; + case 2: + codepoint = (str[0] & 0x3FF) << 10; + codepoint |= str[1] & 0x3FF; + codepoint += 0x10000; + break; + default: + codepoint = 0xFFFD; // UNICODE_REPLACEMENT + break; + } + return codepoint; +} + +static uint32_t utf32utf16(uint32_t in, wchar_t (&out)[2]) +{ + if (in < 0x10000) + { + out[0] = static_cast(in); + return 1; + } + + in -= 0x10000; + out[0] = static_cast(in >> 10); + out[1] = static_cast(in & 0x3FF); + return 2; +} + +AtlasEngine::AtlasEngine() +{ + THROW_IF_FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, _uuidof(_sr.d2dFactory), _sr.d2dFactory.put_void())); + THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(_sr.dwriteFactory), _sr.dwriteFactory.put_unknown())); + _sr.isWindows10OrGreater = IsWindows10OrGreater(); + _r.glyphQueue.reserve(64); +} + +#pragma region IRenderEngine + +// StartPaint() is called while the console buffer lock is being held. +// --> Put as little in here as possible. +[[nodiscard]] HRESULT AtlasEngine::StartPaint() noexcept +try +{ + if (_api.hwnd) + { + RECT rect; + LOG_IF_WIN32_BOOL_FALSE(GetClientRect(_api.hwnd, &rect)); + (void)SetWindowSize({ rect.right - rect.left, rect.bottom - rect.top }); + + if (WI_IsFlagSet(_invalidations, invalidation_flags::title)) + { + LOG_IF_WIN32_BOOL_FALSE(PostMessageW(_api.hwnd, CM_UPDATE_TITLE, 0, 0)); + WI_ClearFlag(_invalidations, invalidation_flags::title); + } + } + + // It's important that we invalidate here instead of in Present() with the rest. + // Other functions, those called before Present(), might depend on _r fields. + // But most of the time _invalidations will be ::none, making this very cheap. + if (_invalidations != invalidation_flags::none) + { + FAIL_FAST_IF(_api.sizeInPixel == u16x2{} || _api.cellSize == u16x2{} || _api.cellCount == u16x2{}); + + if (WI_IsFlagSet(_invalidations, invalidation_flags::device)) + { + _createResources(); + WI_ClearFlag(_invalidations, invalidation_flags::device); + } + if (WI_IsFlagSet(_invalidations, invalidation_flags::size)) + { + _recreateSizeDependentResources(); + WI_ClearFlag(_invalidations, invalidation_flags::size); + } + if (WI_IsFlagSet(_invalidations, invalidation_flags::font)) + { + _recreateFontDependentResources(); + WI_ClearFlag(_invalidations, invalidation_flags::font); + } + } + + _rapi.dirtyArea = til::rectangle{ 0u, 0u, static_cast(_api.cellCount.x), static_cast(_api.cellCount.y) }; + return S_OK; +} +catch (const wil::ResultException& exception) +{ + return _handleException(exception); +} +CATCH_RETURN() + +[[nodiscard]] HRESULT AtlasEngine::EndPaint() noexcept +{ + return S_OK; +} + +[[nodiscard]] bool AtlasEngine::RequiresContinuousRedraw() noexcept +{ + return false; +} + +void AtlasEngine::WaitUntilCanRender() noexcept +{ + if (_r.frameLatencyWaitableObject) + { + WaitForSingleObjectEx(_r.frameLatencyWaitableObject.get(), 1000, true); + } + else + { + Sleep(8); + } +} + +// Present() is called without the console buffer lock being held. +// --> Put as much in here as possible. +[[nodiscard]] HRESULT AtlasEngine::Present() noexcept +try +{ + if (!_r.glyphQueue.empty()) + { + for (const auto& pair : _r.glyphQueue) + { + _drawGlyph(pair); + } + _r.glyphQueue.clear(); + } + + // The values the constant buffer depends on are potentially updated after BeginPaint(). + if (WI_IsFlagSet(_invalidations, invalidation_flags::cbuffer)) + { + _updateConstantBuffer(); + WI_ClearFlag(_invalidations, invalidation_flags::cbuffer); + } + + { +#pragma warning(suppress : 26494) // Variable 'mapped' is uninitialized. Always initialize an object (type.5). + D3D11_MAPPED_SUBRESOURCE mapped; + THROW_IF_FAILED(_r.deviceContext->Map(_r.cellBuffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped)); + assert(mapped.RowPitch >= _r.cells.size() * sizeof(cell)); + memcpy(mapped.pData, _r.cells.data(), _r.cells.size() * sizeof(cell)); + _r.deviceContext->Unmap(_r.cellBuffer.get(), 0); + } + + // After Present calls, the back buffer needs to explicitly be + // re-bound to the D3D11 immediate context before it can be used again. + _r.deviceContext->OMSetRenderTargets(1, _r.renderTargetView.addressof(), nullptr); + _r.deviceContext->Draw(3, 0); + + THROW_IF_FAILED(_r.swapChain->Present(1, 0)); + + // On some GPUs with tile based deferred rendering (TBDR) architectures, binding + // RenderTargets that already have contents in them (from previous rendering) incurs a + // cost for having to copy the RenderTarget contents back into tile memory for rendering. + // + // On Windows 10 with DXGI_SWAP_EFFECT_FLIP_DISCARD we get this for free. + if (!_sr.isWindows10OrGreater) + { + _r.deviceContext->DiscardView(_r.renderTargetView.get()); + } + + return S_OK; +} +catch (const wil::ResultException& exception) +{ + return _handleException(exception); +} +CATCH_RETURN() + +[[nodiscard]] HRESULT AtlasEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept +{ + RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); + *pForcePaint = false; + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::ScrollFrame() noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::Invalidate(const SMALL_RECT* const psrRegion) noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::InvalidateSystem(const RECT* const prcDirtyClient) noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::InvalidateSelection(const std::vector& rectangles) noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::InvalidateScroll(const COORD* const pcoordDelta) noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::InvalidateAll() noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::InvalidateCircling(_Out_ bool* const pForcePaint) noexcept +{ + RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); + *pForcePaint = false; + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::InvalidateTitle() noexcept +{ + WI_SetFlag(_invalidations, invalidation_flags::title); + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::PrepareRenderInfo(const RenderFrameInfo& info) noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::ResetLineTransform() noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::PrepareLineTransform(const LineRendition lineRendition, const size_t targetRow, const size_t viewportLeft) noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::PaintBackground() noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::PaintBufferLine(const gsl::span clusters, const COORD coord, const bool fTrimLeft, const bool lineWrapped) noexcept +try +{ + auto data = _getCell(coord.X, coord.Y); + for (const auto& cluster : clusters) + { + const auto codepoint = utf16utf32(cluster.GetText()); + const uint32_t wide = cluster.GetColumns() != 1; + const uint32_t cells = wide + 1; + + auto entry = _rapi.attributes; + entry.codepoint = codepoint; + entry.wide = wide; + + auto& coords = _r.glyphs[entry]; + if (coords[0] == u16x2{}) + { + coords[0] = _allocateAtlasCell(); + if (wide) + { + coords[1] = _allocateAtlasCell(); + } + _r.glyphQueue.emplace_back(entry, coords); + } + + static_assert(sizeof(data->glyphIndex) == sizeof(coords[0])); + + for (uint32_t i = 0; i < cells; ++i) + { + data[i].glyphIndex16 = coords[i]; + data[i].flags = 0; + data[i].color = _rapi.currentColor; + } + +#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). + data += cluster.GetColumns(); + } + + assert(data <= (_r.cells.data() + _r.cells.size())); + return S_OK; +} +CATCH_RETURN() + +[[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLines lines, const COLORREF color, const size_t cchLine, const COORD coordTarget) noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::PaintSelection(const SMALL_RECT rect) noexcept +{ + const auto width = rect.Right - rect.Left; + auto row = _getCell(rect.Left, rect.Top); + + for (auto x = rect.Top, x1 = rect.Bottom; x < x1; ++x, row += _api.cellCount.x) + { + for (auto data = row, dataEnd = row + width; data != dataEnd; ++data) + { + data->flags |= 2; + } + } + + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::PaintCursor(const CursorOptions& options) noexcept +{ + if (options.isOn) + { + auto data = _getCell(options.coordCursor.X, options.coordCursor.Y); + const auto end = std::min(data + options.fIsDoubleWidth + 1, _r.cells.data() + _r.cells.size()); + + for (; data != end; ++data) + { + data->flags |= 1; + } + } + + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::UpdateDrawingBrushes(const TextAttribute& textAttributes, const gsl::not_null pData, const bool usingSoftFont, const bool isSettingDefaultBrushes) noexcept +{ + const auto [fg, bg] = pData->GetAttributeColors(textAttributes); + + if (!isSettingDefaultBrushes) + { + _rapi.currentColor = { fg, bg }; + _rapi.attributes.bold = textAttributes.IsBold(); + _rapi.attributes.italic = textAttributes.IsItalic(); + } + else if (textAttributes.BackgroundIsDefault() && bg != _rapi.backgroundColor) + { + _rapi.backgroundColor = bg; + WI_SetFlag(_invalidations, invalidation_flags::cbuffer); + } + + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, _Out_ FontInfo& fontInfo) noexcept +{ + return UpdateFont(fontInfoDesired, fontInfo, {}, {}); +} + +[[nodiscard]] HRESULT AtlasEngine::UpdateSoftFont(const gsl::span bitPattern, const SIZE cellSize, const size_t centeringHint) noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::UpdateDpi(const int dpi) noexcept +{ + const auto newDPI = yolo_narrow(dpi); + if (_api.dpi != newDPI) + { + _api.dpi = newDPI; + WI_SetFlag(_invalidations, invalidation_flags::font); + } + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::UpdateViewport(const SMALL_RECT srNewViewport) noexcept +{ + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::GetProposedFont(const FontInfoDesired& fontInfoDesired, _Out_ FontInfo& fontInfo, const int dpi) noexcept +{ + const auto scaling = GetScaling(); + const auto coordFontRequested = fontInfoDesired.GetEngineSize(); + wil::unique_hfont hfont; + COORD coordSize; + + if (fontInfoDesired.IsDefaultRasterFont()) + { + hfont.reset(static_cast(GetStockObject(OEM_FIXED_FONT))); + RETURN_HR_IF(E_FAIL, !hfont); + } + else if (fontInfoDesired.GetFaceName() == DEFAULT_RASTER_FONT_FACENAME) + { + // For future reference, here is the engine weighting and internal details on Windows Font Mapping: + // https://msdn.microsoft.com/en-us/library/ms969909.aspx + // More relevant links: + // https://support.microsoft.com/en-us/kb/94646 + + LOGFONTW lf; + lf.lfHeight = yolo_narrow(std::ceil(coordFontRequested.Y * scaling)); + lf.lfWidth = 0; + lf.lfEscapement = 0; + lf.lfOrientation = 0; + lf.lfWeight = fontInfoDesired.GetWeight(); + lf.lfItalic = FALSE; + lf.lfUnderline = FALSE; + lf.lfStrikeOut = FALSE; + lf.lfCharSet = OEM_CHARSET; + lf.lfOutPrecision = OUT_RASTER_PRECIS; + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfQuality = PROOF_QUALITY; + lf.lfPitchAndFamily = FIXED_PITCH | FF_MODERN; + wmemcpy(lf.lfFaceName, DEFAULT_RASTER_FONT_FACENAME, std::size(DEFAULT_RASTER_FONT_FACENAME)); + + hfont.reset(CreateFontIndirectW(&lf)); + RETURN_HR_IF(E_FAIL, !hfont); + } + + if (hfont) + { + wil::unique_hdc hdc(CreateCompatibleDC(nullptr)); + RETURN_HR_IF(E_FAIL, !hdc); + + DeleteObject(SelectObject(hdc.get(), hfont.get())); + + SIZE sz; + RETURN_HR_IF(E_FAIL, !GetTextExtentPoint32W(hdc.get(), L"M", 1, &sz)); + + coordSize.X = yolo_narrow(sz.cx); + coordSize.Y = yolo_narrow(sz.cy); + } + else + { + getLocaleName(localeName); + const auto textFormat = _createTextFormat( + fontInfoDesired.GetFaceName().c_str(), + static_cast(fontInfoDesired.GetWeight()), + DWRITE_FONT_STYLE_NORMAL, + fontInfoDesired.GetEngineSize().Y, + localeName); + + wil::com_ptr textLayout; + RETURN_IF_FAILED(_sr.dwriteFactory->CreateTextLayout(L"M", 1, textFormat.get(), FLT_MAX, FLT_MAX, textLayout.put())); + + DWRITE_TEXT_METRICS metrics; + RETURN_IF_FAILED(textLayout->GetMetrics(&metrics)); + + coordSize.X = yolo_narrow(std::ceil(metrics.width * scaling)); + coordSize.Y = yolo_narrow(std::ceil(metrics.height * scaling)); + } + + fontInfo.SetFromEngine( + fontInfoDesired.GetFaceName(), + fontInfoDesired.GetFamily(), + fontInfoDesired.GetWeight(), + false, + coordSize, + fontInfoDesired.GetEngineSize()); + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::GetDirtyArea(gsl::span& area) noexcept +{ + area = gsl::span{ &_rapi.dirtyArea, 1 }; + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::GetFontSize(_Out_ COORD* const pFontSize) noexcept +{ + RETURN_HR_IF_NULL(E_INVALIDARG, pFontSize); + pFontSize->X = gsl::narrow_cast(_api.cellSize.x); + pFontSize->Y = gsl::narrow_cast(_api.cellSize.y); + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::IsGlyphWideByFont(const std::wstring_view& glyph, _Out_ bool* const pResult) noexcept +{ + RETURN_HR_IF_NULL(E_INVALIDARG, pResult); + + wil::com_ptr textLayout; + RETURN_IF_FAILED(_sr.dwriteFactory->CreateTextLayout(glyph.data(), yolo_narrow(glyph.size()), _getTextFormat(false, false), FLT_MAX, FLT_MAX, textLayout.put())); + + DWRITE_TEXT_METRICS metrics; + RETURN_IF_FAILED(textLayout->GetMetrics(&metrics)); + + *pResult = static_cast(std::ceil(metrics.width)) > _api.cellSize.x; + return S_OK; +} + +[[nodiscard]] HRESULT AtlasEngine::UpdateTitle(const std::wstring_view newTitle) noexcept +{ + return S_OK; +} + +#pragma endregion + +#pragma region DxRenderer + +[[nodiscard]] bool AtlasEngine::GetRetroTerminalEffect() const noexcept +{ + return false; +} + +[[nodiscard]] float AtlasEngine::GetScaling() const noexcept +{ + return static_cast(_api.dpi) / static_cast(USER_DEFAULT_SCREEN_DPI); +} + +[[nodiscard]] HANDLE AtlasEngine::GetSwapChainHandle() +{ + if (!_r.device) + { + _createResources(); + } + return _r.swapChainHandle.get(); +} + +[[nodiscard]] ::Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInCharacters(const ::Microsoft::Console::Types::Viewport& viewInPixels) const noexcept +{ + return ::Microsoft::Console::Types::Viewport::FromDimensions(viewInPixels.Origin(), COORD{ gsl::narrow_cast(viewInPixels.Width() / _api.cellSize.x), gsl::narrow_cast(viewInPixels.Height() / _api.cellSize.y) }); +} + +[[nodiscard]] ::Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInPixels(const ::Microsoft::Console::Types::Viewport& viewInCharacters) const noexcept +{ + return ::Microsoft::Console::Types::Viewport::FromDimensions(viewInCharacters.Origin(), COORD{ gsl::narrow_cast(viewInCharacters.Width() * _api.cellSize.x), gsl::narrow_cast(viewInCharacters.Height() * _api.cellSize.y) }); +} + +void AtlasEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept +{ + _api.antialiasingMode = yolo_narrow(antialiasingMode); + WI_SetFlag(_invalidations, invalidation_flags::font); +} + +void AtlasEngine::SetCallback(std::function pfn) +{ + _api.swapChainChangedCallback = std::move(pfn); +} + +void AtlasEngine::SetDefaultTextBackgroundOpacity(const float opacity) noexcept +{ +} + +void AtlasEngine::SetForceFullRepaintRendering(bool enable) noexcept +{ +} + +[[nodiscard]] HRESULT AtlasEngine::SetHwnd(const HWND hwnd) noexcept +{ + _api.hwnd = hwnd; + return S_OK; +} + +void AtlasEngine::SetPixelShaderPath(std::wstring_view value) noexcept +{ +} + +void AtlasEngine::SetRetroTerminalEffect(bool enable) noexcept +{ +} + +void AtlasEngine::SetSelectionBackground(const COLORREF color, const float alpha) noexcept +{ + const u32 selectionColor = color | static_cast(std::lroundf(alpha * 255.0f)) << 24; + + if (_rapi.selectionColor != selectionColor) + { + _rapi.selectionColor = selectionColor; + WI_SetFlag(_invalidations, invalidation_flags::cbuffer); + } +} + +void AtlasEngine::SetSoftwareRendering(bool enable) noexcept +{ +} + +void AtlasEngine::SetWarningCallback(std::function pfn) +{ +} + +[[nodiscard]] HRESULT AtlasEngine::SetWindowSize(const SIZE pixels) noexcept +{ + // At the time of writing: + // When Win+D is pressed a render pass is initiated. As conhost is in the background, GetClientRect will return {0,0}. + // This isn't a valid value for _api.sizeInPixel and would crash _recreateSizeDependentResources(). + if (const auto newSize = yolo_vec2(pixels); _api.sizeInPixel != newSize && newSize != u16x2{}) + { + _api.sizeInPixel = newSize; + _api.cellCount = _api.sizeInPixel / _api.cellSize; + WI_SetFlag(_invalidations, invalidation_flags::size); + } + + return S_OK; +} + +void AtlasEngine::ToggleShaderEffects() +{ +} + +[[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept +try +{ + RETURN_IF_FAILED(GetProposedFont(fontInfoDesired, fontInfo, _api.dpi)); + + _api.fontSize = fontInfoDesired.GetEngineSize().Y; + _api.fontName = fontInfo.GetFaceName(); + _api.fontWeight = yolo_narrow(fontInfo.GetWeight()); + + WI_SetFlag(_invalidations, invalidation_flags::font); + + if (const auto newSize = yolo_vec2(fontInfo.GetSize()); _api.cellSize != newSize) + { + const auto scaling = GetScaling(); + _api.cellSizeDIP.x = static_cast(newSize.x) / scaling; + _api.cellSizeDIP.y = static_cast(newSize.y) / scaling; + _api.cellSize = newSize; + _api.cellCount = _api.sizeInPixel / _api.cellSize; + WI_SetFlag(_invalidations, invalidation_flags::size); + } + + return S_OK; +} +CATCH_RETURN() + +void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept +{ +} + +#pragma endregion + +[[nodiscard]] HRESULT AtlasEngine::_handleException(const wil::ResultException& exception) noexcept +{ + const auto hr = exception.GetErrorCode(); + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET || hr == D2DERR_RECREATE_TARGET) + { + _r = {}; + WI_SetFlag(_invalidations, invalidation_flags::device); + return E_PENDING; // Indicate a retry to the renderer + } + return hr; +} + +void AtlasEngine::_createResources() +{ +#ifdef NDEBUG + static constexpr +#endif + auto deviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_SINGLETHREADED; + +#ifndef NDEBUG + // DXGI debug messages + enabling D3D11_CREATE_DEVICE_DEBUG if the Windows SDK was installed. + if (const wil::unique_hmodule module{ LoadLibraryExW(L"dxgidebug.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) }) + { + deviceFlags |= D3D11_CREATE_DEVICE_DEBUG; + + const auto DXGIGetDebugInterface = reinterpret_cast(GetProcAddress(module.get(), "DXGIGetDebugInterface")); + THROW_LAST_ERROR_IF(!DXGIGetDebugInterface); + + wil::com_ptr infoQueue; + if (SUCCEEDED(DXGIGetDebugInterface(IID_PPV_ARGS(infoQueue.addressof())))) + { + // I didn't want to link with dxguid.lib just for getting DXGI_DEBUG_ALL. + static constexpr GUID dxgiDebugAll = { 0xe48ae283, 0xda80, 0x490b, { 0x87, 0xe6, 0x43, 0xe9, 0xa9, 0xcf, 0xda, 0x8 } }; + for (const auto severity : std::array{ DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_WARNING }) + { + infoQueue->SetBreakOnSeverity(dxgiDebugAll, severity, true); + } + } + } +#endif // NDEBUG + + // D3D device setup (basically a D3D class factory) + { + wil::com_ptr deviceContext; + + static constexpr std::array driverTypes{ + D3D_DRIVER_TYPE_HARDWARE, + D3D_DRIVER_TYPE_WARP, + }; + static constexpr std::array featureLevels{ + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_11_1, + }; + + HRESULT hr = S_OK; + for (const auto driverType : driverTypes) + { + hr = D3D11CreateDevice( + /* pAdapter */ nullptr, + /* DriverType */ driverType, + /* Software */ nullptr, + /* Flags */ deviceFlags, + /* pFeatureLevels */ featureLevels.data(), + /* FeatureLevels */ gsl::narrow_cast(featureLevels.size()), + /* SDKVersion */ D3D11_SDK_VERSION, + /* ppDevice */ _r.device.put(), + /* pFeatureLevel */ nullptr, + /* ppImmediateContext */ deviceContext.put()); + if (SUCCEEDED(hr)) + { + break; + } + } + THROW_IF_FAILED(hr); + + _r.deviceContext = deviceContext.query(); + } + +#ifndef NDEBUG + // D3D debug messages + if (deviceFlags & D3D11_CREATE_DEVICE_DEBUG) + { + const auto infoQueue = _r.device.query(); + for (const auto severity : std::array{ D3D11_MESSAGE_SEVERITY_CORRUPTION, D3D11_MESSAGE_SEVERITY_ERROR, D3D11_MESSAGE_SEVERITY_WARNING }) + { + infoQueue->SetBreakOnSeverity(severity, true); + } + } +#endif // NDEBUG + + // D3D swap chain setup (the thing that allows us to present frames on the screen) + { + const auto supportsFrameLatencyWaitableObject = IsWindows8Point1OrGreater(); + + // With C++20 we'll finally have designated initializers. + DXGI_SWAP_CHAIN_DESC1 desc{}; + desc.Width = _api.sizeInPixel.x; + desc.Height = _api.sizeInPixel.y; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.BufferCount = 2; + desc.Scaling = DXGI_SCALING_NONE; + desc.SwapEffect = _sr.isWindows10OrGreater ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + desc.Flags = supportsFrameLatencyWaitableObject ? DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT : 0; + + wil::com_ptr dxgiFactory; + THROW_IF_FAILED(CreateDXGIFactory1(IID_PPV_ARGS(dxgiFactory.put()))); + + if (_api.hwnd) + { + if (FAILED(dxgiFactory->CreateSwapChainForHwnd(_r.device.get(), _api.hwnd, &desc, nullptr, nullptr, _r.swapChain.put()))) + { + desc.Scaling = DXGI_SCALING_STRETCH; + THROW_IF_FAILED(dxgiFactory->CreateSwapChainForHwnd(_r.device.get(), _api.hwnd, &desc, nullptr, nullptr, _r.swapChain.put())); + } + } + else + { + // We can't link with dcomp.lib, as dcomp.dll doesn't exist on Windows 7. + const wil::unique_hmodule module{ LoadLibraryExW(L"dcomp.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) }; + THROW_LAST_ERROR_IF(!module); + + const auto DCompositionCreateSurfaceHandle = reinterpret_cast(GetProcAddress(module.get(), "DCompositionCreateSurfaceHandle")); + THROW_LAST_ERROR_IF(!DCompositionCreateSurfaceHandle); + + // As per: https://docs.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-dcompositioncreatesurfacehandle + static constexpr DWORD COMPOSITIONSURFACE_ALL_ACCESS = 0x0003L; + THROW_IF_FAILED(DCompositionCreateSurfaceHandle(COMPOSITIONSURFACE_ALL_ACCESS, nullptr, _r.swapChainHandle.put())); + THROW_IF_FAILED(dxgiFactory.query()->CreateSwapChainForCompositionSurfaceHandle(_r.device.get(), _r.swapChainHandle.get(), &desc, nullptr, _r.swapChain.put())); + } + + if (supportsFrameLatencyWaitableObject) + { + _r.frameLatencyWaitableObject.reset(_r.swapChain.query()->GetFrameLatencyWaitableObject()); + THROW_LAST_ERROR_IF(!_r.frameLatencyWaitableObject); + } + } + + // Our constant buffer will never get resized + { + D3D11_BUFFER_DESC desc{}; + desc.ByteWidth = sizeof(const_buffer); + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + THROW_IF_FAILED(_r.device->CreateBuffer(&desc, nullptr, _r.constantBuffer.put())); + } + + THROW_IF_FAILED(_r.device->CreateVertexShader(shader_vs, sizeof(shader_vs), nullptr, _r.vertexShader.put())); + THROW_IF_FAILED(_r.device->CreatePixelShader(shader_ps, sizeof(shader_ps), nullptr, _r.pixelShader.put())); + + if (_api.swapChainChangedCallback) + { + try + { + _api.swapChainChangedCallback(); + } + CATCH_LOG(); + } + + WI_SetAllFlags(_invalidations, invalidation_flags::size | invalidation_flags::font | invalidation_flags::cbuffer); +} + +void AtlasEngine::_recreateSizeDependentResources() +{ + // ResizeBuffer() docs: + // Before you call ResizeBuffers, ensure that the application releases all references [...]. + // You can use ID3D11DeviceContext::ClearState to ensure that all [internal] references are released. + _r.renderTargetView.reset(); + _r.deviceContext->ClearState(); + + THROW_IF_FAILED(_r.swapChain->ResizeBuffers(0, _api.sizeInPixel.x, _api.sizeInPixel.y, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT)); + + // The RenderTargetView is later used with OMSetRenderTargets + // to tell D3D where stuff is supposed to be rendered at. + { + wil::com_ptr buffer; + THROW_IF_FAILED(_r.swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), buffer.put_void())); + THROW_IF_FAILED(_r.device->CreateRenderTargetView(buffer.get(), nullptr, _r.renderTargetView.put())); + } + + // Tell D3D which parts of the render target will be visible. + // Everything outside of the viewport will be black. + // + // In the future this should cover the entire _api.sizeInPixel.x/_api.sizeInPixel.y. + // The pixel shader should draw the remaining content in the configured background color. + { + D3D11_VIEWPORT viewport{}; + viewport.Width = static_cast(_api.sizeInPixel.x); + viewport.Height = static_cast(_api.sizeInPixel.y); + _r.deviceContext->RSSetViewports(1, &viewport); + } + + if (const auto totalCellCount = _api.cellCount.area(); totalCellCount != _r.cells.size()) + { + // Prevent a memory usage spike, by first deallocating and then allocating. + _r.cells = {}; + // Our render loop heavily relies on memcpy() which is between 1.5x + // and 40x as fast for allocations with an alignment of 32 or greater. + // (AMD Zen1-3 have a rep movsb performance bug for certain unaligned allocations.) + _r.cells = aligned_buffer{ totalCellCount, 32 }; + + D3D11_BUFFER_DESC desc{}; + desc.ByteWidth = _api.cellCount.x * _api.cellCount.y * sizeof(cell); + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; + desc.StructureByteStride = sizeof(cell); + THROW_IF_FAILED(_r.device->CreateBuffer(&desc, nullptr, _r.cellBuffer.put())); + THROW_IF_FAILED(_r.device->CreateShaderResourceView(_r.cellBuffer.get(), nullptr, _r.cellView.put())); + } + + // We have called _r.deviceContext->ClearState() in the beginning and lost all D3D state. + // This forces us to set up everything up from scratch again. + { + _r.deviceContext->VSSetShader(_r.vertexShader.get(), nullptr, 0); + _r.deviceContext->PSSetShader(_r.pixelShader.get(), nullptr, 0); + + // Our vertex shader uses a trick from Bill Bilodeau published in + // "Vertex Shader Tricks" at GDC14 to draw a fullscreen triangle + // without vertex/index buffers. This prepares our context for this. + _r.deviceContext->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr); + _r.deviceContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_UNKNOWN, 0); + _r.deviceContext->IASetInputLayout(nullptr); + _r.deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + _r.deviceContext->PSSetConstantBuffers(0, 1, _r.constantBuffer.addressof()); + + _setShaderResources(); + } + + WI_SetFlag(_invalidations, invalidation_flags::cbuffer); +} + +void AtlasEngine::_recreateFontDependentResources() +{ + { + static constexpr size_t glyphCellsRequired = 16 * 1024; + + // I want my atlas texture to be square. + // Not just so that we can better fit into the current 8k/16k texture size limit, + // but also because that makes inspecting the texture in a debugger easier. + const size_t csx = _api.cellSize.x; + const size_t csy = _api.cellSize.y; + const auto areaRequired = glyphCellsRequired * csx * csy; + // I wrote a integer-based ceil(sqrt()) function but couldn't justify putting it into such a cold path. + const auto pxOptimal = static_cast(std::ceil(std::sqrt(static_cast(areaRequired)))); + // We want to fit whole glyphs into our texture. --> Round up to fit entire glyphs in. + const auto xFit = (pxOptimal + csx - 1) / csx; + const auto yFit = (glyphCellsRequired + xFit - 1) / xFit; + + _r.glyphs = {}; + _r.glyphQueue = {}; + _r.atlasSizeInPixel = _api.cellSize * u16x2{ yolo_narrow(xFit), yolo_narrow(yFit) }; + // The first cell at {0, 0} is always our cursor texture. + // --> The first glyph starts at {1, 0}. + _r.atlasPosition = _api.cellSize * u16x2{ 1, 0 }; + } + + // D3D + { + D3D11_TEXTURE2D_DESC desc{}; + desc.Width = _r.atlasSizeInPixel.x; + desc.Height = _r.atlasSizeInPixel.y; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc = { 1, 0 }; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + THROW_IF_FAILED(_r.device->CreateTexture2D(&desc, nullptr, _r.glyphBuffer.put())); + THROW_IF_FAILED(_r.device->CreateShaderResourceView(_r.glyphBuffer.get(), nullptr, _r.glyphView.put())); + } + { + // We only support regular narrow and wide characters at the moment. + // A width of cellSize.x*2 is thus enough. + D3D11_TEXTURE2D_DESC desc{}; + desc.Width = _api.cellSize.x * 2; + desc.Height = _api.cellSize.y; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc = { 1, 0 }; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + THROW_IF_FAILED(_r.device->CreateTexture2D(&desc, nullptr, _r.glyphScratchpad.put())); + } + + _setShaderResources(); + + // D2D + { + D2D1_RENDER_TARGET_PROPERTIES props{}; + props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT; + props.pixelFormat = { DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED }; + props.dpiX = static_cast(_api.dpi); + props.dpiY = static_cast(_api.dpi); + const auto surface = _r.glyphScratchpad.query(); + THROW_IF_FAILED(_sr.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, _r.d2dRenderTarget.put())); + // We don't really use D2D for anything except DWrite, but it + // can't hurt to ensure that everything it does is pixel aligned. + _r.d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); + _r.d2dRenderTarget->SetTextAntialiasMode(static_cast(_api.antialiasingMode)); + } + { + static constexpr D2D1_COLOR_F color{ 1, 1, 1, 1 }; + wil::com_ptr brush; + THROW_IF_FAILED(_r.d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, brush.put())); + _r.brush = brush.query(); + } + { + getLocaleName(localeName); + for (auto style = 0; style < 2; ++style) + { + for (auto weight = 0; weight < 2; ++weight) + { + _r.textFormats[style][weight] = _createTextFormat( + _api.fontName.c_str(), + weight ? DWRITE_FONT_WEIGHT_BOLD : static_cast(_api.fontWeight), + static_cast(style * DWRITE_FONT_STYLE_ITALIC), + _api.fontSize, + localeName); + } + } + } + + _drawCursor(); + + WI_SetAllFlags(_invalidations, invalidation_flags::cbuffer); +} + +void AtlasEngine::_setShaderResources() const +{ + const std::array resources{ _r.cellView.get(), _r.glyphView.get() }; + _r.deviceContext->PSSetShaderResources(0, gsl::narrow_cast(resources.size()), resources.data()); +} + +void AtlasEngine::_updateConstantBuffer() const +{ + const_buffer data; + data.viewport.x = 0; + data.viewport.y = 0; + data.viewport.z = static_cast(_api.cellCount.x * _api.cellSize.x); + data.viewport.w = static_cast(_api.cellCount.y * _api.cellSize.y); + data.cellSize.x = _api.cellSize.x; + data.cellSize.y = _api.cellSize.y; + data.cellCountX = _api.cellCount.x; + data.backgroundColor = _rapi.backgroundColor; + data.selectionColor = _rapi.selectionColor; + _r.deviceContext->UpdateSubresource(_r.constantBuffer.get(), 0, nullptr, &data, 0, 0); +} + +wil::com_ptr AtlasEngine::_createTextFormat(const wchar_t* fontFamilyName, DWRITE_FONT_WEIGHT fontWeight, DWRITE_FONT_STYLE fontStyle, float fontSize, const wchar_t* localeName) const +{ + wil::com_ptr textFormat; + THROW_IF_FAILED(_sr.dwriteFactory->CreateTextFormat(fontFamilyName, nullptr, fontWeight, fontStyle, DWRITE_FONT_STRETCH_NORMAL, fontSize, localeName, textFormat.addressof())); + textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER); + return textFormat; +} + +AtlasEngine::u16x2 AtlasEngine::_allocateAtlasCell() noexcept +{ + const auto ret = _r.atlasPosition; + + _r.atlasPosition.x += _api.cellSize.x; + if (_r.atlasPosition.x >= _r.atlasSizeInPixel.x) + { + _r.atlasPosition.x = 0; + _r.atlasPosition.y += _api.cellSize.y; + if (_r.atlasPosition.y >= _r.atlasSizeInPixel.y) + { + _r.atlasPosition.x = _api.cellSize.x; + _r.atlasPosition.y = 0; + } + } + + return ret; +} + +void AtlasEngine::_drawGlyph(const til::pair>& pair) const +{ + wchar_t chars[2]; + const auto entry = pair.first; + const auto charsLength = utf32utf16(entry.codepoint, chars); + const auto cells = entry.wide + UINT32_C(1); + const bool bold = entry.bold; + const bool italic = entry.italic; + const auto textFormat = _getTextFormat(bold, italic); + + D2D1_RECT_F rect; + rect.left = 0.0f; + rect.top = 0.0f; + rect.right = static_cast(cells) * _api.cellSizeDIP.x; + rect.bottom = _api.cellSizeDIP.y; + + { + // See D2DFactory::DrawText + wil::com_ptr textLayout; + THROW_IF_FAILED(_sr.dwriteFactory->CreateTextLayout(chars, charsLength, textFormat, rect.right, rect.bottom, textLayout.put())); + + _r.d2dRenderTarget->BeginDraw(); + _r.d2dRenderTarget->Clear(); + _r.d2dRenderTarget->DrawTextLayout({}, textLayout.get(), _r.brush.get(), D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT); + THROW_IF_FAILED(_r.d2dRenderTarget->EndDraw()); + } + + for (uint32_t i = 0; i < cells; ++i) + { + // Specifying NO_OVERWRITE means that the system can assume that existing references to the surface that + // may be in flight on the GPU will not be affected by the update, so the copy can proceed immediately + // (avoiding either a batch flush or the system maintaining multiple copies of the resource behind the scenes). + // + // Since our shader only draws whatever is in the atlas, and since we don't replace glyph cells that are in use, + // we can safely (?) tell the GPU that we don't overwrite parts of our atlas that are in use. + _copyScratchpadCell(i, pair.second[i], D3D11_COPY_NO_OVERWRITE); + } +} + +void AtlasEngine::_drawCursor() const +{ + D2D1_RECT_F rect; + rect.left = 0; + rect.top = _api.cellSizeDIP.y * 0.81f; + rect.right = _api.cellSizeDIP.x; + rect.bottom = _api.cellSizeDIP.y; + + _r.d2dRenderTarget->BeginDraw(); + _r.d2dRenderTarget->Clear(); + _r.d2dRenderTarget->FillRectangle(&rect, _r.brush.get()); + THROW_IF_FAILED(_r.d2dRenderTarget->EndDraw()); + + _copyScratchpadCell(0, {}); +} + +void AtlasEngine::_copyScratchpadCell(uint32_t scratchpadIndex, u16x2 target, uint32_t copyFlags) const +{ + D3D11_BOX box; + box.left = scratchpadIndex * _api.cellSize.x; + box.top = 0; + box.front = 0; + box.right = (scratchpadIndex + 1) * _api.cellSize.x; + box.bottom = _api.cellSize.y; + box.back = 1; + _r.deviceContext->CopySubresourceRegion1(_r.glyphBuffer.get(), 0, target.x, target.y, 0, _r.glyphScratchpad.get(), 0, &box, copyFlags); +} diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h new file mode 100644 index 000000000..fbc9f56c2 --- /dev/null +++ b/src/renderer/atlas/AtlasEngine.h @@ -0,0 +1,344 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include "../../renderer/inc/IRenderEngine.hpp" + +namespace Microsoft::Console::Render +{ + class AtlasEngine final : public IRenderEngine + { + public: + explicit AtlasEngine(); + + AtlasEngine(const AtlasEngine&) = delete; + AtlasEngine& operator=(const AtlasEngine&) = delete; + + // IRenderEngine + [[nodiscard]] HRESULT StartPaint() noexcept override; + [[nodiscard]] HRESULT EndPaint() noexcept override; + [[nodiscard]] bool RequiresContinuousRedraw() noexcept override; + void WaitUntilCanRender() noexcept override; + [[nodiscard]] HRESULT Present() noexcept override; + [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override; + [[nodiscard]] HRESULT ScrollFrame() noexcept override; + [[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override; + [[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override; + [[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override; + [[nodiscard]] HRESULT InvalidateSelection(const std::vector& rectangles) noexcept override; + [[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override; + [[nodiscard]] HRESULT InvalidateAll() noexcept override; + [[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override; + [[nodiscard]] HRESULT InvalidateTitle() noexcept override; + [[nodiscard]] HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept override; + [[nodiscard]] HRESULT ResetLineTransform() noexcept override; + [[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, const size_t targetRow, const size_t viewportLeft) noexcept override; + [[nodiscard]] HRESULT PaintBackground() noexcept override; + [[nodiscard]] HRESULT PaintBufferLine(gsl::span const clusters, const COORD coord, const bool fTrimLeft, const bool lineWrapped) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLines lines, const COLORREF color, const size_t cchLine, const COORD coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override; + [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; + [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const gsl::not_null pData, const bool usingSoftFont, const bool isSettingDefaultBrushes) noexcept override; + [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override; + [[nodiscard]] HRESULT UpdateSoftFont(const gsl::span bitPattern, const SIZE cellSize, const size_t centeringHint) noexcept override; + [[nodiscard]] HRESULT UpdateDpi(const int iDpi) noexcept override; + [[nodiscard]] HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept override; + [[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, const int iDpi) noexcept override; + [[nodiscard]] HRESULT GetDirtyArea(gsl::span& area) noexcept override; + [[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override; + [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view& glyph, _Out_ bool* const pResult) noexcept override; + [[nodiscard]] HRESULT UpdateTitle(const std::wstring_view newTitle) noexcept override; + + // Just for compatibility with DxEngine, but can be removed at some point. + HRESULT Enable() + { + return S_OK; + } + + // DxRenderer - getter + [[nodiscard]] bool GetRetroTerminalEffect() const noexcept; + [[nodiscard]] float GetScaling() const noexcept; + [[nodiscard]] HANDLE GetSwapChainHandle(); + [[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInCharacters(const ::Microsoft::Console::Types::Viewport& viewInPixels) const noexcept; + [[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInPixels(const ::Microsoft::Console::Types::Viewport& viewInCharacters) const noexcept; + // DxRenderer - setter + void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept; + void SetCallback(std::function pfn); + void SetDefaultTextBackgroundOpacity(const float opacity) noexcept; + void SetForceFullRepaintRendering(bool enable) noexcept; + [[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept; + void SetPixelShaderPath(std::wstring_view value) noexcept; + void SetRetroTerminalEffect(bool enable) noexcept; + void SetSelectionBackground(const COLORREF color, const float alpha = 0.5f) noexcept; + void SetSoftwareRendering(bool enable) noexcept; + void SetWarningCallback(std::function pfn); + [[nodiscard]] HRESULT SetWindowSize(const SIZE pixels) noexcept; + void ToggleShaderEffects(); + [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept; + void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept; + + // Some helper classes for the implementation. + // public because I don't want to sprinkle the code with friends. + public: + template + struct aligned_buffer + { + constexpr aligned_buffer() noexcept = default; + + explicit aligned_buffer(size_t size, size_t alignment) : + _data{ THROW_IF_NULL_ALLOC(static_cast(_aligned_malloc(size * sizeof(T), alignment))) }, + _size{ size } + { + } + + ~aligned_buffer() + { + _aligned_free(_data); + } + + aligned_buffer(aligned_buffer&& other) noexcept : + _data{ std::exchange(other._data, nullptr) }, + _size{ std::exchange(other._size, 0) } + { + } + + aligned_buffer& operator=(aligned_buffer&& other) noexcept + { + _aligned_free(_data); + _data = std::exchange(other._data, nullptr); + _size = std::exchange(other._size, 0); + return *this; + } + + T* data() + { + return _data; + } + + size_t size() + { + return _size; + } + + private: + T* _data = nullptr; + size_t _size = 0; + }; + + template + struct vec2 + { + T x{}; + T y{}; + + bool operator==(const vec2& other) const noexcept + { + return memcmp(this, &other, sizeof(vec2)) == 0; + } + + bool operator!=(const vec2& other) const noexcept + { + return memcmp(this, &other, sizeof(vec2)) != 0; + } + + vec2 operator*(const vec2& other) const noexcept + { + return { static_cast(x * other.x), static_cast(y * other.y) }; + } + + vec2 operator/(const vec2& other) const noexcept + { + return { static_cast(x / other.x), static_cast(y / other.y) }; + } + + template + U area() const noexcept + { + return static_cast(x) * static_cast(y); + } + }; + + template + struct vec4 + { + T x{}; + T y{}; + T z{}; + T w{}; + }; + + using u8 = uint8_t; + using u16 = uint16_t; + using u16x2 = vec2; + using u32 = uint32_t; + using u32x2 = vec2; + using f32 = float; + using f32x2 = vec2; + using f32x4 = vec4; + + union glyph_entry + { + uint32_t value; + struct + { + uint32_t codepoint : 20; + uint32_t wide : 1; + uint32_t bold : 1; + uint32_t italic : 1; + }; + + constexpr bool operator==(const glyph_entry& other) const noexcept + { + return value == other.value; + } + }; + + struct glyph_entry_hasher + { + constexpr size_t operator()(glyph_entry entry) const noexcept + { + uint64_t x = entry.value; + x ^= x >> 33; + x *= UINT64_C(0xff51afd7ed558ccd); + x ^= x >> 33; + return static_cast(x); + } + }; + + private: + // D3D constant buffers sizes must be a multiple of 16 bytes. + struct alignas(16) const_buffer + { + f32x4 viewport; + u32x2 cellSize; + u32 cellCountX; + u32 backgroundColor; + u32 selectionColor; +#pragma warning(suppress : 4324) // structure was padded due to alignment specifier + }; + + struct cell + { + union + { + u32 glyphIndex; + u16x2 glyphIndex16; + }; + u32 flags; + u32x2 color; + }; + + enum class invalidation_flags : u8 + { + none = 0, + device = 1 << 0, + size = 1 << 1, + font = 1 << 2, + cbuffer = 1 << 3, + title = 1 << 4, + }; + friend constexpr invalidation_flags operator~(invalidation_flags v) noexcept { return static_cast(~static_cast(v)); } + friend constexpr invalidation_flags operator|(invalidation_flags lhs, invalidation_flags rhs) noexcept { return static_cast(static_cast(lhs) | static_cast(rhs)); } + friend constexpr invalidation_flags operator&(invalidation_flags lhs, invalidation_flags rhs) noexcept { return static_cast(static_cast(lhs) & static_cast(rhs)); } + friend constexpr void operator|=(invalidation_flags& lhs, invalidation_flags rhs) noexcept { lhs = lhs | rhs; } + friend constexpr void operator&=(invalidation_flags& lhs, invalidation_flags rhs) noexcept { lhs = lhs & rhs; } + + // resource handling + [[nodiscard]] HRESULT _handleException(const wil::ResultException& exception) noexcept; + __declspec(noinline) void _createResources(); + __declspec(noinline) void _recreateSizeDependentResources(); + __declspec(noinline) void _recreateFontDependentResources(); + void _setShaderResources() const; + void _updateConstantBuffer() const; + + // text handling + IDWriteTextFormat* _getTextFormat(bool bold, bool italic) const noexcept { return _r.textFormats[italic][bold].get(); } + wil::com_ptr _createTextFormat(const wchar_t* fontFamilyName, DWRITE_FONT_WEIGHT fontWeight, DWRITE_FONT_STYLE fontStyle, float fontSize, const wchar_t* localeName) const; + u16x2 _allocateAtlasCell() noexcept; + void _drawGlyph(const til::pair>& pair) const; + void _drawCursor() const; + void _copyScratchpadCell(uint32_t scratchpadIndex, u16x2 target, uint32_t copyFlags = 0) const; + + template + cell* _getCell(T1 x, T2 y) noexcept + { + return _r.cells.data() + static_cast(_api.cellCount.x) * y + x; + } + + struct static_resources + { + wil::com_ptr d2dFactory; + wil::com_ptr dwriteFactory; + bool isWindows10OrGreater = true; + } _sr; + + struct resources + { + // D3D resources + wil::com_ptr device; + wil::com_ptr deviceContext; + wil::com_ptr swapChain; + wil::unique_handle swapChainHandle; + wil::unique_handle frameLatencyWaitableObject; + wil::com_ptr renderTargetView; + wil::com_ptr vertexShader; + wil::com_ptr pixelShader; + wil::com_ptr constantBuffer; + wil::com_ptr cellBuffer; + wil::com_ptr cellView; + + // D2D resources + wil::com_ptr glyphBuffer; + wil::com_ptr glyphView; + wil::com_ptr glyphScratchpad; + wil::com_ptr d2dRenderTarget; + wil::com_ptr brush; + wil::com_ptr textFormats[2][2]; + + // Resources dependent on _api.sizeInPixel + aligned_buffer cells; + // Resources dependent on _api.cellSize + robin_hood::unordered_flat_map, glyph_entry_hasher> glyphs; + std::vector>> glyphQueue; + u16x2 atlasSizeInPixel; + u16x2 atlasPosition; + } _r; + + struct api_state + { + f32x2 cellSizeDIP; // invalidation_flags::font + u16x2 cellSize; // invalidation_flags::size + u16x2 cellCount; // caches `sizeInPixel / cellSize` + u16x2 sizeInPixel; // invalidation_flags::size + + std::wstring fontName; // invalidation_flags::font|size + u16 fontSize = 0; // invalidation_flags::font|size + u16 fontWeight = DWRITE_FONT_WEIGHT_NORMAL; // invalidation_flags::font + u16 dpi = USER_DEFAULT_SCREEN_DPI; // invalidation_flags::font|size + u16 antialiasingMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; // invalidation_flags::font + + std::function swapChainChangedCallback; + HWND hwnd = nullptr; + } _api; + + struct render_api_state + { + til::rectangle dirtyArea; + u32x2 currentColor{}; + glyph_entry attributes{}; + u32 backgroundColor = ~u32(0); + u32 selectionColor = 0x7fffffff; + } _rapi; + + invalidation_flags _invalidations = invalidation_flags::device; + }; +} diff --git a/src/renderer/atlas/atlas.vcxproj b/src/renderer/atlas/atlas.vcxproj new file mode 100644 index 000000000..3882891b3 --- /dev/null +++ b/src/renderer/atlas/atlas.vcxproj @@ -0,0 +1,49 @@ + + + + {8222900C-8B6C-452A-91AC-BE95DB04B95F} + Win32Proj + atlas + RendererAtlas + ConRenderAtlas + StaticLibrary + + + + + Create + + + + + + + + + + Pixel + 4.1 + shader_ps + + $(OutDir)$(ProjectName)\%(Filename).h + true + /Qstrip_debug /Qstrip_reflect %(AdditionalOptions) + + + Vertex + 4.1 + shader_vs + + $(OutDir)$(ProjectName)\%(Filename).h + true + /Qstrip_debug /Qstrip_reflect %(AdditionalOptions) + + + + + + pch.h + $(OutDir)$(ProjectName)\;%(AdditionalIncludeDirectories) + + + diff --git a/src/renderer/atlas/pch.cpp b/src/renderer/atlas/pch.cpp new file mode 100644 index 000000000..398a99f66 --- /dev/null +++ b/src/renderer/atlas/pch.cpp @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" diff --git a/src/renderer/atlas/pch.h b/src/renderer/atlas/pch.h new file mode 100644 index 000000000..42336681d --- /dev/null +++ b/src/renderer/atlas/pch.h @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +// Dynamic Bitset (optional dependency on LibPopCnt for perf at bit counting) +// Variable-size compressed-storage header-only bit flag storage library. +#pragma warning(push) +#pragma warning(disable : 4702) // unreachable code +#include +#pragma warning(pop) + +// Chromium Numerics (safe math) +#pragma warning(push) +#pragma warning(disable : 4100) // '...': unreferenced formal parameter +#pragma warning(disable : 26812) // The enum type '...' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). +#include +#pragma warning(pop) + +#include "til.h" +#include diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl new file mode 100644 index 000000000..1a8a96d37 --- /dev/null +++ b/src/renderer/atlas/shader_ps.hlsl @@ -0,0 +1,75 @@ +// According to Nvidia's "Understanding Structured Buffer Performance" guide +// one should aim for structures with sizes divisible by 128 bits (16 bytes). +// This prevents elements from spanning cache lines. +struct Cell +{ + uint glyphPos; + uint flags; + uint2 color; +}; + +cbuffer ConstantBuffer : register(b0) +{ + float4 viewport; + uint2 cellSize; + uint cellCountX; + uint backgroundColor; + uint selectionColor; +}; +StructuredBuffer cells : register(t0); +Texture2D glyphs : register(t1); + +float4 decodeRGB(uint i) +{ + uint r = i & 0xff; + uint g = (i >> 8) & 0xff; + uint b = (i >> 16) & 0xff; + uint a = i >> 24; + return float4(r, g, b, a) / 255.0; +} + +uint2 decodeU16x2(uint i) +{ + return uint2(i & 0xffff, i >> 16); +} + +float insideRect(float2 pos, float4 boundaries) +{ + float2 v = step(boundaries.xy, pos) - step(boundaries.zw, pos); + return v.x * v.y; +} + +float4 main(float4 pos: SV_Position): SV_Target +{ + if (!insideRect(pos.xy, viewport)) + { + return decodeRGB(backgroundColor); + } + + uint2 cellIndex = pos.xy / cellSize; + uint2 cellPos = pos.xy % cellSize; + + Cell cell = cells[cellIndex.y * cellCountX + cellIndex.x]; + + uint2 glyphPos = decodeU16x2(cell.glyphPos); + uint2 pixelPos = glyphPos + cellPos; + float4 alpha = glyphs[pixelPos]; + + float3 color = lerp( + decodeRGB(cell.color.y).rgb, + decodeRGB(cell.color.x).rgb, + alpha.rgb + ); + + if (cell.flags & 1) + { + color = abs(glyphs[cellPos].rgb - color); + } + if (cell.flags & 2) + { + float4 sc = decodeRGB(selectionColor); + color = lerp(color, sc.rgb, sc.a); + } + + return float4(color, 1); +} diff --git a/src/renderer/atlas/shader_vs.hlsl b/src/renderer/atlas/shader_vs.hlsl new file mode 100644 index 000000000..c99b0701f --- /dev/null +++ b/src/renderer/atlas/shader_vs.hlsl @@ -0,0 +1,12 @@ +float4 main(uint id : SV_VERTEXID) : SV_POSITION +{ + // The algorithm below is a fast way to generate a full screen triangle, + // published by Bill Bilodeau "Vertex Shader Tricks" at GDC14. + // It covers the entire viewport and is faster for the GPU than a quad/rectangle. + return float4( + float(id / 2) * 4.0 - 1.0, + float(id % 2) * 4.0 - 1.0, + 0.0, + 1.0 + ); +} diff --git a/src/renderer/base/FontInfoBase.cpp b/src/renderer/base/FontInfoBase.cpp index 40891cf49..620786065 100644 --- a/src/renderer/base/FontInfoBase.cpp +++ b/src/renderer/base/FontInfoBase.cpp @@ -7,20 +7,7 @@ #include "../inc/FontInfoBase.hpp" -bool operator==(const FontInfoBase& a, const FontInfoBase& b) -{ - return a._faceName == b._faceName && - a._weight == b._weight && - a._family == b._family && - a._codePage == b._codePage && - a._fDefaultRasterSetFromEngine == b._fDefaultRasterSetFromEngine; -} - -FontInfoBase::FontInfoBase(const std::wstring_view faceName, - const unsigned char family, - const unsigned int weight, - const bool fSetDefaultRasterFont, - const unsigned int codePage) : +FontInfoBase::FontInfoBase(const std::wstring_view& faceName, const unsigned char family, const unsigned int weight, const bool fSetDefaultRasterFont, const unsigned int codePage) noexcept : _faceName(faceName), _family(family), _weight(weight), @@ -30,20 +17,16 @@ FontInfoBase::FontInfoBase(const std::wstring_view faceName, ValidateFont(); } -FontInfoBase::FontInfoBase(const FontInfoBase& fibFont) : - FontInfoBase(fibFont.GetFaceName(), - fibFont.GetFamily(), - fibFont.GetWeight(), - fibFont.WasDefaultRasterSetFromEngine(), - fibFont.GetCodePage()) +bool FontInfoBase::operator==(const FontInfoBase& other) noexcept { + return _faceName == other._faceName && + _weight == other._weight && + _family == other._family && + _codePage == other._codePage && + _fDefaultRasterSetFromEngine == other._fDefaultRasterSetFromEngine; } -FontInfoBase::~FontInfoBase() -{ -} - -unsigned char FontInfoBase::GetFamily() const +unsigned char FontInfoBase::GetFamily() const noexcept { return _family; } @@ -51,22 +34,22 @@ unsigned char FontInfoBase::GetFamily() const // When the default raster font is forced set from the engine, this is how we differentiate it from a simple apply. // Default raster font is internally represented as a blank face name and zeros for weight, family, and size. This is // the hint for the engine to use whatever comes back from GetStockObject(OEM_FIXED_FONT) (at least in the GDI world). -bool FontInfoBase::WasDefaultRasterSetFromEngine() const +bool FontInfoBase::WasDefaultRasterSetFromEngine() const noexcept { return _fDefaultRasterSetFromEngine; } -unsigned int FontInfoBase::GetWeight() const +unsigned int FontInfoBase::GetWeight() const noexcept { return _weight; } -const std::wstring_view FontInfoBase::GetFaceName() const noexcept +const std::wstring& FontInfoBase::GetFaceName() const noexcept { return _faceName; } -unsigned int FontInfoBase::GetCodePage() const +unsigned int FontInfoBase::GetCodePage() const noexcept { return _codePage; } @@ -77,21 +60,15 @@ unsigned int FontInfoBase::GetCodePage() const // Arguments: // - buffer: the buffer into which to copy characters // - size: the size of buffer -HRESULT FontInfoBase::FillLegacyNameBuffer(gsl::span buffer) const -try +void FontInfoBase::FillLegacyNameBuffer(wchar_t (&buffer)[LF_FACESIZE]) const noexcept { - auto toCopy = std::min(buffer.size() - 1, _faceName.size()); - auto last = std::copy(_faceName.cbegin(), _faceName.cbegin() + toCopy, buffer.begin()); - std::fill(last, buffer.end(), L'\0'); - return S_OK; + const auto toCopy = std::min(std::size(buffer) - 1, _faceName.size()); + auto last = std::copy_n(_faceName.data(), toCopy, &buffer[0]); + *last = L'\0'; } -CATCH_RETURN(); // NOTE: this method is intended to only be used from the engine itself to respond what font it has chosen. -void FontInfoBase::SetFromEngine(const std::wstring_view faceName, - const unsigned char family, - const unsigned int weight, - const bool fSetDefaultRasterFont) +void FontInfoBase::SetFromEngine(const std::wstring_view& faceName, const unsigned char family, const unsigned int weight, const bool fSetDefaultRasterFont) noexcept { _faceName = faceName; _family = family; @@ -101,12 +78,12 @@ void FontInfoBase::SetFromEngine(const std::wstring_view faceName, // Internally, default raster font is represented by empty facename, and zeros for weight, family, and size. Since // FontInfoBase doesn't have sizing information, this helper checks everything else. -bool FontInfoBase::IsDefaultRasterFontNoSize() const +bool FontInfoBase::IsDefaultRasterFontNoSize() const noexcept { return (_weight == 0 && _family == 0 && _faceName.empty()); } -void FontInfoBase::ValidateFont() +void FontInfoBase::ValidateFont() noexcept { // If we were given a blank name, it meant raster fonts, which to us is always Terminal. if (!IsDefaultRasterFontNoSize() && s_pFontDefaultList != nullptr) @@ -115,8 +92,7 @@ void FontInfoBase::ValidateFont() if (_faceName == DEFAULT_TT_FONT_FACENAME) { std::wstring defaultFontFace; - if (SUCCEEDED(s_pFontDefaultList->RetrieveDefaultFontNameForCodepage(GetCodePage(), - defaultFontFace))) + if (SUCCEEDED(s_pFontDefaultList->RetrieveDefaultFontNameForCodepage(GetCodePage(), defaultFontFace))) { _faceName = defaultFontFace; @@ -128,14 +104,14 @@ void FontInfoBase::ValidateFont() } } -bool FontInfoBase::IsTrueTypeFont() const +bool FontInfoBase::IsTrueTypeFont() const noexcept { return WI_IsFlagSet(_family, TMPF_TRUETYPE); } Microsoft::Console::Render::IFontDefaultList* FontInfoBase::s_pFontDefaultList; -void FontInfoBase::s_SetFontDefaultList(_In_ Microsoft::Console::Render::IFontDefaultList* const pFontDefaultList) +void FontInfoBase::s_SetFontDefaultList(_In_ Microsoft::Console::Render::IFontDefaultList* const pFontDefaultList) noexcept { s_pFontDefaultList = pFontDefaultList; } diff --git a/src/renderer/base/FontInfoDesired.cpp b/src/renderer/base/FontInfoDesired.cpp index c293d141a..027b68f2d 100644 --- a/src/renderer/base/FontInfoDesired.cpp +++ b/src/renderer/base/FontInfoDesired.cpp @@ -5,13 +5,25 @@ #include "../inc/FontInfoDesired.hpp" -bool operator==(const FontInfoDesired& a, const FontInfoDesired& b) +FontInfoDesired::FontInfoDesired(const std::wstring_view& faceName, const unsigned char family, const unsigned int weight, const COORD coordSizeDesired, const unsigned int codePage) noexcept : + FontInfoBase(faceName, family, weight, false, codePage), + _coordSizeDesired(coordSizeDesired) { - return (static_cast(a) == static_cast(b) && - a._coordSizeDesired == b._coordSizeDesired); } -COORD FontInfoDesired::GetEngineSize() const +FontInfoDesired::FontInfoDesired(const FontInfo& fiFont) noexcept : + FontInfoBase(fiFont), + _coordSizeDesired(fiFont.GetUnscaledSize()) +{ +} + +bool FontInfoDesired::operator==(const FontInfoDesired& other) noexcept +{ + return FontInfoBase::operator==(other) && + _coordSizeDesired == other._coordSizeDesired; +} + +COORD FontInfoDesired::GetEngineSize() const noexcept { COORD coordSize = _coordSizeDesired; if (IsTrueTypeFont()) @@ -22,30 +34,12 @@ COORD FontInfoDesired::GetEngineSize() const return coordSize; } -FontInfoDesired::FontInfoDesired(const std::wstring_view faceName, - const unsigned char family, - const unsigned int weight, - const COORD coordSizeDesired, - const unsigned int codePage) : - FontInfoBase(faceName, family, weight, false, codePage), - _coordSizeDesired(coordSizeDesired) -{ -} - -FontInfoDesired::FontInfoDesired(const FontInfo& fiFont) : - FontInfoBase(fiFont), - _coordSizeDesired(fiFont.GetUnscaledSize()) -{ -} - // This helper determines if this object represents the default raster font. This can either be because internally we're // using the empty facename and zeros for size, weight, and family, or it can be because we were given explicit // dimensions from the engine that were the result of loading the default raster font. See GdiEngine::_GetProposedFont(). -bool FontInfoDesired::IsDefaultRasterFont() const +bool FontInfoDesired::IsDefaultRasterFont() const noexcept { // Either the raster was set from the engine... // OR the face name is empty with a size of 0x0 or 8x12. - return WasDefaultRasterSetFromEngine() || (GetFaceName().empty() && - ((_coordSizeDesired.X == 0 && _coordSizeDesired.Y == 0) || - (_coordSizeDesired.X == 8 && _coordSizeDesired.Y == 12))); + return WasDefaultRasterSetFromEngine() || (GetFaceName().empty() && (_coordSizeDesired == COORD{ 0, 0 } || _coordSizeDesired == COORD{ 8, 12 })); } diff --git a/src/renderer/base/RenderEngineBase.cpp b/src/renderer/base/RenderEngineBase.cpp index a32b4b525..b253006e1 100644 --- a/src/renderer/base/RenderEngineBase.cpp +++ b/src/renderer/base/RenderEngineBase.cpp @@ -7,38 +7,25 @@ using namespace Microsoft::Console; using namespace Microsoft::Console::Render; -RenderEngineBase::RenderEngineBase() : - _titleChanged(false), - _lastFrameTitle(L"") +HRESULT RenderEngineBase::InvalidateTitle() noexcept { -} - -HRESULT RenderEngineBase::InvalidateTitle(const std::wstring_view proposedTitle) noexcept -{ - if (proposedTitle != _lastFrameTitle) - { - _titleChanged = true; - } - + _titleChanged = true; return S_OK; } HRESULT RenderEngineBase::UpdateTitle(const std::wstring_view newTitle) noexcept { - HRESULT hr = S_FALSE; - if (newTitle != _lastFrameTitle) + if (!_titleChanged) { - RETURN_IF_FAILED(_DoUpdateTitle(newTitle)); - _lastFrameTitle = newTitle; - _titleChanged = false; - hr = S_OK; + return S_FALSE; } - return hr; + + RETURN_IF_FAILED(_DoUpdateTitle(newTitle)); + _titleChanged = false; + return S_OK; } -HRESULT RenderEngineBase::UpdateSoftFont(const gsl::span /*bitPattern*/, - const SIZE /*cellSize*/, - const size_t /*centeringHint*/) noexcept +HRESULT RenderEngineBase::UpdateSoftFont(const gsl::span /*bitPattern*/, const SIZE /*cellSize*/, const size_t /*centeringHint*/) noexcept { return S_FALSE; } @@ -53,9 +40,7 @@ HRESULT RenderEngineBase::ResetLineTransform() noexcept return S_FALSE; } -HRESULT RenderEngineBase::PrepareLineTransform(const LineRendition /*lineRendition*/, - const size_t /*targetRow*/, - const size_t /*viewportLeft*/) noexcept +HRESULT RenderEngineBase::PrepareLineTransform(const LineRendition /*lineRendition*/, const size_t /*targetRow*/, const size_t /*viewportLeft*/) noexcept { return S_FALSE; } @@ -74,5 +59,13 @@ HRESULT RenderEngineBase::PrepareLineTransform(const LineRendition /*lineRenditi // - Blocks until the engine is able to render without blocking. void RenderEngineBase::WaitUntilCanRender() noexcept { - // do nothing by default + Sleep(8); +} + +// Routine Description: +// - Uses the currently selected font to determine how wide the given character will be when rendered. +[[nodiscard]] HRESULT RenderEngineBase::IsGlyphWideByFont(const std::wstring_view& /*glyph*/, _Out_ bool* const pResult) noexcept +{ + *pResult = false; + return S_FALSE; } diff --git a/src/renderer/base/fontinfo.cpp b/src/renderer/base/fontinfo.cpp index ef3c37e8c..ac6fd2c28 100644 --- a/src/renderer/base/fontinfo.cpp +++ b/src/renderer/base/fontinfo.cpp @@ -5,19 +5,7 @@ #include "../inc/FontInfo.hpp" -bool operator==(const FontInfo& a, const FontInfo& b) -{ - return (static_cast(a) == static_cast(b) && - a._coordSize == b._coordSize && - a._coordSizeUnscaled == b._coordSizeUnscaled); -} - -FontInfo::FontInfo(const std::wstring_view faceName, - const unsigned char family, - const unsigned int weight, - const COORD coordSize, - const unsigned int codePage, - const bool fSetDefaultRasterFont /* = false */) : +FontInfo::FontInfo(const std::wstring_view& faceName, const unsigned char family, const unsigned int weight, const COORD coordSize, const unsigned int codePage, const bool fSetDefaultRasterFont) noexcept : FontInfoBase(faceName, family, weight, fSetDefaultRasterFont, codePage), _coordSize(coordSize), _coordSizeUnscaled(coordSize), @@ -26,39 +14,29 @@ FontInfo::FontInfo(const std::wstring_view faceName, ValidateFont(); } -FontInfo::FontInfo(const FontInfo& fiFont) : - FontInfoBase(fiFont), - _coordSize(fiFont.GetSize()), - _coordSizeUnscaled(fiFont.GetUnscaledSize()) +bool FontInfo::operator==(const FontInfo& other) noexcept { + return FontInfoBase::operator==(other) && + _coordSize == other._coordSize && + _coordSizeUnscaled == other._coordSizeUnscaled; } -COORD FontInfo::GetUnscaledSize() const +COORD FontInfo::GetUnscaledSize() const noexcept { return _coordSizeUnscaled; } -COORD FontInfo::GetSize() const +COORD FontInfo::GetSize() const noexcept { return _coordSize; } -void FontInfo::SetFromEngine(const std::wstring_view faceName, - const unsigned char family, - const unsigned int weight, - const bool fSetDefaultRasterFont, - const COORD coordSize, - const COORD coordSizeUnscaled) +void FontInfo::SetFromEngine(const std::wstring_view& faceName, const unsigned char family, const unsigned int weight, const bool fSetDefaultRasterFont, const COORD coordSize, const COORD coordSizeUnscaled) noexcept { - FontInfoBase::SetFromEngine(faceName, - family, - weight, - fSetDefaultRasterFont); - + FontInfoBase::SetFromEngine(faceName, family, weight, fSetDefaultRasterFont); _coordSize = coordSize; _coordSizeUnscaled = coordSizeUnscaled; - - _ValidateCoordSize(); + ValidateFont(); } bool FontInfo::GetFallback() const noexcept @@ -71,12 +49,7 @@ void FontInfo::SetFallback(const bool didFallback) noexcept _didFallback = didFallback; } -void FontInfo::ValidateFont() -{ - _ValidateCoordSize(); -} - -void FontInfo::_ValidateCoordSize() +void FontInfo::ValidateFont() noexcept { // a (0,0) font is okay for the default raster font, as we will eventually set the dimensions based on the font GDI // passes back to us. diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 13a72d4ba..847ec003b 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -69,11 +69,6 @@ Renderer::~Renderer() auto tries = maxRetriesForRenderEngine; while (tries > 0) { - if (_destructing) - { - return S_FALSE; - } - const auto hr = _PaintFrameForEngine(pEngine); if (E_PENDING == hr) { @@ -487,7 +482,7 @@ void Renderer::TriggerTitleChange() const auto newTitle = _pData->GetConsoleTitle(); FOREACH_ENGINE(pEngine) { - LOG_IF_FAILED(pEngine->InvalidateTitle(newTitle)); + LOG_IF_FAILED(pEngine->InvalidateTitle()); } _NotifyPaintFrame(); } @@ -593,7 +588,7 @@ bool Renderer::s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSo // - glyph - the utf16 encoded codepoint to test // Return Value: // - True if the codepoint is full-width (two wide), false if it is half-width (one wide). -bool Renderer::IsGlyphWideByFont(const std::wstring_view glyph) +bool Renderer::IsGlyphWideByFont(const std::wstring_view& glyph) { bool fIsFullWidth = false; @@ -761,7 +756,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, // Retrieve the first pattern id auto patternIds = _pData->GetPatternId(target); // Determine whether we're using a soft font. - auto usingSoftFont = s_IsSoftFontChar(it->Chars(), _firstSoftFontChar, _lastSoftFontChar); + auto usingSoftFont = _isSoftFontChar(it->Chars()); // And hold the point where we should start drawing. auto screenPoint = target; @@ -808,7 +803,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, { COORD thisPoint{ screenPoint.X + gsl::narrow(cols), screenPoint.Y }; const auto thisPointPatterns = _pData->GetPatternId(thisPoint); - const auto thisUsingSoftFont = s_IsSoftFontChar(it->Chars(), _firstSoftFontChar, _lastSoftFontChar); + const auto thisUsingSoftFont = _isSoftFontChar(it->Chars()); const auto changedPatternOrFont = patternIds != thisPointPatterns || usingSoftFont != thisUsingSoftFont; if (color != it->TextAttr() || changedPatternOrFont) { diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index 1c1fb01ad..509105b02 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -73,7 +73,7 @@ namespace Microsoft::Console::Render const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) override; - bool IsGlyphWideByFont(const std::wstring_view glyph) override; + bool IsGlyphWideByFont(const std::wstring_view& glyph) override; void EnablePainting() override; void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) override; @@ -108,6 +108,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT _PaintTitle(IRenderEngine* const pEngine); [[nodiscard]] std::optional _GetCursorInfo(); [[nodiscard]] HRESULT _PrepareRenderInfo(_In_ IRenderEngine* const pEngine); + bool _isSoftFontChar(const std::wstring_view& v) const noexcept; std::array _engines{}; IRenderData* _pData = nullptr; // Non-ownership pointer diff --git a/src/renderer/base/thread.cpp b/src/renderer/base/thread.cpp index 8bd9a1bd8..c18b4faf6 100644 --- a/src/renderer/base/thread.cpp +++ b/src/renderer/base/thread.cpp @@ -61,7 +61,7 @@ RenderThread::~RenderThread() // Return Value: // - S_OK if we succeeded, else an HRESULT corresponding to a failure to create // an Event or Thread. -[[nodiscard]] HRESULT RenderThread::Initialize(IRenderer* const pRendererParent) noexcept +[[nodiscard]] HRESULT RenderThread::Initialize(_In_ IRenderer* const pRendererParent) noexcept { _pRenderer = pRendererParent; @@ -213,12 +213,6 @@ DWORD WINAPI RenderThread::_ThreadProc() LOG_IF_FAILED(_pRenderer->PaintFrame()); SetEvent(_hPaintCompletedEvent); - - // extra check before we sleep since it's a "long" activity, relatively speaking. - if (_fKeepRunning) - { - Sleep(s_FrameLimitMilliseconds); - } } return S_OK; diff --git a/src/renderer/base/thread.hpp b/src/renderer/base/thread.hpp index e65bb1771..62ffb42ce 100644 --- a/src/renderer/base/thread.hpp +++ b/src/renderer/base/thread.hpp @@ -37,8 +37,6 @@ namespace Microsoft::Console::Render static DWORD WINAPI s_ThreadProc(_In_ LPVOID lpParameter); DWORD WINAPI _ThreadProc(); - static DWORD const s_FrameLimitMilliseconds = 8; - HANDLE _hThread; HANDLE _hEvent; diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index bbbdb8b20..137f4d804 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -1498,18 +1498,13 @@ CATCH_RETURN() // - See https://docs.microsoft.com/en-us/windows/uwp/gaming/reduce-latency-with-dxgi-1-3-swap-chains. void DxEngine::WaitUntilCanRender() noexcept { - if (!_swapChainFrameLatencyWaitableObject) - { - return; - } + // DxEngine isn't really performant and holds the console lock for at least 20ms per (full) frame. + // Sleeping 8ms per frame thus increases throughput of the concurrently running VtEngine. + Sleep(8); - const auto ret = WaitForSingleObjectEx( - _swapChainFrameLatencyWaitableObject.get(), - 1000, // 1 second timeout (shouldn't ever occur) - true); - if (ret != WAIT_OBJECT_0) + if (_swapChainFrameLatencyWaitableObject) { - LOG_WIN32_MSG(ret, "Waiting for swap chain frame latency waitable object returned error or timeout."); + WaitForSingleObjectEx(_swapChainFrameLatencyWaitableObject.get(), 1000, true); } } @@ -2135,7 +2130,7 @@ CATCH_RETURN(); // - 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 +[[nodiscard]] HRESULT DxEngine::IsGlyphWideByFont(const std::wstring_view& glyph, _Out_ bool* const pResult) noexcept try { RETURN_HR_IF_NULL(E_INVALIDARG, pResult); diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp index de9c69fd0..625f09097 100644 --- a/src/renderer/dx/DxRenderer.hpp +++ b/src/renderer/dx/DxRenderer.hpp @@ -119,7 +119,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT GetDirtyArea(gsl::span& area) noexcept override; [[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override; - [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override; + [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view& glyph, _Out_ bool* const pResult) noexcept override; [[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInCharacters(const ::Microsoft::Console::Types::Viewport& viewInPixels) noexcept; [[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInPixels(const ::Microsoft::Console::Types::Viewport& viewInCharacters) noexcept; diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index 2e09f374d..646890a0b 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -78,7 +78,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT GetDirtyArea(gsl::span& area) noexcept override; [[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override; - [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override; + [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view& glyph, _Out_ bool* const pResult) noexcept override; protected: [[nodiscard]] HRESULT _DoUpdateTitle(_In_ const std::wstring_view newTitle) noexcept override; diff --git a/src/renderer/gdi/math.cpp b/src/renderer/gdi/math.cpp index a127021ef..fe355167a 100644 --- a/src/renderer/gdi/math.cpp +++ b/src/renderer/gdi/math.cpp @@ -38,7 +38,7 @@ using namespace Microsoft::Console::Render; // - pResult - receives return value, True if it is full-width (2 wide). False if it is half-width (1 wide). // Return Value: // - S_OK -[[nodiscard]] HRESULT GdiEngine::IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept +[[nodiscard]] HRESULT GdiEngine::IsGlyphWideByFont(const std::wstring_view& glyph, _Out_ bool* const pResult) noexcept { bool isFullWidth = false; diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index 04a5df7d5..722577c8e 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -610,7 +610,7 @@ GdiEngine::~GdiEngine() // NOTE: not using what GDI gave us because some fonts don't quite roundtrip (e.g. MS Gothic and VL Gothic) lf.lfPitchAndFamily = (FIXED_PITCH | FF_MODERN); - RETURN_IF_FAILED(FontDesired.FillLegacyNameBuffer(gsl::make_span(lf.lfFaceName))); + FontDesired.FillLegacyNameBuffer(lf.lfFaceName); // Create font. hFont.reset(CreateFontIndirectW(&lf)); diff --git a/src/renderer/inc/FontInfo.hpp b/src/renderer/inc/FontInfo.hpp index 56065881e..9e40b5557 100644 --- a/src/renderer/inc/FontInfo.hpp +++ b/src/renderer/inc/FontInfo.hpp @@ -28,40 +28,21 @@ Author(s): class FontInfo : public FontInfoBase { public: - FontInfo(const std::wstring_view faceName, - const unsigned char family, - const unsigned int weight, - const COORD coordSize, - const unsigned int codePage, - const bool fSetDefaultRasterFont = false); + FontInfo(const std::wstring_view& faceName, const unsigned char family, const unsigned int weight, const COORD coordSize, const unsigned int codePage, const bool fSetDefaultRasterFont = false) noexcept; - FontInfo(const FontInfo& fiFont); - - COORD GetSize() const; - COORD GetUnscaledSize() const; - - void SetFromEngine(const std::wstring_view faceName, - const unsigned char family, - const unsigned int weight, - const bool fSetDefaultRasterFont, - const COORD coordSize, - const COORD coordSizeUnscaled); + bool operator==(const FontInfo& other) noexcept; + COORD GetSize() const noexcept; + COORD GetUnscaledSize() const noexcept; + void SetFromEngine(const std::wstring_view& faceName, const unsigned char family, const unsigned int weight, const bool fSetDefaultRasterFont, const COORD coordSize, const COORD coordSizeUnscaled) noexcept; bool GetFallback() const noexcept; void SetFallback(const bool didFallback) noexcept; - - void ValidateFont(); - - friend bool operator==(const FontInfo& a, const FontInfo& b); + void ValidateFont() noexcept; private: - void _ValidateCoordSize(); - COORD _coordSize; COORD _coordSizeUnscaled; bool _didFallback; }; -bool operator==(const FontInfo& a, const FontInfo& b); - // SET AND UNSET CONSOLE_OEMFONT_DISPLAY unless we can get rid of the stupid recoding in the conhost side. diff --git a/src/renderer/inc/FontInfoBase.hpp b/src/renderer/inc/FontInfoBase.hpp index aa9f3cddb..292b6d6f2 100644 --- a/src/renderer/inc/FontInfoBase.hpp +++ b/src/renderer/inc/FontInfoBase.hpp @@ -26,40 +26,25 @@ static constexpr wchar_t DEFAULT_RASTER_FONT_FACENAME[]{ L"Terminal" }; class FontInfoBase { public: - FontInfoBase(const std::wstring_view faceName, - const unsigned char family, - const unsigned int weight, - const bool fSetDefaultRasterFont, - const unsigned int uiCodePage); + FontInfoBase(const std::wstring_view& faceName, const unsigned char family, const unsigned int weight, const bool fSetDefaultRasterFont, const unsigned int uiCodePage) noexcept; - FontInfoBase(const FontInfoBase& fibFont); + bool operator==(const FontInfoBase& other) noexcept; - ~FontInfoBase(); - - unsigned char GetFamily() const; - unsigned int GetWeight() const; - const std::wstring_view GetFaceName() const noexcept; - unsigned int GetCodePage() const; - - HRESULT FillLegacyNameBuffer(gsl::span buffer) const; - - bool IsTrueTypeFont() const; - - void SetFromEngine(const std::wstring_view faceName, - const unsigned char family, - const unsigned int weight, - const bool fSetDefaultRasterFont); - - bool WasDefaultRasterSetFromEngine() const; - void ValidateFont(); + unsigned char GetFamily() const noexcept; + unsigned int GetWeight() const noexcept; + const std::wstring& GetFaceName() const noexcept; + unsigned int GetCodePage() const noexcept; + void FillLegacyNameBuffer(wchar_t (&buffer)[LF_FACESIZE]) const noexcept; + bool IsTrueTypeFont() const noexcept; + void SetFromEngine(const std::wstring_view& faceName, const unsigned char family, const unsigned int weight, const bool fSetDefaultRasterFont) noexcept; + bool WasDefaultRasterSetFromEngine() const noexcept; + void ValidateFont() noexcept; static Microsoft::Console::Render::IFontDefaultList* s_pFontDefaultList; - static void s_SetFontDefaultList(_In_ Microsoft::Console::Render::IFontDefaultList* const pFontDefaultList); - - friend bool operator==(const FontInfoBase& a, const FontInfoBase& b); + static void s_SetFontDefaultList(_In_ Microsoft::Console::Render::IFontDefaultList* const pFontDefaultList) noexcept; protected: - bool IsDefaultRasterFontNoSize() const; + bool IsDefaultRasterFontNoSize() const noexcept; private: std::wstring _faceName; @@ -68,5 +53,3 @@ private: unsigned int _codePage; bool _fDefaultRasterSetFromEngine; }; - -bool operator==(const FontInfoBase& a, const FontInfoBase& b); diff --git a/src/renderer/inc/FontInfoDesired.hpp b/src/renderer/inc/FontInfoDesired.hpp index a680f1856..2157162af 100644 --- a/src/renderer/inc/FontInfoDesired.hpp +++ b/src/renderer/inc/FontInfoDesired.hpp @@ -24,21 +24,14 @@ Author(s): class FontInfoDesired : public FontInfoBase { public: - FontInfoDesired(const std::wstring_view faceName, - const unsigned char family, - const unsigned int weight, - const COORD coordSizeDesired, - const unsigned int uiCodePage); + FontInfoDesired(const std::wstring_view& faceName, const unsigned char family, const unsigned int weight, const COORD coordSizeDesired, const unsigned int uiCodePage) noexcept; + FontInfoDesired(const FontInfo& fiFont) noexcept; - FontInfoDesired(const FontInfo& fiFont); + bool operator==(const FontInfoDesired& other) noexcept; - COORD GetEngineSize() const; - bool IsDefaultRasterFont() const; - - friend bool operator==(const FontInfoDesired& a, const FontInfoDesired& b); + COORD GetEngineSize() const noexcept; + bool IsDefaultRasterFont() const noexcept; private: COORD _coordSizeDesired; }; - -bool operator==(const FontInfoDesired& a, const FontInfoDesired& b); diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index 4dd69be8a..f0343b70d 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -44,27 +44,22 @@ namespace Microsoft::Console::Render }; using GridLineSet = til::enumset; - virtual ~IRenderEngine() = 0; - - protected: IRenderEngine() = default; IRenderEngine(const IRenderEngine&) = default; IRenderEngine(IRenderEngine&&) = default; + + virtual ~IRenderEngine() = 0; + IRenderEngine& operator=(const IRenderEngine&) = default; IRenderEngine& operator=(IRenderEngine&&) = default; - public: [[nodiscard]] virtual HRESULT StartPaint() noexcept = 0; [[nodiscard]] virtual HRESULT EndPaint() noexcept = 0; - [[nodiscard]] virtual bool RequiresContinuousRedraw() noexcept = 0; virtual void WaitUntilCanRender() noexcept = 0; [[nodiscard]] virtual HRESULT Present() noexcept = 0; - [[nodiscard]] virtual HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept = 0; - [[nodiscard]] virtual HRESULT ScrollFrame() noexcept = 0; - [[nodiscard]] virtual HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept = 0; [[nodiscard]] virtual HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept = 0; [[nodiscard]] virtual HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept = 0; @@ -72,48 +67,24 @@ namespace Microsoft::Console::Render [[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept = 0; [[nodiscard]] virtual HRESULT InvalidateAll() noexcept = 0; [[nodiscard]] virtual HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept = 0; - - [[nodiscard]] virtual HRESULT InvalidateTitle(const std::wstring_view proposedTitle) noexcept = 0; - + [[nodiscard]] virtual HRESULT InvalidateTitle() noexcept = 0; [[nodiscard]] virtual HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept = 0; - [[nodiscard]] virtual HRESULT ResetLineTransform() noexcept = 0; - [[nodiscard]] virtual HRESULT PrepareLineTransform(const LineRendition lineRendition, - const size_t targetRow, - const size_t viewportLeft) noexcept = 0; - + [[nodiscard]] virtual HRESULT PrepareLineTransform(const LineRendition lineRendition, const size_t targetRow, const size_t viewportLeft) noexcept = 0; [[nodiscard]] virtual HRESULT PaintBackground() noexcept = 0; - [[nodiscard]] virtual HRESULT PaintBufferLine(gsl::span const clusters, - const COORD coord, - const bool fTrimLeft, - const bool lineWrapped) noexcept = 0; - [[nodiscard]] virtual HRESULT PaintBufferGridLines(const GridLineSet lines, - const COLORREF color, - const size_t cchLine, - const COORD coordTarget) noexcept = 0; + [[nodiscard]] virtual HRESULT PaintBufferLine(gsl::span const clusters, const COORD coord, const bool fTrimLeft, const bool lineWrapped) noexcept = 0; + [[nodiscard]] virtual HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const COORD coordTarget) noexcept = 0; [[nodiscard]] virtual HRESULT PaintSelection(const SMALL_RECT rect) noexcept = 0; - [[nodiscard]] virtual HRESULT PaintCursor(const CursorOptions& options) noexcept = 0; - - [[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, - const gsl::not_null pData, - const bool usingSoftFont, - const bool isSettingDefaultBrushes) noexcept = 0; - [[nodiscard]] virtual HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, - _Out_ FontInfo& FontInfo) noexcept = 0; - [[nodiscard]] virtual HRESULT UpdateSoftFont(const gsl::span bitPattern, - const SIZE cellSize, - const size_t centeringHint) noexcept = 0; + [[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const gsl::not_null pData, const bool usingSoftFont, const bool isSettingDefaultBrushes) noexcept = 0; + [[nodiscard]] virtual HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept = 0; + [[nodiscard]] virtual HRESULT UpdateSoftFont(const gsl::span bitPattern, const SIZE cellSize, const size_t centeringHint) noexcept = 0; [[nodiscard]] virtual HRESULT UpdateDpi(const int iDpi) noexcept = 0; [[nodiscard]] virtual HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept = 0; - - [[nodiscard]] virtual HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, - _Out_ FontInfo& FontInfo, - const int iDpi) noexcept = 0; - + [[nodiscard]] virtual HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, const int iDpi) noexcept = 0; [[nodiscard]] virtual HRESULT GetDirtyArea(gsl::span& area) noexcept = 0; [[nodiscard]] virtual HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept = 0; - [[nodiscard]] virtual HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept = 0; + [[nodiscard]] virtual HRESULT IsGlyphWideByFont(const std::wstring_view& glyph, _Out_ bool* const pResult) noexcept = 0; [[nodiscard]] virtual HRESULT UpdateTitle(const std::wstring_view newTitle) noexcept = 0; }; diff --git a/src/renderer/inc/IRenderer.hpp b/src/renderer/inc/IRenderer.hpp index a3894bdb7..8d4f0c3e6 100644 --- a/src/renderer/inc/IRenderer.hpp +++ b/src/renderer/inc/IRenderer.hpp @@ -58,7 +58,7 @@ namespace Microsoft::Console::Render const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) = 0; - virtual bool IsGlyphWideByFont(const std::wstring_view glyph) = 0; + virtual bool IsGlyphWideByFont(const std::wstring_view& glyph) = 0; virtual void EnablePainting() = 0; virtual void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) = 0; diff --git a/src/renderer/inc/RenderEngineBase.hpp b/src/renderer/inc/RenderEngineBase.hpp index 6ad59fec3..709d5409c 100644 --- a/src/renderer/inc/RenderEngineBase.hpp +++ b/src/renderer/inc/RenderEngineBase.hpp @@ -24,40 +24,27 @@ namespace Microsoft::Console::Render class RenderEngineBase : public IRenderEngine { public: + RenderEngineBase() = default; ~RenderEngineBase() = 0; - - protected: - RenderEngineBase(); RenderEngineBase(const RenderEngineBase&) = default; RenderEngineBase(RenderEngineBase&&) = default; RenderEngineBase& operator=(const RenderEngineBase&) = default; RenderEngineBase& operator=(RenderEngineBase&&) = default; - public: - [[nodiscard]] HRESULT InvalidateTitle(const std::wstring_view proposedTitle) noexcept override; - + [[nodiscard]] HRESULT InvalidateTitle() noexcept override; [[nodiscard]] HRESULT UpdateTitle(const std::wstring_view newTitle) noexcept override; - - [[nodiscard]] HRESULT UpdateSoftFont(const gsl::span bitPattern, - const SIZE cellSize, - const size_t centeringHint) noexcept override; - + [[nodiscard]] HRESULT UpdateSoftFont(const gsl::span bitPattern, const 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 size_t targetRow, - const size_t viewportLeft) noexcept override; - + [[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, const size_t targetRow, const size_t viewportLeft) noexcept override; [[nodiscard]] virtual bool RequiresContinuousRedraw() noexcept override; - void WaitUntilCanRender() noexcept override; + [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view& glyph, _Out_ bool* const pResult) noexcept override; protected: [[nodiscard]] virtual HRESULT _DoUpdateTitle(const std::wstring_view newTitle) noexcept = 0; - bool _titleChanged; - std::wstring _lastFrameTitle; + bool _titleChanged = false; }; inline Microsoft::Console::Render::RenderEngineBase::~RenderEngineBase() {} diff --git a/src/renderer/uia/UiaRenderer.cpp b/src/renderer/uia/UiaRenderer.cpp index f3206e79d..f9c003753 100644 --- a/src/renderer/uia/UiaRenderer.cpp +++ b/src/renderer/uia/UiaRenderer.cpp @@ -261,6 +261,13 @@ CATCH_RETURN(); return S_OK; } +// RenderEngineBase defines a WaitUntilCanRender() that sleeps for 8ms to throttle rendering. +// But UiaEngine is never the only the engine running. Overriding this function prevents +// us from sleeping 16ms per frame, when the other engine also sleeps for 8ms. +void UiaEngine::WaitUntilCanRender() noexcept +{ +} + // Routine Description: // - Used to perform longer running presentation steps outside the lock so the // other threads can continue. @@ -453,18 +460,6 @@ CATCH_RETURN(); return S_FALSE; } -// 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 UiaEngine::IsGlyphWideByFont(const std::wstring_view /*glyph*/, _Out_ bool* const /*pResult*/) noexcept -{ - return S_FALSE; -} - // Method Description: // - Updates the window's title string. // - Currently unused by this renderer. diff --git a/src/renderer/uia/UiaRenderer.hpp b/src/renderer/uia/UiaRenderer.hpp index 8c438f055..27e83135a 100644 --- a/src/renderer/uia/UiaRenderer.hpp +++ b/src/renderer/uia/UiaRenderer.hpp @@ -36,12 +36,10 @@ namespace Microsoft::Console::Render // IRenderEngine Members [[nodiscard]] HRESULT StartPaint() noexcept override; [[nodiscard]] HRESULT EndPaint() noexcept override; + void WaitUntilCanRender() noexcept override; [[nodiscard]] HRESULT Present() noexcept override; - [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override; - [[nodiscard]] HRESULT ScrollFrame() noexcept override; - [[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override; [[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override; [[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override; @@ -49,30 +47,18 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override; [[nodiscard]] HRESULT InvalidateAll() noexcept override; [[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override; - [[nodiscard]] HRESULT PaintBackground() noexcept override; - [[nodiscard]] HRESULT PaintBufferLine(gsl::span const clusters, - COORD const coord, - bool const fTrimLeft, - const bool lineWrapped) noexcept override; + [[nodiscard]] HRESULT PaintBufferLine(gsl::span const clusters, COORD const coord, bool const fTrimLeft, const bool lineWrapped) noexcept override; [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override; - [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; - - [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, - const gsl::not_null pData, - const bool usingSoftFont, - const bool isSettingDefaultBrushes) noexcept override; - [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo) noexcept override; - [[nodiscard]] HRESULT UpdateDpi(int const iDpi) noexcept override; + [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const gsl::not_null pData, const bool usingSoftFont, const bool isSettingDefaultBrushes) noexcept override; + [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override; + [[nodiscard]] HRESULT UpdateDpi(const int iDpi) noexcept override; [[nodiscard]] HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept override; - - [[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override; - + [[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, const int iDpi) noexcept override; [[nodiscard]] HRESULT GetDirtyArea(gsl::span& area) noexcept override; [[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override; - [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override; protected: [[nodiscard]] HRESULT _DoUpdateTitle(const std::wstring_view newTitle) noexcept override; diff --git a/src/renderer/vt/math.cpp b/src/renderer/vt/math.cpp index 548343993..fec399aac 100644 --- a/src/renderer/vt/math.cpp +++ b/src/renderer/vt/math.cpp @@ -23,20 +23,6 @@ using namespace Microsoft::Console::Types; return S_OK; } -// Routine Description: -// - Uses the currently selected font to determine how wide the given character will be when rendered. -// - NOTE: Only supports determining half-width/full-width status for CJK-type languages (e.g. is it 1 character wide or 2. a.k.a. is it a rectangle or square.) -// Arguments: -// - glyph - utf16 encoded codepoint to check -// - pResult - receives return value, True if it is full-width (2 wide). False if it is half-width (1 wide). -// Return Value: -// - S_FALSE: This is unsupported by the VT Renderer and should use another engine's value. -[[nodiscard]] HRESULT VtEngine::IsGlyphWideByFont(const std::wstring_view /*glyph*/, _Out_ bool* const pResult) noexcept -{ - *pResult = false; - return S_FALSE; -} - // Routine Description: // - Performs a "CombineRect" with the "OR" operation. // - Basically extends the existing rect outward to also encompass the passed-in region. diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index 1965034db..803b46056 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -88,7 +88,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT GetDirtyArea(gsl::span& area) noexcept override; [[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override; - [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override; [[nodiscard]] HRESULT SuppressResizeRepaint() noexcept; diff --git a/src/renderer/wddmcon/WddmConRenderer.cpp b/src/renderer/wddmcon/WddmConRenderer.cpp index 1ee11d741..731369df6 100644 --- a/src/renderer/wddmcon/WddmConRenderer.cpp +++ b/src/renderer/wddmcon/WddmConRenderer.cpp @@ -402,12 +402,6 @@ RECT WddmConEngine::GetDisplaySize() return S_OK; } -[[nodiscard]] HRESULT WddmConEngine::IsGlyphWideByFont(const std::wstring_view /*glyph*/, _Out_ bool* const pResult) noexcept -{ - *pResult = false; - return S_OK; -} - // Method Description: // - Updates the window's title string. // Does nothing for WddmCon. diff --git a/src/renderer/wddmcon/WddmConRenderer.hpp b/src/renderer/wddmcon/WddmConRenderer.hpp index 0da9f5cba..696f2eca0 100644 --- a/src/renderer/wddmcon/WddmConRenderer.hpp +++ b/src/renderer/wddmcon/WddmConRenderer.hpp @@ -62,7 +62,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT GetDirtyArea(gsl::span& area) noexcept override; [[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override; - [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override; protected: [[nodiscard]] HRESULT _DoUpdateTitle(_In_ const std::wstring_view newTitle) noexcept override; diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 51392bed9..dd4761c28 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -1325,7 +1325,8 @@ void StateMachine::_EventCsiParam(const wchar_t wch) // - wch - Character that triggered the event // Return Value: // - -void StateMachine::_EventOscParam(const wchar_t wch) noexcept +#pragma warning(suppress : 26440) // Function ... can be declared 'noexcept' (f.6). +void StateMachine::_EventOscParam(const wchar_t wch) { _trace.TraceOnEvent(L"OscParam"); if (_isOscTerminator(wch)) @@ -1577,7 +1578,8 @@ void StateMachine::_EventDcsEntry(const wchar_t wch) // - wch - Character that triggered the event // Return Value: // - -void StateMachine::_EventDcsIgnore() noexcept +#pragma warning(suppress : 26440) // Function ... can be declared 'noexcept' (f.6). +void StateMachine::_EventDcsIgnore(const wchar_t /*wch*/) { _trace.TraceOnEvent(L"DcsIgnore"); _ActionIgnore(); @@ -1697,7 +1699,8 @@ void StateMachine::_EventDcsPassThrough(const wchar_t wch) // - wch - Character that triggered the event // Return Value: // - -void StateMachine::_EventSosPmApcString(const wchar_t /*wch*/) noexcept +#pragma warning(suppress : 26440) // Function ... can be declared 'noexcept' (f.6). +void StateMachine::_EventSosPmApcString(const wchar_t /*wch*/) { _trace.TraceOnEvent(L"SosPmApcString"); _ActionIgnore(); @@ -1738,52 +1741,34 @@ void StateMachine::ProcessCharacter(const wchar_t wch) _ActionInterrupt(); _EnterEscape(); } - else + else if (_state < VTStates::TotalStates) { + static constexpr alignas(64) void (StateMachine::*funcs[])(wchar_t) = { + &StateMachine::_EventGround, // VTStates::Ground + &StateMachine::_EventEscape, // VTStates::Escape + &StateMachine::_EventEscapeIntermediate, // VTStates::EscapeIntermediate + &StateMachine::_EventCsiEntry, // VTStates::CsiEntry + &StateMachine::_EventCsiIntermediate, // VTStates::CsiIntermediate + &StateMachine::_EventCsiIgnore, // VTStates::CsiIgnore + &StateMachine::_EventCsiParam, // VTStates::CsiParam + &StateMachine::_EventOscParam, // VTStates::OscParam + &StateMachine::_EventOscString, // VTStates::OscString + &StateMachine::_EventOscTermination, // VTStates::OscTermination + &StateMachine::_EventSs3Entry, // VTStates::Ss3Entry + &StateMachine::_EventSs3Param, // VTStates::Ss3Param + &StateMachine::_EventVt52Param, // VTStates::Vt52Param + &StateMachine::_EventDcsEntry, // VTStates::DcsEntry + &StateMachine::_EventDcsIgnore, // VTStates::DcsIgnore + &StateMachine::_EventDcsIntermediate, // VTStates::DcsIntermediate + &StateMachine::_EventDcsParam, // VTStates::DcsParam + &StateMachine::_EventDcsPassThrough, // VTStates::DcsPassThrough + &StateMachine::_EventSosPmApcString, // VTStates::SosPmApcString + }; + // Then pass to the current state as an event - switch (_state) - { - case VTStates::Ground: - return _EventGround(wch); - case VTStates::Escape: - return _EventEscape(wch); - case VTStates::EscapeIntermediate: - return _EventEscapeIntermediate(wch); - case VTStates::CsiEntry: - return _EventCsiEntry(wch); - case VTStates::CsiIntermediate: - return _EventCsiIntermediate(wch); - case VTStates::CsiIgnore: - return _EventCsiIgnore(wch); - case VTStates::CsiParam: - return _EventCsiParam(wch); - case VTStates::OscParam: - return _EventOscParam(wch); - case VTStates::OscString: - return _EventOscString(wch); - case VTStates::OscTermination: - return _EventOscTermination(wch); - case VTStates::Ss3Entry: - return _EventSs3Entry(wch); - case VTStates::Ss3Param: - return _EventSs3Param(wch); - case VTStates::Vt52Param: - return _EventVt52Param(wch); - case VTStates::DcsEntry: - return _EventDcsEntry(wch); - case VTStates::DcsIgnore: - return _EventDcsIgnore(); - case VTStates::DcsIntermediate: - return _EventDcsIntermediate(wch); - case VTStates::DcsParam: - return _EventDcsParam(wch); - case VTStates::DcsPassThrough: - return _EventDcsPassThrough(wch); - case VTStates::SosPmApcString: - return _EventSosPmApcString(wch); - default: - return; - } + // Invalid _state values will jump to address 0, which is fine I guess. +#pragma warning(suppress : 26482) // Only index into arrays using constant expressions (bounds.2). + (this->*funcs[WI_EnumValue(_state)])(wch); } } // Method Description: diff --git a/src/terminal/parser/stateMachine.hpp b/src/terminal/parser/stateMachine.hpp index 2b72c2307..2ef72f968 100644 --- a/src/terminal/parser/stateMachine.hpp +++ b/src/terminal/parser/stateMachine.hpp @@ -100,22 +100,22 @@ namespace Microsoft::Console::VirtualTerminal void _EventCsiIntermediate(const wchar_t wch); void _EventCsiIgnore(const wchar_t wch); void _EventCsiParam(const wchar_t wch); - void _EventOscParam(const wchar_t wch) noexcept; + void _EventOscParam(const wchar_t wch); void _EventOscString(const wchar_t wch); void _EventOscTermination(const wchar_t wch); void _EventSs3Entry(const wchar_t wch); void _EventSs3Param(const wchar_t wch); void _EventVt52Param(const wchar_t wch); void _EventDcsEntry(const wchar_t wch); - void _EventDcsIgnore() noexcept; + void _EventDcsIgnore(const wchar_t wch); void _EventDcsIntermediate(const wchar_t wch); void _EventDcsParam(const wchar_t wch); void _EventDcsPassThrough(const wchar_t wch); - void _EventSosPmApcString(const wchar_t wch) noexcept; + void _EventSosPmApcString(const wchar_t wch); void _AccumulateTo(const wchar_t wch, size_t& value) noexcept; - enum class VTStates + enum class VTStates : uint_fast8_t { Ground, Escape, @@ -135,7 +135,8 @@ namespace Microsoft::Console::VirtualTerminal DcsIntermediate, DcsParam, DcsPassThrough, - SosPmApcString + SosPmApcString, + TotalStates }; Microsoft::Console::VirtualTerminal::ParserTracing _trace; diff --git a/src/types/CodepointWidthDetector.cpp b/src/types/CodepointWidthDetector.cpp index de29ba196..53d067975 100644 --- a/src/types/CodepointWidthDetector.cpp +++ b/src/types/CodepointWidthDetector.cpp @@ -9,12 +9,12 @@ namespace // used to store range data in CodepointWidthDetector's internal map struct UnicodeRange final { - unsigned int lowerBound; - unsigned int upperBound; + uint32_t lowerBound; + uint32_t upperBound; CodepointWidth width; }; - static bool operator<(const UnicodeRange& range, const unsigned int searchTerm) noexcept + static bool operator<(const UnicodeRange& range, const uint32_t searchTerm) noexcept { return range.upperBound < searchTerm; } @@ -323,56 +323,46 @@ namespace }; } -// Routine Description: -// - Constructs an instance of the CodepointWidthDetector class -CodepointWidthDetector::CodepointWidthDetector() noexcept : - _fallbackCache{}, - _pfnFallbackMethod{} -{ -} - // Routine Description: // - returns the width type of codepoint as fast as we can by using quick lookup table and fallback cache. // Arguments: // - glyph - the utf16 encoded codepoint to search for // Return Value: // - the width type of the codepoint -CodepointWidth CodepointWidthDetector::GetWidth(const std::wstring_view glyph) const +CodepointWidth CodepointWidthDetector::GetWidth(const std::wstring_view& glyph) const noexcept +try { - THROW_HR_IF(E_INVALIDARG, glyph.empty()); - if (glyph.size() == 1) +#pragma warning(suppress : 26494) // Variable 'codepoint' is uninitialized. Always initialize an object (type.5). + uint32_t codepoint; + switch (glyph.size()) { - // We first attempt to look at our custom quick lookup table of char width preferences. - const auto width = GetQuickCharWidth(glyph.front()); + case 1: + codepoint = til::at(glyph, 0); + break; + case 2: + codepoint = (til::at(glyph, 0) & 0x3FF) << 10; + codepoint |= til::at(glyph, 1) & 0x3FF; + codepoint += 0x10000; + break; + default: + throw std::invalid_argument("invalid UTF16 scalar value pair"); + } - // If it's invalid, the quick width had no opinion, so go to the lookup table. - if (width == CodepointWidth::Invalid) - { - return _lookupGlyphWidthWithCache(glyph); - } - // If it's ambiguous, the quick width wanted us to ask the font directly, try that if we can. - // If not, go to the lookup table. - else if (width == CodepointWidth::Ambiguous) - { - if (_pfnFallbackMethod) - { - return _checkFallbackViaCache(glyph) ? CodepointWidth::Wide : CodepointWidth::Ambiguous; - } - else - { - return _lookupGlyphWidthWithCache(glyph); - } - } - // Otherwise, return Width as it is. - else - { - return width; - } - } - else + auto width = _lookupGlyphWidth(codepoint); + + if (width == CodepointWidth::Ambiguous && _pfnFallbackMethod) { - return _lookupGlyphWidthWithCache(glyph); + width = _checkFallbackViaCache(codepoint, glyph) ? CodepointWidth::Wide : CodepointWidth::Ambiguous; } + + return width; +} +catch (...) +{ + LOG_CAUGHT_EXCEPTION(); + // If we got this far, we couldn't figure it out. + // It's better to be too wide than too narrow. + return glyph.empty() ? CodepointWidth::Ambiguous : CodepointWidth::Wide; } // Routine Description: @@ -383,13 +373,7 @@ CodepointWidth CodepointWidthDetector::GetWidth(const std::wstring_view glyph) c // - true if wch is wide bool CodepointWidthDetector::IsWide(const wchar_t wch) const noexcept { - try - { - return IsWide({ &wch, 1 }); - } - CATCH_LOG(); - - return true; + return IsWide({ &wch, 1 }); } // Routine Description: @@ -398,7 +382,7 @@ bool CodepointWidthDetector::IsWide(const wchar_t wch) const noexcept // - glyph - the utf16 encoded codepoint to check width of // Return Value: // - true if codepoint is wide -bool CodepointWidthDetector::IsWide(const std::wstring_view glyph) const +bool CodepointWidthDetector::IsWide(const std::wstring_view& glyph) const noexcept { return GetWidth(glyph) == CodepointWidth::Wide; } @@ -409,64 +393,24 @@ bool CodepointWidthDetector::IsWide(const std::wstring_view glyph) const // - glyph - the utf16 encoded codepoint to search for // Return Value: // - the width type of the codepoint -CodepointWidth CodepointWidthDetector::_lookupGlyphWidth(const std::wstring_view glyph) const +CodepointWidth CodepointWidthDetector::_lookupGlyphWidth(uint32_t codepoint) const { - if (glyph.empty()) + // No need to check ASCII + if (codepoint >= 0x80) { - return CodepointWidth::Invalid; - } + const auto it = std::lower_bound(s_wideAndAmbiguousTable.begin(), s_wideAndAmbiguousTable.end(), codepoint); - const auto codepoint = _extractCodepoint(glyph); - const auto it = std::lower_bound(s_wideAndAmbiguousTable.begin(), s_wideAndAmbiguousTable.end(), codepoint); - - // For characters that are not _in_ the table, lower_bound will return the nearest item that is. - // We must check its bounds to make sure that our hit was a true hit. - if (it != s_wideAndAmbiguousTable.end() && codepoint >= it->lowerBound && codepoint <= it->upperBound) - { - return it->width; + // For characters that are not _in_ the table, lower_bound will return the nearest item that is. + // We must check its bounds to make sure that our hit was a true hit. + if (it != s_wideAndAmbiguousTable.end() && codepoint >= it->lowerBound && codepoint <= it->upperBound) + { + return it->width; + } } return CodepointWidth::Narrow; } -// Routine Description: -// - returns the width type of codepoint using fallback methods. -// Arguments: -// - glyph - the utf16 encoded codepoint to check width of -// Return Value: -// - the width type of the codepoint -CodepointWidth CodepointWidthDetector::_lookupGlyphWidthWithCache(const std::wstring_view glyph) const noexcept -{ - try - { - // Use our generated table to try to lookup the width based on the Unicode standard. - const CodepointWidth width = _lookupGlyphWidth(glyph); - - // If it's ambiguous, then ask the font if we can. - if (width == CodepointWidth::Ambiguous) - { - if (_pfnFallbackMethod) - { - return _checkFallbackViaCache(glyph) ? CodepointWidth::Wide : CodepointWidth::Ambiguous; - } - else - { - return CodepointWidth::Ambiguous; - } - } - // If it's not ambiguous, it should say wide or narrow. - else - { - return width; - } - } - CATCH_LOG(); - - // If we got this far, we couldn't figure it out. - // It's better to be too wide than too narrow. - return CodepointWidth::Wide; -} - // Routine Description: // - Checks the fallback function but caches the results until the font changes // because the lookup function is usually very expensive and will return the same results @@ -474,47 +418,18 @@ CodepointWidth CodepointWidthDetector::_lookupGlyphWidthWithCache(const std::wst // Arguments: // - glyph - the utf16 encoded codepoint to check width of // - true if codepoint is wide or false if it is narrow -bool CodepointWidthDetector::_checkFallbackViaCache(const std::wstring_view glyph) const +bool CodepointWidthDetector::_checkFallbackViaCache(uint32_t codepoint, const std::wstring_view& glyph) const { - const std::wstring findMe{ glyph }; - // TODO: Cache needs to be emptied when font changes. - const auto it = _fallbackCache.find(findMe); - if (it == _fallbackCache.end()) - { - auto result = _pfnFallbackMethod(glyph); - _fallbackCache.insert_or_assign(findMe, result); - return result; - } - else + const auto it = _fallbackCache.find(codepoint); + if (it != _fallbackCache.end()) { return it->second; } -} -// Routine Description: -// - extract unicode codepoint from utf16 encoding -// Arguments: -// - glyph - the utf16 encoded codepoint convert -// Return Value: -// - the codepoint being stored -unsigned int CodepointWidthDetector::_extractCodepoint(const std::wstring_view glyph) noexcept -{ - if (glyph.size() == 1) - { - return static_cast(glyph.front()); - } - else - { - const unsigned int mask = 0x3FF; - // leading bits, shifted over to make space for trailing bits - unsigned int codepoint = (glyph.at(0) & mask) << 10; - // trailing bits - codepoint |= (glyph.at(1) & mask); - // 0x10000 is subtracted from the codepoint to encode a surrogate pair, add it back - codepoint += 0x10000; - return codepoint; - } + const auto result = _pfnFallbackMethod(glyph); + _fallbackCache.insert_or_assign(codepoint, result); + return result; } // Method Description: @@ -527,9 +442,9 @@ unsigned int CodepointWidthDetector::_extractCodepoint(const std::wstring_view g // - pfnFallback - the function to use as the fallback method. // Return Value: // - -void CodepointWidthDetector::SetFallbackMethod(std::function pfnFallback) +void CodepointWidthDetector::SetFallbackMethod(std::function pfnFallback) noexcept { - _pfnFallbackMethod = pfnFallback; + _pfnFallbackMethod = std::move(pfnFallback); } // Method Description: @@ -542,5 +457,6 @@ void CodepointWidthDetector::SetFallbackMethod(std::function void CodepointWidthDetector::NotifyFontChanged() const noexcept { +#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function 'clear()' which may throw exceptions (f.6). _fallbackCache.clear(); } diff --git a/src/types/GlyphWidth.cpp b/src/types/GlyphWidth.cpp index f90c79f94..f6bdcd7d7 100644 --- a/src/types/GlyphWidth.cpp +++ b/src/types/GlyphWidth.cpp @@ -12,7 +12,7 @@ static CodepointWidthDetector widthDetector; // Function Description: // - determines if the glyph represented by the string of characters should be // wide or not. See CodepointWidthDetector::IsWide -bool IsGlyphFullWidth(const std::wstring_view glyph) +bool IsGlyphFullWidth(const std::wstring_view& glyph) noexcept { return widthDetector.IsWide(glyph); } @@ -35,7 +35,7 @@ bool IsGlyphFullWidth(const wchar_t wch) noexcept // - pfnFallback - the function to use as the fallback method. // Return Value: // - -void SetGlyphWidthFallback(std::function pfnFallback) +void SetGlyphWidthFallback(std::function pfnFallback) { widthDetector.SetFallbackMethod(pfnFallback); } diff --git a/src/types/TermControlUiaTextRange.cpp b/src/types/TermControlUiaTextRange.cpp index 5d95dbd88..97a5ca41f 100644 --- a/src/types/TermControlUiaTextRange.cpp +++ b/src/types/TermControlUiaTextRange.cpp @@ -134,7 +134,7 @@ void TermControlUiaTextRange::_TranslatePointFromScreen(LPPOINT screenPoint) con screenPoint->y = includeOffsets(screenPoint->y, boundingRect.top, padding.top, scaleFactor); } -const COORD TermControlUiaTextRange::_getScreenFontSize() const +const COORD TermControlUiaTextRange::_getScreenFontSize() const noexcept { // Do NOT get the font info from IRenderData. It is a dummy font info. // Instead, the font info is saved in the TermControl. So we have to diff --git a/src/types/TermControlUiaTextRange.hpp b/src/types/TermControlUiaTextRange.hpp index 4ec34277a..42afc210b 100644 --- a/src/types/TermControlUiaTextRange.hpp +++ b/src/types/TermControlUiaTextRange.hpp @@ -57,6 +57,6 @@ namespace Microsoft::Terminal protected: void _TranslatePointToScreen(LPPOINT clientPoint) const override; void _TranslatePointFromScreen(LPPOINT screenPoint) const override; - const COORD _getScreenFontSize() const override; + const COORD _getScreenFontSize() const noexcept override; }; } diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index c32a6a900..2bc7411d9 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -1313,7 +1313,7 @@ IFACEMETHODIMP UiaTextRangeBase::GetChildren(_Outptr_result_maybenull_ SAFEARRAY #pragma endregion -const COORD UiaTextRangeBase::_getScreenFontSize() const +const COORD UiaTextRangeBase::_getScreenFontSize() const noexcept { COORD coordRet = _pData->GetFontInfo().GetSize(); diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index 24756648f..c9089438d 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -146,7 +146,7 @@ namespace Microsoft::Console::Types RECT _getTerminalRect() const; - virtual const COORD _getScreenFontSize() const; + virtual const COORD _getScreenFontSize() const noexcept; const unsigned int _getViewportHeight(const SMALL_RECT viewport) const noexcept; const Viewport _getOptimizedBufferSize() const noexcept; diff --git a/src/types/inc/CodepointWidthDetector.hpp b/src/types/inc/CodepointWidthDetector.hpp index 810b4bd7b..b0897b4e9 100644 --- a/src/types/inc/CodepointWidthDetector.hpp +++ b/src/types/inc/CodepointWidthDetector.hpp @@ -16,24 +16,21 @@ Author: #include "convert.hpp" #include -static_assert(sizeof(unsigned int) == sizeof(wchar_t) * 2, - "UnicodeRange expects to be able to store a unicode codepoint in an unsigned int"); - // use to measure the width of a codepoint class CodepointWidthDetector final { public: - CodepointWidthDetector() noexcept; + CodepointWidthDetector() noexcept = default; + CodepointWidthDetector(const CodepointWidthDetector&) = delete; CodepointWidthDetector(CodepointWidthDetector&&) = delete; - ~CodepointWidthDetector() = default; CodepointWidthDetector& operator=(const CodepointWidthDetector&) = delete; CodepointWidthDetector& operator=(CodepointWidthDetector&&) = delete; - CodepointWidth GetWidth(const std::wstring_view glyph) const; - bool IsWide(const std::wstring_view glyph) const; + CodepointWidth GetWidth(const std::wstring_view& glyph) const noexcept; + bool IsWide(const std::wstring_view& glyph) const noexcept; bool IsWide(const wchar_t wch) const noexcept; - void SetFallbackMethod(std::function pfnFallback); + void SetFallbackMethod(std::function pfnFallback) noexcept; void NotifyFontChanged() const noexcept; #ifdef UNIT_TESTING @@ -41,11 +38,9 @@ public: #endif private: - CodepointWidth _lookupGlyphWidth(const std::wstring_view glyph) const; - CodepointWidth _lookupGlyphWidthWithCache(const std::wstring_view glyph) const noexcept; - bool _checkFallbackViaCache(const std::wstring_view glyph) const; - static unsigned int _extractCodepoint(const std::wstring_view glyph) noexcept; + CodepointWidth _lookupGlyphWidth(uint32_t codepoint) const; + bool _checkFallbackViaCache(uint32_t codepoint, const std::wstring_view& glyph) const; - mutable std::unordered_map _fallbackCache; - std::function _pfnFallbackMethod; + mutable robin_hood::unordered_flat_map _fallbackCache; + std::function _pfnFallbackMethod; }; diff --git a/src/types/inc/GlyphWidth.hpp b/src/types/inc/GlyphWidth.hpp index 7888915be..35f2ab854 100644 --- a/src/types/inc/GlyphWidth.hpp +++ b/src/types/inc/GlyphWidth.hpp @@ -12,7 +12,7 @@ Abstract: #include #include -bool IsGlyphFullWidth(const std::wstring_view glyph); +bool IsGlyphFullWidth(const std::wstring_view& glyph) noexcept; bool IsGlyphFullWidth(const wchar_t wch) noexcept; -void SetGlyphWidthFallback(std::function pfnFallback); +void SetGlyphWidthFallback(std::function pfnFallback); void NotifyGlyphWidthFontChanged() noexcept;