use std::sync::Arc;
use std::{error::Error, fmt::Display};
use crate::prelude::{CaptureConfig, CaptureStream, VideoFrame};
#[cfg(target_os = "macos")]
use crate::platform::macos::{capture_stream::MacosCaptureConfig, frame::MacosVideoFrame};
#[cfg(target_os = "macos")]
use crate::feature::metal::*;
#[cfg(target_os = "macos")]
use metal::MTLStorageMode;
#[cfg(target_os = "macos")]
use metal::MTLTextureUsage;
#[cfg(target_os = "windows")]
use d3d12::ComPtr;
#[cfg(target_os = "windows")]
use wgpu::hal::Device;
#[cfg(target_os = "windows")]
use windows::core::PCWSTR;
#[cfg(target_os = "windows")]
use windows::Win32::Foundation::WAIT_OBJECT_0;
#[cfg(target_os = "windows")]
use windows::Win32::Foundation::{CloseHandle, GENERIC_ALL};
#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Direct3D::{D3D_DRIVER_TYPE_UNKNOWN, D3D_FEATURE_LEVEL_11_0};
#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Direct3D11::{D3D11CreateDevice, ID3D11Device5, ID3D11DeviceContext4, ID3D11Fence, D3D11_CREATE_DEVICE_DEBUG, D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC};
#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Direct3D12::{D3D12_CLEAR_VALUE, D3D12_FENCE_FLAG_NONE, D3D12_FENCE_FLAG_SHARED};
#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Direct3D12::{ID3D12Fence, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_HEAP_FLAG_SHARED, D3D12_HEAP_PROPERTIES, D3D12_HEAP_TYPE_DEFAULT, D3D12_MEMORY_POOL_UNKNOWN, D3D12_RESOURCE_DESC, D3D12_RESOURCE_DIMENSION_TEXTURE2D, D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COMMON, D3D12_TEXTURE_LAYOUT_UNKNOWN};
#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_B8G8R8A8_TYPELESS;
#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Dxgi::{CreateDXGIFactory, IDXGIAdapter4, IDXGIFactory5};
#[cfg(target_os = "windows")]
use windows::Win32::System::Threading::{CreateEventA, WaitForSingleObjectEx, CREATE_EVENT, INFINITE, PROCESS_DELETE, PROCESS_SYNCHRONIZE};
#[cfg(target_os = "windows")]
use windows::Win32::System::Threading::{CreateEventExW, THREAD_DELETE, THREAD_SYNCHRONIZE};
#[cfg(target_os = "windows")]
use crate::platform::windows::capture_stream::WindowsCaptureConfig;
#[cfg(target_os = "windows")]
use crate::feature::dx11::*;
#[cfg(target_os = "windows")]
use windows::{core::{Interface, ComInterface}, Graphics::DirectX::DirectXPixelFormat, Win32::Graphics::{Direct3D11::ID3D11Texture2D, Direct3D11::D3D11_CREATE_DEVICE_BGRA_SUPPORT, Direct3D12::{ID3D12CommandQueue, ID3D12Device, ID3D12Resource, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE}}};
#[cfg(target_os = "windows")]
use std::ffi::c_void;
pub trait WgpuCaptureConfigExt: Sized {
fn with_wgpu_device(self, device: Arc<dyn AsRef<wgpu::Device> + Send + Sync + 'static>) -> Result<Self, String>;
}
impl WgpuCaptureConfigExt for CaptureConfig {
fn with_wgpu_device(self, wgpu_device: Arc<dyn AsRef<wgpu::Device> + Send + Sync + 'static>) -> Result<Self, String> {
#[cfg(target_os = "macos")]
{
unsafe {
let device = AsRef::<wgpu::Device>::as_ref(&*wgpu_device).as_hal::<wgpu::hal::api::Metal, _, _>(move |device| {
if let Some(device) = device {
Some(device.raw_device().lock().clone())
} else {
None
}
}).expect("Expected metal device underneath wgpu");
Ok(Self {
impl_capture_config: MacosCaptureConfig {
metal_device: device,
wgpu_device: Some(wgpu_device.clone()),
..self.impl_capture_config
},
..self
})
}
}
#[cfg(target_os = "windows")]
{
unsafe {
let mut dxgi_adapter_result = Err("Unimplemented for this wgpu backend".to_string());
AsRef::<wgpu::Device>::as_ref(&*wgpu_device).as_hal::<wgpu::hal::api::Dx12, _, _>(|device| {
device.map(|device| {
let raw_device_ptr = device.raw_device().as_mut_ptr() as *mut c_void;
let raw_queue_ptr = device.raw_queue().as_mut_ptr() as *mut c_void;
let d3d12_device = ID3D12Device::from_raw(raw_device_ptr);
let d3d12_queue = ID3D12CommandQueue::from_raw(raw_queue_ptr);
let adapter_luid = d3d12_device.GetAdapterLuid();
let dxgi_factory: IDXGIFactory5 = match CreateDXGIFactory() {
Err(error) => {
dxgi_adapter_result = Err(format!("Failed to create dxgi factory: {}", error.to_string()));
return;
},
Ok(factory) => factory,
};
dxgi_adapter_result = dxgi_factory.EnumAdapterByLuid(adapter_luid)
.map_err(|error| format!("Failed to find matching dxgi adapter for wgpu device: {}", error.to_string()))
.map(|dxgi_adapter: IDXGIAdapter4| (dxgi_adapter, d3d12_device, d3d12_queue));
})
});
let (dxgi_adapter, _d3d12_device, _d3d12_queue) = dxgi_adapter_result?;
let dxgi_adapter = dxgi_adapter.cast::<IDXGIAdapter4>().unwrap();
let mut d3d11_device = None;
D3D11CreateDevice (
&dxgi_adapter,
D3D_DRIVER_TYPE_UNKNOWN,
None,
D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG,
Some(&[D3D_FEATURE_LEVEL_11_0]),
D3D11_SDK_VERSION,
Some(&mut d3d11_device),
None,
None
).map_err(|error| format!("Failed to create d3d11 device from dxgi adapter: {}", error.to_string()))?;
let d3d11_device = d3d11_device.unwrap();
Ok(Self {
impl_capture_config: WindowsCaptureConfig {
d3d11_device: Some(d3d11_device),
wgpu_device: Some(wgpu_device),
dxgi_adapter: Some(dxgi_adapter),
..self.impl_capture_config
},
..self
})
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum WgpuVideoFramePlaneTexture {
Rgba,
Luminance,
Chroma
}
#[derive(Clone, Debug)]
pub enum WgpuVideoFrameError {
NoBackendTexture,
InvalidVideoPlaneTexture,
NoWgpuDevice,
Other(String)
}
impl Display for WgpuVideoFrameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoBackendTexture => f.write_str("WgpuVideoFrameError::NoBackendTexture"),
Self::InvalidVideoPlaneTexture => f.write_str("WgpuVideoFrameError::InvalidVideoPlaneTexture"),
Self::NoWgpuDevice => f.write_str("WgpuVideoFrameError::NoWgpuDevice"),
Self::Other(error) => f.write_fmt(format_args!("WgpuVideoFrameError::Other(\"{}\")", error)),
}
}
}
impl Error for WgpuVideoFrameError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
fn description(&self) -> &str {
"description() is deprecated; use Display"
}
fn cause(&self) -> Option<&dyn Error> {
self.source()
}
}
pub trait WgpuVideoFrameExt {
fn get_wgpu_texture(&self, plane: WgpuVideoFramePlaneTexture, label: Option<&'static str>) -> Result<wgpu::Texture, WgpuVideoFrameError>;
}
impl WgpuVideoFrameExt for VideoFrame {
fn get_wgpu_texture(&self, plane: WgpuVideoFramePlaneTexture, label: Option<&'static str>) -> Result<wgpu::Texture, WgpuVideoFrameError> {
#[cfg(target_os = "macos")]
{
let wgpu_device = match &self.impl_video_frame {
MacosVideoFrame::SCStream(sc_stream_frame) => sc_stream_frame.wgpu_device.clone(),
MacosVideoFrame::CGDisplayStream(cg_display_stream_frame) => cg_display_stream_frame.wgpu_device.clone(),
}.ok_or(WgpuVideoFrameError::NoWgpuDevice)?;
let metal_plane = match plane {
WgpuVideoFramePlaneTexture::Rgba => MetalVideoFramePlaneTexture::Rgba,
WgpuVideoFramePlaneTexture::Chroma => MetalVideoFramePlaneTexture::Chroma,
WgpuVideoFramePlaneTexture::Luminance => MetalVideoFramePlaneTexture::Luminance,
};
match MetalVideoFrameExt::get_metal_texture(self, metal_plane) {
Ok(metal_texture) => {
unsafe {
let descriptor = wgpu::TextureDescriptor {
label,
size: wgpu::Extent3d {
width: metal_texture.width() as u32,
height: metal_texture.height() as u32,
depth_or_array_layers: metal_texture.depth() as u32,
},
mip_level_count: metal_texture.mipmap_level_count() as u32,
sample_count: metal_texture.sample_count() as u32,
dimension: match metal_texture.texture_type() {
metal::MTLTextureType::D2 |
metal::MTLTextureType::D2Multisample=> wgpu::TextureDimension::D2,
_ => return Err(WgpuVideoFrameError::Other("Unsupported metal texture type".to_string()))
},
format: match metal_texture.pixel_format() {
metal::MTLPixelFormat::BGRA8Unorm => wgpu::TextureFormat::Bgra8Unorm,
metal::MTLPixelFormat::BGRA8Unorm_sRGB => wgpu::TextureFormat::Bgra8UnormSrgb,
metal::MTLPixelFormat::RGBA8Sint => wgpu::TextureFormat::Rgba8Sint,
metal::MTLPixelFormat::RGBA8Uint => wgpu::TextureFormat::Rgba8Uint,
metal::MTLPixelFormat::RGBA8Unorm => wgpu::TextureFormat::Rgba8Unorm,
metal::MTLPixelFormat::RGBA8Unorm_sRGB => wgpu::TextureFormat::Rgba8UnormSrgb,
metal::MTLPixelFormat::RGBA8Snorm => wgpu::TextureFormat::Rgba8Snorm,
metal::MTLPixelFormat::RGB10A2Uint => wgpu::TextureFormat::Rgb10a2Uint,
metal::MTLPixelFormat::RGB10A2Unorm => wgpu::TextureFormat::Rgb10a2Unorm,
metal::MTLPixelFormat::RG8Sint => wgpu::TextureFormat::Rg8Sint,
metal::MTLPixelFormat::RG8Snorm => wgpu::TextureFormat::Rg8Snorm,
metal::MTLPixelFormat::RG8Uint => wgpu::TextureFormat::Rg8Snorm,
metal::MTLPixelFormat::RG8Unorm => wgpu::TextureFormat::Rg8Unorm,
metal::MTLPixelFormat::R8Sint => wgpu::TextureFormat::R8Sint,
metal::MTLPixelFormat::R8Snorm => wgpu::TextureFormat::R8Snorm,
metal::MTLPixelFormat::R8Uint => wgpu::TextureFormat::R8Uint,
metal::MTLPixelFormat::R8Unorm => wgpu::TextureFormat::R8Unorm,
_ => return Err(WgpuVideoFrameError::Other(format!("Unsupported metal texture format: {:?}", metal_texture.pixel_format()))),
},
usage: {
let metal_usage = metal_texture.usage();
let storage_mode = metal_texture.storage_mode();
if metal_usage.contains(MTLTextureUsage::RenderTarget) { wgpu::TextureUsages::RENDER_ATTACHMENT } else { wgpu::TextureUsages::empty() }.union(
if metal_usage.contains(MTLTextureUsage::ShaderRead ) { wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING } else { wgpu::TextureUsages::empty() } ).union(
if metal_usage.contains(MTLTextureUsage::ShaderWrite) { wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING } else { wgpu::TextureUsages::empty() } ).union(
match storage_mode {
MTLStorageMode::Managed |
MTLStorageMode::Private |
MTLStorageMode::Shared => wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC,
MTLStorageMode::Memoryless => wgpu::TextureUsages::empty(),
}
)
},
view_formats: &[],
};
let wgpu_metal_texture = wgpu::hal::metal::Device::texture_from_raw(
metal_texture.clone(),
descriptor.format,
metal_texture.texture_type(),
metal_texture.array_length() as u32,
metal_texture.mipmap_level_count() as u32,
wgpu::hal::CopyExtent { width: metal_texture.width() as u32, height: metal_texture.height() as u32, depth: metal_texture.depth() as u32 }
);
Ok((&*wgpu_device).as_ref().create_texture_from_hal::<wgpu::hal::api::Metal>(wgpu_metal_texture, &descriptor))
}
},
Err(MacosVideoFrameError::InvalidVideoPlaneTexture) => Err(WgpuVideoFrameError::InvalidVideoPlaneTexture),
Err(MacosVideoFrameError::NoImageBuffer) |
Err(MacosVideoFrameError::NoIoSurface) => Err(WgpuVideoFrameError::NoBackendTexture),
Err(MacosVideoFrameError::Other(e)) => Err(WgpuVideoFrameError::Other(e)),
}
}
#[cfg(target_os = "windows")]
{
if plane != WgpuVideoFramePlaneTexture::Rgba {
return Err(WgpuVideoFrameError::InvalidVideoPlaneTexture);
}
let wgpu_device = self.impl_video_frame.wgpu_device.as_ref()
.ok_or(WgpuVideoFrameError::NoWgpuDevice)?.clone();
let d3d11_5_device = self.impl_video_frame.device.cast::<ID3D11Device5>()
.map_err(|error| WgpuVideoFrameError::Other(format!("Device is incompatible with resource sharing interface: {}", error)))?;
let (frame_texture, pixel_format) = WindowsDx11VideoFrame::get_dx11_texture(self)
.map_err(|_| WgpuVideoFrameError::NoBackendTexture)?;
let wgpu_format = match pixel_format {
DirectXPixelFormat::B8G8R8A8Typeless => wgpu::TextureFormat::Bgra8Unorm,
DirectXPixelFormat::B8G8R8A8UIntNormalized => wgpu::TextureFormat::Bgra8Unorm,
DirectXPixelFormat::B8G8R8A8UIntNormalizedSrgb => wgpu::TextureFormat::Bgra8UnormSrgb,
DirectXPixelFormat::R10G10B10A2Typeless => wgpu::TextureFormat::Rgb10a2Uint,
DirectXPixelFormat::R10G10B10A2UInt => wgpu::TextureFormat::Rgb10a2Uint,
DirectXPixelFormat::R10G10B10A2UIntNormalized => wgpu::TextureFormat::Rgb10a2Unorm,
DirectXPixelFormat::R16G16B16A16Float => wgpu::TextureFormat::Rgba16Float,
_ => return Err(WgpuVideoFrameError::Other("Unsupported DirectXPixelFormat".to_string()))
};
unsafe {
AsRef::as_ref(&*wgpu_device).as_hal::<wgpu::hal::api::Dx12, _, _>(|wgpu_dx12_device| {
let wgpu_dx12_device = wgpu_dx12_device.unwrap();
let d3d12_device_ptr = wgpu_dx12_device.raw_device().as_ptr() as *mut c_void;
let d3d12_device = ID3D12Device::from_raw_borrowed(&d3d12_device_ptr).unwrap();
let d3d12_queue_ptr = wgpu_dx12_device.raw_queue().as_ptr() as *mut c_void;
let d3d12_queue = ID3D12CommandQueue::from_raw_borrowed(&d3d12_queue_ptr).unwrap();
let mut frame_desc = D3D11_TEXTURE2D_DESC::default();
frame_texture.GetDesc(&mut frame_desc as *mut _);
let wgpu_size = wgpu::Extent3d {
width: frame_desc.Width,
height: frame_desc.Height,
depth_or_array_layers: frame_desc.ArraySize,
};
let d3d12_texture_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Alignment: 0,
Width: frame_desc.Width as u64,
Height: frame_desc.Height,
DepthOrArraySize: frame_desc.ArraySize as u16,
MipLevels: frame_desc.MipLevels as u16,
Format: frame_desc.Format,
SampleDesc: frame_desc.SampleDesc,
Layout: D3D12_TEXTURE_LAYOUT_UNKNOWN,
Flags: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS | D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS
};
let d3d12_texture_heap_properties = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 0,
VisibleNodeMask: 0,
};
let d3d12_texture_clear_value = D3D12_CLEAR_VALUE {
Format: frame_desc.Format,
Anonymous: windows::Win32::Graphics::Direct3D12::D3D12_CLEAR_VALUE_0 {
Color: [0.0, 0.0, 0.0, 0.0]
}
};
let mut d3d12_texture = None;
d3d12_device.CreateCommittedResource(
&d3d12_texture_heap_properties as *const _,
D3D12_HEAP_FLAG_SHARED,
&d3d12_texture_desc as *const _,
D3D12_RESOURCE_STATE_COMMON,
Some(&d3d12_texture_clear_value),
&mut d3d12_texture as *mut _
).map_err(|error| WgpuVideoFrameError::Other(format!("Failed to create d3d12 texture: {}", error.to_string())))?;
let d3d12_texture: ID3D12Resource = d3d12_texture.unwrap();
let dxgi_shared_texture_handle = d3d12_device.CreateSharedHandle(
&d3d12_texture,
None,
GENERIC_ALL.0,
None
).map_err(|error| WgpuVideoFrameError::Other(format!("Failed to share d3d12 texture: {}", error.to_string())))?;
let d3d11_shared_texture: ID3D11Texture2D = d3d11_5_device.OpenSharedResource1(dxgi_shared_texture_handle)
.map_err(|error| WgpuVideoFrameError::Other(format!("Failed to use dxgi shared texture in d3d11: {}", error.to_string())))?;
let d3d12_fence: ID3D12Fence = d3d12_device.CreateFence(0, D3D12_FENCE_FLAG_SHARED)
.map_err(|error| WgpuVideoFrameError::Other(format!("Failed to create fence: {}", error)))?;
let fence_event = CreateEventA(None, false, false, None)
.map_err(|error| WgpuVideoFrameError::Other(format!("Failed to create fence event: {}", error)))?;
d3d12_fence.SetEventOnCompletion(1, fence_event)
.map_err(|error| WgpuVideoFrameError::Other(format!("Failed to set fence completion event: {}", error.to_string())))?;
let dxgi_shared_fence_handle = d3d12_device.CreateSharedHandle(
&d3d12_fence,
None,
GENERIC_ALL.0,
None
).map_err(|error| WgpuVideoFrameError::Other(format!("Failed to share fence with dxgi: {}", error.to_string())))?;
let mut d3d11_shared_fence = None;
d3d11_5_device.OpenSharedFence(dxgi_shared_fence_handle, &mut d3d11_shared_fence)
.map_err(|error| WgpuVideoFrameError::Other(format!("Failed to use dxgi shared fence: {}", error.to_string())))?;
let d3d11_shared_fence: ID3D11Fence = d3d11_shared_fence.unwrap();
{
let device_context: ID3D11DeviceContext4 = self.impl_video_frame.device.GetImmediateContext()
.map_err(|error| WgpuVideoFrameError::Other(format!("Failed to get d3d11 device context: {}", error.to_string())))?
.cast()
.map_err(|error| WgpuVideoFrameError::Other(format!("Failed to get d3d11 device context v4: {}", error.to_string())))?;
device_context.CopyResource(&d3d11_shared_texture, &frame_texture);
device_context.Signal(&d3d11_shared_fence, 1)
.map_err(|error| WgpuVideoFrameError::Other(format!("Failed to queue fence signal: {}", error.to_string())))?;
drop(frame_texture);
drop(d3d11_shared_texture);
drop(d3d11_shared_fence);
device_context.Flush();
}
CloseHandle(dxgi_shared_texture_handle)
.map_err(|error| WgpuVideoFrameError::Other(format!("Failed to close shared texture handle: {}", error.to_string())))?;
let texture_ptr: ComPtr<winapi::um::d3d12::ID3D12Resource> = d3d12::ComPtr::from_raw(d3d12_texture.into_raw() as *mut _);
let hal_texture = wgpu::hal::dx12::Device::texture_from_raw(
texture_ptr.clone(),
wgpu_format,
wgpu::TextureDimension::D2,
wgpu_size,
frame_desc.MipLevels.max(1),
frame_desc.SampleDesc.Count
);
d3d12_queue.Wait(&d3d12_fence, 1)
.map_err(|error| WgpuVideoFrameError::Other(format!("Failed to enqueue wait on fence: {}", error.to_string())))?;
if WaitForSingleObjectEx(fence_event, INFINITE, false) != WAIT_OBJECT_0 {
Err(WgpuVideoFrameError::Other(format!("Failed wait on completion fence")))?
}
CloseHandle(dxgi_shared_fence_handle)
.map_err(|error| WgpuVideoFrameError::Other(format!("Failed to close shared fence handle: {}", error.to_string())))?;
CloseHandle(fence_event)
.map_err(|error| WgpuVideoFrameError::Other(format!("Failed to close fence event handle: {}", error.to_string())))?;
let desc = wgpu::TextureDescriptor {
label,
size: wgpu_size,
mip_level_count: frame_desc.MipLevels.max(1),
sample_count: frame_desc.SampleDesc.Count,
dimension: wgpu::TextureDimension::D2,
format: wgpu_format,
usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[wgpu_format]
};
let result = Ok((*wgpu_device).as_ref().create_texture_from_hal::<wgpu::hal::api::Dx12>(hal_texture, &desc));
std::mem::drop(std::mem::transmute_copy::<_, ComPtr<winapi::um::d3d12::ID3D12Resource>>(&texture_ptr));
result
}).unwrap()
}
}
}
}
pub trait WgpuCaptureStreamExt {
fn get_wgpu_device_wrapper(&self) -> Option<Arc<dyn AsRef<wgpu::Device> + Send + Sync + 'static>>;
fn get_wgpu_device(&self) -> Option<&wgpu::Device>;
}
impl WgpuCaptureStreamExt for CaptureStream {
fn get_wgpu_device(&self) -> Option<&wgpu::Device> {
#[cfg(target_os = "macos")]
{ self.impl_capture_stream.wgpu_device.as_ref().map(|wgpu_device| AsRef::<wgpu::Device>::as_ref(wgpu_device.as_ref())) }
#[cfg(target_os = "windows")]
{ self.impl_capture_stream.wgpu_device.as_ref().map(|wgpu_device| AsRef::<wgpu::Device>::as_ref(wgpu_device.as_ref())) }
}
fn get_wgpu_device_wrapper(&self) -> Option<Arc<dyn AsRef<wgpu::Device> + Send + Sync + 'static>> {
#[cfg(target_os = "macos")]
{ self.impl_capture_stream.wgpu_device.clone() }
#[cfg(target_os = "windows")]
{ self.impl_capture_stream.wgpu_device.clone() }
}
}