use std::collections::HashMap;
use std::mem;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::Arc;
use std::time::{Duration, Instant};
use imgui::Context;
use once_cell::sync::{Lazy, OnceCell};
use parking_lot::Mutex;
use tracing::{error, warn};
use windows::core::{Error, Result};
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
use windows::Win32::UI::HiDpi::GetDpiForWindow;
use windows::Win32::UI::WindowsAndMessaging::{
CallWindowProcW, DefWindowProcW, SetWindowLongPtrW, GWLP_WNDPROC,
};
use crate::renderer::input::{imgui_wnd_proc_impl, WndProcType};
use crate::renderer::RenderEngine;
use crate::{util, ImguiRenderLoop, MessageFilter};
type RenderLoop = Box<dyn ImguiRenderLoop + Send + Sync>;
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub(crate) struct SendableHwnd(HWND);
unsafe impl Send for SendableHwnd {}
unsafe impl Sync for SendableHwnd {}
static PIPELINE_STATES: Lazy<Mutex<HashMap<usize, Arc<PipelineSharedState>>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
#[derive(Debug)]
pub(crate) struct PipelineMessage(
pub(crate) SendableHwnd,
pub(crate) u32,
pub(crate) WPARAM,
pub(crate) LPARAM,
);
pub(crate) struct PipelineSharedState {
pub(crate) message_filter: AtomicU32,
pub(crate) wnd_proc: WndProcType,
pub(crate) tx: Sender<PipelineMessage>,
}
pub(crate) struct Pipeline<T: RenderEngine> {
hwnd: HWND,
ctx: Context,
engine: T,
render_loop: RenderLoop,
rx: Receiver<PipelineMessage>,
shared_state: Arc<PipelineSharedState>,
queue_buffer: OnceCell<Vec<PipelineMessage>>,
start_of_first_frame: OnceCell<Instant>,
}
impl<T: RenderEngine> Pipeline<T> {
pub(crate) fn new(
hwnd: HWND,
mut ctx: Context,
mut engine: T,
mut render_loop: RenderLoop,
) -> std::result::Result<Self, (Error, RenderLoop)> {
let (width, height) = util::win_size(hwnd);
ctx.io_mut().display_size = [width as f32, height as f32];
render_loop.initialize(&mut ctx, &mut engine);
if let Err(e) = engine.setup_fonts(&mut ctx) {
return Err((e, render_loop));
}
let wnd_proc = unsafe {
#[cfg(target_arch = "x86")]
type SwlpRet = i32;
#[cfg(target_arch = "x86_64")]
type SwlpRet = isize;
mem::transmute::<SwlpRet, WndProcType>(SetWindowLongPtrW(
hwnd,
GWLP_WNDPROC,
pipeline_wnd_proc as *const () as _,
))
};
let (tx, rx) = mpsc::channel();
let shared_state = Arc::new(PipelineSharedState {
message_filter: AtomicU32::new(MessageFilter::empty().bits()),
wnd_proc,
tx,
});
PIPELINE_STATES.lock().insert(hwnd.0 as usize, Arc::clone(&shared_state));
let queue_buffer = OnceCell::from(Vec::new());
Ok(Self {
hwnd,
ctx,
engine,
render_loop,
rx,
shared_state: Arc::clone(&shared_state),
queue_buffer,
start_of_first_frame: OnceCell::new(),
})
}
pub(crate) fn prepare_render(&mut self) -> Result<()> {
let mut queue_buffer = self.queue_buffer.take().unwrap();
queue_buffer.clear();
queue_buffer.extend(self.rx.try_iter());
queue_buffer.drain(..).for_each(
|PipelineMessage(SendableHwnd(hwnd), umsg, wparam, lparam)| {
imgui_wnd_proc_impl(hwnd, umsg, wparam, lparam, self);
},
);
self.queue_buffer.set(queue_buffer).expect("OnceCell should be empty");
let message_filter = self.render_loop.message_filter(self.ctx.io());
self.shared_state.message_filter.store(message_filter.bits(), Ordering::SeqCst);
let io = self.ctx.io_mut();
io.nav_active = true;
io.nav_visible = true;
self.render_loop.before_render(&mut self.ctx, &mut self.engine);
Ok(())
}
pub(crate) fn render(&mut self, render_target: T::RenderTarget) -> Result<()> {
let delta_time = Instant::now()
.checked_duration_since(*self.start_of_first_frame.get_or_init(Instant::now))
.unwrap_or(Duration::ZERO)
.checked_sub(Duration::from_secs_f64(self.ctx.time()))
.unwrap_or(Duration::ZERO);
self.ctx.io_mut().update_delta_time(delta_time);
let [w, h] = self.ctx.io().display_size;
let [fsw, fsh] = self.ctx.io().display_framebuffer_scale;
if (w * fsw) <= 0.0 || (h * fsh) <= 0.0 {
warn!(
"Insufficient display size: {w}x{h}, framebuffer_scale: {fsw}x{fsh}; skipping \
frame"
);
return Ok(());
}
let ui = self.ctx.frame();
self.render_loop.render(ui);
let draw_data = self.ctx.render();
self.engine.render(draw_data, render_target)?;
Ok(())
}
pub(crate) fn context(&mut self) -> &mut Context {
&mut self.ctx
}
pub(crate) fn render_loop(&mut self) -> &mut RenderLoop {
&mut self.render_loop
}
pub(crate) fn resize(&mut self, width: u32, height: u32) {
if width > 0 && height > 0 {
self.ctx.io_mut().display_size = [width as f32, height as f32];
}
}
pub(crate) fn update_display_size_from_swap_chain(&mut self, width: u32, height: u32) {
if width > 0 && height > 0 {
let io = self.ctx.io_mut();
io.display_size = [width as f32, height as f32];
let dpi = unsafe { GetDpiForWindow(self.hwnd) };
if dpi > 0 {
let scale = dpi as f32 / 96.0;
io.display_framebuffer_scale = [scale, scale];
}
}
}
pub(crate) fn cleanup(&mut self) {
unsafe {
SetWindowLongPtrW(self.hwnd, GWLP_WNDPROC, self.shared_state.wnd_proc as usize as _)
};
PIPELINE_STATES.lock().remove(&(self.hwnd.0 as usize));
}
pub(crate) fn take(mut self) -> RenderLoop {
self.cleanup();
self.render_loop
}
}
unsafe extern "system" fn pipeline_wnd_proc(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
let shared_state = {
let Some(shared_state_guard) = PIPELINE_STATES.try_lock() else {
error!("Could not lock shared state in window procedure");
return DefWindowProcW(hwnd, msg, wparam, lparam);
};
let Some(shared_state) = shared_state_guard.get(&(hwnd.0 as usize)) else {
error!("Could not get shared state for handle {hwnd:?}");
return DefWindowProcW(hwnd, msg, wparam, lparam);
};
Arc::clone(shared_state)
};
if let Err(e) = shared_state.tx.send(PipelineMessage(SendableHwnd(hwnd), msg, wparam, lparam)) {
error!("Could not send window message through pipeline: {e:?}");
}
let message_filter =
MessageFilter::from_bits_retain(shared_state.message_filter.load(Ordering::SeqCst));
if message_filter.is_blocking(msg) {
LRESULT(1)
} else {
CallWindowProcW(Some(shared_state.wnd_proc), hwnd, msg, wparam, lparam)
}
}