Repository: microsoft/terminal (language: cpp)

Issue:
AtlasEngine: Fix various bugs found in testing
In testing the following issues were found in AtlasEngine and fixed:
1. "Toggle terminal visual effects" action not working
2. `d2dMode` failed to work with transparent backgrounds
3. `GetSwapChainHandle()` is thread-unsafe due to it being called outside
  of the console lock and with single-threaded Direct2D enabled
4. 2 swap chain buffers are less performant than 3
5. Flip-Discard and `Present()` is less energy efficient than
  Flip-Sequential and `Present1()`
6. `d2dMode` used to copy the front to back buffer for partial rendering,
  but always redraw the entire dirty region anyways
7. Added support for DirectX 9 hardware
8. If custom shaders are used not all pixels would be presented

Closes #13906

## Validation Steps Performed
1. Toggling visual effects runs retro shader ✅
   With a custom shader set, it toggles the shader ✅
   Toggling `experimental.rendering.software` toggles the shader ✅
2. `"backgroundImage": "desktopWallpaper"` works with D2D ✅ and D3D ✅
3. Adding a `Sleep(3000)` in `_AttachDxgiSwapChainToXaml` doesn't break
   Windows 10 ✅ nor Windows 11 ✅
4. Screen animations run at 144 FPS ✅ even while moving the window ✅
5. No weird artefacts during cursor movement or scrolling ✅
6. No weird artefacts during cursor movement or scrolling ✅
7. Forcing DirectX 9.3 in `dxcpl` runs fine ✅


Discussion / hints:

Fix patch:
diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp
index ef2f88e60b5..2e3baf89e98 100644
--- a/src/cascadia/TerminalControl/ControlCore.cpp
+++ b/src/cascadia/TerminalControl/ControlCore.cpp
@@ -326,7 +326,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
             // Tell the DX Engine to notify us when the swap chain changes.
             // We do this after we initially set the swapchain so as to avoid unnecessary callbacks (and locking problems)
-            _renderEngine->SetCallback(std::bind(&ControlCore::_renderEngineSwapChainChanged, this));
+            _renderEngine->SetCallback([this](auto handle) { _renderEngineSwapChainChanged(handle); });
 
             _renderEngine->SetRetroTerminalEffect(_settings->RetroTerminalEffect());
             _renderEngine->SetPixelShaderPath(_settings->PixelShaderPath());
@@ -596,24 +596,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
     void ControlCore::ToggleShaderEffects()
     {
+        const auto path = _settings->PixelShaderPath();
         auto lock = _terminal->LockForWriting();
         // Originally, this action could be used to enable the retro effects
         // even when they're set to `false` in the settings. If the user didn't
         // specify a custom pixel shader, manually enable the legacy retro
         // effect first. This will ensure that a toggle off->on will still work,
         // even if they currently have retro effect off.
-        if (_settings->PixelShaderPath().empty() && !_renderEngine->GetRetroTerminalEffect())
+        if (path.empty())
         {
-            // SetRetroTerminalEffect to true will enable the effect. In this
-            // case, the shader effect will already be disabled (because neither
-            // a pixel shader nor the retro effects were originally requested).
-            // So we _don't_ want to toggle it again below, because that would
-            // toggle it back off.
-            _renderEngine->SetRetroTerminalEffect(true);
+            _renderEngine->SetRetroTerminalEffect(!_renderEngine->GetRetroTerminalEffect());
         }
         else
         {
-            _renderEngine->ToggleShaderEffects();
+            _renderEngine->SetPixelShaderPath(_renderEngine->GetPixelShaderPath().empty() ? std::wstring_view{ path } : std::wstring_view{});
         }
         // Always redraw after toggling effects. This way even if the control
         // does not have focus it will update immediately.
@@ -1558,25 +1554,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         }
     }
 
