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>
104 lines
3.6 KiB
C++
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
|