Repository: microsoft/terminal (language: cpp)

Issue:
Can we get rid of the IRenderTarget interface?
# Description of the new feature/enhancement

As I understand it, the `IRenderTarget` interface is primarily used by the `TextBuffer` class to support the concept of multiple buffers in conhost. So when a text buffer triggers a redraw, that goes through the `ScreenBufferRenderTarget` implementation, and it'll only forward the call to the renderer if we're in the active buffer.

But what if we added a flag in the `TextBuffer` class to track if it is the active buffer or not? That's easy to update whenever the active buffer changes, and then we can deal with the renderer directly. When the buffer needs to trigger a redraw, it simply checks the flag to see if it's active before forwarding the request to the renderer.

I've been working on an PR for this, and it looks to me like it's doable. It doesn't appear to make a great difference to the performance one way or the other, but it does shrink the binary, and I think it makes the code a little simpler. My main reason for wanting this, though, is because I'm hoping it will help with the `AdaptDispatch` refactoring I'm working on for #3849.

# Proposed technical implementation details (optional)

`TextBuffer` gets a new field which tracks whether it's active or not, and has a reference to the actual `Renderer` class rather than `IRenderTarget`. When it needs to trigger a redraw (or any of the other `IRenderTarget` methods), it checks whether it's active or not before forwarding the request to the renderer.

Anywhere that is currently getting a `IRenderTarget` from the `TextBuffer` in order to trigger a redraw, would now just call the `TriggerRedraw` method on the `TextBuffer` class itself, since that will handle the active check which would previously have been the responsibility of the render target. All the trigger methods that are currently in `IRenderTarget` would now be exposed in `TextBuffer`.

One catch with this approach is that we'd need to rethink the initialization order in conhost. In the `ConsoleInitializeConnectInfo` function, we'd need to move the `Renderer` initialization up so it's constructed first, and only after that call the `SetUpConsole` function to initialize the buffer. Once they're both initialized, we can then call the `Renderer::EnablePainting` method to allow the render thread to start.

The other catch is that the renderer tries to setup its initial viewport in the constructor, but at that point in time the viewport size would be unknown. However, that can easily be addressed by moving the viewport initialization into the `EnablePainting` method, since that would still be called *after* the buffer is setup (as mentioned above).

Note that this order of initialization more closely matches the Windows Terminal implementation, which is one of the reasons why I think it'll be beneficial for #3849.

On the whole, the changes needed to make this work look fairly straightforward. The biggest pain is getting the unit tests to work again, because some of them rely on the `IRenderTarget` interface for mocking the renderer, and others depend on the order of initialization. They're all fixable, but it might be a bit messy.


Discussion / hints:

Fix patch:
diff --git a/src/buffer/out/cursor.cpp b/src/buffer/out/cursor.cpp
index 4a3aba783ce..b6d003a3b88 100644
--- a/src/buffer/out/cursor.cpp
+++ b/src/buffer/out/cursor.cpp
@@ -187,7 +187,7 @@ void Cursor::_RedrawCursorAlways() noexcept
 {
     try
     {
-        _parentBuffer.GetRenderTarget().TriggerRedrawCursor(&_cPosition);
+        _parentBuffer.TriggerRedrawCursor(_cPosition);
     }
     CATCH_LOG();
 }
diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp
index a7df90e8c76..66b9980d26c 100644
--- a/src/buffer/out/textBuffer.cpp
+++ b/src/buffer/out/textBuffer.cpp
@@ -6,6 +6,7 @@
 #include "textBuffer.hpp"
 #include "CharRow.hpp"
 
+#include "../renderer/base/renderer.hpp"
 #include "../types/inc/utils.hpp"
 #include "../types/inc/convert.hpp"
 #include "../../types/inc/Utf16Parser.hpp"
@@ -21,23 +22,26 @@ using PointTree = interval_tree::IntervalTree<til::point, size_t>;
 // Routine Description:
 // - Creates a new instance of TextBuffer
 // Arguments:
-// - fontInfo - The font to use for this text buffer as specified in the global font cache
 // - screenBufferSize - The X by Y dimensions of the new screen buffer
-// - fill - Uses the .Attributes property to decide which default color to apply to all text in this buffer
+// - defaultAttributes - The attributes with which the buffer will be initialized
 // - cursorSize - The height of the cursor within this buffer
+// - isActiveBuffer - Whether this is the currently active buffer
+// - renderer - The renderer to use for triggering a redraw
 // Return Value:
 // - constructed object
 // Note: may throw exception
 TextBuffer::TextBuffer(const COORD screenBufferSize,
                        const TextAttribute defaultAttributes,
                        const UINT cursorSize,
-                       Microsoft::Console::Render::IRenderTarget& renderTarget) :
+                       const bool isActiveBuffer,
+                       Microsoft::Console::Render::Renderer& renderer) :
     _firstRow{ 0 },
     _currentAttributes{ defaultAttributes },
     _cursor{ cursorSize, *this },
     _storage{},
     _unicodeStorage{},
-    _renderTarget{ renderTarget },
+    _isActiveBuffer{ isActiveBuffer },
+    _renderer{ renderer },
     _size{},
     _currentHyperlinkId{ 1 },
     _currentPatternId{ 0 }
@@ -387,7 +391,7 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt,
     // Take the cell distance written and notify that it needs to be repainted.
     const auto written = newIt.GetCellDistance(givenIt);
     const Viewport paint = Viewport::FromDimensions(target, { gsl::narrow<SHORT>(written), 1 });
-    _NotifyPaint(paint);
+    TriggerRedraw(paint);
 
     return newIt;
 }
@@ -556,7 +560,10 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode)
 {
     // FirstRow is at any given point in time the array index in the circular buffer that corresponds
     // to the logical position 0 in the window (cursor coordinates and all other coordinates).
-    _renderTarget.TriggerCircling();
+    if (_isActiveBuffer)
+    {
+        _renderer.TriggerCircling();
+    }
 
     // Prune hyperlinks to delete obsolete references
     _PruneHyperlinks();
@@ -825,7 +832,7 @@ void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition)
             // We also need to make sure the cursor is clamped within the new width.
             GetCursor().SetPosition(ClampPositionWithinLine(cursorPosition));
         }
-        _NotifyPaint(Viewport::FromDimensions({ 0, rowIndex }, { GetSize().Width(), 1 }));
+        TriggerRedraw(Viewport::FromDimensions({ 0, rowIndex }, { GetSize().Width(), 1 }));
     }
 }
 
@@ -953,6 +960,69 @@ UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept
     return _unicodeStorage;
 }
 
+void TextBuffer::SetAsActiveBuffer(const bool isActiveBuffer) noexcept
+{
+    _isActiveBuffer = isActiveBuffer;
+}
+
+bool TextBuffer::IsActiveBuffer() const noexcept
+{
+    return _isActiveBuffer;
+}
+
+Microsoft::Console::Render::Renderer& TextBuffer::GetRenderer() noexcept
+{
+    return _renderer;
+}
+
+void TextBuffer::TriggerRedraw(const Viewport& viewport)
+{
+    if (_isActiveBuffer)
+    {
+        _renderer.TriggerRedraw(viewport);
+    }
+}
+
+void TextBuffer::TriggerRedrawCursor(const COORD position)
+{
+    if (_isActiveBuffer)
+    {
+        _renderer.TriggerRedrawCursor(&position);
+    }
+}
+
+void TextBuffer::TriggerRedrawAll()
+{
+    if (_isActiveBuffer)
+    {
+        _renderer.TriggerRedrawAll();
+    }
+}
+
+void TextBuffer::TriggerScroll()
+{
+    if (_isActiveBuffer)
+    {
+        _renderer.TriggerScroll();
+    }
+}
+
+void TextBuffer::TriggerScroll(const COORD delta)
+{
+    if (_isActiveBuffer)
+    {
+        _renderer.TriggerScroll(&delta);
+    }
+}
+
+void TextBuffer::TriggerNewTextNotification(const std::wstring_view newText)
+{
+    if (_isActiveBuffer)
+    {
+        _renderer.TriggerNewTextNotification(newText);
+    }
+}
+
 // Routine Description:
 // - Method to help refresh all the Row IDs after manipulating the row
 //   by shuffling pointers around.
@@ -989,11 +1059,6 @@ void TextBuffer::_RefreshRowIDs(std::optional<SHORT> newRowWidth)
     _unicodeStorage.Remap(rowMap, newRowWidth);
 }
 
-void TextBuffer::_NotifyPaint(const Viewport& viewport) const
-{
-    _renderTarget.TriggerRedraw(viewport);
-}
-
 // Routine Description:
 // - Retrieves the first row from the underlying buffer.
 // Arguments:
@@ -1026,17 +1091,6 @@ ROW& TextBuffer::_GetPrevRowNoWrap(const ROW& Row)
     return _storage.at(prevRowIndex);
 }
 
-// Method Description:
-// - Retrieves this buffer's current render target.
-// Arguments:
-// - <none>
-// Return Value:
-// - This buffer's current render target.
-Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcept
-{
-    return _renderTarget;
-}
-
 // Method Description:
 // - get delimiter class for buffer cell position
 // - used for double click selection and uia word navigation
diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp
index fe87ee8822d..6175d37d2db 100644
--- a/src/buffer/out/textBuffer.hpp
+++ b/src/buffer/out/textBuffer.hpp
@@ -60,7 +60,10 @@ filling in the last row, and updating the screen.
 #include "../buffer/out/textBufferCellIterator.hpp"
 #include "../buffer/out/textBufferTextIterator.hpp"
 
-#include "../renderer/inc/IRenderTarget.hpp"
+namespace Microsoft::Console::Render
+{
+    class Renderer;
+}
 
 class TextBuffer final
 {
@@ -68,7 +71,8 @@ class TextBuffer final
     TextBuffer(const COORD screenBufferSize,
                const TextAttribute defaultAttributes,
                const UINT cursorSize,
-               Microsoft::Console::Render::IRenderTarget& renderTarget);
+               const bool isActiveBuffer,
+               Microsoft::Console::Render::Renderer& renderer);
     TextBuffer(const TextBuffer& a) = delete;
 
     // Used for duplicating properties to another text buffer
@@ -139,7 +143,17 @@ class TextBuffer final
     const UnicodeStorage& GetUnicodeStorage() const noexcept;
     UnicodeStorage& GetUnicodeStorage() noexcept;
 
-    Microsoft::Console::Render::IRenderTarget& GetRenderTarget() noexcept;
+    void SetAsActiveBuffer(const bool isActiveBuffer) noexcept;
+    bool IsActiveBuffer() const noexcept;
+
+    Microsoft::Console::Render::Renderer& GetRenderer() noexcept;
+
+    void TriggerRedraw(const Microsoft::Console::Types::Viewport& viewport);
+    void TriggerRedrawCursor(const COORD position);
+    void TriggerRedrawAll();
+    void TriggerScroll();
+    void TriggerScroll(const COORD delta);
+    void TriggerNewTextNotification(const std::wstring_view newText);
 
     const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false, std::optional<til::point> limitOptional = std::nullopt) const;
     const COORD GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false, std::optional<til::point> limitOptional = std::nullopt) const;
@@ -213,14 +227,15 @@ class TextBuffer final
     // storage location for glyphs that can't fit into the buffer normally
     UnicodeStorage _unicodeStorage;
 
+    bool _isActiveBuffer;
+    Microsoft::Console::Render::Renderer& _renderer;
+
     std::unordered_map<uint16_t, std::wstring> _hyperlinkMap;
     std::unordered_map<std::wstring, uint16_t> _hyperlinkCustomIdMap;
     uint16_t _currentHyperlinkId;
 
     void _RefreshRowIDs(std::optional<SHORT> newRowWidth);
 
-    Microsoft::Console::Render::IRenderTarget& _renderTarget;
-
     void _SetFirstRowIndex(const SHORT FirstRowIndex) noexcept;
 
     COORD _GetPreviousFromCursor() const;
@@ -228,8 +243,6 @@ class TextBuffer final
     void _SetWrapOnCurrentRow();
     void _AdjustWrapOnCurrentRow(const bool fSet);
 
-    void _NotifyPaint(const Microsoft::Console::Types::Viewport& viewport) const;
-
     // Assist with maintaining proper buffer state for Double Byte character sequences
     bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute);
     bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute);
