AtlasEngine: Fix various bugs found in testing (#13906)

In testing the following issues were found in AtlasEngine and fixed:
1. "Toggle terminal visual effects" action not working
2. `d2dMode` failed to work with transparent backgrounds
3. `GetSwapChainHandle()` is thread-unsafe due to it being called outside
  of the console lock and with single-threaded Direct2D enabled
4. 2 swap chain buffers are less performant than 3
5. Flip-Discard and `Present()` is less energy efficient than
  Flip-Sequential and `Present1()`
6. `d2dMode` used to copy the front to back buffer for partial rendering,
  but always redraw the entire dirty region anyways
7. Added support for DirectX 9 hardware
8. If custom shaders are used not all pixels would be presented

Closes #13906

## Validation Steps Performed
1. Toggling visual effects runs retro shader 
   With a custom shader set, it toggles the shader 
   Toggling `experimental.rendering.software` toggles the shader 
2. `"backgroundImage": "desktopWallpaper"` works with D2D  and D3D 
3. Adding a `Sleep(3000)` in `_AttachDxgiSwapChainToXaml` doesn't break
   Windows 10  nor Windows 11 
4. Screen animations run at 144 FPS  even while moving the window 
5. No weird artefacts during cursor movement or scrolling 
6. No weird artefacts during cursor movement or scrolling 
7. Forcing DirectX 9.3 in `dxcpl` runs fine 
This commit is contained in:
Leonard Hecker 2022-09-03 00:10:09 +02:00 committed by GitHub
parent bfd5248a2e
commit 813286c523
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 148 additions and 216 deletions

View File

@ -326,7 +326,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Tell the DX Engine to notify us when the swap chain changes.
// We do this after we initially set the swapchain so as to avoid unnecessary callbacks (and locking problems)
_renderEngine->SetCallback(std::bind(&ControlCore::_renderEngineSwapChainChanged, this));
_renderEngine->SetCallback([this](auto handle) { _renderEngineSwapChainChanged(handle); });
_renderEngine->SetRetroTerminalEffect(_settings->RetroTerminalEffect());
_renderEngine->SetPixelShaderPath(_settings->PixelShaderPath());
@ -596,24 +596,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ControlCore::ToggleShaderEffects()
{
const auto path = _settings->PixelShaderPath();
auto lock = _terminal->LockForWriting();
// Originally, this action could be used to enable the retro effects
// even when they're set to `false` in the settings. If the user didn't
// specify a custom pixel shader, manually enable the legacy retro
// effect first. This will ensure that a toggle off->on will still work,
// even if they currently have retro effect off.
if (_settings->PixelShaderPath().empty() && !_renderEngine->GetRetroTerminalEffect())
if (path.empty())
{
// SetRetroTerminalEffect to true will enable the effect. In this
// case, the shader effect will already be disabled (because neither
// a pixel shader nor the retro effects were originally requested).
// So we _don't_ want to toggle it again below, because that would
// toggle it back off.
_renderEngine->SetRetroTerminalEffect(true);
_renderEngine->SetRetroTerminalEffect(!_renderEngine->GetRetroTerminalEffect());
}
else
{
_renderEngine->ToggleShaderEffects();
_renderEngine->SetPixelShaderPath(_renderEngine->GetPixelShaderPath().empty() ? std::wstring_view{ path } : std::wstring_view{});
}
// Always redraw after toggling effects. This way even if the control
// does not have focus it will update immediately.
@ -1558,25 +1554,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
uint64_t ControlCore::SwapChainHandle() const
{
// This is called by:
// * TermControl::RenderEngineSwapChainChanged, who is only registered
// after Core::Initialize() is called.
// * TermControl::_InitializeTerminal, after the call to Initialize, for
// _AttachDxgiSwapChainToXaml.
// In both cases, we'll have a _renderEngine by then.
return reinterpret_cast<uint64_t>(_renderEngine->GetSwapChainHandle());
}
void ControlCore::_rendererWarning(const HRESULT hr)
{
_RendererWarningHandlers(*this, winrt::make<RendererWarningArgs>(hr));
}
void ControlCore::_renderEngineSwapChainChanged()
void ControlCore::_renderEngineSwapChainChanged(const HANDLE handle)
{
_SwapChainChangedHandlers(*this, nullptr);
_SwapChainChangedHandlers(*this, winrt::box_value<uint64_t>(reinterpret_cast<uint64_t>(handle)));
}
void ControlCore::_rendererBackgroundColorChanged()

View File

@ -75,7 +75,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void SizeChanged(const double width, const double height);
void ScaleChanged(const double scale);
uint64_t SwapChainHandle() const;
void AdjustFontSize(int fontSizeDelta);
void ResetFontSize();
@ -315,7 +314,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
#pragma region RendererCallbacks
void _rendererWarning(const HRESULT hr);
void _renderEngineSwapChainChanged();
void _renderEngineSwapChainChanged(const HANDLE handle);
void _rendererBackgroundColorChanged();
void _rendererTabColorChanged();
#pragma endregion

View File

@ -76,8 +76,6 @@ namespace Microsoft.Terminal.Control
IControlAppearance UnfocusedAppearance { get; };
Boolean HasUnfocusedAppearance();
UInt64 SwapChainHandle { get; };
Windows.Foundation.Size FontSize { get; };
String FontFaceName { get; };
UInt16 FontWeight { get; };

View File

@ -731,19 +731,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _core.ConnectionState();
}
winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged(IInspectable /*sender*/, IInspectable /*args*/)
winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged(IInspectable /*sender*/, IInspectable args)
{
// This event is only registered during terminal initialization,
// so we don't need to check _initializedTerminal.
// We also don't lock for things that come back from the renderer.
auto weakThis{ get_weak() };
const auto weakThis{ get_weak() };
// Create a copy of the swap chain HANDLE in args, since we don't own that parameter.
// By the time we return from the co_await below, it might be deleted already.
winrt::handle handle;
const auto processHandle = GetCurrentProcess();
const auto sourceHandle = reinterpret_cast<HANDLE>(winrt::unbox_value<uint64_t>(args));
THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(processHandle, sourceHandle, processHandle, handle.put(), 0, FALSE, DUPLICATE_SAME_ACCESS));
co_await wil::resume_foreground(Dispatcher());
if (auto control{ weakThis.get() })
{
const auto chainHandle = reinterpret_cast<HANDLE>(control->_core.SwapChainHandle());
_AttachDxgiSwapChainToXaml(chainHandle);
_AttachDxgiSwapChainToXaml(handle.get());
}
}
@ -830,21 +835,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
_interactivity.Initialize();
_AttachDxgiSwapChainToXaml(reinterpret_cast<HANDLE>(_core.SwapChainHandle()));
// Tell the DX Engine to notify us when the swap chain changes. We do
// this after we initially set the swapchain so as to avoid unnecessary
// callbacks (and locking problems)
_core.SwapChainChanged({ get_weak(), &TermControl::RenderEngineSwapChainChanged });
// !! LOAD BEARING !!
// Make sure you enable painting _AFTER_ calling _AttachDxgiSwapChainToXaml
//
// If you EnablePainting first, then you almost certainly won't have any
// problems when running in Debug. However, in Release, you'll run into
// issues where the Renderer starts trying to paint before we've
// actually attached the swapchain to anything, and the DxEngine is not
// prepared to handle that.
_core.EnablePainting();
auto bufferHeight = _core.BufferHeight();

