Use RenderSettings for DECSET 2026 - Synchronized Output (#18833)

`RenderSettings` already stores `DECSCNM` (reversed screen),
so it only makes sense to also store DECSET 2026 there.

## Validation Steps Performed
* Same as in #18826 
This commit is contained in:
Leonard Hecker 2025-04-24 19:22:30 +02:00 committed by GitHub
parent 093f5d168c
commit a2d80682c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 44 additions and 40 deletions

View File

@ -143,7 +143,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// the UIA Engine to the renderer. This prevents us from signaling changes to the cursor or buffer.
{
// Now create the renderer and initialize the render thread.
const auto& renderSettings = _terminal->GetRenderSettings();
auto& renderSettings = _terminal->GetRenderSettings();
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(renderSettings, _terminal.get());
_renderer->SetBackgroundColorChangedCallback([this]() { _rendererBackgroundColorChanged(); });

View File

@ -46,9 +46,9 @@ void RenderSettings::RestoreDefaultSettings() noexcept
{
_colorTable = _defaultColorTable;
_colorAliasIndices = _defaultColorAliasIndices;
// For now, DECSCNM is the only render mode we need to reset. The others are
// all user preferences that can't be changed programmatically.
_renderMode.reset(Mode::ScreenReversed);
// DECSCNM and Synchronized Output are the only render mode we need to reset.
// The others are all user preferences that can't be changed programmatically.
_renderMode.reset(Mode::ScreenReversed, Mode::SynchronizedOutput);
}
// Routine Description:

View File

@ -25,7 +25,7 @@ static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 };
// - pData - The interface to console data structures required for rendering
// Return Value:
// - An instance of a Renderer.
Renderer::Renderer(const RenderSettings& renderSettings, IRenderData* pData) :
Renderer::Renderer(RenderSettings& renderSettings, IRenderData* pData) :
_renderSettings(renderSettings),
_pData(pData)
{
@ -187,31 +187,36 @@ void Renderer::NotifyPaintFrame() noexcept
}
// NOTE: You must be holding the console lock when calling this function.
void Renderer::SynchronizedOutputBegin() noexcept
void Renderer::SynchronizedOutputChanged() noexcept
{
// Kick the render thread into calling `_synchronizeWithOutput()`.
_isSynchronizingOutput = true;
}
const auto so = _renderSettings.GetRenderMode(RenderSettings::Mode::SynchronizedOutput);
if (_isSynchronizingOutput == so)
{
return;
}
// 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);
// If `_isSynchronizingOutput` is true, it'll kick the
// render thread into calling `_synchronizeWithOutput()`...
_isSynchronizingOutput = so;
// 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();
if (!_isSynchronizingOutput)
{
// ...otherwise, unblock `_synchronizeWithOutput()` from the `WaitOnAddress` call.
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
@ -249,6 +254,7 @@ void Renderer::_synchronizeWithOutput() noexcept
// If a timeout occurred, `_isSynchronizingOutput` may still be true.
// Set it to false now to skip calling `_synchronizeWithOutput()` on the next frame.
_isSynchronizingOutput = false;
_renderSettings.SetRenderMode(RenderSettings::Mode::SynchronizedOutput, false);
}
// Routine Description:

View File

@ -28,15 +28,14 @@ namespace Microsoft::Console::Render
class Renderer
{
public:
Renderer(const RenderSettings& renderSettings, IRenderData* pData);
Renderer(RenderSettings& renderSettings, IRenderData* pData);
IRenderData* GetRenderData() const noexcept;
[[nodiscard]] HRESULT PaintFrame();
void NotifyPaintFrame() noexcept;
void SynchronizedOutputBegin() noexcept;
void SynchronizedOutputEnd() noexcept;
void SynchronizedOutputChanged() noexcept;
void TriggerSystemRedraw(const til::rect* const prcDirtyClient);
void TriggerRedraw(const Microsoft::Console::Types::Viewport& region);
void TriggerRedraw(const til::point* const pcoord);
@ -113,7 +112,7 @@ namespace Microsoft::Console::Render
void _prepareNewComposition();
[[nodiscard]] HRESULT _PrepareRenderInfo(_In_ IRenderEngine* const pEngine);
const RenderSettings& _renderSettings;
RenderSettings& _renderSettings;
std::array<IRenderEngine*, 2> _engines{};
IRenderData* _pData = nullptr; // Non-ownership pointer
static constexpr size_t _firstSoftFontChar = 0xEF20;

View File

@ -25,7 +25,8 @@ namespace Microsoft::Console::Render
AlwaysDistinguishableColors,
IntenseIsBold,
IntenseIsBright,
ScreenReversed
ScreenReversed,
SynchronizedOutput,
};
RenderSettings() noexcept;

View File

@ -1915,16 +1915,10 @@ void AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
_api.SetSystemMode(ITerminalApi::Mode::BracketedPaste, enable);
break;
case DispatchTypes::ModeParams::SO_SynchronizedOutput:
_renderSettings.SetRenderMode(RenderSettings::Mode::SynchronizedOutput, enable);
if (_renderer)
{
if (enable)
{
_renderer->SynchronizedOutputBegin();
}
else
{
_renderer->SynchronizedOutputEnd();
}
_renderer->SynchronizedOutputChanged();
}
break;
case DispatchTypes::ModeParams::GCM_GraphemeClusterMode:
@ -2065,6 +2059,9 @@ void AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param)
case DispatchTypes::ModeParams::XTERM_BracketedPasteMode:
state = mapTemp(_api.GetSystemMode(ITerminalApi::Mode::BracketedPaste));
break;
case DispatchTypes::ModeParams::SO_SynchronizedOutput:
state = mapTemp(_renderSettings.GetRenderMode(RenderSettings::Mode::SynchronizedOutput));
break;
case DispatchTypes::ModeParams::GCM_GraphemeClusterMode:
state = mapPerm(CodepointWidthDetector::Singleton().GetMode() == TextMeasurementMode::Graphemes);
break;
@ -3050,6 +3047,7 @@ void AdaptDispatch::HardReset()
if (_renderer)
{
_renderer->TriggerRedrawAll(true, true);
_renderer->SynchronizedOutputChanged();
}
// Cursor to 1,1 - the Soft Reset guarantees this is absolute