diff --git a/src/buffer/out/ut_textbuffer/ReflowTests.cpp b/src/buffer/out/ut_textbuffer/ReflowTests.cpp
index 645cc347fed..6c567ad017d 100644
--- a/src/buffer/out/ut_textbuffer/ReflowTests.cpp
+++ b/src/buffer/out/ut_textbuffer/ReflowTests.cpp
@@ -6,7 +6,7 @@
 #include "../../inc/consoletaeftemplates.hpp"
 
 #include "../textBuffer.hpp"
-#include "../../renderer/inc/DummyRenderTarget.hpp"
+#include "../../renderer/inc/DummyRenderer.hpp"
 #include "../../types/inc/Utf16Parser.hpp"
 #include "../../types/inc/GlyphWidth.hpp"
 
@@ -732,10 +732,10 @@ class ReflowTests
 {
     TEST_CLASS(ReflowTests);
 
-    static DummyRenderTarget target;
+    static DummyRenderer renderer;
     static std::unique_ptr<TextBuffer> _textBufferFromTestBuffer(const TestBuffer& testBuffer)
     {
-        auto buffer = std::make_unique<TextBuffer>(testBuffer.size, TextAttribute{ 0x7 }, 0, target);
+        auto buffer = std::make_unique<TextBuffer>(testBuffer.size, TextAttribute{ 0x7 }, 0, false, renderer);
 
         size_t i{};
         for (const auto& testRow : testBuffer.rows)
@@ -773,7 +773,7 @@ class ReflowTests
 
     static std::unique_ptr<TextBuffer> _textBufferByReflowingTextBuffer(TextBuffer& originalBuffer, const COORD newSize)
     {
-        auto buffer = std::make_unique<TextBuffer>(newSize, TextAttribute{ 0x7 }, 0, target);
+        auto buffer = std::make_unique<TextBuffer>(newSize, TextAttribute{ 0x7 }, 0, false, renderer);
         TextBuffer::Reflow(originalBuffer, *buffer, std::nullopt, std::nullopt);
         return buffer;
     }
@@ -853,4 +853,4 @@ class ReflowTests
     }
 };
 
-DummyRenderTarget ReflowTests::target{};
+DummyRenderer ReflowTests::renderer{};
diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp
index 6bca14aa18c..21a71077389 100644
--- a/src/cascadia/TerminalControl/ControlCore.cpp
+++ b/src/cascadia/TerminalControl/ControlCore.cpp
@@ -1397,9 +1397,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
     {
         auto lock = _terminal->LockForWriting();
 
-        auto& renderTarget = *_renderer;
         auto& renderSettings = _terminal->GetRenderSettings();
-        renderSettings.ToggleBlinkRendition(renderTarget);
+        renderSettings.ToggleBlinkRendition(*_renderer);
     }
 
     void ControlCore::BlinkCursor()
diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp
index 073d5c71273..4b84c73584a 100644
--- a/src/cascadia/TerminalCore/Terminal.cpp
+++ b/src/cascadia/TerminalCore/Terminal.cpp
@@ -77,7 +77,7 @@ Terminal::Terminal() :
     _renderSettings.SetColorAlias(ColorAlias::DefaultBackground, TextColor::DEFAULT_BACKGROUND, RGB(0, 0, 0));
 }
 
-void Terminal::Create(COORD viewportSize, SHORT scrollbackLines, IRenderTarget& renderTarget)
+void Terminal::Create(COORD viewportSize, SHORT scrollbackLines, Renderer& renderer)
 {
     _mutableViewport = Viewport::FromDimensions({ 0, 0 }, viewportSize);
     _scrollbackLines = scrollbackLines;
@@ -85,22 +85,22 @@ void Terminal::Create(COORD viewportSize, SHORT scrollbackLines, IRenderTarget&
                             Utils::ClampToShortMax(viewportSize.Y + scrollbackLines, 1) };
     const TextAttribute attr{};
     const UINT cursorSize = 12;
-    _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, renderTarget);
+    _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, true, renderer);
 }
 
 // Method Description:
 // - Initializes the Terminal from the given set of settings.
 // Arguments:
 // - settings: the set of CoreSettings we need to use to initialize the terminal
-// - renderTarget: A render target the terminal can use for paint invalidation.
+// - renderer: the Renderer that the terminal can use for paint invalidation.
 void Terminal::CreateFromSettings(ICoreSettings settings,
-                                  IRenderTarget& renderTarget)
+                                  Renderer& renderer)
 {
     const COORD viewportSize{ Utils::ClampToShortMax(settings.InitialCols(), 1),
                               Utils::ClampToShortMax(settings.InitialRows(), 1) };
 
     // TODO:MSFT:20642297 - Support infinite scrollback here, if HistorySize is -1
-    Create(viewportSize, Utils::ClampToShortMax(settings.HistorySize(), 0), renderTarget);
+    Create(viewportSize, Utils::ClampToShortMax(settings.HistorySize(), 0), renderer);
 
     UpdateSettings(settings);
 }
@@ -269,7 +269,8 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance)
         newTextBuffer = std::make_unique<TextBuffer>(bufferSize,
                                                      TextAttribute{},
                                                      0, // temporarily set size to 0 so it won't render.
-                                                     _buffer->GetRenderTarget());
+                                                     _buffer->IsActiveBuffer(),
+                                                     _buffer->GetRenderer());
 
         newTextBuffer->GetCursor().StartDeferDrawing();
 
@@ -419,7 +420,7 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance)
     // GH#5029 - make sure to InvalidateAll here, so that we'll paint the entire visible viewport.
     try
     {
-        _buffer->GetRenderTarget().TriggerRedrawAll();
+        _buffer->TriggerRedrawAll();
     }
     CATCH_LOG();
     _NotifyScrollEvent();
@@ -731,7 +732,7 @@ void Terminal::_InvalidateFromCoords(const COORD start, const COORD end)
     if (start.Y == end.Y)
     {
         SMALL_RECT region{ start.X, start.Y, end.X, end.Y };
-        _buffer->GetRenderTarget().TriggerRedraw(Viewport::FromInclusive(region));
+        _buffer->TriggerRedraw(Viewport::FromInclusive(region));
     }
     else
     {
@@ -739,18 +740,18 @@ void Terminal::_InvalidateFromCoords(const COORD start, const COORD end)
 
         // invalidate the first line
         SMALL_RECT region{ start.X, start.Y, gsl::narrow<short>(rowSize - 1), gsl::narrow<short>(start.Y) };
-        _buffer->GetRenderTarget().TriggerRedraw(Viewport::FromInclusive(region));
+        _buffer->TriggerRedraw(Viewport::FromInclusive(region));
 
         if ((end.Y - start.Y) > 1)
         {
             // invalidate the lines in between the first and last line
             region = SMALL_RECT{ 0, start.Y + 1, gsl::narrow<short>(rowSize - 1), gsl::narrow<short>(end.Y - 1) };
-            _buffer->GetRenderTarget().TriggerRedraw(Viewport::FromInclusive(region));
+            _buffer->TriggerRedraw(Viewport::FromInclusive(region));
         }
 
         // invalidate the last line
         region = SMALL_RECT{ 0, end.Y, end.X, end.Y };
-        _buffer->GetRenderTarget().TriggerRedraw(Viewport::FromInclusive(region));
+        _buffer->TriggerRedraw(Viewport::FromInclusive(region));
     }
 }
 
@@ -1005,7 +1006,7 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView)
     // Notify UIA of new text.
     // It's important to do this here instead of in TextBuffer, because here you have access to the entire line of text,
     // whereas TextBuffer writes it one character at a time via the OutputCellIterator.
-    _buffer->GetRenderTarget().TriggerNewTextNotification(stringView);
+    _buffer->TriggerNewTextNotification(stringView);
 
     cursor.EndDeferDrawing();
 }
@@ -1110,7 +1111,7 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)
         // That didn't change the viewport and therefore the TriggerScroll(void)
         // method can't detect the delta on its own.
         COORD delta{ 0, gsl::narrow_cast<short>(-rowsPushedOffTopOfBuffer) };
-        _buffer->GetRenderTarget().TriggerScroll(&delta);
+        _buffer->TriggerScroll(delta);
     }
 }
 
@@ -1129,7 +1130,7 @@ void Terminal::UserScrollViewport(const int viewTop)
     // We can use the void variant of TriggerScroll here because
     // we adjusted the viewport so it can detect the difference
     // from the previous frame drawn.
-    _buffer->GetRenderTarget().TriggerScroll();
+    _buffer->TriggerScroll();
 }
 
 int Terminal::GetScrollOffset() noexcept
diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp
index 5a6d328ecf8..a8b67233396 100644
--- a/src/cascadia/TerminalCore/Terminal.hpp
+++ b/src/cascadia/TerminalCore/Terminal.hpp
@@ -8,6 +8,7 @@
 #include "../../inc/DefaultSettings.h"
 #include "../../buffer/out/textBuffer.hpp"
 #include "../../types/inc/sgrStack.hpp"
+#include "../../renderer/inc/IRenderData.hpp"
 #include "../../renderer/inc/RenderSettings.hpp"
 #include "../../terminal/parser/StateMachine.hpp"
 #include "../../terminal/input/terminalInput.hpp"
@@ -67,10 +68,10 @@ class Microsoft::Terminal::Core::Terminal final :
 
     void Create(COORD viewportSize,
                 SHORT scrollbackLines,
-                Microsoft::Console::Render::IRenderTarget& renderTarget);
+                Microsoft::Console::Render::Renderer& renderer);
 
     void CreateFromSettings(winrt::Microsoft::Terminal::Core::ICoreSettings settings,
-                            Microsoft::Console::Render::IRenderTarget& renderTarget);
+                            Microsoft::Console::Render::Renderer& renderer);
 
     void UpdateSettings(winrt::Microsoft::Terminal::Core::ICoreSettings settings);
     void UpdateAppearance(const winrt::Microsoft::Terminal::Core::ICoreAppearance& appearance);
diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp
index 6e89bce460a..592ce52d540 100644
--- a/src/cascadia/TerminalCore/TerminalApi.cpp
+++ b/src/cascadia/TerminalCore/TerminalApi.cpp
@@ -340,7 +340,7 @@ void Terminal::SetColorTableEntry(const size_t tableIndex, const COLORREF color)
     }
 
     // Repaint everything - the colors might have changed
-    _buffer->GetRenderTarget().TriggerRedrawAll();
+    _buffer->TriggerRedrawAll();
 }
 
 // Method Description:
@@ -416,7 +416,7 @@ void Terminal::SetRenderMode(const RenderSettings::Mode mode, const bool enabled
     _renderSettings.SetRenderMode(mode, enabled);
 
     // Repaint everything - the colors will have changed
-    _buffer->GetRenderTarget().TriggerRedrawAll();
+    _buffer->TriggerRedrawAll();
 }
 
 void Terminal::EnableXtermBracketedPasteMode(const bool enabled)
diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp
index 7b7fec12dba..c0b20b64d23 100644
--- a/src/cascadia/TerminalCore/TerminalSelection.cpp
+++ b/src/cascadia/TerminalCore/TerminalSelection.cpp
@@ -325,7 +325,7 @@ void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion
             _scrollOffset -= amtBelowView;
         }
         _NotifyScrollEvent();
-        _buffer->GetRenderTarget().TriggerScroll();
+        _buffer->TriggerScroll();
     }
 }
 
diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp
index a085a3b0548..212551ae21a 100644
--- a/src/cascadia/TerminalCore/terminalrenderdata.cpp
+++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp
@@ -174,7 +174,7 @@ void Terminal::SelectNewRegion(const COORD coordStart, const COORD coordEnd)
 
     if (notifyScrollChange)
     {
-        _buffer->GetRenderTarget().TriggerScroll();
+        _buffer->TriggerScroll();
         _NotifyScrollEvent();
     }
 
diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
index 0d7f72f1c6f..f8b537bc731 100644
--- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
+++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
@@ -12,7 +12,7 @@
 #include "../../types/inc/Viewport.hpp"
 #include "../../types/inc/convert.hpp"
 
-#include "../renderer/inc/DummyRenderTarget.hpp"
+#include "../renderer/inc/DummyRenderer.hpp"
 #include "../../renderer/base/Renderer.hpp"
 #include "../../renderer/vt/Xterm256Engine.hpp"
 #include "../../renderer/vt/XtermEngine.hpp"
