#![doc = include_str!("../README.md")]
#[cfg(feature = "wgpu-30")]
pub extern crate wgpu_30 as wgpu;
#[cfg(feature = "wgpu-30")]
pub extern crate wgpu_hal_30 as wgpu_hal;
#[cfg(all(feature = "wgpu-29", not(feature = "wgpu-30")))]
pub extern crate wgpu_29 as wgpu;
#[cfg(all(feature = "wgpu-29", not(feature = "wgpu-30")))]
pub extern crate wgpu_hal_29 as wgpu_hal;
#[cfg(all(feature = "wgpu-28", not(feature = "wgpu-29"), not(feature = "wgpu-30")))]
pub extern crate wgpu_28 as wgpu;
#[cfg(all(feature = "wgpu-28", not(feature = "wgpu-29"), not(feature = "wgpu-30")))]
pub extern crate wgpu_hal_28 as wgpu_hal;
#[cfg(not(any(feature = "wgpu-28", feature = "wgpu-29", feature = "wgpu-30")))]
compile_error!(
"grafting needs one wgpu version feature: enable `wgpu-29` (default), `wgpu-30`, or `wgpu-28` to match your host's wgpu"
);
mod error;
mod wgpu_compat;
mod sync;
#[cfg(target_os = "windows")]
mod sync_dx12;
#[cfg(target_vendor = "apple")]
mod sync_metal;
#[cfg(target_os = "linux")]
mod sync_vulkan;
#[cfg(target_os = "linux")]
pub mod vulkan_dmabuf;
#[cfg(target_vendor = "apple")]
mod metal_texture_ref;
#[cfg(target_vendor = "apple")]
pub use metal_texture_ref::{import_metal_texture_borrowed, import_metal_texture_ref};
#[cfg(target_os = "windows")]
mod dx12_shared_texture;
#[cfg(target_os = "windows")]
pub use dx12_shared_texture::{import_dx12_shared_handle_borrowed, import_dx12_shared_texture};
#[cfg(all(
feature = "gl",
any(target_os = "linux", target_os = "android", target_os = "windows")
))]
mod gl_bindings {
#![allow(unsafe_op_in_unsafe_fn)]
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
#[cfg(feature = "gl")]
pub mod raw_gl;
#[cfg(feature = "surfman")]
pub mod surfman_gl;
#[cfg(feature = "gl")]
use std::rc::Rc;
#[cfg(target_os = "windows")]
use std::sync::{
Arc,
atomic::{AtomicU64, Ordering},
};
#[cfg(target_os = "windows")]
use std::os::windows::io::{AsRawHandle, OwnedHandle};
use dpi::PhysicalSize;
pub use error::{InteropError, UnsupportedReason};
pub use sync::{ImplicitOnlySynchronizer, InteropSynchronizer, NoopSynchronizer, SyncMechanism};
#[cfg(target_os = "windows")]
pub use sync_dx12::Dx12FenceSynchronizer;
#[cfg(target_vendor = "apple")]
pub use sync_metal::MetalSharedEventSynchronizer;
#[cfg(target_os = "linux")]
pub use sync_vulkan::VulkanSemaphoreSynchronizer;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum InteropBackend {
Vulkan,
Metal,
Dx12,
Unknown,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TextureOrigin {
TopLeft,
BottomLeft,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum NativeFrameKind {
#[cfg(feature = "gl")]
GlFramebufferSource,
VulkanExternalImage,
MetalTextureRef,
Dx12SharedTexture,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CapabilityStatus {
Supported,
Unsupported(UnsupportedReason),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CapabilityMatrix {
pub host_backend: InteropBackend,
pub gl_framebuffer_source: CapabilityStatus,
pub vulkan_external_image: CapabilityStatus,
pub metal_texture_ref: CapabilityStatus,
pub dx12_shared_texture: CapabilityStatus,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProducerCapabilities {
pub supported_frames: Vec<NativeFrameKind>,
}
#[derive(Clone, Debug)]
pub struct HostWgpuContext {
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub backend: InteropBackend,
pub dmabuf_support: bool,
}
impl HostWgpuContext {
pub fn new(device: wgpu::Device, queue: wgpu::Queue) -> Self {
let backend = detect_backend(&device);
let dmabuf_support = detect_dmabuf_support(&device, backend);
Self {
backend,
dmabuf_support,
device,
queue,
}
}
pub fn capabilities(&self) -> CapabilityMatrix {
CapabilityMatrix::for_host(self.backend, self.dmabuf_support)
}
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct ImportOptions {
pub normalize_origin: bool,
pub normalize_format: bool,
}
impl Default for ImportOptions {
fn default() -> Self {
Self {
normalize_origin: true,
normalize_format: true,
}
}
}
#[derive(Debug)]
pub struct ImportedTexture {
pub texture: wgpu::Texture,
pub format: wgpu::TextureFormat,
pub size: PhysicalSize<u32>,
pub origin: TextureOrigin,
pub generation: u64,
pub consumer_sync: SyncMechanism,
}
#[cfg(feature = "gl")]
pub struct GlFramebufferSource {
size: PhysicalSize<u32>,
generation: u64,
producer_sync: SyncMechanism,
importer: Rc<dyn GlFramebufferSourceImpl>,
}
#[cfg(feature = "gl")]
impl GlFramebufferSource {
pub fn size(&self) -> PhysicalSize<u32> {
self.size
}
pub fn generation(&self) -> u64 {
self.generation
}
pub fn producer_sync(&self) -> SyncMechanism {
self.producer_sync
}
pub fn new(
size: PhysicalSize<u32>,
generation: u64,
producer_sync: SyncMechanism,
importer: Rc<dyn GlFramebufferSourceImpl>,
) -> Self {
Self {
size,
generation,
producer_sync,
importer,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FrameMetadata {
pub size: PhysicalSize<u32>,
pub format: wgpu::TextureFormat,
pub generation: u64,
pub producer_sync: SyncMechanism,
}
pub struct VulkanExternalImage {
metadata: FrameMetadata,
#[cfg(target_os = "linux")]
dmabuf_fd: std::os::fd::OwnedFd,
#[cfg(target_os = "linux")]
dmabuf_offset: u64,
#[cfg(target_os = "linux")]
dmabuf_stride: u64,
#[cfg(target_os = "linux")]
drm_modifier: u64,
#[cfg(target_os = "linux")]
wait_semaphore_fd: Option<std::os::fd::OwnedFd>,
}
impl VulkanExternalImage {
pub fn metadata(&self) -> FrameMetadata {
self.metadata
}
#[cfg(target_os = "linux")]
pub fn new(
metadata: FrameMetadata,
dmabuf_fd: std::os::fd::OwnedFd,
dmabuf_offset: u64,
dmabuf_stride: u64,
drm_modifier: u64,
wait_semaphore_fd: Option<std::os::fd::OwnedFd>,
) -> Self {
Self {
metadata,
dmabuf_fd,
dmabuf_offset,
dmabuf_stride,
drm_modifier,
wait_semaphore_fd,
}
}
#[cfg(target_os = "linux")]
pub unsafe fn from_raw_owned_parts(
metadata: FrameMetadata,
dmabuf_fd: std::os::fd::RawFd,
dmabuf_offset: u64,
dmabuf_stride: u64,
drm_modifier: u64,
wait_semaphore_fd: Option<std::os::fd::RawFd>,
) -> Self {
use std::os::fd::FromRawFd;
Self::new(
metadata,
unsafe { std::os::fd::OwnedFd::from_raw_fd(dmabuf_fd) },
dmabuf_offset,
dmabuf_stride,
drm_modifier,
wait_semaphore_fd.map(|fd| unsafe { std::os::fd::OwnedFd::from_raw_fd(fd) }),
)
}
}
pub struct MetalTextureRef {
metadata: FrameMetadata,
#[cfg(target_vendor = "apple")]
raw_metal_texture:
objc2::rc::Retained<objc2::runtime::ProtocolObject<dyn objc2_metal::MTLTexture>>,
}
impl MetalTextureRef {
pub fn metadata(&self) -> FrameMetadata {
self.metadata
}
#[cfg(target_vendor = "apple")]
pub fn new(
metadata: FrameMetadata,
raw_metal_texture: objc2::rc::Retained<
objc2::runtime::ProtocolObject<dyn objc2_metal::MTLTexture>,
>,
) -> Self {
Self {
metadata,
raw_metal_texture,
}
}
#[cfg(target_vendor = "apple")]
pub unsafe fn from_raw_retained(
metadata: FrameMetadata,
raw_metal_texture: *mut std::ffi::c_void,
) -> Option<Self> {
let raw_metal_texture = unsafe {
objc2::rc::Retained::from_raw(
raw_metal_texture
.cast::<objc2::runtime::ProtocolObject<dyn objc2_metal::MTLTexture>>(),
)
}?;
Some(Self::new(metadata, raw_metal_texture))
}
}
#[derive(Debug)]
pub struct Dx12SharedTexture {
metadata: FrameMetadata,
fence_value: u64,
#[cfg(target_os = "windows")]
resource: Dx12SharedResource,
}
#[cfg(target_os = "windows")]
#[derive(Clone, Debug)]
pub struct Dx12SharedResource {
handle: Arc<OwnedHandle>,
allocation_key: u64,
}
#[cfg(target_os = "windows")]
static NEXT_DX12_ALLOCATION_KEY: AtomicU64 = AtomicU64::new(1);
impl Dx12SharedTexture {
pub fn metadata(&self) -> FrameMetadata {
self.metadata
}
pub fn fence_value(&self) -> u64 {
self.fence_value
}
#[cfg(target_os = "windows")]
pub fn new(metadata: FrameMetadata, resource: Dx12SharedResource, fence_value: u64) -> Self {
Self {
metadata,
fence_value,
resource,
}
}
#[cfg(target_os = "windows")]
pub fn allocation_key(&self) -> u64 {
self.resource.allocation_key()
}
#[cfg(target_os = "windows")]
pub(crate) fn raw_handle(&self) -> *mut std::ffi::c_void {
self.resource.raw_handle()
}
#[cfg(target_os = "windows")]
pub fn resource(&self) -> Dx12SharedResource {
self.resource.clone()
}
}
#[cfg(target_os = "windows")]
impl Dx12SharedResource {
pub fn from_owned_handle(handle: OwnedHandle) -> Self {
Self {
handle: Arc::new(handle),
allocation_key: NEXT_DX12_ALLOCATION_KEY.fetch_add(1, Ordering::Relaxed),
}
}
pub fn allocation_key(&self) -> u64 {
self.allocation_key
}
pub fn frame(&self, metadata: FrameMetadata, fence_value: u64) -> Dx12SharedTexture {
Dx12SharedTexture::new(metadata, self.clone(), fence_value)
}
fn raw_handle(&self) -> *mut std::ffi::c_void {
self.handle.as_raw_handle()
}
}
#[non_exhaustive]
pub enum NativeFrame {
#[cfg(feature = "gl")]
GlFramebufferSource(GlFramebufferSource),
VulkanExternalImage(VulkanExternalImage),
MetalTextureRef(MetalTextureRef),
Dx12SharedTexture(Dx12SharedTexture),
}
impl NativeFrame {
pub fn kind(&self) -> NativeFrameKind {
match self {
#[cfg(feature = "gl")]
NativeFrame::GlFramebufferSource(_) => NativeFrameKind::GlFramebufferSource,
NativeFrame::VulkanExternalImage(_) => NativeFrameKind::VulkanExternalImage,
NativeFrame::MetalTextureRef(_) => NativeFrameKind::MetalTextureRef,
NativeFrame::Dx12SharedTexture(_) => NativeFrameKind::Dx12SharedTexture,
}
}
pub fn producer_sync(&self) -> SyncMechanism {
match self {
#[cfg(feature = "gl")]
NativeFrame::GlFramebufferSource(frame) => frame.producer_sync(),
NativeFrame::VulkanExternalImage(frame) => frame.metadata().producer_sync,
NativeFrame::MetalTextureRef(frame) => frame.metadata().producer_sync,
NativeFrame::Dx12SharedTexture(frame) => frame.metadata().producer_sync,
}
}
}
pub trait FrameProducer {
fn capabilities(&self) -> ProducerCapabilities;
fn acquire_frame(&mut self) -> Result<NativeFrame, InteropError>;
}
pub trait TextureImporter {
fn import_frame(
&self,
frame: NativeFrame,
options: &ImportOptions,
) -> Result<ImportedTexture, InteropError>;
}
pub struct WgpuTextureImporter {
host: HostWgpuContext,
synchronizer: Box<dyn InteropSynchronizer>,
#[cfg(feature = "gl")]
normalizer: crate::raw_gl::texture_normalizer::ImportedTextureNormalizer,
}
impl WgpuTextureImporter {
pub fn new(host: HostWgpuContext) -> Self {
Self::with_synchronizer(host, Box::new(ImplicitOnlySynchronizer))
}
pub fn with_synchronizer(
host: HostWgpuContext,
synchronizer: Box<dyn InteropSynchronizer>,
) -> Self {
#[cfg(feature = "gl")]
let normalizer =
crate::raw_gl::texture_normalizer::ImportedTextureNormalizer::new(&host.device);
Self {
host,
synchronizer,
#[cfg(feature = "gl")]
normalizer,
}
}
pub fn host(&self) -> &HostWgpuContext {
&self.host
}
}
pub enum EpochFrame {
NewResource {
resource_epoch: u64,
frame: NativeFrame,
},
ReusedResource {
resource_epoch: u64,
},
}
struct CachedEpochTexture {
resource_epoch: u64,
imported: ImportedTexture,
}
pub struct EpochCachedImporter {
importer: WgpuTextureImporter,
cached: Option<CachedEpochTexture>,
cache_flush_buffer: Option<wgpu::Buffer>,
}
impl EpochCachedImporter {
pub fn new(host: HostWgpuContext) -> Self {
Self::from_importer(WgpuTextureImporter::new(host))
}
pub fn with_synchronizer(
host: HostWgpuContext,
synchronizer: Box<dyn InteropSynchronizer>,
) -> Self {
Self::from_importer(WgpuTextureImporter::with_synchronizer(host, synchronizer))
}
pub fn from_importer(importer: WgpuTextureImporter) -> Self {
Self {
importer,
cached: None,
cache_flush_buffer: None,
}
}
pub fn host(&self) -> &HostWgpuContext {
self.importer.host()
}
pub fn imported(&self) -> Option<&ImportedTexture> {
self.cached.as_ref().map(|cached| &cached.imported)
}
pub fn resource_epoch(&self) -> Option<u64> {
self.cached.as_ref().map(|cached| cached.resource_epoch)
}
pub fn update(
&mut self,
frame: EpochFrame,
options: &ImportOptions,
) -> Result<&ImportedTexture, InteropError> {
match frame {
EpochFrame::NewResource {
resource_epoch,
frame,
} => {
if self.resource_epoch() != Some(resource_epoch) {
let imported = self.importer.import_frame(frame, options)?;
self.cached = Some(CachedEpochTexture {
resource_epoch,
imported,
});
}
}
EpochFrame::ReusedResource { resource_epoch } => {
if self.resource_epoch() != Some(resource_epoch) {
return Err(InteropError::InvalidFrame(
"reused resource epoch has no matching cached import",
));
}
}
}
self.flush_cached_texture()?;
self.imported().ok_or(InteropError::InvalidFrame(
"epoch cache update produced no texture",
))
}
fn flush_cached_texture(&mut self) -> Result<(), InteropError> {
let Self {
importer,
cached,
cache_flush_buffer,
} = self;
let Some(cached) = cached.as_ref() else {
return Ok(());
};
if cached.imported.size.width == 0 || cached.imported.size.height == 0 {
return Ok(());
}
let device = &importer.host.device;
let queue = &importer.host.queue;
let flush_buffer = cache_flush_buffer.get_or_insert_with(|| {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("grafting epoch-cache flush"),
size: wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as u64,
usage: wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("grafting epoch-cache flush"),
});
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: &cached.imported.texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: flush_buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT),
rows_per_image: Some(1),
},
},
wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
);
queue.submit(std::iter::once(encoder.finish()));
Ok(())
}
}
#[cfg(target_os = "windows")]
#[deprecated(note = "use Dx12SharedResource::from_owned_handle and the move-only safe frame path")]
pub unsafe fn close_shared_handle(handle: *mut std::ffi::c_void) -> Result<(), InteropError> {
use windows::Win32::Foundation::{CloseHandle, HANDLE};
let handle = HANDLE(handle);
if handle.is_invalid() {
return Ok(());
}
unsafe { CloseHandle(handle) }
.map(|_| ())
.map_err(|err| InteropError::Dx12(format!("CloseHandle failed: {err}")))
}
#[cfg(not(target_os = "windows"))]
#[deprecated(note = "use Dx12SharedResource::from_owned_handle and the move-only safe frame path")]
pub unsafe fn close_shared_handle(_handle: *mut std::ffi::c_void) -> Result<(), InteropError> {
Ok(())
}
impl TextureImporter for WgpuTextureImporter {
fn import_frame(
&self,
frame: NativeFrame,
options: &ImportOptions,
) -> Result<ImportedTexture, InteropError> {
self.synchronizer
.producer_complete(&frame, frame.producer_sync())?;
#[cfg(not(feature = "gl"))]
let _ = options;
let imported = match frame {
#[cfg(feature = "gl")]
NativeFrame::GlFramebufferSource(frame_source) => {
frame_source
.importer
.import_into(&frame_source, &self.host, options)
}
NativeFrame::VulkanExternalImage(frame) => {
import_vulkan_external_image(frame, &self.host)
}
NativeFrame::MetalTextureRef(frame) => import_metal_frame(frame, &self.host),
NativeFrame::Dx12SharedTexture(frame) => import_dx12_shared_frame(frame, &self.host),
}?;
#[cfg(feature = "gl")]
let imported = if options.normalize_origin && imported.origin == TextureOrigin::BottomLeft {
let texture = self.normalizer.normalize(
&self.host.device,
&self.host.queue,
&imported.texture,
imported.size,
);
ImportedTexture {
texture,
format: wgpu::TextureFormat::Rgba8Unorm,
origin: TextureOrigin::TopLeft,
..imported
}
} else {
imported
};
self.synchronizer
.consumer_ready(&imported, imported.consumer_sync)?;
Ok(imported)
}
}
impl CapabilityMatrix {
pub fn for_backend(host_backend: InteropBackend) -> Self {
Self::for_host(host_backend, false)
}
pub fn for_host(host_backend: InteropBackend, dmabuf_support: bool) -> Self {
let gl_framebuffer_source = match host_backend {
InteropBackend::Vulkan | InteropBackend::Metal | InteropBackend::Dx12 => {
CapabilityStatus::Supported
}
InteropBackend::Unknown => {
CapabilityStatus::Unsupported(UnsupportedReason::HostBackendUnavailable)
}
};
let vulkan_external_image = match host_backend {
InteropBackend::Vulkan => {
#[cfg(target_os = "linux")]
{
if dmabuf_support {
CapabilityStatus::Supported
} else {
CapabilityStatus::Unsupported(
UnsupportedReason::VulkanDmabufExtensionNotEnabled,
)
}
}
#[cfg(not(target_os = "linux"))]
{
let _ = dmabuf_support;
CapabilityStatus::Unsupported(UnsupportedReason::PlatformNotImplemented)
}
}
InteropBackend::Metal | InteropBackend::Dx12 => {
CapabilityStatus::Unsupported(UnsupportedReason::HostBackendMismatch)
}
InteropBackend::Unknown => {
CapabilityStatus::Unsupported(UnsupportedReason::HostBackendUnavailable)
}
};
let metal_texture_ref = match host_backend {
InteropBackend::Metal => CapabilityStatus::Supported,
InteropBackend::Vulkan | InteropBackend::Dx12 => {
CapabilityStatus::Unsupported(UnsupportedReason::HostBackendMismatch)
}
InteropBackend::Unknown => {
CapabilityStatus::Unsupported(UnsupportedReason::HostBackendUnavailable)
}
};
let dx12_shared_texture = match host_backend {
InteropBackend::Dx12 => CapabilityStatus::Supported,
InteropBackend::Vulkan | InteropBackend::Metal => {
CapabilityStatus::Unsupported(UnsupportedReason::HostBackendMismatch)
}
InteropBackend::Unknown => {
CapabilityStatus::Unsupported(UnsupportedReason::HostBackendUnavailable)
}
};
Self {
host_backend,
gl_framebuffer_source,
vulkan_external_image,
metal_texture_ref,
dx12_shared_texture,
}
}
}
#[cfg(feature = "gl")]
pub trait GlFramebufferSourceImpl {
fn import_into(
&self,
frame: &GlFramebufferSource,
host: &HostWgpuContext,
options: &ImportOptions,
) -> Result<ImportedTexture, InteropError>;
}
fn import_vulkan_external_image(
#[cfg_attr(not(target_os = "linux"), allow(unused_variables))] frame: VulkanExternalImage,
#[cfg_attr(not(target_os = "linux"), allow(unused_variables))] host: &HostWgpuContext,
) -> Result<ImportedTexture, InteropError> {
#[cfg(target_os = "linux")]
{
let metadata = frame.metadata();
let texture = vulkan_dmabuf::import_vulkan_external_image(frame, host)?;
return Ok(ImportedTexture {
texture,
format: metadata.format,
size: metadata.size,
origin: TextureOrigin::TopLeft,
generation: metadata.generation,
consumer_sync: metadata.producer_sync,
});
}
#[cfg(not(target_os = "linux"))]
Err(InteropError::Unsupported(
UnsupportedReason::NativeImportNotYetImplemented,
))
}
fn import_metal_frame(
#[cfg_attr(not(target_vendor = "apple"), allow(unused_variables))] frame: MetalTextureRef,
#[cfg_attr(not(target_vendor = "apple"), allow(unused_variables))] host: &HostWgpuContext,
) -> Result<ImportedTexture, InteropError> {
#[cfg(target_vendor = "apple")]
{
let metadata = frame.metadata();
let texture = metal_texture_ref::import_metal_texture_ref(frame, host)?;
return Ok(ImportedTexture {
texture,
format: metadata.format,
size: metadata.size,
origin: TextureOrigin::TopLeft,
generation: metadata.generation,
consumer_sync: metadata.producer_sync,
});
}
#[cfg(not(target_vendor = "apple"))]
Err(InteropError::Unsupported(
UnsupportedReason::HostBackendMismatch,
))
}
fn import_dx12_shared_frame(
#[cfg_attr(not(target_os = "windows"), allow(unused_variables))] frame: Dx12SharedTexture,
#[cfg_attr(not(target_os = "windows"), allow(unused_variables))] host: &HostWgpuContext,
) -> Result<ImportedTexture, InteropError> {
#[cfg(target_os = "windows")]
{
let metadata = frame.metadata();
let texture = dx12_shared_texture::import_dx12_shared_texture(frame, host)?;
return Ok(ImportedTexture {
texture,
format: metadata.format,
size: metadata.size,
origin: TextureOrigin::TopLeft,
generation: metadata.generation,
consumer_sync: metadata.producer_sync,
});
}
#[cfg(not(target_os = "windows"))]
Err(InteropError::Unsupported(
UnsupportedReason::HostBackendMismatch,
))
}
pub fn backend_name(device: &wgpu::Device) -> &'static str {
match detect_backend(device) {
InteropBackend::Vulkan => "Vulkan",
InteropBackend::Metal => "Metal",
InteropBackend::Dx12 => "DirectX 12",
InteropBackend::Unknown => "Unknown",
}
}
pub fn print_wgpu_backend(device: &wgpu::Device) {
eprintln!("[wgpu] backend: {}", backend_name(device));
}
fn detect_backend(device: &wgpu::Device) -> InteropBackend {
unsafe {
#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
if device.as_hal::<wgpu::wgc::api::Vulkan>().is_some() {
return InteropBackend::Vulkan;
}
#[cfg(target_vendor = "apple")]
if device.as_hal::<wgpu::wgc::api::Metal>().is_some() {
return InteropBackend::Metal;
}
#[cfg(target_os = "windows")]
if device.as_hal::<wgpu::wgc::api::Dx12>().is_some() {
return InteropBackend::Dx12;
}
}
InteropBackend::Unknown
}
fn detect_dmabuf_support(
#[cfg_attr(not(target_os = "linux"), allow(unused_variables))] device: &wgpu::Device,
backend: InteropBackend,
) -> bool {
#[cfg(target_os = "linux")]
{
if backend != InteropBackend::Vulkan {
return false;
}
unsafe {
let Some(hal_device) = device.as_hal::<wgpu::wgc::api::Vulkan>() else {
return false;
};
let enabled = hal_device.enabled_device_extensions();
crate::vulkan_dmabuf::base_dmabuf_device_extensions()
.iter()
.all(|extension| enabled.contains(extension))
}
}
#[cfg(not(target_os = "linux"))]
{
let _ = backend;
false
}
}
#[cfg(test)]
mod ownership_shape_tests {
use super::*;
static_assertions::assert_not_impl_any!(NativeFrame: Clone, Copy);
static_assertions::assert_not_impl_any!(VulkanExternalImage: Clone, Copy);
static_assertions::assert_not_impl_any!(MetalTextureRef: Clone, Copy);
static_assertions::assert_not_impl_any!(Dx12SharedTexture: Clone, Copy);
#[cfg(target_os = "windows")]
static_assertions::assert_impl_all!(Dx12SharedResource: Clone);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn import_options_default_prefers_normalized_textures() {
let options = ImportOptions::default();
assert!(options.normalize_origin);
assert!(options.normalize_format);
}
#[test]
fn capability_matrix_tracks_backend_shape() {
let vulkan = CapabilityMatrix::for_backend(InteropBackend::Vulkan);
let metal = CapabilityMatrix::for_backend(InteropBackend::Metal);
let dx12 = CapabilityMatrix::for_backend(InteropBackend::Dx12);
let unknown = CapabilityMatrix::for_backend(InteropBackend::Unknown);
assert_eq!(vulkan.gl_framebuffer_source, CapabilityStatus::Supported);
assert_eq!(metal.gl_framebuffer_source, CapabilityStatus::Supported);
assert_eq!(dx12.gl_framebuffer_source, CapabilityStatus::Supported);
assert_eq!(
unknown.gl_framebuffer_source,
CapabilityStatus::Unsupported(UnsupportedReason::HostBackendUnavailable)
);
#[cfg(target_os = "linux")]
{
assert_eq!(
vulkan.vulkan_external_image,
CapabilityStatus::Unsupported(UnsupportedReason::VulkanDmabufExtensionNotEnabled)
);
let vulkan_with_dmabuf = CapabilityMatrix::for_host(InteropBackend::Vulkan, true);
assert_eq!(
vulkan_with_dmabuf.vulkan_external_image,
CapabilityStatus::Supported
);
}
#[cfg(not(target_os = "linux"))]
assert_eq!(
vulkan.vulkan_external_image,
CapabilityStatus::Unsupported(UnsupportedReason::PlatformNotImplemented)
);
assert_eq!(
metal.vulkan_external_image,
CapabilityStatus::Unsupported(UnsupportedReason::HostBackendMismatch)
);
assert_eq!(metal.metal_texture_ref, CapabilityStatus::Supported);
assert_eq!(
vulkan.metal_texture_ref,
CapabilityStatus::Unsupported(UnsupportedReason::HostBackendMismatch)
);
assert_eq!(dx12.dx12_shared_texture, CapabilityStatus::Supported);
assert_eq!(
vulkan.dx12_shared_texture,
CapabilityStatus::Unsupported(UnsupportedReason::HostBackendMismatch)
);
}
#[test]
fn implicit_synchronizer_accepts_implicit_flush() {
assert!(ImplicitOnlySynchronizer::validate(SyncMechanism::ImplicitGlFlush).is_ok());
}
#[test]
fn implicit_synchronizer_rejects_explicit_sync() {
assert!(matches!(
ImplicitOnlySynchronizer::validate(SyncMechanism::ExplicitExternalSemaphore),
Err(InteropError::UnsupportedSynchronization(
SyncMechanism::ExplicitExternalSemaphore
))
));
}
}