#![doc = include_str!("../README.md")]
mod error;
mod sync;
#[cfg(target_os = "windows")]
mod sync_dx12;
#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
mod sync_vulkan;
#[cfg(target_vendor = "apple")]
mod sync_metal;
#[cfg(target_os = "linux")]
mod vulkan_dmabuf;
#[cfg(target_vendor = "apple")]
mod metal_texture_ref;
#[cfg(target_os = "windows")]
mod dx12_shared_texture;
#[cfg(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"));
}
pub mod raw_gl;
#[cfg(feature = "surfman")]
pub mod surfman_gl;
use std::rc::Rc;
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(any(target_os = "linux", target_os = "android", target_os = "windows"))]
pub use sync_vulkan::VulkanSemaphoreSynchronizer;
#[cfg(target_vendor = "apple")]
pub use sync_metal::MetalSharedEventSynchronizer;
#[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 {
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,
}
impl HostWgpuContext {
pub fn new(device: wgpu::Device, queue: wgpu::Queue) -> Self {
Self {
backend: detect_backend(&device),
device,
queue,
}
}
pub fn capabilities(&self) -> CapabilityMatrix {
CapabilityMatrix::for_backend(self.backend)
}
}
#[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,
}
pub struct GlFramebufferSource {
size: PhysicalSize<u32>,
generation: u64,
producer_sync: SyncMechanism,
importer: Rc<dyn GlFramebufferSourceImpl>,
}
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)]
pub struct VulkanExternalImage {
pub size: PhysicalSize<u32>,
pub format: wgpu::TextureFormat,
pub generation: u64,
pub producer_sync: SyncMechanism,
pub dmabuf_fd: i32,
pub dmabuf_offset: u64,
pub dmabuf_stride: u64,
pub drm_modifier: u64,
pub wait_semaphore_fd: Option<i32>,
}
#[derive(Clone, Copy, Debug)]
pub struct MetalTextureRef {
pub size: PhysicalSize<u32>,
pub format: wgpu::TextureFormat,
pub generation: u64,
pub producer_sync: SyncMechanism,
#[cfg(target_vendor = "apple")]
pub raw_metal_texture: *mut std::ffi::c_void,
}
#[derive(Clone, Copy, Debug)]
pub struct Dx12SharedTexture {
pub size: PhysicalSize<u32>,
pub format: wgpu::TextureFormat,
pub generation: u64,
pub producer_sync: SyncMechanism,
pub fence_value: u64,
#[cfg(target_os = "windows")]
pub handle: *mut std::ffi::c_void,
}
#[non_exhaustive]
pub enum NativeFrame {
GlFramebufferSource(GlFramebufferSource),
VulkanExternalImage(VulkanExternalImage),
MetalTextureRef(MetalTextureRef),
Dx12SharedTexture(Dx12SharedTexture),
}
impl NativeFrame {
pub fn kind(&self) -> NativeFrameKind {
match self {
NativeFrame::GlFramebufferSource(_) => NativeFrameKind::GlFramebufferSource,
NativeFrame::VulkanExternalImage(_) => NativeFrameKind::VulkanExternalImage,
NativeFrame::MetalTextureRef(_) => NativeFrameKind::MetalTextureRef,
NativeFrame::Dx12SharedTexture(_) => NativeFrameKind::Dx12SharedTexture,
}
}
pub fn producer_sync(&self) -> SyncMechanism {
match self {
NativeFrame::GlFramebufferSource(frame) => frame.producer_sync(),
NativeFrame::VulkanExternalImage(frame) => frame.producer_sync,
NativeFrame::MetalTextureRef(frame) => frame.producer_sync,
NativeFrame::Dx12SharedTexture(frame) => frame.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>,
}
impl WgpuTextureImporter {
pub fn new(host: HostWgpuContext) -> Self {
Self {
host,
synchronizer: Box::new(ImplicitOnlySynchronizer),
}
}
pub fn with_synchronizer(
host: HostWgpuContext,
synchronizer: Box<dyn InteropSynchronizer>,
) -> Self {
Self { host, synchronizer }
}
pub fn host(&self) -> &HostWgpuContext {
&self.host
}
}
impl TextureImporter for WgpuTextureImporter {
fn import_frame(
&self,
frame: &NativeFrame,
options: &ImportOptions,
) -> Result<ImportedTexture, InteropError> {
self.synchronizer
.producer_complete(frame, frame.producer_sync())?;
let imported = match frame {
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_texture_ref(frame, &self.host),
NativeFrame::Dx12SharedTexture(frame) => import_dx12_shared_texture(frame, &self.host),
}?;
self.synchronizer
.consumer_ready(&imported, imported.consumer_sync)?;
Ok(imported)
}
}
impl CapabilityMatrix {
pub fn for_backend(host_backend: InteropBackend) -> 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")]
{
CapabilityStatus::Supported
}
#[cfg(not(target_os = "linux"))]
{
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,
}
}
}
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 texture = vulkan_dmabuf::import_vulkan_external_image(frame, host)?;
return Ok(ImportedTexture {
texture,
format: frame.format,
size: frame.size,
origin: TextureOrigin::TopLeft,
generation: frame.generation,
consumer_sync: frame.producer_sync,
});
}
#[cfg(not(target_os = "linux"))]
Err(InteropError::Unsupported(
UnsupportedReason::NativeImportNotYetImplemented,
))
}
fn import_metal_texture_ref(
#[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 texture = metal_texture_ref::import_metal_texture_ref(frame, host)?;
return Ok(ImportedTexture {
texture,
format: frame.format,
size: frame.size,
origin: TextureOrigin::TopLeft,
generation: frame.generation,
consumer_sync: frame.producer_sync,
});
}
#[cfg(not(target_vendor = "apple"))]
Err(InteropError::Unsupported(
UnsupportedReason::HostBackendMismatch,
))
}
fn import_dx12_shared_texture(
#[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 texture = dx12_shared_texture::import_dx12_shared_texture(frame, host)?;
return Ok(ImportedTexture {
texture,
format: frame.format,
size: frame.size,
origin: TextureOrigin::TopLeft,
generation: frame.generation,
consumer_sync: frame.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
}
#[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::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
))
));
}
}