use std::ffi::{CString, c_char, c_void};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use parking_lot::Mutex;
use super::{ExportBackend, ExportBuffer};
use crate::error::{Error, Result};
type Handle = *mut c_void;
type Hwnd = *mut c_void;
type Hdc = *mut c_void;
type Hglrc = *mut c_void;
type Hmodule = *mut c_void;
type Bool32 = i32;
type Hresult = i32;
const NULL_HANDLE: Handle = std::ptr::null_mut();
#[repr(C)]
#[derive(Clone, Copy)]
struct Guid {
data1: u32,
data2: u16,
data3: u16,
data4: [u8; 8],
}
const IID_IDXGI_FACTORY1: Guid = Guid {
data1: 0x770a_ae78,
data2: 0xf26f,
data3: 0x4dba,
data4: [0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87],
};
#[cfg(feature = "wgpu")]
const IID_IUNKNOWN: Guid = Guid {
data1: 0x0000_0000,
data2: 0x0000,
data3: 0x0000,
data4: [0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46],
};
#[cfg(feature = "wgpu")]
const GUID_EXPORT_RELEASE_KEEPER: Guid = Guid {
data1: 0x7b1c_4f2a,
data2: 0x9d63,
data3: 0x4c58,
data4: [0xa1, 0x0e, 0x6f, 0x2d, 0x83, 0xb4, 0x71, 0x95],
};
const IID_ID3D11_MULTITHREAD: Guid = Guid {
data1: 0x9b7e_4e00,
data2: 0x342c,
data3: 0x4106,
data4: [0xa1, 0x9f, 0x4f, 0x27, 0x04, 0xf6, 0x89, 0xf0],
};
const IID_IDXGI_RESOURCE1: Guid = Guid {
data1: 0x3096_1379,
data2: 0x4609,
data3: 0x4a41,
data4: [0x99, 0x8e, 0x54, 0xfe, 0x56, 0x7e, 0xe0, 0xc1],
};
const DXGI_ERROR_NOT_FOUND: Hresult = 0x887A_0002u32 as Hresult;
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetModuleHandleW(name: *const u16) -> Hmodule;
fn GetModuleHandleA(name: *const c_char) -> Hmodule;
fn GetProcAddress(module: Hmodule, name: *const c_char) -> *mut c_void;
fn CloseHandle(object: Handle) -> Bool32;
}
const CS_OWNDC: u32 = 0x0020;
const WS_OVERLAPPED: u32 = 0;
const PFD_DRAW_TO_WINDOW: u32 = 0x0000_0004;
const PFD_SUPPORT_OPENGL: u32 = 0x0000_0020;
const PFD_DOUBLEBUFFER: u32 = 0x0000_0001;
const PFD_TYPE_RGBA: u8 = 0;
const PFD_MAIN_PLANE: u8 = 0;
#[repr(C)]
struct WndClassW {
style: u32,
wnd_proc: Option<unsafe extern "system" fn(Hwnd, u32, usize, isize) -> isize>,
cls_extra: i32,
wnd_extra: i32,
instance: Hmodule,
icon: Handle,
cursor: Handle,
background: Handle,
menu_name: *const u16,
class_name: *const u16,
}
#[repr(C)]
#[derive(Default)]
struct PixelFormatDescriptor {
size: u16,
version: u16,
flags: u32,
pixel_type: u8,
color_bits: u8,
red_bits: u8,
red_shift: u8,
green_bits: u8,
green_shift: u8,
blue_bits: u8,
blue_shift: u8,
alpha_bits: u8,
alpha_shift: u8,
accum_bits: u8,
accum_red_bits: u8,
accum_green_bits: u8,
accum_blue_bits: u8,
accum_alpha_bits: u8,
depth_bits: u8,
stencil_bits: u8,
aux_buffers: u8,
layer_type: u8,
reserved: u8,
layer_mask: u32,
visible_mask: u32,
damage_mask: u32,
}
#[link(name = "user32")]
unsafe extern "system" {
fn RegisterClassW(class: *const WndClassW) -> u16;
fn DefWindowProcW(hwnd: Hwnd, msg: u32, wparam: usize, lparam: isize) -> isize;
fn CreateWindowExW(
ex_style: u32,
class_name: *const u16,
window_name: *const u16,
style: u32,
x: i32,
y: i32,
width: i32,
height: i32,
parent: Hwnd,
menu: Handle,
instance: Hmodule,
param: *mut c_void,
) -> Hwnd;
fn DestroyWindow(hwnd: Hwnd) -> Bool32;
fn GetDC(hwnd: Hwnd) -> Hdc;
fn ReleaseDC(hwnd: Hwnd, hdc: Hdc) -> i32;
}
#[link(name = "gdi32")]
unsafe extern "system" {
fn ChoosePixelFormat(hdc: Hdc, pfd: *const PixelFormatDescriptor) -> i32;
fn SetPixelFormat(hdc: Hdc, format: i32, pfd: *const PixelFormatDescriptor) -> Bool32;
}
use super::gl_consts::{
GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER, GL_FRAMEBUFFER_COMPLETE, GL_NEAREST, GL_RGBA8,
GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER,
};
pub(crate) const DXGI_FORMAT_BGRA8_UNORM: u32 = 87;
#[link(name = "opengl32")]
unsafe extern "system" {
fn wglCreateContext(hdc: Hdc) -> Hglrc;
fn wglDeleteContext(ctx: Hglrc) -> Bool32;
fn wglMakeCurrent(hdc: Hdc, ctx: Hglrc) -> Bool32;
fn wglGetCurrentContext() -> Hglrc;
fn wglGetProcAddress(name: *const c_char) -> *mut c_void;
fn glGenTextures(n: i32, textures: *mut u32);
fn glDeleteTextures(n: i32, textures: *const u32);
fn glBindTexture(target: u32, texture: u32);
fn glTexParameteri(target: u32, pname: u32, param: i32);
fn glFinish();
}
const WGL_ACCESS_READ_WRITE_NV: u32 = 0x0000_0001;
type PfnDxOpenDevice = unsafe extern "system" fn(*mut c_void) -> Handle;
type PfnDxCloseDevice = unsafe extern "system" fn(Handle) -> Bool32;
type PfnDxRegisterObject = unsafe extern "system" fn(Handle, *mut c_void, u32, u32, u32) -> Handle;
type PfnDxUnregisterObject = unsafe extern "system" fn(Handle, Handle) -> Bool32;
type PfnDxLockObjects = unsafe extern "system" fn(Handle, i32, *mut Handle) -> Bool32;
#[allow(non_snake_case)]
struct GlFns {
GenFramebuffers: unsafe extern "system" fn(i32, *mut u32),
DeleteFramebuffers: unsafe extern "system" fn(i32, *const u32),
BindFramebuffer: unsafe extern "system" fn(u32, u32),
FramebufferTexture2D: unsafe extern "system" fn(u32, u32, u32, u32, i32),
CheckFramebufferStatus: unsafe extern "system" fn(u32) -> u32,
DXOpenDevice: PfnDxOpenDevice,
DXCloseDevice: PfnDxCloseDevice,
DXRegisterObject: PfnDxRegisterObject,
DXUnregisterObject: PfnDxUnregisterObject,
DXLockObjects: PfnDxLockObjects,
DXUnlockObjects: PfnDxLockObjects,
}
macro_rules! load_fn {
($name:literal as $ty:ty) => {{
let ptr = gl_proc_address($name);
if ptr.is_null() {
return Err(Error::ExportSetup(
concat!("required GL/WGL entry point missing: ", $name).into(),
));
}
unsafe { std::mem::transmute::<*mut c_void, $ty>(ptr) }
}};
}
const D3D_DRIVER_TYPE_UNKNOWN: u32 = 0;
const D3D_DRIVER_TYPE_HARDWARE: u32 = 1;
const D3D11_SDK_VERSION: u32 = 7;
const D3D11_CREATE_DEVICE_BGRA_SUPPORT: u32 = 0x20;
const D3D11_USAGE_DEFAULT: u32 = 0;
const D3D11_USAGE_STAGING: u32 = 3;
const D3D11_BIND_SHADER_RESOURCE: u32 = 0x8;
const D3D11_BIND_RENDER_TARGET: u32 = 0x20;
const D3D11_CPU_ACCESS_READ: u32 = 0x0002_0000;
const D3D11_MISC_SHARED_NTHANDLE: u32 = 0x0002 | 0x0800;
const D3D11_MISC_SHARED: u32 = 0x0002;
const D3D11_MAP_READ: u32 = 1;
const GENERIC_ALL: u32 = 0x1000_0000;
#[repr(C)]
struct Texture2dDesc {
width: u32,
height: u32,
mip_levels: u32,
array_size: u32,
format: u32,
sample_count: u32,
sample_quality: u32,
usage: u32,
bind_flags: u32,
cpu_access_flags: u32,
misc_flags: u32,
}
#[repr(C)]
struct MappedSubresource {
data: *mut c_void,
row_pitch: u32,
depth_pitch: u32,
}
#[repr(C)]
struct IUnknownVtbl {
query_interface:
unsafe extern "system" fn(*mut c_void, *const Guid, *mut *mut c_void) -> Hresult,
add_ref: unsafe extern "system" fn(*mut c_void) -> u32,
release: unsafe extern "system" fn(*mut c_void) -> u32,
}
#[repr(C)]
struct ID3D11DeviceVtbl {
_unknown: IUnknownVtbl,
_create_buffer: *const c_void,
_create_texture_1d: *const c_void,
create_texture_2d: unsafe extern "system" fn(
*mut c_void,
*const Texture2dDesc,
*const c_void,
*mut *mut c_void,
) -> Hresult,
}
#[repr(C)]
struct ID3D11DeviceContextVtbl {
_unknown: IUnknownVtbl,
_device_child: [*const c_void; 4],
_before_map: [*const c_void; 7],
map: unsafe extern "system" fn(
*mut c_void,
*mut c_void,
u32,
u32,
u32,
*mut MappedSubresource,
) -> Hresult,
unmap: unsafe extern "system" fn(*mut c_void, *mut c_void, u32),
_before_copy_resource: [*const c_void; 31],
copy_resource: unsafe extern "system" fn(*mut c_void, *mut c_void, *mut c_void),
_before_flush: [*const c_void; 63],
flush: unsafe extern "system" fn(*mut c_void),
}
#[cfg(feature = "wgpu")]
#[repr(C)]
struct ID3D12ObjectVtbl {
_unknown: IUnknownVtbl,
_get_private_data: *const c_void,
_set_private_data: *const c_void,
set_private_data_interface:
unsafe extern "system" fn(*mut c_void, *const Guid, *mut c_void) -> Hresult,
}
#[repr(C)]
struct ID3D11MultithreadVtbl {
_unknown: IUnknownVtbl,
_enter_leave: [*const c_void; 2],
set_multithread_protected: unsafe extern "system" fn(*mut c_void, Bool32) -> Bool32,
}
#[repr(C)]
struct IDXGIResource1Vtbl {
_unknown: IUnknownVtbl,
_object: [*const c_void; 4],
_device_subobject: *const c_void,
_resource: [*const c_void; 4],
_create_subresource_surface: *const c_void,
create_shared_handle: unsafe extern "system" fn(
*mut c_void,
*const c_void,
u32,
*const u16,
*mut Handle,
) -> Hresult,
}
#[repr(C)]
struct IDXGIFactory1Vtbl {
_unknown: IUnknownVtbl,
_object: [*const c_void; 4],
_factory: [*const c_void; 5],
enum_adapters1: unsafe extern "system" fn(*mut c_void, u32, *mut *mut c_void) -> Hresult,
}
unsafe fn vtbl<'a, V>(obj: *mut c_void) -> &'a V {
unsafe { &**obj.cast::<*const V>() }
}
unsafe fn com_release(obj: *mut c_void) {
if !obj.is_null() {
unsafe { (vtbl::<IUnknownVtbl>(obj).release)(obj) };
}
}
#[link(name = "d3d11")]
unsafe extern "system" {
fn D3D11CreateDevice(
adapter: *mut c_void,
driver_type: u32,
software: Hmodule,
flags: u32,
feature_levels: *const u32,
num_feature_levels: u32,
sdk_version: u32,
device: *mut *mut c_void,
feature_level: *mut u32,
immediate_context: *mut *mut c_void,
) -> Hresult;
}
#[link(name = "dxgi")]
unsafe extern "system" {
fn CreateDXGIFactory1(riid: *const Guid, factory: *mut *mut c_void) -> Hresult;
}
fn hresult_error(what: &str, hr: Hresult) -> Error {
Error::ExportSetup(format!("{what}: HRESULT {:#010x}", hr as u32))
}
#[cfg(feature = "wgpu")]
#[repr(C)]
struct ReleaseKeeper {
vtable: *const IUnknownVtbl,
refcount: std::sync::atomic::AtomicU32,
on_release: Option<Box<dyn FnOnce() + Send>>,
}
#[cfg(feature = "wgpu")]
unsafe fn keeper<'a>(this: *mut c_void) -> &'a ReleaseKeeper {
unsafe { &*this.cast::<ReleaseKeeper>() }
}
#[cfg(feature = "wgpu")]
unsafe extern "system" fn keeper_query_interface(
this: *mut c_void,
iid: *const Guid,
out: *mut *mut c_void,
) -> Hresult {
const E_POINTER: Hresult = -0x7FFF_BFFD; const E_NOINTERFACE: Hresult = -0x7FFF_BFFE; if out.is_null() {
return E_POINTER;
}
let matches = !iid.is_null()
&& unsafe {
let iid = &*iid;
iid.data1 == IID_IUNKNOWN.data1
&& iid.data2 == IID_IUNKNOWN.data2
&& iid.data3 == IID_IUNKNOWN.data3
&& iid.data4 == IID_IUNKNOWN.data4
};
if matches {
unsafe {
keeper_add_ref(this);
*out = this;
}
return 0;
}
unsafe { *out = std::ptr::null_mut() };
E_NOINTERFACE
}
#[cfg(feature = "wgpu")]
unsafe extern "system" fn keeper_add_ref(this: *mut c_void) -> u32 {
unsafe { keeper(this) }
.refcount
.fetch_add(1, Ordering::Relaxed)
+ 1
}
#[cfg(feature = "wgpu")]
unsafe extern "system" fn keeper_release(this: *mut c_void) -> u32 {
let left = unsafe { keeper(this) }
.refcount
.fetch_sub(1, Ordering::AcqRel)
- 1;
if left == 0 {
let mut owned = unsafe { Box::from_raw(this.cast::<ReleaseKeeper>()) };
if let Some(on_release) = owned.on_release.take() {
on_release();
}
}
left
}
#[cfg(feature = "wgpu")]
static KEEPER_VTABLE: IUnknownVtbl = IUnknownVtbl {
query_interface: keeper_query_interface,
add_ref: keeper_add_ref,
release: keeper_release,
};
#[cfg(feature = "wgpu")]
pub(crate) fn on_resource_release(
resource: *mut c_void,
on_release: Box<dyn FnOnce() + Send>,
) -> bool {
let raw = Box::into_raw(Box::new(ReleaseKeeper {
vtable: &KEEPER_VTABLE,
refcount: std::sync::atomic::AtomicU32::new(1),
on_release: Some(on_release),
}));
let hr = unsafe {
(vtbl::<ID3D12ObjectVtbl>(resource).set_private_data_interface)(
resource,
&GUID_EXPORT_RELEASE_KEEPER,
raw.cast::<c_void>(),
)
};
if hr < 0 {
unsafe { (*raw).on_release = None };
}
unsafe { com_release(raw.cast::<c_void>()) };
hr >= 0
}
struct D3d11 {
device: *mut c_void,
context: Mutex<*mut c_void>,
}
unsafe impl Send for D3d11 {}
unsafe impl Sync for D3d11 {}
impl D3d11 {
fn create(adapter: *mut c_void) -> Result<Self> {
let driver_type = if adapter.is_null() {
D3D_DRIVER_TYPE_HARDWARE
} else {
D3D_DRIVER_TYPE_UNKNOWN
};
let mut device: *mut c_void = std::ptr::null_mut();
let mut context: *mut c_void = std::ptr::null_mut();
let hr = unsafe {
D3D11CreateDevice(
adapter,
driver_type,
std::ptr::null_mut(),
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
std::ptr::null(),
0,
D3D11_SDK_VERSION,
&mut device,
std::ptr::null_mut(),
&mut context,
)
};
if hr < 0 || device.is_null() || context.is_null() {
unsafe {
com_release(context);
com_release(device);
}
return Err(hresult_error("D3D11CreateDevice", hr));
}
unsafe {
let mut multithread: *mut c_void = std::ptr::null_mut();
let hr = (vtbl::<IUnknownVtbl>(context).query_interface)(
context,
&IID_ID3D11_MULTITHREAD,
&mut multithread,
);
if hr >= 0 && !multithread.is_null() {
(vtbl::<ID3D11MultithreadVtbl>(multithread).set_multithread_protected)(
multithread,
1,
);
com_release(multithread);
} else {
tracing::debug!("export: ID3D11Multithread unavailable (HRESULT {hr:#010x})");
}
}
Ok(Self {
device,
context: Mutex::new(context),
})
}
}
impl Drop for D3d11 {
fn drop(&mut self) {
unsafe {
com_release(*self.context.get_mut());
com_release(self.device);
}
}
}
fn enumerate_adapters() -> Vec<*mut c_void> {
let mut factory: *mut c_void = std::ptr::null_mut();
let hr = unsafe { CreateDXGIFactory1(&IID_IDXGI_FACTORY1, &mut factory) };
if hr < 0 || factory.is_null() {
return Vec::new();
}
let mut adapters = Vec::new();
let mut index = 0u32;
loop {
let mut adapter: *mut c_void = std::ptr::null_mut();
let hr = unsafe {
(vtbl::<IDXGIFactory1Vtbl>(factory).enum_adapters1)(factory, index, &mut adapter)
};
if hr == DXGI_ERROR_NOT_FOUND || hr < 0 || adapter.is_null() {
break;
}
adapters.push(adapter);
index += 1;
}
unsafe { com_release(factory) };
adapters
}
struct HiddenWindow {
hwnd: Hwnd,
hdc: Hdc,
}
impl HiddenWindow {
fn new() -> Result<Self> {
const CLASS_NAME: &[u16] = &[
0x6d, 0x70, 0x76, 0x2d, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2d, 0x65, 0x78, 0x70,
0x6f, 0x72, 0x74, 0x00,
];
let instance = unsafe { GetModuleHandleW(std::ptr::null()) };
static REGISTERED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
REGISTERED.get_or_init(|| {
let class = WndClassW {
style: CS_OWNDC,
wnd_proc: Some(DefWindowProcW),
cls_extra: 0,
wnd_extra: 0,
instance,
icon: NULL_HANDLE,
cursor: NULL_HANDLE,
background: NULL_HANDLE,
menu_name: std::ptr::null(),
class_name: CLASS_NAME.as_ptr(),
};
unsafe { RegisterClassW(&class) };
});
let hwnd = unsafe {
CreateWindowExW(
0,
CLASS_NAME.as_ptr(),
CLASS_NAME.as_ptr(),
WS_OVERLAPPED,
0,
0,
1,
1,
std::ptr::null_mut(),
NULL_HANDLE,
instance,
std::ptr::null_mut(),
)
};
if hwnd.is_null() {
return Err(Error::ExportSetup(
"CreateWindowExW failed for the hidden GL window (a session with no window \
station — a service, or SSH without an interactive desktop?)"
.into(),
));
}
let hdc = unsafe { GetDC(hwnd) };
if hdc.is_null() {
unsafe { DestroyWindow(hwnd) };
return Err(Error::ExportSetup(
"GetDC failed for the hidden window".into(),
));
}
Ok(Self { hwnd, hdc })
}
fn set_pixel_format(&self) -> Result<()> {
let pfd = PixelFormatDescriptor {
size: std::mem::size_of::<PixelFormatDescriptor>() as u16,
version: 1,
flags: PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
pixel_type: PFD_TYPE_RGBA,
color_bits: 32,
alpha_bits: 8,
depth_bits: 24,
stencil_bits: 8,
layer_type: PFD_MAIN_PLANE,
..Default::default()
};
let format = unsafe { ChoosePixelFormat(self.hdc, &pfd) };
if format == 0 {
return Err(Error::ExportSetup(
"ChoosePixelFormat found no OpenGL-capable format".into(),
));
}
if unsafe { SetPixelFormat(self.hdc, format, &pfd) } == 0 {
return Err(Error::ExportSetup("SetPixelFormat failed".into()));
}
Ok(())
}
}
impl Drop for HiddenWindow {
fn drop(&mut self) {
unsafe {
ReleaseDC(self.hwnd, self.hdc);
DestroyWindow(self.hwnd);
}
}
}
struct WglContext {
hglrc: Hglrc,
}
impl Drop for WglContext {
fn drop(&mut self) {
unsafe {
if wglGetCurrentContext() == self.hglrc {
wglMakeCurrent(std::ptr::null_mut(), std::ptr::null_mut());
}
wglDeleteContext(self.hglrc);
}
}
}
pub(crate) struct GlContext {
hglrc: Hglrc,
dx_device: Handle,
fns: GlFns,
d3d: Arc<D3d11>,
window: HiddenWindow,
}
impl GlContext {
const PROBE_SIZE: u32 = 64;
fn try_adapter(adapter: *mut c_void) -> Result<Self> {
let window = HiddenWindow::new()?;
window.set_pixel_format()?;
let hglrc = unsafe { wglCreateContext(window.hdc) };
if hglrc.is_null() {
return Err(Error::ExportSetup(
"wglCreateContext failed (no OpenGL ICD? the Microsoft software rasterizer is \
GL 1.1 and cannot drive mpv)"
.into(),
));
}
let context = WglContext { hglrc };
if unsafe { wglMakeCurrent(window.hdc, context.hglrc) } == 0 {
return Err(Error::ExportSetup("wglMakeCurrent failed".into()));
}
let fns = GlFns {
GenFramebuffers: load_fn!(
"glGenFramebuffers" as unsafe extern "system" fn(i32, *mut u32)
),
DeleteFramebuffers: load_fn!(
"glDeleteFramebuffers" as unsafe extern "system" fn(i32, *const u32)
),
BindFramebuffer: load_fn!("glBindFramebuffer" as unsafe extern "system" fn(u32, u32)),
FramebufferTexture2D: load_fn!(
"glFramebufferTexture2D" as unsafe extern "system" fn(u32, u32, u32, u32, i32)
),
CheckFramebufferStatus: load_fn!(
"glCheckFramebufferStatus" as unsafe extern "system" fn(u32) -> u32
),
DXOpenDevice: load_fn!("wglDXOpenDeviceNV" as PfnDxOpenDevice),
DXCloseDevice: load_fn!("wglDXCloseDeviceNV" as PfnDxCloseDevice),
DXRegisterObject: load_fn!("wglDXRegisterObjectNV" as PfnDxRegisterObject),
DXUnregisterObject: load_fn!("wglDXUnregisterObjectNV" as PfnDxUnregisterObject),
DXLockObjects: load_fn!("wglDXLockObjectsNV" as PfnDxLockObjects),
DXUnlockObjects: load_fn!("wglDXUnlockObjectsNV" as PfnDxLockObjects),
};
let d3d = D3d11::create(adapter)?;
let dx_device = unsafe { (fns.DXOpenDevice)(d3d.device) };
if dx_device.is_null() {
return Err(Error::ExportSetup(
"wglDXOpenDeviceNV refused the D3D11 device (GL and D3D on different \
GPUs, or no WGL_NV_DX_interop2 support)"
.into(),
));
}
let hglrc = context.hglrc;
std::mem::forget(context);
let gl = Self {
hglrc,
dx_device,
fns,
d3d: Arc::new(d3d),
window,
};
let mut probe = SurfaceBuffer::new(&gl, Self::PROBE_SIZE, Self::PROBE_SIZE)?;
probe.delete_gl(&gl);
Ok(gl)
}
}
impl ExportBackend for GlContext {
type Buffer = SurfaceBuffer;
const FBO_INTERNAL_FORMAT: i32 = GL_RGBA8 as i32;
fn new() -> Result<Self> {
let adapters = enumerate_adapters();
let candidates: Vec<*mut c_void> = if adapters.is_empty() {
vec![std::ptr::null_mut()]
} else {
adapters.clone()
};
let mut last_error = None;
let mut opened = None;
for adapter in candidates {
match Self::try_adapter(adapter) {
Ok(context) => {
opened = Some(context);
break;
}
Err(e) => {
tracing::debug!("export: adapter unusable for GL interop: {e}");
last_error = Some(e);
}
}
}
for adapter in adapters {
unsafe { com_release(adapter) };
}
opened.ok_or_else(|| {
last_error.unwrap_or_else(|| {
Error::ExportSetup("no D3D11 adapter could be opened for GL interop".into())
})
})
}
fn make_current(&self) -> Result<()> {
if unsafe { wglMakeCurrent(self.window.hdc, self.hglrc) } == 0 {
return Err(Error::ExportSetup("wglMakeCurrent failed".into()));
}
Ok(())
}
fn begin_render(&self, buffer: &SurfaceBuffer) {
buffer.lock_for_gl(self);
}
fn publish_barrier(&self, buffer: &SurfaceBuffer) {
unsafe { glFinish() };
buffer.unlock_from_gl(self);
buffer.copy_to_shared();
}
fn proc_address(name: &str) -> *mut c_void {
gl_proc_address(name)
}
}
impl Drop for GlContext {
fn drop(&mut self) {
unsafe {
let ctx = self.d3d.context.lock();
(self.fns.DXCloseDevice)(self.dx_device);
drop(ctx);
if wglGetCurrentContext() == self.hglrc {
wglMakeCurrent(std::ptr::null_mut(), std::ptr::null_mut());
}
wglDeleteContext(self.hglrc);
}
}
}
pub(crate) struct SurfaceBuffer {
d3d: Arc<D3d11>,
texture: *mut c_void,
interop_texture: *mut c_void,
handle: Handle,
dx_object: Handle,
gl_texture: u32,
fbo: u32,
locked: AtomicBool,
width: u32,
height: u32,
}
unsafe impl Send for SurfaceBuffer {}
unsafe impl Sync for SurfaceBuffer {}
impl ExportBuffer for SurfaceBuffer {
type Backend = GlContext;
fn new(gl: &GlContext, width: u32, height: u32) -> Result<Self> {
let interop_texture =
create_shared_texture(gl.d3d.device, width, height, D3D11_MISC_SHARED)?;
let texture =
match create_shared_texture(gl.d3d.device, width, height, D3D11_MISC_SHARED_NTHANDLE) {
Ok(texture) => texture,
Err(e) => {
unsafe { com_release(interop_texture) };
return Err(e);
}
};
let handle = match create_shared_handle(texture) {
Ok(handle) => handle,
Err(e) => {
unsafe {
com_release(texture);
com_release(interop_texture);
}
return Err(e);
}
};
let mut buffer = Self {
d3d: Arc::clone(&gl.d3d),
texture,
interop_texture,
handle,
dx_object: NULL_HANDLE,
gl_texture: 0,
fbo: 0,
locked: AtomicBool::new(false),
width,
height,
};
let f = &gl.fns;
unsafe {
glGenTextures(1, &mut buffer.gl_texture);
let _ctx = gl.d3d.context.lock();
buffer.dx_object = (f.DXRegisterObject)(
gl.dx_device,
interop_texture,
buffer.gl_texture,
GL_TEXTURE_2D,
WGL_ACCESS_READ_WRITE_NV,
);
}
if buffer.dx_object.is_null() {
buffer.delete_gl(gl);
return Err(Error::ExportSetup(format!(
"wglDXRegisterObjectNV failed for a {width}x{height} BGRA8 render target"
)));
}
buffer.lock_for_gl(gl);
let status = unsafe {
(f.GenFramebuffers)(1, &mut buffer.fbo);
(f.BindFramebuffer)(GL_FRAMEBUFFER, buffer.fbo);
(f.FramebufferTexture2D)(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
buffer.gl_texture,
0,
);
let status = (f.CheckFramebufferStatus)(GL_FRAMEBUFFER);
(f.BindFramebuffer)(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, buffer.gl_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
status
};
buffer.unlock_from_gl(gl);
if status != GL_FRAMEBUFFER_COMPLETE {
buffer.delete_gl(gl);
return Err(Error::ExportSetup(format!(
"shared-texture framebuffer incomplete (status {status:#x})"
)));
}
Ok(buffer)
}
fn fbo(&self) -> u32 {
self.fbo
}
fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
fn delete_gl(&mut self, gl: &GlContext) {
self.unlock_from_gl(gl);
let f = &gl.fns;
unsafe {
if self.fbo != 0 {
(f.DeleteFramebuffers)(1, &self.fbo);
self.fbo = 0;
}
if !self.dx_object.is_null() {
let ctx = self.d3d.context.lock();
(f.DXUnregisterObject)(gl.dx_device, self.dx_object);
drop(ctx);
self.dx_object = NULL_HANDLE;
}
if self.gl_texture != 0 {
glDeleteTextures(1, &self.gl_texture);
self.gl_texture = 0;
}
}
}
fn copy_pixels(&self) -> Vec<u8> {
let (w, h) = (self.width as usize, self.height as usize);
if w == 0 || h == 0 {
return vec![0u8; w * h * 4];
}
let desc = Texture2dDesc {
width: self.width,
height: self.height,
mip_levels: 1,
array_size: 1,
format: DXGI_FORMAT_BGRA8_UNORM,
sample_count: 1,
sample_quality: 0,
usage: D3D11_USAGE_STAGING,
bind_flags: 0,
cpu_access_flags: D3D11_CPU_ACCESS_READ,
misc_flags: 0,
};
let device = self.d3d.device;
let mut staging: *mut c_void = std::ptr::null_mut();
let hr = unsafe {
(vtbl::<ID3D11DeviceVtbl>(device).create_texture_2d)(
device,
&desc,
std::ptr::null(),
&mut staging,
)
};
if hr < 0 || staging.is_null() {
return vec![0u8; w * h * 4];
}
let ctx = self.d3d.context.lock();
unsafe {
let v = vtbl::<ID3D11DeviceContextVtbl>(*ctx);
(v.copy_resource)(*ctx, staging, self.texture);
let mut mapped = MappedSubresource {
data: std::ptr::null_mut(),
row_pitch: 0,
depth_pitch: 0,
};
let out = if (v.map)(*ctx, staging, 0, D3D11_MAP_READ, 0, &mut mapped) >= 0
&& !mapped.data.is_null()
{
let out = super::pack_bgra_rows(
mapped.data.cast::<u8>(),
mapped.row_pitch as usize,
w,
h,
);
(v.unmap)(*ctx, staging, 0);
out
} else {
vec![0u8; w * h * 4]
};
com_release(staging);
out
}
}
}
impl SurfaceBuffer {
fn lock_for_gl(&self, gl: &GlContext) {
if self.dx_object.is_null() || self.locked.swap(true, Ordering::SeqCst) {
return;
}
let mut object = self.dx_object;
let _ctx = self.d3d.context.lock();
if unsafe { (gl.fns.DXLockObjects)(gl.dx_device, 1, &mut object) } == 0 {
tracing::warn!("export: wglDXLockObjectsNV failed; this frame may be stale");
}
}
fn unlock_from_gl(&self, gl: &GlContext) {
if self.dx_object.is_null() || !self.locked.swap(false, Ordering::SeqCst) {
return;
}
let mut object = self.dx_object;
let _ctx = self.d3d.context.lock();
if unsafe { (gl.fns.DXUnlockObjects)(gl.dx_device, 1, &mut object) } == 0 {
tracing::warn!("export: wglDXUnlockObjectsNV failed; this frame may be torn");
}
}
fn copy_to_shared(&self) {
let ctx = self.d3d.context.lock();
unsafe {
let v = vtbl::<ID3D11DeviceContextVtbl>(*ctx);
(v.copy_resource)(*ctx, self.texture, self.interop_texture);
(v.flush)(*ctx);
}
}
pub(crate) fn shared_handle(&self) -> Handle {
self.handle
}
}
impl Drop for SurfaceBuffer {
fn drop(&mut self) {
unsafe {
if !self.handle.is_null() {
CloseHandle(self.handle);
}
com_release(self.texture);
com_release(self.interop_texture);
}
}
}
fn create_shared_texture(
device: *mut c_void,
width: u32,
height: u32,
misc_flags: u32,
) -> Result<*mut c_void> {
let desc = Texture2dDesc {
width,
height,
mip_levels: 1,
array_size: 1,
format: DXGI_FORMAT_BGRA8_UNORM,
sample_count: 1,
sample_quality: 0,
usage: D3D11_USAGE_DEFAULT,
bind_flags: D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE,
cpu_access_flags: 0,
misc_flags,
};
let mut texture: *mut c_void = std::ptr::null_mut();
let hr = unsafe {
(vtbl::<ID3D11DeviceVtbl>(device).create_texture_2d)(
device,
&desc,
std::ptr::null(),
&mut texture,
)
};
if hr < 0 || texture.is_null() {
return Err(hresult_error(
&format!("CreateTexture2D for a shared {width}x{height} BGRA8 render target"),
hr,
));
}
Ok(texture)
}
fn create_shared_handle(texture: *mut c_void) -> Result<Handle> {
let mut resource: *mut c_void = std::ptr::null_mut();
let hr = unsafe {
(vtbl::<IUnknownVtbl>(texture).query_interface)(
texture,
&IID_IDXGI_RESOURCE1,
&mut resource,
)
};
if hr < 0 || resource.is_null() {
return Err(hresult_error("QueryInterface(IDXGIResource1)", hr));
}
let mut handle: Handle = NULL_HANDLE;
let hr = unsafe {
(vtbl::<IDXGIResource1Vtbl>(resource).create_shared_handle)(
resource,
std::ptr::null(),
GENERIC_ALL,
std::ptr::null(),
&mut handle,
)
};
unsafe { com_release(resource) };
if hr < 0 || handle.is_null() {
return Err(hresult_error("IDXGIResource1::CreateSharedHandle", hr));
}
Ok(handle)
}
fn gl_proc_address(name: &str) -> *mut c_void {
let Ok(cname) = CString::new(name) else {
return std::ptr::null_mut();
};
let ptr = unsafe { wglGetProcAddress(cname.as_ptr()) };
if !matches!(ptr as isize, -1..=3) {
return ptr;
}
static OPENGL32: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
let module =
*OPENGL32.get_or_init(|| unsafe { GetModuleHandleA(c"opengl32.dll".as_ptr()) as usize });
if module == 0 {
return std::ptr::null_mut();
}
unsafe { GetProcAddress(module as Hmodule, cname.as_ptr()) }
}