Skip to content

Commit c488418

Browse files
authored
Merge pull request #18 from DatCaptainHorse/feat/cleaner-pnext
feat: Cleaner p_next handling
2 parents d33166e + 5984a2c commit c488418

17 files changed

Lines changed: 395 additions & 454 deletions

‎README.md‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,9 @@ A Vulkan-based video encoding library for Rust, supporting H.264, H.265, and AV1
2424

2525
| Codec | Encode |
2626
|-------|--------|
27-
| H.264/AVC | ✓ |
27+
| H.264/AVC | ✓ |
2828
| H.265/HEVC | ✓ |
29-
| AV1 | ✓ (experimental) |
30-
31-
> ⚠️ **AV1 Warning**: AV1 encoding is experimental. On NVIDIA GPUs, P-frames cannot
32-
> reference other P-frames, causing all P-frames to reference the I-frame instead. This
33-
> leads to progressively larger frame sizes over time. Consider using H.264 or HEVC
34-
> until this is resolved.
29+
| AV1 | ✓ |
3530

3631
## Requirements
3732

‎examples/query_capabilities.rs‎

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! from the Vulkan video extensions.
55
66
use ash::vk;
7+
use ash::vk::TaggedStructure;
78
use pixelforge::{Codec, VideoContextBuilder};
89
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
910

@@ -37,7 +38,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
3738
println!("Codec Support:");
3839
println!("--------------");
3940

40-
let codecs = [Codec::H264, Codec::H265];
41+
let codecs = [Codec::H264, Codec::H265, Codec::AV1];
4142

