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:
63
host/src/capture_panel.cpp
Normal file
63
host/src/capture_panel.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "capture_panel.hpp"
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
|
||||
bool CapturePanel::init(ID3D11Device* device)
|
||||
{
|
||||
device_ = device;
|
||||
return renderer_.init(device);
|
||||
}
|
||||
|
||||
void CapturePanel::draw_ui()
|
||||
{
|
||||
ImGui::SetNextWindowPos(ImVec2(460, 24), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(360, 0), ImGuiCond_FirstUseEver);
|
||||
ImGui::Begin("Video mirror");
|
||||
|
||||
const bool have_target = target_ != nullptr && IsWindow(target_);
|
||||
ImGui::BeginDisabled(!have_target);
|
||||
if (ImGui::Checkbox("Mirror game window", &enabled_))
|
||||
{
|
||||
if (!enabled_)
|
||||
{
|
||||
capture_.stop();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
if (!have_target)
|
||||
{
|
||||
ImGui::TextDisabled("Inject into a game first (its window is the source).");
|
||||
}
|
||||
|
||||
// Start/restart capture when enabled and the target window changes.
|
||||
if (enabled_ && have_target && capture_.target() != target_)
|
||||
{
|
||||
if (!capture_.start(target_, device_))
|
||||
{
|
||||
enabled_ = false;
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.4f, 1.0f), "Failed to start capture.");
|
||||
}
|
||||
}
|
||||
|
||||
if (capture_.running())
|
||||
{
|
||||
ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "Capturing %ux%u", capture_.frame_width(),
|
||||
capture_.frame_height());
|
||||
ImGui::Text("Render: %.1f FPS", ImGui::GetIO().Framerate);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void CapturePanel::render(ID3D11DeviceContext* ctx, std::uint32_t dst_w, std::uint32_t dst_h)
|
||||
{
|
||||
if (capture_.running())
|
||||
{
|
||||
capture_.draw_latest(renderer_, ctx, dst_w, dst_h);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace coop
|
||||
Reference in New Issue
Block a user