1use std::{
6 collections::VecDeque,
7 error::Error,
8 ffi::{CStr, c_void},
9 fmt,
10 ptr::NonNull,
11 sync::{
12 Arc, Mutex, Weak,
13 atomic::{AtomicBool, Ordering},
14 },
15};
16
17use alpha2_ffmpeg_bridge::{
18 BridgeError, VulkanDeviceInfo, VulkanFrameAccess, VulkanFrameAccessGuard, VulkanFrameLease,
19 VulkanQueueLock, create_vulkan_device_context,
20};
21use alpha2_gpu::{ExternalQueueLock, GpuContext};
22use ash::vk::{self, Handle};
23use rsmpeg::avutil::AVHWDeviceContext;
24use wgpu_hal::Instance as _;
25
26use crate::{NativeGpuIdentity, NativePlaneLayout};
27
28unsafe extern "C" {
31 fn alpha2_native_gpu_vulkan_probe(access: *mut VulkanFrameAccess, status: *mut i32) -> i32;
32 fn alpha2_native_gpu_vulkan_acquire_check(
33 access: *const VulkanFrameAccess,
34 status: *mut i32,
35 ) -> i32;
36 fn alpha2_native_gpu_vulkan_ticket_create(
37 device: u64,
38 allocator: u64,
39 queue_family: u32,
40 output: *mut *mut c_void,
41 status: *mut i32,
42 ) -> i32;
43 fn alpha2_native_gpu_vulkan_ticket_poll(ticket: *const c_void, status: *mut i32) -> i32;
44 fn alpha2_native_gpu_vulkan_ticket_reset(ticket: *mut c_void, status: *mut i32) -> i32;
45 fn alpha2_native_gpu_vulkan_acquire_submit(
46 access: *mut VulkanFrameAccess,
47 ticket: *mut c_void,
48 status: *mut i32,
49 ) -> i32;
50 fn alpha2_native_gpu_vulkan_release(
51 access: *mut VulkanFrameAccess,
52 ticket: *mut c_void,
53 status: *mut i32,
54 ) -> i32;
55 fn alpha2_native_gpu_vulkan_signal(
56 access: *mut VulkanFrameAccess,
57 semaphore: u64,
58 wait_value: u64,
59 signal_value: u64,
60 status: *mut i32,
61 ) -> i32;
62 fn alpha2_native_gpu_vulkan_import_destroy(ticket: *mut c_void);
63 fn alpha2_native_gpu_vulkan_import_retire(ticket: *mut c_void, timeout_ns: u64) -> i32;
64 #[cfg(test)]
65 fn alpha2_native_gpu_vulkan_retained_helpers() -> u64;
66 fn alpha2_native_gpu_vulkan_encode_copier_create(
67 device: u64,
68 queue_family: u32,
69 allocator: u64,
70 output: *mut *mut c_void,
71 status: *mut i32,
72 ) -> i32;
73 fn alpha2_native_gpu_vulkan_encode_copier_destroy(copier: *mut c_void, status: *mut i32)
74 -> i32;
75 fn alpha2_native_gpu_vulkan_encode_copier_slots() -> u32;
76 fn alpha2_native_gpu_vulkan_encode_copier_await_slot(
77 copier: *mut c_void,
78 slot: *mut u32,
79 status: *mut i32,
80 ) -> i32;
81 fn alpha2_native_gpu_vulkan_encode_copier_submit(
82 copier: *mut c_void,
83 access: *mut VulkanFrameAccess,
84 copy: *const VulkanEncodeCopy,
85 status: *mut i32,
86 ) -> i32;
87 fn alpha2_native_gpu_vulkan_set_wait_timeout(nanoseconds: u64);
88 fn alpha2_native_gpu_vulkan_wait_timeout() -> u64;
89}
90
91pub(crate) fn set_wait_timeout(timeout: std::time::Duration) {
92 let nanoseconds = u64::try_from(timeout.as_nanos()).unwrap_or(u64::MAX);
93 unsafe { alpha2_native_gpu_vulkan_set_wait_timeout(nanoseconds) };
95}
96
97fn wait_timeout() -> std::time::Duration {
98 std::time::Duration::from_nanos(unsafe { alpha2_native_gpu_vulkan_wait_timeout() })
100}
101
102fn helper_error(operation: &'static str, step: i32, status: i32) -> BridgeError {
103 crate::status::helper_failure(
104 alpha2_ffmpeg_bridge::NativeGpuBackend::Vulkan,
105 operation,
106 step,
107 status,
108 )
109}
110
111#[cfg(test)]
114pub(crate) mod inject {
115 use std::cell::Cell;
116 use std::sync::atomic::{AtomicU64, Ordering};
117
118 static RETIRES_BEGUN: AtomicU64 = AtomicU64::new(0);
120
121 pub(super) fn retire_begins() {
122 RETIRES_BEGUN.fetch_add(1, Ordering::Release);
123 }
124
125 pub(crate) fn retires_begun() -> u64 {
127 RETIRES_BEGUN.load(Ordering::Acquire)
128 }
129
130 #[derive(Clone, Copy, Debug)]
131 pub(crate) enum Hook {
132 AcquireSubmit,
133 TicketPoll,
134 TicketReset,
135 Release,
136 }
137
138 thread_local! {
139 static COUNTDOWNS: [Cell<u32>; 4] =
140 const { [Cell::new(0), Cell::new(0), Cell::new(0), Cell::new(0)] };
141 static STATUSES: [Cell<i32>; 4] =
142 const { [Cell::new(0), Cell::new(0), Cell::new(0), Cell::new(0)] };
143 }
144
145 pub(crate) fn fail_nth(hook: Hook, nth: u32) {
147 fail_nth_with(hook, nth, super::vk::Result::ERROR_DEVICE_LOST.as_raw());
148 }
149
150 pub(crate) fn fail_nth_with(hook: Hook, nth: u32, status: i32) {
152 COUNTDOWNS.with(|countdowns| countdowns[hook as usize].set(nth));
153 STATUSES.with(|statuses| statuses[hook as usize].set(status));
154 }
155
156 pub(super) fn fails(hook: Hook, step: i32, status: &mut i32) -> Option<i32> {
158 let due = COUNTDOWNS.with(|countdowns| {
159 let countdown = &countdowns[hook as usize];
160 match countdown.get() {
161 0 => false,
162 1 => {
163 countdown.set(0);
164 true
165 }
166 left => {
167 countdown.set(left - 1);
168 false
169 }
170 }
171 });
172 due.then(|| {
173 *status = STATUSES.with(|statuses| statuses[hook as usize].get());
174 step
175 })
176 }
177}
178
179struct LeaseAccess<'a> {
186 frame: VulkanFrameAccessGuard<'a>,
187 ticket: NonNull<c_void>,
188}
189
190unsafe fn acquire_submit(lease: &mut LeaseAccess<'_>, status: &mut i32) -> i32 {
196 #[cfg(test)]
197 if let Some(step) = inject::fails(
198 inject::Hook::AcquireSubmit,
199 crate::interop::ALPHA2_GPU_STEP_QUEUE_SUBMIT,
200 status,
201 ) {
202 return step;
203 }
204 unsafe {
206 alpha2_native_gpu_vulkan_acquire_submit(
207 lease.frame.access_mut(),
208 lease.ticket.as_ptr(),
209 status,
210 )
211 }
212}
213
214unsafe fn release(lease: &mut LeaseAccess<'_>, status: &mut i32) -> i32 {
220 #[cfg(test)]
221 if let Some(step) = inject::fails(
222 inject::Hook::Release,
223 crate::interop::ALPHA2_GPU_STEP_QUEUE_SUBMIT,
224 status,
225 ) {
226 return step;
227 }
228 unsafe {
230 alpha2_native_gpu_vulkan_release(lease.frame.access_mut(), lease.ticket.as_ptr(), status)
231 }
232}
233
234unsafe fn ticket_poll(ticket: *const c_void, status: &mut i32) -> i32 {
239 #[cfg(test)]
240 if let Some(step) = inject::fails(
241 inject::Hook::TicketPoll,
242 crate::interop::ALPHA2_GPU_STEP_FENCE_STATUS,
243 status,
244 ) {
245 return step;
246 }
247 unsafe { alpha2_native_gpu_vulkan_ticket_poll(ticket, status) }
249}
250
251unsafe fn ticket_reset(ticket: *mut c_void, status: &mut i32) -> i32 {
256 #[cfg(test)]
257 if let Some(step) = inject::fails(
258 inject::Hook::TicketReset,
259 crate::interop::ALPHA2_GPU_STEP_RESET_FENCE,
260 status,
261 ) {
262 return step;
263 }
264 unsafe { alpha2_native_gpu_vulkan_ticket_reset(ticket, status) }
266}
267
268use crate::interop::Alpha2VulkanEncodeCopy as VulkanEncodeCopy;
269
270pub struct VulkanEncodeCopier {
277 copier: *mut c_void,
278 queue_lock: alpha2_ffmpeg_bridge::VulkanQueueLock,
281 retained: Option<Box<RetainedEncodeWork>>,
282 close_failed: bool,
283}
284
285struct RetainedEncodeWork {
288 copies: Vec<Option<EncodeCopyInputs>>,
289 _context: SharedVulkanContext,
290 #[cfg(test)]
291 lifetime_probe: Option<Arc<()>>,
292}
293
294struct EncodeCopyInputs {
295 _luma: wgpu::Texture,
296 _chroma: wgpu::Texture,
297 _frame: rsmpeg::avutil::AVFrame,
298}
299
300unsafe impl Send for VulkanEncodeCopier {}
303
304impl fmt::Debug for VulkanEncodeCopier {
305 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
306 formatter
307 .debug_struct("VulkanEncodeCopier")
308 .finish_non_exhaustive()
309 }
310}
311
312impl Drop for VulkanEncodeCopier {
313 fn drop(&mut self) {
314 if !self.copier.is_null() && !self.close_failed {
315 let _ = self.try_close();
316 }
317 if !self.copier.is_null() {
318 std::mem::forget(self.retained.take());
321 }
322 }
323}
324
325impl VulkanEncodeCopier {
326 pub fn finish(mut self) -> Result<(), BridgeError> {
332 self.try_close()
333 }
334
335 fn try_close(&mut self) -> Result<(), BridgeError> {
336 let mut status = 0;
337 let step =
340 unsafe { alpha2_native_gpu_vulkan_encode_copier_destroy(self.copier, &raw mut status) };
341 if step != 0 {
342 self.close_failed = true;
343 return Err(helper_error("encode copier teardown", step, status));
344 }
345 self.copier = std::ptr::null_mut();
346 Ok(())
349 }
350}
351
352impl VulkanEncodeCopier {
353 pub fn new(context: &SharedVulkanContext) -> Result<Self, BridgeError> {
359 let gpu = context.gpu();
360 let device = unsafe { gpu.device().as_hal::<wgpu_hal::api::Vulkan>() }
362 .map(|device| device.raw_device().handle().as_raw())
363 .ok_or(BridgeError::InvalidInput("renderer is not Vulkan"))?;
364 let mut copier = std::ptr::null_mut();
365 let mut status = 0;
366 let step = unsafe {
368 alpha2_native_gpu_vulkan_encode_copier_create(
369 device,
370 0,
371 0,
372 &raw mut copier,
373 &raw mut status,
374 )
375 };
376 if step != 0 {
377 return Err(helper_error("encode copier creation", step, status));
378 }
379 Ok(Self {
380 copier,
381 queue_lock: alpha2_ffmpeg_bridge::VulkanQueueLock::new(context.ffmpeg_device(), 0)?,
382 retained: Some(Box::new(RetainedEncodeWork {
383 copies: (0..Self::slots()).map(|_| None).collect(),
384 _context: context.clone(),
385 #[cfg(test)]
386 lifetime_probe: None,
387 })),
388 close_failed: false,
389 })
390 }
391
392 #[must_use]
398 pub fn slots() -> usize {
399 unsafe { alpha2_native_gpu_vulkan_encode_copier_slots() as usize }
401 }
402
403 pub fn await_slot(&mut self) -> Result<usize, BridgeError> {
412 let (mut slot, mut status) = (0_u32, 0);
413 let step = unsafe {
415 alpha2_native_gpu_vulkan_encode_copier_await_slot(
416 self.copier,
417 &raw mut slot,
418 &raw mut status,
419 )
420 };
421 if step != 0 {
422 return Err(helper_error("encode copy slot", step, status));
423 }
424 if let Some(retained) = self.retained.as_mut() {
425 retained.copies[slot as usize] = None;
426 }
427 Ok(slot as usize)
428 }
429
430 pub fn submit(
440 &mut self,
441 frame: &mut alpha2_ffmpeg_bridge::VulkanEncodeFrameAccess<'_>,
442 luma: &wgpu::Texture,
443 chroma: &wgpu::Texture,
444 size: (u32, u32),
445 ) -> Result<(), BridgeError> {
446 let slot = self.await_slot()?;
447 let inputs = EncodeCopyInputs {
448 _luma: luma.clone(),
449 _chroma: chroma.clone(),
450 _frame: frame.retain_frame()?,
451 };
452 let kept = self
453 .retained
454 .as_mut()
455 .and_then(|work| work.copies.get_mut(slot))
456 .ok_or(BridgeError::InvalidInput("invalid encode copier slot"))?;
457 let luma_image = unsafe { luma.as_hal::<wgpu_hal::api::Vulkan>() }
460 .map(|texture| unsafe { texture.raw_handle() }.as_raw())
461 .ok_or(BridgeError::InvalidInput("luma plane is not Vulkan"))?;
462 let chroma_image = unsafe { chroma.as_hal::<wgpu_hal::api::Vulkan>() }
464 .map(|texture| unsafe { texture.raw_handle() }.as_raw())
465 .ok_or(BridgeError::InvalidInput("chroma plane is not Vulkan"))?;
466 let copy = VulkanEncodeCopy {
467 luma_image,
468 chroma_image,
469 width: size.0,
470 height: size.1,
471 };
472 let queue = self.queue_lock.lock();
476 let mut status = 0;
477 let step = unsafe {
480 alpha2_native_gpu_vulkan_encode_copier_submit(
481 self.copier,
482 frame.access_mut(),
483 &raw const copy,
484 &raw mut status,
485 )
486 };
487 drop(queue);
488 if step != 0 {
489 return Err(helper_error("encode copy", step, status));
490 }
491 *kept = Some(inputs);
492 frame.commit();
493 Ok(())
494 }
495}
496
497pub fn probe_vulkan_submission(lease: &VulkanFrameLease) -> Result<(), BridgeError> {
503 let mut guard = lease.lock_access()?;
504 let mut status = 0;
505 let step = unsafe { alpha2_native_gpu_vulkan_probe(guard.access_mut(), &raw mut status) };
509 guard.commit();
512 if step != 0 {
513 return Err(helper_error("decode probe", step, status));
514 }
515 Ok(())
516}
517
518#[derive(Clone, Debug, Eq, PartialEq)]
520pub struct VideoEncodeExtent {
521 pub profile: &'static str,
524 pub minimum: (u32, u32),
526 pub maximum: (u32, u32),
528 pub granularity: (u32, u32),
532 pub max_p_picture_l0_references: u32,
534 pub max_b_picture_l0_references: u32,
537 pub max_l1_references: u32,
539 pub rate_control_modes: Vec<&'static str>,
542}
543
544impl VideoEncodeExtent {
545 #[must_use]
547 pub const fn supports_b_pictures(&self) -> bool {
548 self.max_b_picture_l0_references > 0 || self.max_l1_references > 0
549 }
550}
551
552impl VideoEncodeExtent {
553 #[must_use]
555 pub const fn accepts(&self, width: u32, height: u32) -> bool {
556 let coded = self.coded(width, height);
557 coded.0 >= self.minimum.0
558 && coded.1 >= self.minimum.1
559 && coded.0 <= self.maximum.0
560 && coded.1 <= self.maximum.1
561 }
562
563 #[must_use]
565 pub const fn coded(&self, width: u32, height: u32) -> (u32, u32) {
566 (
567 align_up(width, self.granularity.0),
568 align_up(height, self.granularity.1),
569 )
570 }
571}
572
573const fn align_up(value: u32, granularity: u32) -> u32 {
574 if granularity <= 1 {
575 return value;
576 }
577 value.div_ceil(granularity).saturating_mul(granularity)
578}
579
580const H264_ENCODE_PROFILES: [(vk::native::StdVideoH264ProfileIdc, &str); 3] = [
584 (
585 vk::native::StdVideoH264ProfileIdc_STD_VIDEO_H264_PROFILE_IDC_BASELINE,
586 "constrained_baseline",
587 ),
588 (
589 vk::native::StdVideoH264ProfileIdc_STD_VIDEO_H264_PROFILE_IDC_MAIN,
590 "main",
591 ),
592 (
593 vk::native::StdVideoH264ProfileIdc_STD_VIDEO_H264_PROFILE_IDC_HIGH,
594 "high",
595 ),
596];
597
598fn last_supported_profile<T>(
601 mut query: impl FnMut(vk::native::StdVideoH264ProfileIdc) -> Option<T>,
602) -> Option<(&'static str, T)> {
603 H264_ENCODE_PROFILES
604 .iter()
605 .filter_map(|&(idc, name)| query(idc).map(|answer| (name, answer)))
606 .last()
607}
608
609const ENCODE_EXTENSIONS: [(&str, &CStr, vk::VideoCodecOperationFlagsKHR); 2] = [
613 (
614 "h264",
615 ash::khr::video_encode_h264::NAME,
616 vk::VideoCodecOperationFlagsKHR::ENCODE_H264,
617 ),
618 (
619 "hevc",
620 ash::khr::video_encode_h265::NAME,
621 vk::VideoCodecOperationFlagsKHR::ENCODE_H265,
622 ),
623];
624
625fn encode_operations(codecs: &[&str]) -> vk::VideoCodecOperationFlagsKHR {
627 ENCODE_EXTENSIONS
628 .iter()
629 .filter(|(codec, _, _)| codecs.contains(codec))
630 .fold(
631 vk::VideoCodecOperationFlagsKHR::empty(),
632 |operations, (_, _, operation)| operations | *operation,
633 )
634}
635
636fn encode_extents(
643 entry: &ash::Entry,
644 instance: &ash::Instance,
645 physical: vk::PhysicalDevice,
646 codecs: &[&'static str],
647) -> Vec<(&'static str, VideoEncodeExtent)> {
648 let video = ash::khr::video_queue::Instance::new(entry, instance);
649 codecs
650 .iter()
651 .filter_map(|&codec| {
652 let extent = match codec {
653 "h264" => h264_encode_extent(&video, physical),
654 "hevc" => hevc_encode_extent(&video, physical),
655 _ => None,
656 }?;
657 Some((codec, extent))
658 })
659 .collect()
660}
661
662fn encode_capabilities<'a>(
665 video: &ash::khr::video_queue::Instance,
666 physical: vk::PhysicalDevice,
667 profile: &vk::VideoProfileInfoKHR<'_>,
668 per_codec: &mut (impl vk::ExtendsVideoCapabilitiesKHR + 'a),
669) -> Option<(
670 (vk::Extent2D, vk::Extent2D),
671 vk::VideoEncodeCapabilitiesKHR<'a>,
672)> {
673 let mut encode = vk::VideoEncodeCapabilitiesKHR::default();
674 let mut capabilities = vk::VideoCapabilitiesKHR::default()
675 .push_next(&mut encode)
676 .push_next(per_codec);
677 let result = unsafe {
681 (video.fp().get_physical_device_video_capabilities_khr)(
682 physical,
683 std::ptr::from_ref(profile),
684 &raw mut capabilities,
685 )
686 };
687 let extents = (capabilities.min_coded_extent, capabilities.max_coded_extent);
688 (result == vk::Result::SUCCESS).then(|| {
689 encode.p_next = std::ptr::null_mut();
690 (extents, encode)
691 })
692}
693
694fn h264_encode_extent(
696 video: &ash::khr::video_queue::Instance,
697 physical: vk::PhysicalDevice,
698) -> Option<VideoEncodeExtent> {
699 let (profile, mut extent) = last_supported_profile(|idc| {
700 let mut h264 = vk::VideoEncodeH264ProfileInfoKHR::default().std_profile_idc(idc);
701 let profile = vk::VideoProfileInfoKHR::default()
702 .video_codec_operation(vk::VideoCodecOperationFlagsKHR::ENCODE_H264)
703 .chroma_subsampling(vk::VideoChromaSubsamplingFlagsKHR::TYPE_420)
704 .luma_bit_depth(vk::VideoComponentBitDepthFlagsKHR::TYPE_8)
705 .chroma_bit_depth(vk::VideoComponentBitDepthFlagsKHR::TYPE_8)
706 .push_next(&mut h264);
707 let mut h264_capabilities = vk::VideoEncodeH264CapabilitiesKHR::default();
708 let (coded, encode) =
709 encode_capabilities(video, physical, &profile, &mut h264_capabilities)?;
710 Some(encode_extent(
711 coded,
712 &encode,
713 (
714 h264_capabilities.max_p_picture_l0_reference_count,
715 h264_capabilities.max_b_picture_l0_reference_count,
716 h264_capabilities.max_l1_reference_count,
717 ),
718 ))
719 })?;
720 extent.profile = profile;
721 Some(extent)
722}
723
724fn hevc_encode_extent(
726 video: &ash::khr::video_queue::Instance,
727 physical: vk::PhysicalDevice,
728) -> Option<VideoEncodeExtent> {
729 let mut hevc = vk::VideoEncodeH265ProfileInfoKHR::default()
730 .std_profile_idc(vk::native::StdVideoH265ProfileIdc_STD_VIDEO_H265_PROFILE_IDC_MAIN);
731 let profile = vk::VideoProfileInfoKHR::default()
732 .video_codec_operation(vk::VideoCodecOperationFlagsKHR::ENCODE_H265)
733 .chroma_subsampling(vk::VideoChromaSubsamplingFlagsKHR::TYPE_420)
734 .luma_bit_depth(vk::VideoComponentBitDepthFlagsKHR::TYPE_8)
735 .chroma_bit_depth(vk::VideoComponentBitDepthFlagsKHR::TYPE_8)
736 .push_next(&mut hevc);
737 let mut hevc_capabilities = vk::VideoEncodeH265CapabilitiesKHR::default();
738 let (coded, encode) = encode_capabilities(video, physical, &profile, &mut hevc_capabilities)?;
739 let mut extent = encode_extent(
740 coded,
741 &encode,
742 (
743 hevc_capabilities.max_p_picture_l0_reference_count,
744 hevc_capabilities.max_b_picture_l0_reference_count,
745 hevc_capabilities.max_l1_reference_count,
746 ),
747 );
748 extent.profile = "main";
749 Some(extent)
750}
751
752fn encode_extent(
754 (minimum, maximum): (vk::Extent2D, vk::Extent2D),
755 encode: &vk::VideoEncodeCapabilitiesKHR<'_>,
756 (p_l0, b_l0, l1): (u32, u32, u32),
757) -> VideoEncodeExtent {
758 VideoEncodeExtent {
759 profile: "",
760 minimum: (minimum.width, minimum.height),
761 maximum: (maximum.width, maximum.height),
762 granularity: (
765 encode.encode_input_picture_granularity.width.max(1),
766 encode.encode_input_picture_granularity.height.max(1),
767 ),
768 max_p_picture_l0_references: p_l0,
769 max_b_picture_l0_references: b_l0,
770 max_l1_references: l1,
771 rate_control_modes: [
772 (vk::VideoEncodeRateControlModeFlagsKHR::CBR, "cbr"),
773 (vk::VideoEncodeRateControlModeFlagsKHR::VBR, "vbr"),
774 (vk::VideoEncodeRateControlModeFlagsKHR::DISABLED, "disabled"),
775 ]
776 .into_iter()
777 .filter(|(flag, _)| encode.rate_control_modes.contains(*flag))
778 .map(|(_, name)| name)
779 .collect(),
780 }
781}
782
783#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
785pub struct VulkanLayerVersion {
786 pub major: u32,
788 pub minor: u32,
790 pub patch: u32,
792}
793
794impl VulkanLayerVersion {
795 pub const VIDEO_SESSIONS: Self = Self {
798 major: 1,
799 minor: 4,
800 patch: 0,
801 };
802
803 #[must_use]
805 pub const fn from_packed(version: u32) -> Self {
806 Self {
807 major: vk::api_version_major(version),
808 minor: vk::api_version_minor(version),
809 patch: vk::api_version_patch(version),
810 }
811 }
812}
813
814impl fmt::Display for VulkanLayerVersion {
815 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
816 write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch)
817 }
818}
819
820const KHRONOS_VALIDATION: &str = "VK_LAYER_KHRONOS_validation";
821
822fn activated_validation_layer(
829 instance: &ash::Instance,
830 physical: vk::PhysicalDevice,
831) -> Option<VulkanLayerVersion> {
832 let layers = unsafe { instance.enumerate_device_layer_properties(physical) }.ok()?;
834 layers
835 .iter()
836 .find(|layer| {
837 layer
838 .layer_name_as_c_str()
839 .is_ok_and(|name| name.to_bytes() == KHRONOS_VALIDATION.as_bytes())
840 })
841 .map(|layer| VulkanLayerVersion::from_packed(layer.spec_version))
842}
843
844fn hal_instance() -> Result<wgpu_hal::vulkan::Instance, SharedVulkanError> {
847 let hal_desc = wgpu_hal::InstanceDescriptor {
848 name: "alpha2 shared Vulkan decode",
849 flags: wgpu::InstanceFlags::from_env_or_default(),
850 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
851 backend_options: wgpu::BackendOptions::from_env_or_default(),
852 telemetry: None,
853 display: None,
854 };
855 unsafe {
857 wgpu_hal::vulkan::Instance::init(&hal_desc)
858 .map_err(|error| SharedVulkanError(format!("Vulkan instance: {error}")))
859 }
860}
861
862#[derive(Debug)]
868struct SharedQueueLock(VulkanQueueLock);
869
870impl ExternalQueueLock for SharedQueueLock {
871 fn lock(&self) {
872 unsafe { self.0.raw_lock() };
875 }
876
877 fn unlock(&self) {
878 unsafe { self.0.raw_unlock() };
880 }
881}
882
883#[derive(Debug)]
885pub struct SharedVulkanError(String);
886
887impl fmt::Display for SharedVulkanError {
888 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
889 formatter.write_str(&self.0)
890 }
891}
892
893impl Error for SharedVulkanError {}
894
895impl SharedVulkanError {
896 #[must_use]
898 pub fn new(message: impl Into<String>) -> Self {
899 Self(message.into())
900 }
901}
902
903#[derive(Debug)]
905pub struct ImportedVulkanFrame {
906 pub y: wgpu::TextureView,
908 pub uv: wgpu::TextureView,
910 pub layout: NativePlaneLayout,
912 pub coded_size: [u32; 2],
915 pub lease: Arc<VulkanImportLease>,
917}
918
919#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
921pub struct ImportTicketStats {
922 pub created: u64,
924 pub reused: u64,
926 pub idle: usize,
928 pub pending: usize,
930 pub abandoned: u64,
932}
933
934struct PooledTicket {
936 ticket: NonNull<c_void>,
937 device: u64,
938 queue_family: u32,
939}
940
941#[derive(Default)]
947struct ImportTicketPool {
948 state: Mutex<ImportTicketPoolState>,
949}
950
951#[derive(Default)]
952struct ImportTicketPoolState {
953 idle: Vec<PooledTicket>,
954 pending: VecDeque<PooledTicket>,
955 created: u64,
956 reused: u64,
957 abandoned: u64,
958}
959
960const MAX_IDLE_IMPORT_TICKETS: usize = 8;
962const MAX_PENDING_IMPORT_TICKETS: usize = 32;
963
964unsafe impl Send for ImportTicketPool {}
967unsafe impl Sync for ImportTicketPool {}
969
970impl ImportTicketPool {
971 fn take(
972 &self,
973 device: u64,
974 allocator: u64,
975 queue_family: u32,
976 ) -> Result<PooledTicket, BridgeError> {
977 let mut state = self
978 .state
979 .lock()
980 .unwrap_or_else(std::sync::PoisonError::into_inner);
981 state.reclaim();
982 if let Some(index) = state
983 .idle
984 .iter()
985 .position(|idle| idle.device == device && idle.queue_family == queue_family)
986 {
987 state.reused = state.reused.saturating_add(1);
988 return Ok(state.idle.swap_remove(index));
989 }
990 state.created = state.created.saturating_add(1);
991 drop(state);
992 let (mut raw, mut status) = (std::ptr::null_mut(), 0);
993 let step = unsafe {
996 alpha2_native_gpu_vulkan_ticket_create(
997 device,
998 allocator,
999 queue_family,
1000 &raw mut raw,
1001 &raw mut status,
1002 )
1003 };
1004 if step != 0 {
1005 return Err(helper_error("import ticket creation", step, status));
1006 }
1007 NonNull::new(raw)
1008 .map(|ticket| PooledTicket {
1009 ticket,
1010 device,
1011 queue_family,
1012 })
1013 .ok_or(BridgeError::InvalidInput(
1014 "Vulkan import ticket creation returned no ticket",
1015 ))
1016 }
1017
1018 fn give_back(&self, ticket: PooledTicket) {
1021 let mut state = self
1022 .state
1023 .lock()
1024 .unwrap_or_else(std::sync::PoisonError::into_inner);
1025 state.pending.push_back(ticket);
1026 if state.pending.len() > MAX_PENDING_IMPORT_TICKETS {
1027 state.reclaim();
1028 }
1029 let excess = state
1030 .pending
1031 .len()
1032 .saturating_sub(MAX_PENDING_IMPORT_TICKETS);
1033 let oldest = state.pending.drain(..excess).collect::<Vec<_>>();
1034 state.abandoned = state.abandoned.saturating_add(oldest.len() as u64);
1035 drop(state);
1037 retire(oldest);
1038 }
1039
1040 fn stats(&self) -> ImportTicketStats {
1041 let state = self
1042 .state
1043 .lock()
1044 .unwrap_or_else(std::sync::PoisonError::into_inner);
1045 ImportTicketStats {
1046 created: state.created,
1047 reused: state.reused,
1048 idle: state.idle.len(),
1049 pending: state.pending.len(),
1050 abandoned: state.abandoned,
1051 }
1052 }
1053}
1054
1055impl ImportTicketPoolState {
1056 fn reclaim(&mut self) {
1059 let mut index = 0;
1060 while index < self.pending.len() {
1061 let ticket = self.pending[index].ticket;
1062 let mut status = 0;
1063 match unsafe { ticket_poll(ticket.as_ptr(), &mut status) } {
1065 0 => index += 1,
1066 1 => {
1067 let Some(finished) = self.pending.remove(index) else {
1068 break;
1069 };
1070 let reset = unsafe { ticket_reset(ticket.as_ptr(), &mut status) };
1072 if reset != 0 {
1073 self.abandoned = self.abandoned.saturating_add(1);
1074 } else if self.idle.len() < MAX_IDLE_IMPORT_TICKETS {
1075 self.idle.push(finished);
1076 } else {
1077 unsafe { alpha2_native_gpu_vulkan_import_destroy(ticket.as_ptr()) };
1079 }
1080 }
1081 _ => {
1082 self.pending.remove(index);
1085 self.abandoned = self.abandoned.saturating_add(1);
1086 }
1087 }
1088 }
1089 }
1090}
1091
1092impl Drop for ImportTicketPool {
1093 fn drop(&mut self) {
1094 let state = self
1095 .state
1096 .get_mut()
1097 .unwrap_or_else(std::sync::PoisonError::into_inner);
1098 for idle in state.idle.drain(..) {
1099 unsafe { alpha2_native_gpu_vulkan_import_destroy(idle.ticket.as_ptr()) };
1101 }
1102 retire(state.pending.drain(..));
1103 }
1104}
1105
1106fn retire(tickets: impl IntoIterator<Item = PooledTicket>) {
1109 #[cfg(test)]
1110 inject::retire_begins();
1111 let deadline = std::time::Instant::now().checked_add(wait_timeout());
1112 for pooled in tickets {
1113 let left = deadline.map_or(std::time::Duration::MAX, |deadline| {
1114 deadline.saturating_duration_since(std::time::Instant::now())
1115 });
1116 let nanoseconds = u64::try_from(left.as_nanos()).unwrap_or(u64::MAX);
1117 unsafe { alpha2_native_gpu_vulkan_import_retire(pooled.ticket.as_ptr(), nanoseconds) };
1120 }
1121}
1122
1123pub struct VulkanImportLease {
1125 frame: Arc<VulkanFrameLease>,
1126 _texture: wgpu::Texture,
1127 gpu: GpuContext,
1128 batch: Arc<VulkanSubmissionBatch>,
1129 tickets: Arc<ImportTicketPool>,
1130 ticket: Option<PooledTicket>,
1132 staged_signals: Mutex<Vec<StagedSignal>>,
1133 queued: AtomicBool,
1134 acquired: AtomicBool,
1135 released: AtomicBool,
1136 retained_bytes: u64,
1137}
1138
1139#[derive(Clone, Copy, Debug)]
1140struct StagedSignal {
1141 semaphore: u64,
1142 acquired_value: u64,
1143 signal_value: u64,
1144}
1145
1146#[derive(Default)]
1147struct VulkanSubmissionBatch {
1148 pending: Mutex<Vec<Weak<VulkanImportLease>>>,
1149}
1150
1151impl VulkanSubmissionBatch {
1152 fn enqueue(&self, lease: &Arc<VulkanImportLease>) {
1153 if !lease.queued.swap(true, Ordering::AcqRel) {
1154 self.pending
1155 .lock()
1156 .unwrap_or_else(std::sync::PoisonError::into_inner)
1157 .push(Arc::downgrade(lease));
1158 }
1159 }
1160
1161 fn flush(&self) -> Result<(), BridgeError> {
1162 let leases = self
1163 .pending
1164 .lock()
1165 .unwrap_or_else(std::sync::PoisonError::into_inner)
1166 .drain(..)
1167 .filter_map(|lease| lease.upgrade())
1168 .filter(|lease| !lease.acquired.load(Ordering::Acquire))
1169 .collect::<Vec<_>>();
1170 if leases.is_empty() {
1171 return Ok(());
1172 }
1173 let result = flush_leases(&leases);
1176 if result.is_err() {
1177 for lease in &leases {
1178 if !lease.acquired.load(Ordering::Acquire) {
1179 lease.queued.store(false, Ordering::Release);
1180 }
1181 }
1182 }
1183 result
1184 }
1185}
1186
1187fn flush_leases(leases: &[Arc<VulkanImportLease>]) -> Result<(), BridgeError> {
1202 for lease in leases {
1203 let mut access = lease.lock()?;
1204 if lease.acquired.load(Ordering::Acquire) {
1206 continue;
1207 }
1208 let mut status = 0;
1209 let step = unsafe { acquire_submit(&mut access, &mut status) };
1212 if step != 0 {
1213 return Err(helper_error("import acquire", step, status));
1214 }
1215 lease.acquired.store(true, Ordering::Release);
1216 access.frame.commit();
1218 lease.stage_submission_signal(unsafe { access.frame.access_mut() })?;
1222 }
1223 Ok(())
1224}
1225
1226unsafe impl Send for VulkanImportLease {}
1231unsafe impl Sync for VulkanImportLease {}
1233
1234impl fmt::Debug for VulkanImportLease {
1235 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1236 formatter
1237 .debug_struct("VulkanImportLease")
1238 .field("released", &self.released.load(Ordering::Acquire))
1239 .field("retained_bytes", &self.retained_bytes)
1240 .finish_non_exhaustive()
1241 }
1242}
1243
1244impl VulkanImportLease {
1245 fn lock(&self) -> Result<LeaseAccess<'_>, BridgeError> {
1247 let ticket = self
1248 .ticket
1249 .as_ref()
1250 .map(|pooled| pooled.ticket)
1251 .ok_or(BridgeError::InvalidInput("missing Vulkan import ticket"))?;
1252 Ok(LeaseAccess {
1253 frame: self.frame.lock_access()?,
1254 ticket,
1255 })
1256 }
1257
1258 #[must_use]
1260 pub const fn retained_bytes(&self) -> u64 {
1261 self.retained_bytes
1262 }
1263
1264 #[must_use]
1268 pub fn staged_signals(&self) -> usize {
1269 self.staged_signals
1270 .lock()
1271 .unwrap_or_else(std::sync::PoisonError::into_inner)
1272 .len()
1273 }
1274
1275 pub fn prepare_submission(self: &Arc<Self>) {
1278 self.batch.enqueue(self);
1279 }
1280
1281 pub fn flush_submission(&self) -> Result<(), BridgeError> {
1288 self.batch.flush()
1289 }
1290
1291 fn stage_submission_signal(&self, access: &mut VulkanFrameAccess) -> Result<(), BridgeError> {
1294 let mut staged = self
1295 .staged_signals
1296 .lock()
1297 .unwrap_or_else(std::sync::PoisonError::into_inner);
1298 if !staged.is_empty() || self.released.load(Ordering::Acquire) {
1299 return Ok(());
1300 }
1301 let queue = unsafe { self.gpu.queue().as_hal::<wgpu_hal::api::Vulkan>() }
1303 .ok_or(BridgeError::InvalidInput("renderer queue is not Vulkan"))?;
1304 let descriptor = access.frame_mut();
1305 for index in 0..descriptor.images.len() {
1306 if descriptor.images[index] == 0 {
1307 continue;
1308 }
1309 let semaphore = descriptor.semaphores[index];
1310 let acquired_value = descriptor.semaphore_values[index];
1311 if semaphore == 0 {
1312 return Err(BridgeError::InvalidInput("frame has no timeline semaphore"));
1313 }
1314 if let Some(existing) = staged
1315 .iter_mut()
1316 .find(|existing| existing.semaphore == semaphore)
1317 {
1318 existing.acquired_value = existing.acquired_value.max(acquired_value);
1319 existing.signal_value = existing.acquired_value.saturating_add(1);
1320 } else {
1321 staged.push(StagedSignal {
1322 semaphore,
1323 acquired_value,
1324 signal_value: acquired_value.saturating_add(1),
1325 });
1326 }
1327 }
1328 for signal in &*staged {
1329 queue.add_signal_semaphore(
1330 vk::Semaphore::from_raw(signal.semaphore),
1331 Some(signal.signal_value),
1332 );
1333 for index in 0..descriptor.images.len() {
1334 if descriptor.semaphores[index] == signal.semaphore {
1335 descriptor.semaphore_values[index] = signal.signal_value;
1336 }
1337 }
1338 }
1339 Ok(())
1340 }
1341
1342 pub fn submitted(&self) -> Result<(), BridgeError> {
1351 if self.released.load(Ordering::Acquire) {
1352 return Ok(());
1353 }
1354 if !self
1355 .staged_signals
1356 .lock()
1357 .unwrap_or_else(std::sync::PoisonError::into_inner)
1358 .is_empty()
1359 {
1360 self.released.store(true, Ordering::Release);
1361 return Ok(());
1362 }
1363 let mut access = self.lock()?;
1364 if self.released.load(Ordering::Acquire) {
1366 return Ok(());
1367 }
1368 let mut status = 0;
1369 let step = unsafe { release(&mut access, &mut status) };
1372 if step != 0 {
1373 return Err(helper_error("import release", step, status));
1374 }
1375 access.frame.commit();
1376 self.released.store(true, Ordering::Release);
1377 Ok(())
1378 }
1379}
1380
1381impl Drop for VulkanImportLease {
1382 fn drop(&mut self) {
1394 let Some(pooled) = self.ticket.take() else {
1395 return;
1396 };
1397 if self.acquired.load(Ordering::Acquire) && !self.released.load(Ordering::Acquire) {
1398 let staged = std::mem::take(
1399 self.staged_signals
1400 .get_mut()
1401 .unwrap_or_else(std::sync::PoisonError::into_inner),
1402 );
1403 if let Ok(frame) = self.frame.lock_access() {
1404 let mut lease = LeaseAccess {
1406 frame,
1407 ticket: pooled.ticket,
1408 };
1409 if staged.is_empty() {
1410 let mut status = 0;
1411 if unsafe { release(&mut lease, &mut status) } == 0 {
1413 lease.frame.commit();
1414 }
1415 } else {
1416 let queue = unsafe { self.gpu.queue().as_hal::<wgpu_hal::api::Vulkan>() };
1418 for signal in &staged {
1419 let unsubmitted = queue.as_ref().is_some_and(|queue| {
1420 queue.remove_signal_semaphore(vk::Semaphore::from_raw(signal.semaphore))
1421 });
1422 if unsubmitted {
1423 let mut status = 0;
1424 let _ = unsafe {
1428 alpha2_native_gpu_vulkan_signal(
1429 lease.frame.access_mut(),
1430 signal.semaphore,
1431 signal.acquired_value,
1432 signal.signal_value,
1433 &raw mut status,
1434 )
1435 };
1436 }
1437 }
1438 }
1439 }
1440 }
1441 self.tickets.give_back(pooled);
1442 }
1443}
1444
1445#[derive(Clone, Copy, Debug, Default)]
1452#[allow(clippy::struct_excessive_bools)]
1453struct FfmpegDeviceFeatures {
1454 synchronization2: bool,
1455 sampler_ycbcr_conversion: bool,
1456 video_maintenance1: bool,
1457 video_decode_vp9: bool,
1458}
1459
1460fn ffmpeg_device_features(
1463 instance: &ash::Instance,
1464 physical: vk::PhysicalDevice,
1465 video_maintenance1: bool,
1466 video_decode_vp9: bool,
1467) -> FfmpegDeviceFeatures {
1468 let mut synchronization2 = vk::PhysicalDeviceSynchronization2Features::default();
1469 let mut ycbcr = vk::PhysicalDeviceSamplerYcbcrConversionFeatures::default();
1470 let mut maintenance = vk::PhysicalDeviceVideoMaintenance1FeaturesKHR::default();
1471 let mut vp9 = VideoDecodeVp9Features::default();
1472 let mut features = vk::PhysicalDeviceFeatures2::default()
1473 .push_next(&mut synchronization2)
1474 .push_next(&mut ycbcr);
1475 if video_maintenance1 {
1476 features = features.push_next(&mut maintenance);
1477 }
1478 if video_decode_vp9 {
1479 features = features.push_next(&mut vp9);
1480 }
1481 unsafe { instance.get_physical_device_features2(physical, &mut features) };
1484 FfmpegDeviceFeatures {
1485 synchronization2: synchronization2.synchronization2 == vk::TRUE,
1486 sampler_ycbcr_conversion: ycbcr.sampler_ycbcr_conversion == vk::TRUE,
1487 video_maintenance1: video_maintenance1 && maintenance.video_maintenance1 == vk::TRUE,
1488 video_decode_vp9: video_decode_vp9 && vp9.video_decode_vp9 == vk::TRUE,
1489 }
1490}
1491
1492const VIDEO_DECODE_VP9_NAME: &CStr = c"VK_KHR_video_decode_vp9";
1495const DECODE_VP9: vk::VideoCodecOperationFlagsKHR = vk::VideoCodecOperationFlagsKHR::from_raw(0x8);
1496const VIDEO_DECODE_VP9_FEATURES: vk::StructureType = vk::StructureType::from_raw(1_000_514_000);
1497
1498#[repr(C)]
1500#[derive(Clone, Copy, Debug)]
1501struct VideoDecodeVp9Features {
1502 s_type: vk::StructureType,
1503 p_next: *mut c_void,
1504 video_decode_vp9: vk::Bool32,
1505}
1506
1507impl Default for VideoDecodeVp9Features {
1508 fn default() -> Self {
1509 Self {
1510 s_type: VIDEO_DECODE_VP9_FEATURES,
1511 p_next: std::ptr::null_mut(),
1512 video_decode_vp9: vk::FALSE,
1513 }
1514 }
1515}
1516
1517unsafe impl vk::ExtendsPhysicalDeviceFeatures2 for VideoDecodeVp9Features {}
1520unsafe impl vk::ExtendsDeviceCreateInfo for VideoDecodeVp9Features {}
1522
1523impl FfmpegDeviceFeatures {
1524 fn enable(self, create_info: &mut vk::DeviceCreateInfo<'_>) {
1528 let mut info = std::mem::take(create_info);
1529 if self.synchronization2 {
1530 info = info.push_next(Box::leak(Box::new(
1531 vk::PhysicalDeviceSynchronization2Features::default().synchronization2(true),
1532 )));
1533 }
1534 if self.sampler_ycbcr_conversion {
1535 info = info.push_next(Box::leak(Box::new(
1536 vk::PhysicalDeviceSamplerYcbcrConversionFeatures::default()
1537 .sampler_ycbcr_conversion(true),
1538 )));
1539 }
1540 if self.video_maintenance1 {
1541 info = info.push_next(Box::leak(Box::new(
1542 vk::PhysicalDeviceVideoMaintenance1FeaturesKHR::default().video_maintenance1(true),
1543 )));
1544 }
1545 if self.video_decode_vp9 {
1546 info = info.push_next(Box::leak(Box::new(VideoDecodeVp9Features {
1547 video_decode_vp9: vk::TRUE,
1548 ..VideoDecodeVp9Features::default()
1549 })));
1550 }
1551 *create_info = info;
1552 }
1553}
1554
1555#[derive(Clone)]
1557pub struct SharedVulkanContext {
1558 import_tickets: Arc<ImportTicketPool>,
1561 ffmpeg_device: AVHWDeviceContext,
1563 gpu: GpuContext,
1564 submission_batch: Arc<VulkanSubmissionBatch>,
1565 identity: NativeGpuIdentity,
1566 decode_codecs: Vec<&'static str>,
1567 encode_codecs: Vec<&'static str>,
1568 encode_extents: Vec<(&'static str, VideoEncodeExtent)>,
1569 validation_layer: Option<VulkanLayerVersion>,
1570}
1571
1572impl fmt::Debug for SharedVulkanContext {
1573 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1574 formatter
1575 .debug_struct("SharedVulkanContext")
1576 .field("identity", &self.identity)
1577 .field("decode_codecs", &self.decode_codecs)
1578 .field("encode_codecs", &self.encode_codecs)
1579 .finish_non_exhaustive()
1580 }
1581}
1582
1583impl SharedVulkanContext {
1584 pub fn create() -> Result<Self, SharedVulkanError> {
1590 let choice = choose_shared_vulkan(&alpha2_gpu::AdapterPolicy::from_env(), true, true)?;
1591 choice.context.ok_or_else(|| {
1592 SharedVulkanError(
1593 choice
1594 .unavailable
1595 .unwrap_or_else(|| choice.report.explanation.clone()),
1596 )
1597 })
1598 }
1599
1600 #[allow(clippy::too_many_lines)]
1607 pub fn create_on(index: usize) -> Result<Self, SharedVulkanError> {
1608 let _turn = alpha2_gpu::serialize_acquisition();
1609 let hal_instance = hal_instance()?;
1610 let mut adapters = unsafe { hal_instance.enumerate_adapters(None) };
1612 if index >= adapters.len() {
1613 return Err(SharedVulkanError(format!(
1614 "there is no Vulkan adapter {index}; there are {}",
1615 adapters.len()
1616 )));
1617 }
1618 let exposed = adapters.swap_remove(index);
1619 let physical = exposed.adapter.raw_physical_device();
1620 let shared_instance = exposed.adapter.shared_instance();
1621 let raw_instance = shared_instance.raw_instance().handle();
1622 let validation_layer = activated_validation_layer(shared_instance.raw_instance(), physical);
1623 let VideoPlan {
1624 decode_family,
1625 encode_family,
1626 mut video_extensions,
1627 mut decode_caps,
1628 mut decode_codecs,
1629 encode_codecs,
1630 } = adapter_video_plan(shared_instance.raw_instance(), &exposed)
1631 .map_err(SharedVulkanError)?;
1632 let graphics_family = 0;
1633 let encode_extents = if encode_family.is_some() {
1637 encode_extents(
1638 shared_instance.entry(),
1639 shared_instance.raw_instance(),
1640 physical,
1641 &encode_codecs,
1642 )
1643 } else {
1644 Vec::new()
1645 };
1646 let required_features = (exposed.features
1649 & (wgpu::Features::TIMESTAMP_QUERY
1650 | wgpu::Features::TEXTURE_FORMAT_P010
1651 | wgpu::Features::TEXTURE_FORMAT_16BIT_NORM))
1652 | wgpu::Features::TEXTURE_FORMAT_NV12;
1653 let required_limits = wgpu::Limits::downlevel_webgl2_defaults()
1654 .using_resolution(exposed.capabilities.limits.clone());
1655 let enabled_extensions = exposed
1656 .adapter
1657 .required_device_extensions(required_features);
1658 let core = exposed
1659 .adapter
1660 .physical_device_features(&enabled_extensions, required_features)
1661 .get_core();
1662 let ffmpeg_features = ffmpeg_device_features(
1663 shared_instance.raw_instance(),
1664 physical,
1665 video_extensions.contains(&ash::khr::video_maintenance1::NAME),
1666 video_extensions.contains(&VIDEO_DECODE_VP9_NAME),
1667 );
1668 if !ffmpeg_features.video_decode_vp9 {
1670 video_extensions.retain(|extension| *extension != VIDEO_DECODE_VP9_NAME);
1671 decode_caps &= !DECODE_VP9;
1672 decode_codecs.retain(|codec| *codec != "vp9");
1673 }
1674 let extra_family = decode_family;
1675 let extra_encode_family =
1676 encode_family.filter(|family| *family != graphics_family && *family != decode_family);
1677 let extra_extensions = video_extensions.clone();
1678 let open = unsafe {
1680 exposed.adapter.open_with_callback(
1681 required_features,
1682 &required_limits,
1683 &wgpu::MemoryHints::Performance,
1684 Some(Box::new(move |args| {
1685 ffmpeg_features.enable(args.create_info);
1686 for extension in extra_extensions {
1687 if !args.extensions.contains(&extension) {
1688 args.extensions.push(extension);
1689 }
1690 }
1691 if extra_family != 0 {
1692 args.queue_create_infos.push(
1693 vk::DeviceQueueCreateInfo::default()
1694 .queue_family_index(extra_family)
1695 .queue_priorities(&[1.0]),
1696 );
1697 }
1698 if let Some(family) = extra_encode_family {
1699 args.queue_create_infos.push(
1700 vk::DeviceQueueCreateInfo::default()
1701 .queue_family_index(family)
1702 .queue_priorities(&[1.0]),
1703 );
1704 }
1705 })),
1706 )
1707 }
1708 .map_err(|error| SharedVulkanError(format!("shared Vulkan device: {error}")))?;
1709 let raw_device = open.device.raw_device().handle();
1710 let device_extensions = open
1711 .device
1712 .enabled_device_extensions()
1713 .iter()
1714 .map(|name| name.to_string_lossy().into_owned())
1715 .collect::<Vec<_>>();
1716 let instance_extensions = shared_instance
1717 .extensions()
1718 .iter()
1719 .map(|name| name.to_string_lossy().into_owned())
1720 .collect::<Vec<_>>();
1721 let ffmpeg_device = unsafe {
1724 create_vulkan_device_context(&VulkanDeviceInfo {
1725 instance: raw_instance.as_raw(),
1726 physical_device: physical.as_raw(),
1727 device: raw_device.as_raw(),
1728 graphics_queue_family: 0,
1729 graphics_queue_count: 1,
1730 decode_queue_family: decode_family,
1731 decode_queue_count: 1,
1732 decode_video_caps: decode_caps.as_raw(),
1733 encode_queue_family: encode_family.unwrap_or(0),
1734 encode_queue_count: u32::from(encode_family.is_some()),
1735 encode_video_caps: encode_family
1736 .map_or(0, |_| encode_operations(&encode_codecs).as_raw()),
1737 core_features: [
1738 core.shader_image_gather_extended,
1739 core.fragment_stores_and_atomics,
1740 core.shader_int64,
1741 core.vertex_pipeline_stores_and_atomics,
1742 ],
1743 synchronization2: u32::from(ffmpeg_features.synchronization2),
1744 sampler_ycbcr_conversion: u32::from(ffmpeg_features.sampler_ycbcr_conversion),
1745 video_maintenance1: u32::from(ffmpeg_features.video_maintenance1),
1746 video_decode_vp9: u32::from(ffmpeg_features.video_decode_vp9),
1747 instance_extensions,
1748 device_extensions,
1749 })
1750 }
1751 .map_err(|error| SharedVulkanError(format!("FFmpeg Vulkan device: {error}")))?;
1752 let wgpu_instance =
1754 unsafe { wgpu::Instance::from_hal::<wgpu_hal::api::Vulkan>(hal_instance) };
1755 let adapter = unsafe { wgpu_instance.create_adapter_from_hal(exposed) };
1757 let descriptor = wgpu::DeviceDescriptor {
1758 label: Some("alpha2 shared Vulkan decode"),
1759 required_features,
1760 required_limits,
1761 ..Default::default()
1762 };
1763 let (device, queue) = unsafe { adapter.create_device_from_hal(open, &descriptor) }
1765 .map_err(|error| SharedVulkanError(format!("wgpu adoption: {error}")))?;
1766 let queue_lock: Arc<dyn ExternalQueueLock> = Arc::new(SharedQueueLock(
1769 VulkanQueueLock::new(ffmpeg_device.clone(), 0)
1770 .map_err(|error| SharedVulkanError(format!("FFmpeg Vulkan queue lock: {error}")))?,
1771 ));
1772 let gpu = GpuContext::from_parts_with_queue_lock(adapter, device, queue, Some(queue_lock));
1773 Ok(Self {
1774 ffmpeg_device,
1775 gpu,
1776 submission_batch: Arc::new(VulkanSubmissionBatch::default()),
1777 import_tickets: Arc::new(ImportTicketPool::default()),
1778 identity: NativeGpuIdentity {
1779 physical_device: physical.as_raw(),
1780 device: raw_device.as_raw(),
1781 epoch: 1,
1782 },
1783 decode_codecs,
1784 encode_extents,
1785 encode_codecs,
1786 validation_layer,
1787 })
1788 }
1789
1790 #[must_use]
1792 pub fn gpu(&self) -> GpuContext {
1793 self.gpu.clone()
1794 }
1795
1796 #[must_use]
1798 pub fn ffmpeg_device(&self) -> AVHWDeviceContext {
1799 self.ffmpeg_device.clone()
1800 }
1801
1802 #[must_use]
1804 pub const fn identity(&self) -> NativeGpuIdentity {
1805 self.identity
1806 }
1807
1808 #[must_use]
1810 pub fn supports_ten_bit(&self) -> bool {
1811 self.gpu
1812 .device()
1813 .features()
1814 .contains(wgpu::Features::TEXTURE_FORMAT_P010)
1815 }
1816
1817 #[must_use]
1820 pub fn decode_codecs(&self) -> &[&'static str] {
1821 &self.decode_codecs
1822 }
1823
1824 #[must_use]
1827 pub const fn validation_layer(&self) -> Option<VulkanLayerVersion> {
1828 self.validation_layer
1829 }
1830
1831 #[must_use]
1836 pub fn video_session_hazard(&self) -> Option<String> {
1837 self.validation_layer
1838 .filter(|layer| *layer < VulkanLayerVersion::VIDEO_SESSIONS)
1839 .map(|layer| {
1840 format!(
1841 "the loaded Vulkan validation layer is {layer}, and layers older than {} \
1842 crash in FFmpeg's video session setup",
1843 VulkanLayerVersion::VIDEO_SESSIONS
1844 )
1845 })
1846 }
1847
1848 #[must_use]
1851 pub fn encode_codecs(&self) -> &[&'static str] {
1852 &self.encode_codecs
1853 }
1854
1855 #[must_use]
1860 pub fn encode_extent(&self, codec: &str) -> Option<&VideoEncodeExtent> {
1861 self.encode_extents
1862 .iter()
1863 .find(|(encoded, _)| *encoded == codec)
1864 .map(|(_, extent)| extent)
1865 }
1866
1867 #[must_use]
1869 pub fn import_ticket_stats(&self) -> ImportTicketStats {
1870 self.import_tickets.stats()
1871 }
1872
1873 #[allow(clippy::too_many_lines)]
1882 pub fn import_surface(
1883 &self,
1884 frame: Arc<VulkanFrameLease>,
1885 ) -> Result<ImportedVulkanFrame, BridgeError> {
1886 let info = frame.info()?;
1887 if info.device != self.identity.device
1888 || info.physical_device != self.identity.physical_device
1889 {
1890 return Err(BridgeError::InvalidInput(
1891 "decoded frame belongs to another Vulkan device",
1892 ));
1893 }
1894 let layout = match (info.software_format, info.formats[0]) {
1895 (rsmpeg::ffi::AV_PIX_FMT_NV12, format)
1896 if format
1897 == vk::Format::G8_B8R8_2PLANE_420_UNORM
1898 .as_raw()
1899 .cast_unsigned() =>
1900 {
1901 NativePlaneLayout::Nv12
1902 }
1903 (rsmpeg::ffi::AV_PIX_FMT_P010LE, format)
1904 if format
1905 == vk::Format::G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16
1906 .as_raw()
1907 .cast_unsigned() =>
1908 {
1909 NativePlaneLayout::P010
1910 }
1911 _ => {
1912 return Err(BridgeError::InvalidInput(
1913 "only sampled single-image NV12 or P010 is importable",
1914 ));
1915 }
1916 };
1917 if layout == NativePlaneLayout::P010
1918 && !self
1919 .gpu
1920 .device()
1921 .features()
1922 .contains(wgpu::Features::TEXTURE_FORMAT_P010)
1923 {
1924 return Err(BridgeError::InvalidInput(
1925 "shared Vulkan device does not expose P010 plane views",
1926 ));
1927 }
1928 if info.plane_count != 1 || !info.sampled || info.width <= 0 || info.height <= 0 {
1929 return Err(BridgeError::InvalidInput(
1930 "only sampled single-image NV12 or P010 is importable",
1931 ));
1932 }
1933 if info.coded_width < info.width || info.coded_height < info.height {
1936 return Err(BridgeError::InvalidInput(
1937 "decoded frame is larger than its allocated image",
1938 ));
1939 }
1940 let coded_width = u32::try_from(info.coded_width)
1941 .map_err(|_| BridgeError::InvalidInput("invalid coded width"))?;
1942 let coded_height = u32::try_from(info.coded_height)
1943 .map_err(|_| BridgeError::InvalidInput("invalid coded height"))?;
1944 let access = frame.lock_access()?;
1945 let mut status = 0;
1946 let step =
1948 unsafe { alpha2_native_gpu_vulkan_acquire_check(access.access(), &raw mut status) };
1949 if step != 0 {
1950 return Err(helper_error("import check", step, status));
1951 }
1952 let (device, allocator, queue_family) = {
1953 let access = access.access();
1954 (
1955 access.frame().device,
1956 access.allocator(),
1957 access.queue_family(),
1958 )
1959 };
1960 drop(access);
1961 let ticket = self.import_tickets.take(device, allocator, queue_family)?;
1962 let (texture_format, luma_format, chroma_format) = match layout {
1963 NativePlaneLayout::P010 => (
1964 wgpu::TextureFormat::P010,
1965 wgpu::TextureFormat::R16Unorm,
1966 wgpu::TextureFormat::Rg16Unorm,
1967 ),
1968 _ => (
1969 wgpu::TextureFormat::NV12,
1970 wgpu::TextureFormat::R8Unorm,
1971 wgpu::TextureFormat::Rg8Unorm,
1972 ),
1973 };
1974 let hal_descriptor = wgpu_hal::TextureDescriptor {
1975 label: Some("alpha2 FFmpeg decode surface"),
1976 size: wgpu::Extent3d {
1977 width: coded_width,
1978 height: coded_height,
1979 depth_or_array_layers: 1,
1980 },
1981 mip_level_count: 1,
1982 sample_count: 1,
1983 dimension: wgpu::TextureDimension::D2,
1984 format: texture_format,
1985 usage: wgpu::TextureUses::RESOURCE,
1986 memory_flags: wgpu_hal::MemoryFlags::empty(),
1987 view_formats: Vec::new(),
1988 };
1989 let hal_device = unsafe { self.gpu.device().as_hal::<wgpu_hal::api::Vulkan>() }
1992 .ok_or(BridgeError::InvalidInput("renderer is not Vulkan"))?;
1993 let drop_owner = Arc::clone(&frame);
1994 let hal_texture = unsafe {
1997 hal_device.texture_from_raw(
1998 vk::Image::from_raw(info.images[0]),
1999 &hal_descriptor,
2000 Some(Box::new(move || drop(drop_owner))),
2001 wgpu_hal::vulkan::TextureMemory::External,
2002 )
2003 };
2004 let descriptor = wgpu::TextureDescriptor {
2005 label: Some("alpha2 FFmpeg decode surface"),
2006 size: hal_descriptor.size,
2007 mip_level_count: 1,
2008 sample_count: 1,
2009 dimension: wgpu::TextureDimension::D2,
2010 format: texture_format,
2011 usage: wgpu::TextureUsages::TEXTURE_BINDING,
2012 view_formats: &[],
2013 };
2014 let texture = unsafe {
2017 self.gpu
2018 .device()
2019 .create_texture_from_hal::<wgpu_hal::api::Vulkan>(
2020 hal_texture,
2021 &descriptor,
2022 wgpu::TextureUses::RESOURCE,
2023 )
2024 };
2025 let y = texture.create_view(&wgpu::TextureViewDescriptor {
2026 label: Some("alpha2 FFmpeg decode Y"),
2027 format: Some(luma_format),
2028 aspect: wgpu::TextureAspect::Plane0,
2029 ..Default::default()
2030 });
2031 let uv = texture.create_view(&wgpu::TextureViewDescriptor {
2032 label: Some("alpha2 FFmpeg decode UV"),
2033 format: Some(chroma_format),
2034 aspect: wgpu::TextureAspect::Plane1,
2035 ..Default::default()
2036 });
2037 let pixels = u64::from(coded_width).saturating_mul(u64::from(coded_height));
2038 let lease = Arc::new(VulkanImportLease {
2039 frame,
2040 _texture: texture,
2041 gpu: self.gpu.clone(),
2042 batch: Arc::clone(&self.submission_batch),
2043 tickets: Arc::clone(&self.import_tickets),
2044 ticket: Some(ticket),
2045 staged_signals: Mutex::new(Vec::new()),
2046 queued: AtomicBool::new(false),
2047 acquired: AtomicBool::new(false),
2048 released: AtomicBool::new(false),
2049 retained_bytes: pixels
2050 .saturating_mul(layout.bytes_per_pixel_numerator())
2051 .div_ceil(2),
2052 });
2053 Ok(ImportedVulkanFrame {
2054 y,
2055 uv,
2056 layout,
2057 coded_size: [coded_width, coded_height],
2058 lease,
2059 })
2060 }
2061}
2062
2063#[derive(Clone, Debug, PartialEq, Eq)]
2066struct VideoPlan {
2067 decode_family: u32,
2068 encode_family: Option<u32>,
2070 video_extensions: Vec<&'static CStr>,
2071 decode_caps: vk::VideoCodecOperationFlagsKHR,
2072 decode_codecs: Vec<&'static str>,
2074 encode_codecs: Vec<&'static str>,
2076}
2077
2078const DECODE_EXTENSIONS: [(&CStr, vk::VideoCodecOperationFlagsKHR, &str); 4] = [
2081 (
2082 ash::khr::video_decode_h264::NAME,
2083 vk::VideoCodecOperationFlagsKHR::DECODE_H264,
2084 "h264",
2085 ),
2086 (
2087 ash::khr::video_decode_h265::NAME,
2088 vk::VideoCodecOperationFlagsKHR::DECODE_H265,
2089 "hevc",
2090 ),
2091 (
2092 ash::khr::video_decode_av1::NAME,
2093 vk::VideoCodecOperationFlagsKHR::DECODE_AV1,
2094 "av1",
2095 ),
2096 (VIDEO_DECODE_VP9_NAME, DECODE_VP9, "vp9"),
2097];
2098
2099#[derive(Clone, Copy, Debug)]
2100struct QueueFamily {
2101 flags: vk::QueueFlags,
2102 count: u32,
2103}
2104
2105fn video_plan(
2112 families: &[QueueFamily],
2113 supports: impl Fn(&CStr) -> bool,
2114 nv12: bool,
2115) -> Result<VideoPlan, String> {
2116 if !nv12 {
2117 return Err("Vulkan adapter does not support NV12 plane views".to_owned());
2118 }
2119 let family = |flag: vk::QueueFlags| {
2120 families
2121 .iter()
2122 .position(|family| family.flags.contains(flag) && family.count > 0)
2123 .and_then(|index| u32::try_from(index).ok())
2124 };
2125 let graphics_family =
2126 family(vk::QueueFlags::GRAPHICS).ok_or("Vulkan adapter has no graphics queue")?;
2127 if graphics_family != 0 {
2128 return Err("wgpu-hal shared-device path requires graphics queue family 0".to_owned());
2129 }
2130 if !families[0]
2131 .flags
2132 .contains(vk::QueueFlags::COMPUTE | vk::QueueFlags::TRANSFER)
2133 {
2134 return Err("graphics queue family lacks FFmpeg compute/transfer capabilities".to_owned());
2135 }
2136 let decode_family = family(vk::QueueFlags::VIDEO_DECODE_KHR)
2137 .ok_or("Vulkan adapter has no video-decode queue")?;
2138 if decode_family == graphics_family {
2139 return Err(
2140 "shared Vulkan device requires a decode queue separate from wgpu graphics".to_owned(),
2141 );
2142 }
2143 let base = [
2144 ash::khr::video_queue::NAME,
2145 ash::khr::video_decode_queue::NAME,
2146 ];
2147 if !base.iter().all(|extension| supports(extension))
2148 || !supports(ash::khr::video_decode_h264::NAME)
2149 {
2150 return Err("Vulkan H.264 video decode is unavailable".to_owned());
2151 }
2152 let mut video_extensions = base.to_vec();
2153 let mut decode_caps = vk::VideoCodecOperationFlagsKHR::empty();
2154 let mut decode_codecs = Vec::new();
2155 for (extension, operation, codec) in DECODE_EXTENSIONS {
2156 if supports(extension) {
2157 video_extensions.push(extension);
2158 decode_caps |= operation;
2159 decode_codecs.push(codec);
2160 }
2161 }
2162 let encode_base = [
2165 ash::khr::video_encode_queue::NAME,
2166 ash::khr::video_maintenance1::NAME,
2167 ];
2168 let encoded: Vec<(&'static str, &'static CStr)> = ENCODE_EXTENSIONS
2169 .iter()
2170 .filter(|(_, extension, _)| supports(extension))
2171 .map(|(codec, extension, _)| (*codec, *extension))
2172 .collect();
2173 let encode_family = family(vk::QueueFlags::VIDEO_ENCODE_KHR)
2174 .filter(|_| encode_base.iter().all(|extension| supports(extension)) && !encoded.is_empty());
2175 let encode_codecs = if encode_family.is_some() {
2176 video_extensions.extend_from_slice(&encode_base);
2177 video_extensions.extend(encoded.iter().map(|(_, extension)| *extension));
2178 encoded.iter().map(|(codec, _)| *codec).collect()
2179 } else {
2180 Vec::new()
2181 };
2182 Ok(VideoPlan {
2183 decode_family,
2184 encode_family,
2185 video_extensions,
2186 decode_caps,
2187 decode_codecs,
2188 encode_codecs,
2189 })
2190}
2191
2192fn adapter_video_plan(
2194 instance: &ash::Instance,
2195 exposed: &wgpu_hal::ExposedAdapter<wgpu_hal::api::Vulkan>,
2196) -> Result<VideoPlan, String> {
2197 let physical = exposed.adapter.raw_physical_device();
2198 let families: Vec<QueueFamily> =
2200 unsafe { instance.get_physical_device_queue_family_properties(physical) }
2201 .iter()
2202 .map(|family| QueueFamily {
2203 flags: family.queue_flags,
2204 count: family.queue_count,
2205 })
2206 .collect();
2207 let extensions = unsafe { instance.enumerate_device_extension_properties(physical) }
2209 .map_err(|error| format!("Vulkan extensions: {error}"))?;
2210 let supports = |name: &CStr| {
2211 extensions.iter().any(|property| {
2212 property
2213 .extension_name_as_c_str()
2214 .is_ok_and(|candidate| candidate == name)
2215 })
2216 };
2217 video_plan(
2218 &families,
2219 supports,
2220 exposed
2221 .features
2222 .contains(wgpu::Features::TEXTURE_FORMAT_NV12),
2223 )
2224}
2225
2226#[derive(Clone, Debug)]
2228pub struct VulkanCandidate {
2229 pub index: usize,
2232 pub loader_index: Option<usize>,
2234 pub name: String,
2236 pub device_type: wgpu::DeviceType,
2238 pub shares: Result<(), String>,
2240 pub decode_codecs: Vec<&'static str>,
2242 pub encode_codecs: Vec<&'static str>,
2244}
2245
2246pub fn vulkan_candidates() -> Result<Vec<VulkanCandidate>, SharedVulkanError> {
2251 let _turn = alpha2_gpu::serialize_acquisition();
2252 let hal_instance = hal_instance()?;
2253 let adapters = unsafe { hal_instance.enumerate_adapters(None) };
2255 Ok(adapters
2256 .iter()
2257 .enumerate()
2258 .map(|(index, exposed)| {
2259 let instance = exposed.adapter.shared_instance().raw_instance();
2260 let physical = exposed.adapter.raw_physical_device();
2261 let loader_index = unsafe { instance.enumerate_physical_devices() }
2263 .ok()
2264 .and_then(|order| order.iter().position(|device| *device == physical));
2265 let plan = adapter_video_plan(instance, exposed);
2266 VulkanCandidate {
2267 index,
2268 loader_index,
2269 name: exposed.info.name.clone(),
2270 device_type: exposed.info.device_type,
2271 decode_codecs: plan
2272 .as_ref()
2273 .map(|plan| plan.decode_codecs.clone())
2274 .unwrap_or_default(),
2275 encode_codecs: plan
2276 .as_ref()
2277 .map(|plan| plan.encode_codecs.clone())
2278 .unwrap_or_default(),
2279 shares: plan.map(|_| ()),
2280 }
2281 })
2282 .collect())
2283}
2284
2285fn candidate_record(
2288 candidate: &VulkanCandidate,
2289 decode_allowed: bool,
2290) -> alpha2_gpu::AdapterRecord {
2291 let mut record = alpha2_gpu::AdapterRecord::unasked(
2292 candidate.index,
2293 candidate.name.clone(),
2294 "vulkan",
2295 candidate.device_type == wgpu::DeviceType::Cpu,
2296 );
2297 record.renders = Ok(());
2298 record.decodes = match (&candidate.shares, decode_allowed) {
2299 (Err(reason), _) => Err(reason.clone()),
2300 (Ok(()), false) => Err("decode is in software".to_owned()),
2301 (Ok(()), true) => Ok(()),
2302 };
2303 record.gpu_encodes = alpha2_gpu::GPU_ENCODE_CODECS
2304 .iter()
2305 .map(|&codec| {
2306 let answer = match &candidate.shares {
2307 Err(reason) => Err(reason.clone()),
2308 Ok(()) if candidate.encode_codecs.contains(&codec) => Ok(()),
2309 Ok(()) => Err(format!(
2310 "{} has no Vulkan video encoder for {codec}",
2311 candidate.name
2312 )),
2313 };
2314 (codec, answer)
2315 })
2316 .collect();
2317 record
2318}
2319
2320pub struct SharedVulkanChoice<T = SharedVulkanContext> {
2322 pub context: Option<T>,
2324 pub report: alpha2_gpu::AdapterChoiceReport,
2326 pub unavailable: Option<String>,
2328}
2329
2330pub fn choose_shared_vulkan(
2342 policy: &alpha2_gpu::AdapterPolicy,
2343 decode_allowed: bool,
2344 wanted: bool,
2345) -> Result<SharedVulkanChoice, SharedVulkanError> {
2346 let candidates = vulkan_candidates()?;
2347 Ok(choose_over(
2348 candidates,
2349 policy,
2350 decode_allowed,
2351 wanted,
2352 |index| SharedVulkanContext::create_on(index).map_err(|error| error.to_string()),
2353 ))
2354}
2355
2356fn choose_over<T>(
2358 mut candidates: Vec<VulkanCandidate>,
2359 policy: &alpha2_gpu::AdapterPolicy,
2360 decode_allowed: bool,
2361 wanted: bool,
2362 mut create_on: impl FnMut(usize) -> Result<T, String>,
2363) -> SharedVulkanChoice<T> {
2364 let kinds: Vec<(usize, wgpu::DeviceType)> = candidates
2366 .iter()
2367 .enumerate()
2368 .map(|(at, candidate)| (at, candidate.device_type))
2369 .collect();
2370 let order = alpha2_gpu::product_order(&kinds);
2371 let mut ordered: Vec<Option<VulkanCandidate>> = candidates.drain(..).map(Some).collect();
2372 let mut candidates: Vec<VulkanCandidate> = order
2373 .into_iter()
2374 .filter_map(|at| ordered[at].take())
2375 .collect();
2376 loop {
2377 let records: Vec<alpha2_gpu::AdapterRecord> = candidates
2378 .iter()
2379 .map(|candidate| candidate_record(candidate, decode_allowed))
2380 .collect();
2381 let ranked = match alpha2_gpu::rank_adapters(&records, policy, decode_allowed) {
2382 Ok(ranked) => ranked,
2383 Err(reason) => {
2384 return SharedVulkanChoice {
2385 context: None,
2386 report: alpha2_gpu::AdapterChoiceReport {
2387 policy: policy.clone(),
2388 chosen: None,
2389 reason: alpha2_gpu::ChoiceReason::NoSharedDevice,
2390 explanation: reason.clone(),
2391 records,
2392 },
2393 unavailable: wanted.then_some(reason),
2394 };
2395 }
2396 };
2397 let mut report = alpha2_gpu::AdapterChoiceReport::ranked(policy.clone(), records, &ranked);
2398 let first = ranked.order[0];
2399 if !wanted {
2400 return SharedVulkanChoice {
2401 context: None,
2402 report,
2403 unavailable: None,
2404 };
2405 }
2406 let candidate = &mut candidates[first];
2407 if let Err(reason) = &candidate.shares {
2408 let unavailable = format!(
2409 "{} cannot share a device with the video decoder and encoder: {reason}",
2410 candidate.name
2411 );
2412 if !policy.is_pin() {
2413 report.reason = alpha2_gpu::ChoiceReason::NoSharedDevice;
2414 }
2415 report.explanation = format!("{}; {unavailable}", report.explanation);
2416 return SharedVulkanChoice {
2417 context: None,
2418 report,
2419 unavailable: Some(unavailable),
2420 };
2421 }
2422 match create_on(candidate.index) {
2423 Ok(context) => {
2424 return SharedVulkanChoice {
2425 context: Some(context),
2426 report,
2427 unavailable: None,
2428 };
2429 }
2430 Err(reason) => candidate.shares = Err(format!("the shared device failed: {reason}")),
2431 }
2432 }
2433}
2434
2435#[must_use]
2438pub fn adapter_pinned() -> bool {
2439 alpha2_gpu::AdapterPolicy::from_env().is_pin()
2440}
2441
2442pub fn shared_device_index() -> Result<usize, SharedVulkanError> {
2450 let candidates = vulkan_candidates()?;
2451 let mut chosen = None;
2452 let choice = choose_over(
2453 candidates.clone(),
2454 &alpha2_gpu::AdapterPolicy::from_env(),
2455 true,
2456 true,
2457 |index| {
2458 chosen = Some(index);
2459 Ok(())
2460 },
2461 );
2462 let index = chosen.ok_or_else(|| {
2463 SharedVulkanError(
2464 choice
2465 .unavailable
2466 .unwrap_or_else(|| "no Vulkan adapter could carry the shared device".to_owned()),
2467 )
2468 })?;
2469 candidates
2470 .iter()
2471 .find(|candidate| candidate.index == index)
2472 .and_then(|candidate| candidate.loader_index)
2473 .ok_or_else(|| SharedVulkanError("the chosen adapter is not in the device list".into()))
2474}
2475
2476#[cfg(test)]
2477#[path = "vulkan_failure_tests.rs"]
2478mod failure_tests;
2479
2480#[cfg(test)]
2481#[path = "vulkan_tests.rs"]
2482mod tests;