decky / build-publish (push) Successful in 19s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
android / android (push) Successful in 15m38s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 13s
arch / build-publish (push) Successful in 16m13s
windows-host / package (push) Successful in 10m20s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6m20s
apple / swift (push) Successful in 5m36s
docker / deploy-docs (push) Successful in 23s
docker / build-push-arm64cross (push) Successful in 8m41s
ci / web (push) Successful in 48s
ci / docs-site (push) Successful in 1m8s
ci / bench (push) Successful in 6m49s
deb / build-publish-client-arm64 (push) Successful in 7m31s
deb / build-publish (push) Successful in 11m40s
ci / rust-arm64 (push) Successful in 12m11s
deb / build-publish-host (push) Successful in 12m15s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m59s
ci / rust (push) Successful in 21m45s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m51s
apple / screenshots (push) Successful in 25m22s
Found by running the smokes at 1920x1080 under the validation layers while chasing the three
items left over from WP4.2. AV1 forbids the encode source's `codedExtent` differing from the
sequence header without `FRAME_SIZE_OVERRIDE`
(VUID-vkCmdEncodeVideoKHR-flags-10324), or from the reference slots without
`MOTION_VECTOR_SCALING` (`-10325`). RADV PHOENIX advertises NEITHER — and RGB-direct is the
default on EFC hosts with true-extent the default at unaligned modes, so plain 1080p AV1 was
tripping both on every frame: source 1920x1080 against an app-aligned 1920x1088 header and
DPB. Measured 16 violations per 8-frame run; the CSC path had none.
The fix is to make all three agree at the RENDER size rather than the aligned one — an
unpadded coded size is valid on this hardware, so the coded frame simply IS the visible
frame:
- the AV1 sequence header follows the render size when true-extent is active (joining
native NV12, which already authors true-size headers for the same reason);
- the DPB setup and reference slots carry `src_extent` instead of the aligned `ext2d`.
`src_extent` already collapses to `ext2d` whenever true-extent is off, so every other
configuration is untouched;
- `render_and_frame_size_different` now compares render against the DECLARED source extent
instead of the aligned size, or true-extent would have claimed a mismatch that no longer
exists.
Fixing only the header is not enough and is actively misleading: it clears -10324 and
immediately exposes -10325, because the mismatch has moved to the reference slots rather than
gone. Both had to move together.
This keeps the EFC fast path. Two alternatives were implemented, measured and rejected on the
way here: falling back to the compute CSC costs the zero-copy the B2 work existed to deliver,
and routing to the padded-copy staging trades these two VUIDs for
VUID-VkImageCreateInfo-pNext-06811 — `pad_img`'s extra TRANSFER_SRC usage is not in the
profile-advertised set, measured 8x per session on HEVC-padded too, so that is a pre-existing
defect of the padded path and not somewhere to route a default session.
HEVC is deliberately untouched: it has no equivalent constraint (its crop rides the
conformance window), it measures zero violations, and its aligned-SPS path is the validated
one.
On RADV PHOENIX (780M, Mesa 26.0.4) the AV1 stream now decodes as coded 1920x1080 / render
1920x1080 — genuinely unpadded, where before the alignment rows were encoded and cropped
back out. The CSC path still reports coded 1920x1088 / render 1920x1080, which is correct for
it. All four `vulkan_smoke*` pass at 256x256 and 1920x1080.
Two validation errors remain on the RGB-direct path and are NOT ours, now with evidence
rather than assumption: `VUID-VkImageViewCreateInfo-image-08336` uses the PROFILE-BLIND format
query, so it cannot see that RGB conversion legalises BGRA as an encode source — the
profile-aware query used by -06811 accepts the very same image; and
`VUID-VkQueryPoolCreateInfo-pNext-pNext` rejects
`VkVideoEncodeProfileRgbConversionInfoVALVE`, which the VALVE extension REQUIRES for profile
identity, and the layer diagnoses itself as "a struct from an extension added to a later
version of the Vulkan header".
Verified: canonical Linux gate (docker linux/amd64) L1-L4 green; on-glass on RADV PHOENIX
under `VK_LOADER_LAYERS_ENABLE='*validation*'`, with the bitstreams read back through
libdav1d/trace_headers.
873 lines
30 KiB
Rust
873 lines
30 KiB
Rust
//! Vendored `VK_KHR_video_encode_av1` bindings — the AV1-encode structs, `StdVideoEncodeAV1*`
|
|
//! types and struct-type constants that our pinned `ash 0.38.0+1.3.281` does not ship (the
|
|
//! extension was finalized in Vulkan 1.3.290). Bumping `ash` to git-master (`+1.4.352`, which has
|
|
//! them) breaks the *client*: it drops the lifetime on `vk::AllocationCallbacks`, and `sdl3-sys`'s
|
|
//! `ash` feature still generates `AllocationCallbacks<'static>`, so the presenter's SDL/Vulkan
|
|
//! surface path won't compile. Rather than churn the client for a host-only need, we vendor just
|
|
//! the encode-side definitions here, **copied verbatim from ash-master's generated code** (so the
|
|
//! layouts are correct-by-construction) and chain them into ash's generic video-encode-queue calls
|
|
//! via raw `p_next`, exactly as the HEVC path already chains its rate-control struct.
|
|
//!
|
|
//! Everything *common* to AV1 (sequence header, tile/quant/loop-filter/CDEF/… sub-structs, the
|
|
//! `StdVideoAV1*` enums) is already present in 1.3.281's `ash::vk::native` — AV1 **decode** brought
|
|
//! it in — so we reuse those and vendor only the encode-specific pieces. Delete this module and
|
|
//! switch to `ash::vk::*` once `ash` publishes a 1.4.x release and `sdl3-sys` regenerates.
|
|
#![allow(non_snake_case, non_camel_case_types, dead_code)]
|
|
|
|
use ash::vk;
|
|
use ash::vk::native::{
|
|
StdVideoAV1FrameType, StdVideoAV1InterpolationFilter, StdVideoAV1Level, StdVideoAV1Profile,
|
|
StdVideoAV1SequenceHeader, StdVideoAV1TxMode,
|
|
};
|
|
use std::ffi::{c_void, CStr};
|
|
|
|
/// `VK_KHR_video_encode_av1` extension name — ash 0.38's `ash::khr::video_encode_av1` doesn't exist,
|
|
/// so we pass this raw to `enabled_extension_names`.
|
|
pub const EXTENSION_NAME: &CStr = c"VK_KHR_video_encode_av1";
|
|
|
|
// ---------- struct-type (VkStructureType) values — construct via `vk::StructureType::from_raw` ----------
|
|
pub const ST_CAPABILITIES: i32 = 1_000_513_000;
|
|
pub const ST_SESSION_PARAMETERS_CREATE_INFO: i32 = 1_000_513_001;
|
|
pub const ST_PICTURE_INFO: i32 = 1_000_513_002;
|
|
pub const ST_DPB_SLOT_INFO: i32 = 1_000_513_003;
|
|
pub const ST_PHYSICAL_DEVICE_FEATURES: i32 = 1_000_513_004;
|
|
pub const ST_PROFILE_INFO: i32 = 1_000_513_005;
|
|
pub const ST_RATE_CONTROL_INFO: i32 = 1_000_513_006;
|
|
pub const ST_RATE_CONTROL_LAYER_INFO: i32 = 1_000_513_007;
|
|
pub const ST_SESSION_CREATE_INFO: i32 = 1_000_513_009;
|
|
pub const ST_GOP_REMAINING_FRAME_INFO: i32 = 1_000_513_010;
|
|
|
|
/// `VK_VIDEO_CODEC_OPERATION_ENCODE_AV1_BIT_KHR` (bit 18).
|
|
pub const VIDEO_CODEC_OPERATION_ENCODE_AV1: u32 = 0x0004_0000;
|
|
/// `VK_MAX_VIDEO_AV1_REFERENCES_PER_FRAME_KHR` — LAST..ALTREF (the 7 inter reference names).
|
|
pub const MAX_VIDEO_AV1_REFERENCES_PER_FRAME: usize = 7;
|
|
/// `STD_VIDEO_AV1_PRIMARY_REF_NONE` — a frame that inherits no CDF/context from any reference
|
|
/// (the recovery-anchor lever: a clean P-frame independent of prior probability state).
|
|
pub const PRIMARY_REF_NONE: u8 = 7;
|
|
/// `VK_VIDEO_ENCODE_AV1_SUPERBLOCK_SIZE_128_BIT_KHR` (bit 1 of the superblock-size flags).
|
|
pub const SUPERBLOCK_SIZE_128: u32 = 0x2;
|
|
|
|
// `VkVideoEncodeAV1CapabilityFlagBitsKHR` — the two that decide whether the encode source may be a
|
|
// different size from the declared frame. Both absent on RADV PHOENIX.
|
|
/// Without this, the source's `codedExtent` MUST equal the sequence header's
|
|
/// `max_frame_{width,height}_minus_1 + 1` (`VUID-vkCmdEncodeVideoKHR-flags-10324`).
|
|
pub const CAPABILITY_FRAME_SIZE_OVERRIDE: u32 = 0x0000_0008;
|
|
/// Without this, EVERY reference slot's `codedExtent` MUST equal the source's
|
|
/// (`VUID-vkCmdEncodeVideoKHR-flags-10325`).
|
|
pub const CAPABILITY_MOTION_VECTOR_SCALING: u32 = 0x0000_0010;
|
|
|
|
// `VkVideoEncodeAV1PredictionModeKHR`
|
|
pub const PREDICTION_MODE_INTRA_ONLY: i32 = 0;
|
|
pub const PREDICTION_MODE_SINGLE_REFERENCE: i32 = 1;
|
|
// `VkVideoEncodeAV1RateControlGroupKHR`
|
|
pub const RC_GROUP_INTRA: i32 = 0;
|
|
pub const RC_GROUP_PREDICTIVE: i32 = 1;
|
|
pub const RC_GROUP_BIPREDICTIVE: i32 = 2;
|
|
|
|
// AV1 reference names (index into `reference_name_slot_indices`, which is 0-based over LAST..ALTREF).
|
|
pub const REFERENCE_NAME_LAST_FRAME_IDX: usize = 0; // STD_VIDEO_AV1_REFERENCE_NAME_LAST_FRAME - 1
|
|
|
|
// ---------- bindgen bitfield helper (copied verbatim from ash-master native.rs) ----------
|
|
#[repr(C)]
|
|
#[derive(Debug, Default, Copy, Clone)]
|
|
pub struct __BindgenBitfieldUnit<Storage> {
|
|
storage: Storage,
|
|
}
|
|
impl<Storage> __BindgenBitfieldUnit<Storage> {
|
|
#[inline]
|
|
pub const fn new(storage: Storage) -> Self {
|
|
Self { storage }
|
|
}
|
|
}
|
|
impl<Storage> __BindgenBitfieldUnit<Storage>
|
|
where
|
|
Storage: AsRef<[u8]> + AsMut<[u8]>,
|
|
{
|
|
#[inline]
|
|
pub fn get_bit(&self, index: usize) -> bool {
|
|
let byte_index = index / 8;
|
|
let byte = self.storage.as_ref()[byte_index];
|
|
let bit_index = if cfg!(target_endian = "big") {
|
|
7 - (index % 8)
|
|
} else {
|
|
index % 8
|
|
};
|
|
byte & (1 << bit_index) == (1 << bit_index)
|
|
}
|
|
#[inline]
|
|
pub fn set_bit(&mut self, index: usize, val: bool) {
|
|
let byte_index = index / 8;
|
|
let byte = &mut self.storage.as_mut()[byte_index];
|
|
let bit_index = if cfg!(target_endian = "big") {
|
|
7 - (index % 8)
|
|
} else {
|
|
index % 8
|
|
};
|
|
let mask = 1 << bit_index;
|
|
if val {
|
|
*byte |= mask;
|
|
} else {
|
|
*byte &= !mask;
|
|
}
|
|
}
|
|
#[inline]
|
|
pub fn get(&self, bit_offset: usize, bit_width: u8) -> u64 {
|
|
let mut val = 0;
|
|
for i in 0..(bit_width as usize) {
|
|
if self.get_bit(i + bit_offset) {
|
|
let index = if cfg!(target_endian = "big") {
|
|
bit_width as usize - 1 - i
|
|
} else {
|
|
i
|
|
};
|
|
val |= 1 << index;
|
|
}
|
|
}
|
|
val
|
|
}
|
|
#[inline]
|
|
pub fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) {
|
|
for i in 0..(bit_width as usize) {
|
|
let mask = 1 << i;
|
|
let val_bit_is_set = val & mask == mask;
|
|
let index = if cfg!(target_endian = "big") {
|
|
bit_width as usize - 1 - i
|
|
} else {
|
|
i
|
|
};
|
|
self.set_bit(index + bit_offset, val_bit_is_set);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------- Std encode structs (copied from ash-master native.rs; common Std types reused from ash) ----------
|
|
#[repr(C, align(4))]
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct StdVideoEncodeAV1PictureInfoFlags {
|
|
pub _bitfield_align_1: [u8; 0],
|
|
pub _bitfield_1: __BindgenBitfieldUnit<[u8; 4usize]>,
|
|
}
|
|
impl StdVideoEncodeAV1PictureInfoFlags {
|
|
#[inline]
|
|
pub fn set_error_resilient_mode(&mut self, val: u32) {
|
|
self._bitfield_1.set(0, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_disable_cdf_update(&mut self, val: u32) {
|
|
self._bitfield_1.set(1, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_use_superres(&mut self, val: u32) {
|
|
self._bitfield_1.set(2, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_render_and_frame_size_different(&mut self, val: u32) {
|
|
self._bitfield_1.set(3, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_allow_screen_content_tools(&mut self, val: u32) {
|
|
self._bitfield_1.set(4, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_is_filter_switchable(&mut self, val: u32) {
|
|
self._bitfield_1.set(5, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_force_integer_mv(&mut self, val: u32) {
|
|
self._bitfield_1.set(6, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_frame_size_override_flag(&mut self, val: u32) {
|
|
self._bitfield_1.set(7, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_buffer_removal_time_present_flag(&mut self, val: u32) {
|
|
self._bitfield_1.set(8, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_allow_intrabc(&mut self, val: u32) {
|
|
self._bitfield_1.set(9, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_frame_refs_short_signaling(&mut self, val: u32) {
|
|
self._bitfield_1.set(10, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_allow_high_precision_mv(&mut self, val: u32) {
|
|
self._bitfield_1.set(11, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_is_motion_mode_switchable(&mut self, val: u32) {
|
|
self._bitfield_1.set(12, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_use_ref_frame_mvs(&mut self, val: u32) {
|
|
self._bitfield_1.set(13, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_disable_frame_end_update_cdf(&mut self, val: u32) {
|
|
self._bitfield_1.set(14, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_allow_warped_motion(&mut self, val: u32) {
|
|
self._bitfield_1.set(15, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_reduced_tx_set(&mut self, val: u32) {
|
|
self._bitfield_1.set(16, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_skip_mode_present(&mut self, val: u32) {
|
|
self._bitfield_1.set(17, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_delta_q_present(&mut self, val: u32) {
|
|
self._bitfield_1.set(18, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_delta_lf_present(&mut self, val: u32) {
|
|
self._bitfield_1.set(19, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_delta_lf_multi(&mut self, val: u32) {
|
|
self._bitfield_1.set(20, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_segmentation_enabled(&mut self, val: u32) {
|
|
self._bitfield_1.set(21, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_segmentation_update_map(&mut self, val: u32) {
|
|
self._bitfield_1.set(22, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_segmentation_temporal_update(&mut self, val: u32) {
|
|
self._bitfield_1.set(23, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_segmentation_update_data(&mut self, val: u32) {
|
|
self._bitfield_1.set(24, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_UsesLr(&mut self, val: u32) {
|
|
self._bitfield_1.set(25, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_usesChromaLr(&mut self, val: u32) {
|
|
self._bitfield_1.set(26, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_show_frame(&mut self, val: u32) {
|
|
self._bitfield_1.set(27, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_showable_frame(&mut self, val: u32) {
|
|
self._bitfield_1.set(28, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_reserved(&mut self, val: u32) {
|
|
self._bitfield_1.set(29, 3, val as u64)
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct StdVideoEncodeAV1PictureInfo {
|
|
pub flags: StdVideoEncodeAV1PictureInfoFlags,
|
|
pub frame_type: StdVideoAV1FrameType,
|
|
pub frame_presentation_time: u32,
|
|
pub current_frame_id: u32,
|
|
pub order_hint: u8,
|
|
pub primary_ref_frame: u8,
|
|
pub refresh_frame_flags: u8,
|
|
pub coded_denom: u8,
|
|
pub render_width_minus_1: u16,
|
|
pub render_height_minus_1: u16,
|
|
pub interpolation_filter: StdVideoAV1InterpolationFilter,
|
|
pub TxMode: StdVideoAV1TxMode,
|
|
pub delta_q_res: u8,
|
|
pub delta_lf_res: u8,
|
|
pub ref_order_hint: [u8; 8usize],
|
|
pub ref_frame_idx: [i8; 7usize],
|
|
pub reserved1: [u8; 3usize],
|
|
pub delta_frame_id_minus_1: [u32; 7usize],
|
|
pub pTileInfo: *const ash::vk::native::StdVideoAV1TileInfo,
|
|
pub pQuantization: *const ash::vk::native::StdVideoAV1Quantization,
|
|
pub pSegmentation: *const ash::vk::native::StdVideoAV1Segmentation,
|
|
pub pLoopFilter: *const ash::vk::native::StdVideoAV1LoopFilter,
|
|
pub pCDEF: *const ash::vk::native::StdVideoAV1CDEF,
|
|
pub pLoopRestoration: *const ash::vk::native::StdVideoAV1LoopRestoration,
|
|
pub pGlobalMotion: *const ash::vk::native::StdVideoAV1GlobalMotion,
|
|
pub pExtensionHeader: *const StdVideoEncodeAV1ExtensionHeader,
|
|
pub pBufferRemovalTimes: *const u32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct StdVideoEncodeAV1ReferenceInfoFlags {
|
|
pub _bitfield_align_1: [u32; 0],
|
|
pub _bitfield_1: __BindgenBitfieldUnit<[u8; 4usize]>,
|
|
}
|
|
impl StdVideoEncodeAV1ReferenceInfoFlags {
|
|
#[inline]
|
|
pub fn set_disable_frame_end_update_cdf(&mut self, val: u32) {
|
|
self._bitfield_1.set(0, 1, val as u64)
|
|
}
|
|
#[inline]
|
|
pub fn set_segmentation_enabled(&mut self, val: u32) {
|
|
self._bitfield_1.set(1, 1, val as u64)
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct StdVideoEncodeAV1ReferenceInfo {
|
|
pub flags: StdVideoEncodeAV1ReferenceInfoFlags,
|
|
pub RefFrameId: u32,
|
|
pub frame_type: StdVideoAV1FrameType,
|
|
pub OrderHint: u8,
|
|
pub reserved1: [u8; 3usize],
|
|
pub pExtensionHeader: *const StdVideoEncodeAV1ExtensionHeader,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct StdVideoEncodeAV1ExtensionHeader {
|
|
pub temporal_id: u8,
|
|
pub spatial_id: u8,
|
|
}
|
|
|
|
// ---------- KHR extension structs (repr(C); lifetimes/PhantomData dropped — layout-identical,
|
|
// chained by raw p_next). Flag/enum newtypes flattened to their u32/i32 repr. ----------
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct VideoEncodeAV1ProfileInfoKHR {
|
|
pub s_type: vk::StructureType,
|
|
pub p_next: *const c_void,
|
|
pub std_profile: StdVideoAV1Profile,
|
|
}
|
|
|
|
/// `VkPhysicalDeviceVideoEncodeAV1FeaturesKHR` — the `videoEncodeAV1` feature MUST be enabled at
|
|
/// device creation for any `VK_VIDEO_CODEC_OPERATION_ENCODE_AV1` use (a spec requirement RADV may
|
|
/// tolerate omitting but validation layers and stricter drivers do not).
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct PhysicalDeviceVideoEncodeAV1FeaturesKHR {
|
|
pub s_type: vk::StructureType,
|
|
pub p_next: *mut c_void,
|
|
pub video_encode_av1: vk::Bool32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct VideoEncodeAV1CapabilitiesKHR {
|
|
pub s_type: vk::StructureType,
|
|
pub p_next: *mut c_void,
|
|
pub flags: u32,
|
|
pub max_level: StdVideoAV1Level,
|
|
pub coded_picture_alignment: vk::Extent2D,
|
|
pub max_tiles: vk::Extent2D,
|
|
pub min_tile_size: vk::Extent2D,
|
|
pub max_tile_size: vk::Extent2D,
|
|
pub superblock_sizes: u32,
|
|
pub max_single_reference_count: u32,
|
|
pub single_reference_name_mask: u32,
|
|
pub max_unidirectional_compound_reference_count: u32,
|
|
pub max_unidirectional_compound_group1_reference_count: u32,
|
|
pub unidirectional_compound_reference_name_mask: u32,
|
|
pub max_bidirectional_compound_reference_count: u32,
|
|
pub max_bidirectional_compound_group1_reference_count: u32,
|
|
pub max_bidirectional_compound_group2_reference_count: u32,
|
|
pub bidirectional_compound_reference_name_mask: u32,
|
|
pub max_temporal_layer_count: u32,
|
|
pub max_spatial_layer_count: u32,
|
|
pub max_operating_points: u32,
|
|
pub min_q_index: u32,
|
|
pub max_q_index: u32,
|
|
pub prefers_gop_remaining_frames: vk::Bool32,
|
|
pub requires_gop_remaining_frames: vk::Bool32,
|
|
pub std_syntax_flags: u32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct VideoEncodeAV1SessionParametersCreateInfoKHR {
|
|
pub s_type: vk::StructureType,
|
|
pub p_next: *const c_void,
|
|
pub p_std_sequence_header: *const StdVideoAV1SequenceHeader,
|
|
pub p_std_decoder_model_info: *const c_void,
|
|
pub std_operating_point_count: u32,
|
|
pub p_std_operating_points: *const c_void,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct VideoEncodeAV1PictureInfoKHR {
|
|
pub s_type: vk::StructureType,
|
|
pub p_next: *const c_void,
|
|
pub prediction_mode: i32,
|
|
pub rate_control_group: i32,
|
|
pub constant_q_index: u32,
|
|
pub p_std_picture_info: *const StdVideoEncodeAV1PictureInfo,
|
|
pub reference_name_slot_indices: [i32; MAX_VIDEO_AV1_REFERENCES_PER_FRAME],
|
|
pub primary_reference_cdf_only: vk::Bool32,
|
|
pub generate_obu_extension_header: vk::Bool32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct VideoEncodeAV1DpbSlotInfoKHR {
|
|
pub s_type: vk::StructureType,
|
|
pub p_next: *const c_void,
|
|
pub p_std_reference_info: *const StdVideoEncodeAV1ReferenceInfo,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct VideoEncodeAV1RateControlInfoKHR {
|
|
pub s_type: vk::StructureType,
|
|
pub p_next: *const c_void,
|
|
pub flags: u32,
|
|
pub gop_frame_count: u32,
|
|
pub key_frame_period: u32,
|
|
pub consecutive_bipredictive_frame_count: u32,
|
|
pub temporal_layer_count: u32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct VideoEncodeAV1QIndexKHR {
|
|
pub intra_q_index: u32,
|
|
pub predictive_q_index: u32,
|
|
pub bipredictive_q_index: u32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct VideoEncodeAV1FrameSizeKHR {
|
|
pub intra_frame_size: u32,
|
|
pub predictive_frame_size: u32,
|
|
pub bipredictive_frame_size: u32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct VideoEncodeAV1RateControlLayerInfoKHR {
|
|
pub s_type: vk::StructureType,
|
|
pub p_next: *const c_void,
|
|
pub use_min_q_index: vk::Bool32,
|
|
pub min_q_index: VideoEncodeAV1QIndexKHR,
|
|
pub use_max_q_index: vk::Bool32,
|
|
pub max_q_index: VideoEncodeAV1QIndexKHR,
|
|
pub use_max_frame_size: vk::Bool32,
|
|
pub max_frame_size: VideoEncodeAV1FrameSizeKHR,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct VideoEncodeAV1GopRemainingFrameInfoKHR {
|
|
pub s_type: vk::StructureType,
|
|
pub p_next: *const c_void,
|
|
pub use_gop_remaining_frames: vk::Bool32,
|
|
pub gop_remaining_intra: u32,
|
|
pub gop_remaining_predictive: u32,
|
|
pub gop_remaining_bipredictive: u32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct VideoEncodeAV1SessionCreateInfoKHR {
|
|
pub s_type: vk::StructureType,
|
|
pub p_next: *const c_void,
|
|
pub use_max_level: vk::Bool32,
|
|
pub max_level: StdVideoAV1Level,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct StdVideoEncodeAV1OperatingPointInfoFlags {
|
|
pub _bitfield_align_1: [u32; 0],
|
|
pub _bitfield_1: __BindgenBitfieldUnit<[u8; 4usize]>,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone)]
|
|
pub struct StdVideoEncodeAV1OperatingPointInfo {
|
|
pub flags: StdVideoEncodeAV1OperatingPointInfoFlags,
|
|
pub operating_point_idc: u16,
|
|
pub seq_level_idx: u8,
|
|
pub seq_tier: u8,
|
|
pub decoder_buffer_delay: u32,
|
|
pub encoder_buffer_delay: u32,
|
|
pub initial_display_delay_minus_1: u8,
|
|
}
|
|
|
|
/// `vk::StructureType` for a raw `ST_*` constant above.
|
|
#[inline]
|
|
pub fn stype(raw: i32) -> vk::StructureType {
|
|
vk::StructureType::from_raw(raw)
|
|
}
|
|
|
|
// ---------- ABI layout guard ----------
|
|
//
|
|
// These structs are hand-copied and handed to the driver through raw `p_next` chains, so nothing in
|
|
// the type system relates them to the C definitions any more: an edit that inserts, drops, widens or
|
|
// re-pads a field is not a compile error, it is the driver reading our bytes at the wrong offsets.
|
|
// The assertions below are the missing compile error. They are `const` rather than `#[cfg(test)]`
|
|
// (the shape `amf.rs` uses) so they hold in every build, including the shipped one, and on any
|
|
// target this module compiles for.
|
|
//
|
|
// What they catch: a changed field width, an inserted or removed field, a changed array length, a
|
|
// padding assumption that only holds on one target. What they CANNOT catch: swapping two fields of
|
|
// the same type — offsets are unchanged. That case is only caught by reading the registry, so the
|
|
// field order here was diffed against `vulkan_core.h` and `vk_video/vulkan_video_codec_av1std_encode.h`
|
|
// (Vulkan-Headers `main`, 2026-07-25) when these assertions were written, along with every `ST_*`,
|
|
// flag-bit and enum value above; the bitfield member order is pinned by the test module below.
|
|
//
|
|
// Deliberately duplicated in `vk_valve_rgb.rs` rather than shared: both modules exist to be deleted
|
|
// wholesale once `ash` ships these bindings, and a shared helper would make deleting one break the
|
|
// other.
|
|
macro_rules! assert_abi_layout {
|
|
($t:ty { size: $size:expr, align: $align:expr $(, $field:ident @ $off:expr)* $(,)? }) => {
|
|
const _: () = {
|
|
assert!(
|
|
::core::mem::size_of::<$t>() == $size,
|
|
concat!(stringify!($t), ": size does not match the C ABI")
|
|
);
|
|
assert!(
|
|
::core::mem::align_of::<$t>() == $align,
|
|
concat!(stringify!($t), ": alignment does not match the C ABI")
|
|
);
|
|
$(assert!(
|
|
::core::mem::offset_of!($t, $field) == $off,
|
|
concat!(stringify!($t), ".", stringify!($field), ": offset does not match the C ABI")
|
|
);)*
|
|
};
|
|
};
|
|
}
|
|
|
|
// Std encode structs. The three `*Flags` types are a single C `uint32_t` of bitfields, so only their
|
|
// size and alignment are layout-checkable here; their member order is covered by `abi_tests`.
|
|
assert_abi_layout!(StdVideoEncodeAV1PictureInfoFlags { size: 4, align: 4 });
|
|
|
|
assert_abi_layout!(StdVideoEncodeAV1PictureInfo {
|
|
size: 152, align: 8,
|
|
flags @ 0,
|
|
frame_type @ 4,
|
|
frame_presentation_time @ 8,
|
|
current_frame_id @ 12,
|
|
order_hint @ 16,
|
|
primary_ref_frame @ 17,
|
|
refresh_frame_flags @ 18,
|
|
coded_denom @ 19,
|
|
render_width_minus_1 @ 20,
|
|
render_height_minus_1 @ 22,
|
|
interpolation_filter @ 24,
|
|
TxMode @ 28,
|
|
delta_q_res @ 32,
|
|
delta_lf_res @ 33,
|
|
ref_order_hint @ 34,
|
|
ref_frame_idx @ 42,
|
|
reserved1 @ 49,
|
|
delta_frame_id_minus_1 @ 52,
|
|
pTileInfo @ 80,
|
|
pQuantization @ 88,
|
|
pSegmentation @ 96,
|
|
pLoopFilter @ 104,
|
|
pCDEF @ 112,
|
|
pLoopRestoration @ 120,
|
|
pGlobalMotion @ 128,
|
|
pExtensionHeader @ 136,
|
|
pBufferRemovalTimes @ 144,
|
|
});
|
|
|
|
assert_abi_layout!(StdVideoEncodeAV1ReferenceInfoFlags { size: 4, align: 4 });
|
|
|
|
assert_abi_layout!(StdVideoEncodeAV1ReferenceInfo {
|
|
size: 24, align: 8,
|
|
flags @ 0,
|
|
RefFrameId @ 4,
|
|
frame_type @ 8,
|
|
OrderHint @ 12,
|
|
reserved1 @ 13,
|
|
pExtensionHeader @ 16,
|
|
});
|
|
|
|
assert_abi_layout!(StdVideoEncodeAV1ExtensionHeader {
|
|
size: 2, align: 1,
|
|
temporal_id @ 0,
|
|
spatial_id @ 1,
|
|
});
|
|
|
|
assert_abi_layout!(StdVideoEncodeAV1OperatingPointInfoFlags { size: 4, align: 4 });
|
|
|
|
assert_abi_layout!(StdVideoEncodeAV1OperatingPointInfo {
|
|
size: 20, align: 4,
|
|
flags @ 0,
|
|
operating_point_idc @ 4,
|
|
seq_level_idx @ 6,
|
|
seq_tier @ 7,
|
|
decoder_buffer_delay @ 8,
|
|
encoder_buffer_delay @ 12,
|
|
initial_display_delay_minus_1 @ 16,
|
|
});
|
|
|
|
// KHR extension structs.
|
|
assert_abi_layout!(VideoEncodeAV1ProfileInfoKHR {
|
|
size: 24, align: 8,
|
|
s_type @ 0,
|
|
p_next @ 8,
|
|
std_profile @ 16,
|
|
});
|
|
|
|
assert_abi_layout!(PhysicalDeviceVideoEncodeAV1FeaturesKHR {
|
|
size: 24, align: 8,
|
|
s_type @ 0,
|
|
p_next @ 8,
|
|
video_encode_av1 @ 16,
|
|
});
|
|
|
|
assert_abi_layout!(VideoEncodeAV1CapabilitiesKHR {
|
|
size: 128, align: 8,
|
|
s_type @ 0,
|
|
p_next @ 8,
|
|
flags @ 16,
|
|
max_level @ 20,
|
|
coded_picture_alignment @ 24,
|
|
max_tiles @ 32,
|
|
min_tile_size @ 40,
|
|
max_tile_size @ 48,
|
|
superblock_sizes @ 56,
|
|
max_single_reference_count @ 60,
|
|
single_reference_name_mask @ 64,
|
|
max_unidirectional_compound_reference_count @ 68,
|
|
max_unidirectional_compound_group1_reference_count @ 72,
|
|
unidirectional_compound_reference_name_mask @ 76,
|
|
max_bidirectional_compound_reference_count @ 80,
|
|
max_bidirectional_compound_group1_reference_count @ 84,
|
|
max_bidirectional_compound_group2_reference_count @ 88,
|
|
bidirectional_compound_reference_name_mask @ 92,
|
|
max_temporal_layer_count @ 96,
|
|
max_spatial_layer_count @ 100,
|
|
max_operating_points @ 104,
|
|
min_q_index @ 108,
|
|
max_q_index @ 112,
|
|
prefers_gop_remaining_frames @ 116,
|
|
requires_gop_remaining_frames @ 120,
|
|
std_syntax_flags @ 124,
|
|
});
|
|
|
|
assert_abi_layout!(VideoEncodeAV1SessionParametersCreateInfoKHR {
|
|
size: 48, align: 8,
|
|
s_type @ 0,
|
|
p_next @ 8,
|
|
p_std_sequence_header @ 16,
|
|
p_std_decoder_model_info @ 24,
|
|
std_operating_point_count @ 32,
|
|
p_std_operating_points @ 40,
|
|
});
|
|
|
|
assert_abi_layout!(VideoEncodeAV1PictureInfoKHR {
|
|
size: 80, align: 8,
|
|
s_type @ 0,
|
|
p_next @ 8,
|
|
prediction_mode @ 16,
|
|
rate_control_group @ 20,
|
|
constant_q_index @ 24,
|
|
p_std_picture_info @ 32,
|
|
reference_name_slot_indices @ 40,
|
|
primary_reference_cdf_only @ 68,
|
|
generate_obu_extension_header @ 72,
|
|
});
|
|
|
|
assert_abi_layout!(VideoEncodeAV1DpbSlotInfoKHR {
|
|
size: 24, align: 8,
|
|
s_type @ 0,
|
|
p_next @ 8,
|
|
p_std_reference_info @ 16,
|
|
});
|
|
|
|
assert_abi_layout!(VideoEncodeAV1RateControlInfoKHR {
|
|
size: 40, align: 8,
|
|
s_type @ 0,
|
|
p_next @ 8,
|
|
flags @ 16,
|
|
gop_frame_count @ 20,
|
|
key_frame_period @ 24,
|
|
consecutive_bipredictive_frame_count @ 28,
|
|
temporal_layer_count @ 32,
|
|
});
|
|
|
|
assert_abi_layout!(VideoEncodeAV1QIndexKHR {
|
|
size: 12, align: 4,
|
|
intra_q_index @ 0,
|
|
predictive_q_index @ 4,
|
|
bipredictive_q_index @ 8,
|
|
});
|
|
|
|
assert_abi_layout!(VideoEncodeAV1FrameSizeKHR {
|
|
size: 12, align: 4,
|
|
intra_frame_size @ 0,
|
|
predictive_frame_size @ 4,
|
|
bipredictive_frame_size @ 8,
|
|
});
|
|
|
|
assert_abi_layout!(VideoEncodeAV1RateControlLayerInfoKHR {
|
|
size: 64, align: 8,
|
|
s_type @ 0,
|
|
p_next @ 8,
|
|
use_min_q_index @ 16,
|
|
min_q_index @ 20,
|
|
use_max_q_index @ 32,
|
|
max_q_index @ 36,
|
|
use_max_frame_size @ 48,
|
|
max_frame_size @ 52,
|
|
});
|
|
|
|
assert_abi_layout!(VideoEncodeAV1GopRemainingFrameInfoKHR {
|
|
size: 32, align: 8,
|
|
s_type @ 0,
|
|
p_next @ 8,
|
|
use_gop_remaining_frames @ 16,
|
|
gop_remaining_intra @ 20,
|
|
gop_remaining_predictive @ 24,
|
|
gop_remaining_bipredictive @ 28,
|
|
});
|
|
|
|
assert_abi_layout!(VideoEncodeAV1SessionCreateInfoKHR {
|
|
size: 24, align: 8,
|
|
s_type @ 0,
|
|
p_next @ 8,
|
|
use_max_level @ 16,
|
|
max_level @ 20,
|
|
});
|
|
|
|
#[cfg(test)]
|
|
mod abi_tests {
|
|
use super::*;
|
|
|
|
/// Assert that `set` lights exactly one bit of the flags word, at `bit`.
|
|
fn sets_only_bit(
|
|
storage: __BindgenBitfieldUnit<[u8; 4]>,
|
|
name: &str,
|
|
bit: usize,
|
|
expected_width: usize,
|
|
) {
|
|
for probe in 0..32 {
|
|
let want = probe >= bit && probe < bit + expected_width;
|
|
assert_eq!(
|
|
storage.get_bit(probe),
|
|
want,
|
|
"`{name}` should occupy bit(s) {bit}..{}, but bit {probe} disagrees",
|
|
bit + expected_width
|
|
);
|
|
}
|
|
}
|
|
|
|
/// A C bitfield allocates its members from bit 0 upward in declaration order, so the setter for
|
|
/// the Nth member of `StdVideoEncodeAV1PictureInfoFlags` must write bit N. The array below is
|
|
/// the member list of `vulkan_video_codec_av1std_encode.h` **in declaration order** — so this
|
|
/// pins the hand-copied bit indices to the header rather than merely to themselves. A silent
|
|
/// renumbering here would make the driver read, say, `use_superres` where we meant
|
|
/// `render_and_frame_size_different`.
|
|
#[test]
|
|
fn picture_info_flag_setters_follow_the_c_declaration_order() {
|
|
#[allow(clippy::type_complexity)]
|
|
let members: [(&str, fn(&mut StdVideoEncodeAV1PictureInfoFlags)); 29] = [
|
|
("error_resilient_mode", |f| f.set_error_resilient_mode(1)),
|
|
("disable_cdf_update", |f| f.set_disable_cdf_update(1)),
|
|
("use_superres", |f| f.set_use_superres(1)),
|
|
("render_and_frame_size_different", |f| {
|
|
f.set_render_and_frame_size_different(1)
|
|
}),
|
|
("allow_screen_content_tools", |f| {
|
|
f.set_allow_screen_content_tools(1)
|
|
}),
|
|
("is_filter_switchable", |f| f.set_is_filter_switchable(1)),
|
|
("force_integer_mv", |f| f.set_force_integer_mv(1)),
|
|
("frame_size_override_flag", |f| {
|
|
f.set_frame_size_override_flag(1)
|
|
}),
|
|
("buffer_removal_time_present_flag", |f| {
|
|
f.set_buffer_removal_time_present_flag(1)
|
|
}),
|
|
("allow_intrabc", |f| f.set_allow_intrabc(1)),
|
|
("frame_refs_short_signaling", |f| {
|
|
f.set_frame_refs_short_signaling(1)
|
|
}),
|
|
("allow_high_precision_mv", |f| {
|
|
f.set_allow_high_precision_mv(1)
|
|
}),
|
|
("is_motion_mode_switchable", |f| {
|
|
f.set_is_motion_mode_switchable(1)
|
|
}),
|
|
("use_ref_frame_mvs", |f| f.set_use_ref_frame_mvs(1)),
|
|
("disable_frame_end_update_cdf", |f| {
|
|
f.set_disable_frame_end_update_cdf(1)
|
|
}),
|
|
("allow_warped_motion", |f| f.set_allow_warped_motion(1)),
|
|
("reduced_tx_set", |f| f.set_reduced_tx_set(1)),
|
|
("skip_mode_present", |f| f.set_skip_mode_present(1)),
|
|
("delta_q_present", |f| f.set_delta_q_present(1)),
|
|
("delta_lf_present", |f| f.set_delta_lf_present(1)),
|
|
("delta_lf_multi", |f| f.set_delta_lf_multi(1)),
|
|
("segmentation_enabled", |f| f.set_segmentation_enabled(1)),
|
|
("segmentation_update_map", |f| {
|
|
f.set_segmentation_update_map(1)
|
|
}),
|
|
("segmentation_temporal_update", |f| {
|
|
f.set_segmentation_temporal_update(1)
|
|
}),
|
|
("segmentation_update_data", |f| {
|
|
f.set_segmentation_update_data(1)
|
|
}),
|
|
("UsesLr", |f| f.set_UsesLr(1)),
|
|
("usesChromaLr", |f| f.set_usesChromaLr(1)),
|
|
("show_frame", |f| f.set_show_frame(1)),
|
|
("showable_frame", |f| f.set_showable_frame(1)),
|
|
];
|
|
|
|
for (bit, (name, set)) in members.into_iter().enumerate() {
|
|
let mut flags = StdVideoEncodeAV1PictureInfoFlags {
|
|
_bitfield_align_1: [],
|
|
_bitfield_1: __BindgenBitfieldUnit::new([0u8; 4]),
|
|
};
|
|
set(&mut flags);
|
|
sets_only_bit(flags._bitfield_1, name, bit, 1);
|
|
}
|
|
}
|
|
|
|
/// `reserved : 3` closes out the word — bits 29..32. Checking it is what proves the 29 members
|
|
/// above are the *whole* list: a dropped member would shift `reserved` down and fail here.
|
|
#[test]
|
|
fn picture_info_reserved_occupies_the_top_three_bits() {
|
|
let mut flags = StdVideoEncodeAV1PictureInfoFlags {
|
|
_bitfield_align_1: [],
|
|
_bitfield_1: __BindgenBitfieldUnit::new([0u8; 4]),
|
|
};
|
|
flags.set_reserved(0b111);
|
|
sets_only_bit(flags._bitfield_1, "reserved", 29, 3);
|
|
}
|
|
|
|
/// The same invariant for the two-member reference-info flags word.
|
|
#[test]
|
|
fn reference_info_flag_setters_follow_the_c_declaration_order() {
|
|
#[allow(clippy::type_complexity)]
|
|
let members: [(&str, fn(&mut StdVideoEncodeAV1ReferenceInfoFlags)); 2] = [
|
|
("disable_frame_end_update_cdf", |f| {
|
|
f.set_disable_frame_end_update_cdf(1)
|
|
}),
|
|
("segmentation_enabled", |f| f.set_segmentation_enabled(1)),
|
|
];
|
|
|
|
for (bit, (name, set)) in members.into_iter().enumerate() {
|
|
let mut flags = StdVideoEncodeAV1ReferenceInfoFlags {
|
|
_bitfield_align_1: [],
|
|
_bitfield_1: __BindgenBitfieldUnit::new([0u8; 4]),
|
|
};
|
|
set(&mut flags);
|
|
sets_only_bit(flags._bitfield_1, name, bit, 1);
|
|
}
|
|
}
|
|
}
|