use std::cell::RefCell;
use dpi::PhysicalSize;
use euclid::default::Size2D;
use glow::HasContext;
use windows::Win32::Foundation::{CloseHandle, GENERIC_ALL};
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()
}
}
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");
let surface_texture = unsafe {
let texture_size = Size2D::new(size.width as i32, size.height as i32);
let raw = state.d3d11_shared_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)?;
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(state.wgpu_texture.clone())
}
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 init_size_dependent_state(
d3d11_device: &ID3D11Device,
size: PhysicalSize<u32>,
wgpu_device: &wgpu::Device,
) -> Result<SizeDependentState, 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}")))?;
let d3d11_shared_texture = d3d11_shared.ok_or_else(|| {
InteropError::Dx12("CreateTexture2D returned null texture".into())
})?;
let dxgi_resource = d3d11_shared_texture
.cast::<IDXGIResource1>()
.map_err(|e| InteropError::Dx12(format!("Cast to IDXGIResource1 failed: {e}")))?;
let nt_handle = dxgi_resource
.CreateSharedHandle(None, GENERIC_ALL.0, PCWSTR::null())
.map_err(|e| InteropError::Dx12(format!("DXGI CreateSharedHandle failed: {e}")))?;
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 = wgpu_device.create_texture_from_hal::<wgpu::wgc::api::Dx12>(
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: &[],
},
);
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, h, w, 0, glow::COLOR_BUFFER_BIT, glow::NEAREST);
gl.flush();
gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None);
gl.delete_framebuffer(draw_framebuffer);
}
Ok(())
}