-    uint64_t ControlCore::SwapChainHandle() const
-    {
-        // This is called by:
-        // * TermControl::RenderEngineSwapChainChanged, who is only registered
-        //   after Core::Initialize() is called.
-        // * TermControl::_InitializeTerminal, after the call to Initialize, for
-        //   _AttachDxgiSwapChainToXaml.
-        // In both cases, we'll have a _renderEngine by then.
-        return reinterpret_cast<uint64_t>(_renderEngine->GetSwapChainHandle());
-    }
-
     void ControlCore::_rendererWarning(const HRESULT hr)
     {
         _RendererWarningHandlers(*this, winrt::make<RendererWarningArgs>(hr));
     }
 
-    void ControlCore::_renderEngineSwapChainChanged()
+    void ControlCore::_renderEngineSwapChainChanged(const HANDLE handle)
     {
-        _SwapChainChangedHandlers(*this, nullptr);
+        _SwapChainChangedHandlers(*this, winrt::box_value<uint64_t>(reinterpret_cast<uint64_t>(handle)));
     }
 
     void ControlCore::_rendererBackgroundColorChanged()
diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h
index 1f9463ae1e4..6f765736354 100644
--- a/src/cascadia/TerminalControl/ControlCore.h
+++ b/src/cascadia/TerminalControl/ControlCore.h
@@ -75,7 +75,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
         void SizeChanged(const double width, const double height);
         void ScaleChanged(const double scale);
-        uint64_t SwapChainHandle() const;
 
         void AdjustFontSize(int fontSizeDelta);
         void ResetFontSize();
@@ -315,7 +314,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
 #pragma region RendererCallbacks
         void _rendererWarning(const HRESULT hr);
-        void _renderEngineSwapChainChanged();
+        void _renderEngineSwapChainChanged(const HANDLE handle);
         void _rendererBackgroundColorChanged();
         void _rendererTabColorChanged();
 #pragma endregion
diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl
index 7ea4d7ee11b..200258fae01 100644
--- a/src/cascadia/TerminalControl/ControlCore.idl
+++ b/src/cascadia/TerminalControl/ControlCore.idl
@@ -76,8 +76,6 @@ namespace Microsoft.Terminal.Control
         IControlAppearance UnfocusedAppearance { get; };
         Boolean HasUnfocusedAppearance();
 
-        UInt64 SwapChainHandle { get; };
-
         Windows.Foundation.Size FontSize { get; };
         String FontFaceName { get; };
         UInt16 FontWeight { get; };
diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp
index c5cd16cce82..5d4061ee4a3 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -731,19 +731,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         return _core.ConnectionState();
     }
 
-    winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged(IInspectable /*sender*/, IInspectable /*args*/)
+    winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged(IInspectable /*sender*/, IInspectable args)
     {
         // This event is only registered during terminal initialization,
         // so we don't need to check _initializedTerminal.
-        // We also don't lock for things that come back from the renderer.
-        auto weakThis{ get_weak() };
+        const auto weakThis{ get_weak() };
+
+        // Create a copy of the swap chain HANDLE in args, since we don't own that parameter.
+        // By the time we return from the co_await below, it might be deleted already.
+        winrt::handle handle;
+        const auto processHandle = GetCurrentProcess();
+        const auto sourceHandle = reinterpret_cast<HANDLE>(winrt::unbox_value<uint64_t>(args));
+        THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(processHandle, sourceHandle, processHandle, handle.put(), 0, FALSE, DUPLICATE_SAME_ACCESS));
 
         co_await wil::resume_foreground(Dispatcher());
 
         if (auto control{ weakThis.get() })
         {
-            const auto chainHandle = reinterpret_cast<HANDLE>(control->_core.SwapChainHandle());
-            _AttachDxgiSwapChainToXaml(chainHandle);
+            _AttachDxgiSwapChainToXaml(handle.get());
         }
     }
 
@@ -830,21 +835,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         }
         _interactivity.Initialize();
 
-        _AttachDxgiSwapChainToXaml(reinterpret_cast<HANDLE>(_core.SwapChainHandle()));
-
-        // Tell the DX Engine to notify us when the swap chain changes. We do
-        // this after we initially set the swapchain so as to avoid unnecessary
-        // callbacks (and locking problems)
         _core.SwapChainChanged({ get_weak(), &TermControl::RenderEngineSwapChainChanged });
