When the renderer fails, try to fall back to D2D + WARP

This commit also ups the number of render failures that are permissible
to 5, and moves us to use an exponential backoff rather than a simple
geometric one.

It also suppresses the dialog box in case of present failures. I feel
like the warning dialog should be used for something that the user can
actually do something about...
This commit is contained in:
Dustin L. Howett 2025-12-08 19:04:09 -06:00
parent 224ac9de47
commit 19433ee514
4 changed files with 30 additions and 6 deletions

View File

@ -152,12 +152,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderer->SetBackgroundColorChangedCallback([this]() { _rendererBackgroundColorChanged(); });
_renderer->SetFrameColorChangedCallback([this]() { _rendererTabColorChanged(); });
_renderer->SetRendererEnteredErrorStateCallback([this]() { RendererEnteredErrorState.raise(nullptr, nullptr); });
_renderer->SetRendererEnteredErrorStateCallback([this]() { _rendererEnteredErrorState(); });
}
UpdateSettings(settings, unfocusedAppearance);
}
void ControlCore::_rendererEnteredErrorState()
{
// The first time the renderer fails out (after all of its own retries), switch it to D2D and WARP
// and force it to try again. If it _still_ fails, we can let it halt.
if (_renderFailures++ == 0)
{
const auto lock = _terminal->LockForWriting();
_renderEngine->SetGraphicsAPI(parseGraphicsAPI(GraphicsAPI::Direct2D));
_renderEngine->SetSoftwareRendering(true);
_renderer->EnablePainting();
return;
}
RendererEnteredErrorState.raise(nullptr, nullptr);
}
void ControlCore::_setupDispatcherAndCallbacks()
{
// Get our dispatcher. If we're hosted in-proc with XAML, this will get
@ -917,6 +932,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderEngine->SetSoftwareRendering(_settings.SoftwareRendering());
// Inform the renderer of our opacity
_renderEngine->EnableTransparentBackground(_isBackgroundTransparent());
_renderFailures = 0; // We may have changed the engine; reset the failure counter.
// Trigger a redraw to repaint the window background and tab colors.
_renderer->TriggerRedrawAll(true, true);
@ -1983,6 +1999,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
// The lock must be held, because it calls into IRenderData which is shared state.
const auto lock = _terminal->LockForWriting();
_renderFailures = 0;
_renderer->EnablePainting();
}

View File

@ -344,6 +344,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
safe_void_coroutine _renderEngineSwapChainChanged(const HANDLE handle);
void _rendererBackgroundColorChanged();
void _rendererTabColorChanged();
void _rendererEnteredErrorState();
#pragma endregion
void _raiseReadOnlyWarning();
@ -398,6 +399,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
float _panelWidth{ 0 };
float _panelHeight{ 0 };
float _compositionScale{ 0 };
uint8_t _renderFailures{ 0 };
bool _forceCursorVisible = false;
// Audio stuff.

View File

@ -61,6 +61,9 @@ catch (const wil::ResultException& exception)
return E_PENDING;
}
#ifndef NDEBUG
// We may fail to present repeatedly, e.g. if there's a short-term device failure.
// We should not bombard the consumer with repeated warning callbacks (where they may present a dialog to the user).
if (_p.warningCallback)
{
try
@ -69,6 +72,7 @@ catch (const wil::ResultException& exception)
}
CATCH_LOG()
}
#endif
_b.reset();
return hr;

View File

@ -12,9 +12,10 @@ using namespace Microsoft::Console::Types;
using PointTree = interval_tree::IntervalTree<til::point, size_t>;
static constexpr TimerRepr TimerReprMax = std::numeric_limits<TimerRepr>::max();
static constexpr DWORD maxRetriesForRenderEngine = 3;
// The renderer will wait this number of milliseconds * how many tries have elapsed before trying again.
static constexpr DWORD renderBackoffBaseTimeMilliseconds = 150;
// We want there to be five retry periods; on the last one, we will mark the render as failed.
static constexpr DWORD maxRetriesForRenderEngine = 6;
// The renderer will wait this number of milliseconds * 2^tries before trying again.
static constexpr DWORD renderBackoffBaseTimeMilliseconds = 100;
// Routine Description:
// - Creates a new renderer controller for a console.
@ -355,8 +356,8 @@ DWORD Renderer::_timerToMillis(TimerRepr t) noexcept
}
// Add a bit of backoff.
// Sleep 150ms, 300ms, 450ms before failing out and disabling the renderer.
Sleep(renderBackoffBaseTimeMilliseconds * (maxRetriesForRenderEngine - tries));
// Sleep 100, 200, 400, 600, 800, 1600ms before failing out and disabling the renderer.
Sleep(renderBackoffBaseTimeMilliseconds * (1 << (maxRetriesForRenderEngine - tries - 1)));
}
return S_OK;