use super::super::{CompletionFence, Egl, EglDisplayKind, TransferBackend};
use super::GlPlatform;
use crate::{Error, Result};
use edgefirst_egl as egl;
use edgefirst_tensor::{PixelFormat, Tensor};
use log::{debug, info, warn};
use std::cell::RefCell;
use std::ffi::c_void;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicPtr, Ordering};
use std::sync::OnceLock;
static LAST_ACTIVE_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
const EGL_OPENGL_ES3_BIT: i32 = 0x0040;
const EGL_PBUFFER_BIT: i32 = 0x0001;
const EGL_RENDERABLE_TYPE: i32 = 0x3040;
const EGL_SURFACE_TYPE: i32 = 0x3033;
const EGL_RED_SIZE: i32 = 0x3024;
const EGL_GREEN_SIZE: i32 = 0x3023;
const EGL_BLUE_SIZE: i32 = 0x3022;
const EGL_ALPHA_SIZE: i32 = 0x3021;
const EGL_BACK_BUFFER: i32 = 0x3084;
const EGL_PLATFORM_ANGLE_ANGLE: u32 = 0x3202;
const EGL_PLATFORM_ANGLE_TYPE_ANGLE: i32 = 0x3203;
const EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE: i32 = 0x3208;
const EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE: i32 = 0x3209;
const EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE: i32 = 0x320A;
const EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE: i32 = 0x320B;
const EGL_PLATFORM_ANGLE_D3D_LUID_HIGH_ANGLE: i32 = 0x34A0;
const EGL_PLATFORM_ANGLE_D3D_LUID_LOW_ANGLE: i32 = 0x34A1;
pub(crate) const ADAPTER_ENV: &str = "EDGEFIRST_ANGLE_ADAPTER";
static EGL_LIB: OnceLock<&'static libloading::Library> = OnceLock::new();
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetModuleHandleExW(flags: u32, module_name: *const u16, module: *mut *mut c_void) -> i32;
fn GetModuleFileNameW(module: *mut c_void, filename: *mut u16, size: u32) -> u32;
}
const GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT: u32 = 0x0000_0002;
const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS: u32 = 0x0000_0004;
pub(in super::super) struct WindowsPlatform;
impl WindowsPlatform {
pub(in super::super) fn load_egl_lib() -> Result<&'static libloading::Library> {
if let Some(lib) = EGL_LIB.get() {
return Ok(lib);
}
let lib = Self::load_egl_lib_inner()?;
let leaked: &'static libloading::Library = Box::leak(Box::new(lib));
Ok(EGL_LIB.get_or_init(|| leaked))
}
fn module_dir() -> Option<PathBuf> {
use std::os::windows::ffi::OsStringExt as _;
let anchor = Self::module_dir as *const () as *const u16;
let mut module: *mut c_void = std::ptr::null_mut();
let ok = unsafe {
GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
| GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
anchor,
&mut module,
)
};
if ok == 0 || module.is_null() {
return None;
}
let mut buf = vec![0u16; 32 * 1024];
let len =
unsafe { GetModuleFileNameW(module, buf.as_mut_ptr(), buf.len() as u32) } as usize;
if len == 0 || len >= buf.len() {
return None;
}
let path = PathBuf::from(std::ffi::OsString::from_wide(&buf[..len]));
path.parent().map(Path::to_path_buf)
}
fn candidate_dirs() -> Vec<PathBuf> {
let mut dirs = Vec::new();
if let Some(p) = std::env::var_os("EDGEFIRST_ANGLE_PATH") {
if !p.is_empty() {
dirs.push(PathBuf::from(p));
}
}
if let Some(d) = Self::module_dir() {
dirs.push(d);
}
if let Some(d) = std::env::current_exe()
.ok()
.and_then(|e| e.parent().map(Path::to_path_buf))
{
if !dirs.contains(&d) {
dirs.push(d);
}
}
dirs
}
fn load_egl_lib_inner() -> Result<libloading::Library> {
use libloading::os::windows::{
Library as WinLibrary, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR,
};
let mut tried: Vec<String> = Vec::new();
for dir in Self::candidate_dirs() {
let path = dir.join("libEGL.dll");
if !path.is_file() {
tried.push(format!("{}: not found", path.display()));
continue;
}
match unsafe {
WinLibrary::load_with_flags(
&path,
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
)
} {
Ok(lib) => {
debug!(
"WindowsPlatform: loaded ANGLE libEGL from {}",
path.display()
);
return Ok(lib.into());
}
Err(e) => {
debug!("WindowsPlatform: {} failed to load: {e}", path.display());
tried.push(format!("{}: {e}", path.display()));
}
}
}
match unsafe { libloading::Library::new("libEGL.dll") } {
Ok(lib) => {
debug!("WindowsPlatform: loaded ANGLE libEGL from the default DLL search path");
return Ok(lib);
}
Err(e) => tried.push(format!("libEGL.dll (default DLL search path): {e}")),
}
warn!(
"WindowsPlatform: ANGLE libEGL.dll not found. Fetch the Windows ANGLE \
package with `scripts/fetch-angle.sh` and set EDGEFIRST_ANGLE_PATH to \
the directory containing libEGL.dll + libGLESv2.dll, or place the two \
DLLs next to the executable / edgefirst_image.dll / _image.pyd — see \
README.md § Windows GPU Acceleration. Falling back to the CPU backend."
);
Err(Error::Io(std::io::Error::other(format!(
"ANGLE libEGL.dll not found: {}",
tried.join("; ")
))))
}
pub(in super::super) fn create_display(
egl: &Egl,
sel: &AdapterSelection,
) -> Result<egl::Display> {
type FnGetPlatformDisplayEXT = unsafe extern "C" fn(
platform: u32,
native: *mut c_void,
attribs: *const i32,
) -> egl::EGLDisplay;
let ptr = egl
.get_proc_address("eglGetPlatformDisplayEXT")
.ok_or_else(|| {
Error::Io(std::io::Error::other(
"eglGetPlatformDisplayEXT not exported by ANGLE libEGL",
))
})?;
let get_platform_display: FnGetPlatformDisplayEXT = unsafe { std::mem::transmute(ptr) };
let mut attribs = vec![
EGL_PLATFORM_ANGLE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
];
match sel {
AdapterSelection::Hardware => attribs.extend([
EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
]),
AdapterSelection::Warp => attribs.extend([
EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE,
]),
AdapterSelection::Luid { high, low } => attribs.extend([
EGL_PLATFORM_ANGLE_D3D_LUID_HIGH_ANGLE,
*high,
EGL_PLATFORM_ANGLE_D3D_LUID_LOW_ANGLE,
*low as i32,
]),
AdapterSelection::Discrete | AdapterSelection::Match(_) => attribs.extend([
EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
]),
}
attribs.push(egl::NONE);
let raw = unsafe {
get_platform_display(
EGL_PLATFORM_ANGLE_ANGLE,
std::ptr::null_mut(),
attribs.as_ptr(),
)
};
if raw.is_null() {
return Err(Error::Io(std::io::Error::other(
"eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, D3D11) returned NO_DISPLAY",
)));
}
Ok(unsafe { egl::Display::from_ptr(raw) })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum AdapterSelection {
Hardware,
Warp,
Discrete,
Luid { high: i32, low: u32 },
Match(String),
}
fn parse_int(s: &str) -> Option<i64> {
let s = s.trim();
if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
i64::from_str_radix(hex, 16).ok()
} else {
s.parse().ok()
}
}
pub(crate) fn parse_adapter_selection(raw: Option<&str>) -> AdapterSelection {
let raw = raw.map(str::trim).unwrap_or("");
match raw.to_ascii_lowercase().as_str() {
"" | "hardware" | "hw" | "default" => AdapterSelection::Hardware,
"warp" | "software" => AdapterSelection::Warp,
"discrete" | "dgpu" => AdapterSelection::Discrete,
_ => {
if let Some((h, l)) = raw.split_once(':') {
if let (Some(high), Some(low)) = (parse_int(h), parse_int(l)) {
if let (Ok(high), Ok(low)) = (i32::try_from(high), u32::try_from(low)) {
return AdapterSelection::Luid { high, low };
}
}
}
AdapterSelection::Match(raw.to_string())
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct DxgiAdapter {
pub(crate) index: u32,
pub(crate) description: String,
pub(crate) luid_high: i32,
pub(crate) luid_low: u32,
pub(crate) software: bool,
pub(crate) dedicated_video_memory: u64,
}
type ComPtr = *mut c_void;
type HResult = i32;
#[repr(C)]
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],
};
#[repr(C)]
struct Luid {
low: u32,
high: i32,
}
#[repr(C)]
struct DxgiAdapterDesc1 {
description: [u16; 128],
vendor_id: u32,
device_id: u32,
subsys_id: u32,
revision: u32,
dedicated_video_memory: usize,
dedicated_system_memory: usize,
shared_system_memory: usize,
adapter_luid: Luid,
flags: u32,
}
const DXGI_ADAPTER_FLAG_SOFTWARE: u32 = 2;
const DXGI_ERROR_NOT_FOUND: HResult = 0x887A_0002_u32 as i32;
const SLOT_RELEASE: usize = 2;
const SLOT_FACTORY1_ENUM_ADAPTERS1: usize = 12;
const SLOT_ADAPTER1_GET_DESC1: usize = 10;
type FnCreateDxgiFactory1 =
unsafe extern "system" fn(riid: *const Guid, out: *mut ComPtr) -> HResult;
type FnRelease = unsafe extern "system" fn(this: ComPtr) -> u32;
type FnEnumAdapters1 =
unsafe extern "system" fn(this: ComPtr, index: u32, out: *mut ComPtr) -> HResult;
type FnGetDesc1 = unsafe extern "system" fn(this: ComPtr, desc: *mut DxgiAdapterDesc1) -> HResult;
unsafe fn com_slot(this: ComPtr, index: usize) -> *const c_void {
unsafe {
let vtbl = *(this as *const *const *const c_void);
*vtbl.add(index)
}
}
pub(crate) fn enumerate_dxgi_adapters() -> Result<Vec<DxgiAdapter>> {
let io = |m: String| Error::Io(std::io::Error::other(m));
let dxgi = unsafe { libloading::Library::new("dxgi.dll") }
.map_err(|e| io(format!("dxgi.dll: {e}")))?;
let create: libloading::Symbol<FnCreateDxgiFactory1> =
unsafe { dxgi.get(b"CreateDXGIFactory1\0") }
.map_err(|e| io(format!("CreateDXGIFactory1: {e}")))?;
let mut factory: ComPtr = std::ptr::null_mut();
let hr = unsafe { create(&IID_IDXGI_FACTORY1, &mut factory) };
if hr < 0 || factory.is_null() {
return Err(io(format!("CreateDXGIFactory1 failed: HRESULT {hr:#010x}")));
}
let (factory_release, enum_adapters1): (FnRelease, FnEnumAdapters1) = unsafe {
(
std::mem::transmute::<*const c_void, FnRelease>(com_slot(factory, SLOT_RELEASE)),
std::mem::transmute::<*const c_void, FnEnumAdapters1>(com_slot(
factory,
SLOT_FACTORY1_ENUM_ADAPTERS1,
)),
)
};
let mut adapters = Vec::new();
let mut index = 0u32;
loop {
let mut adapter: ComPtr = std::ptr::null_mut();
let hr = unsafe { enum_adapters1(factory, index, &mut adapter) };
if hr == DXGI_ERROR_NOT_FOUND || hr < 0 || adapter.is_null() {
break;
}
let (adapter_release, get_desc1): (FnRelease, FnGetDesc1) = unsafe {
(
std::mem::transmute::<*const c_void, FnRelease>(com_slot(adapter, SLOT_RELEASE)),
std::mem::transmute::<*const c_void, FnGetDesc1>(com_slot(
adapter,
SLOT_ADAPTER1_GET_DESC1,
)),
)
};
let mut desc = std::mem::MaybeUninit::<DxgiAdapterDesc1>::zeroed();
let hr = unsafe { get_desc1(adapter, desc.as_mut_ptr()) };
if hr >= 0 {
let desc = unsafe { desc.assume_init() };
let end = desc
.description
.iter()
.position(|&c| c == 0)
.unwrap_or(desc.description.len());
adapters.push(DxgiAdapter {
index,
description: String::from_utf16_lossy(&desc.description[..end]),
luid_high: desc.adapter_luid.high,
luid_low: desc.adapter_luid.low,
software: desc.flags & DXGI_ADAPTER_FLAG_SOFTWARE != 0,
dedicated_video_memory: desc.dedicated_video_memory as u64,
});
}
unsafe { adapter_release(adapter) };
index += 1;
}
unsafe { factory_release(factory) };
Ok(adapters)
}
pub(crate) fn resolve_adapter(sel: AdapterSelection, adapters: &[DxgiAdapter]) -> AdapterSelection {
let luid = |a: &DxgiAdapter| AdapterSelection::Luid {
high: a.luid_high,
low: a.luid_low,
};
match sel {
AdapterSelection::Discrete => match adapters
.iter()
.filter(|a| !a.software)
.max_by_key(|a| a.dedicated_video_memory)
{
Some(a) => luid(a),
None => {
warn!("{ADAPTER_ENV}=discrete: no hardware adapter enumerated; using ANGLE's default adapter");
AdapterSelection::Hardware
}
},
AdapterSelection::Match(needle) => {
let n = needle.to_ascii_lowercase();
match adapters
.iter()
.find(|a| a.description.to_ascii_lowercase().contains(&n))
{
Some(a) => luid(a),
None => {
warn!(
"{ADAPTER_ENV}={needle:?} matches no DXGI adapter description \
({:?}); using ANGLE's default adapter",
adapters
.iter()
.map(|a| a.description.as_str())
.collect::<Vec<_>>()
);
AdapterSelection::Hardware
}
}
}
other => other,
}
}
fn select_adapter() -> (AdapterSelection, String) {
let raw = std::env::var(ADAPTER_ENV).ok();
let sel = parse_adapter_selection(raw.as_deref());
let adapters = match enumerate_dxgi_adapters() {
Ok(a) => a,
Err(e) => {
debug!("DXGI adapter enumeration unavailable ({e}); using ANGLE's default adapter");
Vec::new()
}
};
for a in &adapters {
debug!(
"dxgi adapter #{}: {:?} luid={:#x}:{:#x} vram={} MiB software={}",
a.index,
a.description,
a.luid_high,
a.luid_low,
a.dedicated_video_memory >> 20,
a.software
);
}
let resolved = resolve_adapter(sel, &adapters);
let name = match &resolved {
AdapterSelection::Warp => "WARP (Microsoft Basic Render Driver)".to_string(),
AdapterSelection::Luid { high, low } => adapters
.iter()
.find(|a| a.luid_high == *high && a.luid_low == *low)
.map(|a| a.description.clone())
.unwrap_or_else(|| format!("LUID {high:#x}:{low:#x}")),
_ => adapters
.iter()
.find(|a| !a.software)
.map(|a| format!("{} (ANGLE default)", a.description))
.unwrap_or_else(|| "ANGLE default adapter".to_string()),
};
if matches!(resolved, AdapterSelection::Hardware)
&& !adapters.is_empty()
&& adapters.iter().all(|a| a.software)
{
warn!(
"no hardware D3D11 adapter is enumerated (only {:?}); ANGLE will run on \
the software renderer, which the GL backend rejects unless \
EDGEFIRST_ALLOW_SOFTWARE_GL=1",
adapters
.iter()
.map(|a| a.description.as_str())
.collect::<Vec<_>>()
);
}
info!(
"ANGLE D3D11 adapter: {name} ({ADAPTER_ENV}={})",
raw.as_deref().unwrap_or("unset")
);
(resolved, name)
}
static GL_LOADED: OnceLock<()> = OnceLock::new();
fn load_gl_once(egl: &Egl) {
GL_LOADED.get_or_init(|| {
edgefirst_gl::load_with(|name| match egl.get_proc_address(name) {
Some(ptr) => ptr as *const c_void,
None => std::ptr::null(),
});
});
}
fn create_es_context(
egl: &Egl,
display: egl::Display,
config: egl::Config,
try_31: bool,
) -> Result<(egl::Context, bool)> {
if try_31 {
let attribs_31 = [
egl::CONTEXT_MAJOR_VERSION,
3,
egl::CONTEXT_MINOR_VERSION,
1,
egl::NONE,
];
if let Ok(ctx) = egl.create_context(display, config, None, &attribs_31) {
return Ok((ctx, true));
}
debug!("GLES 3.1 context unavailable on this D3D11 device; falling back to 3.0");
}
let attribs_30 = [egl::CONTEXT_MAJOR_VERSION, 3, egl::NONE];
let ctx = egl
.create_context(display, config, None, &attribs_30)
.map_err(|e| Error::Io(std::io::Error::other(format!("eglCreateContext: {e:?}"))))?;
Ok((ctx, false))
}
pub(in crate::opengl_headless) struct SharedD3d11Display {
pub(in crate::opengl_headless) egl: Egl,
pub(in crate::opengl_headless) display: egl::Display,
pub(in crate::opengl_headless) config: egl::Config,
probe_context: egl::Context,
probe_pbuffer: egl::Surface,
pub(in crate::opengl_headless) has_compute: bool,
pub(in crate::opengl_headless) supports_f32_color: bool,
pub(in crate::opengl_headless) supports_f16_color: bool,
pub(in crate::opengl_headless) adapter: String,
}
unsafe impl Send for SharedD3d11Display {}
unsafe impl Sync for SharedD3d11Display {}
static SHARED_DISPLAY: OnceLock<std::result::Result<SharedD3d11Display, String>> = OnceLock::new();
pub(in crate::opengl_headless) fn shared_display() -> Result<&'static SharedD3d11Display> {
SHARED_DISPLAY
.get_or_init(|| init_shared_display().map_err(|e| e.to_string()))
.as_ref()
.map_err(|s| Error::Io(std::io::Error::other(s.clone())))
}
fn gl_string(name: u32) -> String {
unsafe {
let ptr = edgefirst_gl::gl::GetString(name);
if ptr.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(ptr as *const std::os::raw::c_char)
.to_string_lossy()
.into_owned()
}
}
}
fn init_shared_display() -> Result<SharedD3d11Display> {
let _span =
tracing::info_span!("image.gl_init", platform = "windows", backend = "pbo").entered();
let (selection, adapter) = select_adapter();
let egl_lib = WindowsPlatform::load_egl_lib()
.map_err(|e| Error::Io(std::io::Error::other(format!("ANGLE libEGL: {e}"))))?;
let egl: Egl = unsafe {
edgefirst_egl::Instance::<
edgefirst_egl::Dynamic<&'static libloading::Library, edgefirst_egl::EGL1_4>,
>::load_required_from(egl_lib)
}
.map_err(|e| Error::Io(std::io::Error::other(format!("EGL load: {e:?}"))))?;
debug!("EGL dynamic instance loaded, version = {:?}", egl.version());
let display = WindowsPlatform::create_display(&egl, &selection)?;
let (maj, min) = egl
.initialize(display)
.map_err(|e| Error::Io(std::io::Error::other(format!("eglInitialize: {e:?}"))))?;
let egl_version = egl
.query_string(Some(display), egl::VERSION)
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
debug!("ANGLE EGL {maj}.{min} initialised (process-global shared display): {egl_version}");
egl.bind_api(egl::OPENGL_ES_API)
.map_err(|e| Error::Io(std::io::Error::other(format!("eglBindAPI: {e:?}"))))?;
let cfg_attribs = [
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES3_BIT,
EGL_SURFACE_TYPE,
EGL_PBUFFER_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
egl::NONE,
];
let config = egl
.choose_first_config(display, &cfg_attribs)
.map_err(|e| Error::Io(std::io::Error::other(format!("eglChooseConfig: {e:?}"))))?
.ok_or_else(|| {
Error::NotSupported("no EGL config with GLES3 + PBUFFER on the D3D11 display".into())
})?;
let (probe_context, has_compute) = create_es_context(&egl, display, config, true)?;
let dummy_attribs = [egl::WIDTH, 16, egl::HEIGHT, 16, egl::NONE];
let probe_pbuffer = egl
.create_pbuffer_surface(display, config, &dummy_attribs)
.map_err(|e| {
let _ = egl.destroy_context(display, probe_context);
Error::Io(std::io::Error::other(format!(
"eglCreatePbufferSurface(probe): {e:?}"
)))
})?;
if let Err(e) = egl.make_current(
display,
Some(probe_pbuffer),
Some(probe_pbuffer),
Some(probe_context),
) {
let _ = egl.destroy_surface(display, probe_pbuffer);
let _ = egl.destroy_context(display, probe_context);
return Err(Error::Io(std::io::Error::other(format!(
"eglMakeCurrent(probe): {e:?}"
))));
}
load_gl_once(&egl);
let extensions = gl_string(edgefirst_gl::gl::EXTENSIONS);
let supports_f32_color = extensions
.split_ascii_whitespace()
.any(|e| e == "GL_EXT_color_buffer_float");
let supports_f16_color = extensions
.split_ascii_whitespace()
.any(|e| e == "GL_EXT_color_buffer_half_float");
let gl_version = gl_string(edgefirst_gl::gl::VERSION);
let gl_renderer = gl_string(edgefirst_gl::gl::RENDERER);
info!(
"ANGLE D3D11 display ready: {gl_renderer} — {gl_version} \
(compute={has_compute}, f32_color={supports_f32_color}, f16_color={supports_f16_color})"
);
let _ = egl.make_current(display, None, None, None);
Ok(SharedD3d11Display {
egl,
display,
config,
probe_context,
probe_pbuffer,
has_compute,
supports_f32_color,
supports_f16_color,
adapter,
})
}
pub(in crate::opengl_headless) struct D3d11Display {
pub(in crate::opengl_headless) shared: &'static SharedD3d11Display,
context: egl::Context,
dummy_pbuffer: egl::Surface,
pub(in crate::opengl_headless) transfer_backend: TransferBackend,
pub(in crate::opengl_headless) has_compute: bool,
active_binds: RefCell<Vec<egl::Surface>>,
}
impl Drop for D3d11Display {
fn drop(&mut self) {
let d = self.shared;
let ctx = self.context.as_ptr();
let _ = LAST_ACTIVE_CONTEXT.compare_exchange(
ctx,
std::ptr::null_mut(),
Ordering::AcqRel,
Ordering::Relaxed,
);
let _ = d.egl.make_current(d.display, None, None, None);
let _ = d.egl.destroy_surface(d.display, self.dummy_pbuffer);
let _ = d.egl.destroy_context(d.display, self.context);
}
}
pub(in crate::opengl_headless) struct D3dTexturePbuffer {
shared: &'static SharedD3d11Display,
pub(in crate::opengl_headless) surface: egl::Surface,
}
impl Drop for D3dTexturePbuffer {
fn drop(&mut self) {
let _ = self
.shared
.egl
.destroy_surface(self.shared.display, self.surface);
}
}
pub(crate) struct AngleD3d11;
impl GlPlatform for AngleD3d11 {
type Display = D3d11Display;
type Import = D3dTexturePbuffer;
type ImportHandle = egl::Surface;
const PERSISTENT_TEX_BINDINGS: bool = false;
const EXTERNAL_OES: bool = false;
fn load_gl_once(_display: &D3d11Display) {
}
fn init_display(kind: Option<EglDisplayKind>) -> Result<D3d11Display> {
if let Some(kind) = kind {
debug!("EglDisplayKind::{kind} ignored on Windows — ANGLE/D3D11 is the only display");
}
let shared = shared_display()?;
let (context, has_compute) = create_es_context(
&shared.egl,
shared.display,
shared.config,
shared.has_compute,
)?;
let dummy_attribs = [egl::WIDTH, 16, egl::HEIGHT, 16, egl::NONE];
let dummy_pbuffer = shared
.egl
.create_pbuffer_surface(shared.display, shared.config, &dummy_attribs)
.map_err(|e| {
let _ = shared.egl.destroy_context(shared.display, context);
Error::Io(std::io::Error::other(format!(
"eglCreatePbufferSurface (per-processor dummy): {e:?}"
)))
})?;
if let Err(e) = shared.egl.make_current(
shared.display,
Some(dummy_pbuffer),
Some(dummy_pbuffer),
Some(context),
) {
let _ = shared.egl.destroy_surface(shared.display, dummy_pbuffer);
let _ = shared.egl.destroy_context(shared.display, context);
return Err(Error::Io(std::io::Error::other(format!(
"eglMakeCurrent (per-processor): {e:?}"
))));
}
debug!(
"Windows GL context up on {} (GLES {}, transfer=Pbo)",
shared.adapter,
if has_compute { "3.1" } else { "3.0" }
);
Ok(D3d11Display {
shared,
context,
dummy_pbuffer,
transfer_backend: TransferBackend::Pbo,
has_compute,
active_binds: RefCell::new(Vec::new()),
})
}
fn import_buffer(
_display: &D3d11Display,
_img: &Tensor<u8>,
_fmt: PixelFormat,
_for_dst: bool,
) -> Result<D3dTexturePbuffer> {
Err(Error::NotSupported(
"Windows GL backend has no zero-copy buffer import yet (D3D11 shared-texture \
import is a follow-on); sources are Mem tensors and destinations PBO tensors"
.into(),
))
}
fn import_buffer_nv_r8(
_display: &D3d11Display,
_img: &Tensor<u8>,
_fmt: PixelFormat,
) -> Result<D3dTexturePbuffer> {
Err(Error::NotSupported(
"Windows GL backend has no zero-copy NV import yet (D3D11 shared-texture import is a follow-on)".into(),
))
}
fn import_buffer_packed<T>(
_display: &D3d11Display,
_img: &Tensor<T>,
_width: usize,
_height: usize,
_fmt: super::PackedImportFormat,
) -> Result<D3dTexturePbuffer>
where
T: num_traits::Num + Clone + std::fmt::Debug + Send + Sync + edgefirst_tensor::Element,
{
Err(Error::NotSupported(
"Windows GL backend has no zero-copy packed import yet (D3D11 shared-texture import is a follow-on)".into(),
))
}
fn import_handle(import: &D3dTexturePbuffer) -> egl::Surface {
import.surface
}
unsafe fn attach_tex_image_2d(display: &D3d11Display, handle: egl::Surface) -> Result<()> {
display
.shared
.egl
.bind_tex_image(display.shared.display, handle, EGL_BACK_BUFFER)
.map_err(|e| Error::Io(std::io::Error::other(format!("eglBindTexImage: {e:?}"))))?;
display.active_binds.borrow_mut().push(handle);
Ok(())
}
unsafe fn attach_tex_image_external(
_display: &D3d11Display,
_handle: egl::Surface,
) -> Result<()> {
Err(Error::NotSupported(
"GL_TEXTURE_EXTERNAL_OES is not available on ANGLE/D3D11".into(),
))
}
unsafe fn attach_renderbuffer_storage(
_display: &D3d11Display,
_handle: egl::Surface,
) -> Result<()> {
Err(Error::NotSupported(
"renderbuffer import targets are not available on ANGLE/D3D11 \
(EDGEFIRST_OPENGL_RENDERSURFACE has no effect on Windows)"
.into(),
))
}
fn begin_gpu_pass(display: &D3d11Display) {
let ctx = display.context.as_ptr();
if LAST_ACTIVE_CONTEXT.swap(ctx, Ordering::AcqRel) == ctx {
return;
}
let d = display.shared;
let _ = d.egl.make_current(d.display, None, None, None);
if let Err(e) = d.egl.make_current(
d.display,
Some(display.dummy_pbuffer),
Some(display.dummy_pbuffer),
Some(display.context),
) {
warn!("eglMakeCurrent (per-message context re-sync) failed: {e:?}");
}
}
fn end_gpu_pass(display: &D3d11Display) {
for surface in display.active_binds.borrow_mut().drain(..) {
let _ = display.shared.egl.release_tex_image(
display.shared.display,
surface,
EGL_BACK_BUFFER,
);
}
}
fn native_fence_sync(_display: &D3d11Display) -> bool {
false
}
fn export_completion_fence(_display: &D3d11Display) -> Result<Option<CompletionFence>> {
Ok(None)
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn adapter_selection_parses_env_values() {
assert_eq!(parse_adapter_selection(None), AdapterSelection::Hardware);
assert_eq!(
parse_adapter_selection(Some("")),
AdapterSelection::Hardware
);
assert_eq!(
parse_adapter_selection(Some(" Hardware ")),
AdapterSelection::Hardware
);
assert_eq!(
parse_adapter_selection(Some("WARP")),
AdapterSelection::Warp
);
assert_eq!(
parse_adapter_selection(Some("discrete")),
AdapterSelection::Discrete
);
assert_eq!(
parse_adapter_selection(Some("0x1234:0xabcd")),
AdapterSelection::Luid {
high: 0x1234,
low: 0xabcd
}
);
assert_eq!(
parse_adapter_selection(Some("0:74901")),
AdapterSelection::Luid {
high: 0,
low: 74901
}
);
assert_eq!(
parse_adapter_selection(Some("RTX 3070")),
AdapterSelection::Match("RTX 3070".into())
);
assert_eq!(
parse_adapter_selection(Some("Intel: Arc")),
AdapterSelection::Match("Intel: Arc".into())
);
}
#[test]
fn resolve_adapter_prefers_largest_hardware_adapter_and_matches_substrings() {
let adapters = vec![
DxgiAdapter {
index: 0,
description: "Intel(R) UHD Graphics 630".into(),
luid_high: 0,
luid_low: 1,
software: false,
dedicated_video_memory: 128 << 20,
},
DxgiAdapter {
index: 1,
description: "NVIDIA GeForce RTX 3070".into(),
luid_high: 0,
luid_low: 2,
software: false,
dedicated_video_memory: 8 << 30,
},
DxgiAdapter {
index: 2,
description: "Microsoft Basic Render Driver".into(),
luid_high: 0,
luid_low: 3,
software: true,
dedicated_video_memory: 0,
},
];
assert_eq!(
resolve_adapter(AdapterSelection::Discrete, &adapters),
AdapterSelection::Luid { high: 0, low: 2 }
);
assert_eq!(
resolve_adapter(AdapterSelection::Match("intel".into()), &adapters),
AdapterSelection::Luid { high: 0, low: 1 }
);
assert_eq!(
resolve_adapter(AdapterSelection::Match("no such gpu".into()), &adapters),
AdapterSelection::Hardware
);
assert_eq!(
resolve_adapter(AdapterSelection::Warp, &adapters),
AdapterSelection::Warp
);
assert_eq!(
resolve_adapter(AdapterSelection::Discrete, &adapters[2..]),
AdapterSelection::Hardware
);
}
#[test]
fn dxgi_enumeration_lists_adapters_or_skips() {
match enumerate_dxgi_adapters() {
Ok(adapters) => {
assert!(!adapters.is_empty(), "DXGI enumerated zero adapters");
for a in &adapters {
assert!(!a.description.is_empty());
}
}
Err(e) => eprintln!("DXGI enumeration unavailable — skipping: {e}"),
}
}
#[test]
fn load_egl_lib_finds_angle_or_skips() {
let Some(dir) = std::env::var_os("EDGEFIRST_ANGLE_PATH") else {
eprintln!("EDGEFIRST_ANGLE_PATH unset — skipping ANGLE load probe");
return;
};
if !Path::new(&dir).join("libEGL.dll").is_file() {
eprintln!("no libEGL.dll under EDGEFIRST_ANGLE_PATH — skipping ANGLE load probe");
return;
}
WindowsPlatform::load_egl_lib()
.expect("ANGLE libEGL.dll should load from EDGEFIRST_ANGLE_PATH");
}
#[test]
#[ignore = "needs ANGLE + a D3D11 device; run on demand"]
fn per_processor_context_bring_up_latency() {
use std::time::Instant;
let t0 = Instant::now();
let first = AngleD3d11::init_display(None).expect("first display");
let first_ms = t0.elapsed().as_secs_f64() * 1e3;
drop(first);
let handles: Vec<_> = (0..4)
.map(|i| {
std::thread::spawn(move || {
let t0 = Instant::now();
let d = AngleD3d11::init_display(None).expect("display");
let ms = t0.elapsed().as_secs_f64() * 1e3;
drop(d);
(i, ms)
})
})
.collect();
for h in handles {
let (i, ms) = h.join().expect("thread");
println!("per-processor context {i}: {ms:.1} ms");
assert!(
ms < 50.0,
"context bring-up {ms:.1} ms exceeds 50 ms budget"
);
}
println!("first (incl. shared display init): {first_ms:.1} ms");
}
}