vulkan_layer/vk_utils.rs
1// Copyright 2023 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{collections::BTreeSet, ffi::CStr, fmt::Debug, mem::MaybeUninit, ptr::NonNull};
16
17use ash::vk;
18use num_traits::Zero;
19
20use crate::global_simple_intercept::{ApiVersion, Extension, Feature};
21
22/// A chain of
23/// [`VkBaseOutStructure`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBaseOutStructure.html).
24///
25/// This type provides an easy way to visit the `*mut c_void`
26/// [`pNext` chain](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fundamentals-validusage-pNext).
27/// One can convert an `Option<&mut vk::BaseOutStructure>` to [`VulkanBaseOutStructChain`].
28///
29/// # Examples
30///
31/// ```
32/// use ash::vk;
33/// use vulkan_layer::VulkanBaseOutStructChain;
34///
35/// let mut timeline_semaphore_feature = vk::PhysicalDeviceTimelineSemaphoreFeatures::builder();
36/// let mut ycbcr_feature = vk::PhysicalDeviceSamplerYcbcrConversionFeatures::builder();
37/// let features2 = vk::PhysicalDeviceFeatures2::builder()
38/// .push_next(&mut timeline_semaphore_feature)
39/// .push_next(&mut ycbcr_feature);
40/// let out_chain = features2.p_next.cast::<vk::BaseOutStructure>();
41/// let mut out_chain: VulkanBaseOutStructChain = unsafe { out_chain.as_mut() }.into();
42/// // `push_next` prepends the element to the pNext chain.
43/// assert_eq!(
44/// out_chain.next().unwrap().s_type,
45/// vk::StructureType::PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES
46/// );
47/// assert_eq!(
48/// out_chain.next().unwrap().s_type,
49/// vk::StructureType::PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES
50/// );
51/// assert!(out_chain.next().is_none());
52/// ```
53pub struct VulkanBaseOutStructChain<'a> {
54 next: Option<&'a mut vk::BaseOutStructure>,
55}
56
57impl<'a> From<Option<&'a mut vk::BaseOutStructure>> for VulkanBaseOutStructChain<'a> {
58 fn from(next: Option<&'a mut vk::BaseOutStructure>) -> Self {
59 Self { next }
60 }
61}
62
63impl<'a> Iterator for VulkanBaseOutStructChain<'a> {
64 type Item = &'a mut vk::BaseOutStructure;
65
66 fn next(&mut self) -> Option<Self::Item> {
67 self.next.take().inspect(|element| {
68 self.next = unsafe { element.p_next.as_mut() };
69 })
70 }
71}
72
73/// A chain of
74/// [`VkBaseInStructure`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBaseInStructure.html).
75///
76/// This type provides an easy way to visit the `*const c_void`
77/// [`pNext` chain](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fundamentals-validusage-pNext).
78/// One can convert an `Option<&vk::BaseInStructure>` to [`VulkanBaseInStructChain`].
79///
80/// # Examples
81/// ```
82/// use ash::vk;
83/// use vulkan_layer::VulkanBaseInStructChain;
84///
85/// let mut external_memory_image_info = vk::ExternalMemoryImageCreateInfo::builder();
86/// let mut image_format_list_info = vk::ImageFormatListCreateInfo::builder();
87/// let image_create_info = vk::ImageCreateInfo::builder()
88/// .push_next(&mut external_memory_image_info)
89/// .push_next(&mut image_format_list_info);
90/// let in_chain = image_create_info.p_next.cast::<vk::BaseInStructure>();
91/// let mut in_chain: VulkanBaseInStructChain = unsafe { in_chain.as_ref() }.into();
92/// // `push_next` prepends the element to the pNext chain.
93/// assert_eq!(
94/// in_chain.next().unwrap().s_type,
95/// vk::StructureType::IMAGE_FORMAT_LIST_CREATE_INFO
96/// );
97/// assert_eq!(
98/// in_chain.next().unwrap().s_type,
99/// vk::StructureType::EXTERNAL_MEMORY_IMAGE_CREATE_INFO
100/// );
101/// assert!(in_chain.next().is_none());
102/// ```
103pub struct VulkanBaseInStructChain<'a> {
104 next: Option<&'a vk::BaseInStructure>,
105}
106
107impl<'a> From<Option<&'a vk::BaseInStructure>> for VulkanBaseInStructChain<'a> {
108 fn from(next: Option<&'a vk::BaseInStructure>) -> Self {
109 Self { next }
110 }
111}
112
113impl<'a> Iterator for VulkanBaseInStructChain<'a> {
114 type Item = &'a vk::BaseInStructure;
115
116 fn next(&mut self) -> Option<Self::Item> {
117 self.next.take().inspect(|element| {
118 self.next = unsafe { element.p_next.as_ref() };
119 })
120 }
121}
122
123/// Used to extend an iterator of [`Feature`].
124pub trait IsCommandEnabled {
125 /// Given a list of [`Feature`]s where the command is defined, the Vulkan API version and a set
126 /// of enabled extensions, returns if the command is enabled.
127 ///
128 /// One command with the same name can appear in different features, e.g.
129 /// [`vkCmdBindVertexBuffers2EXT`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdBindVertexBuffers2EXT.html)
130 /// is defined in both
131 /// [`VK_EXT_extended_dynamic_state`](<https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_extended_dynamic_state.html#:~:text=New%20Commands-,vkCmdBindVertexBuffers2EXT,-vkCmdSetCullModeEXT>)
132 /// and
133 /// [`VK_EXT_shader_object`](<https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_shader_object.html#:~:text=vkCmdBindShadersEXT-,vkCmdBindVertexBuffers2EXT,-vkCmdSetAlphaToCoverageEnableEXT>).
134 /// `vkGetDeviceProcAddr` should return a valid function pointer `vkCmdBindVertexBuffers2EXT` if
135 /// either extension is enabled.
136 ///
137 /// # Examples
138 ///
139 /// ```
140 /// use ash::vk;
141 /// use std::collections::BTreeSet;
142 /// use vulkan_layer::{
143 /// unstable_api::{ApiVersion, Feature, IsCommandEnabled},
144 /// Extension,
145 /// };
146 ///
147 /// let features: Vec<Feature> = vec![
148 /// Extension::EXTExtendedDynamicState.into(),
149 /// Extension::EXTShaderObject.into(),
150 /// ];
151 /// assert!(features.is_command_enabled(
152 /// &ApiVersion::V1_1,
153 /// &BTreeSet::from([Extension::EXTExtendedDynamicState.into()])
154 /// ));
155 /// assert!(features.is_command_enabled(
156 /// &ApiVersion::V1_1,
157 /// &BTreeSet::from([Extension::EXTShaderObject.into()])
158 /// ));
159 /// assert!(!features.is_command_enabled(&ApiVersion::V1_1, &BTreeSet::new()));
160 /// ```
161 #[allow(clippy::wrong_self_convention)]
162 fn is_command_enabled(
163 self,
164 api_version: &ApiVersion,
165 enabled_extensions: &BTreeSet<Extension>,
166 ) -> bool;
167}
168
169impl<'a, T: IntoIterator<Item = &'a Feature>> IsCommandEnabled for T {
170 fn is_command_enabled(
171 self,
172 api_version: &ApiVersion,
173 enabled_extensions: &BTreeSet<Extension>,
174 ) -> bool {
175 self.into_iter().any(|feature| match feature {
176 Feature::Extension(extension) => enabled_extensions.contains(extension),
177 Feature::Core(version) => version <= api_version,
178 })
179 }
180}
181
182/// Similar to [`std::slice::from_raw_parts`], but allow null pointer for 0 length slice.
183///
184/// # Safety
185/// Follow the safety requirements for [`std::slice::from_raw_parts`] with one major difference: if
186/// `len` is `0`, `data` can be a null pointer, unlike [`std::slice::from_raw_parts`].
187#[deny(unsafe_op_in_unsafe_fn)]
188pub(crate) unsafe fn slice_from_raw_parts<'a, T>(
189 data: *const T,
190 len: impl TryInto<usize, Error = impl Debug>,
191) -> &'a [T] {
192 let len: usize = len
193 .try_into()
194 .expect("length must be able to cast to usize");
195 if len == 0 {
196 return &[];
197 }
198 // Safety: since `len` isn't 0 at this point, the caller guarantees that the safety requirement
199 // for `std::slice::from_raw_parts` is met.
200 unsafe { std::slice::from_raw_parts(data, len) }
201}
202
203/// Convert from a slice of i8 pointers to an iterator of strings.
204///
205/// Usually used to parse the C style C string array like `VkInstanceInfo::ppEnabledExtensionNames`.
206///
207/// # Safety
208/// Every entry of `cstr_ptrs` must meet the safety requirement of [`std::ffi::CStr::from_ptr`].
209///
210/// # Panic
211/// When iterating the returned iterator, it will panic if the pointer doesn't point to a valid
212/// UTF-8 string.
213#[deny(unsafe_op_in_unsafe_fn)]
214pub(crate) unsafe fn slice_to_owned_strings(
215 cstr_ptrs: &[*const i8],
216) -> impl Iterator<Item = String> + '_ {
217 cstr_ptrs.iter().map(|cstr_ptr| {
218 let cstr = unsafe { CStr::from_ptr(*cstr_ptr) };
219 cstr.to_str()
220 .unwrap_or_else(|e| {
221 panic!(
222 "Failed to decode the string {}: {}",
223 cstr.to_string_lossy(),
224 e
225 )
226 })
227 .to_owned()
228 })
229}
230
231/// Clone the slice to a C style out array with proper `VkResult` return value.
232///
233/// The caller can use this function to handle the output array in Vulkan with slice.
234///
235/// If `p_out` is `NULL`, write the length to `p_out`. If `p_out` is not `NULL`, `p_count` describes
236/// the length of the output array. This function will clone the slice to the output array, and
237/// rewrite the `p_count` to indicate how many elements are written to the out array. If the length
238/// of the slice is larger than the out array, `VK_INCOMPLETE` will be returned. For all other
239/// cases, `VK_SUCCESS` is returned.
240///
241/// # Safety
242/// `p_count` must be a valid pointer to `U`. If `p_count` doesn't reference 0, and `p_out` is not
243/// null, `p_out` must be a valid pointer to `*p_count` number of `T`'s.
244///
245/// # Examples
246///
247/// Obtain the length with a NULL `p_out`.
248///
249/// ```
250/// use ash::vk;
251/// use std::ptr::NonNull;
252/// use vulkan_layer::fill_vk_out_array;
253///
254/// let mut count = 0;
255/// let out_array = &[0, 1, 2, 3];
256/// assert_eq!(
257/// unsafe {
258/// fill_vk_out_array(
259/// out_array,
260/// NonNull::new(&mut count as *mut _).unwrap(),
261/// std::ptr::null_mut(),
262/// )
263/// },
264/// vk::Result::SUCCESS
265/// );
266/// assert_eq!(count as usize, out_array.len());
267/// ```
268///
269/// Short output array results in a `VK_INCOMPLETE`.
270///
271/// ```
272/// use ash::vk;
273/// use std::ptr::NonNull;
274/// use vulkan_layer::fill_vk_out_array;
275///
276/// let mut out_array = [0; 2];
277/// let mut count = out_array.len() as u32;
278/// let out_slice = &[3, 1, 44];
279///
280/// assert_eq!(
281/// unsafe {
282/// fill_vk_out_array(
283/// out_slice,
284/// NonNull::new(&mut count as *mut _).unwrap(),
285/// out_array.as_mut_ptr(),
286/// )
287/// },
288/// vk::Result::INCOMPLETE
289/// );
290/// assert_eq!(count as usize, out_array.len());
291/// assert_eq!(out_array, out_slice[0..out_array.len()]);
292/// ```
293///
294/// Longer output array will only be partly overwritten.
295///
296/// ```
297/// use ash::vk;
298/// use std::ptr::NonNull;
299/// use vulkan_layer::fill_vk_out_array;
300///
301/// let mut out_array = [42; 3];
302/// let mut count = out_array.len() as u32;
303/// let out_slice = &[3, 1];
304///
305/// assert_eq!(
306/// unsafe {
307/// fill_vk_out_array(
308/// out_slice,
309/// NonNull::new(&mut count as *mut _).unwrap(),
310/// out_array.as_mut_ptr(),
311/// )
312/// },
313/// vk::Result::SUCCESS
314/// );
315/// assert_eq!(count as usize, out_slice.len());
316/// assert_eq!(out_array, [3, 1, 42]);
317/// ```
318#[deny(unsafe_op_in_unsafe_fn)]
319pub unsafe fn fill_vk_out_array<T, U>(
320 out: &[T],
321 mut p_count: NonNull<U>,
322 p_out: *mut T,
323) -> vk::Result
324where
325 T: Clone,
326 U: TryFrom<usize> + TryInto<usize> + Zero + Copy,
327 <U as TryFrom<usize>>::Error: Debug,
328 <U as TryInto<usize>>::Error: Debug,
329{
330 let count = unsafe { p_count.as_mut() };
331 let p_out = match NonNull::new(p_out) {
332 Some(p_out) => p_out,
333 None => {
334 *count = out.len().try_into().unwrap();
335 return vk::Result::SUCCESS;
336 }
337 };
338 if count.is_zero() {
339 if out.is_empty() {
340 return vk::Result::SUCCESS;
341 } else {
342 return vk::Result::INCOMPLETE;
343 }
344 }
345 let out_slice =
346 unsafe { std::slice::from_raw_parts_mut(p_out.as_ptr(), (*count).try_into().unwrap()) };
347 if out_slice.len() < out.len() {
348 *count = out_slice.len().try_into().unwrap();
349 out_slice.clone_from_slice(&out[..out_slice.len()]);
350 vk::Result::INCOMPLETE
351 } else {
352 *count = out.len().try_into().unwrap();
353 out_slice[..out.len()].clone_from_slice(out);
354 vk::Result::SUCCESS
355 }
356}
357
358/// Returns `None` if the pointer is null, or else returns a unique reference to the value wrapped
359/// in `Some`. In contrast to `as_mut`, this does not require that the value has to be initialized.
360///
361/// Polyfill of
362/// [`as_uninit_mut`](https://doc.rust-lang.org/std/primitive.pointer.html#method.as_uninit_mut) for
363/// stable Rust.
364///
365/// # Safety
366///
367/// Refers to
368/// [`as_uninit_mut`](https://doc.rust-lang.org/std/primitive.pointer.html#method.as_uninit_mut).
369#[deny(unsafe_op_in_unsafe_fn)]
370pub(crate) unsafe fn ptr_as_uninit_mut<'a, T>(p_data: *mut T) -> Option<&'a mut MaybeUninit<T>> {
371 // SAFETY: same as https://doc.rust-lang.org/std/primitive.pointer.html#method.as_uninit_mut.
372 if p_data.is_null() {
373 None
374 } else {
375 Some(unsafe { &mut *(p_data as *mut MaybeUninit<T>) })
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[test]
384 fn multiple_in_struct_nodes() {
385 let mut external_memory_image_info = vk::ExternalMemoryImageCreateInfo::builder();
386 let mut image_format_list_info = vk::ImageFormatListCreateInfo::builder();
387 let image_create_info = vk::ImageCreateInfo::builder()
388 .push_next(&mut external_memory_image_info)
389 .push_next(&mut image_format_list_info);
390 let in_chain = image_create_info.p_next as *const vk::BaseInStructure;
391 let mut in_chain: VulkanBaseInStructChain = unsafe { in_chain.as_ref() }.into();
392 // `push_next` prepends the element to the pNext chain.
393 assert_eq!(
394 in_chain.next().unwrap().s_type,
395 vk::StructureType::IMAGE_FORMAT_LIST_CREATE_INFO
396 );
397 assert_eq!(
398 in_chain.next().unwrap().s_type,
399 vk::StructureType::EXTERNAL_MEMORY_IMAGE_CREATE_INFO
400 );
401 assert!(in_chain.next().is_none());
402 }
403
404 #[test]
405 fn multiple_out_struct_nodes() {
406 let mut timeline_semaphore_feature = vk::PhysicalDeviceTimelineSemaphoreFeatures::builder();
407 let mut ycbcr_feature = vk::PhysicalDeviceSamplerYcbcrConversionFeatures::builder();
408 let features2 = vk::PhysicalDeviceFeatures2::builder()
409 .push_next(&mut timeline_semaphore_feature)
410 .push_next(&mut ycbcr_feature);
411 let out_chain = features2.p_next.cast::<vk::BaseOutStructure>();
412 let mut out_chain: VulkanBaseOutStructChain = unsafe { out_chain.as_mut() }.into();
413 // `push_next` prepends the element to the pNext chain.
414 assert_eq!(
415 out_chain.next().unwrap().s_type,
416 vk::StructureType::PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES
417 );
418 assert_eq!(
419 out_chain.next().unwrap().s_type,
420 vk::StructureType::PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES
421 );
422 assert!(out_chain.next().is_none());
423 }
424
425 #[test]
426 fn test_is_command_enabled() {
427 let features: Vec<Feature> = vec![
428 Extension::EXTExtendedDynamicState.into(),
429 Extension::EXTShaderObject.into(),
430 ];
431 assert!(features.is_command_enabled(
432 &ApiVersion::V1_1,
433 &BTreeSet::from([Extension::EXTExtendedDynamicState])
434 ));
435 assert!(features.is_command_enabled(
436 &ApiVersion::V1_1,
437 &BTreeSet::from([Extension::EXTShaderObject])
438 ));
439 assert!(!features.is_command_enabled(&ApiVersion::V1_1, &BTreeSet::new()));
440 }
441
442 #[test]
443 fn test_fill_vk_out_array_null_out_ptr() {
444 let data = &[1, 2, 3, 4];
445 let mut count = 0;
446 assert_eq!(
447 unsafe {
448 fill_vk_out_array(
449 data,
450 NonNull::new(&mut count as *mut _).unwrap(),
451 std::ptr::null_mut(),
452 )
453 },
454 vk::Result::SUCCESS
455 );
456 assert_eq!(count as usize, data.len());
457 }
458
459 #[test]
460 fn test_fill_vk_out_array_empty_out_ptr() {
461 let mut count = 0;
462 assert_eq!(
463 unsafe {
464 fill_vk_out_array(
465 &[1, 2, 3, 4],
466 NonNull::new(&mut count as *mut _).unwrap(),
467 NonNull::dangling().as_ptr(),
468 )
469 },
470 vk::Result::INCOMPLETE
471 );
472 assert_eq!(count, 0);
473
474 let data: &[i32] = &[];
475 assert_eq!(
476 unsafe {
477 fill_vk_out_array(
478 data,
479 NonNull::new(&mut count as *mut _).unwrap(),
480 NonNull::dangling().as_ptr(),
481 )
482 },
483 vk::Result::SUCCESS
484 );
485 assert_eq!(count, 0);
486 }
487
488 #[test]
489 fn test_fill_vk_out_array_shorter_out_ptr() {
490 let data = &[1, 2, 3, 4];
491 let mut out_array = [0; 2];
492 let mut count = out_array.len() as u32;
493 assert_eq!(
494 unsafe {
495 fill_vk_out_array(
496 data,
497 NonNull::new(&mut count as *mut _).unwrap(),
498 out_array.as_mut_ptr(),
499 )
500 },
501 vk::Result::INCOMPLETE
502 );
503 assert_eq!(count as usize, out_array.len());
504 assert_eq!(out_array, data[0..out_array.len()]);
505 }
506
507 #[test]
508 fn test_fill_vk_out_array_longer_out_ptr() {
509 let data = &[1, 2];
510 let mut out_array = [10; 4];
511 let mut count = out_array.len() as u32;
512 assert_eq!(
513 unsafe {
514 fill_vk_out_array(
515 data,
516 NonNull::new(&mut count as *mut _).unwrap(),
517 out_array.as_mut_ptr(),
518 )
519 },
520 vk::Result::SUCCESS
521 );
522 assert_eq!(count as usize, data.len());
523 assert_eq!(out_array, [1, 2, 10, 10]);
524 }
525
526 #[test]
527 fn test_ptr_as_uninit_mut_null_ptr() {
528 let ptr: *mut u8 = std::ptr::null_mut();
529 assert!(unsafe { ptr_as_uninit_mut(ptr) }.is_none());
530 }
531
532 #[test]
533 fn test_ptr_as_uninit_mut_uninit_value() {
534 let expected_value: u8 = 28;
535 let mut uninit_data = MaybeUninit::<u8>::uninit();
536 // Miri will test if we read into uninitialized data.
537 let result_uninit = unsafe { ptr_as_uninit_mut(uninit_data.as_mut_ptr()) }
538 .expect("not null pointer should result in Some");
539 assert_eq!(result_uninit.as_mut_ptr(), uninit_data.as_mut_ptr());
540 result_uninit.write(expected_value);
541 assert_eq!(unsafe { uninit_data.assume_init() }, expected_value);
542 }
543
544 // The following 2 tests guarantee that when invalid UTF-8 strings are passed in,
545 // slice_to_owned_strings only panics when the returned iterator is consumed.
546 #[test]
547 fn test_slice_to_owned_strings_invalid_utf8_str_shouldnt_panic_before_iter_consumed() {
548 let invalid_input = b"\xc3\x28\x00";
549 let invalid_input: &[i8] = bytemuck::cast_slice(invalid_input);
550 let invalid_inputs = [invalid_input.as_ptr()];
551 let _ = unsafe { slice_to_owned_strings(&invalid_inputs) };
552 }
553
554 #[test]
555 #[should_panic]
556 fn test_slice_to_owned_strings_invalid_utf8_str() {
557 let invalid_input = b"\xc3\x28\x00";
558 let invalid_input: &[i8] = bytemuck::cast_slice(invalid_input);
559 let invalid_inputs = [invalid_input.as_ptr()];
560 let output = unsafe { slice_to_owned_strings(&invalid_inputs) };
561 output.for_each(|_| {});
562 }
563}