#![allow(unsafe_code)]
#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
reason = "Vulkan FFI: every count/size here is driver-reported and small \
(single digits to low thousands — queue families, DPB slots, \
memory requirement counts, one coded picture's byte size); casts \
mirror ash's own generated builder code (e.g. `.len() as _`)."
)]
#![allow(
clippy::redundant_pub_crate,
reason = "workspace `unreachable_pub` policy (Cargo.toml) wants `pub(crate)` here; \
clippy::pedantic's redundant_pub_crate disagrees for private modules — the \
two lints are mutually exclusive for this shape, workspace policy wins"
)]
use vulkanalia::vk;
use vulkanalia::vk::video as native;
use vulkanalia::vk::{
DeviceV1_0, HasBuilder, KhrVideoEncodeQueueExtensionDeviceCommands,
KhrVideoQueueExtensionDeviceCommands,
};
use crate::vulkan::hevc_params;
use crate::vulkan::session::{EncodeDevice, SessionResources, VulkanEncodeSessionError};
use crate::vulkan::session_command::{record_upload_and_barriers, submit_and_readback};
pub(crate) struct DpbRecordParamsHevc {
pub(crate) layer_count: u32,
pub(crate) transition: bool,
pub(crate) setup_slot: i32,
pub(crate) setup_reference_info: Option<native::StdVideoEncodeH265ReferenceInfo>,
pub(crate) reference: Option<(i32, native::StdVideoEncodeH265ReferenceInfo)>,
}
pub(crate) struct RecordParamsHevc<'a> {
pub(crate) command_buffer: vk::CommandBuffer,
pub(crate) coded_extent: vk::Extent2D,
pub(crate) dst_size: vk::DeviceSize,
pub(crate) picture_info_pnext: &'a mut vk::VideoEncodeH265PictureInfoKHR,
pub(crate) dpb: DpbRecordParamsHevc,
}
pub(crate) fn record_and_submit_hevc(
encode_device: &EncodeDevice<'_>,
resources: &mut SessionResources,
params: &mut RecordParamsHevc<'_>,
) -> Result<Vec<u8>, VulkanEncodeSessionError> {
let device = encode_device.device;
let command_buffer = params.command_buffer;
unsafe { device.reset_command_buffer(command_buffer, vk::CommandBufferResetFlags::empty()) }
.map_err(|result| VulkanEncodeSessionError::VkCall {
call: "vkResetCommandBuffer",
result,
})?;
let begin_info =
vk::CommandBufferBeginInfo::builder().flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
unsafe { device.begin_command_buffer(command_buffer, &begin_info) }.map_err(|result| {
VulkanEncodeSessionError::VkCall {
call: "vkBeginCommandBuffer",
result,
}
})?;
record_upload_and_barriers(
device,
resources,
command_buffer,
params.coded_extent,
params.dst_size,
params.dpb.layer_count,
params.dpb.transition,
);
record_video_coding_hevc(encode_device, resources, params, command_buffer);
unsafe { device.end_command_buffer(command_buffer) }.map_err(|result| {
VulkanEncodeSessionError::VkCall {
call: "vkEndCommandBuffer",
result,
}
})?;
submit_and_readback(encode_device, resources, command_buffer, params.dst_size)
}
#[allow(
clippy::too_many_lines,
reason = "linear per-frame Vulkan Video struct construction — mirrors \
session_command::record_video_coding's own too_many_lines allow \
and reasoning (setup/reference slot resources, DPB-slot-info chains \
must all stay on this function's own stack frame for the raw pointers \
built from them to remain valid through the unsafe calls at the bottom)"
)]
fn record_video_coding_hevc(
encode_device: &EncodeDevice<'_>,
resources: &SessionResources,
params: &mut RecordParamsHevc<'_>,
command_buffer: vk::CommandBuffer,
) {
let device = encode_device.device;
let setup_resource = vk::VideoPictureResourceInfoKHR::builder()
.coded_offset(vk::Offset2D { x: 0, y: 0 })
.coded_extent(params.coded_extent)
.base_array_layer(params.dpb.setup_slot as u32)
.image_view_binding(resources.dpb_image_view);
let setup_reference_info = params
.dpb
.setup_reference_info
.unwrap_or_else(|| hevc_params::build_reference_info(0, true));
let mut setup_dpb_slot_info = vk::VideoEncodeH265DpbSlotInfoKHR::builder()
.std_reference_info(&setup_reference_info)
.build();
let setup_slot = if params.dpb.setup_reference_info.is_some() {
vk::VideoReferenceSlotInfoKHR::builder()
.slot_index(params.dpb.setup_slot)
.picture_resource(&setup_resource)
.push_next(&mut setup_dpb_slot_info)
} else {
vk::VideoReferenceSlotInfoKHR::builder()
.slot_index(params.dpb.setup_slot)
.picture_resource(&setup_resource)
};
let begin_setup_slot =
vk::VideoReferenceSlotInfoKHR::builder().slot_index(params.dpb.setup_slot);
let has_reference = params.dpb.reference.is_some();
let (reference_slot_index, reference_std_info) = params
.dpb
.reference
.unwrap_or_else(|| (0, hevc_params::build_reference_info(0, true)));
let reference_resource = vk::VideoPictureResourceInfoKHR::builder()
.coded_offset(vk::Offset2D { x: 0, y: 0 })
.coded_extent(params.coded_extent)
.base_array_layer(reference_slot_index as u32)
.image_view_binding(resources.dpb_image_view);
let mut begin_reference_dpb_slot_info = vk::VideoEncodeH265DpbSlotInfoKHR::builder()
.std_reference_info(&reference_std_info)
.build();
let mut encode_reference_dpb_slot_info = vk::VideoEncodeH265DpbSlotInfoKHR::builder()
.std_reference_info(&reference_std_info)
.build();
let begin_reference_slot = vk::VideoReferenceSlotInfoKHR::builder()
.slot_index(reference_slot_index)
.picture_resource(&reference_resource)
.push_next(&mut begin_reference_dpb_slot_info);
let encode_reference_slot = vk::VideoReferenceSlotInfoKHR::builder()
.slot_index(reference_slot_index)
.picture_resource(&reference_resource)
.push_next(&mut encode_reference_dpb_slot_info);
let begin_slots_array = [begin_setup_slot, begin_reference_slot];
let begin_slots = if has_reference {
&begin_slots_array[..]
} else {
&begin_slots_array[..1]
};
let encode_reference_slots_array = [encode_reference_slot];
unsafe {
device.cmd_reset_query_pool(command_buffer, resources.encode_feedback_query_pool, 0, 1);
}
let mut rate_control = vk::VideoEncodeRateControlInfoKHR::builder()
.rate_control_mode(vk::VideoEncodeRateControlModeFlagsKHR::DISABLED);
let begin_info = vk::VideoBeginCodingInfoKHR::builder()
.video_session(resources.session)
.video_session_parameters(resources.session_parameters)
.reference_slots(begin_slots)
.push_next(&mut rate_control);
unsafe {
device.cmd_begin_video_coding_khr(command_buffer, &begin_info);
}
let src_resource = vk::VideoPictureResourceInfoKHR::builder()
.coded_offset(vk::Offset2D { x: 0, y: 0 })
.coded_extent(params.coded_extent)
.base_array_layer(0)
.image_view_binding(resources.input_image_view);
let encode_info = if has_reference {
vk::VideoEncodeInfoKHR::builder()
.dst_buffer(resources.dst_buffer)
.dst_buffer_offset(0)
.dst_buffer_range(params.dst_size)
.src_picture_resource(src_resource)
.setup_reference_slot(&setup_slot)
.reference_slots(&encode_reference_slots_array)
.push_next(params.picture_info_pnext)
} else {
vk::VideoEncodeInfoKHR::builder()
.dst_buffer(resources.dst_buffer)
.dst_buffer_offset(0)
.dst_buffer_range(params.dst_size)
.src_picture_resource(src_resource)
.setup_reference_slot(&setup_slot)
.push_next(params.picture_info_pnext)
};
unsafe {
device.cmd_begin_query(
command_buffer,
resources.encode_feedback_query_pool,
0,
vk::QueryControlFlags::empty(),
);
}
unsafe {
device.cmd_encode_video_khr(command_buffer, &encode_info);
}
unsafe {
device.cmd_end_query(command_buffer, resources.encode_feedback_query_pool, 0);
}
let end_info = vk::VideoEndCodingInfoKHR::builder();
unsafe {
device.cmd_end_video_coding_khr(command_buffer, &end_info);
}
}