-
-        // !! LOAD BEARING !!
-        // Make sure you enable painting _AFTER_ calling _AttachDxgiSwapChainToXaml
-        //
-        // If you EnablePainting first, then you almost certainly won't have any
-        // problems when running in Debug. However, in Release, you'll run into
-        // issues where the Renderer starts trying to paint before we've
-        // actually attached the swapchain to anything, and the DxEngine is not
-        // prepared to handle that.
         _core.EnablePainting();
 
         auto bufferHeight = _core.BufferHeight();
diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp
index 59dbb4f5ac4..8861704b5f6 100644
--- a/src/renderer/atlas/AtlasEngine.api.cpp
+++ b/src/renderer/atlas/AtlasEngine.api.cpp
@@ -291,6 +291,11 @@ HRESULT AtlasEngine::Enable() noexcept
     return S_OK;
 }
 
+[[nodiscard]] std::wstring_view AtlasEngine::GetPixelShaderPath() noexcept
+{
+    return _api.customPixelShaderPath;
+}
+
 [[nodiscard]] bool AtlasEngine::GetRetroTerminalEffect() const noexcept
 {
     return _api.useRetroTerminalEffect;
@@ -301,17 +306,6 @@ HRESULT AtlasEngine::Enable() noexcept
     return static_cast<float>(_api.dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
 }
 
-[[nodiscard]] HANDLE AtlasEngine::GetSwapChainHandle()
-{
-    if (WI_IsFlagSet(_api.invalidations, ApiInvalidations::Device))
-    {
-        _createResources();
-        WI_ClearFlag(_api.invalidations, ApiInvalidations::Device);
-    }
-
-    return _api.swapChainHandle.get();
-}
-
 [[nodiscard]] Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept
 {
     assert(_api.fontMetrics.cellSize.x != 0);
@@ -337,7 +331,7 @@ void AtlasEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasin
     }
 }
 
-void AtlasEngine::SetCallback(std::function<void()> pfn) noexcept
+void AtlasEngine::SetCallback(std::function<void(HANDLE)> pfn) noexcept
 {
     _api.swapChainChangedCallback = std::move(pfn);
 }
@@ -428,10 +422,6 @@ void AtlasEngine::SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept
     return S_OK;
 }
 
-void AtlasEngine::ToggleShaderEffects() noexcept
-{
-}
-
 [[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept
 {
     static constexpr std::array fallbackFaceNames{ static_cast<const wchar_t*>(nullptr), L"Consolas", L"Lucida Console", L"Courier New" };
diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp
index e33ac4fae22..91046f34d43 100644
--- a/src/renderer/atlas/AtlasEngine.cpp
+++ b/src/renderer/atlas/AtlasEngine.cpp
@@ -582,6 +582,9 @@ void AtlasEngine::_createResources()
             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;
@@ -624,16 +627,6 @@ void AtlasEngine::_createResources()
         _r.deviceContext = deviceContext.query<ID3D11DeviceContext1>();
     }
 
-    {
-        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()));
-
-        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);
-    }
-
 #ifndef NDEBUG
     // D3D debug messages
     if (deviceFlags & D3D11_CREATE_DEVICE_DEBUG)
@@ -646,6 +639,18 @@ void AtlasEngine::_createResources()
     }
 #endif // NDEBUG
 
