Files
punktfunk/crates/pf-ffvk/src/lib.rs
T
enricobuehler 58ccd530fc feat(ffvk): bindgen shim for FFmpeg's Vulkan hwcontext (pf-ffvk)
ffmpeg-sys-next binds a curated header list that omits every
hwcontext_*.h, so AVVulkanDeviceContext/AVVulkanFramesContext/AVVkFrame
— the surface that lets FFmpeg's Vulkan Video decoder run on the
presenter's own VkDevice and hand the decoded VkImages back — had no
Rust bindings at all. pf-ffvk generates them at build time from the
SYSTEM headers, which is the only ABI-safe option: the struct layout
depends on FF_API_* deprecation gates resolved in libavutil/version.h
(confirmed: FFmpeg 8.1 still carries the FIXED_QUEUES fields). Ships
ash<->vulkan.h handle conversions; opaque AVHW*/AVFrame types keep
ffmpeg-sys-next the owner of the core surface (pointer casts across).
Needs vulkan-headers (Arch) / libvulkan-dev (CI, added);
PF_FFVK_VULKAN_INCLUDE overrides for cross builds. Verified: bindgen
layout tests pass, av_vk_frame_alloc links and zero-initializes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 23:14:08 +02:00

92 lines
3.2 KiB
Rust

//! FFmpeg's Vulkan hwcontext surface (`AVVulkanDeviceContext`, `AVVulkanFramesContext`,
//! `AVVkFrame`), bindgen-generated from the system headers at build time — see build.rs
//! for why this must not be hand-transcribed.
//!
//! The raw bindings use vulkan.h's own handle types (pointers on 64-bit). The [`ash`]
//! conversion helpers below cross between them and ash's u64-newtype handles; both sides
//! are the same underlying Vulkan object handles, so the casts are value-preserving.
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
// bindgen's layout tests deref-null-pointer by design; silence the lints they trip.
#![allow(deref_nullptr)]
#![allow(unnecessary_transmutes)]
#[cfg(target_os = "linux")]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
/// Conversions between the generated vulkan.h handle types and ash's.
#[cfg(target_os = "linux")]
pub mod ashx {
use super::*;
use ash::vk::Handle as _;
/// vulkan.h non-dispatchable handles are `*mut T` on 64-bit; ash's are `u64`
/// newtypes. Same bits either way.
pub fn image(h: VkImage) -> ash::vk::Image {
ash::vk::Image::from_raw(h as u64)
}
pub fn semaphore(h: VkSemaphore) -> ash::vk::Semaphore {
ash::vk::Semaphore::from_raw(h as u64)
}
pub fn image_layout(l: VkImageLayout) -> ash::vk::ImageLayout {
ash::vk::ImageLayout::from_raw(l as i32)
}
// --- ash → vulkan.h (filling AVVulkanDeviceContext) ---------------------------------
pub fn to_instance(h: ash::vk::Instance) -> VkInstance {
h.as_raw() as VkInstance
}
pub fn to_physical_device(h: ash::vk::PhysicalDevice) -> VkPhysicalDevice {
h.as_raw() as VkPhysicalDevice
}
pub fn to_device(h: ash::vk::Device) -> VkDevice {
h.as_raw() as VkDevice
}
/// ash's loader-level `vkGetInstanceProcAddr` as the header's PFN type. Both are the
/// same C ABI function pointer (`extern "system"` == `extern "C"` on the platforms
/// this crate builds for).
pub fn to_get_proc_addr(
f: unsafe extern "system" fn(
ash::vk::Instance,
*const std::ffi::c_char,
) -> ash::vk::PFN_vkVoidFunction,
) -> PFN_vkGetInstanceProcAddr {
unsafe { std::mem::transmute(f) }
}
}
#[cfg(all(test, target_os = "linux"))]
mod tests {
use super::*;
/// The allocator runs (links against the system libavutil) and the struct is
/// readable at the offsets bindgen computed — sem_value zero-initialized.
#[test]
fn vk_frame_alloc_links_and_zeroes() {
unsafe {
let f = av_vk_frame_alloc();
assert!(!f.is_null(), "av_vk_frame_alloc returned NULL");
assert_eq!((*f).sem_value[0], 0);
assert_eq!((*f).queue_family[0], 0);
// Leak the one test frame rather than binding av_free here.
}
}
/// AV_NUM_DATA_POINTERS-sized arrays came through with the right length.
#[test]
fn frame_arrays_are_av_num_data_pointers() {
let f: AVVkFrame = unsafe { std::mem::zeroed() };
assert_eq!(f.img.len(), 8);
assert_eq!(f.sem_value.len(), 8);
}
}