Skip to main content

alpha2_native_gpu/
lib.rs

1//! Platform GPU interop for native decoded video surfaces.
2//!
3//! `FFmpeg` ownership remains in `alpha2-ffmpeg-bridge`; this crate owns raw GPU
4//! handles, barriers, synchronization, and bounded surface accounting.
5
6#[cfg(all(
7    test,
8    feature = "hwdevice",
9    any(target_os = "linux", target_os = "windows", target_os = "macos")
10))]
11mod conformance;
12mod context;
13/// The fixed-width ABI shared with the C helpers, generated from their
14/// headers by the build script, with bindgen's compile-time layout
15/// assertions. Every target generates every platform's declarations. The
16/// assertions come from the same header as the struct, so they agree with any
17/// edit to it; the structs only Windows or macOS consumes are also pinned by
18/// hand below.
19#[allow(
20    dead_code,
21    non_camel_case_types,
22    unreachable_pub,
23    clippy::all,
24    clippy::pedantic,
25    clippy::undocumented_unsafe_blocks
26)]
27mod interop {
28    include!(concat!(env!("OUT_DIR"), "/interop.rs"));
29}
30
31// The layouts of the interop structs that only `c/d3d12.c`, `c/metal.m` and
32// their Rust modules use, which no Linux build compiles. A header edit that
33// moves, resizes or renames one of their fields fails here on every target,
34// so it is made on purpose, together with those consumers.
35const _: () = {
36    use interop::{
37        Alpha2D3D12CopyInfo as D3D12, Alpha2D3D12EncodeCaps as Caps,
38        Alpha2D3D12EncodeCopy as Encode, Alpha2MetalFrameInfo as Metal,
39    };
40    use std::mem::{align_of, offset_of, size_of};
41    assert!(size_of::<D3D12>() == 40 && align_of::<D3D12>() == 8);
42    assert!(offset_of!(D3D12, resource) == 0);
43    assert!(offset_of!(D3D12, fence) == 8);
44    assert!(offset_of!(D3D12, fence_value) == 16);
45    assert!(offset_of!(D3D12, width) == 24);
46    assert!(offset_of!(D3D12, height) == 28);
47    assert!(offset_of!(D3D12, dxgi_format) == 32);
48    assert!(size_of::<Encode>() == 48 && align_of::<Encode>() == 8);
49    assert!(offset_of!(Encode, luma) == 0);
50    assert!(offset_of!(Encode, chroma) == 8);
51    assert!(offset_of!(Encode, destination) == 16);
52    assert!(offset_of!(Encode, frame_fence) == 24);
53    assert!(offset_of!(Encode, frame_value) == 32);
54    assert!(offset_of!(Encode, width) == 40);
55    assert!(offset_of!(Encode, height) == 44);
56    assert!(size_of::<Caps>() == 76 && align_of::<Caps>() == 4);
57    assert!(offset_of!(Caps, supported) == 0);
58    assert!(offset_of!(Caps, refused_check) == 4);
59    assert!(offset_of!(Caps, refused_status) == 8);
60    assert!(offset_of!(Caps, validation_flags) == 12);
61    assert!(offset_of!(Caps, min_width) == 16);
62    assert!(offset_of!(Caps, min_height) == 20);
63    assert!(offset_of!(Caps, max_width) == 24);
64    assert!(offset_of!(Caps, max_height) == 28);
65    assert!(offset_of!(Caps, width_multiple) == 32);
66    assert!(offset_of!(Caps, height_multiple) == 36);
67    assert!(offset_of!(Caps, max_p_l0_references) == 40);
68    assert!(offset_of!(Caps, max_b_l0_references) == 44);
69    assert!(offset_of!(Caps, max_l1_references) == 48);
70    assert!(offset_of!(Caps, rate_control_modes) == 52);
71    assert!(offset_of!(Caps, refused_rate_control_modes) == 56);
72    assert!(offset_of!(Caps, rate_control_status) == 60);
73    assert!(size_of::<Metal>() == 56 && align_of::<Metal>() == 8);
74    assert!(offset_of!(Metal, width) == 0);
75    assert!(offset_of!(Metal, height) == 4);
76    assert!(offset_of!(Metal, plane_width) == 8);
77    assert!(offset_of!(Metal, plane_height) == 16);
78    assert!(offset_of!(Metal, clean_x) == 24);
79    assert!(offset_of!(Metal, clean_y) == 28);
80    assert!(offset_of!(Metal, clean_width) == 32);
81    assert!(offset_of!(Metal, clean_height) == 36);
82    assert!(offset_of!(Metal, pixel_format) == 40);
83    assert!(offset_of!(Metal, io_surface) == 48);
84};
85#[cfg(all(target_os = "windows", feature = "hwdevice"))]
86mod d3d12;
87mod import;
88mod lifetime;
89#[cfg(all(target_os = "macos", feature = "hwdevice"))]
90mod metal;
91#[cfg(feature = "hwdevice")]
92mod status;
93mod surface_budget;
94mod sync;
95#[cfg(all(target_os = "linux", feature = "hwdevice"))]
96mod vulkan;
97
98pub use context::NativeGpuIdentity;
99#[cfg(all(target_os = "windows", feature = "hwdevice"))]
100pub use d3d12::{
101    D3D12AdapterIdentity, D3D12CopyLease, D3D12EncodeCodec, D3D12EncodeCopier, D3D12EncodeExtent,
102    ImportedD3D12Frame, SharedD3D12Context, SharedD3D12Error, d3d12_adapter_identity,
103    gpu_wait_timeout as d3d12_wait_timeout,
104};
105pub use import::{ImportedPlaneDescriptor, NativePlaneLayout};
106pub use lifetime::{NativeSurfaceLease, SurfaceIdentity};
107#[cfg(all(target_os = "macos", feature = "hwdevice"))]
108pub use metal::{
109    ImportedMetalFrame, MetalEncoderPlanes, MetalImportLease, SharedMetalContext, SharedMetalError,
110    gpu_wait_timeout as metal_wait_timeout,
111};
112pub use surface_budget::{SurfaceBudget, SurfaceBudgetError, SurfaceReservation, SurfaceUsage};
113pub use sync::{ExternalSyncPoint, NativeImageState};
114#[cfg(all(target_os = "linux", feature = "hwdevice"))]
115pub use vulkan::{
116    ImportTicketStats, ImportedVulkanFrame, SharedVulkanContext, SharedVulkanError,
117    VideoEncodeExtent, VulkanEncodeCopier, VulkanImportLease, VulkanLayerVersion,
118    probe_vulkan_submission, shared_device_index,
119};
120
121/// Sets how long the platform interop helpers wait on GPU work before they
122/// give up with a timeout, instead of blocking forever on a hung device.
123///
124/// The default is 30 seconds. Destroying a helper whose work did not finish in
125/// time leaks its GPU objects rather than freeing memory the device may still
126/// use.
127pub fn set_gpu_wait_timeout(timeout: std::time::Duration) {
128    #[cfg(all(target_os = "linux", feature = "hwdevice"))]
129    vulkan::set_wait_timeout(timeout);
130    #[cfg(all(target_os = "windows", feature = "hwdevice"))]
131    d3d12::set_wait_timeout(timeout);
132    #[cfg(all(target_os = "macos", feature = "hwdevice"))]
133    metal::set_wait_timeout(timeout);
134    let _ = timeout;
135}
136
137/// `timeout` in whole milliseconds for a Win32 wait, which reads
138/// `0xFFFFFFFF` (`INFINITE`) as no timeout at all: anything that long or
139/// longer waits one millisecond less instead, so no setting makes a wait on a
140/// hung device unbounded.
141#[cfg_attr(
142    not(all(target_os = "windows", feature = "hwdevice")),
143    allow(dead_code)
144)]
145fn win32_wait_milliseconds(timeout: std::time::Duration) -> u32 {
146    const INFINITE: u32 = u32::MAX;
147    u32::try_from(timeout.as_millis())
148        .unwrap_or(INFINITE)
149        .min(INFINITE - 1)
150}
151
152#[cfg(test)]
153mod tests {
154    use std::time::Duration;
155
156    #[test]
157    fn no_wait_timeout_reaches_win32_as_infinite() {
158        let milliseconds =
159            |value: u64| super::win32_wait_milliseconds(Duration::from_millis(value));
160        assert_eq!(milliseconds(30_000), 30_000);
161        assert_eq!(milliseconds(u64::from(u32::MAX) - 1), u32::MAX - 1);
162        assert_eq!(milliseconds(u64::from(u32::MAX)), u32::MAX - 1);
163        assert_eq!(milliseconds(u64::from(u32::MAX) + 1), u32::MAX - 1);
164        assert_eq!(milliseconds(u64::MAX), u32::MAX - 1);
165        assert_eq!(super::win32_wait_milliseconds(Duration::MAX), u32::MAX - 1);
166        // Below a millisecond is no wait, as `Duration::as_millis` rounds it.
167        assert_eq!(
168            super::win32_wait_milliseconds(Duration::from_micros(999)),
169            0
170        );
171    }
172}