#![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 — casts mirror \
mediaway-encoder-vulkan::session_command's identical allow."
)]
#![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"
)]
#![allow(
clippy::too_many_lines,
reason = "linear per-frame command-recording sequence (barriers -> begin video coding -> \
decode -> end video coding -> submit), mirrors session_command_h264.rs's own \
record_and_submit shape"
)]
use vulkanalia::vk;
use vulkanalia::vk::video as native;
use vulkanalia::vk::{
DeviceV1_0, DeviceV1_3, HasBuilder, KhrVideoDecodeQueueExtensionDeviceCommands,
KhrVideoQueueExtensionDeviceCommands,
};
use crate::vulkan::dpb::DpbSlot;
use crate::vulkan::hevc_params::reference_info_from_slot;
use crate::vulkan::session::{DecodeDevice, VulkanDecodeError};
use crate::vulkan::session_command::{SessionResources, color_range_for_layer, submit_and_wait};
#[must_use]
#[allow(
clippy::too_many_arguments,
reason = "one real picture's full picture-info payload"
)]
pub(crate) fn build_picture_info(
sps_video_parameter_set_id: u8,
pps_seq_parameter_set_id: u8,
pps_pic_parameter_set_id: u8,
pic_order_cnt: i32,
is_idr: bool,
is_reference: bool,
ref_pic_set_st_curr_before: [u8; 8],
ref_pic_set_st_curr_after: [u8; 8],
) -> native::StdVideoDecodeH265PictureInfo {
let mut flags = native::StdVideoDecodeH265PictureInfoFlags {
_bitfield_align_1: [],
_bitfield_1: native::__BindgenBitfieldUnit::new([0u8; 1]),
__bindgen_padding_0: [0; 3],
};
flags.set_IrapPicFlag(u32::from(is_idr));
flags.set_IdrPicFlag(u32::from(is_idr));
flags.set_IsReference(u32::from(is_reference));
native::StdVideoDecodeH265PictureInfo {
flags,
sps_video_parameter_set_id,
pps_seq_parameter_set_id,
pps_pic_parameter_set_id,
NumDeltaPocsOfRefRpsIdx: 0,
PicOrderCntVal: pic_order_cnt,
NumBitsForSTRefPicSetInSlice: 0,
reserved: 0,
RefPicSetStCurrBefore: ref_pic_set_st_curr_before,
RefPicSetStCurrAfter: ref_pic_set_st_curr_after,
RefPicSetLtCurr: [0xFF; 8],
}
}
pub(crate) struct RecordParamsHevc<'a> {
pub(crate) command_buffer: vk::CommandBuffer,
pub(crate) coded_extent: vk::Extent2D,
pub(crate) bitstream_len: vk::DeviceSize,
pub(crate) dst_slot_index: u32,
pub(crate) reference_slots: &'a [(u32, DpbSlot)],
pub(crate) picture_info: &'a native::StdVideoDecodeH265PictureInfo,
}
fn reset_session_once(
decode_device: &DecodeDevice<'_>,
resources: &mut SessionResources,
command_buffer: vk::CommandBuffer,
) -> Result<(), VulkanDecodeError> {
if resources.session_reset {
return Ok(());
}
let device = decode_device.device;
unsafe { device.reset_command_buffer(command_buffer, vk::CommandBufferResetFlags::empty()) }
.map_err(|result| VulkanDecodeError::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| {
VulkanDecodeError::VkCall {
call: "vkBeginCommandBuffer",
result,
}
})?;
let begin_coding = vk::VideoBeginCodingInfoKHR::builder()
.video_session(resources.session)
.video_session_parameters(resources.session_parameters);
unsafe { device.cmd_begin_video_coding_khr(command_buffer, &begin_coding) };
let control_info =
vk::VideoCodingControlInfoKHR::builder().flags(vk::VideoCodingControlFlagsKHR::RESET);
unsafe { device.cmd_control_video_coding_khr(command_buffer, &control_info) };
let end_info = vk::VideoEndCodingInfoKHR::builder();
unsafe { device.cmd_end_video_coding_khr(command_buffer, &end_info) };
unsafe { device.end_command_buffer(command_buffer) }.map_err(|result| {
VulkanDecodeError::VkCall {
call: "vkEndCommandBuffer",
result,
}
})?;
submit_and_wait(decode_device, resources, command_buffer)?;
resources.session_reset = true;
Ok(())
}
pub(crate) fn record_and_submit_hevc(
decode_device: &DecodeDevice<'_>,
resources: &mut SessionResources,
params: &RecordParamsHevc<'_>,
) -> Result<(), VulkanDecodeError> {
let device = decode_device.device;
let command_buffer = params.command_buffer;
reset_session_once(decode_device, resources, command_buffer)?;
unsafe { device.reset_command_buffer(command_buffer, vk::CommandBufferResetFlags::empty()) }
.map_err(|result| VulkanDecodeError::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| {
VulkanDecodeError::VkCall {
call: "vkBeginCommandBuffer",
result,
}
})?;
let all_commands = vk::PipelineStageFlags2::ALL_COMMANDS;
let memory_rw = vk::AccessFlags2::MEMORY_READ | vk::AccessFlags2::MEMORY_WRITE;
let bitstream_barrier = vk::BufferMemoryBarrier2::builder()
.src_stage_mask(vk::PipelineStageFlags2::HOST)
.src_access_mask(vk::AccessFlags2::HOST_WRITE)
.dst_stage_mask(all_commands)
.dst_access_mask(memory_rw)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.buffer(resources.bitstream_buffer)
.offset(0)
.size(params.bitstream_len);
let dst_range = color_range_for_layer(params.dst_slot_index);
let to_decode_dst = vk::ImageMemoryBarrier2::builder()
.src_stage_mask(all_commands)
.src_access_mask(memory_rw)
.dst_stage_mask(vk::PipelineStageFlags2::VIDEO_DECODE_KHR)
.dst_access_mask(vk::AccessFlags2::VIDEO_DECODE_WRITE_KHR)
.old_layout(vk::ImageLayout::VIDEO_DECODE_DPB_KHR)
.new_layout(vk::ImageLayout::VIDEO_DECODE_DST_KHR)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(resources.dpb_image)
.subresource_range(dst_range);
let dep_info = vk::DependencyInfo::builder()
.buffer_memory_barriers(std::slice::from_ref(&bitstream_barrier))
.image_memory_barriers(std::slice::from_ref(&to_decode_dst));
unsafe { device.cmd_pipeline_barrier2(command_buffer, &dep_info) };
let dst_resource = vk::VideoPictureResourceInfoKHR::builder()
.coded_offset(vk::Offset2D { x: 0, y: 0 })
.coded_extent(params.coded_extent)
.base_array_layer(params.dst_slot_index)
.image_view_binding(resources.dpb_image_view)
.build();
let dst_reference_info = reference_info_from_slot(&DpbSlot {
frame_num: 0,
frame_num_wrap: 0,
pic_order_cnt: params.picture_info.PicOrderCntVal,
used_for_reference: params.picture_info.flags.IsReference() != 0,
});
let mut dst_dpb_slot_info = vk::VideoDecodeH265DpbSlotInfoKHR::builder()
.std_reference_info(&dst_reference_info)
.build();
let setup_slot = vk::VideoReferenceSlotInfoKHR::builder()
.slot_index(params.dst_slot_index as i32)
.picture_resource(&dst_resource)
.push_next(&mut dst_dpb_slot_info)
.build();
let ref_resources: Vec<vk::VideoPictureResourceInfoKHR> = params
.reference_slots
.iter()
.map(|(slot_index, _)| {
vk::VideoPictureResourceInfoKHR::builder()
.coded_offset(vk::Offset2D { x: 0, y: 0 })
.coded_extent(params.coded_extent)
.base_array_layer(*slot_index)
.image_view_binding(resources.dpb_image_view)
.build()
})
.collect();
let ref_reference_infos: Vec<native::StdVideoDecodeH265ReferenceInfo> = params
.reference_slots
.iter()
.map(|(_, slot)| reference_info_from_slot(slot))
.collect();
let mut ref_dpb_slot_infos: Vec<vk::VideoDecodeH265DpbSlotInfoKHR> = ref_reference_infos
.iter()
.map(|info| {
vk::VideoDecodeH265DpbSlotInfoKHR::builder()
.std_reference_info(info)
.build()
})
.collect();
let reference_slots: Vec<vk::VideoReferenceSlotInfoKHR> = params
.reference_slots
.iter()
.zip(ref_resources.iter())
.zip(ref_dpb_slot_infos.iter_mut())
.map(|(((slot_index, _), resource), dpb_slot_info)| {
vk::VideoReferenceSlotInfoKHR::builder()
.slot_index(*slot_index as i32)
.picture_resource(resource)
.push_next(dpb_slot_info)
.build()
})
.collect();
let mut begin_slots = reference_slots.clone();
let mut begin_setup_slot = setup_slot;
begin_setup_slot.slot_index = -1;
begin_slots.push(begin_setup_slot);
let begin_coding = vk::VideoBeginCodingInfoKHR::builder()
.video_session(resources.session)
.video_session_parameters(resources.session_parameters)
.reference_slots(&begin_slots);
unsafe { device.cmd_begin_video_coding_khr(command_buffer, &begin_coding) };
let slice_segment_offsets = [0u32];
let mut hevc_picture_info = vk::VideoDecodeH265PictureInfoKHR::builder()
.std_picture_info(params.picture_info)
.slice_segment_offsets(&slice_segment_offsets)
.build();
let decode_info = vk::VideoDecodeInfoKHR::builder()
.src_buffer(resources.bitstream_buffer)
.src_buffer_offset(0)
.src_buffer_range(params.bitstream_len)
.dst_picture_resource(dst_resource)
.setup_reference_slot(&setup_slot)
.reference_slots(&reference_slots)
.push_next(&mut hevc_picture_info);
unsafe { device.cmd_decode_video_khr(command_buffer, &decode_info) };
let end_info = vk::VideoEndCodingInfoKHR::builder();
unsafe { device.cmd_end_video_coding_khr(command_buffer, &end_info) };
let from_decode_dst = vk::ImageMemoryBarrier2::builder()
.src_stage_mask(vk::PipelineStageFlags2::VIDEO_DECODE_KHR)
.src_access_mask(vk::AccessFlags2::VIDEO_DECODE_WRITE_KHR)
.dst_stage_mask(all_commands)
.dst_access_mask(memory_rw)
.old_layout(vk::ImageLayout::VIDEO_DECODE_DST_KHR)
.new_layout(vk::ImageLayout::VIDEO_DECODE_DPB_KHR)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(resources.dpb_image)
.subresource_range(dst_range);
let dep_info_back =
vk::DependencyInfo::builder().image_memory_barriers(std::slice::from_ref(&from_decode_dst));
unsafe { device.cmd_pipeline_barrier2(command_buffer, &dep_info_back) };
unsafe { device.end_command_buffer(command_buffer) }.map_err(|result| {
VulkanDecodeError::VkCall {
call: "vkEndCommandBuffer",
result,
}
})?;
submit_and_wait(decode_device, resources, command_buffer)
}