@@ -25,7 +25,7 @@ static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 };
2525// - pData - The interface to console data structures required for rendering
2626// Return Value:
2727// - An instance of a Renderer.
28- Renderer::Renderer (const RenderSettings& renderSettings, IRenderData* pData) :
28+ Renderer::Renderer (RenderSettings& renderSettings, IRenderData* pData) :
2929 _renderSettings(renderSettings),
3030 _pData(pData)
3131{
@@ -187,31 +187,36 @@ void Renderer::NotifyPaintFrame() noexcept
187187}
188188
189189// NOTE: You must be holding the console lock when calling this function.
190- void Renderer::SynchronizedOutputBegin () noexcept
190+ void Renderer::SynchronizedOutputChanged () noexcept
191191{
192- // Kick the render thread into calling `_synchronizeWithOutput()`.
193- _isSynchronizingOutput = true ;
194- }
192+ const auto so = _renderSettings.GetRenderMode (RenderSettings::Mode::SynchronizedOutput);
193+ if (_isSynchronizingOutput == so)
194+ {
195+ return ;
196+ }
195197
196- // NOTE: You must be holding the console lock when calling this function.
197- void Renderer::SynchronizedOutputEnd () noexcept
198- {
199- // Unblock `_synchronizeWithOutput()` from the `WaitOnAddress` call.
200- _isSynchronizingOutput = false ;
201- WakeByAddressSingle (&_isSynchronizingOutput);
202-
203- // It's crucial to give the render thread at least a chance to gain the lock.
204- // Otherwise, a VT application could continuously spam DECSET 2026 (Synchronized Output) and
205- // essentially drop our renderer to 10 FPS, because `_isSynchronizingOutput` is always true.
206- //
207- // Obviously calling LockConsole/UnlockConsole here is an awful, ugly hack,
208- // since there's no guarantee that this is the same lock as the one the VT parser uses.
209- // But the alternative is Denial-Of-Service of the render thread.
210- //
211- // Note that this causes raw throughput of DECSET 2026 to be comparatively low, but that's fine.
212- // Apps that use DECSET 2026 don't produce that sequence continuously, but rather at a fixed rate.
213- _pData->UnlockConsole ();
214- _pData->LockConsole ();
198+ // If `_isSynchronizingOutput` is true, it'll kick the
199+ // render thread into calling `_synchronizeWithOutput()`...
200+ _isSynchronizingOutput = so;
201+
202+ if (!_isSynchronizingOutput)
203+ {
204+ // ...otherwise, unblock `_synchronizeWithOutput()` from the `WaitOnAddress` call.
205+ WakeByAddressSingle (&_isSynchronizingOutput);
206+
207+ // It's crucial to give the render thread at least a chance to gain the lock.
208+ // Otherwise, a VT application could continuously spam DECSET 2026 (Synchronized Output) and
209+ // essentially drop our renderer to 10 FPS, because `_isSynchronizingOutput` is always true.
210+ //
211+ // Obviously calling LockConsole/UnlockConsole here is an awful, ugly hack,
212+ // since there's no guarantee that this is the same lock as the one the VT parser uses.
213+ // But the alternative is Denial-Of-Service of the render thread.
214+ //
215+ // Note that this causes raw throughput of DECSET 2026 to be comparatively low, but that's fine.
216+ // Apps that use DECSET 2026 don't produce that sequence continuously, but rather at a fixed rate.
217+ _pData->UnlockConsole ();
218+ _pData->LockConsole ();
219+ }
215220}
216221
217222void Renderer::_synchronizeWithOutput () noexcept
@@ -249,6 +254,7 @@ void Renderer::_synchronizeWithOutput() noexcept
249254 // If a timeout occurred, `_isSynchronizingOutput` may still be true.
250255 // Set it to false now to skip calling `_synchronizeWithOutput()` on the next frame.
251256 _isSynchronizingOutput = false ;
257+ _renderSettings.SetRenderMode (RenderSettings::Mode::SynchronizedOutput, false );
252258}
253259
254260// Routine Description:
0 commit comments