@@ -87,7 +87,8 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
     {
         // STEP 1: Set up the Terminal
         term = std::make_unique<Terminal>();
-        term->Create({ TerminalViewWidth, TerminalViewHeight }, 100, emptyRT);
+        emptyRenderer = std::make_unique<DummyRenderer>(term.get());
+        term->Create({ TerminalViewWidth, TerminalViewHeight }, 100, *emptyRenderer);
 
         // STEP 2: Set up the Conpty
 
@@ -100,6 +101,8 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
         gci.SetFillAttribute(0x07); // DARK_WHITE on DARK_BLACK
         gci.CalculateDefaultColorIndices();
 
+        g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, nullptr);
+
         m_state->PrepareNewTextBufferInfo(true, TerminalViewWidth, TerminalViewHeight);
         auto& currentBuffer = gci.GetActiveOutputBuffer();
         // Make sure a test hasn't left us in the alt buffer on accident
@@ -107,8 +110,6 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
         VERIFY_SUCCEEDED(currentBuffer.SetViewportOrigin(true, { 0, 0 }, true));
         VERIFY_ARE_EQUAL(COORD({ 0, 0 }), currentBuffer.GetTextBuffer().GetCursor().GetPosition());
 
-        g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, nullptr);
-
         // Set up an xterm-256 renderer for conpty
         wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
         Viewport initialViewport = currentBuffer.GetViewport();
@@ -158,6 +159,7 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
 
         VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Tests should drain all the output they push into the expected output buffer.");
 
+        emptyRenderer = nullptr;
         term = nullptr;
 
         return true;
@@ -237,7 +239,7 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
     bool _checkConptyOutput{ true }; // If true, the test class will check that the output from conpty was expected
     bool _logConpty{ false }; // If true, the test class will log all the output from conpty. Helpful for debugging.
 
-    DummyRenderTarget emptyRT;
+    std::unique_ptr<DummyRenderer> emptyRenderer;
     std::unique_ptr<Terminal> term;
 
     ApiRoutines _apiRoutines;
diff --git a/src/cascadia/UnitTests_TerminalCore/InputTest.cpp b/src/cascadia/UnitTests_TerminalCore/InputTest.cpp
index 34dd9118b91..3ee7c74784a 100644
--- a/src/cascadia/UnitTests_TerminalCore/InputTest.cpp
+++ b/src/cascadia/UnitTests_TerminalCore/InputTest.cpp
@@ -5,7 +5,7 @@
 #include <WexTestClass.h>
 
 #include "../cascadia/TerminalCore/Terminal.hpp"
-#include "../renderer/inc/DummyRenderTarget.hpp"
+#include "../renderer/inc/DummyRenderer.hpp"
 #include "consoletaeftemplates.hpp"
 
 using namespace WEX::Logging;
@@ -21,8 +21,8 @@ namespace TerminalCoreUnitTests
         TEST_CLASS(InputTest);
         TEST_CLASS_SETUP(ClassSetup)
         {
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer;
+            term.Create({ 100, 100 }, 0, renderer);
             auto inputFn = std::bind(&InputTest::_VerifyExpectedInput, this, std::placeholders::_1);
             term.SetWriteInputCallback(inputFn);
             return true;
diff --git a/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp
index cbfa3d6514e..10843787b1a 100644
--- a/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp
+++ b/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp
@@ -6,7 +6,7 @@
 
 #include "../cascadia/TerminalCore/Terminal.hpp"
 #include "MockTermSettings.h"
-#include "../renderer/inc/DummyRenderTarget.hpp"
+#include "../renderer/inc/DummyRenderer.hpp"
 #include "consoletaeftemplates.hpp"
 
 using namespace winrt::Microsoft::Terminal::Core;
@@ -35,13 +35,12 @@ using namespace TerminalCoreUnitTests;
 
 void ScreenSizeLimitsTest::ScreenWidthAndHeightAreClampedToBounds()
 {
-    DummyRenderTarget emptyRenderTarget;
-
     // Negative values for initial visible row count or column count
     // are clamped to 1. Too-large positive values are clamped to SHRT_MAX.
     auto negativeColumnsSettings = winrt::make<MockTermSettings>(10000, 9999999, -1234);
     Terminal negativeColumnsTerminal;
-    negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, emptyRenderTarget);
+    DummyRenderer renderer{ &negativeColumnsTerminal };
+    negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, renderer);
     COORD actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions();
     VERIFY_ARE_EQUAL(actualDimensions.Y, SHRT_MAX, L"Row count clamped to SHRT_MAX == " WCS(SHRT_MAX));
     VERIFY_ARE_EQUAL(actualDimensions.X, 1, L"Column count clamped to 1");
@@ -49,7 +48,7 @@ void ScreenSizeLimitsTest::ScreenWidthAndHeightAreClampedToBounds()
     // Zero values are clamped to 1 as well.
     auto zeroRowsSettings = winrt::make<MockTermSettings>(10000, 0, 9999999);
     Terminal zeroRowsTerminal;
-    zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, emptyRenderTarget);
+    zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, renderer);
     actualDimensions = zeroRowsTerminal.GetViewport().Dimensions();
     VERIFY_ARE_EQUAL(actualDimensions.Y, 1, L"Row count clamped to 1");
     VERIFY_ARE_EQUAL(actualDimensions.X, SHRT_MAX, L"Column count clamped to SHRT_MAX == " WCS(SHRT_MAX));
@@ -62,36 +61,36 @@ void ScreenSizeLimitsTest::ScrollbackHistorySizeIsClampedToBounds()
     // actually visible on screen at the moment.
 
     const unsigned int visibleRowCount = 100;
-    DummyRenderTarget emptyRenderTarget;
 
     // Zero history size is acceptable.
     auto noHistorySettings = winrt::make<MockTermSettings>(0, visibleRowCount, 100);
     Terminal noHistoryTerminal;
-    noHistoryTerminal.CreateFromSettings(noHistorySettings, emptyRenderTarget);
+    DummyRenderer renderer{ &noHistoryTerminal };
+    noHistoryTerminal.CreateFromSettings(noHistorySettings, renderer);
     VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"History size of 0 is accepted");
 
     // Negative history sizes are clamped to zero.
     auto negativeHistorySizeSettings = winrt::make<MockTermSettings>(-100, visibleRowCount, 100);
     Terminal negativeHistorySizeTerminal;
-    negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, emptyRenderTarget);
+    negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, renderer);
     VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"Negative history size is clamped to 0");
 
     // History size + initial visible rows == SHRT_MAX is acceptable.
     auto maxHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount, visibleRowCount, 100);
     Terminal maxHistorySizeTerminal;
-    maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, emptyRenderTarget);
+    maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, renderer);
     VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == SHRT_MAX - initial row count is accepted");
 
     // History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly.
     auto justTooBigHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100);
     Terminal justTooBigHistorySizeTerminal;
-    justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, emptyRenderTarget);
+    justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, renderer);
     VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count");
 
     // Ridiculously large history sizes are also clamped.
     auto farTooBigHistorySizeSettings = winrt::make<MockTermSettings>(99999999, visibleRowCount, 100);
     Terminal farTooBigHistorySizeTerminal;
-    farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, emptyRenderTarget);
+    farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, renderer);
     VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size that is far too large is clamped to SHRT_MAX - initial row count");
 }
 
@@ -106,7 +105,6 @@ void ScreenSizeLimitsTest::ResizeIsClampedToBounds()
     const unsigned int initialVisibleColCount = 50;
     const unsigned int initialVisibleRowCount = 50;
     const auto historySize = SHRT_MAX - (initialVisibleRowCount * 2);
-    DummyRenderTarget emptyRenderTarget;
 
     Log::Comment(L"Watch out - this test takes a while on debug, because "
                  L"ResizeWithReflow takes a while on debug. This is expected.");
@@ -114,7 +112,8 @@ void ScreenSizeLimitsTest::ResizeIsClampedToBounds()
     auto settings = winrt::make<MockTermSettings>(historySize, initialVisibleRowCount, initialVisibleColCount);
     Log::Comment(L"First create a terminal with fewer than SHRT_MAX lines");
     Terminal terminal;
-    terminal.CreateFromSettings(settings, emptyRenderTarget);
+    DummyRenderer renderer{ &terminal };
+    terminal.CreateFromSettings(settings, renderer);
     VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(historySize + initialVisibleRowCount));
 
     Log::Comment(L"Resize the terminal to have exactly SHRT_MAX lines");
diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp
index 6513fbfbc9f..2c94a43ca57 100644
--- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp
+++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp
@@ -6,7 +6,7 @@
 
 #include <DefaultSettings.h>
 
-#include "../renderer/inc/DummyRenderTarget.hpp"
+#include "../renderer/inc/DummyRenderer.hpp"
 #include "../renderer/base/Renderer.hpp"
 #include "../renderer/dx/DxRenderer.hpp"
 
@@ -17,6 +17,7 @@
 
 using namespace winrt::Microsoft::Terminal::Core;
 using namespace Microsoft::Terminal::Core;
+using namespace Microsoft::Console::Render;
 using namespace ::Microsoft::Console::Types;
 
 using namespace WEX::Common;
@@ -25,11 +26,9 @@ using namespace WEX::TestExecution;
 
 namespace
 {
-    class MockScrollRenderTarget final : public ::Microsoft::Console::Render::IRenderTarget
+    class MockScrollRenderEngine final : public RenderEngineBase
     {
     public:
-        ~MockScrollRenderTarget() override{};
-
         std::optional<COORD> TriggerScrollDelta() const
         {
             return _triggerScrollDelta;
@@ -40,20 +39,38 @@ namespace
             _triggerScrollDelta.reset();
         }
 
-        virtual void TriggerRedraw(const Microsoft::Console::Types::Viewport&){};
-        virtual void TriggerRedraw(const COORD* const){};
-        virtual void TriggerRedrawCursor(const COORD* const){};
-        virtual void TriggerRedrawAll(){};
-        virtual void TriggerTeardown() noexcept {};
-        virtual void TriggerSelection(){};
-        virtual void TriggerScroll(){};
-        virtual void TriggerScroll(const COORD* const delta)
+        HRESULT StartPaint() noexcept { return S_OK; }
+        HRESULT EndPaint() noexcept { return S_OK; }
+        HRESULT Present() noexcept { return S_OK; }
+        HRESULT PrepareForTeardown(_Out_ bool* /*pForcePaint*/) noexcept { return S_OK; }
+        HRESULT ScrollFrame() noexcept { return S_OK; }
+        HRESULT Invalidate(const SMALL_RECT* /*psrRegion*/) noexcept { return S_OK; }
+        HRESULT InvalidateCursor(const SMALL_RECT* /*psrRegion*/) noexcept { return S_OK; }
+        HRESULT InvalidateSystem(const RECT* /*prcDirtyClient*/) noexcept { return S_OK; }
+        HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& /*rectangles*/) noexcept { return S_OK; }
+        HRESULT InvalidateScroll(const COORD* pcoordDelta) noexcept
         {
-            _triggerScrollDelta = { *delta };
-        };
-        virtual void TriggerCircling(){};
-        void TriggerTitleChange(){};
-        void TriggerNewTextNotification(const std::wstring_view){};
+            _triggerScrollDelta = { *pcoordDelta };
+            return S_OK;
+        }
+        HRESULT InvalidateAll() noexcept { return S_OK; }
+        HRESULT InvalidateCircling(_Out_ bool* /*pForcePaint*/) noexcept { return S_OK; }
+        HRESULT PaintBackground() noexcept { return S_OK; }
+        HRESULT PaintBufferLine(gsl::span<const Cluster> /*clusters*/, COORD /*coord*/, bool /*fTrimLeft*/, bool /*lineWrapped*/) noexcept { return S_OK; }
+        HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*color*/, size_t /*cchLine*/, COORD /*coordTarget*/) noexcept { return S_OK; }
+        HRESULT PaintSelection(SMALL_RECT /*rect*/) noexcept { return S_OK; }
+        HRESULT PaintCursor(const CursorOptions& /*options*/) noexcept { return S_OK; }
+        HRESULT UpdateDrawingBrushes(const TextAttribute& /*textAttributes*/, const RenderSettings& /*renderSettings*/, gsl::not_null<IRenderData*> /*pData*/, bool /*usingSoftFont*/, bool /*isSettingDefaultBrushes*/) noexcept { return S_OK; }
+        HRESULT UpdateFont(const FontInfoDesired& /*FontInfoDesired*/, _Out_ FontInfo& /*FontInfo*/) noexcept { return S_OK; }
+        HRESULT UpdateDpi(int /*iDpi*/) noexcept { return S_OK; }
+        HRESULT UpdateViewport(SMALL_RECT /*srNewViewport*/) noexcept { return S_OK; }
+        HRESULT GetProposedFont(const FontInfoDesired& /*FontInfoDesired*/, _Out_ FontInfo& /*FontInfo*/, int /*iDpi*/) noexcept { return S_OK; }
+        HRESULT GetDirtyArea(gsl::span<const til::rect>& /*area*/) noexcept { return S_OK; }
+        HRESULT GetFontSize(_Out_ COORD* /*pFontSize*/) noexcept { return S_OK; }
+        HRESULT IsGlyphWideByFont(std::wstring_view /*glyph*/, _Out_ bool* /*pResult*/) noexcept { return S_OK; }
+
+    protected:
+        HRESULT _DoUpdateTitle(const std::wstring_view /*newTitle*/) noexcept { return S_OK; }
 
     private:
         std::optional<COORD> _triggerScrollDelta;
