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

@@ -0,0 +1,75 @@
// Mirrors a target window into a D3D11 texture using Windows.Graphics.Capture
// (no injection). Frames arrive on a WGC thread; the newest is handed to the
// render thread, which copies and draws it -- so all D3D11 context use stays on
// one thread.
#pragma once
#include <cstdint>
#include <mutex>
#include <d3d11.h>
#include <windows.h>
#include <wrl/client.h>
#include <winrt/Windows.Graphics.Capture.h>
#include <winrt/Windows.Graphics.DirectX.Direct3D11.h>
namespace coop
{
class FrameRenderer;
class WindowCapture
{
public:
~WindowCapture();
// Begins capturing `target`. Returns false if WGC is unavailable or the
// window can't be captured.
bool start(HWND target, ID3D11Device* device);
void stop();
[[nodiscard]] bool running() const
{
return session_ != nullptr;
}
[[nodiscard]] HWND target() const
{
return target_;
}
[[nodiscard]] std::uint32_t frame_width() const
{
return width_;
}
[[nodiscard]] std::uint32_t frame_height() const
{
return height_;
}
// Render thread: consume the newest frame (if any) and draw it letterboxed
// into a dst_w x dst_h target via `renderer`.
void draw_latest(FrameRenderer& renderer, ID3D11DeviceContext* ctx, std::uint32_t dst_w, std::uint32_t dst_h);
private:
void on_frame_arrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const& pool,
winrt::Windows::Foundation::IInspectable const&);
HWND target_ = nullptr;
Microsoft::WRL::ComPtr<ID3D11Device> device_;
Microsoft::WRL::ComPtr<ID3D11Texture2D> latest_;
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> latest_srv_;
std::uint32_t width_ = 0;
std::uint32_t height_ = 0;
std::mutex mutex_; // guards pending_ handoff only (no context work under it)
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame pending_{nullptr};
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice winrt_device_{nullptr};
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item_{nullptr};
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool frame_pool_{nullptr};
winrt::Windows::Graphics::Capture::GraphicsCaptureSession session_{nullptr};
winrt::event_token frame_token_{};
winrt::Windows::Graphics::SizeInt32 pool_size_{0, 0};
};
} // namespace coop