+    const auto featureLevel = _r.device->GetFeatureLevel();
+
+    {
+        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()));
+
+        DXGI_ADAPTER_DESC1 desc;
+        THROW_IF_FAILED(dxgiAdapter->GetDesc1(&desc));
+        _r.d2dMode = debugForceD2DMode || featureLevel < D3D_FEATURE_LEVEL_10_0 || WI_IsAnyFlagSet(desc.Flags, DXGI_ADAPTER_FLAG_REMOTE | DXGI_ADAPTER_FLAG_SOFTWARE);
+    }
+
     if (!_r.d2dMode)
     {
         // Our constant buffer will never get resized
@@ -663,7 +668,7 @@ void AtlasEngine::_createResources()
         if (!_api.customPixelShaderPath.empty())
         {
             const char* target = nullptr;
-            switch (_r.device->GetFeatureLevel())
+            switch (featureLevel)
             {
             case D3D_FEATURE_LEVEL_10_0:
                 target = "ps_4_0";
@@ -811,10 +816,19 @@ void AtlasEngine::_createSwapChain()
         desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
         desc.SampleDesc.Count = 1;
         desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
-        desc.BufferCount = 2;
+        // 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;
-        desc.SwapEffect = _sr.isWindows10OrGreater && !_r.d2dMode ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
-        // If our background is opaque we can enable "independent" flips by setting DXGI_SWAP_EFFECT_FLIP_DISCARD and DXGI_ALPHA_MODE_IGNORE.
+        // 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;
@@ -857,7 +871,7 @@ void AtlasEngine::_createSwapChain()
     {
         try
         {
-            _api.swapChainChangedCallback();
+            _api.swapChainChangedCallback(_api.swapChainHandle.get());
         }
         CATCH_LOG();
     }
diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h
index 251595c47d2..5b6b9db3d39 100644
--- a/src/renderer/atlas/AtlasEngine.h
+++ b/src/renderer/atlas/AtlasEngine.h
@@ -3,7 +3,7 @@
 
 #pragma once
 
-#include <d2d1.h>
+#include <d2d1_1.h>
 #include <d3d11_1.h>
 #include <dwrite_3.h>
 
@@ -60,14 +60,14 @@ namespace Microsoft::Console::Render
 
         // DxRenderer - getter
         HRESULT Enable() noexcept override;
+        [[nodiscard]] std::wstring_view GetPixelShaderPath() noexcept override;
         [[nodiscard]] bool GetRetroTerminalEffect() const noexcept override;
         [[nodiscard]] float GetScaling() const noexcept override;
-        [[nodiscard]] HANDLE GetSwapChainHandle() override;
         [[nodiscard]] Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept override;
         [[nodiscard]] Types::Viewport GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept override;
         // DxRenderer - setter
         void SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept override;
-        void SetCallback(std::function<void()> pfn) noexcept override;
+        void SetCallback(std::function<void(HANDLE)> pfn) noexcept override;
         void EnableTransparentBackground(const bool isTransparent) noexcept override;
         void SetForceFullRepaintRendering(bool enable) noexcept override;
         [[nodiscard]] HRESULT SetHwnd(HWND hwnd) noexcept override;
@@ -77,7 +77,6 @@ namespace Microsoft::Console::Render
         void SetSoftwareRendering(bool enable) noexcept override;
         void SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept override;
         [[nodiscard]] HRESULT SetWindowSize(til::size pixels) noexcept override;
-        void ToggleShaderEffects() noexcept override;
         [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept override;
         void UpdateHyperlinkHoveredId(uint16_t hoveredId) noexcept override;
 
@@ -1008,7 +1007,7 @@ namespace Microsoft::Console::Render
             // D2D resources
             wil::com_ptr<ID3D11Texture2D> atlasBuffer;
             wil::com_ptr<ID3D11ShaderResourceView> atlasView;
-            wil::com_ptr<ID2D1RenderTarget> d2dRenderTarget;
+            wil::com_ptr<ID2D1DeviceContext> d2dRenderTarget;
             wil::com_ptr<ID2D1SolidColorBrush> brush;
             wil::com_ptr<IDWriteTextFormat> textFormats[2][2];
             Buffer<DWRITE_FONT_AXIS_VALUE> textFormatAxes[2][2];
@@ -1038,7 +1037,6 @@ namespace Microsoft::Console::Render
             CachedCursorOptions cursorOptions;
             RenderInvalidations invalidations = RenderInvalidations::None;
 
-            til::rect previousDirtyRectInPx;
             til::rect dirtyRect;
             i16 scrollOffset = 0;
             bool d2dMode = false;
@@ -1095,7 +1093,7 @@ namespace Microsoft::Console::Render
             i16 scrollOffset = 0;
 
             std::function<void(HRESULT)> warningCallback;
-            std::function<void()> swapChainChangedCallback;
+            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
diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp
index bdfaa4a9a51..e727570a4a4 100644
--- a/src/renderer/atlas/AtlasEngine.r.cpp
+++ b/src/renderer/atlas/AtlasEngine.r.cpp
@@ -63,6 +63,19 @@ 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)
+    {
+        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.
@@ -102,14 +115,60 @@ try
             _r.deviceContext->OMSetRenderTargets(1, _r.renderTargetView.addressof(), nullptr);
             _r.deviceContext->Draw(3, 0);
         }
+    }
 
-        // > IDXGISwapChain::Present: Partial Presentation (using a dirty rects or scroll) is not supported
-        // > for SwapChains created with DXGI_SWAP_EFFECT_DISCARD or DXGI_SWAP_EFFECT_FLIP_DISCARD.
-        // ---> No need to call IDXGISwapChain1::Present1.
+    // 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)
+    {
+        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;
+
+            params.pScrollRect = &scrollRect;
+            params.pScrollOffset = &scrollOffset;
+        }
+
+        THROW_IF_FAILED(_r.swapChain->Present1(1, 0, &params));
+    }
+    else
+    {
         THROW_IF_FAILED(_r.swapChain->Present(1, 0));
-        _r.waitForPresentation = true;
     }
 
+    _r.waitForPresentation = true;
+
     if (!_r.dxgiFactory->IsCurrent())
     {
         WI_SetFlag(_api.invalidations, ApiInvalidations::Device);
@@ -307,7 +366,9 @@ void AtlasEngine::_adjustAtlasSize()
         props.pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED };
         props.dpiX = static_cast<float>(_r.dpi);
         props.dpiY = static_cast<float>(_r.dpi);
-        THROW_IF_FAILED(_sr.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, _r.d2dRenderTarget.put()));
+        wil::com_ptr<ID2D1RenderTarget> renderTarget;
+        THROW_IF_FAILED(_sr.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, renderTarget.addressof()));
+        _r.d2dRenderTarget = renderTarget.query<ID2D1DeviceContext>();
 
         // We don't really use D2D for anything except DWrite, but it
         // can't hurt to ensure that everything it does is pixel aligned.