@@ -101,8 +118,10 @@ class TerminalCoreUnitTests::ScrollTest final
             *scrollBarNotification = { tmp };
         });
 
-        _renderTarget = std::make_unique<MockScrollRenderTarget>();
-        _term->Create({ TerminalViewWidth, TerminalViewHeight }, TerminalHistoryLength, *_renderTarget);
+        _renderEngine = std::make_unique<MockScrollRenderEngine>();
+        _renderer = std::make_unique<DummyRenderer>(_term.get());
+        _renderer->AddRenderEngine(_renderEngine.get());
+        _term->Create({ TerminalViewWidth, TerminalViewHeight }, TerminalHistoryLength, *_renderer);
         return true;
     }
 
@@ -114,7 +133,8 @@ class TerminalCoreUnitTests::ScrollTest final
 
 private:
     std::unique_ptr<Terminal> _term;
-    std::unique_ptr<MockScrollRenderTarget> _renderTarget;
+    std::unique_ptr<MockScrollRenderEngine> _renderEngine;
+    std::unique_ptr<DummyRenderer> _renderer;
     std::shared_ptr<std::optional<ScrollBarNotification>> _scrollBarNotification;
 };
 
@@ -152,7 +172,7 @@ void ScrollTest::TestNotifyScrolling()
     for (; currentRow < totalBufferSize * 2; currentRow++)
     {
         *_scrollBarNotification = std::nullopt;
-        _renderTarget->Reset();
+        _renderEngine->Reset();
 
         termSm.ProcessString(L"X\r\n");
 
@@ -180,18 +200,18 @@ void ScrollTest::TestNotifyScrolling()
         // call `TriggerScroll` with a delta to tell the renderer about it.
         if (scrolled && circledBuffer)
         {
-            VERIFY_IS_TRUE(_renderTarget->TriggerScrollDelta().has_value(),
-                           fmt::format(L"Expected a 'trigger scroll' notification in RenderTarget for row {}", currentRow).c_str());
+            VERIFY_IS_TRUE(_renderEngine->TriggerScrollDelta().has_value(),
+                           fmt::format(L"Expected a 'trigger scroll' notification in Render Engine for row {}", currentRow).c_str());
 
             COORD expectedDelta;
             expectedDelta.X = 0;
             expectedDelta.Y = -1;
-            VERIFY_ARE_EQUAL(expectedDelta, _renderTarget->TriggerScrollDelta().value(), fmt::format(L"Wrong value in 'trigger scroll' notification in RenderTarget for row {}", currentRow).c_str());
+            VERIFY_ARE_EQUAL(expectedDelta, _renderEngine->TriggerScrollDelta().value(), fmt::format(L"Wrong value in 'trigger scroll' notification in Render Engine for row {}", currentRow).c_str());
         }
         else
         {
-            VERIFY_IS_FALSE(_renderTarget->TriggerScrollDelta().has_value(),
-                            fmt::format(L"Expected to not see a 'trigger scroll' notification in RenderTarget for row {}", currentRow).c_str());
+            VERIFY_IS_FALSE(_renderEngine->TriggerScrollDelta().has_value(),
+                            fmt::format(L"Expected to not see a 'trigger scroll' notification in Render Engine for row {}", currentRow).c_str());
         }
 
         if (_scrollBarNotification->has_value())
diff --git a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp
index e0dc7acc5bf..81a6313af3c 100644
--- a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp
+++ b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp
@@ -10,7 +10,7 @@
 
 #include "../cascadia/TerminalCore/Terminal.hpp"
 #include "../cascadia/UnitTests_TerminalCore/MockTermSettings.h"
-#include "../renderer/inc/DummyRenderTarget.hpp"
+#include "../renderer/inc/DummyRenderer.hpp"
 #include "consoletaeftemplates.hpp"
 
 using namespace WEX::Logging;
@@ -47,8 +47,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(SelectUnit)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // Simulate click at (x,y) = (5,10)
             auto clickPos = COORD{ 5, 10 };
@@ -60,8 +60,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(SelectArea)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // Used for two things:
             //    - click y-pos
@@ -114,8 +114,8 @@ namespace TerminalCoreUnitTests
             // Behavior: clamp coord to viewport.
             auto ValidateSingleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
                 Terminal term;
-                DummyRenderTarget emptyRT;
-                term.Create({ 10, 10 }, scrollback, emptyRT);
+                DummyRenderer renderer{ &term };
+                term.Create({ 10, 10 }, scrollback, renderer);
 
                 // NOTE: SetSelectionEnd(COORD) is called within SetSelectionAnchor(COORD)
                 term.SetSelectionAnchor(maxCoord);
@@ -127,8 +127,8 @@ namespace TerminalCoreUnitTests
             //           Then, do double click selection.
             auto ValidateDoubleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
                 Terminal term;
-                DummyRenderTarget emptyRT;
-                term.Create({ 10, 10 }, scrollback, emptyRT);
+                DummyRenderer renderer{ &term };
+                term.Create({ 10, 10 }, scrollback, renderer);
 
                 term.MultiClickSelection(maxCoord, Terminal::SelectionExpansion::Word);
                 ValidateSingleRowSelection(term, expected);
@@ -139,8 +139,8 @@ namespace TerminalCoreUnitTests
             //           Then, do triple click selection.
             auto ValidateTripleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
                 Terminal term;
-                DummyRenderTarget emptyRT;
-                term.Create({ 10, 10 }, scrollback, emptyRT);
+                DummyRenderer renderer{ &term };
+                term.Create({ 10, 10 }, scrollback, renderer);
 
                 term.MultiClickSelection(maxCoord, Terminal::SelectionExpansion::Line);
                 ValidateSingleRowSelection(term, expected);
@@ -172,8 +172,8 @@ namespace TerminalCoreUnitTests
             */
 
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 10, 10 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 10, 10 }, 0, renderer);
 
             auto viewport = term.GetViewport();
             const SHORT leftBoundary = viewport.Left();
@@ -214,8 +214,8 @@ namespace TerminalCoreUnitTests
             */
 
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 10, 10 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 10, 10 }, 0, renderer);
 
             auto viewport = term.GetViewport();
             const SHORT leftBoundary = 0;
@@ -300,8 +300,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(SelectBoxArea)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // Used for two things:
             //    - click y-pos
@@ -336,9 +336,9 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(SelectAreaAfterScroll)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
+            DummyRenderer renderer{ &term };
             SHORT scrollbackLines = 5;
-            term.Create({ 100, 100 }, scrollbackLines, emptyRT);
+            term.Create({ 100, 100 }, scrollbackLines, renderer);
 
             // Used for two things:
             //    - click y-pos
@@ -386,8 +386,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(SelectWideGlyph_Trailing)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // This is the burrito emoji
             // It's encoded in UTF-16, as needed by the buffer.
@@ -409,8 +409,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(SelectWideGlyph_Leading)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // This is the burrito emoji
             // It's encoded in UTF-16, as needed by the buffer.
@@ -432,8 +432,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(SelectWideGlyphsInBoxSelection)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // This is the burrito emoji
             // It's encoded in UTF-16, as needed by the buffer.
@@ -487,8 +487,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(DoubleClick_GeneralCase)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // set word delimiters for terminal
             auto settings = winrt::make<MockTermSettings>(0, 100, 100);
@@ -510,8 +510,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(DoubleClick_Delimiter)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // set word delimiters for terminal
             auto settings = winrt::make<MockTermSettings>(0, 100, 100);
@@ -531,8 +531,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(DoubleClick_DelimiterClass)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // set word delimiters for terminal
             auto settings = winrt::make<MockTermSettings>(0, 100, 100);
@@ -559,8 +559,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(DoubleClickDrag_Right)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // set word delimiters for terminal
             auto settings = winrt::make<MockTermSettings>(0, 100, 100);
@@ -588,8 +588,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(DoubleClickDrag_Left)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // set word delimiters for terminal
             auto settings = winrt::make<MockTermSettings>(0, 100, 100);
@@ -617,8 +617,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(TripleClick_GeneralCase)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // Simulate click at (x,y) = (5,10)
             auto clickPos = COORD{ 5, 10 };
@@ -631,8 +631,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(TripleClickDrag_Horizontal)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // Simulate click at (x,y) = (5,10)
             auto clickPos = COORD{ 5, 10 };
@@ -648,8 +648,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(TripleClickDrag_Vertical)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // Simulate click at (x,y) = (5,10)
             auto clickPos = COORD{ 5, 10 };
@@ -676,8 +676,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(ShiftClick)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // set word delimiters for terminal
             auto settings = winrt::make<MockTermSettings>(0, 100, 100);
