use std::cell::RefCell;
use dpi::PhysicalSize;
use euclid::default::Size2D;
use glow::HasContext;
use std::os::windows::io::FromRawHandle;
use windows::Win32::Foundation::{CloseHandle, GENERIC_ALL, HANDLE};
use windows::Win32::Graphics::Direct3D11::{
D3D11_BIND_RENDER_TARGET, D3D11_BIND_SHADER_RESOURCE, D3D11_RESOURCE_MISC_SHARED,
D3D11_RESOURCE_MISC_SHARED_NTHANDLE, D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT, ID3D11Device,
ID3D11Texture2D,
};
use windows::Win32::Graphics::Direct3D12::ID3D12Resource;
use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_SAMPLE_DESC};
use windows::Win32::Graphics::Dxgi::IDXGIResource1;
use windows::core::{IUnknown, Interface, PCWSTR};
use crate::{HostWgpuContext, InteropError};
struct SizeDependentState {
d3d11_shared_texture: ID3D11Texture2D,
wgpu_texture: wgpu::Texture,
size: PhysicalSize<u32>,
}
pub(super) struct AngleDx12SharedCache {
state: RefCell<Option<SizeDependentState>>,
}
impl AngleDx12SharedCache {
pub(super) fn new() -> Self {
Self {
state: RefCell::new(None),
}
}
}
impl Default for AngleDx12SharedCache {
fn default() -> Self {
Self::new()
}
}
struct ExportState {
d3d11_shared_texture: ID3D11Texture2D,
shared_resource: crate::Dx12SharedResource,
size: PhysicalSize<u32>,
}
pub(super) struct AngleDx12ExportCache {
state: RefCell<Option<ExportState>>,
}
impl AngleDx12ExportCache {
pub(super) fn new() -> Self {
Self {
state: RefCell::new(None),
}
}
}
impl Default for AngleDx12ExportCache {
fn default() -> Self {
Self::new()
}
}
pub(super) fn import_current_frame(
cache: &AngleDx12SharedCache,
surfman_device: &surfman::Device,
surfman_context: &mut surfman::Context,
glow_gl: &glow::Context,
source_fbo: u32,
size: PhysicalSize<u32>,
host: &HostWgpuContext,
) -> Result<wgpu::Texture, InteropError> {
let _ = unsafe { host.device.as_hal::<wgpu::wgc::api::Dx12>() }.ok_or(
InteropError::BackendMismatch {
expected: "Dx12",
actual: "non-Dx12",
},
)?;
let needs_recreate = cache
.state
.borrow()
.as_ref()
.map_or(true, |s| s.size != size);
if needs_recreate {
let d3d11_device = angle_d3d11_device(surfman_device)?;
let new_state = init_size_dependent_state(&d3d11_device, size, &host.device)?;
*cache.state.borrow_mut() = Some(new_state);
}
let cache_borrow = cache.state.borrow();
let state = cache_borrow.as_ref().expect("just initialized");
blit_source_into_d3d11_texture(
surfman_device,
surfman_context,
glow_gl,
&state.d3d11_shared_texture,
source_fbo,
size,
false,
)?;
Ok(state.wgpu_texture.clone())
}
fn blit_source_into_d3d11_texture(
surfman_device: &surfman::Device,
surfman_context: &mut surfman::Context,
glow_gl: &glow::Context,
d3d11_texture: &ID3D11Texture2D,
source_fbo: u32,
size: PhysicalSize<u32>,
finish: bool,
) -> Result<(), InteropError> {
let surface_texture = unsafe {
let texture_size = Size2D::new(size.width as i32, size.height as i32);
let raw = d3d11_texture.clone().into_raw();
let texture_comptr = wio::com::ComPtr::from_raw(raw as *mut _);
surfman_device
.create_surface_texture_from_texture(surfman_context, &texture_size, texture_comptr)
.map_err(|err| {
InteropError::Surfman(format!(
"create_surface_texture_from_texture failed: {err:?}"
))
})?
};
let gl_texture = surfman_device
.surface_texture_object(&surface_texture)
.ok_or_else(|| InteropError::OpenGl("ANGLE returned no GL texture for pbuffer".into()))?;
blit_fbo_to_gl_texture(glow_gl, source_fbo, gl_texture, size)?;
if finish {
unsafe { glow_gl.finish() };
}
let mut inner_surface = surfman_device
.destroy_surface_texture(surfman_context, surface_texture)
.map_err(|(err, _)| {
InteropError::Surfman(format!("destroy_surface_texture failed: {err:?}"))
})?;
surfman_device
.destroy_surface(surfman_context, &mut inner_surface)
.map_err(|err| InteropError::Surfman(format!("destroy_surface failed: {err:?}")))?;
Ok(())
}
pub(super) fn export_current_frame(
cache: &AngleDx12ExportCache,
surfman_device: &surfman::Device,
surfman_context: &mut surfman::Context,
glow_gl: &glow::Context,
source_fbo: u32,
size: PhysicalSize<u32>,
generation: u64,
) -> Result<crate::Dx12SharedTexture, InteropError> {
let needs_recreate = cache
.state
.borrow()
.as_ref()
.map_or(true, |s| s.size != size);
if needs_recreate {
let d3d11_device = angle_d3d11_device(surfman_device)?;
let d3d11_shared_texture = create_d3d11_shared_texture(&d3d11_device, size)?;
let nt_handle = export_nt_handle(&d3d11_shared_texture)?;
let handle = unsafe { std::os::windows::io::OwnedHandle::from_raw_handle(nt_handle.0) };
*cache.state.borrow_mut() = Some(ExportState {
d3d11_shared_texture,
shared_resource: crate::Dx12SharedResource::from_owned_handle(handle),
size,
});
}
let cache_borrow = cache.state.borrow();
let state = cache_borrow.as_ref().expect("just initialized");
blit_source_into_d3d11_texture(
surfman_device,
surfman_context,
glow_gl,
&state.d3d11_shared_texture,
source_fbo,
size,
true,
)?;
Ok(state.shared_resource.frame(
crate::FrameMetadata {
size,
format: wgpu::TextureFormat::Rgba8Unorm,
generation,
producer_sync: crate::SyncMechanism::None,
},
0,
))
}
fn angle_d3d11_device(surfman_device: &surfman::Device) -> Result<ID3D11Device, InteropError> {
let native_device = surfman_device.native_device();
if native_device.d3d11_device.is_null() {
return Err(InteropError::Surfman(
"ANGLE D3D11 device pointer is null on surfman::Device::native_device()".into(),
));
}
unsafe {
IUnknown::from_raw(native_device.d3d11_device as *mut _)
.cast::<ID3D11Device>()
.map_err(|err| InteropError::Dx12(format!("D3D11 device cast failed: {err}")))
}
}
fn create_d3d11_shared_texture(
d3d11_device: &ID3D11Device,
size: PhysicalSize<u32>,
) -> Result<ID3D11Texture2D, InteropError> {
unsafe {
let mut d3d11_shared: Option<ID3D11Texture2D> = None;
d3d11_device
.CreateTexture2D(
&D3D11_TEXTURE2D_DESC {
Width: size.width,
Height: size.height,
MipLevels: 1,
ArraySize: 1,
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Usage: D3D11_USAGE_DEFAULT,
BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
CPUAccessFlags: 0,
MiscFlags: (D3D11_RESOURCE_MISC_SHARED.0
| D3D11_RESOURCE_MISC_SHARED_NTHANDLE.0) as u32,
},
None,
Some(&mut d3d11_shared),
)
.map_err(|e| InteropError::Dx12(format!("D3D11 CreateTexture2D failed: {e}")))?;
d3d11_shared
.ok_or_else(|| InteropError::Dx12("CreateTexture2D returned null texture".into()))
}
}
fn export_nt_handle(texture: &ID3D11Texture2D) -> Result<HANDLE, InteropError> {
unsafe {
let dxgi_resource = texture
.cast::<IDXGIResource1>()
.map_err(|e| InteropError::Dx12(format!("Cast to IDXGIResource1 failed: {e}")))?;
dxgi_resource
.CreateSharedHandle(None, GENERIC_ALL.0, PCWSTR::null())
.map_err(|e| InteropError::Dx12(format!("DXGI CreateSharedHandle failed: {e}")))
}
}
fn init_size_dependent_state(
d3d11_device: &ID3D11Device,
size: PhysicalSize<u32>,
wgpu_device: &wgpu::Device,
) -> Result<SizeDependentState, InteropError> {
let d3d11_shared_texture = create_d3d11_shared_texture(d3d11_device, size)?;
let nt_handle = export_nt_handle(&d3d11_shared_texture)?;
unsafe {
let hal_device = wgpu_device
.as_hal::<wgpu::wgc::api::Dx12>()
.ok_or(InteropError::BackendMismatch {
expected: "Dx12",
actual: "non-Dx12",
})?;
let dx12_device = hal_device.raw_device().clone();
let mut dx12_resource: Option<ID3D12Resource> = None;
dx12_device
.OpenSharedHandle(nt_handle, &mut dx12_resource)
.map_err(|e| InteropError::Dx12(format!("D3D12 OpenSharedHandle failed: {e}")))?;
let dx12_resource = dx12_resource
.ok_or_else(|| InteropError::Dx12("OpenSharedHandle returned null resource".into()))?;
let _ = CloseHandle(nt_handle);
let extent = wgpu::Extent3d {
width: size.width,
height: size.height,
depth_or_array_layers: 1,
};
let hal_texture = wgpu_hal::dx12::Device::texture_from_raw(
dx12_resource,
wgpu::TextureFormat::Rgba8Unorm,
wgpu::TextureDimension::D2,
extent,
1,
1,
);
let wgpu_texture = crate::wgpu_compat::create_texture_from_hal::<wgpu_hal::api::Dx12>(
&wgpu_device,
hal_texture,
&wgpu::TextureDescriptor {
label: Some("angle-d3d11-shared-dx12-import"),
size: extent,
format: wgpu::TextureFormat::Rgba8Unorm,
dimension: wgpu::TextureDimension::D2,
mip_level_count: 1,
sample_count: 1,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
},
wgpu::TextureUses::UNINITIALIZED,
);
Ok(SizeDependentState {
d3d11_shared_texture,
wgpu_texture,
size,
})
}
}
fn blit_fbo_to_gl_texture(
gl: &glow::Context,
source_fbo: u32,
gl_texture: glow::Texture,
size: PhysicalSize<u32>,
) -> Result<(), InteropError> {
unsafe {
let draw_framebuffer = gl.create_framebuffer().map_err(InteropError::OpenGl)?;
gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, Some(draw_framebuffer));
gl.framebuffer_texture_2d(
glow::DRAW_FRAMEBUFFER,
glow::COLOR_ATTACHMENT0,
glow::TEXTURE_2D,
Some(gl_texture),
0,
);
let read_framebuffer = std::num::NonZeroU32::new(source_fbo).map(glow::NativeFramebuffer);
gl.bind_framebuffer(glow::READ_FRAMEBUFFER, read_framebuffer);
let (w, h) = (size.width as i32, size.height as i32);
gl.blit_framebuffer(0, 0, w, h, 0, 0, w, h, glow::COLOR_BUFFER_BIT, glow::NEAREST);
gl.flush();
gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None);
gl.delete_framebuffer(draw_framebuffer);
}
Ok(())
}