@@ -749,102 +810,19 @@ void AtlasEngine::CachedGlyphLayout::undoScaling(ID2D1RenderTarget* d2dRenderTar
 
 void AtlasEngine::_d2dPresent()
 {
-    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))
+    if (!_r.d2dRenderTarget)
     {
-        _r.dirtyRect = fullRect;
+        _d2dCreateRenderTarget();
     }
 
-    if (!_r.dirtyRect)
-    {
-        return;
-    }
-
-    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;
-
-    if (const auto intersection = _r.previousDirtyRectInPx & dirtyRectInPx)
-    {
-        wil::com_ptr<ID3D11Resource> backBuffer;
-        wil::com_ptr<ID3D11Resource> frontBuffer;
-        THROW_IF_FAILED(_r.swapChain->GetBuffer(0, __uuidof(backBuffer), backBuffer.put_void()));
-        THROW_IF_FAILED(_r.swapChain->GetBuffer(1, __uuidof(frontBuffer), frontBuffer.put_void()));
-
-        D3D11_BOX intersectBox;
-        intersectBox.left = intersection.left;
-        intersectBox.top = intersection.top;
-        intersectBox.front = 0;
-        intersectBox.right = intersection.right;
-        intersectBox.bottom = intersection.bottom;
-        intersectBox.back = 1;
-        _r.deviceContext->CopySubresourceRegion1(backBuffer.get(), 0, intersection.left, intersection.top, 0, frontBuffer.get(), 0, &intersectBox, 0);
-    }
-
-    _d2dCreateRenderTarget();
     _d2dDrawDirtyArea();
 
