diff --git a/src/encoder/pipeline.rs b/src/encoder/pipeline.rs index 233506e..be18660 100644 --- a/src/encoder/pipeline.rs +++ b/src/encoder/pipeline.rs @@ -35,7 +35,7 @@ use std::sync::{Arc, Condvar, Mutex}; use std::task::{Context, Poll}; use std::thread::JoinHandle; -use ash::vk; +use ash::vk::{self, Handle}; use futures_channel::oneshot; use crate::encoder::resources::{ @@ -252,6 +252,22 @@ impl EncodePipeline { let command_buffers = unsafe { device.allocate_command_buffers(&alloc_info) } .map_err(|e| PixelForgeError::CommandBuffer(e.to_string()))?; + // Timestamp queries are only legal on a queue family with non-zero + // `timestampValidBits` (VUID-vkCmdWriteTimestamp-timestampValidBits-00829). + // RADV's dedicated video encode queue reports 0, so recording + // vkCmdWriteTimestamp there causes device loss. When unsupported we + // leave the per-slot pools null and the recording/readback helpers treat + // a null pool as "timestamps disabled". + let timestamps_supported = context.encode_timestamps_supported(); + let timestamp_period = context.device_properties().limits.timestamp_period; + if !timestamps_supported { + tracing::info!( + "Video encode queue family {:?} reports timestampValidBits=0; \ + GPU encode timing stats disabled", + context.video_encode_queue_family() + ); + } + let mut slots = Vec::with_capacity(ENCODE_PIPELINE_DEPTH); for &encode_command_buffer in &command_buffers { let (input_image, input_image_memory, input_image_view) = create_image( @@ -300,13 +316,10 @@ impl EncodePipeline { let mut profile = *config.profile_info; let query_pool = create_encode_feedback_query_pool(context, &mut profile)?; - let timestamp_query_pool = create_encode_timestamp_query_pool(context)?; - let timestamp_period = unsafe { - context - .instance() - .get_physical_device_properties(context.physical_device()) - .limits - .timestamp_period + let timestamp_query_pool = if timestamps_supported { + create_encode_timestamp_query_pool(context)? + } else { + vk::QueryPool::null() }; slots.push(EncodeSlot { @@ -513,8 +526,12 @@ impl EncodePipeline { } slot.bitstream_buffer_ptr = std::ptr::null_mut(); } + if !slot.timestamp_query_pool.is_null() { + unsafe { + device.destroy_query_pool(slot.timestamp_query_pool, None); + } + } unsafe { - device.destroy_query_pool(slot.timestamp_query_pool, None); device.destroy_query_pool(slot.query_pool, None); device.destroy_fence(slot.encode_fence, None); device.destroy_buffer(slot.bitstream_buffer, None); diff --git a/src/encoder/resources.rs b/src/encoder/resources.rs index bc2fe5e..53e878a 100644 --- a/src/encoder/resources.rs +++ b/src/encoder/resources.rs @@ -1,8 +1,8 @@ use crate::encoder::{BitDepth, PixelFormat}; use crate::error::{PixelForgeError, Result}; use crate::vulkan::VideoContext; -use ash::vk; use ash::vk::TaggedStructure; +use ash::vk::{self, Handle}; use std::ptr; /// Minimum bitstream buffer size. @@ -1597,11 +1597,17 @@ pub(crate) fn get_encoded_session_params( } /// Resets the given query pool and writes starting timestamp command. +/// +/// No-op when `query_pool` is null (i.e. the encode queue family does not +/// support timestamp queries). pub(crate) fn reset_start_timestamp( device: &ash::Device, command_buffer: vk::CommandBuffer, query_pool: vk::QueryPool, ) { + if query_pool.is_null() { + return; + } unsafe { device.cmd_reset_query_pool(command_buffer, query_pool, 0, 2); device.cmd_write_timestamp( @@ -1614,11 +1620,17 @@ pub(crate) fn reset_start_timestamp( } /// Writes ending timestamp command to the given query pool. +/// +/// No-op when `query_pool` is null (i.e. the encode queue family does not +/// support timestamp queries). pub(crate) fn end_timestamp( device: &ash::Device, command_buffer: vk::CommandBuffer, query_pool: vk::QueryPool, ) { + if query_pool.is_null() { + return; + } unsafe { device.cmd_write_timestamp( command_buffer, @@ -1631,6 +1643,9 @@ pub(crate) fn end_timestamp( /// Queries the given query pool for recorded timestamps, returning their difference. /// +/// Returns `None` when `query_pool` is null (i.e. the encode queue family does +/// not support timestamp queries). +/// /// # Safety /// This must only be called if both `reset_start_timestamp` and `end_timestamp` /// were previously written and executed for the given query pool. @@ -1640,6 +1655,9 @@ pub(crate) unsafe fn query_timestamp_diff( mut timestamps: [u64; 2], timestamp_period: f32, ) -> Option { + if query_pool.is_null() { + return None; + } let mut encode_time_ns: Option = None; let result = unsafe { device.get_query_pool_results( diff --git a/src/vulkan.rs b/src/vulkan.rs index 9333a20..386bd6f 100644 --- a/src/vulkan.rs +++ b/src/vulkan.rs @@ -70,6 +70,7 @@ struct VideoContextInner { physical_device: vk::PhysicalDevice, device: ash::Device, video_encode_queue_family: Option, + video_encode_timestamp_valid_bits: u32, video_encode_queue: Option, transfer_queue_family: u32, transfer_queue: vk::Queue, @@ -118,6 +119,14 @@ impl VideoContext { self.inner.video_encode_queue } + /// Whether the selected video encode queue family supports timestamp + /// queries, i.e. reports a non-zero `timestampValidBits`. RADV's dedicated + /// video encode queue reports 0, so `vkCmdWriteTimestamp` is illegal there + /// (VUID-vkCmdWriteTimestamp-timestampValidBits-00829). + pub(crate) fn encode_timestamps_supported(&self) -> bool { + self.inner.video_encode_timestamp_valid_bits > 0 + } + /// Get the transfer queue family index. pub fn transfer_queue_family(&self) -> u32 { self.inner.transfer_queue_family @@ -241,6 +250,7 @@ impl VideoContext { let mut selected_device = None; let mut video_encode_queue_family = None; + let mut video_encode_timestamp_valid_bits = 0u32; let mut transfer_queue_family = u32::MAX; let mut compute_queue_family = u32::MAX; let mut supported_encode_codecs = Vec::new(); @@ -258,6 +268,7 @@ impl VideoContext { // Find queue families. let mut encode_queue = None; + let mut encode_ts_bits = 0u32; let mut transfer_q = u32::MAX; let mut compute_q = u32::MAX; @@ -270,6 +281,7 @@ impl VideoContext { // Check for video encode queue. if props.queue_flags.contains(vk::QueueFlags::VIDEO_ENCODE_KHR) { encode_queue = Some(idx as u32); + encode_ts_bits = props.timestamp_valid_bits; debug!("Found video encode queue at family {}", idx); } @@ -347,6 +359,7 @@ impl VideoContext { if has_video_support && encode_supported && has_compute_support { selected_device = Some(physical_device); video_encode_queue_family = encode_queue; + video_encode_timestamp_valid_bits = encode_ts_bits; transfer_queue_family = if transfer_q != u32::MAX { transfer_q } else { @@ -575,6 +588,7 @@ impl VideoContext { physical_device, device, video_encode_queue_family, + video_encode_timestamp_valid_bits, video_encode_queue, transfer_queue_family, transfer_queue,