Detect and surface host device loss instead of spinning silently
D3D11Window ignored the HRESULTs from Present, ResizeBuffers, and CreateRenderTargetView, so a host-side TDR / driver reset / GPU hang left the render loop presenting to a dead device forever with no error. Now note_device_loss() inspects those HRESULTs; on DXGI_ERROR_DEVICE_REMOVED/RESET it captures GetDeviceRemovedReason() and sets device_lost(). The main loop checks it after render_frame, shows a MessageBox with the reason, and stops cleanly. Per the agreed scope this is detect-surface-halt, not full device re-creation (which would have to re-init ImGui + the capture pipeline) -- that's future work. Not unit-testable (TDR isn't deterministically reproducible); fixed by inspection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -114,9 +114,6 @@ From an in-depth review pass. Each item is fixed test-first (a failing test, the
|
||||
as its own commit; "verify" items are confirmed real before any change, and dropped if not.
|
||||
|
||||
Confirmed bugs:
|
||||
- **Host device loss unhandled** — `Present`/`ResizeBuffers`/`CreateRenderTargetView` HRESULTs are
|
||||
ignored and a removed/reset device spins silently. Detect `DEVICE_REMOVED/RESET`, log
|
||||
`GetDeviceRemovedReason`, surface it, and halt the render loop cleanly.
|
||||
- **Keyed-mutex `WAIT_ABANDONED` not handled** — `shared_texture` treats it as "skip", so a mirror
|
||||
never recovers after a host crash + reconnect. Treat it as acquired (copy + release).
|
||||
|
||||
|
||||
@@ -169,12 +169,28 @@ bool D3D11Window::create_device()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D11Window::note_device_loss(HRESULT hr)
|
||||
{
|
||||
if (hr != DXGI_ERROR_DEVICE_REMOVED && hr != DXGI_ERROR_DEVICE_RESET)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// GetDeviceRemovedReason gives the specific cause (HUNG / driver internal / removed); a plain
|
||||
// RESET may report S_OK there, so fall back to the originating error in that case.
|
||||
const HRESULT reason = device_ ? device_->GetDeviceRemovedReason() : hr;
|
||||
device_lost_reason_ = (reason != S_OK) ? reason : hr;
|
||||
device_lost_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D11Window::create_render_target()
|
||||
{
|
||||
ComPtr<ID3D11Texture2D> back_buffer;
|
||||
if (SUCCEEDED(swap_chain_->GetBuffer(0, IID_PPV_ARGS(back_buffer.GetAddressOf()))))
|
||||
{
|
||||
device_->CreateRenderTargetView(back_buffer.Get(), nullptr, rtv_.ReleaseAndGetAddressOf());
|
||||
const HRESULT hr =
|
||||
device_->CreateRenderTargetView(back_buffer.Get(), nullptr, rtv_.ReleaseAndGetAddressOf());
|
||||
note_device_loss(hr); // a removed device surfaces here too; the render loop checks device_lost()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +206,11 @@ void D3D11Window::handle_resize(UINT width, UINT height)
|
||||
return;
|
||||
}
|
||||
release_render_target();
|
||||
swap_chain_->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0);
|
||||
const HRESULT hr = swap_chain_->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0);
|
||||
if (note_device_loss(hr))
|
||||
{
|
||||
return; // device gone; the render loop will see device_lost() and stop
|
||||
}
|
||||
create_render_target();
|
||||
}
|
||||
|
||||
@@ -238,7 +258,8 @@ void D3D11Window::render_frame(const RenderCallback& render, UINT sync_interval)
|
||||
|
||||
// sync_interval 1 (default) vsyncs to the monitor; 0 presents immediately so the
|
||||
// caller can pace the flip itself (frame-sync to the game's published frames).
|
||||
swap_chain_->Present(sync_interval, 0);
|
||||
const HRESULT hr = swap_chain_->Present(sync_interval, 0);
|
||||
note_device_loss(hr); // a TDR/driver reset on the host surfaces here; the render loop halts on it
|
||||
}
|
||||
|
||||
void D3D11Window::request_screenshot(std::wstring path)
|
||||
|
||||
@@ -47,6 +47,19 @@ public:
|
||||
// the result (so a confirmation shows once); otherwise returns an empty string.
|
||||
[[nodiscard]] std::wstring take_screenshot_result();
|
||||
|
||||
// True once Present/ResizeBuffers reported DXGI_ERROR_DEVICE_REMOVED/RESET (a host-side TDR,
|
||||
// driver reset, or GPU hang). The render loop is expected to stop and surface the error rather
|
||||
// than spin forever on a dead device; full device re-creation is intentionally not attempted.
|
||||
[[nodiscard]] bool device_lost() const
|
||||
{
|
||||
return device_lost_;
|
||||
}
|
||||
// The GetDeviceRemovedReason() HRESULT (or the originating error) when device_lost() is true.
|
||||
[[nodiscard]] HRESULT device_lost_reason() const
|
||||
{
|
||||
return device_lost_reason_;
|
||||
}
|
||||
|
||||
[[nodiscard]] HWND hwnd() const
|
||||
{
|
||||
return hwnd_;
|
||||
@@ -68,6 +81,9 @@ private:
|
||||
void release_render_target();
|
||||
void handle_resize(UINT width, UINT height);
|
||||
bool save_backbuffer_png(const std::wstring& path);
|
||||
// If `hr` is DEVICE_REMOVED/RESET, capture the removed reason and set device_lost_; returns true
|
||||
// when the device is now lost so the caller can bail out of whatever it was doing.
|
||||
bool note_device_loss(HRESULT hr);
|
||||
|
||||
HWND hwnd_ = nullptr;
|
||||
std::wstring pending_screenshot_; // set by request_screenshot, consumed in render_frame
|
||||
@@ -75,6 +91,8 @@ private:
|
||||
bool resize_pending_ = false;
|
||||
UINT resize_width_ = 0;
|
||||
UINT resize_height_ = 0;
|
||||
bool device_lost_ = false;
|
||||
HRESULT device_lost_reason_ = S_OK;
|
||||
|
||||
Microsoft::WRL::ComPtr<ID3D11Device> device_;
|
||||
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context_;
|
||||
|
||||
@@ -504,6 +504,20 @@ int run()
|
||||
},
|
||||
sync_interval);
|
||||
|
||||
// A host-side TDR / driver reset / GPU hang surfaces as a lost device on Present. We don't
|
||||
// attempt to recreate the device (it would have to re-init ImGui + the capture pipeline);
|
||||
// surface it and stop cleanly rather than spin forever rendering nothing.
|
||||
if (window.device_lost())
|
||||
{
|
||||
wchar_t msg[320];
|
||||
swprintf_s(msg,
|
||||
L"The graphics device was lost (0x%08lX) -- a driver reset, GPU hang, or TDR on "
|
||||
L"this PC.\n\nThe mirror can't continue; please restart CoopAllTheThings.",
|
||||
static_cast<unsigned long>(window.device_lost_reason()));
|
||||
MessageBoxW(window.hwnd(), msg, L"CoopAllTheThings -- graphics device lost", MB_ICONERROR | MB_OK);
|
||||
break;
|
||||
}
|
||||
|
||||
// render_frame saves a pending F10 screenshot just before Present; pick up the
|
||||
// result here so next frame shows the confirmation toast (kept out of the shot).
|
||||
if (std::wstring shot = window.take_screenshot_result(); !shot.empty())
|
||||
|
||||
Reference in New Issue
Block a user