View File

@ -291,6 +291,11 @@ HRESULT AtlasEngine::Enable() noexcept
return S_OK;
}
[[nodiscard]] std::wstring_view AtlasEngine::GetPixelShaderPath() noexcept
{
return _api.customPixelShaderPath;
}
[[nodiscard]] bool AtlasEngine::GetRetroTerminalEffect() const noexcept
{
return _api.useRetroTerminalEffect;
@ -301,17 +306,6 @@ HRESULT AtlasEngine::Enable() noexcept
return static_cast<float>(_api.dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
}
[[nodiscard]] HANDLE AtlasEngine::GetSwapChainHandle()
{
if (WI_IsFlagSet(_api.invalidations, ApiInvalidations::Device))
{
_createResources();
WI_ClearFlag(_api.invalidations, ApiInvalidations::Device);
}
return _api.swapChainHandle.get();
}
[[nodiscard]] Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept
{
assert(_api.fontMetrics.cellSize.x != 0);
@ -337,7 +331,7 @@ void AtlasEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasin
}
}
void AtlasEngine::SetCallback(std::function<void()> pfn) noexcept
void AtlasEngine::SetCallback(std::function<void(HANDLE)> pfn) noexcept
{
_api.swapChainChangedCallback = std::move(pfn);
}
@ -428,10 +422,6 @@ void AtlasEngine::SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept
return S_OK;
}
void AtlasEngine::ToggleShaderEffects() noexcept
{
}
[[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept
{
static constexpr std::array fallbackFaceNames{ static_cast<const wchar_t*>(nullptr), L"Consolas", L"Lucida Console", L"Courier New" };

View File

@ -582,6 +582,9 @@ void AtlasEngine::_createResources()
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1,
};
auto hr = E_UNEXPECTED;
@ -624,16 +627,6 @@ void AtlasEngine::_createResources()
_r.deviceContext = deviceContext.query<ID3D11DeviceContext1>();
}
{
wil::com_ptr<IDXGIAdapter1> dxgiAdapter;
THROW_IF_FAILED(_r.device.query<IDXGIObject>()->GetParent(__uuidof(dxgiAdapter), dxgiAdapter.put_void()));
THROW_IF_FAILED(dxgiAdapter->GetParent(__uuidof(_r.dxgiFactory), _r.dxgiFactory.put_void()));
DXGI_ADAPTER_DESC1 desc;
THROW_IF_FAILED(dxgiAdapter->GetDesc1(&desc));
_r.d2dMode = debugForceD2DMode || WI_IsAnyFlagSet(desc.Flags, DXGI_ADAPTER_FLAG_REMOTE | DXGI_ADAPTER_FLAG_SOFTWARE);
}
#ifndef NDEBUG
// D3D debug messages
if (deviceFlags & D3D11_CREATE_DEVICE_DEBUG)
@ -646,6 +639,18 @@ void AtlasEngine::_createResources()
}
#endif // NDEBUG
const auto featureLevel = _r.device->GetFeatureLevel();
{
wil::com_ptr<IDXGIAdapter1> dxgiAdapter;
THROW_IF_FAILED(_r.device.query<IDXGIObject>()->GetParent(__uuidof(dxgiAdapter), dxgiAdapter.put_void()));
THROW_IF_FAILED(dxgiAdapter->GetParent(__uuidof(_r.dxgiFactory), _r.dxgiFactory.put_void()));
DXGI_ADAPTER_DESC1 desc;
THROW_IF_FAILED(dxgiAdapter->GetDesc1(&desc));
_r.d2dMode = debugForceD2DMode || featureLevel < D3D_FEATURE_LEVEL_10_0 || WI_IsAnyFlagSet(desc.Flags, DXGI_ADAPTER_FLAG_REMOTE | DXGI_ADAPTER_FLAG_SOFTWARE);
}
if (!_r.d2dMode)
{
// Our constant buffer will never get resized
@ -663,7 +668,7 @@ void AtlasEngine::_createResources()
if (!_api.customPixelShaderPath.empty())
{
const char* target = nullptr;
switch (_r.device->GetFeatureLevel())
switch (featureLevel)
{
case D3D_FEATURE_LEVEL_10_0:
target = "ps_4_0";
@ -811,10 +816,19 @@ void AtlasEngine::_createSwapChain()
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.BufferCount = 2;
// Sometimes up to 2 buffers are locked, for instance during screen capture or when moving the window.
// 3 buffers seems to guarantee a stable framerate at display frequency at all times.
desc.BufferCount = 3;
desc.Scaling = DXGI_SCALING_NONE;
desc.SwapEffect = _sr.isWindows10OrGreater && !_r.d2dMode ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
// If our background is opaque we can enable "independent" flips by setting DXGI_SWAP_EFFECT_FLIP_DISCARD and DXGI_ALPHA_MODE_IGNORE.
// DXGI_SWAP_EFFECT_FLIP_DISCARD is a mode that was created at a time were display drivers
// lacked support for Multiplane Overlays (MPO) and were copying buffers was expensive.
// This allowed DWM to quickly draw overlays (like gamebars) on top of rendered content.
// With faster GPU memory in general and with support for MPO in particular this isn't
// really an advantage anymore. Instead DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL allows for a
// more "intelligent" composition and display updates to occur like Panel Self Refresh
// (PSR) which requires dirty rectangles (Present1 API) to work correctly.
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
// If our background is opaque we can enable "independent" flips by setting DXGI_ALPHA_MODE_IGNORE.
// As our swap chain won't have to compose with DWM anymore it reduces the display latency dramatically.
desc.AlphaMode = _api.backgroundOpaqueMixin ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
desc.Flags = debugGeneralPerformance ? 0 : DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
@ -857,7 +871,7 @@ void AtlasEngine::_createSwapChain()
{
try
{
_api.swapChainChangedCallback();
_api.swapChainChangedCallback(_api.swapChainHandle.get());
}
CATCH_LOG();
}

View File

@ -3,7 +3,7 @@
#pragma once
#include <d2d1.h>
#include <d2d1_1.h>
#include <d3d11_1.h>
#include <dwrite_3.h>
@ -60,14 +60,14 @@ namespace Microsoft::Console::Render
// DxRenderer - getter
HRESULT Enable() noexcept override;
[[nodiscard]] std::wstring_view GetPixelShaderPath() noexcept override;
[[nodiscard]] bool GetRetroTerminalEffect() const noexcept override;
[[nodiscard]] float GetScaling() const noexcept override;
[[nodiscard]] HANDLE GetSwapChainHandle() override;
[[nodiscard]] Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept override;
[[nodiscard]] Types::Viewport GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept override;
// DxRenderer - setter
void SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept override;
void SetCallback(std::function<void()> pfn) noexcept override;
void SetCallback(std::function<void(HANDLE)> pfn) noexcept override;
void EnableTransparentBackground(const bool isTransparent) noexcept override;
void SetForceFullRepaintRendering(bool enable) noexcept override;
[[nodiscard]] HRESULT SetHwnd(HWND hwnd) noexcept override;
@ -77,7 +77,6 @@ namespace Microsoft::Console::Render
void SetSoftwareRendering(bool enable) noexcept override;
void SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept override;
[[nodiscard]] HRESULT SetWindowSize(til::size pixels) noexcept override;
void ToggleShaderEffects() noexcept override;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept override;
void UpdateHyperlinkHoveredId(uint16_t hoveredId) noexcept override;
@ -1008,7 +1007,7 @@ namespace Microsoft::Console::Render
// D2D resources
wil::com_ptr<ID3D11Texture2D> atlasBuffer;
wil::com_ptr<ID3D11ShaderResourceView> atlasView;
wil::com_ptr<ID2D1RenderTarget> d2dRenderTarget;
wil::com_ptr<ID2D1DeviceContext> d2dRenderTarget;
wil::com_ptr<ID2D1SolidColorBrush> brush;
wil::com_ptr<IDWriteTextFormat> textFormats[2][2];
Buffer<DWRITE_FONT_AXIS_VALUE> textFormatAxes[2][2];
@ -1038,7 +1037,6 @@ namespace Microsoft::Console::Render
CachedCursorOptions cursorOptions;
RenderInvalidations invalidations = RenderInvalidations::None;
til::rect previousDirtyRectInPx;
til::rect dirtyRect;
i16 scrollOffset = 0;
bool d2dMode = false;
@ -1095,7 +1093,7 @@ namespace Microsoft::Console::Render
i16 scrollOffset = 0;
std::function<void(HRESULT)> warningCallback;
std::function<void()> swapChainChangedCallback;
std::function<void(HANDLE)> swapChainChangedCallback;
wil::unique_handle swapChainHandle;
HWND hwnd = nullptr;
u16 dpi = USER_DEFAULT_SCREEN_DPI; // changes are flagged as ApiInvalidations::Font|Size

View File

@ -63,6 +63,19 @@ using namespace Microsoft::Console::Render;
[[nodiscard]] HRESULT AtlasEngine::Present() noexcept
try
{
const til::rect fullRect{ 0, 0, _r.cellCount.x, _r.cellCount.y };
// A change in the selection or background color (etc.) forces a full redraw.
if (WI_IsFlagSet(_r.invalidations, RenderInvalidations::ConstBuffer) || _r.customPixelShader)
{
_r.dirtyRect = fullRect;
}
if (!_r.dirtyRect)
{
return S_OK;
}
// See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
// > For every frame it renders, the app should wait on this handle before starting any rendering operations.
// > Note that this requirement includes the first frame the app renders with the swap chain.
@ -102,14 +115,60 @@ try
_r.deviceContext->OMSetRenderTargets(1, _r.renderTargetView.addressof(), nullptr);
_r.deviceContext->Draw(3, 0);
}
// > IDXGISwapChain::Present: Partial Presentation (using a dirty rects or scroll) is not supported
// > for SwapChains created with DXGI_SWAP_EFFECT_DISCARD or DXGI_SWAP_EFFECT_FLIP_DISCARD.
// ---> No need to call IDXGISwapChain1::Present1.
THROW_IF_FAILED(_r.swapChain->Present(1, 0));
_r.waitForPresentation = true;
}
// See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
// > For every frame it renders, the app should wait on this handle before starting any rendering operations.
// > Note that this requirement includes the first frame the app renders with the swap chain.
assert(debugGeneralPerformance || _r.frameLatencyWaitableObjectUsed);
if (_r.dirtyRect != fullRect)
{
auto dirtyRectInPx = _r.dirtyRect;
dirtyRectInPx.left *= _r.fontMetrics.cellSize.x;
dirtyRectInPx.top *= _r.fontMetrics.cellSize.y;
dirtyRectInPx.right *= _r.fontMetrics.cellSize.x;
dirtyRectInPx.bottom *= _r.fontMetrics.cellSize.y;
RECT scrollRect{};
POINT scrollOffset{};
DXGI_PRESENT_PARAMETERS params{
.DirtyRectsCount = 1,
.pDirtyRects = dirtyRectInPx.as_win32_rect(),
};
if (_r.scrollOffset)
{
scrollRect = {
0,
std::max<til::CoordType>(0, _r.scrollOffset),
_r.cellCount.x,
_r.cellCount.y + std::min<til::CoordType>(0, _r.scrollOffset),
};
scrollOffset = {
0,
_r.scrollOffset,
};
scrollRect.top *= _r.fontMetrics.cellSize.y;
scrollRect.right *= _r.fontMetrics.cellSize.x;
scrollRect.bottom *= _r.fontMetrics.cellSize.y;
scrollOffset.y *= _r.fontMetrics.cellSize.y;
params.pScrollRect = &scrollRect;
params.pScrollOffset = &scrollOffset;
}
THROW_IF_FAILED(_r.swapChain->Present1(1, 0, &params));
}
else
{
THROW_IF_FAILED(_r.swapChain->Present(1, 0));
}
_r.waitForPresentation = true;
if (!_r.dxgiFactory->IsCurrent())
{
WI_SetFlag(_api.invalidations, ApiInvalidations::Device);
@ -307,7 +366,9 @@ void AtlasEngine::_adjustAtlasSize()
props.pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED };
props.dpiX = static_cast<float>(_r.dpi);
props.dpiY = static_cast<float>(_r.dpi);
THROW_IF_FAILED(_sr.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, _r.d2dRenderTarget.put()));
wil::com_ptr<ID2D1RenderTarget> renderTarget;
THROW_IF_FAILED(_sr.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, renderTarget.addressof()));
_r.d2dRenderTarget = renderTarget.query<ID2D1DeviceContext>();
// We don't really use D2D for anything except DWrite, but it
// can't hurt to ensure that everything it does is pixel aligned.
@ -749,102 +810,19 @@ void AtlasEngine::CachedGlyphLayout::undoScaling(ID2D1RenderTarget* d2dRenderTar
void AtlasEngine::_d2dPresent()
{
const til::rect fullRect{ 0, 0, _r.cellCount.x, _r.cellCount.y };
// A change in the selection or background color (etc.) forces a full redraw.
if (WI_IsFlagSet(_r.invalidations, RenderInvalidations::ConstBuffer))
if (!_r.d2dRenderTarget)
{
_r.dirtyRect = fullRect;
_d2dCreateRenderTarget();
}
if (!_r.dirtyRect)
{
return;
}
auto dirtyRectInPx = _r.dirtyRect;
dirtyRectInPx.left *= _r.fontMetrics.cellSize.x;
dirtyRectInPx.top *= _r.fontMetrics.cellSize.y;
dirtyRectInPx.right *= _r.fontMetrics.cellSize.x;
dirtyRectInPx.bottom *= _r.fontMetrics.cellSize.y;
if (const auto intersection = _r.previousDirtyRectInPx & dirtyRectInPx)
{
wil::com_ptr<ID3D11Resource> backBuffer;
wil::com_ptr<ID3D11Resource> frontBuffer;
THROW_IF_FAILED(_r.swapChain->GetBuffer(0, __uuidof(backBuffer), backBuffer.put_void()));
THROW_IF_FAILED(_r.swapChain->GetBuffer(1, __uuidof(frontBuffer), frontBuffer.put_void()));
D3D11_BOX intersectBox;
intersectBox.left = intersection.left;
intersectBox.top = intersection.top;
intersectBox.front = 0;
intersectBox.right = intersection.right;
intersectBox.bottom = intersection.bottom;
intersectBox.back = 1;
_r.deviceContext->CopySubresourceRegion1(backBuffer.get(), 0, intersection.left, intersection.top, 0, frontBuffer.get(), 0, &intersectBox, 0);
}
_d2dCreateRenderTarget();
_d2dDrawDirtyArea();
// See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
// > For every frame it renders, the app should wait on this handle before starting any rendering operations.
// > Note that this requirement includes the first frame the app renders with the swap chain.
assert(debugGeneralPerformance || _r.frameLatencyWaitableObjectUsed);
if (_r.dirtyRect != fullRect)
{
RECT scrollRect{};
POINT scrollOffset{};
DXGI_PRESENT_PARAMETERS params{
.DirtyRectsCount = 1,
.pDirtyRects = dirtyRectInPx.as_win32_rect(),
};
if (_r.scrollOffset)
{
scrollRect = {
0,
std::max<til::CoordType>(0, _r.scrollOffset),
_r.cellCount.x,
_r.cellCount.y + std::min<til::CoordType>(0, _r.scrollOffset),
};
scrollOffset = {
0,
_r.scrollOffset,
};
scrollRect.top *= _r.fontMetrics.cellSize.y;
scrollRect.right *= _r.fontMetrics.cellSize.x;
scrollRect.bottom *= _r.fontMetrics.cellSize.y;
scrollOffset.y *= _r.fontMetrics.cellSize.y;
params.pScrollRect = &scrollRect;
params.pScrollOffset = &scrollOffset;
}
THROW_IF_FAILED(_r.swapChain->Present1(1, 0, &params));
}
else
{
THROW_IF_FAILED(_r.swapChain->Present(1, 0));
}
_r.glyphQueue.clear();
_r.previousDirtyRectInPx = dirtyRectInPx;
_r.waitForPresentation = true;
WI_ClearAllFlags(_r.invalidations, RenderInvalidations::Cursor | RenderInvalidations::ConstBuffer);
}
void AtlasEngine::_d2dCreateRenderTarget()
{
if (_r.d2dRenderTarget)
{
return;
}
{
wil::com_ptr<ID3D11Texture2D> buffer;
THROW_IF_FAILED(_r.swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), buffer.put_void()));
@ -856,7 +834,9 @@ void AtlasEngine::_d2dCreateRenderTarget()
props.pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED };
props.dpiX = static_cast<float>(_r.dpi);
props.dpiY = static_cast<float>(_r.dpi);
THROW_IF_FAILED(_sr.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, _r.d2dRenderTarget.put()));
wil::com_ptr<ID2D1RenderTarget> renderTarget;
THROW_IF_FAILED(_sr.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, renderTarget.addressof()));
_r.d2dRenderTarget = renderTarget.query<ID2D1DeviceContext>();
// In case _api.realizedAntialiasingMode is D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE we'll
// continuously adjust it in AtlasEngine::_drawGlyph. See _drawGlyph.
@ -944,6 +924,8 @@ void AtlasEngine::_d2dDrawDirtyArea()
// Draw background.
{
_r.d2dRenderTarget->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
auto x1 = beg;
auto x2 = gsl::narrow_cast<u16>(x1 + 1);
auto currentColor = cells[x1].color.y;
@ -965,6 +947,8 @@ void AtlasEngine::_d2dDrawDirtyArea()
const u16r rect{ x1, y, x2, gsl::narrow_cast<u16>(y + 1) };
_d2dFillRectangle(rect, currentColor);
}
_r.d2dRenderTarget->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
}
// Draw text.