@@ -793,8 +793,8 @@ namespace TerminalCoreUnitTests
         TEST_METHOD(Pivot)
         {
             Terminal term;
-            DummyRenderTarget emptyRT;
-            term.Create({ 100, 100 }, 0, emptyRT);
+            DummyRenderer renderer{ &term };
+            term.Create({ 100, 100 }, 0, renderer);
 
             // Step 1: Create a selection
             {
diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp
index ef5d587fb90..362535da374 100644
--- a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp
+++ b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp
@@ -6,7 +6,7 @@
 
 #include "../cascadia/TerminalCore/Terminal.hpp"
 #include "MockTermSettings.h"
-#include "../renderer/inc/DummyRenderTarget.hpp"
+#include "../renderer/inc/DummyRenderer.hpp"
 #include "consoletaeftemplates.hpp"
 
 using namespace winrt::Microsoft::Terminal::Core;
@@ -49,8 +49,8 @@ using namespace TerminalCoreUnitTests;
 void TerminalApiTest::SetColorTableEntry()
 {
     Terminal term;
-    DummyRenderTarget emptyRT;
-    term.Create({ 100, 100 }, 0, emptyRT);
+    DummyRenderer renderer{ &term };
+    term.Create({ 100, 100 }, 0, renderer);
 
     auto settings = winrt::make<MockTermSettings>(100, 100, 100);
     term.UpdateSettings(settings);
@@ -67,9 +67,9 @@ void TerminalApiTest::SetColorTableEntry()
 // PrintString() is called with more code units than the buffer width.
 void TerminalApiTest::PrintStringOfSurrogatePairs()
 {
-    DummyRenderTarget renderTarget;
     Terminal term;
-    term.Create({ 100, 100 }, 3, renderTarget);
+    DummyRenderer renderer{ &term };
+    term.Create({ 100, 100 }, 3, renderer);
 
     std::wstring text;
     text.reserve(600);
@@ -135,8 +135,8 @@ void TerminalApiTest::CursorVisibility()
 {
     // GH#3093 - Cursor Visibility and On states shouldn't affect each other
     Terminal term;
-    DummyRenderTarget emptyRT;
-    term.Create({ 100, 100 }, 0, emptyRT);
+    DummyRenderer renderer{ &term };
+    term.Create({ 100, 100 }, 0, renderer);
 
     VERIFY_IS_TRUE(term._buffer->GetCursor().IsVisible());
     VERIFY_IS_TRUE(term._buffer->GetCursor().IsOn());
@@ -167,8 +167,8 @@ void TerminalApiTest::CursorVisibilityViaStateMachine()
 {
     // This is a nearly literal copy-paste of ScreenBufferTests::TestCursorIsOn, adapted for the Terminal
     Terminal term;
-    DummyRenderTarget emptyRT;
-    term.Create({ 100, 100 }, 0, emptyRT);
+    DummyRenderer renderer{ &term };
+    term.Create({ 100, 100 }, 0, renderer);
 
     auto& tbi = *(term._buffer);
     auto& stateMachine = *(term._stateMachine);
@@ -218,9 +218,9 @@ void TerminalApiTest::CursorVisibilityViaStateMachine()
 
 void TerminalApiTest::CheckDoubleWidthCursor()
 {
-    DummyRenderTarget renderTarget;
     Terminal term;
-    term.Create({ 100, 100 }, 0, renderTarget);
+    DummyRenderer renderer{ &term };
+    term.Create({ 100, 100 }, 0, renderer);
 
     auto& tbi = *(term._buffer);
     auto& stateMachine = *(term._stateMachine);
@@ -263,8 +263,8 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlink()
     // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlink, adapted for the Terminal
 
     Terminal term;
-    DummyRenderTarget emptyRT;
-    term.Create({ 100, 100 }, 0, emptyRT);
+    DummyRenderer renderer{ &term };
+    term.Create({ 100, 100 }, 0, renderer);
 
     auto& tbi = *(term._buffer);
     auto& stateMachine = *(term._stateMachine);
@@ -289,8 +289,8 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomId()
     // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlinkCustomId, adapted for the Terminal
 
     Terminal term;
-    DummyRenderTarget emptyRT;
-    term.Create({ 100, 100 }, 0, emptyRT);
+    DummyRenderer renderer{ &term };
+    term.Create({ 100, 100 }, 0, renderer);
 
     auto& tbi = *(term._buffer);
     auto& stateMachine = *(term._stateMachine);
@@ -317,8 +317,8 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomIdDifferentUri()
     // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlinkCustomId, adapted for the Terminal
 
     Terminal term;
-    DummyRenderTarget emptyRT;
-    term.Create({ 100, 100 }, 0, emptyRT);
+    DummyRenderer renderer{ &term };
+    term.Create({ 100, 100 }, 0, renderer);
 
     auto& tbi = *(term._buffer);
     auto& stateMachine = *(term._stateMachine);
@@ -345,8 +345,8 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomIdDifferentUri()
 void TerminalCoreUnitTests::TerminalApiTest::SetTaskbarProgress()
 {
     Terminal term;
-    DummyRenderTarget emptyRT;
-    term.Create({ 100, 100 }, 0, emptyRT);
+    DummyRenderer renderer{ &term };
+    term.Create({ 100, 100 }, 0, renderer);
 
     auto& stateMachine = *(term._stateMachine);
 
@@ -416,8 +416,8 @@ void TerminalCoreUnitTests::TerminalApiTest::SetTaskbarProgress()
 void TerminalCoreUnitTests::TerminalApiTest::SetWorkingDirectory()
 {
     Terminal term;
-    DummyRenderTarget emptyRT;
-    term.Create({ 100, 100 }, 0, emptyRT);
+    DummyRenderer renderer{ &term };
+    term.Create({ 100, 100 }, 0, renderer);
 
     auto& stateMachine = *(term._stateMachine);
 
diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp
index a5cbeea6c6f..e9378f50d20 100644
--- a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp
+++ b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp
@@ -4,7 +4,7 @@
 #include "pch.h"
 #include <WexTestClass.h>
 
-#include "../renderer/inc/DummyRenderTarget.hpp"
+#include "../renderer/inc/DummyRenderer.hpp"
 #include "../cascadia/TerminalCore/Terminal.hpp"
 #include "MockTermSettings.h"
 #include "consoletaeftemplates.hpp"
@@ -57,12 +57,14 @@ class TerminalCoreUnitTests::TerminalBufferTests final
     {
         // STEP 1: Set up the Terminal
         term = std::make_unique<Terminal>();
-        term->Create({ TerminalViewWidth, TerminalViewHeight }, TerminalHistoryLength, emptyRT);
+        emptyRenderer = std::make_unique<DummyRenderer>(term.get());
+        term->Create({ TerminalViewWidth, TerminalViewHeight }, TerminalHistoryLength, *emptyRenderer);
         return true;
     }
 
     TEST_METHOD_CLEANUP(MethodCleanup)
     {
+        emptyRenderer = nullptr;
         term = nullptr;
         return true;
     }
@@ -71,7 +73,7 @@ class TerminalCoreUnitTests::TerminalBufferTests final
     void _SetTabStops(std::list<short> columns, bool replace);
     std::list<short> _GetTabStops();
 
-    DummyRenderTarget emptyRT;
+    std::unique_ptr<DummyRenderer> emptyRenderer;
     std::unique_ptr<Terminal> term;
 };
 
diff --git a/src/host/CursorBlinker.cpp b/src/host/CursorBlinker.cpp
index 939efeaed58..f3aee7a4798 100644
--- a/src/host/CursorBlinker.cpp
+++ b/src/host/CursorBlinker.cpp
@@ -150,7 +150,7 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) const noexcept
     }
 
 DoBlinkingRenditionAndScroll:
-    gci.GetRenderSettings().ToggleBlinkRendition(ScreenInfo.GetRenderTarget());
+    gci.GetRenderSettings().ToggleBlinkRendition(buffer.GetRenderer());
 
 DoScroll:
     Scrolling::s_ScrollIfNecessary(ScreenInfo);
diff --git a/src/host/ScreenBufferRenderTarget.cpp b/src/host/ScreenBufferRenderTarget.cpp
deleted file mode 100644
index ed9ebc91608..00000000000
--- a/src/host/ScreenBufferRenderTarget.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-#include "precomp.h"
-#include "ScreenBufferRenderTarget.hpp"
-#include "../interactivity/inc/ServiceLocator.hpp"
-
-using Microsoft::Console::Interactivity::ServiceLocator;
-ScreenBufferRenderTarget::ScreenBufferRenderTarget(SCREEN_INFORMATION& owner) :
-    _owner{ owner }
-{
-}
-
-void ScreenBufferRenderTarget::TriggerRedraw(const Microsoft::Console::Types::Viewport& region)
-{
-    auto* pRenderer = ServiceLocator::LocateGlobals().pRender;
-    const auto* pActive = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer();
-    if (pRenderer != nullptr && pActive == &_owner)
-    {
-        pRenderer->TriggerRedraw(region);
-    }
-}
-
-void ScreenBufferRenderTarget::TriggerRedraw(const COORD* const pcoord)
-{
-    auto* pRenderer = ServiceLocator::LocateGlobals().pRender;
-    const auto* pActive = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer();
-    if (pRenderer != nullptr && pActive == &_owner)
-    {
-        pRenderer->TriggerRedraw(pcoord);
-    }
-}
-
-void ScreenBufferRenderTarget::TriggerRedrawCursor(const COORD* const pcoord)
-{
-    auto* pRenderer = ServiceLocator::LocateGlobals().pRender;
-    const auto* pActive = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer();
-    if (pRenderer != nullptr && pActive == &_owner)
-    {
-        pRenderer->TriggerRedrawCursor(pcoord);
-    }
-}
-
-void ScreenBufferRenderTarget::TriggerRedrawAll()
-{
-    auto* pRenderer = ServiceLocator::LocateGlobals().pRender;
-    const auto* pActive = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer();
-    if (pRenderer != nullptr && pActive == &_owner)
-    {
-        pRenderer->TriggerRedrawAll();
-    }
-}
-
-void ScreenBufferRenderTarget::TriggerTeardown() noexcept
-{
-    auto* pRenderer = ServiceLocator::LocateGlobals().pRender;
-    const auto* pActive = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer();
-    if (pRenderer != nullptr && pActive == &_owner)
-    {
-        pRenderer->TriggerTeardown();
-    }
-}
-
-void ScreenBufferRenderTarget::TriggerSelection()
-{
-    auto* pRenderer = ServiceLocator::LocateGlobals().pRender;
-    const auto* pActive = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer();
-    if (pRenderer != nullptr && pActive == &_owner)
-    {
-        pRenderer->TriggerSelection();
-    }
-}
-
-void ScreenBufferRenderTarget::TriggerScroll()
-{
-    auto* pRenderer = ServiceLocator::LocateGlobals().pRender;
-    const auto* pActive = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer();
-    if (pRenderer != nullptr && pActive == &_owner)
-    {
-        pRenderer->TriggerScroll();
-    }
-}
-
-void ScreenBufferRenderTarget::TriggerScroll(const COORD* const pcoordDelta)
-{
-    auto* pRenderer = ServiceLocator::LocateGlobals().pRender;
-    const auto* pActive = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer();
-    if (pRenderer != nullptr && pActive == &_owner)
-    {
-        pRenderer->TriggerScroll(pcoordDelta);
-    }
-}
-
-void ScreenBufferRenderTarget::TriggerCircling()
-{
-    auto* pRenderer = ServiceLocator::LocateGlobals().pRender;
-    const auto* pActive = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer();
-    if (pRenderer != nullptr && pActive == &_owner)
-    {
-        pRenderer->TriggerCircling();
-    }
-}
-
-void ScreenBufferRenderTarget::TriggerTitleChange()
-{
-    auto* pRenderer = ServiceLocator::LocateGlobals().pRender;
-    const auto* pActive = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer();
-    if (pRenderer != nullptr && pActive == &_owner)
-    {
-        pRenderer->TriggerTitleChange();
-    }
-}
-
-void ScreenBufferRenderTarget::TriggerNewTextNotification(const std::wstring_view newText)
-{
-    auto* pRenderer = ServiceLocator::LocateGlobals().pRender;
-    const auto* pActive = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer();
-    if (pRenderer != nullptr && pActive == &_owner)
-    {
-        pRenderer->TriggerNewTextNotification(newText);
-    }
-}
diff --git a/src/host/ScreenBufferRenderTarget.hpp b/src/host/ScreenBufferRenderTarget.hpp
deleted file mode 100644
index 7f7588faba4..00000000000
--- a/src/host/ScreenBufferRenderTarget.hpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/*++
-Copyright (c) Microsoft Corporation
-Licensed under the MIT license.
-
-Module Name:
-- ScreenBufferRenderTarget.hpp
-
-Abstract:
-Provides an encapsulation for all of the RenderTarget methods for the SCreenBuffer.
-Unfortunately, these cannot be defined directly on the SCREEN_INFORMATION due to
-MSFT 9358743.
-Adding an interface to SCREEN_INFORMATION makes the ConsoleObjectHeader no
-longer the first part of the SCREEN_INFORMATION.
-The Screen buffer will pass this object to other objects that need to trigger
-redrawing the buffer contents.
-
-Author(s):
-- Mike Griese (migrie) Nov 2018
---*/
-
-#pragma once
-#include "../renderer/inc/IRenderTarget.hpp"
-
-// fwdecl
-class SCREEN_INFORMATION;
-
-class ScreenBufferRenderTarget final : public Microsoft::Console::Render::IRenderTarget
-{
-public:
-    ScreenBufferRenderTarget(SCREEN_INFORMATION& owner);
-
-    void TriggerRedraw(const Microsoft::Console::Types::Viewport& region) override;
-    void TriggerRedraw(const COORD* const pcoord) override;
-    void TriggerRedrawCursor(const COORD* const pcoord) override;
-    void TriggerRedrawAll() override;
-    void TriggerTeardown() noexcept override;
-    void TriggerSelection() override;
-    void TriggerScroll() override;
-    void TriggerScroll(const COORD* const pcoordDelta) override;
-    void TriggerCircling() override;
-    void TriggerTitleChange() override;
-    void TriggerNewTextNotification(const std::wstring_view newText) override;
-
-private:
-    SCREEN_INFORMATION& _owner;
-};
diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp
index 52152e0f5ff..544b89af995 100644
--- a/src/host/_stream.cpp
+++ b/src/host/_stream.cpp
@@ -196,7 +196,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine;
             // back where it started, but everything else moved.
             // In this case, delta was 1. So the amount that moved is the entire viewport height minus the delta.
             Viewport invalid = Viewport::FromDimensions(viewport.Origin(), { viewport.Width(), viewport.Height() - delta });
-            screenInfo.GetRenderTarget().TriggerRedraw(invalid);
+            screenInfo.GetTextBuffer().TriggerRedraw(invalid);
         }
 
         // reset where our local viewport is, and recalculate the cursor and
diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp
index 7313132b73b..b567f2a1ea3 100644
--- a/src/host/consoleInformation.cpp
+++ b/src/host/consoleInformation.cpp
@@ -146,7 +146,7 @@ ULONG CONSOLE_INFORMATION::GetCSRecursionCount()
         goto ErrorExit2;
     }
 
-    gci.pCurrentScreenBuffer = gci.ScreenBuffers;
+    gci.SetActiveOutputBuffer(*gci.ScreenBuffers);
 
     gci.GetActiveOutputBuffer().ScrollScale = gci.GetScrollScale();
 
@@ -214,6 +214,16 @@ const SCREEN_INFORMATION& CONSOLE_INFORMATION::GetActiveOutputBuffer() const
     return *pCurrentScreenBuffer;
 }
 
+void CONSOLE_INFORMATION::SetActiveOutputBuffer(SCREEN_INFORMATION& screenBuffer)
+{
+    if (pCurrentScreenBuffer)
+    {
+        pCurrentScreenBuffer->GetTextBuffer().SetAsActiveBuffer(false);
+    }
+    pCurrentScreenBuffer = &screenBuffer;
+    pCurrentScreenBuffer->GetTextBuffer().SetAsActiveBuffer(true);
+}
+
 bool CONSOLE_INFORMATION::HasActiveOutputBuffer() const
 {
     return (pCurrentScreenBuffer != nullptr);
diff --git a/src/host/host-common.vcxitems b/src/host/host-common.vcxitems
index 7645ced09a1..46acdde1aea 100644
--- a/src/host/host-common.vcxitems
+++ b/src/host/host-common.vcxitems
@@ -41,7 +41,6 @@
     <ClCompile Include="..\renderData.cpp" />
     <ClCompile Include="..\renderFontDefaults.cpp" />
     <ClCompile Include="..\screenInfo.cpp" />
-    <ClCompile Include="..\ScreenBufferRenderTarget.cpp" />
     <ClCompile Include="..\scrolling.cpp" />
     <ClCompile Include="..\selection.cpp" />
     <ClCompile Include="..\selectionInput.cpp" />
@@ -101,7 +100,6 @@
     <ClInclude Include="..\renderFontDefaults.hpp" />
     <ClInclude Include="..\resource.h" />
     <ClInclude Include="..\screenInfo.hpp" />
-    <ClInclude Include="..\ScreenBufferRenderTarget.hpp" />
     <ClInclude Include="..\scrolling.hpp" />
     <ClInclude Include="..\selection.hpp" />
     <ClInclude Include="..\server.h" />
diff --git a/src/host/lib/hostlib.vcxproj.filters b/src/host/lib/hostlib.vcxproj.filters
index 11256b620bb..b8b3df1e25b 100644
--- a/src/host/lib/hostlib.vcxproj.filters
+++ b/src/host/lib/hostlib.vcxproj.filters
@@ -177,9 +177,6 @@
     <ClCompile Include="..\CursorBlinker.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\ScreenBufferRenderTarget.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\precomp.h">
@@ -350,9 +347,6 @@
     <ClInclude Include="..\IIoProvider.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\ScreenBufferRenderTarget.hpp">
-      <Filter>Header Files</Filter>
-    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
diff --git a/src/host/output.cpp b/src/host/output.cpp
index a5f539ccc77..8fb20670154 100644
--- a/src/host/output.cpp
+++ b/src/host/output.cpp
@@ -280,13 +280,13 @@ static void _ScrollScreen(SCREEN_INFORMATION& screenInfo, const Viewport& source
         }
     }
 
-    // Get the render target and send it commands.
+    // Get the text buffer and send it commands.
     // It will figure out whether or not we're active and where the messages need to go.
-    auto& render = screenInfo.GetRenderTarget();
+    auto& textBuffer = screenInfo.GetTextBuffer();
     // Redraw anything in the target area
-    render.TriggerRedraw(target);
+    textBuffer.TriggerRedraw(target);
     // Also redraw anything that was filled.
-    render.TriggerRedraw(fill);
+    textBuffer.TriggerRedraw(fill);
 }
 
 // Routine Description:
@@ -464,7 +464,7 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
 void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo)
 {
     CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
-    gci.pCurrentScreenBuffer = &screenInfo;
+    gci.SetActiveOutputBuffer(screenInfo);
 
     // initialize cursor GH#4102 - Typically, the cursor is set to on by the
     // cursor blinker. Unfortunately, in conpty mode, there is no cursor
diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp
index 6c786b4aa42..75b1585b5ac 100644
--- a/src/host/screenInfo.cpp
+++ b/src/host/screenInfo.cpp
@@ -56,7 +56,6 @@ SCREEN_INFORMATION::SCREEN_INFORMATION(
     _fAltWindowChanged{ false },
     _PopupAttributes{ popupAttributes },
     _virtualBottom{ 0 },
-    _renderTarget{ *this },
     _currentFont{ fontInfo },
     _desiredFont{ fontInfo },
     _ignoreLegacyEquivalentVTAttributes{ false }
@@ -120,7 +119,8 @@ SCREEN_INFORMATION::~SCREEN_INFORMATION()
         pScreen->_textBuffer = std::make_unique<TextBuffer>(coordScreenBufferSize,
                                                             defaultAttributes,
                                                             uiCursorSize,
-                                                            pScreen->_renderTarget);
+                                                            pScreen->IsActiveScreenBuffer(),
+                                                            *ServiceLocator::LocateGlobals().pRender);
 
         const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
         pScreen->_textBuffer->GetCursor().SetType(gci.GetCursorType());
@@ -1443,8 +1443,9 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
     {
         newTextBuffer = std::make_unique<TextBuffer>(coordNewScreenSize,
                                                      TextAttribute{},
-                                                     0,
-                                                     _renderTarget); // temporarily set size to 0 so it won't render.
+                                                     0, // temporarily set size to 0 so it won't render.
+                                                     _textBuffer->IsActiveBuffer(),
+                                                     _textBuffer->GetRenderer());
     }
     catch (...)
     {
@@ -2125,7 +2126,7 @@ void SCREEN_INFORMATION::SetDefaultAttributes(const TextAttribute& attributes,
     // because the text attributes changed.
     if (!(gci.IsInVtIoMode()))
     {
-        GetRenderTarget().TriggerRedrawAll();
+        _textBuffer->TriggerRedrawAll();
     }
 
     gci.ConsoleIme.RefreshAreaAttributes();
@@ -2295,7 +2296,7 @@ void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport,
     auto fillData = OutputCellIterator{ fillAttributes, fillLength };
     Write(fillData, fillPosition, false);
 
-    _textBuffer->GetRenderTarget().TriggerRedrawAll();
+    _textBuffer->TriggerRedrawAll();
 
     // Also reset the line rendition for the erased rows.
     _textBuffer->ResetLineRenditionRange(_viewport.Top(), _viewport.BottomExclusive());
@@ -2678,17 +2679,6 @@ bool SCREEN_INFORMATION::CursorIsDoubleWidth() const
     return IsGlyphFullWidth(*it);
 }
 
-// Method Description:
-// - Retrieves this buffer's current render target.
-// Arguments:
-// - <none>
-// Return Value:
-// - This buffer's current render target.
-IRenderTarget& SCREEN_INFORMATION::GetRenderTarget() noexcept
-{
-    return _renderTarget;
-}
-
 // Method Description:
 // - Gets the current font of the screen buffer.
 // Arguments:
diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp
index 97251bf58a7..56554954bf0 100644
--- a/src/host/screenInfo.hpp
+++ b/src/host/screenInfo.hpp
@@ -22,7 +22,6 @@ Revision History:
 #include "conapi.h"
 #include "settings.hpp"
 #include "outputStream.hpp"
-#include "ScreenBufferRenderTarget.hpp"
 
 #include "../buffer/out/OutputCellRect.hpp"
 #include "../buffer/out/TextAttribute.hpp"
@@ -225,8 +224,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console
     void UpdateBottom();
     void MoveToBottom();
 
-    Microsoft::Console::Render::IRenderTarget& GetRenderTarget() noexcept;
-
     FontInfo& GetCurrentFont() noexcept;
     const FontInfo& GetCurrentFont() const noexcept;
 
@@ -298,8 +295,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console
     //  the viewport to move (SetBufferInfo, WriteConsole, etc)
     short _virtualBottom;
 
-    ScreenBufferRenderTarget _renderTarget;
-
     bool _ignoreLegacyEquivalentVTAttributes;
 
 #ifdef UNIT_TESTING
diff --git a/src/host/server.h b/src/host/server.h
index 39d539158dd..1a7ca9ba821 100644
--- a/src/host/server.h
+++ b/src/host/server.h
@@ -111,6 +111,7 @@ class CONSOLE_INFORMATION :
 
     SCREEN_INFORMATION& GetActiveOutputBuffer() override;
     const SCREEN_INFORMATION& GetActiveOutputBuffer() const override;
+    void SetActiveOutputBuffer(SCREEN_INFORMATION& screenBuffer);
     bool HasActiveOutputBuffer() const;
 
     InputBuffer* const GetActiveInputBuffer() const;
diff --git a/src/host/sources.inc b/src/host/sources.inc
index 042622c404e..b92efbd5c38 100644
--- a/src/host/sources.inc
+++ b/src/host/sources.inc
@@ -70,7 +70,6 @@ SOURCES = \
     ..\dbcs.cpp      \
     ..\convarea.cpp  \
     ..\screenInfo.cpp \
-    ..\ScreenBufferRenderTarget.cpp \
     ..\_output.cpp   \
     ..\_stream.cpp   \
     ..\utils.cpp     \
diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp
index f5fa04f1fe1..055c9ec5546 100644
--- a/src/host/srvinit.cpp
+++ b/src/host/srvinit.cpp
@@ -801,12 +801,6 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand,
 
     CONSOLE_INFORMATION& gci = g.getConsoleInformation();
 
-    NTSTATUS Status = SetUpConsole(&p->ConsoleInfo, p->TitleLength, p->Title, p->CurDir, p->AppName);
-    if (!NT_SUCCESS(Status))
-    {
-        return Status;
-    }
-
     // No matter what, create a renderer.
     try
     {
@@ -823,9 +817,6 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand,
 
         THROW_IF_FAILED(localPointerToThread->Initialize(g.pRender));
 
-        // Allow the renderer to paint.
-        g.pRender->EnablePainting();
-
         // Set up the renderer to be used to calculate the width of a glyph,
         //      should we be unable to figure out its width another way.
         auto pfn = std::bind(&Renderer::IsGlyphWideByFont, static_cast<Renderer*>(g.pRender), std::placeholders::_1);
@@ -833,9 +824,22 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand,
     }
     catch (...)
     {
-        Status = NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
+        return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
     }
 
+    // Note that the order of initialization is important here. SetUpConsole is
+    // where the TextBuffer is created (ultimately in the SCREEN_INFORMATION
+    // CreateInstance method), and the TextBuffer needs to be constructed with
+    // a reference to the renderer, so the renderer must be created first.
+    NTSTATUS Status = SetUpConsole(&p->ConsoleInfo, p->TitleLength, p->Title, p->CurDir, p->AppName);
+    if (!NT_SUCCESS(Status))
+    {
+        return Status;
+    }
+
+    // Allow the renderer to paint once the rest of the console is hooked up.
+    g.pRender->EnablePainting();
+
     if (NT_SUCCESS(Status) && ConsoleConnectionDeservesVisibleWindow(p))
     {
         HANDLE Thread = nullptr;
diff --git a/src/host/ut_host/ConptyOutputTests.cpp b/src/host/ut_host/ConptyOutputTests.cpp
index d40a2fc2073..6e4a9d558a8 100644
--- a/src/host/ut_host/ConptyOutputTests.cpp
+++ b/src/host/ut_host/ConptyOutputTests.cpp
@@ -72,6 +72,8 @@ class ConptyOutputTests
         gci.SetFillAttribute(0x07); // DARK_WHITE on DARK_BLACK
         gci.CalculateDefaultColorIndices();
 
+        g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, nullptr);
+
         m_state->PrepareNewTextBufferInfo(true, TerminalViewWidth, TerminalViewHeight);
         auto& currentBuffer = gci.GetActiveOutputBuffer();
         // Make sure a test hasn't left us in the alt buffer on accident
@@ -79,8 +81,6 @@ class ConptyOutputTests
         VERIFY_SUCCEEDED(currentBuffer.SetViewportOrigin(true, { 0, 0 }, true));
         VERIFY_ARE_EQUAL(COORD({ 0, 0 }), currentBuffer.GetTextBuffer().GetCursor().GetPosition());
 
-        g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, nullptr);
-
         // Set up an xterm-256 renderer for conpty
         wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
         Viewport initialViewport = currentBuffer.GetViewport();
diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp
index 2a963911fce..a75636278f8 100644
--- a/src/host/ut_host/ScreenBufferTests.cpp
+++ b/src/host/ut_host/ScreenBufferTests.cpp
@@ -38,7 +38,8 @@ class ScreenBufferTests
         m_state = new CommonState();
 
         m_state->InitEvents();
-        m_state->PrepareGlobalFont();
+        m_state->PrepareGlobalFont({ 1, 1 });
+        m_state->PrepareGlobalRenderer();
         m_state->PrepareGlobalScreenBuffer();
         m_state->PrepareGlobalInputBuffer();
 
@@ -48,6 +49,7 @@ class ScreenBufferTests
     TEST_CLASS_CLEANUP(ClassCleanup)
     {
         m_state->CleanupGlobalScreenBuffer();
+        m_state->CleanupGlobalRenderer();
         m_state->CleanupGlobalFont();
         m_state->CleanupGlobalInputBuffer();
 
@@ -535,8 +537,12 @@ std::list<short> _GetTabStops(SCREEN_INFORMATION& screenInfo)
 void ScreenBufferTests::TestResetClearTabStops()
 {
     // Reset the screen buffer to test the defaults.
+    m_state->CleanupNewTextBufferInfo();
     m_state->CleanupGlobalScreenBuffer();
+    m_state->CleanupGlobalRenderer();
+    m_state->PrepareGlobalRenderer();
     m_state->PrepareGlobalScreenBuffer();
+    m_state->PrepareNewTextBufferInfo();
 
     CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
     SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer();
diff --git a/src/host/ut_host/SearchTests.cpp b/src/host/ut_host/SearchTests.cpp
index 38d9f1c1108..b2d5b0f5f28 100644
--- a/src/host/ut_host/SearchTests.cpp
+++ b/src/host/ut_host/SearchTests.cpp
@@ -25,6 +25,7 @@ class SearchTests
         m_state = new CommonState();
 
         m_state->PrepareGlobalFont();
+        m_state->PrepareGlobalRenderer();
         m_state->PrepareGlobalScreenBuffer();
 
         return true;
@@ -33,6 +34,7 @@ class SearchTests
     TEST_CLASS_CLEANUP(ClassCleanup)
     {
         m_state->CleanupGlobalScreenBuffer();
+        m_state->CleanupGlobalRenderer();
         m_state->CleanupGlobalFont();
 
         delete m_state;
diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp
index 489768461fe..0fc5a374342 100644
--- a/src/host/ut_host/TextBufferTests.cpp
+++ b/src/host/ut_host/TextBufferTests.cpp
@@ -15,7 +15,7 @@
 #include "_stream.h"
 
 #include "../interactivity/inc/ServiceLocator.hpp"
-#include "../renderer/inc/DummyRenderTarget.hpp"
+#include "../renderer/inc/DummyRenderer.hpp"
 
 using namespace Microsoft::Console::Types;
 using namespace Microsoft::Console::Interactivity;
@@ -26,7 +26,7 @@ using namespace WEX::TestExecution;
 
 class TextBufferTests
 {
-    DummyRenderTarget _renderTarget;
+    DummyRenderer _renderer;
     CommonState* m_state;
 
     TEST_CLASS(TextBufferTests);
@@ -358,7 +358,8 @@ void TextBufferTests::TestCopyProperties()
     std::unique_ptr<TextBuffer> testTextBuffer = std::make_unique<TextBuffer>(otherTbi.GetSize().Dimensions(),
                                                                               otherTbi._currentAttributes,
                                                                               12,
-                                                                              otherTbi._renderTarget);
+                                                                              otherTbi.IsActiveBuffer(),
+                                                                              otherTbi._renderer);
     VERIFY_IS_NOT_NULL(testTextBuffer.get());
 
     // set initial mapping values
@@ -1752,7 +1753,7 @@ void TextBufferTests::ResizeTraditional()
     const COORD smallSize = { 5, 5 };
     const TextAttribute defaultAttr(0);
 
-    TextBuffer buffer(smallSize, defaultAttr, 12, _renderTarget);
+    TextBuffer buffer(smallSize, defaultAttr, 12, false, _renderer);
 
     Log::Comment(L"Fill buffer with some data and do assorted resize operations.");
 
@@ -1848,7 +1849,7 @@ void TextBufferTests::ResizeTraditionalRotationPreservesHighUnicode()
     const COORD bufferSize{ 80, 10 };
     const UINT cursorSize = 12;
     const TextAttribute attr{ 0x7f };
-    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
     // Get a position inside the buffer
     const COORD pos{ 2, 1 };
@@ -1890,7 +1891,7 @@ void TextBufferTests::ScrollBufferRotationPreservesHighUnicode()
     const COORD bufferSize{ 80, 10 };
     const UINT cursorSize = 12;
     const TextAttribute attr{ 0x7f };
-    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
     // Get a position inside the buffer
     const COORD pos{ 2, 1 };
@@ -1930,7 +1931,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeRowRemoval()
     const COORD bufferSize{ 80, 10 };
     const UINT cursorSize = 12;
     const TextAttribute attr{ 0x7f };
-    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
     // Get a position inside the buffer in the bottom row
     const COORD pos{ 0, bufferSize.Y - 1 };
@@ -1965,7 +1966,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeColumnRemoval()
     const COORD bufferSize{ 80, 10 };
     const UINT cursorSize = 12;
     const TextAttribute attr{ 0x7f };
-    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
     // Get a position inside the buffer in the last column
     const COORD pos{ bufferSize.X - 1, 0 };
@@ -1997,7 +1998,7 @@ void TextBufferTests::TestBurrito()
     COORD bufferSize{ 80, 9001 };
     UINT cursorSize = 12;
     TextAttribute attr{ 0x7f };
-    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
     // This is the burrito emoji: 🌯
     // It's encoded in UTF-16, as needed by the buffer.
@@ -2072,7 +2073,7 @@ void TextBufferTests::GetWordBoundaries()
     COORD bufferSize{ 80, 9001 };
     UINT cursorSize = 12;
     TextAttribute attr{ 0x7f };
-    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
     // Setup: Write lines of text to the buffer
     const std::vector<std::wstring> text = { L"word other",
@@ -2181,7 +2182,7 @@ void TextBufferTests::MoveByWord()
     COORD bufferSize{ 80, 9001 };
     UINT cursorSize = 12;
     TextAttribute attr{ 0x7f };
-    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
     // Setup: Write lines of text to the buffer
     const std::vector<std::wstring> text = { L"word other",
@@ -2287,7 +2288,7 @@ void TextBufferTests::GetGlyphBoundaries()
     COORD bufferSize{ 10, 10 };
     UINT cursorSize = 12;
     TextAttribute attr{ 0x7f };
-    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
     // This is the burrito emoji: 🌯
     // It's encoded in UTF-16, as needed by the buffer.
@@ -2323,7 +2324,7 @@ void TextBufferTests::GetTextRects()
     COORD bufferSize{ 20, 50 };
     UINT cursorSize = 12;
     TextAttribute attr{ 0x7f };
-    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
     // Setup: Write lines of text to the buffer
     const std::vector<std::wstring> text = { L"0123456789",
@@ -2403,7 +2404,7 @@ void TextBufferTests::GetText()
         COORD bufferSize{ 10, 20 };
         UINT cursorSize = 12;
         TextAttribute attr{ 0x7f };
-        auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+        auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
         // Setup: Write lines of text to the buffer
         const std::vector<std::wstring> bufferText = { L"12345",
@@ -2495,7 +2496,7 @@ void TextBufferTests::GetText()
         COORD bufferSize{ 5, 20 };
         UINT cursorSize = 12;
         TextAttribute attr{ 0x7f };
-        auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+        auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
         // Setup: Write lines of text to the buffer
         const std::vector<std::wstring> bufferText = { L"1234567",
@@ -2631,7 +2632,7 @@ void TextBufferTests::HyperlinkTrim()
     const COORD bufferSize{ 80, 10 };
     const UINT cursorSize = 12;
     const TextAttribute attr{ 0x7f };
-    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
     static constexpr std::wstring_view url{ L"test.url" };
     static constexpr std::wstring_view otherUrl{ L"other.url" };
@@ -2677,7 +2678,7 @@ void TextBufferTests::NoHyperlinkTrim()
     const COORD bufferSize{ 80, 10 };
     const UINT cursorSize = 12;
     const TextAttribute attr{ 0x7f };
-    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
+    auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
 
     static constexpr std::wstring_view url{ L"test.url" };
     static constexpr std::wstring_view customId{ L"CustomId" };
diff --git a/src/renderer/base/RenderSettings.cpp b/src/renderer/base/RenderSettings.cpp
index e3b74513fc6..e889adccbf7 100644
--- a/src/renderer/base/RenderSettings.cpp
+++ b/src/renderer/base/RenderSettings.cpp
@@ -4,7 +4,7 @@
 #include "precomp.h"
 
 #include "../inc/RenderSettings.hpp"
-#include "../inc/IRenderTarget.hpp"
+#include "../base/renderer.hpp"
 #include "../../types/inc/ColorFix.hpp"
 #include "../../types/inc/colorTable.hpp"
 
@@ -262,10 +262,10 @@ std::pair<COLORREF, COLORREF> RenderSettings::GetAttributeColorsWithAlpha(const
 // Routine Description:
 // - Increments the position in the blink cycle, toggling the blink rendition
 //   state on every second call, potentially triggering a redraw of the given
-//   render target if there are blinking cells currently in view.
+//   renderer if there are blinking cells currently in view.
 // Arguments:
-// - renderTarget: the render target that will be redrawn.
-void RenderSettings::ToggleBlinkRendition(IRenderTarget& renderTarget) noexcept
+// - renderer: the renderer that will be redrawn.
+void RenderSettings::ToggleBlinkRendition(Renderer& renderer) noexcept
 try
 {
     if (GetRenderMode(Mode::BlinkAllowed))
@@ -283,7 +283,7 @@ try
             // We reset the _blinkIsInUse flag before redrawing, so we can
             // get a fresh assessment of the current blink attribute usage.
             _blinkIsInUse = false;
-            renderTarget.TriggerRedrawAll();
+            renderer.TriggerRedrawAll();
         }
     }
 }
diff --git a/src/renderer/base/lib/base.vcxproj b/src/renderer/base/lib/base.vcxproj
index 5d7bc816c41..ceed166fd27 100644
--- a/src/renderer/base/lib/base.vcxproj
+++ b/src/renderer/base/lib/base.vcxproj
@@ -31,7 +31,6 @@
     <ClInclude Include="..\..\inc\IFontDefaultList.hpp" />
     <ClInclude Include="..\..\inc\IRenderData.hpp" />
     <ClInclude Include="..\..\inc\IRenderEngine.hpp" />
-    <ClInclude Include="..\..\inc\IRenderTarget.hpp" />
     <ClInclude Include="..\..\inc\RenderEngineBase.hpp" />
     <ClInclude Include="..\..\inc\RenderSettings.hpp" />
     <ClInclude Include="..\FontCache.h" />
diff --git a/src/renderer/base/lib/base.vcxproj.filters b/src/renderer/base/lib/base.vcxproj.filters
index 4e8f12c1821..a678fb27cce 100644
--- a/src/renderer/base/lib/base.vcxproj.filters
+++ b/src/renderer/base/lib/base.vcxproj.filters
@@ -83,9 +83,6 @@
     <ClInclude Include="..\..\inc\Cluster.hpp">
       <Filter>Header Files\inc</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\inc\IRenderTarget.hpp">
-      <Filter>Header Files\inc</Filter>
-    </ClInclude>
     <ClInclude Include="..\..\inc\RenderSettings.hpp">
       <Filter>Header Files\inc</Filter>
     </ClInclude>
diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp
index 66bbcc6b5ad..e720708b9e2 100644
--- a/src/renderer/base/renderer.cpp
+++ b/src/renderer/base/renderer.cpp
@@ -34,9 +34,8 @@ Renderer::Renderer(const RenderSettings& renderSettings,
                    const size_t cEngines,
                    std::unique_ptr<RenderThread> thread) :
     _renderSettings(renderSettings),
-    _pData(THROW_HR_IF_NULL(E_INVALIDARG, pData)),
-    _pThread{ std::move(thread) },
-    _viewport{ pData->GetViewport() }
+    _pData(pData),
+    _pThread{ std::move(thread) }
 {
     for (size_t i = 0; i < cEngines; i++)
     {
@@ -631,7 +630,15 @@ bool Renderer::IsGlyphWideByFont(const std::wstring_view glyph)
 // - <none>
 void Renderer::EnablePainting()
 {
-    _pThread->EnablePainting();
+    // When the renderer is constructed, the initial viewport won't be available yet,
+    // but once EnablePainting is called it should be safe to retrieve.
+    _viewport = _pData->GetViewport();
+
+    // When running the unit tests, we may be using a render without a render thread.
+    if (_pThread)
+    {
+        _pThread->EnablePainting();
+    }
 }
 
 // Routine Description:
diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp
index e53afd2a25f..0a84c6b6e0b 100644
--- a/src/renderer/base/renderer.hpp
+++ b/src/renderer/base/renderer.hpp
@@ -16,7 +16,7 @@ Author(s):
 
 #pragma once
 
-#include "../inc/IRenderTarget.hpp"
+#include "../inc/IRenderEngine.hpp"
 #include "../inc/RenderSettings.hpp"
 
 #include "thread.hpp"
@@ -26,7 +26,7 @@ Author(s):
 
 namespace Microsoft::Console::Render
 {
-    class Renderer : public IRenderTarget
+    class Renderer
     {
     public:
         Renderer(const RenderSettings& renderSettings,
@@ -35,26 +35,26 @@ namespace Microsoft::Console::Render
                  const size_t cEngines,
                  std::unique_ptr<RenderThread> thread);
 
-        virtual ~Renderer();
+        ~Renderer();
 
         [[nodiscard]] HRESULT PaintFrame();
 
         void NotifyPaintFrame() noexcept;
         void TriggerSystemRedraw(const RECT* const prcDirtyClient);
-        void TriggerRedraw(const Microsoft::Console::Types::Viewport& region) override;
-        void TriggerRedraw(const COORD* const pcoord) override;
-        void TriggerRedrawCursor(const COORD* const pcoord) override;
-        void TriggerRedrawAll() override;
-        void TriggerTeardown() noexcept override;
+        void TriggerRedraw(const Microsoft::Console::Types::Viewport& region);
+        void TriggerRedraw(const COORD* const pcoord);
+        void TriggerRedrawCursor(const COORD* const pcoord);
+        void TriggerRedrawAll();
+        void TriggerTeardown() noexcept;
 
-        void TriggerSelection() override;
-        void TriggerScroll() override;
-        void TriggerScroll(const COORD* const pcoordDelta) override;
+        void TriggerSelection();
+        void TriggerScroll();
+        void TriggerScroll(const COORD* const pcoordDelta);
 
-        void TriggerCircling() override;
-        void TriggerTitleChange() override;
+        void TriggerCircling();
+        void TriggerTitleChange();
 
-        void TriggerNewTextNotification(const std::wstring_view newText) override;
+        void TriggerNewTextNotification(const std::wstring_view newText);
 
         void TriggerFontChange(const int iDpi,
                                const FontInfoDesired& FontInfoDesired,
diff --git a/src/renderer/inc/DummyRenderTarget.hpp b/src/renderer/inc/DummyRenderTarget.hpp
deleted file mode 100644
index fb105e599ba..00000000000
--- a/src/renderer/inc/DummyRenderTarget.hpp
+++ /dev/null
@@ -1,35 +0,0 @@
-/*++
-Copyright (c) Microsoft Corporation
-Licensed under the MIT license.
-
-Module Name:
-- DummyRenderTarget.hpp
-
-Abstract:
-- Provides an empty implementation of the IRenderTarget interface.
-    This is needed for some tests, where certain objects need a reference to a
-    IRenderTarget
-
-Author(s):
-- Mike Griese (migrie) Nov 2018
---*/
-
-#pragma once
-#include "IRenderTarget.hpp"
-
-class DummyRenderTarget final : public Microsoft::Console::Render::IRenderTarget
-{
-public:
-    DummyRenderTarget() {}
-    void TriggerRedraw(const Microsoft::Console::Types::Viewport& /*region*/) override {}
-    void TriggerRedraw(const COORD* const /*pcoord*/) override {}
-    void TriggerRedrawCursor(const COORD* const /*pcoord*/) override {}
-    void TriggerRedrawAll() override {}
-    void TriggerTeardown() noexcept override {}
-    void TriggerSelection() override {}
-    void TriggerScroll() override {}
-    void TriggerScroll(const COORD* const /*pcoordDelta*/) override {}
-    void TriggerCircling() override {}
-    void TriggerTitleChange() override {}
-    void TriggerNewTextNotification(const std::wstring_view) override {}
-};
diff --git a/src/renderer/inc/DummyRenderer.hpp b/src/renderer/inc/DummyRenderer.hpp
new file mode 100644
index 00000000000..0d168311a6e
--- /dev/null
+++ b/src/renderer/inc/DummyRenderer.hpp
@@ -0,0 +1,25 @@
+/*++
+Copyright (c) Microsoft Corporation
+Licensed under the MIT license.
+
+Module Name:
+- DummyRenderer.hpp
+
+Abstract:
+- Provides a minimal instantiation of the Renderer class.
+    This is needed for some tests, where certain objects need a reference to a
+    Renderer
+--*/
+
+#pragma once
+#include "../base/renderer.hpp"
+
+class DummyRenderer final : public Microsoft::Console::Render::Renderer
+{
+public:
+    DummyRenderer(Microsoft::Console::Render::IRenderData* pData = nullptr) :
+        Microsoft::Console::Render::Renderer(_renderSettings, pData, nullptr, 0, nullptr) {}
+
+private:
+    Microsoft::Console::Render::RenderSettings _renderSettings;
+};
diff --git a/src/renderer/inc/IRenderTarget.hpp b/src/renderer/inc/IRenderTarget.hpp
deleted file mode 100644
index 6f32c40bd84..00000000000
--- a/src/renderer/inc/IRenderTarget.hpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/*++
-Copyright (c) Microsoft Corporation
-Licensed under the MIT license.
-
-Module Name:
-- IRenderTarget.hpp
-
-Abstract:
-- This serves as the entry point for console rendering activities.
-
-Author(s):
-- Michael Niksa (MiNiksa) 17-Nov-2015
---*/
-
-#pragma once
-
-#include "FontInfoDesired.hpp"
-#include "IRenderEngine.hpp"
-#include "../types/inc/viewport.hpp"
-
-namespace Microsoft::Console::Render
-{
-    class IRenderTarget
-    {
-    public:
-        virtual ~IRenderTarget() = 0;
-
-    protected:
-        IRenderTarget() = default;
-        IRenderTarget(const IRenderTarget&) = default;
-        IRenderTarget(IRenderTarget&&) = default;
-        IRenderTarget& operator=(const IRenderTarget&) = default;
-        IRenderTarget& operator=(IRenderTarget&&) = default;
-
-    public:
-        virtual void TriggerRedraw(const Microsoft::Console::Types::Viewport& region) = 0;
-        virtual void TriggerRedraw(const COORD* const pcoord) = 0;
-        virtual void TriggerRedrawCursor(const COORD* const pcoord) = 0;
-
-        virtual void TriggerRedrawAll() = 0;
-        virtual void TriggerTeardown() noexcept = 0;
-
-        virtual void TriggerSelection() = 0;
-        virtual void TriggerScroll() = 0;
-        virtual void TriggerScroll(const COORD* const pcoordDelta) = 0;
-        virtual void TriggerCircling() = 0;
-        virtual void TriggerTitleChange() = 0;
-
-        virtual void TriggerNewTextNotification(const std::wstring_view newText) = 0;
-    };
-
-    inline Microsoft::Console::Render::IRenderTarget::~IRenderTarget() {}
-
-}
diff --git a/src/renderer/inc/RenderSettings.hpp b/src/renderer/inc/RenderSettings.hpp
index d90283c9f6f..110075b2969 100644
--- a/src/renderer/inc/RenderSettings.hpp
+++ b/src/renderer/inc/RenderSettings.hpp
@@ -41,7 +41,7 @@ namespace Microsoft::Console::Render
         size_t GetColorAliasIndex(const ColorAlias alias) const noexcept;
         std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept;
         std::pair<COLORREF, COLORREF> GetAttributeColorsWithAlpha(const TextAttribute& attr) const noexcept;
-        void ToggleBlinkRendition(class IRenderTarget& renderTarget) noexcept;
+        void ToggleBlinkRendition(class Renderer& renderer) noexcept;
 
     private:
         til::enumset<Mode> _renderMode{ Mode::BlinkAllowed, Mode::IntenseIsBright };
diff --git a/src/types/IBaseData.h b/src/types/IBaseData.h
index 58804c38269..d8ba21d8711 100644
--- a/src/types/IBaseData.h
+++ b/src/types/IBaseData.h
@@ -15,6 +15,7 @@ Author(s):
 #pragma once
 
 #include "inc/viewport.hpp"
+#include "../renderer/inc/FontInfo.hpp"
 
 class TextBuffer;
 


Test patch:
diff --git a/src/inc/test/CommonState.hpp b/src/inc/test/CommonState.hpp
index 6319bb90208..909f8c58131 100644
--- a/src/inc/test/CommonState.hpp
+++ b/src/inc/test/CommonState.hpp
@@ -64,11 +64,8 @@ class CommonState
         m_readHandle.reset(nullptr);
     }
 
-    void PrepareGlobalFont()
+    void PrepareGlobalFont(const COORD coordFontSize = { 8, 12 })
     {
-        COORD coordFontSize;
-        coordFontSize.X = 8;
-        coordFontSize.Y = 12;
         m_pFontInfo = new FontInfo(L"Consolas", 0, 0, coordFontSize, 0);
     }
 
@@ -80,12 +77,26 @@ class CommonState
         }
     }
 
+    void PrepareGlobalRenderer()
+    {
+        Globals& g = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals();
+        CONSOLE_INFORMATION& gci = g.getConsoleInformation();
+        g.pRender = new Microsoft::Console::Render::Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, nullptr);
+    }
+
+    void CleanupGlobalRenderer()
+    {
+        const Globals& g = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals();
+        delete g.pRender;
+    }
+
     void PrepareGlobalScreenBuffer(const short viewWidth = s_csWindowWidth,
                                    const short viewHeight = s_csWindowHeight,
                                    const short bufferWidth = s_csBufferWidth,
                                    const short bufferHeight = s_csBufferHeight)
     {
-        CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
+        Globals& g = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals();
+        CONSOLE_INFORMATION& gci = g.getConsoleInformation();
         COORD coordWindowSize;
         coordWindowSize.X = viewWidth;
         coordWindowSize.Y = viewHeight;
@@ -103,6 +114,18 @@ class CommonState
                                                            TextAttribute{ FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED },
                                                            uiCursorSize,
                                                            &gci.pCurrentScreenBuffer));
+
+        // If we have a renderer, we need to call EnablePainting to initialize
+        // the viewport. If not, we mark the text buffer as inactive so that it
+        // doesn't try to trigger a redraw on a non-existent renderer.
+        if (g.pRender)
+        {
+            g.pRender->EnablePainting();
+        }
+        else
+        {
+            gci.pCurrentScreenBuffer->_textBuffer->SetAsActiveBuffer(false);
+        }
     }
 
     void CleanupGlobalScreenBuffer()
@@ -149,7 +172,8 @@ class CommonState
                                   const short bufferWidth = s_csBufferWidth,
                                   const short bufferHeight = s_csBufferHeight)
     {
-        CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
+        Globals& g = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals();
+        CONSOLE_INFORMATION& gci = g.getConsoleInformation();
         COORD coordScreenBufferSize;
         coordScreenBufferSize.X = bufferWidth;
         coordScreenBufferSize.Y = bufferHeight;
@@ -165,7 +189,8 @@ class CommonState
             std::unique_ptr<TextBuffer> textBuffer = std::make_unique<TextBuffer>(coordScreenBufferSize,
                                                                                   initialAttributes,
                                                                                   uiCursorSize,
-                                                                                  gci.pCurrentScreenBuffer->GetRenderTarget());
+                                                                                  true,
+                                                                                  *g.pRender);
             if (textBuffer.get() == nullptr)
             {
                 m_hrTextBufferInfo = E_OUTOFMEMORY;
@@ -175,6 +200,18 @@ class CommonState
                 m_hrTextBufferInfo = S_OK;
             }
             gci.pCurrentScreenBuffer->_textBuffer.swap(textBuffer);
+
+            // If we have a renderer, we need to call EnablePainting to initialize
+            // the viewport. If not, we mark the text buffer as inactive so that it
+            // doesn't try to trigger a redraw on a non-existent renderer.
+            if (g.pRender)
+            {
+                g.pRender->EnablePainting();
+            }
+            else
+            {
+                gci.pCurrentScreenBuffer->_textBuffer->SetAsActiveBuffer(false);
+            }
         }
         catch (...)
         {