Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions electron/native/wgc-capture/src/capture_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ class CaptureSession {
}
}

// DXGI already releases its frame inside tryGetNextFrame().
void releaseFrame() {
if (backend_ == Backend::Wgc) {
wgc_.releaseFrame();
}
}

void setFrameCallback(FrameCallback callback) {
if (backend_ == Backend::Wgc) {
wgc_.setFrameCallback(std::move(callback));
Expand Down
19 changes: 14 additions & 5 deletions electron/native/wgc-capture/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,10 @@ int wmain(int argc, wchar_t* argv[]) {
const auto frameDuration = std::chrono::duration_cast<std::chrono::steady_clock::duration>(
std::chrono::duration<double>(1.0 / config.fps));
uint64_t frameIndex = 0;
// Ticks that pulled a new screen frame. frameIndex counts every tick,
// repeats included, so it cannot show a capture that only delivers
// every other frame (#510).
uint64_t uniqueFrames = 0;
Comment thread
EtienneLescot marked this conversation as resolved.
Resolved
int64_t lastEncodedVideoTimestampHns = -1;
int64_t lastWebcamTimestampHns = -1;
// Both streams are written on a real-time cadence, one FrameSlotClock
Expand Down Expand Up @@ -1191,6 +1195,8 @@ int wmain(int argc, wchar_t* argv[]) {
// separate WGC callback thread left for it to take a
// lock down with it.
session.context()->CopyResource(latestFrameTexture.Get(), wgcTexture);
session.releaseFrame();
uniqueFrames += 1;
firstFrameWritten = true;
} else if (!latestFrameTexture) {
// No frame captured yet at all: nothing to encode
Expand Down Expand Up @@ -1353,11 +1359,14 @@ int wmain(int argc, wchar_t* argv[]) {
Hns(videoClock.nextDueHns() - nowHns)),
frameDuration));
}
std::cerr << "[pacing] frames=" << frameIndex << " elapsed_ms="
<< std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - control.recordingStartedAt)
.count()
<< " last_sample_ms=" << lastEncodedVideoTimestampHns / 10'000 << std::endl;
const auto pacingElapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - control.recordingStartedAt)
.count();
std::cerr << "[pacing] frames=" << frameIndex << " elapsed_ms=" << pacingElapsedMs
<< " last_sample_ms=" << lastEncodedVideoTimestampHns / 10'000
<< " unique_frames=" << uniqueFrames << " unique_fps="
<< (pacingElapsedMs > 0 ? uniqueFrames * 1000.0 / pacingElapsedMs : 0.0)
Comment thread
EtienneLescot marked this conversation as resolved.
Resolved
<< std::endl;
};

std::thread videoWriterThread;
Expand Down
27 changes: 18 additions & 9 deletions electron/native/wgc-capture/src/wgc_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ bool WgcSession::initialize(HMONITOR monitor, int fps, bool captureCursor) {
framePool_ = wgcap::Direct3D11CaptureFramePool::CreateFreeThreaded(
winrtDevice_,
wgdx::DirectXPixelFormat::B8G8R8A8UIntNormalized,
2,
kFramePoolBuffers,
winrt::Windows::Graphics::SizeInt32{width_, height_});
session_ = framePool_.CreateCaptureSession(item_);

Expand All @@ -274,7 +274,7 @@ bool WgcSession::initialize(HWND window, int fps, bool captureCursor, bool inclu
framePool_ = wgcap::Direct3D11CaptureFramePool::CreateFreeThreaded(
winrtDevice_,
wgdx::DirectXPixelFormat::B8G8R8A8UIntNormalized,
2,
kFramePoolBuffers,
winrt::Windows::Graphics::SizeInt32{width_, height_});
session_ = framePool_.CreateCaptureSession(item_);

Expand Down Expand Up @@ -308,10 +308,17 @@ bool WgcSession::tryGetNextFrame(ID3D11Texture2D** outTexture, int64_t* outTimes
// does with *outTexture) is the caller's own doing on the caller's own
// thread -- this class has no thread of its own left to hang on their
// behalf.
// Drain the queue and keep only the newest frame: taking the oldest one
// per tick fell behind on a burst, and every frame left queued is a pool
// buffer WGC cannot write the next one into (#510).
auto frame = framePool_.TryGetNextFrame();
if (!frame) {
return false;
}
while (auto newer = framePool_.TryGetNextFrame()) {
frame.Close();
frame = newer;
}

auto surface = frame.Surface();
auto access = surface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>();
Expand All @@ -321,20 +328,22 @@ bool WgcSession::tryGetNextFrame(ID3D11Texture2D** outTexture, int64_t* outTimes
return false;
}

// Closing the previous frame here (rather than right after this class
// copied out of it) returns it to the pool only once the caller has had a
// full interval to read the one before that -- the pool has 2 buffers, so
// closing eagerly would let WGC recycle a buffer the caller might still
// be mid-CopyResource on across the two-call boundary. currentFrame_
// holds the reference that keeps *outTexture valid until this class's
// next call or stop() closes it.
// currentFrame_ keeps *outTexture valid until the caller has queued its
// copy and calls releaseFrame() (or the next call / stop() replaces it).
currentFrame_ = frame;

*outTexture = texture.Get();
*outTimestampHns = timeSpanToHns(frame.SystemRelativeTime());
return true;
}

void WgcSession::releaseFrame() {
if (currentFrame_) {
currentFrame_.Close();
currentFrame_ = nullptr;
}
}

void WgcSession::setFrameCallback(FrameCallback callback) {
if (!legacyCallbackRegistered_ && framePool_) {
frameArrivedToken_ = framePool_.FrameArrived({this, &WgcSession::onFrameArrived});
Expand Down
14 changes: 10 additions & 4 deletions electron/native/wgc-capture/src/wgc_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ class WgcSession {
// out of it (e.g. via CopyResource) before either. Do not mix with
// setFrameCallback() on the same session.
bool tryGetNextFrame(ID3D11Texture2D** outTexture, int64_t* outTimestampHns);
// Hands the frame from the last tryGetNextFrame() back to the pool. Call it
// as soon as the copy out of it is queued: holding it until the next tick
// left WGC one free buffer, and under GPU load it dropped every other
// frame for lack of a second one (#510).
void releaseFrame();
Comment thread
EtienneLescot marked this conversation as resolved.
Resolved

// Legacy push-based path (OPENSCREEN_WGC_LEGACY_FRAME_CALLBACK=1 only).
// callback runs on a WGC-owned thread inside FrameArrived and may be
Expand Down Expand Up @@ -100,11 +105,12 @@ class WgcSession {
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item_{nullptr};
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool framePool_{nullptr};
winrt::Windows::Graphics::Capture::GraphicsCaptureSession session_{nullptr};
// Three buffers, not two: one the caller is copying from, one WGC is
// writing, one spare for a frame that lands before the copy has run on a
// busy GPU (#510).
static constexpr int32_t kFramePoolBuffers = 3;
// Keeps the most recent frame's WinRT wrapper (and therefore its
// pool-owned texture) alive between tryGetNextFrame() calls: the pool is
// created with 2 buffers, so holding this reference is what keeps the
// texture valid for the caller to read from until the next call reclaims
// it.
// pool-owned texture) alive from tryGetNextFrame() until releaseFrame().
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame currentFrame_{nullptr};
// Legacy push-based path state; unused unless setFrameCallback() is called.
winrt::event_token frameArrivedToken_{};
Expand Down
Loading