Reduce latency with DXGI 1.3 GetFrameLatencyWaitableObject (#6435)

This pull request reduces input lag, especially with selection, by using
`IDXGISwapChain2::GetFrameLatencyWaitableObject`.

This is based on the [DXGI 1.3 documentation].

Excerpt from the [DXGI 1.3 improvement list]:
> The following functionality has been added in Microsoft DirectX
Graphics Infrastructure (DXGI) 1.3, which is included starting in
Windows 8.1.

Before, during rendering:
1. render frame
2. call `Present` on swap chain:
   2.a. blocks until it can present
   2.b. meanwhile, selection/text in terminal might have changed, but
     we're still using the frame that we rendered before blocking
   2.c. presents

After, during rendering:
1. block until we can present
2. render frame with latest data
3. call `Present` on swap chain:
  3.a. present without blocking

[DXGI 1.3 documentation]: https://docs.microsoft.com/en-us/windows/uwp/gaming/reduce-latency-with-dxgi-1-3-swap-chains
[DXGI 1.3 improvement list]: https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/dxgi-1-3-improvements:
This commit is contained in:
greg904 2020-06-11 00:35:14 +02:00 committed by GitHub
parent 0c93b2ebbe
commit a921bbfebb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 72 additions and 1 deletions

View file

@ -40,3 +40,10 @@ HRESULT RenderEngineBase::PrepareRenderInfo(const RenderFrameInfo& /*info*/) noe
{
return S_FALSE;
}
// Method Description:
// - Blocks until the engine is able to render without blocking.
void RenderEngineBase::WaitUntilCanRender() noexcept
{
// do nothing by default
}

View file

@ -1200,3 +1200,13 @@ void Renderer::ResetErrorStateAndResume()
// because we're not stateful (we could be in the future), all we want to do is reenable painting.
EnablePainting();
}
// Method Description:
// - Blocks until the engines are able to render without blocking.
void Renderer::WaitUntilCanRender()
{
for (const auto pEngine : _rgpEngines)
{
pEngine->WaitUntilCanRender();
}
}

View file

@ -73,6 +73,7 @@ namespace Microsoft::Console::Render
void EnablePainting() override;
void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) override;
void WaitUntilCanRender() override;
void AddRenderEngine(_In_ IRenderEngine* const pEngine) override;

View file

@ -201,6 +201,7 @@ DWORD WINAPI RenderThread::_ThreadProc()
ResetEvent(_hPaintCompletedEvent);
_pRenderer->WaitUntilCanRender();
LOG_IF_FAILED(_pRenderer->PaintFrame());
SetEvent(_hPaintCompletedEvent);

View file