-    // 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)
-    {
-        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;
-
-            params.pScrollRect = &scrollRect;
-            params.pScrollOffset = &scrollOffset;
-        }
-
-        THROW_IF_FAILED(_r.swapChain->Present1(1, 0, &params));
-    }
-    else
-    {
-        THROW_IF_FAILED(_r.swapChain->Present(1, 0));
-    }
-
     _r.glyphQueue.clear();
-    _r.previousDirtyRectInPx = dirtyRectInPx;
-    _r.waitForPresentation = true;
     WI_ClearAllFlags(_r.invalidations, RenderInvalidations::Cursor | RenderInvalidations::ConstBuffer);
 }
 
 void AtlasEngine::_d2dCreateRenderTarget()
 {
-    if (_r.d2dRenderTarget)
-    {
-        return;
-    }
-
     {
         wil::com_ptr<ID3D11Texture2D> buffer;
         THROW_IF_FAILED(_r.swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), buffer.put_void()));
@@ -856,7 +834,9 @@ void AtlasEngine::_d2dCreateRenderTarget()
         props.pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED };
         props.dpiX = static_cast<float>(_r.dpi);
         props.dpiY = static_cast<float>(_r.dpi);
-        THROW_IF_FAILED(_sr.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, _r.d2dRenderTarget.put()));
+        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.
@@ -944,6 +924,8 @@ void AtlasEngine::_d2dDrawDirtyArea()
 
         // 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;
@@ -965,6 +947,8 @@ void AtlasEngine::_d2dDrawDirtyArea()
                 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.
diff --git a/src/renderer/atlas/pch.h b/src/renderer/atlas/pch.h
index 5d59e23544b..a436ae580af 100644
--- a/src/renderer/atlas/pch.h
+++ b/src/renderer/atlas/pch.h
@@ -16,7 +16,7 @@
 #include <unordered_set>
 #include <vector>
 
-#include <d2d1.h>
+#include <d2d1_1.h>
 #include <d3d11_1.h>
 #include <d3dcompiler.h>
 #include <dwrite_3.h>
diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp
index 9621dbb8bb7..540e166ad99 100644
--- a/src/renderer/dx/DxRenderer.cpp
+++ b/src/renderer/dx/DxRenderer.cpp
@@ -244,19 +244,6 @@ bool DxEngine::_HasTerminalEffects() const noexcept
     return _terminalEffectsEnabled && (_retroTerminalEffect || !_pixelShaderPath.empty());
 }
 
-// Routine Description:
-// - Toggles terminal effects off and on. If no terminal effect is configured has no effect
-// Arguments:
-// Return Value:
-// - Void
-void DxEngine::ToggleShaderEffects() noexcept
-{
-    _terminalEffectsEnabled = !_terminalEffectsEnabled;
-    _recreateDeviceRequested = true;
-#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function 'Log_IfFailed()' which may throw exceptions (f.6).
-    LOG_IF_FAILED(InvalidateAll());
-}
-
 // Routine Description:
 // - Loads pixel shader source depending on _retroTerminalEffect and _pixelShaderPath
 // Arguments:
@@ -744,7 +731,7 @@ try
     {
         try
         {
-            _pfn();
+            _pfn(_swapChainHandle.get());
         }
         CATCH_LOG(); // A failure in the notification function isn't a failure to prepare, so just log it and go on.
     }
@@ -996,7 +983,7 @@ try
 }
 CATCH_RETURN();
 
-void DxEngine::SetCallback(std::function<void()> pfn) noexcept
+void DxEngine::SetCallback(std::function<void(const HANDLE)> pfn) noexcept
 {
     _pfn = std::move(pfn);
 }
@@ -1025,6 +1012,11 @@ try
 }
 CATCH_LOG()
 
+std::wstring_view DxEngine::GetPixelShaderPath() noexcept
+{
+    return _pixelShaderPath;
+}
+
 void DxEngine::SetPixelShaderPath(std::wstring_view value) noexcept
 try
 {
@@ -1062,17 +1054,6 @@ try
 }
 CATCH_LOG()
 
-HANDLE DxEngine::GetSwapChainHandle() noexcept
-{
-    if (!_swapChainHandle)
-    {
-#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function 'Log_IfFailed()' which may throw exceptions (f.6).
-        LOG_IF_FAILED(_CreateDeviceResources(true));
-    }
-
-    return _swapChainHandle.get();
-}
-
 void DxEngine::_InvalidateRectangle(const til::rect& rc)
 {
     const auto size = _invalidMap.size();
diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp
index 7d49fe141cb..7a6e2b55de4 100644
--- a/src/renderer/dx/DxRenderer.hpp
+++ b/src/renderer/dx/DxRenderer.hpp
@@ -57,22 +57,19 @@ namespace Microsoft::Console::Render
 
         [[nodiscard]] HRESULT SetWindowSize(const til::size pixels) noexcept override;
 
-        void SetCallback(std::function<void()> pfn) noexcept override;
+        void SetCallback(std::function<void(const HANDLE)> pfn) noexcept override;
         void SetWarningCallback(std::function<void(const HRESULT)> pfn) noexcept override;
 
-        void ToggleShaderEffects() noexcept override;
-
         bool GetRetroTerminalEffect() const noexcept override;
         void SetRetroTerminalEffect(bool enable) noexcept override;
 
+        std::wstring_view GetPixelShaderPath() noexcept override;
         void SetPixelShaderPath(std::wstring_view value) noexcept override;
 
         void SetForceFullRepaintRendering(bool enable) noexcept override;
 
         void SetSoftwareRendering(bool enable) noexcept override;
 
-        HANDLE GetSwapChainHandle() noexcept override;
-
         // IRenderEngine Members
         [[nodiscard]] HRESULT Invalidate(const til::rect* const psrRegion) noexcept override;
         [[nodiscard]] HRESULT InvalidateCursor(const til::rect* const psrRegion) noexcept override;
@@ -162,7 +159,7 @@ namespace Microsoft::Console::Render
         float _scale;
         float _prevScale;
 
-        std::function<void()> _pfn;
+        std::function<void(const HANDLE)> _pfn;
         std::function<void(const HRESULT)> _pfnWarningCallback;
 
         bool _isEnabled;
diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp
index 259b432594f..dc641bb3861 100644
--- a/src/renderer/inc/IRenderEngine.hpp
+++ b/src/renderer/inc/IRenderEngine.hpp
@@ -94,18 +94,14 @@ namespace Microsoft::Console::Render
 
         // DxRenderer - getter
         virtual HRESULT Enable() noexcept { return S_OK; }
+        virtual [[nodiscard]] std::wstring_view GetPixelShaderPath() noexcept { return {}; }
         virtual [[nodiscard]] bool GetRetroTerminalEffect() const noexcept { return false; }
         virtual [[nodiscard]] float GetScaling() const noexcept { return 1; }
-#pragma warning(suppress : 26440) // Function '...' can be declared 'noexcept' (f.6).
-        virtual [[nodiscard]] HANDLE GetSwapChainHandle()
-        {
-            return nullptr;
-        }
         virtual [[nodiscard]] Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept { return Types::Viewport::Empty(); }
         virtual [[nodiscard]] Types::Viewport GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept { return Types::Viewport::Empty(); }
         // DxRenderer - setter
         virtual void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept {}
-        virtual void SetCallback(std::function<void()> pfn) noexcept {}
+        virtual void SetCallback(std::function<void(HANDLE)> pfn) noexcept {}
         virtual void EnableTransparentBackground(const bool isTransparent) noexcept {}
         virtual void SetForceFullRepaintRendering(bool enable) noexcept {}
         virtual [[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept { return E_NOTIMPL; }
@@ -115,7 +111,6 @@ namespace Microsoft::Console::Render
         virtual void SetSoftwareRendering(bool enable) noexcept {}
         virtual void SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept {}
         virtual [[nodiscard]] HRESULT SetWindowSize(const til::size pixels) noexcept { return E_NOTIMPL; }
-        virtual void ToggleShaderEffects() noexcept {}
         virtual [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept { return E_NOTIMPL; }
         virtual void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept {}
     };


Test patch: