Repository: microsoft/terminal (language: cpp)

Issue:
Italics & Bold not rendering properly if they are the final character - text looks "cut off"
<!-- 
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨

I ACKNOWLEDGE THE FOLLOWING BEFORE PROCEEDING:
1. If I delete this entire template and go my own path, the core team may close my issue without further explanation or engagement.
2. If I list multiple bugs/concerns in this one issue, the core team may close my issue without further explanation or engagement.
3. If I write an issue that has many duplicates, the core team may close my issue without further explanation or engagement (and without necessarily spending time to find the exact duplicate ID number).
4. If I leave the title incomplete when filing the issue, the core team may close my issue without further explanation or engagement.
5. If I file something completely blank in the body, the core team may close my issue without further explanation or engagement.

All good? Then proceed!
-->

<!--
This bug tracker is monitored by Windows Terminal development team and other technical folks.

**Important: When reporting BSODs or security issues, DO NOT attach memory dumps, logs, or traces to Github issues**.
Instead, send dumps/traces to secure@microsoft.com, referencing this GitHub issue.

If this is an application crash, please also provide a Feedback Hub submission link so we can find your diagnostic data on the backend. Use the category "Apps > Windows Terminal (Preview)" and choose "Share My Feedback" after submission to get the link.

Please use this form and describe your issue, concisely but precisely, with as much detail as possible.

-->

# Environment

```none
Windows build number: [Version 10.0.19042.844]
Windows Terminal version: 1.6.10571.0

Any other software?
Ubuntu using WSL 1
Fish Shell
```

# Steps to reproduce

<!-- A description of how to trigger this bug. -->
Print any italic text to the terminal

# Expected behavior

<!-- A description of what you're expecting, possibly containing screenshots or reference material. -->
All characters should be italicized and fully visible.

# Actual behavior

<!-- What's actually happening? -->
The last character before a newline is cut-off where the italic character would bleed into the next character. Note this occurs when using both Cascadia Code and FiraCode in my testing.

After further testing, we've determined that the issue is related to the `cleartype` antialiasing setting. Using `grayscale` eliminates the issue.

![image](https://user-images.githubusercontent.com/54364725/110029853-fba17080-7d02-11eb-8651-42bb41cea422.png)




Discussion / hints:
Interestingly, switching the font back and forth actually fixes the issue for anything that has already been printed.
_Huh._ Good catch!
What is "set_color"?

I have no problem doing it manually (in TCSH).

![image](https://user-images.githubusercontent.com/61856645/110033095-f0e8da80-7d06-11eb-9cef-0a2abba2fb1d.png)

It's a function in Fish that makes colorizing output a little easier. And not every character crosses over into the next spot, I found that '0' did it a lot.
![image](https://user-images.githubusercontent.com/54364725/110033490-75d3f400-7d07-11eb-9683-ac0e0df025f9.png)

Everything seems to be fine:

![aaa](https://user-images.githubusercontent.com/78153320/110034670-ca00c780-7d4b-11eb-9b16-d81cc64cd77d.png)


Huh. I've noticed that any action that would cause a re-render like changing the font or font size fixes the issue for anything that's already been printed.
Thanks.  I was using Consolas.  I switch to Cascadia Code and tried a few characters that are tycically wide, and a few font sizes.   I'm still OK (though the lowercase w's are a bit weird).

![image](https://user-images.githubusercontent.com/61856645/110035581-db28e480-7d09-11eb-91ba-5187c2fa5c03.png)

I believe this only happens when you're changing colors. So in most cases you wouldn't see it at the end of a line, but I'm guessing the OP is actually writing out text with a black background while the rest of the screen is using the default color (which just happens to be the same value as black), so there is actually an invisible transition from black to default where the characters get clipped.

I'm was aware this could happen when I implemented italics, but I didn't think there was much we could do about it.
That makes sense, except I'm not changing the background color as far as I know
Hmm... you're right. The test case you did with the escape sequence is clearly using the default background, so it can't be that. I'm not sure what is triggering it for you then. I wonder if the acrylic background is the issue.
Turning off acrylic didn't fix the issue ... but switching my anti-aliasing mode from "cleartype" to "grayscale" did. It seems like that's what's causing it.
> this only happens when you're changing colors

![bbb](https://user-images.githubusercontent.com/78153320/110037228-2b766580-7d4f-11eb-8b5b-a00771b3b19b.png)

May be this is "fish"-behavior
I've confirmed it happens in bash as well
> switching my anti-aliasing mode from "cleartype" to "grayscale" did. It seems like that's what's causing it.

Yep, that's it. I can reproduce it now too.
So, when we render individual "runs" of text we clip the rendering region to the estimated space to avoid characters overhanging their boxes. The italics and the spaces are logically two units . . . at first.

I suspect that when we re-render the line containing the italic text (in response to a selection, cursor blink, etc.), we decide that the italic text plus the spaces after it are a single run and _don't_ clip it. Probably due to our "identical visual representation for space characters" "optimization" ;P
@DHowett  So for me in the shell (fish) and on Ubuntu 20.10 it is fine 
![image](https://user-images.githubusercontent.com/31296212/113462558-82736700-93ef-11eb-83a0-0526678a7d95.png)
But in Neovim comments that are italic has this issue for some reason
![image](https://user-images.githubusercontent.com/31296212/113462646-e6962b00-93ef-11eb-963c-9078ea36cfcf.png)

I'm having the same issue, which I notice only with neovim so far and fonts without italic variant

I'm also seeing this issue in vim/neovim regardless of `"antialiasingMode": "cleartype"` or `"antialiasingMode": "grayscale"`. Especially noticeable when `0` or `{` are the ending characters.

![grayscale_vim](https://user-images.githubusercontent.com/5077424/119307765-30ee9800-bc3a-11eb-9fed-9f7e0429cf8b.PNG)

Although, `grayscale` seemingly fixes it in the shell, zsh in this case.

`echo -e "\e[3m foo {{{ \e[23m"`
![grayscale_shell](https://user-images.githubusercontent.com/5077424/119307756-2d5b1100-bc3a-11eb-9ae6-511d50104ded.PNG)

```
Windows Terminal Preview
Version: 1.8.1032.0
```
might I add that the behavior is not consistent though, on certain lines the last character will not be cut out.. while on some it will be?...

![Screenshot_34](https://user-images.githubusercontent.com/68319923/123201426-da94a380-d4dc-11eb-93c6-afa6b8db212e.png)

### Windows Terminal version (or Windows build number)

10.0.19042.1052

### Other Software

Ubuntu WSL, using Vim inside the terminal

### Font Used

Ubuntu Mono (which has true italic)
I know that italics usually goes out of their bounding box in the font design, and turning on full-hinting like windows does will force the font to strictly follow the the box guide, which might cut some characters off.. 

But how does that explain characters without the error though.. That's what confuses me..

Caption: Italic goes off bounding box, which make full-hinting system like windows terminal likely to cut off:
![Screenshot_35](https://user-images.githubusercontent.com/68319923/123202027-0ebc9400-d4de-11eb-9189-b46c507fdfe3.png)



_Sometimes_ terminal's text run splitter chooses to end the run at the last character and clip to the bounding box, and sometimes it encompasses one or more of the following space characters. When it chooses to include the spaces, the clip extends to cover those cells and doesn't truncate the last glyph. 
Would there be a way to fix this? I mean it sounds like we can fix this by making the behavior of clipping more consistent (like always encompasses some of the following space characters until a non-italic glyph, for example)..

I have no idea how hard it is to implement the functionality though
Any updates on this? Literally the ONLY issue I have with windows terminal is this!
Nope. We'll make sure to update this thread when there is. In the meantime, might I recommend the Subscribe button?
![image](https://user-images.githubusercontent.com/18356694/91237459-5cbb0c80-e700-11ea-9347-b9b1ec2813b1.png)
That way you'll be notified of any updates to this thread, without needlessly pinging everyone on this thread ☺️
I have the same problem when using Neovim with WSL2 and Windows Terminal
For me, using Inconsolata (Nerd Font version), I get black boxes, presumably where the next character is supposed to be..?
![image](https://user-images.githubusercontent.com/40524763/147993994-b46f46e3-9b02-4a88-b3ec-63c0010e6ccb.png)

Cascadia Code does tend to do what is mentioned above and clips text.
@Pranav-Badrinathan this is because the Italic variant of your font has a *significantly* smaller width than the Roman variant. That's actually a hole that was introduced to make up for the accumulated missing width.
Yeah, this is really more a variant of #10678, only kind of in reverse. I'm assuming the atlas renderer should fix this.
> ...Italic variant of your font has a _significantly_ smaller width than the Roman variant. That's actually a hole that was introduced to make up for the accumulated missing width.

Makes sense. 1 to 2 pixels slimmer in Italics, and as you said that causes it to freak out a bit when the sentence gets long enough, by the time which the character is quite far behind. 😅 

Surprising, as the font is monospace, and I thought that they were supposed to remain the same size regardless formatting...
Oh well. Glad to see that this issue is set to be resolved this year!
has anyone has a solution for this? it is because bold font is showing as a regular font and not being bolded
This is still a pain for me. It's the only reason I'm not using Terminal as my daily driver. 
https://github.com/microsoft/terminal/issues/9381#issuecomment-1004448904
>Yeah, this is really more a variant of https://github.com/microsoft/terminal/issues/10678, only kind of in reverse. I'm assuming the atlas renderer should fix this.

For what it's worth, at least the current version (87f5034db1eb0102358d05eeff82ebcc8032c9be) does not seem to fix this.
In addition, the issue seems to apply to each character in Atlas, rather than just the last.
Not sure if this has something to do with what appears to be glyph weight and kerning differences between the rendering engines.

Above is the default engine (using ClearType antialiasing), below is Atlas:
![Untitled](https://user-images.githubusercontent.com/13862850/164489034-b55282ca-3da7-4fd0-a133-d0ed9058bd2c.png)
Which almost turns PowerShell into a 🌸FlowerShell.

Not sure if this belongs in https://github.com/microsoft/terminal/issues/9999 as well or not.

***
Extra context
I'm using a (14-point) NerdFonts patched PragmataPro, with this from my `$PROFILE`:
```
$Icon = "舘"
Write-Host "$Icon `e[3mPowerShell $($PsVersionTable.PSVersion)`e[0m"
```
> > Yeah, this is really more a variant of #10678, only kind of in reverse. I'm assuming the atlas renderer should fix this.
> 
> For what it's worth, at least the current version ([87f5034](https://github.com/microsoft/terminal/commit/87f5034db1eb0102358d05eeff82ebcc8032c9be)) does not seem to fix this.

@djdv I was referring to the user that was getting black boxes at the end of the line because their italic font was *narrower* than the regular variant (https://github.com/microsoft/terminal/issues/9381#issuecomment-1004445152).
I have the same issue when I use neovim.
![image](https://user-images.githubusercontent.com/7571003/169221339-90cceb09-f580-414d-b2fe-b371046604bd.png)

Environment: 
```
Windows Terminal Version: 1.12.10983.0
Font: CaskaydiaCove NF
Antialiasing: ClearType
```
How about to use the italic style font in the font family instead of changing the inclination of font? 

![image](https://user-images.githubusercontent.com/54128430/169547186-ccea813d-ff21-45f0-98a7-40b1d1ffae28.png)

> How about to use the italic style font in the font family instead of changing the inclination of font? 
> 
> 
> 

Terminal automatically does this if the font is authored correctly. :)
> I have the same issue when I use neovim. ![image](https://user-images.githubusercontent.com/7571003/169221339-90cceb09-f580-414d-b2fe-b371046604bd.png)
> 
> Environment:
> 
> ```
> Windows Terminal Version: 1.12.10983.0
> Font: CaskaydiaCove NF
> Antialiasing: ClearType
> ```

I have the same issue too.
> > How about to use the italic style font in the font family instead of changing the inclination of font?
> 
> Terminal automatically does this if the font is authored correctly. :)

My fault, I have edited wrong weight for my italic font info. BTW, does wt support bold font?

![image](https://user-images.githubusercontent.com/54128430/169644201-077d9290-ee6e-4f5f-817d-39a34df1e47c.png)

I'm 100% sure my bold font information is correct, because vscode could render it correctly.

![image](https://user-images.githubusercontent.com/54128430/169644163-c73f1e23-50e7-4837-bc11-492409c88556.png)

Yes, you'll want to set this setting:

<img width="439" alt="image" src="https://user-images.githubusercontent.com/189190/169875481-03dd46e2-faab-4854-aaed-df26db28a175.png">

I'm sorry that it does not work for me. #13150
i tryed to fix it in many ways, changing font to this one seems working:
https://github.com/sainnhe/icursive-nerd-font/tree/master/Fira%20Code%20iCursive%20S12
> Yes, you'll want to set this setting:
> 
> <img alt="image" width="439" src="https://user-images.githubusercontent.com/189190/169875481-03dd46e2-faab-4854-aaed-df26db28a175.png">


This is the setting I have enable already to even show these fonts with a bold and italic style. It is what actually made me aware of the problem.

Here is a screenshot of an example for me where every single character (with a big enough bounding box for it to be noticeable) is cutoff. This is honestly the main reason I am still using Alacritty over WT. 

![image](https://user-images.githubusercontent.com/1278350/171759507-a67b342a-dd0a-495d-b647-a67e9139bc25.png)


Mark and the annoying issue is still there...
Overall workflow blocked still waiting for these issues to be fixed
I'm noticing this issue regardless of the text antialiser I am using (I tried Grayscale, ClearType and Alisased).
Another interesting thing I noticed, this issue is only present on _one_ of my machines. My other (using an identical profile) is not experiencing the same issue.

I have attached the profile as well as whatever info I can provide about the terminal for each machine
[not-broken-italics-settings.txt](https://github.com/microsoft/terminal/files/9181732/not-broken-italics-settings.txt)
[broken-italics-settings.txt](https://github.com/microsoft/terminal/files/9181733/broken-italics-settings.txt)
(Note, I had to upload them as `.txt` files because github hates real code. But you're all smart cookies)

Edit: Both versions are running the same Microsoft Terminal version: 1.13.11431.0

Edit 2: This appears to be an issue with monospace font rendering. 
Using [MesloLGS NF](https://github.com/ryanoasis/nerd-fonts/tree/master/patched-fonts/Meslo/S/Regular/complete) works perfectly, however using [MesloLGM NF](https://github.com/ryanoasis/nerd-fonts/tree/master/patched-fonts/Meslo/M/Regular/complete) results in the cutoff italics. See

![image](https://user-images.githubusercontent.com/2640668/180796161-02eb567c-c867-4ab7-9ae7-6080cb9063a7.png)

I'm seeing something related when rendering text (code comments) with italics;
![image](https://user-images.githubusercontent.com/16577635/181595458-cd0af38e-6d44-4c3f-868e-7863d1bb8f23.png)

As I type across the line, the cursor seems to move further and further to the right, and become disjointed from where the text is actually getting displayed;
![image](https://user-images.githubusercontent.com/16577635/181595601-2f3a934d-d55e-4bec-9aea-d87412178b15.png)

![image](https://user-images.githubusercontent.com/16577635/181595661-42abc951-24a3-4f28-bb16-6c8703fbe120.png)
> When in insert mode in Vim, you can see clearly where the cursor is pointing to (the vertical bar) vs. where the letter is actually going to be inserted (to the left of that black box).

This looks to be some sort of mathematical pixelling issue?
You're right. This is sort of the _opposite_ of the original report, but the root cause is the same. Text will be cut off on the right side when the italic or bold version is wider than the normal version, and it will spring back (for lack of a better term) and squeeze over to the left when it's narrower.

The root cause here is that our current text rendering engine tries to batch text into identical runs--that is, groups of characters with the same display attributes--and render them all at once. It helps us combine things like a base character and a combining diacritic (e + acute), but it also means that the font's native sizing and sometimes kerning take effect. When you do something that _splits_ the display attributes (like, in your case you have `cursorcolumn` enabled), we end up doing this:

```
[One big render containing most of the text                 ] [e]
```

... and that one big render follows the font's native sizing.

At the end of the day, this isn't going to get better until we make the cell-by-cell rendering engine the default; the work to complete it is being tracked in #9999. I believe this case already works over there, but I haven't validated it with all fonts.

That's probably going to be the most complete solution to this issue, so I'm tempted to close this as a duplicate and recommend testing out the "new experimental text rendering engine" in the meantime.

What do you think? (Both the team and the community!)
> What do you think? (Both the team and the community!)

I kinda like the idea of leaving this open for now. It will likely make it easier for those trying to see if this problem is reported and being looked. People sometimes don't read the whole thread (I know, not our problem), and may think that the issue was closed because it was resolved.
Hey all, tried using the new _experimental text rendering engine_ and it still cuts off italics (using _Dina Remaster_). Just checking if it's supposed to be working already or not since @DHowett mentioned that `this case already works over there`, but I may be misunderstanding. If anything it's actually worse with it enabled (more than just the last italicized character is removed):
![image](https://user-images.githubusercontent.com/56184947/188539306-e4b460c3-a7ec-4297-b2cb-abd43ec59755.png)

_Huh_. @lhecker, should we measure cell extents in Atlas by checking all three fonts (bold, italic, regular) that the user could encounter? This will make the window wider **IN MOST CASES** for poorly-behaved fonts like the one @jfaz1 is using, but it would resolve this issue once and for all.
In 1.16 those glyphs won't be cut off anymore. But to retain a proper monospace grid we should implement your suggestion @DHowett.
@lhecker If we're going to force the cells to be wide enough to completely fit italic characters, I suspect that's going to make the letter spacing unnecessarily large for quite a few fonts. That will be particularly annoying for users that aren't even using italics.

I thought at one point that you had planned to allow characters to overflow their cell, which I assumed would largely solve this problem (at leat if we're also rendering the background in a separate pass - I'm not sure if that's true yet). 

I can't say for definite that's what everyone else does, but I know at least some terminals do something like this. For example this is a screenshot from Mintty rendering italics with the background changing between characters.

![image](https://user-images.githubusercontent.com/4181424/188704215-ae0ad9ed-c7c7-4437-99d9-240c77182448.png)

> @lhecker 
> I thought at one point that you had planned to allow characters to overflow their cell, which I assumed would largely solve this problem (at leat if we're also rendering the background in a separate pass - I'm not sure if that's true yet). 

This would be the way I'd expect this to be implemented?

I'm not sure, however, whether this uncovers other issues surrounding obscure placement of characters, especially with other glyphs.

I haven't had a chance to test the latest myself yet, but will try to give it a go for my use case in the next day.
> I thought at one point that you had planned to allow characters to overflow their cell, which I assumed would largely solve this problem (at leat if we're also rendering the background in a separate pass - I'm not sure if that's true yet).

Yeah I still do, but it's not trivial to decide on a proper heuristic here. What I was planning to solve in the near term is joining ambiguous emojis with a trailing whitespace to make Windows Terminal behave like other terminals in that regard. Implementing the heuristic for that is pretty simple after all.

Implementing a glyph atlas with arbitrarily sized glyphs correctly and properly is a lot more annoying. This will require the use of a bin packing algorithm to store glyph textures. Then during rendering you have to render each glyph on its own rectangular polygon (quad). This is how most text renderers including Direct2D do it. It's easy to do this sloppy and hard to do correctly.

After all, you can't just draw each glyph on its own quad. What if the overlapping glyphs intentionally form a ligature? Overlapping them by simply alpha-blending them would mean that the anti-aliased border of the glyph ends up too opaque at the point of the overlap. It also doesn't work with ClearType which a lot of our users use (ClearType doesn't support alpha blending). Joining overlapping glyphs and rasterizing them as one large glyph solves the problem.

But what is the heuristic here? If you simply join all overlapping glyphs you might get a perfect result, but now a screen full of slanted/italic text would all be rendered as one huge glyph with effectively no caching at all. The performance would end up being terrible. Given that Direct2D already handles this perfectly, it might be easier to port our text rendering improvements to Direct2D than it is to replicate Direct2Ds text renderer in Windows Terminal in full.

I'm not a text rendering expert and I've always considered our custom text renderer ("AtlasEngine") to be a "hack" which just works around our immediate performance problems we had. It's a nice bonus that it performs much much faster than Direct2D _ever_ could, thanks to us assuming that each glyph fits into the terminal's raster grid, but clearly this isn't a perfect solution either.

If anyone is knowledgeable about text rendering and has ideas how to improve ours I'd be happy to hear about it.

> I haven't had a chance to test the latest myself yet, but will try to give it a go for my use case in the next day.

1.16 hasn't been released yet, but will be most likely this week.
> But what is the heuristic here? If you simply join all overlapping glyphs you might get a perfect result, but now a screen full of slanted/italic text would all be rendered as one huge glyph

But you surely don't need to join overlapping glyphs just because they're slanted? I would have thought the only time you really care about joining is if you've got an actual ligature, which I assumed you'd know from the font metrics somehow. Or am I wrong about that?

I accept you might still have a situation where you've got something like an italic `W` overlapping a non-italic `H`, with artifacts where the characters intersect, but it seems like that should be fairly rare, and even when it happens I suspect it would be less jarring than having excessive letter spacing everywhere.

I'm not super concerned about any of this stuff though. Just wanted to offer my thoughts on the subject. But I know very little about text rendering, so if I'm not making any sense, feel free to disregard.
> But you surely don't need to join overlapping glyphs just because they're slanted? I would have thought the only time you really care about joining is if you've got an actual ligature, which I assumed you'd know from the font metrics somehow. Or am I wrong about that?

For text rendering with grayscale antialiasing this would mostly work, apart from an uneven appearance when glyphs do intersect. This is the approach I would choose if I had to make this change in the near-ish future, because it provides 80% of the value (overlapping glyphs mostly appear to work) for 20% of the cost (only a minor performance impact). If ClearType is enabled I'd probably force glyphs into the cell grid the way it works now, due to the lack of an alpha channel for ClearType glyphs.

But in either case this does require a non-trivial amount of work (due to the bin packing with conservative memory management & quad layout for each glyph) and and I still need to work on a lot of other rendering improvements which IMO add just as much value to Windows Terminal (looking at you, sixel support in AtlasEngine, our 4th biggest issue). Until then I'm hoping that the new cell-fitting algorithm (#13549) which ships in 1.16 next week improves the situation around italic/bold glyphs so much so that it's far less of an issue for most users, most of the time.

**Edit:** The cell-fitting thing didn't work out. 🥲

Fix patch:
diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt
index c55d71ad4f6..38ef5c30d01 100644
--- a/.github/actions/spelling/allow/apis.txt
+++ b/.github/actions/spelling/allow/apis.txt
@@ -214,6 +214,7 @@ userenv
 USEROBJECTFLAGS
 Viewbox
 virtualalloc
+vsnwprintf
 wcsstr
 wcstoui
 WDJ
diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt
index c8cc9565e6a..8c4bfc3629c 100644
--- a/.github/actions/spelling/expect/expect.txt
+++ b/.github/actions/spelling/expect/expect.txt
@@ -1784,6 +1784,7 @@ STARTWPARMS
 STARTWPARMSA
 STARTWPARMSW
 Statusline
+stb
 stdafx
 STDAPI
 stdc
diff --git a/NOTICE.md b/NOTICE.md
index e1488fd4a20..e5046604574 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -276,6 +276,31 @@ OTHER DEALINGS IN THE SOFTWARE.
 For more information, please refer to <http://unlicense.org/>
 ```
 
+## stb
+
+**Source**: [https://github.com/nothings/stb](https://github.com/nothings/stb)
+
+### License
+
+```
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+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 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.
+```
+
 ## ConEmu
 **Source**: [https://github.com/Maximus5/ConEmu](https://github.com/Maximus5/ConEmu)
 
diff --git a/oss/stb/LICENSE b/oss/stb/LICENSE
new file mode 100644
index 00000000000..21635cc163c
--- /dev/null
+++ b/oss/stb/LICENSE
@@ -0,0 +1,37 @@
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+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.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+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 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/stb/MAINTAINER_README.md b/oss/stb/MAINTAINER_README.md
new file mode 100644
index 00000000000..2f23f5346c8
--- /dev/null
+++ b/oss/stb/MAINTAINER_README.md
@@ -0,0 +1,4 @@
+### Notes for Future Maintainers
+
+Search for files prefixed with `stb_` in this project.
+At the time of writing, the only file being used is `stb_rect_pack.h`.
diff --git a/oss/stb/cgmanifest.json b/oss/stb/cgmanifest.json
new file mode 100644
index 00000000000..69fa8d1e26a
--- /dev/null
+++ b/oss/stb/cgmanifest.json
@@ -0,0 +1,15 @@
+{
+  "$schema": "https://json.schemastore.org/component-detection-manifest.json",
+  "Registrations": [
+    {
+      "component": {
+        "type": "git",
+        "git": {
+          "repositoryUrl": "https://github.com/nothings/stb",
+          "commitHash": "5736b15f7ea0ffb08dd38af21067c314d6a3aae9"
+        }
+      }
+    }
+  ],
+  "Version": 1
+}
diff --git a/oss/stb/stb_rect_pack.h b/oss/stb/stb_rect_pack.h
new file mode 100644
index 00000000000..6a633ce666a
--- /dev/null
+++ b/oss/stb/stb_rect_pack.h
@@ -0,0 +1,623 @@
+// stb_rect_pack.h - v1.01 - public domain - rectangle packing
+// Sean Barrett 2014
+//
+// Useful for e.g. packing rectangular textures into an atlas.
+// Does not do rotation.
+//
+// Before #including,
+//
+//    #define STB_RECT_PACK_IMPLEMENTATION
+//
+// in the file that you want to have the implementation.
+//
+// Not necessarily the awesomest packing method, but better than
+// the totally naive one in stb_truetype (which is primarily what
+// this is meant to replace).
+//
+// Has only had a few tests run, may have issues.
+//
+// More docs to come.
+//
+// No memory allocations; uses qsort() and assert() from stdlib.
+// Can override those by defining STBRP_SORT and STBRP_ASSERT.
+//
+// This library currently uses the Skyline Bottom-Left algorithm.
+//
+// Please note: better rectangle packers are welcome! Please
+// implement them to the same API, but with a different init
+// function.
+//
+// Credits
+//
+//  Library
+//    Sean Barrett
+//  Minor features
+//    Martins Mozeiko
+//    github:IntellectualKitty
+//
+//  Bugfixes / warning fixes
+//    Jeremy Jaussaud
+//    Fabian Giesen
+//
+// Version history:
+//
+//     1.01  (2021-07-11)  always use large rect mode, expose STBRP__MAXVAL in public section
+//     1.00  (2019-02-25)  avoid small space waste; gracefully fail too-wide rectangles
+//     0.99  (2019-02-07)  warning fixes
+//     0.11  (2017-03-03)  return packing success/fail result
+//     0.10  (2016-10-25)  remove cast-away-const to avoid warnings
+//     0.09  (2016-08-27)  fix compiler warnings
+//     0.08  (2015-09-13)  really fix bug with empty rects (w=0 or h=0)
+//     0.07  (2015-09-13)  fix bug with empty rects (w=0 or h=0)
+//     0.06  (2015-04-15)  added STBRP_SORT to allow replacing qsort
+//     0.05:  added STBRP_ASSERT to allow replacing assert
+//     0.04:  fixed minor bug in STBRP_LARGE_RECTS support
+//     0.01:  initial release
+//
+// LICENSE
+//
+//   See end of file for license information.
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//       INCLUDE SECTION
+//
+
+#ifndef STB_INCLUDE_STB_RECT_PACK_H
+#define STB_INCLUDE_STB_RECT_PACK_H
+
+#define STB_RECT_PACK_VERSION  1
+
+#ifdef STBRP_STATIC
+#define STBRP_DEF static
+#else
+#define STBRP_DEF extern
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct stbrp_context stbrp_context;
+typedef struct stbrp_node    stbrp_node;
+typedef struct stbrp_rect    stbrp_rect;
+
+typedef int            stbrp_coord;
+
+#define STBRP__MAXVAL  0x7fffffff
+// Mostly for internal use, but this is the maximum supported coordinate value.
+
+STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
+// Assign packed locations to rectangles. The rectangles are of type
+// 'stbrp_rect' defined below, stored in the array 'rects', and there
+// are 'num_rects' many of them.
+//
+// Rectangles which are successfully packed have the 'was_packed' flag
+// set to a non-zero value and 'x' and 'y' store the minimum location
+// on each axis (i.e. bottom-left in cartesian coordinates, top-left
+// if you imagine y increasing downwards). Rectangles which do not fit
+// have the 'was_packed' flag set to 0.
+//
+// You should not try to access the 'rects' array from another thread
+// while this function is running, as the function temporarily reorders
+// the array while it executes.
+//
+// To pack into another rectangle, you need to call stbrp_init_target
+// again. To continue packing into the same rectangle, you can call
+// this function again. Calling this multiple times with multiple rect
+// arrays will probably produce worse packing results than calling it
+// a single time with the full rectangle array, but the option is
+// available.
+//
+// The function returns 1 if all of the rectangles were successfully
+// packed and 0 otherwise.
+
+struct stbrp_rect
+{
+   // reserved for your use:
+   int            id;
+
+   // input:
+   stbrp_coord    w, h;
+
+   // output:
+   stbrp_coord    x, y;
+   int            was_packed;  // non-zero if valid packing
+
+}; // 16 bytes, nominally
+
+
+STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
+// Initialize a rectangle packer to:
+//    pack a rectangle that is 'width' by 'height' in dimensions
+//    using temporary storage provided by the array 'nodes', which is 'num_nodes' long
+//
+// You must call this function every time you start packing into a new target.
+//
+// There is no "shutdown" function. The 'nodes' memory must stay valid for
+// the following stbrp_pack_rects() call (or calls), but can be freed after
+// the call (or calls) finish.
+//
+// Note: to guarantee best results, either:
+//       1. make sure 'num_nodes' >= 'width'
+//   or  2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
+//
+// If you don't do either of the above things, widths will be quantized to multiples
+// of small integers to guarantee the algorithm doesn't run out of temporary storage.
+//
+// If you do #2, then the non-quantized algorithm will be used, but the algorithm
+// may run out of temporary storage and be unable to pack some rectangles.
+
+STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
+// Optionally call this function after init but before doing any packing to
+// change the handling of the out-of-temp-memory scenario, described above.
+// If you call init again, this will be reset to the default (false).
+
+
+STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
+// Optionally select which packing heuristic the library should use. Different
+// heuristics will produce better/worse results for different data sets.
+// If you call init again, this will be reset to the default.
+
+enum
+{
+   STBRP_HEURISTIC_Skyline_default=0,
+   STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
+   STBRP_HEURISTIC_Skyline_BF_sortHeight
+};
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// the details of the following structures don't matter to you, but they must
+// be visible so you can handle the memory allocations for them
+
+struct stbrp_node
+{
+   stbrp_coord  x,y;
+   stbrp_node  *next;
+};
+
+struct stbrp_context
+{
+   int width;
+   int height;
+   int align;
+   int init_mode;
+   int heuristic;
+   int num_nodes;
+   stbrp_node *active_head;
+   stbrp_node *free_head;
+   stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//     IMPLEMENTATION SECTION
+//
+
+#ifdef STB_RECT_PACK_IMPLEMENTATION
+#ifndef STBRP_SORT
+#include <stdlib.h>
+#define STBRP_SORT qsort
+#endif
+
+#ifndef STBRP_ASSERT
+#include <assert.h>
+#define STBRP_ASSERT assert
+#endif
+
+#ifdef _MSC_VER
+#define STBRP__NOTUSED(v)  (void)(v)
+#define STBRP__CDECL       __cdecl
+#else
+#define STBRP__NOTUSED(v)  (void)sizeof(v)
+#define STBRP__CDECL
+#endif
+
+enum
+{
+   STBRP__INIT_skyline = 1
+};
+
+STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
+{
+   switch (context->init_mode) {
+      case STBRP__INIT_skyline:
+         STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
+         context->heuristic = heuristic;
+         break;
+      default:
+         STBRP_ASSERT(0);
+   }
+}
+
+STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
+{
+   if (allow_out_of_mem)
+      // if it's ok to run out of memory, then don't bother aligning them;
+      // this gives better packing, but may fail due to OOM (even though
+      // the rectangles easily fit). @TODO a smarter approach would be to only
+      // quantize once we've hit OOM, then we could get rid of this parameter.
+      context->align = 1;
+   else {
+      // if it's not ok to run out of memory, then quantize the widths
+      // so that num_nodes is always enough nodes.
+      //
+      // I.e. num_nodes * align >= width
+      //                  align >= width / num_nodes
+      //                  align = ceil(width/num_nodes)
+
+      context->align = (context->width + context->num_nodes-1) / context->num_nodes;
+   }
+}
+
+STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
+{
+   int i;
+
+   for (i=0; i < num_nodes-1; ++i)
+      nodes[i].next = &nodes[i+1];
+   nodes[i].next = NULL;
+   context->init_mode = STBRP__INIT_skyline;
+   context->heuristic = STBRP_HEURISTIC_Skyline_default;
+   context->free_head = &nodes[0];
+   context->active_head = &context->extra[0];
+   context->width = width;
+   context->height = height;
+   context->num_nodes = num_nodes;
+   stbrp_setup_allow_out_of_mem(context, 0);
+
+   // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
+   context->extra[0].x = 0;
+   context->extra[0].y = 0;
+   context->extra[0].next = &context->extra[1];
+   context->extra[1].x = (stbrp_coord) width;
+   context->extra[1].y = (1<<30);
+   context->extra[1].next = NULL;
+}
+
+// find minimum y position if it starts at x1
+static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
+{
+   stbrp_node *node = first;
+   int x1 = x0 + width;
+   int min_y, visited_width, waste_area;
+
+   STBRP__NOTUSED(c);
+
+   STBRP_ASSERT(first->x <= x0);
+
+   #if 0
+   // skip in case we're past the node
+   while (node->next->x <= x0)
+      ++node;
+   #else
+   STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
+   #endif
+
+   STBRP_ASSERT(node->x <= x0);
+
+   min_y = 0;
+   waste_area = 0;
+   visited_width = 0;
+   while (node->x < x1) {
+      if (node->y > min_y) {
+         // raise min_y higher.
+         // we've accounted for all waste up to min_y,
+         // but we'll now add more waste for everything we've visted
+         waste_area += visited_width * (node->y - min_y);
+         min_y = node->y;
+         // the first time through, visited_width might be reduced
+         if (node->x < x0)
+            visited_width += node->next->x - x0;
+         else
+            visited_width += node->next->x - node->x;
+      } else {
+         // add waste area
+         int under_width = node->next->x - node->x;
+         if (under_width + visited_width > width)
+            under_width = width - visited_width;
+         waste_area += under_width * (min_y - node->y);
+         visited_width += under_width;
+      }
+      node = node->next;
+   }
+
+   *pwaste = waste_area;
+   return min_y;
+}
+
+typedef struct
+{
+   int x,y;
+   stbrp_node **prev_link;
+} stbrp__findresult;
+
+static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
+{
+   int best_waste = (1<<30), best_x, best_y = (1 << 30);
+   stbrp__findresult fr;
+   stbrp_node **prev, *node, *tail, **best = NULL;
+
+   // align to multiple of c->align
+   width = (width + c->align - 1);
+   width -= width % c->align;
+   STBRP_ASSERT(width % c->align == 0);
+
+   // if it can't possibly fit, bail immediately
+   if (width > c->width || height > c->height) {
+      fr.prev_link = NULL;
+      fr.x = fr.y = 0;
+      return fr;
+   }
+
+   node = c->active_head;
+   prev = &c->active_head;
+   while (node->x + width <= c->width) {
+      int y,waste;
+      y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
+      if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
+         // bottom left
+         if (y < best_y) {
+            best_y = y;
+            best = prev;
+         }
+      } else {
+         // best-fit
+         if (y + height <= c->height) {
+            // can only use it if it first vertically
+            if (y < best_y || (y == best_y && waste < best_waste)) {
+               best_y = y;
+               best_waste = waste;
+               best = prev;
+            }
+         }
+      }
+      prev = &node->next;
+      node = node->next;
+   }
+
+   best_x = (best == NULL) ? 0 : (*best)->x;
+
+   // if doing best-fit (BF), we also have to try aligning right edge to each node position
+   //
+   // e.g, if fitting
+   //
+   //     ____________________
+   //    |____________________|
+   //
+   //            into
+   //
+   //   |                         |
+   //   |             ____________|
+   //   |____________|
+   //
+   // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
+   //
+   // This makes BF take about 2x the time
+
+   if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
+      tail = c->active_head;
+      node = c->active_head;
+      prev = &c->active_head;
+      // find first node that's admissible
+      while (tail->x < width)
+         tail = tail->next;
+      while (tail) {
+         int xpos = tail->x - width;
+         int y,waste;
+         STBRP_ASSERT(xpos >= 0);
+         // find the left position that matches this
+         while (node->next->x <= xpos) {
+            prev = &node->next;
+            node = node->next;
+         }
+         STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
+         y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
+         if (y + height <= c->height) {
+            if (y <= best_y) {
+               if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
+                  best_x = xpos;
+                  STBRP_ASSERT(y <= best_y);
+                  best_y = y;
+                  best_waste = waste;
+                  best = prev;
+               }
+            }
+         }
+         tail = tail->next;
+      }
+   }
+
+   fr.prev_link = best;
+   fr.x = best_x;
+   fr.y = best_y;
+   return fr;
+}
+
+static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
+{
+   // find best position according to heuristic
+   stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
+   stbrp_node *node, *cur;
+
+   // bail if:
+   //    1. it failed
+   //    2. the best node doesn't fit (we don't always check this)
+   //    3. we're out of memory
+   if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
+      res.prev_link = NULL;
+      return res;
+   }
+
+   // on success, create new node
+   node = context->free_head;
+   node->x = (stbrp_coord) res.x;
+   node->y = (stbrp_coord) (res.y + height);
+
+   context->free_head = node->next;
+
+   // insert the new node into the right starting point, and
+   // let 'cur' point to the remaining nodes needing to be
+   // stiched back in
+
+   cur = *res.prev_link;
+   if (cur->x < res.x) {
+      // preserve the existing one, so start testing with the next one
+      stbrp_node *next = cur->next;
+      cur->next = node;
+      cur = next;
+   } else {
+      *res.prev_link = node;
+   }
+
+   // from here, traverse cur and free the nodes, until we get to one
+   // that shouldn't be freed
+   while (cur->next && cur->next->x <= res.x + width) {
+      stbrp_node *next = cur->next;
+      // move the current node to the free list
+      cur->next = context->free_head;
+      context->free_head = cur;
+      cur = next;
+   }
+
+   // stitch the list back in
+   node->next = cur;
+
+   if (cur->x < res.x + width)
+      cur->x = (stbrp_coord) (res.x + width);
+
+#ifdef _DEBUG
+   cur = context->active_head;
+   while (cur->x < context->width) {
+      STBRP_ASSERT(cur->x < cur->next->x);
+      cur = cur->next;
+   }
+   STBRP_ASSERT(cur->next == NULL);
+
+   {
+      int count=0;
+      cur = context->active_head;
+      while (cur) {
+         cur = cur->next;
+         ++count;
+      }
+      cur = context->free_head;
+      while (cur) {
+         cur = cur->next;
+         ++count;
+      }
+      STBRP_ASSERT(count == context->num_nodes+2);
+   }
+#endif
+
+   return res;
+}
+
+static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
+{
+   const stbrp_rect *p = (const stbrp_rect *) a;
+   const stbrp_rect *q = (const stbrp_rect *) b;
+   if (p->h > q->h)
+      return -1;
+   if (p->h < q->h)
+      return  1;
+   return (p->w > q->w) ? -1 : (p->w < q->w);
+}
+
+static int STBRP__CDECL rect_original_order(const void *a, const void *b)
+{
+   const stbrp_rect *p = (const stbrp_rect *) a;
+   const stbrp_rect *q = (const stbrp_rect *) b;
+   return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
+}
+
+STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
+{
+   int i, all_rects_packed = 1;
+
+   // we use the 'was_packed' field internally to allow sorting/unsorting
+   for (i=0; i < num_rects; ++i) {
+      rects[i].was_packed = i;
+   }
+
+   // sort according to heuristic
+   STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
+
+   for (i=0; i < num_rects; ++i) {
+      if (rects[i].w == 0 || rects[i].h == 0) {
+         rects[i].x = rects[i].y = 0;  // empty rect needs no space
+      } else {
+         stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
+         if (fr.prev_link) {
+            rects[i].x = (stbrp_coord) fr.x;
+            rects[i].y = (stbrp_coord) fr.y;
+         } else {
+            rects[i].x = rects[i].y = STBRP__MAXVAL;
+         }
+      }
+   }
+
+   // unsort
+   STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
+
+   // set was_packed flags and all_rects_packed status
+   for (i=0; i < num_rects; ++i) {
+      rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
+      if (!rects[i].was_packed)
+         all_rects_packed = 0;
+   }
+
+   // return the all_rects_packed status
+   return all_rects_packed;
+}
+#endif
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+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.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+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 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/src/interactivity/win32/window.hpp b/src/interactivity/win32/window.hpp
index c7a53aa74cf..45db4e7fbe6 100644
--- a/src/interactivity/win32/window.hpp
+++ b/src/interactivity/win32/window.hpp
@@ -16,9 +16,14 @@ Author(s):
 
 #include "../inc/IConsoleWindow.hpp"
 
-namespace Microsoft::Console::Render
+namespace Microsoft::Console::Render::Atlas
 {
     class AtlasEngine;
+}
+
+namespace Microsoft::Console::Render
+{
+    using AtlasEngine = Atlas::AtlasEngine;
     class DxEngine;
     class GdiEngine;
 }
diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp
index 5cf54d18583..2d0708bb82e 100644
--- a/src/renderer/atlas/AtlasEngine.api.cpp
+++ b/src/renderer/atlas/AtlasEngine.api.cpp
@@ -4,6 +4,7 @@
 #include "pch.h"
 #include "AtlasEngine.h"
 
+#include "Backend.h"
 #include "../base/FontCache.h"
 
 // #### NOTE ####
@@ -20,7 +21,7 @@
 #pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
 #pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
 
-using namespace Microsoft::Console::Render;
+using namespace Microsoft::Console::Render::Atlas;
 
 // Like gsl::narrow but returns a HRESULT.
 #pragma warning(push)
@@ -34,7 +35,7 @@ constexpr HRESULT api_narrow(U val, T& out) noexcept
 #pragma warning(pop)
 
 template<typename T, typename U>
-constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& out) noexcept
+constexpr HRESULT vec2_narrow(U x, U y, vec2<T>& out) noexcept
 {
     return api_narrow(x, out.x) | api_narrow(y, out.y);
 }
@@ -46,8 +47,8 @@ constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& out) noexcept
     //assert(psrRegion->top < psrRegion->bottom && psrRegion->top >= 0 && psrRegion->bottom <= _api.cellCount.y);
 
     // BeginPaint() protects against invalid out of bounds numbers.
-    _api.invalidatedRows.x = std::min(_api.invalidatedRows.x, gsl::narrow_cast<u16>(psrRegion->top));
-    _api.invalidatedRows.y = std::max(_api.invalidatedRows.y, gsl::narrow_cast<u16>(psrRegion->bottom));
+    _api.invalidatedRows.start = std::min(_api.invalidatedRows.start, gsl::narrow_cast<u16>(psrRegion->top));
+    _api.invalidatedRows.end = std::max(_api.invalidatedRows.end, gsl::narrow_cast<u16>(psrRegion->bottom));
     return S_OK;
 }
 
@@ -71,8 +72,8 @@ constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& out) noexcept
 
 [[nodiscard]] HRESULT AtlasEngine::InvalidateSystem(const til::rect* const prcDirtyClient) noexcept
 {
-    const auto top = prcDirtyClient->top / _api.fontMetrics.cellSize.y;
-    const auto bottom = prcDirtyClient->bottom / _api.fontMetrics.cellSize.y;
+    const auto top = prcDirtyClient->top / _api.s->font->cellSize.y;
+    const auto bottom = prcDirtyClient->bottom / _api.s->font->cellSize.y;
 
     // BeginPaint() protects against invalid out of bounds numbers.
     til::rect rect;
@@ -88,8 +89,8 @@ constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& out) noexcept
         // BeginPaint() protects against invalid out of bounds numbers.
         // TODO: rect can contain invalid out of bounds coordinates when the selection is being
         // dragged outside of the viewport (and the window begins scrolling automatically).
-        _api.invalidatedRows.x = gsl::narrow_cast<u16>(std::min<int>(_api.invalidatedRows.x, std::max<int>(0, rect.top)));
-        _api.invalidatedRows.y = gsl::narrow_cast<u16>(std::max<int>(_api.invalidatedRows.y, std::max<int>(0, rect.bottom)));
+        _api.invalidatedRows.start = gsl::narrow_cast<u16>(std::min<int>(_api.invalidatedRows.start, std::max<int>(0, rect.top)));
+        _api.invalidatedRows.end = gsl::narrow_cast<u16>(std::max<int>(_api.invalidatedRows.end, std::max<int>(0, rect.bottom)));
     }
     return S_OK;
 }
@@ -117,13 +118,13 @@ constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& out) noexcept
 
         if (delta < 0)
         {
-            _api.invalidatedRows.x = gsl::narrow_cast<u16>(clamp<int>(_api.invalidatedRows.x + delta, u16min, u16max));
-            _api.invalidatedRows.y = _api.cellCount.y;
+            _api.invalidatedRows.start = gsl::narrow_cast<u16>(clamp<int>(_api.invalidatedRows.start + delta, u16min, u16max));
+            _api.invalidatedRows.end = _api.s->cellCount.y;
         }
         else
         {
-            _api.invalidatedRows.x = 0;
-            _api.invalidatedRows.y = gsl::narrow_cast<u16>(clamp<int>(_api.invalidatedRows.y + delta, u16min, u16max));
+            _api.invalidatedRows.start = 0;
+            _api.invalidatedRows.end = gsl::narrow_cast<u16>(clamp<int>(_api.invalidatedRows.end + delta, u16min, u16max));
         }
     }
 
@@ -145,7 +146,7 @@ constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& out) noexcept
 
 [[nodiscard]] HRESULT AtlasEngine::InvalidateTitle(const std::wstring_view proposedTitle) noexcept
 {
-    WI_SetFlag(_api.invalidations, ApiInvalidations::Title);
+    _api.invalidatedTitle = true;
     return S_OK;
 }
 
@@ -161,6 +162,9 @@ constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& out) noexcept
 
 [[nodiscard]] HRESULT AtlasEngine::UpdateSoftFont(const std::span<const uint16_t> bitPattern, const til::size cellSize, const size_t centeringHint) noexcept
 {
+    const auto softFont = _api.s.write()->font.write();
+    softFont->softFontPattern = std::vector(bitPattern.begin(), bitPattern.end());
+    softFont->softFontCellSize = cellSize;
     return S_OK;
 }
 
@@ -169,10 +173,9 @@ constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& out) noexcept
     u16 newDPI;
     RETURN_IF_FAILED(api_narrow(dpi, newDPI));
 
-    if (_api.dpi != newDPI)
+    if (_api.s->font->dpi != newDPI)
     {
-        _api.dpi = newDPI;
-        WI_SetFlag(_api.invalidations, ApiInvalidations::Font);
+        _api.s.write()->font.write()->dpi = newDPI;
     }
 
     return S_OK;
@@ -184,10 +187,9 @@ constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& out) noexcept
         gsl::narrow_cast<u16>(std::max(1, srNewViewport.right - srNewViewport.left + 1)),
         gsl::narrow_cast<u16>(std::max(1, srNewViewport.bottom - srNewViewport.top + 1)),
     };
-    if (_api.cellCount != cellCount)
+    if (_api.s->cellCount != cellCount)
     {
-        _api.cellCount = cellCount;
-        WI_SetFlag(_api.invalidations, ApiInvalidations::Size);
+        _api.s.write()->cellCount = cellCount;
     }
     return S_OK;
 }
@@ -263,8 +265,8 @@ CATCH_RETURN()
 [[nodiscard]] HRESULT AtlasEngine::GetFontSize(_Out_ til::size* pFontSize) noexcept
 {
     RETURN_HR_IF_NULL(E_INVALIDARG, pFontSize);
-    pFontSize->width = _api.fontMetrics.cellSize.x;
-    pFontSize->height = _api.fontMetrics.cellSize.y;
+    pFontSize->width = _api.s->font->cellSize.x;
+    pFontSize->height = _api.s->font->cellSize.y;
     return S_OK;
 }
 
@@ -272,13 +274,25 @@ CATCH_RETURN()
 {
     RETURN_HR_IF_NULL(E_INVALIDARG, pResult);
 
+    wil::com_ptr<IDWriteTextFormat> textFormat;
+    RETURN_IF_FAILED(_p.dwriteFactory->CreateTextFormat(
+        /* fontFamilyName */ _api.s->font->fontName.c_str(),
+        /* fontCollection */ _api.s->font->fontCollection.get(),
+        /* fontWeight     */ static_cast<DWRITE_FONT_WEIGHT>(_api.s->font->fontWeight),
+        /* fontStyle      */ DWRITE_FONT_STYLE_NORMAL,
+        /* fontStretch    */ DWRITE_FONT_STRETCH_NORMAL,
+        /* fontSize       */ _api.s->font->fontSize,
+        /* localeName     */ L"",
+        /* textFormat     */ textFormat.put()));
+
     wil::com_ptr<IDWriteTextLayout> textLayout;
-    RETURN_IF_FAILED(_sr.dwriteFactory->CreateTextLayout(glyph.data(), gsl::narrow_cast<uint32_t>(glyph.size()), _getTextFormat(false, false), FLT_MAX, FLT_MAX, textLayout.addressof()));
+    RETURN_IF_FAILED(_p.dwriteFactory->CreateTextLayout(glyph.data(), gsl::narrow_cast<uint32_t>(glyph.size()), textFormat.get(), FLT_MAX, FLT_MAX, textLayout.addressof()));
 
-    DWRITE_TEXT_METRICS metrics;
+    DWRITE_TEXT_METRICS metrics{};
     RETURN_IF_FAILED(textLayout->GetMetrics(&metrics));
 
-    *pResult = static_cast<unsigned int>(std::ceilf(metrics.width)) > _api.fontMetrics.cellSize.x;
+    const auto minWidth = (_api.s->font->cellSize.x * 1.2f);
+    *pResult = metrics.width > minWidth;
     return S_OK;
 }
 
@@ -298,47 +312,46 @@ HRESULT AtlasEngine::Enable() noexcept
 
 [[nodiscard]] std::wstring_view AtlasEngine::GetPixelShaderPath() noexcept
 {
-    return _api.customPixelShaderPath;
+    return _api.s->misc->customPixelShaderPath;
 }
 
 [[nodiscard]] bool AtlasEngine::GetRetroTerminalEffect() const noexcept
 {
-    return _api.useRetroTerminalEffect;
+    return _api.s->misc->useRetroTerminalEffect;
 }
 
 [[nodiscard]] float AtlasEngine::GetScaling() const noexcept
 {
-    return static_cast<float>(_api.dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
+    return static_cast<f32>(_api.s->font->dpi) / static_cast<f32>(USER_DEFAULT_SCREEN_DPI);
 }
 
 [[nodiscard]] Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept
 {
-    assert(_api.fontMetrics.cellSize.x != 0);
-    assert(_api.fontMetrics.cellSize.y != 0);
-    return Types::Viewport::FromDimensions(viewInPixels.Origin(), { viewInPixels.Width() / _api.fontMetrics.cellSize.x, viewInPixels.Height() / _api.fontMetrics.cellSize.y });
+    assert(_api.s->font->cellSize.x != 0);
+    assert(_api.s->font->cellSize.y != 0);
+    return Types::Viewport::FromDimensions(viewInPixels.Origin(), { viewInPixels.Width() / _api.s->font->cellSize.x, viewInPixels.Height() / _api.s->font->cellSize.y });
 }
 
 [[nodiscard]] Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept
 {
-    assert(_api.fontMetrics.cellSize.x != 0);
-    assert(_api.fontMetrics.cellSize.y != 0);
-    return Types::Viewport::FromDimensions(viewInCharacters.Origin(), { viewInCharacters.Width() * _api.fontMetrics.cellSize.x, viewInCharacters.Height() * _api.fontMetrics.cellSize.y });
+    assert(_api.s->font->cellSize.x != 0);
+    assert(_api.s->font->cellSize.y != 0);
+    return Types::Viewport::FromDimensions(viewInCharacters.Origin(), { viewInCharacters.Width() * _api.s->font->cellSize.x, viewInCharacters.Height() * _api.s->font->cellSize.y });
 }
 
 void AtlasEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept
 {
-    const auto mode = gsl::narrow_cast<u8>(antialiasingMode);
+    const auto mode = static_cast<AntialiasingMode>(antialiasingMode);
     if (_api.antialiasingMode != mode)
     {
         _api.antialiasingMode = mode;
         _resolveTransparencySettings();
-        WI_SetFlag(_api.invalidations, ApiInvalidations::Font);
     }
 }
 
 void AtlasEngine::SetCallback(std::function<void(HANDLE)> pfn) noexcept
 {
-    _api.swapChainChangedCallback = std::move(pfn);
+    _p.swapChainChangedCallback = std::move(pfn);
 }
 
 void AtlasEngine::EnableTransparentBackground(const bool isTransparent) noexcept
@@ -347,7 +360,6 @@ void AtlasEngine::EnableTransparentBackground(const bool isTransparent) noexcept
     {
         _api.enableTransparentBackground = isTransparent;
         _resolveTransparencySettings();
-        WI_SetFlag(_api.invalidations, ApiInvalidations::SwapChain);
     }
 }
 
@@ -357,56 +369,53 @@ void AtlasEngine::SetForceFullRepaintRendering(bool enable) noexcept
 
 [[nodiscard]] HRESULT AtlasEngine::SetHwnd(const HWND hwnd) noexcept
 {
-    if (_api.hwnd != hwnd)
+    if (_api.s->target->hwnd != hwnd)
     {
-        _api.hwnd = hwnd;
-        WI_SetFlag(_api.invalidations, ApiInvalidations::SwapChain);
+        _api.s.write()->target.write()->hwnd = hwnd;
     }
     return S_OK;
 }
 
 void AtlasEngine::SetPixelShaderPath(std::wstring_view value) noexcept
+try
 {
-    if (_api.customPixelShaderPath != value)
+    if (_api.s->misc->customPixelShaderPath != value)
     {
-        _api.customPixelShaderPath = value;
+        _api.s.write()->misc.write()->customPixelShaderPath = value;
         _resolveTransparencySettings();
-        WI_SetFlag(_api.invalidations, ApiInvalidations::Device);
     }
 }
+CATCH_LOG()
 
 void AtlasEngine::SetRetroTerminalEffect(bool enable) noexcept
 {
-    if (_api.useRetroTerminalEffect != enable)
+    if (_api.s->misc->useRetroTerminalEffect != enable)
     {
-        _api.useRetroTerminalEffect = enable;
+        _api.s.write()->misc.write()->useRetroTerminalEffect = enable;
         _resolveTransparencySettings();
-        WI_SetFlag(_api.invalidations, ApiInvalidations::Device);
     }
 }
 
 void AtlasEngine::SetSelectionBackground(const COLORREF color, const float alpha) noexcept
 {
-    const u32 selectionColor = (color & 0xffffff) | gsl::narrow_cast<u32>(std::lroundf(alpha * 255.0f)) << 24;
-    if (_api.selectionColor != selectionColor)
+    const u32 selectionColor = (color & 0xffffff) | gsl::narrow_cast<u32>(lrintf(alpha * 255.0f)) << 24;
+    if (_api.s->misc->selectionColor != selectionColor)
     {
-        _api.selectionColor = selectionColor;
-        WI_SetFlag(_api.invalidations, ApiInvalidations::Settings);
+        _api.s.write()->misc.write()->selectionColor = selectionColor;
     }
 }
 
 void AtlasEngine::SetSoftwareRendering(bool enable) noexcept
 {
-    if (_api.useSoftwareRendering != enable)
+    if (_api.s->target->useSoftwareRendering != enable)
     {
-        _api.useSoftwareRendering = enable;
-        WI_SetFlag(_api.invalidations, ApiInvalidations::Device);
+        _api.s.write()->target.write()->useSoftwareRendering = enable;
     }
 }
 
 void AtlasEngine::SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept
 {
-    _api.warningCallback = std::move(pfn);
+    _p.warningCallback = std::move(pfn);
 }
 
 [[nodiscard]] HRESULT AtlasEngine::SetWindowSize(const til::size pixels) noexcept
@@ -418,10 +427,9 @@ void AtlasEngine::SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept
     // When Win+D is pressed, `TriggerRedrawCursor` is called and a render pass is initiated.
     // As conhost is in the background, GetClientRect will return {0,0} and we'll get called with {0,0}.
     // This isn't a valid value for _api.sizeInPixel and would crash _recreateSizeDependentResources().
-    if (_api.sizeInPixel != newSize && newSize != u16x2{})
+    if (_api.s->targetSize != newSize && newSize != u16x2{})
     {
-        _api.sizeInPixel = newSize;
-        WI_SetFlag(_api.invalidations, ApiInvalidations::Size);
+        _api.s.write()->targetSize = newSize;
     }
 
     return S_OK;
@@ -467,10 +475,18 @@ void AtlasEngine::_resolveTransparencySettings() noexcept
     // If the user asks for ClearType, but also for a transparent background
     // (which our ClearType shader doesn't simultaneously support)
     // then we need to sneakily force the renderer to grayscale AA.
-    _api.realizedAntialiasingMode = _api.enableTransparentBackground && _api.antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : _api.antialiasingMode;
-    // An opaque background allows us to use true "independent" flips. See AtlasEngine::_createSwapChain().
-    // We can't enable them if custom shaders are specified, because it's unknown, whether they support opaque inputs.
-    _api.backgroundOpaqueMixin = _api.enableTransparentBackground || !_api.customPixelShaderPath.empty() || _api.useRetroTerminalEffect ? 0x00000000 : 0xff000000;
+    const auto antialiasingMode = _api.enableTransparentBackground && _api.antialiasingMode == AntialiasingMode::ClearType ? AntialiasingMode::Grayscale : _api.antialiasingMode;
+    const bool enableTransparentBackground = _api.enableTransparentBackground || !_api.s->misc->customPixelShaderPath.empty() || _api.s->misc->useRetroTerminalEffect;
+
+    if (antialiasingMode != _api.s->font->antialiasingMode || enableTransparentBackground != _api.s->target->enableTransparentBackground)
+    {
+        const auto s = _api.s.write();
+        s->font.write()->antialiasingMode = antialiasingMode;
+        // An opaque background allows us to use true "independent" flips. See AtlasEngine::_createSwapChain().
+        // We can't enable them if custom shaders are specified, because it's unknown, whether they support opaque inputs.
+        s->target.write()->enableTransparentBackground = enableTransparentBackground;
+        _api.backgroundOpaqueMixin = enableTransparentBackground ? 0x00000000 : 0xff000000;
+    }
 }
 
 void AtlasEngine::_updateFont(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes)
@@ -486,9 +502,9 @@ void AtlasEngine::_updateFont(const wchar_t* faceName, const FontInfoDesired& fo
         // Gsub is for GetGlyphs() and Gpos for GetGlyphPlacements().
         //
         // GH#10774: Apparently specifying all of the features is just redundant.
-        fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES, 1 });
-        fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_LIGATURES, 1 });
-        fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES, 1 });
+        fontFeatures.emplace_back(DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES, 1);
+        fontFeatures.emplace_back(DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_LIGATURES, 1);
+        fontFeatures.emplace_back(DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES, 1);
 
         for (const auto& p : features)
         {
@@ -507,7 +523,7 @@ void AtlasEngine::_updateFont(const wchar_t* faceName, const FontInfoDesired& fo
                     fontFeatures[2].parameter = p.second;
                     break;
                 default:
-                    fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ tag, p.second });
+                    fontFeatures.emplace_back(tag, p.second);
                     break;
                 }
             }
@@ -521,9 +537,9 @@ void AtlasEngine::_updateFont(const wchar_t* faceName, const FontInfoDesired& fo
 
         // AtlasEngine::_recreateFontDependentResources() relies on these fields to
         // exist in this particular order in order to create appropriate default axes.
-        fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_WEIGHT, -1.0f });
-        fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_ITALIC, -1.0f });
-        fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_SLANT, -1.0f });
+        fontAxisValues.emplace_back(DWRITE_FONT_AXIS_TAG_WEIGHT, -1.0f);
+        fontAxisValues.emplace_back(DWRITE_FONT_AXIS_TAG_ITALIC, -1.0f);
+        fontAxisValues.emplace_back(DWRITE_FONT_AXIS_TAG_SLANT, -1.0f);
 
         for (const auto& p : axes)
         {
@@ -542,27 +558,20 @@ void AtlasEngine::_updateFont(const wchar_t* faceName, const FontInfoDesired& fo
                     fontAxisValues[2].value = p.second;
                     break;
                 default:
-                    fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ tag, p.second });
+                    fontAxisValues.emplace_back(tag, p.second);
                     break;
                 }
             }
         }
     }
 
-    const auto previousCellSize = _api.fontMetrics.cellSize;
-    _resolveFontMetrics(faceName, fontInfoDesired, fontInfo, &_api.fontMetrics);
-    _api.fontFeatures = std::move(fontFeatures);
-    _api.fontAxisValues = std::move(fontAxisValues);
-
-    WI_SetFlag(_api.invalidations, ApiInvalidations::Font);
-
-    if (previousCellSize != _api.fontMetrics.cellSize)
-    {
-        WI_SetFlag(_api.invalidations, ApiInvalidations::Size);
-    }
+    const auto font = _api.s.write()->font.write();
+    _resolveFontMetrics(faceName, fontInfoDesired, fontInfo, font);
+    font->fontFeatures = std::move(fontFeatures);
+    font->fontAxisValues = std::move(fontAxisValues);
 }
 
-void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics) const
+void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontSettings* fontMetrics) const
 {
     const auto requestedFamily = fontInfoDesired.GetFamily();
     auto requestedWeight = fontInfoDesired.GetWeight();
@@ -603,40 +612,42 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
     wil::com_ptr<IDWriteFontFace> fontFace;
     THROW_IF_FAILED(font->CreateFontFace(fontFace.addressof()));
 
-    DWRITE_FONT_METRICS metrics;
+    DWRITE_FONT_METRICS metrics{};
     fontFace->GetMetrics(&metrics);
 
-    // According to Wikipedia:
-    // > One em was traditionally defined as the width of the capital 'M' in the current typeface and point size,
-    // > because the 'M' was commonly cast the full-width of the square blocks [...] which are used in printing presses.
-    // Even today M is often the widest character in a font that supports ASCII.
-    // In the future a more robust solution could be written, until then this simple solution works for most cases.
-    static constexpr u32 codePoint = L'M';
-    u16 glyphIndex;
-    THROW_IF_FAILED(fontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex));
-
-    DWRITE_GLYPH_METRICS glyphMetrics;
-    THROW_IF_FAILED(fontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics));
-
     // Point sizes are commonly treated at a 72 DPI scale
     // (including by OpenType), whereas DirectWrite uses 96 DPI.
     // Since we want the height in px we multiply by the display's DPI.
-    const auto fontSizeInDIP = fontSize / 72.0f * 96.0f;
-    const auto fontSizeInPx = fontSize / 72.0f * _api.dpi;
-
-    const auto designUnitsPerPx = fontSizeInPx / static_cast<float>(metrics.designUnitsPerEm);
-    const auto ascent = static_cast<float>(metrics.ascent) * designUnitsPerPx;
-    const auto descent = static_cast<float>(metrics.descent) * designUnitsPerPx;
-    const auto lineGap = static_cast<float>(metrics.lineGap) * designUnitsPerPx;
-    const auto underlinePosition = static_cast<float>(-metrics.underlinePosition) * designUnitsPerPx;
-    const auto underlineThickness = static_cast<float>(metrics.underlineThickness) * designUnitsPerPx;
-    const auto strikethroughPosition = static_cast<float>(-metrics.strikethroughPosition) * designUnitsPerPx;
-    const auto strikethroughThickness = static_cast<float>(metrics.strikethroughThickness) * designUnitsPerPx;
-    const auto advanceWidth = static_cast<float>(glyphMetrics.advanceWidth) * designUnitsPerPx;
+    const auto dpi = static_cast<f32>(_api.s->font->dpi);
+    const auto fontSizeInPx = fontSize / 72.0f * dpi;
+
+    const auto designUnitsPerPx = fontSizeInPx / static_cast<f32>(metrics.designUnitsPerEm);
+    const auto ascent = static_cast<f32>(metrics.ascent) * designUnitsPerPx;
+    const auto descent = static_cast<f32>(metrics.descent) * designUnitsPerPx;
+    const auto lineGap = static_cast<f32>(metrics.lineGap) * designUnitsPerPx;
+    const auto underlinePosition = static_cast<f32>(-metrics.underlinePosition) * designUnitsPerPx;
+    const auto underlineThickness = static_cast<f32>(metrics.underlineThickness) * designUnitsPerPx;
+    const auto strikethroughPosition = static_cast<f32>(-metrics.strikethroughPosition) * designUnitsPerPx;
+    const auto strikethroughThickness = static_cast<f32>(metrics.strikethroughThickness) * designUnitsPerPx;
     const auto advanceHeight = ascent + descent + lineGap;
 
-    auto adjustedWidth = std::roundf(fontInfoDesired.GetCellWidth().Resolve(advanceWidth, _api.dpi, fontSizeInPx, advanceWidth));
-    auto adjustedHeight = std::roundf(fontInfoDesired.GetCellHeight().Resolve(advanceHeight, _api.dpi, fontSizeInPx, advanceWidth));
+    // We use the same character to determine the advance width as CSS for its "ch" unit ("0").
+    // According to the CSS spec, if it's impossible to determine the advance width,
+    // it must be assumed to be 0.5em wide. em in CSS refers to the computed font-size.
+    auto advanceWidth = 0.5f * fontSizeInPx;
+    {
+        static constexpr u32 codePoint = '0';
+        u16 glyphIndex;
+        if (SUCCEEDED(fontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex)))
+        {
+            DWRITE_GLYPH_METRICS glyphMetrics{};
+            THROW_IF_FAILED(fontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics, FALSE));
+            advanceWidth = static_cast<f32>(glyphMetrics.advanceWidth) * designUnitsPerPx;
+        }
+    }
+
+    auto adjustedWidth = std::roundf(fontInfoDesired.GetCellWidth().Resolve(advanceWidth, dpi, fontSizeInPx, advanceWidth));
+    auto adjustedHeight = std::roundf(fontInfoDesired.GetCellHeight().Resolve(advanceHeight, dpi, fontSizeInPx, advanceWidth));
 
     // Protection against bad user values in GetCellWidth/Y.
     // AtlasEngine fails hard with 0 cell sizes.
@@ -667,13 +678,13 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
     doubleUnderlinePosTop = std::max(doubleUnderlinePosTop, baseline + thinLineWidth);
     // 5. The gap is only the distance _between_ the lines, but we need the distance from the
     //    top border of the top and bottom lines, which includes an additional line width.
-    const auto doubleUnderlineGap = std::max(1.0f, std::roundf(1.2f / 72.0f * _api.dpi));
+    const auto doubleUnderlineGap = std::max(1.0f, std::roundf(1.2f / 72.0f * dpi));
     doubleUnderlinePosBottom = std::max(doubleUnderlinePosBottom, doubleUnderlinePosTop + doubleUnderlineGap + thinLineWidth);
     // Our cells can't overlap each other so we additionally clamp the bottom line to be inside the cell boundaries.
     doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, adjustedHeight - thinLineWidth);
 
-    const auto cellWidth = gsl::narrow<u16>(std::lroundf(adjustedWidth));
-    const auto cellHeight = gsl::narrow<u16>(std::lroundf(adjustedHeight));
+    const auto cellWidth = gsl::narrow<u16>(lrintf(adjustedWidth));
+    const auto cellHeight = gsl::narrow<u16>(lrintf(adjustedHeight));
 
     {
         til::size coordSize;
@@ -685,7 +696,7 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
             // The coordSizeUnscaled parameter to SetFromEngine is used for API functions like GetConsoleFontSize.
             // Since clients expect that settings the font height to Y yields back a font height of Y,
             // we're scaling the X relative/proportional to the actual cellWidth/cellHeight ratio.
-            requestedSize.width = gsl::narrow_cast<til::CoordType>(std::lroundf(fontSize / cellHeight * cellWidth));
+            requestedSize.width = gsl::narrow_cast<til::CoordType>(lrintf(fontSize / cellHeight * cellWidth));
         }
 
         fontInfo.SetFromEngine(requestedFaceName, requestedFamily, requestedWeight, false, coordSize, requestedSize);
@@ -695,13 +706,20 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
     {
         std::wstring fontName{ requestedFaceName };
         const auto fontWeightU16 = gsl::narrow_cast<u16>(requestedWeight);
-        const auto underlinePosU16 = gsl::narrow_cast<u16>(underlinePos);
-        const auto underlineWidthU16 = gsl::narrow_cast<u16>(underlineWidth);
-        const auto strikethroughPosU16 = gsl::narrow_cast<u16>(strikethroughPos);
-        const auto strikethroughWidthU16 = gsl::narrow_cast<u16>(strikethroughWidth);
-        const auto doubleUnderlinePosTopU16 = gsl::narrow_cast<u16>(doubleUnderlinePosTop);
-        const auto doubleUnderlinePosBottomU16 = gsl::narrow_cast<u16>(doubleUnderlinePosBottom);
-        const auto thinLineWidthU16 = gsl::narrow_cast<u16>(thinLineWidth);
+        const auto advanceWidthU16 = gsl::narrow_cast<u16>(lrintf(advanceWidth));
+        const auto baselineU16 = gsl::narrow_cast<u16>(lrintf(baseline));
+        const auto descenderU16 = gsl::narrow_cast<u16>(cellHeight - baselineU16);
+        const auto thinLineWidthU16 = gsl::narrow_cast<u16>(lrintf(thinLineWidth));
+
+        const auto gridBottomPositionU16 = gsl::narrow_cast<u16>(cellHeight - thinLineWidth);
+        const auto gridRightPositionU16 = gsl::narrow_cast<u16>(cellWidth - thinLineWidth);
+
+        const auto underlinePosU16 = gsl::narrow_cast<u16>(lrintf(underlinePos));
+        const auto underlineWidthU16 = gsl::narrow_cast<u16>(lrintf(underlineWidth));
+        const auto strikethroughPosU16 = gsl::narrow_cast<u16>(lrintf(strikethroughPos));
+        const auto strikethroughWidthU16 = gsl::narrow_cast<u16>(lrintf(strikethroughWidth));
+        const auto doubleUnderlinePosTopU16 = gsl::narrow_cast<u16>(lrintf(doubleUnderlinePosTop));
+        const auto doubleUnderlinePosBottomU16 = gsl::narrow_cast<u16>(lrintf(doubleUnderlinePosBottom));
 
         // NOTE: From this point onward no early returns or throwing code should exist,
         // as we might cause _api to be in an inconsistent state otherwise.
@@ -709,16 +727,23 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
         fontMetrics->fontCollection = std::move(fontCollection);
         fontMetrics->fontFamily = std::move(fontFamily);
         fontMetrics->fontName = std::move(fontName);
-        fontMetrics->fontSizeInDIP = fontSizeInDIP;
-        fontMetrics->baselineInDIP = baseline / static_cast<float>(_api.dpi) * 96.0f;
-        fontMetrics->advanceScale = cellWidth / advanceWidth;
+        fontMetrics->fontSize = fontSizeInPx;
         fontMetrics->cellSize = { cellWidth, cellHeight };
         fontMetrics->fontWeight = fontWeightU16;
-        fontMetrics->underlinePos = underlinePosU16;
-        fontMetrics->underlineWidth = underlineWidthU16;
-        fontMetrics->strikethroughPos = strikethroughPosU16;
-        fontMetrics->strikethroughWidth = strikethroughWidthU16;
-        fontMetrics->doubleUnderlinePos = { doubleUnderlinePosTopU16, doubleUnderlinePosBottomU16 };
+        fontMetrics->advanceWidth = advanceWidthU16;
+        fontMetrics->baseline = baselineU16;
+        fontMetrics->descender = descenderU16;
         fontMetrics->thinLineWidth = thinLineWidthU16;
+
+        fontMetrics->gridTop = { 0, thinLineWidthU16 };
+        fontMetrics->gridBottom = { gridBottomPositionU16, thinLineWidthU16 };
+        fontMetrics->gridLeft = { 0, thinLineWidthU16 };
+        fontMetrics->gridRight = { gridRightPositionU16, thinLineWidthU16 };
+
+        fontMetrics->underline = { underlinePosU16, underlineWidthU16 };
+        fontMetrics->strikethrough = { strikethroughPosU16, strikethroughWidthU16 };
+        fontMetrics->doubleUnderline[0] = { doubleUnderlinePosTopU16, thinLineWidthU16 };
+        fontMetrics->doubleUnderline[1] = { doubleUnderlinePosBottomU16, thinLineWidthU16 };
+        fontMetrics->overline = { 0, underlineWidthU16 };
     }
 }
diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp
index d6f1562cead..a5f8a95cb6a 100644
--- a/src/renderer/atlas/AtlasEngine.cpp
+++ b/src/renderer/atlas/AtlasEngine.cpp
@@ -4,11 +4,8 @@
 #include "pch.h"
 #include "AtlasEngine.h"
 
-#include <custom_shader_ps.h>
-#include <custom_shader_vs.h>
-#include <shader_ps.h>
-#include <shader_vs.h>
-
+#include "Backend.h"
+#include "DWriteTextAnalysis.h"
 #include "../../interactivity/win32/CustomWindowMessages.h"
 
 // #### NOTE ####
@@ -25,44 +22,27 @@
 #pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
 #pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
 
-using namespace Microsoft::Console::Render;
+using namespace Microsoft::Console::Render::Atlas;
 
 #pragma warning(suppress : 26455) // Default constructor may not throw. Declare it 'noexcept' (f.6).
 AtlasEngine::AtlasEngine()
 {
 #ifdef NDEBUG
-    THROW_IF_FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, _sr.d2dFactory.addressof()));
+    THROW_IF_FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(_p.d2dFactory), nullptr, reinterpret_cast<void**>(_p.d2dFactory.addressof())));
 #else
-    static constexpr D2D1_FACTORY_OPTIONS options{ D2D1_DEBUG_LEVEL_INFORMATION };
-    THROW_IF_FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, options, _sr.d2dFactory.addressof()));
+    static constexpr D2D1_FACTORY_OPTIONS options{ .debugLevel = D2D1_DEBUG_LEVEL_INFORMATION };
+    THROW_IF_FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(_p.d2dFactory), &options, reinterpret_cast<void**>(_p.d2dFactory.addressof())));
 #endif
 
-    THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(_sr.dwriteFactory), reinterpret_cast<::IUnknown**>(_sr.dwriteFactory.addressof())));
-    if (const auto factory2 = _sr.dwriteFactory.try_query<IDWriteFactory2>())
-    {
-        THROW_IF_FAILED(factory2->GetSystemFontFallback(_sr.systemFontFallback.addressof()));
-    }
-    {
-        wil::com_ptr<IDWriteTextAnalyzer> textAnalyzer;
-        THROW_IF_FAILED(_sr.dwriteFactory->CreateTextAnalyzer(textAnalyzer.addressof()));
-        _sr.textAnalyzer = textAnalyzer.query<IDWriteTextAnalyzer1>();
-    }
+    THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(_p.dwriteFactory), reinterpret_cast<::IUnknown**>(_p.dwriteFactory.addressof())));
+    _p.dwriteFactory4 = _p.dwriteFactory.try_query<IDWriteFactory4>();
 
-    _sr.isWindows10OrGreater = IsWindows10OrGreater();
+    THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontFallback(_p.systemFontFallback.addressof()));
+    _p.systemFontFallback1 = _p.systemFontFallback.try_query<IDWriteFontFallback1>();
 
-#ifndef NDEBUG
-    {
-        _sr.sourceDirectory = std::filesystem::path{ __FILE__ }.parent_path();
-        _sr.sourceCodeWatcher = wil::make_folder_change_reader_nothrow(_sr.sourceDirectory.c_str(), false, wil::FolderChangeEvents::FileName | wil::FolderChangeEvents::LastWriteTime, [this](wil::FolderChangeEvent, PCWSTR path) {
-            if (til::ends_with(path, L".hlsl"))
-            {
-                auto expected = INT64_MAX;
-                const auto invalidationTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
-                _sr.sourceCodeInvalidationTime.compare_exchange_strong(expected, invalidationTime.time_since_epoch().count(), std::memory_order_relaxed);
-            }
-        });
-    }
-#endif
+    wil::com_ptr<IDWriteTextAnalyzer> textAnalyzer;
+    THROW_IF_FAILED(_p.dwriteFactory->CreateTextAnalyzer(textAnalyzer.addressof()));
+    _p.textAnalyzer = textAnalyzer.query<IDWriteTextAnalyzer1>();
 }
 
 #pragma region IRenderEngine
@@ -72,103 +52,25 @@ AtlasEngine::AtlasEngine()
 [[nodiscard]] HRESULT AtlasEngine::StartPaint() noexcept
 try
 {
-    if (_api.hwnd)
+    if (const auto hwnd = _api.s->target->hwnd)
     {
         RECT rect;
-        LOG_IF_WIN32_BOOL_FALSE(GetClientRect(_api.hwnd, &rect));
+        LOG_IF_WIN32_BOOL_FALSE(GetClientRect(hwnd, &rect));
         std::ignore = SetWindowSize({ rect.right - rect.left, rect.bottom - rect.top });
 
-        if (WI_IsFlagSet(_api.invalidations, ApiInvalidations::Title))
+        if (_api.invalidatedTitle)
         {
-            LOG_IF_WIN32_BOOL_FALSE(PostMessageW(_api.hwnd, CM_UPDATE_TITLE, 0, 0));
-            WI_ClearFlag(_api.invalidations, ApiInvalidations::Title);
+            LOG_IF_WIN32_BOOL_FALSE(PostMessageW(hwnd, CM_UPDATE_TITLE, 0, 0));
+            _api.invalidatedTitle = false;
         }
     }
 
-    // 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 (_api.invalidations != ApiInvalidations::None)
+    if (_p.s != _api.s)
     {
-        RETURN_HR_IF(E_UNEXPECTED, _api.cellCount == u16x2{});
-
-        if (WI_IsFlagSet(_api.invalidations, ApiInvalidations::Device))
-        {
-            _createResources();
-        }
-        if (WI_IsFlagSet(_api.invalidations, ApiInvalidations::SwapChain))
-        {
-            _createSwapChain();
-        }
-        if (WI_IsFlagSet(_api.invalidations, ApiInvalidations::Size))
-        {
-            _recreateSizeDependentResources();
-        }
-        if (WI_IsFlagSet(_api.invalidations, ApiInvalidations::Font))
-        {
-            _recreateFontDependentResources();
-        }
-        if (WI_IsFlagSet(_api.invalidations, ApiInvalidations::Settings))
-        {
-            _r.selectionColor = _api.selectionColor;
-            WI_SetFlag(_r.invalidations, RenderInvalidations::ConstBuffer);
-            WI_ClearFlag(_api.invalidations, ApiInvalidations::Settings);
-        }
-
-        // Equivalent to InvalidateAll().
-        _api.invalidatedRows = invalidatedRowsAll;
+        _handleSettingsUpdate();
     }
 
-#ifndef NDEBUG
-    if (const auto invalidationTime = _sr.sourceCodeInvalidationTime.load(std::memory_order_relaxed); invalidationTime != INT64_MAX && invalidationTime <= std::chrono::steady_clock::now().time_since_epoch().count())
-    {
-        _sr.sourceCodeInvalidationTime.store(INT64_MAX, std::memory_order_relaxed);
-
-        try
-        {
-            static const auto compile = [](const std::filesystem::path& path, const char* target) {
-                wil::com_ptr<ID3DBlob> error;
-                wil::com_ptr<ID3DBlob> blob;
-                const auto hr = D3DCompileFromFile(
-                    /* pFileName   */ path.c_str(),
-                    /* pDefines    */ nullptr,
-                    /* pInclude    */ D3D_COMPILE_STANDARD_FILE_INCLUDE,
-                    /* pEntrypoint */ "main",
-                    /* pTarget     */ target,
-                    /* Flags1      */ D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS,
-                    /* Flags2      */ 0,
-                    /* ppCode      */ blob.addressof(),
-                    /* ppErrorMsgs */ error.addressof());
-
-                if (error)
-                {
-                    std::thread t{ [error = std::move(error)]() noexcept {
-                        MessageBoxA(nullptr, static_cast<const char*>(error->GetBufferPointer()), "Compilation error", MB_ICONERROR | MB_OK);
-                    } };
-                    t.detach();
-                }
-
-                THROW_IF_FAILED(hr);
-                return blob;
-            };
-
-            const auto vs = compile(_sr.sourceDirectory / L"shader_vs.hlsl", "vs_4_1");
-            const auto ps = compile(_sr.sourceDirectory / L"shader_ps.hlsl", "ps_4_1");
-
-            THROW_IF_FAILED(_r.device->CreateVertexShader(vs->GetBufferPointer(), vs->GetBufferSize(), nullptr, _r.vertexShader.put()));
-            THROW_IF_FAILED(_r.device->CreatePixelShader(ps->GetBufferPointer(), ps->GetBufferSize(), nullptr, _r.pixelShader.put()));
-            _setShaderResources();
-        }
-        CATCH_LOG()
-    }
-#endif
-
-    if constexpr (debugGlyphGenerationPerformance)
-    {
-        _r.glyphs = {};
-        _r.tileAllocator = TileAllocator{ _api.fontMetrics.cellSize, _api.sizeInPixel };
-    }
-    if constexpr (debugTextParsingPerformance)
+    if constexpr (ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION)
     {
         _api.invalidatedRows = invalidatedRowsAll;
         _api.scrollOffset = 0;
@@ -176,126 +78,167 @@ try
 
     // Clamp invalidation rects into valid value ranges.
     {
-        _api.invalidatedCursorArea.left = std::min(_api.invalidatedCursorArea.left, _api.cellCount.x);
-        _api.invalidatedCursorArea.top = std::min(_api.invalidatedCursorArea.top, _api.cellCount.y);
-        _api.invalidatedCursorArea.right = clamp(_api.invalidatedCursorArea.right, _api.invalidatedCursorArea.left, _api.cellCount.x);
-        _api.invalidatedCursorArea.bottom = clamp(_api.invalidatedCursorArea.bottom, _api.invalidatedCursorArea.top, _api.cellCount.y);
+        _api.invalidatedCursorArea.left = std::min(_api.invalidatedCursorArea.left, _p.s->cellCount.x);
+        _api.invalidatedCursorArea.top = std::min(_api.invalidatedCursorArea.top, _p.s->cellCount.y);
+        _api.invalidatedCursorArea.right = clamp(_api.invalidatedCursorArea.right, _api.invalidatedCursorArea.left, _p.s->cellCount.x);
+        _api.invalidatedCursorArea.bottom = clamp(_api.invalidatedCursorArea.bottom, _api.invalidatedCursorArea.top, _p.s->cellCount.y);
     }
     {
-        _api.invalidatedRows.x = std::min(_api.invalidatedRows.x, _api.cellCount.y);
-        _api.invalidatedRows.y = clamp(_api.invalidatedRows.y, _api.invalidatedRows.x, _api.cellCount.y);
+        _api.invalidatedRows.start = std::min(_api.invalidatedRows.start, _p.s->cellCount.y);
+        _api.invalidatedRows.end = clamp(_api.invalidatedRows.end, _api.invalidatedRows.start, _p.s->cellCount.y);
     }
+
+    const auto allInvalid = _api.invalidatedRows == range<u16>{ 0, _p.s->cellCount.y };
+
+    // Avoid scrolling if everything's invalid anyways. This isn't here for performance or correctness
+    // (the code also works without this), but rather because it helps me reason about the way this works.
+    // For instance it ensures we don't pass a scroll rect to Present1() when effectively nothing is scrolling.
+    if (allInvalid)
     {
-        const auto limit = gsl::narrow_cast<i16>(_api.cellCount.y & 0x7fff);
-        _api.scrollOffset = gsl::narrow_cast<i16>(clamp<int>(_api.scrollOffset, -limit, limit));
+        _api.scrollOffset = 0;
     }
-
-    // Scroll the buffer by the given offset and mark the newly uncovered rows as "invalid".
-    if (_api.scrollOffset != 0)
+    else
     {
-        const auto nothingInvalid = _api.invalidatedRows.x == _api.invalidatedRows.y;
-        const auto offset = static_cast<ptrdiff_t>(_api.scrollOffset) * _api.cellCount.x;
+        const auto limit = gsl::narrow_cast<i16>(_p.s->cellCount.y & 0x7fff);
+        const auto offset = gsl::narrow_cast<i16>(clamp<int>(_api.scrollOffset, -limit, limit));
+
+        _api.scrollOffset = offset;
 
-        if (_api.scrollOffset < 0)
+        // Scroll the buffer by the given offset and mark the newly uncovered rows as "invalid".
+        if (offset)
         {
-            // Scroll up (for instance when new text is being written at the end of the buffer).
-            const u16 endRow = _api.cellCount.y + _api.scrollOffset;
-            _api.invalidatedRows.x = nothingInvalid ? endRow : std::min<u16>(_api.invalidatedRows.x, endRow);
-            _api.invalidatedRows.y = _api.cellCount.y;
-
-            // scrollOffset/offset = -1
-            // +----------+    +----------+
-            // |          |    | xxxxxxxxx|         + dst  < beg
-            // | xxxxxxxxx| -> |xxxxxxx   |  + src  |      < beg - offset
-            // |xxxxxxx   |    |          |  |      v
-            // +----------+    +----------+  v             < end
-            {
-                const auto beg = _r.cells.begin();
-                const auto end = _r.cells.end();
-                std::move(beg - offset, end, beg);
-            }
+            const auto nothingInvalid = _api.invalidatedRows.start == _api.invalidatedRows.end;
+
+            if (offset < 0)
             {
-                const auto beg = _r.cellGlyphMapping.begin();
-                const auto end = _r.cellGlyphMapping.end();
-                std::move(beg - offset, end, beg);
+                // scrollOffset/offset = -1
+                // +----------+    +----------+
+                // |          |    | xxxxxxxxx|
+                // | xxxxxxxxx| -> |xxxxxxx   |
+                // |xxxxxxx   |    |          |
+                // +----------+    +----------+
+                const u16 begRow = _p.s->cellCount.y + offset;
+                _api.invalidatedRows.start = nothingInvalid ? begRow : std::min(_api.invalidatedRows.start, begRow);
+                _api.invalidatedRows.end = _p.s->cellCount.y;
+
+                const auto dst = std::copy_n(_p.rows.begin() - offset, _p.rows.size() + offset, _p.rowsScratch.begin());
+                std::copy_n(_p.rows.begin(), -offset, dst);
             }
-        }
-        else
-        {
-            // Scroll down.
-            _api.invalidatedRows.x = 0;
-            _api.invalidatedRows.y = nothingInvalid ? _api.scrollOffset : std::max<u16>(_api.invalidatedRows.y, _api.scrollOffset);
-
-            // scrollOffset/offset = 1
-            // +----------+    +----------+
-            // | xxxxxxxxx|    |          |  + src         < beg
-            // |xxxxxxx   | -> | xxxxxxxxx|  |      ^
-            // |          |    |xxxxxxx   |  v      |      < end - offset
-            // +----------+    +----------+         + dst  < end
+            else
             {
-                const auto beg = _r.cells.begin();
-                const auto end = _r.cells.end();
-                std::move_backward(beg, end - offset, end);
+                // scrollOffset/offset = 1
+                // +----------+    +----------+
+                // | xxxxxxxxx|    |          |
+                // |xxxxxxx   | -> | xxxxxxxxx|
+                // |          |    |xxxxxxx   |
+                // +----------+    +----------+
+                const u16 endRow = offset;
+                _api.invalidatedRows.start = 0;
+                _api.invalidatedRows.end = nothingInvalid ? endRow : std::max(_api.invalidatedRows.end, endRow);
+
+                const auto dst = std::copy_n(_p.rows.end() - offset, offset, _p.rowsScratch.begin());
+                std::copy_n(_p.rows.begin(), _p.rows.size() - offset, dst);
             }
+
+            std::swap(_p.rows, _p.rowsScratch);
+
+            // Scrolling the background bitmap is a lot easier because we can rely on memmove which works
+            // with both forwards and backwards copying. It's a mystery why the STL doesn't have this.
             {
-                const auto beg = _r.cellGlyphMapping.begin();
-                const auto end = _r.cellGlyphMapping.end();
-                std::move_backward(beg, end - offset, end);
+                const auto srcOffset = std::max<ptrdiff_t>(0, -offset) * gsl::narrow_cast<ptrdiff_t>(_p.colorBitmapRowStride);
+                const auto dstOffset = std::max<ptrdiff_t>(0, offset) * gsl::narrow_cast<ptrdiff_t>(_p.colorBitmapRowStride);
+                const auto count = _p.colorBitmapDepthStride - std::max(srcOffset, dstOffset);
+                assert(dstOffset >= 0 && dstOffset + count <= _p.colorBitmapDepthStride);
+                assert(srcOffset >= 0 && srcOffset + count <= _p.colorBitmapDepthStride);
+
+                auto src = _p.colorBitmap.data() + srcOffset;
+                auto dst = _p.colorBitmap.data() + dstOffset;
+                const auto bytes = count * sizeof(u32);
+
+                for (size_t i = 0; i < 2; ++i)
+                {
+                    // Avoid bumping the colorBitmapGeneration unless necessary. This approx. further halves
+                    // the (already small) GPU load. This could easily be replaced with some custom SIMD
+                    // to avoid going over the memory twice, but... that's a story for another day.
+                    if (memcmp(dst, src, bytes) != 0)
+                    {
+                        memmove(dst, src, bytes);
+                        _p.colorBitmapGenerations[i].bump();
+                    }
+
+                    src += _p.colorBitmapDepthStride;
+                    dst += _p.colorBitmapDepthStride;
+                }
             }
         }
     }
 
-    _api.dirtyRect = til::rect{ 0, _api.invalidatedRows.x, _api.cellCount.x, _api.invalidatedRows.y };
-    _r.dirtyRect = _api.dirtyRect;
-    _r.scrollOffset = _api.scrollOffset;
+    _api.dirtyRect = {
+        0,
+        _api.invalidatedRows.start,
+        _p.s->cellCount.x,
+        _api.invalidatedRows.end,
+    };
 
-    // Clear the previous cursor. PaintCursor() is only called if the cursor is on.
-    if (const auto r = _api.invalidatedCursorArea; r.non_empty())
-    {
-        _setCellFlags(r, CellFlags::Cursor, CellFlags::None);
-        _r.dirtyRect |= til::rect{ r.left, r.top, r.right, r.bottom };
-    }
+    _p.dirtyRectInPx = {
+        til::CoordTypeMax,
+        til::CoordTypeMax,
+        til::CoordTypeMin,
+        til::CoordTypeMin,
+    };
+    _p.invalidatedRows = _api.invalidatedRows;
+    _p.cursorRect = {};
+    _p.scrollOffset = _api.scrollOffset;
 
-    // This is an important block of code for our TileHashMap.
-    // We only process glyphs within the dirtyRect, but glyphs outside of the
-    // dirtyRect are still in use and shouldn't be discarded. This is critical
-    // if someone uses a tool like tmux to split the terminal horizontally.
-    // If they then print a lot of Unicode text on just one side, we have to
-    // ensure that the (for example) plain ASCII glyphs on the other half of the
-    // viewport are still retained. This bit of code "refreshes" those glyphs and
-    // brings them to the front of the LRU queue to prevent them from being reused.
+    if (_api.invalidatedRows.non_empty())
     {
-        const std::array<til::point, 2> ranges{ {
-            { 0, _api.dirtyRect.top },
-            { _api.dirtyRect.bottom, _api.cellCount.y },
-        } };
-        const auto stride = static_cast<size_t>(_r.cellCount.x);
+        const auto deltaPx = _api.scrollOffset * _p.s->font->cellSize.y;
+        const til::CoordType targetSizeX = _p.s->targetSize.x;
+        const til::CoordType targetSizeY = _p.s->targetSize.y;
+        u16 y = 0;
 
-        for (const auto& p : ranges)
+        _p.dirtyRectInPx.left = 0;
+        _p.dirtyRectInPx.top = _api.invalidatedRows.start * _p.s->font->cellSize.y;
+        _p.dirtyRectInPx.right = targetSizeX;
+        _p.dirtyRectInPx.bottom = _api.invalidatedRows.end * _p.s->font->cellSize.y;
+
+        for (const auto r : _p.rows)
         {
-            // We (ab)use the .x/.y members of the til::point as the
-            // respective [from,to) range of rows we need to makeNewest().
-            const auto from = p.x;
-            const auto to = p.y;
+            r->dirtyTop += deltaPx;
+            r->dirtyBottom += deltaPx;
 
-            for (auto y = from; y < to; ++y)
+            if (_api.invalidatedRows.contains(y))
             {
-                auto it = _r.cellGlyphMapping.data() + stride * y;
-                const auto end = it + stride;
-                for (; it != end; ++it)
+                const auto clampedTop = clamp(r->dirtyTop, 0, targetSizeY);
+                const auto clampedBottom = clamp(r->dirtyBottom, 0, targetSizeY);
+                if (clampedTop != clampedBottom)
                 {
-                    _r.glyphs.makeNewest(*it);
+                    _p.dirtyRectInPx.top = std::min(_p.dirtyRectInPx.top, clampedTop);
+                    _p.dirtyRectInPx.bottom = std::max(_p.dirtyRectInPx.bottom, clampedBottom);
                 }
+
+                r->clear(y, _p.s->font->cellSize.y);
             }
+
+            ++y;
+        }
+
+        // I feel a little bit like this is a hack, but I'm not sure how to better express this.
+        // This ensures that we end up calling Present1() without dirty rects if the swap chain is
+        // recreated/resized, because DXGI requires you to then call Present1() without dirty rects.
+        if (allInvalid)
+        {
+            _p.dirtyRectInPx.top = 0;
+            _p.dirtyRectInPx.bottom = targetSizeY;
         }
     }
 
+#if ATLAS_DEBUG_CONTINUOUS_REDRAW
+    _p.MarkAllAsDirty();
+#endif
+
     return S_OK;
 }
-catch (const wil::ResultException& exception)
-{
-    return _handleException(exception);
-}
 CATCH_RETURN()
 
 [[nodiscard]] HRESULT AtlasEngine::EndPaint() noexcept
@@ -334,6 +277,9 @@ CATCH_RETURN()
 
 [[nodiscard]] HRESULT AtlasEngine::PrepareLineTransform(const LineRendition lineRendition, const til::CoordType targetRow, const til::CoordType viewportLeft) noexcept
 {
+    const auto y = gsl::narrow_cast<u16>(clamp<til::CoordType>(targetRow, 0, _p.s->cellCount.y));
+    _p.rows[y]->lineRendition = lineRendition;
+    _api.lineRendition = lineRendition;
     return S_OK;
 }
 
@@ -345,13 +291,16 @@ CATCH_RETURN()
 [[nodiscard]] HRESULT AtlasEngine::PaintBufferLine(std::span<const Cluster> clusters, til::point coord, const bool fTrimLeft, const bool lineWrapped) noexcept
 try
 {
-    const auto y = gsl::narrow_cast<u16>(clamp<int>(coord.y, 0, _api.cellCount.y));
+    const auto y = gsl::narrow_cast<u16>(clamp<int>(coord.y, 0, _p.s->cellCount.y));
 
     if (_api.lastPaintBufferLineCoord.y != y)
     {
         _flushBufferLine();
     }
 
+    const auto x = gsl::narrow_cast<u16>(clamp<int>(coord.x, 0, _p.s->cellCount.x));
+    auto columnEnd = x;
+
     // _api.bufferLineColumn contains 1 more item than _api.bufferLine, as it represents the
     // past-the-end index. It'll get appended again later once we built our new _api.bufferLine.
     if (!_api.bufferLineColumn.empty())
@@ -359,56 +308,53 @@ try
         _api.bufferLineColumn.pop_back();
     }
 
-    // `TextBuffer` is buggy and allows a `Trailing` `DbcsAttribute` to be written
-    // into the first column. Since other code then blindly assumes that there's a
-    // preceding `Leading` character, we'll get called with a X coordinate of -1.
-    //
-    // This block can be removed after GH#13626 is merged.
-    if (coord.x < 0)
+    // Due to the current IRenderEngine interface (that wasn't refactored yet) we need to assemble
+    // the current buffer line first as the remaining function operates on whole lines of text.
     {
-        size_t offset = 0;
         for (const auto& cluster : clusters)
         {
-            offset++;
-            coord.x += cluster.GetColumns();
-            if (coord.x >= 0)
+            for (const auto& ch : cluster.GetText())
             {
-                _api.bufferLine.insert(_api.bufferLine.end(), coord.x, L' ');
-                _api.bufferLineColumn.insert(_api.bufferLineColumn.end(), coord.x, 0u);
-                break;
+                _api.bufferLine.emplace_back(ch);
+                _api.bufferLineColumn.emplace_back(columnEnd);
             }
+            columnEnd += gsl::narrow_cast<u16>(cluster.GetColumns());
         }
 
-        clusters = clusters.subspan(offset);
+        _api.bufferLineColumn.emplace_back(columnEnd);
     }
 
-    const auto x = gsl::narrow_cast<u16>(clamp<int>(coord.x, 0, _api.cellCount.x));
-
-    // Due to the current IRenderEngine interface (that wasn't refactored yet) we need to assemble
-    // the current buffer line first as the remaining function operates on whole lines of text.
     {
-        auto column = x;
-        for (const auto& cluster : clusters)
+        const auto shift = gsl::narrow_cast<u8>(_api.lineRendition != LineRendition::SingleWidth);
+        const auto row = _p.colorBitmap.begin() + _p.colorBitmapRowStride * y;
+        auto beg = row + (static_cast<size_t>(x) << shift);
+        auto end = row + (static_cast<size_t>(columnEnd) << shift);
+
+        const u32 colors[] = {
+            u32ColorPremultiply(_api.currentBackground),
+            _api.currentForeground,
+        };
+
+        for (size_t i = 0; i < 2; ++i)
         {
-            for (const auto& ch : cluster.GetText())
+            const auto color = colors[i];
+
+            for (auto it = beg; it != end; ++it)
             {
-                _api.bufferLine.emplace_back(ch);
-                _api.bufferLineColumn.emplace_back(column);
+                if (*it != color)
+                {
+                    _p.colorBitmapGenerations[i].bump();
+                    std::fill(it, end, color);
+                    break;
+                }
             }
 
-            column += gsl::narrow_cast<u16>(cluster.GetColumns());
+            beg += _p.colorBitmapDepthStride;
+            end += _p.colorBitmapDepthStride;
         }
-
-        _api.bufferLineColumn.emplace_back(column);
-
-        const BufferLineMetadata metadata{ _api.currentColor, _api.flags };
-        FAIL_FAST_IF(column > _api.bufferLineMetadata.size());
-        std::fill_n(_api.bufferLineMetadata.data() + x, column - x, metadata);
     }
 
     _api.lastPaintBufferLineCoord = { x, y };
-    _api.bufferLineWasHyperlinked = false;
-
     return S_OK;
 }
 CATCH_RETURN()
@@ -416,16 +362,12 @@ CATCH_RETURN()
 [[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept
 try
 {
-    if (!_api.bufferLineWasHyperlinked && lines.test(GridLines::Underline) && WI_IsFlagClear(_api.flags, CellFlags::Underline))
-    {
-        _api.bufferLineWasHyperlinked = true;
-
-        WI_UpdateFlagsInMask(_api.flags, CellFlags::Underline | CellFlags::UnderlineDotted | CellFlags::UnderlineDouble, CellFlags::Underline);
-
-        const BufferLineMetadata metadata{ _api.currentColor, _api.flags };
-        const size_t x = _api.lastPaintBufferLineCoord.x;
-        std::fill_n(_api.bufferLineMetadata.data() + x, _api.bufferLineMetadata.size() - x, metadata);
-    }
+    const auto shift = gsl::narrow_cast<u8>(_api.lineRendition != LineRendition::SingleWidth);
+    const auto y = gsl::narrow_cast<u16>(clamp<til::CoordType>(coordTarget.y, 0, _p.s->cellCount.y));
+    const auto from = gsl::narrow_cast<u16>(clamp<til::CoordType>(coordTarget.x << shift, 0, _p.s->cellCount.x - 1));
+    const auto to = gsl::narrow_cast<u16>(clamp<size_t>((coordTarget.x + cchLine) << shift, from, _p.s->cellCount.x));
+    const auto fg = gsl::narrow_cast<u32>(color) | 0xff000000;
+    _p.rows[y]->gridLineRanges.emplace_back(lines, fg, from, to);
     return S_OK;
 }
 CATCH_RETURN()
@@ -438,14 +380,18 @@ try
     // As such we got to call _flushBufferLine() here just to be sure.
     _flushBufferLine();
 
-    const u16r u16rect{
-        rect.narrow_left<u16>(),
-        rect.narrow_top<u16>(),
-        rect.narrow_right<u16>(),
-        rect.narrow_bottom<u16>(),
-    };
-    _setCellFlags(u16rect, CellFlags::Selected, CellFlags::Selected);
-    _r.dirtyRect |= rect;
+    const auto y = gsl::narrow_cast<u16>(clamp<til::CoordType>(rect.top, 0, _p.s->cellCount.y));
+    const auto from = gsl::narrow_cast<u16>(clamp<til::CoordType>(rect.left, 0, _p.s->cellCount.x - 1));
+    const auto to = gsl::narrow_cast<u16>(clamp<til::CoordType>(rect.right, from, _p.s->cellCount.x));
+
+    auto& row = *_p.rows[y];
+    row.selectionFrom = from;
+    row.selectionTo = to;
+
+    _p.dirtyRectInPx.left = std::min(_p.dirtyRectInPx.left, from * _p.s->font->cellSize.x);
+    _p.dirtyRectInPx.top = std::min(_p.dirtyRectInPx.top, y * _p.s->font->cellSize.y);
+    _p.dirtyRectInPx.right = std::max(_p.dirtyRectInPx.right, to * _p.s->font->cellSize.x);
+    _p.dirtyRectInPx.bottom = std::max(_p.dirtyRectInPx.bottom, _p.dirtyRectInPx.top + _p.s->font->cellSize.y);
     return S_OK;
 }
 CATCH_RETURN()
@@ -459,30 +405,53 @@ try
     _flushBufferLine();
 
     {
-        const CachedCursorOptions cachedOptions{
-            gsl::narrow_cast<u32>(options.fUseColor ? options.cursorColor | 0xff000000 : INVALID_COLOR),
-            gsl::narrow_cast<u16>(options.cursorType),
-            gsl::narrow_cast<u8>(options.ulCursorHeightPercent),
+        const CursorSettings cachedOptions{
+            .cursorColor = gsl::narrow_cast<u32>(options.fUseColor ? options.cursorColor | 0xff000000 : INVALID_COLOR),
+            .cursorType = gsl::narrow_cast<u16>(options.cursorType),
+            .heightPercentage = gsl::narrow_cast<u16>(options.ulCursorHeightPercent),
         };
-        if (_r.cursorOptions != cachedOptions)
+        if (*_api.s->cursor != cachedOptions)
         {
-            _r.cursorOptions = cachedOptions;
-            WI_SetFlag(_r.invalidations, RenderInvalidations::Cursor);
+            *_api.s.write()->cursor.write() = cachedOptions;
+            *_p.s.write()->cursor.write() = cachedOptions;
         }
     }
 
+    // Clear the previous cursor
+    if (const auto r = _api.invalidatedCursorArea; r.non_empty())
+    {
+        _p.dirtyRectInPx.left = std::min(_p.dirtyRectInPx.left, r.left * _p.s->font->cellSize.x);
+        _p.dirtyRectInPx.top = std::min(_p.dirtyRectInPx.top, r.top * _p.s->font->cellSize.y);
+        _p.dirtyRectInPx.right = std::max(_p.dirtyRectInPx.right, r.right * _p.s->font->cellSize.x);
+        _p.dirtyRectInPx.bottom = std::max(_p.dirtyRectInPx.bottom, r.bottom * _p.s->font->cellSize.y);
+    }
+
     if (options.isOn)
     {
         const auto point = options.coordCursor;
         // TODO: options.coordCursor can contain invalid out of bounds coordinates when
         // the window is being resized and the cursor is on the last line of the viewport.
-        const auto x = gsl::narrow_cast<uint16_t>(clamp(point.x, 0, _r.cellCount.x - 1));
-        const auto y = gsl::narrow_cast<uint16_t>(clamp(point.y, 0, _r.cellCount.y - 1));
+        const auto top = clamp(point.y, 0, _p.s->cellCount.y - 1);
+        const auto bottom = top + 1;
         const auto cursorWidth = 1 + (options.fIsDoubleWidth & (options.cursorType != CursorType::VerticalBar));
-        const auto right = gsl::narrow_cast<uint16_t>(clamp(x + cursorWidth, 0, _r.cellCount.x - 0));
-        const auto bottom = gsl::narrow_cast<uint16_t>(y + 1);
-        _setCellFlags({ x, y, right, bottom }, CellFlags::Cursor, CellFlags::Cursor);
-        _r.dirtyRect |= til::rect{ x, y, right, bottom };
+
+        auto left = std::max(point.x, 0);
+        auto right = std::max(left + cursorWidth, 0);
+
+        if (_p.rows[top]->lineRendition != LineRendition::SingleWidth)
+        {
+            left <<= 1;
+            right <<= 1;
+        }
+
+        left = std::min(left, _p.s->cellCount.x - cursorWidth);
+        right = std::min(right, i32{ _p.s->cellCount.x });
+
+        _p.cursorRect = { left, top, right, bottom };
+        _p.dirtyRectInPx.left = std::min(_p.dirtyRectInPx.left, left * _p.s->font->cellSize.x);
+        _p.dirtyRectInPx.top = std::min(_p.dirtyRectInPx.top, top * _p.s->font->cellSize.y);
+        _p.dirtyRectInPx.right = std::max(_p.dirtyRectInPx.right, right * _p.s->font->cellSize.x);
+        _p.dirtyRectInPx.bottom = std::max(_p.dirtyRectInPx.bottom, bottom * _p.s->font->cellSize.y);
     }
 
     return S_OK;
@@ -498,40 +467,23 @@ try
 
     if (!isSettingDefaultBrushes)
     {
-        const auto hyperlinkId = textAttributes.GetHyperlinkId();
-
-        auto flags = CellFlags::None;
-        WI_SetFlagIf(flags, CellFlags::BorderLeft, textAttributes.IsLeftVerticalDisplayed());
-        WI_SetFlagIf(flags, CellFlags::BorderTop, textAttributes.IsTopHorizontalDisplayed());
-        WI_SetFlagIf(flags, CellFlags::BorderRight, textAttributes.IsRightVerticalDisplayed());
-        WI_SetFlagIf(flags, CellFlags::BorderBottom, textAttributes.IsBottomHorizontalDisplayed());
-        WI_SetFlagIf(flags, CellFlags::Underline, textAttributes.IsUnderlined());
-        WI_SetFlagIf(flags, CellFlags::UnderlineDotted, hyperlinkId != 0);
-        WI_SetFlagIf(flags, CellFlags::UnderlineDouble, textAttributes.IsDoublyUnderlined());
-        WI_SetFlagIf(flags, CellFlags::Strikethrough, textAttributes.IsCrossedOut());
-
-        if (_api.hyperlinkHoveredId && _api.hyperlinkHoveredId == hyperlinkId)
-        {
-            WI_SetFlag(flags, CellFlags::Underline);
-            WI_ClearAllFlags(flags, CellFlags::UnderlineDotted | CellFlags::UnderlineDouble);
-        }
-
-        const u32x2 newColors{ gsl::narrow_cast<u32>(fg), gsl::narrow_cast<u32>(bg) };
-        const AtlasKeyAttributes attributes{ 0, textAttributes.IsIntense() && renderSettings.GetRenderMode(RenderSettings::Mode::IntenseIsBold), textAttributes.IsItalic(), 0 };
+        auto attributes = FontRelevantAttributes::None;
+        WI_SetFlagIf(attributes, FontRelevantAttributes::Bold, textAttributes.IsIntense() && renderSettings.GetRenderMode(RenderSettings::Mode::IntenseIsBold));
+        WI_SetFlagIf(attributes, FontRelevantAttributes::Italic, textAttributes.IsItalic());
 
         if (_api.attributes != attributes)
         {
             _flushBufferLine();
         }
 
-        _api.currentColor = newColors;
+        _api.currentBackground = gsl::narrow_cast<u32>(bg);
+        _api.currentForeground = gsl::narrow_cast<u32>(fg);
         _api.attributes = attributes;
-        _api.flags = flags;
     }
-    else if (textAttributes.BackgroundIsDefault() && bg != _r.backgroundColor)
+    else if (textAttributes.BackgroundIsDefault() && bg != _api.s->misc->backgroundColor)
     {
-        _r.backgroundColor = bg;
-        WI_SetFlag(_r.invalidations, RenderInvalidations::ConstBuffer);
+        _api.s.write()->misc.write()->backgroundColor = bg;
+        _p.s.write()->misc.write()->backgroundColor = bg;
     }
 
     return S_OK;
@@ -540,1113 +492,495 @@ CATCH_RETURN()
 
 #pragma endregion
 
-[[nodiscard]] HRESULT AtlasEngine::_handleException(const wil::ResultException& exception) noexcept
+void AtlasEngine::_handleSettingsUpdate()
 {
-    const auto hr = exception.GetErrorCode();
-    if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET || hr == D2DERR_RECREATE_TARGET)
+    const auto targetChanged = _p.s->target != _api.s->target;
+    const auto fontChanged = _p.s->font != _api.s->font;
+    const auto cellCountChanged = _p.s->cellCount != _api.s->cellCount;
+
+    _p.s = _api.s;
+
+    if (targetChanged)
     {
-        WI_SetFlag(_api.invalidations, ApiInvalidations::Device);
-        return E_PENDING; // Indicate a retry to the renderer
+        // target->useSoftwareRendering affects the selection of our IDXGIAdapter which requires us to reset _p.dxgi.
+        // This will indirectly also recreate the backend, when AtlasEngine::_recreateAdapter() detects this change.
+        _p.dxgi = {};
     }
-
-    // NOTE: This isn't thread safe, as _handleException is called by AtlasEngine.r.cpp.
-    // However it's not too much of a concern at the moment as SetWarningCallback()
-    // is only called once during construction in practice.
-    if (_api.warningCallback)
+    if (fontChanged)
     {
-        try
-        {
-            _api.warningCallback(hr);
-        }
-        CATCH_LOG()
+        _recreateFontDependentResources();
     }
+    if (cellCountChanged)
+    {
+        _recreateCellCountDependentResources();
+    }
+
+    // !!! NOTE !!!
+    // This will indirectly mark the entire viewport as dirty, but AtlasEngine::_recreateBackend()
+    // may do the same, so make sure that that function stays in sync with what this code does.
+    _api.invalidatedRows = invalidatedRowsAll;
 
-    return hr;
+    u16 y = 0;
+    for (const auto r : _p.rows)
+    {
+        r->clear(y, _p.s->font->cellSize.y);
+        ++y;
+    }
 }
 
-void AtlasEngine::_createResources()
+void AtlasEngine::_recreateFontDependentResources()
 {
-    _releaseSwapChain();
-    _r = {};
+    _api.replacementCharacterFontFace.reset();
+    _api.replacementCharacterGlyphIndex = 0;
+    _api.replacementCharacterLookedUp = false;
 
-#ifdef NDEBUG
-    static constexpr
-#endif
-        auto deviceFlags = D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT;
-
-#ifndef NDEBUG
-    // DXGI debug messages + enabling D3D11_CREATE_DEVICE_DEBUG if the Windows SDK was installed.
-    if (const wil::unique_hmodule module{ LoadLibraryExW(L"dxgi.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) })
+    if (_p.s->font->fontAxisValues.empty())
     {
-        deviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
-
-        if (const auto DXGIGetDebugInterface1 = GetProcAddressByFunctionDeclaration(module.get(), DXGIGetDebugInterface1))
+        for (auto& axes : _api.textFormatAxes)
         {
-            if (wil::com_ptr<IDXGIInfoQueue> infoQueue; SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(infoQueue.addressof()))))
-            {
-                // I didn't want to link with dxguid.lib just for getting DXGI_DEBUG_ALL. This GUID is publicly documented.
-                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);
-                }
-            }
-
-            if (wil::com_ptr<IDXGIDebug1> debug; SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(debug.addressof()))))
-            {
-                debug->EnableLeakTrackingForThread();
-            }
+            axes = {};
         }
     }
-#endif // NDEBUG
-
-    // D3D device setup (basically a D3D class factory)
+    else
     {
-        wil::com_ptr<ID3D11DeviceContext> deviceContext;
-
-        static constexpr std::array featureLevels{
-            D3D_FEATURE_LEVEL_11_1,
-            D3D_FEATURE_LEVEL_11_0,
-            D3D_FEATURE_LEVEL_10_1,
-            D3D_FEATURE_LEVEL_10_0,
-            D3D_FEATURE_LEVEL_9_3,
-            D3D_FEATURE_LEVEL_9_2,
-            D3D_FEATURE_LEVEL_9_1,
-        };
-
-        auto hr = E_UNEXPECTED;
+        // See AtlasEngine::UpdateFont.
+        // It hardcodes indices 0/1/2 in fontAxisValues to the weight/italic/slant axes.
+        // If they're -1 they haven't been set by the user and must be filled by us.
+        const auto& standardAxes = _p.s->font->fontAxisValues;
+        auto fontAxisValues = _p.s->font->fontAxisValues;
 
-        if (!_api.useSoftwareRendering)
-        {
-            // Why D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS:
-            // This flag prevents the driver from creating a large thread pool for things like shader computations
-            // that would be advantageous for games. For us this has only a minimal performance benefit,
-            // but comes with a large memory usage overhead. At the time of writing the Nvidia
-            // driver launches $cpu_thread_count more worker threads without this flag.
-            hr = D3D11CreateDevice(
-                /* pAdapter */ nullptr,
-                /* DriverType */ D3D_DRIVER_TYPE_HARDWARE,
-                /* Software */ nullptr,
-                /* Flags */ deviceFlags | D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS,
-                /* pFeatureLevels */ featureLevels.data(),
-                /* FeatureLevels */ gsl::narrow_cast<UINT>(featureLevels.size()),
-                /* SDKVersion */ D3D11_SDK_VERSION,
-                /* ppDevice */ _r.device.put(),
-                /* pFeatureLevel */ nullptr,
-                /* ppImmediateContext */ deviceContext.put());
-        }
-        if (FAILED(hr))
+        for (size_t i = 0; i < 4; ++i)
         {
-            hr = D3D11CreateDevice(
-                /* pAdapter */ nullptr,
-                /* DriverType */ D3D_DRIVER_TYPE_WARP,
-                /* Software */ nullptr,
-                /* Flags */ deviceFlags,
-                /* pFeatureLevels */ featureLevels.data(),
-                /* FeatureLevels */ gsl::narrow_cast<UINT>(featureLevels.size()),
-                /* SDKVersion */ D3D11_SDK_VERSION,
-                /* ppDevice */ _r.device.put(),
-                /* pFeatureLevel */ nullptr,
-                /* ppImmediateContext */ deviceContext.put());
+            const auto bold = (i & static_cast<size_t>(FontRelevantAttributes::Bold)) != 0;
+            const auto italic = (i & static_cast<size_t>(FontRelevantAttributes::Italic)) != 0;
+            // The wght axis defaults to the font weight.
+            fontAxisValues[0].value = bold ? DWRITE_FONT_WEIGHT_BOLD : (standardAxes[0].value < 0 ? static_cast<f32>(_p.s->font->fontWeight) : standardAxes[0].value);
+            // The ital axis defaults to 1 if this is italic and 0 otherwise.
+            fontAxisValues[1].value = italic ? 1.0f : (standardAxes[1].value < 0 ? 0.0f : standardAxes[1].value);
+            // The slnt axis defaults to -12 if this is italic and 0 otherwise.
+            fontAxisValues[2].value = italic ? -12.0f : (standardAxes[2].value < 0 ? 0.0f : standardAxes[2].value);
+            _api.textFormatAxes[i] = { fontAxisValues.data(), fontAxisValues.size() };
         }
-        THROW_IF_FAILED(hr);
+    }
+}
 
-        _r.deviceContext = deviceContext.query<ID3D11DeviceContext1>();
+void AtlasEngine::_recreateCellCountDependentResources()
+{
+    // Let's guess that every cell consists of a surrogate pair.
+    const auto projectedTextSize = static_cast<size_t>(_p.s->cellCount.x) * 2;
+    // IDWriteTextAnalyzer::GetGlyphs says:
+    //   The recommended estimate for the per-glyph output buffers is (3 * textLength / 2 + 16).
+    const auto projectedGlyphSize = 3 * projectedTextSize / 2 + 16;
+
+    _api.bufferLine = std::vector<wchar_t>{};
+    _api.bufferLine.reserve(projectedTextSize);
+    _api.bufferLineColumn.reserve(projectedTextSize + 1);
+
+    _api.analysisResults = std::vector<TextAnalysisSinkResult>{};
+    _api.clusterMap = Buffer<u16>{ projectedTextSize };
+    _api.textProps = Buffer<DWRITE_SHAPING_TEXT_PROPERTIES>{ projectedTextSize };
+    _api.glyphIndices = Buffer<u16>{ projectedGlyphSize };
+    _api.glyphProps = Buffer<DWRITE_SHAPING_GLYPH_PROPERTIES>{ projectedGlyphSize };
+    _api.glyphAdvances = Buffer<f32>{ projectedGlyphSize };
+    _api.glyphOffsets = Buffer<DWRITE_GLYPH_OFFSET>{ projectedGlyphSize };
+
+    _p.unorderedRows = Buffer<ShapedRow>(_p.s->cellCount.y);
+    _p.rowsScratch = Buffer<ShapedRow*>(_p.s->cellCount.y);
+    _p.rows = Buffer<ShapedRow*>(_p.s->cellCount.y);
+
+    // Our render loop heavily relies on memcpy() which is up to between 1.5x (Intel)
+    // and 40x (AMD) faster for allocations with an alignment of 32 or greater.
+    // backgroundBitmapStride is a "count" of u32 and not in bytes,
+    // so we round up to multiple of 8 because 8 * sizeof(u32) == 32.
+    _p.colorBitmapRowStride = (static_cast<size_t>(_p.s->cellCount.x) + 7) & ~7;
+    _p.colorBitmapDepthStride = _p.colorBitmapRowStride * _p.s->cellCount.y;
+    _p.colorBitmap = Buffer<u32, 32>(_p.colorBitmapDepthStride * 2);
+    _p.backgroundBitmap = { _p.colorBitmap.data(), _p.colorBitmapDepthStride };
+    _p.foregroundBitmap = { _p.colorBitmap.data() + _p.colorBitmapDepthStride, _p.colorBitmapDepthStride };
+
+    memset(_p.colorBitmap.data(), 0, _p.colorBitmap.size() * sizeof(u32));
+
+    auto it = _p.unorderedRows.data();
+    for (auto& r : _p.rows)
+    {
+        r = it++;
     }
+}
 
-#ifndef NDEBUG
-    // D3D debug messages
-    if (deviceFlags & D3D11_CREATE_DEVICE_DEBUG)
+void AtlasEngine::_flushBufferLine()
+{
+    if (_api.bufferLine.empty())
     {
-        const auto infoQueue = _r.device.query<ID3D11InfoQueue>();
-        for (const auto severity : std::array{ D3D11_MESSAGE_SEVERITY_CORRUPTION, D3D11_MESSAGE_SEVERITY_ERROR, D3D11_MESSAGE_SEVERITY_WARNING })
-        {
-            infoQueue->SetBreakOnSeverity(severity, true);
-        }
+        return;
     }
-#endif // NDEBUG
 
-    {
-        wil::com_ptr<IDXGIAdapter1> dxgiAdapter;
-        THROW_IF_FAILED(_r.device.query<IDXGIObject>()->GetParent(__uuidof(dxgiAdapter), dxgiAdapter.put_void()));
-        THROW_IF_FAILED(dxgiAdapter->GetParent(__uuidof(_r.dxgiFactory), _r.dxgiFactory.put_void()));
+    const auto cleanup = wil::scope_exit([this]() noexcept {
+        _api.bufferLine.clear();
+        _api.bufferLineColumn.clear();
+    });
 
-        DXGI_ADAPTER_DESC1 desc;
-        THROW_IF_FAILED(dxgiAdapter->GetDesc1(&desc));
-        _r.d2dMode = debugForceD2DMode || WI_IsAnyFlagSet(desc.Flags, DXGI_ADAPTER_FLAG_REMOTE | DXGI_ADAPTER_FLAG_SOFTWARE);
-    }
+    // This would seriously blow us up otherwise.
+    Expects(_api.bufferLineColumn.size() == _api.bufferLine.size() + 1);
 
-    const auto featureLevel = _r.device->GetFeatureLevel();
+    auto& row = *_p.rows[_api.lastPaintBufferLineCoord.y];
 
-    if (featureLevel < D3D_FEATURE_LEVEL_10_0)
-    {
-        _r.d2dMode = true;
-    }
-    else if (featureLevel < D3D_FEATURE_LEVEL_11_0)
-    {
-        D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS options;
-        THROW_IF_FAILED(_r.device->CheckFeatureSupport(D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS, &options, sizeof(options)));
-        if (!options.ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x)
-        {
-            _r.d2dMode = true;
-        }
-    }
+    wil::com_ptr<IDWriteFontFace2> mappedFontFace;
 
-    if (!_r.d2dMode)
+#pragma warning(suppress : 26494) // Variable 'mappedEnd' is uninitialized. Always initialize an object (type.5).
+    for (u32 idx = 0, mappedEnd; idx < _api.bufferLine.size(); idx = mappedEnd)
     {
-        // Our constant buffer will never get resized
+        u32 mappedLength = 0;
+        _mapCharacters(_api.bufferLine.data() + idx, gsl::narrow_cast<u32>(_api.bufferLine.size()) - idx, &mappedLength, mappedFontFace.put());
+        mappedEnd = idx + mappedLength;
+
+        if (!mappedFontFace)
         {
-            D3D11_BUFFER_DESC desc{};
-            desc.ByteWidth = sizeof(ConstBuffer);
-            desc.Usage = D3D11_USAGE_DEFAULT;
-            desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
-            THROW_IF_FAILED(_r.device->CreateBuffer(&desc, nullptr, _r.constantBuffer.put()));
+            _mapReplacementCharacter(idx, mappedEnd, row);
+            continue;
         }
 
-        THROW_IF_FAILED(_r.device->CreateVertexShader(&shader_vs[0], sizeof(shader_vs), nullptr, _r.vertexShader.put()));
-        THROW_IF_FAILED(_r.device->CreatePixelShader(&shader_ps[0], sizeof(shader_ps), nullptr, _r.pixelShader.put()));
+        const auto initialIndicesCount = row.glyphIndices.size();
 
-        if (!_api.customPixelShaderPath.empty())
+        if (mappedLength > _api.glyphIndices.size())
         {
-            const char* target = nullptr;
-            switch (featureLevel)
-            {
-            case D3D_FEATURE_LEVEL_10_0:
-                target = "ps_4_0";
-                break;
-            case D3D_FEATURE_LEVEL_10_1:
-                target = "ps_4_1";
-                break;
-            default:
-                target = "ps_5_0";
-                break;
-            }
+            auto size = _api.glyphIndices.size();
+            size = size + (size >> 1);
+            size = std::max<size_t>(size, mappedLength);
+            Expects(size > _api.glyphIndices.size());
+            _api.glyphIndices = Buffer<u16>{ size };
+            _api.glyphProps = Buffer<DWRITE_SHAPING_GLYPH_PROPERTIES>{ size };
+        }
 
-            static constexpr auto flags =
-                D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR
-#ifdef NDEBUG
-                | D3DCOMPILE_OPTIMIZATION_LEVEL3;
-#else
-                // Only enable strictness and warnings in DEBUG mode
-                //  as these settings makes it very difficult to develop
-                //  shaders as windows terminal is not telling the user
-                //  what's wrong, windows terminal just fails.
-                //  Keep it in DEBUG mode to catch errors in shaders
-                //  shipped with windows terminal
-                | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS | D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
-#endif
+        // We can reuse idx here, as it'll be reset to "idx = mappedEnd" in the outer loop anyways.
+        for (u32 complexityLength = 0; idx < mappedEnd; idx += complexityLength)
+        {
+            BOOL isTextSimple = FALSE;
+            THROW_IF_FAILED(_p.textAnalyzer->GetTextComplexity(_api.bufferLine.data() + idx, mappedEnd - idx, mappedFontFace.get(), &isTextSimple, &complexityLength, _api.glyphIndices.data()));
 
-            wil::com_ptr<ID3DBlob> error;
-            wil::com_ptr<ID3DBlob> blob;
-            const auto hr = D3DCompileFromFile(
-                /* pFileName   */ _api.customPixelShaderPath.c_str(),
-                /* pDefines    */ nullptr,
-                /* pInclude    */ D3D_COMPILE_STANDARD_FILE_INCLUDE,
-                /* pEntrypoint */ "main",
-                /* pTarget     */ target,
-                /* Flags1      */ flags,
-                /* Flags2      */ 0,
-                /* ppCode      */ blob.addressof(),
-                /* ppErrorMsgs */ error.addressof());
-
-            // Unless we can determine otherwise, assume this shader requires evaluation every frame
-            _r.requiresContinuousRedraw = true;
-
-            if (SUCCEEDED(hr))
+            if (isTextSimple)
             {
-                THROW_IF_FAILED(_r.device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _r.customPixelShader.put()));
+                const auto shift = gsl::narrow_cast<u8>(row.lineRendition != LineRendition::SingleWidth);
+                const auto colors = _p.foregroundBitmap.begin() + _p.colorBitmapRowStride * _api.lastPaintBufferLineCoord.y;
 
-                // Try to determine whether the shader uses the Time variable
-                wil::com_ptr<ID3D11ShaderReflection> reflector;
-                if (SUCCEEDED_LOG(D3DReflect(blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(reflector.put()))))
+                for (size_t i = 0; i < complexityLength; ++i)
                 {
-                    if (ID3D11ShaderReflectionConstantBuffer* constantBufferReflector = reflector->GetConstantBufferByIndex(0)) // shader buffer
-                    {
-                        if (ID3D11ShaderReflectionVariable* variableReflector = constantBufferReflector->GetVariableByIndex(0)) // time
-                        {
-                            D3D11_SHADER_VARIABLE_DESC variableDescriptor;
-                            if (SUCCEEDED_LOG(variableReflector->GetDesc(&variableDescriptor)))
-                            {
-                                // only if time is used
-                                _r.requiresContinuousRedraw = WI_IsFlagSet(variableDescriptor.uFlags, D3D_SVF_USED);
-                            }
-                        }
-                    }
+                    const size_t col1 = _api.bufferLineColumn[idx + i + 0];
+                    const size_t col2 = _api.bufferLineColumn[idx + i + 1];
+                    const auto glyphAdvance = (col2 - col1) * _p.s->font->cellSize.x;
+                    const auto fg = colors[col1 << shift];
+                    row.glyphIndices.emplace_back(_api.glyphIndices[i]);
+                    row.glyphAdvances.emplace_back(static_cast<f32>(glyphAdvance));
+                    row.glyphOffsets.emplace_back();
+                    row.colors.emplace_back(fg);
                 }
             }
             else
             {
-                if (error)
-                {
-                    LOG_HR_MSG(hr, "%*hs", error->GetBufferSize(), error->GetBufferPointer());
-                }
-                else
-                {
-                    LOG_HR(hr);
-                }
-                if (_api.warningCallback)
-                {
-                    _api.warningCallback(D2DERR_SHADER_COMPILE_FAILED);
-                }
+                _mapComplex(mappedFontFace.get(), idx, complexityLength, row);
             }
         }
-        else if (_api.useRetroTerminalEffect)
-        {
-            THROW_IF_FAILED(_r.device->CreatePixelShader(&custom_shader_ps[0], sizeof(custom_shader_ps), nullptr, _r.customPixelShader.put()));
-            // We know the built-in retro shader doesn't require continuous redraw.
-            _r.requiresContinuousRedraw = false;
-        }
 
-        if (_r.customPixelShader)
+        const auto indicesCount = row.glyphIndices.size();
+        if (indicesCount > initialIndicesCount)
         {
-            THROW_IF_FAILED(_r.device->CreateVertexShader(&custom_shader_vs[0], sizeof(custom_shader_vs), nullptr, _r.customVertexShader.put()));
-
+            // IDWriteFontFallback::MapCharacters() isn't just awfully slow,
+            // it can also repeatedly return the same font face again and again. :)
+            if (row.mappings.empty() || row.mappings.back().fontFace != mappedFontFace)
             {
-                D3D11_BUFFER_DESC desc{};
-                desc.ByteWidth = sizeof(CustomConstBuffer);
-                desc.Usage = D3D11_USAGE_DYNAMIC;
-                desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
-                desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
-                THROW_IF_FAILED(_r.device->CreateBuffer(&desc, nullptr, _r.customShaderConstantBuffer.put()));
+                row.mappings.emplace_back(std::move(mappedFontFace), gsl::narrow_cast<u32>(initialIndicesCount), gsl::narrow_cast<u32>(indicesCount));
             }
-
+            else
             {
-                D3D11_SAMPLER_DESC desc{};
-                desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
-                desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
-                desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
-                desc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER;
-                desc.MaxAnisotropy = 1;
-                desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
-                desc.MaxLOD = D3D11_FLOAT32_MAX;
-                THROW_IF_FAILED(_r.device->CreateSamplerState(&desc, _r.customShaderSamplerState.put()));
+                row.mappings.back().glyphsTo = gsl::narrow_cast<u32>(indicesCount);
             }
-
-            _r.customShaderStartTime = std::chrono::steady_clock::now();
         }
     }
-
-    WI_ClearFlag(_api.invalidations, ApiInvalidations::Device);
-    WI_SetAllFlags(_api.invalidations, ApiInvalidations::SwapChain);
 }
 
-void AtlasEngine::_releaseSwapChain()
+void AtlasEngine::_mapCharacters(const wchar_t* text, const u32 textLength, u32* mappedLength, IDWriteFontFace2** mappedFontFace) const
 {
-    // Flush() docs:
-    //   However, if an application must actually destroy an old swap chain and create a new swap chain,
-    //   the application must force the destruction of all objects that the application freed.
-    //   To force the destruction, call ID3D11DeviceContext::ClearState (or otherwise ensure
-    //   no views are bound to pipeline state), and then call Flush on the immediate context.
-    if (_r.swapChain && _r.deviceContext)
-    {
-        if (_r.d2dMode)
-        {
-            _r.d2dRenderTarget.reset();
-        }
-        _r.frameLatencyWaitableObject.reset();
-        _r.swapChain.reset();
-        _r.renderTargetView.reset();
-        _r.deviceContext->ClearState();
-        _r.deviceContext->Flush();
-    }
+    TextAnalysisSource analysisSource{ text, textLength };
+    const auto& textFormatAxis = _api.textFormatAxes[static_cast<size_t>(_api.attributes)];
+
+    // We don't read from scale anyways.
+#pragma warning(suppress : 26494) // Variable 'scale' is uninitialized. Always initialize an object (type.5).
+    f32 scale;
+
+    if (textFormatAxis)
+    {
+        THROW_IF_FAILED(_p.systemFontFallback1->MapCharacters(
+            /* analysisSource     */ &analysisSource,
+            /* textPosition       */ 0,
+            /* textLength         */ textLength,
+            /* baseFontCollection */ _p.s->font->fontCollection.get(),
+            /* baseFamilyName     */ _p.s->font->fontName.c_str(),
+            /* fontAxisValues     */ textFormatAxis.data(),
+            /* fontAxisValueCount */ gsl::narrow_cast<u32>(textFormatAxis.size()),
+            /* mappedLength       */ mappedLength,
+            /* scale              */ &scale,
+            /* mappedFontFace     */ reinterpret_cast<IDWriteFontFace5**>(mappedFontFace)));
+    }
+    else
+    {
+        const auto baseWeight = WI_IsFlagSet(_api.attributes, FontRelevantAttributes::Bold) ? DWRITE_FONT_WEIGHT_BOLD : static_cast<DWRITE_FONT_WEIGHT>(_p.s->font->fontWeight);
+        const auto baseStyle = WI_IsFlagSet(_api.attributes, FontRelevantAttributes::Italic) ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
+        wil::com_ptr<IDWriteFont> font;
+
+        THROW_IF_FAILED(_p.systemFontFallback->MapCharacters(
+            /* analysisSource     */ &analysisSource,
+            /* textPosition       */ 0,
+            /* textLength         */ textLength,
+            /* baseFontCollection */ _p.s->font->fontCollection.get(),
+            /* baseFamilyName     */ _p.s->font->fontName.c_str(),
+            /* baseWeight         */ baseWeight,
+            /* baseStyle          */ baseStyle,
+            /* baseStretch        */ DWRITE_FONT_STRETCH_NORMAL,
+            /* mappedLength       */ mappedLength,
+            /* mappedFont         */ font.addressof(),
+            /* scale              */ &scale));
+
+        if (font)
+        {
+            THROW_IF_FAILED(font->CreateFontFace(reinterpret_cast<IDWriteFontFace**>(mappedFontFace)));
+        }
+    }
+
+    // Oh wow! You found a case where scale isn't 1! I tried every font and none
+    // returned something besides 1. I just couldn't figure out why this exists.
+    assert(scale == 1);
 }
 
-void AtlasEngine::_createSwapChain()
+void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 length, ShapedRow& row)
 {
-    _releaseSwapChain();
-
-    // D3D swap chain setup (the thing that allows us to present frames on the screen)
-    {
-        // 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;
-        // Sometimes up to 2 buffers are locked, for instance during screen capture or when moving the window.
-        // 3 buffers seems to guarantee a stable framerate at display frequency at all times.
-        desc.BufferCount = 3;
-        desc.Scaling = DXGI_SCALING_NONE;
-        // DXGI_SWAP_EFFECT_FLIP_DISCARD is a mode that was created at a time were display drivers
-        // lacked support for Multiplane Overlays (MPO) and were copying buffers was expensive.
-        // This allowed DWM to quickly draw overlays (like gamebars) on top of rendered content.
-        // With faster GPU memory in general and with support for MPO in particular this isn't
-        // really an advantage anymore. Instead DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL allows for a
-        // more "intelligent" composition and display updates to occur like Panel Self Refresh
-        // (PSR) which requires dirty rectangles (Present1 API) to work correctly.
-        desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
-        // If our background is opaque we can enable "independent" flips by setting DXGI_ALPHA_MODE_IGNORE.
-        // As our swap chain won't have to compose with DWM anymore it reduces the display latency dramatically.
-        desc.AlphaMode = _api.backgroundOpaqueMixin ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
-        desc.Flags = debugGeneralPerformance ? 0 : DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
-
-        wil::com_ptr<IDXGIFactory2> dxgiFactory;
-        THROW_IF_FAILED(CreateDXGIFactory1(IID_PPV_ARGS(dxgiFactory.addressof())));
-
-        if (_api.hwnd)
-        {
-            THROW_IF_FAILED(dxgiFactory->CreateSwapChainForHwnd(_r.device.get(), _api.hwnd, &desc, nullptr, nullptr, _r.swapChain.put()));
-        }
-        else
-        {
-            const wil::unique_hmodule module{ LoadLibraryExW(L"dcomp.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) };
-            THROW_LAST_ERROR_IF(!module);
-            const auto DCompositionCreateSurfaceHandle = GetProcAddressByFunctionDeclaration(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, _api.swapChainHandle.put()));
-            THROW_IF_FAILED(dxgiFactory.query<IDXGIFactoryMedia>()->CreateSwapChainForCompositionSurfaceHandle(_r.device.get(), _api.swapChainHandle.get(), &desc, nullptr, _r.swapChain.put()));
-        }
-
-        if constexpr (!debugGeneralPerformance)
-        {
-            const auto swapChain2 = _r.swapChain.query<IDXGISwapChain2>();
-            _r.frameLatencyWaitableObject.reset(swapChain2->GetFrameLatencyWaitableObject());
-            THROW_LAST_ERROR_IF(!_r.frameLatencyWaitableObject);
-        }
-    }
-
-    // See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
-    // > For every frame it renders, the app should wait on this handle before starting any rendering operations.
-    // > Note that this requirement includes the first frame the app renders with the swap chain.
-    _r.waitForPresentation = true;
-    WaitUntilCanRender();
+    _api.analysisResults.clear();
 
-    if (_api.swapChainChangedCallback)
-    {
-        try
-        {
-            _api.swapChainChangedCallback(_api.swapChainHandle.get());
-        }
-        CATCH_LOG();
-    }
-
-    WI_ClearFlag(_api.invalidations, ApiInvalidations::SwapChain);
-    WI_SetAllFlags(_api.invalidations, ApiInvalidations::Size | ApiInvalidations::Font);
-}
-
-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.
-    // The _r.cells check exists simply to prevent us from calling ResizeBuffers() on startup (i.e. when `_r` is empty).
-    if (_r.cells)
-    {
-        if (_r.d2dMode)
-        {
-            _r.d2dRenderTarget.reset();
-        }
-        _r.renderTargetView.reset();
-        _r.deviceContext->ClearState();
-        _r.deviceContext->Flush();
-        THROW_IF_FAILED(_r.swapChain->ResizeBuffers(0, _api.sizeInPixel.x, _api.sizeInPixel.y, DXGI_FORMAT_UNKNOWN, debugGeneralPerformance ? 0 : DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT));
-    }
-
-    const auto totalCellCount = static_cast<size_t>(_api.cellCount.x) * static_cast<size_t>(_api.cellCount.y);
-    const auto resize = _api.cellCount != _r.cellCount;
+    TextAnalysisSource analysisSource{ _api.bufferLine.data(), gsl::narrow<UINT32>(_api.bufferLine.size()) };
+    TextAnalysisSink analysisSink{ _api.analysisResults };
+    THROW_IF_FAILED(_p.textAnalyzer->AnalyzeScript(&analysisSource, idx, length, &analysisSink));
 
-    if (resize)
+    for (const auto& a : _api.analysisResults)
     {
-        // Let's guess that every cell consists of a surrogate pair.
-        const auto projectedTextSize = static_cast<size_t>(_api.cellCount.x) * 2;
-        // IDWriteTextAnalyzer::GetGlyphs says:
-        //   The recommended estimate for the per-glyph output buffers is (3 * textLength / 2 + 16).
-        const auto projectedGlyphSize = 3 * projectedTextSize / 2 + 16;
-
-        // This buffer is a bit larger than the others (multiple MB).
-        // Prevent a memory usage spike, by first deallocating and then allocating.
-        _r.cells = {};
-        _r.cellGlyphMapping = {};
-        // Our render loop heavily relies on memcpy() which is between 1.5x
-        // and 40x faster for allocations with an alignment of 32 or greater.
-        // (40x on AMD Zen1-3, which have a rep movsb performance issue. MSFT:33358259.)
-        _r.cells = Buffer<Cell, 32>{ totalCellCount };
-        _r.cellGlyphMapping = Buffer<TileHashMap::iterator>{ totalCellCount };
-        _r.cellCount = _api.cellCount;
-        _r.tileAllocator.setMaxArea(_api.sizeInPixel);
-
-        // .clear() doesn't free the memory of these buffers.
-        // This code allows them to shrink again.
-        _api.bufferLine = {};
-        _api.bufferLine.reserve(projectedTextSize);
-        _api.bufferLineColumn.reserve(projectedTextSize + 1);
-        _api.bufferLineMetadata = Buffer<BufferLineMetadata>{ _api.cellCount.x };
-        _api.analysisResults = {};
-
-        _api.clusterMap = Buffer<u16>{ projectedTextSize };
-        _api.textProps = Buffer<DWRITE_SHAPING_TEXT_PROPERTIES>{ projectedTextSize };
-        _api.glyphIndices = Buffer<u16>{ projectedGlyphSize };
-        _api.glyphProps = Buffer<DWRITE_SHAPING_GLYPH_PROPERTIES>{ projectedGlyphSize };
-        _api.glyphAdvances = Buffer<f32>{ projectedGlyphSize };
-        _api.glyphOffsets = Buffer<DWRITE_GLYPH_OFFSET>{ projectedGlyphSize };
-
-        // Initialize cellGlyphMapping with valid data (whitespace), so that it can be
-        // safely used by the TileHashMap refresh logic via makeNewest() in StartPaint().
-        {
-            u16x2* coords{};
-            AtlasKey key{ { .cellCount = 1 }, 1, L" " };
-            AtlasValue value{ CellFlags::None, 1, &coords };
+        u32 actualGlyphCount = 0;
 
-            coords[0] = _r.tileAllocator.allocate(_r.glyphs);
+#pragma warning(push)
+#pragma warning(disable : 26494) // Variable '...' is uninitialized. Always initialize an object (type.5).
+        // None of these variables need to be initialized.
+        // features/featureRangeLengths are marked _In_reads_opt_(featureRanges).
+        // featureRanges is only > 0 when we also initialize all these variables.
+        DWRITE_TYPOGRAPHIC_FEATURES feature;
+        const DWRITE_TYPOGRAPHIC_FEATURES* features;
+        u32 featureRangeLengths;
+#pragma warning(pop)
+        u32 featureRanges = 0;
+
+        if (!_p.s->font->fontFeatures.empty())
+        {
+            // Direct2D, why is this mutable?         Why?
+#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3).
+            feature.features = const_cast<DWRITE_FONT_FEATURE*>(_p.s->font->fontFeatures.data());
+            feature.featureCount = gsl::narrow_cast<u32>(_p.s->font->fontFeatures.size());
+            features = &feature;
+            featureRangeLengths = a.textLength;
+            featureRanges = 1;
+        }
+
+        if (_api.clusterMap.size() <= a.textLength)
+        {
+            _api.clusterMap = Buffer<u16>{ static_cast<size_t>(a.textLength) + 1 };
+            _api.textProps = Buffer<DWRITE_SHAPING_TEXT_PROPERTIES>{ a.textLength };
+        }
+
+        for (auto retry = 0;;)
+        {
+            const auto hr = _p.textAnalyzer->GetGlyphs(
+                /* textString          */ _api.bufferLine.data() + a.textPosition,
+                /* textLength          */ a.textLength,
+                /* fontFace            */ mappedFontFace,
+                /* isSideways          */ false,
+                /* isRightToLeft       */ 0,
+                /* scriptAnalysis      */ &a.analysis,
+                /* localeName          */ nullptr,
+                /* numberSubstitution  */ nullptr,
+                /* features            */ &features,
+                /* featureRangeLengths */ &featureRangeLengths,
+                /* featureRanges       */ featureRanges,
+                /* maxGlyphCount       */ gsl::narrow_cast<u32>(_api.glyphIndices.size()),
+                /* clusterMap          */ _api.clusterMap.data(),
+                /* textProps           */ _api.textProps.data(),
+                /* glyphIndices        */ _api.glyphIndices.data(),
+                /* glyphProps          */ _api.glyphProps.data(),
+                /* actualGlyphCount    */ &actualGlyphCount);
+
+            if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) && ++retry < 8)
+            {
+                // Grow factor 1.5x.
+                auto size = _api.glyphIndices.size();
+                size = size + (size >> 1);
+                // Overflow check.
+                Expects(size > _api.glyphIndices.size());
+                _api.glyphIndices = Buffer<u16>{ size };
+                _api.glyphProps = Buffer<DWRITE_SHAPING_GLYPH_PROPERTIES>{ size };
+                continue;
+            }
 
-            const auto it = _r.glyphs.insert(std::move(key), std::move(value));
-            _r.glyphQueue.emplace_back(it);
+            THROW_IF_FAILED(hr);
+            break;
+        }
+
+        if (_api.glyphAdvances.size() < actualGlyphCount)
+        {
+            // Grow the buffer by at least 1.5x and at least of `actualGlyphCount` items.
+            // The 1.5x growth ensures we don't reallocate every time we need 1 more slot.
+            auto size = _api.glyphAdvances.size();
+            size = size + (size >> 1);
+            size = std::max<size_t>(size, actualGlyphCount);
+            _api.glyphAdvances = Buffer<f32>{ size };
+            _api.glyphOffsets = Buffer<DWRITE_GLYPH_OFFSET>{ size };
+        }
+
+        THROW_IF_FAILED(_p.textAnalyzer->GetGlyphPlacements(
+            /* textString          */ _api.bufferLine.data() + a.textPosition,
+            /* clusterMap          */ _api.clusterMap.data(),
+            /* textProps           */ _api.textProps.data(),
+            /* textLength          */ a.textLength,
+            /* glyphIndices        */ _api.glyphIndices.data(),
+            /* glyphProps          */ _api.glyphProps.data(),
+            /* glyphCount          */ actualGlyphCount,
+            /* fontFace            */ mappedFontFace,
+            /* fontEmSize          */ _p.s->font->fontSize,
+            /* isSideways          */ false,
+            /* isRightToLeft       */ 0,
+            /* scriptAnalysis      */ &a.analysis,
+            /* localeName          */ nullptr,
+            /* features            */ &features,
+            /* featureRangeLengths */ &featureRangeLengths,
+            /* featureRanges       */ featureRanges,
+            /* glyphAdvances       */ _api.glyphAdvances.data(),
+            /* glyphOffsets        */ _api.glyphOffsets.data()));
+
+        _api.clusterMap[a.textLength] = gsl::narrow_cast<u16>(actualGlyphCount);
+
+        const auto shift = gsl::narrow_cast<u8>(row.lineRendition != LineRendition::SingleWidth);
+        const auto colors = _p.foregroundBitmap.begin() + _p.colorBitmapRowStride * _api.lastPaintBufferLineCoord.y;
+        auto prevCluster = _api.clusterMap[0];
+        size_t beg = 0;
+
+        for (size_t i = 1; i <= a.textLength; ++i)
+        {
+            const auto nextCluster = _api.clusterMap[i];
+            if (prevCluster == nextCluster)
+            {
+                continue;
+            }
 
-            std::fill(_r.cellGlyphMapping.begin(), _r.cellGlyphMapping.end(), it);
-        }
-    }
+            const size_t col1 = _api.bufferLineColumn[a.textPosition + beg];
+            const size_t col2 = _api.bufferLineColumn[a.textPosition + i];
+            const auto fg = colors[col1 << shift];
 
-    if (!_r.d2dMode)
-    {
-        // The RenderTargetView is later used with OMSetRenderTargets
-        // to tell D3D where stuff is supposed to be rendered at.
-        {
-            wil::com_ptr<ID3D11Texture2D> 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()));
-        }
-        if (_r.customPixelShader)
-        {
-            D3D11_TEXTURE2D_DESC desc{};
-            desc.Width = _api.sizeInPixel.x;
-            desc.Height = _api.sizeInPixel.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.customOffscreenTexture.addressof()));
-            THROW_IF_FAILED(_r.device->CreateShaderResourceView(_r.customOffscreenTexture.get(), nullptr, _r.customOffscreenTextureView.addressof()));
-            THROW_IF_FAILED(_r.device->CreateRenderTargetView(_r.customOffscreenTexture.get(), nullptr, _r.customOffscreenTextureTargetView.addressof()));
-        }
+            const auto expectedAdvance = (col2 - col1) * _p.s->font->cellSize.x;
+            f32 actualAdvance = 0;
+            for (auto j = prevCluster; j < nextCluster; ++j)
+            {
+                actualAdvance += _api.glyphAdvances[j];
+            }
+            _api.glyphAdvances[nextCluster - 1] += expectedAdvance - actualAdvance;
 
-        // Tell D3D which parts of the render target will be visible.
-        // Everything outside of the viewport will be black.
-        {
-            D3D11_VIEWPORT viewport{};
-            viewport.Width = static_cast<float>(_api.sizeInPixel.x);
-            viewport.Height = static_cast<float>(_api.sizeInPixel.y);
-            _r.deviceContext->RSSetViewports(1, &viewport);
-        }
+            row.colors.insert(row.colors.end(), nextCluster - prevCluster, fg);
 
-        if (resize)
-        {
-            D3D11_BUFFER_DESC desc;
-            desc.ByteWidth = gsl::narrow<u32>(totalCellCount * sizeof(Cell)); // totalCellCount can theoretically be UINT32_MAX!
-            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()));
+            prevCluster = nextCluster;
+            beg = i;
         }
 
-        // 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.
-        _setShaderResources();
+        row.glyphIndices.insert(row.glyphIndices.end(), _api.glyphIndices.begin(), _api.glyphIndices.begin() + actualGlyphCount);
+        row.glyphAdvances.insert(row.glyphAdvances.end(), _api.glyphAdvances.begin(), _api.glyphAdvances.begin() + actualGlyphCount);
+        row.glyphOffsets.insert(row.glyphOffsets.end(), _api.glyphOffsets.begin(), _api.glyphOffsets.begin() + actualGlyphCount);
     }
-
-    WI_ClearFlag(_api.invalidations, ApiInvalidations::Size);
-    WI_SetAllFlags(_r.invalidations, RenderInvalidations::ConstBuffer);
 }
 
-void AtlasEngine::_recreateFontDependentResources()
+void AtlasEngine::_mapReplacementCharacter(u32 from, u32 to, ShapedRow& row)
 {
+    if (!_api.replacementCharacterLookedUp)
     {
-        // We're likely resizing the atlas anyways and can
-        // thus also release any of these buffers prematurely.
-        _r.d2dRenderTarget.reset(); // depends on _r.atlasBuffer
-        _r.atlasView.reset();
-        _r.atlasBuffer.reset();
-    }
+        bool succeeded = false;
 
-    // D3D
-    {
-        const auto scaling = GetScaling();
-
-        _r.cellSizeDIP.x = static_cast<float>(_api.fontMetrics.cellSize.x) / scaling;
-        _r.cellSizeDIP.y = static_cast<float>(_api.fontMetrics.cellSize.y) / scaling;
-        _r.cellCount = _api.cellCount;
-        _r.dpi = _api.dpi;
-        _r.fontMetrics = _api.fontMetrics;
-        _r.dipPerPixel = static_cast<float>(USER_DEFAULT_SCREEN_DPI) / static_cast<float>(_r.dpi);
-        _r.pixelPerDIP = static_cast<float>(_r.dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
-        _r.atlasSizeInPixel = { 0, 0 };
-        _r.tileAllocator = TileAllocator{ _api.fontMetrics.cellSize, _api.sizeInPixel };
-
-        _r.glyphs = {};
-        _r.glyphQueue = {};
-        _r.glyphQueue.reserve(64);
-    }
-    // D3D specifically for UpdateDpi()
-    // This compensates for the built in scaling factor in a XAML SwapChainPanel (CompositionScaleX/Y).
-    if (!_api.hwnd)
-    {
-        if (const auto swapChain2 = _r.swapChain.try_query<IDXGISwapChain2>())
-        {
-            const auto inverseScale = static_cast<float>(USER_DEFAULT_SCREEN_DPI) / static_cast<float>(_api.dpi);
-            DXGI_MATRIX_3X2_F matrix{};
-            matrix._11 = inverseScale;
-            matrix._22 = inverseScale;
-            THROW_IF_FAILED(swapChain2->SetMatrixTransform(&matrix));
-        }
-    }
+        u32 mappedLength = 0;
+        _mapCharacters(L"\uFFFD", 1, &mappedLength, _api.replacementCharacterFontFace.put());
 
-    // D2D
-    {
-        // See AtlasEngine::UpdateFont.
-        // It hardcodes indices 0/1/2 in fontAxisValues to the weight/italic/slant axes.
-        // If they're -1.0f they haven't been set by the user and must be filled by us.
-        // When we call SetFontAxisValues() we basically override (disable) DirectWrite's internal font axes,
-        // and if either of the 3 aren't set we'd make it impossible for the user to see bold/italic text.
-#pragma warning(suppress : 26494) // Variable 'standardAxes' is uninitialized. Always initialize an object (type.5).
-        std::array<DWRITE_FONT_AXIS_VALUE, 3> standardAxes;
-
-        if (!_api.fontAxisValues.empty())
+        if (mappedLength == 1)
         {
-            Expects(_api.fontAxisValues.size() >= standardAxes.size());
-            memcpy(standardAxes.data(), _api.fontAxisValues.data(), sizeof(standardAxes));
+            static constexpr u32 codepoint = 0xFFFD;
+            succeeded = SUCCEEDED(_api.replacementCharacterFontFace->GetGlyphIndicesW(&codepoint, 1, &_api.replacementCharacterGlyphIndex));
         }
 
-        const auto restoreFontAxisValues = wil::scope_exit([&]() noexcept {
-            if (!_api.fontAxisValues.empty())
-            {
-                memcpy(_api.fontAxisValues.data(), standardAxes.data(), sizeof(standardAxes));
-            }
-        });
-
-        for (auto italic = 0; italic < 2; ++italic)
+        if (!succeeded)
         {
-            for (auto bold = 0; bold < 2; ++bold)
-            {
-                const auto fontWeight = bold ? DWRITE_FONT_WEIGHT_BOLD : static_cast<DWRITE_FONT_WEIGHT>(_api.fontMetrics.fontWeight);
-                const auto fontStyle = italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
-                auto& textFormat = _r.textFormats[italic][bold];
-
-                wil::com_ptr<IDWriteFont> font;
-                THROW_IF_FAILED(_r.fontMetrics.fontFamily->GetFirstMatchingFont(fontWeight, DWRITE_FONT_STRETCH_NORMAL, fontStyle, font.addressof()));
-                THROW_IF_FAILED(font->CreateFontFace(_r.fontFaces[italic << 1 | bold].put()));
-
-                THROW_IF_FAILED(_sr.dwriteFactory->CreateTextFormat(_api.fontMetrics.fontName.c_str(), _api.fontMetrics.fontCollection.get(), fontWeight, fontStyle, DWRITE_FONT_STRETCH_NORMAL, _api.fontMetrics.fontSizeInDIP, L"", textFormat.put()));
-                THROW_IF_FAILED(textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP));
-
-                // DWRITE_LINE_SPACING_METHOD_UNIFORM:
-                // > Lines are explicitly set to uniform spacing, regardless of contained font sizes.
-                // > This can be useful to avoid the uneven appearance that can occur from font fallback.
-                // We want that. Otherwise fallback fonts might be rendered with an incorrect baseline and get cut off vertically.
-                THROW_IF_FAILED(textFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, _r.cellSizeDIP.y, _api.fontMetrics.baselineInDIP));
-
-                // NOTE: SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER) breaks certain
-                // bitmap fonts which expect glyphs to be laid out left-aligned.
-
-                // NOTE: SetAutomaticFontAxes(DWRITE_AUTOMATIC_FONT_AXES_OPTICAL_SIZE) breaks certain
-                // fonts making them look fairly unslightly. With no option to easily disable this
-                // feature in Windows Terminal, it's better left disabled by default.
-
-                const DWRITE_LINE_SPACING lineSpacing{
-                    .method = DWRITE_LINE_SPACING_METHOD_UNIFORM,
-                    .height = _r.cellSizeDIP.y,
-                    .baseline = _api.fontMetrics.baselineInDIP,
-                    .fontLineGapUsage = DWRITE_FONT_LINE_GAP_USAGE_ENABLED,
-                };
-                THROW_IF_FAILED(textFormat.query<IDWriteTextFormat2>()->SetLineSpacing(&lineSpacing));
-
-                if (!_api.fontAxisValues.empty())
-                {
-                    if (const auto textFormat3 = textFormat.try_query<IDWriteTextFormat3>())
-                    {
-                        // The wght axis defaults to the font weight.
-                        _api.fontAxisValues[0].value = bold || standardAxes[0].value == -1.0f ? static_cast<float>(fontWeight) : standardAxes[0].value;
-                        // The ital axis defaults to 1 if this is italic and 0 otherwise.
-                        _api.fontAxisValues[1].value = italic ? 1.0f : (standardAxes[1].value == -1.0f ? 0.0f : standardAxes[1].value);
-                        // The slnt axis defaults to -12 if this is italic and 0 otherwise.
-                        _api.fontAxisValues[2].value = italic ? -12.0f : (standardAxes[2].value == -1.0f ? 0.0f : standardAxes[2].value);
-
-                        THROW_IF_FAILED(textFormat3->SetFontAxisValues(_api.fontAxisValues.data(), gsl::narrow_cast<u32>(_api.fontAxisValues.size())));
-                        _r.textFormatAxes[italic][bold] = { _api.fontAxisValues.data(), _api.fontAxisValues.size() };
-                    }
-                }
-            }
+            _api.replacementCharacterFontFace.reset();
+            _api.replacementCharacterGlyphIndex = 0;
         }
-    }
-    {
-        _r.typography.reset();
 
-        if (!_api.fontFeatures.empty())
-        {
-            _sr.dwriteFactory->CreateTypography(_r.typography.addressof());
-            for (const auto& v : _api.fontFeatures)
-            {
-                THROW_IF_FAILED(_r.typography->AddFontFeature(v));
-            }
-        }
+        _api.replacementCharacterLookedUp = true;
     }
 
-    WI_ClearFlag(_api.invalidations, ApiInvalidations::Font);
-    WI_SetAllFlags(_r.invalidations, RenderInvalidations::Cursor | RenderInvalidations::ConstBuffer);
-}
-
-IDWriteTextFormat* AtlasEngine::_getTextFormat(bool bold, bool italic) const noexcept
-{
-    return _r.textFormats[italic][bold].get();
-}
-
-const AtlasEngine::Buffer<DWRITE_FONT_AXIS_VALUE>& AtlasEngine::_getTextFormatAxis(bool bold, bool italic) const noexcept
-{
-    return _r.textFormatAxes[italic][bold];
-}
-
-AtlasEngine::Cell* AtlasEngine::_getCell(u16 x, u16 y) noexcept
-{
-    assert(x < _r.cellCount.x);
-    assert(y < _r.cellCount.y);
-    return _r.cells.data() + static_cast<size_t>(_r.cellCount.x) * y + x;
-}
-
-AtlasEngine::TileHashMap::iterator* AtlasEngine::_getCellGlyphMapping(u16 x, u16 y) noexcept
-{
-    assert(x < _r.cellCount.x);
-    assert(y < _r.cellCount.y);
-    return _r.cellGlyphMapping.data() + static_cast<size_t>(_r.cellCount.x) * y + x;
-}
-
-void AtlasEngine::_setCellFlags(u16r coords, CellFlags mask, CellFlags bits) noexcept
-{
-    assert(coords.left <= coords.right);
-    assert(coords.top <= coords.bottom);
-    assert(coords.right <= _r.cellCount.x);
-    assert(coords.bottom <= _r.cellCount.y);
-
-    const auto filter = ~mask;
-    const auto width = static_cast<size_t>(coords.right) - coords.left;
-    const auto height = static_cast<size_t>(coords.bottom) - coords.top;
-    const auto stride = static_cast<size_t>(_r.cellCount.x);
-    auto row = _r.cells.data() + static_cast<size_t>(_r.cellCount.x) * coords.top + coords.left;
-    const auto end = row + height * stride;
-
-    for (; row != end; row += stride)
-    {
-        const auto dataEnd = row + width;
-        for (auto data = row; data != dataEnd; ++data)
-        {
-            const auto current = data->flags;
-            data->flags = (current & filter) | bits;
-        }
-    }
-}
-
-void AtlasEngine::_flushBufferLine()
-{
-    if (_api.bufferLine.empty())
+    if (!_api.replacementCharacterFontFace)
     {
         return;
     }
 
-    const auto cleanup = wil::scope_exit([this]() noexcept {
-        _api.bufferLine.clear();
-        _api.bufferLineColumn.clear();
-    });
-
-    // This would seriously blow us up otherwise.
-    Expects(_api.bufferLineColumn.size() == _api.bufferLine.size() + 1);
-
-    // GH#13962: With the lack of proper LineRendition support, just fill
-    // the remaining columns with whitespace to prevent any weird artifacts.
-    for (auto lastColumn = _api.bufferLineColumn.back(); lastColumn < _api.cellCount.x;)
-    {
-        ++lastColumn;
-        _api.bufferLine.emplace_back(L' ');
-        _api.bufferLineColumn.emplace_back(lastColumn);
-    }
-
-    // NOTE:
-    // This entire function is one huge hack to see if it works.
-
-    // UH OH UNICODE MADNESS AHEAD
-    //
-    // # What do we want?
-    //
-    // Segment a line of text (_api.bufferLine) into unicode "clusters".
-    // Each cluster is one "whole" glyph with diacritics, ligatures, zero width joiners
-    // and whatever else, that should be cached as a whole in our texture atlas.
-    //
-    // # How do we get that?
-    //
-    // ## The unfortunate preface
-    //
-    // DirectWrite can be "reluctant" to segment text into clusters and I found no API which offers simply that.
-    // What it offers are a large number of low level APIs that can sort of be used in combination to do this.
-    // The resulting text parsing is very slow unfortunately, consuming up to 95% of rendering time in extreme cases.
-    //
-    // ## The actual approach
-    //
-    // DirectWrite has 2 APIs which can segment text properly (including ligatures and zero width joiners):
-    // * IDWriteTextAnalyzer1::GetTextComplexity
-    // * IDWriteTextAnalyzer::GetGlyphs
-    //
-    // Both APIs require us to attain an IDWriteFontFace as the functions themselves don't handle font fallback.
-    // This forces us to call IDWriteFontFallback::MapCharacters first.
-    //
-    // Additionally IDWriteTextAnalyzer::GetGlyphs requires an instance of DWRITE_SCRIPT_ANALYSIS,
-    // which can only be attained by running IDWriteTextAnalyzer::AnalyzeScript first.
-    //
-    // Font fallback with IDWriteFontFallback::MapCharacters is very slow.
-
-    const auto textFormat = _getTextFormat(_api.attributes.bold, _api.attributes.italic);
-    const auto& textFormatAxis = _getTextFormatAxis(_api.attributes.bold, _api.attributes.italic);
-
-    TextAnalysisSource analysisSource{ _api.bufferLine.data(), gsl::narrow<UINT32>(_api.bufferLine.size()) };
-    TextAnalysisSink analysisSink{ _api.analysisResults };
-
-    wil::com_ptr<IDWriteFontCollection> fontCollection;
-    THROW_IF_FAILED(textFormat->GetFontCollection(fontCollection.addressof()));
+    static constexpr auto isSoftFontChar = [](wchar_t ch) noexcept {
+        return ch >= 0xEF20 && ch < 0xEF80;
+    };
 
-    wil::com_ptr<IDWriteFontFace> mappedFontFace;
+    auto pos1 = from;
+    auto pos2 = pos1;
+    size_t col1 = _api.bufferLineColumn[from];
+    size_t col2 = col1;
+    auto initialIndicesCount = row.glyphIndices.size();
+    const auto softFontAvailable = !_p.s->font->softFontPattern.empty();
+    auto currentlyMappingSoftFont = isSoftFontChar(_api.bufferLine[pos1]);
+    const auto shift = gsl::narrow_cast<u8>(row.lineRendition != LineRendition::SingleWidth);
+    const auto colors = _p.foregroundBitmap.begin() + _p.colorBitmapRowStride * _api.lastPaintBufferLineCoord.y;
 
-#pragma warning(suppress : 26494) // Variable 'mappedEnd' is uninitialized. Always initialize an object (type.5).
-    for (u32 idx = 0, mappedEnd; idx < _api.bufferLine.size(); idx = mappedEnd)
+    while (pos2 < to)
     {
-        if (_sr.systemFontFallback)
+        col2 = _api.bufferLineColumn[++pos2];
+        if (col1 == col2)
         {
-            auto scale = 1.0f;
-            u32 mappedLength = 0;
-
-            if (textFormatAxis)
-            {
-                wil::com_ptr<IDWriteFontFace5> fontFace5;
-                THROW_IF_FAILED(_sr.systemFontFallback.query<IDWriteFontFallback1>()->MapCharacters(
-                    /* analysisSource */ &analysisSource,
-                    /* textPosition */ idx,
-                    /* textLength */ gsl::narrow_cast<u32>(_api.bufferLine.size()) - idx,
-                    /* baseFontCollection */ fontCollection.get(),
-                    /* baseFamilyName */ _api.fontMetrics.fontName.c_str(),
-                    /* fontAxisValues */ textFormatAxis.data(),
-                    /* fontAxisValueCount */ gsl::narrow_cast<u32>(textFormatAxis.size()),
-                    /* mappedLength */ &mappedLength,
-                    /* scale */ &scale,
-                    /* mappedFontFace */ fontFace5.put()));
-                mappedFontFace = std::move(fontFace5);
-            }
-            else
-            {
-                const auto baseWeight = _api.attributes.bold ? DWRITE_FONT_WEIGHT_BOLD : static_cast<DWRITE_FONT_WEIGHT>(_api.fontMetrics.fontWeight);
-                const auto baseStyle = _api.attributes.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
-                wil::com_ptr<IDWriteFont> font;
-
-                THROW_IF_FAILED(_sr.systemFontFallback->MapCharacters(
-                    /* analysisSource     */ &analysisSource,
-                    /* textPosition       */ idx,
-                    /* textLength         */ gsl::narrow_cast<u32>(_api.bufferLine.size()) - idx,
-                    /* baseFontCollection */ fontCollection.get(),
-                    /* baseFamilyName     */ _api.fontMetrics.fontName.c_str(),
-                    /* baseWeight         */ baseWeight,
-                    /* baseStyle          */ baseStyle,
-                    /* baseStretch        */ DWRITE_FONT_STRETCH_NORMAL,
-                    /* mappedLength       */ &mappedLength,
-                    /* mappedFont         */ font.addressof(),
-                    /* scale              */ &scale));
-
-                mappedFontFace.reset();
-                if (font)
-                {
-                    THROW_IF_FAILED(font->CreateFontFace(mappedFontFace.addressof()));
-                }
-            }
-
-            mappedEnd = idx + mappedLength;
-
-            if (!mappedFontFace)
-            {
-                // Task: Replace all characters in this range with unicode replacement characters.
-                // Input (where "n" is a narrow and "ww" is a wide character):
-                //    _api.bufferLine       = "nwwnnw"
-                //    _api.bufferLineColumn = {0, 1, 1, 3, 4, 5, 5, 6}
-                //                             n  w  w  n  n  w  w
-                // Solution:
-                //   Iterate through bufferLineColumn until the value changes, because this indicates we passed over a
-                //   complete (narrow or wide) cell. To do so we'll use col1 (previous column) and col2 (next column).
-                //   Then we emit a replacement character by telling _emplaceGlyph that this range has no font face.
-                auto pos1 = idx;
-                auto col1 = _api.bufferLineColumn[pos1];
-                for (auto pos2 = idx + 1; pos2 <= mappedEnd; ++pos2)
-                {
-                    if (const auto col2 = _api.bufferLineColumn[pos2]; col1 != col2)
-                    {
-                        _emplaceGlyph(nullptr, pos1, pos2);
-                        pos1 = pos2;
-                        col1 = col2;
-                    }
-                }
-
-                continue;
-            }
+            continue;
         }
-        else
-        {
-            if (!mappedFontFace)
-            {
-                const auto baseWeight = _api.attributes.bold ? DWRITE_FONT_WEIGHT_BOLD : static_cast<DWRITE_FONT_WEIGHT>(_api.fontMetrics.fontWeight);
-                const auto baseStyle = _api.attributes.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
-
-                wil::com_ptr<IDWriteFontFamily> fontFamily;
-                THROW_IF_FAILED(fontCollection->GetFontFamily(0, fontFamily.addressof()));
 
-                wil::com_ptr<IDWriteFont> font;
-                THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(baseWeight, DWRITE_FONT_STRETCH_NORMAL, baseStyle, font.addressof()));
+        const auto cols = col2 - col1;
+        const auto ch = static_cast<u16>(_api.bufferLine[pos1]);
+        const auto nowMappingSoftFont = isSoftFontChar(ch);
 
-                THROW_IF_FAILED(font->CreateFontFace(mappedFontFace.put()));
-            }
-
-            mappedEnd = gsl::narrow_cast<u32>(_api.bufferLine.size());
-        }
+        row.glyphIndices.emplace_back(nowMappingSoftFont ? ch : _api.replacementCharacterGlyphIndex);
+        row.glyphAdvances.emplace_back(static_cast<f32>(cols * _p.s->font->cellSize.x));
+        row.glyphOffsets.emplace_back();
+        row.colors.emplace_back(colors[col1 << shift]);
 
-        // We can reuse idx here, as it'll be reset to "idx = mappedEnd" in the outer loop anyways.
-        for (u32 complexityLength = 0; idx < mappedEnd; idx += complexityLength)
+        if (currentlyMappingSoftFont != nowMappingSoftFont)
         {
-            BOOL isTextSimple;
-            THROW_IF_FAILED(_sr.textAnalyzer->GetTextComplexity(_api.bufferLine.data() + idx, mappedEnd - idx, mappedFontFace.get(), &isTextSimple, &complexityLength, _api.glyphIndices.data()));
+            const auto indicesCount = row.glyphIndices.size();
+            const auto fontFace = currentlyMappingSoftFont && softFontAvailable ? nullptr : _api.replacementCharacterFontFace.get();
 
-            if (isTextSimple)
+            if (indicesCount > initialIndicesCount)
             {
-                size_t beg = 0;
-                for (size_t i = 0; i < complexityLength; ++i)
-                {
-                    if (_emplaceGlyph(mappedFontFace.get(), idx + beg, idx + i + 1))
-                    {
-                        beg = i + 1;
-                    }
-                }
-            }
-            else
-            {
-                _api.analysisResults.clear();
-                THROW_IF_FAILED(_sr.textAnalyzer->AnalyzeScript(&analysisSource, idx, complexityLength, &analysisSink));
-                //_sr.textAnalyzer->AnalyzeBidi(&atlasAnalyzer, idx, complexityLength, &atlasAnalyzer);
-
-                for (const auto& a : _api.analysisResults)
-                {
-                    DWRITE_SCRIPT_ANALYSIS scriptAnalysis{ a.script, static_cast<DWRITE_SCRIPT_SHAPES>(a.shapes) };
-                    u32 actualGlyphCount = 0;
-
-#pragma warning(push)
-#pragma warning(disable : 26494) // Variable '...' is uninitialized. Always initialize an object (type.5).
-                    // None of these variables need to be initialized.
-                    // features/featureRangeLengths are marked _In_reads_opt_(featureRanges).
-                    // featureRanges is only > 0 when we also initialize all these variables.
-                    DWRITE_TYPOGRAPHIC_FEATURES feature;
-                    const DWRITE_TYPOGRAPHIC_FEATURES* features;
-                    u32 featureRangeLengths;
-#pragma warning(pop)
-                    u32 featureRanges = 0;
-
-                    if (!_api.fontFeatures.empty())
-                    {
-                        feature.features = _api.fontFeatures.data();
-                        feature.featureCount = gsl::narrow_cast<u32>(_api.fontFeatures.size());
-                        features = &feature;
-                        featureRangeLengths = a.textLength;
-                        featureRanges = 1;
-                    }
-
-                    if (_api.clusterMap.size() < a.textLength)
-                    {
-                        _api.clusterMap = Buffer<u16>{ a.textLength };
-                        _api.textProps = Buffer<DWRITE_SHAPING_TEXT_PROPERTIES>{ a.textLength };
-                    }
-
-                    for (auto retry = 0;;)
-                    {
-                        const auto hr = _sr.textAnalyzer->GetGlyphs(
-                            /* textString          */ _api.bufferLine.data() + a.textPosition,
-                            /* textLength          */ a.textLength,
-                            /* fontFace            */ mappedFontFace.get(),
-                            /* isSideways          */ false,
-                            /* isRightToLeft       */ a.bidiLevel & 1,
-                            /* scriptAnalysis      */ &scriptAnalysis,
-                            /* localeName          */ nullptr,
-                            /* numberSubstitution  */ nullptr,
-                            /* features            */ &features,
-                            /* featureRangeLengths */ &featureRangeLengths,
-                            /* featureRanges       */ featureRanges,
-                            /* maxGlyphCount       */ gsl::narrow_cast<u32>(_api.glyphProps.size()),
-                            /* clusterMap          */ _api.clusterMap.data(),
-                            /* textProps           */ _api.textProps.data(),
-                            /* glyphIndices        */ _api.glyphIndices.data(),
-                            /* glyphProps          */ _api.glyphProps.data(),
-                            /* actualGlyphCount    */ &actualGlyphCount);
-
-                        if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) && ++retry < 8)
-                        {
-                            // Grow factor 1.5x.
-                            auto size = _api.glyphProps.size();
-                            size = size + (size >> 1);
-                            // Overflow check.
-                            Expects(size > _api.glyphProps.size());
-                            _api.glyphIndices = Buffer<u16>{ size };
-                            _api.glyphProps = Buffer<DWRITE_SHAPING_GLYPH_PROPERTIES>(size);
-                            continue;
-                        }
-
-                        THROW_IF_FAILED(hr);
-                        break;
-                    }
-
-                    if (_api.glyphAdvances.size() < actualGlyphCount)
-                    {
-                        // Grow the buffer by at least 1.5x and at least of `actualGlyphCount` items.
-                        // The 1.5x growth ensures we don't reallocate every time we need 1 more slot.
-                        auto size = _api.glyphAdvances.size();
-                        size = size + (size >> 1);
-                        size = std::max<size_t>(size, actualGlyphCount);
-                        _api.glyphAdvances = Buffer<f32>{ size };
-                        _api.glyphOffsets = Buffer<DWRITE_GLYPH_OFFSET>{ size };
-                    }
-
-                    THROW_IF_FAILED(_sr.textAnalyzer->GetGlyphPlacements(
-                        /* textString          */ _api.bufferLine.data() + a.textPosition,
-                        /* clusterMap          */ _api.clusterMap.data(),
-                        /* textProps           */ _api.textProps.data(),
-                        /* textLength          */ a.textLength,
-                        /* glyphIndices        */ _api.glyphIndices.data(),
-                        /* glyphProps          */ _api.glyphProps.data(),
-                        /* glyphCount          */ actualGlyphCount,
-                        /* fontFace            */ mappedFontFace.get(),
-                        /* fontEmSize          */ _api.fontMetrics.fontSizeInDIP,
-                        /* isSideways          */ false,
-                        /* isRightToLeft       */ a.bidiLevel & 1,
-                        /* scriptAnalysis      */ &scriptAnalysis,
-                        /* localeName          */ nullptr,
-                        /* features            */ &features,
-                        /* featureRangeLengths */ &featureRangeLengths,
-                        /* featureRanges       */ featureRanges,
-                        /* glyphAdvances       */ _api.glyphAdvances.data(),
-                        /* glyphOffsets        */ _api.glyphOffsets.data()));
-
-                    _api.textProps[a.textLength - 1].canBreakShapingAfter = 1;
-
-                    size_t beg = 0;
-                    for (size_t i = 0; i < a.textLength; ++i)
-                    {
-                        if (_api.textProps[i].canBreakShapingAfter)
-                        {
-                            if (_emplaceGlyph(mappedFontFace.get(), a.textPosition + beg, a.textPosition + i + 1))
-                            {
-                                beg = i + 1;
-                            }
-                        }
-                    }
-                }
+                row.mappings.emplace_back(fontFace, gsl::narrow_cast<u32>(initialIndicesCount), gsl::narrow_cast<u32>(indicesCount));
+                initialIndicesCount = indicesCount;
             }
         }
-    }
-}
-// ^^^ Look at that amazing 8-fold nesting level. Lovely. <3
-
-bool AtlasEngine::_emplaceGlyph(IDWriteFontFace* fontFace, size_t bufferPos1, size_t bufferPos2)
-{
-    static constexpr auto replacement = L'\uFFFD';
 
-    // This would seriously blow us up otherwise.
-    Expects(bufferPos1 < bufferPos2 && bufferPos2 <= _api.bufferLine.size());
-
-    // _flushBufferLine() ensures that bufferLineColumn.size() > bufferLine.size().
-    const auto x1 = _api.bufferLineColumn[bufferPos1];
-    const auto x2 = _api.bufferLineColumn[bufferPos2];
-
-    // x1 == x2, if our TextBuffer and DirectWrite disagree where glyph boundaries are. Example:
-    // Our line of text contains a wide glyph consisting of 2 surrogate pairs "xx" and "yy".
-    // If DirectWrite considers the first "xx" to be separate from the second "yy", we'll get:
-    //   _api.bufferLine       = "...xxyy..."
-    //   _api.bufferLineColumn = {01233335678}
-    //                               ^ ^
-    //                              /   \
-    //                      bufferPos1  bufferPos2
-    //   x1: _api.bufferLineColumn[bufferPos1] == 3
-    //   x1: _api.bufferLineColumn[bufferPos2] == 3
-    // --> cellCount (which is x2 - x1) is now 0 (invalid).
-    //
-    // Assuming that the TextBuffer implementation doesn't have any bugs...
-    // I'm not entirely certain why this occurs, but to me, a layperson, it appears as if
-    // IDWriteFontFallback::MapCharacters() doesn't respect extended grapheme clusters.
-    // It could also possibly be due to a difference in the supported Unicode version.
-    if (x1 >= x2 || x2 > _api.cellCount.x)
-    {
-        return false;
+        pos1 = pos2;
+        col1 = col2;
+        currentlyMappingSoftFont = nowMappingSoftFont;
     }
 
-    const auto chars = fontFace ? &_api.bufferLine[bufferPos1] : &replacement;
-    const auto charCount = fontFace ? bufferPos2 - bufferPos1 : 1;
-    const u16 cellCount = x2 - x1;
-
-    auto attributes = _api.attributes;
-    attributes.cellCount = cellCount;
-
-    AtlasKey key{ attributes, gsl::narrow<u16>(charCount), chars };
-    auto it = _r.glyphs.find(key);
-
-    if (it == _r.glyphs.end())
     {
-        // Do fonts exist *in practice* which contain both colored and uncolored glyphs? I'm pretty sure...
-        // However doing it properly means using either of:
-        // * IDWriteFactory2::TranslateColorGlyphRun
-        // * IDWriteFactory4::TranslateColorGlyphRun
-        // * IDWriteFontFace4::GetGlyphImageData
-        //
-        // For the first two I wonder how one is supposed to restitch the 27 required parameters from a
-        // partial (!) text range returned by GetGlyphs(). Our caller breaks the GetGlyphs() result up
-        // into runs of characters up until the first canBreakShapingAfter == true after all.
-        // There's no documentation for it and I'm sure I'd mess it up.
-        // For very obvious reasons I didn't want to write this code.
-        //
-        // IDWriteFontFace4::GetGlyphImageData seems like the best approach and DirectWrite uses the
-        // same code that GetGlyphImageData uses to implement TranslateColorGlyphRun (well partially).
-        // However, it's a heavy operation and parses the font file on disk on every call (TranslateColorGlyphRun doesn't).
-        // For less obvious reasons I didn't want to write this code either.
-        //
-        // So this is a job for future me/someone.
-        // Bonus points for doing it without impacting performance.
-        auto flags = CellFlags::None;
-        if (fontFace)
-        {
-            const auto fontFace2 = wil::try_com_query<IDWriteFontFace2>(fontFace);
-            WI_SetFlagIf(flags, CellFlags::ColoredGlyph, fontFace2 && fontFace2->IsColorFont());
-        }
-
-        // The AtlasValue constructor fills the `coords` variable with a pointer to an array
-        // of at least `cellCount` elements. I did this so that I don't have to type out
-        // `value.data()->coords` again, despite the constructor having all the data necessary.
-        u16x2* coords;
-        AtlasValue value{ flags, cellCount, &coords };
+        const auto indicesCount = row.glyphIndices.size();
+        const auto fontFace = currentlyMappingSoftFont && softFontAvailable ? nullptr : _api.replacementCharacterFontFace.get();
 
-        for (u16 i = 0; i < cellCount; ++i)
+        if (indicesCount > initialIndicesCount)
         {
-            coords[i] = _r.tileAllocator.allocate(_r.glyphs);
+            row.mappings.emplace_back(fontFace, gsl::narrow_cast<u32>(initialIndicesCount), gsl::narrow_cast<u32>(indicesCount));
         }
-
-        it = _r.glyphs.insert(std::move(key), std::move(value));
-        _r.glyphQueue.emplace_back(it);
-    }
-
-    const auto valueData = it->second.data();
-    const auto coords = &valueData->coords[0];
-    const auto cells = _getCell(x1, _api.lastPaintBufferLineCoord.y);
-    const auto cellGlyphMappings = _getCellGlyphMapping(x1, _api.lastPaintBufferLineCoord.y);
-
-    for (u32 i = 0; i < cellCount; ++i)
-    {
-        cells[i].tileIndex = coords[i];
-        // We should apply the column color and flags from each column (instead
-        // of copying them from the x1) so that ligatures can appear in multiple
-        // colors with different line styles.
-        cells[i].flags = valueData->flags | _api.bufferLineMetadata[static_cast<size_t>(x1) + i].flags;
-        cells[i].color = _api.bufferLineMetadata[static_cast<size_t>(x1) + i].colors;
     }
-
-    std::fill_n(cellGlyphMappings, cellCount, it);
-    return true;
 }
diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h
index 1ca8f150415..7e38fae5b5c 100644
--- a/src/renderer/atlas/AtlasEngine.h
+++ b/src/renderer/atlas/AtlasEngine.h
@@ -3,14 +3,13 @@
 
 #pragma once
 
-#include <d2d1_1.h>
-#include <d3d11_1.h>
 #include <dwrite_3.h>
+#include <d3d11_2.h>
+#include <dxgi1_3.h>
 
-#include "../../renderer/inc/IRenderEngine.hpp"
-#include "DWriteTextAnalysis.h"
+#include "common.h"
 
-namespace Microsoft::Console::Render
+namespace Microsoft::Console::Render::Atlas
 {
     struct TextAnalysisSinkResult;
 
@@ -80,1009 +79,59 @@ namespace Microsoft::Console::Render
         [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept override;
         void UpdateHyperlinkHoveredId(uint16_t hoveredId) noexcept override;
 
-        // Some helper classes for the implementation.
-        // public because I don't want to sprinkle the code with friends.
-    public:
-#define ATLAS_POD_OPS(type)                                    \
-    constexpr bool operator==(const type& rhs) const noexcept  \
-    {                                                          \
-        return __builtin_memcmp(this, &rhs, sizeof(rhs)) == 0; \
-    }                                                          \
-                                                               \
-    constexpr bool operator!=(const type& rhs) const noexcept  \
-    {                                                          \
-        return __builtin_memcmp(this, &rhs, sizeof(rhs)) != 0; \
-    }
-
-#define ATLAS_FLAG_OPS(type, underlying)                                                       \
-    friend constexpr type operator~(type v) noexcept                                           \
-    {                                                                                          \
-        return static_cast<type>(~static_cast<underlying>(v));                                 \
-    }                                                                                          \
-    friend constexpr type operator|(type lhs, type rhs) noexcept                               \
-    {                                                                                          \
-        return static_cast<type>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs)); \
-    }                                                                                          \
-    friend constexpr type operator&(type lhs, type rhs) noexcept                               \
-    {                                                                                          \
-        return static_cast<type>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs)); \
-    }                                                                                          \
-    friend constexpr type operator^(type lhs, type rhs) noexcept                               \
-    {                                                                                          \
-        return static_cast<type>(static_cast<underlying>(lhs) ^ static_cast<underlying>(rhs)); \
-    }                                                                                          \
-    friend constexpr void operator|=(type& lhs, type rhs) noexcept                             \
-    {                                                                                          \
-        lhs = lhs | rhs;                                                                       \
-    }                                                                                          \
-    friend constexpr void operator&=(type& lhs, type rhs) noexcept                             \
-    {                                                                                          \
-        lhs = lhs & rhs;                                                                       \
-    }                                                                                          \
-    friend constexpr void operator^=(type& lhs, type rhs) noexcept                             \
-    {                                                                                          \
-        lhs = lhs ^ rhs;                                                                       \
-    }
-
-        template<typename T>
-        struct vec2
-        {
-            T x{};
-            T y{};
-
-            ATLAS_POD_OPS(vec2)
-        };
-
-        template<typename T>
-        struct vec3
-        {
-            T x{};
-            T y{};
-            T z{};
-
-            ATLAS_POD_OPS(vec3)
-        };
-
-        template<typename T>
-        struct vec4
-        {
-            T x{};
-            T y{};
-            T z{};
-            T w{};
-
-            ATLAS_POD_OPS(vec4)
-        };
-
-        template<typename T>
-        struct rect
-        {
-            T left{};
-            T top{};
-            T right{};
-            T bottom{};
-
-            ATLAS_POD_OPS(rect)
-
-            constexpr bool non_empty() const noexcept
-            {
-                return (left < right) & (top < bottom);
-            }
-        };
-
-        using u8 = uint8_t;
-
-        using u16 = uint16_t;
-        using u16x2 = vec2<u16>;
-        using u16r = rect<u16>;
-
-        using i16 = int16_t;
-
-        using u32 = uint32_t;
-        using u32x2 = vec2<u32>;
-
-        using i32 = int32_t;
-
-        using f32 = float;
-        using f32x2 = vec2<f32>;
-        using f32x3 = vec3<f32>;
-        using f32x4 = vec4<f32>;
-
-        struct TextAnalyzerResult
-        {
-            u32 textPosition = 0;
-            u32 textLength = 0;
-
-            // These 2 fields represent DWRITE_SCRIPT_ANALYSIS.
-            // Not using DWRITE_SCRIPT_ANALYSIS drops the struct size from 20 down to 12 bytes.
-            u16 script = 0;
-            u8 shapes = 0;
-
-            u8 bidiLevel = 0;
-        };
-
     private:
-        // I wrote `Buffer` instead of using `std::vector`, because I want to convey that these things
-        // explicitly _don't_ hold resizeable contents, but rather plain content of a fixed size.
-        // For instance I didn't want a resizeable vector with a `push_back` method for my fixed-size
-        // viewport arrays - that doesn't make sense after all. `Buffer` also doesn't initialize
-        // contents to zero, allowing rapid creation/destruction and you can easily specify a custom
-        // (over-)alignment which can improve rendering perf by up to ~20% over `std::vector`.
-        template<typename T, size_t Alignment = alignof(T)>
-        struct Buffer
-        {
-            constexpr Buffer() noexcept = default;
-
-            explicit Buffer(size_t size) :
-                _data{ allocate(size) },
-                _size{ size }
-            {
-                std::uninitialized_default_construct_n(_data, size);
-            }
-
-            Buffer(const T* data, size_t size) :
-                _data{ allocate(size) },
-                _size{ size }
-            {
-                // Changing the constructor arguments to accept std::span might
-                // be a good future extension, but not to improve security here.
-                // You can trivially construct std::span's from invalid ranges.
-                // Until then the raw-pointer style is more practical.
-#pragma warning(suppress : 26459) // You called an STL function '...' with a raw pointer parameter at position '3' that may be unsafe [...].
-                std::uninitialized_copy_n(data, size, _data);
-            }
-
-            ~Buffer()
-            {
-                destroy();
-            }
-
-            Buffer(Buffer&& other) noexcept :
-                _data{ std::exchange(other._data, nullptr) },
-                _size{ std::exchange(other._size, 0) }
-            {
-            }
-
-#pragma warning(suppress : 26432) // If you define or delete any default operation in the type '...', define or delete them all (c.21).
-            Buffer& operator=(Buffer&& other) noexcept
-            {
-                destroy();
-                _data = std::exchange(other._data, nullptr);
-                _size = std::exchange(other._size, 0);
-                return *this;
-            }
-
-            explicit operator bool() const noexcept
-            {
-                return _data != nullptr;
-            }
-
-            T& operator[](size_t index) noexcept
-            {
-                assert(index < _size);
-                return _data[index];
-            }
-
-            const T& operator[](size_t index) const noexcept
-            {
-                assert(index < _size);
-                return _data[index];
-            }
-
-            T* data() noexcept
-            {
-                return _data;
-            }
-
-            const T* data() const noexcept
-            {
-                return _data;
-            }
-
-            size_t size() const noexcept
-            {
-                return _size;
-            }
-
-            T* begin() noexcept
-            {
-                return _data;
-            }
-
-            T* begin() const noexcept
-            {
-                return _data;
-            }
-
-            T* end() noexcept
-            {
-                return _data + _size;
-            }
-
-            T* end() const noexcept
-            {
-                return _data + _size;
-            }
-
-        private:
-            // These two functions don't need to use scoped objects or standard allocators,
-            // since this class is in fact an scoped allocator object itself.
-#pragma warning(push)
-#pragma warning(disable : 26402) // Return a scoped object instead of a heap-allocated if it has a move constructor (r.3).
-#pragma warning(disable : 26409) // Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11).
-            static T* allocate(size_t size)
-            {
-                if constexpr (Alignment <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
-                {
-                    return static_cast<T*>(::operator new(size * sizeof(T)));
-                }
-                else
-                {
-                    return static_cast<T*>(::operator new(size * sizeof(T), static_cast<std::align_val_t>(Alignment)));
-                }
-            }
-
-            static void deallocate(T* data) noexcept
-            {
-                if constexpr (Alignment <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
-                {
-                    ::operator delete(data);
-                }
-                else
-                {
-                    ::operator delete(data, static_cast<std::align_val_t>(Alignment));
-                }
-            }
-#pragma warning(pop)
-
-            void destroy() noexcept
-            {
-                std::destroy_n(_data, _size);
-                deallocate(_data);
-            }
-
-            T* _data = nullptr;
-            size_t _size = 0;
-        };
-
-        // This structure works similar to how std::string works:
-        // You can think of a std::string as a structure consisting of:
-        //   char*  data;
-        //   size_t size;
-        //   size_t capacity;
-        // where data is some backing memory allocated on the heap.
-        //
-        // But std::string employs an optimization called "small string optimization" (SSO).
-        // To simplify things it could be explained as:
-        // If the string capacity is small, then the characters are stored inside the "data"
-        // pointer and you make sure to set the lowest bit in the pointer one way or another.
-        // Heap allocations are always aligned by at least 4-8 bytes on any platform.
-        // If the address of the "data" pointer is not even you know data is stored inline.
-        template<typename T>
-        union SmallObjectOptimizer
-        {
-            static_assert(std::is_trivially_copyable_v<T>);
-            static_assert(std::has_unique_object_representations_v<T>);
-
-            T* allocated = nullptr;
-            T inlined;
-
-            constexpr SmallObjectOptimizer() = default;
-
-            SmallObjectOptimizer(const SmallObjectOptimizer& other) = delete;
-            SmallObjectOptimizer& operator=(const SmallObjectOptimizer& other) = delete;
-
-            SmallObjectOptimizer(SmallObjectOptimizer&& other) noexcept
-            {
-                memcpy(this, &other, std::max(sizeof(allocated), sizeof(inlined)));
-                other.allocated = nullptr;
-            }
-
-            SmallObjectOptimizer& operator=(SmallObjectOptimizer&& other) noexcept
-            {
-                std::destroy_at(this);
-                return *std::construct_at(this, std::move(other));
-            }
-
-            ~SmallObjectOptimizer()
-            {
-                if (!is_inline())
-                {
-#pragma warning(suppress : 26408) // Avoid malloc() and free(), prefer the nothrow version of new with delete (r.10).
-                    free(allocated);
-                }
-            }
-
-            T* initialize(size_t byteSize)
-            {
-                if (would_inline(byteSize))
-                {
-                    return &inlined;
-                }
-
-#pragma warning(suppress : 26408) // Avoid malloc() and free(), prefer the nothrow version of new with delete (r.10).
-                allocated = THROW_IF_NULL_ALLOC(static_cast<T*>(malloc(byteSize)));
-                return allocated;
-            }
-
-            constexpr bool would_inline(size_t byteSize) const noexcept
-            {
-                return byteSize <= sizeof(T);
-            }
-
-            bool is_inline() const noexcept
-            {
-                // VSO-1430353: __builtin_bitcast crashes the compiler under /permissive-. (BODGY)
-#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
-                return (reinterpret_cast<uintptr_t>(allocated) & 1) != 0;
-            }
-
-            const T* data() const noexcept
-            {
-                return is_inline() ? &inlined : allocated;
-            }
-
-            size_t size() const noexcept
-            {
-                return is_inline() ? sizeof(inlined) : _msize(allocated);
-            }
-        };
-
-        struct FontMetrics
-        {
-            wil::com_ptr<IDWriteFontCollection> fontCollection;
-            wil::com_ptr<IDWriteFontFamily> fontFamily;
-            std::wstring fontName;
-            f32 baselineInDIP = 0.0f;
-            f32 fontSizeInDIP = 0.0f;
-            f32 advanceScale = 0;
-            u16x2 cellSize;
-            u16 fontWeight = 0;
-            u16 underlinePos = 0;
-            u16 underlineWidth = 0;
-            u16 strikethroughPos = 0;
-            u16 strikethroughWidth = 0;
-            u16x2 doubleUnderlinePos;
-            u16 thinLineWidth = 0;
-        };
-
-        // These flags are shared with shader_ps.hlsl.
-        // If you change this be sure to copy it over to shader_ps.hlsl.
-        //
-        // clang-format off
-        enum class CellFlags : u32
-        {
-            None            = 0x00000000,
-            Inlined         = 0x00000001,
-
-            ColoredGlyph    = 0x00000002,
-
-            Cursor          = 0x00000008,
-            Selected        = 0x00000010,
-
-            BorderLeft      = 0x00000020,
-            BorderTop       = 0x00000040,
-            BorderRight     = 0x00000080,
-            BorderBottom    = 0x00000100,
-            Underline       = 0x00000200,
-            UnderlineDotted = 0x00000400,
-            UnderlineDouble = 0x00000800,
-            Strikethrough   = 0x00001000,
-        };
-        // clang-format on
-        ATLAS_FLAG_OPS(CellFlags, u32)
-
-        // This structure is shared with the GPU shader and needs to follow certain alignment rules.
-        // You can generally assume that only u32 or types of that alignment are allowed.
-        struct Cell
-        {
-            alignas(u32) u16x2 tileIndex;
-            alignas(u32) CellFlags flags = CellFlags::None;
-            u32x2 color;
-        };
-
-        struct AtlasKeyAttributes
-        {
-            u16 inlined : 1;
-            u16 bold : 1;
-            u16 italic : 1;
-            u16 cellCount : 13;
-
-            ATLAS_POD_OPS(AtlasKeyAttributes)
-        };
-
-        struct AtlasKeyData
-        {
-            AtlasKeyAttributes attributes;
-            u16 charCount;
-            wchar_t chars[14];
-        };
-
-        struct AtlasKey
-        {
-            AtlasKey(AtlasKeyAttributes attributes, u16 charCount, const wchar_t* chars)
-            {
-                const auto size = dataSize(charCount);
-                const auto data = _data.initialize(size);
-                attributes.inlined = _data.would_inline(size);
-                data->attributes = attributes;
-                data->charCount = charCount;
-                memcpy(&data->chars[0], chars, static_cast<size_t>(charCount) * sizeof(AtlasKeyData::chars[0]));
-            }
-
-            const AtlasKeyData* data() const noexcept
-            {
-                return _data.data();
-            }
-
-            size_t hash() const noexcept
-            {
-                const auto d = data();
-#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
-                return std::_Fnv1a_append_bytes(std::_FNV_offset_basis, reinterpret_cast<const u8*>(d), dataSize(d->charCount));
-            }
-
-            bool operator==(const AtlasKey& rhs) const noexcept
-            {
-                const auto a = data();
-                const auto b = rhs.data();
-                return a->charCount == b->charCount && memcmp(a, b, dataSize(a->charCount)) == 0;
-            }
-
-        private:
-            SmallObjectOptimizer<AtlasKeyData> _data;
-
-            static constexpr size_t dataSize(u16 charCount) noexcept
-            {
-                // This returns the actual byte size of a AtlasKeyData struct for the given charCount.
-                // The `wchar_t chars[2]` is only a buffer for the inlined variant after
-                // all and the actual charCount can be smaller or larger. Due to this we
-                // remove the size of the `chars` array and add its true length on top.
-                return sizeof(AtlasKeyData) - sizeof(AtlasKeyData::chars) + static_cast<size_t>(charCount) * sizeof(AtlasKeyData::chars[0]);
-            }
-        };
-
-        struct CachedGlyphLayout
-        {
-            wil::com_ptr<IDWriteTextLayout> textLayout;
-            f32x2 offset;
-            f32x2 scale;
-            f32x2 scaleCenter;
-            D2D1_DRAW_TEXT_OPTIONS options = D2D1_DRAW_TEXT_OPTIONS_NONE;
-            bool scalingRequired = false;
-
-            explicit operator bool() const noexcept;
-            void reset() noexcept;
-            void applyScaling(ID2D1RenderTarget* d2dRenderTarget, D2D1_POINT_2F origin) const noexcept;
-            void undoScaling(ID2D1RenderTarget* d2dRenderTarget) const noexcept;
-        };
-
-        struct AtlasValueData
-        {
-            CellFlags flags = CellFlags::None;
-            u16x2 coords[7];
-        };
-
-        struct AtlasValue
-        {
-            AtlasValue(CellFlags flags, u16 cellCount, u16x2** coords)
-            {
-                __assume(coords != nullptr);
-                const auto size = dataSize(cellCount);
-                const auto data = _data.initialize(size);
-                WI_SetFlagIf(flags, CellFlags::Inlined, _data.would_inline(size));
-                data->flags = flags;
-                *coords = &data->coords[0];
-            }
-
-            const AtlasValueData* data() const noexcept
-            {
-                return _data.data();
-            }
-
-            CachedGlyphLayout cachedLayout;
-
-        private:
-            SmallObjectOptimizer<AtlasValueData> _data;
-
-            static constexpr size_t dataSize(u16 coordCount) noexcept
-            {
-                return sizeof(AtlasValueData) - sizeof(AtlasValueData::coords) + static_cast<size_t>(coordCount) * sizeof(AtlasValueData::coords[0]);
-            }
-        };
-
-        struct AtlasKeyHasher
-        {
-            using is_transparent = int;
-
-            size_t operator()(const AtlasKey& v) const noexcept
-            {
-                return v.hash();
-            }
-
-            size_t operator()(const std::list<std::pair<AtlasKey, AtlasValue>>::iterator& v) const noexcept
-            {
-                return operator()(v->first);
-            }
-        };
-
-        struct AtlasKeyEq
-        {
-            using is_transparent = int;
-
-            bool operator()(const AtlasKey& a, const std::list<std::pair<AtlasKey, AtlasValue>>::iterator& b) const noexcept
-            {
-                return a == b->first;
-            }
-
-            bool operator()(const std::list<std::pair<AtlasKey, AtlasValue>>::iterator& a, const std::list<std::pair<AtlasKey, AtlasValue>>::iterator& b) const noexcept
-            {
-                return operator()(a->first, b);
-            }
-        };
-
-        struct TileHashMap
-        {
-            using iterator = std::list<std::pair<AtlasKey, AtlasValue>>::iterator;
-
-            TileHashMap() noexcept = default;
-
-            iterator end() noexcept
-            {
-                return _lru.end();
-            }
-
-            iterator find(const AtlasKey& key)
-            {
-                const auto it = _map.find(key);
-                if (it != _map.end())
-                {
-                    // Move the key to the head of the LRU queue.
-                    makeNewest(*it);
-                    return *it;
-                }
-                return end();
-            }
-
-            iterator insert(AtlasKey&& key, AtlasValue&& value)
-            {
-                // Insert the key/value right at the head of the LRU queue, just like find().
-                //
-                // && decays to & if the argument is named, because C++ is a simple language
-                // and so you have to std::move it again, because C++ is a simple language.
-                _lru.emplace_front(std::move(key), std::move(value));
-                auto it = _lru.begin();
-                _map.emplace(it);
-                return it;
-            }
-
-            void makeNewest(const iterator& it)
-            {
-                _lru.splice(_lru.begin(), _lru, it);
-            }
-
-            void popOldestTiles(std::vector<u16x2>& out) noexcept
-            {
-                Expects(!_lru.empty());
-                const auto it = --_lru.end();
-
-                const auto key = it->first.data();
-                const auto value = it->second.data();
-                const auto beg = &value->coords[0];
-                const auto cellCount = key->attributes.cellCount;
-
-                const auto offset = out.size();
-                out.resize(offset + cellCount);
-                std::copy_n(beg, cellCount, out.begin() + offset);
-
-                _map.erase(it);
-                _lru.pop_back();
-            }
-
-        private:
-            // Please don't copy this code. It's a proof-of-concept.
-            // If you need a LRU hash-map, write a custom one with an intrusive
-            // prev/next linked list (it's easier than you might think!).
-            std::list<std::pair<AtlasKey, AtlasValue>> _lru;
-            std::unordered_set<iterator, AtlasKeyHasher, AtlasKeyEq> _map;
-        };
-
-        // TileAllocator yields `tileSize`-sized tiles for our texture atlas.
-        // While doing so it'll grow the atlas size() by a factor of 2 if needed.
-        // Once the setMaxArea() is exceeded it'll stop growing and instead
-        // snatch tiles back from the oldest TileHashMap entries.
-        //
-        // The quadratic growth works by alternating the size()
-        // between an 1:1 and 2:1 aspect ratio, like so:
-        //   (64,64) -> (128,64) -> (128,128) -> (256,128) -> (256,256)
-        // These initial tile positions allocate() returns are in a Z
-        // pattern over the available space in the atlas texture.
-        // You can log the `return _pos;` in allocate() using "Tracepoint"s
-        // in Visual Studio if you'd like to understand the Z pattern better.
-        struct TileAllocator
-        {
-            TileAllocator() = default;
-
-            explicit TileAllocator(u16x2 tileSize, u16x2 windowSize) noexcept :
-                _tileSize{ tileSize }
-            {
-                const auto initialSize = std::max(u16{ _absoluteMinSize }, std::bit_ceil(std::max(tileSize.x, tileSize.y)));
-                _size = { initialSize, initialSize };
-                _limit = { gsl::narrow_cast<u16>(initialSize - _tileSize.x), gsl::narrow_cast<u16>(initialSize - _tileSize.y) };
-                setMaxArea(windowSize);
-            }
-
-            u16x2 size() const noexcept
-            {
-                return _size;
-            }
-
-            void setMaxArea(u16x2 windowSize) noexcept
-            {
-                // _generate() uses a quadratic growth factor for _size's area.
-                // Once it exceeds the _maxArea, it'll start snatching tiles back from the
-                // TileHashMap using its LRU queue. Since _size will at least reach half
-                // of _maxSize (because otherwise it could still grow by a factor of 2)
-                // and by ensuring that _maxArea is at least twice the window size
-                // we make it impossible* for _generate() to return false before
-                // TileHashMap contains at least as many tiles as the window contains.
-                // If that wasn't the case we'd snatch and reuse tiles that are still in use.
-                // * lhecker's legal department:
-                //   No responsibility is taken for the correctness of this information.
-                setMaxArea(static_cast<size_t>(windowSize.x) * static_cast<size_t>(windowSize.y) * 2);
-            }
-
-            void setMaxArea(size_t max) noexcept
-            {
-                // We need to reserve at least 1 extra `tileArea`, because the tile
-                // at position {0,0} is already reserved for the cursor texture.
-                const auto tileArea = static_cast<size_t>(_tileSize.x) * static_cast<size_t>(_tileSize.y);
-                _maxArea = clamp(max + tileArea, _absoluteMinArea, _absoluteMaxArea);
-                _updateCanGenerate();
-            }
-
-            u16x2 allocate(TileHashMap& map) noexcept
-            {
-                if (_generate())
-                {
-                    return _pos;
-                }
-
-                if (_cache.empty())
-                {
-                    map.popOldestTiles(_cache);
-                }
-
-                const auto pos = _cache.back();
-                _cache.pop_back();
-                return pos;
-            }
-
-        private:
-            // This method generates the Z pattern coordinates
-            // described above in the TileAllocator comment.
-            bool _generate() noexcept
-            {
-                if (!_canGenerate)
-                {
-                    return false;
-                }
-
-                // We need to backup _pos/_size in case our resize below exceeds _maxArea.
-                // In that case we have to restore _pos/_size so that if _maxArea is increased
-                // (window resize for instance), we can pick up where we previously left off.
-                const auto pos = _pos;
-
-                _pos.x += _tileSize.x;
-                if (_pos.x <= _limit.x)
-                {
-                    return true;
-                }
-
-                _pos.y += _tileSize.y;
-                if (_pos.y <= _limit.y)
-                {
-                    _pos.x = _originX;
-                    return true;
-                }
-
-                // Same as for pos.
-                const auto size = _size;
-
-                // This implements a quadratic growth factor for _size, by
-                // alternating between an 1:1 and 2:1 aspect ratio, like so:
-                //   (64,64) -> (128,64) -> (128,128) -> (256,128) -> (256,256)
-                // This behavior is strictly dependent on setMaxArea(u16x2)'s
-                // behavior. See its comment for an explanation.
-                if (_size.x == _size.y)
-                {
-                    _size.x *= 2;
-                    _pos.y = 0;
-                }
-                else
-                {
-                    _size.y *= 2;
-                    _pos.x = 0;
-                }
-
-                _updateCanGenerate();
-                if (_canGenerate)
-                {
-                    _limit = { gsl::narrow_cast<u16>(_size.x - _tileSize.x), gsl::narrow_cast<u16>(_size.y - _tileSize.y) };
-                    _originX = _pos.x;
-                }
-                else
-                {
-                    _size = size;
-                    _pos = pos;
-                }
-
-                return _canGenerate;
-            }
-
-            void _updateCanGenerate() noexcept
-            {
-                _canGenerate = static_cast<size_t>(_size.x) * static_cast<size_t>(_size.y) <= _maxArea;
-            }
-
-            static constexpr u16 _absoluteMinSize = 256;
-            static constexpr size_t _absoluteMinArea = _absoluteMinSize * _absoluteMinSize;
-            // TODO: Consider using IDXGIAdapter3::QueryVideoMemoryInfo() and IDXGIAdapter3::RegisterVideoMemoryBudgetChangeNotificationEvent()
-            // That way we can make better to use of a user's available video memory.
-            static constexpr size_t _absoluteMaxArea = D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION * D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION;
-
-            std::vector<u16x2> _cache;
-            size_t _maxArea = _absoluteMaxArea;
-            u16x2 _tileSize;
-            u16x2 _size;
-            u16x2 _limit;
-            // Since _pos starts at {0, 0}, it'll result in the first allocate()d tile to be at {_tileSize.x, 0}.
-            // Coincidentally that's exactly what we want as the cursor texture lives at {0, 0}.
-            u16x2 _pos;
-            u16 _originX = 0;
-            // Indicates whether we've exhausted our Z pattern across the atlas texture.
-            // If this is false, we have to snatch tiles back from TileHashMap.
-            bool _canGenerate = true;
-        };
-
-        struct CachedCursorOptions
-        {
-            u32 cursorColor = INVALID_COLOR;
-            u16 cursorType = gsl::narrow_cast<u16>(CursorType::Legacy);
-            u8 heightPercentage = 20;
-            u8 _padding = 0;
-
-            ATLAS_POD_OPS(CachedCursorOptions)
-        };
-
-        struct BufferLineMetadata
-        {
-            u32x2 colors;
-            CellFlags flags = CellFlags::None;
-        };
-
-        // NOTE: D3D constant buffers sizes must be a multiple of 16 bytes.
-        struct alignas(16) ConstBuffer
-        {
-            // WARNING: Modify this carefully after understanding how HLSL struct packing works.
-            // The gist is:
-            // * Minimum alignment is 4 bytes (like `#pragma pack 4`)
-            // * Members cannot straddle 16 byte boundaries
-            //   This means a structure like {u32; u32; u32; u32x2} would require
-            //   padding so that it is {u32; u32; u32; <4 byte padding>; u32x2}.
-            // * bool will probably not work the way you want it to,
-            //   because HLSL uses 32-bit bools and C++ doesn't.
-            alignas(sizeof(f32x4)) f32x4 viewport;
-            alignas(sizeof(f32x4)) f32 gammaRatios[4]{};
-            alignas(sizeof(f32)) f32 enhancedContrast = 0;
-            alignas(sizeof(u32)) u32 cellCountX = 0;
-            alignas(sizeof(u32x2)) u32x2 cellSize;
-            alignas(sizeof(u32)) u32 underlinePos = 0;
-            alignas(sizeof(u32)) u32 underlineWidth = 0;
-            alignas(sizeof(u32)) u32 strikethroughPos = 0;
-            alignas(sizeof(u32)) u32 strikethroughWidth = 0;
-            alignas(sizeof(u32x2)) u32x2 doubleUnderlinePos;
-            alignas(sizeof(u32)) u32 thinLineWidth = 0;
-            alignas(sizeof(u32)) u32 backgroundColor = 0;
-            alignas(sizeof(u32)) u32 cursorColor = 0;
-            alignas(sizeof(u32)) u32 selectionColor = 0;
-            alignas(sizeof(u32)) u32 useClearType = 0;
-#pragma warning(suppress : 4324) // 'ConstBuffer': structure was padded due to alignment specifier
-        };
-
-        struct alignas(16) CustomConstBuffer
-        {
-            // WARNING: Same rules as for ConstBuffer above apply.
-            alignas(sizeof(f32)) f32 time = 0;
-            alignas(sizeof(f32)) f32 scale = 0;
-            alignas(sizeof(f32x2)) f32x2 resolution;
-            alignas(sizeof(f32x4)) f32x4 background;
-#pragma warning(suppress : 4324) // 'CustomConstBuffer': structure was padded due to alignment specifier
-        };
-
-        // Handled in BeginPaint()
-        enum class ApiInvalidations : u8
-        {
-            None = 0,
-            Title = 1 << 0,
-            Device = 1 << 1,
-            SwapChain = 1 << 2,
-            Size = 1 << 3,
-            Font = 1 << 4,
-            Settings = 1 << 5,
-        };
-        ATLAS_FLAG_OPS(ApiInvalidations, u8)
-
-        // Handled in Present()
-        enum class RenderInvalidations : u8
-        {
-            None = 0,
-            Cursor = 1 << 0,
-            ConstBuffer = 1 << 1,
-        };
-        ATLAS_FLAG_OPS(RenderInvalidations, u8)
-
-        // MSVC STL (version 22000) implements std::clamp<T>(T, T, T) in terms of the generic
-        // std::clamp<T, Predicate>(T, T, T, Predicate) with std::less{} as the argument,
-        // which introduces branching. While not perfect, this is still better than std::clamp.
-        template<typename T>
-        static constexpr T clamp(T val, T min, T max)
-        {
-            return std::max(min, std::min(max, val));
-        }
-
         // AtlasEngine.cpp
-        [[nodiscard]] HRESULT _handleException(const wil::ResultException& exception) noexcept;
-        __declspec(noinline) void _createResources();
-        void _releaseSwapChain();
-        __declspec(noinline) void _createSwapChain();
-        __declspec(noinline) void _recreateSizeDependentResources();
-        __declspec(noinline) void _recreateFontDependentResources();
-        IDWriteTextFormat* _getTextFormat(bool bold, bool italic) const noexcept;
-        const Buffer<DWRITE_FONT_AXIS_VALUE>& _getTextFormatAxis(bool bold, bool italic) const noexcept;
-        Cell* _getCell(u16 x, u16 y) noexcept;
-        TileHashMap::iterator* _getCellGlyphMapping(u16 x, u16 y) noexcept;
-        void _setCellFlags(u16r coords, CellFlags mask, CellFlags bits) noexcept;
+        ATLAS_ATTR_COLD void _handleSettingsUpdate();
+        void _recreateFontDependentResources();
+        void _recreateCellCountDependentResources();
         void _flushBufferLine();
-        bool _emplaceGlyph(IDWriteFontFace* fontFace, size_t bufferPos1, size_t bufferPos2);
+        void _mapCharacters(const wchar_t* text, u32 textLength, u32* mappedLength, IDWriteFontFace2** mappedFontFace) const;
+        void _mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 length, ShapedRow& row);
+        ATLAS_ATTR_COLD void _mapReplacementCharacter(u32 from, u32 to, ShapedRow& row);
 
         // AtlasEngine.api.cpp
         void _resolveTransparencySettings() noexcept;
         void _updateFont(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes);
-        void _resolveFontMetrics(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics = nullptr) const;
+        void _resolveFontMetrics(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontSettings* fontMetrics = nullptr) const;
 
         // AtlasEngine.r.cpp
-        void _renderWithCustomShader() const;
-        void _setShaderResources() const;
-        void _updateConstantBuffer() const noexcept;
-        void _adjustAtlasSize();
-        void _processGlyphQueue();
-        void _drawGlyph(const TileHashMap::iterator& it) const;
-        CachedGlyphLayout _getCachedGlyphLayout(const wchar_t* chars, u16 charsLength, u16 cellCount, IDWriteTextFormat* textFormat, bool coloredGlyph) const;
-        void _drawCursor(u16r rect, u32 color, bool clear);
-        ID2D1Brush* _brushWithColor(u32 color);
-        void _d2dPresent();
-        void _d2dCreateRenderTarget();
-        void _d2dDrawDirtyArea();
-        u16 _d2dDrawGlyph(const TileHashMap::iterator& it, u16x2 coord, u32 color);
-        void _d2dDrawLine(u16r rect, u16 pos, u16 width, u32 color, ID2D1StrokeStyle* strokeStyle = nullptr);
-        void _d2dFillRectangle(u16r rect, u32 color);
-        void _d2dCellFlagRendererCursor(u16r rect, u32 color);
-        void _d2dCellFlagRendererSelected(u16r rect, u32 color);
-        void _d2dCellFlagRendererUnderline(u16r rect, u32 color);
-        void _d2dCellFlagRendererUnderlineDotted(u16r rect, u32 color);
-        void _d2dCellFlagRendererUnderlineDouble(u16r rect, u32 color);
-        void _d2dCellFlagRendererStrikethrough(u16r rect, u32 color);
-
-        static constexpr bool debugForceD2DMode = false;
-        static constexpr bool debugGlyphGenerationPerformance = false;
-        static constexpr bool debugTextParsingPerformance = false || debugGlyphGenerationPerformance;
-        static constexpr bool debugGeneralPerformance = false || debugTextParsingPerformance;
+        ATLAS_ATTR_COLD void _recreateAdapter();
+        ATLAS_ATTR_COLD void _recreateBackend();
+        ATLAS_ATTR_COLD void _handleSwapChainUpdate();
+        void _createSwapChain();
+        void _resizeBuffers();
+        void _updateMatrixTransform();
+        void _waitUntilCanRender() noexcept;
+        void _present();
 
         static constexpr u16 u16min = 0x0000;
         static constexpr u16 u16max = 0xffff;
         static constexpr i16 i16min = -0x8000;
         static constexpr i16 i16max = 0x7fff;
         static constexpr u16r invalidatedAreaNone = { u16max, u16max, u16min, u16min };
-        static constexpr u16x2 invalidatedRowsNone{ u16max, u16min };
-        static constexpr u16x2 invalidatedRowsAll{ u16min, u16max };
-
-        struct StaticResources
-        {
-            wil::com_ptr<ID2D1Factory> d2dFactory;
-            wil::com_ptr<IDWriteFactory1> dwriteFactory;
-            wil::com_ptr<IDWriteFontFallback> systemFontFallback;
-            wil::com_ptr<IDWriteTextAnalyzer1> textAnalyzer;
-            bool isWindows10OrGreater = true;
-
-#ifndef NDEBUG
-            std::filesystem::path sourceDirectory;
-            wil::unique_folder_change_reader_nothrow sourceCodeWatcher;
-            std::atomic<int64_t> sourceCodeInvalidationTime{ INT64_MAX };
-#endif
-        } _sr;
-
-        struct Resources
-        {
-            // DXGI resources
-            wil::com_ptr<IDXGIFactory1> dxgiFactory;
-
-            // D3D resources
-            wil::com_ptr<ID3D11Device> device;
-            wil::com_ptr<ID3D11DeviceContext1> deviceContext;
-            wil::com_ptr<IDXGISwapChain1> swapChain;
-            wil::unique_handle frameLatencyWaitableObject;
-            wil::com_ptr<ID3D11RenderTargetView> renderTargetView;
-            wil::com_ptr<ID3D11VertexShader> vertexShader;
-            wil::com_ptr<ID3D11PixelShader> pixelShader;
-            wil::com_ptr<ID3D11Buffer> constantBuffer;
-            wil::com_ptr<ID3D11Buffer> cellBuffer;
-            wil::com_ptr<ID3D11ShaderResourceView> cellView;
-            wil::com_ptr<ID3D11Texture2D> customOffscreenTexture;
-            wil::com_ptr<ID3D11ShaderResourceView> customOffscreenTextureView;
-            wil::com_ptr<ID3D11RenderTargetView> customOffscreenTextureTargetView;
-            wil::com_ptr<ID3D11VertexShader> customVertexShader;
-            wil::com_ptr<ID3D11PixelShader> customPixelShader;
-            wil::com_ptr<ID3D11Buffer> customShaderConstantBuffer;
-            wil::com_ptr<ID3D11SamplerState> customShaderSamplerState;
-            std::chrono::steady_clock::time_point customShaderStartTime;
-
-            // D2D resources
-            wil::com_ptr<ID3D11Texture2D> atlasBuffer;
-            wil::com_ptr<ID3D11ShaderResourceView> atlasView;
-            wil::com_ptr<ID2D1DeviceContext> d2dRenderTarget;
-            wil::com_ptr<ID2D1SolidColorBrush> brush;
-            wil::com_ptr<IDWriteFontFace> fontFaces[4];
-            wil::com_ptr<IDWriteTextFormat> textFormats[2][2];
-            Buffer<DWRITE_FONT_AXIS_VALUE> textFormatAxes[2][2];
-            wil::com_ptr<IDWriteTypography> typography;
-            wil::com_ptr<ID2D1StrokeStyle> dottedStrokeStyle;
+        static constexpr range<u16> invalidatedRowsNone{ u16max, u16min };
+        static constexpr range<u16> invalidatedRowsAll{ u16min, u16max };
 
-            Buffer<Cell, 32> cells; // invalidated by ApiInvalidations::Size
-            Buffer<TileHashMap::iterator> cellGlyphMapping; // invalidated by ApiInvalidations::Size
-            f32x2 cellSizeDIP; // invalidated by ApiInvalidations::Font, caches _api.cellSize but in DIP
-            u16x2 cellCount; // invalidated by ApiInvalidations::Font|Size, caches _api.cellCount
-            u16 dpi = USER_DEFAULT_SCREEN_DPI; // invalidated by ApiInvalidations::Font, caches _api.dpi
-            FontMetrics fontMetrics; // invalidated by ApiInvalidations::Font, cached _api.fontMetrics
-            f32 dipPerPixel = 1.0f; // invalidated by ApiInvalidations::Font, caches USER_DEFAULT_SCREEN_DPI / _api.dpi
-            f32 pixelPerDIP = 1.0f; // invalidated by ApiInvalidations::Font, caches _api.dpi / USER_DEFAULT_SCREEN_DPI
-            u16x2 atlasSizeInPixel; // invalidated by ApiInvalidations::Font
-            TileHashMap glyphs;
-            TileAllocator tileAllocator;
-            std::vector<TileHashMap::iterator> glyphQueue;
-
-            f32 gamma = 0;
-            f32 cleartypeEnhancedContrast = 0;
-            f32 grayscaleEnhancedContrast = 0;
-            u32 backgroundColor = 0xff000000;
-            u32 selectionColor = 0x7fffffff;
-            u32 brushColor = 0xffffffff;
-
-            CachedCursorOptions cursorOptions;
-            RenderInvalidations invalidations = RenderInvalidations::None;
-
-            til::rect dirtyRect;
-            i16 scrollOffset = 0;
-            bool d2dMode = false;
-            bool waitForPresentation = false;
-            bool requiresContinuousRedraw = false;
-
-#ifndef NDEBUG
-            // See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
-            // > For every frame it renders, the app should wait on this handle before starting any rendering operations.
-            // > Note that this requirement includes the first frame the app renders with the swap chain.
-            bool frameLatencyWaitableObjectUsed = false;
-#endif
-        } _r;
+        std::unique_ptr<IBackend> _b;
+        RenderingPayload _p;
 
         struct ApiState
         {
+            GenerationalSettings s = DirtyGenerationalSettings();
+
             // This structure is loosely sorted in chunks from "very often accessed together"
             // to seldom accessed and/or usually not together.
 
+            bool invalidatedTitle = false;
+            // These two are redundant with TargetSettings/MiscellaneousSettings, but that's because _resolveTransparencySettings()
+            // turns the given settings into potentially different actual settings (which are then written into the Settings).
+            bool enableTransparentBackground = false;
+            AntialiasingMode antialiasingMode = DefaultAntialiasingMode;
+
             std::vector<wchar_t> bufferLine;
             std::vector<u16> bufferLineColumn;
-            Buffer<BufferLineMetadata> bufferLineMetadata;
+
+            std::array<Buffer<DWRITE_FONT_AXIS_VALUE>, 4> textFormatAxes;
             std::vector<TextAnalysisSinkResult> analysisResults;
             Buffer<u16> clusterMap;
             Buffer<DWRITE_SHAPING_TEXT_PROPERTIES> textProps;
@@ -1090,49 +139,33 @@ namespace Microsoft::Console::Render
             Buffer<DWRITE_SHAPING_GLYPH_PROPERTIES> glyphProps;
             Buffer<f32> glyphAdvances;
             Buffer<DWRITE_GLYPH_OFFSET> glyphOffsets;
-            std::vector<DWRITE_FONT_FEATURE> fontFeatures; // changes are flagged as ApiInvalidations::Font|Size
-            std::vector<DWRITE_FONT_AXIS_VALUE> fontAxisValues; // changes are flagged as ApiInvalidations::Font|Size
-            FontMetrics fontMetrics; // changes are flagged as ApiInvalidations::Font|Size
 
-            u16x2 cellCount; // caches `sizeInPixel / cellSize`
-            u16x2 sizeInPixel; // changes are flagged as ApiInvalidations::Size
+            wil::com_ptr<IDWriteFontFace2> replacementCharacterFontFace;
+            u16 replacementCharacterGlyphIndex = 0;
+            bool replacementCharacterLookedUp = false;
 
+            // PrepareLineTransform()
+            LineRendition lineRendition = LineRendition::SingleWidth;
             // UpdateDrawingBrushes()
-            u32 backgroundOpaqueMixin = 0xff000000; // changes are flagged as ApiInvalidations::SwapChain
-            u32x2 currentColor;
-            AtlasKeyAttributes attributes{};
-            u16x2 lastPaintBufferLineCoord;
-            CellFlags flags = CellFlags::None;
-            // SetSelectionBackground()
-            u32 selectionColor = 0x7fffffff;
+            u32 backgroundOpaqueMixin = 0xff000000;
+            u32 currentBackground = 0;
+            u32 currentForeground = 0;
+            FontRelevantAttributes attributes = FontRelevantAttributes::None;
+            u16x2 lastPaintBufferLineCoord{};
             // UpdateHyperlinkHoveredId()
             u16 hyperlinkHoveredId = 0;
-            bool bufferLineWasHyperlinked = false;
 
             // dirtyRect is a computed value based on invalidatedRows.
             til::rect dirtyRect;
             // These "invalidation" fields are reset in EndPaint()
             u16r invalidatedCursorArea = invalidatedAreaNone;
-            u16x2 invalidatedRows = invalidatedRowsNone; // x is treated as "top" and y as "bottom"
+            range<u16> invalidatedRows = invalidatedRowsNone; // x is treated as "top" and y as "bottom"
             i16 scrollOffset = 0;
-
-            std::function<void(HRESULT)> warningCallback;
-            std::function<void(HANDLE)> swapChainChangedCallback;
-            wil::unique_handle swapChainHandle;
-            HWND hwnd = nullptr;
-            u16 dpi = USER_DEFAULT_SCREEN_DPI; // changes are flagged as ApiInvalidations::Font|Size
-            u8 antialiasingMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; // changes are flagged as ApiInvalidations::Font
-            u8 realizedAntialiasingMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; // caches antialiasingMode, depends on antialiasingMode and backgroundOpaqueMixin, see _resolveTransparencySettings
-            bool enableTransparentBackground = false;
-
-            std::wstring customPixelShaderPath; // changes are flagged as ApiInvalidations::Device
-            bool useRetroTerminalEffect = false; // changes are flagged as ApiInvalidations::Device
-            bool useSoftwareRendering = false; // changes are flagged as ApiInvalidations::Device
-
-            ApiInvalidations invalidations = ApiInvalidations::Device;
         } _api;
-
-#undef ATLAS_POD_OPS
-#undef ATLAS_FLAG_OPS
     };
 }
+
+namespace Microsoft::Console::Render
+{
+    using AtlasEngine = Atlas::AtlasEngine;
+}
diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp
index a8bc092c0b8..7b4fc1885f4 100644
--- a/src/renderer/atlas/AtlasEngine.r.cpp
+++ b/src/renderer/atlas/AtlasEngine.r.cpp
@@ -4,7 +4,8 @@
 #include "pch.h"
 #include "AtlasEngine.h"
 
-#include "dwrite.h"
+#include "BackendD2D.h"
+#include "BackendD3D.h"
 
 // #### NOTE ####
 // If you see any code in here that contains "_api." you might be seeing a race condition.
@@ -22,39 +23,7 @@
 #pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
 #pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
 
-// https://en.wikipedia.org/wiki/Inversion_list
-template<size_t N>
-constexpr bool isInInversionList(const std::array<wchar_t, N>& ranges, wchar_t needle)
-{
-    const auto beg = ranges.begin();
-    const auto end = ranges.end();
-    decltype(ranges.begin()) it;
-
-    // Linear search is faster than binary search for short inputs.
-    if constexpr (N < 16)
-    {
-        it = std::find_if(beg, end, [=](wchar_t v) { return needle < v; });
-    }
-    else
-    {
-        it = std::upper_bound(beg, end, needle);
-    }
-
-    const auto idx = it - beg;
-    return (idx & 1) != 0;
-}
-
-template<typename T = D2D1_COLOR_F>
-constexpr T colorFromU32(uint32_t rgba)
-{
-    const auto r = static_cast<float>((rgba >> 0) & 0xff) / 255.0f;
-    const auto g = static_cast<float>((rgba >> 8) & 0xff) / 255.0f;
-    const auto b = static_cast<float>((rgba >> 16) & 0xff) / 255.0f;
-    const auto a = static_cast<float>((rgba >> 24) & 0xff) / 255.0f;
-    return { r, g, b, a };
-}
-
-using namespace Microsoft::Console::Render;
+using namespace Microsoft::Console::Render::Atlas;
 
 #pragma region IRenderEngine
 
@@ -63,1044 +32,395 @@ using namespace Microsoft::Console::Render;
 [[nodiscard]] HRESULT AtlasEngine::Present() noexcept
 try
 {
-    const til::rect fullRect{ 0, 0, _r.cellCount.x, _r.cellCount.y };
-
-    // A change in the selection or background color (etc.) forces a full redraw.
-    if (WI_IsFlagSet(_r.invalidations, RenderInvalidations::ConstBuffer) || _r.customPixelShader)
-    {
-        _r.dirtyRect = fullRect;
-    }
-
-    if (!_r.dirtyRect)
+    if (!_p.dirtyRectInPx)
     {
         return S_OK;
     }
 
-    // See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
-    // > For every frame it renders, the app should wait on this handle before starting any rendering operations.
-    // > Note that this requirement includes the first frame the app renders with the swap chain.
-    assert(debugGeneralPerformance || _r.frameLatencyWaitableObjectUsed);
-
-    if (_r.d2dMode) [[unlikely]]
+    if (!_p.dxgi.adapter || !_p.dxgi.factory->IsCurrent())
     {
-        _d2dPresent();
+        _recreateAdapter();
     }
-    else
-    {
-        _adjustAtlasSize();
-        _processGlyphQueue();
-
-        // The values the constant buffer depends on are potentially updated after BeginPaint().
-        if (WI_IsFlagSet(_r.invalidations, RenderInvalidations::ConstBuffer))
-        {
-            _updateConstantBuffer();
-            WI_ClearFlag(_r.invalidations, RenderInvalidations::ConstBuffer);
-        }
 
-        {
-#pragma warning(suppress : 26494) // Variable 'mapped' is uninitialized. Always initialize an object (type.5).
-            D3D11_MAPPED_SUBRESOURCE mapped;
-            THROW_IF_FAILED(_r.deviceContext->Map(_r.cellBuffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped));
-            assert(mapped.RowPitch >= _r.cells.size() * sizeof(Cell));
-            memcpy(mapped.pData, _r.cells.data(), _r.cells.size() * sizeof(Cell));
-            _r.deviceContext->Unmap(_r.cellBuffer.get(), 0);
-        }
-
-        if (_r.customPixelShader) [[unlikely]]
-        {
-            _renderWithCustomShader();
-        }
-        else
-        {
-            _r.deviceContext->OMSetRenderTargets(1, _r.renderTargetView.addressof(), nullptr);
-            _r.deviceContext->Draw(3, 0);
-        }
+    if (!_b)
+    {
+        _recreateBackend();
     }
 
-    // See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
-    // > For every frame it renders, the app should wait on this handle before starting any rendering operations.
-    // > Note that this requirement includes the first frame the app renders with the swap chain.
-    assert(debugGeneralPerformance || _r.frameLatencyWaitableObjectUsed);
-
-    if (_r.dirtyRect != fullRect)
+    if (_p.swapChain.generation != _p.s.generation())
     {
-        auto dirtyRectInPx = _r.dirtyRect;
-        dirtyRectInPx.left *= _r.fontMetrics.cellSize.x;
-        dirtyRectInPx.top *= _r.fontMetrics.cellSize.y;
-        dirtyRectInPx.right *= _r.fontMetrics.cellSize.x;
-        dirtyRectInPx.bottom *= _r.fontMetrics.cellSize.y;
-
-        RECT scrollRect{};
-        POINT scrollOffset{};
-        DXGI_PRESENT_PARAMETERS params{
-            .DirtyRectsCount = 1,
-            .pDirtyRects = dirtyRectInPx.as_win32_rect(),
-        };
-
-        if (_r.scrollOffset)
-        {
-            scrollRect = {
-                0,
-                std::max<til::CoordType>(0, _r.scrollOffset),
-                _r.cellCount.x,
-                _r.cellCount.y + std::min<til::CoordType>(0, _r.scrollOffset),
-            };
-            scrollOffset = {
-                0,
-                _r.scrollOffset,
-            };
-
-            scrollRect.top *= _r.fontMetrics.cellSize.y;
-            scrollRect.right *= _r.fontMetrics.cellSize.x;
-            scrollRect.bottom *= _r.fontMetrics.cellSize.y;
-
-            scrollOffset.y *= _r.fontMetrics.cellSize.y;
+        _handleSwapChainUpdate();
+    }
 
-            params.pScrollRect = &scrollRect;
-            params.pScrollOffset = &scrollOffset;
-        }
+    _b->Render(_p);
+    _present();
+    return S_OK;
+}
+catch (const wil::ResultException& exception)
+{
+    const auto hr = exception.GetErrorCode();
 
-        THROW_IF_FAILED(_r.swapChain->Present1(1, 0, &params));
-    }
-    else
+    if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
     {
-        THROW_IF_FAILED(_r.swapChain->Present(1, 0));
+        _p.dxgi = {};
+        return E_PENDING;
     }
 
-    _r.waitForPresentation = true;
-
-    if (!_r.dxgiFactory->IsCurrent())
+    if (_p.warningCallback)
     {
-        WI_SetFlag(_api.invalidations, ApiInvalidations::Device);
+        try
+        {
+            _p.warningCallback(hr);
+        }
+        CATCH_LOG()
     }
 
-    return S_OK;
-}
-catch (const wil::ResultException& exception)
-{
-    // TODO: this writes to _api.
-    return _handleException(exception);
+    _b.reset();
+    return hr;
 }
 CATCH_RETURN()
 
 [[nodiscard]] bool AtlasEngine::RequiresContinuousRedraw() noexcept
 {
-    return debugGeneralPerformance || _r.requiresContinuousRedraw;
+    return ATLAS_DEBUG_CONTINUOUS_REDRAW || (_b && _b->RequiresContinuousRedraw());
 }
 
 void AtlasEngine::WaitUntilCanRender() noexcept
 {
-    // IDXGISwapChain2::GetFrameLatencyWaitableObject returns an auto-reset event.
-    // Once we've waited on the event, waiting on it again will block until the timeout elapses.
-    // _r.waitForPresentation guards against this.
-    if (!debugGeneralPerformance && std::exchange(_r.waitForPresentation, false))
+    if constexpr (ATLAS_DEBUG_RENDER_DELAY)
     {
-        WaitForSingleObjectEx(_r.frameLatencyWaitableObject.get(), 100, true);
-#ifndef NDEBUG
-        _r.frameLatencyWaitableObjectUsed = true;
-#endif
+        Sleep(ATLAS_DEBUG_RENDER_DELAY);
     }
+    _waitUntilCanRender();
 }
 
 #pragma endregion
 
-void AtlasEngine::_renderWithCustomShader() const
+void AtlasEngine::_recreateAdapter()
 {
-    // Render with our main shader just like Present().
+#ifndef NDEBUG
+    if (IsDebuggerPresent())
     {
-        // OM: Output Merger
-        _r.deviceContext->OMSetRenderTargets(1, _r.customOffscreenTextureTargetView.addressof(), nullptr);
-        _r.deviceContext->Draw(3, 0);
+        // DXGIGetDebugInterface1 returns E_NOINTERFACE on systems without the Windows SDK installed.
+        if (wil::com_ptr<IDXGIInfoQueue> infoQueue; SUCCEEDED_LOG(DXGIGetDebugInterface1(0, IID_PPV_ARGS(infoQueue.addressof()))))
+        {
+            // I didn't want to link with dxguid.lib just for getting DXGI_DEBUG_ALL. This GUID is publicly documented.
+            static constexpr GUID dxgiDebugAll{ 0xe48ae283, 0xda80, 0x490b, { 0x87, 0xe6, 0x43, 0xe9, 0xa9, 0xcf, 0xda, 0x8 } };
+            for (const auto severity : { DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_WARNING, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_INFO })
+            {
+                LOG_IF_FAILED(infoQueue->SetBreakOnSeverity(dxgiDebugAll, severity, true));
+            }
+        }
     }
+#endif
 
-    // Update the custom shader's constant buffer.
-    {
-        CustomConstBuffer data;
-        data.time = std::chrono::duration<float>(std::chrono::steady_clock::now() - _r.customShaderStartTime).count();
-        data.scale = _r.pixelPerDIP;
-        data.resolution.x = static_cast<float>(_r.cellCount.x * _r.fontMetrics.cellSize.x);
-        data.resolution.y = static_cast<float>(_r.cellCount.y * _r.fontMetrics.cellSize.y);
-        data.background = colorFromU32<f32x4>(_r.backgroundColor);
+#ifndef NDEBUG
+    static constexpr UINT flags = DXGI_CREATE_FACTORY_DEBUG;
+#else
+    static constexpr UINT flags = 0;
+#endif
 
-#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.customShaderConstantBuffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped));
-        assert(mapped.RowPitch >= sizeof(data));
-        memcpy(mapped.pData, &data, sizeof(data));
-        _r.deviceContext->Unmap(_r.customShaderConstantBuffer.get(), 0);
-    }
+    // IID_PPV_ARGS doesn't work here for some reason.
+    THROW_IF_FAILED(CreateDXGIFactory2(flags, __uuidof(_p.dxgi.factory), _p.dxgi.factory.put_void()));
 
-    // Render with the custom shader.
-    {
-        // OM: Output Merger
-        // customOffscreenTextureView was just rendered to via customOffscreenTextureTargetView and is
-        // set as the output target. Before we can use it as an input we have to remove it as an output.
-        _r.deviceContext->OMSetRenderTargets(1, _r.renderTargetView.addressof(), nullptr);
+    wil::com_ptr<IDXGIAdapter1> adapter;
+    DXGI_ADAPTER_DESC1 desc{};
 
-        // VS: Vertex Shader
-        _r.deviceContext->VSSetShader(_r.customVertexShader.get(), nullptr, 0);
+    {
+        const auto useSoftwareRendering = _p.s->target->useSoftwareRendering;
+        UINT index = 0;
 
-        // PS: Pixel Shader
-        _r.deviceContext->PSSetShader(_r.customPixelShader.get(), nullptr, 0);
-        _r.deviceContext->PSSetConstantBuffers(0, 1, _r.customShaderConstantBuffer.addressof());
-        _r.deviceContext->PSSetShaderResources(0, 1, _r.customOffscreenTextureView.addressof());
-        _r.deviceContext->PSSetSamplers(0, 1, _r.customShaderSamplerState.addressof());
+        do
+        {
+            THROW_IF_FAILED(_p.dxgi.factory->EnumAdapters1(index++, adapter.put()));
+            THROW_IF_FAILED(adapter->GetDesc1(&desc));
 
-        _r.deviceContext->Draw(4, 0);
+            // If useSoftwareRendering is false we exit during the first iteration. Using the default adapter (index 0)
+            // is the right thing to do under most circumstances, unless you _really_ want to get your hands dirty.
+            // The alternative is to track the window rectangle in respect to all IDXGIOutputs and select the right
+            // IDXGIAdapter, while also considering the "graphics preference" override in the windows settings app, etc.
+            //
+            // If useSoftwareRendering is true we search until we find the first WARP adapter (usually the last adapter).
+        } while (useSoftwareRendering && WI_IsFlagClear(desc.Flags, DXGI_ADAPTER_FLAG_SOFTWARE));
     }
 
-    // For the next frame we need to restore our context state.
+    if (memcmp(&_p.dxgi.adapterLuid, &desc.AdapterLuid, sizeof(LUID)) != 0)
     {
-        // VS: Vertex Shader
-        _r.deviceContext->VSSetShader(_r.vertexShader.get(), nullptr, 0);
-
-        // PS: Pixel Shader
-        _r.deviceContext->PSSetShader(_r.pixelShader.get(), nullptr, 0);
-        _r.deviceContext->PSSetConstantBuffers(0, 1, _r.constantBuffer.addressof());
-        const std::array resources{ _r.cellView.get(), _r.atlasView.get() };
-        _r.deviceContext->PSSetShaderResources(0, gsl::narrow_cast<UINT>(resources.size()), resources.data());
-        _r.deviceContext->PSSetSamplers(0, 0, nullptr);
+        _p.dxgi.adapter = std::move(adapter);
+        _p.dxgi.adapterLuid = desc.AdapterLuid;
+        _p.dxgi.adapterFlags = desc.Flags;
+        _b.reset();
     }
 }
 
-void AtlasEngine::_setShaderResources() const
+void AtlasEngine::_recreateBackend()
 {
-    // IA: Input Assembler
-    // 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_TRIANGLESTRIP);
-
-    // VS: Vertex Shader
-    _r.deviceContext->VSSetShader(_r.vertexShader.get(), nullptr, 0);
-
-    // PS: Pixel Shader
-    _r.deviceContext->PSSetShader(_r.pixelShader.get(), nullptr, 0);
-    _r.deviceContext->PSSetConstantBuffers(0, 1, _r.constantBuffer.addressof());
-    const std::array resources{ _r.cellView.get(), _r.atlasView.get() };
-    _r.deviceContext->PSSetShaderResources(0, gsl::narrow_cast<UINT>(resources.size()), resources.data());
-}
-
-void AtlasEngine::_updateConstantBuffer() const noexcept
-{
-    const auto useClearType = _api.realizedAntialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
+    auto d2dMode = ATLAS_DEBUG_FORCE_D2D_MODE;
+    auto deviceFlags =
+        D3D11_CREATE_DEVICE_SINGLETHREADED
+#ifndef NDEBUG
+        | D3D11_CREATE_DEVICE_DEBUG
+#endif
+        // This flag prevents the driver from creating a large thread pool for things like shader computations
+        // that would be advantageous for games. For us this has only a minimal performance benefit,
+        // but comes with a large memory usage overhead. At the time of writing the Nvidia
+        // driver launches $cpu_thread_count more worker threads without this flag.
+        | D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS
+        // Direct2D support.
+        | D3D11_CREATE_DEVICE_BGRA_SUPPORT;
+
+    if (WI_IsFlagSet(_p.dxgi.adapterFlags, DXGI_ADAPTER_FLAG_SOFTWARE))
+    {
+        WI_ClearFlag(deviceFlags, D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS);
+        d2dMode = true;
+    }
+
+    wil::com_ptr<ID3D11Device> device0;
+    wil::com_ptr<ID3D11DeviceContext> deviceContext0;
+    D3D_FEATURE_LEVEL featureLevel{};
+
+    static constexpr std::array featureLevels{
+        D3D_FEATURE_LEVEL_11_1,
+        D3D_FEATURE_LEVEL_11_0,
+        D3D_FEATURE_LEVEL_10_1,
+        D3D_FEATURE_LEVEL_10_0,
+        D3D_FEATURE_LEVEL_9_3,
+        D3D_FEATURE_LEVEL_9_2,
+        D3D_FEATURE_LEVEL_9_1,
+    };
 
-    ConstBuffer data;
-    data.viewport.x = 0;
-    data.viewport.y = 0;
-    data.viewport.z = static_cast<float>(_r.cellCount.x * _r.fontMetrics.cellSize.x);
-    data.viewport.w = static_cast<float>(_r.cellCount.y * _r.fontMetrics.cellSize.y);
-    DWrite_GetGammaRatios(_r.gamma, data.gammaRatios);
-    data.enhancedContrast = useClearType ? _r.cleartypeEnhancedContrast : _r.grayscaleEnhancedContrast;
-    data.cellCountX = _r.cellCount.x;
-    data.cellSize.x = _r.fontMetrics.cellSize.x;
-    data.cellSize.y = _r.fontMetrics.cellSize.y;
-    data.underlinePos = _r.fontMetrics.underlinePos;
-    data.underlineWidth = _r.fontMetrics.underlineWidth;
-    data.strikethroughPos = _r.fontMetrics.strikethroughPos;
-    data.strikethroughWidth = _r.fontMetrics.strikethroughWidth;
-    data.doubleUnderlinePos.x = _r.fontMetrics.doubleUnderlinePos.x;
-    data.doubleUnderlinePos.y = _r.fontMetrics.doubleUnderlinePos.y;
-    data.thinLineWidth = _r.fontMetrics.thinLineWidth;
-    data.backgroundColor = _r.backgroundColor;
-    data.cursorColor = _r.cursorOptions.cursorColor;
-    data.selectionColor = _r.selectionColor;
-    data.useClearType = useClearType;
-#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6).
-    _r.deviceContext->UpdateSubresource(_r.constantBuffer.get(), 0, nullptr, &data, 0, 0);
-}
+    THROW_IF_FAILED(D3D11CreateDevice(
+        /* pAdapter */ _p.dxgi.adapter.get(),
+        /* DriverType */ D3D_DRIVER_TYPE_UNKNOWN,
+        /* Software */ nullptr,
+        /* Flags */ deviceFlags,
+        /* pFeatureLevels */ featureLevels.data(),
+        /* FeatureLevels */ gsl::narrow_cast<UINT>(featureLevels.size()),
+        /* SDKVersion */ D3D11_SDK_VERSION,
+        /* ppDevice */ device0.put(),
+        /* pFeatureLevel */ &featureLevel,
+        /* ppImmediateContext */ deviceContext0.put()));
+
+    auto device = device0.query<ID3D11Device2>();
+    auto deviceContext = deviceContext0.query<ID3D11DeviceContext2>();
 
-void AtlasEngine::_adjustAtlasSize()
-{
-    // Only grow the atlas texture if our tileAllocator needs it to be larger.
-    // We have no way of shrinking our tileAllocator at the moment,
-    // so technically a `requiredSize != _r.atlasSizeInPixel`
-    // comparison would be sufficient, but better safe than sorry.
-    const auto requiredSize = _r.tileAllocator.size();
-    if (requiredSize.y <= _r.atlasSizeInPixel.y && requiredSize.x <= _r.atlasSizeInPixel.x)
+#ifndef NDEBUG
+    if (IsDebuggerPresent())
     {
-        return;
+        if (const auto d3dInfoQueue = device.try_query<ID3D11InfoQueue>())
+        {
+            for (const auto severity : { D3D11_MESSAGE_SEVERITY_CORRUPTION, D3D11_MESSAGE_SEVERITY_ERROR, D3D11_MESSAGE_SEVERITY_WARNING, D3D11_MESSAGE_SEVERITY_INFO })
+            {
+                LOG_IF_FAILED(d3dInfoQueue->SetBreakOnSeverity(severity, true));
+            }
+        }
     }
+#endif
 
-    wil::com_ptr<ID3D11Texture2D> atlasBuffer;
-    wil::com_ptr<ID3D11ShaderResourceView> atlasView;
+    if (featureLevel < D3D_FEATURE_LEVEL_10_0)
     {
-        D3D11_TEXTURE2D_DESC desc{};
-        desc.Width = requiredSize.x;
-        desc.Height = requiredSize.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, atlasBuffer.addressof()));
-        THROW_IF_FAILED(_r.device->CreateShaderResourceView(atlasBuffer.get(), nullptr, atlasView.addressof()));
+        d2dMode = true;
     }
-
-    // If a _r.atlasBuffer already existed, we can copy its glyphs
-    // over to the new texture without re-rendering everything.
-    const auto copyFromExisting = _r.atlasSizeInPixel != u16x2{};
-    if (copyFromExisting)
+    else if (featureLevel < D3D_FEATURE_LEVEL_11_0)
     {
-        D3D11_BOX box;
-        box.left = 0;
-        box.top = 0;
-        box.front = 0;
-        box.right = _r.atlasSizeInPixel.x;
-        box.bottom = _r.atlasSizeInPixel.y;
-        box.back = 1;
-        _r.deviceContext->CopySubresourceRegion1(atlasBuffer.get(), 0, 0, 0, 0, _r.atlasBuffer.get(), 0, &box, D3D11_COPY_NO_OVERWRITE);
+        D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS options{};
+        // I'm assuming if `CheckFeatureSupport` fails, it'll leave `options` untouched which will result in `d2dMode |= true`.
+        std::ignore = device->CheckFeatureSupport(D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS, &options, sizeof(options));
+        d2dMode |= !options.ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x;
     }
 
-    {
-        const auto surface = atlasBuffer.query<IDXGISurface>();
-
-        wil::com_ptr<IDWriteRenderingParams1> renderingParams;
-        DWrite_GetRenderParams(_sr.dwriteFactory.get(), &_r.gamma, &_r.cleartypeEnhancedContrast, &_r.grayscaleEnhancedContrast, renderingParams.addressof());
-
-        D2D1_RENDER_TARGET_PROPERTIES props{};
-        props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
-        props.pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED };
-        props.dpiX = static_cast<float>(_r.dpi);
-        props.dpiY = static_cast<float>(_r.dpi);
-        wil::com_ptr<ID2D1RenderTarget> renderTarget;
-        THROW_IF_FAILED(_sr.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, renderTarget.addressof()));
-        _r.d2dRenderTarget = renderTarget.query<ID2D1DeviceContext>();
+    _p.swapChain = {};
+    _p.device = std::move(device);
+    _p.deviceContext = std::move(deviceContext);
 
-        // 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);
-        // In case _api.realizedAntialiasingMode is D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE we'll
-        // continuously adjust it in AtlasEngine::_drawGlyph. See _drawGlyph.
-        _r.d2dRenderTarget->SetTextAntialiasMode(static_cast<D2D1_TEXT_ANTIALIAS_MODE>(_api.realizedAntialiasingMode));
-        // Ensure that D2D uses the exact same gamma as our shader uses.
-        _r.d2dRenderTarget->SetTextRenderingParams(renderingParams.get());
+    if (d2dMode)
+    {
+        _b = std::make_unique<BackendD2D>();
     }
+    else
     {
-        static constexpr D2D1_COLOR_F color{ 1, 1, 1, 1 };
-        THROW_IF_FAILED(_r.d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, _r.brush.put()));
-        _r.brushColor = 0xffffffff;
+        _b = std::make_unique<BackendD3D>(_p);
     }
 
-    _r.atlasSizeInPixel = requiredSize;
-    _r.atlasBuffer = std::move(atlasBuffer);
-    _r.atlasView = std::move(atlasView);
-    _setShaderResources();
-
-    WI_SetAllFlags(_r.invalidations, RenderInvalidations::ConstBuffer);
-    WI_SetFlagIf(_r.invalidations, RenderInvalidations::Cursor, !copyFromExisting);
+    // !!! NOTE !!!
+    // Normally the viewport is indirectly marked as dirty by `AtlasEngine::_handleSettingsUpdate()` whenever
+    // the settings change, but the `!_p.dxgi.factory->IsCurrent()` check is not part of the settings change
+    // flow and so we have to manually recreate how AtlasEngine.cpp marks viewports as dirty here.
+    // This ensures that the backends redraw their entire viewports whenever a new swap chain is created.
+    _p.MarkAllAsDirty();
 }
 
-void AtlasEngine::_processGlyphQueue()
+void AtlasEngine::_handleSwapChainUpdate()
 {
-    if (_r.glyphQueue.empty() && WI_IsFlagClear(_r.invalidations, RenderInvalidations::Cursor))
+    if (_p.swapChain.targetGeneration != _p.s->target.generation())
     {
-        return;
+        if (_p.swapChain.swapChain)
+        {
+            _b->ReleaseResources();
+            _p.deviceContext->ClearState();
+            _p.deviceContext->Flush();
+        }
+        _createSwapChain();
     }
-
-    _r.d2dRenderTarget->BeginDraw();
-
-    if (WI_IsFlagSet(_r.invalidations, RenderInvalidations::Cursor))
+    else if (_p.swapChain.targetSize != _p.s->targetSize)
     {
-        _drawCursor({ 0, 0, 1, 1 }, 0xffffffff, true);
-        WI_ClearFlag(_r.invalidations, RenderInvalidations::Cursor);
+        _b->ReleaseResources();
+        _p.deviceContext->ClearState();
+        _resizeBuffers();
     }
 
-    for (const auto& it : _r.glyphQueue)
+    if (_p.swapChain.fontGeneration != _p.s->font.generation())
     {
-        _drawGlyph(it);
+        _updateMatrixTransform();
     }
-    _r.glyphQueue.clear();
 
-    THROW_IF_FAILED(_r.d2dRenderTarget->EndDraw());
+    _p.swapChain.generation = _p.s.generation();
 }
 
-void AtlasEngine::_drawGlyph(const TileHashMap::iterator& it) const
-{
-    const auto key = it->first.data();
-    const auto value = it->second.data();
-    const auto coords = &value->coords[0];
-    const auto charsLength = key->charCount;
-    const auto cellCount = key->attributes.cellCount;
-    const auto textFormat = _getTextFormat(key->attributes.bold, key->attributes.italic);
-    const auto coloredGlyph = WI_IsFlagSet(value->flags, CellFlags::ColoredGlyph);
-    const auto cachedLayout = _getCachedGlyphLayout(&key->chars[0], charsLength, cellCount, textFormat, coloredGlyph);
-
-    // Colored glyphs cannot be drawn in linear gamma.
-    // That's why we're simply alpha-blending them in the shader.
-    // In order for this to work correctly we have to prevent them from being drawn
-    // with ClearType, because we would then lack the alpha channel for the glyphs.
-    if (_api.realizedAntialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE)
-    {
-        _r.d2dRenderTarget->SetTextAntialiasMode(coloredGlyph ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
-    }
-
-    for (u16 i = 0; i < cellCount; ++i)
-    {
-        const auto coord = coords[i];
-
-        D2D1_RECT_F rect;
-        rect.left = static_cast<float>(coord.x) * _r.dipPerPixel;
-        rect.top = static_cast<float>(coord.y) * _r.dipPerPixel;
-        rect.right = rect.left + _r.cellSizeDIP.x;
-        rect.bottom = rect.top + _r.cellSizeDIP.y;
+static constexpr DXGI_SWAP_CHAIN_FLAG swapChainFlags = ATLAS_DEBUG_DISABLE_FRAME_LATENCY_WAITABLE_OBJECT ? DXGI_SWAP_CHAIN_FLAG{} : DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
 
-        D2D1_POINT_2F origin;
-        origin.x = rect.left - i * _r.cellSizeDIP.x;
-        origin.y = rect.top;
-
-        _r.d2dRenderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED);
-        _r.d2dRenderTarget->Clear();
-
-        cachedLayout.applyScaling(_r.d2dRenderTarget.get(), origin);
-
-        // Now that we're done using origin to calculate the center point for our transformation
-        // we can use it for its intended purpose to slightly shift the glyph around.
-        origin.x += cachedLayout.offset.x;
-        origin.y += cachedLayout.offset.y;
-        _r.d2dRenderTarget->DrawTextLayout(origin, cachedLayout.textLayout.get(), _r.brush.get(), cachedLayout.options);
-
-        cachedLayout.undoScaling(_r.d2dRenderTarget.get());
-
-        _r.d2dRenderTarget->PopAxisAlignedClip();
-    }
-}
-
-AtlasEngine::CachedGlyphLayout AtlasEngine::_getCachedGlyphLayout(const wchar_t* chars, u16 charsLength, u16 cellCount, IDWriteTextFormat* textFormat, bool coloredGlyph) const
+void AtlasEngine::_createSwapChain()
 {
-    const f32x2 layoutBox{ cellCount * _r.cellSizeDIP.x, _r.cellSizeDIP.y };
-    bool scalingRequired = false;
-    f32x2 offset{ 0, 0 };
-    f32x2 scale{ 1, 1 };
-    f32x2 scaleCenter;
-
-    // See D2DFactory::DrawText
-    wil::com_ptr<IDWriteTextLayout> textLayout;
-    THROW_IF_FAILED(_sr.dwriteFactory->CreateTextLayout(chars, charsLength, textFormat, layoutBox.x, layoutBox.y, textLayout.addressof()));
-    if (_r.typography)
-    {
-        textLayout->SetTypography(_r.typography.get(), { 0, charsLength });
-    }
-
-    // Block Element and Box Drawing characters need to be handled separately,
-    // because unlike regular ones they're supposed to fill the entire layout box.
-    //
-    // Ranges:
-    // * 0x2500-0x257F: Box Drawing
-    // * 0x2580-0x259F: Block Elements
-    // * 0xE0A0-0xE0A3,0xE0B0-0xE0C8,0xE0CA-0xE0CA,0xE0CC-0xE0D4: PowerLine
-    //   (https://github.com/ryanoasis/nerd-fonts/wiki/Glyph-Sets-and-Code-Points#powerline-symbols)
-    //
-    // The following `blockCharacters` forms a so called "inversion list".
-    static constexpr std::array blockCharacters{
-        // clang-format off
-        L'\u2500', L'\u2580',
-        L'\u2580', L'\u25A0',
-        L'\uE0A0', L'\uE0A4',
-        L'\uE0B0', L'\uE0C9',
-        L'\uE0CA', L'\uE0CB',
-        L'\uE0CC', L'\uE0D5',
-        // clang-format on
-    };
-
-    if (charsLength == 1 && isInInversionList(blockCharacters, chars[0]))
-    {
-        wil::com_ptr<IDWriteFontCollection> fontCollection;
-        THROW_IF_FAILED(textFormat->GetFontCollection(fontCollection.addressof()));
-        const auto baseWeight = textFormat->GetFontWeight();
-        const auto baseStyle = textFormat->GetFontStyle();
-
-        TextAnalysisSource analysisSource{ chars, 1 };
-        UINT32 mappedLength = 0;
-        wil::com_ptr<IDWriteFont> mappedFont;
-        FLOAT mappedScale = 0;
-        THROW_IF_FAILED(_sr.systemFontFallback->MapCharacters(
-            /* analysisSource     */ &analysisSource,
-            /* textPosition       */ 0,
-            /* textLength         */ 1,
-            /* baseFontCollection */ fontCollection.get(),
-            /* baseFamilyName     */ _r.fontMetrics.fontName.data(),
-            /* baseWeight         */ baseWeight,
-            /* baseStyle          */ baseStyle,
-            /* baseStretch        */ DWRITE_FONT_STRETCH_NORMAL,
-            /* mappedLength       */ &mappedLength,
-            /* mappedFont         */ mappedFont.addressof(),
-            /* scale              */ &mappedScale));
-
-        if (mappedFont)
-        {
-            wil::com_ptr<IDWriteFontFace> fontFace;
-            THROW_IF_FAILED(mappedFont->CreateFontFace(fontFace.addressof()));
-
-            // Don't adjust the size of block glyphs that are part of the user's chosen font.
-            if (std::ranges::find(_r.fontFaces, fontFace) == std::end(_r.fontFaces))
-            {
-                DWRITE_FONT_METRICS metrics;
-                fontFace->GetMetrics(&metrics);
-
-                static constexpr u32 codePoint = L'\u2588'; // Full Block character
-                u16 glyphIndex;
-                THROW_IF_FAILED(fontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex));
-
-                if (glyphIndex)
-                {
-                    DWRITE_GLYPH_METRICS glyphMetrics;
-                    THROW_IF_FAILED(fontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics));
-
-                    const auto fontScale = _r.fontMetrics.fontSizeInDIP / metrics.designUnitsPerEm;
-
-                    // How-to-DWRITE_OVERHANG_METRICS given a single glyph:
-                    DWRITE_OVERHANG_METRICS overhang;
-                    overhang.left = static_cast<f32>(glyphMetrics.leftSideBearing) * fontScale;
-                    overhang.top = static_cast<f32>(glyphMetrics.verticalOriginY - glyphMetrics.topSideBearing) * fontScale - _r.fontMetrics.baselineInDIP;
-                    overhang.right = static_cast<f32>(gsl::narrow_cast<INT32>(glyphMetrics.advanceWidth) - glyphMetrics.rightSideBearing) * fontScale - layoutBox.x;
-                    overhang.bottom = static_cast<f32>(gsl::narrow_cast<INT32>(glyphMetrics.advanceHeight) - glyphMetrics.verticalOriginY - glyphMetrics.bottomSideBearing) * fontScale + _r.fontMetrics.baselineInDIP - layoutBox.y;
-
-                    scalingRequired = true;
-                    // Center glyphs.
-                    offset.x = (overhang.left - overhang.right) * 0.5f;
-                    offset.y = (overhang.top - overhang.bottom) * 0.5f;
-                    // We always want box drawing glyphs to exactly match the size of a terminal cell.
-                    // But add 1px to the destination size, so that we don't end up with fractional pixels.
-                    scale.x = (layoutBox.x + _r.pixelPerDIP) / (layoutBox.x + overhang.left + overhang.right);
-                    scale.y = (layoutBox.y + _r.pixelPerDIP) / (layoutBox.y + overhang.top + overhang.bottom);
-                    // Now that the glyph is in the center of the cell thanks
-                    // to the offset, the scaleCenter is center of the cell.
-                    scaleCenter.x = layoutBox.x * 0.5f;
-                    scaleCenter.y = layoutBox.y * 0.5f;
-                }
-            }
-        }
-    }
-    else
-    {
-        DWRITE_OVERHANG_METRICS overhang;
-        THROW_IF_FAILED(textLayout->GetOverhangMetrics(&overhang));
-
-        auto actualSizeX = layoutBox.x + overhang.left + overhang.right;
-
-        // Long glyphs should be drawn with their proper design size, even if that makes them a bit blurry,
-        // because otherwise we fail to support "pseudo" block characters like the "===" ligature in Cascadia Code.
-        // If we didn't force upscale that ligatures it would seemingly shrink shorter and shorter, as its
-        // glyph advance is often slightly shorter by a fractional pixel or two compared to our terminal's cells.
-        // It's a trade off that keeps most glyphs "crisp" while retaining support for things like "===".
-        // At least I can't think of any better heuristic for this at the moment...
-        if (cellCount > 2)
-        {
-            const auto advanceScale = _r.fontMetrics.advanceScale;
-            scalingRequired = true;
-            scale = { advanceScale, advanceScale };
-            actualSizeX *= advanceScale;
-        }
-
-        // We need to offset glyphs that are simply outside of our layout box (layoutBox.x/.y)
-        // and additionally downsize glyphs that are entirely too large to fit in.
-        // The DWRITE_OVERHANG_METRICS will tell us how many DIPs the layout box is too large/small.
-        // It contains a positive number if the glyph is outside and a negative one if it's inside
-        // the layout box. For example, given a layoutBox.x/.y (and cell size) of 20/30:
-        // * "M" is the "largest" ASCII character and might be:
-        //     left:    -0.6f
-        //     right:   -0.6f
-        //     top:     -7.6f
-        //     bottom:  -7.4f
-        //   "M" doesn't fill the layout box at all!
-        //   This is because we've rounded up the Terminal's cell size to whole pixels in
-        //   _resolveFontMetrics. top/bottom margins are fairly large because we added the
-        //   chosen font's ascender, descender and line gap metrics to get our line height.
-        //   --> offsetX = 0
-        //   --> offsetY = 0
-        //   --> scale   = 1
-        // * The bar diacritic (U+0336 combining long stroke overlay)
-        //     left:    -9.0f
-        //     top:    -16.3f
-        //     right:    5.6f
-        //     bottom: -11.7f
-        //   right is positive! Our glyph is 5.6 DIPs outside of the layout box and would
-        //   appear cut off during rendering. left is negative at -9, which indicates that
-        //   we can simply shift the glyph by 5.6 DIPs to the left to fit it into our bounds.
-        //   --> offsetX = -5.6f
-        //   --> offsetY = 0
-        //   --> scale   = 1
-        // * Any wide emoji in a narrow cell (U+26A0 warning sign)
-        //     left:     6.7f
-        //     top:     -4.1f
-        //     right:    6.7f
-        //     bottom:  -3.0f
-        //   Our emoji is outside the bounds on both the left and right side and we need to shrink it.
-        //   --> offsetX = 0
-        //   --> offsetY = 0
-        //   --> scale   = layoutBox.y / (layoutBox.y + left + right)
-        //               = 0.69f
-        offset.x = std::max(0.0f, overhang.left) - std::max(0.0f, overhang.right);
-        scaleCenter.x = offset.x;
-        scaleCenter.y = _r.fontMetrics.baselineInDIP;
-
-        if ((actualSizeX - layoutBox.x) > _r.dipPerPixel)
-        {
-            scalingRequired = true;
-            offset.x = (overhang.left - overhang.right) * 0.5f;
-            scale.x = layoutBox.x / actualSizeX;
-            scale.y = scale.x;
-            scaleCenter.x = layoutBox.x * 0.5f;
-        }
-        if (overhang.top > _r.dipPerPixel || overhang.bottom > _r.dipPerPixel)
-        {
-            const auto descend = _r.cellSizeDIP.y - _r.fontMetrics.baselineInDIP;
-            const auto scaleTop = _r.fontMetrics.baselineInDIP / (_r.fontMetrics.baselineInDIP + overhang.top);
-            const auto scaleBottom = descend / (descend + overhang.bottom);
-            scalingRequired = true;
-            scale.x = std::min(scale.x, std::min(scaleTop, scaleBottom));
-            scale.y = scale.x;
-        }
-    }
-
-    auto options = D2D1_DRAW_TEXT_OPTIONS_NONE;
-    // D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT enables a bunch of internal machinery
-    // which doesn't have to run if we know we can't use it anyways in the shader.
-    WI_SetFlagIf(options, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT, coloredGlyph);
-    // !!! IMPORTANT !!!
-    // DirectWrite/2D snaps the baseline to whole pixels, which is something we technically
-    // want (it makes text look crisp), but fails in weird ways if `scalingRequired` is true.
-    // As our scaling matrix's dx/dy (center point) is based on the `origin` coordinates
-    // each cell we draw gets a unique, fractional baseline which gets rounded differently.
-    // I'm not 100% sure why that happens, since `origin` is always in full pixels...
-    // But this causes wide glyphs to draw as tiles that are potentially misaligned vertically by a pixel.
-    // The resulting text rendering looks especially bad for ligatures like "====" in Cascadia Code,
-    // where every single "=" might be blatantly misaligned vertically (same for any box drawings).
-    WI_SetFlagIf(options, D2D1_DRAW_TEXT_OPTIONS_NO_SNAP, scalingRequired);
-
-    // ClearType basically has a 3x higher horizontal resolution. To make our glyphs render the same everywhere,
-    // it's probably for the best to ensure we initially rasterize them on a whole pixel boundary.
-    // (https://en.wikipedia.org/wiki/ClearType#How_ClearType_works)
-    offset.x = roundf(offset.x * _r.pixelPerDIP) * _r.dipPerPixel;
-    // As explained below, we use D2D1_DRAW_TEXT_OPTIONS_NO_SNAP to prevent a weird issue with baseline snapping.
-    // But we do want it technically, so this re-implements baseline snapping... I think?
-    offset.y = roundf(offset.y * _r.pixelPerDIP) * _r.dipPerPixel;
-
-    return CachedGlyphLayout{
-        .textLayout = textLayout,
-        .offset = offset,
-        .scale = scale,
-        .scaleCenter = scaleCenter,
-        .options = options,
-        .scalingRequired = scalingRequired,
+    _p.swapChain.swapChain.reset();
+    _p.swapChain.frameLatencyWaitableObject.reset();
+
+    DXGI_SWAP_CHAIN_DESC1 desc{
+        .Width = _p.s->targetSize.x,
+        .Height = _p.s->targetSize.y,
+        .Format = DXGI_FORMAT_B8G8R8A8_UNORM,
+        .SampleDesc = { .Count = 1 },
+        .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
+        // Sometimes up to 2 buffers are locked, for instance during screen capture or when moving the window.
+        // 3 buffers seems to guarantee a stable framerate at display frequency at all times.
+        .BufferCount = 3,
+        .Scaling = DXGI_SCALING_NONE,
+        // DXGI_SWAP_EFFECT_FLIP_DISCARD is a mode that was created at a time were display drivers
+        // lacked support for Multiplane Overlays (MPO) and were copying buffers was expensive.
+        // This allowed DWM to quickly draw overlays (like gamebars) on top of rendered content.
+        // With faster GPU memory in general and with support for MPO in particular this isn't
+        // really an advantage anymore. Instead DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL allows for a
+        // more "intelligent" composition and display updates to occur like Panel Self Refresh
+        // (PSR) which requires dirty rectangles (Present1 API) to work correctly.
+        .SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
+        // If our background is opaque we can enable "independent" flips by setting DXGI_ALPHA_MODE_IGNORE.
+        // As our swap chain won't have to compose with DWM anymore it reduces the display latency dramatically.
+        .AlphaMode = _p.s->target->enableTransparentBackground ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE,
+        .Flags = swapChainFlags,
     };
-}
-
-void AtlasEngine::_drawCursor(u16r rect, u32 color, bool clear)
-{
-    // lineWidth is in D2D's DIPs. For instance if we have a 150-200% zoom scale we want to draw a 2px wide line.
-    // At 150% scale lineWidth thus needs to be 1.33333... because at a zoom scale of 1.5 this results in a 2px wide line.
-    const auto lineWidth = std::max(1.0f, static_cast<float>((_r.dpi + USER_DEFAULT_SCREEN_DPI / 2) / USER_DEFAULT_SCREEN_DPI * USER_DEFAULT_SCREEN_DPI) / static_cast<float>(_r.dpi));
-    const auto cursorType = static_cast<CursorType>(_r.cursorOptions.cursorType);
-
-    // `clip` is the rectangle within our texture atlas that's reserved for our cursor texture, ...
-    D2D1_RECT_F clip;
-    clip.left = static_cast<float>(rect.left) * _r.cellSizeDIP.x;
-    clip.top = static_cast<float>(rect.top) * _r.cellSizeDIP.y;
-    clip.right = static_cast<float>(rect.right) * _r.cellSizeDIP.x;
-    clip.bottom = static_cast<float>(rect.bottom) * _r.cellSizeDIP.y;
 
-    // ... whereas `rect` is just the visible (= usually white) portion of our cursor.
-    auto box = clip;
+    wil::com_ptr<IDXGISwapChain1> swapChain1;
 
-    switch (cursorType)
+    if (_p.s->target->hwnd)
     {
-    case CursorType::Legacy:
-        box.top = box.bottom - _r.cellSizeDIP.y * static_cast<float>(_r.cursorOptions.heightPercentage) / 100.0f;
-        break;
-    case CursorType::VerticalBar:
-        box.right = box.left + lineWidth;
-        break;
-    case CursorType::EmptyBox:
-    {
-        // EmptyBox is drawn as a line and unlike filled rectangles those are drawn centered on their
-        // coordinates in such a way that the line border extends half the width to each side.
-        // --> Our coordinates have to be 0.5 DIP off in order to draw a 2px line on a 200% scaling.
-        const auto halfWidth = lineWidth / 2.0f;
-        box.left += halfWidth;
-        box.top += halfWidth;
-        box.right -= halfWidth;
-        box.bottom -= halfWidth;
-        break;
-    }
-    case CursorType::Underscore:
-    case CursorType::DoubleUnderscore:
-        box.top = box.bottom - lineWidth;
-        break;
-    default:
-        break;
-    }
-
-    const auto brush = _brushWithColor(color);
-
-    // We need to clip the area we draw in to ensure we don't
-    // accidentally draw into any neighboring texture atlas tiles.
-    _r.d2dRenderTarget->PushAxisAlignedClip(&clip, D2D1_ANTIALIAS_MODE_ALIASED);
-
-    if (clear)
-    {
-        _r.d2dRenderTarget->Clear();
-    }
-
-    if (cursorType == CursorType::EmptyBox)
-    {
-        _r.d2dRenderTarget->DrawRectangle(&box, brush, lineWidth);
+        desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
+        THROW_IF_FAILED(_p.dxgi.factory->CreateSwapChainForHwnd(_p.device.get(), _p.s->target->hwnd, &desc, nullptr, nullptr, swapChain1.addressof()));
     }
     else
     {
-        _r.d2dRenderTarget->FillRectangle(&box, brush);
-    }
+        const auto module = GetModuleHandleW(L"dcomp.dll");
+        const auto DCompositionCreateSurfaceHandle = GetProcAddressByFunctionDeclaration(module, DCompositionCreateSurfaceHandle);
+        THROW_LAST_ERROR_IF(!DCompositionCreateSurfaceHandle);
 
-    if (cursorType == CursorType::DoubleUnderscore)
-    {
-        const auto offset = lineWidth * 2.0f;
-        box.top -= offset;
-        box.bottom -= offset;
-        _r.d2dRenderTarget->FillRectangle(&box, brush);
+        // 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, _p.swapChain.handle.addressof()));
+        THROW_IF_FAILED(_p.dxgi.factory.query<IDXGIFactoryMedia>()->CreateSwapChainForCompositionSurfaceHandle(_p.device.get(), _p.swapChain.handle.get(), &desc, nullptr, swapChain1.addressof()));
     }
 
-    _r.d2dRenderTarget->PopAxisAlignedClip();
-}
-
-ID2D1Brush* AtlasEngine::_brushWithColor(u32 color)
-{
-    if (_r.brushColor != color)
-    {
-        const auto d2dColor = colorFromU32(color);
-        THROW_IF_FAILED(_r.d2dRenderTarget->CreateSolidColorBrush(&d2dColor, nullptr, _r.brush.put()));
-        _r.brushColor = color;
-    }
-    return _r.brush.get();
-}
-
-AtlasEngine::CachedGlyphLayout::operator bool() const noexcept
-{
-    return static_cast<bool>(textLayout);
-}
-
-void AtlasEngine::CachedGlyphLayout::reset() noexcept
-{
-    textLayout.reset();
-}
+    _p.swapChain.swapChain = swapChain1.query<IDXGISwapChain2>();
+    _p.swapChain.frameLatencyWaitableObject.reset(_p.swapChain.swapChain->GetFrameLatencyWaitableObject());
+    _p.swapChain.targetGeneration = _p.s->target.generation();
+    _p.swapChain.fontGeneration = {};
+    _p.swapChain.targetSize = _p.s->targetSize;
+    _p.swapChain.waitForPresentation = true;
 
-void AtlasEngine::CachedGlyphLayout::applyScaling(ID2D1RenderTarget* d2dRenderTarget, D2D1_POINT_2F origin) const noexcept
-{
-    __assume(d2dRenderTarget != nullptr);
+    WaitUntilCanRender();
 
-    if (scalingRequired)
+    if (_p.swapChainChangedCallback)
     {
-        const D2D1_MATRIX_3X2_F transform{
-            scale.x,
-            0,
-            0,
-            scale.y,
-            (origin.x + scaleCenter.x) * (1.0f - scale.x),
-            (origin.y + scaleCenter.y) * (1.0f - scale.y),
-        };
-        d2dRenderTarget->SetTransform(&transform);
+        try
+        {
+            _p.swapChainChangedCallback(_p.swapChain.handle.get());
+        }
+        CATCH_LOG()
     }
 }
 
-void AtlasEngine::CachedGlyphLayout::undoScaling(ID2D1RenderTarget* d2dRenderTarget) const noexcept
+void AtlasEngine::_resizeBuffers()
 {
-    __assume(d2dRenderTarget != nullptr);
-
-    if (scalingRequired)
-    {
-        static constexpr D2D1_MATRIX_3X2_F identity{ 1, 0, 0, 1, 0, 0 };
-        d2dRenderTarget->SetTransform(&identity);
-    }
+    THROW_IF_FAILED(_p.swapChain.swapChain->ResizeBuffers(0, _p.s->targetSize.x, _p.s->targetSize.y, DXGI_FORMAT_UNKNOWN, swapChainFlags));
+    _p.swapChain.targetSize = _p.s->targetSize;
 }
 
-void AtlasEngine::_d2dPresent()
+void AtlasEngine::_updateMatrixTransform()
 {
-    if (!_r.d2dRenderTarget)
+    if (!_p.s->target->hwnd)
     {
-        _d2dCreateRenderTarget();
+        // XAML's SwapChainPanel combines the worst of both worlds and always applies a transform
+        // to the swap chain to make it match the display scale. This undoes the damage.
+        const DXGI_MATRIX_3X2_F matrix{
+            ._11 = static_cast<f32>(USER_DEFAULT_SCREEN_DPI) / static_cast<f32>(_p.s->font->dpi),
+            ._22 = static_cast<f32>(USER_DEFAULT_SCREEN_DPI) / static_cast<f32>(_p.s->font->dpi),
+        };
+        THROW_IF_FAILED(_p.swapChain.swapChain->SetMatrixTransform(&matrix));
     }
-
-    _d2dDrawDirtyArea();
-
-    _r.glyphQueue.clear();
-    WI_ClearAllFlags(_r.invalidations, RenderInvalidations::Cursor | RenderInvalidations::ConstBuffer);
+    _p.swapChain.fontGeneration = _p.s->font.generation();
 }
 
-void AtlasEngine::_d2dCreateRenderTarget()
+void AtlasEngine::_waitUntilCanRender() noexcept
 {
+    // IDXGISwapChain2::GetFrameLatencyWaitableObject returns an auto-reset event.
+    // Once we've waited on the event, waiting on it again will block until the timeout elapses.
+    // _waitForPresentation guards against this.
+    if constexpr (!ATLAS_DEBUG_DISABLE_FRAME_LATENCY_WAITABLE_OBJECT)
     {
-        wil::com_ptr<ID3D11Texture2D> buffer;
-        THROW_IF_FAILED(_r.swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), buffer.put_void()));
-
-        const auto surface = buffer.query<IDXGISurface>();
-
-        D2D1_RENDER_TARGET_PROPERTIES props{};
-        props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
-        props.pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED };
-        props.dpiX = static_cast<float>(_r.dpi);
-        props.dpiY = static_cast<float>(_r.dpi);
-        wil::com_ptr<ID2D1RenderTarget> renderTarget;
-        THROW_IF_FAILED(_sr.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, renderTarget.addressof()));
-        _r.d2dRenderTarget = renderTarget.query<ID2D1DeviceContext>();
-
-        // In case _api.realizedAntialiasingMode is D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE we'll
-        // continuously adjust it in AtlasEngine::_drawGlyph. See _drawGlyph.
-        _r.d2dRenderTarget->SetTextAntialiasMode(static_cast<D2D1_TEXT_ANTIALIAS_MODE>(_api.realizedAntialiasingMode));
-    }
-    {
-        static constexpr D2D1_COLOR_F color{ 1, 1, 1, 1 };
-        THROW_IF_FAILED(_r.d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, _r.brush.put()));
-        _r.brushColor = 0xffffffff;
+        if (_p.swapChain.waitForPresentation)
+        {
+            WaitForSingleObjectEx(_p.swapChain.frameLatencyWaitableObject.get(), 100, true);
+            _p.swapChain.waitForPresentation = false;
+        }
     }
 }
 
-void AtlasEngine::_d2dDrawDirtyArea()
+void AtlasEngine::_present()
 {
-    struct CellFlagHandler
-    {
-        CellFlags filter;
-        decltype(&AtlasEngine::_d2dCellFlagRendererCursor) func;
-    };
+    const til::rect fullRect{ 0, 0, _p.swapChain.targetSize.x, _p.swapChain.targetSize.y };
 
-    static constexpr std::array cellFlagHandlers{
-        // Ordered by lowest to highest "layer".
-        // The selection for instance is drawn on top of underlines, not under them.
-        CellFlagHandler{ CellFlags::Underline, &AtlasEngine::_d2dCellFlagRendererUnderline },
-        CellFlagHandler{ CellFlags::UnderlineDotted, &AtlasEngine::_d2dCellFlagRendererUnderlineDotted },
-        CellFlagHandler{ CellFlags::UnderlineDouble, &AtlasEngine::_d2dCellFlagRendererUnderlineDouble },
-        CellFlagHandler{ CellFlags::Strikethrough, &AtlasEngine::_d2dCellFlagRendererStrikethrough },
-        CellFlagHandler{ CellFlags::Cursor, &AtlasEngine::_d2dCellFlagRendererCursor },
-        CellFlagHandler{ CellFlags::Selected, &AtlasEngine::_d2dCellFlagRendererSelected },
-    };
+    DXGI_PRESENT_PARAMETERS params{};
+    RECT scrollRect{};
+    POINT scrollOffset{};
 
-    auto left = gsl::narrow<u16>(_r.dirtyRect.left);
-    auto top = gsl::narrow<u16>(_r.dirtyRect.top);
-    auto right = gsl::narrow<u16>(_r.dirtyRect.right);
-    auto bottom = gsl::narrow<u16>(_r.dirtyRect.bottom);
-    if constexpr (debugGlyphGenerationPerformance)
-    {
-        left = 0;
-        top = 0;
-        right = _r.cellCount.x;
-        bottom = _r.cellCount.y;
-    }
+    // Since rows might be taller than their cells, they might have drawn outside of the viewport.
+    auto dirtyRect = _p.dirtyRectInPx;
+    dirtyRect.left = std::max(dirtyRect.left, 0);
+    dirtyRect.top = std::max(dirtyRect.top, 0);
+    dirtyRect.right = std::min(dirtyRect.right, fullRect.right);
+    dirtyRect.bottom = std::min(dirtyRect.bottom, fullRect.bottom);
 
-    _r.d2dRenderTarget->BeginDraw();
-
-    if (WI_IsFlagSet(_r.invalidations, RenderInvalidations::ConstBuffer))
-    {
-        _r.d2dRenderTarget->Clear(colorFromU32(_r.backgroundColor));
-    }
-
-    for (u16 y = top; y < bottom; ++y)
+    if constexpr (!ATLAS_DEBUG_SHOW_DIRTY)
     {
-        const Cell* cells = _getCell(0, y);
-        const TileHashMap::iterator* cellGlyphMappings = _getCellGlyphMapping(0, y);
-
-        // left/right might intersect a wide glyph. We have to extend left/right
-        // to include the entire glyph so that we can properly render it.
-        // Since a series of identical narrow glyphs (2 spaces for instance) are stored in cellGlyphMappings
-        // just like a single wide glyph (2 references to the same glyph in a row), the only way for us to
-        // know where wide glyphs begin and end is to iterate the entire row and use the stored `cellCount`.
-        u16 beg = 0;
-        for (;;)
+        if (dirtyRect != fullRect)
         {
-            const auto cellCount = cellGlyphMappings[beg]->first.data()->attributes.cellCount;
-            const auto begNext = gsl::narrow_cast<u16>(beg + cellCount);
+            params.DirtyRectsCount = 1;
+            params.pDirtyRects = dirtyRect.as_win32_rect();
 
-            if (begNext > left)
+            if (_p.scrollOffset)
             {
-                break;
-            }
+                const auto offsetInPx = _p.scrollOffset * _p.s->font->cellSize.y;
+                const auto width = _p.s->targetSize.x;
+                const auto height = _p.s->cellCount.y * _p.s->font->cellSize.y;
+                const auto top = std::max(0, offsetInPx);
+                const auto bottom = height + std::min(0, offsetInPx);
 
-            beg = begNext;
-        }
-        auto end = beg;
-        for (;;)
-        {
-            const auto cellCount = cellGlyphMappings[end]->first.data()->attributes.cellCount;
-            end += cellCount;
+                scrollRect = { 0, top, width, bottom };
+                scrollOffset = { 0, offsetInPx };
 
-            if (end >= right)
-            {
-                break;
-            }
-        }
-
-        // Draw background.
-        {
-            _r.d2dRenderTarget->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
-
-            auto x1 = beg;
-            auto x2 = gsl::narrow_cast<u16>(x1 + 1);
-            auto currentColor = cells[x1].color.y;
-
-            for (; x2 < end; ++x2)
-            {
-                const auto color = cells[x2].color.y;
-
-                if (currentColor != color)
-                {
-                    const u16r rect{ x1, y, x2, gsl::narrow_cast<u16>(y + 1) };
-                    _d2dFillRectangle(rect, currentColor);
-                    x1 = x2;
-                    currentColor = color;
-                }
-            }
-
-            {
-                const u16r rect{ x1, y, x2, gsl::narrow_cast<u16>(y + 1) };
-                _d2dFillRectangle(rect, currentColor);
-            }
-
-            _r.d2dRenderTarget->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
-        }
-
-        // Draw text.
-        for (auto x = beg; x < end;)
-        {
-            const auto& it = cellGlyphMappings[x];
-            const u16x2 coord{ x, y };
-            const auto color = cells[x].color.x;
-            x += _d2dDrawGlyph(it, coord, color);
-        }
-
-        // Draw underlines, cursors, selections, etc.
-        for (const auto& handler : cellFlagHandlers)
-        {
-            auto x1 = beg;
-            auto currentFlags = CellFlags::None;
-
-            for (auto x2 = beg; x2 < end; ++x2)
-            {
-                const auto flags = cells[x2].flags & handler.filter;
-
-                if (currentFlags != flags)
-                {
-                    if (currentFlags != CellFlags::None)
-                    {
-                        const u16r rect{ x1, y, x2, gsl::narrow_cast<u16>(y + 1) };
-                        const auto color = cells[x1].color.x;
-                        (this->*handler.func)(rect, color);
-                    }
-
-                    x1 = x2;
-                    currentFlags = flags;
-                }
-            }
-
-            if (currentFlags != CellFlags::None)
-            {
-                const u16r rect{ x1, y, right, gsl::narrow_cast<u16>(y + 1) };
-                const auto color = cells[x1].color.x;
-                (this->*handler.func)(rect, color);
+                params.pScrollRect = &scrollRect;
+                params.pScrollOffset = &scrollOffset;
             }
         }
     }
 
-    THROW_IF_FAILED(_r.d2dRenderTarget->EndDraw());
-}
-
-// See _drawGlyph() for reference.
-AtlasEngine::u16 AtlasEngine::_d2dDrawGlyph(const TileHashMap::iterator& it, const u16x2 coord, const u32 color)
-{
-    const auto key = it->first.data();
-    const auto value = it->second.data();
-    const auto charsLength = key->charCount;
-    const auto cellCount = key->attributes.cellCount;
-    const auto textFormat = _getTextFormat(key->attributes.bold, key->attributes.italic);
-    const auto coloredGlyph = WI_IsFlagSet(value->flags, CellFlags::ColoredGlyph);
-
-    auto& cachedLayout = it->second.cachedLayout;
-    if (!cachedLayout)
-    {
-        cachedLayout = _getCachedGlyphLayout(&key->chars[0], charsLength, cellCount, textFormat, coloredGlyph);
-    }
-
-    D2D1_RECT_F rect;
-    rect.left = static_cast<float>(coord.x) * _r.cellSizeDIP.x;
-    rect.top = static_cast<float>(coord.y) * _r.cellSizeDIP.y;
-    rect.right = static_cast<float>(coord.x + cellCount) * _r.cellSizeDIP.x;
-    rect.bottom = rect.top + _r.cellSizeDIP.y;
-
-    D2D1_POINT_2F origin;
-    origin.x = rect.left;
-    origin.y = rect.top;
-
-    _r.d2dRenderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED);
-
-    cachedLayout.applyScaling(_r.d2dRenderTarget.get(), origin);
-
-    origin.x += cachedLayout.offset.x;
-    origin.y += cachedLayout.offset.y;
-    _r.d2dRenderTarget->DrawTextLayout(origin, cachedLayout.textLayout.get(), _brushWithColor(color), cachedLayout.options);
-
-    cachedLayout.undoScaling(_r.d2dRenderTarget.get());
-
-    _r.d2dRenderTarget->PopAxisAlignedClip();
-
-    return cellCount;
-}
-
-void AtlasEngine::_d2dDrawLine(u16r rect, u16 pos, u16 width, u32 color, ID2D1StrokeStyle* strokeStyle)
-{
-    const auto w = static_cast<float>(width) * _r.dipPerPixel;
-    const auto y1 = static_cast<float>(rect.top) * _r.cellSizeDIP.y + static_cast<float>(pos) * _r.dipPerPixel + w * 0.5f;
-    const auto x1 = static_cast<float>(rect.left) * _r.cellSizeDIP.x;
-    const auto x2 = static_cast<float>(rect.right) * _r.cellSizeDIP.x;
-    const auto brush = _brushWithColor(color);
-    _r.d2dRenderTarget->DrawLine({ x1, y1 }, { x2, y1 }, brush, w, strokeStyle);
-}
-
-void AtlasEngine::_d2dFillRectangle(u16r rect, u32 color)
-{
-    const D2D1_RECT_F r{
-        .left = static_cast<float>(rect.left) * _r.cellSizeDIP.x,
-        .top = static_cast<float>(rect.top) * _r.cellSizeDIP.y,
-        .right = static_cast<float>(rect.right) * _r.cellSizeDIP.x,
-        .bottom = static_cast<float>(rect.bottom) * _r.cellSizeDIP.y,
-    };
-    const auto brush = _brushWithColor(color);
-    _r.d2dRenderTarget->FillRectangle(r, brush);
-}
-
-void AtlasEngine::_d2dCellFlagRendererCursor(u16r rect, u32 color)
-{
-    _drawCursor(rect, _r.cursorOptions.cursorColor, false);
-}
-
-void AtlasEngine::_d2dCellFlagRendererSelected(u16r rect, u32 color)
-{
-    _d2dFillRectangle(rect, _r.selectionColor);
-}
-
-void AtlasEngine::_d2dCellFlagRendererUnderline(u16r rect, u32 color)
-{
-    _d2dDrawLine(rect, _r.fontMetrics.underlinePos, _r.fontMetrics.underlineWidth, color);
-}
-
-void AtlasEngine::_d2dCellFlagRendererUnderlineDotted(u16r rect, u32 color)
-{
-    if (!_r.dottedStrokeStyle)
-    {
-        static constexpr D2D1_STROKE_STYLE_PROPERTIES props{ .dashStyle = D2D1_DASH_STYLE_CUSTOM };
-        static constexpr FLOAT dashes[2]{ 1, 2 };
-        THROW_IF_FAILED(_sr.d2dFactory->CreateStrokeStyle(&props, &dashes[0], 2, _r.dottedStrokeStyle.addressof()));
-    }
-
-    _d2dDrawLine(rect, _r.fontMetrics.underlinePos, _r.fontMetrics.underlineWidth, color, _r.dottedStrokeStyle.get());
-}
-
-void AtlasEngine::_d2dCellFlagRendererUnderlineDouble(u16r rect, u32 color)
-{
-    _d2dDrawLine(rect, _r.fontMetrics.doubleUnderlinePos.x, _r.fontMetrics.thinLineWidth, color);
-    _d2dDrawLine(rect, _r.fontMetrics.doubleUnderlinePos.y, _r.fontMetrics.thinLineWidth, color);
-}
-
-void AtlasEngine::_d2dCellFlagRendererStrikethrough(u16r rect, u32 color)
-{
-    _d2dDrawLine(rect, _r.fontMetrics.strikethroughPos, _r.fontMetrics.strikethroughWidth, color);
+    THROW_IF_FAILED(_p.swapChain.swapChain->Present1(1, 0, &params));
+    _p.swapChain.waitForPresentation = true;
 }
diff --git a/src/renderer/atlas/Backend.cpp b/src/renderer/atlas/Backend.cpp
new file mode 100644
index 00000000000..08fb370f45f
--- /dev/null
+++ b/src/renderer/atlas/Backend.cpp
@@ -0,0 +1,109 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+#include "Backend.h"
+
+#include <dwmapi.h>
+
+TIL_FAST_MATH_BEGIN
+
+// Disable a bunch of warnings which get in the way of writing performant code.
+#pragma warning(disable : 26429) // Symbol 'data' is never tested for nullness, it can be marked as not_null (f.23).
+#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
+#pragma warning(disable : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe [...].
+#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
+#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
+
+using namespace Microsoft::Console::Render::Atlas;
+
+void Microsoft::Console::Render::Atlas::GlyphRunAccumulateBounds(const ID2D1DeviceContext* d2dRenderTarget, D2D1_POINT_2F baselineOrigin, const DWRITE_GLYPH_RUN* glyphRun, D2D1_RECT_F& bounds)
+{
+    D2D1_RECT_F rect{};
+    THROW_IF_FAILED(d2dRenderTarget->GetGlyphRunWorldBounds(baselineOrigin, glyphRun, DWRITE_MEASURING_MODE_NATURAL, &rect));
+    if (rect.top < rect.bottom)
+    {
+        bounds.left = std::min(bounds.left, rect.left);
+        bounds.top = std::min(bounds.top, rect.top);
+        bounds.right = std::max(bounds.right, rect.right);
+        bounds.bottom = std::max(bounds.bottom, rect.bottom);
+    }
+}
+
+wil::com_ptr<IDWriteColorGlyphRunEnumerator1> Microsoft::Console::Render::Atlas::TranslateColorGlyphRun(IDWriteFactory4* dwriteFactory4, D2D_POINT_2F baselineOrigin, const DWRITE_GLYPH_RUN* glyphRun) noexcept
+{
+    static constexpr auto formats =
+        DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE |
+        DWRITE_GLYPH_IMAGE_FORMATS_CFF |
+        DWRITE_GLYPH_IMAGE_FORMATS_COLR |
+        DWRITE_GLYPH_IMAGE_FORMATS_SVG |
+        DWRITE_GLYPH_IMAGE_FORMATS_PNG |
+        DWRITE_GLYPH_IMAGE_FORMATS_JPEG |
+        DWRITE_GLYPH_IMAGE_FORMATS_TIFF |
+        DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8;
+
+    wil::com_ptr<IDWriteColorGlyphRunEnumerator1> enumerator;
+
+    if (dwriteFactory4)
+    {
+        std::ignore = dwriteFactory4->TranslateColorGlyphRun(baselineOrigin, glyphRun, nullptr, formats, DWRITE_MEASURING_MODE_NATURAL, nullptr, 0, enumerator.addressof());
+    }
+
+    return enumerator;
+}
+
+bool Microsoft::Console::Render::Atlas::ColorGlyphRunMoveNext(IDWriteColorGlyphRunEnumerator1* enumerator)
+{
+    BOOL hasRun;
+    THROW_IF_FAILED(enumerator->MoveNext(&hasRun));
+    return hasRun;
+}
+
+const DWRITE_COLOR_GLYPH_RUN1* Microsoft::Console::Render::Atlas::ColorGlyphRunGetCurrentRun(IDWriteColorGlyphRunEnumerator1* enumerator)
+{
+    const DWRITE_COLOR_GLYPH_RUN1* colorGlyphRun = nullptr;
+    THROW_IF_FAILED(enumerator->GetCurrentRun(&colorGlyphRun));
+    return colorGlyphRun;
+}
+
+void Microsoft::Console::Render::Atlas::ColorGlyphRunAccumulateBounds(const ID2D1DeviceContext* d2dRenderTarget, const DWRITE_COLOR_GLYPH_RUN1* colorGlyphRun, D2D1_RECT_F& bounds)
+{
+    const D2D1_POINT_2F baselineOrigin{ colorGlyphRun->baselineOriginX, colorGlyphRun->baselineOriginY };
+    GlyphRunAccumulateBounds(d2dRenderTarget, baselineOrigin, &colorGlyphRun->glyphRun, bounds);
+}
+
+void Microsoft::Console::Render::Atlas::ColorGlyphRunDraw(ID2D1DeviceContext4* d2dRenderTarget4, ID2D1SolidColorBrush* emojiBrush, ID2D1SolidColorBrush* foregroundBrush, const DWRITE_COLOR_GLYPH_RUN1* colorGlyphRun) noexcept
+{
+    ID2D1Brush* runBrush = nullptr;
+    if (colorGlyphRun->paletteIndex == /*DWRITE_NO_PALETTE_INDEX*/ 0xffff)
+    {
+        runBrush = foregroundBrush;
+    }
+    else
+    {
+        emojiBrush->SetColor(&colorGlyphRun->runColor);
+        runBrush = emojiBrush;
+    }
+
+    const D2D1_POINT_2F baselineOrigin{ colorGlyphRun->baselineOriginX, colorGlyphRun->baselineOriginY };
+
+    switch (colorGlyphRun->glyphImageFormat)
+    {
+    case DWRITE_GLYPH_IMAGE_FORMATS_NONE:
+        break;
+    case DWRITE_GLYPH_IMAGE_FORMATS_PNG:
+    case DWRITE_GLYPH_IMAGE_FORMATS_JPEG:
+    case DWRITE_GLYPH_IMAGE_FORMATS_TIFF:
+    case DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8:
+        d2dRenderTarget4->DrawColorBitmapGlyphRun(colorGlyphRun->glyphImageFormat, baselineOrigin, &colorGlyphRun->glyphRun, colorGlyphRun->measuringMode, D2D1_COLOR_BITMAP_GLYPH_SNAP_OPTION_DEFAULT);
+        break;
+    case DWRITE_GLYPH_IMAGE_FORMATS_SVG:
+        d2dRenderTarget4->DrawSvgGlyphRun(baselineOrigin, &colorGlyphRun->glyphRun, runBrush, nullptr, 0, colorGlyphRun->measuringMode);
+        break;
+    default:
+        d2dRenderTarget4->DrawGlyphRun(baselineOrigin, &colorGlyphRun->glyphRun, colorGlyphRun->glyphRunDescription, runBrush, colorGlyphRun->measuringMode);
+        break;
+    }
+}
+
+TIL_FAST_MATH_END
diff --git a/src/renderer/atlas/Backend.h b/src/renderer/atlas/Backend.h
new file mode 100644
index 00000000000..5da33f6e304
--- /dev/null
+++ b/src/renderer/atlas/Backend.h
@@ -0,0 +1,85 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+#include "common.h"
+
+namespace Microsoft::Console::Render::Atlas
+{
+    // If set to 1, this will cause the entire viewport to be invalidated at all times.
+    // Helpful for benchmarking our text shaping code based on DirectWrite.
+#define ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION 0
+
+    // Redraw at display refresh rate at all times. This helps with shader debugging.
+#define ATLAS_DEBUG_CONTINUOUS_REDRAW 0
+
+    // Disables the use of DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT.
+    // This helps with benchmarking the application as it'll run beyond display refresh rate.
+#define ATLAS_DEBUG_DISABLE_FRAME_LATENCY_WAITABLE_OBJECT 0
+
+    // Forces the use of Direct2D for text rendering (= BackendD2D).
+#define ATLAS_DEBUG_FORCE_D2D_MODE 0
+
+    // Adds an artificial delay before every render pass. In milliseconds.
+#define ATLAS_DEBUG_RENDER_DELAY 0
+
+    // Shows the dirty rects as given to IDXGISwapChain2::Present1() during each frame.
+#define ATLAS_DEBUG_SHOW_DIRTY 0
+
+    // Dumps the contents of the swap chain on each render pass into the given directory as PNG.
+    // I highly recommend setting ATLAS_DEBUG_RENDER_DELAY to 250 or similar if this is used.
+#define ATLAS_DEBUG_DUMP_RENDER_TARGET 0
+#define ATLAS_DEBUG_DUMP_RENDER_TARGET_PATH LR"(%USERPROFILE%\Downloads\AtlasEngine)"
+
+    template<typename T = D2D1_COLOR_F>
+    constexpr T colorFromU32(u32 rgba)
+    {
+        const auto r = static_cast<f32>((rgba >> 0) & 0xff) / 255.0f;
+        const auto g = static_cast<f32>((rgba >> 8) & 0xff) / 255.0f;
+        const auto b = static_cast<f32>((rgba >> 16) & 0xff) / 255.0f;
+        const auto a = static_cast<f32>((rgba >> 24) & 0xff) / 255.0f;
+        return { r, g, b, a };
+    }
+
+    template<typename T = D2D1_COLOR_F>
+    constexpr T colorFromU32Premultiply(u32 rgba)
+    {
+        const auto r = static_cast<f32>((rgba >> 0) & 0xff) / 255.0f;
+        const auto g = static_cast<f32>((rgba >> 8) & 0xff) / 255.0f;
+        const auto b = static_cast<f32>((rgba >> 16) & 0xff) / 255.0f;
+        const auto a = static_cast<f32>((rgba >> 24) & 0xff) / 255.0f;
+        return { r * a, g * a, b * a, a };
+    }
+
+    constexpr u32 u32ColorPremultiply(u32 rgba)
+    {
+        auto rb = rgba & 0x00ff00ff;
+        auto g = rgba & 0x0000ff00;
+        const auto a = rgba & 0xff000000;
+
+        const auto m = rgba >> 24;
+        rb = (rb * m / 0xff) & 0x00ff00ff;
+        g = (g * m / 0xff) & 0x0000ff00;
+
+        return rb | g | a;
+    }
+
+    // MSVC STL (version 22000) implements std::clamp<T>(T, T, T) in terms of the generic
+    // std::clamp<T, Predicate>(T, T, T, Predicate) with std::less{} as the argument,
+    // which introduces branching. While not perfect, this is still better than std::clamp.
+    template<typename T>
+    constexpr T clamp(T val, T min, T max)
+    {
+        return val < min ? min : (max < val ? max : val);
+    }
+
+    inline constexpr D2D1_RECT_F GlyphRunEmptyBounds{ 1e38f, 1e38f, -1e38f, -1e38f };
+    void GlyphRunAccumulateBounds(const ID2D1DeviceContext* d2dRenderTarget, D2D1_POINT_2F baselineOrigin, const DWRITE_GLYPH_RUN* glyphRun, D2D1_RECT_F& bounds);
+
+    wil::com_ptr<IDWriteColorGlyphRunEnumerator1> TranslateColorGlyphRun(IDWriteFactory4* dwriteFactory4, D2D_POINT_2F baselineOrigin, const DWRITE_GLYPH_RUN* glyphRun) noexcept;
+    bool ColorGlyphRunMoveNext(IDWriteColorGlyphRunEnumerator1* enumerator);
+    const DWRITE_COLOR_GLYPH_RUN1* ColorGlyphRunGetCurrentRun(IDWriteColorGlyphRunEnumerator1* enumerator);
+    void ColorGlyphRunAccumulateBounds(const ID2D1DeviceContext* d2dRenderTarget, const DWRITE_COLOR_GLYPH_RUN1* colorGlyphRun, D2D1_RECT_F& bounds);
+    void ColorGlyphRunDraw(ID2D1DeviceContext4* d2dRenderTarget4, ID2D1SolidColorBrush* emojiBrush, ID2D1SolidColorBrush* foregroundBrush, const DWRITE_COLOR_GLYPH_RUN1* colorGlyphRun) noexcept;
+}
diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp
new file mode 100644
index 00000000000..f9e14275117
--- /dev/null
+++ b/src/renderer/atlas/BackendD2D.cpp
@@ -0,0 +1,684 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+#include "BackendD2D.h"
+
+#if ATLAS_DEBUG_SHOW_DIRTY
+#include "colorbrewer.h"
+#endif
+
+#if ATLAS_DEBUG_DUMP_RENDER_TARGET
+#include "wic.h"
+#endif
+
+TIL_FAST_MATH_BEGIN
+
+// Disable a bunch of warnings which get in the way of writing performant code.
+#pragma warning(disable : 26429) // Symbol 'data' is never tested for nullness, it can be marked as not_null (f.23).
+#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
+#pragma warning(disable : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe [...].
+#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
+#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
+
+using namespace Microsoft::Console::Render::Atlas;
+
+void BackendD2D::ReleaseResources() noexcept
+{
+    _renderTarget.reset();
+    _renderTarget4.reset();
+    // Ensure _handleSettingsUpdate() is called so that _renderTarget gets recreated.
+    _generation = {};
+}
+
+void BackendD2D::Render(RenderingPayload& p)
+{
+    if (_generation != p.s.generation())
+    {
+        _handleSettingsUpdate(p);
+    }
+
+    _renderTarget->BeginDraw();
+#if ATLAS_DEBUG_SHOW_DIRTY || ATLAS_DEBUG_DUMP_RENDER_TARGET
+    // Invalidating the render target helps with spotting Present1() bugs.
+    _renderTarget->Clear();
+#endif
+    _drawBackground(p);
+    _drawCursorPart1(p);
+    _drawText(p);
+    _drawCursorPart2(p);
+    _drawSelection(p);
+#if ATLAS_DEBUG_SHOW_DIRTY
+    _debugShowDirty(p);
+#endif
+    THROW_IF_FAILED(_renderTarget->EndDraw());
+
+#if ATLAS_DEBUG_DUMP_RENDER_TARGET
+    _debugDumpRenderTarget(p);
+#endif
+}
+
+bool BackendD2D::RequiresContinuousRedraw() noexcept
+{
+    return false;
+}
+
+void BackendD2D::_handleSettingsUpdate(const RenderingPayload& p)
+{
+    const auto renderTargetChanged = !_renderTarget;
+    const auto fontChanged = _fontGeneration != p.s->font.generation();
+    const auto cursorChanged = _cursorGeneration != p.s->cursor.generation();
+    const auto cellCountChanged = _cellCount != p.s->cellCount;
+
+    if (renderTargetChanged)
+    {
+        {
+            wil::com_ptr<ID3D11Texture2D> buffer;
+            THROW_IF_FAILED(p.swapChain.swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(buffer.addressof())));
+
+            const D2D1_RENDER_TARGET_PROPERTIES props{
+                .type = D2D1_RENDER_TARGET_TYPE_DEFAULT,
+                .pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED },
+                .dpiX = static_cast<f32>(p.s->font->dpi),
+                .dpiY = static_cast<f32>(p.s->font->dpi),
+            };
+            // ID2D1RenderTarget and ID2D1DeviceContext are the same and I'm tired of pretending they're not.
+            THROW_IF_FAILED(p.d2dFactory->CreateDxgiSurfaceRenderTarget(buffer.query<IDXGISurface>().get(), &props, reinterpret_cast<ID2D1RenderTarget**>(_renderTarget.addressof())));
+            _renderTarget.query_to(_renderTarget4.addressof());
+
+            _renderTarget->SetUnitMode(D2D1_UNIT_MODE_PIXELS);
+            _renderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
+        }
+        {
+            static constexpr D2D1_COLOR_F color{};
+            THROW_IF_FAILED(_renderTarget->CreateSolidColorBrush(&color, nullptr, _emojiBrush.put()));
+            THROW_IF_FAILED(_renderTarget->CreateSolidColorBrush(&color, nullptr, _brush.put()));
+            _brushColor = 0;
+        }
+    }
+
+    if (!_dottedStrokeStyle)
+    {
+        static constexpr D2D1_STROKE_STYLE_PROPERTIES props{ .dashStyle = D2D1_DASH_STYLE_CUSTOM };
+        static constexpr FLOAT dashes[2]{ 1, 1 };
+        THROW_IF_FAILED(p.d2dFactory->CreateStrokeStyle(&props, &dashes[0], 2, _dottedStrokeStyle.addressof()));
+    }
+
+    if (renderTargetChanged || fontChanged)
+    {
+        const auto dpi = static_cast<f32>(p.s->font->dpi);
+        _renderTarget->SetDpi(dpi, dpi);
+        _renderTarget->SetTextAntialiasMode(static_cast<D2D1_TEXT_ANTIALIAS_MODE>(p.s->font->antialiasingMode));
+    }
+
+    if (renderTargetChanged || fontChanged || cellCountChanged)
+    {
+        const D2D1_BITMAP_PROPERTIES props{
+            .pixelFormat = { DXGI_FORMAT_R8G8B8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED },
+            .dpiX = static_cast<f32>(p.s->font->dpi),
+            .dpiY = static_cast<f32>(p.s->font->dpi),
+        };
+        const D2D1_SIZE_U size{
+            p.s->cellCount.x,
+            p.s->cellCount.y,
+        };
+        const D2D1_MATRIX_3X2_F transform{
+            .m11 = static_cast<f32>(p.s->font->cellSize.x),
+            .m22 = static_cast<f32>(p.s->font->cellSize.y),
+        };
+        THROW_IF_FAILED(_renderTarget->CreateBitmap(size, nullptr, 0, &props, _backgroundBitmap.put()));
+        THROW_IF_FAILED(_renderTarget->CreateBitmapBrush(_backgroundBitmap.get(), _backgroundBrush.put()));
+        _backgroundBrush->SetInterpolationMode(D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
+        _backgroundBrush->SetExtendModeX(D2D1_EXTEND_MODE_MIRROR);
+        _backgroundBrush->SetExtendModeY(D2D1_EXTEND_MODE_MIRROR);
+        _backgroundBrush->SetTransform(&transform);
+        _backgroundBitmapGeneration = {};
+    }
+
+    if (fontChanged || cursorChanged)
+    {
+        _cursorBitmap.reset();
+        _cursorBitmapSize = {};
+    }
+
+    _generation = p.s.generation();
+    _fontGeneration = p.s->font.generation();
+    _cursorGeneration = p.s->cursor.generation();
+    _cellCount = p.s->cellCount;
+}
+
+void BackendD2D::_drawBackground(const RenderingPayload& p) noexcept
+{
+    if (_backgroundBitmapGeneration != p.colorBitmapGenerations[0])
+    {
+        _backgroundBitmap->CopyFromMemory(nullptr, p.backgroundBitmap.data(), gsl::narrow_cast<UINT32>(p.colorBitmapRowStride * sizeof(u32)));
+        _backgroundBitmapGeneration = p.colorBitmapGenerations[0];
+    }
+
+    // If the terminal was 120x30 cells and 1200x600 pixels large, this would draw the
+    // background by upscaling a 120x30 pixel bitmap to fill the entire render target.
+    const D2D1_RECT_F rect{ 0, 0, static_cast<f32>(p.s->targetSize.x), static_cast<f32>(p.s->targetSize.y) };
+    _renderTarget->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
+    _renderTarget->FillRectangle(&rect, _backgroundBrush.get());
+    _renderTarget->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
+}
+
+void BackendD2D::_drawText(RenderingPayload& p)
+{
+    til::CoordType dirtyTop = til::CoordTypeMax;
+    til::CoordType dirtyBottom = til::CoordTypeMin;
+
+    // It is possible to create a "_foregroundBrush" similar to how the `_backgroundBrush` is created and
+    // use that as the brush for text rendering below. That way we wouldn't have to search `row->colors` for color
+    // changes and could draw entire lines of text in a single call. Unfortunately Direct2D is not particularly
+    // smart if you do this and chooses to draw the given text into a way too small offscreen texture first and
+    // then blends it on the screen with the given bitmap brush. While this roughly doubles the performance
+    // when drawing lots of colors, the extra latency drops performance by >10x when drawing fewer colors.
+    // Since fewer colors are more common, I've chosen to go with regular solid-color brushes.
+    u16 y = 0;
+    for (const auto row : p.rows)
+    {
+        auto baselineX = 0.0f;
+        auto baselineY = static_cast<f32>(p.s->font->cellSize.y * y + p.s->font->baseline);
+
+        if (row->lineRendition != LineRendition::SingleWidth)
+        {
+            baselineY = _drawTextPrepareLineRendition(p, row, baselineY);
+        }
+
+        for (const auto& m : row->mappings)
+        {
+            const auto colorsBegin = row->colors.begin();
+            auto it = colorsBegin + m.glyphsFrom;
+            const auto end = colorsBegin + m.glyphsTo;
+
+            while (it != end)
+            {
+                const auto beg = it;
+                const auto off = it - colorsBegin;
+                const auto fg = *it;
+
+                while (++it != end && *it == fg)
+                {
+                }
+
+                const auto count = it - beg;
+                const auto brush = _brushWithColor(fg);
+                const DWRITE_GLYPH_RUN glyphRun{
+                    .fontFace = m.fontFace.get(),
+                    .fontEmSize = p.s->font->fontSize,
+                    .glyphCount = gsl::narrow_cast<UINT32>(count),
+                    .glyphIndices = &row->glyphIndices[off],
+                    .glyphAdvances = &row->glyphAdvances[off],
+                    .glyphOffsets = &row->glyphOffsets[off],
+                };
+                const D2D1_POINT_2F baselineOrigin{
+                    baselineX,
+                    baselineY,
+                };
+
+                if (glyphRun.fontFace)
+                {
+                    D2D1_RECT_F bounds = GlyphRunEmptyBounds;
+
+                    if (const auto enumerator = TranslateColorGlyphRun(p.dwriteFactory4.get(), baselineOrigin, &glyphRun))
+                    {
+                        while (ColorGlyphRunMoveNext(enumerator.get()))
+                        {
+                            const auto colorGlyphRun = ColorGlyphRunGetCurrentRun(enumerator.get());
+                            ColorGlyphRunDraw(_renderTarget4.get(), _emojiBrush.get(), brush, colorGlyphRun);
+                            ColorGlyphRunAccumulateBounds(_renderTarget.get(), colorGlyphRun, bounds);
+                        }
+                    }
+                    else
+                    {
+                        _renderTarget->DrawGlyphRun(baselineOrigin, &glyphRun, brush, DWRITE_MEASURING_MODE_NATURAL);
+                        GlyphRunAccumulateBounds(_renderTarget.get(), baselineOrigin, &glyphRun, bounds);
+                    }
+
+                    if (bounds.top < bounds.bottom)
+                    {
+                        // Since we used SetUnitMode(D2D1_UNIT_MODE_PIXELS), bounds.top/bottom is in pixels already and requires no conversion/rounding.
+                        if (row->lineRendition != LineRendition::DoubleHeightTop)
+                        {
+                            row->dirtyBottom = std::max(row->dirtyBottom, static_cast<i32>(lrintf(bounds.bottom)));
+                        }
+                        if (row->lineRendition != LineRendition::DoubleHeightBottom)
+                        {
+                            row->dirtyTop = std::min(row->dirtyTop, static_cast<i32>(lrintf(bounds.top)));
+                        }
+                    }
+                }
+
+                for (UINT32 i = 0; i < glyphRun.glyphCount; ++i)
+                {
+                    baselineX += glyphRun.glyphAdvances[i];
+                }
+            }
+        }
+
+        if (!row->gridLineRanges.empty())
+        {
+            _drawGridlineRow(p, row, y);
+        }
+
+        if (row->lineRendition != LineRendition::SingleWidth)
+        {
+            _drawTextResetLineRendition(row);
+        }
+
+        if (p.invalidatedRows.contains(y))
+        {
+            dirtyTop = std::min(dirtyTop, row->dirtyTop);
+            dirtyBottom = std::max(dirtyBottom, row->dirtyBottom);
+        }
+
+        ++y;
+    }
+
+    if (dirtyTop < dirtyBottom)
+    {
+        p.dirtyRectInPx.top = std::min(p.dirtyRectInPx.top, dirtyTop);
+        p.dirtyRectInPx.bottom = std::max(p.dirtyRectInPx.bottom, dirtyBottom);
+    }
+}
+
+f32 BackendD2D::_drawTextPrepareLineRendition(const RenderingPayload& p, const ShapedRow* row, f32 baselineY) const noexcept
+{
+    const auto lineRendition = row->lineRendition;
+    D2D1_MATRIX_3X2_F transform{
+        .m11 = 2.0f,
+        .m22 = 1.0f,
+    };
+
+    if (lineRendition >= LineRendition::DoubleHeightTop)
+    {
+        D2D1_RECT_F clipRect{ 0, 0, static_cast<f32>(p.s->targetSize.x), static_cast<f32>(p.s->targetSize.y) };
+
+        transform.m22 = 2.0f;
+        transform.dy = -1.0f * (baselineY + p.s->font->descender);
+
+        // If you print the top half of a double height row (DECDHL), the expectation is that only
+        // the top half is visible, which requires us to keep the clip rect at the bottom of the row.
+        // (Vice versa for the bottom half of a double height row.)
+        if (lineRendition == LineRendition::DoubleHeightTop)
+        {
+            const auto delta = static_cast<f32>(p.s->font->cellSize.y);
+            baselineY += delta;
+            transform.dy -= delta;
+            clipRect.bottom = static_cast<f32>(row->dirtyBottom);
+        }
+        else
+        {
+            clipRect.top = static_cast<f32>(row->dirtyTop);
+        }
+
+        _renderTarget->PushAxisAlignedClip(&clipRect, D2D1_ANTIALIAS_MODE_ALIASED);
+    }
+
+    _renderTarget->SetTransform(&transform);
+    return baselineY;
+}
+
+void BackendD2D::_drawTextResetLineRendition(const ShapedRow* row) const noexcept
+{
+    static constexpr D2D1_MATRIX_3X2_F identity{ .m11 = 1, .m22 = 1 };
+    _renderTarget->SetTransform(&identity);
+
+    if (row->lineRendition >= LineRendition::DoubleHeightTop)
+    {
+        _renderTarget->PopAxisAlignedClip();
+    }
+}
+
+// Returns the theoretical/design design size of the given `DWRITE_GLYPH_RUN`, relative the the given baseline origin.
+// This algorithm replicates what DirectWrite does internally to provide `IDWriteTextLayout::GetMetrics`.
+f32r BackendD2D::_getGlyphRunDesignBounds(const DWRITE_GLYPH_RUN& glyphRun, f32 baselineX, f32 baselineY)
+{
+    DWRITE_FONT_METRICS fontMetrics;
+    glyphRun.fontFace->GetMetrics(&fontMetrics);
+
+    if (glyphRun.glyphCount > _glyphMetrics.size())
+    {
+        // Growth factor 1.5x.
+        auto size = _glyphMetrics.size();
+        size = size + (size >> 1);
+        size = std::max<size_t>(size, glyphRun.glyphCount);
+        // Overflow check.
+        Expects(size > _glyphMetrics.size());
+        _glyphMetrics = Buffer<DWRITE_GLYPH_METRICS>{ size };
+    }
+
+    glyphRun.fontFace->GetDesignGlyphMetrics(glyphRun.glyphIndices, glyphRun.glyphCount, _glyphMetrics.data(), false);
+
+    const f32 fontScale = glyphRun.fontEmSize / fontMetrics.designUnitsPerEm;
+    f32r accumulatedBounds{
+        baselineX,
+        baselineY,
+        baselineX,
+        baselineY,
+    };
+
+    for (uint32_t i = 0; i < glyphRun.glyphCount; ++i)
+    {
+        const auto& glyphMetrics = _glyphMetrics[i];
+        const auto glyphAdvance = glyphRun.glyphAdvances ? glyphRun.glyphAdvances[i] : glyphMetrics.advanceWidth * fontScale;
+
+        const auto left = static_cast<f32>(glyphMetrics.leftSideBearing) * fontScale;
+        const auto top = static_cast<f32>(glyphMetrics.topSideBearing - glyphMetrics.verticalOriginY) * fontScale;
+        const auto right = static_cast<f32>(gsl::narrow_cast<INT32>(glyphMetrics.advanceWidth) - glyphMetrics.rightSideBearing) * fontScale;
+        const auto bottom = static_cast<f32>(gsl::narrow_cast<INT32>(glyphMetrics.advanceHeight) - glyphMetrics.bottomSideBearing - glyphMetrics.verticalOriginY) * fontScale;
+
+        if (left < right && top < bottom)
+        {
+            auto glyphX = baselineX;
+            auto glyphY = baselineY;
+            if (glyphRun.glyphOffsets)
+            {
+                glyphX += glyphRun.glyphOffsets[i].advanceOffset;
+                glyphY -= glyphRun.glyphOffsets[i].ascenderOffset;
+            }
+
+            accumulatedBounds.left = std::min(accumulatedBounds.left, left + glyphX);
+            accumulatedBounds.top = std::min(accumulatedBounds.top, top + glyphY);
+            accumulatedBounds.right = std::max(accumulatedBounds.right, right + glyphX);
+            accumulatedBounds.bottom = std::max(accumulatedBounds.bottom, bottom + glyphY);
+        }
+
+        baselineX += glyphAdvance;
+    }
+
+    return accumulatedBounds;
+}
+
+void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* row, u16 y)
+{
+    const auto widthShift = gsl::narrow_cast<u8>(row->lineRendition != LineRendition::SingleWidth);
+    const auto cellSize = p.s->font->cellSize;
+    const auto rowTop = gsl::narrow_cast<i16>(cellSize.y * y);
+    const auto rowBottom = gsl::narrow_cast<i16>(rowTop + cellSize.y);
+    const auto textCellCenter = row->lineRendition == LineRendition::DoubleHeightTop ? rowBottom : rowTop;
+
+    const auto appendVerticalLines = [&](const GridLineRange& r, FontDecorationPosition pos) {
+        const auto from = r.from >> widthShift;
+        const auto to = r.to >> widthShift;
+
+        auto posX = from * cellSize.x + pos.position;
+        const auto end = to * cellSize.x;
+
+        D2D1_POINT_2F point0{ 0, static_cast<f32>(textCellCenter) };
+        D2D1_POINT_2F point1{ 0, static_cast<f32>(textCellCenter + cellSize.y) };
+        const auto brush = _brushWithColor(r.color);
+        const f32 w = pos.height;
+        const f32 hw = w * 0.5f;
+
+        for (; posX < end; posX += cellSize.x)
+        {
+            const auto centerX = posX + hw;
+            point0.x = centerX;
+            point1.x = centerX;
+            _renderTarget->DrawLine(point0, point1, brush, w, nullptr);
+        }
+    };
+    const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle) {
+        const auto from = r.from >> widthShift;
+        const auto to = r.to >> widthShift;
+
+        const auto brush = _brushWithColor(r.color);
+        const f32 w = pos.height;
+        const f32 centerY = textCellCenter + pos.position + w * 0.5f;
+        const D2D1_POINT_2F point0{ static_cast<f32>(from * cellSize.x), centerY };
+        const D2D1_POINT_2F point1{ static_cast<f32>(to * cellSize.x), centerY };
+        _renderTarget->DrawLine(point0, point1, brush, w, strokeStyle);
+    };
+
+    for (const auto& r : row->gridLineRanges)
+    {
+        // AtlasEngine.cpp shouldn't add any gridlines if they don't do anything.
+        assert(r.lines.any());
+
+        if (r.lines.test(GridLines::Left))
+        {
+            appendVerticalLines(r, p.s->font->gridLeft);
+        }
+        if (r.lines.test(GridLines::Right))
+        {
+            appendVerticalLines(r, p.s->font->gridRight);
+        }
+        if (r.lines.test(GridLines::Top))
+        {
+            appendHorizontalLine(r, p.s->font->gridTop, nullptr);
+        }
+        if (r.lines.test(GridLines::Bottom))
+        {
+            appendHorizontalLine(r, p.s->font->gridBottom, nullptr);
+        }
+
+        if (r.lines.test(GridLines::Underline))
+        {
+            appendHorizontalLine(r, p.s->font->underline, nullptr);
+        }
+        if (r.lines.test(GridLines::HyperlinkUnderline))
+        {
+            appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get());
+        }
+        if (r.lines.test(GridLines::DoubleUnderline))
+        {
+            for (const auto pos : p.s->font->doubleUnderline)
+            {
+                appendHorizontalLine(r, pos, nullptr);
+            }
+        }
+        if (r.lines.test(GridLines::Strikethrough))
+        {
+            appendHorizontalLine(r, p.s->font->strikethrough, nullptr);
+        }
+    }
+}
+
+void BackendD2D::_drawCursorPart1(const RenderingPayload& p)
+{
+    if (p.cursorRect.empty())
+    {
+        return;
+    }
+
+    const auto cursorColor = p.s->cursor->cursorColor;
+
+    if (cursorColor != 0xffffffff)
+    {
+        const D2D1_RECT_F rect{
+            static_cast<f32>(p.cursorRect.left * p.s->font->cellSize.x),
+            static_cast<f32>(p.cursorRect.top * p.s->font->cellSize.y),
+            static_cast<f32>(p.cursorRect.right * p.s->font->cellSize.x),
+            static_cast<f32>(p.cursorRect.bottom * p.s->font->cellSize.y),
+        };
+        const auto brush = _brushWithColor(cursorColor);
+        _drawCursor(p, _renderTarget.get(), rect, brush);
+    }
+}
+
+void BackendD2D::_drawCursorPart2(const RenderingPayload& p)
+{
+    if (p.cursorRect.empty())
+    {
+        return;
+    }
+
+    if (p.s->cursor->cursorColor == 0xffffffff)
+    {
+        const auto cursorSize = p.cursorRect.size();
+        if (cursorSize != _cursorBitmapSize)
+        {
+            _resizeCursorBitmap(p, cursorSize);
+        }
+
+        const D2D1_POINT_2F target{
+            static_cast<f32>(p.cursorRect.left * p.s->font->cellSize.x),
+            static_cast<f32>(p.cursorRect.top * p.s->font->cellSize.y),
+        };
+        _renderTarget->DrawImage(_cursorBitmap.get(), &target, nullptr, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_COMPOSITE_MODE_MASK_INVERT);
+    }
+}
+
+void BackendD2D::_resizeCursorBitmap(const RenderingPayload& p, const til::size newSize)
+{
+    const til::size newSizeInPx{
+        newSize.width * p.s->font->cellSize.x,
+        newSize.height * p.s->font->cellSize.y,
+    };
+
+    // CreateCompatibleRenderTarget is a terrific API and does not adopt _any_ of the settings of the
+    // parent render target (like the AA mode or D2D1_UNIT_MODE_PIXELS). Not sure who came up with that,
+    // but fact is that we need to set both sizes to override the DPI and fake D2D1_UNIT_MODE_PIXELS.
+    const D2D1_SIZE_F sizeF{ static_cast<f32>(newSizeInPx.width), static_cast<f32>(newSizeInPx.height) };
+    const D2D1_SIZE_U sizeU{ gsl::narrow_cast<UINT32>(newSizeInPx.width), gsl::narrow_cast<UINT32>(newSizeInPx.height) };
+    wil::com_ptr<ID2D1BitmapRenderTarget> cursorRenderTarget;
+    _renderTarget->CreateCompatibleRenderTarget(&sizeF, &sizeU, nullptr, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, cursorRenderTarget.addressof());
+    cursorRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
+
+    cursorRenderTarget->BeginDraw();
+    {
+        const D2D1_RECT_F rect{ 0, 0, sizeF.width, sizeF.height };
+        const auto brush = _brushWithColor(0xffffffff);
+        _drawCursor(p, cursorRenderTarget.get(), rect, brush);
+    }
+    THROW_IF_FAILED(cursorRenderTarget->EndDraw());
+
+    cursorRenderTarget->GetBitmap(_cursorBitmap.put());
+    _cursorBitmapSize = newSize;
+}
+
+void BackendD2D::_drawCursor(const RenderingPayload& p, ID2D1RenderTarget* renderTarget, D2D1_RECT_F rect, ID2D1Brush* brush) noexcept
+{
+    switch (static_cast<CursorType>(p.s->cursor->cursorType))
+    {
+    case CursorType::Legacy:
+    {
+        const auto height = p.s->cursor->heightPercentage / 100.0f;
+        rect.top = roundf((rect.top - rect.bottom) * height + rect.bottom);
+        renderTarget->FillRectangle(&rect, brush);
+        break;
+    }
+    case CursorType::VerticalBar:
+        rect.right = rect.left + p.s->font->thinLineWidth;
+        renderTarget->FillRectangle(&rect, brush);
+        break;
+    case CursorType::Underscore:
+        rect.top += p.s->font->underline.position;
+        rect.bottom = rect.top + p.s->font->underline.height;
+        renderTarget->FillRectangle(&rect, brush);
+        break;
+    case CursorType::EmptyBox:
+    {
+        const auto w = static_cast<f32>(p.s->font->thinLineWidth);
+        const auto wh = w / 2.0f;
+        rect.left += wh;
+        rect.top += wh;
+        rect.right -= wh;
+        rect.bottom -= wh;
+        renderTarget->DrawRectangle(&rect, brush, w, nullptr);
+        break;
+    }
+    case CursorType::FullBox:
+        renderTarget->FillRectangle(&rect, brush);
+        break;
+    case CursorType::DoubleUnderscore:
+    {
+        auto rect2 = rect;
+        rect2.top = rect.top + p.s->font->doubleUnderline[0].position;
+        rect2.bottom = rect2.top + p.s->font->thinLineWidth;
+        renderTarget->FillRectangle(&rect2, brush);
+        rect.top = rect.top + p.s->font->doubleUnderline[1].position;
+        rect.bottom = rect.top + p.s->font->thinLineWidth;
+        renderTarget->FillRectangle(&rect, brush);
+        break;
+    }
+    default:
+        break;
+    }
+}
+
+void BackendD2D::_drawSelection(const RenderingPayload& p)
+{
+    u16 y = 0;
+    for (const auto& row : p.rows)
+    {
+        if (row->selectionTo > row->selectionFrom)
+        {
+            const D2D1_RECT_F rect{
+                static_cast<f32>(p.s->font->cellSize.x * row->selectionFrom),
+                static_cast<f32>(p.s->font->cellSize.y * y),
+                static_cast<f32>(p.s->font->cellSize.x * row->selectionTo),
+                static_cast<f32>(p.s->font->cellSize.y * (y + 1)),
+            };
+            _fillRectangle(rect, p.s->misc->selectionColor);
+        }
+
+        y++;
+    }
+}
+
+#if ATLAS_DEBUG_SHOW_DIRTY
+void BackendD2D::_debugShowDirty(const RenderingPayload& p)
+{
+    _presentRects[_presentRectsPos] = p.dirtyRectInPx;
+    _presentRectsPos = (_presentRectsPos + 1) % std::size(_presentRects);
+
+    for (size_t i = 0; i < std::size(_presentRects); ++i)
+    {
+        if (const auto& rect = _presentRects[i])
+        {
+            const D2D1_RECT_F rectF{
+                static_cast<f32>(rect.left),
+                static_cast<f32>(rect.top),
+                static_cast<f32>(rect.right),
+                static_cast<f32>(rect.bottom),
+            };
+            const auto color = colorbrewer::pastel1[i] | 0x1f000000;
+            _fillRectangle(rectF, color);
+        }
+    }
+}
+#endif
+
+#if ATLAS_DEBUG_DUMP_RENDER_TARGET
+void BackendD2D::_debugDumpRenderTarget(const RenderingPayload& p)
+{
+    if (_dumpRenderTargetCounter == 0)
+    {
+        ExpandEnvironmentStringsW(ATLAS_DEBUG_DUMP_RENDER_TARGET_PATH, &_dumpRenderTargetBasePath[0], gsl::narrow_cast<DWORD>(std::size(_dumpRenderTargetBasePath)));
+        std::filesystem::create_directories(_dumpRenderTargetBasePath);
+    }
+
+    wchar_t path[MAX_PATH];
+    swprintf_s(path, L"%s\\%u_%08zu.png", &_dumpRenderTargetBasePath[0], GetCurrentProcessId(), _dumpRenderTargetCounter);
+    SaveTextureToPNG(_deviceContext.get(), _swapChainManager.GetBuffer().get(), p.s->font->dpi, &path[0]);
+    _dumpRenderTargetCounter++;
+}
+#endif
+
+ID2D1SolidColorBrush* BackendD2D::_brushWithColor(u32 color)
+{
+    if (_brushColor != color)
+    {
+        _brushWithColorUpdate(color);
+    }
+    return _brush.get();
+}
+
+ID2D1SolidColorBrush* BackendD2D::_brushWithColorUpdate(u32 color)
+{
+    const auto d2dColor = colorFromU32(color);
+    _brush->SetColor(&d2dColor);
+    _brushColor = color;
+    return _brush.get();
+}
+
+void BackendD2D::_fillRectangle(const D2D1_RECT_F& rect, u32 color)
+{
+    const auto brush = _brushWithColor(color);
+    _renderTarget->FillRectangle(&rect, brush);
+}
+
+TIL_FAST_MATH_END
diff --git a/src/renderer/atlas/BackendD2D.h b/src/renderer/atlas/BackendD2D.h
new file mode 100644
index 00000000000..a68dcd52619
--- /dev/null
+++ b/src/renderer/atlas/BackendD2D.h
@@ -0,0 +1,68 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+#include <til/flat_set.h>
+
+#include "Backend.h"
+
+namespace Microsoft::Console::Render::Atlas
+{
+    struct BackendD2D : IBackend
+    {
+        void ReleaseResources() noexcept override;
+        void Render(RenderingPayload& payload) override;
+        bool RequiresContinuousRedraw() noexcept override;
+
+    private:
+        ATLAS_ATTR_COLD void _handleSettingsUpdate(const RenderingPayload& p);
+        void _drawBackground(const RenderingPayload& p) noexcept;
+        void _drawText(RenderingPayload& p);
+        ATLAS_ATTR_COLD f32 _drawTextPrepareLineRendition(const RenderingPayload& p, const ShapedRow* row, f32 baselineY) const noexcept;
+        ATLAS_ATTR_COLD void _drawTextResetLineRendition(const ShapedRow* row) const noexcept;
+        ATLAS_ATTR_COLD f32r _getGlyphRunDesignBounds(const DWRITE_GLYPH_RUN& glyphRun, f32 baselineX, f32 baselineY);
+        ATLAS_ATTR_COLD void _drawGridlineRow(const RenderingPayload& p, const ShapedRow* row, u16 y);
+        void _drawCursorPart1(const RenderingPayload& p);
+        void _drawCursorPart2(const RenderingPayload& p);
+        static void _drawCursor(const RenderingPayload& p, ID2D1RenderTarget* renderTarget, D2D1_RECT_F rect, ID2D1Brush* brush) noexcept;
+        void _resizeCursorBitmap(const RenderingPayload& p, til::size newSize);
+        void _drawSelection(const RenderingPayload& p);
+        void _debugShowDirty(const RenderingPayload& p);
+        void _debugDumpRenderTarget(const RenderingPayload& p);
+        ID2D1SolidColorBrush* _brushWithColor(u32 color);
+        ATLAS_ATTR_COLD ID2D1SolidColorBrush* _brushWithColorUpdate(u32 color);
+        void _fillRectangle(const D2D1_RECT_F& rect, u32 color);
+
+        wil::com_ptr<ID2D1DeviceContext> _renderTarget;
+        wil::com_ptr<ID2D1DeviceContext4> _renderTarget4; // Optional. Supported since Windows 10 14393.
+        wil::com_ptr<ID2D1StrokeStyle> _dottedStrokeStyle;
+        wil::com_ptr<ID2D1Bitmap> _backgroundBitmap;
+        wil::com_ptr<ID2D1BitmapBrush> _backgroundBrush;
+        til::generation_t _backgroundBitmapGeneration;
+
+        wil::com_ptr<ID2D1Bitmap> _cursorBitmap;
+        til::size _cursorBitmapSize; // in columns/rows
+
+        wil::com_ptr<ID2D1SolidColorBrush> _emojiBrush;
+        wil::com_ptr<ID2D1SolidColorBrush> _brush;
+        u32 _brushColor = 0;
+
+        Buffer<DWRITE_GLYPH_METRICS> _glyphMetrics;
+
+        til::generation_t _generation;
+        til::generation_t _fontGeneration;
+        til::generation_t _cursorGeneration;
+        u16x2 _cellCount{};
+
+#if ATLAS_DEBUG_SHOW_DIRTY
+        til::rect _presentRects[9]{};
+        size_t _presentRectsPos = 0;
+#endif
+
+#if ATLAS_DEBUG_DUMP_RENDER_TARGET
+        wchar_t _dumpRenderTargetBasePath[MAX_PATH]{};
+        size_t _dumpRenderTargetCounter = 0;
+#endif
+    };
+}
diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp
new file mode 100644
index 00000000000..9ecf9beac7e
--- /dev/null
+++ b/src/renderer/atlas/BackendD3D.cpp
@@ -0,0 +1,2065 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+#include "BackendD3D.h"
+
+#include <custom_shader_ps.h>
+#include <custom_shader_vs.h>
+#include <shader_ps.h>
+#include <shader_vs.h>
+
+#include "dwrite.h"
+
+#if ATLAS_DEBUG_SHOW_DIRTY || ATLAS_DEBUG_COLORIZE_GLYPH_ATLAS
+#include "colorbrewer.h"
+#endif
+
+#if ATLAS_DEBUG_DUMP_RENDER_TARGET
+#include "wic.h"
+#endif
+
+TIL_FAST_MATH_BEGIN
+
+// This code packs various data into smaller-than-int types to save both CPU and GPU memory. This warning would force
+// us to add dozens upon dozens of gsl::narrow_cast<>s throughout the file which is more annoying than helpful.
+#pragma warning(disable : 4242) // '=': conversion from '...' to '...', possible loss of data
+#pragma warning(disable : 4244) // 'initializing': conversion from '...' to '...', possible loss of data
+#pragma warning(disable : 4267) // 'argument': conversion from '...' to '...', possible loss of data
+#pragma warning(disable : 4838) // conversion from '...' to '...' requires a narrowing conversion
+#pragma warning(disable : 26472) // Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1).
+// Disable a bunch of warnings which get in the way of writing performant code.
+#pragma warning(disable : 26429) // Symbol 'data' is never tested for nullness, it can be marked as not_null (f.23).
+#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
+#pragma warning(disable : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe [...].
+#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
+#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
+
+using namespace Microsoft::Console::Render::Atlas;
+
+template<>
+struct ::std::hash<BackendD3D::AtlasGlyphEntry>
+{
+    constexpr size_t operator()(u16 key) const noexcept
+    {
+        return til::flat_set_hash_integer(key);
+    }
+
+    constexpr size_t operator()(const BackendD3D::AtlasGlyphEntry& slot) const noexcept
+    {
+        return til::flat_set_hash_integer(slot.glyphIndex);
+    }
+};
+
+template<>
+struct ::std::hash<BackendD3D::AtlasFontFaceEntry>
+{
+    using T = BackendD3D::AtlasFontFaceEntry;
+
+    size_t operator()(const BackendD3D::AtlasFontFaceKey& key) const noexcept
+    {
+        return til::flat_set_hash_integer(std::bit_cast<uintptr_t>(key.fontFace) | static_cast<u8>(key.lineRendition));
+    }
+
+    size_t operator()(const BackendD3D::AtlasFontFaceEntry& slot) const noexcept
+    {
+        const auto& inner = *slot.inner;
+        return til::flat_set_hash_integer(std::bit_cast<uintptr_t>(inner.fontFace.get()) | static_cast<u8>(inner.lineRendition));
+    }
+};
+
+BackendD3D::BackendD3D(const RenderingPayload& p)
+{
+    THROW_IF_FAILED(p.device->CreateVertexShader(&shader_vs[0], sizeof(shader_vs), nullptr, _vertexShader.addressof()));
+    THROW_IF_FAILED(p.device->CreatePixelShader(&shader_ps[0], sizeof(shader_ps), nullptr, _pixelShader.addressof()));
+
+    {
+        static constexpr D3D11_INPUT_ELEMENT_DESC layout[]{
+            { "SV_Position", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+            { "shadingType", 0, DXGI_FORMAT_R32_UINT, 1, offsetof(QuadInstance, shadingType), D3D11_INPUT_PER_INSTANCE_DATA, 1 },
+            { "position", 0, DXGI_FORMAT_R16G16_SINT, 1, offsetof(QuadInstance, position), D3D11_INPUT_PER_INSTANCE_DATA, 1 },
+            { "size", 0, DXGI_FORMAT_R16G16_UINT, 1, offsetof(QuadInstance, size), D3D11_INPUT_PER_INSTANCE_DATA, 1 },
+            { "texcoord", 0, DXGI_FORMAT_R16G16_UINT, 1, offsetof(QuadInstance, texcoord), D3D11_INPUT_PER_INSTANCE_DATA, 1 },
+            { "color", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 1, offsetof(QuadInstance, color), D3D11_INPUT_PER_INSTANCE_DATA, 1 },
+        };
+        THROW_IF_FAILED(p.device->CreateInputLayout(&layout[0], std::size(layout), &shader_vs[0], sizeof(shader_vs), _inputLayout.addressof()));
+    }
+
+    {
+        static constexpr f32x2 vertices[]{
+            { 0, 0 },
+            { 1, 0 },
+            { 1, 1 },
+            { 0, 1 },
+        };
+        static constexpr D3D11_SUBRESOURCE_DATA initialData{ &vertices[0] };
+
+        D3D11_BUFFER_DESC desc{};
+        desc.ByteWidth = sizeof(vertices);
+        desc.Usage = D3D11_USAGE_IMMUTABLE;
+        desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
+        THROW_IF_FAILED(p.device->CreateBuffer(&desc, &initialData, _vertexBuffer.addressof()));
+    }
+
+    {
+        static constexpr u16 indices[]{
+            0, // { 0, 0 }
+            1, // { 1, 0 }
+            2, // { 1, 1 }
+            2, // { 1, 1 }
+            3, // { 0, 1 }
+            0, // { 0, 0 }
+        };
+        static constexpr D3D11_SUBRESOURCE_DATA initialData{ &indices[0] };
+
+        D3D11_BUFFER_DESC desc{};
+        desc.ByteWidth = sizeof(indices);
+        desc.Usage = D3D11_USAGE_IMMUTABLE;
+        desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
+        THROW_IF_FAILED(p.device->CreateBuffer(&desc, &initialData, _indexBuffer.addressof()));
+    }
+
+    {
+        static constexpr D3D11_BUFFER_DESC desc{
+            .ByteWidth = sizeof(VSConstBuffer),
+            .Usage = D3D11_USAGE_DEFAULT,
+            .BindFlags = D3D11_BIND_CONSTANT_BUFFER,
+        };
+        THROW_IF_FAILED(p.device->CreateBuffer(&desc, nullptr, _vsConstantBuffer.addressof()));
+    }
+
+    {
+        static constexpr D3D11_BUFFER_DESC desc{
+            .ByteWidth = sizeof(PSConstBuffer),
+            .Usage = D3D11_USAGE_DEFAULT,
+            .BindFlags = D3D11_BIND_CONSTANT_BUFFER,
+        };
+        THROW_IF_FAILED(p.device->CreateBuffer(&desc, nullptr, _psConstantBuffer.addressof()));
+    }
+
+    {
+        // The final step of the ClearType blending algorithm is a lerp() between the premultiplied alpha
+        // background color and straight alpha foreground color given the 3 RGB weights in alphaCorrected:
+        //   lerp(background, foreground, weights)
+        // Which is equivalent to:
+        //   background * (1 - weights) + foreground * weights
+        //
+        // This COULD be implemented using dual source color blending like so:
+        //   .SrcBlend = D3D11_BLEND_SRC1_COLOR
+        //   .DestBlend = D3D11_BLEND_INV_SRC1_COLOR
+        //   .BlendOp = D3D11_BLEND_OP_ADD
+        // Because:
+        //   background * (1 - weights) + foreground * weights
+        //       ^             ^        ^     ^           ^
+        //      Dest     INV_SRC1_COLOR |    Src      SRC1_COLOR
+        //                            OP_ADD
+        //
+        // BUT we need simultaneous support for regular "source over" alpha blending
+        // (SHADING_TYPE_PASSTHROUGH)  like this:
+        //   background * (1 - alpha) + foreground
+        //
+        // This is why we set:
+        //   .SrcBlend = D3D11_BLEND_ONE
+        //
+        // --> We need to multiply the foreground with the weights ourselves.
+        static constexpr D3D11_BLEND_DESC desc{
+            .RenderTarget = { {
+                .BlendEnable = TRUE,
+                .SrcBlend = D3D11_BLEND_ONE,
+                .DestBlend = D3D11_BLEND_INV_SRC1_COLOR,
+                .BlendOp = D3D11_BLEND_OP_ADD,
+                .SrcBlendAlpha = D3D11_BLEND_ONE,
+                .DestBlendAlpha = D3D11_BLEND_INV_SRC1_ALPHA,
+                .BlendOpAlpha = D3D11_BLEND_OP_ADD,
+                .RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL,
+            } },
+        };
+        THROW_IF_FAILED(p.device->CreateBlendState(&desc, _blendState.addressof()));
+    }
+
+#ifndef NDEBUG
+    _sourceDirectory = std::filesystem::path{ __FILE__ }.parent_path();
+    _sourceCodeWatcher = wil::make_folder_change_reader_nothrow(_sourceDirectory.c_str(), false, wil::FolderChangeEvents::FileName | wil::FolderChangeEvents::LastWriteTime, [this](wil::FolderChangeEvent, PCWSTR path) {
+        if (til::ends_with(path, L".hlsl"))
+        {
+            auto expected = INT64_MAX;
+            const auto invalidationTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
+            _sourceCodeInvalidationTime.compare_exchange_strong(expected, invalidationTime.time_since_epoch().count(), std::memory_order_relaxed);
+        }
+    });
+#endif
+}
+
+void BackendD3D::ReleaseResources() noexcept
+{
+    _renderTargetView.reset();
+    _customRenderTargetView.reset();
+    // Ensure _handleSettingsUpdate() is called so that _renderTarget gets recreated.
+    _generation = {};
+}
+
+void BackendD3D::Render(RenderingPayload& p)
+{
+    if (_generation != p.s.generation())
+    {
+        _handleSettingsUpdate(p);
+    }
+
+#ifndef NDEBUG
+    _debugUpdateShaders(p);
+#endif
+
+    // After a Present() the render target becomes unbound.
+    p.deviceContext->OMSetRenderTargets(1, _customRenderTargetView ? _customRenderTargetView.addressof() : _renderTargetView.addressof(), nullptr);
+
+    // Invalidating the render target helps with spotting invalid quad instances and Present1() bugs.
+#if ATLAS_DEBUG_SHOW_DIRTY || ATLAS_DEBUG_DUMP_RENDER_TARGET
+    {
+        static constexpr f32 clearColor[4]{};
+        p.deviceContext->ClearView(_renderTargetView.get(), &clearColor[0], nullptr, 0);
+    }
+#endif
+
+    _drawBackground(p);
+    _drawCursorBackground(p);
+    _drawText(p);
+    _drawSelection(p);
+#if ATLAS_DEBUG_SHOW_DIRTY
+    _debugShowDirty(p);
+#endif
+    _flushQuads(p);
+
+    if (_customPixelShader)
+    {
+        _executeCustomShader(p);
+    }
+
+#if ATLAS_DEBUG_DUMP_RENDER_TARGET
+    _debugDumpRenderTarget(p);
+#endif
+}
+
+bool BackendD3D::RequiresContinuousRedraw() noexcept
+{
+    return _requiresContinuousRedraw;
+}
+
+void BackendD3D::_handleSettingsUpdate(const RenderingPayload& p)
+{
+    if (!_renderTargetView)
+    {
+        wil::com_ptr<ID3D11Texture2D> buffer;
+        THROW_IF_FAILED(p.swapChain.swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(buffer.addressof())));
+        THROW_IF_FAILED(p.device->CreateRenderTargetView(buffer.get(), nullptr, _renderTargetView.put()));
+    }
+
+    const auto fontChanged = _fontGeneration != p.s->font.generation();
+    const auto miscChanged = _miscGeneration != p.s->misc.generation();
+    const auto cellCountChanged = _cellCount != p.s->cellCount;
+
+    if (fontChanged)
+    {
+        _updateFontDependents(p);
+    }
+    if (miscChanged)
+    {
+        _recreateCustomShader(p);
+    }
+    if (cellCountChanged)
+    {
+        _recreateBackgroundColorBitmap(p);
+    }
+
+    // Similar to _renderTargetView above, we might have to recreate the _customRenderTargetView whenever _swapChainManager
+    // resets it. We only do it after calling _recreateCustomShader however, since that sets the _customPixelShader.
+    if (_customPixelShader && !_customRenderTargetView)
+    {
+        _recreateCustomRenderTargetView(p);
+    }
+
+    _recreateConstBuffer(p);
+    _setupDeviceContextState(p);
+
+    _generation = p.s.generation();
+    _fontGeneration = p.s->font.generation();
+    _miscGeneration = p.s->misc.generation();
+    _targetSize = p.s->targetSize;
+    _cellCount = p.s->cellCount;
+}
+
+void BackendD3D::_updateFontDependents(const RenderingPayload& p)
+{
+    const auto& font = *p.s->font;
+
+    DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put());
+    // Clearing the atlas requires BeginDraw(), which is expensive. Defer this until we need Direct2D anyways.
+    _fontChangedResetGlyphAtlas = true;
+    _textShadingType = font.antialiasingMode == AntialiasingMode::ClearType ? ShadingType::TextClearType : ShadingType::TextGrayscale;
+
+    {
+        auto ligaturesDisabled = false;
+        for (const auto& feature : font.fontFeatures)
+        {
+            if (feature.nameTag == DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES)
+            {
+                ligaturesDisabled = !feature.parameter;
+                break;
+            }
+        }
+
+        if (ligaturesDisabled)
+        {
+            _ligatureOverhangTriggerLeft = til::CoordTypeMin;
+            _ligatureOverhangTriggerRight = til::CoordTypeMax;
+        }
+        else
+        {
+            const auto halfCellWidth = font.cellSize.x / 2;
+            _ligatureOverhangTriggerLeft = -halfCellWidth;
+            _ligatureOverhangTriggerRight = font.advanceWidth + halfCellWidth;
+        }
+    }
+
+    if (_d2dRenderTarget)
+    {
+        _d2dRenderTargetUpdateFontSettings(p);
+    }
+
+    _softFontBitmap.reset();
+}
+
+void BackendD3D::_d2dRenderTargetUpdateFontSettings(const RenderingPayload& p) const noexcept
+{
+    const auto& font = *p.s->font;
+    _d2dRenderTarget->SetDpi(font.dpi, font.dpi);
+    _d2dRenderTarget->SetTextAntialiasMode(static_cast<D2D1_TEXT_ANTIALIAS_MODE>(font.antialiasingMode));
+}
+
+void BackendD3D::_recreateCustomShader(const RenderingPayload& p)
+{
+    _customRenderTargetView.reset();
+    _customOffscreenTexture.reset();
+    _customOffscreenTextureView.reset();
+    _customVertexShader.reset();
+    _customPixelShader.reset();
+    _customShaderConstantBuffer.reset();
+    _customShaderSamplerState.reset();
+    _requiresContinuousRedraw = false;
+
+    if (!p.s->misc->customPixelShaderPath.empty())
+    {
+        const char* target = nullptr;
+        switch (p.device->GetFeatureLevel())
+        {
+        case D3D_FEATURE_LEVEL_10_0:
+            target = "ps_4_0";
+            break;
+        case D3D_FEATURE_LEVEL_10_1:
+            target = "ps_4_1";
+            break;
+        default:
+            target = "ps_5_0";
+            break;
+        }
+
+        static constexpr auto flags =
+            D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR
+#ifdef NDEBUG
+            | D3DCOMPILE_OPTIMIZATION_LEVEL3;
+#else
+            // Only enable strictness and warnings in DEBUG mode
+            //  as these settings makes it very difficult to develop
+            //  shaders as windows terminal is not telling the user
+            //  what's wrong, windows terminal just fails.
+            //  Keep it in DEBUG mode to catch errors in shaders
+            //  shipped with windows terminal
+            | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS | D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
+#endif
+
+        wil::com_ptr<ID3DBlob> error;
+        wil::com_ptr<ID3DBlob> blob;
+        const auto hr = D3DCompileFromFile(
+            /* pFileName   */ p.s->misc->customPixelShaderPath.c_str(),
+            /* pDefines    */ nullptr,
+            /* pInclude    */ D3D_COMPILE_STANDARD_FILE_INCLUDE,
+            /* pEntrypoint */ "main",
+            /* pTarget     */ target,
+            /* Flags1      */ flags,
+            /* Flags2      */ 0,
+            /* ppCode      */ blob.addressof(),
+            /* ppErrorMsgs */ error.addressof());
+
+        // Unless we can determine otherwise, assume this shader requires evaluation every frame
+        _requiresContinuousRedraw = true;
+
+        if (SUCCEEDED(hr))
+        {
+            THROW_IF_FAILED(p.device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _customPixelShader.put()));
+
+            // Try to determine whether the shader uses the Time variable
+            wil::com_ptr<ID3D11ShaderReflection> reflector;
+            if (SUCCEEDED_LOG(D3DReflect(blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(reflector.put()))))
+            {
+                if (ID3D11ShaderReflectionConstantBuffer* constantBufferReflector = reflector->GetConstantBufferByIndex(0)) // shader buffer
+                {
+                    if (ID3D11ShaderReflectionVariable* variableReflector = constantBufferReflector->GetVariableByIndex(0)) // time
+                    {
+                        D3D11_SHADER_VARIABLE_DESC variableDescriptor;
+                        if (SUCCEEDED_LOG(variableReflector->GetDesc(&variableDescriptor)))
+                        {
+                            // only if time is used
+                            _requiresContinuousRedraw = WI_IsFlagSet(variableDescriptor.uFlags, D3D_SVF_USED);
+                        }
+                    }
+                }
+            }
+        }
+        else
+        {
+            if (error)
+            {
+                LOG_HR_MSG(hr, "%*hs", error->GetBufferSize(), error->GetBufferPointer());
+            }
+            else
+            {
+                LOG_HR(hr);
+            }
+            if (p.warningCallback)
+            {
+                p.warningCallback(D2DERR_SHADER_COMPILE_FAILED);
+            }
+        }
+    }
+    else if (p.s->misc->useRetroTerminalEffect)
+    {
+        THROW_IF_FAILED(p.device->CreatePixelShader(&custom_shader_ps[0], sizeof(custom_shader_ps), nullptr, _customPixelShader.put()));
+        // We know the built-in retro shader doesn't require continuous redraw.
+        _requiresContinuousRedraw = false;
+    }
+
+    if (_customPixelShader)
+    {
+        THROW_IF_FAILED(p.device->CreateVertexShader(&custom_shader_vs[0], sizeof(custom_shader_vs), nullptr, _customVertexShader.put()));
+
+        {
+            static constexpr D3D11_BUFFER_DESC desc{
+                .ByteWidth = sizeof(CustomConstBuffer),
+                .Usage = D3D11_USAGE_DYNAMIC,
+                .BindFlags = D3D11_BIND_CONSTANT_BUFFER,
+                .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE,
+            };
+            THROW_IF_FAILED(p.device->CreateBuffer(&desc, nullptr, _customShaderConstantBuffer.put()));
+        }
+
+        {
+            static constexpr D3D11_SAMPLER_DESC desc{
+                .Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR,
+                .AddressU = D3D11_TEXTURE_ADDRESS_BORDER,
+                .AddressV = D3D11_TEXTURE_ADDRESS_BORDER,
+                .AddressW = D3D11_TEXTURE_ADDRESS_BORDER,
+                .MaxAnisotropy = 1,
+                .ComparisonFunc = D3D11_COMPARISON_ALWAYS,
+                .MaxLOD = D3D11_FLOAT32_MAX,
+            };
+            THROW_IF_FAILED(p.device->CreateSamplerState(&desc, _customShaderSamplerState.put()));
+        }
+
+        _customShaderStartTime = std::chrono::steady_clock::now();
+    }
+}
+
+void BackendD3D::_recreateCustomRenderTargetView(const RenderingPayload& p)
+{
+    // Avoid memory usage spikes by releasing memory first.
+    _customOffscreenTexture.reset();
+    _customOffscreenTextureView.reset();
+
+    const D3D11_TEXTURE2D_DESC desc{
+        .Width = p.s->targetSize.x,
+        .Height = p.s->targetSize.y,
+        .MipLevels = 1,
+        .ArraySize = 1,
+        .Format = DXGI_FORMAT_B8G8R8A8_UNORM,
+        .SampleDesc = { 1, 0 },
+        .BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET,
+    };
+    THROW_IF_FAILED(p.device->CreateTexture2D(&desc, nullptr, _customOffscreenTexture.addressof()));
+    THROW_IF_FAILED(p.device->CreateShaderResourceView(_customOffscreenTexture.get(), nullptr, _customOffscreenTextureView.addressof()));
+    THROW_IF_FAILED(p.device->CreateRenderTargetView(_customOffscreenTexture.get(), nullptr, _customRenderTargetView.addressof()));
+}
+
+void BackendD3D::_recreateBackgroundColorBitmap(const RenderingPayload& p)
+{
+    // Avoid memory usage spikes by releasing memory first.
+    _backgroundBitmap.reset();
+    _backgroundBitmapView.reset();
+
+    const D3D11_TEXTURE2D_DESC desc{
+        .Width = p.s->cellCount.x,
+        .Height = p.s->cellCount.y,
+        .MipLevels = 1,
+        .ArraySize = 1,
+        .Format = DXGI_FORMAT_R8G8B8A8_UNORM,
+        .SampleDesc = { 1, 0 },
+        .Usage = D3D11_USAGE_DYNAMIC,
+        .BindFlags = D3D11_BIND_SHADER_RESOURCE,
+        .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE,
+    };
+    THROW_IF_FAILED(p.device->CreateTexture2D(&desc, nullptr, _backgroundBitmap.addressof()));
+    THROW_IF_FAILED(p.device->CreateShaderResourceView(_backgroundBitmap.get(), nullptr, _backgroundBitmapView.addressof()));
+    _backgroundBitmapGeneration = {};
+}
+
+void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const
+{
+    {
+        VSConstBuffer data{};
+        data.positionScale = { 2.0f / p.s->targetSize.x, -2.0f / p.s->targetSize.y };
+        p.deviceContext->UpdateSubresource(_vsConstantBuffer.get(), 0, nullptr, &data, 0, 0);
+    }
+    {
+        PSConstBuffer data{};
+        data.backgroundColor = colorFromU32Premultiply<f32x4>(p.s->misc->backgroundColor);
+        data.cellSize = { static_cast<f32>(p.s->font->cellSize.x), static_cast<f32>(p.s->font->cellSize.y) };
+        data.cellCount = { static_cast<f32>(p.s->cellCount.x), static_cast<f32>(p.s->cellCount.y) };
+        DWrite_GetGammaRatios(_gamma, data.gammaRatios);
+        data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast;
+        data.underlineWidth = p.s->font->underline.height;
+        p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0);
+    }
+}
+
+void BackendD3D::_setupDeviceContextState(const RenderingPayload& p)
+{
+    // IA: Input Assembler
+    ID3D11Buffer* vertexBuffers[]{ _vertexBuffer.get(), _instanceBuffer.get() };
+    static constexpr UINT strides[]{ sizeof(f32x2), sizeof(QuadInstance) };
+    static constexpr UINT offsets[]{ 0, 0 };
+    p.deviceContext->IASetIndexBuffer(_indexBuffer.get(), DXGI_FORMAT_R16_UINT, 0);
+    p.deviceContext->IASetInputLayout(_inputLayout.get());
+    p.deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+    p.deviceContext->IASetVertexBuffers(0, 2, &vertexBuffers[0], &strides[0], &offsets[0]);
+
+    // VS: Vertex Shader
+    p.deviceContext->VSSetShader(_vertexShader.get(), nullptr, 0);
+    p.deviceContext->VSSetConstantBuffers(0, 1, _vsConstantBuffer.addressof());
+
+    // RS: Rasterizer Stage
+    D3D11_VIEWPORT viewport{};
+    viewport.Width = static_cast<f32>(p.s->targetSize.x);
+    viewport.Height = static_cast<f32>(p.s->targetSize.y);
+    p.deviceContext->RSSetViewports(1, &viewport);
+
+    // PS: Pixel Shader
+    ID3D11ShaderResourceView* resources[]{ _backgroundBitmapView.get(), _glyphAtlasView.get() };
+    p.deviceContext->PSSetShader(_pixelShader.get(), nullptr, 0);
+    p.deviceContext->PSSetConstantBuffers(0, 1, _psConstantBuffer.addressof());
+    p.deviceContext->PSSetShaderResources(0, 2, &resources[0]);
+
+    // OM: Output Merger
+    p.deviceContext->OMSetBlendState(_blendState.get(), nullptr, 0xffffffff);
+    p.deviceContext->OMSetRenderTargets(1, _customRenderTargetView ? _customRenderTargetView.addressof() : _renderTargetView.addressof(), nullptr);
+}
+
+#ifndef NDEBUG
+void BackendD3D::_debugUpdateShaders(const RenderingPayload& p) noexcept
+try
+{
+    const auto invalidationTime = _sourceCodeInvalidationTime.load(std::memory_order_relaxed);
+
+    if (invalidationTime == INT64_MAX || invalidationTime > std::chrono::steady_clock::now().time_since_epoch().count())
+    {
+        return;
+    }
+
+    _sourceCodeInvalidationTime.store(INT64_MAX, std::memory_order_relaxed);
+
+    static const auto compile = [](const std::filesystem::path& path, const char* target) {
+        wil::com_ptr<ID3DBlob> error;
+        wil::com_ptr<ID3DBlob> blob;
+        const auto hr = D3DCompileFromFile(
+            /* pFileName   */ path.c_str(),
+            /* pDefines    */ nullptr,
+            /* pInclude    */ D3D_COMPILE_STANDARD_FILE_INCLUDE,
+            /* pEntrypoint */ "main",
+            /* pTarget     */ target,
+            /* Flags1      */ D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS,
+            /* Flags2      */ 0,
+            /* ppCode      */ blob.addressof(),
+            /* ppErrorMsgs */ error.addressof());
+
+        if (error)
+        {
+            std::thread t{ [error = std::move(error)]() noexcept {
+                MessageBoxA(nullptr, static_cast<const char*>(error->GetBufferPointer()), "Compilation error", MB_ICONERROR | MB_OK);
+            } };
+            t.detach();
+        }
+
+        THROW_IF_FAILED(hr);
+        return blob;
+    };
+
+    struct FileVS
+    {
+        std::wstring_view filename;
+        wil::com_ptr<ID3D11VertexShader> BackendD3D::*target;
+    };
+    struct FilePS
+    {
+        std::wstring_view filename;
+        wil::com_ptr<ID3D11PixelShader> BackendD3D::*target;
+    };
+
+    static constexpr std::array filesVS{
+        FileVS{ L"shader_vs.hlsl", &BackendD3D::_vertexShader },
+    };
+    static constexpr std::array filesPS{
+        FilePS{ L"shader_ps.hlsl", &BackendD3D::_pixelShader },
+    };
+
+    std::array<wil::com_ptr<ID3D11VertexShader>, filesVS.size()> compiledVS;
+    std::array<wil::com_ptr<ID3D11PixelShader>, filesPS.size()> compiledPS;
+
+    // Compile our files before moving them into `this` below to ensure we're
+    // always in a consistent state where all shaders are seemingly valid.
+    for (size_t i = 0; i < filesVS.size(); ++i)
+    {
+        const auto blob = compile(_sourceDirectory / filesVS[i].filename, "vs_4_0");
+        THROW_IF_FAILED(p.device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, compiledVS[i].addressof()));
+    }
+    for (size_t i = 0; i < filesPS.size(); ++i)
+    {
+        const auto blob = compile(_sourceDirectory / filesPS[i].filename, "ps_4_0");
+        THROW_IF_FAILED(p.device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, compiledPS[i].addressof()));
+    }
+
+    for (size_t i = 0; i < filesVS.size(); ++i)
+    {
+        this->*filesVS[i].target = std::move(compiledVS[i]);
+    }
+    for (size_t i = 0; i < filesPS.size(); ++i)
+    {
+        this->*filesPS[i].target = std::move(compiledPS[i]);
+    }
+
+    _setupDeviceContextState(p);
+}
+CATCH_LOG()
+#endif
+
+void BackendD3D::_d2dBeginDrawing() noexcept
+{
+    if (!_d2dBeganDrawing)
+    {
+        _d2dRenderTarget->BeginDraw();
+        _d2dBeganDrawing = true;
+    }
+}
+
+void BackendD3D::_d2dEndDrawing()
+{
+    if (_d2dBeganDrawing)
+    {
+        THROW_IF_FAILED(_d2dRenderTarget->EndDraw());
+        _d2dBeganDrawing = false;
+    }
+}
+
+void BackendD3D::_resetGlyphAtlas(const RenderingPayload& p)
+{
+    // The index returned by _BitScanReverse is undefined when the input is 0. We can simultaneously guard
+    // against that and avoid unreasonably small textures, by clamping the min. texture size to `minArea`.
+    // `minArea` results in a 64kB RGBA texture which is the min. alignment for placed memory.
+    static constexpr u32 minArea = 128 * 128;
+    static constexpr u32 maxArea = D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION * D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION;
+
+    const auto cellArea = static_cast<u32>(p.s->font->cellSize.x) * p.s->font->cellSize.y;
+    const auto targetArea = static_cast<u32>(p.s->targetSize.x) * p.s->targetSize.y;
+
+    const auto minAreaByFont = cellArea * 95; // Covers all printable ASCII characters
+    const auto minAreaByGrowth = static_cast<u32>(_rectPacker.width) * _rectPacker.height * 2;
+    const auto min = std::max(minArea, std::max(minAreaByFont, minAreaByGrowth));
+
+    // It's hard to say what the max. size of the cache should be. Optimally I think we should use as much
+    // memory as is available, but the rendering code in this project is a big mess and so integrating
+    // memory pressure feedback (RegisterVideoMemoryBudgetChangeNotificationEvent) is rather difficult.
+    // As an alternative I'm using 1.25x the size of the swap chain. The 1.25x is there to avoid situations, where
+    // we're locked into a state, where on every render pass we're starting with a half full atlas, drawing once,
+    // filling it with the remaining half and drawing again, requiring two rendering passes on each frame.
+    const auto maxAreaByFont = targetArea + targetArea / 4;
+    const auto area = std::min(maxArea, std::min(maxAreaByFont, min));
+
+    // This block of code calculates the size of a power-of-2 texture that has an area larger than the given `area`.
+    // For instance, for an area of 985x1946 = 1916810 it would result in a u/v of 2048x1024 (area = 2097152).
+    // This has 2 benefits: GPUs like power-of-2 textures and it ensures that we don't resize the texture
+    // every time you resize the window by a pixel. Instead it only grows/shrinks by a factor of 2.
+    unsigned long index;
+    _BitScanReverse(&index, area - 1);
+    const auto u = static_cast<u16>(1u << ((index + 2) / 2));
+    const auto v = static_cast<u16>(1u << ((index + 1) / 2));
+
+    if (u != _rectPacker.width || v != _rectPacker.height)
+    {
+        _resizeGlyphAtlas(p, u, v);
+    }
+
+    stbrp_init_target(&_rectPacker, u, v, _rectPackerData.data(), _rectPackerData.size());
+
+    for (auto& slot : _glyphAtlasMap.container())
+    {
+        slot.inner.reset();
+    }
+
+    _d2dBeginDrawing();
+    _d2dRenderTarget->Clear();
+
+    _fontChangedResetGlyphAtlas = false;
+}
+
+void BackendD3D::_resizeGlyphAtlas(const RenderingPayload& p, const u16 u, const u16 v)
+{
+    _d2dRenderTarget.reset();
+    _d2dRenderTarget4.reset();
+    _glyphAtlas.reset();
+    _glyphAtlasView.reset();
+
+    {
+        const D3D11_TEXTURE2D_DESC desc{
+            .Width = u,
+            .Height = v,
+            .MipLevels = 1,
+            .ArraySize = 1,
+            .Format = DXGI_FORMAT_B8G8R8A8_UNORM,
+            .SampleDesc = { 1, 0 },
+            .BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET,
+        };
+        THROW_IF_FAILED(p.device->CreateTexture2D(&desc, nullptr, _glyphAtlas.addressof()));
+        THROW_IF_FAILED(p.device->CreateShaderResourceView(_glyphAtlas.get(), nullptr, _glyphAtlasView.addressof()));
+    }
+
+    {
+        const auto surface = _glyphAtlas.query<IDXGISurface>();
+
+        static constexpr D2D1_RENDER_TARGET_PROPERTIES props{
+            .type = D2D1_RENDER_TARGET_TYPE_DEFAULT,
+            .pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED },
+        };
+        // ID2D1RenderTarget and ID2D1DeviceContext are the same and I'm tired of pretending they're not.
+        THROW_IF_FAILED(p.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, reinterpret_cast<ID2D1RenderTarget**>(_d2dRenderTarget.addressof())));
+        _d2dRenderTarget.try_query_to(_d2dRenderTarget4.addressof());
+
+        _d2dRenderTarget->SetUnitMode(D2D1_UNIT_MODE_PIXELS);
+        // We don't really use D2D for anything except DWrite, but it
+        // can't hurt to ensure that everything it does is pixel aligned.
+        _d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
+        // Ensure that D2D uses the exact same gamma as our shader uses.
+        _d2dRenderTarget->SetTextRenderingParams(_textRenderingParams.get());
+
+        _d2dRenderTargetUpdateFontSettings(p);
+    }
+
+    // We have our own glyph cache so Direct2D's cache doesn't help much.
+    // This saves us 1MB of RAM, which is not much, but also not nothing.
+    {
+        wil::com_ptr<ID2D1Device> device;
+        _d2dRenderTarget4->GetDevice(device.addressof());
+
+        device->SetMaximumTextureMemory(0);
+        if (const auto device4 = device.try_query<ID2D1Device4>())
+        {
+            device4->SetMaximumColorGlyphCacheMemory(0);
+        }
+    }
+
+    {
+        static constexpr D2D1_COLOR_F color{ 1, 1, 1, 1 };
+        THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, _emojiBrush.put()));
+        THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, _brush.put()));
+    }
+
+    ID3D11ShaderResourceView* resources[]{ _backgroundBitmapView.get(), _glyphAtlasView.get() };
+    p.deviceContext->PSSetShaderResources(0, 2, &resources[0]);
+
+    _rectPackerData = Buffer<stbrp_node>{ u };
+}
+
+BackendD3D::QuadInstance& BackendD3D::_getLastQuad() noexcept
+{
+    assert(_instancesCount != 0);
+    return _instances[_instancesCount - 1];
+}
+
+// NOTE: Up to 5M calls per second -> no std::vector, no std::unordered_map.
+// This function is an easy >100x faster than std::vector, can be
+// inlined and reduces overall (!) renderer CPU usage by 5%.
+BackendD3D::QuadInstance& BackendD3D::_appendQuad()
+{
+    if (_instancesCount >= _instances.size())
+    {
+        _bumpInstancesSize();
+    }
+
+    return _instances[_instancesCount++];
+}
+
+void BackendD3D::_bumpInstancesSize()
+{
+    auto newSize = std::max(_instancesCount, _instances.size() * 2);
+    newSize = std::max(size_t{ 256 }, newSize);
+    Expects(newSize > _instances.size());
+
+    // Our render loop heavily relies on memcpy() which is up to between 1.5x (Intel)
+    // and 40x (AMD) faster for allocations with an alignment of 32 or greater.
+    auto newInstances = Buffer<QuadInstance, 32>{ newSize };
+    std::copy_n(_instances.data(), _instances.size(), newInstances.data());
+
+    _instances = std::move(newInstances);
+}
+
+void BackendD3D::_flushQuads(const RenderingPayload& p)
+{
+    if (!_instancesCount)
+    {
+        return;
+    }
+
+    if (!_cursorRects.empty())
+    {
+        _drawCursorForeground(p);
+    }
+
+    // TODO: Shrink instances buffer
+    if (_instancesCount > _instanceBufferCapacity)
+    {
+        _recreateInstanceBuffers(p);
+    }
+
+    {
+        D3D11_MAPPED_SUBRESOURCE mapped{};
+        THROW_IF_FAILED(p.deviceContext->Map(_instanceBuffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped));
+        memcpy(mapped.pData, _instances.data(), _instancesCount * sizeof(QuadInstance));
+        p.deviceContext->Unmap(_instanceBuffer.get(), 0);
+    }
+
+    // I found 4 approaches to drawing lots of quads quickly. There are probably even more.
+    // They can often be found in discussions about "particle" or "point sprite" rendering in game development.
+    // * Compute Shader: My understanding is that at the time of writing games are moving over to bucketing
+    //   particles into "tiles" on the screen and drawing them with a compute shader. While this improves
+    //   performance, it doesn't mix well with our goal of allowing arbitrary overlaps between glyphs.
+    //   Additionally none of the next 3 approaches use any significant amount of GPU time in the first place.
+    // * Geometry Shader: Geometry shaders can generate vertices on the fly, which would neatly replace our need
+    //   for an index buffer. However, many sources claim they're significantly slower than the following approaches.
+    // * DrawIndexed & DrawInstanced: Again, many sources claim that GPU instancing (Draw(Indexed)Instanced) performs
+    //   poorly for small meshes, and instead indexed vertices with a SRV (shader resource view) should be used.
+    //   The popular "Vertex Shader Tricks" talk from Bill Bilodeau at GDC 2014 suggests this approach, explains
+    //   how it works (you divide the `SV_VertexID` by 4 and index into the SRV that contains the per-instance data;
+    //   it's basically manual instancing inside the vertex shader) and shows how it outperforms regular instancing.
+    //   However on my own limited test hardware (built around ~2020), I found that for at least our use case,
+    //   GPU instancing matches the performance of using a custom buffer. In fact on my Nvidia GPU in particular,
+    //   instancing with ~10k instances appears to be about 50% faster and so DrawInstanced was chosen.
+    //   Instead I found that packing instance data as tightly as possible made the biggest performance difference,
+    //   and packing 16 bit integers with ID3D11InputLayout is quite a bit more convenient too.
+
+    p.deviceContext->DrawIndexedInstanced(6, static_cast<UINT>(_instancesCount), 0, 0, 0);
+    _instancesCount = 0;
+}
+
+void BackendD3D::_recreateInstanceBuffers(const RenderingPayload& p)
+{
+    // We use the viewport size of the terminal as the initial estimate for the amount of instances we'll see.
+    const auto minCapacity = static_cast<size_t>(p.s->cellCount.x) * p.s->cellCount.y;
+    auto newCapacity = std::max(_instancesCount, minCapacity);
+    auto newSize = newCapacity * sizeof(QuadInstance);
+    // Round up to multiples of 64kB to avoid reallocating too often.
+    // 64kB is the minimum alignment for committed resources in D3D12.
+    newSize = (newSize + 0xffff) & ~size_t{ 0xffff };
+    newCapacity = newSize / sizeof(QuadInstance);
+
+    _instanceBuffer.reset();
+
+    {
+        const D3D11_BUFFER_DESC desc{
+            .ByteWidth = gsl::narrow<UINT>(newSize),
+            .Usage = D3D11_USAGE_DYNAMIC,
+            .BindFlags = D3D11_BIND_VERTEX_BUFFER,
+            .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE,
+            .StructureByteStride = sizeof(QuadInstance),
+        };
+        THROW_IF_FAILED(p.device->CreateBuffer(&desc, nullptr, _instanceBuffer.addressof()));
+    }
+
+    // IA: Input Assembler
+    ID3D11Buffer* vertexBuffers[]{ _vertexBuffer.get(), _instanceBuffer.get() };
+    static constexpr UINT strides[]{ sizeof(f32x2), sizeof(QuadInstance) };
+    static constexpr UINT offsets[]{ 0, 0 };
+    p.deviceContext->IASetVertexBuffers(0, 2, &vertexBuffers[0], &strides[0], &offsets[0]);
+
+    _instanceBufferCapacity = newCapacity;
+}
+
+void BackendD3D::_drawBackground(const RenderingPayload& p)
+{
+    // Not uploading the bitmap halves (!) the GPU load for any given frame on 2023 hardware.
+    if (_backgroundBitmapGeneration != p.colorBitmapGenerations[0])
+    {
+        _uploadBackgroundBitmap(p);
+    }
+
+    _appendQuad() = {
+        .shadingType = ShadingType::Background,
+        .size = p.s->targetSize,
+    };
+}
+
+void BackendD3D::_uploadBackgroundBitmap(const RenderingPayload& p)
+{
+    D3D11_MAPPED_SUBRESOURCE mapped{};
+    THROW_IF_FAILED(p.deviceContext->Map(_backgroundBitmap.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped));
+
+    auto src = std::bit_cast<const char*>(p.backgroundBitmap.data());
+    const auto srcEnd = std::bit_cast<const char*>(p.backgroundBitmap.data() + p.backgroundBitmap.size());
+    const auto srcStride = p.colorBitmapRowStride * sizeof(u32);
+    auto dst = static_cast<char*>(mapped.pData);
+
+    while (src < srcEnd)
+    {
+        memcpy(dst, src, srcStride);
+        src += srcStride;
+        dst += mapped.RowPitch;
+    }
+
+    p.deviceContext->Unmap(_backgroundBitmap.get(), 0);
+    _backgroundBitmapGeneration = p.colorBitmapGenerations[0];
+}
+
+void BackendD3D::_drawText(RenderingPayload& p)
+{
+    if (_fontChangedResetGlyphAtlas)
+    {
+        _resetGlyphAtlas(p);
+    }
+
+    til::CoordType dirtyTop = til::CoordTypeMax;
+    til::CoordType dirtyBottom = til::CoordTypeMin;
+
+    u16 y = 0;
+    for (const auto row : p.rows)
+    {
+        f32 baselineX = 0;
+        f32 baselineY = y * p.s->font->cellSize.y + p.s->font->baseline;
+        f32 scaleX = 1;
+        f32 scaleY = 1;
+
+        if (row->lineRendition != LineRendition::SingleWidth)
+        {
+            scaleX = 2;
+
+            if (row->lineRendition >= LineRendition::DoubleHeightTop)
+            {
+                scaleY = 2;
+                baselineY /= 2;
+            }
+        }
+
+        for (const auto& m : row->mappings)
+        {
+            auto x = m.glyphsFrom;
+            const AtlasFontFaceKey fontFaceKey{
+                .fontFace = m.fontFace.get(),
+                .lineRendition = row->lineRendition,
+            };
+
+        // This goto label exists to allow us to retry rendering a glyph if the glyph atlas was full.
+        // We need to goto here, because a retry will cause the atlas texture as well as the
+        // _glyphCache hashmap to be cleared, and so we'll have to call insert() again.
+        drawGlyphRetry:
+            auto& fontFaceEntry = *_glyphAtlasMap.insert(fontFaceKey).first.inner;
+
+            while (x < m.glyphsTo)
+            {
+                const auto [glyphEntry, inserted] = fontFaceEntry.glyphs.insert(row->glyphIndices[x]);
+
+                if (inserted && !_drawGlyph(p, fontFaceEntry, glyphEntry))
+                {
+                    // A deadlock in this retry loop is detected in _drawGlyphPrepareRetry.
+                    //
+                    // Yes, I agree, avoid goto. Sometimes. It's not my fault that C++ still doesn't
+                    // have a `continue outerloop;` like other languages had it for decades. :(
+#pragma warning(suppress : 26438) // Avoid 'goto' (es.76).
+#pragma warning(suppress : 26448) // Consider using gsl::finally if final action is intended (gsl.util).
+                    goto drawGlyphRetry;
+                }
+
+                if (glyphEntry.data.GetShadingType() != ShadingType::Default)
+                {
+                    auto l = static_cast<til::CoordType>(lrintf((baselineX + row->glyphOffsets[x].advanceOffset) * scaleX));
+                    auto t = static_cast<til::CoordType>(lrintf((baselineY - row->glyphOffsets[x].ascenderOffset) * scaleY));
+
+                    l += glyphEntry.data.offset.x;
+                    t += glyphEntry.data.offset.y;
+
+                    row->dirtyTop = std::min(row->dirtyTop, t);
+                    row->dirtyBottom = std::max(row->dirtyBottom, t + glyphEntry.data.size.y);
+
+                    _appendQuad() = {
+                        .shadingType = glyphEntry.data.GetShadingType(),
+                        .position = { static_cast<i16>(l), static_cast<i16>(t) },
+                        .size = glyphEntry.data.size,
+                        .texcoord = glyphEntry.data.texcoord,
+                        .color = row->colors[x],
+                    };
+
+                    if (glyphEntry.data.overlapSplit)
+                    {
+                        _drawTextOverlapSplit(p, y);
+                    }
+                }
+
+                baselineX += row->glyphAdvances[x];
+                ++x;
+            }
+        }
+
+        if (!row->gridLineRanges.empty())
+        {
+            _drawGridlines(p, y);
+        }
+
+        if (p.invalidatedRows.contains(y))
+        {
+            dirtyTop = std::min(dirtyTop, row->dirtyTop);
+            dirtyBottom = std::max(dirtyBottom, row->dirtyBottom);
+        }
+
+        ++y;
+    }
+
+    if (dirtyTop < dirtyBottom)
+    {
+        p.dirtyRectInPx.top = std::min(p.dirtyRectInPx.top, dirtyTop);
+        p.dirtyRectInPx.bottom = std::max(p.dirtyRectInPx.bottom, dirtyBottom);
+    }
+
+    _d2dEndDrawing();
+}
+
+// There are a number of coding-oriented fonts that feature ligatures which (for instance)
+// translate text like "!=" into a glyph that looks like "≠" (just 2 columns wide and not 1).
+// Glyphs like that still need to be colored in potentially multiple colors however, so this
+// function will handle these ligatures by splitting them up into multiple QuadInstances.
+//
+// It works by iteratively splitting the wide glyph into shorter and shorter segments like so
+// (whitespaces indicate that the glyph was split up in a leading and trailing half):
+//   <!--
+//   < !--
+//   < ! --
+//   < ! - -
+void BackendD3D::_drawTextOverlapSplit(const RenderingPayload& p, u16 y)
+{
+    const auto& originalQuad = _getLastQuad();
+
+    i32 columnAdvance = 1;
+    i32 columnAdvanceInPx{ p.s->font->cellSize.x };
+    i32 cellCount{ p.s->cellCount.x };
+
+    if (p.rows[y]->lineRendition != LineRendition::SingleWidth)
+    {
+        columnAdvance = 2;
+        columnAdvanceInPx <<= 1;
+        cellCount >>= 1;
+    }
+
+    i32 originalLeft = originalQuad.position.x;
+    i32 originalRight = originalQuad.position.x + originalQuad.size.x;
+    originalLeft = std::max(originalLeft, 0);
+    originalRight = std::min(originalRight, cellCount * columnAdvanceInPx);
+
+    auto column = originalLeft / columnAdvanceInPx + 1;
+    auto clipLeft = column * columnAdvanceInPx;
+    column *= columnAdvance;
+
+    const auto colors = &p.foregroundBitmap[p.colorBitmapRowStride * y];
+    auto lastFg = originalQuad.color;
+
+    // We must ensure to exit the loop while `column` is less than `cellCount.x`,
+    // otherwise we cause a potential out of bounds access into foregroundBitmap.
+    // This may happen with glyphs that are severely overlapping their cells,
+    // outside of the viewport. The `clipLeft < originalRight` condition doubles
+    // as a `column < cellCount.x` condition thanks to us std::min()ing it above.
+    for (; clipLeft < originalRight; column += columnAdvance, clipLeft += columnAdvanceInPx)
+    {
+        const auto fg = colors[column];
+
+        if (lastFg != fg)
+        {
+            // NOTE: _appendQuad might reallocate and any pointers
+            // acquired before calling this function are now invalid.
+            auto& next = _appendQuad();
+            // The item at -1 is the quad we've just appended, which means
+            // that the previous quad we want to split up is at -2.
+            auto& prev = _instances[_instancesCount - 2];
+
+            const auto prevWidth = clipLeft - prev.position.x;
+            const auto nextWidth = prev.size.x - prevWidth;
+
+            prev.size.x = gsl::narrow<u16>(prevWidth);
+
+            next = prev;
+            next.position.x = gsl::narrow<i16>(next.position.x + prevWidth);
+            next.texcoord.x = gsl::narrow<u16>(next.texcoord.x + prevWidth);
+            next.size.x = gsl::narrow<u16>(nextWidth);
+            next.color = fg;
+
+            lastFg = fg;
+        }
+    }
+}
+
+bool BackendD3D::_drawGlyph(const RenderingPayload& p, const BackendD3D::AtlasFontFaceEntryInner& fontFaceEntry, BackendD3D::AtlasGlyphEntry& glyphEntry)
+{
+    if (!fontFaceEntry.fontFace)
+    {
+        return _drawSoftFontGlyph(p, fontFaceEntry, glyphEntry);
+    }
+
+    const DWRITE_GLYPH_RUN glyphRun{
+        .fontFace = fontFaceEntry.fontFace.get(),
+        .fontEmSize = p.s->font->fontSize,
+        .glyphCount = 1,
+        .glyphIndices = &glyphEntry.glyphIndex,
+    };
+
+    // It took me a while to figure out how to rasterize glyphs manually with DirectWrite without depending on Direct2D.
+    // The benefits are a reduction in memory usage, an increase in performance under certain circumstances and most
+    // importantly, the ability to debug the renderer more easily, because many graphics debuggers don't support Direct2D.
+    // Direct2D has one big advantage however: It features a clever glyph uploader with a pool of D3D11_USAGE_STAGING textures,
+    // which I was too short on time to implement myself. This makes rasterization with Direct2D roughly 2x faster.
+    //
+    // This code remains, because it features some parts that are slightly more and some parts that are outright difficult to figure out.
+#if 0
+    const auto wantsClearType = p.s->font->antialiasingMode == AntialiasingMode::ClearType;
+    const auto wantsAliased = p.s->font->antialiasingMode == AntialiasingMode::Aliased;
+    const auto antialiasMode = wantsClearType ? DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE : DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE;
+    const auto outlineThreshold = wantsAliased ? DWRITE_OUTLINE_THRESHOLD_ALIASED : DWRITE_OUTLINE_THRESHOLD_ANTIALIASED;
+
+    DWRITE_RENDERING_MODE renderingMode{};
+    DWRITE_GRID_FIT_MODE gridFitMode{};
+    THROW_IF_FAILED(fontFaceEntry.fontFace->GetRecommendedRenderingMode(
+        /* fontEmSize       */ fontEmSize,
+        /* dpiX             */ 1, // fontEmSize is already in pixel
+        /* dpiY             */ 1, // fontEmSize is already in pixel
+        /* transform        */ nullptr,
+        /* isSideways       */ FALSE,
+        /* outlineThreshold */ outlineThreshold,
+        /* measuringMode    */ DWRITE_MEASURING_MODE_NATURAL,
+        /* renderingParams  */ _textRenderingParams.get(),
+        /* renderingMode    */ &renderingMode,
+        /* gridFitMode      */ &gridFitMode));
+
+    wil::com_ptr<IDWriteGlyphRunAnalysis> glyphRunAnalysis;
+    THROW_IF_FAILED(p.dwriteFactory->CreateGlyphRunAnalysis(
+        /* glyphRun         */ &glyphRun,
+        /* transform        */ nullptr,
+        /* renderingMode    */ renderingMode,
+        /* measuringMode    */ DWRITE_MEASURING_MODE_NATURAL,
+        /* gridFitMode      */ gridFitMode,
+        /* antialiasMode    */ antialiasMode,
+        /* baselineOriginX  */ 0,
+        /* baselineOriginY  */ 0,
+        /* glyphRunAnalysis */ glyphRunAnalysis.put()));
+
+    RECT textureBounds{};
+
+    if (wantsClearType)
+    {
+        THROW_IF_FAILED(glyphRunAnalysis->GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1, &textureBounds));
+
+        // Some glyphs cannot be drawn with ClearType, such as bitmap fonts. In that case
+        // GetAlphaTextureBounds() supposedly returns an empty RECT, but I haven't tested that yet.
+        if (!IsRectEmpty(&textureBounds))
+        {
+            // Allocate a buffer of `3 * width * height` bytes.
+            THROW_IF_FAILED(glyphRunAnalysis->CreateAlphaTexture(DWRITE_TEXTURE_CLEARTYPE_3x1, &textureBounds, buffer.data(), buffer.size()));
+            // The buffer contains RGB ClearType weights which can now be packed into RGBA and uploaded to the glyph atlas.
+            return;
+        }
+
+        // --> Retry with grayscale AA.
+    }
+
+    // Even though it says "ALIASED" and even though the docs for the flag still say:
+    // > [...] that is, each pixel is either fully opaque or fully transparent [...]
+    // don't be confused: It's grayscale antialiased. lol
+    THROW_IF_FAILED(glyphRunAnalysis->GetAlphaTextureBounds(DWRITE_TEXTURE_ALIASED_1x1, &textureBounds));
+
+    // Allocate a buffer of `width * height` bytes.
+    THROW_IF_FAILED(glyphRunAnalysis->CreateAlphaTexture(DWRITE_TEXTURE_ALIASED_1x1, &textureBounds, buffer.data(), buffer.size()));
+    // The buffer now contains a grayscale alpha mask.
+#endif
+
+    const auto lineRendition = static_cast<LineRendition>(fontFaceEntry.lineRendition);
+    std::optional<D2D1_MATRIX_3X2_F> transform;
+
+    if (lineRendition != LineRendition::SingleWidth)
+    {
+        auto& t = transform.emplace();
+        t.m11 = 2.0f;
+        t.m22 = lineRendition >= LineRendition::DoubleHeightTop ? 2.0f : 1.0f;
+        _d2dRenderTarget->SetTransform(&t);
+    }
+
+    const auto restoreTransform = wil::scope_exit([&]() noexcept {
+        if (transform)
+        {
+            static constexpr D2D1_MATRIX_3X2_F identity{ .m11 = 1, .m22 = 1 };
+            _d2dRenderTarget->SetTransform(&identity);
+        }
+    });
+
+    // This calculates the black box of the glyph, or in other words,
+    // it's extents/size relative to its baseline origin (at 0,0).
+    //
+    // bounds.top ------++-----######--+
+    //   (-7)           ||  ############
+    //                  ||####      ####
+    //                  |###       #####
+    //  baseline ______ |###      #####|
+    //   origin        \|############# |
+    //  (= 0,0)         \|###########  |
+    //                  ++-------###---+
+    //                  ##      ###    |
+    // bounds.bottom ---+#########-----+
+    //    (+2)          |              |
+    //             bounds.left     bounds.right
+    //                 (-1)           (+14)
+    //
+
+    bool isColorGlyph = false;
+    D2D1_RECT_F bounds = GlyphRunEmptyBounds;
+
+    const auto cleanup = wil::scope_exit([&]() {
+        if (isColorGlyph)
+        {
+            _d2dRenderTarget4->SetTextAntialiasMode(static_cast<D2D1_TEXT_ANTIALIAS_MODE>(p.s->font->antialiasingMode));
+        }
+    });
+
+    {
+        const auto enumerator = TranslateColorGlyphRun(p.dwriteFactory4.get(), {}, &glyphRun);
+
+        if (!enumerator)
+        {
+            THROW_IF_FAILED(_d2dRenderTarget->GetGlyphRunWorldBounds({}, &glyphRun, DWRITE_MEASURING_MODE_NATURAL, &bounds));
+        }
+        else
+        {
+            isColorGlyph = true;
+            _d2dRenderTarget4->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
+
+            while (ColorGlyphRunMoveNext(enumerator.get()))
+            {
+                const auto colorGlyphRun = ColorGlyphRunGetCurrentRun(enumerator.get());
+                ColorGlyphRunAccumulateBounds(_d2dRenderTarget.get(), colorGlyphRun, bounds);
+            }
+        }
+    }
+
+    // box may be empty if the glyph is whitespace.
+    if (bounds.left >= bounds.right || bounds.top >= bounds.bottom)
+    {
+        return true;
+    }
+
+    const auto bl = lrintf(bounds.left);
+    const auto bt = lrintf(bounds.top);
+    const auto br = lrintf(bounds.right);
+    const auto bb = lrintf(bounds.bottom);
+
+    stbrp_rect rect{
+        .w = br - bl,
+        .h = bb - bt,
+    };
+    if (!stbrp_pack_rects(&_rectPacker, &rect, 1))
+    {
+        _drawGlyphPrepareRetry(p);
+        return false;
+    }
+
+    const D2D1_POINT_2F baselineOrigin{
+        static_cast<f32>(rect.x - bl),
+        static_cast<f32>(rect.y - bt),
+    };
+
+    if (transform)
+    {
+        auto& t = *transform;
+        t.dx = (1.0f - t.m11) * baselineOrigin.x;
+        t.dy = (1.0f - t.m22) * baselineOrigin.y;
+        _d2dRenderTarget->SetTransform(&t);
+    }
+
+    _d2dBeginDrawing();
+
+    if (!isColorGlyph)
+    {
+        _d2dRenderTarget->DrawGlyphRun(baselineOrigin, &glyphRun, _brush.get(), DWRITE_MEASURING_MODE_NATURAL);
+    }
+    else
+    {
+        const auto enumerator = TranslateColorGlyphRun(p.dwriteFactory4.get(), baselineOrigin, &glyphRun);
+        while (ColorGlyphRunMoveNext(enumerator.get()))
+        {
+            const auto colorGlyphRun = ColorGlyphRunGetCurrentRun(enumerator.get());
+            ColorGlyphRunDraw(_d2dRenderTarget4.get(), _emojiBrush.get(), _brush.get(), colorGlyphRun);
+        }
+    }
+
+    // Ligatures are drawn with strict cell-wise foreground color, while other text allows colors to overhang
+    // their cells. This makes sure that italics and such retain their color and don't look "cut off".
+    //
+    // The former condition makes sure to exclude diacritics and such from being considered a ligature,
+    // while the latter condition-pair makes sure to exclude regular BMP wide glyphs that overlap a little.
+    const auto horizontalScale = lineRendition != LineRendition::SingleWidth ? 2 : 1;
+    const auto triggerLeft = _ligatureOverhangTriggerLeft * horizontalScale;
+    const auto triggerRight = _ligatureOverhangTriggerRight * horizontalScale;
+    const auto overlapSplit = rect.w >= p.s->font->cellSize.x && (bl <= triggerLeft || br >= triggerRight);
+
+    glyphEntry.data.shadingType = static_cast<u16>(isColorGlyph ? ShadingType::TextPassthrough : _textShadingType);
+    glyphEntry.data.overlapSplit = overlapSplit;
+    glyphEntry.data.offset.x = bl;
+    glyphEntry.data.offset.y = bt;
+    glyphEntry.data.size.x = rect.w;
+    glyphEntry.data.size.y = rect.h;
+    glyphEntry.data.texcoord.x = rect.x;
+    glyphEntry.data.texcoord.y = rect.y;
+
+    if (lineRendition >= LineRendition::DoubleHeightTop)
+    {
+        _splitDoubleHeightGlyph(p, fontFaceEntry, glyphEntry);
+    }
+
+    return true;
+}
+
+bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry)
+{
+    stbrp_rect rect{
+        .w = p.s->font->cellSize.x,
+        .h = p.s->font->cellSize.y,
+    };
+
+    const auto lineRendition = static_cast<LineRendition>(fontFaceEntry.lineRendition);
+    auto baseline = p.s->font->baseline;
+
+    if (lineRendition != LineRendition::SingleWidth)
+    {
+        const auto heightShift = static_cast<u8>(lineRendition >= LineRendition::DoubleHeightTop);
+        rect.w <<= 1;
+        rect.h <<= heightShift;
+        baseline <<= heightShift;
+    }
+
+    if (!stbrp_pack_rects(&_rectPacker, &rect, 1))
+    {
+        _drawGlyphPrepareRetry(p);
+        return false;
+    }
+
+    if (!_softFontBitmap)
+    {
+        // Allocating such a tiny texture is very wasteful (min. texture size on GPUs
+        // right now is 64kB), but this is a seldomly used feature so it's fine...
+        const D2D1_SIZE_U size{
+            static_cast<UINT32>(p.s->font->softFontCellSize.width),
+            static_cast<UINT32>(p.s->font->softFontCellSize.height),
+        };
+        const D2D1_BITMAP_PROPERTIES1 bitmapProperties{
+            .pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED },
+            .dpiX = static_cast<f32>(p.s->font->dpi),
+            .dpiY = static_cast<f32>(p.s->font->dpi),
+        };
+        THROW_IF_FAILED(_d2dRenderTarget->CreateBitmap(size, nullptr, 0, &bitmapProperties, _softFontBitmap.addressof()));
+    }
+
+    {
+        const auto width = static_cast<size_t>(p.s->font->softFontCellSize.width);
+        const auto height = static_cast<size_t>(p.s->font->softFontCellSize.height);
+
+        auto bitmapData = Buffer<u32>{ width * height };
+        const auto glyphIndex = glyphEntry.glyphIndex - 0xEF20u;
+        auto src = p.s->font->softFontPattern.begin() + height * glyphIndex;
+        auto dst = bitmapData.begin();
+
+        for (size_t y = 0; y < height; y++)
+        {
+            auto srcBits = *src++;
+            for (size_t x = 0; x < width; x++)
+            {
+                const auto srcBitIsSet = (srcBits & 0x8000) != 0;
+                *dst++ = srcBitIsSet ? 0xffffffff : 0x00000000;
+                srcBits <<= 1;
+            }
+        }
+
+        const auto pitch = static_cast<UINT32>(width * sizeof(u32));
+        THROW_IF_FAILED(_softFontBitmap->CopyFromMemory(nullptr, bitmapData.data(), pitch));
+    }
+
+    const auto interpolation = p.s->font->antialiasingMode == AntialiasingMode::Aliased ? D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR : D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC;
+    const D2D1_RECT_F dest{
+        static_cast<f32>(rect.x),
+        static_cast<f32>(rect.y),
+        static_cast<f32>(rect.x + rect.w),
+        static_cast<f32>(rect.y + rect.h),
+    };
+
+    _d2dBeginDrawing();
+    _d2dRenderTarget->DrawBitmap(_softFontBitmap.get(), &dest, 1, interpolation, nullptr, nullptr);
+
+    glyphEntry.data.shadingType = static_cast<u16>(ShadingType::TextGrayscale);
+    glyphEntry.data.overlapSplit = 0;
+    glyphEntry.data.offset.x = 0;
+    glyphEntry.data.offset.y = -baseline;
+    glyphEntry.data.size.x = rect.w;
+    glyphEntry.data.size.y = rect.h;
+    glyphEntry.data.texcoord.x = rect.x;
+    glyphEntry.data.texcoord.y = rect.y;
+
+    if (lineRendition >= LineRendition::DoubleHeightTop)
+    {
+        _splitDoubleHeightGlyph(p, fontFaceEntry, glyphEntry);
+    }
+
+    return true;
+}
+
+void BackendD3D::_drawGlyphPrepareRetry(const RenderingPayload& p)
+{
+    THROW_HR_IF_MSG(E_UNEXPECTED, _glyphAtlasMap.empty(), "BackendD3D::_drawGlyph deadlock");
+    _d2dEndDrawing();
+    _flushQuads(p);
+    _resetGlyphAtlas(p);
+}
+
+// If this is a double-height glyph (DECDHL), we need to split it into 2 glyph entries:
+// One for the top/bottom half each, because that's how DECDHL works. This will clip the
+// `glyphEntry` to only contain the one specified by `fontFaceEntry.lineRendition`
+// and create a second entry in our glyph cache hashmap that contains the other half.
+void BackendD3D::_splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry)
+{
+    // Twice the line height, twice the descender gap. For both.
+    glyphEntry.data.offset.y -= p.s->font->descender;
+
+    const auto isTop = fontFaceEntry.lineRendition == LineRendition::DoubleHeightTop;
+
+    const AtlasFontFaceKey key2{
+        .fontFace = fontFaceEntry.fontFace.get(),
+        .lineRendition = isTop ? LineRendition::DoubleHeightBottom : LineRendition::DoubleHeightTop,
+    };
+
+    auto& glyphCache = _glyphAtlasMap.insert(key2).first.inner->glyphs;
+    auto& entry2 = glyphCache.insert(glyphEntry.glyphIndex).first;
+    entry2.data = glyphEntry.data;
+
+    auto& top = isTop ? glyphEntry : entry2;
+    auto& bottom = isTop ? entry2 : glyphEntry;
+
+    const auto topSize = clamp(-glyphEntry.data.offset.y - p.s->font->baseline, 0, static_cast<int>(glyphEntry.data.size.y));
+    top.data.offset.y += p.s->font->cellSize.y;
+    top.data.size.y = topSize;
+    bottom.data.offset.y += topSize;
+    bottom.data.size.y = std::max(0, bottom.data.size.y - topSize);
+    bottom.data.texcoord.y += topSize;
+
+    // Things like diacritics might be so small that they only exist on either half of the
+    // double-height row. This effectively turns the other (unneeded) side into whitespace.
+    if (!top.data.size.y)
+    {
+        top.data.shadingType = static_cast<u16>(ShadingType::Default);
+    }
+    if (!bottom.data.size.y)
+    {
+        bottom.data.shadingType = static_cast<u16>(ShadingType::Default);
+    }
+}
+
+void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y)
+{
+    const auto row = p.rows[y];
+
+    const auto horizontalShift = static_cast<u8>(row->lineRendition != LineRendition::SingleWidth);
+    const auto verticalShift = static_cast<u8>(row->lineRendition >= LineRendition::DoubleHeightTop);
+
+    const auto cellSize = p.s->font->cellSize;
+    const auto dottedLineType = horizontalShift ? ShadingType::DottedLineWide : ShadingType::DottedLine;
+
+    const auto rowTop = static_cast<i16>(cellSize.y * y);
+    const auto rowBottom = static_cast<i16>(rowTop + cellSize.y);
+
+    auto textCellTop = rowTop;
+    if (row->lineRendition == LineRendition::DoubleHeightBottom)
+    {
+        textCellTop -= cellSize.y;
+    }
+
+    const i32 clipTop = row->lineRendition == LineRendition::DoubleHeightBottom ? rowTop : 0;
+    const i32 clipBottom = row->lineRendition == LineRendition::DoubleHeightTop ? rowBottom : p.s->targetSize.y;
+
+    const auto appendVerticalLines = [&](const GridLineRange& r, FontDecorationPosition pos) {
+        const auto textCellWidth = cellSize.x << horizontalShift;
+        const auto offset = pos.position << horizontalShift;
+        const auto width = static_cast<u16>(pos.height << horizontalShift);
+
+        auto posX = r.from * cellSize.x + offset;
+        const auto end = r.to * cellSize.x;
+
+        for (; posX < end; posX += textCellWidth)
+        {
+            _appendQuad() = {
+                .shadingType = ShadingType::SolidLine,
+                .position = { static_cast<i16>(posX), rowTop },
+                .size = { width, p.s->font->cellSize.y },
+                .color = r.color,
+            };
+        }
+    };
+    const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ShadingType shadingType) {
+        const auto offset = pos.position << verticalShift;
+        const auto height = static_cast<u16>(pos.height << verticalShift);
+
+        const auto left = static_cast<i16>(r.from * cellSize.x);
+        const auto width = static_cast<u16>((r.to - r.from) * cellSize.x);
+
+        i32 rt = textCellTop + offset;
+        i32 rb = rt + height;
+        rt = clamp(rt, clipTop, clipBottom);
+        rb = clamp(rb, clipTop, clipBottom);
+
+        if (rt < rb)
+        {
+            _appendQuad() = {
+                .shadingType = shadingType,
+                .position = { left, static_cast<i16>(rt) },
+                .size = { width, static_cast<u16>(rb - rt) },
+                .color = r.color,
+            };
+        }
+    };
+
+    for (const auto& r : row->gridLineRanges)
+    {
+        // AtlasEngine.cpp shouldn't add any gridlines if they don't do anything.
+        assert(r.lines.any());
+
+        if (r.lines.test(GridLines::Left))
+        {
+            appendVerticalLines(r, p.s->font->gridLeft);
+        }
+        if (r.lines.test(GridLines::Right))
+        {
+            appendVerticalLines(r, p.s->font->gridRight);
+        }
+        if (r.lines.test(GridLines::Top))
+        {
+            appendHorizontalLine(r, p.s->font->gridTop, ShadingType::SolidLine);
+        }
+        if (r.lines.test(GridLines::Bottom))
+        {
+            appendHorizontalLine(r, p.s->font->gridBottom, ShadingType::SolidLine);
+        }
+
+        if (r.lines.test(GridLines::Underline))
+        {
+            appendHorizontalLine(r, p.s->font->underline, ShadingType::SolidLine);
+        }
+        if (r.lines.test(GridLines::HyperlinkUnderline))
+        {
+            appendHorizontalLine(r, p.s->font->underline, dottedLineType);
+        }
+        if (r.lines.test(GridLines::DoubleUnderline))
+        {
+            for (const auto pos : p.s->font->doubleUnderline)
+            {
+                appendHorizontalLine(r, pos, ShadingType::SolidLine);
+            }
+        }
+        if (r.lines.test(GridLines::Strikethrough))
+        {
+            appendHorizontalLine(r, p.s->font->strikethrough, ShadingType::SolidLine);
+        }
+    }
+}
+
+void BackendD3D::_drawCursorBackground(const RenderingPayload& p)
+{
+    _cursorRects.clear();
+
+    if (p.cursorRect.empty())
+    {
+        return;
+    }
+
+    _cursorPosition = {
+        p.s->font->cellSize.x * p.cursorRect.left,
+        p.s->font->cellSize.y * p.cursorRect.top,
+        p.s->font->cellSize.x * p.cursorRect.right,
+        p.s->font->cellSize.y * p.cursorRect.bottom,
+    };
+
+    const auto cursorColor = p.s->cursor->cursorColor;
+    const auto offset = p.cursorRect.top * p.colorBitmapRowStride;
+
+    for (auto x1 = p.cursorRect.left; x1 < p.cursorRect.right; ++x1)
+    {
+        const auto x0 = x1;
+        const auto bg = p.backgroundBitmap[offset + x1] | 0xff000000;
+
+        for (; x1 < p.cursorRect.right && (p.backgroundBitmap[offset + x1] | 0xff000000) == bg; ++x1)
+        {
+        }
+
+        const i16x2 position{
+            p.s->font->cellSize.x * x0,
+            p.s->font->cellSize.y * p.cursorRect.top,
+        };
+        const u16x2 size{
+            static_cast<u16>(p.s->font->cellSize.x * (x1 - x0)),
+            p.s->font->cellSize.y,
+        };
+        const auto isInverted = cursorColor == 0xffffffff;
+        const auto background = isInverted ? bg ^ 0xc0c0c0 : cursorColor;
+        const auto foreground = isInverted ? 0 : bg;
+        auto& c0 = _cursorRects.emplace_back(position, size, background, foreground);
+
+        switch (static_cast<CursorType>(p.s->cursor->cursorType))
+        {
+        case CursorType::Legacy:
+        {
+            const auto height = (c0.size.y * p.s->cursor->heightPercentage + 50) / 100;
+            c0.position.y += c0.size.y - height;
+            c0.size.y = height;
+            break;
+        }
+        case CursorType::VerticalBar:
+            c0.size.x = p.s->font->thinLineWidth;
+            break;
+        case CursorType::Underscore:
+            c0.position.y += p.s->font->underline.position;
+            c0.size.y = p.s->font->underline.height;
+            break;
+        case CursorType::EmptyBox:
+        {
+            auto& c1 = _cursorRects.emplace_back(c0);
+            if (x0 == p.cursorRect.left)
+            {
+                auto& c = _cursorRects.emplace_back(c0);
+                // Make line a little shorter vertically so it doesn't overlap with the top/bottom horizontal lines.
+                c.position.y += p.s->font->thinLineWidth;
+                c.size.y -= 2 * p.s->font->thinLineWidth;
+                // The actual adjustment...
+                c.size.x = p.s->font->thinLineWidth;
+            }
+            if (x1 == p.cursorRect.right)
+            {
+                auto& c = _cursorRects.emplace_back(c0);
+                // Make line a little shorter vertically so it doesn't overlap with the top/bottom horizontal lines.
+                c.position.y += p.s->font->thinLineWidth;
+                c.size.y -= 2 * p.s->font->thinLineWidth;
+                // The actual adjustment...
+                c.position.x += c.size.x - p.s->font->thinLineWidth;
+                c.size.x = p.s->font->thinLineWidth;
+            }
+            c0.size.y = p.s->font->thinLineWidth;
+            c1.position.y += c1.size.y - p.s->font->thinLineWidth;
+            c1.size.y = p.s->font->thinLineWidth;
+            break;
+        }
+        case CursorType::FullBox:
+            break;
+        case CursorType::DoubleUnderscore:
+        {
+            auto& c1 = _cursorRects.emplace_back(c0);
+            c0.position.y += p.s->font->doubleUnderline[0].position;
+            c0.size.y = p.s->font->thinLineWidth;
+            c1.position.y += p.s->font->doubleUnderline[1].position;
+            c1.size.y = p.s->font->thinLineWidth;
+            break;
+        }
+        default:
+            break;
+        }
+    }
+
+    for (const auto& c : _cursorRects)
+    {
+        _appendQuad() = {
+            .shadingType = ShadingType::Cursor,
+            .position = c.position,
+            .size = c.size,
+            .color = c.background,
+        };
+    }
+}
+
+void BackendD3D::_drawCursorForeground(const RenderingPayload& p)
+{
+    // NOTE: _appendQuad() may reallocate the _instances vector. It's important to iterate
+    // by index, because pointers (or iterators) would get invalidated. It's also important
+    // to cache the original _instancesCount since it'll get changed with each append.
+    auto instancesCount = _instancesCount;
+    size_t instancesOffset = 0;
+
+    assert(instancesCount != 0);
+
+    // All of the text drawing primitives are drawn as a single block, after drawing
+    // the background and cursor background and before drawing the selection overlay.
+    // To avoid having to check the shadingType in the loop below, we'll find the
+    // start and end of this "block" here in advance.
+    for (; instancesOffset < instancesCount; ++instancesOffset)
+    {
+        const auto shadingType = _instances[instancesOffset].shadingType;
+        if (shadingType >= ShadingType::TextDrawingFirst && shadingType <= ShadingType::TextDrawingLast)
+        {
+            break;
+        }
+    }
+    // We can also skip any instances (= any rows) at the beginning that are clearly not overlapping with
+    // the cursor. This reduces the the CPU cost of this function by roughly half (a few microseconds).
+    for (; instancesOffset < instancesCount; ++instancesOffset)
+    {
+        const auto& it = _instances[instancesOffset];
+        if ((it.position.y + it.size.y) > _cursorPosition.top)
+        {
+            break;
+        }
+    }
+
+    // Now do the same thing as above, but backwards from the end.
+    for (; instancesCount > instancesOffset; --instancesCount)
+    {
+        const auto shadingType = _instances[instancesCount - 1].shadingType;
+        if (shadingType >= ShadingType::TextDrawingFirst && shadingType <= ShadingType::TextDrawingLast)
+        {
+            break;
+        }
+    }
+    for (; instancesCount > instancesOffset; --instancesCount)
+    {
+        const auto& it = _instances[instancesCount - 1];
+        if (it.position.y < _cursorPosition.bottom)
+        {
+            break;
+        }
+    }
+
+    // For cursors with multiple rectangles this really isn't all that fast, because it iterates
+    // over the instances vector multiple times. But I also don't really care, because the
+    // double-underline and empty-box cursors are pretty annoying to deal with in any case.
+    //
+    // It would definitely help if instead of position & size QuadInstances would use left/top/right/bottom
+    // with f32, because then computing the intersection would be much faster via SIMD. But that would
+    // make the struct size larger and cost more power to transmit more data to the GPU. ugh.
+    for (const auto& c : _cursorRects)
+    {
+        const int cursorL = c.position.x;
+        const int cursorT = c.position.y;
+        const int cursorR = cursorL + c.size.x;
+        const int cursorB = cursorT + c.size.y;
+
+        for (size_t i = instancesOffset; i < instancesCount; ++i)
+        {
+            const auto& it = _instances[i];
+            const int instanceL = it.position.x;
+            const int instanceT = it.position.y;
+            const int instanceR = instanceL + it.size.x;
+            const int instanceB = instanceT + it.size.y;
+
+            if (instanceL < cursorR && instanceR > cursorL && instanceT < cursorB && instanceB > cursorT)
+            {
+                // The _instances vector is _huge_ (easily up to 50k items) whereas only 1-2 items will actually overlap
+                // with the cursor. --> Make this loop more compact by putting as much as possible into a function call.
+                const auto added = _drawCursorForegroundSlowPath(p, c, i);
+                i += added;
+                instancesCount += added;
+            }
+        }
+    }
+}
+
+size_t BackendD3D::_drawCursorForegroundSlowPath(const RenderingPayload& p, const CursorRect& c, size_t offset)
+{
+    // We won't die from copying 24 bytes. It simplifies the code below especially in
+    // respect to when/if we overwrite the _instances[offset] slot with a cutout.
+#pragma warning(suppress : 26820) // This is a potentially expensive copy operation. Consider using a reference unless a copy is required (p.9).
+    const auto it = _instances[offset];
+
+    // There's one special exception to the rule: Emojis. We currently don't really support inverting
+    // (or reversing) colored glyphs like that, so we can return early here and avoid cutting them up.
+    // It'd be too expensive to check for these rare glyph types inside the _drawCursorForeground() loop.
+    if (it.shadingType == ShadingType::TextPassthrough)
+    {
+        return 0;
+    }
+
+    const int cursorL = c.position.x;
+    const int cursorT = c.position.y;
+    const int cursorR = cursorL + c.size.x;
+    const int cursorB = cursorT + c.size.y;
+
+    const int instanceL = it.position.x;
+    const int instanceT = it.position.y;
+    const int instanceR = instanceL + it.size.x;
+    const int instanceB = instanceT + it.size.y;
+
+    const auto intersectionL = std::max(cursorL, instanceL);
+    const auto intersectionT = std::max(cursorT, instanceT);
+    const auto intersectionR = std::min(cursorR, instanceR);
+    const auto intersectionB = std::min(cursorB, instanceB);
+
+    // We should only get called if there's actually an intersection.
+    assert(intersectionL < intersectionR && intersectionT < intersectionB);
+
+    // We need to ensure that the glyph doesn't "dirty" the cursor background with its un-inverted/un-reversed color.
+    // If it did, and we'd draw the inverted/reversed glyph on top, it would look smudged.
+    // As such, this cuts a cursor-sized hole into the original glyph and splits it up.
+    //
+    // > Always initialize an object
+    // I would pay money if this warning was a little smarter. The array can remain uninitialized,
+    // because it acts like a tiny small_vector, but without the assertions.
+#pragma warning(suppress : 26494) // Variable 'cutouts' is uninitialized. Always initialize an object (type.5).
+    rect<int> cutouts[4];
+    size_t cutoutCount = 0;
+
+    if (instanceT < intersectionT)
+    {
+        cutouts[cutoutCount++] = { instanceL, instanceT, instanceR, intersectionT };
+    }
+    if (instanceB > intersectionB)
+    {
+        cutouts[cutoutCount++] = { instanceL, intersectionB, instanceR, instanceB };
+    }
+    if (instanceL < intersectionL)
+    {
+        cutouts[cutoutCount++] = { instanceL, intersectionT, intersectionL, intersectionB };
+    }
+    if (instanceR > intersectionR)
+    {
+        cutouts[cutoutCount++] = { intersectionR, intersectionT, instanceR, intersectionB };
+    }
+
+    const auto addedInstances = cutoutCount ? cutoutCount - 1 : 0;
+
+    // Make place for cutoutCount-many items at position.
+    // NOTE: _bumpInstancesSize() reallocates the vector and all references to _instances will now be invalid.
+    if (addedInstances)
+    {
+        const auto instancesCount = _instancesCount;
+
+        _instancesCount += addedInstances;
+        if (_instancesCount >= _instances.size())
+        {
+            _bumpInstancesSize();
+        }
+
+        const auto src = _instances.data() + offset;
+        const auto dst = src + addedInstances;
+        const auto count = instancesCount - offset;
+        assert(src >= _instances.begin() && (src + count) < _instances.end());
+        assert(dst >= _instances.begin() && (dst + count) < _instances.end());
+        memmove(dst, src, count * sizeof(QuadInstance));
+    }
+
+    // Now that there's space we can write the glyph cutouts back into the instances vector.
+    for (size_t i = 0; i < cutoutCount; ++i)
+    {
+        const auto& cutout = cutouts[i];
+        auto& target = _instances[offset + i];
+
+        target.shadingType = it.shadingType;
+        target.position.x = static_cast<i16>(cutout.left);
+        target.position.y = static_cast<i16>(cutout.top);
+        target.size.x = static_cast<u16>(cutout.right - cutout.left);
+        target.size.y = static_cast<u16>(cutout.bottom - cutout.top);
+        target.texcoord.x = static_cast<u16>(it.texcoord.x + cutout.left - instanceL);
+        target.texcoord.y = static_cast<u16>(it.texcoord.y + cutout.top - instanceT);
+        target.color = it.color;
+    }
+
+    const auto cursorColor = p.s->cursor->cursorColor;
+    // If the cursor covers the entire glyph (like, let's say, a full-box cursor with an ASCII character),
+    // we don't append a new quad, but rather reuse the one that already exists (cutoutCount == 0).
+    auto& target = cutoutCount ? _appendQuad() : _instances[offset];
+
+    target.shadingType = it.shadingType;
+    target.position.x = static_cast<i16>(intersectionL);
+    target.position.y = static_cast<i16>(intersectionT);
+    target.size.x = static_cast<u16>(intersectionR - intersectionL);
+    target.size.y = static_cast<u16>(intersectionB - intersectionT);
+    target.texcoord.x = static_cast<u16>(it.texcoord.x + intersectionL - instanceL);
+    target.texcoord.y = static_cast<u16>(it.texcoord.y + intersectionT - instanceT);
+    target.color = cursorColor == 0xffffffff ? it.color ^ 0xc0c0c0 : c.foreground;
+
+    return addedInstances;
+}
+
+void BackendD3D::_drawSelection(const RenderingPayload& p)
+{
+    u16 y = 0;
+    u16 lastFrom = 0;
+    u16 lastTo = 0;
+
+    for (const auto& row : p.rows)
+    {
+        if (row->selectionTo > row->selectionFrom)
+        {
+            // If the current selection line matches the previous one, we can just extend the previous quad downwards.
+            // The way this is implemented isn't very smart, but we also don't have very many rows to iterate through.
+            if (row->selectionFrom == lastFrom && row->selectionTo == lastTo)
+            {
+                _getLastQuad().size.y += p.s->font->cellSize.y;
+            }
+            else
+            {
+                _appendQuad() = {
+                    .shadingType = ShadingType::Selection,
+                    .position = {
+                        p.s->font->cellSize.x * row->selectionFrom,
+                        p.s->font->cellSize.y * y,
+                    },
+                    .size = {
+                        static_cast<u16>(p.s->font->cellSize.x * (row->selectionTo - row->selectionFrom)),
+                        p.s->font->cellSize.y,
+                    },
+                    .color = p.s->misc->selectionColor,
+                };
+                lastFrom = row->selectionFrom;
+                lastTo = row->selectionTo;
+            }
+        }
+
+        y++;
+    }
+}
+
+#if ATLAS_DEBUG_SHOW_DIRTY
+void BackendD3D::_debugShowDirty(const RenderingPayload& p)
+{
+    _presentRects[_presentRectsPos] = p.dirtyRectInPx;
+    _presentRectsPos = (_presentRectsPos + 1) % std::size(_presentRects);
+
+    for (size_t i = 0; i < std::size(_presentRects); ++i)
+    {
+        if (const auto& rect = _presentRects[i])
+        {
+            _appendQuad() = {
+                .shadingType = ShadingType::SolidFill,
+                .position = {
+                    static_cast<i16>(rect.left),
+                    static_cast<i16>(rect.top),
+                },
+                .size = {
+                    static_cast<u16>(rect.right - rect.left),
+                    static_cast<u16>(rect.bottom - rect.top),
+                },
+                .color = colorbrewer::pastel1[i] | 0x1f000000,
+            };
+        }
+    }
+}
+#endif
+
+#if ATLAS_DEBUG_DUMP_RENDER_TARGET
+void BackendD3D::_debugDumpRenderTarget(const RenderingPayload& p)
+{
+    if (_dumpRenderTargetCounter == 0)
+    {
+        ExpandEnvironmentStringsW(ATLAS_DEBUG_DUMP_RENDER_TARGET_PATH, &_dumpRenderTargetBasePath[0], gsl::narrow_cast<DWORD>(std::size(_dumpRenderTargetBasePath)));
+        std::filesystem::create_directories(_dumpRenderTargetBasePath);
+    }
+
+    wchar_t path[MAX_PATH];
+    swprintf_s(path, L"%s\\%u_%08zu.png", &_dumpRenderTargetBasePath[0], GetCurrentProcessId(), _dumpRenderTargetCounter);
+    SaveTextureToPNG(p.deviceContext.get(), _swapChainManager.GetBuffer().get(), p.s->font->dpi, &path[0]);
+    _dumpRenderTargetCounter++;
+}
+#endif
+
+void BackendD3D::_executeCustomShader(RenderingPayload& p)
+{
+    {
+        const CustomConstBuffer data{
+            .time = std::chrono::duration<f32>(std::chrono::steady_clock::now() - _customShaderStartTime).count(),
+            .scale = static_cast<f32>(p.s->font->dpi) / static_cast<f32>(USER_DEFAULT_SCREEN_DPI),
+            .resolution = {
+                static_cast<f32>(_cellCount.x * p.s->font->cellSize.x),
+                static_cast<f32>(_cellCount.y * p.s->font->cellSize.y),
+            },
+            .background = colorFromU32Premultiply<f32x4>(p.s->misc->backgroundColor),
+        };
+
+        D3D11_MAPPED_SUBRESOURCE mapped{};
+        THROW_IF_FAILED(p.deviceContext->Map(_customShaderConstantBuffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped));
+        memcpy(mapped.pData, &data, sizeof(data));
+        p.deviceContext->Unmap(_customShaderConstantBuffer.get(), 0);
+    }
+
+    {
+        // Before we do anything else we have to unbound _renderTargetView from being
+        // a render target, otherwise we can't use it as a shader resource below.
+        p.deviceContext->OMSetRenderTargets(1, _renderTargetView.addressof(), nullptr);
+
+        // IA: Input Assembler
+        p.deviceContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_UNKNOWN, 0);
+        p.deviceContext->IASetInputLayout(nullptr);
+        p.deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
+        p.deviceContext->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr);
+
+        // VS: Vertex Shader
+        p.deviceContext->VSSetShader(_customVertexShader.get(), nullptr, 0);
+        p.deviceContext->VSSetConstantBuffers(0, 0, nullptr);
+
+        // PS: Pixel Shader
+        p.deviceContext->PSSetShader(_customPixelShader.get(), nullptr, 0);
+        p.deviceContext->PSSetConstantBuffers(0, 1, _customShaderConstantBuffer.addressof());
+        p.deviceContext->PSSetShaderResources(0, 1, _customOffscreenTextureView.addressof());
+        p.deviceContext->PSSetSamplers(0, 1, _customShaderSamplerState.addressof());
+
+        // OM: Output Merger
+        p.deviceContext->OMSetBlendState(nullptr, nullptr, 0xffffffff);
+    }
+
+    p.deviceContext->Draw(4, 0);
+
+    {
+        // IA: Input Assembler
+        ID3D11Buffer* vertexBuffers[]{ _vertexBuffer.get(), _instanceBuffer.get() };
+        static constexpr UINT strides[]{ sizeof(f32x2), sizeof(QuadInstance) };
+        static constexpr UINT offsets[]{ 0, 0 };
+        p.deviceContext->IASetIndexBuffer(_indexBuffer.get(), DXGI_FORMAT_R16_UINT, 0);
+        p.deviceContext->IASetInputLayout(_inputLayout.get());
+        p.deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+        p.deviceContext->IASetVertexBuffers(0, 2, &vertexBuffers[0], &strides[0], &offsets[0]);
+
+        // VS: Vertex Shader
+        p.deviceContext->VSSetShader(_vertexShader.get(), nullptr, 0);
+        p.deviceContext->VSSetConstantBuffers(0, 1, _vsConstantBuffer.addressof());
+
+        // PS: Pixel Shader
+        ID3D11ShaderResourceView* resources[]{ _backgroundBitmapView.get(), _glyphAtlasView.get() };
+        p.deviceContext->PSSetShader(_pixelShader.get(), nullptr, 0);
+        p.deviceContext->PSSetConstantBuffers(0, 1, _psConstantBuffer.addressof());
+        p.deviceContext->PSSetShaderResources(0, 2, &resources[0]);
+        p.deviceContext->PSSetSamplers(0, 0, nullptr);
+
+        // OM: Output Merger
+        p.deviceContext->OMSetBlendState(_blendState.get(), nullptr, 0xffffffff);
+        p.deviceContext->OMSetRenderTargets(1, _customRenderTargetView.addressof(), nullptr);
+    }
+
+    // With custom shaders, everything might be invalidated, so we have to
+    // indirectly disable Present1() and its dirty rects this way.
+    p.dirtyRectInPx = { 0, 0, p.s->targetSize.x, p.s->targetSize.y };
+}
+
+TIL_FAST_MATH_END
diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h
new file mode 100644
index 00000000000..c5cf07e6553
--- /dev/null
+++ b/src/renderer/atlas/BackendD3D.h
@@ -0,0 +1,311 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+#include <stb_rect_pack.h>
+#include <til/flat_set.h>
+#include <til/small_vector.h>
+
+#include "Backend.h"
+
+namespace Microsoft::Console::Render::Atlas
+{
+    struct BackendD3D : IBackend
+    {
+        BackendD3D(const RenderingPayload& p);
+
+        void ReleaseResources() noexcept override;
+        void Render(RenderingPayload& payload) override;
+        bool RequiresContinuousRedraw() noexcept override;
+
+        // NOTE: D3D constant buffers sizes must be a multiple of 16 bytes.
+        struct alignas(16) VSConstBuffer
+        {
+            // WARNING: Modify this carefully after understanding how HLSL struct packing works. The gist is:
+            // * Minimum alignment is 4 bytes
+            // * Members cannot straddle 16 byte boundaries
+            //   This means a structure like {u32; u32; u32; u32x2} would require
+            //   padding so that it is {u32; u32; u32; <4 byte padding>; u32x2}.
+            // * bool will probably not work the way you want it to,
+            //   because HLSL uses 32-bit bools and C++ doesn't.
+            alignas(sizeof(f32x2)) f32x2 positionScale;
+#pragma warning(suppress : 4324) // 'VSConstBuffer': structure was padded due to alignment specifier
+        };
+
+        // WARNING: Same rules as for VSConstBuffer above apply.
+        struct alignas(16) PSConstBuffer
+        {
+            alignas(sizeof(f32x4)) f32x4 backgroundColor;
+            alignas(sizeof(f32x2)) f32x2 cellSize;
+            alignas(sizeof(f32x2)) f32x2 cellCount;
+            alignas(sizeof(f32x4)) f32 gammaRatios[4]{};
+            alignas(sizeof(f32)) f32 enhancedContrast = 0;
+            alignas(sizeof(f32)) f32 underlineWidth = 0;
+#pragma warning(suppress : 4324) // 'PSConstBuffer': structure was padded due to alignment specifier
+        };
+
+        // WARNING: Same rules as for VSConstBuffer above apply.
+        struct alignas(16) CustomConstBuffer
+        {
+            alignas(sizeof(f32)) f32 time = 0;
+            alignas(sizeof(f32)) f32 scale = 0;
+            alignas(sizeof(f32x2)) f32x2 resolution;
+            alignas(sizeof(f32x4)) f32x4 background;
+#pragma warning(suppress : 4324) // 'CustomConstBuffer': structure was padded due to alignment specifier
+        };
+
+        enum class ShadingType : u32
+        {
+            Default = 0,
+            Background = 0,
+
+            // This block of values will be used for the TextDrawingFirst/Last range and need to stay together.
+            // This is used to quickly check if an instance is related to a "text drawing primitive".
+            TextGrayscale = 1,
+            TextClearType = 2,
+            TextPassthrough = 3,
+            DottedLine = 4,
+            DottedLineWide = 5,
+            // All items starting here will be drawing as a solid RGBA color
+            SolidLine = 6,
+
+            Cursor = 7,
+            Selection = 8,
+
+            TextDrawingFirst = TextGrayscale,
+            TextDrawingLast = SolidLine,
+        };
+
+        // NOTE: Don't initialize any members in this struct. This ensures that no
+        // zero-initialization needs to occur when we allocate large buffers of this object.
+        struct QuadInstance
+        {
+            // `position` might clip outside of the bounds of the viewport and so it needs to be a
+            // signed coordinate. i16x2 is used as the size of the instance buffer made the largest
+            // impact on performance and power draw. If (when?) displays with >32k resolution make their
+            // appearance in the future, this should be changed to f32x2. But if you do so, please change
+            // all other occurrences of i16x2 positions/offsets throughout the class to keep it consistent.
+            alignas(u32) ShadingType shadingType;
+            alignas(u32) i16x2 position;
+            alignas(u32) u16x2 size;
+            alignas(u32) u16x2 texcoord;
+            alignas(u32) u32 color;
+        };
+
+        struct alignas(u32) AtlasGlyphEntryData
+        {
+            u16 shadingType;
+            u16 overlapSplit;
+            i16x2 offset;
+            u16x2 size;
+            u16x2 texcoord;
+
+            constexpr ShadingType GetShadingType() const noexcept
+            {
+                return static_cast<ShadingType>(shadingType);
+            }
+        };
+
+        // NOTE: Don't initialize any members in this struct. This ensures that no
+        // zero-initialization needs to occur when we allocate large buffers of this object.
+        struct AtlasGlyphEntry
+        {
+            u16 glyphIndex;
+            // All data in QuadInstance is u32-aligned anyways, so this simultaneously serves as padding.
+            u16 _occupied;
+
+            AtlasGlyphEntryData data;
+
+            constexpr bool operator==(u16 key) const noexcept
+            {
+                return glyphIndex == key;
+            }
+
+            constexpr operator bool() const noexcept
+            {
+                return _occupied != 0;
+            }
+
+            constexpr AtlasGlyphEntry& operator=(u16 key) noexcept
+            {
+                glyphIndex = key;
+                _occupied = 1;
+                return *this;
+            }
+        };
+
+        // This exists so that we can look up a AtlasFontFaceEntry without AddRef()/Release()ing fontFace first.
+        struct AtlasFontFaceKey
+        {
+            IDWriteFontFace2* fontFace;
+            LineRendition lineRendition;
+        };
+
+        struct AtlasFontFaceEntryInner
+        {
+            // BODGY: At the time of writing IDWriteFontFallback::MapCharacters returns the same IDWriteFontFace instance
+            // for the same font face variant as long as someone is holding a reference to the instance (see ActiveFaceCache).
+            // This allows us to hash the value of the pointer as if it was uniquely identifying the font face variant.
+            wil::com_ptr<IDWriteFontFace2> fontFace;
+            LineRendition lineRendition = LineRendition::SingleWidth;
+
+            til::linear_flat_set<AtlasGlyphEntry> glyphs;
+        };
+
+        struct AtlasFontFaceEntry
+        {
+            // This being a heap allocated allows us to insert into `glyphs` in `_splitDoubleHeightGlyph`
+            // (which might resize the hashmap!), while the caller `_drawText` is holding onto `glyphs`.
+            // If it wasn't heap allocated, all pointers into `linear_flat_set` would be invalidated.
+            std::unique_ptr<AtlasFontFaceEntryInner> inner;
+
+            bool operator==(const AtlasFontFaceKey& key) const noexcept
+            {
+                const auto& i = *inner;
+                return i.fontFace.get() == key.fontFace && i.lineRendition == key.lineRendition;
+            }
+
+            operator bool() const noexcept
+            {
+                return static_cast<bool>(inner);
+            }
+
+            AtlasFontFaceEntry& operator=(const AtlasFontFaceKey& key)
+            {
+                inner = std::make_unique<AtlasFontFaceEntryInner>();
+                auto& i = *inner;
+                i.fontFace = key.fontFace;
+                i.lineRendition = key.lineRendition;
+                return *this;
+            }
+        };
+
+    private:
+        struct CursorRect
+        {
+            i16x2 position;
+            u16x2 size;
+            u32 background;
+            u32 foreground;
+        };
+
+        ATLAS_ATTR_COLD void _handleSettingsUpdate(const RenderingPayload& p);
+        void _updateFontDependents(const RenderingPayload& p);
+        void _d2dRenderTargetUpdateFontSettings(const RenderingPayload& p) const noexcept;
+        void _recreateCustomShader(const RenderingPayload& p);
+        void _recreateCustomRenderTargetView(const RenderingPayload& p);
+        void _recreateBackgroundColorBitmap(const RenderingPayload& p);
+        void _recreateConstBuffer(const RenderingPayload& p) const;
+        void _setupDeviceContextState(const RenderingPayload& p);
+        void _debugUpdateShaders(const RenderingPayload& p) noexcept;
+        void _debugShowDirty(const RenderingPayload& p);
+        void _debugDumpRenderTarget(const RenderingPayload& p);
+        void _d2dBeginDrawing() noexcept;
+        void _d2dEndDrawing();
+        ATLAS_ATTR_COLD void _resetGlyphAtlas(const RenderingPayload& p);
+        ATLAS_ATTR_COLD void _resizeGlyphAtlas(const RenderingPayload& p, u16 u, u16 v);
+        QuadInstance& _getLastQuad() noexcept;
+        QuadInstance& _appendQuad();
+        ATLAS_ATTR_COLD void _bumpInstancesSize();
+        void _flushQuads(const RenderingPayload& p);
+        ATLAS_ATTR_COLD void _recreateInstanceBuffers(const RenderingPayload& p);
+        void _drawBackground(const RenderingPayload& p);
+        void _uploadBackgroundBitmap(const RenderingPayload& p);
+        void _drawText(RenderingPayload& p);
+        ATLAS_ATTR_COLD void _drawTextOverlapSplit(const RenderingPayload& p, u16 y);
+        ATLAS_ATTR_COLD [[nodiscard]] bool _drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry);
+        bool _drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry);
+        void _drawGlyphPrepareRetry(const RenderingPayload& p);
+        void _splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry);
+        void _drawGridlines(const RenderingPayload& p, u16 y);
+        void _drawCursorBackground(const RenderingPayload& p);
+        ATLAS_ATTR_COLD void _drawCursorForeground(const RenderingPayload& p);
+        ATLAS_ATTR_COLD size_t _drawCursorForegroundSlowPath(const RenderingPayload& p, const CursorRect& c, size_t offset);
+        void _drawSelection(const RenderingPayload& p);
+        void _executeCustomShader(RenderingPayload& p);
+
+        wil::com_ptr<ID3D11RenderTargetView> _renderTargetView;
+        wil::com_ptr<ID3D11InputLayout> _inputLayout;
+        wil::com_ptr<ID3D11VertexShader> _vertexShader;
+        wil::com_ptr<ID3D11PixelShader> _pixelShader;
+        wil::com_ptr<ID3D11BlendState> _blendState;
+        wil::com_ptr<ID3D11Buffer> _vsConstantBuffer;
+        wil::com_ptr<ID3D11Buffer> _psConstantBuffer;
+        wil::com_ptr<ID3D11Buffer> _vertexBuffer;
+        wil::com_ptr<ID3D11Buffer> _indexBuffer;
+        wil::com_ptr<ID3D11Buffer> _instanceBuffer;
+        size_t _instanceBufferCapacity = 0;
+        Buffer<QuadInstance, 32> _instances;
+        size_t _instancesCount = 0;
+
+        wil::com_ptr<ID3D11RenderTargetView> _customRenderTargetView;
+        wil::com_ptr<ID3D11Texture2D> _customOffscreenTexture;
+        wil::com_ptr<ID3D11ShaderResourceView> _customOffscreenTextureView;
+        wil::com_ptr<ID3D11VertexShader> _customVertexShader;
+        wil::com_ptr<ID3D11PixelShader> _customPixelShader;
+        wil::com_ptr<ID3D11Buffer> _customShaderConstantBuffer;
+        wil::com_ptr<ID3D11SamplerState> _customShaderSamplerState;
+        std::chrono::steady_clock::time_point _customShaderStartTime;
+
+        wil::com_ptr<ID3D11Texture2D> _backgroundBitmap;
+        wil::com_ptr<ID3D11ShaderResourceView> _backgroundBitmapView;
+        til::generation_t _backgroundBitmapGeneration;
+
+        wil::com_ptr<ID3D11Texture2D> _glyphAtlas;
+        wil::com_ptr<ID3D11ShaderResourceView> _glyphAtlasView;
+        til::linear_flat_set<AtlasFontFaceEntry> _glyphAtlasMap;
+        Buffer<stbrp_node> _rectPackerData;
+        stbrp_context _rectPacker{};
+        til::CoordType _ligatureOverhangTriggerLeft = 0;
+        til::CoordType _ligatureOverhangTriggerRight = 0;
+
+        wil::com_ptr<ID2D1DeviceContext> _d2dRenderTarget;
+        wil::com_ptr<ID2D1DeviceContext4> _d2dRenderTarget4; // Optional. Supported since Windows 10 14393.
+        wil::com_ptr<ID2D1SolidColorBrush> _emojiBrush;
+        wil::com_ptr<ID2D1SolidColorBrush> _brush;
+        wil::com_ptr<ID2D1Bitmap1> _softFontBitmap;
+        bool _d2dBeganDrawing = false;
+        bool _fontChangedResetGlyphAtlas = false;
+
+        float _gamma = 0;
+        float _cleartypeEnhancedContrast = 0;
+        float _grayscaleEnhancedContrast = 0;
+        wil::com_ptr<IDWriteRenderingParams1> _textRenderingParams;
+
+        til::generation_t _generation;
+        til::generation_t _fontGeneration;
+        til::generation_t _miscGeneration;
+        u16x2 _targetSize{};
+        u16x2 _cellCount{};
+        ShadingType _textShadingType = ShadingType::Default;
+
+        // An empty-box cursor spanning a wide glyph that has different
+        // background colors on each side results in 6 lines being drawn.
+        til::small_vector<CursorRect, 6> _cursorRects;
+        // The bounding rect of _cursorRects in pixels.
+        til::rect _cursorPosition;
+
+        bool _requiresContinuousRedraw = false;
+
+#if ATLAS_DEBUG_SHOW_DIRTY
+        til::rect _presentRects[9]{};
+        size_t _presentRectsPos = 0;
+#endif
+
+#if ATLAS_DEBUG_DUMP_RENDER_TARGET
+        wchar_t _dumpRenderTargetBasePath[MAX_PATH]{};
+        size_t _dumpRenderTargetCounter = 0;
+#endif
+
+#if ATLAS_DEBUG_COLORIZE_GLYPH_ATLAS
+        size_t _colorizeGlyphAtlasCounter = 0;
+#endif
+
+#ifndef NDEBUG
+        std::filesystem::path _sourceDirectory;
+        wil::unique_folder_change_reader_nothrow _sourceCodeWatcher;
+        std::atomic<int64_t> _sourceCodeInvalidationTime{ INT64_MAX };
+#endif
+    };
+}
diff --git a/src/renderer/atlas/DWriteTextAnalysis.cpp b/src/renderer/atlas/DWriteTextAnalysis.cpp
index 8b4ba477349..af43f75ecaf 100644
--- a/src/renderer/atlas/DWriteTextAnalysis.cpp
+++ b/src/renderer/atlas/DWriteTextAnalysis.cpp
@@ -1,10 +1,13 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
 #include "pch.h"
 #include "DWriteTextAnalysis.h"
 
 #pragma warning(disable : 4100) // '...': unreferenced formal parameter
 #pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
 
-using namespace Microsoft::Console::Render;
+using namespace Microsoft::Console::Render::Atlas;
 
 TextAnalysisSource::TextAnalysisSource(const wchar_t* _text, const UINT32 _textLength) noexcept :
     _text{ _text },
@@ -151,7 +154,8 @@ HRESULT TextAnalysisSink::QueryInterface(const IID& riid, void** ppvObject) noex
 HRESULT __stdcall TextAnalysisSink::SetScriptAnalysis(UINT32 textPosition, UINT32 textLength, const DWRITE_SCRIPT_ANALYSIS* scriptAnalysis) noexcept
 try
 {
-    _results.emplace_back(TextAnalysisSinkResult{ textPosition, textLength, scriptAnalysis->script, static_cast<UINT8>(scriptAnalysis->shapes), 0 });
+    __assume(scriptAnalysis != nullptr);
+    _results.emplace_back(textPosition, textLength, *scriptAnalysis);
     return S_OK;
 }
 CATCH_RETURN()
diff --git a/src/renderer/atlas/DWriteTextAnalysis.h b/src/renderer/atlas/DWriteTextAnalysis.h
index 15490ca868f..a970127a5a3 100644
--- a/src/renderer/atlas/DWriteTextAnalysis.h
+++ b/src/renderer/atlas/DWriteTextAnalysis.h
@@ -3,21 +3,10 @@
 
 #pragma once
 
-namespace Microsoft::Console::Render
-{
-    struct TextAnalysisSinkResult
-    {
-        uint32_t textPosition = 0;
-        uint32_t textLength = 0;
-
-        // These 2 fields represent DWRITE_SCRIPT_ANALYSIS.
-        // Not using DWRITE_SCRIPT_ANALYSIS drops the struct size from 20 down to 12 bytes.
-        uint16_t script = 0;
-        uint8_t shapes = 0;
-
-        uint8_t bidiLevel = 0;
-    };
+#include "common.h"
 
+namespace Microsoft::Console::Render::Atlas
+{
     struct TextAnalysisSource final : IDWriteTextAnalysisSource
     {
         TextAnalysisSource(const wchar_t* _text, const UINT32 _textLength) noexcept;
diff --git a/src/renderer/atlas/README.md b/src/renderer/atlas/README.md
new file mode 100644
index 00000000000..e5e87f2ca0f
--- /dev/null
+++ b/src/renderer/atlas/README.md
@@ -0,0 +1,120 @@
+# AtlasEngine
+
+## General architecture overview
+
+```mermaid
+graph TD
+    RenderThread["RenderThread (base/thread.cpp)\n<small>calls Renderer::PaintFrame() x times per sec</small>"]
+    Renderer["Renderer (base/renderer.cpp)\n<small>breaks the text buffer down into GDI-oriented graphics\nprimitives (#quot;change brush to color X#quot;, #quot;draw string Y#quot;, ...)</small>"]
+    RenderEngineBase[/"RenderEngineBase\n(base/RenderEngineBase.cpp)\n<small>abstracts 24 LOC 👻</small>"\]
+    GdiEngine["GdiEngine (gdi/...)"]
+    DxEngine["DxEngine (dx/...)"]
+
+    subgraph AtlasEngine["AtlasEngine (atlas/...)"]
+        AtlasEngine.cpp["AtlasEngine.cpp\n<small>Implements IRenderEngine text rendering API\nbreaks GDI graphics primitives down into DWRITE_GLYPH_RUNs</small>"]
+        AtlasEngine.api.cpp["AtlasEngine.api.cpp\n<small>Implements the parts run inside the console\nlock (many IRenderEngine setters)<small>"]
+        AtlasEngine.r.cpp["AtlasEngine.r.cpp\n<small>Implements the parts run\noutside of the console lock<small>"]
+        Backend.cpp["Backend.cpp\n<small>Implements common functionality/helpers</small>"]
+        BackendD2D.cpp["BackendD2D.cpp\n<small>Pure Direct2D text renderer (for low latency\nremote desktop and older/no GPUs)</small>"]
+        BackendD3D.cpp["BackendD3D.cpp\n<small>Custom, performant text renderer\nwith our own glyph cache</small>"]
+    end
+
+    RenderThread --> Renderer
+    Renderer -->|owns| RenderThread
+    Renderer -.-> RenderEngineBase
+    %% Mermaid.js has no support for backwards arrow at the moment
+    RenderEngineBase <-.->|extends| GdiEngine
+    RenderEngineBase <-.->|extends| DxEngine
+    Renderer ----> AtlasEngine
+    AtlasEngine.cpp <--> AtlasEngine.api.cpp
+    AtlasEngine.cpp <--> AtlasEngine.r.cpp
+    AtlasEngine.r.cpp --> BackendD2D.cpp
+    AtlasEngine.r.cpp --> BackendD3D.cpp
+    BackendD2D.cpp -.- Backend.cpp
+    BackendD3D.cpp -.- Backend.cpp
+```
+
+As you can see, breaking the text buffer down into GDI-style primitives just to rebuild them into DirectWrite ones, is pretty wasteful. It's also incredibly bug prone. It would be beneficial if the TextBuffer and rendering settings were given directly to AtlasEngine so it can do its own bidding.
+
+## BackendD3D
+
+The primary entrypoint for rendering is `IBackend::Render` and `BackendD3D` implements it via the following functions, by calling them one by one in the order listed here.
+
+### `_handleSettingsUpdate`
+
+```mermaid
+graph TD
+    Render --> _handleSettingsUpdate
+    _handleSettingsUpdate -->|font changes| _updateFontDependents --> _d2dRenderTargetUpdateFontSettings
+    _handleSettingsUpdate -->|misc changes| _recreateCustomShader
+    _handleSettingsUpdate --->|misc changes| _recreateCustomRenderTargetView
+    _handleSettingsUpdate ---->|size changes| _recreateBackgroundColorBitmap
+    _handleSettingsUpdate -----> _recreateConstBuffer
+    _handleSettingsUpdate ------> _setupDeviceContextState
+```
+
+### `_drawBackground`
+
+```mermaid
+graph TD
+    Render --> _drawBackground
+    _drawBackground --> _uploadBackgroundBitmap
+```
+
+### `_drawCursorPart1` / `_drawCursorPart2`
+
+```mermaid
+graph TD
+    Render --> _drawCursorPart1["_drawCursorPart1\nruns before _drawText\ndraws cursors that are behind the text"]
+    Render --> _drawCursorPart2["_drawCursorPart2\nruns after _drawText\ndraws inverted cursors"]
+    _drawCursorPart1 -.->|_cursorRects| _drawCursorPart2
+```
+
+### `_drawText`
+
+```mermaid
+graph TD
+    Render --> _drawText
+
+    _drawText --> foreachRow(("for each row"))
+    foreachRow --> foreachRow
+    foreachRow --> foreachFont(("for each font face"))
+    foreachFont --> foreachFont
+    foreachFont --> foreachGlyph(("for each glyph"))
+    foreachGlyph --> foreachGlyph
+
+    foreachGlyph --> _glyphAtlasMap[("font/glyph-pair lookup in\nglyph cache hashmap")]
+    _glyphAtlasMap --> drawGlyph
+    drawGlyph --> _appendQuad["_appendQuad\n<small>stages the glyph for later drawing</small>"]
+    _glyphAtlasMap --> _appendQuad
+
+    subgraph drawGlyph["if glyph is missing"]
+        _drawGlyph["_drawGlyph\n<small>(defers to _drawSoftFontGlyph for soft fonts)</small>"]
+
+        _drawGlyph -.->|if glpyh cache is full| _drawGlyphPrepareRetry
+        _drawGlyphPrepareRetry --> _flushQuads["_flushQuads\n<small>draws the current state\ninto the render target</small>"]
+        _flushQuads --> _recreateInstanceBuffers["_recreateInstanceBuffers\n<small>allocates a GPU buffer\nfor our glyph instances</small>"]
+        _drawGlyphPrepareRetry --> _resetGlyphAtlas["_resetGlyphAtlas\n<small>clears the glyph texture</small>"]
+        _resetGlyphAtlas --> _resizeGlyphAtlas["_resizeGlyphAtlas\n<small>resizes the glyph texture if it's still small</small>"]
+
+        _drawGlyph -.->|if it's a DECDHL glyph| _splitDoubleHeightGlyph["_splitDoubleHeightGlyph\n<small>DECDHL glyphs are split up into their\ntop/bottom halves to emulate clip rects</small>"]
+    end
+
+    foreachGlyph -.-> _drawTextOverlapSplit["_drawTextOverlapSplit\n<small>splits overly wide glyphs up into smaller chunks to support\nforeground color changes within the ligature</small>"]
+
+    foreachRow -.->|if gridlines exist| _drawGridlineRow["_drawGridlineRow\n<small>draws underlines, etc.</small>"]
+```
+
+### `_drawSelection`
+
+```mermaid
+graph TD
+    Render --> _drawSelection
+```
+
+### `_handleSettingsUpdate`
+
+```mermaid
+graph TD
+    Render --> _executeCustomShader
+```
diff --git a/src/renderer/atlas/atlas.vcxproj b/src/renderer/atlas/atlas.vcxproj
index e9907d15490..689ec6ff8d7 100644
--- a/src/renderer/atlas/atlas.vcxproj
+++ b/src/renderer/atlas/atlas.vcxproj
@@ -13,25 +13,35 @@
   <ItemGroup>
     <ClCompile Include="AtlasEngine.api.cpp" />
     <ClCompile Include="AtlasEngine.r.cpp" />
+    <ClCompile Include="Backend.cpp" />
+    <ClCompile Include="BackendD2D.cpp" />
+    <ClCompile Include="BackendD3D.cpp" />
     <ClCompile Include="dwrite.cpp" />
     <ClCompile Include="DWriteTextAnalysis.cpp" />
     <ClCompile Include="pch.cpp">
       <PrecompiledHeader>Create</PrecompiledHeader>
     </ClCompile>
     <ClCompile Include="AtlasEngine.cpp" />
+    <ClCompile Include="stb_rect_pack.cpp" />
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="Backend.h" />
+    <ClInclude Include="BackendD2D.h" />
+    <ClInclude Include="BackendD3D.h" />
+    <ClInclude Include="colorbrewer.h" />
+    <ClInclude Include="common.h" />
     <ClInclude Include="dwrite.h" />
     <ClInclude Include="DWriteTextAnalysis.h" />
     <ClInclude Include="pch.h" />
     <ClInclude Include="AtlasEngine.h" />
+    <ClInclude Include="wic.h" />
   </ItemGroup>
   <ItemGroup>
     <FxCompile Include="custom_shader_ps.hlsl">
       <ShaderType>Pixel</ShaderType>
       <ShaderModel>4.0</ShaderModel>
       <AllResourcesBound>true</AllResourcesBound>
-      <VariableName>custom_shader_ps</VariableName>
+      <VariableName>%(Filename)</VariableName>
       <ObjectFileOutput />
       <HeaderFileOutput>$(OutDir)$(ProjectName)\%(Filename).h</HeaderFileOutput>
       <TreatWarningAsError>true</TreatWarningAsError>
@@ -42,7 +52,7 @@
       <ShaderType>Vertex</ShaderType>
       <ShaderModel>4.0</ShaderModel>
       <AllResourcesBound>true</AllResourcesBound>
-      <VariableName>custom_shader_vs</VariableName>
+      <VariableName>%(Filename)</VariableName>
       <ObjectFileOutput />
       <HeaderFileOutput>$(OutDir)$(ProjectName)\%(Filename).h</HeaderFileOutput>
       <TreatWarningAsError>true</TreatWarningAsError>
@@ -52,11 +62,14 @@
     <FxCompile Include="dwrite.hlsl">
       <ExcludedFromBuild>true</ExcludedFromBuild>
     </FxCompile>
+    <FxCompile Include="shader_common.hlsl">
+      <ExcludedFromBuild>true</ExcludedFromBuild>
+    </FxCompile>
     <FxCompile Include="shader_ps.hlsl">
       <ShaderType>Pixel</ShaderType>
       <ShaderModel>4.0</ShaderModel>
       <AllResourcesBound>true</AllResourcesBound>
-      <VariableName>shader_ps</VariableName>
+      <VariableName>%(Filename)</VariableName>
       <ObjectFileOutput />
       <HeaderFileOutput>$(OutDir)$(ProjectName)\%(Filename).h</HeaderFileOutput>
       <TreatWarningAsError>true</TreatWarningAsError>
@@ -67,7 +80,7 @@
       <ShaderType>Vertex</ShaderType>
       <ShaderModel>4.0</ShaderModel>
       <AllResourcesBound>true</AllResourcesBound>
-      <VariableName>shader_vs</VariableName>
+      <VariableName>%(Filename)</VariableName>
       <ObjectFileOutput />
       <HeaderFileOutput>$(OutDir)$(ProjectName)\%(Filename).h</HeaderFileOutput>
       <TreatWarningAsError>true</TreatWarningAsError>
@@ -75,12 +88,15 @@
       <AdditionalOptions Condition="'$(Configuration)'=='Release'">/O3 /Qstrip_debug /Qstrip_reflect %(AdditionalOptions)</AdditionalOptions>
     </FxCompile>
   </ItemGroup>
+  <ItemGroup>
+    <None Include="README.md" />
+  </ItemGroup>
   <Import Project="$(SolutionDir)src\common.build.post.props" />
   <Import Project="$(SolutionDir)src\common.nugetversions.targets" />
   <ItemDefinitionGroup>
     <ClCompile>
       <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
-      <AdditionalIncludeDirectories>$(OutDir)$(ProjectName)\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(SolutionDir)\oss\stb;$(OutDir)$(ProjectName);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
     </ClCompile>
   </ItemDefinitionGroup>
 </Project>
diff --git a/src/renderer/atlas/colorbrewer.h b/src/renderer/atlas/colorbrewer.h
new file mode 100644
index 00000000000..be5cef6b8a6
--- /dev/null
+++ b/src/renderer/atlas/colorbrewer.h
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+namespace Microsoft::Console::Render::Atlas::colorbrewer
+{
+    // The following list of colors is only used as a debug aid and not part of the final product.
+    // They're licensed under:
+    //
+    //   Apache-Style Software License for ColorBrewer software and ColorBrewer Color Schemes
+    //
+    //   Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University.
+    //
+    //   Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
+    //   You may obtain a copy of the License at
+    //
+    //   http://www.apache.org/licenses/LICENSE-2.0
+    //
+    //   Unless required by applicable law or agreed to in writing, software distributed
+    //   under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+    //   CONDITIONS OF ANY KIND, either express or implied. See the License for the
+    //   specific language governing permissions and limitations under the License.
+    //
+    inline constexpr u32 pastel1[]{
+        0xfbb4ae,
+        0xb3cde3,
+        0xccebc5,
+        0xdecbe4,
+        0xfed9a6,
+        0xffffcc,
+        0xe5d8bd,
+        0xfddaec,
+        0xf2f2f2,
+    };
+}
diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h
new file mode 100644
index 00000000000..42a02894fa0
--- /dev/null
+++ b/src/renderer/atlas/common.h
@@ -0,0 +1,557 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+#include <til/generational.h>
+
+#include "../../renderer/inc/IRenderEngine.hpp"
+
+namespace Microsoft::Console::Render::Atlas
+{
+#define ATLAS_FLAG_OPS(type, underlying)                                                       \
+    constexpr type operator~(type v) noexcept                                                  \
+    {                                                                                          \
+        return static_cast<type>(~static_cast<underlying>(v));                                 \
+    }                                                                                          \
+    constexpr type operator|(type lhs, type rhs) noexcept                                      \
+    {                                                                                          \
+        return static_cast<type>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs)); \
+    }                                                                                          \
+    constexpr type operator&(type lhs, type rhs) noexcept                                      \
+    {                                                                                          \
+        return static_cast<type>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs)); \
+    }                                                                                          \
+    constexpr type operator^(type lhs, type rhs) noexcept                                      \
+    {                                                                                          \
+        return static_cast<type>(static_cast<underlying>(lhs) ^ static_cast<underlying>(rhs)); \
+    }                                                                                          \
+    constexpr void operator|=(type& lhs, type rhs) noexcept                                    \
+    {                                                                                          \
+        lhs = lhs | rhs;                                                                       \
+    }                                                                                          \
+    constexpr void operator&=(type& lhs, type rhs) noexcept                                    \
+    {                                                                                          \
+        lhs = lhs & rhs;                                                                       \
+    }                                                                                          \
+    constexpr void operator^=(type& lhs, type rhs) noexcept                                    \
+    {                                                                                          \
+        lhs = lhs ^ rhs;                                                                       \
+    }
+
+#define ATLAS_POD_OPS(type)                                    \
+    constexpr bool operator==(const type& rhs) const noexcept  \
+    {                                                          \
+        return __builtin_memcmp(this, &rhs, sizeof(rhs)) == 0; \
+    }                                                          \
+                                                               \
+    constexpr bool operator!=(const type& rhs) const noexcept  \
+    {                                                          \
+        return !(*this == rhs);                                \
+    }
+
+    // My best effort of replicating __attribute__((cold)) from gcc/clang.
+#define ATLAS_ATTR_COLD __declspec(noinline)
+
+    template<typename T>
+    struct vec2
+    {
+        // These members aren't zero-initialized to make these trivial types,
+        // and allow the compiler to quickly memset() allocations, etc.
+        T x;
+        T y;
+
+        ATLAS_POD_OPS(vec2)
+    };
+
+    template<typename T>
+    struct vec4
+    {
+        // These members aren't zero-initialized to make these trivial types,
+        // and allow the compiler to quickly memset() allocations, etc.
+        T x;
+        T y;
+        T z;
+        T w;
+
+        ATLAS_POD_OPS(vec4)
+    };
+
+    template<typename T>
+    struct rect
+    {
+        // These members aren't zero-initialized to make these trivial types,
+        // and allow the compiler to quickly memset() allocations, etc.
+        T left;
+        T top;
+        T right;
+        T bottom;
+
+        ATLAS_POD_OPS(rect)
+
+        constexpr bool empty() const noexcept
+        {
+            return left >= right || top >= bottom;
+        }
+
+        constexpr bool non_empty() const noexcept
+        {
+            return left < right && top < bottom;
+        }
+    };
+
+    template<typename T>
+    struct range
+    {
+        T start;
+        T end;
+
+        ATLAS_POD_OPS(range)
+
+        constexpr bool empty() const noexcept
+        {
+            return start >= end;
+        }
+
+        constexpr bool non_empty() const noexcept
+        {
+            return start < end;
+        }
+
+        constexpr bool contains(T v) const noexcept
+        {
+            return v >= start && v < end;
+        }
+    };
+
+    using u8 = uint8_t;
+
+    using u16 = uint16_t;
+    using u16x2 = vec2<u16>;
+    using u16r = rect<u16>;
+
+    using i16 = int16_t;
+    using i16x2 = vec2<i16>;
+    using i16x4 = vec4<i16>;
+    using i16r = rect<i16>;
+
+    using u32 = uint32_t;
+    using u32x2 = vec2<u32>;
+    using u32x4 = vec4<u32>;
+    using u32r = rect<u32>;
+
+    using i32 = int32_t;
+    using i32x2 = vec2<i32>;
+    using i32x4 = vec4<i32>;
+    using i32r = rect<i32>;
+
+    using f32 = float;
+    using f32x2 = vec2<f32>;
+    using f32x4 = vec4<f32>;
+    using f32r = rect<f32>;
+
+    // I wrote `Buffer` instead of using `std::vector`, because I want to convey that these things
+    // explicitly _don't_ hold resizeable contents, but rather plain content of a fixed size.
+    // For instance I didn't want a resizeable vector with a `push_back` method for my fixed-size
+    // viewport arrays - that doesn't make sense after all. `Buffer` also doesn't initialize
+    // contents to zero, allowing rapid creation/destruction and you can easily specify a custom
+    // (over-)alignment which can improve rendering perf by up to ~20% over `std::vector`.
+    template<typename T, size_t Alignment = alignof(T)>
+    struct Buffer
+    {
+        constexpr Buffer() noexcept = default;
+
+        explicit Buffer(size_t size) :
+            _data{ allocate(size) },
+            _size{ size }
+        {
+            std::uninitialized_default_construct_n(_data, size);
+        }
+
+        Buffer(const T* data, size_t size) :
+            _data{ allocate(size) },
+            _size{ size }
+        {
+            // Changing the constructor arguments to accept std::span might
+            // be a good future extension, but not to improve security here.
+            // You can trivially construct std::span's from invalid ranges.
+            // Until then the raw-pointer style is more practical.
+#pragma warning(suppress : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe [...].
+            std::uninitialized_copy_n(data, size, _data);
+        }
+
+        ~Buffer()
+        {
+            destroy();
+        }
+
+        Buffer(const Buffer& other) = delete;
+        Buffer& operator=(const Buffer& other) = delete;
+
+        Buffer(Buffer&& other) noexcept :
+            _data{ std::exchange(other._data, nullptr) },
+            _size{ std::exchange(other._size, 0) }
+        {
+        }
+
+        Buffer& operator=(Buffer&& other) noexcept
+        {
+            destroy();
+            _data = std::exchange(other._data, nullptr);
+            _size = std::exchange(other._size, 0);
+            return *this;
+        }
+
+        explicit operator bool() const noexcept
+        {
+            return _data != nullptr;
+        }
+
+        T& operator[](size_t index) noexcept
+        {
+            assert(index < _size);
+#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
+            return _data[index];
+        }
+
+        const T& operator[](size_t index) const noexcept
+        {
+            assert(index < _size);
+#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
+            return _data[index];
+        }
+
+        T* data() noexcept
+        {
+            return _data;
+        }
+
+        const T* data() const noexcept
+        {
+            return _data;
+        }
+
+        size_t size() const noexcept
+        {
+            return _size;
+        }
+
+        T* begin() noexcept
+        {
+            return _data;
+        }
+
+        const T* begin() const noexcept
+        {
+            return _data;
+        }
+
+        T* end() noexcept
+        {
+#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
+            return _data + _size;
+        }
+
+        const T* end() const noexcept
+        {
+#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
+            return _data + _size;
+        }
+
+    private:
+        // These two functions don't need to use scoped objects or standard allocators,
+        // since this class is in fact an scoped allocator object itself.
+#pragma warning(push)
+#pragma warning(disable : 26402) // Return a scoped object instead of a heap-allocated if it has a move constructor (r.3).
+#pragma warning(disable : 26409) // Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11).
+        static T* allocate(size_t size)
+        {
+            if (!size)
+            {
+                return nullptr;
+            }
+            if constexpr (Alignment <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
+            {
+                return static_cast<T*>(::operator new(size * sizeof(T)));
+            }
+            else
+            {
+                return static_cast<T*>(::operator new(size * sizeof(T), static_cast<std::align_val_t>(Alignment)));
+            }
+        }
+
+        static void deallocate(T* data) noexcept
+        {
+            if constexpr (Alignment <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
+            {
+                ::operator delete(data);
+            }
+            else
+            {
+                ::operator delete(data, static_cast<std::align_val_t>(Alignment));
+            }
+        }
+#pragma warning(pop)
+
+        void destroy() noexcept
+        {
+            std::destroy_n(_data, _size);
+            deallocate(_data);
+        }
+
+        T* _data = nullptr;
+        size_t _size = 0;
+    };
+
+    struct TextAnalysisSinkResult
+    {
+        uint32_t textPosition;
+        uint32_t textLength;
+        DWRITE_SCRIPT_ANALYSIS analysis;
+    };
+
+    struct TargetSettings
+    {
+        HWND hwnd = nullptr;
+        bool enableTransparentBackground = false;
+        bool useSoftwareRendering = false;
+    };
+
+    enum class AntialiasingMode : u8
+    {
+        ClearType = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE,
+        Grayscale = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE,
+        Aliased = D2D1_TEXT_ANTIALIAS_MODE_ALIASED,
+    };
+
+    inline constexpr auto DefaultAntialiasingMode = AntialiasingMode::ClearType;
+
+    struct FontDecorationPosition
+    {
+        u16 position = 0;
+        u16 height = 0;
+    };
+
+    struct FontSettings
+    {
+        wil::com_ptr<IDWriteFontCollection> fontCollection;
+        wil::com_ptr<IDWriteFontFamily> fontFamily;
+        std::wstring fontName;
+        std::vector<DWRITE_FONT_FEATURE> fontFeatures;
+        std::vector<DWRITE_FONT_AXIS_VALUE> fontAxisValues;
+        f32 fontSize = 0;
+        u16x2 cellSize;
+        u16 fontWeight = 0;
+        u16 advanceWidth = 0;
+        u16 baseline = 0;
+        u16 descender = 0;
+        u16 thinLineWidth = 0;
+
+        FontDecorationPosition gridTop;
+        FontDecorationPosition gridBottom;
+        FontDecorationPosition gridLeft;
+        FontDecorationPosition gridRight;
+
+        FontDecorationPosition underline;
+        FontDecorationPosition strikethrough;
+        FontDecorationPosition doubleUnderline[2];
+        FontDecorationPosition overline;
+
+        u16 dpi = 96;
+        AntialiasingMode antialiasingMode = DefaultAntialiasingMode;
+
+        std::vector<uint16_t> softFontPattern;
+        til::size softFontCellSize;
+    };
+
+    struct CursorSettings
+    {
+        ATLAS_POD_OPS(CursorSettings)
+
+        u32 cursorColor = 0xffffffff;
+        u16 cursorType = 0;
+        u16 heightPercentage = 20;
+    };
+
+    struct MiscellaneousSettings
+    {
+        u32 backgroundColor = 0;
+        u32 selectionColor = 0x7fffffff;
+        std::wstring customPixelShaderPath;
+        bool useRetroTerminalEffect = false;
+    };
+
+    struct Settings
+    {
+        til::generational<TargetSettings> target;
+        til::generational<FontSettings> font;
+        til::generational<CursorSettings> cursor;
+        til::generational<MiscellaneousSettings> misc;
+        u16x2 targetSize{};
+        u16x2 cellCount{};
+    };
+
+    using GenerationalSettings = til::generational<Settings>;
+
+    inline GenerationalSettings DirtyGenerationalSettings() noexcept
+    {
+        return GenerationalSettings{
+            til::generation_t{ 1 },
+            til::generational<TargetSettings>{ til::generation_t{ 1 } },
+            til::generational<FontSettings>{ til::generation_t{ 1 } },
+            til::generational<CursorSettings>{ til::generation_t{ 1 } },
+            til::generational<MiscellaneousSettings>{ til::generation_t{ 1 } },
+        };
+    }
+
+    enum class FontRelevantAttributes : u8
+    {
+        None = 0,
+        Bold = 0b01,
+        Italic = 0b10,
+    };
+    ATLAS_FLAG_OPS(FontRelevantAttributes, u8)
+
+    struct FontMapping
+    {
+        wil::com_ptr<IDWriteFontFace2> fontFace;
+        u32 glyphsFrom = 0;
+        u32 glyphsTo = 0;
+    };
+
+    struct GridLineRange
+    {
+        GridLineSet lines;
+        u32 color = 0;
+        u16 from = 0;
+        u16 to = 0;
+    };
+
+    struct ShapedRow
+    {
+        void clear(u16 y, u16 cellHeight) noexcept
+        {
+            mappings.clear();
+            glyphIndices.clear();
+            glyphAdvances.clear();
+            glyphOffsets.clear();
+            colors.clear();
+            gridLineRanges.clear();
+            lineRendition = LineRendition::SingleWidth;
+            selectionFrom = 0;
+            selectionTo = 0;
+            dirtyTop = y * cellHeight;
+            dirtyBottom = dirtyTop + cellHeight;
+        }
+
+        std::vector<FontMapping> mappings;
+        std::vector<u16> glyphIndices;
+        std::vector<f32> glyphAdvances; // same size as glyphIndices
+        std::vector<DWRITE_GLYPH_OFFSET> glyphOffsets; // same size as glyphIndices
+        std::vector<u32> colors; // same size as glyphIndices
+        std::vector<GridLineRange> gridLineRanges;
+        LineRendition lineRendition = LineRendition::SingleWidth;
+        u16 selectionFrom = 0;
+        u16 selectionTo = 0;
+        til::CoordType dirtyTop = 0;
+        til::CoordType dirtyBottom = 0;
+    };
+
+    struct RenderingPayload
+    {
+        //// Parameters which are constant across backends.
+        wil::com_ptr<ID2D1Factory> d2dFactory;
+        wil::com_ptr<IDWriteFactory2> dwriteFactory;
+        wil::com_ptr<IDWriteFactory4> dwriteFactory4; // optional, might be nullptr
+        wil::com_ptr<IDWriteFontFallback> systemFontFallback;
+        wil::com_ptr<IDWriteFontFallback1> systemFontFallback1; // optional, might be nullptr
+        wil::com_ptr<IDWriteTextAnalyzer1> textAnalyzer;
+        wil::com_ptr<IDWriteRenderingParams1> renderingParams;
+        std::function<void(HRESULT)> warningCallback;
+        std::function<void(HANDLE)> swapChainChangedCallback;
+
+        //// Parameters which are constant for the existence of the backend.
+        struct
+        {
+            wil::com_ptr<IDXGIFactory2> factory;
+            wil::com_ptr<IDXGIAdapter1> adapter;
+            LUID adapterLuid{};
+            UINT adapterFlags = 0;
+        } dxgi;
+        struct
+        {
+            wil::com_ptr<IDXGISwapChain2> swapChain;
+            wil::unique_handle handle;
+            wil::unique_handle frameLatencyWaitableObject;
+            til::generation_t generation;
+            til::generation_t targetGeneration;
+            til::generation_t fontGeneration;
+            u16x2 targetSize{};
+            bool waitForPresentation = false;
+        } swapChain;
+        wil::com_ptr<ID3D11Device2> device;
+        wil::com_ptr<ID3D11DeviceContext2> deviceContext;
+
+        //// Parameters which change seldom.
+        GenerationalSettings s;
+
+        //// Parameters which change every frame.
+        // This is the backing buffer for `rows`.
+        Buffer<ShapedRow> unorderedRows;
+        // This is used as a scratch buffer during scrolling.
+        Buffer<ShapedRow*> rowsScratch;
+        // This contains the rows in the right order from row 0 to N.
+        // They get rotated around when we scroll the buffer. Technically
+        // we could also implement scrolling by using a circular array.
+        Buffer<ShapedRow*> rows;
+        // This contains two viewport-sized bitmaps back to back, sort of like a Texture2DArray.
+        // The first NxM (for instance 120x30 pixel) chunk contains background colors and the
+        // second chunk contains foreground colors. The distance in u32 items between the start
+        // and the begin of the foreground bitmap is equal to colorBitmapDepthStride.
+        //
+        // The background part is in premultiplied alpha, whereas the foreground part is in straight
+        // alpha. This is mostly because of Direct2D being annoying, as the former is the only thing
+        // it supports for bitmaps, whereas the latter is the only thing it supports for text.
+        // Since we implement Direct2D's text blending algorithm, we're equally dependent on
+        // straight alpha for BackendD3D, as straight alpha is used in the pixel shader there.
+        Buffer<u32, 32> colorBitmap;
+        // This exists as a convenience access to colorBitmap and
+        // contains a view into the background color bitmap.
+        std::span<u32> backgroundBitmap;
+        // This exists as a convenience access to colorBitmap and
+        // contains a view into the foreground color bitmap.
+        std::span<u32> foregroundBitmap;
+        // This stride of the colorBitmap is a "count" of u32 and not in bytes.
+        size_t colorBitmapRowStride = 0;
+        // FYI depth refers to the `colorBitmapRowStride * height` size of each bitmap contained
+        // in colorBitmap. colorBitmap contains 2 bitmaps (background and foreground colors).
+        size_t colorBitmapDepthStride = 0;
+        // A generation of 1 ensures that the backends redraw the background on the first Present().
+        // The 1st entry in this array corresponds to the background and the 2nd to the foreground bitmap.
+        std::array<til::generation_t, 2> colorBitmapGenerations{ 1, 1 };
+        // In columns/rows.
+        til::rect cursorRect;
+        // In pixel.
+        til::rect dirtyRectInPx;
+        // In rows.
+        range<u16> invalidatedRows{};
+        // In pixel.
+        i16 scrollOffset = 0;
+
+        void MarkAllAsDirty() noexcept
+        {
+            dirtyRectInPx = { 0, 0, s->targetSize.x, s->targetSize.y };
+            invalidatedRows = { 0, s->cellCount.y };
+            scrollOffset = 0;
+        }
+    };
+
+    struct IBackend
+    {
+        virtual ~IBackend() = default;
+        virtual void ReleaseResources() noexcept = 0;
+        virtual void Render(RenderingPayload& payload) = 0;
+        virtual bool RequiresContinuousRedraw() noexcept = 0;
+    };
+
+}
diff --git a/src/renderer/atlas/custom_shader_ps.hlsl b/src/renderer/atlas/custom_shader_ps.hlsl
index 0073f2ca87c..221be267b42 100644
--- a/src/renderer/atlas/custom_shader_ps.hlsl
+++ b/src/renderer/atlas/custom_shader_ps.hlsl
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
 // The original retro pixel shader
 Texture2D shaderTexture;
 SamplerState samplerState;
diff --git a/src/renderer/atlas/custom_shader_vs.hlsl b/src/renderer/atlas/custom_shader_vs.hlsl
index 5bb9fbff70b..97b51c4c23e 100644
--- a/src/renderer/atlas/custom_shader_vs.hlsl
+++ b/src/renderer/atlas/custom_shader_vs.hlsl
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
 struct VS_OUTPUT
 {
     float4 pos : SV_POSITION;
diff --git a/src/renderer/atlas/pch.h b/src/renderer/atlas/pch.h
index f80306b1ea3..cbfdfb3f11a 100644
--- a/src/renderer/atlas/pch.h
+++ b/src/renderer/atlas/pch.h
@@ -17,8 +17,8 @@
 #include <unordered_set>
 #include <vector>
 
-#include <d2d1_1.h>
-#include <d3d11_1.h>
+#include <d2d1_3.h>
+#include <d3d11_2.h>
 #include <d3dcompiler.h>
 #include <dwrite_3.h>
 #include <dcomp.h>
diff --git a/src/renderer/atlas/shader_common.hlsl b/src/renderer/atlas/shader_common.hlsl
new file mode 100644
index 00000000000..957b17ac6ad
--- /dev/null
+++ b/src/renderer/atlas/shader_common.hlsl
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+// clang-format off
+#define SHADING_TYPE_TEXT_BACKGROUND    0
+#define SHADING_TYPE_TEXT_GRAYSCALE     1
+#define SHADING_TYPE_TEXT_CLEARTYPE     2
+#define SHADING_TYPE_TEXT_PASSTHROUGH   3
+#define SHADING_TYPE_DOTTED_LINE        4
+#define SHADING_TYPE_DOTTED_LINE_WIDE   5
+// clang-format on
+
+struct VSData
+{
+    float2 vertex : SV_Position;
+    uint shadingType : shadingType;
+    int2 position : position;
+    uint2 size : size;
+    uint2 texcoord : texcoord;
+    float4 color : color;
+};
+
+struct PSData
+{
+    float4 position : SV_Position;
+    float2 texcoord : texcoord;
+    nointerpolation uint shadingType : shadingType;
+    nointerpolation float4 color : color;
+};
+
+float4 premultiplyColor(float4 color)
+{
+    color.rgb *= color.a;
+    return color;
+}
+
+float4 alphaBlendPremultiplied(float4 bottom, float4 top)
+{
+    bottom *= 1 - top.a;
+    return bottom + top;
+}
+
+float4 decodeRGBA(uint i)
+{
+    return (i >> uint4(0, 8, 16, 24) & 0xff) / 255.0f;
+}
diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl
index 867bcccf2fd..691b06c9661 100644
--- a/src/renderer/atlas/shader_ps.hlsl
+++ b/src/renderer/atlas/shader_ps.hlsl
@@ -2,200 +2,99 @@
 // Licensed under the MIT license.
 
 #include "dwrite.hlsl"
-
-#define INVALID_COLOR 0xffffffff
-
-// These flags are shared with AtlasEngine::CellFlags.
-//
-// clang-format off
-#define CellFlags_None            0x00000000
-#define CellFlags_Inlined         0x00000001
-
-#define CellFlags_ColoredGlyph    0x00000002
-
-#define CellFlags_Cursor          0x00000008
-#define CellFlags_Selected        0x00000010
-
-#define CellFlags_BorderLeft      0x00000020
-#define CellFlags_BorderTop       0x00000040
-#define CellFlags_BorderRight     0x00000080
-#define CellFlags_BorderBottom    0x00000100
-#define CellFlags_Underline       0x00000200
-#define CellFlags_UnderlineDotted 0x00000400
-#define CellFlags_UnderlineDouble 0x00000800
-#define CellFlags_Strikethrough   0x00001000
-// clang-format on
-
-// According to Nvidia's "Understanding Structured Buffer Performance" guide
-// one should aim for structures with sizes divisible by 128 bits (16 bytes).
-// This prevents elements from spanning cache lines.
-struct Cell
-{
-    uint glyphPos;
-    uint flags;
-    uint2 color; // x: foreground, y: background
-};
+#include "shader_common.hlsl"
 
 cbuffer ConstBuffer : register(b0)
 {
-    float4 viewport;
+    float4 backgroundColor;
+    float2 cellSize;
+    float2 cellCount;
     float4 gammaRatios;
     float enhancedContrast;
-    uint cellCountX;
-    uint2 cellSize;
-    uint underlinePos;
-    uint underlineWidth;
-    uint strikethroughPos;
-    uint strikethroughWidth;
-    uint2 doubleUnderlinePos;
-    uint thinLineWidth;
-    uint backgroundColor;
-    uint cursorColor;
-    uint selectionColor;
-    uint useClearType;
-};
-StructuredBuffer<Cell> cells : register(t0);
-Texture2D<float4> glyphs : register(t1);
-
-float4 decodeRGBA(uint i)
-{
-    float4 c = (i >> uint4(0, 8, 16, 24) & 0xff) / 255.0f;
-    // Convert to premultiplied alpha for simpler alpha blending.
-    c.rgb *= c.a;
-    return c;
+    float underlineWidth;
 }
 
-uint2 decodeU16x2(uint i)
-{
-    return uint2(i & 0xffff, i >> 16);
-}
+Texture2D<float4> background : register(t0);
+Texture2D<float4> glyphAtlas : register(t1);
 
-float4 alphaBlendPremultiplied(float4 bottom, float4 top)
+struct Output
 {
-    bottom *= 1 - top.a;
-    return bottom + top;
-}
+    float4 color;
+    float4 weights;
+};
 
 // clang-format off
-float4 main(float4 pos: SV_Position): SV_Target
+Output main(PSData data) : SV_Target
 // clang-format on
 {
-    // We need to fill the entire render target with pixels, but only our "viewport"
-    // has cells we want to draw. The rest gets treated with the background color.
-    [branch] if (any(pos.xy < viewport.xy || pos.xy >= viewport.zw))
+    float4 color;
+    float4 weights;
+
+    switch (data.shadingType)
     {
-        return decodeRGBA(backgroundColor);
+    case SHADING_TYPE_TEXT_BACKGROUND:
+    {
+        const float2 cell = data.position.xy / cellSize;
+        color = all(cell < cellCount) ? background[cell] : backgroundColor;
+        weights = float4(1, 1, 1, 1);
+        break;
     }
-
-    uint2 viewportPos = pos.xy - viewport.xy;
-    uint2 cellIndex = viewportPos / cellSize;
-    uint2 cellPos = viewportPos % cellSize;
-    Cell cell = cells[cellIndex.y * cellCountX + cellIndex.x];
-
-    // Layer 0:
-    // The cell's background color
-    float4 color = decodeRGBA(cell.color.y);
-    float4 fg = decodeRGBA(cell.color.x);
-
-    // Layer 1 (optional):
-    // Colored cursors are drawn "in between" the background color and the text of a cell.
-    [branch] if (cell.flags & CellFlags_Cursor)
+    case SHADING_TYPE_TEXT_GRAYSCALE:
     {
-        [branch] if (cursorColor != INVALID_COLOR)
-        {
-            // The cursor texture is stored at the top-left-most glyph cell.
-            // Cursor pixels are either entirely transparent or opaque.
-            // --> We can just use .a as a mask to flip cursor pixels on or off.
-            color = alphaBlendPremultiplied(color, decodeRGBA(cursorColor) * glyphs[cellPos].a);
-        }
-        else if (glyphs[cellPos].a != 0)
-        {
-            // Make sure the cursor is always readable (see gh-3647)
-            // If we imagine the two colors to be in 0-255 instead of 0-1,
-            // this effectively XORs them with 63. This avoids a situation
-            // where a gray background color (0.5) gets inverted to the
-            // same gray making the cursor invisible.
-            float2x4 colors = { color, fg };
-            float2x4 ip; // integral part
-            float2x4 frac = modf(colors * (255.0f / 64.0f), ip);
-            colors = (3.0f - ip + frac) * (64.0f / 255.0f);
-            color = float4(colors[0].rgb, 1);
-            fg = float4(colors[1].rgb, 1);
-        }
+        // These are independent of the glyph texture and could be moved to the vertex shader or CPU side of things.
+        const float4 foreground = premultiplyColor(data.color);
+        const float blendEnhancedContrast = DWrite_ApplyLightOnDarkContrastAdjustment(enhancedContrast, data.color.rgb);
+        const float intensity = DWrite_CalcColorIntensity(data.color.rgb);
+        // These aren't.
+        const float4 glyph = glyphAtlas[data.texcoord];
+        const float contrasted = DWrite_EnhanceContrast(glyph.a, blendEnhancedContrast);
+        const float alphaCorrected = DWrite_ApplyAlphaCorrection(contrasted, intensity, gammaRatios);
+        color = alphaCorrected * foreground;
+        weights = color.aaaa;
+        break;
     }
-
-    // Layer 2:
-    // Step 1: The cell's glyph, potentially drawn in the foreground color
+    case SHADING_TYPE_TEXT_CLEARTYPE:
     {
-        float4 glyph = glyphs[decodeU16x2(cell.glyphPos) + cellPos];
-
-        [branch] if (cell.flags & CellFlags_ColoredGlyph)
-        {
-            color = alphaBlendPremultiplied(color, glyph);
-        }
-        else
-        {
-            float3 foregroundStraight = DWrite_UnpremultiplyColor(fg);
-            float blendEnhancedContrast = DWrite_ApplyLightOnDarkContrastAdjustment(enhancedContrast, foregroundStraight);
-
-            [branch] if (useClearType)
-            {
-                // See DWrite_ClearTypeBlend
-                float3 contrasted = DWrite_EnhanceContrast3(glyph.rgb, blendEnhancedContrast);
-                float3 alphaCorrected = DWrite_ApplyAlphaCorrection3(contrasted, foregroundStraight, gammaRatios);
-                color = float4(lerp(color.rgb, foregroundStraight, alphaCorrected * fg.a), 1.0f);
-            }
-            else
-            {
-                // See DWrite_GrayscaleBlend
-                float intensity = DWrite_CalcColorIntensity(foregroundStraight);
-                float contrasted = DWrite_EnhanceContrast(glyph.a, blendEnhancedContrast);
-                float4 alphaCorrected = DWrite_ApplyAlphaCorrection(contrasted, intensity, gammaRatios);
-                color = alphaBlendPremultiplied(color, alphaCorrected * fg);
-            }
-        }
+        // These are independent of the glyph texture and could be moved to the vertex shader or CPU side of things.
+        const float blendEnhancedContrast = DWrite_ApplyLightOnDarkContrastAdjustment(enhancedContrast, data.color.rgb);
+        // These aren't.
+        const float4 glyph = glyphAtlas[data.texcoord];
+        const float3 contrasted = DWrite_EnhanceContrast3(glyph.rgb, blendEnhancedContrast);
+        const float3 alphaCorrected = DWrite_ApplyAlphaCorrection3(contrasted, data.color.rgb, gammaRatios);
+        weights = float4(alphaCorrected * data.color.a, 1);
+        color = weights * data.color;
+        break;
     }
-    // Step 2: Lines
+    case SHADING_TYPE_TEXT_PASSTHROUGH:
     {
-        // What a nice coincidence that we have exactly 8 flags to handle right now!
-        // `mask` will mask away any positive results from checks we don't want.
-        // (I.e. even if we're in an underline, it doesn't matter if we don't want an underline.)
-        bool2x4 mask = {
-            cell.flags & CellFlags_BorderLeft,
-            cell.flags & CellFlags_BorderTop,
-            cell.flags & CellFlags_BorderRight,
-            cell.flags & CellFlags_BorderBottom,
-            cell.flags & CellFlags_Underline,
-            cell.flags & CellFlags_UnderlineDotted,
-            cell.flags & CellFlags_UnderlineDouble,
-            cell.flags & CellFlags_Strikethrough,
-        };
-        // The following <lineWidth checks rely on underflow turning the
-        // uint into a way larger number than any reasonable lineWidth.
-        // That way we don't need to write `y >= lo && y < hi`.
-        bool2x4 checks = {
-            // These 2 expand to 4 bools, because cellPos is a
-            // uint2 vector which results in a bool2 result each.
-            cellPos < thinLineWidth,
-            (cellSize - cellPos) <= thinLineWidth,
-            // These 4 are 4 regular bools.
-            (cellPos.y - underlinePos) < underlineWidth,
-            (cellPos.y - underlinePos) < underlineWidth && (viewportPos.x / underlineWidth & 3) == 0,
-            any((cellPos.y - doubleUnderlinePos) < thinLineWidth),
-            (cellPos.y - strikethroughPos) < strikethroughWidth,
-        };
-        [flatten] if (any(mask && checks))
-        {
-            color = alphaBlendPremultiplied(color, fg);
-        }
+        color = glyphAtlas[data.texcoord];
+        weights = color.aaaa;
+        break;
     }
-
-    // Layer 4:
-    // The current selection is drawn semi-transparent on top.
-    [branch] if (cell.flags & CellFlags_Selected)
+    case SHADING_TYPE_DOTTED_LINE:
+    {
+        const bool on = frac(data.position.x / (2.0f * underlineWidth)) < 0.5f;
+        color = on * premultiplyColor(data.color);
+        weights = color.aaaa;
+        break;
+    }
+    case SHADING_TYPE_DOTTED_LINE_WIDE:
     {
-        color = alphaBlendPremultiplied(color, decodeRGBA(selectionColor));
+        const bool on = frac(data.position.x / (4.0f * underlineWidth)) < 0.5f;
+        color = on * premultiplyColor(data.color);
+        weights = color.aaaa;
+        break;
+    }
+    default:
+    {
+        color = premultiplyColor(data.color);
+        weights = color.aaaa;
+        break;
+    }
     }
 
-    return color;
+    Output output;
+    output.color = color;
+    output.weights = weights;
+    return output;
 }
diff --git a/src/renderer/atlas/shader_vs.hlsl b/src/renderer/atlas/shader_vs.hlsl
index e4c75b3e984..49b9030b156 100644
--- a/src/renderer/atlas/shader_vs.hlsl
+++ b/src/renderer/atlas/shader_vs.hlsl
@@ -1,17 +1,24 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT license.
 
+#include "shader_common.hlsl"
+
+cbuffer ConstBuffer : register(b0)
+{
+    float2 positionScale;
+}
+
 // clang-format off
-float4 main(uint id: SV_VERTEXID): SV_POSITION
+PSData main(VSData data)
 // clang-format on
 {
-    // The algorithm below is a fast way to generate a full screen triangle,
-    // published by Bill Bilodeau "Vertex Shader Tricks" at GDC14.
-    // It covers the entire viewport and is faster for the GPU than a quad/rectangle.
-    return float4(
-        float(id / 2) * 4.0 - 1.0,
-        float(id % 2) * 4.0 - 1.0,
-        0.0,
-        1.0
-    );
+    PSData output;
+    output.color = data.color;
+    output.shadingType = data.shadingType;
+    // positionScale is expected to be float2(2.0f / sizeInPixel.x, -2.0f / sizeInPixel.y). Together with the
+    // addition below this will transform our "position" from pixel into normalized device coordinate (NDC) space.
+    output.position.xy = (data.position + data.vertex.xy * data.size) * positionScale + float2(-1.0f, 1.0f);
+    output.position.zw = float2(0, 1);
+    output.texcoord = data.texcoord + data.vertex.xy * data.size;
+    return output;
 }
diff --git a/src/renderer/atlas/stb_rect_pack.cpp b/src/renderer/atlas/stb_rect_pack.cpp
new file mode 100644
index 00000000000..e90d9b988f9
--- /dev/null
+++ b/src/renderer/atlas/stb_rect_pack.cpp
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+
+// AtlasEngine doesn't use stb_rect_pack efficiently right now and packs rectangles one by one,
+// because this simplifies the text rendering implementation quite a bit. On the flip side however,
+// this allows us to skip sorting rectangles, because sorting arrays of size 1 is pointless.
+#pragma warning(disable : 4505) // '...': unreferenced function with internal linkage has been removed
+#define STBRP_SORT(_Base, _NumOfElements, _SizeOfElements, _CompareFunction) \
+    assert(_NumOfElements == 1)
+
+#define STB_RECT_PACK_IMPLEMENTATION
+#include "stb_rect_pack.h"
diff --git a/src/renderer/atlas/wic.h b/src/renderer/atlas/wic.h
new file mode 100644
index 00000000000..f9c2c81035f
--- /dev/null
+++ b/src/renderer/atlas/wic.h
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+#include <wincodec.h>
+
+inline void SaveTextureToPNG(ID3D11DeviceContext* deviceContext, ID3D11Resource* source, double dpi, const wchar_t* fileName)
+{
+    __assume(deviceContext != nullptr);
+    __assume(source != nullptr);
+
+    wil::com_ptr<ID3D11Texture2D> texture;
+    THROW_IF_FAILED(source->QueryInterface(IID_PPV_ARGS(texture.addressof())));
+
+    wil::com_ptr<ID3D11Device> d3dDevice;
+    deviceContext->GetDevice(d3dDevice.addressof());
+
+    D3D11_TEXTURE2D_DESC desc{};
+    texture->GetDesc(&desc);
+    desc.BindFlags = 0;
+    desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+    desc.Usage = D3D11_USAGE_STAGING;
+
+    wil::com_ptr<ID3D11Texture2D> staging;
+    THROW_IF_FAILED(d3dDevice->CreateTexture2D(&desc, nullptr, staging.put()));
+
+    deviceContext->CopyResource(staging.get(), source);
+
+    static const auto coUninitialize = wil::CoInitializeEx();
+    static const auto wicFactory = wil::CoCreateInstance<IWICImagingFactory2>(CLSID_WICImagingFactory2);
+
+    wil::com_ptr<IWICStream> stream;
+    THROW_IF_FAILED(wicFactory->CreateStream(stream.addressof()));
+    THROW_IF_FAILED(stream->InitializeFromFilename(fileName, GENERIC_WRITE));
+
+    wil::com_ptr<IWICBitmapEncoder> encoder;
+    THROW_IF_FAILED(wicFactory->CreateEncoder(GUID_ContainerFormatPng, nullptr, encoder.addressof()));
+    THROW_IF_FAILED(encoder->Initialize(stream.get(), WICBitmapEncoderNoCache));
+
+    wil::com_ptr<IWICBitmapFrameEncode> frame;
+    wil::com_ptr<IPropertyBag2> props;
+    THROW_IF_FAILED(encoder->CreateNewFrame(frame.addressof(), props.addressof()));
+    THROW_IF_FAILED(frame->Initialize(props.get()));
+    THROW_IF_FAILED(frame->SetSize(desc.Width, desc.Height));
+    THROW_IF_FAILED(frame->SetResolution(dpi, dpi));
+    auto pixelFormat = GUID_WICPixelFormat32bppBGRA;
+    THROW_IF_FAILED(frame->SetPixelFormat(&pixelFormat));
+
+    D3D11_MAPPED_SUBRESOURCE mapped;
+    THROW_IF_FAILED(deviceContext->Map(staging.get(), 0, D3D11_MAP_READ, 0, &mapped));
+    THROW_IF_FAILED(frame->WritePixels(desc.Height, mapped.RowPitch, mapped.RowPitch * desc.Height, static_cast<BYTE*>(mapped.pData)));
+    deviceContext->Unmap(staging.get(), 0);
+
+    THROW_IF_FAILED(frame->Commit());
+    THROW_IF_FAILED(encoder->Commit());
+}
diff --git a/src/tools/RenderingTests/main.cpp b/src/tools/RenderingTests/main.cpp
index 49cac079889..9db85e1e840 100644
--- a/src/tools/RenderingTests/main.cpp
+++ b/src/tools/RenderingTests/main.cpp
@@ -149,6 +149,8 @@ int main()
         };
         static constexpr VTAttributeTest vtAttributeTests[]{
             { L"ANSI escape SGR:", 0 },
+            { L"bold", 1 },
+            { L"faint", 2 },
             { L"italic", 3 },
             { L"underline", 4 },
             { L"reverse", 7 },
@@ -172,11 +174,43 @@ int main()
 
     {
         printUTF16(
-            L"\x1B[3;5HDECDWL Double Width \U0001FAE0 A\u0353\u0353 B\u036F\u036F"
-            L"\x1B[4;5H\x1b#6DECDWL Double Width         \U0001FAE0 A\u0353\u0353 B\u036F\u036F"
-            L"\x1B[8;5HDECDHL Double Height \U0001F642\U0001F6C1 A\u0353\u0353 B\u036F\u036F X\u0353\u0353 Y\u036F\u036F"
-            L"\x1B[9;5H\x1b#3DECDHL Double Height Top    \U0001F642 A\u0353\u0353 B\u036F\u036F"
-            L"\x1B[10;5H\x1b#4DECDHL Double Height Bottom \U0001F6C1 X\u0353\u0353 Y\u036F\u036F");
+            L"\x1B[3;5HDECDWL Double Width \U0001FAE0 \x1B[45;92mA\u0353\u0353\x1B[m B\u036F\u036F"
+            L"\x1B[4;3H\x1b#6DECDWL Double Width         \U0001FAE0 \x1B[45;92mA\u0353\u0353\x1B[m B\u036F\u036F"
+            L"\x1B[7;5HDECDHL Double Height \U0001F952\U0001F6C1 A\u0353\u0353 \x1B[45;92mB\u036F\u036F\x1B[m \x1B[45;92mX\u0353\u0353\x1B[m Y\u036F\u036F"
+            L"\x1B[8;3H\x1b#3DECDHL Double Height Top    \U0001F952 A\u0353\u0353 \x1B[45;92mB\u036F\u036F\x1B[m"
+            L"\x1B[9;3H\x1b#4DECDHL Double Height Bottom \U0001F6C1 \x1B[45;92mX\u0353\u0353\x1B[m Y\u036F\u036F"
+            L"\x1B[13;5H\x1b]8;;https://example.com\x1b\\DECDxL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1B[3mitalic\x1b[m        \x1b[4munderline\x1b[m        \x1b[7mreverse\x1b[m"
+            L"\x1B[15;5H\x1b]8;;https://example.com\x1b\\DECDxL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1b[9mstrikethrough\x1b[m \x1b[21mdouble underline\x1b[m \x1b[53moverlined\x1b[m"
+            L"\x1B[17;3H\x1b#6\x1b]8;;https://vt100.net/docs/vt510-rm/DECDWL.html\x1b\\DECDWL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1B[3mitalic\x1b[m        \x1b[4munderline\x1b[m        \x1b[7mreverse\x1b[m"
+            L"\x1B[19;3H\x1b#6\x1b]8;;https://vt100.net/docs/vt510-rm/DECDWL.html\x1b\\DECDWL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1b[9mstrikethrough\x1b[m \x1b[21mdouble underline\x1b[m \x1b[53moverlined\x1b[m"
+            L"\x1B[21;3H\x1b#3\x1b]8;;https://vt100.net/docs/vt510-rm/DECDHL.html\x1b\\DECDHL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1B[3mitalic\x1b[m        \x1b[4munderline\x1b[m        \x1b[7mreverse\x1b[m"
+            L"\x1B[22;3H\x1b#4\x1b]8;;https://vt100.net/docs/vt510-rm/DECDHL.html\x1b\\DECDHL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1B[3mitalic\x1b[m        \x1b[4munderline\x1b[m        \x1b[7mreverse\x1b[m"
+            L"\x1B[24;3H\x1b#3\x1b]8;;https://vt100.net/docs/vt510-rm/DECDHL.html\x1b\\DECDHL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1b[9mstrikethrough\x1b[m \x1b[21mdouble underline\x1b[m \x1b[53moverlined\x1b[m"
+            L"\x1B[25;3H\x1b#4\x1b]8;;https://vt100.net/docs/vt510-rm/DECDHL.html\x1b\\DECDHL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1b[9mstrikethrough\x1b[m \x1b[21mdouble underline\x1b[m \x1b[53moverlined\x1b[m");
+
+        static constexpr WORD attributes[]{
+            FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | COMMON_LVB_GRID_HORIZONTAL,
+            FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | COMMON_LVB_GRID_HORIZONTAL,
+            FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | COMMON_LVB_GRID_LVERTICAL,
+            FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | COMMON_LVB_GRID_LVERTICAL,
+            FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | COMMON_LVB_GRID_RVERTICAL,
+            FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | COMMON_LVB_GRID_RVERTICAL,
+            FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | COMMON_LVB_UNDERSCORE,
+            FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | COMMON_LVB_UNDERSCORE,
+        };
+
+        DWORD numberOfAttrsWritten;
+        DWORD offset = 0;
+
+        for (const auto r : { 12, 14, 16, 18, 20, 21, 23, 24 })
+        {
+            COORD coord;
+            coord.X = r > 14 ? 2 : 4;
+            coord.X += offset ? 2 : 0;
+            coord.Y = static_cast<SHORT>(r);
+            WriteConsoleOutputAttribute(outputHandle, &attributes[offset], 4, coord, &numberOfAttrsWritten);
+            offset = (offset + 4) & 7;
+        }
 
         wait();
         clear();
diff --git a/tools/ConsoleTypes.natvis b/tools/ConsoleTypes.natvis
index de0c91ffd23..79a2fe7abd0 100644
--- a/tools/ConsoleTypes.natvis
+++ b/tools/ConsoleTypes.natvis
@@ -107,10 +107,9 @@
     </Type>
 
     <Type Name="til::generational&lt;*&gt;">
-        <DisplayString>{{ gen={_generation._value}, {_value} }}</DisplayString>
-        <Expand>
-            <ExpandedItem>_value</ExpandedItem>
-        </Expand>
+        <AlternativeType Name="$T1" />
+        <SmartPointer Usage="Minimal">&amp;_value</SmartPointer>
+        <DisplayString>{{ generation={_generation._value} }}</DisplayString>
     </Type>
 
     <Type Name="til::linear_flat_set&lt;*,*&gt;">
@@ -122,4 +121,32 @@
             </ArrayItems>
         </Expand>
     </Type>
+
+    <Type Name="Microsoft::Console::Render::Atlas::Buffer&lt;*&gt;">
+        <DisplayString>{{ size={_size} }}</DisplayString>
+        <Expand>
+            <Item Name="[size]" ExcludeView="simple">_size</Item>
+            <ArrayItems>
+                <Size>_size</Size>
+                <ValuePointer>_data</ValuePointer>
+            </ArrayItems>
+        </Expand>
+    </Type>
+
+    <Type Name="Microsoft::Console::Render::Atlas::ShapedRow">
+        <DisplayString>{{ dirtyTop={dirtyTop}, dirtyBottom={dirtyBottom} }}</DisplayString>
+    </Type>
+
+    <Type Name="Microsoft::Console::Render::Atlas::BackendD3D::AtlasGlyphEntry">
+        <DisplayString Condition="!_occupied">(empty)</DisplayString>
+        <DisplayString Condition="_occupied">{glyphIndex}</DisplayString>
+    </Type>
+
+    <Type Name="Microsoft::Console::Render::Atlas::BackendD3D::AtlasFontFaceEntry">
+        <DisplayString Condition="!_occupied">(empty)</DisplayString>
+        <DisplayString Condition="_occupied">{(void*)fontFace.m_ptr}, {lineRendition}</DisplayString>
+        <Expand>
+            <ExpandedItem>glyphs</ExpandedItem>
+        </Expand>
+    </Type>
 </AutoVisualizer>


Test patch: