Phase 1b: video mirror via Windows Graphics Capture

Mirror the injected game's window into the host's borderless window so Steam
RPT streams a live copy of the game. No injection needed for video.

- capture/window_capture: WGC capture of a target HWND. Wraps our D3D11 device
  as an IDirect3DDevice, creates a free-threaded frame pool + capture session,
  hides the cursor and (best-effort) the capture border. Frames arrive on a WGC
  thread and are handed to the render thread, which copies the newest into a
  shader-resource texture and draws it -- keeping all D3D11 context use on one
  thread. Handles window resize via frame-pool Recreate.
- capture/frame_renderer: fullscreen-triangle shader that blits the captured
  texture letterboxed (aspect-preserved) into the window.
- capture_panel: "Mirror game window" toggle + lifecycle; target HWND comes
  from the hook's reported game window.
- main: winrt apartment init; mirrored frame drawn as background, ImGui on top.
- CMake: link windowsapp + d3dcompiler.

Verified: builds clean; host starts (apartment init + shader compile succeed).
Visual capture quality/latency to be judged on a real game.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 01:03:59 +02:00
parent a0a12d69fe
commit e38df6cf88
10 changed files with 615 additions and 12 deletions

View File

@@ -1,16 +1,17 @@
// CoopAllTheThings host -- Phase 0 spike.
// CoopAllTheThings host.
//
// Brings up the borderless window Steam Remote Play Together will capture and an
// ImGui overlay that lists every controller it can see. The goal of this build
// is to validate the riskiest assumption end-to-end: launch this exe under a
// donor appid (steam -applaunch <donorAppId> <path-to-host.exe>), invite a
// friend, and confirm (a) the window streams and (b) the guest's gamepad shows
// up in the overlay. Capture/injection are built on top of this once it holds.
// Borderless window that Steam Remote Play Together captures, plus the overlay
// that drives the tool: it forwards controller input into an injected game
// (Injection panel), spoofs the game's focus, and mirrors the game's window into
// this window via Windows Graphics Capture (Video mirror panel).
#include <memory>
#include <windows.h>
#include <winrt/Windows.Foundation.h>
#include "capture_panel.hpp"
#include "d3d11_window.hpp"
#include "debug_overlay.hpp"
#include "imgui_layer.hpp"
@@ -38,17 +39,34 @@ int run()
auto input = std::make_unique<coop::XInputSource>();
coop::InjectionPanel injection;
coop::CapturePanel capture;
if (!capture.init(window.device()))
{
MessageBoxW(nullptr, L"Failed to initialize the video mirror.", L"CoopAllTheThings", MB_ICONERROR);
return 1;
}
while (window.pump_messages())
{
input->poll();
injection.publish(input->pads());
capture.set_target(injection.game_hwnd());
imgui.begin_frame();
coop::draw_debug_overlay(*input);
injection.draw();
capture.draw_ui();
window.render_frame([&imgui]() { imgui.end_frame(); });
RECT client = {};
GetClientRect(window.hwnd(), &client);
const auto dst_w = static_cast<std::uint32_t>(client.right - client.left);
const auto dst_h = static_cast<std::uint32_t>(client.bottom - client.top);
// Mirrored frame first (the window background), ImGui overlay on top.
window.render_frame([&]() {
capture.render(window.context(), dst_w, dst_h);
imgui.end_frame();
});
}
return 0;
@@ -58,5 +76,10 @@ int run()
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
{
return run();
// WGC requires an initialized apartment; multi-threaded suits the
// free-threaded frame pool.
winrt::init_apartment(winrt::apartment_type::multi_threaded);
const int result = run();
winrt::uninit_apartment();
return result;
}