#[cfg(target_os = "windows")]
use std::sync::Condvar;
use std::sync::atomic::AtomicBool;
#[cfg(target_os = "windows")]
use std::sync::atomic::{AtomicU8, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, PoisonError};
use iced_wgpu::wgpu;
pub(crate) type PumpInit<T> = (T, wgpu::Device, wgpu::SurfaceConfiguration);
pub(crate) type PumpInitFn<T> = Box<
dyn FnOnce(&wgpu::Instance, &wgpu::Adapter, &wgpu::Surface<'static>) -> Option<PumpInit<T>>
+ Send,
>;
#[cfg(target_os = "windows")]
const STATE_INIT: u8 = 0;
#[cfg(target_os = "windows")]
const STATE_READY: u8 = 1;
#[cfg(target_os = "windows")]
const STATE_FAILED: u8 = 2;
#[allow(clippy::struct_excessive_bools)]
#[cfg(target_os = "windows")]
#[derive(Default)]
struct Slot {
resize: Option<(u32, u32)>,
held: Option<wgpu::SurfaceTexture>,
want_frame: bool,
present: Option<wgpu::SurfaceTexture>,
taken: bool,
shutdown: bool,
exited: bool,
}
#[cfg(target_os = "windows")]
struct Shared {
slot: Mutex<Slot>,
cv: Condvar,
state: AtomicU8,
last_acquire_nanos: AtomicU64,
}
#[cfg(target_os = "windows")]
fn lock(slot: &Mutex<Slot>) -> std::sync::MutexGuard<'_, Slot> {
slot.lock().unwrap_or_else(PoisonError::into_inner)
}
#[cfg(not(target_os = "windows"))]
struct InlineState {
surface: wgpu::Surface<'static>,
device: wgpu::Device,
config: wgpu::SurfaceConfiguration,
last_acquire: std::time::Duration,
}
#[cfg(not(target_os = "windows"))]
fn lock_inline(state: &Mutex<InlineState>) -> std::sync::MutexGuard<'_, InlineState> {
state.lock().unwrap_or_else(PoisonError::into_inner)
}
#[derive(Clone)]
pub(crate) struct PumpClient {
#[cfg(target_os = "windows")]
shared: Arc<Shared>,
#[cfg(not(target_os = "windows"))]
state: Arc<Mutex<InlineState>>,
}
impl PumpClient {
pub(crate) fn resize(&self, phys_w: u32, phys_h: u32) {
#[cfg(target_os = "windows")]
{
let mut slot = lock(&self.shared.slot);
slot.resize = Some((phys_w, phys_h));
drop(slot);
self.shared.cv.notify_all();
}
#[cfg(not(target_os = "windows"))]
{
let mut state = lock_inline(&self.state);
state.config.width = phys_w.max(1);
state.config.height = phys_h.max(1);
let InlineState {
surface,
device,
config,
..
} = &mut *state;
surface.configure(device, config);
}
}
pub(crate) fn try_take_frame(&self) -> Option<wgpu::SurfaceTexture> {
#[cfg(target_os = "windows")]
{
let mut slot = lock(&self.shared.slot);
slot.want_frame = true;
let frame = slot.held.take();
if frame.is_some() {
slot.taken = true;
}
drop(slot);
self.shared.cv.notify_all();
frame
}
#[cfg(not(target_os = "windows"))]
{
let mut state = lock_inline(&self.state);
let acquire_start = std::time::Instant::now();
let mut acquired = None;
for _ in 0..2 {
match state.surface.get_current_texture() {
Ok(frame) => {
acquired = Some(frame);
break;
}
Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost) => {
let InlineState {
surface,
device,
config,
..
} = &mut *state;
surface.configure(device, config);
}
Err(e) => {
log::warn!("iced surface acquire error: {e}");
break;
}
}
}
state.last_acquire = acquire_start.elapsed();
acquired
}
}
#[allow(clippy::unused_self)]
pub(crate) fn present(&self, frame: wgpu::SurfaceTexture) {
#[cfg(target_os = "windows")]
{
let mut slot = lock(&self.shared.slot);
slot.present = Some(frame);
slot.taken = false;
drop(slot);
self.shared.cv.notify_all();
}
#[cfg(not(target_os = "windows"))]
{
frame.present();
}
}
#[allow(clippy::unused_self)]
pub(crate) fn discard(&self, frame: wgpu::SurfaceTexture) {
#[cfg(target_os = "windows")]
self.present(frame);
#[cfg(not(target_os = "windows"))]
drop(frame);
}
pub(crate) fn last_acquire_wait(&self) -> std::time::Duration {
#[cfg(target_os = "windows")]
{
std::time::Duration::from_nanos(self.shared.last_acquire_nanos.load(Ordering::Relaxed))
}
#[cfg(not(target_os = "windows"))]
{
lock_inline(&self.state).last_acquire
}
}
}
pub(crate) struct SurfacePump<T: Send + 'static> {
client: PumpClient,
init: InitDelivery<T>,
#[cfg(target_os = "windows")]
join: Option<std::thread::JoinHandle<()>>,
}
enum InitDelivery<T> {
#[cfg_attr(target_os = "windows", allow(dead_code))]
Now(Option<T>),
#[cfg(target_os = "windows")]
Chan(std::sync::mpsc::Receiver<T>),
}
impl<T: Send + 'static> SurfacePump<T> {
pub(crate) unsafe fn spawn(
window: &baseview::Window,
device_lost: &Arc<AtomicBool>,
init: PumpInitFn<T>,
) -> Option<Self> {
#[cfg(target_os = "windows")]
{
use raw_window_handle::HasRawWindowHandle;
let raw_window_handle::RawWindowHandle::Win32(handle) = window.raw_window_handle()
else {
return None;
};
let hwnd = handle.hwnd as isize;
if hwnd == 0 {
return None;
}
Self::spawn_threaded(hwnd, device_lost.clone(), init)
}
#[cfg(not(target_os = "windows"))]
{
let _ = device_lost;
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: crate::runtime::editor_backends(),
..Default::default()
});
let surface = unsafe { crate::platform::create_wgpu_surface(&instance, window) }?;
let adapter =
pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
}))
.ok()?;
let (product, device, config) = init(&instance, &adapter, &surface)?;
surface.configure(&device, &config);
Some(Self {
client: PumpClient {
state: Arc::new(Mutex::new(InlineState {
surface,
device,
config,
last_acquire: std::time::Duration::ZERO,
})),
},
init: InitDelivery::Now(Some(product)),
})
}
}
#[cfg(target_os = "windows")]
fn spawn_threaded(
hwnd: isize,
device_lost: Arc<AtomicBool>,
init: PumpInitFn<T>,
) -> Option<Self> {
let shared = Arc::new(Shared {
slot: Mutex::new(Slot::default()),
cv: Condvar::new(),
state: AtomicU8::new(STATE_INIT),
last_acquire_nanos: AtomicU64::new(0),
});
let (init_tx, init_rx) = std::sync::mpsc::channel();
let thread_shared = shared.clone();
let spawned = std::thread::Builder::new()
.name("truce-iced-pump".into())
.spawn(move || run(&thread_shared, hwnd, &device_lost, init, &init_tx));
match spawned {
Ok(join) => Some(Self {
client: PumpClient { shared },
init: InitDelivery::Chan(init_rx),
join: Some(join),
}),
Err(e) => {
log::error!("iced surface pump: failed to spawn: {e}");
None
}
}
}
pub(crate) fn take_init(&mut self) -> Option<T> {
match &mut self.init {
InitDelivery::Now(product) => product.take(),
#[cfg(target_os = "windows")]
InitDelivery::Chan(rx) => rx.try_recv().ok(),
}
}
pub(crate) fn client(&self) -> PumpClient {
self.client.clone()
}
}
#[cfg(target_os = "windows")]
impl<T: Send + 'static> Drop for SurfacePump<T> {
fn drop(&mut self) {
let mut slot = lock(&self.client.shared.slot);
slot.shutdown = true;
self.client.shared.cv.notify_all();
let (slot, timeout) = self
.client
.shared
.cv
.wait_timeout_while(slot, std::time::Duration::from_secs(1), |s| !s.exited)
.unwrap_or_else(PoisonError::into_inner);
drop(slot);
if timeout.timed_out() {
log::warn!("iced surface pump did not exit within 1s (driver stall?); detaching");
drop(self.join.take());
} else if let Some(join) = self.join.take() {
let _ = join.join();
}
}
}
#[cfg(target_os = "windows")]
unsafe fn surface_from_hwnd(
instance: &wgpu::Instance,
hwnd: isize,
) -> Option<wgpu::Surface<'static>> {
let mut win32 = wgpu::rwh::Win32WindowHandle::new(std::num::NonZeroIsize::new(hwnd)?);
win32.hinstance = crate::platform::current_module_hinstance();
let target = wgpu::SurfaceTargetUnsafe::RawHandle {
raw_display_handle: wgpu::rwh::RawDisplayHandle::Windows(
wgpu::rwh::WindowsDisplayHandle::new(),
),
raw_window_handle: wgpu::rwh::RawWindowHandle::Win32(win32),
};
unsafe { instance.create_surface_unsafe(target) }.ok()
}
#[cfg(target_os = "windows")]
fn run<T: Send>(
shared: &Shared,
hwnd: isize,
device_lost: &Arc<AtomicBool>,
init: PumpInitFn<T>,
init_tx: &std::sync::mpsc::Sender<T>,
) {
let built = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: crate::runtime::editor_backends(),
..Default::default()
});
let surface = unsafe { surface_from_hwnd(&instance, hwnd) }?;
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
}))
.ok()?;
let (product, device, config) = init(&instance, &adapter, &surface)?;
surface.configure(&device, &config);
Some((product, device, config, surface))
}))
.ok()
.flatten();
let Some((product, device, mut config, surface)) = built else {
shared.state.store(STATE_FAILED, Ordering::Release);
log::error!("iced surface pump: gpu init failed; editor stays blank");
mark_exited(shared);
return;
};
let _ = init_tx.send(product);
shared.state.store(STATE_READY, Ordering::Release);
'work: loop {
let (resize, present, need_acquire) = {
let mut slot = lock(&shared.slot);
loop {
if slot.shutdown {
break 'work;
}
let need_acquire = slot.want_frame && slot.held.is_none() && !slot.taken;
let can_resize = slot.resize.is_some() && !slot.taken;
if can_resize || slot.present.is_some() || need_acquire {
break;
}
slot = shared.cv.wait(slot).unwrap_or_else(PoisonError::into_inner);
}
let need_acquire = slot.want_frame && slot.held.is_none() && !slot.taken;
let resize = if slot.taken { None } else { slot.resize.take() };
(resize, slot.present.take(), need_acquire)
};
let ok = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if let Some(frame) = present {
frame.present();
}
if let Some((w, h)) = resize {
if let Some(stale) = lock(&shared.slot).held.take() {
stale.present();
}
config.width = w.max(1);
config.height = h.max(1);
surface.configure(&device, &config);
}
if need_acquire && lock(&shared.slot).resize.is_none() {
let acquire_start = std::time::Instant::now();
let mut acquired = None;
for _ in 0..2 {
match surface.get_current_texture() {
Ok(frame) => {
acquired = Some(frame);
break;
}
Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost) => {
surface.configure(&device, &config);
}
Err(e) => {
log::warn!("iced surface pump acquire error: {e}");
break;
}
}
}
let nanos = u64::try_from(acquire_start.elapsed().as_nanos()).unwrap_or(u64::MAX);
shared.last_acquire_nanos.store(nanos, Ordering::Relaxed);
if let Some(frame) = acquired {
let mut slot = lock(&shared.slot);
if slot.resize.is_none() {
slot.held = Some(frame);
} else {
drop(slot);
frame.present();
}
}
}
}));
if ok.is_err() {
device_lost.store(true, Ordering::Release);
shared.state.store(STATE_FAILED, Ordering::Release);
log::error!("iced surface pump panicked; flagging device loss for rebuild");
break;
}
}
mark_exited(shared);
}
#[cfg(target_os = "windows")]
fn mark_exited(shared: &Shared) {
let mut slot = lock(&shared.slot);
let held = slot.held.take();
let present = slot.present.take();
slot.exited = true;
drop(slot);
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
drop(held);
drop(present);
}));
shared.cv.notify_all();
}