4243
for codec in codecs {
4344
println!("\n{:?}:", codec);
@@ -95,7 +96,7 @@ fn query_detailed_capabilities(
9596
println!(" Checking {}: ", desc);
9697

9798
// Construct profile info
98-
let (mut profile_info, mut h264_profile, mut h265_profile) = match codec {
99+
let (mut profile_info, mut h264_profile, mut h265_profile, mut av1_profile) = match codec {
99100
Codec::H264 => {
100101
let profile_idc = if subsampling == vk::VideoChromaSubsamplingFlagsKHR::TYPE_444 {
101102
ash::vk::native::StdVideoH264ProfileIdc_STD_VIDEO_H264_PROFILE_IDC_HIGH_444_PREDICTIVE
@@ -110,7 +111,7 @@ fn query_detailed_capabilities(
110111
.chroma_subsampling(subsampling)
111112
.luma_bit_depth(bit_depth)
112113
.chroma_bit_depth(bit_depth);
113-
(info, Some(h264), None)
114+
(info, Some(h264), None, None)
114115
}
115116
Codec::H265 => {
116117
let profile_idc = if subsampling == vk::VideoChromaSubsamplingFlagsKHR::TYPE_444 {
@@ -128,30 +129,46 @@ fn query_detailed_capabilities(
128129
.chroma_subsampling(subsampling)
129130
.luma_bit_depth(bit_depth)
130131
.chroma_bit_depth(bit_depth);
131-
(info, None, Some(h265))
132+
(info, None, Some(h265), None)
133+
}
134+
Codec::AV1 => {
135+
let profile = if subsampling == vk::VideoChromaSubsamplingFlagsKHR::TYPE_444 {
136+
ash::vk::native::StdVideoAV1Profile_STD_VIDEO_AV1_PROFILE_HIGH
137+
} else {
138+
ash::vk::native::StdVideoAV1Profile_STD_VIDEO_AV1_PROFILE_MAIN
139+
};
140+
141+
let av1 = vk::VideoEncodeAV1ProfileInfoKHR::default().std_profile(profile);
142+
let info = vk::VideoProfileInfoKHR::default()
143+
.video_codec_operation(vk::VideoCodecOperationFlagsKHR::ENCODE_AV1)
144+
.chroma_subsampling(subsampling)
145+
.luma_bit_depth(bit_depth)
146+
.chroma_bit_depth(bit_depth);
147+
(info, None, None, Some(av1))
132148
}
133-
_ => return Ok(()),
134149
};
135150

136151
if let Some(h264) = &mut h264_profile {
137-
profile_info.p_next = (h264 as *mut vk::VideoEncodeH264ProfileInfoKHR).cast();
152+
profile_info = profile_info.push(h264);
138153
}
139154
if let Some(h265) = &mut h265_profile {
140-
profile_info.p_next = (h265 as *mut vk::VideoEncodeH265ProfileInfoKHR).cast();
155+
profile_info = profile_info.push(h265);
156+
}
157+
if let Some(av1) = &mut av1_profile {
158+
profile_info = profile_info.push(av1);
141159
}
142160

143161
// 1. Query Video Capabilities
144-
let mut caps = vk::VideoCapabilitiesKHR::default();
145162
let mut encode_caps = vk::VideoEncodeCapabilitiesKHR::default();
146-
caps.p_next = (&mut encode_caps as *mut vk::VideoEncodeCapabilitiesKHR).cast();
163+
let mut caps = vk::VideoCapabilitiesKHR::default().push(&mut encode_caps);
147164

148165
let mut h264_caps = vk::VideoEncodeH264CapabilitiesKHR::default();
149166
let mut h265_caps = vk::VideoEncodeH265CapabilitiesKHR::default();
150-
151-
if codec == Codec::H264 {
152-
encode_caps.p_next = (&mut h264_caps as *mut vk::VideoEncodeH264CapabilitiesKHR).cast();
153-
} else if codec == Codec::H265 {
154-
encode_caps.p_next = (&mut h265_caps as *mut vk::VideoEncodeH265CapabilitiesKHR).cast();
167+
let mut av1_caps = vk::VideoEncodeAV1CapabilitiesKHR::default();
168+
match codec {
169+
Codec::H264 => caps = caps.push(&mut h264_caps),
170+
Codec::H265 => caps = caps.push(&mut h265_caps),
171+
Codec::AV1 => caps = caps.push(&mut av1_caps),
155172
}
156173

157174
let result = unsafe {
@@ -185,9 +202,9 @@ fn query_detailed_capabilities(
185202
vk::VideoProfileListInfoKHR::default().profiles(std::slice::from_ref(&profile_info));
186203

187204
// Check for Input Image support (VIDEO_ENCODE_SRC_KHR)
188-
let mut format_info = vk::PhysicalDeviceVideoFormatInfoKHR::default()
189-
.image_usage(vk::ImageUsageFlags::VIDEO_ENCODE_SRC_KHR);
190-
format_info.p_next = (&mut format_props_list as *mut vk::VideoProfileListInfoKHR).cast();
205+
let format_info = vk::PhysicalDeviceVideoFormatInfoKHR::default()
206+
.image_usage(vk::ImageUsageFlags::VIDEO_ENCODE_SRC_KHR)
207+
.push(&mut format_props_list);
191208

192209
let result = unsafe {
193210
(video_queue_fn
@@ -220,9 +237,9 @@ fn query_detailed_capabilities(
220237
}
221238

222239
// Check for DPB Image support (VIDEO_ENCODE_DPB_KHR)
223-
let mut format_info = vk::PhysicalDeviceVideoFormatInfoKHR::default()
224-
.image_usage(vk::ImageUsageFlags::VIDEO_ENCODE_DPB_KHR);
225-
format_info.p_next = (&mut format_props_list as *mut vk::VideoProfileListInfoKHR).cast();
240+
let format_info = vk::PhysicalDeviceVideoFormatInfoKHR::default()
241+
.image_usage(vk::ImageUsageFlags::VIDEO_ENCODE_DPB_KHR)
242+
.push(&mut format_props_list);
226243

227244
let mut format_props_count = 0;
228245
let result = unsafe {

‎src/converter/pipeline.rs‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::encoder::resources::find_memory_type;
88
use crate::error::{PixelForgeError, Result};
99
use crate::vulkan::VideoContext;
1010
use ash::vk;
11+
use ash::vk::TaggedStructure;
1112

1213
/// Precompiled SPIR-V bytecode for the color conversion compute shader.
1314
const COLOR_CONVERT_SPIRV_BYTES: &[u8] = include_bytes!("../../shader/color_convert.spv");
@@ -153,10 +154,7 @@ pub fn create_converter(
153154
// runtime descriptor population. The correct sizes for in-buffer
154155
// descriptors are the regular `*_descriptor_size` fields.
155156
let mut db_props = vk::PhysicalDeviceDescriptorBufferPropertiesEXT::default();
156-
let mut props = vk::PhysicalDeviceProperties2 {
157-
p_next: &mut db_props as *mut _ as *mut _,
158-
..Default::default()
159-
};
157+
let mut props = vk::PhysicalDeviceProperties2::default().push(&mut db_props);
160158
unsafe {
161159
instance.get_physical_device_properties2(physical_device, &mut props);
162160
}

‎src/encoder/av1/api.rs‎

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,6 @@ impl AV1Encoder {
6969
self.order_hint = 0;
7070
// Reset references for key frames.
7171
self.references.clear();
72-
// Reset DPB slot activation tracking on key frame - all slots become inactive.
73-
for active in &mut self.dpb_slot_active {
74-
*active = false;
75-
}
7672
}
7773

7874
let mut encoded_data = Vec::new();
@@ -107,29 +103,42 @@ impl AV1Encoder {
107103
self.frame_num += 1;
108104
self.order_hint = (self.order_hint + 1) & 0xFF; // 8-bit order hint
109105

110-
// Only KEY frames are stored as references. P frames all reference the KEY frame
111-
// and don't update any reference buffer, avoiding P→P which produces corrupt output
112-
// on NVIDIA AV1 encoders.
106+
// Update reference frames and DPB slot management
107+
// Key frames refresh all reference slots. Inter frames refresh only their own slot,
108+
// becoming the new LAST_FRAME for the next inter frame
109+
let ref_info = super::ReferenceInfo {
110+
dpb_slot: self.current_dpb_slot,
111+
order_hint: encoded_order_hint,
112+
frame_type: if is_key_frame {
113+
ash::vk::native::StdVideoAV1FrameType_STD_VIDEO_AV1_FRAME_TYPE_KEY
114+
} else {
115+
ash::vk::native::StdVideoAV1FrameType_STD_VIDEO_AV1_FRAME_TYPE_INTER
116+
},
117+
};
118+
119+
// Store the encoded frame as the most recent reference
113120
if is_key_frame {
114-
let ref_info = super::ReferenceInfo {
115-
dpb_slot: self.current_dpb_slot,
116-
order_hint: encoded_order_hint,
117-
frame_type: ash::vk::native::StdVideoAV1FrameType_STD_VIDEO_AV1_FRAME_TYPE_KEY,
118-
};
119121
self.references.clear();
120-
self.references.push(ref_info);
122+
}
123+
self.references.insert(0, ref_info);
124+
// Keep only the most recent reference for single-reference prediction
125+
self.references.truncate(1);
126+
127+
// Cycle to next available DPB slot for the next frame
128+
let used_slots: Vec<u8> = self.references.iter().map(|r| r.dpb_slot).collect();
129+
let mut next_slot = (self.current_dpb_slot + 1) % self.dpb_slot_count as u8;
121130

122-
// KEY frame uses the current DPB slot; pick a different one for P frames.
123-
let used_slots: Vec<u8> = self.references.iter().map(|r| r.dpb_slot).collect();
131+
// If the next slot is in use, find the first available slot
132+
if used_slots.contains(&next_slot) {
124133
for i in 0..self.dpb_slot_count as u8 {
125134
if !used_slots.contains(&i) {
126-
self.current_dpb_slot = i;
135+
next_slot = i;
127136
break;
128137
}
129138
}
130139
}
131-
// P frames reuse the same scratch DPB slot (current_dpb_slot stays unchanged
132-
// between P frames since it's always different from the KEY frame's slot).
140+
141+
self.current_dpb_slot = next_slot;
133142

134143
Ok(EncodedPacket {
135144
data: encoded_data,

0 commit comments

Comments
 (0)