native/desktop-win32/src/win32/renderer_angle.rs (206 lines of code) (raw):

#![allow(non_upper_case_globals)] use std::{ffi::OsString, os::windows::ffi::OsStringExt, path::PathBuf, sync::atomic::AtomicUsize}; use anyhow::Context; use khronos_egl as egl; use windows::{ UI::Composition::SpriteVisual, Win32::{ Foundation::ERROR_PATH_NOT_FOUND, Graphics::{ Direct3D11::ID3D11Device, Dwm::DwmFlush, Dxgi::{IDXGIDevice1, IDXGIOutput}, Gdi::{MONITOR_DEFAULTTONULL, MonitorFromWindow}, }, System::LibraryLoader::GetModuleFileNameW, }, core::{Error as WinError, Interface}, }; use windows_numerics::Vector2; use super::{ renderer_api::EglSurfaceData, renderer_egl_utils::{ EGL_DEVICE_EXT, EGLDeviceEXT, EGLOk, EglInstance, GR_GL_FRAMEBUFFER_BINDING, GrGLFunctions, PostSubBufferNVFn, QueryDeviceAttribEXTFn, QueryDisplayAttribEXTFn, load_egl_function, }, window::Window, }; /// cbindgen:ignore const EGL_PLATFORM_ANGLE_ANGLE: egl::Enum = 0x3202; /// cbindgen:ignore const EGL_PLATFORM_ANGLE_TYPE_ANGLE: egl::Attrib = 0x3203; /// cbindgen:ignore const EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE: egl::Attrib = 0x3208; /// cbindgen:ignore const EGL_D3D11_DEVICE_ANGLE: egl::Int = 0x33A1; /// cbindgen:ignore const EGL_EXPERIMENTAL_PRESENT_PATH_ANGLE: egl::Attrib = 0x33A4; /// cbindgen:ignore const EGL_EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE: egl::Attrib = 0x33A9; /// cbindgen:ignore const EGL_POST_SUB_BUFFER_SUPPORTED_NV: egl::Int = 0x30BE; /// cbindgen:ignore const EGL_D3D11_ONLY_DISPLAY_ANGLE: egl::NativeDisplayType = -3isize as egl::NativeDisplayType; pub struct AngleDevice { egl_instance: EglInstance, display: egl::Display, output: IDXGIOutput, context: egl::Context, visual: SpriteVisual, surface: egl::Surface, functions: GrGLFunctions, } impl AngleDevice { pub fn create_for_window(window: &Window) -> anyhow::Result<Self> { let egl_instance = load_angle_egl_instance()?; let display = { #[rustfmt::skip] let display_attribs = [ EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, EGL_EXPERIMENTAL_PRESENT_PATH_ANGLE, EGL_EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE, egl::ATTRIB_NONE, egl::ATTRIB_NONE, ]; unsafe { egl_instance.get_platform_display(EGL_PLATFORM_ANGLE_ANGLE, EGL_D3D11_ONLY_DISPLAY_ANGLE, &display_attribs) }? }; let (_major, _minor) = egl_instance.initialize(display)?; let device = query_d3d11device_from_angle(&egl_instance, display)?; unsafe { device.SetMaximumFrameLatency(1)? }; let adapter = unsafe { device.GetAdapter()? }; let monitor = unsafe { MonitorFromWindow(window.hwnd(), MONITOR_DEFAULTTONULL) }; let mut i = 0; let output = loop { let output = unsafe { adapter.EnumOutputs(i)? }; let desc = unsafe { output.GetDesc()? }; if desc.Monitor == monitor { break output; } i += 1; }; let surface_config = { #[rustfmt::skip] let config_attribs = [ egl::RENDERABLE_TYPE, egl::OPENGL_ES2_BIT, egl::RED_SIZE, 8, egl::GREEN_SIZE, 8, egl::BLUE_SIZE, 8, egl::ALPHA_SIZE, 8, egl::NONE, egl::NONE, ]; let mut configs = Vec::with_capacity(1); egl_instance.choose_config(display, &config_attribs, &mut configs)?; configs.pop().context("No configs were found.")? }; let context = { #[rustfmt::skip] let context_attribs = [ egl::CONTEXT_MAJOR_VERSION, 2, egl::CONTEXT_MINOR_VERSION, 0, egl::NONE, egl::NONE, ]; egl_instance.create_context(display, surface_config, None, &context_attribs)? }; let visual = window.add_visual()?; let surface = unsafe { #[rustfmt::skip] let surface_attribs = [ EGL_POST_SUB_BUFFER_SUPPORTED_NV, egl::TRUE as _, egl::NONE, egl::NONE ]; egl_instance.create_window_surface(display, surface_config, visual.as_raw(), Some(&surface_attribs)) }?; let functions = GrGLFunctions::init(&egl_instance)?; Ok(Self { egl_instance, display, output, context, visual, surface, functions, }) } pub fn resize_surface(&mut self, width: egl::Int, height: egl::Int) -> anyhow::Result<EglSurfaceData> { #[allow(clippy::cast_precision_loss)] self.visual.SetSize(Vector2 { X: width as f32, Y: height as f32, })?; unsafe { DwmFlush()? }; self.egl_instance.swap_interval(self.display, 0)?; post_sub_buffer(&self.egl_instance, self.display, self.surface, 1, 1, width, height)?; unsafe { self.output.WaitForVBlank()? }; let mut framebuffer_binding = 0; unsafe { (self.functions.fGetIntegerv)(GR_GL_FRAMEBUFFER_BINDING, &raw mut framebuffer_binding) }; Ok(EglSurfaceData { framebuffer_binding }) } pub fn make_current(&self) -> anyhow::Result<()> { self.egl_instance .make_current(self.display, Some(self.surface), Some(self.surface), Some(self.context))?; Ok(()) } #[allow(clippy::bool_to_int_with_if)] pub fn swap_buffers(&self, wait_for_vsync: bool) -> anyhow::Result<()> { self.egl_instance.swap_interval(self.display, if wait_for_vsync { 1 } else { 0 })?; self.egl_instance.swap_buffers(self.display, self.surface)?; Ok(()) } #[inline] #[must_use] pub fn get_proc_address(&self, procname: &str) -> Option<extern "system" fn()> { self.egl_instance.get_proc_address(procname) } } impl Drop for AngleDevice { fn drop(&mut self) { let _ = self.egl_instance.make_current(self.display, None, None, None); if self.context.as_ptr() != egl::NO_CONTEXT { let _ = self.egl_instance.destroy_context(self.display, self.context); } if self.surface.as_ptr() != egl::NO_SURFACE { let _ = self.egl_instance.destroy_surface(self.display, self.surface); } if self.display.as_ptr() != egl::NO_DISPLAY { let _ = self.egl_instance.terminate(self.display); } } } fn post_sub_buffer( egl_instance: &EglInstance, display: egl::Display, surface: egl::Surface, x: egl::Int, y: egl::Int, width: egl::Int, height: egl::Int, ) -> anyhow::Result<()> { static POST_SUB_BUFFER_FN: AtomicUsize = AtomicUsize::new(0usize); let post_sub_buffer_fn: PostSubBufferNVFn = unsafe { load_egl_function(&POST_SUB_BUFFER_FN, egl_instance, "eglPostSubBufferNV")? }; unsafe { post_sub_buffer_fn(display.as_ptr(), surface.as_ptr(), x, y, width, height) } .ok(egl_instance) .context("Could not post sub buffer.") } fn query_d3d11device_from_angle(egl_instance: &EglInstance, display: egl::Display) -> anyhow::Result<IDXGIDevice1> { static QUERY_DISPLAY_ATTRIB_FN: AtomicUsize = AtomicUsize::new(0usize); static QUERY_DEVICE_ATTRIB_FN: AtomicUsize = AtomicUsize::new(0usize); let query_display_attrib_fun: QueryDisplayAttribEXTFn = unsafe { load_egl_function(&QUERY_DISPLAY_ATTRIB_FN, egl_instance, "eglQueryDisplayAttribEXT")? }; let query_device_attrib_fn: QueryDeviceAttribEXTFn = unsafe { load_egl_function(&QUERY_DEVICE_ATTRIB_FN, egl_instance, "eglQueryDeviceAttribEXT")? }; let mut device = 0; unsafe { query_display_attrib_fun(display.as_ptr(), EGL_DEVICE_EXT, &raw mut device) } .ok(egl_instance) .context("failed to query device from ANGLE display")?; let mut d3d11_device_raw = 0; unsafe { query_device_attrib_fn(device as EGLDeviceEXT, EGL_D3D11_DEVICE_ANGLE, &raw mut d3d11_device_raw) } .ok(egl_instance) .context("failed to query ID3D11Device from ANGLE device")?; let d3d11_device = unsafe { ID3D11Device::from_raw(d3d11_device_raw as _) }; d3d11_device .cast() .context("failed to query interface from ID3D11Device to IDXGIDevice1") } fn load_angle_egl_instance() -> anyhow::Result<EglInstance> { let current_module_path: PathBuf = unsafe { let hmodule = crate::get_dll_instance().into(); let mut filename = vec![0u16; 1024]; match GetModuleFileNameW(Some(hmodule), &mut filename) { 0 => Err(WinError::from_thread()), len => Ok(OsString::from_wide(&filename[..len as _]).into()), }? }; let current_directory = current_module_path.parent().ok_or_else(|| WinError::from(ERROR_PATH_NOT_FOUND))?; let egl_instance = unsafe { EglInstance::load_required_from_filename(current_directory.join("libEGL.dll")) }?; Ok(egl_instance) }