#![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::{
DeviceV1_0, DeviceV1_3, HasBuilder, KhrVideoEncodeQueueExtensionDeviceCommands,
KhrVideoQueueExtensionDeviceCommands,
};
use crate::vulkan::session::{EncodeDevice, SessionResources, VulkanEncodeSessionError};
pub(crate) struct RecordParams<'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::VideoEncodeH264PictureInfoKHR,
}
pub(crate) fn record_and_submit(
encode_device: &EncodeDevice<'_>,
resources: &mut SessionResources,
params: &mut RecordParams<'_>,
) -> 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,
);
record_video_coding(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)
}
const fn whole_color_range() -> vk::ImageSubresourceRange {
vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
}
}
pub(crate) fn record_upload_and_barriers(
device: &vulkanalia::Device,
resources: &SessionResources,
command_buffer: vk::CommandBuffer,
coded_extent: vk::Extent2D,
dst_size: vk::DeviceSize,
) {
record_upload(device, resources, command_buffer, coded_extent);
unsafe { device.cmd_fill_buffer(command_buffer, resources.dst_buffer, 0, dst_size, 0) };
record_pre_encode_barriers(device, resources, command_buffer, dst_size);
}
fn record_upload(
device: &vulkanalia::Device,
resources: &SessionResources,
command_buffer: vk::CommandBuffer,
coded_extent: vk::Extent2D,
) {
let whole_color_range = whole_color_range();
let all_commands = vk::PipelineStageFlags2::ALL_COMMANDS;
let memory_rw = vk::AccessFlags2::MEMORY_READ | vk::AccessFlags2::MEMORY_WRITE;
let to_transfer_dst = vk::ImageMemoryBarrier2::builder()
.src_stage_mask(all_commands)
.src_access_mask(vk::AccessFlags2::empty())
.dst_stage_mask(all_commands)
.dst_access_mask(memory_rw)
.old_layout(vk::ImageLayout::UNDEFINED)
.new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(resources.input_image)
.subresource_range(whole_color_range);
let dep_info =
vk::DependencyInfo::builder().image_memory_barriers(std::slice::from_ref(&to_transfer_dst));
unsafe { device.cmd_pipeline_barrier2(command_buffer, &dep_info) };
let luma_extent = vk::Extent3D {
width: coded_extent.width,
height: coded_extent.height,
depth: 1,
};
let chroma_extent = vk::Extent3D {
width: coded_extent.width / 2,
height: coded_extent.height / 2,
depth: 1,
};
let luma_bytes =
vk::DeviceSize::from(coded_extent.width) * vk::DeviceSize::from(coded_extent.height);
let copy_regions = [
vk::BufferImageCopy::builder()
.buffer_offset(0)
.image_subresource(vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::PLANE_0,
mip_level: 0,
base_array_layer: 0,
layer_count: 1,
})
.image_extent(luma_extent),
vk::BufferImageCopy::builder()
.buffer_offset(luma_bytes)
.image_subresource(vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::PLANE_1,
mip_level: 0,
base_array_layer: 0,
layer_count: 1,
})
.image_extent(chroma_extent),
];
unsafe {
device.cmd_copy_buffer_to_image(
command_buffer,
resources.staging_buffer,
resources.input_image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
©_regions,
);
}
}
fn record_pre_encode_barriers(
device: &vulkanalia::Device,
resources: &SessionResources,
command_buffer: vk::CommandBuffer,
dst_size: vk::DeviceSize,
) {
let whole_color_range = whole_color_range();
let all_commands = vk::PipelineStageFlags2::ALL_COMMANDS;
let memory_rw = vk::AccessFlags2::MEMORY_READ | vk::AccessFlags2::MEMORY_WRITE;
let to_encode_src = vk::ImageMemoryBarrier2::builder()
.src_stage_mask(all_commands)
.src_access_mask(memory_rw)
.dst_stage_mask(all_commands)
.dst_access_mask(memory_rw)
.old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
.new_layout(vk::ImageLayout::VIDEO_ENCODE_SRC_KHR)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(resources.input_image)
.subresource_range(whole_color_range);
let dpb_to_encode = vk::ImageMemoryBarrier2::builder()
.src_stage_mask(all_commands)
.src_access_mask(vk::AccessFlags2::empty())
.dst_stage_mask(all_commands)
.dst_access_mask(memory_rw)
.old_layout(vk::ImageLayout::UNDEFINED)
.new_layout(vk::ImageLayout::VIDEO_ENCODE_DPB_KHR)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(resources.dpb_image)
.subresource_range(whole_color_range);
let dst_buffer_barrier = vk::BufferMemoryBarrier2::builder()
.src_stage_mask(all_commands)
.src_access_mask(memory_rw)
.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.dst_buffer)
.offset(0)
.size(dst_size);
let image_barriers = [to_encode_src, dpb_to_encode];
let dep_info = vk::DependencyInfo::builder()
.image_memory_barriers(&image_barriers)
.buffer_memory_barriers(std::slice::from_ref(&dst_buffer_barrier));
unsafe { device.cmd_pipeline_barrier2(command_buffer, &dep_info) };
}
fn record_video_coding(
encode_device: &EncodeDevice<'_>,
resources: &SessionResources,
params: &mut RecordParams<'_>,
command_buffer: vk::CommandBuffer,
) {
let device = encode_device.device;
let dpb_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.dpb_image_view);
let setup_slot = vk::VideoReferenceSlotInfoKHR::builder()
.slot_index(0)
.picture_resource(&dpb_resource);
let begin_slots = [vk::VideoReferenceSlotInfoKHR::builder().slot_index(0)];
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 = 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);
}
}
pub(crate) fn submit_and_readback(
encode_device: &EncodeDevice<'_>,
resources: &mut SessionResources,
command_buffer: vk::CommandBuffer,
dst_size: vk::DeviceSize,
) -> Result<Vec<u8>, VulkanEncodeSessionError> {
let device = encode_device.device;
unsafe { device.reset_fences(&[resources.fence]) }.map_err(|result| {
VulkanEncodeSessionError::VkCall {
call: "vkResetFences",
result,
}
})?;
let cb_submit_info = vk::CommandBufferSubmitInfo::builder()
.command_buffer(command_buffer)
.device_mask(0);
let submit_info =
vk::SubmitInfo2::builder().command_buffer_infos(std::slice::from_ref(&cb_submit_info));
unsafe { device.queue_submit2(encode_device.queue, &[submit_info], resources.fence) }.map_err(
|result| VulkanEncodeSessionError::VkCall {
call: "vkQueueSubmit2",
result,
},
)?;
unsafe { device.wait_for_fences(&[resources.fence], true, u64::MAX) }.map_err(|result| {
VulkanEncodeSessionError::VkCall {
call: "vkWaitForFences",
result,
}
})?;
let ptr = unsafe {
device.map_memory(
resources.dst_memory,
0,
dst_size,
vk::MemoryMapFlags::empty(),
)
}
.map_err(|result| VulkanEncodeSessionError::VkCall {
call: "vkMapMemory (dst)",
result,
})?;
let dst_bytes =
unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), dst_size as usize) }.to_vec();
unsafe { device.unmap_memory(resources.dst_memory) };
let mut bytes_written = [0u32; 1];
let bytes_written_view = unsafe {
std::slice::from_raw_parts_mut(
bytes_written.as_mut_ptr().cast::<u8>(),
std::mem::size_of_val(&bytes_written),
)
};
unsafe {
device.get_query_pool_results(
resources.encode_feedback_query_pool,
0,
1,
bytes_written_view,
vk::DeviceSize::try_from(std::mem::size_of::<u32>()).unwrap_or(4),
vk::QueryResultFlags::WAIT,
)
}
.map_err(|result| VulkanEncodeSessionError::VkCall {
call: "vkGetQueryPoolResults",
result,
})?;
let written = usize::try_from(bytes_written[0])
.unwrap_or(0)
.min(dst_bytes.len());
Ok(dst_bytes[..written].to_vec())
}