Files
CoopAllTheThings/host/src/d3d11_window.hpp
BlackMark 8182389091 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>
2026-06-24 01:08:47 +02:00

104 lines
3.6 KiB
C++

// Borderless full-screen D3D11 window. This is the surface Steam Remote Play
// Together captures, so it must be a normal (non-exclusive) top-level window
// that owns a flip-model swap chain.
#pragma once
#include <d3d11.h>
#include <dxgi1_2.h>
#include <wrl/client.h>
#include <functional>
#include <string>
namespace coop
{
class D3D11Window
{
public:
using RenderCallback = std::function<void()>;
D3D11Window() = default;
~D3D11Window();
D3D11Window(const D3D11Window&) = delete;
D3D11Window& operator=(const D3D11Window&) = delete;
// Creates the borderless window sized to the primary monitor and brings up
// the D3D11 device + swap chain. Returns false on any failure.
bool create(const wchar_t* title);
// Pumps the message queue once. Returns false when the window is closing.
bool pump_messages();
// Clears the back buffer, invokes render (where ImGui draws), and presents.
// sync_interval is the DXGI Present sync interval: 1 = vsync (default, matches the
// monitor), 0 = present immediately (used by the frame-sync path, which paces the
// flip to the game's published frames instead of the monitor).
void render_frame(const RenderCallback& render, UINT sync_interval = 1);
// Request a PNG screenshot of the next rendered frame, saved to `path`. The capture
// happens inside render_frame just before Present, so it includes the ImGui overlay,
// and reads the back buffer off the GPU -- independent of window focus, z-order, or
// occlusion (works even when the window is fully covered). One-shot.
void request_screenshot(std::wstring path);
// If render_frame saved a screenshot since the last call, returns its path and clears
// 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_;
}
[[nodiscard]] ID3D11Device* device() const
{
return device_.Get();
}
[[nodiscard]] ID3D11DeviceContext* context() const
{
return context_.Get();
}
private:
static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
bool create_device();
void create_render_target();
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
std::wstring saved_screenshot_; // last successfully written shot, for a UI confirmation
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_;
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain_;
Microsoft::WRL::ComPtr<ID3D11RenderTargetView> rtv_;
};
} // namespace coop