View File

@ -16,7 +16,7 @@
#include <unordered_set>
#include <vector>
#include <d2d1.h>
#include <d2d1_1.h>
#include <d3d11_1.h>
#include <d3dcompiler.h>
#include <dwrite_3.h>

View File

@ -244,19 +244,6 @@ bool DxEngine::_HasTerminalEffects() const noexcept
return _terminalEffectsEnabled && (_retroTerminalEffect || !_pixelShaderPath.empty());
}
// Routine Description:
// - Toggles terminal effects off and on. If no terminal effect is configured has no effect
// Arguments:
// Return Value:
// - Void
void DxEngine::ToggleShaderEffects() noexcept
{
_terminalEffectsEnabled = !_terminalEffectsEnabled;
_recreateDeviceRequested = true;
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function 'Log_IfFailed()' which may throw exceptions (f.6).
LOG_IF_FAILED(InvalidateAll());
}
// Routine Description:
// - Loads pixel shader source depending on _retroTerminalEffect and _pixelShaderPath
// Arguments:
@ -744,7 +731,7 @@ try
{
try
{
_pfn();
_pfn(_swapChainHandle.get());
}
CATCH_LOG(); // A failure in the notification function isn't a failure to prepare, so just log it and go on.
}
@ -996,7 +983,7 @@ try
}
CATCH_RETURN();
void DxEngine::SetCallback(std::function<void()> pfn) noexcept
void DxEngine::SetCallback(std::function<void(const HANDLE)> pfn) noexcept
{
_pfn = std::move(pfn);
}
@ -1025,6 +1012,11 @@ try
}
CATCH_LOG()
std::wstring_view DxEngine::GetPixelShaderPath() noexcept
{
return _pixelShaderPath;
}
void DxEngine::SetPixelShaderPath(std::wstring_view value) noexcept
try
{
@ -1062,17 +1054,6 @@ try
}
CATCH_LOG()
HANDLE DxEngine::GetSwapChainHandle() noexcept
{
if (!_swapChainHandle)
{
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function 'Log_IfFailed()' which may throw exceptions (f.6).
LOG_IF_FAILED(_CreateDeviceResources(true));
}
return _swapChainHandle.get();
}
void DxEngine::_InvalidateRectangle(const til::rect& rc)
{
const auto size = _invalidMap.size();

View File

@ -57,22 +57,19 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT SetWindowSize(const til::size pixels) noexcept override;
void SetCallback(std::function<void()> pfn) noexcept override;
void SetCallback(std::function<void(const HANDLE)> pfn) noexcept override;
void SetWarningCallback(std::function<void(const HRESULT)> pfn) noexcept override;
void ToggleShaderEffects() noexcept override;
bool GetRetroTerminalEffect() const noexcept override;
void SetRetroTerminalEffect(bool enable) noexcept override;
std::wstring_view GetPixelShaderPath() noexcept override;
void SetPixelShaderPath(std::wstring_view value) noexcept override;
void SetForceFullRepaintRendering(bool enable) noexcept override;
void SetSoftwareRendering(bool enable) noexcept override;
HANDLE GetSwapChainHandle() noexcept override;
// IRenderEngine Members
[[nodiscard]] HRESULT Invalidate(const til::rect* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const til::rect* const psrRegion) noexcept override;
@ -162,7 +159,7 @@ namespace Microsoft::Console::Render
float _scale;
float _prevScale;
std::function<void()> _pfn;
std::function<void(const HANDLE)> _pfn;
std::function<void(const HRESULT)> _pfnWarningCallback;
bool _isEnabled;

View File

@ -94,18 +94,14 @@ namespace Microsoft::Console::Render
// DxRenderer - getter
virtual HRESULT Enable() noexcept { return S_OK; }
virtual [[nodiscard]] std::wstring_view GetPixelShaderPath() noexcept { return {}; }
virtual [[nodiscard]] bool GetRetroTerminalEffect() const noexcept { return false; }
virtual [[nodiscard]] float GetScaling() const noexcept { return 1; }
#pragma warning(suppress : 26440) // Function '...' can be declared 'noexcept' (f.6).
virtual [[nodiscard]] HANDLE GetSwapChainHandle()
{
return nullptr;
}
virtual [[nodiscard]] Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept { return Types::Viewport::Empty(); }
virtual [[nodiscard]] Types::Viewport GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept { return Types::Viewport::Empty(); }
// DxRenderer - setter
virtual void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept {}
virtual void SetCallback(std::function<void()> pfn) noexcept {}
virtual void SetCallback(std::function<void(HANDLE)> pfn) noexcept {}
virtual void EnableTransparentBackground(const bool isTransparent) noexcept {}
virtual void SetForceFullRepaintRendering(bool enable) noexcept {}
virtual [[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept { return E_NOTIMPL; }
@ -115,7 +111,6 @@ namespace Microsoft::Console::Render
virtual void SetSoftwareRendering(bool enable) noexcept {}
virtual void SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept {}
virtual [[nodiscard]] HRESULT SetWindowSize(const til::size pixels) noexcept { return E_NOTIMPL; }
virtual void ToggleShaderEffects() noexcept {}
virtual [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept { return E_NOTIMPL; }
virtual void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept {}
};