use std::{
cell::RefCell,
rc::Rc,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
};
use cranpose_render_wgpu::DeviceLostHandler;
use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
use super::{App, request_primary_device};
use crate::app_launcher::LaunchError;
const MAX_RECOVERIES: u32 = 3;
pub(super) struct DeviceLoss {
lost: Arc<AtomicU64>,
generation: u64,
recoveries: u32,
content: Option<Rc<RefCell<crate::GpuContext>>>,
}
impl Default for DeviceLoss {
fn default() -> Self {
Self {
lost: Arc::default(),
generation: 1,
recoveries: 0,
content: None,
}
}
}
impl DeviceLoss {
pub(super) fn handler(&self, proxy: &EventLoopProxy) -> DeviceLostHandler {
let (lost, proxy, generation) = (Arc::clone(&self.lost), proxy.clone(), self.generation);
Arc::new(move || {
lost.store(generation, Ordering::SeqCst);
proxy.wake_up();
})
}
pub(super) fn share(
&mut self,
gpu: crate::GpuContext,
) -> impl Fn() -> crate::GpuContext + 'static {
let shared = Rc::new(RefCell::new(gpu));
self.content = Some(Rc::clone(&shared));
move || shared.borrow().clone()
}
pub(super) fn presented(&mut self) {
self.recoveries = 0;
}
fn take_loss(&self) -> bool {
self.lost.swap(0, Ordering::SeqCst) == self.generation
}
}
#[cfg(feature = "robot")]
pub(super) fn lose_device(gpu: Option<&crate::GpuContext>) {
if let Some(gpu) = gpu {
gpu.device.destroy();
let _ = gpu.device.poll(wgpu::PollType::Poll);
}
}
impl App {
pub(super) fn recover_lost_device(&mut self, event_loop: &dyn ActiveEventLoop) {
if !self.device_loss.take_loss() || self.exiting {
return;
}
if self.device_loss.recoveries >= MAX_RECOVERIES {
self.abort_launch(event_loop, LaunchError::GpuDeviceLost);
return;
}
self.device_loss.recoveries += 1;
if let Err(error) = self.replace_device() {
self.abort_launch(event_loop, error);
}
}
fn replace_device(&mut self) -> Result<(), LaunchError> {
let (Some(context), Some(app)) = (self.gpu_context.as_mut(), self.app.as_mut()) else {
return Ok(());
};
let requirements = self.settings.gpu_requirements;
let (adapter, (device, queue)) =
match request_primary_device(&context.gpu.adapter, requirements) {
Ok(opened) => (context.gpu.adapter.clone(), opened),
Err(_) => {
let adapter = crate::wgpu_surface::request_adapter(
&context.gpu.instance,
self.surface.as_ref(),
)
.map_err(LaunchError::NoAdapter)?;
let opened = request_primary_device(&adapter, requirements)
.map_err(LaunchError::DeviceCreate)?;
(adapter, opened)
}
};
log::warn!("[gpu-device] opened a new device after the last one was lost");
let backend = adapter.get_info().backend;
let downlevel = adapter.get_downlevel_capabilities().flags;
context.gpu.adapter = adapter;
context.gpu.device = Arc::new(device);
context.gpu.queue = Arc::new(queue);
context.adapter_backend = backend;
self.device_loss.generation += 1;
let handler = self.device_loss.handler(&self.event_proxy);
let gpu = &context.gpu;
let hand_over = |renderer: &mut cranpose_render_wgpu::WgpuRenderer, format| {
renderer.set_device_lost_handler(Arc::clone(&handler));
renderer.init_gpu(
Arc::clone(&gpu.device),
Arc::clone(&gpu.queue),
crate::surface_format::display_surface_view_format(format),
backend,
downlevel,
);
};
if let (Some(surface), Some(config)) = (&self.surface, &self.surface_config) {
surface.configure(&gpu.device, config);
hand_over(app.renderer(), config.format);
}
for native in self.native_windows.values() {
native
.surface
.configure(&gpu.device, &native.surface_config);
if let Some(mut surface) = super::native_surface(app, native) {
hand_over(surface.renderer(), native.surface_config.format);
}
}
if let Some(content) = &self.device_loss.content {
*content.borrow_mut() = gpu.clone();
}
app.request_root_render();
if let Some(window) = &self.window {
window.request_redraw();
}
Ok(())
}
}