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:
parent
bfd5248a2e
commit
813286c523
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; };
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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" };
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, ¶ms));
|
||||
}
|
||||
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, ¶ms));
|
||||
}
|
||||
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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue