Phase 2: Present-hook video path (shared-texture mirror)
Add an injected IDXGISwapChain::Present / Present1 hook as a lower-latency, border-free alternative to WGC. The hook copies the swapchain backbuffer into a shared keyed-mutex texture (coop_video_<pid>); the host opens it by name and samples it. New opt-in HookSubsys_Video (protocol v6 -> v7); the Video mirror panel gains a WGC vs Hooked source toggle that installs/removes the subsystem. Verified by present_hook_test (drives a real D3D11 swapchain end-to-end and reads the rendered pixels back through the shared texture) and against Phantom Brave (D3D9: hook installs cleanly and stays idle, WGC fallback). All 5 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
128
host/src/capture/shared_texture.cpp
Normal file
128
host/src/capture/shared_texture.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "capture/shared_texture.hpp"
|
||||
|
||||
#include "coop/protocol.hpp"
|
||||
#include "coop/shared_memory.hpp"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
|
||||
bool SharedTextureSource::init(ID3D11Device* device)
|
||||
{
|
||||
if (device == nullptr || FAILED(device->QueryInterface(IID_PPV_ARGS(&device_))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
device_->GetImmediateContext(&ctx_);
|
||||
return ctx_ != nullptr;
|
||||
}
|
||||
|
||||
void SharedTextureSource::reset()
|
||||
{
|
||||
srv_.Reset();
|
||||
private_.Reset();
|
||||
mutex_.Reset();
|
||||
shared_.Reset();
|
||||
pid_ = 0;
|
||||
width_ = height_ = format_ = 0;
|
||||
last_generation_ = 0;
|
||||
frames_copied_ = 0;
|
||||
}
|
||||
|
||||
bool SharedTextureSource::reopen(unsigned long pid, const VideoShareView& share)
|
||||
{
|
||||
srv_.Reset();
|
||||
private_.Reset();
|
||||
mutex_.Reset();
|
||||
shared_.Reset();
|
||||
width_ = height_ = format_ = 0;
|
||||
pid_ = pid;
|
||||
|
||||
if (pid == 0 || share.width == 0 || share.height == 0)
|
||||
{
|
||||
return false; // the hook hasn't shared a backbuffer yet
|
||||
}
|
||||
|
||||
const std::wstring name = video_share_name(pid);
|
||||
if (FAILED(device_->OpenSharedResourceByName(name.c_str(),
|
||||
DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,
|
||||
IID_PPV_ARGS(&shared_))) ||
|
||||
shared_ == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (FAILED(shared_.As(&mutex_)) || mutex_ == nullptr)
|
||||
{
|
||||
shared_.Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Private copy we sample from, so we only hold the keyed mutex during the copy.
|
||||
D3D11_TEXTURE2D_DESC desc{};
|
||||
desc.Width = share.width;
|
||||
desc.Height = share.height;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = static_cast<DXGI_FORMAT>(share.format);
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
if (FAILED(device_->CreateTexture2D(&desc, nullptr, &private_)) || private_ == nullptr)
|
||||
{
|
||||
mutex_.Reset();
|
||||
shared_.Reset();
|
||||
return false;
|
||||
}
|
||||
if (FAILED(device_->CreateShaderResourceView(private_.Get(), nullptr, &srv_)))
|
||||
{
|
||||
srv_.Reset();
|
||||
private_.Reset();
|
||||
mutex_.Reset();
|
||||
shared_.Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
width_ = share.width;
|
||||
height_ = share.height;
|
||||
format_ = share.format;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SharedTextureSource::update(const VideoShareView& share, unsigned long pid)
|
||||
{
|
||||
if (device_ == nullptr || pid == 0)
|
||||
{
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// (Re)open whenever the target or the published backbuffer geometry changes.
|
||||
if (pid != pid_ || share.width != width_ || share.height != height_ || share.format != format_)
|
||||
{
|
||||
if (!reopen(pid, share))
|
||||
{
|
||||
return srv_ != nullptr; // couldn't open yet; keep any prior frame
|
||||
}
|
||||
last_generation_ = 0; // force a copy of the current frame
|
||||
}
|
||||
|
||||
if (shared_ == nullptr || mutex_ == nullptr)
|
||||
{
|
||||
return srv_ != nullptr;
|
||||
}
|
||||
if (share.generation == last_generation_)
|
||||
{
|
||||
return srv_ != nullptr; // no new frame; keep showing the last copy
|
||||
}
|
||||
|
||||
// Bounded wait so a stalled producer can't hang the host's render thread.
|
||||
if (mutex_->AcquireSync(kVideoMutexKey, 8) == S_OK)
|
||||
{
|
||||
ctx_->CopyResource(private_.Get(), shared_.Get());
|
||||
mutex_->ReleaseSync(kVideoMutexKey);
|
||||
last_generation_ = share.generation;
|
||||
++frames_copied_;
|
||||
}
|
||||
return srv_ != nullptr;
|
||||
}
|
||||
|
||||
} // namespace coop
|
||||
Reference in New Issue
Block a user