Merge pull request #456 from webadderallorg/fix/issue424-wgc-resize-stability

fix(recording): stabilize WGC resize frames
This commit is contained in:
Phạm Thị Minh Hồng
2026-05-09 07:52:46 +07:00
committed by GitHub
7 changed files with 142 additions and 6 deletions
@@ -5,10 +5,10 @@
"helpers": {
"wgc-capture": {
"binaryName": "wgc-capture.exe",
"binarySha256": "88226bd499c820081a27264b40f37875d0949d9f921920738e05b7eaeb1a9e7d",
"binarySha256": "4f8873abbff58add37672114184c154dee838939ee8f2b7df45d658d078af28c",
"sourceDir": "electron/native/wgc-capture",
"sourceFingerprint": "7190e148ede1fb1c573cc1866405d6345b0c35378a7e32499580bffacf1cc6d2",
"updatedAt": "2026-05-07T15:21:44.354Z"
"sourceFingerprint": "6f725fad6eb515d81b2d553ec45ddbf7c681d2725bba48f0c0722ce00166d967",
"updatedAt": "2026-05-09T00:49:28.306Z"
},
"cursor-monitor": {
"binaryName": "cursor-monitor.exe",
Binary file not shown.
+20 -1
View File
@@ -397,7 +397,7 @@ int main(int argc, char* argv[]) {
}
// Wait for stop signal while pausing/resuming audio tracks in lockstep.
while (!g_stopRequested) {
while (!g_stopRequested && !session.hasFatalError()) {
if (g_pauseRequested) {
if (audioActive) loopback.pause();
if (micActive) micCapture.pause();
@@ -416,6 +416,25 @@ int main(int argc, char* argv[]) {
if (audioActive) loopback.stop();
if (micActive) micCapture.stop();
if (session.hasFatalError()) {
std::cerr << "ERROR: WGC capture session failed during recording" << std::endl;
encoder.finalize();
DeleteFileW(outputPathW.c_str());
if (!config.audioOutputPath.empty()) {
const std::wstring audioPathW = utf8ToWide(config.audioOutputPath);
const std::wstring audioMetadataPathW = utf8ToWide(config.audioOutputPath + ".json");
DeleteFileW(audioPathW.c_str());
DeleteFileW(audioMetadataPathW.c_str());
}
if (!config.micOutputPath.empty()) {
const std::wstring micPathW = utf8ToWide(config.micOutputPath);
const std::wstring micMetadataPathW = utf8ToWide(config.micOutputPath + ".json");
DeleteFileW(micPathW.c_str());
DeleteFileW(micMetadataPathW.c_str());
}
return 1;
}
if (audioActive) {
writeCompanionAudioTimingMetadata(
config.audioOutputPath,
+61 -1
View File
@@ -2,6 +2,7 @@
#include <mfapi.h>
#include <mferror.h>
#include <codecapi.h>
#include <algorithm>
#include <iostream>
#include <cstring>
@@ -116,6 +117,31 @@ bool MFEncoder::initialize(const std::wstring& outputPath, int width, int height
return false;
}
// WGC window captures can change frame size while recording. Keep the muxer
// output dimensions stable by compositing resized frames into this fixed
// BGRA surface before CPU readback.
D3D11_TEXTURE2D_DESC compositeDesc = {};
compositeDesc.Width = width_;
compositeDesc.Height = height_;
compositeDesc.MipLevels = 1;
compositeDesc.ArraySize = 1;
compositeDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
compositeDesc.SampleDesc.Count = 1;
compositeDesc.Usage = D3D11_USAGE_DEFAULT;
compositeDesc.BindFlags = D3D11_BIND_RENDER_TARGET;
hr = device_->CreateTexture2D(&compositeDesc, nullptr, &resizeCompositeTexture_);
if (FAILED(hr)) {
std::cerr << "ERROR: Failed to create resize composite texture: 0x" << std::hex << hr << std::endl;
return false;
}
hr = device_->CreateRenderTargetView(resizeCompositeTexture_.Get(), nullptr, &resizeCompositeView_);
if (FAILED(hr)) {
std::cerr << "ERROR: Failed to create resize composite view: 0x" << std::hex << hr << std::endl;
return false;
}
// Pre-allocate NV12 buffer
const int ySize = width_ * height_;
const int uvSize = (width_ / 2) * (height_ / 2) * 2;
@@ -132,7 +158,39 @@ bool MFEncoder::writeFrame(ID3D11Texture2D* texture, int64_t timestampHns) {
if (!initialized_ || !sinkWriter_) return false;
context_->CopyResource(stagingTexture_.Get(), texture);
D3D11_TEXTURE2D_DESC sourceDesc = {};
texture->GetDesc(&sourceDesc);
if (sourceDesc.Width == static_cast<UINT>(width_) &&
sourceDesc.Height == static_cast<UINT>(height_)) {
context_->CopyResource(stagingTexture_.Get(), texture);
} else {
if (!resizeCompositeTexture_ || !resizeCompositeView_) return false;
const FLOAT clearColor[4] = {0.0f, 0.0f, 0.0f, 1.0f};
context_->ClearRenderTargetView(resizeCompositeView_.Get(), clearColor);
D3D11_BOX sourceBox = {};
sourceBox.left = 0;
sourceBox.top = 0;
sourceBox.front = 0;
sourceBox.right = (std::min)(sourceDesc.Width, static_cast<UINT>(width_));
sourceBox.bottom = (std::min)(sourceDesc.Height, static_cast<UINT>(height_));
sourceBox.back = 1;
if (sourceBox.right == 0 || sourceBox.bottom == 0) return false;
context_->CopySubresourceRegion(
resizeCompositeTexture_.Get(),
0,
0,
0,
0,
texture,
0,
&sourceBox);
context_->CopyResource(stagingTexture_.Get(), resizeCompositeTexture_.Get());
}
D3D11_MAPPED_SUBRESOURCE mapped;
HRESULT hr = context_->Map(stagingTexture_.Get(), 0, D3D11_MAP_READ, 0, &mapped);
@@ -242,6 +300,8 @@ bool MFEncoder::finalize() {
initialized_ = false;
sinkWriter_.Reset();
stagingTexture_.Reset();
resizeCompositeView_.Reset();
resizeCompositeTexture_.Reset();
nv12Buffer_.clear();
lastFrameBuffer_.clear();
nv12Buffer_.shrink_to_fit();
@@ -30,6 +30,8 @@ private:
ID3D11Device* device_ = nullptr;
ID3D11DeviceContext* context_ = nullptr;
ComPtr<ID3D11Texture2D> stagingTexture_;
ComPtr<ID3D11Texture2D> resizeCompositeTexture_;
ComPtr<ID3D11RenderTargetView> resizeCompositeView_;
std::vector<uint8_t> nv12Buffer_;
std::vector<uint8_t> lastFrameBuffer_;
DWORD streamIndex_ = 0;
@@ -24,6 +24,12 @@ extern "C" {
IInspectable** graphicsDevice);
}
static int normalizeFramePoolExtent(int value) {
int normalized = value < 2 ? 2 : value;
if ((normalized % 2) != 0) ++normalized;
return normalized;
}
WgcSession::WgcSession() {}
WgcSession::~WgcSession() {
@@ -114,6 +120,8 @@ bool WgcSession::initializeWithItem(int fps) {
auto size = captureItem_.Size();
captureWidth_ = size.Width;
captureHeight_ = size.Height;
framePoolWidth_ = size.Width;
framePoolHeight_ = size.Height;
framePool_ = winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::CreateFreeThreaded(
winrtDevice_,
@@ -128,7 +136,42 @@ bool WgcSession::initializeWithItem(int fps) {
// IsBorderRequired is only available on Windows 11+ (build 22000). propagating an hresult_error results in Native Windows capture failure
try {
session_.IsBorderRequired(false);
} catch (winrt::hresult_error const&) {
}
return true;
}
bool WgcSession::recreateFramePoolIfNeeded(
winrt::Windows::Graphics::SizeInt32 const& contentSize) {
if (!framePool_) return false;
const int normalizedWidth = normalizeFramePoolExtent(contentSize.Width);
const int normalizedHeight = normalizeFramePoolExtent(contentSize.Height);
if (normalizedWidth == framePoolWidth_ && normalizedHeight == framePoolHeight_) {
return false;
}
winrt::Windows::Graphics::SizeInt32 normalizedSize{
normalizedWidth,
normalizedHeight,
};
try {
framePool_.Recreate(
winrtDevice_,
winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
2,
normalizedSize);
framePoolWidth_ = normalizedWidth;
framePoolHeight_ = normalizedHeight;
std::cerr << "INFO: Recreated WGC frame pool for resized content "
<< framePoolWidth_ << "x" << framePoolHeight_ << std::endl;
} catch (winrt::hresult_error const& e) {
fatalError_ = true;
capturing_ = false;
std::cerr << "ERROR: Failed to recreate WGC frame pool after resize: 0x"
<< std::hex << e.code() << std::dec << std::endl;
}
return true;
@@ -174,6 +217,7 @@ bool WgcSession::startCapture() {
if (!session_ || !framePool_) return false;
capturing_ = true;
fatalError_ = false;
lastFrameTimeHns_ = 0;
frameArrivedRevoker_ = framePool_.FrameArrived(
@@ -205,10 +249,15 @@ void WgcSession::onFrameArrived(
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const& sender,
winrt::Windows::Foundation::IInspectable const&) {
if (!capturing_) return;
if (!capturing_ || fatalError_) return;
auto frame = sender.TryGetNextFrame();
if (!frame) return;
auto contentSize = frame.ContentSize();
if (recreateFramePoolIfNeeded(contentSize)) {
frame.Close();
return;
}
auto timestamp = frame.SystemRelativeTime();
int64_t frameTimeHns = std::chrono::duration_cast<std::chrono::duration<int64_t, std::ratio<1, 10000000>>>(timestamp).count();
@@ -28,6 +28,7 @@ public:
void setFrameCallback(FrameCallback callback);
bool startCapture();
void stopCapture();
bool hasFatalError() const { return fatalError_.load(); }
int captureWidth() const { return captureWidth_; }
int captureHeight() const { return captureHeight_; }
@@ -45,9 +46,12 @@ private:
FrameCallback frameCallback_;
std::atomic<bool> capturing_{false};
std::atomic<bool> fatalError_{false};
int fps_ = 60;
int captureWidth_ = 0;
int captureHeight_ = 0;
int framePoolWidth_ = 0;
int framePoolHeight_ = 0;
int64_t frameIntervalHns_ = 0;
int64_t lastFrameTimeHns_ = 0;
@@ -56,6 +60,8 @@ private:
winrt::Windows::Graphics::Capture::GraphicsCaptureItem createCaptureItemForMonitor(HMONITOR monitor);
winrt::Windows::Graphics::Capture::GraphicsCaptureItem createCaptureItemForWindow(HWND hwnd);
bool initializeWithItem(int fps);
bool recreateFramePoolIfNeeded(
winrt::Windows::Graphics::SizeInt32 const& contentSize);
void onFrameArrived(
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const& sender,
winrt::Windows::Foundation::IInspectable const& args);