mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 18:43:54 -06:00
Implement DECSET 2026 - Synchronized Output (#18826)
This commit is contained in:
parent
68d9e0d038
commit
773a4b9198
@ -4,8 +4,6 @@
|
||||
#include "precomp.h"
|
||||
#include "renderer.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
using namespace Microsoft::Console::Render;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
||||
@ -90,6 +88,11 @@ IRenderData* Renderer::GetRenderData() const noexcept
|
||||
_pData->UnlockConsole();
|
||||
});
|
||||
|
||||
if (_isSynchronizingOutput)
|
||||
{
|
||||
_synchronizeWithOutput();
|
||||
}
|
||||
|
||||
// Last chance check if anything scrolled without an explicit invalidate notification since the last frame.
|
||||
_CheckViewportAndScroll();
|
||||
|
||||
@ -183,6 +186,71 @@ void Renderer::NotifyPaintFrame() noexcept
|
||||
_thread.NotifyPaint();
|
||||
}
|
||||
|
||||
// NOTE: You must be holding the console lock when calling this function.
|
||||
void Renderer::SynchronizedOutputBegin() noexcept
|
||||
{
|
||||
// Kick the render thread into calling `_synchronizeWithOutput()`.
|
||||
_isSynchronizingOutput = true;
|
||||
}
|
||||
|
||||
// NOTE: You must be holding the console lock when calling this function.
|
||||
void Renderer::SynchronizedOutputEnd() noexcept
|
||||
{
|
||||
// Unblock `_synchronizeWithOutput()` from the `WaitOnAddress` call.
|
||||
_isSynchronizingOutput = false;
|
||||
WakeByAddressSingle(&_isSynchronizingOutput);
|
||||
|
||||
// It's crucial to give the render thread at least a chance to gain the lock.
|
||||
// Otherwise, a VT application could continuously spam DECSET 2026 (Synchronized Output) and
|
||||
// essentially drop our renderer to 10 FPS, because `_isSynchronizingOutput` is always true.
|
||||
//
|
||||
// Obviously calling LockConsole/UnlockConsole here is an awful, ugly hack,
|
||||
// since there's no guarantee that this is the same lock as the one the VT parser uses.
|
||||
// But the alternative is Denial-Of-Service of the render thread.
|
||||
//
|
||||
// Note that this causes raw throughput of DECSET 2026 to be comparatively low, but that's fine.
|
||||
// Apps that use DECSET 2026 don't produce that sequence continuously, but rather at a fixed rate.
|
||||
_pData->UnlockConsole();
|
||||
_pData->LockConsole();
|
||||
}
|
||||
|
||||
void Renderer::_synchronizeWithOutput() noexcept
|
||||
{
|
||||
constexpr DWORD timeout = 100;
|
||||
|
||||
UINT64 start = 0;
|
||||
DWORD elapsed = 0;
|
||||
bool wrong = false;
|
||||
|
||||
QueryUnbiasedInterruptTime(&start);
|
||||
|
||||
// Wait for `_isSynchronizingOutput` to be set to false or for a timeout to occur.
|
||||
while (true)
|
||||
{
|
||||
// We can't call a blocking function while holding the console lock, so release it temporarily.
|
||||
_pData->UnlockConsole();
|
||||
const auto ok = WaitOnAddress(&_isSynchronizingOutput, &wrong, sizeof(_isSynchronizingOutput), timeout - elapsed);
|
||||
_pData->LockConsole();
|
||||
|
||||
if (!ok || !_isSynchronizingOutput)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
UINT64 now;
|
||||
QueryUnbiasedInterruptTime(&now);
|
||||
elapsed = static_cast<DWORD>((now - start) / 10000);
|
||||
if (elapsed >= timeout)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If a timeout occurred, `_isSynchronizingOutput` may still be true.
|
||||
// Set it to false now to skip calling `_synchronizeWithOutput()` on the next frame.
|
||||
_isSynchronizingOutput = false;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Called when the system has requested we redraw a portion of the console.
|
||||
// Arguments:
|
||||
|
||||
@ -35,6 +35,8 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT PaintFrame();
|
||||
|
||||
void NotifyPaintFrame() noexcept;
|
||||
void SynchronizedOutputBegin() noexcept;
|
||||
void SynchronizedOutputEnd() noexcept;
|
||||
void TriggerSystemRedraw(const til::rect* const prcDirtyClient);
|
||||
void TriggerRedraw(const Microsoft::Console::Types::Viewport& region);
|
||||
void TriggerRedraw(const til::point* const pcoord);
|
||||
@ -91,6 +93,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
[[nodiscard]] HRESULT _PaintFrame() noexcept;
|
||||
[[nodiscard]] HRESULT _PaintFrameForEngine(_In_ IRenderEngine* const pEngine) noexcept;
|
||||
void _synchronizeWithOutput() noexcept;
|
||||
bool _CheckViewportAndScroll();
|
||||
[[nodiscard]] HRESULT _PaintBackground(_In_ IRenderEngine* const pEngine);
|
||||
void _PaintBufferOutput(_In_ IRenderEngine* const pEngine);
|
||||
@ -124,6 +127,7 @@ namespace Microsoft::Console::Render
|
||||
std::function<void()> _pfnBackgroundColorChanged;
|
||||
std::function<void()> _pfnFrameColorChanged;
|
||||
std::function<void()> _pfnRendererEnteredErrorState;
|
||||
bool _isSynchronizingOutput = false;
|
||||
bool _forceUpdateViewport = false;
|
||||
|
||||
til::point_span _lastSelectionPaintSpan{};
|
||||
|
||||
@ -34,7 +34,7 @@ DWORD WINAPI RenderThread::_ThreadProc()
|
||||
|
||||
// Between waiting on _hEvent and calling PaintFrame() there should be a minimal delay,
|
||||
// so that a key press progresses to a drawing operation as quickly as possible.
|
||||
// As such, we wait for the renderer to complete _before_ waiting on _hEvent.
|
||||
// As such, we wait for the renderer to complete _before_ waiting on `_redraw`.
|
||||
renderer->WaitUntilCanRender();
|
||||
|
||||
_redraw.wait();
|
||||
@ -78,6 +78,8 @@ void RenderThread::EnablePainting() noexcept
|
||||
}
|
||||
}
|
||||
|
||||
// This function is meant to only be called by `Renderer`. You should use `TriggerTeardown()` instead,
|
||||
// even if you plan to call `EnablePainting()` later, because that ensures proper synchronization.
|
||||
void RenderThread::DisablePainting() noexcept
|
||||
{
|
||||
_enable.ResetEvent();
|
||||
|
||||
@ -544,6 +544,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
|
||||
ALTERNATE_SCROLL = DECPrivateMode(1007),
|
||||
ASB_AlternateScreenBuffer = DECPrivateMode(1049),
|
||||
XTERM_BracketedPasteMode = DECPrivateMode(2004),
|
||||
SO_SynchronizedOutput = DECPrivateMode(2026),
|
||||
GCM_GraphemeClusterMode = DECPrivateMode(2027),
|
||||
W32IM_Win32InputMode = DECPrivateMode(9001),
|
||||
};
|
||||
|
||||
@ -1914,6 +1914,19 @@ void AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
|
||||
case DispatchTypes::ModeParams::XTERM_BracketedPasteMode:
|
||||
_api.SetSystemMode(ITerminalApi::Mode::BracketedPaste, enable);
|
||||
break;
|
||||
case DispatchTypes::ModeParams::SO_SynchronizedOutput:
|
||||
if (_renderer)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
_renderer->SynchronizedOutputBegin();
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderer->SynchronizedOutputEnd();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DispatchTypes::ModeParams::GCM_GraphemeClusterMode:
|
||||
break;
|
||||
case DispatchTypes::ModeParams::W32IM_Win32InputMode:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user