@ -83,6 +83,7 @@ DxEngine::DxEngine() :
_glyphCell{},
_boxDrawingEffect{},
_haveDeviceResources{ false },
_swapChainFrameLatencyWaitableObject{ INVALID_HANDLE_VALUE },
_retroTerminalEffects{ false },
_forceFullRepaintRendering{ false },
_softwareRendering{ false },
@ -425,6 +426,11 @@ try
if (createSwapChain)
{
_swapChainFlags = 0;
// requires DXGI 1.3 which was introduced in Windows 8.1
WI_SetFlagIf(_swapChainFlags, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT, IsWindows8Point1OrGreater());
DXGI_SWAP_CHAIN_DESC1 SwapChainDesc = { 0 };
SwapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
SwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
@ -433,6 +439,7 @@ try
SwapChainDesc.SampleDesc.Count = 1;
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
SwapChainDesc.Scaling = DXGI_SCALING_NONE;
SwapChainDesc.Flags = _swapChainFlags;
switch (_chainMode)
{
@ -487,6 +494,20 @@ try
THROW_HR(E_NOTIMPL);
}
if (IsWindows8Point1OrGreater())
{
::Microsoft::WRL::ComPtr<IDXGISwapChain2> swapChain2;
const HRESULT asResult = _dxgiSwapChain.As(&swapChain2);
if (SUCCEEDED(asResult))
{
_swapChainFrameLatencyWaitableObject = wil::unique_handle{ swapChain2->GetFrameLatencyWaitableObject() };
}
else
{
LOG_HR_MSG(asResult, "Failed to obtain IDXGISwapChain2 from swap chain");
}
}
if (_retroTerminalEffects)
{
const HRESULT hr = _SetupTerminalEffects();
@ -612,6 +633,7 @@ void DxEngine::_ReleaseDeviceResources() noexcept
_dxgiSurface.Reset();
_dxgiSwapChain.Reset();
_swapChainFrameLatencyWaitableObject.reset();
if (nullptr != _d3dDeviceContext.Get())
{
@ -960,7 +982,7 @@ try
_d2dRenderTarget.Reset();
// Change the buffer size and recreate the render target (and surface)
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.width<UINT>(), clientSize.height<UINT>(), DXGI_FORMAT_B8G8R8A8_UNORM, 0));
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.width<UINT>(), clientSize.height<UINT>(), DXGI_FORMAT_B8G8R8A8_UNORM, _swapChainFlags));
RETURN_IF_FAILED(_PrepareRenderTarget());
// OK we made it past the parts that can cause errors. We can release our failure handler.
@ -1085,6 +1107,26 @@ CATCH_RETURN()
return S_OK;
}
// Method Description:
// - Blocks until the engine is able to render without blocking.
// - See https://docs.microsoft.com/en-us/windows/uwp/gaming/reduce-latency-with-dxgi-1-3-swap-chains.
void DxEngine::WaitUntilCanRender() noexcept
{
if (!_swapChainFrameLatencyWaitableObject)
{
return;
}
const auto ret = WaitForSingleObjectEx(
_swapChainFrameLatencyWaitableObject.get(),
1000, // 1 second timeout (shouldn't ever occur)
true);
if (ret != WAIT_OBJECT_0)
{
LOG_WIN32_MSG(ret, "Waiting for swap chain frame latency waitable object returned error or timeout.");
}
}
// Routine Description:
// - Takes queued drawing information and presents it to the screen.
// - This is separated out so it can be done outside the lock as it's expensive.

View file

@ -9,6 +9,7 @@
#include <dxgi.h>
#include <dxgi1_2.h>
#include <dxgi1_3.h>
#include <d3d11.h>
#include <d2d1.h>
@ -73,6 +74,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT StartPaint() noexcept override;
[[nodiscard]] HRESULT EndPaint() noexcept override;
void WaitUntilCanRender() noexcept override;
[[nodiscard]] HRESULT Present() noexcept override;
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
@ -180,7 +183,9 @@ namespace Microsoft::Console::Render
::Microsoft::WRL::ComPtr<ID2D1RenderTarget> _d2dRenderTarget;
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> _d2dBrushForeground;
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> _d2dBrushBackground;
UINT _swapChainFlags;
::Microsoft::WRL::ComPtr<IDXGISwapChain1> _dxgiSwapChain;
wil::unique_handle _swapChainFrameLatencyWaitableObject;
// Terminal effects resources.
bool _retroTerminalEffects;

View file

@ -49,6 +49,8 @@ namespace Microsoft::Console::Render
public:
[[nodiscard]] virtual HRESULT StartPaint() noexcept = 0;
[[nodiscard]] virtual HRESULT EndPaint() noexcept = 0;
virtual void WaitUntilCanRender() noexcept = 0;
[[nodiscard]] virtual HRESULT Present() noexcept = 0;
[[nodiscard]] virtual HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept = 0;

View file

@ -58,6 +58,7 @@ namespace Microsoft::Console::Render
virtual void EnablePainting() = 0;
virtual void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) = 0;
virtual void WaitUntilCanRender() = 0;
virtual void AddRenderEngine(_In_ IRenderEngine* const pEngine) = 0;

View file

@ -40,6 +40,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept override;
void WaitUntilCanRender() noexcept override;
protected:
[[nodiscard]] virtual HRESULT _DoUpdateTitle(const std::wstring& newTitle) noexcept = 0;