The FPS readout only measured the host's own render rate, hiding capture stutter. The Video panel now shows a "Pipeline rates" section: - Tool render (host FPS / frametime, as before); - hooked source: Game present (/s, from VideoShare.present_calls deltas), Hook publish (/s, generation deltas), and capture->display latency avg/min/max ms; - WGC source: WGC capture (/s) from a new WindowCapture frame-arrival counter (game present + latency are n/a, since WGC frames aren't game-timestamped). Latency uses a system-wide clock: protocol v11->v12 adds VideoShare.present_qpc, stamped by the hook at publish (publish_video_frame); the host measures now_qpc - present_qpc per newly published frame, windowed to min/avg/max each second. Verified: present_hook_test (x64 + x86) now asserts present_qpc is stamped; full build x64 + x86 clean; ctest x64 9/9, x86 3/3. The live rate/latency numbers need a real game mirroring to read meaningfully; wiring validated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
83 lines
2.4 KiB
C++
83 lines
2.4 KiB
C++
// 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_;
|
|
}
|
|
|
|
// Cumulative frames WGC has delivered (for the capture-rate metric).
|
|
[[nodiscard]] std::uint64_t frames_arrived() const
|
|
{
|
|
return frames_arrived_;
|
|
}
|
|
|
|
// 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::uint64_t frames_arrived_ = 0; // incremented in on_frame_arrived
|
|
|
|
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
|