use std::time::{Duration, Instant};
use tracing::warn;
use windows::System::DispatcherQueueController;
use windows::UI::Composition::CompositionGraphicsDevice;
use windows::UI::Composition::Desktop::DesktopWindowTarget;
use windows::UI::Composition::ICompositionSurface;
use windows::Win32::Foundation::{HWND, POINT, RECT};
use windows::Win32::Graphics::Direct2D::{ID2D1Device, ID2D1DeviceContext, ID2D1DeviceContext3};
use windows::Win32::Graphics::Dxgi::IDXGISwapChain1;
use windows::Win32::System::WinRT::Composition::{
ICompositionDrawingSurfaceInterop, ICompositorDesktopInterop, ICompositorInterop,
};
use windows::Win32::System::WinRT::{
CreateDispatcherQueueController, DISPATCHERQUEUE_THREAD_APARTMENTTYPE, DQTYPE_THREAD_CURRENT,
DispatcherQueueOptions,
};
use windows::Win32::UI::WindowsAndMessaging::{
DispatchMessageW, MSG, PM_REMOVE, PeekMessageW, TranslateMessage, WM_QUIT,
};
use windows::core::{Interface, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DrainStep {
Completed,
TimedOut,
Continue,
}
#[inline]
pub(crate) fn drain_step(still_started: bool, elapsed: Duration, timeout: Duration) -> DrainStep {
if !still_started {
DrainStep::Completed
} else if elapsed >= timeout {
DrainStep::TimedOut
} else {
DrainStep::Continue
}
}
pub fn drain_dispatcher_queue(controller: &DispatcherQueueController, timeout: Duration) -> bool {
let action = match controller.ShutdownQueueAsync() {
Ok(a) => a,
Err(e) => {
warn!("[drain_dispatcher_queue] ShutdownQueueAsync 発行失敗(継続): {e:?}");
return false;
}
};
let start = Instant::now();
loop {
pump_current_thread_messages();
let still_started = action.Status().map(|s| s.0 == 0).unwrap_or(false);
match drain_step(still_started, start.elapsed(), timeout) {
DrainStep::Completed => return true,
DrainStep::TimedOut => {
warn!(
"[drain_dispatcher_queue] ドレインが timeout({:?})を超過。保険で抜ける",
timeout
);
return false;
}
DrainStep::Continue => std::thread::sleep(Duration::from_millis(5)),
}
}
}
#[inline]
fn pump_current_thread_messages() {
unsafe {
let mut msg = MSG::default();
while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
if msg.message == WM_QUIT {
return;
}
let _ = TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
}
pub trait CompositorInteropExt {
fn create_graphics_device(&self, d2d_device: &ID2D1Device)
-> Result<CompositionGraphicsDevice>;
fn create_composition_surface_for_swap_chain(
&self,
swapchain: &IDXGISwapChain1,
) -> Result<ICompositionSurface>;
}
impl CompositorInteropExt for ICompositorInterop {
#[inline(always)]
fn create_graphics_device(
&self,
d2d_device: &ID2D1Device,
) -> Result<CompositionGraphicsDevice> {
unsafe { self.CreateGraphicsDevice(d2d_device) }
}
#[inline(always)]
fn create_composition_surface_for_swap_chain(
&self,
swapchain: &IDXGISwapChain1,
) -> Result<ICompositionSurface> {
unsafe { self.CreateCompositionSurfaceForSwapChain(swapchain) }
}
}
pub trait CompositorDesktopInteropExt {
fn create_desktop_window_target(
&self,
hwnd: HWND,
topmost: bool,
) -> Result<DesktopWindowTarget>;
}
impl CompositorDesktopInteropExt for ICompositorDesktopInterop {
#[inline(always)]
fn create_desktop_window_target(
&self,
hwnd: HWND,
topmost: bool,
) -> Result<DesktopWindowTarget> {
unsafe { self.CreateDesktopWindowTarget(hwnd, topmost) }
}
}
pub trait DrawingSurfaceInteropExt {
fn begin_draw(&self, update_rect: Option<&RECT>) -> Result<(ID2D1DeviceContext3, POINT)>;
fn end_draw(&self) -> Result<()>;
}
impl DrawingSurfaceInteropExt for ICompositionDrawingSurfaceInterop {
#[inline(always)]
fn begin_draw(&self, update_rect: Option<&RECT>) -> Result<(ID2D1DeviceContext3, POINT)> {
let update_rect = update_rect.map(|r| r as *const _);
let mut update_offset = POINT::default();
let dc: ID2D1DeviceContext = unsafe { self.BeginDraw(update_rect, &mut update_offset)? };
Ok((dc.cast()?, update_offset))
}
#[inline(always)]
fn end_draw(&self) -> Result<()> {
unsafe { self.EndDraw() }
}
}
#[inline(always)]
pub fn create_dispatcher_queue_controller(
apartment: DISPATCHERQUEUE_THREAD_APARTMENTTYPE,
) -> Result<DispatcherQueueController> {
let options = DispatcherQueueOptions {
dwSize: core::mem::size_of::<DispatcherQueueOptions>() as u32,
threadType: DQTYPE_THREAD_CURRENT,
apartmentType: apartment,
};
unsafe { CreateDispatcherQueueController(options) }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ecs::GraphicsCore;
use windows::Foundation::Size;
use windows::Graphics::DirectX::{DirectXAlphaMode, DirectXPixelFormat};
use windows::UI::Composition::{CompositionDrawingSurface, Compositor};
use windows::Win32::System::WinRT::{DQTAT_COM_ASTA, DQTAT_COM_NONE};
#[test]
fn drain_step_covers_completed_timeout_and_continue() {
assert_eq!(
drain_step(false, Duration::from_millis(0), Duration::from_secs(2)),
DrainStep::Completed
);
assert_eq!(
drain_step(true, Duration::from_millis(10), Duration::from_secs(2)),
DrainStep::Continue
);
assert_eq!(
drain_step(true, Duration::from_secs(2), Duration::from_secs(2)),
DrainStep::TimedOut
);
assert_eq!(
drain_step(true, Duration::from_nanos(1), Duration::ZERO),
DrainStep::TimedOut
);
}
#[test]
fn drain_dispatcher_queue_drains_live_controller_without_panic() {
let controller = create_dispatcher_queue_controller(DQTAT_COM_ASTA)
.or_else(|e| create_dispatcher_queue_controller(DQTAT_COM_NONE).map_err(|_| e))
.expect("DispatcherQueueController 生成失敗(ASTA/NONE いずれも不可)");
let _compositor = Compositor::new().expect("Compositor::new 失敗");
let drained = drain_dispatcher_queue(&controller, Duration::from_secs(2));
assert!(
drained,
"空に近いキューは十分な timeout 内で drain 完了するはず"
);
let _second = drain_dispatcher_queue(&controller, Duration::from_millis(50));
}
#[test]
fn begin_draw_roundtrip() {
let dq = create_dispatcher_queue_controller(DQTAT_COM_ASTA)
.or_else(|e_asta| {
create_dispatcher_queue_controller(DQTAT_COM_NONE).map_err(|_| e_asta)
})
.expect("DispatcherQueueController 生成失敗(ASTA/NONE いずれも不可)");
let _dq = dq;
let compositor = Compositor::new().expect("Compositor::new 失敗");
let core = GraphicsCore::new().expect("GraphicsCore::new 失敗(HARDWARE デバイス生成)");
let d2d_device = core.d2d_device().expect("d2d_device が None");
let interop = compositor
.cast::<ICompositorInterop>()
.expect("ICompositorInterop へ cast 失敗");
let graphics_device = interop
.create_graphics_device(d2d_device)
.expect("create_graphics_device 失敗");
let size = Size {
Width: 256.0,
Height: 256.0,
};
let surface: CompositionDrawingSurface = graphics_device
.CreateDrawingSurface(
size,
DirectXPixelFormat::B8G8R8A8UIntNormalized,
DirectXAlphaMode::Premultiplied,
)
.expect("CreateDrawingSurface 失敗");
let surface_interop = surface
.cast::<ICompositionDrawingSurfaceInterop>()
.expect("ICompositionDrawingSurfaceInterop へ cast 失敗");
let (dc, offset) = surface_interop
.begin_draw(None)
.expect("begin_draw(None) 失敗");
let mut transform = windows_numerics::Matrix3x2::default();
unsafe { dc.GetTransform(&mut transform) };
assert!(
offset.x >= 0 && offset.y >= 0,
"updateoffset が負値: ({}, {})",
offset.x,
offset.y
);
eprintln!(
"[begin_draw_roundtrip] atlas updateoffset = ({}, {})",
offset.x, offset.y
);
surface_interop.end_draw().expect("end_draw 失敗");
}
#[test]
fn create_composition_surface_for_swap_chain_roundtrip() {
use crate::com::dxgi::create_composition_swap_chain;
let dq = create_dispatcher_queue_controller(DQTAT_COM_ASTA)
.or_else(|e_asta| {
create_dispatcher_queue_controller(DQTAT_COM_NONE).map_err(|_| e_asta)
})
.expect("DispatcherQueueController 生成失敗(ASTA/NONE いずれも不可)");
let _dq = dq;
let compositor = Compositor::new().expect("Compositor::new 失敗");
let interop = compositor
.cast::<ICompositorInterop>()
.expect("ICompositorInterop へ cast 失敗");
let core = GraphicsCore::new().expect("GraphicsCore::new 失敗(HARDWARE デバイス生成)");
let d3d = core.d3d().expect("d3d が None");
let dxgi = core.dxgi().expect("dxgi が None");
let swapchain = create_composition_swap_chain(d3d, dxgi, 64, 48)
.expect("create_composition_swap_chain 失敗");
let surface = interop
.create_composition_surface_for_swap_chain(&swapchain)
.expect("create_composition_surface_for_swap_chain 失敗");
use windows::UI::Composition::ICompositionSurface;
let _typed: &ICompositionSurface = &surface;
}
}