diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 841db27..f1e6902 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -17,12 +17,12 @@ jobs: image: archlinux:latest steps: - name: Install system deps - run: pacman --noconfirm -Syu ffmpeg rustup gcc clang pkgconf + run: pacman --noconfirm -Syu ffmpeg rustup gcc clang pkgconf vulkan-headers - name: Install rust run: rustup install stable - uses: actions/checkout@v4 - name: Build - run: cargo build --verbose + run: cargo build --verbose --locked --all-features - name: Format run: cargo fmt --check #- name: Run tests diff --git a/.gitignore b/.gitignore index 861c03f..e3d5b5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target +/.vscode *.avi *.mp4 diff --git a/.vscode/launch.json b/.vscode/launch.json index ace6190..f4cf94e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,6 +22,42 @@ "args": [], "cwd": "${workspaceFolder}" }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'wl-screenrec' (WAYLAND-2)", + "cargo": { + "args": [ + "build", + "--bin=wl-screenrec", + "--package=wl-screenrec", + "--features=experimental-vulkan" + ], + "filter": { + "name": "wl-screenrec", + "kind": "bin" + }, + "env": { + "FFMPEG_DIR": "/home/russell/ffmpeg/inst" + } + }, + "env": { + "WAYLAND_DISPLAY": "wayland-2", + "RUST_BACKTRACE": "full", + "LD_LIBRARY_PATH": "/home/russell/ffmpeg/inst/lib", + "VK_ICD_FILENAMES": "/home/russell/mesa/build/src/intel/vulkan/intel_devenv_icd.x86_64.json" + }, + "preRunCommands": [ + "process handle -p true -s false INT" + ], + "args": [ + "--experimental-ext-image-copy-capture", + "--experimental-vulkan", + "--codec=avc", + "-vvv", + ], + "cwd": "${workspaceFolder}" + }, { "type": "lldb", "request": "launch", diff --git a/Cargo.lock b/Cargo.lock index d2198c1..88d55b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,6 +66,15 @@ version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + [[package]] name = "bindgen" version = "0.70.1" @@ -289,8 +298,6 @@ dependencies = [ [[package]] name = "ffmpeg-next" version = "7.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da02698288e0275e442a47fc12ca26d50daf0d48b15398ba5906f20ac2e2a9f9" dependencies = [ "bitflags", "ffmpeg-sys-next", @@ -300,9 +307,8 @@ dependencies = [ [[package]] name = "ffmpeg-sys-next" version = "7.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc3234d0a4b2f7d083699d0860c6c9dd83713908771b60f94a96f8704adfe45" dependencies = [ + "ash", "bindgen", "cc", "libc", @@ -890,6 +896,7 @@ name = "wl-screenrec" version = "0.1.6" dependencies = [ "anyhow", + "ash", "clap", "clap_complete", "drm", diff --git a/Cargo.toml b/Cargo.toml index 2c551a0..75dd7c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ wayland-protocols = { version = "0.32", features = [ ] } wayland-protocols-wlr = { version = "0.3", features = ["client"] } ffmpeg-next = "7.0.1" -ffmpeg-sys-next = "7.0.0" # need direct dep on -sys to get metadata to consume in build.rs +ffmpeg-sys-next = { version = "7.0.0" } thiserror = "2.0.3" human-size = "0.4.2" signal-hook = "0.3.15" @@ -39,6 +39,7 @@ log = "0.4.21" clap_complete = "4.5.8" log-once = "0.4.1" drm = "0.14.0" +ash = { version = "0.38.0", optional = true } # [patch.crates-io] # ffmpeg-next = { path = "../rust-ffmpeg" } @@ -52,5 +53,12 @@ nix = { version = "0.29.0", default-features = false, features = [ ] } serde_json = "1.0.103" +[features] +experimental-vulkan = [ + "ffmpeg-sys-next/vulkan", + "ash" +] + + [profile.release] lto = "thin" diff --git a/build.rs b/build.rs index ae65696..196243f 100644 --- a/build.rs +++ b/build.rs @@ -2,8 +2,9 @@ use std::env; fn main() { println!("cargo::rustc-check-cfg=cfg(ffmpeg_7_0)"); - for (name, _value) in env::vars() { - if name.starts_with("DEP_FFMPEG_") { + + for (name, value) in env::vars() { + if name.starts_with("DEP_FFMPEG_") && !value.is_empty() { println!( r#"cargo::rustc-cfg={}"#, name["DEP_FFMPEG_".len()..name.len()].to_lowercase() diff --git a/src/avhw.rs b/src/avhw.rs index 8209ec3..3a64359 100644 --- a/src/avhw.rs +++ b/src/avhw.rs @@ -3,18 +3,19 @@ use std::{ffi::CString, path::Path, ptr::null_mut}; use ffmpeg::{ dict, ffi::{ - av_buffer_ref, av_buffer_unref, av_hwdevice_ctx_create, av_hwframe_ctx_alloc, - av_hwframe_ctx_init, av_hwframe_get_buffer, AVHWFramesContext, + av_buffer_ref, av_buffer_unref, av_hwdevice_ctx_create, av_hwdevice_ctx_create_derived, + av_hwframe_ctx_alloc, av_hwframe_ctx_init, av_hwframe_get_buffer, AVHWFramesContext, }, format::Pixel, frame, }; -use log::error; use crate::DrmModifier; +use log::error; pub struct AvHwDevCtx { ptr: *mut ffmpeg::sys::AVBufferRef, + fmt: Pixel, } impl AvHwDevCtx { @@ -38,7 +39,48 @@ impl AvHwDevCtx { if sts != 0 { Err(ffmpeg::Error::from(sts)) } else { - Ok(Self { ptr: hw_device_ctx }) + Ok(Self { + ptr: hw_device_ctx, + fmt: Pixel::VAAPI, + }) + } + } + } + + pub fn new_vulkan(dri_device: &Path) -> Result { + unsafe { + let mut hw_device_ctx_drm = null_mut(); + let mut hw_device_ctx = null_mut(); + + let dev_cstr = CString::new(dri_device.to_str().unwrap()).unwrap(); + + let sts = av_hwdevice_ctx_create( + &mut hw_device_ctx_drm, + ffmpeg_sys_next::AVHWDeviceType::AV_HWDEVICE_TYPE_DRM, + dev_cstr.as_ptr(), + null_mut(), + 0, + ); + if sts != 0 { + return Err(ffmpeg::Error::from(sts)); + } + + let sts = av_hwdevice_ctx_create_derived( + &mut hw_device_ctx, + ffmpeg_next::ffi::AVHWDeviceType::AV_HWDEVICE_TYPE_VULKAN, + hw_device_ctx_drm, + 0, + ); + + av_buffer_unref(&mut hw_device_ctx_drm); + + if sts != 0 { + Err(ffmpeg::Error::from(sts)) + } else { + Ok(Self { + ptr: hw_device_ctx, + fmt: Pixel::VULKAN, + }) } } } @@ -48,24 +90,88 @@ impl AvHwDevCtx { pixfmt: Pixel, width: i32, height: i32, - modifier: DrmModifier, + modifiers: &[DrmModifier], ) -> Result { unsafe { let mut hwframe = av_hwframe_ctx_alloc(self.ptr as *mut _); - let hwframe_casted = (*hwframe).data as *mut AVHWFramesContext; + let hwframe_casted = &mut *((*hwframe).data as *mut AVHWFramesContext); // ffmpeg does not expose RGB vaapi - (*hwframe_casted).format = Pixel::VAAPI.into(); - (*hwframe_casted).sw_format = pixfmt.into(); - (*hwframe_casted).width = width; - (*hwframe_casted).height = height; - (*hwframe_casted).initial_pool_size = 5; - - if modifier != DrmModifier::LINEAR { - error!("unknown how to request non-linear frames in vaapi"); + hwframe_casted.format = self.fmt.into(); + hwframe_casted.sw_format = pixfmt.into(); + hwframe_casted.width = width; + hwframe_casted.height = height; + hwframe_casted.initial_pool_size = 5; + + let mut sts = -1; + if self.fmt == Pixel::VULKAN { + #[cfg(feature = "experimental-vulkan")] + { + use ash::vk; + use ffmpeg::ffi::{ + av_vkfmt_from_pixfmt, AVHWDeviceContext, AVVulkanDeviceContext, + AVVulkanFramesContext, + }; + + let av_devctx = &(*((*self.as_mut_ptr()).data as *mut AVHWDeviceContext)); + let vk_hwctx = &*(av_devctx.hwctx as *mut AVVulkanDeviceContext); + + let inst = ash::Instance::load( + &ash::StaticFn { + get_instance_proc_addr: vk_hwctx.get_proc_addr, + }, + vk_hwctx.inst, + ); + + let mut modifiers_filtered: Vec = Vec::new(); + for modifier in modifiers { + let mut drm_info = ash::vk::PhysicalDeviceImageDrmFormatModifierInfoEXT { + drm_format_modifier: modifier.0, + + ..Default::default() + }; + let mut image_format_prop = ash::vk::ImageFormatProperties2 { + ..Default::default() + }; + + if let Ok(()) = inst.get_physical_device_image_format_properties2( + vk_hwctx.phys_dev, + &vk::PhysicalDeviceImageFormatInfo2 { + format: *av_vkfmt_from_pixfmt(pixfmt.into()), + p_next: &mut drm_info as *mut _ as _, + ..Default::default() + }, + &mut image_format_prop, + ) { + modifiers_filtered.push(*modifier); + } + } + + // some buffer requirements are complex, just start removing modifiers until it works + while sts != 0 && !modifiers_filtered.is_empty() { + let mut create_info = ash::vk::ImageDrmFormatModifierListCreateInfoEXT { + drm_format_modifier_count: modifiers_filtered.len() as u32, + p_drm_format_modifiers: modifiers_filtered.as_ptr() as _, + ..Default::default() + }; + + let vk_ptr = hwframe_casted.hwctx as *mut AVVulkanFramesContext; + + (*vk_ptr).tiling = vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT; + (*vk_ptr).create_pnext = &mut create_info as *mut _ as _; + + sts = av_hwframe_ctx_init(hwframe); + modifiers_filtered.pop(); + } + } + #[cfg(not(feature = "experimental-vulkan"))] + panic!("vulkan requested but built without vulkan support") + } else { + if modifiers != &[DrmModifier::LINEAR] { + error!("unknown how to request non-linear frames in vaapi"); + } + sts = av_hwframe_ctx_init(hwframe); } - - let sts = av_hwframe_ctx_init(hwframe); if sts != 0 { return Err(ffmpeg::Error::from(sts)); } diff --git a/src/main.rs b/src/main.rs index bf7509f..cb8c753 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ use std::{ time::Duration, }; -use anyhow::{bail, format_err, Context}; +use anyhow::{anyhow, bail, format_err, Context}; use audio::AudioHandle; use cap_ext_image_copy::CapExtImageCopy; use cap_wlr_screencopy::CapWlrScreencopy; @@ -233,6 +233,14 @@ pub struct Args { default_value = "false" )] ext_image_copy_capture: bool, + + #[cfg_attr(not(feature = "experimental-vulkan"), clap(hide = true))] + #[clap( + long = "experimental-vulkan", + help = "use vulkan allocator & encode", + default_value = "false" + )] + vulkan: bool, } trait CaptureSource: Sized { @@ -468,6 +476,7 @@ impl fmt::Debug for TypedObjectId { } #[derive(Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] struct DrmModifier(u64); impl DrmModifier { @@ -510,18 +519,18 @@ impl fmt::Debug for DrmModifier { } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] struct DmabufPotentialFormat { fourcc: DrmFourcc, modifiers: Vec, } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] struct DmabufFormat { width: i32, height: i32, fourcc: DrmFourcc, - modifier: DrmModifier, + modifiers: Vec, } #[link(name = "drm")] @@ -932,7 +941,8 @@ impl State { panic!("queue_frame_capture called in a strange state"); }; - let av_surface = enc.frames_rgb.alloc().unwrap(); + let mut av_surface = enc.frames_rgb.alloc().unwrap(); + av_surface.set_color_space(ffmpeg::color::Space::RGB); let (desc, av_mapping) = map_drm(&av_surface); @@ -972,9 +982,9 @@ impl State { fn on_new_capture_format( &mut self, mut cs: CompleteState, - new_format: DmabufFormat, + new_format: &DmabufFormat, ) -> anyhow::Result> { - if new_format == cs.enc.selected_format { + if *new_format == cs.enc.selected_format { return Ok(cs); } info!("compositor gave new format {new_format:?}"); @@ -1008,16 +1018,17 @@ impl State { } cs.enc.frames_rgb = cs.enc.hw_device_ctx - .create_frame_ctx(capture_pixfmt, new_format.width, new_format.height, new_format.modifier) + .create_frame_ctx(capture_pixfmt, new_format.width, new_format.height, &new_format.modifiers) .with_context(|| format!("Failed to create vaapi frame context for capture surfaces of format {capture_pixfmt:?} {new_format:?}"))?; // todo: proper size here let enc_pixfmt_av = match cs.enc.enc_pixfmt { EncodePixelFormat::Vaapi(fmt) => fmt, EncodePixelFormat::Sw(fmt) => fmt, + EncodePixelFormat::Vulkan(fmt) => fmt, }; - cs.enc.selected_format = new_format; + cs.enc.selected_format = new_format.clone(); // flush old filter & encoder cs.enc @@ -1037,7 +1048,7 @@ impl State { // create a new encoder // TODO: correct scaling let mut frames_yuv = cs.enc.hw_device_ctx - .create_frame_ctx(enc_pixfmt_av, cs.enc.roi_screen_coord.w, cs.enc.roi_screen_coord.h, DrmModifier::LINEAR) + .create_frame_ctx(enc_pixfmt_av, cs.enc.roi_screen_coord.w, cs.enc.roi_screen_coord.h, &[DrmModifier::LINEAR]) .with_context(|| { format!("Failed to create a vaapi frame context for encode surfaces of format {enc_pixfmt_av:?} {}x{}", cs.enc.roi_screen_coord.w, cs.enc.roi_screen_coord.h) })?; @@ -1071,6 +1082,7 @@ impl State { cs.enc.roi_screen_coord, (cs.enc.roi_screen_coord.w, cs.enc.roi_screen_coord.h), cs.enc.transform, + self.args.vulkan, ); cs.enc.video_filter = filter; cs.enc.filter_output_timebase = filter_timebase; @@ -1391,32 +1403,26 @@ impl State { height: i32, capture_formats: &[DmabufPotentialFormat], ) -> anyhow::Result { - let mut selected_format = None; for preferred_format in [ DrmFourcc::Xrgb8888, DrmFourcc::Xbgr8888, DrmFourcc::Xrgb2101010, ] { - let is_fmt_supported = capture_formats.iter().any(|p| { + let find = capture_formats.iter().find(|p| { p.fourcc == preferred_format && p.modifiers.iter().any(|m| *m == DrmModifier::LINEAR) }); - if is_fmt_supported { - selected_format = Some(DmabufFormat { - fourcc: preferred_format, - modifier: DrmModifier::LINEAR, + if let Some(find) = find { + return Ok(DmabufFormat { width, height, + fourcc: find.fourcc, + modifiers: find.modifiers.clone(), }); - break; } } - Ok( match selected_format { - Some(sf) => sf, - None => - bail!("failed to select a viable capture format. This is probably a bug. Availabe capture formats are {:?}", capture_formats), - }) + bail!("failed to select a viable capture format. This is probably a bug. Availabe capture formats are {:?}", capture_formats) } let selected_format = match negotiate_format_impl(w as i32, h as i32, capture_formats) { @@ -1455,7 +1461,7 @@ impl State { } EncConstructionStage::Complete(mut c) => { // can happen on dispaly disconnect & reconnect OR output resize - c = match self.on_new_capture_format(c, selected_format) { + c = match self.on_new_capture_format(c, &selected_format) { Ok(enc) => enc, Err(e) => { error!("failed to renegotiate new format {selected_format:?}: {e}"); @@ -1514,17 +1520,27 @@ struct EncState { #[derive(Copy, Clone, Debug)] enum EncodePixelFormat { Vaapi(Pixel), + Vulkan(Pixel), Sw(Pixel), } -fn vaapi_codec_id(codec: codec::Id) -> Option<&'static str> { - match codec { - codec::Id::H264 => Some("h264_vaapi"), - codec::Id::H265 | codec::Id::HEVC => Some("hevc_vaapi"), - codec::Id::VP8 => Some("vp8_vaapi"), - codec::Id::VP9 => Some("vp9_vaapi"), - codec::Id::AV1 => Some("av1_vaapi"), - _ => None, +fn hw_codec_id(codec: codec::Id, vulkan: bool) -> Option<&'static str> { + if vulkan { + match codec { + codec::Id::H264 => Some("h264_vulkan"), + codec::Id::H265 | codec::Id::HEVC => Some("hevc_vulkan"), + codec::Id::AV1 => Some("av1_vulkan"), + _ => None, + } + } else { + match codec { + codec::Id::H264 => Some("h264_vaapi"), + codec::Id::H265 | codec::Id::HEVC => Some("hevc_vaapi"), + codec::Id::VP8 => Some("vp8_vaapi"), + codec::Id::VP9 => Some("vp9_vaapi"), + codec::Id::AV1 => Some("av1_vaapi"), + _ => None, + } } } @@ -1559,10 +1575,11 @@ fn make_video_params( enc.set_format(match enc_pix_fmt { EncodePixelFormat::Vaapi(_) => Pixel::VAAPI, + EncodePixelFormat::Vulkan(_) => Pixel::VULKAN, EncodePixelFormat::Sw(sw) => sw, }); - if let EncodePixelFormat::Vaapi(sw_pix_fmt) = enc_pix_fmt { + if let EncodePixelFormat::Vaapi(sw_pix_fmt) | EncodePixelFormat::Vulkan(sw_pix_fmt) = enc_pix_fmt { unsafe { (*enc.as_mut_ptr()).hw_device_ctx = av_buffer_ref(hw_device_ctx.as_mut_ptr()); (*enc.as_mut_ptr()).hw_frames_ctx = av_buffer_ref(frames_yuv.as_mut_ptr()); @@ -1611,7 +1628,7 @@ fn get_encoder(args: &Args, format: &Output) -> anyhow::Result { }; let maybe_hw_codec = if args.hw { - if let Some(hw_codec_name) = vaapi_codec_id(codec_id) { + if let Some(hw_codec_name) = hw_codec_id(codec_id, args.vulkan) { if let Some(codec) = ffmpeg_next::encoder::find_by_name(hw_codec_name) { Some(codec) } else { @@ -1653,6 +1670,8 @@ fn get_enc_pixfmt(args: &Args, encoder: &ffmpeg::Codec) -> anyhow::Result EncodePixelFormat::Sw(supported_formats[0]), @@ -1719,18 +1738,26 @@ impl EncState { let global_header = octx.format().flags().contains(format::Flags::GLOBAL_HEADER); - eprintln!( - "Opening libva device from DRM device {}", - dri_device.display() - ); + let mut hw_device_ctx = if args.vulkan { + error!("Vulkan is buggy and isn't known to work on any platform. Do not report bugs."); - let mut hw_device_ctx = match AvHwDevCtx::new_libva(dri_device) { - Ok(hdc) => hdc, - Err(e) => bail!("Failed to load vaapi device: {e}. This is likely *not* a bug in wl-screenrec, but an issue with your vaapi installation. Follow your distribution's instructions. If you're pretty sure you've done this correctly, create a new issue with the output of `vainfo` and if `wf-recorder -c h264_vaapi -d {}` works.", dri_device.display()), + #[allow(unreachable_code)] + { + info!("Opening vulkan device from {}", dri_device.display()); + AvHwDevCtx::new_vulkan(dri_device) + .map_err(|e| anyhow!("Failed to open vulkan device: {e}"))? + } + } else { + info!( + "Opening libva device from DRM device {}", + dri_device.display() + ); + AvHwDevCtx::new_libva(dri_device).map_err( + |e| anyhow!("Failed to load vaapi device: {e}. This is likely *not* a bug in wl-screenrec, but an issue with your vaapi installation. Follow your distribution's instructions. If you're pretty sure you've done this correctly, create a new issue with the output of `vainfo` and if `wf-recorder -c h264_vaapi -d {}` works.", dri_device.display()))? }; let mut frames_rgb = hw_device_ctx - .create_frame_ctx(dmabuf_to_av(capture_format.fourcc), capture_format.width, capture_format.height, capture_format.modifier) + .create_frame_ctx(dmabuf_to_av(capture_format.fourcc), capture_format.width, capture_format.height, &capture_format.modifiers) .with_context(|| format!("Failed to create vaapi frame context for capture surfaces of format {capture_format:?}"))?; let (enc_w_screen_coord, enc_h_screen_coord) = match args.encode_resolution { @@ -1745,21 +1772,23 @@ impl EncState { roi_screen_coord, (enc_w_screen_coord, enc_h_screen_coord), transform, + args.vulkan, // xx enum ); let enc_pixfmt_av = match enc_pixfmt { EncodePixelFormat::Vaapi(fmt) => fmt, + EncodePixelFormat::Vulkan(fmt) => fmt, EncodePixelFormat::Sw(fmt) => fmt, }; let mut frames_yuv = hw_device_ctx - .create_frame_ctx(enc_pixfmt_av, enc_w_screen_coord, enc_h_screen_coord, DrmModifier::LINEAR) + .create_frame_ctx(enc_pixfmt_av, enc_w_screen_coord, enc_h_screen_coord, &[DrmModifier::LINEAR]) .with_context(|| { format!("Failed to create a vaapi frame context for encode surfaces of format {enc_pixfmt_av:?} {enc_w_screen_coord}x{enc_h_screen_coord}") })?; info!("{}", video_filter.dump()); - let enc = make_video_params( + let mut enc = make_video_params( args, enc_pixfmt, &encoder, @@ -1790,6 +1819,10 @@ impl EncState { passed_enc_options.clone() }; + unsafe { + (*enc.as_mut_ptr()).hw_frames_ctx = av_buffer_ref(frames_yuv.as_mut_ptr()); + } + match args.low_power { LowPowerMode::Auto => match enc.open_with(low_power_opts.clone()) { Ok(enc) => (enc, low_power_opts), @@ -1816,7 +1849,7 @@ impl EncState { } } else { let mut enc_options = passed_enc_options.clone(); - if enc_options.get("preset").is_none() { + if encoder.name() == "x264" && enc_options.get("preset").is_none() { enc_options.set("preset", "ultrafast"); } (enc.open_with(enc_options.clone()).unwrap(), enc_options) @@ -2061,9 +2094,16 @@ fn video_filter( roi_screen_coord: Rect, // size (pixels) (enc_w_screen_coord, enc_h_screen_coord): (i32, i32), // size (pixels) to encode. if not same as roi_{w,h}, the image will be scaled. transform: Transform, + vulkan: bool, ) -> (filter::Graph, Rational) { let mut g = ffmpeg::filter::graph::Graph::new(); + let pixfmt_int = if vulkan { + AVPixelFormat::AV_PIX_FMT_VULKAN as c_int + } else { + AVPixelFormat::AV_PIX_FMT_VAAPI as c_int + }; + // src unsafe { let buffersrc_ctx = avfilter_graph_alloc_filter( @@ -2079,7 +2119,7 @@ fn video_filter( p.width = capture_width; p.height = capture_height; - p.format = AVPixelFormat::AV_PIX_FMT_VAAPI as c_int; + p.format = pixfmt_int; p.time_base.num = 1; p.time_base.den = 1_000_000_000; p.hw_frames_ctx = inctx.as_mut_ptr(); @@ -2102,6 +2142,7 @@ fn video_filter( match pix_fmt { EncodePixelFormat::Vaapi(fmt) => fmt, EncodePixelFormat::Sw(fmt) => fmt, + EncodePixelFormat::Vulkan(fmt) => fmt, } .into(), )) @@ -2109,16 +2150,25 @@ fn video_filter( ) }; - let transpose_filter = match transform { - Transform::_90 => ",transpose_vaapi=dir=clock", - Transform::_180 => ",transpose_vaapi=dir=reversal", - Transform::_270 => ",transpose_vaapi=dir=cclock", - Transform::Flipped => ",transpose_vaapi=dir=hflip", - Transform::Flipped90 => ",transpose_vaapi=dir=cclock_flip", - Transform::Flipped180 => ",transpose_vaapi=dir=vflip", - Transform::Flipped270 => ",transpose_vaapi=dir=clock_flip", - _ => "", + let transpose_dir = match transform { + Transform::_90 => Some("clock"), + Transform::_180 => Some("reversal"), + Transform::_270 => Some("cclock"), + Transform::Flipped => Some("hflip"), + Transform::Flipped90 => Some("cclock_flip"), + Transform::Flipped180 => Some("vflip"), + Transform::Flipped270 => Some("clock_flip"), + _ => None, }; + let transpose_filter = transpose_dir + .map(|transpose_dir| { + if vulkan { + format!("transpose_vulkan=dir={transpose_dir}") + } else { + format!("transpose_vaapi=dir={transpose_dir}") + } + }) + .unwrap_or_default(); // it seems intel's vaapi driver doesn't support transpose in RGB space, so we have to transpose // after the format conversion @@ -2137,10 +2187,25 @@ fn video_filter( let (enc_w, enc_h) = transpose_if_transform_transposed((enc_w_screen_coord, enc_h_screen_coord), transform); - // exact=1 should not be necessary, as the input is not chroma-subsampled - // however, there is a bug in ffmpeg that makes it required: https://trac.ffmpeg.org/ticket/10669 - // it is harmless to add though, so keep it as a workaround - g.output("in", 0) + if vulkan { + g.output("in", 0) + .unwrap() + .input("out", 0) + .unwrap() + .parse(&format!( + "crop={roi_w}:{roi_h}:{roi_x}:{roi_y}:exact=1,scale_vulkan=format={output_real_pixfmt_name}:w={enc_w}:h={enc_h}{transpose_filter}{}", + if let EncodePixelFormat::Vulkan(_) = pix_fmt { + "" + } else { + ", hwdownload" + }, + )) + .unwrap(); + } else { + // exact=1 should not be necessary, as the input is not chroma-subsampled + // however, there is a bug in ffmpeg that makes it required: https://trac.ffmpeg.org/ticket/10669 + // it is harmless to add though, so keep it as a workaround + g.output("in", 0) .unwrap() .input("out", 0) .unwrap() @@ -2153,6 +2218,7 @@ fn video_filter( }, )) .unwrap(); + } g.validate().unwrap();