Skip to content

Commit a42edb3

Browse files
Merge pull request #1661 from CapSoftware/misc-fixes-2
GPU context cleanup, exit guards, and error handling fixes
2 parents 83699f2 + acc8ada commit a42edb3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+580
-237
lines changed

apps/desktop/src-tauri/src/gpu_context.rs

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -49,31 +49,7 @@ pub struct SharedGpuContext {
4949
static GPU: OnceCell<Option<SharedGpuContext>> = OnceCell::const_new();
5050

5151
async fn init_gpu_inner() -> Option<SharedGpuContext> {
52-
#[cfg(not(target_os = "windows"))]
53-
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
54-
55-
#[cfg(target_os = "windows")]
56-
let instance = {
57-
let dx12_instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
58-
backends: wgpu::Backends::DX12,
59-
..Default::default()
60-
});
61-
let has_dx12 = dx12_instance
62-
.request_adapter(&wgpu::RequestAdapterOptions {
63-
power_preference: wgpu::PowerPreference::HighPerformance,
64-
force_fallback_adapter: false,
65-
compatible_surface: None,
66-
})
67-
.await
68-
.is_ok();
69-
if has_dx12 {
70-
tracing::info!("Using DX12 backend for shared GPU context");
71-
dx12_instance
72-
} else {
73-
tracing::info!("DX12 not available for shared context, falling back to all backends");
74-
wgpu::Instance::new(&wgpu::InstanceDescriptor::default())
75-
}
76-
};
52+
let instance = cap_rendering::create_wgpu_instance().await;
7753

