use std::cmp::max;
use std::ffi::CString;
use std::mem::{size_of, swap};
use std::pin::Pin;
use std::ptr::{null, null_mut};
use std::sync::mpsc::{channel, Receiver, TryRecvError};
use std::task::{Context, Poll, Waker};
use std::thread::{JoinHandle, sleep, spawn};
use std::time::Duration;
use futures::Stream;
use log::{error, trace};
use windows::core::{PCSTR, Result as WinResult};
use windows::Win32::Graphics::Dxgi::{DXGI_MODE_DESC1, DXGI_OUTPUT_DESC1, IDXGIOutput6};
use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT, DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R8G8B8A8_UNORM};
use windows::Win32::Graphics::Gdi::{CDS_TYPE, ChangeDisplaySettingsExA, DEVMODE_DISPLAY_ORIENTATION, DEVMODEA, DISP_CHANGE_SUCCESSFUL, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_DISPLAYORIENTATION, DM_PELSHEIGHT, DM_PELSWIDTH, ENUM_CURRENT_SETTINGS, ENUM_DISPLAY_SETTINGS_FLAGS, EnumDisplaySettingsExA};
use crate::errors::DDApiError;
use crate::utils::convert_u16_to_string;
#[cfg(test)]
mod test {
use std::sync::Arc;
use std::sync::atomic::{AtomicI32, Ordering};
use std::thread::sleep;
use std::time::Duration;
use futures::StreamExt;
use tokio::runtime::Builder;
use tokio::time;
use crate::devices::AdapterFactory;
use crate::outputs::{DisplayMode, DisplayOrientation};
#[test]
fn test_display_names() {
for adapter in AdapterFactory::new() {
println!("{}", adapter.name());
for display in adapter.iter_displays() {
println!("\t{}", display.name())
}
}
}
#[test]
fn test_display_modes() {
for display in AdapterFactory::new().get_adapter_by_idx(0).unwrap().iter_displays() {
println!("{}", display.name());
println!("{:?}", display.get_display_modes().unwrap());
}
}
#[test]
fn test_display_setting_change() {
let disp = AdapterFactory::new().get_adapter_by_idx(0).unwrap().get_display_by_idx(0).unwrap();
let curr_settings = disp.get_current_display_mode().unwrap();
let mode = DisplayMode {
width: 1920,
height: 1080,
orientation: DisplayOrientation::Rotate180,
refresh_num: 60,
refresh_den: 1,
hdr: false,
};
disp.set_display_mode(&mode).unwrap();
sleep(Duration::from_secs(5));
disp.set_display_mode(&curr_settings).unwrap();
println!("{:?}", curr_settings);
}
#[test]
fn test_get_display_mode() {
let disp = AdapterFactory::new().get_adapter_by_idx(0).unwrap().get_display_by_idx(0).unwrap();
let curr_settings = disp.get_current_display_mode().unwrap();
println!("{:?}", curr_settings);
}
#[test]
fn test_display_sync_stream() {
let disp = AdapterFactory::new().get_adapter_by_idx(0).unwrap().get_display_by_idx(0).unwrap();
let rt = Builder::new_current_thread().enable_time().build().unwrap();
let counter = Arc::new(AtomicI32::new(0));
let counter2 = counter.clone();
rt.spawn(async move {
let disp = disp;
let counter = counter2;
let mut s = disp.get_vsync_stream();
while let Some(Ok(())) = s.next().await {
let _ = counter.fetch_add(1, Ordering::Release);
}
});
let counter2 = counter.clone();
rt.block_on(async move {
let counter = counter2;
let total = 5;
let mut interval = time::interval(Duration::from_secs(1));
interval.tick().await;
for _ in 0..total {
interval.tick().await;
let read_refresh = counter.load(Ordering::Acquire);
println!("{}", read_refresh);
counter.store(0, Ordering::Release);
}
});
}
}
#[repr(transparent)]
#[derive(Clone)]
pub struct Display(IDXGIOutput6);
impl Display {
pub fn new(output: IDXGIOutput6) -> Self {
Self(output)
}
pub fn name(&self) -> String {
let mut desc: DXGI_OUTPUT_DESC1 = Default::default();
unsafe { self.0.GetDesc1(&mut desc).unwrap() };
convert_u16_to_string(&desc.DeviceName)
}
pub fn get_display_modes(&self) -> Result<Vec<DisplayMode>, DDApiError> {
let mut out = Vec::new();
self.fill_modes(DXGI_FORMAT_R8G8B8A8_UNORM, false, &mut out)?;
self.fill_modes(DXGI_FORMAT_R16G16B16A16_FLOAT, true, &mut out)?;
Ok(out)
}
pub fn set_display_mode(&self, mode: &DisplayMode) -> Result<(), DDApiError> {
let name = self.name();
let name = CString::new(name).unwrap();
let mut display_mode = DEVMODEA {
..Default::default()
};
display_mode.dmSize = size_of::<DEVMODEA>() as _;
match mode.orientation {
DisplayOrientation::NoRotation | DisplayOrientation::Rotate180 => {
display_mode.dmPelsHeight = mode.height;
display_mode.dmPelsWidth = mode.width;
}
DisplayOrientation::Rotate90 | DisplayOrientation::Rotate270 => {
display_mode.dmPelsHeight = mode.width;
display_mode.dmPelsWidth = mode.height;
}
}
display_mode.dmBitsPerPel = if mode.hdr { 64 } else { 32 };
display_mode.dmDisplayFrequency = mode.refresh_num / mode.refresh_den;
unsafe {
display_mode.Anonymous1.Anonymous2.dmDisplayOrientation = mode.orientation.into();
}
display_mode.dmFields |= DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_BITSPERPEL | DM_DISPLAYORIENTATION;
let resp = unsafe { ChangeDisplaySettingsExA(PCSTR(name.as_ptr() as _), Some(&display_mode), None, CDS_TYPE(0), None) };
if resp != DISP_CHANGE_SUCCESSFUL {
Err(DDApiError::BadParam(format!("failed to change display settings. DISP_CHANGE={}", resp.0)))
} else {
Ok(())
}
}
pub fn get_current_display_mode(&self) -> Result<DisplayMode, DDApiError> {
let name = self.name();
let name = CString::new(name).unwrap();
let mut mode: DEVMODEA = DEVMODEA {
dmSize: size_of::<DEVMODEA>() as _,
dmDriverExtra: 0,
..Default::default()
};
let success = unsafe { EnumDisplaySettingsExA(PCSTR(name.as_c_str().as_ptr() as _), ENUM_CURRENT_SETTINGS, &mut mode, ENUM_DISPLAY_SETTINGS_FLAGS(0)) };
if !success.as_bool() {
Err(DDApiError::Unexpected("Failed to retrieve display settings for output".to_string()))
} else {
let mut dm = DisplayMode {
width: mode.dmPelsWidth,
height: mode.dmPelsHeight,
orientation: unsafe { mode.Anonymous1.Anonymous2.dmDisplayOrientation }.into(),
refresh_num: mode.dmDisplayFrequency,
refresh_den: 1,
hdr: mode.dmBitsPerPel != 32,
};
if matches!(dm.orientation,DisplayOrientation::Rotate90|DisplayOrientation::Rotate270) {
dm.height = mode.dmPelsWidth;
dm.width = mode.dmPelsHeight;
}
Ok(dm)
}
}
pub fn get_vsync_stream(&self) -> DisplayVSyncStream {
DisplayVSyncStream::new(self.clone())
}
pub fn wait_for_vsync(&self) -> Result<(), DDApiError> {
let err = unsafe { self.0.WaitForVBlank() };
if err.is_err() {
return Err(DDApiError::Unexpected(format!("DisplaySyncStream received a sync error. Maybe monitor disconnected? {:?}", err)));
} else {
Ok(())
}
}
pub fn as_raw_ref(&self) -> &IDXGIOutput6 {
&self.0
}
fn fill_modes(&self, format: DXGI_FORMAT, hdr: bool, mode_list: &mut Vec<DisplayMode>) -> Result<(), DDApiError> {
let mut num_modes: u32 = 0;
if let Err(e) = unsafe { self.0.GetDisplayModeList1(format, 0, &mut num_modes, None) } {
return Err(DDApiError::Unexpected(format!("{:?}", e)));
}
let mut modes: Vec<DXGI_MODE_DESC1> = Vec::with_capacity(num_modes as _);
if let Err(e) = unsafe { self.0.GetDisplayModeList1(format, 0, &mut num_modes, Some(modes.as_mut_ptr())) } {
return Err(DDApiError::Unexpected(format!("{:?}", e)));
}
unsafe { modes.set_len(num_modes as _) };
let reserve = max(0, num_modes as usize - mode_list.capacity() + mode_list.len());
mode_list.reserve(reserve);
for mode in modes.iter() {
mode_list.push(DisplayMode {
width: mode.Width,
height: mode.Height,
refresh_num: mode.RefreshRate.Numerator,
refresh_den: mode.RefreshRate.Denominator,
hdr,
..Default::default()
})
}
Ok(())
}
}
unsafe impl Send for Display {}
unsafe impl Sync for Display {}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Default)]
pub enum DisplayOrientation {
#[default]
NoRotation,
Rotate90,
Rotate180,
Rotate270,
}
impl From<DEVMODE_DISPLAY_ORIENTATION> for DisplayOrientation {
fn from(i: DEVMODE_DISPLAY_ORIENTATION) -> Self {
match i.0 {
1 => Self::Rotate90,
2 => Self::Rotate180,
3 => Self::Rotate270,
_ => Self::NoRotation,
}
}
}
impl From<DisplayOrientation> for DEVMODE_DISPLAY_ORIENTATION {
fn from(i: DisplayOrientation) -> Self {
DEVMODE_DISPLAY_ORIENTATION(match i {
DisplayOrientation::NoRotation => { 0 }
DisplayOrientation::Rotate90 => { 1 }
DisplayOrientation::Rotate180 => { 2 }
DisplayOrientation::Rotate270 => { 3 }
})
}
}
#[repr(C)]
#[derive(Clone, Default, Debug)]
pub struct DisplayMode {
pub width: u32,
pub height: u32,
pub orientation: DisplayOrientation,
pub refresh_num: u32,
pub refresh_den: u32,
pub hdr: bool,
}
pub struct DisplayVSyncStream {
sync_rx: tokio::sync::mpsc::Receiver<Result<(), DDApiError>>,
thread_handle: Option<Box<JoinHandle<()>>>,
}
unsafe impl Send for DisplayVSyncStream {}
unsafe impl Sync for DisplayVSyncStream {}
impl DisplayVSyncStream {
pub fn new(output: Display) -> Self {
let (sync_tx, sync_rx) = tokio::sync::mpsc::channel::<Result<(), DDApiError>>(1);
let thread_handle = spawn(move || {
let output = output;
loop {
let mut out = Ok(());
let res = unsafe { output.0.WaitForVBlank() };
sleep(Duration::from_millis(6));
if let Err(e) = res {
out = Err(DDApiError::Unexpected(format!("{:?}", e)));
}
let err = sync_tx.blocking_send(out);
if err.is_err() {
trace!("exiting display sync wait thread");
return;
}
}
});
Self {
sync_rx,
thread_handle: Some(Box::new(thread_handle)),
}
}
}
impl Stream for DisplayVSyncStream {
type Item = Result<(), DDApiError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.sync_rx.poll_recv(cx)
}
}