// 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 #include #include #include #include namespace coop { class D3D11Window { public: using RenderCallback = std::function; 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 device_; Microsoft::WRL::ComPtr context_; Microsoft::WRL::ComPtr swap_chain_; Microsoft::WRL::ComPtr rtv_; }; } // namespace coop