7854
let hardware_adapter = instance
7955
.request_adapter(&wgpu::RequestAdapterOptions {
@@ -85,12 +61,26 @@ async fn init_gpu_inner() -> Option<SharedGpuContext> {
8561
.ok();
8662

8763
let (adapter, is_software_adapter) = if let Some(adapter) = hardware_adapter {
88-
tracing::info!(
89-
adapter_name = adapter.get_info().name,
90-
adapter_backend = ?adapter.get_info().backend,
91-
"Using hardware GPU adapter for shared context"
92-
);
93-
(adapter, false)
64+
let adapter_info = adapter.get_info();
65+
let is_software_adapter = cap_rendering::is_software_wgpu_adapter(&adapter_info);
66+
67+
if is_software_adapter {
68+
tracing::warn!(
69+
adapter_name = adapter_info.name,
70+
adapter_backend = ?adapter_info.backend,
71+
adapter_device_type = ?adapter_info.device_type,
72+
"Selected shared-context adapter behaves like a software renderer"
73+
);
74+
} else {
75+
tracing::info!(
76+
adapter_name = adapter_info.name,
77+
adapter_backend = ?adapter_info.backend,
78+
adapter_device_type = ?adapter_info.device_type,
79+
"Using hardware GPU adapter for shared context"
80+
);
81+
}
82+
83+
(adapter, is_software_adapter)
9484
} else {
9585
tracing::warn!(
9686
"No hardware GPU adapter found, attempting software fallback for shared context"
@@ -104,9 +94,12 @@ async fn init_gpu_inner() -> Option<SharedGpuContext> {
10494
.await
10595
.ok()?;
10696

97+
let adapter_info = software_adapter.get_info();
98+
10799
tracing::info!(
108-
adapter_name = software_adapter.get_info().name,
109-
adapter_backend = ?software_adapter.get_info().backend,
100+
adapter_name = adapter_info.name,
101+
adapter_backend = ?adapter_info.backend,
102+
adapter_device_type = ?adapter_info.device_type,
110103
"Using software adapter for shared context (CPU rendering - performance may be reduced)"
111104
);
112105
(software_adapter, true)

apps/desktop/src-tauri/src/lib.rs

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,13 @@ fn spawn_exit_watchdog() {
194194
});
195195
}
196196

197+
fn app_is_exiting(app: &AppHandle) -> bool {
198+
match app.try_state::<AppExitState>() {
199+
Some(state) => state.is_exiting(),
200+
None => false,
201+
}
202+
}
203+
197204
fn now_millis() -> u64 {
198205
SystemTime::now()
199206
.duration_since(UNIX_EPOCH)
@@ -799,6 +806,10 @@ fn spawn_mic_error_handler(app_handle: AppHandle, error_rx: flume::Receiver<Stre
799806
let error_rx = error_rx;
800807

801808
while let Ok(err) = error_rx.recv_async().await {
809+
if app_is_exiting(&app_handle) {
810+
break;
811+
}
812+
802813
error!("Mic feed actor error: {err}");
803814

804815
{
@@ -873,6 +884,10 @@ fn spawn_devices_snapshot_emitter(app_handle: AppHandle) {
873884
let mut last_mics: Vec<String> = Vec::new();
874885
let mut fast_loops = 0u32;
875886
loop {
887+
if app_is_exiting(&app_handle) {
888+
break;
889+
}
890+
876891
let permissions = permissions::do_permissions_check(false);
877892
let cameras = if permissions.camera.permitted() {
878893
cap_camera::list_cameras().collect::<Vec<_>>()
@@ -935,6 +950,10 @@ fn spawn_devices_snapshot_emitter(app_handle: AppHandle) {
935950
};
936951
fast_loops = fast_loops.saturating_add(1);
937952
tokio::time::sleep(dur).await;
953+
954+
if app_is_exiting(&app_handle) {
955+
break;
956+
}
938957
}
939958
});
940959
}
@@ -1128,6 +1147,10 @@ fn spawn_microphone_watcher(app_handle: AppHandle) {
11281147
let state = state.inner().clone();
11291148

11301149
loop {
1150+
if app_is_exiting(&app_handle) {
1151+
break;
1152+
}
1153+
11311154
let (should_check, label, is_marked) = {
11321155
let guard = state.read().await;
11331156
(
@@ -1185,6 +1208,10 @@ fn spawn_camera_watcher(app_handle: AppHandle) {
11851208
let state = state.inner().clone();
11861209

11871210
loop {
1211+
if app_is_exiting(&app_handle) {
1212+
break;
1213+
}
1214+
11881215
let (should_check, camera_id, is_marked) = {
11891216
let guard = state.read().await;
11901217
(
@@ -1808,7 +1835,9 @@ struct SerializedEditorInstance {
18081835
#[specta::specta]
18091836
#[instrument(skip(window))]
18101837
async fn create_editor_instance(window: Window) -> Result<SerializedEditorInstance, String> {
1811-
let CapWindowId::Editor { id } = CapWindowId::from_str(window.label()).unwrap() else {
1838+
let CapWindowId::Editor { id } =
1839+
CapWindowId::from_str(window.label()).map_err(|e| e.to_string())?
1840+
else {
18121841
return Err("Invalid window".to_string());
18131842
};
18141843

@@ -1844,7 +1873,9 @@ async fn create_editor_instance(window: Window) -> Result<SerializedEditorInstan
18441873
#[specta::specta]
18451874
#[instrument(skip(window))]
18461875
async fn get_editor_project_path(window: Window) -> Result<PathBuf, String> {
1847-
let CapWindowId::Editor { id } = CapWindowId::from_str(window.label()).unwrap() else {
1876+
let CapWindowId::Editor { id } =
1877+
CapWindowId::from_str(window.label()).map_err(|e| e.to_string())?
1878+
else {
18481879
return Err("Invalid window".to_string());
18491880
};
18501881

@@ -3521,6 +3552,16 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
35213552
let label = window.label();
35223553
let app = window.app_handle();
35233554

3555+
if matches!(
3556+
event,
3557+
WindowEvent::CloseRequested { .. }
3558+
| WindowEvent::Moved(_)
3559+
| WindowEvent::Focused(_)
3560+
) && app_is_exiting(app)
3561+
{
3562+
return;
3563+
}
3564+
35243565
match event {
35253566
WindowEvent::CloseRequested { api, .. } => {
35263567
if let Ok(window_id) = CapWindowId::from_str(label) {
@@ -3582,6 +3623,9 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
35823623
}
35833624
}
35843625
WindowEvent::Destroyed => {
3626+
if app_is_exiting(app) {
3627+
return;
3628+
}
35853629
if let Ok(window_id) = CapWindowId::from_str(label) {
35863630
if matches!(window_id, CapWindowId::Camera) {
35873631
tracing::warn!("Camera window Destroyed event received!");
@@ -3702,10 +3746,11 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
37023746

37033747
if let Some(settings) = GeneralSettingsStore::get(app).unwrap_or(None)
37043748
&& settings.hide_dock_icon
3705-
&& app
3706-
.webview_windows()
3707-
.keys()
3708-
.all(|label| !CapWindowId::from_str(label).unwrap().activates_dock())
3749+
&& app.webview_windows().keys().all(|label| {
3750+
CapWindowId::from_str(label)
3751+
.map(|id| !id.activates_dock())
3752+
.unwrap_or(false)
3753+
})
37093754
{
37103755
#[cfg(target_os = "macos")]
37113756
app.set_activation_policy(tauri::ActivationPolicy::Accessory)
@@ -4032,17 +4077,16 @@ async fn create_editor_instance_impl(
40324077

40334078
wait_for_recording_ready(&app, &path).await?;
40344079

4035-
let shared_device = if let Some(shared) = gpu_context::get_shared_gpu().await {
4036-
Some(cap_rendering::SharedWgpuDevice {
4037-
instance: (*shared.instance).clone(),
4038-
adapter: (*shared.adapter).clone(),
4039-
device: (*shared.device).clone(),
4040-
queue: (*shared.queue).clone(),
4041-
is_software_adapter: shared.is_software_adapter,
4042-
})
4043-
} else {
4044-
None
4045-
};
4080+
let shared_device =
4081+
gpu_context::get_shared_gpu()
4082+
.await
4083+
.map(|shared| cap_rendering::SharedWgpuDevice {
4084+
instance: (*shared.instance).clone(),
4085+
adapter: (*shared.adapter).clone(),
4086+
device: (*shared.device).clone(),
4087+
queue: (*shared.queue).clone(),
4088+
is_software_adapter: shared.is_software_adapter,
4089+
});
40464090

40474091
let instance = {
40484092
let app = app.clone();

apps/desktop/src-tauri/src/screenshot_editor.rs

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -249,62 +249,61 @@ impl ScreenshotEditorInstances {
249249
}
250250
};
251251

252-
let (instance, adapter, device, queue, is_software_adapter) =
253-
if let Some(shared) = gpu_context::get_shared_gpu().await {
254-
(
255-
shared.instance.clone(),
256-
shared.adapter.clone(),
257-
shared.device.clone(),
258-
shared.queue.clone(),
259-
shared.is_software_adapter,
260-
)
261-
} else {
262-
let instance =
263-
Arc::new(wgpu::Instance::new(&wgpu::InstanceDescriptor::default()));
264-
let adapter = Arc::new(
265-
instance
266-
.request_adapter(&wgpu::RequestAdapterOptions {
267-
power_preference: wgpu::PowerPreference::HighPerformance,
268-
force_fallback_adapter: false,
269-
compatible_surface: None,
270-
})
271-
.await
272-
.map_err(|_| "No GPU adapter found".to_string())?,
273-
);
274-
275-
let (device, queue) = adapter
276-
.request_device(&wgpu::DeviceDescriptor {
277-
label: Some("cap-rendering-device"),
278-
required_features: wgpu::Features::empty(),
279-
..Default::default()
280-
})
281-
.await
282-
.map_err(|e| e.to_string())?;
283-
(instance, adapter, Arc::new(device), Arc::new(queue), false)
284-
};
252+
let shared = if let Some(gpu) = gpu_context::get_shared_gpu().await {
253+
cap_rendering::SharedWgpuDevice {
254+
instance: (*gpu.instance).clone(),
255+
adapter: (*gpu.adapter).clone(),
256+
device: (*gpu.device).clone(),
257+
queue: (*gpu.queue).clone(),
258+
is_software_adapter: gpu.is_software_adapter,
259+
}
260+
} else {
261+
let instance = cap_rendering::create_wgpu_instance().await;
262+
let adapter = instance
263+
.request_adapter(&wgpu::RequestAdapterOptions {
264+
power_preference: wgpu::PowerPreference::HighPerformance,
265+
force_fallback_adapter: false,
266+
compatible_surface: None,
267+
})
268+
.await
269+
.map_err(|_| "No GPU adapter found".to_string())?;
270+
let adapter_info = adapter.get_info();
271+
let is_software_adapter =
272+
cap_rendering::is_software_wgpu_adapter(&adapter_info);
273+
274+
let (device, queue) = adapter
275+
.request_device(&wgpu::DeviceDescriptor {
276+
label: Some("cap-rendering-device"),
277+
required_features: wgpu::Features::empty(),
278+
..Default::default()
279+
})
280+
.await
281+
.map_err(|e| e.to_string())?;
282+
cap_rendering::SharedWgpuDevice {
283+
instance,
284+
adapter,
285+
device,
286+
queue,
287+
is_software_adapter,
288+
}
289+
};
285290

286291
let options = cap_rendering::RenderOptions {
287292
screen_size: cap_project::XY::new(width, height),
288293
camera_size: None,
289294
};
290295

291-
// We need to extract the studio meta from the recording meta
292296
let studio_meta = match &recording_meta.inner {
293297
RecordingMetaInner::Studio(meta) => meta.clone(),
294298
_ => return Err("Invalid recording meta for screenshot".to_string()),
295299
};
296300

297-
let constants = RenderVideoConstants {
298-
_instance: (*instance).clone(),
299-
_adapter: (*adapter).clone(),
300-
queue: (*queue).clone(),
301-
device: (*device).clone(),
301+
let constants = RenderVideoConstants::from_shared_device(
302+
shared,
302303
options,
303-
meta: *studio_meta,
304-
recording_meta: recording_meta.clone(),
305-
background_textures: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
306-
is_software_adapter,
307-
};
304+
*studio_meta,
305+
recording_meta.clone(),
306+
);
308307

309308
let (config_tx, mut config_rx) = watch::channel(loaded_config.unwrap_or_default());
310309

apps/desktop/src-tauri/src/tray.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,10 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> {
789789
});
790790
}
791791
Ok(TrayItem::Quit) => {
792-
app.exit(0);
792+
let app = app.clone();
793+
tokio::spawn(async move {
794+
crate::request_app_exit(app).await;
795+
});
793796
}
794797
Ok(TrayItem::PreviousItem(path)) => {
795798
handle_previous_item_click(app, &path);

0 commit comments

Comments
 (0)