Decode & HW Acceleration
vv_media::Decoder decodes one video stream, sequentially (next_frame) or after seek_to_time. Every source, software or hardware, ends up as the same FrameYuv420, so the cache and the compositor never know which path produced a frame.
Choosing the GPU
The Playback setting is resolved once (off the UI thread, in vv-app/src/hw_decode.rs) into an ordered list of devices; every decoder tries them in that order. Only devices that actually open are kept.
| Setting | Devices tried (Linux) |
|---|---|
| Auto | Hybrid laptop (integrated + discrete): Vulkan on the integrated GPU, so the discrete one is not woken. Otherwise NVDEC if an NVIDIA GPU exists, then Vulkan on the first GPU. |
| NVDEC | CUDA |
| Vulkan | Vulkan on the discrete GPU, else the first one |
| Off | none: software |
On macOS the only choice is VideoToolbox (Auto) or Off. A Vulkan device without the VK_KHR_video_decode_queue extension (e.g. Asahi's Honeykrisp today) is rejected up front, so in practice hardware decoding works on x86 GPUs only.
Open and fallback

- One device per backend per process, created on first use and shared by all decoders.
- Surface budget: each HW decoder leases its worst-case surfaces (16-frame DPB + 2 extra + 1) from a global budget, by default ⅛ of physical RAM.
HwPriority::Lowdecoders (window behind the playhead, proxy generation) may use only half of it, so they cannot starve the playback ahead. - Silent fallback detection: FFmpeg falls back to software on its own when a hwaccel refuses a stream. The decoder checks each frame's pixel format; on a mismatch or an error the media is added to
hw::FAILED, reopened in software and re-seeked to where it was. It never goes back to HW in this process. - HW decoders run with one thread; software decoders use FFmpeg frame threading, which makes the decode from keyframe to seek target much faster.
Seeking
seek_to_time guarantees landing on a keyframe at or before the target: the landing frame is decoded immediately, and if it is past the target the seek is retried further back, doubling the step (up to 20 tries). skip_before then lets the caller fast-forward to the wanted frame without converting the frames in between, and non-reference frames there are not even decoded.
Output format
| Source | FrameYuv420 chroma |
|---|---|
| HW frames, NV12/P010 | interleaved (NV12), 10-bit truncated to 8 |
| formats with alpha | planar + alpha plane (YUVA420P) |
| everything else | planar YUV420P |
No conversion runs when the frame is already in the target format. Matrix (BT.601/709/2020) and range come from the source; a missing matrix is guessed from the height (≥ 720 rows → BT.709). RGB-to-YUV conversion stays in the GPU shader. VFR streams are conformed to a contiguous CFR index: holes repeat the last frame (shared, not copied), and frames denser than the rate are dropped so the index stays tied to the pts.
No comments to display
No comments to display