Repository navigation
perf(video-decode): pipeline the Media Foundation GPU readback - #2583
Conversation
The decoder thread copied each sample to a staging texture and mapped it straight away, so it sat blocked in Map while the GPU decoded and copied, and the GPU sat idle while the CPU copied the frame out. read_nv12 now queues the copy of the next sample before mapping the current one, through a ring of two staging textures created with the decoder. Samples are copied from Media Foundation's output surface directly into staging, skipping the pool texture that only the disabled zero-copy path needs. The rendering decoder caches the CPU NV12 data itself and drops the zero-copy fields its frames could never fill, along with the two zero-copy constructors that were their only producers.
|
@cesarfrancots is attempting to deploy a commit to the Cap Software Inc Team on Vercel. A member of the Team first needs to authorize it. |
… job An export that ffprobe cannot read, that has the wrong frame count, or that falls outside the requested size is now an invalid run instead of a timing sample. Benchmark processes start suspended and resume only once they are in the job object, so nothing they spawn escapes its CPU accounting, and a failed assignment kills the process. A note says why playback forces FFmpeg.
… is copied read_decoder_surface released the IMFSample when it returned, before the caller queued the copy out of its texture slice. Media Foundation can decode the next frame into that slice as soon as the sample is released, so a frame sometimes came back with another frame's pixels. The surface now carries the sample, which is released only after the copy is queued; the shared immediate context then orders the copy before any later decode into the slice. pipelined_readback_returns_every_sample_in_order failed 3 of 15 runs before and 0 of 30 after.
1 similar comment
…pipelined-readback
|
@greptileai review |
…pipelined-readback
|
@greptileai review |
|
@richiemcilroy Greptile is at 5/5 on the current head (4f2a22c), and all review threads are resolved. Since the last round I merged the latest main (one conflict in |
…pipelined-readback
|
@greptileai review |
…pipelined-readback # Conflicts: # scripts/test-capbench.py
|
@greptileai review |
…pipelined-readback
|
@greptileai review |
|
@greptileai review |
|
@greptileai review |
perf(video-decode): pipeline the Media Foundation GPU readbackfix(scripts): reject broken exports and start benchmarks inside their job, which fixes review feedback onscripts/capbench.py(that script comes from perf(windows): fence-waited readbacks, overlapped MF export decode, cheaper screen capture #2541)fix(video-decode): hold the Media Foundation sample until its surface is copiedSummary
Every Media Foundation frame is read back to the CPU, because the zero-copy path has been off since b971194. The decoder thread used to copy each sample to a staging texture and
Mapit straight away. As a result:Mapwhile the GPU decoded and copied the sample.Profiling the 3024×1964 screen track of the reference recording gave these per-frame costs:
ReadSampleMap(waiting for the GPU)In the 1080p60 export, waiting for the next frame's decode (4.4 s) took longer than rendering (3.2 s). Decoding was the critical path.
MediaFoundationDecoder::read_nv12pipelines the readback:read_nv12returns exactly what the one-at-a-time path did. It drains the queued sample before reporting end of stream or a read error, and a seek drops whatever is queued.IMFSamplestays alive until the copy out of its texture slice is queued. Releasing a sample lets Media Foundation decode into that slice again, so releasing it earlier sometimes returned another frame's pixels. The first version of this branch had that race; the new test caught it intermittently (3 of 15 runs), and it passes 30 of 30 with the fix.The rendering decoder now caches
NV12Datadirectly. Its cached frames had zero-copy fields that were never filled; those are gone, along with the texture-read-failure tracking and twoDecodedFramezero-copy constructors that this branch was the last caller of. A failedMapis now reported as a read error. Previously it cached a black frame.Verification
a88dca3b…).pipelined_readback_returns_every_sample_in_orderis#[ignore]like the existing device test, so CI doesn't run it. Run it withcargo test -p cap-video-decode --lib -- --include-ignored.(pts, pixels)pair fromread_nv12against a one-at-a-time readback.cargo test -p cap-rendering --lib media_foundation: 13 pass.cargo clippy -p cap-rendering -p cap-video-decode --all-targets -- -D warningsis clean.scripts/capbench.pyagainst the refactor(rendering): drive the Media Foundation decoder from one request state machine #2545 build (n=8). Each row is the median, with a bootstrap 95% CI.The playback benchmarks run the FFmpeg decoder, because that is what the editor uses on Windows, so they do not exercise this change and are left out of the table. 4K export is limited by the encoder (
encoder_senddominates the loop stats), so this change doesn't move it. Random access needs one extra sample decoded after each seek before the first frame comes back. It leans about 3 ms slower, but the CI still crosses zero.decode-displayrandom access also varies by about 20% between sessions on the same build. The gain is modest; the clearest win is the 1080p export.Notes for reviewers
scripts/capbench.py(from perf(windows): fence-waited readbacks, overlapped MF export decode, cheaper screen capture #2541) now:read_sample,MFDecodedFrame,FramePoolandNv12PlaneConverterare untouched and still public. Nothing in the repo reads frames through them any more, so they are the scaffolding for a future zero-copy path. Don't mixread_samplewithread_nv12on one decoder:read_nv12keeps one sample queued. If zero-copy isn't coming back, they and the plane shader can be deleted in a follow-up.On the reference recording and the
h264-decoder-lifecycle.mp4fixture, Media Foundation ends each stream withMF_SOURCE_READERF_ERRORrather than end-of-stream. That was true before this PR too: theStream errorwarnings at segment ends are not new.🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; no new actionable defects were found.Summary
This PR gives Media Foundation two staging textures so the next frame’s GPU copy can overlap the current frame’s CPU copy.
Reviews (19) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." · Reviewed by Greptile